【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
背景
上篇 blog
【AI】【Agent】联网使用大模型(DashScope&OpenAI)
详细分析了messages中的各个角色:system(系统指令,可以设定 AI 角色),user(用户输入,是可以触发 AI 响应的信号),以及assistant(保存 AI 的回复,用于多轮对话),并分析了role角色是如何影响模型输出的,比如通过system设定不同的角色,或者有无assistant对模型的理解,最后列出了一些常见的错误写法,下面继续分析
Agent
再介绍两个写messages的技巧
- 动态
system指令:虽然不推荐频繁改system,但在任务切换时可以重建整个messages,适合复杂的 Agent,比如
# 从【程序员】切换到【翻译官】messages=[{"role":"system","content":"你是一名中英翻译专家"},{"role":"user","content":"Hello world"}]- 用
system控制输出格式:可以强化结构输出,便于程序解析,比如
{"role":"system","content":"你的回答必须是 JSON 格式,包含 {\"answer\": \"...\"}"}OK,message的三种角色分析完了,之前 blog 举的例子基本上都是聚焦单轮对话,而在多轮对话中,就需要assistant里面 AI 的回复了,assistant需要不断保存之前 AI 的回复,形成连续的上下文,模型才能结合之前的交互,给出最近问题的新回答,那么这里问题来了,在多轮对话中,assistant会不断地累积token消耗,最终可能就会超出模型上下文的长度限制,这也正是所有基于messages的对话系统必须面对的核心挑战,下面来分析下
首先这个问题的本质,是每条消息(包括user和assistant)都会被编码成tokens,比如
messages=[{"role":"user","content":"你好"},# ≈ 3 tokens{"role":"assistant","content":"你好!有什么我可以帮忙的吗?"},# ≈ 10 tokens{"role":"user","content":"你能写代码吗?"},# ≈ 6 tokens# ... 继续下去]随着对话轮数的增加,虽然上下文更连贯了,但token总量会越来越逼近上下,比如简化版的 Qwen-Plus 最大支持 128K 个tokens
超出后 API 会报错Input length exceeds max context length,而token成本 = 所有messages的总长度 + 新回复长度
对于这种多轮对话,assistant会不断地累积token的场景,也有一些策略可以处理,比如
- 滑动窗口(最常用):只保留最近 N 轮对话,丢弃最早的历史,比如
MAX_TURNS=5# 保留最近5轮(即10条消息)defadd_message(messages,role,content):messages.append({"role":role,"content":content})# 确保 user/assistant 成对,所以最多保留 MAX_TURNS * 2 条iflen(messages)>MAX_TURNS*2:# 丢弃最早的两条(一轮)messages=messages[2:]returnmessages这样做的话简单高效,但是会忘记早期的重要信息
- 总结摘要(适合长对话):当历史太长时,让 AI 自己总结前面的对话
# 当 messages 太长时summary_prompt=[{"role":"system","content":"请用一句话总结以下对话的核心内容:"},*old_messages]summary=call_llm(summary_prompt)# 替换历史为摘要 + 最近几轮new_messages=[{"role":"system","content":f"之前的对话摘要:{summary}"},*recent_messages# 保留最近2-3轮]这样做可以保留早期对话的语义,节省大量tokens,但需额外调用一次模型,增加了成本和延迟
- 选择性保留:只保留关键信息,比如用户明确要求记住的内容,任务相关的上下文(文件内容,代码片断等),忽略寒暄,重复提问等不重要信息,比如
KEYWORDS=["记住","注意","重要","配置","代码"]essential=[msgformsginmessagesifany(kwinmsg["content"]forkwinKEYWORDS)]这种方法适合 Agent 或工具调用等场景,但需要设计过滤规则
- 外部存储(高级方案):通过向量数据库存储历史,每次只检索相关片断注入
system或user
# 伪代码relevant_snippets=vector_db.search(query=current_question)context="\n".join(relevant_snippets)messages=[{"role":"system","content":f"相关背景:{context}"},{"role":"user","content":current_question}]这种方案可支持无限长对话,但架构复杂,适合企业级应用
在实际使用中,可按场景对策略进行选择,比如
| 场景 | 推荐策略 |
|---|---|
| 普通聊天机器人 | 滑动窗口(保留最近 3 ~ 5 轮) |
| 编程助手(OpenCoder) | 选择性保留(只保留代码或错误信息等) |
| 客服,任务型 Agent | 总结摘要 + 关键信息保留 |
| 长期记忆需求(如个人助理) | 外部存储方案 |
多轮对话会累积token,但通过滑动窗口等策略,可以低成本实现高效上下文管理,对于 90% 的应用场景,保留最近 3~5 轮对话就足够了,既能控制成本,又能保证连贯性
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog
【Agent】【OpenCode】介绍