LLM上下文工程實戰:Prompt組裝、Token預算與RAG上下文設計
摘要
- Context Engineering(上下文工程)是2026年LLM應用落地的核心能力,比Prompt Engineering覆蓋面更廣、更貼近生產
- Token預算是硬約束:系統提示詞、工具定義、RAG檢索、對話歷史必須分層分配,否則要么超窗截斷、要么成本失控
- Prompt組裝不是字串拼接,而是有優先級的上下文管道(Context Pipeline),每一層都有明確的預算和淘汰策略
- RAG上下文注入的3大坑:檢索噪聲淹沒關鍵資訊、Chunk邊界切斷語義、重複內容浪費Token
- 本文提供從原理到Python實作的完整方案,含Token預算計算器與生產級Context Manager
目錄
- 為什麼Prompt Engineering不夠用了
- Context Engineering核心概念
- Token預算:上下文視窗的「財務規劃」
- Prompt組裝流水線設計
- RAG上下文注入最佳實踐
- 上下文壓縮與淘汰策略
- Python生產級Context Manager實作
- 面試高頻考點與避坑指南
- 總結與延伸閱讀
為什麼Prompt Engineering不夠用了
2024年大家還在討論「怎麼寫好Prompt」,到了2026年,企業落地LLM應用時真正卡脖子的不是「一句話怎麼寫」,而是整個上下文怎麼組織。
一個真實的生產故障
某電商客服AI上線第一週,用戶投訴率飆升。排查發現:
| 現象 | 根因 | 影響 |
|---|---|---|
| AI回答與訂單無關 | RAG檢索了10條歷史工單,淹沒了當前訂單上下文 | 答非所問 |
| 多輪對話後響應變慢 | 對話歷史無限累積,Token超窗後觸發截斷 | 丟失用戶意圖 |
| 月API帳單超預算3倍 | 系統提示詞重複發送,未利用Prompt Caching | 成本失控 |
這三個問題,沒有一個靠「優化Prompt措辭」能解決。它們都屬於Context Engineering的範疇。
Prompt Engineering vs Context Engineering
| 維度 | Prompt Engineering | Context Engineering |
|---|---|---|
| 關注點 | 單條提示詞的措辭和格式 | 整個上下文的組織、預算和生命週期 |
| 適用範圍 | 簡單問答、一次性任務 | 多輪對話、RAG、Agent、工具呼叫 |
| 核心技能 | 角色設定、Few-shot、CoT | Token預算、上下文分層、壓縮淘汰 |
| 生產複雜度 | 低 | 高(涉及快取、檢索、狀態管理) |
| 2026面試權重 | 基礎題 | 高頻架構題 |
簡單說:Prompt Engineering管「說什麼」,Context Engineering管「給模型看什麼」。
Context Engineering核心概念
上下文的4個層次
把發給LLM的完整輸入想像成一個「資訊三明治」:
┌─────────────────────────────────────────────┐
│ Layer 1: 系統層 (System Context) │
│ 角色定義、行為約束、輸出格式、安全策略 │
├─────────────────────────────────────────────┤
│ Layer 2: 工具層 (Tool Context) │
│ Function Calling定義、MCP工具描述、Schema │
├─────────────────────────────────────────────┤
│ Layer 3: 知識層 (Knowledge Context) │
│ RAG檢索結果、知識圖譜、業務規則文件 │
├─────────────────────────────────────────────┤
│ Layer 4: 對話層 (Conversation Context) │
│ 多輪歷史、用戶當前輸入、中間推理步驟 │
└─────────────────────────────────────────────┘
每一層都有獨立的Token預算,層與層之間還有優先級關係。當總Token接近視窗上限時,從低優先級層開始淘汰。
上下文的生命週期
Context不是一次性組裝的,它在一次完整互動中會經歷多個階段:
- 組裝(Assembly):按優先級合併各層上下文
- 驗證(Validation):檢查Token總量、格式合規、敏感資訊過濾
- 發送(Dispatch):呼叫LLM API,利用Prompt Caching減少重複Token
- 更新(Update):將模型響應追加到對話層,觸發下一輪組裝
- 壓縮(Compaction):對話過長時,摘要或淘汰低價值歷史
Token預算:上下文視窗的「財務規劃」
為什麼要做Token預算
假設你用的是128K上下文視窗的模型,看起來很大,但生產環境中:
- 系統提示詞 + 工具定義:通常佔 2K-8K Token
- RAG檢索10條文件:每條500 Token = 5K Token
- 50輪對話歷史:每輪200 Token = 10K Token
- 模型輸出預留:4K Token
合計已經 21K-27K Token。如果業務複雜(多工具Agent、長文件RAG),輕鬆突破50K。沒有預算管理,就是在裸奔。
推薦預算分配表(128K視窗)
| 層級 | 預算比例 | Token上限 | 淘汰策略 |
|---|---|---|---|
| 系統層 | 固定 | 4K | 不淘汰,利用Prompt Caching |
| 工具層 | 固定 | 6K | 按使用頻率動態載入 |
| 知識層 | 彈性 | 20K | 按相關性分數截斷 |
| 對話層 | 彈性 | 30K | 滑動視窗 + 摘要壓縮 |
| 輸出預留 | 固定 | 8K | — |
| 安全緩衝 | 固定 | 10K | 防止估算誤差超窗 |
Token計數器的3種實作
方案1:tiktoken精確計數(推薦)
import tiktoken
def count_tokens(text: str, model: str = "gpt-4o") -> int:
encoding = tiktoken.encoding_for_model(model)
return len(encoding.encode(text))
def budget_check(layers: dict[str, str], max_tokens: int = 128000) -> dict:
usage = {name: count_tokens(content) for name, content in layers.items()}
total = sum(usage.values())
return {
"usage": usage,
"total": total,
"remaining": max_tokens - total,
"overflow": total > max_tokens,
}
方案2:字元估算法(快速預檢)
中文約 1.5-2 字元/Token,英文約 4 字元/Token。用於組裝前的快速預檢,最終發送前再用tiktoken精確校驗。
方案3:API返回的實際用量
呼叫後從 response.usage 取得真實消耗,用於監控和動態調整預算。
Prompt組裝流水線設計
流水線架構
用戶輸入 ──→ [意圖識別] ──→ [知識檢索] ──→ [上下文組裝] ──→ [預算校驗] ──→ LLM API
│ │ │ │
▼ ▼ ▼ ▼
決定載入哪些工具 Top-K文件篩選 按優先級合併 超限則壓縮
組裝優先級規則
當Token預算不足時,按以下順序裁剪:
- 先裁對話層最舊的訊息(保留最近N輪 + 首輪系統互動)
- 再裁知識層低分文件(按Rerank分數從低到高移除)
- 再裁工具層未使用的工具定義(只保留本輪可能呼叫的工具)
- 最後才動系統層(系統層應盡量固定,利用Prompt Caching)
生產級組裝器實作
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class ContextLayer:
name: str
content: str
priority: int
max_tokens: int
cacheable: bool = False
@dataclass
class ContextBudget:
total_limit: int = 128000
output_reserve: int = 8000
safety_buffer: int = 10000
@property
def available(self) -> int:
return self.total_limit - self.output_reserve - self.safety_buffer
class ContextAssembler:
def __init__(self, budget: ContextBudget):
self.budget = budget
self.layers: list[ContextLayer] = []
def add_layer(self, layer: ContextLayer) -> None:
self.layers.append(layer)
self.layers.sort(key=lambda l: l.priority)
def assemble(self) -> str:
available = self.budget.available
parts: list[str] = []
for layer in self.layers:
tokens = count_tokens(layer.content)
if tokens <= layer.max_tokens and tokens <= available:
parts.append(layer.content)
available -= tokens
elif tokens > layer.max_tokens:
truncated = self._truncate(layer.content, layer.max_tokens)
parts.append(truncated)
available -= layer.max_tokens
return "\n\n---\n\n".join(parts)
def _truncate(self, text: str, max_tokens: int) -> str:
encoding = tiktoken.encoding_for_model("gpt-4o")
tokens = encoding.encode(text)
if len(tokens) <= max_tokens:
return text
return encoding.decode(tokens[:max_tokens])
RAG上下文注入最佳實踐
RAG是Context Engineering中最容易踩坑的環節。檢索到的文件如何「餵」給模型,直接決定回答品質。
文件注入的3種格式
格式1:XML標籤包裹(推薦,Claude/GPT均友好)
<retrieved_documents>
<document source="product_manual_v3.pdf" score="0.92">
產品保固期為購買之日起12個月,人為損壞不在保固範圍內...
</document>
<document source="faq_returns.md" score="0.87">
7天無理由退貨需保持商品完好,運費由買家承擔...
</document>
</retrieved_documents>
格式2:編號引用(適合需要溯源的場景)
[1] 產品保固期為購買之日起12個月...(來源:product_manual_v3.pdf)
[2] 7天無理由退貨需保持商品完好...(來源:faq_returns.md)
請基於以上參考資料回答用戶問題,並在回答中標註引用編號。
RAG上下文的5條鐵律
| 規則 | 說明 | 違反後果 |
|---|---|---|
| 相關性閾值過濾 | 低於0.7分的文件不注入 | 噪聲干擾,幻覺增加 |
| 去重 | 同一文件的不同Chunk合併 | Token浪費 |
| 元資料保留 | 標註來源、時間、版本 | 無法溯源,合規風險 |
| 位置優先 | 最相關的文件放最前面 | 模型注意力衰減(Lost in the Middle) |
| 動態Top-K | 根據剩餘預算調整K值 | 預算不足時硬截斷 |
Lost in the Middle問題
2023年史丹佛大學的研究發現:當相關資訊放在上下文中间位置時,模型的召回準確率顯著下降。生產中的應對策略:
- 最相關的2條文件放在最前面
- 次相關的放在最後面
- 中間位置放系統提示詞或工具定義等「固定內容」
上下文壓縮與淘汰策略
策略1:滑動視窗
保留最近N輪對話,最簡單也最有效。推薦 N=10-20(視單輪Token量而定)。
策略2:摘要壓縮
當對話超過閾值時,用一個小模型(或同模型)對早期對話做摘要:
SUMMARIZE_PROMPT = """請將以下對話歷史壓縮為一段摘要,保留:
1. 用戶的核心訴求和約束條件
2. 已確認的關鍵事實(訂單號、產品型號等)
3. 尚未解決的問題
對話歷史:
{history}
摘要(不超過200字):"""
策略3:結構化狀態提取
對於Agent場景,不保留完整對話,而是維護一個結構化的「會話狀態」:
{
"user_intent": "查詢訂單物流",
"entities": {"order_id": "ORD-20260703-8842"},
"resolved": ["已確認訂單存在", "物流承運商為順豐"],
"pending": ["預計送達時間"]
}
每輪只把狀態JSON注入上下文,Token消耗從O(輪數)降到O(1)。
Python生產級Context Manager實作
class ProductionContextManager:
def __init__(
self,
model: str = "gpt-4o",
budget: ContextBudget = ContextBudget(),
):
self.model = model
self.budget = budget
self.assembler = ContextAssembler(budget)
self.conversation_history: list[dict] = []
def build_context(
self,
user_input: str,
system_prompt: str,
retrieved_docs: Optional[list[dict]] = None,
tools: Optional[list[dict]] = None,
) -> str:
self.assembler.layers.clear()
self.assembler.add_layer(ContextLayer(
name="system", content=system_prompt,
priority=0, max_tokens=4000, cacheable=True,
))
if tools:
tools_text = self._format_tools(tools)
self.assembler.add_layer(ContextLayer(
name="tools", content=tools_text,
priority=1, max_tokens=6000,
))
if retrieved_docs:
docs_text = self._format_rag_docs(retrieved_docs)
self.assembler.add_layer(ContextLayer(
name="knowledge", content=docs_text,
priority=2, max_tokens=20000,
))
history_text = self._format_history()
self.assembler.add_layer(ContextLayer(
name="conversation", content=history_text,
priority=3, max_tokens=30000,
))
user_layer = f"<user_query>\n{user_input}\n</user_query>"
self.assembler.add_layer(ContextLayer(
name="current_input", content=user_layer,
priority=4, max_tokens=4000,
))
return self.assembler.assemble()
def _format_rag_docs(self, docs: list[dict]) -> str:
filtered = [d for d in docs if d.get("score", 0) >= 0.7]
filtered.sort(key=lambda d: d["score"], reverse=True)
parts = ["<retrieved_documents>"]
for doc in filtered:
parts.append(
f' <document source="{doc["source"]}" score="{doc["score"]:.2f}">\n'
f' {doc["content"]}\n'
f' </document>'
)
parts.append("</retrieved_documents>")
return "\n".join(parts)
def update_history(self, role: str, content: str) -> None:
self.conversation_history.append({"role": role, "content": content})
if len(self.conversation_history) > 40:
self._compact_history()
def _compact_history(self) -> None:
old = self.conversation_history[:20]
self.conversation_history = self.conversation_history[20:]
summary = f"[Earlier conversation summary: {len(old)} messages condensed]"
self.conversation_history.insert(0, {"role": "system", "content": summary})
面試高頻考點與避坑指南
面試常問的5個問題
Q1:Context Engineering和Prompt Engineering有什麼區別?
Context Engineering關注整個輸入上下文的組織、預算和生命週期管理,是系統級設計;Prompt Engineering關注單條提示詞的措辭優化,是技巧級優化。生產環境中前者決定系統能不能跑、跑多少錢,後者決定回答品質好多少。
Q2:128K上下文是不是可以不做壓縮了?
不能。128K是理論上限,實際中要考慮:成本(按Token計費)、延遲(上下文越長推理越慢)、注意力衰減(Lost in the Middle)、以及多使用者並發時的資源競爭。生產環境必須做預算管理。
Q3:RAG檢索10條文件全部注入好不好?
不好。檢索結果必須經過相關性過濾(閾值)、去重、Rerank重排,並按預算動態調整Top-K。噪聲文件會顯著增加幻覺率。
Q4:Prompt Caching和Context Engineering什麼關係?
Prompt Caching是成本優化手段,Context Engineering決定哪些內容適合快取。系統提示詞和工具定義等固定內容應標記為cacheable,對話層和RAG結果不適合快取。
Q5:多Agent系統中上下文怎麼管理?
每個Agent維護獨立的Context Manager,透過結構化狀態(而非完整對話歷史)在Agent間傳遞資訊。避免把Agent A的完整上下文傳給Agent B,Token會指數級膨脹。
生產避坑清單
| 坑 | 表現 | 解法 |
|---|---|---|
| 無限累積對話歷史 | 多輪後響應變慢、超窗截斷 | 滑動視窗 + 摘要壓縮 |
| RAG噪聲注入 | 回答偏離用戶問題 | 相關性閾值 + Rerank |
| 系統提示詞重複計費 | API成本居高不下 | Prompt Caching |
| 工具定義全量載入 | 8K+ Token浪費在無用工具上 | 按意圖動態載入工具 |
| 缺少Token監控 | 成本失控後才發現 | 每次呼叫記錄 usage 指標 |
多模型上下文的差異化管理
生產環境抽象 TokenCounter 介面,絕不用 GPT 的 tiktoken 估算 Claude 用量。
Prompt Caching 生產實踐
系統提示詞和工具定義標記為可快取,90% 快取命中率可降低約 80% API 成本。
企業客服系統改造案例
平均輸入 Token 從 45K 降至 12K,月 API 成本從 ¥28 萬降至 ¥9 萬,答非所問率從 23% 降至 6%。
上下文工程 vs 微調 vs 長上下文
80% 的「模型效果不好」是上下文組織問題,不是模型能力問題。先優化 Context Engineering,再考慮微調。
2026 年趨勢
上下文感知 Reranker、自適應 Token 預算、跨會話長期記憶、多模態上下文管理、上下文安全。
總結與延伸閱讀
Context Engineering是2026年LLM應用從Demo走向生產的分水嶺。核心就三件事:分層管理上下文、嚴格Token預算、智慧壓縮淘汰。
設計要點回顧:
- 上下文分4層:系統層、工具層、知識層、對話層,各有獨立預算
- Token預算是硬約束,128K視窗不等於可以揮霍
- RAG注入必須過濾、去重、排序,避免Lost in the Middle
- 對話壓縮三策略:滑動視窗、摘要壓縮、結構化狀態提取
- Prompt Caching + Context Pipeline是成本優化的組合拳
相關閱讀:
權威參考:
本站提供瀏覽器本地工具,免註冊即可試用 →