Skip to content

Commit 911717e

Browse files
committed
tool output support and bug fixes
1 parent 42b23c6 commit 911717e

File tree

11 files changed

+727
-392
lines changed

11 files changed

+727
-392
lines changed

examples/memory/file-hitl.ts

Lines changed: 46 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,35 @@
1-
import { z } from 'zod';
21
import readline from 'node:readline/promises';
32
import { stdin as input, stdout as output } from 'node:process';
43
import {
54
Agent,
65
RunResult,
76
RunToolApprovalItem,
87
run,
9-
tool,
8+
withTrace,
109
} from '@openai/agents';
1110

1211
import type { Interface as ReadlineInterface } from 'node:readline/promises';
1312
import { FileSession } from './sessions';
13+
import { createLookupCustomerProfileTool, fetchImageData } from './tools';
14+
15+
const customerDirectory: Record<string, string> = {
16+
'101':
17+
'Customer Kaz S. (tier gold) can be reached at +1-415-555-AAAA. Notes: Prefers SMS follow ups and values concise summaries.',
18+
'104':
19+
'Customer Yu S. (tier platinum) can be reached at +1-415-555-BBBB. Notes: Recently reported sync issues. Flagged for a proactive onboarding call.',
20+
'205':
21+
'Customer Ken S. (tier standard) can be reached at +1-415-555-CCCC. Notes: Interested in automation tutorials sent last week.',
22+
};
1423

15-
const instructions =
16-
'You assist support agents. Always consult the lookup_customer_profile tool before answering customer questions so your replies include stored notes. If the tool reports a transient failure, request approval and retry the same call once before responding. Keep responses under three sentences.';
17-
18-
let hasSimulatedLookupFailure = false;
19-
20-
async function fetchCustomerProfile(id: string): Promise<string> {
21-
if (!hasSimulatedLookupFailure) {
22-
hasSimulatedLookupFailure = true;
23-
throw new Error(
24-
'Simulated CRM outage for the first lookup. Please retry the tool call.',
25-
);
26-
}
27-
28-
const record = customerDirectory[id];
29-
if (!record) {
30-
return `No customer found for id ${id}.`;
31-
}
32-
33-
return `Customer ${record.name} (tier ${record.tier}) can be reached at ${record.phone}. Notes: ${record.notes}`;
34-
}
35-
36-
const lookupCustomerProfile = tool({
37-
name: 'lookup_customer_profile',
38-
description:
39-
'Look up stored profile details for a customer by their internal id.',
40-
parameters: z.object({
41-
id: z
42-
.string()
43-
.describe('The internal identifier for the customer to retrieve.'),
44-
}),
45-
async needsApproval() {
46-
return true;
47-
},
48-
async execute({ id }) {
49-
return await fetchCustomerProfile(id);
50-
},
24+
const lookupCustomerProfile = createLookupCustomerProfileTool({
25+
directory: customerDirectory,
26+
transientErrorMessage:
27+
'Simulated CRM outage for the first lookup. Please retry the tool call.',
5128
});
29+
lookupCustomerProfile.needsApproval = async () => true;
5230

53-
const customerDirectory: Record<
54-
string,
55-
{ name: string; phone: string; tier: string; notes: string }
56-
> = {
57-
'101': {
58-
name: 'Kaz S.',
59-
phone: '+1-415-555-AAAA',
60-
tier: 'gold',
61-
notes: 'Prefers SMS follow ups and values concise summaries.',
62-
},
63-
'104': {
64-
name: 'Yu S.',
65-
phone: '+1-415-555-BBBB',
66-
tier: 'platinum',
67-
notes:
68-
'Recently reported sync issues. Flagged for a proactive onboarding call.',
69-
},
70-
'205': {
71-
name: 'Ken S.',
72-
phone: '+1-415-555-CCCC',
73-
tier: 'standard',
74-
notes: 'Interested in automation tutorials sent last week.',
75-
},
76-
};
31+
const instructions =
32+
'You assist support agents. For every user turn you must call lookup_customer_profile and fetch_image_data before responding so replies include stored notes and the sample image. If a tool reports a transient failure, request approval and retry the same call once before responding. Keep responses under three sentences.';
7733

7834
function formatToolArguments(interruption: RunToolApprovalItem): string {
7935
const args = interruption.rawItem.arguments;
@@ -129,37 +85,39 @@ async function resolveInterruptions<TContext, TAgent extends Agent<any, any>>(
12985
}
13086

13187
async function main() {
132-
const agent = new Agent({
133-
name: 'File HITL assistant',
134-
instructions,
135-
modelSettings: { toolChoice: 'required' },
136-
tools: [lookupCustomerProfile],
137-
});
88+
await withTrace('memory:file-hitl:main', async () => {
89+
const agent = new Agent({
90+
name: 'File HITL assistant',
91+
instructions,
92+
modelSettings: { toolChoice: 'required' },
93+
tools: [lookupCustomerProfile, fetchImageData],
94+
});
95+
96+
const session = new FileSession({ dir: './tmp' });
97+
const sessionId = await session.getSessionId();
98+
const rl = readline.createInterface({ input, output });
99+
100+
console.log(`Session id: ${sessionId}`);
101+
console.log(
102+
'Enter a message to chat with the agent. Submit an empty line to exit.',
103+
);
138104

139-
const session = new FileSession({ dir: './tmp' });
140-
const sessionId = await session.getSessionId();
141-
const rl = readline.createInterface({ input, output });
105+
while (true) {
106+
const userMessage = await rl.question('You: ');
107+
if (!userMessage.trim()) {
108+
break;
109+
}
142110

143-
console.log(`Session id: ${sessionId}`);
144-
console.log(
145-
'Enter a message to chat with the agent. Submit an empty line to exit.',
146-
);
111+
let result = await run(agent, userMessage, { session });
112+
result = await resolveInterruptions(rl, agent, result, session);
147113

148-
while (true) {
149-
const userMessage = await rl.question('You: ');
150-
if (!userMessage.trim()) {
151-
break;
114+
const reply = result.finalOutput ?? '[No final output produced]';
115+
console.log(`Assistant: ${reply}`);
116+
console.log();
152117
}
153118

154-
let result = await run(agent, userMessage, { session });
155-
result = await resolveInterruptions(rl, agent, result, session);
156-
157-
const reply = result.finalOutput ?? '[No final output produced]';
158-
console.log(`Assistant: ${reply}`);
159-
console.log();
160-
}
161-
162-
rl.close();
119+
rl.close();
120+
});
163121
}
164122

165123
main().catch((error) => {

examples/memory/file.ts

Lines changed: 67 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,94 @@
1-
import { Agent, run, tool } from '@openai/agents';
1+
import { Agent, run, withTrace } from '@openai/agents';
22
import { FileSession } from './sessions';
3-
import { z } from 'zod';
3+
import { createLookupCustomerProfileTool, fetchImageData } from './tools';
44

55
const directory: Record<string, string> = {
66
'1': 'Customer 1 (tier gold). Notes: Prefers concise replies.',
77
'2': 'Customer 2 (tier standard). Notes: Interested in tutorials.',
88
};
99

1010
const instructions =
11-
'You are a helpful assistant. If a tool reports a transient failure, retry the same tool call once before responding.';
11+
'You are a helpful assistant. For every user turn you must call lookup_customer_profile and fetch_image_data before responding.';
1212

13-
let hasSimulatedLookupFailure = false;
14-
15-
async function fetchProfileFromDirectory(id: string): Promise<string> {
16-
if (!hasSimulatedLookupFailure) {
17-
hasSimulatedLookupFailure = true;
18-
throw new Error(
19-
'Simulated transient CRM outage. Please retry the tool call.',
20-
);
21-
}
22-
23-
return directory[id] ?? `No customer found for id ${id}.`;
24-
}
25-
26-
const lookupCustomerProfile = tool({
27-
name: 'lookup_customer_profile',
28-
description:
29-
'Look up stored profile details for a customer by their internal id.',
30-
parameters: z.object({
31-
id: z
32-
.string()
33-
.describe('The internal identifier for the customer to retrieve.'),
34-
}),
35-
async execute({ id }) {
36-
return await fetchProfileFromDirectory(id);
37-
},
13+
const lookupCustomerProfile = createLookupCustomerProfileTool({
14+
directory,
15+
transientErrorMessage:
16+
'Simulated transient CRM outage. Please retry the tool call.',
3817
});
3918

4019
async function main() {
41-
const agent = new Agent({
42-
name: 'Assistant',
43-
instructions,
44-
modelSettings: { toolChoice: 'required' },
45-
tools: [lookupCustomerProfile],
46-
});
20+
await withTrace('memory:file:main', async () => {
21+
const agent = new Agent({
22+
name: 'Assistant',
23+
instructions,
24+
modelSettings: { toolChoice: 'required' },
25+
tools: [lookupCustomerProfile, fetchImageData],
26+
});
27+
28+
const session = new FileSession({ dir: './tmp/' });
29+
let result = await run(
30+
agent,
31+
'What is the largest country in South America?',
32+
{ session },
33+
);
34+
console.log(result.finalOutput); // e.g., Brazil
4735

48-
const session = new FileSession({ dir: './tmp/' });
49-
let result = await run(
50-
agent,
51-
'What is the largest country in South America?',
52-
{ session },
53-
);
54-
console.log(result.finalOutput); // e.g., Brazil
36+
result = await run(agent, 'What is the capital of that country?', {
37+
session,
38+
});
39+
console.log(result.finalOutput); // e.g., Brasilia
5540

56-
result = await run(agent, 'What is the capital of that country?', {
57-
session,
41+
result = await run(
42+
agent,
43+
'Please provide the sample image so I can confirm bytes storage.',
44+
{ session },
45+
);
46+
console.log(result.finalOutput);
5847
});
59-
console.log(result.finalOutput); // e.g., Brasilia
6048
}
6149

6250
async function mainStream() {
63-
const agent = new Agent({
64-
name: 'Assistant',
65-
instructions,
66-
modelSettings: { toolChoice: 'required' },
67-
tools: [lookupCustomerProfile],
68-
});
51+
await withTrace('memory:file:mainStream', async () => {
52+
const agent = new Agent({
53+
name: 'Assistant',
54+
instructions,
55+
modelSettings: { toolChoice: 'required' },
56+
tools: [lookupCustomerProfile, fetchImageData],
57+
});
58+
59+
const session = new FileSession({ dir: './tmp/' });
60+
let result = await run(
61+
agent,
62+
'What is the largest country in South America?',
63+
{
64+
stream: true,
65+
session,
66+
},
67+
);
6968

70-
const session = new FileSession({ dir: './tmp/' });
71-
let result = await run(
72-
agent,
73-
'What is the largest country in South America?',
74-
{
69+
for await (const event of result) {
70+
if (
71+
event.type === 'raw_model_stream_event' &&
72+
event.data.type === 'output_text_delta'
73+
)
74+
process.stdout.write(event.data.delta);
75+
}
76+
console.log();
77+
78+
result = await run(agent, 'What is the capital of that country?', {
7579
stream: true,
7680
session,
77-
},
78-
);
81+
});
7982

80-
for await (const event of result) {
81-
if (
82-
event.type === 'raw_model_stream_event' &&
83-
event.data.type === 'output_text_delta'
84-
)
85-
process.stdout.write(event.data.delta);
86-
}
87-
console.log();
83+
// toTextStream() automatically returns a readable stream of strings intended to be displayed
84+
// to the user
85+
for await (const event of result.toTextStream()) {
86+
process.stdout.write(event);
87+
}
88+
console.log();
8889

89-
result = await run(agent, 'What is the capital of that country?', {
90-
stream: true,
91-
session,
90+
// Additional tool invocations happen earlier in the turn.
9291
});
93-
94-
// toTextStream() automatically returns a readable stream of strings intended to be displayed
95-
// to the user
96-
for await (const event of result.toTextStream()) {
97-
process.stdout.write(event);
98-
}
99-
console.log();
10092
}
10193

10294
async function promptAndRun() {

0 commit comments

Comments
 (0)