- Python 68.4%
- TypeScript 30.7%
- HTML 0.7%
- Shell 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| artifacts | ||
| backend | ||
| docs | ||
| frontend | ||
| .env.example | ||
| .gitignore | ||
| config.yaml | ||
| pyproject.toml | ||
| README.md | ||
| run.sh | ||
| uv.lock | ||
寻知 · AI-MetaSearch
供个人使用的 React + Python/FastAPI 聚合搜索工作台。首版通过搜索引擎网页 URL 直接请求,不依赖搜索 API,也不运行浏览器自动化。
快速运行
需要 Python 3.11+、uv 和 Node.js 20.19+ 或 22.12+(推荐当前 LTS)。
git clone https://git.cloverdo.cn/Cloverdo/Layla_MS.git
cd Layla_MS
uv sync --extra test
cd frontend
npm ci
npm run build
cd ..
./run.sh
浏览器打开 http://127.0.0.1:8000。React 构建产物由 FastAPI 同源提供,不需要另外运行前端服务。项目路径可以移动,配置与数据路径相对于项目根目录解析。
开发时分两个终端运行:
# 终端一:后端
uv run uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000 --reload --no-access-log
# 终端二:前端,/api 自动代理到后端
cd frontend
npm run dev
前端开发地址为 http://127.0.0.1:5173。生产构建前修改前端后,需要重新执行 npm run build。
已实现
- Google、Bing 两个独立网页抓手;UTF-8 查询编码、HTML 解析、Bing 跳转链接本地解码,保留原始链接。
- 并发原始搜索、有限请求预算、单源与全局超时、失败隔离;429 尊重 Retry-After,遇到验证或拒绝访问进入暂停期。
- 按标题与 URL 精确组合去重,共用稳定 Document UUID;各引擎、查询变体与排名独立存为 Observation。
- 标准 RRF 基础融合,扩展查询权重与同引擎最高贡献控制;排名软调整规则接口预留,首版仅有恒等规则,不默认硬性分组。
- SQLite 历史、结果及证据快照、基础缓存、实时刷新、可配置保留策略和每分钟到期清理。
- 可选 AI 查询扩展、整体总结与单条摘要;SSE 先返回基础结果,后补 AI;结构与引用 UUID 校验。
- React 搜索页面、来源开关、AI 开关、10/15 条结果、中文/英文偏好、域名排除、来源筛选、排名明细、历史快照和 JSON 导出。
- 默认本机访问;可配置访问令牌;密钥不返回前端。
真实访问边界
2026-10-24 本环境的直接请求探测:Bing 返回可解析的自然搜索结果;Google 返回要求 JavaScript 验证的页面。两者的实际行为可能随网络、地区和时间变化。
Google 抓手能够解析常见服务端 HTML 结果,但不会执行挑战脚本、处理验证码或绕过访问限制。UI 会明确显示 需要 JavaScript、需要验证 或 暂时暂停 等状态,并保留原搜索页入口;Bing 等其他源继续搜索。请求失败不是“没有搜索结果”。
不自动切换付费 API,不伪造 AI 输出或搜索数据。没有配置模型时,普通搜索、排序、历史仍可运行。接入真实模型后的质量和延迟需要用你自己的服务验证。
配置 AI
复制 .env.example 为 .env,填写你自己的兼容 OpenAI Chat Completions 的服务:
METASEARCH_AI_BASE_URL=https://your-provider.example/v1
METASEARCH_AI_API_KEY=your-key
METASEARCH_AI_MODEL=your-model
端点需要支持 response_format: {"type":"json_object"}。可通过 METASEARCH_EXPANSION_MODEL 和 METASEARCH_SUMMARY_MODEL 分别指定模型。修改 .env 后重启后端。API 密钥只在后端读取;.env 已忽略,不应提交或分享。
扩展与原始搜索并行;原文不被替换。扩展超时或无效时只使用原始查询。摘要证据只有本次真实标题与 snippet,并未读取原网页正文;合法引用只说明引用存在,不自动证明说法正确。
配置搜索、排序和保留策略
config.yaml 包含请求超时、总预算、候选深度、扩展数量、AI 超时、缓存 TTL、RRF k、查询变体权重、引擎权重、同分规则及各来源保留策略。配置在启动时校验,修改后重启。
标准原始查询融合为:
RRF(d) = Σ_engine 1 / (k + original_rank)
有扩展查询时,每个引擎只计一次最高的加权贡献:
score(d) = Σ_engine engine_weight × max_query(query_weight / (k + rank))
rank_rule: identity 表示不增加排名软惩罚。未注册规则和非法参数会启动失败,不静默改变算法。预留 RankingRules.register(name, version, modifier, validate),后续可加入连续软衰减,规则关闭后恢复基线。排序配置、版本、解析版本和模型版本参与缓存键,配置不同不会误用旧排序。
默认 k=10、扩展权重 0.4 都是可调整初值,未证明最优。标准 RRF 仍允许多个低排名共同命中压过单源第一名,这个首版行为与已确认方案一致。
来源保留策略:
persistent:长期保存该来源许可范围内的文本与命中证据。temporary:保存到retention_seconds到期,清理结果、摘要及相关快照。默认 24 小时是产品清理配置,不代表来源授予了缓存许可。none:不保存该来源的网页实体、结果文本、命中及任务记录。若一次搜索包含此类来源,整份混合结果与 AI 衍生快照不落盘、不缓存,仅保留用户自身查询与运行元数据,避免混合内容泄漏。
请按你获得的来源许可配置策略。任一临时来源到期后,整份混合快照失效,仍可保留的独立命中不受影响。缓存命中仅新增一次运行视图,不伪造新引擎请求或命中。历史页面保留最近 50 次查询入口,结果过期时可重新搜索。
数据库默认在 data/metasearch.sqlite3,首次启动自动建立 v1 结构、外键、组合唯一约束和 WAL。备份运行中的数据库时,请使用 SQLite 在线 backup API,避免只复制主文件遗漏 WAL。
UUID 在查询已有身份映射后首次分配,采用固定项目命名空间与 JSON 编码的 [title,url] 生成 UUIDv5。因此临时数据清理或重启后,再次出现精确组合仍获得相同 ID;不自动删除跟踪参数,不做标题相似度、URL alias 或正文合并。命名空间发布后不能随意修改。
插件扩展
backend/app/
layer1/query_analyzer.py # AI 有限查询扩展
layer2/orchestrator.py # 请求调度、汇总与缓存协调
layer2/ranker.py # 独立 RRF 与排名规则注册表
layer2/summarizer.py # AI 整体与单条摘要
layer3/base.py # 抓手协议、请求与错误契约
layer3/engines/ # Google/Bing 网页插件
layer3/registry.py # 显式插件注册
storage/repository.py # 身份、命中、运行与快照
services/ai_client.py # 可替换模型服务
main.py # HTTP/SSE 入口、生命周期与静态 UI
增加抓手时继承 SearchEngine,声明 engine_id、plugin_id、parser_version,实现 search_url、parse,必要时覆盖 search 以适配 API。然后注册插件,并在配置中声明对应来源。不要将引擎特定解析逻辑放进汇总层。
同一逻辑引擎的网页与 API 插件使用同一个 engine_id,贡献按引擎取最高值,不算两票;独立插件使用不同 plugin_id,保留实际请求溯源。
HTTP 接口
API 文档在 http://127.0.0.1:8000/docs。
| 接口 | 用途 |
|---|---|
POST /api/search |
完整 JSON 搜索响应,等待 AI 完成或降级 |
POST /api/search/stream |
SSE:progress、engine、results、summary、done;8 秒心跳 |
GET /api/config |
搜索源、模型配置状态和排序参数,不含密钥 |
GET /api/history |
查询历史,可按问题文本筛选 |
GET /api/history/{run_id} |
原运行快照或到期状态 |
GET /api/stats |
网页、命中、运行、有效缓存计数 |
GET /api/health |
健康检查 |
curl -X POST http://127.0.0.1:8000/api/search \
-H 'Content-Type: application/json' \
-d '{"query":"Python asyncio documentation","engines":["google","bing"],"limit":10,"ai_expand":false,"ai_summarize":false}'
如果设置了 METASEARCH_ACCESS_TOKEN,所有 /api/ 请求必须携带 X-API-Key。前端设置抽屉可以输入该访问令牌,只存于当前浏览器会话。默认只监听 loopback;如果另行暴露服务,请同时配置网络保护和访问令牌。
验证
uv run pytest -q
cd frontend
npm run build
自动化测试覆盖精确身份与并发插入、查询变体计票、RRF 数值和软规则扩展、缓存来源、到期清理、none 策略、HTML 解析、429 暂停、AI 降级与非法引用、HTTP/SSE、历史和访问控制。抓手解析测试使用内嵌 HTML,服务测试使用明确的 fixture,不访问外部网络、不冒充真实结果。
后续范围
MCP 包装、其他网页/API 抓手、历史全文检索、Embedding/Reranker、正文读取、相似度合并、排名软惩罚和 AI 离线复盘均未实现。当前没有自动动态调权。业务三层与注册扩展点已预留。