Skip to content

Commit 6158083

Browse files
kalenkevichcopybara-github
authored andcommitted
Handle error during the tool execution and return event as part of the functionResponse.error field.
PiperOrigin-RevId: 825682127
1 parent 7fd7088 commit 6158083

File tree

2 files changed

+57
-48
lines changed

2 files changed

+57
-48
lines changed

core/src/agents/functions.ts

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
// TODO - b/436079721: implement traceMergedToolCalls, traceToolCall, tracer.
8-
import {Content, FunctionCall, Part} from '@google/genai';
8+
import {Content, createUserContent, FunctionCall, Part} from '@google/genai';
99

1010
import {InvocationContext} from '../agents/invocation_context.js';
1111
import {createEvent, Event, getFunctionCalls} from '../events/event.js';
@@ -196,39 +196,6 @@ async function callToolAsync(
196196
return await tool.runAsync({args, toolContext});
197197
}
198198

199-
function buildResponseEvent(
200-
tool: BaseTool,
201-
functionResult: any,
202-
toolContext: ToolContext,
203-
invocationContext: InvocationContext,
204-
): Event {
205-
let responseResult = functionResult;
206-
if (typeof functionResult !== 'object' || functionResult == null) {
207-
responseResult = {result: functionResult};
208-
}
209-
210-
const partFunctionResponse: Part = {
211-
functionResponse: {
212-
name: tool.name,
213-
response: responseResult,
214-
id: toolContext.functionCallId,
215-
},
216-
};
217-
218-
const content: Content = {
219-
role: 'user',
220-
parts: [partFunctionResponse],
221-
};
222-
223-
return createEvent({
224-
invocationId: invocationContext.invocationId,
225-
author: invocationContext.agent.name,
226-
content: content,
227-
actions: toolContext.actions,
228-
branch: invocationContext.branch,
229-
});
230-
}
231-
232199
/**
233200
* Handles function calls.
234201
* Runtime behavior to pay attention to:
@@ -307,10 +274,10 @@ export async function handleFunctionCallList({
307274

308275
const {tool, toolContext} = getToolAndContext(
309276
{
310-
invocationContext: invocationContext,
311-
functionCall: functionCall,
312-
toolsDict: toolsDict,
313-
toolConfirmation: toolConfirmation,
277+
invocationContext,
278+
functionCall,
279+
toolsDict,
280+
toolConfirmation,
314281
},
315282
);
316283

@@ -321,6 +288,7 @@ export async function handleFunctionCallList({
321288
// Step 1: Check if plugin before_tool_callback overrides the function
322289
// response.
323290
let functionResponse = null;
291+
let functionResponseError: string|unknown|undefined;
324292
functionResponse =
325293
await invocationContext.pluginManager.runBeforeToolCallback({
326294
tool: tool,
@@ -360,18 +328,23 @@ export async function handleFunctionCallList({
360328
tool: tool,
361329
toolArgs: functionArgs,
362330
toolContext: toolContext,
363-
error: e as Error,
331+
error: e,
364332
},
365333
);
366334

367335
// Set function response to the result of the error callback and
368336
// continue execution, do not shortcut
369337
if (onToolErrorResponse) {
370338
functionResponse = onToolErrorResponse;
339+
} else {
340+
// If the error callback returns undefined, use the error message
341+
// as the function response error.
342+
functionResponseError = e.message;
371343
}
372344
} else {
373-
logger.error('Unknown error on tool execution type', e);
374-
throw e;
345+
// If the error is not an Error, use the error object as the function
346+
// response error.
347+
functionResponseError = e;
375348
}
376349
}
377350
}
@@ -414,13 +387,28 @@ export async function handleFunctionCallList({
414387
continue;
415388
}
416389

390+
if (functionResponseError) {
391+
functionResponse = {error: functionResponseError};
392+
} else if (
393+
typeof functionResponse !== 'object' || functionResponse == null) {
394+
functionResponse = {result: functionResponse};
395+
}
396+
417397
// Builds the function response event.
418-
const functionResponseEvent = buildResponseEvent(
419-
tool,
420-
functionResponse,
421-
toolContext,
422-
invocationContext,
423-
);
398+
const functionResponseEvent = createEvent({
399+
invocationId: invocationContext.invocationId,
400+
author: invocationContext.agent.name,
401+
content: createUserContent({
402+
functionResponse: {
403+
id: toolContext.functionCallId,
404+
name: tool.name,
405+
response: functionResponse,
406+
},
407+
}),
408+
actions: toolContext.actions,
409+
branch: invocationContext.branch,
410+
});
411+
424412
// TODO - b/436079721: implement [traceToolCall]
425413
logger.debug('traceToolCall', {
426414
tool: tool.name,

core/test/agents/functions_test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const errorTool = new FunctionTool({
2525
description: 'error tool',
2626
parameters: z.object({}),
2727
execute: async () => {
28-
throw new Error('tool error');
28+
throw new Error('tool error message content');
2929
},
3030
});
3131

@@ -259,4 +259,25 @@ describe('handleFunctionCallList', () => {
259259
result: 'onToolErrorCallback executed',
260260
});
261261
});
262+
263+
it('should return error message when error is thrown during tool execution, when no plugin onToolErrorCallback is provided',
264+
async () => {
265+
const errorFunctionCall: FunctionCall = {
266+
id: randomIdForTestingOnly(),
267+
name: 'errorTool',
268+
args: {},
269+
};
270+
271+
const event = await handleFunctionCallList({
272+
invocationContext,
273+
functionCalls: [errorFunctionCall],
274+
toolsDict: {'errorTool': errorTool},
275+
beforeToolCallbacks: [],
276+
afterToolCallbacks: [],
277+
});
278+
279+
expect(event!.content!.parts![0].functionResponse!.response).toEqual({
280+
error: 'Error in tool \'errorTool\': tool error message content',
281+
});
282+
});
262283
});

0 commit comments

Comments
 (0)