kalibr 1.3.0__py3-none-any.whl → 1.4.0__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.
kalibr/router.py CHANGED
@@ -260,34 +260,80 @@ class Router:
260
260
  else:
261
261
  router_span.set_attribute("kalibr.fallback", True)
262
262
 
263
- # Step 5: Dispatch to provider
264
- try:
265
- response = self._dispatch(model_id, messages, tool_id, **{**params, **kwargs})
266
-
267
- # Auto-report if success_when provided
268
- if self.success_when and not self._outcome_reported:
263
+ # Step 5: Build ordered candidate paths for fallback
264
+ # First: intelligence-selected path, then remaining registered paths
265
+ candidate_paths = []
266
+ selected_path = {"model": model_id, "tools": tool_id, "params": params}
267
+ candidate_paths.append(selected_path)
268
+
269
+ # Add remaining paths, skipping duplicates of the selected model
270
+ for path in self._paths:
271
+ if path["model"] != model_id:
272
+ candidate_paths.append(path)
273
+
274
+ # Step 6: Try each candidate path with fallback
275
+ from kalibr.intelligence import report_outcome
276
+
277
+ last_exception = None
278
+ for i, candidate in enumerate(candidate_paths):
279
+ candidate_model = candidate["model"]
280
+ candidate_tools = candidate.get("tools")
281
+ candidate_params = candidate.get("params") or {}
282
+
283
+ is_fallback = (i > 0)
284
+ if is_fallback:
285
+ logger.warning(f"Primary path failed, trying fallback: {candidate_model}")
286
+
287
+ try:
288
+ response = self._dispatch(
289
+ candidate_model,
290
+ messages,
291
+ candidate_tools,
292
+ **{**candidate_params, **kwargs}
293
+ )
294
+
295
+ # Success! Update state to reflect which model succeeded
296
+ self._last_model_id = candidate_model
297
+
298
+ # Auto-report success if success_when provided
299
+ if self.success_when and not self._outcome_reported:
300
+ try:
301
+ output = response.choices[0].message.content or ""
302
+ success = self.success_when(output)
303
+ self.report(success=success)
304
+ except Exception as e:
305
+ logger.warning(f"Auto-outcome evaluation failed: {e}")
306
+
307
+ # Add trace_id to response for explicit linkage
308
+ response.kalibr_trace_id = trace_id
309
+ return response
310
+
311
+ except Exception as e:
312
+ last_exception = e
313
+
314
+ # Log the failure with model name and error
315
+ logger.warning(f"Model {candidate_model} failed: {type(e).__name__}: {e}")
316
+
317
+ # Report failure for this path to enable Thompson Sampling learning
269
318
  try:
270
- output = response.choices[0].message.content or ""
271
- success = self.success_when(output)
272
- self.report(success=success)
273
- except Exception as e:
274
- logger.warning(f"Auto-outcome evaluation failed: {e}")
319
+ report_outcome(
320
+ trace_id=trace_id,
321
+ goal=self.goal,
322
+ success=False,
323
+ failure_reason=f"provider_error: {type(e).__name__}",
324
+ model_id=candidate_model,
325
+ )
326
+ except Exception:
327
+ pass
275
328
 
276
- # Add trace_id to response for explicit linkage
277
- response.kalibr_trace_id = trace_id
278
- return response
329
+ # Continue to next candidate
330
+ continue
279
331
 
280
- except Exception as e:
281
- router_span.set_attribute("error", True)
282
- router_span.set_attribute("error.type", type(e).__name__)
283
-
284
- # Auto-report failure
285
- if not self._outcome_reported:
286
- try:
287
- self.report(success=False, reason=f"provider_error: {type(e).__name__}")
288
- except:
289
- pass
290
- raise
332
+ # All paths failed - set error attributes and raise
333
+ router_span.set_attribute("error", True)
334
+ router_span.set_attribute("error.type", type(last_exception).__name__)
335
+ self._outcome_reported = True # Prevent double-reporting on raise
336
+ raise last_exception
291
337
 
292
338
  def report(
293
339
  self,
kalibr/tokens.py CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
- import tiktoken
5
+ # Import tiktoken optionally for token counting
6
+ try:
7
+ import tiktoken
8
+ HAS_TIKTOKEN = True
9
+ except ImportError:
10
+ HAS_TIKTOKEN = False
6
11
 
7
12
  # Cache for tokenizer instances
8
13
  _tokenizer_cache = {}
9
14
 
10
15
 
11
- def count_tokens(text: str, model_id: str) -> int:
16
+ def count_tokens(text: str, model_id: str = "gpt-4o") -> int:
12
17
  """Count tokens for given text and model.
13
18
 
14
19
  Args:
@@ -16,11 +21,15 @@ def count_tokens(text: str, model_id: str) -> int:
16
21
  model_id: Model identifier
17
22
 
18
23
  Returns:
19
- Token count (approximate)
24
+ Token count (approximate if tiktoken is not installed)
20
25
  """
21
26
  if not text:
22
27
  return 0
23
28
 
29
+ if not HAS_TIKTOKEN:
30
+ # Fallback: rough estimate of 4 chars per token
31
+ return len(text) // 4
32
+
24
33
  # Try to get exact tokenizer for OpenAI models
25
34
  if "gpt" in model_id.lower():
26
35
  try:
@@ -34,7 +43,13 @@ def count_tokens(text: str, model_id: str) -> int:
34
43
 
35
44
 
36
45
  def get_openai_encoding(model_id: str):
37
- """Get tiktoken encoding for OpenAI model."""
46
+ """Get tiktoken encoding for OpenAI model.
47
+
48
+ Returns None if tiktoken is not installed.
49
+ """
50
+ if not HAS_TIKTOKEN:
51
+ return None
52
+
38
53
  if model_id in _tokenizer_cache:
39
54
  return _tokenizer_cache[model_id]
40
55
 
@@ -48,5 +63,5 @@ def get_openai_encoding(model_id: str):
48
63
  _tokenizer_cache[model_id] = encoding
49
64
  return encoding
50
65
  except Exception as e:
51
- print(f"⚠️ Failed to load tokenizer for {model_id}: {e}")
66
+ print(f"Warning: Failed to load tokenizer for {model_id}: {e}")
52
67
  raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kalibr
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Adaptive routing for AI agents. Learns which models work best and routes automatically.
5
5
  Author-email: Kalibr Team <support@kalibr.systems>
6
6
  License: Apache-2.0
@@ -13,18 +13,15 @@ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.8
17
- Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
17
  Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Programming Language :: Python :: 3.12
21
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
20
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
- Requires-Python: >=3.9
21
+ Requires-Python: >=3.10
24
22
  Description-Content-Type: text/markdown
25
23
  License-File: LICENSE
26
24
  Requires-Dist: httpx>=0.27.0
27
- Requires-Dist: tiktoken>=0.8.0
28
25
  Requires-Dist: fastapi>=0.110.1
29
26
  Requires-Dist: uvicorn>=0.25.0
30
27
  Requires-Dist: pydantic>=2.6.4
@@ -35,6 +32,8 @@ Requires-Dist: requests>=2.31.0
35
32
  Requires-Dist: opentelemetry-api>=1.20.0
36
33
  Requires-Dist: opentelemetry-sdk>=1.20.0
37
34
  Requires-Dist: opentelemetry-exporter-otlp>=1.20.0
35
+ Provides-Extra: tokens
36
+ Requires-Dist: tiktoken>=0.8.0; extra == "tokens"
38
37
  Provides-Extra: langchain
39
38
  Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
40
39
  Provides-Extra: langchain-openai
@@ -73,11 +72,21 @@ Adaptive routing for AI agents. Kalibr learns which models work best for your ta
73
72
  [![Python](https://img.shields.io/pypi/pyversions/kalibr)](https://pypi.org/project/kalibr/)
74
73
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
75
74
 
75
+ ## Requirements
76
+
77
+ - Python 3.10 or higher
78
+ - pip 21.0 or higher
79
+
76
80
  ## Installation
77
81
  ```bash
78
82
  pip install kalibr
79
83
  ```
80
84
 
85
+ For accurate token counting, install with:
86
+ ```bash
87
+ pip install kalibr[tokens]
88
+ ```
89
+
81
90
  ## Setup
82
91
 
83
92
  Get your credentials from [dashboard.kalibr.systems/settings](https://dashboard.kalibr.systems/settings), then:
@@ -259,6 +268,7 @@ report_outcome(trace_id="...", goal="book_meeting", success=True)
259
268
 
260
269
  ## Other Integrations
261
270
  ```bash
271
+ pip install kalibr[tokens] # Accurate token counting (tiktoken)
262
272
  pip install kalibr[crewai] # CrewAI
263
273
  pip install kalibr[openai-agents] # OpenAI Agents SDK
264
274
  pip install kalibr[langchain-all] # LangChain with all providers
@@ -12,10 +12,10 @@ kalibr/kalibr_app.py,sha256=ItZwEh0FZPx9_BE-zPQajC2yxI2y9IHYwJD0k9tbHvY,2773
12
12
  kalibr/models.py,sha256=HwD_-iysZMSnCzMQYO1Qcf0aeXySupY7yJeBwl_dLS0,1024
13
13
  kalibr/pricing.py,sha256=wY0GzcrZdXuHlZoq2e74RkX0scd6somk_KYbr-RSHdE,8844
14
14
  kalibr/redaction.py,sha256=XibxX4Lv1Ci0opE6Tb5ZI2GLbO0a8E9U66MAg60llnc,1139
15
- kalibr/router.py,sha256=PS43LAkMnhQLrADX8bPACPP0PygEc9Sv-BWhX6TLQuc,18905
15
+ kalibr/router.py,sha256=UCRw5qBzA46c1dmPnw05P2-_2DqnElSeeDhI2N3AD9A,20975
16
16
  kalibr/schemas.py,sha256=XLZNLkXca6jbj9AF6gDIyGVnIcr1SVOsNYaKvW-wbgE,3669
17
17
  kalibr/simple_tracer.py,sha256=oiwXtiYaIqZxqCNV-b79_dsiJT0D3XvKhNT_LF6bRD4,9736
18
- kalibr/tokens.py,sha256=istjgaxi9S4dMddjuGtoQaTnZYcWLCqdnxRjV86yNXA,1297
18
+ kalibr/tokens.py,sha256=ug4y6h8gBaMLIfS0v9LQ-TzFaik5u5h4WANfOC7xV6U,1675
19
19
  kalibr/trace_capsule.py,sha256=SEfTE-GXvM9kcGCOZ5uEQSD8AnbmRRA0UUu0X8c8isw,10492
20
20
  kalibr/trace_models.py,sha256=9o7VJQk3gCrvdfXPrNh3Ptkq5sRgA9_qrLLE3jNkSBg,7304
21
21
  kalibr/tracer.py,sha256=jwWBpZbGXn6fEv4pw25BLFCH-22QUbyzofPWp1Iwdkk,11911
@@ -44,9 +44,9 @@ kalibr_langchain/callback.py,sha256=SNM1aHOXdG55grHmGyTwbXOeM6hjZTub2REiZD2H-d8,
44
44
  kalibr_langchain/chat_model.py,sha256=Y4xsZGx9gZpDUF8NP-edJuYam4k0NBySdA6B5484MKk,3190
45
45
  kalibr_openai_agents/__init__.py,sha256=wL59LzGstptKigfQDrKKt_7hcMO1JGVQtVAsE0lz-Zw,1367
46
46
  kalibr_openai_agents/processor.py,sha256=F550sdRf3rpguP1yOlgAUQWDLPBy4hSACV3-zOyCpOU,18257
47
- kalibr-1.3.0.dist-info/LICENSE,sha256=5mwAnB38l3_PjmOQn6_L6cZnJvus143DUjMBPIH1yso,10768
48
- kalibr-1.3.0.dist-info/METADATA,sha256=b9D17tB5m0XMvQh5QL9XGJ0yEr7ftJb18iMWMG5hNC4,9298
49
- kalibr-1.3.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
50
- kalibr-1.3.0.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
51
- kalibr-1.3.0.dist-info/top_level.txt,sha256=dIfBOWUnnHGFDwgz5zfIx5_0bU3wOUgAbYr4JcFHZmo,59
52
- kalibr-1.3.0.dist-info/RECORD,,
47
+ kalibr-1.4.0.dist-info/LICENSE,sha256=5mwAnB38l3_PjmOQn6_L6cZnJvus143DUjMBPIH1yso,10768
48
+ kalibr-1.4.0.dist-info/METADATA,sha256=xyDkq8f65JLaUddRwCzJf98L57w1MFrS7YPuh6OND14,9458
49
+ kalibr-1.4.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
50
+ kalibr-1.4.0.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
51
+ kalibr-1.4.0.dist-info/top_level.txt,sha256=dIfBOWUnnHGFDwgz5zfIx5_0bU3wOUgAbYr4JcFHZmo,59
52
+ kalibr-1.4.0.dist-info/RECORD,,
File without changes