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.
Files changed (75) 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 +1 -1
  19. agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
  20. agno/knowledge/reader/json_reader.py +1 -1
  21. agno/os/app.py +104 -23
  22. agno/os/auth.py +25 -1
  23. agno/os/interfaces/a2a/a2a.py +7 -6
  24. agno/os/interfaces/a2a/router.py +13 -13
  25. agno/os/interfaces/agui/agui.py +5 -3
  26. agno/os/interfaces/agui/router.py +23 -16
  27. agno/os/interfaces/base.py +7 -7
  28. agno/os/interfaces/slack/router.py +6 -6
  29. agno/os/interfaces/slack/slack.py +7 -7
  30. agno/os/interfaces/whatsapp/router.py +29 -6
  31. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  32. agno/os/managers.py +326 -0
  33. agno/os/mcp.py +651 -79
  34. agno/os/router.py +125 -18
  35. agno/os/routers/agents/router.py +65 -22
  36. agno/os/routers/agents/schema.py +16 -4
  37. agno/os/routers/database.py +5 -0
  38. agno/os/routers/evals/evals.py +93 -11
  39. agno/os/routers/evals/utils.py +6 -6
  40. agno/os/routers/knowledge/knowledge.py +104 -16
  41. agno/os/routers/memory/memory.py +124 -7
  42. agno/os/routers/metrics/metrics.py +21 -4
  43. agno/os/routers/session/session.py +141 -12
  44. agno/os/routers/teams/router.py +40 -14
  45. agno/os/routers/teams/schema.py +12 -4
  46. agno/os/routers/traces/traces.py +54 -4
  47. agno/os/routers/workflows/router.py +223 -117
  48. agno/os/routers/workflows/schema.py +65 -1
  49. agno/os/schema.py +38 -12
  50. agno/os/utils.py +87 -166
  51. agno/remote/__init__.py +3 -0
  52. agno/remote/base.py +484 -0
  53. agno/run/workflow.py +1 -0
  54. agno/team/__init__.py +2 -0
  55. agno/team/remote.py +287 -0
  56. agno/team/team.py +25 -54
  57. agno/tracing/exporter.py +10 -6
  58. agno/tracing/setup.py +2 -1
  59. agno/utils/agent.py +58 -1
  60. agno/utils/http.py +68 -20
  61. agno/utils/os.py +0 -0
  62. agno/utils/remote.py +23 -0
  63. agno/vectordb/chroma/chromadb.py +452 -16
  64. agno/vectordb/pgvector/pgvector.py +7 -0
  65. agno/vectordb/redis/redisdb.py +1 -1
  66. agno/workflow/__init__.py +2 -0
  67. agno/workflow/agent.py +2 -2
  68. agno/workflow/remote.py +222 -0
  69. agno/workflow/types.py +0 -73
  70. agno/workflow/workflow.py +119 -68
  71. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/METADATA +1 -1
  72. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/RECORD +75 -65
  73. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/WHEEL +0 -0
  74. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/licenses/LICENSE +0 -0
  75. {agno-2.3.16.dist-info → agno-2.3.17.dist-info}/top_level.txt +0 -0
agno/workflow/workflow.py CHANGED
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from datetime import datetime
5
5
  from os import getenv
6
6
  from typing import (
7
+ TYPE_CHECKING,
7
8
  Any,
8
9
  AsyncIterator,
9
10
  Awaitable,
@@ -24,6 +25,9 @@ from uuid import uuid4
24
25
  from fastapi import WebSocket
25
26
  from pydantic import BaseModel
26
27
 
28
+ if TYPE_CHECKING:
29
+ from agno.os.managers import WebSocketHandler
30
+
27
31
  from agno.agent.agent import Agent
28
32
  from agno.db.base import AsyncBaseDb, BaseDb, SessionType
29
33
  from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledException
@@ -53,7 +57,7 @@ from agno.run.workflow import (
53
57
  )
54
58
  from agno.session.workflow import WorkflowChatInteraction, WorkflowSession
55
59
  from agno.team.team import Team
56
- from agno.utils.common import is_typed_dict, validate_typed_dict
60
+ from agno.utils.agent import validate_input
57
61
  from agno.utils.log import (
58
62
  log_debug,
59
63
  log_error,
@@ -69,7 +73,7 @@ from agno.utils.print_response.workflow import (
69
73
  print_response,
70
74
  print_response_stream,
71
75
  )
72
- from agno.workflow import WorkflowAgent
76
+ from agno.workflow.agent import WorkflowAgent
73
77
  from agno.workflow.condition import Condition
74
78
  from agno.workflow.loop import Loop
75
79
  from agno.workflow.parallel import Parallel
@@ -81,7 +85,6 @@ from agno.workflow.types import (
81
85
  StepMetrics,
82
86
  StepOutput,
83
87
  StepType,
84
- WebSocketHandler,
85
88
  WorkflowExecutionInput,
86
89
  WorkflowMetrics,
87
90
  )
@@ -165,7 +168,7 @@ class Workflow:
165
168
  # Control whether to store executor responses (agent/team responses) in flattened runs
166
169
  store_executor_outputs: bool = True
167
170
 
168
- websocket_handler: Optional[WebSocketHandler] = None
171
+ websocket_handler: Optional["WebSocketHandler"] = None
169
172
 
170
173
  # Input schema to validate the input to the workflow
171
174
  input_schema: Optional[Type[BaseModel]] = None
@@ -267,59 +270,6 @@ class Workflow:
267
270
  def _has_async_db(self) -> bool:
268
271
  return self.db is not None and isinstance(self.db, AsyncBaseDb)
269
272
 
270
- def _validate_input(
271
- self, input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]]
272
- ) -> Optional[Union[str, List, Dict, Message, BaseModel]]:
273
- """Parse and validate input against input_schema if provided"""
274
- if self.input_schema is None:
275
- return input # Return input unchanged if no schema is set
276
-
277
- if input is None:
278
- raise ValueError("Input required when input_schema is set")
279
-
280
- # Handle Message objects - extract content
281
- if isinstance(input, Message):
282
- input = input.content # type: ignore
283
-
284
- # If input is a string, convert it to a dict
285
- if isinstance(input, str):
286
- import json
287
-
288
- try:
289
- input = json.loads(input)
290
- except Exception as e:
291
- raise ValueError(f"Failed to parse input. Is it a valid JSON string?: {e}")
292
-
293
- # Case 1: Message is already a BaseModel instance
294
- if isinstance(input, BaseModel):
295
- if isinstance(input, self.input_schema):
296
- try:
297
- return input
298
- except Exception as e:
299
- raise ValueError(f"BaseModel validation failed: {str(e)}")
300
- else:
301
- # Different BaseModel types
302
- raise ValueError(f"Expected {self.input_schema.__name__} but got {type(input).__name__}")
303
-
304
- # Case 2: Message is a dict
305
- elif isinstance(input, dict):
306
- try:
307
- # Check if the schema is a TypedDict
308
- if is_typed_dict(self.input_schema):
309
- validated_dict = validate_typed_dict(input, self.input_schema)
310
- return validated_dict
311
- else:
312
- validated_model = self.input_schema(**input)
313
- return validated_model
314
- except Exception as e:
315
- raise ValueError(f"Failed to parse dict into {self.input_schema.__name__}: {str(e)}")
316
-
317
- # Case 3: Other types not supported for structured input
318
- else:
319
- raise ValueError(
320
- f"Cannot validate {type(input)} against input_schema. Expected dict or {self.input_schema.__name__} instance."
321
- )
322
-
323
273
  @property
324
274
  def run_parameters(self) -> Dict[str, Any]:
325
275
  """Get the run parameters for the workflow"""
@@ -1016,7 +966,7 @@ class Workflow:
1016
966
  def _broadcast_to_websocket(
1017
967
  self,
1018
968
  event: Any,
1019
- websocket_handler: Optional[WebSocketHandler] = None,
969
+ websocket_handler: Optional["WebSocketHandler"] = None,
1020
970
  ) -> None:
1021
971
  """Broadcast events to WebSocket if available (async context only)"""
1022
972
  if websocket_handler:
@@ -1031,7 +981,7 @@ class Workflow:
1031
981
  self,
1032
982
  event: "WorkflowRunOutputEvent",
1033
983
  workflow_run_response: WorkflowRunOutput,
1034
- websocket_handler: Optional[WebSocketHandler] = None,
984
+ websocket_handler: Optional["WebSocketHandler"] = None,
1035
985
  ) -> "WorkflowRunOutputEvent":
1036
986
  """Handle workflow events for storage - similar to Team._handle_event"""
1037
987
  from agno.run.agent import RunOutput
@@ -1059,8 +1009,71 @@ class Workflow:
1059
1009
  workflow_run_response.events = []
1060
1010
  workflow_run_response.events.append(event)
1061
1011
 
1012
+ # Add to event buffer for reconnection support
1013
+ # Use workflow_run_id for agent/team events, run_id for workflow events
1014
+ buffer_run_id = None
1015
+ event_index = None
1016
+ if hasattr(event, "workflow_run_id") and event.workflow_run_id:
1017
+ # Agent/Team event - use workflow_run_id
1018
+ buffer_run_id = event.workflow_run_id
1019
+ elif hasattr(event, "run_id") and event.run_id:
1020
+ # Workflow event - use run_id
1021
+ buffer_run_id = event.run_id
1022
+
1023
+ if buffer_run_id:
1024
+ try:
1025
+ from agno.os.managers import event_buffer
1026
+
1027
+ # add_event now returns the event_index
1028
+ event_index = event_buffer.add_event(buffer_run_id, event) # type: ignore
1029
+ except Exception as e:
1030
+ # Don't fail workflow execution if buffering fails
1031
+ log_debug(f"Failed to add event to buffer: {e}")
1032
+
1062
1033
  # Broadcast to WebSocket if available (async context only)
1063
- self._broadcast_to_websocket(event, websocket_handler)
1034
+ # Include event_index for frontend reconnection support
1035
+ if websocket_handler:
1036
+ import asyncio
1037
+
1038
+ try:
1039
+ loop = asyncio.get_running_loop()
1040
+ if loop:
1041
+ # Pass event_index and run_id to websocket handler
1042
+ asyncio.create_task(
1043
+ websocket_handler.handle_event(event, event_index=event_index, run_id=buffer_run_id)
1044
+ )
1045
+ except RuntimeError:
1046
+ pass
1047
+
1048
+ # ALSO broadcast through websocket manager for reconnected clients
1049
+ # This ensures clients who reconnect after workflow started still receive events
1050
+ if buffer_run_id:
1051
+ try:
1052
+ import asyncio
1053
+
1054
+ from agno.os.managers import websocket_manager
1055
+
1056
+ loop = asyncio.get_running_loop()
1057
+ if loop:
1058
+ # Format the event for broadcast
1059
+ event_dict = event.model_dump() if hasattr(event, "model_dump") else event.to_dict()
1060
+ if event_index is not None:
1061
+ event_dict["event_index"] = event_index
1062
+ if "run_id" not in event_dict:
1063
+ event_dict["run_id"] = buffer_run_id
1064
+
1065
+ # Broadcast to registered websocket (if different from original)
1066
+ import json
1067
+
1068
+ from agno.utils.serialize import json_serializer
1069
+
1070
+ asyncio.create_task(
1071
+ websocket_manager.broadcast_to_run(
1072
+ buffer_run_id, json.dumps(event_dict, default=json_serializer)
1073
+ )
1074
+ )
1075
+ except Exception as e:
1076
+ log_debug(f"Failed to broadcast through manager: {e}")
1064
1077
 
1065
1078
  return event
1066
1079
 
@@ -1755,6 +1768,17 @@ class Workflow:
1755
1768
  )
1756
1769
  yield self._handle_event(workflow_completed_event, workflow_run_response)
1757
1770
 
1771
+ # Mark run as completed in event buffer
1772
+ try:
1773
+ from agno.os.managers import event_buffer
1774
+
1775
+ event_buffer.set_run_completed(
1776
+ workflow_run_response.run_id, # type: ignore
1777
+ workflow_run_response.status or RunStatus.completed,
1778
+ )
1779
+ except Exception as e:
1780
+ log_debug(f"Failed to mark run as completed in buffer: {e}")
1781
+
1758
1782
  # Stop timer on error
1759
1783
  if workflow_run_response.metrics:
1760
1784
  workflow_run_response.metrics.stop_timer()
@@ -2034,7 +2058,7 @@ class Workflow:
2034
2058
  workflow_run_response: WorkflowRunOutput,
2035
2059
  run_context: RunContext,
2036
2060
  stream_events: bool = False,
2037
- websocket_handler: Optional[WebSocketHandler] = None,
2061
+ websocket_handler: Optional["WebSocketHandler"] = None,
2038
2062
  background_tasks: Optional[Any] = None,
2039
2063
  **kwargs: Any,
2040
2064
  ) -> AsyncIterator[WorkflowRunOutputEvent]:
@@ -2347,6 +2371,17 @@ class Workflow:
2347
2371
  )
2348
2372
  yield self._handle_event(workflow_completed_event, workflow_run_response, websocket_handler=websocket_handler)
2349
2373
 
2374
+ # Mark run as completed in event buffer
2375
+ try:
2376
+ from agno.os.managers import event_buffer
2377
+
2378
+ event_buffer.set_run_completed(
2379
+ workflow_run_response.run_id, # type: ignore
2380
+ workflow_run_response.status or RunStatus.completed,
2381
+ )
2382
+ except Exception as e:
2383
+ log_debug(f"Failed to mark run as completed in buffer: {e}")
2384
+
2350
2385
  # Stop timer on error
2351
2386
  if workflow_run_response.metrics:
2352
2387
  workflow_run_response.metrics.stop_timer()
@@ -2406,6 +2441,7 @@ class Workflow:
2406
2441
  run_id=run_id,
2407
2442
  input=input,
2408
2443
  session_id=session_id,
2444
+ user_id=user_id,
2409
2445
  workflow_id=self.id,
2410
2446
  workflow_name=self.name,
2411
2447
  created_at=int(datetime.now().timestamp()),
@@ -2494,7 +2530,7 @@ class Workflow:
2494
2530
  videos: Optional[List[Video]] = None,
2495
2531
  files: Optional[List[File]] = None,
2496
2532
  stream_events: bool = False,
2497
- websocket_handler: Optional[WebSocketHandler] = None,
2533
+ websocket_handler: Optional["WebSocketHandler"] = None,
2498
2534
  **kwargs: Any,
2499
2535
  ) -> WorkflowRunOutput:
2500
2536
  """Execute workflow in background with streaming and WebSocket broadcasting"""
@@ -2524,6 +2560,7 @@ class Workflow:
2524
2560
  run_id=run_id,
2525
2561
  input=input,
2526
2562
  session_id=session_id,
2563
+ user_id=user_id,
2527
2564
  workflow_id=self.id,
2528
2565
  workflow_name=self.name,
2529
2566
  created_at=int(datetime.now().timestamp()),
@@ -2779,6 +2816,7 @@ class Workflow:
2779
2816
  run_id=run_id,
2780
2817
  input=execution_input.input,
2781
2818
  session_id=session.session_id,
2819
+ user_id=session.user_id,
2782
2820
  workflow_id=self.id,
2783
2821
  workflow_name=self.name,
2784
2822
  created_at=int(datetime.now().timestamp()),
@@ -2960,6 +2998,7 @@ class Workflow:
2960
2998
  run_id=run_id,
2961
2999
  input=execution_input.input,
2962
3000
  session_id=session.session_id,
3001
+ user_id=session.user_id,
2963
3002
  workflow_id=self.id,
2964
3003
  workflow_name=self.name,
2965
3004
  created_at=int(datetime.now().timestamp()),
@@ -3008,6 +3047,7 @@ class Workflow:
3008
3047
  run_id=str(uuid4()),
3009
3048
  input=execution_input.input,
3010
3049
  session_id=session.session_id,
3050
+ user_id=session.user_id,
3011
3051
  workflow_id=self.id,
3012
3052
  workflow_name=self.name,
3013
3053
  created_at=int(datetime.now().timestamp()),
@@ -3020,7 +3060,7 @@ class Workflow:
3020
3060
  session: WorkflowSession,
3021
3061
  execution_input: WorkflowExecutionInput,
3022
3062
  run_context: RunContext,
3023
- websocket_handler: Optional[WebSocketHandler] = None,
3063
+ websocket_handler: Optional["WebSocketHandler"] = None,
3024
3064
  stream: bool = False,
3025
3065
  ) -> None:
3026
3066
  """Initialize the workflow agent with async tools (but NOT context - that's passed per-run)"""
@@ -3056,7 +3096,7 @@ class Workflow:
3056
3096
  run_context: RunContext,
3057
3097
  execution_input: WorkflowExecutionInput,
3058
3098
  stream: bool = False,
3059
- websocket_handler: Optional[WebSocketHandler] = None,
3099
+ websocket_handler: Optional["WebSocketHandler"] = None,
3060
3100
  **kwargs: Any,
3061
3101
  ):
3062
3102
  """
@@ -3117,7 +3157,7 @@ class Workflow:
3117
3157
  execution_input: WorkflowExecutionInput,
3118
3158
  run_context: RunContext,
3119
3159
  stream: bool = False,
3120
- websocket_handler: Optional[WebSocketHandler] = None,
3160
+ websocket_handler: Optional["WebSocketHandler"] = None,
3121
3161
  **kwargs: Any,
3122
3162
  ) -> AsyncIterator[WorkflowRunOutputEvent]:
3123
3163
  """
@@ -3162,6 +3202,7 @@ class Workflow:
3162
3202
  run_id=run_id,
3163
3203
  input=execution_input.input,
3164
3204
  session_id=session.session_id,
3205
+ user_id=session.user_id,
3165
3206
  workflow_id=self.id,
3166
3207
  workflow_name=self.name,
3167
3208
  created_at=int(datetime.now().timestamp()),
@@ -3360,6 +3401,7 @@ class Workflow:
3360
3401
  run_id=run_id,
3361
3402
  input=execution_input.input,
3362
3403
  session_id=session.session_id,
3404
+ user_id=session.user_id,
3363
3405
  workflow_id=self.id,
3364
3406
  workflow_name=self.name,
3365
3407
  created_at=int(datetime.now().timestamp()),
@@ -3427,6 +3469,7 @@ class Workflow:
3427
3469
  run_id=str(uuid4()),
3428
3470
  input=execution_input.input,
3429
3471
  session_id=session.session_id,
3472
+ user_id=session.user_id,
3430
3473
  workflow_id=self.id,
3431
3474
  workflow_name=self.name,
3432
3475
  created_at=int(datetime.now().timestamp()),
@@ -3512,7 +3555,10 @@ class Workflow:
3512
3555
  run_id = run_id or str(uuid4())
3513
3556
  register_run(run_id)
3514
3557
 
3515
- input = self._validate_input(input)
3558
+ if input is None and self.input_schema is not None:
3559
+ raise ValueError("Input is required when input_schema is provided")
3560
+ if input is not None and self.input_schema is not None:
3561
+ input = validate_input(input, self.input_schema)
3516
3562
  if background:
3517
3563
  raise RuntimeError("Background execution is not supported for sync run()")
3518
3564
 
@@ -3591,6 +3637,7 @@ class Workflow:
3591
3637
  run_id=run_id,
3592
3638
  input=input,
3593
3639
  session_id=session_id,
3640
+ user_id=user_id,
3594
3641
  workflow_id=self.id,
3595
3642
  workflow_name=self.name,
3596
3643
  created_at=int(datetime.now().timestamp()),
@@ -3684,11 +3731,14 @@ class Workflow:
3684
3731
  ) -> Union[WorkflowRunOutput, AsyncIterator[WorkflowRunOutputEvent]]:
3685
3732
  """Execute the workflow synchronously with optional streaming"""
3686
3733
 
3687
- input = self._validate_input(input)
3734
+ if input is None and self.input_schema is not None:
3735
+ raise ValueError("Input is required when input_schema is provided")
3736
+ if input is not None and self.input_schema is not None:
3737
+ input = validate_input(input, self.input_schema)
3688
3738
 
3689
3739
  websocket_handler = None
3690
3740
  if websocket:
3691
- from agno.workflow.types import WebSocketHandler
3741
+ from agno.os.managers import WebSocketHandler
3692
3742
 
3693
3743
  websocket_handler = WebSocketHandler(websocket=websocket)
3694
3744
 
@@ -3798,6 +3848,7 @@ class Workflow:
3798
3848
  run_id=run_id,
3799
3849
  input=input,
3800
3850
  session_id=session_id,
3851
+ user_id=user_id,
3801
3852
  workflow_id=self.id,
3802
3853
  workflow_name=self.name,
3803
3854
  created_at=int(datetime.now().timestamp()),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.3.16
3
+ Version: 2.3.17
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  Project-URL: homepage, https://agno.com