Tracely手动Span API:agent/tool/llm/thinking上下文管理器完全指南
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 包)的埋点有三层路线:
- 自动埋点(默认):
tracely.init(instrument="auto")自动接管 OpenAI/Anthropic/LangChain 等库,零 Span 代码; @observe装饰器:给任意函数加 Span,自动捕获入参/返回/异常;- 手动 Span:本文主角。每个
with块就是一个 Span,嵌套即建树,无需任何手动父子接线。
手动 Span 不是"退而求其次"的 fallback——它与自动埋点混用是常态:手动 Span 会嵌在自动采集的 trace 里,tracely.trace() 设置的运行上下文(agent、conversation 等)会经由自定义 SpanProcessor 自动流到块内每一个 Span。
埋完之后,一次 Agent 运行的完整调用树就是下面这样:AGENT 根节点之下,THINKING、SKILL、GENERATION、DELEGATE、RETRIEVER 各就各位,右侧还能看到该 Span 的 INPUT 与时长——这正是手动 Span API 想要你亲手画出的形状。
所有上下文管理器都定义在 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 树:
进阶:更多 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 上会留下这样的判定结果:
快速要点回顾 📌
| 要点 | 说明 |
|---|---|
| 建树方式 | 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 个标注函数,就是全部你需要记住的东西。


