agno 2.3.16__py3-none-any.whl → 2.3.17__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.
- agno/agent/__init__.py +2 -0
- agno/agent/agent.py +4 -53
- agno/agent/remote.py +351 -0
- agno/client/__init__.py +3 -0
- agno/client/os.py +2669 -0
- agno/db/base.py +20 -0
- agno/db/mongo/async_mongo.py +11 -0
- agno/db/mongo/mongo.py +10 -0
- agno/db/mysql/async_mysql.py +9 -0
- agno/db/mysql/mysql.py +9 -0
- agno/db/postgres/async_postgres.py +9 -0
- agno/db/postgres/postgres.py +9 -0
- agno/db/postgres/utils.py +3 -2
- agno/db/sqlite/async_sqlite.py +9 -0
- agno/db/sqlite/sqlite.py +11 -1
- agno/exceptions.py +23 -0
- agno/knowledge/chunking/semantic.py +123 -46
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/os/app.py +104 -23
- agno/os/auth.py +25 -1
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +13 -13
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +23 -16
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +6 -6
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +29 -6
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +651 -79
- agno/os/router.py +125 -18
- agno/os/routers/agents/router.py +65 -22
- agno/os/routers/agents/schema.py +16 -4
- agno/os/routers/database.py +5 -0
- agno/os/routers/evals/evals.py +93 -11
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/knowledge.py +104 -16
- agno/os/routers/memory/memory.py +124 -7
- agno/os/routers/metrics/metrics.py +21 -4
- agno/os/routers/session/session.py +141 -12
- agno/os/routers/teams/router.py +40 -14
- agno/os/routers/teams/schema.py +12 -4
- agno/os/routers/traces/traces.py +54 -4
- agno/os/routers/workflows/router.py +223 -117
- agno/os/routers/workflows/schema.py +65 -1
- agno/os/schema.py +38 -12
- agno/os/utils.py +87 -166
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +484 -0
- agno/run/workflow.py +1 -0
- agno/team/__init__.py +2 -0
- agno/team/remote.py +287 -0
- agno/team/team.py +25 -54
- agno/tracing/exporter.py +10 -6
- agno/tracing/setup.py +2 -1
- agno/utils/agent.py +58 -1
- agno/utils/http.py +68 -20
- agno/utils/os.py +0 -0
- agno/utils/remote.py +23 -0
- agno/vectordb/chroma/chromadb.py +452 -16
- agno/vectordb/pgvector/pgvector.py +7 -0
- agno/vectordb/redis/redisdb.py +1 -1
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +2 -2
- agno/workflow/remote.py +222 -0
- agno/workflow/types.py +0 -73
- agno/workflow/workflow.py +119 -68
- {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/METADATA +1 -1
- {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/RECORD +75 -65
- {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/WHEEL +0 -0
- {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/top_level.txt +0 -0
agno/workflow/remote.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Literal, Optional, Tuple, Union, overload
|
|
3
|
+
|
|
4
|
+
from fastapi import WebSocket
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from agno.media import Audio, File, Image, Video
|
|
8
|
+
from agno.models.message import Message
|
|
9
|
+
from agno.remote.base import BaseRemote, RemoteDb
|
|
10
|
+
from agno.run.workflow import WorkflowRunOutput, WorkflowRunOutputEvent
|
|
11
|
+
from agno.utils.agent import validate_input
|
|
12
|
+
from agno.utils.remote import serialize_input
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RemoteWorkflow(BaseRemote):
|
|
19
|
+
# Private cache for workflow config with TTL: (config, timestamp)
|
|
20
|
+
_cached_workflow_config: Optional[Tuple["WorkflowResponse", float]] = None
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
base_url: str,
|
|
25
|
+
workflow_id: str,
|
|
26
|
+
timeout: float = 300.0,
|
|
27
|
+
config_ttl: float = 300.0,
|
|
28
|
+
):
|
|
29
|
+
"""Initialize AgentOSRunner for local or remote execution.
|
|
30
|
+
|
|
31
|
+
For remote execution, provide base_url and workflow_id.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
base_url: Base URL for remote AgentOS instance (e.g., "http://localhost:7777")
|
|
35
|
+
workflow_id: ID of remote workflow
|
|
36
|
+
timeout: Request timeout in seconds (default: 300)
|
|
37
|
+
config_ttl: Time-to-live for cached config in seconds (default: 300)
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(base_url, timeout, config_ttl)
|
|
40
|
+
self.workflow_id = workflow_id
|
|
41
|
+
self._cached_workflow_config = None
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def id(self) -> str:
|
|
45
|
+
return self.workflow_id
|
|
46
|
+
|
|
47
|
+
async def get_workflow_config(self) -> "WorkflowResponse":
|
|
48
|
+
"""Get the workflow config from remote (always fetches fresh)."""
|
|
49
|
+
return await self.client.aget_workflow(self.workflow_id)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def _workflow_config(self) -> "WorkflowResponse":
|
|
53
|
+
"""Get the workflow config from remote, cached with TTL."""
|
|
54
|
+
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
55
|
+
|
|
56
|
+
current_time = time.time()
|
|
57
|
+
|
|
58
|
+
# Check if cache is valid
|
|
59
|
+
if self._cached_workflow_config is not None:
|
|
60
|
+
config, cached_at = self._cached_workflow_config
|
|
61
|
+
if current_time - cached_at < self.config_ttl:
|
|
62
|
+
return config
|
|
63
|
+
|
|
64
|
+
# Fetch fresh config
|
|
65
|
+
config: WorkflowResponse = self.client.get_workflow(self.workflow_id) # type: ignore
|
|
66
|
+
self._cached_workflow_config = (config, current_time)
|
|
67
|
+
return config
|
|
68
|
+
|
|
69
|
+
def refresh_config(self) -> "WorkflowResponse":
|
|
70
|
+
"""Force refresh the cached workflow config."""
|
|
71
|
+
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
72
|
+
|
|
73
|
+
config: WorkflowResponse = self.client.get_workflow(self.workflow_id)
|
|
74
|
+
self._cached_workflow_config = (config, time.time())
|
|
75
|
+
return config
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def name(self) -> Optional[str]:
|
|
79
|
+
if self._workflow_config is not None:
|
|
80
|
+
return self._workflow_config.name
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> Optional[str]:
|
|
85
|
+
if self._workflow_config is not None:
|
|
86
|
+
return self._workflow_config.description
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def db(self) -> Optional[RemoteDb]:
|
|
91
|
+
if self._workflow_config is not None and self._workflow_config.db_id is not None:
|
|
92
|
+
return RemoteDb.from_config(
|
|
93
|
+
db_id=self._workflow_config.db_id,
|
|
94
|
+
client=self.client,
|
|
95
|
+
config=self._config,
|
|
96
|
+
)
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
@overload
|
|
100
|
+
async def arun(
|
|
101
|
+
self,
|
|
102
|
+
input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]] = None,
|
|
103
|
+
additional_data: Optional[Dict[str, Any]] = None,
|
|
104
|
+
user_id: Optional[str] = None,
|
|
105
|
+
run_id: Optional[str] = None,
|
|
106
|
+
session_id: Optional[str] = None,
|
|
107
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
108
|
+
audio: Optional[List[Audio]] = None,
|
|
109
|
+
images: Optional[List[Image]] = None,
|
|
110
|
+
videos: Optional[List[Video]] = None,
|
|
111
|
+
files: Optional[List[File]] = None,
|
|
112
|
+
stream: Literal[False] = False,
|
|
113
|
+
stream_events: Optional[bool] = None,
|
|
114
|
+
stream_intermediate_steps: Optional[bool] = None,
|
|
115
|
+
background: Optional[bool] = False,
|
|
116
|
+
websocket: Optional[WebSocket] = None,
|
|
117
|
+
background_tasks: Optional[Any] = None,
|
|
118
|
+
auth_token: Optional[str] = None,
|
|
119
|
+
) -> WorkflowRunOutput: ...
|
|
120
|
+
|
|
121
|
+
@overload
|
|
122
|
+
def arun(
|
|
123
|
+
self,
|
|
124
|
+
input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]] = None,
|
|
125
|
+
additional_data: Optional[Dict[str, Any]] = None,
|
|
126
|
+
user_id: Optional[str] = None,
|
|
127
|
+
run_id: Optional[str] = None,
|
|
128
|
+
session_id: Optional[str] = None,
|
|
129
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
130
|
+
audio: Optional[List[Audio]] = None,
|
|
131
|
+
images: Optional[List[Image]] = None,
|
|
132
|
+
videos: Optional[List[Video]] = None,
|
|
133
|
+
files: Optional[List[File]] = None,
|
|
134
|
+
stream: Literal[True] = True,
|
|
135
|
+
stream_events: Optional[bool] = None,
|
|
136
|
+
stream_intermediate_steps: Optional[bool] = None,
|
|
137
|
+
background: Optional[bool] = False,
|
|
138
|
+
websocket: Optional[WebSocket] = None,
|
|
139
|
+
background_tasks: Optional[Any] = None,
|
|
140
|
+
auth_token: Optional[str] = None,
|
|
141
|
+
) -> AsyncIterator[WorkflowRunOutputEvent]: ...
|
|
142
|
+
|
|
143
|
+
def arun( # type: ignore
|
|
144
|
+
self,
|
|
145
|
+
input: Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]],
|
|
146
|
+
additional_data: Optional[Dict[str, Any]] = None,
|
|
147
|
+
user_id: Optional[str] = None,
|
|
148
|
+
run_id: Optional[str] = None,
|
|
149
|
+
session_id: Optional[str] = None,
|
|
150
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
151
|
+
audio: Optional[List[Audio]] = None,
|
|
152
|
+
images: Optional[List[Image]] = None,
|
|
153
|
+
videos: Optional[List[Video]] = None,
|
|
154
|
+
files: Optional[List[File]] = None,
|
|
155
|
+
stream: bool = False,
|
|
156
|
+
stream_events: Optional[bool] = None,
|
|
157
|
+
background: Optional[bool] = False,
|
|
158
|
+
websocket: Optional[WebSocket] = None,
|
|
159
|
+
background_tasks: Optional[Any] = None,
|
|
160
|
+
auth_token: Optional[str] = None,
|
|
161
|
+
**kwargs: Any,
|
|
162
|
+
) -> Union[WorkflowRunOutput, AsyncIterator[WorkflowRunOutputEvent]]:
|
|
163
|
+
# TODO: Deal with background
|
|
164
|
+
validated_input = validate_input(input)
|
|
165
|
+
serialized_input = serialize_input(validated_input)
|
|
166
|
+
headers = self._get_auth_headers(auth_token)
|
|
167
|
+
|
|
168
|
+
if stream:
|
|
169
|
+
# Handle streaming response
|
|
170
|
+
return self.get_client().run_workflow_stream(
|
|
171
|
+
workflow_id=self.workflow_id,
|
|
172
|
+
message=serialized_input,
|
|
173
|
+
additional_data=additional_data,
|
|
174
|
+
run_id=run_id,
|
|
175
|
+
session_id=session_id,
|
|
176
|
+
user_id=user_id,
|
|
177
|
+
audio=audio,
|
|
178
|
+
images=images,
|
|
179
|
+
videos=videos,
|
|
180
|
+
files=files,
|
|
181
|
+
session_state=session_state,
|
|
182
|
+
stream_events=stream_events,
|
|
183
|
+
headers=headers,
|
|
184
|
+
**kwargs,
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
return self.get_client().run_workflow( # type: ignore
|
|
188
|
+
workflow_id=self.workflow_id,
|
|
189
|
+
message=serialized_input,
|
|
190
|
+
additional_data=additional_data,
|
|
191
|
+
run_id=run_id,
|
|
192
|
+
session_id=session_id,
|
|
193
|
+
user_id=user_id,
|
|
194
|
+
audio=audio,
|
|
195
|
+
images=images,
|
|
196
|
+
videos=videos,
|
|
197
|
+
files=files,
|
|
198
|
+
session_state=session_state,
|
|
199
|
+
headers=headers,
|
|
200
|
+
**kwargs,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
async def cancel_run(self, run_id: str, auth_token: Optional[str] = None) -> bool:
|
|
204
|
+
"""Cancel a running workflow execution.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
run_id (str): The run_id to cancel.
|
|
208
|
+
auth_token: Optional JWT token for authentication.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
bool: True if the run was found and marked for cancellation, False otherwise.
|
|
212
|
+
"""
|
|
213
|
+
headers = self._get_auth_headers(auth_token)
|
|
214
|
+
try:
|
|
215
|
+
await self.get_client().cancel_workflow_run(
|
|
216
|
+
workflow_id=self.workflow_id,
|
|
217
|
+
run_id=run_id,
|
|
218
|
+
headers=headers,
|
|
219
|
+
)
|
|
220
|
+
return True
|
|
221
|
+
except Exception:
|
|
222
|
+
return False
|
agno/workflow/types.py
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
from enum import Enum
|
|
4
3
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
5
4
|
|
|
6
|
-
from fastapi import WebSocket
|
|
7
5
|
from pydantic import BaseModel
|
|
8
6
|
|
|
9
7
|
from agno.media import Audio, File, Image, Video
|
|
10
8
|
from agno.models.metrics import Metrics
|
|
11
9
|
from agno.session.workflow import WorkflowSession
|
|
12
|
-
from agno.utils.log import log_warning
|
|
13
10
|
from agno.utils.media import (
|
|
14
11
|
reconstruct_audio_list,
|
|
15
12
|
reconstruct_files,
|
|
16
13
|
reconstruct_images,
|
|
17
14
|
reconstruct_videos,
|
|
18
15
|
)
|
|
19
|
-
from agno.utils.serialize import json_serializer
|
|
20
16
|
from agno.utils.timer import Timer
|
|
21
17
|
|
|
22
18
|
|
|
@@ -477,75 +473,6 @@ class WorkflowMetrics:
|
|
|
477
473
|
self.duration = self.timer.elapsed
|
|
478
474
|
|
|
479
475
|
|
|
480
|
-
@dataclass
|
|
481
|
-
class WebSocketHandler:
|
|
482
|
-
"""Generic WebSocket handler for real-time workflow events"""
|
|
483
|
-
|
|
484
|
-
websocket: Optional[WebSocket] = None
|
|
485
|
-
|
|
486
|
-
def format_sse_event(self, json_data: str) -> str:
|
|
487
|
-
"""Parse JSON data into SSE-compliant format.
|
|
488
|
-
|
|
489
|
-
Args:
|
|
490
|
-
json_data: JSON string containing the event data
|
|
491
|
-
|
|
492
|
-
Returns:
|
|
493
|
-
SSE-formatted response with event type and data
|
|
494
|
-
"""
|
|
495
|
-
import json
|
|
496
|
-
|
|
497
|
-
try:
|
|
498
|
-
# Parse the JSON to extract the event type
|
|
499
|
-
data = json.loads(json_data)
|
|
500
|
-
event_type = data.get("event", "message")
|
|
501
|
-
|
|
502
|
-
# Format as SSE: event: <event_type>\ndata: <json_data>\n\n
|
|
503
|
-
return f"event: {event_type}\ndata: {json_data}\n\n"
|
|
504
|
-
except (json.JSONDecodeError, KeyError):
|
|
505
|
-
# Fallback to generic message event if parsing fails
|
|
506
|
-
return f"event: message\ndata: {json_data}\n\n"
|
|
507
|
-
|
|
508
|
-
async def handle_event(self, event: Any) -> None:
|
|
509
|
-
"""Handle an event object - serializes and sends via WebSocket"""
|
|
510
|
-
if not self.websocket:
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
try:
|
|
514
|
-
if hasattr(event, "to_dict"):
|
|
515
|
-
data = event.to_dict()
|
|
516
|
-
elif hasattr(event, "__dict__"):
|
|
517
|
-
data = event.__dict__
|
|
518
|
-
elif isinstance(event, dict):
|
|
519
|
-
data = event
|
|
520
|
-
else:
|
|
521
|
-
data = {"type": "message", "content": str(event)}
|
|
522
|
-
|
|
523
|
-
await self.websocket.send_text(self.format_sse_event(json.dumps(data, default=json_serializer)))
|
|
524
|
-
|
|
525
|
-
except Exception as e:
|
|
526
|
-
log_warning(f"Failed to handle WebSocket event: {e}")
|
|
527
|
-
|
|
528
|
-
async def handle_text(self, message: str) -> None:
|
|
529
|
-
"""Handle a plain text message"""
|
|
530
|
-
if not self.websocket:
|
|
531
|
-
return
|
|
532
|
-
|
|
533
|
-
try:
|
|
534
|
-
await self.websocket.send_text(self.format_sse_event(message))
|
|
535
|
-
except Exception as e:
|
|
536
|
-
log_warning(f"Failed to send WebSocket text: {e}")
|
|
537
|
-
|
|
538
|
-
async def handle_dict(self, data: Dict[str, Any]) -> None:
|
|
539
|
-
"""Handle a dictionary directly"""
|
|
540
|
-
if not self.websocket:
|
|
541
|
-
return
|
|
542
|
-
|
|
543
|
-
try:
|
|
544
|
-
await self.websocket.send_text(self.format_sse_event(json.dumps(data, default=json_serializer)))
|
|
545
|
-
except Exception as e:
|
|
546
|
-
log_warning(f"Failed to send WebSocket dict: {e}")
|
|
547
|
-
|
|
548
|
-
|
|
549
476
|
class StepType(str, Enum):
|
|
550
477
|
FUNCTION = "Function"
|
|
551
478
|
STEP = "Step"
|