agno 2.0.5__py3-none-any.whl → 2.0.6__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 (57) hide show
  1. agno/agent/agent.py +53 -17
  2. agno/db/dynamo/dynamo.py +7 -5
  3. agno/db/firestore/firestore.py +4 -2
  4. agno/db/gcs_json/gcs_json_db.py +4 -2
  5. agno/db/json/json_db.py +8 -4
  6. agno/db/mongo/mongo.py +6 -4
  7. agno/db/mysql/mysql.py +2 -1
  8. agno/db/postgres/postgres.py +2 -1
  9. agno/db/redis/redis.py +1 -1
  10. agno/db/singlestore/singlestore.py +2 -2
  11. agno/db/sqlite/sqlite.py +1 -1
  12. agno/knowledge/embedder/openai.py +19 -11
  13. agno/knowledge/knowledge.py +4 -3
  14. agno/knowledge/reader/website_reader.py +33 -16
  15. agno/media.py +70 -0
  16. agno/models/aimlapi/aimlapi.py +2 -2
  17. agno/models/base.py +31 -4
  18. agno/models/cerebras/cerebras_openai.py +2 -2
  19. agno/models/deepinfra/deepinfra.py +2 -2
  20. agno/models/deepseek/deepseek.py +2 -2
  21. agno/models/fireworks/fireworks.py +2 -2
  22. agno/models/internlm/internlm.py +2 -2
  23. agno/models/langdb/langdb.py +4 -4
  24. agno/models/litellm/litellm_openai.py +2 -2
  25. agno/models/message.py +26 -0
  26. agno/models/meta/llama_openai.py +2 -2
  27. agno/models/nebius/nebius.py +2 -2
  28. agno/models/nexus/__init__.py +3 -0
  29. agno/models/nexus/nexus.py +25 -0
  30. agno/models/nvidia/nvidia.py +2 -2
  31. agno/models/openrouter/openrouter.py +2 -2
  32. agno/models/perplexity/perplexity.py +2 -2
  33. agno/models/portkey/portkey.py +3 -3
  34. agno/models/response.py +2 -1
  35. agno/models/sambanova/sambanova.py +2 -2
  36. agno/models/together/together.py +2 -2
  37. agno/models/vercel/v0.py +2 -2
  38. agno/models/xai/xai.py +2 -2
  39. agno/os/router.py +3 -1
  40. agno/os/utils.py +1 -1
  41. agno/run/agent.py +16 -0
  42. agno/run/team.py +15 -0
  43. agno/run/workflow.py +10 -0
  44. agno/team/team.py +37 -7
  45. agno/tools/e2b.py +14 -7
  46. agno/tools/file_generation.py +350 -0
  47. agno/tools/function.py +2 -0
  48. agno/utils/gemini.py +24 -4
  49. agno/vectordb/chroma/chromadb.py +66 -25
  50. agno/vectordb/lancedb/lance_db.py +15 -4
  51. agno/vectordb/milvus/milvus.py +6 -0
  52. agno/workflow/workflow.py +4 -0
  53. {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/METADATA +4 -1
  54. {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/RECORD +57 -54
  55. {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/WHEEL +0 -0
  56. {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/licenses/LICENSE +0 -0
  57. {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/top_level.txt +0 -0
agno/utils/gemini.py CHANGED
@@ -146,13 +146,24 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
146
146
  # For Gemini, we need to represent Dict[str, T] as an object with at least one property
147
147
  # to avoid the "properties should be non-empty" error.
148
148
  # We'll create a generic property that represents the dictionary structure
149
- value_type = additional_props.get("type", "string").upper()
149
+
150
+ # Handle both single types and union types (arrays) from Zod schemas
151
+ type_value = additional_props.get("type", "string")
152
+ if isinstance(type_value, list):
153
+ value_type = type_value[0].upper() if type_value else "STRING"
154
+ union_types = ", ".join(type_value)
155
+ type_description_suffix = f" (supports union types: {union_types})"
156
+ else:
157
+ # Single type
158
+ value_type = type_value.upper()
159
+ type_description_suffix = ""
160
+
150
161
  # Create a placeholder property to satisfy Gemini's requirements
151
162
  # This is a workaround since Gemini doesn't support additionalProperties directly
152
163
  placeholder_properties = {
153
164
  "example_key": Schema(
154
165
  type=value_type,
155
- description=f"Example key-value pair. This object can contain any number of keys with {value_type.lower()} values.",
166
+ description=f"Example key-value pair. This object can contain any number of keys with {value_type.lower()} values{type_description_suffix}.",
156
167
  )
157
168
  }
158
169
  if value_type == "ARRAY":
@@ -162,7 +173,7 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
162
173
  type=Type.OBJECT,
163
174
  properties=placeholder_properties,
164
175
  description=description
165
- or f"Dictionary with {value_type.lower()} values. Can contain any number of key-value pairs.",
176
+ or f"Dictionary with {value_type.lower()} values{type_description_suffix}. Can contain any number of key-value pairs.",
166
177
  default=default,
167
178
  )
168
179
  else:
@@ -174,7 +185,10 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
174
185
  return Schema(type=Type.OBJECT, description=description, default=default)
175
186
 
176
187
  elif schema_type == "array" and "items" in schema_dict:
177
- items = convert_schema(schema_dict["items"], root_schema)
188
+ if not schema_dict["items"]: # Handle empty {}
189
+ items = Schema(type=Type.STRING)
190
+ else:
191
+ items = convert_schema(schema_dict["items"], root_schema)
178
192
  min_items = schema_dict.get("minItems")
179
193
  max_items = schema_dict.get("maxItems")
180
194
  return Schema(
@@ -233,6 +247,12 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
233
247
  default=default,
234
248
  )
235
249
  else:
250
+ if isinstance(schema_type, list):
251
+ non_null_types = [t for t in schema_type if t != "null"]
252
+ if non_null_types:
253
+ schema_type = non_null_types[0]
254
+ else:
255
+ schema_type = ""
236
256
  # Only convert to uppercase if schema_type is not empty
237
257
  if schema_type:
238
258
  schema_type = schema_type.upper()
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import json
2
3
  from hashlib import md5
3
4
  from typing import Any, Dict, List, Mapping, Optional, Union, cast
4
5
 
@@ -60,6 +61,44 @@ class ChromaDb(VectorDb):
60
61
  # Chroma client kwargs
61
62
  self.kwargs = kwargs
62
63
 
64
+ def _flatten_metadata(self, metadata: Dict[str, Any]) -> Dict[str, Union[str, int, float, bool]]:
65
+ """
66
+ Flatten nested metadata to ChromaDB-compatible format.
67
+
68
+ Args:
69
+ metadata: Dictionary that may contain nested structures
70
+
71
+ Returns:
72
+ Flattened dictionary with only primitive values
73
+ """
74
+ flattened: Dict[str, Any] = {}
75
+
76
+ def _flatten_recursive(obj: Any, prefix: str = "") -> None:
77
+ if isinstance(obj, dict):
78
+ if len(obj) == 0:
79
+ # Handle empty dictionaries by converting to JSON string
80
+ flattened[prefix] = json.dumps(obj)
81
+ else:
82
+ for key, value in obj.items():
83
+ new_key = f"{prefix}.{key}" if prefix else key
84
+ _flatten_recursive(value, new_key)
85
+ elif isinstance(obj, (list, tuple)):
86
+ # Convert lists/tuples to JSON strings
87
+ flattened[prefix] = json.dumps(obj)
88
+ elif isinstance(obj, (str, int, float, bool)) or obj is None:
89
+ if obj is not None: # ChromaDB doesn't accept None values
90
+ flattened[prefix] = obj
91
+ else:
92
+ # Convert other complex types to JSON strings
93
+ try:
94
+ flattened[prefix] = json.dumps(obj)
95
+ except (TypeError, ValueError):
96
+ # If it can't be serialized, convert to string
97
+ flattened[prefix] = str(obj)
98
+
99
+ _flatten_recursive(metadata)
100
+ return flattened
101
+
63
102
  @property
64
103
  def client(self) -> ClientAPI:
65
104
  if self._client is None:
@@ -147,11 +186,14 @@ class ChromaDb(VectorDb):
147
186
 
148
187
  metadata["content_hash"] = content_hash
149
188
 
189
+ # Flatten metadata for ChromaDB compatibility
190
+ flattened_metadata = self._flatten_metadata(metadata)
191
+
150
192
  docs_embeddings.append(document.embedding)
151
193
  docs.append(cleaned_content)
152
194
  ids.append(doc_id)
153
- docs_metadata.append(metadata)
154
- log_debug(f"Prepared document: {document.id} | {document.name} | {metadata}")
195
+ docs_metadata.append(flattened_metadata)
196
+ log_debug(f"Prepared document: {document.id} | {document.name} | {flattened_metadata}")
155
197
 
156
198
  if self._collection is None:
157
199
  logger.warning("Collection does not exist")
@@ -196,11 +238,14 @@ class ChromaDb(VectorDb):
196
238
 
197
239
  metadata["content_hash"] = content_hash
198
240
 
241
+ # Flatten metadata for ChromaDB compatibility
242
+ flattened_metadata = self._flatten_metadata(metadata)
243
+
199
244
  docs_embeddings.append(document.embedding)
200
245
  docs.append(cleaned_content)
201
246
  ids.append(doc_id)
202
- docs_metadata.append(metadata)
203
- log_debug(f"Prepared document: {document.id} | {document.name} | {metadata}")
247
+ docs_metadata.append(flattened_metadata)
248
+ log_debug(f"Prepared document: {document.id} | {document.name} | {flattened_metadata}")
204
249
 
205
250
  if self._collection is None:
206
251
  logger.warning("Collection does not exist")
@@ -262,11 +307,14 @@ class ChromaDb(VectorDb):
262
307
 
263
308
  metadata["content_hash"] = content_hash
264
309
 
310
+ # Flatten metadata for ChromaDB compatibility
311
+ flattened_metadata = self._flatten_metadata(metadata)
312
+
265
313
  docs_embeddings.append(document.embedding)
266
314
  docs.append(cleaned_content)
267
315
  ids.append(doc_id)
268
- docs_metadata.append(metadata)
269
- log_debug(f"Upserted document: {document.id} | {document.name} | {metadata}")
316
+ docs_metadata.append(flattened_metadata)
317
+ log_debug(f"Upserted document: {document.id} | {document.name} | {flattened_metadata}")
270
318
 
271
319
  if self._collection is None:
272
320
  logger.warning("Collection does not exist")
@@ -313,11 +361,14 @@ class ChromaDb(VectorDb):
313
361
 
314
362
  metadata["content_hash"] = content_hash
315
363
 
364
+ # Flatten metadata for ChromaDB compatibility
365
+ flattened_metadata = self._flatten_metadata(metadata)
366
+
316
367
  docs_embeddings.append(document.embedding)
317
368
  docs.append(cleaned_content)
318
369
  ids.append(doc_id)
319
- docs_metadata.append(metadata)
320
- log_debug(f"Upserted document: {document.id} | {document.name} | {metadata}")
370
+ docs_metadata.append(flattened_metadata)
371
+ log_debug(f"Upserted document: {document.id} | {document.name} | {flattened_metadata}")
321
372
 
322
373
  if self._collection is None:
323
374
  logger.warning("Collection does not exist")
@@ -747,6 +798,9 @@ class ChromaDb(VectorDb):
747
798
  logger.debug(f"No documents found with content_id: {content_id}")
748
799
  return
749
800
 
801
+ # Flatten the new metadata first
802
+ flattened_new_metadata = self._flatten_metadata(metadata)
803
+
750
804
  # Merge metadata for each document
751
805
  updated_metadatas = []
752
806
  for i, current_meta in enumerate(current_metadatas or []):
@@ -754,26 +808,13 @@ class ChromaDb(VectorDb):
754
808
  meta_dict: Dict[str, Any] = {}
755
809
  else:
756
810
  meta_dict = dict(current_meta) # Convert Mapping to dict
757
- updated_meta: Dict[str, Any] = meta_dict.copy()
758
- updated_meta.update(metadata)
759
-
760
- if "filters" not in updated_meta:
761
- updated_meta["filters"] = {}
762
- if isinstance(updated_meta["filters"], dict):
763
- updated_meta["filters"].update(metadata)
764
- else:
765
- updated_meta["filters"] = metadata
766
- updated_metadatas.append(updated_meta)
767
811
 
768
- # Update the documents
769
- # Filter out None values from metadata as ChromaDB doesn't accept them
770
- cleaned_metadatas = []
771
- for meta in updated_metadatas:
772
- cleaned_meta = {k: v for k, v in meta.items() if v is not None}
773
- cleaned_metadatas.append(cleaned_meta)
812
+ # Update with flattened metadata
813
+ meta_dict.update(flattened_new_metadata)
814
+ updated_metadatas.append(meta_dict)
774
815
 
775
816
  # Convert to the expected type for ChromaDB
776
- chroma_metadatas = cast(List[Mapping[str, Union[str, int, float, bool]]], cleaned_metadatas)
817
+ chroma_metadatas = cast(List[Mapping[str, Union[str, int, float, bool]]], updated_metadatas)
777
818
  collection.update(ids=ids, metadatas=chroma_metadatas) # type: ignore
778
819
  logger.debug(f"Updated metadata for {len(ids)} documents with content_id: {content_id}")
779
820
 
@@ -950,17 +950,28 @@ class LanceDb(VectorDb):
950
950
  logger.error("Table not initialized")
951
951
  return
952
952
 
953
- # Search for documents with the given content_id
954
- query_filter = f"payload->>'content_id' = '{content_id}'"
955
- results = self.table.search().where(query_filter).to_pandas()
953
+ # Get all documents and filter in Python (LanceDB doesn't support JSON operators)
954
+ total_count = self.table.count_rows()
955
+ results = self.table.search().select(["id", "payload"]).limit(total_count).to_pandas()
956
956
 
957
957
  if results.empty:
958
+ logger.debug("No documents found")
959
+ return
960
+
961
+ # Find matching documents with the given content_id
962
+ matching_rows = []
963
+ for _, row in results.iterrows():
964
+ payload = json.loads(row["payload"])
965
+ if payload.get("content_id") == content_id:
966
+ matching_rows.append(row)
967
+
968
+ if not matching_rows:
958
969
  logger.debug(f"No documents found with content_id: {content_id}")
959
970
  return
960
971
 
961
972
  # Update each matching document
962
973
  updated_count = 0
963
- for _, row in results.iterrows():
974
+ for row in matching_rows:
964
975
  row_id = row["id"]
965
976
  current_payload = json.loads(row["payload"])
966
977
 
@@ -423,6 +423,9 @@ class Milvus(VectorDb):
423
423
  else:
424
424
  for document in documents:
425
425
  document.embed(embedder=self.embedder)
426
+ if not document.embedding:
427
+ log_debug(f"Skipping document without embedding: {document.name} ({document.meta_data})")
428
+ continue
426
429
  cleaned_content = document.content.replace("\x00", "\ufffd")
427
430
  doc_id = md5(cleaned_content.encode()).hexdigest()
428
431
 
@@ -465,6 +468,9 @@ class Milvus(VectorDb):
465
468
 
466
469
  async def process_document(document):
467
470
  document.embed(embedder=self.embedder)
471
+ if not document.embedding:
472
+ log_debug(f"Skipping document without embedding: {document.name} ({document.meta_data})")
473
+ return None
468
474
  cleaned_content = document.content.replace("\x00", "\ufffd")
469
475
  doc_id = md5(cleaned_content.encode()).hexdigest()
470
476
 
agno/workflow/workflow.py CHANGED
@@ -1708,6 +1708,7 @@ class Workflow:
1708
1708
  # Create workflow run response with PENDING status
1709
1709
  workflow_run_response = WorkflowRunOutput(
1710
1710
  run_id=run_id,
1711
+ input=input,
1711
1712
  session_id=session_id,
1712
1713
  workflow_id=self.id,
1713
1714
  workflow_name=self.name,
@@ -1798,6 +1799,7 @@ class Workflow:
1798
1799
  # Create workflow run response with PENDING status
1799
1800
  workflow_run_response = WorkflowRunOutput(
1800
1801
  run_id=run_id,
1802
+ input=input,
1801
1803
  session_id=session_id,
1802
1804
  workflow_id=self.id,
1803
1805
  workflow_name=self.name,
@@ -1971,6 +1973,7 @@ class Workflow:
1971
1973
  # Create workflow run response that will be updated by reference
1972
1974
  workflow_run_response = WorkflowRunOutput(
1973
1975
  run_id=run_id,
1976
+ input=input,
1974
1977
  session_id=session_id,
1975
1978
  workflow_id=self.id,
1976
1979
  workflow_name=self.name,
@@ -2139,6 +2142,7 @@ class Workflow:
2139
2142
  # Create workflow run response that will be updated by reference
2140
2143
  workflow_run_response = WorkflowRunOutput(
2141
2144
  run_id=run_id,
2145
+ input=input,
2142
2146
  session_id=session_id,
2143
2147
  workflow_id=self.id,
2144
2148
  workflow_name=self.name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.0.5
3
+ Version: 2.0.6
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
@@ -161,6 +161,8 @@ Requires-Dist: opencv-python; extra == "opencv"
161
161
  Provides-Extra: psycopg
162
162
  Requires-Dist: psycopg-binary; extra == "psycopg"
163
163
  Requires-Dist: psycopg; extra == "psycopg"
164
+ Provides-Extra: reportlab
165
+ Requires-Dist: reportlab; extra == "reportlab"
164
166
  Provides-Extra: todoist
165
167
  Requires-Dist: todoist-api-python; extra == "todoist"
166
168
  Provides-Extra: valyu
@@ -306,6 +308,7 @@ Requires-Dist: agno[mem0]; extra == "tools"
306
308
  Requires-Dist: agno[memori]; extra == "tools"
307
309
  Requires-Dist: agno[google_bigquery]; extra == "tools"
308
310
  Requires-Dist: agno[psycopg]; extra == "tools"
311
+ Requires-Dist: agno[reportlab]; extra == "tools"
309
312
  Requires-Dist: agno[trafilatura]; extra == "tools"
310
313
  Requires-Dist: agno[neo4j]; extra == "tools"
311
314
  Provides-Extra: storage