Tracely手动Span API:agent/tool/llm/thinking上下文管理器完全指南

原创2026-10-11 14:34:241,001 阅读

Tracely手动Span API:agent/tool/llm/thinking上下文管理器完全指南

Tracely 是一个 Trace 原生的 AI Agent CI/CD 平台,生产环境的失败会被自动聚类并冻结成回归用例,最终在 CI 中以 $0 成本回放、拦截 PR。它的 SDK 默认提供零代码的自动埋点,但当你的 Agent 有自定义检索、多智能体交接或自研 Provider 客户端时,手动 Span API 就是那个"逃生舱口"——本文用最小代码讲清 agent()、llm()、tool()、thinking() 四个核心上下文管理器,以及配套的标注与失败信号 API。

手动 Span API 是什么:自动埋点的逃生舱口

Tracely SDK(tracely-ai 包)的埋点有三层路线:

  1. 自动埋点(默认):tracely.init(instrument="auto") 自动接管 OpenAI/Anthropic/LangChain 等库,零 Span 代码;
  2. @observe 装饰器:给任意函数加 Span,自动捕获入参/返回/异常;
  3. 手动 Span:本文主角。每个 with 块就是一个 Span,嵌套即建树,无需任何手动父子接线。

手动 Span 不是"退而求其次"的 fallback——它与自动埋点混用是常态:手动 Span 会嵌在自动采集的 trace 里,tracely.trace() 设置的运行上下文(agent、conversation 等)会经由自定义 SpanProcessor 自动流到块内每一个 Span。

埋完之后,一次 Agent 运行的完整调用树就是下面这样:AGENT 根节点之下,THINKING、SKILL、GENERATION、DELEGATE、RETRIEVER 各就各位,右侧还能看到该 Span 的 INPUT 与时长——这正是手动 Span API 想要你亲手画出的形状。

Tracely 手动 Span 生成的 Trace 时间轴:AGENT、THINKING、GENERATION、TOOL 等 Span 嵌套树与右侧输入输出面板

所有上下文管理器都定义在 sdk/tracely_sdk/init.py 中,官方示例见 sdk/examples/manual_spans.py。

核心四件套:agent / llm / tool / thinking 用法

1. agent() — 运行的根 Span(AGENT)

agent() 标记"一次 Agent 运行",通常作为整棵树的根。常用参数:

参数 作用
slug Agent 名称(位置参数),写入 tracely.agent.id
version 版本(如 "v3")——自动注册进 agent registry,是回归门禁的锚点
conversation 会话 ID,把多次运行串成一个可回放的多轮会话
turn / user 轮次序号 / 终端用户 ID
handoff_from 多智能体交接时,记录"谁把任务委派给了我"

2. llm() — 模型调用(GENERATION)

llm(model, *, agent, temperature, ..., tool_calls) 把一次模型请求变成 GENERATION Span:

  • 采样参数(temperature/top_p/max_tokens/seed 等)会写成标准的 gen_ai.request.* 属性,直接渲染在 Span 的 Metadata 里;
  • tool_calls= 是"静默失败探测器":记录模型请求了哪些工具,哪怕工具最终根本没执行。"请求了却从未执行"的工具调用,正是 tool_consistency 结构评估器专门抓的问题。

3. tool() — 工具执行(TOOL)

tool(name, *, agent) 包裹一次工具/函数执行,写入 gen_ai.tool.name。失败时不要吞异常,而是调用 tracely.error(span, message) 把 Span 标记为 level=ERROR——这是 Tracely 故障检测、聚类和门禁唯一的失败信号。即使 Agent 自己优雅地处理了错误,也要照样标记工具 Span:你想被评估的是"处理过程",而不是伪装成功。

4. thinking() — 推理(THINKING)

thinking() 是一个独立步骤,不是包装器。它记录 Agent 自己做的规划/推理,推理文本放 set_io(output=...),推理 token 放 set_usage(thinking_tokens=...)。

⚠️ 常见误区:不要把 LLM 调用包进 thinking() 里——那会产生起止时间完全相同的 THINKING 和 GENERATION 两个 Span,时间轴上同一秒出现两次,无法区分。推理模型(extended thinking)的推理内容建议走流式调用,SDK 会自动在 GENERATION 上拆出"思考 X 秒 + 作答 Y 秒"。

标注 Span:set_io / set_usage / error 一览

Span 建好树后,用一组"标注函数"给它填肉(源码见 sdk/tracely_sdk/init.py):

调用 写入内容
set_io(span, *, input=None, output=None) 输入/输出,output 建议直接放 chat API 返回的 completion 对象
set_usage(span, *, input_tokens, output_tokens, thinking_tokens, ...) token 用量 → gen_ai.usage.*,成本由模型名 + token 数推导,永远不要手动写 cost 字段
set_metadata(span, **kv) 任意可搜索标签,如 prompt 版本、租户
error(span, message="") 标记 level=ERROR——故障信号本尊
flush() 进程退出前强制冲刷 OTLP exporter,别忘了调

多模态 I/O 也走 set_io:消息数组会渲染成对话气泡,image_url / input_file 类型的内容块会显示缩略图和文件芯片。

完整示例:含工具故障与恢复的支持 Agent

下面是最小可运行的手动 Span 场景(完整版无需任何 API Key,见 sdk/examples/manual_spans.py):

tracely.init(endpoint="http://localhost:8000", api_key="tracely_dev_key",
             service_name="support-agent", env="prod", instrument=False)

with tracely.agent("support-agent", version="v3",
                   conversation="manual_spans", turn=0) as a:
    tracely.set_io(a, input=question)

    with tracely.thinking(agent="support-agent") as th:      # 推理步骤
        tracely.set_io(th, output={"role": "thinking",
                                  "content": "先查订单,再查库存。"})
        tracely.set_usage(th, thinking_tokens=40)

    with tracely.llm("gpt-4o", agent="support-agent",
                     tool_calls=["get_order_status", "check_inventory"]) as g:
        tracely.set_io(g, input=messages, output=completion)
        tracely.set_usage(g, input_tokens=180, output_tokens=24)

    with tracely.tool("check_inventory", agent="support-agent") as t:
        tracely.set_io(t, input={"sku": "SKU-COAT-01"})
        try:
            raise TimeoutError("inventory service timeout")
        except TimeoutError as e:
            tracely.error(t, f"inventory upstream timeout: {e}")   # ← 失败信号

    tracely.set_io(a, output=answer)

tracely.flush()

这段代码埋出的就是 Trace 列表里一行可展开的会话——点开即见 Span 树:

Tracely Traces 列表:Agent 运行按会话分组,展示 agent、时长、token 用量与评估结果

进阶:更多 Span 类型与多智能体交接

核心四件套之外,手动 API 还提供了一整套语义 Span(完整表格见 skills/tracely/references/manual.md):

  • skill(name, *, agent, version):Agent 主动选择执行的能力/剧本,介于 tool 与 agent 之间;
  • retriever() / embedding() / guardrail():RAG 三件套与安全策略检查;
  • chain(name, *, agent):分组 Span,比如把整个 RAG 流水线框起来;
  • delegate(to, *, agent, task):单独把"委派"这个动作变成 Span——"路由选对了没"和"被委派方干得好不好"是两种不同的故障,只有 delegate Span 能把它们分开。

多智能体交接只需嵌套 + 记边:

with tracely.agent("router", role="orchestrator", conversation="c1") as root:
    with tracely.agent("billing-agent", role="specialist",
                       conversation="c1", handoff_from="router"):
        ...

handoff_from 会记录 router → billing-agent 的边,驱动多智能体图视图。

手动埋点的终点:从 Trace 到 CI 门禁

手动 Span 的价值最终落在 Tracely 的 CI 故事上:生产失败被聚类 → 提升为回归用例 → tracely replay 在 CI 中离线确定性回放(无 API Key、零成本)。为此 SDK 提供了 call_tool() / call_llm() 缝隙 API——同一份代码,生产环境真实调用并记录输出,CI 回放时自动改喂录制的 fixture;生产中出错的调用会重新抛出 tracely.ToolError,让 Agent 自己的 try/except 原样运行。门禁失败时,PR 上会留下这样的判定结果:

Tracely CI 门禁运行详情:FAIL 判定、逐用例结果与 token/延迟统计

快速要点回顾 📌

要点 说明
建树方式 with 块嵌套即父子关系,零接线
根 Span agent(),version 参数是回归门禁的锚
失败信号 必须显式 error(span, msg),吞掉的异常对检测不可见
成本 由模型名 + set_usage token 推导,勿手动埋 cost
退出前 记得 flush()
全量手册 skills/tracely/references/manual.md
免 Key 完整示例 sdk/examples/manual_spans.py

自动埋点覆盖 80% 的日常场景;剩下 20% 的自定义检索、多智能体交接与自研客户端,交给手动 Span API——这 4 个上下文管理器加 3 个标注函数,就是全部你需要记住的东西。

登录后查看全文
Tracely-ai