| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- """
- CodebaseMaintainer - 代码库维护助手
- 完整的长程智能体实现,整合:
- 1. ContextBuilder - 上下文管理
- 2. NoteTool - 结构化笔记
- 3. TerminalTool - 即时文件访问
- 4. MemoryTool - 对话记忆
- 关键改进:使用 Agentic 方式,让 agent 自主决定使用哪些工具
- """
- from typing import Dict, Any, List, Optional
- from datetime import datetime
- import json
- 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
- class CodebaseMaintainer:
- """代码库维护助手 - 长程智能体示例
- 整合 ContextBuilder + NoteTool + TerminalTool + MemoryTool
- 实现跨会话的代码库维护任务管理
-
- 核心特性:
- - Agent 自主使用工具探索代码库
- - 不预定义工作流,完全基于 agent 决策
- - 跨会话记忆和上下文管理
- """
- def __init__(
- self,
- project_name: str,
- codebase_path: str,
- llm: Optional[HelloAgentsLLM] = None
- ):
- self.project_name = project_name
- self.codebase_path = codebase_path
- self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
- # 初始化 LLM
- self.llm = llm or HelloAgentsLLM()
- # 初始化工具
- self.memory_tool = MemoryTool(
- user_id=project_name,
- memory_types=["working"]
- )
- self.note_tool = NoteTool(workspace=f"./{project_name}_notes")
- self.terminal_tool = TerminalTool(workspace=codebase_path, timeout=60)
- # 初始化上下文构建器
- self.context_builder = ContextBuilder(
- memory_tool=self.memory_tool,
- rag_tool=None, # 本案例不使用 RAG
- config=ContextConfig(
- max_tokens=4000,
- reserve_ratio=0.15,
- min_relevance=0.2,
- enable_compression=True
- )
- )
- # 创建工具注册表并注册工具
- 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] = []
- # 统计信息
- self.stats = {
- "session_start": datetime.now(),
- "commands_executed": 0,
- "notes_created": 0,
- "issues_found": 0,
- "tool_calls": 0
- }
- 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: 运行模式提示(给 agent 提供方向性建议)
- - "auto": 自动决策是否使用工具
- - "explore": 建议 agent 侧重代码探索
- - "analyze": 建议 agent 侧重问题分析
- - "plan": 建议 agent 侧重任务规划
- Returns:
- str: 助手的回答
- """
- print(f"\n{'='*80}")
- print(f"👤 用户: {user_input}")
- print(f"{'='*80}\n")
- # 第一步: 检索相关笔记(为 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
- )
- # 第三步: 让 Agent 自主决策和使用工具
- print("🤖 Agent 正在思考并决定使用哪些工具...\n")
-
- # 更新 agent 的系统提示(包含上下文)
- self.agent.system_prompt = context
-
- # 调用 agent(agent 会自主决定是否使用工具)
- response = self.agent.run(user_input)
- # 第四步: 统计工具使用情况
- self._track_tool_usage()
- # 第五步: 更新对话历史
- self._update_history(user_input, response)
- print(f"\n🤖 助手: {response}\n")
- print(f"{'='*80}\n")
- return response
- def _build_base_system_prompt(self) -> str:
- """构建基础系统提示"""
- return f"""你是 {self.project_name} 项目的代码库维护助手。
- 你的核心能力:
- 1. 使用 TerminalTool 探索代码库
- - 你可以执行任何 shell 命令: ls, cat, grep, find, git 等
- - 工作目录: {self.codebase_path}
-
- 2. 使用 NoteTool 记录发现和任务
- - 创建笔记记录重要发现
- - 笔记类型: blocker(阻塞问题)、action(行动计划)、task_state(任务状态)、conclusion(结论)
-
- 3. 使用 MemoryTool 存储关键信息
- - 记住重要的上下文信息
- - 跨会话保持连贯性
- 当前会话ID: {self.session_id}
- 重要原则:
- - 你要自主决定使用哪些工具、执行什么命令
- - 探索代码库时,先了解整体结构,再深入细节
- - 发现重要信息时,主动使用 NoteTool 记录
- - 保持回答的专业性和实用性
- """
- 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]:
- """检索相关笔记"""
- try:
- # 优先检索 blocker
- blockers_raw = self.note_tool.run({
- "action": "list",
- "note_type": "blocker",
- "limit": 2
- })
- blockers = self._normalize_note_results(blockers_raw)
- # 搜索相关笔记
- search_results_raw = self.note_tool.run({
- "action": "search",
- "query": query,
- "limit": limit
- })
- search_results = self._normalize_note_results(search_results_raw)
- # 合并去重
- all_notes = {}
- for note in blockers + search_results:
- if not isinstance(note, dict):
- continue
- note_id = note.get('note_id') or note.get('id')
- if not note_id:
- continue
- if note_id not in all_notes:
- all_notes[note_id] = note
- return list(all_notes.values())[:limit]
- except Exception as e:
- print(f"[WARNING] 笔记检索失败: {e}")
- return []
- def _normalize_note_results(self, result: Any) -> List[Dict]:
- """将笔记工具的返回值转换为笔记字典列表"""
- if not result:
- return []
- if isinstance(result, dict):
- return [result]
- if isinstance(result, list):
- return [item for item in result if isinstance(item, dict)]
- if isinstance(result, str):
- text = result.strip()
- if not text:
- return []
- if text.startswith("{") or text.startswith("["):
- try:
- parsed = json.loads(text)
- return self._normalize_note_results(parsed)
- except Exception:
- return []
- return []
- return []
- def _notes_to_packets(self, notes: List[Dict]) -> List[ContextPacket]:
- """将笔记转换为上下文包"""
- packets = []
- for note in notes:
- if not isinstance(note, dict):
- continue
- # 根据笔记类型设置不同的相关性分数
- relevance_map = {
- "blocker": 0.9,
- "action": 0.8,
- "task_state": 0.75,
- "conclusion": 0.7
- }
- note_type = note.get('type', 'general')
- relevance = relevance_map.get(note_type, 0.6)
- content = f"[笔记:{note.get('title', 'Untitled')}]\n类型: {note_type}\n\n{note.get('content', '')}"
- updated_at = note.get('updated_at')
- try:
- note_timestamp = datetime.fromisoformat(updated_at) if updated_at else datetime.now()
- except (ValueError, TypeError):
- note_timestamp = datetime.now()
- packets.append(ContextPacket(
- content=content,
- timestamp=note_timestamp,
- token_count=len(content) // 4,
- relevance_score=relevance,
- metadata={
- "type": "note",
- "note_type": note_type,
- "note_id": note.get('note_id') or note.get('id')
- }
- ))
- return packets
- def _build_system_instructions(self, mode: str) -> str:
- """构建系统指令(Agentic 方式)"""
- base_instructions = self._build_base_system_prompt()
- mode_hints = {
- "explore": """
- 用户当前关注: 探索代码库
- 建议策略:
- - 考虑使用 TerminalTool 了解代码结构(如 find, ls, tree)
- - 查看关键文件(如 README, 主要模块)
- - 将架构信息记录到笔记方便后续查阅
- """,
- "analyze": """
- 用户当前关注: 分析代码质量
- 建议策略:
- - 考虑使用 grep 查找潜在问题(TODO, FIXME, BUG)
- - 分析代码复杂度和结构
- - 将发现的问题记录为 blocker 或 action 笔记
- """,
- "plan": """
- 用户当前关注: 任务规划
- 建议策略:
- - 回顾历史笔记了解当前进度
- - 基于已有信息制定行动计划
- - 创建或更新 task_state 类型的笔记
- """,
- "auto": """
- 用户当前关注: 自由对话
- 建议策略:
- - 根据用户需求灵活决策
- - 在需要时主动使用工具获取信息
- - 不需要时可以直接回答
- """
- }
- return base_instructions + "\n" + mode_hints.get(mode, mode_hints["auto"])
- def _update_history(self, user_input: str, response: str):
- """更新对话历史"""
- self.conversation_history.append(
- Message(content=user_input, role="user", timestamp=datetime.now())
- )
- self.conversation_history.append(
- Message(content=response, role="assistant", timestamp=datetime.now())
- )
- # 限制历史长度(保留最近10轮对话)
- if len(self.conversation_history) > 20:
- self.conversation_history = self.conversation_history[-20:]
- # === 便捷方法 ===
- def explore(self, target: str = ".") -> str:
- """探索代码库(Agentic 方式)
-
- Agent 会自主决定使用哪些命令来探索代码库
- """
- return self.run(f"请探索 {target} 的代码结构,了解项目组织方式", mode="explore")
- def analyze(self, focus: str = "") -> str:
- """分析代码质量(Agentic 方式)
-
- Agent 会自主决定如何分析代码质量
- """
- query = f"请分析代码质量" + (f",重点关注{focus}" if focus else "")
- return self.run(query, mode="analyze")
- def plan_next_steps(self) -> str:
- """规划下一步任务(Agentic 方式)
-
- Agent 会查看历史笔记并规划下一步
- """
- return self.run("根据我们之前的分析和当前进度,规划下一步任务", mode="plan")
- def execute_command(self, command: str) -> str:
- """执行终端命令"""
- result = self.terminal_tool.run({"command": command})
- self.stats["commands_executed"] += 1
- return result
- def create_note(
- self,
- title: str,
- content: str,
- note_type: str = "general",
- tags: List[str] = None
- ) -> str:
- """创建笔记"""
- result = self.note_tool.run({
- "action": "create",
- "title": title,
- "content": content,
- "note_type": note_type,
- "tags": tags or [self.project_name]
- })
- self.stats["notes_created"] += 1
- return result
- def get_stats(self) -> Dict[str, Any]:
- """获取统计信息"""
- duration = (datetime.now() - self.stats["session_start"]).total_seconds()
- # 获取笔记摘要
- try:
- note_summary = self.note_tool.run({"action": "summary"})
- except:
- note_summary = {}
- return {
- "session_info": {
- "session_id": self.session_id,
- "project": self.project_name,
- "duration_seconds": duration
- },
- "activity": {
- "commands_executed": self.stats["commands_executed"],
- "notes_created": self.stats["notes_created"],
- "issues_found": self.stats["issues_found"]
- },
- "notes": note_summary
- }
- def generate_report(self, save_to_file: bool = True) -> Dict[str, Any]:
- """生成会话报告"""
- report = self.get_stats()
- if save_to_file:
- report_file = f"maintainer_report_{self.session_id}.json"
- with open(report_file, 'w', encoding='utf-8') as f:
- json.dump(report, f, ensure_ascii=False, indent=2, default=str)
- report["report_file"] = report_file
- print(f"📄 报告已保存: {report_file}")
- return report
- def main():
- """主函数 - 演示 CodebaseMaintainer 的使用(Agentic 版本)
-
- 在这个版本中:
- - Agent 自主决定使用哪些工具
- - 不预定义工作流
- - Agent 根据需求灵活探索代码库
- """
- print("=" * 80)
- print("CodebaseMaintainer 演示(Agentic 版本)")
- print("=" * 80 + "\n")
- # 初始化助手
- maintainer = CodebaseMaintainer(
- project_name="my_flask_app",
- codebase_path="./my_flask_app",
- llm=HelloAgentsLLM()
- )
- # 探索代码库(Agent 自主决定如何探索)
- print("\n### 探索代码库(Agent 自主探索)###")
- response = maintainer.explore()
- # 分析代码质量(Agent 自主决定分析方法)
- print("\n### 分析代码质量(Agent 自主分析)###")
- response = maintainer.analyze()
- # 规划下一步(Agent 基于历史信息规划)
- print("\n### 规划下一步任务(Agent 自主规划)###")
- response = maintainer.plan_next_steps()
- # 生成报告
- print("\n### 生成会话报告 ###")
- report = maintainer.generate_report()
- print(json.dumps(report, indent=2, ensure_ascii=False))
- print("\n" + "=" * 80)
- print("演示完成!")
- print("=" * 80)
- if __name__ == "__main__":
- main()
|