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 +5 -1
- erdo/config/__init__.py +2 -2
- erdo/formatting.py +279 -0
- erdo/invoke/__init__.py +2 -1
- erdo/invoke/client.py +23 -8
- erdo/invoke/invoke.py +255 -30
- erdo/test/__init__.py +41 -0
- erdo/test/evaluate.py +272 -0
- erdo/test/runner.py +263 -0
- erdo/types.py +0 -311
- {erdo-0.1.8.dist-info → erdo-0.1.9.dist-info}/METADATA +96 -9
- {erdo-0.1.8.dist-info → erdo-0.1.9.dist-info}/RECORD +15 -12
- erdo/py.typed +0 -1
- {erdo-0.1.8.dist-info → erdo-0.1.9.dist-info}/WHEEL +0 -0
- {erdo-0.1.8.dist-info → erdo-0.1.9.dist-info}/entry_points.txt +0 -0
- {erdo-0.1.8.dist-info → erdo-0.1.9.dist-info}/licenses/LICENSE +0 -0
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
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
|
-
|
|
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)
|