ngpt 1.7.0__py3-none-any.whl → 1.7.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ngpt/cli.py CHANGED
@@ -385,20 +385,38 @@ def main():
385
385
  print(f"Total configurations: {len(configs)}")
386
386
  print(f"Active configuration index: {args.config_index}")
387
387
 
388
+ # Temporarily initialize a client to get the detected provider for each config
389
+ def get_detected_provider(config):
390
+ base_url = config.get('base_url', '')
391
+ temp_client = NGPTClient(
392
+ api_key=config.get('api_key', ''),
393
+ base_url=base_url,
394
+ provider=config.get('provider', 'N/A'),
395
+ model=config.get('model', 'N/A')
396
+ )
397
+ detected = temp_client._detect_provider_from_url(base_url)
398
+ return detected if detected else config.get('provider', 'N/A')
399
+
388
400
  if args.all:
389
401
  # Show details for all configurations
390
402
  print("\nAll configuration details:")
391
403
  for i, cfg in enumerate(configs):
392
404
  active_str = '(Active)' if i == args.config_index else ''
405
+ detected_provider = get_detected_provider(cfg)
406
+ provider_str = f" [{detected_provider}]" if detected_provider else ""
407
+
393
408
  print(f"\n--- Configuration Index {i} {active_str} ---")
394
- print(f" API Key: {'[Set]' if cfg.get('api_key') else '[Not Set]'}")
409
+ print(f" API Key: {'[Set]' if cfg.get('api_key') else '[Not Set]'}{provider_str}")
395
410
  print(f" Base URL: {cfg.get('base_url', 'N/A')}")
396
411
  print(f" Provider: {cfg.get('provider', 'N/A')}")
397
412
  print(f" Model: {cfg.get('model', 'N/A')}")
398
413
  else:
399
414
  # Show active config details and summary list
415
+ detected_provider = get_detected_provider(active_config)
416
+ provider_str = f" [{detected_provider}]" if detected_provider else ""
417
+
400
418
  print("\nActive configuration details:")
401
- print(f" API Key: {'[Set]' if active_config.get('api_key') else '[Not Set]'}")
419
+ print(f" API Key: {'[Set]' if active_config.get('api_key') else '[Not Set]'}{provider_str}")
402
420
  print(f" Base URL: {active_config.get('base_url', 'N/A')}")
403
421
  print(f" Provider: {active_config.get('provider', 'N/A')}")
404
422
  print(f" Model: {active_config.get('model', 'N/A')}")
@@ -407,7 +425,9 @@ def main():
407
425
  print("\nAvailable configurations:")
408
426
  for i, cfg in enumerate(configs):
409
427
  active_marker = "*" if i == args.config_index else " "
410
- print(f"[{i}]{active_marker} {cfg.get('provider', 'N/A')} - {cfg.get('model', 'N/A')} ({'[API Key Set]' if cfg.get('api_key') else '[API Key Not Set]'})")
428
+ detected = get_detected_provider(cfg)
429
+ detected_str = f" [{detected}]" if detected else ""
430
+ print(f"[{i}]{active_marker} {cfg.get('provider', 'N/A')} - {cfg.get('model', 'N/A')} ({'[API Key Set]' if cfg.get('api_key') else '[API Key Not Set]'}{detected_str})")
411
431
 
412
432
  return
413
433
 
ngpt/client.py CHANGED
@@ -18,12 +18,65 @@ class NGPTClient:
18
18
  self.base_url = base_url if base_url.endswith('/') else base_url + '/'
19
19
  self.model = model
20
20
 
21
+ # Detect provider based on base_url domain first
22
+ detected_provider = self._detect_provider_from_url(self.base_url)
23
+ self.provider = detected_provider if detected_provider else provider
24
+
21
25
  # Default headers
22
26
  self.headers = {
23
27
  "Content-Type": "application/json",
24
28
  "Authorization": f"Bearer {self.api_key}"
25
29
  }
26
30
 
31
+ def _detect_provider_from_url(self, url: str) -> str:
32
+ """
33
+ Detect the API provider based on the base URL domain.
34
+
35
+ Args:
36
+ url: The base URL to analyze
37
+
38
+ Returns:
39
+ Detected provider name or empty string if not detected
40
+ """
41
+ import re
42
+ from urllib.parse import urlparse
43
+
44
+ # Extract domain from URL
45
+ try:
46
+ parsed_url = urlparse(url)
47
+ domain = parsed_url.netloc.lower()
48
+
49
+ # Check for known API provider domains
50
+ if 'openai.com' in domain:
51
+ return 'OpenAI'
52
+ elif 'anthropic.com' in domain:
53
+ return 'Anthropic'
54
+ elif 'generativelanguage.googleapis.com' in domain:
55
+ return 'Gemini'
56
+ elif 'groq.com' in domain:
57
+ return 'Groq'
58
+ elif 'cohere.com' in domain or 'cohere.ai' in domain:
59
+ return 'Cohere'
60
+ elif 'mistral.ai' in domain:
61
+ return 'Mistral'
62
+ elif 'claude.ai' in domain:
63
+ return 'Claude'
64
+ elif re.match(r'(127\.0\.0\.1|localhost)', domain):
65
+ # Local APIs, check path components for clues
66
+ path = parsed_url.path.lower()
67
+ if '/ollama' in path:
68
+ return 'Ollama'
69
+ elif '/llama' in path:
70
+ return 'Llama'
71
+ # Default to the explicitly provided provider for local APIs
72
+ return ''
73
+
74
+ # Return empty string if no match found, will fall back to provided provider
75
+ return ''
76
+ except Exception:
77
+ # In case of any parsing error, fall back to provided provider
78
+ return ''
79
+
27
80
  def chat(
28
81
  self,
29
82
  prompt: str,
@@ -79,11 +132,26 @@ class NGPTClient:
79
132
  endpoint = "chat/completions"
80
133
  url = f"{self.base_url}{endpoint}"
81
134
 
135
+ # Set headers based on provider
136
+ headers = self.headers.copy()
137
+ provider = self.provider.lower() if hasattr(self, 'provider') else ""
138
+
139
+ # Provider-specific customizations
140
+ if 'gemini' in provider:
141
+ # Gemini uses X-Goog-Api-Key instead of Bearer token
142
+ headers = {
143
+ "Content-Type": "application/json",
144
+ "X-Goog-Api-Key": self.api_key
145
+ }
146
+
147
+ # Gemini may require a different endpoint or payload structure
148
+ # This is a simplistic approach - may need further customization
149
+
82
150
  try:
83
151
  if not stream:
84
152
  # Regular request
85
153
  try:
86
- response = requests.post(url, headers=self.headers, json=payload)
154
+ response = requests.post(url, headers=headers, json=payload)
87
155
  response.raise_for_status() # Raise exception for HTTP errors
88
156
  result = response.json()
89
157
 
@@ -97,7 +165,7 @@ class NGPTClient:
97
165
  else:
98
166
  # Streaming request
99
167
  collected_content = ""
100
- with requests.post(url, headers=self.headers, json=payload, stream=True) as response:
168
+ with requests.post(url, headers=headers, json=payload, stream=True) as response:
101
169
  response.raise_for_status() # Raise exception for HTTP errors
102
170
 
103
171
  try:
@@ -271,36 +339,77 @@ Code:"""
271
339
  if not self.api_key:
272
340
  print("Error: API key is not set. Please configure your API key in the config file or provide it with --api-key.")
273
341
  return []
274
-
275
- # Endpoint for models
276
- url = f"{self.base_url}models"
277
342
 
278
- try:
279
- response = requests.get(url, headers=self.headers)
280
- response.raise_for_status() # Raise exception for HTTP errors
281
- result = response.json()
282
-
283
- if "data" in result:
284
- return result["data"]
343
+ provider = getattr(self, 'provider', '').lower()
344
+
345
+ # Handle provider-specific API differences
346
+ if 'gemini' in provider:
347
+ # Gemini API specific handling
348
+ # Base URL typically ends with /v1beta or similar - we want to add /models
349
+ base_url = self.base_url.rstrip('/')
350
+ if base_url.endswith('/v1beta'):
351
+ url = f"{base_url}/models"
285
352
  else:
286
- print("Error: Unexpected response format when retrieving models.")
353
+ url = f"{base_url}/models"
354
+
355
+ # Gemini uses X-Goog-Api-Key instead of Bearer token
356
+ headers = {
357
+ "Content-Type": "application/json",
358
+ "X-Goog-Api-Key": self.api_key
359
+ }
360
+
361
+ try:
362
+ response = requests.get(url, headers=headers)
363
+ response.raise_for_status()
364
+ result = response.json()
365
+
366
+ # Gemini API returns models in a different format
367
+ if "models" in result:
368
+ # Transform to match our expected format
369
+ return [{"id": model.get("name", "").split("/")[-1],
370
+ "owned_by": "Google"}
371
+ for model in result["models"]]
372
+ else:
373
+ print("Error: Unexpected response format when retrieving Gemini models.")
374
+ return []
375
+
376
+ except requests.exceptions.HTTPError as e:
377
+ print(f"HTTP Error with Gemini API: {e}")
287
378
  return []
288
379
 
289
- except requests.exceptions.HTTPError as e:
290
- if e.response.status_code == 401:
291
- print("Error: Authentication failed. Please check your API key.")
292
- elif e.response.status_code == 404:
293
- print(f"Error: Models endpoint not found at {url}")
294
- elif e.response.status_code == 429:
295
- print("Error: Rate limit exceeded. Please try again later.")
296
- else:
297
- print(f"HTTP Error: {e}")
298
- return []
299
-
300
- except requests.exceptions.ConnectionError:
301
- print(f"Error: Could not connect to {self.base_url}. Please check your internet connection and base URL.")
302
- return []
380
+ except Exception as e:
381
+ print(f"Error retrieving Gemini models: {e}")
382
+ return []
383
+ else:
384
+ # Standard OpenAI-compatible endpoint
385
+ url = f"{self.base_url}models"
303
386
 
304
- except Exception as e:
305
- print(f"Error: An unexpected error occurred while retrieving models: {e}")
306
- return []
387
+ try:
388
+ response = requests.get(url, headers=self.headers)
389
+ response.raise_for_status() # Raise exception for HTTP errors
390
+ result = response.json()
391
+
392
+ if "data" in result:
393
+ return result["data"]
394
+ else:
395
+ print("Error: Unexpected response format when retrieving models.")
396
+ return []
397
+
398
+ except requests.exceptions.HTTPError as e:
399
+ if e.response.status_code == 401:
400
+ print("Error: Authentication failed. Please check your API key.")
401
+ elif e.response.status_code == 404:
402
+ print(f"Error: Models endpoint not found at {url}")
403
+ elif e.response.status_code == 429:
404
+ print("Error: Rate limit exceeded. Please try again later.")
405
+ else:
406
+ print(f"HTTP Error: {e}")
407
+ return []
408
+
409
+ except requests.exceptions.ConnectionError:
410
+ print(f"Error: Could not connect to {self.base_url}. Please check your internet connection and base URL.")
411
+ return []
412
+
413
+ except Exception as e:
414
+ print(f"Error: An unexpected error occurred while retrieving models: {e}")
415
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=Ba3B4VAs-fnuSdfo22dAe2vmSy3ADVZlKYiDGTy6C6I,29818
3
+ ngpt/client.py,sha256=qwAmBHSJvadbsvK5YPbY0By1M4bBvxd7j8piP5UdTgE,16553
4
+ ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
+ ngpt-1.7.2.dist-info/METADATA,sha256=uW2p_2ENm1OJLNW35nZcyDr_L0JvD5eeMrTvWGCPQmg,10568
6
+ ngpt-1.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-1.7.2.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-1.7.2.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-1.7.2.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
- ngpt/cli.py,sha256=AyIraZFq7icPot0moqPVJer72iqbtJxKBhy6VH-dwAA,28746
3
- ngpt/client.py,sha256=ygtY2xuu-PAFPrz1CUJxcj3hyWw7q2kRG85ClDGClCw,12089
4
- ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
- ngpt-1.7.0.dist-info/METADATA,sha256=N0MgHiXxicUgkDTNS6dfCw32CHDmgpnX0N4HpFKm2nE,10568
6
- ngpt-1.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-1.7.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-1.7.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-1.7.0.dist-info/RECORD,,
File without changes