tactus 0.36.0__py3-none-any.whl → 0.37.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/channels/base.py +20 -2
- tactus/adapters/channels/broker.py +1 -0
- tactus/adapters/channels/host.py +3 -1
- tactus/adapters/channels/ipc.py +18 -3
- tactus/adapters/channels/sse.py +2 -0
- tactus/adapters/mcp_manager.py +24 -7
- tactus/backends/http_backend.py +2 -2
- tactus/backends/pytorch_backend.py +2 -2
- tactus/broker/client.py +3 -3
- tactus/broker/server.py +17 -5
- tactus/core/dsl_stubs.py +3 -3
- tactus/core/execution_context.py +23 -23
- tactus/core/message_history_manager.py +2 -2
- tactus/core/output_validator.py +6 -6
- tactus/core/registry.py +29 -29
- tactus/core/runtime.py +44 -44
- tactus/dspy/broker_lm.py +13 -7
- tactus/dspy/config.py +7 -4
- tactus/primitives/model.py +1 -1
- tactus/primitives/procedure.py +1 -1
- tactus/primitives/state.py +2 -2
- tactus/sandbox/container_runner.py +11 -6
- tactus/testing/context.py +6 -6
- tactus/testing/evaluation_runner.py +5 -5
- tactus/testing/steps/builtin.py +2 -2
- tactus/testing/test_runner.py +6 -4
- tactus/utils/asyncio_helpers.py +2 -1
- {tactus-0.36.0.dist-info → tactus-0.37.0.dist-info}/METADATA +7 -5
- {tactus-0.36.0.dist-info → tactus-0.37.0.dist-info}/RECORD +33 -33
- {tactus-0.36.0.dist-info → tactus-0.37.0.dist-info}/WHEEL +0 -0
- {tactus-0.36.0.dist-info → tactus-0.37.0.dist-info}/entry_points.txt +0 -0
- {tactus-0.36.0.dist-info → tactus-0.37.0.dist-info}/licenses/LICENSE +0 -0
tactus/core/registry.py
CHANGED
|
@@ -6,7 +6,7 @@ procedure declarations from .tac files.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Dict, Optional, Union
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field, ValidationError, ConfigDict
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ class OutputFieldDeclaration(BaseModel):
|
|
|
19
19
|
name: str
|
|
20
20
|
field_type: str = Field(alias="type") # string, number, boolean, array, object
|
|
21
21
|
required: bool = False
|
|
22
|
-
description: str
|
|
22
|
+
description: Optional[str] = None
|
|
23
23
|
|
|
24
24
|
model_config = ConfigDict(populate_by_name=True)
|
|
25
25
|
|
|
@@ -31,7 +31,7 @@ class MessageHistoryConfiguration(BaseModel):
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
source: str = "own" # "own", "shared", or another agent's name
|
|
34
|
-
filter: Any
|
|
34
|
+
filter: Optional[Any] = None # Lua function reference or filter name
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class AgentOutputSchema(BaseModel):
|
|
@@ -44,21 +44,21 @@ class AgentDeclaration(BaseModel):
|
|
|
44
44
|
"""Agent declaration from DSL."""
|
|
45
45
|
|
|
46
46
|
name: str
|
|
47
|
-
provider: str
|
|
48
|
-
model: str
|
|
49
|
-
system_prompt: str
|
|
50
|
-
initial_message: str
|
|
47
|
+
provider: Optional[str] = None
|
|
48
|
+
model: Union[str, Dict[str, Any]] = "gpt-4o"
|
|
49
|
+
system_prompt: Union[str, Any] # String with {markers} or Lua function
|
|
50
|
+
initial_message: Optional[str] = None
|
|
51
51
|
tools: list[Any] = Field(default_factory=list) # Tool/toolset references and expressions
|
|
52
52
|
inline_tools: list[dict[str, Any]] = Field(default_factory=list) # Inline tool definitions
|
|
53
|
-
output: AgentOutputSchema
|
|
54
|
-
message_history: MessageHistoryConfiguration
|
|
53
|
+
output: Optional[AgentOutputSchema] = None # Aligned with pydantic-ai
|
|
54
|
+
message_history: Optional[MessageHistoryConfiguration] = None
|
|
55
55
|
max_turns: int = 50
|
|
56
56
|
disable_streaming: bool = (
|
|
57
57
|
False # Disable streaming for models that don't support tools in streaming mode
|
|
58
58
|
)
|
|
59
|
-
temperature: float
|
|
60
|
-
max_tokens: int
|
|
61
|
-
model_type: str
|
|
59
|
+
temperature: Optional[float] = None
|
|
60
|
+
max_tokens: Optional[int] = None
|
|
61
|
+
model_type: Optional[str] = None # e.g., "chat", "responses" for reasoning models
|
|
62
62
|
|
|
63
63
|
model_config = ConfigDict(extra="allow")
|
|
64
64
|
|
|
@@ -69,9 +69,9 @@ class HITLDeclaration(BaseModel):
|
|
|
69
69
|
name: str
|
|
70
70
|
hitl_type: str = Field(alias="type") # approval, input, review
|
|
71
71
|
message: str
|
|
72
|
-
timeout: int
|
|
72
|
+
timeout: Optional[int] = None
|
|
73
73
|
default: Any = None
|
|
74
|
-
options: list[dict[str, Any]]
|
|
74
|
+
options: Optional[list[dict[str, Any]]] = None
|
|
75
75
|
|
|
76
76
|
model_config = ConfigDict(populate_by_name=True)
|
|
77
77
|
|
|
@@ -81,9 +81,9 @@ class ScenarioDeclaration(BaseModel):
|
|
|
81
81
|
|
|
82
82
|
name: str
|
|
83
83
|
given: dict[str, Any] = Field(default_factory=dict)
|
|
84
|
-
when: str
|
|
85
|
-
then_output:
|
|
86
|
-
then_state:
|
|
84
|
+
when: Optional[str] = None # defaults to "procedure_completes"
|
|
85
|
+
then_output: Optional[Dict[str, Any]] = None
|
|
86
|
+
then_state: Optional[Dict[str, Any]] = None
|
|
87
87
|
mocks: dict[str, Any] = Field(default_factory=dict) # tool_name -> response
|
|
88
88
|
|
|
89
89
|
|
|
@@ -134,7 +134,7 @@ class ProcedureRegistry(BaseModel):
|
|
|
134
134
|
model_config = {"arbitrary_types_allowed": True}
|
|
135
135
|
|
|
136
136
|
# Metadata
|
|
137
|
-
description: str
|
|
137
|
+
description: Optional[str] = None
|
|
138
138
|
|
|
139
139
|
# Declarations
|
|
140
140
|
input_schema: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -154,26 +154,26 @@ class ProcedureRegistry(BaseModel):
|
|
|
154
154
|
message_history_config: dict[str, Any] = Field(default_factory=dict)
|
|
155
155
|
|
|
156
156
|
# Gherkin BDD Testing
|
|
157
|
-
gherkin_specifications: str
|
|
157
|
+
gherkin_specifications: Optional[str] = None # Raw Gherkin text
|
|
158
158
|
specs_from_references: list[str] = Field(default_factory=list) # External spec file paths
|
|
159
159
|
custom_steps: dict[str, Any] = Field(default_factory=dict) # step_text -> lua_function
|
|
160
160
|
evaluation_config: dict[str, Any] = Field(default_factory=dict) # runs, parallel, etc.
|
|
161
161
|
|
|
162
162
|
# Pydantic Evals Integration
|
|
163
|
-
pydantic_evaluations:
|
|
163
|
+
pydantic_evaluations: Optional[Dict[str, Any]] = None # Pydantic Evals configuration
|
|
164
164
|
|
|
165
165
|
# Prompts
|
|
166
166
|
prompts: dict[str, str] = Field(default_factory=dict)
|
|
167
|
-
return_prompt: str
|
|
168
|
-
error_prompt: str
|
|
169
|
-
status_prompt: str
|
|
167
|
+
return_prompt: Optional[str] = None
|
|
168
|
+
error_prompt: Optional[str] = None
|
|
169
|
+
status_prompt: Optional[str] = None
|
|
170
170
|
|
|
171
171
|
# Execution settings
|
|
172
172
|
async_enabled: bool = False
|
|
173
173
|
max_depth: int = 5
|
|
174
174
|
max_turns: int = 50
|
|
175
|
-
default_provider: str
|
|
176
|
-
default_model: str
|
|
175
|
+
default_provider: Optional[str] = None
|
|
176
|
+
default_model: Optional[str] = None
|
|
177
177
|
|
|
178
178
|
# Named procedures (for in-file sub-procedures)
|
|
179
179
|
named_procedures: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
|
@@ -193,8 +193,8 @@ class ValidationMessage(BaseModel):
|
|
|
193
193
|
|
|
194
194
|
level: str # "error" or "warning"
|
|
195
195
|
message: str
|
|
196
|
-
location: tuple[int, int]
|
|
197
|
-
declaration: str
|
|
196
|
+
location: Optional[tuple[int, int]] = None
|
|
197
|
+
declaration: Optional[str] = None
|
|
198
198
|
|
|
199
199
|
|
|
200
200
|
class ValidationResult(BaseModel):
|
|
@@ -203,7 +203,7 @@ class ValidationResult(BaseModel):
|
|
|
203
203
|
valid: bool
|
|
204
204
|
errors: list[ValidationMessage] = Field(default_factory=list)
|
|
205
205
|
warnings: list[ValidationMessage] = Field(default_factory=list)
|
|
206
|
-
registry: ProcedureRegistry
|
|
206
|
+
registry: Optional["ProcedureRegistry"] = None
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
class RegistryBuilder:
|
|
@@ -229,7 +229,7 @@ class RegistryBuilder:
|
|
|
229
229
|
self,
|
|
230
230
|
name: str,
|
|
231
231
|
config: dict,
|
|
232
|
-
output_schema: dict
|
|
232
|
+
output_schema: Optional[dict] = None,
|
|
233
233
|
) -> None:
|
|
234
234
|
"""Register an agent declaration."""
|
|
235
235
|
agent_config = dict(config)
|
tactus/core/runtime.py
CHANGED
|
@@ -13,7 +13,7 @@ import io
|
|
|
13
13
|
import logging
|
|
14
14
|
import time
|
|
15
15
|
import uuid
|
|
16
|
-
from typing import Any
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
17
|
|
|
18
18
|
from tactus.core.registry import ProcedureRegistry, RegistryBuilder
|
|
19
19
|
from tactus.core.dsl_stubs import create_dsl_stubs, lua_table_to_dict
|
|
@@ -68,19 +68,19 @@ class TactusRuntime:
|
|
|
68
68
|
def __init__(
|
|
69
69
|
self,
|
|
70
70
|
procedure_id: str,
|
|
71
|
-
storage_backend: StorageBackend
|
|
72
|
-
hitl_handler: HITLHandler
|
|
73
|
-
chat_recorder: ChatRecorder
|
|
71
|
+
storage_backend: Optional[StorageBackend] = None,
|
|
72
|
+
hitl_handler: Optional[HITLHandler] = None,
|
|
73
|
+
chat_recorder: Optional[ChatRecorder] = None,
|
|
74
74
|
mcp_server=None,
|
|
75
|
-
mcp_servers:
|
|
76
|
-
openai_api_key: str
|
|
75
|
+
mcp_servers: Optional[Dict[str, Any]] = None,
|
|
76
|
+
openai_api_key: Optional[str] = None,
|
|
77
77
|
log_handler=None,
|
|
78
|
-
tool_primitive: ToolPrimitive
|
|
78
|
+
tool_primitive: Optional[ToolPrimitive] = None,
|
|
79
79
|
recursion_depth: int = 0,
|
|
80
|
-
tool_paths:
|
|
81
|
-
external_config:
|
|
82
|
-
run_id: str
|
|
83
|
-
source_file_path: str
|
|
80
|
+
tool_paths: Optional[List[str]] = None,
|
|
81
|
+
external_config: Optional[Dict[str, Any]] = None,
|
|
82
|
+
run_id: Optional[str] = None,
|
|
83
|
+
source_file_path: Optional[str] = None,
|
|
84
84
|
):
|
|
85
85
|
"""
|
|
86
86
|
Initialize the Tactus runtime.
|
|
@@ -144,31 +144,31 @@ class TactusRuntime:
|
|
|
144
144
|
self.source_file_path = source_file_path
|
|
145
145
|
|
|
146
146
|
# Will be initialized during setup
|
|
147
|
-
self.config:
|
|
148
|
-
self.registry: ProcedureRegistry
|
|
149
|
-
self.lua_sandbox: LuaSandbox
|
|
150
|
-
self.output_validator: OutputValidator
|
|
151
|
-
self.template_resolver: TemplateResolver
|
|
152
|
-
self.message_history_manager: MessageHistoryManager
|
|
147
|
+
self.config: Optional[Dict[str, Any]] = None # Legacy YAML support
|
|
148
|
+
self.registry: Optional[ProcedureRegistry] = None # New DSL registry
|
|
149
|
+
self.lua_sandbox: Optional[LuaSandbox] = None
|
|
150
|
+
self.output_validator: Optional[OutputValidator] = None
|
|
151
|
+
self.template_resolver: Optional[TemplateResolver] = None
|
|
152
|
+
self.message_history_manager: Optional[MessageHistoryManager] = None
|
|
153
153
|
|
|
154
154
|
# Execution context
|
|
155
|
-
self.execution_context: BaseExecutionContext
|
|
155
|
+
self.execution_context: Optional[BaseExecutionContext] = None
|
|
156
156
|
|
|
157
157
|
# Primitives (shared across all agents)
|
|
158
|
-
self.state_primitive: StatePrimitive
|
|
159
|
-
self.iterations_primitive: IterationsPrimitive
|
|
160
|
-
self.stop_primitive: StopPrimitive
|
|
161
|
-
self.tool_primitive: ToolPrimitive
|
|
162
|
-
self.human_primitive: HumanPrimitive
|
|
163
|
-
self.step_primitive: StepPrimitive
|
|
164
|
-
self.checkpoint_primitive: CheckpointPrimitive
|
|
165
|
-
self.log_primitive: LogPrimitive
|
|
166
|
-
self.json_primitive: JsonPrimitive
|
|
167
|
-
self.retry_primitive: RetryPrimitive
|
|
168
|
-
self.file_primitive: FilePrimitive
|
|
169
|
-
self.procedure_primitive: ProcedurePrimitive
|
|
170
|
-
self.system_primitive: SystemPrimitive
|
|
171
|
-
self.host_primitive: HostPrimitive
|
|
158
|
+
self.state_primitive: Optional[StatePrimitive] = None
|
|
159
|
+
self.iterations_primitive: Optional[IterationsPrimitive] = None
|
|
160
|
+
self.stop_primitive: Optional[StopPrimitive] = None
|
|
161
|
+
self.tool_primitive: Optional[ToolPrimitive] = None
|
|
162
|
+
self.human_primitive: Optional[HumanPrimitive] = None
|
|
163
|
+
self.step_primitive: Optional[StepPrimitive] = None
|
|
164
|
+
self.checkpoint_primitive: Optional[CheckpointPrimitive] = None
|
|
165
|
+
self.log_primitive: Optional[LogPrimitive] = None
|
|
166
|
+
self.json_primitive: Optional[JsonPrimitive] = None
|
|
167
|
+
self.retry_primitive: Optional[RetryPrimitive] = None
|
|
168
|
+
self.file_primitive: Optional[FilePrimitive] = None
|
|
169
|
+
self.procedure_primitive: Optional[ProcedurePrimitive] = None
|
|
170
|
+
self.system_primitive: Optional[SystemPrimitive] = None
|
|
171
|
+
self.host_primitive: Optional[HostPrimitive] = None
|
|
172
172
|
|
|
173
173
|
# Agent primitives (one per agent)
|
|
174
174
|
self.agents: dict[str, Any] = {}
|
|
@@ -181,17 +181,17 @@ class TactusRuntime:
|
|
|
181
181
|
|
|
182
182
|
# User dependencies (HTTP clients, DB connections, etc.)
|
|
183
183
|
self.user_dependencies: dict[str, Any] = {}
|
|
184
|
-
self.dependency_manager: Any
|
|
184
|
+
self.dependency_manager: Optional[Any] = None # ResourceManager for cleanup
|
|
185
185
|
|
|
186
186
|
# Mock manager for testing
|
|
187
|
-
self.mock_manager: Any
|
|
188
|
-
self.external_agent_mocks:
|
|
187
|
+
self.mock_manager: Optional[Any] = None # MockManager instance
|
|
188
|
+
self.external_agent_mocks: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
189
189
|
self.mock_all_agents: bool = False
|
|
190
190
|
|
|
191
191
|
logger.info("TactusRuntime initialized for procedure %s", procedure_id)
|
|
192
192
|
|
|
193
193
|
async def execute(
|
|
194
|
-
self, source: str, context:
|
|
194
|
+
self, source: str, context: Optional[Dict[str, Any]] = None, format: str = "yaml"
|
|
195
195
|
) -> dict[str, Any]:
|
|
196
196
|
"""
|
|
197
197
|
Execute a workflow (Lua DSL or legacy YAML format).
|
|
@@ -772,7 +772,7 @@ class TactusRuntime:
|
|
|
772
772
|
except Exception as e:
|
|
773
773
|
logger.warning("Error cleaning up dependencies: %s", e)
|
|
774
774
|
|
|
775
|
-
def _resolve_sandbox_base_path(self) -> str
|
|
775
|
+
def _resolve_sandbox_base_path(self) -> Optional[str]:
|
|
776
776
|
# Compute base_path for sandbox from source file path if available.
|
|
777
777
|
# This ensures require() works correctly even when running from different directories.
|
|
778
778
|
if not self.source_file_path:
|
|
@@ -798,7 +798,7 @@ class TactusRuntime:
|
|
|
798
798
|
|
|
799
799
|
async def _initialize_primitives(
|
|
800
800
|
self,
|
|
801
|
-
placeholder_tool: ToolPrimitive
|
|
801
|
+
placeholder_tool: Optional[ToolPrimitive] = None,
|
|
802
802
|
):
|
|
803
803
|
"""Initialize all primitive objects.
|
|
804
804
|
|
|
@@ -836,7 +836,7 @@ class TactusRuntime:
|
|
|
836
836
|
|
|
837
837
|
logger.debug("All primitives initialized")
|
|
838
838
|
|
|
839
|
-
def resolve_toolset(self, name: str) -> Any
|
|
839
|
+
def resolve_toolset(self, name: str) -> Optional[Any]:
|
|
840
840
|
"""
|
|
841
841
|
Resolve a toolset by name from runtime's registered toolsets.
|
|
842
842
|
|
|
@@ -1047,7 +1047,7 @@ class TactusRuntime:
|
|
|
1047
1047
|
for name, toolset in self.toolset_registry.items():
|
|
1048
1048
|
logger.debug(f" - {name}: {type(toolset)} -> {toolset}")
|
|
1049
1049
|
|
|
1050
|
-
async def _resolve_tool_source(self, tool_name: str, source: str) -> Any
|
|
1050
|
+
async def _resolve_tool_source(self, tool_name: str, source: str) -> Optional[Any]:
|
|
1051
1051
|
"""
|
|
1052
1052
|
Resolve a tool from an external source.
|
|
1053
1053
|
|
|
@@ -1442,7 +1442,7 @@ class TactusRuntime:
|
|
|
1442
1442
|
|
|
1443
1443
|
async def _create_toolset_from_config(
|
|
1444
1444
|
self, name: str, definition: dict[str, Any]
|
|
1445
|
-
) -> Any
|
|
1445
|
+
) -> Optional[Any]:
|
|
1446
1446
|
"""
|
|
1447
1447
|
Create toolset from YAML config definition.
|
|
1448
1448
|
|
|
@@ -2194,7 +2194,7 @@ class TactusRuntime:
|
|
|
2194
2194
|
if is_required:
|
|
2195
2195
|
fields[field_name] = (field_type, ...) # Required field
|
|
2196
2196
|
else:
|
|
2197
|
-
fields[field_name] = (field_type
|
|
2197
|
+
fields[field_name] = (Optional[field_type], None) # Optional field
|
|
2198
2198
|
|
|
2199
2199
|
return create_model(model_name, **fields)
|
|
2200
2200
|
|
|
@@ -2652,7 +2652,7 @@ class TactusRuntime:
|
|
|
2652
2652
|
in_body = False
|
|
2653
2653
|
brace_depth = 0
|
|
2654
2654
|
function_depth = 0 # Track function...end blocks
|
|
2655
|
-
long_string_eq: str
|
|
2655
|
+
long_string_eq: Optional[str] = None
|
|
2656
2656
|
|
|
2657
2657
|
decl_start = re.compile(
|
|
2658
2658
|
r"^\s*(?:"
|
|
@@ -2871,7 +2871,7 @@ class TactusRuntime:
|
|
|
2871
2871
|
return False
|
|
2872
2872
|
|
|
2873
2873
|
def _parse_declarations(
|
|
2874
|
-
self, source: str, tool_primitive: ToolPrimitive
|
|
2874
|
+
self, source: str, tool_primitive: Optional[ToolPrimitive] = None
|
|
2875
2875
|
) -> ProcedureRegistry:
|
|
2876
2876
|
"""
|
|
2877
2877
|
Execute .tac to collect declarations.
|
tactus/dspy/broker_lm.py
CHANGED
|
@@ -11,7 +11,7 @@ while still supporting streaming via DSPy's `streamify()` mechanism.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
15
|
|
|
16
16
|
import dspy
|
|
17
17
|
import litellm
|
|
@@ -42,10 +42,10 @@ class BrokeredLM(dspy.BaseLM):
|
|
|
42
42
|
model: str,
|
|
43
43
|
*,
|
|
44
44
|
model_type: str = "chat",
|
|
45
|
-
temperature: float
|
|
46
|
-
max_tokens: int
|
|
47
|
-
cache: bool
|
|
48
|
-
socket_path: str
|
|
45
|
+
temperature: Optional[float] = None,
|
|
46
|
+
max_tokens: Optional[int] = None,
|
|
47
|
+
cache: Optional[bool] = None,
|
|
48
|
+
socket_path: Optional[str] = None,
|
|
49
49
|
**kwargs: Any,
|
|
50
50
|
):
|
|
51
51
|
if model_type != "chat":
|
|
@@ -70,12 +70,18 @@ class BrokeredLM(dspy.BaseLM):
|
|
|
70
70
|
self._client = env_client
|
|
71
71
|
|
|
72
72
|
def forward(
|
|
73
|
-
self,
|
|
73
|
+
self,
|
|
74
|
+
prompt: Optional[str] = None,
|
|
75
|
+
messages: Optional[List[Dict[str, Any]]] = None,
|
|
76
|
+
**kwargs: Any,
|
|
74
77
|
):
|
|
75
78
|
return syncify(self.aforward)(prompt=prompt, messages=messages, **kwargs)
|
|
76
79
|
|
|
77
80
|
async def aforward(
|
|
78
|
-
self,
|
|
81
|
+
self,
|
|
82
|
+
prompt: Optional[str] = None,
|
|
83
|
+
messages: Optional[List[Dict[str, Any]]] = None,
|
|
84
|
+
**kwargs: Any,
|
|
79
85
|
):
|
|
80
86
|
provider, model_id = _split_provider_model(self.model)
|
|
81
87
|
|
tactus/dspy/config.py
CHANGED
|
@@ -103,10 +103,13 @@ def configure_lm(
|
|
|
103
103
|
|
|
104
104
|
logger = logging.getLogger(__name__)
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
try:
|
|
107
|
+
adapter = ChatAdapter(use_native_function_calling=True)
|
|
108
|
+
except TypeError:
|
|
109
|
+
adapter = ChatAdapter()
|
|
110
|
+
|
|
111
|
+
use_native = getattr(adapter, "use_native_function_calling", None)
|
|
112
|
+
logger.info(f"[ADAPTER] Created ChatAdapter with use_native_function_calling={use_native}")
|
|
110
113
|
|
|
111
114
|
# Set as global default with adapter
|
|
112
115
|
dspy.configure(lm=lm, adapter=adapter)
|
tactus/primitives/model.py
CHANGED
tactus/primitives/procedure.py
CHANGED
|
@@ -550,7 +550,7 @@ class ProcedurePrimitive:
|
|
|
550
550
|
|
|
551
551
|
name_path = Path(name)
|
|
552
552
|
|
|
553
|
-
def add_candidates(base: Path
|
|
553
|
+
def add_candidates(base: Optional[Path], rel: Path) -> None:
|
|
554
554
|
candidate = (base / rel) if base is not None else rel
|
|
555
555
|
add_path(candidate)
|
|
556
556
|
if candidate.suffix != ".tac":
|
tactus/primitives/state.py
CHANGED
|
@@ -10,7 +10,7 @@ Provides:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ class StatePrimitive:
|
|
|
23
23
|
progress, accumulate results, and coordinate between agents.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
def __init__(self, state_schema:
|
|
26
|
+
def __init__(self, state_schema: Optional[Dict[str, Any]] = None):
|
|
27
27
|
"""
|
|
28
28
|
Initialize state storage.
|
|
29
29
|
|
|
@@ -17,7 +17,7 @@ import tempfile
|
|
|
17
17
|
import time
|
|
18
18
|
import uuid
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
20
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
21
21
|
|
|
22
22
|
from .config import SandboxConfig
|
|
23
23
|
from .docker_manager import (
|
|
@@ -523,6 +523,11 @@ class ContainerRunner:
|
|
|
523
523
|
llm_backend_config=llm_backend_config,
|
|
524
524
|
)
|
|
525
525
|
finally:
|
|
526
|
+
try:
|
|
527
|
+
await broker_server.aclose()
|
|
528
|
+
broker_server = None
|
|
529
|
+
except Exception:
|
|
530
|
+
logger.debug("[BROKER] Failed to close broker server", exc_info=True)
|
|
526
531
|
# Cancel broker task when container finishes
|
|
527
532
|
broker_task.cancel()
|
|
528
533
|
try:
|
|
@@ -589,7 +594,7 @@ class ContainerRunner:
|
|
|
589
594
|
"""
|
|
590
595
|
broker_transport = (self.config.broker_transport or "stdio").lower()
|
|
591
596
|
|
|
592
|
-
stdio_request_prefix: str
|
|
597
|
+
stdio_request_prefix: Optional[str] = None
|
|
593
598
|
if broker_transport == "stdio":
|
|
594
599
|
from tactus.broker.server import OpenAIChatBackend
|
|
595
600
|
from tactus.broker.server import HostToolRegistry
|
|
@@ -950,9 +955,9 @@ class ContainerRunner:
|
|
|
950
955
|
)
|
|
951
956
|
logger.debug("[SANDBOX] Spawned container process pid=%s", process.pid)
|
|
952
957
|
|
|
953
|
-
stdout_task: asyncio.Task[None]
|
|
954
|
-
stderr_task: asyncio.Task[None]
|
|
955
|
-
wait_task: asyncio.Task[int]
|
|
958
|
+
stdout_task: Optional[asyncio.Task[None]] = None
|
|
959
|
+
stderr_task: Optional[asyncio.Task[None]] = None
|
|
960
|
+
wait_task: Optional[asyncio.Task[int]] = None
|
|
956
961
|
|
|
957
962
|
try:
|
|
958
963
|
assert process.stdin is not None
|
|
@@ -1159,7 +1164,7 @@ class ContainerRunner:
|
|
|
1159
1164
|
return
|
|
1160
1165
|
|
|
1161
1166
|
# Rich/terminal: parse our container log format and re-emit.
|
|
1162
|
-
current:
|
|
1167
|
+
current: Optional[Tuple[str, int, List[str]]] = None # (logger_name, levelno, lines)
|
|
1163
1168
|
|
|
1164
1169
|
def flush_current() -> None:
|
|
1165
1170
|
nonlocal current
|
tactus/testing/context.py
CHANGED
|
@@ -45,18 +45,18 @@ class TactusTestContext:
|
|
|
45
45
|
self.total_tokens: int = 0 # Track total tokens
|
|
46
46
|
self.cost_breakdown: List[Any] = [] # Track per-call costs
|
|
47
47
|
self._agent_mock_turns: Dict[str, List[Dict[str, Any]]] = {}
|
|
48
|
-
self._scenario_message: str
|
|
48
|
+
self._scenario_message: Optional[str] = None
|
|
49
49
|
|
|
50
50
|
def set_scenario_message(self, message: str) -> None:
|
|
51
51
|
"""Set the scenario's primary injected message (for in-spec mocking coordination)."""
|
|
52
52
|
self._scenario_message = message
|
|
53
53
|
|
|
54
|
-
def get_scenario_message(self) -> str
|
|
54
|
+
def get_scenario_message(self) -> Optional[str]:
|
|
55
55
|
"""Get the scenario's primary injected message, if set."""
|
|
56
56
|
return self._scenario_message
|
|
57
57
|
|
|
58
58
|
def mock_agent_response(
|
|
59
|
-
self, agent: str, message: str, when_message: str
|
|
59
|
+
self, agent: str, message: str, when_message: Optional[str] = None
|
|
60
60
|
) -> None:
|
|
61
61
|
"""Add a mocked agent response for this scenario (temporal; 1 per agent turn).
|
|
62
62
|
|
|
@@ -79,8 +79,8 @@ class TactusTestContext:
|
|
|
79
79
|
self,
|
|
80
80
|
agent: str,
|
|
81
81
|
tool: str,
|
|
82
|
-
args: Dict[str, Any]
|
|
83
|
-
when_message: str
|
|
82
|
+
args: Optional[Dict[str, Any]] = None,
|
|
83
|
+
when_message: Optional[str] = None,
|
|
84
84
|
) -> None:
|
|
85
85
|
"""Add a mocked tool call to an agent's next mocked turn for this scenario."""
|
|
86
86
|
args = args or {}
|
|
@@ -114,7 +114,7 @@ class TactusTestContext:
|
|
|
114
114
|
self.runtime.external_agent_mocks = self._agent_mock_turns
|
|
115
115
|
|
|
116
116
|
def mock_agent_data(
|
|
117
|
-
self, agent: str, data: Dict[str, Any], when_message: str
|
|
117
|
+
self, agent: str, data: Dict[str, Any], when_message: Optional[str] = None
|
|
118
118
|
) -> None:
|
|
119
119
|
"""Set structured output mock data for an agent's next mocked turn.
|
|
120
120
|
|
|
@@ -94,6 +94,9 @@ class TactusEvaluationRunner(TactusTestRunner):
|
|
|
94
94
|
EvaluationResult with all metrics
|
|
95
95
|
"""
|
|
96
96
|
logger.info(f"Evaluating scenario '{scenario_name}' with {runs} runs")
|
|
97
|
+
run_iteration = self._run_single_iteration
|
|
98
|
+
if isinstance(run_iteration, staticmethod):
|
|
99
|
+
run_iteration = run_iteration.__func__
|
|
97
100
|
|
|
98
101
|
# Run scenario N times
|
|
99
102
|
if parallel:
|
|
@@ -102,12 +105,9 @@ class TactusEvaluationRunner(TactusTestRunner):
|
|
|
102
105
|
ctx = multiprocessing.get_context("spawn")
|
|
103
106
|
with ctx.Pool(processes=workers) as pool:
|
|
104
107
|
iteration_args = [(scenario_name, str(self.work_dir), i) for i in range(runs)]
|
|
105
|
-
results = pool.starmap(
|
|
108
|
+
results = pool.starmap(run_iteration, iteration_args)
|
|
106
109
|
else:
|
|
107
|
-
results = [
|
|
108
|
-
self._run_single_iteration(scenario_name, str(self.work_dir), i)
|
|
109
|
-
for i in range(runs)
|
|
110
|
-
]
|
|
110
|
+
results = [run_iteration(scenario_name, str(self.work_dir), i) for i in range(runs)]
|
|
111
111
|
|
|
112
112
|
# Calculate metrics
|
|
113
113
|
return self._calculate_metrics(scenario_name, results)
|
tactus/testing/steps/builtin.py
CHANGED
|
@@ -14,7 +14,7 @@ Provides a comprehensive library of steps for testing:
|
|
|
14
14
|
import logging
|
|
15
15
|
import re
|
|
16
16
|
import ast
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any, Optional
|
|
18
18
|
|
|
19
19
|
from .registry import StepRegistry
|
|
20
20
|
|
|
@@ -645,7 +645,7 @@ def step_agent_takes_turn(context: Any, agent: str) -> None:
|
|
|
645
645
|
|
|
646
646
|
|
|
647
647
|
def step_mock_agent_responds_with(
|
|
648
|
-
context: Any, agent: str, message: str, when_message: str
|
|
648
|
+
context: Any, agent: str, message: str, when_message: Optional[str] = None
|
|
649
649
|
) -> None:
|
|
650
650
|
"""Configure a per-scenario mock agent response (temporal)."""
|
|
651
651
|
message, _ = _parse_step_string_literal(message)
|
tactus/testing/test_runner.py
CHANGED
|
@@ -128,6 +128,10 @@ class TactusTestRunner:
|
|
|
128
128
|
if not scenarios:
|
|
129
129
|
raise ValueError(f"Scenario not found: {scenario_filter}")
|
|
130
130
|
|
|
131
|
+
run_scenario = self._run_single_scenario
|
|
132
|
+
if isinstance(run_scenario, staticmethod):
|
|
133
|
+
run_scenario = run_scenario.__func__
|
|
134
|
+
|
|
131
135
|
# Run scenarios
|
|
132
136
|
if parallel and len(scenarios) > 1:
|
|
133
137
|
# Run in parallel using 'spawn' to avoid Behave global state conflicts
|
|
@@ -135,13 +139,11 @@ class TactusTestRunner:
|
|
|
135
139
|
ctx = multiprocessing.get_context("spawn")
|
|
136
140
|
with ctx.Pool(processes=min(len(scenarios), os.cpu_count() or 1)) as pool:
|
|
137
141
|
scenario_results = pool.starmap(
|
|
138
|
-
|
|
142
|
+
run_scenario, [(s.name, str(self.work_dir)) for s in scenarios]
|
|
139
143
|
)
|
|
140
144
|
else:
|
|
141
145
|
# Run sequentially
|
|
142
|
-
scenario_results = [
|
|
143
|
-
self._run_single_scenario(s.name, str(self.work_dir)) for s in scenarios
|
|
144
|
-
]
|
|
146
|
+
scenario_results = [run_scenario(s.name, str(self.work_dir)) for s in scenarios]
|
|
145
147
|
|
|
146
148
|
# Build feature result
|
|
147
149
|
feature_result = self._build_feature_result(scenario_results)
|
tactus/utils/asyncio_helpers.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tactus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.37.0
|
|
4
4
|
Summary: Tactus: Lua-based DSL for agentic workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/AnthusAI/Tactus
|
|
6
6
|
Project-URL: Documentation, https://github.com/AnthusAI/Tactus/tree/main/docs
|
|
@@ -14,15 +14,17 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
21
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.9
|
|
22
24
|
Requires-Dist: antlr4-python3-runtime==4.13.1
|
|
23
25
|
Requires-Dist: behave>=1.2.6
|
|
24
26
|
Requires-Dist: boto3>=1.28.0
|
|
25
|
-
Requires-Dist: dotyaml>=0.1.
|
|
27
|
+
Requires-Dist: dotyaml>=0.1.4
|
|
26
28
|
Requires-Dist: dspy>=2.5
|
|
27
29
|
Requires-Dist: flask-cors>=4.0.0
|
|
28
30
|
Requires-Dist: flask>=3.0.0
|
|
@@ -48,9 +50,9 @@ Requires-Dist: typer
|
|
|
48
50
|
Provides-Extra: dev
|
|
49
51
|
Requires-Dist: antlr4-tools>=0.2.1; extra == 'dev'
|
|
50
52
|
Requires-Dist: behave>=1.2.6; extra == 'dev'
|
|
51
|
-
Requires-Dist: black==
|
|
53
|
+
Requires-Dist: black==24.10.0; extra == 'dev'
|
|
52
54
|
Requires-Dist: coverage>=7.4; extra == 'dev'
|
|
53
|
-
Requires-Dist: fastmcp>=2.3.5; extra == 'dev'
|
|
55
|
+
Requires-Dist: fastmcp>=2.3.5; (python_version >= '3.10') and extra == 'dev'
|
|
54
56
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
55
57
|
Requires-Dist: pytest-xdist>=3.0; extra == 'dev'
|
|
56
58
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|