erdo 0.1.7__tar.gz → 0.1.9__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.

Files changed (72) hide show
  1. {erdo-0.1.7 → erdo-0.1.9}/PKG-INFO +96 -9
  2. {erdo-0.1.7 → erdo-0.1.9}/README.md +94 -8
  3. {erdo-0.1.7 → erdo-0.1.9}/erdo/__init__.py +8 -10
  4. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/codeexec.py +4 -2
  5. erdo-0.1.9/erdo/config/__init__.py +5 -0
  6. erdo-0.1.9/erdo/formatting.py +279 -0
  7. {erdo-0.1.7 → erdo-0.1.9}/erdo/invoke/__init__.py +2 -1
  8. {erdo-0.1.7 → erdo-0.1.9}/erdo/invoke/client.py +23 -8
  9. erdo-0.1.9/erdo/invoke/invoke.py +469 -0
  10. {erdo-0.1.7 → erdo-0.1.9}/erdo/sync/extractor.py +6 -0
  11. erdo-0.1.9/erdo/test/__init__.py +41 -0
  12. erdo-0.1.9/erdo/test/evaluate.py +272 -0
  13. erdo-0.1.9/erdo/test/runner.py +263 -0
  14. {erdo-0.1.7 → erdo-0.1.9}/erdo/types.py +0 -311
  15. {erdo-0.1.7 → erdo-0.1.9}/pyproject.toml +6 -0
  16. erdo-0.1.7/.github/workflows/ci.yml +0 -66
  17. erdo-0.1.7/.github/workflows/publish.yml +0 -84
  18. erdo-0.1.7/CLAUDE.md +0 -246
  19. erdo-0.1.7/MANIFEST.in +0 -11
  20. erdo-0.1.7/PYPI_SETUP.md +0 -185
  21. erdo-0.1.7/VERSION +0 -1
  22. erdo-0.1.7/erdo/config/__init__.py +0 -5
  23. erdo-0.1.7/erdo/invoke/invoke.py +0 -244
  24. erdo-0.1.7/erdo/py.typed +0 -1
  25. erdo-0.1.7/examples/agent_centric_example.py +0 -153
  26. erdo-0.1.7/examples/analysis_files/analysis_result.json +0 -19
  27. erdo-0.1.7/examples/analysis_files/analyze.py +0 -175
  28. erdo-0.1.7/examples/analysis_files/utils.py +0 -196
  29. erdo-0.1.7/examples/bot_permissions_example.py +0 -115
  30. erdo-0.1.7/examples/simple_echo_agent.py +0 -31
  31. erdo-0.1.7/examples/state_example.py +0 -77
  32. erdo-0.1.7/examples/test_agent.py +0 -26
  33. erdo-0.1.7/examples/test_data.json +0 -129
  34. erdo-0.1.7/examples/test_echo_agent.py +0 -39
  35. erdo-0.1.7/setup.py +0 -18
  36. erdo-0.1.7/test_bot_permissions.py +0 -50
  37. erdo-0.1.7/tests/__init__.py +0 -1
  38. erdo-0.1.7/tests/test_basic.py +0 -136
  39. erdo-0.1.7/uv.lock +0 -1028
  40. {erdo-0.1.7 → erdo-0.1.9}/.gitignore +0 -0
  41. {erdo-0.1.7 → erdo-0.1.9}/LICENSE +0 -0
  42. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/__init__.py +0 -0
  43. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/__init__.py +0 -0
  44. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/analysis.py +0 -0
  45. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/bot.py +0 -0
  46. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/llm.py +0 -0
  47. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/memory.py +0 -0
  48. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/pdfextractor.py +0 -0
  49. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/resource_definitions.py +0 -0
  50. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/sqlexec.py +0 -0
  51. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/utils.py +0 -0
  52. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/webparser.py +0 -0
  53. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/actions/websearch.py +0 -0
  54. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/condition/__init__.py +145 -145
  55. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/internal.py +0 -0
  56. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/internal_actions.py +0 -0
  57. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/parameters.py +0 -0
  58. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/secrets.py +0 -0
  59. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/template_functions.py +0 -0
  60. {erdo-0.1.7 → erdo-0.1.9}/erdo/_generated/types.py +72 -72
  61. {erdo-0.1.7 → erdo-0.1.9}/erdo/actions/__init__.py +4 -4
  62. {erdo-0.1.7 → erdo-0.1.9}/erdo/bot_permissions.py +0 -0
  63. {erdo-0.1.7 → erdo-0.1.9}/erdo/cli_entry.py +0 -0
  64. {erdo-0.1.7 → erdo-0.1.9}/erdo/conditions/__init__.py +0 -0
  65. {erdo-0.1.7 → erdo-0.1.9}/erdo/config/config.py +0 -0
  66. {erdo-0.1.7 → erdo-0.1.9}/erdo/install_cli.py +0 -0
  67. {erdo-0.1.7 → erdo-0.1.9}/erdo/integrations.py +0 -0
  68. {erdo-0.1.7 → erdo-0.1.9}/erdo/state.py +0 -0
  69. {erdo-0.1.7 → erdo-0.1.9}/erdo/sync/__init__.py +0 -0
  70. {erdo-0.1.7 → erdo-0.1.9}/erdo/sync/client.py +0 -0
  71. {erdo-0.1.7 → erdo-0.1.9}/erdo/sync/sync.py +0 -0
  72. {erdo-0.1.7 → erdo-0.1.9}/erdo/template.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: erdo
3
- Version: 0.1.7
3
+ Version: 0.1.9
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
- ## CLI Integration
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
- Deploy your agents using the Erdo CLI:
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
- # Install the CLI
301
- pip install erdo
302
- erdo install-cli
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
- # Login to your Erdo account
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
- ## CLI Integration
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
- Deploy your agents using the Erdo CLI:
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
- # Install the CLI
263
- pip install erdo
264
- erdo install-cli
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
- # Login to your Erdo account
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
 
@@ -4,32 +4,30 @@
4
4
 
5
5
  # Import all condition functions from generated condition module
6
6
  from ._generated.condition import * # noqa: F403,F401
7
+ from ._generated.condition import __all__ as condition_all
7
8
 
8
9
  # Import all generated types automatically
9
10
  from ._generated.types import * # noqa: F403,F401
11
+ from ._generated.types import __all__ as generated_all
12
+
13
+ # Import invoke function for production and testing
14
+ from .invoke import invoke # noqa: F401
10
15
 
11
16
  # Import state object for template support
12
17
  from .state import state # noqa: F401
13
18
 
14
19
  # Import all handwritten SDK classes automatically
15
20
  from .types import * # noqa: F403,F401
21
+ from .types import __all__ as handwritten_all
16
22
 
17
23
  __version__ = "0.1.0"
18
24
 
19
- # Add all condition functions automatically
20
- from ._generated.condition import __all__ as condition_all # noqa: E402
21
-
22
- # Add all generated types automatically
23
- from ._generated.types import __all__ as generated_all # noqa: E402
24
-
25
- # Add handwritten SDK classes
26
- from .types import __all__ as handwritten_all # noqa: E402
27
-
28
25
  # Build __all__ dynamically by combining all available types
29
26
  __all__ = []
30
27
  __all__.extend(handwritten_all)
31
28
  __all__.extend(generated_all)
32
29
  __all__.extend(condition_all)
33
30
 
34
- # Add state to __all__ for explicit import
31
+ # Add state and invoke to __all__ for explicit import
35
32
  __all__.append("state")
33
+ __all__.append("invoke")
@@ -8,7 +8,7 @@ Actual execution happens in the Go backend after syncing.
8
8
 
9
9
  from typing import Any, Optional, Union
10
10
 
11
- from pydantic import BaseModel
11
+ from pydantic import BaseModel, Field
12
12
 
13
13
  from erdo.template import TemplateString
14
14
 
@@ -94,7 +94,9 @@ class ParseFileAsJsonResult(BaseModel):
94
94
  Result schema for codeexec.parse_file_as_json action.
95
95
  """
96
96
 
97
- json: Any
97
+ model_config = {"populate_by_name": True} # Allow both field names and aliases
98
+
99
+ json_data: Any = Field(alias="json")
98
100
 
99
101
 
100
102
  def execute(
@@ -0,0 +1,5 @@
1
+ """Configuration management for Erdo SDK."""
2
+
3
+ from .config import Config, get_config, set_config
4
+
5
+ __all__ = ["Config", "get_config", "set_config"]
@@ -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
- dataset_ids: Optional[list] = None,
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
- dataset_ids: Optional dataset IDs to include
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
- "parameters": parameters or {},
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 dataset_ids:
84
- invoke_params["dataset_ids"] = dataset_ids
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)