agno 2.3.15__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.
Files changed (82) hide show
  1. agno/agent/__init__.py +2 -0
  2. agno/agent/agent.py +4 -53
  3. agno/agent/remote.py +351 -0
  4. agno/client/__init__.py +3 -0
  5. agno/client/os.py +2669 -0
  6. agno/db/base.py +20 -0
  7. agno/db/mongo/async_mongo.py +11 -0
  8. agno/db/mongo/mongo.py +10 -0
  9. agno/db/mysql/async_mysql.py +9 -0
  10. agno/db/mysql/mysql.py +9 -0
  11. agno/db/postgres/async_postgres.py +9 -0
  12. agno/db/postgres/postgres.py +9 -0
  13. agno/db/postgres/utils.py +3 -2
  14. agno/db/sqlite/async_sqlite.py +9 -0
  15. agno/db/sqlite/sqlite.py +11 -1
  16. agno/exceptions.py +23 -0
  17. agno/knowledge/chunking/semantic.py +123 -46
  18. agno/knowledge/reader/csv_reader.py +9 -14
  19. agno/knowledge/reader/docx_reader.py +2 -4
  20. agno/knowledge/reader/field_labeled_csv_reader.py +11 -17
  21. agno/knowledge/reader/json_reader.py +9 -17
  22. agno/knowledge/reader/markdown_reader.py +6 -6
  23. agno/knowledge/reader/pdf_reader.py +14 -11
  24. agno/knowledge/reader/pptx_reader.py +2 -4
  25. agno/knowledge/reader/s3_reader.py +2 -11
  26. agno/knowledge/reader/text_reader.py +6 -18
  27. agno/knowledge/reader/web_search_reader.py +4 -15
  28. agno/os/app.py +104 -23
  29. agno/os/auth.py +25 -1
  30. agno/os/interfaces/a2a/a2a.py +7 -6
  31. agno/os/interfaces/a2a/router.py +13 -13
  32. agno/os/interfaces/agui/agui.py +5 -3
  33. agno/os/interfaces/agui/router.py +23 -16
  34. agno/os/interfaces/base.py +7 -7
  35. agno/os/interfaces/slack/router.py +6 -6
  36. agno/os/interfaces/slack/slack.py +7 -7
  37. agno/os/interfaces/whatsapp/router.py +29 -6
  38. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  39. agno/os/managers.py +326 -0
  40. agno/os/mcp.py +651 -79
  41. agno/os/router.py +125 -18
  42. agno/os/routers/agents/router.py +65 -22
  43. agno/os/routers/agents/schema.py +16 -4
  44. agno/os/routers/database.py +5 -0
  45. agno/os/routers/evals/evals.py +93 -11
  46. agno/os/routers/evals/utils.py +6 -6
  47. agno/os/routers/knowledge/knowledge.py +104 -16
  48. agno/os/routers/memory/memory.py +124 -7
  49. agno/os/routers/metrics/metrics.py +21 -4
  50. agno/os/routers/session/session.py +141 -12
  51. agno/os/routers/teams/router.py +40 -14
  52. agno/os/routers/teams/schema.py +12 -4
  53. agno/os/routers/traces/traces.py +54 -4
  54. agno/os/routers/workflows/router.py +223 -117
  55. agno/os/routers/workflows/schema.py +65 -1
  56. agno/os/schema.py +38 -12
  57. agno/os/utils.py +87 -166
  58. agno/remote/__init__.py +3 -0
  59. agno/remote/base.py +484 -0
  60. agno/run/workflow.py +1 -0
  61. agno/team/__init__.py +2 -0
  62. agno/team/remote.py +287 -0
  63. agno/team/team.py +25 -54
  64. agno/tracing/exporter.py +10 -6
  65. agno/tracing/setup.py +2 -1
  66. agno/utils/agent.py +58 -1
  67. agno/utils/http.py +68 -20
  68. agno/utils/os.py +0 -0
  69. agno/utils/remote.py +23 -0
  70. agno/vectordb/chroma/chromadb.py +452 -16
  71. agno/vectordb/pgvector/pgvector.py +7 -0
  72. agno/vectordb/redis/redisdb.py +1 -1
  73. agno/workflow/__init__.py +2 -0
  74. agno/workflow/agent.py +2 -2
  75. agno/workflow/remote.py +222 -0
  76. agno/workflow/types.py +0 -73
  77. agno/workflow/workflow.py +119 -68
  78. {agno-2.3.15.dist-info → agno-2.3.17.dist-info}/METADATA +1 -1
  79. {agno-2.3.15.dist-info → agno-2.3.17.dist-info}/RECORD +82 -72
  80. {agno-2.3.15.dist-info → agno-2.3.17.dist-info}/WHEEL +0 -0
  81. {agno-2.3.15.dist-info → agno-2.3.17.dist-info}/licenses/LICENSE +0 -0
  82. {agno-2.3.15.dist-info → agno-2.3.17.dist-info}/top_level.txt +0 -0
@@ -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"