agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- 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 +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -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 +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- 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 +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/tools/tavily.py
CHANGED
|
@@ -17,21 +17,51 @@ class TavilyTools(Toolkit):
|
|
|
17
17
|
api_key: Optional[str] = None,
|
|
18
18
|
enable_search: bool = True,
|
|
19
19
|
enable_search_context: bool = False,
|
|
20
|
+
enable_extract: bool = False,
|
|
20
21
|
all: bool = False,
|
|
21
22
|
max_tokens: int = 6000,
|
|
22
23
|
include_answer: bool = True,
|
|
23
24
|
search_depth: Literal["basic", "advanced"] = "advanced",
|
|
25
|
+
extract_depth: Literal["basic", "advanced"] = "basic",
|
|
26
|
+
include_images: bool = False,
|
|
27
|
+
include_favicon: bool = False,
|
|
28
|
+
extract_timeout: Optional[int] = None,
|
|
29
|
+
extract_format: Literal["markdown", "text"] = "markdown",
|
|
24
30
|
format: Literal["json", "markdown"] = "markdown",
|
|
25
31
|
**kwargs,
|
|
26
32
|
):
|
|
33
|
+
"""Initialize TavilyTools with search and extract capabilities.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_key: Tavily API key. If not provided, will use TAVILY_API_KEY env var.
|
|
37
|
+
enable_search: Enable web search functionality. Defaults to True.
|
|
38
|
+
enable_search_context: Use search context mode instead of regular search. Defaults to False.
|
|
39
|
+
enable_extract: Enable URL content extraction functionality. Defaults to False.
|
|
40
|
+
all: Enable all available tools. Defaults to False.
|
|
41
|
+
max_tokens: Maximum tokens for search results. Defaults to 6000.
|
|
42
|
+
include_answer: Include AI-generated answer in search results. Defaults to True.
|
|
43
|
+
search_depth: Search depth level - basic (1 credit) or advanced (2 credits). Defaults to "advanced".
|
|
44
|
+
extract_depth: Extract depth level - basic (1 credit/5 URLs) or advanced (2 credits/5 URLs). Defaults to "basic".
|
|
45
|
+
include_images: Include images in extracted content. Defaults to False.
|
|
46
|
+
include_favicon: Include favicon in extracted content. Defaults to False.
|
|
47
|
+
extract_timeout: Timeout in seconds for extraction requests. Defaults to None.
|
|
48
|
+
extract_format: Output format for extracted content - markdown or text. Defaults to "markdown".
|
|
49
|
+
format: Output format for search results - json or markdown. Defaults to "markdown".
|
|
50
|
+
**kwargs: Additional arguments passed to Toolkit.
|
|
51
|
+
"""
|
|
27
52
|
self.api_key = api_key or getenv("TAVILY_API_KEY")
|
|
28
53
|
if not self.api_key:
|
|
29
54
|
logger.error("TAVILY_API_KEY not provided")
|
|
30
55
|
|
|
31
56
|
self.client: TavilyClient = TavilyClient(api_key=self.api_key)
|
|
32
57
|
self.search_depth: Literal["basic", "advanced"] = search_depth
|
|
58
|
+
self.extract_depth: Literal["basic", "advanced"] = extract_depth
|
|
33
59
|
self.max_tokens: int = max_tokens
|
|
34
60
|
self.include_answer: bool = include_answer
|
|
61
|
+
self.include_images: bool = include_images
|
|
62
|
+
self.include_favicon: bool = include_favicon
|
|
63
|
+
self.extract_timeout: Optional[int] = extract_timeout
|
|
64
|
+
self.extract_format: Literal["markdown", "text"] = extract_format
|
|
35
65
|
self.format: Literal["json", "markdown"] = format
|
|
36
66
|
|
|
37
67
|
tools: List[Any] = []
|
|
@@ -42,6 +72,9 @@ class TavilyTools(Toolkit):
|
|
|
42
72
|
else:
|
|
43
73
|
tools.append(self.web_search_using_tavily)
|
|
44
74
|
|
|
75
|
+
if enable_extract or all:
|
|
76
|
+
tools.append(self.extract_url_content)
|
|
77
|
+
|
|
45
78
|
super().__init__(name="tavily_tools", tools=tools, **kwargs)
|
|
46
79
|
|
|
47
80
|
def web_search_using_tavily(self, query: str, max_results: int = 5) -> str:
|
|
@@ -106,3 +139,116 @@ class TavilyTools(Toolkit):
|
|
|
106
139
|
return self.client.get_search_context(
|
|
107
140
|
query=query, search_depth=self.search_depth, max_tokens=self.max_tokens, include_answer=self.include_answer
|
|
108
141
|
)
|
|
142
|
+
|
|
143
|
+
def extract_url_content(self, urls: str) -> str:
|
|
144
|
+
"""Extract content from one or more URLs using Tavily's Extract API.
|
|
145
|
+
This function retrieves the main content from web pages in markdown or text format.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
urls (str): Single URL or multiple comma-separated URLs to extract content from.
|
|
149
|
+
Example: "https://example.com" or "https://example.com,https://another.com"
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
str: Extracted content in the specified format (markdown or text).
|
|
153
|
+
For multiple URLs, returns combined content with URL headers.
|
|
154
|
+
Failed extractions are noted in the output.
|
|
155
|
+
"""
|
|
156
|
+
# Parse URLs - handle both single and comma-separated multiple URLs
|
|
157
|
+
url_list = [url.strip() for url in urls.split(",") if url.strip()]
|
|
158
|
+
|
|
159
|
+
if not url_list:
|
|
160
|
+
return "Error: No valid URLs provided."
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Prepare extract parameters
|
|
164
|
+
extract_params: Dict[str, Any] = {
|
|
165
|
+
"urls": url_list,
|
|
166
|
+
"depth": self.extract_depth,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Add optional parameters if specified
|
|
170
|
+
if self.include_images:
|
|
171
|
+
extract_params["include_images"] = True
|
|
172
|
+
if self.include_favicon:
|
|
173
|
+
extract_params["include_favicon"] = True
|
|
174
|
+
if self.extract_timeout is not None:
|
|
175
|
+
extract_params["timeout"] = self.extract_timeout
|
|
176
|
+
|
|
177
|
+
# Call Tavily Extract API
|
|
178
|
+
response = self.client.extract(**extract_params)
|
|
179
|
+
|
|
180
|
+
# Process response based on format preference
|
|
181
|
+
if not response or "results" not in response:
|
|
182
|
+
return "Error: No content could be extracted from the provided URL(s)."
|
|
183
|
+
|
|
184
|
+
results = response.get("results", [])
|
|
185
|
+
if not results:
|
|
186
|
+
return "Error: No content could be extracted from the provided URL(s)."
|
|
187
|
+
|
|
188
|
+
# Format output
|
|
189
|
+
if self.extract_format == "markdown":
|
|
190
|
+
return self._format_extract_markdown(results)
|
|
191
|
+
elif self.extract_format == "text":
|
|
192
|
+
return self._format_extract_text(results)
|
|
193
|
+
else:
|
|
194
|
+
# Fallback to JSON if format is unrecognized
|
|
195
|
+
return json.dumps(results, indent=2)
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error extracting content from URLs: {e}")
|
|
199
|
+
return f"Error extracting content: {str(e)}"
|
|
200
|
+
|
|
201
|
+
def _format_extract_markdown(self, results: List[Dict[str, Any]]) -> str:
|
|
202
|
+
"""Format extraction results as markdown.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
results: List of extraction result dictionaries from Tavily API.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
str: Formatted markdown content.
|
|
209
|
+
"""
|
|
210
|
+
output = []
|
|
211
|
+
|
|
212
|
+
for result in results:
|
|
213
|
+
url = result.get("url", "Unknown URL")
|
|
214
|
+
raw_content = result.get("raw_content", "")
|
|
215
|
+
failed_reason = result.get("failed_reason")
|
|
216
|
+
|
|
217
|
+
if failed_reason:
|
|
218
|
+
output.append(f"## {url}\n\n **Extraction Failed**: {failed_reason}\n\n")
|
|
219
|
+
elif raw_content:
|
|
220
|
+
output.append(f"## {url}\n\n{raw_content}\n\n")
|
|
221
|
+
else:
|
|
222
|
+
output.append(f"## {url}\n\n*No content available*\n\n")
|
|
223
|
+
|
|
224
|
+
return "".join(output) if output else "No content extracted."
|
|
225
|
+
|
|
226
|
+
def _format_extract_text(self, results: List[Dict[str, Any]]) -> str:
|
|
227
|
+
"""Format extraction results as plain text.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
results: List of extraction result dictionaries from Tavily API.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: Formatted plain text content.
|
|
234
|
+
"""
|
|
235
|
+
output = []
|
|
236
|
+
|
|
237
|
+
for result in results:
|
|
238
|
+
url = result.get("url", "Unknown URL")
|
|
239
|
+
raw_content = result.get("raw_content", "")
|
|
240
|
+
failed_reason = result.get("failed_reason")
|
|
241
|
+
|
|
242
|
+
output.append(f"URL: {url}")
|
|
243
|
+
output.append("-" * 80)
|
|
244
|
+
|
|
245
|
+
if failed_reason:
|
|
246
|
+
output.append(f"EXTRACTION FAILED: {failed_reason}")
|
|
247
|
+
elif raw_content:
|
|
248
|
+
output.append(raw_content)
|
|
249
|
+
else:
|
|
250
|
+
output.append("No content available")
|
|
251
|
+
|
|
252
|
+
output.append("\n")
|
|
253
|
+
|
|
254
|
+
return "\n".join(output) if output else "No content extracted."
|
agno/tools/toolkit.py
CHANGED
|
@@ -6,6 +6,10 @@ from agno.utils.log import log_debug, log_warning, logger
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Toolkit:
|
|
9
|
+
# Set to True for toolkits that require connection management (e.g., database connections)
|
|
10
|
+
# When True, the Agent will automatically call connect() before using tools and close() after
|
|
11
|
+
_requires_connect: bool = False
|
|
12
|
+
|
|
9
13
|
def __init__(
|
|
10
14
|
self,
|
|
11
15
|
name: str = "toolkit",
|
|
@@ -139,6 +143,27 @@ class Toolkit:
|
|
|
139
143
|
logger.warning(f"Failed to create Function for: {function.__name__}")
|
|
140
144
|
raise e
|
|
141
145
|
|
|
146
|
+
@property
|
|
147
|
+
def requires_connect(self) -> bool:
|
|
148
|
+
"""Whether the toolkit requires connection management."""
|
|
149
|
+
return self._requires_connect
|
|
150
|
+
|
|
151
|
+
def connect(self) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Establish any required connections for the toolkit.
|
|
154
|
+
Override this method in subclasses that require connection management.
|
|
155
|
+
Called automatically by the Agent when _requires_connect is True.
|
|
156
|
+
"""
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
def close(self) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Close any open connections for the toolkit.
|
|
162
|
+
Override this method in subclasses that require connection management.
|
|
163
|
+
Called automatically by the Agent when _requires_connect is True.
|
|
164
|
+
"""
|
|
165
|
+
pass
|
|
166
|
+
|
|
142
167
|
def __repr__(self):
|
|
143
168
|
return f"<{self.__class__.__name__} name={self.name} functions={list(self.functions.keys())}>"
|
|
144
169
|
|
agno/tools/workflow.py
CHANGED
|
@@ -130,9 +130,13 @@ class WorkflowTools(Toolkit):
|
|
|
130
130
|
) -> str:
|
|
131
131
|
"""Use this tool to execute the workflow with the specified inputs and parameters.
|
|
132
132
|
After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
|
|
133
|
+
|
|
133
134
|
Args:
|
|
134
|
-
|
|
135
|
+
input: The input data for the workflow.
|
|
135
136
|
"""
|
|
137
|
+
if isinstance(input, dict):
|
|
138
|
+
input = RunWorkflowInput.model_validate(input)
|
|
139
|
+
|
|
136
140
|
try:
|
|
137
141
|
log_debug(f"Running workflow with input: {input.input_data}")
|
|
138
142
|
|
|
@@ -170,6 +174,9 @@ class WorkflowTools(Toolkit):
|
|
|
170
174
|
input_data: The input data for the workflow (use a `str` for a simple input)
|
|
171
175
|
additional_data: The additional data for the workflow. This is a dictionary of key-value pairs that will be passed to the workflow. E.g. {"topic": "food", "style": "Humour"}
|
|
172
176
|
"""
|
|
177
|
+
if isinstance(input, dict):
|
|
178
|
+
input = RunWorkflowInput.model_validate(input)
|
|
179
|
+
|
|
173
180
|
try:
|
|
174
181
|
log_debug(f"Running workflow with input: {input.input_data}")
|
|
175
182
|
|
agno/tools/yfinance.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any, List
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
3
|
|
|
4
4
|
from agno.tools import Toolkit
|
|
5
5
|
from agno.utils.log import log_debug
|
|
@@ -18,6 +18,7 @@ class YFinanceTools(Toolkit):
|
|
|
18
18
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
21
|
+
session: Optional[Any] = None,
|
|
21
22
|
**kwargs,
|
|
22
23
|
):
|
|
23
24
|
tools: List[Any] = [
|
|
@@ -31,7 +32,7 @@ class YFinanceTools(Toolkit):
|
|
|
31
32
|
self.get_technical_indicators,
|
|
32
33
|
self.get_historical_stock_prices,
|
|
33
34
|
]
|
|
34
|
-
|
|
35
|
+
self.session = session
|
|
35
36
|
super().__init__(name="yfinance_tools", tools=tools, **kwargs)
|
|
36
37
|
|
|
37
38
|
def get_current_stock_price(self, symbol: str) -> str:
|
|
@@ -46,7 +47,7 @@ class YFinanceTools(Toolkit):
|
|
|
46
47
|
"""
|
|
47
48
|
try:
|
|
48
49
|
log_debug(f"Fetching current price for {symbol}")
|
|
49
|
-
stock = yf.Ticker(symbol)
|
|
50
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
50
51
|
# Use "regularMarketPrice" for regular market hours, or "currentPrice" for pre/post market
|
|
51
52
|
current_price = stock.info.get("regularMarketPrice", stock.info.get("currentPrice"))
|
|
52
53
|
return f"{current_price:.4f}" if current_price else f"Could not fetch current price for {symbol}"
|
|
@@ -63,7 +64,7 @@ class YFinanceTools(Toolkit):
|
|
|
63
64
|
str: JSON containing company profile and overview.
|
|
64
65
|
"""
|
|
65
66
|
try:
|
|
66
|
-
company_info_full = yf.Ticker(symbol).info
|
|
67
|
+
company_info_full = yf.Ticker(symbol, session=self.session).info
|
|
67
68
|
if company_info_full is None:
|
|
68
69
|
return f"Could not fetch company info for {symbol}"
|
|
69
70
|
|
|
@@ -120,7 +121,7 @@ class YFinanceTools(Toolkit):
|
|
|
120
121
|
"""
|
|
121
122
|
try:
|
|
122
123
|
log_debug(f"Fetching historical prices for {symbol}")
|
|
123
|
-
stock = yf.Ticker(symbol)
|
|
124
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
124
125
|
historical_price = stock.history(period=period, interval=interval)
|
|
125
126
|
return historical_price.to_json(orient="index")
|
|
126
127
|
except Exception as e:
|
|
@@ -150,7 +151,7 @@ class YFinanceTools(Toolkit):
|
|
|
150
151
|
"""
|
|
151
152
|
try:
|
|
152
153
|
log_debug(f"Fetching fundamentals for {symbol}")
|
|
153
|
-
stock = yf.Ticker(symbol)
|
|
154
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
154
155
|
info = stock.info
|
|
155
156
|
fundamentals = {
|
|
156
157
|
"symbol": symbol,
|
|
@@ -181,7 +182,7 @@ class YFinanceTools(Toolkit):
|
|
|
181
182
|
"""
|
|
182
183
|
try:
|
|
183
184
|
log_debug(f"Fetching income statements for {symbol}")
|
|
184
|
-
stock = yf.Ticker(symbol)
|
|
185
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
185
186
|
financials = stock.financials
|
|
186
187
|
return financials.to_json(orient="index")
|
|
187
188
|
except Exception as e:
|
|
@@ -198,7 +199,7 @@ class YFinanceTools(Toolkit):
|
|
|
198
199
|
"""
|
|
199
200
|
try:
|
|
200
201
|
log_debug(f"Fetching key financial ratios for {symbol}")
|
|
201
|
-
stock = yf.Ticker(symbol)
|
|
202
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
202
203
|
key_ratios = stock.info
|
|
203
204
|
return json.dumps(key_ratios, indent=2)
|
|
204
205
|
except Exception as e:
|
|
@@ -215,7 +216,7 @@ class YFinanceTools(Toolkit):
|
|
|
215
216
|
"""
|
|
216
217
|
try:
|
|
217
218
|
log_debug(f"Fetching analyst recommendations for {symbol}")
|
|
218
|
-
stock = yf.Ticker(symbol)
|
|
219
|
+
stock = yf.Ticker(symbol, session=self.session)
|
|
219
220
|
recommendations = stock.recommendations
|
|
220
221
|
return recommendations.to_json(orient="index")
|
|
221
222
|
except Exception as e:
|
|
@@ -233,7 +234,7 @@ class YFinanceTools(Toolkit):
|
|
|
233
234
|
"""
|
|
234
235
|
try:
|
|
235
236
|
log_debug(f"Fetching company news for {symbol}")
|
|
236
|
-
news = yf.Ticker(symbol).news
|
|
237
|
+
news = yf.Ticker(symbol, session=self.session).news
|
|
237
238
|
return json.dumps(news[:num_stories], indent=2)
|
|
238
239
|
except Exception as e:
|
|
239
240
|
return f"Error fetching company news for {symbol}: {e}"
|
|
@@ -251,7 +252,7 @@ class YFinanceTools(Toolkit):
|
|
|
251
252
|
"""
|
|
252
253
|
try:
|
|
253
254
|
log_debug(f"Fetching technical indicators for {symbol}")
|
|
254
|
-
indicators = yf.Ticker(symbol).history(period=period)
|
|
255
|
+
indicators = yf.Ticker(symbol, session=self.session).history(period=period)
|
|
255
256
|
return indicators.to_json(orient="index")
|
|
256
257
|
except Exception as e:
|
|
257
258
|
return f"Error fetching technical indicators for {symbol}: {e}"
|
agno/tracing/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agno Tracing Module
|
|
3
|
+
|
|
4
|
+
This module provides OpenTelemetry-based tracing capabilities for Agno agents.
|
|
5
|
+
It uses the openinference-instrumentation-agno package for automatic instrumentation
|
|
6
|
+
and provides a custom DatabaseSpanExporter to store traces in the Agno database.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from agno.tracing.exporter import DatabaseSpanExporter
|
|
10
|
+
from agno.tracing.setup import setup_tracing
|
|
11
|
+
|
|
12
|
+
__all__ = ["DatabaseSpanExporter", "setup_tracing"]
|
agno/tracing/exporter.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom OpenTelemetry SpanExporter that writes traces to Agno database.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from typing import Dict, List, Sequence, Union
|
|
8
|
+
|
|
9
|
+
from opentelemetry.sdk.trace import ReadableSpan # type: ignore
|
|
10
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult # type: ignore
|
|
11
|
+
|
|
12
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
13
|
+
from agno.tracing.schemas import Span, create_trace_from_spans
|
|
14
|
+
from agno.utils.log import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DatabaseSpanExporter(SpanExporter):
|
|
18
|
+
"""Custom OpenTelemetry SpanExporter that writes to Agno database"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, db: Union[BaseDb, AsyncBaseDb]):
|
|
21
|
+
"""
|
|
22
|
+
Initialize the DatabaseSpanExporter.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
db: Database instance (sync or async) to store traces
|
|
26
|
+
"""
|
|
27
|
+
self.db = db
|
|
28
|
+
self._shutdown = False
|
|
29
|
+
|
|
30
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
31
|
+
"""
|
|
32
|
+
Export spans to the database.
|
|
33
|
+
|
|
34
|
+
This method:
|
|
35
|
+
1. Converts OpenTelemetry spans to Span objects
|
|
36
|
+
2. Groups spans by trace_id
|
|
37
|
+
3. Creates Trace records (one per trace_id)
|
|
38
|
+
4. Creates Span records (multiple per trace_id)
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
spans: Sequence of OpenTelemetry ReadableSpan objects
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
SpanExportResult indicating success or failure
|
|
45
|
+
"""
|
|
46
|
+
if self._shutdown:
|
|
47
|
+
logger.warning("DatabaseSpanExporter is shutdown, cannot export spans")
|
|
48
|
+
return SpanExportResult.FAILURE
|
|
49
|
+
|
|
50
|
+
if not spans:
|
|
51
|
+
return SpanExportResult.SUCCESS
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Convert OpenTelemetry spans to Span objects
|
|
55
|
+
converted_spans: List[Span] = []
|
|
56
|
+
for span in spans:
|
|
57
|
+
try:
|
|
58
|
+
converted_span = Span.from_otel_span(span)
|
|
59
|
+
converted_spans.append(converted_span)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(f"Failed to convert span {span.name}: {e}")
|
|
62
|
+
# Continue processing other spans
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if not converted_spans:
|
|
66
|
+
return SpanExportResult.SUCCESS
|
|
67
|
+
|
|
68
|
+
# Group spans by trace_id
|
|
69
|
+
spans_by_trace: Dict[str, List[Span]] = defaultdict(list)
|
|
70
|
+
for converted_span in converted_spans:
|
|
71
|
+
spans_by_trace[converted_span.trace_id].append(converted_span)
|
|
72
|
+
|
|
73
|
+
# Handle async DB
|
|
74
|
+
if isinstance(self.db, AsyncBaseDb):
|
|
75
|
+
self._export_async(spans_by_trace)
|
|
76
|
+
else:
|
|
77
|
+
# Synchronous database
|
|
78
|
+
self._export_sync(spans_by_trace)
|
|
79
|
+
|
|
80
|
+
return SpanExportResult.SUCCESS
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"Failed to export spans to database: {e}", exc_info=True)
|
|
83
|
+
return SpanExportResult.FAILURE
|
|
84
|
+
|
|
85
|
+
def _export_sync(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
86
|
+
"""Export traces and spans to synchronous database"""
|
|
87
|
+
try:
|
|
88
|
+
# Create trace and span records for each trace
|
|
89
|
+
for trace_id, spans in spans_by_trace.items():
|
|
90
|
+
# Create trace record (aggregate of all spans)
|
|
91
|
+
trace = create_trace_from_spans(spans)
|
|
92
|
+
if trace:
|
|
93
|
+
self.db.upsert_trace(trace)
|
|
94
|
+
|
|
95
|
+
# Create span records
|
|
96
|
+
self.db.create_spans(spans)
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"Failed to export sync traces: {e}", exc_info=True)
|
|
100
|
+
raise
|
|
101
|
+
|
|
102
|
+
def _export_async(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
103
|
+
"""Handle async database export"""
|
|
104
|
+
try:
|
|
105
|
+
loop = asyncio.get_event_loop()
|
|
106
|
+
if loop.is_running():
|
|
107
|
+
# We're in an async context, schedule the coroutine
|
|
108
|
+
asyncio.create_task(self._do_async_export(spans_by_trace))
|
|
109
|
+
else:
|
|
110
|
+
# No running loop, run in new loop
|
|
111
|
+
loop.run_until_complete(self._do_async_export(spans_by_trace))
|
|
112
|
+
except RuntimeError:
|
|
113
|
+
# No event loop, create new one
|
|
114
|
+
try:
|
|
115
|
+
asyncio.run(self._do_async_export(spans_by_trace))
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Failed to export async traces: {e}", exc_info=True)
|
|
118
|
+
|
|
119
|
+
async def _do_async_export(self, spans_by_trace: Dict[str, List[Span]]) -> None:
|
|
120
|
+
"""Actually perform the async export"""
|
|
121
|
+
try:
|
|
122
|
+
# Create trace and span records for each trace
|
|
123
|
+
for trace_id, spans in spans_by_trace.items():
|
|
124
|
+
# Create trace record (aggregate of all spans)
|
|
125
|
+
trace = create_trace_from_spans(spans)
|
|
126
|
+
if trace:
|
|
127
|
+
create_trace_result = self.db.upsert_trace(trace)
|
|
128
|
+
if create_trace_result is not None:
|
|
129
|
+
await create_trace_result
|
|
130
|
+
|
|
131
|
+
# Create span records
|
|
132
|
+
create_spans_result = self.db.create_spans(spans)
|
|
133
|
+
if create_spans_result is not None:
|
|
134
|
+
await create_spans_result
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Failed to do async export: {e}", exc_info=True)
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
def shutdown(self) -> None:
|
|
141
|
+
"""Shutdown the exporter"""
|
|
142
|
+
self._shutdown = True
|
|
143
|
+
logger.debug("DatabaseSpanExporter shutdown")
|
|
144
|
+
|
|
145
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Force flush any pending spans.
|
|
148
|
+
|
|
149
|
+
Since we write immediately to the database, this is a no-op.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
timeout_millis: Timeout in milliseconds
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if flush was successful
|
|
156
|
+
"""
|
|
157
|
+
return True
|