数据截至 (上游 commit 01f4282f1ffe)
Open WebUI — 聊天补全与智能体编排架构与原理
30 秒导读: Open WebUI 是自托管的 AI 聊天前端 + 后端。它最有意思的不是界面,而是后端那条把"一条聊天消息"变成"会自己查资料、调工具、多轮迭代的智能体回答"的中间件管线。这份子库讲清楚这条管线:请求进来后先按你勾选的特性(记忆、联网、RAG、图像、工具)装配上下文,再路由到某个模型后端,最后在出口驱动一个"模型说要调工具 → 后端执行 → 结果回灌 → 再问模型"的循环。
1. 这是什么(零基础也能懂)
一句话定义: Open WebUI 是一个自己部署、界面像 ChatGPT 的聊天应用,后端能把普通问答升级成智能体(会调工具、查网页、翻记忆的 AI)。
解决什么问题 / 给谁用: 假设你想在自己的服务器上用 Ollama 跑本地大模型,或接 OpenAI/兼容 API,但又想要"联网搜索、上传文档问答(RAG)、画图、让模型自己调工具"这些高级能力——Open WebUI 把这些开箱即用地拼好,还允许你用 Python 插件二次开发。它给的是运维/开发者,不是终端小白。
它能做什么(后端视角):
- 把一条聊天请求按"勾选的特性"装配成带上下文的 payload(记忆、联网、RAG 文件、图像生成提示)。
- 挂载四类来源的工具给模型:内置工具、用户写的 Python 函数工具、外部 OpenAPI 服务器、MCP 服务器。
- 支持两种工具调用模式:模型原生
tool_calls,或后端用提示词模拟的"伪函数调用"。 - 在出口驱动多轮智能体循环:模型要调工具 → 后端执行 → 结果塞回对话 → 再问模型,直到模型不再要工具。
- 整条链路可被 pipe/filter 插件拦截:pipe 让你把任意 Python 函数注册成一个"模型",filter 让你在请求进/出时改写数据。
用起来什么样(对外就是 OpenAI 接口): 后端暴露的是标准的 POST /api/chat/completions(见 backend/open_webui/main.py:1052),body 和 OpenAI 一样是 {model, messages, stream, ...},只是多了 features、tool_ids、files 等字段来打开那些高级能力。
一句话直觉: 把它想成一个**"聊天请求的流水线工厂"**——原料是一条消息,进厂后先经过"装配工位"(挂上下文和工具),送去"加工中心"(某个模型后端),出来再经过"质检/返工工位"(如果模型说要调工具,就返工再跑一遍),最后打包成流式响应发给你。
本节不谈代码细节;记住"入口装配 → 路由后端 → 出口循环"这三段即可。
2. 顶层全景(它大概怎么转)
2.1 主线:一条消息的三段旅程
怎么读下面这张图: 从上到下是一条 POST /api/chat/completions 请求的时间顺序。三个粗体大块就是三段旅程,中间的 generate_chat_completion 是"分叉路由器"。
HTTP POST /api/chat/completions main.py:1668 chat_completion()
│
▼
┌─────────────────────────────────────────────────────────┐
│ ① 入口装配 process_chat_payload middleware.py:2311 │
│ • pipeline inlet + filter inlet(插件拦截改写) │
│ • 按 features 挂上下文:记忆/联网/图像/RAG │
│ • 解析 tool_ids → 装配四类工具 │
│ • 按 function_calling 分叉:native 挂 tools 数组; │
│ 非 native 现在就调好工具、把结果注入提示 │
└─────────────────────────────────────────────────────────┘
│ form_data(已装配好)
▼
┌─────────────────────────────────────────────────────────┐
│ ② 路由后端 generate_chat_completion chat.py:152 │
│ ├─ model.pipe? → 插件函数当模型(functions.py) │
│ ├─ owned_by=ollama → /ollama/api/chat │
│ └─ 其它 → OpenAI 兼容后端 │
└─────────────────────────────────────────────────────────┘
│ 上游返回(通常是 SSE 流)
▼
┌─────────────────────────────────────────────────────────┐
│ ③ 出口循环 process_chat_response middleware.py:5259 │
│ • 流式转发 token 给前端 │
│ • 若模型发了 tool_calls → 智能体循环: │
│ 执行工具 → 结果回灌进 messages → │
│ 再 generate_chat_completion → 重复 │
│ • filter outlet(插件拦截最终输出) │
└─────────────────────────────────────────────────────────┘
│
▼
流式响应 SSE → 前端逐字渲染
编排代码就在 process_chat() 里,三段依次调用:main.py:2023(入口)→ main.py:2025(路由,别名 chat_completion_handler)→ main.py:2044(出口)。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪(文件:符号) |
|---|---|---|
| HTTP 入口 | 接收 OpenAI 风格请求、准备 metadata | main.py:1668 chat_completion |
| 入口装配 | 按特性挂上下文、装配工具、按模式分叉 | middleware.py:2248 process_chat_payload |
| 路由分发 | 按模型类型分流到 pipe/ollama/openai | chat.py:151 generate_chat_completion |
| 出口处理 | 转发流、驱动智能体工具循环、跑 outlet | middleware.py:5656 process_chat_response |
| 工具装配 | 加载并合并四类工具来源 | tools.py:221 get_tools / tools.py:430 get_builtin_tools |
| 非 native 工具 | 用任务模型+提示词模 拟函数调用 | middleware.py:1139 chat_completion_tools_handler |
| 插件-pipe | 把 Python 函数当成一个"模型" | functions.py:147 generate_function_chat_completion |
| 插件-filter | inlet/outlet/stream 三点拦截 | filter.py:197 process_filter_functions |
2.3 一句话点透那个"关键开关"
整条链路的行为被一个参数劈成两半:metadata['params']['function_calling'] 是不是 'native'。
native(模型自主调工具): 入口不强行注入记忆/联网/RAG,而是把它们做成工具挂到form_data['tools'],让模型自己决定要不要调;真正的执行在出口循环里做(middleware.py:2862、2908)。- 非 native(后端替模型调): 入口就用一个任务模型把工具调用"算出来"、当场执行、把结果注入到提示词里,模型只负责最后总结(
middleware.py:2917→1246)。
这条分叉在下面每一章都会反复出现,是理解本项目的钥匙。
3. 阅读地图(建议顺序)
五章由浅入深,顺着"一条请求的旅程"排列。第一次读按顺序;想直接看某个机制可跳读。
-
01-request-lifecycle.md — 一条聊天请求的生命周期:从 HTTP 入口到流式出口。先建立"三段旅程"的整体骨架,搞清
process_chat、generate_chat_completion、process_chat_response各自的边界与交接。先读这章。 -
02-context-assembly.md — 入口装配:把"特性"变成智能体能力(记忆 / 联网 / RAG / 图像)。讲
process_chat_payload如何按features逐项挂上下文,以及 native/非 native 在这一步就开始的分叉(middleware.py:2528–2611)。 -
03-tools-system.md — 工具系统:四类工具来源 + 两种函数调用模式。讲清楚 builtin / Python 函数 / OpenAPI 服务器(
server:)/ MCP(server:mcp:)如何统一成一个tools_dict,以及 native 数组模式 vs 非 native 提示模式的实现差异。 -
04-agentic-loop.md — 智能体循环与出口处理:原生工具调用如何迭代。讲
process_chat_response里那个while tool_calls循环——执行、回灌、再调用,以及CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS的触顶保护。 -
05-plugins-pipes-filters.md — 插件框架:pipe 自定义模型与 filter 拦截函数。讲 pipe 如何把函数注 册成模型、filter 的 inlet/outlet/stream 三个拦截点与优先级排序。
想快速判断"我该读哪章":要理解整体流程读 01;要改上下文/RAG/联网读 02;要接外部工具/MCP读 03;要调多轮智能体行为读 04;要写插件读 05。
4. 巧妙之处(可带走的精华)
每条先白话点出"妙在哪",再给 file:line。细节在对应章里展开。
-
一个开关切换两种智能体范式。
function_calling是否为显式legacy决定"模型自主调工具(native,默认)"还是"后端替它调好再注入提示(legacy)"。同一套特性(联网/RAG/图像/知识库),native 下变成工具、legacy 下变成被强灌的上下文——用一个判断复用了两条完全不同的实现路径(middleware.py:2466、2552、2563、2570)。 -
四类异构工具被抹平成同一个字典。 内置函数、用户 Python 工具、远程 OpenAPI 服务器、MCP 服务器,来源天差地别,却都被规整成
{name: {'spec', 'callable', ...}}的统一形状塞进tools_dict,出口循环因此不必关心工具来自哪里(middleware.py:2794、2837、2862;tools.py:221、430)。 -
工具参数解析先试 JSON 再退回
ast.literal_eval。 顺序已从"先 ast 后 json"翻转为先JSONCodec.loads、失败再退 Python 字面量解析器兜一层,更宽容(middleware.py:4957-4963)。 -
智能体循环有硬上限,而非无限递归。 出口的
while tool_calls由CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS(默认 256,可设 -1 解除)封顶,触顶写一条错误消息而不是卡死(middleware.py:4916、4913;env.py:888)。 -
"caller 自带 tools 就整段跳过服务端工具解析。" 当外部 API 客户端在 body 里直接给了 OpenAI 风格
tools数组,后端识别为"你自己管工具",跳过全部四类工具装配,原样透传(middleware.py:2723的if payload_tools is None)。 -
RAG 注入可被"回滚"以避免重复。 出口在多轮循环里会把消息还原到注入 RAG 模板之前的原始状态再重新应用,防止每轮都把检索上下文叠一遍(
middleware.py:2934–2902存快照,4760–4768还原)。 -
pipe 让"一个函数"就是"一个模型"。 用户写的 pipe 函数被
get_function_models枚举成模型列表,选它时generate_chat_completion直接分流到generate_function_chat_completion执行该函数——模型和插件在这里是同一个抽象(functions.py:71、147、286)。
5. 代码地图(导航索引)
grep 符号名比行号抗漂移(上游更新后行号会变,符号名通常还在)。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| HTTP 入口路由 | backend/open_webui/main.py | chat_completion |
| 三段编排 | backend/open_webui/main.py | process_chat(内联于 chat_completion) |
| 入口装配 | backend/open_webui/utils/middleware.py | process_chat_payload |
| 记忆特性 | backend/open_webui/utils/middleware.py | chat_memory_handler |
| 联网特性 | backend/open_webui/utils/middleware.py | chat_web_search_handler |
| 图像特性 | backend/open_webui/utils/middleware.py | chat_image_generation_handler |
| 非 native 工具调用 | backend/open_webui/utils/middleware.py | chat_completion_tools_handler |
| RAG/文件上下文 | backend/open_webui/utils/middleware.py | chat_completion_files_handler |
| 路由分发 | backend/open_webui/utils/chat.py | generate_chat_completion |
| 出口处理入口 | backend/open_webui/utils/middleware.py | process_chat_response |
| 智能体工具循环 | backend/open_webui/utils/middleware.py | streaming_chat_response_handler(内 while tool_calls) |
| 工具结果处理 | backend/open_webui/utils/middleware.py | process_tool_result |
| 加载 DB/Python 工具 | backend/open_webui/utils/tools.py | get_tools |
| 内置工具集合 | backend/open_webui/utils/tools.py | get_builtin_tools |
| OpenAPI→工具 spec | backend/open_webui/utils/tools.py | convert_openapi_to_tool_payload |
| 调用 OpenAPI 服务器 | backend/open_webui/utils/tools.py | execute_tool_server |
| 连接 MCP 服务器 | backend/open_webui/utils/middleware.py | connect_mcp_server |
| pipe 当模型执行 | backend/open_webui/functions.py | generate_function_chat_completion |
| pipe 模型枚举 | backend/open_webui/functions.py | get_function_models |
| filter 三点拦截 | backend/open_webui/utils/filter.py | process_filter_functions |
| filter 排序/启用 | backend/open_webui/utils/filter.py | get_sorted_filter_ids |
| 循环上限配置 | backend/open_webui/env.py | CHAT_RESPONSE_MAX_TOOL_CALL_ITERATIONS |