一次 ask 请求的主线
本章讲:一句提问进来后,
NLWebHandler把它带过哪几个阶段,以及这些阶段之间用什么机制协调(重点是 asyncio 的事件信号)。把这条主线吃透,后面三章的机制就都有了挂靠点。
1. 入口:所有协议都收敛到 NLWebHandler
无论请求从 REST 还是 MCP 进来,最终都构造一个 NLWebHandler,参数统一成 query_params 字典。
__init__ 做的全是「解析参数 + 初始化状态容器」,没有业务逻辑(baseHandler.py:38)。值得记住几个字段:
| 字段 | 含义 | 默认 |
|---|---|---|
site | 问哪个站,可逗号分隔多站 | "all" |
query | 用户原始提问 | "" |
prev_queries | 之前的提问(多轮用) | [] |
generate_mode | 输出形态:none / summarize / generate | "none" |
min_score | 排序后过滤的分数下限 | 51 |
max_results | 最多返回几条 | 10 |
streaming | 是否流式发送 | True |
关键直觉:__init__ 还顺手 new 了一堆 asyncio.Event(baseHandler.py:150-154)——pre_checks_done_event、retrieval_done_event、abort_fast_track_event 等。整个管道的「谁等谁」全靠这些事件,而不是轮询标志位。
2. runQuery:主线的五步
runQuery() 是顶层骨架,短到可以全文记住其结构(baseHandler.py:321):
# 示意,非源码:runQuery 的骨架
async def runQuery(self):
await self.message_sender.send_begin_response() # 发「开始」消息
await self.prepare() # 并发预检 + 检索
if self.query_done: # 中途判定结束就直接返回
return [...]
if not self.fastTrackWorked: # FastTrack 没把结果发出去
await self.route_query_based_on_tools() # 才按工具路由处理
if self.query_done:
return [...]
await post_ranking.PostRanking(self).do() # 排序后处理
await self.message_sender.send_end_response() # 发「结束」消息
return [msg.to_dict() for msg in self.messages]
注意 if not self.fastTrackWorked 这行(baseHandler.py:330):如果 FastTrack 已经成功把结果流式发出去了,主线就跳过正常的工具路由+ranking,不重复干活。这是 FastTrack 设计的回报。