agno 2.3.20__py3-none-any.whl → 2.3.22__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 +26 -1
- agno/agent/remote.py +233 -72
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/v2_3_0.py +92 -53
- agno/db/postgres/async_postgres.py +162 -40
- agno/db/postgres/postgres.py +181 -31
- agno/db/postgres/utils.py +6 -2
- agno/eval/agent_as_judge.py +24 -14
- agno/knowledge/chunking/document.py +3 -2
- agno/knowledge/chunking/markdown.py +8 -3
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/embedder/mistral.py +1 -1
- agno/models/openai/chat.py +1 -1
- agno/models/openai/responses.py +14 -7
- agno/os/middleware/jwt.py +66 -27
- agno/os/routers/agents/router.py +2 -2
- agno/os/routers/evals/evals.py +0 -9
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/knowledge.py +3 -3
- agno/os/routers/teams/router.py +2 -2
- agno/os/routers/workflows/router.py +2 -2
- agno/reasoning/deepseek.py +11 -1
- agno/reasoning/gemini.py +6 -2
- agno/reasoning/groq.py +8 -3
- agno/reasoning/openai.py +2 -0
- agno/remote/base.py +105 -8
- agno/run/agent.py +19 -19
- agno/run/team.py +19 -19
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +370 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/team/remote.py +219 -59
- agno/team/team.py +22 -2
- agno/tools/mcp/mcp.py +299 -17
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/utils/mcp.py +49 -8
- agno/utils/string.py +43 -1
- agno/workflow/condition.py +4 -2
- agno/workflow/loop.py +20 -1
- agno/workflow/remote.py +172 -32
- agno/workflow/router.py +4 -1
- agno/workflow/steps.py +4 -0
- {agno-2.3.20.dist-info → agno-2.3.22.dist-info}/METADATA +59 -130
- {agno-2.3.20.dist-info → agno-2.3.22.dist-info}/RECORD +58 -44
- {agno-2.3.20.dist-info → agno-2.3.22.dist-info}/WHEEL +0 -0
- {agno-2.3.20.dist-info → agno-2.3.22.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.20.dist-info → agno-2.3.22.dist-info}/top_level.txt +0 -0
agno/workflow/remote.py
CHANGED
|
@@ -24,21 +24,28 @@ class RemoteWorkflow(BaseRemote):
|
|
|
24
24
|
base_url: str,
|
|
25
25
|
workflow_id: str,
|
|
26
26
|
timeout: float = 300.0,
|
|
27
|
+
protocol: Literal["agentos", "a2a"] = "agentos",
|
|
28
|
+
a2a_protocol: Literal["json-rpc", "rest"] = "rest",
|
|
27
29
|
config_ttl: float = 300.0,
|
|
28
30
|
):
|
|
29
|
-
"""Initialize
|
|
31
|
+
"""Initialize RemoteWorkflow for remote execution.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Supports two protocols:
|
|
34
|
+
- "agentos": Agno's proprietary AgentOS REST API (default)
|
|
35
|
+
- "a2a": A2A (Agent-to-Agent) protocol for cross-framework communication
|
|
32
36
|
|
|
33
37
|
Args:
|
|
34
|
-
base_url: Base URL for remote
|
|
35
|
-
workflow_id: ID of remote workflow
|
|
38
|
+
base_url: Base URL for remote instance (e.g., "http://localhost:7777")
|
|
39
|
+
workflow_id: ID of remote workflow on the remote server
|
|
36
40
|
timeout: Request timeout in seconds (default: 300)
|
|
41
|
+
protocol: Communication protocol - "agentos" (default) or "a2a"
|
|
42
|
+
a2a_protocol: For A2A protocol only - Whether to use JSON-RPC or REST protocol.
|
|
37
43
|
config_ttl: Time-to-live for cached config in seconds (default: 300)
|
|
38
44
|
"""
|
|
39
|
-
super().__init__(base_url, timeout, config_ttl)
|
|
45
|
+
super().__init__(base_url, timeout, protocol, a2a_protocol, config_ttl)
|
|
40
46
|
self.workflow_id = workflow_id
|
|
41
47
|
self._cached_workflow_config = None
|
|
48
|
+
self._config_ttl = config_ttl
|
|
42
49
|
|
|
43
50
|
@property
|
|
44
51
|
def id(self) -> str:
|
|
@@ -46,13 +53,38 @@ class RemoteWorkflow(BaseRemote):
|
|
|
46
53
|
|
|
47
54
|
async def get_workflow_config(self) -> "WorkflowResponse":
|
|
48
55
|
"""Get the workflow config from remote (always fetches fresh)."""
|
|
49
|
-
|
|
56
|
+
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
57
|
+
|
|
58
|
+
if self.protocol == "a2a":
|
|
59
|
+
from agno.client.a2a.schemas import AgentCard
|
|
60
|
+
|
|
61
|
+
agent_card: Optional[AgentCard] = await self.a2a_client.aget_agent_card() # type: ignore
|
|
62
|
+
|
|
63
|
+
return WorkflowResponse(
|
|
64
|
+
id=self.workflow_id,
|
|
65
|
+
name=agent_card.name if agent_card else self.workflow_id,
|
|
66
|
+
description=agent_card.description if agent_card else f"A2A workflow: {self.workflow_id}",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# AgentOS protocol: fetch fresh config from remote
|
|
70
|
+
return await self.agentos_client.aget_workflow(self.workflow_id) # type: ignore
|
|
50
71
|
|
|
51
72
|
@property
|
|
52
73
|
def _workflow_config(self) -> "WorkflowResponse":
|
|
53
74
|
"""Get the workflow config from remote, cached with TTL."""
|
|
54
75
|
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
55
76
|
|
|
77
|
+
if self.protocol == "a2a":
|
|
78
|
+
from agno.client.a2a.schemas import AgentCard
|
|
79
|
+
|
|
80
|
+
agent_card: Optional[AgentCard] = self.a2a_client.get_agent_card() # type: ignore
|
|
81
|
+
|
|
82
|
+
return WorkflowResponse(
|
|
83
|
+
id=self.workflow_id,
|
|
84
|
+
name=agent_card.name if agent_card else self.workflow_id,
|
|
85
|
+
description=agent_card.description if agent_card else f"A2A workflow: {self.workflow_id}",
|
|
86
|
+
)
|
|
87
|
+
|
|
56
88
|
current_time = time.time()
|
|
57
89
|
|
|
58
90
|
# Check if cache is valid
|
|
@@ -62,15 +94,15 @@ class RemoteWorkflow(BaseRemote):
|
|
|
62
94
|
return config
|
|
63
95
|
|
|
64
96
|
# Fetch fresh config
|
|
65
|
-
config: WorkflowResponse = self.
|
|
97
|
+
config: WorkflowResponse = self.agentos_client.get_workflow(self.workflow_id) # type: ignore
|
|
66
98
|
self._cached_workflow_config = (config, current_time)
|
|
67
99
|
return config
|
|
68
100
|
|
|
69
|
-
def refresh_config(self) -> "WorkflowResponse":
|
|
101
|
+
async def refresh_config(self) -> "WorkflowResponse":
|
|
70
102
|
"""Force refresh the cached workflow config."""
|
|
71
103
|
from agno.os.routers.workflows.schema import WorkflowResponse
|
|
72
104
|
|
|
73
|
-
config: WorkflowResponse = self.
|
|
105
|
+
config: WorkflowResponse = await self.agentos_client.aget_workflow(self.workflow_id) # type: ignore
|
|
74
106
|
self._cached_workflow_config = (config, time.time())
|
|
75
107
|
return config
|
|
76
108
|
|
|
@@ -88,10 +120,15 @@ class RemoteWorkflow(BaseRemote):
|
|
|
88
120
|
|
|
89
121
|
@property
|
|
90
122
|
def db(self) -> Optional[RemoteDb]:
|
|
91
|
-
if
|
|
123
|
+
if (
|
|
124
|
+
self.agentos_client
|
|
125
|
+
and self._config
|
|
126
|
+
and self._workflow_config is not None
|
|
127
|
+
and self._workflow_config.db_id is not None
|
|
128
|
+
):
|
|
92
129
|
return RemoteDb.from_config(
|
|
93
130
|
db_id=self._workflow_config.db_id,
|
|
94
|
-
client=self.
|
|
131
|
+
client=self.agentos_client,
|
|
95
132
|
config=self._config,
|
|
96
133
|
)
|
|
97
134
|
return None
|
|
@@ -165,41 +202,144 @@ class RemoteWorkflow(BaseRemote):
|
|
|
165
202
|
serialized_input = serialize_input(validated_input)
|
|
166
203
|
headers = self._get_auth_headers(auth_token)
|
|
167
204
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return self.
|
|
171
|
-
workflow_id=self.workflow_id,
|
|
205
|
+
# A2A protocol path
|
|
206
|
+
if self.a2a_client:
|
|
207
|
+
return self._arun_a2a( # type: ignore[return-value]
|
|
172
208
|
message=serialized_input,
|
|
173
|
-
|
|
174
|
-
run_id=run_id,
|
|
175
|
-
session_id=session_id,
|
|
209
|
+
stream=stream or False,
|
|
176
210
|
user_id=user_id,
|
|
177
|
-
|
|
211
|
+
context_id=session_id, # Map session_id → context_id for A2A
|
|
178
212
|
images=images,
|
|
179
213
|
videos=videos,
|
|
214
|
+
audio=audio,
|
|
180
215
|
files=files,
|
|
181
|
-
session_state=session_state,
|
|
182
|
-
stream_events=stream_events,
|
|
183
216
|
headers=headers,
|
|
184
|
-
**kwargs,
|
|
185
217
|
)
|
|
218
|
+
|
|
219
|
+
# AgentOS protocol path (default)
|
|
220
|
+
if self.agentos_client:
|
|
221
|
+
if stream:
|
|
222
|
+
# Handle streaming response
|
|
223
|
+
return self.agentos_client.run_workflow_stream(
|
|
224
|
+
workflow_id=self.workflow_id,
|
|
225
|
+
message=serialized_input,
|
|
226
|
+
additional_data=additional_data,
|
|
227
|
+
run_id=run_id,
|
|
228
|
+
session_id=session_id,
|
|
229
|
+
user_id=user_id,
|
|
230
|
+
audio=audio,
|
|
231
|
+
images=images,
|
|
232
|
+
videos=videos,
|
|
233
|
+
files=files,
|
|
234
|
+
session_state=session_state,
|
|
235
|
+
stream_events=stream_events,
|
|
236
|
+
headers=headers,
|
|
237
|
+
**kwargs,
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
return self.agentos_client.run_workflow( # type: ignore
|
|
241
|
+
workflow_id=self.workflow_id,
|
|
242
|
+
message=serialized_input,
|
|
243
|
+
additional_data=additional_data,
|
|
244
|
+
run_id=run_id,
|
|
245
|
+
session_id=session_id,
|
|
246
|
+
user_id=user_id,
|
|
247
|
+
audio=audio,
|
|
248
|
+
images=images,
|
|
249
|
+
videos=videos,
|
|
250
|
+
files=files,
|
|
251
|
+
session_state=session_state,
|
|
252
|
+
headers=headers,
|
|
253
|
+
**kwargs,
|
|
254
|
+
)
|
|
186
255
|
else:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
256
|
+
raise ValueError("No client available")
|
|
257
|
+
|
|
258
|
+
def _arun_a2a(
|
|
259
|
+
self,
|
|
260
|
+
message: str,
|
|
261
|
+
stream: bool,
|
|
262
|
+
user_id: Optional[str],
|
|
263
|
+
context_id: Optional[str],
|
|
264
|
+
images: Optional[List[Image]],
|
|
265
|
+
videos: Optional[List[Video]],
|
|
266
|
+
audio: Optional[List[Audio]],
|
|
267
|
+
files: Optional[List[File]],
|
|
268
|
+
headers: Optional[Dict[str, str]],
|
|
269
|
+
) -> Union[WorkflowRunOutput, AsyncIterator[WorkflowRunOutputEvent]]:
|
|
270
|
+
"""Execute via A2A protocol.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
message: Serialized message string
|
|
274
|
+
stream: Whether to stream the response
|
|
275
|
+
user_id: User identifier
|
|
276
|
+
context_id: Session/context ID (maps to session_id)
|
|
277
|
+
images: Images to include
|
|
278
|
+
videos: Videos to include
|
|
279
|
+
audio: Audio files to include
|
|
280
|
+
files: Files to include
|
|
281
|
+
headers: HTTP headers to include in the request (optional)
|
|
282
|
+
Returns:
|
|
283
|
+
WorkflowRunOutput for non-streaming, AsyncIterator[WorkflowRunOutputEvent] for streaming
|
|
284
|
+
"""
|
|
285
|
+
if not self.a2a_client:
|
|
286
|
+
raise ValueError("A2A client not available")
|
|
287
|
+
from agno.client.a2a.utils import map_stream_events_to_workflow_run_events
|
|
288
|
+
|
|
289
|
+
if stream:
|
|
290
|
+
# Return async generator for streaming
|
|
291
|
+
event_stream = self.a2a_client.stream_message(
|
|
292
|
+
message=message,
|
|
293
|
+
context_id=context_id,
|
|
193
294
|
user_id=user_id,
|
|
194
|
-
|
|
295
|
+
images=list(images) if images else None,
|
|
296
|
+
audio=list(audio) if audio else None,
|
|
297
|
+
videos=list(videos) if videos else None,
|
|
298
|
+
files=list(files) if files else None,
|
|
299
|
+
headers=headers,
|
|
300
|
+
)
|
|
301
|
+
return map_stream_events_to_workflow_run_events(event_stream, workflow_id=self.workflow_id) # type: ignore
|
|
302
|
+
else:
|
|
303
|
+
# Return coroutine for non-streaming
|
|
304
|
+
return self._arun_a2a_send( # type: ignore[return-value]
|
|
305
|
+
message=message,
|
|
306
|
+
user_id=user_id,
|
|
307
|
+
context_id=context_id,
|
|
195
308
|
images=images,
|
|
309
|
+
audio=audio,
|
|
196
310
|
videos=videos,
|
|
197
311
|
files=files,
|
|
198
|
-
session_state=session_state,
|
|
199
312
|
headers=headers,
|
|
200
|
-
**kwargs,
|
|
201
313
|
)
|
|
202
314
|
|
|
315
|
+
async def _arun_a2a_send(
|
|
316
|
+
self,
|
|
317
|
+
message: str,
|
|
318
|
+
user_id: Optional[str],
|
|
319
|
+
context_id: Optional[str],
|
|
320
|
+
images: Optional[List[Image]],
|
|
321
|
+
videos: Optional[List[Video]],
|
|
322
|
+
audio: Optional[List[Audio]],
|
|
323
|
+
files: Optional[List[File]],
|
|
324
|
+
headers: Optional[Dict[str, str]],
|
|
325
|
+
) -> WorkflowRunOutput:
|
|
326
|
+
"""Send a non-streaming A2A message and convert response to WorkflowRunOutput."""
|
|
327
|
+
if not self.a2a_client:
|
|
328
|
+
raise ValueError("A2A client not available")
|
|
329
|
+
from agno.client.a2a.utils import map_task_result_to_workflow_run_output
|
|
330
|
+
|
|
331
|
+
task_result = await self.a2a_client.send_message(
|
|
332
|
+
message=message,
|
|
333
|
+
context_id=context_id,
|
|
334
|
+
user_id=user_id,
|
|
335
|
+
images=list(images) if images else None,
|
|
336
|
+
audio=list(audio) if audio else None,
|
|
337
|
+
videos=list(videos) if videos else None,
|
|
338
|
+
files=list(files) if files else None,
|
|
339
|
+
headers=headers,
|
|
340
|
+
)
|
|
341
|
+
return map_task_result_to_workflow_run_output(task_result, workflow_id=self.workflow_id, user_id=user_id)
|
|
342
|
+
|
|
203
343
|
async def cancel_run(self, run_id: str, auth_token: Optional[str] = None) -> bool:
|
|
204
344
|
"""Cancel a running workflow execution.
|
|
205
345
|
|
|
@@ -212,7 +352,7 @@ class RemoteWorkflow(BaseRemote):
|
|
|
212
352
|
"""
|
|
213
353
|
headers = self._get_auth_headers(auth_token)
|
|
214
354
|
try:
|
|
215
|
-
await self.
|
|
355
|
+
await self.get_os_client().cancel_workflow_run(
|
|
216
356
|
workflow_id=self.workflow_id,
|
|
217
357
|
run_id=run_id,
|
|
218
358
|
headers=headers,
|
agno/workflow/router.py
CHANGED
|
@@ -267,6 +267,7 @@ class Router:
|
|
|
267
267
|
step_type=StepType.ROUTER,
|
|
268
268
|
content=f"Router {self.name} completed with {len(all_results)} results",
|
|
269
269
|
success=all(result.success for result in all_results) if all_results else True,
|
|
270
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
270
271
|
steps=all_results,
|
|
271
272
|
)
|
|
272
273
|
|
|
@@ -438,6 +439,7 @@ class Router:
|
|
|
438
439
|
step_type=StepType.ROUTER,
|
|
439
440
|
content=f"Router {self.name} completed with {len(all_results)} results",
|
|
440
441
|
success=all(result.success for result in all_results) if all_results else True,
|
|
442
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
441
443
|
steps=all_results,
|
|
442
444
|
)
|
|
443
445
|
|
|
@@ -544,6 +546,7 @@ class Router:
|
|
|
544
546
|
step_type=StepType.ROUTER,
|
|
545
547
|
content=f"Router {self.name} completed with {len(all_results)} results",
|
|
546
548
|
success=all(result.success for result in all_results) if all_results else True,
|
|
549
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
547
550
|
steps=all_results,
|
|
548
551
|
)
|
|
549
552
|
|
|
@@ -718,6 +721,6 @@ class Router:
|
|
|
718
721
|
content=f"Router {self.name} completed with {len(all_results)} results",
|
|
719
722
|
success=all(result.success for result in all_results) if all_results else True,
|
|
720
723
|
error=None,
|
|
721
|
-
stop=False,
|
|
724
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
722
725
|
steps=all_results,
|
|
723
726
|
)
|
agno/workflow/steps.py
CHANGED
|
@@ -194,6 +194,7 @@ class Steps:
|
|
|
194
194
|
step_type=StepType.STEPS,
|
|
195
195
|
content=f"Steps {self.name} completed with {len(all_results)} results",
|
|
196
196
|
success=all(result.success for result in all_results) if all_results else True,
|
|
197
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
197
198
|
steps=all_results,
|
|
198
199
|
)
|
|
199
200
|
|
|
@@ -351,6 +352,7 @@ class Steps:
|
|
|
351
352
|
step_type=StepType.STEPS,
|
|
352
353
|
content=f"Steps {self.name} completed with {len(all_results)} results",
|
|
353
354
|
success=all(result.success for result in all_results) if all_results else True,
|
|
355
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
354
356
|
steps=all_results,
|
|
355
357
|
)
|
|
356
358
|
|
|
@@ -443,6 +445,7 @@ class Steps:
|
|
|
443
445
|
step_type=StepType.STEPS,
|
|
444
446
|
content=f"Steps {self.name} completed with {len(all_results)} results",
|
|
445
447
|
success=all(result.success for result in all_results) if all_results else True,
|
|
448
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
446
449
|
steps=all_results,
|
|
447
450
|
)
|
|
448
451
|
|
|
@@ -599,6 +602,7 @@ class Steps:
|
|
|
599
602
|
step_type=StepType.STEPS,
|
|
600
603
|
content=f"Steps {self.name} completed with {len(all_results)} results",
|
|
601
604
|
success=all(result.success for result in all_results) if all_results else True,
|
|
605
|
+
stop=any(result.stop for result in all_results) if all_results else False,
|
|
602
606
|
steps=all_results,
|
|
603
607
|
)
|
|
604
608
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agno
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.22
|
|
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
|
|
@@ -157,7 +157,7 @@ Requires-Dist: mcp>=1.9.2; extra == "mcp"
|
|
|
157
157
|
Provides-Extra: mem0
|
|
158
158
|
Requires-Dist: mem0ai; extra == "mem0"
|
|
159
159
|
Provides-Extra: memori
|
|
160
|
-
Requires-Dist:
|
|
160
|
+
Requires-Dist: memori>=3.0.5; extra == "memori"
|
|
161
161
|
Provides-Extra: newspaper
|
|
162
162
|
Requires-Dist: newspaper4k; extra == "newspaper"
|
|
163
163
|
Requires-Dist: lxml_html_clean; extra == "newspaper"
|
|
@@ -415,7 +415,7 @@ Dynamic: license-file
|
|
|
415
415
|
<div align="center">
|
|
416
416
|
<a href="https://docs.agno.com">Documentation</a>
|
|
417
417
|
<span> • </span>
|
|
418
|
-
<a href="https://
|
|
418
|
+
<a href="https://github.com/agno-agi/agno/tree/main/cookbook">Cookbook</a>
|
|
419
419
|
<span> • </span>
|
|
420
420
|
<a href="https://www.agno.com/?utm_source=github&utm_medium=readme&utm_campaign=agno-github">Website</a>
|
|
421
421
|
<br />
|
|
@@ -423,25 +423,26 @@ Dynamic: license-file
|
|
|
423
423
|
|
|
424
424
|
## What is Agno?
|
|
425
425
|
|
|
426
|
-
Agno is
|
|
426
|
+
Agno is a multi-agent framework, runtime, and control plane. Use it to build private and secure AI products that run in your cloud.
|
|
427
427
|
|
|
428
|
-
|
|
428
|
+
- **Build** agents, teams, and workflows with memory, knowledge, guardrails and 100+ integrations.
|
|
429
|
+
- **Run** in production with a stateless FastAPI runtime. Horizontally scalable.
|
|
430
|
+
- **Manage** with a control plane that connects directly to your runtime — no data leaves your environment.
|
|
429
431
|
|
|
430
|
-
|
|
432
|
+
## Why Agno?
|
|
431
433
|
|
|
432
|
-
- **
|
|
433
|
-
- **
|
|
434
|
-
- **
|
|
434
|
+
- **Your cloud, your data:** Runs entirely in your infrastructure. Nothing leaves your environment.
|
|
435
|
+
- **Ready for production on day one:** Pre-built FastAPI runtime with SSE endpoints, ready to deploy.
|
|
436
|
+
- **Incredibly fast:** 529× faster than LangGraph, 24× lower memory.
|
|
435
437
|
|
|
436
|
-
|
|
438
|
+
## Getting Started
|
|
437
439
|
|
|
438
|
-
|
|
440
|
+
Start with the [getting started guide](https://github.com/agno-agi/agno/tree/main/cookbook/00_getting_started), then:
|
|
439
441
|
|
|
440
|
-
|
|
442
|
+
- Browse the [cookbooks](https://github.com/agno-agi/agno/tree/main/cookbook) for real-world examples
|
|
443
|
+
- Read the [docs](https://docs.agno.com) to learn more
|
|
441
444
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
## Documentation, Community & More Examples
|
|
445
|
+
## Resources
|
|
445
446
|
|
|
446
447
|
- Docs: <a href="https://docs.agno.com" target="_blank" rel="noopener noreferrer">docs.agno.com</a>
|
|
447
448
|
- Cookbook: <a href="https://github.com/agno-agi/agno/tree/main/cookbook" target="_blank" rel="noopener noreferrer">Cookbook</a>
|
|
@@ -497,144 +498,72 @@ When you run the example script shared above, you get a FastAPI app that you can
|
|
|
497
498
|
|
|
498
499
|
https://github.com/user-attachments/assets/feb23db8-15cc-4e88-be7c-01a21a03ebf6
|
|
499
500
|
|
|
500
|
-
##
|
|
501
|
+
## Private by Design
|
|
501
502
|
|
|
502
|
-
|
|
503
|
+
This is the part we care most about.
|
|
503
504
|
|
|
504
|
-
|
|
505
|
-
- A ready-to-use FastAPI app that gets you building AI products on day one.
|
|
506
|
-
- A control plane for testing, monitoring and managing your system.
|
|
505
|
+
AgentOS runs in **your** cloud. The control plane UI connects directly to your runtime from your browser. Your data never touches our servers. No retention costs, no vendor lock-in, no compliance headaches.
|
|
507
506
|
|
|
508
|
-
|
|
507
|
+
This isn't a privacy mode or enterprise add-on. It's how Agno works.
|
|
509
508
|
|
|
510
509
|
## Features
|
|
511
510
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
| | **Data Governance** | Your data lives securely in your Agent database, no external data sharing or vendor lock-in. |
|
|
538
|
-
| | **Access Control** | Role-based access (RBAC) and per-agent permissions to protect sensitive contexts and tools. |
|
|
539
|
-
|
|
540
|
-
Every part of Agno is built for real-world deployment — where developer experience meets production performance.
|
|
541
|
-
|
|
542
|
-
## Setup Your Coding Agent to Use Agno
|
|
543
|
-
|
|
544
|
-
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. This file is built for AI systems to efficiently parse and reference our documentation.
|
|
545
|
-
|
|
546
|
-
### IDE Integration
|
|
547
|
-
|
|
548
|
-
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:
|
|
549
|
-
|
|
550
|
-
1. In Cursor, go to the "Cursor Settings" menu.
|
|
551
|
-
2. Find the "Indexing & Docs" section.
|
|
552
|
-
3. Add `https://docs.agno.com/llms-full.txt` to the list of documentation URLs.
|
|
553
|
-
4. Save the changes.
|
|
554
|
-
|
|
555
|
-
Now, Cursor will have access to the Agno documentation. You can do the same with other IDEs like VSCode, Windsurf etc.
|
|
511
|
+
### Core:
|
|
512
|
+
- Model agnostic — works with OpenAI, Anthropic, Google, local models, whatever
|
|
513
|
+
- Type-safe I/O with `input_schema` and `output_schema`
|
|
514
|
+
- Async-first, built for long-running tasks
|
|
515
|
+
- Natively multimodal (text, images, audio, video, files)
|
|
516
|
+
|
|
517
|
+
### Memory & Knowledge:
|
|
518
|
+
- Persistent storage for session history and state
|
|
519
|
+
- User memory that persists across sessions
|
|
520
|
+
- Agentic RAG with 20+ vector stores, hybrid search, reranking
|
|
521
|
+
- Culture — shared long-term memory across agents
|
|
522
|
+
|
|
523
|
+
### Execution:
|
|
524
|
+
- Human-in-the-loop (confirmations, approvals, overrides)
|
|
525
|
+
- Guardrails for validation and security
|
|
526
|
+
- Pre/post hooks for the agent lifecycle
|
|
527
|
+
- First-class MCP and A2A support
|
|
528
|
+
- 100+ built-in toolkits
|
|
529
|
+
|
|
530
|
+
### Production:
|
|
531
|
+
- Ready-to-use FastAPI runtime
|
|
532
|
+
- Integrated control plane UI
|
|
533
|
+
- Evals for accuracy, performance, latency
|
|
534
|
+
- Durable execution for resumable workflows
|
|
535
|
+
- RBAC and per-agent permissions
|
|
556
536
|
|
|
557
537
|
## Performance
|
|
558
538
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
At Agno, we optimize performance across 3 dimensions:
|
|
562
|
-
|
|
563
|
-
1. **Agent performance:** We optimize static operations (instantiation, memory footprint) and runtime operations (tool calls, memory updates, history management).
|
|
564
|
-
2. **System performance:** The AgentOS API is async by default and has a minimal memory footprint. The system is stateless and horizontally scalable, with a focus on preventing memory leaks. It handles parallel and batch embedding generation during knowledge ingestion, metrics collection in background tasks, and other system-level optimizations.
|
|
565
|
-
3. **Agent reliability and accuracy:** Monitored through evals, which we'll explore later.
|
|
566
|
-
|
|
567
|
-
### Agent Performance
|
|
568
|
-
|
|
569
|
-
Let's measure the time it takes to instantiate an Agent and the memory footprint of an Agent. Here are the numbers (last measured in Oct 2025, on an Apple M4 MacBook Pro):
|
|
570
|
-
|
|
571
|
-
- **Agent instantiation:** ~3μs on average
|
|
572
|
-
- **Memory footprint:** ~6.6Kib on average
|
|
573
|
-
|
|
574
|
-
We'll show below that Agno Agents instantiate **529× faster than Langgraph**, **57× faster than PydanticAI**, and **70× faster than CrewAI**. Agno Agents also use **24× lower memory than Langgraph**, **4× lower than PydanticAI**, and **10× lower than CrewAI**.
|
|
575
|
-
|
|
576
|
-
> [!NOTE]
|
|
577
|
-
> Run time performance is bottlenecked by inference and hard to benchmark accurately, so we focus on minimizing overhead, reducing memory usage, and parallelizing tool calls.
|
|
578
|
-
|
|
579
|
-
### Instantiation Time
|
|
539
|
+
We're obsessive about performance because agent workloads spawn hundreds of instances and run long tasks. Stateless, horizontal scalability isn't optional.
|
|
580
540
|
|
|
581
|
-
|
|
541
|
+
**Benchmarks** (Apple M4 MacBook Pro, Oct 2025):
|
|
582
542
|
|
|
583
|
-
|
|
584
|
-
|
|
543
|
+
| Metric | Agno | LangGraph | PydanticAI | CrewAI |
|
|
544
|
+
|--------|------|-----------|------------|--------|
|
|
545
|
+
| Instantiation | **3μs** | 1,587μs (529× slower) | 170μs (57× slower) | 210μs (70× slower) |
|
|
546
|
+
| Memory | **6.6 KiB** | 161 KiB (24× higher) | 29 KiB (4× higher) | 66 KiB (10× higher) |
|
|
585
547
|
|
|
586
|
-
|
|
587
|
-
# Setup virtual environment
|
|
588
|
-
./scripts/perf_setup.sh
|
|
589
|
-
source .venvs/perfenv/bin/activate
|
|
590
|
-
|
|
591
|
-
# Agno
|
|
592
|
-
python cookbook/evals/performance/instantiate_agent_with_tool.py
|
|
593
|
-
|
|
594
|
-
# LangGraph
|
|
595
|
-
python cookbook/evals/performance/comparison/langgraph_instantiation.py
|
|
596
|
-
# CrewAI
|
|
597
|
-
python cookbook/evals/performance/comparison/crewai_instantiation.py
|
|
598
|
-
# Pydantic AI
|
|
599
|
-
python cookbook/evals/performance/comparison/pydantic_ai_instantiation.py
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
LangGraph is on the right, **let's start it first and give it a head start**. Then CrewAI and Pydantic AI follow, and finally Agno. Agno obviously finishes first, but let's see by how much.
|
|
548
|
+
Run the benchmarks yourself: [`cookbook/12_evals/performance`](https://github.com/agno-agi/agno/tree/main/cookbook/12_evals/performance)
|
|
603
549
|
|
|
604
550
|
https://github.com/user-attachments/assets/54b98576-1859-4880-9f2d-15e1a426719d
|
|
605
551
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
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.
|
|
609
|
-
|
|
610
|
-
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.
|
|
611
|
-
|
|
612
|
-
### Results
|
|
613
|
-
|
|
614
|
-
Taking Agno as the baseline, we can see that:
|
|
615
|
-
|
|
616
|
-
| Metric | Agno | Langgraph | PydanticAI | CrewAI |
|
|
617
|
-
| ------------------ | ---- | ----------- | ---------- | ---------- |
|
|
618
|
-
| **Time (seconds)** | 1× | 529× slower | 57× slower | 70× slower |
|
|
619
|
-
| **Memory (MiB)** | 1× | 24× higher | 4× higher | 10× higher |
|
|
552
|
+
## IDE Integration
|
|
620
553
|
|
|
621
|
-
|
|
554
|
+
For AI-assisted development, add our docs to your IDE:
|
|
622
555
|
|
|
623
|
-
|
|
624
|
-
| ------------------ | -------- | --------- | ---------- | -------- |
|
|
625
|
-
| **Time (seconds)** | 0.000003 | 0.001587 | 0.000170 | 0.000210 |
|
|
626
|
-
| **Memory (MiB)** | 0.006642 | 0.161435 | 0.028712 | 0.065652 |
|
|
556
|
+
**Cursor:** Settings → Indexing & Docs → Add `https://docs.agno.com/llms-full.txt`
|
|
627
557
|
|
|
628
|
-
|
|
629
|
-
> Agno agents are designed for performance and while we share benchmarks against other frameworks, we should be mindful that accuracy and reliability are more important than speed.
|
|
558
|
+
Works with VSCode, Windsurf, and other AI-enabled editors too.
|
|
630
559
|
|
|
631
|
-
##
|
|
560
|
+
## Contributing
|
|
632
561
|
|
|
633
|
-
We welcome contributions
|
|
562
|
+
We welcome contributions. See the [contributing guide](https://github.com/agno-agi/agno/blob/v2.0/CONTRIBUTING.md).
|
|
634
563
|
|
|
635
564
|
## Telemetry
|
|
636
565
|
|
|
637
|
-
Agno logs which model
|
|
566
|
+
Agno logs which model providers are used so we can prioritize updates. Disable with `AGNO_TELEMETRY=false`.
|
|
638
567
|
|
|
639
568
|
<p align="left">
|
|
640
569
|
<a href="#top">⬆️ Back to Top</a>
|