LLM 调用链可视化:一次 Agent 调用到底走了几步
LLM 调用链可视化一次 Agent 调用到底走了几步一、背景与问题LLM Agent 的调用链路比单次推理复杂得多。一个典型的 Agent 请求不是用户提问→模型回答而是用户提问→意图识别→工具选择→工具调用→结果整合→二次推理→最终回答。每一步都是一次独立的 LLM 调用中间还穿插外部 API 调用搜索、数据库查询等。这种多步骤链路带来的运维问题是当 Agent 响应时间从 3 秒涨到 10 秒你不知道是哪个步骤慢了。是意图识别的推理慢了是工具调用外部搜索 API慢了是结果整合的二次推理慢了还是某个步骤触发了错误重试导致多跑了一轮调用链可视化解决的就是这个问题把 Agent 的多步骤调用展开成一条可见的链路每一步的耗时、输入输出摘要、调用类型LLM 调用 vs 外部 API 调用 vs 内部逻辑一目了然。这不是锦上添花的可视化需求是 Agent 服务运维的基本要求——你必须知道一个请求到底走了几步才能知道该优化哪一步。二、调用链数据模型flowchart TD A[用户请求: 查询北京天气] -- B[Step1: 意图识别 LLM] B -- C[Step2: 工具选择 LLM] C -- D[Step3: 天气API调用] D -- E[Step4: 结果整合 LLM] E -- F[Step5: 最终回答生成 LLM] F -- G[响应: 北京今天晴 25°C] H[Step2-alt: 知识库检索] -- I[Step4-alt: 结合检索结果整合] style B fill:#7b1fa2,color:#fff style C fill:#8e24aa,color:#fff style D fill:#9c27b0,color:#fff style E fill:#ab47bc,color:#fff style F fill:#ba68c8上图展示了一条典型 Agent 调用链。5 个步骤中有 4 次 LLM 调用和 1 次外部 API 调用。关键数据模型字段类型说明trace_idstring请求唯一标识step_idint步骤序号1, 2, 3...step_typeenumllm_call / tool_call / logicmodel_namestringLLM 调用时的模型名input_tokensint输入 Token 数output_tokensint输出 Token 数latency_msint本步骤耗时statusenumsuccess / error / retryparent_step_idint父步骤并行分支时用step_type是核心分类维度。LLM 调用和工具调用的性能特征完全不同——LLM 调用延迟由模型和 GPU 决定工具调用延迟由外部 API 决定。如果不区分类型平均步骤延迟 500ms 这个数字没有任何诊断价值。三、实现方案3.1 Agent 框架层的 Span 嵌套以 LangChain/LangGraph 为例每个 Agent 步骤天然对应一个 Span。关键是正确嵌套父子关系from opentelemetry import trace tracer trace.get_tracer(agent-service) async def run_agent(user_query: str): with tracer.start_as_current_span(agent.request) as root_span: root_span.set_attribute(user_query_length, len(user_query)) # Step1: 意图识别 with tracer.start_as_current_span(agent.intent识别) as s1: intent_result await llm_call( modelgpt-4o-mini, promptf识别意图: {user_query} ) s1.set_attribute(step_type, llm_call) s1.set_attribute(model_name, gpt-4o-mini) s1.set_attribute(input_tokens, intent_result.usage.input_tokens) s1.set_attribute(output_tokens, intent_result.usage.output_tokens) # Step2: 工具选择 with tracer.start_as_current_span(agent.tool_select) as s2: tool_name await llm_call( modelgpt-4o-mini, promptf选择工具: intent{intent_result} ) s2.set_attribute(step_type, llm_call) # Step3: 工具调用 with tracer.start_as_current_span(agent.tool_call) as s3: tool_result await call_weather_api(city北京) s3.set_attribute(step_type, tool_call) s3.set_attribute(tool_name, weather_api) s3.set_attribute(external_latency_ms, tool_result.latency) # Step4: 结果整合 with tracer.start_as_current_span(agent.result整合) as s4: final await llm_call( modelgpt-4o, promptf整合结果: query{user_query}, data{tool_result} ) s4.set_attribute(step_type, llm_call) s4.set_attribute(model_name, gpt-4o) return final.content每个start_as_current_span自动嵌套在父 Span 下。OpenTelemetry SDK 会自动传递trace-id和计算span-id的父子关系。在 Trace Viewer 中这5个 Span 会以树状结构展示一目了然。3.2 Token 耗用聚合Agent 调用链中的 LLM 步骤可能使用不同模型意图识别用小模型最终回答用大模型。Token 耗用需要按步骤拆分而非只看总量# 每个步骤的 Token 耗用 sum(inference_token_total{directionoutput}) by (step_type, model_name) # 整条链路的 Token 总耗用 sum(inference_token_total{trace_idtrace-abc123})这个拆分直接影响成本优化。如果 Step1 和 Step2 用 gpt-4o-mini 各消耗 50 token而 Step4 用 gpt-4o 消耗 500 token优化方向很明确Step4 的模型选择和 prompt 设计是成本大头Step1/Step2 的优化空间很小。3.3 重试和循环的可视化Agent 调用链不是线性流程可能包含循环反思机制结果不满意时重新推理。循环在 Trace 中表现为同一类型的 Span 重复出现flowchart LR A[Step1: 意图识别] -- B[Step2: 推理] B -- C[Step3: 反思评估] C -- D{质量达标?} D --|否| B2[Step4: 二次推理 retry] D --|是| E[Step5: 输出] style D fill:#ff6f00 style B2 fill:#d32f2f,color:#fff重试步骤必须标注retry_count属性。在 Span 上设置s_retry tracer.start_span(agent.inference_retry) s_retry.set_attribute(retry_count, 2) s_retry.set_attribute(original_step_id, 2)这样在 Trace Viewer 中可以区分这是第几次重试和原始步骤是哪个。没有这个标注一条包含 3 次重试的链路看起来像是 6 个独立步骤无法理解实际逻辑。四、生产实践与踩坑Span 数量控制。一个复杂 Agent 调用可能包含 10 个步骤加上每个步骤内部的子 SpanLLM API 的 HTTP 调用、工具 API 的 HTTP 调用单次请求可能产生 30 个 Span。这个量级 OTel Collector 可以处理但要注意采样策略——Agent 调用的 QPS 通常不高几十到几百建议全量采集。Token 统计口径。不同 LLM 提供商的 Token 计数方法不同OpenAI 的 tokenizer 和 Anthropic 的 tokenizer 不兼容。在 Span attribute 中记录token_provider字段如 openai_bpe避免跨提供商的 Token 数汇总出现歧义。如果只做成本估算用美元金额cost_usd而非 Token 数作为统一口径——金额的计算在 Agent 框架层完成不受 tokenizer 差异影响。超长调用链的展示。包含反思循环的 Agent 调用链可能长达 20 步骤在 Trace Viewer 中线性展示不直观。更好的方式是按步骤类型分组先展示 LLM 调用的总耗时和总 Token 数再展开每一步的细节。Grafana 的 Trace View 支持折叠子 Span利用这个功能让长链路可读。和传统微服务追踪的区别。传统微服务的追踪是一层调用一层A→B→CSpan 深度通常 3-5 层。Agent 的追踪是同一进程内的多步骤串联Span 深度可能 10 层。OTel SDK 默认的 Span 限制是 1000Agent 调用不会触碰这个限制但要确保自定义的 Span limit 配置不会被误改。外部工具调用的超时追踪。工具调用搜索 API、数据库查询的超时是 Agent 延迟飙升的常见原因。在工具调用 Span 上必须记录external_service_timeout_ms和external_service_status_code。当这两个字段异常时问题不在 Agent 框架而在外部依赖。五、总结Agent 的调用链不是单次推理是多步骤、多类型调用LLM 工具 逻辑的串联。可视化这条链路不是为了好看是为了在延迟飙升时立刻定位瓶颈步骤。三个关键实践按step_type分类LLM 调用、工具调用、内部逻辑的延迟特征不同不分类就无法诊断。Token 耗用按步骤拆分不同步骤可能用不同模型拆分后才能找到成本优化方向。重试和循环必须标注反思机制的循环会让调用链变长标注retry_count和original_step_id才能理解实际逻辑。基础设施不需要漂亮话。调用链可视化的价值不是图表有多漂亮是延迟从 3 秒涨到 10 秒时你能在 10 秒内找到瓶颈在哪一步。找不到就只能逐步骤手动排查每排查一次消耗 30 分钟。