mock-llm.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import type { Context } from 'cordis'
  2. import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
  3. import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
  4. /**
  5. * Demo adapter for the `mock-echo` model.
  6. *
  7. * Behavior: if the last user text starts with "echo ", it calls the `echo`
  8. * tool with the rest of the line (exercising the tool round-trip), otherwise
  9. * it streams a canned reply quoting the input.
  10. */
  11. class MockEchoAdapter extends LlmAdapter {
  12. async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
  13. const lastUserText = [...options.messages].reverse()
  14. .filter(message => message.role === 'user')
  15. .flatMap(message => message.content)
  16. .filter(block => block.type === 'text')
  17. .map(block => block.text)
  18. .find(text => !text.startsWith('<')) ?? ''
  19. const hasToolResult = options.messages.at(-1)?.content.some(block => block.type === 'tool-result')
  20. if (lastUserText.startsWith('echo ') && !hasToolResult) {
  21. const payload = lastUserText.slice(5)
  22. const args = JSON.stringify({ text: payload })
  23. yield { type: 'block-start', index: 0, blockType: 'text' }
  24. for (const char of 'Let me echo that for you.') {
  25. yield { type: 'text-delta', index: 0, text: char }
  26. await new Promise(resolve => setTimeout(resolve, 2))
  27. }
  28. yield { type: 'block-end', index: 0, block: { type: 'text', text: 'Let me echo that for you.' } }
  29. yield { type: 'block-start', index: 1, blockType: 'tool-call' }
  30. yield { type: 'tool-call-delta', index: 1, id: CallId('call-echo'), name: 'echo', argumentsDelta: args }
  31. yield { type: 'block-end', index: 1, block: { type: 'tool-call', id: CallId('call-echo'), name: 'echo', arguments: args } }
  32. yield { type: 'usage', usage: { inputTokens: 20, outputTokens: 10 } }
  33. yield { type: 'finish', reason: { kind: 'tool-calls' } }
  34. return
  35. }
  36. const reply = hasToolResult
  37. ? 'The echo tool has spoken.'
  38. : `You said: "${lastUserText}". Try "echo <something>" to see a tool call.`
  39. yield { type: 'block-start', index: 0, blockType: 'text' }
  40. for (const char of reply) {
  41. yield { type: 'text-delta', index: 0, text: char }
  42. await new Promise(resolve => setTimeout(resolve, 2))
  43. }
  44. yield { type: 'block-end', index: 0, block: { type: 'text', text: reply } }
  45. yield { type: 'usage', usage: { inputTokens: 20, outputTokens: reply.length } }
  46. yield { type: 'finish', reason: { kind: 'stop' } }
  47. }
  48. }
  49. export const name = 'mock-llm'
  50. export const inject = ['llm']
  51. export function apply(ctx: Context) {
  52. ctx.llm.registerAdapter(['mock-echo'], new MockEchoAdapter())
  53. }