tactus 0.31.2__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 +49 -0
- tactus/adapters/__init__.py +9 -0
- tactus/adapters/broker_log.py +76 -0
- tactus/adapters/cli_hitl.py +189 -0
- tactus/adapters/cli_log.py +223 -0
- tactus/adapters/cost_collector_log.py +56 -0
- tactus/adapters/file_storage.py +367 -0
- tactus/adapters/http_callback_log.py +109 -0
- tactus/adapters/ide_log.py +71 -0
- tactus/adapters/lua_tools.py +336 -0
- tactus/adapters/mcp.py +289 -0
- tactus/adapters/mcp_manager.py +196 -0
- tactus/adapters/memory.py +53 -0
- tactus/adapters/plugins.py +419 -0
- tactus/backends/http_backend.py +58 -0
- tactus/backends/model_backend.py +35 -0
- tactus/backends/pytorch_backend.py +110 -0
- tactus/broker/__init__.py +12 -0
- tactus/broker/client.py +247 -0
- tactus/broker/protocol.py +183 -0
- tactus/broker/server.py +1123 -0
- tactus/broker/stdio.py +12 -0
- tactus/cli/__init__.py +7 -0
- tactus/cli/app.py +2245 -0
- tactus/cli/commands/__init__.py +0 -0
- tactus/core/__init__.py +32 -0
- tactus/core/config_manager.py +790 -0
- tactus/core/dependencies/__init__.py +14 -0
- tactus/core/dependencies/registry.py +180 -0
- tactus/core/dsl_stubs.py +2117 -0
- tactus/core/exceptions.py +66 -0
- tactus/core/execution_context.py +480 -0
- tactus/core/lua_sandbox.py +508 -0
- tactus/core/message_history_manager.py +236 -0
- tactus/core/mocking.py +286 -0
- tactus/core/output_validator.py +291 -0
- tactus/core/registry.py +499 -0
- tactus/core/runtime.py +2907 -0
- tactus/core/template_resolver.py +142 -0
- tactus/core/yaml_parser.py +301 -0
- tactus/docker/Dockerfile +61 -0
- tactus/docker/entrypoint.sh +69 -0
- tactus/dspy/__init__.py +39 -0
- tactus/dspy/agent.py +1144 -0
- tactus/dspy/broker_lm.py +181 -0
- tactus/dspy/config.py +212 -0
- tactus/dspy/history.py +196 -0
- tactus/dspy/module.py +405 -0
- tactus/dspy/prediction.py +318 -0
- tactus/dspy/signature.py +185 -0
- tactus/formatting/__init__.py +7 -0
- tactus/formatting/formatter.py +437 -0
- tactus/ide/__init__.py +9 -0
- tactus/ide/coding_assistant.py +343 -0
- tactus/ide/server.py +2223 -0
- tactus/primitives/__init__.py +49 -0
- tactus/primitives/control.py +168 -0
- tactus/primitives/file.py +229 -0
- tactus/primitives/handles.py +378 -0
- tactus/primitives/host.py +94 -0
- tactus/primitives/human.py +342 -0
- tactus/primitives/json.py +189 -0
- tactus/primitives/log.py +187 -0
- tactus/primitives/message_history.py +157 -0
- tactus/primitives/model.py +163 -0
- tactus/primitives/procedure.py +564 -0
- tactus/primitives/procedure_callable.py +318 -0
- tactus/primitives/retry.py +155 -0
- tactus/primitives/session.py +152 -0
- tactus/primitives/state.py +182 -0
- tactus/primitives/step.py +209 -0
- tactus/primitives/system.py +93 -0
- tactus/primitives/tool.py +375 -0
- tactus/primitives/tool_handle.py +279 -0
- tactus/primitives/toolset.py +229 -0
- tactus/protocols/__init__.py +38 -0
- tactus/protocols/chat_recorder.py +81 -0
- tactus/protocols/config.py +97 -0
- tactus/protocols/cost.py +31 -0
- tactus/protocols/hitl.py +71 -0
- tactus/protocols/log_handler.py +27 -0
- tactus/protocols/models.py +355 -0
- tactus/protocols/result.py +33 -0
- tactus/protocols/storage.py +90 -0
- tactus/providers/__init__.py +13 -0
- tactus/providers/base.py +92 -0
- tactus/providers/bedrock.py +117 -0
- tactus/providers/google.py +105 -0
- tactus/providers/openai.py +98 -0
- tactus/sandbox/__init__.py +63 -0
- tactus/sandbox/config.py +171 -0
- tactus/sandbox/container_runner.py +1099 -0
- tactus/sandbox/docker_manager.py +433 -0
- tactus/sandbox/entrypoint.py +227 -0
- tactus/sandbox/protocol.py +213 -0
- tactus/stdlib/__init__.py +10 -0
- tactus/stdlib/io/__init__.py +13 -0
- tactus/stdlib/io/csv.py +88 -0
- tactus/stdlib/io/excel.py +136 -0
- tactus/stdlib/io/file.py +90 -0
- tactus/stdlib/io/fs.py +154 -0
- tactus/stdlib/io/hdf5.py +121 -0
- tactus/stdlib/io/json.py +109 -0
- tactus/stdlib/io/parquet.py +83 -0
- tactus/stdlib/io/tsv.py +88 -0
- tactus/stdlib/loader.py +274 -0
- tactus/stdlib/tac/tactus/tools/done.tac +33 -0
- tactus/stdlib/tac/tactus/tools/log.tac +50 -0
- tactus/testing/README.md +273 -0
- tactus/testing/__init__.py +61 -0
- tactus/testing/behave_integration.py +380 -0
- tactus/testing/context.py +486 -0
- tactus/testing/eval_models.py +114 -0
- tactus/testing/evaluation_runner.py +222 -0
- tactus/testing/evaluators.py +634 -0
- tactus/testing/events.py +94 -0
- tactus/testing/gherkin_parser.py +134 -0
- tactus/testing/mock_agent.py +315 -0
- tactus/testing/mock_dependencies.py +234 -0
- tactus/testing/mock_hitl.py +171 -0
- tactus/testing/mock_registry.py +168 -0
- tactus/testing/mock_tools.py +133 -0
- tactus/testing/models.py +115 -0
- tactus/testing/pydantic_eval_runner.py +508 -0
- tactus/testing/steps/__init__.py +13 -0
- tactus/testing/steps/builtin.py +902 -0
- tactus/testing/steps/custom.py +69 -0
- tactus/testing/steps/registry.py +68 -0
- tactus/testing/test_runner.py +489 -0
- tactus/tracing/__init__.py +5 -0
- tactus/tracing/trace_manager.py +417 -0
- tactus/utils/__init__.py +1 -0
- tactus/utils/cost_calculator.py +72 -0
- tactus/utils/model_pricing.py +132 -0
- tactus/utils/safe_file_library.py +502 -0
- tactus/utils/safe_libraries.py +234 -0
- tactus/validation/LuaLexerBase.py +66 -0
- tactus/validation/LuaParserBase.py +23 -0
- tactus/validation/README.md +224 -0
- tactus/validation/__init__.py +7 -0
- tactus/validation/error_listener.py +21 -0
- tactus/validation/generated/LuaLexer.interp +231 -0
- tactus/validation/generated/LuaLexer.py +5548 -0
- tactus/validation/generated/LuaLexer.tokens +124 -0
- tactus/validation/generated/LuaLexerBase.py +66 -0
- tactus/validation/generated/LuaParser.interp +173 -0
- tactus/validation/generated/LuaParser.py +6439 -0
- tactus/validation/generated/LuaParser.tokens +124 -0
- tactus/validation/generated/LuaParserBase.py +23 -0
- tactus/validation/generated/LuaParserVisitor.py +118 -0
- tactus/validation/generated/__init__.py +7 -0
- tactus/validation/grammar/LuaLexer.g4 +123 -0
- tactus/validation/grammar/LuaParser.g4 +178 -0
- tactus/validation/semantic_visitor.py +817 -0
- tactus/validation/validator.py +157 -0
- tactus-0.31.2.dist-info/METADATA +1809 -0
- tactus-0.31.2.dist-info/RECORD +160 -0
- tactus-0.31.2.dist-info/WHEEL +4 -0
- tactus-0.31.2.dist-info/entry_points.txt +2 -0
- tactus-0.31.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Pydantic models used across Tactus protocols.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def utc_now() -> datetime:
|
|
11
|
+
"""Return current UTC time as a timezone-aware datetime."""
|
|
12
|
+
return datetime.now(timezone.utc)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SourceLocation(BaseModel):
|
|
16
|
+
"""Source code location for a checkpoint."""
|
|
17
|
+
|
|
18
|
+
file: str = Field(..., description="Absolute path to .tac file")
|
|
19
|
+
line: int = Field(..., description="Line number (1-indexed)")
|
|
20
|
+
function: Optional[str] = Field(None, description="Function/procedure name")
|
|
21
|
+
code_context: Optional[str] = Field(None, description="3 lines of surrounding code")
|
|
22
|
+
|
|
23
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CheckpointEntry(BaseModel):
|
|
27
|
+
"""A single checkpoint entry in the execution log (position-based)."""
|
|
28
|
+
|
|
29
|
+
position: int = Field(..., description="Checkpoint position (0, 1, 2, ...)")
|
|
30
|
+
type: str = Field(
|
|
31
|
+
...,
|
|
32
|
+
description="Checkpoint type: agent_turn, model_predict, procedure_call, hitl_approval, explicit_checkpoint",
|
|
33
|
+
)
|
|
34
|
+
result: Any = Field(..., description="Result value from the checkpointed operation")
|
|
35
|
+
timestamp: datetime = Field(..., description="When checkpoint was created")
|
|
36
|
+
duration_ms: Optional[float] = Field(None, description="Operation duration in milliseconds")
|
|
37
|
+
input_hash: Optional[str] = Field(None, description="Hash of inputs for determinism checking")
|
|
38
|
+
run_id: Optional[str] = Field(
|
|
39
|
+
None, description="Unique identifier for the run that created this checkpoint"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# NEW: Debugging/tracing fields
|
|
43
|
+
source_location: Optional[SourceLocation] = Field(
|
|
44
|
+
None, description="Source code location where checkpoint was created"
|
|
45
|
+
)
|
|
46
|
+
captured_vars: Optional[Dict[str, Any]] = Field(
|
|
47
|
+
None, description="State snapshot at checkpoint"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ProcedureMetadata(BaseModel):
|
|
54
|
+
"""Complete metadata for a procedure run (position-based execution log)."""
|
|
55
|
+
|
|
56
|
+
procedure_id: str = Field(..., description="Unique procedure identifier")
|
|
57
|
+
execution_log: list[CheckpointEntry] = Field(
|
|
58
|
+
default_factory=list,
|
|
59
|
+
description="Position-based execution log (ordered list of checkpoints)",
|
|
60
|
+
)
|
|
61
|
+
replay_index: int = Field(
|
|
62
|
+
default=0, description="Current replay position (next checkpoint to execute)"
|
|
63
|
+
)
|
|
64
|
+
state: Dict[str, Any] = Field(default_factory=dict, description="Mutable state dictionary")
|
|
65
|
+
lua_state: Dict[str, Any] = Field(
|
|
66
|
+
default_factory=dict, description="Lua-specific state (preserved across execution)"
|
|
67
|
+
)
|
|
68
|
+
status: str = Field(
|
|
69
|
+
default="RUNNING",
|
|
70
|
+
description="Current procedure status (RUNNING, WAITING_FOR_HUMAN, COMPLETED, FAILED)",
|
|
71
|
+
)
|
|
72
|
+
waiting_on_message_id: Optional[str] = Field(
|
|
73
|
+
default=None, description="Message ID if procedure is waiting for human response"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class HITLResponse(BaseModel):
|
|
80
|
+
"""Response from a human interaction."""
|
|
81
|
+
|
|
82
|
+
value: Any = Field(..., description="The response value from the human")
|
|
83
|
+
responded_at: datetime = Field(..., description="When the human responded")
|
|
84
|
+
timed_out: bool = Field(default=False, description="Whether the response timed out")
|
|
85
|
+
|
|
86
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class HITLRequest(BaseModel):
|
|
90
|
+
"""Request for human interaction."""
|
|
91
|
+
|
|
92
|
+
request_type: str = Field(
|
|
93
|
+
..., description="Type of interaction: 'approval', 'input', 'review', 'escalation'"
|
|
94
|
+
)
|
|
95
|
+
message: str = Field(..., description="Message to display to the human")
|
|
96
|
+
timeout_seconds: Optional[int] = Field(
|
|
97
|
+
default=None, description="Timeout in seconds (None = wait forever)"
|
|
98
|
+
)
|
|
99
|
+
default_value: Any = Field(default=None, description="Default value to return on timeout")
|
|
100
|
+
options: Optional[list[Dict[str, Any]]] = Field(
|
|
101
|
+
default=None, description="Options for review requests (list of {label, type} dicts)"
|
|
102
|
+
)
|
|
103
|
+
metadata: Dict[str, Any] = Field(
|
|
104
|
+
default_factory=dict, description="Additional context and metadata"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class LogEvent(BaseModel):
|
|
111
|
+
"""A log event from procedure execution."""
|
|
112
|
+
|
|
113
|
+
event_type: str = Field(default="log", description="Event type identifier")
|
|
114
|
+
level: str = Field(..., description="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
|
115
|
+
message: str = Field(..., description="Log message")
|
|
116
|
+
context: Optional[Dict[str, Any]] = Field(None, description="Additional context")
|
|
117
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
118
|
+
logger_name: Optional[str] = Field(None, description="Logger name")
|
|
119
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
120
|
+
|
|
121
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AgentTurnEvent(BaseModel):
|
|
125
|
+
"""Event emitted when an agent turn starts or completes."""
|
|
126
|
+
|
|
127
|
+
event_type: str = Field(default="agent_turn", description="Event type")
|
|
128
|
+
agent_name: str = Field(..., description="Agent name")
|
|
129
|
+
stage: str = Field(..., description="Stage: 'started' or 'completed'")
|
|
130
|
+
duration_ms: Optional[float] = Field(None, description="Duration in ms (for completed stage)")
|
|
131
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
132
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
133
|
+
|
|
134
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class AgentStreamChunkEvent(BaseModel):
|
|
138
|
+
"""Event emitted for each chunk of streamed agent response."""
|
|
139
|
+
|
|
140
|
+
event_type: str = Field(default="agent_stream_chunk", description="Event type")
|
|
141
|
+
agent_name: str = Field(..., description="Agent name")
|
|
142
|
+
chunk_text: str = Field(..., description="Text chunk from this update")
|
|
143
|
+
accumulated_text: str = Field(..., description="Full text accumulated so far")
|
|
144
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
145
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
146
|
+
|
|
147
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class CostEvent(BaseModel):
|
|
151
|
+
"""Cost event for a single LLM call with comprehensive tracing data."""
|
|
152
|
+
|
|
153
|
+
event_type: str = Field(default="cost", description="Event type")
|
|
154
|
+
|
|
155
|
+
# Agent/Model Info
|
|
156
|
+
agent_name: str = Field(..., description="Agent that made the call")
|
|
157
|
+
model: str = Field(..., description="Model used")
|
|
158
|
+
provider: str = Field(..., description="Provider (openai, bedrock, etc.)")
|
|
159
|
+
|
|
160
|
+
# Token Usage (Primary Metrics)
|
|
161
|
+
prompt_tokens: int = Field(..., description="Prompt tokens used")
|
|
162
|
+
completion_tokens: int = Field(..., description="Completion tokens used")
|
|
163
|
+
total_tokens: int = Field(..., description="Total tokens")
|
|
164
|
+
|
|
165
|
+
# Cost Calculation (Primary Metrics)
|
|
166
|
+
prompt_cost: float = Field(..., description="Cost for prompt tokens")
|
|
167
|
+
completion_cost: float = Field(..., description="Cost for completion tokens")
|
|
168
|
+
total_cost: float = Field(..., description="Total cost")
|
|
169
|
+
|
|
170
|
+
# Performance Metrics (Details)
|
|
171
|
+
duration_ms: Optional[float] = Field(None, description="Call duration in milliseconds")
|
|
172
|
+
latency_ms: Optional[float] = Field(None, description="Time to first token (if available)")
|
|
173
|
+
|
|
174
|
+
# Retry/Validation Metrics (Details)
|
|
175
|
+
retry_count: int = Field(default=0, description="Number of retries due to validation")
|
|
176
|
+
validation_errors: list[str] = Field(
|
|
177
|
+
default_factory=list, description="Validation errors encountered"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Cache Metrics (Details)
|
|
181
|
+
cache_hit: bool = Field(default=False, description="Whether cache was used")
|
|
182
|
+
cache_tokens: Optional[int] = Field(None, description="Cached tokens used (if available)")
|
|
183
|
+
cache_cost: Optional[float] = Field(None, description="Cost saved via cache")
|
|
184
|
+
|
|
185
|
+
# Message Metrics (Details)
|
|
186
|
+
message_count: int = Field(default=0, description="Number of messages in conversation")
|
|
187
|
+
new_message_count: int = Field(default=0, description="New messages from this call")
|
|
188
|
+
|
|
189
|
+
# Request Metadata (Details)
|
|
190
|
+
request_id: Optional[str] = Field(None, description="Provider request ID (if available)")
|
|
191
|
+
model_version: Optional[str] = Field(None, description="Specific model version")
|
|
192
|
+
temperature: Optional[float] = Field(None, description="Temperature setting used")
|
|
193
|
+
max_tokens: Optional[int] = Field(None, description="Max tokens setting")
|
|
194
|
+
|
|
195
|
+
# Timestamps
|
|
196
|
+
timestamp: datetime = Field(default_factory=utc_now)
|
|
197
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
198
|
+
|
|
199
|
+
# Raw tracing data (for future analysis)
|
|
200
|
+
raw_tracing_data: Optional[Dict[str, Any]] = Field(
|
|
201
|
+
None, description="Any additional tracing data"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Response data (new field)
|
|
205
|
+
response_data: Optional[Dict[str, Any]] = Field(
|
|
206
|
+
None, description="Agent's response data (extracted from result.data)"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ExecutionSummaryEvent(BaseModel):
|
|
213
|
+
"""Summary event at the end of procedure execution."""
|
|
214
|
+
|
|
215
|
+
event_type: str = Field(default="execution_summary", description="Event type identifier")
|
|
216
|
+
result: Any = Field(..., description="Validated procedure result")
|
|
217
|
+
final_state: Dict[str, Any] = Field(default_factory=dict, description="Final state dictionary")
|
|
218
|
+
iterations: int = Field(default=0, description="Number of iterations executed")
|
|
219
|
+
tools_used: list[str] = Field(default_factory=list, description="List of tool names used")
|
|
220
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
221
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
222
|
+
|
|
223
|
+
# Cost tracking
|
|
224
|
+
total_cost: float = Field(default=0.0, description="Total LLM cost")
|
|
225
|
+
total_tokens: int = Field(default=0, description="Total tokens used")
|
|
226
|
+
cost_breakdown: list[Any] = Field(default_factory=list, description="Per-call cost details")
|
|
227
|
+
|
|
228
|
+
# Checkpoint tracking
|
|
229
|
+
checkpoint_count: int = Field(default=0, description="Total number of checkpoints")
|
|
230
|
+
checkpoint_types: Dict[str, int] = Field(
|
|
231
|
+
default_factory=dict, description="Checkpoint count by type"
|
|
232
|
+
)
|
|
233
|
+
checkpoint_duration_ms: Optional[float] = Field(None, description="Total checkpoint duration")
|
|
234
|
+
|
|
235
|
+
# Exit code and error information
|
|
236
|
+
exit_code: Optional[int] = Field(
|
|
237
|
+
default=0, description="Exit code (0 for success, non-zero for error)"
|
|
238
|
+
)
|
|
239
|
+
error_message: Optional[str] = Field(
|
|
240
|
+
default=None, description="Error message if execution failed"
|
|
241
|
+
)
|
|
242
|
+
error_type: Optional[str] = Field(
|
|
243
|
+
default=None, description="Error type/class name if execution failed"
|
|
244
|
+
)
|
|
245
|
+
traceback: Optional[str] = Field(default=None, description="Full traceback if execution failed")
|
|
246
|
+
|
|
247
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ToolCallEvent(BaseModel):
|
|
251
|
+
"""Event emitted when a tool is called by an agent."""
|
|
252
|
+
|
|
253
|
+
event_type: str = Field(default="tool_call", description="Event type")
|
|
254
|
+
agent_name: str = Field(..., description="Agent that called the tool")
|
|
255
|
+
tool_name: str = Field(..., description="Name of the tool called")
|
|
256
|
+
tool_args: Dict[str, Any] = Field(
|
|
257
|
+
default_factory=dict, description="Arguments passed to the tool"
|
|
258
|
+
)
|
|
259
|
+
tool_result: Any = Field(None, description="Result returned by the tool")
|
|
260
|
+
duration_ms: Optional[float] = Field(
|
|
261
|
+
None, description="Tool execution duration in milliseconds"
|
|
262
|
+
)
|
|
263
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
264
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
265
|
+
|
|
266
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class SystemAlertEvent(BaseModel):
|
|
270
|
+
"""Event emitted for non-blocking system alerts."""
|
|
271
|
+
|
|
272
|
+
event_type: str = Field(default="system_alert", description="Event type")
|
|
273
|
+
level: str = Field(..., description="Alert level: info, warning, error, critical")
|
|
274
|
+
message: str = Field(..., description="Alert message")
|
|
275
|
+
source: Optional[str] = Field(default=None, description="Alert source identifier")
|
|
276
|
+
context: Optional[Dict[str, Any]] = Field(default=None, description="Structured context data")
|
|
277
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
278
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
279
|
+
|
|
280
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class CheckpointCreatedEvent(BaseModel):
|
|
284
|
+
"""Event emitted when a checkpoint is created during execution."""
|
|
285
|
+
|
|
286
|
+
event_type: str = Field(default="checkpoint_created", description="Event type")
|
|
287
|
+
checkpoint_position: int = Field(..., description="Checkpoint position (0, 1, 2, ...)")
|
|
288
|
+
checkpoint_type: str = Field(
|
|
289
|
+
..., description="Checkpoint type (agent_turn, model_predict, etc.)"
|
|
290
|
+
)
|
|
291
|
+
duration_ms: Optional[float] = Field(None, description="Operation duration in milliseconds")
|
|
292
|
+
source_location: Optional[SourceLocation] = Field(None, description="Source code location")
|
|
293
|
+
timestamp: datetime = Field(default_factory=utc_now, description="Event timestamp")
|
|
294
|
+
procedure_id: Optional[str] = Field(None, description="Procedure identifier")
|
|
295
|
+
|
|
296
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class ChatMessage(BaseModel):
|
|
300
|
+
"""A message in a chat session."""
|
|
301
|
+
|
|
302
|
+
role: str = Field(..., description="Message role: USER, ASSISTANT, SYSTEM, TOOL")
|
|
303
|
+
content: str = Field(..., description="Message content")
|
|
304
|
+
message_type: str = Field(default="MESSAGE", description="Type of message")
|
|
305
|
+
tool_name: Optional[str] = Field(
|
|
306
|
+
default=None, description="Tool name if this is a tool message"
|
|
307
|
+
)
|
|
308
|
+
tool_parameters: Optional[Dict[str, Any]] = Field(
|
|
309
|
+
default=None, description="Tool call parameters"
|
|
310
|
+
)
|
|
311
|
+
tool_response: Optional[Dict[str, Any]] = Field(default=None, description="Tool response data")
|
|
312
|
+
parent_message_id: Optional[str] = Field(
|
|
313
|
+
default=None, description="Parent message ID for threading"
|
|
314
|
+
)
|
|
315
|
+
human_interaction: Optional[str] = Field(
|
|
316
|
+
default=None, description="Human interaction type (PENDING_APPROVAL, RESPONSE, etc.)"
|
|
317
|
+
)
|
|
318
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
319
|
+
default=None, description="Additional message metadata"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class Breakpoint(BaseModel):
|
|
326
|
+
"""A breakpoint set by the user for debugging."""
|
|
327
|
+
|
|
328
|
+
breakpoint_id: str = Field(..., description="Unique breakpoint identifier")
|
|
329
|
+
file: str = Field(..., description="File path where breakpoint is set")
|
|
330
|
+
line: int = Field(..., description="Line number (1-indexed)")
|
|
331
|
+
condition: Optional[str] = Field(None, description="Python expression to evaluate")
|
|
332
|
+
enabled: bool = Field(default=True, description="Whether breakpoint is active")
|
|
333
|
+
hit_count: int = Field(default=0, description="Number of times breakpoint has been hit")
|
|
334
|
+
|
|
335
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ExecutionRun(BaseModel):
|
|
339
|
+
"""A complete procedure execution with tracing data."""
|
|
340
|
+
|
|
341
|
+
run_id: str = Field(..., description="Unique run identifier")
|
|
342
|
+
procedure_name: str = Field(..., description="Name of the procedure")
|
|
343
|
+
file_path: str = Field(..., description="Path to the .tac file")
|
|
344
|
+
start_time: datetime = Field(..., description="When execution started")
|
|
345
|
+
end_time: Optional[datetime] = Field(None, description="When execution completed")
|
|
346
|
+
status: str = Field(..., description="RUNNING, PAUSED, COMPLETED, FAILED")
|
|
347
|
+
execution_log: list[CheckpointEntry] = Field(
|
|
348
|
+
default_factory=list, description="All checkpoints from this run"
|
|
349
|
+
)
|
|
350
|
+
final_state: Dict[str, Any] = Field(default_factory=dict, description="Final state dictionary")
|
|
351
|
+
breakpoints: list[Breakpoint] = Field(
|
|
352
|
+
default_factory=list, description="Breakpoints for this run"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Result object returned by cost-incurring primitives (e.g., Agents).
|
|
3
|
+
|
|
4
|
+
Standardizes on `result.output` for the returned data (string or structured).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from tactus.protocols.cost import UsageStats, CostStats
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TactusResult(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
Standard Result wrapper for Lua and Python consumption.
|
|
19
|
+
|
|
20
|
+
- `output`: The returned data (string or structured dict/list/etc.)
|
|
21
|
+
- `usage`: Token usage stats for the call that produced this result
|
|
22
|
+
- `cost_stats`: Cost stats for the call that produced this result
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
output: Any = Field(..., description="Result output (string or structured data)")
|
|
26
|
+
usage: UsageStats = Field(default_factory=UsageStats)
|
|
27
|
+
cost_stats: CostStats = Field(default_factory=CostStats)
|
|
28
|
+
|
|
29
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
30
|
+
|
|
31
|
+
def cost(self) -> CostStats:
|
|
32
|
+
"""Return cost statistics for this result."""
|
|
33
|
+
return self.cost_stats
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage backend protocol for Tactus.
|
|
3
|
+
|
|
4
|
+
Defines the interface for persisting procedure state, execution log, and metadata.
|
|
5
|
+
Implementations can use any storage backend (memory, files, databases, etc.).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Protocol, Optional, Any
|
|
9
|
+
from tactus.protocols.models import ProcedureMetadata
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StorageBackend(Protocol):
|
|
13
|
+
"""
|
|
14
|
+
Protocol for storage backends.
|
|
15
|
+
|
|
16
|
+
Implementations provide persistence for procedure state and execution log.
|
|
17
|
+
This allows Tactus to work with any storage system (memory, files, databases, etc.).
|
|
18
|
+
|
|
19
|
+
Position-based checkpointing: All checkpoints are stored in ProcedureMetadata.execution_log
|
|
20
|
+
as an ordered list. No named checkpoint methods needed.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def load_procedure_metadata(self, procedure_id: str) -> ProcedureMetadata:
|
|
24
|
+
"""
|
|
25
|
+
Load complete procedure metadata from storage.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
procedure_id: Unique procedure identifier
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ProcedureMetadata with execution_log, state, replay_index, and status
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
StorageError: If loading fails
|
|
35
|
+
"""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
def save_procedure_metadata(self, procedure_id: str, metadata: ProcedureMetadata) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Save complete procedure metadata to storage.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
procedure_id: Unique procedure identifier
|
|
44
|
+
metadata: ProcedureMetadata to persist (includes execution_log)
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
StorageError: If saving fails
|
|
48
|
+
"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def update_procedure_status(
|
|
52
|
+
self, procedure_id: str, status: str, waiting_on_message_id: Optional[str] = None
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Update procedure status (and optionally waiting message ID).
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
procedure_id: Unique procedure identifier
|
|
59
|
+
status: New status (RUNNING, WAITING_FOR_HUMAN, COMPLETED, FAILED)
|
|
60
|
+
waiting_on_message_id: Optional message ID if waiting for human
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
StorageError: If update fails
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def get_state(self, procedure_id: str) -> dict[str, Any]:
|
|
68
|
+
"""
|
|
69
|
+
Get mutable state dictionary.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
procedure_id: Unique procedure identifier
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
State dictionary
|
|
76
|
+
"""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
def set_state(self, procedure_id: str, state: dict[str, Any]) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Set mutable state dictionary.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
procedure_id: Unique procedure identifier
|
|
85
|
+
state: State dictionary to save
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
StorageError: If saving fails
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider abstraction layer for LLM providers.
|
|
3
|
+
|
|
4
|
+
This module provides abstractions for different LLM providers (OpenAI, Bedrock, Google, etc.)
|
|
5
|
+
to enable multi-provider support in Tactus.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from tactus.providers.base import ProviderConfig
|
|
9
|
+
from tactus.providers.openai import OpenAIProvider
|
|
10
|
+
from tactus.providers.bedrock import BedrockProvider
|
|
11
|
+
from tactus.providers.google import GoogleProvider
|
|
12
|
+
|
|
13
|
+
__all__ = ["ProviderConfig", "OpenAIProvider", "BedrockProvider", "GoogleProvider"]
|
tactus/providers/base.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base provider configuration protocol.
|
|
3
|
+
|
|
4
|
+
Defines the interface that all LLM providers must implement.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Protocol, Optional, Dict, Any
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ProviderConfig:
|
|
13
|
+
"""
|
|
14
|
+
Configuration for an LLM provider.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
provider_name: Name of the provider (e.g., 'openai', 'bedrock')
|
|
18
|
+
model_id: Model identifier within the provider
|
|
19
|
+
credentials: Optional credentials dict (API keys, etc.)
|
|
20
|
+
region: Optional region for cloud providers
|
|
21
|
+
additional_config: Any additional provider-specific configuration
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
provider_name: str
|
|
25
|
+
model_id: str
|
|
26
|
+
credentials: Optional[Dict[str, str]] = None
|
|
27
|
+
region: Optional[str] = None
|
|
28
|
+
additional_config: Optional[Dict[str, Any]] = None
|
|
29
|
+
|
|
30
|
+
def get_model_string(self) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Get the model string for pydantic-ai.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Model string in format 'provider:model_id'
|
|
36
|
+
"""
|
|
37
|
+
return f"{self.provider_name}:{self.model_id}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Provider(Protocol):
|
|
41
|
+
"""
|
|
42
|
+
Protocol for LLM provider implementations.
|
|
43
|
+
|
|
44
|
+
Each provider must implement methods to:
|
|
45
|
+
- Validate configuration
|
|
46
|
+
- Get model string for pydantic-ai
|
|
47
|
+
- Check if credentials are available
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def validate_model(model_id: str) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Validate that a model ID is valid for this provider.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
model_id: The model identifier to validate
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if valid, False otherwise
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def get_required_credentials() -> list[str]:
|
|
65
|
+
"""
|
|
66
|
+
Get list of required credential keys for this provider.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of environment variable names needed
|
|
70
|
+
"""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def create_config(
|
|
75
|
+
model_id: str,
|
|
76
|
+
credentials: Optional[Dict[str, str]] = None,
|
|
77
|
+
region: Optional[str] = None,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> ProviderConfig:
|
|
80
|
+
"""
|
|
81
|
+
Create a provider configuration.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
model_id: Model identifier
|
|
85
|
+
credentials: Optional credentials dict
|
|
86
|
+
region: Optional region
|
|
87
|
+
**kwargs: Additional provider-specific config
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
ProviderConfig instance
|
|
91
|
+
"""
|
|
92
|
+
...
|