Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/angjustinl_cctrader.json
T

1 line
20 KiB
JSON

{"repo": "angjustinl/cctrader", "files": {"src/trader/ai/llm_client.py": {"ec8735d907ba89cb73c74cc1265238ade3dfdf23": {"t": 1785746717.5748045, "n": 9728, "h": "743c9d579d65f9a8", "c": "\"\"\"LLM client for DeepSeek and OpenAI compatible APIs.\n\nProvides a unified interface for interacting with LLM APIs with\nautomatic retry logic, error handling, and response parsing.\n\"\"\"\nimport os\nimport time\nimport json\nfrom typing import Dict, List, Optional, Any, Callable\nfrom decimal import Decimal\nfrom dataclasses import dataclass\nfrom pydantic import BaseModel, Field\nimport httpx\n\n\nclass LLMMessage(BaseModel):\n \"\"\"Message in a conversation with an LLM.\"\"\"\n role: str = Field(description=\"Role of the message sender (system, user, assistant)\")\n content: str = Field(description=\"Content of the message\")\n\n\nclass LLMFunctionCall(BaseModel):\n \"\"\"Function call requested by the LLM.\"\"\"\n name: str = Field(description=\"Name of the function to call\")\n arguments: Dict[str, Any] = Field(description=\"Arguments for the function\")\n\n\nclass LLMResponse(BaseModel):\n \"\"\"Response from an LLM.\"\"\"\n content: Optional[str] = Field(default=None, description=\"Text content of the response\")\n function_calls: List[LLMFunctionCall] = Field(default_factory=list, description=\"Function calls requested\")\n model: str = Field(description=\"Model used for the response\")\n usage: Dict[str, int] = Field(default_factory=dict, description=\"Token usage statistics\")\n prompt_tokens: int = Field(default=0, description=\"Number of tokens in the prompt\")\n completion_tokens: int = Field(default=0, description=\"Number of tokens in the completion\")\n total_tokens: int = Field(default=0, description=\"Total tokens used\")\n\n\nclass LLMConfig(BaseModel):\n \"\"\"Configuration for LLM client.\"\"\"\n api_key: str = Field(description=\"API key for authentication\")\n base_url: str = Field(description=\"Base URL for the API\")\n model: str = Field(default=\"deepseek-chat\", description=\"Model to use\")\n temperature: float = Field(default=0.7, description=\"Temperature for sampling\")\n max_tokens: int = Field(default=2000, description=\"Maximum tokens in response\")\n timeout: int = Field(default=60, description=\"Request timeout in seconds\")\n max_retries: int = Field(default=3, description=\"Maximum number of retries\")\n retry_delay: float = Field(default=1.0, description=\"Delay between retries in seconds\")\n\n\nclass LLMClient:\n \"\"\"Unified LLM client supporting DeepSeek and OpenAI compatible APIs.\n\n Provides automatic retry logic, error handling, and function calling support.\n \"\"\"\n\n def __init__(\n self,\n config: Optional[LLMConfig] = None,\n api_key: Optional[str] = None,\n base_url: Optional[str] = None,\n model: Optional[str] = None,\n ):\n \"\"\"Initialize LLM client.\n\n Args:\n config: Complete LLMConfig object\n api_key: API key (alternative to config)\n base_url: Base URL (alternative to config)\n model: Model name (alternative to config)\n \"\"\"\n if config is None:\n # Try to get from environment variables\n api_key = api_key or os.getenv(\"DEEPSEEK_API_KEY\") or os.getenv(\"OPENAI_API_KEY\")\n base_url = base_url or os.getenv(\"DEEPSEEK_BASE_URL\") or os.getenv(\"OPENAI_BASE_URL\")\n \n if not api_key:\n raise ValueError(\"API key must be provided either via config or environment variable\")\n \n # Set reasonable defaults\n if base_url is None:\n # Default to DeepSeek\n base_url = \"https://api.deepseek.com\"\n model = model or \"deepseek-chat\"\n elif \"openai\" in base_url.lower():\n model = model or \"gpt-4\"\n \n config = LLMConfig(\n api_key=api_key,\n base_url=base_url,\n model=model or \"deepseek-chat\",\n )\n \n self.config = config\n self._client = httpx.Client(timeout=config.timeout)\n\n def chat(\n self,\n messages: List[LLMMessage],\n temperature: Optional[float] = None,\n max_tokens: Optional[int] = None,\n functions: Optional[List[Dict]] = None,\n ) -> LLMResponse:\n \"\"\"Send a chat request to the LLM.\n\n Args:\n messages: List of conversation messages\n temperature: Temperature for sampling (overrides config)\n max_tokens: Maximum tokens (overrides config)\n functions: Optional list of function definitions for function calling\n\n Returns:\n LLMResponse with the model's response\n \"\"\"\n url = f\"{self.config.base_url.rstrip('/')}/chat/completions\"\n \n headers = {\n \"Authorization\": f\"Bearer {self.config.api_key}\",\n \"Content-Type\": \"application/json\",\n }\n \n payload = {\n \"model\": self.config.model,\n \"messages\": [{\"role\": m.role, \"content\": m.content} for m in messages],\n \"temperature\": temperature or self.config.temperature,\n \"max_tokens\": max_tokens or self.config.max_tokens,\n }\n \n if functions:\n payload[\"functions\"] = functions\n payload[\"function_call\"] = \"auto\"\n \n # Execute with retries\n last_exception = None\n for attempt in range(self.config.max_retries):\n try:\n response = self._client.post(url, headers=headers, json=payload)\n response.raise_for_status()\n result = response.json()\n \n return self._parse_response(result)\n \n except (httpx.HTTPStatusError, httpx.RequestError) as e:\n last_exception = e\n if attempt < self.config.max_retries - 1:\n time.sleep(self.config.retry_delay * (attempt + 1))\n continue\n \n raise RuntimeError(f\"Failed after {self.config.max_retries} attempts\") from last_exception\n\n def _parse_response(self, response: Dict) -> LLMResponse:\n \"\"\"Parse the raw API response into LLMResponse.\"\"\"\n choice = response[\"choices\"][0]\n message = choice.get(\"message\", {})\n \n content = message.get(\"content\")\n \n # Parse function calls\n function_calls = []\n if \"function_call\" in message:\n func_call = message[\"function_call\"]\n try:\n arguments = json.loads(func_call.get(\"arguments\", \"{}\"))\n except json.JSONDecodeError:\n arguments = {}\n \n function_calls.append(LLMFunctionCall(\n name=func_call.get(\"name\", \"\"),\n arguments=arguments,\n ))\n \n usage = response.get(\"usage\", {})\n \n return LLMResponse(\n content=content,\n function_calls=function_calls,\n model=response.get(\"model\", self.config.model),\n usage=usage,\n prompt_tokens=usage.get(\"prompt_tokens\", 0),\n completion_tokens=usage.get(\"completion_tokens\", 0),\n total_tokens=usage.get(\"total_tokens\", 0),\n )\n\n def simple_chat(\n self,\n user_message: str,\n system_prompt: Optional[str] = None,\n temperature: Optional[float] = None,\n ) -> str:\n \"\"\"Simple one-off chat with the LLM.\n\n Args:\n user_message: User's message\n system_prompt: Optional system prompt\n temperature: Optional temperature override\n\n Returns:\n The LLM's text response\n \"\"\"\n messages = []\n if system_prompt:\n messages.append(LLMMessage(role=\"system\", content=system_prompt))\n messages.append(LLMMessage(role=\"user\", content=user_message))\n \n response = self.chat(messages, temperature=temperature)\n return response.content or \"\"\n\n def close(self) -> None:\n \"\"\"Close the HTTP client.\"\"\"\n self._client.close()\n\n def __enter__(self) -> \"LLMClient\":\n \"\"\"Context manager entry.\"\"\"\n return self\n\n def __exit__(self, exc_type, exc_val, exc_tb) -> None:\n \"\"\"Context manager exit.\"\"\"\n self.close()\n\n\n# Convenience factory functions\ndef create_deepseek_client(\n api_key: Optional[str] = None,\n model: str = \"deepseek-chat\",\n **kwargs,\n) -> LLMClient:\n \"\"\"Create a DeepSeek LLM client.\n\n Args:\n api_key: DeepSeek API key (defaults to DEEPSEEK_API_KEY env var)\n model: Model to use (default: deepseek-chat)\n **kwargs: Additional config options\n\n Returns:\n Configured LLMClient for DeepSeek\n \"\"\"\n api_key = api_key or os.getenv(\"DEEPSEEK_API_KEY\")\n if not api_key:\n raise ValueError(\"DEEPSEEK_API_KEY environment variable not set\")\n \n config = LLMConfig(\n api_key=api_key,\n base_url=\"https://api.deepseek.com\",\n model=model,\n **kwargs,\n )\n return LLMClient(config=config)\n\n\ndef create_openai_client(\n api_key: Optional[str] = None,\n model: str = \"gpt-4\",\n base_url: Optional[str] = None,\n **kwargs,\n) -> LLMClient:\n \"\"\"Create an OpenAI-compatible LLM client.\n\n Args:\n api_key: OpenAI API key (defaults to OPENAI_API_KEY env var)\n model: Model to use (default: gpt-4)\n base_url: Optional base URL for custom endpoints\n **kwargs: Additional config options\n\n Returns:\n Configured LLMClient for OpenAI\n \"\"\"\n api_key = api_key or os.getenv(\"OPENAI_API_KEY\")\n if not api_key:\n raise ValueError(\"OPENAI_API_KEY environment variable not set\")\n \n config = LLMConfig(\n api_key=api_key,\n base_url=base_url or \"https://api.openai.com/v1\",\n model=model,\n **kwargs,\n )\n return LLMClient(config=config)\n"}, "603611d8c55db0c3c2d4cbaec17edcf035107383": {"t": 1785767233.122584, "n": 9728, "h": "743c9d579d65f9a8", "c": "\"\"\"LLM client for DeepSeek and OpenAI compatible APIs.\n\nProvides a unified interface for interacting with LLM APIs with\nautomatic retry logic, error handling, and response parsing.\n\"\"\"\nimport os\nimport time\nimport json\nfrom typing import Dict, List, Optional, Any, Callable\nfrom decimal import Decimal\nfrom dataclasses import dataclass\nfrom pydantic import BaseModel, Field\nimport httpx\n\n\nclass LLMMessage(BaseModel):\n \"\"\"Message in a conversation with an LLM.\"\"\"\n role: str = Field(description=\"Role of the message sender (system, user, assistant)\")\n content: str = Field(description=\"Content of the message\")\n\n\nclass LLMFunctionCall(BaseModel):\n \"\"\"Function call requested by the LLM.\"\"\"\n name: str = Field(description=\"Name of the function to call\")\n arguments: Dict[str, Any] = Field(description=\"Arguments for the function\")\n\n\nclass LLMResponse(BaseModel):\n \"\"\"Response from an LLM.\"\"\"\n content: Optional[str] = Field(default=None, description=\"Text content of the response\")\n function_calls: List[LLMFunctionCall] = Field(default_factory=list, description=\"Function calls requested\")\n model: str = Field(description=\"Model used for the response\")\n usage: Dict[str, int] = Field(default_factory=dict, description=\"Token usage statistics\")\n prompt_tokens: int = Field(default=0, description=\"Number of tokens in the prompt\")\n completion_tokens: int = Field(default=0, description=\"Number of tokens in the completion\")\n total_tokens: int = Field(default=0, description=\"Total tokens used\")\n\n\nclass LLMConfig(BaseModel):\n \"\"\"Configuration for LLM client.\"\"\"\n api_key: str = Field(description=\"API key for authentication\")\n base_url: str = Field(description=\"Base URL for the API\")\n model: str = Field(default=\"deepseek-chat\", description=\"Model to use\")\n temperature: float = Field(default=0.7, description=\"Temperature for sampling\")\n max_tokens: int = Field(default=2000, description=\"Maximum tokens in response\")\n timeout: int = Field(default=60, description=\"Request timeout in seconds\")\n max_retries: int = Field(default=3, description=\"Maximum number of retries\")\n retry_delay: float = Field(default=1.0, description=\"Delay between retries in seconds\")\n\n\nclass LLMClient:\n \"\"\"Unified LLM client supporting DeepSeek and OpenAI compatible APIs.\n\n Provides automatic retry logic, error handling, and function calling support.\n \"\"\"\n\n def __init__(\n self,\n config: Optional[LLMConfig] = None,\n api_key: Optional[str] = None,\n base_url: Optional[str] = None,\n model: Optional[str] = None,\n ):\n \"\"\"Initialize LLM client.\n\n Args:\n config: Complete LLMConfig object\n api_key: API key (alternative to config)\n base_url: Base URL (alternative to config)\n model: Model name (alternative to config)\n \"\"\"\n if config is None:\n # Try to get from environment variables\n api_key = api_key or os.getenv(\"DEEPSEEK_API_KEY\") or os.getenv(\"OPENAI_API_KEY\")\n base_url = base_url or os.getenv(\"DEEPSEEK_BASE_URL\") or os.getenv(\"OPENAI_BASE_URL\")\n \n if not api_key:\n raise ValueError(\"API key must be provided either via config or environment variable\")\n \n # Set reasonable defaults\n if base_url is None:\n # Default to DeepSeek\n base_url = \"https://api.deepseek.com\"\n model = model or \"deepseek-chat\"\n elif \"openai\" in base_url.lower():\n model = model or \"gpt-4\"\n \n config = LLMConfig(\n api_key=api_key,\n base_url=base_url,\n model=model or \"deepseek-chat\",\n )\n \n self.config = config\n self._client = httpx.Client(timeout=config.timeout)\n\n def chat(\n self,\n messages: List[LLMMessage],\n temperature: Optional[float] = None,\n max_tokens: Optional[int] = None,\n functions: Optional[List[Dict]] = None,\n ) -> LLMResponse:\n \"\"\"Send a chat request to the LLM.\n\n Args:\n messages: List of conversation messages\n temperature: Temperature for sampling (overrides config)\n max_tokens: Maximum tokens (overrides config)\n functions: Optional list of function definitions for function calling\n\n Returns:\n LLMResponse with the model's response\n \"\"\"\n url = f\"{self.config.base_url.rstrip('/')}/chat/completions\"\n \n headers = {\n \"Authorization\": f\"Bearer {self.config.api_key}\",\n \"Content-Type\": \"application/json\",\n }\n \n payload = {\n \"model\": self.config.model,\n \"messages\": [{\"role\": m.role, \"content\": m.content} for m in messages],\n \"temperature\": temperature or self.config.temperature,\n \"max_tokens\": max_tokens or self.config.max_tokens,\n }\n \n if functions:\n payload[\"functions\"] = functions\n payload[\"function_call\"] = \"auto\"\n \n # Execute with retries\n last_exception = None\n for attempt in range(self.config.max_retries):\n try:\n response = self._client.post(url, headers=headers, json=payload)\n response.raise_for_status()\n result = response.json()\n \n return self._parse_response(result)\n \n except (httpx.HTTPStatusError, httpx.RequestError) as e:\n last_exception = e\n if attempt < self.config.max_retries - 1:\n time.sleep(self.config.retry_delay * (attempt + 1))\n continue\n \n raise RuntimeError(f\"Failed after {self.config.max_retries} attempts\") from last_exception\n\n def _parse_response(self, response: Dict) -> LLMResponse:\n \"\"\"Parse the raw API response into LLMResponse.\"\"\"\n choice = response[\"choices\"][0]\n message = choice.get(\"message\", {})\n \n content = message.get(\"content\")\n \n # Parse function calls\n function_calls = []\n if \"function_call\" in message:\n func_call = message[\"function_call\"]\n try:\n arguments = json.loads(func_call.get(\"arguments\", \"{}\"))\n except json.JSONDecodeError:\n arguments = {}\n \n function_calls.append(LLMFunctionCall(\n name=func_call.get(\"name\", \"\"),\n arguments=arguments,\n ))\n \n usage = response.get(\"usage\", {})\n \n return LLMResponse(\n content=content,\n function_calls=function_calls,\n model=response.get(\"model\", self.config.model),\n usage=usage,\n prompt_tokens=usage.get(\"prompt_tokens\", 0),\n completion_tokens=usage.get(\"completion_tokens\", 0),\n total_tokens=usage.get(\"total_tokens\", 0),\n )\n\n def simple_chat(\n self,\n user_message: str,\n system_prompt: Optional[str] = None,\n temperature: Optional[float] = None,\n ) -> str:\n \"\"\"Simple one-off chat with the LLM.\n\n Args:\n user_message: User's message\n system_prompt: Optional system prompt\n temperature: Optional temperature override\n\n Returns:\n The LLM's text response\n \"\"\"\n messages = []\n if system_prompt:\n messages.append(LLMMessage(role=\"system\", content=system_prompt))\n messages.append(LLMMessage(role=\"user\", content=user_message))\n \n response = self.chat(messages, temperature=temperature)\n return response.content or \"\"\n\n def close(self) -> None:\n \"\"\"Close the HTTP client.\"\"\"\n self._client.close()\n\n def __enter__(self) -> \"LLMClient\":\n \"\"\"Context manager entry.\"\"\"\n return self\n\n def __exit__(self, exc_type, exc_val, exc_tb) -> None:\n \"\"\"Context manager exit.\"\"\"\n self.close()\n\n\n# Convenience factory functions\ndef create_deepseek_client(\n api_key: Optional[str] = None,\n model: str = \"deepseek-chat\",\n **kwargs,\n) -> LLMClient:\n \"\"\"Create a DeepSeek LLM client.\n\n Args:\n api_key: DeepSeek API key (defaults to DEEPSEEK_API_KEY env var)\n model: Model to use (default: deepseek-chat)\n **kwargs: Additional config options\n\n Returns:\n Configured LLMClient for DeepSeek\n \"\"\"\n api_key = api_key or os.getenv(\"DEEPSEEK_API_KEY\")\n if not api_key:\n raise ValueError(\"DEEPSEEK_API_KEY environment variable not set\")\n \n config = LLMConfig(\n api_key=api_key,\n base_url=\"https://api.deepseek.com\",\n model=model,\n **kwargs,\n )\n return LLMClient(config=config)\n\n\ndef create_openai_client(\n api_key: Optional[str] = None,\n model: str = \"gpt-4\",\n base_url: Optional[str] = None,\n **kwargs,\n) -> LLMClient:\n \"\"\"Create an OpenAI-compatible LLM client.\n\n Args:\n api_key: OpenAI API key (defaults to OPENAI_API_KEY env var)\n model: Model to use (default: gpt-4)\n base_url: Optional base URL for custom endpoints\n **kwargs: Additional config options\n\n Returns:\n Configured LLMClient for OpenAI\n \"\"\"\n api_key = api_key or os.getenv(\"OPENAI_API_KEY\")\n if not api_key:\n raise ValueError(\"OPENAI_API_KEY environment variable not set\")\n \n config = LLMConfig(\n api_key=api_key,\n base_url=base_url or \"https://api.openai.com/v1\",\n model=model,\n **kwargs,\n )\n return LLMClient(config=config)\n"}}}}