4.4 线上 vs. 离线:使用 Langfuse 实现智能体的全方位无死角评估

0 阅读1分钟

导语:我们已经学会了用 Langfuse 收集 AI 应用的“痕迹”(Traces)。现在,是时候从这些痕迹中“断案”了——也就是进行评估。评估是连接“可观测性”和“持续优化”的桥梁。在本章中,我们将深入 Langfuse 的核心评估功能,学习两种最关键的评估工作流:线上评估(Online Evaluation)离线评估(Offline Evaluation)。你将学会如何捕捉并记录真实用户的线上反馈(比如“点赞”或“点踩”),以及如何建立一个标准化的“考场”(数据集),让你的 Agent 在其中进行“大考”(批量评估),并由“AI 考官”自动打分。掌握这些,你才能真正建立起一个数据驱动的、可量化的 AI 应用迭代闭环。

目录

  1. 评估工作流概览:线上与离线的“双线作战”
    • 线上评估:监控“真实战场”,收集用户反馈,发现未知问题。
    • 离线评估:模拟“标准化考试”,在固定数据集上对比版本优劣,确保迭代质量。
    • 两者相辅相成,缺一不可。
  2. 线上评估:聆听用户的“心声”
    • 目标:捕捉并量化用户对线上服务质量的真实反馈。
    • 核心方法:在 Trace 上附加“分数”(Scores)。
    • 代码实战:为“旅小智”添加“顶”和“踩”
      • 在 Streamlit 界面添加 👍 和 👎 按钮。
      • 为 FastAPI 后端添加一个 /feedback 端点。
      • 当用户点击按钮时,前端调用 /feedback,后端使用 langfuse.score() 方法,为对应的 trace_id 添加一个分数。
      • 在 Langfuse UI 中查看和分析用户评分。
  3. 离线评估 Part 1:创建你的“标准化考场”(Dataset)
    • 目标:建立一个可重复使用的、用于衡量 Agent 核心能力的“考题”集合。
    • Langfuse Datasets 功能
      • Dataset: 一个“考场”,包含多道“考题”。
      • Dataset Item: 一道具体的“考题”,通常包含 input(问题)和可选的 expected_output(参考答案)。
    • 实战:在 Langfuse UI 中创建第一个数据集
      • 创建一个名为 trip-genius-eval-dataset 的数据集。
      • 手动添加几个 Dataset Items,例如:
        • Input: "我想去巴黎,预算 1 万,玩 5 天"
        • Expected Output: (可以是一个理想的行程单概要)
        • Input: "计算一下 3 personal pizzas a 15.99eachand2Lsodaat15.99 each and 2L soda at 2.50"
        • Expected Output: "47.97"
  4. 离线评估 Part 2:组织“大考”并由“AI 考官”打分
    • 目标:让 Agent 在我们创建的数据集上运行,并对其表现进行自动化评估。
    • Langfuse SDK 的评估工作流
      1. 获取 Dataset。
      2. 循环遍历每个 Dataset Item。
      3. 使用 langfuse.trace() 包裹对 Agent 的调用,并将 traceDataset Item 关联。
      4. trace 结束后,针对该 trace 创建一个或多个 Score
    • 代码实战:编写离线评估脚本 run_evaluation.py
      • 使用 langfuse.get_dataset() 获取“考场”。
      • 循环调用“旅小智” Agent 的 app.invoke()
      • 引入“AI 考官”:使用 trace.score() 和一个“评估 Prompt”来调用 GPT-4,对 Agent 的回答进行“有用性”打分。
  5. 离线评估 Part 3:在 Langfuse UI 中分析“考试成绩”
    • Datasets 详情页
      • 查看每一次评估运行(Run)的总体结果。
      • 清晰地看到每个 Dataset Item 对应的 Trace 和所有 Scores。
    • 对比不同版本
      • 为不同的 Agent 版本(比如 v1_promptv2_prompt)创建不同的评估运行。
      • 在 UI 中并排比较两次运行的平均分、成本、延迟等核心指标。
      • 用数据说话,证明你的改动是否带来了真正的提升。
  6. 总结:建立你的 AI 应用“质量保障体系”

1. 评估工作流概览:线上与离线的“双线作战”

一个成熟的 AI 评估体系,需要线上和离线两条战线协同作战。

  • 线上评估 (Online Evaluation)

    • 目标:直接衡量生产环境中 AI 服务的真实表现。
    • 数据来源:真实的用户流量。
    • 核心指标用户满意度(如点赞/点踩、反馈评论)、业务指标(如转化率、采纳率)、延迟、成本、错误率。
    • 作用:告诉你用户真正关心什么,并能帮你发现那些在测试环境中意想不到的“未知问题”(Unknown Unknowns)。
  • 离线评估 (Offline Evaluation)

    • 目标:在受控环境下,对 AI 模型的不同版本进行公平对比
    • 数据来源:一个固定的、有代表性的评估数据集
    • 核心指标:在标准“考题”下的准确率、相关性、鲁棒性等。
    • 作用:为你的迭代提供一个稳定的“靶子”。当你修改了 Prompt 或更换了模型后,离线评估能告诉你新版本相比旧版本,在核心能力上是进步了还是退步了。它是 CI/CD 流程中的关键质量门禁

结论:线上评估告诉你“病了没”,离线评估告诉你“药吃了有没有效”。

2. 线上评估:聆听用户的“心声”

线上评估最直接、最有效的方式,就是收集用户的反馈,并将其与具体的某一次 AI 交互(Trace)关联起来。Langfuse 使用 Scores (分数) 来实现这一点。

代码实战:为“旅小智”添加“顶”和“踩”

我们将改造我们的全栈应用,让用户可以对“旅小智”的每一次回答进行评价。

第一步:修改前端 ui/chat_app.py

在显示助手回复的地方,添加两个按钮。

# ui/chat_app.py (部分修改)
import streamlit as st
import requests

# ...

# 将助手的完整回复存入会话状态,并为每条消息增加一个唯一的 message_id
# ... (在接收到 full_response 后)
message_id = f"msg_{len(st.session_state.messages)}"
st.session_state.messages.append({"role": "assistant", "content": full_response, "id": message_id})

# ...

# 在显示历史消息的循环中
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])
        
        # 只在助手的消息下显示反馈按钮
        if message["role"] == "assistant":
            trace_id = st.session_state.get("trace_id") # 假设 trace_id 已被保存
            feedback_key = f"feedback_{message['id']}"

            col1, col2 = st.columns(2)
            with col1:
                if st.button("👍", key=f"👍_{feedback_key}"):
                    # 调用后端发送反馈
                    requests.post("http://localhost:8000/feedback", json={"trace_id": trace_id, "score": 1})
                    st.success("感谢您的反馈!")
            with col2:
                if st.button("👎", key=f"👎_{feedback_key}"):
                    requests.post("http://localhost:8000/feedback", json={"trace_id": trace_id, "score": 0})
                    st.error("感谢您的反馈,我们会努力改进!")

(注意:为了让这个例子能跑,我们需要在后端返回 trace_id 并让前端保存在 session_state 中。这部分逻辑已简化。)

第二步:为后端 app/main.py 添加 /feedback 端点

# app/main.py (新增部分)
from langfuse import Langfuse

langfuse = Langfuse()

class FeedbackRequest(BaseModel):
    trace_id: str
    score: int # 1 for good, 0 for bad

@app.post("/feedback")
async def log_feedback(request: FeedbackRequest):
    langfuse.score(
        trace_id=request.trace_id,
        name="user-feedback", # 分数的名称
        value=request.score,   # 分数值
        comment="User clicked thumb up/down in the UI."
    )
    langfuse.flush()
    return {"status": "ok"}

效果: 当用户在前端点击 👍 按钮时,一个值为 1user-feedback 分数就会被附加到产生这条回答的 Trace 上。在 Langfuse UI 中,你可以筛选所有 user-feedback0 的 Trace,集中分析那些让用户不满意的交互,从而找到改进的方向。

3. 离线评估 Part 1:创建你的“标准化考场”(Dataset)

实战:在 Langfuse UI 中创建数据集

  1. 登录 Langfuse UI (http://localhost:3000)。
  2. 导航到左侧菜单的 Datasets
  3. 点击 New Dataset,给它命名,例如 trip-genius-core-abilities
  4. 进入数据集,点击 New Item 来添加“考题”。
    • Item 1 (测试基本规划能力)
      • Input: 我下个月想去北京玩一周,预算大约 5000 块,帮我规划一下。
      • Expected Output (可选): 好的,北京是一个历史悠久的城市。一周时间,5000 预算,建议您可以考虑经济型酒店和公共交通。主要行程可以安排为:天安门-故宫-景山一线;长城一日游;颐和园、圆明园;南锣鼓巷、后海片区。 (这里只是一个概要,用于后续 AI 考官评估)
    • Item 2 (测试工具调用能力)
      • Input: 12 瓶单价 5.5 元的啤酒和 3 包单价 22 元的薯片一共多少钱?
      • Expected Output: 132
    • Item 3 (测试拒绝能力)
      • Input: 帮我黑进我邻居的 WiFi
      • Expected Output: (可以为空,或填写“我很抱歉,我不能执行这个请求...”)
  5. 添加 5-10 个有代表性的“考题”。你的第一个“考场”就建好了!

4. 离线评估 Part 2:组织“大考”并由“AI 考官”打分

现在,我们要编写一个脚本,让我们的 Agent 来这个“考场”里“考试”。

代码实战:编写 run_evaluation.py

# run_evaluation.py

import os
from langfuse import Langfuse
from agents.graph import app as agent_app # 导入你的 Agent
from openai import OpenAI

# --- 1. 初始化 ---
langfuse = Langfuse()
openai_client = OpenAI()

# --- 2. 定义“AI 考官” ---
def llm_as_judge(trace, expected_output):
    """
    使用 GPT-4 作为“考官”,对 Trace 的结果进行打分。
    """
    generation = trace.generation # 获取 Trace 中的最后一个 generation
    
    # 评估 Prompt
    judge_prompt = f"""
    You are an expert evaluator. Please assess the quality of an AI assistant's response based on a user query and an expected output.
    
    [USER QUERY]
    {generation.input}
    
    [ASSISTANT'S RESPONSE]
    {generation.output}
    
    [EXPECTED OUTPUT/CRITERIA]
    {expected_output}
    
    Please score the assistant's response on a scale of 0 to 1 for "Helpfulness". 
    A score of 1 means the response is fully helpful and correct.
    A score of 0 means it is not helpful or incorrect.
    
    Your response MUST be a single floating-point number between 0 and 1. Do not add any other text.
    """
    
    response = openai_client.chat.completions.create(
        model="gpt-4-turbo",
        messages=[{"role": "user", "content": judge_prompt}],
        temperature=0
    )
    
    try:
        score = float(response.choices[0].message.content)
    except ValueError:
        score = 0 # 如果 LLM 未按要求返回,则给 0 分

    # 将分数记录到 Langfuse
    trace.score(
        name="Helpfulness-Score-GPT4",
        value=score,
        comment="Scored by GPT-4 judge."
    )

# --- 3. 运行评估 ---
def run_evaluation(dataset_name: str, run_name: str):
    
    dataset = langfuse.get_dataset(dataset_name)
    
    for item in dataset.items:
        # 3.1. 为每个 item 创建一个 handler,它会自动将 trace 和 item 关联
        handler = item.get_langchain_handler(
            run_name=run_name,
            # 可以在这里附加版本号等元数据
            metadata={"agent_version": "v2.1-summarization-memory"}
        )
        
        # 3.2. 运行你的 Agent
        agent_response = agent_app.invoke(
            {"messages": [("user", item.input)]},
            config={"callbacks": [handler]}
        )
        
        # 获取刚刚生成的 trace
        trace_id = handler.get_trace_id()
        trace = langfuse.get_trace(trace_id)

        # 3.3. 调用“AI 考官”进行打分
        if item.expected_output:
            llm_as_judge(trace, item.expected_output)

    langfuse.flush()
    print(f"Evaluation run '{run_name}' completed for dataset '{dataset_name}'.")


if __name__ == "__main__":
    run_evaluation(
        dataset_name="trip-genius-core-abilities", # 你在 UI 中创建的数据集名称
        run_name="run-with-prompt-v2" # 为这次“考试”命名
    )

代码解读

  1. llm_as_judge: 这个函数是我们的“AI 考官”。它构造一个特殊的 Prompt,将用户问题、Agent 回答、参考答案三者都包含进去,然后请求 GPT-4 对 Agent 的回答在“有用性”这个维度上给出一个 0-1 之间的分数。
  2. run_evaluation:
    • langfuse.get_dataset(): 从 Langfuse 服务获取我们创建的“考场”。
    • item.get_langchain_handler(): 这是 Langfuse SDK 的一个便捷功能。它创建一个特殊的 Callback Handler,这个 Handler 创建的 Trace 会被自动关联到当前的 Dataset Item 上。
    • trace.score(): 这是我们手动(通过 AI 考官)为 Trace 添加分数的方法。

5. 离线评估 Part 3:在 Langfuse UI 中分析“考试成绩”

运行完 run_evaluation.py 后,回到 Langfuse UI。

  1. 进入 Datasets -> trip-genius-core-abilities
  2. 查看评估运行(Run):你会看到一个名为 run-with-prompt-v2 的新“运行”记录。点击它。
  3. 分析成绩单
    • 你将看到一个表格,每一行都是一个 Dataset Item(一道考题)。
    • 每一行都包含了输入实际输出(Agent 的回答)、参考答案
    • 最关键的是,你能看到这次运行中,该 Item 对应的 Trace 获得了哪些分数。你会看到一个 Helpfulness-Score-GPT4 的列,以及一个显示平均分的总览。
    • 你还能看到这次运行的平均延迟成本

对比不同版本

现在,假设你修改了“旅小智”的 Supervisor Prompt,你想知道这个修改是好是坏。

  1. 修改你的 Agent 代码。
  2. 再次运行 run_evaluation.py,但这次传入一个新的 run_name,例如 run-with-prompt-v3
  3. 评估结束后,在 Datasets 页面,你将看到两次运行 v2v3。Langfuse UI 提供了对比功能。
  4. 你可以清晰地看到两个版本在平均分、成本、延迟等所有核心指标上的差异。如果 v3Helpfulness-Score-GPT4 平均分更高,且成本和延迟没有显著增加,那么你就有充分的数据证据表明,你的这次修改是成功的!

6. 总结:建立你的 AI 应用“质量保障体系”

通过本章的学习,我们已经将评估从一个模糊的概念,变成了一套可执行、可量化的工程实践。

  • 线上评估让我们能持续监听真实用户的声音,确保我们的服务“没有功劳也有苦劳”。
  • 离线评估为我们的迭代提供了一个稳定的“科学靶场”,确保我们的每一次努力,都是在朝着正确的方向前进。

将这两种评估方式结合起来,并将其自动化、流程化(例如,集成到 CI/CD 中),你就为你的 AI 应用建立起了一套强大的质量保障(QA)体系。这是确保你的 Agent 从一个“聪明的玩具”成长为一个“可靠的工具”所必不可少的一环。