agno 1.7.3__py3-none-any.whl → 1.7.5__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/agent.py +113 -31
- agno/api/schemas/agent.py +1 -0
- agno/api/schemas/team.py +1 -0
- agno/app/fastapi/app.py +1 -1
- agno/app/fastapi/async_router.py +67 -16
- agno/app/fastapi/sync_router.py +80 -14
- agno/app/playground/app.py +2 -1
- agno/app/playground/async_router.py +97 -28
- agno/app/playground/operator.py +25 -19
- agno/app/playground/schemas.py +1 -0
- agno/app/playground/sync_router.py +93 -26
- agno/knowledge/agent.py +39 -2
- agno/knowledge/combined.py +1 -1
- agno/run/base.py +2 -0
- agno/run/response.py +4 -4
- agno/run/team.py +6 -6
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +563 -0
- agno/storage/base.py +4 -4
- agno/storage/dynamodb.py +74 -10
- agno/storage/firestore.py +6 -1
- agno/storage/gcs_json.py +8 -2
- agno/storage/json.py +20 -5
- agno/storage/mongodb.py +14 -5
- agno/storage/mysql.py +56 -17
- agno/storage/postgres.py +55 -13
- agno/storage/redis.py +25 -5
- agno/storage/session/__init__.py +3 -1
- agno/storage/session/agent.py +3 -0
- agno/storage/session/team.py +3 -0
- agno/storage/session/v2/__init__.py +5 -0
- agno/storage/session/v2/workflow.py +89 -0
- agno/storage/singlestore.py +74 -12
- agno/storage/sqlite.py +64 -18
- agno/storage/yaml.py +26 -6
- agno/team/team.py +105 -21
- agno/tools/decorator.py +45 -2
- agno/tools/function.py +16 -12
- agno/utils/log.py +12 -0
- agno/utils/message.py +5 -1
- agno/utils/openai.py +20 -5
- agno/utils/pprint.py +34 -8
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +493 -0
- agno/workflow/v2/__init__.py +21 -0
- agno/workflow/v2/condition.py +554 -0
- agno/workflow/v2/loop.py +602 -0
- agno/workflow/v2/parallel.py +659 -0
- agno/workflow/v2/router.py +521 -0
- agno/workflow/v2/step.py +861 -0
- agno/workflow/v2/steps.py +465 -0
- agno/workflow/v2/types.py +347 -0
- agno/workflow/v2/workflow.py +3132 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/METADATA +4 -1
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/RECORD +59 -44
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/WHEEL +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/entry_points.txt +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.3.dist-info → agno-1.7.5.dist-info}/top_level.txt +0 -0
agno/team/team.py
CHANGED
|
@@ -112,6 +112,9 @@ class Team:
|
|
|
112
112
|
parent_team_id: Optional[str] = None
|
|
113
113
|
# The workflow this team belongs to
|
|
114
114
|
workflow_id: Optional[str] = None
|
|
115
|
+
# Set when this team is part of a workflow.
|
|
116
|
+
workflow_session_id: Optional[str] = None
|
|
117
|
+
|
|
115
118
|
role: Optional[str] = None
|
|
116
119
|
|
|
117
120
|
# --- User settings ---
|
|
@@ -127,9 +130,13 @@ class Team:
|
|
|
127
130
|
session_name: Optional[str] = None
|
|
128
131
|
# Session state (stored in the database to persist across runs)
|
|
129
132
|
session_state: Optional[Dict[str, Any]] = None
|
|
133
|
+
# If True, cache the session in memory
|
|
134
|
+
cache_session: bool = True
|
|
130
135
|
|
|
131
136
|
# Team session state (shared between team leaders and team members)
|
|
132
137
|
team_session_state: Optional[Dict[str, Any]] = None
|
|
138
|
+
workflow_session_state: Optional[Dict[str, Any]] = None
|
|
139
|
+
|
|
133
140
|
# If True, add the session state variables in the user and system messages
|
|
134
141
|
add_state_in_messages: bool = False
|
|
135
142
|
|
|
@@ -304,7 +311,9 @@ class Team:
|
|
|
304
311
|
session_name: Optional[str] = None,
|
|
305
312
|
session_state: Optional[Dict[str, Any]] = None,
|
|
306
313
|
team_session_state: Optional[Dict[str, Any]] = None,
|
|
314
|
+
workflow_session_state: Optional[Dict[str, Any]] = None,
|
|
307
315
|
add_state_in_messages: bool = False,
|
|
316
|
+
cache_session: bool = True,
|
|
308
317
|
description: Optional[str] = None,
|
|
309
318
|
instructions: Optional[Union[str, List[str], Callable]] = None,
|
|
310
319
|
expected_output: Optional[str] = None,
|
|
@@ -381,8 +390,11 @@ class Team:
|
|
|
381
390
|
self.session_name = session_name
|
|
382
391
|
self.session_state = session_state
|
|
383
392
|
self.team_session_state = team_session_state
|
|
393
|
+
self.workflow_session_state = workflow_session_state
|
|
384
394
|
self.add_state_in_messages = add_state_in_messages
|
|
385
395
|
|
|
396
|
+
self.cache_session = cache_session
|
|
397
|
+
|
|
386
398
|
self.description = description
|
|
387
399
|
self.instructions = instructions
|
|
388
400
|
self.expected_output = expected_output
|
|
@@ -472,7 +484,7 @@ class Team:
|
|
|
472
484
|
self.full_team_session_metrics: Optional[SessionMetrics] = None
|
|
473
485
|
|
|
474
486
|
self.run_id: Optional[str] = None
|
|
475
|
-
self.run_input: Optional[Union[str, List, Dict]] = None
|
|
487
|
+
self.run_input: Optional[Union[str, List, Dict, BaseModel]] = None
|
|
476
488
|
self.run_messages: Optional[RunMessages] = None
|
|
477
489
|
self.run_response: Optional[TeamRunResponse] = None
|
|
478
490
|
|
|
@@ -549,6 +561,12 @@ class Team:
|
|
|
549
561
|
else:
|
|
550
562
|
merge_dictionaries(member.team_session_state, self.team_session_state)
|
|
551
563
|
|
|
564
|
+
if self.workflow_session_state is not None:
|
|
565
|
+
if member.workflow_session_state is None:
|
|
566
|
+
member.workflow_session_state = self.workflow_session_state
|
|
567
|
+
else:
|
|
568
|
+
merge_dictionaries(member.workflow_session_state, self.workflow_session_state)
|
|
569
|
+
|
|
552
570
|
if isinstance(member, Agent):
|
|
553
571
|
member.team_id = self.team_id
|
|
554
572
|
member.set_agent_id()
|
|
@@ -587,6 +605,8 @@ class Team:
|
|
|
587
605
|
|
|
588
606
|
def _reset_session(self) -> None:
|
|
589
607
|
self.session_name = None
|
|
608
|
+
self.session_state = None
|
|
609
|
+
self.team_session_state = None
|
|
590
610
|
self.session_metrics = None
|
|
591
611
|
self.session_state = None
|
|
592
612
|
self.images = None
|
|
@@ -658,11 +678,15 @@ class Team:
|
|
|
658
678
|
self.session_state["current_user_id"] = user_id
|
|
659
679
|
if self.team_session_state is not None:
|
|
660
680
|
self.team_session_state["current_user_id"] = user_id
|
|
681
|
+
if self.workflow_session_state is not None:
|
|
682
|
+
self.workflow_session_state["current_user_id"] = user_id
|
|
661
683
|
|
|
662
684
|
if session_id is not None:
|
|
663
685
|
self.session_state["current_session_id"] = session_id
|
|
664
686
|
if self.team_session_state is not None:
|
|
665
687
|
self.team_session_state["current_session_id"] = session_id
|
|
688
|
+
if self.workflow_session_state is not None:
|
|
689
|
+
self.workflow_session_state["current_user_id"] = user_id
|
|
666
690
|
|
|
667
691
|
def _reset_session_state(self) -> None:
|
|
668
692
|
"""Reset the session state for the agent."""
|
|
@@ -710,15 +734,12 @@ class Team:
|
|
|
710
734
|
|
|
711
735
|
self._initialize_session_state(user_id=user_id, session_id=session_id)
|
|
712
736
|
|
|
713
|
-
# Read existing session from storage
|
|
714
|
-
self.read_from_storage(session_id=session_id)
|
|
715
|
-
|
|
716
737
|
return session_id, user_id
|
|
717
738
|
|
|
718
739
|
@overload
|
|
719
740
|
def run(
|
|
720
741
|
self,
|
|
721
|
-
message: Union[str, List, Dict, Message],
|
|
742
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
722
743
|
*,
|
|
723
744
|
stream: Literal[False] = False,
|
|
724
745
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -737,7 +758,7 @@ class Team:
|
|
|
737
758
|
@overload
|
|
738
759
|
def run(
|
|
739
760
|
self,
|
|
740
|
-
message: Union[str, List, Dict, Message],
|
|
761
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
741
762
|
*,
|
|
742
763
|
stream: Literal[True] = True,
|
|
743
764
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -755,7 +776,7 @@ class Team:
|
|
|
755
776
|
|
|
756
777
|
def run(
|
|
757
778
|
self,
|
|
758
|
-
message: Union[str, List, Dict, Message],
|
|
779
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
759
780
|
*,
|
|
760
781
|
stream: Optional[bool] = None,
|
|
761
782
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -780,6 +801,9 @@ class Team:
|
|
|
780
801
|
# Initialize Team
|
|
781
802
|
self.initialize_team(session_id=session_id)
|
|
782
803
|
|
|
804
|
+
# Read existing session from storage
|
|
805
|
+
self.read_from_storage(session_id=session_id)
|
|
806
|
+
|
|
783
807
|
# Initialize Knowledge Filters
|
|
784
808
|
effective_filters = knowledge_filters
|
|
785
809
|
|
|
@@ -1119,7 +1143,7 @@ class Team:
|
|
|
1119
1143
|
@overload
|
|
1120
1144
|
async def arun(
|
|
1121
1145
|
self,
|
|
1122
|
-
message: Union[str, List, Dict, Message],
|
|
1146
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
1123
1147
|
*,
|
|
1124
1148
|
stream: Literal[False] = False,
|
|
1125
1149
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -1138,7 +1162,7 @@ class Team:
|
|
|
1138
1162
|
@overload
|
|
1139
1163
|
async def arun(
|
|
1140
1164
|
self,
|
|
1141
|
-
message: Union[str, List, Dict, Message],
|
|
1165
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
1142
1166
|
*,
|
|
1143
1167
|
stream: Literal[True] = True,
|
|
1144
1168
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -1156,7 +1180,7 @@ class Team:
|
|
|
1156
1180
|
|
|
1157
1181
|
async def arun(
|
|
1158
1182
|
self,
|
|
1159
|
-
message: Union[str, List, Dict, Message],
|
|
1183
|
+
message: Union[str, List, Dict, Message, BaseModel],
|
|
1160
1184
|
*,
|
|
1161
1185
|
stream: Optional[bool] = None,
|
|
1162
1186
|
stream_intermediate_steps: Optional[bool] = None,
|
|
@@ -1172,14 +1196,17 @@ class Team:
|
|
|
1172
1196
|
**kwargs: Any,
|
|
1173
1197
|
) -> Union[TeamRunResponse, AsyncIterator[Union[RunResponseEvent, TeamRunResponseEvent]]]:
|
|
1174
1198
|
"""Run the Team asynchronously and return the response."""
|
|
1175
|
-
|
|
1176
1199
|
session_id, user_id = self._initialize_session(
|
|
1177
1200
|
session_id=session_id, user_id=user_id, session_state=session_state
|
|
1178
1201
|
)
|
|
1179
1202
|
log_debug(f"Session ID: {session_id}", center=True)
|
|
1180
1203
|
|
|
1204
|
+
# Initialize Team
|
|
1181
1205
|
self.initialize_team(session_id=session_id)
|
|
1182
1206
|
|
|
1207
|
+
# Read existing session from storage
|
|
1208
|
+
self.read_from_storage(session_id=session_id)
|
|
1209
|
+
|
|
1183
1210
|
effective_filters = knowledge_filters
|
|
1184
1211
|
|
|
1185
1212
|
# When filters are passed manually
|
|
@@ -1611,7 +1638,7 @@ class Team:
|
|
|
1611
1638
|
self.memory.add_team_run(team_run) # type: ignore
|
|
1612
1639
|
|
|
1613
1640
|
elif isinstance(self.memory, Memory):
|
|
1614
|
-
# Add
|
|
1641
|
+
# Add run to memory
|
|
1615
1642
|
self.memory.add_run(session_id=session_id, run=run_response)
|
|
1616
1643
|
|
|
1617
1644
|
def _update_memory(
|
|
@@ -2388,7 +2415,7 @@ class Team:
|
|
|
2388
2415
|
|
|
2389
2416
|
def print_response(
|
|
2390
2417
|
self,
|
|
2391
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
2418
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
2392
2419
|
*,
|
|
2393
2420
|
stream: bool = False,
|
|
2394
2421
|
stream_intermediate_steps: bool = False,
|
|
@@ -2458,7 +2485,7 @@ class Team:
|
|
|
2458
2485
|
|
|
2459
2486
|
def _print_response(
|
|
2460
2487
|
self,
|
|
2461
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
2488
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
2462
2489
|
console: Optional[Any] = None,
|
|
2463
2490
|
show_message: bool = True,
|
|
2464
2491
|
show_reasoning: bool = True,
|
|
@@ -2742,7 +2769,7 @@ class Team:
|
|
|
2742
2769
|
|
|
2743
2770
|
def _print_response_stream(
|
|
2744
2771
|
self,
|
|
2745
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
2772
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
2746
2773
|
console: Optional[Any] = None,
|
|
2747
2774
|
show_message: bool = True,
|
|
2748
2775
|
show_reasoning: bool = True,
|
|
@@ -3259,7 +3286,7 @@ class Team:
|
|
|
3259
3286
|
|
|
3260
3287
|
async def aprint_response(
|
|
3261
3288
|
self,
|
|
3262
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
3289
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
3263
3290
|
*,
|
|
3264
3291
|
stream: bool = False,
|
|
3265
3292
|
stream_intermediate_steps: bool = False,
|
|
@@ -3329,7 +3356,7 @@ class Team:
|
|
|
3329
3356
|
|
|
3330
3357
|
async def _aprint_response(
|
|
3331
3358
|
self,
|
|
3332
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
3359
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
3333
3360
|
console: Optional[Any] = None,
|
|
3334
3361
|
show_message: bool = True,
|
|
3335
3362
|
show_reasoning: bool = True,
|
|
@@ -3611,7 +3638,7 @@ class Team:
|
|
|
3611
3638
|
|
|
3612
3639
|
async def _aprint_response_stream(
|
|
3613
3640
|
self,
|
|
3614
|
-
message: Optional[Union[List, Dict, str, Message]] = None,
|
|
3641
|
+
message: Optional[Union[List, Dict, str, Message, BaseModel]] = None,
|
|
3615
3642
|
console: Optional[Any] = None,
|
|
3616
3643
|
show_message: bool = True,
|
|
3617
3644
|
show_reasoning: bool = True,
|
|
@@ -4803,7 +4830,7 @@ class Team:
|
|
|
4803
4830
|
user_id: Optional[str] = None,
|
|
4804
4831
|
async_mode: bool = False,
|
|
4805
4832
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
4806
|
-
message: Optional[Union[str, List, Dict, Message]] = None,
|
|
4833
|
+
message: Optional[Union[str, List, Dict, Message, BaseModel]] = None,
|
|
4807
4834
|
images: Optional[Sequence[Image]] = None,
|
|
4808
4835
|
videos: Optional[Sequence[Video]] = None,
|
|
4809
4836
|
audio: Optional[Sequence[Audio]] = None,
|
|
@@ -5299,7 +5326,7 @@ class Team:
|
|
|
5299
5326
|
*,
|
|
5300
5327
|
session_id: str,
|
|
5301
5328
|
user_id: Optional[str] = None,
|
|
5302
|
-
message: Optional[Union[str, List, Dict, Message]] = None,
|
|
5329
|
+
message: Optional[Union[str, List, Dict, Message, BaseModel]] = None,
|
|
5303
5330
|
audio: Optional[Sequence[Audio]] = None,
|
|
5304
5331
|
images: Optional[Sequence[Image]] = None,
|
|
5305
5332
|
videos: Optional[Sequence[Video]] = None,
|
|
@@ -5381,7 +5408,7 @@ class Team:
|
|
|
5381
5408
|
|
|
5382
5409
|
def _get_user_message(
|
|
5383
5410
|
self,
|
|
5384
|
-
message: Optional[Union[str, List, Dict, Message]] = None,
|
|
5411
|
+
message: Optional[Union[str, List, Dict, Message, BaseModel]] = None,
|
|
5385
5412
|
user_id: Optional[str] = None,
|
|
5386
5413
|
audio: Optional[Sequence[Audio]] = None,
|
|
5387
5414
|
images: Optional[Sequence[Image]] = None,
|
|
@@ -5399,6 +5426,8 @@ class Team:
|
|
|
5399
5426
|
message_str = message
|
|
5400
5427
|
elif callable(message):
|
|
5401
5428
|
message_str = message(agent=self)
|
|
5429
|
+
elif isinstance(message, BaseModel):
|
|
5430
|
+
message_str = message.model_dump_json(indent=2, exclude_none=True)
|
|
5402
5431
|
else:
|
|
5403
5432
|
raise Exception("message must be a string or a callable when add_references is True")
|
|
5404
5433
|
|
|
@@ -5544,6 +5573,7 @@ class Team:
|
|
|
5544
5573
|
format_variables = ChainMap(
|
|
5545
5574
|
self.session_state or {},
|
|
5546
5575
|
self.team_session_state or {},
|
|
5576
|
+
self.workflow_session_state or {},
|
|
5547
5577
|
self.context or {},
|
|
5548
5578
|
self.extra_data or {},
|
|
5549
5579
|
{"user_id": user_id} if user_id is not None else {},
|
|
@@ -5816,6 +5846,23 @@ class Team:
|
|
|
5816
5846
|
else:
|
|
5817
5847
|
merge_dictionaries(self.team_session_state, member_agent.team_session_state)
|
|
5818
5848
|
|
|
5849
|
+
def _update_workflow_session_state(self, member_agent: Union[Agent, "Team"]) -> None:
|
|
5850
|
+
"""Update workflow session state from either an Agent or nested Team member"""
|
|
5851
|
+
# Get member state safely
|
|
5852
|
+
member_state = getattr(member_agent, "workflow_session_state", None)
|
|
5853
|
+
|
|
5854
|
+
# Only proceed if member has valid state
|
|
5855
|
+
if member_state is not None and isinstance(member_state, dict):
|
|
5856
|
+
# Initialize team state if needed
|
|
5857
|
+
if self.workflow_session_state is None:
|
|
5858
|
+
self.workflow_session_state = {}
|
|
5859
|
+
|
|
5860
|
+
# Only merge if both are dictionaries and member state is not empty
|
|
5861
|
+
if isinstance(self.workflow_session_state, dict) and member_state:
|
|
5862
|
+
from agno.utils.merge_dict import merge_dictionaries
|
|
5863
|
+
|
|
5864
|
+
merge_dictionaries(self.workflow_session_state, member_state)
|
|
5865
|
+
|
|
5819
5866
|
def get_run_member_agents_function(
|
|
5820
5867
|
self,
|
|
5821
5868
|
session_id: str,
|
|
@@ -5944,6 +5991,8 @@ class Team:
|
|
|
5944
5991
|
# Update team session state
|
|
5945
5992
|
self._update_team_session_state(member_agent)
|
|
5946
5993
|
|
|
5994
|
+
self._update_workflow_session_state(member_agent)
|
|
5995
|
+
|
|
5947
5996
|
# Update the team media
|
|
5948
5997
|
self._update_team_media(member_agent.run_response) # type: ignore
|
|
5949
5998
|
|
|
@@ -6023,6 +6072,8 @@ class Team:
|
|
|
6023
6072
|
# Update team session state
|
|
6024
6073
|
self._update_team_session_state(current_agent)
|
|
6025
6074
|
|
|
6075
|
+
self._update_workflow_session_state(current_agent)
|
|
6076
|
+
|
|
6026
6077
|
# Update the team media
|
|
6027
6078
|
self._update_team_media(agent.run_response)
|
|
6028
6079
|
|
|
@@ -6259,6 +6310,8 @@ class Team:
|
|
|
6259
6310
|
# Update team session state
|
|
6260
6311
|
self._update_team_session_state(member_agent)
|
|
6261
6312
|
|
|
6313
|
+
self._update_workflow_session_state(member_agent)
|
|
6314
|
+
|
|
6262
6315
|
# Update the team media
|
|
6263
6316
|
self._update_team_media(member_agent.run_response) # type: ignore
|
|
6264
6317
|
|
|
@@ -6389,6 +6442,8 @@ class Team:
|
|
|
6389
6442
|
# Update team session state
|
|
6390
6443
|
self._update_team_session_state(member_agent)
|
|
6391
6444
|
|
|
6445
|
+
self._update_workflow_session_state(member_agent)
|
|
6446
|
+
|
|
6392
6447
|
# Update the team media
|
|
6393
6448
|
self._update_team_media(member_agent.run_response) # type: ignore
|
|
6394
6449
|
|
|
@@ -6627,6 +6682,8 @@ class Team:
|
|
|
6627
6682
|
# Update team session state
|
|
6628
6683
|
self._update_team_session_state(member_agent)
|
|
6629
6684
|
|
|
6685
|
+
self._update_workflow_session_state(member_agent)
|
|
6686
|
+
|
|
6630
6687
|
# Update the team media
|
|
6631
6688
|
self._update_team_media(member_agent.run_response) # type: ignore
|
|
6632
6689
|
|
|
@@ -6756,6 +6813,8 @@ class Team:
|
|
|
6756
6813
|
# Update team session state
|
|
6757
6814
|
self._update_team_session_state(member_agent)
|
|
6758
6815
|
|
|
6816
|
+
self._update_workflow_session_state(member_agent)
|
|
6817
|
+
|
|
6759
6818
|
# Update the team media
|
|
6760
6819
|
self._update_team_media(member_agent.run_response) # type: ignore
|
|
6761
6820
|
|
|
@@ -6797,6 +6856,11 @@ class Team:
|
|
|
6797
6856
|
self.team_session = cast(
|
|
6798
6857
|
TeamSession, self.storage.upsert(session=self._get_team_session(session_id=session_id, user_id=user_id))
|
|
6799
6858
|
)
|
|
6859
|
+
|
|
6860
|
+
# Remove session from memory
|
|
6861
|
+
if not self.cache_session:
|
|
6862
|
+
if self.memory is not None and self.memory.runs is not None and session_id in self.memory.runs:
|
|
6863
|
+
self.memory.runs.pop(session_id) # type: ignore
|
|
6800
6864
|
return self.team_session
|
|
6801
6865
|
|
|
6802
6866
|
def rename_session(self, session_name: str, session_id: Optional[str] = None) -> None:
|
|
@@ -6872,6 +6936,20 @@ class Team:
|
|
|
6872
6936
|
# Update the current team_session_state
|
|
6873
6937
|
self.team_session_state = team_session_state_from_db
|
|
6874
6938
|
|
|
6939
|
+
if "workflow_session_state" in session.session_data:
|
|
6940
|
+
workflow_session_state_from_db = session.session_data.get("workflow_session_state")
|
|
6941
|
+
if (
|
|
6942
|
+
workflow_session_state_from_db is not None
|
|
6943
|
+
and isinstance(workflow_session_state_from_db, dict)
|
|
6944
|
+
and len(workflow_session_state_from_db) > 0
|
|
6945
|
+
):
|
|
6946
|
+
# If the workflow_session_state is already set, merge the workflow_session_state from the database with the current workflow_session_state
|
|
6947
|
+
if self.workflow_session_state is not None and len(self.workflow_session_state) > 0:
|
|
6948
|
+
# This updates workflow_session_state_from_db
|
|
6949
|
+
merge_dictionaries(workflow_session_state_from_db, self.workflow_session_state)
|
|
6950
|
+
# Update the current workflow_session_state
|
|
6951
|
+
self.workflow_session_state = workflow_session_state_from_db
|
|
6952
|
+
|
|
6875
6953
|
# Get the session_metrics from the database
|
|
6876
6954
|
if "session_metrics" in session.session_data:
|
|
6877
6955
|
session_metrics_from_db = session.session_data.get("session_metrics")
|
|
@@ -6960,6 +7038,7 @@ class Team:
|
|
|
6960
7038
|
self.memory.runs[session.session_id] = []
|
|
6961
7039
|
for run in session.memory["runs"]:
|
|
6962
7040
|
run_session_id = run["session_id"]
|
|
7041
|
+
|
|
6963
7042
|
if "team_id" in run:
|
|
6964
7043
|
self.memory.runs[run_session_id].append(TeamRunResponse.from_dict(run))
|
|
6965
7044
|
else:
|
|
@@ -7645,6 +7724,8 @@ class Team:
|
|
|
7645
7724
|
session_data["session_state"] = self.session_state
|
|
7646
7725
|
if self.team_session_state is not None and len(self.team_session_state) > 0:
|
|
7647
7726
|
session_data["team_session_state"] = self.team_session_state
|
|
7727
|
+
if self.workflow_session_state is not None and len(self.workflow_session_state) > 0:
|
|
7728
|
+
session_data["workflow_session_state"] = self.workflow_session_state
|
|
7648
7729
|
if self.session_metrics is not None:
|
|
7649
7730
|
session_data["session_metrics"] = asdict(self.session_metrics) if self.session_metrics is not None else None
|
|
7650
7731
|
if self.images is not None:
|
|
@@ -7672,11 +7753,13 @@ class Team:
|
|
|
7672
7753
|
run_responses = self.memory.runs.get(session_id)
|
|
7673
7754
|
if run_responses is not None:
|
|
7674
7755
|
memory_dict["runs"] = [rr.to_dict() for rr in run_responses]
|
|
7756
|
+
|
|
7675
7757
|
return TeamSession(
|
|
7676
7758
|
session_id=session_id,
|
|
7677
7759
|
team_id=self.team_id,
|
|
7678
7760
|
user_id=user_id,
|
|
7679
7761
|
team_session_id=self.team_session_id,
|
|
7762
|
+
workflow_session_id=self.workflow_session_id,
|
|
7680
7763
|
memory=memory_dict,
|
|
7681
7764
|
team_data=self._get_team_data(),
|
|
7682
7765
|
session_data=self._get_session_data(),
|
|
@@ -7701,6 +7784,7 @@ class Team:
|
|
|
7701
7784
|
run_id=self.run_id, # type: ignore
|
|
7702
7785
|
run_data=run_data,
|
|
7703
7786
|
team_session_id=team_session.team_session_id,
|
|
7787
|
+
workflow_session_id=self.workflow_session_id,
|
|
7704
7788
|
session_id=team_session.session_id,
|
|
7705
7789
|
team_data=team_session.to_dict() if self.monitoring else team_session.telemetry_data(),
|
|
7706
7790
|
),
|
agno/tools/decorator.py
CHANGED
|
@@ -9,6 +9,49 @@ F = TypeVar("F", bound=Callable[..., Any])
|
|
|
9
9
|
ToolConfig = TypeVar("ToolConfig", bound=Dict[str, Any])
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def _is_async_function(func: Callable) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Check if a function is async, even when wrapped by decorators like @staticmethod.
|
|
15
|
+
|
|
16
|
+
This function tries to detect async functions by:
|
|
17
|
+
1. Checking the function directly with inspect functions
|
|
18
|
+
2. Looking at the original function if it's wrapped
|
|
19
|
+
3. Checking the function's code object for async indicators
|
|
20
|
+
"""
|
|
21
|
+
from inspect import iscoroutine, iscoroutinefunction
|
|
22
|
+
|
|
23
|
+
# First, try the standard inspect functions
|
|
24
|
+
if iscoroutinefunction(func) or iscoroutine(func):
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
# If the function has a __wrapped__ attribute, check the original function
|
|
28
|
+
if hasattr(func, "__wrapped__"):
|
|
29
|
+
original_func = func.__wrapped__
|
|
30
|
+
if iscoroutinefunction(original_func) or iscoroutine(original_func):
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
# Check if the function has CO_COROUTINE flag in its code object
|
|
34
|
+
try:
|
|
35
|
+
if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE flag
|
|
36
|
+
return True
|
|
37
|
+
except (AttributeError, TypeError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
# For static methods, try to get the original function
|
|
41
|
+
try:
|
|
42
|
+
if hasattr(func, "__func__"):
|
|
43
|
+
original_func = func.__func__
|
|
44
|
+
if iscoroutinefunction(original_func) or iscoroutine(original_func):
|
|
45
|
+
return True
|
|
46
|
+
# Check the code object of the original function
|
|
47
|
+
if hasattr(original_func, "__code__") and original_func.__code__.co_flags & 0x80:
|
|
48
|
+
return True
|
|
49
|
+
except (AttributeError, TypeError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
12
55
|
@overload
|
|
13
56
|
def tool() -> Callable[[F], Function]: ...
|
|
14
57
|
|
|
@@ -125,7 +168,7 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
|
|
|
125
168
|
)
|
|
126
169
|
|
|
127
170
|
def decorator(func: F) -> Function:
|
|
128
|
-
from inspect import isasyncgenfunction
|
|
171
|
+
from inspect import isasyncgenfunction
|
|
129
172
|
|
|
130
173
|
@wraps(func)
|
|
131
174
|
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -163,7 +206,7 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
|
|
|
163
206
|
# Choose appropriate wrapper based on function type
|
|
164
207
|
if isasyncgenfunction(func):
|
|
165
208
|
wrapper = async_gen_wrapper
|
|
166
|
-
elif
|
|
209
|
+
elif _is_async_function(func):
|
|
167
210
|
wrapper = async_wrapper
|
|
168
211
|
else:
|
|
169
212
|
wrapper = sync_wrapper
|
agno/tools/function.py
CHANGED
|
@@ -151,7 +151,7 @@ class Function(BaseModel):
|
|
|
151
151
|
param_type_hints = {
|
|
152
152
|
name: type_hints.get(name)
|
|
153
153
|
for name in sig.parameters
|
|
154
|
-
if name != "return" and name not in ["agent", "team"]
|
|
154
|
+
if name != "return" and name not in ["agent", "team", "self"]
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
# Parse docstring for parameters
|
|
@@ -177,7 +177,9 @@ class Function(BaseModel):
|
|
|
177
177
|
# If strict=True mark all fields as required
|
|
178
178
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
179
179
|
if strict:
|
|
180
|
-
parameters["required"] = [
|
|
180
|
+
parameters["required"] = [
|
|
181
|
+
name for name in parameters["properties"] if name not in ["agent", "team", "self"]
|
|
182
|
+
]
|
|
181
183
|
else:
|
|
182
184
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
183
185
|
parameters["required"] = [
|
|
@@ -235,7 +237,7 @@ class Function(BaseModel):
|
|
|
235
237
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
236
238
|
|
|
237
239
|
# Filter out return type and only process parameters
|
|
238
|
-
excluded_params = ["return", "agent", "team"]
|
|
240
|
+
excluded_params = ["return", "agent", "team", "self"]
|
|
239
241
|
if self.requires_user_input and self.user_input_fields:
|
|
240
242
|
if len(self.user_input_fields) == 0:
|
|
241
243
|
excluded_params.extend(list(type_hints.keys()))
|
|
@@ -337,7 +339,9 @@ class Function(BaseModel):
|
|
|
337
339
|
|
|
338
340
|
def process_schema_for_strict(self):
|
|
339
341
|
self.parameters["additionalProperties"] = False
|
|
340
|
-
self.parameters["required"] = [
|
|
342
|
+
self.parameters["required"] = [
|
|
343
|
+
name for name in self.parameters["properties"] if name not in ["agent", "team", "self"]
|
|
344
|
+
]
|
|
341
345
|
|
|
342
346
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
343
347
|
"""Generate a cache key based on function name and arguments."""
|
|
@@ -554,7 +558,7 @@ class FunctionCall(BaseModel):
|
|
|
554
558
|
from functools import reduce
|
|
555
559
|
from inspect import iscoroutinefunction
|
|
556
560
|
|
|
557
|
-
def execute_entrypoint():
|
|
561
|
+
def execute_entrypoint(name, func, args):
|
|
558
562
|
"""Execute the entrypoint function."""
|
|
559
563
|
arguments = entrypoint_args.copy()
|
|
560
564
|
if self.arguments is not None:
|
|
@@ -572,9 +576,9 @@ class FunctionCall(BaseModel):
|
|
|
572
576
|
# Pass the inner function as next_func to the hook
|
|
573
577
|
# The hook will call next_func to continue the chain
|
|
574
578
|
def next_func(**kwargs):
|
|
575
|
-
return inner_func()
|
|
579
|
+
return inner_func(name, func, kwargs)
|
|
576
580
|
|
|
577
|
-
hook_args = self._build_hook_args(hook, name,
|
|
581
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
578
582
|
|
|
579
583
|
return hook(**hook_args)
|
|
580
584
|
|
|
@@ -716,7 +720,7 @@ class FunctionCall(BaseModel):
|
|
|
716
720
|
from functools import reduce
|
|
717
721
|
from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction
|
|
718
722
|
|
|
719
|
-
async def execute_entrypoint_async():
|
|
723
|
+
async def execute_entrypoint_async(name, func, args):
|
|
720
724
|
"""Execute the entrypoint function asynchronously."""
|
|
721
725
|
arguments = entrypoint_args.copy()
|
|
722
726
|
if self.arguments is not None:
|
|
@@ -729,7 +733,7 @@ class FunctionCall(BaseModel):
|
|
|
729
733
|
result = await result
|
|
730
734
|
return result
|
|
731
735
|
|
|
732
|
-
def execute_entrypoint():
|
|
736
|
+
def execute_entrypoint(name, func, args):
|
|
733
737
|
"""Execute the entrypoint function synchronously."""
|
|
734
738
|
arguments = entrypoint_args.copy()
|
|
735
739
|
if self.arguments is not None:
|
|
@@ -750,11 +754,11 @@ class FunctionCall(BaseModel):
|
|
|
750
754
|
# The hook will call next_func to continue the chain
|
|
751
755
|
async def next_func(**kwargs):
|
|
752
756
|
if iscoroutinefunction(inner_func):
|
|
753
|
-
return await inner_func()
|
|
757
|
+
return await inner_func(name, func, kwargs)
|
|
754
758
|
else:
|
|
755
|
-
return inner_func()
|
|
759
|
+
return inner_func(name, func, kwargs)
|
|
756
760
|
|
|
757
|
-
hook_args = self._build_hook_args(hook, name,
|
|
761
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
758
762
|
|
|
759
763
|
if iscoroutinefunction(hook):
|
|
760
764
|
return await hook(**hook_args)
|
agno/utils/log.py
CHANGED
|
@@ -7,6 +7,7 @@ from rich.text import Text
|
|
|
7
7
|
|
|
8
8
|
LOGGER_NAME = "agno"
|
|
9
9
|
TEAM_LOGGER_NAME = f"{LOGGER_NAME}-team"
|
|
10
|
+
WORKFLOW_LOGGER_NAME = f"{LOGGER_NAME}-workflow"
|
|
10
11
|
|
|
11
12
|
# Define custom styles for different log sources
|
|
12
13
|
LOG_STYLES = {
|
|
@@ -18,6 +19,10 @@ LOG_STYLES = {
|
|
|
18
19
|
"debug": "magenta",
|
|
19
20
|
"info": "steel_blue1",
|
|
20
21
|
},
|
|
22
|
+
"workflow": {
|
|
23
|
+
"debug": "sandy_brown",
|
|
24
|
+
"info": "orange3",
|
|
25
|
+
},
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
|
|
@@ -92,6 +97,7 @@ def build_logger(logger_name: str, source_type: Optional[str] = None) -> Any:
|
|
|
92
97
|
|
|
93
98
|
agent_logger: AgnoLogger = build_logger(LOGGER_NAME, source_type="agent")
|
|
94
99
|
team_logger: AgnoLogger = build_logger(TEAM_LOGGER_NAME, source_type="team")
|
|
100
|
+
workflow_logger: AgnoLogger = build_logger(WORKFLOW_LOGGER_NAME, source_type="workflow")
|
|
95
101
|
|
|
96
102
|
# Set the default logger to the agent logger
|
|
97
103
|
logger: AgnoLogger = agent_logger
|
|
@@ -146,6 +152,12 @@ def use_agent_logger():
|
|
|
146
152
|
logger = agent_logger
|
|
147
153
|
|
|
148
154
|
|
|
155
|
+
def use_workflow_logger():
|
|
156
|
+
"""Switch the default logger to use workflow_logger"""
|
|
157
|
+
global logger
|
|
158
|
+
logger = workflow_logger
|
|
159
|
+
|
|
160
|
+
|
|
149
161
|
def log_debug(msg, center: bool = False, symbol: str = "*", log_level: Literal[1, 2] = 1, *args, **kwargs):
|
|
150
162
|
global logger
|
|
151
163
|
global debug_on
|
agno/utils/message.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from typing import Dict, List, Union
|
|
2
2
|
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
3
5
|
from agno.models.message import Message
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
def get_text_from_message(message: Union[List, Dict, str, Message]) -> str:
|
|
8
|
+
def get_text_from_message(message: Union[List, Dict, str, Message, BaseModel]) -> str:
|
|
7
9
|
"""Return the user texts from the message"""
|
|
8
10
|
|
|
9
11
|
if isinstance(message, str):
|
|
10
12
|
return message
|
|
13
|
+
if isinstance(message, BaseModel):
|
|
14
|
+
return message.model_dump_json(indent=2, exclude_none=True)
|
|
11
15
|
if isinstance(message, list):
|
|
12
16
|
text_messages = []
|
|
13
17
|
if len(message) == 0:
|