workload.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /** Reviewed synthetic tool history shared by continuation and child-catalog measurements. */
  2. import { AssistantStreamAccumulator } from '@deepseek-ai/dsh-llm/assistant-stream'
  3. import { MessageId, ToolCallId } from '@deepseek-ai/dsh-llm'
  4. import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm'
  5. import { Session, SessionId } from '@deepseek-ai/dsh-session'
  6. import type { SessionEvent } from '@deepseek-ai/dsh-session'
  7. /** Workload dimensions, independent of environment and recorded user material. */
  8. export const WORKLOAD = {
  9. historyTurns: 800,
  10. toolsPerHistoricalTurn: 4,
  11. toolResultChars: 2_048,
  12. requestTurns: 40,
  13. continuationTurns: 20,
  14. profileTurns: 100,
  15. toolsPerLiveTurn: 8,
  16. children: 16,
  17. childHistoryTurns: 80,
  18. } as const
  19. /** Fixed clock used only to author persisted synthetic input. */
  20. export const TIME_ZERO = 1_700_000_000_000
  21. /** Durable parent identity of the measured continuation. */
  22. export const PARENT_ID = SessionId('bench-parent')
  23. /**
  24. * Construct a deterministic model reply without retaining past requests.
  25. * @param serial - unique response ordinal.
  26. * @param tools - number of synthetic tool calls, or zero for a final text reply.
  27. * @returns streamed chunks and their known final blocks.
  28. */
  29. export function response(serial: number, tools: number): { chunks: StreamChunk[]; content: ContentBlock[] } {
  30. const content: ContentBlock[] = [
  31. { type: 'reasoning', text: 'Inspect the synthetic result. '.repeat(8) },
  32. { type: 'text', text: 'Synthetic response. '.repeat(8) },
  33. ...Array.from({ length: tools }, (_, index): ContentBlock => ({
  34. type: 'tool-call', id: ToolCallId('call-' + String(serial) + '-' + String(index)), name: 'bench_tool',
  35. arguments: JSON.stringify({ ordinal: serial * 100 + index }),
  36. })),
  37. ]
  38. const chunks: StreamChunk[] = []
  39. content.forEach((block, index) => {
  40. chunks.push({ type: 'block-start', index, blockType: block.type })
  41. if (block.type === 'text' || block.type === 'reasoning') {
  42. for (let offset = 0; offset < block.text.length; offset += 16) {
  43. chunks.push({ type: block.type === 'text' ? 'text-delta' : 'reasoning-delta', index, text: block.text.slice(offset, offset + 16) })
  44. }
  45. } else if (block.type === 'tool-call') {
  46. for (let offset = 0; offset < block.arguments.length; offset += 8) {
  47. chunks.push({ type: 'tool-call-delta', index, id: block.id, name: block.name, argumentsDelta: block.arguments.slice(offset, offset + 8) })
  48. }
  49. }
  50. chunks.push({ type: 'block-end', index, block })
  51. })
  52. chunks.push({ type: 'usage', usage: { inputTokens: 10_000, outputTokens: 100 } })
  53. chunks.push({ type: 'finish', reason: { kind: tools === 0 ? 'stop' : 'tool-calls' } })
  54. return { chunks, content }
  55. }
  56. /**
  57. * Author completed two-step turns through production Session append and stream compaction.
  58. * @param turns - completed historical turns.
  59. * @returns detached current-generation events with fixed ids, timestamps and payloads.
  60. */
  61. export function syntheticHistory(turns: number): SessionEvent[] {
  62. const session = Session.create(PARENT_ID)
  63. for (let turn = 1; turn <= turns; turn++) {
  64. session.append('turn/start', { turn })
  65. session.append('step/start', { turn, step: 1 })
  66. if (turn === 1) session.append('system/message', {
  67. turn, step: 1, message: { id: MessageId('system-head'), role: 'system', content: [], source: { kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' } },
  68. }, { surfaceOp: 'append' })
  69. session.append('user/message', {
  70. id: MessageId('prompt-' + String(turn)), role: 'user',
  71. content: [{ type: 'text', text: 'Inspect synthetic module ' + String(turn) }], source: { kind: 'user' },
  72. }, { surfaceOp: 'append' })
  73. for (const step of [1, 2]) {
  74. if (step === 2) session.append('step/start', { turn, step })
  75. const reply = response(turn * 2 + step, step === 1 ? WORKLOAD.toolsPerHistoricalTurn : 0)
  76. const stream = new AssistantStreamAccumulator()
  77. reply.chunks.forEach((chunk, index) => { stream.push({ time: TIME_ZERO + turn * 1_000 + step * 100 + index, chunk }) })
  78. session.append('assistant/message', {
  79. turn, step,
  80. message: { id: MessageId('reply-' + String(turn) + '-' + String(step)), role: 'assistant', content: reply.content, source: { kind: 'model', provider: 'bench', model: 'bench' } },
  81. stream: [...stream.snapshot()],
  82. }, { surfaceOp: 'append' })
  83. for (const block of reply.content) {
  84. if (block.type !== 'tool-call') continue
  85. const call = session.append('tool/call', { turn, step, callId: block.id, name: block.name, arguments: block.arguments })
  86. session.append('tool/result', {
  87. turn, step,
  88. message: {
  89. id: MessageId('result-' + block.id), role: 'user', source: { kind: 'tool', callId: block.id },
  90. content: [{ type: 'tool-result', toolCallId: block.id, content: [{ type: 'text', text: resultText(turn) }], isError: false }],
  91. },
  92. }, { surfaceOp: 'append', sourceEventSeqs: [call.seq] })
  93. }
  94. session.append('step/end', { turn, step })
  95. }
  96. session.append('turn/end', { turn, reason: { kind: 'completed' } })
  97. }
  98. return session.snapshotEvents().map(event => ({ ...event, time: TIME_ZERO + event.seq }))
  99. }
  100. /**
  101. * Build a bounded synthetic file-read result with a varying prefix.
  102. * @param ordinal - deterministic result identifier.
  103. * @returns exactly the reviewed number of UTF-16 characters.
  104. */
  105. export function resultText(ordinal: number): string {
  106. return ('module ' + String(ordinal) + '\n' + 'export const synthetic = 42;\n'.repeat(100)).slice(0, WORKLOAD.toolResultChars)
  107. }