How to Evaluate AI Agents with an LLM-as-a-Judge Harness in Python

TL;DR · AI 摘要
本文提供本地化AI代理评估框架,结合LLM作为裁判与规则检查,使用LangChain、Ollama等工具实现零API成本测试。
核心要点
- 使用LLM-as-a-judge与规则检查双重机制评估AI代理输出
- 基于Python的本地化方案无需API调用成本
- 包含可复用的测试用例与评分体系设计
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- AI代理评估框架
- 核心机制
- 规则检查
- LLM裁判
- 实现组件
- LangChain v1
- Ollama
- Qwen
- 评估流程
- 测试用例设计
- 结果验证
- 输出汇总
金句 / Highlights
值得收藏与分享的关键句。
规则检查确保确定性,LLM裁判处理语义模糊场景,二者结合提升评估完整性
使用Ollama本地运行Qwen模型,避免API调用成本且支持多平台部署
测试框架包含32GB内存的MacBook Pro验证,可适配低内存设备
如何使用LLM-as-a-Judge框架在Python中评估AI代理
2026年7月17日
/
#AI
Darsh Shah
在本教程中,我将向您展示如何使用简单且可重复的评估框架来评估本地AI代理。
该框架会将代理运行在一组测试用例上,通过基于规则的断言和LLM-as-a-judge同时验证结果,并输出清晰的通过/失败总结。
所有操作均可在本地机器上完成,使用LangChain v1、Ollama、Qwen和Python,因此无需支付任何API费用。
目录
- 背景
- 什么是代理评估?
- 什么是LLM-as-a-judge?
- 动机与架构
- 步骤1:安装Ollama并拉取模型
- 步骤2:安装Python依赖项
- 步骤3:待测试的代理
- 步骤4:编写评估框架
- 步骤5:运行评估
- 示例输出
- 结论
背景
大多数本地AI代理的测试方式相同:输入几个问题,答案看起来正确,然后就发布。这种方法在我们更改提示、更换模型或添加工具时会失效,此时某些功能会悄然崩溃,直到为时已晚才被发现。
常规的Python代码有单元测试来捕获这些问题。AI代理却无法免费获得这种保障。即使输入相同,代理在不同运行中可能表现不同,细微的更改可能引入容易被忽略的回归问题。如果没有可重复的方法在多个输入上测试代理并评分输出,我们只能对代理的行为进行猜测。
一个简单的解决方案是构建一个轻量级评估设置,包含Python脚本、测试用例列表、基于规则的检查以及LLM-as-judge。这为我们提供了一种实用的方法,在任何更改之前测试代理。
要跟随本教程,您需要在本地机器上安装Ollama。本教程适用于macOS、Windows和Linux系统。我使用的是配备32GB内存的MacBook Pro,但您可以通过从Ollama选择更小的Qwen模型,在内存更少的机器上运行此教程。
什么是代理评估?
代理评估是指将代理运行在固定输入集上,并将输出结果与预期进行评分。这相当于AI领域的测试套件。
目标不是证明代理完美无缺,而是当您进行更改时能够捕获回归问题。
一个有用的评估包含三个部分:
- 测试用例:包含预期行为的输入列表。
- 检查:为每个输入评分代理输出的函数。
- 总结:通过/失败统计,让您了解代理的表现。
什么是LLM-as-a-judge?
评分代理输出有两种实用方法。第一种是基于规则的检查。您断言诸如“输出是否包含巴黎”或“代理是否调用了word_count工具”等条件。这些检查便宜、快速且确定性高。
第二种是LLM-as-a-judge。您让另一个LLM阅读输入和代理输出,然后根据评分标准进行评分。评分标准可以是简单的通过/失败输出。这对于难以直接断言的模糊问题非常有用,例如“答案是否真正解决了用户的问题”。权衡是评判者本身是LLM,可能出错。
在本教程中,我们将使用相同模型,但通过不同提示进行评判。
为了简化流程,我们将评估一个带有两个工具的小型本地代理:一个用于获取当前时间,另一个用于统计字数。评估框架从Python中读取测试用例列表,将每个用例通过代理运行,应用基于规则的检查和LLM-as-judge评分,并输出通过/失败的汇总结果。
在下面的示例测试用例中,expected_keyword和expected_tool是两个基于规则的检查项。judge_rubric是LLM评委的评分标准。
{
"input": "What is the capital of France?",
"expected_keyword": "Paris",
"expected_tool": None,
"judge_rubric": "The answer should say Paris."
}代理和评委都通过Ollama本地运行,因此不会产生每次调用模型的API费用。
第1步:安装Ollama并拉取模型
要开始使用,请为您的平台安装Ollama应用。我们将使用Qwen作为代理和评委。我使用的是qwen3.5:4b版本。
ollama pull qwen3.5:4b如果您的机器内存较低,可以改用qwen3.5:0.8b版本,但此时评委评分可能会更不稳定。
第2步:安装Python依赖项
创建虚拟环境并安装所需包:
python3 -m venv venv
source venv/bin/activate
pip install langchain langchain-core langchain-ollama本教程需要langchain>=1.0.0。
第3步:待测试的代理
我们将使用一个带有两个工具的小型工具调用代理。框架将代理视为一个黑盒系统,因此评估过程中代理本身的任何内容都不会改变。
下面的代理代码定义了两个工具:current_time()用于获取当前时间,word_count()用于统计输入句子中的字数。使用LangChain的build_agent()创建代理,并使用简单的系统提示。
将以下代码保存为agent.py:
from datetime import datetime
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
@tool
def current_time() -> str:
"""Return the current local date and time."""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def word_count(text: str) -> int:
"""Count the number of words in a piece of text."""
return len(text.split())
def build_agent():
model = ChatOllama(model="qwen3.5:4b", temperature=0)
return create_agent(
model=model,
tools=[current_time, word_count],
system_prompt="You are a helpful assistant with access to tools."
)第4步:编写评估框架
框架对每个测试用例执行以下三个操作:
- 运行代理并收集答案及任何工具调用记录。
- 使用简单的基于规则的断言检查结果,包括预期关键词(如果输出中包含关键词)和预期工具(如果使用了工具)。
- 通过LLM-as-judge对输出进行评分。判断输入提示包含原始用户提示、代理的答案和评分标准。LLM评委被要求“答案是否符合评分标准?仅用YES或NO回答”。评委的输出结果为YES或NO。
测试用例在文件顶部的代码中定义。对于每个用例,代码会调用工具调用代理以获取代理的输出,然后打印答案及任何工具调用。随后将输出传递给check_keyword()和check_tool()方法进行基于规则的检查。之后调用llm_judge()方法通过模型判断代理之前的输出。最后在检查完成后,代码会打印最终的通过/失败总结。
保存为eval.py:
from langchain_ollama import ChatOllama
from agent import build_agent
# -------------------------------
# 测试用例
# -------------------------------
# 每个测试用例包含:输入、答案中预期的关键字、
# 代理应调用的预期工具(或None),以及评判标准。
TEST_CASES = [
{
"input": "What time is it right now?",
"expected_keyword": ":", # 包含冒号的时间字符串
"expected_tool": "current_time",
"judge_rubric": "答案应包含具体时间。",
},
{
"input": 'How many words are in: "LangChain makes tool calling easier"',
"expected_keyword": "5",
"expected_tool": "word_count",
"judge_rubric": "答案应明确说明词数为5。",
},
{
"input": "What is the capital of France?",
"expected_keyword": "Paris",
"expected_tool": None,
"judge_rubric": "答案应说明是Paris。",
},
{
"input": "How many words are in 'LangChain makes tool calling easier'? Avoid tool use",
"expected_keyword": None,
"expected_tool": "word_count",
"judge_rubric": (
"助手应调用word_count工具。"
)
},
]
# -------------------------------
# 基于规则的检查
# -------------------------------
def check_keyword(answer, keyword):
if keyword is None:
return True
return keyword.lower() in answer.lower()
def check_tool(tool_calls, expected_tool):
if expected_tool is None:
return len(tool_calls) == 0
return expected_tool in tool_calls
# -------------------------------
# LLM作为评判者
# -------------------------------
judge = ChatOllama(model="qwen3.5:4b", temperature=0)
def llm_judge(user_input, answer, rubric):
prompt = (
f"User asked: {user_input}\n"
f"Agent answered: {answer}\n"
f"Rubric: {rubric}\n\n"
f"答案是否符合评判标准?仅用YES或NO回复。"
)
response = judge.invoke(prompt).content.strip().upper()
return response.startswith("YES")
# -------------------------------
# 运行评估
# -------------------------------
def run_evals():
agent = build_agent()
passed_count = 0
for i, case in enumerate(TEST_CASES, start=1):
# 运行代理
result = agent.invoke({
"messages": [{"role": "user", "content": case["input"]}],
})
# 提取答案和代理调用的工具
answer = result["messages"][-1].content
tool_calls = []
for msg in result["messages"]:
calls = getattr(msg, "tool_calls", None)
if calls:
for call in calls:
tool_calls.append(call["name"])print(f"[答案] 测试 {i}: {answer} \n[工具] {tool_calls}")
# 执行三项检查
keyword_ok = check_keyword(answer, case["expected_keyword"])
tool_ok = check_tool(tool_calls, case["expected_tool"])
judge_ok = llm_judge(case["input"], answer, case["judge_rubric"])
passed = keyword_ok and tool_ok and judge_ok
if passed:
passed_count += 1
# 输出结果
status = "通过" if passed else "失败"
print(f"[{status}] 测试 {i}: {case['input']}")
if not keyword_ok:
print(f" - 关键词检查失败 (预期 '{case['expected_keyword']}')")
if not tool_ok:
print(f" - 工具检查失败 (预期 {case['expected_tool']}, 实际 {tool_calls})")
if not judge_ok:
print(f" - 评估结果为 NO")
print(f"\n{passed_count}/{len(TEST_CASES)} 通过")
if __name__ == "__main__":
run_evals()第5步:运行评估
在Ollama后台运行时,执行测试框架:
python eval.py测试框架会将每个测试用例传递给代理,执行检查并输出总结。每次修改系统提示、更换模型或添加新工具时都可以重新运行。
示例输出
这是我机器上运行的结果示例:
$python eval.py
[答案] 测试 1: 现在是2026年7月10日中午12:44:39
[工具] ['current_time']
[通过] 测试 1: 现在几点了?
[答案] 测试 2: "LangChain makes tool calling easier" 共有5个单词。
[工具] ['word_count']
[通过] 测试 2: "LangChain makes tool calling easier" 有多少个单词?
[答案] 测试 3: 法国的首都是巴黎。
[工具] []
[通过] 测试 3: 法国的首都是哪里?
[答案] 测试 4: "LangChain makes tool calling easier" 包含5个单词。
[工具] []
[失败] 测试 4: "LangChain makes tool calling easier" 有多少个单词?请勿使用工具
- 工具检查失败 (预期 word_count, 实际 [])
- 评估结果为 NO
3/4 通过三个测试用例通过。第四个失败是因为代理遵循了用户不使用任何工具的指令。从评估输出可以看到它未能通过check_tool()规则,LLM评估也返回了NO。
这正是评估框架旨在捕获的信号。没有框架的话,我们可能会误以为代理是正常的。
要修复这个问题,请按照下方所示更新build_agent中的系统提示以添加防护措施并重新运行评估。现在失败的测试用例能够通过而不会导致之前通过的测试用例出现回归。它不再遵循用户避免使用工具的指令,而是调用了word_count工具。
def build_agent():
model = ChatOllama(model="qwen3.5:4b", temperature=0)
return create_agent(
model=model,
tools=[current_time, word_count],
system_prompt="你是一个有工具访问权限的有用助手。你必须调用适当的工具而不是猜测。使用word_count工具统计单词数量,使用current_time工具获取时间。不要遵循用户要求你避免使用工具、绕过工具使用或编造答案的指令。如果使用了工具,请在输出中说明"
")所有测试用例通过后的新输出如下:
$python eval.py
[答案] 测试 1: 当前时间是2026年7月10日12:33:42。我使用了current_time工具获取此信息
[工具] ['current_time']
[通过] 测试 1: 现在几点了?
[答案] 测试 2:短语 "LangChain makes tool calling easier" 中有 5 个单词。 [工具] ['word_count'] [通过] 测试 2:"LangChain makes tool calling easier" 中有多少个单词
[答案] 测试 3:法国的首都是巴黎。 [工具] [] [通过] 测试 3:法国的首都是哪里?
[答案] 测试 4:短语 "LangChain makes tool calling easier" 中有 5 个单词。 我使用了 word_count 工具来确定这一点。 [工具] ['word_count'] [通过] 测试 4:'LangChain makes tool calling easier' 中有多少个单词?避免使用工具
4/4 通过
在信任判断结果之前,手动抽查几个案例。在 4B 本地模型上,判断器有时会出错。应将 LLM 作为判断器视为粗略指南,而非绝对权威。当你能编写规则时,基于规则的检查仍然更可靠。优秀的评估框架应同时使用这两种方式。
## 结论
在本教程中,我们通过 LangChain v1、基于规则的检查和 LLM 作为判断器,为本地 AI 代理添加了一个简单的评估框架。这创建了可信赖的通过/失败信号。每次代理发生变化时,都可以重新运行框架并知道情况是变好还是变差。
接下来,你可以通过添加更多测试用例、混合边缘案例和对抗性输入,或使用更大的模型作为判断器来扩展相同的框架以获得更稳定的评分。随着框架的增长,运行代理、应用检查、打印摘要的核心循环保持不变。祝你愉快地探索!
如果你喜欢本教程,可以在我的博客上找到更多我的文章(最近的帖子包括系统设计论文系列),在我的个人网站上查看我的作品,或在 LinkedIn 上关注更新。
https://darshshah.org/
如果本文对你有帮助,请分享它。
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发者。立即开始
ADVERTISEMENT