fix(ai): preserve project bindings across unrelated model settings
This commit is contained in:
parent
441878adb9
commit
88e0a058e2
@ -68,6 +68,8 @@ HTTP 410“未留存”、访问失败、格式或模型身份无法核对时,
|
||||
|
||||
是否可向用户开放,以当前服务端用途验证记录为准。实际执行过哪些模型及产品验收项目,须另附对应记录,不从目录数量、适配协议数量或部署成功推断。
|
||||
|
||||
项目中的服务配置版本保留作审计。仅调整其它模型的开放用途或默认值,不会使当前模型身份不变的项目失效,也不要求重复付费分析。执行前仍检查当前权限、用途可用性、服务地址、协议指纹及密钥对应的验证记录;真正更换连接、密钥、协议或验证记录时,原模型快照仍会被拒绝。
|
||||
|
||||
## 详情页资料分析与文案恢复
|
||||
|
||||
`POST /api/detail-generator/jobs/{id}/recover` 复用登录鉴权,当前数据库超级管理员资格与任务创建者资格均须通过,且必须发送 `Content-Type: application/json`。
|
||||
|
||||
@ -68,9 +68,28 @@ test.each(['disabled-provider', 'disabled-model', 'missing-model', 'unconfirmed'
|
||||
await expect(configureAiModels('admin', { revision: 1, providers: [], defaults: { analysis: legacyModelKey, copy: null, image: null } })).rejects.toMatchObject({ code: 'default_unavailable' })
|
||||
expect(saveAiModelDefaults).not.toHaveBeenCalled()
|
||||
})
|
||||
test('changed connection revisions stop existing bindings before any model request', async () => {
|
||||
test('unrelated catalog settings do not invalidate an unchanged project model identity', async () => {
|
||||
const binding = (await resolveAiModel('image', secondModelKey)).binding
|
||||
providers[1].revision++
|
||||
await expect(resolveAiModel('image', secondModelKey, binding)).resolves.toMatchObject({ binding: { ...binding, revision: 2 } })
|
||||
expect(requestAiModel).not.toHaveBeenCalled()
|
||||
})
|
||||
test('a catalog revision change cannot revive a withdrawn purpose on an existing project', async () => {
|
||||
const binding = (await resolveAiModel('image', secondModelKey)).binding
|
||||
providers[1].revision++
|
||||
models[1].stages = ['analysis', 'copy']
|
||||
await expect(resolveAiModel('image', secondModelKey, binding)).rejects.toMatchObject({ code: 'model_unavailable' })
|
||||
expect(requestAiModel).not.toHaveBeenCalled()
|
||||
})
|
||||
test('a rotated encrypted credential and its new validation cannot reuse an old binding', async () => {
|
||||
const binding = (await resolveAiModel('image', secondModelKey)).binding
|
||||
const prior = await listAiModelVerifications()
|
||||
Object.assign(providers[1], encryptAiModelKey('synthetic-new-encrypted-secret'), { revision: 2 })
|
||||
await expect(resolveAiModel('image', secondModelKey, binding)).rejects.toMatchObject({ code: 'model_unavailable' })
|
||||
;(listAiModelVerifications as jest.Mock).mockResolvedValue(prior.map(value => value.modelKey === secondModelKey && value.stage === 'image' ? {
|
||||
...value, id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
|
||||
contextHash: aiModelVerificationContext(providers[1].baseUrl, models[1].id, models[1].capabilityMetadata, 'synthetic-new-encrypted-secret', getAiModelStageProtocol(models[1].capabilityMetadata, 'image')!),
|
||||
} : value))
|
||||
await expect(resolveAiModel('image', secondModelKey, binding)).rejects.toMatchObject({ code: 'model_binding_changed' })
|
||||
expect(requestAiModel).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@ -263,7 +263,9 @@ export async function resolveAiModel(stage: AiModelStage, selection: string, exp
|
||||
// Pin the server-owned validation generation too: environment key rotation does not change the provider revision.
|
||||
const verificationId = aiModelVerificationSummary(verifications, model.key, stage, modelContext(model, provider, stage)).id!
|
||||
const binding: AiModelBinding = { providerId: provider.id, modelKey: model.key, modelId: model.id, providerName: provider.name, baseUrl: provider.baseUrl, revision: provider.revision, protocolFingerprint, verificationId }
|
||||
if (expected && (expected.providerId !== binding.providerId || expected.modelKey !== binding.modelKey || expected.modelId !== binding.modelId || expected.baseUrl !== binding.baseUrl || expected.revision !== binding.revision || expected.protocolFingerprint !== binding.protocolFingerprint || expected.verificationId !== binding.verificationId)) throw new AiModelError('模型连接、路由或测试记录已改变,请刷新模型选择后重新分析;当前任务已停止', 409, 'model_binding_changed')
|
||||
// The provider revision also tracks other models' visibility settings. Execution identity
|
||||
// is pinned by the endpoint, protocol and credential-bound verification, not that edit counter.
|
||||
if (expected && (expected.providerId !== binding.providerId || expected.modelKey !== binding.modelKey || expected.modelId !== binding.modelId || expected.baseUrl !== binding.baseUrl || expected.protocolFingerprint !== binding.protocolFingerprint || expected.verificationId !== binding.verificationId)) throw new AiModelError('模型连接、路由或测试记录已改变,请刷新模型选择后重新分析;当前任务已停止', 409, 'model_binding_changed')
|
||||
const protocol = getAiModelStageProtocol(model.capabilityMetadata, stage)!
|
||||
const testedCount = stage === 'image' && protocol.referenceContract === 'probe-required' ? verifiedReferenceImageCount(verifications.find(row => row.modelKey === model.key && row.stage === stage)) : null
|
||||
return { binding, apiKey: providerKey(provider), protocol: testedCount ? { ...protocol, maxImages: Math.min(protocol.maxImages ?? 8, testedCount) } : protocol }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user