You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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