Files
2026-moving-helper/docs/design/step-3-ai-search.md
T
tliu93 d36b940981
test / pytest (push) Successful in 1m13s
Add LLM settings integration
Add app_settings migration, settings UI, and OpenAI-compatible httpx LLM client with mocked tests.

Preserve API keys on blank form submissions, require a fresh key when base_url changes, and keep AI search settings untouched for step 3.

Update docs/design LLM integration and step 3 AI search notes, including prompt contract and extra-hints planning.
2026-06-01 20:06:22 +02:00

8.4 KiB
Raw Blame History

步骤 3 · 基础 AI 搜索 / Step 3 · Basic AI Search

可独立执行 / Self-contained. 完整背景见设计文档 llm-integration-design.md §5;跨步骤约定见 implementation-plan.md前置 / Prerequisite 步骤 2 已合入(app/llm.py::expand_queryapp_settings 配置、ai_search_enabled 开关均已就绪)。Step 2 merged. 产出 / Output 一个可独立合入的 PR不改 schema


目标 / Goal

在搜索页提供一个常驻的「AI 智能搜索」动作:点击后用查询词扩展增强搜索结果。不以"零结果"为前提——即便普通搜索已出结果,用户不满意时也能用。 A persistent "AI search" action on the search page that broadens results via query-term expansion. Not gated on zero results — usable even when normal results exist.


必要背景 / Essential Context

  • 现有搜索:app/main.py::_build_search_results(db, query)Box/Item/SubItemnamenote 做大小写不敏感 LIKE,返回结果列表;路由 GET /search(函数 search_page,参数 q)渲染 app/templates/search/index.html。 Existing search: _build_search_results(db, query) does case-insensitive LIKE over name/note; route GET /search renders search/index.html.
  • 步骤 2 已提供:app/llm.py::expand_query(cfg, query) -> list[str]、配置读取 get_app_settings(db)、开关 ai_search_enabledis_configured(cfg)、设置页 app/templates/settings/form.html
  • 本步新增配置项 ai_search_extra_hints(可选「额外领域提示」)并在设置页加一个多行输入——这是本步唯一触及设置页之处。 This step adds the ai_search_extra_hints setting + a textarea on the settings page (the only settings-page change here).
  • 本轮检索范围=name + noteimage_description 本轮不存在,属未来图片分析轮次)。 Search scope = name + note (no image_description this round).

关键决策 / Key Decisions

  • 常驻、不依赖零结果。 普通 LIKE 照常先出结果;AI 动作始终可用(开启且已配置时)。 Persistent and not gated on zero results.
  • 流程: 触发 AI → expand_query 得到"原词 + 一批近义/相关词" → 用这组词对 name/note 做 OR LIKE 重搜 → 展示,并用横幅标注「AI 帮你扩展了:…」。只把查询词发出去,不外泄物品清单。 Trigger → expand → OR-LIKE over the original + expanded terms → render with a banner of the expansion. Only the query leaves.
  • 可替换的检索 seam。 把 AI 检索抽成一个函数(如 ai_search(db, query) -> (expanded_terms, results)),本轮内部=查询词扩展 + 本地 LIKE未来换成向量嵌入 + 相似度时,路由与模板不变。 Wrap AI retrieval behind a swappable seam so embeddings can replace it later without touching route/template.
  • 提示词(决策 C,详见设计 §5.2)。 基础系统提示词写死在 app/llm.py;设置页可选的 ai_search_extra_hints 非空时追加到其后;输出契约由代码强制(要求 JSON 字符串数组 → 容忍性解析 → 失败返回 []),用户改 hints 也改不坏解析。 Base prompt hardcoded; optional extra hints appended; output contract (JSON array → tolerant parse → []) enforced in code.
  • 优雅降级。 AI 关闭/未配置 → 不显示按钮(或提示去 /settings);调用失败 → 友好提示 + 回退普通结果。

任务 / Tasks

  • 落地/校准 expand_query 的提示词(按设计 §5.2
    • 基础系统提示词写死在 app/llm.py(搬家/家居场景、列相关命名词、跟随查询语言、≤ ~8 个、不解释、不造无关词)。默认提示词起点(可迭代 / a starting point, tune during testing):

      你是搬家物品搜索助手。用户在搜索自己打包的箱子与物品(家居/搬家场景)。给定一个搜索词,列出用户可能用来命名同一类物品的相关词:近义词、常见别称、上位类别、具体品类。规则:用与查询相同的语言;只给与该物品紧密相关、有助于在清单里找到它的词;不要解释、不要造无关词;最多 8 个;只输出一个 JSON 字符串数组,例如 ["炒锅","平底锅","汤锅","厨具"]

    • 读取 ai_search_extra_hints,非空则追加到基础提示词之后(只补充,不改格式)。
    • 输出契约:要求模型只回 JSON 字符串数组;解析去 ```json 围栏 → json.loads → 失败按行/逗号兜底 → 再不行返回 [];任何异常/超时都返回 [](不抛错)。
  • 新增配置项 ai_search_extra_hintsKV 默认空;纳入 get_app_settings / save_app_settings;设置页 app/templates/settings/form.html 加一个多行输入(沿用 step 2 风格)。
  • 实现检索 seam:在 app/main.py(或抽一个小搜索模块 app/search.py)加 ai_search(db, query) -> (expanded_terms, results)
    • expand_query(cfg, query) 得到扩展词;
    • 用「原词 + 扩展词」对 name/note 做 OR LIKE复用现有 _build_search_results 的匹配逻辑,避免重复实现),去重。
    • 注意:现有 _build_search_results(db, query) 只接收单个查询词;建议把它泛化为接收一组关键词(对多个词做 OR),让 AI 搜索与普通搜索共用同一套匹配逻辑,避免分叉。 Note: _build_search_results currently takes a single query — generalize it to accept multiple keywords so AI and normal search share one matching path.
  • 扩展 GET /search:支持 ai=1 触发位(如 GET /search?q=锅&ai=1),保持单页、可收藏、SSR 友好。
    • ai=1 且 AI 开启且 is_configured() → 走 ai_search,把 expanded_terms 传给模板做横幅。
    • 否则走原有普通搜索。
  • 模板 app/templates/search/index.html
    • 常驻「AI 智能搜索」按钮,链接到 ?q=<当前词>&ai=1
    • AI 关闭/未配置时隐藏按钮(或显示去 /settings 的提示);
    • ai=1 结果页顶部显示横幅「AI 帮你扩展了:term1、term2…」。
  • 降级:ai_search 内部调用失败时捕获,渲染友好提示并回退到普通 LIKE 结果。
  • 测试(mock expand_queryCI 不联网):
    • 扩展词驱动命中:原词 LIKE 搜不到、扩展后能搜到。
    • 已有结果时点 AI 仍可用,且结果集被扩大(含原结果)。
    • 按钮可见性随 ai_search_enabled + is_configured() 门控。
    • 调用失败 → 回退普通结果、页面不报错。
    • expand_query 输出解析:模型回合法 JSON 数组 → 正确解析;回散文/坏 JSON/超时 → 返回 []、不抛错。 Output parsing: valid JSON array → parsed; prose/bad JSON/timeout → [], no raise.
    • ai_search_extra_hints 非空时确被追加进请求(可对构造的请求体断言)。 Extra hints, when set, are appended to the request.

涉及文件 / Files

app/llm.pyapp/main.py、(可选 app/search.py)、app/templates/search/index.htmlapp/templates/settings/form.html、配置读写 helperstep 2 的 settings store)、tests/


验收 / Acceptance

  • 搜索页在 AI 开启时始终可见「AI 智能搜索」;点击后结果按扩展词扩大,并标注扩展词。
  • 未配置/失败时优雅降级,普通搜索完全不受影响。
  • 检索逻辑收敛在 ai_search seam,未来可整体替换为向量语义搜索而不动路由/模板。

风险与缓解 / Risks & Mitigations

  • 扩展词过多/过散 → 结果噪声大。 缓解:限制扩展词数量;横幅透明展示扩展词,让用户理解结果来源。 Too many/too-loose terms → cap the expansion count and show it transparently.
  • AI 调用慢/失败拖累搜索页。 缓解:仅在 ai=1 时才调用(普通搜索零开销);设超时;失败回退。 Slow/failed calls → only call on ai=1, set a timeout, fall back.

相关约定 / Conventions(详见 implementation-plan.md

  • 不主动 push/commit,除非业主要求。
  • CI 不联网(mock expand_query)。
  • 实现与设计若有偏差 → 回写设计文档 §5 与仓库简报 §15。