NLWeb — 架构与原理
30 秒导读: NLWeb 让一个网站用一句话就能被问答。它吃的是网站早就有的 Schema.org 结构化数据(菜谱、商品、影视、活动……),吐的是一份按相关性排好序、用 Schema.org 描述的 JSON 结果。关键在于:同一个
ask方法既服务人类聊天界面,也作为 MCP server 服务 AI agent。一句话定位——「NLWeb 之于 MCP/A2A,正如 HTML 之于 HTTP」。
1. 这是什么(零基础也能懂)
一句话定义。 NLWeb 是一套「协议 + 参考实现」,帮网站在已有的结构化数据之上,快速长出一个自然语言问答端点。
解决什么问题 / 给谁用。 假设你运营一个菜谱站,库里有几万条菜谱,每条都带 Schema.org 的 Recipe 标注(名字、配料、步骤、热量)。今天用户只能用关键词搜 索框。你想让他们直接问「有什么 20 分钟能做完的素食意面」,甚至想让 ChatGPT 这类 agent 也能替用户来问。NLWeb 就是把「你已有的结构化数据」接上「自然语言 + LLM」的那段管道。
它能做什么。
- 接受一句自然语言提问,返回排好序的结果(Schema.org JSON)。
- 支持多轮对话:把「那再便宜点的呢」自动补全成完整问题(去上下文化)。
- 不只是搜索:能识别「问某个具体条目的细节」「对比两个条目」等意图,路由到不同处理器。
- 天生是个 MCP server:暴露
ask/list_sites/who工具,agent 可直接 JSON-RPC 调用。 - 后端可插拔:向量库(Qdrant / Azure AI Search / Milvus / Postgres / Elasticsearch…)和 LLM(OpenAI / Anthropic / Gemini / Ollama…)都是配置切换。
用起来什么样。 最直白的就是 HTTP 调用(site 指定问哪个站,query 是问题):
# 示意:问 seriouseats 站「20 分钟能做的素食意面」
curl "http://localhost:8000/ask?site=seriouseats&query=20+minute+vegetarian+pasta"
返回是一串 Schema.org Item,每个带 score、description、原始 schema_object。如果是 agent,通过 MCP 调 tools/call 同样问,引擎走的是同一条管道(见 05-protocol-surface.md)。
一句话直觉/类比。 把它想成**「给网站数据配的一个 RAG 接待员」**:检索(从向量库捞候选)→ 打分(LLM 给每条相关性 0-100 分)→ 排序输出。RAG 里典型的「retrieve-then-read」,在这里是「retrieve-then-rank」——不一定生成长答案,默认只是把最相关的条目排好端给你。
2. 顶层全景(它大概怎么转)
怎么读这张图
从左到右是一次 ask 的数据流。中间「prepare 阶段」里的三件事是并发跑的(虚线框),这是 NLWeb 最关键的性能设计:一边赌「这是个简单查询、直接检索打分」(FastTrack),一边并发做「会不会需要补上下文 / 该不该换工具」的预检;预检结果出来后再决定 FastTrack 的成果能不能发出去。
┌─────────────── prepare():以下并发跑 ───────────────┐
HTTP /ask │ │
或 MCP tools/call │ ① 去上下文化 ② FastTrack(赌简单) │
│ │ (补全多轮提问) 检索+逐条LLM打分,结果先攒着 │
▼ │ │ │ │
┌──────────┐ query_params │ ▼ ▼ │
│NLWebHandler├────────────▶│ ③ 工具路由:search? details? compare? │
│(主编排器) │ │ 若选了非 search → 叫停 FastTrack │
└────┬─────┘ └───────────────────┬─────────────────────────────────┘
│ │ 预检完成
│ ▼
│ ┌──────────────────────────┐
│ FastTrack 成功 → │ ranking 阶段 │ ← FastTrack 失败则
│ │ 逐条打分、过滤、按分排序 │ 走常规检索+ranking
│ └────────────┬─────────────┘
│ ▼
│ ┌──────────────────────────┐
└────────────────────────▶│ post-ranking │
流式 Message │ 地图消息? summarize? │
(SSE / JSON-RPC) └────────────┬─────────────┘
▼
排好序的 Schema.org 结果
部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
NLWebHandler | 主编排器:解析参数、跑 prepare、路由、收尾 | core/baseHandler.py |
| 去上下文化 | 把多轮里的「那个呢」补成独立问题 | core/query_analysis/decontextualize.py |
FastTrack | 赌查询简单,先检索+打分,结果攒着等预检放行 | core/fastTrack.py |
ToolSelector | 用 LLM 给每个工具打分,选出 search/details/compare… | core/router.py |
Ranking | 逐条让 LLM 打 0-100 分,过阈值的边算边发 | core/ranking.py |
PostRanking | 排序后处理:要不要发地图、要不要 summarize | core/post_ranking.py |
| 检索抽象 | 把 N 个向量库统一成一个 search() | core/retriever.py |
Message 协议 | 统一的「谁发的 + 发了什么」消息结构 | core/schemas.py |
| MCP 包装 | 把引擎包成标准 MCP JSON-RPC server | webserver/mcp_wrapper.py |
路径基准说明:以上
core/...、webserver/...等.py路径均相对仓库的AskAgent/python/;而后文出现的config/tools.xml、config/config_nlweb.yaml等配置文件相对仓库根(config/在根目录,不在AskAgent/python/下)。
主线走一遍(高层)
- 请求进来(REST 的
/ask或 MCP 的tools/callask),统一变成query_params喂给NLWebHandler(baseHandler.py:38__init__)。 runQuery()先发一个 begin 消息,然后prepare()并发跑去上下文化、FastTrack、查询重写、工具路由(baseHandler.py:352prepare)。- 若 FastTrack 没成功,按工具路由结果走对应处理器(
baseHandler.py:433route_query_based_on_tools);默认工具是 search,即走Ranking。 Ranking把候选逐条丢给 LLM 打分,高分项边算边流式发出(ranking.py:214rankItem)。PostRanking看要不要追加地图消息或生成式摘要(post_ranking.py:15do)。- 发 end 消息,返回所有
Message的序列化结果。
3. 阅读地图(各章导航)
建议顺序:
01-pipeline.md— 先把主线吃透:一次请求经过哪 几个阶段、并发模型长什么样、状态机怎么协调。这是理解其余一切的地基。02-ranking-and-fasttrack.md— NLWeb 的「价值核心」:逐条 LLM 打分、边算边发、以及 FastTrack「赌一把」的并发设计与回滚条件。03-tool-routing.md— 「不止搜索」:怎么用 XML 声明工具、LLM 怎么选工具、选中非 search 时如何叫停 FastTrack。04-retrieval-and-ingestion.md— 数据侧:多向量库统一抽象、多端点并行检索与去重,以及数据如何从 Schema.org 灌进向量库。05-protocol-surface.md— 协议侧:同一引擎如何同时当 REST 端点和 MCP server,统一的Message流式协议。
4. 一句话边界(详见各章 §边界)
- 它默认不生成长答案,默认只排序返回条目;生成/摘要要显式
mode=generate|summarize。 - 它严重依赖站点已有结构化数据质量:库里没有的、标注差的,再聪明的排序也救不回。
- 代码里多处自承
WARNING: This code is under development(如baseHandler.py:7),是参考实现而非生产级框架。
5. 横向对比(同 shelf)
NLWeb 属于 ai-protocol-reference 货架的 web-interop 区。它和 MCP spec 的关系最紧:NLWeb 把自己包成一个标准 MCP server(MCP_PROTOCOL_VERSION = "2024-11-05",mcp_wrapper.py:27),暴露 ask 工具。读完本库可对照货架里的 MCP 规范文档(见本 ai-protocol-reference 货架的 MCP-spec 子库,例如 docs/mcp-spec/03-server-primitives.md 讲 tools/list、tools/call 这些 server primitive;该路径属于 MCP-spec 子库,非 nlweb 仓库内文件),看「一个真实服务怎么落地 MCP 的 tools 能力」。详见 05-protocol-surface.md §横向对比。