一句话定义 :上下文工程不是"写几句提示词",而是把指令、知识、工具、记忆、状态和用户请求 按规则拼装成可执行、可评测、可复用 的输入,像写"接口契约 + 运行时编排"。
关键词:结构化、预算、证据、Schema、回退策略。
这篇文章适合谁
- 在做问答/RAG、智能客服、Copilot/Agent 的后端或全栈工程师。
- 觉得"prompt 可用但不稳""一改场景就崩"的同学。
- 想把 LLM 从 demo 推到生产,并且可观测、可回归的人。
你会收获:一套能落地的思路(白话解释)+ 可复制的最小示例(代码与模板)+ 小 Checklist(上线前自检)。
热身:为什么"只写 Prompt"不够
"把报销政策丢给模型,问:机票能报销吗? "------第一次看起来能答,第二次换个说法就开始"自信胡说"。根因通常有三点:
- 信息不全:没把制度原文、更新日期、边界条件一起给到。
- 无结构约束:输出自由度太高,长答案不稳定。
- 无证据与回退:答错时无法追溯,也没有"保底答复"。
上下文工程做的事 :把"要素与规则"装进一个有结构的输入,并且控成本、控风险。
白话版:到底什么是"上下文"
把模型看作函数:
ini
answer = LLM(context)
这个 context
不是一段话,而是一个拼装好的信息包:
ini
context = assemble(
instructions, # 系统/任务指令与风格、安全边界
knowledge, # 可靠来源的证据(文档片段/检索结果)
tools, # 可调用函数(参数契约/权限/超时)
memory, # 对话摘要、用户画像/历史
state, # 时间/位置/渠道/会话状态
query # 本轮用户请求
)
上面每一项都要预算 token 、控制顺序 、设置约束。这就是上下文工程的基本功。
10 分钟最小例子:做一个"退款客服"上下文
目标:让模型先查订单 再回答,答案里带证据与决策原因 ,失败时给出可解释回退 。
技术选型:任何主流 LLM 均可;示例用伪代码 + JSON。
1)系统指令(摘录)
diff
你是退款客服助手。必须遵守:
- 先决策"是否需要查订单",需要则调用工具,不得臆测。
- 回答使用简洁中文;给出"要点"和"证据来源"。
- 严禁超出政策权限的承诺;不确定时走回退模板。
2)输出 Schema(约束自由度)
json
{
"decision": "lookup|answer|fallback",
"reason": "string",
"tool_params": {"order_id":"string"},
"answer": "string",
"citations": [{"title":"string","section":"string"}]
}
3)工具定义(函数契约)
json
{
"name": "get_order_status",
"description": "查询订单的支付与发货状态",
"params": {"order_id": "string"},
"timeout_ms": 1200
}
4)知识片段(证据)
diff
《退款与退货政策 v3.2》 2024-11-01
- 已发货未签收:支持退款,需走退货流程;
- 已签收 > 7 天:仅质量问题;需凭证;
- 数码产品:拆封后非质量问题不支持无理由;
5)内存与状态(可选)
json
{
"user_profile": {"tier":"gold","history_refund_rate":0.0},
"now": "2025-08-08T09:30:00Z",
"channel": "web"
}
6)组装(伪代码)
scss
def assemble(query, user_state):
instructions = load("system_refund_cn")
schema = load("schema_refund_v1")
tool_spec = load("tool_get_order_status")
evidence = retrieve(query, topk=4) # 混合检索
evidence = rerank_dedup(evidence)[:2] # 去冗重排
budget = plan_budget({
"instructions":300, "schema":80, "tools":120,
"evidence":800, "memory":120, "query":80
})
return format_context(instructions, schema, tool_spec,
evidence, user_state.memory, query)
7)一次交互(模拟示例)
用户 :我的订单 8123 已发货,但想退款可以吗?
模型(第一步) :
json
{"decision":"lookup","reason":"需确认是否已签收","tool_params":{"order_id":"8123"}}
工具返回 :{ "order_id":"8123","paid":true,"shipped":true,"delivered":false }
模型(第二步) :
json
{
"decision":"answer",
"reason":"已发货未签收,可退款需退货流程;不涉及超过7天或质量条款",
"answer":"可以办理退款。请在包裹签收前或签收后7天内发起退货,快递寄回后原路退款。",
"citations":[{"title":"退款与退货政策 v3.2","section":"已发货未签收"}]
}
失败回退(示例) :
json
{
"decision":"fallback",
"reason":"工具超时",
"answer":"我这边暂时查不到订单状态。请稍后再试,或在个人中心查看订单 8123 的物流与签收信息。"
}
对比"只写 Prompt" :
- 过去:一段话让模型"猜"。
- 现在:先判断→再工具→有证据→结构化输出,稳定性显著提升。
如何把它落在你的项目里:四个阶段
S0:单轮可控输出(1 天)
- 写清系统指令;给 JSON Schema;准备 2--3 个反例 few-shot。
- 目标:答非所问显著下降;长输出结构完整。
S1:加证据(2--3 天)
- 用 BM25+向量混合检索;重排+去冗;把"片段原文+元数据"塞进上下文。
- 目标:答案可溯源,幻觉明显减少。
S2:加工具与状态(1 周)
- 把核心查询、下单、审批做成"最小接口",设定超时、幂等、权限。
- 目标:从"会说"到"会做" ;错误有回退路径。
S3:加记忆与评测(持续)
- 对话摘要→事件/实体库→长期画像;定期去重与过期。
- 建离线评测集(100--300 条)+ 在线 A/B(解决率、延迟、成本)。
- 目标:可回归、可观测、可持续优化。
工程细节与小技巧(真正好用的那类)
1)切分与索引
- 切分按"标题锚点+语义边界";保留"生效时间/版本号/条款号"。
- 为规章/FAQ 建"结构化字段索引"(主题、适用人群、例外场景)。
2)查询与重排
- 先"改写/扩展查询"(同义、上下位词、关键实体)再检索。
- Cross-Encoder 重排 + 片段投票,极大降低"跑题证据"。
3)压缩与位置编排
- "约束式摘要":保留数字、时间、名词;禁止改写结论。
- 靠近原则:最关键的证据放在 Schema 前或紧挨问题处。
4)输出与校验
- 让模型按 Schema 输出,再由程序二次校验(缺字段/类型不符→重试一次)。
- 把"风险词/承诺词"列入黑名单,出现则强制回退。
5)成本与延迟
- 预算每块 token,给出上限;过长则"先压缩后投喂"。
- 并发调用工具;对慢工具设超时 + 降级答案。
常见坑与修复
现象 | 根因 | 快速修复 |
---|---|---|
同一问题答法不一 | 输出自由度高 | 加 JSON Schema + few-shot 示例 |
胡编硬造 | 无证据 | 强制引用片段;答案必须带 citations |
文档很全但答错 | 检索错/重排弱 | 扩展查询;加 Cross-Encoder 重排;投票融合 |
工具乱调或死循环 | 无权限/幂等/超时 | 工具契约化;加超时与重试上限 |
老事实覆盖新规则 | 记忆缺失治理 | 记忆加 TTL;新规则置顶、老版本降权或过滤 |
长答案东拉西扯 | 无结构与位置编排 | 约束式摘要;证据靠近;分段投喂 |
可观测与评测(上线必备)
离线集 :query, expected, doc_ids
指标:
- 任务成功率(Exact/Soft)
- 证据覆盖率(回答引用是否命中正确文档)
- 延迟 P95、Token 成本
- 工具成功率、重试次数
- 结构完整度(Schema 缺字段率)
最小评测脚本(伪代码)
csharp
def evaluate(cases):
stats = []
for q, expected in cases:
ctx = assemble(q, user_state={})
out = call_llm(ctx)
ok = match(out["answer"], expected)
stats.append({"ok": ok, "latency_ms": out["latency"], "cost": out["tokens"]})
return aggregate(stats)
一个最小的上下文流水线
模板合集(拿走即用)
系统指令模板
diff
[角色] 你是{domain}助手,目标是{business_goal}。
[硬性要求]
- 先判断是否需要工具;需要则仅输出工具调用意图与参数;
- 回答必须符合 {style};禁止虚构;
- 不确定时走回退模板:"{fallback}"。
工具模板
json
{
"name": "{tool_name}",
"description": "{what_it_does}",
"params": { "id": "string" },
"timeout_ms": 1500,
"idempotent": true,
"auth": "service_jwt"
}
输出 Schema 模板
json
{
"decision": "lookup|answer|fallback",
"reason": "string",
"tool_params": {},
"answer": "string",
"citations": [{"title":"string","section":"string"}]
}
回退模板
我暂时无法获取必要信息(原因:{reason})。建议你:
1) {step1}
2) {step2}
FAQ(面试与评审常见问题)
- RAG 就是上下文工程吗?
不是。RAG 只覆盖"找知识"的一部分;上下文工程还包含指令、工具、记忆、状态、结构化输出、回退与评测。 - 要不要微调?
先把上下文工程做好,能解决 80% 的稳定性问题;剩余再考虑小规模微调或校准。 - 有了长上下文模型就不用管了?
不。窗口大≠有效信息密度高。重排、压缩、位置编排仍关键。 - 为什么强调 Schema?
因为输出自由度越小,稳定性越高;Schema 还能让后续系统直接消费。
结语
上下文工程的本质是工程化的输入治理 。你不是在"写几句好听的话",而是在定义契约、管理证据、安排动作、约束输出并可观测地改进。做到了这些,LLM 从"能聊"变成"能用"。