13、【AI】【Agent】联网使用大模型(多轮对话&tokens)
【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如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条消息)
def add_message(messages, role, content):
messages.append({"role": role, "content": content})
# 确保 user/assistant 成对,所以最多保留 MAX_TURNS * 2 条
if len(messages) > MAX_TURNS * 2:
# 丢弃最早的两条(一轮)
messages = messages[2:]
return messages
这样做的话简单高效,但是会忘记早期的重要信息
- 总结摘要(适合长对话):当历史太长时,让 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 = [msg for msg in messages if any(kw in msg["content"] for kw in KEYWORDS)]
这种方法适合 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】介绍
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)