haixunMaster/lib/ai/filter-discover-relevance.ts

110 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
import { z } from "zod";
import type { ProviderApiKeys } from "./keys";
import { generateStructuredObject } from "./generate-structured";
import { getModel } from "./provider";
const filterSchema = z.object({
items: z.array(
z.object({
id: z.string(),
relevant: z.boolean(),
score: z.number().min(0).max(1),
reason: z.string(),
})
),
});
export interface DiscoverFilterItem {
id: string;
text: string;
username?: string;
source: string;
tags?: string[];
}
export interface DiscoverFilterResult {
relevant: boolean;
score: number;
reason: string;
}
export async function filterDiscoverItemsWithAi(input: {
label: string;
query: string;
brief?: string | null;
exclusions?: string[];
pillars?: string[];
requiredConcepts?: string[];
items: DiscoverFilterItem[];
aiProvider: string;
aiModel: string;
apiKeys?: ProviderApiKeys;
}): Promise<Map<string, DiscoverFilterResult>> {
const fallback = new Map<string, DiscoverFilterResult>();
for (const item of input.items) {
fallback.set(item.id, { relevant: true, score: 0.55, reason: "規則通過AI 未審核)" });
}
if (input.items.length === 0) return fallback;
try {
const model = getModel(input.aiProvider, input.aiModel, input.apiKeys ?? {});
const listBlock = input.items
.map((item) => {
const tags = item.tags?.length ? `\n標籤${item.tags.join("、")}` : "";
const user = item.username ? `\n帳號@${item.username}` : "";
return `[${item.id}](來源:${item.source}${user}${tags}\n內容${item.text.slice(0, 280)}`;
})
.join("\n\n");
const result = await generateStructuredObject({
model,
provider: input.aiProvider,
modelId: input.aiModel,
schema: filterSchema,
system: `你是 Threads 主題研究助理。任務:判斷搜尋結果是否與「指定主題」真正相關,用於找相似創作者帳號。
-
- vsvs
- relevant=false
- relevant=false
- score 0.4
idrelevantscore0-1reason`,
prompt: `【主題】${input.label}
${input.query}
${input.brief ? `【受眾簡述】${input.brief}` : ""}
${input.pillars?.length ? `【內容支柱】${input.pillars.join("、")}` : ""}
${input.requiredConcepts?.length ? `【必備概念(需同時符合)】${input.requiredConcepts.join(" + ")}` : ""}
${input.exclusions?.length ? `【明確排除】${input.exclusions.join("、")}` : ""}
${input.items.length}
${listBlock}`,
jsonPromptSuffix:
'\n\n只回傳 JSON{"items":[{"id":"...","relevant":true,"score":0.8,"reason":"..."}]}',
});
const mapped = new Map<string, DiscoverFilterResult>();
for (const row of result.items) {
mapped.set(row.id, {
relevant: row.relevant,
score: row.score,
reason: row.reason.slice(0, 120),
});
}
for (const item of input.items) {
if (!mapped.has(item.id)) {
mapped.set(item.id, { relevant: false, score: 0, reason: "AI 未回傳審核結果" });
}
}
return mapped;
} catch (error) {
console.warn("[filter-discover-relevance] AI filter failed, using rules only:", error);
return fallback;
}
}