erdo 0.1.10__py3-none-any.whl → 0.1.12__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.
Potentially problematic release.
This version of erdo might be problematic. Click here for more details.
- erdo/__init__.py +4 -4
- erdo/_generated/actions/bot.py +6 -0
- erdo/_generated/actions/codeexec.py +32 -1
- erdo/_generated/actions/llm.py +5 -0
- erdo/_generated/types.py +1876 -575
- erdo/invoke/client.py +9 -3
- erdo/invoke/invoke.py +104 -43
- erdo/types.py +43 -5
- {erdo-0.1.10.dist-info → erdo-0.1.12.dist-info}/METADATA +44 -7
- {erdo-0.1.10.dist-info → erdo-0.1.12.dist-info}/RECORD +13 -13
- {erdo-0.1.10.dist-info → erdo-0.1.12.dist-info}/WHEEL +0 -0
- {erdo-0.1.10.dist-info → erdo-0.1.12.dist-info}/entry_points.txt +0 -0
- {erdo-0.1.10.dist-info → erdo-0.1.12.dist-info}/licenses/LICENSE +0 -0
erdo/invoke/client.py
CHANGED
|
@@ -53,7 +53,8 @@ class InvokeClient:
|
|
|
53
53
|
messages: Optional[List[Dict[str, str]]] = None,
|
|
54
54
|
parameters: Optional[Dict[str, Any]] = None,
|
|
55
55
|
dataset_slugs: Optional[list] = None,
|
|
56
|
-
mode: Optional[str] = None,
|
|
56
|
+
mode: Optional[Union[str, Dict[str, Any]]] = None,
|
|
57
|
+
manual_mocks: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
57
58
|
stream: bool = False,
|
|
58
59
|
) -> Union[SSEClient, Dict[str, Any]]:
|
|
59
60
|
"""Invoke a bot via the backend orchestrator.
|
|
@@ -63,7 +64,8 @@ class InvokeClient:
|
|
|
63
64
|
messages: Messages in format [{"role": "user", "content": "..."}]
|
|
64
65
|
parameters: Parameters to pass to the bot
|
|
65
66
|
dataset_slugs: Optional dataset slugs to include
|
|
66
|
-
mode: Invocation mode: "live"
|
|
67
|
+
mode: Invocation mode - string: "live|replay|manual" OR dict: {"mode": "replay", "refresh": true}
|
|
68
|
+
manual_mocks: Manual mock responses for mode="manual" (step_path -> mock response)
|
|
67
69
|
stream: Whether to return SSE client for streaming (default: False)
|
|
68
70
|
|
|
69
71
|
Returns:
|
|
@@ -94,10 +96,14 @@ class InvokeClient:
|
|
|
94
96
|
if dataset_slugs:
|
|
95
97
|
invoke_params["dataset_slugs"] = dataset_slugs
|
|
96
98
|
|
|
97
|
-
# Add mode if provided (live/replay/
|
|
99
|
+
# Add mode if provided (live/replay/manual)
|
|
98
100
|
if mode:
|
|
99
101
|
invoke_params["mode"] = mode
|
|
100
102
|
|
|
103
|
+
# Add manual mocks if provided (for manual mode)
|
|
104
|
+
if manual_mocks:
|
|
105
|
+
invoke_params["manual_mocks"] = manual_mocks
|
|
106
|
+
|
|
101
107
|
# Make the request - always stream to handle SSE
|
|
102
108
|
response = requests.post(url, json=invoke_params, headers=headers, stream=True)
|
|
103
109
|
|
erdo/invoke/invoke.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Main invoke functionality for running agents via the orchestrator."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
7
|
|
|
7
8
|
from .client import InvokeClient
|
|
8
9
|
|
|
@@ -106,7 +107,8 @@ class Invoke:
|
|
|
106
107
|
messages: Optional[List[Dict[str, str]]] = None,
|
|
107
108
|
parameters: Optional[Dict[str, Any]] = None,
|
|
108
109
|
dataset_slugs: Optional[list] = None,
|
|
109
|
-
mode: Optional[str] = None,
|
|
110
|
+
mode: Optional[Union[str, Dict[str, Any]]] = None,
|
|
111
|
+
manual_mocks: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
110
112
|
stream: bool = False,
|
|
111
113
|
output_format: str = "events",
|
|
112
114
|
verbose: bool = False,
|
|
@@ -118,7 +120,8 @@ class Invoke:
|
|
|
118
120
|
messages: Messages in format [{"role": "user", "content": "..."}]
|
|
119
121
|
parameters: Parameters to pass to the bot
|
|
120
122
|
dataset_slugs: Dataset slugs to include (e.g. ["my-dataset"] or ["org.my-dataset"])
|
|
121
|
-
mode: Invocation mode: "live"
|
|
123
|
+
mode: Invocation mode - string: "live|replay|manual" OR dict: {"mode": "replay", "refresh": true}
|
|
124
|
+
manual_mocks: Manual mock responses for mode="manual" (step_path -> mock response)
|
|
122
125
|
stream: Whether to stream events
|
|
123
126
|
output_format: Output format: "events" (raw), "text" (formatted), "json" (summary)
|
|
124
127
|
verbose: Show detailed steps (only for text format)
|
|
@@ -133,6 +136,7 @@ class Invoke:
|
|
|
133
136
|
parameters=parameters,
|
|
134
137
|
dataset_slugs=dataset_slugs,
|
|
135
138
|
mode=mode,
|
|
139
|
+
manual_mocks=manual_mocks,
|
|
136
140
|
stream=stream,
|
|
137
141
|
)
|
|
138
142
|
|
|
@@ -145,16 +149,28 @@ class Invoke:
|
|
|
145
149
|
if not isinstance(response, dict):
|
|
146
150
|
# For formatted output, print as we stream
|
|
147
151
|
if output_format in ["text", "json"] and not self.print_events:
|
|
148
|
-
from ..formatting import parse_invocation_events
|
|
149
|
-
|
|
150
152
|
bot_name_printed = False
|
|
151
153
|
completed_steps = set()
|
|
154
|
+
step_names = {} # Map to track step keys
|
|
155
|
+
printed_content_ids = (
|
|
156
|
+
set()
|
|
157
|
+
) # Track which message content we've printed
|
|
152
158
|
|
|
153
159
|
for event in response.events():
|
|
160
|
+
if not event:
|
|
161
|
+
continue
|
|
154
162
|
events.append(event)
|
|
155
163
|
|
|
156
|
-
# Extract
|
|
164
|
+
# Extract payload and metadata
|
|
157
165
|
payload = event.get("payload", {})
|
|
166
|
+
metadata = event.get("metadata", {})
|
|
167
|
+
status = (
|
|
168
|
+
payload.get("status")
|
|
169
|
+
if isinstance(payload, dict)
|
|
170
|
+
else None
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Extract invocation ID and bot name from payload
|
|
158
174
|
if isinstance(payload, dict):
|
|
159
175
|
if "invocation_id" in payload and not invocation_id:
|
|
160
176
|
invocation_id = payload["invocation_id"]
|
|
@@ -165,39 +181,75 @@ class Invoke:
|
|
|
165
181
|
print(
|
|
166
182
|
f"Invocation ID: {invocation_id or 'N/A'}"
|
|
167
183
|
)
|
|
168
|
-
|
|
169
|
-
print("\nSteps:")
|
|
184
|
+
print() # Empty line before steps start
|
|
170
185
|
bot_name_printed = True
|
|
171
186
|
|
|
172
|
-
#
|
|
187
|
+
# Track and display step info (ONLY in verbose mode)
|
|
188
|
+
# step_info events have both key and action_type in payload
|
|
189
|
+
if (
|
|
190
|
+
verbose
|
|
191
|
+
and output_format == "text"
|
|
192
|
+
and isinstance(payload, dict)
|
|
193
|
+
):
|
|
194
|
+
if "key" in payload and "action_type" in payload:
|
|
195
|
+
# This is a step_info event - show "▸" here if visible
|
|
196
|
+
step_key = payload["key"]
|
|
197
|
+
action_type = payload["action_type"]
|
|
198
|
+
step_names[step_key] = action_type
|
|
199
|
+
|
|
200
|
+
# Show step start on step_info event if visible
|
|
201
|
+
if metadata.get("user_visibility") == "visible":
|
|
202
|
+
print(f"▸ {step_key} ({action_type})")
|
|
203
|
+
|
|
204
|
+
# Handle step finished events (check status in payload)
|
|
205
|
+
# We look for the most recent step from step_names (ONLY in verbose mode)
|
|
173
206
|
if (
|
|
174
207
|
verbose
|
|
175
208
|
and output_format == "text"
|
|
176
|
-
and
|
|
209
|
+
and status == "step finished"
|
|
177
210
|
):
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
completed_steps.add(step.key)
|
|
211
|
+
if metadata.get("user_visibility") == "visible":
|
|
212
|
+
# Find the last step that we haven't marked complete yet
|
|
213
|
+
for step_key in reversed(list(step_names.keys())):
|
|
214
|
+
if step_key not in completed_steps:
|
|
215
|
+
print(f"✓ {step_key}")
|
|
216
|
+
completed_steps.add(step_key)
|
|
217
|
+
break
|
|
186
218
|
|
|
187
219
|
# Print message content as it streams
|
|
220
|
+
# ONLY print if:
|
|
221
|
+
# 1. user_visibility is "visible"
|
|
222
|
+
# 2. Has message_content_id (actual message content, not fragments)
|
|
223
|
+
# 3. content_type is "text" (not "json" or other types)
|
|
224
|
+
# 4. Haven't already printed chunks for this content_id
|
|
188
225
|
if (
|
|
189
226
|
output_format == "text"
|
|
190
227
|
and isinstance(payload, str)
|
|
191
228
|
and len(payload) > 0
|
|
229
|
+
and isinstance(metadata, dict)
|
|
230
|
+
and metadata.get("user_visibility") == "visible"
|
|
231
|
+
and metadata.get(
|
|
232
|
+
"message_content_id"
|
|
233
|
+
) # Must be message content
|
|
234
|
+
and metadata.get("content_type")
|
|
235
|
+
== "text" # Only plain text, not JSON
|
|
192
236
|
):
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
237
|
+
content_id = metadata.get("message_content_id")
|
|
238
|
+
# If this is a full message (long) and we've already printed chunks, skip
|
|
239
|
+
if (
|
|
240
|
+
len(payload) > 20
|
|
241
|
+
and content_id in printed_content_ids
|
|
242
|
+
):
|
|
243
|
+
# This is a duplicate full message after streaming chunks
|
|
244
|
+
pass
|
|
245
|
+
else:
|
|
197
246
|
print(payload, end="", flush=True)
|
|
247
|
+
printed_content_ids.add(content_id)
|
|
198
248
|
else:
|
|
199
249
|
# For raw events or print_events mode, just collect
|
|
200
250
|
for event in response.events():
|
|
251
|
+
if not event:
|
|
252
|
+
continue
|
|
201
253
|
events.append(event)
|
|
202
254
|
|
|
203
255
|
if self.print_events:
|
|
@@ -341,7 +393,8 @@ def invoke(
|
|
|
341
393
|
messages: Optional[List[Dict[str, str]]] = None,
|
|
342
394
|
parameters: Optional[Dict[str, Any]] = None,
|
|
343
395
|
datasets: Optional[list] = None,
|
|
344
|
-
mode: Optional[str] = None,
|
|
396
|
+
mode: Optional[Union[str, Dict[str, Any]]] = None,
|
|
397
|
+
manual_mocks: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
345
398
|
stream: bool = False,
|
|
346
399
|
output_format: str = "events",
|
|
347
400
|
verbose: bool = False,
|
|
@@ -355,7 +408,8 @@ def invoke(
|
|
|
355
408
|
messages: Messages in format [{"role": "user", "content": "..."}]
|
|
356
409
|
parameters: Parameters to pass to the bot
|
|
357
410
|
datasets: Dataset slugs to include (e.g. ["my-dataset"] or ["org.my-dataset"])
|
|
358
|
-
mode: Invocation mode: "live"
|
|
411
|
+
mode: Invocation mode - string: "live|replay|manual" OR dict: {"mode": "replay", "refresh": true}
|
|
412
|
+
manual_mocks: Manual mock responses for mode="manual" (step_path -> mock response)
|
|
359
413
|
stream: Whether to stream events
|
|
360
414
|
output_format: Output format: "events" (raw), "text" (formatted), "json" (summary)
|
|
361
415
|
verbose: Show detailed steps (only for text format)
|
|
@@ -368,31 +422,28 @@ def invoke(
|
|
|
368
422
|
Example:
|
|
369
423
|
>>> from erdo import invoke
|
|
370
424
|
>>>
|
|
371
|
-
>>> #
|
|
372
|
-
>>> response = invoke("my_agent", messages=[...])
|
|
373
|
-
>>> print(response.result) # {"events": [...]}
|
|
425
|
+
>>> # Simple replay mode
|
|
426
|
+
>>> response = invoke("my_agent", messages=[...], mode="replay")
|
|
374
427
|
>>>
|
|
375
|
-
>>> #
|
|
376
|
-
>>> response = invoke("my_agent", messages=[...],
|
|
377
|
-
>>> print(response.result)
|
|
378
|
-
Bot: my agent
|
|
379
|
-
Invocation ID: abc-123
|
|
380
|
-
Result:
|
|
381
|
-
The answer is 4
|
|
428
|
+
>>> # Replay mode with refresh (bypass cache)
|
|
429
|
+
>>> response = invoke("my_agent", messages=[...], mode={"mode": "replay", "refresh": True})
|
|
382
430
|
>>>
|
|
383
|
-
>>> #
|
|
384
|
-
>>> response = invoke("my_agent", messages=[...],
|
|
385
|
-
|
|
386
|
-
>>> # JSON summary
|
|
387
|
-
>>> response = invoke("my_agent", messages=[...], output_format="json")
|
|
388
|
-
>>> print(response.result) # {"bot_name": ..., "steps": [...], "result": ...}
|
|
431
|
+
>>> # Manual mode with mocks
|
|
432
|
+
>>> response = invoke("my_agent", messages=[...], mode="manual",
|
|
433
|
+
... manual_mocks={"llm.message": {"status": "success", "output": {"content": "Mocked"}}})
|
|
389
434
|
"""
|
|
435
|
+
# Check ERDO_REFRESH environment variable
|
|
436
|
+
# If set and mode is "replay" (string), convert to dict with refresh=True
|
|
437
|
+
if os.environ.get("ERDO_REFRESH") == "1" and mode == "replay":
|
|
438
|
+
mode = {"mode": "replay", "refresh": True}
|
|
439
|
+
|
|
390
440
|
return invoke_by_key(
|
|
391
441
|
bot_key=bot_key,
|
|
392
442
|
messages=messages,
|
|
393
443
|
parameters=parameters,
|
|
394
444
|
dataset_slugs=datasets,
|
|
395
445
|
mode=mode,
|
|
446
|
+
manual_mocks=manual_mocks,
|
|
396
447
|
stream=stream,
|
|
397
448
|
output_format=output_format,
|
|
398
449
|
verbose=verbose,
|
|
@@ -435,7 +486,8 @@ def invoke_by_key(
|
|
|
435
486
|
messages: Optional[List[Dict[str, str]]] = None,
|
|
436
487
|
parameters: Optional[Dict[str, Any]] = None,
|
|
437
488
|
dataset_slugs: Optional[list] = None,
|
|
438
|
-
mode: Optional[str] = None,
|
|
489
|
+
mode: Optional[Union[str, Dict[str, Any]]] = None,
|
|
490
|
+
manual_mocks: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
439
491
|
stream: bool = False,
|
|
440
492
|
output_format: str = "events",
|
|
441
493
|
verbose: bool = False,
|
|
@@ -449,7 +501,8 @@ def invoke_by_key(
|
|
|
449
501
|
messages: Messages in format [{"role": "user", "content": "..."}]
|
|
450
502
|
parameters: Parameters to pass to the bot
|
|
451
503
|
dataset_slugs: Dataset slugs to include (e.g. ["my-dataset"] or ["org.my-dataset"])
|
|
452
|
-
mode: Invocation mode: "live"
|
|
504
|
+
mode: Invocation mode - string: "live|replay|manual" OR dict: {"mode": "replay", "refresh": true}
|
|
505
|
+
manual_mocks: Manual mock responses for mode="manual" (step_path -> mock response)
|
|
453
506
|
stream: Whether to stream events
|
|
454
507
|
output_format: Output format: "events" (raw), "text" (formatted), "json" (summary)
|
|
455
508
|
verbose: Show detailed steps (only for text format)
|
|
@@ -465,5 +518,13 @@ def invoke_by_key(
|
|
|
465
518
|
print_events=print_events,
|
|
466
519
|
)
|
|
467
520
|
return invoke.invoke_by_key(
|
|
468
|
-
bot_key,
|
|
521
|
+
bot_key,
|
|
522
|
+
messages,
|
|
523
|
+
parameters,
|
|
524
|
+
dataset_slugs,
|
|
525
|
+
mode,
|
|
526
|
+
manual_mocks,
|
|
527
|
+
stream,
|
|
528
|
+
output_format,
|
|
529
|
+
verbose,
|
|
469
530
|
)
|
erdo/types.py
CHANGED
|
@@ -10,9 +10,12 @@ from enum import Enum
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union, cast
|
|
12
12
|
|
|
13
|
-
from pydantic import BaseModel, Field, field_serializer
|
|
13
|
+
from pydantic import BaseModel, Field, field_serializer, model_serializer
|
|
14
14
|
|
|
15
15
|
from ._generated.types import (
|
|
16
|
+
BotResource,
|
|
17
|
+
Dataset,
|
|
18
|
+
DatasetType,
|
|
16
19
|
ExecutionModeType,
|
|
17
20
|
HandlerType,
|
|
18
21
|
OutputContentType,
|
|
@@ -81,6 +84,11 @@ class PythonFile(BaseModel):
|
|
|
81
84
|
"_type": "PythonFile", # Marker to identify this as a file reference
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
@model_serializer
|
|
88
|
+
def _serialize_model(self) -> Dict[str, Any]:
|
|
89
|
+
"""Pydantic v2 serializer - always use our custom format."""
|
|
90
|
+
return self.to_dict()
|
|
91
|
+
|
|
84
92
|
def resolve_content(self, base_path: Optional[str] = None) -> Dict[str, str]:
|
|
85
93
|
"""Resolve the file content for inclusion in code_files.
|
|
86
94
|
|
|
@@ -233,10 +241,21 @@ class StepMetadata(BaseModel):
|
|
|
233
241
|
ui_content_type: Optional[str] = None
|
|
234
242
|
user_output_visibility: OutputVisibility = OutputVisibility.VISIBLE
|
|
235
243
|
bot_output_visibility: OutputVisibility = OutputVisibility.HIDDEN
|
|
236
|
-
running_message: Optional[str] = None
|
|
237
|
-
finished_message: Optional[str] = None
|
|
244
|
+
running_message: Optional[Union[str, TemplateString]] = None
|
|
245
|
+
finished_message: Optional[Union[str, TemplateString]] = None
|
|
238
246
|
parameter_hydration_behaviour: Optional[ParameterHydrationBehaviour] = None
|
|
239
247
|
|
|
248
|
+
@field_serializer(
|
|
249
|
+
"running_message", "finished_message", mode="wrap", when_used="always"
|
|
250
|
+
)
|
|
251
|
+
def serialize_message_fields(
|
|
252
|
+
self, value: Optional[Union[str, TemplateString]], _info
|
|
253
|
+
) -> Optional[str]:
|
|
254
|
+
"""Convert TemplateString to str during serialization."""
|
|
255
|
+
if isinstance(value, TemplateString):
|
|
256
|
+
return str(value)
|
|
257
|
+
return value
|
|
258
|
+
|
|
240
259
|
|
|
241
260
|
class Step(StepMetadata):
|
|
242
261
|
"""A single step in an agent workflow."""
|
|
@@ -315,6 +334,8 @@ class Step(StepMetadata):
|
|
|
315
334
|
params = action_obj.model_dump()
|
|
316
335
|
# Remove the redundant 'name' field since it's already known from action type
|
|
317
336
|
params.pop("name", None)
|
|
337
|
+
# Remove step_metadata - it's for Step config, not action parameters
|
|
338
|
+
params.pop("step_metadata", None)
|
|
318
339
|
# Map Python SDK field names to backend field names
|
|
319
340
|
if "json_data" in params:
|
|
320
341
|
params["json"] = params.pop("json_data")
|
|
@@ -331,6 +352,8 @@ class Step(StepMetadata):
|
|
|
331
352
|
params = action_dict_method()
|
|
332
353
|
# Remove the redundant 'name' field since it's already known from action type
|
|
333
354
|
params.pop("name", None)
|
|
355
|
+
# Remove step_metadata - it's for Step config, not action parameters
|
|
356
|
+
params.pop("step_metadata", None)
|
|
334
357
|
# Map Python SDK field names to backend field names
|
|
335
358
|
if "json_data" in params:
|
|
336
359
|
params["json"] = params.pop("json_data")
|
|
@@ -920,8 +943,8 @@ class Agent(BaseModel):
|
|
|
920
943
|
description: Optional[str] = None
|
|
921
944
|
persona: Optional[str] = None
|
|
922
945
|
visibility: str = "public"
|
|
923
|
-
running_message: Optional[str] = None
|
|
924
|
-
finished_message: Optional[str] = None
|
|
946
|
+
running_message: Optional[Union[str, TemplateString]] = None
|
|
947
|
+
finished_message: Optional[Union[str, TemplateString]] = None
|
|
925
948
|
version: str = "1.0"
|
|
926
949
|
timeout: Optional[int] = None
|
|
927
950
|
retry_attempts: int = 0
|
|
@@ -929,6 +952,17 @@ class Agent(BaseModel):
|
|
|
929
952
|
steps: List[Step] = Field(default_factory=list)
|
|
930
953
|
parameter_definitions: List["ParameterDefinition"] = Field(default_factory=list)
|
|
931
954
|
|
|
955
|
+
@field_serializer(
|
|
956
|
+
"running_message", "finished_message", mode="wrap", when_used="always"
|
|
957
|
+
)
|
|
958
|
+
def serialize_message_fields(
|
|
959
|
+
self, value: Optional[Union[str, TemplateString]], _info
|
|
960
|
+
) -> Optional[str]:
|
|
961
|
+
"""Convert TemplateString to str during serialization."""
|
|
962
|
+
if isinstance(value, TemplateString):
|
|
963
|
+
return str(value)
|
|
964
|
+
return value
|
|
965
|
+
|
|
932
966
|
def step(
|
|
933
967
|
self,
|
|
934
968
|
action: Any,
|
|
@@ -1319,6 +1353,10 @@ __all__ = [
|
|
|
1319
1353
|
# Complex types
|
|
1320
1354
|
"ExecutionCondition",
|
|
1321
1355
|
"ConditionDefinition",
|
|
1356
|
+
# Resource types
|
|
1357
|
+
"BotResource",
|
|
1358
|
+
"Dataset",
|
|
1359
|
+
"DatasetType",
|
|
1322
1360
|
# Bot permissions
|
|
1323
1361
|
"BotPermissions",
|
|
1324
1362
|
"set_bot_public",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: erdo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.12
|
|
4
4
|
Summary: Python SDK for building workflow automation agents with Erdo
|
|
5
5
|
Project-URL: Homepage, https://erdo.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.erdo.ai
|
|
@@ -316,18 +316,46 @@ else:
|
|
|
316
316
|
|
|
317
317
|
### Invocation Modes
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
319
|
+
Control how bot actions are executed for testing and development:
|
|
320
|
+
|
|
321
|
+
| Mode | Description | Cost |
|
|
322
|
+
|------|-------------|------|
|
|
323
|
+
| **live** | Execute with real API calls | $$$ per run |
|
|
324
|
+
| **replay** | Cache responses, replay on subsequent runs | $$$ first run, FREE after |
|
|
325
|
+
| **manual** | Use developer-provided mock responses | FREE always |
|
|
322
326
|
|
|
323
327
|
```python
|
|
324
|
-
#
|
|
328
|
+
# Live mode (default) - real API calls
|
|
329
|
+
response = invoke("my-agent", messages=[...], mode="live")
|
|
330
|
+
|
|
331
|
+
# Replay mode - cache after first run (recommended for testing!)
|
|
325
332
|
response = invoke("my-agent", messages=[...], mode="replay")
|
|
326
333
|
|
|
327
|
-
#
|
|
328
|
-
response = invoke("my-agent", messages=[...], mode="
|
|
334
|
+
# Replay with refresh - bypass cache, get fresh response
|
|
335
|
+
response = invoke("my-agent", messages=[...], mode={"mode": "replay", "refresh": True})
|
|
336
|
+
|
|
337
|
+
# Manual mode - use mock responses
|
|
338
|
+
response = invoke(
|
|
339
|
+
"my-agent",
|
|
340
|
+
messages=[...],
|
|
341
|
+
mode="manual",
|
|
342
|
+
manual_mocks={
|
|
343
|
+
"llm.message": {
|
|
344
|
+
"status": "success",
|
|
345
|
+
"output": {"content": "Mocked response"}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
)
|
|
329
349
|
```
|
|
330
350
|
|
|
351
|
+
**Replay Mode Refresh:**
|
|
352
|
+
The `refresh` parameter forces a fresh API call while staying in replay mode:
|
|
353
|
+
- First run: Executes and caches the response
|
|
354
|
+
- Subsequent runs: Uses cached response (free!)
|
|
355
|
+
- With refresh: Bypasses cache, gets fresh response, updates cache
|
|
356
|
+
|
|
357
|
+
Perfect for updating cached responses after bot changes without switching modes.
|
|
358
|
+
|
|
331
359
|
## Testing Agents
|
|
332
360
|
|
|
333
361
|
Write fast, parallel agent tests using `agent_test_*` functions:
|
|
@@ -358,8 +386,17 @@ erdo agent-test tests/test_my_agent.py
|
|
|
358
386
|
|
|
359
387
|
# Verbose output
|
|
360
388
|
erdo agent-test tests/test_my_agent.py --verbose
|
|
389
|
+
|
|
390
|
+
# Refresh cached responses (bypass cache for all replay mode tests)
|
|
391
|
+
erdo agent-test tests/test_my_agent.py --refresh
|
|
361
392
|
```
|
|
362
393
|
|
|
394
|
+
**Refresh Flag:**
|
|
395
|
+
The `--refresh` flag forces all tests using `mode="replay"` to bypass cache and get fresh responses. Perfect for:
|
|
396
|
+
- Updating cached responses after bot changes
|
|
397
|
+
- Verifying tests with current LLM behavior
|
|
398
|
+
- No code changes needed - just add the flag!
|
|
399
|
+
|
|
363
400
|
### Test Helpers
|
|
364
401
|
|
|
365
402
|
The `erdo.test` module provides assertion helpers:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
erdo/__init__.py,sha256=
|
|
1
|
+
erdo/__init__.py,sha256=57AWoaFi3nxED99SJKdJpD_lPNiZE1AerfSIld6-XFc,1137
|
|
2
2
|
erdo/bot_permissions.py,sha256=NZSjjGURORpq3hRqv0tA0YvkxcTZ9MQqtmzrlmof29c,8261
|
|
3
3
|
erdo/cli_entry.py,sha256=jz18bWz-D09rmkEvwLTAbcK1shHUqFCpiU7U7czEltY,2057
|
|
4
4
|
erdo/formatting.py,sha256=jIfQlqk1oKratM8Fkrlq1rRsnergthbSQFCfeEd6rqY,8346
|
|
@@ -6,19 +6,19 @@ erdo/install_cli.py,sha256=jgRoSm-MIbLHW0Sr5m78q3htRwIh7JXo_wNmpVsiAcM,4454
|
|
|
6
6
|
erdo/integrations.py,sha256=-xYBpjYmjhJtv_UnaIfBb46cJC4icLOx9P1kOf5lM-g,4219
|
|
7
7
|
erdo/state.py,sha256=o89-SThDWYNjfwMZuDfier1Lx96_tLi5wYSth7dPI6I,14270
|
|
8
8
|
erdo/template.py,sha256=gvlSpEN3whD4LEqY6ppeYYZuFeM9ndU68qzKP-ETRJ8,4641
|
|
9
|
-
erdo/types.py,sha256=
|
|
9
|
+
erdo/types.py,sha256=OtCBZjG1cyNFq5hzd-dRcAnh0tQRNdTNifZYCk7WcOc,54027
|
|
10
10
|
erdo/_generated/__init__.py,sha256=jFLoVFeswecK5mGp4-MPWwnygol2dYIsEpnmm9gubHY,506
|
|
11
11
|
erdo/_generated/internal.py,sha256=ghd1g_9WF0kXO2MkFIBxcuHuRF00SlOeT-mjJcfQNyM,1569
|
|
12
12
|
erdo/_generated/internal_actions.py,sha256=dbZgHNF5RQhjyB8VAJ-4eaV2XUlqHKv99m-DtNNx7rI,2661
|
|
13
13
|
erdo/_generated/parameters.py,sha256=QC-_75fQg_iFu-dvI9ce4yHP9UnFItDWe6sBfzfXaig,472
|
|
14
14
|
erdo/_generated/secrets.py,sha256=F2xBkFsiYXOJnZ5Tcrc9uiaakyirHlKFkYCFxZKANtw,454
|
|
15
15
|
erdo/_generated/template_functions.py,sha256=mgFqsXxL36dMNptzDaGvNEjoA76nFf4k9ZRz-J4Ozdc,1174
|
|
16
|
-
erdo/_generated/types.py,sha256=
|
|
16
|
+
erdo/_generated/types.py,sha256=ecQN1OnrYQhoJ3Fk1eO3YDFycO9cTpYxiAg-3DkG5_0,129612
|
|
17
17
|
erdo/_generated/actions/__init__.py,sha256=L5YKDEe1RikpvtkWxmTAThVLa-HxGfur3kcO-S7RB_w,1223
|
|
18
18
|
erdo/_generated/actions/analysis.py,sha256=xuDxpd4NiS4bW_JAAQEhQa4Impmq_sBfSaUt3SYhf0g,5483
|
|
19
|
-
erdo/_generated/actions/bot.py,sha256=
|
|
20
|
-
erdo/_generated/actions/codeexec.py,sha256=
|
|
21
|
-
erdo/_generated/actions/llm.py,sha256=
|
|
19
|
+
erdo/_generated/actions/bot.py,sha256=VhDIgtHCcUDovDyXT14ow8qntKxe73KhOvN6FO3c454,6456
|
|
20
|
+
erdo/_generated/actions/codeexec.py,sha256=hwi46L1b7VLuRyq4kJVkTbv3aictP0ZLufdM4h5Kl_c,8076
|
|
21
|
+
erdo/_generated/actions/llm.py,sha256=CnAdNU-Ei3P_N6LSuvyuPzFvN8Zl-C6B6L7rgefPiAo,5282
|
|
22
22
|
erdo/_generated/actions/memory.py,sha256=cSzlVrR2tMobaO4VBGtRPmBv5NGpUHq36nAG3yQQFVg,16321
|
|
23
23
|
erdo/_generated/actions/pdfextractor.py,sha256=UQ10OQ3K3FNKkqx18aG67Xbqd3GjB0nNh7DA6mpNs2g,3535
|
|
24
24
|
erdo/_generated/actions/resource_definitions.py,sha256=0vzElFygA__fUYhgUOlcmyhkGwqLCNnxiPYcW6c3sDk,10787
|
|
@@ -32,8 +32,8 @@ erdo/conditions/__init__.py,sha256=xN7MS1aj4lh65CrE-94yFQXnQV8v8VjdEXMzPLTK0CU,3
|
|
|
32
32
|
erdo/config/__init__.py,sha256=lROKyMtaH5cMiF1RcIRA2NLR1Sp8AKWx4brcPT1Vwy8,147
|
|
33
33
|
erdo/config/config.py,sha256=BSerQVQdbbp1KuARpIuKp6bM1M4Cqr-Bsf0nQJJTND8,4279
|
|
34
34
|
erdo/invoke/__init__.py,sha256=QhMBmUR8OXuyFS8fbNntjpRd7LNeKlV8Y8x6qSjgQ0I,249
|
|
35
|
-
erdo/invoke/client.py,sha256=
|
|
36
|
-
erdo/invoke/invoke.py,sha256=
|
|
35
|
+
erdo/invoke/client.py,sha256=NNuZRQGvLFDENMCub4xLzEpihFYdc6PauKyA99pncsk,8523
|
|
36
|
+
erdo/invoke/invoke.py,sha256=1bYjUPCIrk7dNoe7f5fV19UgBo0rp_dGdePVQiLIa0M,21368
|
|
37
37
|
erdo/sync/__init__.py,sha256=f-7-9cYngOiLrTDAng3JxMC86JIE6jFk6-Vjz_dMybs,311
|
|
38
38
|
erdo/sync/client.py,sha256=LrcyrsapuyrqmuPh-UwbocL7WUhHZ9zkDItXzon_AUs,3022
|
|
39
39
|
erdo/sync/extractor.py,sha256=iAIlRJqHr4e69XYnir2mZ6F6cUjnXp0P_EKsG1jdDLc,17217
|
|
@@ -41,8 +41,8 @@ erdo/sync/sync.py,sha256=KzuChW5ramY8rX6QM3emRda928A09CQzFLmLJ5PLztg,10421
|
|
|
41
41
|
erdo/test/__init__.py,sha256=_OevGagFDTykFcYzA__FARXiS5kgT9tK90r_0TmyGQQ,1114
|
|
42
42
|
erdo/test/evaluate.py,sha256=-6vX6ynW8ICYaoXZLl-xdzAALvx1aSl1CurIRkgSJ0s,7730
|
|
43
43
|
erdo/test/runner.py,sha256=vZR9PLKT2nDMzHyPm_jniGSA9eddGk4p1dQq0h1dRms,7299
|
|
44
|
-
erdo-0.1.
|
|
45
|
-
erdo-0.1.
|
|
46
|
-
erdo-0.1.
|
|
47
|
-
erdo-0.1.
|
|
48
|
-
erdo-0.1.
|
|
44
|
+
erdo-0.1.12.dist-info/METADATA,sha256=QmEM3ox9LHEtOUrC1VZmrUzwpwUoHY_jsZD3Net6kMw,12712
|
|
45
|
+
erdo-0.1.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
46
|
+
erdo-0.1.12.dist-info/entry_points.txt,sha256=KFGSp8-6IE3-8dSr-3Djqye3IdEY65Y4E8fABoFUCHg,45
|
|
47
|
+
erdo-0.1.12.dist-info/licenses/LICENSE,sha256=9pdgUAuBAumY5tewMdJnx2Ozj8dS6gGKsSiY-SVInu4,1034
|
|
48
|
+
erdo-0.1.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|