五条编出来的知识,和一个判不对分的判分器
对应 7.2 · 01_RAG_agent.py
import asyncio
from agents import (Agent, Runner, function_tool,
set_default_openai_api, set_tracing_disabled)
from dotenv import load_dotenv
# 从 .env 读取 OPENAI_BASE_URL、OPENAI_API_KEY、OPENAI_DEFAULT_MODEL
load_dotenv()
# 下面两行仓库原文没有,是为了接第三方模型加的
set_default_openai_api("chat_completions")
set_tracing_disabled(True)
# 一个极简的内存知识库
# 这五条是作者编的,现实中不存在 —— 保持英文,因为下面的检索是英文子串匹配
_special_knowledge_db = [
"Nebula Forge engine spins antimatter rings for gravity control.",
"Solaris Glacier absorbs heat, releasing luminescent icefire at dusk.",
"Quantum Bark trees emit entangled photons guiding nocturnal insect migrations.",
"Aether Silk fabric self-weaves repairs within fourteen-millisecond microtears.",
"Chrono Coral reefs rewind water currents three minutes every solstice.",
]
# 针对这些特殊知识的基准题
_benchmarks = [
{"q": "Nebula Forge engine spins what?", "a": "antimatter", "wrong": "fusion"},
{
"q": "Solaris Glacier releases luminescent what?",
"a": "icefire",
"wrong": "lava",
},
{"q": "Quantum Bark trees emit what?", "a": "photons", "wrong": "spores"},
{"q": "Aether Silk fabric repairs what?", "a": "microtears", "wrong": "threads"},
{"q": "Chrono Coral reefs rewind what?", "a": "currents", "wrong": "tides"},
]
@function_tool
def search_knowledge(query: str) -> dict:
"""在知识库里检索相关事实。"""
matches = [doc for doc in _special_knowledge_db if query.lower() in doc.lower()]
print(f"'{query}' 命中 {len(matches)} 条")
return {"status": "ok", "context": "\n".join(matches)}
agent = Agent(
name="RAG Agent",
instructions="""
你是一个检索增强的知识智能体。
""",
tools=[search_knowledge],
)
for benchmark in _benchmarks:
question = benchmark["q"]
answer = benchmark["a"]
wrong_answer = benchmark["wrong"]
result = asyncio.run(Runner.run(agent, input=question)).final_output.strip()
print("" + "=" * 40)
print(f"问题: {question}")
print(f"回答: {result}")
# ↓ 这一行就是坏掉的判分器 —— 见下方说明
if result.lower() == answer.lower():
print(f"正确 -> {answer}")
else:
print(f"错误 -> 期望: {answer}")
if wrong_answer:
print(f"错误答案应该是: {wrong_answer}")
Nebula Forge、Quantum Bark、Chrono Coral——这些东西现实中根本不存在, 是作者编的。为什么要编?因为模型的训练数据里绝不可能有它们。
所以只要智能体答得出「Nebula Forge 转的是反物质环」,就只能是它真的去查了知识库。 换成真实知识(比如「地球绕太阳转」),模型直接背出来也能答对, 你根本分不清它是查了还是猜的。这一招是本章最值得偷的招—— 第六章用整个《回到未来》剧本干的也是同一件事。
result 是智能体的完整回答,answer 是
"antimatter" 一个词。然后代码做的是
result.lower() == answer.lower()——精确相等。我实测了几种可能的回答:
判对 ✓ 回答: 'antimatter'
判错 ✗ 回答: 'The Nebula Forge engine spins antimatter rings.'
判错 ✗ 回答: 'Antimatter rings.'
判错 ✗ 回答: 'The Nebula Forge engine spins antimatter rings for gravity control.'
只有智能体恰好只吐「antimatter」这一个孤零零的单词才算对。
而真实模型几乎总会说一句完整的话——于是全部判错。
01、02、03 三个文件都是这个判分器。
这正是本章的出发点:判分器比被判的东西还难写。
用 == 判自然语言,和第四章拿 in 当护栏、
第五章拿 in 筛分支,是同一个病:
用字符串操作去理解语言。
search_knowledge 干的是 query.lower() in doc.lower()——
把整个查询当子串去匹配整条知识。所以问「Nebula Forge engine spins what?」
会命中 0 条,因为没有哪条知识包含这一整个问句。
它只在模型刚好用单个关键词去查时才有用——这就是为什么 03
把工具改名成 search_knowledge_by_keyword,
并在 docstring 里强调「single keyword」。改名不是美化,是在教模型怎么用它。
(第六章那个 Chroma 才是真语义检索。这里是刻意简化。)