Skip to content

Commit c67e8df

Browse files
authored
Merge pull request #19 from jihe520/dev
fix bug
2 parents 30db251 + 57b7f55 commit c67e8df

File tree

8 files changed

+400
-17
lines changed

8 files changed

+400
-17
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272

7373

7474
提供三种部署方式,请选择最适合你的方案:
75-
1. docker(最简单)
76-
2. 本地部署
77-
3. 脚本本地部署(社区)
75+
1. [docker(最简单)](#-方案一docker-部署推荐最简单)
76+
2. [本地部署](#-方案二-本地部署)
77+
3. [脚本本地部署(社区)](#-方案三自动脚本部署来自社区)
7878

7979

8080
下载项目
@@ -204,6 +204,7 @@ Thanks to the following projects:
204204
- [Code-Interpreter](https://github.com/MrGreyfun/Local-Code-Interpreter/tree/main)
205205
- [Latex](https://github.com/Veni222987/MathModelingLatexTemplate/tree/main)
206206
- [Agent Laboratory](https://github.com/SamuelSchmidgall/AgentLaboratory)
207+
- [ai-manus](https://github.com/Simpleyyt/ai-manus)
207208

208209
## 其他
209210

backend/app/core/agents/agent.py

Lines changed: 169 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from app.core.llm.llm import LLM, simple_chat
22
from app.utils.log_util import logger
3+
from icecream import ic
4+
5+
# TODO: Memory 的管理
6+
# TODO: 评估任务完成情况,rethinking
37

48

59
class Agent:
@@ -8,7 +12,7 @@ def __init__(
812
task_id: str,
913
model: LLM,
1014
max_chat_turns: int = 30, # 单个agent最大对话轮次
11-
max_memory: int = 25, # 最大记忆轮次
15+
max_memory: int = 12, # 最大记忆轮次
1216
) -> None:
1317
self.task_id = task_id
1418
self.model = model
@@ -51,14 +55,26 @@ async def run(self, prompt: str, system_prompt: str, sub_title: str) -> str:
5155
return error_msg
5256

5357
async def append_chat_history(self, msg: dict) -> None:
54-
await self.clear_memory()
58+
ic(f"添加消息: role={msg.get('role')}, 当前历史长度={len(self.chat_history)}")
5559
self.chat_history.append(msg)
60+
ic(f"添加后历史长度={len(self.chat_history)}")
61+
62+
# 只有在添加非tool消息时才进行内存清理,避免在工具调用期间破坏消息结构
63+
if msg.get("role") != "tool":
64+
ic("触发内存清理")
65+
await self.clear_memory()
66+
else:
67+
ic("跳过内存清理(tool消息)")
5668

5769
async def clear_memory(self):
5870
"""当聊天历史超过最大记忆轮次时,使用 simple_chat 进行总结压缩"""
71+
ic(f"检查内存清理: 当前={len(self.chat_history)}, 最大={self.max_memory}")
72+
5973
if len(self.chat_history) <= self.max_memory:
74+
ic("无需清理内存")
6075
return
6176

77+
ic("开始内存清理")
6278
logger.info(
6379
f"{self.__class__.__name__}:开始清除记忆,当前记录数:{len(self.chat_history)}"
6480
)
@@ -71,16 +87,21 @@ async def clear_memory(self):
7187
else None
7288
)
7389

74-
# 构造总结提示
75-
summarize_history = []
76-
if system_msg:
77-
summarize_history.append(system_msg)
90+
# 查找需要保留的消息范围 - 保留最后几条完整的对话和工具调用
91+
preserve_start_idx = self._find_safe_preserve_point()
92+
ic(f"保留起始索引: {preserve_start_idx}")
7893

79-
# 添加要总结的对话内容(跳过第一条系统消息和最后5条消息)
94+
# 确定需要总结的消息范围
8095
start_idx = 1 if system_msg else 0
81-
end_idx = len(self.chat_history) - 5
96+
end_idx = preserve_start_idx
97+
ic(f"总结范围: {start_idx} -> {end_idx}")
8298

8399
if end_idx > start_idx:
100+
# 构造总结提示
101+
summarize_history = []
102+
if system_msg:
103+
summarize_history.append(system_msg)
104+
84105
summarize_history.append(
85106
{
86107
"role": "user",
@@ -91,7 +112,7 @@ async def clear_memory(self):
91112
# 调用 simple_chat 进行总结
92113
summary = await simple_chat(self.model, summarize_history)
93114

94-
# 重构聊天历史:系统消息 + 总结 + 最后5条消息
115+
# 重构聊天历史:系统消息 + 总结 + 保留的消息
95116
new_history = []
96117
if system_msg:
97118
new_history.append(system_msg)
@@ -100,10 +121,11 @@ async def clear_memory(self):
100121
{"role": "assistant", "content": f"[历史对话总结] {summary}"}
101122
)
102123

103-
# 添加最后5条消息
104-
new_history.extend(self.chat_history[-5:])
124+
# 添加需要保留的消息(最后几条完整对话)
125+
new_history.extend(self.chat_history[preserve_start_idx:])
105126

106127
self.chat_history = new_history
128+
ic(f"内存清理完成,新历史长度: {len(self.chat_history)}")
107129
logger.info(
108130
f"{self.__class__.__name__}:记忆清除完成,压缩至:{len(self.chat_history)}条记录"
109131
)
@@ -112,8 +134,142 @@ async def clear_memory(self):
112134

113135
except Exception as e:
114136
logger.error(f"记忆清除失败,使用简单切片策略: {str(e)}")
115-
# 如果总结失败,回退到简单的切片策略
116-
self.chat_history = self.chat_history[:2] + self.chat_history[-5:]
137+
# 如果总结失败,回退到安全的策略:保留系统消息和最后几条消息,确保工具调用完整性
138+
safe_history = self._get_safe_fallback_history()
139+
self.chat_history = safe_history
140+
141+
def _find_safe_preserve_point(self) -> int:
142+
"""找到安全的保留起始点,确保不会破坏工具调用序列"""
143+
# 最少保留最后3条消息,确保基本对话完整性
144+
min_preserve = min(3, len(self.chat_history))
145+
preserve_start = len(self.chat_history) - min_preserve
146+
ic(
147+
f"寻找安全保留点: 历史长度={len(self.chat_history)}, 最少保留={min_preserve}, 开始位置={preserve_start}"
148+
)
149+
150+
# 从后往前查找,确保不会在工具调用序列中间切断
151+
for i in range(preserve_start, -1, -1):
152+
if i >= len(self.chat_history):
153+
continue
154+
155+
# 检查从这个位置开始是否是安全的(没有孤立的tool消息)
156+
is_safe = self._is_safe_cut_point(i)
157+
ic(f"检查位置 {i}: 安全={is_safe}")
158+
if is_safe:
159+
ic(f"找到安全保留点: {i}")
160+
return i
161+
162+
# 如果找不到安全点,至少保留最后1条消息
163+
fallback = len(self.chat_history) - 1
164+
ic(f"未找到安全点,使用备用位置: {fallback}")
165+
return fallback
166+
167+
def _is_safe_cut_point(self, start_idx: int) -> bool:
168+
"""检查从指定位置开始切割是否安全(不会产生孤立的tool消息)"""
169+
if start_idx >= len(self.chat_history):
170+
ic(f"切割点 {start_idx} >= 历史长度,安全")
171+
return True
172+
173+
# 检查切割后的消息序列是否有孤立的tool消息
174+
tool_messages = []
175+
for i in range(start_idx, len(self.chat_history)):
176+
msg = self.chat_history[i]
177+
if isinstance(msg, dict) and msg.get("role") == "tool":
178+
tool_call_id = msg.get("tool_call_id")
179+
tool_messages.append((i, tool_call_id))
180+
ic(f"发现tool消息在位置 {i}, tool_call_id={tool_call_id}")
181+
182+
# 向前查找对应的tool_calls消息
183+
if tool_call_id:
184+
found_tool_call = False
185+
for j in range(start_idx, i):
186+
prev_msg = self.chat_history[j]
187+
if (
188+
isinstance(prev_msg, dict)
189+
and "tool_calls" in prev_msg
190+
and prev_msg["tool_calls"]
191+
):
192+
for tool_call in prev_msg["tool_calls"]:
193+
if tool_call.get("id") == tool_call_id:
194+
found_tool_call = True
195+
ic(f"找到对应的tool_call在位置 {j}")
196+
break
197+
if found_tool_call:
198+
break
199+
200+
if not found_tool_call:
201+
ic(
202+
f"❌ tool消息 {tool_call_id} 没有找到对应的tool_call,切割点不安全"
203+
)
204+
return False
205+
206+
ic(f"切割点 {start_idx} 安全,检查了 {len(tool_messages)} 个tool消息")
207+
return True
208+
209+
def _get_safe_fallback_history(self) -> list:
210+
"""获取安全的后备历史记录,确保不会有孤立的tool消息"""
211+
if not self.chat_history:
212+
return []
213+
214+
# 保留系统消息
215+
safe_history = []
216+
if self.chat_history and self.chat_history[0]["role"] == "system":
217+
safe_history.append(self.chat_history[0])
218+
219+
# 从后往前查找安全的消息序列
220+
for preserve_count in range(1, min(4, len(self.chat_history)) + 1):
221+
start_idx = len(self.chat_history) - preserve_count
222+
if self._is_safe_cut_point(start_idx):
223+
safe_history.extend(self.chat_history[start_idx:])
224+
return safe_history
225+
226+
# 如果都不安全,只保留最后一条非tool消息
227+
for i in range(len(self.chat_history) - 1, -1, -1):
228+
msg = self.chat_history[i]
229+
if isinstance(msg, dict) and msg.get("role") != "tool":
230+
safe_history.append(msg)
231+
break
232+
233+
return safe_history
234+
235+
def _find_last_unmatched_tool_call(self) -> int | None:
236+
"""查找最后一个未匹配的tool call的索引"""
237+
ic("开始查找未匹配的tool_call")
238+
239+
# 从后往前查找,寻找没有对应tool response的tool call
240+
for i in range(len(self.chat_history) - 1, -1, -1):
241+
msg = self.chat_history[i]
242+
243+
# 检查是否是包含tool_calls的消息
244+
if isinstance(msg, dict) and "tool_calls" in msg and msg["tool_calls"]:
245+
ic(f"在位置 {i} 发现tool_calls消息")
246+
247+
# 检查每个tool call是否都有对应的response
248+
for tool_call in msg["tool_calls"]:
249+
tool_call_id = tool_call.get("id")
250+
ic(f"检查tool_call_id: {tool_call_id}")
251+
252+
if tool_call_id:
253+
# 在后续消息中查找对应的tool response
254+
response_found = False
255+
for j in range(i + 1, len(self.chat_history)):
256+
response_msg = self.chat_history[j]
257+
if (
258+
isinstance(response_msg, dict)
259+
and response_msg.get("role") == "tool"
260+
and response_msg.get("tool_call_id") == tool_call_id
261+
):
262+
ic(f"找到匹配的tool响应在位置 {j}")
263+
response_found = True
264+
break
265+
266+
if not response_found:
267+
# 找到未匹配的tool call
268+
ic(f"❌ 发现未匹配的tool_call在位置 {i}, id={tool_call_id}")
269+
return i
270+
271+
ic("没有发现未匹配的tool_call")
272+
return None
117273

118274
def _format_history_for_summary(self, history: list[dict]) -> str:
119275
"""格式化历史记录用于总结"""

backend/app/core/agents/coder_agent.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
from app.core.functions import coder_tools
1414
from icecream import ic
1515

16+
# TODO: 时间等待过久,stop 进程
17+
# TODO: 支持 cuda
18+
# TODO: 引入创新方案:
19+
1620

1721
# 代码强
1822
class CoderAgent(Agent): # 同样继承自Agent类

backend/app/core/agents/modeler_agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import json
77
from icecream import ic
88

9+
# TODO: 提问工具tool
10+
911

1012
class ModelerAgent(Agent): # 继承自Agent类
1113
def __init__(

0 commit comments

Comments
 (0)