
文章摘要有些Spring AI项目可以在日志中看到工具已经被调用数据库查询或HTTP请求也成功执行但客户端最终收到空字符串、模型重复调用同一工具或者回答完全没有使用工具结果。这类问题与“模型没有选择工具”不同通常发生在工具返回值序列化、异常处理、Tool Calling循环、流式事件拼接、结果过长、消息持久化和终止条件等环节。本文给出从工具执行结果到最终Assistant回答的完整排查路径。一、先把链路分成五个阶段一个完整工具调用不是只有“方法执行成功”模型选择工具 → 参数解析 → 工具执行 → 结果写入Tool Response → 模型基于结果生成最终回答日志只显示orderService.query()执行成功只能证明第三阶段完成。后面仍可能失败返回对象无法序列化Tool Response为空结果没有进入下一轮模型请求模型再次调用工具流式客户端漏掉最终事件上下文超限Advisor提前返回最终回答被安全策略拦截。二、工具不要返回null错误实现Tool(description查询订单)publicOrderResultqueryOrder(StringorderId){returnrepository.find(orderId).orElse(null);}工具返回null时模型可能只看到一个空结果无法区分订单不存在 系统异常 没有权限 返回值丢失推荐结构化返回publicrecordToolResultT(booleansuccess,Stringcode,Stringmessage,Tdata){publicstaticTToolResultTsuccess(Tdata){returnnewToolResult(true,OK,执行成功,data);}publicstaticTToolResultTfailure(Stringcode,Stringmessage){returnnewToolResult(false,code,message,null);}}不存在时返回{success:false,code:ORDER_NOT_FOUND,message:订单不存在,data:null}三、不要直接返回数据库Entity数据库实体可能包含Hibernate代理懒加载集合双向关系循环引用内部字段敏感字段超大关联对象。例如Order → Customer → Orders → Customer → ……序列化可能失败或生成巨大结果。推荐返回专用DTOpublicrecordOrderSummary(StringorderId,Stringstatus,BigDecimalamount,Stringcurrency,InstantupdatedAt){}只返回模型完成任务真正需要的字段。四、返回值是否被异常转换成字符串一些工具为了“方便”这样写catch(Exceptionexception){returnexception.getMessage();}模型会把错误字符串当成正常业务结果。更危险的写法return查询完成;但没有返回真实数据模型无法回答用户问题。推荐区分业务成功 业务失败 技术异常 权限拒绝 等待确认每类都使用稳定错误码。五、检查工具结果的实际序列化内容不要只打印Java对象log.info(result{},result);还应在安全脱敏后检查发送给模型的内容tool_result_json result_size_bytes serialization_status可以在测试环境中显式序列化StringjsonobjectMapper.writeValueAsString(result);检查是否为合法JSON是否包含所需字段是否出现空对象{}是否被截断是否包含敏感信息是否大到无法进入上下文。六、工具结果过长会发生什么如果工具返回5000条数据库记录 整个日志文件 完整网页HTML 几十万字文档下一轮模型请求可能超过上下文窗口被Provider拒绝成本骤增模型忽略关键信息最终回答变空流式连接超时。工具应该返回摘要 分页信息 少量关键记录 可继续查询的游标例如{total:2387,returned:20,nextCursor:eyJwYWdlIjoyfQ,items:[]}七、Tool Calling循环是否继续进入下一轮Spring AI 2.0通过ToolCallingAdvisor执行循环模型请求工具 → 执行工具 → 把结果加入对话 → 再次调用模型 → 得到最终回答如果自动Tool Advisor被关闭AdvisorParams.toolCallingAdvisorAutoRegister(false)应用必须自己完成后续循环。否则你只能拿到工具调用请求或工具执行结果却没有最终自然语言回答。八、是否错误地注册了多个ToolAdvisor一个调用链中不应该同时存在多个负责工具执行循环的Advisor。重复注册可能造成工具执行两次对话历史重复循环顺序混乱最终消息被覆盖幂等冲突。检查ChatClient自动注册的ToolCallingAdvisor 自定义ToolCallingAdvisor ToolSearchToolCallingAdvisor应该只有一个工具循环策略。九、模型为什么重复调用同一个工具常见原因1. 结果不包含完成信号返回{status:PROCESSING}模型可能继续查询。2. 工具描述暗示需要再次确认3. 返回值缺少用户要求的字段用户问物流单号工具只返回订单状态。4. Tool Response没有进入下一轮上下文5. Prompt要求“直到确认成功为止”6. 工具调用失败却被包装为成功建议在结果中加入terminal retryable nextAction例如{success:true,terminal:true,retryable:false,data:{trackingNo:SF123456}}十、必须为副作用工具设置幂等键如果重复调用的是创建订单退款发邮件修改权限提交审批发布内容后果可能很严重。幂等键建议由业务系统生成或验证tenant_id user_id conversation_id tool_name business_request_id示例StringidempotencyKeyString.join(:,tenantId,conversationId,cancel_order,orderId);数据库建立唯一约束不能只依赖内存缓存。十一、流式接口是否漏掉最终事件流式工具调用可能包含模型文本增量 工具参数增量 工具调用开始 工具结果 下一轮模型文本 完成事件如果前端只处理第一类文本事件工具执行后生成的第二轮回答可能被忽略。检查SSE事件类型是否在工具轮次后继续订阅是否过早调用takeUntil是否收到CompleteNginx是否断开长连接客户端是否因空Chunk判定结束取消信号是否传播到上游。十二、Memory位置是否导致工具消息丢失MessageChatMemoryAdvisor放在Tool Calling循环外部时通常只持久化最终用户消息和Assistant消息。如果业务需要完整工具轨迹必须确认所用Memory Repository支持AI Tool Call RequestTool Response Message多轮工具消息。否则工具结果可能在当前请求可用但下一轮对话无法恢复。不要为了保存工具消息盲目把Memory Advisor移入循环。还要同时处理重复写入Repository序列化能力上下文膨胀敏感参数存储。十三、最终回答是否被其他Advisor改变调用链可能还有内容审核输出过滤结构化输出验证缓存日志自定义响应转换。工具执行成功后最终回答可能被安全策略阻断Schema校验反复重试缓存返回旧空结果自定义Advisor提前替换响应转换器解析失败。建议记录每个Advisor的enter exit order duration response_present十四、最终回答为空时的最小实验第一步固定工具返回值Tool(description返回测试订单)publicOrderSummarytestOrder(){returnnewOrderSummary(A1001,SHIPPED,newBigDecimal(99.00),CNY,Instant.now());}第二步要求模型必须复述字段调用testOrder并返回orderId和status。第三步关闭其他Advisor只保留Tool Calling。第四步分别测试call与stream如果同步正常、流式失败重点检查事件消费。第五步查看第二轮模型请求确认Tool Response是否真的进入上下文。十五、建议记录的观测字段tool_call_id tool_name arguments_hash execution_status execution_duration_ms result_serialization_status result_size_bytes tool_result_hash loop_iteration model_after_tool_called final_response_present stream_completed advisor_chain高风险业务还应记录idempotency_key approval_id operator business_result_id十六、完整排查清单□ 工具没有返回null □ 返回的是DTO而不是数据库Entity □ 结果可以稳定序列化 □ 错误没有被伪装成成功字符串 □ 结果大小受到限制 □ ToolCallingAdvisor完成了第二轮模型调用 □ 没有重复注册ToolAdvisor □ 结果包含terminal与retryable语义 □ 副作用工具使用数据库级幂等 □ 流式客户端没有漏掉工具后的回答 □ Memory与工具消息能力匹配 □ 其他Advisor没有替换最终响应 □ Trace中可以看到工具结果进入下一轮模型请求总结“工具调用成功但最终回答为空”说明问题已经越过工具选择阶段应该重点检查返回值序列化 → Tool Response写入 → Tool Calling下一轮 → 流式事件消费 → 最终Advisor处理只有把工具调用拆成完整阶段并逐段观测才能判断结果究竟丢在了哪里。