tactus 0.33.0__py3-none-any.whl → 0.34.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tactus/__init__.py +1 -1
- tactus/adapters/__init__.py +18 -1
- tactus/adapters/broker_log.py +127 -34
- tactus/adapters/channels/__init__.py +153 -0
- tactus/adapters/channels/base.py +174 -0
- tactus/adapters/channels/broker.py +179 -0
- tactus/adapters/channels/cli.py +448 -0
- tactus/adapters/channels/host.py +225 -0
- tactus/adapters/channels/ipc.py +297 -0
- tactus/adapters/channels/sse.py +305 -0
- tactus/adapters/cli_hitl.py +223 -1
- tactus/adapters/control_loop.py +879 -0
- tactus/adapters/file_storage.py +35 -2
- tactus/adapters/ide_log.py +7 -1
- tactus/backends/http_backend.py +0 -1
- tactus/broker/client.py +31 -1
- tactus/broker/server.py +416 -92
- tactus/cli/app.py +270 -7
- tactus/cli/control.py +393 -0
- tactus/core/config_manager.py +33 -6
- tactus/core/dsl_stubs.py +102 -18
- tactus/core/execution_context.py +265 -8
- tactus/core/lua_sandbox.py +8 -9
- tactus/core/registry.py +19 -2
- tactus/core/runtime.py +235 -27
- tactus/docker/Dockerfile.pypi +49 -0
- tactus/docs/__init__.py +33 -0
- tactus/docs/extractor.py +326 -0
- tactus/docs/html_renderer.py +72 -0
- tactus/docs/models.py +121 -0
- tactus/docs/templates/base.html +204 -0
- tactus/docs/templates/index.html +58 -0
- tactus/docs/templates/module.html +96 -0
- tactus/dspy/agent.py +382 -22
- tactus/dspy/broker_lm.py +57 -6
- tactus/dspy/config.py +14 -3
- tactus/dspy/history.py +2 -1
- tactus/dspy/module.py +136 -11
- tactus/dspy/signature.py +0 -1
- tactus/ide/server.py +300 -9
- tactus/primitives/human.py +619 -47
- tactus/primitives/system.py +0 -1
- tactus/protocols/__init__.py +25 -0
- tactus/protocols/control.py +427 -0
- tactus/protocols/notification.py +207 -0
- tactus/sandbox/container_runner.py +79 -11
- tactus/sandbox/docker_manager.py +23 -0
- tactus/sandbox/entrypoint.py +26 -0
- tactus/sandbox/protocol.py +3 -0
- tactus/stdlib/README.md +77 -0
- tactus/stdlib/__init__.py +27 -1
- tactus/stdlib/classify/__init__.py +165 -0
- tactus/stdlib/classify/classify.spec.tac +195 -0
- tactus/stdlib/classify/classify.tac +257 -0
- tactus/stdlib/classify/fuzzy.py +282 -0
- tactus/stdlib/classify/llm.py +319 -0
- tactus/stdlib/classify/primitive.py +287 -0
- tactus/stdlib/core/__init__.py +57 -0
- tactus/stdlib/core/base.py +320 -0
- tactus/stdlib/core/confidence.py +211 -0
- tactus/stdlib/core/models.py +161 -0
- tactus/stdlib/core/retry.py +171 -0
- tactus/stdlib/core/validation.py +274 -0
- tactus/stdlib/extract/__init__.py +125 -0
- tactus/stdlib/extract/llm.py +330 -0
- tactus/stdlib/extract/primitive.py +256 -0
- tactus/stdlib/tac/tactus/classify/base.tac +51 -0
- tactus/stdlib/tac/tactus/classify/fuzzy.tac +87 -0
- tactus/stdlib/tac/tactus/classify/index.md +77 -0
- tactus/stdlib/tac/tactus/classify/init.tac +29 -0
- tactus/stdlib/tac/tactus/classify/llm.tac +150 -0
- tactus/stdlib/tac/tactus/classify.spec.tac +191 -0
- tactus/stdlib/tac/tactus/extract/base.tac +138 -0
- tactus/stdlib/tac/tactus/extract/index.md +96 -0
- tactus/stdlib/tac/tactus/extract/init.tac +27 -0
- tactus/stdlib/tac/tactus/extract/llm.tac +201 -0
- tactus/stdlib/tac/tactus/extract.spec.tac +153 -0
- tactus/stdlib/tac/tactus/generate/base.tac +142 -0
- tactus/stdlib/tac/tactus/generate/index.md +195 -0
- tactus/stdlib/tac/tactus/generate/init.tac +28 -0
- tactus/stdlib/tac/tactus/generate/llm.tac +169 -0
- tactus/stdlib/tac/tactus/generate.spec.tac +210 -0
- tactus/testing/behave_integration.py +171 -7
- tactus/testing/context.py +0 -1
- tactus/testing/evaluation_runner.py +0 -1
- tactus/testing/gherkin_parser.py +0 -1
- tactus/testing/mock_hitl.py +0 -1
- tactus/testing/mock_tools.py +0 -1
- tactus/testing/models.py +0 -1
- tactus/testing/steps/builtin.py +0 -1
- tactus/testing/steps/custom.py +81 -22
- tactus/testing/steps/registry.py +0 -1
- tactus/testing/test_runner.py +7 -1
- tactus/validation/semantic_visitor.py +11 -5
- tactus/validation/validator.py +0 -1
- {tactus-0.33.0.dist-info → tactus-0.34.0.dist-info}/METADATA +14 -2
- {tactus-0.33.0.dist-info → tactus-0.34.0.dist-info}/RECORD +100 -49
- {tactus-0.33.0.dist-info → tactus-0.34.0.dist-info}/WHEEL +0 -0
- {tactus-0.33.0.dist-info → tactus-0.34.0.dist-info}/entry_points.txt +0 -0
- {tactus-0.33.0.dist-info → tactus-0.34.0.dist-info}/licenses/LICENSE +0 -0
tactus/adapters/file_storage.py
CHANGED
|
@@ -82,6 +82,26 @@ class FileStorage:
|
|
|
82
82
|
except (IOError, OSError) as e:
|
|
83
83
|
raise RuntimeError(f"Failed to write procedure file {file_path}: {e}")
|
|
84
84
|
|
|
85
|
+
def _deserialize_result(self, result: Any) -> Any:
|
|
86
|
+
"""Deserialize checkpoint result, reconstructing Pydantic models."""
|
|
87
|
+
if result is None:
|
|
88
|
+
return None
|
|
89
|
+
# Check if result is a serialized Pydantic model
|
|
90
|
+
if isinstance(result, dict) and result.get("__pydantic__"):
|
|
91
|
+
from tactus.protocols.models import HITLResponse
|
|
92
|
+
|
|
93
|
+
model_name = result.get("__model__")
|
|
94
|
+
# Remove metadata fields
|
|
95
|
+
data = {k: v for k, v in result.items() if not k.startswith("__")}
|
|
96
|
+
# Reconstruct based on model name
|
|
97
|
+
if model_name == "HITLResponse":
|
|
98
|
+
# Need to parse datetime string back to datetime
|
|
99
|
+
if "responded_at" in data and isinstance(data["responded_at"], str):
|
|
100
|
+
data["responded_at"] = datetime.fromisoformat(data["responded_at"])
|
|
101
|
+
return HITLResponse(**data)
|
|
102
|
+
# Add other model types as needed
|
|
103
|
+
return result
|
|
104
|
+
|
|
85
105
|
def load_procedure_metadata(self, procedure_id: str) -> ProcedureMetadata:
|
|
86
106
|
"""Load procedure metadata from file."""
|
|
87
107
|
data = self._read_file(procedure_id)
|
|
@@ -102,7 +122,7 @@ class FileStorage:
|
|
|
102
122
|
CheckpointEntry(
|
|
103
123
|
position=entry_data["position"],
|
|
104
124
|
type=entry_data["type"],
|
|
105
|
-
result=entry_data["result"],
|
|
125
|
+
result=self._deserialize_result(entry_data["result"]),
|
|
106
126
|
timestamp=datetime.fromisoformat(entry_data["timestamp"]),
|
|
107
127
|
duration_ms=entry_data.get("duration_ms"),
|
|
108
128
|
input_hash=entry_data.get("input_hash"),
|
|
@@ -122,6 +142,19 @@ class FileStorage:
|
|
|
122
142
|
waiting_on_message_id=data.get("waiting_on_message_id"),
|
|
123
143
|
)
|
|
124
144
|
|
|
145
|
+
def _serialize_result(self, result: Any) -> Any:
|
|
146
|
+
"""Serialize checkpoint result, handling Pydantic models."""
|
|
147
|
+
if result is None:
|
|
148
|
+
return None
|
|
149
|
+
# Check if result is a Pydantic model (has model_dump method)
|
|
150
|
+
if hasattr(result, "model_dump"):
|
|
151
|
+
return {
|
|
152
|
+
"__pydantic__": True,
|
|
153
|
+
"__model__": result.__class__.__name__,
|
|
154
|
+
**result.model_dump(),
|
|
155
|
+
}
|
|
156
|
+
return result
|
|
157
|
+
|
|
125
158
|
def save_procedure_metadata(self, procedure_id: str, metadata: ProcedureMetadata) -> None:
|
|
126
159
|
"""Save procedure metadata to file."""
|
|
127
160
|
# Convert to serializable dict
|
|
@@ -131,7 +164,7 @@ class FileStorage:
|
|
|
131
164
|
{
|
|
132
165
|
"position": entry.position,
|
|
133
166
|
"type": entry.type,
|
|
134
|
-
"result": entry.result,
|
|
167
|
+
"result": self._serialize_result(entry.result),
|
|
135
168
|
"timestamp": entry.timestamp.isoformat(),
|
|
136
169
|
"duration_ms": entry.duration_ms,
|
|
137
170
|
"input_hash": entry.input_hash,
|
tactus/adapters/ide_log.py
CHANGED
|
@@ -21,6 +21,8 @@ class IDELogHandler:
|
|
|
21
21
|
for retrieval and streaming to the IDE frontend.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
supports_streaming = True
|
|
25
|
+
|
|
24
26
|
def __init__(self):
|
|
25
27
|
"""Initialize IDE log handler."""
|
|
26
28
|
self.events = queue.Queue()
|
|
@@ -34,6 +36,9 @@ class IDELogHandler:
|
|
|
34
36
|
Args:
|
|
35
37
|
event: Structured log event
|
|
36
38
|
"""
|
|
39
|
+
# CRITICAL DEBUG: Log every call to this method
|
|
40
|
+
logger.info(f"[IDE_LOG] log() called with event type: {type(event).__name__}")
|
|
41
|
+
|
|
37
42
|
# Track cost events for aggregation
|
|
38
43
|
from tactus.protocols.models import CostEvent, AgentStreamChunkEvent
|
|
39
44
|
|
|
@@ -47,7 +52,8 @@ class IDELogHandler:
|
|
|
47
52
|
)
|
|
48
53
|
|
|
49
54
|
self.events.put(event)
|
|
50
|
-
|
|
55
|
+
# Use INFO level to ensure we see this in logs
|
|
56
|
+
logger.info(
|
|
51
57
|
f"[IDE_LOG] Event queued: type={type(event).__name__}, queue_size={self.events.qsize()}"
|
|
52
58
|
)
|
|
53
59
|
|
tactus/backends/http_backend.py
CHANGED
tactus/broker/client.py
CHANGED
|
@@ -9,6 +9,7 @@ Uses a broker transport selected at runtime:
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import json
|
|
12
|
+
import logging
|
|
12
13
|
import os
|
|
13
14
|
import ssl
|
|
14
15
|
import sys
|
|
@@ -20,6 +21,8 @@ from typing import Any, AsyncIterator, Optional
|
|
|
20
21
|
from tactus.broker.protocol import read_message, write_message
|
|
21
22
|
from tactus.broker.stdio import STDIO_REQUEST_PREFIX, STDIO_TRANSPORT_VALUE
|
|
22
23
|
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
def _json_dumps(obj: Any) -> str:
|
|
25
28
|
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
|
|
@@ -162,7 +165,15 @@ class BrokerClient:
|
|
|
162
165
|
ssl_ctx.verify_mode = ssl.CERT_NONE
|
|
163
166
|
|
|
164
167
|
reader, writer = await asyncio.open_connection(host, port, ssl=ssl_ctx)
|
|
165
|
-
|
|
168
|
+
logger.info(
|
|
169
|
+
f"[BROKER_CLIENT] Writing message to broker, params keys: {list(params.keys())}"
|
|
170
|
+
)
|
|
171
|
+
try:
|
|
172
|
+
await write_message(writer, {"id": req_id, "method": method, "params": params})
|
|
173
|
+
except TypeError as e:
|
|
174
|
+
logger.error(f"[BROKER_CLIENT] JSON serialization error: {e}")
|
|
175
|
+
logger.error(f"[BROKER_CLIENT] Params: {params}")
|
|
176
|
+
raise
|
|
166
177
|
|
|
167
178
|
try:
|
|
168
179
|
while True:
|
|
@@ -207,6 +218,8 @@ class BrokerClient:
|
|
|
207
218
|
temperature: Optional[float] = None,
|
|
208
219
|
max_tokens: Optional[int] = None,
|
|
209
220
|
stream: bool,
|
|
221
|
+
tools: Optional[list[dict[str, Any]]] = None,
|
|
222
|
+
tool_choice: Optional[str] = None,
|
|
210
223
|
) -> AsyncIterator[dict[str, Any]]:
|
|
211
224
|
params: dict[str, Any] = {
|
|
212
225
|
"provider": provider,
|
|
@@ -218,6 +231,23 @@ class BrokerClient:
|
|
|
218
231
|
params["temperature"] = temperature
|
|
219
232
|
if max_tokens is not None:
|
|
220
233
|
params["max_tokens"] = max_tokens
|
|
234
|
+
if tools is not None:
|
|
235
|
+
params["tools"] = tools
|
|
236
|
+
import logging
|
|
237
|
+
|
|
238
|
+
logger = logging.getLogger(__name__)
|
|
239
|
+
logger.info(f"[BROKER_CLIENT] Adding {len(tools)} tools to params")
|
|
240
|
+
else:
|
|
241
|
+
import logging
|
|
242
|
+
|
|
243
|
+
logger = logging.getLogger(__name__)
|
|
244
|
+
logger.warning("[BROKER_CLIENT] No tools to add to params")
|
|
245
|
+
if tool_choice is not None:
|
|
246
|
+
params["tool_choice"] = tool_choice
|
|
247
|
+
import logging
|
|
248
|
+
|
|
249
|
+
logger = logging.getLogger(__name__)
|
|
250
|
+
logger.info(f"[BROKER_CLIENT] Adding tool_choice={tool_choice} to params")
|
|
221
251
|
return self._request("llm.chat", params)
|
|
222
252
|
|
|
223
253
|
async def call_tool(self, *, name: str, args: dict[str, Any]) -> Any:
|