agno 2.0.2__py3-none-any.whl → 2.0.4__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 +164 -87
- agno/db/dynamo/dynamo.py +8 -0
- agno/db/firestore/firestore.py +8 -1
- agno/db/gcs_json/gcs_json_db.py +9 -0
- agno/db/json/json_db.py +8 -0
- agno/db/mongo/mongo.py +10 -1
- agno/db/mysql/mysql.py +10 -0
- agno/db/postgres/postgres.py +16 -8
- agno/db/redis/redis.py +6 -0
- agno/db/singlestore/schemas.py +1 -1
- agno/db/singlestore/singlestore.py +8 -1
- agno/db/sqlite/sqlite.py +9 -1
- agno/db/utils.py +14 -0
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/knowledge.py +91 -65
- agno/knowledge/reader/base.py +3 -0
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/knowledge/reader/markdown_reader.py +5 -5
- agno/knowledge/reader/s3_reader.py +0 -12
- agno/knowledge/reader/text_reader.py +5 -5
- agno/models/base.py +2 -2
- agno/models/cerebras/cerebras.py +5 -3
- agno/models/cerebras/cerebras_openai.py +5 -3
- agno/models/google/gemini.py +33 -11
- agno/models/litellm/chat.py +1 -1
- agno/models/openai/chat.py +3 -0
- agno/models/openai/responses.py +81 -40
- agno/models/response.py +5 -0
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/os/app.py +4 -1
- agno/os/auth.py +24 -14
- agno/os/interfaces/slack/router.py +1 -1
- agno/os/interfaces/whatsapp/router.py +2 -0
- agno/os/router.py +187 -76
- agno/os/routers/evals/utils.py +9 -9
- agno/os/routers/health.py +26 -0
- agno/os/routers/knowledge/knowledge.py +11 -11
- agno/os/routers/session/session.py +24 -8
- agno/os/schema.py +8 -2
- agno/run/agent.py +5 -2
- agno/run/base.py +6 -3
- agno/run/team.py +11 -3
- agno/run/workflow.py +69 -12
- agno/session/team.py +1 -0
- agno/team/team.py +196 -93
- agno/tools/mcp.py +1 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memory.py +419 -0
- agno/tools/workflow.py +279 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/print_response/agent.py +6 -2
- agno/utils/streamlit.py +14 -8
- agno/vectordb/chroma/chromadb.py +8 -2
- agno/workflow/step.py +111 -13
- agno/workflow/workflow.py +16 -13
- {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/METADATA +1 -1
- {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/RECORD +63 -58
- {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/WHEEL +0 -0
- {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.2.dist-info → agno-2.0.4.dist-info}/top_level.txt +0 -0
agno/workflow/step.py
CHANGED
|
@@ -164,6 +164,38 @@ class Step:
|
|
|
164
164
|
return response.metrics
|
|
165
165
|
return None
|
|
166
166
|
|
|
167
|
+
def _call_custom_function(
|
|
168
|
+
self,
|
|
169
|
+
func: Callable,
|
|
170
|
+
step_input: StepInput,
|
|
171
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
172
|
+
) -> Any:
|
|
173
|
+
"""Call custom function with session_state support if the function accepts it"""
|
|
174
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
175
|
+
return func(step_input, session_state)
|
|
176
|
+
else:
|
|
177
|
+
return func(step_input)
|
|
178
|
+
|
|
179
|
+
async def _acall_custom_function(
|
|
180
|
+
self,
|
|
181
|
+
func: Callable,
|
|
182
|
+
step_input: StepInput,
|
|
183
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
184
|
+
) -> Any:
|
|
185
|
+
"""Call custom async function with session_state support if the function accepts it"""
|
|
186
|
+
import inspect
|
|
187
|
+
|
|
188
|
+
if inspect.isasyncgenfunction(func):
|
|
189
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
190
|
+
return func(step_input, session_state)
|
|
191
|
+
else:
|
|
192
|
+
return func(step_input)
|
|
193
|
+
else:
|
|
194
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
195
|
+
return await func(step_input, session_state)
|
|
196
|
+
else:
|
|
197
|
+
return await func(step_input)
|
|
198
|
+
|
|
167
199
|
def execute(
|
|
168
200
|
self,
|
|
169
201
|
step_input: StepInput,
|
|
@@ -191,8 +223,11 @@ class Step:
|
|
|
191
223
|
if inspect.isgeneratorfunction(self.active_executor):
|
|
192
224
|
content = ""
|
|
193
225
|
final_response = None
|
|
226
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
194
227
|
try:
|
|
195
|
-
for chunk in self.
|
|
228
|
+
for chunk in self._call_custom_function(
|
|
229
|
+
self.active_executor, step_input, session_state_copy
|
|
230
|
+
): # type: ignore
|
|
196
231
|
if (
|
|
197
232
|
hasattr(chunk, "content")
|
|
198
233
|
and chunk.content is not None
|
|
@@ -208,13 +243,22 @@ class Step:
|
|
|
208
243
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
209
244
|
final_response = e.value
|
|
210
245
|
|
|
246
|
+
# Merge session_state changes back
|
|
247
|
+
if session_state_copy and session_state:
|
|
248
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
249
|
+
|
|
211
250
|
if final_response is not None:
|
|
212
251
|
response = final_response
|
|
213
252
|
else:
|
|
214
253
|
response = StepOutput(content=content)
|
|
215
254
|
else:
|
|
216
|
-
# Execute function
|
|
217
|
-
|
|
255
|
+
# Execute function with signature inspection for session_state support
|
|
256
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
257
|
+
result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
258
|
+
|
|
259
|
+
# Merge session_state changes back
|
|
260
|
+
if session_state_copy and session_state:
|
|
261
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
218
262
|
|
|
219
263
|
# If function returns StepOutput, use it directly
|
|
220
264
|
if isinstance(result, StepOutput):
|
|
@@ -291,6 +335,19 @@ class Step:
|
|
|
291
335
|
|
|
292
336
|
return StepOutput(content=f"Step {self.name} failed but skipped", success=False)
|
|
293
337
|
|
|
338
|
+
def _function_has_session_state_param(self) -> bool:
|
|
339
|
+
"""Check if the custom function has a session_state parameter"""
|
|
340
|
+
if self._executor_type != "function":
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
from inspect import signature
|
|
345
|
+
|
|
346
|
+
sig = signature(self.active_executor) # type: ignore
|
|
347
|
+
return "session_state" in sig.parameters
|
|
348
|
+
except Exception:
|
|
349
|
+
return False
|
|
350
|
+
|
|
294
351
|
def execute_stream(
|
|
295
352
|
self,
|
|
296
353
|
step_input: StepInput,
|
|
@@ -338,8 +395,10 @@ class Step:
|
|
|
338
395
|
if inspect.isgeneratorfunction(self.active_executor):
|
|
339
396
|
log_debug("Function returned iterable, streaming events")
|
|
340
397
|
content = ""
|
|
398
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
341
399
|
try:
|
|
342
|
-
|
|
400
|
+
iterator = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
401
|
+
for event in iterator: # type: ignore
|
|
343
402
|
if (
|
|
344
403
|
hasattr(event, "content")
|
|
345
404
|
and event.content is not None
|
|
@@ -353,6 +412,11 @@ class Step:
|
|
|
353
412
|
break
|
|
354
413
|
else:
|
|
355
414
|
yield event # type: ignore[misc]
|
|
415
|
+
|
|
416
|
+
# Merge session_state changes back
|
|
417
|
+
if session_state_copy and session_state:
|
|
418
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
419
|
+
|
|
356
420
|
if not final_response:
|
|
357
421
|
final_response = StepOutput(content=content)
|
|
358
422
|
except StopIteration as e:
|
|
@@ -360,7 +424,13 @@ class Step:
|
|
|
360
424
|
final_response = e.value
|
|
361
425
|
|
|
362
426
|
else:
|
|
363
|
-
|
|
427
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
428
|
+
result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
429
|
+
|
|
430
|
+
# Merge session_state changes back
|
|
431
|
+
if session_state_copy and session_state:
|
|
432
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
433
|
+
|
|
364
434
|
if isinstance(result, StepOutput):
|
|
365
435
|
final_response = result
|
|
366
436
|
else:
|
|
@@ -505,9 +575,13 @@ class Step:
|
|
|
505
575
|
):
|
|
506
576
|
content = ""
|
|
507
577
|
final_response = None
|
|
578
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
508
579
|
try:
|
|
509
580
|
if inspect.isgeneratorfunction(self.active_executor):
|
|
510
|
-
|
|
581
|
+
iterator = self._call_custom_function(
|
|
582
|
+
self.active_executor, step_input, session_state_copy
|
|
583
|
+
) # type: ignore
|
|
584
|
+
for chunk in iterator: # type: ignore
|
|
511
585
|
if (
|
|
512
586
|
hasattr(chunk, "content")
|
|
513
587
|
and chunk.content is not None
|
|
@@ -520,7 +594,10 @@ class Step:
|
|
|
520
594
|
final_response = chunk
|
|
521
595
|
else:
|
|
522
596
|
if inspect.isasyncgenfunction(self.active_executor):
|
|
523
|
-
|
|
597
|
+
iterator = await self._acall_custom_function(
|
|
598
|
+
self.active_executor, step_input, session_state_copy
|
|
599
|
+
) # type: ignore
|
|
600
|
+
async for chunk in iterator: # type: ignore
|
|
524
601
|
if (
|
|
525
602
|
hasattr(chunk, "content")
|
|
526
603
|
and chunk.content is not None
|
|
@@ -536,15 +613,26 @@ class Step:
|
|
|
536
613
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
537
614
|
final_response = e.value
|
|
538
615
|
|
|
616
|
+
# Merge session_state changes back
|
|
617
|
+
if session_state_copy and session_state:
|
|
618
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
619
|
+
|
|
539
620
|
if final_response is not None:
|
|
540
621
|
response = final_response
|
|
541
622
|
else:
|
|
542
623
|
response = StepOutput(content=content)
|
|
543
624
|
else:
|
|
625
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
544
626
|
if inspect.iscoroutinefunction(self.active_executor):
|
|
545
|
-
result = await self.
|
|
627
|
+
result = await self._acall_custom_function(
|
|
628
|
+
self.active_executor, step_input, session_state_copy
|
|
629
|
+
) # type: ignore
|
|
546
630
|
else:
|
|
547
|
-
result = self.active_executor
|
|
631
|
+
result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
632
|
+
|
|
633
|
+
# Merge session_state changes back
|
|
634
|
+
if session_state_copy and session_state:
|
|
635
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
548
636
|
|
|
549
637
|
# If function returns StepOutput, use it directly
|
|
550
638
|
if isinstance(result, StepOutput):
|
|
@@ -662,11 +750,16 @@ class Step:
|
|
|
662
750
|
log_debug(f"Executing async function executor for step: {self.name}")
|
|
663
751
|
import inspect
|
|
664
752
|
|
|
753
|
+
session_state_copy = copy(session_state) if session_state else None
|
|
754
|
+
|
|
665
755
|
# Check if the function is an async generator
|
|
666
756
|
if inspect.isasyncgenfunction(self.active_executor):
|
|
667
757
|
content = ""
|
|
668
758
|
# It's an async generator - iterate over it
|
|
669
|
-
|
|
759
|
+
iterator = await self._acall_custom_function(
|
|
760
|
+
self.active_executor, step_input, session_state_copy
|
|
761
|
+
) # type: ignore
|
|
762
|
+
async for event in iterator: # type: ignore
|
|
670
763
|
if (
|
|
671
764
|
hasattr(event, "content")
|
|
672
765
|
and event.content is not None
|
|
@@ -684,7 +777,7 @@ class Step:
|
|
|
684
777
|
final_response = StepOutput(content=content)
|
|
685
778
|
elif inspect.iscoroutinefunction(self.active_executor):
|
|
686
779
|
# It's a regular async function - await it
|
|
687
|
-
result = await self.active_executor
|
|
780
|
+
result = await self._acall_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
688
781
|
if isinstance(result, StepOutput):
|
|
689
782
|
final_response = result
|
|
690
783
|
else:
|
|
@@ -692,7 +785,8 @@ class Step:
|
|
|
692
785
|
elif inspect.isgeneratorfunction(self.active_executor):
|
|
693
786
|
content = ""
|
|
694
787
|
# It's a regular generator function - iterate over it
|
|
695
|
-
|
|
788
|
+
iterator = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
789
|
+
for event in iterator: # type: ignore
|
|
696
790
|
if (
|
|
697
791
|
hasattr(event, "content")
|
|
698
792
|
and event.content is not None
|
|
@@ -710,11 +804,15 @@ class Step:
|
|
|
710
804
|
final_response = StepOutput(content=content)
|
|
711
805
|
else:
|
|
712
806
|
# It's a regular function - call it directly
|
|
713
|
-
result = self.active_executor
|
|
807
|
+
result = self._call_custom_function(self.active_executor, step_input, session_state_copy) # type: ignore
|
|
714
808
|
if isinstance(result, StepOutput):
|
|
715
809
|
final_response = result
|
|
716
810
|
else:
|
|
717
811
|
final_response = StepOutput(content=str(result))
|
|
812
|
+
|
|
813
|
+
# Merge session_state changes back
|
|
814
|
+
if session_state_copy and session_state:
|
|
815
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
718
816
|
else:
|
|
719
817
|
# For agents and teams, prepare message with context
|
|
720
818
|
message = self._prepare_message(
|
agno/workflow/workflow.py
CHANGED
|
@@ -51,6 +51,7 @@ from agno.run.workflow import (
|
|
|
51
51
|
)
|
|
52
52
|
from agno.session.workflow import WorkflowSession
|
|
53
53
|
from agno.team.team import Team
|
|
54
|
+
from agno.utils.common import is_typed_dict, validate_typed_dict
|
|
54
55
|
from agno.utils.log import (
|
|
55
56
|
log_debug,
|
|
56
57
|
log_warning,
|
|
@@ -217,14 +218,18 @@ class Workflow:
|
|
|
217
218
|
|
|
218
219
|
def _validate_input(
|
|
219
220
|
self, input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]]]
|
|
220
|
-
) -> Optional[BaseModel]:
|
|
221
|
+
) -> Optional[Union[str, List, Dict, Message, BaseModel]]:
|
|
221
222
|
"""Parse and validate input against input_schema if provided"""
|
|
222
223
|
if self.input_schema is None:
|
|
223
|
-
return
|
|
224
|
+
return input # Return input unchanged if no schema is set
|
|
224
225
|
|
|
225
226
|
if input is None:
|
|
226
227
|
raise ValueError("Input required when input_schema is set")
|
|
227
228
|
|
|
229
|
+
# Handle Message objects - extract content
|
|
230
|
+
if isinstance(input, Message):
|
|
231
|
+
input = input.content # type: ignore
|
|
232
|
+
|
|
228
233
|
# If input is a string, convert it to a dict
|
|
229
234
|
if isinstance(input, str):
|
|
230
235
|
import json
|
|
@@ -238,8 +243,6 @@ class Workflow:
|
|
|
238
243
|
if isinstance(input, BaseModel):
|
|
239
244
|
if isinstance(input, self.input_schema):
|
|
240
245
|
try:
|
|
241
|
-
# Re-validate to catch any field validation errors
|
|
242
|
-
input.model_validate(input.model_dump())
|
|
243
246
|
return input
|
|
244
247
|
except Exception as e:
|
|
245
248
|
raise ValueError(f"BaseModel validation failed: {str(e)}")
|
|
@@ -250,8 +253,13 @@ class Workflow:
|
|
|
250
253
|
# Case 2: Message is a dict
|
|
251
254
|
elif isinstance(input, dict):
|
|
252
255
|
try:
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
# Check if the schema is a TypedDict
|
|
257
|
+
if is_typed_dict(self.input_schema):
|
|
258
|
+
validated_dict = validate_typed_dict(input, self.input_schema)
|
|
259
|
+
return validated_dict
|
|
260
|
+
else:
|
|
261
|
+
validated_model = self.input_schema(**input)
|
|
262
|
+
return validated_model
|
|
255
263
|
except Exception as e:
|
|
256
264
|
raise ValueError(f"Failed to parse dict into {self.input_schema.__name__}: {str(e)}")
|
|
257
265
|
|
|
@@ -1924,10 +1932,7 @@ class Workflow:
|
|
|
1924
1932
|
) -> Union[WorkflowRunOutput, Iterator[WorkflowRunOutputEvent]]:
|
|
1925
1933
|
"""Execute the workflow synchronously with optional streaming"""
|
|
1926
1934
|
|
|
1927
|
-
|
|
1928
|
-
if validated_input is not None:
|
|
1929
|
-
input = validated_input
|
|
1930
|
-
|
|
1935
|
+
input = self._validate_input(input)
|
|
1931
1936
|
if background:
|
|
1932
1937
|
raise RuntimeError("Background execution is not supported for sync run()")
|
|
1933
1938
|
|
|
@@ -2059,9 +2064,7 @@ class Workflow:
|
|
|
2059
2064
|
) -> Union[WorkflowRunOutput, AsyncIterator[WorkflowRunOutputEvent]]:
|
|
2060
2065
|
"""Execute the workflow synchronously with optional streaming"""
|
|
2061
2066
|
|
|
2062
|
-
|
|
2063
|
-
if validated_input is not None:
|
|
2064
|
-
input = validated_input
|
|
2067
|
+
input = self._validate_input(input)
|
|
2065
2068
|
|
|
2066
2069
|
websocket_handler = None
|
|
2067
2070
|
if websocket:
|