知识库页不再把"退回本地哈希"藏起来:配了语义模型却没连上时说实话,也不再劝人重建

本机配的是 LM Studio 上的 text-embedding-qwen3-embedding-4b。服务启动时它没起,
load_setting 连不上,整个进程就一直用本地哈希——它后来起了也不会自己连上,原因只在
服务端日志里留一句。页面按"配的是不是 local"判断,于是:
- 不标"没有语义能力",看着一切正常,检索却一直是纯关键词;
- 高亮"重建索引(73 段)"——那些"对不上"的向量正是 qwen3 建好的,一点就拿哈希
  覆盖掉,连上之后还得再重建一遍。

改法:
1. 后端记下保存的配置为什么没生效(embeddings.unavailable_reason),状态接口多报
   fallback / fallback_reason:配的是语义模型、实际在用本地哈希。重新配置成功就清掉。
2. 页面按实际生效的 embedder(has_semantics)标"没有语义能力"。退回时说清配的是哪个、
   在哪、为什么没连上,给「重新连接」——发的就是保存着的那份配置,服务起来后点它即可,
   不用重启;"重建索引"收起,存量提示改口成先别重建。明确选了本地哈希的照旧:标没有
   语义能力,重建按钮还在。

验证:test_kb 新增一条,走完"启动连不上 → 状态里报退回并带原因 → 重新连上后消失 →
明确选本地不算退回"。check-ui 新增一组伪造状态接口的断言(退回 / 重新连接 / 明确选
本地,写请求一律拦下);把页面换回旧版本跑,这组挂 8 项。后端 471 passed,tsc 与
check-decode / guards / stream / ui 全绿;本机状态接口现在如实报出退回和原因。
This commit is contained in:
ThearchyHelios
2026-09-25 19:09:19 +08:00
parent 3b2c082743
commit 86a0928512
6 changed files with 211 additions and 7 deletions
+82
View File
@@ -229,6 +229,88 @@ console.log('\n=== 向量模型设置 ===')
await page.close()
}
console.log('\n=== 向量模型:配了语义模型却退回本地 ===')
{
// 配的是本机 LM Studio 上的模型,服务启动时它没起,进程就一直用本地哈希。
// 以前页面按"配的是不是 local"判断:不说没有语义能力,还高亮"重建索引"——
// 那些"对不上"的向量正是那个模型建好的,一点就被哈希覆盖。真实环境撞不撞得上
// 这个状态看运气,所以这里伪造状态接口,几种情况各看一遍;写请求一律拦下
const embeddingPage = async (initial, onPut) => {
let status = initial
const page = await ctx.newPage()
const errors = []
page.on('pageerror', (e) => errors.push(e.message))
const sent = []
await page.route('**/api/**', (route) => {
const r = route.request()
if (new URL(r.url()).pathname.endsWith('/api/kb/embedding')) {
if (r.method() === 'GET') return route.fulfill({ json: status })
if (r.method() === 'PUT') {
sent.push(r.postDataJSON())
status = onPut(status)
return route.fulfill({ json: {
embedder: status.embedder, dim: status.dim,
stale_chunks: status.stale_chunks, stale_memories: status.stale_memories } })
}
}
return r.method() === 'GET' ? route.continue() : route.abort()
})
await page.goto(`${WEB}/knowledge`, { waitUntil: 'networkidle' })
await page.waitForTimeout(500)
return { page, errors, sent, body: () => page.locator('body').innerText() }
}
const down = {
embedder: 'local-hashing', dim: 512, configured: true, kind: 'openai',
model: 'text-embedding-qwen3-embedding-4b', base_url: 'http://127.0.0.1:1234/v1',
stale_chunks: 68, stale_memories: 5, unindexed_chunks: 0,
has_semantics: false, default_alpha: 0, fallback: true,
fallback_reason: '用不了 http://127.0.0.1:1234/v1 上的 embedding 模型 '
+ 'text-embedding-qwen3-embedding-4b:APIConnectionError: Connection error.。检查服务在不在',
}
const up = {
...down, embedder: 'openai:text-embedding-qwen3-embedding-4b', dim: 2560,
stale_chunks: 0, stale_memories: 0, has_semantics: true, default_alpha: 0.5,
fallback: false, fallback_reason: '',
}
const a = await embeddingPage(down, () => up)
let body = await a.body()
check('退回本地时标出没有语义能力', body.includes('没有语义能力'))
check('说清配的是哪个、为什么没用上',
body.includes('text-embedding-qwen3-embedding-4b') && body.includes('没连上'))
check('没连上的原因原样给出来', body.includes('APIConnectionError'))
check('不再劝人重建(会把建好的向量覆盖掉)',
await a.page.getByRole('button', { name: /重建索引/ }).count() === 0)
check('存量提示改口成先别重建', body.includes('先别重建'))
const reconnect = a.page.getByRole('button', { name: '重新连接' })
check('给得出「重新连接」', await reconnect.count() === 1)
if (await reconnect.count()) {
await reconnect.click()
await a.page.waitForTimeout(800)
}
const req = a.sent[0]
check('重新连接发的是保存着的那份配置',
req?.kind === 'openai' && req.model === down.model && req.base_url === down.base_url,
JSON.stringify(req))
body = await a.body()
check('连上了说一声', body.includes('已重新连上'))
check('连上之后提示消失', !body.includes('没有语义能力') && !body.includes('没连上'))
check('没有运行时报错', a.errors.length === 0, a.errors.slice(0, 2).join(' | '))
await a.page.close()
// 明确选了本地哈希是正常情况:照旧标没有语义能力,重建按钮也还在
const b = await embeddingPage(
{ ...down, kind: 'local', model: '', base_url: '', fallback: false, fallback_reason: '' },
(s) => s)
body = await b.body()
check('明确选了本地哈希:照旧标没有语义能力', body.includes('没有语义能力'))
check('……不冒出"没连上"', !body.includes('没连上'))
check('……重建按钮还在,正常情况别一起藏了',
await b.page.getByRole('button', { name: /重建索引/ }).count() === 1)
await b.page.close()
}
console.log('\n=== 会话 ===')
{
// 对话以前只活在内存里,刷新就没了。这一组守的是它真的落了库: