跳到主要内容

常见问题 ​

概述 ​

本页帮助你处理使用 Agent 时最常见的疑问和故障。例如:任务创建后为什么没有运行、怎样查询最终结果、 为什么工具没有被调用、审批后怎样继续,以及应用重启后如何找回任务。

不需要先了解框架内部结构。你可以根据遇到的现象直接选择下面的分类:

遇到的情况查看章节
不清楚 Agent 的作用,或不知道该用 run(...) 还是 start(...)基础使用
Agent 忘记上一轮内容,或对话越来越慢对话与上下文
工具没有调用、审批没有生效、任务等待输入工具与人工交互
后台任务不执行,或重启后找不到任务后台任务与持久化
任务失败、超时、没有重试或长时间不结束错误与排查
准备将 Agent 部署到正式环境生产环境检查清单

如果问题与某一项任务有关,请先保存它的 turnId。它相当于任务编号,可用于查询最新状态、提交审批或 输入,以及关联日志。

基础使用 ​

Agent 和 ChatModel 有什么区别? ​

ChatModel 负责向大模型发送一次请求并返回结果。Agent 在模型之外,还定义了任务指令、可用工具和 执行规则,让模型能够完成查询、计算或业务操作。

如果只需要“一问一答”,直接使用 ChatModel 即可。如果任务需要调用工具、等待人工操作、后台运行或失败 后重试,更适合使用 Agent。Agent 的完整介绍见 Agent。

run(...) 和 start(...) 应该选哪个? ​

方法会发生什么适用场景
run(...)创建任务,并在当前线程执行到完成、失败或需要等待几秒内完成的接口、本地程序
start(...)只创建任务并返回任务 ID,不立即执行长任务、需要接口快速返回的任务

使用 start(...) 后,需要由业务自己的线程池、消息队列或调度器显式调用 Runner 执行。不要把可能运行几分钟的任务放在 HTTP 请求线程中一直等待。执行入口的完整说明见 AgentRunner。

为什么调用 start(...) 后任务一直是 READY? ​

这是 start(...) 的正常行为。READY 表示任务已经创建,正在等待执行,并不表示发生了错误。

需要依次确认:

  1. 是否有业务线程、消息消费者或调度器调用 Runner;
  2. 调度代码和 Runner 是否连接同一个 AgentTurnStore;
  3. AgentLoader 是否能加载该任务对应的 Agent 版本;
  4. 执行线程是否仍在运行,并且还有可用的并发执行名额。

如果不需要异步执行,可以直接使用 runner.run(...)。

为什么 run(...) 返回后不是 COMPLETED? ​

run(...) 会一直执行到“当前可以停下的位置”,不保证每次都正常完成。任务可能正在等待用户输入、工具 审批、外部工具结果或下次重试时间,也可能已经失败、被取消或达到运行限制。

先读取 turn.getStatus()。等待状态需要提交对应结果,失败和限制状态则需要查看错误或调整配置。各种状态 的含义见 AgentTurn。

怎样根据 turnId 查询状态和最终结果? ​

使用创建任务时返回的 turnId 重新读取最新任务:

java
AgentTurn turn = runner.restore(turnId);

System.out.println("当前状态:" + turn.getStatus());

if (turn.getStatus() == AgentTurnStatus.COMPLETED) {
    System.out.println("最终结果:" + turn.getFinalOutput());
}

restore(...) 只查询任务,不会自动继续执行。只有状态为 COMPLETED 时,getFinalOutput() 才表示正常 完成后的最终答案。查询接口还应校验当前用户是否有权访问这个 turnId。

对话与上下文 ​

新的用户消息应该创建新任务,还是调用 resume(...)? ​

新的问题或正常的下一轮聊天,应调用带相同 conversationId 的 run(...)。会话没有活跃 Turn 时,它会 创建新的 AgentTurn;活跃 Turn 正处于阻塞状态时,它会把消息追加到原 Turn 并重新规划。

审批、表单、模型重试或外部工具结果等类型明确的回调使用 resume(...)。业务明确要求创建全新 Turn 时 使用 start(...);如果会话仍有活跃 Turn,该方法会拒绝创建。详细区别见模型故障恢复。

下一轮对话需要手动读取并传入历史消息吗? ​

不一定。配置 ChatMemoryProvider 后,Runner 会根据 conversationId 自动读取历史消息,并在执行过程中 保存本轮新增消息:

java
AgentRunner runner = AgentRunner.builder()
    .chatMemoryProvider(chatMemoryProvider)
    .build();

AgentTurn turn = runner.run(
    agent,
    "conversation-1001",
    "继续查询刚才的订单"
);

没有配置 ChatMemoryProvider 时,Runner 无法自动获得之前的对话。连续对话的配置和消息范围见 上下文管理。

AgentTurnStore 和 ChatMemory 有什么区别? ​

组件保存什么解决什么问题
AgentTurnStore一项任务的状态、进度和等待信息让同一项任务可以查询、恢复或由业务代码继续
ChatMemory同一会话的多轮聊天记录让新的任务理解之前聊过什么

例如,退款任务正在等待审批,这个进度由 Store 保存;审批完成后,用户开启新一轮对话询问“退款到账了吗”, 历史聊天内容由 ChatMemory 提供。两者用途不同,不能互相替代。

为什么同一 conversationId 不能同时创建两个活动任务? ​

同一会话中如果两项任务同时写入历史,消息顺序可能混乱,后一项任务也可能在错误的上下文上回答。因此, 配置 ChatMemory 后,同一会话同时只允许一个尚未结束的 AgentTurn。

先完成、取消或恢复当前任务,再创建下一项任务。确实需要并行处理时,应使用不同的 conversationId。

上下文越来越长、响应越来越慢怎么办? ​

长对话会把越来越多的历史消息发送给模型,从而增加耗时和 Token 消耗。可以按以下顺序处理:

  1. 不要把日志、完整文档等大段内容直接作为工具结果返回;
  2. 为查询工具增加分页、筛选和摘要;
  3. 设置上下文窗口大小,避免无限保留历史消息;
  4. 开启上下文压缩,把较早内容整理成摘要,同时保留最近消息。

配置方法见上下文管理和上下文压缩。

修改 Agent 后,尚未结束的任务会使用新版本吗? ​

不会自动切换。任务会记录创建时使用的 Agent ID 和版本,恢复时应继续加载同一版本,否则工具、指令和 执行规则发生变化,原任务可能无法正确继续。

发布新版本时,应保留仍有未完成任务的旧版本。版本加载方式见 Agent 加载与版本。

为什么恢复任务时提示找不到 Agent? ​

Store 保存的是任务进度,不会保存模型客户端和 Java 工具函数。恢复任务时,Runner 需要通过 AgentLoader 重新加载任务创建时使用的 Agent 版本。

请确认 Loader 已配置到 Runner,Agent ID 和版本一致,并且旧版本尚未被删除。多实例部署时,每个实例都 必须能够加载相同版本。

工具与人工交互 ​

为什么模型没有调用已经配置的工具? ​

注册工具表示模型“可以使用”,并不保证它一定会调用。可以依次检查:

  1. 工具是否注册到了当前 Agent,而不是只在其他 Agent 中创建;
  2. 工具名称、用途和参数说明是否清楚;
  3. Agent 指令是否明确要求在相应场景使用工具;
  4. 当前模型是否支持工具调用(Tool Calling);
  5. 工具是否被当前的可见性规则隐藏;
  6. 用户的问题是否真的需要这个工具。

对于订单状态、库存等不能猜测的数据,应在指令中明确写出“必须调用工具,以工具结果为准”。仍有问题时, 通过 AgentEventListener 观察模型是否返回了工具调用请求。

为什么工具需要审批,但任务直接执行了? ​

最常见的原因是没有为当前 Agent 配置 toolApprovalPolicy(...)。未配置审批规则时,工具默认允许执行。

如果已经配置,请检查规则是否匹配实际工具名称或元数据,以及匹配后是否错误地返回了 ALLOW。审批只对 由 AgentRunner 调用的工具生效;业务代码绕过 Runner 直接调用工具函数,自然不会进入审批流程。完整示例 见人工审批。

审批、表单输入和普通追问有什么区别? ​

场景含义应该怎样处理
人工审批参数已经确定,需要决定是否允许执行高风险操作恢复原任务并提交批准或拒绝
表单输入执行所需信息不完整,需要用户填写固定字段恢复原任务并提交表单数据
普通追问用户在上一 Turn 结束后开始新的问题创建新的 AgentTurn
阻塞时改口原 Turn 尚未结束,用户修改要求或要求继续向原 Turn 追加消息并重新规划

审批、表单和阻塞时改口都在继续原来的任务,因此必须保留原 turnId,不能重新创建任务代替恢复。

resume(...) 和 submitResume(...) 有什么区别? ​

两者都会向当前等待中的任务提交与等待原因匹配的结果,区别在于由谁继续执行:

方法提交结果后适用场景
resume(...)在当前线程立即继续,直到再次等待或结束同步接口、提交后需要立即返回结果
submitResume(...)只将任务恢复为可执行状态提交接口快速返回,由业务调度器后台继续

恢复内容包括用户输入、审批批准或拒绝、外部工具成功或错误结果、模型重试、继续执行和到期重试。 userMessage(...) 是普通消息,在 WAITING_FOR_USER 中优先回答当前请求,在其他等待中触发重新规划; replanWithMessage(...) 才表示无条件打断任意阻塞状态。其他命令必须与当前等待原因匹配,否则 Runner 会拒绝提交。 详细说明见挂起和恢复。

为什么任务一直等待表单、审批或外部工具? ​

等待状态不会自己消失,业务系统必须完成对应动作:

状态正在等待下一步
WAITING_FOR_USER用户文本或表单提交 userInput(...)
WAITING_FOR_APPROVAL工具审批提交 approveTool(...) 或 rejectTool(...)
WAITING_FOR_TOOL外部工具结果提交 toolResult(...) 或 toolError(...)
WAITING_FOR_MODEL模型额度、限流、Token 上限或服务异常修复后提交 retryModel(...),或发送新消息

还要确认提交时使用了正确的 turnId 和当前等待事项的关联 ID。使用 submitResume(...) 后,业务代码还需要显式调用 Runner;否则它会保持可执行但不会继续。

为什么审批结果或表单数据提交后被拒绝? ​

通常是任务状态已经变化,或者提交的结果不属于当前等待事项。例如,用上一次审批的工具调用 ID 去恢复新的 审批请求,Runner 会拒绝,以免结果作用到错误操作。

提交前重新调用 restore(turnId) 读取最新状态和等待信息,不要长期缓存旧的任务对象。重复提交或迟到结果 应当按“已经处理”向用户返回,而不是改用另一个恢复命令强行继续。

工具为什么可能执行两次? ​

应用异常退出、业务代码重新推进任务、自动重试,以及表单提交后重新执行原工具,都可能让同一业务 动作再次到达工具代码。框架会保护任务进度,但无法撤回已经发送给支付、邮件或物流系统的请求。

退款、扣款、发货、发送消息等写操作必须支持幂等,也就是同一个业务请求即使重复到达,也只产生一次影响。 工具中可以使用 AgentToolContext.getIdempotencyKey() 获取稳定的幂等键,详见 工具运行上下文。

AgentToolContext 和 AgentToolResumeInfo 有什么作用? ​

它们供正在执行的 Java 工具读取本次任务的运行信息,不需要业务代码自行创建。

  • AgentToolContext 提供任务 ID、工具调用 ID、执行次数、幂等键、已提交的表单数据和进度上报能力;
  • AgentToolResumeInfo 说明这次工具执行是否来自审批、表单恢复或错误重试,并记录恢复次数、附加信息和 上一次错误。

普通只读工具通常不需要使用它们。需要防止重复写入、读取表单数据、记录审批人或区分错误重试时,再引入 这些信息。完整示例见工具运行上下文。

后台任务与持久化 ​

浏览器关闭和应用服务重启有什么区别? ​

浏览器关闭只会中断页面连接。只要应用服务中的执行线程仍在运行,后台任务可以继续;用户重新打开页面后, 根据 turnId 查询最新状态即可。

应用服务重启会结束当前进程。如果使用内存 Store,任务数据会丢失;如果使用共享的 JDBC 或 Redis Store, 并且重启后的应用能加载原 Agent 版本,业务代码可以在新请求到达时显式恢复或修复未结束的任务。

为什么应用重启后找不到任务? ​

最常见的原因是使用了默认的内存 Store。内存数据只存在于当前应用进程,重启后无法恢复。

生产环境应配置 JDBC 或 Redis AgentTurnStore,并确认重启前后的应用连接同一套存储。Redis 是否能在自身 重启后保留数据,还取决于 Redis 的持久化和备份配置。具体配置见任务快照持久化。

内存 Store 可以用于多实例部署吗? ​

不可以。每个应用实例都有自己的内存,实例 A 创建的任务无法被实例 B 查询或执行。

多实例必须使用所有实例共享的 JDBC 数据库或 Redis,并由业务层协调同一 Turn 的执行,并能够加载相同的 Agent 版本。

因此,涉及真实业务影响的工具仍要使用幂等键。可以把幂等理解为“同一请求重复执行,业务结果仍然只有 一份”。

保存任务时出现版本冲突是什么意思? ​

这表示当前代码准备保存的任务已经被其他请求或执行线程更新。框架通过版本号避免旧数据覆盖新数据,这种 “比较旧版本后再更新”的保护有时也称为 CAS。

遇到冲突时,应停止使用手中的旧对象,通过 restore(turnId) 重新读取最新任务,再根据最新状态决定是否 仍需操作。不要跳过版本检查直接覆盖数据。

错误与排查 ​

怎样判断任务正在等待、执行失败还是已经结束? ​

先查看 AgentTurn.getStatus(),不需要从内部字段推测:

状态含义常见处理
READY、RUNNING等待执行或正在执行检查业务执行线程、模型和工具耗时
WAITING_FOR_USER、WAITING_FOR_APPROVAL、WAITING_FOR_TOOL等待外部结果展示对应操作并提交恢复命令
WAITING_FOR_MODEL等待模型条件恢复检查 getModelFailure(),修复后重试或接收新消息
RETRY_SCHEDULED暂时失败,等待下次重试检查重试时间和业务调度器
COMPLETED正常完成读取 getFinalOutput()
FAILED执行失败读取错误并查看相关日志
CANCELLED已取消向用户展示取消结果
MAX_ITERATIONS_REACHED、MAX_STEPS_REACHED、BUDGET_EXCEEDED达到运行限制检查任务设计和预算配置

状态的完整生命周期见 AgentTurn。

为什么自动重试没有执行? ​

先确认错误被配置为“可以重试”,并且任务状态已经变为 RETRY_SCHEDULED。然后检查:

  1. 等待时间是否已经到达;
  2. 业务调度器是否在到期后显式推进;
  3. 调度器与 Runner 是否使用同一个 Store;
  4. 最大重试次数是否已经用完;
  5. 整个任务是否已经超过运行时间或预算限制。

自动重试需要由业务调度器在到期后调用 resume(...),不需要页面反复调用。

如何排查长时间处于 RUNNING 的任务? ​

RUNNING 可能表示模型请求或工具仍在进行,也可能是执行进程异常退出后尚未被修复。建议依次检查:

  1. 最近一条事件停在模型调用还是工具调用;
  2. 模型请求和工具是否配置了超时;
  3. 业务调度器是否仍在运行并能正常访问 Store;
  4. 是否只有一个实例卡住,是否需要业务代码在新请求到达时修复;
  5. 外部接口是否已经产生业务结果,避免人工重试造成重复操作。

不要只依赖浏览器中的实时消息判断。应以 restore(turnId) 读取的最新状态为准,排查方法见 可观测性。

Token 预算为什么没有按预期生效? ​

Token 数量依赖模型返回用量信息。如果模型实现没有提供用量,Runner 无法准确累计 Token。

生产环境不要只设置 Token 上限,还应同时限制模型迭代次数、总执行步骤、工具调用次数和总运行时间,并为 模型和工具设置超时。配置说明见运行限制与预算和超时与过期。

如何取消任务? ​

调用 runner.cancel(turnId)。取消采用协作方式:Runner 会在适合停止的位置结束任务,但不会强制终止已经 发出的 HTTP 请求或正在执行的 Java 方法。

用户点击“停止生成”时使用 runner.stop(turnId);如果需要等待本地模型流或工具执行退出,可使用 runner.stopAndWait(turnId, timeoutMillis)。该操作仍会持久化取消标记,跨进程执行时会退化为协作式取消。

如果本地 Tool 创建了子进程、HTTP 请求或其他外部资源,应通过 AgentToolContext.getCancellation().onStop(...) 注册资源清理动作,并在 Tool 自己的 finally 中确认资源退出。停止回调只负责发出取消信号,不能保证已经提交的 扣款、发货或消息发送等外部副作用被回滚。

因此,取消后仍应确认外部系统是否已经完成扣款、发送或发布等操作。取消一项任务后,任务状态会变为 CANCELLED,不能再通过恢复命令重新打开。

AgentEventListener 可以用于可靠审计吗? ​

不能单独承担可靠审计。AgentEventListener 默认在发布事件的线程中同步执行;通过 AgentRunnerOptions.eventExecutor(...) 配置执行器后,也可以异步分发。

无论同步还是异步,事件都不会由框架自动持久化,应用重启或监听器处理失败时可能丢失。页面进度、普通日志 和监控指标可以直接使用监听器;计费、合规审计等不能丢失的数据,应转发到业务审计库或可靠消息系统,并按 事件 ID 做重复处理保护。详细说明见 AgentEventListener。

挂起恢复后还会继续流式输出吗? ​

会。流式调用是当前 AgentTurn 的执行设置,审批、表单或外部工具等待结束后,继续执行时仍会沿用该设置。 但浏览器断线期间的增量内容不一定会重新推送,页面重连后应先查询任务状态和已保存结果,再继续接收新事件。

metadata 可以保存任意对象吗? ​

不建议。metadata 表示随任务保存的附加信息,只应存放体积小、结构稳定、能够序列化且不敏感的数据, 例如业务单号、操作来源和经过筛选的标签。 数据库连接、线程、本地回调、模型客户端、密码和 API Key 都不应放入 metadata。

自定义类型还要考虑应用升级后的兼容性。能用字符串、数字、布尔值和简单集合表达时,优先使用这些基础类型。

生产环境检查清单 ​

在正式环境启用 Agent 前,至少确认以下事项:

  • 使用共享的 JDBC 或 Redis AgentTurnStore,不依赖进程内存保存任务;
  • 配置 AgentLoader,并保留未结束任务所依赖的历史 Agent 版本;
  • 需要连续对话时,配置所有实例共享的 ChatMemoryProvider;
  • 后台任务已配置业务线程池或消息调度器,并设置合理的总并发数;
  • 模型请求和工具调用都有超时,任务设置了迭代、步骤、工具次数、Token 和运行时间限制;
  • 根据错误类型配置自动重试,确认到期任务能够被业务调度器继续;
  • 退款、扣款、发货、发送消息等写操作使用稳定的业务幂等键;
  • 审批、表单、恢复、取消和任务查询接口都执行登录、权限及数据范围校验;
  • 日志、事件、任务数据和表单数据在展示或外发前完成脱敏;
  • 监控待执行任务、长期等待任务、失败率、重试积压、调度器状态和 Store 异常;
  • 关键审计数据写入可靠存储,不只依赖进程内的 AgentEventListener;
  • 应用关闭时先停止接收新任务,并为正在执行的步骤预留完成时间;
  • 为任务数据、聊天记录和审计记录分别制定备份、保留与清理策略。

相关文档索引 ​

主题文档
认识 Agent 和完成第一个示例Agent、快速开始
创建、执行、恢复和取消任务AgentRunner
查看任务状态和结果AgentTurn
管理连续对话和长上下文上下文管理、上下文压缩
控制工具执行方式工具执行控制、工具运行上下文
处理人工操作和等待状态人工审批、表单输入、挂起和恢复
执行后台任务业务线程池、消息队列或调度器
保存任务和加载 Agent 版本任务快照持久化、Agent 加载与版本
处理故障和运行限制错误处理与重试、超时与过期、运行限制与预算
观察任务运行过程AgentEventListener、可观测性