// triplet-extract.ts — LLM 三元組萃取 + 寫入工具函數 // 萃取=純 LLM(不碰 DB);寫入=走基本盤 API(零 SQL / 零 D1)。 // 供 block-ingest.ts 和 block-process.ts 共用 import type { KbdbClient } from '../lib/kbdb-client'; import { createTriplet, queryTriplets } from './triplet-crud'; const EXTRACT_PROMPT = `你是知識萃取助手,從文章中萃取知識三元組。 只萃取:性格特質、關鍵經歷精華、核心觀點信念、說話方式與風格。 禁止:具體年份日期、純事實統計、流水帳年表。 格式:[{"subject":"...","predicate":"2-6字","object":"15-50字","confidence":0.8}] 直接輸出 JSON Array,第一字元 [,最後字元 ]。不要其他文字。`; export interface LLMTriplet { subject: string; predicate: string; object: string; confidence: number; } /** Workers AI 萃取三元組,每段獨立呼叫,單段失敗不中斷 */ export async function extractTripletsViaLLM(ai: Ai, chunks: string[]): Promise { const results: LLMTriplet[] = []; for (const chunk of chunks) { if (!chunk.trim()) continue; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const raw = await (ai as any).run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', { messages: [ { role: 'system', content: EXTRACT_PROMPT }, { role: 'user', content: `萃取三元組:\n${chunk}` }, ], max_tokens: 512, temperature: 0.1, }); const text = (typeof raw === 'string' ? raw : (raw?.response ?? '')) .replace(/[\s\S]*?<\/think>/g, '').trim(); const m = text.match(/\[[\s\S]*\]/); if (!m) continue; const parsed = JSON.parse(m[0]); if (!Array.isArray(parsed)) continue; for (const t of parsed) { const s = String(t?.subject ?? '').trim(); const p = String(t?.predicate ?? '').trim(); const o = String(t?.object ?? '').trim(); if (s && p && o) results.push({ subject: s, predicate: p, object: o, confidence: Number(t?.confidence) || 0.8 }); } } catch { /* 單段失敗跳過 */ } } return results; } /** 寫入一條三元組(走基本盤 API),已存在回傳 false,新寫入回傳 true。 * 查重 + 寫入全走 KbdbClient → triplet-crud,零 SQL / 零 D1。 */ export async function writeTripletToDb( client: KbdbClient, t: { subject: string; predicate: string; object: string; confidence?: number }, owner: string, // 必經:萃取寫入必帶 owner(不再收 null → 不會 owner_id: undefined) ): Promise { // 查重:以 S-P-O 三欄精確比對(queryTriplets 取 template record 後在插件層 filter) const { count } = await queryTriplets(client, { subject: t.subject, predicate: t.predicate, object: t.object, limit: 1, }); if (count > 0) return false; await createTriplet(client, { subject: t.subject, predicate: t.predicate, object: t.object, confidence: t.confidence ?? 0.8, owner_id: owner, }); return true; }