erdo 0.1.8__tar.gz → 0.1.10__tar.gz
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-0.1.8 → erdo-0.1.10}/PKG-INFO +96 -9
- {erdo-0.1.8 → erdo-0.1.10}/README.md +94 -8
- {erdo-0.1.8 → erdo-0.1.10}/erdo/__init__.py +5 -1
- erdo-0.1.10/erdo/config/__init__.py +5 -0
- erdo-0.1.10/erdo/formatting.py +279 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/invoke/__init__.py +2 -1
- {erdo-0.1.8 → erdo-0.1.10}/erdo/invoke/client.py +23 -8
- erdo-0.1.10/erdo/invoke/invoke.py +469 -0
- erdo-0.1.10/erdo/test/__init__.py +41 -0
- erdo-0.1.10/erdo/test/evaluate.py +272 -0
- erdo-0.1.10/erdo/test/runner.py +263 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/types.py +0 -311
- {erdo-0.1.8 → erdo-0.1.10}/pyproject.toml +6 -0
- erdo-0.1.8/.github/workflows/ci.yml +0 -66
- erdo-0.1.8/.github/workflows/publish.yml +0 -84
- erdo-0.1.8/CLAUDE.md +0 -246
- erdo-0.1.8/MANIFEST.in +0 -11
- erdo-0.1.8/PYPI_SETUP.md +0 -185
- erdo-0.1.8/VERSION +0 -1
- erdo-0.1.8/erdo/config/__init__.py +0 -5
- erdo-0.1.8/erdo/invoke/invoke.py +0 -244
- erdo-0.1.8/erdo/py.typed +0 -1
- erdo-0.1.8/examples/agent_centric_example.py +0 -153
- erdo-0.1.8/examples/analysis_files/analysis_result.json +0 -19
- erdo-0.1.8/examples/analysis_files/analyze.py +0 -175
- erdo-0.1.8/examples/analysis_files/utils.py +0 -196
- erdo-0.1.8/examples/bot_permissions_example.py +0 -115
- erdo-0.1.8/examples/simple_echo_agent.py +0 -31
- erdo-0.1.8/examples/state_example.py +0 -77
- erdo-0.1.8/examples/test_agent.py +0 -26
- erdo-0.1.8/examples/test_data.json +0 -129
- erdo-0.1.8/examples/test_echo_agent.py +0 -42
- erdo-0.1.8/setup.py +0 -18
- erdo-0.1.8/test_bot_permissions.py +0 -50
- erdo-0.1.8/tests/__init__.py +0 -1
- erdo-0.1.8/tests/test_basic.py +0 -136
- erdo-0.1.8/uv.lock +0 -1028
- {erdo-0.1.8 → erdo-0.1.10}/.gitignore +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/LICENSE +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/analysis.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/bot.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/codeexec.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/llm.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/memory.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/pdfextractor.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/resource_definitions.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/sqlexec.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/utils.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/webparser.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/actions/websearch.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/condition/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/internal.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/internal_actions.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/parameters.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/secrets.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/template_functions.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/_generated/types.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/actions/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/bot_permissions.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/cli_entry.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/conditions/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/config/config.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/install_cli.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/integrations.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/state.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/sync/__init__.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/sync/client.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/sync/extractor.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/sync/sync.py +0 -0
- {erdo-0.1.8 → erdo-0.1.10}/erdo/template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: erdo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
25
|
Requires-Python: >=3.9
|
|
@@ -292,20 +293,104 @@ main_step.on(
|
|
|
292
293
|
)
|
|
293
294
|
```
|
|
294
295
|
|
|
295
|
-
##
|
|
296
|
+
## Invoking Agents
|
|
297
|
+
|
|
298
|
+
Use the `invoke()` function to execute agents programmatically:
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from erdo import invoke
|
|
302
|
+
|
|
303
|
+
# Invoke an agent
|
|
304
|
+
response = invoke(
|
|
305
|
+
"data-question-answerer",
|
|
306
|
+
messages=[{"role": "user", "content": "What were Q4 sales?"}],
|
|
307
|
+
datasets=["sales-2024"],
|
|
308
|
+
parameters={"time_period": "Q4"},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if response.success:
|
|
312
|
+
print(response.result)
|
|
313
|
+
else:
|
|
314
|
+
print(f"Error: {response.error}")
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Invocation Modes
|
|
318
|
+
|
|
319
|
+
- **Live Mode** (default): Runs against real backend with LLM API
|
|
320
|
+
- **Replay Mode**: Uses cached responses - free after first run (perfect for testing!)
|
|
321
|
+
- **Mock Mode**: Returns synthetic responses - always free
|
|
296
322
|
|
|
297
|
-
|
|
323
|
+
```python
|
|
324
|
+
# Replay mode - free after first run
|
|
325
|
+
response = invoke("my-agent", messages=[...], mode="replay")
|
|
326
|
+
|
|
327
|
+
# Mock mode - always free
|
|
328
|
+
response = invoke("my-agent", messages=[...], mode="mock")
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Testing Agents
|
|
332
|
+
|
|
333
|
+
Write fast, parallel agent tests using `agent_test_*` functions:
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from erdo import invoke
|
|
337
|
+
from erdo.test import text_contains
|
|
338
|
+
|
|
339
|
+
def agent_test_csv_sales():
|
|
340
|
+
"""Test CSV sales analysis."""
|
|
341
|
+
response = invoke(
|
|
342
|
+
"data-question-answerer",
|
|
343
|
+
messages=[{"role": "user", "content": "What were Q4 sales?"}],
|
|
344
|
+
datasets=["sales-q4-2024"],
|
|
345
|
+
mode="replay", # Free after first run!
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert response.success
|
|
349
|
+
result_text = str(response.result)
|
|
350
|
+
assert text_contains(result_text, "sales", case_sensitive=False)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Run tests in parallel with the CLI:
|
|
298
354
|
|
|
299
355
|
```bash
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
|
|
356
|
+
# Run all tests
|
|
357
|
+
erdo agent-test tests/test_my_agent.py
|
|
358
|
+
|
|
359
|
+
# Verbose output
|
|
360
|
+
erdo agent-test tests/test_my_agent.py --verbose
|
|
361
|
+
```
|
|
303
362
|
|
|
304
|
-
|
|
363
|
+
### Test Helpers
|
|
364
|
+
|
|
365
|
+
The `erdo.test` module provides assertion helpers:
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
from erdo.test import (
|
|
369
|
+
text_contains, # Check if text contains substring
|
|
370
|
+
text_equals, # Check exact match
|
|
371
|
+
text_matches, # Check regex pattern
|
|
372
|
+
json_path_equals, # Check JSON path value
|
|
373
|
+
json_path_exists, # Check if JSON path exists
|
|
374
|
+
has_dataset, # Check if dataset is present
|
|
375
|
+
)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## CLI Integration
|
|
379
|
+
|
|
380
|
+
Deploy and manage your agents using the Erdo CLI:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
# Login to your account
|
|
305
384
|
erdo login
|
|
306
385
|
|
|
307
|
-
# Sync your agents
|
|
308
|
-
erdo sync
|
|
386
|
+
# Sync your agents to the platform
|
|
387
|
+
erdo sync-agent my_agent.py
|
|
388
|
+
|
|
389
|
+
# Invoke an agent
|
|
390
|
+
erdo invoke my-agent --message "Hello!"
|
|
391
|
+
|
|
392
|
+
# Run agent tests
|
|
393
|
+
erdo agent-test tests/test_my_agent.py
|
|
309
394
|
```
|
|
310
395
|
|
|
311
396
|
## Examples
|
|
@@ -314,6 +399,8 @@ See the `examples/` directory for complete examples:
|
|
|
314
399
|
|
|
315
400
|
- `agent_centric_example.py` - Comprehensive agent with multiple steps
|
|
316
401
|
- `state_example.py` - State management and templating
|
|
402
|
+
- `invoke_example.py` - Agent invocation patterns
|
|
403
|
+
- `agent_test_example.py` - Agent testing examples
|
|
317
404
|
|
|
318
405
|
## API Reference
|
|
319
406
|
|
|
@@ -254,20 +254,104 @@ main_step.on(
|
|
|
254
254
|
)
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
##
|
|
257
|
+
## Invoking Agents
|
|
258
|
+
|
|
259
|
+
Use the `invoke()` function to execute agents programmatically:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from erdo import invoke
|
|
263
|
+
|
|
264
|
+
# Invoke an agent
|
|
265
|
+
response = invoke(
|
|
266
|
+
"data-question-answerer",
|
|
267
|
+
messages=[{"role": "user", "content": "What were Q4 sales?"}],
|
|
268
|
+
datasets=["sales-2024"],
|
|
269
|
+
parameters={"time_period": "Q4"},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if response.success:
|
|
273
|
+
print(response.result)
|
|
274
|
+
else:
|
|
275
|
+
print(f"Error: {response.error}")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Invocation Modes
|
|
279
|
+
|
|
280
|
+
- **Live Mode** (default): Runs against real backend with LLM API
|
|
281
|
+
- **Replay Mode**: Uses cached responses - free after first run (perfect for testing!)
|
|
282
|
+
- **Mock Mode**: Returns synthetic responses - always free
|
|
258
283
|
|
|
259
|
-
|
|
284
|
+
```python
|
|
285
|
+
# Replay mode - free after first run
|
|
286
|
+
response = invoke("my-agent", messages=[...], mode="replay")
|
|
287
|
+
|
|
288
|
+
# Mock mode - always free
|
|
289
|
+
response = invoke("my-agent", messages=[...], mode="mock")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Testing Agents
|
|
293
|
+
|
|
294
|
+
Write fast, parallel agent tests using `agent_test_*` functions:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from erdo import invoke
|
|
298
|
+
from erdo.test import text_contains
|
|
299
|
+
|
|
300
|
+
def agent_test_csv_sales():
|
|
301
|
+
"""Test CSV sales analysis."""
|
|
302
|
+
response = invoke(
|
|
303
|
+
"data-question-answerer",
|
|
304
|
+
messages=[{"role": "user", "content": "What were Q4 sales?"}],
|
|
305
|
+
datasets=["sales-q4-2024"],
|
|
306
|
+
mode="replay", # Free after first run!
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
assert response.success
|
|
310
|
+
result_text = str(response.result)
|
|
311
|
+
assert text_contains(result_text, "sales", case_sensitive=False)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Run tests in parallel with the CLI:
|
|
260
315
|
|
|
261
316
|
```bash
|
|
262
|
-
#
|
|
263
|
-
|
|
264
|
-
|
|
317
|
+
# Run all tests
|
|
318
|
+
erdo agent-test tests/test_my_agent.py
|
|
319
|
+
|
|
320
|
+
# Verbose output
|
|
321
|
+
erdo agent-test tests/test_my_agent.py --verbose
|
|
322
|
+
```
|
|
265
323
|
|
|
266
|
-
|
|
324
|
+
### Test Helpers
|
|
325
|
+
|
|
326
|
+
The `erdo.test` module provides assertion helpers:
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
from erdo.test import (
|
|
330
|
+
text_contains, # Check if text contains substring
|
|
331
|
+
text_equals, # Check exact match
|
|
332
|
+
text_matches, # Check regex pattern
|
|
333
|
+
json_path_equals, # Check JSON path value
|
|
334
|
+
json_path_exists, # Check if JSON path exists
|
|
335
|
+
has_dataset, # Check if dataset is present
|
|
336
|
+
)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## CLI Integration
|
|
340
|
+
|
|
341
|
+
Deploy and manage your agents using the Erdo CLI:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# Login to your account
|
|
267
345
|
erdo login
|
|
268
346
|
|
|
269
|
-
# Sync your agents
|
|
270
|
-
erdo sync
|
|
347
|
+
# Sync your agents to the platform
|
|
348
|
+
erdo sync-agent my_agent.py
|
|
349
|
+
|
|
350
|
+
# Invoke an agent
|
|
351
|
+
erdo invoke my-agent --message "Hello!"
|
|
352
|
+
|
|
353
|
+
# Run agent tests
|
|
354
|
+
erdo agent-test tests/test_my_agent.py
|
|
271
355
|
```
|
|
272
356
|
|
|
273
357
|
## Examples
|
|
@@ -276,6 +360,8 @@ See the `examples/` directory for complete examples:
|
|
|
276
360
|
|
|
277
361
|
- `agent_centric_example.py` - Comprehensive agent with multiple steps
|
|
278
362
|
- `state_example.py` - State management and templating
|
|
363
|
+
- `invoke_example.py` - Agent invocation patterns
|
|
364
|
+
- `agent_test_example.py` - Agent testing examples
|
|
279
365
|
|
|
280
366
|
## API Reference
|
|
281
367
|
|
|
@@ -10,6 +10,9 @@ from ._generated.condition import __all__ as condition_all
|
|
|
10
10
|
from ._generated.types import * # noqa: F403,F401
|
|
11
11
|
from ._generated.types import __all__ as generated_all
|
|
12
12
|
|
|
13
|
+
# Import invoke function for production and testing
|
|
14
|
+
from .invoke import invoke # noqa: F401
|
|
15
|
+
|
|
13
16
|
# Import state object for template support
|
|
14
17
|
from .state import state # noqa: F401
|
|
15
18
|
|
|
@@ -25,5 +28,6 @@ __all__.extend(handwritten_all)
|
|
|
25
28
|
__all__.extend(generated_all)
|
|
26
29
|
__all__.extend(condition_all)
|
|
27
30
|
|
|
28
|
-
# Add state to __all__ for explicit import
|
|
31
|
+
# Add state and invoke to __all__ for explicit import
|
|
29
32
|
__all__.append("state")
|
|
33
|
+
__all__.append("invoke")
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Output formatting helpers for bot invocations.
|
|
2
|
+
|
|
3
|
+
This module provides utilities to parse and format bot invocation events
|
|
4
|
+
into human-readable output. Use these for displaying invocation results
|
|
5
|
+
in terminals, scripts, or other user-facing contexts.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from erdo import invoke
|
|
9
|
+
>>> from erdo.formatting import format_invocation
|
|
10
|
+
>>>
|
|
11
|
+
>>> response = invoke("my_agent", messages=[...])
|
|
12
|
+
>>> print(format_invocation(response))
|
|
13
|
+
Bot: my agent
|
|
14
|
+
Invocation ID: abc-123
|
|
15
|
+
|
|
16
|
+
Result:
|
|
17
|
+
The answer is 4
|
|
18
|
+
|
|
19
|
+
>>> # Verbose mode shows steps
|
|
20
|
+
>>> print(format_invocation(response, verbose=True))
|
|
21
|
+
Bot: my agent
|
|
22
|
+
Invocation ID: abc-123
|
|
23
|
+
|
|
24
|
+
Steps:
|
|
25
|
+
✓ step1 (utils.echo)
|
|
26
|
+
✓ step2 (llm.message)
|
|
27
|
+
|
|
28
|
+
Result:
|
|
29
|
+
The answer is 4
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from typing import Any, Dict, List, Optional
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class StepInfo:
|
|
39
|
+
"""Information about a single step execution."""
|
|
40
|
+
|
|
41
|
+
key: str
|
|
42
|
+
action: str
|
|
43
|
+
status: str = "completed"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class InvocationSummary:
|
|
48
|
+
"""Structured summary of a bot invocation.
|
|
49
|
+
|
|
50
|
+
This provides a clean, parsed view of the invocation events
|
|
51
|
+
with key information extracted and organized.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
bot_name: Optional[str] = None
|
|
55
|
+
bot_key: Optional[str] = None
|
|
56
|
+
invocation_id: Optional[str] = None
|
|
57
|
+
steps: List[StepInfo] = field(default_factory=list)
|
|
58
|
+
result: Optional[Any] = None
|
|
59
|
+
error: Optional[str] = None
|
|
60
|
+
success: bool = True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_invocation_events(
|
|
64
|
+
events: List[Dict[str, Any]],
|
|
65
|
+
bot_key: Optional[str] = None,
|
|
66
|
+
invocation_id: Optional[str] = None,
|
|
67
|
+
) -> InvocationSummary:
|
|
68
|
+
"""Parse raw invocation events into a structured summary.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
events: List of event dictionaries from the backend
|
|
72
|
+
bot_key: Bot key to use as fallback if not in events
|
|
73
|
+
invocation_id: Invocation ID to use as fallback if not in events
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
InvocationSummary with parsed information
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
>>> summary = parse_invocation_events(response.events, bot_key="my_agent")
|
|
80
|
+
>>> print(summary.bot_name)
|
|
81
|
+
'my agent'
|
|
82
|
+
>>> print(summary.steps)
|
|
83
|
+
[StepInfo(key='step1', action='utils.echo', status='completed')]
|
|
84
|
+
"""
|
|
85
|
+
summary = InvocationSummary(
|
|
86
|
+
bot_key=bot_key,
|
|
87
|
+
invocation_id=invocation_id,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
steps_seen = set()
|
|
91
|
+
final_messages = []
|
|
92
|
+
|
|
93
|
+
for event in events:
|
|
94
|
+
# Events are dicts with 'payload' and 'metadata'
|
|
95
|
+
payload = event.get("payload", {})
|
|
96
|
+
metadata = event.get("metadata", {})
|
|
97
|
+
|
|
98
|
+
# Extract invocation ID if not set
|
|
99
|
+
if summary.invocation_id is None:
|
|
100
|
+
if "invocation_id" in payload:
|
|
101
|
+
summary.invocation_id = payload["invocation_id"]
|
|
102
|
+
elif "invocation_id" in metadata:
|
|
103
|
+
summary.invocation_id = metadata["invocation_id"]
|
|
104
|
+
|
|
105
|
+
# Extract bot name
|
|
106
|
+
if summary.bot_name is None and "bot_name" in payload:
|
|
107
|
+
summary.bot_name = payload["bot_name"]
|
|
108
|
+
|
|
109
|
+
# Track step execution
|
|
110
|
+
if "action_type" in payload and "key" in payload:
|
|
111
|
+
step_key = payload["key"]
|
|
112
|
+
# Only add if not already present (avoid duplicates)
|
|
113
|
+
if step_key not in steps_seen:
|
|
114
|
+
steps_seen.add(step_key)
|
|
115
|
+
summary.steps.append(
|
|
116
|
+
StepInfo(
|
|
117
|
+
key=step_key,
|
|
118
|
+
action=payload["action_type"],
|
|
119
|
+
status="completed",
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Collect user-visible text messages (final output)
|
|
124
|
+
if isinstance(payload, str) and metadata.get("user_visibility") == "visible":
|
|
125
|
+
if metadata.get("message_content_id"): # It's part of a message
|
|
126
|
+
final_messages.append(payload)
|
|
127
|
+
|
|
128
|
+
# Combine final messages into result
|
|
129
|
+
if final_messages:
|
|
130
|
+
summary.result = "".join(final_messages)
|
|
131
|
+
|
|
132
|
+
return summary
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def format_invocation(
|
|
136
|
+
response: Any,
|
|
137
|
+
mode: str = "text",
|
|
138
|
+
verbose: bool = False,
|
|
139
|
+
) -> str:
|
|
140
|
+
"""Format a bot invocation response for display.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
response: InvokeResult from invoke()
|
|
144
|
+
mode: Output mode - 'text' or 'json'
|
|
145
|
+
verbose: Show detailed step execution (only applies to text mode)
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Formatted string ready to print
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> from erdo import invoke
|
|
152
|
+
>>> from erdo.formatting import format_invocation
|
|
153
|
+
>>>
|
|
154
|
+
>>> response = invoke("my_agent", messages=[...])
|
|
155
|
+
>>>
|
|
156
|
+
>>> # Simple text output
|
|
157
|
+
>>> print(format_invocation(response))
|
|
158
|
+
>>>
|
|
159
|
+
>>> # Verbose text output (shows steps)
|
|
160
|
+
>>> print(format_invocation(response, verbose=True))
|
|
161
|
+
>>>
|
|
162
|
+
>>> # JSON output
|
|
163
|
+
>>> print(format_invocation(response, mode='json'))
|
|
164
|
+
"""
|
|
165
|
+
if mode == "json":
|
|
166
|
+
return _format_as_json(response)
|
|
167
|
+
else:
|
|
168
|
+
return _format_as_text(response, verbose)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _format_as_json(response: Any) -> str:
|
|
172
|
+
"""Format response as JSON."""
|
|
173
|
+
output = {
|
|
174
|
+
"success": response.success,
|
|
175
|
+
"invocation_id": response.invocation_id,
|
|
176
|
+
"result": response.result,
|
|
177
|
+
"error": response.error,
|
|
178
|
+
"events": response.events,
|
|
179
|
+
}
|
|
180
|
+
return json.dumps(output, indent=2)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _format_as_text(response: Any, verbose: bool = False) -> str:
|
|
184
|
+
"""Format response as human-readable text."""
|
|
185
|
+
# Get bot key from response if available
|
|
186
|
+
bot_key = getattr(response, "bot_id", None)
|
|
187
|
+
invocation_id = response.invocation_id
|
|
188
|
+
|
|
189
|
+
# Parse events to extract information
|
|
190
|
+
# Handle SDK response structure: response.result may contain {'events': [...]}
|
|
191
|
+
events_to_process = response.events
|
|
192
|
+
if isinstance(response.result, dict) and "events" in response.result:
|
|
193
|
+
events_to_process = response.result["events"]
|
|
194
|
+
|
|
195
|
+
summary = parse_invocation_events(
|
|
196
|
+
events_to_process,
|
|
197
|
+
bot_key=bot_key,
|
|
198
|
+
invocation_id=invocation_id,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Build output
|
|
202
|
+
lines = []
|
|
203
|
+
|
|
204
|
+
if not response.success:
|
|
205
|
+
lines.append(f"❌ Invocation failed: {response.error}")
|
|
206
|
+
return "\n".join(lines)
|
|
207
|
+
|
|
208
|
+
# Header
|
|
209
|
+
lines.append(f"Bot: {summary.bot_name or summary.bot_key or 'unknown'}")
|
|
210
|
+
lines.append(f"Invocation ID: {summary.invocation_id or 'N/A'}")
|
|
211
|
+
|
|
212
|
+
# Steps (verbose mode)
|
|
213
|
+
if verbose and summary.steps:
|
|
214
|
+
lines.append("")
|
|
215
|
+
lines.append("Steps:")
|
|
216
|
+
for step in summary.steps:
|
|
217
|
+
status_icon = "✓" if step.status == "completed" else "•"
|
|
218
|
+
lines.append(f" {status_icon} {step.key} ({step.action})")
|
|
219
|
+
|
|
220
|
+
# Result
|
|
221
|
+
if summary.result:
|
|
222
|
+
lines.append("")
|
|
223
|
+
lines.append("Result:")
|
|
224
|
+
|
|
225
|
+
# Try to parse as JSON for pretty printing
|
|
226
|
+
try:
|
|
227
|
+
parsed = json.loads(summary.result)
|
|
228
|
+
lines.append(json.dumps(parsed, indent=2))
|
|
229
|
+
except (json.JSONDecodeError, TypeError):
|
|
230
|
+
# If not JSON, print as is
|
|
231
|
+
lines.append(summary.result)
|
|
232
|
+
elif response.result is not None:
|
|
233
|
+
lines.append("")
|
|
234
|
+
lines.append("Result:")
|
|
235
|
+
if isinstance(response.result, (dict, list)):
|
|
236
|
+
lines.append(json.dumps(response.result, indent=2))
|
|
237
|
+
else:
|
|
238
|
+
lines.append(str(response.result))
|
|
239
|
+
|
|
240
|
+
return "\n".join(lines)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Convenience method to add to InvokeResult
|
|
244
|
+
def add_format_method():
|
|
245
|
+
"""Add format() method to InvokeResult class.
|
|
246
|
+
|
|
247
|
+
This is called during SDK initialization to add the format()
|
|
248
|
+
method to InvokeResult instances.
|
|
249
|
+
"""
|
|
250
|
+
try:
|
|
251
|
+
from .invoke.invoke import InvokeResult
|
|
252
|
+
|
|
253
|
+
def format_method(self, mode="text", verbose=False):
|
|
254
|
+
"""Format this invocation result for display.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
mode: 'text' or 'json'
|
|
258
|
+
verbose: Show step details (text mode only)
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Formatted string
|
|
262
|
+
|
|
263
|
+
Example:
|
|
264
|
+
>>> response = invoke("my_agent", messages=[...])
|
|
265
|
+
>>> print(response.format())
|
|
266
|
+
>>> print(response.format(verbose=True))
|
|
267
|
+
"""
|
|
268
|
+
return format_invocation(self, mode=mode, verbose=verbose)
|
|
269
|
+
|
|
270
|
+
# Add method to class
|
|
271
|
+
InvokeResult.format = format_method
|
|
272
|
+
|
|
273
|
+
except ImportError:
|
|
274
|
+
# InvokeResult not available, skip
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Auto-add format method on import
|
|
279
|
+
add_format_method()
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Invoke module for running agents via the Erdo orchestrator."""
|
|
2
2
|
|
|
3
|
-
from .invoke import Invoke, InvokeResult, invoke_agent, invoke_by_key
|
|
3
|
+
from .invoke import Invoke, InvokeResult, invoke, invoke_agent, invoke_by_key
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
6
|
"Invoke",
|
|
7
7
|
"InvokeResult",
|
|
8
|
+
"invoke",
|
|
8
9
|
"invoke_agent",
|
|
9
10
|
"invoke_by_key",
|
|
10
11
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""API client for invoking bots via the backend orchestrator."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import Any, Dict, Generator, Optional, Union
|
|
4
|
+
from typing import Any, Dict, Generator, List, Optional, Union
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
@@ -50,16 +50,20 @@ class InvokeClient:
|
|
|
50
50
|
def invoke_bot(
|
|
51
51
|
self,
|
|
52
52
|
bot_identifier: str,
|
|
53
|
+
messages: Optional[List[Dict[str, str]]] = None,
|
|
53
54
|
parameters: Optional[Dict[str, Any]] = None,
|
|
54
|
-
|
|
55
|
+
dataset_slugs: Optional[list] = None,
|
|
56
|
+
mode: Optional[str] = None,
|
|
55
57
|
stream: bool = False,
|
|
56
58
|
) -> Union[SSEClient, Dict[str, Any]]:
|
|
57
59
|
"""Invoke a bot via the backend orchestrator.
|
|
58
60
|
|
|
59
61
|
Args:
|
|
60
62
|
bot_identifier: Bot ID or key (e.g., "erdo.data-analyzer")
|
|
63
|
+
messages: Messages in format [{"role": "user", "content": "..."}]
|
|
61
64
|
parameters: Parameters to pass to the bot
|
|
62
|
-
|
|
65
|
+
dataset_slugs: Optional dataset slugs to include
|
|
66
|
+
mode: Invocation mode: "live" (default), "replay" (cached), or "mock" (synthetic)
|
|
63
67
|
stream: Whether to return SSE client for streaming (default: False)
|
|
64
68
|
|
|
65
69
|
Returns:
|
|
@@ -76,12 +80,23 @@ class InvokeClient:
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
# Build invoke parameters
|
|
79
|
-
invoke_params: Dict[str, Any] = {
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
invoke_params: Dict[str, Any] = {}
|
|
84
|
+
|
|
85
|
+
# Add messages if provided
|
|
86
|
+
if messages:
|
|
87
|
+
invoke_params["messages"] = messages
|
|
88
|
+
|
|
89
|
+
# Add parameters if provided
|
|
90
|
+
if parameters:
|
|
91
|
+
invoke_params["parameters"] = parameters
|
|
92
|
+
|
|
93
|
+
# Add dataset slugs if provided
|
|
94
|
+
if dataset_slugs:
|
|
95
|
+
invoke_params["dataset_slugs"] = dataset_slugs
|
|
82
96
|
|
|
83
|
-
if
|
|
84
|
-
|
|
97
|
+
# Add mode if provided (live/replay/mock)
|
|
98
|
+
if mode:
|
|
99
|
+
invoke_params["mode"] = mode
|
|
85
100
|
|
|
86
101
|
# Make the request - always stream to handle SSE
|
|
87
102
|
response = requests.post(url, json=invoke_params, headers=headers, stream=True)
|