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
@@ -7,7 +7,7 @@ import { CAPABILITY_CATALOG, type CapabilitySpec, type KnownCapabilityId } from
|
||||
import { Id, JsonObject, NonNegInt } from "./common.ts";
|
||||
import { MediaEntity, MediaKind } from "./content.ts";
|
||||
import { AcquisitionPlan, DownloadStatus, DownloadTask, Task, TaskStatus } from "./execution.ts";
|
||||
import { ExternalEntityType, ExternalIdentity } from "./identity.ts";
|
||||
import { EnrichmentStatus, EnrichSuggestion, EnrichUsage, ExternalEntityType, ExternalIdentity } from "./identity.ts";
|
||||
import { ServerMapping } from "./ops.ts";
|
||||
import { OrganizationAction, OrganizationRecord } from "./organization.ts";
|
||||
import {
|
||||
@@ -773,6 +773,38 @@ export const WebFetchOutput = z.object({
|
||||
text: z.string(),
|
||||
truncated: z.boolean(),
|
||||
bytes: NonNegInt,
|
||||
/** 页面 og:image(绝对地址);没有时为 null */
|
||||
image: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
// META-1 metadata.enrich:TMDB 没有的作品(短剧等)按标题查网页、抽字段,输出带证据的建议(不落作品表)
|
||||
export const MetadataEnrichInput = z
|
||||
.object({
|
||||
/** 待识别池条目 id(按它的解析标题 / 年份查) */
|
||||
poolEntryId: Id.optional(),
|
||||
title: z.string().trim().min(1).max(200).optional(),
|
||||
year: z.number().int().min(1900).max(2100).optional(),
|
||||
/** 额外检索词,如平台名、主演 */
|
||||
hints: z.string().trim().max(200).optional(),
|
||||
/** 忽略归一化标题缓存(7 天)重新查,计入每日预算 */
|
||||
fresh: z.boolean().optional(),
|
||||
})
|
||||
.refine((v) => !!v.poolEntryId !== !!v.title, { message: "poolEntryId 与 title 必须且只能给一个" });
|
||||
export const MetadataEnrichOutput = z.object({
|
||||
enrichmentId: Id,
|
||||
status: EnrichmentStatus,
|
||||
/** 命中归一化标题缓存:没有新的搜索 / 读取 / 模型调用 */
|
||||
cached: z.boolean(),
|
||||
normalizedTitle: z.string(),
|
||||
query: z.object({ title: z.string(), year: z.number().int().nullable(), hints: z.string().nullable() }),
|
||||
suggestion: EnrichSuggestion.nullable(),
|
||||
error: z.string().nullable(),
|
||||
usage: EnrichUsage,
|
||||
budget: z.object({ usedToday: NonNegInt, dailyLimit: NonNegInt }),
|
||||
/** 同名去重:标题 / 别名相同的已有作品(应用时可选择并入) */
|
||||
existing: z.array(z.object({ mediaEntityId: Id, title: z.string(), year: z.number().int().nullable() })),
|
||||
appliedMediaEntityId: Id.nullable(),
|
||||
notice: z.string(),
|
||||
});
|
||||
|
||||
/** POST /websearch/test */
|
||||
@@ -828,6 +860,7 @@ export const CAPABILITY_IO = {
|
||||
"agent.propose_options": { input: AgentProposeOptionsInput, output: AgentProposeOptionsOutput },
|
||||
"web.search": { input: WebSearchInput, output: WebSearchOutput },
|
||||
"web.fetch": { input: WebFetchInput, output: WebFetchOutput },
|
||||
"metadata.enrich": { input: MetadataEnrichInput, output: MetadataEnrichOutput },
|
||||
} as const satisfies Record<KnownCapabilityId, { input: z.ZodType; output: z.ZodType }>;
|
||||
|
||||
type CapabilityIo = typeof CAPABILITY_IO;
|
||||
@@ -913,3 +946,5 @@ export type WebSearchInput = z.infer<typeof WebSearchInput>;
|
||||
export type WebSearchOutput = z.infer<typeof WebSearchOutput>;
|
||||
export type WebFetchInput = z.infer<typeof WebFetchInput>;
|
||||
export type WebFetchOutput = z.infer<typeof WebFetchOutput>;
|
||||
export type MetadataEnrichInput = z.infer<typeof MetadataEnrichInput>;
|
||||
export type MetadataEnrichOutput = z.infer<typeof MetadataEnrichOutput>;
|
||||
Reference in new issue
Block a user