settings-host.mjs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import assert from 'node:assert/strict'
  2. import * as fs from 'node:fs'
  3. import * as path from 'node:path'
  4. import { pathToFileURL } from 'node:url'
  5. import { createServer } from 'node:http'
  6. import { boot } from '@deepseek-ai/dsh-app-boot'
  7. import { credentialRef } from '@deepseek-ai/dsh-credentials'
  8. const root = process.argv[2]
  9. const plugin = pathToFileURL(process.argv[3]).href
  10. const requests = []
  11. const server = createServer((req, res) => {
  12. void (async () => {
  13. let text = ''
  14. for await (const part of req) text += part
  15. const body = JSON.parse(text)
  16. requests.push({ body, key: req.headers.authorization })
  17. res.setHeader('content-type', 'application/json')
  18. res.end(JSON.stringify({ data: body.input.map((_text, index) => ({ index, embedding: Array.from({ length: body.dimensions }, (_, i) => i + 1) })) }))
  19. })().catch(() => res.destroy())
  20. })
  21. await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
  22. const settingsFile = path.join(root, 'settings.yaml')
  23. const credentialsFile = path.join(root, 'credentials.yaml')
  24. const profile = path.join(root, 'cordis.json')
  25. fs.writeFileSync(profile, JSON.stringify([
  26. { id: 'settings', name: '@deepseek-ai/dsh-settings-file', config: { path: settingsFile, watch: false } },
  27. { id: 'credentials', name: '@deepseek-ai/dsh-credentials-local', config: { path: credentialsFile, watch: false } },
  28. { id: 'embedding', name: plugin },
  29. ]))
  30. let ctx
  31. try {
  32. ctx = await boot('webnovel-embedding-settings-test', profile)
  33. const settings = ctx.get('settings')
  34. const credentials = ctx.get('credentials')
  35. const service = ctx.get('embeddings')
  36. const namespace = 'webnovel-embeddings'
  37. assert.equal(service.current(), undefined)
  38. const descriptor = settings.describe({ redactSecrets: true }).find(item => item.ns === namespace)
  39. assert.ok(descriptor)
  40. assert.ok(JSON.stringify(descriptor.schema).includes('dimensions'))
  41. assert.equal(requests.length, 0)
  42. const base = { enabled: true, protocol: 'openai-compatible', baseURL: `http://127.0.0.1:${server.address().port}/v1`, model: 'fixture-model', apiKeyEnv: 'WEBNOVEL_TEST_EMBED_KEY' }
  43. await assert.rejects(settings.update(namespace, base), /维度/)
  44. await credentials.set(credentialRef(base.apiKeyEnv), 'fixture-native-credential')
  45. await settings.update(namespace, { ...base, dimensions: 3 })
  46. const first = service.current()
  47. assert.equal(first.metadata.dimensions, 3)
  48. assert.deepEqual(await first.embed([{ text: '合成测试文本' }], 'query'), [[1, 2, 3]])
  49. assert.equal(requests[0].body.dimensions, 3)
  50. assert.equal(requests[0].key, 'Bearer fixture-native-credential')
  51. assert.ok(!fs.readFileSync(settingsFile, 'utf8').includes('fixture-native-credential'))
  52. assert.ok(!JSON.stringify(settings.describe({ redactSecrets: true })).includes('fixture-native-credential'))
  53. await settings.update(namespace, { dimensions: 4 })
  54. const next = service.current()
  55. assert.notEqual(next.metadata.revision, first.metadata.revision)
  56. assert.equal(next.metadata.dimensions, 4)
  57. await assert.rejects(first.embed([{ text: '旧配置不可再调用' }], 'query'), /配置已变更/)
  58. await next.embed([{ text: '新配置' }], 'query')
  59. assert.equal(requests.at(-1).body.dimensions, 4)
  60. const row = [...ctx.get('loader').entries()].find(entry => entry.options.id === 'embedding')
  61. await row.update({ disabled: true }, false, true)
  62. await ctx.get('loader').await()
  63. assert.equal(ctx.get('embeddings'), undefined)
  64. assert.ok(!settings.describe().some(item => item.ns === namespace))
  65. await row.update({ disabled: false }, false, true)
  66. await ctx.get('loader').await()
  67. assert.equal(ctx.get('embeddings').current().metadata.dimensions, 4)
  68. await settings.update(namespace, { enabled: false })
  69. assert.equal(ctx.get('embeddings').current(), undefined)
  70. console.log(JSON.stringify({ nativeSettings: true, dimensionsRequired: true, dimensionsSentAndValidated: true, revisionChanges: true, credentialsIsolated: true, reloadPreservesSettings: true }))
  71. } finally {
  72. if (ctx) await ctx.fiber.dispose()
  73. server.closeAllConnections()
  74. await new Promise(resolve => server.close(resolve))
  75. }