codebase_maintainer.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. """
  2. CodebaseMaintainer - 代码库维护助手
  3. 完整的长程智能体实现,整合:
  4. 1. ContextBuilder - 上下文管理
  5. 2. NoteTool - 结构化笔记
  6. 3. TerminalTool - 即时文件访问
  7. 4. MemoryTool - 对话记忆
  8. 关键改进:使用 Agentic 方式,让 agent 自主决定使用哪些工具
  9. """
  10. from typing import Dict, Any, List, Optional
  11. from datetime import datetime
  12. import json
  13. from hello_agents import HelloAgentsLLM
  14. from hello_agents.agents import FunctionCallAgent
  15. from hello_agents.context import ContextBuilder, ContextConfig, ContextPacket
  16. from hello_agents.tools import MemoryTool, NoteTool, TerminalTool
  17. from hello_agents.tools.registry import ToolRegistry
  18. from hello_agents.core.message import Message
  19. class CodebaseMaintainer:
  20. """代码库维护助手 - 长程智能体示例
  21. 整合 ContextBuilder + NoteTool + TerminalTool + MemoryTool
  22. 实现跨会话的代码库维护任务管理
  23. 核心特性:
  24. - Agent 自主使用工具探索代码库
  25. - 不预定义工作流,完全基于 agent 决策
  26. - 跨会话记忆和上下文管理
  27. """
  28. def __init__(
  29. self,
  30. project_name: str,
  31. codebase_path: str,
  32. llm: Optional[HelloAgentsLLM] = None
  33. ):
  34. self.project_name = project_name
  35. self.codebase_path = codebase_path
  36. self.session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
  37. # 初始化 LLM
  38. self.llm = llm or HelloAgentsLLM()
  39. # 初始化工具
  40. self.memory_tool = MemoryTool(
  41. user_id=project_name,
  42. memory_types=["working"]
  43. )
  44. self.note_tool = NoteTool(workspace=f"./{project_name}_notes")
  45. self.terminal_tool = TerminalTool(workspace=codebase_path, timeout=60)
  46. # 初始化上下文构建器
  47. self.context_builder = ContextBuilder(
  48. memory_tool=self.memory_tool,
  49. rag_tool=None, # 本案例不使用 RAG
  50. config=ContextConfig(
  51. max_tokens=4000,
  52. reserve_ratio=0.15,
  53. min_relevance=0.2,
  54. enable_compression=True
  55. )
  56. )
  57. # 创建工具注册表并注册工具
  58. self.tool_registry = ToolRegistry()
  59. self.tool_registry.register_tool(self.terminal_tool)
  60. self.tool_registry.register_tool(self.note_tool)
  61. self.tool_registry.register_tool(self.memory_tool)
  62. # 创建 Agent
  63. self.agent = FunctionCallAgent(
  64. name="CodebaseMaintainer",
  65. llm=self.llm,
  66. system_prompt=self._build_base_system_prompt(),
  67. tool_registry=self.tool_registry,
  68. enable_tool_calling=True,
  69. max_tool_iterations=30
  70. )
  71. # 对话历史
  72. self.conversation_history: List[Message] = []
  73. # 统计信息
  74. self.stats = {
  75. "session_start": datetime.now(),
  76. "commands_executed": 0,
  77. "notes_created": 0,
  78. "issues_found": 0,
  79. "tool_calls": 0
  80. }
  81. print(f"✅ 代码库维护助手已初始化: {project_name} (Agentic Mode)")
  82. print(f"📁 工作目录: {codebase_path}")
  83. print(f"🆔 会话ID: {self.session_id}")
  84. print(f"🔧 可用工具: {', '.join(self.tool_registry.list_tools())}")
  85. def run(self, user_input: str, mode: str = "auto") -> str:
  86. """运行助手(Agentic 方式)
  87. Args:
  88. user_input: 用户输入
  89. mode: 运行模式提示(给 agent 提供方向性建议)
  90. - "auto": 自动决策是否使用工具
  91. - "explore": 建议 agent 侧重代码探索
  92. - "analyze": 建议 agent 侧重问题分析
  93. - "plan": 建议 agent 侧重任务规划
  94. Returns:
  95. str: 助手的回答
  96. """
  97. print(f"\n{'='*80}")
  98. print(f"👤 用户: {user_input}")
  99. print(f"{'='*80}\n")
  100. # 第一步: 检索相关笔记(为 agent 提供上下文)
  101. relevant_notes = self._retrieve_relevant_notes(user_input)
  102. note_packets = self._notes_to_packets(relevant_notes)
  103. # 第二步: 构建优化的上下文
  104. context = self.context_builder.build(
  105. user_query=user_input,
  106. conversation_history=self.conversation_history,
  107. system_instructions=self._build_system_instructions(mode),
  108. additional_packets=note_packets
  109. )
  110. # 第三步: 让 Agent 自主决策和使用工具
  111. print("🤖 Agent 正在思考并决定使用哪些工具...\n")
  112. # 更新 agent 的系统提示(包含上下文)
  113. self.agent.system_prompt = context
  114. # 调用 agent(agent 会自主决定是否使用工具)
  115. response = self.agent.run(user_input)
  116. # 第四步: 统计工具使用情况
  117. self._track_tool_usage()
  118. # 第五步: 更新对话历史
  119. self._update_history(user_input, response)
  120. print(f"\n🤖 助手: {response}\n")
  121. print(f"{'='*80}\n")
  122. return response
  123. def _build_base_system_prompt(self) -> str:
  124. """构建基础系统提示"""
  125. return f"""你是 {self.project_name} 项目的代码库维护助手。
  126. 你的核心能力:
  127. 1. 使用 TerminalTool 探索代码库
  128. - 你可以执行任何 shell 命令: ls, cat, grep, find, git 等
  129. - 工作目录: {self.codebase_path}
  130. 2. 使用 NoteTool 记录发现和任务
  131. - 创建笔记记录重要发现
  132. - 笔记类型: blocker(阻塞问题)、action(行动计划)、task_state(任务状态)、conclusion(结论)
  133. 3. 使用 MemoryTool 存储关键信息
  134. - 记住重要的上下文信息
  135. - 跨会话保持连贯性
  136. 当前会话ID: {self.session_id}
  137. 重要原则:
  138. - 你要自主决定使用哪些工具、执行什么命令
  139. - 探索代码库时,先了解整体结构,再深入细节
  140. - 发现重要信息时,主动使用 NoteTool 记录
  141. - 保持回答的专业性和实用性
  142. """
  143. def _track_tool_usage(self):
  144. """统计工具使用情况"""
  145. # 从 agent 的执行历史中统计
  146. if hasattr(self.agent, 'message_history'):
  147. for msg in self.agent.message_history[-10:]: # 只看最近10条
  148. if msg.role == "tool":
  149. self.stats["tool_calls"] += 1
  150. # 根据工具名统计
  151. if "terminal" in str(msg.content).lower() or "command" in str(msg.content).lower():
  152. self.stats["commands_executed"] += 1
  153. elif "note" in str(msg.content).lower():
  154. if "create" in str(msg.content).lower():
  155. self.stats["notes_created"] += 1
  156. def _retrieve_relevant_notes(self, query: str, limit: int = 3) -> List[Dict]:
  157. """检索相关笔记"""
  158. try:
  159. # 优先检索 blocker
  160. blockers_raw = self.note_tool.run({
  161. "action": "list",
  162. "note_type": "blocker",
  163. "limit": 2
  164. })
  165. blockers = self._normalize_note_results(blockers_raw)
  166. # 搜索相关笔记
  167. search_results_raw = self.note_tool.run({
  168. "action": "search",
  169. "query": query,
  170. "limit": limit
  171. })
  172. search_results = self._normalize_note_results(search_results_raw)
  173. # 合并去重
  174. all_notes = {}
  175. for note in blockers + search_results:
  176. if not isinstance(note, dict):
  177. continue
  178. note_id = note.get('note_id') or note.get('id')
  179. if not note_id:
  180. continue
  181. if note_id not in all_notes:
  182. all_notes[note_id] = note
  183. return list(all_notes.values())[:limit]
  184. except Exception as e:
  185. print(f"[WARNING] 笔记检索失败: {e}")
  186. return []
  187. def _normalize_note_results(self, result: Any) -> List[Dict]:
  188. """将笔记工具的返回值转换为笔记字典列表"""
  189. if not result:
  190. return []
  191. if isinstance(result, dict):
  192. return [result]
  193. if isinstance(result, list):
  194. return [item for item in result if isinstance(item, dict)]
  195. if isinstance(result, str):
  196. text = result.strip()
  197. if not text:
  198. return []
  199. if text.startswith("{") or text.startswith("["):
  200. try:
  201. parsed = json.loads(text)
  202. return self._normalize_note_results(parsed)
  203. except Exception:
  204. return []
  205. return []
  206. return []
  207. def _notes_to_packets(self, notes: List[Dict]) -> List[ContextPacket]:
  208. """将笔记转换为上下文包"""
  209. packets = []
  210. for note in notes:
  211. if not isinstance(note, dict):
  212. continue
  213. # 根据笔记类型设置不同的相关性分数
  214. relevance_map = {
  215. "blocker": 0.9,
  216. "action": 0.8,
  217. "task_state": 0.75,
  218. "conclusion": 0.7
  219. }
  220. note_type = note.get('type', 'general')
  221. relevance = relevance_map.get(note_type, 0.6)
  222. content = f"[笔记:{note.get('title', 'Untitled')}]\n类型: {note_type}\n\n{note.get('content', '')}"
  223. updated_at = note.get('updated_at')
  224. try:
  225. note_timestamp = datetime.fromisoformat(updated_at) if updated_at else datetime.now()
  226. except (ValueError, TypeError):
  227. note_timestamp = datetime.now()
  228. packets.append(ContextPacket(
  229. content=content,
  230. timestamp=note_timestamp,
  231. token_count=len(content) // 4,
  232. relevance_score=relevance,
  233. metadata={
  234. "type": "note",
  235. "note_type": note_type,
  236. "note_id": note.get('note_id') or note.get('id')
  237. }
  238. ))
  239. return packets
  240. def _build_system_instructions(self, mode: str) -> str:
  241. """构建系统指令(Agentic 方式)"""
  242. base_instructions = self._build_base_system_prompt()
  243. mode_hints = {
  244. "explore": """
  245. 用户当前关注: 探索代码库
  246. 建议策略:
  247. - 考虑使用 TerminalTool 了解代码结构(如 find, ls, tree)
  248. - 查看关键文件(如 README, 主要模块)
  249. - 将架构信息记录到笔记方便后续查阅
  250. """,
  251. "analyze": """
  252. 用户当前关注: 分析代码质量
  253. 建议策略:
  254. - 考虑使用 grep 查找潜在问题(TODO, FIXME, BUG)
  255. - 分析代码复杂度和结构
  256. - 将发现的问题记录为 blocker 或 action 笔记
  257. """,
  258. "plan": """
  259. 用户当前关注: 任务规划
  260. 建议策略:
  261. - 回顾历史笔记了解当前进度
  262. - 基于已有信息制定行动计划
  263. - 创建或更新 task_state 类型的笔记
  264. """,
  265. "auto": """
  266. 用户当前关注: 自由对话
  267. 建议策略:
  268. - 根据用户需求灵活决策
  269. - 在需要时主动使用工具获取信息
  270. - 不需要时可以直接回答
  271. """
  272. }
  273. return base_instructions + "\n" + mode_hints.get(mode, mode_hints["auto"])
  274. def _update_history(self, user_input: str, response: str):
  275. """更新对话历史"""
  276. self.conversation_history.append(
  277. Message(content=user_input, role="user", timestamp=datetime.now())
  278. )
  279. self.conversation_history.append(
  280. Message(content=response, role="assistant", timestamp=datetime.now())
  281. )
  282. # 限制历史长度(保留最近10轮对话)
  283. if len(self.conversation_history) > 20:
  284. self.conversation_history = self.conversation_history[-20:]
  285. # === 便捷方法 ===
  286. def explore(self, target: str = ".") -> str:
  287. """探索代码库(Agentic 方式)
  288. Agent 会自主决定使用哪些命令来探索代码库
  289. """
  290. return self.run(f"请探索 {target} 的代码结构,了解项目组织方式", mode="explore")
  291. def analyze(self, focus: str = "") -> str:
  292. """分析代码质量(Agentic 方式)
  293. Agent 会自主决定如何分析代码质量
  294. """
  295. query = f"请分析代码质量" + (f",重点关注{focus}" if focus else "")
  296. return self.run(query, mode="analyze")
  297. def plan_next_steps(self) -> str:
  298. """规划下一步任务(Agentic 方式)
  299. Agent 会查看历史笔记并规划下一步
  300. """
  301. return self.run("根据我们之前的分析和当前进度,规划下一步任务", mode="plan")
  302. def execute_command(self, command: str) -> str:
  303. """执行终端命令"""
  304. result = self.terminal_tool.run({"command": command})
  305. self.stats["commands_executed"] += 1
  306. return result
  307. def create_note(
  308. self,
  309. title: str,
  310. content: str,
  311. note_type: str = "general",
  312. tags: List[str] = None
  313. ) -> str:
  314. """创建笔记"""
  315. result = self.note_tool.run({
  316. "action": "create",
  317. "title": title,
  318. "content": content,
  319. "note_type": note_type,
  320. "tags": tags or [self.project_name]
  321. })
  322. self.stats["notes_created"] += 1
  323. return result
  324. def get_stats(self) -> Dict[str, Any]:
  325. """获取统计信息"""
  326. duration = (datetime.now() - self.stats["session_start"]).total_seconds()
  327. # 获取笔记摘要
  328. try:
  329. note_summary = self.note_tool.run({"action": "summary"})
  330. except:
  331. note_summary = {}
  332. return {
  333. "session_info": {
  334. "session_id": self.session_id,
  335. "project": self.project_name,
  336. "duration_seconds": duration
  337. },
  338. "activity": {
  339. "commands_executed": self.stats["commands_executed"],
  340. "notes_created": self.stats["notes_created"],
  341. "issues_found": self.stats["issues_found"]
  342. },
  343. "notes": note_summary
  344. }
  345. def generate_report(self, save_to_file: bool = True) -> Dict[str, Any]:
  346. """生成会话报告"""
  347. report = self.get_stats()
  348. if save_to_file:
  349. report_file = f"maintainer_report_{self.session_id}.json"
  350. with open(report_file, 'w', encoding='utf-8') as f:
  351. json.dump(report, f, ensure_ascii=False, indent=2, default=str)
  352. report["report_file"] = report_file
  353. print(f"📄 报告已保存: {report_file}")
  354. return report
  355. def main():
  356. """主函数 - 演示 CodebaseMaintainer 的使用(Agentic 版本)
  357. 在这个版本中:
  358. - Agent 自主决定使用哪些工具
  359. - 不预定义工作流
  360. - Agent 根据需求灵活探索代码库
  361. """
  362. print("=" * 80)
  363. print("CodebaseMaintainer 演示(Agentic 版本)")
  364. print("=" * 80 + "\n")
  365. # 初始化助手
  366. maintainer = CodebaseMaintainer(
  367. project_name="my_flask_app",
  368. codebase_path="./my_flask_app",
  369. llm=HelloAgentsLLM()
  370. )
  371. # 探索代码库(Agent 自主决定如何探索)
  372. print("\n### 探索代码库(Agent 自主探索)###")
  373. response = maintainer.explore()
  374. # 分析代码质量(Agent 自主决定分析方法)
  375. print("\n### 分析代码质量(Agent 自主分析)###")
  376. response = maintainer.analyze()
  377. # 规划下一步(Agent 基于历史信息规划)
  378. print("\n### 规划下一步任务(Agent 自主规划)###")
  379. response = maintainer.plan_next_steps()
  380. # 生成报告
  381. print("\n### 生成会话报告 ###")
  382. report = maintainer.generate_report()
  383. print(json.dumps(report, indent=2, ensure_ascii=False))
  384. print("\n" + "=" * 80)
  385. print("演示完成!")
  386. print("=" * 80)
  387. if __name__ == "__main__":
  388. main()