DeepEval 与 Confident AI:用原生 OpenTelemetry(OTLP)将 AI 应用 Trace 导出到 Confident AI Observatory
2026/9/13 4:09:17 网站建设 项目流程

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 类型——agentllmretrievertool——都是为描述 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-sdk
    • opentelemetry-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 步,可视为一份接入检查单:

  1. 确认目标是 AI 应用(含 LLM 调用、Agent 循环、检索或工具调用),否则停止;同时检查是否已存在 OpenTelemetry 设施(TracerProvider、span exporter 或 OpenTelemetry Collector),优先改造已有管线而非新建平行管线;
  2. 根据 API key 的区域前缀选择端点(见下文“端点与鉴权”);
  3. 接入(或改指)一个带x-confident-api-key头的 OTLP/HTTP span exporter,Python 场景可直接从 confident_otel_setup.py 模板起步;
  4. 如果进程中还有其他 OpenTelemetry instrumentation 或 APM agent(HTTP/DB 自动埋点、Datadog 等),隔离 Confident AI 导出,确保只有 AI span 到达它(见下文“只导出 AI span”);
  5. 在 span 上设置confident.span.*属性,trace 级字段设置confident.trace.*(见下文属性契约两节);
  6. 遵守 OTLP 数据类型规则:dict/metadata 必须 JSON 编码,字符串列表用原生数组(见“OTLP 数据类型规则”);
  7. 如果应用已经在产生 OpenTelemetry GenAI 语义约定(gen_ai.*)span,先了解回退行为再决定是否补充冗余属性(见“gen_ai 回退机制”);
  8. 在 Confident AI Observatory 中验证 trace 是否出现。

端点选择、鉴权与传输协议

两个区域端点

Confident AI 每个区域暴露一个 OTLP/HTTP traces 端点,恰好两个:

区域Base endpointTraces 实际 POST 到
默认(US/AU)https://otel.confident-ai.comhttps://otel.confident-ai.com/v1/traces
EUhttps://eu.otel.confident-ai.comhttps://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-httpHttpProtobuf或语言等价物)。

Python 最小接入示例

最小路径分四步:创建TracerProvider;挂一个包裹 OTLP/HTTPOTLPSpanExporterBatchSpanProcessor(指向<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 中都相同,只有类名/包名不同。所有语言里都是同样的四步:

  1. 构造 OTLP/HTTP span exporter;
  2. endpoint/url设为<区域端点>/v1/traces
  3. 添加x-confident-api-key头;
  4. 将其注册到 tracer provider 的 batch span processor 上。

之后照常发 span、设置confident.*属性即可——属性 key 就是完整契约,与语言无关,这正是“语言无关”声明的落地方式。

Trace 级属性:confident.trace.*契约

Trace 级属性描述整条 trace(一次端到端执行),而非单个 span。它们可以设置在 trace 中任意 span上——最自然的位置是根 span——Confident AI 会将其聚合到 trace 层。完整属性表如下(全部可选,只设置有意义的字段):

属性 key类型说明
confident.trace.namestring人类可读的 trace 名称
confident.trace.inputstringtrace 输入;透传,非字符串需先 JSON 编码
confident.trace.outputstringtrace 输出;透传,非字符串需先 JSON 编码
confident.trace.user_idstring最终用户/客户标识
confident.trace.thread_idstring会话或线程标识
confident.trace.tagslist of strings分组标签;原生 OTLP 字符串数组或 JSON 数组字符串
confident.trace.metadataJSON string任意键值上下文;必须是 JSON 编码的对象字符串(OTLP 无 map 类型)
confident.trace.environmentstring部署环境,默认"production",见下文 Environment Resolution
confident.trace.retrieval_contextlist of stringstrace 的检索片段/文档
confident.trace.contextlist of stringstrace 的 ground-truth 上下文
confident.trace.tools_calledlist of strings本次 trace 调用的工具;原生 OTLP list,每个元素为 JSON 序列化的ToolCall
confident.trace.expected_toolslist of strings本应调用的工具;同上编码方式
confident.trace.test_case_idstring关联的测试用例 ID
confident.trace.turn_idstring多轮对话的轮次标识
confident.trace.metric_collectionstringConfident AI metric collection 名称,用于对该 trace 运行在线(服务端)评估

Environment Resolution

confident.trace.environment接受部署环境字符串(常见为"production""staging""development""testing"),默认值为"production"。它可以在两个位置设置,且Resource 属性优先于 span 属性

  • 作为 span 属性:在某 span 上设置confident.trace.environment
  • 作为TracerProviderResource上的 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 有意义,应最先设置

允许的类型取值恰好是:llmtoolagentretriever,以及无额外类型化字段的通用类型base

通用 span 属性(所有类型有效)

属性 key类型说明
confident.span.typestringllm/tool/agent/retriever/base之一;缺省时从gen_ai.*属性推断
confident.span.namestring显示名;覆盖原生 OTel span 名
confident.span.inputstringspan 输入;透传,非字符串需 JSON 编码
confident.span.outputstringspan 输出;透传,非字符串需 JSON 编码
confident.span.metadataJSON string帮助诊断故障的组件事实;必须是 JSON 编码的对象字符串
confident.span.contextlist of strings该 span 的 ground-truth 上下文
confident.span.retrieval_contextlist of strings该 span 的检索片段
confident.span.tools_calledlist of strings原生 OTLP list,元素为 JSON 序列化的ToolCall字符串
confident.span.expected_toolslist of strings原生 OTLP list,元素为 JSON 序列化的ToolCall字符串
confident.span.metric_collectionstringConfident AI metric collection 名称,对该 span 运行在线评估

各类型专属属性

LLM spanconfident.span.type设为llm):

属性 key类型说明
confident.llm.modelstring模型名(如gpt-4o);回退:gen_ai.request.model
confident.span.providerstringLLM 提供方(如openaianthropic);可选,缺省时从模型名推断
confident.llm.input_token_countint输入/prompt token 数;回退:gen_ai.usage.input_tokens
confident.llm.output_token_countint输出/completion token 数;回退:gen_ai.usage.output_tokens
confident.llm.cost_per_input_tokenfloat每输入 token 成本,用于成本汇总
confident.llm.cost_per_output_tokenfloat每输出 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 spanconfident.span.type设为agent):

属性 key类型说明
confident.agent.namestringAgent 名称/标识
confident.agent.available_toolslist of strings该 agent 可用的工具
confident.agent.agent_handoffslist of strings可交接的其他 agent

Retriever spanconfident.span.type设为retriever):

属性 key类型说明
confident.retriever.embedderstring嵌入模型名(如text-embedding-3-small
confident.retriever.top_kint检索结果数
confident.retriever.chunk_sizeint文档 chunk 大小

检索到的片段应放在通用的confident.span.retrieval_context上。

Tool spanconfident.span.type设为tool):

属性 key类型说明
confident.tool.namestring工具/函数名;回退:gen_ai.tool.name
confident.tool.descriptionstring人类可读的工具描述

工具的参数放在confident.span.input,结果放在confident.span.output

OTLP 数据类型规则

OpenTelemetry 属性值只能是原始类型(string、bool、int、float)或同质原始类型列表,不存在 map/object 属性类型。编码规则:

  • 对象/dictconfident.span.metadataconfident.trace.metadata):必须JSON 编码为字符串json.dumps(...));
  • 字符串列表tagscontextretrieval_contextavailable_toolsagent_handoffs):用原生 OTLP 字符串数组(Python 的list/tupleofstr),JSON 数组字符串也被接受;
  • ToolCall列表tools_calledexpected_tools):必须是原生 OTLP list,且每个元素是一个 JSON 序列化的ToolCall字符串——即“JSON 字符串的列表”,而不是“一个列表的 JSON 字符串”;
  • input/output:透传,值若不是字符串则先 JSON 编码;
  • 数字top_kchunk_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.namechatgenerate_contenttext_completionllm
存在gen_ai.tool.nametool
其他base

属性回退表

confident.*属性回退到gen_ai.*
confident.llm.modelgen_ai.request.model
confident.llm.input_token_countgen_ai.usage.input_tokens
confident.llm.output_token_countgen_ai.usage.output_tokens
confident.tool.namegen_ai.tool.name

使用建议:

  • 两者同时存在时,confident.*属性总是胜出
  • 新打点优先显式设置confident.*属性——它们映射直接且无歧义;
  • 依赖回退只是为了避免与既有 GenAI 集成重复属性,除非需要覆盖,否则不要为应用已设置的gen_ai.*属性添加confident.*副本。

这一回退机制在 DeepEval 的 Python 侧同样有实现佐证:从源码结构看,deepeval/tracing/otel/exporter.py 中调用了check_span_type_from_gen_ai_attributescheck_model_from_gen_ai_attributescheck_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:DeepEvalBatchFilterProcessoronStart/onEnd中对非ai.span 直接跳过,而DeepEvalExporterWrapper在导出批次时记录 AI span id,并对已知 AI 根 span 剥离悬空的parentSpanContext

注意——过滤时要保留 span 嵌套。丢弃一个中间的非 AI span 可能使其 AI 子 span 成为孤儿(它们的parentSpanId指向一个从未被导出的 span)。过滤时应把孤儿 AI span 重新挂到最近的已导出祖先上,或者剥掉悬空 parent 引用让其成为干净的根 span。上述DeepEvalExporterWrapper对根 span 恰好做了这件事。

核心原则速查

技能文档总结的 8 条核心原则,可作为上线前的最后自检:

  1. 只打点 AI 组件——agent、LLM、retriever、tool span;绝不把confident.*属性用于非 AI 软件或非 AI span;
  2. 只导出 AI span。进程若有其他 OTel instrumentation 或 APM agent,隔离 Confident AI 管线(专用 provider 或 span filter),确保 HTTP 请求、DB 查询、基础设施 span 永不导出;
  3. 优先改造已有的 OTLP exporter,而非新增平行管线;
  4. confident.*属性 key 就是完整契约——各语言中完全相同,语言选择无关紧要;
  5. 永远使用 OTLP/HTTP;端点不接受 gRPC;
  6. 遵守 OTLP 数据类型规则:属性值必须是原始类型或同质原始类型列表,dict/metadata 必须 JSON 编码;
  7. 已知时显式设置confident.span.typegen_ai.*推断只作为回退;
  8. 绝不把密钥、凭证或原始敏感数据放进 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 设置 + 示例 traceconfident_otel_setup.py
技能入口文档SKILL.md

最后强调适用前提:本方案要求一个 Confident AI 账号与CONFIDENT_API_KEY,依赖应用语言的标准 OTel SDK(Python 示例假设opentelemetry-sdkopentelemetry-exporter-otlp-proto-http),且端点仅支持 OTLP/HTTP。只要你的应用中有 LLM、Agent、检索或工具调用这任一类 AI 组件,这套“属性 key + OTLP 端点”的语言无关契约即可直接套用。

【免费下载链接】deepevalThe LLM Evaluation Framework项目地址: https://gitcode.com/GitHub_Trending/de/deepeval

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询