mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 06:14:59 +00:00
Add full Python workflow execution engine with: Core Executor: - engine.py: WorkflowEngine for running n8n configs - n8n_executor.py: N8N-style workflow execution with connections - node_executor.py: Individual node execution with plugin dispatch - loop_executor.py: Loop node execution with iteration control - execution_order.py: Topological sort for node ordering Schema & Validation: - n8n_schema.py: N8N workflow schema types and validation - n8n_converter.py: Legacy to n8n schema conversion Plugin System: - plugin_loader.py: Dynamic plugin loading - plugin_registry.py: Plugin discovery and registration - plugin_map.json: 116 plugin type mappings Runtime & Context: - runtime.py: Workflow runtime container - input_resolver.py: Binding and coercion resolution - value_helpers.py: Value normalization helpers - workflow_context_builder.py: Runtime context assembly - workflow_config_loader.py: Configuration loading - workflow_engine_builder.py: Engine assembly with dependencies Utilities: - tool_calls_handler.py: LLM tool call handling - tool_runner.py: Tool execution with logging - notification_helpers.py: Slack/Discord notifications - workflow_adapter.py: N8N format handling - workflow_graph.py: Node/edge graph for visualization Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
"""Handle tool calls from LLM responses."""
|
|
import json
|
|
|
|
|
|
def handle_tool_calls(resp_msg, tool_map: dict, msgs: dict, args, policies: dict, logger) -> list:
|
|
"""Execute tool calls and return tool result messages."""
|
|
if not resp_msg.tool_calls:
|
|
return []
|
|
|
|
modifying_tools = set(policies.get("modifying_tools", []))
|
|
tool_results = []
|
|
|
|
for tool_call in resp_msg.tool_calls:
|
|
function_name = tool_call.function.name
|
|
call_id = tool_call.id
|
|
payload = json.loads(tool_call.function.arguments)
|
|
logger.trace("Tool call %s payload: %s", function_name, payload)
|
|
|
|
handler = tool_map.get(function_name)
|
|
if not handler:
|
|
msg_template = msgs.get(
|
|
"error_tool_not_found",
|
|
"Tool {name} not found or unavailable."
|
|
)
|
|
msg = msg_template.format(name=function_name)
|
|
logger.error(msg)
|
|
tool_results.append({
|
|
"tool_call_id": call_id,
|
|
"role": "tool",
|
|
"name": function_name,
|
|
"content": msg,
|
|
})
|
|
continue
|
|
|
|
if not args.yolo:
|
|
confirm = input(
|
|
msgs.get(
|
|
"confirm_tool_execution",
|
|
"Do you want to execute {name} with {args}? [y/N]: "
|
|
).format(name=function_name, args=payload)
|
|
)
|
|
if confirm.lower() != "y":
|
|
skipped_template = msgs.get("info_tool_skipped", "Skipping tool: {name}")
|
|
logger.info(skipped_template.format(name=function_name))
|
|
tool_results.append({
|
|
"tool_call_id": call_id,
|
|
"role": "tool",
|
|
"name": function_name,
|
|
"content": "Skipped by user.",
|
|
})
|
|
continue
|
|
|
|
if args.dry_run and function_name in modifying_tools:
|
|
logger.info(
|
|
msgs.get(
|
|
"info_dry_run_skipping",
|
|
"DRY RUN: Skipping state-modifying tool {name}"
|
|
).format(name=function_name)
|
|
)
|
|
tool_results.append({
|
|
"tool_call_id": call_id,
|
|
"role": "tool",
|
|
"name": function_name,
|
|
"content": "Skipped due to dry-run.",
|
|
})
|
|
continue
|
|
|
|
exec_template = msgs.get("info_executing_tool", "Executing tool: {name}")
|
|
logger.info(exec_template.format(name=function_name))
|
|
try:
|
|
result = handler(**payload)
|
|
content = str(result) if result is not None else "Success"
|
|
if hasattr(result, "__iter__") and not isinstance(result, str):
|
|
items = list(result)[:5]
|
|
content = "\n".join([f"- {item}" for item in items])
|
|
logger.info(content)
|
|
elif result is not None:
|
|
logger.info(result)
|
|
|
|
tool_results.append({
|
|
"tool_call_id": call_id,
|
|
"role": "tool",
|
|
"name": function_name,
|
|
"content": content,
|
|
})
|
|
except Exception as error: # pylint: disable=broad-exception-caught
|
|
error_msg = f"Error executing {function_name}: {error}"
|
|
logger.error(error_msg)
|
|
tool_results.append({
|
|
"tool_call_id": call_id,
|
|
"role": "tool",
|
|
"name": function_name,
|
|
"content": error_msg,
|
|
})
|
|
|
|
return tool_results
|