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.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.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/whatsapp.py CHANGED
@@ -16,7 +16,7 @@ class WhatsAppTools(Toolkit):
16
16
  self,
17
17
  access_token: Optional[str] = None,
18
18
  phone_number_id: Optional[str] = None,
19
- version: str = "v22.0",
19
+ version: Optional[str] = None,
20
20
  recipient_waid: Optional[str] = None,
21
21
  async_mode: bool = False,
22
22
  ):
agno/tools/workflow.py ADDED
@@ -0,0 +1,278 @@
1
+ import json
2
+ from textwrap import dedent
3
+ from typing import Any, Dict, Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from agno.tools import Toolkit
8
+ from agno.utils.log import log_debug, log_error
9
+ from agno.workflow.workflow import Workflow, WorkflowRunOutput
10
+
11
+
12
+ class RunWorkflowInput(BaseModel):
13
+ input_data: str = Field(..., description="The input data for the workflow.")
14
+ additional_data: Optional[Dict[str, Any]] = Field(default=None, description="The additional data for the workflow.")
15
+
16
+
17
+ class WorkflowTools(Toolkit):
18
+ def __init__(
19
+ self,
20
+ workflow: Workflow,
21
+ enable_run_workflow: bool = True,
22
+ enable_think: bool = False,
23
+ enable_analyze: bool = False,
24
+ all: bool = False,
25
+ instructions: Optional[str] = None,
26
+ add_instructions: bool = True,
27
+ add_few_shot: bool = False,
28
+ few_shot_examples: Optional[str] = None,
29
+ async_mode: bool = False,
30
+ **kwargs,
31
+ ):
32
+ # Add instructions for using this toolkit
33
+ if instructions is None:
34
+ self.instructions = self.DEFAULT_INSTRUCTIONS
35
+ if add_few_shot:
36
+ if few_shot_examples is not None:
37
+ self.instructions += "\n" + few_shot_examples
38
+ else:
39
+ self.instructions = instructions
40
+
41
+ # The workflow to execute
42
+ self.workflow: Workflow = workflow
43
+
44
+ super().__init__(
45
+ name="workflow_tools",
46
+ instructions=self.instructions,
47
+ add_instructions=add_instructions,
48
+ auto_register=False,
49
+ **kwargs,
50
+ )
51
+
52
+ if enable_think or all:
53
+ if async_mode:
54
+ self.register(self.async_think, name="think")
55
+ else:
56
+ self.register(self.think, name="think")
57
+ if enable_run_workflow or all:
58
+ if async_mode:
59
+ self.register(self.async_run_workflow, name="run_workflow")
60
+ else:
61
+ self.register(self.run_workflow, name="run_workflow")
62
+ if enable_analyze or all:
63
+ if async_mode:
64
+ self.register(self.async_analyze, name="analyze")
65
+ else:
66
+ self.register(self.analyze, name="analyze")
67
+
68
+ def think(self, session_state: Dict[str, Any], thought: str) -> str:
69
+ """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
70
+ Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
71
+ You should use this tool as frequently as needed.
72
+ Args:
73
+ thought: Your thought process and reasoning about workflow execution.
74
+ """
75
+ try:
76
+ log_debug(f"Workflow Thought: {thought}")
77
+
78
+ # Add the thought to the session state
79
+ if session_state is None:
80
+ session_state = {}
81
+ if "workflow_thoughts" not in session_state:
82
+ session_state["workflow_thoughts"] = []
83
+ session_state["workflow_thoughts"].append(thought)
84
+
85
+ # Return the full log of thoughts and the new thought
86
+ thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
87
+ formatted_thoughts = dedent(
88
+ f"""Workflow Thoughts:
89
+ {thoughts}
90
+ """
91
+ ).strip()
92
+ return formatted_thoughts
93
+ except Exception as e:
94
+ log_error(f"Error recording workflow thought: {e}")
95
+ return f"Error recording workflow thought: {e}"
96
+
97
+ async def async_think(self, session_state: Dict[str, Any], thought: str) -> str:
98
+ """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
99
+ Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
100
+ You should use this tool as frequently as needed.
101
+ Args:
102
+ thought: Your thought process and reasoning about workflow execution.
103
+ """
104
+ try:
105
+ log_debug(f"Workflow Thought: {thought}")
106
+
107
+ # Add the thought to the session state
108
+ if session_state is None:
109
+ session_state = {}
110
+ if "workflow_thoughts" not in session_state:
111
+ session_state["workflow_thoughts"] = []
112
+ session_state["workflow_thoughts"].append(thought)
113
+
114
+ # Return the full log of thoughts and the new thought
115
+ thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
116
+ formatted_thoughts = dedent(
117
+ f"""Workflow Thoughts:
118
+ {thoughts}
119
+ """
120
+ ).strip()
121
+ return formatted_thoughts
122
+ except Exception as e:
123
+ log_error(f"Error recording workflow thought: {e}")
124
+ return f"Error recording workflow thought: {e}"
125
+
126
+ def run_workflow(
127
+ self,
128
+ session_state: Dict[str, Any],
129
+ input: RunWorkflowInput,
130
+ ) -> str:
131
+ """Use this tool to execute the workflow with the specified inputs and parameters.
132
+ After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
133
+ Args:
134
+ input_data: The input data for the workflow.
135
+ """
136
+ try:
137
+ log_debug(f"Running workflow with input: {input.input_data}")
138
+
139
+ user_id = session_state.get("current_user_id")
140
+ session_id = session_state.get("current_session_id")
141
+
142
+ # Execute the workflow
143
+ result: WorkflowRunOutput = self.workflow.run(
144
+ input=input.input_data,
145
+ user_id=user_id,
146
+ session_id=session_id,
147
+ session_state=session_state,
148
+ additional_data=input.additional_data,
149
+ )
150
+
151
+ if "workflow_results" not in session_state:
152
+ session_state["workflow_results"] = []
153
+
154
+ session_state["workflow_results"].append(result.to_dict())
155
+
156
+ return json.dumps(result.to_dict(), indent=2)
157
+
158
+ except Exception as e:
159
+ log_error(f"Error running workflow: {e}")
160
+ return f"Error running workflow: {e}"
161
+
162
+ async def async_run_workflow(
163
+ self,
164
+ session_state: Dict[str, Any],
165
+ input: RunWorkflowInput,
166
+ ) -> str:
167
+ """Use this tool to execute the workflow with the specified inputs and parameters.
168
+ After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
169
+ Args:
170
+ input_data: The input data for the workflow (use a `str` for a simple input)
171
+ 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
+ """
173
+ try:
174
+ log_debug(f"Running workflow with input: {input.input_data}")
175
+
176
+ user_id = session_state.get("current_user_id")
177
+ session_id = session_state.get("current_session_id")
178
+
179
+ # Execute the workflow
180
+ result: WorkflowRunOutput = await self.workflow.arun(
181
+ input=input.input_data,
182
+ user_id=user_id,
183
+ session_id=session_id,
184
+ session_state=session_state,
185
+ additional_data=input.additional_data,
186
+ )
187
+
188
+ if "workflow_results" not in session_state:
189
+ session_state["workflow_results"] = []
190
+
191
+ session_state["workflow_results"].append(result.to_dict())
192
+
193
+ return json.dumps(result.to_dict(), indent=2)
194
+
195
+ except Exception as e:
196
+ log_error(f"Error running workflow: {e}")
197
+ return f"Error running workflow: {e}"
198
+
199
+ def analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
200
+ """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
201
+ If not, go back to "Think" or "Run" with refined inputs or parameters.
202
+ Args:
203
+ analysis: Your analysis of the workflow execution results.
204
+ """
205
+ try:
206
+ log_debug(f"Workflow Analysis: {analysis}")
207
+
208
+ # Add the analysis to the session state
209
+ if session_state is None:
210
+ session_state = {}
211
+ if "workflow_analysis" not in session_state:
212
+ session_state["workflow_analysis"] = []
213
+ session_state["workflow_analysis"].append(analysis)
214
+
215
+ # Return the full log of analysis and the new analysis
216
+ analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
217
+ formatted_analysis = dedent(
218
+ f"""Workflow Analysis:
219
+ {analysis_log}
220
+ """
221
+ ).strip()
222
+ return formatted_analysis
223
+ except Exception as e:
224
+ log_error(f"Error recording workflow analysis: {e}")
225
+ return f"Error recording workflow analysis: {e}"
226
+
227
+ async def async_analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
228
+ """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
229
+ If not, go back to "Think" or "Run" with refined inputs or parameters.
230
+ Args:
231
+ analysis: Your analysis of the workflow execution results.
232
+ """
233
+ try:
234
+ log_debug(f"Workflow Analysis: {analysis}")
235
+
236
+ # Add the analysis to the session state
237
+ if session_state is None:
238
+ session_state = {}
239
+ if "workflow_analysis" not in session_state:
240
+ session_state["workflow_analysis"] = []
241
+ session_state["workflow_analysis"].append(analysis)
242
+
243
+ # Return the full log of analysis and the new analysis
244
+ analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
245
+ formatted_analysis = dedent(
246
+ f"""Workflow Analysis:
247
+ {analysis_log}
248
+ """
249
+ ).strip()
250
+ return formatted_analysis
251
+ except Exception as e:
252
+ log_error(f"Error recording workflow analysis: {e}")
253
+ return f"Error recording workflow analysis: {e}"
254
+
255
+ DEFAULT_INSTRUCTIONS = dedent("""\
256
+ You have access to the Think, Run Workflow, and Analyze tools that will help you execute workflows and analyze their results. Use these tools as frequently as needed to successfully complete workflow-based tasks.
257
+ ## How to use the Think, Run Workflow, and Analyze tools:
258
+
259
+ 1. **Think**
260
+ - Purpose: A scratchpad for planning workflow execution, brainstorming inputs, and refining your approach. You never reveal your "Think" content to the user.
261
+ - Usage: Call `think` whenever you need to figure out what workflow inputs to use, analyze requirements, or decide on execution strategy before (or after) you run the workflow.
262
+ 2. **Run Workflow**
263
+ - Purpose: Executes the workflow with specified inputs and parameters.
264
+ - Usage: Call `run_workflow` with appropriate input data whenever you want to execute the workflow.
265
+ - For all workflows, start with simple inputs and gradually increase complexity
266
+ 3. **Analyze**
267
+ - Purpose: Evaluate whether the workflow execution results are correct and sufficient. If not, go back to "Think" or "Run Workflow" with refined inputs.
268
+ - Usage: Call `analyze` after getting workflow results to verify the quality and correctness of the execution. Consider:
269
+ - Completeness: Did the workflow complete all expected steps?
270
+ - Quality: Are the results accurate and meet the requirements?
271
+ - Errors: Were there any failures or unexpected behaviors?
272
+ **Important Guidelines**:
273
+ - Do not include your internal chain-of-thought in direct user responses.
274
+ - Use "Think" to reason internally. These notes are never exposed to the user.
275
+ - When you provide a final answer to the user, be clear, concise, and based on the workflow results.
276
+ - If workflow execution fails or produces unexpected results, acknowledge limitations and explain what went wrong.
277
+ - Synthesize information from multiple workflow runs if you execute the workflow several times with different inputs.\
278
+ """)
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}"