跳转到内容

上下文追踪

上下文追踪让宿主应用在不改变 agent loop 的情况下观察运行过程。SDK 会通过 很小的 ContextTracer 接口输出结构化事件。内置 tracer 可以把事件写成本地 JSONL,也可以把事件投影到 LangSmith 或 Langfuse。

追踪是 append-only 的可观测性能力,不等于 resume。当前 Agent 的对话状态仍然 只在实例生命周期内保存在内存中。

需要本地调试、审计日志或测试产物时,可以使用 createJsonlContextTracer()

import { createAgent, createJsonlContextTracer } from "agent-lattice";
const tracer = createJsonlContextTracer({
path: ".agent-runs/session.jsonl",
});
const agent = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
tracer,
});
await agent.prompt("记住我的名字是 Ada。", {
stream: false,
});

每一行 JSONL 都是一个独立对象:

{
"version": 1,
"timestamp": "2026-06-28T12:00:00.000Z",
"session_id": "session-id",
"run_id": "run-id",
"seq": 1,
"source": { "kind": "agent", "name": "agent" },
"type": "user_message",
"data": {
"message": {
"role": "user",
"content": "记住我的名字是 Ada。"
}
}
}

如果传入 dir 而不是 path,tracer 会按 session 写文件:

const tracer = createJsonlContextTracer({
dir: ".agent-runs",
});

需要把 SDK 运行显示成 LangSmith trace 时,可以使用 createLangSmithContextTracer()。SDK 直接依赖 langsmith,并复用它的 官方 RunTree 类型,开箱即用。从 0.17.0 起默认使用内置的 RunTree; 只有在注入自定义 runtime 或测试 fake 时才需要传 RunTreerunTree

按 LangSmith 官方方式配置环境变量:

Terminal window
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=<your-langsmith-api-key>
LANGSMITH_PROJECT=<your-langsmith-project>
# 只有组织级或多 workspace API key 才需要。
LANGSMITH_WORKSPACE_ID=<your-langsmith-workspace-id>
import { createAgent, createLangSmithContextTracer } from "agent-lattice";
const tracer = createLangSmithContextTracer({
projectName: process.env.LANGSMITH_PROJECT,
workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
tags: ["local-debug"],
});
const agent = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
tracer,
});
try {
await agent.prompt("Trace this run.", { stream: false });
} finally {
await tracer.close?.();
}

短生命周期的测试进程退出前,请 close 或 flush LangSmith tracer。这样会等待 LangSmith 内部 trace batch 队列,包括把根 run 标记为已结束的最后一次 patch。

如果你更想显式传值,也可以直接传给 SDK tracer。workspaceId 是可选的, 只用于选择 LangSmith workspace;它不是 trace project 名。

import { createLangSmithContextTracer } from "agent-lattice";
const tracer = createLangSmithContextTracer({
apiKey: process.env.LANGSMITH_API_KEY,
apiUrl: process.env.LANGSMITH_ENDPOINT,
projectName: process.env.LANGSMITH_PROJECT,
// 可选:只有 LangSmith 要求显式指定 workspace 时才传。
workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
});

LangSmith tracer 和 JSONL tracer 一样只是实现 ContextTracer,所以 agent loop 不需要知道具体观测后端。

LangSmith 会收到这样的投影:

SDK 事件序列 LangSmith 投影
run_startresult chain run;没有 parent_run_id 时是根节点,否则挂在对应的父 chain 下。
model_requestassistant_message llm run。
tool_usetool_result tool run。
team_message 和其它辅助事件 当前 chain 上的 run event。

每个 run 的耗时对应真实的执行时间。tool run 从该工具的 handler 真正开始时计时, 所以串行调用在时间轴上依次排开,并发调用才会重叠。llm run 在模型请求结束时收尾, 不包含宿主消费流式事件所花的时间。tool run 还会把工具的 description 放进 tool_description metadata,trace 里能看到工具被声明成做什么,而不只是输入输出。

可以用 createCompositeContextTracer() 把同一份 trace stream 同时发送到多个 sink:

const tracer = createCompositeContextTracer([
createJsonlContextTracer({ path: ".agent-runs/session.jsonl" }),
createLangSmithContextTracer({
projectName: process.env.LANGSMITH_PROJECT,
workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
}),
]);

如果 composite tracer 里包含 LangSmith,也在同一个 finally 里 close 这个 composite tracer;它会把 close() 转发给每个子 tracer。

需要 0.19.0 及以上版本。

想让 SDK 运行以 Langfuse trace 的形式呈现时,使用 createLangfuseContextTracer()。适配器面向当前的 Langfuse JS SDK (@langfuse/tracing v5),它基于 OpenTelemetry:在进程启动时注册一次 LangfuseSpanProcessor,之后 tracer 开箱即用。

用 Langfuse 的标准环境变量完成配置:

Terminal window
LANGFUSE_PUBLIC_KEY=<your-langfuse-public-key>
LANGFUSE_SECRET_KEY=<your-langfuse-secret-key>
LANGFUSE_BASE_URL=https://us.cloud.langfuse.com # 或你的自建地址
Terminal window
npm install @langfuse/otel @opentelemetry/sdk-trace-node
// instrumentation:在 agent 运行前注册 span processor。
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
export const langfuseSpanProcessor = new LangfuseSpanProcessor();
const tracerProvider = new NodeTracerProvider({
spanProcessors: [langfuseSpanProcessor],
});
tracerProvider.register();
import { createAgent, createLangfuseContextTracer } from "agent-lattice";
import { langfuseSpanProcessor } from "./instrumentation";
const tracer = createLangfuseContextTracer({
// tracer.flush()/close() 会排空它,保证短生命周期进程退出前
// span 已送达 Langfuse。
spanProcessor: langfuseSpanProcessor,
tags: ["local-debug"],
});
const agent = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
tracer,
});
try {
await agent.prompt("Trace this run.", { stream: false });
} finally {
await tracer.close?.();
}

startObservation 默认使用内置的 @langfuse/tracing 函数;只有在注入自定义 运行时或测试替身时才需要显式传入。

Langfuse 每次 SDK 查询收到一条 trace:

SDK 事件序列 Langfuse 投影
run_startresult chain observation;没有 parent_run_id 时是 trace 根节点,否则嵌套在另一个 chain 下。
model_requestassistant_message 子级 generation observation,带 model 属性。
tool_usetool_result 子级 tool observation。
team_message 等其他辅助事件 挂在当前 chain 上的自动结束 event observation。

trace 根 observation 携带 trace 名称、session.idlangfuse.trace.tags 属性——与 propagateAttributes 写到活跃 span 上的属性一致——因此在 Langfuse UI 里 trace 仍然可以按会话和标签聚合、筛选。出错的 result 和工具错误会把 observation 的 level 置为 ERROR 并附上 statusMessage

类型 说明
run_start Agent run 元数据,包括模型和工具名。
user_message 加入模型上下文的用户消息或工具结果消息。
model_request 模型请求上下文,包括选中的 messages、tools 和 stream 模式。
assistant_message 模型客户端返回的 assistant message。
tool_use Assistant 请求的工具调用,包含工具的 description(0.17.0 起)。
tool_result SDK 产生并回填给模型的工具结果。
result 本次 query 的最终结果,包括成功和错误 subtype。

seq 在同一个 tracer 实例中单调递增。UI 或日志管线可以用 session_idrun_idsource 来分组。

team.query()createTeamRunner().query() options 中传入 tracer,可以把 同一个追踪 sink 传递给被委托的子 agent。

for await (const event of team.query("请 engineering 检查 tracing 设计。", {
tracer,
})) {
console.log(event);
}

每次 team.query()team.prompt() 只创建一个 Team 顶层 run。Lead 首次处理、 各个 Member 执行以及 Lead 收到报告后的后续处理,都会成为这个顶层 run 的子节点。 这些节点共享同一个 trace session_id,因此 LangSmith 会把一次完整 handoff 显示成 一条 trace,而不是多个互不相关的顶层 trace。

共享 trace session 不会替换 Agent 自己的 SDK session。Trace metadata 会把 Agent 原来的 session 记录为 agent_session_id,SDK 对外消息中的 session_id 也保持不变。

被委托的 agent 会带上运行来源:

{
"source": {
"kind": "team_member",
"name": "engineering",
"member": "engineering",
"mailbox": "engineering"
},
"type": "result",
"data": {
"subtype": "success",
"result": "Engineering result"
}
}

即使根 agent 通过 team 或嵌套 team 委托工作,JSONL 也能保持可读。

ContextTracer 很小,宿主可以写入自己的数据库或可观测性系统。端口对象只暴露 方法——failOnError 在工厂创建 tracer 时绑定。自定义 sink 通过 defineContextTracer() 实现。需要 0.17.0 及以上版本。

import { defineContextTracer } from "agent-lattice";
const tracer = defineContextTracer({
async onEvent(event) {
// TODO: 替换成你自己的数据库写入逻辑。
},
async flush() {
// TODO: 替换成你自己的缓冲区 flush 逻辑。
},
});

只有当 trace 持久性是产品契约的一部分时,才建议在工厂里传 failOnError: true。 默认情况下,tracing 应该是旁路观察,不应该让 agent run 失败。

Trace 事件可能包含 prompt、模型上下文、工具输入和工具输出。写入 JSONL 前可以 使用 redact 执行本地策略。

redact 是你传给 SDK 的回调函数。JSONL tracer 在写入每个 trace event 前, 会先把 event 传给这个回调。回调可以返回原 event、返回修改后的副本,或者返回 undefined 来跳过这个 event。这只会改变写入 trace 文件的内容,不会改变真正 发给模型的请求。

const tracer = createJsonlContextTracer({
path: ".agent-runs/session.jsonl",
redact(event) {
if (event.type === "model_request") {
return {
...event,
data: {
...event.data,
messages: "[redacted]",
},
};
}
return event;
},
});

这个例子会在持久化前把 model_request.data.messages 替换成 "[redacted]"。这个字段通常可能包含用户 prompt、对话历史和工具结果,所以常常 是本地隐私策略优先处理的位置。其它事件类型会原样写入。

redact 返回 undefined 可以跳过整个事件:

const tracer = createJsonlContextTracer({
path: ".agent-runs/session.jsonl",
redact(event) {
if (event.type === "tool_result") {
return undefined;
}
return event;
},
});