Kevinshh commited on
Commit
8aebd96
·
verified ·
1 Parent(s): e07949f

Delete layers/llm_providers.py

Browse files
Files changed (1) hide show
  1. layers/llm_providers.py +0 -443
layers/llm_providers.py DELETED
@@ -1,443 +0,0 @@
1
- """
2
- LLM Provider Abstraction Layer.
3
-
4
- This module provides a unified interface for multiple LLM providers:
5
- - OpenAI (ChatGPT)
6
- - Google (Gemini)
7
- - Anthropic (Claude)
8
- - xAI (Grok)
9
- - Moonshot (Kimi)
10
- - Deepseek
11
- - Zhipu (智谱清言 GLM)
12
-
13
- Users can select their preferred provider and configure API keys.
14
- """
15
-
16
- import os
17
- import httpx
18
- import json
19
- from abc import ABC, abstractmethod
20
- from typing import Optional, Dict, Any, List
21
- from dataclasses import dataclass
22
- from enum import Enum
23
-
24
-
25
- class LLMProvider(str, Enum):
26
- """Supported LLM providers."""
27
- OPENAI = "openai" # ChatGPT
28
- GEMINI = "gemini" # Google Gemini
29
- CLAUDE = "claude" # Anthropic Claude
30
- GROK = "grok" # xAI Grok
31
- KIMI = "kimi" # Moonshot Kimi
32
- DEEPSEEK = "deepseek" # Deepseek
33
- ZHIPU = "zhipu" # 智谱清言 GLM
34
-
35
-
36
- @dataclass
37
- class LLMConfig:
38
- """Configuration for LLM providers."""
39
- provider: LLMProvider
40
- api_key: str
41
- model: Optional[str] = None # If None, use default model
42
- base_url: Optional[str] = None # Custom API endpoint
43
- temperature: float = 0.3
44
- max_tokens: int = 4096
45
- timeout: int = 120
46
-
47
-
48
- # Default models for each provider (Updated 2026-01)
49
- DEFAULT_MODELS = {
50
- LLMProvider.OPENAI: "gpt-5.2", # Released Dec 2025
51
- LLMProvider.GEMINI: "gemini-3-pro", # Released Late 2025
52
- LLMProvider.CLAUDE: "claude-opus-4.5", # Released Nov 2025
53
- LLMProvider.GROK: "grok-3", # Latest Grok
54
- LLMProvider.KIMI: "kimi-k2", # Kimi K2
55
- LLMProvider.DEEPSEEK: "deepseek-v3.2", # Released Dec 2025
56
- LLMProvider.ZHIPU: "glm-5", # Released Late 2025
57
- }
58
-
59
- # API endpoints for each provider
60
- API_ENDPOINTS = {
61
- LLMProvider.OPENAI: "https://api.openai.com/v1/chat/completions",
62
- LLMProvider.GEMINI: "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent",
63
- LLMProvider.CLAUDE: "https://api.anthropic.com/v1/messages",
64
- LLMProvider.GROK: "https://api.x.ai/v1/chat/completions",
65
- LLMProvider.KIMI: "https://api.moonshot.cn/v1/chat/completions",
66
- LLMProvider.DEEPSEEK: "https://api.deepseek.com/chat/completions", # Fixed: removed /v1
67
- LLMProvider.ZHIPU: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
68
- }
69
-
70
- # Environment variable names for API keys
71
- API_KEY_ENV_VARS = {
72
- LLMProvider.OPENAI: "OPENAI_API_KEY",
73
- LLMProvider.GEMINI: "GEMINI_API_KEY",
74
- LLMProvider.CLAUDE: "ANTHROPIC_API_KEY",
75
- LLMProvider.GROK: "GROK_API_KEY",
76
- LLMProvider.KIMI: "MOONSHOT_API_KEY",
77
- LLMProvider.DEEPSEEK: "DEEPSEEK_API_KEY",
78
- LLMProvider.ZHIPU: "ZHIPU_API_KEY",
79
- }
80
-
81
-
82
- class BaseLLMClient(ABC):
83
- """Abstract base class for LLM clients."""
84
-
85
- def __init__(self, config: LLMConfig):
86
- self.config = config
87
- self.model = config.model or DEFAULT_MODELS.get(config.provider)
88
-
89
- @abstractmethod
90
- async def chat(
91
- self,
92
- messages: List[Dict[str, str]],
93
- temperature: Optional[float] = None,
94
- ) -> str:
95
- """Send chat messages and get response."""
96
- pass
97
-
98
- def chat_sync(
99
- self,
100
- messages: List[Dict[str, str]],
101
- temperature: Optional[float] = None,
102
- ) -> str:
103
- """Synchronous version of chat."""
104
- import asyncio
105
- try:
106
- loop = asyncio.get_event_loop()
107
- if loop.is_running():
108
- # Create new loop in thread
109
- import concurrent.futures
110
- with concurrent.futures.ThreadPoolExecutor() as executor:
111
- future = executor.submit(
112
- asyncio.run,
113
- self.chat(messages, temperature)
114
- )
115
- return future.result()
116
- else:
117
- return asyncio.run(self.chat(messages, temperature))
118
- except RuntimeError:
119
- return asyncio.run(self.chat(messages, temperature))
120
-
121
-
122
- class OpenAICompatibleClient(BaseLLMClient):
123
- """
124
- Client for OpenAI-compatible APIs.
125
-
126
- Works with: OpenAI, Grok, Kimi, Deepseek, Zhipu
127
- These all use the same API format.
128
- """
129
-
130
- async def chat(
131
- self,
132
- messages: List[Dict[str, str]],
133
- temperature: Optional[float] = None,
134
- ) -> str:
135
- """Send chat request to OpenAI-compatible endpoint."""
136
-
137
- endpoint = self.config.base_url or API_ENDPOINTS.get(self.config.provider)
138
-
139
- headers = {
140
- "Authorization": f"Bearer {self.config.api_key}",
141
- "Content-Type": "application/json",
142
- }
143
-
144
- payload = {
145
- "model": self.model,
146
- "messages": messages,
147
- "temperature": temperature or self.config.temperature,
148
- "max_tokens": self.config.max_tokens,
149
- }
150
-
151
- print(f"[LLM] Calling {self.config.provider.value} API...")
152
- print(f"[LLM] Endpoint: {endpoint}")
153
- print(f"[LLM] Model: {self.model}")
154
-
155
- try:
156
- async with httpx.AsyncClient(timeout=self.config.timeout) as client:
157
- response = await client.post(
158
- endpoint,
159
- headers=headers,
160
- json=payload,
161
- )
162
-
163
- if response.status_code != 200:
164
- error_text = response.text
165
- print(f"[LLM] Error response: {error_text}")
166
- raise Exception(f"API Error {response.status_code}: {error_text}")
167
-
168
- data = response.json()
169
- content = data["choices"][0]["message"]["content"]
170
- print(f"[LLM] Success! Response length: {len(content)} chars")
171
- return content
172
-
173
- except httpx.TimeoutException:
174
- raise Exception(f"Request timeout after {self.config.timeout}s")
175
- except httpx.RequestError as e:
176
- raise Exception(f"Request failed: {str(e)}")
177
-
178
-
179
- class ClaudeClient(BaseLLMClient):
180
- """Client for Anthropic Claude API."""
181
-
182
- async def chat(
183
- self,
184
- messages: List[Dict[str, str]],
185
- temperature: Optional[float] = None,
186
- ) -> str:
187
- """Send chat request to Claude API."""
188
-
189
- endpoint = self.config.base_url or API_ENDPOINTS[LLMProvider.CLAUDE]
190
-
191
- # Extract system message if present
192
- system_content = ""
193
- user_messages = []
194
-
195
- for msg in messages:
196
- if msg["role"] == "system":
197
- system_content = msg["content"]
198
- else:
199
- user_messages.append(msg)
200
-
201
- headers = {
202
- "x-api-key": self.config.api_key,
203
- "Content-Type": "application/json",
204
- "anthropic-version": "2023-06-01",
205
- }
206
-
207
- payload = {
208
- "model": self.model,
209
- "max_tokens": self.config.max_tokens,
210
- "messages": user_messages,
211
- "temperature": temperature or self.config.temperature,
212
- }
213
-
214
- if system_content:
215
- payload["system"] = system_content
216
-
217
- print(f"[LLM] Calling Claude API...")
218
- print(f"[LLM] Model: {self.model}")
219
-
220
- try:
221
- async with httpx.AsyncClient(timeout=self.config.timeout) as client:
222
- response = await client.post(
223
- endpoint,
224
- headers=headers,
225
- json=payload,
226
- )
227
-
228
- if response.status_code != 200:
229
- error_text = response.text
230
- print(f"[LLM] Error response: {error_text}")
231
- raise Exception(f"API Error {response.status_code}: {error_text}")
232
-
233
- data = response.json()
234
- content = data["content"][0]["text"]
235
- print(f"[LLM] Success! Response length: {len(content)} chars")
236
- return content
237
-
238
- except httpx.TimeoutException:
239
- raise Exception(f"Request timeout after {self.config.timeout}s")
240
- except httpx.RequestError as e:
241
- raise Exception(f"Request failed: {str(e)}")
242
-
243
-
244
- class GeminiClient(BaseLLMClient):
245
- """Client for Google Gemini API."""
246
-
247
- async def chat(
248
- self,
249
- messages: List[Dict[str, str]],
250
- temperature: Optional[float] = None,
251
- ) -> str:
252
- """Send chat request to Gemini API."""
253
-
254
- endpoint = API_ENDPOINTS[LLMProvider.GEMINI].format(model=self.model)
255
- endpoint = f"{endpoint}?key={self.config.api_key}"
256
-
257
- # Convert messages to Gemini format
258
- contents = []
259
- system_instruction = None
260
-
261
- for msg in messages:
262
- if msg["role"] == "system":
263
- system_instruction = msg["content"]
264
- elif msg["role"] == "user":
265
- contents.append({
266
- "role": "user",
267
- "parts": [{"text": msg["content"]}]
268
- })
269
- elif msg["role"] == "assistant":
270
- contents.append({
271
- "role": "model",
272
- "parts": [{"text": msg["content"]}]
273
- })
274
-
275
- payload = {
276
- "contents": contents,
277
- "generationConfig": {
278
- "temperature": temperature or self.config.temperature,
279
- "maxOutputTokens": self.config.max_tokens,
280
- }
281
- }
282
-
283
- if system_instruction:
284
- payload["systemInstruction"] = {
285
- "parts": [{"text": system_instruction}]
286
- }
287
-
288
- headers = {"Content-Type": "application/json"}
289
-
290
- print(f"[LLM] Calling Gemini API...")
291
- print(f"[LLM] Model: {self.model}")
292
-
293
- try:
294
- async with httpx.AsyncClient(timeout=self.config.timeout) as client:
295
- response = await client.post(
296
- endpoint,
297
- headers=headers,
298
- json=payload,
299
- )
300
-
301
- if response.status_code != 200:
302
- error_text = response.text
303
- print(f"[LLM] Error response: {error_text}")
304
- raise Exception(f"API Error {response.status_code}: {error_text}")
305
-
306
- data = response.json()
307
- content = data["candidates"][0]["content"]["parts"][0]["text"]
308
- print(f"[LLM] Success! Response length: {len(content)} chars")
309
- return content
310
-
311
- except httpx.TimeoutException:
312
- raise Exception(f"Request timeout after {self.config.timeout}s")
313
- except httpx.RequestError as e:
314
- raise Exception(f"Request failed: {str(e)}")
315
-
316
-
317
- def create_llm_client(config: LLMConfig) -> BaseLLMClient:
318
- """
319
- Factory function to create appropriate LLM client.
320
-
321
- Args:
322
- config: LLM configuration
323
-
324
- Returns:
325
- Appropriate LLM client instance
326
- """
327
- print(f"[LLM] Creating client for provider: {config.provider.value}")
328
-
329
- if config.provider == LLMProvider.CLAUDE:
330
- return ClaudeClient(config)
331
- elif config.provider == LLMProvider.GEMINI:
332
- return GeminiClient(config)
333
- else:
334
- # OpenAI, Grok, Kimi, Deepseek, Zhipu all use OpenAI-compatible API
335
- return OpenAICompatibleClient(config)
336
-
337
-
338
- def get_available_providers() -> List[Dict[str, str]]:
339
- """
340
- Get list of available providers with their display names.
341
-
342
- Returns:
343
- List of provider info dicts
344
- """
345
- return [
346
- {"id": "openai", "name": "OpenAI (ChatGPT)", "env_var": "OPENAI_API_KEY"},
347
- {"id": "gemini", "name": "Google Gemini", "env_var": "GEMINI_API_KEY"},
348
- {"id": "claude", "name": "Anthropic Claude", "env_var": "ANTHROPIC_API_KEY"},
349
- {"id": "grok", "name": "xAI Grok", "env_var": "GROK_API_KEY"},
350
- {"id": "kimi", "name": "Moonshot Kimi", "env_var": "MOONSHOT_API_KEY"},
351
- {"id": "deepseek", "name": "Deepseek", "env_var": "DEEPSEEK_API_KEY"},
352
- {"id": "zhipu", "name": "智谱清言 (GLM)", "env_var": "ZHIPU_API_KEY"},
353
- ]
354
-
355
-
356
- def detect_available_provider() -> Optional[LLMProvider]:
357
- """
358
- Auto-detect which provider has an API key configured.
359
-
360
- Returns:
361
- First available provider, or None
362
- """
363
- for provider, env_var in API_KEY_ENV_VARS.items():
364
- if os.environ.get(env_var):
365
- print(f"[LLM] Auto-detected provider: {provider.value} (from {env_var})")
366
- return provider
367
- return None
368
-
369
-
370
- def create_client_from_env(
371
- provider: Optional[LLMProvider] = None
372
- ) -> Optional[BaseLLMClient]:
373
- """
374
- Create LLM client from environment variables.
375
-
376
- Args:
377
- provider: Specific provider to use, or None to auto-detect
378
-
379
- Returns:
380
- LLM client or None if no API key found
381
- """
382
- if provider is None:
383
- provider = detect_available_provider()
384
-
385
- if provider is None:
386
- return None
387
-
388
- env_var = API_KEY_ENV_VARS.get(provider)
389
- api_key = os.environ.get(env_var, "")
390
-
391
- if not api_key:
392
- return None
393
-
394
- config = LLMConfig(
395
- provider=provider,
396
- api_key=api_key,
397
- )
398
-
399
- return create_llm_client(config)
400
-
401
-
402
- # Test function for debugging
403
- def test_provider(provider_name: str, api_key: str, test_message: str = "Hello, respond with 'OK' if you receive this.") -> Dict[str, Any]:
404
- """
405
- Test a specific LLM provider.
406
-
407
- Args:
408
- provider_name: Provider name (openai, gemini, etc.)
409
- api_key: API key
410
- test_message: Test message to send
411
-
412
- Returns:
413
- Dict with success status and response/error
414
- """
415
- import asyncio
416
-
417
- try:
418
- provider = LLMProvider(provider_name.lower())
419
- config = LLMConfig(
420
- provider=provider,
421
- api_key=api_key,
422
- )
423
- client = create_llm_client(config)
424
-
425
- messages = [
426
- {"role": "user", "content": test_message}
427
- ]
428
-
429
- response = asyncio.run(client.chat(messages))
430
-
431
- return {
432
- "success": True,
433
- "provider": provider_name,
434
- "model": client.model,
435
- "response": response[:500] if len(response) > 500 else response,
436
- }
437
-
438
- except Exception as e:
439
- return {
440
- "success": False,
441
- "provider": provider_name,
442
- "error": str(e),
443
- }