| import json |
| from pathlib import Path |
| from typing import Any |
|
|
| from config import MAX_TOOL_OUTPUT_CHARS |
| from tools.attachment_loader import download_task_file |
| from tools.code_runner import solve_python_output |
| from tools.common import normalize_answer, read_plain_file, truncate_text |
| from tools.direct_rules import solve_direct |
| from tools.sports_solver import solve_sports |
| from tools.spreadsheet_solver import solve_excel_food_sales |
| from tools.structured_web_tools import solve_structured_web, wikipedia_parse_html, wikipedia_search_titles |
| from tools.tool_specs import ToolSpec |
| from tools.web_tools import fetch_url_text, search_web |
|
|
|
|
| def solver_result_to_observation(result, tool_name: str) -> dict[str, Any]: |
| return { |
| "tool": tool_name, |
| "ok": result.solved, |
| "answer": result.answer, |
| "confidence": result.confidence, |
| "source": result.source, |
| "evidence": truncate_text(result.evidence, MAX_TOOL_OUTPUT_CHARS), |
| "error": result.error, |
| } |
|
|
|
|
| def disabled_tool_observation(tool_name: str, reason: str) -> dict[str, Any]: |
| return { |
| "tool": tool_name, |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": f"{tool_name}.disabled", |
| "evidence": "", |
| "error": reason, |
| } |
|
|
|
|
| def direct_answer_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| question = args.get("question") or context["question"] |
| return solver_result_to_observation(solve_direct(question), "direct_answer_tool") |
|
|
|
|
| def python_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| return solver_result_to_observation( |
| solve_python_output( |
| context["question"], |
| args.get("task_id") or context.get("task_id", ""), |
| args.get("file_name") or context.get("file_name", ""), |
| ), |
| "python_tool", |
| ) |
|
|
|
|
| def spreadsheet_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| return solver_result_to_observation( |
| solve_excel_food_sales( |
| context["question"], |
| args.get("task_id") or context.get("task_id", ""), |
| args.get("file_name") or context.get("file_name", ""), |
| ), |
| "spreadsheet_tool", |
| ) |
|
|
|
|
| def wikipedia_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| question = args.get("question") or context["question"] |
| result = solve_structured_web(question) |
| if result.solved: |
| return solver_result_to_observation(result, "wikipedia_tool") |
|
|
| query = args.get("query") or question |
| try: |
| titles = wikipedia_search_titles(query, limit=5) |
| evidence_parts = [f"search_titles={titles}"] |
| if titles: |
| html = wikipedia_parse_html(titles[0]) |
| evidence_parts.append(truncate_text(html, 5000)) |
| return { |
| "tool": "wikipedia_tool", |
| "ok": bool(titles), |
| "answer": None, |
| "confidence": "medium" if titles else "low", |
| "source": "wikipedia_tool.search", |
| "evidence": truncate_text("\n\n".join(evidence_parts), MAX_TOOL_OUTPUT_CHARS), |
| "error": "" if titles else "Wikipedia search returned no titles.", |
| } |
| except Exception as exc: |
| return { |
| "tool": "wikipedia_tool", |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": "wikipedia_tool.error", |
| "evidence": "", |
| "error": str(exc), |
| } |
|
|
|
|
| def sports_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| question = args.get("question") or context["question"] |
| return solver_result_to_observation(solve_sports(question), "sports_tool") |
|
|
|
|
| def web_search_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| query = args.get("query") or context["question"] |
| results = search_web(query, max_results=int(args.get("max_results", 5))) |
| return { |
| "tool": "web_search_tool", |
| "ok": bool(results), |
| "answer": None, |
| "confidence": "medium" if results else "low", |
| "source": "web_search_tool", |
| "evidence": truncate_text(json.dumps(results, ensure_ascii=False), MAX_TOOL_OUTPUT_CHARS), |
| "error": "" if results else "No search results.", |
| } |
|
|
|
|
| def web_read_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| url = args.get("url", "") |
| if not url: |
| return { |
| "tool": "web_read_tool", |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": "web_read_tool", |
| "evidence": "", |
| "error": "Missing url.", |
| } |
| text = fetch_url_text(url, limit=MAX_TOOL_OUTPUT_CHARS) |
| return { |
| "tool": "web_read_tool", |
| "ok": bool(text and not text.startswith("无法读取网页")), |
| "answer": None, |
| "confidence": "medium", |
| "source": "web_read_tool", |
| "evidence": truncate_text(text, MAX_TOOL_OUTPUT_CHARS), |
| "error": "" if not text.startswith("无法读取网页") else text, |
| } |
|
|
|
|
| def attachment_text_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| task_id = args.get("task_id") or context.get("task_id", "") |
| file_name = args.get("file_name") or context.get("file_name", "") |
| file_path, note = download_task_file(task_id, file_name) |
| if not file_path: |
| return { |
| "tool": "attachment_text_tool", |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": "attachment_text_tool", |
| "evidence": note, |
| "error": note, |
| } |
| text = read_plain_file(Path(file_path), limit=MAX_TOOL_OUTPUT_CHARS) |
| return { |
| "tool": "attachment_text_tool", |
| "ok": True, |
| "answer": None, |
| "confidence": "medium", |
| "source": "attachment_text_tool", |
| "evidence": f"{note}\n{text}", |
| "error": "", |
| } |
|
|
|
|
| def final_format_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| answer = normalize_answer(str(args.get("answer", ""))) |
| return { |
| "tool": "final_format_tool", |
| "ok": bool(answer), |
| "answer": answer or None, |
| "confidence": args.get("confidence", "medium"), |
| "source": "final_format_tool", |
| "evidence": "Normalized final answer.", |
| "error": "" if answer else "Missing answer.", |
| } |
|
|
|
|
| def audio_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| return disabled_tool_observation("audio_tool", "当前版本未启用音频转写工具。") |
|
|
|
|
| def video_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| return disabled_tool_observation("video_tool", "当前版本未启用视频/YouTube 工具。") |
|
|
|
|
| def vision_tool(args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| return disabled_tool_observation("vision_tool", "当前版本未启用图片/棋局视觉工具。") |
|
|
|
|
| TOOL_REGISTRY: dict[str, ToolSpec] = { |
| "direct_answer_tool": ToolSpec( |
| name="direct_answer_tool", |
| description="Low-cost deterministic solver for self-contained logic questions: reversed text, non-commutative table, botanical vegetable list.", |
| input_schema={"question": "optional string; omit to use current question"}, |
| run=direct_answer_tool, |
| cost_level="free", |
| ), |
| "python_tool": ToolSpec( |
| name="python_tool", |
| description="Download and execute/analyze an attached Python file, then return the final numeric/text output if recoverable.", |
| input_schema={"task_id": "optional string", "file_name": "optional string"}, |
| run=python_tool, |
| cost_level="free", |
| ), |
| "spreadsheet_tool": ToolSpec( |
| name="spreadsheet_tool", |
| description="Download and analyze an attached Excel spreadsheet; currently solves food-sales-not-drinks style calculations.", |
| input_schema={"task_id": "optional string", "file_name": "optional string"}, |
| run=spreadsheet_tool, |
| cost_level="free", |
| ), |
| "wikipedia_tool": ToolSpec( |
| name="wikipedia_tool", |
| description="Use MediaWiki/Wikipedia APIs and structured parsers for Wikipedia-style questions.", |
| input_schema={"query": "optional search query", "question": "optional question override"}, |
| run=wikipedia_tool, |
| cost_level="low", |
| ), |
| "sports_tool": ToolSpec( |
| name="sports_tool", |
| description="Use sports data APIs; currently handles the 1977 Yankees walks/at-bats question.", |
| input_schema={"question": "optional question override"}, |
| run=sports_tool, |
| cost_level="low", |
| ), |
| "web_search_tool": ToolSpec( |
| name="web_search_tool", |
| description="Search the web and return result titles and URLs.", |
| input_schema={"query": "string", "max_results": "optional integer, default 5"}, |
| run=web_search_tool, |
| cost_level="low", |
| ), |
| "web_read_tool": ToolSpec( |
| name="web_read_tool", |
| description="Read a specific URL and return extracted page text.", |
| input_schema={"url": "string"}, |
| run=web_read_tool, |
| cost_level="low", |
| ), |
| "attachment_text_tool": ToolSpec( |
| name="attachment_text_tool", |
| description="Download an attachment and read it as text when it is a plain text/CSV/JSON/Markdown-like file.", |
| input_schema={"task_id": "optional string", "file_name": "optional string"}, |
| run=attachment_text_tool, |
| cost_level="free", |
| ), |
| "final_format_tool": ToolSpec( |
| name="final_format_tool", |
| description="Normalize a candidate final answer string to submission-safe format.", |
| input_schema={"answer": "string", "confidence": "optional string"}, |
| run=final_format_tool, |
| cost_level="free", |
| ), |
| "audio_tool": ToolSpec( |
| name="audio_tool", |
| description="Disabled audio transcription tool. Calling it reports unavailable.", |
| input_schema={}, |
| run=audio_tool, |
| enabled=False, |
| cost_level="disabled", |
| ), |
| "video_tool": ToolSpec( |
| name="video_tool", |
| description="Disabled video/YouTube analysis tool. Calling it reports unavailable.", |
| input_schema={}, |
| run=video_tool, |
| enabled=False, |
| cost_level="disabled", |
| ), |
| "vision_tool": ToolSpec( |
| name="vision_tool", |
| description="Disabled image/chess vision tool. Calling it reports unavailable.", |
| input_schema={}, |
| run=vision_tool, |
| enabled=False, |
| cost_level="disabled", |
| ), |
| } |
|
|
|
|
| def tool_prompt() -> str: |
| return "\n".join(spec.prompt_block() for spec in TOOL_REGISTRY.values()) |
|
|
|
|
| def execute_tool(action: str, args: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: |
| spec = TOOL_REGISTRY.get(action) |
| if not spec: |
| return { |
| "tool": action, |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": "tool_executor.unknown_tool", |
| "evidence": "", |
| "error": f"Unknown tool: {action}", |
| } |
| if not spec.enabled: |
| return disabled_tool_observation(action, f"Tool {action} is disabled.") |
| try: |
| return spec.run(args or {}, context) |
| except Exception as exc: |
| return { |
| "tool": action, |
| "ok": False, |
| "answer": None, |
| "confidence": "low", |
| "source": f"{action}.exception", |
| "evidence": "", |
| "error": str(exc), |
| } |
|
|