登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

AI OpenTelemetry 补 trace 怎样处理

来源:17golang原创

时间:2026-10-22 07:00:21 219浏览 收藏

AI 应用出现“响应很慢”时,只在入口打一个 span 往往不够:你还需要把模型请求、工具调用和重试放在同一条 trace 里。最稳妥的补法是沿着现有的请求 Context 创建子 span,再用低基数的 GenAI 属性描述调用;不要把完整提示词、用户输入和模型输出默认塞进 span。

官方地址:https://opentelemetry.io/docs/

补 trace 的关键不是“每个函数都打点”,而是让入口、模型和工具保持正确的父子关系,并留下足够判断耗时、错误和重试的证据。

先把 AI 请求拆成三层边界

我在给 Agent 补观测时,最容易犯的错是只包住 SDK 的一行调用。这样能看到模型耗时,却看不出请求是在检索、工具执行还是模型重试上变慢。一个可维护的边界通常是:

  • 入口 span:代表一次用户请求或一次 Agent 调度,记录服务名、路由和请求结果。
  • 模型 span:代表一次 chat、generate_content 或 embedding 调用,记录 provider、模型名、流式开关和 token 统计。
  • 工具 span:代表一次检索、数据库查询或外部 API 调用,记录工具名、低基数结果和错误类型。
AI 请求入口、模型调用和工具调用的 OpenTelemetry 静态结构图
图1:结构说明图,查看入口、模型和工具三层 span 的静态关系;它是原创说明图,不是运行截图。

用当前 Context 补模型和工具 span

OpenTelemetry Python 的手动埋点可以用 start_as_current_span 创建当前 span。只要入口 span 已经处于当前 Context,模型和工具 span 就会自动成为子 span;异步任务或跨线程时,则要确认 Context 没有在调度边界丢失。

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("ai-service")

def call_tool(tool_name, fn):
    # 工具 span 只记录工具身份和低基数结果,不记录完整用户数据。
    with tracer.start_as_current_span("execute_tool " + tool_name) as span:
        span.set_attribute("gen_ai.operation.name", "execute_tool")
        span.set_attribute("gen_ai.tool.name", tool_name)
        try:
            result = fn()
            span.set_attribute("tool.result", "ok")
            return result
        except Exception as exc:
            # 把错误类型写入 span,正文或敏感参数留在受控日志中。
            span.record_exception(exc)
            span.set_status(Status(StatusCode.ERROR, "tool failed"))
            span.set_attribute("error.type", type(exc).__name__)
            raise

def call_model(client, model, messages):
    # 模型 span 继承当前 Context,自动挂到入口或 Agent span 下。
    with tracer.start_as_current_span("chat " + model) as span:
        span.set_attribute("gen_ai.operation.name", "chat")
        span.set_attribute("gen_ai.provider.name", "configured-provider")
        span.set_attribute("gen_ai.request.model", model)
        span.set_attribute("gen_ai.request.stream", False)
        try:
            response = client.chat(messages)
            usage = getattr(response, "usage", None)
            if usage is not None:
                # token 数适合用于聚合;不要把 prompt 原文当作属性。
                span.set_attribute("gen_ai.usage.input_tokens", usage.input_tokens)
                span.set_attribute("gen_ai.usage.output_tokens", usage.output_tokens)
            return response
        except Exception as exc:
            span.record_exception(exc)
            span.set_status(Status(StatusCode.ERROR, "model failed"))
            span.set_attribute("error.type", type(exc).__name__)
            raise

这段代码只展示补 trace 的边界,client.chat 和工具函数仍由业务实现。模型调用若由已有自动 instrumentation 产生 span,应先确认是否已经有重复的模型 span,再决定只补 Agent/工具层,避免一条调用出现两套难以区分的记录。

属性要少而稳定,内容采集要显式开启

OpenTelemetry 的语义约定提供了统一的 gen_ai.operation.name、gen_ai.provider.name 和 gen_ai.request.model 等字段。它们适合检索和聚合;用户问题、系统提示词、完整工具参数则可能含有个人信息或业务机密,应当默认关闭,或先做截断、脱敏和采样。

建议把下面几类信息分开处理:

信息建议用途
模型名、provider、operation默认记录按调用类型和模型聚合耗时
输入/输出 token有返回值才记录分析成本和上下文膨胀
错误类型、异常事件记录低基数类型,限制堆栈范围区分超时、鉴权和服务端错误
提示词、输出、工具参数显式开关后再脱敏采集只在确有排障需要时关联内容
AI trace 中模型属性、token 统计、工具错误和内容采集边界的静态结构图
图2:结构说明图,查看哪些属性适合聚合、哪些内容需要显式控制;它不是观测平台截图。

用 span 证据反向确认到底补对没有

不要只看“有没有 trace”。打开一条慢请求后,先确认入口 span 是根节点,模型和工具是否挂在它下面;再比较各子 span 的时长。如果模型 span 很短而工具 span 很长,问题不在模型;如果同一工具出现多个连续子 span,优先检查重试或循环;如果模型属性存在但没有父节点,通常是异步调度时 Context 没有传过去。

导出端可以使用 OTLP Collector 或已有后端。开发期先用控制台 exporter 看父子关系,生产再通过采样、脱敏和数据保留策略控制成本。确认时至少保留三条证据:trace ID 能贯穿入口到子 span、模型 span 有稳定的 operation/model 属性、失败调用带有 error.type 和异常事件。

常见问题

只给模型调用加 span,为什么还是看不出 Agent 慢在哪里?

因为检索、工具和重试没有独立边界。模型 span 只能回答一次模型调用花了多久,不能解释它之前或之后的业务耗时。

要不要把完整 prompt 写进 trace?

默认不要。先用 token 数、消息数量、模板版本和脱敏后的摘要满足排障;只有在明确的合规范围内开启内容采集,并设置截断、访问控制和保留期限。

最终可以把补 trace 的判断浓缩成一句话:先保证 Context 关系正确,再补低基数 GenAI 属性,最后用 span 时长和错误证据反向定位。这样既能看见 AI 请求的关键链路,也不会把可观测性变成敏感内容的另一个存储点。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>