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/run/agent.py
CHANGED
|
@@ -11,6 +11,7 @@ from agno.models.metrics import Metrics
|
|
|
11
11
|
from agno.models.response import ToolExecution
|
|
12
12
|
from agno.reasoning.step import ReasoningStep
|
|
13
13
|
from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
|
|
14
|
+
from agno.run.requirement import RunRequirement
|
|
14
15
|
from agno.utils.log import logger
|
|
15
16
|
from agno.utils.media import (
|
|
16
17
|
reconstruct_audio_list,
|
|
@@ -54,8 +55,11 @@ class RunInput:
|
|
|
54
55
|
return self.input_content.model_dump_json(exclude_none=True)
|
|
55
56
|
elif isinstance(self.input_content, Message):
|
|
56
57
|
return json.dumps(self.input_content.to_dict())
|
|
57
|
-
elif isinstance(self.input_content, list)
|
|
58
|
-
|
|
58
|
+
elif isinstance(self.input_content, list):
|
|
59
|
+
try:
|
|
60
|
+
return json.dumps(self.to_dict().get("input_content"))
|
|
61
|
+
except Exception:
|
|
62
|
+
return str(self.input_content)
|
|
59
63
|
else:
|
|
60
64
|
return str(self.input_content)
|
|
61
65
|
|
|
@@ -70,22 +74,15 @@ class RunInput:
|
|
|
70
74
|
result["input_content"] = self.input_content.model_dump(exclude_none=True)
|
|
71
75
|
elif isinstance(self.input_content, Message):
|
|
72
76
|
result["input_content"] = self.input_content.to_dict()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Handle input_content provided as a list of dicts
|
|
83
|
-
elif (
|
|
84
|
-
isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
|
|
85
|
-
):
|
|
86
|
-
for content in self.input_content:
|
|
87
|
-
# Handle media input
|
|
88
|
-
if isinstance(content, dict):
|
|
77
|
+
elif isinstance(self.input_content, list):
|
|
78
|
+
serialized_items: List[Any] = []
|
|
79
|
+
for item in self.input_content:
|
|
80
|
+
if isinstance(item, Message):
|
|
81
|
+
serialized_items.append(item.to_dict())
|
|
82
|
+
elif isinstance(item, BaseModel):
|
|
83
|
+
serialized_items.append(item.model_dump(exclude_none=True))
|
|
84
|
+
elif isinstance(item, dict):
|
|
85
|
+
content = dict(item)
|
|
89
86
|
if content.get("images"):
|
|
90
87
|
content["images"] = [
|
|
91
88
|
img.to_dict() if isinstance(img, Image) else img for img in content["images"]
|
|
@@ -102,7 +99,11 @@ class RunInput:
|
|
|
102
99
|
content["files"] = [
|
|
103
100
|
file.to_dict() if isinstance(file, File) else file for file in content["files"]
|
|
104
101
|
]
|
|
105
|
-
|
|
102
|
+
serialized_items.append(content)
|
|
103
|
+
else:
|
|
104
|
+
serialized_items.append(item)
|
|
105
|
+
|
|
106
|
+
result["input_content"] = serialized_items
|
|
106
107
|
else:
|
|
107
108
|
result["input_content"] = self.input_content
|
|
108
109
|
|
|
@@ -152,9 +153,11 @@ class RunEvent(str, Enum):
|
|
|
152
153
|
|
|
153
154
|
tool_call_started = "ToolCallStarted"
|
|
154
155
|
tool_call_completed = "ToolCallCompleted"
|
|
156
|
+
tool_call_error = "ToolCallError"
|
|
155
157
|
|
|
156
158
|
reasoning_started = "ReasoningStarted"
|
|
157
159
|
reasoning_step = "ReasoningStep"
|
|
160
|
+
reasoning_content_delta = "ReasoningContentDelta"
|
|
158
161
|
reasoning_completed = "ReasoningCompleted"
|
|
159
162
|
|
|
160
163
|
memory_update_started = "MemoryUpdateStarted"
|
|
@@ -169,6 +172,12 @@ class RunEvent(str, Enum):
|
|
|
169
172
|
output_model_response_started = "OutputModelResponseStarted"
|
|
170
173
|
output_model_response_completed = "OutputModelResponseCompleted"
|
|
171
174
|
|
|
175
|
+
model_request_started = "ModelRequestStarted"
|
|
176
|
+
model_request_completed = "ModelRequestCompleted"
|
|
177
|
+
|
|
178
|
+
compression_started = "CompressionStarted"
|
|
179
|
+
compression_completed = "CompressionCompleted"
|
|
180
|
+
|
|
172
181
|
custom_event = "CustomEvent"
|
|
173
182
|
|
|
174
183
|
|
|
@@ -273,11 +282,18 @@ class RunCompletedEvent(BaseAgentRunEvent):
|
|
|
273
282
|
class RunPausedEvent(BaseAgentRunEvent):
|
|
274
283
|
event: str = RunEvent.run_paused.value
|
|
275
284
|
tools: Optional[List[ToolExecution]] = None
|
|
285
|
+
requirements: Optional[List[RunRequirement]] = None
|
|
276
286
|
|
|
277
287
|
@property
|
|
278
288
|
def is_paused(self):
|
|
279
289
|
return True
|
|
280
290
|
|
|
291
|
+
@property
|
|
292
|
+
def active_requirements(self) -> List[RunRequirement]:
|
|
293
|
+
if not self.requirements:
|
|
294
|
+
return []
|
|
295
|
+
return [requirement for requirement in self.requirements if not requirement.is_resolved()]
|
|
296
|
+
|
|
281
297
|
|
|
282
298
|
@dataclass
|
|
283
299
|
class RunContinuedEvent(BaseAgentRunEvent):
|
|
@@ -339,6 +355,7 @@ class MemoryUpdateStartedEvent(BaseAgentRunEvent):
|
|
|
339
355
|
@dataclass
|
|
340
356
|
class MemoryUpdateCompletedEvent(BaseAgentRunEvent):
|
|
341
357
|
event: str = RunEvent.memory_update_completed.value
|
|
358
|
+
memories: Optional[List[Any]] = None
|
|
342
359
|
|
|
343
360
|
|
|
344
361
|
@dataclass
|
|
@@ -365,6 +382,14 @@ class ReasoningStepEvent(BaseAgentRunEvent):
|
|
|
365
382
|
reasoning_content: str = ""
|
|
366
383
|
|
|
367
384
|
|
|
385
|
+
@dataclass
|
|
386
|
+
class ReasoningContentDeltaEvent(BaseAgentRunEvent):
|
|
387
|
+
"""Event for streaming reasoning content chunks as they arrive."""
|
|
388
|
+
|
|
389
|
+
event: str = RunEvent.reasoning_content_delta.value
|
|
390
|
+
reasoning_content: str = "" # The delta/chunk of reasoning content
|
|
391
|
+
|
|
392
|
+
|
|
368
393
|
@dataclass
|
|
369
394
|
class ReasoningCompletedEvent(BaseAgentRunEvent):
|
|
370
395
|
event: str = RunEvent.reasoning_completed.value
|
|
@@ -388,6 +413,13 @@ class ToolCallCompletedEvent(BaseAgentRunEvent):
|
|
|
388
413
|
audio: Optional[List[Audio]] = None # Audio produced by the tool call
|
|
389
414
|
|
|
390
415
|
|
|
416
|
+
@dataclass
|
|
417
|
+
class ToolCallErrorEvent(BaseAgentRunEvent):
|
|
418
|
+
event: str = RunEvent.tool_call_error.value
|
|
419
|
+
tool: Optional[ToolExecution] = None
|
|
420
|
+
error: Optional[str] = None
|
|
421
|
+
|
|
422
|
+
|
|
391
423
|
@dataclass
|
|
392
424
|
class ParserModelResponseStartedEvent(BaseAgentRunEvent):
|
|
393
425
|
event: str = RunEvent.parser_model_response_started.value
|
|
@@ -408,9 +440,53 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
|
|
|
408
440
|
event: str = RunEvent.output_model_response_completed.value
|
|
409
441
|
|
|
410
442
|
|
|
443
|
+
@dataclass
|
|
444
|
+
class ModelRequestStartedEvent(BaseAgentRunEvent):
|
|
445
|
+
"""Event sent when a model request is about to be made"""
|
|
446
|
+
|
|
447
|
+
event: str = RunEvent.model_request_started.value
|
|
448
|
+
model: Optional[str] = None
|
|
449
|
+
model_provider: Optional[str] = None
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass
|
|
453
|
+
class ModelRequestCompletedEvent(BaseAgentRunEvent):
|
|
454
|
+
"""Event sent when a model request has completed"""
|
|
455
|
+
|
|
456
|
+
event: str = RunEvent.model_request_completed.value
|
|
457
|
+
model: Optional[str] = None
|
|
458
|
+
model_provider: Optional[str] = None
|
|
459
|
+
input_tokens: Optional[int] = None
|
|
460
|
+
output_tokens: Optional[int] = None
|
|
461
|
+
total_tokens: Optional[int] = None
|
|
462
|
+
time_to_first_token: Optional[float] = None
|
|
463
|
+
reasoning_tokens: Optional[int] = None
|
|
464
|
+
cache_read_tokens: Optional[int] = None
|
|
465
|
+
cache_write_tokens: Optional[int] = None
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@dataclass
|
|
469
|
+
class CompressionStartedEvent(BaseAgentRunEvent):
|
|
470
|
+
"""Event sent when tool result compression is about to start"""
|
|
471
|
+
|
|
472
|
+
event: str = RunEvent.compression_started.value
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@dataclass
|
|
476
|
+
class CompressionCompletedEvent(BaseAgentRunEvent):
|
|
477
|
+
"""Event sent when tool result compression has completed"""
|
|
478
|
+
|
|
479
|
+
event: str = RunEvent.compression_completed.value
|
|
480
|
+
tool_results_compressed: Optional[int] = None
|
|
481
|
+
original_size: Optional[int] = None
|
|
482
|
+
compressed_size: Optional[int] = None
|
|
483
|
+
|
|
484
|
+
|
|
411
485
|
@dataclass
|
|
412
486
|
class CustomEvent(BaseAgentRunEvent):
|
|
413
487
|
event: str = RunEvent.custom_event.value
|
|
488
|
+
# tool_call_id for ToolExecution
|
|
489
|
+
tool_call_id: Optional[str] = None
|
|
414
490
|
|
|
415
491
|
def __init__(self, **kwargs):
|
|
416
492
|
# Store arbitrary attributes directly on the instance
|
|
@@ -434,6 +510,7 @@ RunOutputEvent = Union[
|
|
|
434
510
|
PostHookCompletedEvent,
|
|
435
511
|
ReasoningStartedEvent,
|
|
436
512
|
ReasoningStepEvent,
|
|
513
|
+
ReasoningContentDeltaEvent,
|
|
437
514
|
ReasoningCompletedEvent,
|
|
438
515
|
MemoryUpdateStartedEvent,
|
|
439
516
|
MemoryUpdateCompletedEvent,
|
|
@@ -441,10 +518,15 @@ RunOutputEvent = Union[
|
|
|
441
518
|
SessionSummaryCompletedEvent,
|
|
442
519
|
ToolCallStartedEvent,
|
|
443
520
|
ToolCallCompletedEvent,
|
|
521
|
+
ToolCallErrorEvent,
|
|
444
522
|
ParserModelResponseStartedEvent,
|
|
445
523
|
ParserModelResponseCompletedEvent,
|
|
446
524
|
OutputModelResponseStartedEvent,
|
|
447
525
|
OutputModelResponseCompletedEvent,
|
|
526
|
+
ModelRequestStartedEvent,
|
|
527
|
+
ModelRequestCompletedEvent,
|
|
528
|
+
CompressionStartedEvent,
|
|
529
|
+
CompressionCompletedEvent,
|
|
448
530
|
CustomEvent,
|
|
449
531
|
]
|
|
450
532
|
|
|
@@ -466,6 +548,7 @@ RUN_EVENT_TYPE_REGISTRY = {
|
|
|
466
548
|
RunEvent.post_hook_completed.value: PostHookCompletedEvent,
|
|
467
549
|
RunEvent.reasoning_started.value: ReasoningStartedEvent,
|
|
468
550
|
RunEvent.reasoning_step.value: ReasoningStepEvent,
|
|
551
|
+
RunEvent.reasoning_content_delta.value: ReasoningContentDeltaEvent,
|
|
469
552
|
RunEvent.reasoning_completed.value: ReasoningCompletedEvent,
|
|
470
553
|
RunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
|
|
471
554
|
RunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
|
|
@@ -473,10 +556,15 @@ RUN_EVENT_TYPE_REGISTRY = {
|
|
|
473
556
|
RunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
|
|
474
557
|
RunEvent.tool_call_started.value: ToolCallStartedEvent,
|
|
475
558
|
RunEvent.tool_call_completed.value: ToolCallCompletedEvent,
|
|
559
|
+
RunEvent.tool_call_error.value: ToolCallErrorEvent,
|
|
476
560
|
RunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
|
|
477
561
|
RunEvent.parser_model_response_completed.value: ParserModelResponseCompletedEvent,
|
|
478
562
|
RunEvent.output_model_response_started.value: OutputModelResponseStartedEvent,
|
|
479
563
|
RunEvent.output_model_response_completed.value: OutputModelResponseCompletedEvent,
|
|
564
|
+
RunEvent.model_request_started.value: ModelRequestStartedEvent,
|
|
565
|
+
RunEvent.model_request_completed.value: ModelRequestCompletedEvent,
|
|
566
|
+
RunEvent.compression_started.value: CompressionStartedEvent,
|
|
567
|
+
RunEvent.compression_completed.value: CompressionCompletedEvent,
|
|
480
568
|
RunEvent.custom_event.value: CustomEvent,
|
|
481
569
|
}
|
|
482
570
|
|
|
@@ -539,11 +627,20 @@ class RunOutput:
|
|
|
539
627
|
|
|
540
628
|
status: RunStatus = RunStatus.running
|
|
541
629
|
|
|
630
|
+
# User control flow (HITL) requirements to continue a run when paused, in order of arrival
|
|
631
|
+
requirements: Optional[list[RunRequirement]] = None
|
|
632
|
+
|
|
542
633
|
# === FOREIGN KEY RELATIONSHIPS ===
|
|
543
634
|
# These fields establish relationships to parent workflow/step structures
|
|
544
635
|
# and should be treated as foreign keys for data integrity
|
|
545
636
|
workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
|
|
546
637
|
|
|
638
|
+
@property
|
|
639
|
+
def active_requirements(self) -> list[RunRequirement]:
|
|
640
|
+
if not self.requirements:
|
|
641
|
+
return []
|
|
642
|
+
return [requirement for requirement in self.requirements if not requirement.is_resolved()]
|
|
643
|
+
|
|
547
644
|
@property
|
|
548
645
|
def is_paused(self):
|
|
549
646
|
return self.status == RunStatus.paused
|
|
@@ -572,6 +669,7 @@ class RunOutput:
|
|
|
572
669
|
and k
|
|
573
670
|
not in [
|
|
574
671
|
"messages",
|
|
672
|
+
"metrics",
|
|
575
673
|
"tools",
|
|
576
674
|
"metadata",
|
|
577
675
|
"images",
|
|
@@ -586,6 +684,7 @@ class RunOutput:
|
|
|
586
684
|
"reasoning_steps",
|
|
587
685
|
"reasoning_messages",
|
|
588
686
|
"references",
|
|
687
|
+
"requirements",
|
|
589
688
|
]
|
|
590
689
|
}
|
|
591
690
|
|
|
@@ -671,6 +770,9 @@ class RunOutput:
|
|
|
671
770
|
else:
|
|
672
771
|
_dict["tools"].append(tool)
|
|
673
772
|
|
|
773
|
+
if self.requirements is not None:
|
|
774
|
+
_dict["requirements"] = [req.to_dict() if hasattr(req, "to_dict") else req for req in self.requirements]
|
|
775
|
+
|
|
674
776
|
if self.input is not None:
|
|
675
777
|
_dict["input"] = self.input.to_dict()
|
|
676
778
|
|
|
@@ -717,6 +819,18 @@ class RunOutput:
|
|
|
717
819
|
tools = data.pop("tools", [])
|
|
718
820
|
tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
|
|
719
821
|
|
|
822
|
+
# Handle requirements
|
|
823
|
+
requirements_data = data.pop("requirements", None)
|
|
824
|
+
requirements: Optional[List[RunRequirement]] = None
|
|
825
|
+
if requirements_data is not None:
|
|
826
|
+
requirements_list: List[RunRequirement] = []
|
|
827
|
+
for item in requirements_data:
|
|
828
|
+
if isinstance(item, RunRequirement):
|
|
829
|
+
requirements_list.append(item)
|
|
830
|
+
elif isinstance(item, dict):
|
|
831
|
+
requirements_list.append(RunRequirement.from_dict(item))
|
|
832
|
+
requirements = requirements_list if requirements_list else None
|
|
833
|
+
|
|
720
834
|
images = reconstruct_images(data.pop("images", []))
|
|
721
835
|
videos = reconstruct_videos(data.pop("videos", []))
|
|
722
836
|
audio = reconstruct_audio_list(data.pop("audio", []))
|
|
@@ -771,6 +885,7 @@ class RunOutput:
|
|
|
771
885
|
reasoning_steps=reasoning_steps,
|
|
772
886
|
reasoning_messages=reasoning_messages,
|
|
773
887
|
references=references,
|
|
888
|
+
requirements=requirements,
|
|
774
889
|
**filtered_data,
|
|
775
890
|
)
|
|
776
891
|
|
agno/run/base.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import asdict, dataclass
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
@@ -18,10 +18,14 @@ class RunContext:
|
|
|
18
18
|
session_id: str
|
|
19
19
|
user_id: Optional[str] = None
|
|
20
20
|
|
|
21
|
+
workflow_id: Optional[str] = None
|
|
22
|
+
workflow_name: Optional[str] = None
|
|
23
|
+
|
|
21
24
|
dependencies: Optional[Dict[str, Any]] = None
|
|
22
25
|
knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
23
26
|
metadata: Optional[Dict[str, Any]] = None
|
|
24
27
|
session_state: Optional[Dict[str, Any]] = None
|
|
28
|
+
output_schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
@dataclass
|
|
@@ -49,6 +53,9 @@ class BaseRunOutputEvent:
|
|
|
49
53
|
"additional_input",
|
|
50
54
|
"session_summary",
|
|
51
55
|
"metrics",
|
|
56
|
+
"run_input",
|
|
57
|
+
"requirements",
|
|
58
|
+
"memories",
|
|
52
59
|
]
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -133,6 +140,15 @@ class BaseRunOutputEvent:
|
|
|
133
140
|
if hasattr(self, "session_summary") and self.session_summary is not None:
|
|
134
141
|
_dict["session_summary"] = self.session_summary.to_dict()
|
|
135
142
|
|
|
143
|
+
if hasattr(self, "run_input") and self.run_input is not None:
|
|
144
|
+
_dict["run_input"] = self.run_input.to_dict()
|
|
145
|
+
|
|
146
|
+
if hasattr(self, "requirements") and self.requirements is not None:
|
|
147
|
+
_dict["requirements"] = [req.to_dict() if hasattr(req, "to_dict") else req for req in self.requirements]
|
|
148
|
+
|
|
149
|
+
if hasattr(self, "memories") and self.memories is not None:
|
|
150
|
+
_dict["memories"] = [mem.to_dict() if hasattr(mem, "to_dict") else mem for mem in self.memories]
|
|
151
|
+
|
|
136
152
|
return _dict
|
|
137
153
|
|
|
138
154
|
def to_json(self, separators=(", ", ": "), indent: Optional[int] = 2) -> str:
|
|
@@ -201,7 +217,39 @@ class BaseRunOutputEvent:
|
|
|
201
217
|
|
|
202
218
|
data["session_summary"] = SessionSummary.from_dict(session_summary)
|
|
203
219
|
|
|
220
|
+
run_input = data.pop("run_input", None)
|
|
221
|
+
if run_input:
|
|
222
|
+
from agno.run.team import BaseTeamRunEvent
|
|
223
|
+
|
|
224
|
+
if issubclass(cls, BaseTeamRunEvent):
|
|
225
|
+
from agno.run.team import TeamRunInput
|
|
226
|
+
|
|
227
|
+
data["run_input"] = TeamRunInput.from_dict(run_input)
|
|
228
|
+
else:
|
|
229
|
+
from agno.run.agent import RunInput
|
|
230
|
+
|
|
231
|
+
data["run_input"] = RunInput.from_dict(run_input)
|
|
232
|
+
|
|
233
|
+
# Handle requirements
|
|
234
|
+
|
|
235
|
+
# Handle requirements
|
|
236
|
+
requirements_data = data.pop("requirements", None)
|
|
237
|
+
if requirements_data is not None:
|
|
238
|
+
from agno.run.requirement import RunRequirement
|
|
239
|
+
|
|
240
|
+
requirements_list: List[RunRequirement] = []
|
|
241
|
+
for item in requirements_data:
|
|
242
|
+
if isinstance(item, RunRequirement):
|
|
243
|
+
requirements_list.append(item)
|
|
244
|
+
elif isinstance(item, dict):
|
|
245
|
+
requirements_list.append(RunRequirement.from_dict(item))
|
|
246
|
+
data["requirements"] = requirements_list if requirements_list else None
|
|
247
|
+
|
|
204
248
|
# Filter data to only include fields that are actually defined in the target class
|
|
249
|
+
# CustomEvent accepts arbitrary fields, so skip filtering for it
|
|
250
|
+
if cls.__name__ == "CustomEvent":
|
|
251
|
+
return cls(**data)
|
|
252
|
+
|
|
205
253
|
from dataclasses import fields
|
|
206
254
|
|
|
207
255
|
supported_fields = {f.name for f in fields(cls)}
|
agno/run/cancel.py
CHANGED
|
@@ -1,64 +1,37 @@
|
|
|
1
1
|
"""Run cancellation management."""
|
|
2
2
|
|
|
3
|
-
import threading
|
|
4
3
|
from typing import Dict
|
|
5
4
|
|
|
6
|
-
from agno.
|
|
5
|
+
from agno.run.cancellation_management.base import BaseRunCancellationManager
|
|
6
|
+
from agno.run.cancellation_management.in_memory_cancellation_manager import InMemoryRunCancellationManager
|
|
7
7
|
from agno.utils.log import logger
|
|
8
8
|
|
|
9
|
+
# Global cancellation manager instance
|
|
10
|
+
_cancellation_manager: BaseRunCancellationManager = InMemoryRunCancellationManager()
|
|
9
11
|
|
|
10
|
-
class RunCancellationManager:
|
|
11
|
-
"""Manages cancellation state for agent runs."""
|
|
12
|
-
|
|
13
|
-
def __init__(self):
|
|
14
|
-
self._cancelled_runs: Dict[str, bool] = {}
|
|
15
|
-
self._lock = threading.Lock()
|
|
16
|
-
|
|
17
|
-
def register_run(self, run_id: str) -> None:
|
|
18
|
-
"""Register a new run as not cancelled."""
|
|
19
|
-
with self._lock:
|
|
20
|
-
self._cancelled_runs[run_id] = False
|
|
21
|
-
|
|
22
|
-
def cancel_run(self, run_id: str) -> bool:
|
|
23
|
-
"""Cancel a run by marking it as cancelled.
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
bool: True if run was found and cancelled, False if run not found.
|
|
27
|
-
"""
|
|
28
|
-
with self._lock:
|
|
29
|
-
if run_id in self._cancelled_runs:
|
|
30
|
-
self._cancelled_runs[run_id] = True
|
|
31
|
-
logger.info(f"Run {run_id} marked for cancellation")
|
|
32
|
-
return True
|
|
33
|
-
else:
|
|
34
|
-
logger.warning(f"Attempted to cancel unknown run {run_id}")
|
|
35
|
-
return False
|
|
36
|
-
|
|
37
|
-
def is_cancelled(self, run_id: str) -> bool:
|
|
38
|
-
"""Check if a run is cancelled."""
|
|
39
|
-
with self._lock:
|
|
40
|
-
return self._cancelled_runs.get(run_id, False)
|
|
41
|
-
|
|
42
|
-
def cleanup_run(self, run_id: str) -> None:
|
|
43
|
-
"""Remove a run from tracking (called when run completes)."""
|
|
44
|
-
with self._lock:
|
|
45
|
-
if run_id in self._cancelled_runs:
|
|
46
|
-
del self._cancelled_runs[run_id]
|
|
47
|
-
|
|
48
|
-
def raise_if_cancelled(self, run_id: str) -> None:
|
|
49
|
-
"""Check if a run should be cancelled and raise exception if so."""
|
|
50
|
-
if self.is_cancelled(run_id):
|
|
51
|
-
logger.info(f"Cancelling run {run_id}")
|
|
52
|
-
raise RunCancelledException(f"Run {run_id} was cancelled")
|
|
53
|
-
|
|
54
|
-
def get_active_runs(self) -> Dict[str, bool]:
|
|
55
|
-
"""Get all currently tracked runs and their cancellation status."""
|
|
56
|
-
with self._lock:
|
|
57
|
-
return self._cancelled_runs.copy()
|
|
58
12
|
|
|
13
|
+
def set_cancellation_manager(manager: BaseRunCancellationManager) -> None:
|
|
14
|
+
"""Set a custom cancellation manager.
|
|
59
15
|
|
|
60
|
-
|
|
61
|
-
|
|
16
|
+
Args:
|
|
17
|
+
manager: A BaseRunCancellationManager instance or subclass.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
```python
|
|
21
|
+
class MyCustomManager(BaseRunCancellationManager):
|
|
22
|
+
....
|
|
23
|
+
|
|
24
|
+
set_cancellation_manager(MyCustomManager())
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
global _cancellation_manager
|
|
28
|
+
_cancellation_manager = manager
|
|
29
|
+
logger.info(f"Cancellation manager set to {type(manager).__name__}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_cancellation_manager() -> BaseRunCancellationManager:
|
|
33
|
+
"""Get the current cancellation manager instance."""
|
|
34
|
+
return _cancellation_manager
|
|
62
35
|
|
|
63
36
|
|
|
64
37
|
def register_run(run_id: str) -> None:
|
|
@@ -66,16 +39,56 @@ def register_run(run_id: str) -> None:
|
|
|
66
39
|
_cancellation_manager.register_run(run_id)
|
|
67
40
|
|
|
68
41
|
|
|
42
|
+
async def aregister_run(run_id: str) -> None:
|
|
43
|
+
"""Register a new run for cancellation tracking (async version)."""
|
|
44
|
+
await _cancellation_manager.aregister_run(run_id)
|
|
45
|
+
|
|
46
|
+
|
|
69
47
|
def cancel_run(run_id: str) -> bool:
|
|
70
48
|
"""Cancel a run."""
|
|
71
49
|
return _cancellation_manager.cancel_run(run_id)
|
|
72
50
|
|
|
73
51
|
|
|
52
|
+
async def acancel_run(run_id: str) -> bool:
|
|
53
|
+
"""Cancel a run (async version)."""
|
|
54
|
+
return await _cancellation_manager.acancel_run(run_id)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_cancelled(run_id: str) -> bool:
|
|
58
|
+
"""Check if a run is cancelled."""
|
|
59
|
+
return _cancellation_manager.is_cancelled(run_id)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def ais_cancelled(run_id: str) -> bool:
|
|
63
|
+
"""Check if a run is cancelled (async version)."""
|
|
64
|
+
return await _cancellation_manager.ais_cancelled(run_id)
|
|
65
|
+
|
|
66
|
+
|
|
74
67
|
def cleanup_run(run_id: str) -> None:
|
|
75
68
|
"""Clean up cancellation tracking for a completed run."""
|
|
76
69
|
_cancellation_manager.cleanup_run(run_id)
|
|
77
70
|
|
|
78
71
|
|
|
72
|
+
async def acleanup_run(run_id: str) -> None:
|
|
73
|
+
"""Clean up cancellation tracking for a completed run (async version)."""
|
|
74
|
+
await _cancellation_manager.acleanup_run(run_id)
|
|
75
|
+
|
|
76
|
+
|
|
79
77
|
def raise_if_cancelled(run_id: str) -> None:
|
|
80
78
|
"""Check if a run should be cancelled and raise exception if so."""
|
|
81
79
|
_cancellation_manager.raise_if_cancelled(run_id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def araise_if_cancelled(run_id: str) -> None:
|
|
83
|
+
"""Check if a run should be cancelled and raise exception if so (async version)."""
|
|
84
|
+
await _cancellation_manager.araise_if_cancelled(run_id)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_active_runs() -> Dict[str, bool]:
|
|
88
|
+
"""Get all currently tracked runs and their cancellation status."""
|
|
89
|
+
return _cancellation_manager.get_active_runs()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def aget_active_runs() -> Dict[str, bool]:
|
|
93
|
+
"""Get all currently tracked runs and their cancellation status (async version)."""
|
|
94
|
+
return await _cancellation_manager.aget_active_runs()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from agno.run.cancellation_management.base import BaseRunCancellationManager
|
|
2
|
+
from agno.run.cancellation_management.in_memory_cancellation_manager import InMemoryRunCancellationManager
|
|
3
|
+
from agno.run.cancellation_management.redis_cancellation_manager import RedisRunCancellationManager
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"BaseRunCancellationManager",
|
|
7
|
+
"InMemoryRunCancellationManager",
|
|
8
|
+
"RedisRunCancellationManager",
|
|
9
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseRunCancellationManager(ABC):
|
|
6
|
+
"""Manages cancellation state for agent runs.
|
|
7
|
+
|
|
8
|
+
This class can be extended to implement custom cancellation logic.
|
|
9
|
+
Use set_cancellation_manager() to replace the global instance with your own.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def register_run(self, run_id: str) -> None:
|
|
14
|
+
"""Register a new run as not cancelled."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def aregister_run(self, run_id: str) -> None:
|
|
19
|
+
"""Register a new run as not cancelled (async version)."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def cancel_run(self, run_id: str) -> bool:
|
|
24
|
+
"""Cancel a run by marking it as cancelled.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
bool: True if run was found and cancelled, False if run not found.
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
async def acancel_run(self, run_id: str) -> bool:
|
|
33
|
+
"""Cancel a run by marking it as cancelled (async version).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
bool: True if run was found and cancelled, False if run not found.
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def is_cancelled(self, run_id: str) -> bool:
|
|
42
|
+
"""Check if a run is cancelled."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
async def ais_cancelled(self, run_id: str) -> bool:
|
|
47
|
+
"""Check if a run is cancelled (async version)."""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def cleanup_run(self, run_id: str) -> None:
|
|
52
|
+
"""Remove a run from tracking (called when run completes)."""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def acleanup_run(self, run_id: str) -> None:
|
|
57
|
+
"""Remove a run from tracking (called when run completes) (async version)."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def raise_if_cancelled(self, run_id: str) -> None:
|
|
62
|
+
"""Check if a run should be cancelled and raise exception if so."""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def araise_if_cancelled(self, run_id: str) -> None:
|
|
67
|
+
"""Check if a run should be cancelled and raise exception if so (async version)."""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def get_active_runs(self) -> Dict[str, bool]:
|
|
72
|
+
"""Get all currently tracked runs and their cancellation status."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def aget_active_runs(self) -> Dict[str, bool]:
|
|
77
|
+
"""Get all currently tracked runs and their cancellation status (async version)."""
|
|
78
|
+
pass
|