AI Agent生产就绪:工具调用链与OpenTelemetry实践
AI Agent 从 demo 进入生产,最容易翻车的地方通常不是模型本身,而是工具调用链。
一个 Agent 只要开始调用数据库、HTTP API、RAG 检索、工单系统或内部审批流,它就已经变成一个分布式系统。生产就绪的重点,是让每一次工具调用都有边界、有证据、可恢复、可审计。
本文记录一套我更常用的做法:先统一工具接口,再把 OpenTelemetry 埋到 Agent 编排层和工具执行层。
1. 工具调用先做统一契约
不要让每个工具各写各的参数、错误码和日志字段。否则一旦 Agent 调错工具,排查会非常痛苦。
建议最少固定这些字段:
tool_name:工具名称tool_version:工具版本trace_id:链路追踪 IDinput_schema_version:输入结构版本timeout_ms:超时时间retry_count:当前重试次数error_code:统一错误码
一个极简接口可以这样写:
type ToolContext = {
traceId: string;
tenantId: string;
userId?: string;
timeoutMs: number;
};
type ToolResult<T> = {
ok: boolean;
data?: T;
errorCode?: string;
retryable?: boolean;
evidence?: Record<string, unknown>;
};
interface AgentTool<I, O> {
name: string;
version: string;
run(input: I, ctx: ToolContext): Promise<ToolResult<O>>;
}
这里的重点不是类型写得多漂亮,而是让所有工具都能被同一套执行器治理。
2. 执行器负责超时、重试和降级
不要把重试逻辑散落在每个工具里。工具只负责业务动作,执行器负责生产控制面。
async function executeTool<I, O>(
tool: AgentTool<I, O>,
input: I,
ctx: ToolContext,
): Promise<ToolResult<O>> {
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const startedAt = Date.now();
try {
const result = await withTimeout(
tool.run(input, ctx),
ctx.timeoutMs,
);
return {
...result,
evidence: {
...result.evidence,
attempt,
latencyMs: Date.now() - startedAt,
},
};
} catch (error) {
const retryable = isRetryableError(error);
if (!retryable || attempt === maxAttempts) {
return {
ok: false,
errorCode: normalizeError(error),
retryable,
evidence: {
attempt,
latencyMs: Date.now() - startedAt,
},
};
}
}
}
return { ok: false, errorCode: "UNKNOWN_TOOL_FAILURE" };
}
实际项目里还要加熔断、限流和幂等键。尤其是会写数据的工具,必须区分“可以重试”和“不能重试”。
3. OpenTelemetry 要埋在 Agent 决策边界
很多团队只记录 HTTP 请求日志,这对 Agent 不够。Agent 的关键链路是:用户输入、模型选择、工具选择、工具执行、结果合成。
每一步都应该有 span。
import { trace, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("agent-runtime");
async function tracedToolCall<I, O>(
tool: AgentTool<I, O>,
input: I,
ctx: ToolContext,
) {
return tracer.startActiveSpan(`tool.${tool.name}`, async (span) => {
span.setAttribute("tool.name", tool.name);
span.setAttribute("tool.version", tool.version);
span.setAttribute("tenant.id", ctx.tenantId);
span.setAttribute("timeout.ms", ctx.timeoutMs);
try {
const result = await executeTool(tool, input, ctx);
span.setAttribute("tool.ok", result.ok);
if (!result.ok) {
span.setAttribute("tool.error_code", result.errorCode ?? "unknown");
span.setStatus({ code: SpanStatusCode.ERROR });
}
return result;
} finally {
span.end();
}
});
}
注意:不要把完整 prompt、个人信息、密钥或原始业务数据直接写进 span attribute。生产环境里通常只记录摘要、hash、版本号和必要的错误分类。
4. 审计日志和观测日志分开
可观测性是为了排障,审计是为了复盘和合规。两者不要混在一张日志表里。
观测日志关注:延迟、错误率、重试、token 成本、模型版本。
审计日志关注:谁触发了什么动作、Agent 为什么选择这个工具、工具返回了什么关键证据、最终是否影响业务状态。
如果是金融、工业 IoT 或内部审批系统,我会要求审计日志至少做到 append-only,并保留 trace_id,这样能从审计事件反查完整调用链。
5. 上线前检查清单
我通常会问这几件事:
- 工具失败时,Agent 是重试、降级、转人工,还是继续编答案?
- 每个工具有没有超时和统一错误码?
- prompt、模型、工具 schema 是否有版本号?
- OpenTelemetry 能不能看到一次请求里的模型调用和工具调用?
- 审计日志能不能解释“为什么做了这个动作”?
- 写操作有没有幂等键和回滚方案?
这些问题如果答不上来,Agent 还不适合直接接生产业务。
总结
生产级 AI Agent 不是“模型加几个工具”这么简单。
真正要补的是三条链:调用链、证据链、恢复链。工具调用框架负责把外部依赖管住,OpenTelemetry 负责让行为可见,审计日志负责让关键动作可追溯。
如果你的团队正在把 Agent 接进真实业务,建议先做一次生产就绪检查,把 P0/P1 风险列出来,再决定是继续扩功能,还是先补工程底座。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)