File size: 4,295 Bytes
3aaa9f7 cbfc437 3aaa9f7 cbfc437 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import ast
import datetime as dt
import math
import re
from functools import lru_cache
from typing import Dict, List
from duckduckgo_search import DDGS
_ALLOWED_MATH_FUNCS = {
"sqrt": math.sqrt,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"log": math.log,
"log10": math.log10,
"exp": math.exp,
"fabs": math.fabs,
"ceil": math.ceil,
"floor": math.floor,
"pow": math.pow,
}
_ALLOWED_CONSTANTS = {
"pi": math.pi,
"e": math.e,
}
class UnsafeExpressionError(ValueError):
pass
def _safe_eval(node: ast.AST) -> float:
if isinstance(node, ast.Expression):
return _safe_eval(node.body)
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return float(node.value)
if isinstance(node, ast.BinOp):
left = _safe_eval(node.left)
right = _safe_eval(node.right)
if isinstance(node.op, ast.Add):
return left + right
if isinstance(node.op, ast.Sub):
return left - right
if isinstance(node.op, ast.Mult):
return left * right
if isinstance(node.op, ast.Div):
return left / right
if isinstance(node.op, ast.FloorDiv):
return left // right
if isinstance(node.op, ast.Mod):
return left % right
if isinstance(node.op, ast.Pow):
return left ** right
raise UnsafeExpressionError("Unsupported binary operator.")
if isinstance(node, ast.UnaryOp):
value = _safe_eval(node.operand)
if isinstance(node.op, ast.UAdd):
return +value
if isinstance(node.op, ast.USub):
return -value
raise UnsafeExpressionError("Unsupported unary operator.")
if isinstance(node, ast.Name):
if node.id in _ALLOWED_CONSTANTS:
return _ALLOWED_CONSTANTS[node.id]
raise UnsafeExpressionError(f"Unknown identifier: {node.id}")
if isinstance(node, ast.Call):
if not isinstance(node.func, ast.Name):
raise UnsafeExpressionError("Only direct function calls are allowed.")
func_name = node.func.id
func = _ALLOWED_MATH_FUNCS.get(func_name)
if func is None:
raise UnsafeExpressionError(f"Unsupported function: {func_name}")
args = [_safe_eval(arg) for arg in node.args]
return float(func(*args))
raise UnsafeExpressionError("Unsupported expression.")
def calculator_tool(expression: str) -> str:
try:
parsed = ast.parse(expression, mode="eval")
value = _safe_eval(parsed)
if value.is_integer():
return str(int(value))
return str(round(value, 10))
except Exception as exc:
return f"Calculation error: {exc}"
@lru_cache(maxsize=128)
def web_search_tool(query: str, max_results: int = 5) -> str:
try:
rows: List[Dict] = []
with DDGS() as ddgs:
for item in ddgs.text(query, max_results=max_results):
rows.append(item)
if not rows:
return "No web results found."
summary_lines = []
for idx, row in enumerate(rows, start=1):
title = row.get("title", "Untitled")
snippet = row.get("body", "").strip()
href = row.get("href", "")
snippet = snippet[:220] + ("..." if len(snippet) > 220 else "")
summary_lines.append(f"{idx}. {title} | {snippet} | Source: {href}")
return "\n".join(summary_lines)
except Exception as exc:
return f"Web search unavailable: {exc}"
def datetime_tool() -> str:
now = dt.datetime.now().astimezone()
return (
f"Current date: {now.strftime('%Y-%m-%d')}\n"
f"Current time: {now.strftime('%H:%M:%S %Z')}"
)
def text_stats_tool(text: str) -> str:
cleaned = text.strip()
if not cleaned:
return "Words: 0\nCharacters: 0\nSentences: 0"
words = len(re.findall(r"\b\w+\b", cleaned))
chars = len(cleaned)
sentences = len([s for s in re.split(r"[.!?]+", cleaned) if s.strip()])
return f"Words: {words}\nCharacters: {chars}\nSentences: {sentences}"
|