ct-toolkit 0.3.6__tar.gz → 0.3.7__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 (59) hide show
  1. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/PKG-INFO +5 -3
  2. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/README.md +1 -1
  3. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/__init__.py +1 -1
  4. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/cli.py +32 -0
  5. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/wrapper.py +40 -37
  6. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/l3_icm.py +19 -16
  7. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/embedding.py +16 -14
  8. ct_toolkit-0.3.7/ct_toolkit/middleware/litellm.py +344 -0
  9. ct_toolkit-0.3.7/ct_toolkit/server.py +174 -0
  10. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/PKG-INFO +5 -3
  11. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/SOURCES.txt +2 -0
  12. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/requires.txt +3 -1
  13. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/pyproject.toml +4 -2
  14. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/LICENSE +0 -0
  15. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/__main__.py +0 -0
  16. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/__init__.py +0 -0
  17. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/compatibility.py +0 -0
  18. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/compression_guard.py +0 -0
  19. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/exceptions.py +0 -0
  20. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/integrity.py +0 -0
  21. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/core/kernel.py +0 -0
  22. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/__init__.py +0 -0
  23. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/analysis.py +0 -0
  24. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/engine.py +0 -0
  25. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/l2_judge.py +0 -0
  26. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/loss.py +0 -0
  27. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/divergence/scheduler.py +0 -0
  28. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/__init__.py +0 -0
  29. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/probes/__init__.py +0 -0
  30. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/reflective.py +0 -0
  31. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/__init__.py +0 -0
  32. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/__init__.py +0 -0
  33. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/defense.yaml +0 -0
  34. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/finance.yaml +0 -0
  35. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/general.yaml +0 -0
  36. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/medical.yaml +0 -0
  37. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/kernels/__init__.py +0 -0
  38. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/kernels/default.yaml +0 -0
  39. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/kernels/defense.yaml +0 -0
  40. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/middleware/__init__.py +0 -0
  41. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/middleware/autogen.py +0 -0
  42. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/middleware/crewai.py +0 -0
  43. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/middleware/deepagents.py +0 -0
  44. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/middleware/langchain.py +0 -0
  45. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/provenance/__init__.py +0 -0
  46. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/provenance/log.py +0 -0
  47. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/provenance/vault/__init__.py +0 -0
  48. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/utils/__init__.py +0 -0
  49. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit/utils/logger.py +0 -0
  50. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/dependency_links.txt +0 -0
  51. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/entry_points.txt +0 -0
  52. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/top_level.txt +0 -0
  53. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/setup.cfg +0 -0
  54. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_analysis.py +0 -0
  55. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_cli.py +0 -0
  56. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_cli_extended.py +0 -0
  57. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_deep_agents_integration.py +0 -0
  58. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_loss.py +0 -0
  59. {ct_toolkit-0.3.6 → ct_toolkit-0.3.7}/tests/test_summarization_drift.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -9,7 +9,7 @@ Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: instructor>=1.3.0
12
- Requires-Dist: any-llm-sdk[all]>=1.0.0
12
+ Requires-Dist: litellm>=1.40.0
13
13
  Requires-Dist: numpy>=1.26.0
14
14
  Requires-Dist: pyyaml>=6.0
15
15
  Requires-Dist: pydantic>=2.5.0
@@ -20,6 +20,8 @@ Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: jinja2>=3.1.0
21
21
  Requires-Dist: pyasn1>=0.6.3
22
22
  Requires-Dist: typer[all]>=0.12.0
23
+ Requires-Dist: fastapi>=0.110.0
24
+ Requires-Dist: uvicorn>=0.29.0
23
25
  Provides-Extra: ollama
24
26
  Requires-Dist: ollama>=0.2.0; extra == "ollama"
25
27
  Provides-Extra: langchain
@@ -116,7 +118,7 @@ ct-toolkit list-templates
116
118
 
117
119
  | Metric | Status |
118
120
  | :--------------- | :------------------------------------------------------ |
119
- | **Tests** | ✅ 269/272 passing (92% coverage) |
121
+ | **Tests** | ✅ 281/292 passing (95% coverage) |
120
122
  | **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
121
123
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
122
124
 
@@ -75,7 +75,7 @@ ct-toolkit list-templates
75
75
 
76
76
  | Metric | Status |
77
77
  | :--------------- | :------------------------------------------------------ |
78
- | **Tests** | ✅ 269/272 passing (92% coverage) |
78
+ | **Tests** | ✅ 281/292 passing (95% coverage) |
79
79
  | **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
80
80
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
81
81
 
@@ -23,7 +23,7 @@ from ct_toolkit.core.exceptions import (
23
23
  ChainIntegrityError,
24
24
  )
25
25
 
26
- __version__ = "0.3.6"
26
+ __version__ = "0.3.7"
27
27
 
28
28
  __all__ = [
29
29
  "TheseusWrapper",
@@ -17,6 +17,7 @@ from rich.table import Table
17
17
  from ct_toolkit import TheseusWrapper, WrapperConfig, __version__
18
18
  from ct_toolkit.divergence.l3_icm import ICMRunner
19
19
  from ct_toolkit.core.kernel import ConstitutionalKernel
20
+ from ct_toolkit.server import start_server
20
21
 
21
22
  app = typer.Typer(
22
23
  help="Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems.",
@@ -135,6 +136,37 @@ def audit(
135
136
  if not report.is_healthy:
136
137
  raise typer.Exit(code=1)
137
138
 
139
+ @app.command()
140
+ def serve(
141
+ host: str = typer.Option("127.0.0.1", "--host", help="Host to bind the server to."),
142
+ port: int = typer.Option(8001, "--port", help="Port to bind the server to."),
143
+ kernel: str = typer.Option("general", "--kernel", help="Name of the Constitutional Kernel to use."),
144
+ template: str = typer.Option("general", "--template", help="Name of the Identity Template to use."),
145
+ vault_path: str = typer.Option("./ct_provenance.db", "--vault", help="Path to the provenance log database."),
146
+ judge_provider: str = typer.Option("openai", "--judge-provider", help="LLM provider for the L2 judge."),
147
+ judge_model: Optional[str] = typer.Option(None, "--judge-model", help="Model ID for the L2 judge."),
148
+ ):
149
+ """Start the CT-Toolkit Guardrail Server for LiteLLM integration."""
150
+ console.print(f"Starting Guardrail Server...")
151
+ console.print(f" - Kernel: [bold yellow]{kernel}[/bold yellow]")
152
+ console.print(f" - Template: [bold green]{template}[/bold green]")
153
+ console.print(f" - Bind: [bold cyan]{host}:{port}[/bold cyan]")
154
+
155
+ try:
156
+ config = WrapperConfig(
157
+ kernel_name=kernel,
158
+ template=template,
159
+ vault_path=vault_path,
160
+ project_root=Path.cwd()
161
+ )
162
+ wrapper = TheseusWrapper(provider=judge_provider, config=config)
163
+
164
+ # Start the uvicorn server
165
+ start_server(wrapper=wrapper, host=host, port=port)
166
+ except Exception as e:
167
+ console.print(f"[bold red]Failed to start server:[/bold red] {e}")
168
+ raise typer.Exit(code=1)
169
+
138
170
  @app.command()
139
171
  def list_kernels():
140
172
  """List available Constitutional Kernels."""
@@ -25,9 +25,7 @@ import os
25
25
  from dataclasses import dataclass, field
26
26
  from pathlib import Path
27
27
  from typing import Any, Iterator, Optional, Callable, Dict
28
- import any_llm
29
- from any_llm import AnyLLM
30
- import glob
28
+ import litellm
31
29
 
32
30
  from ct_toolkit.core.kernel import ConstitutionalKernel
33
31
  from ct_toolkit.core.compatibility import CompatibilityLayer, CompatibilityResult
@@ -97,7 +95,7 @@ class TheseusWrapper:
97
95
  Identity-continuity proxy wrapping the LLM API client.
98
96
 
99
97
  Supported providers: openai (default), anthropic, ollama, google, etc.
100
- (Any provider supported by any-llm-sdk)
98
+ (Any provider supported by LiteLLM)
101
99
 
102
100
  Usage:
103
101
  from ct_toolkit import TheseusWrapper
@@ -133,7 +131,7 @@ class TheseusWrapper:
133
131
  Optional if 'provider' is specified.
134
132
  config: Optional WrapperConfig instance.
135
133
  provider: Provider name (e.g. "openai", "anthropic", "ollama", "google", "cohere").
136
- If provided, 'any-llm' will be used to initialize the client internally.
134
+ If provided, LiteLLM will be used to initialize the client internally.
137
135
  project_root: Path to the user's project root directory. Used to find a custom 'config/' folder.
138
136
  Defaults to the current working directory.
139
137
  """
@@ -243,7 +241,7 @@ class TheseusWrapper:
243
241
  return client.lower()
244
242
 
245
243
  try:
246
- # Check for any_llm wrapper
244
+ # Check for LiteLLM specific attributes if any
247
245
  if hasattr(client, "provider") and isinstance(client.provider, str):
248
246
  return client.provider
249
247
 
@@ -501,7 +499,7 @@ class TheseusWrapper:
501
499
  model: str | None,
502
500
  **kwargs: Any,
503
501
  ) -> Any:
504
- """Uses any_llm for unified provider calling."""
502
+ """Uses LiteLLM for unified provider calling."""
505
503
  # --- Passive Context Compression Detection ---
506
504
  if self._config.compression_passive_detection and self._shadow_history:
507
505
  # Simple heuristic: message count dropped significantly
@@ -514,8 +512,8 @@ class TheseusWrapper:
514
512
 
515
513
  # Update shadow history for next call
516
514
  self._shadow_history = list(messages)
517
- # Convert messages to any_llm format if necessary
518
- # any_llm.completion handles different formats internally
515
+
516
+ # LiteLLM handles different message formats internally
519
517
 
520
518
  # Set default models per provider if not specified
521
519
  if not model:
@@ -524,32 +522,29 @@ class TheseusWrapper:
524
522
  elif self._provider == "ollama": model = "llama3"
525
523
  else: model = "gpt-4o-mini"
526
524
 
527
- # any_llm expects "provider:model" format
525
+ # LiteLLM expects "provider/model" format for non-default providers
526
+ # Usually OpenAI models don't need a prefix.
528
527
  full_model = model
529
- if ":" not in model:
530
- full_model = f"{self._provider}:{model}"
531
-
532
- # Prepare any_llm call
533
- any_llm_kwargs = {
534
- "model": full_model,
535
- "messages": messages,
536
- **kwargs
537
- }
528
+ if ":" in model:
529
+ # If it has the legacy format "provider:model", convert it
530
+ full_model = model.replace(":", "/", 1)
531
+ elif self._provider not in ("openai", "unknown"):
532
+ full_model = f"{self._provider}/{model}"
538
533
 
539
534
  # Extract connection info from client if possible
540
535
  if hasattr(self._client, "base_url") and self._client.base_url:
541
- any_llm_kwargs["api_base"] = str(self._client.base_url)
536
+ kwargs["api_base"] = str(self._client.base_url)
542
537
  if hasattr(self._client, "api_key") and self._client.api_key:
543
- any_llm_kwargs["api_key"] = self._client.api_key
538
+ kwargs["api_key"] = self._client.api_key
544
539
 
545
540
  # Transfer common client configurations if available
546
- if hasattr(self._client, "timeout") and "timeout" not in any_llm_kwargs:
547
- any_llm_kwargs["timeout"] = self._client.timeout
541
+ if hasattr(self._client, "timeout") and "timeout" not in kwargs:
542
+ kwargs["timeout"] = self._client.timeout
548
543
 
549
544
  try:
550
- return any_llm.completion(**any_llm_kwargs)
545
+ return litellm.completion(model=full_model, messages=messages, **kwargs)
551
546
  except Exception as e:
552
- logger.error(f"any_llm call failed: {e}")
547
+ logger.error(f"litellm call failed: {e}")
553
548
  raise
554
549
 
555
550
  # ── Helper Methods ──────────────────────────────────────────────────────
@@ -603,25 +598,33 @@ class TheseusWrapper:
603
598
  return messages
604
599
 
605
600
  def _extract_content(self, raw: Any) -> str:
606
- # Handle dict responses (e.g. from some mockers or raw APIs)
601
+ # Handle dict responses
607
602
  if isinstance(raw, dict):
608
603
  if "choices" in raw and isinstance(raw["choices"], list) and raw["choices"]:
609
- # OpenAI dict format
610
604
  return raw["choices"][0].get("message", {}).get("content", "") or ""
611
605
  if "message" in raw:
612
- # Ollama dict format
606
+ # Ollama dictate format
613
607
  return raw["message"].get("content", "") or ""
614
608
  return str(raw)
615
609
 
616
- # OpenAI
617
- if hasattr(raw, "choices"):
618
- return raw.choices[0].message.content or ""
619
- # Anthropic
620
- if hasattr(raw, "content") and isinstance(raw.content, list):
621
- return raw.content[0].text if raw.content else ""
622
- # Ollama
623
- if hasattr(raw, "message"):
624
- return raw.message.content or ""
610
+ # LiteLLM ModelResponse (Standardized for all providers)
611
+ try:
612
+ if hasattr(raw, "choices") and raw.choices:
613
+ choice = raw.choices[0]
614
+ if hasattr(choice, "message") and choice.message:
615
+ return choice.message.content or ""
616
+ # Some older/alternative formats
617
+ if hasattr(choice, "text"):
618
+ return choice.text or ""
619
+ except Exception:
620
+ pass
621
+
622
+ # Fallback raw extraction for edge cases
623
+ if hasattr(raw, "content") and isinstance(raw.content, list): # Anthropic style
624
+ return raw.content[0].text if raw.content else ""
625
+ if hasattr(raw, "message"): # Ollama generic style
626
+ return getattr(raw.message, "content", "") or ""
627
+
625
628
  return str(raw)
626
629
 
627
630
  def _extract_model(self, raw: Any, fallback: str | None) -> str:
@@ -19,7 +19,7 @@ from dataclasses import dataclass, field
19
19
  from pathlib import Path
20
20
  from typing import Any
21
21
 
22
- import any_llm
22
+ import litellm
23
23
 
24
24
  from ct_toolkit.utils.logger import get_logger
25
25
 
@@ -290,11 +290,14 @@ class ICMRunner:
290
290
  def _call_model(self, prompt: str) -> str:
291
291
  system = self._kernel.get_system_prompt_injection()
292
292
 
293
+ # LiteLLM format
293
294
  full_model = self._model
294
- if ":" not in self._model:
295
- full_model = f"{self._provider}:{self._model}"
295
+ if ":" in self._model:
296
+ full_model = self._model.replace(":", "/", 1)
297
+ elif self._provider not in ("openai", "unknown"):
298
+ full_model = f"{self._provider}/{self._model}"
296
299
 
297
- any_llm_kwargs = {
300
+ kwargs: dict[str, Any] = {
298
301
  "model": full_model,
299
302
  "messages": [
300
303
  {"role": "system", "content": system},
@@ -306,13 +309,20 @@ class ICMRunner:
306
309
 
307
310
  # Extract connection info from client if possible
308
311
  if hasattr(self._client, "base_url") and self._client.base_url:
309
- any_llm_kwargs["api_base"] = str(self._client.base_url)
312
+ kwargs["api_base"] = str(self._client.base_url)
310
313
  if hasattr(self._client, "api_key") and self._client.api_key:
311
- any_llm_kwargs["api_key"] = self._client.api_key
314
+ kwargs["api_key"] = self._client.api_key
312
315
 
313
316
  try:
314
- resp = any_llm.completion(**any_llm_kwargs)
315
- # any_llm SDK standardizes responses but fallback handling preserves robustness
317
+ resp = litellm.completion(**kwargs)
318
+
319
+ # Extract content from LiteLLM response
320
+ if hasattr(resp, "choices") and resp.choices:
321
+ choice = resp.choices[0]
322
+ if hasattr(choice, "message") and choice.message:
323
+ return choice.message.content or ""
324
+
325
+ # Fallback
316
326
  if isinstance(resp, dict):
317
327
  if "choices" in resp and resp["choices"]:
318
328
  return resp["choices"][0].get("message", {}).get("content", "")
@@ -320,16 +330,9 @@ class ICMRunner:
320
330
  return resp["message"].get("content", "")
321
331
  return str(resp)
322
332
 
323
- if hasattr(resp, "choices"):
324
- return resp.choices[0].message.content or ""
325
- if hasattr(resp, "content") and isinstance(resp.content, list):
326
- return resp.content[0].text if resp.content else ""
327
- if hasattr(resp, "message"):
328
- return resp.message.content or ""
329
-
330
333
  return str(resp)
331
334
  except Exception as e:
332
- logger.error(f"ICM probe any_llm call failed: {e}")
335
+ logger.error(f"ICM probe litellm call failed: {e}")
333
336
  raise
334
337
 
335
338
  # ── Probe Loading ──────────────────────────────────────────────────────────
@@ -9,9 +9,7 @@ Step 3 MVP implementation:
9
9
  - Each response embedding is calculated
10
10
  - Cosine distance = 1 - cosine_similarity → divergence score
11
11
  """
12
- from __future__ import annotations
13
-
14
- import json
12
+ import litellm
15
13
  import hashlib
16
14
  from pathlib import Path
17
15
  from typing import Any
@@ -155,17 +153,21 @@ class IdentityEmbeddingLayer:
155
153
  """
156
154
  if self._embedding_client is not None:
157
155
  try:
158
- # Robust attribute check for OpenAI-compatible clients
159
- embeddings_api = getattr(self._embedding_client, "embeddings", None)
160
- if embeddings_api:
161
- response = embeddings_api.create(
162
- input=[text],
163
- model=self._embedding_model
164
- )
165
- embedding = response.data[0].embedding
166
- return np.array(embedding, dtype=np.float32)
167
- else:
168
- logger.debug("Embedding client does not support .embeddings API. Falling back.")
156
+ # Prepare arguments for litellm.embedding
157
+ kwargs: dict[str, Any] = {
158
+ "model": self._embedding_model,
159
+ "input": [text]
160
+ }
161
+
162
+ # Transfer client configuration if available
163
+ if hasattr(self._embedding_client, "base_url") and self._embedding_client.base_url:
164
+ kwargs["api_base"] = str(self._embedding_client.base_url)
165
+ if hasattr(self._embedding_client, "api_key") and self._embedding_client.api_key:
166
+ kwargs["api_key"] = self._embedding_client.api_key
167
+
168
+ response = litellm.embedding(**kwargs)
169
+ embedding = response.data[0]["embedding"]
170
+ return np.array(embedding, dtype=np.float32)
169
171
  except Exception as e:
170
172
  if self._strict_embedding:
171
173
  raise RuntimeError(f"Embedding API failed: {e}") from e
@@ -0,0 +1,344 @@
1
+ """
2
+ ct_toolkit.middleware.litellm
3
+ ------------------------------
4
+ LiteLLM integration for identity-continuity guardrails.
5
+
6
+ Provides ``TheseusLiteLLMCallback`` which hooks into LiteLLM's CustomLogger
7
+ system to enforce Constitutional Kernel rules on every LLM call — regardless
8
+ of which provider or model is being used underneath.
9
+
10
+ This is the "Governance Proxy Middleware" pattern:
11
+ - Works with ANY provider LiteLLM supports (100+ models)
12
+ - No agent code changes required
13
+ - Pre-call: kernel validation → blocks axiomatic violations
14
+ - Post-call: divergence analysis + provenance logging
15
+
16
+ Optional dependency — requires ``pip install 'ct-toolkit[litellm]'``.
17
+ LiteLLM does NOT need to be installed for the rest of ct-toolkit to work.
18
+
19
+ ## SDK usage (direct litellm calls)::
20
+
21
+ import litellm
22
+ from ct_toolkit import TheseusWrapper, WrapperConfig
23
+ from ct_toolkit.middleware.litellm import TheseusLiteLLMCallback
24
+
25
+ wrapper = TheseusWrapper(provider="openai", kernel_name="defense")
26
+ handler = TheseusLiteLLMCallback(wrapper)
27
+
28
+ litellm.callbacks = [handler]
29
+
30
+ # All litellm.completion() calls are now identity-protected
31
+ response = litellm.completion(
32
+ model="gpt-4o-mini",
33
+ messages=[{"role": "user", "content": "Hello!"}]
34
+ )
35
+
36
+ ## LiteLLM Proxy (proxy_config.yaml)::
37
+
38
+ model_list:
39
+ - model_name: gpt-4o-mini
40
+ litellm_params:
41
+ model: gpt-4o-mini
42
+
43
+ litellm_settings:
44
+ callbacks: ct_toolkit.middleware.litellm.proxy_handler_instance
45
+
46
+ Compatible with litellm >= 1.40.0
47
+ """
48
+ from __future__ import annotations
49
+
50
+ import time
51
+ from typing import Any, Optional
52
+
53
+ from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
54
+ from ct_toolkit.utils.logger import get_logger
55
+
56
+ logger = get_logger(__name__)
57
+
58
+ try:
59
+ from litellm.integrations.custom_logger import CustomLogger
60
+ except ImportError as _exc:
61
+ raise ImportError(
62
+ "TheseusLiteLLMCallback requires litellm. "
63
+ "Install with: pip install 'litellm'"
64
+ ) from _exc
65
+
66
+
67
+ class TheseusLiteLLMCallback(CustomLogger):
68
+ """
69
+ LiteLLM CustomLogger that enforces CT-Toolkit identity guardrails.
70
+
71
+ Hooks:
72
+ async_pre_call_hook → Kernel validation (blocks axiomatic violations)
73
+ async_log_success_event → Divergence engine + Provenance Log
74
+ async_log_failure_event → Failure logging
75
+ log_success_event → Sync fallback for non-async callers
76
+ """
77
+
78
+ def __init__(self, wrapper: TheseusWrapper) -> None:
79
+ super().__init__()
80
+ self.wrapper = wrapper
81
+ logger.info(
82
+ f"CT Toolkit | TheseusLiteLLMCallback initialized "
83
+ f"(kernel='{wrapper.kernel.name}', template='{wrapper._config.template}')"
84
+ )
85
+
86
+ # ── Pre-call: Axiomatic Kernel Validation ─────────────────────────────────
87
+
88
+ async def async_pre_call_hook(
89
+ self,
90
+ user_api_key_dict: Any,
91
+ cache: Any,
92
+ data: dict,
93
+ call_type: str,
94
+ ) -> Optional[str]:
95
+ """
96
+ Runs BEFORE the LLM API call.
97
+ Extracts user message and validates against Constitutional Kernel.
98
+
99
+ Returns:
100
+ None → call proceeds normally
101
+ str → LiteLLM returns this string as the response (call blocked)
102
+ """
103
+ messages = data.get("messages", [])
104
+ user_text = _extract_user_text(messages)
105
+
106
+ if not user_text:
107
+ return None
108
+
109
+ try:
110
+ self.wrapper.validate_user_rule(user_text)
111
+ return None # All good, proceed
112
+ except Exception as e:
113
+ logger.warning(
114
+ f"CT Toolkit | LiteLLM pre-call kernel violation: {e} | "
115
+ f"model={data.get('model', 'unknown')}"
116
+ )
117
+ # Return blocked message — LiteLLM formats this into a proper response
118
+ return (
119
+ f"[CT Toolkit BLOCKED] This request violates an identity constraint "
120
+ f"and cannot be processed. Reason: {e}"
121
+ )
122
+
123
+ # ── Post-call: Divergence Analysis + Provenance ───────────────────────────
124
+
125
+ async def async_log_success_event(
126
+ self,
127
+ kwargs: dict,
128
+ response_obj: Any,
129
+ start_time: float,
130
+ end_time: float,
131
+ ) -> None:
132
+ """
133
+ Runs AFTER a successful LLM call.
134
+ Runs Divergence Engine and writes to Provenance Log.
135
+ """
136
+ try:
137
+ messages = kwargs.get("messages", [])
138
+ user_text = _extract_user_text(messages)
139
+ response_text = _extract_response_text(response_obj)
140
+ model = kwargs.get("model", "unknown")
141
+
142
+ if not response_text:
143
+ return
144
+
145
+ # Interaction count for Elasticity Scheduler
146
+ interaction_count = 0
147
+ if self.wrapper._config.log_requests:
148
+ interaction_count = self.wrapper._provenance_log.get_interaction_count(
149
+ template=self.wrapper._config.template,
150
+ kernel_name=self.wrapper.kernel.name,
151
+ model=model,
152
+ )
153
+
154
+ # Run Divergence Engine (L1 → L2 → L3)
155
+ div_result = self.wrapper._run_divergence_engine(
156
+ message=user_text,
157
+ response=response_text,
158
+ interaction_count=interaction_count,
159
+ )
160
+
161
+ if getattr(div_result, 'cascade_blocked', False):
162
+ logger.warning(
163
+ f"CT Toolkit | cascade_blocked=True via LiteLLM callback. "
164
+ f"model={model} | score={div_result.l1_score:.4f} | "
165
+ f"Consider halting downstream agent calls."
166
+ )
167
+
168
+ # Provenance Log
169
+ if self.wrapper._config.log_requests:
170
+ elapsed_ms = (end_time - start_time) * 1000 if isinstance(start_time, float) else 0.0
171
+ self.wrapper._provenance_log.record(
172
+ request_text=user_text or "[LiteLLM Interaction]",
173
+ response_text=response_text,
174
+ divergence_score=div_result.l1_score,
175
+ metadata={
176
+ "middleware": "litellm",
177
+ "provider": kwargs.get("custom_llm_provider", "unknown"),
178
+ "model": model,
179
+ "elapsed_ms": round(elapsed_ms, 2),
180
+ "tier": div_result.tier.value,
181
+ "cascade_blocked": getattr(div_result, 'cascade_blocked', False),
182
+ "template": self.wrapper._config.template,
183
+ "kernel": self.wrapper.kernel.name,
184
+ },
185
+ )
186
+
187
+ logger.debug(
188
+ f"CT Toolkit | LiteLLM success | model={model} | "
189
+ f"divergence={div_result.l1_score:.4f} | tier={div_result.tier.value}"
190
+ )
191
+
192
+ except Exception as e:
193
+ # Never crash the caller — log and continue
194
+ logger.error(f"CT Toolkit | LiteLLM callback error (success event): {e}")
195
+
196
+ async def async_log_failure_event(
197
+ self,
198
+ kwargs: dict,
199
+ response_obj: Any,
200
+ start_time: float,
201
+ end_time: float,
202
+ ) -> None:
203
+ """Logs LLM failures to provenance for audit trail completeness."""
204
+ try:
205
+ model = kwargs.get("model", "unknown")
206
+ exception = kwargs.get("exception", "unknown error")
207
+ logger.warning(f"CT Toolkit | LiteLLM failure | model={model} | error={exception}")
208
+
209
+ if self.wrapper._config.log_requests:
210
+ messages = kwargs.get("messages", [])
211
+ self.wrapper._provenance_log.record(
212
+ request_text=_extract_user_text(messages) or "[LiteLLM Failed Interaction]",
213
+ response_text=f"[ERROR: {exception}]",
214
+ divergence_score=None,
215
+ metadata={
216
+ "middleware": "litellm",
217
+ "model": model,
218
+ "status": "failed",
219
+ "template": self.wrapper._config.template,
220
+ "kernel": self.wrapper.kernel.name,
221
+ },
222
+ )
223
+ except Exception as e:
224
+ logger.error(f"CT Toolkit | LiteLLM callback error (failure event): {e}")
225
+
226
+ # ── Sync fallback ─────────────────────────────────────────────────────────
227
+
228
+ def log_success_event(
229
+ self,
230
+ kwargs: dict,
231
+ response_obj: Any,
232
+ start_time: float,
233
+ end_time: float,
234
+ ) -> None:
235
+ """Sync fallback — runs divergence + provenance for non-async callers."""
236
+ try:
237
+ messages = kwargs.get("messages", [])
238
+ user_text = _extract_user_text(messages)
239
+ response_text = _extract_response_text(response_obj)
240
+ model = kwargs.get("model", "unknown")
241
+
242
+ if not response_text:
243
+ return
244
+
245
+ div_result = self.wrapper._run_divergence_engine(
246
+ message=user_text,
247
+ response=response_text,
248
+ )
249
+
250
+ if self.wrapper._config.log_requests:
251
+ self.wrapper._provenance_log.record(
252
+ request_text=user_text or "[LiteLLM Interaction]",
253
+ response_text=response_text,
254
+ divergence_score=div_result.l1_score,
255
+ metadata={
256
+ "middleware": "litellm",
257
+ "model": model,
258
+ "tier": div_result.tier.value,
259
+ "template": self.wrapper._config.template,
260
+ "kernel": self.wrapper.kernel.name,
261
+ },
262
+ )
263
+ except Exception as e:
264
+ logger.error(f"CT Toolkit | LiteLLM callback error (sync success): {e}")
265
+
266
+
267
+ # ── Proxy Mode: singleton instance for proxy_config.yaml ─────────────────────
268
+
269
+ def create_proxy_handler(
270
+ kernel_name: str = "default",
271
+ template: str = "general",
272
+ vault_path: str = "./ct_provenance.db",
273
+ enterprise_mode: bool = False,
274
+ ) -> TheseusLiteLLMCallback:
275
+ """
276
+ Factory for LiteLLM Proxy mode.
277
+
278
+ Usage in proxy_config.yaml::
279
+
280
+ litellm_settings:
281
+ callbacks: ct_toolkit.middleware.litellm.proxy_handler_instance
282
+
283
+ Or with custom config::
284
+
285
+ # custom_callbacks.py
286
+ from ct_toolkit.middleware.litellm import create_proxy_handler
287
+ proxy_handler_instance = create_proxy_handler(kernel_name="defense", template="defense")
288
+ """
289
+ wrapper = TheseusWrapper(
290
+ provider="openai", # Proxy doesn't need a specific provider
291
+ config=WrapperConfig(
292
+ kernel_name=kernel_name,
293
+ template=template,
294
+ vault_path=vault_path,
295
+ enterprise_mode=enterprise_mode,
296
+ ),
297
+ )
298
+ return TheseusLiteLLMCallback(wrapper)
299
+
300
+
301
+ # Default proxy singleton — works out-of-the-box for proxy_config.yaml
302
+ proxy_handler_instance = create_proxy_handler()
303
+
304
+
305
+ # ── Helpers ───────────────────────────────────────────────────────────────────
306
+
307
+ def _extract_user_text(messages: list[dict]) -> str:
308
+ """Extract non-system message content from LiteLLM messages list."""
309
+ parts = []
310
+ for msg in messages:
311
+ if isinstance(msg, dict) and msg.get("role") != "system":
312
+ content = msg.get("content", "")
313
+ if isinstance(content, str):
314
+ parts.append(content)
315
+ elif isinstance(content, list):
316
+ # Handle multimodal content blocks
317
+ for block in content:
318
+ if isinstance(block, dict) and block.get("type") == "text":
319
+ parts.append(block.get("text", ""))
320
+ return " ".join(parts)
321
+
322
+
323
+ def _extract_response_text(response_obj: Any) -> str:
324
+ """Extract text content from LiteLLM ModelResponse."""
325
+ if response_obj is None:
326
+ return ""
327
+ try:
328
+ # Standard LiteLLM ModelResponse
329
+ if hasattr(response_obj, "choices"):
330
+ if not response_obj.choices:
331
+ return "" # Empty choices list
332
+ choice = response_obj.choices[0]
333
+ if hasattr(choice, "message") and choice.message:
334
+ return choice.message.content or ""
335
+ if hasattr(choice, "text"):
336
+ return choice.text or ""
337
+ # Dict format
338
+ if isinstance(response_obj, dict):
339
+ choices = response_obj.get("choices", [])
340
+ if choices:
341
+ return choices[0].get("message", {}).get("content", "")
342
+ except Exception:
343
+ pass
344
+ return str(response_obj)
@@ -0,0 +1,174 @@
1
+ """
2
+ ct_toolkit.server
3
+ -----------------
4
+ FastAPI server for LiteLLM Generic Guardrail API.
5
+ Exposes /guardrail/check to LiteLLM for pre-call and post-call validation.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, List, Optional
10
+ from fastapi import FastAPI, Body, HTTPException
11
+ from pydantic import BaseModel, Field
12
+ import uvicorn
13
+
14
+ from ct_toolkit.core.wrapper import TheseusWrapper
15
+ from ct_toolkit.utils.logger import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+ # This will be injected by the CLI when starting the server
20
+ _wrapper_instance: Optional[TheseusWrapper] = None
21
+
22
+ app = FastAPI(
23
+ title="CT-Toolkit Guardrail Server",
24
+ description="Identity Continuity Guardrails for Agentic Systems via LiteLLM Generic Guardrail API.",
25
+ version="1.0.0"
26
+ )
27
+
28
+ class GuardrailRequest(BaseModel):
29
+ """LiteLLM Generic Guardrail API Request Schema."""
30
+ texts: Optional[List[str]] = Field(None, description="Extracted text from the request.")
31
+ input_type: str = Field(..., description="Phase of the call: 'request' or 'response'.")
32
+ structured_messages: Optional[List[Dict[str, Any]]] = Field(None, description="Full messages in OpenAI format.")
33
+ images: Optional[List[str]] = None
34
+ tools: Optional[List[Any]] = None
35
+ tool_calls: Optional[List[Any]] = None
36
+ request_data: Optional[Dict[str, Any]] = None
37
+ litellm_call_id: Optional[str] = None
38
+ litellm_trace_id: Optional[str] = None
39
+
40
+ class GuardrailResponse(BaseModel):
41
+ """LiteLLM Generic Guardrail API Response Schema."""
42
+ action: str = Field(..., description="'BLOCKED' | 'NONE' | 'GUARDRAIL_INTERVENED'")
43
+ blocked_reason: Optional[str] = Field(None, description="Reason for denial (required if BLOCKED).")
44
+ texts: Optional[List[str]] = Field(None, description="Optional array of modified text strings.")
45
+
46
+ @app.post("/guardrail/check", response_model=GuardrailResponse)
47
+ async def check_guardrail(request: GuardrailRequest):
48
+ """
49
+ Main endpoint for LiteLLM Generic Guardrail API.
50
+ """
51
+ global _wrapper_instance
52
+ if not _wrapper_instance:
53
+ raise HTTPException(status_code=500, detail="Server not initialized with a Constitutional Kernel.")
54
+
55
+ logger.info(f"Received {request.input_type} check (call_id={request.litellm_call_id})")
56
+
57
+ try:
58
+ if request.input_type == "request":
59
+ return await _handle_pre_call(request)
60
+ elif request.input_type == "response":
61
+ return await _handle_post_call(request)
62
+ else:
63
+ logger.warning(f"Unknown input_type: {request.input_type}")
64
+ return GuardrailResponse(action="NONE")
65
+ except Exception as e:
66
+ logger.error(f"Error during guardrail check: {e}")
67
+ # Default to NONE to avoid breaking production flows on server error
68
+ return GuardrailResponse(action="NONE")
69
+
70
+ async def _handle_pre_call(request: GuardrailRequest) -> GuardrailResponse:
71
+ """Validate user message against Constitutional Kernel before LLM call."""
72
+ user_text = ""
73
+ if request.texts is not None:
74
+ user_text = " ".join(request.texts)
75
+ elif request.structured_messages is not None:
76
+ user_text = _extract_user_text(request.structured_messages)
77
+
78
+ if user_text:
79
+ try:
80
+ _wrapper_instance.validate_user_rule(user_text)
81
+ return GuardrailResponse(action="NONE")
82
+ except Exception as e:
83
+ logger.warning(f"Pre-call kernel violation: {e}")
84
+ return GuardrailResponse(
85
+ action="BLOCKED",
86
+ blocked_reason=f"Identity constraint violation: {e}"
87
+ )
88
+ return GuardrailResponse(action="NONE")
89
+
90
+ async def _handle_post_call(request: GuardrailRequest) -> GuardrailResponse:
91
+ """Analyze LLM response for divergence after call."""
92
+ user_text = ""
93
+ if request.structured_messages is not None:
94
+ user_text = _extract_user_text(request.structured_messages)
95
+
96
+ response_text = ""
97
+ if request.texts is not None:
98
+ response_text = " ".join(request.texts)
99
+
100
+ if not response_text:
101
+ return GuardrailResponse(action="NONE")
102
+
103
+ model = "unknown"
104
+ if request.request_data:
105
+ # LiteLLM might not send the model directly here, but we can try to extract from metadata
106
+ pass
107
+
108
+ interaction_count = 0
109
+ if _wrapper_instance._config.log_requests:
110
+ interaction_count = _wrapper_instance._provenance_log.get_interaction_count(
111
+ template=_wrapper_instance._config.template,
112
+ kernel_name=_wrapper_instance.kernel.name,
113
+ model=model,
114
+ )
115
+
116
+ # Run Divergence Engine
117
+ div_result = _wrapper_instance._run_divergence_engine(
118
+ message=user_text,
119
+ response=response_text,
120
+ interaction_count=interaction_count,
121
+ )
122
+
123
+ # Record to Provenance Log
124
+ if _wrapper_instance._config.log_requests:
125
+ _wrapper_instance._provenance_log.record(
126
+ request_text=user_text or "[Generic Guardrail Interaction]",
127
+ response_text=response_text,
128
+ divergence_score=div_result.l1_score,
129
+ metadata={
130
+ "middleware": "generic_guardrail_api",
131
+ "model": model,
132
+ "tier": div_result.tier.value,
133
+ "template": _wrapper_instance._config.template,
134
+ "kernel": _wrapper_instance.kernel.name,
135
+ "litellm_call_id": request.litellm_call_id,
136
+ },
137
+ )
138
+
139
+ # Handle cascade_blocked
140
+ if getattr(div_result, 'cascade_blocked', False):
141
+ return GuardrailResponse(
142
+ action="BLOCKED",
143
+ blocked_reason=f"Identity drift detected (score={div_result.l1_score:.4f})"
144
+ )
145
+
146
+ return GuardrailResponse(action="NONE")
147
+
148
+ def _extract_user_text(messages: List[Dict[str, Any]]) -> str:
149
+ """Extract non-system message content from messages list."""
150
+ parts = []
151
+ for msg in messages:
152
+ if msg.get("role") != "system":
153
+ content = msg.get("content", "")
154
+ if isinstance(content, str):
155
+ parts.append(content)
156
+ return " ".join(parts)
157
+
158
+ def _extract_response_text(response_obj: Any) -> str:
159
+ """Extract text content from LiteLLM response object."""
160
+ try:
161
+ if isinstance(response_obj, dict):
162
+ choices = response_obj.get("choices", [])
163
+ if choices:
164
+ return choices[0].get("message", {}).get("content", "")
165
+ except Exception:
166
+ pass
167
+ return ""
168
+
169
+ def start_server(wrapper: TheseusWrapper, host: str, port: int):
170
+ """Inject wrapper and start uvicorn server."""
171
+ global _wrapper_instance
172
+ _wrapper_instance = wrapper
173
+ logger.info(f"Starting Guardrail Server on {host}:{port}")
174
+ uvicorn.run(app, host=host, port=port)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -9,7 +9,7 @@ Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: instructor>=1.3.0
12
- Requires-Dist: any-llm-sdk[all]>=1.0.0
12
+ Requires-Dist: litellm>=1.40.0
13
13
  Requires-Dist: numpy>=1.26.0
14
14
  Requires-Dist: pyyaml>=6.0
15
15
  Requires-Dist: pydantic>=2.5.0
@@ -20,6 +20,8 @@ Requires-Dist: rich>=13.0.0
20
20
  Requires-Dist: jinja2>=3.1.0
21
21
  Requires-Dist: pyasn1>=0.6.3
22
22
  Requires-Dist: typer[all]>=0.12.0
23
+ Requires-Dist: fastapi>=0.110.0
24
+ Requires-Dist: uvicorn>=0.29.0
23
25
  Provides-Extra: ollama
24
26
  Requires-Dist: ollama>=0.2.0; extra == "ollama"
25
27
  Provides-Extra: langchain
@@ -116,7 +118,7 @@ ct-toolkit list-templates
116
118
 
117
119
  | Metric | Status |
118
120
  | :--------------- | :------------------------------------------------------ |
119
- | **Tests** | ✅ 269/272 passing (92% coverage) |
121
+ | **Tests** | ✅ 281/292 passing (95% coverage) |
120
122
  | **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
121
123
  | **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
122
124
 
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  ct_toolkit/__init__.py
5
5
  ct_toolkit/__main__.py
6
6
  ct_toolkit/cli.py
7
+ ct_toolkit/server.py
7
8
  ct_toolkit.egg-info/PKG-INFO
8
9
  ct_toolkit.egg-info/SOURCES.txt
9
10
  ct_toolkit.egg-info/dependency_links.txt
@@ -42,6 +43,7 @@ ct_toolkit/middleware/autogen.py
42
43
  ct_toolkit/middleware/crewai.py
43
44
  ct_toolkit/middleware/deepagents.py
44
45
  ct_toolkit/middleware/langchain.py
46
+ ct_toolkit/middleware/litellm.py
45
47
  ct_toolkit/provenance/__init__.py
46
48
  ct_toolkit/provenance/log.py
47
49
  ct_toolkit/provenance/vault/__init__.py
@@ -1,5 +1,5 @@
1
1
  instructor>=1.3.0
2
- any-llm-sdk[all]>=1.0.0
2
+ litellm>=1.40.0
3
3
  numpy>=1.26.0
4
4
  pyyaml>=6.0
5
5
  pydantic>=2.5.0
@@ -10,6 +10,8 @@ rich>=13.0.0
10
10
  jinja2>=3.1.0
11
11
  pyasn1>=0.6.3
12
12
  typer[all]>=0.12.0
13
+ fastapi>=0.110.0
14
+ uvicorn>=0.29.0
13
15
 
14
16
  [autogen]
15
17
  pyautogen>=0.4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ct-toolkit"
7
- version = "0.3.6"
7
+ version = "0.3.7"
8
8
  description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -14,7 +14,7 @@ keywords = ["llm", "ai-safety", "identity-continuity", "guardrails", "alignment"
14
14
 
15
15
  dependencies = [
16
16
  "instructor>=1.3.0",
17
- "any-llm-sdk[all]>=1.0.0",
17
+ "litellm>=1.40.0",
18
18
  "numpy>=1.26.0",
19
19
  "pyyaml>=6.0",
20
20
  "pydantic>=2.5.0",
@@ -25,6 +25,8 @@ dependencies = [
25
25
  "jinja2>=3.1.0",
26
26
  "pyasn1>=0.6.3",
27
27
  "typer[all]>=0.12.0",
28
+ "fastapi>=0.110.0",
29
+ "uvicorn>=0.29.0",
28
30
  ]
29
31
 
30
32
  [project.optional-dependencies]
File without changes
File without changes
File without changes