ai-assistant-comparison / models /tool_routing.py
logesh28's picture
Upload 19 files
a2854ac verified
Raw
History Blame Contribute Delete
6.3 kB
"""
tool_routing.py
~~~~~~~~~~~~~~~
Modular tool implementation and routing engine.
Provides safe execution of calculation, datetime query, web search,
and context QA lookup tools.
Usage
-----
from models.tool_routing import ToolRouter
router = ToolRouter()
reply, executed = router.route_and_execute("What is the current time?")
if executed:
print("Tool Output:", reply)
"""
from __future__ import annotations
import os
import ast
import operator
from datetime import datetime
import re
from typing import Dict, Any, Tuple
from models.logger_config import logger
# ── Safe Calculator Evaluation ────────────────────────────────────────────────
# Supported AST operators to avoid unsafe eval() execution
_SAFE_OPERATORS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.USub: operator.neg,
}
def _safe_eval(node) -> float:
"""Recursively evaluate AST nodes safely."""
if isinstance(node, ast.Num): # python < 3.8 compatibility
return node.n
elif isinstance(node, ast.Constant): # python 3.8+ compatibility
if isinstance(node.value, (int, float)):
return node.value
raise TypeError("Unsupported constant type")
elif isinstance(node, ast.BinOp):
op_type = type(node.op)
if op_type in _SAFE_OPERATORS:
return _SAFE_OPERATORS[op_type](_safe_eval(node.left), _safe_eval(node.right))
raise ValueError(f"Unsupported operator: {op_type}")
elif isinstance(node, ast.UnaryOp):
op_type = type(node.op)
if op_type in _SAFE_OPERATORS:
return _SAFE_OPERATORS[op_type](_safe_eval(node.operand))
raise ValueError(f"Unsupported operator: {op_type}")
raise ValueError(f"Unsupported syntax: {type(node)}")
def safe_calculate(expression: str) -> str:
"""Parse and calculate math expressions safely using AST representation."""
clean_expr = re.sub(r"[^\d+\-*/().\s]", "", expression)
if not clean_expr.strip():
return "Error: Empty expression or invalid characters."
try:
node = ast.parse(clean_expr, mode="eval").body
res = _safe_eval(node)
return str(res)
except Exception as e:
return f"Error evaluating expression '{expression}': {e}"
# ── Tool Registry ─────────────────────────────────────────────────────────────
class ToolRouter:
"""
Modular execution router for AI assistant tools.
Integrates calculator, datetime, web search, and retrieval QA capabilities.
"""
def __init__(self) -> None:
pass
def tool_calculator(self, expression: str) -> str:
"""Safe math calculation tool."""
logger.info(f"[TOOL] Executing Calculator: {expression}")
return f"[Calculator Result] {safe_calculate(expression)}"
def tool_datetime(self) -> str:
"""Returns the current date and time."""
logger.info("[TOOL] Executing Datetime")
now = datetime.now()
return f"[Datetime Result] Current date and time is {now.strftime('%Y-%m-%d %H:%M:%S')}."
def tool_web_retrieval(self, query: str) -> str:
"""Mocked web retrieval interface returning contextual details."""
logger.info(f"[TOOL] Executing Web Retrieval: {query}")
query_lower = query.lower()
# Simple simulated search summaries
if "weather" in query_lower:
return "[Web Result] Weather forecast is currently sunny, 22Β°C with light wind."
elif "groq" in query_lower:
return "[Web Result] Groq is an AI infrastructure company that builds LPU (Language Processing Unit) chips for high-speed inference."
elif "qwen" in query_lower:
return "[Web Result] Qwen is a series of open-source large language models developed by Alibaba Group, optimized for chat and reasoning."
return f"[Web Result] Web search matches for '{query}': No highly relevant resources found."
def tool_retrieval_qa(self, query: str, context: str) -> str:
"""Extract matching sentence or answer from a local text context."""
logger.info(f"[TOOL] Executing Retrieval QA on query: {query}")
sentences = re.split(r"(?<=[.!?])\s+", context)
matches = []
for word in query.lower().split():
if len(word) > 3: # focus on key terms
for sentence in sentences:
if word in sentence.lower() and sentence not in matches:
matches.append(sentence)
if matches:
return f"[QA Result] Found matching context: " + " ".join(matches[:2])
return "[QA Result] No direct matches found in current text context."
def route_and_execute(self, user_input: str, qa_context: str = "") -> Tuple[str, bool]:
"""
Scan input text for specific tool triggers and execute matched tool.
Returns:
--------
(output_text, executed)
"""
input_lower = user_input.lower()
# 1. Calculator Trigger
calc_match = re.search(r"\b(?:calculate|math|calc)\s+([\d+\-*/().\s]+)", input_lower)
if calc_match:
expr = calc_match.group(1).strip()
return self.tool_calculator(expr), True
# 2. Datetime Trigger
if any(term in input_lower for term in ["current time", "what time is it", "today's date", "what is the date"]):
return self.tool_datetime(), True
# 3. Web Retrieval Trigger
search_match = re.search(r"\b(?:search\s+for|web\s+search|lookup|find\s+info\s+on)\s+(.+)", input_lower)
if search_match:
q = search_match.group(1).strip()
return self.tool_web_retrieval(q), True
# 4. Retrieval QA Trigger (if context is present and question is asked)
if qa_context and any(term in input_lower for term in ["who", "what", "where", "explain", "find"]):
return self.tool_retrieval_qa(user_input, qa_context), True
return "", False