# 02 知识点地图 每个知识点都绑到源码。学的时候合上文档,能用自己的话讲出来,才算过关。 ## A. Agent 核心(必须牢固) ### A1. ReAct - 概念:Thought → Action → Observation 循环,直到模型不再调工具 - 源码:`qoze_code_agent.py` 的 `llm_call` / `tool_node` / `should_continue` - 过关标准:不看代码画出状态图;能解释“为什么 tool 之后必须再进 llm_call” ### A2. LangGraph StateGraph - 概念:节点函数返回要**更新**的 state 字段,不是替换整个对象 - `messages: Annotated[list, operator.add]` 表示每次 append - `compile(checkpointer=...)` 让同一 `thread_id` 能恢复对话 - 源码:`MessagesState`、`agent_builder`、`init_agent()` - 过关标准:自己用 30 行写出同等的两节点图 ### A3. Tool Calling(Function Calling) - 概念:模型不直接“执行 Python”,它只输出 `tool_calls` JSON;运行时按名字找到函数再跑 - LangChain:`@tool` 从类型注解和 docstring 生成 schema;`llm.bind_tools(tools)` - 源码:`tools/*.py` 的 `@tool`;`tool_node` 里 `tools_by_name[name].ainvoke(args)` - 过关标准:自己写一个 `read_file` 工具,让模型真的读到磁盘内容 ### A4. 消息协议 必须分清四种消息,否则 400 错误会搞很久: | 类型 | 谁产生 | 作用 | |------|--------|------| | SystemMessage | 你 | 角色与规则 | | HumanMessage | 用户 | 任务 | | AIMessage | 模型 | 回复或 tool_calls | | ToolMessage | 你的运行时 | 把工具结果交回,必须带 `tool_call_id` | 源码:`_repair_incomplete_tool_calls` ### A5. Prompt Caching 拆分 - 静态放 System,动态放 User 前缀 - 源码:`utils/system_prompt.py` 的 `get_static_system_prompt` / `get_dynamic_context` - 过关标准:能说出“目录树为什么不能放进 SystemMessage” ## B. 模型接入(结合你现在的环境) ### B1. OpenAI 兼容协议 - `base_url` + `api_key` + `model` 三件套 - 你已经在用 `https://api.agicto.cn/v1` + DeepSeek - 源码:`model_initializer.py` 的 `ChatOpenAI(...)` - 过关标准:10 行脚本能 `ainvoke` 通;再 `bind_tools` 通 ### B2. 推理模型的 thinking - DeepSeek 一类会在 delta 里带 `reasoning_content` - 源码:`model_initializer.patch_langchain_openai` - TUI:`ThinkingWidget` - 过关标准:知道 thinking 不是最终答案,不要当 assistant content 存 ### B3. 多厂商适配 - 这是 Qoze 变复杂的主因:Vertex、Kimi、智谱、Qwen 各写一套 - 改写建议:第一版只保留 **OpenAI 兼容** 一个适配器 ## C. 工具与安全 ### C1. 文件工具 - 源码:`tools/file_tools.py` - 要点:路径限制在 cwd;读文件带行号范围;改文件用精确替换而不是整文件覆盖 - 过关标准:能讲清为什么 `write_file` 危险、`replace_in_file` 更稳 ### C2. Shell 工具 - 源码:`tools/execute_command_tool.py` - 要点:`create_subprocess_shell`、超时、stdout/stderr 合并 - 风险:模型可以 `rm -rf` / 删盘;改写时要加**白名单或人工确认** ### C3. 并行 tool_calls - 一轮里模型可能同时调多个工具 - 源码:`asyncio.gather` in `tool_node` - 过关标准:知道“读两个文件可以并行,写同一文件不能瞎并行” ## D. 工程结构(决定你改写时像不像业余) ### D1. 配置与密钥 - 源码:`config_manager.py`,Windows 在 `%APPDATA%\qoze\qoze.conf` - 原则:密钥不进仓库 ### D2. Checkpoint / 会话 - SQLite + `thread_id` - `/clear` 会换新 uuid 并清库 - 源码:`init_agent` / `save_thread_id` / `qoze_tui.py` 的 `clear` ### D3. 规则注入 - `.qoze/rules/*.md` 每次动态塞进 prompt - 这就是 Cursor 的 `.cursor/rules` 同类机制,非常值得学 ## E. 插件三件套(理解即可,MVP 不做) ### E1. Skills - 发现路径:项目 `.qoze/skills` > 用户 `~/.qoze/skills` > 内置 - 激活后把 SKILL.md 正文注入动态上下文 - 源码:`skills/skill_manager.py` ### E2. MCP - 配置里写 `command + args`,stdio 拉起外部 server - 激活后 `get_active_tools()` 合并进 `tools_by_name` - 源码:`qoze_mcp/mcp_manager.py`、`tools/mcp_tools.py` ### E3. Subagent - 再 compile 一张无 checkpointer 的图 - 工具子集,禁止再 dispatch,避免套娃 - 源码:`tools/subagent_tool.py` 的 `_build_subagent` ## F. TUI(体验层,可后置) | 概念 | 源码 | 你要懂什么 | |------|------|------------| | Textual App | `qoze_tui.py` 的 `class Qoze(App)` | compose / 事件 / 异步任务 | | 流式增量 | `display_buffer.py` + `stream_scheduler.py` | 不要每个 token 都 refresh | | 思考区 | `thinking_widget.py` | 和正文拆开 | | 终端兼容 | `terminal_compat.py` | Windows GBK / 旧终端会炸 Unicode | Windows 坑你已经踩过:Rich 默认 legacy_windows 会 GBK 编码失败。自己项目一开始就 `PYTHONUTF8=1`,TUI 用 Windows Terminal。 ## G. 建议的练习题(每题半天到一天) 1. 不用 LangGraph,手写一个 while 循环 ReAct(messages 列表自己维护) 2. 用 LangGraph 把上面的循环换成两节点图 3. 给图加上 `read_file` + `execute_command`,完成“读 README 并总结” 4. 给图加上 SQLite checkpointer,重启进程还能续聊 5. 把 `astream` 的 token 打到终端(先 print,再考虑 Textual) 6. 实现一个 skill:激活后模型会按你的 Java 代码规范改代码 7. 实现一个危险命令拦截:`execute_command` 前打印 diff/命令并等 y/N