基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-09 21:48

用 OpenTelemetry 串联 AI 决策与人工审批

dev.to · AI#可观测性#链路追踪#人工审批
Editor brief · 编辑速览

文章介绍如何将请求、上下文检索、模型调用和工具执行纳入分布式追踪,并保存追踪上下文。对于隔日发生的人工审批,则通过新追踪关联原始请求,同时讨论追踪能力的边界。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

退款回复由 AI 起草,隔天由同事批准,一周后客户提出异议。模型、输入和审批人的信息可能都在,却分散在不同系统里,无法可靠串起来。原文给出的办法是:用 OpenTelemetry 连接生成与审批过程,再用业务存储保留决策证据。

trace_id 适合串联一次决策的执行过程,但不能单独承担长期留证。 对正在做客服助手、工具调用和人工审核流程的团队,这个区分比多记几条模型日志更有用。

跨了一天的审批,应该怎样接回生成过程

W3C 于 2021 年 11 月 23 日发布的 Trace Context Recommendation,定义了通过 traceparent 请求头传播追踪上下文的方法。其中 trace-id 是一个 16 字节的值,同一次请求处理中的各个 span 共享它。span 可以理解为追踪链路里的一段操作。

在 AI 功能里,请求、检索上下文、调用 chat model-a、执行工具,可以属于同一条同步链路。原文示意中的 trace_id 4bf9... 就负责把这些步骤连起来。

人工审批另算。

草稿可能第二天才被打开,此时应该创建新的 trace,再通过 span link 指向生成草稿的原始上下文。这样既保留因果关系,也不会把一天的等待伪装成一个持续运行的操作。

下面这段代码解决两个问题:记录模型调用身份,并让保存后的草稿携带可供审批端恢复的追踪上下文。

from opentelemetry import trace
from opentelemetry.trace import Link, SpanKind
from opentelemetry.propagate import inject, extract

tracer = trace.get_tracer("support-assistant")
MODEL = "model-a-2026-01-15"   # 固定模型 ID,不使用别名

def draft_reply(ticket: dict, call_model) -> dict:
    with tracer.start_as_current_span("draft_reply"):
        with tracer.start_as_current_span(
                f"chat {MODEL}", kind=SpanKind.CLIENT) as span:
            span.set_attribute("gen_ai.operation.name", "chat")
            span.set_attribute("gen_ai.provider.name", "example")
            span.set_attribute("gen_ai.request.model", MODEL)
            resp = call_model(MODEL, ticket["text"])
            span.set_attribute("gen_ai.response.model", resp["model"])
            span.set_attribute("gen_ai.response.id", resp["id"])
        carrier = {}
        inject(carrier)              # 写入 W3C traceparent
        return {"ticket_id": ticket["id"], "text": resp["text"],
                "trace": carrier}    # 随草稿保存

def approve(draft: dict, reviewer_id: str) -> None:
    origin = trace.get_current_span(extract(draft["trace"]))
    with tracer.start_as_current_span(
            "review.approve",
            links=[Link(origin.get_span_context())]) as span:
        span.set_attribute("app.reviewer.id", reviewer_id)
        span.set_attribute("app.ticket.id", draft["ticket_id"])

关键是 inject(carrier) 所处的位置:模型调用的 span 已经结束,当前仍处于 draft_reply 内,因此保存的是草稿生成操作的上下文,审批通过它关联回原 trace,并非直接链接到模型调用的那个 span。

模型日志要回答「请求了谁,实际由谁处理」

原文采用 OpenTelemetry 的 GenAI semantic conventions,为不同供应商的调用提供统一字段。其客户端推理 span 名称采用 {gen_ai.operation.name} {gen_ai.request.model},例如 chat model-a。

字段要求也有区别:

  • gen_ai.operation.name 和 gen_ai.provider.name 为必需字段。
  • gen_ai.request.model 在可获得时要求记录。
  • gen_ai.response.model 和 gen_ai.response.id 为推荐字段。

同时保存请求模型和返回模型,是为了应对别名解析到新快照的情况。代码中的 model-a-2026-01-15 展示了固定模型 ID 的写法,不代表原文验证过某个真实模型的能力。

另一个容易影响排障的细节是重试:同一个逻辑操作的自动重试,应由一个 span 覆盖,避免让重试看起来像几次独立决策。

不过,这套约定仍标记为 Status: Development,且已迁到独立仓库。文章没有给出具体约定版本,接入时需要确认所用埋点库支持哪些字段,不能把文中的名称当成永远不变的接口。

追踪连得起来,不代表证据留得下来

原文建议默认不把指令、输入和输出写进 span。这些内容可能敏感,体积也可能很大。示例虽然把工单文本传给模型,但追踪属性只记录模型名称和标识,没有记录正文。

需要保留内容时,可以存到外部存储,再在 span 上记录引用,让正文拥有独立的访问控制。

更大的问题是采样和保留期限。Trace Context 的 sampled 标志只表示调用方可能记录了追踪数据,并不保证某次决策一定有完整 trace;追踪存储的保留窗口通常服务于调试,也可能早于争议发生时间结束。

因此,原文要求把草稿、模型、审批人和结果写进自行控制、明确保留期限的业务存储,以工单 ID 为查询入口,再附上 trace_id。审核人员通常拿着工单来查,不会先知道追踪 ID。

文中的测试使用 SDK 的内存导出器验证关联关系:

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
    InMemorySpanExporter)

def test_review_links_to_model_call():
    exporter = InMemorySpanExporter()
    provider = TracerProvider()
    provider.add_span_processor(SimpleSpanProcessor(exporter))
    trace.set_tracer_provider(provider)

    fake = lambda m, t: {"model": m, "id": "resp_1", "text": "ok"}
    draft = draft_reply({"id": "T-1", "text": "refund?"}, fake)
    approve(draft, "u_17")

    spans = {s.name: s for s in exporter.get_finished_spans()}
    chat, review = spans[f"chat {MODEL}"], spans["review.approve"]
    assert review.context.trace_id != chat.context.trace_id
    assert review.links[0].context.trace_id == chat.context.trace_id

最后两行验证的是审批与模型调用属于不同 trace,且审批 link 指向原 trace。它没有断言链接的具体 span_id,也没有验证生产环境中的持久化、采样或证据完整性。

我的判断

我认同把运行追踪与决策记录分开。相比只在日志里打印工单 ID,这套做法增加了操作间的结构化关系;相比把全部内容塞进追踪平台,它又能分别设置内容权限和保留期限。

但示例离完整方案还有距离。approve 没有显式清空当前上下文,独立运行时可以产生新 trace,接到已有 Web 请求链路中时则可能继承该请求。实际接入要明确审批应加入当前请求,还是另建根 trace。

原文也没有交代如何防止记录被修改、如何处理写入失败,或如何证明保存的是审批当时看到的草稿。它提供的是关联方案,不能据此推出完整审计能力。

此外,文章由 Claude 为 Obligra Verify 团队生成,声明核对过引用来源,并介绍了处于 public preview 的 Obligra Verify™。文中没有生产规模实测、性能对照或产品比较数据,架构建议可以参考,产品效果仍缺乏证据。

能拿来做什么

  • 给已有 AI 草稿流程增加持久化追踪上下文,让后续人工操作能够恢复来源;如果后端用 Node,需要按对应 SDK 实现传播与链接,原文只提供 Python 示例。
  • 在模型调用层统一记录请求模型、返回模型和响应 ID,并检查自动重试是否被错误拆成多次逻辑操作。
  • 以工单 ID 保存草稿、模型、审批人和结果,同时记录 trace_id,让业务查询能接到仍在保留期内的运行细节。
  • 为跨时审批添加关联测试,并按实际运行环境检查现有请求上下文,确保 trace 的归属符合设计。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
代码编辑推测解码兼顾提速与输出一致
下一篇
编程模型榜单分数为何难以直接比较

资讯速览

dev.to · AI · 2026-11-09 21:48

AI 评分 8.0 / 10

相关标签

#可观测性#链路追踪#人工审批

继续阅读

  • 代码编辑推测解码兼顾提速与输出一致
  • 编程模型榜单分数为何难以直接比较
  • 用流量模拟估算 OCR 服务容量
  • Agent重试如何避免重复执行与额度超支