AI Engineer Notebooks 最近出现在 GitHub 与 Hacker News 的 AI 工程讨论里。它的定位很清楚:不用 LangChain、LlamaIndex 这类框架起步,而是用 raw API 写模型调用、结构化输出、工具调用、RAG、eval、agent loop、安全和推理服务。项目 README 还明确把 evals 称为主线,并且大部分内容可以通过免费的 Groq API 跑通。参考来源:GitHub 仓库、Hacker News 首页。
这类项目值得写成工坊,不是因为 notebook 本身多稀缺,而是因为它给了一个更健康的学习顺序。很多开发者做 AI 项目时先堆功能:接一个模型,接一个向量库,加一个网页,最后才问效果怎样。这个顺序在 demo 阶段很快,到了生产环境就会变成灾难。你无法回答换模型是否变好,无法解释 RAG 为什么漏召回,无法知道 agent 多跑三步到底值不值。
下面我们用它的思路搭一个最小项目骨架:一个支持知识库问答的小型 RAG agent,先写评测,再写检索和生成。代码可以在本地 Python 里跑,也可以搬到 notebook。
项目目录
先建一个很薄的目录结构。目标不是造框架,而是把“数据、系统、评测”分开。
ai-eval-lab/
data/
docs.jsonl
golden.jsonl
app/
rag.py
evals.py
run_eval.py
docs.jsonl 放知识库片段,golden.jsonl 放人工确认过的问题与期望答案。真实团队可以从客服工单、产品文档、内部 runbook 中抽样,但第一版不要太大,30 到 80 条高质量样本已经够发现问题。
示例黄金集如下:
{"id":"q1","question":"免费套餐是否支持导出审计日志?","must_contain":["不支持","专业版"],"category":"billing"}
{"id":"q2","question":"API key 泄露后应该怎么处理?","must_contain":["立即轮换","吊销旧 key"],"category":"security"}
注意 must_contain 不是完美评分器,但它稳定、便宜、可复现。早期不要一上来就把所有评测交给 LLM judge。先用规则抓住硬事实,再让 judge 处理语义一致性。
写一个朴素检索器
为了让代码可运行,这里先不用向量库,用 BM25 风格的词重叠做基线。这个基线很重要,因为它会告诉你“复杂系统是否真的值得”。
import json
import re
from pathlib import Path
def tokenize(text: str) -> set[str]:
return set(re.findall(r"[a-zA-Z0-9_\u4e00-\u9fff]+", text.lower()))
def load_jsonl(path: str) -> list[dict]:
return [json.loads(line) for line in Path(path).read_text().splitlines() if line.strip()]
def retrieve(question: str, docs: list[dict], top_k: int = 3) -> list[dict]:
q = tokenize(question)
scored = []
for doc in docs:
terms = tokenize(doc["text"])
score = len(q & terms) / max(1, len(q))
scored.append((score, doc))
return [doc for score, doc in sorted(scored, key=lambda x: x[0], reverse=True)[:top_k]]
很多团队不愿意写这种“土”的 baseline,直接上 embedding、reranker 和 graph RAG。问题是,没有 baseline,你不知道高级方案究竟提升了什么。AI Engineer Notebooks 强调 raw API 的原因也类似:先知道模型调用、工具 schema、重试、输出解析如何工作,再判断框架替你做的事是否值得。
生成回答前先保留证据
RAG 的核心不是把文档拼进 prompt,而是把证据链留住。即使你用最强模型,也要记录检索到了什么、用了哪个 prompt、输出是否覆盖关键事实。
def build_prompt(question: str, contexts: list[dict]) -> str:
evidence = "\n\n".join(
f"[{i+1}] {doc['title']}\n{doc['text']}"
for i, doc in enumerate(contexts)
)
return f"""你是产品支持助手。只根据证据回答问题。
证据:
{evidence}
问题:
{question}
要求:
1. 如果证据不足,直接说无法确认。
2. 回答必须简洁。
3. 涉及操作步骤时使用编号。"""
生产中这里会调用模型 API。为了让评测骨架先跑起来,可以先写一个伪生成器:返回 top 文档摘要,保证流程可测。
def fake_generate(question: str, contexts: list[dict]) -> str:
if not contexts:
return "无法确认。"
return contexts[0]["text"][:220]
等规则评测跑通后,再把 fake_generate 换成 OpenAI 兼容客户端。这样做的好处是,评测、数据加载、检索记录和报告逻辑不会依赖具体模型。
写回归评测
评测脚本应该输出每条样本的结果,而不只是一个平均分。平均分能看趋势,单条失败才能指导修复。
def evaluate_case(case: dict, answer: str) -> dict:
required = case.get("must_contain", [])
hits = [term for term in required if term in answer]
return {
"id": case["id"],
"category": case.get("category", "default"),
"score": len(hits) / max(1, len(required)),
"missing": [term for term in required if term not in hits],
"answer": answer,
}
def run_eval(docs: list[dict], golden: list[dict]) -> list[dict]:
rows = []
for case in golden:
contexts = retrieve(case["question"], docs)
answer = fake_generate(case["question"], contexts)
row = evaluate_case(case, answer)
row["sources"] = [doc["id"] for doc in contexts]
rows.append(row)
return rows
这就是“evals are the spine”的最小实现。后续你可以替换任意组件:换 embedding、加 reranker、改 prompt、换模型、加工具调用、让 agent 多走一步。只要评测输入不变,就能知道变化是否真的改善了任务。
加一个失败分析报告
不要只打印 pass 或 fail。你需要把失败聚类。
from collections import defaultdict
def report(rows: list[dict]) -> None:
by_category = defaultdict(list)
for row in rows:
by_category[row["category"]].append(row["score"])
total = sum(row["score"] for row in rows) / max(1, len(rows))
print(f"overall={total:.3f}")
for category, scores in by_category.items():
print(f"{category}={sum(scores) / len(scores):.3f}")
print("\nfailures:")
for row in rows:
if row["score"] < 1:
print(row["id"], row["missing"], row["sources"])
当失败集中在 security 类问题时,你该补安全文档或提高安全片段权重;当失败集中在 billing 类问题时,可能是价格页更新没入库;当所有类别都差,可能是 prompt 太松或检索器太弱。评测的目的不是证明系统厉害,而是给下一步修改排序。
从学习材料变成团队训练营
如果你要把 AI Engineer Notebooks 改造成内部训练营,不要让成员“看完 12 个 notebook”。更好的安排是每周一个可验收任务。
第一周,完成模型 API、结构化输出和成本保护,提交一个能重试、能解析 JSON、能记录 token 成本的脚本。第二周,完成 RAG baseline 和黄金集,提交检索命中率与答案覆盖率报告。第三周,写 agent loop,但必须和固定 pipeline 对比,说明什么时候 agent 多余。第四周,做安全和运维:注入测试、日志脱敏、fallback、超时、预算上限。第五周,做 serving 与推理性能,给出吞吐、延迟、缓存和并发的估算。
这条路线比单纯学习框架慢一点,但会省掉大量生产返工。真正的 AI Engineer 不是会调用一个 SDK,而是能解释系统为什么失败,能用评测证明修改有效,能在成本和质量之间做可审计的取舍。
结论
AI Engineer Notebooks 的核心启发是:应用型 AI 工程不应该从框架开始,而应该从可测任务开始。raw API、RAG、agent、LoRA、serving 都只是手段;黄金集、指标、失败分析和回归检查才是系统能长期迭代的骨架。
如果你今天要启动一个 AI 项目,先不要问“用哪个 agent 框架”。先问四个问题:任务样本在哪里,正确答案怎么判,失败能不能复现,改动能不能回归。能回答这四个问题,再接入任何框架都不晚。