一、Token计数与成本控制

1. Token 是 AI 模型处理文本时的基本计算单位,但是ai分割文字的逻辑与人类不同。其中,中文token的效率低于英文token的。

"我爱编程" → 3 个 Token
[我] [爱] [编程]

"Hello world" → 2 个 Token
[Hello] [world]

"unbelievable" → 3 个 Token
[un] [believe] [able]

"Hello, world!" → 4 个 Token
[Hello] [,] [world] [!]

2. 不需要手动计算token,只需要学会token的计算工具

(1)tiktoken (2) Colab (3)访问 OpenAI Tokenizer 在线计算

3. token的重要性

(1)OpenAI 等 API 按 Token 数量收费:输入*输入单价+输出*输出单价

(2)每个模型有最大 Token 限制,超出限制会报错

(3)token越多,处理时间越长

(4)不同模型编码方式不同,GPT-4和GPT-3.5的Token计数可能不同

6. 优化Token的方法:

策略 效果 实施方式
选择合适模型 可节省60-70% 简单任务用GPT-3.5,复杂任务用GPT-4o 
精简提示词 减少30-50%输入 删除冗余说明,使用简洁指令 
限制输出长度 直接控制成本 设置 max_tokens 参数 
实施缓存 重复查询降90% 对常见问题缓存结果 
批量处理 减少API调用次数 合并多个请求 
管理对话历史 防止Token累积 周期性总结,仅保留关键上下文 
使用缓存Token定价 重复内容享受折扣 大型系统提示词可受益 

总结:Token就是ai的货币,要优化token的使用,更好的控制成本

二、Prompt格式化与消息结构

一、ChatGPT API的消息结构(system, user, assistant)

1. system role

作用:设定AI的身份、行为准则、回答风格

特点:全局、优先级最高

# 设定专业身份
system_messages = [
    {"role": "system", "content": "你是一位资深中医专家,擅长舌诊和脉诊"},
]

# 设定回答格式
system_messages = [
    {"role": "system", "content": "用JSON格式回答,包含'答案'和'置信度'两个字段"}
]

# 设定行为边界
system_messages = [
    {"role": "system", "content": "只能回答编程相关问题,其他问题礼貌拒绝"}
]

2. User Role(用户角色)

(1) 作用:用户的输入、提问、指令

(2) 特点:支持多模态,每次调用api必须有user message

3. Assistant Role(助手角色)

(1) 作用:AI的回复内容,用于维持对话上下文

(2) 每次调用都是独立的,上下文完全由你传入的messages决定

4. 高级用法

(1)动态切换system指令

# 第一阶段:扮演老师
messages = [
    {"role": "system", "content": "你是数学老师,详细讲解解题步骤"}
]

# 第二阶段:切换角色
messages.append({"role": "system", "content": "现在你是考官,只提问不解答"})

(2) 使用function calling时的消息结构

messages = [
    {"role": "system", "content": "你是天气预报助手"},
    {"role": "user", "content": "北京天气怎么样?"},
    {"role": "assistant", "content": None, "function_call": {
        "name": "get_weather",
        "arguments": '{"city": "Beijing"}'
    }},
    {"role": "function", "name": "get_weather", "content": "25°C,晴天"}
]

(3) 构建多轮对话的注意事项

# ✅ 正确:完整保存所有历史
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(model="gpt-4o", messages=messages)
messages.append({"role": "assistant", "content": response.choices[0].message.content})

# ❌ 错误:每次都重新开始,没有上下文
def ask(question):
    messages = [{"role": "system", "content": "你是助手"},
                {"role": "user", "content": question}]
    return client.chat.completions.create(...)  # 每次都是新对话

(4)控制上下文长度(防止超限)

def trim_messages(messages, max_tokens=4000):
    """当历史消息过长时,删除最早的对话"""
    total_tokens = count_tokens(messages)
    
    while total_tokens > max_tokens and len(messages) > 1:
        # 保留system消息,删除最早的user+assistant对话
        if messages[1]["role"] == "user" and messages[2]["role"] == "assistant":
            messages.pop(1)  # 删除user
            messages.pop(1)  # 删除assistant
            total_tokens = count_tokens(messages)
    
    return messages

二、设计有效的 System Prompt

1. 基础模板

system_prompt = """
你是 [角色定位],擅长 [核心能力]。

任务目标:
- [具体要完成什么]

输出要求:
- [格式要求]
- [风格要求]

约束条件:
- [不能做什么]
- [必须遵守什么]

示例(可选):
- [输入] → [输出]
"""

2. 实战案例

(1)专业助手

system_prompt = """
你是资深Python工程师,擅长代码审查和性能优化。

任务目标:
- 分析用户提供的代码
- 指出潜在的性能问题和安全漏洞
- 提供优化建议和最佳实践

输出格式:
1. 问题列表(严重程度:高/中/低)
2. 优化建议(附代码示例)
3. 性能对比(优化前后)

约束:
- 不要修改原始代码,只提供建议
- 不要评价代码风格(除非影响性能)
- 用中文回答,代码注释用英文

示例:
用户代码:for i in range(len(arr)): 
          print(arr[i])
输出:问题:[中] 低效的索引访问
建议:使用直接迭代 for item in arr:
性能:提升约30%
"""

(2)角色扮演

system_prompt = """
你是古代中医李时珍,精通本草纲目。

行为准则:
- 用古代中医的语言风格对话
- 提供养生建议时引用《本草纲目》
- 遇到现代医学问题,说明"古今有别,仅供参考"

禁止行为:
- 不开具具体药方
- 不替代现代医疗诊断
- 不推荐未经考证的偏方

语气:
温和、耐心,偶尔引用古籍原文
"""

(3)格式约束

system_prompt = """
你是数据提取专家,从用户输入中提取关键信息并以JSON格式返回。

输出JSON结构:
{
  "intent": "用户意图(查询/创建/删除/更新)",
  "entities": {
    "时间": "提取的时间信息",
    "地点": "提取的地点信息",
    "对象": "操作对象"
  },
  "confidence": 0.0-1.0之间的置信度
}

规则:
- 如果信息不完整,confidence设为0.5以下
- 无法识别的字段设置为null
- 只返回JSON,不要添加任何额外文字

示例:
输入:"明天下午3点提醒我开会"
输出:{"intent": "创建", "entities": {"时间": "明天下午3点", "对象": "开会"}, "confidence": 0.95}
"""

(4) 多步骤推理型

system_prompt = """
你是数学解题助手,需要逐步推理。

回答流程:
1. 分析问题:列出已知条件和未知量
2. 选择方法:说明使用什么公式或思路
3. 详细计算:展示每一步推导过程
4. 验证答案:检查结果是否合理
5. 总结:用一句话概括关键点

注意事项:
- 如果有多解法,列出至少两种
- 计算过程不要跳步
- 用数学符号(LaTeX格式)表示公式
"""
  • System Prompt不是万能的,复杂约束需要分步骤实现
  • 历史消息会占用Token,要设计合理的上下文窗口管理策略
  • 不要过度依赖"你是XXX专家",实际效果有限

三、多轮对话的上下文管理

1. 上下文 = 对话历史 + 当前输入 + 系统设定 + 外部信息

2. 为什么需要管理?

(1)Token超限会报错 
(2)每次都传完整历史,成本线性增长 
(3)注意力稀释
(4)敏感信息留在历史 会有数据泄露风险 

3. 核心管理策略  

(1)滑动窗口:只保留最近 N 轮对话,删除更早的历史

优点:简单可控,Token可控
缺点:丢失早期重要信息

(2)摘要压缩:将早期对话压缩成摘要,保留故事线,压缩篇幅,但信息完整

优点:保留关键信息,Token可控
缺点:可能丢失细节,需额外API调用

(3)关键信息提取:从对话中抽取出结构化的事实,丢弃叙事细节。(用户偏好、决策、待办等)

优点:高效保留核心信息
缺点:需要信息提取能力

4. 实战技术方案

(1)智能截断(最简单)

(2)分层记忆(推荐)

分层记忆架构
    - Level 0: 当前会话(最近N轮)
    - Level 1: 短期记忆(当前session的关键信息)
    - Level 2: 长期记忆(跨session的用户画像)

(3)向量检索(RAG方式)适用于超长对话或需要检索特定信息的场景

5. 不同场景的选型建议:判断对前面记忆的依赖程度。

场景 推荐策略 Token消耗 实现难度 示例
客服机器人 滑动窗口 + 摘要 简单 保留最近10轮 + 每日总结
个人助手 关键信息提取 中等 记住用户偏好、日程
教育辅导 摘要压缩 中等 记录学习进度
角色扮演 向量检索 复杂 检索角色设定和剧情
代码助手 滑动窗口 简单 保留最近代码+对话
情感陪伴 分层记忆 复杂 长期记忆+情绪追踪

6. 压缩的几种选择

(1)监控Token使用:用超了就压缩

```python
class TokenMonitor:
    def __init__(self, model="gpt-4", alert_threshold=0.8):
        self.enc = tiktoken.encoding_for_model(model)
        self.alert_threshold = alert_threshold
        self.model_limit = {
            "gpt-4": 8192,
            "gpt-4-turbo": 128000,
            "gpt-3.5-turbo": 16384
        }.get(model, 8192)
    
    def count_messages(self, messages):
        total = 0
        for msg in messages:
            total += len(self.enc.encode(msg["content"]))
        return total
    
    def should_compress(self, messages):
        usage = self.count_messages(messages)
        ratio = usage / self.model_limit
        if ratio > self.alert_threshold:
            print(f"⚠️ 上下文使用率: {ratio:.1%},建议压缩")
            return True
        return False
```

(2)主动压缩策略

```python
def auto_compress(messages, compressor):
    """自动压缩:当Token超过阈值时触发"""
    monitor = TokenMonitor()
    
    while monitor.should_compress(messages):
        # 压缩策略:先尝试摘要,再滑动窗口
        if len(messages) > 10:
            # 压缩前5轮对话
            to_compress = messages[1:4]  # 跳过system
            summary = compressor.summarize(to_compress)
            
            # 用摘要替换
            messages = [messages[0]] + [{"role": "system", "content": f"历史摘要:{summary}"}] + messages[5:]
        else:
            # 最后手段:滑动窗口
            messages = [messages[0]] + messages[-6:]
    
    return messages
```

(3) 上下文管理配置模板

```python
context_config = {
    "strategy": "sliding_window",  # sliding_window / summarization / key_info
    "max_tokens": 4000,            # 最大Token数
    "max_turns": 10,               # 最大轮数
    "compress_threshold": 0.7,     # 压缩阈值(70%)
    "keep_system": True,           # 保留system消息
    "summarize_interval": 20,      # 每20轮压缩一次
    "key_info_extraction": True,   # 是否提取关键信息
    "memory_persistence": False    # 是否持久化记忆
}
```

🎯 面试/求职要点

如果面试被问到"如何处理长对话",可以这样回答:

> 我会采用**分层记忆架构**:
> 1. **短期记忆**:保留最近N轮对话,用滑动窗口控制Token
> 2. **中期记忆**:提取关键信息(用户偏好、决策、待办)
> 3. **长期记忆**:将重要信息沉淀到用户画像,跨会话复用
> 4. **监控机制**:实时监控Token使用率,超过阈值触发压缩
> 同时,我会根据业务场景选择合适的策略——客服场景侧重时效性,用滑动窗口;个人助手需要个性化,用关键信息提取。

三、流式输出

1. 流式输出(Streaming)是指AI边生成边传输,用户逐字逐句看到回复,而不是等待完整内容生成后才一次性显示。

2. 产品设计实践

(1)自适应速度:根据内容长度调整速度

(2)进度指示器

class ProgressIndicator:
    """流式输出时显示进度"""
    
    def __init__(self):
        self.chars_received = 0
        self.start_time = time.time()
    
    def update(self, chunk):
        self.chars_received += len(chunk)
        
        # 每100字符更新进度
        if self.chars_received % 100 == 0:
            elapsed = time.time() - self.start_time
            speed = self.chars_received / elapsed
            print(f"\n[进度: {self.chars_received}字符, 速度: {speed:.0f}字/秒]", 
                  file=sys.stderr)
    
    def stream_with_progress(self, stream_generator):
        for chunk in stream_generator:
            if chunk:
                self.update(chunk)
                yield chunk

(3)错误恢复机制

class ResilientStream:
    """网络中断时的优雅降级"""
    
    def __init__(self):
        self.buffer = []
    
    def stream_with_fallback(self, stream_generator):
        try:
            for chunk in stream_generator:
                if chunk:
                    self.buffer.append(chunk)
                    yield chunk
        except Exception as e:
            # 网络中断,显示已生成的部分
            print(f"\n[网络错误,已显示{len(''.join(self.buffer))}字符]", 
                  file=sys.stderr)
            # 可以选择重新连接或显示部分内容

3. 流式输出的性能优势

主要是减少首字延迟,提高用户体验

  • 流式输出不适合需要完整结果后再处理的场景(如JSON解析)
  • 前端需要处理网络中断和重连逻辑
  • 移动端要考虑电量消耗

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐