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
@@ -1,18 +1,22 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Optional
2
+ from typing import List, Optional
3
3
 
4
4
  from fastapi import APIRouter
5
5
 
6
6
  from agno.agent import Agent
7
7
  from agno.team import Team
8
+ from agno.workflow.workflow import Workflow
8
9
 
9
10
 
10
11
  class BaseInterface(ABC):
11
12
  type: str
12
13
  version: str = "1.0"
13
- router_prefix: str = ""
14
14
  agent: Optional[Agent] = None
15
15
  team: Optional[Team] = None
16
+ workflow: Optional[Workflow] = None
17
+
18
+ prefix: str
19
+ tags: List[str]
16
20
 
17
21
  router: APIRouter
18
22
 
@@ -1,16 +1,51 @@
1
- from typing import Optional
1
+ from typing import Optional, Union
2
2
 
3
3
  from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
4
+ from pydantic import BaseModel, Field
4
5
 
5
6
  from agno.agent.agent import Agent
6
7
  from agno.os.interfaces.slack.security import verify_slack_signature
7
8
  from agno.team.team import Team
8
9
  from agno.tools.slack import SlackTools
9
10
  from agno.utils.log import log_info
11
+ from agno.workflow.workflow import Workflow
10
12
 
11
13
 
12
- def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Optional[Team] = None) -> APIRouter:
13
- @router.post("/slack/events")
14
+ class SlackEventResponse(BaseModel):
15
+ """Response model for Slack event processing"""
16
+
17
+ status: str = Field(default="ok", description="Processing status")
18
+
19
+
20
+ class SlackChallengeResponse(BaseModel):
21
+ """Response model for Slack URL verification challenge"""
22
+
23
+ challenge: str = Field(description="Challenge string to echo back to Slack")
24
+
25
+
26
+ def attach_routes(
27
+ router: APIRouter,
28
+ agent: Optional[Agent] = None,
29
+ team: Optional[Team] = None,
30
+ workflow: Optional[Workflow] = None,
31
+ reply_to_mentions_only: bool = True,
32
+ ) -> APIRouter:
33
+ # Determine entity type for documentation
34
+ entity_type = "agent" if agent else "team" if team else "workflow" if workflow else "unknown"
35
+
36
+ @router.post(
37
+ "/events",
38
+ operation_id=f"slack_events_{entity_type}",
39
+ name="slack_events",
40
+ description="Process incoming Slack events",
41
+ response_model=Union[SlackChallengeResponse, SlackEventResponse],
42
+ response_model_exclude_none=True,
43
+ responses={
44
+ 200: {"description": "Event processed successfully"},
45
+ 400: {"description": "Missing Slack headers"},
46
+ 403: {"description": "Invalid Slack signature"},
47
+ },
48
+ )
14
49
  async def slack_events(request: Request, background_tasks: BackgroundTasks):
15
50
  body = await request.body()
16
51
  timestamp = request.headers.get("X-Slack-Request-Timestamp")
@@ -26,7 +61,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
26
61
 
27
62
  # Handle URL verification
28
63
  if data.get("type") == "url_verification":
29
- return {"challenge": data.get("challenge")}
64
+ return SlackChallengeResponse(challenge=data.get("challenge"))
30
65
 
31
66
  # Process other event types (e.g., message events) asynchronously
32
67
  if "event" in data:
@@ -37,31 +72,54 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
37
72
  else:
38
73
  background_tasks.add_task(_process_slack_event, event)
39
74
 
40
- return {"status": "ok"}
75
+ return SlackEventResponse(status="ok")
41
76
 
42
77
  async def _process_slack_event(event: dict):
43
- if event.get("type") == "message":
44
- user = None
45
- message_text = event.get("text", "")
46
- channel_id = event.get("channel", "")
47
- user = event.get("user")
48
- if event.get("thread_ts"):
49
- ts = event.get("thread_ts", "")
50
- else:
51
- ts = event.get("ts", "")
78
+ event_type = event.get("type")
52
79
 
53
- # Use the timestamp as the session id, so that each thread is a separate session
54
- session_id = ts
80
+ # Only handle app_mention and message events
81
+ if event_type not in ("app_mention", "message"):
82
+ return
83
+
84
+ channel_type = event.get("channel_type", "")
55
85
 
56
- if agent:
57
- response = await agent.arun(message_text, user_id=user if user else None, session_id=session_id)
58
- elif team:
59
- response = await team.arun(message_text, user_id=user if user else None, session_id=session_id) # type: ignore
86
+ # Handle duplicate replies
87
+ if not reply_to_mentions_only and event_type == "app_mention":
88
+ return
60
89
 
61
- if response.reasoning_content:
90
+ # If reply_to_mentions_only is True, ignore every message that is not a DM
91
+ if reply_to_mentions_only and event_type == "message" and channel_type != "im":
92
+ return
93
+
94
+ # Extract event data
95
+ user = None
96
+ message_text = event.get("text", "")
97
+ channel_id = event.get("channel", "")
98
+ user = event.get("user")
99
+ if event.get("thread_ts"):
100
+ ts = event.get("thread_ts", "")
101
+ else:
102
+ ts = event.get("ts", "")
103
+
104
+ # Use the timestamp as the session id, so that each thread is a separate session
105
+ session_id = ts
106
+
107
+ if agent:
108
+ response = await agent.arun(message_text, user_id=user, session_id=session_id)
109
+ elif team:
110
+ response = await team.arun(message_text, user_id=user, session_id=session_id) # type: ignore
111
+ elif workflow:
112
+ response = await workflow.arun(message_text, user_id=user, session_id=session_id) # type: ignore
113
+
114
+ if response:
115
+ if hasattr(response, "reasoning_content") and response.reasoning_content:
62
116
  _send_slack_message(
63
- channel=channel_id, message=f"Reasoning: \n{response.reasoning_content}", thread_ts=ts, italics=True
117
+ channel=channel_id,
118
+ message=f"Reasoning: \n{response.reasoning_content}",
119
+ thread_ts=ts,
120
+ italics=True,
64
121
  )
122
+
65
123
  _send_slack_message(channel=channel_id, message=response.content or "", thread_ts=ts)
66
124
 
67
125
  def _send_slack_message(channel: str, thread_ts: str, message: str, italics: bool = False):
@@ -85,6 +143,6 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
85
143
  formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
86
144
  SlackTools().send_message_thread(channel=channel, text=formatted_batch or "", thread_ts=thread_ts)
87
145
  else:
88
- SlackTools().send_message_thread(channel=channel, text=message or "", thread_ts=thread_ts)
146
+ SlackTools().send_message_thread(channel=channel, text=batch_message or "", thread_ts=thread_ts)
89
147
 
90
148
  return router
@@ -1,5 +1,4 @@
1
- import logging
2
- from typing import Optional
1
+ from typing import List, Optional
3
2
 
4
3
  from fastapi.routing import APIRouter
5
4
 
@@ -7,8 +6,7 @@ from agno.agent.agent import Agent
7
6
  from agno.os.interfaces.base import BaseInterface
8
7
  from agno.os.interfaces.slack.router import attach_routes
9
8
  from agno.team.team import Team
10
-
11
- logger = logging.getLogger(__name__)
9
+ from agno.workflow.workflow import Workflow
12
10
 
13
11
 
14
12
  class Slack(BaseInterface):
@@ -16,17 +14,34 @@ class Slack(BaseInterface):
16
14
 
17
15
  router: APIRouter
18
16
 
19
- def __init__(self, agent: Optional[Agent] = None, team: Optional[Team] = None):
17
+ def __init__(
18
+ self,
19
+ agent: Optional[Agent] = None,
20
+ team: Optional[Team] = None,
21
+ workflow: Optional[Workflow] = None,
22
+ prefix: str = "/slack",
23
+ tags: Optional[List[str]] = None,
24
+ reply_to_mentions_only: bool = True,
25
+ ):
20
26
  self.agent = agent
21
27
  self.team = team
22
-
23
- if not self.agent and not self.team:
24
- raise ValueError("Slack requires an agent and a team")
25
-
26
- def get_router(self, **kwargs) -> APIRouter:
27
- # Cannot be overridden
28
- self.router = APIRouter(prefix="/slack", tags=["Slack"])
29
-
30
- self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
28
+ self.workflow = workflow
29
+ self.prefix = prefix
30
+ self.tags = tags or ["Slack"]
31
+ self.reply_to_mentions_only = reply_to_mentions_only
32
+
33
+ if not (self.agent or self.team or self.workflow):
34
+ raise ValueError("Slack requires an agent, team or workflow")
35
+
36
+ def get_router(self) -> APIRouter:
37
+ self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
38
+
39
+ self.router = attach_routes(
40
+ router=self.router,
41
+ agent=self.agent,
42
+ team=self.team,
43
+ workflow=self.workflow,
44
+ reply_to_mentions_only=self.reply_to_mentions_only,
45
+ )
31
46
 
32
47
  return self.router
@@ -19,6 +19,9 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
19
19
  if agent is None and team is None:
20
20
  raise ValueError("Either agent or team must be provided.")
21
21
 
22
+ # Create WhatsApp tools instance once for reuse
23
+ whatsapp_tools = WhatsAppTools(async_mode=True)
24
+
22
25
  @router.get("/status")
23
26
  async def status():
24
27
  return {"status": "available"}
@@ -120,6 +123,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
120
123
  response = await agent.arun(
121
124
  message_text,
122
125
  user_id=phone_number,
126
+ session_id=f"wa:{phone_number}",
123
127
  images=[Image(content=await get_media_async(message_image))] if message_image else None,
124
128
  files=[File(content=await get_media_async(message_doc))] if message_doc else None,
125
129
  videos=[Video(content=await get_media_async(message_video))] if message_video else None,
@@ -129,6 +133,7 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
129
133
  response = await team.arun( # type: ignore
130
134
  message_text,
131
135
  user_id=phone_number,
136
+ session_id=f"wa:{phone_number}",
132
137
  files=[File(content=await get_media_async(message_doc))] if message_doc else None,
133
138
  images=[Image(content=await get_media_async(message_image))] if message_image else None,
134
139
  videos=[Video(content=await get_media_async(message_video))] if message_video else None,
@@ -167,6 +172,8 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
167
172
  )
168
173
  await _send_whatsapp_message(phone_number, response.content) # type: ignore
169
174
  await _send_whatsapp_message(phone_number, response.content) # type: ignore
175
+ else:
176
+ await _send_whatsapp_message(phone_number, response.content) # type: ignore
170
177
 
171
178
  except Exception as e:
172
179
  log_error(f"Error processing message: {str(e)}")
@@ -183,9 +190,9 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
183
190
  if italics:
184
191
  # Handle multi-line messages by making each line italic
185
192
  formatted_message = "\n".join([f"_{line}_" for line in message.split("\n")])
186
- await WhatsAppTools().send_text_message_async(recipient=recipient, text=formatted_message)
193
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_message)
187
194
  else:
188
- await WhatsAppTools().send_text_message_async(recipient=recipient, text=message)
195
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=message)
189
196
  return
190
197
 
191
198
  # Split message into batches of 4000 characters (WhatsApp message limit is 4096)
@@ -197,8 +204,8 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
197
204
  if italics:
198
205
  # Handle multi-line messages by making each line italic
199
206
  formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
200
- await WhatsAppTools().send_text_message_async(recipient=recipient, text=formatted_batch)
207
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_batch)
201
208
  else:
202
- await WhatsAppTools().send_text_message_async(recipient=recipient, text=batch_message)
209
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=batch_message)
203
210
 
204
211
  return router
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import List, Optional
2
2
 
3
3
  from fastapi.routing import APIRouter
4
4
 
@@ -13,16 +13,23 @@ class Whatsapp(BaseInterface):
13
13
 
14
14
  router: APIRouter
15
15
 
16
- def __init__(self, agent: Optional[Agent] = None, team: Optional[Team] = None):
16
+ def __init__(
17
+ self,
18
+ agent: Optional[Agent] = None,
19
+ team: Optional[Team] = None,
20
+ prefix: str = "/whatsapp",
21
+ tags: Optional[List[str]] = None,
22
+ ):
17
23
  self.agent = agent
18
24
  self.team = team
25
+ self.prefix = prefix
26
+ self.tags = tags or ["Whatsapp"]
19
27
 
20
- if not self.agent and not self.team:
21
- raise ValueError("Whatsapp requires an agent and a team")
28
+ if not (self.agent or self.team):
29
+ raise ValueError("Whatsapp requires an agent or a team")
22
30
 
23
- def get_router(self, **kwargs) -> APIRouter:
24
- # Cannot be overridden
25
- self.router = APIRouter(prefix="/whatsapp", tags=["Whatsapp"])
31
+ def get_router(self) -> APIRouter:
32
+ self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
26
33
 
27
34
  self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
28
35
 
agno/os/mcp.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Router for MCP interface providing Model Context Protocol endpoints."""
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING, List, Optional
4
+ from typing import TYPE_CHECKING, List, Optional, cast
5
5
  from uuid import uuid4
6
6
 
7
7
  from fastmcp import FastMCP
@@ -9,7 +9,7 @@ from fastmcp.server.http import (
9
9
  StarletteWithLifespan,
10
10
  )
11
11
 
12
- from agno.db.base import SessionType
12
+ from agno.db.base import AsyncBaseDb, SessionType
13
13
  from agno.db.schemas import UserMemory
14
14
  from agno.os.routers.memory.schemas import (
15
15
  UserMemorySchema,
@@ -54,10 +54,10 @@ def get_mcp_server(
54
54
  ) # type: ignore
55
55
  async def config() -> ConfigResponse:
56
56
  return ConfigResponse(
57
- os_id=os.os_id or "AgentOS",
57
+ os_id=os.id or "AgentOS",
58
58
  description=os.description,
59
59
  available_models=os.config.available_models if os.config else [],
60
- databases=[db.id for db in os.dbs.values()],
60
+ databases=[db.id for db_list in os.dbs.values() for db in db_list],
61
61
  chat=os.config.chat if os.config else None,
62
62
  session=os._get_session_config(),
63
63
  memory=os._get_memory_config(),
@@ -68,7 +68,7 @@ def get_mcp_server(
68
68
  teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
69
69
  workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],
70
70
  interfaces=[
71
- InterfaceResponse(type=interface.type, version=interface.version, route=interface.router_prefix)
71
+ InterfaceResponse(type=interface.type, version=interface.version, route=interface.prefix)
72
72
  for interface in os.interfaces
73
73
  ],
74
74
  )
@@ -78,21 +78,21 @@ def get_mcp_server(
78
78
  agent = get_agent_by_id(agent_id, os.agents)
79
79
  if agent is None:
80
80
  raise Exception(f"Agent {agent_id} not found")
81
- return agent.run(message)
81
+ return await agent.arun(message)
82
82
 
83
83
  @mcp.tool(name="run_team", description="Run a team", tags={"core"}) # type: ignore
84
84
  async def run_team(team_id: str, message: str) -> TeamRunOutput:
85
85
  team = get_team_by_id(team_id, os.teams)
86
86
  if team is None:
87
87
  raise Exception(f"Team {team_id} not found")
88
- return team.run(message)
88
+ return await team.arun(message)
89
89
 
90
90
  @mcp.tool(name="run_workflow", description="Run a workflow", tags={"core"}) # type: ignore
91
91
  async def run_workflow(workflow_id: str, message: str) -> WorkflowRunOutput:
92
92
  workflow = get_workflow_by_id(workflow_id, os.workflows)
93
93
  if workflow is None:
94
94
  raise Exception(f"Workflow {workflow_id} not found")
95
- return workflow.run(message)
95
+ return await workflow.arun(message)
96
96
 
97
97
  # Session Management Tools
98
98
  @mcp.tool(name="get_sessions_for_agent", description="Get list of sessions for an agent", tags={"session"}) # type: ignore
@@ -103,15 +103,26 @@ def get_mcp_server(
103
103
  sort_by: str = "created_at",
104
104
  sort_order: str = "desc",
105
105
  ):
106
- db = get_db(os.dbs, db_id)
107
- sessions, _ = db.get_sessions(
108
- session_type=SessionType.AGENT,
109
- component_id=agent_id,
110
- user_id=user_id,
111
- sort_by=sort_by,
112
- sort_order=sort_order,
113
- deserialize=False,
114
- )
106
+ db = await get_db(os.dbs, db_id)
107
+ if isinstance(db, AsyncBaseDb):
108
+ db = cast(AsyncBaseDb, db)
109
+ sessions = await db.get_sessions(
110
+ session_type=SessionType.AGENT,
111
+ component_id=agent_id,
112
+ user_id=user_id,
113
+ sort_by=sort_by,
114
+ sort_order=sort_order,
115
+ deserialize=False,
116
+ )
117
+ else:
118
+ sessions = db.get_sessions(
119
+ session_type=SessionType.AGENT,
120
+ component_id=agent_id,
121
+ user_id=user_id,
122
+ sort_by=sort_by,
123
+ sort_order=sort_order,
124
+ deserialize=False,
125
+ )
115
126
 
116
127
  return {
117
128
  "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
@@ -125,15 +136,26 @@ def get_mcp_server(
125
136
  sort_by: str = "created_at",
126
137
  sort_order: str = "desc",
127
138
  ):
128
- db = get_db(os.dbs, db_id)
129
- sessions, _ = db.get_sessions(
130
- session_type=SessionType.TEAM,
131
- component_id=team_id,
132
- user_id=user_id,
133
- sort_by=sort_by,
134
- sort_order=sort_order,
135
- deserialize=False,
136
- )
139
+ db = await get_db(os.dbs, db_id)
140
+ if isinstance(db, AsyncBaseDb):
141
+ db = cast(AsyncBaseDb, db)
142
+ sessions = await db.get_sessions(
143
+ session_type=SessionType.TEAM,
144
+ component_id=team_id,
145
+ user_id=user_id,
146
+ sort_by=sort_by,
147
+ sort_order=sort_order,
148
+ deserialize=False,
149
+ )
150
+ else:
151
+ sessions = db.get_sessions(
152
+ session_type=SessionType.TEAM,
153
+ component_id=team_id,
154
+ user_id=user_id,
155
+ sort_by=sort_by,
156
+ sort_order=sort_order,
157
+ deserialize=False,
158
+ )
137
159
 
138
160
  return {
139
161
  "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
@@ -147,15 +169,26 @@ def get_mcp_server(
147
169
  sort_by: str = "created_at",
148
170
  sort_order: str = "desc",
149
171
  ):
150
- db = get_db(os.dbs, db_id)
151
- sessions, _ = db.get_sessions(
152
- session_type=SessionType.WORKFLOW,
153
- component_id=workflow_id,
154
- user_id=user_id,
155
- sort_by=sort_by,
156
- sort_order=sort_order,
157
- deserialize=False,
158
- )
172
+ db = await get_db(os.dbs, db_id)
173
+ if isinstance(db, AsyncBaseDb):
174
+ db = cast(AsyncBaseDb, db)
175
+ sessions = await db.get_sessions(
176
+ session_type=SessionType.WORKFLOW,
177
+ component_id=workflow_id,
178
+ user_id=user_id,
179
+ sort_by=sort_by,
180
+ sort_order=sort_order,
181
+ deserialize=False,
182
+ )
183
+ else:
184
+ sessions = db.get_sessions(
185
+ session_type=SessionType.WORKFLOW,
186
+ component_id=workflow_id,
187
+ user_id=user_id,
188
+ sort_by=sort_by,
189
+ sort_order=sort_order,
190
+ deserialize=False,
191
+ )
159
192
 
160
193
  return {
161
194
  "data": [SessionSchema.from_dict(session) for session in sessions], # type: ignore
@@ -169,7 +202,7 @@ def get_mcp_server(
169
202
  user_id: str,
170
203
  topics: Optional[List[str]] = None,
171
204
  ) -> UserMemorySchema:
172
- db = get_db(os.dbs, db_id)
205
+ db = await get_db(os.dbs, db_id)
173
206
  user_memory = db.upsert_user_memory(
174
207
  memory=UserMemory(
175
208
  memory_id=str(uuid4()),
@@ -191,13 +224,22 @@ def get_mcp_server(
191
224
  sort_order: str = "desc",
192
225
  db_id: Optional[str] = None,
193
226
  ):
194
- db = get_db(os.dbs, db_id)
195
- user_memories, _ = db.get_user_memories(
196
- user_id=user_id,
197
- sort_by=sort_by,
198
- sort_order=sort_order,
199
- deserialize=False,
200
- )
227
+ db = await get_db(os.dbs, db_id)
228
+ if isinstance(db, AsyncBaseDb):
229
+ db = cast(AsyncBaseDb, db)
230
+ user_memories = await db.get_user_memories(
231
+ user_id=user_id,
232
+ sort_by=sort_by,
233
+ sort_order=sort_order,
234
+ deserialize=False,
235
+ )
236
+ else:
237
+ user_memories = db.get_user_memories(
238
+ user_id=user_id,
239
+ sort_by=sort_by,
240
+ sort_order=sort_order,
241
+ deserialize=False,
242
+ )
201
243
  return {
202
244
  "data": [UserMemorySchema.from_dict(user_memory) for user_memory in user_memories], # type: ignore
203
245
  }
@@ -209,15 +251,26 @@ def get_mcp_server(
209
251
  memory: str,
210
252
  user_id: str,
211
253
  ) -> UserMemorySchema:
212
- db = get_db(os.dbs, db_id)
213
- user_memory = db.upsert_user_memory(
214
- memory=UserMemory(
215
- memory_id=memory_id,
216
- memory=memory,
217
- user_id=user_id,
218
- ),
219
- deserialize=False,
220
- )
254
+ db = await get_db(os.dbs, db_id)
255
+ if isinstance(db, AsyncBaseDb):
256
+ db = cast(AsyncBaseDb, db)
257
+ user_memory = await db.upsert_user_memory(
258
+ memory=UserMemory(
259
+ memory_id=memory_id,
260
+ memory=memory,
261
+ user_id=user_id,
262
+ ),
263
+ deserialize=False,
264
+ )
265
+ else:
266
+ user_memory = db.upsert_user_memory(
267
+ memory=UserMemory(
268
+ memory_id=memory_id,
269
+ memory=memory,
270
+ user_id=user_id,
271
+ ),
272
+ deserialize=False,
273
+ )
221
274
  if not user_memory:
222
275
  raise Exception("Failed to update memory")
223
276
 
@@ -228,8 +281,12 @@ def get_mcp_server(
228
281
  db_id: str,
229
282
  memory_id: str,
230
283
  ) -> None:
231
- db = get_db(os.dbs, db_id)
232
- db.delete_user_memory(memory_id=memory_id)
284
+ db = await get_db(os.dbs, db_id)
285
+ if isinstance(db, AsyncBaseDb):
286
+ db = cast(AsyncBaseDb, db)
287
+ await db.delete_user_memory(memory_id=memory_id)
288
+ else:
289
+ db.delete_user_memory(memory_id=memory_id)
233
290
 
234
291
  mcp_app = mcp.http_app(path="/mcp")
235
292
  return mcp_app
@@ -0,0 +1,7 @@
1
+ from agno.os.middleware.jwt import (
2
+ JWTMiddleware,
3
+ )
4
+
5
+ __all__ = [
6
+ "JWTMiddleware",
7
+ ]