
文章摘要有些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處理只有把工具調用拆成完整階段并逐段觀測才能判斷結果究竟丟在了哪里。