2026-06-18-query-builder-design.md 14 KB

Query 规则组织器 + 正交评估高亮 — 设计

日期:2026-06-18 所属:examples/mode_workflow

目标

在工作台「新建搜索」旁增加「Query 规则」入口,打开一个弹层:用内容树各维度的词 正交生成「动作 × 类型」候选 query 矩阵,用 Sonnet 评估挑出有意义、人话、有助于 内容制作知识库目的的组合并在表中高亮,点高亮格即可直接发起搜索。

把"凭直觉拍 query"变成"按内容树系统性铺词 → LLM 筛 → 一键搜 → 标签自动入库 → 分 tab 解构" 的端到端闭环(搜索/入库/解构见 Part B/C)。

背景与复用

  • 表的骨架直接复用 reference/judged_matrix.json:27 动作(列,按 获取/提取/生成/修改 → l1 → 叶子分组)× 50 类型(行,按 程序控制/数据复用/内容/知识 → l1 → 叶子分组), 每格 {tier:0-3, r:理由, q:[示例query]}。1350 格中 643 个 tier≥1(有效),707 个 tier0。 server.py 已加载它(MATRIX_FILE,Dashboard 覆盖度在用)。
  • flat 维度词表 工具类型/模态/后缀 移植自参考稿 ~/Downloads/query-builder.htmlDIMS 常量,内嵌进 index.html
  • 实质/形式 维度数据来自外部接口(深层分类树,不写死)GET https://library.aiddit.com/api/pattern/executions/{exec_id}/category-tree?source_type=实质|形式 (exec_id 默认 401;返回 {tree:[{category_id,name,classified_as,total_element_count,children:[...]}]}, 6+ 层)。server 端直连真实域名拉取并缓存,前端逐级下钻选节点,选中节点 + 祖先路径作为 Sonnet 领域上下文。注:用户给的 /lib-proxy 是前端 dev vite 代理(rewrite 去前缀),后端不走它。
  • 搜索发起复用既有 POST /api/run_search → spawn stages/search_eval.py{task_id, query_id}; Part B 后搜索方向无关(落表由评估标签决定),默认 platforms=xhs,gzhmax_count=20
  • LLM 调用复用 agent.llm.openrouter.create_openrouter_llm_call(model=...)(同 procedure_extract)。
  • 子进程 + 轮询复用 server.py:_spawn_task / _task_status

四项关键决策(已与用户确认)

  1. 表骨架 = judged_matrix(动作×类型,tier 决定底色,附示例 q)。
  2. 格子词串 = [工具类型] 动作叶 类型叶 [模态] [后缀],工具类型/模态/后缀 取 chips 单选值统一拼入; 实质/形式不进表、不拼词,仅把选中的实质/形式节点(含祖先路径,数据来自上述外部接口) 作为评分上下文供 Sonnet 理解领域。
  3. 评分范围 = 只评 643 个 tier≥1 格 × 当前 chips 上下文,分批交 Sonnet。
  4. 高亮后用途 = 达标格(keep=true)可点单格「搜全部达标」批量发起搜索;渠道默认 xhs,gzh 各 20 帖、搜索方向无关(落表由 Part B 评估标签决定)。

架构与组件

[新建搜索] [Query规则]                         ← index.html 顶栏新增按钮
                │
                ▼ 全屏 modal(复用 .modal-bg 模式)
┌─ 上半:维度 chips(移植 query-builder DIMS 渲染) ───────────────┐
│   工具类型/模态(flat,单选)·后缀 · 动作/类型/实质/形式(逐级下钻)   │
│   [生成正交表 & 评估高亮]  按钮                                  │
├─ 下半:动作×类型 正交表(27列×50行,l1/l2 分组,sticky 表头)──────┤
│   · 每格 query = [工具类型] 动作叶 类型叶 [模态] [后缀]          │
│   · tier 底色(0灰/1浅/2中/3深);tier0 格置灰不可选               │
│   · Sonnet keep=true 的格 → 高亮描边 + 角标综合分                │
│   · 点达标格 / 「搜全部达标」→ /api/run_search(xhs+gzh×20,方向无关)│
└────────────────────────────────────────────────────────────────┘

前端(index.html,单文件)

  • 顶栏按钮:在「+ 新建搜索」旁加「Query 规则」,onclick 打开 #query-modal
  • 维度 chips 区:工具类型/模态/后缀 移植 DIMS 常量(flat 单选);动作/类型 三级下钻; 实质/形式 从 GET /api/category_tree?source_type=实质|形式 拉树做任意深度逐级下钻renderDim 推广到 N 级),选中节点记录其 name 与祖先路径数组。
  • 正交表区:拉 GET /api/query_matrix 拿 actions/types/matrix;按 l1/l2 分组渲染表头 与行分组(参考 Image #2)。每格文本 = 拼词结果;底色按 tier;tier0 置灰。
  • 评估:点「生成正交表 & 评估高亮」→ POST /api/query_score(带当前选择)→ 轮询 /api/task_status → 成功后 GET /api/query_score?sel=<hash> 取每格 verdict → 给 keep=true 的格加高亮 class + 角标分数。
  • 发起搜索:点单个达标格 → 确认弹框(query=rewrite 或原串)→ POST /api/run_search; 另有「搜全部达标」按钮,对所有 keep=true 格循环发起。渠道默认 xhs,gzh 各 20、方向无关。

后端(server.py 新增)

接口 方法 作用
/api/query_matrix GET 返回 judged_matrix.json(actions/types/matrix);带 ETag/304
/api/category_tree GET ?source_type=实质\|形式 → 直连 + 缓存 library.aiddit.com 分类树;带 ETag
/api/query_score POST body=当前选择 → 命中缓存直接返回结果;否则 _spawn_taskstages/query_score.py,返回 {task_id, sel}
/api/query_score GET ?sel=<hash>.cache/query_score/<hash>.json 返回结果(未就绪 404/202)

POST /api/query_score body:

{"tool_type":"AI|无","modality":"图片|无","suffix":"怎么做|无",
 "substance_path":["表象","实体","物品"],"form_path":["呈现","视觉"],
 "model":"anthropic/claude-sonnet-4-6"}

评分脚本(新增 stages/query_score.py

职责单一:输入选择上下文 → 输出每格 verdict JSON。

  • reference/judged_matrix.json,筛 tier≥1 格,对每格拼 query 串。
  • 异步并发分批(asyncio + Semaphore,默认并发 5、每批 ~40 格)调 create_openrouter_llm_call(model),用下方 Prompt 打分。
  • 结果写 .cache/query_score/<sel_hash>.json

    {"sel": {...}, "model": "...", "matrix_sig": "<judged_matrix 指纹>",
    "cells": {"<action_idx>_<type_idx>": {"query":"...","natural":8,"findable":7,
             "useful":9,"keep":true,"rewrite":"...","reason":"...","score":8.0}}}
    
    • sel_hash = 稳定哈希(选择 + model + matrix_sig),同选择不重复付费 (承袭项目一贯的去重省钱思路)。--force 可绕过缓存重评。
    • 命令行可独立跑:python stages/query_score.py --tool-type AI --modality 图片 --suffix 怎么做 ...

    路径锚点同其它 stages/ 脚本(parents[3]=Agent,parent.parent=mode_workflow,import db 非必需)。

    评分 Prompt(核心交付)

    给 Sonnet 的 system + 每批 user。判据:当前 chips 上下文下,每个 tier≥1 的 动作叶×类型叶 拼出的 query 是否「人话、真实创作者会搜、公共域能搜到内容制作 how-to/工具」。

    ```

你是「内容制作知识库」的检索词评审。我们要从小红书/公众号等公共平台搜到 "怎么做某类内容"的工序/工具教程。现给定一批由维度正交生成的候选 query。

【固定上下文(本批所有候选共享)】 工具类型: {tool_type|无} 模态: {modality|无} 后缀: {suffix|无} (实质/形式不参与拼词,仅供你理解领域定位: 实质路径={substance_path 如 表象›实体›物品} 形式路径={form_path 如 呈现›视觉})

【候选列表】每条 = 动作 + 类型 + 上下文词拼成的 query: {idx}. "{query}" (动作={action} 类型={type} 内容树tier={tier})

【逐条打分,只看这条 query 本身】

  • natural(0-10): 像不像真人会在搜索框打的人话(越口语、越具体越高;生造词/学术腔低)
  • findable(0-10): 公共域是否真有对口的"怎么做"内容能被搜到(参考 tier,但以 query 实际可搜性为准)
  • useful(0-10): 命中的内容对"内容制作知识库"是否有价值(教程/方法/工具 高;成品展示/抽象概念 低)
  • keep(bool): 三者均衡后是否值得真的拿去搜(有意义=true)
  • rewrite: 若 keep 但措辞生硬,给一个更像人话的等价搜索词(否则原样)
  • reason: 一句话理由(≤30字)

只输出 JSON 数组: [{"idx":1,"natural":8,"findable":7,"useful":9,"keep":true, "rewrite":"...","reason":"..."}]


- 综合分 `score` = 加权(默认 `natural*0.4 + findable*0.3 + useful*0.3`),角标显示。
- 高亮:`keep=true` 描边 + 角标分数;点击用 `rewrite`(空则原串)发起搜索。
- Prompt 文件放 `prompts/query_score_system.md`,可单独迭代(同其它 prompt 约定)。

## 数据流

开弹层 → /api/query_matrix(表) + /api/category_tree(实质/形式树) → 选维度 → [评估高亮] → POST /api/query_score → (缓存命中? 直接返回 : spawn stages/query_score.py) → 轮询 /api/task_status → GET /api/query_score?sel=hash → 每格 verdict → 高亮 keep 格 → 点单格/搜全部达标 → POST /api/run_search(xhs+gzh×20,方向无关) → stages/search_eval.py 搜一次 + 统一评估一次 → 按 知识类型 路由写 search_process/search_tools → Dataset 工序/工具 tab 各自「解构全部已采纳」→ procedure_extract / tool_extract ```

Part B:搜索 → 统一评估 → 按标签路由双表(req#1 + #2)

已确认事实:评估是一套统一逻辑,输出 知识类型 ⊆ {工序,能力,工具} + 单一 overall_scorellm_evaluate_sources.py:55/264db.py:overall_score),不分方向。 故"搜一次、评一次、按标签落表",零重复打分。

触发(req#1)

  • query-builder 达标格(keep=true)即可发起搜索,两种方式(用户要"都要"):
    • 点单格 → 一个搜索任务。
    • 「搜全部达标」 → 对所有 keep=true 格循环各起一个搜索任务。
  • 渠道默认 xhs,gzh--max-count 20(每渠道 20)。搜索方向无关,落表由标签决定。

统一评估 + 标签路由(req#2)

  • stages/search_eval.py 改造:搜一次 → 评估一次(产出 知识类型 + overall_score)→ 路由:
    • 知识类型 ∩ {工序, 能力} ≠ ∅ → upsert search_process
    • 知识类型 ∩ {工具} ≠ ∅ → upsert search_tools
    • 两者都含 → 两表都写,同一 llm_evaluation blob(零重复打分)
    • 知识类型 为空(兜底)→ search_process
  • 评估去重跨表db.fetch_existing_eval 改为"任一表评过即复用"(eval blob 与表无关), 避免同帖在两表各评一次。成本:每帖最多评一次,与落表数无关。
  • --mode-type 退化为兼容参数,不再决定路由(路由全自动)。
  • runs/ JSON 保留全量(含能力-only 帖)。

Part C:tab 已采纳 → 对应解构(req#3)

  • UI 已有工序/工具模式切换(#m-process / #m-tools)。各模式下加「解构全部已采纳」按钮:
    • 工序模式 → POST /api/extract_process(该 query 下 search_process 全部已采纳 case)→ procedure_extract
    • 工具模式 → POST /api/extract_toolssearch_tools 全部已采纳 case)→ tool_extract
  • 复用已支持的"--query-id = 全部已采纳"脚本能力;server.py/api/extract_* 放宽 case_ids 为可选:缺省时服务端用 db.fetch_posts(query_id, 方向)adopted 过滤 取全部已采纳 case。解构去重 / 认领锁 / (query,case,version,seq) 唯一键(前序已做)继续生效。

错误处理

  • LLM 某批失败/超时:该批格标记 error(不高亮、可重试),不阻断其它批;脚本退出码非 0 时 前端提示"部分批次失败,可重评"。
  • 评估无标签 / 知识类型解析失败:兜底落 search_process,不丢帖(Part B)。
  • 「搜全部达标」批量:逐格起任务、各自轮询,单格失败不阻断其余。
  • judged_matrix 缺失/解析失败:/api/query_matrix 返回 500,弹层提示。
  • 实质/形式 接口不可达:弹层提示该维度暂不可选,评分照常进行(上下文路径缺省为空),不阻断。
  • 缓存文件半写:写临时文件再原子 rename,避免读到半截。
  • 点格搜索沿用 run_search 既有校验(缺 query 报错)。

测试

  • stages/query_score.py --dry-run:只拼词 + 打印将评的格数/示例,不调 LLM(验证筛选与拼词)。
  • 小范围真评:--limit 20 只评前 20 格,核对 JSON 结构与缓存落盘。
  • 前端:手测弹层渲染、tier 底色、轮询、高亮、点格确认与发起搜索。
  • 缓存:同选择二次点击应秒回(命中缓存,无 LLM 调用)。
  • Part B:构造/挑一帖含 工序+工具 双标签 → 应在两表各 1 行、llm_evaluation 一致, 且评估成本日志显示只评一次(跨表去重生效)。
  • Part C:某 tab「解构全部已采纳」→ 只解构该方向已采纳 case;重复点应被去重/认领锁/唯一键拦住。

范围与非目标(YAGNI)

  • 「搜全部达标」先朴素循环逐格起任务,暂不做并发上限/限流(量大再加节流)。
  • 不把评分结果入 MySQL(落 .cache/ 文件即可,按选择哈希复用)。
  • 不做维度词表的在线编辑(flat 词表内嵌 index.html,改词改常量)。
  • 实质/形式不进正交、不进拼词,仅作评分上下文(数据来自外部接口,逐级下钻选节点)。

涉及文件

  • 改:index.html(按钮+弹层+表+交互、「搜全部达标」、各 tab「解构全部已采纳」)、 server.py(query_matrix/category_tree/query_score 接口、runsearch 默认渠道、extract* 放宽 case_ids)、 stages/search_eval.py(去 mode_type 路由、统一评估、按标签双表路由、跨表去重)、 db.pyfetch_existing_eval 跨表、双表 upsert 复用)、README.md/流程执行手册.md
  • 增:stages/query_score.pyprompts/query_score_system.md
  • 只读复用:reference/judged_matrix.jsonagent.llm.openrouterstages/{procedure,tool}_extract.py
  • 外部依赖:library.aiddit.com category-tree(server 端直连 + .cache/category_tree/ 缓存)。