diff --git a/packages/components/nodes/agentflow/Agent/Agent.ts b/packages/components/nodes/agentflow/Agent/Agent.ts index ae494210c..f6013b75f 100644 --- a/packages/components/nodes/agentflow/Agent/Agent.ts +++ b/packages/components/nodes/agentflow/Agent/Agent.ts @@ -182,6 +182,35 @@ class Agent_Agentflow implements INode { agentModel: 'chatGoogleGenerativeAI' } }, + { + label: 'Anthropic Built-in Tools', + name: 'agentToolsBuiltInAnthropic', + type: 'multiOptions', + optional: true, + options: [ + { + label: 'Web Search', + name: 'web_search_20250305', + description: 'Search the web for the latest information' + }, + { + label: 'Web Fetch', + name: 'web_fetch_20250910', + description: 'Retrieve full content from specified web pages' + } + /* + * Not supported yet as we need to get bash_code_execution_tool_result from content: + https://docs.claude.com/en/docs/agents-and-tools/tool-use/code-execution-tool#retrieve-generated-files + { + label: 'Code Interpreter', + name: 'code_execution_20250825', + description: 'Write and run Python code in a sandboxed environment' + }*/ + ], + show: { + agentModel: 'chatAnthropic' + } + }, { label: 'Tools', name: 'agentTools', @@ -803,6 +832,43 @@ class Agent_Agentflow implements INode { } } + const agentToolsBuiltInAnthropic = convertMultiOptionsToStringArray(nodeData.inputs?.agentToolsBuiltInAnthropic) + if (agentToolsBuiltInAnthropic && agentToolsBuiltInAnthropic.length > 0) { + for (const tool of agentToolsBuiltInAnthropic) { + // split _ to get the tool name by removing the last part (date) + const toolName = tool.split('_').slice(0, -1).join('_') + + if (tool === 'code_execution_20250825') { + ;(llmNodeInstance as any).clientOptions = { + defaultHeaders: { + 'anthropic-beta': ['code-execution-2025-08-25', 'files-api-2025-04-14'] + } + } + } + + if (tool === 'web_fetch_20250910') { + ;(llmNodeInstance as any).clientOptions = { + defaultHeaders: { + 'anthropic-beta': ['web-fetch-2025-09-10'] + } + } + } + + const builtInTool: ICommonObject = { + type: tool, + name: toolName + } + ;(toolsInstance as any).push(builtInTool) + ;(availableTools as any).push({ + name: tool, + toolNode: { + label: tool, + name: tool + } + }) + } + } + if (llmNodeInstance && toolsInstance.length > 0) { if (llmNodeInstance.bindTools === undefined) { throw new Error(`Agent needs to have a function calling capable models.`) @@ -1659,6 +1725,10 @@ class Agent_Agentflow implements INode { }> { // Track total tokens used throughout this process let totalTokens = response.usage_metadata?.total_tokens || 0 + const usedTools: IUsedTool[] = [] + let sourceDocuments: Array = [] + let artifacts: any[] = [] + let isWaitingForHumanInput: boolean | undefined if (!response.tool_calls || response.tool_calls.length === 0) { return { response, usedTools: [], sourceDocuments: [], artifacts: [], totalTokens } @@ -1669,6 +1739,23 @@ class Agent_Agentflow implements INode { sseStreamer.streamCalledToolsEvent(chatId, JSON.stringify(response.tool_calls)) } + const toBeRemovedToolCalls = [] + for (let i = 0; i < response.tool_calls.length; i++) { + const toolCall = response.tool_calls[i] + if (!toolCall.id) { + toBeRemovedToolCalls.push(toolCall) + usedTools.push({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: response.content + }) + } + } + + for (const toolCall of toBeRemovedToolCalls) { + response.tool_calls.splice(response.tool_calls.indexOf(toolCall), 1) + } + // Add LLM response with tool calls to messages messages.push({ id: response.id, @@ -1678,11 +1765,6 @@ class Agent_Agentflow implements INode { usage_metadata: response.usage_metadata }) - const usedTools: IUsedTool[] = [] - let sourceDocuments: Array = [] - let artifacts: any[] = [] - let isWaitingForHumanInput: boolean | undefined - // Process each tool call for (let i = 0; i < response.tool_calls.length; i++) { const toolCall = response.tool_calls[i] @@ -1826,6 +1908,17 @@ class Agent_Agentflow implements INode { } } + if (response.tool_calls.length === 0) { + const responseContent = typeof response.content === 'string' ? response.content : JSON.stringify(response.content, null, 2) + return { + response: new AIMessageChunk(responseContent), + usedTools, + sourceDocuments, + artifacts, + totalTokens + } + } + // Get LLM response after tool calls let newResponse: AIMessageChunk @@ -1925,6 +2018,10 @@ class Agent_Agentflow implements INode { isWaitingForHumanInput?: boolean }> { let llmNodeInstance = llmWithoutToolsBind + const usedTools: IUsedTool[] = [] + let sourceDocuments: Array = [] + let artifacts: any[] = [] + let isWaitingForHumanInput: boolean | undefined const lastCheckpointMessages = humanInputAction?.data?.input?.messages ?? [] if (!lastCheckpointMessages.length) { @@ -1950,6 +2047,23 @@ class Agent_Agentflow implements INode { sseStreamer.streamCalledToolsEvent(chatId, JSON.stringify(response.tool_calls)) } + const toBeRemovedToolCalls = [] + for (let i = 0; i < response.tool_calls.length; i++) { + const toolCall = response.tool_calls[i] + if (!toolCall.id) { + toBeRemovedToolCalls.push(toolCall) + usedTools.push({ + tool: toolCall.name || 'tool', + toolInput: toolCall.args, + toolOutput: response.content + }) + } + } + + for (const toolCall of toBeRemovedToolCalls) { + response.tool_calls.splice(response.tool_calls.indexOf(toolCall), 1) + } + // Add LLM response with tool calls to messages messages.push({ id: response.id, @@ -1959,11 +2073,6 @@ class Agent_Agentflow implements INode { usage_metadata: response.usage_metadata }) - const usedTools: IUsedTool[] = [] - let sourceDocuments: Array = [] - let artifacts: any[] = [] - let isWaitingForHumanInput: boolean | undefined - // Process each tool call for (let i = 0; i < response.tool_calls.length; i++) { const toolCall = response.tool_calls[i] diff --git a/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx b/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx index 7941d69b9..3c4cbde50 100644 --- a/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx +++ b/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx @@ -25,7 +25,8 @@ import { IconCode, IconWorldWww, IconPhoto, - IconBrandGoogle + IconBrandGoogle, + IconBrowserCheck } from '@tabler/icons-react' import StopCircleIcon from '@mui/icons-material/StopCircle' import CancelIcon from '@mui/icons-material/Cancel' @@ -154,6 +155,17 @@ const AgentFlowNode = ({ data }) => { } } + const getBuiltInAnthropicToolIcon = (toolName) => { + switch (toolName) { + case 'web_search_20250305': + return + case 'web_fetch_20250910': + return + default: + return null + } + } + useEffect(() => { if (ref.current) { setTimeout(() => { @@ -455,6 +467,16 @@ const AgentFlowNode = ({ data }) => { : [], toolProperty: 'builtInTool', isBuiltInGemini: true + }, + { + tools: data.inputs?.agentToolsBuiltInAnthropic + ? (typeof data.inputs.agentToolsBuiltInAnthropic === 'string' + ? JSON.parse(data.inputs.agentToolsBuiltInAnthropic) + : data.inputs.agentToolsBuiltInAnthropic + ).map((tool) => ({ builtInTool: tool })) + : [], + toolProperty: 'builtInTool', + isBuiltInAnthropic: true } ] @@ -541,6 +563,32 @@ const AgentFlowNode = ({ data }) => { ] } + // Handle built-in Anthropic tools with icons + if (config.isBuiltInAnthropic) { + const icon = getBuiltInAnthropicToolIcon(toolName) + if (!icon) return [] + + return [ + + {icon} + + ] + } + return [