跳到主要内容

顶层全景:一次请求的一生

本章讲清楚 avante 的「大盘」:有哪些层、一次提问怎么从输入框流到代码落盘。不深入算法(那在后面几章),只把「谁调谁、数据怎么流」摆清楚。

1. 五层结构

avante 大致分五层,自上而下:

职责代表文件/符号
入口层命令、keymap、setup()、Zen Modeinit.luaapi.lua
UI 层侧边栏:输入框 / 聊天流 / 选中代码 / 提交sidebar.lua(Sidebar:handle_submit)
编排层agentic 循环、prompt 组装、内存压缩llm.lua(M._streamM.generate_prompts)
能力层工具系统 + 改代码引擎llm_tools/*.lua
适配层各家 LLM 供应商 / ACP 外接 agentproviders/*.lualibs/acp_client.lua

下面跟着一条真实请求,把这五层串起来。

2. 第一步:UI 层收集上下文(Sidebar:handle_submit)

你在输入框提交后,入口是 Sidebar:handle_submit(request)(lua/avante/sidebar.lua:2795)。它做几件事:

  • 处理斜杠命令(/commit/lines 等)和 @codebase、快捷词替换(sidebar.lua:28082838)。
  • 收集选中的文件选区代码:
-- 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:2885)。
    • on_state_change(state) → 渲染「正在生成 / 正在调工具」状态(sidebar.lua:2888)。
    • on_tool_log(...) → 把某个工具的执行日志挂到对应消息上(sidebar.lua:2898)。
    • on_chunk → 把流式文本片段追加到聊天流。

收集完上下文、挂好回调后,它最终调用 Llm.stream(...)(在 handle_submit 后段),把控制权交给编排层。

3. 第二步:编排层组装 prompt(M.generate_prompts)

M._stream(llm.lua:1765)后,第一件事是 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_fastapplymodel_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:557):设置 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:1819)里按 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:18611986)。

6. 第五步:能力层落地(工具 / 改代码)

reason == "tool_use",_streamhandle_next_tool_use(llm.lua:1825)逐个跑 pending 工具,每个工具走 LLMTools.process_tool_use(llm_tools/init.lua:1331)分发到具体工具函数。

其中「改文件」类工具(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:1775-1783),让递归的下一轮也能复用同一套回调。

8. 代码地图

主题文件符号
提交入口、收集上下文、挂回调lua/avante/sidebar.luaSidebar:handle_submit
流式入口、scheduler 包裹回调、dual-boostlua/avante/llm.luaM.streamM._dual_boost_stream
agentic 循环主体lua/avante/llm.luaM._stream
prompt 组装(读 avante.md、过滤已看文件、选模板)lua/avante/llm.luaM.generate_prompts
HTTP 发送 + 流解析骨架lua/avante/llm.luaM.curl
Claude 请求拼装 / 响应解析lua/avante/providers/claude.luaparse_curl_argsparse_responseparse_messages
默认配置(mode / provider / behaviour)lua/avante/config.luaM.defaults
入口命令与 keymaplua/avante/init.lualua/avante/api.luaM.setupzen_mode