特性分析
集成支持
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) -> 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/openai 和deepeval/tracing/patchers.py 。
OpenAI 的两种 patch 方式区别:
类级别 patch(
from deepeval.openai import OpenAI):替换 SDK 类的方法,所有实例自动生效实例级别 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") -> None:
observer = self.span_observers.pop(span.span_id, None)
if observer:
observer.__exit__(None, None, None)
def get_span_kind(self, span_data) -> 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><metric>.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>