Skip to content

Commit 5923cf8

Browse files
authored
fix: improve tool input validation logic to return specific error message (#578)
* fix: improve tool input validation logic to return specific error message * fix: refactor
1 parent e1b19d2 commit 5923cf8

File tree

2 files changed

+61
-9
lines changed

2 files changed

+61
-9
lines changed

runtimes/runtimes/agent.test.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,32 @@ describe('Agent Tools', () => {
3434
} as const
3535
const SOME_TOOL_HANDLER = async (_: { test: string }) => true
3636

37+
const TOOL_SPEC_WITH_ARRAY_TYPE = {
38+
name: 'fsReplace',
39+
description: 'test',
40+
inputSchema: {
41+
type: 'object',
42+
properties: {
43+
diffs: {
44+
type: 'array',
45+
items: {
46+
type: 'object',
47+
properties: {
48+
oldStr: {
49+
type: 'string',
50+
},
51+
newStr: {
52+
type: 'string',
53+
},
54+
},
55+
},
56+
},
57+
},
58+
required: ['diffs'],
59+
},
60+
} as const
61+
const DIFFS_TOOL_HANDLER = async (_: { diffs: [] }) => true
62+
3763
beforeEach(() => {
3864
AGENT = newAgent()
3965
})
@@ -59,11 +85,30 @@ describe('Agent Tools', () => {
5985
}, Error)
6086
})
6187

62-
it('should throw if the tool input does not validate', async () => {
88+
it('should throw specific message if the tool input does not validate', async () => {
89+
AGENT.addTool(TOOL_SPEC_WITH_ARRAY_TYPE, DIFFS_TOOL_HANDLER)
90+
await assert.rejects(
91+
async () => {
92+
await AGENT.runTool(TOOL_SPEC_WITH_ARRAY_TYPE.name, {
93+
diffs: '[{"oldStr": "toReplace"}, {"newStr": "newContet"}]',
94+
})
95+
},
96+
(error: Error) => {
97+
assert.ok(error.message === 'fsReplace tool input validation failed: /diffs: must be array')
98+
return true
99+
}
100+
)
101+
63102
AGENT.addTool(SOME_TOOL_SPEC, SOME_TOOL_HANDLER)
64-
assert.rejects(async () => {
65-
await AGENT.runTool(SOME_TOOL_SPEC.name, { test: 1 })
66-
}, Error)
103+
await assert.rejects(
104+
async () => {
105+
await AGENT.runTool(SOME_TOOL_SPEC.name, { test: 1 })
106+
},
107+
(error: Error) => {
108+
assert.ok(error.message === 'test tool input validation failed: /test: must be string')
109+
return true
110+
}
111+
)
67112
})
68113

69114
it('should execute the named tool if multiple are available', async () => {

runtimes/runtimes/agent.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Ajv from 'ajv'
1+
import Ajv, { ErrorObject, ValidateFunction } from 'ajv'
22
import {
33
Agent,
44
BedrockTools,
@@ -14,7 +14,7 @@ type Tool<T, R> = {
1414
name: string
1515
description: string
1616
inputSchema: ObjectSchema
17-
validate: (input: T, token?: CancellationToken) => boolean
17+
validate: (input: T, token?: CancellationToken) => boolean | ValidateFunction<unknown>['errors']
1818
invoke: (input: T, token?: CancellationToken, updates?: WritableStream) => Promise<R>
1919
}
2020

@@ -30,7 +30,8 @@ export const newAgent = (): Agent => {
3030
const validator = ajv.compile(spec.inputSchema)
3131
const tool = {
3232
validate: (input: InferSchema<S['inputSchema']>) => {
33-
return validator(input)
33+
const isValid = validator(input)
34+
return validator.errors ?? isValid
3435
},
3536
invoke: handler,
3637
name: spec.name,
@@ -47,8 +48,14 @@ export const newAgent = (): Agent => {
4748
throw new Error(`Tool ${toolName} not found`)
4849
}
4950

50-
if (!tool.validate(input, token)) {
51-
throw new Error(`Input for tool ${toolName} is invalid`)
51+
const validateResult = tool.validate(input, token)
52+
if (validateResult !== true) {
53+
const errorDetails =
54+
((validateResult as ValidateFunction['errors']) || [])
55+
.map((err: ErrorObject) => `${err.instancePath || 'root'}: ${err.message}`)
56+
.join('\n') || `\nReceived: ${input}`
57+
58+
throw new Error(`${toolName} tool input validation failed: ${errorDetails}`)
5259
}
5360

5461
return tool.invoke(input, token, updates)

0 commit comments

Comments
 (0)