codebase_maintainer.py 17 KB

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