Forráskód Böngészése

Merge pull request #53 from fengju0213/st

chore: update
jjyaoao 8 hónapja
szülő
commit
41985c2514

+ 31 - 24
code/chapter9/01_context_builder_basic.py

@@ -19,23 +19,23 @@ def main():
     print("ContextBuilder 基础使用示例")
     print("=" * 80 + "\n")
 
-    # 1. 初始化工具
+    # 1. 初始化工具(Optional)
     print("1. 初始化工具...")
-    memory_tool = MemoryTool(user_id="user123")
-    rag_tool = RAGTool(knowledge_base_path="./knowledge_base")
+    # memory_tool = MemoryTool(user_id="user123")
+    # rag_tool = RAGTool(knowledge_base_path="./knowledge_base")
 
     # 2. 创建 ContextBuilder
     print("2. 创建 ContextBuilder...")
     config = ContextConfig(
         max_tokens=3000,
         reserve_ratio=0.2,
-        min_relevance=0.2,
+        min_relevance=0,#最小相关性阈值,0代表所有历史信息会被保留,
         enable_compression=True
     )
 
     builder = ContextBuilder(
-        memory_tool=memory_tool,
-        rag_tool=rag_tool,
+        # memory_tool=memory_tool,
+        # rag_tool=rag_tool,
         config=config
     )
 
@@ -50,19 +50,19 @@ def main():
 
     # 4. 添加一些记忆
     print("4. 添加记忆...")
-    memory_tool.execute(
-        "add",
-        content="用户正在开发数据分析工具,使用Python和Pandas",
-        memory_type="semantic",
-        importance=0.8
-    )
-
-    memory_tool.execute(
-        "add",
-        content="已完成CSV读取模块的开发",
-        memory_type="episodic",
-        importance=0.7
-    )
+    # memory_tool.execute(
+    #     "add",
+    #     content="用户正在开发数据分析工具,使用Python和Pandas",
+    #     memory_type="semantic",
+    #     importance=0.8
+    # )
+
+    # memory_tool.execute(
+    #     "add",
+    #     content="已完成CSV读取模块的开发",
+    #     memory_type="episodic",
+    #     importance=0.7
+    # )
 
     # 5. 构建上下文
     print("5. 构建上下文...\n")
@@ -82,14 +82,21 @@ def main():
     # 6. 将上下文字符串转换为消息格式供 LLM 使用
     print("6. 将上下文传给 LLM...")
     messages = [
-        {"role": "system", "content": context_str}
+        {"role": "system", "content": context_str},
+        {"role": "user", "content": "请回答"}
+
     ]
 
+    from hello_agents.core.llm import HelloAgentsLLM
+    llm = HelloAgentsLLM(
+        model="ZhipuAI/GLM-4.6",
+        api_key="6ff5219e-410a-4293-8772-0c948bfa691c",
+        base_url="https://api-inference.modelscope.cn/v1/",
+        provider="modelscope"
+    )
     # 注意: 实际使用时需要配置 LLM
-    # from config_helper import get_llm
-    # llm = get_llm()
-    # response = llm.invoke(messages)
-    # print(f"LLM 回答: {response}")
+    response = llm.invoke(messages)
+    print(f"LLM 回答: {response}")
 
     print("✅ ContextBuilder 演示完成!")
     print("\n提示: ContextBuilder 返回的是结构化的上下文字符串,")

+ 18 - 14
code/chapter9/02_context_builder_with_agent.py

@@ -20,13 +20,15 @@ class ContextAwareAgent(SimpleAgent):
     def __init__(self, name: str, llm: HelloAgentsLLM, **kwargs):
         super().__init__(name=name, llm=llm, **kwargs)
 
-        # 初始化上下文构建器
-        self.memory_tool = MemoryTool(user_id=kwargs.get("user_id", "default"))
-        self.rag_tool = RAGTool(knowledge_base_path=kwargs.get("knowledge_base_path", "./kb"))
+        
+        #(Optional)
+        # self.memory_tool = MemoryTool(user_id=kwargs.get("user_id", "default")) 
+        # self.rag_tool = RAGTool(knowledge_base_path=kwargs.get("knowledge_base_path", "./kb"))
 
+        # 初始化上下文构建器
         self.context_builder = ContextBuilder(
-            memory_tool=self.memory_tool,
-            rag_tool=self.rag_tool,
+            # memory_tool=self.memory_tool,
+            # rag_tool=self.rag_tool,
             config=ContextConfig(max_tokens=4000)
         )
 
@@ -43,7 +45,11 @@ class ContextAwareAgent(SimpleAgent):
         )
 
         # 2. 使用优化后的上下文调用 LLM
-        response = self.llm.invoke(optimized_context)
+        messages = [
+            {"role": "system", "content": optimized_context},
+            {"role": "user", "content": user_input}
+        ]
+        response = self.llm.invoke(messages)
 
         # 3. 更新对话历史
         self.conversation_history.append(
@@ -54,12 +60,12 @@ class ContextAwareAgent(SimpleAgent):
         )
 
         # 4. 将重要交互记录到记忆系统
-        self.memory_tool.execute(
-            "add",
-            content=f"Q: {user_input}\nA: {response[:200]}...",  # 摘要
-            memory_type="episodic",
-            importance=0.6
-        )
+        # self.memory_tool.execute(
+        #     "add",
+        #     content=f"Q: {user_input}\nA: {response[:200]}...",  # 摘要
+        #     memory_type="episodic",
+        #     importance=0.6
+        # )
 
         return response
 
@@ -82,8 +88,6 @@ def main():
     agent = ContextAwareAgent(
         name="数据分析顾问",
         llm=llm,
-        user_id="user123",
-        knowledge_base_path="./data_science_kb",
         system_prompt="你是一位资深的Python数据工程顾问。"
     )
 

+ 26 - 26
code/chapter9/03_note_tool_operations.py

@@ -12,7 +12,15 @@ NoteTool 基本操作示例
 """
 
 from hello_agents.tools import NoteTool
-import json
+import re
+
+
+def extract_note_id(output: str) -> str:
+    """从 NoteTool 的输出文本中提取 note_id"""
+    match = re.search(r"ID:\s*(note_[0-9_]+)", output)
+    if not match:
+        raise ValueError(f"无法从输出解析 note_id:\n{output}")
+    return match.group(1)
 
 
 def main():
@@ -25,7 +33,7 @@ def main():
 
     # 1. 创建笔记
     print("1. 创建笔记...")
-    note_id_1 = notes.run({
+    create_output_1 = notes.run({
         "action": "create",
         "title": "重构项目 - 第一阶段",
         "content": """## 完成情况
@@ -36,10 +44,11 @@ def main():
         "note_type": "task_state",
         "tags": ["refactoring", "phase1"]
     })
-    print(f"✅ 笔记创建成功,ID: {note_id_1}\n")
+    print(create_output_1 + "\n")
+    note_id_1 = extract_note_id(create_output_1)
 
     # 创建第二个笔记
-    note_id_2 = notes.run({
+    create_output_2 = notes.run({
         "action": "create",
         "title": "依赖冲突问题",
         "content": """## 问题描述
@@ -55,21 +64,20 @@ def main():
         "note_type": "blocker",
         "tags": ["dependency", "urgent"]
     })
-    print(f"✅ 笔记创建成功,ID: {note_id_2}\n")
+    print(create_output_2 + "\n")
+    note_id_2 = extract_note_id(create_output_2)
 
     # 2. 读取笔记
     print("2. 读取笔记...")
-    note = notes.run({
+    note_detail = notes.run({
         "action": "read",
         "note_id": note_id_1
     })
-    print(f"标题: {note['metadata']['title']}")
-    print(f"类型: {note['metadata']['type']}")
-    print(f"内容:\n{note['content']}\n")
+    print(note_detail + "\n")
 
     # 3. 更新笔记
     print("3. 更新笔记...")
-    result = notes.run({
+    update_result = notes.run({
         "action": "update",
         "note_id": note_id_1,
         "content": """## 完成情况
@@ -81,7 +89,7 @@ def main():
 ## 下一步
 先解决依赖冲突,再继续重构业务逻辑层"""
     })
-    print(f"{result}\n")
+    print(update_result + "\n")
 
     # 4. 搜索笔记
     print("4. 搜索笔记...")
@@ -90,10 +98,7 @@ def main():
         "query": "依赖",
         "limit": 5
     })
-    print(f"找到 {len(search_results)} 个相关笔记:")
-    for note in search_results:
-        print(f"  - {note['title']} ({note['type']})")
-    print()
+    print(search_results + "\n")
 
     # 5. 列出笔记
     print("5. 列出所有 blocker 类型的笔记...")
@@ -102,28 +107,23 @@ def main():
         "note_type": "blocker",
         "limit": 10
     })
-    print(f"找到 {len(blockers)} 个 blocker:")
-    for blocker in blockers:
-        print(f"  - {blocker['title']} (更新于: {blocker['updated_at']})")
-    print()
+    print(blockers + "\n")
 
     # 6. 笔记摘要
     print("6. 生成笔记摘要...")
-    summary = notes.run({
+    summary_output = notes.run({
         "action": "summary"
     })
-    print("笔记摘要:")
-    print(json.dumps(summary, indent=2, ensure_ascii=False))
-    print()
+    print(summary_output + "\n")
 
     # 7. 删除笔记 (演示,实际使用时谨慎)
     print("7. 删除笔记 (演示)...")
-    # result = notes.run({
+    # delete_result = notes.run({
     #     "action": "delete",
     #     "note_id": note_id_2
     # })
-    # print(f"{result}\n")
-    print("(已跳过实际删除操作)\n")
+    # print(delete_result + "\n")
+    print(f"(已跳过实际删除操作, 笔记ID: {note_id_2})\n")
 
     print("=" * 80)
     print("NoteTool 操作演示完成!")

+ 83 - 15
code/chapter9/04_note_tool_integration.py

@@ -28,14 +28,14 @@ class ProjectAssistant(SimpleAgent):
         self.project_name = project_name
 
         # 初始化工具
-        self.memory_tool = MemoryTool(user_id=project_name)
-        self.rag_tool = RAGTool(knowledge_base_path=f"./{project_name}_kb")
+        # self.memory_tool = MemoryTool(user_id=project_name)
+        # self.rag_tool = RAGTool(knowledge_base_path=f"./{project_name}_kb")
         self.note_tool = NoteTool(workspace=f"./{project_name}_notes")
 
         # 初始化上下文构建器
         self.context_builder = ContextBuilder(
-            memory_tool=self.memory_tool,
-            rag_tool=self.rag_tool,
+            # memory_tool=self.memory_tool,
+            # rag_tool=self.rag_tool,
             config=ContextConfig(max_tokens=4000)
         )
 
@@ -51,15 +51,19 @@ class ProjectAssistant(SimpleAgent):
         note_packets = self._notes_to_packets(relevant_notes)
 
         # 3. 构建优化的上下文
-        context = self.context_builder.build(
+        optimized_context = self.context_builder.build(
             user_query=user_input,
             conversation_history=self.conversation_history,
             system_instructions=self._build_system_instructions(),
-            custom_packets=note_packets
+            additional_packets=note_packets
         )
 
-        # 4. 调用 LLM
-        response = self.llm.invoke(context)
+        # 4. 调用 LLM (以 messages 数组形式传入)
+        messages = [
+            {"role": "system", "content": optimized_context},
+            {"role": "user", "content": user_input}
+        ]
+        response = self.llm.invoke(messages)
 
         # 5. 如果需要,将交互记录为笔记
         if note_as_action:
@@ -74,43 +78,107 @@ class ProjectAssistant(SimpleAgent):
         """检索相关笔记"""
         try:
             # 优先检索 blocker 和 action 类型的笔记
-            blockers = self.note_tool.run({
+            blockers_raw = self.note_tool.run({
                 "action": "list",
                 "note_type": "blocker",
                 "limit": 2
             })
 
             # 通用搜索
-            search_results = self.note_tool.run({
+            search_results_raw = self.note_tool.run({
                 "action": "search",
                 "query": query,
                 "limit": limit
             })
 
+            blockers = self._ensure_list_of_dicts(blockers_raw)
+            search_results = self._ensure_list_of_dicts(search_results_raw)
+
             # 合并并去重
-            all_notes = {note['note_id']: note for note in blockers + search_results}
+            all_notes = {}
+            for note in blockers + search_results:
+                if not isinstance(note, dict):
+                    continue
+                note_id = (
+                    note.get("note_id")
+                    or note.get("id")
+                    or note.get("uuid")
+                    or note.get("title")
+                    or str(hash(str(note)))
+                )
+                all_notes[note_id] = note
             return list(all_notes.values())[:limit]
 
         except Exception as e:
             print(f"[WARNING] 笔记检索失败: {e}")
             return []
 
+    def _ensure_list_of_dicts(self, data) -> List[Dict]:
+        """将 NoteTool 返回规范化为字典列表"""
+        import json
+        if data is None:
+            return []
+        if isinstance(data, str):
+            try:
+                data = json.loads(data)
+            except Exception:
+                return []
+        if isinstance(data, dict):
+            # 兼容 {"items": [...]} 或单条记录
+            if "items" in data and isinstance(data["items"], list):
+                return [item for item in data["items"] if isinstance(item, dict)]
+            return [data]
+        if isinstance(data, list):
+            return [item for item in data if isinstance(item, dict)]
+        return []
+
     def _notes_to_packets(self, notes: List[Dict]) -> List[ContextPacket]:
         """将笔记转换为上下文包"""
         packets = []
 
         for note in notes:
-            content = f"[笔记:{note['title']}]\n{note['content']}"
+            title = note.get("title", "")
+            body = note.get("content", "")
+            content = f"[笔记:{title}]\n{body}"
+
+            # 安全解析时间戳
+            ts = None
+            for key in ("updated_at", "updatedAt", "time", "timestamp"):
+                if key in note:
+                    ts = note.get(key)
+                    break
+            parsed_ts = None
+            if isinstance(ts, (int, float)):
+                try:
+                    parsed_ts = datetime.fromtimestamp(ts)
+                except Exception:
+                    parsed_ts = None
+            elif isinstance(ts, str):
+                try:
+                    parsed_ts = datetime.fromisoformat(ts)
+                except Exception:
+                    parsed_ts = None
+            if parsed_ts is None:
+                parsed_ts = datetime.now()
+
+            note_type = note.get("type") or note.get("note_type") or "note"
+            note_id = (
+                note.get("note_id")
+                or note.get("id")
+                or note.get("uuid")
+                or title
+                or str(hash(str(note)))
+            )
 
             packets.append(ContextPacket(
                 content=content,
-                timestamp=datetime.fromisoformat(note['updated_at']),
+                timestamp=parsed_ts,
                 token_count=len(content) // 4,  # 简单估算
                 relevance_score=0.75,  # 笔记具有较高相关性
                 metadata={
                     "type": "note",
-                    "note_type": note['type'],
-                    "note_id": note['note_id']
+                    "note_type": note_type,
+                    "note_id": note_id
                 }
             ))
 

+ 19 - 14
code/chapter9/05_terminal_tool_examples.py

@@ -8,8 +8,13 @@ TerminalTool 使用示例
 4. 代码库分析
 """
 
+import os
+from pathlib import Path
 from hello_agents.tools import TerminalTool
 
+# 获取脚本所在目录
+SCRIPT_DIR = Path(__file__).parent.absolute()
+
 
 def demo_exploratory_navigation():
     """演示探索式导航"""
@@ -17,27 +22,27 @@ def demo_exploratory_navigation():
     print("场景1: 探索式导航")
     print("=" * 80 + "\n")
 
-    terminal = TerminalTool(workspace="./my_project")
+    terminal = TerminalTool(workspace=str(SCRIPT_DIR))
 
-    # 第一步:查看项目根目录
-    print("1. 查看项目根目录:")
+    # 第一步:查看当前目录
+    print("1. 查看当前目录:")
     result = terminal.run({"command": "ls -la"})
     print(result)
 
-    # 第二步:进入源代码目录
-    print("\n2. 进入源代码目录:")
-    result = terminal.run({"command": "cd src"})
+    # 第二步:查看Python文件
+    print("\n2. 查看Python文件:")
+    result = terminal.run({"command": "ls -la *.py"})
     print(result)
 
     # 第三步:查找特定文件
     print("\n3. 查找特定模式的文件:")
-    result = terminal.run({"command": "find . -name '*service*.py'"})
+    result = terminal.run({"command": "find . -name '*codebase_maintainer.py'"})
     print(result)
 
     # 第四步:查看文件内容
     print("\n4. 查看文件内容:")
-    result = terminal.run({"command": "cat user_service.py"})
-    print(result[:500] + "..." if len(result) > 500 else result)
+    result = terminal.run({"command": "head -n 20 codebase_maintainer.py"})
+    print(result)
 
 
 def demo_data_file_analysis():
@@ -46,7 +51,7 @@ def demo_data_file_analysis():
     print("场景2: 数据文件分析")
     print("=" * 80 + "\n")
 
-    terminal = TerminalTool(workspace="./data")
+    terminal = TerminalTool(workspace=str(SCRIPT_DIR / "data"))
 
     # 查看 CSV 文件的前几行
     print("1. 查看 CSV 文件前5行:")
@@ -60,7 +65,7 @@ def demo_data_file_analysis():
 
     # 提取和统计产品类别
     print("\n3. 统计产品类别分布:")
-    result = terminal.run({"command": "tail -n +2 sales_2024.csv | cut -d',' -f2 | sort | uniq -c"})
+    result = terminal.run({"command": "tail -n +2 sales_2024.csv | cut -d',' -f3 | sort | uniq -c"})
     print(result)
 
 
@@ -70,7 +75,7 @@ def demo_log_analysis():
     print("场景3: 日志文件分析")
     print("=" * 80 + "\n")
 
-    terminal = TerminalTool(workspace="/var/log")
+    terminal = TerminalTool(workspace=str(SCRIPT_DIR / "logs"))
 
     # 查看最新的错误日志
     print("1. 查看最新的错误日志:")
@@ -94,7 +99,7 @@ def demo_codebase_analysis():
     print("场景4: 代码库分析")
     print("=" * 80 + "\n")
 
-    terminal = TerminalTool(workspace="./codebase")
+    terminal = TerminalTool(workspace=str(SCRIPT_DIR / "codebase"))
 
     # 统计代码行数
     print("1. 统计代码行数:")
@@ -118,7 +123,7 @@ def demo_security_features():
     print("安全特性演示")
     print("=" * 80 + "\n")
 
-    terminal = TerminalTool(workspace="./project")
+    terminal = TerminalTool(workspace=str(SCRIPT_DIR / "project"))
 
     # 尝试执行不允许的命令
     print("1. 尝试执行危险命令 (rm):")

+ 53 - 35
code/chapter9/06_three_day_workflow.py

@@ -8,6 +8,20 @@ CodebaseMaintainer 三天工作流演示
 - 一周后: 检查进度
 """
 
+import os
+# 配置嵌入模型(三选一)
+# 方案一:TF-IDF(最简单,无需额外依赖)
+os.environ['EMBED_MODEL_TYPE'] = 'tfidf'
+os.environ['EMBED_MODEL_NAME'] = ''  # 重要:必须清空,否则会传递不兼容的参数
+# 方案二:本地Transformer(需要: pip install sentence-transformers 和 HF token)
+# os.environ['EMBED_MODEL_TYPE'] = 'local'
+# os.environ['EMBED_MODEL_NAME'] = 'sentence-transformers/all-MiniLM-L6-v2'
+# os.environ['HF_TOKEN'] = 'your_hf_token_here'  # 或使用 huggingface-cli login
+# 方案三:通义千问(需要API key)
+# os.environ['EMBED_MODEL_TYPE'] = 'dashscope'
+# os.environ['EMBED_MODEL_NAME'] = 'text-embedding-v3'
+# os.environ['EMBED_API_KEY'] = 'your_api_key_here'
+
 from hello_agents import HelloAgentsLLM
 from datetime import datetime
 import json
@@ -31,8 +45,8 @@ def day_1_exploration(maintainer):
     print(f"\n助手总结:\n{response[:500]}...\n")
 
     # 2. 深入分析某个模块
-    print("### 2. 深入分析数据模型 ###")
-    response = maintainer.run("请分析 app/models/ 目录下的数据模型设计")
+    print("### 2. 分析数据处理模块 ###")
+    response = maintainer.run("请查看 data_processor.py 文件,分析其代码设计")
     print(f"\n助手总结:\n{response[:500]}...\n")
 
     # 模拟时间流逝
@@ -46,14 +60,14 @@ def day_2_analysis(maintainer):
     print("=" * 80 + "\n")
 
     # 1. 整体质量分析
-    print("### 1. 整体代码质量分析 ###")
+    print("### 1. 查找所有 TODO 注释 ###")
     response = maintainer.analyze()
     print(f"\n助手总结:\n{response[:500]}...\n")
 
     # 2. 查看具体问题
-    print("### 2. 深入分析问题方法 ###")
+    print("### 2. 分析 API 客户端代码 ###")
     response = maintainer.run(
-        "请查看 order_service.py 的 process_order 方法,给出重构建议"
+        "请分析 api_client.py 的代码质量,特别是错误处理部分,给出改进建议"
     )
     print(f"\n助手总结:\n{response[:500]}...\n")
 
@@ -77,23 +91,23 @@ def day_3_planning(maintainer):
     maintainer.create_note(
         title="本周重构计划 - Week 1",
         content="""## 目标
-完成数据模型层的优化
+完成代码质量改进和 TODO 清理
 
 ## 任务清单
-- [ ] 为 User.email 添加唯一约束
-- [ ] 为 Order 添加 created_at, updated_at 字段
-- [ ] 编写数据库迁移脚本
-- [ ] 更新相关测试用例
+- [ ] 实现 data_processor.py 中的数据验证逻辑
+- [ ] 添加 api_client.py 的重试和错误处理机制
+- [ ] 优化 utils.py 的格式化逻辑
+- [ ] 补充 models.py 的用户验证方法
 
 ## 时间安排
-- 周一: 设计迁移脚本
-- 周二-周三: 执行迁移并测试
-- 周四: 更新测试用例
-- 周五: Code Review
+- 周一: 实现数据验证逻辑
+- 周二: 完善错误处理
+- 周三-周四: 优化工具函数
+- 周五: Code Review 和测试
 
 ## 风险
-- 数据库迁移可能影响线上环境,需要在非高峰期执行
-- 现有数据中可能存在重复email,需要先清理
+- 新增验证逻辑可能影响现有功能
+- 需要充分的单元测试覆盖
 """,
         note_type="task_state",
         tags=["refactoring", "week1", "high_priority"]
@@ -133,17 +147,17 @@ def demonstrate_cross_session_continuity():
     # 第一次会话
     print("### 第一次会话 (session_1) ###")
     maintainer_1 = CodebaseMaintainer(
-        project_name="my_flask_app",
-        codebase_path="./my_flask_app",
+        project_name="demo_codebase",
+        codebase_path="./codebase",
         llm=HelloAgentsLLM()
     )
 
     # 创建一些笔记
     maintainer_1.create_note(
-        title="数据模型问题",
-        content="User.email 缺少唯一约束",
+        title="代码质量问题",
+        content="发现多处 TODO 注释需要实现,特别是数据验证和错误处理部分",
         note_type="blocker",
-        tags=["database", "urgent"]
+        tags=["quality", "urgent"]
     )
 
     stats_1 = maintainer_1.get_stats()
@@ -155,14 +169,14 @@ def demonstrate_cross_session_continuity():
     # 第二次会话 (新的会话ID,但笔记被保留)
     print("### 第二次会话 (session_2) ###")
     maintainer_2 = CodebaseMaintainer(
-        project_name="my_flask_app",  # 同一个项目
-        codebase_path="./my_flask_app",
+        project_name="demo_codebase",  # 同一个项目
+        codebase_path="./codebase",
         llm=HelloAgentsLLM()
     )
 
     # 检索之前的笔记
     response = maintainer_2.run(
-        "我们之前发现了什么问题?现在应该如何处理?"
+        "我们之前发现了什么代码质量问题?现在应该优先处理哪些?"
     )
     print(f"\n助手回答:\n{response[:300]}...\n")
 
@@ -183,34 +197,34 @@ def demonstrate_tool_synergy():
 
     maintainer = CodebaseMaintainer(
         project_name="synergy_demo",
-        codebase_path="./demo_project",
+        codebase_path="./codebase",
         llm=HelloAgentsLLM()
     )
 
     # 1. TerminalTool 发现问题
-    print("### 1. TerminalTool 发现项目结构 ###")
-    structure = maintainer.execute_command("ls -la")
-    print(f"项目结构:\n{structure[:200]}...\n")
+    print("### 1. TerminalTool 查找 TODO 注释 ###")
+    todos = maintainer.execute_command("grep -rn 'TODO' --include='*.py' .")
+    print(f"发现的 TODO:\n{todos[:300]}...\n")
 
     # 2. NoteTool 记录发现
     print("### 2. NoteTool 记录发现 ###")
     maintainer.create_note(
-        title="项目结构分析",
-        content=f"项目包含以下主要目录:\n{structure}",
+        title="待实现功能清单",
+        content=f"通过代码扫描发现以下待实现功能:\n{todos[:500]}",
         note_type="conclusion",
-        tags=["structure", "analysis"]
+        tags=["todo", "analysis"]
     )
     print("✅ 已记录到笔记\n")
 
     # 3. MemoryTool 存储关键信息 (通过对话)
     print("### 3. MemoryTool 存储关键信息 ###")
-    response = maintainer.run("项目的主要结构是什么?")
+    response = maintainer.run("代码库中有哪些待实现的功能?")
     print(f"助手回答:\n{response[:200]}...\n")
 
     # 4. ContextBuilder 整合所有信息
     print("### 4. ContextBuilder 整合所有信息 ###")
     response = maintainer.run(
-        "基于我们之前的分析,项目有哪些需要改进的地方?"
+        "基于我们的代码分析,应该优先实现哪些 TODO 功能?"
     )
     print(f"助手回答:\n{response[:300]}...\n")
 
@@ -227,11 +241,15 @@ def main():
     print("=" * 80)
     print("CodebaseMaintainer 三天工作流演示")
     print("=" * 80)
+    
+    print("\n💡 使用我们在 chapter9 创建的示例代码库")
+    print("📁 代码库路径: ./codebase")
+    print("📦 包含文件: data_processor.py, api_client.py, utils.py, models.py\n")
 
     # 初始化助手
     maintainer = CodebaseMaintainer(
-        project_name="my_flask_app",
-        codebase_path="./my_flask_app",
+        project_name="demo_codebase",
+        codebase_path="./codebase",
         llm=HelloAgentsLLM()
     )
 

+ 321 - 0
code/chapter9/README.md

@@ -0,0 +1,321 @@
+# Chapter 9 - 上下文工程示例代码
+
+本目录包含第九章"上下文工程"的所有示例代码和演示文件。
+
+## 📁 目录结构
+
+```
+chapter9/
+├── 01_context_builder_basic.py          # ContextBuilder 基础用法
+├── 02_context_builder_with_agent.py     # ContextBuilder 与 Agent 集成
+├── 03_note_tool_operations.py           # NoteTool 基本操作
+├── 04_note_tool_integration.py          # NoteTool 高级集成
+├── 05_terminal_tool_examples.py         # TerminalTool 使用示例
+├── 06_three_day_workflow.py             # 完整三天工作流演示
+├── codebase_maintainer.py               # 代码库维护助手(核心组件)
+├── codebase/                            # 示例代码库
+│   ├── data_processor.py
+│   ├── api_client.py
+│   ├── utils.py
+│   └── models.py
+├── data/                                # 示例数据
+│   └── sales_2024.csv
+├── logs/                                # 示例日志
+│   └── app.log
+└── project/                             # 示例项目
+    ├── README.md
+    └── main.py
+```
+
+## 🚀 快速开始
+
+### 1. 配置嵌入模型
+
+所有使用记忆功能的示例都需要配置嵌入模型。最简单的方式:
+
+```python
+import os
+# 使用 TF-IDF(无需额外依赖或下载)
+os.environ['EMBED_MODEL_TYPE'] = 'tfidf'
+os.environ['EMBED_MODEL_NAME'] = ''  # 必须清空
+```
+
+### 2. 运行示例
+
+```bash
+# 进入 chapter9 目录
+cd code/chapter9
+
+# 运行 TerminalTool 示例(无需 LLM)
+python 05_terminal_tool_examples.py
+
+# 运行 NoteTool 基本操作(无需 LLM)
+python 03_note_tool_operations.py
+
+# 运行完整工作流演示(需要配置 LLM)
+python 06_three_day_workflow.py
+```
+
+## 📖 示例说明
+
+### 基础示例
+
+#### 01_context_builder_basic.py
+- ContextBuilder 的基本用法
+- 上下文包(ContextPacket)的创建和管理
+- Token 限制和上下文优先级
+
+#### 02_context_builder_with_agent.py
+- ContextBuilder 与 SimpleAgent 集成
+- 自动上下文管理
+- 对话历史的处理
+
+#### 03_note_tool_operations.py
+- NoteTool 的 CRUD 操作
+- 笔记搜索和标签管理
+- 笔记导出功能
+
+#### 04_note_tool_integration.py
+- NoteTool 与 ContextBuilder 集成
+- 长期项目追踪
+- 基于历史笔记的建议
+
+#### 05_terminal_tool_examples.py
+- TerminalTool 的典型使用场景
+- 探索式导航
+- 数据文件分析
+- 日志分析
+- 代码库分析
+- 安全特性演示
+
+### 高级示例
+
+#### 06_three_day_workflow.py
+**完整的长程智能体工作流演示**,包括:
+- 第一天:探索代码库
+- 第二天:分析代码质量
+- 第三天:规划重构任务
+- 一周后:检查进度
+- 跨会话连贯性演示
+- 三大工具协同演示
+
+使用我们创建的示例代码库(`./codebase`),包含:
+- `data_processor.py` - 数据处理模块(含多个 TODO)
+- `api_client.py` - API 客户端(需要改进错误处理)
+- `utils.py` - 工具函数(需要优化)
+- `models.py` - 数据模型(需要补充验证)
+
+#### codebase_maintainer.py
+**核心组件:代码库维护助手**,集成了:
+- ContextBuilder - 上下文管理
+- NoteTool - 结构化笔记
+- TerminalTool - 即时文件访问
+- MemoryTool - 对话记忆(仅使用 working 记忆)
+
+## ⚙️ 配置说明
+
+### 嵌入模型配置
+
+有三种选择:
+
+#### 方案一:TF-IDF(推荐用于测试)
+
+```python
+import os
+os.environ['EMBED_MODEL_TYPE'] = 'tfidf'
+os.environ['EMBED_MODEL_NAME'] = ''  # 重要!
+```
+
+**优点**:
+- ✅ 无需额外依赖
+- ✅ 无需 API key
+- ✅ 无需下载模型
+
+**缺点**:
+- ⚠️ 语义理解能力较弱
+
+#### 方案二:本地 Transformer(推荐用于离线使用)
+
+```python
+import os
+os.environ['EMBED_MODEL_TYPE'] = 'local'
+os.environ['EMBED_MODEL_NAME'] = 'sentence-transformers/all-MiniLM-L6-v2'
+os.environ['HF_TOKEN'] = 'your_huggingface_token'
+```
+
+**需要**:
+1. 安装依赖:`pip install sentence-transformers`
+2. Hugging Face Token(从 https://huggingface.co/settings/tokens 获取)
+3. 首次运行会下载模型(约 90MB)
+
+**配置 HF Token 的方式**:
+```bash
+# 方式一:使用 huggingface-cli(推荐,一次配置永久使用)
+pip install huggingface-hub
+huggingface-cli login
+
+# 方式二:在代码中设置
+os.environ['HF_TOKEN'] = 'hf_your_token_here'
+
+# 方式三:命令行设置
+export HF_TOKEN="hf_your_token_here"
+```
+
+#### 方案三:通义千问 DashScope(推荐用于生产环境)
+
+```python
+import os
+os.environ['EMBED_MODEL_TYPE'] = 'dashscope'
+os.environ['EMBED_MODEL_NAME'] = 'text-embedding-v3'
+os.environ['EMBED_API_KEY'] = 'your_dashscope_api_key'
+```
+
+**需要**:
+1. 注册:https://dashscope.aliyun.com/
+2. 获取 API key
+3. 安装依赖:`pip install dashscope`
+
+### LLM 配置
+
+如果使用需要 LLM 的示例,需要配置:
+
+```python
+from hello_agents import HelloAgentsLLM
+
+# 使用默认配置(需要设置 OPENAI_API_KEY)
+llm = HelloAgentsLLM()
+
+# 或者明确指定
+llm = HelloAgentsLLM(
+    api_key="your_api_key",
+    base_url="https://api.openai.com/v1",
+    model="gpt-4"
+)
+```
+
+### 记忆功能配置
+
+`codebase_maintainer.py` 已配置为只使用 `working` 记忆,避免需要 Qdrant 向量数据库:
+
+```python
+self.memory_tool = MemoryTool(
+    user_id=project_name,
+    memory_types=["working"]  # 只使用工作记忆
+)
+```
+
+如果需要更强大的记忆功能(episodic, semantic),需要安装并启动 Qdrant:
+
+```bash
+# 使用 Docker 启动 Qdrant
+docker run -p 6333:6333 qdrant/qdrant
+```
+
+## 🔍 示例文件说明
+
+### 演示数据文件
+
+#### data/sales_2024.csv
+包含 40+ 条销售数据,字段包括:
+- date(日期)
+- product(产品)
+- category(类别:Electronics, Furniture)
+- quantity(数量)
+- price(价格)
+- customer_id(客户ID)
+- region(地区:North, South, East, West)
+
+#### logs/app.log
+模拟一天的应用日志,包含:
+- 多种日志级别(INFO, WARNING, ERROR)
+- 多种错误类型(DatabaseConnectionError, ValidationError 等)
+- 时间戳从 2024-01-19 14:00 到 23:30
+
+#### codebase/
+包含 4 个 Python 模块,共 10+ 个 TODO 注释,适合演示:
+- 代码分析
+- TODO 查找
+- 函数定义搜索
+- 代码统计
+
+## 🐛 常见问题
+
+### Q1: RuntimeError: 所有嵌入模型都不可用
+
+**原因**:嵌入模型配置不正确。
+
+**解决**:确保设置了 `EMBED_MODEL_NAME` 为空字符串:
+
+```python
+os.environ['EMBED_MODEL_TYPE'] = 'tfidf'
+os.environ['EMBED_MODEL_NAME'] = ''  # 必须有这行!
+```
+
+### Q2: Qdrant 连接失败
+
+**原因**:默认配置尝试连接 Qdrant 向量数据库。
+
+**解决方案一**(推荐):使用只需 working 记忆的配置(已在 codebase_maintainer.py 中配置)
+
+**解决方案二**:安装并启动 Qdrant:
+```bash
+docker run -p 6333:6333 qdrant/qdrant
+```
+
+### Q3: 下载 Hugging Face 模型失败
+
+**原因**:网络问题或缺少 Token。
+
+**解决方案**:
+1. 配置 HF Token(见上文"方案二")
+2. 或使用镜像:`export HF_ENDPOINT=https://hf-mirror.com`
+3. 或改用 TF-IDF:`os.environ['EMBED_MODEL_TYPE'] = 'tfidf'`
+
+### Q4: TerminalTool 提示"不允许的命令"
+
+**原因**:TerminalTool 有白名单限制,只允许安全的命令。
+
+**解决**:使用允许的命令列表中的命令,如:
+- 文件操作:ls, cat, head, tail, grep, find
+- 文本处理:awk, sed, cut, sort, uniq, wc
+- 其他:pwd, cd, tree, stat
+
+## 📝 运行顺序建议
+
+1. **先运行无需 LLM 的示例**:
+   - `03_note_tool_operations.py` - 了解 NoteTool
+   - `05_terminal_tool_examples.py` - 了解 TerminalTool
+
+2. **配置嵌入模型后运行**:
+   - `01_context_builder_basic.py` - 理解上下文管理
+
+3. **配置 LLM 后运行**:
+   - `02_context_builder_with_agent.py` - Agent 集成
+   - `04_note_tool_integration.py` - 高级集成
+   - `06_three_day_workflow.py` - 完整工作流
+
+## 🎯 学习路径
+
+1. **基础概念** → `01_context_builder_basic.py`
+2. **工具使用** → `03_note_tool_operations.py`, `05_terminal_tool_examples.py`
+3. **Agent 集成** → `02_context_builder_with_agent.py`
+4. **高级应用** → `04_note_tool_integration.py`
+5. **实战案例** → `06_three_day_workflow.py`
+
+## 💡 提示
+
+- 所有示例都在代码开头包含了嵌入模型配置
+- TF-IDF 方案适合快速测试和演示
+- 生产环境建议使用 DashScope 或本地 Transformer
+- codebase_maintainer.py 是完整的实战案例,值得深入学习
+
+## 📚 相关文档
+
+- 详细文档:`docs/chapter9/第九章 上下文工程.md`
+- API 文档:查看各工具类的 docstring
+- 项目主页:README.md
+
+## 🤝 贡献
+
+如有问题或建议,欢迎提 Issue 或 PR!
+

+ 6 - 0
code/chapter9/codebase/__init__.py

@@ -0,0 +1,6 @@
+"""
+代码库包初始化文件
+"""
+
+__version__ = "1.0.0"
+

+ 91 - 0
code/chapter9/codebase/api_client.py

@@ -0,0 +1,91 @@
+"""
+API客户端模块
+用于与外部API交互
+"""
+
+import requests
+from typing import Dict, Any, Optional
+
+
+class APIClient:
+    """API客户端基类"""
+    
+    def __init__(self, base_url: str, api_key: Optional[str] = None):
+        """
+        初始化API客户端
+        
+        Args:
+            base_url: API基础URL
+            api_key: API密钥
+        """
+        self.base_url = base_url
+        self.api_key = api_key
+        self.session = requests.Session()
+        
+        if api_key:
+            self.session.headers.update({
+                'Authorization': f'Bearer {api_key}'
+            })
+    
+    def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
+        """
+        发送GET请求
+        
+        Args:
+            endpoint: API端点
+            params: 查询参数
+            
+        Returns:
+            响应数据
+        """
+        # TODO: 添加重试逻辑
+        url = f"{self.base_url}/{endpoint}"
+        response = self.session.get(url, params=params)
+        response.raise_for_status()
+        return response.json()
+    
+    def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        发送POST请求
+        
+        Args:
+            endpoint: API端点
+            data: 请求数据
+            
+        Returns:
+            响应数据
+        """
+        # TODO: 添加错误处理
+        url = f"{self.base_url}/{endpoint}"
+        response = self.session.post(url, json=data)
+        response.raise_for_status()
+        return response.json()
+    
+    def put(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        发送PUT请求
+        
+        Args:
+            endpoint: API端点
+            data: 请求数据
+            
+        Returns:
+            响应数据
+        """
+        url = f"{self.base_url}/{endpoint}"
+        response = self.session.put(url, json=data)
+        response.raise_for_status()
+        return response.json()
+    
+    def delete(self, endpoint: str) -> None:
+        """
+        发送DELETE请求
+        
+        Args:
+            endpoint: API端点
+        """
+        # TODO: 添加确认机制
+        url = f"{self.base_url}/{endpoint}"
+        response = self.session.delete(url)
+        response.raise_for_status()
+

+ 85 - 0
code/chapter9/codebase/data_processor.py

@@ -0,0 +1,85 @@
+"""
+数据处理模块
+用于处理和转换数据
+"""
+
+import pandas as pd
+from typing import List, Dict, Any
+
+
+def process_data(data: List[Dict[str, Any]]) -> pd.DataFrame:
+    """
+    处理原始数据并返回DataFrame
+    
+    Args:
+        data: 原始数据列表
+        
+    Returns:
+        处理后的DataFrame
+    """
+    # TODO: 添加数据验证逻辑
+    df = pd.DataFrame(data)
+    df = clean_data(df)
+    df = transform_data(df)
+    return df
+
+
+def clean_data(df: pd.DataFrame) -> pd.DataFrame:
+    """
+    清理数据中的空值和异常值
+    
+    Args:
+        df: 原始DataFrame
+        
+    Returns:
+        清理后的DataFrame
+    """
+    # TODO: 实现更复杂的清理逻辑
+    df = df.dropna()
+    df = df.drop_duplicates()
+    return df
+
+
+def transform_data(df: pd.DataFrame) -> pd.DataFrame:
+    """
+    转换数据格式
+    
+    Args:
+        df: 输入DataFrame
+        
+    Returns:
+        转换后的DataFrame
+    """
+    # TODO: 添加更多转换规则
+    df['processed_date'] = pd.to_datetime(df['date'])
+    return df
+
+
+def aggregate_data(df: pd.DataFrame, group_by: List[str]) -> pd.DataFrame:
+    """
+    聚合数据
+    
+    Args:
+        df: 输入DataFrame
+        group_by: 分组字段列表
+        
+    Returns:
+        聚合后的DataFrame
+    """
+    return df.groupby(group_by).agg({
+        'value': ['sum', 'mean', 'count']
+    })
+
+
+def export_data(df: pd.DataFrame, output_path: str) -> None:
+    """
+    导出数据到文件
+    
+    Args:
+        df: 要导出的DataFrame
+        output_path: 输出文件路径
+    """
+    # TODO: 支持更多输出格式
+    df.to_csv(output_path, index=False)
+    print(f"Data exported to {output_path}")
+

+ 85 - 0
code/chapter9/codebase/models.py

@@ -0,0 +1,85 @@
+"""
+数据模型模块
+定义应用中使用的数据模型
+"""
+
+from dataclasses import dataclass
+from datetime import datetime
+from typing import Optional, List
+
+
+@dataclass
+class User:
+    """用户模型"""
+    id: int
+    username: str
+    email: str
+    created_at: datetime
+    is_active: bool = True
+    
+    def __str__(self) -> str:
+        return f"User({self.username}, {self.email})"
+    
+    # TODO: 添加用户验证方法
+
+
+@dataclass
+class Product:
+    """产品模型"""
+    id: int
+    name: str
+    category: str
+    price: float
+    stock: int
+    description: Optional[str] = None
+    
+    def is_in_stock(self) -> bool:
+        """检查是否有库存"""
+        return self.stock > 0
+    
+    def apply_discount(self, percentage: float) -> float:
+        """
+        应用折扣
+        
+        Args:
+            percentage: 折扣百分比
+            
+        Returns:
+            折后价格
+        """
+        # TODO: 添加折扣验证
+        return self.price * (1 - percentage / 100)
+
+
+@dataclass
+class Order:
+    """订单模型"""
+    id: int
+    user_id: int
+    products: List[Product]
+    total_amount: float
+    status: str
+    created_at: datetime
+    
+    def calculate_total(self) -> float:
+        """计算订单总额"""
+        # TODO: 考虑折扣和税费
+        return sum(p.price for p in self.products)
+    
+    def is_completed(self) -> bool:
+        """检查订单是否完成"""
+        return self.status == "completed"
+
+
+@dataclass
+class Transaction:
+    """交易模型"""
+    id: int
+    order_id: int
+    amount: float
+    payment_method: str
+    timestamp: datetime
+    status: str
+    
+    # TODO: 添加退款功能
+

+ 91 - 0
code/chapter9/codebase/utils.py

@@ -0,0 +1,91 @@
+"""
+工具函数模块
+提供常用的辅助函数
+"""
+
+import json
+import os
+from datetime import datetime
+from typing import Any, Dict, List
+
+
+def load_config(config_path: str) -> Dict[str, Any]:
+    """
+    加载配置文件
+    
+    Args:
+        config_path: 配置文件路径
+        
+    Returns:
+        配置字典
+    """
+    # TODO: 支持多种配置文件格式
+    with open(config_path, 'r') as f:
+        return json.load(f)
+
+
+def save_config(config: Dict[str, Any], config_path: str) -> None:
+    """
+    保存配置到文件
+    
+    Args:
+        config: 配置字典
+        config_path: 配置文件路径
+    """
+    with open(config_path, 'w') as f:
+        json.dump(config, f, indent=2)
+
+
+def get_timestamp() -> str:
+    """
+    获取当前时间戳
+    
+    Returns:
+        ISO格式的时间戳字符串
+    """
+    return datetime.now().isoformat()
+
+
+def ensure_dir(directory: str) -> None:
+    """
+    确保目录存在,不存在则创建
+    
+    Args:
+        directory: 目录路径
+    """
+    if not os.path.exists(directory):
+        os.makedirs(directory)
+
+
+def format_size(size_bytes: int) -> str:
+    """
+    格式化文件大小
+    
+    Args:
+        size_bytes: 字节数
+        
+    Returns:
+        格式化后的大小字符串
+    """
+    # TODO: 优化格式化逻辑
+    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
+        if size_bytes < 1024.0:
+            return f"{size_bytes:.2f} {unit}"
+        size_bytes /= 1024.0
+    return f"{size_bytes:.2f} PB"
+
+
+def validate_email(email: str) -> bool:
+    """
+    验证邮箱地址格式
+    
+    Args:
+        email: 邮箱地址
+        
+    Returns:
+        是否有效
+    """
+    import re
+    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
+    return re.match(pattern, email) is not None
+

+ 66 - 9
code/chapter9/codebase_maintainer.py

@@ -41,7 +41,10 @@ class CodebaseMaintainer:
         self.llm = llm or HelloAgentsLLM()
 
         # 初始化工具
-        self.memory_tool = MemoryTool(user_id=project_name)
+        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)
 
@@ -102,12 +105,22 @@ class CodebaseMaintainer:
             user_query=user_input,
             conversation_history=self.conversation_history,
             system_instructions=self._build_system_instructions(mode),
-            custom_packets=note_packets + pre_context
+            additional_packets=note_packets + pre_context
         )
 
         # 第四步:调用 LLM
         print("🤖 正在思考...")
-        response = self.llm.invoke(context)
+        messages = [
+            {
+                "role": "system",
+                "content": context
+            },
+            {
+                "role": "user",
+                "content": user_input
+            }
+        ]
+        response = self.llm.invoke(messages)
 
         # 第五步:后处理
         self._postprocess_response(user_input, response)
@@ -167,14 +180,15 @@ class CodebaseMaintainer:
             # 规划模式:加载最近的笔记
             print("📋 加载任务规划...")
 
-            task_notes = self.note_tool.run({
+            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['title']}" for note in 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(),
@@ -189,32 +203,70 @@ class CodebaseMaintainer:
         """检索相关笔记"""
         try:
             # 优先检索 blocker
-            blockers = self.note_tool.run({
+            blockers_raw = self.note_tool.run({
                 "action": "list",
                 "note_type": "blocker",
                 "limit": 2
             })
+            blockers = self._normalize_note_results(blockers_raw)
 
             # 搜索相关笔记
-            search_results = self.note_tool.run({
+            search_results_raw = self.note_tool.run({
                 "action": "search",
                 "query": query,
                 "limit": limit
             })
+            search_results = self._normalize_note_results(search_results_raw)
 
             # 合并去重
-            all_notes = {note.get('note_id') or note.get('id'): note for note in (blockers or []) + (search_results or [])}
+            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,
@@ -227,10 +279,15 @@ class CodebaseMaintainer:
             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=datetime.fromisoformat(note.get('updated_at', datetime.now().isoformat())),
+                timestamp=note_timestamp,
                 token_count=len(content) // 4,
                 relevance_score=relevance,
                 metadata={

+ 42 - 0
code/chapter9/data/sales_2024.csv

@@ -0,0 +1,42 @@
+date,product,category,quantity,price,customer_id,region
+2024-01-15,Laptop Pro,Electronics,2,1299.99,C001,North
+2024-01-15,Wireless Mouse,Electronics,5,29.99,C002,South
+2024-01-16,Office Chair,Furniture,3,249.99,C003,East
+2024-01-16,Standing Desk,Furniture,1,599.99,C004,West
+2024-01-17,Laptop Pro,Electronics,1,1299.99,C005,North
+2024-01-17,USB-C Cable,Electronics,10,15.99,C006,South
+2024-01-18,Monitor 27",Electronics,2,399.99,C007,East
+2024-01-18,Desk Lamp,Furniture,4,45.99,C008,West
+2024-01-19,Keyboard Mechanical,Electronics,3,129.99,C009,North
+2024-01-19,Office Chair,Furniture,2,249.99,C010,South
+2024-01-20,Laptop Pro,Electronics,1,1299.99,C011,East
+2024-01-20,Webcam HD,Electronics,5,79.99,C012,West
+2024-01-21,Standing Desk,Furniture,2,599.99,C013,North
+2024-01-21,Wireless Mouse,Electronics,8,29.99,C014,South
+2024-01-22,Monitor 27",Electronics,3,399.99,C015,East
+2024-01-22,Desk Organizer,Furniture,6,24.99,C016,West
+2024-01-23,Laptop Pro,Electronics,2,1299.99,C017,North
+2024-01-23,USB Hub,Electronics,4,34.99,C018,South
+2024-01-24,Office Chair,Furniture,1,249.99,C019,East
+2024-01-24,Keyboard Mechanical,Electronics,2,129.99,C020,West
+2024-01-25,Standing Desk,Furniture,3,599.99,C021,North
+2024-01-25,Monitor 27",Electronics,1,399.99,C022,South
+2024-01-26,Laptop Pro,Electronics,1,1299.99,C023,East
+2024-01-26,Wireless Mouse,Electronics,7,29.99,C024,West
+2024-01-27,Desk Lamp,Furniture,5,45.99,C025,North
+2024-01-27,USB-C Cable,Electronics,12,15.99,C026,South
+2024-01-28,Office Chair,Furniture,2,249.99,C027,East
+2024-01-28,Webcam HD,Electronics,3,79.99,C028,West
+2024-01-29,Laptop Pro,Electronics,2,1299.99,C029,North
+2024-01-29,Standing Desk,Furniture,1,599.99,C030,South
+2024-01-30,Monitor 27",Electronics,4,399.99,C031,East
+2024-01-30,Keyboard Mechanical,Electronics,1,129.99,C032,West
+2024-01-31,Wireless Mouse,Electronics,6,29.99,C033,North
+2024-01-31,Office Chair,Furniture,3,249.99,C034,South
+2024-02-01,Laptop Pro,Electronics,1,1299.99,C035,East
+2024-02-01,USB Hub,Electronics,5,34.99,C036,West
+2024-02-02,Standing Desk,Furniture,2,599.99,C037,North
+2024-02-02,Desk Organizer,Furniture,8,24.99,C038,South
+2024-02-03,Monitor 27",Electronics,2,399.99,C039,East
+2024-02-03,Webcam HD,Electronics,4,79.99,C040,West
+

+ 18 - 0
code/chapter9/project/README.md

@@ -0,0 +1,18 @@
+# 项目演示目录
+
+这个目录用于演示 TerminalTool 的安全特性。
+
+## 说明
+
+TerminalTool 具有以下安全特性:
+
+1. **命令白名单**:只允许执行特定的安全命令
+2. **工作目录限制**:不能访问工作目录之外的文件
+3. **路径逃逸保护**:防止通过 `..` 等方式逃逸工作目录
+
+## 测试场景
+
+- 尝试执行危险命令(如 `rm -rf`)会被阻止
+- 尝试访问工作目录外的文件会被拒绝
+- 尝试通过相对路径逃逸工作目录会被检测并阻止
+

+ 16 - 0
code/chapter9/project/main.py

@@ -0,0 +1,16 @@
+"""
+项目主入口文件
+"""
+
+from datetime import datetime
+
+
+def main():
+    """主函数"""
+    print(f"Application started at {datetime.now()}")
+    print("Hello from the project!")
+
+
+if __name__ == "__main__":
+    main()
+

+ 1 - 1
docs/chapter9/第九章 上下文工程.md

@@ -5,7 +5,7 @@
 为了让读者能够快速体验本章的完整功能,我们提供了可直接安装的Python包。你可以通过以下命令安装本章对应的版本:
 
 ```bash
-pip install hello-agents[all]==0.2.7
+pip install "hello-agents[all]==0.2.7"
 ```
 
 本章主要介绍上下文工程的核心概念与实践,并在HelloAgents框架中新增了两个工具: