Hooks 生命周期钩子

Hooks 让你在智能体每个步骤的前后插入自定义逻辑——日志记录、动态修改配置、提前终止循环、错误处理,实现对执行流程的精细控制。

智能体在执行过程中会经历多个步骤——模型推理、工具调用、结构化输出。Hooks 让你在这个流程的关键节点插入自定义逻辑,无需修改核心代码即可实现日志记录、动态配置、流程控制和错误处理。无论是追踪每一步的执行状态,还是根据运行时条件切换模型,Hooks 都提供了简洁而强大的扩展机制。

三个 Hook

deepseek-kit 提供三个生命周期 Hook,覆盖智能体执行的完整流程:

Hook触发时机典型用途
beforeStep每个步骤开始前日志记录、动态修改配置、提前终止
afterStep每个步骤完成后日志记录、结果追踪、提前终止
onError步骤执行出错时错误处理、自定义错误转换

此外,还有四个 Compact Hook 用于控制上下文压缩过程:

Hook触发时机典型用途
beforeMessageCompact对话历史压缩前跳过压缩、提前终止、日志记录
afterMessageCompact对话历史压缩后质量检查、指标追踪
beforeToolCompact工具结果压缩前跳过压缩、提前终止、日志记录
afterToolCompact工具结果压缩后质量检查、指标追踪
import { createAgent, createModel } from 'deepseek-kit'

const model = createModel({ model: 'deepseek-v4-flash' })

const agent = createAgent({
  model,
  hooks: {
    beforeStep: (context, hookCtx) => {
      console.log(`步骤 ${context.step} 开始`)
    },
    afterStep: (step, hookCtx) => {
      console.log(`步骤 ${step.step} 完成: ${step.type}`)
    },
    onError: (error, hookCtx) => {
      console.error(`步骤出错: ${error.type} - ${error.message}`)
    },
  },
})

beforeStep — 步骤前拦截

beforeStep 在每个步骤开始前调用,接收当前步骤的上下文信息和 HookContext。你可以用它来:

  • 记录执行进度
  • 动态修改当前步骤的配置
  • 替换当前步骤的消息或工具
  • 提前终止循环

参数

beforeStep 接收两个参数:

  1. context — 当前步骤的上下文信息
  2. hookCtx — Hook 上下文,提供 stop() 方法

日志记录

最简单的用法是记录每个步骤的开始:

beforeStep: (context, hookCtx) => {
  console.log(`步骤 ${context.step} 开始,当前 ${context.messages.length} 条消息`)
}

动态修改配置

beforeStep 可以返回一个对象来修改当前步骤的配置。返回的对象会与默认配置合并:

beforeStep: (context, hookCtx) => {
  if (context.step > 5) {
    return {
      config: {
        model: 'deepseek-v4-pro',
      },
    }
  }
}

这会让智能体在第 6 步后切换到 Pro 模型。可修改的配置项包括:

  • config — 模型配置(model、temperature、maxTokens 等)
  • messages — 当前步骤的消息列表
  • tools — 当前步骤可用的工具列表

动态选择工具

根据步骤编号或上下文动态调整可用工具:

beforeStep: (context, hookCtx) => {
  if (context.step === 1) {
    return {
      tools: [searchTool],
    }
  }
  return {
    tools: [searchTool, calculatorTool, weatherTool],
  }
}

修改消息

在特定步骤前注入额外消息:

beforeStep: (context, hookCtx) => {
  if (context.step === 3) {
    return {
      messages: [
        ...context.messages,
        { role: 'system', content: '请在回答中引用数据来源。' },
      ],
    }
  }
}

提前终止

通过 hookCtx.stop() 可以在任何 Hook 中提前终止智能体的执行循环:

beforeStep: (context, hookCtx) => {
  if (context.step > 10) {
    hookCtx.stop()
  }
}

调用 stop() 后,智能体会立即结束循环并返回当前已有的结果。这在需要设置自定义终止条件时非常有用。

afterStep — 步骤后处理

afterStep 在每个步骤完成后调用,接收步骤结果和 HookContext。步骤的类型决定了结果中包含哪些信息:

步骤类型

类型说明可用字段
'tool'工具调用步骤toolCallstextusage
'text'文本生成步骤textusage
'format'结构化输出步骤textusage

日志记录

记录每个步骤的执行结果:

afterStep: (step, hookCtx) => {
  console.log(`步骤 ${step.step} 完成: ${step.type}`)

  if (step.type === 'tool' && step.toolCalls) {
    console.log(`  调用工具: ${step.toolCalls.map(t => t.function.name).join(', ')}`)
  }

  if (step.type === 'text') {
    console.log(`  生成文本: ${step.text?.substring(0, 50)}...`)
  }

  console.log(`  Token 使用: ${step.usage.total_tokens}`)
}

Token 用量追踪

通过 afterStep 累计追踪 Token 消耗:

let totalTokens = 0

afterStep: (step, hookCtx) => {
  totalTokens += step.usage.total_tokens
  console.log(`累计 Token: ${totalTokens}`)
}

工具调用监控

专门监控工具调用步骤:

afterStep: (step, hookCtx) => {
  if (step.type === 'tool' && step.toolCalls) {
    for (const tc of step.toolCalls) {
      console.log(`[工具调用] ${tc.function.name}`)
      console.log(`  参数: ${tc.function.arguments}`)
    }
  }
}

提前终止

afterStep 同样支持通过 hookCtx.stop() 提前终止:

afterStep: (step, hookCtx) => {
  if (step.type === 'tool' && step.toolCalls) {
    const names = step.toolCalls.map(t => t.function.name)
    if (names.includes('dangerousAction')) {
      hookCtx.stop()
    }
  }
}

onError — 错误处理

onError 在步骤执行出错时调用,接收 AgentError 和 HookContext。你可以用它来:

  • 记录错误信息
  • 自定义错误转换
  • 根据错误类型决定是否继续执行

错误类型

AgentError 包含一个 type 字段,标识错误的类别:

类型说明可重试
'rate_limit'API 速率限制
'model_error'模型返回错误
'timeout'请求超时
'network_error'网络连接错误
'tool_error'工具执行错误
'max_steps'达到最大步数
'schema_error'结构化输出验证失败

日志记录

记录错误信息用于调试:

onError: (error, hookCtx) => {
  console.error(`[错误] 步骤 ${error.step}: ${error.type} - ${error.message}`)
  if (error.retryable) {
    console.log('  此错误可重试')
  }
}

自定义错误转换

onError 可以返回一个新的 AgentError 来替换原始错误,或者返回 undefined 来抑制错误(让循环继续):

onError: (error, hookCtx) => {
  if (error.type === 'rate_limit') {
    console.log('遇到速率限制,将自动重试...')
    return undefined
  }

  if (error.type === 'network_error') {
    return new AgentError({
      message: '网络连接失败,请检查网络设置后重试。',
      type: 'network_error',
      step: error.step,
      retryable: false,
    })
  }

  return error
}

返回值规则:

  • 返回 undefined — 抑制错误,循环继续执行下一步
  • 返回 AgentError — 用新的错误替换原始错误;如果 hookCtx.stop() 被调用,循环终止
  • 不返回值(void) — 原始错误继续抛出

提前终止

onError 中结合 hookCtx.stop() 可以在遇到特定错误时优雅终止:

onError: (error, hookCtx) => {
  if (error.type === 'max_steps') {
    console.log('已达到最大步数限制,提前终止。')
    hookCtx.stop()
    return
  }

  if (!error.retryable) {
    console.error(`不可重试的错误: ${error.message}`)
    hookCtx.stop()
  }
}

Compact Hook

当智能体的上下文窗口接近限制时,deepseek-kit 会自动压缩对话历史和工具结果以释放空间。Compact Hook 让你能够观察和控制这个压缩过程。

beforeMessageCompact — 压缩前拦截

beforeMessageCompact 在对话历史被压缩前调用。你可以用它来:

  • 记录压缩事件(token 数量、阈值等)
  • 通过 hookCtx.skip() 跳过本次压缩
  • 通过 hookCtx.stop() 终止智能体循环
const agent = createAgent({
  model,
  compact: true,
  hooks: {
    beforeMessageCompact: (context, hookCtx) => {
      console.log(`压缩触发: ${context.promptTokens} tokens, 阈值 ${context.threshold}`)
    },
  },
})

跳过压缩

在某些场景下(如代码生成任务),你可能不希望丢失对话细节。使用 hookCtx.skip() 可以跳过本次压缩:

beforeMessageCompact: (context, hookCtx) => {
  if (context.promptTokens < 900_000) {
    console.log('上下文尚未达到临界值,跳过压缩。')
    hookCtx.skip()
  }
}

调用 skip() 后,智能体会继续正常执行而不进行压缩。skip 状态在每次压缩决策后会自动重置。

压缩时终止

如果你想在压缩发生时直接终止智能体:

beforeMessageCompact: (context, hookCtx) => {
  console.log('上下文已达限制,终止以保留完整历史。')
  hookCtx.stop()
}

afterMessageCompact — 压缩后处理

afterMessageCompact 在对话历史被压缩后调用。你可以用它来:

  • 检查压缩结果(压缩前后的消息)
  • 追踪 token 节省量
  • 验证摘要质量
afterMessageCompact: (event, hookCtx) => {
  const beforeCount = event.messagesBefore.length
  const afterCount = event.messagesAfter.length
  console.log(`已压缩: ${beforeCount} 条消息 → ${afterCount} 条消息`)
}

beforeToolCompact — 工具结果压缩前拦截

beforeToolCompact 在工具结果被压缩前调用。与 beforeMessageCompact 类似,支持 skip()stop()

beforeToolCompact: (context, hookCtx) => {
  if (context.toolName === 'codeSearch') {
    console.log('跳过代码搜索结果的压缩。')
    hookCtx.skip()
  }
}

afterToolCompact — 工具结果压缩后处理

afterToolCompact 在工具结果被压缩后调用:

afterToolCompact: (event, hookCtx) => {
  const ratio = event.contentAfter.length / event.contentBefore.length
  console.log(`工具 "${event.toolName}" 已压缩: ${event.contentBefore.length} → ${event.contentAfter.length} 字符 (${(ratio * 100).toFixed(1)}%)`)
}

HookContext: stop() 和 skip()

HookContext 提供两个控制方法:

方法效果作用域
stop()终止整个智能体循环永久的 — 循环立即结束
skip()跳过当前压缩操作一次性的 — 每次决策后自动重置
hooks: {
  beforeMessageCompact: (context, hookCtx) => {
    if (context.promptTokens < 900_000) {
      hookCtx.skip()
    }
    else if (context.promptTokens > 950_000) {
      hookCtx.stop()
    }
  },
}

组合使用

三个 Hook 通常组合使用,构建完整的可观测性和控制逻辑:

完整日志中间件

const agent = createAgent({
  model,
  tools: [weatherTool, searchTool],
  hooks: {
    beforeStep: (context, hookCtx) => {
      console.log(`\n--- 步骤 ${context.step} ---`)
      console.log(`消息数: ${context.messages.length}`)
      console.log(`可用工具: ${context.tools.map(t => t.name).join(', ')}`)
    },
    afterStep: (step, hookCtx) => {
      if (step.type === 'tool') {
        console.log(`工具调用: ${step.toolCalls?.map(t => t.function.name).join(', ')}`)
      }
      else if (step.type === 'text') {
        console.log(`生成文本 (${step.text?.length} 字符)`)
      }
      console.log(`Token: ${step.usage.total_tokens}`)
    },
    onError: (error, hookCtx) => {
      console.error(`错误: [${error.type}] ${error.message}`)
      if (!error.retryable) {
        hookCtx.stop()
      }
    },
  },
})

动态模型切换

根据步骤进度动态切换模型——前期使用快速模型,后期切换到高精度模型:

const fastModel = createModel({ model: 'deepseek-v4-flash' })
const proModel = createModel({ model: 'deepseek-v4-pro' })

const agent = createAgent({
  model: fastModel,
  tools: [searchTool, analysisTool],
  hooks: {
    beforeStep: (context, hookCtx) => {
      if (context.step >= 3) {
        return {
          config: {
            model: 'deepseek-v4-pro',
          },
        }
      }
    },
    afterStep: (step, hookCtx) => {
      console.log(`步骤 ${step.step} (${step.type}), Token: ${step.usage.total_tokens}`)
    },
  },
})

步数限制与优雅降级

当步骤过多时优雅终止,而不是直接抛出错误:

const agent = createAgent({
  model,
  tools: [searchTool],
  maxSteps: 20,
  hooks: {
    beforeStep: (context, hookCtx) => {
      if (context.step > 10) {
        console.log(`步骤 ${context.step} 超过建议限制,准备终止。`)
        hookCtx.stop()
      }
    },
    onError: (error, hookCtx) => {
      if (error.type === 'max_steps') {
        console.log('达到最大步数,返回已有结果。')
        hookCtx.stop()
      }
    },
  },
})

Token 预算控制

设置 Token 预算,超出预算时提前终止:

let totalTokens = 0
const TOKEN_BUDGET = 10000

const agent = createAgent({
  model,
  tools: [searchTool],
  hooks: {
    afterStep: (step, hookCtx) => {
      totalTokens += step.usage.total_tokens
      if (totalTokens > TOKEN_BUDGET) {
        console.log(`Token 预算已用尽 (${totalTokens}/${TOKEN_BUDGET}),提前终止。`)
        hookCtx.stop()
      }
    },
  },
})

执行流程

以下是 Hooks 在智能体循环中的触发时机:

Rendering Chart

API 参考

GenerateTextHooks

beforeStep(context: BeforeStepContext, hookCtx: HookContext) => BeforeStepResult | void
步骤开始前的 Hook。接收当前步骤上下文和 Hook 上下文,可返回配置修改对象。
afterStep(step: StepEvent, hookCtx: HookContext) => void
步骤完成后的 Hook。接收步骤结果和 Hook 上下文。
onError(error: AgentError, hookCtx: HookContext) => void | AgentError | Promise<AgentError | void>
错误处理 Hook。接收错误对象和 Hook 上下文。可返回 undefined 抑制错误,或返回新的 AgentError 替换原始错误。
beforeMessageCompact(context: BeforeMessageCompactContext, hookCtx: HookContext) => void
对话历史压缩前的 Hook。接收压缩上下文和 Hook 上下文。支持 hookCtx.skip() 跳过压缩,hookCtx.stop() 终止循环。
afterMessageCompact(event: MessageCompactEvent, hookCtx: HookContext) => void
对话历史压缩后的 Hook。接收压缩事件(压缩前后消息、token 数量、阈值)和 Hook 上下文。
beforeToolCompact(context: BeforeToolCompactContext, hookCtx: HookContext) => void
工具结果压缩前的 Hook。接收压缩上下文和 Hook 上下文。支持 hookCtx.skip() 跳过压缩,hookCtx.stop() 终止循环。
afterToolCompact(event: ToolCompactEvent, hookCtx: HookContext) => void
工具结果压缩后的 Hook。接收压缩事件(压缩前后内容、工具名称、阈值)和 Hook 上下文。

BeforeStepContext

stepnumber
当前步骤编号(从 1 开始)。
configModelOptions
当前模型的配置信息,包括 model、temperature、maxTokens 等。
messagesChatMessage[]
当前步骤的消息列表副本。修改返回值中的 messages 可替换当前步骤的消息。
toolsTool[]
当前步骤可用的工具列表。修改返回值中的 tools 可替换当前步骤的工具。

BeforeStepResult

messagesChatMessage[]
替换当前步骤的消息列表。
toolsTool[]
替换当前步骤的工具列表。
configPartial<ModelOptions>
修改当前步骤的模型配置。支持的字段包括 modeltemperaturemaxTokensthinking 等。

StepEvent

stepnumber
步骤编号。
type'tool' | 'text' | 'format'
步骤类型。'tool' 表示工具调用步骤,'text' 表示文本生成步骤,'format' 表示结构化输出步骤。
usageUsage
当前步骤的 Token 使用量。
toolCallsChatCompletionTool[]
工具调用列表(仅 'tool' 类型步骤可用)。
textstring
生成的文本内容。
reasoningContentstring
推理内容(思考模式启用时可用)。

HookContext

stop() => void
终止智能体的执行循环。调用后,智能体会立即结束并返回当前已有的结果。
skip() => void
跳过当前压缩操作。仅在 beforeMessageCompactbeforeToolCompact Hook 中生效。跳过状态在每次压缩决策后会自动重置,因此只影响当前压缩。

BeforeMessageCompactContext

promptTokensnumber
压缩触发时的 prompt token 数量。
messagesChatMessage[]
压缩前的消息列表副本。
thresholdnumber
压缩阈值(如 0.85 表示上下文窗口使用率达到 85% 时触发压缩)。

MessageCompactEvent

messagesBeforeChatMessage[]
压缩前的消息列表。
messagesAfterChatMessage[]
压缩后的消息列表。
promptTokensnumber
压缩触发时的 prompt token 数量。
thresholdnumber
压缩阈值。

BeforeToolCompactContext

toolNamestring
即将被压缩结果的工具名称。
toolDescriptionstring
工具描述。
contentstring
原始工具结果内容。
thresholdnumber
工具结果压缩的字符长度阈值。

ToolCompactEvent

toolNamestring
已被压缩结果的工具名称。
toolDescriptionstring
工具描述。
contentBeforestring
压缩前的工具结果内容。
contentAfterstring
压缩后的工具结果内容。
thresholdnumber
工具结果压缩的字符长度阈值。

AgentError

typeAgentErrorType
错误类型。可选值为 'rate_limit''model_error''timeout''network_error''tool_error''max_steps''schema_error'
messagestring
错误描述信息。
stepnumber
发生错误时的步骤编号。
retryableboolean
是否可重试。'rate_limit''model_error''timeout''network_error' 类型的错误默认可重试。
causeError
原始错误对象。