mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
bug(core): Do not throw validation errors when building tools in nonInteractiveToolExecutor. (#6363)
This commit is contained in:
@@ -13,6 +13,7 @@ export enum ToolErrorType {
|
||||
UNKNOWN = 'unknown',
|
||||
UNHANDLED_EXCEPTION = 'unhandled_exception',
|
||||
TOOL_NOT_REGISTERED = 'tool_not_registered',
|
||||
EXECUTION_FAILED = 'execution_failed',
|
||||
|
||||
// File System Errors
|
||||
FILE_NOT_FOUND = 'file_not_found',
|
||||
|
||||
@@ -4,8 +4,119 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { hasCycleInSchema } from './tools.js'; // Added getStringifiedResultForDisplay
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import {
|
||||
DeclarativeTool,
|
||||
hasCycleInSchema,
|
||||
Kind,
|
||||
ToolInvocation,
|
||||
ToolResult,
|
||||
} from './tools.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
|
||||
class TestToolInvocation implements ToolInvocation<object, ToolResult> {
|
||||
constructor(
|
||||
readonly params: object,
|
||||
private readonly executeFn: () => Promise<ToolResult>,
|
||||
) {}
|
||||
|
||||
getDescription(): string {
|
||||
return 'A test invocation';
|
||||
}
|
||||
|
||||
toolLocations() {
|
||||
return [];
|
||||
}
|
||||
|
||||
shouldConfirmExecute(): Promise<false> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
execute(): Promise<ToolResult> {
|
||||
return this.executeFn();
|
||||
}
|
||||
}
|
||||
|
||||
class TestTool extends DeclarativeTool<object, ToolResult> {
|
||||
private readonly buildFn: (params: object) => TestToolInvocation;
|
||||
|
||||
constructor(buildFn: (params: object) => TestToolInvocation) {
|
||||
super('test-tool', 'Test Tool', 'A tool for testing', Kind.Other, {});
|
||||
this.buildFn = buildFn;
|
||||
}
|
||||
|
||||
build(params: object): ToolInvocation<object, ToolResult> {
|
||||
return this.buildFn(params);
|
||||
}
|
||||
}
|
||||
|
||||
describe('DeclarativeTool', () => {
|
||||
describe('validateBuildAndExecute', () => {
|
||||
const abortSignal = new AbortController().signal;
|
||||
|
||||
it('should return INVALID_TOOL_PARAMS error if build fails', async () => {
|
||||
const buildError = new Error('Invalid build parameters');
|
||||
const buildFn = vi.fn().mockImplementation(() => {
|
||||
throw buildError;
|
||||
});
|
||||
const tool = new TestTool(buildFn);
|
||||
const params = { foo: 'bar' };
|
||||
|
||||
const result = await tool.validateBuildAndExecute(params, abortSignal);
|
||||
|
||||
expect(buildFn).toHaveBeenCalledWith(params);
|
||||
expect(result).toEqual({
|
||||
llmContent: `Error: Invalid parameters provided. Reason: ${buildError.message}`,
|
||||
returnDisplay: buildError.message,
|
||||
error: {
|
||||
message: buildError.message,
|
||||
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return EXECUTION_FAILED error if execute fails', async () => {
|
||||
const executeError = new Error('Execution failed');
|
||||
const executeFn = vi.fn().mockRejectedValue(executeError);
|
||||
const invocation = new TestToolInvocation({}, executeFn);
|
||||
const buildFn = vi.fn().mockReturnValue(invocation);
|
||||
const tool = new TestTool(buildFn);
|
||||
const params = { foo: 'bar' };
|
||||
|
||||
const result = await tool.validateBuildAndExecute(params, abortSignal);
|
||||
|
||||
expect(buildFn).toHaveBeenCalledWith(params);
|
||||
expect(executeFn).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
llmContent: `Error: Tool call execution failed. Reason: ${executeError.message}`,
|
||||
returnDisplay: executeError.message,
|
||||
error: {
|
||||
message: executeError.message,
|
||||
type: ToolErrorType.EXECUTION_FAILED,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the result of execute on success', async () => {
|
||||
const successResult: ToolResult = {
|
||||
llmContent: 'Success!',
|
||||
returnDisplay: 'Success!',
|
||||
summary: 'Tool executed successfully',
|
||||
};
|
||||
const executeFn = vi.fn().mockResolvedValue(successResult);
|
||||
const invocation = new TestToolInvocation({}, executeFn);
|
||||
const buildFn = vi.fn().mockReturnValue(invocation);
|
||||
const tool = new TestTool(buildFn);
|
||||
const params = { foo: 'bar' };
|
||||
|
||||
const result = await tool.validateBuildAndExecute(params, abortSignal);
|
||||
|
||||
expect(buildFn).toHaveBeenCalledWith(params);
|
||||
expect(executeFn).toHaveBeenCalled();
|
||||
expect(result).toEqual(successResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCycleInSchema', () => {
|
||||
it('should detect a simple direct cycle', () => {
|
||||
|
||||
@@ -200,6 +200,64 @@ export abstract class DeclarativeTool<
|
||||
const invocation = this.build(params);
|
||||
return invocation.execute(signal, updateOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `build` but never throws.
|
||||
* @param params The raw, untrusted parameters from the model.
|
||||
* @returns A `ToolInvocation` instance.
|
||||
*/
|
||||
private silentBuild(
|
||||
params: TParams,
|
||||
): ToolInvocation<TParams, TResult> | Error {
|
||||
try {
|
||||
return this.build(params);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
return e;
|
||||
}
|
||||
return new Error(String(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method that builds and executes the tool in one step.
|
||||
* Never throws.
|
||||
* @param params The raw, untrusted parameters from the model.
|
||||
* @params abortSignal a signal to abort.
|
||||
* @returns The result of the tool execution.
|
||||
*/
|
||||
async validateBuildAndExecute(
|
||||
params: TParams,
|
||||
abortSignal: AbortSignal,
|
||||
): Promise<ToolResult> {
|
||||
const invocationOrError = this.silentBuild(params);
|
||||
if (invocationOrError instanceof Error) {
|
||||
const errorMessage = invocationOrError.message;
|
||||
return {
|
||||
llmContent: `Error: Invalid parameters provided. Reason: ${errorMessage}`,
|
||||
returnDisplay: errorMessage,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await invocationOrError.execute(abortSignal);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
llmContent: `Error: Tool call execution failed. Reason: ${errorMessage}`,
|
||||
returnDisplay: errorMessage,
|
||||
error: {
|
||||
message: errorMessage,
|
||||
type: ToolErrorType.EXECUTION_FAILED,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user