特性分析

集成支持

deepeval 通过一个统一的 Tracing 核心层来兼容所有框架。无论外部框架的形式如何不同,最终都汇聚到同一套数据模型和 trace 管理器。

  • Monkey-Patch 直接替换 SDK 类方法

  • Callback Handler 实现框架原生回调接口

  • Trace Processor 实现框架的追踪处理器协议

  • OTel 桥接则通过 OpenTelemetry 标准协议转换

所有路径最终都通过 TraceManager 统一管理 span 的生命周期。

在其中不同的数据最终都会转变成span系统

  • BaseSpan 是所有 span 的基类,包含通用字段(uuid、父子关系、时间戳、状态)。

  • 四种子类分别对应 LLM 调用、Agent执行、工具调用、检索器调用,各自携带领域特定的属性。

  • Trace 是顶层容器,包含一棵 span 树。

其核心在于Observer 与 TraceManager

Observer 是一个上下文管理器,负责 span 的创建、注册、上下文传播和结束:

# 简化的 Observer 工作流程
class Observer:
      def __enter__(self):
          # 1. 确保有活跃的 trace(没有则创建)
          # 2. 根据 span_type 创建对应的 span 实例
          # 3. 将 span 注册到 trace_manager.active_spans
          # 4. 设置 current_span_context(ContextVar)
          # 5. 建立父子关系
          return self
  def __exit__(self, exc_type, exc_val, exc_tb):
      # 1. 设置 span 状态(SUCCESS / ERRORED)
      # 2. 记录 end_time
      # 3. 恢复 current_span_context 到父 span
      # 4. 如果是根 span,结束 trace</code></pre><pre><code class="language-python">trace_manager = TraceManager()  # 全局单例

class TraceManager: def configure(self, openai_client=None, anthropic_client=None, …): # 配置客户端 patch、采样率、环境等

  def start_new_trace(self) -&gt; Trace:
      # 创建新 trace,注册到 active_traces

  def end_trace(self, trace_uuid):
      # 结束 trace,根据 eval_session.mode 决定:
      # - 非评估模式:post_trace() 发送到 Confident AI
      # - 评估模式:存入 eval_session 供 evaluate() 使用

  def post_trace(self, trace):
      # 通过后台 worker 线程异步发送 trace 到 API</code></pre><pre><code class="language-python">  # deepeval/tracing/context.py

current_trace_context: ContextVar[Optional[Trace]] = ContextVar(…)

current_span_context: ContextVar[Optional[BaseSpan]] = ContextVar(…)

所有集成都通过这两个 ContextVar 实现 span 的嵌套和父子关系。

异步场景下,LangChain 的 CallbackHandler 通过 _ctx() 上下文管理器显式恢复context,解决跨 Task 边界的 ContextVar 丢失问题。

模式一:Monkey-Patch(OpenAI / Anthropic)

用户只需将 from openai import OpenAI 改为 from deepeval.openai import OpenAI,导入时自动触发类级别的 monkey-patch。

每次 API 调用被包裹在@observe(type=“llm”) 中,自动提取 model、messages、token 用量等信息填充到 LlmSpan。

这些主要是在 deepeval/openaideepeval/tracing/patchers.py

OpenAI 的两种 patch 方式区别:

  1. 类级别 patch(from deepeval.openai import OpenAI):替换 SDK 类的方法,所有实例自动生效

  2. 实例级别 patch(trace_manager.configure(openai_client=client)):只 patch 传入的特定客户端实例

Anthropic 仅支持实例级别 patch:

  from anthropic import Anthropic

from deepeval.tracing import trace_manager

client = Anthropic()

trace_manager.configure(anthropic_client=client)

Anthropic patcher 本身不创建 span,它依赖调用方已通过 @observe(type=“llm”) 在上下文中放置了 LlmSpan,patcher 只负责填充model、input、output、token 用量。

模式二:Callback Handler(LangChain / LangGraph / LlamaIndex / CrewAI)

框架在执行过程中主动调用回调方法,deepeval 的 CallbackHandler 在每个回调中创建/结束对应类型的 span。

通过 run_id → span_uuid的映射维护正确的父子层级关系。

  • LlamaIndex span 分类启发式基于方法名(run → agent, call_tool → tool, retrieve → retriever)

  • LangGraph 兼容:LangGraph 构建在 LangChain 之上,使用相同的 callback 系统,on_chain_start/end 自动捕获 graph node 执行

  • CrewAI 双层 hook:

    • Monkey-patch 层(wrap_all()):替换 Crew.kickoff、Agent.execute_task 等方法,用 Observer 创建顶层 span

    • 事件监听层(CrewAIEventsListener(BaseEventListener)):监听 LLMCallStartedEvent、ToolUsageStartedEvent 等细粒度事件创建子 span

模式三:Trace Processor(OpenAI Agents)

OpenAI Agents SDK 提供了 TracingProcessor 接口,deepeval 实现该接口后注册为 trace processor。

SDK 在执行过程中主动推送 span的开始/结束事件,deepeval 根据 SpanData 的具体类型(AgentSpanData、GenerationSpanData、FunctionSpanData 等)分类为对应的 span 类型。

deepeval/openai_agents/callback_handler.py中有:

class DeepEvalTracingProcessor(TracingProcessor):
def on_span_start(self, span: “Span”) -> None:
span_type = self.get_span_kind(span.span_data)
if span_type == “noop”:
return
observer = Observer(span_type=span_type, func_name=“NA”)
self.span_observers[span.span_id] = observer
observer.enter()

  def on_span_end(self, span: "Span") -&gt; None:
      observer = self.span_observers.pop(span.span_id, None)
      if observer:
          observer.__exit__(None, None, None)

  def get_span_kind(self, span_data) -&gt; str:
      if isinstance(span_data, AgentSpanData): return "agent"
      if isinstance(span_data, FunctionSpanData): return "tool"
      if isinstance(span_data, GenerationSpanData): return "llm"
      if isinstance(span_data, ResponseSpanData): return "llm"
      # TaskSpanData, TurnSpanData 等 → "noop"(忽略)</code></pre><p style=""></p><p style=""></p><h3 style="" id="%E6%A8%A1%E5%BC%8F%E5%9B%9B%EF%BC%9Aotel-spanprocessor%EF%BC%88pydantic-ai-%2F-aws-agentcore%EF%BC%89">模式四:OTel SpanProcessor(Pydantic AI / AWS AgentCore)</h3><figure style="align-items: center; display: flex; flex-direction: column" data-content-type="image"><img src="https://moyublog-picture.oss-cn-guangzhou.aliyuncs.com/images/20260519003231554.png" width="2039px"></figure><p style="">这些框架<strong><mark>原生支持 OpenTelemetry</mark></strong>,deepeval <strong><mark>注册自定义的 SpanProcessor </mark></strong>来拦截 OTel span。</p><ul><li><p style="">SpanInterceptor 负责将框架特定的 OTel 属性翻译为<code>confident.* </code>命名空间的属性。</p></li><li><p style="">ContextAwareSpanProcessor 根据是否存在 deepeval trace context 来智能路由:</p><ul><li><p style="">有 context 时走 REST 路径(通过ConfidentSpanExporter 重建为 deepeval 的 BaseSpan 树)</p></li><li><p style="">无 context 时走 OTLP 直推</p></li></ul></li></ul><p style=""></p><p style=""></p><h2 style="" id="eval-%E6%8C%87%E6%A0%87">eval 指标</h2><p style=""></p><figure style="align-items: center; display: flex; flex-direction: column" data-content-type="image"><img src="https://moyublog-picture.oss-cn-guangzhou.aliyuncs.com/images/20260520020007565.png" width="2105px"></figure><p style="">所有指标位于<code> deepeval/metrics/ </code>目录下,每个指标一个子文件夹,包含三个核心文件:</p><ul><li><p style=""><code>&lt;metric&gt;.py</code> — 核心实现(<code>measure / a_measure</code>)</p></li><li><p style=""><code>schema.py </code>— Pydantic 输出模型</p></li><li><p style=""><code>template.py</code> — LLM 提示词模板</p></li></ul><p style=""></p><h3 style="" id=""></h3><h3 style="" id="%E7%9C%9F%E5%AE%9E%E6%80%A7">真实性</h3><p style="">这是 RAG 系统最核心的诉求。<strong><u>用户给了agent一堆参考资料,agent的回答必须忠于这些资料,不能"创造性发挥"。</u></strong></p><p style="">它的颗粒度在sequence,所以<strong><mark>一般都会拆开来句子,然后逐个判断。</mark></strong></p><p style="">这个范式是 deepeval 最常见的设计模式。你会在后面的偏见、毒性、指令遵循等指标里反复看到它。</p><p style="">主要治标有:</p><ul><li><p style="">FaithfulnessMetric:每句话都能在参考资料里找到依据吗</p></li><li><p style="">HallucinationMetric:编了多少没有出处的东西</p></li><li><p style="">ContextualRecallMetric:参考资料里的关键信息有没有用</p></li></ul><p style="">只有拆开来每一句分析,才能知道溯源回去看看哪里不对。</p><p style=""></p><p style="">除了真实性本身错,还有比如说<strong><mark>检索不对导致的真实性错了</mark></strong></p><figure style="align-items: center; display: flex; flex-direction: column" data-content-type="image"><img src="https://moyublog-picture.oss-cn-guangzhou.aliyuncs.com/images/20260520033742637.png" width="1405px"></figure><p style="">这三个参数是互补的,巧妙之处在于 ContextualPrecision 不是简单的 "相关数/总数"。</p><p style="">它用的是<strong><mark>加权精确率@k</mark></strong>——相关文档排在越前面,得分越高。</p><p style="">这背后的直觉是:<strong><mark>如果有用的段落被埋在第 10 条,LLM可能根本看不到它(受限于注意力窗口),等于白检索。</mark></strong></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p><p style=""></p>