Jelajahi Sumber

Update creation prompts and query board filters

SamLee 2 minggu lalu
induk
melakukan
a7c8b91b60

+ 1 - 5
acquisition/queries/__init__.py

@@ -1,17 +1,13 @@
-"""Formal query generation and filtering package."""
+"""Formal query generation package."""
 
 from acquisition.queries.builder import (
     QueryBuildOptions,
     build_creation_query_batch,
     persist_query_batch,
 )
-from acquisition.queries.filter import VALID_MIN, filter_queries, prompt_version
 
 __all__ = [
     "QueryBuildOptions",
     "build_creation_query_batch",
     "persist_query_batch",
-    "VALID_MIN",
-    "filter_queries",
-    "prompt_version",
 ]

+ 2 - 16
acquisition/queries/builder.py

@@ -9,7 +9,6 @@ from pathlib import Path
 from typing import Any
 
 from acquisition.queries.axes import ACTIONS, STAGES, _nonleaf_d4
-from acquisition.queries.filter import filter_queries, prompt_version
 from acquisition.repositories.base import AcquisitionRepository
 from core.config import Settings
 
@@ -26,7 +25,6 @@ class QueryBuildOptions:
     per: int = 0
     batch_n: int = 0
     seed: int = 7
-    enable_query_filter: bool = False
     active_family_keys: tuple[str, ...] = DEFAULT_ACTIVE_FAMILY_KEYS
 
 
@@ -236,9 +234,7 @@ def build_creation_query_batch(
             "seed": opts.seed,
             "per": opts.per,
             "batch_n": opts.batch_n,
-            "query_filter_enabled": opts.enable_query_filter,
             "active_family_keys": list(opts.active_family_keys),
-            "query_filter_prompt_version": prompt_version(),
         },
         "families": [],
     }
@@ -262,13 +258,8 @@ def build_creation_query_batch(
             items.append({"query": query, "parts": parts})
             if opts.per > 0 and len(items) >= opts.per:
                 break
-        verdicts = (
-            filter_queries([it["query"] for it in items], settings)
-            if opts.enable_query_filter
-            else [{"keep": True, "valid": None, "relevant": True, "reason": ""} for _ in items]
-        )
-        for item, verdict in zip(items, verdicts):
-            item.update(verdict)
+        for item in items:
+            item.update({"keep": True, "reason": ""})
         out["families"].append(
             {"key": family["key"], "name": name, "axes": family["axes"], "items": items}
         )
@@ -310,11 +301,6 @@ def persist_query_batch(
                     "family_key": family.get("key"),
                     "family_name": family.get("name"),
                     "family_axes": family.get("axes") or [],
-                    "valid": item.get("valid"),
-                    "relevant": item.get("relevant"),
-                    "query_filter_prompt_version": (generated.get("metadata") or {}).get(
-                        "query_filter_prompt_version"
-                    ),
                 },
             )
             count += 1

+ 0 - 142
acquisition/queries/filter.py

@@ -1,142 +0,0 @@
-"""Formal query filter used by generated query batches."""
-from __future__ import annotations
-
-import hashlib
-import json
-import os
-from pathlib import Path
-
-import httpx
-
-from core.config import Settings, load_env_file
-from core.text_limits import (
-    ERROR_MESSAGE_MAX_CHARS,
-    QUERY_FILTER_REASON_MAX_CHARS,
-    clip_text,
-)
-
-PROMPT = Path(__file__).resolve().parent.parent / "query_filter.txt"
-VALID_MIN = 6
-
-
-def prompt_version() -> str:
-    h = hashlib.sha256()
-    h.update(PROMPT.read_bytes())
-    return h.hexdigest()[:16]
-
-
-def filter_queries(
-    queries: list[str],
-    settings: Settings,
-    *,
-    batch: int = 40,
-) -> list[dict]:
-    """Batch-filter query strings and keep output aligned with input order."""
-    out: list[dict] = []
-    for i in range(0, len(queries), batch):
-        out.extend(_filter_batch(queries[i:i + batch], settings))
-    return out
-
-
-def _filter_batch(queries: list[str], settings: Settings) -> list[dict]:
-    if not queries:
-        return []
-    user = json.dumps(
-        [{"idx": i, "query": q} for i, q in enumerate(queries)],
-        ensure_ascii=False,
-    )
-    messages = [
-        {"role": "system", "content": PROMPT.read_text("utf-8")},
-        {"role": "user", "content": user},
-    ]
-    try:
-        txt = _chat_content(settings, messages)
-        data = json.loads(txt)
-        arr = data if isinstance(data, list) else next(
-            (v for v in data.values() if isinstance(v, list)),
-            [],
-        )
-        by = {d.get("idx"): d for d in arr if isinstance(d, dict)}
-        out = []
-        for i in range(len(queries)):
-            d = by.get(i, {})
-            try:
-                valid = int(d.get("valid", 10))
-            except (TypeError, ValueError):
-                valid = 10
-            relevant = bool(d.get("relevant", True))
-            out.append(
-                {
-                    "keep": valid >= VALID_MIN and relevant,
-                    "valid": valid,
-                    "relevant": relevant,
-                    "reason": clip_text(d.get("reason", ""), QUERY_FILTER_REASON_MAX_CHARS),
-                }
-            )
-        return out
-    except Exception as exc:
-        reason = f"筛选失败:{clip_text(exc, ERROR_MESSAGE_MAX_CHARS)}"
-        return [
-            {"keep": True, "valid": 10, "relevant": True, "reason": reason}
-            for _ in queries
-        ]
-
-
-def _chat_content(settings: Settings, messages: list[dict]) -> str:
-    body = {
-        "model": settings.llm_model,
-        "messages": messages,
-        "response_format": {"type": "json_object"},
-    }
-    env = load_env_file(os.getenv("CK_ENV_FILE", ".env"))
-    prefer_ark = os.getenv("QUERY_FILTER_PROVIDER") == "ark" or bool(
-        os.getenv("ARK_CHAT_MODEL")
-    )
-    openrouter_exc: Exception | None = None
-    if settings.openrouter_api_key and not prefer_ark:
-        try:
-            resp = httpx.post(
-                settings.openrouter_base_url.rstrip("/") + "/chat/completions",
-                headers={
-                    "Authorization": f"Bearer {settings.openrouter_api_key}",
-                    "Content-Type": "application/json",
-                },
-                json=body,
-                timeout=120,
-            )
-            resp.raise_for_status()
-            return resp.json()["choices"][0]["message"]["content"]
-        except Exception as exc:
-            openrouter_exc = exc
-
-    ark_key = os.getenv("ARK_API_KEY") or env.get("ARK_API_KEY")
-    if ark_key:
-        ark_model = (
-            os.getenv("ARK_CHAT_MODEL")
-            or env.get("ARK_CHAT_MODEL")
-            or "doubao-seed-1-6-flash-250615"
-        )
-        ark_url = (
-            os.getenv("ARK_CHAT_URL")
-            or env.get("ARK_CHAT_URL")
-            or "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
-        )
-        resp = httpx.post(
-            ark_url,
-            headers={
-                "Authorization": f"Bearer {ark_key}",
-                "Content-Type": "application/json",
-            },
-            json={
-                "model": ark_model,
-                "messages": messages,
-                "response_format": {"type": "json_object"},
-            },
-            timeout=120,
-        )
-        resp.raise_for_status()
-        return resp.json()["choices"][0]["message"]["content"]
-
-    if openrouter_exc:
-        raise openrouter_exc
-    raise RuntimeError("missing OpenRouter/Ark chat credentials")

+ 0 - 6
acquisition/query_filter.py

@@ -1,6 +0,0 @@
-"""Legacy import shim for the formal query filter."""
-from __future__ import annotations
-
-from acquisition.queries.filter import VALID_MIN, filter_queries, prompt_version
-
-__all__ = ["VALID_MIN", "filter_queries", "prompt_version"]

+ 0 - 28
acquisition/query_filter.txt

@@ -1,28 +0,0 @@
-你是「创作知识检索词」的筛选器。我们要从小红书 / 公众号 / 抖音 / B站 等平台,搜到「创作知识」——可迁移的创作方法 / 决策 / 框架(指导"怎么想 / 怎么判断 / 怎么搭框架",产出因人而异):选题、构思、钩子开头、脚本与结构、标题、封面、叙事呈现、人物 / 冲突设计、起号涨粉等。题材不限(美食 / 游戏 / 健身 / 历史 / 政治 / 军事 / 穿搭…都行)。
-
-记住:每条候选都是要拿去平台【搜创作知识】用的检索词。你判断的核心 = 这条 query 本身有没有意义、值不值得拿去搜——只有"字面说得通 且 搜回来大概率是可迁移的创作方法"的,才有价值、值得搜;说不通、或搜回来跟创作知识无关的,就没价值、别拿去搜。据此逐条给每个候选打两项,只看 query 本身:
-
-【一、语义合法性 valid(0-10)】这串词拼起来,像不像一个做内容的真人会敲进搜索框、读得通的一句话?只看"顺不顺口、像不像人话",先不管内容对不对(内容是第二项的事)。
-· 读着顺、一眼就明白在问什么 → 高分。例:「美食 选题怎么找」「开头 钩子怎么写」「三幕式 结构怎么搭」。
-· 几个词硬凑在一起、别扭拗口、没人会这么说、读完不知道在问啥 → 低分。例:「句式辞格 选题改编 直述开篇」「强硬对抗 选题策划 详细说明」——词是堆上去的,连不成一句正常的话。
-
-【二、创作相关性 relevant(true / false)】这条 query 搜回来的结果,大概率和"创作知识"相关吗?
-先用三把尺判它是不是在搜"可迁移的创作方法":
-① 设计决策 vs 工艺执行(创作 / 制作的根本分界,按实质判、不看词面)
-   · 创作 = 设计决策:怎么想 / 怎么判断 / 怎么搭框架,能一步步指导创作、产出因人而异。
-   · 制作 = 纯工艺执行:器材 / 光圈ISO / 打光 / 收音 / 剪辑软件操作 / 调色参数 / 导出压制,或"打开某 App → 点按钮 → 设参数 → 生成 / 导出"。
-   · 易混(别误判):景别 / 角度 / 构图 / 字幕 / 配乐 / 结构 = 把创意翻成镜头语言的【设计决策】→ 属创作;只有"拿方案去操作设备 / 软件"那层才是制作。
-② 可迁移性:题材 / 平台 / 工具名一换、这套还成立 → 创作;绑死某具体工具 / 平台、一换就失效 → 制作。例:「某小程序三步生成封面」= 制作;「封面怎么排版抓眼球」= 创作。
-③ 业务阶段:贴得上 灵感(发现方向 / 素材 / 切口)/ 选题(判断写什么 / 从哪个角度切,含起标题)/ 脚本(组织表达 / 文案 / 镜头 / 结构)这条创作主线,才沾创作;三个都贴不上 = 跑偏创作的强信号。
-
-在三把尺判为"创作"的基础上,再命中下面任一 → relevant=false(搜回来大概率与创作知识无关):
-B.【工艺执行 / 工具操作 = 制作】违反尺①②:纯操作设备 / 软件、或绑死某工具把内容做出来 / 导出来。
-C.【非内容创作题材本身】教"题材本身"的玩法 / 技能 / 业务——游戏玩法 / 关卡设计、做菜、产品开发、电商 / 运营 / 选品 / 投流、行业分析 本身;而"做关于它的内容(解说 / 视频 / 脚本 / 小说)"要保留。同一题材:做游戏 = 排,做游戏视频 = 收。题材敏感(军人 / 政治 / 历史 等)不是排除理由——它们是合法的内容创作题材。
-D.【应试 / 学术 / 公务 / 政企写作】申论、作文、答题模板、范文 / 作文素材、论文、公文 / 讲话稿、政务 / 企业材料——目标是拿分 / 合规 / 办公务,不是内容创作。
-E.【成品 / 作品 / 素材本身】一篇具体作品、一份素材 / 金句合集、纯展示赏析。
-把握不准、但确实在讲"怎么创作内容"的,relevant=true(倾向保留);明显命中 B–E,才 false。别因一条 query 硬蹭上"脚本 / 选题"就放行越界的(游戏 / 工具操作)——贴不上才是信号,不是借口。
-
-(是否最终保留由调用方按"valid ≥ 阈值 且 relevant=true"决定,你只需如实打分 / 判断。)
-
-严格只输出 JSON 数组,idx 对应输入序号,无任何额外文字 / 解释 / markdown 围栏:
-[{"idx":0,"valid":8,"relevant":true,"reason":"说明打分与判断依据;保留必要证据,不要为了简短省略关键原因"}]

File diff ditekan karena terlalu besar
+ 0 - 0
app/frontend/dist/assets/index-C9UG2pZh.css


File diff ditekan karena terlalu besar
+ 0 - 0
app/frontend/dist/assets/index-D4PSWR2J.css


File diff ditekan karena terlalu besar
+ 1 - 1
app/frontend/dist/assets/index-DyD-4keP.js


+ 2 - 2
app/frontend/dist/index.html

@@ -4,8 +4,8 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>创作知识</title>
-    <script type="module" crossorigin src="/app/assets/index-BZxaJqWu.js"></script>
-    <link rel="stylesheet" crossorigin href="/app/assets/index-C9UG2pZh.css">
+    <script type="module" crossorigin src="/app/assets/index-DyD-4keP.js"></script>
+    <link rel="stylesheet" crossorigin href="/app/assets/index-D4PSWR2J.css">
   </head>
   <body>
     <div id="root"></div>

+ 0 - 3
app/frontend/src/features/query-board/QueryColumn.jsx

@@ -25,9 +25,6 @@ function QueryRow({ item, latest, selected, onSelect, style }) {
         onClick={() => canSelect && onSelect?.(item, latest)}
       >
         <span className="qmark">{item.keep === false ? '✕' : '✓'}</span>
-        {typeof item.valid === 'number' && (
-          <span className={`qvalid ${item.valid >= 6 ? 'ok' : 'low'}`}>语义{item.valid}</span>
-        )}
         <span className="qtext">{item.query}</span>
         {latest ? (
           <>

+ 55 - 10
app/frontend/src/features/query-board/SearchResultColumn.jsx

@@ -1,18 +1,60 @@
+import { useMemo, useState } from 'react'
 import { flatDetailItems, isFinalKnowledge, itemBrief, itemMediaUrl } from './model.js'
 
+const PLATFORM_FILTERS = [
+  ['all', '全部'],
+  ['weixin', '微信'],
+  ['xiaohongshu', '小红书'],
+  ['douyin', '抖音'],
+]
+
 export default function SearchResultColumn({ selected, detail, loading, error, onKnowledgeClick }) {
-  const items = selected ? [...flatDetailItems(detail)].sort((a, b) => {
-    const ak = isFinalKnowledge(a) ? 0 : 1
-    const bk = isFinalKnowledge(b) ? 0 : 1
-    if (ak !== bk) return ak - bk
-    return (a.platform || '').localeCompare(b.platform || '') || (a.title || '').localeCompare(b.title || '')
-  }) : []
+  const [platformFilter, setPlatformFilter] = useState('all')
+  const allItems = useMemo(() => {
+    return selected ? [...flatDetailItems(detail)].sort((a, b) => {
+      const ak = isFinalKnowledge(a) ? 0 : 1
+      const bk = isFinalKnowledge(b) ? 0 : 1
+      if (ak !== bk) return ak - bk
+      return (a.platform || '').localeCompare(b.platform || '') || (a.title || '').localeCompare(b.title || '')
+    }) : []
+  }, [detail, selected])
+  const platformCounts = useMemo(() => {
+    return allItems.reduce((counts, item) => {
+      counts[item.platform] = (counts[item.platform] || 0) + 1
+      return counts
+    }, {})
+  }, [allItems])
+  const items = platformFilter === 'all'
+    ? allItems
+    : allItems.filter((item) => item.platform === platformFilter)
+  const totalCount = allItems.length
+  const selectedFilterLabel = PLATFORM_FILTERS.find(([key]) => key === platformFilter)?.[1] || '全部'
   return (
     <div className="axcol qresult-col">
       <div className="axhd qresult-hd">
-        <div className="axis-title">
-          <span className="query-search-icon">帖</span>
-          <span>搜索结果{selected ? `(${items.length}贴)` : ''}</span>
+        <div className="qresult-title-row">
+          <div className="axis-title">
+            <span className="query-search-icon">帖</span>
+            <span>
+              搜索结果{selected ? `(${platformFilter === 'all' ? totalCount : `${selectedFilterLabel} ${items.length}/${totalCount}`}贴)` : ''}
+            </span>
+          </div>
+          <div className="platform-filter" aria-label="按平台筛选搜索结果">
+            {PLATFORM_FILTERS.map(([key, label]) => {
+              const count = key === 'all' ? totalCount : platformCounts[key] || 0
+              return (
+                <button
+                  className={platformFilter === key ? 'on' : ''}
+                  disabled={!selected || count === 0}
+                  key={key}
+                  type="button"
+                  onClick={() => setPlatformFilter(key)}
+                >
+                  {label}<span>{count}</span>
+                </button>
+              )
+            })}
+          </div>
         </div>
         <div className="qresult-sub">
           {selected ? selected.query : '选择 Query 查看帖子'}
@@ -22,9 +64,12 @@ export default function SearchResultColumn({ selected, detail, loading, error, o
         {!selected && <div className="qresult-empty">点击左侧 Query 词后,这里展示本次搜到的帖子。</div>}
         {selected && loading && <div className="qresult-empty">加载搜索结果...</div>}
         {selected && error && <div className="qresult-empty error">{error}</div>}
-        {selected && !loading && !error && items.length === 0 && (
+        {selected && !loading && !error && allItems.length === 0 && (
           <div className="qresult-empty">这个 Query 暂无搜索结果。若后台还在运行,请等待数据写入。</div>
         )}
+        {selected && !loading && !error && allItems.length > 0 && items.length === 0 && (
+          <div className="qresult-empty">当前筛选下没有{selectedFilterLabel}帖子。</div>
+        )}
         {items.map((item) => {
           const knowledge = isFinalKnowledge(item)
           const cover = itemMediaUrl(item)

+ 68 - 16
app/frontend/src/styles/legacy.css

@@ -53,9 +53,6 @@ h1 { font-size: 22px; margin: 0 0 2px; letter-spacing: .5px; }
 .qrow.drop .qmark { color: var(--bad); }
 .qrow.drop .qtext { text-decoration: line-through; }
 .qmark { flex: 0 0 auto; }
-.qvalid { flex: 0 0 auto; font-size: 11px; font-weight: 700; padding: 0 6px; border-radius: 9px; }
-.qvalid.ok { color: var(--ok); background: #e6f6ee; }
-.qvalid.low { color: var(--bad); background: #fbeaea; }
 .qtext { flex: 1 1 auto; white-space: normal; word-break: break-word; }   /* 长 query 换行显示,别截断 */
 .qreason { flex: 1 0 100%; margin-left: 20px; color: var(--muted); font-size: 11.5px; }   /* 理由独占一行、占满宽 */
 .qdetail { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line);
@@ -1103,25 +1100,12 @@ h1 {
   font-weight: 800;
 }
 
-.dashboard-page .qvalid,
 .dashboard-page .qcreation,
 .dashboard-page .qdetail,
 .dashboard-page .qreason {
   flex: 0 0 auto;
 }
 
-.dashboard-page .qvalid {
-  justify-self: start;
-  padding: 1px 7px;
-  border-radius: 999px;
-  font-size: 11px;
-}
-
-.dashboard-page .qvalid.ok {
-  color: #00875a;
-  background: #e8fff3;
-}
-
 .dashboard-page .qtext {
   flex: 1 1 150px;
   min-width: 0;
@@ -1178,6 +1162,74 @@ h1 {
   display: block;
 }
 
+.qresult-title-row {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 10px;
+  min-width: 0;
+}
+
+.qresult-title-row .axis-title {
+  flex: 1 1 auto;
+  min-width: 136px;
+}
+
+.platform-filter {
+  flex: 0 0 auto;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  padding: 2px;
+  border: 1px solid #e5e8ef;
+  border-radius: 6px;
+  background: #f7f8fa;
+}
+
+.platform-filter button {
+  height: 24px;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  border: 0;
+  border-radius: 5px;
+  padding: 0 7px;
+  background: transparent;
+  color: #4e5969;
+  font-size: 11px;
+  font-weight: 700;
+  line-height: 1;
+  cursor: pointer;
+}
+
+.platform-filter button:hover:not(:disabled) {
+  color: #165dff;
+  background: #e8f3ff;
+}
+
+.platform-filter button:focus-visible {
+  outline: 2px solid #165dff;
+  outline-offset: 1px;
+}
+
+.platform-filter button.on {
+  color: #165dff;
+  background: #fff;
+  box-shadow: 0 1px 2px rgba(29, 33, 41, .08);
+}
+
+.platform-filter button:disabled {
+  color: #c9cdd4;
+  cursor: not-allowed;
+}
+
+.platform-filter button span {
+  color: inherit;
+  opacity: .78;
+  font-size: 10px;
+}
+
 .qresult-sub {
   margin-top: 10px;
   color: #4e5969;

+ 0 - 2
app/routes/query_generation.py

@@ -118,7 +118,6 @@ def _normalize_light_query_detail(detail: dict[str, Any]) -> dict[str, Any]:
 def query_generation_preview(
     per: int = Query(default=0, ge=0, le=10000),
     batch_n: int = Query(default=0, ge=0, le=1000),
-    enable_query_filter: bool = Query(default=False),
 ) -> dict[str, Any]:
     """Preview the currently active formal query families without writing DB rows."""
 
@@ -129,7 +128,6 @@ def query_generation_preview(
         options=QueryBuildOptions(
             per=per,
             batch_n=batch_n,
-            enable_query_filter=enable_query_filter,
             active_family_keys=("f1", "f2"),
         ),
     )

+ 13 - 5
prompts/classify_imgtext.txt

@@ -16,16 +16,19 @@
 - 如何构思图文选题
 - 如何设计标题、封面、开头、结尾
 - 如何组织故事、观点、节奏、冲突、反差、笑点
-- 如何把历史、游戏、电商、生活经验等题材做成视频、图文或文章
-- 如何拍人像、街景、产品、美食、穿搭、旅行画面
+- 如何把历史、游戏、电商、生活经验、旅行、日常记录等题材做成视频、图文或文章
+- 如何拍人像、街景、产品、美食、旅行画面
 - 如何把一个场景拍成照片、图片组、图文、短视频或影像内容
 - 如何拍 vlog、日常记录视频、旅行 vlog、人像 vlog;如何设计 vlog 的空镜、跟拍、景别变化、转场、细节镜头、结尾回扣和画面节奏
 - 如何整理可复用的拍照姿势、构图、机位、光线、镜头或画面参考
+- 如何组织游记 / 旅行图文:旅行叙事结构、路线如何转成故事、地点观察方式、照片组组织、情绪线、图文节奏
+- 如何写日记体 / 日常记录内容:日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材方式
 
 不算:
 - 教游戏玩法、关卡设计、产品开发、电商选品、运营动作、行业分析、做菜本身
 - 这些是在做题材本身,不是在做"关于题材的内容"
-- 教穿搭本身、菜谱本身、旅游攻略本身不算;但教"如何把穿搭/美食/旅行拍成内容"算
+- 菜谱本身、纯旅游消费攻略本身不算;但教"如何把美食/旅行/日常生活做成内容或生活表达作品"算
+- 游记、日记不是无条件都算:纯流水账、纯晒图、纯消费攻略、纯个人情绪不算;有可复用生活表达结构、旅行叙事方法或日记体内容组织方法才算
 
 【轴B·是创作不是制作】
 它教的是构思、选题、写作、结构、表达、呈现、判断、复盘,或视觉表达上的创作决策。
@@ -48,6 +51,8 @@
 - 构图、景别、机位、取景、主体与背景关系
 - 光线选择、光位、光质、色彩氛围、视觉风格
 - 镜头运动、镜头组接、画面节奏、场景调度、视觉叙事
+- 游记的路线叙事、地点观察、情绪线、照片组节奏
+- 日记体表达、生活细节选择、日常记录结构、情绪推进
 
 不算:
 - 打开工具 -> 输入/粘贴 -> 生成/扩写 -> 设参数 -> 导出
@@ -71,9 +76,11 @@
 - "产品摄影 6 种构图:中心、对角线、三角、留白、重复、框景" = 构图选项清单,算 What 创作知识。
 - "街景视频 5 种运镜:推近强调,横移展示空间,跟拍增强代入" = 镜头组织方法,算创作知识。
 - "日常 vlog 5 个镜头:环境空镜建立地点、手部动作做转场、跟拍走路、近景补细节、结尾回到同一场景" = vlog 镜头组织和视觉叙事方法,算创作知识。
+- "旅行游记结构:用到达地标开场,按路线推进观察,穿插人物/天气/气味细节,最后用一个情绪回扣收束" = 游记叙事和图文节奏方法,算创作知识。
+- "日记体小红书写法:时间锚点、生活场景、一个具体细节、情绪转折、结尾一句自我回扣" = 日记体内容结构,算创作知识。
 
 但不能把所有成品都拔高成方法。
-如果只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集,且看不出可迁移的姿势、构图、光线、镜头、画面组织、创作结构、方法或判断依据,仍判非创作。
+如果只是普通照片展示、壁纸分享、旅行图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集、纯流水账、纯消费攻略、纯个人情绪,且看不出可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构、创作结构、方法或判断依据,仍判非创作。
 
 粗筛阶段的原则:
 - 如果它明显只是无方法、无参考意图的展示,判非创作。
@@ -104,8 +111,9 @@
 除非它明确教"如何把这些题材创作为内容作品"。
 
 5. 【纯展示/无创作参考】
-只是提供现成脚本、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,且无法抽出可迁移创作结构或视觉参考选项。
+只是提供现成脚本、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,且无法抽出可迁移创作结构、生活表达结构或视觉参考选项。
 但姿势合集、构图合集、拍照 pose 图、镜头清单、样片拆解若可作为创作者的可复用参考,不属于纯展示。
+游记、日记如果能抽出可复用的旅行叙事、路线转故事、地点观察、照片组组织、日记体表达、生活记录结构或情绪推进方法,也不属于纯展示。
 </一票否决>
 
 <输出要求>

+ 13 - 5
prompts/classify_video.txt

@@ -16,16 +16,19 @@
 - 如何构思图文选题
 - 如何设计标题、封面、开头、结尾
 - 如何组织故事、观点、节奏、冲突、反差、笑点
-- 如何把历史、游戏、电商、生活经验等题材做成视频、图文或文章
-- 如何拍人像、街景、产品、美食、穿搭、旅行画面
+- 如何把历史、游戏、电商、生活经验、旅行、日常记录等题材做成视频、图文或文章
+- 如何拍人像、街景、产品、美食、旅行画面
 - 如何把一个场景拍成照片、图片组、图文、短视频或影像内容
 - 如何拍 vlog、日常记录视频、旅行 vlog、人像 vlog;如何设计 vlog 的空镜、跟拍、景别变化、转场、细节镜头、结尾回扣和画面节奏
 - 如何整理可复用的拍照姿势、构图、机位、光线、镜头或画面参考
+- 如何组织游记 / 旅行图文:旅行叙事结构、路线如何转成故事、地点观察方式、照片组组织、情绪线、图文节奏
+- 如何写日记体 / 日常记录内容:日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材方式
 
 不算:
 - 教游戏玩法、关卡设计、产品开发、电商选品、运营动作、行业分析、做菜本身
 - 这些是在做题材本身,不是在做"关于题材的内容"
-- 教穿搭本身、菜谱本身、旅游攻略本身不算;但教"如何把穿搭/美食/旅行拍成内容"算
+- 菜谱本身、纯旅游消费攻略本身不算;但教"如何把美食/旅行/日常生活做成内容或生活表达作品"算
+- 游记、日记不是无条件都算:纯流水账、纯晒图、纯消费攻略、纯个人情绪不算;有可复用生活表达结构、旅行叙事方法或日记体内容组织方法才算
 
 【轴B·是创作不是制作】
 它教的是构思、选题、写作、结构、表达、呈现、判断、复盘,或视觉表达上的创作决策。
@@ -48,6 +51,8 @@
 - 构图、景别、机位、取景、主体与背景关系
 - 光线选择、光位、光质、色彩氛围、视觉风格
 - 镜头运动、镜头组接、画面节奏、场景调度、视觉叙事
+- 游记的路线叙事、地点观察、情绪线、照片组节奏
+- 日记体表达、生活细节选择、日常记录结构、情绪推进
 
 不算:
 - 打开工具 -> 输入/粘贴 -> 生成/扩写 -> 设参数 -> 导出
@@ -71,9 +76,11 @@
 - "产品摄影 6 种构图:中心、对角线、三角、留白、重复、框景" = 构图选项清单,算 What 创作知识。
 - "街景视频 5 种运镜:推近强调,横移展示空间,跟拍增强代入" = 镜头组织方法,算创作知识。
 - "日常 vlog 5 个镜头:环境空镜建立地点、手部动作做转场、跟拍走路、近景补细节、结尾回到同一场景" = vlog 镜头组织和视觉叙事方法,算创作知识。
+- "旅行游记结构:用到达地标开场,按路线推进观察,穿插人物/天气/气味细节,最后用一个情绪回扣收束" = 游记叙事和图文节奏方法,算创作知识。
+- "日记体小红书写法:时间锚点、生活场景、一个具体细节、情绪转折、结尾一句自我回扣" = 日记体内容结构,算创作知识。
 
 但不能把所有成品都拔高成方法。
-如果只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集,且看不出可迁移的姿势、构图、光线、镜头、画面组织、创作结构、方法或判断依据,仍判非创作。
+如果只是普通照片展示、壁纸分享、旅行图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集、纯流水账、纯消费攻略、纯个人情绪,且看不出可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构、创作结构、方法或判断依据,仍判非创作。
 
 粗筛阶段的原则:
 - 如果它明显只是无方法、无参考意图的展示,判非创作。
@@ -104,8 +111,9 @@
 除非它明确教"如何把这些题材创作为内容作品"。
 
 5. 【纯展示/无创作参考】
-只是提供现成脚本、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,且无法抽出可迁移创作结构或视觉参考选项。
+只是提供现成脚本、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,且无法抽出可迁移创作结构、生活表达结构或视觉参考选项。
 但姿势合集、构图合集、拍照 pose 图、镜头清单、样片拆解若可作为创作者的可复用参考,不属于纯展示。
+游记、日记如果能抽出可复用的旅行叙事、路线转故事、地点观察、照片组组织、日记体表达、生活记录结构或情绪推进方法,也不属于纯展示。
 </一票否决>
 
 <输出要求>

+ 12 - 6
prompts/extract.txt

@@ -3,8 +3,8 @@
 <什么算创作知识>
 能迁移、能复用、能教别人"怎么创作图文/视频/照片/视觉内容"的方法、原理、清单、案例或参考库。**判断要同时过两条轴,缺一不算:**
 
-【轴A·产出物是不是「内容」】这条知识帮你创作出来的成品,是不是 图文 / 视频 / 脚本 / 剧本 / 小说 这类**内容**?题材不限(美食/游戏/健身/穿搭…都行),卡的是产出物:
-- 算:产出"关于某题材的**内容或视觉作品**"——美食视频拍法、游戏实况/解说脚本、健身图文写法、剧本/小说写法、人像/街景/产品/美食/穿搭/旅行照片拍法、vlog/日常记录视频/旅行 vlog 拍法、封面图/海报图/短视频画面设计、照片组/视觉案例集。
+【轴A·产出物是不是「内容」】这条知识帮你创作出来的成品,是不是 图文 / 视频 / 脚本 / 剧本 / 小说 这类**内容**?题材不限(美食/游戏/健身…都行),卡的是产出物:
+- 算:产出"关于某题材的**内容、视觉作品或生活表达作品**"——美食视频拍法、游戏实况/解说脚本、健身图文写法、剧本/小说写法、人像/街景/产品/美食/旅行照片拍法、vlog/日常记录视频/旅行 vlog 拍法、游记/旅行图文、日记体内容、封面图/海报图/短视频画面设计、照片组/视觉案例集。
 - 不算:产出"那个题材**本身**"——一道菜的做法、一个**游戏的玩法/关卡设计**、一个产品、一套运营/选品策略(这是做题材本身,不是做关于它的内容)。
 
 【轴B·是「创作」不是「制作」】它教的是"怎么构思/选题/写/结构/呈现"(创作),还是"用哪个软件/AI、点哪个按钮把成品产出来"(制作/工具操作)?
@@ -15,15 +15,17 @@
   禁止用"虽然是 AI 工具流程,但能高效产出内容/视觉效果"来放行。
 
 【两轴 AND】两条都过才算;任一不过 → 不算。
-另外不算(作品本身):一段具体文案、一张图的 AI 出图提示词、一个具体选题标题、一份具体食谱用量、普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏。
+另外不算(作品本身):一段具体文案、一张图的 AI 出图提示词、一个具体选题标题、一份具体食谱用量、普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪
 作品里有时夹带可迁移的**创作**方法,有就提、没有就如实标空。
 </什么算创作知识>
 
 <视觉/摄影/摄像知识特别规则>
-- 摄影、拍照、摄像、扫街、人像、街景、产品、美食、穿搭、旅行画面教程,只要在教如何创作照片、图片组、图文、短视频或视觉画面,就属于创作知识。
+- 摄影、拍照、摄像、扫街、人像、街景、产品、美食、旅行画面教程,只要在教如何创作照片、图片组、图文、短视频或视觉画面,就属于创作知识。
 - vlog、日常记录视频、旅行 vlog、人像 vlog 教程,只要在教空镜、跟拍、转场、景别变化、细节镜头、结尾回扣、镜头顺序或画面节奏,就属于创作知识。
+- 游记 / 旅行图文也可以是生活叙事知识:如果原帖提供旅行叙事结构、路线如何转成故事、地点观察方式、照片组组织、情绪线、图文节奏,要忠实提取。
+- 日记 / 日常记录也可以是生活表达知识:如果原帖提供日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材方式,要忠实提取。
 - 姿势合集、构图合集、拍照 pose 图、运镜示例、布光示例、样片拆解、镜头清单、视觉参考库也可以算。它们通常是 What 型知识:一组可复用的视觉选项或案例参考,不要求必须有完整 How 或 Why。
-- 如果原帖只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,看不出可迁移的姿势、构图、光线、镜头、画面组织或创作判断,才判空。
+- 如果原帖只是普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,看不出可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构或创作判断,才判空。
 - 光线选择、色彩氛围、构图、景别、机位、镜头组织是创作判断;相机菜单、灯具连接、参数设置、软件按钮、导出保存才是制作/工具执行。
 </视觉/摄影/摄像知识特别规则>
 
@@ -36,7 +38,7 @@
 - 以原始素材为准,忠实转述,不编造、不补全、不替作者总结它没说的结论;有的尽量提全。
 - `text` 是主产物:把整帖中可迁移的创作知识完整讲清楚。`cards` 只是辅助:只有当某张图片/卡片**本身明确承载了创作知识或视觉参考选项**时,才写入 cards;如果知识来自正文,就只写进 text,不必写 cards。`from_video` 只有在真实看到了视频内容并能确认视频里讲了这些知识时才写;如果正文里只是出现 video 链接或占位符,不要写 from_video。
 - **is_empty(整条流程的总闸)**:判 `true` 后,后面的拆颗 / 归类 / 入库**全部不走**。两种情况标 `true`:
-  ① **纯作品/纯展示/纯无关**——通篇看完确实没有任何可迁移的创作方法/原理/清单/案例参考。注意:姿势合集、构图合集、镜头清单、样片拆解若可作为视觉参考,不是纯展示。
+  ① **纯作品/纯展示/纯无关**——通篇看完确实没有任何可迁移的创作方法/原理/清单/案例参考。注意:姿势合集、构图合集、镜头清单、样片拆解若可作为视觉参考,不是纯展示;游记、日记若能提供可复用的叙事结构或生活表达方法,也不是纯展示
   ② **整帖不在范围内(越界要果断判 true,别放行)**——整帖产出物不是图文/视频内容(如游戏玩法/关卡设计、产品/运营/选品、做菜本身),**或**整帖是制作/工具操作(如"用某 AI/App 几步做出一段视频"的打开→点按钮→导出流程)。这类即便看着像"教程/步骤",也按轴A/轴B 判定为越界。
   只要有一点点"怎么**创作内容**"的可迁移方法就 `false`;**范围内但拿不准有没有知识 → `false`**(交后面再筛);**但"在不在范围内"要果断**——明确越界(游戏设计/纯工具操作)就标 `true`,别用"拿不准"把越界帖放进来。标 `true` 时 `text` 留空字符串、`cards` 留空数组。
 </工作要点>
@@ -59,6 +61,10 @@
 正文:"日常 vlog 可以用五个镜头拍出故事感:环境空镜建立地点,手部动作做转场,跟拍走路增强进入感,近景补吃饭/整理东西的细节,结尾回到同一场景做回扣。"
 输出:{{"text": "讲解日常 vlog 的镜头组织方法:先用环境空镜建立地点,再用手部动作做转场,用跟拍走路增强进入感,用近景补充吃饭或整理东西的生活细节,最后回到同一场景形成结尾回扣。", "cards": [], "from_image": "", "from_video": "", "is_empty": false}}
 </example>
+<example>  游记/日记体表达
+正文:"旅行日记别只写去了哪里:先用一个地点细节开场,再按路线推进观察,穿插天气、声音、气味和人的小动作,最后用一句情绪回扣收尾。"
+输出:{{"text": "讲解旅行日记的内容组织方法:不要只记录路线,应先用地点细节开场,再按路线推进观察,穿插天气、声音、气味和人物小动作等场景细节,最后用一句情绪回扣收束。", "cards": [], "from_image": "", "from_video": "", "is_empty": false}}
+</example>
 </示例>
 
 <输入>

+ 5 - 3
prompts/extract_video.txt

@@ -4,14 +4,16 @@
 能迁移、能复用、能教别人"怎么做内容或视觉作品"的东西(选题法、爆款结构、起号逻辑、脚本/文案/镜头技巧、摄影/摄像方法、拍照姿势、构图、光线、机位、景别、画面叙事、为什么某种做法有效)。
 照片、摄影作品、拍摄成片、镜头画面、图片组、封面图、海报图、视觉案例集、短视频画面,都算内容作品。
 拍 vlog、日常记录视频、旅行 vlog、人像 vlog 时的空镜、跟拍、转场、景别变化、细节镜头、镜头顺序、结尾回扣和画面节奏,也算创作知识。
-不算的:普通作品展示、壁纸分享、旅行图集、成片欣赏;纯相机菜单、设备参数、软件按钮、批量套预设、导出保存;整条视频核心是打开豆包/AI/App、上传图片、选择模型/比例/时长、输入提示词、生成服装展示图/视频/成片。禁止用"虽然是 AI 工具流程,但能高效产出内容/视觉效果"来放行。
+游记 / 旅行图文 / 日记体内容如果在教旅行叙事结构、路线转故事、地点观察方式、照片组组织、情绪线、图文节奏、日记体表达、生活记录结构、场景细节或日常内容选材,也算创作知识。
+不算的:普通作品展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪;纯相机菜单、设备参数、软件按钮、批量套预设、导出保存;整条视频核心是打开豆包/AI/App、上传图片、选择模型/比例/时长、输入提示词、生成服装展示图/视频/成片。禁止用"虽然是 AI 工具流程,但能高效产出内容/视觉效果"来放行。
 </什么算创作知识>
 
 <视觉/摄影/摄像知识特别规则>
-- 如果视频在教拍人像、街景、产品、美食、穿搭、旅行画面,或教扫街/街拍/人像视频的镜头组织,属于创作知识。
+- 如果视频在教拍人像、街景、产品、美食、旅行画面,或教扫街/街拍/人像视频的镜头组织,属于创作知识。
 - 如果视频在教 vlog、日常记录视频、旅行 vlog、人像 vlog 的镜头组织、空镜、跟拍、转场、景别变化、细节镜头、结尾回扣或视觉叙事,属于创作知识。
+- 如果视频在教游记/日记的叙事结构、地点观察、照片组组织、情绪线、生活记录结构,属于创作知识。
 - 姿势合集、构图合集、拍照 pose 图、运镜示例、布光示例、样片拆解、镜头清单也可以算。它们通常是 What 型知识:一组可复用的视觉选项或案例参考,不要求必须有完整 How 或 Why。
-- 如果只是成片展示,没有可迁移的姿势、构图、光线、镜头、画面组织或创作判断,就不要生成 segment。
+- 如果只是成片展示、旅行记录流水账、个人情绪记录,没有可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构或创作判断,就不要生成 segment。
 </视觉/摄影/摄像知识特别规则>
 
 <工作要点>

+ 6 - 2
prompts/gate_admit.txt

@@ -20,11 +20,13 @@
 - 如何设计标题、开头、封面、结尾
 - 如何组织故事、观点、节奏、冲突、反差、笑点
 - 如何把某个题材做成视频、图文、文章或脚本
-- 如何拍人像、街景、产品、美食、穿搭、旅行画面
+- 如何拍人像、街景、产品、美食、旅行画面
 - 如何拍 vlog、日常记录视频、旅行 vlog、人像 vlog;如何设计 vlog 的空镜、跟拍、景别变化、转场、细节镜头、结尾回扣和画面节奏
 - 如何设计姿势、表情、人物与道具互动、构图、景别、机位、光线、色彩氛围、镜头运动、镜头组接、场景调度、画面叙事
 - 如何把一个场景拍成照片、图片组、图文、短视频或影像内容
 - 如何整理可复用的拍照姿势、构图、机位、光线、镜头或画面参考
+- 如何组织游记 / 旅行图文:旅行叙事结构、路线如何转成故事、地点观察方式、照片组组织、情绪线、图文节奏
+- 如何写日记体 / 日常记录内容:日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材方式
 
 不算:
 - 如何做游戏玩法、产品开发、电商选品、运营动作、行业分析
@@ -48,8 +50,10 @@
 
 如果读懂内容在教 vlog 拍摄方法、日常记录视频结构、旅行 vlog 镜头组织、空镜、跟拍、转场、景别变化、细节镜头或视觉叙事,并且不是单纯成片展示,优先判 in_scope=true。
 
+如果读懂内容在教游记/日记的叙事结构、地点观察、照片组组织、情绪线、日常内容选材、生活记录结构,也可以判 in_scope=true。这类属于生活表达型创作知识。
+
 但不要把任何成品都拔高成方法。
-如果它只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集,且看不出可迁移的姿势、构图、光线、镜头、画面组织、创作结构、方法或判断依据,判 in_scope=false。
+如果它只是普通照片展示、壁纸分享、旅行图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集、纯流水账、纯消费攻略、纯个人情绪,且看不出可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构、创作结构、方法或判断依据,判 in_scope=false。
 
 判断案例型知识时,重点问:
 1. 去掉具体人物、行业、话题后,剩下的结构还能不能迁移到另一个选题?

+ 4 - 2
prompts/gate_refute.txt

@@ -19,7 +19,7 @@
 只是在讲某个知识、观点、故事、感悟、评论、经历,没有教内容创作方法。
 
 5. 纯素材/纯范文/具体作品:
-只提供现成脚本、文案、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,无法抽出可迁移创作结构或视觉参考选项。
+只提供现成脚本、文案、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,无法抽出可迁移创作结构、生活表达结构或视觉参考选项。
 </排除规则>
 
 <不要误排除>
@@ -32,7 +32,9 @@
 姿势合集、构图合集、拍照 pose 图、运镜示例、布光示例、样片拆解、镜头清单,只要能作为可迁移的视觉选项、画面参考、动作参考、构图参考或镜头组织参考,就不要判 out_of_scope=true。它们可以拆成 What / 子集型知识,不必一定是 How。
 vlog 拍摄内容也不要因为题材是日常生活、旅行、街拍、人像记录就直接按"经历/普通展示"排除。只要它在教空镜、跟拍、转场、景别变化、近景细节、镜头顺序、结尾回扣、画面节奏或视觉叙事,就不要判 out_of_scope=true。
 
-但如果它只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,抽不出可迁移的姿势、构图、光线、镜头、画面组织、创作结构、方法或判断依据,就应判 out_of_scope=true。
+游记、日记也不要仅因题材偏生活就直接按"题材本身/经历/普通展示"排除。只要它在教旅行叙事结构、路线如何转成故事、地点观察方式、照片组组织、情绪线、图文节奏,或日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材,就不要判 out_of_scope=true。
+
+但如果它只是普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,抽不出可迁移的姿势、构图、光线、镜头、画面组织、旅行叙事、日记体结构、创作结构、方法或判断依据,就应判 out_of_scope=true。
 </不要误排除>
 
 逐条对照上面的排除规则如实核验:确实命中就判 out_of_scope=true;一条都不命中,就判 out_of_scope=false。

+ 5 - 3
prompts/gate_tiebreak.txt

@@ -9,8 +9,10 @@
 
 题材不限,看产出物和层面:
 - 创作游戏视频、构思电商带货视频 idea、写历史解说脚本 = 创作。
-- 拍人像、拍街景、拍产品、拍美食、拍穿搭、拍旅行画面,只要是在教如何创作照片/视频/图文/视觉内容 = 创作。
+- 拍人像、拍街景、拍产品、拍美食、拍旅行画面,只要是在教如何创作照片/视频/图文/视觉内容 = 创作。
 - 拍 vlog、日常记录视频、旅行 vlog、人像 vlog,只要在教空镜、跟拍、转场、景别变化、细节镜头、画面节奏或视觉叙事 = 创作。
+- 游记 / 旅行图文如果提供旅行叙事结构、路线转故事、地点观察方式、照片组组织、情绪线、图文节奏 = 创作。
+- 日记 / 日常记录如果提供日记体表达、生活记录结构、情绪推进、场景细节、日常内容选材方式 = 创作。
 - 设计游戏玩法、做电商选品运营、讲行业分析本身 = 越界。
 </裁决标准>
 
@@ -25,7 +27,7 @@
 姿势合集、构图合集、拍照 pose 图、运镜示例、布光示例、样片拆解、镜头清单,只要能作为可迁移的视觉选项、画面参考、动作参考、构图参考或镜头组织参考,可以判 in_scope=true。它们不必一定有完整 How 或 Why,可以拆成 What / 子集型知识。
 vlog 拍摄示例也可以算:如果内容给出一组可迁移镜头安排(如环境空镜、手部转场、跟拍走路、近景细节、同场景回扣),这是镜头组织/视觉叙事方法,判 in_scope=true。
 
-如果只是普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集,且抽不出可迁移结构、方法、视觉参考选项或判断依据,判 in_scope=false。
+如果只是普通照片展示、壁纸分享、旅行图集、成片欣赏、可复制文案、脚本范文、标题合集、金句合集、纯流水账、纯消费攻略、纯个人情绪,且抽不出可迁移结构、方法、生活表达结构、视觉参考选项或判断依据,判 in_scope=false。
 </案例型创作知识>
 
 <排除规则>
@@ -35,7 +37,7 @@ vlog 拍摄示例也可以算:如果内容给出一组可迁移镜头安排(
 - 越界题材本身:做游戏玩法、产品开发、电商选品、运营、行业分析等题材本身。
 - 应试/学术/公文:申论、作文、答题模板、论文、公文、讲话稿等。
 - 观点/知识/故事本身:只是在讲知识、观点、故事、感悟、评论、经历,没有教内容创作方法。
-- 纯展示/无创作参考:只提供现成脚本、文案、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、穿搭图集、成片欣赏,无法抽出可迁移创作结构或视觉参考选项。
+- 纯展示/无创作参考:只提供现成脚本、文案、标题、金句、范文、普通照片展示、壁纸分享、旅行图集、成片欣赏、纯流水账、纯消费攻略、纯个人情绪,无法抽出可迁移创作结构、生活表达结构或视觉参考选项。
 </排除规则>
 
 拿不准、模棱两可、半创作半制作时:如果核心知识明确落在内容创作决策、视觉表达决策、可迁移案例结构,或可迁移视觉参考选项上,判 in_scope=true;如果核心只是工具执行或普通展示,判 in_scope=false。

+ 0 - 2
scripts/build_creation_demo.py

@@ -33,7 +33,6 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
     parser.add_argument("--per", type=int, default=0, help="Max queries per family; 0 means all combinations")
     parser.add_argument("--batch-n", type=int, default=0, help="Max tree nodes per axis; 0 means all L3/L4 nodes")
     parser.add_argument("--seed", type=int, default=7)
-    parser.add_argument("--enable-query-filter", action="store_true", help="Run the optional LLM query pre-filter")
     parser.add_argument(
         "--family",
         action="append",
@@ -74,7 +73,6 @@ def main(argv: list[str] | None = None) -> int:
             per=args.per,
             batch_n=args.batch_n,
             seed=args.seed,
-            enable_query_filter=args.enable_query_filter,
             active_family_keys=tuple(args.families) if args.families else ("f1", "f2"),
         ),
     )

+ 0 - 6
scripts/run_creation_singleton.py

@@ -36,11 +36,6 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
     parser.add_argument("--per", type=int, default=1, help="Queries per active family")
     parser.add_argument("--batch-n", type=int, default=30)
     parser.add_argument("--seed", type=int, default=7)
-    parser.add_argument(
-        "--enable-query-filter",
-        action="store_true",
-        help="Run the optional query-filter LLM before search.",
-    )
     parser.add_argument(
         "--platform",
         action="append",
@@ -109,7 +104,6 @@ def main(argv: list[str] | None = None) -> int:
             per=args.per,
             batch_n=args.batch_n,
             seed=args.seed,
-            enable_query_filter=args.enable_query_filter,
             active_family_keys=("f1", "f2"),
         ),
     )

+ 6 - 52
tests/test_query_builder.py

@@ -6,8 +6,6 @@ from uuid import uuid4
 import pytest
 
 from acquisition.domain import Query, QueryBatch
-from acquisition.queries import builder as query_builder
-from acquisition.queries import filter as query_filter
 from acquisition.queries.builder import (
     QueryBuildOptions,
     TREES,
@@ -15,7 +13,6 @@ from acquisition.queries.builder import (
     persist_query_batch,
 )
 from core.config import PgConfig, Settings
-from core.text_limits import QUERY_FILTER_REASON_MAX_CHARS
 
 
 def _settings() -> Settings:
@@ -35,23 +32,6 @@ def _settings() -> Settings:
     )
 
 
-def test_query_filter_preserves_wide_reason(monkeypatch):
-    reason = "理" * (QUERY_FILTER_REASON_MAX_CHARS + 3)
-
-    monkeypatch.setattr(
-        query_filter,
-        "_chat_content",
-        lambda settings, messages: json.dumps(
-            [{"idx": 0, "valid": 8, "relevant": True, "reason": reason}],
-            ensure_ascii=False,
-        ),
-    )
-
-    rows = query_filter.filter_queries(["符号 视频 灵感 怎么做"], _settings())
-
-    assert rows[0]["reason"] == "理" * QUERY_FILTER_REASON_MAX_CHARS
-
-
 class FakeRepo:
     def __init__(self) -> None:
         self.batch_kwargs = None
@@ -182,47 +162,22 @@ def test_build_creation_query_batch_rejects_unknown_family_key():
         )
 
 
-def test_build_creation_query_batch_keeps_all_queries_without_default_llm_filter(monkeypatch):
-    def fail_filter(*args, **kwargs):
-        raise AssertionError("query filter should be disabled by default")
-
-    monkeypatch.setattr(query_builder, "filter_queries", fail_filter)
-
+def test_build_creation_query_batch_keeps_all_queries():
     generated = build_creation_query_batch(
         _settings(),
         options=QueryBuildOptions(per=3, batch_n=4),
     )
 
-    assert generated["metadata"]["query_filter_enabled"] is False
+    assert "query_filter_enabled" not in generated["metadata"]
+    assert "query_filter_prompt_version" not in generated["metadata"]
     assert all(item["keep"] is True for family in generated["families"] for item in family["items"])
     assert all(item["reason"] == "" for family in generated["families"] for item in family["items"])
 
 
-def test_build_creation_query_batch_can_enable_llm_filter(monkeypatch):
-    def fake_filter(queries, settings):
-        return [
-            {"keep": i != 1, "valid": 9 if i != 1 else 5, "relevant": i != 1, "reason": f"r{i}"}
-            for i, _ in enumerate(queries)
-        ]
-
-    monkeypatch.setattr(query_builder, "filter_queries", fake_filter)
-
-    generated = build_creation_query_batch(
-        _settings(),
-        options=QueryBuildOptions(per=3, batch_n=4, enable_query_filter=True),
-    )
-    items = generated["families"][0]["items"]
-
-    assert generated["metadata"]["query_filter_enabled"] is True
-    assert [item["keep"] for item in items] == [True, False, True]
-    assert [item["reason"] for item in items] == ["r0", "r1", "r2"]
-
-
 def test_persist_query_batch_writes_formal_batch_and_query_contract():
     repo = FakeRepo()
     generated = {
         "metadata": {
-            "query_filter_prompt_version": "abc123",
             "active_family_keys": ["f1", "f2"],
         },
         "families": [
@@ -235,8 +190,6 @@ def test_persist_query_batch_writes_formal_batch_and_query_contract():
                         "query": "反转 视频 脚本 怎么做",
                         "parts": {"实质": "反转", "模态": "视频"},
                         "keep": True,
-                        "valid": 8,
-                        "relevant": True,
                         "reason": "可搜创作方法",
                     }
                 ],
@@ -258,5 +211,6 @@ def test_persist_query_batch_writes_formal_batch_and_query_contract():
     assert row["keep"] is True
     assert row["filter_reason"] == "可搜创作方法"
     assert row["metadata"]["family_key"] == "f1"
-    assert row["metadata"]["valid"] == 8
-    assert row["metadata"]["query_filter_prompt_version"] == "abc123"
+    assert "valid" not in row["metadata"]
+    assert "relevant" not in row["metadata"]
+    assert "query_filter_prompt_version" not in row["metadata"]

+ 2 - 0
创作知识提取-skill/extraction/phase1-frame.md

@@ -127,6 +127,8 @@
 边界看「设计决策 vs 工艺执行」:景别/构图/字幕/结构/姿势/光线选择/色彩氛围/镜头组织=设计决策→保留为 step 或 What;操作设备软件那层→剔。
 视觉案例型内容要保留:姿势合集、构图合集、拍照 pose 图、运镜示例、布光示例、样片拆解、镜头清单,只要能作为可迁移的视觉选项、画面参考、动作参考、构图参考或镜头组织参考,就可以拆成 What(通常是 `子集`)。
 vlog 拍摄知识要保留:日常记录视频、旅行 vlog、人像 vlog 里的空镜、跟拍、转场、景别变化、近景细节、镜头顺序、结尾回扣、画面节奏,属于镜头组织/视觉叙事/拍摄表达,可拆成 How 或 What;不要因为题材是日常生活就当成普通生活记录剔除。
+生活表达型内容要保留:游记、旅行图文、日记、日常记录如果提供旅行叙事结构、路线转故事、地点观察、照片组组织、情绪线、图文节奏、日记体表达、生活记录结构、场景细节、日常内容选材,可以拆成 What/How/Why。不要因为题材是旅行、日记就直接按普通展示或个人经历剔除。
+但纯流水账、纯晒图、纯消费攻略、纯个人情绪,抽不出可迁移表达方法/叙事结构/视觉参考的,仍剔除。
 
 **整颗级 drop(兜底——①若已判越界整帖 is_empty 就不会到这;这里拦"混在 in-scope 里的越界颗")。按两轴判,任一不过即 drop:**
 1. **轴A 不过·产出物不是内容**:这颗教你做出来的是"题材本身"而非"关于它的内容"——游戏玩法/关卡设计、产品开发、电商/运营/选品、行业分析等 → drop(同样讲游戏:做游戏=drop,做游戏视频=收)。

+ 2 - 2
开发文档/正式版开发文档.md

@@ -123,7 +123,7 @@ app/ 或 web/
 
 正式职责:
 
-- query 生成与筛选
+- query 生成。
 - 平台搜索。
 - 详情抓取。
 - 媒体稳定化,正式版以 OSS/CDN URL 为主。
@@ -132,7 +132,7 @@ app/ 或 web/
 
 这里的粗分类不是最终知识入库,它只是为了避免把明显非创作内容送进昂贵的单帖解构。
 
-证据:`acquisition/runner.py:16` 定义正式默认三平台;`acquisition/queries/builder.py` 与 `acquisition/queries/filter.py` 承接 query 生成和筛选;`acquisition/classification/coarse.py:76-130` 做粗分类;`acquisition/repositories/postgres.py` 负责把 run/job/item/classification 写入正式库。
+证据:`acquisition/runner.py:16` 定义正式默认三平台;`acquisition/queries/builder.py` 承接 query 生成;query LLM 预筛已从正式链路移除;`acquisition/classification/coarse.py:76-130` 做粗分类;`acquisition/repositories/postgres.py` 负责把 run/job/item/classification 写入正式库。
 
 ### 4.3 `decode_content`
 

+ 6 - 6
开发文档/正式版施工文档.md

@@ -385,23 +385,23 @@ rg "acquisition.store|CK_SQLITE_PATH|data/app.db" core acquisition decode_conten
 
 ## 4. 改造 Acquisition
 
-### 4.1 Query 生成与筛选
+### 4.1 Query 生成
 
 移动:
 
 - `scripts/build_creation_demo.py` 核心逻辑 -> `acquisition/queries/builder.py`
-- `acquisition/query_filter.py` -> `acquisition/queries/filter.py`
 
 变化:
 
 - 不再默认写 `data/queries/creation_demo.json`。
 - 生成结果写入云端 `query_batches` 和 `queries`。
-- 记录 query 生成方式、筛选结果、筛选 prompt 版本。
+- 记录 query 生成方式和 active family。
+- query LLM 预筛逻辑已从正式链路移除,所有生成 query 默认进入搜索。
 
 保留:
 
 - 当前正交组合思路。
-- `keep/reason/valid/relevant` 这类业务判断
+- `keep/filter_reason` 作为通用执行开关和人工备注字段保留,不再代表 LLM 预筛结果
 
 ### 4.2 平台搜索与详情抓取
 
@@ -507,7 +507,7 @@ scripts/run_acquisition.py
 | 对象 | 本步骤处理方式 | 后续删除条件 |
 |---|---|---|
 | `scripts/build_creation_demo.py` | 保留为正式薄入口 | 不删;它已不再默认写本地 JSON |
-| `acquisition/query_filter.py` | 保留为 shim | 等旧脚本全部改 import 后,可移除 |
+| `acquisition/query_filter.py` / `acquisition/queries/filter.py` / `acquisition/query_filter.txt` | 已删除 | query LLM 预筛不再属于正式链路 |
 | `scripts/run_acquisition.py` | 保留 | 正式采集 CLI |
 | `acquisition/runner.py` | 保留 | 正式采集 runner |
 | `scripts/run_creation_search.py` | 暂时保留为 legacy | 正式 API/前端不再读 legacy summary,真实 query batch 跑通后归档 |
@@ -530,7 +530,7 @@ rg "creation_demo.json|data/app.db|CK_SQLITE_PATH|acquisition.store" acquisition
 本轮已完成第一版 Acquisition 正式链路拆分,没有删除 legacy SQLite runner:
 
 - 已新增 `acquisition/queries/builder.py`,承接原 `scripts/build_creation_demo.py` 的正交 query 生成核心。
-- 已新增 `acquisition/queries/filter.py`,`acquisition/query_filter.py` 退为兼容 shim
+- query LLM 预筛后续已移除:`acquisition/queries/filter.py`、`acquisition/query_filter.py`、`acquisition/query_filter.txt` 不再保留
 - 已将 `scripts/build_creation_demo.py` 改成薄入口:默认只打印摘要;只有显式 `--export-json` 才导出本地 JSON,只有显式 `--persist` 才写正式 PG。
 - 已新增三平台 adapter:`acquisition/platforms/xiaohongshu.py`、`weixin.py`、`douyin.py`。
 - 已新增 `acquisition/media/service.py`,统一把图片/视频转成可入库的媒体记录;OSS 失败时保留原 URL,不丢候选 item。

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini