A2X 搜索模块设计文档
版本: v0.1.1
本文档详细描述搜索模块(a2x_registry/a2x/search/)的设计。系统整体视图见 a2x_design.md,构建模块见 build_design.md。
1. 模块结构
a2x_registry/a2x/search/
├── __init__.py — 公共 API: A2XSearch, SearchResult, SearchStats, NavigationStep
├── __main__.py — CLI 入口 (python -m a2x_registry.a2x.search)
├── a2x_search.py — 编排器: 组合 Navigator + Selector,管理搜索流程和流式输出
├── models.py — 数据类: SearchStats, NavigationStep, TerminalNode, ServiceGroup (SearchResult 已移至 a2x_registry/common/models.py)
├── navigator.py — Phase 1: CategoryNavigator (LLM 递归分类导航)
├── selector.py — Phase 2+3: ServiceSelector (去重、分组合并、LLM 服务筛选)
└── prompts.py — Prompt 模板 + 响应解析 (纯函数,无状态)
2. 搜索模式
| 模式 | 分类导航策略 | 服务筛选策略 | 适用场景 |
|---|---|---|---|
get_all |
选择所有可能相关的分类 | 包含所有可能相关的服务 | 高召回场景 |
get_important |
选择明确需要的分类 | 只选用户确实需要的服务,去重同功能服务 | 平衡精度与召回 |
get_one |
只选最匹配的一个分类 | 只选最匹配的一个服务 | 高精度场景 |
get_one 在结果为空时自动 fallback 到 get_important,取其第一个结果。
3. 流程逻辑
搜索分为两阶段:
Phase 1(CategoryNavigator):LLM 从根节点出发,在每一层判断"哪些子分类与查询相关",递归进入被选中的分支直至叶节点。多个分支的导航可并行执行。产出:一组终端节点,每个关联一批候选服务。
Phase 2+3(ServiceSelector):
- 去重:跨终端节点先到先得去重
- 合并:将过小的服务组按 LCA 距离与最近邻合并(MIN_GROUP_SIZE=30)
- 筛选:对每个服务组并行调用 LLM 选出匹配结果
4. 对外调用接口
Python 接口
from a2x_registry.a2x.search import A2XSearch
searcher = A2XSearch(
taxonomy_path="taxonomy.json",
class_path="class.json",
service_path="service.json",
max_workers=20,
parallel=True,
mode="get_all", # "get_all" | "get_important" | "get_one"
)
# 同步搜索
results, stats = searcher.search(query)
# 流式搜索(实时返回导航步骤,用于 UI 动画)
for msg in searcher.search(query, stream=True):
if msg["type"] == "step":
# {"parent_id": str, "selected": [str], "pruned": [str]}
elif msg["type"] == "result":
# {"results": [...], "stats": {...}}
数据类
# SearchResult 定义在 a2x_registry/common/models.py,三个搜索方法共用
@dataclass
class SearchResult:
id: str
name: str
description: str = ""
@dataclass
class SearchStats:
llm_calls: int
total_tokens: int
visited_categories: List[str]
pruned_categories: List[str]
visited_category_ids: List[str]
@dataclass
class NavigationStep: # UI 动画用
parent_id: str
selected: List[str]
pruned: List[str]
CLI
python -m a2x_registry.a2x.search --query "I need to book a flight" --mode get_important
python -m a2x_registry.a2x.evaluation --data-dir database/ToolRet_clean --max-queries 50 --mode get_all
5. 逻辑视图
flowchart LR
A([查询]) --> B[Phase 1: 分类导航<br/>CategoryNavigator]
T[(分类树)] --> B
B --> C[Phase 2a: 服务去重]
C --> D[Phase 2b: 小组合并<br/>LCA 最近邻]
D --> E[Phase 2c: 服务筛选]
E --> F([服务列表])
E -.->|get_one 空结果| G[Fallback: get_important]
G --> F
style A fill:#eceff1,stroke:#607d8b
style T fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
style B fill:#c8e6c9,stroke:#4caf50
style C fill:#a5d6a7,stroke:#4caf50
style D fill:#81c784,stroke:#4caf50
style E fill:#66bb6a,stroke:#4caf50
style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style G fill:#fff3e0,stroke:#ff9800
6. 类图
classDiagram
class A2XSearch {
-_navigator: CategoryNavigator
-_selector: ServiceSelector
-llm: LLMClient
-mode: str
+search(query, stream) tuple|Generator
-_search_internal(query, step_callback) tuple
-_search_stream(query) Generator
-_set_mode(mode)
}
class CategoryNavigator {
-llm: LLMClient
-categories: Dict
-classes: Dict
-mode: str
+navigate(query, category_id, stats, ...) List~TerminalNode~
-_navigate_children(...)
-_select_categories(...)
}
class ServiceSelector {
-llm: LLMClient
-services: Dict
-mode: str
+deduplicate(terminal_nodes) List~TerminalNode~
+merge_small_groups(terminal_nodes) List~ServiceGroup~
+select_services(query, groups, stats) List~SearchResult~
}
class prompts {
<<module>>
+build_category_prompt(mode, query, ...)$ str
+build_service_prompt(mode, query, ...)$ str
+parse_selection(response, max_index, mode)$ List~int~
}
A2XSearch *-- CategoryNavigator
A2XSearch *-- ServiceSelector
CategoryNavigator ..> prompts : uses
ServiceSelector ..> prompts : uses
A2XSearch --> LLMClient : uses (a2x_registry.common.llm_client)