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
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, cast
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from fastapi import HTTPException
|
|
6
|
+
from typing_extensions import AsyncIterator, List, Union
|
|
7
|
+
|
|
8
|
+
from agno.run.team import MemoryUpdateCompletedEvent as TeamMemoryUpdateCompletedEvent
|
|
9
|
+
from agno.run.team import MemoryUpdateStartedEvent as TeamMemoryUpdateStartedEvent
|
|
10
|
+
from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
|
|
11
|
+
from agno.run.team import ReasoningStartedEvent as TeamReasoningStartedEvent
|
|
12
|
+
from agno.run.team import ReasoningStepEvent as TeamReasoningStepEvent
|
|
13
|
+
from agno.run.team import RunCancelledEvent as TeamRunCancelledEvent
|
|
14
|
+
from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
|
|
15
|
+
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
16
|
+
from agno.run.team import RunStartedEvent as TeamRunStartedEvent
|
|
17
|
+
from agno.run.team import TeamRunOutputEvent
|
|
18
|
+
from agno.run.team import ToolCallCompletedEvent as TeamToolCallCompletedEvent
|
|
19
|
+
from agno.run.team import ToolCallStartedEvent as TeamToolCallStartedEvent
|
|
20
|
+
from agno.run.workflow import (
|
|
21
|
+
ConditionExecutionCompletedEvent,
|
|
22
|
+
ConditionExecutionStartedEvent,
|
|
23
|
+
LoopExecutionCompletedEvent,
|
|
24
|
+
LoopExecutionStartedEvent,
|
|
25
|
+
LoopIterationCompletedEvent,
|
|
26
|
+
LoopIterationStartedEvent,
|
|
27
|
+
ParallelExecutionCompletedEvent,
|
|
28
|
+
ParallelExecutionStartedEvent,
|
|
29
|
+
RouterExecutionCompletedEvent,
|
|
30
|
+
RouterExecutionStartedEvent,
|
|
31
|
+
StepsExecutionCompletedEvent,
|
|
32
|
+
StepsExecutionStartedEvent,
|
|
33
|
+
WorkflowCancelledEvent,
|
|
34
|
+
WorkflowCompletedEvent,
|
|
35
|
+
WorkflowRunOutput,
|
|
36
|
+
WorkflowRunOutputEvent,
|
|
37
|
+
WorkflowStartedEvent,
|
|
38
|
+
)
|
|
39
|
+
from agno.run.workflow import StepCompletedEvent as WorkflowStepCompletedEvent
|
|
40
|
+
from agno.run.workflow import StepErrorEvent as WorkflowStepErrorEvent
|
|
41
|
+
from agno.run.workflow import StepStartedEvent as WorkflowStepStartedEvent
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
from a2a.types import (
|
|
45
|
+
Artifact,
|
|
46
|
+
DataPart,
|
|
47
|
+
FilePart,
|
|
48
|
+
FileWithBytes,
|
|
49
|
+
FileWithUri,
|
|
50
|
+
Part,
|
|
51
|
+
Role,
|
|
52
|
+
SendMessageRequest,
|
|
53
|
+
SendStreamingMessageRequest,
|
|
54
|
+
SendStreamingMessageSuccessResponse,
|
|
55
|
+
Task,
|
|
56
|
+
TaskState,
|
|
57
|
+
TaskStatus,
|
|
58
|
+
TaskStatusUpdateEvent,
|
|
59
|
+
TextPart,
|
|
60
|
+
)
|
|
61
|
+
from a2a.types import Message as A2AMessage
|
|
62
|
+
except ImportError as e:
|
|
63
|
+
raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a`") from e
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
from agno.media import Audio, File, Image, Video
|
|
67
|
+
from agno.run.agent import (
|
|
68
|
+
MemoryUpdateCompletedEvent,
|
|
69
|
+
MemoryUpdateStartedEvent,
|
|
70
|
+
ReasoningCompletedEvent,
|
|
71
|
+
ReasoningStartedEvent,
|
|
72
|
+
ReasoningStepEvent,
|
|
73
|
+
RunCancelledEvent,
|
|
74
|
+
RunCompletedEvent,
|
|
75
|
+
RunContentEvent,
|
|
76
|
+
RunInput,
|
|
77
|
+
RunOutput,
|
|
78
|
+
RunOutputEvent,
|
|
79
|
+
RunStartedEvent,
|
|
80
|
+
ToolCallCompletedEvent,
|
|
81
|
+
ToolCallStartedEvent,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def map_a2a_request_to_run_input(request_body: dict, stream: bool = True) -> RunInput:
|
|
86
|
+
"""Map A2A SendMessageRequest to Agno RunInput.
|
|
87
|
+
|
|
88
|
+
1. Validate the request
|
|
89
|
+
2. Process message parts
|
|
90
|
+
3. Build and return RunInput
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
request_body: A2A-valid JSON-RPC request body dict:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"jsonrpc": "2.0",
|
|
98
|
+
"method": "message/send",
|
|
99
|
+
"id": "id",
|
|
100
|
+
"params": {
|
|
101
|
+
"message": {
|
|
102
|
+
"messageId": "id",
|
|
103
|
+
"role": "user",
|
|
104
|
+
"contextId": "id",
|
|
105
|
+
"parts": [{"kind": "text", "text": "Hello"}]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
RunInput: The Agno RunInput
|
|
113
|
+
stream: Wheter we are in stream mode
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
# 1. Validate the request
|
|
117
|
+
if stream:
|
|
118
|
+
try:
|
|
119
|
+
a2a_request = SendStreamingMessageRequest.model_validate(request_body)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
raise HTTPException(status_code=400, detail=f"Invalid A2A request: {str(e)}")
|
|
122
|
+
else:
|
|
123
|
+
try:
|
|
124
|
+
a2a_request = SendMessageRequest.model_validate(request_body) # type: ignore[assignment]
|
|
125
|
+
except Exception as e:
|
|
126
|
+
raise HTTPException(status_code=400, detail=f"Invalid A2A request: {str(e)}")
|
|
127
|
+
|
|
128
|
+
a2a_message = a2a_request.params.message
|
|
129
|
+
if a2a_message.role != "user":
|
|
130
|
+
raise HTTPException(status_code=400, detail="Only user messages are accepted")
|
|
131
|
+
|
|
132
|
+
# 2. Process message parts
|
|
133
|
+
text_parts = []
|
|
134
|
+
images = []
|
|
135
|
+
videos = []
|
|
136
|
+
audios = []
|
|
137
|
+
files = []
|
|
138
|
+
|
|
139
|
+
for part in a2a_message.parts:
|
|
140
|
+
# Handle message text content
|
|
141
|
+
if isinstance(part.root, TextPart):
|
|
142
|
+
text_parts.append(part.root.text)
|
|
143
|
+
|
|
144
|
+
# Handle message files
|
|
145
|
+
elif isinstance(part.root, FilePart):
|
|
146
|
+
file_data = part.root.file
|
|
147
|
+
if isinstance(file_data, FileWithUri):
|
|
148
|
+
if not file_data.mime_type:
|
|
149
|
+
continue
|
|
150
|
+
elif file_data.mime_type.startswith("image/"):
|
|
151
|
+
images.append(Image(url=file_data.uri))
|
|
152
|
+
elif file_data.mime_type.startswith("video/"):
|
|
153
|
+
videos.append(Video(url=file_data.uri))
|
|
154
|
+
elif file_data.mime_type.startswith("audio/"):
|
|
155
|
+
audios.append(Audio(url=file_data.uri))
|
|
156
|
+
else:
|
|
157
|
+
files.append(File(url=file_data.uri, mime_type=file_data.mime_type))
|
|
158
|
+
elif isinstance(file_data, FileWithBytes):
|
|
159
|
+
if not file_data.mime_type:
|
|
160
|
+
continue
|
|
161
|
+
files.append(File(content=file_data.bytes, mime_type=file_data.mime_type))
|
|
162
|
+
|
|
163
|
+
# Handle message structured data parts
|
|
164
|
+
elif isinstance(part.root, DataPart):
|
|
165
|
+
import json
|
|
166
|
+
|
|
167
|
+
text_parts.append(json.dumps(part.root.data))
|
|
168
|
+
|
|
169
|
+
# 3. Build and return RunInput
|
|
170
|
+
complete_input_content = "\n".join(text_parts) if text_parts else ""
|
|
171
|
+
return RunInput(
|
|
172
|
+
input_content=complete_input_content,
|
|
173
|
+
images=images if images else None,
|
|
174
|
+
videos=videos if videos else None,
|
|
175
|
+
audios=audios if audios else None,
|
|
176
|
+
files=files if files else None,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def map_run_output_to_a2a_task(run_output: Union[RunOutput, WorkflowRunOutput]) -> Task:
|
|
181
|
+
"""Map the given RunOutput or WorkflowRunOutput into an A2A Task.
|
|
182
|
+
|
|
183
|
+
1. Handle output content
|
|
184
|
+
2. Handle output media
|
|
185
|
+
3. Build the A2A message
|
|
186
|
+
4. Build and return the A2A task
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
run_output: The Agno RunOutput or WorkflowRunOutput
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Task: The A2A Task
|
|
193
|
+
"""
|
|
194
|
+
parts: List[Part] = []
|
|
195
|
+
|
|
196
|
+
# 1. Handle output content
|
|
197
|
+
if run_output.content:
|
|
198
|
+
parts.append(Part(root=TextPart(text=str(run_output.content))))
|
|
199
|
+
|
|
200
|
+
# 2. Handle output media
|
|
201
|
+
artifacts: List[Artifact] = []
|
|
202
|
+
if hasattr(run_output, "images") and run_output.images:
|
|
203
|
+
for idx, img in enumerate(run_output.images):
|
|
204
|
+
artifact_parts = []
|
|
205
|
+
if img.url:
|
|
206
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=img.url, mime_type="image/jpeg"))))
|
|
207
|
+
artifacts.append(
|
|
208
|
+
Artifact(
|
|
209
|
+
artifact_id=str(uuid4()),
|
|
210
|
+
name=f"image_{idx}",
|
|
211
|
+
description=f"Generated image {idx}",
|
|
212
|
+
parts=artifact_parts,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
if hasattr(run_output, "videos") and run_output.videos:
|
|
216
|
+
for idx, vid in enumerate(run_output.videos):
|
|
217
|
+
artifact_parts = []
|
|
218
|
+
if vid.url:
|
|
219
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=vid.url, mime_type="video/mp4"))))
|
|
220
|
+
artifacts.append(
|
|
221
|
+
Artifact(
|
|
222
|
+
artifact_id=str(uuid4()),
|
|
223
|
+
name=f"video_{idx}",
|
|
224
|
+
description=f"Generated video {idx}",
|
|
225
|
+
parts=artifact_parts,
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
if hasattr(run_output, "audio") and run_output.audio:
|
|
229
|
+
for idx, aud in enumerate(run_output.audio):
|
|
230
|
+
artifact_parts = []
|
|
231
|
+
if aud.url:
|
|
232
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=aud.url, mime_type="audio/mpeg"))))
|
|
233
|
+
artifacts.append(
|
|
234
|
+
Artifact(
|
|
235
|
+
artifact_id=str(uuid4()),
|
|
236
|
+
name=f"audio_{idx}",
|
|
237
|
+
description=f"Generated audio {idx}",
|
|
238
|
+
parts=artifact_parts,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
if hasattr(run_output, "files") and run_output.files:
|
|
242
|
+
for idx, file in enumerate(run_output.files):
|
|
243
|
+
artifact_parts = []
|
|
244
|
+
if file.url:
|
|
245
|
+
artifact_parts.append(
|
|
246
|
+
Part(
|
|
247
|
+
root=FilePart(
|
|
248
|
+
file=FileWithUri(uri=file.url, mime_type=file.mime_type or "application/octet-stream")
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
artifacts.append(
|
|
253
|
+
Artifact(
|
|
254
|
+
artifact_id=str(uuid4()),
|
|
255
|
+
name=getattr(file, "name", f"file_{idx}"),
|
|
256
|
+
description=f"Generated file {idx}",
|
|
257
|
+
parts=artifact_parts,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# 3. Build the A2A message
|
|
262
|
+
metadata = {}
|
|
263
|
+
if hasattr(run_output, "user_id") and run_output.user_id:
|
|
264
|
+
metadata["userId"] = run_output.user_id
|
|
265
|
+
|
|
266
|
+
agent_message = A2AMessage(
|
|
267
|
+
message_id=str(uuid4()), # TODO: use our message_id once it's implemented
|
|
268
|
+
role=Role.agent,
|
|
269
|
+
parts=parts,
|
|
270
|
+
context_id=run_output.session_id,
|
|
271
|
+
task_id=run_output.run_id,
|
|
272
|
+
metadata=metadata if metadata else None,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# 4. Build and return the A2A task
|
|
276
|
+
run_id = cast(str, run_output.run_id) if run_output.run_id else str(uuid4())
|
|
277
|
+
session_id = cast(str, run_output.session_id) if run_output.session_id else str(uuid4())
|
|
278
|
+
return Task(
|
|
279
|
+
id=run_id,
|
|
280
|
+
context_id=session_id,
|
|
281
|
+
status=TaskStatus(state=TaskState.completed),
|
|
282
|
+
history=[agent_message],
|
|
283
|
+
artifacts=artifacts if artifacts else None,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
async def stream_a2a_response(
|
|
288
|
+
event_stream: AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRunOutputEvent, RunOutput]],
|
|
289
|
+
request_id: Union[str, int],
|
|
290
|
+
) -> AsyncIterator[str]:
|
|
291
|
+
"""Stream the given event stream as A2A responses.
|
|
292
|
+
|
|
293
|
+
1. Send initial event
|
|
294
|
+
2. Send content and secondary events
|
|
295
|
+
3. Send final status event
|
|
296
|
+
4. Send final complete task
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
event_stream: The async iterator of Agno events from agent/team/workflow.arun(stream=True)
|
|
300
|
+
request_id: The JSON-RPC request ID
|
|
301
|
+
|
|
302
|
+
Yields:
|
|
303
|
+
str: JSON-RPC response objects (A2A-valid)
|
|
304
|
+
"""
|
|
305
|
+
task_id: str = str(uuid4())
|
|
306
|
+
context_id: str = str(uuid4())
|
|
307
|
+
message_id: str = str(uuid4())
|
|
308
|
+
accumulated_content = ""
|
|
309
|
+
completion_event = None
|
|
310
|
+
cancelled_event = None
|
|
311
|
+
|
|
312
|
+
# Stream events
|
|
313
|
+
async for event in event_stream:
|
|
314
|
+
# 1. Send initial event
|
|
315
|
+
if isinstance(event, (RunStartedEvent, TeamRunStartedEvent, WorkflowStartedEvent)):
|
|
316
|
+
if hasattr(event, "run_id") and event.run_id:
|
|
317
|
+
task_id = event.run_id
|
|
318
|
+
if hasattr(event, "session_id") and event.session_id:
|
|
319
|
+
context_id = event.session_id
|
|
320
|
+
|
|
321
|
+
status_event = TaskStatusUpdateEvent(
|
|
322
|
+
task_id=task_id,
|
|
323
|
+
context_id=context_id,
|
|
324
|
+
status=TaskStatus(state=TaskState.working),
|
|
325
|
+
final=False,
|
|
326
|
+
)
|
|
327
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
328
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
329
|
+
|
|
330
|
+
# 2. Send all content and secondary events
|
|
331
|
+
|
|
332
|
+
# Send content events
|
|
333
|
+
elif isinstance(event, (RunContentEvent, TeamRunContentEvent)) and event.content:
|
|
334
|
+
accumulated_content += event.content
|
|
335
|
+
message = A2AMessage(
|
|
336
|
+
message_id=message_id,
|
|
337
|
+
role=Role.agent,
|
|
338
|
+
parts=[Part(root=TextPart(text=event.content))],
|
|
339
|
+
context_id=context_id,
|
|
340
|
+
task_id=task_id,
|
|
341
|
+
metadata={"agno_content_category": "content"},
|
|
342
|
+
)
|
|
343
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=message)
|
|
344
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
345
|
+
|
|
346
|
+
# Send tool call events
|
|
347
|
+
elif isinstance(event, (ToolCallStartedEvent, TeamToolCallStartedEvent)):
|
|
348
|
+
metadata: Dict[str, Any] = {"agno_event_type": "tool_call_started"}
|
|
349
|
+
if event.tool:
|
|
350
|
+
metadata["tool_name"] = event.tool.tool_name or "tool"
|
|
351
|
+
if hasattr(event.tool, "tool_call_id") and event.tool.tool_call_id:
|
|
352
|
+
metadata["tool_call_id"] = event.tool.tool_call_id
|
|
353
|
+
if hasattr(event.tool, "tool_args") and event.tool.tool_args:
|
|
354
|
+
metadata["tool_args"] = json.dumps(event.tool.tool_args)
|
|
355
|
+
|
|
356
|
+
status_event = TaskStatusUpdateEvent(
|
|
357
|
+
task_id=task_id,
|
|
358
|
+
context_id=context_id,
|
|
359
|
+
status=TaskStatus(state=TaskState.working),
|
|
360
|
+
final=False,
|
|
361
|
+
metadata=metadata,
|
|
362
|
+
)
|
|
363
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
364
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
365
|
+
|
|
366
|
+
elif isinstance(event, (ToolCallCompletedEvent, TeamToolCallCompletedEvent)):
|
|
367
|
+
metadata = {"agno_event_type": "tool_call_completed"}
|
|
368
|
+
if event.tool:
|
|
369
|
+
metadata["tool_name"] = event.tool.tool_name or "tool"
|
|
370
|
+
if hasattr(event.tool, "tool_call_id") and event.tool.tool_call_id:
|
|
371
|
+
metadata["tool_call_id"] = event.tool.tool_call_id
|
|
372
|
+
if hasattr(event.tool, "tool_args") and event.tool.tool_args:
|
|
373
|
+
metadata["tool_args"] = json.dumps(event.tool.tool_args)
|
|
374
|
+
|
|
375
|
+
status_event = TaskStatusUpdateEvent(
|
|
376
|
+
task_id=task_id,
|
|
377
|
+
context_id=context_id,
|
|
378
|
+
status=TaskStatus(state=TaskState.working),
|
|
379
|
+
final=False,
|
|
380
|
+
metadata=metadata,
|
|
381
|
+
)
|
|
382
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
383
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
384
|
+
|
|
385
|
+
# Send reasoning events
|
|
386
|
+
elif isinstance(event, (ReasoningStartedEvent, TeamReasoningStartedEvent)):
|
|
387
|
+
status_event = TaskStatusUpdateEvent(
|
|
388
|
+
task_id=task_id,
|
|
389
|
+
context_id=context_id,
|
|
390
|
+
status=TaskStatus(state=TaskState.working),
|
|
391
|
+
final=False,
|
|
392
|
+
metadata={"agno_event_type": "reasoning_started"},
|
|
393
|
+
)
|
|
394
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
395
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
396
|
+
|
|
397
|
+
elif isinstance(event, (ReasoningStepEvent, TeamReasoningStepEvent)):
|
|
398
|
+
if event.reasoning_content:
|
|
399
|
+
# Send reasoning step as a message
|
|
400
|
+
reasoning_message = A2AMessage(
|
|
401
|
+
message_id=str(uuid4()),
|
|
402
|
+
role=Role.agent,
|
|
403
|
+
parts=[
|
|
404
|
+
Part(
|
|
405
|
+
root=TextPart(
|
|
406
|
+
text=event.reasoning_content,
|
|
407
|
+
metadata={
|
|
408
|
+
"step_type": event.content_type if event.content_type else "str",
|
|
409
|
+
},
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
],
|
|
413
|
+
context_id=context_id,
|
|
414
|
+
task_id=task_id,
|
|
415
|
+
metadata={"agno_content_category": "reasoning", "agno_event_type": "reasoning_step"},
|
|
416
|
+
)
|
|
417
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=reasoning_message)
|
|
418
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
419
|
+
|
|
420
|
+
elif isinstance(event, (ReasoningCompletedEvent, TeamReasoningCompletedEvent)):
|
|
421
|
+
status_event = TaskStatusUpdateEvent(
|
|
422
|
+
task_id=task_id,
|
|
423
|
+
context_id=context_id,
|
|
424
|
+
status=TaskStatus(state=TaskState.working),
|
|
425
|
+
final=False,
|
|
426
|
+
metadata={"agno_event_type": "reasoning_completed"},
|
|
427
|
+
)
|
|
428
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
429
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
430
|
+
|
|
431
|
+
# Send memory update events
|
|
432
|
+
elif isinstance(event, (MemoryUpdateStartedEvent, TeamMemoryUpdateStartedEvent)):
|
|
433
|
+
status_event = TaskStatusUpdateEvent(
|
|
434
|
+
task_id=task_id,
|
|
435
|
+
context_id=context_id,
|
|
436
|
+
status=TaskStatus(state=TaskState.working),
|
|
437
|
+
final=False,
|
|
438
|
+
metadata={"agno_event_type": "memory_update_started"},
|
|
439
|
+
)
|
|
440
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
441
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
442
|
+
|
|
443
|
+
elif isinstance(event, (MemoryUpdateCompletedEvent, TeamMemoryUpdateCompletedEvent)):
|
|
444
|
+
status_event = TaskStatusUpdateEvent(
|
|
445
|
+
task_id=task_id,
|
|
446
|
+
context_id=context_id,
|
|
447
|
+
status=TaskStatus(state=TaskState.working),
|
|
448
|
+
final=False,
|
|
449
|
+
metadata={"agno_event_type": "memory_update_completed"},
|
|
450
|
+
)
|
|
451
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
452
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
453
|
+
|
|
454
|
+
# Send workflow events
|
|
455
|
+
elif isinstance(event, WorkflowStepStartedEvent):
|
|
456
|
+
metadata = {"agno_event_type": "workflow_step_started"}
|
|
457
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
458
|
+
metadata["step_name"] = event.step_name
|
|
459
|
+
|
|
460
|
+
status_event = TaskStatusUpdateEvent(
|
|
461
|
+
task_id=task_id,
|
|
462
|
+
context_id=context_id,
|
|
463
|
+
status=TaskStatus(state=TaskState.working),
|
|
464
|
+
final=False,
|
|
465
|
+
metadata=metadata,
|
|
466
|
+
)
|
|
467
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
468
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
469
|
+
|
|
470
|
+
elif isinstance(event, WorkflowStepCompletedEvent):
|
|
471
|
+
metadata = {"agno_event_type": "workflow_step_completed"}
|
|
472
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
473
|
+
metadata["step_name"] = event.step_name
|
|
474
|
+
|
|
475
|
+
status_event = TaskStatusUpdateEvent(
|
|
476
|
+
task_id=task_id,
|
|
477
|
+
context_id=context_id,
|
|
478
|
+
status=TaskStatus(state=TaskState.working),
|
|
479
|
+
final=False,
|
|
480
|
+
metadata=metadata,
|
|
481
|
+
)
|
|
482
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
483
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
484
|
+
|
|
485
|
+
elif isinstance(event, WorkflowStepErrorEvent):
|
|
486
|
+
metadata = {"agno_event_type": "workflow_step_error"}
|
|
487
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
488
|
+
metadata["step_name"] = event.step_name
|
|
489
|
+
if hasattr(event, "error") and event.error:
|
|
490
|
+
metadata["error"] = event.error
|
|
491
|
+
|
|
492
|
+
status_event = TaskStatusUpdateEvent(
|
|
493
|
+
task_id=task_id,
|
|
494
|
+
context_id=context_id,
|
|
495
|
+
status=TaskStatus(state=TaskState.working),
|
|
496
|
+
final=False,
|
|
497
|
+
metadata=metadata,
|
|
498
|
+
)
|
|
499
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
500
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
501
|
+
|
|
502
|
+
# Send loop events
|
|
503
|
+
elif isinstance(event, LoopExecutionStartedEvent):
|
|
504
|
+
metadata = {"agno_event_type": "loop_execution_started"}
|
|
505
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
506
|
+
metadata["step_name"] = event.step_name
|
|
507
|
+
if hasattr(event, "max_iterations") and event.max_iterations:
|
|
508
|
+
metadata["max_iterations"] = event.max_iterations
|
|
509
|
+
|
|
510
|
+
status_event = TaskStatusUpdateEvent(
|
|
511
|
+
task_id=task_id,
|
|
512
|
+
context_id=context_id,
|
|
513
|
+
status=TaskStatus(state=TaskState.working),
|
|
514
|
+
final=False,
|
|
515
|
+
metadata=metadata,
|
|
516
|
+
)
|
|
517
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
518
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
519
|
+
|
|
520
|
+
elif isinstance(event, LoopIterationStartedEvent):
|
|
521
|
+
metadata = {"agno_event_type": "loop_iteration_started"}
|
|
522
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
523
|
+
metadata["step_name"] = event.step_name
|
|
524
|
+
if hasattr(event, "iteration") and event.iteration is not None:
|
|
525
|
+
metadata["iteration"] = event.iteration
|
|
526
|
+
if hasattr(event, "max_iterations") and event.max_iterations:
|
|
527
|
+
metadata["max_iterations"] = event.max_iterations
|
|
528
|
+
|
|
529
|
+
status_event = TaskStatusUpdateEvent(
|
|
530
|
+
task_id=task_id,
|
|
531
|
+
context_id=context_id,
|
|
532
|
+
status=TaskStatus(state=TaskState.working),
|
|
533
|
+
final=False,
|
|
534
|
+
metadata=metadata,
|
|
535
|
+
)
|
|
536
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
537
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
538
|
+
|
|
539
|
+
elif isinstance(event, LoopIterationCompletedEvent):
|
|
540
|
+
metadata = {"agno_event_type": "loop_iteration_completed"}
|
|
541
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
542
|
+
metadata["step_name"] = event.step_name
|
|
543
|
+
if hasattr(event, "iteration") and event.iteration is not None:
|
|
544
|
+
metadata["iteration"] = event.iteration
|
|
545
|
+
if hasattr(event, "should_continue") and event.should_continue is not None:
|
|
546
|
+
metadata["should_continue"] = event.should_continue
|
|
547
|
+
|
|
548
|
+
status_event = TaskStatusUpdateEvent(
|
|
549
|
+
task_id=task_id,
|
|
550
|
+
context_id=context_id,
|
|
551
|
+
status=TaskStatus(state=TaskState.working),
|
|
552
|
+
final=False,
|
|
553
|
+
metadata=metadata,
|
|
554
|
+
)
|
|
555
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
556
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
557
|
+
|
|
558
|
+
elif isinstance(event, LoopExecutionCompletedEvent):
|
|
559
|
+
metadata = {"agno_event_type": "loop_execution_completed"}
|
|
560
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
561
|
+
metadata["step_name"] = event.step_name
|
|
562
|
+
if hasattr(event, "total_iterations") and event.total_iterations is not None:
|
|
563
|
+
metadata["total_iterations"] = event.total_iterations
|
|
564
|
+
|
|
565
|
+
status_event = TaskStatusUpdateEvent(
|
|
566
|
+
task_id=task_id,
|
|
567
|
+
context_id=context_id,
|
|
568
|
+
status=TaskStatus(state=TaskState.working),
|
|
569
|
+
final=False,
|
|
570
|
+
metadata=metadata,
|
|
571
|
+
)
|
|
572
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
573
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
574
|
+
|
|
575
|
+
# Send parallel events
|
|
576
|
+
elif isinstance(event, ParallelExecutionStartedEvent):
|
|
577
|
+
metadata = {"agno_event_type": "parallel_execution_started"}
|
|
578
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
579
|
+
metadata["step_name"] = event.step_name
|
|
580
|
+
if hasattr(event, "parallel_step_count") and event.parallel_step_count:
|
|
581
|
+
metadata["parallel_step_count"] = event.parallel_step_count
|
|
582
|
+
|
|
583
|
+
status_event = TaskStatusUpdateEvent(
|
|
584
|
+
task_id=task_id,
|
|
585
|
+
context_id=context_id,
|
|
586
|
+
status=TaskStatus(state=TaskState.working),
|
|
587
|
+
final=False,
|
|
588
|
+
metadata=metadata,
|
|
589
|
+
)
|
|
590
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
591
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
592
|
+
|
|
593
|
+
elif isinstance(event, ParallelExecutionCompletedEvent):
|
|
594
|
+
metadata = {"agno_event_type": "parallel_execution_completed"}
|
|
595
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
596
|
+
metadata["step_name"] = event.step_name
|
|
597
|
+
if hasattr(event, "parallel_step_count") and event.parallel_step_count:
|
|
598
|
+
metadata["parallel_step_count"] = event.parallel_step_count
|
|
599
|
+
|
|
600
|
+
status_event = TaskStatusUpdateEvent(
|
|
601
|
+
task_id=task_id,
|
|
602
|
+
context_id=context_id,
|
|
603
|
+
status=TaskStatus(state=TaskState.working),
|
|
604
|
+
final=False,
|
|
605
|
+
metadata=metadata,
|
|
606
|
+
)
|
|
607
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
608
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
609
|
+
|
|
610
|
+
# Send condition events
|
|
611
|
+
elif isinstance(event, ConditionExecutionStartedEvent):
|
|
612
|
+
metadata = {"agno_event_type": "condition_execution_started"}
|
|
613
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
614
|
+
metadata["step_name"] = event.step_name
|
|
615
|
+
if hasattr(event, "condition_result") and event.condition_result is not None:
|
|
616
|
+
metadata["condition_result"] = event.condition_result
|
|
617
|
+
|
|
618
|
+
status_event = TaskStatusUpdateEvent(
|
|
619
|
+
task_id=task_id,
|
|
620
|
+
context_id=context_id,
|
|
621
|
+
status=TaskStatus(state=TaskState.working),
|
|
622
|
+
final=False,
|
|
623
|
+
metadata=metadata,
|
|
624
|
+
)
|
|
625
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
626
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
627
|
+
|
|
628
|
+
elif isinstance(event, ConditionExecutionCompletedEvent):
|
|
629
|
+
metadata = {"agno_event_type": "condition_execution_completed"}
|
|
630
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
631
|
+
metadata["step_name"] = event.step_name
|
|
632
|
+
if hasattr(event, "condition_result") and event.condition_result is not None:
|
|
633
|
+
metadata["condition_result"] = event.condition_result
|
|
634
|
+
if hasattr(event, "executed_steps") and event.executed_steps is not None:
|
|
635
|
+
metadata["executed_steps"] = event.executed_steps
|
|
636
|
+
|
|
637
|
+
status_event = TaskStatusUpdateEvent(
|
|
638
|
+
task_id=task_id,
|
|
639
|
+
context_id=context_id,
|
|
640
|
+
status=TaskStatus(state=TaskState.working),
|
|
641
|
+
final=False,
|
|
642
|
+
metadata=metadata,
|
|
643
|
+
)
|
|
644
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
645
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
646
|
+
|
|
647
|
+
# Send router events
|
|
648
|
+
elif isinstance(event, RouterExecutionStartedEvent):
|
|
649
|
+
metadata = {"agno_event_type": "router_execution_started"}
|
|
650
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
651
|
+
metadata["step_name"] = event.step_name
|
|
652
|
+
if hasattr(event, "selected_steps") and event.selected_steps:
|
|
653
|
+
metadata["selected_steps"] = event.selected_steps
|
|
654
|
+
|
|
655
|
+
status_event = TaskStatusUpdateEvent(
|
|
656
|
+
task_id=task_id,
|
|
657
|
+
context_id=context_id,
|
|
658
|
+
status=TaskStatus(state=TaskState.working),
|
|
659
|
+
final=False,
|
|
660
|
+
metadata=metadata,
|
|
661
|
+
)
|
|
662
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
663
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
664
|
+
|
|
665
|
+
elif isinstance(event, RouterExecutionCompletedEvent):
|
|
666
|
+
metadata = {"agno_event_type": "router_execution_completed"}
|
|
667
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
668
|
+
metadata["step_name"] = event.step_name
|
|
669
|
+
if hasattr(event, "selected_steps") and event.selected_steps:
|
|
670
|
+
metadata["selected_steps"] = event.selected_steps
|
|
671
|
+
if hasattr(event, "executed_steps") and event.executed_steps is not None:
|
|
672
|
+
metadata["executed_steps"] = event.executed_steps
|
|
673
|
+
|
|
674
|
+
status_event = TaskStatusUpdateEvent(
|
|
675
|
+
task_id=task_id,
|
|
676
|
+
context_id=context_id,
|
|
677
|
+
status=TaskStatus(state=TaskState.working),
|
|
678
|
+
final=False,
|
|
679
|
+
metadata=metadata,
|
|
680
|
+
)
|
|
681
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
682
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
683
|
+
|
|
684
|
+
# Send steps events
|
|
685
|
+
elif isinstance(event, StepsExecutionStartedEvent):
|
|
686
|
+
metadata = {"agno_event_type": "steps_execution_started"}
|
|
687
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
688
|
+
metadata["step_name"] = event.step_name
|
|
689
|
+
if hasattr(event, "steps_count") and event.steps_count:
|
|
690
|
+
metadata["steps_count"] = event.steps_count
|
|
691
|
+
|
|
692
|
+
status_event = TaskStatusUpdateEvent(
|
|
693
|
+
task_id=task_id,
|
|
694
|
+
context_id=context_id,
|
|
695
|
+
status=TaskStatus(state=TaskState.working),
|
|
696
|
+
final=False,
|
|
697
|
+
metadata=metadata,
|
|
698
|
+
)
|
|
699
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
700
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
701
|
+
|
|
702
|
+
elif isinstance(event, StepsExecutionCompletedEvent):
|
|
703
|
+
metadata = {"agno_event_type": "steps_execution_completed"}
|
|
704
|
+
if hasattr(event, "step_name") and event.step_name:
|
|
705
|
+
metadata["step_name"] = event.step_name
|
|
706
|
+
if hasattr(event, "steps_count") and event.steps_count:
|
|
707
|
+
metadata["steps_count"] = event.steps_count
|
|
708
|
+
if hasattr(event, "executed_steps") and event.executed_steps is not None:
|
|
709
|
+
metadata["executed_steps"] = event.executed_steps
|
|
710
|
+
|
|
711
|
+
status_event = TaskStatusUpdateEvent(
|
|
712
|
+
task_id=task_id,
|
|
713
|
+
context_id=context_id,
|
|
714
|
+
status=TaskStatus(state=TaskState.working),
|
|
715
|
+
final=False,
|
|
716
|
+
metadata=metadata,
|
|
717
|
+
)
|
|
718
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
719
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
720
|
+
|
|
721
|
+
# Capture completion event for final task construction
|
|
722
|
+
elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent, WorkflowCompletedEvent)):
|
|
723
|
+
completion_event = event
|
|
724
|
+
|
|
725
|
+
# Capture cancelled event for final task construction
|
|
726
|
+
elif isinstance(event, (RunCancelledEvent, TeamRunCancelledEvent, WorkflowCancelledEvent)):
|
|
727
|
+
cancelled_event = event
|
|
728
|
+
|
|
729
|
+
# 3. Send final status event
|
|
730
|
+
# If cancelled, send canceled status; otherwise send completed
|
|
731
|
+
if cancelled_event:
|
|
732
|
+
final_state = TaskState.canceled
|
|
733
|
+
metadata = {"agno_event_type": "run_cancelled"}
|
|
734
|
+
if hasattr(cancelled_event, "reason") and cancelled_event.reason:
|
|
735
|
+
metadata["reason"] = cancelled_event.reason
|
|
736
|
+
final_status_event = TaskStatusUpdateEvent(
|
|
737
|
+
task_id=task_id,
|
|
738
|
+
context_id=context_id,
|
|
739
|
+
status=TaskStatus(state=final_state),
|
|
740
|
+
final=True,
|
|
741
|
+
metadata=metadata,
|
|
742
|
+
)
|
|
743
|
+
else:
|
|
744
|
+
final_status_event = TaskStatusUpdateEvent(
|
|
745
|
+
task_id=task_id,
|
|
746
|
+
context_id=context_id,
|
|
747
|
+
status=TaskStatus(state=TaskState.completed),
|
|
748
|
+
final=True,
|
|
749
|
+
)
|
|
750
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=final_status_event)
|
|
751
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
752
|
+
|
|
753
|
+
# 4. Send final task
|
|
754
|
+
# Handle cancelled case
|
|
755
|
+
if cancelled_event:
|
|
756
|
+
cancel_message = "Run was cancelled"
|
|
757
|
+
if hasattr(cancelled_event, "reason") and cancelled_event.reason:
|
|
758
|
+
cancel_message = f"Run was cancelled: {cancelled_event.reason}"
|
|
759
|
+
|
|
760
|
+
parts: List[Part] = []
|
|
761
|
+
if accumulated_content:
|
|
762
|
+
parts.append(Part(root=TextPart(text=accumulated_content)))
|
|
763
|
+
parts.append(Part(root=TextPart(text=cancel_message)))
|
|
764
|
+
|
|
765
|
+
final_message = A2AMessage(
|
|
766
|
+
message_id=message_id,
|
|
767
|
+
role=Role.agent,
|
|
768
|
+
parts=parts,
|
|
769
|
+
context_id=context_id,
|
|
770
|
+
task_id=task_id,
|
|
771
|
+
metadata={"agno_event_type": "run_cancelled"},
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
task = Task(
|
|
775
|
+
id=task_id,
|
|
776
|
+
context_id=context_id,
|
|
777
|
+
status=TaskStatus(state=TaskState.canceled),
|
|
778
|
+
history=[final_message],
|
|
779
|
+
)
|
|
780
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
|
|
781
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
782
|
+
return
|
|
783
|
+
|
|
784
|
+
# Build from completion_event if available, otherwise use accumulated content
|
|
785
|
+
if completion_event:
|
|
786
|
+
final_content = completion_event.content if completion_event.content else accumulated_content
|
|
787
|
+
|
|
788
|
+
final_parts: List[Part] = []
|
|
789
|
+
if final_content:
|
|
790
|
+
final_parts.append(Part(root=TextPart(text=str(final_content))))
|
|
791
|
+
|
|
792
|
+
# Handle all media artifacts
|
|
793
|
+
artifacts: List[Artifact] = []
|
|
794
|
+
if hasattr(completion_event, "images") and completion_event.images:
|
|
795
|
+
for idx, image in enumerate(completion_event.images):
|
|
796
|
+
artifact_parts = []
|
|
797
|
+
if image.url:
|
|
798
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=image.url, mime_type="image/*"))))
|
|
799
|
+
artifacts.append(
|
|
800
|
+
Artifact(
|
|
801
|
+
artifact_id=f"image-{idx}",
|
|
802
|
+
name=getattr(image, "name", None) or f"image-{idx}",
|
|
803
|
+
description="Image generated during task",
|
|
804
|
+
parts=artifact_parts,
|
|
805
|
+
)
|
|
806
|
+
)
|
|
807
|
+
if hasattr(completion_event, "videos") and completion_event.videos:
|
|
808
|
+
for idx, video in enumerate(completion_event.videos):
|
|
809
|
+
artifact_parts = []
|
|
810
|
+
if video.url:
|
|
811
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=video.url, mime_type="video/*"))))
|
|
812
|
+
artifacts.append(
|
|
813
|
+
Artifact(
|
|
814
|
+
artifact_id=f"video-{idx}",
|
|
815
|
+
name=getattr(video, "name", None) or f"video-{idx}",
|
|
816
|
+
description="Video generated during task",
|
|
817
|
+
parts=artifact_parts,
|
|
818
|
+
)
|
|
819
|
+
)
|
|
820
|
+
if hasattr(completion_event, "audio") and completion_event.audio:
|
|
821
|
+
for idx, audio in enumerate(completion_event.audio):
|
|
822
|
+
artifact_parts = []
|
|
823
|
+
if audio.url:
|
|
824
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=audio.url, mime_type="audio/*"))))
|
|
825
|
+
artifacts.append(
|
|
826
|
+
Artifact(
|
|
827
|
+
artifact_id=f"audio-{idx}",
|
|
828
|
+
name=getattr(audio, "name", None) or f"audio-{idx}",
|
|
829
|
+
description="Audio generated during task",
|
|
830
|
+
parts=artifact_parts,
|
|
831
|
+
)
|
|
832
|
+
)
|
|
833
|
+
if hasattr(completion_event, "response_audio") and completion_event.response_audio:
|
|
834
|
+
audio = completion_event.response_audio
|
|
835
|
+
artifact_parts = []
|
|
836
|
+
if audio.url:
|
|
837
|
+
artifact_parts.append(Part(root=FilePart(file=FileWithUri(uri=audio.url, mime_type="audio/*"))))
|
|
838
|
+
artifacts.append(
|
|
839
|
+
Artifact(
|
|
840
|
+
artifact_id="response-audio",
|
|
841
|
+
name=getattr(audio, "name", None) or "response-audio",
|
|
842
|
+
description="Audio response from agent",
|
|
843
|
+
parts=artifact_parts,
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
# Handle all other data as Message metadata
|
|
848
|
+
final_metadata: Dict[str, Any] = {}
|
|
849
|
+
if hasattr(completion_event, "metrics") and completion_event.metrics:
|
|
850
|
+
final_metadata["metrics"] = completion_event.metrics.__dict__
|
|
851
|
+
if hasattr(completion_event, "metadata") and completion_event.metadata:
|
|
852
|
+
final_metadata.update(completion_event.metadata)
|
|
853
|
+
|
|
854
|
+
final_message = A2AMessage(
|
|
855
|
+
message_id=message_id,
|
|
856
|
+
role=Role.agent,
|
|
857
|
+
parts=final_parts,
|
|
858
|
+
context_id=context_id,
|
|
859
|
+
task_id=task_id,
|
|
860
|
+
metadata=final_metadata if final_metadata else None,
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
else:
|
|
864
|
+
# Fallback in case we didn't find the completion event, using accumulated content
|
|
865
|
+
final_message = A2AMessage(
|
|
866
|
+
message_id=message_id,
|
|
867
|
+
role=Role.agent,
|
|
868
|
+
parts=[Part(root=TextPart(text=accumulated_content))] if accumulated_content else [],
|
|
869
|
+
context_id=context_id,
|
|
870
|
+
task_id=task_id,
|
|
871
|
+
)
|
|
872
|
+
artifacts = []
|
|
873
|
+
|
|
874
|
+
# Build and return the final Task
|
|
875
|
+
task = Task(
|
|
876
|
+
id=task_id,
|
|
877
|
+
context_id=context_id,
|
|
878
|
+
status=TaskStatus(state=TaskState.completed),
|
|
879
|
+
history=[final_message],
|
|
880
|
+
artifacts=artifacts if artifacts else None,
|
|
881
|
+
)
|
|
882
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
|
|
883
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
async def stream_a2a_response_with_error_handling(
|
|
887
|
+
event_stream: AsyncIterator[Union[RunOutputEvent, TeamRunOutputEvent, WorkflowRunOutputEvent, RunOutput]],
|
|
888
|
+
request_id: Union[str, int],
|
|
889
|
+
) -> AsyncIterator[str]:
|
|
890
|
+
"""Wrapper around stream_a2a_response to handle critical errors."""
|
|
891
|
+
task_id: str = str(uuid4())
|
|
892
|
+
context_id: str = str(uuid4())
|
|
893
|
+
|
|
894
|
+
try:
|
|
895
|
+
async for response_chunk in stream_a2a_response(event_stream, request_id):
|
|
896
|
+
yield response_chunk
|
|
897
|
+
|
|
898
|
+
# Catch any critical errors, emit the expected status task and close the stream
|
|
899
|
+
except Exception as e:
|
|
900
|
+
failed_status_event = TaskStatusUpdateEvent(
|
|
901
|
+
task_id=task_id,
|
|
902
|
+
context_id=context_id,
|
|
903
|
+
status=TaskStatus(state=TaskState.failed),
|
|
904
|
+
final=True,
|
|
905
|
+
)
|
|
906
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_status_event)
|
|
907
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|
|
908
|
+
|
|
909
|
+
# Send failed Task
|
|
910
|
+
error_message = A2AMessage(
|
|
911
|
+
message_id=str(uuid4()),
|
|
912
|
+
role=Role.agent,
|
|
913
|
+
parts=[Part(root=TextPart(text=f"Error: {str(e)}"))],
|
|
914
|
+
context_id=context_id,
|
|
915
|
+
)
|
|
916
|
+
failed_task = Task(
|
|
917
|
+
id=task_id,
|
|
918
|
+
context_id=context_id,
|
|
919
|
+
status=TaskStatus(state=TaskState.failed),
|
|
920
|
+
history=[error_message],
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_task)
|
|
924
|
+
yield json.dumps(response.model_dump(exclude_none=True)) + "\n"
|