☰
上下文工程:比提示词工程更重要的事
2026/10/17 13:48:37 网站建设 项目流程
个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • 上下文工程:比提示词工程更重要的事
    • 一、从 Prompt Engineering 到 Context Engineering
    • 二、上下文的六个组成部分
    • 三、① 系统提示:写"边界"而不是"愿望"
    • 四、② 工具定义:让模型"一眼选对"
    • 五、③ 长期记忆:注入"最小必要"信息
    • 六、④ 检索内容:位置和格式都有讲究
    • 七、⑤ 对话历史:分级而不是全留
    • 八、⑥ 当前输入:别只给"这一句"
    • 九、装配顺序很重要
    • 十、一套可复用的装配器
    • 十一、怎么验证上下文的质量
    • 十二、小结

上下文工程:比提示词工程更重要的事

提示词工程问的是"这句话该怎么说"。
上下文工程问的是"该给模型看什么"。

后者对效果的影响,往往比前者大一个数量级。

一、从 Prompt Engineering 到 Context Engineering

Prompt EngineeringContext Engineering
关注对象措辞信息
载体一句话系统提示 + 历史 + 检索 + 工具 + 示例
关键能力表达取舍
效果天花板中高

一个真实的例子:

❌ 提示词写得很好,但检索到的是 3 篇不相关的文档 → 模型只能基于不相关内容回答,或者拒答 ✅ 提示词写得很朴素,但检索到了精确的 2 段内容 → 模型直接给出正确答案

信息对了,措辞可以朴素;信息错了,措辞再精妙也没用。

二、上下文的六个组成部分

一次 LLM 调用的上下文,其实是六块拼起来的:

┌─────────────────────────────────────┐ │ ① 系统提示 角色、规则、约束 │ ├─────────────────────────────────────┤ │ ② 工具定义 schema 与描述 │ ├─────────────────────────────────────┤ │ ③ 长期记忆 用户偏好、历史事实 │ ├─────────────────────────────────────┤ │ ④ 检索内容 知识库片段 │ ├─────────────────────────────────────┤ │ ⑤ 对话历史 最近几轮 │ ├─────────────────────────────────────┤ │ ⑥ 当前输入 用户这句话 │ └─────────────────────────────────────┘

每一块都需要单独设计,而且要设计它们的装配顺序。

三、① 系统提示:写"边界"而不是"愿望"

# ❌ 愿望式(模型会自由发挥)SYSTEM="""你是一个专业的、友善的、乐于助人的技术助手, 请尽可能准确、详细地回答用户的问题。"""# ✅ 边界式(模型知道该怎么做)SYSTEM="""你是 {product} 的技术支持助手。 【必答】 - 产品的功能用法、常见问题 - 报错信息的排查步骤 【不答】 - 价格(引导到销售) - 与其他产品的对比(说"不便评价") 【格式】 - 结论写在第一句 - 步骤用有序列表 - 代码用 ```包裹 【不确定时】 直接说"我不确定",不要猜。不要编造 API 名称或参数。 """

关键差别:

愿望式边界式
“尽可能准确”“不确定就说不确定”
“友善专业”“结论写第一句”
“详细回答”给出具体的"必答/不答"清单

后者可验证——你能检查模型有没有违反。

四、② 工具定义:让模型"一眼选对"

工具定义的上下文成本很高(每个 100~300 token),而且直接影响选择准确率。

描述模板:

@tooldefsearch_orders(user_id:str,status:Literal["pending","paid","refunded"]|None=None,days:int=30,)->str:"""查询用户的订单列表。 何时使用:用户问"我买了什么"、"我的订单"、"订单状态"。 何时不用:查单个订单详情用 get_order_detail(更快)。 Args: user_id: 用户 ID,格式 U + 8 位数字 status: 订单状态。不传则返回所有状态 days: 查询最近多少天。默认 30 天,最大 365 """

四段结构:能力 → 何时用 → 何时不用 → 参数说明。

"何时不用"这一段经常被忽略,但它最能减少误选。

精简原则:

每个工具的描述控制在 3 行以内 工具总数控制在 8 个以内(超过就拆分 Agent) 枚举值写进 Literal,不写在描述里

五、③ 长期记忆:注入"最小必要"信息

defbuild_memory_block(user_id:str,query:str)->str:"""只注入两类记忆: 1. 高重要度的偏好/约束(永久) 2. 与当前问题相关的(按需) """pinned=store.search(namespace=("memories",user_id),filter={"importance":{"$gte":4},"category":{"$in":["preference","constraint"]}},limit=5,)relevant=store.search(namespace=("memories",user_id),query=query,limit=3)lines=[]formindedupe(pinned+relevant):lines.append(f"- [{m.value['category']}]{m.value['content']}")return"【关于该用户的已知信息】\n"+"\n".join(lines)iflineselse""

常见的错误是把所有记忆都注入——50 条记忆占了 3000 token,其中 45 条和当前问题无关。

六、④ 检索内容:位置和格式都有讲究

位置:研究显示,模型对开头和结尾的内容注意力最强(Lost in the Middle)。

deforder_docs_for_context(docs:list[Document])->list[Document]:"""最相关的放开头和结尾,中等相关的放中间"""docs=sorted(docs,key=lambdad:-d.metadata["score"])iflen(docs)<=2:returndocs# 第 1 个放最前,第 2 个放最后,其余放中间out=[docs[0]]out.extend(docs[2:])out.append(docs[1])returnout

格式:给每段内容加来源标记,模型更容易引用,也更容易识别"这是资料"。

defformat_docs(docs:list[Document])->str:parts=[]fori,dinenumerate(docs,1):src=d.metadata.get("source","未知")title=d.metadata.get("title","")parts.append(f"[资料{i}] 来源:{titleorsrc}\n{d.page_content}")return"\n\n---\n\n".join(parts)

加上"资料 N"编号,回答时模型会写"根据资料 2",用户能核对。

七、⑤ 对话历史:分级而不是全留

defbuild_context(history,question,docs,token_budget=8000):parts=[]# 关键事实(永不压缩)facts=extract_pinned_facts(history)iffacts:parts.append("【已确认的事实】\n"+"\n".join(f"-{f}"forfinfacts))# 较早的对话(摘要)iflen(history)>8:parts.append("【较早的对话摘要】\n"+summarize(history[:-8]))# 最近几轮(原文)recent=history[-8:]return{"facts":facts,"summary":...,"recent_history":recent,"docs":docs,"question":question,}

三级结构:事实(结构化)→ 摘要(压缩)→ 原文(保真)。

八、⑥ 当前输入:别只给"这一句"

用户的这句话常常省略了上下文:

用户:"那它的价格呢"

"它"是什么?得从历史里解析。两种做法:

① 查询改写(推荐)

REWRITE_PROMPT="""根据对话历史,把用户的最新问题改写成独立完整的问句。 对话历史: {history} 最新问题:{question} 只输出改写后的问题。如果问题本身已经完整,原样输出。"""defrewrite(history,question):ifis_standalone(question):returnquestionreturn(rewrite_prompt|cheap_llm|StrOutputParser()).invoke({"history":to_text(history[-4:]),"question":question})
"那它的价格呢" → "LangChain 的 Pro 套餐价格是多少"

改写后的问题同时用于检索和生成——这能显著提升检索命中率。

② 把历史一起给模型(次优,但简单)

九、装配顺序很重要

同样的内容,顺序不同效果不同:

messages=[SystemMessage(SYSTEM),# 1. 身份和规则SystemMessage(memory_block),# 2. 用户长期记忆SystemMessage(facts_block),# 3. 已确认事实*recent_history,# 4. 最近对话HumanMessage(context_block),# 5. 检索内容HumanMessage(question),# 6. 当前问题]

为什么检索内容放在最后?

  • 离问题最近,注意力最强;
  • 用户消息位置的内容被当作"当前关注的信息";
  • 系统消息位置的内容容易被当作"背景设定"。

另一种有效布局是把检索内容用<context>包起来放在用户消息里:

HumanMessage(f"""<context>{context_block}</context>{question}""")

关键:给内容打标签,让模型能区分"资料"和"问题"。

十、一套可复用的装配器

classContextBuilder:def__init__(self,budget:Budget):self.budget=budgetdefbuild(self,user,question,history,docs)->list[BaseMessage]:# 1. 改写查询(如果依赖上下文)q=rewrite(history,question)# 2. 检索(用改写后的问题)ifdocsisNone:docs=retriever.invoke(q)docs=dedupe_docs(docs)[:self.budget.max_docs]# 3. 分类预算alloc=self.budget.allocate({"history":0.4,"retrieval":0.6})# 4. 组装各部分msgs=[SystemMessage(SYSTEM),*self._memory(user,q),*self._facts(history),*self._recent(history,alloc["history"]),HumanMessage(self._context(docs,alloc["retrieval"])),HumanMessage(question),# 原始问题,不是改写的]returnself.budget.fit(msgs)def_recent(self,history,tokens):trimmed=trim_messages(history,max_tokens=tokens,token_counter=count_tokens,strategy="last",include_system=False,start_on="human")returntrimmeddef_context(self,docs,tokens):text=format_docs(docs)returnf"<context>\n{truncate_by_paragraph(text,tokens*3)}\n</context>"

注意最后一行:HumanMessage(question)用的是原始问题,不是改写后的——因为用户看到的是他自己的话,回答也应该直接回应他的问题。

十一、怎么验证上下文的质量

上下文工程的效果可测:

① 检索质量

defeval_retrieval(queries,ground_truth):hits=0forq,gtinzip(queries,ground_truth):docs=retriever.invoke(rewrite(history,q))texts=[d.page_contentfordindocs]ifany(gtintfortintexts):hits+=1returnhits/len(queries)

② 上下文精度

检索 5 篇,只有 2 篇被引用 → 精度 40%,说明多塞了 3 篇垃圾。

defcontext_precision(answer_with_citations,total_docs):returnlen(set(answer_with_citations))/total_docs

③ Lost in the Middle 检查

把关键信息放在中间位置,看模型能不能找到:

deftest_middle_recall(docs):"""把答案放在第 3 篇(中间),看模型能否答出"""docs=reorder_put_at_middle(docs,answer_idx=2)returnask(docs)==EXPECTED

这三项指标,是上下文工程效果的量化依据。

十二、小结

上下文 = 系统提示 + 工具 + 长期记忆 + 检索 + 历史 + 当前问题

六条实践:

1. 系统提示写"边界"(必答/不答清单),不写"愿望" 2. 工具描述加"何时不用",控制数量在 8 个以内 3. 长期记忆只注入高价值项,不全量 4. 检索内容按相关性排序位置,加来源编号 5. 历史三级处理:事实 / 摘要 / 原文 6. 查询先改写,再检索和生成

一句话总结:提示词工程的产出是一段文字,上下文工程的产出一套信息装配流程。

后者更难,但它是可测的、可迭代的、能沉淀成基础设施的——这正是它在工程上更有价值的原因。
er_idx=2)
return ask(docs) == EXPECTED

**这三项指标,是上下文工程效果的量化依据。** ## 十二、小结

上下文 = 系统提示 + 工具 + 长期记忆 + 检索 + 历史 + 当前问题

六条实践:
  1. 系统提示写"边界"(必答/不答清单),不写"愿望"
  2. 工具描述加"何时不用",控制数量在 8 个以内
  3. 长期记忆只注入高价值项,不全量
  4. 检索内容按相关性排序位置,加来源编号
  5. 历史三级处理:事实 / 摘要 / 原文
  6. 查询先改写,再检索和生成
**一句话总结:提示词工程的产出是一段文字,上下文工程的产出一套信息装配流程。** 后者更难,但它是可测的、可迭代的、能沉淀成基础设施的——**这正是它在工程上更有价值的原因。**

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询