feat: 增强 core/agents 工具和 API

- 新增 loop.py Agent 运行循环
- 优化 memory.py 记忆模块
- 扩展 api/routes.py 接口
- 更新 tools 模块:builtin.py, manager.py, __init__.py
- 新增 .env.example 配置示例
- 更新 requirements.txt 依赖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 19:49:40 +08:00
parent 31f0feafb5
commit 1afa88e812
8 changed files with 231 additions and 17 deletions

View File

@@ -79,6 +79,18 @@ class AgentLoop:
"""
history = history or []
# Load history from session if session_key is provided
if session_key and session_key != "default":
loaded_history = self.memory.get_history(session_key, max_messages=20)
if loaded_history:
logger.info(f"Loaded {len(loaded_history)} messages from session history")
# Merge loaded history with provided history (loaded takes precedence if empty)
if not history:
history = loaded_history
else:
# Append loaded history before current messages
history = loaded_history + history
# Check if dynamic provider parameters are provided
if api_key or model_provider:
logger.info(f"Using dynamic provider: model_provider={model_provider}, model_name={model_name}, base_url={base_url}")
@@ -142,6 +154,19 @@ class AgentLoop:
Agent response content
"""
history = history or []
# Load history from session if session_key is provided
if session_key and session_key != "default":
loaded_history = self.memory.get_history(session_key, max_messages=20)
if loaded_history:
logger.info(f"Loaded {len(loaded_history)} messages from session history")
# Merge loaded history with provided history (loaded takes precedence if empty)
if not history:
history = loaded_history
else:
# Append loaded history before current messages
history = loaded_history + history
provider = provider or self.provider
model = model or self.model
@@ -191,6 +216,18 @@ class AgentLoop:
"""
history = history or []
# Load history from session if session_key is provided
if session_key and session_key != "default":
loaded_history = self.memory.get_history(session_key, max_messages=20)
if loaded_history:
logger.info(f"[stream] Loaded {len(loaded_history)} messages from session history")
# Merge loaded history with provided history (loaded takes precedence if empty)
if not history:
history = loaded_history
else:
# Append loaded history before current messages
history = loaded_history + history
# Check if dynamic provider parameters are provided
if api_key or model_provider:
logger.info(f"[stream] Using dynamic provider: model_provider={model_provider}, model_name={model_name}, base_url={base_url}")
@@ -244,6 +281,19 @@ class AgentLoop:
Response content chunks
"""
history = history or []
# Load history from session if session_key is provided
if session_key and session_key != "default":
loaded_history = self.memory.get_history(session_key, max_messages=20)
if loaded_history:
logger.info(f"[stream] Loaded {len(loaded_history)} messages from session history")
# Merge loaded history with provided history (loaded takes precedence if empty)
if not history:
history = loaded_history
else:
# Append loaded history before current messages
history = loaded_history + history
provider = provider or self.provider
model = model or self.model
@@ -461,3 +511,19 @@ class AgentLoop:
self.memory.add_to_history("user", str(content)[:1000], session_key)
elif role == "assistant" and content:
self.memory.add_to_history("assistant", str(content)[:1000], session_key)
# Save tool_calls for assistant messages (needed for multi-turn tool calls)
elif role == "assistant" and m.get("tool_calls"):
# Save the assistant message with tool_calls
tool_calls_str = json.dumps(m.get("tool_calls", []))
self.memory.add_to_history("assistant", f"[tool_calls]{tool_calls_str}", session_key)
# Save tool results (needed for multi-turn conversations)
elif role == "tool":
tool_call_id = m.get("tool_call_id", "")
tool_name = m.get("name", "")
tool_content = m.get("content", "")
tool_result_str = json.dumps({
"tool_call_id": tool_call_id,
"name": tool_name,
"content": tool_content
})
self.memory.add_to_history("tool", f"[tool_result]{tool_result_str}", session_key)

View File

@@ -537,8 +537,25 @@ class AgentMemory:
except:
pass
# Check if content contains tool_calls or tool_result markers
# Format as Markdown (产品经理指定格式)
entry = f"## 消息 {msg_count}\n角色: {role}\n时间: {display_timestamp}\n内容: {content}\n\n"
entry_lines = [
f"## 消息 {msg_count}",
f"角色: {role}",
f"时间: {display_timestamp}",
]
# Handle tool_calls and tool_result content
if content.startswith("[tool_calls]"):
entry_lines.append(f"工具调用: {content[len('[tool_calls]'):]}")
entry_lines.append(f"内容: ")
elif content.startswith("[tool_result]"):
entry_lines.append(f"工具结果: {content[len('[tool_result]'):]}")
entry_lines.append(f"内容: ")
else:
entry_lines.append(f"内容: {content}")
entry = "\n".join(entry_lines) + "\n\n"
with open(session_file, "a", encoding="utf-8") as f:
if header:
@@ -610,6 +627,27 @@ class AgentMemory:
current_message["timestamp"] = line.split(":", 1)[1].strip()
continue
# Parse "工具调用: xxx" - for tool_calls
if line.startswith("工具调用:") and current_message is not None:
tool_calls_json = line.split(":", 1)[1].strip()
try:
current_message["tool_calls"] = json.loads(tool_calls_json)
except json.JSONDecodeError:
pass
continue
# Parse "工具结果: xxx" - for tool_result
if line.startswith("工具结果:") and current_message is not None:
tool_result_json = line.split(":", 1)[1].strip()
try:
tool_result = json.loads(tool_result_json)
current_message["tool_call_id"] = tool_result.get("tool_call_id", "")
current_message["name"] = tool_result.get("name", "")
current_message["content"] = tool_result.get("content", "")
except json.JSONDecodeError:
pass
continue
# Parse "内容: xxx"
if line.startswith("内容:") and current_message is not None:
current_message["content"] = line.split(":", 1)[1].strip()
@@ -617,7 +655,7 @@ class AgentMemory:
# Content line
if current_message:
if current_message["content"]:
if current_message.get("content"):
current_message["content"] += "\n" + line
else:
current_message["content"] = line