agno 2.2.13__py3-none-any.whl → 2.4.3__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/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/os/interfaces/a2a/router.py
CHANGED
|
@@ -9,36 +9,633 @@ from fastapi.routing import APIRouter
|
|
|
9
9
|
from typing_extensions import List
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
|
-
from a2a.types import
|
|
12
|
+
from a2a.types import (
|
|
13
|
+
AgentCapabilities,
|
|
14
|
+
AgentCard,
|
|
15
|
+
AgentSkill,
|
|
16
|
+
SendMessageSuccessResponse,
|
|
17
|
+
Task,
|
|
18
|
+
TaskState,
|
|
19
|
+
TaskStatus,
|
|
20
|
+
)
|
|
13
21
|
except ImportError as e:
|
|
14
22
|
raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a-sdk`") from e
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
import warnings
|
|
25
|
+
|
|
26
|
+
from agno.agent import Agent, RemoteAgent
|
|
17
27
|
from agno.os.interfaces.a2a.utils import (
|
|
18
28
|
map_a2a_request_to_run_input,
|
|
19
29
|
map_run_output_to_a2a_task,
|
|
20
30
|
stream_a2a_response_with_error_handling,
|
|
21
31
|
)
|
|
22
|
-
from agno.os.
|
|
23
|
-
from agno.
|
|
24
|
-
from agno.
|
|
25
|
-
from agno.workflow import Workflow
|
|
32
|
+
from agno.os.utils import get_agent_by_id, get_request_kwargs, get_team_by_id, get_workflow_by_id
|
|
33
|
+
from agno.team import RemoteTeam, Team
|
|
34
|
+
from agno.workflow import RemoteWorkflow, Workflow
|
|
26
35
|
|
|
27
36
|
|
|
28
37
|
def attach_routes(
|
|
29
38
|
router: APIRouter,
|
|
30
|
-
agents: Optional[List[Agent]] = None,
|
|
31
|
-
teams: Optional[List[Team]] = None,
|
|
32
|
-
workflows: Optional[List[Workflow]] = None,
|
|
39
|
+
agents: Optional[List[Union[Agent, RemoteAgent]]] = None,
|
|
40
|
+
teams: Optional[List[Union[Team, RemoteTeam]]] = None,
|
|
41
|
+
workflows: Optional[List[Union[Workflow, RemoteWorkflow]]] = None,
|
|
33
42
|
) -> APIRouter:
|
|
34
43
|
if agents is None and teams is None and workflows is None:
|
|
35
44
|
raise ValueError("Agents, Teams, or Workflows are required to setup the A2A interface.")
|
|
36
45
|
|
|
46
|
+
# ============= AGENTS =============
|
|
47
|
+
@router.get("/agents/{id}/.well-known/agent-card.json")
|
|
48
|
+
async def get_agent_card(request: Request, id: str):
|
|
49
|
+
agent = get_agent_by_id(id, agents)
|
|
50
|
+
if not agent:
|
|
51
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
52
|
+
|
|
53
|
+
base_url = str(request.base_url).rstrip("/")
|
|
54
|
+
skill = AgentSkill(
|
|
55
|
+
id=agent.id or "",
|
|
56
|
+
name=agent.name or "",
|
|
57
|
+
description=agent.description or "",
|
|
58
|
+
tags=["agno"],
|
|
59
|
+
examples=["search", "ok"],
|
|
60
|
+
output_modes=["application/json"],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return AgentCard(
|
|
64
|
+
name=agent.name or "",
|
|
65
|
+
version="1.0.0",
|
|
66
|
+
description=agent.description or "",
|
|
67
|
+
url=f"{base_url}/a2a/agents/{agent.id}/v1/message:stream",
|
|
68
|
+
default_input_modes=["text"],
|
|
69
|
+
default_output_modes=["text"],
|
|
70
|
+
capabilities=AgentCapabilities(streaming=True, push_notifications=False, state_transition_history=False),
|
|
71
|
+
skills=[skill],
|
|
72
|
+
supports_authenticated_extended_card=False,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@router.post(
|
|
76
|
+
"/agents/{id}/v1/message:send",
|
|
77
|
+
operation_id="run_message_agent",
|
|
78
|
+
name="run_message_agent",
|
|
79
|
+
description="Send a message to an Agno Agent (non-streaming). The Agent is identified via the path parameter '{id}'. "
|
|
80
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
|
|
81
|
+
response_model_exclude_none=True,
|
|
82
|
+
responses={
|
|
83
|
+
200: {
|
|
84
|
+
"description": "Message sent successfully",
|
|
85
|
+
"content": {
|
|
86
|
+
"application/json": {
|
|
87
|
+
"example": {
|
|
88
|
+
"jsonrpc": "2.0",
|
|
89
|
+
"id": "request-123",
|
|
90
|
+
"result": {
|
|
91
|
+
"task": {
|
|
92
|
+
"id": "task-456",
|
|
93
|
+
"context_id": "context-789",
|
|
94
|
+
"status": "completed",
|
|
95
|
+
"history": [
|
|
96
|
+
{
|
|
97
|
+
"message_id": "msg-1",
|
|
98
|
+
"role": "agent",
|
|
99
|
+
"parts": [{"kind": "text", "text": "Response from agent"}],
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
400: {"description": "Invalid request"},
|
|
109
|
+
404: {"description": "Agent not found"},
|
|
110
|
+
},
|
|
111
|
+
response_model=SendMessageSuccessResponse,
|
|
112
|
+
)
|
|
113
|
+
async def a2a_run_agent(request: Request, id: str):
|
|
114
|
+
if not agents:
|
|
115
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
116
|
+
|
|
117
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
118
|
+
request_body = await request.json()
|
|
119
|
+
kwargs = await get_request_kwargs(request, a2a_run_agent)
|
|
120
|
+
|
|
121
|
+
# 1. Get the Agent to run
|
|
122
|
+
agent = get_agent_by_id(id, agents)
|
|
123
|
+
if not agent:
|
|
124
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
125
|
+
|
|
126
|
+
# 2. Map the request to our run_input and run variables
|
|
127
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=False)
|
|
128
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
129
|
+
user_id = request.headers.get("X-User-ID")
|
|
130
|
+
if not user_id:
|
|
131
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
132
|
+
|
|
133
|
+
# 3. Run the Agent
|
|
134
|
+
try:
|
|
135
|
+
response = await agent.arun(
|
|
136
|
+
input=run_input.input_content,
|
|
137
|
+
images=run_input.images,
|
|
138
|
+
videos=run_input.videos,
|
|
139
|
+
audio=run_input.audios,
|
|
140
|
+
files=run_input.files,
|
|
141
|
+
session_id=context_id,
|
|
142
|
+
user_id=user_id,
|
|
143
|
+
**kwargs,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# 4. Send the response
|
|
147
|
+
a2a_task = map_run_output_to_a2a_task(response)
|
|
148
|
+
return SendMessageSuccessResponse(
|
|
149
|
+
id=request_body.get("id", "unknown"),
|
|
150
|
+
result=a2a_task,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Handle any critical error
|
|
154
|
+
except Exception as e:
|
|
155
|
+
from a2a.types import Message as A2AMessage
|
|
156
|
+
from a2a.types import Part, Role, TextPart
|
|
157
|
+
|
|
158
|
+
error_message = A2AMessage(
|
|
159
|
+
message_id=str(uuid4()),
|
|
160
|
+
role=Role.agent,
|
|
161
|
+
parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
|
|
162
|
+
context_id=context_id or str(uuid4()),
|
|
163
|
+
)
|
|
164
|
+
failed_task = Task(
|
|
165
|
+
id=str(uuid4()),
|
|
166
|
+
context_id=context_id or str(uuid4()),
|
|
167
|
+
status=TaskStatus(state=TaskState.failed),
|
|
168
|
+
history=[error_message],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return SendMessageSuccessResponse(
|
|
172
|
+
id=request_body.get("id", "unknown"),
|
|
173
|
+
result=failed_task,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@router.post(
|
|
177
|
+
"/agents/{id}/v1/message:stream",
|
|
178
|
+
operation_id="stream_message_agent",
|
|
179
|
+
name="stream_message_agent",
|
|
180
|
+
description="Stream a message to an Agno Agent (streaming). The Agent is identified via the path parameter '{id}'. "
|
|
181
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
|
|
182
|
+
"Returns real-time updates as newline-delimited JSON (NDJSON).",
|
|
183
|
+
response_model_exclude_none=True,
|
|
184
|
+
responses={
|
|
185
|
+
200: {
|
|
186
|
+
"description": "Streaming response with task updates",
|
|
187
|
+
"content": {
|
|
188
|
+
"text/event-stream": {
|
|
189
|
+
"example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
|
|
190
|
+
'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
400: {"description": "Invalid request"},
|
|
195
|
+
404: {"description": "Agent not found"},
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
async def a2a_stream_agent(request: Request, id: str):
|
|
199
|
+
if not agents:
|
|
200
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
201
|
+
|
|
202
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
203
|
+
request_body = await request.json()
|
|
204
|
+
kwargs = await get_request_kwargs(request, a2a_stream_agent)
|
|
205
|
+
|
|
206
|
+
# 1. Get the Agent to run
|
|
207
|
+
agent = get_agent_by_id(id, agents)
|
|
208
|
+
if not agent:
|
|
209
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
210
|
+
|
|
211
|
+
# 2. Map the request to our run_input and run variables
|
|
212
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=True)
|
|
213
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
214
|
+
user_id = request.headers.get("X-User-ID")
|
|
215
|
+
if not user_id:
|
|
216
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
217
|
+
|
|
218
|
+
# 3. Run the Agent and stream the response
|
|
219
|
+
try:
|
|
220
|
+
event_stream = agent.arun(
|
|
221
|
+
input=run_input.input_content,
|
|
222
|
+
images=run_input.images,
|
|
223
|
+
videos=run_input.videos,
|
|
224
|
+
audio=run_input.audios,
|
|
225
|
+
files=run_input.files,
|
|
226
|
+
session_id=context_id,
|
|
227
|
+
user_id=user_id,
|
|
228
|
+
stream=True,
|
|
229
|
+
stream_events=True,
|
|
230
|
+
**kwargs,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# 4. Stream the response
|
|
234
|
+
return StreamingResponse(
|
|
235
|
+
stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
|
|
236
|
+
media_type="text/event-stream",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
|
|
241
|
+
|
|
242
|
+
# ============= TEAMS =============
|
|
243
|
+
@router.get("/teams/{id}/.well-known/agent-card.json")
|
|
244
|
+
async def get_team_card(request: Request, id: str):
|
|
245
|
+
team = get_team_by_id(id, teams)
|
|
246
|
+
if not team:
|
|
247
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
248
|
+
|
|
249
|
+
base_url = str(request.base_url).rstrip("/")
|
|
250
|
+
skill = AgentSkill(
|
|
251
|
+
id=team.id or "",
|
|
252
|
+
name=team.name or "",
|
|
253
|
+
description=team.description or "",
|
|
254
|
+
tags=["agno"],
|
|
255
|
+
examples=["search", "ok"],
|
|
256
|
+
output_modes=["application/json"],
|
|
257
|
+
)
|
|
258
|
+
return AgentCard(
|
|
259
|
+
name=team.name or "",
|
|
260
|
+
version="1.0.0",
|
|
261
|
+
description=team.description or "",
|
|
262
|
+
url=f"{base_url}/a2a/teams/{team.id}/v1/message:stream",
|
|
263
|
+
default_input_modes=["text"],
|
|
264
|
+
default_output_modes=["text"],
|
|
265
|
+
capabilities=AgentCapabilities(streaming=True, push_notifications=False, state_transition_history=False),
|
|
266
|
+
skills=[skill],
|
|
267
|
+
supports_authenticated_extended_card=False,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@router.post(
|
|
271
|
+
"/teams/{id}/v1/message:send",
|
|
272
|
+
operation_id="run_message_team",
|
|
273
|
+
name="run_message_team",
|
|
274
|
+
description="Send a message to an Agno Team (non-streaming). The Team is identified via the path parameter '{id}'. "
|
|
275
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
|
|
276
|
+
response_model_exclude_none=True,
|
|
277
|
+
responses={
|
|
278
|
+
200: {
|
|
279
|
+
"description": "Message sent successfully",
|
|
280
|
+
"content": {
|
|
281
|
+
"application/json": {
|
|
282
|
+
"example": {
|
|
283
|
+
"jsonrpc": "2.0",
|
|
284
|
+
"id": "request-123",
|
|
285
|
+
"result": {
|
|
286
|
+
"task": {
|
|
287
|
+
"id": "task-456",
|
|
288
|
+
"context_id": "context-789",
|
|
289
|
+
"status": "completed",
|
|
290
|
+
"history": [
|
|
291
|
+
{
|
|
292
|
+
"message_id": "msg-1",
|
|
293
|
+
"role": "agent",
|
|
294
|
+
"parts": [{"kind": "text", "text": "Response from agent"}],
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
400: {"description": "Invalid request"},
|
|
304
|
+
404: {"description": "Team not found"},
|
|
305
|
+
},
|
|
306
|
+
response_model=SendMessageSuccessResponse,
|
|
307
|
+
)
|
|
308
|
+
async def a2a_run_team(request: Request, id: str):
|
|
309
|
+
if not teams:
|
|
310
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
311
|
+
|
|
312
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
313
|
+
request_body = await request.json()
|
|
314
|
+
kwargs = await get_request_kwargs(request, a2a_run_team)
|
|
315
|
+
|
|
316
|
+
# 1. Get the Team to run
|
|
317
|
+
team = get_team_by_id(id, teams)
|
|
318
|
+
if not team:
|
|
319
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
320
|
+
|
|
321
|
+
# 2. Map the request to our run_input and run variables
|
|
322
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=False)
|
|
323
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
324
|
+
user_id = request.headers.get("X-User-ID")
|
|
325
|
+
if not user_id:
|
|
326
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
327
|
+
|
|
328
|
+
# 3. Run the Team
|
|
329
|
+
try:
|
|
330
|
+
response = await team.arun(
|
|
331
|
+
input=run_input.input_content,
|
|
332
|
+
images=run_input.images,
|
|
333
|
+
videos=run_input.videos,
|
|
334
|
+
audio=run_input.audios,
|
|
335
|
+
files=run_input.files,
|
|
336
|
+
session_id=context_id,
|
|
337
|
+
user_id=user_id,
|
|
338
|
+
**kwargs,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# 4. Send the response
|
|
342
|
+
a2a_task = map_run_output_to_a2a_task(response)
|
|
343
|
+
return SendMessageSuccessResponse(
|
|
344
|
+
id=request_body.get("id", "unknown"),
|
|
345
|
+
result=a2a_task,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Handle all critical errors
|
|
349
|
+
except Exception as e:
|
|
350
|
+
from a2a.types import Message as A2AMessage
|
|
351
|
+
from a2a.types import Part, Role, TextPart
|
|
352
|
+
|
|
353
|
+
error_message = A2AMessage(
|
|
354
|
+
message_id=str(uuid4()),
|
|
355
|
+
role=Role.agent,
|
|
356
|
+
parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
|
|
357
|
+
context_id=context_id or str(uuid4()),
|
|
358
|
+
)
|
|
359
|
+
failed_task = Task(
|
|
360
|
+
id=str(uuid4()),
|
|
361
|
+
context_id=context_id or str(uuid4()),
|
|
362
|
+
status=TaskStatus(state=TaskState.failed),
|
|
363
|
+
history=[error_message],
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return SendMessageSuccessResponse(
|
|
367
|
+
id=request_body.get("id", "unknown"),
|
|
368
|
+
result=failed_task,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
@router.post(
|
|
372
|
+
"/teams/{id}/v1/message:stream",
|
|
373
|
+
operation_id="stream_message_team",
|
|
374
|
+
name="stream_message_team",
|
|
375
|
+
description="Stream a message to an Agno Team (streaming). The Team is identified via the path parameter '{id}'. "
|
|
376
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
|
|
377
|
+
"Returns real-time updates as newline-delimited JSON (NDJSON).",
|
|
378
|
+
response_model_exclude_none=True,
|
|
379
|
+
responses={
|
|
380
|
+
200: {
|
|
381
|
+
"description": "Streaming response with task updates",
|
|
382
|
+
"content": {
|
|
383
|
+
"text/event-stream": {
|
|
384
|
+
"example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
|
|
385
|
+
'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
400: {"description": "Invalid request"},
|
|
390
|
+
404: {"description": "Team not found"},
|
|
391
|
+
},
|
|
392
|
+
)
|
|
393
|
+
async def a2a_stream_team(request: Request, id: str):
|
|
394
|
+
if not teams:
|
|
395
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
396
|
+
|
|
397
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
398
|
+
request_body = await request.json()
|
|
399
|
+
kwargs = await get_request_kwargs(request, a2a_stream_team)
|
|
400
|
+
|
|
401
|
+
# 1. Get the Team to run
|
|
402
|
+
team = get_team_by_id(id, teams)
|
|
403
|
+
if not team:
|
|
404
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
405
|
+
|
|
406
|
+
# 2. Map the request to our run_input and run variables
|
|
407
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=True)
|
|
408
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
409
|
+
user_id = request.headers.get("X-User-ID")
|
|
410
|
+
if not user_id:
|
|
411
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
412
|
+
|
|
413
|
+
# 3. Run the Team and stream the response
|
|
414
|
+
try:
|
|
415
|
+
event_stream = team.arun(
|
|
416
|
+
input=run_input.input_content,
|
|
417
|
+
images=run_input.images,
|
|
418
|
+
videos=run_input.videos,
|
|
419
|
+
audio=run_input.audios,
|
|
420
|
+
files=run_input.files,
|
|
421
|
+
session_id=context_id,
|
|
422
|
+
user_id=user_id,
|
|
423
|
+
stream=True,
|
|
424
|
+
stream_events=True,
|
|
425
|
+
**kwargs,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# 4. Stream the response
|
|
429
|
+
return StreamingResponse(
|
|
430
|
+
stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
|
|
431
|
+
media_type="text/event-stream",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
|
|
436
|
+
|
|
437
|
+
# ============= WORKFLOWS =============
|
|
438
|
+
@router.get("/workflows/{id}/.well-known/agent-card.json")
|
|
439
|
+
async def get_workflow_card(request: Request, id: str):
|
|
440
|
+
workflow = get_workflow_by_id(id, workflows)
|
|
441
|
+
if not workflow:
|
|
442
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
443
|
+
|
|
444
|
+
base_url = str(request.base_url).rstrip("/")
|
|
445
|
+
skill = AgentSkill(
|
|
446
|
+
id=workflow.id or "",
|
|
447
|
+
name=workflow.name or "",
|
|
448
|
+
description=workflow.description or "",
|
|
449
|
+
tags=["agno"],
|
|
450
|
+
examples=["search", "ok"],
|
|
451
|
+
output_modes=["application/json"],
|
|
452
|
+
)
|
|
453
|
+
return AgentCard(
|
|
454
|
+
name=workflow.name or "",
|
|
455
|
+
version="1.0.0",
|
|
456
|
+
description=workflow.description or "",
|
|
457
|
+
url=f"{base_url}/a2a/workflows/{workflow.id}/v1/message:stream",
|
|
458
|
+
default_input_modes=["text"],
|
|
459
|
+
default_output_modes=["text"],
|
|
460
|
+
capabilities=AgentCapabilities(streaming=False, push_notifications=False, state_transition_history=False),
|
|
461
|
+
skills=[skill],
|
|
462
|
+
supports_authenticated_extended_card=False,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
@router.post(
|
|
466
|
+
"/workflows/{id}/v1/message:send",
|
|
467
|
+
operation_id="run_message_workflow",
|
|
468
|
+
name="run_message_workflow",
|
|
469
|
+
description="Send a message to an Agno Workflow (non-streaming). The Workflow is identified via the path parameter '{id}'. "
|
|
470
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
|
|
471
|
+
response_model_exclude_none=True,
|
|
472
|
+
responses={
|
|
473
|
+
200: {
|
|
474
|
+
"description": "Message sent successfully",
|
|
475
|
+
"content": {
|
|
476
|
+
"application/json": {
|
|
477
|
+
"example": {
|
|
478
|
+
"jsonrpc": "2.0",
|
|
479
|
+
"id": "request-123",
|
|
480
|
+
"result": {
|
|
481
|
+
"task": {
|
|
482
|
+
"id": "task-456",
|
|
483
|
+
"context_id": "context-789",
|
|
484
|
+
"status": "completed",
|
|
485
|
+
"history": [
|
|
486
|
+
{
|
|
487
|
+
"message_id": "msg-1",
|
|
488
|
+
"role": "agent",
|
|
489
|
+
"parts": [{"kind": "text", "text": "Response from agent"}],
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
400: {"description": "Invalid request"},
|
|
499
|
+
404: {"description": "Workflow not found"},
|
|
500
|
+
},
|
|
501
|
+
response_model=SendMessageSuccessResponse,
|
|
502
|
+
)
|
|
503
|
+
async def a2a_run_workflow(request: Request, id: str):
|
|
504
|
+
if not workflows:
|
|
505
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
506
|
+
|
|
507
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
508
|
+
request_body = await request.json()
|
|
509
|
+
kwargs = await get_request_kwargs(request, a2a_run_workflow)
|
|
510
|
+
|
|
511
|
+
# 1. Get the Workflow to run
|
|
512
|
+
workflow = get_workflow_by_id(id, workflows)
|
|
513
|
+
if not workflow:
|
|
514
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
515
|
+
|
|
516
|
+
# 2. Map the request to our run_input and run variables
|
|
517
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=False)
|
|
518
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
519
|
+
user_id = request.headers.get("X-User-ID")
|
|
520
|
+
if not user_id:
|
|
521
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
522
|
+
|
|
523
|
+
# 3. Run the Workflow
|
|
524
|
+
try:
|
|
525
|
+
response = await workflow.arun(
|
|
526
|
+
input=run_input.input_content,
|
|
527
|
+
images=list(run_input.images) if run_input.images else None,
|
|
528
|
+
videos=list(run_input.videos) if run_input.videos else None,
|
|
529
|
+
audio=list(run_input.audios) if run_input.audios else None,
|
|
530
|
+
files=list(run_input.files) if run_input.files else None,
|
|
531
|
+
session_id=context_id,
|
|
532
|
+
user_id=user_id,
|
|
533
|
+
**kwargs,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# 4. Send the response
|
|
537
|
+
a2a_task = map_run_output_to_a2a_task(response)
|
|
538
|
+
return SendMessageSuccessResponse(
|
|
539
|
+
id=request_body.get("id", "unknown"),
|
|
540
|
+
result=a2a_task,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Handle all critical errors
|
|
544
|
+
except Exception as e:
|
|
545
|
+
from a2a.types import Message as A2AMessage
|
|
546
|
+
from a2a.types import Part, Role, TextPart
|
|
547
|
+
|
|
548
|
+
error_message = A2AMessage(
|
|
549
|
+
message_id=str(uuid4()),
|
|
550
|
+
role=Role.agent,
|
|
551
|
+
parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
|
|
552
|
+
context_id=context_id or str(uuid4()),
|
|
553
|
+
)
|
|
554
|
+
failed_task = Task(
|
|
555
|
+
id=str(uuid4()),
|
|
556
|
+
context_id=context_id or str(uuid4()),
|
|
557
|
+
status=TaskStatus(state=TaskState.failed),
|
|
558
|
+
history=[error_message],
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
return SendMessageSuccessResponse(
|
|
562
|
+
id=request_body.get("id", "unknown"),
|
|
563
|
+
result=failed_task,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
@router.post(
|
|
567
|
+
"/workflows/{id}/v1/message:stream",
|
|
568
|
+
operation_id="stream_message_workflow",
|
|
569
|
+
name="stream_message_workflow",
|
|
570
|
+
description="Stream a message to an Agno Workflow (streaming). The Workflow is identified via the path parameter '{id}'. "
|
|
571
|
+
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
|
|
572
|
+
"Returns real-time updates as newline-delimited JSON (NDJSON).",
|
|
573
|
+
response_model_exclude_none=True,
|
|
574
|
+
responses={
|
|
575
|
+
200: {
|
|
576
|
+
"description": "Streaming response with task updates",
|
|
577
|
+
"content": {
|
|
578
|
+
"text/event-stream": {
|
|
579
|
+
"example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
|
|
580
|
+
'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
400: {"description": "Invalid request"},
|
|
585
|
+
404: {"description": "Workflow not found"},
|
|
586
|
+
},
|
|
587
|
+
)
|
|
588
|
+
async def a2a_stream_workflow(request: Request, id: str):
|
|
589
|
+
if not workflows:
|
|
590
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
591
|
+
|
|
592
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
593
|
+
request_body = await request.json()
|
|
594
|
+
kwargs = await get_request_kwargs(request, a2a_stream_workflow)
|
|
595
|
+
|
|
596
|
+
# 1. Get the Workflow to run
|
|
597
|
+
workflow = get_workflow_by_id(id, workflows)
|
|
598
|
+
if not workflow:
|
|
599
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
600
|
+
|
|
601
|
+
# 2. Map the request to our run_input and run variables
|
|
602
|
+
run_input = await map_a2a_request_to_run_input(request_body, stream=True)
|
|
603
|
+
context_id = request_body.get("params", {}).get("message", {}).get("contextId")
|
|
604
|
+
user_id = request.headers.get("X-User-ID")
|
|
605
|
+
if not user_id:
|
|
606
|
+
user_id = request_body.get("params", {}).get("message", {}).get("metadata", {}).get("userId")
|
|
607
|
+
|
|
608
|
+
# 3. Run the Workflow and stream the response
|
|
609
|
+
try:
|
|
610
|
+
event_stream = workflow.arun(
|
|
611
|
+
input=run_input.input_content,
|
|
612
|
+
images=list(run_input.images) if run_input.images else None,
|
|
613
|
+
videos=list(run_input.videos) if run_input.videos else None,
|
|
614
|
+
audio=list(run_input.audios) if run_input.audios else None,
|
|
615
|
+
files=list(run_input.files) if run_input.files else None,
|
|
616
|
+
session_id=context_id,
|
|
617
|
+
user_id=user_id,
|
|
618
|
+
stream=True,
|
|
619
|
+
stream_events=True,
|
|
620
|
+
**kwargs,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# 4. Stream the response
|
|
624
|
+
return StreamingResponse(
|
|
625
|
+
stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
|
|
626
|
+
media_type="text/event-stream",
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
except Exception as e:
|
|
630
|
+
raise HTTPException(status_code=500, detail=f"Failed to start run: {str(e)}")
|
|
631
|
+
|
|
632
|
+
# ============= DEPRECATED ENDPOINTS =============
|
|
633
|
+
|
|
37
634
|
@router.post(
|
|
38
635
|
"/message/send",
|
|
39
636
|
operation_id="send_message",
|
|
40
637
|
name="send_message",
|
|
41
|
-
description="Send a message to an Agno Agent, Team, or Workflow. "
|
|
638
|
+
description="[DEPRECATED] Send a message to an Agno Agent, Team, or Workflow. "
|
|
42
639
|
"The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
|
|
43
640
|
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
|
|
44
641
|
response_model_exclude_none=True,
|
|
@@ -74,8 +671,14 @@ def attach_routes(
|
|
|
74
671
|
response_model=SendMessageSuccessResponse,
|
|
75
672
|
)
|
|
76
673
|
async def a2a_send_message(request: Request):
|
|
674
|
+
warnings.warn(
|
|
675
|
+
"This endpoint will be deprecated soon. Use /agents/{agents_id}/v1/message:send, /teams/{teams_id}/v1/message:send, or /workflows/{workflows_id}/v1/message:send instead.",
|
|
676
|
+
DeprecationWarning,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
77
680
|
request_body = await request.json()
|
|
78
|
-
kwargs = await
|
|
681
|
+
kwargs = await get_request_kwargs(request, a2a_send_message)
|
|
79
682
|
|
|
80
683
|
# 1. Get the Agent, Team, or Workflow to run
|
|
81
684
|
agent_id = request_body.get("params", {}).get("message", {}).get("agentId") or request.headers.get("X-Agent-ID")
|
|
@@ -84,7 +687,7 @@ def attach_routes(
|
|
|
84
687
|
status_code=400,
|
|
85
688
|
detail="Entity ID required. Provide it via 'agentId' in params.message or 'X-Agent-ID' header.",
|
|
86
689
|
)
|
|
87
|
-
entity: Optional[Union[Agent, Team, Workflow]] = None
|
|
690
|
+
entity: Optional[Union[Agent, RemoteAgent, Team, RemoteTeam, Workflow, RemoteWorkflow]] = None
|
|
88
691
|
if agents:
|
|
89
692
|
entity = get_agent_by_id(agent_id, agents)
|
|
90
693
|
if not entity and teams:
|
|
@@ -104,7 +707,7 @@ def attach_routes(
|
|
|
104
707
|
# 3. Run the agent, team, or workflow
|
|
105
708
|
try:
|
|
106
709
|
if isinstance(entity, Workflow):
|
|
107
|
-
response =
|
|
710
|
+
response = entity.arun(
|
|
108
711
|
input=run_input.input_content,
|
|
109
712
|
images=list(run_input.images) if run_input.images else None,
|
|
110
713
|
videos=list(run_input.videos) if run_input.videos else None,
|
|
@@ -115,12 +718,12 @@ def attach_routes(
|
|
|
115
718
|
**kwargs,
|
|
116
719
|
)
|
|
117
720
|
else:
|
|
118
|
-
response =
|
|
721
|
+
response = entity.arun(
|
|
119
722
|
input=run_input.input_content,
|
|
120
|
-
images=run_input.images,
|
|
121
|
-
videos=run_input.videos,
|
|
122
|
-
audio=run_input.audios,
|
|
123
|
-
files=run_input.files,
|
|
723
|
+
images=run_input.images, # type: ignore
|
|
724
|
+
videos=run_input.videos, # type: ignore
|
|
725
|
+
audio=run_input.audios, # type: ignore
|
|
726
|
+
files=run_input.files, # type: ignore
|
|
124
727
|
session_id=context_id,
|
|
125
728
|
user_id=user_id,
|
|
126
729
|
**kwargs,
|
|
@@ -160,7 +763,7 @@ def attach_routes(
|
|
|
160
763
|
"/message/stream",
|
|
161
764
|
operation_id="stream_message",
|
|
162
765
|
name="stream_message",
|
|
163
|
-
description="Stream a message to an Agno Agent, Team, or Workflow."
|
|
766
|
+
description="[DEPRECATED] Stream a message to an Agno Agent, Team, or Workflow. "
|
|
164
767
|
"The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
|
|
165
768
|
"Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
|
|
166
769
|
"Returns real-time updates as newline-delimited JSON (NDJSON).",
|
|
@@ -169,9 +772,9 @@ def attach_routes(
|
|
|
169
772
|
200: {
|
|
170
773
|
"description": "Streaming response with task updates",
|
|
171
774
|
"content": {
|
|
172
|
-
"
|
|
173
|
-
"example": '{"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n'
|
|
174
|
-
'{"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n'
|
|
775
|
+
"text/event-stream": {
|
|
776
|
+
"example": 'event: TaskStatusUpdateEvent\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"taskId":"task-456","status":"working"}}\n\n'
|
|
777
|
+
'event: Message\ndata: {"jsonrpc":"2.0","id":"request-123","result":{"messageId":"msg-1","role":"agent","parts":[{"kind":"text","text":"Response"}]}}\n\n'
|
|
175
778
|
}
|
|
176
779
|
},
|
|
177
780
|
},
|
|
@@ -180,8 +783,14 @@ def attach_routes(
|
|
|
180
783
|
},
|
|
181
784
|
)
|
|
182
785
|
async def a2a_stream_message(request: Request):
|
|
786
|
+
warnings.warn(
|
|
787
|
+
"This endpoint will be deprecated soon. Use /agents/{agents_id}/v1/message:stream, /teams/{teams_id}/v1/message:stream, or /workflows/{workflows_id}/v1/message:stream instead.",
|
|
788
|
+
DeprecationWarning,
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# Load the request body. Unknown args are passed down as kwargs.
|
|
183
792
|
request_body = await request.json()
|
|
184
|
-
kwargs = await
|
|
793
|
+
kwargs = await get_request_kwargs(request, a2a_stream_message)
|
|
185
794
|
|
|
186
795
|
# 1. Get the Agent, Team, or Workflow to run
|
|
187
796
|
agent_id = request_body.get("params", {}).get("message", {}).get("agentId")
|
|
@@ -192,7 +801,7 @@ def attach_routes(
|
|
|
192
801
|
status_code=400,
|
|
193
802
|
detail="Entity ID required. Provide 'agentId' in params.message or 'X-Agent-ID' header.",
|
|
194
803
|
)
|
|
195
|
-
entity: Optional[Union[Agent, Team, Workflow]] = None
|
|
804
|
+
entity: Optional[Union[Agent, RemoteAgent, Team, RemoteTeam, Workflow, RemoteWorkflow]] = None
|
|
196
805
|
if agents:
|
|
197
806
|
entity = get_agent_by_id(agent_id, agents)
|
|
198
807
|
if not entity and teams:
|
|
@@ -225,7 +834,7 @@ def attach_routes(
|
|
|
225
834
|
**kwargs,
|
|
226
835
|
)
|
|
227
836
|
else:
|
|
228
|
-
event_stream = entity.arun( # type: ignore
|
|
837
|
+
event_stream = entity.arun( # type: ignore
|
|
229
838
|
input=run_input.input_content,
|
|
230
839
|
images=run_input.images,
|
|
231
840
|
videos=run_input.videos,
|
|
@@ -241,7 +850,7 @@ def attach_routes(
|
|
|
241
850
|
# 4. Stream the response
|
|
242
851
|
return StreamingResponse(
|
|
243
852
|
stream_a2a_response_with_error_handling(event_stream=event_stream, request_id=request_body["id"]), # type: ignore[arg-type]
|
|
244
|
-
media_type="
|
|
853
|
+
media_type="text/event-stream",
|
|
245
854
|
)
|
|
246
855
|
|
|
247
856
|
except Exception as e:
|