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.

176 lines
8.2 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.

# 01 架构与调用链
对照源码:`D:\ideaProject\QozeCode-main`
## 1. 它到底是什么
QozeCode 是一个**命令行里的 Coding Agent**。用户在终端打字,模型按 ReAct 循环自己决定要不要读文件、跑命令、搜网页;过程实时画在 Textual TUI 上。
它不是 IDE 插件,也不是 Web 对话页。核心就三件事:
- 把“对话状态”交给 LangGraph 管
- 把“动手能力”做成 LangChain Tool
- 把“思考 / 工具 / 回复”流式渲染到终端
## 2. 分层(从外到内)
```text
┌─────────────────────────────────────────────────────────┐
│ 启动层 launcher.py + qoze_tui.py:main() │
│ 选模型、读配置、拉起 Textual App │
├─────────────────────────────────────────────────────────┤
│ 交互层 qoze_tui.py + tui_components/ │
│ 斜杠命令、流式渲染、思考区、工具状态条 │
├─────────────────────────────────────────────────────────┤
│ 决策层 qoze_code_agent.py │
│ LangGraph: llm_call ⇄ tool_node │
├─────────────────────────────────────────────────────────┤
│ 模型层 model_initializer.py + config_manager.py │
│ ChatOpenAI 等,兼容 OpenAI 协议的自定义 base_url │
├─────────────────────────────────────────────────────────┤
│ 能力层 │
│ tools/ 文件、命令、搜索 │
│ skills/ 专家知识注入 prompt │
│ qoze_mcp/ 外部 MCP 工具热加载 │
│ tools/subagent_tool.py 子图并行 │
└─────────────────────────────────────────────────────────┘
```
## 3. 目录对照(只记主干)
| 路径 | 职责 | 改写时优先级 |
|------|------|----------------|
| `qoze_tui.py` | Textual App,用户输入入口 | 第二阶段再写,MVP 可用纯 CLI |
| `launcher.py` | 启动选模型 | 可做成 `--model` 参数 |
| `qoze_code_agent.py` | **心脏**:State、图、checkpoint | 第一阶段必须吃透并重写 |
| `model_initializer.py` | 按厂商创建 LLM | 先只留 DeepSeek / OpenAI 兼容 |
| `config_manager.py` | 读 ini 配置和密钥 | 先 YAML/ENV 即可 |
| `enums.py` | Provider / ModelType | 自己项目用简单字符串就行 |
| `utils/system_prompt.py` | 静态 prompt + 动态上下文 | 必须理解,缓存设计值得学 |
| `tools/` | 所有 Function Calling 工具 | 先 4 个:读文件、改文件、跑命令、列目录 |
| `tools/subagent_tool.py` | 迷你版主图 | 第三阶段 |
| `skills/` | SKILL.md 发现与激活 | 第三阶段 |
| `qoze_mcp/` | MCP 配置、激活、工具注入 | 第三阶段 |
| `tui_components/` | 气泡、流式、thinking widget | 第二阶段 |
| `utils/git_context.py` | 把 git status/diff 塞进 prompt | 增强阶段很有用 |
## 4. 启动调用链
```text
qoze / python qoze_tui.py
│
├─ launcher.ensure_config() 没有配置就写模板
├─ get_model_choice() 交互选模型(可用 --model 跳过)
└─ Qoze(provider, model_type).run()
│
├─ initialize_llm() 得到 ChatOpenAI 实例
├─ llm.bind_tools(tools)
├─ init_agent() SQLite checkpointer 编译 StateGraph
└─ 用户回车
└─ process_user_input()
```
TUI 里以 `/` 开头的是**本地命令**,不进图:`/clear`、`/skills`、`/mcp`、`/quit`、`/checkpoint`。
普通自然语言才 `agent.astream(...)`。
## 5. 一轮对话怎么走完(必须能默画)
主图在 `qoze_code_agent.py`:
```text
START
│
▼
llm_call
│ 拼 SystemMessage(静态 prompt)
│ + 动态上下文(目录树、git、规则、技能)
│ + 历史 messages
│ → await llm_with_tools.ainvoke(...)
│
├─ 有 tool_calls ──► tool_node ──► 再回到 llm_call
│ │
│ └─ 多个 tool_call 用 asyncio.gather 并行
│
└─ 没有 tool_calls ──► END
```
对应代码:
- 状态:`MessagesState`(`messages` 用 `operator.add` 追加)
- 节点:`llm_call`、`tool_node`
- 边:`should_continue` / `should_continue_from_tool`
- 持久化:`AsyncSqliteSaver`,库在当前项目 `.qoze/data/checkpoints.db`
这就是 ReAct:**Reason(llm_call)→ Act(tool_node)→ Observe(ToolMessage 写回)→ 再 Reason**。
## 6. Prompt 怎么拼(这是质量关键)
`llm_call` 每次都会重算上下文,但拆成两段:
| 段 | 放哪 | 内容 | 为什么 |
|----|------|------|--------|
| 静态 | `SystemMessage` | 角色、工具用法、行为规范 | 方便 Prompt Caching |
| 动态 | 第一条 `HumanMessage` 前面 | 系统信息、工作目录树、git、`.qoze/rules`、已激活 skill、memory | 每次会变,不能进静态段 |
还要处理两件脏活:
- 模型不支持视觉时,剥掉 `image_url`
- checkpoint 恢复后,assistant 有 `tool_calls` 但缺 `ToolMessage`,要补占位,否则 OpenAI 兼容接口会 400
## 7. 工具层真实情况(以代码为准)
当前挂在主 Agent 上的核心工具:
- `execute_command`:shell(Windows 也能跑,但没有 Linux 的进程组杀法)
- `read_file` / `list_files` / `search_in_files` / `grep_file` / `find_files` / `replace_in_file`
- `tavily_search` / `read_url`
- skill 四个:activate / list / deactivate / install guide
- MCP 四个:list / activate / deactivate / install guide
- `dispatch_subagent`
- `analyze_project` / `find_symbols` / `trace_imports`
- `transcribe_audio` / `get_current_datetime`
注意:
- `write_file` **被注释了**,写文件靠 `replace_in_file` 或 `execute_command`
- `browser_*` **整段注释**,浏览器走 MCP(`chrome-devtools`)
- README 的 `/plan` 在这份代码里**没有对应实现**,别按文档去找
路径安全:`file_tools._resolve_under_cwd` 只允许 cwd 和 `~/.qoze/`,这是改写时必须保留的约束。
## 8. 三条插件通道(先分清,再决定要不要抄)
```text
Skill 发现 SKILL.md → 用户 activate → 把文档塞进动态 prompt
MCP 读 mcp_config.json → 拉起外部进程 → 把远端 tools 注入 tools_by_name
Subagent 再建一张同样的小图,工具更少、无 checkpoint、上下文隔离
```
记忆口诀:
- Skill = **知识**(改 prompt)
- MCP = **外来工具**(改 tool 列表)
- Subagent = **再开一个大脑**(改执行拓扑)
## 9. TUI 流式
`tui_components/messages/stream_handler.py` 订阅 `agent.astream`:
- 模型 `reasoning_content` → `ThinkingWidget`
- 正文 token → `BotMessageWidget`(增量 buffer + 调度器,避免刷爆终端)
- 出现 tool_call → 工具状态面板
- ToolMessage 回来 → 标记完成/失败
自己复刻时,第一版可以不流式,等图跑完再 `print`。流式是体验,不是 Agent 正确性。
## 10. 和宣传文案不一致的地方(精读避坑)
| README 说法 | 这份源码 |
|-------------|----------|
| `/plan` 三份文档 | 未见实现 |
| Playwright 浏览器工具 | `browser_tool.py` 注释,改 MCP |
| 语音 | 有 `AudioManager` / Soniox,Windows 基本不可用 |
| 内置 skills 一堆 | 仓库 `skills/` 只有管理器,内置技能目录可能不在这份拷贝里 |
精读原则:**调用链以 `qoze_code_agent.py` + `qoze_tui.py` 为准。**