refactor(web): publish transient model request capacity (round 1)
This commit is contained in:
@@ -7,8 +7,10 @@
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import LlmService, { LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, LlmModelReasoningInfo, LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm'
|
||||
import LlmService, { LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
import type {
|
||||
GenerateOptions, LlmModelReasoningInfo, LlmResolvedModelInfo, StreamChunk,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { Session, SessionId, foldRequestHeader } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
@@ -179,6 +181,7 @@ describe('request stability across the loop', () => {
|
||||
provider,
|
||||
id: model,
|
||||
name: model,
|
||||
context: { contextWindow: 64_000 },
|
||||
reasoning: await reasoning.promise,
|
||||
}
|
||||
}
|
||||
@@ -189,6 +192,12 @@ describe('request stability across the loop', () => {
|
||||
})
|
||||
const disposeFirst = ctx.llm.registerAdapter(['mock'], first)
|
||||
const agent = ctx.agentLoop.create(SessionId('effort-hmr'), { provider: 'mock', model: 'mock' })
|
||||
const dispatched: number[] = []
|
||||
ctx.on('agent/model-request', (subject, _turn, _step, request) => {
|
||||
if (subject === agent && request.contextWindow !== undefined) {
|
||||
dispatched.push(request.contextWindow)
|
||||
}
|
||||
})
|
||||
|
||||
send(agent, 'go')
|
||||
await started.promise
|
||||
@@ -204,6 +213,7 @@ describe('request stability across the loop', () => {
|
||||
ReasoningEffortId('high'),
|
||||
])
|
||||
expect(second.requests).toHaveLength(0)
|
||||
expect(dispatched).toEqual([64_000])
|
||||
const headers = agent.session.events.filter(event => event.type === 'request/header')
|
||||
expect(headers.at(-1)?.data.header.config.reasoningEffort).toBe(ReasoningEffortId('high'))
|
||||
})
|
||||
@@ -308,6 +318,94 @@ describe('request stability across the loop', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('notifies one contained live model-request edge only after successful stream construction', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt, { persona: 'stable base' })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
let resolutions = 0
|
||||
const adapter = new class extends LlmAdapter {
|
||||
override resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo> {
|
||||
resolutions += 1
|
||||
return Promise.resolve({
|
||||
provider,
|
||||
id: model,
|
||||
name: model,
|
||||
...model === 'capacity'
|
||||
? { context: { contextWindow: 128_000 } }
|
||||
: {},
|
||||
})
|
||||
}
|
||||
|
||||
override stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
if (options.model === 'sync-failure') throw new LlmError('construction failed', 'CONSTRUCTION')
|
||||
if (options.model === 'async-failure') {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: () => Promise.reject(new LlmError('iteration failed', 'ITERATION')),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return (async function* () {
|
||||
yield* textResponse(options.model)
|
||||
})()
|
||||
}
|
||||
}()
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const agent = ctx.agentLoop.create(SessionId('model-request-live'), {
|
||||
provider: 'mock',
|
||||
model: 'capacity',
|
||||
})
|
||||
const observed: {
|
||||
turn: number
|
||||
step: number
|
||||
provider: string
|
||||
model: string
|
||||
contextWindow?: number
|
||||
}[] = []
|
||||
ctx.on('agent/model-request', (subject) => {
|
||||
if (subject === agent) throw new Error('observer failed')
|
||||
})
|
||||
ctx.on('agent/model-request', (subject, turn, step, request) => {
|
||||
if (subject === agent) observed.push({ turn, step, ...request })
|
||||
})
|
||||
ctx.on('agent/request', async (_subject, turn, _step, _signal, next) => ({
|
||||
...await next(),
|
||||
model: ['capacity', 'unknown', 'async-failure', 'sync-failure'][turn - 1]!,
|
||||
}))
|
||||
|
||||
for (const prompt of ['one', 'two', 'three', 'four']) {
|
||||
send(agent, prompt)
|
||||
await waitForIdle(ctx, agent)
|
||||
}
|
||||
|
||||
expect(observed).toEqual([
|
||||
{
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'mock',
|
||||
model: 'capacity',
|
||||
contextWindow: 128_000,
|
||||
},
|
||||
{
|
||||
turn: 2,
|
||||
step: 1,
|
||||
provider: 'mock',
|
||||
model: 'unknown',
|
||||
},
|
||||
{
|
||||
turn: 3,
|
||||
step: 1,
|
||||
provider: 'mock',
|
||||
model: 'async-failure',
|
||||
},
|
||||
])
|
||||
expect(resolutions).toBe(4)
|
||||
})
|
||||
|
||||
it('a compaction replace rewrites the resend, and the log explains it', async () => {
|
||||
const adapter = new MockAdapter([textResponse('one'), textResponse('two')])
|
||||
const ctx = await harness(adapter)
|
||||
|
||||
Reference in New Issue
Block a user