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

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

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

提示词工程问的是"这句话该怎么说"。

上下文工程问的是"该给模型看什么"。

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

一、从 Prompt Engineering 到 Context Engineering

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

一个真实的例子:

复制代码
❌ 提示词写得很好,但检索到的是 3 篇不相关的文档
   → 模型只能基于不相关内容回答,或者拒答

✅ 提示词写得很朴素,但检索到了精确的 2 段内容
   → 模型直接给出正确答案

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

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

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

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

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

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

python 复制代码
# ❌ 愿望式(模型会自由发挥)
SYSTEM = """你是一个专业的、友善的、乐于助人的技术助手,
请尽可能准确、详细地回答用户的问题。"""

# ✅ 边界式(模型知道该怎么做)
SYSTEM = """你是 {product} 的技术支持助手。

【必答】
- 产品的功能用法、常见问题
- 报错信息的排查步骤

【不答】
- 价格(引导到销售)
- 与其他产品的对比(说"不便评价")

【格式】
- 结论写在第一句
- 步骤用有序列表
- 代码用 ```包裹

【不确定时】
直接说"我不确定",不要猜。不要编造 API 名称或参数。
"""

关键差别:

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

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

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

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

描述模板:

python 复制代码
@tool
def search_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,不写在描述里

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

python 复制代码
def build_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 = []
    for m in dedupe(pinned + relevant):
        lines.append(f"- [{m.value['category']}] {m.value['content']}")

    return "【关于该用户的已知信息】\n" + "\n".join(lines) if lines else ""

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

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

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

python 复制代码
def order_docs_for_context(docs: list[Document]) -> list[Document]:
    """最相关的放开头和结尾,中等相关的放中间"""
    docs = sorted(docs, key=lambda d: -d.metadata["score"])
    if len(docs) <= 2:
        return docs

    # 第 1 个放最前,第 2 个放最后,其余放中间
    out = [docs[0]]
    out.extend(docs[2:])
    out.append(docs[1])
    return out

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

python 复制代码
def format_docs(docs: list[Document]) -> str:
    parts = []
    for i, d in enumerate(docs, 1):
        src = d.metadata.get("source", "未知")
        title = d.metadata.get("title", "")
        parts.append(f"[资料 {i}] 来源:{title or src}\n{d.page_content}")
    return "\n\n---\n\n".join(parts)

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

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

python 复制代码
def build_context(history, question, docs, token_budget=8000):
    parts = []

    # 关键事实(永不压缩)
    facts = extract_pinned_facts(history)
    if facts:
        parts.append("【已确认的事实】\n" + "\n".join(f"- {f}" for f in facts))

    # 较早的对话(摘要)
    if len(history) > 8:
        parts.append("【较早的对话摘要】\n" + summarize(history[:-8]))

    # 最近几轮(原文)
    recent = history[-8:]

    return {
        "facts": facts,
        "summary": ...,
        "recent_history": recent,
        "docs": docs,
        "question": question,
    }

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

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

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

复制代码
用户:"那它的价格呢"

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

① 查询改写(推荐)

python 复制代码
REWRITE_PROMPT = """根据对话历史,把用户的最新问题改写成独立完整的问句。

对话历史:
{history}

最新问题:{question}

只输出改写后的问题。如果问题本身已经完整,原样输出。"""

def rewrite(history, question):
    if is_standalone(question):
        return question
    return (rewrite_prompt | cheap_llm | StrOutputParser()).invoke(
        {"history": to_text(history[-4:]), "question": question}
    )
复制代码
"那它的价格呢" → "LangChain 的 Pro 套餐价格是多少"

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

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

九、装配顺序很重要

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

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

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

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

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

python 复制代码
HumanMessage(f"""<context>
{context_block}
</context>

{question}""")

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

十、一套可复用的装配器

python 复制代码
class ContextBuilder:
    def __init__(self, budget: Budget):
        self.budget = budget

    def build(self, user, question, history, docs) -> list[BaseMessage]:
        # 1. 改写查询(如果依赖上下文)
        q = rewrite(history, question)

        # 2. 检索(用改写后的问题)
        if docs is None:
            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),           # 原始问题,不是改写的
        ]

        return self.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")
        return trimmed

    def _context(self, docs, tokens):
        text = format_docs(docs)
        return f"<context>\n{truncate_by_paragraph(text, tokens * 3)}\n</context>"

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

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

上下文工程的效果可测:

① 检索质量

python 复制代码
def eval_retrieval(queries, ground_truth):
    hits = 0
    for q, gt in zip(queries, ground_truth):
        docs = retriever.invoke(rewrite(history, q))
        texts = [d.page_content for d in docs]
        if any(gt in t for t in texts):
            hits += 1
    return hits / len(queries)

② 上下文精度

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

python 复制代码
def context_precision(answer_with_citations, total_docs):
    return len(set(answer_with_citations)) / total_docs

③ Lost in the Middle 检查

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

python 复制代码
def test_middle_recall(docs):
    """把答案放在第 3 篇(中间),看模型能否答出"""
    docs = reorder_put_at_middle(docs, answer_idx=2)
    return ask(docs) == EXPECTED

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

十二、小结

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

六条实践:

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

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

后者更难,但它是可测的、可迭代的、能沉淀成基础设施的------这正是它在工程上更有价值的原因。

er_idx=2)

return ask(docs) == EXPECTED

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

## 十二、小结

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

复制代码
六条实践:
  1. 系统提示写"边界"(必答/不答清单),不写"愿望"

  2. 工具描述加"何时不用",控制数量在 8 个以内

  3. 长期记忆只注入高价值项,不全量

  4. 检索内容按相关性排序位置,加来源编号

  5. 历史三级处理:事实 / 摘要 / 原文

  6. 查询先改写,再检索和生成

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

    后者更难,但它是可测的、可迭代的、能沉淀成基础设施的------这正是它在工程上更有价值的原因。

相关推荐
lucas_AI1 小时前
Meta 掀桌了:号称「美国最强开源模型」的 Muse Spark Open 开放下载,816B MoE + 1M 上下文
人工智能·llm
方方洛2 小时前
ai-agent教程-09-MCP模型上下文协议
人工智能·llm·agent
一心同学2 小时前
Hermes架构拆解之Tool执行安全机制
安全·架构·agent·hermes
GoGeekBaird2 小时前
我做了一个给 Agent 用的电商开源项目:开店、上架、改版,跟 Agent 说一声就行
后端·github·agent
不好听6132 小时前
Neo4j:图数据库入门:节点、关系、标
agent
李溪白2 小时前
RAG 进阶:Agentic RAG 与多智能体协作(系列二·第1篇):高级工具设计
agent
杨超越luckly2 小时前
国庆V型、春节迁徙、五一升温:节假日数据里的中国旅游
agent·旅游·数据可视化·节假日·cpi·国定假日
熊猫钓鱼>_>2 小时前
当工具成为类:Softmax困境与连接器过载的算法根源
算法·ai·agent·工具·曲线·华为云码道·生态图鉴
云半S一2 小时前
Agent 学习总结一
经验分享·笔记·学习·agent