erdo 0.1.31__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.
- erdo/__init__.py +35 -0
- erdo/_generated/__init__.py +18 -0
- erdo/_generated/actions/__init__.py +34 -0
- erdo/_generated/actions/analysis.py +179 -0
- erdo/_generated/actions/bot.py +186 -0
- erdo/_generated/actions/codeexec.py +199 -0
- erdo/_generated/actions/llm.py +148 -0
- erdo/_generated/actions/memory.py +463 -0
- erdo/_generated/actions/pdfextractor.py +97 -0
- erdo/_generated/actions/resource_definitions.py +296 -0
- erdo/_generated/actions/sqlexec.py +90 -0
- erdo/_generated/actions/utils.py +475 -0
- erdo/_generated/actions/webparser.py +119 -0
- erdo/_generated/actions/websearch.py +85 -0
- erdo/_generated/condition/__init__.py +556 -0
- erdo/_generated/internal.py +51 -0
- erdo/_generated/internal_actions.py +91 -0
- erdo/_generated/parameters.py +17 -0
- erdo/_generated/secrets.py +17 -0
- erdo/_generated/template_functions.py +55 -0
- erdo/_generated/types.py +3907 -0
- erdo/actions/__init__.py +40 -0
- erdo/bot_permissions.py +266 -0
- erdo/cli_entry.py +73 -0
- erdo/conditions/__init__.py +11 -0
- erdo/config/__init__.py +5 -0
- erdo/config/config.py +140 -0
- erdo/formatting.py +279 -0
- erdo/install_cli.py +140 -0
- erdo/integrations.py +131 -0
- erdo/invoke/__init__.py +11 -0
- erdo/invoke/client.py +234 -0
- erdo/invoke/invoke.py +555 -0
- erdo/state.py +376 -0
- erdo/sync/__init__.py +17 -0
- erdo/sync/client.py +95 -0
- erdo/sync/extractor.py +492 -0
- erdo/sync/sync.py +327 -0
- erdo/template.py +136 -0
- erdo/test/__init__.py +41 -0
- erdo/test/evaluate.py +272 -0
- erdo/test/runner.py +263 -0
- erdo/types.py +1431 -0
- erdo-0.1.31.dist-info/METADATA +471 -0
- erdo-0.1.31.dist-info/RECORD +48 -0
- erdo-0.1.31.dist-info/WHEEL +4 -0
- erdo-0.1.31.dist-info/entry_points.txt +2 -0
- erdo-0.1.31.dist-info/licenses/LICENSE +22 -0
erdo/sync/sync.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Main sync functionality for syncing agents to the backend."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from .client import SyncClient
|
|
8
|
+
from .extractor import (
|
|
9
|
+
extract_agent_from_instance,
|
|
10
|
+
extract_agents_from_file,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SyncResult:
|
|
16
|
+
"""Result of a sync operation."""
|
|
17
|
+
|
|
18
|
+
success: bool
|
|
19
|
+
bot_id: Optional[str] = None
|
|
20
|
+
bot_name: Optional[str] = None
|
|
21
|
+
error: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
if self.success:
|
|
25
|
+
return f"✅ Successfully synced {self.bot_name} (ID: {self.bot_id})"
|
|
26
|
+
else:
|
|
27
|
+
return f"❌ Failed to sync {self.bot_name}: {self.error}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Sync:
|
|
31
|
+
"""Main sync class for syncing agents."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
agent: Optional[Any] = None,
|
|
36
|
+
endpoint: Optional[str] = None,
|
|
37
|
+
auth_token: Optional[str] = None,
|
|
38
|
+
):
|
|
39
|
+
"""Initialize sync and optionally sync an agent immediately.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
agent: Optional Agent instance to sync immediately
|
|
43
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
44
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
45
|
+
"""
|
|
46
|
+
self.client = SyncClient(endpoint=endpoint, auth_token=auth_token)
|
|
47
|
+
self.result = None
|
|
48
|
+
|
|
49
|
+
# If an agent is provided, sync it immediately
|
|
50
|
+
if agent:
|
|
51
|
+
self.result = self.sync_agent(agent)
|
|
52
|
+
|
|
53
|
+
def sync_agent(
|
|
54
|
+
self, agent: Any, source_file_path: Optional[str] = None
|
|
55
|
+
) -> SyncResult:
|
|
56
|
+
"""Sync a single agent to the backend.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
agent: Agent instance to sync
|
|
60
|
+
source_file_path: Optional path to the source file for better extraction
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
SyncResult with the outcome of the sync operation
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
# Extract agent data
|
|
67
|
+
agent_data = extract_agent_from_instance(agent, source_file_path)
|
|
68
|
+
|
|
69
|
+
# Convert to API format
|
|
70
|
+
bot_request = self._convert_to_api_format(agent_data)
|
|
71
|
+
|
|
72
|
+
# Send to backend
|
|
73
|
+
bot_id = self.client.upsert_bot(bot_request)
|
|
74
|
+
|
|
75
|
+
return SyncResult(success=True, bot_id=bot_id, bot_name=agent.name)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return SyncResult(
|
|
79
|
+
success=False, bot_name=getattr(agent, "name", "Unknown"), error=str(e)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_file(
|
|
84
|
+
cls,
|
|
85
|
+
file_path: str,
|
|
86
|
+
endpoint: Optional[str] = None,
|
|
87
|
+
auth_token: Optional[str] = None,
|
|
88
|
+
) -> List[SyncResult]:
|
|
89
|
+
"""Sync agents from a Python file.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
file_path: Path to the Python file containing agents
|
|
93
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
94
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
List of SyncResult objects for each agent found
|
|
98
|
+
"""
|
|
99
|
+
sync = cls(endpoint=endpoint, auth_token=auth_token)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Extract agents from file
|
|
103
|
+
agent_data = extract_agents_from_file(file_path)
|
|
104
|
+
|
|
105
|
+
# Handle single agent or list of agents
|
|
106
|
+
if isinstance(agent_data, list):
|
|
107
|
+
results = []
|
|
108
|
+
for data in agent_data:
|
|
109
|
+
result = sync._sync_agent_data(data)
|
|
110
|
+
results.append(result)
|
|
111
|
+
return results
|
|
112
|
+
else:
|
|
113
|
+
result = sync._sync_agent_data(agent_data)
|
|
114
|
+
return [result]
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return [
|
|
118
|
+
SyncResult(
|
|
119
|
+
success=False, error=f"Failed to extract agents from file: {e}"
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_directory(
|
|
125
|
+
cls,
|
|
126
|
+
directory_path: str = ".",
|
|
127
|
+
endpoint: Optional[str] = None,
|
|
128
|
+
auth_token: Optional[str] = None,
|
|
129
|
+
) -> List[SyncResult]:
|
|
130
|
+
"""Sync all agents from a directory.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
directory_path: Path to directory containing agent files (default: current directory)
|
|
134
|
+
endpoint: API endpoint URL. If not provided, uses config.
|
|
135
|
+
auth_token: Authentication token. If not provided, uses config.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of SyncResult objects for each agent found
|
|
139
|
+
"""
|
|
140
|
+
results = []
|
|
141
|
+
|
|
142
|
+
directory = Path(directory_path)
|
|
143
|
+
|
|
144
|
+
# Check for __init__.py with agents first
|
|
145
|
+
init_file = directory / "__init__.py"
|
|
146
|
+
if init_file.exists():
|
|
147
|
+
try:
|
|
148
|
+
init_results = cls.from_file(
|
|
149
|
+
str(init_file), endpoint=endpoint, auth_token=auth_token
|
|
150
|
+
)
|
|
151
|
+
if init_results:
|
|
152
|
+
return init_results
|
|
153
|
+
except Exception:
|
|
154
|
+
pass # Fall back to directory scan
|
|
155
|
+
|
|
156
|
+
# Scan directory for agent files
|
|
157
|
+
for py_file in directory.glob("**/*.py"):
|
|
158
|
+
# Skip common non-agent files
|
|
159
|
+
if any(
|
|
160
|
+
skip in str(py_file)
|
|
161
|
+
for skip in ["__pycache__", "test_", "_test.py", ".venv", "venv"]
|
|
162
|
+
):
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Check if file has agents
|
|
167
|
+
with open(py_file, "r") as f:
|
|
168
|
+
content = f.read()
|
|
169
|
+
if "agents = [" not in content:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Try to sync agents from this file
|
|
173
|
+
file_results = cls.from_file(
|
|
174
|
+
str(py_file), endpoint=endpoint, auth_token=auth_token
|
|
175
|
+
)
|
|
176
|
+
results.extend(file_results)
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
results.append(
|
|
180
|
+
SyncResult(success=False, error=f"Failed to process {py_file}: {e}")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
def _sync_agent_data(self, agent_data: Dict[str, Any]) -> SyncResult:
|
|
186
|
+
"""Sync extracted agent data to the backend.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
agent_data: Extracted agent data dictionary
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
SyncResult with the outcome of the sync operation
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
# Convert to API format
|
|
196
|
+
bot_request = self._convert_to_api_format(agent_data)
|
|
197
|
+
|
|
198
|
+
# Send to backend
|
|
199
|
+
bot_id = self.client.upsert_bot(bot_request)
|
|
200
|
+
|
|
201
|
+
return SyncResult(
|
|
202
|
+
success=True, bot_id=bot_id, bot_name=agent_data["bot"]["name"]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
return SyncResult(
|
|
207
|
+
success=False,
|
|
208
|
+
bot_name=agent_data.get("bot", {}).get("name", "Unknown"),
|
|
209
|
+
error=str(e),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def _convert_to_api_format(self, agent_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
213
|
+
"""Convert extracted agent data to API format.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
agent_data: Extracted agent data
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Bot request in API format
|
|
220
|
+
"""
|
|
221
|
+
# Override source to "user" as per Go implementation
|
|
222
|
+
bot_data = agent_data["bot"].copy()
|
|
223
|
+
bot_data["source"] = "user"
|
|
224
|
+
|
|
225
|
+
# Convert steps to API format
|
|
226
|
+
api_steps = []
|
|
227
|
+
for step_with_handlers in agent_data.get("steps", []):
|
|
228
|
+
api_step = self._convert_step_to_api(step_with_handlers)
|
|
229
|
+
api_steps.append(api_step)
|
|
230
|
+
|
|
231
|
+
# Build the API request
|
|
232
|
+
# Convert parameter_definitions to dicts if they're objects
|
|
233
|
+
param_defs = agent_data.get("parameter_definitions", [])
|
|
234
|
+
if param_defs and hasattr(param_defs[0], "to_dict"):
|
|
235
|
+
param_defs = [
|
|
236
|
+
pd.to_dict() if hasattr(pd, "to_dict") else pd for pd in param_defs
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
bot_request = {
|
|
240
|
+
"bot": bot_data,
|
|
241
|
+
"steps": api_steps,
|
|
242
|
+
"source": "user",
|
|
243
|
+
"parameter_definitions": param_defs,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Include action result schemas if present
|
|
247
|
+
if "action_result_schemas" in agent_data:
|
|
248
|
+
bot_request["action_result_schemas"] = agent_data["action_result_schemas"]
|
|
249
|
+
|
|
250
|
+
return bot_request
|
|
251
|
+
|
|
252
|
+
def _convert_step_to_api(
|
|
253
|
+
self, step_with_handlers: Dict[str, Any]
|
|
254
|
+
) -> Dict[str, Any]:
|
|
255
|
+
"""Convert StepWithHandlers to API format.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
step_with_handlers: Step with handlers dictionary
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Step in API format
|
|
262
|
+
"""
|
|
263
|
+
# Ensure parameters is not None
|
|
264
|
+
step = step_with_handlers["step"].copy()
|
|
265
|
+
if step.get("parameters") is None:
|
|
266
|
+
step["parameters"] = {}
|
|
267
|
+
|
|
268
|
+
# Convert result handlers
|
|
269
|
+
api_handlers = []
|
|
270
|
+
for handler in step_with_handlers.get("result_handlers", []):
|
|
271
|
+
api_handler = handler.copy()
|
|
272
|
+
|
|
273
|
+
# Convert nested steps in handler
|
|
274
|
+
if "steps" in api_handler:
|
|
275
|
+
api_handler_steps = []
|
|
276
|
+
for nested_step in api_handler["steps"]:
|
|
277
|
+
api_nested = self._convert_step_to_api(nested_step)
|
|
278
|
+
api_handler_steps.append(api_nested)
|
|
279
|
+
api_handler["steps"] = api_handler_steps
|
|
280
|
+
|
|
281
|
+
api_handlers.append(api_handler)
|
|
282
|
+
|
|
283
|
+
return {"step": step, "result_handlers": api_handlers}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# Convenience functions
|
|
287
|
+
def sync_agent(
|
|
288
|
+
agent: Any, source_file_path: Optional[str] = None, **kwargs
|
|
289
|
+
) -> SyncResult:
|
|
290
|
+
"""Sync a single agent to the backend.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
agent: Agent instance to sync
|
|
294
|
+
source_file_path: Optional path to the source file
|
|
295
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
SyncResult with the outcome of the sync operation
|
|
299
|
+
"""
|
|
300
|
+
sync = Sync(endpoint=kwargs.get("endpoint"), auth_token=kwargs.get("auth_token"))
|
|
301
|
+
return sync.sync_agent(agent, source_file_path)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def sync_agents_from_file(file_path: str, **kwargs) -> List[SyncResult]:
|
|
305
|
+
"""Sync agents from a Python file.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
file_path: Path to the Python file containing agents
|
|
309
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of SyncResult objects
|
|
313
|
+
"""
|
|
314
|
+
return Sync.from_file(file_path, **kwargs)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def sync_agents_from_directory(directory_path: str = ".", **kwargs) -> List[SyncResult]:
|
|
318
|
+
"""Sync all agents from a directory.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
directory_path: Path to directory containing agent files
|
|
322
|
+
**kwargs: Additional arguments (endpoint, auth_token)
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of SyncResult objects
|
|
326
|
+
"""
|
|
327
|
+
return Sync.from_directory(directory_path, **kwargs)
|
erdo/template.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template string handling for export/import roundtrip compatibility.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for handling template strings during the
|
|
5
|
+
export/import roundtrip process. Template strings are Go template expressions
|
|
6
|
+
that can't be executed as Python, so they need special handling.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Union
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from erdo.types import Prompt
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TemplateString:
|
|
16
|
+
"""
|
|
17
|
+
A wrapper class for template strings during export/import roundtrip.
|
|
18
|
+
|
|
19
|
+
This class represents a Go template string (like {{.Data.field}}) in a way
|
|
20
|
+
that can be executed as Python code and then converted back to the original
|
|
21
|
+
template string during import.
|
|
22
|
+
|
|
23
|
+
It implements various duck-typing methods to be compatible with Pydantic
|
|
24
|
+
validation while preserving the template content for later extraction.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
template: str
|
|
28
|
+
|
|
29
|
+
def __init__(self, template: Union[str, "Prompt"]):
|
|
30
|
+
"""
|
|
31
|
+
Initialize a TemplateString with the template content.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
template: The template string content (e.g., "{{.Data.field}}") or a Prompt object
|
|
35
|
+
"""
|
|
36
|
+
# Convert Prompt objects to strings, ensure template is always a string
|
|
37
|
+
if hasattr(template, "content"):
|
|
38
|
+
# This is a Prompt object
|
|
39
|
+
self.template = str(template)
|
|
40
|
+
else:
|
|
41
|
+
self.template = str(template)
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
"""Return the template string for display purposes."""
|
|
45
|
+
return self.template
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
"""Return a representation of the TemplateString."""
|
|
49
|
+
return f"TemplateString({self.template!r})"
|
|
50
|
+
|
|
51
|
+
def __eq__(self, other: object) -> bool:
|
|
52
|
+
"""Check equality with another TemplateString."""
|
|
53
|
+
if isinstance(other, TemplateString):
|
|
54
|
+
return self.template == other.template
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def __hash__(self) -> int:
|
|
58
|
+
"""Make TemplateString hashable."""
|
|
59
|
+
return hash(self.template)
|
|
60
|
+
|
|
61
|
+
# Duck typing methods to make it behave like a string for Pydantic validation
|
|
62
|
+
def __len__(self) -> int:
|
|
63
|
+
"""Return length to behave like a string."""
|
|
64
|
+
return len(self.template)
|
|
65
|
+
|
|
66
|
+
def __contains__(self, item) -> bool:
|
|
67
|
+
"""Support 'in' operator to behave like a string."""
|
|
68
|
+
return item in self.template
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, key):
|
|
71
|
+
"""Support indexing to behave like a string."""
|
|
72
|
+
return self.template[key]
|
|
73
|
+
|
|
74
|
+
def __iter__(self):
|
|
75
|
+
"""Support iteration to behave like a string."""
|
|
76
|
+
return iter(self.template)
|
|
77
|
+
|
|
78
|
+
# List-like methods for cases where template strings represent arrays
|
|
79
|
+
def __getstate__(self):
|
|
80
|
+
"""Support pickling."""
|
|
81
|
+
return {"template": self.template}
|
|
82
|
+
|
|
83
|
+
def __setstate__(self, state):
|
|
84
|
+
"""Support unpickling."""
|
|
85
|
+
self.template = state["template"]
|
|
86
|
+
|
|
87
|
+
# Additional methods to help with Pydantic validation
|
|
88
|
+
@classmethod
|
|
89
|
+
def __get_validators__(cls):
|
|
90
|
+
"""Pydantic v1 compatibility."""
|
|
91
|
+
yield cls.validate
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def validate(cls, v, *args, **kwargs):
|
|
95
|
+
"""Validate that the value is a string, TemplateString, or basic type."""
|
|
96
|
+
if isinstance(v, cls):
|
|
97
|
+
return v
|
|
98
|
+
# Allow strings and basic types that can be converted to strings
|
|
99
|
+
if isinstance(v, (str, int, float, bool)):
|
|
100
|
+
return v
|
|
101
|
+
# Allow Prompt objects (they have __str__ method and content attribute)
|
|
102
|
+
if hasattr(v, "__str__") and hasattr(v, "content"):
|
|
103
|
+
# This is likely a Prompt object
|
|
104
|
+
return str(v)
|
|
105
|
+
# Reject functions, lambdas, and other complex types
|
|
106
|
+
if callable(v):
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Template fields cannot accept callable objects like {type(v).__name__}"
|
|
109
|
+
)
|
|
110
|
+
# For other types, try to convert to string
|
|
111
|
+
try:
|
|
112
|
+
str(v)
|
|
113
|
+
return v
|
|
114
|
+
except Exception:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Template fields cannot accept objects of type {type(v).__name__}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def __get_pydantic_core_schema__(cls, source_type, handler):
|
|
121
|
+
"""Pydantic v2 compatibility."""
|
|
122
|
+
from pydantic_core import core_schema
|
|
123
|
+
|
|
124
|
+
return core_schema.no_info_plain_validator_function(cls.validate)
|
|
125
|
+
|
|
126
|
+
def to_template_string(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Convert back to the original template string format.
|
|
129
|
+
|
|
130
|
+
This is used during the import process to convert the TemplateString
|
|
131
|
+
object back to the raw template string.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The original template string
|
|
135
|
+
"""
|
|
136
|
+
return self.template
|
erdo/test/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Test evaluation helpers for erdo agent testing.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions for writing clean assertions when testing agents.
|
|
4
|
+
Use these in regular Python scripts - no pytest needed!
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from erdo import invoke
|
|
8
|
+
>>> from erdo.test import text_contains, json_path_equals
|
|
9
|
+
>>>
|
|
10
|
+
>>> # Test in a regular Python script
|
|
11
|
+
>>> response = invoke("my_agent", messages=[...], mode="replay")
|
|
12
|
+
>>> assert text_contains(response.result, "expected text")
|
|
13
|
+
>>> assert json_path_equals(response.result, "status", "success")
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Or use via CLI
|
|
16
|
+
>>> # ./erdo invoke my_agent --message "test" --mode replay
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .evaluate import (
|
|
20
|
+
has_dataset,
|
|
21
|
+
json_path_equals,
|
|
22
|
+
json_path_exists,
|
|
23
|
+
text_contains,
|
|
24
|
+
text_equals,
|
|
25
|
+
text_matches,
|
|
26
|
+
)
|
|
27
|
+
from .runner import discover_tests
|
|
28
|
+
from .runner import main as run_tests
|
|
29
|
+
from .runner import run_tests_parallel
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"text_contains",
|
|
33
|
+
"text_equals",
|
|
34
|
+
"text_matches",
|
|
35
|
+
"json_path_equals",
|
|
36
|
+
"json_path_exists",
|
|
37
|
+
"has_dataset",
|
|
38
|
+
"run_tests",
|
|
39
|
+
"run_tests_parallel",
|
|
40
|
+
"discover_tests",
|
|
41
|
+
]
|