Make server tool http based

This commit is contained in:
Xin Wang
2026-02-11 11:39:45 +08:00
parent 80e1d24443
commit 4c46793169
9 changed files with 281 additions and 17 deletions

View File

@@ -189,3 +189,23 @@ async def search_knowledge_context(
except Exception as exc:
logger.warning(f"Knowledge search failed (kb_id={kb_id}): {exc}")
return []
async def fetch_tool_resource(tool_id: str) -> Optional[Dict[str, Any]]:
"""Fetch tool resource configuration from backend API."""
base_url = _backend_base_url()
if not base_url or not tool_id:
return None
url = f"{base_url}/api/tools/resources/{tool_id}"
try:
async with aiohttp.ClientSession(timeout=_timeout()) as session:
async with session.get(url) as resp:
if resp.status == 404:
return None
resp.raise_for_status()
data = await resp.json()
return data if isinstance(data, dict) else None
except Exception as exc:
logger.warning(f"Failed to fetch tool resource ({tool_id}): {exc}")
return None

View File

@@ -1,10 +1,13 @@
"""Server-side tool execution helpers."""
import asyncio
import ast
import operator
from datetime import datetime
from typing import Any, Dict
import aiohttp
from app.backend_client import fetch_tool_resource
_BIN_OPS = {
ast.Add: operator.add,
@@ -206,19 +209,6 @@ async def execute_server_tool(tool_call: Dict[str, Any]) -> Dict[str, Any]:
"status": {"code": 422, "message": "invalid_expression"},
}
if tool_name == "current_time":
now = datetime.now().astimezone()
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {
"iso": now.isoformat(),
"local": now.strftime("%Y-%m-%d %H:%M:%S %Z"),
"unix": int(now.timestamp()),
},
"status": {"code": 200, "message": "ok"},
}
if tool_name == "code_interpreter":
code = str(args.get("code") or args.get("expression") or "").strip()
if not code:
@@ -251,6 +241,82 @@ async def execute_server_tool(tool_call: Dict[str, Any]) -> Dict[str, Any]:
"status": {"code": 422, "message": "invalid_code"},
}
if tool_name and tool_name not in {"calculator", "code_interpreter"}:
resource = await fetch_tool_resource(tool_name)
if resource and str(resource.get("category") or "") == "query":
method = str(resource.get("http_method") or "GET").strip().upper()
if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}:
method = "GET"
url = str(resource.get("http_url") or "").strip()
headers = resource.get("http_headers") if isinstance(resource.get("http_headers"), dict) else {}
timeout_ms = resource.get("http_timeout_ms")
try:
timeout_s = max(1.0, float(timeout_ms) / 1000.0)
except Exception:
timeout_s = 10.0
if not url:
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {"error": "http_url not configured"},
"status": {"code": 422, "message": "invalid_tool_config"},
}
request_kwargs: Dict[str, Any] = {}
if method in {"GET", "DELETE"}:
request_kwargs["params"] = args
else:
request_kwargs["json"] = args
try:
timeout = aiohttp.ClientTimeout(total=timeout_s)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.request(method, url, headers=headers, **request_kwargs) as resp:
content_type = str(resp.headers.get("Content-Type") or "").lower()
if "application/json" in content_type:
body: Any = await resp.json()
else:
body = await resp.text()
status_code = int(resp.status)
if 200 <= status_code < 300:
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {
"method": method,
"url": url,
"status_code": status_code,
"response": _json_safe(body),
},
"status": {"code": 200, "message": "ok"},
}
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {
"method": method,
"url": url,
"status_code": status_code,
"response": _json_safe(body),
},
"status": {"code": status_code, "message": "http_error"},
}
except asyncio.TimeoutError:
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {"method": method, "url": url, "error": "request timeout"},
"status": {"code": 504, "message": "http_timeout"},
}
except Exception as exc:
return {
"tool_call_id": call_id,
"name": tool_name,
"output": {"method": method, "url": url, "error": str(exc)},
"status": {"code": 502, "message": "http_request_failed"},
}
return {
"tool_call_id": call_id,
"name": tool_name or "unknown_tool",