DeepEval 与 Confident AI:用原生 OpenTelemetry(OTLP)将 AI 应用 Trace 导出到 Confident AI Observatory
【免费下载链接】deepevalThe LLM Evaluation Framework项目地址: https://gitcode.com/GitHub_Trending/de/deepeval
本文围绕 DeepEval 仓库中skills/deepeval-otel技能文档展开,讲解如何不依赖deepevalPython 包,仅用任意语言的 OpenTelemetry SDK 通过 OTLP/HTTP 将 LLM 应用、Agent、RAG 流水线或聊天机器人的 trace 发送到 Confident AI 的 Observatory。读完本文,你将掌握 Confident AI OTLP 端点的选择与鉴权方式、confident.span.*/confident.trace.*属性契约、OTLP 数据类型规则、GenAI 语义约定(gen_ai.*)回退机制,以及“只导出 AI span”的管线隔离方案,并能直接复用仓库中的可运行模板完成接入。
适用边界:只针对 AI 应用
该技能的核心前提是只对被测对象中的 AI 部分打点。confident.*属性与 span 类型——agent、llm、retriever、tool——都是为描述 AI 组件而设计的,Confident AI 的 Observatory 也是围绕 AI 行为的评估与监控构建的。
- 只应打点系统中的 AI 部分:Agent 循环与规划、LLM 调用、检索/向量搜索、工具调用;
- 不要把
confident.*属性应用到非 AI 软件(Web 服务器、CRUD 后端、数据库层、基础设施)或非 AI span 上——这些数据不属于 Confident AI,也无法被有意义地渲染; - 如果目标系统既没有 LLM、没有 Agent 循环、没有检索也没有工具调用,则此方案不适用。
此外,DeepEval 仓库中的技能体系按职责划分:本方案(deepeval-otel)负责厂商中立的 OTLP 导出;而用 pytest 构建评估套件、生成数据集/goldens、编写 metrics、运行deepeval test run、使用@observe装饰器等场景则应使用 DeepEval SDK 本身(参见仓库中的 deepeval 技能文档 与 deepeval-tracing 技能文档)。两者是互补关系,而非替代关系。
前置条件
- 一个 Confident AI 账号以及对应的
CONFIDENT_API_KEY; - 应用语言对应的 OpenTelemetry SDK。Python 场景需要安装:
opentelemetry-sdkopentelemetry-exporter-otlp-proto-http
- 端点只接受 OTLP/HTTP,绝不接受 gRPC——这是贯穿全文的第一条硬约束。
工作原理
Confident AI 暴露一个 OTLP/HTTP traces 端点。把任意 OpenTelemetry span exporter 指向该端点,并携带x-confident-api-key请求头即可。Confident AI 侧的 exporter 随后从每个 span 上读取confident.*属性来构建 trace 与 span 结构。
关键点在于:父子嵌套关系来自原生 OpenTelemetry span context(span 上下文),不来自任何属性。也就是说,只要在父 span 的with上下文内开启子 span,Confident AI 就会自动恢复完整的树形结构,无需任何额外的“父子”属性约定。
端到端接入工作流
技能文档给出的标准工作流共 8 步,可视为一份接入检查单:
- 确认目标是 AI 应用(含 LLM 调用、Agent 循环、检索或工具调用),否则停止;同时检查是否已存在 OpenTelemetry 设施(
TracerProvider、span exporter 或 OpenTelemetry Collector),优先改造已有管线而非新建平行管线; - 根据 API key 的区域前缀选择端点(见下文“端点与鉴权”);
- 接入(或改指)一个带
x-confident-api-key头的 OTLP/HTTP span exporter,Python 场景可直接从 confident_otel_setup.py 模板起步; - 如果进程中还有其他 OpenTelemetry instrumentation 或 APM agent(HTTP/DB 自动埋点、Datadog 等),隔离 Confident AI 导出,确保只有 AI span 到达它(见下文“只导出 AI span”);
- 在 span 上设置
confident.span.*属性,trace 级字段设置confident.trace.*(见下文属性契约两节); - 遵守 OTLP 数据类型规则:dict/metadata 必须 JSON 编码,字符串列表用原生数组(见“OTLP 数据类型规则”);
- 如果应用已经在产生 OpenTelemetry GenAI 语义约定(
gen_ai.*)span,先了解回退行为再决定是否补充冗余属性(见“gen_ai 回退机制”); - 在 Confident AI Observatory 中验证 trace 是否出现。
端点选择、鉴权与传输协议
两个区域端点
Confident AI 每个区域暴露一个 OTLP/HTTP traces 端点,恰好两个:
| 区域 | Base endpoint | Traces 实际 POST 到 |
|---|---|---|
| 默认(US/AU) | https://otel.confident-ai.com | https://otel.confident-ai.com/v1/traces |
| EU | https://eu.otel.confident-ai.com | https://eu.otel.confident-ai.com/v1/traces |
这里有一个容易踩坑的细节:直接配置 OTLP/HTTP span exporter 时,endpoint值必须包含/v1/traces后缀;而通过标准环境变量OTEL_EXPORTER_OTLP_ENDPOINT配置时,只需给 base endpoint——SDK 会自动追加/v1/traces。
按 API key 前缀选择端点
Confident AI 的 API key 带区域前缀,选择规则如下:
| API key 前缀 | 端点 |
|---|---|
confident_eu_… | https://eu.otel.confident-ai.com |
confident_us_… | https://otel.confident-ai.com |
| 其他任意前缀 | https://otel.confident-ai.com(默认) |
只有confident_eu_…开头的 key 使用 EU 端点,拿不准时用默认端点。confident_otel_setup.py 中的pick_endpoint函数正是按此逻辑实现的:
def pick_endpoint(api_key: str) -> str: if api_key.startswith("confident_eu_"): return "https://eu.otel.confident-ai.com" return "https://otel.confident-ai.com"鉴权与标准环境变量
每个请求都必须携带 API key:
x-confident-api-key: <CONFIDENT_API_KEY>key 应从CONFIDENT_API_KEY环境变量读取,永远不要硬编码到源码中。也可以完全用标准 OpenTelemetry 环境变量配置,无需改代码:
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otel.confident-ai.com" export OTEL_EXPORTER_OTLP_HEADERS="x-confident-api-key=<CONFIDENT_API_KEY>"传输:只有 HTTP
- Python:使用
opentelemetry.exporter.otlp.proto.http.trace_exporter中的OTLPSpanExporter(包opentelemetry-exporter-otlp-proto-http),不要使用opentelemetry.exporter.otlp.proto.grpc变体; - OpenTelemetry Collector:使用
otlphttpexporter,而不是otlp(gRPC); - 其他 SDK:选择 OTLP/HTTP exporter(
proto-http、HttpProtobuf或语言等价物)。
Python 最小接入示例
最小路径分四步:创建TracerProvider;挂一个包裹 OTLP/HTTPOTLPSpanExporter的BatchSpanProcessor(指向<endpoint>/v1/traces并带x-confident-api-key头);注册为全局 tracer provider;获取 tracer、开启 span 并设置confident.*属性:
import os from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter api_key = os.environ["CONFIDENT_API_KEY"] endpoint = ( "https://eu.otel.confident-ai.com" if api_key.startswith("confident_eu_") else "https://otel.confident-ai.com" ) provider = TracerProvider() provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint=f"{endpoint}/v1/traces", headers={"x-confident-api-key": api_key}, ) ) ) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("my-llm-app") as span: span.set_attribute("confident.span.type", "agent") span.set_attribute("confident.trace.name", "my-llm-app")完整的可运行版本见 confident_otel_setup.py。该模板同时演示了一条完整示例 trace(agent 根 span + 子 LLM span),并覆盖了属性与数据类型契约的几个关键点:字符串列表用原生 OTLP 数组(root.set_attribute("confident.trace.tags", ["support", "example"]))、metadata 用 JSON 编码字符串(json.dumps({...}))、span 错误用原生 OTelStatus而非confident.*属性、进程退出前调用trace.get_tracer_provider().shutdown()冲刷批次。直接python confident_otel_setup.py即可作为连通性冒烟测试。
其他语言
接线形状在任何 OpenTelemetry SDK 中都相同,只有类名/包名不同。所有语言里都是同样的四步:
- 构造 OTLP/HTTP span exporter;
- 将
endpoint/url设为<区域端点>/v1/traces; - 添加
x-confident-api-key头; - 将其注册到 tracer provider 的 batch span processor 上。
之后照常发 span、设置confident.*属性即可——属性 key 就是完整契约,与语言无关,这正是“语言无关”声明的落地方式。
Trace 级属性:confident.trace.*契约
Trace 级属性描述整条 trace(一次端到端执行),而非单个 span。它们可以设置在 trace 中任意 span上——最自然的位置是根 span——Confident AI 会将其聚合到 trace 层。完整属性表如下(全部可选,只设置有意义的字段):
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.trace.name | string | 人类可读的 trace 名称 |
confident.trace.input | string | trace 输入;透传,非字符串需先 JSON 编码 |
confident.trace.output | string | trace 输出;透传,非字符串需先 JSON 编码 |
confident.trace.user_id | string | 最终用户/客户标识 |
confident.trace.thread_id | string | 会话或线程标识 |
confident.trace.tags | list of strings | 分组标签;原生 OTLP 字符串数组或 JSON 数组字符串 |
confident.trace.metadata | JSON string | 任意键值上下文;必须是 JSON 编码的对象字符串(OTLP 无 map 类型) |
confident.trace.environment | string | 部署环境,默认"production",见下文 Environment Resolution |
confident.trace.retrieval_context | list of strings | trace 的检索片段/文档 |
confident.trace.context | list of strings | trace 的 ground-truth 上下文 |
confident.trace.tools_called | list of strings | 本次 trace 调用的工具;原生 OTLP list,每个元素为 JSON 序列化的ToolCall |
confident.trace.expected_tools | list of strings | 本应调用的工具;同上编码方式 |
confident.trace.test_case_id | string | 关联的测试用例 ID |
confident.trace.turn_id | string | 多轮对话的轮次标识 |
confident.trace.metric_collection | string | Confident AI metric collection 名称,用于对该 trace 运行在线(服务端)评估 |
Environment Resolution
confident.trace.environment接受部署环境字符串(常见为"production"、"staging"、"development"、"testing"),默认值为"production"。它可以在两个位置设置,且Resource 属性优先于 span 属性:
- 作为 span 属性:在某 span 上设置
confident.trace.environment; - 作为
TracerProvider的Resource上的 OpenTelemetry Resource 属性confident.trace.environment——这是推荐的整进程一次打环境戳的方式。
Span 级属性:confident.span.*契约与数据类型规则
Span 级属性描述单个 span(trace 中的一个组件),设置为该 span 上的confident.span.*(以及按类型的confident.llm.*、confident.agent.*、confident.retriever.*、confident.tool.*)属性。confident.span.type决定哪些按类型的 key 有意义,应最先设置。
允许的类型取值恰好是:llm、tool、agent、retriever,以及无额外类型化字段的通用类型base。
通用 span 属性(所有类型有效)
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.span.type | string | llm/tool/agent/retriever/base之一;缺省时从gen_ai.*属性推断 |
confident.span.name | string | 显示名;覆盖原生 OTel span 名 |
confident.span.input | string | span 输入;透传,非字符串需 JSON 编码 |
confident.span.output | string | span 输出;透传,非字符串需 JSON 编码 |
confident.span.metadata | JSON string | 帮助诊断故障的组件事实;必须是 JSON 编码的对象字符串 |
confident.span.context | list of strings | 该 span 的 ground-truth 上下文 |
confident.span.retrieval_context | list of strings | 该 span 的检索片段 |
confident.span.tools_called | list of strings | 原生 OTLP list,元素为 JSON 序列化的ToolCall字符串 |
confident.span.expected_tools | list of strings | 原生 OTLP list,元素为 JSON 序列化的ToolCall字符串 |
confident.span.metric_collection | string | Confident AI metric collection 名称,对该 span 运行在线评估 |
各类型专属属性
LLM span(confident.span.type设为llm):
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.llm.model | string | 模型名(如gpt-4o);回退:gen_ai.request.model |
confident.span.provider | string | LLM 提供方(如openai、anthropic);可选,缺省时从模型名推断 |
confident.llm.input_token_count | int | 输入/prompt token 数;回退:gen_ai.usage.input_tokens |
confident.llm.output_token_count | int | 输出/completion token 数;回退:gen_ai.usage.output_tokens |
confident.llm.cost_per_input_token | float | 每输入 token 成本,用于成本汇总 |
confident.llm.cost_per_output_token | float | 每输出 token 成本,用于成本汇总 |
如果 span 引用了在 Confident AI 中管理的 prompt,还可选设离散 prompt 字段(只设置有意义的):confident.span.prompt_alias(prompt 别名)、confident.span.prompt_version(版本标识)、confident.span.prompt_commit_hash(提交哈希)、confident.span.prompt_label(标签)。
Agent span(confident.span.type设为agent):
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.agent.name | string | Agent 名称/标识 |
confident.agent.available_tools | list of strings | 该 agent 可用的工具 |
confident.agent.agent_handoffs | list of strings | 可交接的其他 agent |
Retriever span(confident.span.type设为retriever):
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.retriever.embedder | string | 嵌入模型名(如text-embedding-3-small) |
confident.retriever.top_k | int | 检索结果数 |
confident.retriever.chunk_size | int | 文档 chunk 大小 |
检索到的片段应放在通用的confident.span.retrieval_context上。
Tool span(confident.span.type设为tool):
| 属性 key | 类型 | 说明 |
|---|---|---|
confident.tool.name | string | 工具/函数名;回退:gen_ai.tool.name |
confident.tool.description | string | 人类可读的工具描述 |
工具的参数放在confident.span.input,结果放在confident.span.output。
OTLP 数据类型规则
OpenTelemetry 属性值只能是原始类型(string、bool、int、float)或同质原始类型列表,不存在 map/object 属性类型。编码规则:
- 对象/dict(
confident.span.metadata、confident.trace.metadata):必须JSON 编码为字符串(json.dumps(...)); - 字符串列表(
tags、context、retrieval_context、available_tools、agent_handoffs):用原生 OTLP 字符串数组(Python 的list/tupleofstr),JSON 数组字符串也被接受; ToolCall列表(tools_called、expected_tools):必须是原生 OTLP list,且每个元素是一个 JSON 序列化的ToolCall字符串——即“JSON 字符串的列表”,而不是“一个列表的 JSON 字符串”;input/output:透传,值若不是字符串则先 JSON 编码;- 数字(
top_k、chunk_size、token 数、成本):设为原生 int/float,不要写成字符串。
span 错误与嵌套
- span 错误不是
confident.*属性,而应使用原生 OpenTelemetry spanStatus:
from opentelemetry.trace import Status, StatusCode try: ... except Exception as e: span.set_status(Status(StatusCode.ERROR), str(e)) span.record_exception(e)带StatusCode.ERROR的 span 会在 Observatory 中渲染为出错;若它是根 span,则整条 trace 被标记为出错。
- span 嵌套:父子关系完全来自原生 OTel span context——在父 span 的上下文中开启子 span 即可,不存在任何
confident.*的父子属性。使用tracer.start_as_current_span(...)时,with块内打开的 span 会自动嵌套。
gen_ai 语义约定回退机制
当confident.*属性缺失时,Confident AI 的 exporter 会回退读取标准的 OpenTelemetryGenAI 语义约定属性(gen_ai.*)。这意味着:如果应用已经被某个 GenAI 感知的库打点、天然产生gen_ai.*span,这些数据无需任何额外confident.*属性即可带进 Confident AI。
span 类型推断(未设置confident.span.type时):
| 条件 | 推断出的confident.span.type |
|---|---|
gen_ai.operation.name为chat、generate_content或text_completion | llm |
存在gen_ai.tool.name | tool |
| 其他 | base |
属性回退表:
confident.*属性 | 回退到gen_ai.* |
|---|---|
confident.llm.model | gen_ai.request.model |
confident.llm.input_token_count | gen_ai.usage.input_tokens |
confident.llm.output_token_count | gen_ai.usage.output_tokens |
confident.tool.name | gen_ai.tool.name |
使用建议:
- 两者同时存在时,
confident.*属性总是胜出; - 新打点优先显式设置
confident.*属性——它们映射直接且无歧义; - 依赖回退只是为了避免与既有 GenAI 集成重复属性,除非需要覆盖,否则不要为应用已设置的
gen_ai.*属性添加confident.*副本。
这一回退机制在 DeepEval 的 Python 侧同样有实现佐证:从源码结构看,deepeval/tracing/otel/exporter.py 中调用了check_span_type_from_gen_ai_attributes、check_model_from_gen_ai_attributes、check_tool_name_from_gen_ai_attributes等一组gen_ai属性回退检查函数,与技能文档描述的回退契约一致。
只导出 AI span:与 APM/自动埋点隔离
真实应用中跑的 OpenTelemetry instrumentation 往往远不止 AI 代码。自动埋点库与 APM agent(Datadog、New Relic、Grafana、OpenTelemetry auto-instrumentation 等)会为 HTTP 请求、数据库查询、缓存调用、出站网络调用和框架内部逻辑产生 span。如果 Confident AI exporter 与这些埋点共享同一个 tracer provider 或 processor 管线,所有这些无关 span 都会被发送到 Confident AI Observatory,把 AI trace 淹没在非 AI 噪音里。
规则:Confident AI 导出管线只能承载 AI span。有两种做法:
方案 1 —— 专用管线(可行时优先)
把 Confident AI exporter 注册到一个只被 AI instrumentation 使用的 tracer provider / processor 上,与自动埋点和 APM agent 使用的全局 provider 分离。AI span 用该专用 provider 的 tracer 创建——非 AI span 从未进入它的管线,自然到不了 Confident AI exporter。
方案 2 —— 管线过滤
当 AI span 与其他 span 不可避免地共享一个 provider 时(AI 框架把 span 发到全局 provider 很常见),把面向 Confident AI 的 processor 或 exporter 包在一个过滤器里:只转发 AI span,丢弃其余。
判定一个 span 是 AI span 的依据(满足其一即可):
- 设置了
confident.span.type属性; - 携带
gen_ai.*语义约定属性; - span 名匹配已知的 AI 框架前缀(例如 Vercel AI SDK 产生名为
ai.*的 span)。
过滤器可做成两种形态之一:一个对非 AI span 的onStart/onEnd直接 no-op 的span processor,或一个在调用真正的 OTLP exporter 前从每个批次中剔除非 AI span 的exporter wrapper。
仓库中有一个可直接参照的工作实现:DeepEval TypeScript SDK 的 typescript/src/integrations/ai-sdk/index.ts 中的DeepEvalBatchFilterProcessor(按 span 名前缀过滤的 span processor,只放行ai.前缀的 span)和DeepEvalExporterWrapper(exporter wrapper)。可以推断其设计意图正是上述方案 2:DeepEvalBatchFilterProcessor在onStart/onEnd中对非ai.span 直接跳过,而DeepEvalExporterWrapper在导出批次时记录 AI span id,并对已知 AI 根 span 剥离悬空的parentSpanContext。
注意——过滤时要保留 span 嵌套。丢弃一个中间的非 AI span 可能使其 AI 子 span 成为孤儿(它们的parentSpanId指向一个从未被导出的 span)。过滤时应把孤儿 AI span 重新挂到最近的已导出祖先上,或者剥掉悬空 parent 引用让其成为干净的根 span。上述DeepEvalExporterWrapper对根 span 恰好做了这件事。
核心原则速查
技能文档总结的 8 条核心原则,可作为上线前的最后自检:
- 只打点 AI 组件——agent、LLM、retriever、tool span;绝不把
confident.*属性用于非 AI 软件或非 AI span; - 只导出 AI span。进程若有其他 OTel instrumentation 或 APM agent,隔离 Confident AI 管线(专用 provider 或 span filter),确保 HTTP 请求、DB 查询、基础设施 span 永不导出;
- 优先改造已有的 OTLP exporter,而非新增平行管线;
confident.*属性 key 就是完整契约——各语言中完全相同,语言选择无关紧要;- 永远使用 OTLP/HTTP;端点不接受 gRPC;
- 遵守 OTLP 数据类型规则:属性值必须是原始类型或同质原始类型列表,dict/metadata 必须 JSON 编码;
- 已知时显式设置
confident.span.type,gen_ai.*推断只作为回退; - 绝不把密钥、凭证或原始敏感数据放进 span 属性。
参考文件索引
| 主题 | 文件 |
|---|---|
| 端点、区域选择、鉴权、exporter 接线、AI span 隔离 | endpoint-and-exporter.md |
Trace 级confident.trace.*属性 | trace-attributes.md |
Span 级confident.span.*属性与数据类型规则 | span-attributes.md |
标准 OTelgen_ai.*回退行为 | gen-ai-fallbacks.md |
| 最小可运行的 Python OTLP exporter 设置 + 示例 trace | confident_otel_setup.py |
| 技能入口文档 | SKILL.md |
最后强调适用前提:本方案要求一个 Confident AI 账号与CONFIDENT_API_KEY,依赖应用语言的标准 OTel SDK(Python 示例假设opentelemetry-sdk与opentelemetry-exporter-otlp-proto-http),且端点仅支持 OTLP/HTTP。只要你的应用中有 LLM、Agent、检索或工具调用这任一类 AI 组件,这套“属性 key + OTLP 端点”的语言无关契约即可直接套用。
【免费下载链接】deepevalThe LLM Evaluation Framework项目地址: https://gitcode.com/GitHub_Trending/de/deepeval
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考