{"repo": "balizero1987/ayo", "files": {"backend/services/llm_clients/deepseek_client.py": {"23cd129904123772abdfe94e41758c4e74cb2b80": {"t": 1785745440.041311, "n": 6588, "h": "734ad4a71f2313f4", "c": "\"\"\"\nDeepSeek V3 Client - Cheapest LLM API fallback\n\nPricing: $0.27/1M input, $1.10/1M output (December 2025)\nAPI: OpenAI-compatible\n\nFallback chain: Gemini 2.0 Flash → DeepSeek V3 → OpenRouter (free models)\n\"\"\"\n\nimport logging\nfrom collections.abc import AsyncGenerator\nfrom dataclasses import dataclass\n\nimport httpx\n\nfrom app.core.config import settings\n\nlogger = logging.getLogger(__name__)\n\n\n@dataclass\nclass DeepSeekResponse:\n \"\"\"Response from DeepSeek API\"\"\"\n\n content: str\n model_name: str\n input_tokens: int\n output_tokens: int\n finish_reason: str\n\n\nclass DeepSeekClient:\n \"\"\"\n DeepSeek V3 Client - Cheapest available LLM API.\n\n Pricing (December 2025):\n - Input: $0.27 per 1M tokens\n - Output: $1.10 per 1M tokens\n - Context: 64k tokens\n\n Use as fallback when Gemini quota is exhausted.\n \"\"\"\n\n BASE_URL = \"https://api.deepseek.com/v1\"\n DEFAULT_MODEL = \"deepseek-chat\" # DeepSeek V3\n\n def __init__(self, api_key: str | None = None):\n \"\"\"\n Initialize DeepSeek client.\n\n Args:\n api_key: DeepSeek API key (defaults to settings.deepseek_api_key)\n \"\"\"\n self.api_key = api_key or settings.deepseek_api_key\n if not self.api_key:\n logger.warning(\"⚠️ DeepSeek API key not set. Set DEEPSEEK_API_KEY env var.\")\n\n @property\n def is_available(self) -> bool:\n \"\"\"Check if DeepSeek is configured and available\"\"\"\n return bool(self.api_key)\n\n async def complete(\n self,\n messages: list[dict],\n model: str = DEFAULT_MODEL,\n temperature: float = 0.7,\n max_tokens: int = 8192,\n ) -> DeepSeekResponse:\n \"\"\"\n Generate completion from DeepSeek.\n\n Args:\n messages: List of messages in OpenAI format\n model: Model to use (default: deepseek-chat)\n temperature: Sampling temperature (0.0-2.0)\n max_tokens: Maximum tokens to generate\n\n Returns:\n DeepSeekResponse with content and metadata\n \"\"\"\n if not self.api_key:\n raise RuntimeError(\"DeepSeek API key not configured\")\n\n async with httpx.AsyncClient(timeout=60.0) as client:\n response = await client.post(\n f\"{self.BASE_URL}/chat/completions\",\n headers={\n \"Authorization\": f\"Bearer {self.api_key}\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"model\": model,\n \"messages\": messages,\n \"temperature\": temperature,\n \"max_tokens\": max_tokens,\n },\n )\n\n if response.status_code != 200:\n error_text = response.text\n logger.error(f\"DeepSeek API error: {response.status_code} - {error_text}\")\n raise RuntimeError(f\"DeepSeek API error: {response.status_code}\")\n\n data = response.json()\n\n return DeepSeekResponse(\n content=data[\"choices\"][0][\"message\"][\"content\"],\n model_name=data.get(\"model\", model),\n input_tokens=data.get(\"usage\", {}).get(\"prompt_tokens\", 0),\n output_tokens=data.get(\"usage\", {}).get(\"completion_tokens\", 0),\n finish_reason=data[\"choices\"][0].get(\"finish_reason\", \"stop\"),\n )\n\n async def complete_stream(\n self,\n messages: list[dict],\n model: str = DEFAULT_MODEL,\n temperature: float = 0.7,\n max_tokens: int = 8192,\n ) -> AsyncGenerator[str, None]:\n \"\"\"\n Stream completion from DeepSeek.\n\n Args:\n messages: List of messages in OpenAI format\n model: Model to use\n temperature: Sampling temperature\n max_tokens: Maximum tokens to generate\n\n Yields:\n Text chunks as they are generated\n \"\"\"\n if not self.api_key:\n raise RuntimeError(\"DeepSeek API key not configured\")\n\n async with (\n httpx.AsyncClient(timeout=120.0) as client,\n client.stream(\n \"POST\",\n f\"{self.BASE_URL}/chat/completions\",\n headers={\n \"Authorization\": f\"Bearer {self.api_key}\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"model\": model,\n \"messages\": messages,\n \"temperature\": temperature,\n \"max_tokens\": max_tokens,\n \"stream\": True,\n },\n ) as response,\n ):\n if response.status_code != 200:\n error_text = await response.aread()\n logger.error(f\"DeepSeek streaming error: {response.status_code}\")\n raise RuntimeError(f\"DeepSeek API error: {response.status_code}\")\n\n async for line in response.aiter_lines():\n if line.startswith(\"data: \"):\n data_str = line[6:]\n if data_str == \"[DONE]\":\n break\n\n try:\n import json\n\n data = json.loads(data_str)\n delta = data.get(\"choices\", [{}])[0].get(\"delta\", {})\n content = delta.get(\"content\", \"\")\n if content:\n yield content\n except Exception as e:\n logger.warning(f\"Failed to parse DeepSeek stream: {e}\")\n continue\n\n\ndeepseek_client = DeepSeekClient()\n\n\nif __name__ == \"__main__\":\n import asyncio\n\n async def test():\n logger.info(\"🚀 Testing DeepSeek V3 Client...\")\n logger.info(f\" API Key: {'✅ Set' if settings.deepseek_api_key else '❌ Not set'}\")\n\n if not settings.deepseek_api_key:\n logger.info(\"Set DEEPSEEK_API_KEY to test\")\n return\n\n client = DeepSeekClient()\n\n messages = [\n {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n {\"role\": \"user\", \"content\": \"What is 2+2? Answer in one word.\"},\n ]\n\n try:\n result = await client.complete(messages)\n logger.info(f\"✅ Response: {result.content}\")\n logger.info(f\" Model: {result.model_name}\")\n logger.info(f\" Tokens: {result.input_tokens} in, {result.output_tokens} out\")\n except Exception as e:\n logger.error(f\"❌ Error: {e}\")\n\n asyncio.run(test())\n"}}}}