agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- 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 +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -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 +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- 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 +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -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 +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -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 +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- 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 +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/run/workflow.py
CHANGED
|
@@ -6,9 +6,15 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
from agno.media import Audio, Image, Video
|
|
9
|
-
from agno.run.agent import RunOutput
|
|
9
|
+
from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
|
|
10
10
|
from agno.run.base import BaseRunOutputEvent, RunStatus
|
|
11
|
-
from agno.run.team import TeamRunOutput
|
|
11
|
+
from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
|
|
12
|
+
from agno.utils.media import (
|
|
13
|
+
reconstruct_audio_list,
|
|
14
|
+
reconstruct_images,
|
|
15
|
+
reconstruct_response_audio,
|
|
16
|
+
reconstruct_videos,
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
if TYPE_CHECKING:
|
|
14
20
|
from agno.workflow.types import StepOutput, WorkflowMetrics
|
|
@@ -25,6 +31,9 @@ class WorkflowRunEvent(str, Enum):
|
|
|
25
31
|
workflow_cancelled = "WorkflowCancelled"
|
|
26
32
|
workflow_error = "WorkflowError"
|
|
27
33
|
|
|
34
|
+
workflow_agent_started = "WorkflowAgentStarted"
|
|
35
|
+
workflow_agent_completed = "WorkflowAgentCompleted"
|
|
36
|
+
|
|
28
37
|
step_started = "StepStarted"
|
|
29
38
|
step_completed = "StepCompleted"
|
|
30
39
|
step_error = "StepError"
|
|
@@ -120,6 +129,21 @@ class WorkflowStartedEvent(BaseWorkflowRunOutputEvent):
|
|
|
120
129
|
event: str = WorkflowRunEvent.workflow_started.value
|
|
121
130
|
|
|
122
131
|
|
|
132
|
+
@dataclass
|
|
133
|
+
class WorkflowAgentStartedEvent(BaseWorkflowRunOutputEvent):
|
|
134
|
+
"""Event sent when workflow agent starts (before deciding to run workflow or answer directly)"""
|
|
135
|
+
|
|
136
|
+
event: str = WorkflowRunEvent.workflow_agent_started.value
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class WorkflowAgentCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
141
|
+
"""Event sent when workflow agent completes (after running workflow or answering directly)"""
|
|
142
|
+
|
|
143
|
+
event: str = WorkflowRunEvent.workflow_agent_completed.value
|
|
144
|
+
content: Optional[Any] = None
|
|
145
|
+
|
|
146
|
+
|
|
123
147
|
@dataclass
|
|
124
148
|
class WorkflowCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
125
149
|
"""Event sent when workflow execution completes"""
|
|
@@ -388,10 +412,17 @@ class CustomEvent(BaseWorkflowRunOutputEvent):
|
|
|
388
412
|
|
|
389
413
|
event: str = WorkflowRunEvent.custom_event.value
|
|
390
414
|
|
|
415
|
+
def __init__(self, **kwargs):
|
|
416
|
+
# Store arbitrary attributes directly on the instance
|
|
417
|
+
for key, value in kwargs.items():
|
|
418
|
+
setattr(self, key, value)
|
|
419
|
+
|
|
391
420
|
|
|
392
421
|
# Union type for all workflow run response events
|
|
393
422
|
WorkflowRunOutputEvent = Union[
|
|
394
423
|
WorkflowStartedEvent,
|
|
424
|
+
WorkflowAgentStartedEvent,
|
|
425
|
+
WorkflowAgentCompletedEvent,
|
|
395
426
|
WorkflowCompletedEvent,
|
|
396
427
|
WorkflowErrorEvent,
|
|
397
428
|
WorkflowCancelledEvent,
|
|
@@ -417,6 +448,8 @@ WorkflowRunOutputEvent = Union[
|
|
|
417
448
|
# Map event string to dataclass for workflow events
|
|
418
449
|
WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
|
|
419
450
|
WorkflowRunEvent.workflow_started.value: WorkflowStartedEvent,
|
|
451
|
+
WorkflowRunEvent.workflow_agent_started.value: WorkflowAgentStartedEvent,
|
|
452
|
+
WorkflowRunEvent.workflow_agent_completed.value: WorkflowAgentCompletedEvent,
|
|
420
453
|
WorkflowRunEvent.workflow_completed.value: WorkflowCompletedEvent,
|
|
421
454
|
WorkflowRunEvent.workflow_cancelled.value: WorkflowCancelledEvent,
|
|
422
455
|
WorkflowRunEvent.workflow_error.value: WorkflowErrorEvent,
|
|
@@ -442,10 +475,15 @@ WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
|
|
|
442
475
|
|
|
443
476
|
def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
|
|
444
477
|
event_type = data.get("event", "")
|
|
445
|
-
|
|
446
|
-
|
|
478
|
+
if event_type in {e.value for e in RunEvent}:
|
|
479
|
+
return run_output_event_from_dict(data) # type: ignore
|
|
480
|
+
elif event_type in {e.value for e in TeamRunEvent}:
|
|
481
|
+
return team_run_output_event_from_dict(data) # type: ignore
|
|
482
|
+
else:
|
|
483
|
+
event_class = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
|
|
484
|
+
if not event_class:
|
|
447
485
|
raise ValueError(f"Unknown workflow event type: {event_type}")
|
|
448
|
-
return
|
|
486
|
+
return event_class.from_dict(data) # type: ignore
|
|
449
487
|
|
|
450
488
|
|
|
451
489
|
@dataclass
|
|
@@ -475,6 +513,10 @@ class WorkflowRunOutput:
|
|
|
475
513
|
# Store agent/team responses separately with parent_run_id references
|
|
476
514
|
step_executor_runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
|
|
477
515
|
|
|
516
|
+
# Workflow agent run - stores the full agent RunOutput when workflow agent is used
|
|
517
|
+
# The agent's parent_run_id will point to this workflow run's run_id to establish the relationship
|
|
518
|
+
workflow_agent_run: Optional[RunOutput] = None
|
|
519
|
+
|
|
478
520
|
# Store events from workflow execution
|
|
479
521
|
events: Optional[List[WorkflowRunOutputEvent]] = None
|
|
480
522
|
|
|
@@ -506,6 +548,7 @@ class WorkflowRunOutput:
|
|
|
506
548
|
"step_executor_runs",
|
|
507
549
|
"events",
|
|
508
550
|
"metrics",
|
|
551
|
+
"workflow_agent_run",
|
|
509
552
|
]
|
|
510
553
|
}
|
|
511
554
|
|
|
@@ -541,6 +584,9 @@ class WorkflowRunOutput:
|
|
|
541
584
|
if self.step_executor_runs:
|
|
542
585
|
_dict["step_executor_runs"] = [run.to_dict() for run in self.step_executor_runs]
|
|
543
586
|
|
|
587
|
+
if self.workflow_agent_run is not None:
|
|
588
|
+
_dict["workflow_agent_run"] = self.workflow_agent_run.to_dict()
|
|
589
|
+
|
|
544
590
|
if self.metrics is not None:
|
|
545
591
|
_dict["metrics"] = self.metrics.to_dict()
|
|
546
592
|
|
|
@@ -551,7 +597,7 @@ class WorkflowRunOutput:
|
|
|
551
597
|
_dict["input"] = self.input
|
|
552
598
|
|
|
553
599
|
if self.content and isinstance(self.content, BaseModel):
|
|
554
|
-
_dict["content"] = self.content.model_dump(exclude_none=True)
|
|
600
|
+
_dict["content"] = self.content.model_dump(exclude_none=True, mode="json")
|
|
555
601
|
|
|
556
602
|
if self.events is not None:
|
|
557
603
|
_dict["events"] = [e.to_dict() for e in self.events]
|
|
@@ -588,19 +634,20 @@ class WorkflowRunOutput:
|
|
|
588
634
|
else:
|
|
589
635
|
step_executor_runs.append(RunOutput.from_dict(run_data))
|
|
590
636
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
637
|
+
workflow_agent_run_data = data.pop("workflow_agent_run", None)
|
|
638
|
+
workflow_agent_run = None
|
|
639
|
+
if workflow_agent_run_data:
|
|
640
|
+
if isinstance(workflow_agent_run_data, dict):
|
|
641
|
+
workflow_agent_run = RunOutput.from_dict(workflow_agent_run_data)
|
|
642
|
+
elif isinstance(workflow_agent_run_data, RunOutput):
|
|
643
|
+
workflow_agent_run = workflow_agent_run_data
|
|
598
644
|
|
|
599
|
-
|
|
600
|
-
audio = [Audio.model_validate(audio) for audio in audio] if audio else None
|
|
645
|
+
metadata = data.pop("metadata", None)
|
|
601
646
|
|
|
602
|
-
|
|
603
|
-
|
|
647
|
+
images = reconstruct_images(data.pop("images", []))
|
|
648
|
+
videos = reconstruct_videos(data.pop("videos", []))
|
|
649
|
+
audio = reconstruct_audio_list(data.pop("audio", []))
|
|
650
|
+
response_audio = reconstruct_response_audio(data.pop("response_audio", None))
|
|
604
651
|
|
|
605
652
|
events_data = data.pop("events", [])
|
|
606
653
|
final_events = []
|
|
@@ -623,8 +670,15 @@ class WorkflowRunOutput:
|
|
|
623
670
|
|
|
624
671
|
input_data = data.pop("input", None)
|
|
625
672
|
|
|
673
|
+
# Filter data to only include fields that are actually defined in the WorkflowRunOutput dataclass
|
|
674
|
+
from dataclasses import fields
|
|
675
|
+
|
|
676
|
+
supported_fields = {f.name for f in fields(cls)}
|
|
677
|
+
filtered_data = {k: v for k, v in data.items() if k in supported_fields}
|
|
678
|
+
|
|
626
679
|
return cls(
|
|
627
680
|
step_results=parsed_step_results,
|
|
681
|
+
workflow_agent_run=workflow_agent_run,
|
|
628
682
|
metadata=metadata,
|
|
629
683
|
images=images,
|
|
630
684
|
videos=videos,
|
|
@@ -634,7 +688,7 @@ class WorkflowRunOutput:
|
|
|
634
688
|
metrics=workflow_metrics,
|
|
635
689
|
step_executor_runs=step_executor_runs,
|
|
636
690
|
input=input_data,
|
|
637
|
-
**
|
|
691
|
+
**filtered_data,
|
|
638
692
|
)
|
|
639
693
|
|
|
640
694
|
def get_content_as_string(self, **kwargs) -> str:
|
agno/session/agent.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import asdict, dataclass
|
|
4
|
-
from typing import Any, Dict, List, Mapping, Optional
|
|
4
|
+
from typing import Any, Dict, List, Mapping, Optional, Union
|
|
5
5
|
|
|
6
6
|
from agno.models.message import Message
|
|
7
7
|
from agno.run.agent import RunOutput
|
|
8
8
|
from agno.run.base import RunStatus
|
|
9
|
+
from agno.run.team import TeamRunOutput
|
|
9
10
|
from agno.session.summary import SessionSummary
|
|
10
11
|
from agno.utils.log import log_debug, log_warning
|
|
11
12
|
|
|
@@ -33,7 +34,7 @@ class AgentSession:
|
|
|
33
34
|
# Agent Data: agent_id, name and model
|
|
34
35
|
agent_data: Optional[Dict[str, Any]] = None
|
|
35
36
|
# List of all runs in the session
|
|
36
|
-
runs: Optional[List[RunOutput]] = None
|
|
37
|
+
runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
|
|
37
38
|
# Summary of the session
|
|
38
39
|
summary: Optional["SessionSummary"] = None
|
|
39
40
|
|
|
@@ -57,9 +58,13 @@ class AgentSession:
|
|
|
57
58
|
return None
|
|
58
59
|
|
|
59
60
|
runs = data.get("runs")
|
|
60
|
-
serialized_runs: List[RunOutput] = []
|
|
61
|
+
serialized_runs: List[Union[RunOutput, TeamRunOutput]] = []
|
|
61
62
|
if runs is not None and isinstance(runs[0], dict):
|
|
62
|
-
|
|
63
|
+
for run in runs:
|
|
64
|
+
if "agent_id" in run:
|
|
65
|
+
serialized_runs.append(RunOutput.from_dict(run))
|
|
66
|
+
elif "team_id" in run:
|
|
67
|
+
serialized_runs.append(TeamRunOutput.from_dict(run))
|
|
63
68
|
|
|
64
69
|
summary = data.get("summary")
|
|
65
70
|
if summary is not None and isinstance(summary, dict):
|
|
@@ -101,76 +106,136 @@ class AgentSession:
|
|
|
101
106
|
|
|
102
107
|
log_debug("Added RunOutput to Agent Session")
|
|
103
108
|
|
|
104
|
-
def get_run(self, run_id: str) -> Optional[RunOutput]:
|
|
109
|
+
def get_run(self, run_id: str) -> Optional[Union[RunOutput, TeamRunOutput]]:
|
|
105
110
|
for run in self.runs or []:
|
|
106
111
|
if run.run_id == run_id:
|
|
107
112
|
return run
|
|
108
113
|
return None
|
|
109
114
|
|
|
110
|
-
def
|
|
115
|
+
def get_messages(
|
|
111
116
|
self,
|
|
112
117
|
agent_id: Optional[str] = None,
|
|
113
118
|
team_id: Optional[str] = None,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
last_n_runs: Optional[int] = None,
|
|
120
|
+
limit: Optional[int] = None,
|
|
121
|
+
skip_roles: Optional[List[str]] = None,
|
|
122
|
+
skip_statuses: Optional[List[RunStatus]] = None,
|
|
117
123
|
skip_history_messages: bool = True,
|
|
118
124
|
) -> List[Message]:
|
|
119
|
-
"""Returns the messages
|
|
125
|
+
"""Returns the messages belonging to the session that fit the given criteria.
|
|
126
|
+
|
|
120
127
|
Args:
|
|
121
128
|
agent_id: The id of the agent to get the messages from.
|
|
122
129
|
team_id: The id of the team to get the messages from.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
last_n_runs: The number of runs to return messages from, counting from the latest. Defaults to all runs.
|
|
131
|
+
last_n_messages: The number of messages to return, counting from the latest. Defaults to all messages.
|
|
132
|
+
skip_roles: Skip messages with these roles.
|
|
133
|
+
skip_statuses: Skip messages with these statuses.
|
|
126
134
|
skip_history_messages: Skip messages that were tagged as history in previous runs.
|
|
135
|
+
|
|
127
136
|
Returns:
|
|
128
|
-
A list of Messages
|
|
137
|
+
A list of Messages belonging to the session.
|
|
129
138
|
"""
|
|
139
|
+
|
|
140
|
+
def _should_skip_message(
|
|
141
|
+
message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
|
|
142
|
+
) -> bool:
|
|
143
|
+
"""Logic to determine if a message should be skipped"""
|
|
144
|
+
# Skip messages that were tagged as history in previous runs
|
|
145
|
+
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
# Skip messages with specified role
|
|
149
|
+
if skip_roles and message.role in skip_roles:
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
return False
|
|
153
|
+
|
|
130
154
|
if not self.runs:
|
|
131
155
|
return []
|
|
132
156
|
|
|
133
|
-
if
|
|
134
|
-
|
|
157
|
+
if skip_statuses is None:
|
|
158
|
+
skip_statuses = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
|
|
159
|
+
|
|
160
|
+
runs = self.runs
|
|
135
161
|
|
|
136
|
-
session_runs = self.runs
|
|
137
162
|
# Filter by agent_id and team_id
|
|
138
163
|
if agent_id:
|
|
139
|
-
|
|
164
|
+
runs = [run for run in runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
|
|
140
165
|
if team_id:
|
|
141
|
-
|
|
166
|
+
runs = [run for run in runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
|
|
167
|
+
|
|
168
|
+
# Skip any messages that might be part of members of teams (for session re-use)
|
|
169
|
+
runs = [run for run in runs if run.parent_run_id is None] # type: ignore
|
|
142
170
|
|
|
143
171
|
# Filter by status
|
|
144
|
-
|
|
172
|
+
runs = [run for run in runs if hasattr(run, "status") and run.status not in skip_statuses] # type: ignore
|
|
145
173
|
|
|
146
|
-
# Filter by last_n
|
|
147
|
-
runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
|
|
148
174
|
messages_from_history = []
|
|
149
175
|
system_message = None
|
|
150
|
-
for run_response in runs_to_process:
|
|
151
|
-
if not (run_response and run_response.messages):
|
|
152
|
-
continue
|
|
153
176
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
# Limit the number of messages returned if limit is set
|
|
178
|
+
if limit is not None:
|
|
179
|
+
for run_response in runs:
|
|
180
|
+
if not run_response or not run_response.messages:
|
|
157
181
|
continue
|
|
158
182
|
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
for message in run_response.messages or []:
|
|
184
|
+
if _should_skip_message(message, skip_roles, skip_history_messages):
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if message.role == "system":
|
|
188
|
+
# Only add the system message once
|
|
189
|
+
if system_message is None:
|
|
190
|
+
system_message = message
|
|
191
|
+
else:
|
|
192
|
+
messages_from_history.append(message)
|
|
193
|
+
|
|
194
|
+
if system_message:
|
|
195
|
+
messages_from_history = [system_message] + messages_from_history[
|
|
196
|
+
-(limit - 1) :
|
|
197
|
+
] # Grab one less message then add the system message
|
|
198
|
+
else:
|
|
199
|
+
messages_from_history = messages_from_history[-limit:]
|
|
200
|
+
|
|
201
|
+
# Remove tool result messages that don't have an associated assistant message with tool calls
|
|
202
|
+
while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
|
|
203
|
+
messages_from_history.pop(0)
|
|
204
|
+
|
|
205
|
+
# If limit is not set, return all messages
|
|
206
|
+
else:
|
|
207
|
+
runs_to_process = runs[-last_n_runs:] if last_n_runs is not None else runs
|
|
208
|
+
for run_response in runs_to_process:
|
|
209
|
+
if not run_response or not run_response.messages:
|
|
161
210
|
continue
|
|
162
211
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
for message in run_response.messages or []:
|
|
213
|
+
if _should_skip_message(message, skip_roles, skip_history_messages):
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
if message.role == "system":
|
|
217
|
+
# Only add the system message once
|
|
218
|
+
if system_message is None:
|
|
219
|
+
system_message = message
|
|
220
|
+
messages_from_history.append(system_message)
|
|
221
|
+
else:
|
|
222
|
+
messages_from_history.append(message)
|
|
170
223
|
|
|
171
224
|
log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
|
|
172
225
|
return messages_from_history
|
|
173
226
|
|
|
227
|
+
def get_chat_history(self, last_n_runs: Optional[int] = None) -> List[Message]:
|
|
228
|
+
"""Return the chat history (user and assistant messages) for the session.
|
|
229
|
+
Use get_messages() for more filtering options.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A list of user and assistant Messages belonging to the session.
|
|
236
|
+
"""
|
|
237
|
+
return self.get_messages(skip_roles=["system", "tool"], last_n_runs=last_n_runs)
|
|
238
|
+
|
|
174
239
|
def get_tool_calls(self, num_calls: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
175
240
|
"""Returns a list of tool calls from the messages"""
|
|
176
241
|
|
|
@@ -187,61 +252,9 @@ class AgentSession:
|
|
|
187
252
|
return tool_calls
|
|
188
253
|
return tool_calls
|
|
189
254
|
|
|
190
|
-
def get_messages_for_session(
|
|
191
|
-
self,
|
|
192
|
-
user_role: str = "user",
|
|
193
|
-
assistant_role: Optional[List[str]] = None,
|
|
194
|
-
skip_history_messages: bool = True,
|
|
195
|
-
) -> List[Message]:
|
|
196
|
-
"""Returns a list of messages for the session that iterate through user message and assistant response."""
|
|
197
|
-
|
|
198
|
-
if assistant_role is None:
|
|
199
|
-
# TODO: Check if we still need CHATBOT as a role
|
|
200
|
-
assistant_role = ["assistant", "model", "CHATBOT"]
|
|
201
|
-
|
|
202
|
-
final_messages: List[Message] = []
|
|
203
|
-
session_runs = self.runs
|
|
204
|
-
if not session_runs:
|
|
205
|
-
return []
|
|
206
|
-
|
|
207
|
-
for run_response in session_runs:
|
|
208
|
-
if run_response and run_response.messages:
|
|
209
|
-
user_message_from_run = None
|
|
210
|
-
assistant_message_from_run = None
|
|
211
|
-
|
|
212
|
-
# Start from the beginning to look for the user message
|
|
213
|
-
for message in run_response.messages or []:
|
|
214
|
-
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
215
|
-
continue
|
|
216
|
-
if message.role == user_role:
|
|
217
|
-
user_message_from_run = message
|
|
218
|
-
break
|
|
219
|
-
|
|
220
|
-
# Start from the end to look for the assistant response
|
|
221
|
-
for message in run_response.messages[::-1]:
|
|
222
|
-
if hasattr(message, "from_history") and message.from_history and skip_history_messages:
|
|
223
|
-
continue
|
|
224
|
-
if message.role in assistant_role:
|
|
225
|
-
assistant_message_from_run = message
|
|
226
|
-
break
|
|
227
|
-
|
|
228
|
-
if user_message_from_run and assistant_message_from_run:
|
|
229
|
-
final_messages.append(user_message_from_run)
|
|
230
|
-
final_messages.append(assistant_message_from_run)
|
|
231
|
-
return final_messages
|
|
232
|
-
|
|
233
255
|
def get_session_summary(self) -> Optional[SessionSummary]:
|
|
234
256
|
"""Get the session summary for the session"""
|
|
235
257
|
|
|
236
258
|
if self.summary is None:
|
|
237
259
|
return None
|
|
238
260
|
return self.summary
|
|
239
|
-
|
|
240
|
-
# Chat History functions
|
|
241
|
-
def get_chat_history(self) -> List[Message]:
|
|
242
|
-
"""Get the chat history for the session"""
|
|
243
|
-
|
|
244
|
-
messages = []
|
|
245
|
-
for run in self.runs or []:
|
|
246
|
-
messages.extend([msg for msg in run.messages or [] if not msg.from_history])
|
|
247
|
-
return messages
|
agno/session/summary.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from textwrap import dedent
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
7
|
|
|
8
8
|
from agno.models.base import Model
|
|
9
|
+
from agno.models.utils import get_model
|
|
9
10
|
from agno.run.agent import Message
|
|
10
11
|
from agno.utils.log import log_debug, log_warning
|
|
11
12
|
|
|
@@ -66,6 +67,9 @@ class SessionSummaryManager:
|
|
|
66
67
|
# Prompt used for session summary generation
|
|
67
68
|
session_summary_prompt: Optional[str] = None
|
|
68
69
|
|
|
70
|
+
# User message prompt for requesting the summary
|
|
71
|
+
summary_request_message: str = "Provide the summary of the conversation."
|
|
72
|
+
|
|
69
73
|
# Whether session summaries were created in the last run
|
|
70
74
|
summaries_updated: bool = False
|
|
71
75
|
|
|
@@ -102,7 +106,23 @@ class SessionSummaryManager:
|
|
|
102
106
|
system_prompt += "<conversation>"
|
|
103
107
|
for message in conversation:
|
|
104
108
|
if message.role == "user":
|
|
105
|
-
|
|
109
|
+
# Handle empty user messages with media - note what media was provided
|
|
110
|
+
if not message.content or (isinstance(message.content, str) and message.content.strip() == ""):
|
|
111
|
+
media_types = []
|
|
112
|
+
if hasattr(message, "images") and message.images:
|
|
113
|
+
media_types.append(f"{len(message.images)} image(s)")
|
|
114
|
+
if hasattr(message, "videos") and message.videos:
|
|
115
|
+
media_types.append(f"{len(message.videos)} video(s)")
|
|
116
|
+
if hasattr(message, "audio") and message.audio:
|
|
117
|
+
media_types.append(f"{len(message.audio)} audio file(s)")
|
|
118
|
+
if hasattr(message, "files") and message.files:
|
|
119
|
+
media_types.append(f"{len(message.files)} file(s)")
|
|
120
|
+
|
|
121
|
+
if media_types:
|
|
122
|
+
conversation_messages.append(f"User: [Provided {', '.join(media_types)}]")
|
|
123
|
+
# Skip empty messages with no media
|
|
124
|
+
else:
|
|
125
|
+
conversation_messages.append(f"User: {message.content}")
|
|
106
126
|
elif message.role in ["assistant", "model"]:
|
|
107
127
|
conversation_messages.append(f"Assistant: {message.content}\n")
|
|
108
128
|
system_prompt += "\n".join(conversation_messages)
|
|
@@ -118,23 +138,30 @@ class SessionSummaryManager:
|
|
|
118
138
|
def _prepare_summary_messages(
|
|
119
139
|
self,
|
|
120
140
|
session: Optional["Session"] = None,
|
|
121
|
-
) -> List[Message]:
|
|
122
|
-
"""Prepare messages for session summary generation"""
|
|
123
|
-
|
|
141
|
+
) -> Optional[List[Message]]:
|
|
142
|
+
"""Prepare messages for session summary generation. Returns None if no meaningful messages to summarize."""
|
|
143
|
+
if not session:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
self.model = get_model(self.model)
|
|
147
|
+
if self.model is None:
|
|
148
|
+
return None
|
|
149
|
+
|
|
124
150
|
response_format = self.get_response_format(self.model)
|
|
125
151
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
conversation=session.get_messages_for_session(), # type: ignore
|
|
130
|
-
response_format=response_format,
|
|
131
|
-
),
|
|
132
|
-
Message(role="user", content="Provide the summary of the conversation."),
|
|
133
|
-
]
|
|
134
|
-
if session
|
|
135
|
-
else []
|
|
152
|
+
system_message = self.get_system_message(
|
|
153
|
+
conversation=session.get_messages(), # type: ignore
|
|
154
|
+
response_format=response_format,
|
|
136
155
|
)
|
|
137
156
|
|
|
157
|
+
if system_message is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
system_message,
|
|
162
|
+
Message(role="user", content=self.summary_request_message),
|
|
163
|
+
]
|
|
164
|
+
|
|
138
165
|
def _process_summary_response(self, summary_response, session_summary_model: "Model") -> Optional[SessionSummary]: # type: ignore
|
|
139
166
|
"""Process the model response into a SessionSummary"""
|
|
140
167
|
from datetime import datetime
|
|
@@ -187,10 +214,17 @@ class SessionSummaryManager:
|
|
|
187
214
|
) -> Optional[SessionSummary]:
|
|
188
215
|
"""Creates a summary of the session"""
|
|
189
216
|
log_debug("Creating session summary", center=True)
|
|
217
|
+
self.model = get_model(self.model)
|
|
190
218
|
if self.model is None:
|
|
191
219
|
return None
|
|
192
220
|
|
|
193
221
|
messages = self._prepare_summary_messages(session)
|
|
222
|
+
|
|
223
|
+
# Skip summary generation if there are no meaningful messages
|
|
224
|
+
if messages is None:
|
|
225
|
+
log_debug("No meaningful messages to summarize, skipping session summary")
|
|
226
|
+
return None
|
|
227
|
+
|
|
194
228
|
response_format = self.get_response_format(self.model)
|
|
195
229
|
|
|
196
230
|
summary_response = self.model.response(messages=messages, response_format=response_format)
|
|
@@ -208,10 +242,17 @@ class SessionSummaryManager:
|
|
|
208
242
|
) -> Optional[SessionSummary]:
|
|
209
243
|
"""Creates a summary of the session"""
|
|
210
244
|
log_debug("Creating session summary", center=True)
|
|
245
|
+
self.model = get_model(self.model)
|
|
211
246
|
if self.model is None:
|
|
212
247
|
return None
|
|
213
248
|
|
|
214
249
|
messages = self._prepare_summary_messages(session)
|
|
250
|
+
|
|
251
|
+
# Skip summary generation if there are no meaningful messages
|
|
252
|
+
if messages is None:
|
|
253
|
+
log_debug("No meaningful messages to summarize, skipping session summary")
|
|
254
|
+
return None
|
|
255
|
+
|
|
215
256
|
response_format = self.get_response_format(self.model)
|
|
216
257
|
|
|
217
258
|
summary_response = await self.model.aresponse(messages=messages, response_format=response_format)
|