Compare commits

...

2 Commits

2 changed files with 158 additions and 45 deletions

View File

@ -81,7 +81,7 @@ class Agent_Agentflow implements INode {
constructor() { constructor() {
this.label = 'Agent' this.label = 'Agent'
this.name = 'agentAgentflow' this.name = 'agentAgentflow'
this.version = 2.0 this.version = 2.1
this.type = 'Agent' this.type = 'Agent'
this.category = 'Agent Flows' this.category = 'Agent Flows'
this.description = 'Dynamically choose and utilize tools during runtime, enabling multi-step reasoning' this.description = 'Dynamically choose and utilize tools during runtime, enabling multi-step reasoning'
@ -161,6 +161,27 @@ class Agent_Agentflow implements INode {
agentModel: 'chatOpenAI' agentModel: 'chatOpenAI'
} }
}, },
{
label: 'Gemini Built-in Tools',
name: 'agentToolsBuiltInGemini',
type: 'multiOptions',
optional: true,
options: [
{
label: 'URL Context',
name: 'urlContext',
description: 'Extract content from given URLs'
},
{
label: 'Google Search',
name: 'googleSearch',
description: 'Search real-time web content'
}
],
show: {
agentModel: 'chatGoogleGenerativeAI'
}
},
{ {
label: 'Tools', label: 'Tools',
name: 'agentTools', name: 'agentTools',
@ -765,6 +786,23 @@ class Agent_Agentflow implements INode {
} }
} }
const agentToolsBuiltInGemini = convertMultiOptionsToStringArray(nodeData.inputs?.agentToolsBuiltInGemini)
if (agentToolsBuiltInGemini && agentToolsBuiltInGemini.length > 0) {
for (const tool of agentToolsBuiltInGemini) {
const builtInTool: ICommonObject = {
[tool]: {}
}
;(toolsInstance as any).push(builtInTool)
;(availableTools as any).push({
name: tool,
toolNode: {
label: tool,
name: tool
}
})
}
}
if (llmNodeInstance && toolsInstance.length > 0) { if (llmNodeInstance && toolsInstance.length > 0) {
if (llmNodeInstance.bindTools === undefined) { if (llmNodeInstance.bindTools === undefined) {
throw new Error(`Agent needs to have a function calling capable models.`) throw new Error(`Agent needs to have a function calling capable models.`)
@ -1177,53 +1215,80 @@ class Agent_Agentflow implements INode {
return builtInUsedTools return builtInUsedTools
} }
const { output, tools } = response.response_metadata const { output, tools, groundingMetadata, urlContextMetadata } = response.response_metadata
if (!output || !Array.isArray(output) || output.length === 0 || !tools || !Array.isArray(tools) || tools.length === 0) { // Handle OpenAI built-in tools
return builtInUsedTools if (output && Array.isArray(output) && output.length > 0 && tools && Array.isArray(tools) && tools.length > 0) {
for (const outputItem of output) {
if (outputItem.type && outputItem.type.endsWith('_call')) {
let toolInput = outputItem.action ?? outputItem.code
let toolOutput = outputItem.status === 'completed' ? 'Success' : outputItem.status
// Handle image generation calls specially
if (outputItem.type === 'image_generation_call') {
// Create input summary for image generation
toolInput = {
prompt: outputItem.revised_prompt || 'Image generation request',
size: outputItem.size || '1024x1024',
quality: outputItem.quality || 'standard',
output_format: outputItem.output_format || 'png'
}
// Check if image has been processed (base64 replaced with file path)
if (outputItem.result && !outputItem.result.startsWith('data:') && !outputItem.result.includes('base64')) {
toolOutput = `Image generated and saved`
} else {
toolOutput = `Image generated (base64)`
}
}
// Remove "_call" suffix to get the base tool name
const baseToolName = outputItem.type.replace('_call', '')
// Find matching tool that includes the base name in its type
const matchingTool = tools.find((tool) => tool.type && tool.type.includes(baseToolName))
if (matchingTool) {
// Check for duplicates
if (builtInUsedTools.find((tool) => tool.tool === matchingTool.type)) {
continue
}
builtInUsedTools.push({
tool: matchingTool.type,
toolInput,
toolOutput
})
}
}
}
} }
for (const outputItem of output) { // Handle Gemini googleSearch tool
if (outputItem.type && outputItem.type.endsWith('_call')) { if (groundingMetadata && groundingMetadata.webSearchQueries && Array.isArray(groundingMetadata.webSearchQueries)) {
let toolInput = outputItem.action ?? outputItem.code // Check for duplicates
let toolOutput = outputItem.status === 'completed' ? 'Success' : outputItem.status if (!builtInUsedTools.find((tool) => tool.tool === 'googleSearch')) {
builtInUsedTools.push({
tool: 'googleSearch',
toolInput: {
queries: groundingMetadata.webSearchQueries
},
toolOutput: `Searched for: ${groundingMetadata.webSearchQueries.join(', ')}`
})
}
}
// Handle image generation calls specially // Handle Gemini urlContext tool
if (outputItem.type === 'image_generation_call') { if (urlContextMetadata && urlContextMetadata.urlMetadata && Array.isArray(urlContextMetadata.urlMetadata)) {
// Create input summary for image generation // Check for duplicates
toolInput = { if (!builtInUsedTools.find((tool) => tool.tool === 'urlContext')) {
prompt: outputItem.revised_prompt || 'Image generation request', builtInUsedTools.push({
size: outputItem.size || '1024x1024', tool: 'urlContext',
quality: outputItem.quality || 'standard', toolInput: {
output_format: outputItem.output_format || 'png' urlMetadata: urlContextMetadata.urlMetadata
} },
toolOutput: `Processed ${urlContextMetadata.urlMetadata.length} URL(s)`
// Check if image has been processed (base64 replaced with file path) })
if (outputItem.result && !outputItem.result.startsWith('data:') && !outputItem.result.includes('base64')) {
toolOutput = `Image generated and saved`
} else {
toolOutput = `Image generated (base64)`
}
}
// Remove "_call" suffix to get the base tool name
const baseToolName = outputItem.type.replace('_call', '')
// Find matching tool that includes the base name in its type
const matchingTool = tools.find((tool) => tool.type && tool.type.includes(baseToolName))
if (matchingTool) {
// Check for duplicates
if (builtInUsedTools.find((tool) => tool.tool === matchingTool.type)) {
continue
}
builtInUsedTools.push({
tool: matchingTool.type,
toolInput,
toolOutput
})
}
} }
} }

View File

@ -24,7 +24,8 @@ import {
IconAlertCircleFilled, IconAlertCircleFilled,
IconCode, IconCode,
IconWorldWww, IconWorldWww,
IconPhoto IconPhoto,
IconBrandGoogle
} from '@tabler/icons-react' } from '@tabler/icons-react'
import StopCircleIcon from '@mui/icons-material/StopCircle' import StopCircleIcon from '@mui/icons-material/StopCircle'
import CancelIcon from '@mui/icons-material/Cancel' import CancelIcon from '@mui/icons-material/Cancel'
@ -142,6 +143,17 @@ const AgentFlowNode = ({ data }) => {
} }
} }
const getBuiltInGeminiToolIcon = (toolName) => {
switch (toolName) {
case 'urlContext':
return <IconWorldWww size={14} color={'white'} />
case 'googleSearch':
return <IconBrandGoogle size={14} color={'white'} />
default:
return null
}
}
useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
setTimeout(() => { setTimeout(() => {
@ -433,6 +445,16 @@ const AgentFlowNode = ({ data }) => {
: [], : [],
toolProperty: 'builtInTool', toolProperty: 'builtInTool',
isBuiltInOpenAI: true isBuiltInOpenAI: true
},
{
tools: data.inputs?.agentToolsBuiltInGemini
? (typeof data.inputs.agentToolsBuiltInGemini === 'string'
? JSON.parse(data.inputs.agentToolsBuiltInGemini)
: data.inputs.agentToolsBuiltInGemini
).map((tool) => ({ builtInTool: tool }))
: [],
toolProperty: 'builtInTool',
isBuiltInGemini: true
} }
] ]
@ -493,6 +515,32 @@ const AgentFlowNode = ({ data }) => {
] ]
} }
// Handle built-in Gemini tools with icons
if (config.isBuiltInGemini) {
const icon = getBuiltInGeminiToolIcon(toolName)
if (!icon) return []
return [
<Box
key={`tool-${configIndex}-${toolIndex}`}
sx={{
width: 20,
height: 20,
borderRadius: '50%',
backgroundColor: customization.isDarkMode
? darken(data.color, 0.5)
: darken(data.color, 0.2),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: 0.2
}}
>
{icon}
</Box>
]
}
return [ return [
<Box <Box
key={`tool-${configIndex}-${toolIndex}`} key={`tool-${configIndex}-${toolIndex}`}