mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(optional-tool-input): added ability to add optional params (#285)
* feat(optional-tool-input): added ability to add optional params to a tool call * fix(tests): added testing for optional param
This commit is contained in:
+60
-25
@@ -51,6 +51,7 @@ interface ToolParam {
|
||||
type: string
|
||||
description?: string
|
||||
requiredForToolCall: boolean
|
||||
optionalToolInput?: boolean
|
||||
}
|
||||
|
||||
// Assumes the first tool in the access array is the tool to be used
|
||||
@@ -60,17 +61,35 @@ const getToolIdFromBlock = (blockType: string): string | undefined => {
|
||||
return block?.tools.access[0]
|
||||
}
|
||||
|
||||
const getRequiredToolParams = (toolId: string): ToolParam[] => {
|
||||
// Get parameters that need to be displayed in the tool input UI
|
||||
const getToolDisplayParams = (toolId: string): ToolParam[] => {
|
||||
const tool = getTool(toolId)
|
||||
if (!tool) return []
|
||||
|
||||
return Object.entries(tool.params)
|
||||
.filter(([_, param]) => param.requiredForToolCall)
|
||||
.filter(([_, param]) => param.requiredForToolCall || param.optionalToolInput)
|
||||
.map(([paramId, param]) => ({
|
||||
id: paramId,
|
||||
type: param.type,
|
||||
description: param.description,
|
||||
requiredForToolCall: param.requiredForToolCall ?? false,
|
||||
optionalToolInput: param.optionalToolInput ?? false,
|
||||
}))
|
||||
}
|
||||
|
||||
// Keep this for backward compatibility - only get strictly required parameters
|
||||
const getRequiredToolParams = (toolId: string): ToolParam[] => {
|
||||
const tool = getTool(toolId)
|
||||
if (!tool) return []
|
||||
|
||||
return Object.entries(tool.params)
|
||||
.filter(([_, param]) => param.requiredForToolCall || param.optionalToolInput)
|
||||
.map(([paramId, param]) => ({
|
||||
id: paramId,
|
||||
type: param.type,
|
||||
description: param.description,
|
||||
requiredForToolCall: param.requiredForToolCall ?? false,
|
||||
optionalToolInput: param.optionalToolInput ?? false,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -86,12 +105,14 @@ const getCustomToolParams = (schema: any): ToolParam[] => {
|
||||
|
||||
const properties = schema.function.parameters.properties
|
||||
const required = schema.function.parameters.required || []
|
||||
const optionalInputs = schema.function.parameters.optionalToolInputs || []
|
||||
|
||||
return Object.entries(properties).map(([paramId, param]: [string, any]) => ({
|
||||
id: paramId,
|
||||
type: param.type || 'string',
|
||||
description: param.description || '',
|
||||
requiredForToolCall: required.includes(paramId),
|
||||
optionalToolInput: optionalInputs.includes(paramId),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -188,29 +209,33 @@ const formatParamId = (paramId: string): string => {
|
||||
// Special case for common parameter names
|
||||
if (paramId === 'apiKey') return 'API Key'
|
||||
if (paramId === 'apiVersion') return 'API Version'
|
||||
|
||||
|
||||
// Handle underscore and hyphen separated words
|
||||
if (paramId.includes('_') || paramId.includes('-')) {
|
||||
return paramId
|
||||
.split(/[-_]/)
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
|
||||
// Handle single character parameters
|
||||
if (paramId.length === 1) return paramId.toUpperCase()
|
||||
|
||||
|
||||
// Handle camelCase
|
||||
if (/[A-Z]/.test(paramId)) {
|
||||
const result = paramId.replace(/([A-Z])/g, ' $1')
|
||||
return result.charAt(0).toUpperCase() + result.slice(1)
|
||||
.replace(/ Api/g, ' API')
|
||||
.replace(/ Id/g, ' ID')
|
||||
.replace(/ Url/g, ' URL')
|
||||
.replace(/ Uri/g, ' URI')
|
||||
.replace(/ Ui/g, ' UI')
|
||||
return (
|
||||
result.charAt(0).toUpperCase() +
|
||||
result
|
||||
.slice(1)
|
||||
.replace(/ Api/g, ' API')
|
||||
.replace(/ Id/g, ' ID')
|
||||
.replace(/ Url/g, ' URL')
|
||||
.replace(/ Uri/g, ' URI')
|
||||
.replace(/ Ui/g, ' UI')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Simple case - just capitalize first letter
|
||||
return paramId.charAt(0).toUpperCase() + paramId.slice(1)
|
||||
}
|
||||
@@ -265,12 +290,12 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
const defaultOperation = operationOptions.length > 0 ? operationOptions[0].id : undefined
|
||||
|
||||
const toolId = getToolIdFromBlock(toolBlock.type) || toolBlock.type
|
||||
const requiredParams = toolId ? getRequiredToolParams(toolId) : []
|
||||
const displayParams = toolId ? getToolDisplayParams(toolId) : []
|
||||
|
||||
// Use the helper function to initialize parameters with blockId as instanceId
|
||||
const initialParams = initializeToolParams(
|
||||
toolId,
|
||||
requiredParams,
|
||||
displayParams,
|
||||
subBlockStore,
|
||||
isAutoFillEnvVarsEnabled,
|
||||
blockId
|
||||
@@ -399,19 +424,22 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
// New handler for when a custom tool is completely deleted from the store
|
||||
const handleDeleteTool = (toolId: string) => {
|
||||
// Find any instances of this tool in the current workflow and remove them
|
||||
const updatedTools = selectedTools.filter(tool => {
|
||||
const updatedTools = selectedTools.filter((tool) => {
|
||||
// For custom tools, we need to check if it matches the deleted tool
|
||||
if (tool.type === 'custom-tool' &&
|
||||
tool.schema?.function?.name &&
|
||||
customTools.some(customTool =>
|
||||
customTool.id === toolId &&
|
||||
if (
|
||||
tool.type === 'custom-tool' &&
|
||||
tool.schema?.function?.name &&
|
||||
customTools.some(
|
||||
(customTool) =>
|
||||
customTool.id === toolId &&
|
||||
customTool.schema.function.name === tool.schema.function.name
|
||||
)) {
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
// Update the workflow value if any tools were removed
|
||||
if (updatedTools.length !== selectedTools.length) {
|
||||
setValue(updatedTools)
|
||||
@@ -832,8 +860,13 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
{/* Existing parameters */}
|
||||
{requiredParams.map((param) => (
|
||||
<div key={param.id} className="space-y-1.5 relative">
|
||||
<div className="text-xs font-medium text-muted-foreground">
|
||||
<div className="text-xs font-medium text-muted-foreground flex items-center">
|
||||
{formatParamId(param.id)}
|
||||
{param.optionalToolInput && !param.requiredForToolCall && (
|
||||
<span className="ml-1 text-xs text-muted-foreground/60">
|
||||
(Optional)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<ShortInput
|
||||
@@ -995,8 +1028,10 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
|
||||
initialValues={
|
||||
editingToolIndex !== null && selectedTools[editingToolIndex]?.type === 'custom-tool'
|
||||
? {
|
||||
id: customTools.find(tool =>
|
||||
tool.schema.function.name === selectedTools[editingToolIndex].schema.function.name
|
||||
id: customTools.find(
|
||||
(tool) =>
|
||||
tool.schema.function.name ===
|
||||
selectedTools[editingToolIndex].schema.function.name
|
||||
)?.id,
|
||||
schema: selectedTools[editingToolIndex].schema,
|
||||
code: selectedTools[editingToolIndex].code || '',
|
||||
|
||||
@@ -48,6 +48,7 @@ export const scrapeTool: ToolConfig<ScrapeParams, ScrapeResponse> = {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description: 'The URL to scrape content from',
|
||||
},
|
||||
scrapeOptions: {
|
||||
|
||||
@@ -31,6 +31,7 @@ export const slackMessageTool: ToolConfig<SlackMessageParams, SlackMessageRespon
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Target Slack channel (e.g., #general)',
|
||||
optionalToolInput: true,
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface ToolConfig<P = any, R = any> {
|
||||
type: string
|
||||
required?: boolean
|
||||
requiredForToolCall?: boolean
|
||||
optionalToolInput?: boolean
|
||||
default?: any
|
||||
description?: string
|
||||
}
|
||||
|
||||
+20
-4
@@ -156,6 +156,7 @@ export function validateToolRequest(
|
||||
}
|
||||
|
||||
// Ensure all required parameters for tool call are provided
|
||||
// Note: optionalToolInput parameters are not checked here as they're optional
|
||||
for (const [paramName, paramConfig] of Object.entries(tool.params)) {
|
||||
if (paramConfig.requiredForToolCall && !(paramName in params)) {
|
||||
throw new Error(`Parameter "${paramName}" is required for ${toolId} but was not provided`)
|
||||
@@ -180,13 +181,28 @@ export function createParamSchema(customTool: any): Record<string, any> {
|
||||
const params: Record<string, any> = {}
|
||||
|
||||
if (customTool.schema.function?.parameters?.properties) {
|
||||
Object.entries(customTool.schema.function.parameters.properties).forEach(([key, config]: [string, any]) => {
|
||||
params[key] = {
|
||||
const properties = customTool.schema.function.parameters.properties;
|
||||
const required = customTool.schema.function.parameters.required || [];
|
||||
const optionalToolInputs = customTool.schema.function.parameters.optionalToolInputs || [];
|
||||
|
||||
Object.entries(properties).forEach(([key, config]: [string, any]) => {
|
||||
const isRequired = required.includes(key);
|
||||
const isOptionalInput = optionalToolInputs.includes(key);
|
||||
|
||||
// Create the base parameter configuration
|
||||
const paramConfig: Record<string, any> = {
|
||||
type: config.type || 'string',
|
||||
required: customTool.schema.function.parameters.required?.includes(key) || false,
|
||||
requiredForToolCall: customTool.schema.function.parameters.required?.includes(key) || false,
|
||||
required: isRequired,
|
||||
requiredForToolCall: isRequired,
|
||||
description: config.description || '',
|
||||
};
|
||||
|
||||
// Only add optionalToolInput if it's true to maintain backward compatibility with tests
|
||||
if (isOptionalInput) {
|
||||
paramConfig.optionalToolInput = true;
|
||||
}
|
||||
|
||||
params[key] = paramConfig;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user