Files

77 lines
6.5 KiB
JavaScript
Raw Permalink Normal View History

const fs = require('node:fs')
const assert = require('node:assert/strict')
const root = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance'
const statePath = `${root}/wiki-rag-pilot-20260916.json`
const base = 'http://127.0.0.1:18080/api/v1'
async function main() {
const c = JSON.parse(fs.readFileSync(`${root}/super-admin-login.json`, 'utf8'))
const login = await fetch(`${base}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: c.email, password: c.password }) })
assert.equal(login.status, 200)
const auth = await login.json()
const request = async (route, method = 'GET', body) => {
const response = await fetch(`${base}${route}`, { method, headers: { Authorization: `Bearer ${auth.token}`, 'Content-Type': 'application/json' }, ...(body ? { body: JSON.stringify(body) } : {}), signal: AbortSignal.timeout(90000) })
const value = await response.json()
if (!response.ok) throw new Error(`${method} ${route}: ${response.status} ${JSON.stringify(value)}`)
return value
}
let state = fs.existsSync(statePath) ? JSON.parse(fs.readFileSync(statePath, 'utf8')) : { created_at: new Date().toISOString(), documents: [] }
const save = () => fs.writeFileSync(statePath, JSON.stringify(state, null, 2))
if (process.argv.includes('--create')) {
if (!state.kb_id) {
const result = await request('/knowledge-bases', 'POST', {
name: 'Wiki + RAG 小规模验证', description: '两篇非业务示例资料,用于验证 Wiki 自动整理与原文检索并存。', type: 'document',
embedding_model_id: 'a2c3ce32-6192-46c8-8072-910abfe8afed', summary_model_id: '404e005b-83ae-5e2e-a3b3-999f9d5cd47e',
indexing_strategy: { vector_enabled: true, keyword_enabled: true, wiki_enabled: true, graph_enabled: false },
wiki_config: { synthesis_model_id: '404e005b-83ae-5e2e-a3b3-999f9d5cd47e', max_pages_per_ingest: 4, extraction_granularity: 'focused' },
})
state.kb_id = result.data.id
state.url = `http://127.0.0.1:15173/platform/knowledge-bases/${state.kb_id}`
save()
}
const documents = [
{ title: '示例知识库的资料管理约定', content: '# 示例知识库的资料管理约定\n\n本资料只用于 Wiki 与原文检索试点,不代表真实业务规定。\n\n示例资料库的入库流程分为上传、解析、索引和验收四步。上传负责人先填写资料标题和版本号。解析完成后,系统保存原始文档,并建立关键词索引及向量索引。管理员抽查三条典型问题,确认回答可引用原文后,才把该资料列为验收通过。\n\n本试点的示例归档编号为 WK-PILOT-916。该编号只是定位测试资料的识别码,不是访问密钥。原文是事实来源,Wiki 是跨资料整理的说明层,两者同时保留。更新资料时应注明版本和修改原因。' },
{ title: '示例知识库的回答核对约定', content: '# 示例知识库的回答核对约定\n\n本资料只用于 Wiki 与原文检索试点,不代表真实业务规定。\n\n资料管理约定要求验收前抽查三条典型问题。回答核对时,优先从 Wiki 查找术语和流程概述,再用原始资料核对编号、步骤及具体条件。若 Wiki 与原文不一致,应引用原文并标明差异,不能自行补造结论。\n\n本试点的检查顺序为先确认资料标题,再确认版本号,最后确认引用段落。归档编号 WK-PILOT-916 关联本试点的资料管理约定。完成核对后,由验收人员记录通过或待修改结果;待修改资料应修订后再次抽查。' },
]
for (const doc of documents) {
if (state.documents.some(item => item.title === doc.title)) continue
const result = await request(`/knowledge-bases/${state.kb_id}/knowledge/manual`, 'POST', { ...doc, status: 'publish' })
state.documents.push({ id: result.data.id, title: doc.title })
save()
}
}
assert.ok(state.kb_id)
const docs = await request(`/knowledge-bases/${state.kb_id}/knowledge?page=1&page_size=20`)
const stats = await request(`/knowledgebase/${state.kb_id}/wiki/stats`)
const pages = await request(`/knowledgebase/${state.kb_id}/wiki/pages?page=1&page_size=30`)
state.checked_at = new Date().toISOString()
state.statuses = docs.data.map(k => ({ id: k.id, title: k.title, parse_status: k.parse_status, error_message: k.error_message, pending_subtasks_count: k.pending_subtasks_count }))
state.wiki_stats = stats
state.wiki_pages = pages
if (process.argv.includes('--verify')) {
assert.ok(state.statuses.every(k => k.parse_status === 'completed'))
assert.ok(stats.total_pages > 0)
const timed = async (route, method, body) => {
const started = performance.now()
const response = await request(route, method, body)
return { elapsed_ms: Math.round(performance.now() - started), response }
}
const wiki = await timed(`/knowledgebase/${state.kb_id}/wiki/search?q=WK-PILOT-916&limit=5`)
const rag = await timed(`/knowledge-bases/${state.kb_id}/hybrid-search`, 'POST', { query_text: 'WK-PILOT-916 的资料入库流程和验收要求是什么', match_count: 5, vector_threshold: 0, keyword_threshold: 0 })
const wikiItems = wiki.response.pages || wiki.response.data || []
const ragItems = rag.response.data || []
assert.ok(wikiItems.length > 0)
assert.ok(ragItems.length > 0)
assert.ok(JSON.stringify(wikiItems).includes('WK-PILOT-916'))
assert.ok(JSON.stringify(ragItems).includes('WK-PILOT-916'))
const sourceIDs = new Set(state.documents.map(d => d.id))
const linked = pages.pages.filter(p => p.page_type !== 'index')
assert.ok(linked.every(p => p.source_refs?.some(id => sourceIDs.has(id))))
state.verification = { wiki_search_ms: wiki.elapsed_ms, wiki_results: wikiItems.length, rag_search_ms: rag.elapsed_ms, rag_results: ragItems.length, grounded_pages: linked.length, source_documents_preserved: true, query_contains_expected_identifier: true }
state.url = `http://127.0.0.1:15173/platform/knowledge-bases/${state.kb_id}?tab=wiki`
}
save()
console.log(JSON.stringify({ kb_id: state.kb_id, url: state.url, statuses: state.statuses, stats: { total_pages: stats.total_pages, pages_by_type: stats.pages_by_type, total_links: stats.total_links, pending_tasks: stats.pending_tasks, is_active: stats.is_active }, verification: state.verification, pages: pages.pages?.map(p => ({ slug: p.slug, title: p.title, type: p.page_type })) }, null, 2))
}
main().catch(error => { console.error(error.message); process.exitCode = 1 })