|
| 1 | +import { |
| 2 | + Agent, |
| 3 | + OpenAIResponsesCompactionSession, |
| 4 | + run, |
| 5 | + withTrace, |
| 6 | +} from '@openai/agents'; |
| 7 | +import { fetchImageData } from './tools'; |
| 8 | +import { FileSession } from './sessions'; |
| 9 | + |
| 10 | +async function main() { |
| 11 | + const session = new OpenAIResponsesCompactionSession({ |
| 12 | + // This compaction decorator handles only compaction logic. |
| 13 | + // The underlying session is responsible for storing the history. |
| 14 | + underlyingSession: new FileSession(), |
| 15 | + // Set a low threshold to observe compaction in action. |
| 16 | + compactionThreshold: 3, |
| 17 | + model: 'gpt-4.1', |
| 18 | + }); |
| 19 | + |
| 20 | + const agent = new Agent({ |
| 21 | + name: 'Assistant', |
| 22 | + model: 'gpt-4.1', |
| 23 | + instructions: |
| 24 | + 'Keep answers short. This example demonstrates responses.compact with a custom session. For every user turn, call fetch_image_data with the provided label. Do not include raw image bytes or data URLs in your final answer.', |
| 25 | + modelSettings: { toolChoice: 'required' }, |
| 26 | + tools: [fetchImageData], |
| 27 | + }); |
| 28 | + |
| 29 | + // To see compaction debug logs, run with: |
| 30 | + // DEBUG=openai-agents:openai:compaction pnpm -C examples/memory start:oai-compact |
| 31 | + await withTrace('memory:compactSession:main', async () => { |
| 32 | + const prompts = [ |
| 33 | + 'Call fetch_image_data with label "alpha". Then explain compaction in 1 sentence.', |
| 34 | + 'Call fetch_image_data with label "beta". Then add a fun fact about space in 1 sentence.', |
| 35 | + 'Call fetch_image_data with label "gamma". Then add a fun fact about oceans in 1 sentence.', |
| 36 | + 'Call fetch_image_data with label "delta". Then add a fun fact about volcanoes in 1 sentence.', |
| 37 | + 'Call fetch_image_data with label "epsilon". Then add a fun fact about deserts in 1 sentence.', |
| 38 | + ]; |
| 39 | + |
| 40 | + for (const prompt of prompts) { |
| 41 | + const result = await run(agent, prompt, { session, stream: true }); |
| 42 | + console.log(`\nUser: ${prompt}`); |
| 43 | + |
| 44 | + for await (const event of result.toStream()) { |
| 45 | + if (event.type === 'raw_model_stream_event') { |
| 46 | + continue; |
| 47 | + } |
| 48 | + if (event.type === 'agent_updated_stream_event') { |
| 49 | + continue; |
| 50 | + } |
| 51 | + if (event.type !== 'run_item_stream_event') { |
| 52 | + continue; |
| 53 | + } |
| 54 | + |
| 55 | + if (event.item.type === 'tool_call_item') { |
| 56 | + const toolName = (event.item as any).rawItem?.name; |
| 57 | + console.log(`-- Tool called: ${toolName ?? '(unknown)'}`); |
| 58 | + } else if (event.item.type === 'tool_call_output_item') { |
| 59 | + console.log( |
| 60 | + `-- Tool output: ${formatToolOutputForLog((event.item as any).output)}`, |
| 61 | + ); |
| 62 | + } else if (event.item.type === 'message_output_item') { |
| 63 | + console.log(`Assistant: ${event.item.content.trim()}`); |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + const compactedHistory = await session.getItems(); |
| 69 | + console.log('\nStored history after compaction:'); |
| 70 | + for (const item of compactedHistory) { |
| 71 | + console.log(`- ${item.type}`); |
| 72 | + } |
| 73 | + }); |
| 74 | +} |
| 75 | + |
| 76 | +function formatToolOutputForLog(output: unknown): string { |
| 77 | + if (output === null) { |
| 78 | + return 'null'; |
| 79 | + } |
| 80 | + if (output === undefined) { |
| 81 | + return 'undefined'; |
| 82 | + } |
| 83 | + if (typeof output === 'string') { |
| 84 | + return output.length > 200 ? `${output.slice(0, 200)}…` : output; |
| 85 | + } |
| 86 | + if (Array.isArray(output)) { |
| 87 | + const parts = output.map((part) => formatToolOutputPartForLog(part)); |
| 88 | + return `[${parts.join(', ')}]`; |
| 89 | + } |
| 90 | + if (typeof output === 'object') { |
| 91 | + const keys = Object.keys(output as Record<string, unknown>).sort(); |
| 92 | + return `{${keys.slice(0, 10).join(', ')}${keys.length > 10 ? ', …' : ''}}`; |
| 93 | + } |
| 94 | + return String(output); |
| 95 | +} |
| 96 | + |
| 97 | +function formatToolOutputPartForLog(part: unknown): string { |
| 98 | + if (!part || typeof part !== 'object') { |
| 99 | + return String(part); |
| 100 | + } |
| 101 | + const record = part as Record<string, unknown>; |
| 102 | + const type = typeof record.type === 'string' ? record.type : 'unknown'; |
| 103 | + if (type === 'text' && typeof record.text === 'string') { |
| 104 | + return `text(${record.text.length} chars)`; |
| 105 | + } |
| 106 | + if (type === 'image' && typeof record.image === 'string') { |
| 107 | + return `image(${record.image.length} chars)`; |
| 108 | + } |
| 109 | + return type; |
| 110 | +} |
| 111 | + |
| 112 | +main().catch((error) => { |
| 113 | + console.error(error); |
| 114 | + process.exit(1); |
| 115 | +}); |
0 commit comments