组件概述

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(工具调用和结果,循环)

核心原则

  1. System 优先:系统提示词必须放在最前面,确保 Agent 理解自己的角色
  2. 上下文连续性:History 紧跟在 System 之后,保持对话连贯性
  3. 知识注入时机:RAG 内容在 User 之前注入,为回答提供知识基础
  4. 工具定义前置:Tool Definitions 需要在调用前定义
  5. 动态循环:Tool Calls 和 Results 可以多轮循环,直到任务完成

历史对话的顺序排列

这是一个关于**上下文管理(Context Management)**的细节问题,在实际应用中非常重要。

在 LLM 的 History(历史对话)中,数据的排列是严格按照时间顺序排列的,即:

顺序排列(Chronological Order)

最旧的对话 → ... → 最近的对话
历史对话的顺序结构

在最终拼接给模型的提示词中,历史对话会按照以下格式出现:

System → User₁ → Assistant₁ → User₂ → Assistant₂ → ... → Userₙ → Assistantₙ → User_new

结构解析

  1. System Prompt 永远在最前面(定义角色和规则)。
  2. 对话从第一轮开始(User₁ / Assistant₁)。
  3. 对话按时间推移,一轮接一轮地交替出现。
  4. 新的用户提问(User_new) 永远在历史对话的末尾,紧接在它前面最近的一轮 Assistant 回答之后。
为什么是顺序排列?

顺序排列的原因基于大模型的工作原理和设计目标:

  1. 模拟人类对话:人类对话是线性、有时序性的。模型需要学习和理解上下文是如何随着时间积累和演变的,才能生成逻辑连贯的回复。

  2. Next Token Prediction:模型是基于序列的机器。它需要一个完整的、有序的上下文来预测下一个合理的词元(Token)。如果对话顺序被打乱,上下文关系就会被破坏,模型将无法理解当前正在讨论什么。

  3. 注意力机制:虽然 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,系统会:

  1. 保留 System Prompt(200 tokens)
  2. 从最旧的对话开始移除(User₁ + Assistant₁ = 600 tokens)
  3. 继续移除直到总 tokens 在限制内
  4. 保留最新的对话轮次

截断后的结果:

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)  ← 保留

常见的历史管理策略

  1. 固定窗口大小:只保留最近的 N 轮对话
  2. Token 限制:根据 Token 数量截断,而不是轮数
  3. 智能摘要:将旧对话压缩为摘要,保留关键信息
  4. 重要性评分:根据对话的重要性保留,而不是简单的时间顺序

完整交互流程

流程图

┌─────────────────────────────────────────────────────────┐
│                    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: "根据查询结果,北京今天..."│
└─────────────────────────────────────┘

工具调用的完整闭环

User Agent Tool Model 问退货政策 检索RAG(退货政策) system + RAG + 问题 需调用get_order_detail 执行get_order_detail("XZ2025") 返回订单详情 添加tool消息 生成最终回答 回复退货政策 问物流状态 检索RAG(物流政策) system + history + RAG + 问题 需调用get_logistics_status 执行get_logistics_status("SF123456789CN") 返回物流轨迹 添加tool消息 生成最终回答 回复物流状态 User Agent Tool Model

RAG 内容放置位置详解

概述

RAG(检索增强生成)内容可以放在两个位置:

  1. User 消息中(推荐,大多数场景)
  2. System 消息中(特殊场景)

选择哪种方式取决于具体场景和需求。下面详细说明。


RAG 内容放在 User 消息中

适用场景

推荐使用的情况

  1. 动态知识检索

    • 每次用户查询都需要检索不同的知识
    • 知识内容随用户问题变化
    • 典型的 RAG 应用场景
  2. 多轮对话

    • 每轮对话可能需要不同的知识
    • 需要根据当前问题动态检索
  3. 知识库较大

    • 知识库内容很多,无法全部放入 System
    • 只检索与当前问题相关的部分
  4. 用户问题驱动

    • 知识检索完全由用户问题决定
    • 不同用户可能检索到不同内容
实现方式
# 方式 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 消息中

适用场景

适合使用的情况

  1. 静态知识库

    • 知识库内容相对固定,不随用户问题变化
    • 例如:公司规章制度、产品文档、FAQ
  2. 全局上下文

    • 所有对话都需要这些知识
    • 知识作为 Agent 的"基础知识"
  3. 知识量较小

    • 知识库内容不多,可以全部放入 System
    • 不会导致 System 消息过长
  4. 一次性设置

    • 在 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 长度限制 更灵活
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐

最佳实践建议

  1. 默认选择 User:大多数场景下,RAG 内容应该放在 User 消息中
  2. System 用于核心知识:只有核心、固定、全局需要的知识才放 System
  3. 混合方式最优:核心知识放 System,详细知识放 User
  4. 注意 Token 管理:无论哪种方式,都要注意 Token 消耗和上下文窗口限制
  5. 明确分隔:使用清晰的分隔符和格式,让模型区分不同内容

决策流程图

开始
  ↓
知识库是否固定不变?
  ├─ 是 → 知识量是否很小(< 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)。

标准的拼接顺序如下:

  1. System Prompt(系统提示词):放在最前面。这是为了奠定整个对话的基调、规则和人设。模型在生成后续任何内容时,都会通过"注意力机制"回头看最开头的这些规则。
  2. History(历史对话):按照时间顺序排列的 User 和 Assistant 的交替对话(多轮对话场景)。
  3. Current User Input(当前用户问题):放在最后。
  4. 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),流程也是一样的文本拼接:

  1. User:帮我查下北京天气。
  2. Assistant:(模型输出一段特殊的文本,表示想调用查询天气的函数)。
  3. Tool:(你的代码运行完函数,把结果 {"temp": "25度"} 包装成 tool 角色传回去)。
  4. Assistant:(模型看到 tool 的结果,结合前面的上下文,最终输出:北京今天25度)。

在模型看来,这依然是一长串历史剧本,它只是根据剧本的最新进展(看到了工具的返回结果)来续写下一句台词。

总结

关键要点

  1. 编排顺序:System → History → RAG → Tool Definitions → User → Assistant → Tool Calls/Results
    用户输入 
      ↓
    RAG 检索(如果需要)
      ↓
    构建消息(System + History + RAG + User)
      ↓
    LLM 推理
      ↓
    是否需要工具?
      ├─ 是 → 执行工具 → 添加工具结果 → 继续推理
      └─ 否 → 返回最终回答
    
  2. System 优先:系统提示词必须放在最前面
  3. RAG 注入:RAG 内容作为 User 消息的一部分,在用户问题之前
  4. 工具循环:支持多轮工具调用,直到任务完成
  5. 历史管理:合理管理对话历史,避免超出 Token 限制
  6. 模型视角:所有的角色最后都变成了特殊的文本标记,按照System → History → User 的顺序拼接成一个巨大的 Prompt。模型通过注意力机制,在生成回答的每一瞬间,同时"关注"着系统指令和用户问题。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐