Vertix-agent / nodes /tool_executor.py
Naif Alqubalee
Update space
16ab8a2
Raw
History Blame Contribute Delete
6.91 kB
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
from typing import Any
from config import Settings
from graph.state import AgentState, PlanStep
from models.openrouter_model import OpenRouterModels
from tools.audio_transcriber import transcribe_audio
from tools.image_analyzer import analyze_image
from tools.pdf_reader import read_supported_file
from tools.python_tool import calculate_expression
from tools.web_search import search_web
from tools.webpage_reader import read_webpage
def execute_next_tool(
state: AgentState,
settings: Settings,
models: OpenRouterModels,
) -> dict[str, Any]:
plan = [
dict(step)
for step in state.get("plan", [])
]
index = state.get(
"step_index",
0,
)
if index >= len(plan):
return {
"step_index": index,
}
step: PlanStep = plan[index]
action = step.get(
"action",
"",
)
result: dict[str, Any]
try:
if action == "web_search":
query = (
step.get("query")
or step.get("instruction")
or state["question"]
)
result = search_web(
query,
settings,
)
elif action == "read_webpage":
url = (
step.get("query")
or _best_url(state)
)
result = read_webpage(
url,
settings,
)
elif action == "read_file":
result = read_supported_file(
state.get("file_path"),
state["question"],
)
elif action == "analyze_image":
result = analyze_image(
state.get("file_path"),
state["question"],
models.vision,
)
elif action == "transcribe_audio":
file_path = state.get(
"file_path"
)
if not file_path:
raise ValueError(
"No audio file is available."
)
result = transcribe_audio(
file_path
)
elif action == "execute_python_file":
file_path = state.get(
"file_path"
)
if not file_path:
raise ValueError(
"No Python file is available."
)
result = _execute_python_file(
file_path
)
elif action == "calculate":
expression = (
step.get("expression")
or step.get("query")
or step.get(
"instruction",
"",
)
)
result = calculate_expression(
expression
)
else:
result = {
"ok": False,
"error": (
f"Unsupported action: {action}"
),
}
except Exception as exc:
result = {
"ok": False,
"error": (
f"{type(exc).__name__}: {exc}"
),
}
step["status"] = (
"done"
if result.get("ok")
else "failed"
)
step["result"] = str(
result.get("content")
or result.get("error")
or ""
)[:6000]
plan[index] = step
tool_record = {
"action": action,
"instruction": step.get(
"instruction",
"",
),
**result,
}
evidence = list(
state.get("evidence", [])
)
if result.get("ok"):
evidence.append(
{
"source": result.get(
"source",
action,
),
"content": str(
result.get(
"content",
"",
)
)[:10000],
"metadata": result.get(
"metadata",
{},
),
}
)
return {
"plan": plan,
"step_index": index + 1,
"iteration": (
state.get("iteration", 0)
+ 1
),
"tool_results": [
*state.get(
"tool_results",
[],
),
tool_record,
],
"evidence": evidence,
"error": "",
}
def _execute_python_file(
file_path: str,
timeout: int = 45,
) -> dict[str, Any]:
path = Path(file_path)
if not path.exists():
return {
"ok": False,
"error": (
f"Python file not found: {path}"
),
}
if path.suffix.lower() != ".py":
return {
"ok": False,
"error": (
"Attachment is not a Python file."
),
}
try:
completed = subprocess.run(
[
sys.executable,
str(path),
],
capture_output=True,
text=True,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired:
return {
"ok": False,
"error": (
"Python execution timed out."
),
}
stdout = completed.stdout.strip()
stderr = completed.stderr.strip()
output_lines = [
line.strip()
for line in stdout.splitlines()
if line.strip()
]
final_output = (
output_lines[-1]
if output_lines
else ""
)
if completed.returncode != 0:
return {
"ok": False,
"error": (
stderr
or "Python execution failed."
),
"metadata": {
"stdout": stdout,
"stderr": stderr,
"returncode": (
completed.returncode
),
},
}
return {
"ok": bool(final_output),
"source": str(path),
"content": final_output,
"metadata": {
"stdout": stdout,
"stderr": stderr,
"returncode": (
completed.returncode
),
},
}
def _best_url(
state: AgentState,
) -> str:
for record in reversed(
state.get("tool_results", [])
):
for item in record.get(
"results",
[],
):
if (
isinstance(item, dict)
and item.get("url")
):
return str(item["url"])
raise ValueError(
"No URL is available. Add a web_search "
"step before read_webpage."
)