agno 2.0.11__py3-none-any.whl → 2.1.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.
- agno/agent/agent.py +607 -176
- agno/db/in_memory/in_memory_db.py +42 -29
- agno/db/mongo/mongo.py +65 -66
- agno/db/postgres/postgres.py +6 -4
- agno/db/utils.py +50 -22
- agno/exceptions.py +62 -1
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +51 -0
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/ollama.py +5 -0
- agno/knowledge/embedder/openai.py +18 -54
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +11 -4
- agno/knowledge/reader/pdf_reader.py +4 -3
- agno/knowledge/reader/website_reader.py +3 -2
- agno/models/base.py +125 -32
- agno/models/cerebras/cerebras.py +1 -0
- agno/models/cerebras/cerebras_openai.py +1 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/google/gemini.py +27 -5
- agno/models/openai/chat.py +13 -4
- agno/models/openai/responses.py +1 -1
- agno/models/perplexity/perplexity.py +2 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +49 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +1 -0
- agno/os/app.py +98 -126
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/agui/agui.py +21 -5
- agno/os/interfaces/base.py +4 -2
- agno/os/interfaces/slack/slack.py +13 -8
- agno/os/interfaces/whatsapp/router.py +2 -0
- agno/os/interfaces/whatsapp/whatsapp.py +12 -5
- agno/os/mcp.py +2 -2
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +182 -46
- agno/os/routers/home.py +2 -2
- agno/os/routers/memory/memory.py +23 -1
- agno/os/routers/memory/schemas.py +1 -1
- agno/os/routers/session/session.py +20 -3
- agno/os/utils.py +74 -8
- agno/run/agent.py +120 -77
- agno/run/base.py +2 -13
- agno/run/team.py +115 -72
- agno/run/workflow.py +5 -15
- agno/session/summary.py +9 -10
- agno/session/team.py +2 -1
- agno/team/team.py +721 -169
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +42 -2
- agno/tools/knowledge.py +3 -3
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/spider.py +2 -2
- agno/tools/workflow.py +4 -5
- agno/utils/events.py +66 -1
- agno/utils/hooks.py +57 -0
- agno/utils/media.py +11 -9
- agno/utils/print_response/agent.py +43 -5
- agno/utils/print_response/team.py +48 -12
- agno/utils/serialize.py +32 -0
- agno/vectordb/cassandra/cassandra.py +44 -4
- agno/vectordb/chroma/chromadb.py +79 -8
- agno/vectordb/clickhouse/clickhousedb.py +43 -6
- agno/vectordb/couchbase/couchbase.py +76 -5
- agno/vectordb/lancedb/lance_db.py +38 -3
- agno/vectordb/milvus/milvus.py +76 -4
- agno/vectordb/mongodb/mongodb.py +76 -4
- agno/vectordb/pgvector/pgvector.py +50 -6
- agno/vectordb/pineconedb/pineconedb.py +39 -2
- agno/vectordb/qdrant/qdrant.py +76 -26
- agno/vectordb/singlestore/singlestore.py +77 -4
- agno/vectordb/upstashdb/upstashdb.py +42 -2
- agno/vectordb/weaviate/weaviate.py +39 -3
- agno/workflow/types.py +5 -6
- agno/workflow/workflow.py +58 -2
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/METADATA +4 -3
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/RECORD +93 -82
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/WHEEL +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.11.dist-info → agno-2.1.1.dist-info}/top_level.txt +0 -0
|
@@ -504,8 +504,48 @@ class UpstashVectorDb(VectorDb):
|
|
|
504
504
|
_namespace = self.namespace if namespace is None else namespace
|
|
505
505
|
vectors = []
|
|
506
506
|
|
|
507
|
-
|
|
508
|
-
|
|
507
|
+
if (
|
|
508
|
+
self.embedder
|
|
509
|
+
and self.embedder.enable_batch
|
|
510
|
+
and hasattr(self.embedder, "async_get_embeddings_batch_and_usage")
|
|
511
|
+
):
|
|
512
|
+
# Use batch embedding when enabled and supported
|
|
513
|
+
try:
|
|
514
|
+
# Extract content from all documents
|
|
515
|
+
doc_contents = [doc.content for doc in documents]
|
|
516
|
+
|
|
517
|
+
# Get batch embeddings and usage
|
|
518
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
519
|
+
|
|
520
|
+
# Process documents with pre-computed embeddings
|
|
521
|
+
for j, doc in enumerate(documents):
|
|
522
|
+
try:
|
|
523
|
+
if j < len(embeddings):
|
|
524
|
+
doc.embedding = embeddings[j]
|
|
525
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
526
|
+
except Exception as e:
|
|
527
|
+
logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
528
|
+
|
|
529
|
+
except Exception as e:
|
|
530
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
531
|
+
error_str = str(e).lower()
|
|
532
|
+
is_rate_limit = any(
|
|
533
|
+
phrase in error_str
|
|
534
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
if is_rate_limit:
|
|
538
|
+
logger.error(f"Rate limit detected during batch embedding. {e}")
|
|
539
|
+
raise e
|
|
540
|
+
else:
|
|
541
|
+
logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
542
|
+
# Fall back to individual embedding
|
|
543
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
544
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
545
|
+
else:
|
|
546
|
+
# Use individual embedding
|
|
547
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
548
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
509
549
|
|
|
510
550
|
for i, document in enumerate(documents):
|
|
511
551
|
if document.id is None:
|
|
@@ -270,9 +270,45 @@ class Weaviate(VectorDb):
|
|
|
270
270
|
if not documents:
|
|
271
271
|
return
|
|
272
272
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
# Apply batch embedding logic
|
|
274
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
275
|
+
# Use batch embedding when enabled and supported
|
|
276
|
+
try:
|
|
277
|
+
# Extract content from all documents
|
|
278
|
+
doc_contents = [doc.content for doc in documents]
|
|
279
|
+
|
|
280
|
+
# Get batch embeddings and usage
|
|
281
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
282
|
+
|
|
283
|
+
# Process documents with pre-computed embeddings
|
|
284
|
+
for j, doc in enumerate(documents):
|
|
285
|
+
try:
|
|
286
|
+
if j < len(embeddings):
|
|
287
|
+
doc.embedding = embeddings[j]
|
|
288
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
294
|
+
error_str = str(e).lower()
|
|
295
|
+
is_rate_limit = any(
|
|
296
|
+
phrase in error_str
|
|
297
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
if is_rate_limit:
|
|
301
|
+
logger.error(f"Rate limit detected during batch embedding. {e}")
|
|
302
|
+
raise e
|
|
303
|
+
else:
|
|
304
|
+
logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
305
|
+
# Fall back to individual embedding
|
|
306
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
307
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
308
|
+
else:
|
|
309
|
+
# Use individual embedding
|
|
310
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
311
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
276
312
|
|
|
277
313
|
client = await self.get_async_client()
|
|
278
314
|
try:
|
agno/workflow/types.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from enum import Enum
|
|
3
4
|
from typing import Any, Dict, List, Optional, Union
|
|
@@ -8,6 +9,7 @@ from pydantic import BaseModel
|
|
|
8
9
|
from agno.media import Audio, File, Image, Video
|
|
9
10
|
from agno.models.metrics import Metrics
|
|
10
11
|
from agno.utils.log import log_warning
|
|
12
|
+
from agno.utils.serialize import json_serializer
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@dataclass
|
|
@@ -57,6 +59,7 @@ class WorkflowExecutionInput:
|
|
|
57
59
|
"images": [img.to_dict() for img in self.images] if self.images else None,
|
|
58
60
|
"videos": [vid.to_dict() for vid in self.videos] if self.videos else None,
|
|
59
61
|
"audio": [aud.to_dict() for aud in self.audio] if self.audio else None,
|
|
62
|
+
"files": [file.to_dict() for file in self.files] if self.files else None,
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
|
|
@@ -442,9 +445,7 @@ class WebSocketHandler:
|
|
|
442
445
|
else:
|
|
443
446
|
data = {"type": "message", "content": str(event)}
|
|
444
447
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
await self.websocket.send_text(self.format_sse_event(json.dumps(data)))
|
|
448
|
+
await self.websocket.send_text(self.format_sse_event(json.dumps(data, default=json_serializer)))
|
|
448
449
|
|
|
449
450
|
except Exception as e:
|
|
450
451
|
log_warning(f"Failed to handle WebSocket event: {e}")
|
|
@@ -465,9 +466,7 @@ class WebSocketHandler:
|
|
|
465
466
|
return
|
|
466
467
|
|
|
467
468
|
try:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
await self.websocket.send_text(self.format_sse_event(json.dumps(data)))
|
|
469
|
+
await self.websocket.send_text(self.format_sse_event(json.dumps(data, default=json_serializer)))
|
|
471
470
|
except Exception as e:
|
|
472
471
|
log_warning(f"Failed to send WebSocket dict: {e}")
|
|
473
472
|
|
agno/workflow/workflow.py
CHANGED
|
@@ -25,7 +25,7 @@ from pydantic import BaseModel
|
|
|
25
25
|
|
|
26
26
|
from agno.agent.agent import Agent
|
|
27
27
|
from agno.db.base import BaseDb, SessionType
|
|
28
|
-
from agno.exceptions import RunCancelledException
|
|
28
|
+
from agno.exceptions import InputCheckError, OutputCheckError, RunCancelledException
|
|
29
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
|
|
@@ -54,6 +54,7 @@ from agno.team.team import Team
|
|
|
54
54
|
from agno.utils.common import is_typed_dict, validate_typed_dict
|
|
55
55
|
from agno.utils.log import (
|
|
56
56
|
log_debug,
|
|
57
|
+
log_error,
|
|
57
58
|
log_warning,
|
|
58
59
|
logger,
|
|
59
60
|
set_log_level_to_debug,
|
|
@@ -1000,8 +1001,14 @@ class Workflow:
|
|
|
1000
1001
|
workflow_run_response.audio = output_audio
|
|
1001
1002
|
workflow_run_response.status = RunStatus.completed
|
|
1002
1003
|
|
|
1004
|
+
except (InputCheckError, OutputCheckError) as e:
|
|
1005
|
+
log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
|
|
1006
|
+
# Store error response
|
|
1007
|
+
workflow_run_response.status = RunStatus.error
|
|
1008
|
+
workflow_run_response.content = f"Validation failed: {str(e)} | Check: {e.check_trigger}"
|
|
1009
|
+
|
|
1010
|
+
raise e
|
|
1003
1011
|
except RunCancelledException as e:
|
|
1004
|
-
# Handle run cancellation
|
|
1005
1012
|
logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled")
|
|
1006
1013
|
workflow_run_response.status = RunStatus.cancelled
|
|
1007
1014
|
workflow_run_response.content = str(e)
|
|
@@ -1013,6 +1020,7 @@ class Workflow:
|
|
|
1013
1020
|
# Store error response
|
|
1014
1021
|
workflow_run_response.status = RunStatus.error
|
|
1015
1022
|
workflow_run_response.content = f"Workflow execution failed: {e}"
|
|
1023
|
+
raise e
|
|
1016
1024
|
|
|
1017
1025
|
finally:
|
|
1018
1026
|
self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
|
|
@@ -1199,6 +1207,24 @@ class Workflow:
|
|
|
1199
1207
|
workflow_run_response.audio = output_audio
|
|
1200
1208
|
workflow_run_response.status = RunStatus.completed
|
|
1201
1209
|
|
|
1210
|
+
except (InputCheckError, OutputCheckError) as e:
|
|
1211
|
+
log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
|
|
1212
|
+
|
|
1213
|
+
from agno.run.workflow import WorkflowErrorEvent
|
|
1214
|
+
|
|
1215
|
+
error_event = WorkflowErrorEvent(
|
|
1216
|
+
run_id=workflow_run_response.run_id or "",
|
|
1217
|
+
workflow_id=self.id,
|
|
1218
|
+
workflow_name=self.name,
|
|
1219
|
+
session_id=session.session_id,
|
|
1220
|
+
error=str(e),
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
yield error_event
|
|
1224
|
+
|
|
1225
|
+
# Update workflow_run_response with error
|
|
1226
|
+
workflow_run_response.content = error_event.error
|
|
1227
|
+
workflow_run_response.status = RunStatus.error
|
|
1202
1228
|
except RunCancelledException as e:
|
|
1203
1229
|
# Handle run cancellation during streaming
|
|
1204
1230
|
logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled during streaming")
|
|
@@ -1230,6 +1256,7 @@ class Workflow:
|
|
|
1230
1256
|
# Update workflow_run_response with error
|
|
1231
1257
|
workflow_run_response.content = error_event.error
|
|
1232
1258
|
workflow_run_response.status = RunStatus.error
|
|
1259
|
+
raise e
|
|
1233
1260
|
|
|
1234
1261
|
# Yield workflow completed event
|
|
1235
1262
|
workflow_completed_event = WorkflowCompletedEvent(
|
|
@@ -1436,6 +1463,13 @@ class Workflow:
|
|
|
1436
1463
|
workflow_run_response.audio = output_audio
|
|
1437
1464
|
workflow_run_response.status = RunStatus.completed
|
|
1438
1465
|
|
|
1466
|
+
except (InputCheckError, OutputCheckError) as e:
|
|
1467
|
+
log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
|
|
1468
|
+
# Store error response
|
|
1469
|
+
workflow_run_response.status = RunStatus.error
|
|
1470
|
+
workflow_run_response.content = f"Validation failed: {str(e)} | Check: {e.check_trigger}"
|
|
1471
|
+
|
|
1472
|
+
raise e
|
|
1439
1473
|
except RunCancelledException as e:
|
|
1440
1474
|
logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled")
|
|
1441
1475
|
workflow_run_response.status = RunStatus.cancelled
|
|
@@ -1444,6 +1478,7 @@ class Workflow:
|
|
|
1444
1478
|
logger.error(f"Workflow execution failed: {e}")
|
|
1445
1479
|
workflow_run_response.status = RunStatus.error
|
|
1446
1480
|
workflow_run_response.content = f"Workflow execution failed: {e}"
|
|
1481
|
+
raise e
|
|
1447
1482
|
|
|
1448
1483
|
self._update_session_metrics(session=session, workflow_run_response=workflow_run_response)
|
|
1449
1484
|
session.upsert_run(run=workflow_run_response)
|
|
@@ -1471,6 +1506,7 @@ class Workflow:
|
|
|
1471
1506
|
from inspect import isasyncgenfunction, iscoroutinefunction, isgeneratorfunction
|
|
1472
1507
|
|
|
1473
1508
|
workflow_run_response.status = RunStatus.running
|
|
1509
|
+
|
|
1474
1510
|
workflow_started_event = WorkflowStartedEvent(
|
|
1475
1511
|
run_id=workflow_run_response.run_id or "",
|
|
1476
1512
|
workflow_name=workflow_run_response.workflow_name,
|
|
@@ -1633,6 +1669,24 @@ class Workflow:
|
|
|
1633
1669
|
workflow_run_response.audio = output_audio
|
|
1634
1670
|
workflow_run_response.status = RunStatus.completed
|
|
1635
1671
|
|
|
1672
|
+
except (InputCheckError, OutputCheckError) as e:
|
|
1673
|
+
log_error(f"Validation failed: {str(e)} | Check: {e.check_trigger}")
|
|
1674
|
+
|
|
1675
|
+
from agno.run.workflow import WorkflowErrorEvent
|
|
1676
|
+
|
|
1677
|
+
error_event = WorkflowErrorEvent(
|
|
1678
|
+
run_id=workflow_run_response.run_id or "",
|
|
1679
|
+
workflow_id=self.id,
|
|
1680
|
+
workflow_name=self.name,
|
|
1681
|
+
session_id=session.session_id,
|
|
1682
|
+
error=str(e),
|
|
1683
|
+
)
|
|
1684
|
+
|
|
1685
|
+
yield error_event
|
|
1686
|
+
|
|
1687
|
+
# Update workflow_run_response with error
|
|
1688
|
+
workflow_run_response.content = error_event.error
|
|
1689
|
+
workflow_run_response.status = RunStatus.error
|
|
1636
1690
|
except RunCancelledException as e:
|
|
1637
1691
|
# Handle run cancellation during streaming
|
|
1638
1692
|
logger.info(f"Workflow run {workflow_run_response.run_id} was cancelled during streaming")
|
|
@@ -1668,6 +1722,7 @@ class Workflow:
|
|
|
1668
1722
|
# Update workflow_run_response with error
|
|
1669
1723
|
workflow_run_response.content = error_event.error
|
|
1670
1724
|
workflow_run_response.status = RunStatus.error
|
|
1725
|
+
raise e
|
|
1671
1726
|
|
|
1672
1727
|
# Yield workflow completed event
|
|
1673
1728
|
workflow_completed_event = WorkflowCompletedEvent(
|
|
@@ -2175,6 +2230,7 @@ class Workflow:
|
|
|
2175
2230
|
audio=audio, # type: ignore
|
|
2176
2231
|
images=images, # type: ignore
|
|
2177
2232
|
videos=videos, # type: ignore
|
|
2233
|
+
files=files,
|
|
2178
2234
|
)
|
|
2179
2235
|
log_debug(
|
|
2180
2236
|
f"Created async pipeline input with session state keys: {list(session_state.keys()) if session_state else 'None'}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agno
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -48,6 +48,7 @@ Requires-Dist: uvicorn; extra == "dev"
|
|
|
48
48
|
Provides-Extra: os
|
|
49
49
|
Requires-Dist: fastapi; extra == "os"
|
|
50
50
|
Requires-Dist: uvicorn; extra == "os"
|
|
51
|
+
Requires-Dist: PyJWT; extra == "os"
|
|
51
52
|
Provides-Extra: integration-tests
|
|
52
53
|
Requires-Dist: exa_py; extra == "integration-tests"
|
|
53
54
|
Requires-Dist: ddgs; extra == "integration-tests"
|
|
@@ -444,7 +445,7 @@ At Agno, we're obsessed with performance. Why? because even simple AI workflows
|
|
|
444
445
|
- Agent instantiation: ~3μs on average
|
|
445
446
|
- Memory footprint: ~6.5Kib on average
|
|
446
447
|
|
|
447
|
-
> Tested on an Apple M4
|
|
448
|
+
> Tested on an Apple M4 MacBook Pro.
|
|
448
449
|
|
|
449
450
|
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.
|
|
450
451
|
|
|
@@ -468,7 +469,7 @@ python evals/performance/instantiation_with_tool.py
|
|
|
468
469
|
python evals/performance/other/langgraph_instantiation.py
|
|
469
470
|
```
|
|
470
471
|
|
|
471
|
-
> The following evaluation is run on an Apple M4
|
|
472
|
+
> The following evaluation is run on an Apple M4 MacBook Pro. It also runs as a Github action on this repo.
|
|
472
473
|
|
|
473
474
|
LangGraph is on the right, **let's start it first and give it a head start**.
|
|
474
475
|
|