ct-toolkit 0.3.16__tar.gz → 0.3.18__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.16 → ct_toolkit-0.3.18}/PKG-INFO +3 -3
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/README.md +2 -2
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/__init__.py +1 -1
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/l2_judge.py +46 -25
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/l3_icm.py +104 -34
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/PKG-INFO +3 -3
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/pyproject.toml +1 -1
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/LICENSE +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/__main__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/cli.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/compression_guard.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/core/wrapper.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/middleware/litellm.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/server.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/SOURCES.txt +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/entry_points.txt +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/setup.cfg +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_cli.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_cli_extended.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.16 → ct_toolkit-0.3.18}/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.18
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -120,9 +120,9 @@ ct-toolkit list-templates
|
|
|
120
120
|
|
|
121
121
|
| Metric | Status |
|
|
122
122
|
| :--------------- | :------------------------------------------------------ |
|
|
123
|
-
| **Tests** | ✅
|
|
123
|
+
| **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
|
|
124
124
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
125
|
-
| **Last Phase** | ✅ v0.3.
|
|
125
|
+
| **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
|
|
126
126
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
127
127
|
|
|
128
128
|
### Framework & Model Support
|
|
@@ -77,9 +77,9 @@ ct-toolkit list-templates
|
|
|
77
77
|
|
|
78
78
|
| Metric | Status |
|
|
79
79
|
| :--------------- | :------------------------------------------------------ |
|
|
80
|
-
| **Tests** | ✅
|
|
80
|
+
| **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
|
|
81
81
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
82
|
-
| **Last Phase** | ✅ v0.3.
|
|
82
|
+
| **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
|
|
83
83
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
84
84
|
|
|
85
85
|
### Framework & Model Support
|
|
@@ -19,6 +19,7 @@ from enum import Enum
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
from pydantic import BaseModel, Field
|
|
21
21
|
import instructor
|
|
22
|
+
import litellm
|
|
22
23
|
from jinja2 import Template
|
|
23
24
|
|
|
24
25
|
from ct_toolkit.utils.logger import get_logger
|
|
@@ -103,26 +104,22 @@ class LLMJudge:
|
|
|
103
104
|
self._provider = provider
|
|
104
105
|
self._model = model or self._default_model(provider)
|
|
105
106
|
|
|
106
|
-
# Fix for Ollama:
|
|
107
|
+
# Fix for Ollama: LiteLLM's native Ollama provider expects root port (no /v1)
|
|
108
|
+
self._api_base = None
|
|
107
109
|
if provider == "ollama" and hasattr(client, "base_url"):
|
|
108
110
|
base_url = str(client.base_url).rstrip("/")
|
|
109
111
|
if base_url.endswith("/v1"):
|
|
110
|
-
|
|
111
|
-
logger.debug(f"Stripping /v1 from Ollama judge
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
elif provider == "anthropic":
|
|
122
|
-
self._instructor_client = instructor.from_anthropic(client)
|
|
123
|
-
else:
|
|
124
|
-
# Fallback to standard patch for other providers (e.g. Ollama)
|
|
125
|
-
self._instructor_client = instructor.patch(client)
|
|
112
|
+
self._api_base = base_url[:-3]
|
|
113
|
+
logger.debug(f"Stripping /v1 from Ollama judge api_base: {base_url} -> {self._api_base}")
|
|
114
|
+
else:
|
|
115
|
+
self._api_base = base_url
|
|
116
|
+
elif hasattr(client, "base_url"):
|
|
117
|
+
self._api_base = str(client.base_url)
|
|
118
|
+
|
|
119
|
+
self._api_key = getattr(client, "api_key", None)
|
|
120
|
+
|
|
121
|
+
# Use instructor with litellm for unified provider support
|
|
122
|
+
self._instructor_client = instructor.from_litellm(litellm.completion)
|
|
126
123
|
|
|
127
124
|
@staticmethod
|
|
128
125
|
def _default_model(provider: str) -> str:
|
|
@@ -149,27 +146,51 @@ class LLMJudge:
|
|
|
149
146
|
request_text=request_text[:1000],
|
|
150
147
|
)
|
|
151
148
|
|
|
149
|
+
full_model = self._model
|
|
150
|
+
# Prevent colon-to-slash replacement if model already has ollama/ prefix
|
|
151
|
+
# or if the provider is specifically ollama
|
|
152
|
+
is_ollama = (self._provider == "ollama" or self._model.startswith("ollama/"))
|
|
153
|
+
|
|
154
|
+
if ":" in self._model and not is_ollama:
|
|
155
|
+
full_model = self._model.replace(":", "/", 1)
|
|
156
|
+
elif self._provider not in ("openai", "unknown") and not is_ollama:
|
|
157
|
+
if not self._model.startswith(f"{self._provider}/"):
|
|
158
|
+
full_model = f"{self._provider}/{self._model}"
|
|
159
|
+
|
|
160
|
+
# Ensure ollama always has prefix if it has a colon
|
|
161
|
+
if self._provider == "ollama" and ":" in self._model and not full_model.startswith("ollama/"):
|
|
162
|
+
full_model = f"ollama/{full_model}"
|
|
163
|
+
|
|
152
164
|
try:
|
|
165
|
+
kwargs: dict[str, Any] = {
|
|
166
|
+
"model": full_model,
|
|
167
|
+
"response_model": JudgeResponse,
|
|
168
|
+
"max_retries": 2,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if self._api_base:
|
|
172
|
+
kwargs["api_base"] = self._api_base
|
|
173
|
+
if self._api_key:
|
|
174
|
+
kwargs["api_key"] = self._api_key
|
|
175
|
+
|
|
153
176
|
# Call using instructor for structured data
|
|
154
177
|
if self._provider == "anthropic":
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
# Anthropic LiteLLM requires system prompt in extra_body or via specific messages call
|
|
179
|
+
# But instructor.from_litellm handles standard messages list
|
|
180
|
+
data: JudgeResponse = self._instructor_client.chat.completions.create(
|
|
181
|
+
**kwargs,
|
|
159
182
|
messages=[
|
|
183
|
+
{"role": "system", "content": system_prompt},
|
|
160
184
|
{"role": "user", "content": user_prompt},
|
|
161
185
|
],
|
|
162
|
-
max_retries=2
|
|
163
186
|
)
|
|
164
187
|
else:
|
|
165
188
|
data: JudgeResponse = self._instructor_client.chat.completions.create(
|
|
166
|
-
|
|
167
|
-
response_model=JudgeResponse,
|
|
189
|
+
**kwargs,
|
|
168
190
|
messages=[
|
|
169
191
|
{"role": "system", "content": system_prompt},
|
|
170
192
|
{"role": "user", "content": user_prompt},
|
|
171
193
|
],
|
|
172
|
-
max_retries=2
|
|
173
194
|
)
|
|
174
195
|
|
|
175
196
|
result = JudgeResult(
|
|
@@ -19,13 +19,23 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
|
+
import instructor
|
|
22
23
|
import litellm
|
|
24
|
+
from pydantic import BaseModel, Field
|
|
23
25
|
|
|
24
26
|
from ct_toolkit.utils.logger import get_logger
|
|
25
27
|
|
|
26
28
|
logger = get_logger(__name__)
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
# ── Pydantic Models for Structured Responses ────────────────────────────────
|
|
32
|
+
|
|
33
|
+
class ProbeResponse(BaseModel):
|
|
34
|
+
"""Pydantic model for structured probe response with graceful fallback."""
|
|
35
|
+
content: str = Field(..., description="Model's response text")
|
|
36
|
+
reasoning: str = Field(default="", description="Model's reasoning if provided")
|
|
37
|
+
|
|
38
|
+
|
|
29
39
|
# ── Data Models ─────────────────────────────────────────────────────────────
|
|
30
40
|
|
|
31
41
|
@dataclass
|
|
@@ -221,6 +231,9 @@ class ICMRunner:
|
|
|
221
231
|
self._include_domain = include_domain_probes
|
|
222
232
|
self._project_root = project_root
|
|
223
233
|
self._max_probes = max_probes
|
|
234
|
+
|
|
235
|
+
# Use instructor with litellm for structured response validation
|
|
236
|
+
self._instructor_client = instructor.from_litellm(litellm.completion)
|
|
224
237
|
|
|
225
238
|
@staticmethod
|
|
226
239
|
def _default_model(provider: str) -> str:
|
|
@@ -322,35 +335,93 @@ class ICMRunner:
|
|
|
322
335
|
# ── Model Call ──────────────────────────────────────────────────────────
|
|
323
336
|
|
|
324
337
|
def _call_model(self, prompt: str) -> str:
|
|
338
|
+
"""
|
|
339
|
+
Calls model with structured response validation and graceful fallback.
|
|
340
|
+
|
|
341
|
+
L3 Graceful Fallback Strategy:
|
|
342
|
+
1. Try structured call via instructor (validates against ProbeResponse schema)
|
|
343
|
+
2. If validation fails (e.g., JSON parse error), fall back to raw completion
|
|
344
|
+
3. If raw completion fails, return safe error placeholder
|
|
345
|
+
|
|
346
|
+
This mirrors L2 Judge's approach, preventing "parse failure = hard error" behavior.
|
|
347
|
+
"""
|
|
325
348
|
system = self._kernel.get_system_prompt_injection()
|
|
349
|
+
full_model = self._format_model_name()
|
|
350
|
+
|
|
351
|
+
user_content = (
|
|
352
|
+
f"{prompt}\n\n"
|
|
353
|
+
"Please provide your reasoning step-by-step wrapped in <think></think> tags "
|
|
354
|
+
"before your final response."
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
kwargs = self._build_litellm_kwargs(full_model, system, user_content)
|
|
326
358
|
|
|
327
|
-
#
|
|
359
|
+
# ── Attempt 1: Structured call with instructor validation ────────
|
|
360
|
+
try:
|
|
361
|
+
logger.debug(f"L3 ICM attempting structured validation for model {full_model}")
|
|
362
|
+
response: ProbeResponse = self._instructor_client.chat.completions.create(
|
|
363
|
+
**kwargs,
|
|
364
|
+
response_model=ProbeResponse,
|
|
365
|
+
max_retries=1,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Combine reasoning and content into full response
|
|
369
|
+
full_response = f"<think>{response.reasoning}</think>\n{response.content}" \
|
|
370
|
+
if response.reasoning else response.content
|
|
371
|
+
logger.debug(f"L3 ICM structured call succeeded")
|
|
372
|
+
return full_response
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
logger.warning(
|
|
376
|
+
f"L3 ICM structured validation failed (likely JSON/tool-call parse error): {e}. "
|
|
377
|
+
f"Falling back to raw completion."
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# ── Attempt 2: Raw litellm.completion without structured validation ────────
|
|
381
|
+
try:
|
|
382
|
+
resp = litellm.completion(**kwargs)
|
|
383
|
+
content = self._extract_response_content(resp)
|
|
384
|
+
logger.info(f"L3 ICM raw fallback succeeded")
|
|
385
|
+
return content
|
|
386
|
+
|
|
387
|
+
except Exception as fallback_e:
|
|
388
|
+
# ── Attempt 3: Safe error response ────────
|
|
389
|
+
logger.error(
|
|
390
|
+
f"L3 ICM both structured and raw calls failed: "
|
|
391
|
+
f"structured={type(e).__name__}, raw={type(fallback_e).__name__}. "
|
|
392
|
+
f"Returning error placeholder for graceful degradation."
|
|
393
|
+
)
|
|
394
|
+
return "[ERROR: Model call failed, unable to assess response]"
|
|
395
|
+
|
|
396
|
+
def _format_model_name(self) -> str:
|
|
397
|
+
"""Format model name according to provider conventions."""
|
|
328
398
|
full_model = self._model
|
|
329
|
-
# Prevent colon-to-slash replacement if model already has ollama/ prefix
|
|
330
|
-
# or if the provider is specifically ollama
|
|
331
399
|
is_ollama = (self._provider == "ollama" or self._model.startswith("ollama/"))
|
|
332
|
-
|
|
400
|
+
|
|
333
401
|
if ":" in self._model and not is_ollama:
|
|
334
|
-
|
|
402
|
+
full_model = self._model.replace(":", "/", 1)
|
|
335
403
|
elif self._provider not in ("openai", "unknown") and not is_ollama:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
# Ensure ollama always has prefix if it has a colon
|
|
404
|
+
if not self._model.startswith(f"{self._provider}/"):
|
|
405
|
+
full_model = f"{self._provider}/{self._model}"
|
|
406
|
+
|
|
340
407
|
if self._provider == "ollama" and ":" in self._model and not full_model.startswith("ollama/"):
|
|
341
|
-
|
|
408
|
+
full_model = f"ollama/{full_model}"
|
|
409
|
+
|
|
410
|
+
return full_model
|
|
342
411
|
|
|
412
|
+
def _build_litellm_kwargs(self, full_model: str, system: str, user_content: str) -> dict[str, Any]:
|
|
413
|
+
"""Build kwargs for litellm call."""
|
|
343
414
|
kwargs: dict[str, Any] = {
|
|
344
415
|
"model": full_model,
|
|
345
416
|
"messages": [
|
|
346
417
|
{"role": "system", "content": system},
|
|
347
|
-
{"role": "user", "content":
|
|
418
|
+
{"role": "user", "content": user_content},
|
|
348
419
|
],
|
|
349
420
|
"max_tokens": 512,
|
|
350
421
|
"temperature": 0.0,
|
|
351
422
|
}
|
|
352
|
-
|
|
353
|
-
# Extract connection info from client
|
|
423
|
+
|
|
424
|
+
# Extract connection info from client
|
|
354
425
|
if hasattr(self._client, "base_url") and self._client.base_url:
|
|
355
426
|
api_base = str(self._client.base_url).rstrip("/")
|
|
356
427
|
if self._provider == "ollama" and api_base.endswith("/v1"):
|
|
@@ -358,31 +429,30 @@ class ICMRunner:
|
|
|
358
429
|
logger.debug(f"Stripping /v1 from Ollama api_base (ICM): {api_base} -> {new_base}")
|
|
359
430
|
api_base = new_base
|
|
360
431
|
kwargs["api_base"] = api_base
|
|
361
|
-
|
|
432
|
+
|
|
362
433
|
if hasattr(self._client, "api_key") and self._client.api_key:
|
|
363
434
|
kwargs["api_key"] = self._client.api_key
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
resp = litellm.completion(**kwargs)
|
|
367
|
-
|
|
368
|
-
# Extract content from LiteLLM response
|
|
369
|
-
if hasattr(resp, "choices") and resp.choices:
|
|
370
|
-
choice = resp.choices[0]
|
|
371
|
-
if hasattr(choice, "message") and choice.message:
|
|
372
|
-
return choice.message.content or ""
|
|
373
|
-
|
|
374
|
-
# Fallback
|
|
375
|
-
if isinstance(resp, dict):
|
|
376
|
-
if "choices" in resp and resp["choices"]:
|
|
377
|
-
return resp["choices"][0].get("message", {}).get("content", "")
|
|
378
|
-
if "message" in resp:
|
|
379
|
-
return resp["message"].get("content", "")
|
|
380
|
-
return str(resp)
|
|
435
|
+
|
|
436
|
+
return kwargs
|
|
381
437
|
|
|
438
|
+
@staticmethod
|
|
439
|
+
def _extract_response_content(resp: Any) -> str:
|
|
440
|
+
"""Extract text content from litellm response object."""
|
|
441
|
+
# Handle object response with .choices attribute
|
|
442
|
+
if hasattr(resp, "choices") and resp.choices:
|
|
443
|
+
choice = resp.choices[0]
|
|
444
|
+
if hasattr(choice, "message") and choice.message:
|
|
445
|
+
return choice.message.content or ""
|
|
446
|
+
|
|
447
|
+
# Handle dict response
|
|
448
|
+
if isinstance(resp, dict):
|
|
449
|
+
if "choices" in resp and resp["choices"]:
|
|
450
|
+
return resp["choices"][0].get("message", {}).get("content", "")
|
|
451
|
+
if "message" in resp:
|
|
452
|
+
return resp["message"].get("content", "")
|
|
382
453
|
return str(resp)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
raise
|
|
454
|
+
|
|
455
|
+
return str(resp)
|
|
386
456
|
|
|
387
457
|
# ── Probe Loading ──────────────────────────────────────────────────────────
|
|
388
458
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.18
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -120,9 +120,9 @@ ct-toolkit list-templates
|
|
|
120
120
|
|
|
121
121
|
| Metric | Status |
|
|
122
122
|
| :--------------- | :------------------------------------------------------ |
|
|
123
|
-
| **Tests** | ✅
|
|
123
|
+
| **Tests** | ✅ 297 passed, 0 failed (100% success rate, 93% coverage) |
|
|
124
124
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
125
|
-
| **Last Phase** | ✅ v0.3.
|
|
125
|
+
| **Last Phase** | ✅ v0.3.18: L3 ICM graceful fallback for tool-call parsing failures |
|
|
126
126
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
127
127
|
|
|
128
128
|
### Framework & Model Support
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.18"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
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
|
|
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
|