4cc7057cea
修「打標≠讀標」:node gloss+embed 標在 entity record、但 base embed 只掃 entries.metadata.embed → persistNodes 另落一筆 content=canonical:gloss、metadata.embed=true 的 base entry(走 API/零SQL)。 - gloss-entry.ts: upsertGlossEntry 冪等(確定性 page_name + list-then-write) - backfill-gloss-entries.ts + route: 對既有 entity record 補 gloss entry - triplet-ingest 帶 source.uri 進 metadata;kbdb-client 暴露 metadata_json(base 早支援) - vitest 30/30(+7);[→arcrun] base 缺 upsert entry 端點,暫以 list-then-write 冪等
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
// node 層打標落地 — 把 envelope nodes[] 的向量化打標(embed/gloss/aliases)存進 entity slot。
|
||
// 向量化分工(ingest#1 升格,2026-06-26):ingest 打標、base/KBDB embed 模組讀標執行;graph 不算向量。
|
||
// 鐵律:走 base API(API-as-Wall)、零 SQL。
|
||
|
||
import type { KbdbClient } from '../lib/kbdb-client';
|
||
import { TPL_ENTITY, ensurePluginTemplates } from '../lib/templates';
|
||
import { upsertGlossEntry } from './gloss-entry';
|
||
|
||
export type IngestNode = {
|
||
name: string;
|
||
id?: string;
|
||
aliases?: string[];
|
||
gloss?: string;
|
||
embed?: boolean;
|
||
entity_type?: string;
|
||
};
|
||
|
||
/**
|
||
* 把 node 層打標存進 entity record,供 base embed 模組讀標執行 embedding。
|
||
* 去重:以 id(無則 name)為鍵,同鍵在這批內只存一筆——wikilink 卡被多條邊指到仍是一個 node。
|
||
* graph 不做 embedding,只負責透傳/落地打標。
|
||
*/
|
||
export async function persistNodes(
|
||
client: KbdbClient,
|
||
nodes: IngestNode[],
|
||
owner_id?: string,
|
||
source?: string, // envelope source.uri,帶進 gloss entry 的 metadata.source(供 base backfill 依 source 過濾)
|
||
): Promise<void> {
|
||
if (!nodes || nodes.length === 0) return;
|
||
await ensurePluginTemplates(client);
|
||
|
||
const seen = new Set<string>();
|
||
for (const n of nodes) {
|
||
const key = (n.id ?? n.name).toLowerCase().trim();
|
||
if (seen.has(key)) continue; // 同卡多邊指到 → 只存一次
|
||
seen.add(key);
|
||
await client.createRecord(
|
||
TPL_ENTITY,
|
||
{
|
||
canonical: n.name,
|
||
node_id: n.id ?? '',
|
||
aliases_json: JSON.stringify(n.aliases ?? []),
|
||
entity_type: n.entity_type ?? '',
|
||
gloss: n.gloss ?? '',
|
||
// contract 預設 true;只在明確 false 時存標(base 看 'false' 跳過 embed)。
|
||
embed: n.embed === false ? 'false' : 'true',
|
||
owner: owner_id ?? '',
|
||
},
|
||
owner_id,
|
||
);
|
||
|
||
// 另落一筆 embeddable base entry(metadata.embed=true)——record 的 gloss 標 base embed 讀不到,
|
||
// 必須也落成 entry base embed 模組才掃得到(打標≠讀標的修補)。只在要 embed(embed !== false)時落;
|
||
// 空 gloss 由 upsertGlossEntry 自行跳過。冪等:同 node 重複 ingest 不造重複 entry。
|
||
if (n.embed !== false) {
|
||
await upsertGlossEntry(client, { canonical: n.name, node_id: n.id, gloss: n.gloss, source }, owner_id);
|
||
}
|
||
}
|
||
}
|