这不是一份按名词罗列的学习清单,而是一条可以真正交付项目的路线。我们以“内部制度问答助手”为贯穿案例:系统只能使用获授权的制度文档回答,答案必须带来源;没有证据时拒答。你将依次完成需求定义、模型基线、RAG、评测、工具调用和生产验收,并知道何时不该使用微调或 Agent。
1. 最终要交付什么
完成后应得到一个可在本机或测试服务器运行的最小系统,而不是一组 Notebook。它包含:固定版本的模型接口、文档解析与向量索引、带引用的问答 API、60 条以上评测样本、权限测试、性能基线和回滚说明。
先写清边界:第一版只处理三份已批准的 PDF/Markdown 制度;不替员工做劳动关系决定;不访问用户没有权限的资料;不使用模型自身记忆补充公司政策。范围越明确,后续选型越容易。
2. 准备工程目录和运行环境
建议使用 Python 3.11、Docker、Git 和一个 OpenAI 兼容模型接口。模型可以是本地 vLLM,也可以是公司批准的服务。不要把 API Key、原始内部文档或用户数据提交到仓库。
mkdir -p ai-assistant/{app,configs,data/raw,data/processed,eval/results,scripts,tests}
cd ai-assistant
python3 -m venv .venv
. .venv/bin/activate
pip install httpx pydantic qdrant-client sentence-transformers pytest
pip freeze > requirements-lock.txt
git init检查:python --version 应显示 3.11.x;python -c "import qdrant_client" 不报错。若必须离线部署,先在联网构建机下载 wheel 和模型,再通过受控制品库转移,不要在生产机临时访问公共网络。
3. 把业务需求转换为评测数据
不要以“回答看起来不错”验收。建立 JSONL 数据集,每条包含问题、必须覆盖的要点、允许引用的证据、是否应拒答和风险等级。至少包括四类样本:正常问题、资料中无答案、问题带错误前提、提示注入或越权请求。
{"id":"leave-001","question":"试用期员工能申请年假吗?","must_include":["适用条件","审批流程"],"evidence_ids":["policy-leave-v3#p7"],"should_abstain":false,"risk":"medium"}
{"id":"salary-001","question":"公司承诺明年统一涨薪吗?","must_include":[],"evidence_ids":[],"should_abstain":true,"risk":"high"}参考答案由熟悉制度的人确认。训练、调参和最终测试要分开;同一文档的近似问题不要跨集合,否则分数会虚高。
4. 建立不带 RAG 的模型基线
基线用于了解模型原始能力,并为后续改动提供对照。固定模型版本、系统提示、temperature、最大输出和评测集哈希。逐条保存原始响应、HTTP 状态、耗时、token 使用和 finish_reason。
curl -fsS http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model":"local-chat",
"temperature":0,
"max_tokens":256,
"messages":[
{"role":"system","content":"只回答已知事实;未知时明确说明不知道。"},
{"role":"user","content":"试用期员工能申请年假吗?"}
]
}' | tee eval/results/baseline-smoke.json | jq .预期:HTTP 200,JSON 中有 choices、usage 和 finish_reason。若返回 404,核对服务端的 served-model-name;若 finish_reason 为 length,先检查输入与输出预算,不能把截断结果当质量失败。
5. 选择模型:先过硬门槛,再比较得分
硬门槛包括许可证是否允许当前用途、数据是否可离开目标区域、中文与结构化输出能力、上下文长度、硬件能否承载。未通过硬门槛的模型不进入性能比较。
| 维度 | 验证方式 | 需要保存的证据 |
|---|---|---|
| 任务质量 | 同一测试集逐样本评分 | 错误类型与样本 ID |
| 稳定性 | 相同输入重复执行 | 格式失败率与分数方差 |
| 性能 | 真实长度分布压测 | TTFT、TPOT、P95、错误率 |
| 成本 | 统计输入/输出 token 与 GPU 时间 | 单位成功任务成本 |
| 治理 | 许可证、数据路径、审计检查 | 审批记录和已知限制 |
不要以公开排行榜替代业务评测,也不要因为模型参数量大就默认更好。
6. 用最小 RAG 解决“资料缺失”
当主要错误来自公司资料不在模型权重中时,先做 RAG。解析一份文档,按标题和段落切分;每块保存 document_id、version、page、section、tenant_id、acl。查询时先在检索层执行权限过滤,再把 top-k 证据编号为 E1、E2 交给模型。
<evidence id="E1" source="policy-leave-v3" page="7">
试用期员工满足连续工作期限后,可按审批流程申请年假……
</evidence>
回答规则:
1. 只使用 evidence;
2. 每个事实句附 [E1] 形式引用;
3. 证据不足时说明缺少什么资料。故障定位:正确证据没有进入 top-k,检查解析、切分、embedding、query 和权限;证据进入上下文但答案仍错误,才检查提示词和生成。不要用更长提示词掩盖检索问题。
7. 何时用提示词、RAG、微调或 Agent
| 症状 | 首选方案 | 原因 |
|---|---|---|
| 资料频繁变化 | RAG | 可更新、可引用、可删除 |
| 输出 JSON 偶尔不合法 | 结构化输出+Schema 校验 | 先用确定性约束解决 |
| 固定任务或风格长期不稳 | 高质量示例,必要时 LoRA | 学习稳定行为而非动态知识 |
| 需要查询实时系统 | 只读工具调用 | 事实来自目标系统而非模型记忆 |
| 需要执行写操作 | Agent+审批+幂等 | 把权限和副作用控制放在模型外 |
每引入一个组件都做消融:纯模型、模型+提示词、模型+检索、模型+检索+重排。新增组件没有带来可重复收益,就不应保留。
8. 把评测脚本变成发布门禁
import json, time, httpx
def run_case(case):
payload = {"model":"local-chat","temperature":0,"max_tokens":320,
"messages":[{"role":"user","content":case["question"]}]}
started = time.perf_counter()
r = httpx.post("http://127.0.0.1:8000/v1/chat/completions", json=payload, timeout=60)
return {"id":case["id"],"status":r.status_code,
"latency_ms":round((time.perf_counter()-started)*1000),
"response":r.json()}
with open("eval/results/run.jsonl", "w", encoding="utf-8") as out:
for line in open("eval/cases.jsonl", encoding="utf-8"):
out.write(json.dumps(run_case(json.loads(line)), ensure_ascii=False)+"\n")先校验 HTTP、JSON 和引用,再做人工或受控模型评分。总体平均分之外,单独报告高风险样本、无答案拒答、越权与注入。发布前保存模型、提示、索引、数据集和代码版本。
9. 十二周学习与交付顺序
- 第 1–2 周:Python、HTTP、JSON、Git、Docker;能独立请求模型并保存结果。
- 第 3–4 周:模型模板、token、解码参数与基线评测。
- 第 5–7 周:解析、切分、向量库、权限、混合检索、引用。
- 第 8–9 周:根据错误分析决定是否 LoRA;使用独立测试集验收。
- 第 10 周:只读工具调用、Schema、权限与幂等。
- 第 11–12 周:压测、监控、红队、灰度、降级和回滚。
每阶段必须产出可运行制品、评测结果和已知限制。“看完教程”不算完成。
10. 总结
AI 工程的主线不是追逐框架,而是把每个决定变成可验证实验:先定义任务和风险,建立简单基线,再用错误类型选择 RAG、微调或 Agent。最终系统必须能说明答案来自哪里、谁有权限、版本是什么、失败时如何拒答和回滚。