跳到主要内容

多轮对话的 messages 数组演变

本文档展示 3 轮对话中 messages 数组的完整演变过程。

场景设定

  • 第 1 轮: 用户"你好" → AI 回复
  • 第 2 轮: 用户"读取 README.md" → AI 调用工具 → AI 回复
  • 第 3 轮: 用户"谢谢" → AI 回复

第 1 轮对话

用户输入

你好

发送给 LLM 的 messages(第 1 轮)

[
{
"role": "system",
"content": "# 核心身份\n\n你的名字是\"小C\",运行在 CountBot框架内的专用智能助手...(完整系统提示词)"
},
{
"role": "user",
"content": "你好"
}
]

LLM 响应

{
"role": "assistant",
"content": "你好!很高兴见到你!有什么我可以帮助你的吗?"
}

保存到数据库

-- messages 表
INSERT INTO messages (session_id, role, content, created_at) VALUES
('550e8400-e29b-41d4-a716-446655440000', 'user', '你好', '2024-01-15 10:30:00'),
('550e8400-e29b-41d4-a716-446655440000', 'assistant', '你好!很高兴见到你!有什么我可以帮助你的吗?', '2024-01-15 10:30:02');

第 2 轮对话

用户输入

读取 README.md 文件

获取历史消息

# backend/api/chat.py - send_message()
context = await session_manager.get_history_with_summary(
session_id=request.session_id,
limit=None if max_history == -1 else max_history
)

返回的 context:

[
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!很高兴见到你!有什么我可以帮助你的吗?"}
]

发送给 LLM 的 messages(第 2 轮 - 第 1 次调用)

[
{
"role": "system",
"content": "# 核心身份\n\n你的名字是\"小C\"...(完整系统提示词)"
},
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content": "你好!很高兴见到你!有什么我可以帮助你的吗?"
},
{
"role": "user",
"content": "读取 README.md 文件"
}
]

LLM 响应(包含 tool_calls)

{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\":\"README.md\",\"show_line_numbers\":true}"
}
}
]
}

执行工具

# backend/modules/tools/registry.py - execute()
result = await self.tools.execute(
tool_name="read_file",
arguments={"path": "README.md", "show_line_numbers": True}
)

工具结果:

[File: README.md | Lines: 150]
1| # CountBot
2|
3| CountBot 是一个基于 LLM 的智能助手框架。
4|
5| ## 功能特性
6|
7| - 多模型支持(OpenAI、Claude、国产大模型)
8| - 工具调用系统
9| - 记忆管理
10| - 技能系统
... (省略后续内容)

发送给 LLM 的 messages(第 2 轮 - 第 2 次调用)

[
{
"role": "system",
"content": "# 核心身份\n\n你的名字是\"小C\"...(完整系统提示词)"
},
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content": "你好!很高兴见到你!有什么我可以帮助你的吗?"
},
{
"role": "user",
"content": "读取 README.md 文件"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\":\"README.md\",\"show_line_numbers\":true}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "read_file",
"content": "[File: README.md | Lines: 150]\n 1| # CountBot\n 2| \n 3| CountBot 是一个基于 LLM 的智能助手框架..."
}
]

LLM 最终响应

{
"role": "assistant",
"content": "我已经读取了 README.md 文件。这是 CountBot 项目的说明文档,主要介绍了以下内容:\n\n1. **功能特性**:\n - 多模型支持(OpenAI、Claude、国产大模型)\n - 工具调用系统\n - 记忆管理\n - 技能系统\n - 多渠道接入(Web、钉钉、Telegram)\n\n2. **快速开始**:提供了安装和运行命令\n\n3. **配置说明**:需要编辑 config.yaml 文件\n\n需要我详细解释某个部分吗?"
}

保存到数据库

-- messages 表
INSERT INTO messages (session_id, role, content, created_at) VALUES
('550e8400-e29b-41d4-a716-446655440000', 'user', '读取 README.md 文件', '2024-01-15 10:31:00'),
('550e8400-e29b-41d4-a716-446655440000', 'assistant', '我已经读取了 README.md 文件。这是 CountBot 项目的说明文档...', '2024-01-15 10:31:05');

-- tool_conversations 表
INSERT INTO tool_conversations (id, session_id, tool_name, arguments, result, duration_ms, timestamp, user_message, message_id) VALUES
('tc_789xyz', '550e8400-e29b-41d4-a716-446655440000', 'read_file', '{"path":"README.md","show_line_numbers":true}', '[File: README.md | Lines: 150]...', 45, '2024-01-15 10:31:02', '读取 README.md 文件', 4);

第 3 轮对话

用户输入

谢谢

获取历史消息

context = await session_manager.get_history_with_summary(
session_id=request.session_id,
limit=None if max_history == -1 else max_history
)

返回的 context:

[
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!很高兴见到你!有什么我可以帮助你的吗?"},
{"role": "user", "content": "读取 README.md 文件"},
{"role": "assistant", "content": "我已经读取了 README.md 文件。这是 CountBot 项目的说明文档..."}
]

注意: 工具调用的 assistanttool 消息不会保存到数据库,只保存最终的用户消息和 AI 回复。

发送给 LLM 的 messages(第 3 轮)

[
{
"role": "system",
"content": "# 核心身份\n\n你的名字是\"小C\"...(完整系统提示词)"
},
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content": "你好!很高兴见到你!有什么我可以帮助你的吗?"
},
{
"role": "user",
"content": "读取 README.md 文件"
},
{
"role": "assistant",
"content": "我已经读取了 README.md 文件。这是 CountBot 项目的说明文档,主要介绍了以下内容:\n\n1. **功能特性**:\n - 多模型支持(OpenAI、Claude、国产大模型)\n - 工具调用系统\n - 记忆管理\n - 技能系统\n - 多渠道接入(Web、钉钉、Telegram)\n\n2. **快速开始**:提供了安装和运行命令\n\n3. **配置说明**:需要编辑 config.yaml 文件\n\n需要我详细解释某个部分吗?"
},
{
"role": "user",
"content": "谢谢"
}
]

LLM 响应

{
"role": "assistant",
"content": "不客气!如果你在使用 CountBot 的过程中遇到任何问题,或者需要进一步的帮助,随时告诉我。祝你使用愉快!😊"
}

保存到数据库

-- messages 表
INSERT INTO messages (session_id, role, content, created_at) VALUES
('550e8400-e29b-41d4-a716-446655440000', 'user', '谢谢', '2024-01-15 10:32:00'),
('550e8400-e29b-41d4-a716-446655440000', 'assistant', '不客气!如果你在使用 CountBot 的过程中遇到任何问题...', '2024-01-15 10:32:01');

历史消息限制

配置

# config.yaml
persona:
max_history_messages: 50 # 最多保留 50 条历史消息,-1 表示不限制

滚动窗口机制

当历史消息超过 max_history_messages 时,会触发滚动窗口截断:

# backend/api/chat.py - send_message()
if max_history > 0:
await session_manager.summarize_overflow(
session_id=request.session_id,
max_history=max_history,
provider=agent_loop.provider,
model=agent_loop.model,
memory_store=_overflow_memory,
)

滚动窗口流程

  1. 检测溢出: 如果消息数 > max_history,触发总结
  2. 总结旧消息: 将最旧的消息(超出窗口的部分)总结为记忆条目
  3. 写入记忆: 保存到 MEMORY.md 文件
  4. 删除旧消息: 从数据库删除已总结的消息
  5. 保留窗口: 只保留最近的 max_history 条消息

总结提示词

# backend/modules/agent/prompts.py - OVERFLOW_SUMMARY_PROMPT
OVERFLOW_SUMMARY_PROMPT = """你是一个对话总结器。以下是一段即将被截断的旧对话历史,请将其中有长期价值的信息总结为简洁的记忆条目。

要求:
1. 输出格式: 一行文本,多个事项用中文分号(;)分隔
2. 只记录有长期价值的事实信息
3. 不要记录闲聊、一次性查询结果
4. 每个事项必须包含具体信息
5. 如果对话没有值得长期记录的信息,输出: 无需记录

对话内容:
{messages}

输出(一行,事项用;分隔):"""

示例

假设 max_history=50,当前有 55 条消息:

# 消息 1-5(最旧的 5 条)会被总结
old_messages = [
{"role": "user", "content": "我的邮箱是 user@example.com"},
{"role": "assistant", "content": "好的,我记住了你的邮箱是 user@example.com"},
{"role": "user", "content": "我喜欢用 Python 开发"},
{"role": "assistant", "content": "了解,你偏好使用 Python"},
{"role": "user", "content": "今天天气怎么样"},
]

# LLM 总结为记忆条目
summary = "用户邮箱: user@example.com;偏好编程语言: Python"

# 写入 MEMORY.md
# 2024-01-15|web-chat|用户邮箱: user@example.com;偏好编程语言: Python

# 删除消息 1-5,保留消息 6-55

会话总结注入

手动创建会话总结

PUT /api/chat/sessions/550e8400-e29b-41d4-a716-446655440000/summary HTTP/1.1
Content-Type: application/json

{
"summary": "这是一个关于 CountBot 项目开发的会话。用户正在学习如何使用工具系统。"
}

总结注入到系统提示词

# backend/modules/agent/context.py - build_messages()
if session_summary:
system_prompt += f"\n\n## Current Session Context\n{session_summary}"

生成的系统提示词(末尾追加):

## Current Session Context
这是一个关于 CountBot 项目开发的会话。用户正在学习如何使用工具系统。

发送给 LLM 的 messages(包含会话总结)

[
{
"role": "system",
"content": "# 核心身份\n\n你的名字是\"小C\"...(完整系统提示词)\n\n## Current Session Context\n这是一个关于 CountBot 项目开发的会话。用户正在学习如何使用工具系统。"
},
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content": "你好!很高兴见到你!有什么我可以帮助你的吗?"
},
{
"role": "user",
"content": "读取 README.md 文件"
},
{
"role": "assistant",
"content": "我已经读取了 README.md 文件..."
},
{
"role": "user",
"content": "谢谢"
}
]

自动记忆写入

触发条件

当会话满足以下任一条件时,自动触发记忆写入:

  1. 消息数 >= 30 条
  2. 总字符数 >= 15000

自动记忆流程

# backend/api/chat.py - _maybe_auto_summarize()
async def _maybe_auto_summarize(
session_id: str,
session_manager: SessionManager,
agent_loop: AgentLoop,
) -> None:
messages = await session_manager.get_messages(session_id=session_id)
msg_count = len(messages)
total_chars = sum(len(msg.content or "") for msg in messages)

# 检查阈值
if msg_count >= 30 or total_chars >= 15000:
# 格式化消息
formatted = analyzer.format_messages_for_summary(message_dicts, max_chars=4000)

# 调用 LLM 总结
prompt = CONVERSATION_TO_MEMORY_PROMPT.format(messages=formatted)
summary = await agent_loop.provider.chat_stream(...)

# 写入记忆
if "无需记录" not in summary:
memory.append_entry(source="web-chat", content=summary)

记忆格式

# MEMORY.md
2024-01-15|web-chat|用户学习了 CountBot 的工具系统,包括 read_file、write_file 的使用方法;用户邮箱: user@example.com

完整流程图

第 1 轮:
用户: "你好"

messages: [system, user]

LLM: "你好!很高兴见到你!"

保存: user + assistant

第 2 轮:
用户: "读取 README.md"

获取历史: [user, assistant]

messages: [system, user(1), assistant(1), user(2)]

LLM: tool_calls [read_file]

执行工具: read_file

messages: [system, user(1), assistant(1), user(2), assistant(tool_calls), tool(result)]

LLM: "我已经读取了 README.md..."

保存: user + assistant + tool_conversation

第 3 轮:
用户: "谢谢"

获取历史: [user(1), assistant(1), user(2), assistant(2)]

messages: [system, user(1), assistant(1), user(2), assistant(2), user(3)]

LLM: "不客气!..."

保存: user + assistant

检查自动记忆: 消息数 < 30,跳过

关键要点

  1. 系统提示词: 每次调用都会重新构建,包含最新的环境信息
  2. 历史消息: 从数据库加载,受 max_history_messages 限制
  3. 工具调用: 不保存到数据库,只在当前对话中使用
  4. 滚动窗口: 超出限制的旧消息会被总结并写入记忆
  5. 会话总结: 手动创建,注入到系统提示词末尾
  6. 自动记忆: 达到阈值时自动触发,写入 MEMORY.md