tools.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """工具注册表 — 每个工具是真实的可执行函数 + OpenAI function schema"""
  2. import json, subprocess, sys, tempfile, os, re, time
  3. try:
  4. from tavily import TavilyClient
  5. except ImportError:
  6. TavilyClient = None
  7. from config import TAVILY_API_KEY
  8. # ── 工具函数 ──
  9. def web_search(query: str, max_results: int = 2) -> str:
  10. """真实调用 Tavily Search API(带重试)"""
  11. if not TavilyClient:
  12. return "[错误] tavily-python 未安装"
  13. for attempt in range(3):
  14. try:
  15. client = TavilyClient(api_key=TAVILY_API_KEY)
  16. resp = client.search(query=query, max_results=max_results, search_depth="basic")
  17. results = resp.get("results", [])
  18. if not results:
  19. return "未找到相关结果"
  20. output = []
  21. for i, r in enumerate(results, 1):
  22. title = r.get('title', '')
  23. content = r.get('content', '')[:250]
  24. output.append(f"[{i}] {title}\n {content}")
  25. return "\n\n".join(output)
  26. except Exception as e:
  27. if attempt < 2:
  28. time.sleep(1)
  29. continue
  30. return f"[搜索错误] {e}"
  31. return "[搜索错误] 重试3次均失败"
  32. def calculator(expression: str) -> str:
  33. """安全数学计算"""
  34. allowed = set("0123456789+-*/.() %")
  35. cleaned = expression.replace("^", "**")
  36. if not all(c in allowed or c.isalpha() for c in cleaned):
  37. return "[错误] 不允许的字符"
  38. try:
  39. result = eval(cleaned, {"__builtins__": {}}, {"abs": abs, "round": round, "pow": pow})
  40. return f"{expression} = {result}"
  41. except Exception as e:
  42. return f"[计算错误] {e}"
  43. def run_python(code: str) -> str:
  44. """在隔离子进程中执行 Python 代码(限时 10 秒)"""
  45. with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
  46. f.write(code)
  47. f.flush()
  48. tmp_path = f.name
  49. try:
  50. result = subprocess.run(
  51. [sys.executable, tmp_path],
  52. capture_output=True, text=True, timeout=10,
  53. env={**os.environ, 'PYTHONIOENCODING': 'utf-8'}
  54. )
  55. output = result.stdout
  56. if result.returncode != 0:
  57. output += f"\n[STDERR] {result.stderr}"
  58. return output.strip() or "(无输出)"
  59. except subprocess.TimeoutExpired:
  60. return "[错误] 执行超时(10秒限制)"
  61. finally:
  62. os.unlink(tmp_path)
  63. def text_analyze(text: str) -> str:
  64. """文本统计分析"""
  65. words = text.split()
  66. lines = text.strip().split('\n')
  67. chars = len(text)
  68. return (
  69. f"字符数: {chars}\n"
  70. f"词数: {len(words)}\n"
  71. f"行数: {len(lines)}\n"
  72. f"平均行长: {chars / max(len(lines), 1):.1f}"
  73. )
  74. # ── OpenAI Function Schemas ──
  75. TOOL_SCHEMAS = {
  76. "web_search": {
  77. "type": "function",
  78. "function": {
  79. "name": "web_search",
  80. "description": "搜索互联网获取实时信息。每次调用返回2条最相关的结果。建议一次搜索用精准关键词,避免多次搜索。",
  81. "parameters": {
  82. "type": "object",
  83. "properties": {
  84. "query": {"type": "string", "description": "搜索关键词,建议用英文或中英混合"},
  85. "max_results": {"type": "integer", "description": "返回结果数量,默认2", "default": 2}
  86. },
  87. "required": ["query"]
  88. }
  89. }
  90. },
  91. "calculator": {
  92. "type": "function",
  93. "function": {
  94. "name": "calculator",
  95. "description": "执行数学计算(支持加减乘除、幂运算、括号)",
  96. "parameters": {
  97. "type": "object",
  98. "properties": {
  99. "expression": {"type": "string", "description": "数学表达式,如 (3 + 5) * 7"}
  100. },
  101. "required": ["expression"]
  102. }
  103. }
  104. },
  105. "run_python": {
  106. "type": "function",
  107. "function": {
  108. "name": "run_python",
  109. "description": "执行 Python 代码并返回输出。用于数据处理、分析、生成报告等",
  110. "parameters": {
  111. "type": "object",
  112. "properties": {
  113. "code": {"type": "string", "description": "要执行的 Python 代码"}
  114. },
  115. "required": ["code"]
  116. }
  117. }
  118. },
  119. "text_analyze": {
  120. "type": "function",
  121. "function": {
  122. "name": "text_analyze",
  123. "description": "对文本进行统计分析(字符数、词数、行数等)",
  124. "parameters": {
  125. "type": "object",
  126. "properties": {
  127. "text": {"type": "string", "description": "要分析的文本"}
  128. },
  129. "required": ["text"]
  130. }
  131. }
  132. },
  133. }
  134. # 函数映射
  135. TOOL_FUNCTIONS = {
  136. "web_search": web_search,
  137. "calculator": calculator,
  138. "run_python": run_python,
  139. "text_analyze": text_analyze,
  140. }
  141. def execute_tool(name: str, args: dict) -> str:
  142. """统一工具执行入口"""
  143. fn = TOOL_FUNCTIONS.get(name)
  144. if not fn:
  145. return f"[错误] 未知工具: {name}"
  146. try:
  147. return fn(**args)
  148. except Exception as e:
  149. return f"[工具执行错误] {name}: {e}"