fix(agent): filter out empty params to ensure LLM can set tool params at runtime (#2288)

This commit is contained in:
Waleed
2025-12-10 11:38:54 -08:00
committed by GitHub
parent bbbb13af7e
commit 163db5cf50
3 changed files with 64 additions and 4 deletions

View File

@@ -1011,10 +1011,21 @@ export function prepareToolExecution(
toolParams: Record<string, any>
executionParams: Record<string, any>
} {
// Filter out empty/null/undefined values from user params
// so that cleared fields don't override LLM-generated values
const filteredUserParams: Record<string, any> = {}
if (tool.params) {
for (const [key, value] of Object.entries(tool.params)) {
if (value !== undefined && value !== null && value !== '') {
filteredUserParams[key] = value
}
}
}
// User-provided params take precedence over LLM-generated params
const toolParams = {
...llmArgs,
...tool.params,
...filteredUserParams,
}
// Add system parameters for execution

View File

@@ -177,6 +177,44 @@ describe('Tool Parameters Utils', () => {
expect(merged.message).toBe('Hello world')
expect(merged.timeout).toBe(10000)
})
it.concurrent('should skip empty strings so LLM values are used', () => {
const userProvided = {
apiKey: 'user-key',
channel: '', // User cleared this field
message: '', // User cleared this field too
}
const llmGenerated = {
message: 'Hello world',
channel: '#random',
timeout: 10000,
}
const merged = mergeToolParameters(userProvided, llmGenerated)
expect(merged.apiKey).toBe('user-key') // Non-empty user value preserved
expect(merged.channel).toBe('#random') // LLM value used because user value was empty
expect(merged.message).toBe('Hello world') // LLM value used because user value was empty
expect(merged.timeout).toBe(10000)
})
it.concurrent('should skip null and undefined values', () => {
const userProvided = {
apiKey: 'user-key',
channel: null,
message: undefined,
}
const llmGenerated = {
message: 'Hello world',
channel: '#random',
}
const merged = mergeToolParameters(userProvided, llmGenerated)
expect(merged.apiKey).toBe('user-key')
expect(merged.channel).toBe('#random') // LLM value used
expect(merged.message).toBe('Hello world') // LLM value used
})
})
describe('validateToolParameters', () => {

View File

@@ -572,16 +572,27 @@ export function createExecutionToolSchema(toolConfig: ToolConfig): ToolSchema {
}
/**
* Merges user-provided parameters with LLM-generated parameters
* Merges user-provided parameters with LLM-generated parameters.
* User-provided parameters take precedence, but empty strings are skipped
* so that LLM-generated values are used when user clears a field.
*/
export function mergeToolParameters(
userProvidedParams: Record<string, unknown>,
llmGeneratedParams: Record<string, unknown>
): Record<string, unknown> {
// User-provided parameters take precedence
// Filter out empty strings from user-provided params
// so that cleared fields don't override LLM values
const filteredUserParams: Record<string, unknown> = {}
for (const [key, value] of Object.entries(userProvidedParams)) {
if (value !== undefined && value !== null && value !== '') {
filteredUserParams[key] = value
}
}
// User-provided parameters take precedence (after filtering empty values)
return {
...llmGeneratedParams,
...userProvidedParams,
...filteredUserParams,
}
}