workflow-ptc.e2e.ts 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { afterEach, describe, expect, it } from 'vitest'
  2. import { Context } from '@deepseek-ai/cordis'
  3. import { SessionId } from '@deepseek-ai/dsh-session'
  4. import AgentLoop from '@deepseek-ai/dsh-agent-loop'
  5. import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
  6. import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
  7. import SubagentRuntime from '@deepseek-ai/dsh-subagent'
  8. import * as Spawn from '@deepseek-ai/dsh-subagent-spawn-in-process'
  9. import PtcWorkflowEngine from '../src/index.ts'
  10. import { mountPtcRuntime } from './setup.ts'
  11. let ctx: Context | undefined
  12. afterEach(async () => {
  13. await ctx?.fiber.dispose()
  14. ctx = undefined
  15. })
  16. async function harness(): Promise<Context> {
  17. const built = new Context()
  18. await mountAgentLoopTestDependencies(built)
  19. await mountPtcRuntime(built)
  20. await built.plugin(AgentLoop, { agents: [] })
  21. await built.plugin(LlmDeepSeek)
  22. await built.plugin(SubagentRuntime)
  23. await built.plugin(Spawn, { providerName: 'spawn' })
  24. await built.plugin(PtcWorkflowEngine, { provider: 'spawn' })
  25. return built
  26. }
  27. const META = {
  28. name: 'e2e-ptc-arithmetic',
  29. description: 'two real children through PTC: one prose, one structured',
  30. phases: [{ title: 'Ask' }, { title: 'Judge' }],
  31. }
  32. const SCRIPT = `phase('Ask')
  33. log('asking the prose child')
  34. const prose = await agent('Reply with exactly one short sentence: what is 2 + 2?')
  35. phase('Judge')
  36. const judged = await agent(
  37. 'Here is an answer to the question "what is 2+2": ' + prose
  38. + ' — report whether it contains the number 4 and your confidence between 0 and 1.',
  39. { schema: { type: 'object', properties: { containsFour: { type: 'boolean' }, confidence: { type: 'number' } }, required: ['containsFour'] } },
  40. )
  41. return { prose, containsFour: judged === null ? null : judged.containsFour }`
  42. describe.skipIf(!process.env.DEEPSEEK_API_KEY)('PTC workflow engine with-key e2e', () => {
  43. it('runs a two-phase script in the Node PTC runtime over real children, one through the structured runtime', async () => {
  44. ctx = await harness()
  45. const parentHandle = await ctx.agents.create({
  46. sessionId: 'wf-ptc-e2e-session' as never,
  47. agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
  48. })
  49. const events: string[] = []
  50. const childIds: string[] = []
  51. for (const name of ['workflow/start', 'workflow/phase', 'workflow/log', 'workflow/agent-start', 'workflow/agent-end', 'workflow/end'] as const) {
  52. ctx.on(name, (...payload: unknown[]) => {
  53. events.push(name)
  54. if (name === 'workflow/agent-start') childIds.push((payload[1] as { childId: string }).childId)
  55. })
  56. }
  57. const run = ctx.workflowEngine.start({ script: SCRIPT, meta: META, parent: parentHandle.agent })
  58. const result = await run.result
  59. await run.dispose()
  60. expect(result.stopReason).toBe('completed')
  61. expect(result.agentsStarted).toBe(2)
  62. const value = result.value as { prose: string; containsFour: boolean | null }
  63. // World checks: the prose child really answered (a real completion), and
  64. // the structured child judged it against the REAL schema-forced tool.
  65. expect(value.prose.length).toBeGreaterThan(0)
  66. expect(value.containsFour).toBe(true)
  67. expect(events[0]).toBe('workflow/start')
  68. expect(events.at(-1)).toBe('workflow/end')
  69. expect(events.filter(name => name === 'workflow/phase').length).toBe(2)
  70. expect(events.filter(name => name === 'workflow/agent-start').length).toBe(2)
  71. expect(childIds.length).toBe(2)
  72. // The children were disposed to quiescence after collection.
  73. for (const childId of childIds) {
  74. expect(ctx.agents.get(SessionId(childId))).toBeUndefined()
  75. }
  76. await parentHandle.dispose()
  77. }, 240_000)
  78. })