fix(llm): keep request generations coherent

This commit is contained in:
Tianyi Cui
2026-07-31 01:27:12 +08:00
parent afb05b4049
commit a9e2489db8
26 changed files with 237 additions and 142 deletions

View File

@@ -25,8 +25,10 @@ async function harness(baseURL: string, overrides: Record<string, unknown> = {})
/** Direct adapter over the real profile resolver, with literal-key resolution. */
function adapterOf(providers: Record<string, LlmPiAi.PiAiProviderProfile>): PiAiAdapter {
const compositionProfiles = resolveProfiles(providers)
return new PiAiAdapter({
profiles: () => resolveProfiles(providers),
profiles: () => compositionProfiles,
compositionProfiles,
resolveApiKey: (_provider, profile) => Promise.resolve(profile.apiKey),
})
}

View File

@@ -18,6 +18,7 @@ afterEach(async () => {
while (cleanups.length > 0) await cleanups.pop()!()
await closeMockServers()
vi.unstubAllEnvs()
vi.restoreAllMocks()
})
async function home(): Promise<string> {
@@ -97,4 +98,45 @@ describe('request-level dynamic profiles', () => {
jitterRatio: 0.2,
})
})
it('cannot mix earlier capability facts with a later settings connection', async () => {
const dir = await home()
const first = await mockServer([{ events: textEvents }])
const second = await mockServer([{ events: textEvents }])
const ctx = await boot(dir, {
providers: {
deepseek: {
apiKey: 'first-key',
baseURL: first.url,
reasoning: 'off',
},
},
})
const resolveModel = vi.spyOn(LlmPiAi.PiAiAdapter.prototype, 'resolveModel')
resolveModel.mockImplementation(async function (
this: LlmPiAi.PiAiAdapter,
provider,
model,
signal,
) {
resolveModel.mockRestore()
const resolved = await this.resolveModel(provider, model, signal)
// Land a complete settings generation after capability resolution but
// before stream dispatch. Its changed reasoning default rejects it whole.
await ctx.settings.update(NS, {
providers: {
deepseek: {
apiKey: 'second-key',
baseURL: second.url,
reasoning: 'max',
},
},
})
return resolved
})
await assemble(ctx, { provider: 'deepseek', model: 'deepseek-v4-flash', messages: [] })
expect(second.requests).toHaveLength(0)
expect(first.headers[0]?.authorization).toBe('Bearer first-key')
})
})

View File

@@ -22,8 +22,10 @@ describe('pi-ai SDK retry boundary', () => {
throw failure
},
})
const compositionProfiles = resolveProfiles({ openai: { apiKey: 'test-key' } })
const adapter = new PiAiAdapter({
profiles: () => resolveProfiles({ openai: { apiKey: 'test-key' } }),
profiles: () => compositionProfiles,
compositionProfiles,
resolveApiKey: () => Promise.resolve('test-key'),
})
const drain = async (): Promise<void> => {