erdo 0.1.4__py3-none-any.whl → 0.1.6__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/invoke/invoke.py ADDED
@@ -0,0 +1,244 @@
1
+ """Main invoke functionality for running agents via the orchestrator."""
2
+
3
+ import json
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from .client import InvokeClient
8
+
9
+
10
+ @dataclass
11
+ class InvokeResult:
12
+ """Result from a bot invocation."""
13
+
14
+ success: bool
15
+ bot_id: Optional[str] = None
16
+ invocation_id: Optional[str] = None
17
+ result: Optional[Any] = None
18
+ error: Optional[str] = None
19
+ events: List[Dict[str, Any]] = field(default_factory=list)
20
+
21
+ def __str__(self) -> str:
22
+ if self.success:
23
+ return f"✅ Invocation successful (ID: {self.invocation_id})"
24
+ else:
25
+ return f"❌ Invocation failed: {self.error}"
26
+
27
+ def get_final_result(self) -> Optional[Any]:
28
+ """Get the final result from the invocation."""
29
+ return self.result
30
+
31
+ def get_all_events(self) -> List[Dict[str, Any]]:
32
+ """Get all events from the invocation."""
33
+ return self.events
34
+
35
+
36
+ class Invoke:
37
+ """Main class for invoking agents."""
38
+
39
+ def __init__(
40
+ self,
41
+ agent: Optional[Any] = None,
42
+ parameters: Optional[Dict[str, Any]] = None,
43
+ dataset_ids: Optional[list] = None,
44
+ endpoint: Optional[str] = None,
45
+ auth_token: Optional[str] = None,
46
+ stream: bool = False,
47
+ print_events: bool = False,
48
+ ):
49
+ """Initialize and optionally invoke an agent immediately.
50
+
51
+ Args:
52
+ agent: Optional Agent instance to invoke immediately
53
+ parameters: Parameters to pass to the agent
54
+ dataset_ids: Dataset IDs to include
55
+ endpoint: API endpoint URL
56
+ auth_token: Authentication token
57
+ stream: Whether to stream events
58
+ print_events: Whether to print events as they arrive
59
+ """
60
+ self.client = InvokeClient(endpoint=endpoint, auth_token=auth_token)
61
+ self.print_events = print_events
62
+ self.result = None
63
+
64
+ # If an agent is provided, invoke it immediately
65
+ if agent:
66
+ bot_key = getattr(agent, "key", None)
67
+ if not bot_key:
68
+ raise ValueError("Agent must have a 'key' attribute for invocation")
69
+
70
+ self.result = self.invoke_by_key(
71
+ bot_key, parameters=parameters, dataset_ids=dataset_ids, stream=stream
72
+ )
73
+
74
+ def invoke_agent(
75
+ self,
76
+ agent: Any,
77
+ parameters: Optional[Dict[str, Any]] = None,
78
+ dataset_ids: Optional[list] = None,
79
+ stream: bool = False,
80
+ ) -> InvokeResult:
81
+ """Invoke an agent instance.
82
+
83
+ Args:
84
+ agent: Agent instance with a 'key' attribute
85
+ parameters: Parameters to pass to the agent
86
+ dataset_ids: Dataset IDs to include
87
+ stream: Whether to stream events
88
+
89
+ Returns:
90
+ InvokeResult with the outcome
91
+ """
92
+ bot_key = getattr(agent, "key", None)
93
+ if not bot_key:
94
+ raise ValueError("Agent must have a 'key' attribute for invocation")
95
+
96
+ return self.invoke_by_key(bot_key, parameters, dataset_ids, stream)
97
+
98
+ def invoke_by_key(
99
+ self,
100
+ bot_key: str,
101
+ parameters: Optional[Dict[str, Any]] = None,
102
+ dataset_ids: Optional[list] = None,
103
+ stream: bool = False,
104
+ ) -> InvokeResult:
105
+ """Invoke a bot by its key.
106
+
107
+ Args:
108
+ bot_key: Bot key (e.g., "erdo.data-analyzer")
109
+ parameters: Parameters to pass to the bot
110
+ dataset_ids: Dataset IDs to include
111
+ stream: Whether to stream events
112
+
113
+ Returns:
114
+ InvokeResult with the outcome
115
+ """
116
+ try:
117
+ response = self.client.invoke_bot(
118
+ bot_key, parameters=parameters, dataset_ids=dataset_ids, stream=stream
119
+ )
120
+
121
+ if stream:
122
+ # Process SSE events
123
+ events = []
124
+ final_result = None
125
+ invocation_id = None
126
+
127
+ # Type guard: response should be SSEClient when stream=True
128
+ if not isinstance(response, dict):
129
+ for event in response.events():
130
+ events.append(event)
131
+
132
+ if self.print_events:
133
+ self._print_event(event)
134
+
135
+ # Extract invocation ID from events
136
+ if "invocation_id" in event:
137
+ invocation_id = event["invocation_id"]
138
+
139
+ # Check for final result
140
+ if event.get("type") == "invocation_completed":
141
+ final_result = event.get("result")
142
+ elif event.get("type") == "result":
143
+ final_result = event.get("data")
144
+
145
+ return InvokeResult(
146
+ success=True,
147
+ bot_id=bot_key,
148
+ invocation_id=invocation_id,
149
+ result=final_result,
150
+ events=events,
151
+ )
152
+ else:
153
+ # Non-streaming response - response is a dict
154
+ response_dict = response if isinstance(response, dict) else {}
155
+ return InvokeResult(
156
+ success=True,
157
+ bot_id=bot_key,
158
+ result=response_dict,
159
+ events=[response_dict],
160
+ )
161
+
162
+ except Exception as e:
163
+ return InvokeResult(success=False, bot_id=bot_key, error=str(e))
164
+
165
+ def _print_event(self, event: Dict[str, Any]):
166
+ """Print an event in a readable format."""
167
+ event_type = event.get("type", "unknown")
168
+
169
+ if event_type == "step_started":
170
+ step_name = event.get("step_name", "Unknown step")
171
+ print(f"🔄 Step started: {step_name}")
172
+ elif event_type == "step_completed":
173
+ step_name = event.get("step_name", "Unknown step")
174
+ print(f"✅ Step completed: {step_name}")
175
+ elif event_type == "llm_chunk":
176
+ content = event.get("content", "")
177
+ print(content, end="", flush=True)
178
+ elif event_type == "invocation_completed":
179
+ print("\n✨ Invocation completed")
180
+ elif event_type == "error":
181
+ error = event.get("error", "Unknown error")
182
+ print(f"❌ Error: {error}")
183
+ else:
184
+ # Generic event printing
185
+ print(f"📡 {event_type}: {json.dumps(event, indent=2)}")
186
+
187
+
188
+ # Convenience functions
189
+ def invoke_agent(
190
+ agent: Any,
191
+ parameters: Optional[Dict[str, Any]] = None,
192
+ dataset_ids: Optional[list] = None,
193
+ stream: bool = False,
194
+ print_events: bool = False,
195
+ **kwargs,
196
+ ) -> InvokeResult:
197
+ """Invoke an agent instance.
198
+
199
+ Args:
200
+ agent: Agent instance with a 'key' attribute
201
+ parameters: Parameters to pass to the agent
202
+ dataset_ids: Dataset IDs to include
203
+ stream: Whether to stream events
204
+ print_events: Whether to print events
205
+ **kwargs: Additional arguments (endpoint, auth_token)
206
+
207
+ Returns:
208
+ InvokeResult with the outcome
209
+ """
210
+ invoke = Invoke(
211
+ endpoint=kwargs.get("endpoint"),
212
+ auth_token=kwargs.get("auth_token"),
213
+ print_events=print_events,
214
+ )
215
+ return invoke.invoke_agent(agent, parameters, dataset_ids, stream)
216
+
217
+
218
+ def invoke_by_key(
219
+ bot_key: str,
220
+ parameters: Optional[Dict[str, Any]] = None,
221
+ dataset_ids: Optional[list] = None,
222
+ stream: bool = False,
223
+ print_events: bool = False,
224
+ **kwargs,
225
+ ) -> InvokeResult:
226
+ """Invoke a bot by its key.
227
+
228
+ Args:
229
+ bot_key: Bot key (e.g., "erdo.data-analyzer")
230
+ parameters: Parameters to pass to the bot
231
+ dataset_ids: Dataset IDs to include
232
+ stream: Whether to stream events
233
+ print_events: Whether to print events
234
+ **kwargs: Additional arguments (endpoint, auth_token)
235
+
236
+ Returns:
237
+ InvokeResult with the outcome
238
+ """
239
+ invoke = Invoke(
240
+ endpoint=kwargs.get("endpoint"),
241
+ auth_token=kwargs.get("auth_token"),
242
+ print_events=print_events,
243
+ )
244
+ return invoke.invoke_by_key(bot_key, parameters, dataset_ids, stream)
erdo/sync/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """Sync module for Erdo SDK - sync agents to the backend."""
2
+
3
+ from .sync import (
4
+ Sync,
5
+ SyncResult,
6
+ sync_agent,
7
+ sync_agents_from_directory,
8
+ sync_agents_from_file,
9
+ )
10
+
11
+ __all__ = [
12
+ "Sync",
13
+ "SyncResult",
14
+ "sync_agent",
15
+ "sync_agents_from_file",
16
+ "sync_agents_from_directory",
17
+ ]
erdo/sync/client.py ADDED
@@ -0,0 +1,95 @@
1
+ """API client for syncing agents to the backend."""
2
+
3
+ import json
4
+ from typing import Any, Dict, Optional
5
+
6
+ import requests
7
+
8
+ from ..config import get_config
9
+ from .extractor import TemplateStringEncoder
10
+
11
+
12
+ class SyncClient:
13
+ """Client for syncing agents to the Erdo backend."""
14
+
15
+ def __init__(
16
+ self, endpoint: Optional[str] = None, auth_token: Optional[str] = None
17
+ ):
18
+ """Initialize the sync client.
19
+
20
+ Args:
21
+ endpoint: API endpoint URL. If not provided, uses config.
22
+ auth_token: Authentication token. If not provided, uses config.
23
+ """
24
+ config = get_config()
25
+ self.endpoint = endpoint or config.endpoint
26
+ self.auth_token = auth_token or config.auth_token
27
+
28
+ def upsert_bot(self, bot_request: Dict[str, Any]) -> str:
29
+ """Upsert a bot to the backend.
30
+
31
+ Args:
32
+ bot_request: The bot request data containing bot info, steps, etc.
33
+
34
+ Returns:
35
+ The bot ID of the upserted bot.
36
+
37
+ Raises:
38
+ requests.RequestException: If the API request fails.
39
+ ValueError: If the response is invalid.
40
+ """
41
+ url = f"{self.endpoint}/bot/upsert"
42
+ headers = {
43
+ "Content-Type": "application/json",
44
+ "Authorization": f"Bearer {self.auth_token}",
45
+ }
46
+
47
+ # Use custom encoder to handle special types
48
+ json_data = json.dumps(bot_request, cls=TemplateStringEncoder)
49
+ response = requests.post(url, data=json_data, headers=headers)
50
+
51
+ if response.status_code != 200:
52
+ error_msg = f"API request failed with status {response.status_code}"
53
+ try:
54
+ error_details = response.text
55
+ error_msg = f"{error_msg}: {error_details}"
56
+ except Exception:
57
+ pass
58
+ raise requests.RequestException(error_msg)
59
+
60
+ try:
61
+ result = response.json()
62
+ return result.get("bot_id", "")
63
+ except json.JSONDecodeError as e:
64
+ raise ValueError(f"Failed to decode response: {e}")
65
+
66
+ def sync_test(self, test_data: Dict[str, Any]) -> Dict[str, Any]:
67
+ """Sync a test to the backend.
68
+
69
+ Args:
70
+ test_data: The test data to sync.
71
+
72
+ Returns:
73
+ The response from the API.
74
+
75
+ Raises:
76
+ requests.RequestException: If the API request fails.
77
+ """
78
+ url = f"{self.endpoint}/test/sync"
79
+ headers = {
80
+ "Content-Type": "application/json",
81
+ "Authorization": f"Bearer {self.auth_token}",
82
+ }
83
+
84
+ response = requests.post(url, json=test_data, headers=headers)
85
+
86
+ if response.status_code != 200:
87
+ error_msg = f"API request failed with status {response.status_code}"
88
+ try:
89
+ error_details = response.text
90
+ error_msg = f"{error_msg}: {error_details}"
91
+ except Exception:
92
+ pass
93
+ raise requests.RequestException(error_msg)
94
+
95
+ return response.json()