Files
kbdb-graph-plugin/src/actions/triplet-crud.ts
T
Claude 4190fbcf90 feat(graph): owner_id 寫入鏈必經(根治無主資料)——配合 base owner-mandatory(D28)
- kbdb-client requireOwner 守衛:漏 owner 插件端即 throw,不再靜默送 owner:''
- owner 一路 thread:persistNodes/ingestEnvelope/entity-crud/triplet-crud 全必填
- 病灶修:POST /triplets/ingest 原本沒傳 owner→加 owner_id query+400
- 寫入 route 缺 owner→400;vitest 23→27
- 註:gloss-bridge 分支的 backfill/gloss-entry 另需套 owner 必填(附說明),不跨支疊改
2026-07-05 11:00:30 +00:00

104 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 三元組 CRUD — 走基本盤 APIAPI-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; // 必經:三元組寫入必帶 ownercreateRecord 內 requireOwner 守衛)
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 /recordstemplate=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<string, string> = {
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; // 預設只回 activerollback/考古才開(T3.5
};
/** 查三元組 → 取 template 全部 record,插件層 filterbase 無複合 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-onlydeprecated 不進圖遍歷/查詢(缺省 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<Triplet | null> {
const rec = await client.getRecord(id);
if (!rec) return null;
return recordToTriplet({ ...rec, template: TPL_TRIPLET });
}
// re-export clusters helperAI 分群,純函式 + 走 client 無關)
export { classifyClusters };