Add Helicone mock client support

- Created helicone_mock_client.py using factory pattern (similar to GCS)
- Integrated mock mode detection and initialization in HeliconeLogger
- Mock client patches HTTPHandler.post to intercept Helicone API calls
- Uses factory pattern for should_use_mock and MockResponse utilities
- Custom HTTPHandler.post patching required since HTTPHandler uses self.client.send()
This commit is contained in:
Alexsander Hamir 2026-01-24 11:47:35 -08:00
parent 05b39a02fb
commit 5706ba9fe2
2 changed files with 110 additions and 1 deletions

View file

@ -4,6 +4,11 @@ import os
import traceback
import litellm
from litellm._logging import verbose_logger
from litellm.integrations.helicone_mock_client import (
should_use_helicone_mock,
create_mock_helicone_client,
)
class HeliconeLogger:
@ -22,6 +27,11 @@ class HeliconeLogger:
def __init__(self):
# Instance variables
self.is_mock_mode = should_use_helicone_mock()
if self.is_mock_mode:
create_mock_helicone_client()
verbose_logger.info("[HELICONE MOCK] Helicone logger initialized in mock mode")
self.provider_url = "https://api.openai.com/v1"
self.key = os.getenv("HELICONE_API_KEY")
self.api_base = os.getenv("HELICONE_API_BASE") or "https://api.hconeai.com"
@ -185,7 +195,10 @@ class HeliconeLogger:
}
response = litellm.module_level_client.post(url, headers=headers, json=data)
if response.status_code == 200:
print_verbose("Helicone Logging - Success!")
if self.is_mock_mode:
print_verbose("[HELICONE MOCK] Helicone Logging - Successfully mocked!")
else:
print_verbose("Helicone Logging - Success!")
else:
print_verbose(
f"Helicone Logging - Error Request was not successful. Status Code: {response.status_code}"

View file

@ -0,0 +1,96 @@
"""
Mock HTTP client for Helicone integration testing.
This module intercepts Helicone API calls and returns successful mock responses,
allowing full code execution without making actual network calls.
Usage:
Set HELICONE_MOCK=true in environment variables or config to enable mock mode.
"""
import os
import time
from litellm._logging import verbose_logger
from litellm.integrations.mock_client_factory import MockClientConfig, MockResponse, create_mock_client_factory
# Use factory for should_use_mock and MockResponse
# HTTPHandler uses self.client.send(), not self.client.post(), so we need custom patching
_config = MockClientConfig(
name="HELICONE",
env_var="HELICONE_MOCK",
default_latency_ms=100,
default_status_code=200,
default_json_data={"status": "success"},
url_matchers=[
".hconeai.com",
"hconeai.com",
".helicone.ai",
"helicone.ai",
],
patch_async_handler=False,
patch_sync_client=False, # HTTPHandler uses self.client.send(), not self.client.post()
)
# Get should_use_mock from factory (but don't use its patching since HTTPHandler is different)
_, should_use_helicone_mock = create_mock_client_factory(_config)
# Store original HTTPHandler.post method (Helicone-specific)
_original_http_handler_post = None
_mocks_initialized = False
# Default mock latency in seconds
_MOCK_LATENCY_SECONDS = float(os.getenv("HELICONE_MOCK_LATENCY_MS", "100")) / 1000.0
def _is_helicone_url(url: str) -> bool:
"""Check if URL is a Helicone API URL."""
url_lower = url.lower()
return "hconeai.com" in url_lower or "helicone.ai" in url_lower
def _mock_http_handler_post(self, url, data=None, json=None, params=None, headers=None, timeout=None, stream=False, files=None, content=None, logging_obj=None):
"""Monkey-patched HTTPHandler.post that intercepts Helicone calls."""
# Only mock Helicone API calls
if isinstance(url, str) and _is_helicone_url(url):
verbose_logger.info(f"[HELICONE MOCK] POST to {url}")
time.sleep(_MOCK_LATENCY_SECONDS)
return MockResponse(
status_code=_config.default_status_code,
json_data=_config.default_json_data,
url=url,
elapsed_seconds=_MOCK_LATENCY_SECONDS
)
if _original_http_handler_post is not None:
return _original_http_handler_post(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, files=files, content=content, logging_obj=logging_obj)
raise RuntimeError("Original HTTPHandler.post not available")
def create_mock_helicone_client():
"""
Monkey-patch HTTPHandler.post to intercept Helicone calls.
Helicone uses litellm.module_level_client which is an HTTPHandler instance.
HTTPHandler.post uses self.client.send(), not self.client.post(), so we need
custom patching (similar to how GCS has custom GET/DELETE handlers).
This function is idempotent - it only initializes mocks once, even if called multiple times.
"""
global _original_http_handler_post, _mocks_initialized
if _mocks_initialized:
return
verbose_logger.debug("[HELICONE MOCK] Initializing Helicone mock client...")
from litellm.llms.custom_httpx.http_handler import HTTPHandler
if _original_http_handler_post is None:
_original_http_handler_post = HTTPHandler.post
HTTPHandler.post = _mock_http_handler_post # type: ignore
verbose_logger.debug("[HELICONE MOCK] Patched HTTPHandler.post")
verbose_logger.debug(f"[HELICONE MOCK] Mock latency set to {_MOCK_LATENCY_SECONDS*1000:.0f}ms")
verbose_logger.debug("[HELICONE MOCK] Helicone mock client initialization complete")
_mocks_initialized = True