可观测性工具链实操:Langfuse / Ragas / DeepEval 链路追踪与评测

一句话:Agent 的可观测性不止是「打日志」——而是要追踪每一次 LLM 调用、每一次工具调用、每一次状态变更,让 Agent 的执行过程从「黑盒」变成「透明水管」——水流过每一步都看得见。


一、Agent 可观测性的三个层次

传统后端服务的可观测性只需要三件套:Metrics + Logs + Traces。Agent 多了一层——你得看见 LLM 的「思考过程」。


flowchart TD

    subgraph TRADITIONAL["传统后端三件套"]

        M1["Metrics<br/>请求数/错误率/延迟"]

        M2["Logs<br/>事件日志"]

        M3["Traces<br/>分布式链路追踪"]

    end

    subgraph AGENT["Agent 新增两层"]

        A1["LLM 调用追踪<br/>每次调了什么 Prompt?<br/>Token 用了多少?<br/>返回了什么?"]

        A2["决策可解释性<br/>为什么选了工具A而不是B?<br/>为什么在这步停下来?"]

    end

    TRADITIONAL --> COMPLETE["Agent 完整可观测性<br/>= 三件套 + LLM 调用 + 决策解释"]

    A1 --> COMPLETE

    A2 --> COMPLETE

三层分别需要什么工具

层次需要追踪什么用什么工具谁来看
基础设施层CPU / 内存 / 请求量 / 错误率Prometheus + GrafanaDevOps
LLM 调用层Token 用量 / 延迟 / Prompt/Response 内容Langfuse开发者 + PM
质量评测层Faithfulness / Relevance / 幻觉率Ragas + DeepEval开发者 + QA

二、端到端集成架构

三工具在一个项目中不是竞争关系——各管一摊:


flowchart TD

    USER["用户请求"] --> FASTAPI["FastAPI 应用"]

    FASTAPI --> LANGFUSE_MW["Langfuse 中间件<br/>自动创建 Trace + Span"]

    FASTAPI --> AGENT["Agent 执行<br/>graph.ainvoke()"]

    AGENT --> LLM_CALL["LLM 调用<br/>@observe 自动追踪"]

    AGENT --> TOOL_CALL["工具调用<br/>手动创建 Span"]

    LLM_CALL --> LANGFUSE_DB[("Langfuse<br/>Traces + Scores")]

    TOOL_CALL --> LANGFUSE_DB

    LANGFUSE_DB --> DASHBOARD["Langfuse Dashboard<br/>实时查看追踪链路"]

    subgraph OFFLINE["离线评测流水线"]

        DATASET["评测数据集"] --> RAGAS["Ragas 批量评测<br/>Faithfulness / Relevance / PR"]

        DATASET --> DEEPEVAL["DeepEval CI<br/>每次 PR 自动跑"]

        RAGAS --> SCORE["评分回写 Langfuse<br/>Score API"]

        DEEPEVAL --> CI_FAIL["CI 不通过 → 阻断合并"]

    end


三、Langfuse:全链路追踪核心

3.1 部署 Langfuse(本地 / 自托管)

 
# 方式一:Docker Compose 自托管(开发环境推荐)
 
git clone https://github.com/langfuse/langfuse.git
 
cd langfuse
 
docker compose up -d
 
# 注册 https://cloud.langfuse.com → 获取 Public Key + Secret Key
 

3.2 FastAPI 集成:中间件自动创建 Trace

 
"""
 
Langfuse + FastAPI 完整集成示例。
 
核心思路:用中间件为每个 HTTP 请求创建 Trace,
 
         Agent 内部的 LLM 调用和工具调用自动挂为子 Span。
 
安装:pip install langfuse fastapi
 
环境变量:
 
  LANGFUSE_PUBLIC_KEY=pk-...
 
  LANGFUSE_SECRET_KEY=sk-...
 
  LANGFUSE_HOST=http://localhost:3000  # 自托管地址
 
"""
 
import time
 
import uuid
 
from contextvars import ContextVar
 
from fastapi import FastAPI, Request
 
from langfuse import Langfuse
 
from langfuse.decorators import observe, langfuse_context
 
app = FastAPI()
 
langfuse = Langfuse()
 
# 用 contextvars 在请求生命周期内传递 trace_id
 
current_trace_id: ContextVar[str] = ContextVar("trace_id", default="")
 
@app.middleware("http")
 
async def langfuse_trace_middleware(request: Request, call_next):
 
    """
 
    中间件:每个 HTTP 请求创建一个 Langfuse Trace。
 
    Agent 内部的所有 LLM 调用都自动挂在这个 Trace 下。
 
    """
 
    trace_id = str(uuid.uuid4())[:8]
 
    current_trace_id.set(trace_id)
 
    # 创建 Trace
 
    trace = langfuse.trace(
 
        id=trace_id,
 
        name=f"{request.method} {request.url.path}",
 
        metadata={
 
            "method": request.method,
 
            "path": request.url.path,
 
            "user_agent": request.headers.get("user-agent", ""),
 
        },
 
    )
 
    start_time = time.time()
 
    try:
 
        response = await call_next(request)
 
        duration = time.time() - start_time
 
        # 记录成功
 
        trace.update(
 
            output={"status_code": response.status_code},
 
            metadata={"duration_sec": round(duration, 3)},
 
        )
 
        trace.score(name="latency_sec", value=duration)
 
        return response
 
    except Exception as e:
 
        duration = time.time() - start_time
 
        # 记录失败
 
        trace.update(
 
            output={"error": str(e)},
 
            metadata={"duration_sec": round(duration, 3)},
 
        )
 
        trace.score(name="error", value=1)
 
        raise
 
    finally:
 
        # 确保 Trace 被 flush
 
        langfuse.flush()
 
@observe()
 
async def run_agent_task(task_input: dict) -> dict:
 
    """
 
    Agent 主入口——@observe 装饰器自动创建 Span
 
    并挂到当前请求的 Trace 下。
 
    """
 
    # 1. 分解任务
 
    sub_tasks = await decompose_task(task_input)
 
    langfuse_context.update_current_observation(
 
        metadata={"sub_task_count": len(sub_tasks)}
 
    )
 
    # 2. 并行执行 Worker
 
    results = []
 
    for task in sub_tasks:
 
        result = await execute_worker(task)
 
        results.append(result)
 
    # 3. 聚合结果
 
    aggregated = aggregate_results(results)
 
    # 4. 打分
 
    langfuse_context.score_current_observation(
 
        name="task_success",
 
        value=1.0 if aggregated["status"] == "success" else 0.0,
 
    )
 
    return aggregated
 
@observe(as_type="generation")
 
async def call_llm(prompt: str, model: str = "deepseek-v4-pro") -> str:
 
    """
 
    LLM 调用——标注为 generation 类型,Langfuse 自动记录:
 
    - model name
 
    - token usage(prompt + completion)
 
    - latency
 
    - input / output 内容
 
    """
 
    from openai import AsyncOpenAI
 
    import os
 
    client = AsyncOpenAI(
 
        api_key=os.environ["DEEPSEEK_API_KEY"],
 
        base_url="https://api.deepseek.com",
 
    )
 
    response = await client.chat.completions.create(
 
        model=model,
 
        messages=[{"role": "user", "content": prompt}],
 
        temperature=0.0,
 
    )
 
    # Langfuse 自动记录 usage
 
    langfuse_context.update_current_observation(
 
        model=model,
 
        usage={
 
            "prompt_tokens": response.usage.prompt_tokens,
 
            "completion_tokens": response.usage.completion_tokens,
 
        },
 
    )
 
    return response.choices[0].message.content
 
async def execute_worker(task: dict) -> dict:
 
    """工具调用——手动创建 Span"""
 
    with langfuse.start_as_current_observation(
 
        name=f"worker-{task['type']}",
 
        as_type="span",
 
        input=task,
 
    ) as span:
 
        try:
 
            result = await call_tool(task)
 
            span.update(output=result)
 
            return result
 
        except Exception as e:
 
            span.update(level="ERROR", status_message=str(e))
 
            span.score(name="tool_error", value=1)
 
            raise
 

3.3 在 Langfuse Dashboard 看到什么

 
Trace: POST /api/review
 
├── Span: run_agent_task .......................... 2.3s
 
│   ├── Generation: call_llm (decompose) .......... 0.8s, 450 tokens
 
│   ├── Span: worker-security ..................... 1.2s
 
│   │   └── Generation: call_llm (security check) . 1.0s, 820 tokens
 
│   ├── Span: worker-quality ...................... 0.9s
 
│   │   └── Generation: call_llm (quality check) .. 0.8s, 650 tokens
 
│   └── Span: worker-performance .................. 1.1s
 
│       └── Generation: call_llm (perf check) ..... 0.9s, 720 tokens
 
├── Score: task_success = 1.0
 
├── Score: latency_sec = 2.3
 
└── Total tokens: 2,640
 

四、Ragas:离线批量评测

生产环境不可能实时跑 Ragas——LLM Judge 本身要花钱花时间。Ragas 适合离线批量评测,定期(如每天/每次发布前)跑一次。

 
"""
 
离线批量评测脚本:加载生产日志 → 用 Ragas 打分 → 回写 Langfuse Score。
 
可以设为 cron job 每天跑一次。
 
"""
 
from ragas import evaluate, EvaluationDataset
 
from ragas.metrics import Faithfulness, AnswerRelevancy
 
from ragas.llms import LangchainLLMWrapper
 
from langfuse import Langfuse
 
async def offline_batch_evaluation():
 
    """离线批量评测流水线"""
 
    langfuse = Langfuse()
 
    judge_llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o", temperature=0.0))
 
    # 第1步:从 Langfuse 拉取最近的 Traces 作为评测数据集
 
    traces = langfuse.fetch_traces(limit=100, tags=["production"])
 
    # 第2步:构造 Ragas 评测样本
 
    samples = []
 
    for trace in traces:
 
        # 从 Trace 的结构化 output 中提取评测所需字段
 
        samples.append({
 
            "user_input": trace.input.get("question", ""),
 
            "response": trace.output.get("answer", ""),
 
            "retrieved_contexts": trace.output.get("contexts", []),
 
            "reference": trace.output.get("expected", ""),
 
        })
 
    dataset = EvaluationDataset.from_list(samples)
 
    # 第3步:跑 Ragas 评测
 
    metrics = [Faithfulness(llm=judge_llm), AnswerRelevancy(llm=judge_llm)]
 
    result = evaluate(dataset, metrics=metrics)
 
    # 第4步:将 Ragas 评分回写到 Langfuse
 
    for i, trace in enumerate(traces):
 
        langfuse.score(
 
            trace_id=trace.id,
 
            name="ragas_faithfulness",
 
            value=result["faithfulness"][i],
 
        )
 
        langfuse.score(
 
            trace_id=trace.id,
 
            name="ragas_answer_relevancy",
 
            value=result["answer_relevancy"][i],
 
        )
 
    print(f"Batch eval done: {len(samples)} samples")
 
    print(f"Avg Faithfulness: {result['faithfulness'].mean():.3f}")
 
    print(f"Avg Answer Relevancy: {result['answer_relevancy'].mean():.3f}")
 

五、DeepEval:CI 流水线中的质量门禁

DeepEval 的独特价值在于它是 Pytest 原生风格,可以无缝嵌入 CI:

 
# .github/workflows/eval.yml
 
name: LLM Evaluation Pipeline
 
on:
 
  pull_request:
 
    branches: [main]
 
  push:
 
    branches: [main]
 
jobs:
 
  eval:
 
    runs-on: ubuntu-latest
 
    steps:
 
      - uses: actions/checkout@v4
 
      - uses: actions/setup-python@v5
 
        with:
 
          python-version: "3.12"
 
      - run: pip install -r requirements.txt
 
      - run: pip install deepeval
 
      # 关键步骤:跑 DeepEval 评测
 
      - name: Run LLM evaluation tests
 
        env:
 
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
 
        run: pytest tests/eval/ --deepeval-max-samples 20
 
      # 如果不通过 → CI 失败 → 阻断 PR 合并
 
 
# tests/eval/test_code_review_quality.py
 
"""DeepEval 评测文件——Pytest 风格,跑在 CI 上"""
 
from deepeval import assert_test
 
from deepeval.test_case import LLMTestCase
 
from deepeval.metrics import (
 
    AnswerRelevancyMetric,
 
    FaithfulnessMetric,
 
    HallucinationMetric,
 
)
 
# 基线测试用例(Golden Test Cases)
 
GOLDEN_CASES = [
 
    LLMTestCase(
 
        input="审查代码:\ndef unsafe_sql(user_input):\n    query = 'SELECT * FROM users WHERE name=' + user_input",
 
        actual_output="发现 SQL 注入风险:第2行使用字符串拼接构造查询,应使用参数化查询。",
 
        expected_output="应指出 SQL 注入风险",
 
    ),
 
    LLMTestCase(
 
        input="审查代码:\ndef read_file(path):\n    with open(path) as f: return f.read()",
 
        actual_output="发现路径遍历风险:未验证 path 参数,攻击者可读取任意文件。",
 
        expected_output="应指出路径遍历风险",
 
    ),
 
]
 
def test_security_review_quality():
 
    """测试:安全审查质量"""
 
    for case in GOLDEN_CASES:
 
        assert_test(case, [
 
            AnswerRelevancyMetric(threshold=0.7),
 
            HallucinationMetric(threshold=0.85),
 
        ])
 
def test_no_regression():
 
    """测试:与基线分数对比,检测回归"""
 
    # 跑新版本的 Agent 处理 Golden Cases
 
    # 比较新分数 vs 基线分数
 
    # 如果掉 5% 以上 → 测试失败
 
    baseline_scores = {"security": 0.88, "quality": 0.85, "performance": 0.82}
 
    new_scores = run_eval_on_current_version()  # 调用当前 Agent
 
    for category, baseline in baseline_scores.items():
 
        new_score = new_scores.get(category, 0)
 
        drop = baseline - new_score
 
        assert drop < 0.05, (
 
            f"回归检测失败:{category}{baseline:.2f} 掉到 {new_score:.2f}(drop={drop:.2f})"
 
        )
 

六、三工具完整集成工作流


flowchart TD

    subgraph DEV["开发阶段"]

        D1["本地写代码"] --> D2["跑 DeepEval<br/>pytest tests/eval/"]

        D2 -->|通过| D3["提交 PR"]

        D2 -->|不通过| D1

    end

    subgraph CI["CI 流水线"]

        C1["PR 触发 GitHub Actions"] --> C2["DeepEval 评测<br/>threshold 门禁"]

        C2 -->|通过| C3_MERGE["合并到 main"]

        C2 -->|不通过| C3_BLOCK["PR 评论 + 阻断合并"]

    end

    subgraph PROD["生产环境"]

        P1["用户请求 → FastAPI"] --> P2["Langfuse 自动 Trace"]

        P2 --> P3["Langfuse Dashboard<br/>实时监控"]

        P3 -->|异常| P4["告警(Slack / 飞书)"]

    end

    subgraph OFFLINE["离线评测"]

        O1["每天 2am 跑 Ragas<br/>批量评测生产 Trace"]

        O1 --> O2["评分回写 Langfuse"]

        O2 --> O3["发现质量下降 → 创建 Issue"]

    end

    DEV --> CI

    CI --> PROD

    PROD --> OFFLINE

工具选型速查

我想要…用什么怎么接
看每次请求的执行链路LangfuseFastAPI 中间件 + @observe 装饰器
看 Token 用量和成本Langfuse@observe(as_type="generation")
CI 里自动跑 LLM 质量评测DeepEvalPytest 文件 + GitHub Actions
离线批量评测 RAG 质量Ragas定时脚本 + 从 Langfuse 拉数据
评测分数统一存储Langfuselangfuse.score() API
版本对比(改了 Prompt 后质量变化)LangfuseExperiments 功能

速记卡(面试闪卡)

Q1:一句话讲清「可观测性工具链实操:Langfuse / Ragas / DeepEval 链路追踪与评测」到底是什么?

A:Agent 可观测性三件套:Langfuse 全链路追踪、Ragas 离线批量评测、DeepEval 嵌入 CI 做质量门禁。

Q2:一、Agent 可观测性的三个层次 —— 怎么理解?

A:像给 Agent 做体检:传统后端只看 Metrics+Logs+Traces 三件套;Agent 多两层——LLM 调用追踪(每次调了什么 Prompt、花多少 Token)和决策可解释性(为什么选工具 A 不选 B)。基础设施层用 Prometheus,LLM 层用 Langfuse,质量层用 Ragas+DeepEval。

Q3:二、端到端集成架构 —— 怎么理解?

A:像一条流水线:FastAPI 中间件自动给每个请求建 Trace,Agent 内的 LLM 调用靠 @observe 自动挂 Span、工具调用手动建 Span,全进 Langfuse 库;离线时 Ragas 和 DeepEval 批量打分再回写 Langfuse 的 Score API——三工具各管一摊,不是竞争关系。

Q4:三、Langfuse:全链路追踪核心 —— 怎么理解?

A:像给每次对话装行车记录仪:中间件为每个 HTTP 请求建 Trace,@observe 把 LLM 调用标成 generation 自动记 model/token/latency/输入输出,工具调用手动 start_as_current_observation 建 Span。Dashboard 里能看到完整的 Trace→Span→Generation 树和 Score。

Q5:四、Ragas:离线批量评测 —— 怎么理解?

A:像定期抽检试卷:生产环境不能实时跑(LLM Judge 费钱费时),所以定时从 Langfuse 拉 Traces 当数据集,用 Faithfulness、AnswerRelevancy 等 LLM Judge 指标批量打分,再把结果回写 Langfuse(langfuse.score)。

Q6:核心速记主线有哪些?

  • 三个层次:传统三件套 + LLM 调用追踪 + 决策可解释性

  • 集成架构:FastAPI 中间件建 Trace,@observe 自动 Span,评分回写 Langfuse

  • Langfuse:全链路追踪核心,generation 记 token/延迟,Dashboard 看调用树

  • Ragas/DeepEval:离线批量评测 + CI 质量门禁,阻断坏 PR

口诀

A:Agent 要透明,三件套配齐;

Langfuse 追链路,调用全知悉;

Ragas 离线评,DeepEval 守CI;

评分回写库,质量看得见。

相关链接


技术学习路线图 > Harness 与评测