argo-search 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/backends/domain_profiles.json +364 -0
- package/backends/engine_registry.yaml +505 -0
- package/backends/quota_profiles.json +395 -0
- package/bin/argo.js +54 -0
- package/config.yaml +554 -0
- package/package.json +43 -0
- package/scripts/adaptive.py +179 -0
- package/scripts/benchmark.py +124 -0
- package/scripts/cache.py +374 -0
- package/scripts/clarify.py +689 -0
- package/scripts/config.py +262 -0
- package/scripts/crawl.py +73 -0
- package/scripts/engines.py +386 -0
- package/scripts/evidence.py +381 -0
- package/scripts/extract.py +69 -0
- package/scripts/fetch.py +118 -0
- package/scripts/health_check.py +437 -0
- package/scripts/health_probe.py +218 -0
- package/scripts/mcp_diag.py +81 -0
- package/scripts/mcp_server.py +488 -0
- package/scripts/query_rewriter.py +278 -0
- package/scripts/quota.py +196 -0
- package/scripts/research.py +499 -0
- package/scripts/route.py +341 -0
- package/scripts/search.py +508 -0
- package/scripts/search_types.py +72 -0
- package/scripts/tfidf_router.py +312 -0
- package/sub-skills/local-search/SKILL.md +104 -0
- package/sub-skills/local-search/config.yaml +328 -0
- package/sub-skills/local-search/engine_registry.py +298 -0
- package/sub-skills/local-search/health_check.py +347 -0
- package/sub-skills/local-search/local_search_adapter.py +56 -0
- package/sub-skills/local-search/parse_maps.yaml +184 -0
- package/sub-skills/local-search/search_v3.py +558 -0
- package/sub-skills/local-search/smart_router.py +215 -0
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
clarify.py — 意图消歧工具(wigolo query_understanding 理念移植)
|
|
4
|
+
|
|
5
|
+
核心能力:
|
|
6
|
+
1. 歧义检测:识别查询中的多义词
|
|
7
|
+
2. 意图分类:判断查询的真实意图
|
|
8
|
+
3. 推荐路由:给出最优搜索策略
|
|
9
|
+
4. 多义展开:列出所有可能含义供用户选择
|
|
10
|
+
|
|
11
|
+
用法:
|
|
12
|
+
python3 clarify.py "Python 吞苹果 兼容吗"
|
|
13
|
+
python3 clarify.py "苹果股价" --explain
|
|
14
|
+
python3 clarify.py "Java" --json
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ── 歧义词库 ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
AMBIGUOUS_TERMS = {
|
|
29
|
+
# 多义实体
|
|
30
|
+
"苹果": {
|
|
31
|
+
"meanings": [
|
|
32
|
+
{"text": "Apple 公司(科技/股票)", "domain": "tech", "weight": 0.6},
|
|
33
|
+
{"text": "苹果(水果/食品)", "domain": "general", "weight": 0.3},
|
|
34
|
+
{"text": "苹果(操作系统/macOS)", "domain": "tech", "weight": 0.1},
|
|
35
|
+
],
|
|
36
|
+
"disambiguation_keywords": {
|
|
37
|
+
"tech": ["股价", "股票", "iPhone", "Mac", "iOS", "WWDC", "市值", "AAPL", "库克", "蒂姆"],
|
|
38
|
+
"food": ["吃", "水果", "营养", "减肥", "种植", "产地", "品种"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
"Python": {
|
|
42
|
+
"meanings": [
|
|
43
|
+
{"text": "Python 编程语言", "domain": "tech", "weight": 0.7},
|
|
44
|
+
{"text": "蟒蛇(动物)", "domain": "nature", "weight": 0.2},
|
|
45
|
+
{"text": "Monty Python(喜剧团体)", "domain": "entertainment", "weight": 0.1},
|
|
46
|
+
],
|
|
47
|
+
"disambiguation_keywords": {
|
|
48
|
+
"tech": ["代码", "编程", "函数", "库", "框架", "pip", "安装", "版本", "async", "Django", "Flask", "报错", "bug"],
|
|
49
|
+
"nature": ["蛇", "爬", "动物", "宠物", "饲养", "鳞片"],
|
|
50
|
+
"entertainment": ["电影", "喜剧", "英国", "动画"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"Java": {
|
|
54
|
+
"meanings": [
|
|
55
|
+
{"text": "Java 编程语言", "domain": "tech", "weight": 0.7},
|
|
56
|
+
{"text": "印度尼西亚爪哇岛", "domain": "travel", "weight": 0.15},
|
|
57
|
+
{"text": "Java 咖啡", "domain": "food", "weight": 0.15},
|
|
58
|
+
],
|
|
59
|
+
"disambiguation_keywords": {
|
|
60
|
+
"tech": ["编程", "代码", "JDK", "Spring", "Maven", "Gradle", "JVM", "报错", "版本"],
|
|
61
|
+
"travel": ["旅游", "岛", "签证", "机票", "酒店"],
|
|
62
|
+
"food": ["咖啡", "豆", "烘焙", "产地"],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"Rust": {
|
|
66
|
+
"meanings": [
|
|
67
|
+
{"text": "Rust 编程语言", "domain": "tech", "weight": 0.7},
|
|
68
|
+
{"text": "锈蚀(化学/材料)", "domain": "science", "weight": 0.2},
|
|
69
|
+
{"text": "Rust 游戏", "domain": "gaming", "weight": 0.1},
|
|
70
|
+
],
|
|
71
|
+
"disambiguation_keywords": {
|
|
72
|
+
"tech": ["编程", "cargo", "crate", "所有权", "borrow", "编译", "代码", "性能"],
|
|
73
|
+
"science": ["金属", "腐蚀", "防护", "涂层", "氧化"],
|
|
74
|
+
"gaming": ["游戏", "服务器", "联机", "steam"],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
"茅台": {
|
|
78
|
+
"meanings": [
|
|
79
|
+
{"text": "贵州茅台(股票/白酒品牌)", "domain": "finance", "weight": 0.7},
|
|
80
|
+
{"text": "茅台镇(地名/产区)", "domain": "travel", "weight": 0.2},
|
|
81
|
+
{"text": "茅台酒(产品/品鉴)", "domain": "lifestyle", "weight": 0.1},
|
|
82
|
+
],
|
|
83
|
+
"disambiguation_keywords": {
|
|
84
|
+
"finance": ["股价", "股票", "市值", "财报", "营收", "利润", "分红", "600519"],
|
|
85
|
+
"travel": ["镇", "旅游", "产区", "参观", "路线"],
|
|
86
|
+
"lifestyle": ["口感", "品鉴", "收藏", "年份", "真假"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"Transformer": {
|
|
90
|
+
"meanings": [
|
|
91
|
+
{"text": "Transformer 模型(AI/深度学习)", "domain": "tech", "weight": 0.6},
|
|
92
|
+
{"text": "变形金刚(玩具/电影)", "domain": "entertainment", "weight": 0.25},
|
|
93
|
+
{"text": "变压器(电气设备)", "domain": "engineering", "weight": 0.15},
|
|
94
|
+
],
|
|
95
|
+
"disambiguation_keywords": {
|
|
96
|
+
"tech": ["attention", "BERT", "GPT", "神经网络", "NLP", "论文", "模型", "训练", "AI", "大模型"],
|
|
97
|
+
"entertainment": ["电影", "玩具", "擎天柱", "大黄蜂", "动画"],
|
|
98
|
+
"engineering": ["电力", "电压", "电网", "变电站"],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
# ── 扩展歧义词库(v2.1 新增 30+ 词)──
|
|
102
|
+
"小米": {
|
|
103
|
+
"meanings": [
|
|
104
|
+
{"text": "小米科技(手机/IoT品牌)", "domain": "tech", "weight": 0.6},
|
|
105
|
+
{"text": "小米(谷物/粮食)", "domain": "food", "weight": 0.3},
|
|
106
|
+
{"text": "小米SU7(汽车)", "domain": "auto", "weight": 0.1},
|
|
107
|
+
],
|
|
108
|
+
"disambiguation_keywords": {
|
|
109
|
+
"tech": ["手机", "MIUI", "HyperOS", "雷军", "SU7", "市值", "港股", "1810"],
|
|
110
|
+
"food": ["粮食", "作物", "种植", "产量", "小米粥"],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
"华为": {
|
|
114
|
+
"meanings": [
|
|
115
|
+
{"text": "华为技术有限公司", "domain": "tech", "weight": 0.8},
|
|
116
|
+
{"text": "华为(人名/地名)", "domain": "general", "weight": 0.2},
|
|
117
|
+
],
|
|
118
|
+
"disambiguation_keywords": {"tech": ["手机", "鸿蒙", "5G", "芯片", "Mate", "任正非"]},
|
|
119
|
+
},
|
|
120
|
+
"特斯拉": {
|
|
121
|
+
"meanings": [
|
|
122
|
+
{"text": "Tesla(电动汽车品牌)", "domain": "auto", "weight": 0.8},
|
|
123
|
+
{"text": "尼古拉·特斯拉(发明家)", "domain": "science", "weight": 0.2},
|
|
124
|
+
],
|
|
125
|
+
"disambiguation_keywords": {
|
|
126
|
+
"auto": ["Model", "充电桩", "自动驾驶", "FSD", "马斯克"],
|
|
127
|
+
"science": ["交流电", "发明", "无线电", "特斯拉线圈"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
"Docker": {
|
|
131
|
+
"meanings": [
|
|
132
|
+
{"text": "Docker 容器化平台", "domain": "tech", "weight": 0.85},
|
|
133
|
+
{"text": "码头工人", "domain": "general", "weight": 0.15},
|
|
134
|
+
],
|
|
135
|
+
"disambiguation_keywords": {"tech": ["容器", "镜像", "compose", "Kubernetes", "部署"]},
|
|
136
|
+
},
|
|
137
|
+
"Go": {
|
|
138
|
+
"meanings": [
|
|
139
|
+
{"text": "Go 编程语言", "domain": "tech", "weight": 0.7},
|
|
140
|
+
{"text": "去/进行(动词)", "domain": "general", "weight": 0.3},
|
|
141
|
+
],
|
|
142
|
+
"disambiguation_keywords": {"tech": ["Golang", "并发", "goroutine", "channel", "编译", "stdlib"]},
|
|
143
|
+
},
|
|
144
|
+
"Redis": {
|
|
145
|
+
"meanings": [
|
|
146
|
+
{"text": "Redis 缓存数据库", "domain": "tech", "weight": 0.9},
|
|
147
|
+
{"text": "Redis(人名)", "domain": "general", "weight": 0.1},
|
|
148
|
+
],
|
|
149
|
+
"disambiguation_keywords": {"tech": ["缓存", "内存", "键值", "持久化", "集群"]},
|
|
150
|
+
},
|
|
151
|
+
"Swift": {
|
|
152
|
+
"meanings": [
|
|
153
|
+
{"text": "Swift 编程语言(Apple)", "domain": "tech", "weight": 0.7},
|
|
154
|
+
{"text": "Swift(金融转账系统)", "domain": "finance", "weight": 0.15},
|
|
155
|
+
{"text": "Swift(歌手)", "domain": "entertainment", "weight": 0.15},
|
|
156
|
+
],
|
|
157
|
+
"disambiguation_keywords": {
|
|
158
|
+
"tech": ["iOS", "macOS", "Xcode", "SwiftUI", "苹果"],
|
|
159
|
+
"finance": ["转账", "银行", "国际结算"],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
"Kotlin": {
|
|
163
|
+
"meanings": [
|
|
164
|
+
{"text": "Kotlin 编程语言", "domain": "tech", "weight": 0.85},
|
|
165
|
+
{"text": "Kotlin(地名/岛名)", "domain": "travel", "weight": 0.15},
|
|
166
|
+
],
|
|
167
|
+
"disambiguation_keywords": {"tech": ["Android", "JVM", "JetBrains", "协程", "空安全"]},
|
|
168
|
+
},
|
|
169
|
+
"抖音": {
|
|
170
|
+
"meanings": [
|
|
171
|
+
{"text": "抖音/TikTok(短视频平台)", "domain": "tech", "weight": 0.8},
|
|
172
|
+
{"text": "抖音(动词/拟声)", "domain": "general", "weight": 0.2},
|
|
173
|
+
],
|
|
174
|
+
"disambiguation_keywords": {"tech": ["短视频", "直播", "字节跳动", "TikTok", "算法"]},
|
|
175
|
+
},
|
|
176
|
+
"微信": {
|
|
177
|
+
"meanings": [
|
|
178
|
+
{"text": "微信(通讯/社交平台)", "domain": "tech", "weight": 0.85},
|
|
179
|
+
{"text": "微信(动词)", "domain": "general", "weight": 0.15},
|
|
180
|
+
],
|
|
181
|
+
"disambiguation_keywords": {"tech": ["小程序", "公众号", "支付", "腾讯", "朋友圈"]},
|
|
182
|
+
},
|
|
183
|
+
"GPT": {
|
|
184
|
+
"meanings": [
|
|
185
|
+
{"text": "GPT 系列大语言模型(OpenAI)", "domain": "tech", "weight": 0.8},
|
|
186
|
+
{"text": "GPT(GUID Partition Table)", "domain": "tech", "weight": 0.2},
|
|
187
|
+
],
|
|
188
|
+
"disambiguation_keywords": {"tech": ["OpenAI", "ChatGPT", "大模型", "GPT-4", "GPT-5"]},
|
|
189
|
+
},
|
|
190
|
+
"RAG": {
|
|
191
|
+
"meanings": [
|
|
192
|
+
{"text": "RAG(检索增强生成)", "domain": "tech", "weight": 0.7},
|
|
193
|
+
{"text": "RAG(摇滚乐风格)", "domain": "music", "weight": 0.15},
|
|
194
|
+
{"text": "Rag(抹布)", "domain": "general", "weight": 0.15},
|
|
195
|
+
],
|
|
196
|
+
"disambiguation_keywords": {"tech": ["检索", "向量", "embedding", "大模型", "知识库"]},
|
|
197
|
+
},
|
|
198
|
+
"芯片": {
|
|
199
|
+
"meanings": [
|
|
200
|
+
{"text": "半导体芯片(IC)", "domain": "tech", "weight": 0.7},
|
|
201
|
+
{"text": "薯片/食品", "domain": "food", "weight": 0.2},
|
|
202
|
+
{"text": "筹码(赌博)", "domain": "general", "weight": 0.1},
|
|
203
|
+
],
|
|
204
|
+
"disambiguation_keywords": {
|
|
205
|
+
"tech": ["半导体", "NVIDIA", "台积电", "ASML", "光刻机", "GPU"],
|
|
206
|
+
"food": ["零食", "好吃", "口味", "乐事"],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
"蔚来": {
|
|
210
|
+
"meanings": [
|
|
211
|
+
{"text": "蔚来汽车(NIO)", "domain": "auto", "weight": 0.8},
|
|
212
|
+
{"text": "蔚蓝/蔚然(形容词)", "domain": "general", "weight": 0.2},
|
|
213
|
+
],
|
|
214
|
+
"disambiguation_keywords": {"auto": ["电动车", "换电", "NIO", "李斌", "ET5", "ES6"]},
|
|
215
|
+
},
|
|
216
|
+
"理想": {
|
|
217
|
+
"meanings": [
|
|
218
|
+
{"text": "理想汽车(Li Auto)", "domain": "auto", "weight": 0.6},
|
|
219
|
+
{"text": "理想(概念/哲学)", "domain": "general", "weight": 0.4},
|
|
220
|
+
],
|
|
221
|
+
"disambiguation_keywords": {"auto": ["增程", "L系列", "MEGA", "李想", "纯电"]},
|
|
222
|
+
},
|
|
223
|
+
"小鹏": {
|
|
224
|
+
"meanings": [
|
|
225
|
+
{"text": "小鹏汽车(XPeng)", "domain": "auto", "weight": 0.8},
|
|
226
|
+
{"text": "小鹏(人名)", "domain": "general", "weight": 0.2},
|
|
227
|
+
],
|
|
228
|
+
"disambiguation_keywords": {"auto": ["P7", "G6", "飞行汽车", "何小鹏", "智驾"]},
|
|
229
|
+
},
|
|
230
|
+
"Anthropic": {
|
|
231
|
+
"meanings": [
|
|
232
|
+
{"text": "Anthropic(AI 公司/Claude)", "domain": "tech", "weight": 0.85},
|
|
233
|
+
{"text": "Anthropic(人类学的)", "domain": "science", "weight": 0.15},
|
|
234
|
+
],
|
|
235
|
+
"disambiguation_keywords": {"tech": ["Claude", "大模型", "AI安全", "Constitutional AI"]},
|
|
236
|
+
},
|
|
237
|
+
"OpenAI": {
|
|
238
|
+
"meanings": [
|
|
239
|
+
{"text": "OpenAI(AI 公司)", "domain": "tech", "weight": 0.9},
|
|
240
|
+
{"text": "OpenAI(开源 AI 组织)", "domain": "tech", "weight": 0.1},
|
|
241
|
+
],
|
|
242
|
+
"disambiguation_keywords": {"tech": ["GPT", "ChatGPT", "Sam Altman", "DALL-E", "Sora"]},
|
|
243
|
+
},
|
|
244
|
+
"Cursor": {
|
|
245
|
+
"meanings": [
|
|
246
|
+
{"text": "Cursor(AI 代码编辑器)", "domain": "tech", "weight": 0.7},
|
|
247
|
+
{"text": "光标/鼠标指针", "domain": "general", "weight": 0.3},
|
|
248
|
+
],
|
|
249
|
+
"disambiguation_keywords": {"tech": ["编辑器", "VSCode", "AI编程", "Tab补全", "Composer"]},
|
|
250
|
+
},
|
|
251
|
+
"Gemini": {
|
|
252
|
+
"meanings": [
|
|
253
|
+
{"text": "Google Gemini(AI 模型)", "domain": "tech", "weight": 0.7},
|
|
254
|
+
{"text": "双子座(星座)", "domain": "general", "weight": 0.2},
|
|
255
|
+
{"text": "Gemini(电影工作室)", "domain": "entertainment", "weight": 0.1},
|
|
256
|
+
],
|
|
257
|
+
"disambiguation_keywords": {"tech": ["Google", "大模型", "Gemini Pro", "多模态"]},
|
|
258
|
+
},
|
|
259
|
+
"Claude": {
|
|
260
|
+
"meanings": [
|
|
261
|
+
{"text": "Claude(Anthropic AI 助手)", "domain": "tech", "weight": 0.8},
|
|
262
|
+
{"text": "Claude(法语人名)", "domain": "general", "weight": 0.2},
|
|
263
|
+
],
|
|
264
|
+
"disambiguation_keywords": {"tech": ["Anthropic", "大模型", "Claude 3", "AI助手"]},
|
|
265
|
+
},
|
|
266
|
+
"Notion": {
|
|
267
|
+
"meanings": [
|
|
268
|
+
{"text": "Notion(协作/笔记工具)", "domain": "tech", "weight": 0.85},
|
|
269
|
+
{"text": "概念/观念", "domain": "general", "weight": 0.15},
|
|
270
|
+
],
|
|
271
|
+
"disambiguation_keywords": {"tech": ["笔记", "协作", "数据库", "模板", "AI"]},
|
|
272
|
+
},
|
|
273
|
+
"飞书": {
|
|
274
|
+
"meanings": [
|
|
275
|
+
{"text": "飞书(字节跳动协作工具)", "domain": "tech", "weight": 0.8},
|
|
276
|
+
{"text": "飞书(飞来的书信)", "domain": "general", "weight": 0.2},
|
|
277
|
+
],
|
|
278
|
+
"disambiguation_keywords": {"tech": ["协作", "OKR", "视频会议", "文档", "多维表格"]},
|
|
279
|
+
},
|
|
280
|
+
"比特币": {
|
|
281
|
+
"meanings": [
|
|
282
|
+
{"text": "Bitcoin(加密货币)", "domain": "finance", "weight": 0.85},
|
|
283
|
+
{"text": "比特币(泛指加密货币)", "domain": "finance", "weight": 0.15},
|
|
284
|
+
],
|
|
285
|
+
"disambiguation_keywords": {"finance": ["BTC", "挖矿", "区块链", "交易所", "减半", "ETF"]},
|
|
286
|
+
},
|
|
287
|
+
"新能源": {
|
|
288
|
+
"meanings": [
|
|
289
|
+
{"text": "新能源汽车", "domain": "auto", "weight": 0.6},
|
|
290
|
+
{"text": "新能源(太阳能/风能等)", "domain": "energy", "weight": 0.4},
|
|
291
|
+
],
|
|
292
|
+
"disambiguation_keywords": {
|
|
293
|
+
"auto": ["电动车", "比亚迪", "特斯拉", "充电桩", "续航"],
|
|
294
|
+
"energy": ["光伏", "风电", "储能", "碳中和"],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
"A股": {
|
|
298
|
+
"meanings": [
|
|
299
|
+
{"text": "A股市场(中国股票)", "domain": "finance", "weight": 0.9},
|
|
300
|
+
{"text": "A股(游戏术语)", "domain": "gaming", "weight": 0.1},
|
|
301
|
+
],
|
|
302
|
+
"disambiguation_keywords": {"finance": ["上证", "深证", "创业板", "科创板", "北交所"]},
|
|
303
|
+
},
|
|
304
|
+
"ETF": {
|
|
305
|
+
"meanings": [
|
|
306
|
+
{"text": "ETF(交易所交易基金)", "domain": "finance", "weight": 0.9},
|
|
307
|
+
{"text": "ETF(其他缩写)", "domain": "general", "weight": 0.1},
|
|
308
|
+
],
|
|
309
|
+
"disambiguation_keywords": {"finance": ["指数基金", "净值", "场内", "LOF", "QDII"]},
|
|
310
|
+
},
|
|
311
|
+
"量化": {
|
|
312
|
+
"meanings": [
|
|
313
|
+
{"text": "量化投资/量化交易", "domain": "finance", "weight": 0.7},
|
|
314
|
+
{"text": "量化(物理学/化学)", "domain": "science", "weight": 0.3},
|
|
315
|
+
],
|
|
316
|
+
"disambiguation_keywords": {
|
|
317
|
+
"finance": ["对冲", "因子", "Alpha", "回测", "高频", "策略"],
|
|
318
|
+
"science": ["测量", "分析", "浓度", "实验"],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
"期权": {
|
|
322
|
+
"meanings": [
|
|
323
|
+
{"text": "期权(金融衍生品)", "domain": "finance", "weight": 0.85},
|
|
324
|
+
{"text": "期权(选择权/选项)", "domain": "general", "weight": 0.15},
|
|
325
|
+
],
|
|
326
|
+
"disambiguation_keywords": {"finance": ["认购", "认沽", "行权", "波动率", "Greeks"]},
|
|
327
|
+
},
|
|
328
|
+
"基金": {
|
|
329
|
+
"meanings": [
|
|
330
|
+
{"text": "公募/私募基金", "domain": "finance", "weight": 0.8},
|
|
331
|
+
{"text": "基金(基础/根本)", "domain": "general", "weight": 0.2},
|
|
332
|
+
],
|
|
333
|
+
"disambiguation_keywords": {"finance": ["净值", "回撤", "夏普", "基金经理", "ETF"]},
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# ── 品牌碰撞检测 ──────────────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
BRAND_COLLISIONS = {
|
|
340
|
+
"Amazon": {"domains": ["amazon.com", "aws.amazon.com"], "alt": ["亚马逊(河流/地区)"]},
|
|
341
|
+
"Apple": {"domains": ["apple.com"], "alt": ["苹果(水果)"]},
|
|
342
|
+
"小米": {"domains": ["xiaomi.com", "mi.com"], "alt": ["小米(谷物)"]},
|
|
343
|
+
"华为": {"domains": ["huawei.com"], "alt": ["华为(人名)"]},
|
|
344
|
+
"特斯拉": {"domains": ["tesla.com"], "alt": ["尼古拉·特斯拉(发明家)"]},
|
|
345
|
+
"字节": {"domains": ["bytedance.com"], "alt": ["字节(计算机单位)"]},
|
|
346
|
+
"快手": {"domains": ["kuaishou.com"], "alt": ["快手(手快)"]},
|
|
347
|
+
"贝壳": {"domains": ["ke.com", "beike.com"], "alt": ["贝壳(海洋生物)"]},
|
|
348
|
+
"飞书": {"domains": ["feishu.cn"], "alt": ["飞来的书信"]},
|
|
349
|
+
"钉钉": {"domains": ["dingtalk.com"], "alt": ["钉钉子的声音"]},
|
|
350
|
+
"美团": {"domains": ["meituan.com"], "alt": ["美好的团体"]},
|
|
351
|
+
"京东": {"domains": ["jd.com"], "alt": ["京东(地名)"]},
|
|
352
|
+
"淘宝": {"domains": ["taobao.com"], "alt": ["淘到的宝贝"]},
|
|
353
|
+
"拼多多": {"domains": ["pinduoduo.com"], "alt": ["拼凑"]},
|
|
354
|
+
"蔚来": {"domains": ["nio.com"], "alt": ["蔚蓝/蔚然"]},
|
|
355
|
+
"理想": {"domains": ["lixiang.com"], "alt": ["理想(概念)"]},
|
|
356
|
+
"小鹏": {"domains": ["xiaopeng.com"], "alt": ["小鹏(人名)"]},
|
|
357
|
+
"豆瓣": {"domains": ["douban.com"], "alt": ["豆瓣酱"]},
|
|
358
|
+
"雪球": {"domains": ["xueqiu.com"], "alt": ["雪球(游戏)"]},
|
|
359
|
+
"知乎": {"domains": ["zhihu.com"], "alt": ["知乎(文言文)"]},
|
|
360
|
+
"闲鱼": {"domains": ["goofish.com"], "alt": ["闲置的鱼"]},
|
|
361
|
+
"盒马": {"domains": ["hema.com"], "alt": ["盒马(动物)"]},
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def detect_brand_collision(query: str) -> dict | None:
|
|
366
|
+
"""检测查询中的品牌碰撞风险。"""
|
|
367
|
+
for brand, info in BRAND_COLLISIONS.items():
|
|
368
|
+
if brand in query or brand.lower() in query.lower():
|
|
369
|
+
return {
|
|
370
|
+
"brand": brand,
|
|
371
|
+
"collision_domains": info["domains"],
|
|
372
|
+
"alt_meanings": info["alt"],
|
|
373
|
+
"warning": f"「{brand}」可能指向多个品牌/产品",
|
|
374
|
+
}
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# 意图模式
|
|
379
|
+
INTENT_PATTERNS = {
|
|
380
|
+
"search_fact": {
|
|
381
|
+
"patterns": [
|
|
382
|
+
r"(?:多少|几|价格|值|身高|重量|面积|人口|GDP|增长率)",
|
|
383
|
+
r"(?:what|how many|how much|when|where|who)",
|
|
384
|
+
r"(?:是谁|是什么|在哪里|什么时候|多少钱)",
|
|
385
|
+
],
|
|
386
|
+
"label": "事实查询",
|
|
387
|
+
"weight": 1.0,
|
|
388
|
+
},
|
|
389
|
+
"search_opinion": {
|
|
390
|
+
"patterns": [
|
|
391
|
+
r"(?:怎么看|如何评价|观点|想法|建议|推荐|意见)",
|
|
392
|
+
r"(?:how|why|what do you think|opinion|view)",
|
|
393
|
+
r"(?:值得|好不好|怎么样|该不该|有没有必要)",
|
|
394
|
+
],
|
|
395
|
+
"label": "观点/评价",
|
|
396
|
+
"weight": 1.0,
|
|
397
|
+
},
|
|
398
|
+
"search_tech": {
|
|
399
|
+
"patterns": [
|
|
400
|
+
r"(?:怎么用|如何配置|安装|部署|报错|bug|error|教程|入门)",
|
|
401
|
+
r"(?:how to|tutorial|guide|setup|install|configure|troubleshoot)",
|
|
402
|
+
r"(?:API|SDK|框架|库|组件|插件|扩展)",
|
|
403
|
+
],
|
|
404
|
+
"label": "技术操作",
|
|
405
|
+
"weight": 1.0,
|
|
406
|
+
},
|
|
407
|
+
"search_compare": {
|
|
408
|
+
"patterns": [
|
|
409
|
+
r"(?:vs| versus |对比|比较|区别|差异|选择|哪个好|优缺点)",
|
|
410
|
+
r"(?:A vs B|A or B|A compared to B)",
|
|
411
|
+
],
|
|
412
|
+
"label": "对比分析",
|
|
413
|
+
"weight": 1.0,
|
|
414
|
+
},
|
|
415
|
+
"search_news": {
|
|
416
|
+
"patterns": [
|
|
417
|
+
r"(?:最新|今天|最近|新闻|动态|进展|发生|事件)",
|
|
418
|
+
r"(?:latest|recent|news|update|breaking)",
|
|
419
|
+
],
|
|
420
|
+
"label": "新闻/动态",
|
|
421
|
+
"weight": 1.0,
|
|
422
|
+
},
|
|
423
|
+
"search_deep": {
|
|
424
|
+
"patterns": [
|
|
425
|
+
r"(?:深度|全面|详细|系统|完整|综述|研究|分析|探讨)",
|
|
426
|
+
r"(?:deep|comprehensive|review|survey|research|analysis)",
|
|
427
|
+
],
|
|
428
|
+
"label": "深度研究",
|
|
429
|
+
"weight": 1.0,
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# ── 核心分析 ──────────────────────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
def analyze_query(query: str) -> dict[str, Any]:
|
|
437
|
+
"""分析查询的意图和歧义。"""
|
|
438
|
+
analysis = {
|
|
439
|
+
"query": query,
|
|
440
|
+
"language": _detect_language(query),
|
|
441
|
+
"ambiguities": [],
|
|
442
|
+
"intents": [],
|
|
443
|
+
"entities": [],
|
|
444
|
+
"recommended_strategy": "general",
|
|
445
|
+
"confidence": 0.8,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# 歧义检测
|
|
449
|
+
for term, info in AMBIGUOUS_TERMS.items():
|
|
450
|
+
if term in query or (term.isascii() and re.search(r'\b' + re.escape(term) + r'\b', query, re.I)):
|
|
451
|
+
# 检查上下文关键词
|
|
452
|
+
matched_meanings = []
|
|
453
|
+
for meaning in info["meanings"]:
|
|
454
|
+
domain = meaning["domain"]
|
|
455
|
+
keywords = info.get("disambiguation_keywords", {}).get(domain, [])
|
|
456
|
+
match_count = sum(1 for kw in keywords if kw in query)
|
|
457
|
+
# 强信号加分:产品型号、金融术语、学术术语
|
|
458
|
+
strong_bonus = 0
|
|
459
|
+
if domain == "auto" and re.search(r'(SU7|Model|ET5|ES6|P7|G6|L系列|MEGA|AION)', query, re.I):
|
|
460
|
+
strong_bonus = 0.3
|
|
461
|
+
elif domain == "finance" and re.search(r'(股价|财报|市值|基金|ETF|净值|涨跌|K线|分红)', query):
|
|
462
|
+
strong_bonus = 0.2
|
|
463
|
+
elif domain == "tech" and re.search(r'(论文|paper|arxiv|API|SDK|框架|库|编程|代码|编译|部署)', query, re.I):
|
|
464
|
+
strong_bonus = 0.15
|
|
465
|
+
elif domain == "energy" and re.search(r'(光伏|风电|储能|碳中和|太阳能|风能)', query):
|
|
466
|
+
strong_bonus = 0.2
|
|
467
|
+
if match_count > 0 or strong_bonus > 0:
|
|
468
|
+
matched_meanings.append({
|
|
469
|
+
"meaning": meaning["text"],
|
|
470
|
+
"domain": domain,
|
|
471
|
+
"context_match": match_count,
|
|
472
|
+
"base_weight": meaning["weight"],
|
|
473
|
+
"weight": meaning["weight"] + match_count * 0.1 + strong_bonus,
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
if matched_meanings:
|
|
477
|
+
matched_meanings.sort(key=lambda x: x["weight"], reverse=True)
|
|
478
|
+
top = matched_meanings[0]
|
|
479
|
+
# 置信度计算:基础分 + 关键词匹配加分 + 强信号加分
|
|
480
|
+
base_conf = 0.5
|
|
481
|
+
keyword_bonus = min(top["context_match"] * 0.12, 0.3)
|
|
482
|
+
# strong_bonus 从 weight 中提取(weight = base_weight + keyword*0.1 + strong_bonus)
|
|
483
|
+
strong_bonus = max(0, top["weight"] - top.get("base_weight", 0.5) - top["context_match"] * 0.1)
|
|
484
|
+
conf = min(base_conf + keyword_bonus + strong_bonus * 0.5, 0.95)
|
|
485
|
+
analysis["ambiguities"].append({
|
|
486
|
+
"term": term,
|
|
487
|
+
"possible_meanings": [m["meaning"] for m in matched_meanings],
|
|
488
|
+
"top_choice": top["meaning"],
|
|
489
|
+
"confidence": round(conf, 2),
|
|
490
|
+
})
|
|
491
|
+
analysis["confidence"] = min(analysis["confidence"], conf)
|
|
492
|
+
else:
|
|
493
|
+
# 无法消歧
|
|
494
|
+
analysis["ambiguities"].append({
|
|
495
|
+
"term": term,
|
|
496
|
+
"possible_meanings": [m["text"] for m in info["meanings"]],
|
|
497
|
+
"top_choice": info["meanings"][0]["text"],
|
|
498
|
+
"confidence": info["meanings"][0]["weight"],
|
|
499
|
+
})
|
|
500
|
+
analysis["confidence"] = min(analysis["confidence"], 0.6)
|
|
501
|
+
|
|
502
|
+
# 意图分类
|
|
503
|
+
for intent_key, intent_info in INTENT_PATTERNS.items():
|
|
504
|
+
for pattern in intent_info["patterns"]:
|
|
505
|
+
if re.search(pattern, query, re.I):
|
|
506
|
+
analysis["intents"].append({
|
|
507
|
+
"type": intent_key,
|
|
508
|
+
"label": intent_info["label"],
|
|
509
|
+
})
|
|
510
|
+
break
|
|
511
|
+
|
|
512
|
+
# 实体提取(简单规则)
|
|
513
|
+
# 英文专有名词
|
|
514
|
+
eng_entities = re.findall(r"\b([A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*)\b", query)
|
|
515
|
+
for e in eng_entities:
|
|
516
|
+
if len(e) > 2:
|
|
517
|
+
analysis["entities"].append({"text": e, "type": "proper_noun"})
|
|
518
|
+
|
|
519
|
+
# 中文专业术语
|
|
520
|
+
cn_tech = re.findall(r"([\u4e00-\u9fff]{2,4}(?:引擎|框架|工具|平台|模型|算法|协议|接口|数据库))", query)
|
|
521
|
+
for e in cn_tech:
|
|
522
|
+
analysis["entities"].append({"text": e, "type": "tech_term"})
|
|
523
|
+
|
|
524
|
+
# 编号/代码
|
|
525
|
+
codes = re.findall(r"\b(\d{6})\b", query)
|
|
526
|
+
for c in codes:
|
|
527
|
+
analysis["entities"].append({"text": c, "type": "stock_code"})
|
|
528
|
+
|
|
529
|
+
cve_codes = re.findall(r"(CVE-\d{4}-\d+)", query, re.I)
|
|
530
|
+
for c in cve_codes:
|
|
531
|
+
analysis["entities"].append({"text": c, "type": "cve"})
|
|
532
|
+
|
|
533
|
+
# 品牌碰撞检测
|
|
534
|
+
collision = detect_brand_collision(query)
|
|
535
|
+
if collision:
|
|
536
|
+
analysis["brand_collision"] = collision
|
|
537
|
+
analysis["confidence"] *= 0.85
|
|
538
|
+
|
|
539
|
+
# 推荐策略
|
|
540
|
+
if analysis["ambiguities"] and analysis["ambiguities"][0]["confidence"] < 0.7:
|
|
541
|
+
analysis["recommended_strategy"] = "clarify_first"
|
|
542
|
+
analysis["confidence"] = 0.5
|
|
543
|
+
elif any(i["type"] == "search_deep" for i in analysis["intents"]):
|
|
544
|
+
analysis["recommended_strategy"] = "deep_research"
|
|
545
|
+
elif any(i["type"] == "search_compare" for i in analysis["intents"]):
|
|
546
|
+
analysis["recommended_strategy"] = "split_search"
|
|
547
|
+
elif any(i["type"] == "search_fact" for i in analysis["intents"]):
|
|
548
|
+
analysis["recommended_strategy"] = "direct_search"
|
|
549
|
+
elif any(i["type"] == "search_news" for i in analysis["intents"]):
|
|
550
|
+
analysis["recommended_strategy"] = "news_priority"
|
|
551
|
+
else:
|
|
552
|
+
analysis["recommended_strategy"] = "general"
|
|
553
|
+
|
|
554
|
+
return analysis
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _detect_language(text: str) -> str:
|
|
558
|
+
"""检测主要语言。"""
|
|
559
|
+
cn_count = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
|
|
560
|
+
en_count = sum(1 for c in text if c.isascii() and c.isalpha())
|
|
561
|
+
ja_count = sum(1 for c in text if "\u3040" <= c <= "\u30ff")
|
|
562
|
+
|
|
563
|
+
total = cn_count + en_count + ja_count
|
|
564
|
+
if total == 0:
|
|
565
|
+
return "unknown"
|
|
566
|
+
|
|
567
|
+
if cn_count / total > 0.5:
|
|
568
|
+
return "zh" if en_count / total < 0.3 else "zh-en"
|
|
569
|
+
elif ja_count / total > 0.3:
|
|
570
|
+
return "ja" if cn_count / total < 0.3 else "zh-ja"
|
|
571
|
+
elif en_count / total > 0.5:
|
|
572
|
+
return "en"
|
|
573
|
+
else:
|
|
574
|
+
return "mixed"
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
# ── 路由推荐 ──────────────────────────────────────────────────────────────────
|
|
578
|
+
|
|
579
|
+
def recommend_routing(analysis: dict[str, Any]) -> dict[str, Any]:
|
|
580
|
+
"""基于意图分析推荐搜索路由。"""
|
|
581
|
+
strategy = analysis["recommended_strategy"]
|
|
582
|
+
|
|
583
|
+
routing = {
|
|
584
|
+
"strategy": strategy,
|
|
585
|
+
"engines": [],
|
|
586
|
+
"mode": "auto",
|
|
587
|
+
"explanation": "",
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if strategy == "clarify_first":
|
|
591
|
+
routing["engines"] = ["clarify_first"]
|
|
592
|
+
routing["explanation"] = f"检测到歧义词「{analysis['ambiguities'][0]['term']}」,建议先消歧再搜索"
|
|
593
|
+
routing["mode"] = "auto"
|
|
594
|
+
|
|
595
|
+
elif strategy == "deep_research":
|
|
596
|
+
routing["engines"] = ["research"]
|
|
597
|
+
routing["explanation"] = "检测到深度研究意图,建议使用深度研究工具"
|
|
598
|
+
routing["mode"] = "deep"
|
|
599
|
+
|
|
600
|
+
elif strategy == "split_search":
|
|
601
|
+
routing["engines"] = ["search", "search"] # 两次搜索
|
|
602
|
+
routing["explanation"] = "检测到对比意图,建议分别搜索各对象后对比"
|
|
603
|
+
routing["mode"] = "auto"
|
|
604
|
+
|
|
605
|
+
elif strategy == "news_priority":
|
|
606
|
+
routing["engines"] = ["byted", "uapi", "duckduckgo"]
|
|
607
|
+
routing["explanation"] = "检测到新闻/动态意图,优先使用新闻引擎"
|
|
608
|
+
routing["mode"] = "fast"
|
|
609
|
+
|
|
610
|
+
elif strategy == "direct_search":
|
|
611
|
+
routing["engines"] = ["auto"]
|
|
612
|
+
routing["explanation"] = "事实查询,直接搜索即可"
|
|
613
|
+
routing["mode"] = "fast"
|
|
614
|
+
|
|
615
|
+
else:
|
|
616
|
+
# 根据实体和意图推荐引擎
|
|
617
|
+
entities = [e["type"] for e in analysis["entities"]]
|
|
618
|
+
intents = [i["type"] for i in analysis["intents"]]
|
|
619
|
+
|
|
620
|
+
if "stock_code" in entities or "finance" in [a.get("domain") for a in analysis.get("ambiguities", [])]:
|
|
621
|
+
routing["engines"] = ["eastmoney", "anysearch"]
|
|
622
|
+
routing["explanation"] = "检测到金融实体,优先使用金融引擎"
|
|
623
|
+
elif "cve" in entities:
|
|
624
|
+
routing["engines"] = ["anysearch"]
|
|
625
|
+
routing["explanation"] = "检测到 CVE 编号,使用安全垂直域"
|
|
626
|
+
elif "search_tech" in intents:
|
|
627
|
+
routing["engines"] = ["github", "duckduckgo", "uapi"]
|
|
628
|
+
routing["explanation"] = "检测到技术意图,优先技术源"
|
|
629
|
+
elif "search_opinion" in intents:
|
|
630
|
+
routing["engines"] = ["zhihu", "byted"]
|
|
631
|
+
routing["explanation"] = "检测到观点/评价意图,优先知乎"
|
|
632
|
+
else:
|
|
633
|
+
routing["engines"] = ["auto"]
|
|
634
|
+
routing["explanation"] = "无特殊意图,使用默认路由"
|
|
635
|
+
|
|
636
|
+
return routing
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
def main():
|
|
642
|
+
parser = argparse.ArgumentParser(description="意图消歧工具")
|
|
643
|
+
parser.add_argument("query", help="搜索查询")
|
|
644
|
+
parser.add_argument("--explain", action="store_true", help="详细解释")
|
|
645
|
+
parser.add_argument("--json", action="store_true", help="JSON 输出")
|
|
646
|
+
args = parser.parse_args()
|
|
647
|
+
|
|
648
|
+
analysis = analyze_query(args.query)
|
|
649
|
+
routing = recommend_routing(analysis)
|
|
650
|
+
analysis["routing"] = routing
|
|
651
|
+
|
|
652
|
+
if args.json:
|
|
653
|
+
print(json.dumps(analysis, ensure_ascii=False, indent=2))
|
|
654
|
+
else:
|
|
655
|
+
print(f"\n查询分析:{analysis['query']}")
|
|
656
|
+
print(f"语言:{analysis['language']} | 置信度:{analysis['confidence']:.2f}")
|
|
657
|
+
print(f"推荐策略:{routing['strategy']}")
|
|
658
|
+
print(f"说明:{routing['explanation']}")
|
|
659
|
+
print()
|
|
660
|
+
|
|
661
|
+
if analysis["ambiguities"]:
|
|
662
|
+
print("── 歧义检测 ──")
|
|
663
|
+
for a in analysis["ambiguities"]:
|
|
664
|
+
print(f" 「{a['term']}」→ {a['confidence']:.0%} 置信度")
|
|
665
|
+
for m in a["possible_meanings"]:
|
|
666
|
+
marker = "→" if m == a["top_choice"] else " "
|
|
667
|
+
print(f" {marker} {m}")
|
|
668
|
+
print()
|
|
669
|
+
|
|
670
|
+
if analysis["intents"]:
|
|
671
|
+
print("── 意图分类 ──")
|
|
672
|
+
for i in analysis["intents"]:
|
|
673
|
+
print(f" • {i['label']}")
|
|
674
|
+
print()
|
|
675
|
+
|
|
676
|
+
if analysis["entities"]:
|
|
677
|
+
print("── 实体识别 ──")
|
|
678
|
+
for e in analysis["entities"]:
|
|
679
|
+
print(f" • {e['text']} ({e['type']})")
|
|
680
|
+
print()
|
|
681
|
+
|
|
682
|
+
if routing["engines"] and routing["engines"] != ["auto"]:
|
|
683
|
+
print(f"── 推荐引擎 ──")
|
|
684
|
+
print(f" {', '.join(routing['engines'])}")
|
|
685
|
+
print()
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
if __name__ == "__main__":
|
|
689
|
+
main()
|