# 04 最小复刻清单 目标:一个你能完全讲清楚的小 Agent,不是功能全集。 建议包名 / 命令:自己定,下文用 `myagent` 指代。 ## 1. 目录骨架 ```text my-agent/ pyproject.toml README.md src/myagent/ __init__.py cli.py # argparse:--model / --session config.py # 读环境变量或用户目录 ini/yaml llm.py # 只创建 ChatOpenAI(base_url, api_key, model) prompt.py # 静态 system + 动态 cwd/tree graph.py # StateGraph:llm_call / tool_node tools/ fs.py # read_file, list_dir, replace_in_file shell.py # execute_command + 确认回调 session.py # sqlite checkpointer tests/ test_graph_smoke.py test_path_sandbox.py ``` 第一版**不要**:textual、mcp、skills、subagent、语音、多厂商。 ## 2. 配置(最小) 环境变量即可: ```text MYAGENT_API_KEY=... MYAGENT_BASE_URL=https://api.agicto.cn/v1 MYAGENT_MODEL=deepseek-v4-flash ``` 密钥只放用户目录或系统环境,禁止进 git。 ## 3. 图的最小实现要点 ```text State = { messages, llm_calls } START → llm_call → (有 tool_calls?) 是 → tool_node → llm_call 否 → END ``` 硬性要求: - 工具异常要变成 ToolMessage 文本,不能把整个图打崩 - 一轮多个 tool_calls 可以先串行,跑通再并行 - `thread_id` 用参数传入,默认 `default` ## 4. 工具验收 | 工具 | 必须有的约束 | 怎么测 | |------|----------------|--------| | read_file | 只能读 cwd 内;限制行数 | 读 `../` 应拒绝 | | list_dir | 跳过 `.git` / `node_modules` / `.venv` | 在本项目根目录跑 | | replace_in_file | old_text 必须唯一匹配 | 改一个函数名再读回来 | | execute_command | 超时;打印命令等 y/N | 输入 `n` 应取消 | ## 5. CLI 体验(第一版足够) ```text $ myagent > 请总结当前目录 README [think] ... [tool] read_file README.md [final] ... > /clear > /exit ``` 没有气泡也没关系。日志清晰比好看重要。 ## 6. 测试底线 - `test_path_sandbox.py`:逃出 cwd 失败 - `test_graph_smoke.py`:mock LLM 返回一次 tool_call 再返回纯文本,断言 tool 被调用、最终 END 没有 mock 测试,不要进入阶段 4。 ## 7. 对照 Qoze 时允许借鉴的“形状” 可以借鉴: - 两节点 ReAct 图 - 静态/动态 prompt 分离 - 文件路径沙箱 - checkpoint 的 thread_id 思路 不要复制: - 整份 `system_prompt.py` 长文案 - Textual CSS 和像素 logo - 十几个厂商的 `initialize_llm` 分支 - 被注释掉的浏览器工具堆 ## 8. 完成定义(Definition of Done) 当下面全是“是”,阶段 3 才结束: - [ ] 新仓库能独立 `pip install -e .` 跑起来 - [ ] 只用 DeepSeek 兼容接口 - [ ] 四件套工具都有单测或手工脚本 - [ ] 会话可恢复 - [ ] README 是你自己写的架构说明(不是拷来的) - [ ] 你能不看代码画出数据流