agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/os/interfaces/base.py
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
75
|
+
return SlackEventResponse(status="ok")
|
|
41
76
|
|
|
42
77
|
async def _process_slack_event(event: dict):
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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,
|
|
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=
|
|
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
|
|
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__(
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
193
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_message)
|
|
187
194
|
else:
|
|
188
|
-
await
|
|
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
|
|
207
|
+
await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_batch)
|
|
201
208
|
else:
|
|
202
|
-
await
|
|
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__(
|
|
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
|
|
21
|
-
raise ValueError("Whatsapp requires an agent
|
|
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
|
|
24
|
-
#
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
memory=
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
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
|