// 三元組 CRUD — 走基本盤 API(API-as-Wall,零 SQL) // 寫 triplet = 確保 template='triplet' + POST /records 填 slot。 // 查 triplet = GET /records/by-template/triplet → 插件層 filter/組裝。 import type { Triplet } from '../types'; import type { KbdbClient } from '../lib/kbdb-client'; import { TPL_TRIPLET, ensurePluginTemplates, recordToTriplet } from '../lib/templates'; import { classifyClusters } from './triplet-cluster'; export type CreateTripletData = { subject: string; predicate: string; object: string; source_block_id?: string; confidence?: number; owner_id?: string; clusters?: string[]; bridge_score?: number; subject_entity_type?: string; object_entity_type?: string; source_uri?: string; content_hash?: string; source_anchor?: string; predicate_embed?: boolean; // 謂詞向量化打標(ingest 打標、base 讀標執行);graph 不算向量 }; /** 建立三元組 → POST /records(template=triplet)。 */ export async function createTriplet( client: KbdbClient, data: CreateTripletData, ): Promise<{ id: string; subject: string; predicate: string; object: string }> { await ensurePluginTemplates(client); const clusters = data.clusters ?? []; const bridgeScore = data.bridge_score ?? Math.max(0, clusters.length - 1); const values: Record = { subject: data.subject, predicate: data.predicate, object: data.object, confidence: String(data.confidence ?? 1.0), clusters_json: JSON.stringify(clusters), bridge_score: String(bridgeScore), status: 'active', }; if (data.source_block_id) values.source_block_id = data.source_block_id; if (data.subject_entity_type) values.subject_entity_type = data.subject_entity_type; if (data.object_entity_type) values.object_entity_type = data.object_entity_type; if (data.source_uri) values.source_uri = data.source_uri; if (data.content_hash) values.content_hash = data.content_hash; if (data.source_anchor) values.source_anchor = data.source_anchor; if (data.predicate_embed === false) values.predicate_embed = 'false'; // 謂詞向量化打標透傳,base 讀標執行 const id = await client.createRecord(TPL_TRIPLET, values, data.owner_id); return { id, subject: data.subject, predicate: data.predicate, object: data.object }; } export type TripletFilters = { subject?: string; predicate?: string; object?: string; limit?: number; offset?: number; owner_id?: string; entity_type?: string; includeDeprecated?: boolean; // 預設只回 active;rollback/考古才開(T3.5) }; /** 查三元組 → 取 template 全部 record,插件層 filter(base 無複合 slot 查詢)。 */ export async function queryTriplets( client: KbdbClient, filters: TripletFilters, ): Promise<{ triplets: Triplet[]; count: number }> { const records = await client.listRecordsByTemplate(TPL_TRIPLET, filters.owner_id); let triplets = records.map(recordToTriplet); // active-only:deprecated 不進圖遍歷/查詢(缺省 status 視為 active,相容舊資料)。 if (!filters.includeDeprecated) triplets = triplets.filter((t) => t.status === 'active'); if (filters.subject) triplets = triplets.filter((t) => t.subject === filters.subject); if (filters.predicate) triplets = triplets.filter((t) => t.predicate === filters.predicate); if (filters.object) triplets = triplets.filter((t) => t.object === filters.object); if (filters.entity_type) { triplets = triplets.filter( (t) => t.subject_entity_type === filters.entity_type || t.object_entity_type === filters.entity_type, ); } const offset = filters.offset ?? 0; const limit = Math.min(filters.limit ?? 50, 2000); const page = triplets.slice(offset, offset + limit); return { triplets: page, count: page.length }; } /** 取單一三元組 → GET /records/:id。 */ export async function getTriplet(client: KbdbClient, id: string): Promise { const rec = await client.getRecord(id); if (!rec) return null; return recordToTriplet({ ...rec, template: TPL_TRIPLET }); } // re-export clusters helper(AI 分群,純函式 + 走 client 無關) export { classifyClusters };