ソースを参照

Update codebase_maintainer.py

Tao Sun 7 ヶ月 前
コミット
fd5dc46aa4
1 ファイル変更135 行追加170 行削除
  1. 135 170
      code/chapter9/codebase_maintainer.py

+ 135 - 170
code/chapter9/codebase_maintainer.py

@@ -7,16 +7,18 @@ CodebaseMaintainer - 代码库维护助手
 3. TerminalTool - 即时文件访问
 4. MemoryTool - 对话记忆
 
-实现跨会话的代码库维护任务管理
+关键改进:使用 Agentic 方式,让 agent 自主决定使用哪些工具
 """
 
 from typing import Dict, Any, List, Optional
 from datetime import datetime
 import json
 
-from hello_agents import SimpleAgent, HelloAgentsLLM
+from hello_agents import HelloAgentsLLM
+from hello_agents.agents import FunctionCallAgent
 from hello_agents.context import ContextBuilder, ContextConfig, ContextPacket
 from hello_agents.tools import MemoryTool, NoteTool, TerminalTool
+from hello_agents.tools.registry import ToolRegistry
 from hello_agents.core.message import Message
 
 
@@ -25,6 +27,11 @@ class CodebaseMaintainer:
 
     整合 ContextBuilder + NoteTool + TerminalTool + MemoryTool
     实现跨会话的代码库维护任务管理
+    
+    核心特性:
+    - Agent 自主使用工具探索代码库
+    - 不预定义工作流,完全基于 agent 决策
+    - 跨会话记忆和上下文管理
     """
 
     def __init__(
@@ -60,6 +67,22 @@ class CodebaseMaintainer:
             )
         )
 
+        # 创建工具注册表并注册工具
+        self.tool_registry = ToolRegistry()
+        self.tool_registry.register_tool(self.terminal_tool)
+        self.tool_registry.register_tool(self.note_tool)
+        self.tool_registry.register_tool(self.memory_tool)
+
+        # 创建 Agent
+        self.agent = FunctionCallAgent(
+            name="CodebaseMaintainer",
+            llm=self.llm,
+            system_prompt=self._build_base_system_prompt(),
+            tool_registry=self.tool_registry,
+            enable_tool_calling=True,
+            max_tool_iterations=30
+        )
+
         # 对话历史
         self.conversation_history: List[Message] = []
 
@@ -68,23 +91,25 @@ class CodebaseMaintainer:
             "session_start": datetime.now(),
             "commands_executed": 0,
             "notes_created": 0,
-            "issues_found": 0
+            "issues_found": 0,
+            "tool_calls": 0
         }
 
-        print(f"✅ 代码库维护助手已初始化: {project_name}")
+        print(f"✅ 代码库维护助手已初始化: {project_name} (Agentic Mode)")
         print(f"📁 工作目录: {codebase_path}")
         print(f"🆔 会话ID: {self.session_id}")
+        print(f"🔧 可用工具: {', '.join(self.tool_registry.list_tools())}")
 
     def run(self, user_input: str, mode: str = "auto") -> str:
-        """运行助手
+        """运行助手(Agentic 方式)
 
         Args:
             user_input: 用户输入
-            mode: 运行模式
+            mode: 运行模式提示(给 agent 提供方向性建议)
                 - "auto": 自动决策是否使用工具
-                - "explore": 侧重代码探索
-                - "analyze": 侧重问题分析
-                - "plan": 侧重任务规划
+                - "explore": 建议 agent 侧重代码探索
+                - "analyze": 建议 agent 侧重问题分析
+                - "plan": 建议 agent 侧重任务规划
 
         Returns:
             str: 助手的回答
@@ -93,39 +118,31 @@ class CodebaseMaintainer:
         print(f"👤 用户: {user_input}")
         print(f"{'='*80}\n")
 
-        # 第一步:根据模式执行预处理
-        pre_context = self._preprocess_by_mode(user_input, mode)
-
-        # 第二步:检索相关笔记
+        # 第一步: 检索相关笔记(为 agent 提供上下文)
         relevant_notes = self._retrieve_relevant_notes(user_input)
         note_packets = self._notes_to_packets(relevant_notes)
 
-        # 第三步:构建优化的上下文
+        # 第二步: 构建优化的上下文
         context = self.context_builder.build(
             user_query=user_input,
             conversation_history=self.conversation_history,
             system_instructions=self._build_system_instructions(mode),
-            additional_packets=note_packets + pre_context
+            additional_packets=note_packets
         )
 
-        # 第四步:调用 LLM
-        print("🤖 正在思考...")
-        messages = [
-            {
-                "role": "system",
-                "content": context
-            },
-            {
-                "role": "user",
-                "content": user_input
-            }
-        ]
-        response = self.llm.invoke(messages)
+        # 第三步: 让 Agent 自主决策和使用工具
+        print("🤖 Agent 正在思考并决定使用哪些工具...\n")
+        
+        # 更新 agent 的系统提示(包含上下文)
+        self.agent.system_prompt = context
+        
+        # 调用 agent(agent 会自主决定是否使用工具)
+        response = self.agent.run(user_input)
 
-        # 第五步:后处理
-        self._postprocess_response(user_input, response)
+        # 第四步: 统计工具使用情况
+        self._track_tool_usage()
 
-        # 第六步:更新对话历史
+        # 第五步: 更新对话历史
         self._update_history(user_input, response)
 
         print(f"\n🤖 助手: {response}\n")
@@ -133,71 +150,45 @@ class CodebaseMaintainer:
 
         return response
 
-    def _preprocess_by_mode(
-        self,
-        user_input: str,
-        mode: str
-    ) -> List[ContextPacket]:
-        """根据模式执行预处理,收集相关信息"""
-        packets = []
-
-        if mode == "explore" or mode == "auto":
-            # 探索模式:自动查看项目结构
-            print("🔍 探索代码库结构...")
-
-            structure = self.terminal_tool.run({"command": "find . -type f -name '*.py' | head -n 20"})
-            self.stats["commands_executed"] += 1
-
-            packets.append(ContextPacket(
-                content=f"[代码库结构]\n{structure}",
-                timestamp=datetime.now(),
-                token_count=len(structure) // 4,
-                relevance_score=0.6,
-                metadata={"type": "code_structure", "source": "terminal"}
-            ))
-
-        if mode == "analyze":
-            # 分析模式:检查代码复杂度和问题
-            print("📊 分析代码质量...")
-
-            # 统计代码行数
-            loc = self.terminal_tool.run({"command": "find . -name '*.py' -exec wc -l {} + | tail -n 1"})
-
-            # 查找 TODO 和 FIXME
-            todos = self.terminal_tool.run({"command": "grep -rn 'TODO\\|FIXME' --include='*.py' | head -n 10"})
+    def _build_base_system_prompt(self) -> str:
+        """构建基础系统提示"""
+        return f"""你是 {self.project_name} 项目的代码库维护助手。
 
-            self.stats["commands_executed"] += 2
-
-            packets.append(ContextPacket(
-                content=f"[代码统计]\n{loc}\n\n[待办事项]\n{todos}",
-                timestamp=datetime.now(),
-                token_count=(len(loc) + len(todos)) // 4,
-                relevance_score=0.7,
-                metadata={"type": "code_analysis", "source": "terminal"}
-            ))
+你的核心能力:
+1. 使用 TerminalTool 探索代码库
+   - 你可以执行任何 shell 命令: ls, cat, grep, find, git 等
+   - 工作目录: {self.codebase_path}
+   
+2. 使用 NoteTool 记录发现和任务
+   - 创建笔记记录重要发现
+   - 笔记类型: blocker(阻塞问题)、action(行动计划)、task_state(任务状态)、conclusion(结论)
+   
+3. 使用 MemoryTool 存储关键信息
+   - 记住重要的上下文信息
+   - 跨会话保持连贯性
 
-        if mode == "plan":
-            # 规划模式:加载最近的笔记
-            print("📋 加载任务规划...")
+当前会话ID: {self.session_id}
 
-            task_notes_raw = self.note_tool.run({
-                "action": "list",
-                "note_type": "task_state",
-                "limit": 3
-            })
-            task_notes = self._normalize_note_results(task_notes_raw)
-
-            if task_notes:
-                content = "\n".join([f"- {note.get('title', 'Untitled')}" for note in task_notes])
-                packets.append(ContextPacket(
-                    content=f"[当前任务]\n{content}",
-                    timestamp=datetime.now(),
-                    token_count=len(content) // 4,
-                    relevance_score=0.8,
-                    metadata={"type": "task_plan", "source": "notes"}
-                ))
+重要原则:
+- 你要自主决定使用哪些工具、执行什么命令
+- 探索代码库时,先了解整体结构,再深入细节
+- 发现重要信息时,主动使用 NoteTool 记录
+- 保持回答的专业性和实用性
+"""
 
-        return packets
+    def _track_tool_usage(self):
+        """统计工具使用情况"""
+        # 从 agent 的执行历史中统计
+        if hasattr(self.agent, 'message_history'):
+            for msg in self.agent.message_history[-10:]:  # 只看最近10条
+                if msg.role == "tool":
+                    self.stats["tool_calls"] += 1
+                    # 根据工具名统计
+                    if "terminal" in str(msg.content).lower() or "command" in str(msg.content).lower():
+                        self.stats["commands_executed"] += 1
+                    elif "note" in str(msg.content).lower():
+                        if "create" in str(msg.content).lower():
+                            self.stats["notes_created"] += 1
 
     def _retrieve_relevant_notes(self, query: str, limit: int = 3) -> List[Dict]:
         """检索相关笔记"""
@@ -300,87 +291,46 @@ class CodebaseMaintainer:
         return packets
 
     def _build_system_instructions(self, mode: str) -> str:
-        """构建系统指令"""
-        base_instructions = f"""你是 {self.project_name} 项目的代码库维护助手。
-
-你的核心能力:
-1. 使用 TerminalTool 探索代码库(ls, cat, grep, find等)
-2. 使用 NoteTool 记录发现和任务
-3. 基于历史笔记提供连贯的建议
-
-当前会话ID: {self.session_id}
-"""
+        """构建系统指令(Agentic 方式)"""
+        base_instructions = self._build_base_system_prompt()
 
-        mode_specific = {
+        mode_hints = {
             "explore": """
-当前模式: 探索代码库
+用户当前关注: 探索代码库
 
-你应该:
-- 主动使用 terminal 命令了解代码结构
-- 识别关键模块和文件
-- 记录项目架构到笔记
+建议策略:
+- 考虑使用 TerminalTool 了解代码结构(如 find, ls, tree)
+- 查看关键文件(如 README, 主要模块)
+- 将架构信息记录到笔记方便后续查阅
 """,
             "analyze": """
-当前模式: 分析代码质量
+用户当前关注: 分析代码质量
 
-你应该:
-- 查找代码问题(重复、复杂度、TODO等)
-- 评估代码质量
+建议策略:
+- 考虑使用 grep 查找潜在问题(TODO, FIXME, BUG)
+- 分析代码复杂度和结构
 - 将发现的问题记录为 blocker 或 action 笔记
 """,
             "plan": """
-当前模式: 任务规划
+用户当前关注: 任务规划
 
-你应该:
-- 回顾历史笔记和任务
-- 制定下一步行动计划
-- 更新任务状态笔记
+建议策略:
+- 回顾历史笔记了解当前进度
+- 基于已有信息制定行动计划
+- 创建或更新 task_state 类型的笔记
 """,
             "auto": """
-当前模式: 自动决策
+用户当前关注: 自由对话
 
-你应该:
-- 根据用户需求灵活选择策略
-- 在需要时使用工具
-- 保持回答的专业性和实用性
+建议策略:
+- 根据用户需求灵活决策
+- 在需要时主动使用工具获取信息
+- 不需要时可以直接回答
 """
         }
 
-        return base_instructions + mode_specific.get(mode, mode_specific["auto"])
+        return base_instructions + "\n" + mode_hints.get(mode, mode_hints["auto"])
 
-    def _postprocess_response(self, user_input: str, response: str):
-        """后处理:分析回答,自动记录重要信息"""
-
-        # 如果发现问题,自动创建 blocker 笔记
-        if any(keyword in response.lower() for keyword in ["问题", "bug", "错误", "阻塞"]):
-            try:
-                self.note_tool.run({
-                    "action": "create",
-                    "title": f"发现问题: {user_input[:30]}...",
-                    "content": f"## 用户输入\n{user_input}\n\n## 问题分析\n{response[:500]}...",
-                    "note_type": "blocker",
-                    "tags": [self.project_name, "auto_detected", self.session_id]
-                })
-                self.stats["notes_created"] += 1
-                self.stats["issues_found"] += 1
-                print("📝 已自动创建问题笔记")
-            except Exception as e:
-                print(f"[WARNING] 创建笔记失败: {e}")
-
-        # 如果是任务规划,自动创建 action 笔记
-        elif any(keyword in user_input.lower() for keyword in ["计划", "下一步", "任务", "todo"]):
-            try:
-                self.note_tool.run({
-                    "action": "create",
-                    "title": f"任务规划: {user_input[:30]}...",
-                    "content": f"## 讨论\n{user_input}\n\n## 行动计划\n{response[:500]}...",
-                    "note_type": "action",
-                    "tags": [self.project_name, "planning", self.session_id]
-                })
-                self.stats["notes_created"] += 1
-                print("📝 已自动创建行动计划笔记")
-            except Exception as e:
-                print(f"[WARNING] 创建笔记失败: {e}")
 
     def _update_history(self, user_input: str, response: str):
         """更新对话历史"""
@@ -398,17 +348,26 @@ class CodebaseMaintainer:
     # === 便捷方法 ===
 
     def explore(self, target: str = ".") -> str:
-        """探索代码库"""
-        return self.run(f"请探索 {target} 的代码结构", mode="explore")
+        """探索代码库(Agentic 方式)
+        
+        Agent 会自主决定使用哪些命令来探索代码库
+        """
+        return self.run(f"请探索 {target} 的代码结构,了解项目组织方式", mode="explore")
 
     def analyze(self, focus: str = "") -> str:
-        """分析代码质量"""
-        query = f"请分析代码质量" + (f",重点关注{focus}" if focus else "")
+        """分析代码质量(Agentic 方式)
+        
+        Agent 会自主决定如何分析代码质量
+        """
+        query = f"请分析代码质量" + (f",重点关注{focus}" if focus else "")
         return self.run(query, mode="analyze")
 
     def plan_next_steps(self) -> str:
-        """规划下一步任务"""
-        return self.run("根据当前进度,规划下一步任务", mode="plan")
+        """规划下一步任务(Agentic 方式)
+        
+        Agent 会查看历史笔记并规划下一步
+        """
+        return self.run("根据我们之前的分析和当前进度,规划下一步任务", mode="plan")
 
     def execute_command(self, command: str) -> str:
         """执行终端命令"""
@@ -473,9 +432,15 @@ class CodebaseMaintainer:
 
 
 def main():
-    """主函数 - 演示 CodebaseMaintainer 的使用"""
+    """主函数 - 演示 CodebaseMaintainer 的使用(Agentic 版本)
+    
+    在这个版本中:
+    - Agent 自主决定使用哪些工具
+    - 不预定义工作流
+    - Agent 根据需求灵活探索代码库
+    """
     print("=" * 80)
-    print("CodebaseMaintainer 演示")
+    print("CodebaseMaintainer 演示(Agentic 版本)")
     print("=" * 80 + "\n")
 
     # 初始化助手
@@ -485,16 +450,16 @@ def main():
         llm=HelloAgentsLLM()
     )
 
-    # 探索代码库
-    print("\n### 探索代码库 ###")
+    # 探索代码库(Agent 自主决定如何探索)
+    print("\n### 探索代码库(Agent 自主探索)###")
     response = maintainer.explore()
 
-    # 分析代码质量
-    print("\n### 分析代码质量 ###")
+    # 分析代码质量(Agent 自主决定分析方法)
+    print("\n### 分析代码质量(Agent 自主分析)###")
     response = maintainer.analyze()
 
-    # 规划下一步
-    print("\n### 规划下一步任务 ###")
+    # 规划下一步(Agent 基于历史信息规划)
+    print("\n### 规划下一步任务(Agent 自主规划)###")
     response = maintainer.plan_next_steps()
 
     # 生成报告