diff --git a/tools/debug_helpers.py b/tools/debug_helpers.py new file mode 100644 index 000000000..f1934fd5b --- /dev/null +++ b/tools/debug_helpers.py @@ -0,0 +1,104 @@ +"""Shared debug session infrastructure for Hermes tools. + +Replaces the identical DEBUG_MODE / _log_debug_call / _save_debug_log / +get_debug_session_info boilerplate previously duplicated across web_tools, +vision_tools, mixture_of_agents_tool, and image_generation_tool. + +Usage in a tool module: + + from tools.debug_helpers import DebugSession + + _debug = DebugSession("web_tools", env_var="WEB_TOOLS_DEBUG") + + # Log a call (no-op when debug mode is off) + _debug.log_call("web_search", {"query": q, "results": len(r)}) + + # Save the debug log (no-op when debug mode is off) + _debug.save() + + # Expose debug info to external callers + def get_debug_session_info(): + return _debug.get_session_info() +""" + +import datetime +import json +import logging +import os +import uuid +from pathlib import Path +from typing import Any, Dict + +logger = logging.getLogger(__name__) + + +class DebugSession: + """Per-tool debug session that records tool calls to a JSON log file. + + Activated by a tool-specific environment variable (e.g. WEB_TOOLS_DEBUG=true). + When disabled, all methods are cheap no-ops. + """ + + def __init__(self, tool_name: str, *, env_var: str) -> None: + self.tool_name = tool_name + self.enabled = os.getenv(env_var, "false").lower() == "true" + self.session_id = str(uuid.uuid4()) if self.enabled else "" + self.log_dir = Path("./logs") + self._calls: list[Dict[str, Any]] = [] + self._start_time = datetime.datetime.now().isoformat() if self.enabled else "" + + if self.enabled: + self.log_dir.mkdir(exist_ok=True) + logger.debug("%s debug mode enabled - Session ID: %s", + tool_name, self.session_id) + + @property + def active(self) -> bool: + return self.enabled + + def log_call(self, call_name: str, call_data: Dict[str, Any]) -> None: + """Append a tool-call entry to the in-memory log.""" + if not self.enabled: + return + self._calls.append({ + "timestamp": datetime.datetime.now().isoformat(), + "tool_name": call_name, + **call_data, + }) + + def save(self) -> None: + """Flush the in-memory log to a JSON file in the logs directory.""" + if not self.enabled: + return + try: + filename = f"{self.tool_name}_debug_{self.session_id}.json" + filepath = self.log_dir / filename + payload = { + "session_id": self.session_id, + "start_time": self._start_time, + "end_time": datetime.datetime.now().isoformat(), + "debug_enabled": True, + "total_calls": len(self._calls), + "tool_calls": self._calls, + } + with open(filepath, "w", encoding="utf-8") as f: + json.dump(payload, f, indent=2, ensure_ascii=False) + logger.debug("%s debug log saved: %s", self.tool_name, filepath) + except Exception as e: + logger.error("Error saving %s debug log: %s", self.tool_name, e) + + def get_session_info(self) -> Dict[str, Any]: + """Return a summary dict suitable for returning from get_debug_session_info().""" + if not self.enabled: + return { + "enabled": False, + "session_id": None, + "log_path": None, + "total_calls": 0, + } + return { + "enabled": True, + "session_id": self.session_id, + "log_path": str(self.log_dir / f"{self.tool_name}_debug_{self.session_id}.json"), + "total_calls": len(self._calls), + } diff --git a/tools/image_generation_tool.py b/tools/image_generation_tool.py index 43e5815ab..6279b2a79 100644 --- a/tools/image_generation_tool.py +++ b/tools/image_generation_tool.py @@ -32,11 +32,10 @@ import json import logging import os import asyncio -import uuid import datetime -from pathlib import Path from typing import Dict, Any, Optional, Union import fal_client +from tools.debug_helpers import DebugSession logger = logging.getLogger(__name__) @@ -78,65 +77,7 @@ VALID_IMAGE_SIZES = [ VALID_OUTPUT_FORMATS = ["jpeg", "png"] VALID_ACCELERATION_MODES = ["none", "regular", "high"] -# Debug mode configuration -DEBUG_MODE = os.getenv("IMAGE_TOOLS_DEBUG", "false").lower() == "true" -DEBUG_SESSION_ID = str(uuid.uuid4()) -DEBUG_LOG_PATH = Path("./logs") -DEBUG_DATA = { - "session_id": DEBUG_SESSION_ID, - "start_time": datetime.datetime.now().isoformat(), - "debug_enabled": DEBUG_MODE, - "tool_calls": [] -} if DEBUG_MODE else None - -# Create logs directory if debug mode is enabled -if DEBUG_MODE: - DEBUG_LOG_PATH.mkdir(exist_ok=True) - logger.debug("Image generation debug mode enabled - Session ID: %s", DEBUG_SESSION_ID) - - -def _log_debug_call(tool_name: str, call_data: Dict[str, Any]) -> None: - """ - Log a debug call entry to the global debug data structure. - - Args: - tool_name (str): Name of the tool being called - call_data (Dict[str, Any]): Data about the call including parameters and results - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - call_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "tool_name": tool_name, - **call_data - } - - DEBUG_DATA["tool_calls"].append(call_entry) - - -def _save_debug_log() -> None: - """ - Save the current debug data to a JSON file in the logs directory. - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - try: - debug_filename = f"image_tools_debug_{DEBUG_SESSION_ID}.json" - debug_filepath = DEBUG_LOG_PATH / debug_filename - - # Update end time - DEBUG_DATA["end_time"] = datetime.datetime.now().isoformat() - DEBUG_DATA["total_calls"] = len(DEBUG_DATA["tool_calls"]) - - with open(debug_filepath, 'w', encoding='utf-8') as f: - json.dump(DEBUG_DATA, f, indent=2, ensure_ascii=False) - - logger.debug("Image generation debug log saved: %s", debug_filepath) - - except Exception as e: - logger.error("Error saving image generation debug log: %s", e) +_debug = DebugSession("image_tools", env_var="IMAGE_TOOLS_DEBUG") def _validate_parameters( @@ -423,8 +364,8 @@ async def image_generate_tool( debug_call_data["generation_time"] = generation_time # Log debug information - _log_debug_call("image_generate_tool", debug_call_data) - _save_debug_log() + _debug.log_call("image_generate_tool", debug_call_data) + _debug.save() return json.dumps(response_data, indent=2, ensure_ascii=False) @@ -441,8 +382,8 @@ async def image_generate_tool( debug_call_data["error"] = error_msg debug_call_data["generation_time"] = generation_time - _log_debug_call("image_generate_tool", debug_call_data) - _save_debug_log() + _debug.log_call("image_generate_tool", debug_call_data) + _debug.save() return json.dumps(response_data, indent=2, ensure_ascii=False) @@ -484,20 +425,7 @@ def get_debug_session_info() -> Dict[str, Any]: Returns: Dict[str, Any]: Dictionary containing debug session information """ - if not DEBUG_MODE or not DEBUG_DATA: - return { - "enabled": False, - "session_id": None, - "log_path": None, - "total_calls": 0 - } - - return { - "enabled": True, - "session_id": DEBUG_SESSION_ID, - "log_path": str(DEBUG_LOG_PATH / f"image_tools_debug_{DEBUG_SESSION_ID}.json"), - "total_calls": len(DEBUG_DATA["tool_calls"]) - } + return _debug.get_session_info() if __name__ == "__main__": @@ -532,9 +460,9 @@ if __name__ == "__main__": print(f"šŸ” Auto-upscaling with: {UPSCALER_MODEL} ({UPSCALER_FACTOR}x)") # Show debug mode status - if DEBUG_MODE: - print(f"šŸ› Debug mode ENABLED - Session ID: {DEBUG_SESSION_ID}") - print(f" Debug logs will be saved to: ./logs/image_tools_debug_{DEBUG_SESSION_ID}.json") + if _debug.active: + print(f"šŸ› Debug mode ENABLED - Session ID: {_debug.session_id}") + print(f" Debug logs will be saved to: ./logs/image_tools_debug_{_debug.session_id}.json") else: print("šŸ› Debug mode disabled (set IMAGE_TOOLS_DEBUG=true to enable)") diff --git a/tools/mixture_of_agents_tool.py b/tools/mixture_of_agents_tool.py index 0bb123995..3e388c23f 100644 --- a/tools/mixture_of_agents_tool.py +++ b/tools/mixture_of_agents_tool.py @@ -49,30 +49,13 @@ import json import logging import os import asyncio -import uuid import datetime -from pathlib import Path from typing import Dict, Any, List, Optional -from openai import AsyncOpenAI -from hermes_constants import OPENROUTER_BASE_URL +from tools.openrouter_client import get_async_client as _get_openrouter_client, check_api_key as check_openrouter_api_key +from tools.debug_helpers import DebugSession logger = logging.getLogger(__name__) -_openrouter_client = None - -def _get_openrouter_client(): - """Get or create the OpenRouter client (lazy initialization).""" - global _openrouter_client - if _openrouter_client is None: - api_key = os.getenv("OPENROUTER_API_KEY") - if not api_key: - raise ValueError("OPENROUTER_API_KEY environment variable not set") - _openrouter_client = AsyncOpenAI( - api_key=api_key, - base_url=OPENROUTER_BASE_URL - ) - return _openrouter_client - # Configuration for MoA processing # Reference models - these generate diverse initial responses in parallel (OpenRouter slugs) REFERENCE_MODELS = [ @@ -97,65 +80,7 @@ AGGREGATOR_SYSTEM_PROMPT = """You have been provided with a set of responses fro Responses from models:""" -# Debug mode configuration -DEBUG_MODE = os.getenv("MOA_TOOLS_DEBUG", "false").lower() == "true" -DEBUG_SESSION_ID = str(uuid.uuid4()) -DEBUG_LOG_PATH = Path("./logs") -DEBUG_DATA = { - "session_id": DEBUG_SESSION_ID, - "start_time": datetime.datetime.now().isoformat(), - "debug_enabled": DEBUG_MODE, - "tool_calls": [] -} if DEBUG_MODE else None - -# Create logs directory if debug mode is enabled -if DEBUG_MODE: - DEBUG_LOG_PATH.mkdir(exist_ok=True) - logger.debug("MoA debug mode enabled - Session ID: %s", DEBUG_SESSION_ID) - - -def _log_debug_call(tool_name: str, call_data: Dict[str, Any]) -> None: - """ - Log a debug call entry to the global debug data structure. - - Args: - tool_name (str): Name of the tool being called - call_data (Dict[str, Any]): Data about the call including parameters and results - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - call_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "tool_name": tool_name, - **call_data - } - - DEBUG_DATA["tool_calls"].append(call_entry) - - -def _save_debug_log() -> None: - """ - Save the current debug data to a JSON file in the logs directory. - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - try: - debug_filename = f"moa_tools_debug_{DEBUG_SESSION_ID}.json" - debug_filepath = DEBUG_LOG_PATH / debug_filename - - # Update end time - DEBUG_DATA["end_time"] = datetime.datetime.now().isoformat() - DEBUG_DATA["total_calls"] = len(DEBUG_DATA["tool_calls"]) - - with open(debug_filepath, 'w', encoding='utf-8') as f: - json.dump(DEBUG_DATA, f, indent=2, ensure_ascii=False) - - logger.debug("MoA debug log saved: %s", debug_filepath) - - except Exception as e: - logger.error("Error saving MoA debug log: %s", e) +_debug = DebugSession("moa_tools", env_var="MOA_TOOLS_DEBUG") def _construct_aggregator_prompt(system_prompt: str, responses: List[str]) -> str: @@ -432,8 +357,8 @@ async def mixture_of_agents_tool( debug_call_data["models_used"] = result["models_used"] # Log debug information - _log_debug_call("mixture_of_agents_tool", debug_call_data) - _save_debug_log() + _debug.log_call("mixture_of_agents_tool", debug_call_data) + _debug.save() return json.dumps(result, indent=2, ensure_ascii=False) @@ -458,22 +383,12 @@ async def mixture_of_agents_tool( debug_call_data["error"] = error_msg debug_call_data["processing_time_seconds"] = processing_time - _log_debug_call("mixture_of_agents_tool", debug_call_data) - _save_debug_log() + _debug.log_call("mixture_of_agents_tool", debug_call_data) + _debug.save() return json.dumps(result, indent=2, ensure_ascii=False) -def check_openrouter_api_key() -> bool: - """ - Check if the OpenRouter API key is available in environment variables. - - Returns: - bool: True if API key is set, False otherwise - """ - return bool(os.getenv("OPENROUTER_API_KEY")) - - def check_moa_requirements() -> bool: """ Check if all requirements for MoA tools are met. @@ -491,20 +406,7 @@ def get_debug_session_info() -> Dict[str, Any]: Returns: Dict[str, Any]: Dictionary containing debug session information """ - if not DEBUG_MODE or not DEBUG_DATA: - return { - "enabled": False, - "session_id": None, - "log_path": None, - "total_calls": 0 - } - - return { - "enabled": True, - "session_id": DEBUG_SESSION_ID, - "log_path": str(DEBUG_LOG_PATH / f"moa_tools_debug_{DEBUG_SESSION_ID}.json"), - "total_calls": len(DEBUG_DATA["tool_calls"]) - } + return _debug.get_session_info() def get_available_models() -> Dict[str, List[str]]: @@ -570,9 +472,9 @@ if __name__ == "__main__": print(f" šŸ“Š Minimum successful models: {config['min_successful_references']}") # Show debug mode status - if DEBUG_MODE: - print(f"\nšŸ› Debug mode ENABLED - Session ID: {DEBUG_SESSION_ID}") - print(f" Debug logs will be saved to: ./logs/moa_tools_debug_{DEBUG_SESSION_ID}.json") + if _debug.active: + print(f"\nšŸ› Debug mode ENABLED - Session ID: {_debug.session_id}") + print(f" Debug logs will be saved to: ./logs/moa_tools_debug_{_debug.session_id}.json") else: print("\nšŸ› Debug mode disabled (set MOA_TOOLS_DEBUG=true to enable)") diff --git a/tools/openrouter_client.py b/tools/openrouter_client.py new file mode 100644 index 000000000..c960f4f4a --- /dev/null +++ b/tools/openrouter_client.py @@ -0,0 +1,34 @@ +"""Shared OpenRouter API client for Hermes tools. + +Provides a single lazy-initialized AsyncOpenAI client that all tool modules +can share, eliminating the duplicated _get_openrouter_client() / +_get_summarizer_client() pattern previously copy-pasted across web_tools, +vision_tools, mixture_of_agents_tool, and session_search_tool. +""" + +import os + +from openai import AsyncOpenAI +from hermes_constants import OPENROUTER_BASE_URL + +_client: AsyncOpenAI | None = None + + +def get_async_client() -> AsyncOpenAI: + """Return a shared AsyncOpenAI client pointed at OpenRouter. + + The client is created lazily on first call and reused thereafter. + Raises ValueError if OPENROUTER_API_KEY is not set. + """ + global _client + if _client is None: + api_key = os.getenv("OPENROUTER_API_KEY") + if not api_key: + raise ValueError("OPENROUTER_API_KEY environment variable not set") + _client = AsyncOpenAI(api_key=api_key, base_url=OPENROUTER_BASE_URL) + return _client + + +def check_api_key() -> bool: + """Check whether the OpenRouter API key is present.""" + return bool(os.getenv("OPENROUTER_API_KEY")) diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index bac2f710f..23b972870 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -22,29 +22,12 @@ import os import logging from typing import Dict, Any, List, Optional -from openai import AsyncOpenAI -from hermes_constants import OPENROUTER_BASE_URL +from tools.openrouter_client import get_async_client as _get_client SUMMARIZER_MODEL = "google/gemini-3-flash-preview" MAX_SESSION_CHARS = 100_000 MAX_SUMMARY_TOKENS = 2000 -_summarizer_client = None - - -def _get_client() -> AsyncOpenAI: - """Lazy-init the summarizer client (shared with web_tools pattern).""" - global _summarizer_client - if _summarizer_client is None: - api_key = os.getenv("OPENROUTER_API_KEY") - if not api_key: - raise ValueError("OPENROUTER_API_KEY not set") - _summarizer_client = AsyncOpenAI( - api_key=api_key, - base_url=OPENROUTER_BASE_URL, - ) - return _summarizer_client - def _format_conversation(messages: List[Dict[str, Any]]) -> str: """Format session messages into a readable transcript for summarization.""" diff --git a/tools/vision_tools.py b/tools/vision_tools.py index fd0f8fd2d..fe5198e5a 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -32,93 +32,19 @@ import logging import os import asyncio import uuid -import datetime import base64 from pathlib import Path from typing import Dict, Any, Optional -from openai import AsyncOpenAI import httpx -from hermes_constants import OPENROUTER_BASE_URL +from tools.openrouter_client import get_async_client as _get_openrouter_client, check_api_key as check_openrouter_api_key +from tools.debug_helpers import DebugSession logger = logging.getLogger(__name__) -_openrouter_client = None - -def _get_openrouter_client(): - """Get or create the OpenRouter client (lazy initialization).""" - global _openrouter_client - if _openrouter_client is None: - api_key = os.getenv("OPENROUTER_API_KEY") - if not api_key: - raise ValueError("OPENROUTER_API_KEY environment variable not set") - _openrouter_client = AsyncOpenAI( - api_key=api_key, - base_url=OPENROUTER_BASE_URL - ) - return _openrouter_client - # Configuration for vision processing DEFAULT_VISION_MODEL = "google/gemini-3-flash-preview" -# Debug mode configuration -DEBUG_MODE = os.getenv("VISION_TOOLS_DEBUG", "false").lower() == "true" -DEBUG_SESSION_ID = str(uuid.uuid4()) -DEBUG_LOG_PATH = Path("./logs") -DEBUG_DATA = { - "session_id": DEBUG_SESSION_ID, - "start_time": datetime.datetime.now().isoformat(), - "debug_enabled": DEBUG_MODE, - "tool_calls": [] -} if DEBUG_MODE else None - -# Create logs directory if debug mode is enabled -if DEBUG_MODE: - DEBUG_LOG_PATH.mkdir(exist_ok=True) - logger.debug("Vision debug mode enabled - Session ID: %s", DEBUG_SESSION_ID) - - -def _log_debug_call(tool_name: str, call_data: Dict[str, Any]) -> None: - """ - Log a debug call entry to the global debug data structure. - - Args: - tool_name (str): Name of the tool being called - call_data (Dict[str, Any]): Data about the call including parameters and results - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - call_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "tool_name": tool_name, - **call_data - } - - DEBUG_DATA["tool_calls"].append(call_entry) - - -def _save_debug_log() -> None: - """ - Save the current debug data to a JSON file in the logs directory. - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - try: - debug_filename = f"vision_tools_debug_{DEBUG_SESSION_ID}.json" - debug_filepath = DEBUG_LOG_PATH / debug_filename - - # Update end time - DEBUG_DATA["end_time"] = datetime.datetime.now().isoformat() - DEBUG_DATA["total_calls"] = len(DEBUG_DATA["tool_calls"]) - - with open(debug_filepath, 'w', encoding='utf-8') as f: - json.dump(DEBUG_DATA, f, indent=2, ensure_ascii=False) - - logger.debug("Vision debug log saved: %s", debug_filepath) - - except Exception as e: - logger.error("Error saving vision debug log: %s", e) +_debug = DebugSession("vision_tools", env_var="VISION_TOOLS_DEBUG") def _validate_image_url(url: str) -> bool: @@ -395,8 +321,8 @@ async def vision_analyze_tool( debug_call_data["analysis_length"] = analysis_length # Log debug information - _log_debug_call("vision_analyze_tool", debug_call_data) - _save_debug_log() + _debug.log_call("vision_analyze_tool", debug_call_data) + _debug.save() return json.dumps(result, indent=2, ensure_ascii=False) @@ -411,8 +337,8 @@ async def vision_analyze_tool( } debug_call_data["error"] = error_msg - _log_debug_call("vision_analyze_tool", debug_call_data) - _save_debug_log() + _debug.log_call("vision_analyze_tool", debug_call_data) + _debug.save() return json.dumps(result, indent=2, ensure_ascii=False) @@ -426,16 +352,6 @@ async def vision_analyze_tool( logger.warning("Could not delete temporary file: %s", cleanup_error) -def check_openrouter_api_key() -> bool: - """ - Check if the OpenRouter API key is available in environment variables. - - Returns: - bool: True if API key is set, False otherwise - """ - return bool(os.getenv("OPENROUTER_API_KEY")) - - def check_vision_requirements() -> bool: """ Check if all requirements for vision tools are met. @@ -453,20 +369,7 @@ def get_debug_session_info() -> Dict[str, Any]: Returns: Dict[str, Any]: Dictionary containing debug session information """ - if not DEBUG_MODE or not DEBUG_DATA: - return { - "enabled": False, - "session_id": None, - "log_path": None, - "total_calls": 0 - } - - return { - "enabled": True, - "session_id": DEBUG_SESSION_ID, - "log_path": str(DEBUG_LOG_PATH / f"vision_tools_debug_{DEBUG_SESSION_ID}.json"), - "total_calls": len(DEBUG_DATA["tool_calls"]) - } + return _debug.get_session_info() if __name__ == "__main__": @@ -491,9 +394,9 @@ if __name__ == "__main__": print(f"🧠 Using model: {DEFAULT_VISION_MODEL}") # Show debug mode status - if DEBUG_MODE: - print(f"šŸ› Debug mode ENABLED - Session ID: {DEBUG_SESSION_ID}") - print(f" Debug logs will be saved to: ./logs/vision_tools_debug_{DEBUG_SESSION_ID}.json") + if _debug.active: + print(f"šŸ› Debug mode ENABLED - Session ID: {_debug.session_id}") + print(f" Debug logs will be saved to: ./logs/vision_tools_debug_{_debug.session_id}.json") else: print("šŸ› Debug mode disabled (set VISION_TOOLS_DEBUG=true to enable)") diff --git a/tools/web_tools.py b/tools/web_tools.py index 0dceb13fc..f898c0f3d 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -45,18 +45,13 @@ import logging import os import re import asyncio -import uuid -import datetime -from pathlib import Path from typing import List, Dict, Any, Optional from firecrawl import Firecrawl -from openai import AsyncOpenAI -from hermes_constants import OPENROUTER_BASE_URL +from tools.openrouter_client import get_async_client as _get_openrouter_client +from tools.debug_helpers import DebugSession logger = logging.getLogger(__name__) -# Initialize Firecrawl client lazily (only when needed) -# This prevents import errors when FIRECRAWL_API_KEY is not set _firecrawl_client = None def _get_firecrawl_client(): @@ -69,85 +64,10 @@ def _get_firecrawl_client(): _firecrawl_client = Firecrawl(api_key=api_key) return _firecrawl_client -# Initialize OpenRouter API client lazily (only when needed) -_summarizer_client = None - -def _get_summarizer_client(): - """Get or create the summarizer client (lazy initialization).""" - global _summarizer_client - if _summarizer_client is None: - api_key = os.getenv("OPENROUTER_API_KEY") - if not api_key: - raise ValueError("OPENROUTER_API_KEY environment variable not set") - _summarizer_client = AsyncOpenAI( - api_key=api_key, - base_url=OPENROUTER_BASE_URL - ) - return _summarizer_client - -# Configuration for LLM processing DEFAULT_SUMMARIZER_MODEL = "google/gemini-3-flash-preview" DEFAULT_MIN_LENGTH_FOR_SUMMARIZATION = 5000 -# Debug mode configuration -DEBUG_MODE = os.getenv("WEB_TOOLS_DEBUG", "false").lower() == "true" -DEBUG_SESSION_ID = str(uuid.uuid4()) -DEBUG_LOG_PATH = Path("./logs") -DEBUG_DATA = { - "session_id": DEBUG_SESSION_ID, - "start_time": datetime.datetime.now().isoformat(), - "debug_enabled": DEBUG_MODE, - "tool_calls": [] -} if DEBUG_MODE else None - -# Create logs directory if debug mode is enabled -if DEBUG_MODE: - DEBUG_LOG_PATH.mkdir(exist_ok=True) - logger.info("Debug mode enabled - Session ID: %s", DEBUG_SESSION_ID) - - -def _log_debug_call(tool_name: str, call_data: Dict[str, Any]) -> None: - """ - Log a debug call entry to the global debug data structure. - - Args: - tool_name (str): Name of the tool being called - call_data (Dict[str, Any]): Data about the call including parameters and results - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - call_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "tool_name": tool_name, - **call_data - } - - DEBUG_DATA["tool_calls"].append(call_entry) - - -def _save_debug_log() -> None: - """ - Save the current debug data to a JSON file in the logs directory. - """ - if not DEBUG_MODE or not DEBUG_DATA: - return - - try: - debug_filename = f"web_tools_debug_{DEBUG_SESSION_ID}.json" - debug_filepath = DEBUG_LOG_PATH / debug_filename - - # Update end time - DEBUG_DATA["end_time"] = datetime.datetime.now().isoformat() - DEBUG_DATA["total_calls"] = len(DEBUG_DATA["tool_calls"]) - - with open(debug_filepath, 'w', encoding='utf-8') as f: - json.dump(DEBUG_DATA, f, indent=2, ensure_ascii=False) - - logger.debug("Debug log saved: %s", debug_filepath) - - except Exception as e: - logger.error("Error saving debug log: %s", e) +_debug = DebugSession("web_tools", env_var="WEB_TOOLS_DEBUG") async def process_content_with_llm( @@ -303,7 +223,7 @@ Create a markdown summary that captures all key information in a well-organized, for attempt in range(max_retries): try: - response = await _get_summarizer_client().chat.completions.create( + response = await _get_openrouter_client().chat.completions.create( model=model, messages=[ {"role": "system", "content": system_prompt}, @@ -422,7 +342,7 @@ Synthesize these into ONE cohesive, comprehensive summary that: Create a single, unified markdown summary.""" try: - response = await _get_summarizer_client().chat.completions.create( + response = await _get_openrouter_client().chat.completions.create( model=model, messages=[ {"role": "system", "content": "You synthesize multiple summaries into one cohesive, comprehensive summary. Be thorough but concise."}, @@ -597,8 +517,8 @@ def web_search_tool(query: str, limit: int = 5) -> str: debug_call_data["final_response_size"] = len(result_json) # Log debug information - _log_debug_call("web_search_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_search_tool", debug_call_data) + _debug.save() return result_json @@ -607,8 +527,8 @@ def web_search_tool(query: str, limit: int = 5) -> str: logger.error("%s", error_msg) debug_call_data["error"] = error_msg - _log_debug_call("web_search_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_search_tool", debug_call_data) + _debug.save() return json.dumps({"error": error_msg}, ensure_ascii=False) @@ -859,8 +779,8 @@ async def web_extract_tool( debug_call_data["processing_applied"].append("base64_image_removal") # Log debug information - _log_debug_call("web_extract_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_extract_tool", debug_call_data) + _debug.save() return cleaned_result @@ -869,8 +789,8 @@ async def web_extract_tool( logger.error("%s", error_msg) debug_call_data["error"] = error_msg - _log_debug_call("web_extract_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_extract_tool", debug_call_data) + _debug.save() return json.dumps({"error": error_msg}, ensure_ascii=False) @@ -1149,8 +1069,8 @@ async def web_crawl_tool( debug_call_data["processing_applied"].append("base64_image_removal") # Log debug information - _log_debug_call("web_crawl_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_crawl_tool", debug_call_data) + _debug.save() return cleaned_result @@ -1159,8 +1079,8 @@ async def web_crawl_tool( logger.error("%s", error_msg) debug_call_data["error"] = error_msg - _log_debug_call("web_crawl_tool", debug_call_data) - _save_debug_log() + _debug.log_call("web_crawl_tool", debug_call_data) + _debug.save() return json.dumps({"error": error_msg}, ensure_ascii=False) @@ -1187,30 +1107,8 @@ def check_nous_api_key() -> bool: def get_debug_session_info() -> Dict[str, Any]: - """ - Get information about the current debug session. - - Returns: - Dict[str, Any]: Dictionary containing debug session information: - - enabled: Whether debug mode is enabled - - session_id: Current session UUID (if enabled) - - log_path: Path where debug logs are saved (if enabled) - - total_calls: Number of tool calls logged so far (if enabled) - """ - if not DEBUG_MODE or not DEBUG_DATA: - return { - "enabled": False, - "session_id": None, - "log_path": None, - "total_calls": 0 - } - - return { - "enabled": True, - "session_id": DEBUG_SESSION_ID, - "log_path": str(DEBUG_LOG_PATH / f"web_tools_debug_{DEBUG_SESSION_ID}.json"), - "total_calls": len(DEBUG_DATA["tool_calls"]) - } + """Get information about the current debug session.""" + return _debug.get_session_info() if __name__ == "__main__": @@ -1249,9 +1147,9 @@ if __name__ == "__main__": print(f" Default min length for processing: {DEFAULT_MIN_LENGTH_FOR_SUMMARIZATION} chars") # Show debug mode status - if DEBUG_MODE: - print(f"šŸ› Debug mode ENABLED - Session ID: {DEBUG_SESSION_ID}") - print(f" Debug logs will be saved to: ./logs/web_tools_debug_{DEBUG_SESSION_ID}.json") + if _debug.active: + print(f"šŸ› Debug mode ENABLED - Session ID: {_debug.session_id}") + print(f" Debug logs will be saved to: {_debug.log_dir}/web_tools_debug_{_debug.session_id}.json") else: print("šŸ› Debug mode disabled (set WEB_TOOLS_DEBUG=true to enable)")