agno 2.0.0rc2__py3-none-any.whl → 2.3.0__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 +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- 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 +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- 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/fastembed.py +1 -1
- 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/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/utils/serialize.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""JSON serialization utilities for handling datetime and enum objects."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def json_serializer(obj: Any) -> Any:
|
|
9
|
+
"""Custom JSON serializer for objects not serializable by default json module.
|
|
10
|
+
|
|
11
|
+
Handles:
|
|
12
|
+
- datetime, date, time objects -> ISO format strings
|
|
13
|
+
- Enum objects -> their values (or names if values are not JSON-serializable)
|
|
14
|
+
- All other objects -> string representation
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
obj: Object to serialize
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
JSON-serializable representation of the object
|
|
21
|
+
"""
|
|
22
|
+
# Datetime like
|
|
23
|
+
if isinstance(obj, (datetime, date, time)):
|
|
24
|
+
return obj.isoformat()
|
|
25
|
+
|
|
26
|
+
# Enums
|
|
27
|
+
if isinstance(obj, Enum):
|
|
28
|
+
v = obj.value
|
|
29
|
+
return v if isinstance(v, (str, int, float, bool, type(None))) else obj.name
|
|
30
|
+
|
|
31
|
+
# Fallback to string
|
|
32
|
+
return str(obj)
|
agno/utils/streamlit.py
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Any, Callable, Dict, List, Optional
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from agno.
|
|
7
|
-
from agno.
|
|
8
|
-
from agno.models.
|
|
9
|
-
from agno.models.
|
|
10
|
-
from agno.
|
|
11
|
-
|
|
4
|
+
try:
|
|
5
|
+
from agno.agent import Agent
|
|
6
|
+
from agno.db.base import SessionType
|
|
7
|
+
from agno.models.anthropic import Claude
|
|
8
|
+
from agno.models.google import Gemini
|
|
9
|
+
from agno.models.openai import OpenAIChat
|
|
10
|
+
from agno.utils.log import logger
|
|
11
|
+
except ImportError:
|
|
12
|
+
raise ImportError("`agno` not installed. Please install using `pip install agno`")
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import streamlit as st
|
|
16
|
+
except ImportError:
|
|
17
|
+
raise ImportError("`streamlit` not installed. Please install using `pip install streamlit`")
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
def add_message(role: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None) -> None:
|
|
@@ -74,7 +80,7 @@ def session_selector_widget(agent: Agent, model_id: str, agent_creation_callback
|
|
|
74
80
|
session_options = []
|
|
75
81
|
session_dict = {}
|
|
76
82
|
|
|
77
|
-
for session in sessions:
|
|
83
|
+
for session in sessions: # type: ignore
|
|
78
84
|
if not hasattr(session, "session_id") or not session.session_id:
|
|
79
85
|
continue
|
|
80
86
|
|
|
@@ -446,7 +452,7 @@ MODELS = [
|
|
|
446
452
|
"gpt-4o",
|
|
447
453
|
"o3-mini",
|
|
448
454
|
"gpt-5",
|
|
449
|
-
"claude-4-
|
|
455
|
+
"claude-sonnet-4-5-20250929",
|
|
450
456
|
"gemini-2.5-pro",
|
|
451
457
|
]
|
|
452
458
|
|
agno/utils/string.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import json
|
|
3
3
|
import re
|
|
4
|
+
import uuid
|
|
4
5
|
from typing import Optional, Type
|
|
6
|
+
from uuid import uuid4
|
|
5
7
|
|
|
6
8
|
from pydantic import BaseModel, ValidationError
|
|
7
9
|
|
|
@@ -156,6 +158,15 @@ def _parse_individual_json(content: str, output_schema: Type[BaseModel]) -> Opti
|
|
|
156
158
|
def parse_response_model_str(content: str, output_schema: Type[BaseModel]) -> Optional[BaseModel]:
|
|
157
159
|
structured_output = None
|
|
158
160
|
|
|
161
|
+
# Extract thinking content first to prevent <think> tags from corrupting JSON
|
|
162
|
+
from agno.utils.reasoning import extract_thinking_content
|
|
163
|
+
|
|
164
|
+
# handle thinking content b/w <think> tags
|
|
165
|
+
if "</think>" in content:
|
|
166
|
+
reasoning_content, output_content = extract_thinking_content(content)
|
|
167
|
+
if reasoning_content:
|
|
168
|
+
content = output_content
|
|
169
|
+
|
|
159
170
|
# Clean content first to simplify all parsing attempts
|
|
160
171
|
cleaned_content = _clean_json_content(content)
|
|
161
172
|
|
|
@@ -188,3 +199,33 @@ def parse_response_model_str(content: str, output_schema: Type[BaseModel]) -> Op
|
|
|
188
199
|
logger.warning("All parsing attempts failed.")
|
|
189
200
|
|
|
190
201
|
return structured_output
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def generate_id(seed: Optional[str] = None) -> str:
|
|
205
|
+
"""
|
|
206
|
+
Generate a deterministic UUID5 based on a seed string.
|
|
207
|
+
If no seed is provided, generate a random UUID4.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
seed (str): The seed string to generate the UUID from.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
str: A deterministic UUID5 string.
|
|
214
|
+
"""
|
|
215
|
+
if seed is None:
|
|
216
|
+
return str(uuid4())
|
|
217
|
+
return str(uuid.uuid5(uuid.NAMESPACE_DNS, seed))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def generate_id_from_name(name: Optional[str] = None) -> str:
|
|
221
|
+
"""
|
|
222
|
+
Generate a deterministic ID from a name string.
|
|
223
|
+
If no name is provided, generate a random UUID4.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
name (str): The name string to generate the ID from.
|
|
227
|
+
"""
|
|
228
|
+
if name:
|
|
229
|
+
return name.lower().replace(" ", "-").replace("_", "-")
|
|
230
|
+
else:
|
|
231
|
+
return str(uuid4())
|
agno/utils/team.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from agno.agent import Agent
|
|
4
|
+
from agno.media import Audio, File, Image, Video
|
|
5
|
+
from agno.run.agent import RunOutput
|
|
6
|
+
from agno.run.team import TeamRunOutput
|
|
7
|
+
from agno.utils.log import log_debug
|
|
4
8
|
from agno.utils.string import is_valid_uuid, url_safe_string
|
|
5
9
|
|
|
6
10
|
if TYPE_CHECKING:
|
|
@@ -9,19 +13,20 @@ if TYPE_CHECKING:
|
|
|
9
13
|
|
|
10
14
|
def format_member_agent_task(
|
|
11
15
|
task_description: str,
|
|
12
|
-
expected_output: Optional[str] = None,
|
|
13
16
|
team_member_interactions_str: Optional[str] = None,
|
|
17
|
+
team_history_str: Optional[str] = None,
|
|
14
18
|
) -> str:
|
|
15
|
-
|
|
16
|
-
member_agent_task += f"\n\n<task>\n{task_description}\n</task>"
|
|
17
|
-
|
|
18
|
-
if expected_output is not None:
|
|
19
|
-
member_agent_task += f"\n\n<expected_output>\n{expected_output}\n</expected_output>"
|
|
19
|
+
member_task_str = ""
|
|
20
20
|
|
|
21
21
|
if team_member_interactions_str:
|
|
22
|
-
|
|
22
|
+
member_task_str += f"{team_member_interactions_str}\n\n"
|
|
23
|
+
|
|
24
|
+
if team_history_str:
|
|
25
|
+
member_task_str += f"{team_history_str}\n\n"
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
member_task_str += f"{task_description}"
|
|
28
|
+
|
|
29
|
+
return member_task_str
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
def get_member_id(member: Union[Agent, "Team"]) -> str:
|
|
@@ -48,3 +53,87 @@ def get_member_id(member: Union[Agent, "Team"]) -> str:
|
|
|
48
53
|
else:
|
|
49
54
|
url_safe_member_id = None
|
|
50
55
|
return url_safe_member_id
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add_interaction_to_team_run_context(
|
|
59
|
+
team_run_context: Dict[str, Any],
|
|
60
|
+
member_name: str,
|
|
61
|
+
task: str,
|
|
62
|
+
run_response: Union[RunOutput, TeamRunOutput],
|
|
63
|
+
) -> None:
|
|
64
|
+
if "member_responses" not in team_run_context:
|
|
65
|
+
team_run_context["member_responses"] = []
|
|
66
|
+
team_run_context["member_responses"].append(
|
|
67
|
+
{
|
|
68
|
+
"member_name": member_name,
|
|
69
|
+
"task": task,
|
|
70
|
+
"run_response": run_response,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
log_debug(f"Updated team run context with member name: {member_name}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_team_member_interactions_str(team_run_context: Dict[str, Any]) -> str:
|
|
77
|
+
if not team_run_context:
|
|
78
|
+
return ""
|
|
79
|
+
team_member_interactions_str = ""
|
|
80
|
+
if "member_responses" in team_run_context:
|
|
81
|
+
team_member_interactions_str += "<member_interaction_context>\nSee below interactions wit other team members.\n"
|
|
82
|
+
|
|
83
|
+
for interaction in team_run_context["member_responses"]:
|
|
84
|
+
response_dict = interaction["run_response"].to_dict()
|
|
85
|
+
response_content = (
|
|
86
|
+
response_dict.get("content")
|
|
87
|
+
or ",".join([tool.get("content", "") for tool in response_dict.get("tools", [])])
|
|
88
|
+
or ""
|
|
89
|
+
)
|
|
90
|
+
team_member_interactions_str += f"Member: {interaction['member_name']}\n"
|
|
91
|
+
team_member_interactions_str += f"Task: {interaction['task']}\n"
|
|
92
|
+
team_member_interactions_str += f"Response: {response_content}\n"
|
|
93
|
+
team_member_interactions_str += "\n"
|
|
94
|
+
team_member_interactions_str += "</member_interaction_context>\n"
|
|
95
|
+
return team_member_interactions_str
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_team_run_context_images(team_run_context: Dict[str, Any]) -> List[Image]:
|
|
99
|
+
if not team_run_context:
|
|
100
|
+
return []
|
|
101
|
+
images = []
|
|
102
|
+
if "member_responses" in team_run_context:
|
|
103
|
+
for interaction in team_run_context["member_responses"]:
|
|
104
|
+
if interaction["run_response"].images:
|
|
105
|
+
images.extend(interaction["run_response"].images)
|
|
106
|
+
return images
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_team_run_context_videos(team_run_context: Dict[str, Any]) -> List[Video]:
|
|
110
|
+
if not team_run_context:
|
|
111
|
+
return []
|
|
112
|
+
videos = []
|
|
113
|
+
if "member_responses" in team_run_context:
|
|
114
|
+
for interaction in team_run_context["member_responses"]:
|
|
115
|
+
if interaction["run_response"].videos:
|
|
116
|
+
videos.extend(interaction["run_response"].videos)
|
|
117
|
+
return videos
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_team_run_context_audio(team_run_context: Dict[str, Any]) -> List[Audio]:
|
|
121
|
+
if not team_run_context:
|
|
122
|
+
return []
|
|
123
|
+
audio = []
|
|
124
|
+
if "member_responses" in team_run_context:
|
|
125
|
+
for interaction in team_run_context["member_responses"]:
|
|
126
|
+
if interaction["run_response"].audio:
|
|
127
|
+
audio.extend(interaction["run_response"].audio)
|
|
128
|
+
return audio
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_team_run_context_files(team_run_context: Dict[str, Any]) -> List[File]:
|
|
132
|
+
if not team_run_context:
|
|
133
|
+
return []
|
|
134
|
+
files = []
|
|
135
|
+
if "member_responses" in team_run_context:
|
|
136
|
+
for interaction in team_run_context["member_responses"]:
|
|
137
|
+
if interaction["run_response"].files:
|
|
138
|
+
files.extend(interaction["run_response"].files)
|
|
139
|
+
return files
|
agno/utils/tools.py
CHANGED
|
@@ -13,7 +13,7 @@ def get_function_call_for_tool_call(
|
|
|
13
13
|
_tool_call_function = tool_call.get("function")
|
|
14
14
|
if _tool_call_function is not None:
|
|
15
15
|
_tool_call_function_name = _tool_call_function.get("name")
|
|
16
|
-
_tool_call_function_arguments_str = _tool_call_function.get("arguments")
|
|
16
|
+
_tool_call_function_arguments_str = _tool_call_function.get("arguments") or "{}"
|
|
17
17
|
if _tool_call_function_name is not None:
|
|
18
18
|
return get_function_call(
|
|
19
19
|
name=_tool_call_function_name,
|
agno/vectordb/base.py
CHANGED
|
@@ -2,11 +2,28 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
from agno.knowledge.document import Document
|
|
5
|
+
from agno.utils.string import generate_id
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class VectorDb(ABC):
|
|
8
9
|
"""Base class for Vector Databases"""
|
|
9
10
|
|
|
11
|
+
def __init__(self, *, id: Optional[str] = None, name: Optional[str] = None, description: Optional[str] = None):
|
|
12
|
+
"""Initialize base VectorDb.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
id: Optional custom ID. If not provided, an id will be generated.
|
|
16
|
+
name: Optional name for the vector database.
|
|
17
|
+
description: Optional description for the vector database.
|
|
18
|
+
"""
|
|
19
|
+
if name is None:
|
|
20
|
+
name = self.__class__.__name__
|
|
21
|
+
|
|
22
|
+
self.name = name
|
|
23
|
+
self.description = description
|
|
24
|
+
# Last resort fallback to generate id from name if ID not specified
|
|
25
|
+
self.id = id if id else generate_id(name)
|
|
26
|
+
|
|
10
27
|
@abstractmethod
|
|
11
28
|
def create(self) -> None:
|
|
12
29
|
raise NotImplementedError
|
|
@@ -55,13 +72,11 @@ class VectorDb(ABC):
|
|
|
55
72
|
raise NotImplementedError
|
|
56
73
|
|
|
57
74
|
@abstractmethod
|
|
58
|
-
def search(self, query: str, limit: int = 5, filters: Optional[
|
|
75
|
+
def search(self, query: str, limit: int = 5, filters: Optional[Any] = None) -> List[Document]:
|
|
59
76
|
raise NotImplementedError
|
|
60
77
|
|
|
61
78
|
@abstractmethod
|
|
62
|
-
async def async_search(
|
|
63
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
64
|
-
) -> List[Document]:
|
|
79
|
+
async def async_search(self, query: str, limit: int = 5, filters: Optional[Any] = None) -> List[Document]:
|
|
65
80
|
raise NotImplementedError
|
|
66
81
|
|
|
67
82
|
@abstractmethod
|
|
@@ -106,3 +121,7 @@ class VectorDb(ABC):
|
|
|
106
121
|
@abstractmethod
|
|
107
122
|
def delete_by_content_id(self, content_id: str) -> bool:
|
|
108
123
|
raise NotImplementedError
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def get_supported_search_types(self) -> List[str]:
|
|
127
|
+
raise NotImplementedError
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
2
|
+
from typing import Any, Dict, Iterable, List, Optional, Union
|
|
3
3
|
|
|
4
|
+
from agno.filters import FilterExpr
|
|
4
5
|
from agno.knowledge.document import Document
|
|
5
6
|
from agno.knowledge.embedder import Embedder
|
|
6
|
-
from agno.utils.log import log_debug, log_error, log_info
|
|
7
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
7
8
|
from agno.vectordb.base import VectorDb
|
|
8
9
|
from agno.vectordb.cassandra.index import AgnoMetadataVectorCassandraTable
|
|
9
10
|
|
|
@@ -15,6 +16,8 @@ class Cassandra(VectorDb):
|
|
|
15
16
|
keyspace: str,
|
|
16
17
|
embedder: Optional[Embedder] = None,
|
|
17
18
|
session=None,
|
|
19
|
+
name: Optional[str] = None,
|
|
20
|
+
description: Optional[str] = None,
|
|
18
21
|
) -> None:
|
|
19
22
|
if not table_name:
|
|
20
23
|
raise ValueError("Table name must be provided.")
|
|
@@ -30,6 +33,9 @@ class Cassandra(VectorDb):
|
|
|
30
33
|
|
|
31
34
|
embedder = OpenAIEmbedder()
|
|
32
35
|
log_info("Embedder not provided, using OpenAIEmbedder as default.")
|
|
36
|
+
# Initialize base class with name and description
|
|
37
|
+
super().__init__(name=name, description=description)
|
|
38
|
+
|
|
33
39
|
self.table_name: str = table_name
|
|
34
40
|
self.embedder: Embedder = embedder
|
|
35
41
|
self.session = session
|
|
@@ -119,12 +125,52 @@ class Cassandra(VectorDb):
|
|
|
119
125
|
"""Insert documents asynchronously by running in a thread."""
|
|
120
126
|
log_info(f"Cassandra VectorDB : Inserting Documents to the table {self.table_name}")
|
|
121
127
|
|
|
122
|
-
|
|
128
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
129
|
+
# Use batch embedding when enabled and supported
|
|
123
130
|
try:
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
# Extract content from all documents
|
|
132
|
+
doc_contents = [doc.content for doc in documents]
|
|
133
|
+
|
|
134
|
+
# Get batch embeddings and usage
|
|
135
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
136
|
+
|
|
137
|
+
# Process documents with pre-computed embeddings
|
|
138
|
+
for j, doc in enumerate(documents):
|
|
139
|
+
try:
|
|
140
|
+
if j < len(embeddings):
|
|
141
|
+
doc.embedding = embeddings[j]
|
|
142
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
143
|
+
except Exception as e:
|
|
144
|
+
log_error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
145
|
+
|
|
126
146
|
except Exception as e:
|
|
127
|
-
|
|
147
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
148
|
+
error_str = str(e).lower()
|
|
149
|
+
is_rate_limit = any(
|
|
150
|
+
phrase in error_str
|
|
151
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if is_rate_limit:
|
|
155
|
+
log_error(f"Rate limit detected during batch embedding. {e}")
|
|
156
|
+
raise e
|
|
157
|
+
else:
|
|
158
|
+
log_error(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
159
|
+
# Fall back to individual embedding
|
|
160
|
+
for doc in documents:
|
|
161
|
+
try:
|
|
162
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder)]
|
|
163
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
log_error(f"Error processing document '{doc.name}': {e}")
|
|
166
|
+
else:
|
|
167
|
+
# Use individual embedding (original behavior)
|
|
168
|
+
for doc in documents:
|
|
169
|
+
try:
|
|
170
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder)]
|
|
171
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
log_error(f"Error processing document '{doc.name}': {e}")
|
|
128
174
|
|
|
129
175
|
futures = []
|
|
130
176
|
for doc in documents:
|
|
@@ -159,13 +205,17 @@ class Cassandra(VectorDb):
|
|
|
159
205
|
self.delete_by_content_hash(content_hash)
|
|
160
206
|
await self.async_insert(content_hash, documents, filters)
|
|
161
207
|
|
|
162
|
-
def search(
|
|
208
|
+
def search(
|
|
209
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
210
|
+
) -> List[Document]:
|
|
163
211
|
"""Keyword-based search on document metadata."""
|
|
164
212
|
log_debug(f"Cassandra VectorDB : Performing Vector Search on {self.table_name} with query {query}")
|
|
213
|
+
if filters is not None:
|
|
214
|
+
log_warning("Filters are not yet supported in Cassandra. No filters will be applied.")
|
|
165
215
|
return self.vector_search(query=query, limit=limit)
|
|
166
216
|
|
|
167
217
|
async def async_search(
|
|
168
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
218
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
169
219
|
) -> List[Document]:
|
|
170
220
|
"""Search asynchronously by running in a thread."""
|
|
171
221
|
return await asyncio.to_thread(self.search, query, limit, filters)
|
|
@@ -176,7 +226,9 @@ class Cassandra(VectorDb):
|
|
|
176
226
|
) -> List[Document]:
|
|
177
227
|
return [self._row_to_document(row=hit) for hit in hits]
|
|
178
228
|
|
|
179
|
-
def vector_search(
|
|
229
|
+
def vector_search(
|
|
230
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
231
|
+
) -> List[Document]:
|
|
180
232
|
"""Vector similarity search implementation."""
|
|
181
233
|
query_embedding = self.embedder.get_embedding(query)
|
|
182
234
|
hits = list(
|
|
@@ -443,3 +495,7 @@ class Cassandra(VectorDb):
|
|
|
443
495
|
except Exception as e:
|
|
444
496
|
log_error(f"Error updating metadata for content_id {content_id}: {e}")
|
|
445
497
|
raise
|
|
498
|
+
|
|
499
|
+
def get_supported_search_types(self) -> List[str]:
|
|
500
|
+
"""Get the supported search types for this vector database."""
|
|
501
|
+
return [] # Cassandra doesn't use SearchType enum
|