ct-toolkit 0.2.1__tar.gz → 0.2.3__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.2.1 → ct_toolkit-0.2.3}/PKG-INFO +15 -15
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/README.md +12 -14
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/core/wrapper.py +7 -0
- ct_toolkit-0.2.3/ct_toolkit/divergence/loss.py +59 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/identity/embedding.py +6 -1
- ct_toolkit-0.2.3/ct_toolkit/middleware/deepagents.py +135 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/middleware/langchain.py +2 -1
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit.egg-info/PKG-INFO +15 -15
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit.egg-info/SOURCES.txt +6 -1
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit.egg-info/requires.txt +3 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/pyproject.toml +2 -1
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/tests/test_analysis.py +30 -0
- ct_toolkit-0.2.3/tests/test_deep_agents_integration.py +52 -0
- ct_toolkit-0.2.3/tests/test_loss.py +66 -0
- ct_toolkit-0.2.3/tests/test_summarization_drift.py +105 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/LICENSE +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.2.1 → ct_toolkit-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,6 +28,8 @@ Provides-Extra: autogen
|
|
|
28
28
|
Requires-Dist: pyautogen>=0.4.0; extra == "autogen"
|
|
29
29
|
Provides-Extra: docs
|
|
30
30
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
31
|
+
Provides-Extra: ml
|
|
32
|
+
Requires-Dist: torch>=2.0.0; extra == "ml"
|
|
31
33
|
Provides-Extra: dev
|
|
32
34
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
35
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -49,9 +51,11 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
49
51
|
---
|
|
50
52
|
|
|
51
53
|
## 📖 Official Documentation
|
|
52
|
-
For full API reference, architecture details, and integration guides, visit our documentation site:
|
|
54
|
+
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
53
55
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
54
56
|
|
|
57
|
+
- [**Live Examples & Case Studies**](https://hakandamar.github.io/ct-toolkit/examples/): Real-world scenarios like Financial Auditor Integrity.
|
|
58
|
+
|
|
55
59
|
---
|
|
56
60
|
|
|
57
61
|
## Why CT Toolkit?
|
|
@@ -90,23 +94,19 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
90
94
|
|
|
91
95
|
| Metric | Status |
|
|
92
96
|
| :--- | :--- |
|
|
93
|
-
| **Tests** | ✅
|
|
94
|
-
| **Last Phase** | ✅ Phase
|
|
95
|
-
| **Current Goal** | 🔶 Phase
|
|
97
|
+
| **Tests** | ✅ 218/218 passing (93% coverage) |
|
|
98
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
99
|
+
| **Current Goal** | 🔶 Phase 5: Vault and Security Infrastructure |
|
|
96
100
|
|
|
97
101
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
98
102
|
|
|
99
|
-
### Framework Support
|
|
100
|
-
Seamlessly integrate with your favorite frameworks:
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
# LangChain
|
|
104
|
-
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
105
|
-
llm = TheseusChatModel(provider="openai")
|
|
103
|
+
### Framework & Model Support
|
|
104
|
+
Seamlessly integrate with your favorite frameworks and local models:
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
107
|
+
- **LangChain & Deep Agents**: `wrap_deep_agent_factory`.
|
|
108
|
+
- **CrewAI**: `TheseusCrewMiddleware.apply_to_crew`.
|
|
109
|
+
- **AutoGen**: `register_reply` hooks.
|
|
110
110
|
|
|
111
111
|
---
|
|
112
112
|
|
|
@@ -12,9 +12,11 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## 📖 Official Documentation
|
|
15
|
-
For full API reference, architecture details, and integration guides, visit our documentation site:
|
|
15
|
+
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
16
16
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
17
17
|
|
|
18
|
+
- [**Live Examples & Case Studies**](https://hakandamar.github.io/ct-toolkit/examples/): Real-world scenarios like Financial Auditor Integrity.
|
|
19
|
+
|
|
18
20
|
---
|
|
19
21
|
|
|
20
22
|
## Why CT Toolkit?
|
|
@@ -53,23 +55,19 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
53
55
|
|
|
54
56
|
| Metric | Status |
|
|
55
57
|
| :--- | :--- |
|
|
56
|
-
| **Tests** | ✅
|
|
57
|
-
| **Last Phase** | ✅ Phase
|
|
58
|
-
| **Current Goal** | 🔶 Phase
|
|
58
|
+
| **Tests** | ✅ 218/218 passing (93% coverage) |
|
|
59
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
60
|
+
| **Current Goal** | 🔶 Phase 5: Vault and Security Infrastructure |
|
|
59
61
|
|
|
60
62
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
61
63
|
|
|
62
|
-
### Framework Support
|
|
63
|
-
Seamlessly integrate with your favorite frameworks:
|
|
64
|
-
|
|
65
|
-
```python
|
|
66
|
-
# LangChain
|
|
67
|
-
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
68
|
-
llm = TheseusChatModel(provider="openai")
|
|
64
|
+
### Framework & Model Support
|
|
65
|
+
Seamlessly integrate with your favorite frameworks and local models:
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
68
|
+
- **LangChain & Deep Agents**: `wrap_deep_agent_factory`.
|
|
69
|
+
- **CrewAI**: `TheseusCrewMiddleware.apply_to_crew`.
|
|
70
|
+
- **AutoGen**: `register_reply` hooks.
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
@@ -49,6 +49,7 @@ class WrapperConfig:
|
|
|
49
49
|
vault_path: str = "./ct_provenance.db"
|
|
50
50
|
auto_inject_kernel: bool = True
|
|
51
51
|
log_requests: bool = True
|
|
52
|
+
drift_alert_callback: Optional[Callable[[Dict[str, Any]], None]] = None
|
|
52
53
|
judge_client: Any = None # Separate provider client for L2/L3
|
|
53
54
|
embedding_client: Any = None # Client for L1 ECS embedding (falls back to main client if compatible)
|
|
54
55
|
embedding_model: str = "text-embedding-3-small"
|
|
@@ -250,6 +251,7 @@ class TheseusWrapper:
|
|
|
250
251
|
l3_threshold=self._config.divergence_l3_threshold,
|
|
251
252
|
enterprise_mode=self._config.enterprise_mode,
|
|
252
253
|
scheduler=scheduler,
|
|
254
|
+
provenance_log=self._provenance_log,
|
|
253
255
|
)
|
|
254
256
|
|
|
255
257
|
def _log_init(self) -> None:
|
|
@@ -337,6 +339,7 @@ class TheseusWrapper:
|
|
|
337
339
|
"tier": div_result.tier.value,
|
|
338
340
|
"template": self._config.template,
|
|
339
341
|
"kernel": self._kernel.name,
|
|
342
|
+
"drift_alert_enabled": self._config.drift_alert_callback is not None,
|
|
340
343
|
},
|
|
341
344
|
)
|
|
342
345
|
|
|
@@ -535,6 +538,10 @@ class TheseusWrapper:
|
|
|
535
538
|
def compatibility(self) -> CompatibilityResult:
|
|
536
539
|
return self._compatibility
|
|
537
540
|
|
|
541
|
+
@property
|
|
542
|
+
def divergence_engine(self) -> Any:
|
|
543
|
+
return self._divergence_engine
|
|
544
|
+
|
|
538
545
|
def __repr__(self) -> str:
|
|
539
546
|
return (
|
|
540
547
|
f"TheseusWrapper("
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
import torch.nn.functional as F
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
class DivergencePenaltyLoss(nn.Module):
|
|
9
|
+
"""
|
|
10
|
+
Implements the Divergence Penalty as defined in Section 5.1 of the research paper.
|
|
11
|
+
|
|
12
|
+
This loss function penalizes normative drift by calculating the distance between
|
|
13
|
+
the model's generated embeddings and a reference 'Kernel' embedding.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, alpha: float = 1.0, distance_metric: str = "cosine"):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.alpha = alpha
|
|
18
|
+
self.distance_metric = distance_metric
|
|
19
|
+
|
|
20
|
+
def forward(
|
|
21
|
+
self,
|
|
22
|
+
current_embeddings: torch.Tensor,
|
|
23
|
+
reference_embeddings: torch.Tensor
|
|
24
|
+
) -> torch.Tensor:
|
|
25
|
+
"""
|
|
26
|
+
Calculates the identity-divergence penalty.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
current_embeddings: The hidden states or outputs of the model being trained.
|
|
30
|
+
reference_embeddings: The frozen CIK (Constitutional Identity Kernel) embeddings.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A scalar tensor representing the penalty.
|
|
34
|
+
"""
|
|
35
|
+
if self.distance_metric == "cosine":
|
|
36
|
+
# 1 - cosine_similarity (range: 0 to 2, where 0 is identical)
|
|
37
|
+
similarity = F.cosine_similarity(current_embeddings, reference_embeddings, dim=-1)
|
|
38
|
+
dist = 1.0 - similarity
|
|
39
|
+
else:
|
|
40
|
+
# Fallback to MSE / Euclidean distance
|
|
41
|
+
dist = F.mse_loss(current_embeddings, reference_embeddings, reduction='none')
|
|
42
|
+
if dist.dim() > 1:
|
|
43
|
+
dist = dist.mean(dim=-1)
|
|
44
|
+
|
|
45
|
+
# Penalize drift beyond a certain magnitude
|
|
46
|
+
penalty = self.alpha * torch.mean(dist)
|
|
47
|
+
|
|
48
|
+
return penalty
|
|
49
|
+
|
|
50
|
+
def compute_alignment_loss(
|
|
51
|
+
model_output_embeddings: torch.Tensor,
|
|
52
|
+
kernel_anchor_embeddings: torch.Tensor,
|
|
53
|
+
alpha: float = 0.5
|
|
54
|
+
) -> torch.Tensor:
|
|
55
|
+
"""
|
|
56
|
+
Helper function to compute identity alignment loss during training.
|
|
57
|
+
"""
|
|
58
|
+
criterion = DivergencePenaltyLoss(alpha=alpha)
|
|
59
|
+
return criterion(model_output_embeddings, kernel_anchor_embeddings)
|
|
@@ -167,7 +167,8 @@ class IdentityEmbeddingLayer:
|
|
|
167
167
|
return vector
|
|
168
168
|
|
|
169
169
|
@staticmethod
|
|
170
|
-
def
|
|
170
|
+
def calculate_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
171
|
+
"""Static method for cosine similarity calculation."""
|
|
171
172
|
if a.shape != b.shape:
|
|
172
173
|
# Safe fallback for dimension mismatch
|
|
173
174
|
min_len = min(len(a), len(b))
|
|
@@ -178,3 +179,7 @@ class IdentityEmbeddingLayer:
|
|
|
178
179
|
if norm_a == 0 or norm_b == 0:
|
|
179
180
|
return 0.0
|
|
180
181
|
return float(dot / (norm_a * norm_b))
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
185
|
+
return IdentityEmbeddingLayer.calculate_similarity(a, b)
|
|
@@ -0,0 +1,135 @@
|
|
|
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.middleware.langchain import TheseusChatModel
|
|
16
|
+
from ct_toolkit.utils.logger import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
class ContextCompressionGuard:
|
|
21
|
+
"""
|
|
22
|
+
Guardrails for identity-preservation during context compression (summarization).
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, wrapper: TheseusWrapper, threshold: float = 0.85) -> None:
|
|
25
|
+
self.wrapper = wrapper
|
|
26
|
+
self.threshold = threshold
|
|
27
|
+
|
|
28
|
+
def analyze_summary_drift(
|
|
29
|
+
self,
|
|
30
|
+
original_messages: List[Dict[str, str]],
|
|
31
|
+
summary_text: str
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Compares the identity embedding of original messages against the summary.
|
|
35
|
+
Records the event in Provenance Log.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict containing similarity score and drift status.
|
|
39
|
+
"""
|
|
40
|
+
# 1. Flatten original messages into a single identity string
|
|
41
|
+
original_text = " ".join([m.get("content", "") for m in original_messages])
|
|
42
|
+
|
|
43
|
+
# 2. Compute embeddings via identity layer
|
|
44
|
+
original_emb = self.wrapper._identity_layer._compute_vector(original_text)
|
|
45
|
+
summary_emb = self.wrapper._identity_layer._compute_vector(summary_text)
|
|
46
|
+
|
|
47
|
+
# 3. Calculate similarity (L1 ECS style)
|
|
48
|
+
from ct_toolkit.identity.embedding import IdentityEmbeddingLayer
|
|
49
|
+
similarity = IdentityEmbeddingLayer.calculate_similarity(original_emb, summary_emb)
|
|
50
|
+
|
|
51
|
+
# 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
|
+
drift_detected = similarity < self.threshold
|
|
54
|
+
is_critical = similarity < 0.70 # A hard floor for identity integrity
|
|
55
|
+
|
|
56
|
+
result = {
|
|
57
|
+
"similarity": similarity,
|
|
58
|
+
"threshold": self.threshold,
|
|
59
|
+
"drift_detected": drift_detected,
|
|
60
|
+
"critical_drift": is_critical,
|
|
61
|
+
"event": "context_compression"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# 5. Record to Provenance Log (Tamper-proof record)
|
|
65
|
+
if self.wrapper._config.log_requests:
|
|
66
|
+
self.wrapper._provenance_log.record(
|
|
67
|
+
request_text=f"[SYSTEM_ACTION: CONTEXT_SUMMARIZATION] History: {original_text[:500]}...",
|
|
68
|
+
response_text=summary_text,
|
|
69
|
+
divergence_score=1.0 - similarity,
|
|
70
|
+
metadata={
|
|
71
|
+
"type": "compression_audit",
|
|
72
|
+
"similarity": similarity,
|
|
73
|
+
"threshold_applied": self.threshold,
|
|
74
|
+
"is_critical": is_critical,
|
|
75
|
+
"drift_flag": drift_detected,
|
|
76
|
+
"template": self.wrapper._config.template,
|
|
77
|
+
"kernel": self.wrapper.kernel.name,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# 6. Trigger alert if detected (User action required)
|
|
82
|
+
if (drift_detected or is_critical) and self.wrapper._config.drift_alert_callback:
|
|
83
|
+
logger.warning(
|
|
84
|
+
f"CT Toolkit | Identity Drift during compression: similarity={similarity:.2f} "
|
|
85
|
+
f"(Threshold: {self.threshold}, Critical: {is_critical})"
|
|
86
|
+
)
|
|
87
|
+
self.wrapper._config.drift_alert_callback(result)
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def wrap_deep_agent_factory(
|
|
92
|
+
create_deep_agent_fn: Callable,
|
|
93
|
+
wrapper: Optional[TheseusWrapper] = None,
|
|
94
|
+
wrapper_config: Optional[WrapperConfig] = None,
|
|
95
|
+
compression_threshold: float = 0.85,
|
|
96
|
+
) -> Callable:
|
|
97
|
+
"""
|
|
98
|
+
Wraps the LangChain `create_deep_agent` function to automatically
|
|
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
|
+
}
|
|
@@ -194,10 +194,11 @@ class TheseusChatModel(BaseChatModel):
|
|
|
194
194
|
*,
|
|
195
195
|
provider: str = "openai",
|
|
196
196
|
model: str = "gpt-4o-mini",
|
|
197
|
+
wrapper: Optional[TheseusWrapper] = None,
|
|
197
198
|
wrapper_config: Optional[WrapperConfig] = None,
|
|
198
199
|
**kwargs: Any,
|
|
199
200
|
) -> None:
|
|
200
|
-
_wrapper = TheseusWrapper(
|
|
201
|
+
_wrapper = wrapper or TheseusWrapper(
|
|
201
202
|
provider=provider,
|
|
202
203
|
config=wrapper_config,
|
|
203
204
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -28,6 +28,8 @@ Provides-Extra: autogen
|
|
|
28
28
|
Requires-Dist: pyautogen>=0.4.0; extra == "autogen"
|
|
29
29
|
Provides-Extra: docs
|
|
30
30
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
31
|
+
Provides-Extra: ml
|
|
32
|
+
Requires-Dist: torch>=2.0.0; extra == "ml"
|
|
31
33
|
Provides-Extra: dev
|
|
32
34
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
35
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -49,9 +51,11 @@ CT Toolkit is an open-source security layer designed to preserve the **identity
|
|
|
49
51
|
---
|
|
50
52
|
|
|
51
53
|
## 📖 Official Documentation
|
|
52
|
-
For full API reference, architecture details, and integration guides, visit our documentation site:
|
|
54
|
+
For full API reference, architecture details, examples, and integration guides, visit our documentation site:
|
|
53
55
|
👉 [**https://hakandamar.github.io/ct-toolkit/**](https://hakandamar.github.io/ct-toolkit/)
|
|
54
56
|
|
|
57
|
+
- [**Live Examples & Case Studies**](https://hakandamar.github.io/ct-toolkit/examples/): Real-world scenarios like Financial Auditor Integrity.
|
|
58
|
+
|
|
55
59
|
---
|
|
56
60
|
|
|
57
61
|
## Why CT Toolkit?
|
|
@@ -90,23 +94,19 @@ print(f"Divergence Score: {response.divergence_score}")
|
|
|
90
94
|
|
|
91
95
|
| Metric | Status |
|
|
92
96
|
| :--- | :--- |
|
|
93
|
-
| **Tests** | ✅
|
|
94
|
-
| **Last Phase** | ✅ Phase
|
|
95
|
-
| **Current Goal** | 🔶 Phase
|
|
97
|
+
| **Tests** | ✅ 218/218 passing (93% coverage) |
|
|
98
|
+
| **Last Phase** | ✅ Phase 4: OS Support, Deep Agents & Alerts (Complete) |
|
|
99
|
+
| **Current Goal** | 🔶 Phase 5: Vault and Security Infrastructure |
|
|
96
100
|
|
|
97
101
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
98
102
|
|
|
99
|
-
### Framework Support
|
|
100
|
-
Seamlessly integrate with your favorite frameworks:
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
# LangChain
|
|
104
|
-
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
105
|
-
llm = TheseusChatModel(provider="openai")
|
|
103
|
+
### Framework & Model Support
|
|
104
|
+
Seamlessly integrate with your favorite frameworks and local models:
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
- **Local Models**: Support for LM Studio, Ollama, and local Qwen/Llama endpoints.
|
|
107
|
+
- **LangChain & Deep Agents**: `wrap_deep_agent_factory`.
|
|
108
|
+
- **CrewAI**: `TheseusCrewMiddleware.apply_to_crew`.
|
|
109
|
+
- **AutoGen**: `register_reply` hooks.
|
|
110
110
|
|
|
111
111
|
---
|
|
112
112
|
|
|
@@ -17,6 +17,7 @@ ct_toolkit/divergence/analysis.py
|
|
|
17
17
|
ct_toolkit/divergence/engine.py
|
|
18
18
|
ct_toolkit/divergence/l2_judge.py
|
|
19
19
|
ct_toolkit/divergence/l3_icm.py
|
|
20
|
+
ct_toolkit/divergence/loss.py
|
|
20
21
|
ct_toolkit/divergence/scheduler.py
|
|
21
22
|
ct_toolkit/endorsement/__init__.py
|
|
22
23
|
ct_toolkit/endorsement/reflective.py
|
|
@@ -26,10 +27,14 @@ ct_toolkit/identity/embedding.py
|
|
|
26
27
|
ct_toolkit/middleware/__init__.py
|
|
27
28
|
ct_toolkit/middleware/autogen.py
|
|
28
29
|
ct_toolkit/middleware/crewai.py
|
|
30
|
+
ct_toolkit/middleware/deepagents.py
|
|
29
31
|
ct_toolkit/middleware/langchain.py
|
|
30
32
|
ct_toolkit/provenance/__init__.py
|
|
31
33
|
ct_toolkit/provenance/log.py
|
|
32
34
|
ct_toolkit/provenance/vault/__init__.py
|
|
33
35
|
ct_toolkit/utils/__init__.py
|
|
34
36
|
ct_toolkit/utils/logger.py
|
|
35
|
-
tests/test_analysis.py
|
|
37
|
+
tests/test_analysis.py
|
|
38
|
+
tests/test_deep_agents_integration.py
|
|
39
|
+
tests/test_loss.py
|
|
40
|
+
tests/test_summarization_drift.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -31,6 +31,7 @@ langchain = ["langchain-core>=1.2.0"]
|
|
|
31
31
|
crewai = ["crewai>=1.10.0"]
|
|
32
32
|
autogen = ["pyautogen>=0.4.0"]
|
|
33
33
|
docs = ["mkdocs-material>=9.5.0"]
|
|
34
|
+
ml = ["torch>=2.0.0"]
|
|
34
35
|
dev = [
|
|
35
36
|
"pytest>=8.0",
|
|
36
37
|
"pytest-asyncio>=0.23",
|
|
@@ -53,6 +53,36 @@ class TestPolicyDrift(unittest.TestCase):
|
|
|
53
53
|
self.assertGreater(score_high, score_low)
|
|
54
54
|
print(f"Risk Normalized Test: LowRiskScore={score_low}, HighRiskScore={score_high}")
|
|
55
55
|
|
|
56
|
+
def test_missing_metadata(self):
|
|
57
|
+
# Entry with missing template/kernel/model should be ignored
|
|
58
|
+
entry_missing = MagicMock(spec=ProvenanceEntry)
|
|
59
|
+
entry_missing.divergence_score = 0.5
|
|
60
|
+
entry_missing.metadata = {} # Missing everything
|
|
61
|
+
|
|
62
|
+
entry_valid = MagicMock(spec=ProvenanceEntry)
|
|
63
|
+
entry_valid.divergence_score = 0.1
|
|
64
|
+
entry_valid.metadata = {"template": "general", "kernel": "standard", "model": "gpt-4"}
|
|
65
|
+
|
|
66
|
+
self.mock_log.get_entries.return_value = [entry_missing] * 10 + [entry_valid] * 5
|
|
67
|
+
|
|
68
|
+
report = self.analyzer.analyze_drift("general", "standard", "gpt-4")
|
|
69
|
+
# Only the 5 valid ones should be counted
|
|
70
|
+
self.assertEqual(report.data_points, 5)
|
|
71
|
+
|
|
72
|
+
def test_inconsistent_scores(self):
|
|
73
|
+
# Even with high variance and negative velocity, shouldn't crash
|
|
74
|
+
entries = []
|
|
75
|
+
for i in range(10):
|
|
76
|
+
entry = MagicMock(spec=ProvenanceEntry)
|
|
77
|
+
entry.divergence_score = 0.5 - (i * 0.05) # Decreasing divergence
|
|
78
|
+
entry.metadata = {"template": "general", "kernel": "standard", "model": "gpt-4"}
|
|
79
|
+
entries.append(entry)
|
|
80
|
+
|
|
81
|
+
self.mock_log.get_entries.return_value = entries
|
|
82
|
+
report = self.analyzer.analyze_drift("general", "standard", "gpt-4")
|
|
83
|
+
self.assertLessEqual(report.drift_velocity, 0)
|
|
84
|
+
self.assertEqual(report.ssc_severity_score, 0.0) # Negative velocity = 0 severity
|
|
85
|
+
|
|
56
86
|
class TestSSCSeverity(unittest.TestCase):
|
|
57
87
|
def test_severity_formula(self):
|
|
58
88
|
calc = SSCSeverityCalculator()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
from ct_toolkit.middleware.deepagents import wrap_deep_agent_factory, DeepAgentTheseusHelper
|
|
5
|
+
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
6
|
+
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
7
|
+
|
|
8
|
+
class TestDeepAgentsIntegration(unittest.TestCase):
|
|
9
|
+
def test_wrap_deep_agent_factory_injections(self):
|
|
10
|
+
# Mock the create_deep_agent function from the library
|
|
11
|
+
mock_create = MagicMock()
|
|
12
|
+
|
|
13
|
+
# Setup CT Toolkit
|
|
14
|
+
config = WrapperConfig(template="defense")
|
|
15
|
+
wrapper = TheseusWrapper(config=config)
|
|
16
|
+
|
|
17
|
+
# Wrap the factory
|
|
18
|
+
wrapped_factory = wrap_deep_agent_factory(
|
|
19
|
+
create_deep_agent_fn=mock_create,
|
|
20
|
+
wrapper=wrapper,
|
|
21
|
+
wrapper_config=config
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Test 1: Model injection
|
|
25
|
+
wrapped_factory(model="gpt-4o")
|
|
26
|
+
|
|
27
|
+
# Verify that the model was replaced with a TheseusChatModel
|
|
28
|
+
args, kwargs = mock_create.call_args
|
|
29
|
+
self.assertIsInstance(kwargs["model"], TheseusChatModel)
|
|
30
|
+
self.assertEqual(kwargs["model"].wrapper.kernel.name, "default")
|
|
31
|
+
self.assertEqual(kwargs["model"].wrapper._config.template, "defense")
|
|
32
|
+
|
|
33
|
+
def test_subagent_propagation_logging(self):
|
|
34
|
+
mock_create = MagicMock()
|
|
35
|
+
wrapper = TheseusWrapper()
|
|
36
|
+
wrapped_factory = wrap_deep_agent_factory(mock_create, wrapper=wrapper)
|
|
37
|
+
|
|
38
|
+
subagents = [
|
|
39
|
+
{"name": "researcher", "model": "gpt-4o"},
|
|
40
|
+
{"name": "writer", "model": "gpt-4o"}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
wrapped_factory(subagents=subagents)
|
|
44
|
+
|
|
45
|
+
# Verify call happened
|
|
46
|
+
mock_create.assert_called_once()
|
|
47
|
+
# In current implementation, we just log and pass through,
|
|
48
|
+
# but we verify the subagents list was passed.
|
|
49
|
+
self.assertEqual(len(mock_create.call_args[1]["subagents"]), 2)
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
unittest.main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import torch
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from ct_toolkit.divergence.loss import DivergencePenaltyLoss
|
|
5
|
+
|
|
6
|
+
class TestDivergenceLoss(unittest.TestCase):
|
|
7
|
+
def test_gradient_flow(self):
|
|
8
|
+
# Setup: Current embedding (trainable) and Reference (frozen)
|
|
9
|
+
current = torch.randn(1, 128, requires_grad=True)
|
|
10
|
+
reference = torch.randn(1, 128)
|
|
11
|
+
|
|
12
|
+
criterion = DivergencePenaltyLoss(alpha=1.0)
|
|
13
|
+
loss = criterion(current, reference)
|
|
14
|
+
|
|
15
|
+
# Verify loss can backpropagate
|
|
16
|
+
loss.backward()
|
|
17
|
+
|
|
18
|
+
self.assertIsNotNone(current.grad)
|
|
19
|
+
self.assertGreater(loss.item(), 0)
|
|
20
|
+
|
|
21
|
+
def test_identical_embeddings(self):
|
|
22
|
+
# If embeddings are identical, loss should be near zero (for cosine distance)
|
|
23
|
+
current = torch.ones(1, 128, requires_grad=True)
|
|
24
|
+
reference = torch.ones(1, 128)
|
|
25
|
+
|
|
26
|
+
criterion = DivergencePenaltyLoss(alpha=1.0, distance_metric="cosine")
|
|
27
|
+
loss = criterion(current, reference)
|
|
28
|
+
|
|
29
|
+
self.assertLess(loss.item(), 1e-6)
|
|
30
|
+
|
|
31
|
+
def test_high_divergence_penalty(self):
|
|
32
|
+
# Orthogonal embeddings should have higher loss than parallel ones
|
|
33
|
+
ref = torch.tensor([[1.0, 0.0]])
|
|
34
|
+
parallel = torch.tensor([[0.9, 0.0]], requires_grad=True)
|
|
35
|
+
orthogonal = torch.tensor([[0.0, 1.0]], requires_grad=True)
|
|
36
|
+
|
|
37
|
+
criterion = DivergencePenaltyLoss(alpha=1.0)
|
|
38
|
+
|
|
39
|
+
loss_low = criterion(parallel, ref)
|
|
40
|
+
loss_high = criterion(orthogonal, ref)
|
|
41
|
+
|
|
42
|
+
self.assertGreater(loss_high.item(), loss_low.item())
|
|
43
|
+
|
|
44
|
+
def test_batch_sequence_dimensions(self):
|
|
45
|
+
# Test with [Batch, Seq, Dim] common in LLM hidden states
|
|
46
|
+
current = torch.randn(8, 32, 128, requires_grad=True)
|
|
47
|
+
# Reference might be a single anchor [1, 128] or matched [8, 32, 128]
|
|
48
|
+
reference = torch.randn(1, 1, 128)
|
|
49
|
+
|
|
50
|
+
criterion = DivergencePenaltyLoss(alpha=1.0)
|
|
51
|
+
loss = criterion(current, reference)
|
|
52
|
+
|
|
53
|
+
loss.backward()
|
|
54
|
+
self.assertEqual(current.grad.shape, current.shape)
|
|
55
|
+
|
|
56
|
+
def test_dimension_mismatch(self):
|
|
57
|
+
# Should raise error if dimensions are completely incompatible
|
|
58
|
+
current = torch.randn(1, 128)
|
|
59
|
+
reference = torch.randn(1, 64)
|
|
60
|
+
|
|
61
|
+
criterion = DivergencePenaltyLoss(alpha=1.0)
|
|
62
|
+
with self.assertRaises(RuntimeError):
|
|
63
|
+
criterion(current, reference)
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
unittest.main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
from ct_toolkit.middleware.deepagents import ContextCompressionGuard
|
|
5
|
+
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
6
|
+
|
|
7
|
+
class TestSummarizationDrift(unittest.TestCase):
|
|
8
|
+
def test_analyze_summary_drift(self):
|
|
9
|
+
# Setup wrapper with a mock identity layer
|
|
10
|
+
wrapper = TheseusWrapper()
|
|
11
|
+
|
|
12
|
+
# We need to mock the identity layer to return controlled embeddings
|
|
13
|
+
mock_identity = MagicMock()
|
|
14
|
+
import numpy as np
|
|
15
|
+
# High similarity case (nearly identical embeddings)
|
|
16
|
+
mock_identity._compute_vector.side_effect = [
|
|
17
|
+
np.array([0.1, 0.2, 0.3], dtype=np.float32),
|
|
18
|
+
np.array([0.1, 0.2, 0.31], dtype=np.float32)
|
|
19
|
+
]
|
|
20
|
+
wrapper._identity_layer = mock_identity
|
|
21
|
+
|
|
22
|
+
guard = ContextCompressionGuard(wrapper, threshold=0.9)
|
|
23
|
+
|
|
24
|
+
original_msgs = [{"role": "user", "content": "I love safety and ethics."}]
|
|
25
|
+
summary = "Safety and ethics are important."
|
|
26
|
+
|
|
27
|
+
result = guard.analyze_summary_drift(original_msgs, summary)
|
|
28
|
+
|
|
29
|
+
# IdentityEmbeddingLayer.calculate_similarity is a static method,
|
|
30
|
+
# it will calculate a high value for our side_effect vectors.
|
|
31
|
+
self.assertGreater(result["similarity"], 0.9)
|
|
32
|
+
self.assertFalse(result["drift_detected"])
|
|
33
|
+
|
|
34
|
+
def test_drift_alert_trigger(self):
|
|
35
|
+
# Setup alert callback
|
|
36
|
+
alert_hit = False
|
|
37
|
+
def mock_alert(payload):
|
|
38
|
+
nonlocal alert_hit
|
|
39
|
+
alert_hit = True
|
|
40
|
+
|
|
41
|
+
config = WrapperConfig(drift_alert_callback=mock_alert)
|
|
42
|
+
wrapper = TheseusWrapper(config=config)
|
|
43
|
+
|
|
44
|
+
# Force a very low similarity
|
|
45
|
+
mock_identity = MagicMock()
|
|
46
|
+
# Totally different vectors
|
|
47
|
+
mock_identity.get_embeddings.side_effect = [[1, 0, 0], [0, 1, 0]]
|
|
48
|
+
wrapper._identity_layer = mock_identity
|
|
49
|
+
|
|
50
|
+
guard = ContextCompressionGuard(wrapper, threshold=0.8)
|
|
51
|
+
|
|
52
|
+
original_msgs = [{"role": "user", "content": "A"}]
|
|
53
|
+
summary = "B"
|
|
54
|
+
|
|
55
|
+
result = guard.analyze_summary_drift(original_msgs, summary)
|
|
56
|
+
|
|
57
|
+
def test_safety_floor_trigger(self):
|
|
58
|
+
"""Verify that even with a low user threshold, the 0.70 safety floor triggers an alert."""
|
|
59
|
+
alert_hit = False
|
|
60
|
+
def mock_alert(payload):
|
|
61
|
+
nonlocal alert_hit
|
|
62
|
+
alert_hit = True
|
|
63
|
+
|
|
64
|
+
# User sets a dangerously low threshold
|
|
65
|
+
config = WrapperConfig(drift_alert_callback=mock_alert)
|
|
66
|
+
wrapper = TheseusWrapper(config=config)
|
|
67
|
+
|
|
68
|
+
import numpy as np
|
|
69
|
+
mock_identity = MagicMock()
|
|
70
|
+
# Similarity = 0.65 (Above user 0.4 but below safety floor 0.7)
|
|
71
|
+
mock_identity._compute_vector.side_effect = [
|
|
72
|
+
np.array([1, 0, 0], dtype=np.float32),
|
|
73
|
+
np.array([0.65, 0.75, 0], dtype=np.float32) # Not perfectly orthogonal but enough to be < 0.7
|
|
74
|
+
]
|
|
75
|
+
wrapper._identity_layer = mock_identity
|
|
76
|
+
|
|
77
|
+
guard = ContextCompressionGuard(wrapper, threshold=0.4)
|
|
78
|
+
|
|
79
|
+
result = guard.analyze_summary_drift([{"content": "A"}], "B")
|
|
80
|
+
|
|
81
|
+
self.assertFalse(result["drift_detected"]) # Similarity 0.65 > 0.4
|
|
82
|
+
self.assertTrue(result["critical_drift"]) # Similarity 0.65 < 0.7
|
|
83
|
+
self.assertTrue(alert_hit) # Alert should fire because of critical_drift
|
|
84
|
+
|
|
85
|
+
def test_provenance_recording(self):
|
|
86
|
+
"""Verify that compression events are written to the Provenance Log."""
|
|
87
|
+
wrapper = TheseusWrapper()
|
|
88
|
+
wrapper._provenance_log = MagicMock()
|
|
89
|
+
|
|
90
|
+
import numpy as np
|
|
91
|
+
mock_identity = MagicMock()
|
|
92
|
+
mock_identity._compute_vector.return_value = np.array([1, 0, 0], dtype=np.float32)
|
|
93
|
+
wrapper._identity_layer = mock_identity
|
|
94
|
+
|
|
95
|
+
guard = ContextCompressionGuard(wrapper)
|
|
96
|
+
guard.analyze_summary_drift([{"content": "original"}], "summary")
|
|
97
|
+
|
|
98
|
+
# Verify provenance_log.record was called
|
|
99
|
+
self.assertTrue(wrapper._provenance_log.record.called)
|
|
100
|
+
args, kwargs = wrapper._provenance_log.record.call_args
|
|
101
|
+
self.assertIn("CONTEXT_SUMMARIZATION", kwargs["request_text"])
|
|
102
|
+
self.assertEqual(kwargs["metadata"]["type"], "compression_audit")
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|