Compare commits
3 Commits
main
...
598d45a39d
| Author | SHA1 | Date |
|---|---|---|
|
|
598d45a39d | |
|
|
d496a1beac | |
|
|
1a09129395 |
|
|
@ -0,0 +1 @@
|
||||||
|
"""__init__.py"""
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
"""客户端:用户交互 & 会话管理"""
|
||||||
|
"""
|
||||||
|
client/agent_client.py
|
||||||
|
Agent 客户端:协调 LLM 引擎、MCP 服务器、记忆模块,驱动完整 Agent 执行流程
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from llm.llm_engine import LLMEngine
|
||||||
|
from mcp.mcp_protocol import MCPMethod, MCPRequest
|
||||||
|
from mcp.mcp_server import MCPServer
|
||||||
|
from memory.memory_store import MemoryStore
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
# ── 单轮执行结果 ───────────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class AgentResponse:
|
||||||
|
"""一次完整 Agent 调用的结果"""
|
||||||
|
user_input: str
|
||||||
|
final_reply: str
|
||||||
|
tool_used: str | None = None
|
||||||
|
tool_output: str | None = None
|
||||||
|
success: bool = True
|
||||||
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# ── Agent 客户端 ───────────────────────────────────────────────
|
||||||
|
class AgentClient:
|
||||||
|
"""
|
||||||
|
Agent 客户端:实现完整的 ReAct 执行循环
|
||||||
|
|
||||||
|
执行流程 (5步):
|
||||||
|
1. [CLIENT] 接收用户输入,写入 Memory
|
||||||
|
2. [LLM] 分析意图,决策是否调用工具
|
||||||
|
3. [MCP] 构造 JSON-RPC 请求,发送给 MCP Server
|
||||||
|
4. [TOOL] MCP Server 执行工具,返回结果
|
||||||
|
5. [LLM] 整合结果,生成最终回复,写入 Memory
|
||||||
|
|
||||||
|
使用示例:
|
||||||
|
client = AgentClient(llm=llm, mcp_server=mcp, memory=memory)
|
||||||
|
response = client.chat("帮我计算 100 * 200")
|
||||||
|
print(response.final_reply)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
llm: LLMEngine,
|
||||||
|
mcp_server: MCPServer,
|
||||||
|
memory: MemoryStore,
|
||||||
|
):
|
||||||
|
self.llm = llm
|
||||||
|
self.mcp_server = mcp_server
|
||||||
|
self.memory = memory
|
||||||
|
self.logger = get_logger("CLIENT")
|
||||||
|
self.logger.info("💻 Agent Client 初始化完成")
|
||||||
|
|
||||||
|
# ── 主入口 ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def chat(self, user_input: str) -> AgentResponse:
|
||||||
|
"""
|
||||||
|
处理一轮用户对话,执行完整 Agent 流程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_input: 用户输入的自然语言文本
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AgentResponse 实例
|
||||||
|
"""
|
||||||
|
self.logger.info(f"{'='*55}")
|
||||||
|
self.logger.info(f"📨 Step 1 [CLIENT] 收到用户输入: {user_input}")
|
||||||
|
|
||||||
|
# ── Step 1: 记录用户消息 ────────────────────────────────
|
||||||
|
self.memory.add_user_message(user_input)
|
||||||
|
context = self.memory.get_context_summary()
|
||||||
|
|
||||||
|
# ── Step 2: LLM 推理决策 ────────────────────────────────
|
||||||
|
self.logger.info("🧠 Step 2 [LLM] 开始推理,分析意图...")
|
||||||
|
tool_schemas = self.mcp_server.get_tool_schemas()
|
||||||
|
decision = self.llm.think_and_decide(user_input, tool_schemas, context)
|
||||||
|
|
||||||
|
# ── 分支:是否需要工具 ──────────────────────────────────
|
||||||
|
if not decision.need_tool:
|
||||||
|
return self._handle_direct_reply(user_input, context)
|
||||||
|
|
||||||
|
return self._handle_tool_call(user_input, decision, context)
|
||||||
|
|
||||||
|
# ── 私有流程方法 ────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _handle_direct_reply(self, user_input: str, context: str) -> AgentResponse:
|
||||||
|
"""无需工具时直接生成回复"""
|
||||||
|
self.logger.info("💬 无需工具,直接生成回复")
|
||||||
|
reply = self.llm.generate_direct_reply(user_input, context)
|
||||||
|
self.memory.add_assistant_message(reply)
|
||||||
|
return AgentResponse(user_input=user_input, final_reply=reply)
|
||||||
|
|
||||||
|
def _handle_tool_call(
|
||||||
|
self,
|
||||||
|
user_input: str,
|
||||||
|
decision,
|
||||||
|
context: str,
|
||||||
|
) -> AgentResponse:
|
||||||
|
"""执行工具调用的完整流程(Step 3 → 4 → 5)"""
|
||||||
|
|
||||||
|
# ── Step 3: 构造 MCP 请求 ───────────────────────────────
|
||||||
|
mcp_request: MCPRequest = decision.to_mcp_request()
|
||||||
|
self.logger.info(
|
||||||
|
f"📡 Step 3 [MCP] 发送工具调用请求\n"
|
||||||
|
f" 方法: {mcp_request.method}\n"
|
||||||
|
f" 工具: {decision.tool_name}\n"
|
||||||
|
f" 参数: {decision.arguments}\n"
|
||||||
|
f" 请求体: {mcp_request.to_dict()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Step 4: MCP Server 执行工具 ─────────────────────────
|
||||||
|
self.logger.info(f"🔧 Step 4 [TOOL] MCP Server 执行工具 [{decision.tool_name}]...")
|
||||||
|
mcp_response = self.mcp_server.handle_request(mcp_request)
|
||||||
|
|
||||||
|
if not mcp_response.success:
|
||||||
|
error_msg = f"工具调用失败: {mcp_response.error}"
|
||||||
|
self.logger.error(f"❌ {error_msg}")
|
||||||
|
return AgentResponse(
|
||||||
|
user_input=user_input,
|
||||||
|
final_reply=f"抱歉,工具调用失败:{mcp_response.error.get('message')}",
|
||||||
|
tool_used=decision.tool_name,
|
||||||
|
success=False,
|
||||||
|
error=error_msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_output = mcp_response.content
|
||||||
|
self.logger.info(f"✅ 工具执行成功,输出: {tool_output[:80]}...")
|
||||||
|
self.memory.add_tool_result(decision.tool_name, tool_output)
|
||||||
|
|
||||||
|
# ── Step 5: LLM 整合结果,生成最终回复 ──────────────────
|
||||||
|
self.logger.info("✍️ Step 5 [LLM] 整合工具结果,生成最终回复...")
|
||||||
|
final_reply = self.llm.generate_final_reply(
|
||||||
|
user_input, decision.tool_name, tool_output, context
|
||||||
|
)
|
||||||
|
self.memory.add_assistant_message(final_reply)
|
||||||
|
|
||||||
|
self.logger.info(f"🎉 [CLIENT] 流程完成,回复已返回")
|
||||||
|
return AgentResponse(
|
||||||
|
user_input=user_input,
|
||||||
|
final_reply=final_reply,
|
||||||
|
tool_used=decision.tool_name,
|
||||||
|
tool_output=tool_output,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 工具方法 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_memory_stats(self) -> dict:
|
||||||
|
"""获取当前记忆统计"""
|
||||||
|
return self.memory.stats()
|
||||||
|
|
||||||
|
def clear_session(self) -> None:
|
||||||
|
"""清空当前会话"""
|
||||||
|
self.memory.clear_history()
|
||||||
|
self.logger.info("🗑 会话已清空")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""__init__.py"""
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
"""LLM 引擎:意图理解 & 工具决策"""
|
||||||
|
"""
|
||||||
|
llm/llm_engine.py
|
||||||
|
LLM 引擎:负责意图理解、工具选择决策、最终回复生成
|
||||||
|
生产环境可替换 _call_llm_api() 为真实 API 调用(OpenAI / Anthropic 等)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from mcp.mcp_protocol import MCPRequest, MCPMethod, ToolSchema
|
||||||
|
from utils.logger import get_logger
|
||||||
|
from openai import OpenAI
|
||||||
|
|
||||||
|
|
||||||
|
# ── 工具调用决策结果 ───────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class ToolDecision:
|
||||||
|
"""LLM 决策是否调用工具及调用参数"""
|
||||||
|
need_tool: bool
|
||||||
|
tool_name: str = ""
|
||||||
|
arguments: dict = None
|
||||||
|
reasoning: str = "" # 推理过程说明
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.arguments = self.arguments or {}
|
||||||
|
|
||||||
|
def to_mcp_request(self) -> MCPRequest | None:
|
||||||
|
"""将工具决策转换为 MCP 请求"""
|
||||||
|
if not self.need_tool:
|
||||||
|
return None
|
||||||
|
return MCPRequest(
|
||||||
|
method=MCPMethod.TOOLS_CALL,
|
||||||
|
params={"name": self.tool_name, "arguments": self.arguments},
|
||||||
|
)
|
||||||
|
|
||||||
|
class MonicaClient:
|
||||||
|
|
||||||
|
BASE_URL = "https://openapi.monica.im/v1"
|
||||||
|
def __init__(self, api_key):
|
||||||
|
self.client = OpenAI(base_url=self.BASE_URL,
|
||||||
|
api_key=api_key)
|
||||||
|
def create(self, model_name, tool_schemas, user_input) -> ToolDecision:
|
||||||
|
tools = [{
|
||||||
|
"name": s.name,
|
||||||
|
"description": s.description,
|
||||||
|
"parameters": s.parameters} for s in tool_schemas]
|
||||||
|
completion = self.client.chat.completions.create(
|
||||||
|
model=model_name,
|
||||||
|
functions=tools,
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [{
|
||||||
|
"type": "text",
|
||||||
|
"text": user_input
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
response = json.loads(completion.choices[0].message.content)
|
||||||
|
return ToolDecision(need_tool=response['need_tool'],
|
||||||
|
tool_name=response['tool_name'],
|
||||||
|
arguments=response['arguments'],
|
||||||
|
reasoning=response['reasoning'])
|
||||||
|
|
||||||
|
# ── LLM 引擎 ──────────────────────────────────────────────────
|
||||||
|
class LLMEngine:
|
||||||
|
"""
|
||||||
|
LLM 推理引擎(ReAct 模式)
|
||||||
|
|
||||||
|
执行流程:
|
||||||
|
1. 接收用户输入 + 工具列表
|
||||||
|
2. 分析意图,决策是否调用工具(think)
|
||||||
|
3. 若需要工具,生成 MCPRequest(act)
|
||||||
|
4. 接收工具结果,生成最终回复(observe)
|
||||||
|
|
||||||
|
生产环境替换:
|
||||||
|
将 _call_llm_api() 替换为真实 LLM API 调用即可,
|
||||||
|
其余流程控制逻辑保持不变。
|
||||||
|
"""
|
||||||
|
|
||||||
|
API_KEY = "sk-AUmOuFI731Ty5Nob38jY26d8lydfDT-QkE2giqb0sCuPCAE2JH6zjLM4lZLpvL5WMYPOocaMe2FwVDmqM_9KimmKACjR"
|
||||||
|
def __init__(self, model_name: str = "claude-sonnet-4-6"):
|
||||||
|
self.model_name = model_name
|
||||||
|
self.logger = get_logger("LLM")
|
||||||
|
self.logger.info(f"🧠 LLM 引擎初始化,模型: {model_name}")
|
||||||
|
self.client = MonicaClient(api_key=self.API_KEY)
|
||||||
|
|
||||||
|
# ── 核心推理流程 ────────────────────────────────────────────
|
||||||
|
|
||||||
|
def think_and_decide(
|
||||||
|
self,
|
||||||
|
user_input: str,
|
||||||
|
tool_schemas: list[ToolSchema],
|
||||||
|
context: str = "",
|
||||||
|
) -> ToolDecision:
|
||||||
|
"""
|
||||||
|
Step 1 & 2: 理解意图,决策工具调用(Think 阶段)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_input: 用户输入文本
|
||||||
|
tool_schemas: 可用工具的 Schema 列表
|
||||||
|
context: 对话历史上下文摘要
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ToolDecision 实例
|
||||||
|
"""
|
||||||
|
self.logger.info(f"💭 分析意图: {user_input[:50]}...")
|
||||||
|
|
||||||
|
# 构造 Prompt(生产环境发送给真实 LLM)
|
||||||
|
prompt = self._build_decision_prompt(user_input, tool_schemas, context)
|
||||||
|
self.logger.debug(f"📝 Prompt 已构造 ({len(prompt)} chars)")
|
||||||
|
|
||||||
|
# 调用 LLM(Demo 中使用规则模拟)
|
||||||
|
# decision = self._call_llm_api(user_input, tool_schemas)
|
||||||
|
decision = self._call_llm_api(prompt, tool_schemas)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"🎯 决策结果: {'调用工具 [' + decision.tool_name + ']' if decision.need_tool else '直接回复'}"
|
||||||
|
)
|
||||||
|
self.logger.debug(f"💡 推理: {decision.reasoning}")
|
||||||
|
return decision
|
||||||
|
|
||||||
|
def generate_final_reply(
|
||||||
|
self,
|
||||||
|
user_input: str,
|
||||||
|
tool_name: str,
|
||||||
|
tool_output: str,
|
||||||
|
context: str = "",
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Step 5: 整合工具结果,生成最终自然语言回复(Observe 阶段)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_input: 原始用户输入
|
||||||
|
tool_name: 被调用的工具名称
|
||||||
|
tool_output: 工具返回的原始输出
|
||||||
|
context: 对话历史上下文
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
最终回复字符串
|
||||||
|
"""
|
||||||
|
self.logger.info("✍️ 整合工具结果,生成最终回复...")
|
||||||
|
|
||||||
|
# 生产环境:将 tool_output 注入 Prompt,调用 LLM 生成回复
|
||||||
|
reply = self._synthesize_reply(user_input, tool_name, tool_output)
|
||||||
|
self.logger.info(f"💬 回复已生成 ({len(reply)} chars)")
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def generate_direct_reply(self, user_input: str, context: str = "") -> str:
|
||||||
|
"""无需工具时直接生成回复"""
|
||||||
|
self.logger.info("💬 直接生成回复(无需工具)")
|
||||||
|
return f"[{self.model_name}] 您好!关于「{user_input}」,这是一个直接回复示例。\n(生产环境此处调用真实 LLM API)"
|
||||||
|
|
||||||
|
# ── Prompt 构造 ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _build_decision_prompt(
|
||||||
|
self,
|
||||||
|
user_input: str,
|
||||||
|
tool_schemas: list[ToolSchema],
|
||||||
|
context: str,
|
||||||
|
) -> str:
|
||||||
|
"""构造工具决策 Prompt(ReAct 格式)"""
|
||||||
|
tools_desc = "\n".join(
|
||||||
|
f"- {s.name}: {s.description}" for s in tool_schemas
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"你是一个智能助手,请分析用户输入并决定是否需要调用工具。\n\n"
|
||||||
|
f"## 可用工具\n{tools_desc}\n\n"
|
||||||
|
f"## 对话历史\n{context or '(无)'}\n\n"
|
||||||
|
f"## 用户输入\n{user_input}\n\n"
|
||||||
|
f"## 指令\n"
|
||||||
|
f"以 JSON 格式回复:\n"
|
||||||
|
f'{{"need_tool": true/false, "tool_name": "...", "arguments": {{...}}, "reasoning": "..."}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 模拟 LLM API(Demo 用规则引擎替代)────────────────────
|
||||||
|
|
||||||
|
def _call_llm_api(self, user_input: str, tool_schemas: list[ToolSchema]) -> ToolDecision:
|
||||||
|
"""
|
||||||
|
模拟 LLM API 调用(Demo 版本使用关键词规则)
|
||||||
|
|
||||||
|
生产环境替换示例:
|
||||||
|
import anthropic
|
||||||
|
client = anthropic.Anthropic()
|
||||||
|
response = client.messages.create(
|
||||||
|
model=self.model_name,
|
||||||
|
tools=[s.to_dict() for s in tool_schemas],
|
||||||
|
messages=[{"role": "user", "content": user_input}]
|
||||||
|
)
|
||||||
|
# 解析 response.content 中的 tool_use block
|
||||||
|
"""
|
||||||
|
return self.client.create(self.model_name, user_input=user_input, tool_schemas=tool_schemas)
|
||||||
|
|
||||||
|
text = user_input.lower()
|
||||||
|
|
||||||
|
# 规则匹配:计算器
|
||||||
|
calc_pattern = re.search(r"[\d\s\+\-\*\/\(\)\^]+[=??]?", user_input)
|
||||||
|
if any(kw in text for kw in ["计算", "等于", "多少", "×", "÷"]) and calc_pattern:
|
||||||
|
expr = re.sub(r"[^0-9+\-*/().**]", "", user_input.replace("×","*").replace("÷","/"))
|
||||||
|
return ToolDecision(
|
||||||
|
need_tool=True, tool_name="calculator",
|
||||||
|
arguments={"expression": expr or "1+1"},
|
||||||
|
reasoning="用户请求数学计算,调用 calculator 工具",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 规则匹配:搜索
|
||||||
|
if any(kw in text for kw in ["搜索", "查询", "天气", "新闻", "查一下", "search"]):
|
||||||
|
return ToolDecision(
|
||||||
|
need_tool=True, tool_name="web_search",
|
||||||
|
arguments={"query": user_input, "max_results": 3},
|
||||||
|
reasoning="用户需要实时信息,调用 web_search 工具",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 规则匹配:文件读取
|
||||||
|
if any(kw in text for kw in ["文件", "读取", "file", "config", "json", "txt"]):
|
||||||
|
filename = re.search(r"[\w\-\.]+\.\w+", user_input)
|
||||||
|
return ToolDecision(
|
||||||
|
need_tool=True, tool_name="file_reader",
|
||||||
|
arguments={"path": filename.group() if filename else "config.json"},
|
||||||
|
reasoning="用户请求读取文件,调用 file_reader 工具",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 规则匹配:代码执行
|
||||||
|
if any(kw in text for kw in ["执行", "运行", "代码", "python", "print", "code"]):
|
||||||
|
code_match = re.search(r'[`\'"](.+?)[`\'"]', user_input)
|
||||||
|
code = code_match.group(1) if code_match else 'print("Hello, Agent!")'
|
||||||
|
return ToolDecision(
|
||||||
|
need_tool=True, tool_name="code_executor",
|
||||||
|
arguments={"code": code, "timeout": 5},
|
||||||
|
reasoning="用户请求执行代码,调用 code_executor 工具",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 默认:直接回复
|
||||||
|
return ToolDecision(
|
||||||
|
need_tool=False,
|
||||||
|
reasoning="问题可直接回答,无需工具",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _synthesize_reply(self, user_input: str, tool_name: str, tool_output: str) -> str:
|
||||||
|
"""基于工具输出合成最终回复(Demo 版本)"""
|
||||||
|
return (
|
||||||
|
f"✅ 已通过 [{tool_name}] 工具处理您的请求。\n\n"
|
||||||
|
f"**执行结果:**\n{tool_output}\n\n"
|
||||||
|
f"---\n*由 {self.model_name} 生成 · 工具: {tool_name}*"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
[2026-02-28 13:16:51,269] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 13:16:51,270] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 13:16:51,271] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 13:16:51,271] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 13:16:51,271] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 13:16:51,271] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 13:16:51,272] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: claude-sonnet-4-6
|
||||||
|
[2026-02-28 13:16:51,273] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 13:16:51,273] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 13:16:51,273] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 13:59:08,843] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 13:59:08,844] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 13:59:08,845] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 13:59:08,845] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 13:59:08,845] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 13:59:08,845] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 13:59:08,846] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: claude-sonnet-4-6
|
||||||
|
[2026-02-28 13:59:08,847] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 13:59:08,848] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 13:59:08,848] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 13:59:40,111] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 13:59:41,400] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:00:07,071] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:00:51,574] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:02:14,371] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:04:03,060] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:15:21,435] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:15:21,437] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:15:21,437] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:15:21,438] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:15:21,438] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:15:21,438] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:15:21,439] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: claude-sonnet-4-6
|
||||||
|
[2026-02-28 14:15:21,730] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:15:21,731] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:15:21,731] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:15:37,946] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:15:38,634] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1+1等于多少
|
||||||
|
[2026-02-28 14:15:39,212] [agent.MEMORY] DEBUG: 💬 [USER] 1+1等于多少...
|
||||||
|
[2026-02-28 14:15:40,563] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:15:47,714] [agent.LLM] INFO: 💭 分析意图: 1+1等于多少...
|
||||||
|
[2026-02-28 14:15:50,157] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:17:52,834] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:17:52,836] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:17:52,837] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:17:52,837] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:17:52,837] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:17:52,838] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:17:52,839] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: claude-sonnet-4-6
|
||||||
|
[2026-02-28 14:17:53,009] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:17:53,010] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:17:53,010] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:18:02,052] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:18:02,614] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:18:03,083] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:18:04,093] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:18:07,998] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:18:09,082] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:19:28,781] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:19:28,781] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:19:28,782] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:19:28,782] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:19:28,783] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:19:28,783] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:19:28,783] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:19:28,912] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:19:28,913] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:19:28,913] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:19:38,773] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:19:38,773] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:19:38,774] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:19:38,774] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:19:38,774] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:19:38,774] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:24:25,989] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:24:25,990] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:24:25,991] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:24:25,991] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:24:25,991] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:24:25,991] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:24:25,992] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:24:26,120] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:24:26,120] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:24:26,121] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:24:47,520] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:24:47,520] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:24:47,521] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:24:47,521] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:24:47,521] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:24:47,521] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:26:41,628] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:26:41,629] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:26:41,630] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:26:41,630] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:26:41,631] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:26:41,631] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:26:41,632] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:26:41,764] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:26:41,764] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:26:41,765] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:26:52,266] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:26:52,266] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:26:52,267] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:26:52,267] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:26:52,267] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:26:52,268] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:29:31,672] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:29:31,673] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:29:31,674] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:29:31,674] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:29:31,674] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:29:31,675] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:29:31,675] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:29:31,809] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:29:31,810] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:29:31,811] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:29:43,546] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:29:43,546] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:29:43,546] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:29:43,547] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:29:43,547] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:29:43,547] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:32:01,347] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:32:01,348] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:32:01,349] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:32:01,349] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:32:01,349] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:32:01,350] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:32:01,351] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:32:01,477] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:32:01,477] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:32:01,478] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:32:08,959] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:32:08,959] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:32:08,959] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:32:08,960] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:32:08,960] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:32:08,960] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:34:04,981] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:34:04,982] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:34:04,983] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:34:04,984] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:34:04,984] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:34:04,985] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:34:04,987] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:34:05,137] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:34:05,140] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:34:05,141] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:34:14,908] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:34:14,908] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:34:14,908] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:34:14,908] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:34:14,909] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:34:14,909] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:35:39,204] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:35:39,205] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:35:39,205] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:35:39,205] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:35:39,206] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:35:39,206] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:35:39,207] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:35:39,333] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:35:39,334] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:35:39,334] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:35:51,583] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:35:51,583] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:35:51,584] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:35:51,584] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:35:51,584] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:35:51,585] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:43:51,069] [agent.SYSTEM] INFO: 🔧 开始组装 Agent 系统...
|
||||||
|
[2026-02-28 14:43:51,069] [agent.MCP] INFO: 🚀 MCP Server [DemoMCPServer] 启动
|
||||||
|
[2026-02-28 14:43:51,070] [agent.MCP] INFO: 📌 注册工具: [calculator] — 计算数学表达式,支持加减乘除、幂运算、括号等
|
||||||
|
[2026-02-28 14:43:51,070] [agent.MCP] INFO: 📌 注册工具: [web_search] — 在互联网上搜索信息,返回相关网页摘要
|
||||||
|
[2026-02-28 14:43:51,071] [agent.MCP] INFO: 📌 注册工具: [file_reader] — 读取本地文件内容,仅限 workspace/ 目录下的文件
|
||||||
|
[2026-02-28 14:43:51,071] [agent.MCP] INFO: 📌 注册工具: [code_executor] — 在沙箱环境中执行 Python 代码片段,返回标准输出
|
||||||
|
[2026-02-28 14:43:51,071] [agent.LLM] INFO: 🧠 LLM 引擎初始化,模型: gpt-4o
|
||||||
|
[2026-02-28 14:43:51,198] [agent.MEMORY] INFO: 💾 Memory 初始化,最大历史: 20 条
|
||||||
|
[2026-02-28 14:43:51,198] [agent.CLIENT] INFO: 💻 Agent Client 初始化完成
|
||||||
|
[2026-02-28 14:43:51,199] [agent.SYSTEM] INFO: ✅ Agent 组装完成,已注册工具: ['calculator', 'web_search', 'file_reader', 'code_executor']
|
||||||
|
[2026-02-28 14:44:04,009] [agent.CLIENT] INFO: =======================================================
|
||||||
|
[2026-02-28 14:44:04,009] [agent.CLIENT] INFO: 📨 Step 1 [CLIENT] 收到用户输入: 1加1等于多少
|
||||||
|
[2026-02-28 14:44:04,009] [agent.MEMORY] DEBUG: 💬 [USER] 1加1等于多少...
|
||||||
|
[2026-02-28 14:44:04,010] [agent.CLIENT] INFO: 🧠 Step 2 [LLM] 开始推理,分析意图...
|
||||||
|
[2026-02-28 14:44:04,010] [agent.LLM] INFO: 💭 分析意图: 1加1等于多少...
|
||||||
|
[2026-02-28 14:44:04,010] [agent.LLM] DEBUG: 📝 Prompt 已构造 (344 chars)
|
||||||
|
[2026-02-28 14:44:10,159] [agent.LLM] INFO: 🎯 决策结果: 调用工具 [calculator]
|
||||||
|
[2026-02-28 14:44:10,159] [agent.LLM] DEBUG: 💡 推理: 用户询问数学计算问题,直接调用计算工具得到1+1的结果是最合适的。
|
||||||
|
[2026-02-28 14:44:21,419] [agent.CLIENT] INFO: 📡 Step 3 [MCP] 发送工具调用请求
|
||||||
|
方法: tools/call
|
||||||
|
工具: calculator
|
||||||
|
参数: {'expression': '1+1'}
|
||||||
|
请求体: {'jsonrpc': '2.0', 'id': 'e4d8732a', 'method': 'tools/call', 'params': {'name': 'calculator', 'arguments': {'expression': '1+1'}}}
|
||||||
|
[2026-02-28 14:44:36,340] [agent.CLIENT] INFO: 🔧 Step 4 [TOOL] MCP Server 执行工具 [calculator]...
|
||||||
|
[2026-02-28 14:44:40,181] [agent.MCP] INFO: 📨 收到请求 id=e4d8732a method=tools/call
|
||||||
|
[2026-02-28 14:46:24,701] [agent.TOOL] INFO: ▶ 执行工具 [calculator],参数: {'expression': '1+1'}
|
||||||
|
[2026-02-28 14:46:50,239] [agent.TOOL] INFO: ✅ 工具 [calculator] 执行成功
|
||||||
|
[2026-02-28 16:05:27,152] [agent.CLIENT] INFO: ✅ 工具执行成功,输出: 1+1 = 2...
|
||||||
|
[2026-02-28 16:05:46,521] [agent.MEMORY] DEBUG: 💬 [TOOL] 1+1 = 2...
|
||||||
|
[2026-02-28 16:05:49,469] [agent.CLIENT] INFO: ✍️ Step 5 [LLM] 整合工具结果,生成最终回复...
|
||||||
|
[2026-02-28 16:05:53,270] [agent.LLM] INFO: ✍️ 整合工具结果,生成最终回复...
|
||||||
|
[2026-02-28 16:05:53,271] [agent.LLM] INFO: 💬 回复已生成 (83 chars)
|
||||||
|
[2026-02-28 16:06:03,400] [agent.MEMORY] DEBUG: 💬 [ASSISTANT] ✅ 已通过 [calculator] 工具处理您的请求。
|
||||||
|
|
||||||
|
**执行结果:**
|
||||||
|
1+1 = 2
|
||||||
|
|
||||||
|
---
|
||||||
|
*由 gpt-...
|
||||||
|
[2026-02-28 16:06:05,167] [agent.CLIENT] INFO: 🎉 [CLIENT] 流程完成,回复已返回
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
"""程序入口"""
|
||||||
|
"""
|
||||||
|
main.py
|
||||||
|
智能体 Demo 程序入口
|
||||||
|
组装所有模块,启动交互式对话循环
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ── 导入各模块 ─────────────────────────────────────────────────
|
||||||
|
from client.agent_client import AgentClient
|
||||||
|
from llm.llm_engine import LLMEngine
|
||||||
|
from mcp.mcp_server import MCPServer
|
||||||
|
from memory.memory_store import MemoryStore
|
||||||
|
from tools.calculator import CalculatorTool
|
||||||
|
from tools.code_executor import CodeExecutorTool
|
||||||
|
from tools.file_reader import FileReaderTool
|
||||||
|
from tools.web_search import WebSearchTool
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("SYSTEM")
|
||||||
|
|
||||||
|
|
||||||
|
# ── 系统组装 ───────────────────────────────────────────────────
|
||||||
|
def build_agent() -> AgentClient:
|
||||||
|
"""
|
||||||
|
工厂函数:组装并返回完整的 Agent 实例
|
||||||
|
|
||||||
|
组装顺序:
|
||||||
|
1. 初始化 MCP Server,注册所有工具
|
||||||
|
2. 初始化 LLM 引擎
|
||||||
|
3. 初始化 Memory 模块
|
||||||
|
4. 组装 AgentClient
|
||||||
|
"""
|
||||||
|
logger.info("🔧 开始组装 Agent 系统...")
|
||||||
|
|
||||||
|
# 1. MCP Server:注册所有工具
|
||||||
|
mcp_server = MCPServer(server_name="DemoMCPServer")
|
||||||
|
mcp_server.register_tools(
|
||||||
|
CalculatorTool,
|
||||||
|
WebSearchTool,
|
||||||
|
FileReaderTool,
|
||||||
|
CodeExecutorTool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. LLM 引擎
|
||||||
|
llm = LLMEngine(model_name="gpt-4o")
|
||||||
|
|
||||||
|
# 3. 记忆模块
|
||||||
|
memory = MemoryStore(max_history=20)
|
||||||
|
|
||||||
|
# 4. 组装客户端
|
||||||
|
client = AgentClient(llm=llm, mcp_server=mcp_server, memory=memory)
|
||||||
|
|
||||||
|
logger.info(f"✅ Agent 组装完成,已注册工具: {mcp_server.list_tools()}")
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
# ── 演示场景 ───────────────────────────────────────────────────
|
||||||
|
def run_demo(client: AgentClient) -> None:
|
||||||
|
"""运行预设演示场景,展示各工具的完整调用链路"""
|
||||||
|
demo_cases = [
|
||||||
|
("🔢 数学计算", "计算 (100 + 200) × 3 等于多少"),
|
||||||
|
("🌐 网络搜索", "搜索 Python 最新版本的新特性"),
|
||||||
|
("📄 文件读取", "读取文件 config.json 的内容"),
|
||||||
|
("🐍 代码执行", '执行代码 `print("Hello, Agent!")`'),
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.info("\n" + "═" * 60)
|
||||||
|
logger.info("🎬 开始演示模式,共 4 个场景")
|
||||||
|
logger.info("═" * 60)
|
||||||
|
|
||||||
|
for title, question in demo_cases:
|
||||||
|
logger.info(f"\n{'─' * 55}")
|
||||||
|
logger.info(f"📌 场景: {title}")
|
||||||
|
logger.info(f"{'─' * 55}")
|
||||||
|
|
||||||
|
response = client.chat(question)
|
||||||
|
|
||||||
|
print(f"\n{'─' * 55}")
|
||||||
|
print(f"👤 用户: {response.user_input}")
|
||||||
|
if response.tool_used:
|
||||||
|
print(f"🔧 工具: {response.tool_used}")
|
||||||
|
print(f"📤 输出: {response.tool_output[:120]}...")
|
||||||
|
print(f"🤖 回复:\n{response.final_reply}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 打印记忆统计
|
||||||
|
stats = client.get_memory_stats()
|
||||||
|
logger.info(f"\n📊 Memory 统计: {stats}")
|
||||||
|
|
||||||
|
|
||||||
|
# ── 交互式对话循环 ─────────────────────────────────────────────
|
||||||
|
def run_interactive(client: AgentClient) -> None:
|
||||||
|
"""启动交互式命令行对话"""
|
||||||
|
print("\n" + "═" * 60)
|
||||||
|
print(" 🤖 Agent Demo — 交互模式")
|
||||||
|
print(" 输入 'quit' → 退出程序")
|
||||||
|
print(" 输入 'clear' → 清空会话历史")
|
||||||
|
print(" 输入 'stats' → 查看 Memory 统计")
|
||||||
|
print(" 输入 'tools' → 查看已注册工具列表")
|
||||||
|
print("═" * 60 + "\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
user_input = input("👤 你: ").strip()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print("\n👋 再见!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ── 内置命令 ──────────────────────────────────────────
|
||||||
|
match user_input.lower():
|
||||||
|
case "quit" | "exit":
|
||||||
|
print("👋 再见!")
|
||||||
|
break
|
||||||
|
case "clear":
|
||||||
|
client.clear_session()
|
||||||
|
print("✅ 会话已清空\n")
|
||||||
|
continue
|
||||||
|
case "stats":
|
||||||
|
print(f"📊 Memory 统计: {client.get_memory_stats()}\n")
|
||||||
|
continue
|
||||||
|
case "tools":
|
||||||
|
tools = client.mcp_server.list_tools()
|
||||||
|
print(f"🔧 已注册工具 ({len(tools)} 个): {', '.join(tools)}\n")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ── 执行 Agent 完整流程 ───────────────────────────────
|
||||||
|
response = client.chat(user_input)
|
||||||
|
|
||||||
|
print(f"\n{'─' * 55}")
|
||||||
|
if response.tool_used:
|
||||||
|
print(f" 🔧 调用工具: {response.tool_used}")
|
||||||
|
print(f"🤖 Agent:\n{response.final_reply}")
|
||||||
|
print(f"{'─' * 55}\n")
|
||||||
|
|
||||||
|
|
||||||
|
# ── 主函数 ─────────────────────────────────────────────────────
|
||||||
|
def main() -> None:
|
||||||
|
"""
|
||||||
|
主函数入口,支持两种运行模式:
|
||||||
|
|
||||||
|
python main.py → 交互模式(默认)
|
||||||
|
python main.py demo → 演示模式(自动执行预设场景)
|
||||||
|
"""
|
||||||
|
client = build_agent()
|
||||||
|
|
||||||
|
mode = sys.argv[1] if len(sys.argv) > 1 else "interactive"
|
||||||
|
|
||||||
|
if mode == "demo":
|
||||||
|
run_demo(client)
|
||||||
|
else:
|
||||||
|
run_interactive(client)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""__init__.py"""
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
"""MCP 协议:JSON-RPC 消息定义"""
|
||||||
|
"""
|
||||||
|
mcp/mcp_protocol.py
|
||||||
|
MCP (Model Context Protocol) 协议数据结构定义
|
||||||
|
基于 JSON-RPC 2.0 规范封装请求/响应消息体
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
# ── MCP 方法常量 ───────────────────────────────────────────────
|
||||||
|
class MCPMethod:
|
||||||
|
TOOLS_LIST = "tools/list" # 列出所有可用工具
|
||||||
|
TOOLS_CALL = "tools/call" # 调用指定工具
|
||||||
|
RESOURCES_READ = "resources/read" # 读取资源
|
||||||
|
|
||||||
|
|
||||||
|
# ── 请求消息 ───────────────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class MCPRequest:
|
||||||
|
"""
|
||||||
|
MCP 工具调用请求(JSON-RPC 2.0 格式)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "abc-123",
|
||||||
|
"method": "tools/call",
|
||||||
|
"params": {
|
||||||
|
"name": "calculator",
|
||||||
|
"arguments": {"expression": "1+1"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
method: str
|
||||||
|
params: dict[str, Any] = field(default_factory=dict)
|
||||||
|
jsonrpc: str = "2.0"
|
||||||
|
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"jsonrpc": self.jsonrpc,
|
||||||
|
"id": self.id,
|
||||||
|
"method": self.method,
|
||||||
|
"params": self.params,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── 响应消息 ───────────────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class MCPResponse:
|
||||||
|
"""
|
||||||
|
MCP 工具调用响应(JSON-RPC 2.0 格式)
|
||||||
|
|
||||||
|
成功示例:
|
||||||
|
{"jsonrpc": "2.0", "id": "abc-123", "result": {"content": [...]}}
|
||||||
|
|
||||||
|
失败示例:
|
||||||
|
{"jsonrpc": "2.0", "id": "abc-123", "error": {"code": -32601, "message": "..."}}
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
result: dict[str, Any] | None = None
|
||||||
|
error: dict[str, Any] | None = None
|
||||||
|
jsonrpc: str = "2.0"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def success(self) -> bool:
|
||||||
|
return self.error is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self) -> str:
|
||||||
|
"""提取响应中的文本内容"""
|
||||||
|
if not self.success or not self.result:
|
||||||
|
return self.error.get("message", "Unknown error") if self.error else ""
|
||||||
|
items = self.result.get("content", [])
|
||||||
|
return "\n".join(item.get("text", "") for item in items if item.get("type") == "text")
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
base = {"jsonrpc": self.jsonrpc, "id": self.id}
|
||||||
|
if self.success:
|
||||||
|
base["result"] = self.result
|
||||||
|
else:
|
||||||
|
base["error"] = self.error
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
# ── 工具描述 ───────────────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class ToolSchema:
|
||||||
|
"""
|
||||||
|
工具的元数据描述,用于 LLM 识别和选择工具
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
parameters: dict[str, Any] # JSON Schema 格式的参数定义
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"description": self.description,
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": self.parameters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
"""MCP 服务器:工具注册 & 调度"""
|
||||||
|
"""
|
||||||
|
mcp/mcp_server.py
|
||||||
|
MCP Server:工具注册中心与调度引擎
|
||||||
|
负责管理所有工具的生命周期,处理 JSON-RPC 格式的工具调用请求
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from mcp.mcp_protocol import MCPMethod, MCPRequest, MCPResponse, ToolSchema
|
||||||
|
from tools.base_tool import BaseTool, ToolResult
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
class MCPServer:
|
||||||
|
"""
|
||||||
|
MCP 服务器核心类
|
||||||
|
|
||||||
|
职责:
|
||||||
|
1. 工具注册(register_tool)
|
||||||
|
2. 工具列表查询(tools/list)
|
||||||
|
3. 工具调用分发(tools/call)
|
||||||
|
4. JSON-RPC 协议封装/解析
|
||||||
|
|
||||||
|
使用示例:
|
||||||
|
server = MCPServer()
|
||||||
|
server.register_tool(CalculatorTool)
|
||||||
|
response = server.handle_request(request)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, server_name: str = "AgentMCPServer"):
|
||||||
|
self.server_name = server_name
|
||||||
|
self.logger = get_logger("MCP")
|
||||||
|
self._registry: dict[str, BaseTool] = {} # 工具名 → 工具实例
|
||||||
|
|
||||||
|
self.logger.info(f"🚀 MCP Server [{server_name}] 启动")
|
||||||
|
|
||||||
|
# ── 工具注册 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def register_tool(self, tool_class: Type[BaseTool]) -> None:
|
||||||
|
"""
|
||||||
|
注册一个工具类到服务器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_class: 继承自 BaseTool 的工具类(传入类本身,不是实例)
|
||||||
|
"""
|
||||||
|
instance = tool_class()
|
||||||
|
if not instance.name:
|
||||||
|
raise ValueError(f"工具类 {tool_class.__name__} 未设置 name 属性")
|
||||||
|
|
||||||
|
self._registry[instance.name] = instance
|
||||||
|
self.logger.info(f"📌 注册工具: [{instance.name}] — {instance.description}")
|
||||||
|
|
||||||
|
def register_tools(self, *tool_classes: Type[BaseTool]) -> None:
|
||||||
|
"""批量注册多个工具类"""
|
||||||
|
for cls in tool_classes:
|
||||||
|
self.register_tool(cls)
|
||||||
|
|
||||||
|
# ── 请求处理入口 ────────────────────────────────────────────
|
||||||
|
|
||||||
|
def handle_request(self, request: MCPRequest) -> MCPResponse:
|
||||||
|
"""
|
||||||
|
处理 MCP 请求的统一入口,根据 method 分发到对应处理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: MCPRequest 实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MCPResponse 实例
|
||||||
|
"""
|
||||||
|
self.logger.info(f"📨 收到请求 id={request.id} method={request.method}")
|
||||||
|
|
||||||
|
handlers = {
|
||||||
|
MCPMethod.TOOLS_LIST: self._handle_tools_list,
|
||||||
|
MCPMethod.TOOLS_CALL: self._handle_tools_call,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = handlers.get(request.method)
|
||||||
|
if handler is None:
|
||||||
|
return self._error_response(request.id, -32601, f"未知方法: {request.method}")
|
||||||
|
|
||||||
|
return handler(request)
|
||||||
|
|
||||||
|
# ── 私有处理器 ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _handle_tools_list(self, request: MCPRequest) -> MCPResponse:
|
||||||
|
"""处理 tools/list 请求,返回所有已注册工具的 Schema"""
|
||||||
|
schemas = [tool.get_schema().to_dict() for tool in self._registry.values()]
|
||||||
|
self.logger.info(f"📋 返回工具列表,共 {len(schemas)} 个工具")
|
||||||
|
return MCPResponse(
|
||||||
|
id=request.id,
|
||||||
|
result={"tools": schemas},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_tools_call(self, request: MCPRequest) -> MCPResponse:
|
||||||
|
"""处理 tools/call 请求,调用指定工具并返回结果"""
|
||||||
|
tool_name = request.params.get("name")
|
||||||
|
arguments = request.params.get("arguments", {})
|
||||||
|
|
||||||
|
# 检查工具是否存在
|
||||||
|
tool = self._registry.get(tool_name)
|
||||||
|
if tool is None:
|
||||||
|
available = list(self._registry.keys())
|
||||||
|
return self._error_response(
|
||||||
|
request.id, -32602,
|
||||||
|
f"工具 [{tool_name}] 不存在,可用工具: {available}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 执行工具
|
||||||
|
result: ToolResult = tool.safe_execute(**arguments)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
return MCPResponse(
|
||||||
|
id=request.id,
|
||||||
|
result={
|
||||||
|
"content": [{"type": "text", "text": result.output}],
|
||||||
|
"metadata": result.metadata,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self._error_response(request.id, -32000, result.output)
|
||||||
|
|
||||||
|
# ── 工具方法 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_tool_schemas(self) -> list[ToolSchema]:
|
||||||
|
"""获取所有工具的 Schema 列表(供 LLM 引擎使用)"""
|
||||||
|
return [tool.get_schema() for tool in self._registry.values()]
|
||||||
|
|
||||||
|
def list_tools(self) -> list[str]:
|
||||||
|
"""返回所有已注册工具的名称列表"""
|
||||||
|
return list(self._registry.keys())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _error_response(req_id: str, code: int, message: str) -> MCPResponse:
|
||||||
|
"""构造标准 JSON-RPC 错误响应"""
|
||||||
|
return MCPResponse(
|
||||||
|
id=req_id,
|
||||||
|
error={"code": code, "message": message},
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"MCPServer(name={self.server_name!r}, tools={self.list_tools()})"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""__init__.py"""
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
"""记忆模块:对话历史管理"""
|
||||||
|
"""
|
||||||
|
memory/memory_store.py
|
||||||
|
Agent 记忆模块:管理对话历史(短期记忆)与关键信息摘要(长期记忆)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
# ── 消息数据结构 ───────────────────────────────────────────────
|
||||||
|
@dataclass
|
||||||
|
class Message:
|
||||||
|
"""单条对话消息"""
|
||||||
|
role: Literal["user", "assistant", "tool"]
|
||||||
|
content: str
|
||||||
|
timestamp: str = field(default_factory=lambda: datetime.now().strftime("%H:%M:%S"))
|
||||||
|
metadata: dict = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"role": self.role,
|
||||||
|
"content": self.content,
|
||||||
|
"timestamp": self.timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── 记忆存储 ───────────────────────────────────────────────────
|
||||||
|
class MemoryStore:
|
||||||
|
"""
|
||||||
|
对话记忆存储
|
||||||
|
|
||||||
|
短期记忆: 使用 deque 保存最近 N 轮对话,自动滚动淘汰旧消息
|
||||||
|
长期记忆: 保存关键事实摘要(生产环境可替换为向量数据库)
|
||||||
|
|
||||||
|
使用示例:
|
||||||
|
memory = MemoryStore(max_history=10)
|
||||||
|
memory.add_user_message("你好")
|
||||||
|
memory.add_assistant_message("你好!有什么可以帮你?")
|
||||||
|
history = memory.get_history()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, max_history: int = 20):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
max_history: 短期记忆保留的最大消息条数
|
||||||
|
"""
|
||||||
|
self.logger = get_logger("MEMORY")
|
||||||
|
self.max_history = max_history
|
||||||
|
self._history: deque[Message] = deque(maxlen=max_history)
|
||||||
|
self._facts: list[str] = [] # 长期记忆:关键事实
|
||||||
|
|
||||||
|
self.logger.info(f"💾 Memory 初始化,最大历史: {max_history} 条")
|
||||||
|
|
||||||
|
# ── 写入接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def add_user_message(self, content: str) -> None:
|
||||||
|
"""记录用户消息"""
|
||||||
|
self._add(Message(role="user", content=content))
|
||||||
|
|
||||||
|
def add_assistant_message(self, content: str) -> None:
|
||||||
|
"""记录 Agent 回复"""
|
||||||
|
self._add(Message(role="assistant", content=content))
|
||||||
|
|
||||||
|
def add_tool_result(self, tool_name: str, result: str) -> None:
|
||||||
|
"""记录工具调用结果"""
|
||||||
|
self._add(Message(
|
||||||
|
role="tool",
|
||||||
|
content=result,
|
||||||
|
metadata={"tool": tool_name},
|
||||||
|
))
|
||||||
|
|
||||||
|
def add_fact(self, fact: str) -> None:
|
||||||
|
"""向长期记忆中添加关键事实"""
|
||||||
|
self._facts.append(fact)
|
||||||
|
self.logger.debug(f"📌 长期记忆新增: {fact}")
|
||||||
|
|
||||||
|
# ── 读取接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def get_history(self, last_n: int | None = None) -> list[dict]:
|
||||||
|
"""
|
||||||
|
获取对话历史(LLM 上下文格式)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
last_n: 仅返回最近 N 条,None 表示全部
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
消息字典列表,格式: [{"role": ..., "content": ...}, ...]
|
||||||
|
"""
|
||||||
|
messages = list(self._history)
|
||||||
|
if last_n:
|
||||||
|
messages = messages[-last_n:]
|
||||||
|
return [m.to_dict() for m in messages]
|
||||||
|
|
||||||
|
def get_facts(self) -> list[str]:
|
||||||
|
"""获取所有长期记忆事实"""
|
||||||
|
return list(self._facts)
|
||||||
|
|
||||||
|
def get_context_summary(self) -> str:
|
||||||
|
"""生成上下文摘要字符串,供 LLM Prompt 使用"""
|
||||||
|
history = self.get_history(last_n=6)
|
||||||
|
lines = [f"[{m['role'].upper()}] {m['content'][:80]}" for m in history]
|
||||||
|
return "\n".join(lines) if lines else "(暂无对话历史)"
|
||||||
|
|
||||||
|
# ── 管理接口 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def clear_history(self) -> None:
|
||||||
|
"""清空短期对话历史"""
|
||||||
|
self._history.clear()
|
||||||
|
self.logger.info("🗑 对话历史已清空")
|
||||||
|
|
||||||
|
def stats(self) -> dict:
|
||||||
|
"""返回记忆统计信息"""
|
||||||
|
return {
|
||||||
|
"history_count": len(self._history),
|
||||||
|
"facts_count": len(self._facts),
|
||||||
|
"max_history": self.max_history,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── 私有方法 ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _add(self, message: Message) -> None:
|
||||||
|
self._history.append(message)
|
||||||
|
self.logger.debug(f"💬 [{message.role.upper()}] {message.content[:60]}...")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""__init__.py"""
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
"""
|
||||||
|
tools/base_tool.py
|
||||||
|
所有工具的抽象基类,定义统一接口规范
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from mcp.mcp_protocol import ToolSchema
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToolResult:
|
||||||
|
"""工具执行结果的统一封装"""
|
||||||
|
success: bool
|
||||||
|
output: str
|
||||||
|
metadata: dict[str, Any] = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.metadata = self.metadata or {}
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTool(ABC):
|
||||||
|
"""
|
||||||
|
工具基类:所有具体工具必须继承此类并实现 execute() 方法
|
||||||
|
|
||||||
|
子类示例:
|
||||||
|
class MyTool(BaseTool):
|
||||||
|
name = "my_tool"
|
||||||
|
description = "这是我的工具"
|
||||||
|
parameters = {"input": {"type": "string", "description": "输入内容"}}
|
||||||
|
|
||||||
|
def execute(self, **kwargs) -> ToolResult:
|
||||||
|
return ToolResult(success=True, output=f"处理结果: {kwargs['input']}")
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 子类必须定义的类属性
|
||||||
|
name: str = ""
|
||||||
|
description: str = ""
|
||||||
|
parameters: dict[str, Any] = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = get_logger("TOOL")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self, **kwargs) -> ToolResult:
|
||||||
|
"""
|
||||||
|
执行工具逻辑(子类必须实现)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: 工具参数,与 parameters 中定义的字段对应
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ToolResult 实例
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_schema(self) -> ToolSchema:
|
||||||
|
"""返回工具的 MCP Schema 描述"""
|
||||||
|
return ToolSchema(
|
||||||
|
name=self.name,
|
||||||
|
description=self.description,
|
||||||
|
parameters=self.parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
def safe_execute(self, **kwargs) -> ToolResult:
|
||||||
|
"""
|
||||||
|
带异常捕获的安全执行入口,由 MCP Server 调用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ToolResult,失败时 success=False 并携带错误信息
|
||||||
|
"""
|
||||||
|
self.logger.info(f"▶ 执行工具 [{self.name}],参数: {kwargs}")
|
||||||
|
try:
|
||||||
|
result = self.execute(**kwargs)
|
||||||
|
self.logger.info(f"✅ 工具 [{self.name}] 执行成功")
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
self.logger.error(f"❌ 工具 [{self.name}] 执行失败: {exc}")
|
||||||
|
return ToolResult(success=False, output=f"工具执行异常: {exc}")
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
"""计算器工具"""
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# tools/calculator.py — 数学计算工具
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
"""
|
||||||
|
tools/calculator.py
|
||||||
|
安全的数学表达式计算工具(使用 ast 模块避免 eval 注入风险)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import operator
|
||||||
|
from tools.base_tool import BaseTool, ToolResult
|
||||||
|
|
||||||
|
|
||||||
|
class CalculatorTool(BaseTool):
|
||||||
|
name = "calculator"
|
||||||
|
description = "计算数学表达式,支持加减乘除、幂运算、括号等"
|
||||||
|
parameters = {
|
||||||
|
"expression": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数学表达式,例如 '(1+2)*3' 或 '2**10'",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 允许的运算符白名单(防止注入)
|
||||||
|
_OPERATORS = {
|
||||||
|
ast.Add: operator.add,
|
||||||
|
ast.Sub: operator.sub,
|
||||||
|
ast.Mult: operator.mul,
|
||||||
|
ast.Div: operator.truediv,
|
||||||
|
ast.Pow: operator.pow,
|
||||||
|
ast.Mod: operator.mod,
|
||||||
|
ast.USub: operator.neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, expression: str, **_) -> ToolResult:
|
||||||
|
try:
|
||||||
|
tree = ast.parse(expression, mode="eval")
|
||||||
|
result = self._eval_node(tree.body)
|
||||||
|
return ToolResult(
|
||||||
|
success=True,
|
||||||
|
output=f"{expression} = {result}",
|
||||||
|
metadata={"expression": expression, "result": result},
|
||||||
|
)
|
||||||
|
except (ValueError, TypeError, ZeroDivisionError) as exc:
|
||||||
|
return ToolResult(success=False, output=f"计算错误: {exc}")
|
||||||
|
|
||||||
|
def _eval_node(self, node: ast.AST) -> float:
|
||||||
|
"""递归解析 AST 节点"""
|
||||||
|
match node:
|
||||||
|
case ast.Constant(value=v) if isinstance(v, (int, float)):
|
||||||
|
return v
|
||||||
|
case ast.BinOp(left=left, op=op, right=right):
|
||||||
|
fn = self._OPERATORS.get(type(op))
|
||||||
|
if fn is None:
|
||||||
|
raise ValueError(f"不支持的运算符: {type(op).__name__}")
|
||||||
|
return fn(self._eval_node(left), self._eval_node(right))
|
||||||
|
case ast.UnaryOp(op=op, operand=operand):
|
||||||
|
fn = self._OPERATORS.get(type(op))
|
||||||
|
if fn is None:
|
||||||
|
raise ValueError(f"不支持的一元运算符: {type(op).__name__}")
|
||||||
|
return fn(self._eval_node(operand))
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"不支持的表达式节点: {type(node).__name__}")
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
"""代码执行工具"""
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# tools/code_executor.py — 代码执行工具
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
"""
|
||||||
|
tools/code_executor.py
|
||||||
|
沙箱代码执行工具:在受限环境中运行 Python 代码片段
|
||||||
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import contextlib
|
||||||
|
import time
|
||||||
|
from tools.base_tool import BaseTool, ToolResult
|
||||||
|
|
||||||
|
|
||||||
|
class CodeExecutorTool(BaseTool):
|
||||||
|
name = "code_executor"
|
||||||
|
description = "在沙箱环境中执行 Python 代码片段,返回标准输出"
|
||||||
|
parameters = {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "要执行的 Python 代码",
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "超时时间(秒),默认 5",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 沙箱:仅允许安全的内置函数
|
||||||
|
_SAFE_BUILTINS = {
|
||||||
|
"print": print, "range": range, "len": len,
|
||||||
|
"int": int, "float": float, "str": str, "list": list,
|
||||||
|
"dict": dict, "tuple": tuple, "set": set, "bool": bool,
|
||||||
|
"abs": abs, "max": max, "min": min, "sum": sum,
|
||||||
|
"enumerate": enumerate, "zip": zip, "map": map,
|
||||||
|
"sorted": sorted, "reversed": reversed,
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, code: str, timeout: int = 5, **_) -> ToolResult:
|
||||||
|
stdout_buf = io.StringIO()
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 重定向 stdout,捕获 print 输出
|
||||||
|
with contextlib.redirect_stdout(stdout_buf):
|
||||||
|
exec( # noqa: S102
|
||||||
|
compile(code, "<agent_sandbox>", "exec"),
|
||||||
|
{"__builtins__": self._SAFE_BUILTINS},
|
||||||
|
)
|
||||||
|
elapsed = (time.perf_counter() - start_time) * 1000
|
||||||
|
output = stdout_buf.getvalue() or "(无输出)"
|
||||||
|
return ToolResult(
|
||||||
|
success=True,
|
||||||
|
output=f"执行成功 ({elapsed:.1f}ms):\n{output}",
|
||||||
|
metadata={"elapsed_ms": elapsed},
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
return ToolResult(success=False, output=f"执行错误: {type(exc).__name__}: {exc}")
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""文件读取工具"""
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# tools/file_reader.py — 文件读取工具
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
"""
|
||||||
|
tools/file_reader.py
|
||||||
|
本地文件读取工具,支持文本文件,限制读取路径防止越权
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from tools.base_tool import BaseTool, ToolResult
|
||||||
|
|
||||||
|
|
||||||
|
# 允许读取的根目录(沙箱限制)
|
||||||
|
_ALLOWED_ROOT = Path("./workspace")
|
||||||
|
|
||||||
|
|
||||||
|
class FileReaderTool(BaseTool):
|
||||||
|
name = "file_reader"
|
||||||
|
description = "读取本地文件内容,仅限 workspace/ 目录下的文件"
|
||||||
|
parameters = {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件路径,相对于 workspace/ 目录",
|
||||||
|
},
|
||||||
|
"encoding": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "文件编码,默认 utf-8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, path: str, encoding: str = "utf-8", **_) -> ToolResult:
|
||||||
|
_ALLOWED_ROOT.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 路径安全检查:防止目录穿越攻击
|
||||||
|
target = (_ALLOWED_ROOT / path).resolve()
|
||||||
|
if not str(target).startswith(str(_ALLOWED_ROOT.resolve())):
|
||||||
|
return ToolResult(success=False, output=f"❌ 拒绝访问: 路径超出允许范围")
|
||||||
|
|
||||||
|
if not target.exists():
|
||||||
|
# Demo 模式:自动创建示例文件
|
||||||
|
self._create_demo_file(target)
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = target.read_text(encoding=encoding)
|
||||||
|
return ToolResult(
|
||||||
|
success=True,
|
||||||
|
output=f"文件 [{path}] 内容:\n{content}",
|
||||||
|
metadata={"path": str(target), "size": target.stat().st_size},
|
||||||
|
)
|
||||||
|
except OSError as exc:
|
||||||
|
return ToolResult(success=False, output=f"读取失败: {exc}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_demo_file(path: Path) -> None:
|
||||||
|
"""自动创建演示用文件"""
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(
|
||||||
|
'{\n "app": "AgentDemo",\n "version": "1.0.0",\n'
|
||||||
|
' "llm": "claude-sonnet-4-6",\n "tools": ["calculator", "web_search"]\n}\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""网络搜索工具"""
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
# tools/web_search.py — 网络搜索工具(模拟)
|
||||||
|
# ════════════════════════════════════════════════════════════════
|
||||||
|
"""
|
||||||
|
tools/web_search.py
|
||||||
|
网络搜索工具(Demo 中使用模拟数据,生产环境可替换为真实 API)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from tools.base_tool import BaseTool, ToolResult
|
||||||
|
|
||||||
|
|
||||||
|
# 模拟搜索结果数据库
|
||||||
|
_MOCK_RESULTS: dict[str, list[dict]] = {
|
||||||
|
"天气": [
|
||||||
|
{"title": "今日天气预报", "snippet": "晴转多云,气温 15°C ~ 24°C,东南风 3 级"},
|
||||||
|
{"title": "未来 7 天天气", "snippet": "本周整体晴好,周末有小雨"},
|
||||||
|
],
|
||||||
|
"python": [
|
||||||
|
{"title": "Python 官方文档", "snippet": "Python 3.12 新特性:改进的错误提示、更快的启动速度"},
|
||||||
|
{"title": "Python 教程", "snippet": "从零开始学 Python,包含 300+ 实战案例"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
_DEFAULT_RESULTS = [
|
||||||
|
{"title": "搜索结果 1", "snippet": "找到相关内容,请查看详情"},
|
||||||
|
{"title": "搜索结果 2", "snippet": "更多相关信息可通过链接访问"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WebSearchTool(BaseTool):
|
||||||
|
name = "web_search"
|
||||||
|
description = "在互联网上搜索信息,返回相关网页摘要"
|
||||||
|
parameters = {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "搜索关键词或问题",
|
||||||
|
},
|
||||||
|
"max_results": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "返回结果数量,默认 3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, query: str, max_results: int = 3, **_) -> ToolResult:
|
||||||
|
time.sleep(0.1) # 模拟网络延迟
|
||||||
|
|
||||||
|
# 关键词匹配模拟结果
|
||||||
|
results = _DEFAULT_RESULTS
|
||||||
|
for keyword, data in _MOCK_RESULTS.items():
|
||||||
|
if keyword in query:
|
||||||
|
results = data
|
||||||
|
break
|
||||||
|
|
||||||
|
results = results[:max_results]
|
||||||
|
formatted = "\n".join(
|
||||||
|
f"[{i+1}] {r['title']}\n {r['snippet']}"
|
||||||
|
for i, r in enumerate(results)
|
||||||
|
)
|
||||||
|
return ToolResult(
|
||||||
|
success=True,
|
||||||
|
output=f"搜索「{query}」,共 {len(results)} 条结果:\n{formatted}",
|
||||||
|
metadata={"query": query, "count": len(results)},
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
"""日志工具"""
|
||||||
|
"""
|
||||||
|
utils/logger.py
|
||||||
|
统一日志模块,支持彩色终端输出与文件记录
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# ── ANSI 颜色常量 ──────────────────────────────────────────────
|
||||||
|
class Color:
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
CYAN = "\033[96m"
|
||||||
|
GREEN = "\033[92m"
|
||||||
|
YELLOW = "\033[93m"
|
||||||
|
RED = "\033[91m"
|
||||||
|
MAGENTA = "\033[95m"
|
||||||
|
BLUE = "\033[94m"
|
||||||
|
GREY = "\033[90m"
|
||||||
|
|
||||||
|
|
||||||
|
# ── 自定义彩色 Formatter ───────────────────────────────────────
|
||||||
|
class ColorFormatter(logging.Formatter):
|
||||||
|
LEVEL_COLORS = {
|
||||||
|
logging.DEBUG: Color.GREY,
|
||||||
|
logging.INFO: Color.CYAN,
|
||||||
|
logging.WARNING: Color.YELLOW,
|
||||||
|
logging.ERROR: Color.RED,
|
||||||
|
logging.CRITICAL: Color.MAGENTA,
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPONENT_COLORS = {
|
||||||
|
"CLIENT": Color.BLUE,
|
||||||
|
"LLM": Color.GREEN,
|
||||||
|
"MCP": Color.YELLOW,
|
||||||
|
"TOOL": Color.MAGENTA,
|
||||||
|
"MEMORY": Color.CYAN,
|
||||||
|
"SYSTEM": Color.GREY,
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
level_color = self.LEVEL_COLORS.get(record.levelno, Color.RESET)
|
||||||
|
time_str = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||||
|
|
||||||
|
# 从 logger 名称提取组件标签,例如 "agent.CLIENT"
|
||||||
|
component = record.name.split(".")[-1].upper()
|
||||||
|
comp_color = self.COMPONENT_COLORS.get(component, Color.RESET)
|
||||||
|
|
||||||
|
prefix = (
|
||||||
|
f"{Color.GREY}[{time_str}]{Color.RESET} "
|
||||||
|
f"{comp_color}{Color.BOLD}[{component:6s}]{Color.RESET} "
|
||||||
|
f"{level_color}{record.getMessage()}{Color.RESET}"
|
||||||
|
)
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
|
||||||
|
# ── Logger 工厂函数 ────────────────────────────────────────────
|
||||||
|
def get_logger(component: str, level: int = logging.DEBUG) -> logging.Logger:
|
||||||
|
"""
|
||||||
|
获取指定组件的 Logger 实例。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component: 组件名称,如 "CLIENT"、"LLM"、"MCP"
|
||||||
|
level: 日志级别,默认 DEBUG
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
配置好的 Logger 实例
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger(f"agent.{component}")
|
||||||
|
logger.setLevel(level)
|
||||||
|
|
||||||
|
# 避免重复添加 Handler
|
||||||
|
if logger.handlers:
|
||||||
|
return logger
|
||||||
|
|
||||||
|
# 终端 Handler(彩色)
|
||||||
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
console_handler.setFormatter(ColorFormatter())
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# 文件 Handler(纯文本)
|
||||||
|
log_dir = Path("logs")
|
||||||
|
log_dir.mkdir(exist_ok=True)
|
||||||
|
file_handler = logging.FileHandler(log_dir / "agent.log", encoding="utf-8")
|
||||||
|
file_handler.setFormatter(
|
||||||
|
logging.Formatter("[%(asctime)s] [%(name)s] %(levelname)s: %(message)s")
|
||||||
|
)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# 防止日志向上传播到 root logger
|
||||||
|
logger.propagate = False
|
||||||
|
return logger
|
||||||
Loading…
Reference in New Issue