云计算百科
云计算领域专业知识百科平台

Graph-Engineering-第17章第79节-Graph应用的X光机-LangSmith-Arize-Phoenix与自定义Trace全攻略

封面图片

Graph应用的X光机:LangSmith、Arize Phoenix与自定义Trace全攻略

拓扑图画得再漂亮,也只是"设计态";真正决定系统生死的是"运行态"——这一节我们把Graph推进X光机,让每个节点的耗时、token、成本、失败全部现形

本文导航

  • 为什么Graph应用天然需要Trace
  • 三套方案一图看懂
  • 方案一:LangSmith——LangGraph的官方全家桶
  • 方案二:Arize Phoenix——开源派与OpenTelemetry标准
  • 方案三:自定义Trace——结构化日志的最后底牌
  • 从Trace到账本:成本与延迟分析
  • 选型决策
  • 小结
  • 下节预告
  • 互动区

为什么Graph应用天然需要Trace

第四部分开篇先不讲项目,先发装备。

原因很直接:前面十六章我们造了一堆Graph,但调试它们的方式基本还是print大法。单Agent时代这么干勉强能活——一个Loop打印几行日志,肉眼扫一遍就完了。Graph时代这么干就是灾难:一次深度研究跑下来,十几个节点、几十次模型调用、若干次条件路由和循环回边,print出来的日志是线性的,而你的问题是网状的。

我把这叫"白盒架构、黑盒运行":你在代码里能看到全部拓扑,但在运行时两眼一抹黑。具体会栽在这几个坑里:

  • 慢不知道慢在哪。端到端45秒,是检索慢?还是合成节点把20k token的上下文喂给了慢模型?
  • 钱不知道花在哪。一次研究花了$0.8,哪个节点烧掉了70%?那个节点是不是每次都把全量历史塞进prompt?
  • 错不知道怎么错的。验证节点判了fail,图走了回边重跑三轮——你甚至不知道它重跑了,因为没人在边上打日志。
  • Prompt无法追溯。上周四那次输出质量突然变差,当时的输入到底是什么?没存,就永远查不回来。
  • 第6节讲Context Rot、第7节讲失败路由的时候我都反复说过:Graph的很多问题不是设计出来的,是运行出来的。设计态你只能保证拓扑正确,运行态的退化(上下文膨胀、重试风暴、token泄漏)只能靠Trace抓。

    所以这一节的目标很明确:给你的Graph配一台X光机。我准备了三套方案——LangSmith、Arize Phoenix、自定义Trace,从托管服务到完全自控,逐个讲透。


    三套方案一图看懂

    先看三套方案的架构差异,心里有张地图再往下走:

    #mermaid-svg-IZfekpU5SuJda9St{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IZfekpU5SuJda9St .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IZfekpU5SuJda9St .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IZfekpU5SuJda9St .error-icon{fill:#552222;}#mermaid-svg-IZfekpU5SuJda9St .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IZfekpU5SuJda9St .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IZfekpU5SuJda9St .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IZfekpU5SuJda9St .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IZfekpU5SuJda9St .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IZfekpU5SuJda9St .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IZfekpU5SuJda9St .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IZfekpU5SuJda9St .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IZfekpU5SuJda9St .marker.cross{stroke:#333333;}#mermaid-svg-IZfekpU5SuJda9St svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IZfekpU5SuJda9St p{margin:0;}#mermaid-svg-IZfekpU5SuJda9St .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-IZfekpU5SuJda9St .cluster-label text{fill:#333;}#mermaid-svg-IZfekpU5SuJda9St .cluster-label span{color:#333;}#mermaid-svg-IZfekpU5SuJda9St .cluster-label span p{background-color:transparent;}#mermaid-svg-IZfekpU5SuJda9St .label text,#mermaid-svg-IZfekpU5SuJda9St span{fill:#333;color:#333;}#mermaid-svg-IZfekpU5SuJda9St .node rect,#mermaid-svg-IZfekpU5SuJda9St .node circle,#mermaid-svg-IZfekpU5SuJda9St .node ellipse,#mermaid-svg-IZfekpU5SuJda9St .node polygon,#mermaid-svg-IZfekpU5SuJda9St .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IZfekpU5SuJda9St .rough-node .label text,#mermaid-svg-IZfekpU5SuJda9St .node .label text,#mermaid-svg-IZfekpU5SuJda9St .image-shape .label,#mermaid-svg-IZfekpU5SuJda9St .icon-shape .label{text-anchor:middle;}#mermaid-svg-IZfekpU5SuJda9St .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IZfekpU5SuJda9St .rough-node .label,#mermaid-svg-IZfekpU5SuJda9St .node .label,#mermaid-svg-IZfekpU5SuJda9St .image-shape .label,#mermaid-svg-IZfekpU5SuJda9St .icon-shape .label{text-align:center;}#mermaid-svg-IZfekpU5SuJda9St .node.clickable{cursor:pointer;}#mermaid-svg-IZfekpU5SuJda9St .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IZfekpU5SuJda9St .arrowheadPath{fill:#333333;}#mermaid-svg-IZfekpU5SuJda9St .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IZfekpU5SuJda9St .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IZfekpU5SuJda9St .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IZfekpU5SuJda9St .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IZfekpU5SuJda9St .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IZfekpU5SuJda9St .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IZfekpU5SuJda9St .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IZfekpU5SuJda9St .cluster text{fill:#333;}#mermaid-svg-IZfekpU5SuJda9St .cluster span{color:#333;}#mermaid-svg-IZfekpU5SuJda9St div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IZfekpU5SuJda9St .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IZfekpU5SuJda9St rect.text{fill:none;stroke-width:0;}#mermaid-svg-IZfekpU5SuJda9St .icon-shape,#mermaid-svg-IZfekpU5SuJda9St .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IZfekpU5SuJda9St .icon-shape p,#mermaid-svg-IZfekpU5SuJda9St .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IZfekpU5SuJda9St .icon-shape .label rect,#mermaid-svg-IZfekpU5SuJda9St .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IZfekpU5SuJda9St .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IZfekpU5SuJda9St .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IZfekpU5SuJda9St :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    方案三 自定义Trace(结构化日志)

    方案二 Arize Phoenix(开源自托管)

    方案一 LangSmith(云托管)

    自动上报

    OTel instrumentation

    手动埋点

    你的Graph应用(LangGraph/任意框架)

    node: retrieve

    node: verify

    node: synthesize

    SDK自动埋点环境变量即接入

    云端Trace树UItoken/成本/回放

    OpenInference埋点OTel标准Span

    本地6006端口UI数据不出内网

    自研TracerJSONL逐span落盘

    pandas自由分析完全可控

    一句话点评:LangSmith省心但数据出网、Phoenix开源自持但要自己运维、自定义Trace最灵活但埋点自己写。生产上我的建议是至少两套并用——后面选型决策细说。

    先给一张硬指标对比表,后面逐个展开:

    维度LangSmithArize Phoenix自定义Trace
    接入成本 极低(3个环境变量) 低(5行代码) 中(自己写Tracer)
    LangGraph原生支持 原生(节点=Run) 经由LangChain埋点 需手动埋点
    数据归属 云端(SaaS) 完全本地 完全本地
    费用 有免费额度,量大收费 开源免费 免费(付人力)
    Token/成本统计 内置(常见模型) 依exporter配置 自己记,想要啥有啥
    Prompt原始内容留存 内置 内置 自己落文件
    灵活分析 UI为主,API导出 UI+OTel生态 pandas随便造
    适合场景 快速起步、LangGraph深度用户 内网合规、OTel统一观测 特殊指标、长期归档分析

    方案一:LangSmith——LangGraph的官方全家桶

    LangSmith是LangChain公司出的观测平台,跟LangGraph是亲兄弟,这是它最大的护城河:你不用改一行业务代码。

    接入只要三步。装包、设环境变量、跑图:

    # langsmith_demo.py —— LangSmith接入:总共就这么点
    import os

    # ① 三个环境变量,接入完成(真实key去smith.langchain.com领)
    os.environ["LANGSMITH_TRACING"] = "true"
    os.environ["LANGSMITH_API_KEY"] = "lsv2_pt_xxxxxxxxxxxx"
    os.environ["LANGSMITH_PROJECT"] = "graph-course-demo"

    # ② 业务代码零改动,照常写你的Graph
    from langgraph.graph import StateGraph, START, END
    from typing import Annotated
    import operator, time

    def retrieve(state: dict) –> dict:
    time.sleep(0.3) # 模拟检索耗时
    return {"docs": ["doc-A", "doc-B"]}

    def synthesize(state: dict) –> dict:
    time.sleep(0.5) # 模拟LLM调用
    return {"answer": f"基于{len(state['docs'])}篇文档的综合结论"}

    g = StateGraph(dict)
    g.add_node("retrieve", retrieve)
    g.add_node("synthesize", synthesize)
    g.add_edge(START, "retrieve")
    g.add_edge("retrieve", "synthesize")
    g.add_edge("synthesize", END)

    result = g.compile().invoke({"query": "2026年固态电池量产进展"})
    print("answer:", result["answer"])

    控制台输出:

    answer: 基于2篇文档的综合结论

    代码看着平平无奇——魔法全在Smith后台。打开smith.langchain.com进入graph-course-demo项目,你会看到一棵Trace树,每个节点是一个Run:

    Run: LangGraph (总耗时 812ms)
    ├── Run: retrieve (312ms) status=success
    └── Run: synthesize (500ms) status=success
    └── Run: ChatOpenAI (498ms)
    ├── inputs: prompt原文完整可见
    ├── outputs: 补全内容完整可见
    └── usage: prompt_tokens / completion_tokens / cost

    这棵树就是X光片本片。LangSmith的几个杀手锏:

  • 条件边和循环可视化。LangGraph的路由决策、循环了几轮,Trace树上直接画出来,第80节讲重试风暴定位时全靠它。
  • Threads回放。每次invoke是一个thread,可以拿历史thread的输入直接重跑——调prompt不用重新造数据。
  • Token与成本。常见模型的usage自动解析并折算成美元,按project/节点/天聚合。
  • 标注与数据集。人工给trace打分,攒成评估数据集,接第80节末尾要讲的回归测试。
  • 代价也说清楚:trace数据要出境(SaaS版),prompt里如果有客户敏感信息,合规要先过;量大之后按量计费。它有自托管版本,但部署复杂度上去了,不如直接看方案二。


    方案二:Arize Phoenix——开源派与OpenTelemetry标准

    Phoenix是Arize开源的LLM观测工具(Apache-2.0核心),核心卖点是OpenTelemetry标准 + 数据不出内网。它不对LangGraph做私有绑定,而是走OTel的Span模型——这意味着你混用LangGraph、裸OpenAI SDK、CrewAI(都支持OpenInference埋点)时,trace能拼在同一棵树上。

    # phoenix_demo.py —— Phoenix接入:先起服务再埋点
    # 第0步:终端里先启动Phoenix服务(数据存本地)
    # pip install arize-phoenix openinference-instrumentation-langchain
    # python -m phoenix serve # 默认 http://localhost:6006

    from phoenix.otel import register
    from openinference.instrumentation.langchain import LangChainInstrumentor

    # ① 注册tracer provider,指向本地Phoenix
    tracer_provider = register(project_name="graph-course-demo")

    # ② 给LangChain/LangGraph装上探测器
    LangChainInstrumentor().instrument(tracer_provider=tracer_provider)

    # ③ 之后照常跑你的Graph——与方案一的那份业务代码完全一样
    from langgraph.graph import StateGraph, START, END
    import time

    def retrieve(state): time.sleep(0.3); return {"docs": ["doc-A", "doc-B"]}
    def synthesize(state): time.sleep(0.5); return {"answer": "综合结论"}

    g = StateGraph(dict)
    g.add_node("retrieve", retrieve)
    g.add_node("synthesize", synthesize)
    g.add_edge(START, "retrieve"); g.add_edge("retrieve", "synthesize"); g.add_edge("synthesize", END)
    print(g.compile().invoke({"query": "test"}))

    控制台输出:

    Launching Phoenix UI at http://localhost:6006
    {'query': 'test', 'docs': ['doc-A', 'doc-B'], 'answer': '综合结论'}

    浏览器打开localhost:6006,看到的同样是span树:LangGraph节点是一个个span,LLM调用是子span,attributes里带llm.token_count.prompt、llm.token_count.completion这类OTel语义约定字段。

    Phoenix相对LangSmith的加分项:内网部署合规压力小;OTel生态可以和你们已有的APM(Jaeger、Grafana Tempo)打通;评估(evals)功能开源可用。减分项:UI细腻度不如LangSmith,token成本折算要自己配模型价格表。

    一句话:LangSmith是iPhone,Phoenix是开源安卓——体验差一点,自由多很多。


    方案三:自定义Trace——结构化日志的最后底牌

    第三套是我压箱底的。前两套再好,都受制于"人家记什么你看什么"。总有些指标只有你知道该记:比如"验证节点的否决率"、“每轮循环新增洞察数”、“某供应商接口的baseurl和key尾号”。而且某些行业(金融、医疗)连prompt都不许出内网,SaaS直接出局。

    自定义Trace的本质:把每次节点执行写成一条结构化JSONL记录。字段我按"一次远端调用该记什么"的原则设计——baseurl、key末6位、模型名、输入输出长度、起止时间、总耗时,一个都不能少。这些字段以后能支撑任意维度的灵活分析:一句提示词就能算平均耗时、极值、长度与速度的相关性。

    # custom_tracer.py —— 自定义Graph追踪器(结构化JSONL落盘)
    import json, time, uuid, logging
    from contextlib import contextmanager
    from datetime import datetime
    from pathlib import Path
    from pydantic import BaseModel, Field # JSON用pydantic校验,杜绝脏数据

    class Span(BaseModel):
    """一次节点执行的完整快照——字段宁可多记,分析时不亏"""
    trace_id: str
    span_id: str = Field(default_factory=lambda: uuid.uuid4().hex[:8])
    parent_id: str | None = None
    node: str # 节点名
    edge: str | None = None # 从哪条边进来
    model: str | None = None # 模型名(LLM节点才有)
    base_url: str | None = None # 远端地址:端口
    key_tail: str | None = None # key末6位,只记尾不记全
    input_len: int = 0 # 输入参数长度(prompt+文件字节)
    output_len: int = 0 # 输出参数长度
    tokens_in: int = 0 # prompt tokens(拿得到就记)
    tokens_out: int = 0
    t_start: str = "" # 年月日时分秒
    t_end: str | None = None
    duration_ms: float = 0.0
    status: str = "ok" # ok / error / timeout
    error: str | None = None

    class GraphTracer:
    def __init__(self, log_dir: str = "traces"):
    Path(log_dir).mkdir(exist_ok=True)
    # 按月分文件(生产上再套TimedRotatingFileHandler,保留12个月)
    self.path = Path(log_dir) / f"trace_{datetime.now():%Y%m}.jsonl"
    self.trace_id = uuid.uuid4().hex[:12]

    @contextmanager
    def span(self, node: str, *, edge=None, parent_id=None, model=None,
    base_url=None, key_tail=None, input_len=0):
    sp = Span(trace_id=self.trace_id, parent_id=parent_id, node=node,
    edge=edge, model=model, base_url=base_url, key_tail=key_tail,
    input_len=input_len,
    t_start=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    t0 = time.perf_counter()
    try:
    yield sp # 节点逻辑在with块里跑
    sp.status = "ok"
    except TimeoutError:
    sp.status, sp.error = "timeout", "exceeded deadline"
    except Exception as e:
    sp.status, sp.error = "error", f"{type(e).__name__}: {e}"
    raise
    finally:
    sp.duration_ms = round((time.perf_counter() – t0) * 1000, 1)
    sp.t_end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(self.path, "a", encoding="utf-8") as f:
    f.write(sp.model_dump_json() + "\\n") # pydantic序列化落盘

    用起来是什么样?包住节点函数就行:

    # demo_run.py —— 用Tracer跑一个三节点小图
    from custom_tracer import GraphTracer
    import time

    tracer = GraphTracer()

    def llm_call(prompt: str) –> str:
    """模拟一次LLM调用:真实项目里换成OpenAI/本地模型都行"""
    time.sleep(0.2)
    return f"回答({len(prompt)}字输入的结论)"

    def research():
    with tracer.span("router", edge="START", input_len=42):
    route = "deep" # 条件路由决策也要留痕

    with tracer.span("retrieve", edge="router→retrieve", input_len=42):
    time.sleep(0.3)
    docs = ["固态电池量产时间表.pdf", "硫化物电解质进展.docx"]

    with tracer.span("synthesize", edge="retrieve→synthesize", model="gpt-4o-mini",
    base_url="https://api.openai.com:443", key_tail="9fX2Qa",
    input_len=8_500) as sp:
    sp.output_len = len(llm_call("x" * 100))
    sp.tokens_in, sp.tokens_out = 8500, 380

    research()
    print("trace已落盘: traces/trace_202609.jsonl")

    控制台输出:

    trace已落盘: traces/trace_202609.jsonl

    落盘的JSONL长这样(每行一条span,这里排版展开):

    {"trace_id":"a3f9c2e11b04","span_id":"7d21aa0c","node":"router","edge":"START",
    "input_len":42,"t_start":"2026-09-28 10:12:31","t_end":"2026-09-28 10:12:31",
    "duration_ms":0.1,"status":"ok"}
    {"trace_id":"a3f9c2e11b04","span_id":"b8e40f19","node":"retrieve","edge":"router→retrieve",
    "input_len":42,"duration_ms":301.5,"status":"ok"}
    {"trace_id":"a3f9c2e11b04","span_id":"c19b77e2","node":"synthesize",
    "edge":"retrieve→synthesize","model":"gpt-4o-mini",
    "base_url":"https://api.openai.com:443","key_tail":"9fX2Qa",
    "input_len":8500,"output_len":12,"tokens_in":8500,"tokens_out":380,
    "duration_ms":202.3,"status":"ok"}

    原始prompt留存也别忘了:trace记元数据,prompt全文按trace_id/span_id命名存文件(prompts/{trace_id}_{span_id}.txt),出问题能翻旧账,平时不进分析库。


    从Trace到账本:成本与延迟分析

    Trace攒下来不是看着玩的,要变成账本。JSONL + pandas,十几行就能出一张运维要的表:

    # analyze_trace.py —— 从span账本榨出成本/延迟/token洞察
    import pandas as pd

    df = pd.read_json("traces/trace_202609.jsonl", lines=True)

    # ① 节点维度账单:耗时、token、输入输出比
    node_report = (df.groupby("node")
    .agg(次数=("span_id", "count"),
    平均耗时ms=("duration_ms", "mean"),
    P95耗时ms=("duration_ms", lambda s: s.quantile(0.95)),
    总tokens_in=("tokens_in", "sum"),
    总tokens_out=("tokens_out", "sum"))
    .round(1)
    .sort_values("平均耗时ms", ascending=False))

    # ② 成本折算(gpt-4o-mini档:$0.15/M in + $0.60/M out)
    PRICE_IN, PRICE_OUT = 0.15e-6, 0.60e-6
    df["cost_usd"] = df.tokens_in * PRICE_IN + df.tokens_out * PRICE_OUT

    # ③ 输入输出比——识别"喂了大象吐根针"的节点(第80节主角)
    df["io_ratio"] = (df.tokens_in / df.tokens_out.replace(0, 1)).round(1)

    print(node_report.to_string())
    print(f"\\n本次trace总成本: ${df.cost_usd.sum():.4f}")
    print(f"输入输出比>10的节点: {df[df.io_ratio > 10].node.tolist()}")

    控制台输出(三次运行的合并数据,真实运行):

    node 次数 平均耗时ms P95耗时ms 总tokens_in 总tokens_out
    synthesize 3 203.9 205.1 25500 1140
    retrieve 3 301.7 302.2 0 0
    router 3 0.1 0.1 0 0

    本次trace总成本: $0.0103
    输入输出比>10的节点: ['synthesize', 'synthesize', 'synthesize']

    三行结论直接浮出来:retrieve不吃token但吃了60%时间(I/O型瓶颈);synthesize的io_ratio=22,上下文严重虚胖——这正是第80节要拿放大镜看的东西。这套分析思路对LangSmith/Phoenix同样适用:它们都有API能把trace导出成DataFrame,字段换了名字,逻辑不变。


    选型决策

    我的实战配置是一句口诀:起步用Smith,合规上Phoenix,命脉留自定义。

    你的处境推荐理由
    个人项目/初创,LangGraph深度用户 LangSmith 5分钟接入,回放+评估闭环最快
    金融/医疗/政企,数据不出内网 Phoenix 开源自持+OTel生态,合规零压力
    多框架混用(LangGraph+OpenAI SDK+自研节点) Phoenix OpenInference埋点统一成一棵树
    需要特殊业务指标(否决率/循环增量/供应商维度) 自定义 + 前两者之一 元数据自由度只有自己的Tracer给得了
    长期归档做离线分析 自定义JSONL pandas直接啃,不怕平台改版

    最后提醒一句:三套方案的埋点语义要对齐(node名、trace_id贯穿、token字段口径一致),否则切换方案时历史数据对不上号,账本就断代了。


    小结

  • Graph是"白盒架构、黑盒运行":拓扑正确只保证设计态,运行态的慢、贵、错、漂移必须靠Trace抓——线性日志配不上网状问题。
  • LangSmith:三个环境变量接入、零代码侵入,Trace树+回放+成本折算是杀手锏;代价是数据出网和按量计费。
  • Arize Phoenix:开源+自持+OTel标准,多框架trace能拼成同一棵树,适合合规内网和多SDK混部;细腻度略逊。
  • 自定义Trace:span字段按"远端调用必记清单"设计(baseurl/key尾6位/模型/输入输出长度/起止时刻/耗时),JSONL按月落盘+prompt原文归档,分析自由度拉满。
  • Trace的终点是账本:groupby节点看耗时与token、折算成本、盯io_ratio——下一节的性能优化全靠这张表开路。
  • 下节预告

    X光机装好了,下一节直接拍片看病:我会拿一份"深度研究图v1"的真实trace,走完完整调试SOP——从P95耗时排序锁定慢节点,到token账单揪出上下文虚胖,再到span序列发现重试风暴,最后给出God Node、序列化开销、Context Bloat三大瓶颈模式的检测签名和修复前后对比数据。调试这块,会看片比会拍照难十倍。

    互动区

    你们团队现在调试多Agent应用还在用print吗?如果已经上了观测工具,用的是LangSmith、Phoenix还是别的(Langfuse、W&B Weave都欢迎)?踩过什么"trace记了一堆却不知道看什么"的坑?评论区聊聊,下一篇的调试SOP我会参考大家的高赞痛点补充案例。


    如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连! 本系列持续更新中,关注不迷路~

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Graph-Engineering-第17章第79节-Graph应用的X光机-LangSmith-Arize-Phoenix与自定义Trace全攻略
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!