agno 2.0.0rc2__py3-none-any.whl → 2.0.1__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 (51) hide show
  1. agno/agent/agent.py +78 -135
  2. agno/knowledge/knowledge.py +1 -1
  3. agno/knowledge/reranker/__init__.py +9 -0
  4. agno/media.py +269 -268
  5. agno/models/base.py +13 -48
  6. agno/models/google/gemini.py +11 -10
  7. agno/models/message.py +4 -4
  8. agno/models/ollama/chat.py +1 -1
  9. agno/models/openai/chat.py +33 -14
  10. agno/models/response.py +5 -5
  11. agno/os/app.py +8 -5
  12. agno/run/agent.py +28 -28
  13. agno/run/base.py +9 -19
  14. agno/run/team.py +24 -24
  15. agno/run/workflow.py +16 -16
  16. agno/team/team.py +72 -154
  17. agno/tools/brightdata.py +3 -3
  18. agno/tools/cartesia.py +3 -5
  19. agno/tools/dalle.py +7 -4
  20. agno/tools/desi_vocal.py +2 -2
  21. agno/tools/e2b.py +6 -6
  22. agno/tools/eleven_labs.py +3 -3
  23. agno/tools/fal.py +4 -4
  24. agno/tools/function.py +7 -7
  25. agno/tools/giphy.py +2 -2
  26. agno/tools/lumalab.py +3 -3
  27. agno/tools/models/azure_openai.py +2 -2
  28. agno/tools/models/gemini.py +3 -3
  29. agno/tools/models/groq.py +3 -5
  30. agno/tools/models/nebius.py +2 -2
  31. agno/tools/models_labs.py +5 -5
  32. agno/tools/openai.py +4 -9
  33. agno/tools/opencv.py +3 -3
  34. agno/tools/replicate.py +7 -7
  35. agno/utils/events.py +5 -5
  36. agno/utils/gemini.py +1 -1
  37. agno/utils/mcp.py +3 -3
  38. agno/utils/models/aws_claude.py +1 -1
  39. agno/utils/models/cohere.py +1 -1
  40. agno/utils/models/watsonx.py +1 -1
  41. agno/utils/openai.py +1 -1
  42. agno/vectordb/lancedb/lance_db.py +82 -25
  43. agno/workflow/step.py +7 -7
  44. agno/workflow/types.py +13 -13
  45. agno/workflow/workflow.py +28 -28
  46. {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/METADATA +140 -1
  47. {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/RECORD +50 -50
  48. agno-2.0.1.dist-info/licenses/LICENSE +201 -0
  49. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  50. {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/WHEEL +0 -0
  51. {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/top_level.txt +0 -0
@@ -140,6 +140,29 @@ class LanceDb(VectorDb):
140
140
 
141
141
  log_debug(f"Initialized LanceDb with table: '{self.table_name}'")
142
142
 
143
+ def _prepare_vector(self, embedding) -> List[float]:
144
+ """Prepare vector embedding for insertion, ensuring correct dimensions and type."""
145
+ if embedding is not None:
146
+ # Convert to list of floats
147
+ vector = [float(x) for x in embedding]
148
+
149
+ # Ensure vector has correct dimensions if specified
150
+ if self.dimensions:
151
+ if len(vector) != self.dimensions:
152
+ if len(vector) > self.dimensions:
153
+ # Truncate if too long
154
+ vector = vector[: self.dimensions]
155
+ log_debug(f"Truncated vector from {len(embedding)} to {self.dimensions} dimensions")
156
+ else:
157
+ # Pad with zeros if too short
158
+ vector.extend([0.0] * (self.dimensions - len(vector)))
159
+ log_debug(f"Padded vector from {len(embedding)} to {self.dimensions} dimensions")
160
+
161
+ return vector
162
+ else:
163
+ # Fallback if embedding is None
164
+ return [0.0] * (self.dimensions or 1536)
165
+
143
166
  async def _get_async_connection(self) -> lancedb.AsyncConnection:
144
167
  """Get or create an async connection to LanceDB."""
145
168
  if self.async_connection is None:
@@ -174,22 +197,37 @@ class LanceDb(VectorDb):
174
197
  async def async_create(self) -> None:
175
198
  """Create the table asynchronously if it does not exist."""
176
199
  if not await self.async_exists():
177
- conn = await self._get_async_connection()
178
- schema = self._base_schema()
200
+ try:
201
+ conn = await self._get_async_connection()
202
+ schema = self._base_schema()
179
203
 
180
- log_debug(f"Creating table asynchronously: {self.table_name}")
181
- self.async_table = await conn.create_table(self.table_name, schema=schema, mode="overwrite", exist_ok=True)
204
+ log_debug(f"Creating table asynchronously: {self.table_name}")
205
+ self.async_table = await conn.create_table(
206
+ self.table_name, schema=schema, mode="overwrite", exist_ok=True
207
+ )
208
+ log_debug(f"Successfully created async table: {self.table_name}")
209
+ except Exception as e:
210
+ logger.error(f"Error creating async table: {e}")
211
+ # Try to fall back to sync table creation
212
+ try:
213
+ log_debug("Falling back to sync table creation")
214
+ self.table = self._init_table()
215
+ log_debug("Sync table created successfully")
216
+ except Exception as sync_e:
217
+ logger.error(f"Sync table creation also failed: {sync_e}")
218
+ raise
182
219
 
183
220
  def _base_schema(self) -> pa.Schema:
221
+ # Use fixed-size list for vector field as required by LanceDB
222
+ if self.dimensions:
223
+ vector_field = pa.field(self._vector_col, pa.list_(pa.float32(), self.dimensions))
224
+ else:
225
+ # Fallback to dynamic list if dimensions not known (should be rare)
226
+ vector_field = pa.field(self._vector_col, pa.list_(pa.float32()))
227
+
184
228
  return pa.schema(
185
229
  [
186
- pa.field(
187
- self._vector_col,
188
- pa.list_(
189
- pa.float32(),
190
- len(self.embedder.get_embedding("test")), # type: ignore
191
- ),
192
- ),
230
+ vector_field,
193
231
  pa.field(self._id, pa.string()),
194
232
  pa.field("payload", pa.string()),
195
233
  ]
@@ -278,7 +316,7 @@ class LanceDb(VectorDb):
278
316
  data.append(
279
317
  {
280
318
  "id": doc_id,
281
- "vector": document.embedding,
319
+ "vector": self._prepare_vector(document.embedding),
282
320
  "payload": json.dumps(payload),
283
321
  }
284
322
  )
@@ -343,7 +381,7 @@ class LanceDb(VectorDb):
343
381
  data.append(
344
382
  {
345
383
  "id": doc_id,
346
- "vector": document.embedding,
384
+ "vector": self._prepare_vector(document.embedding),
347
385
  "payload": json.dumps(payload),
348
386
  }
349
387
  )
@@ -356,6 +394,19 @@ class LanceDb(VectorDb):
356
394
  try:
357
395
  await self._get_async_connection()
358
396
 
397
+ # Ensure the async table is created before inserting
398
+ if self.async_table is None:
399
+ try:
400
+ await self.async_create()
401
+ except Exception as create_e:
402
+ logger.error(f"Failed to create async table: {create_e}")
403
+ # Continue to fallback logic below
404
+
405
+ if self.async_table is None:
406
+ # Fall back to sync insertion if async table creation failed
407
+ logger.warning("Async table not available, falling back to sync insertion")
408
+ return self.insert(content_hash, documents, filters)
409
+
359
410
  if self.on_bad_vectors is not None:
360
411
  await self.async_table.add(data, on_bad_vectors=self.on_bad_vectors, fill_value=self.fill_value) # type: ignore
361
412
  else:
@@ -367,7 +418,14 @@ class LanceDb(VectorDb):
367
418
  self._refresh_sync_connection()
368
419
  except Exception as e:
369
420
  logger.error(f"Error during async document insertion: {e}")
370
- raise
421
+ # Try falling back to sync insertion as a last resort
422
+ try:
423
+ logger.warning("Async insertion failed, attempting sync fallback")
424
+ self.insert(content_hash, documents, filters)
425
+ logger.info("Sync fallback successful")
426
+ except Exception as sync_e:
427
+ logger.error(f"Sync fallback also failed: {sync_e}")
428
+ raise e from sync_e
371
429
 
372
430
  def upsert_available(self) -> bool:
373
431
  """Check if upsert is available in LanceDB."""
@@ -638,26 +696,25 @@ class LanceDb(VectorDb):
638
696
  return await self.async_table.count_rows()
639
697
  return 0
640
698
 
641
- def _async_get_count_sync(self) -> int:
642
- """Helper method to run async_get_count in a new thread with its own event loop"""
643
- import asyncio
644
-
645
- return asyncio.run(self.async_get_count())
646
-
647
699
  def get_count(self) -> int:
648
700
  # If we have data in the async table but sync table isn't available, try to get count from async table
649
701
  if self.async_table is not None:
650
702
  try:
651
703
  import asyncio
652
704
 
653
- # Check if we're already in an async context
705
+ # Check if we're already in an event loop
654
706
  try:
655
- return self._async_get_count_sync()
707
+ asyncio.get_running_loop()
708
+ # We're in an async context, can't use asyncio.run
709
+ log_debug("Already in async context, falling back to sync table for count")
656
710
  except RuntimeError:
657
711
  # No event loop running, safe to use asyncio.run
658
- return asyncio.run(self.async_get_count())
659
- except Exception:
660
- pass
712
+ try:
713
+ return asyncio.run(self.async_get_count())
714
+ except Exception as e:
715
+ log_debug(f"Failed to get async count: {e}")
716
+ except Exception as e:
717
+ log_debug(f"Error in async count logic: {e}")
661
718
 
662
719
  if self.exists() and self.table:
663
720
  return self.table.count_rows()
agno/workflow/step.py CHANGED
@@ -7,7 +7,7 @@ from uuid import uuid4
7
7
  from pydantic import BaseModel
8
8
 
9
9
  from agno.agent import Agent
10
- from agno.media import Audio, AudioArtifact, Image, ImageArtifact, Video, VideoArtifact
10
+ from agno.media import Audio, Image, Video
11
11
  from agno.models.metrics import Metrics
12
12
  from agno.run.agent import RunOutput
13
13
  from agno.run.team import TeamRunOutput
@@ -931,20 +931,20 @@ class Step:
931
931
  # Convert any other type to string
932
932
  return RunOutput(content=str(result))
933
933
 
934
- def _convert_audio_artifacts_to_audio(self, audio_artifacts: List[AudioArtifact]) -> List[Audio]:
934
+ def _convert_audio_artifacts_to_audio(self, audio_artifacts: List[Audio]) -> List[Audio]:
935
935
  """Convert AudioArtifact objects to Audio objects"""
936
936
  audios = []
937
937
  for audio_artifact in audio_artifacts:
938
938
  if audio_artifact.url:
939
939
  audios.append(Audio(url=audio_artifact.url))
940
- elif audio_artifact.base64_audio: # use base64_audio instead of content
941
- audios.append(Audio(content=audio_artifact.base64_audio))
940
+ elif audio_artifact.content:
941
+ audios.append(Audio(content=audio_artifact.content))
942
942
  else:
943
- logger.warning(f"Skipping AudioArtifact with no URL or base64_audio: {audio_artifact}")
943
+ logger.warning(f"Skipping AudioArtifact with no URL or content: {audio_artifact}")
944
944
  continue
945
945
  return audios
946
946
 
947
- def _convert_image_artifacts_to_images(self, image_artifacts: List[ImageArtifact]) -> List[Image]:
947
+ def _convert_image_artifacts_to_images(self, image_artifacts: List[Image]) -> List[Image]:
948
948
  """
949
949
  Convert ImageArtifact objects to Image objects with proper content handling.
950
950
 
@@ -996,7 +996,7 @@ class Step:
996
996
 
997
997
  return images
998
998
 
999
- def _convert_video_artifacts_to_videos(self, video_artifacts: List[VideoArtifact]) -> List[Video]:
999
+ def _convert_video_artifacts_to_videos(self, video_artifacts: List[Video]) -> List[Video]:
1000
1000
  """
1001
1001
  Convert VideoArtifact objects to Video objects with proper content handling.
1002
1002
 
agno/workflow/types.py CHANGED
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional, Union
5
5
  from fastapi import WebSocket
6
6
  from pydantic import BaseModel
7
7
 
8
- from agno.media import AudioArtifact, File, ImageArtifact, VideoArtifact
8
+ from agno.media import Audio, File, Image, Video
9
9
  from agno.models.metrics import Metrics
10
10
  from agno.utils.log import log_warning
11
11
 
@@ -19,9 +19,9 @@ class WorkflowExecutionInput:
19
19
  additional_data: Optional[Dict[str, Any]] = None
20
20
 
21
21
  # Media inputs
22
- images: Optional[List[ImageArtifact]] = None
23
- videos: Optional[List[VideoArtifact]] = None
24
- audio: Optional[List[AudioArtifact]] = None
22
+ images: Optional[List[Image]] = None
23
+ videos: Optional[List[Video]] = None
24
+ audio: Optional[List[Audio]] = None
25
25
  files: Optional[List[File]] = None
26
26
 
27
27
  def get_input_as_string(self) -> Optional[str]:
@@ -72,9 +72,9 @@ class StepInput:
72
72
  additional_data: Optional[Dict[str, Any]] = None
73
73
 
74
74
  # Media inputs
75
- images: Optional[List[ImageArtifact]] = None
76
- videos: Optional[List[VideoArtifact]] = None
77
- audio: Optional[List[AudioArtifact]] = None
75
+ images: Optional[List[Image]] = None
76
+ videos: Optional[List[Video]] = None
77
+ audio: Optional[List[Audio]] = None
78
78
  files: Optional[List[File]] = None
79
79
 
80
80
  def get_input_as_string(self) -> Optional[str]:
@@ -225,9 +225,9 @@ class StepOutput:
225
225
  step_run_id: Optional[str] = None
226
226
 
227
227
  # Media outputs
228
- images: Optional[List[ImageArtifact]] = None
229
- videos: Optional[List[VideoArtifact]] = None
230
- audio: Optional[List[AudioArtifact]] = None
228
+ images: Optional[List[Image]] = None
229
+ videos: Optional[List[Video]] = None
230
+ audio: Optional[List[Audio]] = None
231
231
  files: Optional[List[File]] = None
232
232
 
233
233
  # Metrics for this step execution
@@ -282,15 +282,15 @@ class StepOutput:
282
282
  # Reconstruct media artifacts
283
283
  images = data.get("images")
284
284
  if images:
285
- images = [ImageArtifact.model_validate(img) for img in images]
285
+ images = [Image.model_validate(img) for img in images]
286
286
 
287
287
  videos = data.get("videos")
288
288
  if videos:
289
- videos = [VideoArtifact.model_validate(vid) for vid in videos]
289
+ videos = [Video.model_validate(vid) for vid in videos]
290
290
 
291
291
  audio = data.get("audio")
292
292
  if audio:
293
- audio = [AudioArtifact.model_validate(aud) for aud in audio]
293
+ audio = [Audio.model_validate(aud) for aud in audio]
294
294
 
295
295
  files = data.get("files")
296
296
  if files:
agno/workflow/workflow.py CHANGED
@@ -26,7 +26,7 @@ from pydantic import BaseModel
26
26
  from agno.agent.agent import Agent
27
27
  from agno.db.base import BaseDb, SessionType
28
28
  from agno.exceptions import RunCancelledException
29
- from agno.media import Audio, AudioArtifact, File, Image, ImageArtifact, Video, VideoArtifact
29
+ from agno.media import Audio, File, Image, Video
30
30
  from agno.models.message import Message
31
31
  from agno.models.metrics import Metrics
32
32
  from agno.run.agent import RunEvent
@@ -745,9 +745,9 @@ class Workflow:
745
745
  self,
746
746
  execution_input: WorkflowExecutionInput,
747
747
  previous_step_outputs: Optional[Dict[str, StepOutput]] = None,
748
- shared_images: Optional[List[ImageArtifact]] = None,
749
- shared_videos: Optional[List[VideoArtifact]] = None,
750
- shared_audio: Optional[List[AudioArtifact]] = None,
748
+ shared_images: Optional[List[Image]] = None,
749
+ shared_videos: Optional[List[Video]] = None,
750
+ shared_audio: Optional[List[Audio]] = None,
751
751
  shared_files: Optional[List[File]] = None,
752
752
  ) -> StepInput:
753
753
  """Helper method to create StepInput with enhanced data flow support"""
@@ -890,12 +890,12 @@ class Workflow:
890
890
  collected_step_outputs: List[Union[StepOutput, List[StepOutput]]] = []
891
891
  previous_step_outputs: Dict[str, StepOutput] = {}
892
892
 
893
- shared_images: List[ImageArtifact] = execution_input.images or []
894
- output_images: List[ImageArtifact] = (execution_input.images or []).copy() # Start with input images
895
- shared_videos: List[VideoArtifact] = execution_input.videos or []
896
- output_videos: List[VideoArtifact] = (execution_input.videos or []).copy() # Start with input videos
897
- shared_audio: List[AudioArtifact] = execution_input.audio or []
898
- output_audio: List[AudioArtifact] = (execution_input.audio or []).copy() # Start with input audio
893
+ shared_images: List[Image] = execution_input.images or []
894
+ output_images: List[Image] = (execution_input.images or []).copy() # Start with input images
895
+ shared_videos: List[Video] = execution_input.videos or []
896
+ output_videos: List[Video] = (execution_input.videos or []).copy() # Start with input videos
897
+ shared_audio: List[Audio] = execution_input.audio or []
898
+ output_audio: List[Audio] = (execution_input.audio or []).copy() # Start with input audio
899
899
  shared_files: List[File] = execution_input.files or []
900
900
  output_files: List[File] = (execution_input.files or []).copy() # Start with input files
901
901
 
@@ -1050,12 +1050,12 @@ class Workflow:
1050
1050
  collected_step_outputs: List[Union[StepOutput, List[StepOutput]]] = []
1051
1051
  previous_step_outputs: Dict[str, StepOutput] = {}
1052
1052
 
1053
- shared_images: List[ImageArtifact] = execution_input.images or []
1054
- output_images: List[ImageArtifact] = (execution_input.images or []).copy() # Start with input images
1055
- shared_videos: List[VideoArtifact] = execution_input.videos or []
1056
- output_videos: List[VideoArtifact] = (execution_input.videos or []).copy() # Start with input videos
1057
- shared_audio: List[AudioArtifact] = execution_input.audio or []
1058
- output_audio: List[AudioArtifact] = (execution_input.audio or []).copy() # Start with input audio
1053
+ shared_images: List[Image] = execution_input.images or []
1054
+ output_images: List[Image] = (execution_input.images or []).copy() # Start with input images
1055
+ shared_videos: List[Video] = execution_input.videos or []
1056
+ output_videos: List[Video] = (execution_input.videos or []).copy() # Start with input videos
1057
+ shared_audio: List[Audio] = execution_input.audio or []
1058
+ output_audio: List[Audio] = (execution_input.audio or []).copy() # Start with input audio
1059
1059
  shared_files: List[File] = execution_input.files or []
1060
1060
  output_files: List[File] = (execution_input.files or []).copy() # Start with input files
1061
1061
 
@@ -1326,12 +1326,12 @@ class Workflow:
1326
1326
  collected_step_outputs: List[Union[StepOutput, List[StepOutput]]] = []
1327
1327
  previous_step_outputs: Dict[str, StepOutput] = {}
1328
1328
 
1329
- shared_images: List[ImageArtifact] = execution_input.images or []
1330
- output_images: List[ImageArtifact] = (execution_input.images or []).copy() # Start with input images
1331
- shared_videos: List[VideoArtifact] = execution_input.videos or []
1332
- output_videos: List[VideoArtifact] = (execution_input.videos or []).copy() # Start with input videos
1333
- shared_audio: List[AudioArtifact] = execution_input.audio or []
1334
- output_audio: List[AudioArtifact] = (execution_input.audio or []).copy() # Start with input audio
1329
+ shared_images: List[Image] = execution_input.images or []
1330
+ output_images: List[Image] = (execution_input.images or []).copy() # Start with input images
1331
+ shared_videos: List[Video] = execution_input.videos or []
1332
+ output_videos: List[Video] = (execution_input.videos or []).copy() # Start with input videos
1333
+ shared_audio: List[Audio] = execution_input.audio or []
1334
+ output_audio: List[Audio] = (execution_input.audio or []).copy() # Start with input audio
1335
1335
  shared_files: List[File] = execution_input.files or []
1336
1336
  output_files: List[File] = (execution_input.files or []).copy() # Start with input files
1337
1337
 
@@ -1484,12 +1484,12 @@ class Workflow:
1484
1484
  collected_step_outputs: List[Union[StepOutput, List[StepOutput]]] = []
1485
1485
  previous_step_outputs: Dict[str, StepOutput] = {}
1486
1486
 
1487
- shared_images: List[ImageArtifact] = execution_input.images or []
1488
- output_images: List[ImageArtifact] = (execution_input.images or []).copy() # Start with input images
1489
- shared_videos: List[VideoArtifact] = execution_input.videos or []
1490
- output_videos: List[VideoArtifact] = (execution_input.videos or []).copy() # Start with input videos
1491
- shared_audio: List[AudioArtifact] = execution_input.audio or []
1492
- output_audio: List[AudioArtifact] = (execution_input.audio or []).copy() # Start with input audio
1487
+ shared_images: List[Image] = execution_input.images or []
1488
+ output_images: List[Image] = (execution_input.images or []).copy() # Start with input images
1489
+ shared_videos: List[Video] = execution_input.videos or []
1490
+ output_videos: List[Video] = (execution_input.videos or []).copy() # Start with input videos
1491
+ shared_audio: List[Audio] = execution_input.audio or []
1492
+ output_audio: List[Audio] = (execution_input.audio or []).copy() # Start with input audio
1493
1493
  shared_files: List[File] = execution_input.files or []
1494
1494
  output_files: List[File] = (execution_input.files or []).copy() # Start with input files
1495
1495
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 2.0.0rc2
3
+ Version: 2.0.1
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
@@ -353,3 +353,142 @@ Requires-Dist: agno[agui]; extra == "tests"
353
353
  Requires-Dist: twine; extra == "tests"
354
354
  Requires-Dist: build; extra == "tests"
355
355
  Dynamic: license-file
356
+
357
+ <div align="center" id="top">
358
+ <a href="https://docs.agno.com">
359
+ <picture>
360
+ <source media="(prefers-color-scheme: dark)" srcset="https://agno-public.s3.us-east-1.amazonaws.com/assets/logo-dark.svg">
361
+ <source media="(prefers-color-scheme: light)" srcset="https://agno-public.s3.us-east-1.amazonaws.com/assets/logo-light.svg">
362
+ <img src="https://agno-public.s3.us-east-1.amazonaws.com/assets/logo-light.svg" alt="Agno">
363
+ </picture>
364
+ </a>
365
+ </div>
366
+ <div align="center">
367
+ <a href="https://docs.agno.com">📚 Documentation</a> &nbsp;|&nbsp;
368
+ <a href="https://docs.agno.com/examples/introduction">💡 Examples</a> &nbsp;|&nbsp;
369
+ <a href="https://github.com/agno-agi/agno/stargazers">🌟 Star Us</a>
370
+ </div>
371
+
372
+ ## What is Agno?
373
+
374
+ [Agno](https://docs.agno.com) is a high-performance runtime for multi-agent systems. Use it to build, run and manage secure multi-agent systems in your cloud.
375
+
376
+ Agno gives you the fastest framework for building agents with session management, memory, knowledge, human in the loop and MCP support. You can put agents together as an autonomous multi-agent team, or build step-based agentic workflows for full control over complex multi-step processes.
377
+
378
+ In 10 lines of code, we can build an Agent that will fetch the top stories from HackerNews and summarize them.
379
+
380
+ ```python hackernews_agent.py
381
+ from agno.agent import Agent
382
+ from agno.models.anthropic import Claude
383
+ from agno.tools.hackernews import HackerNewsTools
384
+
385
+ agent = Agent(
386
+ model=Claude(id="claude-sonnet-4-0"),
387
+ tools=[HackerNewsTools()],
388
+ markdown=True,
389
+ )
390
+ agent.print_response("Summarize the top 5 stories on hackernews", stream=True)
391
+ ```
392
+
393
+ But the real advantage of Agno is its [AgentOS](https://docs.agno.com/agent-os/introduction) runtime:
394
+
395
+ 1. You get a pre-built FastAPI app for running your agentic system, meaning you start building your product on day one. This is a remarkable advantage over other solutions or rolling your own.
396
+ 2. You also get a control plane which connects directly to your AgentOS for testing, monitoring and managing your system. This gives you unmatched visibility and control over your system.
397
+ 3. Your AgentOS runs in your cloud and you get complete data privacy because no data ever leaves your system. This is incredible for security conscious enterprises that can't send traces to external services.
398
+
399
+ For organizations building agents, Agno provides the complete solution. You get the fastest framework for building agents (speed of development and execution), a pre-built FastAPI app that lets you build your product on day one, and a control plane for managing your system.
400
+
401
+ We bring a novel architecture that no other framework provides, your AgentOS runs securely in your cloud, and the control plane connects directly to it from your browser. You don't need to send data to external services or pay retention costs, you get complete privacy and control.
402
+
403
+ ## Getting started
404
+
405
+ If you're new to Agno, follow our [quickstart](https://docs.agno.com/introduction/quickstart) to build your first Agent and run it using the AgentOS.
406
+
407
+ After that, checkout the [examples gallery](https://docs.agno.com/examples/introduction) and build real-world applications with Agno.
408
+
409
+ ## Documentation, Community & More examples
410
+
411
+ - Docs: <a href="https://docs.agno.com" target="_blank" rel="noopener noreferrer">docs.agno.com</a>
412
+ - Cookbook: <a href="https://github.com/agno-agi/agno/tree/main/cookbook" target="_blank" rel="noopener noreferrer">Cookbook</a>
413
+ - Community forum: <a href="https://community.agno.com/" target="_blank" rel="noopener noreferrer">community.agno.com</a>
414
+ - Discord: <a href="https://discord.gg/4MtYHHrgA8" target="_blank" rel="noopener noreferrer">discord</a>
415
+
416
+ ## Setup your coding agent to use Agno
417
+
418
+ For LLMs and AI assistants to understand and navigate Agno's documentation, we provide an [llms.txt](https://docs.agno.com/llms.txt) or [llms-full.txt](https://docs.agno.com/llms-full.txt) file.
419
+
420
+ This file is built for AI systems to efficiently parse and reference our documentation.
421
+
422
+ ### IDE Integration
423
+
424
+ When building Agno agents, using Agno documentation as a source in your IDE is a great way to speed up your development. Here's how to integrate with Cursor:
425
+
426
+ 1. In Cursor, go to the "Cursor Settings" menu.
427
+ 2. Find the "Indexing & Docs" section.
428
+ 3. Add `https://docs.agno.com/llms-full.txt` to the list of documentation URLs.
429
+ 4. Save the changes.
430
+
431
+ Now, Cursor will have access to the Agno documentation. You can do the same with other IDEs like VSCode, Windsurf etc.
432
+
433
+ ## Performance
434
+
435
+ At Agno, we're obsessed with performance. Why? because even simple AI workflows can spawn thousands of Agents. Scale that to a modest number of users and performance becomes a bottleneck. Agno is designed for building highly performant agentic systems:
436
+
437
+ - Agent instantiation: ~3μs on average
438
+ - Memory footprint: ~6.5Kib on average
439
+
440
+ > Tested on an Apple M4 Mackbook Pro.
441
+
442
+ While an Agent's run-time is bottlenecked by inference, we must do everything possible to minimize execution time, reduce memory usage, and parallelize tool calls. These numbers may seem trivial at first, but our experience shows that they add up even at a reasonably small scale.
443
+
444
+ ### Instantiation time
445
+
446
+ Let's measure the time it takes for an Agent with 1 tool to start up. We'll run the evaluation 1000 times to get a baseline measurement.
447
+
448
+ You should run the evaluation yourself on your own machine, please, do not take these results at face value.
449
+
450
+ ```shell
451
+ # Setup virtual environment
452
+ ./scripts/perf_setup.sh
453
+ source .venvs/perfenv/bin/activate
454
+ # OR Install dependencies manually
455
+ # pip install openai agno langgraph langchain_openai
456
+
457
+ # Agno
458
+ python evals/performance/instantiation_with_tool.py
459
+
460
+ # LangGraph
461
+ python evals/performance/other/langgraph_instantiation.py
462
+ ```
463
+
464
+ > The following evaluation is run on an Apple M4 Mackbook Pro. It also runs as a Github action on this repo.
465
+
466
+ LangGraph is on the right, **let's start it first and give it a head start**.
467
+
468
+ Agno is on the left, notice how it finishes before LangGraph gets 1/2 way through the runtime measurement, and hasn't even started the memory measurement. That's how fast Agno is.
469
+
470
+ https://github.com/user-attachments/assets/ba466d45-75dd-45ac-917b-0a56c5742e23
471
+
472
+ ### Memory usage
473
+
474
+ To measure memory usage, we use the `tracemalloc` library. We first calculate a baseline memory usage by running an empty function, then run the Agent 1000x times and calculate the difference. This gives a (reasonably) isolated measurement of the memory usage of the Agent.
475
+
476
+ We recommend running the evaluation yourself on your own machine, and digging into the code to see how it works. If we've made a mistake, please let us know.
477
+
478
+ ### Conclusion
479
+
480
+ Agno agents are designed for performance and while we do share some benchmarks against other frameworks, we should be mindful that accuracy and reliability are more important than speed.
481
+
482
+ Given that each framework is different and we won't be able to tune their performance like we do with Agno, for future benchmarks we'll only be comparing against ourselves.
483
+
484
+ ## Contributions
485
+
486
+ We welcome contributions, read our [contributing guide](https://github.com/agno-agi/agno/blob/v2.0/CONTRIBUTING.md) to get started.
487
+
488
+ ## Telemetry
489
+
490
+ Agno logs which model an agent used so we can prioritize updates to the most popular providers. You can disable this by setting `AGNO_TELEMETRY=false` in your environment.
491
+
492
+ <p align="left">
493
+ <a href="#top">⬆️ Back to Top</a>
494
+ </p>