|
|
# 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
|