const fs = require('node:fs') const path = require('node:path') const assert = require('node:assert/strict') const root = path.resolve(__dirname, '../..') const ids = ['16c3e59c-4bb6-41cf-ae0a-6d0941b3d032', '20b9280e-ed73-4700-8fa5-944d6ae6dd5f', 'bb86b44a-576b-4bf8-a6bf-8f1baca3a845', 'badd084b-f229-4005-8fb3-196fbe139fe0', '5d28a7e5-e00e-44a7-a0e1-c72342cdcf68', '3ae9257d-928a-4886-8101-7cfd0ed8c27c'] let token async function api(route, method = 'GET', body) { const response = await fetch(`http://127.0.0.1:18080/api/v1${route}`, { method, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}`, 'X-Tenant-ID': '10059' } : {}) }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(30000) }) const result = await response.json() assert.ok(response.ok && result.success !== false, `${method} ${route}: HTTP ${response.status}`) return result } async function main() { const credentials = JSON.parse(fs.readFileSync(path.join(root, 'acceptance/super-admin-login.json'), 'utf8')) const login = await api('/auth/login', 'POST', { username: credentials.username, password: credentials.password }) token = (login.data || login).token const selected = process.argv.includes('--product-only') ? ids.slice(0, 1) : ids const before = [] for (const id of selected) before.push((await api(`/knowledge-bases/${id}`)).data) if (!process.argv.includes('--apply')) { console.log(JSON.stringify(before.map(kb => ({ name: kb.name, enabled: !!kb.extract_config?.enabled, initialized: !!(kb.embedding_model_id && kb.summary_model_id) })))) return } fs.writeFileSync(path.join(root, 'acceptance', `kb-graph-config-before-${Date.now()}.json`), JSON.stringify(before, null, 2), { mode: 0o600, flag: 'wx' }) for (const kb of before) { assert.ok(kb.embedding_model_id && kb.summary_model_id, 'Models must be configured first') const config = (await api(`/initialization/config/${kb.id}`)).data const request = { ...config, llmModelId: kb.summary_model_id, embeddingModelId: kb.embedding_model_id, nodeExtract: { enabled: true, text: '基础概念是实践练习的先修条件。', tags: ['先修条件', '包含', '适用于', '条件适用于', '禁用于', '慎用于', '不适用于', '不能用于', '关联', '导致', '不导致', '约束', '对比', '支持', '不支持', '抑制'], nodes: [{ name: '基础概念', attributes: ['概念'] }, { name: '实践练习', attributes: ['方法'] }], relations: [{ node1: '基础概念', node2: '实践练习', type: '先修条件', evidence: '基础概念是实践练习的先修条件。' }], customInstructions: '按当前原文的实际主题提取有依据的实体和关系,不根据知识库名称预设领域。每条关系的 evidence 必须逐字引用连续原文,保留主体、时间、否定、条件、观点归属和不确定性。叙事和个人观点不能改写成通用因果;先修条件按前提指向后续目标;约束须保留完整动作。产品使用标签只适用于明确的产品使用说明,不同产品分节不得串用关系。不要把格式示例写入实际图谱,不要补造原文没有的关系;确实没有关系时如实返回,交由覆盖复核。', }, } await api(`/initialization/config/${kb.id}`, 'PUT', request) await api(`/knowledge-bases/${kb.id}`, 'PUT', { name: kb.name, description: kb.description, config: { chunking_config: kb.chunking_config, image_processing_config: kb.image_processing_config, faq_config: kb.faq_config, auto_tag_config: kb.auto_tag_config, wiki_config: kb.wiki_config, indexing_strategy: { ...kb.indexing_strategy, graph_enabled: true } } }) const after = (await api(`/knowledge-bases/${kb.id}`)).data assert.equal(after.extract_config.enabled, true) assert.equal(after.indexing_strategy.graph_enabled, true) assert.equal(after.embedding_model_id, kb.embedding_model_id) assert.equal(after.summary_model_id, kb.summary_model_id) assert.equal(after.storage_backend_id, kb.storage_backend_id) assert.deepEqual(after.wiki_config, kb.wiki_config) assert.deepEqual(after.chunking_config, kb.chunking_config) } console.log(JSON.stringify({ configured: before.map(kb => kb.name), existing_indexes_preserved: true, graph_build_complete: false })) } main().catch(error => { console.error(error.message); process.exitCode = 1 })