数据截至 (上游 commit 1a36a37b1a96)
顶层全景:一次请求的一生
本章讲清楚 avante 的「大盘」:有哪些层、一次提问怎么从输入框流到代码落盘。不深入算法(那在后面几章),只把「谁调谁、数据怎么流」摆清楚。
1. 五层结构
avante 大致分五层,自上而下:
| 层 | 职责 | 代表文件/符号 |
|---|---|---|
| 入口层 | 命令、keymap、setup()、Zen Mode | init.lua、api.lua |
| UI 层 | 侧边栏:输入框 / 聊天流 / 选中代码 / 提交 | sidebar.lua(Sidebar:handle_submit) |
| 编排层 | agentic 循环、prompt 组装、内存压缩 | llm.lua(M._stream、M.generate_prompts) |
| 能力层 | 工具系统 + 改代码引擎 | llm_tools/*.lua |
| 适配层 | 各家 LLM 供应商 / ACP 外接 agent | providers/*.lua、libs/acp_client.lua |
下面跟着一条真实请求,把这五层串起来。
2. 第一步:UI 层收集上下文(Sidebar:handle_submit)
你在输入框提交后,入口是 Sidebar:handle_submit(request)(lua/avante/sidebar.lua:2808)。它做几件事:
- 处理斜杠命令(
/commit、/lines等)和@codebase、快捷词替换(sidebar.lua:2821、2838)。 - 收集选中的文件和选区代码:
-- sidebar.lua:2841 起(节选):把当前选区打包成 selected_code
local selected_filepaths = self.file_selector:get_selected_filepaths()
local selected_code = self.code.selection and {
path = self.code.selection.filepath,
file_type = self.code.selection.filetype,
content = self.code.selection.content,
}
- 定义一组回调(这是关键:整个编排层通过回调把状态推回 UI):
on_messages_add(messages)→self:add_history_messages(...),把新消息塞进聊天历史并渲染(sidebar.lua:2911)。on_state_change(state)→ 渲染「正在生成 / 正在调工具」状态(sidebar.lua:2901)。on_tool_log(...)→ 把某个工具的执行日志挂到对应消息上(sidebar.lua:2911)。on_chunk→ 把流式文本片段追加到聊天流。
收集完上下文、挂好回调后,它最终调用 Llm.stream(...)(在 handle_submit 后段),把控制权交给编排层。
3. 第二步:编排层组装 prompt(M.generate_prompts)
进 M._stream(llm.lua:1777)后,第一件事是 M.generate_prompts(opts)(llm.lua:261)拼出最终要发的 system prompt 和消息体:
- 读项目根的
avante.md(可配Config.instructions_file)作为项目级指令(llm.lua:262-273)。 - 把选中文件读进来、过滤掉「已经被
view工具看过的文件」避免重复塞(llm.lua:301-312)。 - 决定模式(
agentic/legacy)和模型名,据此选 Jinja 模板渲染。
system prompt 是 Jinja 模板渲染出来的,不是硬编码字符串。模板在 lua/avante/templates/*.avanterules,由 Rust 的 avante-templates(minijinja)渲染(见 05 章)。比如 agentic 模式用 agentic.avanterules,它 extends "base.avanterules" 并按 enable_fastapply、model_name 等条件拼接不同段落(templates/agentic.avanterules:1-13)。
直觉:prompt 不是一坨固定文本,而是「按当前模式 + 模型 + 是否开 fast-apply」动态拼出来的——这让同一套代码能驱动很不一样的模型。
4. 第三步:适配层拼请求、解析流(providers/*.lua)
_stream 把组装好的 prompt_opts 交给 M.curl(llm.lua:514),由具体 provider 负责两件事:
parse_curl_args(prompt_opts):拼出这家 API 的 HTTP 请求(headers、body、工具定义)。以 Claude 为例(providers/claude.lua:582):设置anthropic-version头、按auth_type选 API key 或 OAuth、把 avante 工具转成 Anthropic 的tool格式、对支持的模型挂上原生text_editor_*工具(claude.lua:612-634)、并给最后一条消息打cache_control做 prompt caching(claude.lua:636-655)。parse_response/parse_stream_data:把 SSE 流解析成统一事件——文本增量走on_chunk,工具调用归一成{ type = "tool_use", name, id, input },流结束时调on_stop({ reason = ... })(claude.lua:390起)。
关键:不同供应商的响应差异,在这一层被「抹平」成同一套事件(tool_use / complete / tool_use / rate_limit / error / cancelled)。上层 _stream 只认这套统一事件,不关心是哪 家模型。
5. 第四步:循环回到编排层(on_stop 的分支)
流结束时,_stream 在它的 handler_opts.on_stop(llm.lua:1831)里按 reason 分流——这就是 agent 循环的转盘:
on_stop(stop_opts)
│
┌──────────┼───────────────┬──────────────┬───────────┐
▼ ▼ ▼ ▼ ▼
reason== reason== reason== reason== reason==
tool_use complete rate_limit cancelled error
│ (agentic 时) │ │ │
▼ 检查是否漏调 睡 N 秒 撤销并 直接
串行跑工具 attempt_completion 后重试 提示用户 上抛
│ → 补一条提醒 │
▼ 再跑一轮 │
结果作 tool_result │
追加,递归再调模型 ◀────────────┘
这套分支逻辑是 02 章 的主题。这里只需记住:「跑完工具就递归再调模型」是循环成立的根本(llm.lua:1873、1986)。
6. 第五步:能力层落地(工具 / 改代码)
当 reason == "tool_use",_stream 调 handle_next_tool_use(llm.lua:1837)逐个跑 pending 工具,每个工具走 LLMTools.process_tool_use(llm_tools/init.lua:1349)分发到具体工具函数。
其中「改文件」类工具(str_replace / edit_file / write_to_file)最终都汇到 replace_in_file.func(replace_in_file.lua:104):它把 SEARCH/REPLACE 块用 fuzzy_match 定位到真实 buffer、用 extmark 铺出 diff、弹确认框,你接受后 noautocmd write! 落盘(replace_in_file.lua:722-738)。详见 03 章 和 04 章。
7. 回调流向小结
整个过程没有「请求-返回」式的同步调用,而是回调驱动:UI 层把 on_chunk / on_messages_add / on_state_change / on_tool_log / on_stop 一路传进编排层和适配层;任何一层有进展(收到一段文本、生成一个工具调用、工具产出日志),就通过这些回调把状态推回 UI 实时渲染。_stream 还把这些回调存进 session_ctx(llm.lua:1787-1795),让递归的下一轮也能复用同一套回调。
8. 代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 提交入口、收集上下文、挂回调 | lua/avante/sidebar.lua | Sidebar:handle_submit |
| 流式入口、scheduler 包裹回调、dual-boost | lua/avante/llm.lua | M.stream、M._dual_boost_stream |
| agentic 循环主体 | lua/avante/llm.lua | M._stream |
| prompt 组装(读 avante.md、过滤 已看文件、选模板) | lua/avante/llm.lua | M.generate_prompts |
| HTTP 发送 + 流解析骨架 | lua/avante/llm.lua | M.curl |
| Claude 请求拼装 / 响应解析 | lua/avante/providers/claude.lua | parse_curl_args、parse_response、parse_messages |
| 默认配置(mode / provider / behaviour) | lua/avante/config.lua | M.defaults |
| 入口命令与 keymap | lua/avante/init.lua、lua/avante/api.lua | M.setup、zen_mode |