Function Calling 链路追踪参数从 Prompt 到执行的全路径可视化一、当 Agent 告诉你我已经调用了搜索但实际没有一个常见的场景用户问 Agent 某个问题。Agent 回复我已查询了最新数据结果是……但实际上工具调用根本没有发生。大模型凭空生成了结果即幻觉。排查这类问题极其困难。你无法知道模型是否真的产生了 tool_call。即便产生了参数是否正确传递给了工具。即便正确传递了工具返回的结果是否被模型正确消费。每一步都是黑盒只能靠猜。核心诉求很明确。需要看到 Function Calling 的完整调用链路。从用户输入 Prompt到模型输出 tool_call 参数。再到参数注入工具执行最后结果回传给模型。二、Function Calling 的请求-执行链路Function Calling 的调用包含四个关键节点。第一Prompt 编译将用户输入和工具 Schema 组装为模型输入。第二模型推理模型决定调用哪个工具及参数。第三工具执行解析参数调用实际函数。第四结果注入工具返回结果写入对话上下文。sequenceDiagram participant U as 用户 participant P as Prompt 组装层 participant M as LLM participant T as 工具调度器 participant F as 实际函数 participant L as 追踪系统 U-P: 发送问题 P-P: 注入工具 Schema P-L: Node1: Prompt 快照记录 P-M: 发起推理请求 M-L: Node2: 模型输出含 tool_call M--P: 返回 tool_call 决策 alt 需要调用工具 P-T: 派发 tool_call T-T: 参数校验 T-L: Node3: 工具名 参数 T-F: 执行函数 F--T: 返回结果 T-L: Node4: 工具返回值 T--P: 注入结果到上下文 else 直接回复 M--U: 返回文本回复 end P-L: Node5: 最终响应 P--U: 返回给用户每个节点记录三类信息。时间戳、输入输出快照、延迟耗时。这些数据构成完整的调用链。三、链路追踪的 Go 实现package tracing import ( context encoding/json fmt time ) // TraceNode 追踪节点 type TraceNode struct { ID string json:id Type string json:type // prompt_compile, llm_call, tool_exec, result_inject Timestamp time.Time json:timestamp Input json.RawMessage json:input Output json.RawMessage json:output Duration time.Duration json:duration_ms Error string json:error,omitempty } // TraceChain 完整调用链 type TraceChain struct { TraceID string json:trace_id Nodes []TraceNode json:nodes StartTime time.Time json:start_time EndTime time.Time json:end_time } // Tracer 链路追踪器 type Tracer struct { chain *TraceChain mu sync.Mutex } // NewTracer 创建追踪器 func NewTracer(traceID string) *Tracer { return Tracer{ chain: TraceChain{ TraceID: traceID, Nodes: make([]TraceNode, 0), StartTime: time.Now(), }, } } // RecordNode 记录一个调用节点返回结束函数 func (t *Tracer) RecordNode(nodeType string, input interface{}) func(output interface{}, err error) { inputBytes, _ : json.Marshal(input) start : time.Now() node : TraceNode{ ID: fmt.Sprintf(%s-%d, nodeType, len(t.chain.Nodes)), Type: nodeType, Timestamp: start, Input: inputBytes, } return func(output interface{}, err error) { node.Duration time.Since(start) if err ! nil { node.Error err.Error() } else if output ! nil { node.Output, _ json.Marshal(output) } t.mu.Lock() t.chain.Nodes append(t.chain.Nodes, node) t.mu.Unlock() } } // Export 导出完整链路 func (t *Tracer) Export() *TraceChain { t.mu.Lock() defer t.mu.Unlock() t.chain.EndTime time.Now() return t.chain }在 Function Calling 流程中的使用。func handleFunctionCall(ctx context.Context, prompt string, tools []Tool) (string, *TraceChain) { tracer : NewTracer(generateTraceID()) // Node 1: Prompt 编译 endPrompt : tracer.RecordNode(prompt_compile, map[string]interface{}{ user_input: prompt, tool_count: len(tools), }) compiledPrompt : buildPrompt(prompt, tools) endPrompt(compiledPrompt, nil) // Node 2: LLM 推理 endLLM : tracer.RecordNode(llm_call, map[string]interface{}{ model: gpt-4, prompt_len: len(compiledPrompt), }) response, err : callLLM(ctx, compiledPrompt) if err ! nil { endLLM(nil, fmt.Errorf(LLM 调用失败: %w, err)) return , tracer.Export() } endLLM(response, nil) // Node 3: 工具执行 if response.ToolCall ! nil { endTool : tracer.RecordNode(tool_exec, map[string]interface{}{ tool_name: response.ToolCall.Name, parameters: response.ToolCall.Args, }) result, execErr : executeTool(ctx, response.ToolCall) endTool(result, execErr) if execErr ! nil { return fmt.Sprintf(工具执行失败: %v, execErr), tracer.Export() } } return response.Content, tracer.Export() }四、追踪系统的边界与成本全链路追踪带来存储和性能开销。每个调用链约 2-5KB JSON 数据。日均 10 万次调用产生约 500MB 追踪数据。建议设置采样率生产环境采样 10% 即可。追踪系统不适合的场景延迟极度敏感的实时推理增加 1-2ms 序列化开销本地开发的快速调试直接用 print 更高效。隐私问题需注意。用户 Prompt 可能包含敏感信息。追踪数据中应脱敏处理或设置存储过期策略。五、总结Function Calling 的链路追踪让每个调用可观测。通过在关键节点插入 Recorder捕获输入、输出、耗时。Tracer 模式比侵入式日志更结构化。生产环境建议 10% 采样率配合数据脱敏。有了完整链路排查幻觉和参数错误不再靠猜。