erdo 0.1.8__py3-none-any.whl → 0.1.9__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 CHANGED
@@ -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")
erdo/config/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Configuration management for Erdo SDK."""
2
2
 
3
- from .config import Config, get_config
3
+ from .config import Config, get_config, set_config
4
4
 
5
- __all__ = ["Config", "get_config"]
5
+ __all__ = ["Config", "get_config", "set_config"]
erdo/formatting.py ADDED
@@ -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()
erdo/invoke/__init__.py CHANGED
@@ -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
  ]
erdo/invoke/client.py CHANGED
@@ -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)