Files
2026-moving-helper/docs/design/step-2-llm-integration.md
T
tliu93 c42cc2ddb6 docs: add LLM integration design and three-step implementation plan
Add docs/: a bilingual repository brief, plus docs/design/ with the high-level design (Alembic migration foundation, LLM integration, basic AI search) and a self-contained per-step implementation plan (step 1-3).
2026-06-01 13:10:59 +02:00

6.2 KiB
Raw Blame History

步骤 2 · LLM 接入 / Step 2 · LLM Integration

可独立执行 / Self-contained. 完整背景见设计文档 llm-integration-design.md §4;跨步骤约定见 implementation-plan.md前置 / Prerequisite 步骤 1 已合入(Alembic 已就位——schema 变更一律通过新建迁移完成)。Step 1 merged; Alembic is in place — all schema changes go through a new migration. 产出 / Output 一个可独立合入的 PR。


目标 / Goal

提供一个配置页:能填写并测试 OpenAI 兼容的 base_url/model/api_key,配置落库到 app_settings;并提供一个可复用、可 mock 的 LLM 客户端。未配置时整站行为不变。 A settings page to enter & test the LLM config, persisted to app_settings, plus a reusable, mockable LLM client. App behavior is unchanged when unconfigured.


必要背景 / Essential Context

  • 路由全部在 app/main.py::create_app();模板在 app/templates/,基础模板 base.html 顶部有导航(现有「箱子」「搜索」两个链接)。 All routes live in create_app(); templates under app/templates/; nav lives in base.html.
  • DB 会话依赖:Depends(get_db)app/db.py)。models 在 app/models.pyBaseapp/db.py
  • 同步 handler 即可FastAPI 把同步 def 路由丢线程池执行,阻塞式 httpx 调用可接受。 Sync handlers are fine — FastAPI runs them in a threadpool, so blocking httpx is acceptable.
  • httpx 已在 requirements.txt不要新增依赖(不引入 openai SDK)。 httpx is already a dependency; add no new deps.

关键决策 / Key Decisions

  • 配置存储用键值表,不是定型列:app_settings(key TEXT PRIMARY KEY, value TEXT)。原因:后续配置项会变多,KV 加项=加一行、永不迁移;类型/校验在 Python 侧。 KV table, not typed columns — future settings = new rows, never a migration.
  • API Key 明文落库(业主在其威胁模型下的明确选择),但配置页绝不回显明文:显示「已配置,留空=不修改」,提交留空则保留原值。 Plaintext key in DB (owner's explicit choice), but the UI never echoes it — show "configured, leave blank to keep".
  • 优雅降级llm_enabled 关或缺 model/api_key 时,is_configured() 为假;调用失败不抛 500,返回可识别的失败信号。 Graceful degradation throughout.

本轮使用的 key / Keys this round

key 含义 / Meaning 默认 / Default
llm_enabled LLM 总开关 / master toggle false
llm_base_url OpenAI 兼容端点 / endpoint https://api.openai.com/v1
llm_model 模型名 / model name (空 / empty
llm_api_key API Key(明文 / plaintext (空 / empty
ai_search_enabled AI 搜索功能开关(步骤 3 用)/ AI-search toggle false

任务 / Tasks

  • 新建 V2 迁移(用 Alembic,遵循步骤 1 的工作流):创建 app_settings(key TEXT PRIMARY KEY, value TEXT)。 New V2 Alembic migration creating app_settings.
  • app/models.py:新增 AppSetting 模型(映射 app_settings)。
  • 配置读写 helper(建议放 app/settings_store.pyapp/config.py 旁):
    • get_app_settings(db) -> LLMConfigdataclassenabled/base_url/model/api_key/ai_search_enabled,含默认值)。
    • save_app_settings(db, ...):写回 KV;Key 留空则不覆盖原值。
  • 新增 app/llm.py(基于 httpx):
    • is_configured(cfg) -> bool
    • test_connection(cfg) -> Result(发最小请求验证 base_url/model/api_key)。
    • expand_query(cfg, query) -> list[str](查询词扩展;步骤 3 会用,本步先落地+单测)。
    • 统一超时 + 错误处理;失败优雅降级。
    • (预留,不实现) analyze_image(...):仅留 TODO/签名占位 + 注释指向"未来图片分析轮次"。Reserved, not implemented.
    • 把所有网络调用收敛到单一函数边界,便于测试整体 mock。
  • 路由(app/main.py):
    • GET /settings:渲染配置表单(Key 脱敏)。
    • POST /settings:保存到 app_settings(303 重定向,沿用现有 POST 风格)。
    • POST /settings/test:用当前/待保存配置测试连接,回显结果。
  • 模板:app/templates/settings/form.html(沿用现有卡片/表单样式);base.html 导航加「设置」入口。
  • 测试(LLM 全程 mock,CI 不联网):
    • 保存/读取配置;Key 脱敏(响应 HTML 不含明文;提交留空不覆盖原 Key)。
    • POST /settings/test 成功/失败两条分支(mock test_connection 或底层 httpx)。
    • 未配置时 is_configured() 为假;配置页在 llm_enabled=false 下仍可正常打开保存。

涉及文件 / Files

migrations/versions/**(V2)、app/models.pyapp/llm.py(新)、app/settings_store.py(新,或并入既有模块)、app/main.pyapp/templates/settings/form.html(新)、app/templates/base.htmltests/


验收 / Acceptance

  • /settings 填入配置 → 保存 → 重启应用后仍在(已落库)。Config persists across restarts.
  • 「测试连接」对真实 OpenAI 端点可用(手动验证);自动化测试中走 mock。
  • 配置页 HTML 不含明文 Key;留空提交保留原值。
  • llm_enabled=false 或缺 Key 时,全站行为与步骤 1 后一致(无回归)。

风险与缓解 / Risks & Mitigations

  • 把网络调用散落各处 → 难 mock、CI 易联网。 缓解:所有外呼集中在 app/llm.py 单一边界。 Scattered network calls → keep all egress in app/llm.py.
  • Key 不慎回显。 缓解:模板永不输出 api_key 值,仅输出"是否已配置"。 Accidental key echo → template never prints the key value.

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

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