大模型应用技术之Agent提示词编排
本文概述了AI Agent交互的核心组件及其编排原则。主要包含六个关键组件:系统提示词(定义角色规则)、用户输入、对话历史、RAG检索、工具调用和助手回复。编排遵循系统提示优先、上下文连续、知识适时注入等原则,对话历史严格按时间顺序排列,采用滑动窗口策略管理超长上下文。完整交互流程展示了从初始化到最终回复的决策路径,包含工具调用循环和RAG检索环节。系统通过动态组合这些组件实现连贯智能的对话体验。
组件概述
1. System(系统提示词)
作用:定义 Agent 的角色、能力、行为准则和输出格式
- 设定 Agent 的身份和职责
- 定义工具使用规则
- 规定输出格式和风格
- 设置安全约束
2. User(用户输入)
作用:用户的当前请求或问题
- 用户意图
- 具体任务
- 输入数据
3. History(对话历史)
作用:维护多轮对话的上下文
- 之前的对话记录
- 用户偏好
- 已执行的操作
4. RAG(检索增强生成)
作用:从知识库检索相关信息并注入上下文
- 向量检索
- 知识库查询
- 相关文档片段
5. Tool(工具定义与调用)
作用:定义可用工具和工具调用结果
- 工具描述(Function Calling)
- 工具调用结果(Tool Response)
- 工具执行状态
6. Assistant(助手回复)
作用:Agent 的回复内容
- 思考过程
- 最终回答
- 工具调用决策
编排顺序与原则
标准编排顺序
1. System Prompt(系统提示词)
↓
2. History(对话历史,可选)
↓
3. RAG Context(检索到的知识,可选)
↓
4. Tool Definitions(工具定义,Function Calling)
↓
5. User Message(当前用户输入)
↓
6. Assistant Response(助手回复)
↓
7. Tool Calls & Results(工具调用和结果,循环)
核心原则
- System 优先:系统提示词必须放在最前面,确保 Agent 理解自己的角色
- 上下文连续性:History 紧跟在 System 之后,保持对话连贯性
- 知识注入时机:RAG 内容在 User 之前注入,为回答提供知识基础
- 工具定义前置:Tool Definitions 需要在调用前定义
- 动态循环:Tool Calls 和 Results 可以多轮循环,直到任务完成
历史对话的顺序排列
这是一个关于**上下文管理(Context Management)**的细节问题,在实际应用中非常重要。
在 LLM 的 History(历史对话)中,数据的排列是严格按照时间顺序排列的,即:
顺序排列(Chronological Order):
最旧的对话 → ... → 最近的对话
历史对话的顺序结构
在最终拼接给模型的提示词中,历史对话会按照以下格式出现:
System → User₁ → Assistant₁ → User₂ → Assistant₂ → ... → Userₙ → Assistantₙ → User_new
结构解析:
- System Prompt 永远在最前面(定义角色和规则)。
- 对话从第一轮开始(User₁ / Assistant₁)。
- 对话按时间推移,一轮接一轮地交替出现。
- 新的用户提问(User_new) 永远在历史对话的末尾,紧接在它前面最近的一轮 Assistant 回答之后。
为什么是顺序排列?
顺序排列的原因基于大模型的工作原理和设计目标:
-
模拟人类对话:人类对话是线性、有时序性的。模型需要学习和理解上下文是如何随着时间积累和演变的,才能生成逻辑连贯的回复。
-
Next Token Prediction:模型是基于序列的机器。它需要一个完整的、有序的上下文来预测下一个合理的词元(Token)。如果对话顺序被打乱,上下文关系就会被破坏,模型将无法理解当前正在讨论什么。
-
注意力机制:虽然 Transformer 模型的自注意力机制可以同时关注序列中的任何位置,但保持正确的顺序是因果关系的基础。模型需要知道 User₁ 是在 Assistant₁ 之前发生的,这样才能理解 Assistant₁ 是对 User₁ 的回应。
针对超长历史的优化(滑动窗口)
需要注意的是,由于 LLM 的上下文窗口(Context Window)长度是有限的(例如 128k Tokens),当对话轮数过多导致总长度超过限制时,通常会采用截断策略:
- 截断最旧的部分:从 User₁ 和 Assistant₁ 开始,移除最旧的对话轮次,以腾出空间给最新的对话。
- System Prompt 保留:System Prompt 通常会被保留,因为它包含关键的规则和人设,不能轻易移除。
这种截断保持了顺序结构,但只保留了最近的历史,形成了"滑动窗口"。
示例:滑动窗口截断
假设上下文窗口限制为 10k tokens,当前对话历史如下:
System (200 tokens)
User₁ (100 tokens) → Assistant₁ (500 tokens)
User₂ (150 tokens) → Assistant₂ (600 tokens)
User₃ (120 tokens) → Assistant₃ (550 tokens)
User₄ (180 tokens) → Assistant₄ (700 tokens)
User₅ (200 tokens) → Assistant₅ (800 tokens)
User_new (150 tokens)
总 tokens = 200 + 100 + 500 + 150 + 600 + 120 + 550 + 180 + 700 + 200 + 800 + 150 = 4850 tokens
如果新的对话导致总 tokens 超过 10k,系统会:
- 保留 System Prompt(200 tokens)
- 从最旧的对话开始移除(User₁ + Assistant₁ = 600 tokens)
- 继续移除直到总 tokens 在限制内
- 保留最新的对话轮次
截断后的结果:
System (200 tokens) ← 保留
User₃ (120 tokens) → Assistant₃ (550 tokens) ← 保留
User₄ (180 tokens) → Assistant₄ (700 tokens) ← 保留
User₅ (200 tokens) → Assistant₅ (800 tokens) ← 保留
User_new (150 tokens) ← 保留
常见的历史管理策略:
- 固定窗口大小:只保留最近的 N 轮对话
- Token 限制:根据 Token 数量截断,而不是轮数
- 智能摘要:将旧对话压缩为摘要,保留关键信息
- 重要性评分:根据对话的重要性保留,而不是简单的时间顺序
完整交互流程
流程图
┌─────────────────────────────────────────────────────────┐
│ AI Agent 交互流程 │
└─────────────────────────────────────────────────────────┘
1. 初始化阶段
┌─────────────┐
│ System Prompt│ ← 定义角色、规则、工具使用方式
└─────────────┘
2. 上下文准备
┌─────────────┐
│ History │ ← 加载历史对话(如果有)
└─────────────┘
3. 用户输入
┌─────────────┐
│ User Query │ ← 用户当前问题/任务
└─────────────┘
4. RAG 检索(如果需要)
┌─────────────┐
│ RAG Retrieval│ ← 从知识库检索相关信息
└─────────────┘
↓
┌─────────────┐
│ RAG Context │ ← 检索到的知识片段
└─────────────┘
5. 工具定义注入
┌─────────────┐
│ Tool Defs │ ← 可用工具的描述(Function Calling)
└─────────────┘
6. LLM 推理
┌─────────────┐
│ LLM │ ← 分析任务,决定是否需要调用工具
└─────────────┘
↓
┌─────────────┐
│ Decision │ ← 判断:直接回答 or 调用工具?
└─────────────┘
│
┌────┴────┐
│ │
↓ ↓
直接回答 调用工具
│ │
│ ┌─────────────┐
│ │ Tool Call │ ← 工具调用请求
│ └─────────────┘
│ ↓
│ ┌─────────────┐
│ │ Tool Exec │ ← 执行工具(API、数据库等)
│ └─────────────┘
│ ↓
│ ┌─────────────┐
│ │ Tool Result │ ← 工具执行结果
│ └─────────────┘
│ ↓
│ ┌─────────────┐
│ │ LLM │ ← 基于工具结果生成最终回答
│ └─────────────┘
│
└─────────┐
↓
┌─────────────┐
│ Final Answer│ ← 最终回复给用户
└─────────────┘
消息序列示例
Round 1: 简单问题(不需要工具)
┌─────────────────────────────────────┐
│ System: "你是AI助手..." │
│ User: "什么是机器学习?" │
│ Assistant: "机器学习是..." │
└─────────────────────────────────────┘
Round 2: 需要 RAG
┌─────────────────────────────────────┐
│ System: "你是AI助手..." │
│ History: [之前的对话] │
│ RAG Context: "根据文档,..." │
│ User: "我们公司的产品特点是什么?" │
│ Assistant: "根据文档,我们公司的..." │
└─────────────────────────────────────┘
Round 3: 需要工具调用
┌─────────────────────────────────────┐
│ System: "你是AI助手,可以使用工具..." │
│ Tool Definitions: [search_web, ...] │
│ User: "查询今天北京的天气" │
│ Assistant: "我需要查询天气..." │
│ Tool Call: search_weather("北京") │
│ Tool Result: "北京今天晴天,25°C" │
│ Assistant: "根据查询结果,北京今天..."│
└─────────────────────────────────────┘
工具调用的完整闭环
RAG 内容放置位置详解
概述
RAG(检索增强生成)内容可以放在两个位置:
- User 消息中(推荐,大多数场景)
- System 消息中(特殊场景)
选择哪种方式取决于具体场景和需求。下面详细说明。
RAG 内容放在 User 消息中
适用场景
✅ 推荐使用的情况:
-
动态知识检索
- 每次用户查询都需要检索不同的知识
- 知识内容随用户问题变化
- 典型的 RAG 应用场景
-
多轮对话
- 每轮对话可能需要不同的知识
- 需要根据当前问题动态检索
-
知识库较大
- 知识库内容很多,无法全部放入 System
- 只检索与当前问题相关的部分
-
用户问题驱动
- 知识检索完全由用户问题决定
- 不同用户可能检索到不同内容
实现方式
# 方式 1:RAG 内容作为 User 消息的一部分
user_message = f"""## 相关背景知识
{rag_context}
## 用户问题
{user_input}"""
messages.append({
"role": "user",
"content": user_message
})
# 方式 2:RAG 内容作为单独的 User 消息(不推荐,但可行)
messages.append({
"role": "user",
"content": f"## 背景知识\n{rag_context}"
})
messages.append({
"role": "user",
"content": user_input
})
带来的问题及解决方案
问题 1:Token 消耗增加
问题描述:
- 每轮对话都要重复发送 RAG 内容
- 如果 RAG 内容很长,会消耗大量 Token
- 在多轮对话中,历史消息累积导致 Token 快速增长
解决方案:
class OptimizedRAGAgent:
def __init__(self):
self.rag_cache = {} # 缓存已检索的内容
def chat_with_rag(self, user_input: str):
# 1. 检查缓存
query_hash = hash(user_input)
if query_hash in self.rag_cache:
rag_context = self.rag_cache[query_hash]
else:
# 2. 检索并缓存
rag_context = self.retrieve(user_input)
self.rag_cache[query_hash] = rag_context
# 3. 压缩 RAG 内容(如果太长)
if self.count_tokens(rag_context) > 2000:
rag_context = self.compress_rag(rag_context)
# 4. 使用压缩后的内容
user_message = f"{rag_context}\n\n## 用户问题\n{user_input}"
return self.chat(user_message)
def compress_rag(self, rag_context: str) -> str:
"""压缩 RAG 内容,只保留最相关的部分"""
# 方法 1:截断(简单但可能丢失信息)
# return rag_context[:2000]
# 方法 2:使用 LLM 提取关键信息(推荐)
compression_prompt = f"""请从以下文档中提取最关键的3-5个要点,每个要点不超过100字:
文档:
{rag_context}
输出格式:
1. 要点1
2. 要点2
..."""
compressed = self.llm_call(compression_prompt)
return compressed
问题 2:上下文窗口限制
问题描述:
- RAG 内容 + 历史对话 + 当前问题可能超出模型上下文窗口
- 导致早期对话被截断
解决方案:
def manage_context_with_rag(messages: List[Dict],
rag_context: str,
max_tokens: int = 8000):
"""管理包含 RAG 的上下文"""
# 1. 计算当前 Token 数
current_tokens = count_tokens(messages) + count_tokens(rag_context)
# 2. 如果超出限制,压缩历史
if current_tokens > max_tokens:
# 保留 System 消息
system_msg = messages[0] if messages[0]["role"] == "system" else None
# 压缩历史对话
history = messages[1:] if system_msg else messages
compressed_history = compress_history(history,
max_tokens=max_tokens -
count_tokens(rag_context) -
1000) # 预留空间
# 重新组装
if system_msg:
return [system_msg] + compressed_history
return compressed_history
return messages
def compress_history(history: List[Dict], max_tokens: int) -> List[Dict]:
"""压缩历史对话"""
# 策略 1:只保留最近的 N 轮对话
# return history[-10:]
# 策略 2:提取历史对话的摘要
summary = summarize_conversation(history)
return [{"role": "user", "content": f"## 对话历史摘要\n{summary}"}]
问题 3:RAG 内容与用户问题混淆
问题描述:
- 模型可能无法清晰区分 RAG 内容和用户问题
- 可能将 RAG 内容误认为是用户问题的一部分
解决方案:
def format_rag_in_user_message(rag_context: str, user_input: str) -> str:
"""格式化 RAG 内容,明确区分背景知识和用户问题"""
return f"""# 背景知识(仅供参考)
以下是与你问题相关的背景信息,请优先使用这些信息回答问题:
{rag_context}
---
# 用户问题
{user_input}
---
# 回答要求
1. 优先使用上述背景知识
2. 如果背景知识中没有相关信息,可以结合你的通用知识
3. 明确标注信息来源(背景知识 or 通用知识)
4. 如果信息不足,请明确说明"""
问题 4:多轮对话中 RAG 内容重复
问题描述:
- 在多轮对话中,如果用户问题相关,可能检索到相同的 RAG 内容
- 导致重复发送相同内容,浪费 Token
解决方案:
class SmartRAGAgent:
def __init__(self):
self.recent_rag = [] # 最近使用的 RAG 内容
self.rag_hashes = set() # 已使用的 RAG 内容哈希
def chat(self, user_input: str):
# 1. 检索 RAG
rag_context = self.retrieve(user_input)
rag_hash = hash(rag_context)
# 2. 检查是否最近使用过
if rag_hash in self.rag_hashes:
# 使用简短的引用,而不是完整内容
user_message = f"""## 背景知识
(使用之前检索到的相关知识)
## 用户问题
{user_input}"""
else:
# 3. 首次使用,完整发送
self.recent_rag.append(rag_hash)
self.rag_hashes.add(rag_hash)
# 限制最近 RAG 数量
if len(self.recent_rag) > 5:
old_hash = self.recent_rag.pop(0)
self.rag_hashes.remove(old_hash)
user_message = f"""## 背景知识
{rag_context}
## 用户问题
{user_input}"""
return self.llm_call(user_message)
RAG 内容放在 System 消息中
适用场景
✅ 适合使用的情况:
-
静态知识库
- 知识库内容相对固定,不随用户问题变化
- 例如:公司规章制度、产品文档、FAQ
-
全局上下文
- 所有对话都需要这些知识
- 知识作为 Agent 的"基础知识"
-
知识量较小
- 知识库内容不多,可以全部放入 System
- 不会导致 System 消息过长
-
一次性设置
- 在 Agent 初始化时设置,后续不再变化
- 避免每轮对话重复发送
实现方式
# 方式 1:在初始化时设置
system_prompt = f"""你是一位专业的AI助手。
## 你的知识库
{rag_knowledge_base}
## 你的职责
基于上述知识库回答用户问题。如果知识库中没有相关信息,请明确说明。"""
agent.initialize(system_prompt)
# 方式 2:动态更新 System(不推荐,但可行)
def update_system_with_rag(agent, new_rag_content: str):
"""更新 System 消息中的 RAG 内容"""
original_system = agent.messages[0]["content"]
# 提取原始 System 提示(不含 RAG)
base_system = extract_base_system(original_system)
# 更新 System
agent.messages[0]["content"] = f"""{base_system}
## 知识库更新
{new_rag_content}"""
带来的问题及解决方案
问题 1:System 消息过长
问题描述:
- System 消息有长度限制(不同模型不同,通常 8K-32K tokens)
- 如果知识库很大,无法全部放入 System
- 可能导致 System 消息被截断
解决方案:
def smart_system_rag(knowledge_base: List[str], max_tokens: int = 4000):
"""智能选择放入 System 的知识"""
# 策略 1:只放入核心知识
core_knowledge = filter_core_knowledge(knowledge_base)
# 策略 2:知识摘要
if count_tokens(knowledge_base) > max_tokens:
summarized = summarize_knowledge(knowledge_base)
return summarized
# 策略 3:分层知识
# 核心知识放 System,详细知识放 User(混合方式)
core = extract_core(knowledge_base)
details = extract_details(knowledge_base)
system_prompt = f"""你的核心知识:
{core}
注意:详细知识会在用户问题中提供。"""
return system_prompt, details
def filter_core_knowledge(kb: List[str]) -> List[str]:
"""过滤出核心知识"""
# 根据重要性、使用频率等筛选
# 这里简化处理
return kb[:10] # 只取前10条
问题 2:无法动态更新
问题描述:
- System 消息通常在整个对话中保持不变
- 如果知识库更新,无法及时反映
- 需要重新初始化 Agent
解决方案:
class DynamicSystemRAGAgent:
def __init__(self):
self.base_system = "你是一位AI助手。"
self.static_knowledge = [] # 静态知识(放 System)
self.dynamic_knowledge = [] # 动态知识(放 User)
def initialize(self):
"""初始化,静态知识放 System"""
system_content = f"""{self.base_system}
## 静态知识库(固定不变)
{self.format_knowledge(self.static_knowledge)}"""
self.messages = [{"role": "system", "content": system_content}]
def chat(self, user_input: str):
"""对话,动态知识放 User"""
# 检索动态知识
dynamic_rag = self.retrieve_dynamic(user_input)
user_message = f"""## 动态知识(本次检索)
{dynamic_rag}
## 用户问题
{user_input}"""
self.messages.append({"role": "user", "content": user_message})
response = self.llm_call()
return response
问题 3:所有对话都携带相同知识
问题描述:
- System 中的知识在所有对话中都存在
- 即使当前问题不需要这些知识,也会消耗 Token
- 在多轮对话中,这些 Token 会累积
解决方案:
# 方案 1:精简 System 知识
# 只放入最核心、最常用的知识
# 方案 2:使用混合方式
# 核心知识放 System,详细知识放 User
# 方案 3:知识分层
system_prompt = """你的知识库分为两层:
1. 核心知识(在 System 中,始终可用)
2. 详细知识(在 User 消息中,按需提供)
优先使用 User 消息中的详细知识。"""
问题 4:知识更新困难
问题描述:
- 更新 System 消息需要重新初始化整个对话
- 无法在对话过程中动态更新知识
解决方案:
class UpdatableSystemRAGAgent:
def __init__(self):
self.base_system = "你是一位AI助手。"
self.knowledge_base = []
self.initialize()
def update_knowledge(self, new_knowledge: List[str]):
"""更新知识库"""
self.knowledge_base = new_knowledge
# 重新构建 System
system_content = f"""{self.base_system}
## 知识库(已更新)
{self.format_knowledge(self.knowledge_base)}"""
# 更新 System 消息
self.messages[0]["content"] = system_content
# 可选:通知用户知识已更新
self.messages.append({
"role": "assistant",
"content": "【系统通知】知识库已更新,我将使用最新知识回答您的问题。"
})
混合方式
结合两种方式的优点:
class HybridRAGAgent:
"""混合 RAG Agent:核心知识放 System,动态知识放 User"""
def __init__(self):
# 核心知识(固定,放 System)
self.core_knowledge = [
"公司成立于2020年",
"主要产品是AI助手",
"总部位于北京"
]
# 详细知识库(动态检索,放 User)
self.detailed_kb = DetailedKnowledgeBase()
def initialize(self):
"""初始化:核心知识放 System"""
system_prompt = f"""你是一位专业的AI助手。
## 核心知识(始终可用)
{self.format_knowledge(self.core_knowledge)}
## 使用说明
1. 核心知识始终可用,用于回答基础问题
2. 详细知识会在用户问题中提供(如果相关)
3. 优先使用详细知识,核心知识作为补充"""
self.messages = [{"role": "system", "content": system_prompt}]
def chat(self, user_input: str):
"""对话:详细知识放 User"""
# 检索详细知识
detailed_rag = self.detailed_kb.retrieve(user_input)
if detailed_rag:
user_message = f"""## 详细背景知识
{detailed_rag}
## 用户问题
{user_input}"""
else:
user_message = user_input
self.messages.append({"role": "user", "content": user_message})
response = self.llm_call()
return response
对比总结
| 特性 | 放在 User | 放在 System | 混合方式 |
|---|---|---|---|
| 适用场景 | 动态检索、多轮对话 | 静态知识、全局上下文 | 复杂场景 |
| Token 消耗 | 每轮都消耗 | 一次性消耗,但持续存在 | 平衡 |
| 更新灵活性 | 高(每轮可更新) | 低(需重新初始化) | 高 |
| 上下文管理 | 需要管理历史 | 相对简单 | 需要分层管理 |
| 知识量限制 | 受单次上下文限制 | 受 System 长度限制 | 更灵活 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
最佳实践建议
- 默认选择 User:大多数场景下,RAG 内容应该放在 User 消息中
- System 用于核心知识:只有核心、固定、全局需要的知识才放 System
- 混合方式最优:核心知识放 System,详细知识放 User
- 注意 Token 管理:无论哪种方式,都要注意 Token 消耗和上下文窗口限制
- 明确分隔:使用清晰的分隔符和格式,让模型区分不同内容
决策流程图
开始
↓
知识库是否固定不变?
├─ 是 → 知识量是否很小(< 2K tokens)?
│ ├─ 是 → 放在 System ✅
│ └─ 否 → 放在 User(或混合方式)✅
│
└─ 否 → 知识是否需要动态检索?
├─ 是 → 放在 User ✅
└─ 否 → 混合方式 ✅
最佳实践
1. System Prompt 设计
✅ 好的 System Prompt:
system_prompt = """你是一位专业的AI助手,擅长技术咨询。
你的能力:
1. 基于知识库回答问题(RAG)
2. 调用工具执行任务
3. 维护多轮对话上下文
回答原则:
- 优先使用提供的背景知识
- 需要时主动调用工具
- 如果信息不足,明确说明"我不确定"
- 引用具体的知识片段或工具结果
输出格式:
- 使用 Markdown 格式
- 重要信息用加粗
- 代码用代码块
"""
❌ 差的 System Prompt:
system_prompt = "你是AI助手" # 太简单,没有指导性
2. RAG 上下文注入
✅ 好的方式:
# 将 RAG 内容作为 User 消息的一部分
user_message = f"""## 相关背景知识
{rag_context}
## 用户问题
{user_input}"""
❌ 差的方式:
# 不要将 RAG 作为单独的 system 或 assistant 消息
messages.append({"role": "system", "content": rag_context}) # 错误
3. 工具调用管理
✅ 好的实践:
- 工具定义清晰、参数明确
- 工具结果格式化(JSON)
- 支持多轮工具调用
- 错误处理完善
# 工具定义要详细
tool_def = {
"type": "function",
"function": {
"name": "search_weather",
"description": "查询指定城市的当前天气信息,包括温度、天气状况、湿度等",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "要查询的城市名称,必须是中文城市名,例如:北京、上海、广州"
}
},
"required": ["city"]
}
}
}
4. 历史管理
✅ 好的实践:
- 限制历史长度(避免超出 Token 限制)
- 保留关键上下文
- 压缩旧对话
def manage_history(messages: List[Dict], max_history: int = 10):
"""管理对话历史"""
# 保留 system 消息
system_msg = messages[0] if messages[0]["role"] == "system" else None
# 只保留最近的对话
recent_messages = messages[-max_history:]
if system_msg:
return [system_msg] + recent_messages
return recent_messages
5. 错误处理
def safe_chat(agent, user_input: str) -> str:
"""安全的对话调用"""
try:
return agent.chat(user_input)
except Exception as e:
return f"抱歉,处理您的请求时出现错误:{str(e)}"
6. 性能优化
- RAG 检索优化:使用合适的 top_k,避免检索过多无关内容
- 工具调用优化:批量执行工具,减少 API 调用次数
- 上下文压缩:定期压缩历史对话,保留关键信息
模型处理机制解析
核心机制:从"角色对象"到"纯文本"
当你调用 API(例如 OpenAI 格式)发送如下请求时:
[
{"role": "system", "content": "你是一个翻译助手。"},
{"role": "user", "content": "Hello world 是什么意思?"}
]
大模型本身并不能直接读懂 JSON 对象。在进入模型之前,有一个模板化(Templating)的过程,会将这些角色转换为模型在训练时学过的一种特定格式的字符串。
模型实际看到的内容
不同的模型有不同的"特殊标记(Special Tokens)"格式(例如 ChatML 格式),大概长这样:
<|im_start|>system
你是一个翻译助手。<|im_end|>
<|im_start|>user
Hello world 是什么意思?<|im_end|>
<|im_start|>assistant
关键点:
- 扁平化:所有的角色和内容被压扁成了一行字。
- 特殊标记:
<|im_start|>和<|im_end|>就像是剧本里的"幕布",告诉模型哪里是"系统指令"的开始,哪里是"用户说话"的结束。
顺序是怎么样的?
拼接的顺序非常严格,通常遵循时间线性逻辑,因为大模型本质上是一个"文字接龙"机器(Next Token Prediction)。
标准的拼接顺序如下:
- System Prompt(系统提示词):放在最前面。这是为了奠定整个对话的基调、规则和人设。模型在生成后续任何内容时,都会通过"注意力机制"回头看最开头的这些规则。
- History(历史对话):按照时间顺序排列的 User 和 Assistant 的交替对话(多轮对话场景)。
- Current User Input(当前用户问题):放在最后。
- Pending Assistant Trigger(待生成的回答):最后通常加上一个标记(如
<|im_start|>assistant),提示模型:“好,现在轮到你说话了,请开始补全后面的字。”
模型是如何"处理"这些角色的?
这并不是一个"步骤 1 处理 System → 步骤 2 处理 User"的过程。
并行处理(Self-Attention)
在大模型内部(Transformer 架构),当你把上面那一长串文本输入进去时,模型会一次性计算所有文字之间的关系。
- 注意力机制(Attention):当模型试图回答最后的
Hello world时,它的"注意力"会同时分配给开头的system(翻译助手)和中间的user内容。 - 权重影响:即使 System Prompt 在最前面,模型在训练时已经学会了赋予
<|im_start|>system里的内容极高的权重。它知道:“写在这个标签里的内容是’天条’,我必须遵守。”
为什么会有 tool 角色?
如果涉及工具调用(Function Calling),流程也是一样的文本拼接:
- User:帮我查下北京天气。
- Assistant:(模型输出一段特殊的文本,表示想调用查询天气的函数)。
- Tool:(你的代码运行完函数,把结果
{"temp": "25度"}包装成 tool 角色传回去)。 - Assistant:(模型看到 tool 的结果,结合前面的上下文,最终输出:北京今天25度)。
在模型看来,这依然是一长串历史剧本,它只是根据剧本的最新进展(看到了工具的返回结果)来续写下一句台词。
总结
关键要点
- 编排顺序:System → History → RAG → Tool Definitions → User → Assistant → Tool Calls/Results
用户输入 ↓ RAG 检索(如果需要) ↓ 构建消息(System + History + RAG + User) ↓ LLM 推理 ↓ 是否需要工具? ├─ 是 → 执行工具 → 添加工具结果 → 继续推理 └─ 否 → 返回最终回答 - System 优先:系统提示词必须放在最前面
- RAG 注入:RAG 内容作为 User 消息的一部分,在用户问题之前
- 工具循环:支持多轮工具调用,直到任务完成
- 历史管理:合理管理对话历史,避免超出 Token 限制
- 模型视角:所有的角色最后都变成了特殊的文本标记,按照System → History → User 的顺序拼接成一个巨大的 Prompt。模型通过注意力机制,在生成回答的每一瞬间,同时"关注"着系统指令和用户问题。
更多推荐



所有评论(0)