ct-toolkit 0.3.5__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.
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/PKG-INFO +12 -6
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/README.md +8 -4
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/__init__.py +1 -1
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/cli.py +32 -0
- ct_toolkit-0.3.5/ct_toolkit/middleware/deepagents.py → ct_toolkit-0.3.7/ct_toolkit/core/compression_guard.py +23 -60
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/wrapper.py +64 -37
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/l3_icm.py +19 -16
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/embedding.py +16 -14
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/middleware/crewai.py +4 -0
- ct_toolkit-0.3.7/ct_toolkit/middleware/deepagents.py +67 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/middleware/langchain.py +5 -0
- ct_toolkit-0.3.7/ct_toolkit/middleware/litellm.py +344 -0
- ct_toolkit-0.3.7/ct_toolkit/server.py +174 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/PKG-INFO +12 -6
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/SOURCES.txt +4 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/requires.txt +3 -1
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/pyproject.toml +4 -2
- ct_toolkit-0.3.7/tests/test_cli_extended.py +53 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/LICENSE +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/__main__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/entry_points.txt +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/setup.cfg +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/tests/test_cli.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.5 → ct_toolkit-0.3.7}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.5 → 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.
|
|
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:
|
|
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
|
|
@@ -65,6 +67,7 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
65
67
|
|
|
66
68
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
67
69
|
|
|
70
|
+
- **Passive Context Compression Detection**: Automatically detects silent provider-side history compression (e.g., OpenAI/Anthropic) via history shrinkage heuristics.
|
|
68
71
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
69
72
|
- **Standalone Auditor CLI**: Rapidly audit any LLM endpoint for identity drift without writing code.
|
|
70
73
|
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
@@ -81,10 +84,13 @@ pip install ct-toolkit
|
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
```python
|
|
84
|
-
from ct_toolkit import TheseusWrapper
|
|
87
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig
|
|
88
|
+
|
|
89
|
+
# Protect against silent provider context compression
|
|
90
|
+
config = WrapperConfig(compression_passive_detection=True)
|
|
85
91
|
|
|
86
92
|
# One-line injection for any LLM provider
|
|
87
|
-
client = TheseusWrapper(provider="openai")
|
|
93
|
+
client = TheseusWrapper(provider="openai", config=config)
|
|
88
94
|
|
|
89
95
|
# Guardrails and drift analysis applied automatically
|
|
90
96
|
response = client.chat("What are your core security axioms?")
|
|
@@ -112,9 +118,9 @@ ct-toolkit list-templates
|
|
|
112
118
|
|
|
113
119
|
| Metric | Status |
|
|
114
120
|
| :--------------- | :------------------------------------------------------ |
|
|
115
|
-
| **Tests** | ✅
|
|
121
|
+
| **Tests** | ✅ 281/292 passing (95% coverage) |
|
|
116
122
|
| **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
|
|
117
|
-
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (
|
|
123
|
+
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
118
124
|
|
|
119
125
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
120
126
|
|
|
@@ -24,6 +24,7 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
24
24
|
|
|
25
25
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
26
26
|
|
|
27
|
+
- **Passive Context Compression Detection**: Automatically detects silent provider-side history compression (e.g., OpenAI/Anthropic) via history shrinkage heuristics.
|
|
27
28
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
28
29
|
- **Standalone Auditor CLI**: Rapidly audit any LLM endpoint for identity drift without writing code.
|
|
29
30
|
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
@@ -40,10 +41,13 @@ pip install ct-toolkit
|
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
```python
|
|
43
|
-
from ct_toolkit import TheseusWrapper
|
|
44
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig
|
|
45
|
+
|
|
46
|
+
# Protect against silent provider context compression
|
|
47
|
+
config = WrapperConfig(compression_passive_detection=True)
|
|
44
48
|
|
|
45
49
|
# One-line injection for any LLM provider
|
|
46
|
-
client = TheseusWrapper(provider="openai")
|
|
50
|
+
client = TheseusWrapper(provider="openai", config=config)
|
|
47
51
|
|
|
48
52
|
# Guardrails and drift analysis applied automatically
|
|
49
53
|
response = client.chat("What are your core security axioms?")
|
|
@@ -71,9 +75,9 @@ ct-toolkit list-templates
|
|
|
71
75
|
|
|
72
76
|
| Metric | Status |
|
|
73
77
|
| :--------------- | :------------------------------------------------------ |
|
|
74
|
-
| **Tests** | ✅
|
|
78
|
+
| **Tests** | ✅ 281/292 passing (95% coverage) |
|
|
75
79
|
| **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
|
|
76
|
-
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (
|
|
80
|
+
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
77
81
|
|
|
78
82
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
79
83
|
|
|
@@ -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."""
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
ct_toolkit.
|
|
2
|
+
ct_toolkit.core.compression_guard
|
|
3
3
|
---------------------------------
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
identity-continuity guardrails, specifically ensuring that when a
|
|
8
|
-
Deep Agent spawns sub-agents, they inherit the mother agent's
|
|
9
|
-
Constitutional Kernel.
|
|
4
|
+
Provider-agnostic compression drift detector.
|
|
5
|
+
Works with OpenAI compaction, Anthropic compaction, LangChain memory,
|
|
6
|
+
or any framework that silently shortens context.
|
|
10
7
|
"""
|
|
11
8
|
from __future__ import annotations
|
|
12
9
|
|
|
13
|
-
from typing import Any, Dict, List,
|
|
14
|
-
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
15
|
-
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
10
|
+
from typing import Any, Dict, List, TYPE_CHECKING
|
|
16
11
|
from ct_toolkit.utils.logger import get_logger
|
|
17
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ct_toolkit.core.wrapper import TheseusWrapper
|
|
15
|
+
|
|
18
16
|
logger = get_logger(__name__)
|
|
19
17
|
|
|
20
18
|
class ContextCompressionGuard:
|
|
@@ -27,7 +25,7 @@ class ContextCompressionGuard:
|
|
|
27
25
|
|
|
28
26
|
def analyze_summary_drift(
|
|
29
27
|
self,
|
|
30
|
-
original_messages: List[Dict[str, str]],
|
|
28
|
+
original_messages: List[Dict[str, str]] | str,
|
|
31
29
|
summary_text: str
|
|
32
30
|
) -> Dict[str, Any]:
|
|
33
31
|
"""
|
|
@@ -37,9 +35,12 @@ class ContextCompressionGuard:
|
|
|
37
35
|
Returns:
|
|
38
36
|
Dict containing similarity score and drift status.
|
|
39
37
|
"""
|
|
40
|
-
# 1. Flatten original messages into a single identity string
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
# 1. Flatten original messages into a single identity string if it's a list
|
|
39
|
+
if isinstance(original_messages, list):
|
|
40
|
+
original_text = " ".join([m.get("content", "") for m in original_messages])
|
|
41
|
+
else:
|
|
42
|
+
original_text = original_messages
|
|
43
|
+
|
|
43
44
|
# 2. Compute embeddings via identity layer
|
|
44
45
|
original_emb = self.wrapper._identity_layer._compute_vector(original_text)
|
|
45
46
|
summary_emb = self.wrapper._identity_layer._compute_vector(summary_text)
|
|
@@ -49,7 +50,6 @@ class ContextCompressionGuard:
|
|
|
49
50
|
similarity = IdentityEmbeddingLayer.calculate_similarity(original_emb, summary_emb)
|
|
50
51
|
|
|
51
52
|
# 4. Identity Safety Floor (Internal protection against manipulated thresholds)
|
|
52
|
-
# Even if the user sets threshold to 0.4, 0.6 is still risky for LLM identity.
|
|
53
53
|
drift_detected = similarity < self.threshold
|
|
54
54
|
is_critical = similarity < 0.70 # A hard floor for identity integrity
|
|
55
55
|
|
|
@@ -88,48 +88,11 @@ class ContextCompressionGuard:
|
|
|
88
88
|
|
|
89
89
|
return result
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
inject Theseus guardrails, enable hierarchical kernel propagation,
|
|
100
|
-
and monitor context compression drift.
|
|
101
|
-
"""
|
|
102
|
-
_wrapper = wrapper or TheseusWrapper(config=wrapper_config)
|
|
103
|
-
guard = ContextCompressionGuard(_wrapper, threshold=compression_threshold)
|
|
104
|
-
|
|
105
|
-
def wrapped_create_deep_agent(*args: Any, **kwargs: Any) -> Any:
|
|
106
|
-
# 1. Inject TheseusChatModel
|
|
107
|
-
model = kwargs.get("model")
|
|
108
|
-
if isinstance(model, str) or model is None:
|
|
109
|
-
kwargs["model"] = TheseusChatModel(
|
|
110
|
-
wrapper=_wrapper,
|
|
111
|
-
model=model if isinstance(model, str) else "gpt-4o-mini"
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# 2. Inject ContextCompressionGuard into the middleware stack if possible
|
|
115
|
-
# Since deepagents middleware is internal, we use the helper to signal it
|
|
116
|
-
# In a real integration, we'd wrap the SummarizationMiddleware.
|
|
117
|
-
|
|
118
|
-
return create_deep_agent_fn(*args, **kwargs)
|
|
119
|
-
|
|
120
|
-
return wrapped_create_deep_agent
|
|
121
|
-
|
|
122
|
-
class DeepAgentTheseusHelper:
|
|
123
|
-
"""
|
|
124
|
-
Helper for programmatic integration with Deep Agents.
|
|
125
|
-
"""
|
|
126
|
-
@staticmethod
|
|
127
|
-
def prepare_config(wrapper: TheseusWrapper) -> Dict[str, Any]:
|
|
128
|
-
"""Returns configuration for Deep Agents that includes identity constraints."""
|
|
129
|
-
return {
|
|
130
|
-
"callbacks": [], # We could add TheseusLangChainCallback here
|
|
131
|
-
"metadata": {
|
|
132
|
-
"ct_kernel": wrapper.kernel.name,
|
|
133
|
-
"ct_identity_protection": True
|
|
134
|
-
}
|
|
135
|
-
}
|
|
91
|
+
def on_passive_detection(self, original: List[Dict[str, str]], compressed: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Triggered when TheseusWrapper detects a significant history shrinkage.
|
|
94
|
+
"""
|
|
95
|
+
logger.info("CT Toolkit | Passive compression detection triggered.")
|
|
96
|
+
original_text = " ".join(m.get("content", "") for m in original)
|
|
97
|
+
compressed_text = " ".join(m.get("content", "") for m in compressed)
|
|
98
|
+
return self.analyze_summary_drift(original_text, compressed_text)
|
|
@@ -25,15 +25,14 @@ 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
|
|
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
|
|
34
32
|
from ct_toolkit.core.exceptions import CTToolkitError, MissingClientError
|
|
35
33
|
from ct_toolkit.core.integrity import IntegrityMonitor
|
|
36
34
|
from ct_toolkit.divergence.scheduler import RiskProfile
|
|
35
|
+
from ct_toolkit.core.compression_guard import ContextCompressionGuard
|
|
37
36
|
from ct_toolkit.utils.logger import get_logger
|
|
38
37
|
|
|
39
38
|
logger = get_logger(__name__)
|
|
@@ -71,6 +70,10 @@ class WrapperConfig:
|
|
|
71
70
|
|
|
72
71
|
strict_embedding: bool = False # Raise error if embedding API fails
|
|
73
72
|
|
|
73
|
+
# -- Context Compression Guard --
|
|
74
|
+
compression_threshold: float = 0.85
|
|
75
|
+
compression_passive_detection: bool = True
|
|
76
|
+
|
|
74
77
|
|
|
75
78
|
@dataclass
|
|
76
79
|
class CTResponse:
|
|
@@ -92,7 +95,7 @@ class TheseusWrapper:
|
|
|
92
95
|
Identity-continuity proxy wrapping the LLM API client.
|
|
93
96
|
|
|
94
97
|
Supported providers: openai (default), anthropic, ollama, google, etc.
|
|
95
|
-
(Any provider supported by
|
|
98
|
+
(Any provider supported by LiteLLM)
|
|
96
99
|
|
|
97
100
|
Usage:
|
|
98
101
|
from ct_toolkit import TheseusWrapper
|
|
@@ -128,7 +131,7 @@ class TheseusWrapper:
|
|
|
128
131
|
Optional if 'provider' is specified.
|
|
129
132
|
config: Optional WrapperConfig instance.
|
|
130
133
|
provider: Provider name (e.g. "openai", "anthropic", "ollama", "google", "cohere").
|
|
131
|
-
If provided,
|
|
134
|
+
If provided, LiteLLM will be used to initialize the client internally.
|
|
132
135
|
project_root: Path to the user's project root directory. Used to find a custom 'config/' folder.
|
|
133
136
|
Defaults to the current working directory.
|
|
134
137
|
"""
|
|
@@ -165,6 +168,13 @@ class TheseusWrapper:
|
|
|
165
168
|
self._divergence_engine = self._init_divergence_engine()
|
|
166
169
|
self._last_model: str = "unknown"
|
|
167
170
|
|
|
171
|
+
# ── Context Compression Guard ──
|
|
172
|
+
self._compression_guard = ContextCompressionGuard(
|
|
173
|
+
self,
|
|
174
|
+
threshold=self._config.compression_threshold
|
|
175
|
+
)
|
|
176
|
+
self._shadow_history: Optional[List[Dict[str, str]]] = None
|
|
177
|
+
|
|
168
178
|
# ── Integrity Monitoring ──
|
|
169
179
|
self._integrity_monitor = IntegrityMonitor()
|
|
170
180
|
self._register_monitored_files()
|
|
@@ -231,7 +241,7 @@ class TheseusWrapper:
|
|
|
231
241
|
return client.lower()
|
|
232
242
|
|
|
233
243
|
try:
|
|
234
|
-
# Check for
|
|
244
|
+
# Check for LiteLLM specific attributes if any
|
|
235
245
|
if hasattr(client, "provider") and isinstance(client.provider, str):
|
|
236
246
|
return client.provider
|
|
237
247
|
|
|
@@ -489,9 +499,21 @@ class TheseusWrapper:
|
|
|
489
499
|
model: str | None,
|
|
490
500
|
**kwargs: Any,
|
|
491
501
|
) -> Any:
|
|
492
|
-
"""Uses
|
|
493
|
-
#
|
|
494
|
-
|
|
502
|
+
"""Uses LiteLLM for unified provider calling."""
|
|
503
|
+
# --- Passive Context Compression Detection ---
|
|
504
|
+
if self._config.compression_passive_detection and self._shadow_history:
|
|
505
|
+
# Simple heuristic: message count dropped significantly
|
|
506
|
+
# We use 0.7 as a threshold (30% drop)
|
|
507
|
+
if len(messages) < len(self._shadow_history) * 0.7:
|
|
508
|
+
self._compression_guard.on_passive_detection(
|
|
509
|
+
original=self._shadow_history,
|
|
510
|
+
compressed=messages
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# Update shadow history for next call
|
|
514
|
+
self._shadow_history = list(messages)
|
|
515
|
+
|
|
516
|
+
# LiteLLM handles different message formats internally
|
|
495
517
|
|
|
496
518
|
# Set default models per provider if not specified
|
|
497
519
|
if not model:
|
|
@@ -500,32 +522,29 @@ class TheseusWrapper:
|
|
|
500
522
|
elif self._provider == "ollama": model = "llama3"
|
|
501
523
|
else: model = "gpt-4o-mini"
|
|
502
524
|
|
|
503
|
-
#
|
|
525
|
+
# LiteLLM expects "provider/model" format for non-default providers
|
|
526
|
+
# Usually OpenAI models don't need a prefix.
|
|
504
527
|
full_model = model
|
|
505
|
-
if ":"
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
"model": full_model,
|
|
511
|
-
"messages": messages,
|
|
512
|
-
**kwargs
|
|
513
|
-
}
|
|
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}"
|
|
514
533
|
|
|
515
534
|
# Extract connection info from client if possible
|
|
516
535
|
if hasattr(self._client, "base_url") and self._client.base_url:
|
|
517
|
-
|
|
536
|
+
kwargs["api_base"] = str(self._client.base_url)
|
|
518
537
|
if hasattr(self._client, "api_key") and self._client.api_key:
|
|
519
|
-
|
|
538
|
+
kwargs["api_key"] = self._client.api_key
|
|
520
539
|
|
|
521
540
|
# Transfer common client configurations if available
|
|
522
|
-
if hasattr(self._client, "timeout") and "timeout" not in
|
|
523
|
-
|
|
541
|
+
if hasattr(self._client, "timeout") and "timeout" not in kwargs:
|
|
542
|
+
kwargs["timeout"] = self._client.timeout
|
|
524
543
|
|
|
525
544
|
try:
|
|
526
|
-
return
|
|
545
|
+
return litellm.completion(model=full_model, messages=messages, **kwargs)
|
|
527
546
|
except Exception as e:
|
|
528
|
-
logger.error(f"
|
|
547
|
+
logger.error(f"litellm call failed: {e}")
|
|
529
548
|
raise
|
|
530
549
|
|
|
531
550
|
# ── Helper Methods ──────────────────────────────────────────────────────
|
|
@@ -579,25 +598,33 @@ class TheseusWrapper:
|
|
|
579
598
|
return messages
|
|
580
599
|
|
|
581
600
|
def _extract_content(self, raw: Any) -> str:
|
|
582
|
-
# Handle dict responses
|
|
601
|
+
# Handle dict responses
|
|
583
602
|
if isinstance(raw, dict):
|
|
584
603
|
if "choices" in raw and isinstance(raw["choices"], list) and raw["choices"]:
|
|
585
|
-
# OpenAI dict format
|
|
586
604
|
return raw["choices"][0].get("message", {}).get("content", "") or ""
|
|
587
605
|
if "message" in raw:
|
|
588
|
-
# Ollama
|
|
606
|
+
# Ollama dictate format
|
|
589
607
|
return raw["message"].get("content", "") or ""
|
|
590
608
|
return str(raw)
|
|
591
609
|
|
|
592
|
-
#
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
+
|
|
601
628
|
return str(raw)
|
|
602
629
|
|
|
603
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
|
|
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 ":"
|
|
295
|
-
full_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
|
-
|
|
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
|
-
|
|
312
|
+
kwargs["api_base"] = str(self._client.base_url)
|
|
310
313
|
if hasattr(self._client, "api_key") and self._client.api_key:
|
|
311
|
-
|
|
314
|
+
kwargs["api_key"] = self._client.api_key
|
|
312
315
|
|
|
313
316
|
try:
|
|
314
|
-
resp =
|
|
315
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
@@ -92,6 +92,8 @@ class TheseusCrewMiddleware:
|
|
|
92
92
|
parent_kernel=manager_wrapper.kernel,
|
|
93
93
|
vault_path=manager_wrapper._config.vault_path,
|
|
94
94
|
template=manager_wrapper._config.template,
|
|
95
|
+
compression_threshold=manager_wrapper._config.compression_threshold,
|
|
96
|
+
compression_passive_detection=manager_wrapper._config.compression_passive_detection,
|
|
95
97
|
)
|
|
96
98
|
agent.llm = TheseusChatModel(
|
|
97
99
|
provider=manager_wrapper._provider,
|
|
@@ -134,6 +136,8 @@ class TheseusCrewMiddleware:
|
|
|
134
136
|
parent_kernel=manager_wrapper.kernel,
|
|
135
137
|
vault_path=manager_wrapper._config.vault_path,
|
|
136
138
|
template=manager_wrapper._config.template,
|
|
139
|
+
compression_threshold=manager_wrapper._config.compression_threshold,
|
|
140
|
+
compression_passive_detection=manager_wrapper._config.compression_passive_detection,
|
|
137
141
|
)
|
|
138
142
|
agent.llm = TheseusChatModel(
|
|
139
143
|
provider=manager_wrapper._provider,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ct_toolkit.middleware.deepagents
|
|
3
|
+
---------------------------------
|
|
4
|
+
Specialized integration for LangChain Deep Agents (deepagents).
|
|
5
|
+
|
|
6
|
+
This module provides helpers to wrap Deep Agents with CT Toolkit's
|
|
7
|
+
identity-continuity guardrails, specifically ensuring that when a
|
|
8
|
+
Deep Agent spawns sub-agents, they inherit the mother agent's
|
|
9
|
+
Constitutional Kernel.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List, Optional, Union, Callable
|
|
14
|
+
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
15
|
+
from ct_toolkit.core.compression_guard import ContextCompressionGuard
|
|
16
|
+
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
17
|
+
from ct_toolkit.utils.logger import get_logger
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def wrap_deep_agent_factory(
|
|
24
|
+
create_deep_agent_fn: Callable,
|
|
25
|
+
wrapper: Optional[TheseusWrapper] = None,
|
|
26
|
+
wrapper_config: Optional[WrapperConfig] = None,
|
|
27
|
+
compression_threshold: float = 0.85,
|
|
28
|
+
) -> Callable:
|
|
29
|
+
"""
|
|
30
|
+
Wraps the LangChain `create_deep_agent` function to automatically
|
|
31
|
+
inject Theseus guardrails, enable hierarchical kernel propagation,
|
|
32
|
+
and monitor context compression drift.
|
|
33
|
+
"""
|
|
34
|
+
_wrapper = wrapper or TheseusWrapper(config=wrapper_config)
|
|
35
|
+
guard = ContextCompressionGuard(_wrapper, threshold=compression_threshold)
|
|
36
|
+
|
|
37
|
+
def wrapped_create_deep_agent(*args: Any, **kwargs: Any) -> Any:
|
|
38
|
+
# 1. Inject TheseusChatModel
|
|
39
|
+
model = kwargs.get("model")
|
|
40
|
+
if isinstance(model, str) or model is None:
|
|
41
|
+
kwargs["model"] = TheseusChatModel(
|
|
42
|
+
wrapper=_wrapper,
|
|
43
|
+
model=model if isinstance(model, str) else "gpt-4o-mini"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 2. Inject ContextCompressionGuard into the middleware stack if possible
|
|
47
|
+
# Since deepagents middleware is internal, we use the helper to signal it
|
|
48
|
+
# In a real integration, we'd wrap the SummarizationMiddleware.
|
|
49
|
+
|
|
50
|
+
return create_deep_agent_fn(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
return wrapped_create_deep_agent
|
|
53
|
+
|
|
54
|
+
class DeepAgentTheseusHelper:
|
|
55
|
+
"""
|
|
56
|
+
Helper for programmatic integration with Deep Agents.
|
|
57
|
+
"""
|
|
58
|
+
@staticmethod
|
|
59
|
+
def prepare_config(wrapper: TheseusWrapper) -> Dict[str, Any]:
|
|
60
|
+
"""Returns configuration for Deep Agents that includes identity constraints."""
|
|
61
|
+
return {
|
|
62
|
+
"callbacks": [], # We could add TheseusLangChainCallback here
|
|
63
|
+
"metadata": {
|
|
64
|
+
"ct_kernel": wrapper.kernel.name,
|
|
65
|
+
"ct_identity_protection": True
|
|
66
|
+
}
|
|
67
|
+
}
|