跳到主要内容

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,每个带 scoredescription、原始 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排序后处理:要不要发地图、要不要 summarizecore/post_ranking.py
检索抽象把 N 个向量库统一成一个 search()core/retriever.py
Message 协议统一的「谁发的 + 发了什么」消息结构core/schemas.py
MCP 包装把引擎包成标准 MCP JSON-RPC serverwebserver/mcp_wrapper.py

路径基准说明:以上 core/...webserver/....py 路径均相对仓库的 AskAgent/python/;而后文出现的 config/tools.xmlconfig/config_nlweb.yaml 等配置文件相对仓库根config/ 在根目录,不在 AskAgent/python/ 下)。

主线走一遍(高层)

  1. 请求进来(REST 的 /ask 或 MCP 的 tools/call ask),统一变成 query_params 喂给 NLWebHandlerbaseHandler.py:38 __init__)。
  2. runQuery() 先发一个 begin 消息,然后 prepare() 并发跑去上下文化、FastTrack、查询重写、工具路由(baseHandler.py:352 prepare)。
  3. 若 FastTrack 没成功,按工具路由结果走对应处理器(baseHandler.py:433 route_query_based_on_tools);默认工具是 search,即走 Ranking
  4. Ranking 把候选逐条丢给 LLM 打分,高分项边算边流式发出(ranking.py:214 rankItem)。
  5. PostRanking 看要不要追加地图消息或生成式摘要(post_ranking.py:15 do)。
  6. 发 end 消息,返回所有 Message 的序列化结果。

3. 阅读地图(各章导航)

建议顺序:

  1. 01-pipeline.md — 先把主线吃透:一次请求经过哪几个阶段、并发模型长什么样、状态机怎么协调。这是理解其余一切的地基。
  2. 02-ranking-and-fasttrack.md — NLWeb 的「价值核心」:逐条 LLM 打分、边算边发、以及 FastTrack「赌一把」的并发设计与回滚条件。
  3. 03-tool-routing.md — 「不止搜索」:怎么用 XML 声明工具、LLM 怎么选工具、选中非 search 时如何叫停 FastTrack。
  4. 04-retrieval-and-ingestion.md — 数据侧:多向量库统一抽象、多端点并行检索与去重,以及数据如何从 Schema.org 灌进向量库。
  5. 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 §横向对比。