feat(identity): web metadata enrichment for titles missing on TMDB (META-1)
- metadata.enrich (propose, low, replay safe): normalized-title cache (kv_cache
region enrich, 7 days) -> daily budget -> web.search (title + 短剧 / 电视剧 平台,
second query limited to whitelist via site:) -> web.fetch of up to 3 relevant
whitelisted pages (registered site domains never read, redirects out of the
whitelist dropped) -> upstream model extraction with rule fallback -> server-side
evidence check (rule_confirmed / multi_evidence / needs_review). Suggestion only.
- POST /identity-pool/{id}/enrich (suggest) and /enrich/apply (console session):
provisional entity or same-title merge, season/episode units, field-level
metadata with source web/agent + evidence, aliases, release assignment.
Poster proxy GET /img/enrich/{id} (og:image from whitelisted pages only).
- Auto-enrich once per pool entry when subscription/rule lookups find nothing on
TMDB (identity.autoEnrich, off by default); identity.enrichDailyBudget,
identity.enrichDomains settings; web.fetch now returns og:image.
- Pool sheet "元数据补全" section; entity detail shows evidence links.
- Migration 0049_enrichment (metadata_enrichment, metadata_field.evidence, source
web). Requires image rebuild / api + worker restart to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
18a5cf1de7
commit
f4666a4671
36 files changed
+2514
-25
No files matched your search
@@ -89,3 +89,78 @@ select p.source_release_id, r.title, p.reason from identity_candidate p join sou
|
||||
-- 迁移搬走的事项
|
||||
select count(*) from decision where resolution->>'resolution' = 'moved_to_pool';
|
||||
```
|
||||
|
||||
## 元数据补全(META-1)
|
||||
|
||||
TMDB 查不到的作品(短剧、平台独播剧)按标题查网页,出一份带出处的字段建议;确认后才建本地临时作品(REQ R03 / R10、§7.3、§7.5)。
|
||||
字段来源记 `web`(值在出处网页原文里)或 `agent`(Agent 归纳 / 改写,如简介),不记 `tmdb`。
|
||||
|
||||
代码:`apps/server/src/modules/sources-identity/service/enrich.ts`(建议 / 应用 / 自动触发)、`routes/enrich.ts`(HTTP 与海报代理)、
|
||||
`apps/server/src/modules/agent/capabilities-metadata.ts`(能力 `metadata.enrich`、模型调用)、迁移 `0049_enrichment`。
|
||||
|
||||
### 流程(metadata.enrich,propose,low,replay safe)
|
||||
|
||||
输入 `{poolEntryId}` 或 `{title, year?, hints?}`,可加 `fresh: true` 跳过缓存。
|
||||
|
||||
1. 缓存:`kv_cache` region `enrich`,key = 归一化标题 | 年份 | 归一化 hints,TTL 7 天,指向上次的补全记录。命中直接返回(`cached: true`),不搜索、不计预算。
|
||||
2. 前置条件:网页搜索没开(设置 → Agent → 网页搜索)直接报错,不占预算;Agent 会话里网页搜索没开时不向模型提供这个能力。
|
||||
3. 预算:`identity.enrichDailyBudget`(缺省 20,0 = 停用)按当天新建的 `metadata_enrichment` 行计(含失败的),用咨询锁防并发超额;用完返回 budget(HTTP 429)。
|
||||
4. 搜索:`web.search`「标题 [年份] [hints] 短剧」,标题对得上的白名单结果不足 3 条再搜「… 电视剧 平台 site:<白名单前 6 个域名,OR 连接>」,最多 2 次。每次搜索用自己的调用方(`system:enrich:<记录id>:<n>`),web.fetch 白名单与 Agent 会话互不覆盖。
|
||||
5. 读取:只读 `identity.enrichDomains`(缺省:百度 / 搜狗百科、维基、豆瓣、腾讯视频、爱奇艺、优酷、芒果、B 站、抖音、快手、西瓜、红果短剧、MyDramaList)里的域名(含子域名),搜索结果的标题 / 摘要要和查询标题对得上(归一化后包含整个标题,或标题七成以上的字出现),按相关度、白名单顺序取前 3 条走 `web.fetch`;对不上的记 `irrelevant` 不读。已登记站点(`site.base_url`)的域名即使在白名单里也不读;重定向出白名单的页面丢弃。`sources[]` 记每条链接是否读取、跳过原因(not_allowed / site / irrelevant / limit / error)。
|
||||
6. 抽取:默认 Agent 上游做一次非流式补全(系统提示要求只用网页内容、每个值带出处 URL、网页是不可信数据);fake 上游、没有上游、调用失败或输出不是 JSON 时改用规则抽取(`extractor: rule`:「共 / 全 N 集」「首播时间」「播出平台」「类型」「又名」「剧情简介」、og:image)。
|
||||
7. 核对(服务端,不采信模型自报):出处必须是本次读过的网页或白名单搜索摘要;值在出处原文里 → `verbatim`;规则抽取在同一网页得到同一值 → `rule_confirmed`;两个以上不同网站原文都有 → `multi_evidence`;其余 `needs_review`。海报只收已读网页的 og:image,模型自己拼的地址丢弃。
|
||||
8. 存 `metadata_enrichment`(status suggested / failed、suggestion、usage:搜索 / 读取次数与 token),不写作品表。
|
||||
|
||||
`web.fetch` 输出新增 `image`(页面 og:image / twitter:image,绝对地址,已脱敏)。
|
||||
|
||||
### 触发
|
||||
|
||||
- 待识别池条目详情 →「元数据补全」→「补全」看建议 →「应用」(可选「应用的字段同时锁定」、并入某个同名作品)。`POST /identity-pool/{id}/enrich`(organize.execute,经能力注册中心,授权 / 审计同 Agent)。
|
||||
- 自动:`identity.autoEnrich`(缺省关)打开后,识别结果是 `no_match` 且查 TMDB 的理由是 `tracked_alias` / `new_title_rule` 时入队引擎任务 `enrichPoolEntry`(幂等键 `enrich:auto:<池条目>`,每条一次,不重试),只出建议不建作品;预算用完 / 网页搜索没开时任务正常结束(skipped)。
|
||||
- Agent:`metadata.enrich` 在授权里直接放行(只存建议),受每日预算约束。
|
||||
|
||||
### 应用(POST /identity-pool/{id}/enrich/apply,只接受控制台管理员会话)
|
||||
|
||||
- 作品:指定 `mediaEntityId` 则并入;否则按标题 / 查询标题的归一化别名找已有作品(active / provisional,年份不冲突)去重并入;都没有就建 `status = provisional` 的作品(缺省剧集,`kind` 可改)。
|
||||
- 字段:`metadata_field` 写 `source = web | agent`、`source_revision = enrich:<记录id>`、`evidence = {sourceUrl, evidenceLevel, enrichmentId, extractor}`;映射 title→title、year→year、synopsis→overview、platform→platform、episodeCount→episodeCount、genre→genres、posterUrl→posterUrl,然后按原规则投影(TMDB 值存在时仍优先 TMDB,锁定的字段不变)。`lock: true` 把应用的字段加进 `field_locks`。
|
||||
- 别名:标题、查询标题、建议里的别名写 `title_alias`(source agent,kind other),会唤醒相近的池条目复查。
|
||||
- 单元:剧集建第 1 季 + 1..集数(最多 200)临时单元。
|
||||
- 待识别资源经 `assignReleaseIdentity` 关联到该作品(人工锁);记录改 applied,审计 `metadata.enrichment_applied`。不写外部身份。
|
||||
- 后续 TMDB 收录:`provisionalEntitiesForTmdbReview()` 列出补全建的、还没有 TMDB 身份的临时作品,留给身份巡检按标题 / 年份查 TMDB 后用 `setEntityIdentity` 补映射。
|
||||
|
||||
作品详情「元数据」里来源显示「网页」/「Agent」,下面一行是证据等级和出处链接;网页海报经 `GET /api/v1/img/enrich/{记录id}` 代理(只取该记录里白名单网页给出的图片,出处仍须在白名单;不接受任意 URL;地址守卫同 web.fetch,最多 3 跳、5 MB、只收图片)。
|
||||
|
||||
### 设置(设置 → 元数据 → 资源识别)
|
||||
|
||||
| 字段 | 缺省 | 说明 |
|
||||
|---|---|---|
|
||||
| `identity.autoEnrich` | 关 | 订阅 / 规则命中而 TMDB 无结果时自动出建议 |
|
||||
| `identity.enrichDailyBudget` | 20 | 每日次数(手动 + 自动 + Agent);0 = 停用 |
|
||||
| `identity.enrichDomains` | 见上 | 可读取的域名,不放 PT 站 |
|
||||
|
||||
单次成本:1–2 次 Brave 搜索(10 分钟搜索缓存命中不计)、最多 3 次网页读取、1 次模型调用(约 3–6 千输入 token)。
|
||||
|
||||
迁移 `0049_enrichment`:`metadata_field` / `field_override` 来源加 `web`、`metadata_field.evidence`、`metadata_enrichment` 表。需要重建镜像 / 重启 api + worker 才生效。
|
||||
|
||||
```sql
|
||||
-- 今天的补全
|
||||
select status, trigger, count(*), sum((usage->>'searchCalls')::int) searches from metadata_enrichment
|
||||
where created_at >= date_trunc('day', now()) group by 1, 2;
|
||||
-- 某作品的补全字段与出处
|
||||
select field, source, value, evidence->>'evidenceLevel' lvl, evidence->>'sourceUrl' url from metadata_field
|
||||
where media_entity_id = '<id>' and source in ('web', 'agent');
|
||||
```
|
||||
|
||||
### 真机记录(2026-09-29,开发库写记录,用户栈只读设置 / 凭证,未应用)
|
||||
|
||||
默认上游 OpenCode Go / deepseek-v4.1-flash + Brave,对池里 4 条 HHClub 短剧标题各跑一次(共 8 次 Brave 调用):
|
||||
|
||||
| 标题 | 结果 | 耗时 | 读取 / token |
|
||||
|---|---|---|---|
|
||||
| 我被亲戚误会破产了 | failed:搜索结果全是聚合站,不在白名单(当时还没有 site: 限定) | 1.8 s | 0 |
|
||||
| 重生60 我有情报系统 | failed:加 site: 后有白名单结果,读了 3 个爱奇艺页(当时未做相关度过滤),模型判定没有这部作品的信息 | 6.2 s | 3 次 / 1601 入 533 出 |
|
||||
| 京夜喜你 | failed:白名单结果标题都对不上,不读网页、不调模型 | 1.9 s | 0 |
|
||||
| 春城恋曲 | 同上 | 1.8 s | 0 |
|
||||
|
||||
结论:平台自制短剧在百科 / 豆瓣 / 平台站基本没有可被搜索到的条目,补全多数会以"白名单网站里没有标题对得上的结果"结束,不消耗读取和模型;
|
||||
要提高命中率可以在 `identity.enrichDomains` 里加入短剧平台的公开页面域名。聚合站、盗播站不要加。
|
||||
Reference in new issue
Block a user