更新于

LLM 上下文工程方法论


“提示工程”这个词已经被用滥了。更准确地描述我们在做的事情,是上下文工程(Context Engineering)——系统性地决定在每次 LLM 调用时,把什么信息放入上下文窗口,以什么顺序、什么密度呈现,最终影响模型的输出质量与成本。

模型越来越强,但上下文管理永远是约束:窗口有限、Token 有成本、信息有噪音。本文提供一套可落地的方法论。

一、为什么说”上下文工程”而非”提示工程”

提示工程(Prompt Engineering)通常指设计单次请求的文本措辞。上下文工程的范围更广:

  • 什么进窗口:系统提示、历史对话、检索文档、工具结果、用户画像……
  • 多少进窗口:截断策略、摘要压缩、去重过滤
  • 如何排列:信息顺序对注意力分布有实质影响
  • 动态管理:多轮对话中如何随任务进展更新上下文

对于简单的单次问答,提示工程足够。但对于 Agent、多轮对话、长文档处理等场景,不做上下文工程就是在浪费模型能力、烧冗余成本。

二、上下文窗口的物理约束

理解限制是设计的前提:

模型输入窗口(约估)输出上限备注
claude-sonnet-4-6200K tokens64K tokens支持 Prompt Caching
claude-haiku-4-5200K tokens8K tokens低延迟场景
GPT-4o128K tokens16K tokens
Gemini 1.5 Pro1M tokens8K tokens超长但成本高

200K tokens 听起来很大,实际上:

  • 1 个英文单词 ≈ 1.3 tokens
  • 1 个中文字符 ≈ 1.5–2 tokens
  • 一本普通小说 ≈ 100K–150K tokens
  • 10 万行代码仓库 ≈ 200K–500K tokens

所以即便是 200K 窗口,稍大一点的代码库或多轮长对话也会触顶。

三、信息优先级框架

在构建上下文时,先做信息分级:

P0(必须包含)

  • 当前任务的核心指令
  • 与当前问题直接相关的参考材料
  • 影响输出格式/约束的规则

P1(高价值,尽量包含)

  • 近期几轮关键对话
  • 用户/业务的个性化背景
  • 上一步工具调用的结果

P2(低优先级,按空间决定)

  • 早期对话历史(已无直接关联)
  • 通用知识(模型本身已有)
  • 重复出现的冗余信息

规则:P0 永远保留;P1 按质量截取;P2 先压缩再裁剪。

四、历史对话管理的三种策略

多轮对话是上下文膨胀的主要来源。三种处理策略各有适用场景:

策略 A:滑动窗口截断

保留最近 N 轮,直接丢弃更早的对话。

def sliding_window(messages: list[dict], max_turns: int = 10) -> list[dict]:
    # 保留 system message + 最近 max_turns 轮
    system = [m for m in messages if m["role"] == "system"]
    conv = [m for m in messages if m["role"] != "system"]
    return system + conv[-max_turns * 2:]  # 每轮 = user + assistant

适用:任务无需长期记忆,对话较独立(如客服问答)。

策略 B:递进摘要

每隔 N 轮,将历史对话压缩成一段摘要,用摘要替换原始消息。

def summarize_history(client, messages: list[dict]) -> str:
    history_text = "\n".join(
        f"{m['role']}: {m['content']}" for m in messages
    )
    resp = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": f"请将以下对话历史压缩为 200 字以内的摘要,保留关键事实和决策:\n{history_text}"
        }]
    )
    return resp.content[0].text

适用:需要保留长期上下文但对精确引用要求不高的场景。

策略 C:结构化记忆提取

不保留原始对话,而是从对话中提取结构化事实存入数据库,需要时检索注入。

# 每轮对话后提取实体/事实
memory_prompt = """
从以下对话中提取关键事实,以 JSON 返回:
{
  "user_preferences": [...],
  "decided_facts": [...],
  "pending_questions": [...]
}
对话:{conversation}
"""

适用:长期个性化 Agent、需要持久记忆的助手产品。

五、RAG 上下文整合的常见陷阱

RAG(检索增强生成)本质上是往上下文里注入检索结果。整合质量直接决定效果:

陷阱 1:检索结果堆砌

把 Top-10 检索结果全放进去,模型会被低质量片段干扰。

建议:用相关性阈值过滤(如余弦相似度 > 0.75),或用 reranker 精选 Top-3。

陷阱 2:来源信息缺失

模型不知道文档来自哪里,无法评估可信度。

建议:每段检索结果加元信息标注:

[来源:产品文档 v2.3,第 4 章,更新于 2026-01]
检索内容:...

陷阱 3:上下文位置效应

研究表明,LLM 对窗口中间的内容注意力最弱(“Lost in the Middle”现象)。

建议:将最重要的检索片段放在系统提示紧后方,或用户消息之前,而不是夹在中间。

陷阱 4:检索结果与问题错位

用户问 A,检索结果回答 B,但模型还是”用”了这些结果。

建议:在检索结果前加一句上下文锚定:

以下是与用户问题相关的参考材料(若不相关请忽略):

六、系统提示的结构化设计

系统提示是每次调用必然占据的上下文空间,值得精心设计:

# 角色定义
你是 [产品名] 的技术支持助手,负责解答用户关于 API 集成的问题。

# 行为准则
- 优先引用官方文档,不要凭空捏造 API 参数
- 代码示例使用 Python,除非用户明确指定其他语言
- 遇到超出范围的问题,引导用户联系人工客服

# 输出格式
- 技术问题:先结论,再步骤,最后注意事项
- 非技术问题:简洁回答,不超过 3 句话
- 代码块始终标注语言类型

# 当前上下文
用户级别:{user_tier}
当前使用的 SDK 版本:{sdk_version}

结构化系统提示的好处:模型更容易”找到”相关规则,且便于程序动态插值。

七、Token 密度优化

同样的信息量,Token 数可以差很多。提高 Token 密度的技巧:

用列表替代长句

❌ 低密度:

请注意,在发送请求时,你需要确保请求头中包含正确的认证信息,同时请求体需要符合 JSON 格式规范,另外要注意设置合适的超时时间以防止请求挂起。

✅ 高密度:

请求要求:① 请求头含 Authorization ② Body 为合法 JSON ③ 设置 timeout(建议 30s)

删除装饰性语言

“非常感谢您的提问,这是一个很好的问题,让我来为您详细解释……” → 直接给答案。

用代码替代文字描述

解释一个算法逻辑,20 行代码比 200 字描述更紧凑且准确。

八、Agent 场景的上下文设计

多步骤 Agent 任务中,上下文工程更为关键:

def build_agent_context(
    task: str,
    completed_steps: list[dict],
    available_tools: list[str],
    constraints: dict
) -> list[dict]:
    """为 Agent 单步调用构建精简上下文"""
    
    # 压缩历史步骤为简洁摘要
    steps_summary = "\n".join(
        f"步骤{i+1}{s['action']}{s['result'][:100]}"
        for i, s in enumerate(completed_steps[-5:])  # 只保留最近 5 步
    )
    
    system_content = f"""你是任务执行 Agent。

当前任务:{task}

已完成步骤:
{steps_summary}

可用工具:{', '.join(available_tools)}

约束条件:
- 最大步骤数:{constraints.get('max_steps', 20)}
- 禁止操作:{constraints.get('forbidden', '无')}

请决定下一步行动。"""
    
    return [{"role": "system", "content": system_content}]

核心原则:每步调用只告诉模型它当前需要知道的,历史步骤压缩为摘要,避免原始工具输出堆积。

九、相关阅读

上下文工程做好了,每次调用都能用更少的 Token 获得更好的结果,YoTradeApi 提供稳定的 Claude 中转接口,让你专注于工程而非网络问题。