ccs-llmconnector 1.1.1__tar.gz → 1.1.2__tar.gz

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.
Files changed (22) hide show
  1. {ccs_llmconnector-1.1.1/src/ccs_llmconnector.egg-info → ccs_llmconnector-1.1.2}/PKG-INFO +1 -1
  2. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/pyproject.toml +1 -1
  3. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2/src/ccs_llmconnector.egg-info}/PKG-INFO +1 -1
  4. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/gemini_client.py +56 -38
  5. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/LICENSE +0 -0
  6. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/MANIFEST.in +0 -0
  7. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/README.md +0 -0
  8. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/setup.cfg +0 -0
  9. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/ccs_llmconnector.egg-info/SOURCES.txt +0 -0
  10. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/ccs_llmconnector.egg-info/dependency_links.txt +0 -0
  11. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/ccs_llmconnector.egg-info/entry_points.txt +0 -0
  12. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/ccs_llmconnector.egg-info/requires.txt +0 -0
  13. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/ccs_llmconnector.egg-info/top_level.txt +0 -0
  14. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/__init__.py +0 -0
  15. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/anthropic_client.py +0 -0
  16. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/client.py +0 -0
  17. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/client_cli.py +0 -0
  18. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/grok_client.py +0 -0
  19. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/openai_client.py +0 -0
  20. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/py.typed +0 -0
  21. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/types.py +0 -0
  22. {ccs_llmconnector-1.1.1 → ccs_llmconnector-1.1.2}/src/llmconnector/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccs-llmconnector
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Lightweight wrapper around different LLM provider Python SDK Responses APIs.
5
5
  Author: CCS
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ccs-llmconnector"
7
- version = "1.1.1"
7
+ version = "1.1.2"
8
8
  description = "Lightweight wrapper around different LLM provider Python SDK Responses APIs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccs-llmconnector
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Lightweight wrapper around different LLM provider Python SDK Responses APIs.
5
5
  Author: CCS
6
6
  License: MIT
@@ -15,11 +15,29 @@ from google.genai import types
15
15
  from .types import ImageInput, MessageSequence, normalize_messages
16
16
  from .utils import clamp_retries, run_sync_in_thread, run_with_retries
17
17
 
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class GeminiClient:
22
- """Convenience wrapper around the Google Gemini SDK."""
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ _GEMINI_MIN_TIMEOUT_S = 10.0
22
+ _GEMINI_MIN_TIMEOUT_MS = int(_GEMINI_MIN_TIMEOUT_S * 1000)
23
+
24
+
25
+ def _normalize_gemini_timeout_ms(timeout_s: float) -> int:
26
+ """Convert a seconds timeout into the millisecond value expected by google-genai HttpOptions."""
27
+ # google-genai HttpOptions expects milliseconds, but our public API uses seconds.
28
+ effective_timeout_s = max(_GEMINI_MIN_TIMEOUT_S, timeout_s)
29
+ if effective_timeout_s != timeout_s:
30
+ logger.warning(
31
+ "Gemini timeout %ss is too short, clamping to %ss.",
32
+ timeout_s,
33
+ effective_timeout_s,
34
+ )
35
+ timeout_ms = int(effective_timeout_s * 1000)
36
+ return max(_GEMINI_MIN_TIMEOUT_MS, timeout_ms)
37
+
38
+
39
+ class GeminiClient:
40
+ """Convenience wrapper around the Google Gemini SDK."""
23
41
 
24
42
  def generate_response(
25
43
  self,
@@ -102,15 +120,13 @@ class GeminiClient:
102
120
 
103
121
  retry_count = clamp_retries(max_retries)
104
122
 
105
- def _build_client() -> genai.Client:
106
- client_kwargs: dict[str, object] = {"api_key": api_key}
107
- if timeout_s is not None:
108
- # Gemini requires at least 10s timeout if set
109
- effective_timeout = max(10.0, timeout_s)
110
- if effective_timeout != timeout_s:
111
- logger.warning("Gemini timeout %ss is too short, clamping to %ss.", timeout_s, effective_timeout)
112
- client_kwargs["http_options"] = types.HttpOptions(timeout=effective_timeout)
113
- return genai.Client(**client_kwargs)
123
+ def _build_client() -> genai.Client:
124
+ client_kwargs: dict[str, object] = {"api_key": api_key}
125
+ if timeout_s is not None:
126
+ client_kwargs["http_options"] = types.HttpOptions(
127
+ timeout=_normalize_gemini_timeout_ms(timeout_s)
128
+ )
129
+ return genai.Client(**client_kwargs)
114
130
 
115
131
  def _run_request() -> str:
116
132
  client = _build_client()
@@ -268,15 +284,13 @@ class GeminiClient:
268
284
 
269
285
  retry_count = clamp_retries(max_retries)
270
286
 
271
- def _build_client() -> genai.Client:
272
- client_kwargs: dict[str, object] = {"api_key": api_key}
273
- if timeout_s is not None:
274
- # Gemini requires at least 10s timeout if set
275
- effective_timeout = max(10.0, timeout_s)
276
- if effective_timeout != timeout_s:
277
- logger.warning("Gemini timeout %ss is too short, clamping to %ss.", timeout_s, effective_timeout)
278
- client_kwargs["http_options"] = types.HttpOptions(timeout=effective_timeout)
279
- return genai.Client(**client_kwargs)
287
+ def _build_client() -> genai.Client:
288
+ client_kwargs: dict[str, object] = {"api_key": api_key}
289
+ if timeout_s is not None:
290
+ client_kwargs["http_options"] = types.HttpOptions(
291
+ timeout=_normalize_gemini_timeout_ms(timeout_s)
292
+ )
293
+ return genai.Client(**client_kwargs)
280
294
 
281
295
  def _run_request() -> bytes:
282
296
  client = _build_client()
@@ -362,15 +376,13 @@ class GeminiClient:
362
376
 
363
377
  retry_count = clamp_retries(max_retries)
364
378
 
365
- def _build_client() -> genai.Client:
366
- client_kwargs: dict[str, object] = {"api_key": api_key}
367
- if timeout_s is not None:
368
- # Gemini requires at least 10s timeout if set
369
- effective_timeout = max(10.0, timeout_s)
370
- if effective_timeout != timeout_s:
371
- logger.warning("Gemini timeout %ss is too short, clamping to %ss.", timeout_s, effective_timeout)
372
- client_kwargs["http_options"] = types.HttpOptions(timeout=effective_timeout)
373
- return genai.Client(**client_kwargs)
379
+ def _build_client() -> genai.Client:
380
+ client_kwargs: dict[str, object] = {"api_key": api_key}
381
+ if timeout_s is not None:
382
+ client_kwargs["http_options"] = types.HttpOptions(
383
+ timeout=_normalize_gemini_timeout_ms(timeout_s)
384
+ )
385
+ return genai.Client(**client_kwargs)
374
386
 
375
387
  def _run_request() -> list[dict[str, Optional[str]]]:
376
388
  models: list[dict[str, Optional[str]]] = []
@@ -457,12 +469,18 @@ class GeminiClient:
457
469
  return _part_from_path(Path(image))
458
470
 
459
471
 
460
- def _part_from_path(path: Path) -> types.Part:
461
- """Create an image part from a local filesystem path."""
462
- expanded = path.expanduser()
463
- data = expanded.read_bytes()
464
- mime_type = mimetypes.guess_type(expanded.name)[0] or "application/octet-stream"
465
- return types.Part.from_bytes(data=data, mime_type=mime_type)
472
+ def _part_from_path(path: Path) -> types.Part:
473
+ """Create an image part from a local filesystem path."""
474
+ # Ensure common audio types are recognized across platforms (used for transcription as well).
475
+ mimetypes.add_type("audio/mp4", ".m4a")
476
+ mimetypes.add_type("audio/mpeg", ".mp3")
477
+ mimetypes.add_type("audio/wav", ".wav")
478
+ mimetypes.add_type("audio/aac", ".aac")
479
+
480
+ expanded = path.expanduser()
481
+ data = expanded.read_bytes()
482
+ mime_type = mimetypes.guess_type(expanded.name)[0] or "application/octet-stream"
483
+ return types.Part.from_bytes(data=data, mime_type=mime_type)
466
484
 
467
485
 
468
486
  def _part_from_url(url: str) -> types.Part: