07 Evaluation Observability

本文记录的是我用 Vibe Coding 方式学习 AI 应用开发中相关评估指标和评估方法的 AI (Gemini-3.1-Pro)生成对话。
为什么学:没有评估 = 无法上生产(2026 年 LLMOps 标配)。
核心内容:RAGAS / DeepEval 指标、Langfuse(开源首选) / LangSmith / Arize Phoenix、Prompt 版本管理、Trace 分析。
项目:给前面所有项目加上完整评估 + 可视化仪表盘。

介绍 AI 评估的核心指标 (Faithfulness, Answer Relevance, Context Relevance)

创建第一个评估代码练习 (LLM-as-a-Judge) 并提供挖空供用户实现

你好!很高兴能带你进入AI 应用评估与可观测性(Evaluation & Observability)这极其关键的一环。

在业界有一句铁律:“没有评估 = 无法上生产”。大模型总是会带来不可控的输出,如果不把这些输出“量化”,我们就永远不知道今天的系统是不是比昨天好。

📅 下一步计划

为了达成目标,我们的学习路径如下:

  1. 理解核心概念:掌握目前业界最主流的 AI 应用评估指标(RAGAS 体系)。
  2. 动手实践评估原理:不急着用框架,先自己手写一个“大模型作为裁判 (LLM-as-a-Judge)”的代码,深刻理解它是怎么打分的。
  3. 接入专业评估框架:在后续项目中引入 RAGAS 或 DeepEval,实现批量化、自动化评估。
  4. 引入可观测性(Observability):接入 Langfuse,实现完整的请求追踪 (Trace) 和仪表盘监控。

🧠 概念讲解:有哪些评估指标?如何量化?

以我们之前 02-RAG-Basics 目录下的问答系统为例,2026 年最成熟的评估标准是 RAGAS 评估指标体系。要评估一个 RAG(检索增强生成)系统,我们主要看以下三个核心指标:

  1. 忠实度(Faithfulness)
    • 意义:AI 的回答是否 100% 来源于检索到的参考资料
    • 抓什么:抓“幻觉”。如果 AI 胡编乱造了资料里没有的信息,忠实度就会极低。
  2. 答案相关性(Answer Relevance)
    • 意义:AI 的回答有没有直接回答用户的问题?
    • 抓什么:抓“答非所问”。比如用户问“苹果怎么吃”,AI 回答了“苹果公司的历史”,这就很不相关。
  3. 上下文精确度/召回率(Context Precision/Recall)
    • 意义:你从向量数据库里检索出来的资料,真的是回答问题所需要的吗?
    • 抓什么:抓“检索质量差”。如果检索出来的全是废话,大模型再聪明也没用。

那这些文本指标如何“量化”呢?
传统的软件测试是写代码断言 assert result == expected,但在大模型时代,回答是灵活多变的。所以我们使用一种革命性的技术:LLM-as-a-Judge(让大模型当裁判)
原理非常简单:让一个能力极强的大模型(比如 Deepseek API)去阅读“问题”、“检索到的资料”和“AI 的回答”,然后根据我们制定的评分规则,自动打出一个 0~1 的分数。


💻 挖空练习:编写你的第一个“大模型裁判”

考虑到你偏好动手写代码和挑战 # TODO 填空 ,我已经在你的项目里为你准备好了第一份练习文件。

我已经创建了 01_llm_as_a_judge.py,这份代码模拟了评估“忠实度 (Faithfulness)”的过程。里面有 3 个关键的 # TODO 等着你来完成:

  1. 练习 1:编写裁判的 Prompt:用自然语言告诉大模型应该怎么打分,要求它分析是否有幻觉,并强制输出 JSON 格式。
  2. 练习 2:解析 JSON 结果:把大模型返回的 JSON 字符串转为 Python 字典。
  3. 练习 3:构造优质回答:测试你写的裁判是不是真的有效。

你的任务:
请打开 01_llm_as_a_judge.py 文件,尝试补全这三个 # TODO。完成后,在终端运行 python 01_llm_as_a_judge.py 看看你的裁判给出的评分和理由!

理解“忠实度”和“LLM-as-a-Judge”的运作原理了吗?如果在填空的过程中卡住了,随时把代码发给我,我们一起来推敲!等你完成这步,我们再进行代码审查。

点击查看代码
import os
import json
from openai import OpenAI
from dotenv import load_dotenv

# 加载环境变量 (需要有 DEEPSEEK_API_KEY)
load_dotenv()

def evaluate_faithfulness(question: str, context: str, answer: str) -> dict:
    """
    使用大模型作为评委 (LLM-as-a-Judge),评估“忠实度 (Faithfulness)”
    忠实度:回答是否完全基于给定的参考资料(Context),没有幻觉。
    """
    api_key = os.environ.get("DEEPSEEK_API_KEY")
    if not api_key:
        raise ValueError("请在环境变量或 .env 文件中设置 DEEPSEEK_API_KEY")

    client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
    
    # 评委的提示词
    # TODO 练习 1:请你补充下面的评委 Prompt!
    # 要求评委:
    # 1. 仔细阅读【用户问题】、【参考资料】和【AI 回答】
    # 2. 判断【AI 回答】中的每一个事实陈述,是否都能在【参考资料】中找到依据
    # 3. 给出 0 到 1 之间的分数 (1代表完全忠实,0代表全是幻觉)
    # 4. 给出简短的理由 (reason)
    # 5. 强制输出 JSON 格式,包含 "score" (float) 和 "reason" (str) 两个字段
    judge_prompt = f"""你是一个严格的 AI 评估专家。请根据以下信息评估“忠实度 (Faithfulness)”。

【用户问题】
{question}

【参考资料】
{context}

【AI 回答】
{answer}

请严格按照以下步骤进行评估:
# TODO
"""

    print("🤖 评委正在打分中...")
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": "你是一个严谨的评估器。"},
            {"role": "user", "content": judge_prompt}
        ],
        temperature=0.0, # 评估需要确定性,温度设为 0
        response_format={"type": "json_object"} # 强制输出 JSON
    )
    
    # 解析 JSON 结果
    result_str = response.choices[0].message.content
    try:
        # TODO 练习 2:请将大模型返回的 JSON 字符串转换为 Python 字典
        # 提示:使用 json 模块
        result = {} # 修改这里
    except Exception as e:
        print(f"解析 JSON 失败: {e}")
        result = {"score": 0.0, "reason": "解析失败"}
        
    return result

if __name__ == "__main__":
    # 这是一个我们前面 02-RAG-Basics 章节里可能产生的数据样本
    sample_question = "Agentic RAG 会怎么处理复杂问题?"
    sample_context = "Agentic RAG 结合了智能体的能力。当面对复杂问题时,它会将大问题拆解为多个子问题,然后对每个子问题分别进行检索和推理,最后将结果汇总生成最终答案。"
    
    # 这是一个“产生幻觉”的错误回答(包含了上下文中没有的信息:强化学习)
    sample_bad_answer = "Agentic RAG 会使用强化学习算法,将复杂问题拆解,然后通过自我对弈来找到最优解。"
    
    print("="*40)
    print("正在评估包含幻觉的回答...")
    eval_result = evaluate_faithfulness(sample_question, sample_context, sample_bad_answer)
    print(f"✅ 评估得分: {eval_result.get('score')}")
    print(f"📝 评估理由: {eval_result.get('reason')}")
    print("="*40)
    
    # TODO 练习 3:请你自己构造一个“完全忠实”的好回答,并调用 evaluate_faithfulness 看看得分

讲解 Answer Relevance 和 Context Relevance 评估方法

🎉 太棒了!你的第一个评估器运行得非常完美!
从结果能看出来,Deepseek 把 JSON 返回得非常标准,并且你写的 Prompt 让它敏锐地抓住了“强化学习”这个并不存在于参考资料中的核心幻觉。这说明你已经完全掌握了 LLM-as-a-Judge 的底层工作原理。

现在我们趁热打铁,来解答你的问题:Answer Relevance 和 Context Relevance 要怎么评估?

🧠 概念讲解

这两个指标的评估原理和 Faithfulness(忠实度)一模一样,都是写 Prompt 让大模型当裁判,唯一的区别是评委看的东西(输入数据)评判的标准不同。

1. 答案相关性 (Answer Relevance)

  • 评委看什么?:只看【用户问题】和【AI 回答】。不需要看参考资料。
  • 评判标准:回答有没有直接回答用户的问题?有没有冗长不相关的废话?
  • 常见误区:很多人会把“回答真实性”和“回答相关性”搞混。如果用户问“月亮是奶酪做的吗?”,AI 回答“是的,而且味道很好”。这个回答虽然是的,但是它的相关性很高,因为它正面回答了问题(没答非所问)。真实性由刚才的 Faithfulness 负责。

2. 上下文相关性 (Context Relevance)

  • 评委看什么?:只看【用户问题】和【检索到的参考资料】。不需要看最终的 AI 回答。
  • 评判标准:如果把这段参考资料给一个普通人,他能不能仅凭这段资料回答出用户的问题?
  • 为什么重要:如果大模型最终答错了,到底是“大模型太笨不会总结”,还是“向量数据库太蠢没搜出有用的内容”?这个指标就是专门用来“背锅定责”的,它评估的是检索器 (Retriever) 的质量

💻 挖空练习:编写相关性评估器

为了让你更深入理解它们的不同视角,我已经为你创建了第二个练习文件:02_evaluate_relevance.py。

为了避免写重复代码,我把你刚刚做过的 API 调用和 JSON 解析封装成了一个 ask_llm_judge(prompt) 函数。

你的任务:
打开 02_evaluate_relevance.py,里面有两个 # TODO

  1. 练习 1:编写“答案相关性”的打分规则(惩罚跑题和废话)。
  2. 练习 2:编写“上下文相关性”的打分规则(判断资料是否有用)。

完成并保存后,运行 python 02_evaluate_relevance.py。你会看到我在底部写了四个有趣的测试案例(包括废话连篇的回答、毫无相关的资料等)。

理解这两个指标的区别了吗?试试自己定义打分规则吧!如果有卡壳的地方,随时告诉我。

点击查看代码
import os
import json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

def ask_llm_judge(prompt: str) -> dict:
    """通用的 LLM 裁判调用函数"""
    api_key = os.environ.get("DEEPSEEK_API_KEY")
    client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
    
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": "你是一个严谨的 AI 评估专家。"},
            {"role": "user", "content": prompt}
        ],
        temperature=0.0,
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

def evaluate_answer_relevance(question: str, answer: str) -> dict:
    """
    评估“答案相关性 (Answer Relevance)”
    目标:判断 AI 的回答是否直接、切题地回答了用户的问题,没有废话或跑题。
    """
    
    # TODO 练习 1:请编写“答案相关性”的裁判 Prompt!
    # 提示:
    # 1. 裁判只需要看【用户问题】和【AI 回答】。
    # 2. 告诉裁判:如果回答跑题、或者包含了大量与问题无关的废话,就给低分。
    # 3. 如果回答直接、准确地解答了问题,给高分。
    # 4. 强制输出 JSON,包含 "score" 和 "reason"。
    judge_prompt = f"""
请评估以下回答的“答案相关性 (Answer Relevance)”。

【用户问题】
{question}

【AI 回答】
{answer}

评估标准:
- 如果回答直接、准确地解答了问题的核心要点,且没有冗余或无关信息,则为高度相关(分数接近 1.0)。
- 如果回答虽然包含正确信息,但同时混杂了大量无关信息、废话或跑题,则为中度相关(分数在 0.5 左右)。
- 如果回答完全跑题或没有回应问题的核心要点,则为不相关(分数接近 0.0)。

请严格按以下 JSON 格式输出你的评估结果:
{{"score": <float>, "reason": "<string>"}}
"""
    return ask_llm_judge(judge_prompt)


def evaluate_context_relevance(question: str, context: str) -> dict:
    """
    评估“上下文相关性 (Context Relevance)”
    目标:判断检索到的 Context 是否包含了回答 Question 所需的关键信息。
    """
    
    # TODO 练习 2:请编写“上下文相关性”的裁判 Prompt!
    # 提示:
    # 1. 裁判只需要看【用户问题】和【参考资料】。
    # 2. 告诉裁判:评估这份资料对于回答这个问题有没有帮助?
    # 3. 如果资料包含了完整的答案线索,给 1.0;如果毫无关系,给 0.0。
    # 4. 强制输出 JSON,包含 "score" 和 "reason"。
    judge_prompt = f"""
请评估以下资料的“上下文相关性 (Context Relevance)”。

【用户问题】
{question}

【参考资料】
{context}

评估标准:
- 如果参考资料包含了回答该问题所需的全部或核心关键信息,则为高度相关(分数接近 1.0)。
- 如果参考资料部分包含有用信息,对回答问题有一定帮助,则为中度相关(分数在 0.5 左右)。
- 如果参考资料与该问题毫无关系,无法用于回答问题,则为不相关(分数接近 0.0)。

请严格按以下 JSON 格式输出你的评估结果:
{{"score": <float>, "reason": "<string>"}}
"""
    return ask_llm_judge(judge_prompt)


if __name__ == "__main__":
    sample_question = "法国的首都是哪里?"
    
    # --- 测试 1:答案相关性 ---
    print("\n" + "="*40)
    print("测试 1:答案相关性 (Answer Relevance)")
    # 回答了正确事实,但废话连篇、甚至跑题到了美食
    bad_relevance_answer = "法国是一个美丽的国家,它的首都是巴黎。巴黎的法棍面包很好吃,而且那里还有卢浮宫,每年有数百万人去参观。我昨天刚吃了一个牛角包。"
    good_relevance_answer = "法国的首都是巴黎。"
    
    print("\n正在评估废话连篇的回答...")
    res1 = evaluate_answer_relevance(sample_question, bad_relevance_answer)
    print(f"得分: {res1.get('score')}, 理由: {res1.get('reason')}")
    
    print("\n正在评估直接切题的回答...")
    res2 = evaluate_answer_relevance(sample_question, good_relevance_answer)
    print(f"得分: {res2.get('score')}, 理由: {res2.get('reason')}")


    # --- 测试 2:上下文相关性 ---
    print("\n" + "="*40)
    print("测试 2:上下文相关性 (Context Relevance)")
    # 检索到了毫无关系的文本
    bad_context = "法国的国庆日是 7 月 14 日,这一天通常会有阅兵仪式和烟花表演。"
    # 检索到了精确包含答案的文本
    good_context = "巴黎是法国的首都和最大城市,也是法国的政治、经济、文化和商业中心。"
    
    print("\n正在评估无关的参考资料...")
    res3 = evaluate_context_relevance(sample_question, bad_context)
    print(f"得分: {res3.get('score')}, 理由: {res3.get('reason')}")
    
    print("\n正在评估包含答案的参考资料...")
    res4 = evaluate_context_relevance(sample_question, good_context)
    print(f"得分: {res4.get('score')}, 理由: {res4.get('reason')}")

介绍可观测性 (Observability) 概念和 Langfuse 工具

编写代码练习:将 Langfuse 接入现有的 OpenAI 调用中

没错!你总结得非常到位!利用 LLM-as-a-Judge 进行自动化评估,正是目前 AI 应用(尤其是 RAG 和 Agent)能够走向工业化生产的核心秘密。人工去看成千上万条回答是不现实的,但大模型裁判一分钟就能跑完几千条,还可以无缝集成到 CI/CD(持续集成/持续部署)流水线中。

接下来,既然我们已经解决了“如何评估(打分)”的问题,我们马上要面临下一个痛点:这些分数、每一次对话的记录、用户的反馈,我们要记在哪里?怎么查看?
这就引出了我们的下一个核心概念:可观测性(Observability)

🧠 概念讲解:什么是 AI 应用的可观测性?

如果说“评估(Evaluation)”是考试打分,那么“可观测性(Observability)”就是安装在考场里的全方位监控摄像头

想象一下,你的应用上线了,突然有个用户抱怨:“你们的 AI 刚才胡说八道!”。
作为开发者,你没有可观测性工具,你只能问用户:“你刚才问了什么?AI 怎么回的?” —— 这叫“两眼一抹黑”。

如果你有可观测性工具,你会看到一个华丽的仪表盘(Dashboard),里面记录了:

  1. Traces(追踪):用户问的每一句话,经过了哪些 Agent,调用了多少次数据库,大模型每次思考的 Prompt 是什么,耗时多少秒,花了多少 Token(钱)。
  2. Generations(生成):大模型的具体输入输出。
  3. Scores(评分):结合我们刚才学的 LLM-as-a-Judge,给每一条 Trace 自动打上的分数。

主流工具介绍

  • LangSmith:LangChain 官方出品,功能最强,但闭源且贵。
  • Arize Phoenix:轻量级,适合本地跑。
  • Langfuse目前开源界的首选! 界面美观,功能强大,支持自托管。我们今天就学它!

💻 挖空练习:将 Langfuse 接入你的应用

Langfuse 的接入极其简单。我已经帮你用 pip install langfuse 安装好了库。

它的核心魔法在于:它提供了一个一模一样的 OpenAI 客户端包装器(Wrapper)。你只需要把代码里的 from openai import OpenAI 换成 from langfuse.openai import OpenAI,其他的什么都不用改,所有的调用就会自动飞到 Langfuse 后台!

前期准备(必须完成):
由于需要仪表盘,你需要去 Langfuse 官网白嫖一个免费账号:

  1. 打开浏览器访问 Langfuse 官网 (cloud.langfuse.com) 并注册(可以使用 GitHub 登录)。
  2. 创建一个新项目(比如叫 "My-AI-Learning")。
  3. 在项目的 Settings -> API Keys 中,生成新的 API Key。
  4. 打开项目根目录下的 .env 文件(如果没有就新建一个),加入以下三行(把你的真实 Key 填进去):
    LANGFUSE_SECRET_KEY=sk-lf-...
    LANGFUSE_PUBLIC_KEY=pk-lf-...
    LANGFUSE_HOST="https://cloud.langfuse.com"
    

你的任务:
我已经创建了 03_langfuse_integration.py 文件。

  1. 请先完成上面说的 .env 配置。
  2. 打开文件,完成里面唯一的一个 # TODO(非常简单,就是正常调用大模型,只是多传一个 name 参数)。
  3. 运行代码 python 03_langfuse_integration.py
  4. 见证奇迹的时刻:回到浏览器里的 Langfuse 后台,点击左侧的 "Traces" 或 "Generations",你就能看到刚刚跑的那条数据了!能看到耗时、Token 和详细的 Prompt。

快去试试注册并跑通代码吧!如果你在获取 Key 或者运行代码时遇到任何问题,随时告诉我。成功在后台看到数据后,记得截图或者遇到卡点,请把结果告诉我!

点击查看代码
import os
from dotenv import load_dotenv
# 1. 导入 Langfuse 包装过的 OpenAI 客户端
from langfuse.openai import OpenAI

load_dotenv()

# 请确保你的 .env 文件中有以下变量:
# DEEPSEEK_API_KEY=your_deepseek_key
# LANGFUSE_PUBLIC_KEY=your_langfuse_public_key
# LANGFUSE_SECRET_KEY=your_langfuse_secret_key
# LANGFUSE_HOST="https://cloud.langfuse.com" # 或者你的私有部署地址

def generate_story_with_langfuse(topic: str):
    """
    使用集成了 Langfuse 的 OpenAI 客户端生成故事。
    所有的调用都会自动被记录 (Traced) 到 Langfuse 后台。
    """
    print(f"正在为主题 '{topic}' 生成故事,请稍候...")
    
    # 初始化客户端(注意这里使用的是 langfuse.openai.OpenAI)
    # 它会自动读取环境变量中的 LANGFUSE_* 变量进行鉴权
    client = OpenAI(
        api_key=os.environ.get("DEEPSEEK_API_KEY"), 
        base_url="https://api.deepseek.com"
    )
    
    # TODO 练习:请在这里调用 client.chat.completions.create 接口生成一段小故事
    # 提示:
    # 1. 模型使用 "deepseek-chat"
    # 2. 传入 messages,让 AI 根据 topic 创作一个 50 字左右的微小说
    # 3. 额外传入一个参数:name="story_generation" 
    #    (这是 Langfuse 特有的参数,用于在后台给这次调用起个名字,方便查找)

    # response = client.chat.completions.create( ... )
    
    # 打印结果
    # print(response.choices[0].message.content)

if __name__ == "__main__":
    # 在运行前,请确保你已经去 https://cloud.langfuse.com 注册了账号,
    # 创建了 Project,并把对应的 Key 写到了 .env 文件中。
    generate_story_with_langfuse("一只想飞的赛博企鹅")

讲解并实现将 LLM 评估与 Langfuse 结合,构建完整的评估+可观测闭环

太棒了!能在后台看到数据,说明你的环境已经完全打通了。你可以点开那条记录看看,是不是能清晰地看到花费了多少 token,耗时多少毫秒?这就是可观测性的魅力。

接下来,我们要完成这个章节最核心、也是在企业里最有价值的最后一步构建评估与可观测性的闭环 (Evaluation + Observability Loop)

🧠 概念讲解:为什么要闭环?

我们前面学了:

  1. 怎么用 LLM 当裁判打分 (Evaluation)
  2. 怎么把大模型的调用记录存下来 (Observability)

在真实的企业级应用里,这两者是紧密结合的:

  1. 用户问了一个问题,系统回答了。Langfuse 记录下了这次对话(生成了一个唯一的 Trace ID)。
  2. 在后台,我们的“大模型裁判”立刻对这次回答进行评估,打出分数(比如:相关性 1.0,忠实度 0.8)。
  3. 最关键的一步:裁判把这个分数通过 API 传回给 Langfuse,并绑定在刚才那个 Trace ID 上。

结果就是:当你在 Langfuse 后台看用户的对话记录时,旁边直接亮着绿灯(高分)或红灯(低分)!你只需要筛选出低分的记录去重点分析,极大地提高了迭代效率。


💻 挖空练习:完成最终闭环

我已经为你写好了最后一份练习代码:04_eval_observability_loop.py。

这份代码模拟了上述的闭环流程:

  1. 它用 @observe 装饰器自动追踪业务逻辑,并获取了 trace_id
  2. 它调用了我们之前写的裁判函数,得到了评分(score)。
  3. 只差最后一步:把分数传回 Langfuse。

你的任务:
请打开 04_eval_observability_loop.py 文件,找到 # TODO 练习 的位置。
按照提示,使用 langfuse.score() 方法,把得到的分数传上去。

完成填空后,运行 python 04_eval_observability_loop.py
最激动人心的验证:运行成功后,再去你的 Langfuse 后台,找到刚刚生成的那条 Trace,点进去看,你是不是在详情页里看到了一个叫做 "helpfulness" 的评分标签?

快去试试!这是你完成“从零到专业 LLMOps”的最后一块极其重要的拼图。完成后告诉我你看到了什么!

点击查看代码
import os
import json
from dotenv import load_dotenv

# 1. 导入原始的 OpenAI 和 Langfuse 客户端
from openai import OpenAI
from langfuse import Langfuse, observe

load_dotenv()

# 初始化普通的 OpenAI 客户端用于业务逻辑
api_key = os.environ.get("DEEPSEEK_API_KEY")
client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")

# 初始化 Langfuse 客户端用于打分
langfuse = Langfuse()

# ==========================================
# 1. 我们的业务逻辑 (带 @observe 装饰器,自动追踪)
# ==========================================
# TODO: 可以在 @observe 中通过 name 参数自定义 Trace 的名字,例如 name="My-QA-Service"
@observe(name="My-QA-Service") 
def answer_question(question: str) -> str:
    print(f"正在回答问题: {question}")
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": "你是一个有用的助手。回答请尽量简短。"},
            {"role": "user", "content": question}
        ]
    )
    answer = response.choices[0].message.content
    print(f"AI 回答: {answer}")
    
    # 关键:获取当前这通对话在 Langfuse 中的 Trace ID,一会儿打分要用!
    trace_id = langfuse.get_current_trace_id()
    return answer, trace_id

# ==========================================
# 2. 我们的评估逻辑 (LLM-as-a-Judge)
# ==========================================
def evaluate_helpfulness(question: str, answer: str) -> dict:
    """使用大模型评估回答的'有用性'"""
    judge_prompt = f"""
请评估以下回答的“有用性 (Helpfulness)”。
【用户问题】
{question}
【AI 回答】
{answer}

如果回答解决了问题,给 1.0 分;如果部分解决,给 0.5 分;如果完全没用,给 0.0 分。
输出 JSON: {{"score": <float>, "reason": "<string>"}}
"""
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": "你是一个严谨的 AI 评估专家。"},
            {"role": "user", "content": judge_prompt}
        ],
        temperature=0.0,
        response_format={"type": "json_object"}
    )
    return json.loads(response.choices[0].message.content)

# ==========================================
# 3. 闭环:业务生成 -> LLM 打分 -> 分数传回 Langfuse
# ==========================================
def main():
    test_question = "如何用 Python 打印 Hello World?"
    
    # 第一步:业务系统生成回答(此时已经被 Langfuse 记录了 Trace)
    answer, trace_id = answer_question(test_question)
    
    # 第二步:大模型裁判进行打分
    print("\n🤖 裁判正在打分...")
    eval_result = evaluate_helpfulness(test_question, answer)
    score = eval_result.get("score", 0)
    reason = eval_result.get("reason", "")
    print(f"得分: {score}, 理由: {reason}")
    
    # 第三步:将分数绑定到刚刚那条 Trace 上!
    # TODO 练习:使用 langfuse.create_score() 方法将分数传回后台。
    # 提示:需要传入以下参数(通过关键字参数传入):
    # trace_id=trace_id, name="helpfulness", value=score, comment=reason
    
    # langfuse.create_score(...)
    
    # 确保数据发送完毕
    langfuse.flush()
    print(f"\n✅ 流程结束!请前往 Langfuse 后台查看 Trace ID: {trace_id} 的详情。")

if __name__ == "__main__":
    main()

解释 Langfuse 中 Trace Name 和 Score Name 的区别

这是一个非常敏锐且关键的发现!这涉及到 Langfuse 中两个不同的概念:Trace Name (追踪名称)Score Name (评分名称)

让我用一个通俗的比喻来解释:

  1. Trace Name (answer_question) = 考试的名字

    • 默认情况下,如果你只写了 @observe(),Langfuse 会自动把被装饰的函数名(即 answer_question)作为这条追踪记录的名字。
    • 这代表了“这次执行的业务动作是什么”。
  2. Score Name (helpfulness) = 这门考试里某一科的成绩单

    • 一场考试可以有多个科目的成绩。同样,一条 Trace 也可以有多个 Score!
    • 你刚才传的 name="helpfulness",是指“在这个 answer_question 的操作里,我们在‘有用性’这个维度上打了 1.0 分”。
    • 如果你再跑一个相关性的评估,你完全可以对同一个 Trace ID 再调用一次 langfuse.create_score(name="relevance", value=0.8),那么你在后台就会看到这条 answer_question 的记录上,挂着两个标签:helpfulness: 1.0relevance: 0.8

🛠️ 如何自定义这个名字呢?

如果你不想让记录的名字叫做函数名 answer_question,你可以给 @observe 装饰器传参数。

我已经帮你修改了 04_eval_observability_loop.py 代码:

@observe(name="My-QA-Service") 
def answer_question(question: str) -> str:
    ...

你可以再次运行 python 04_eval_observability_loop.py,然后去后台看,你会发现新出现的那条记录的名字变成了 "My-QA-Service",并且里面同样挂着 "helpfulness" 的分数!

理解这两个概念的区别了吗?在实际企业项目中,我们通常会把 Trace Name 设为“服务名或接口名”(比如 /api/chat),把 Score Name 设为具体的指标名(如 faithfulness, latency_score)。

到目前为止,我们在评估与可观测性这一章的内容就非常圆满了!你接下来是想去其他目录看看,还是有什么特别想深入想了解的呢?

posted @ 2026-03-29 17:14  鼓舞人心的杰克森  阅读(3)  评论(0)    收藏  举报