agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/workflow/step.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
import warnings
|
|
2
3
|
from copy import copy
|
|
3
4
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union
|
|
5
|
+
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union, cast
|
|
5
6
|
from uuid import uuid4
|
|
6
7
|
|
|
7
8
|
from pydantic import BaseModel
|
|
9
|
+
from typing_extensions import TypeGuard
|
|
8
10
|
|
|
9
11
|
from agno.agent import Agent
|
|
10
|
-
from agno.media import Audio,
|
|
12
|
+
from agno.media import Audio, Image, Video
|
|
13
|
+
from agno.models.message import Message
|
|
11
14
|
from agno.models.metrics import Metrics
|
|
12
|
-
from agno.run
|
|
15
|
+
from agno.run import RunContext
|
|
16
|
+
from agno.run.agent import RunContentEvent, RunOutput
|
|
17
|
+
from agno.run.base import BaseRunOutputEvent
|
|
18
|
+
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
13
19
|
from agno.run.team import TeamRunOutput
|
|
14
20
|
from agno.run.workflow import (
|
|
15
21
|
StepCompletedEvent,
|
|
@@ -17,8 +23,11 @@ from agno.run.workflow import (
|
|
|
17
23
|
WorkflowRunOutput,
|
|
18
24
|
WorkflowRunOutputEvent,
|
|
19
25
|
)
|
|
26
|
+
from agno.session.agent import AgentSession
|
|
27
|
+
from agno.session.team import TeamSession
|
|
28
|
+
from agno.session.workflow import WorkflowSession
|
|
20
29
|
from agno.team import Team
|
|
21
|
-
from agno.utils.log import log_debug, logger, use_agent_logger, use_team_logger, use_workflow_logger
|
|
30
|
+
from agno.utils.log import log_debug, log_warning, logger, use_agent_logger, use_team_logger, use_workflow_logger
|
|
22
31
|
from agno.utils.merge_dict import merge_dictionaries
|
|
23
32
|
from agno.workflow.types import StepInput, StepOutput, StepType
|
|
24
33
|
|
|
@@ -60,6 +69,9 @@ class Step:
|
|
|
60
69
|
# If False, only warn about missing inputs
|
|
61
70
|
strict_input_validation: bool = False
|
|
62
71
|
|
|
72
|
+
add_workflow_history: Optional[bool] = None
|
|
73
|
+
num_history_runs: int = 3
|
|
74
|
+
|
|
63
75
|
_retry_count: int = 0
|
|
64
76
|
|
|
65
77
|
def __init__(
|
|
@@ -74,6 +86,8 @@ class Step:
|
|
|
74
86
|
timeout_seconds: Optional[int] = None,
|
|
75
87
|
skip_on_failure: bool = False,
|
|
76
88
|
strict_input_validation: bool = False,
|
|
89
|
+
add_workflow_history: Optional[bool] = None,
|
|
90
|
+
num_history_runs: int = 3,
|
|
77
91
|
):
|
|
78
92
|
# Auto-detect name for function executors if not provided
|
|
79
93
|
if name is None and executor is not None:
|
|
@@ -93,6 +107,8 @@ class Step:
|
|
|
93
107
|
self.timeout_seconds = timeout_seconds
|
|
94
108
|
self.skip_on_failure = skip_on_failure
|
|
95
109
|
self.strict_input_validation = strict_input_validation
|
|
110
|
+
self.add_workflow_history = add_workflow_history
|
|
111
|
+
self.num_history_runs = num_history_runs
|
|
96
112
|
self.step_id = step_id
|
|
97
113
|
|
|
98
114
|
if step_id is None:
|
|
@@ -164,14 +180,55 @@ class Step:
|
|
|
164
180
|
return response.metrics
|
|
165
181
|
return None
|
|
166
182
|
|
|
183
|
+
def _call_custom_function(
|
|
184
|
+
self,
|
|
185
|
+
func: Callable,
|
|
186
|
+
step_input: StepInput,
|
|
187
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
188
|
+
run_context: Optional[RunContext] = None,
|
|
189
|
+
) -> Any:
|
|
190
|
+
"""Call custom function with session_state support if the function accepts it"""
|
|
191
|
+
|
|
192
|
+
kwargs: Dict[str, Any] = {}
|
|
193
|
+
if run_context is not None and self._function_has_run_context_param():
|
|
194
|
+
kwargs["run_context"] = run_context
|
|
195
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
196
|
+
kwargs["session_state"] = session_state
|
|
197
|
+
|
|
198
|
+
return func(step_input, **kwargs)
|
|
199
|
+
|
|
200
|
+
async def _acall_custom_function(
|
|
201
|
+
self,
|
|
202
|
+
func: Callable,
|
|
203
|
+
step_input: StepInput,
|
|
204
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
205
|
+
run_context: Optional[RunContext] = None,
|
|
206
|
+
) -> Any:
|
|
207
|
+
"""Call custom async function with session_state support if the function accepts it"""
|
|
208
|
+
|
|
209
|
+
kwargs: Dict[str, Any] = {}
|
|
210
|
+
if run_context is not None and self._function_has_run_context_param():
|
|
211
|
+
kwargs["run_context"] = run_context
|
|
212
|
+
if session_state is not None and self._function_has_session_state_param():
|
|
213
|
+
kwargs["session_state"] = session_state
|
|
214
|
+
|
|
215
|
+
if _is_async_generator_function(func):
|
|
216
|
+
return func(step_input, **kwargs)
|
|
217
|
+
else:
|
|
218
|
+
return await func(step_input, **kwargs)
|
|
219
|
+
|
|
167
220
|
def execute(
|
|
168
221
|
self,
|
|
169
222
|
step_input: StepInput,
|
|
170
223
|
session_id: Optional[str] = None,
|
|
171
224
|
user_id: Optional[str] = None,
|
|
172
225
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
226
|
+
run_context: Optional[RunContext] = None,
|
|
173
227
|
session_state: Optional[Dict[str, Any]] = None,
|
|
174
228
|
store_executor_outputs: bool = True,
|
|
229
|
+
workflow_session: Optional[WorkflowSession] = None,
|
|
230
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
231
|
+
num_history_runs: int = 3,
|
|
175
232
|
) -> StepOutput:
|
|
176
233
|
"""Execute the step with StepInput, returning final StepOutput (non-streaming)"""
|
|
177
234
|
log_debug(f"Executing step: {self.name}")
|
|
@@ -179,46 +236,89 @@ class Step:
|
|
|
179
236
|
if step_input.previous_step_outputs:
|
|
180
237
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
181
238
|
|
|
239
|
+
if workflow_session:
|
|
240
|
+
step_input.workflow_session = workflow_session
|
|
241
|
+
|
|
242
|
+
# Create session_state copy once to avoid duplication.
|
|
243
|
+
# Consider both run_context.session_state and session_state.
|
|
244
|
+
if run_context is not None and run_context.session_state is not None:
|
|
245
|
+
session_state_copy = run_context.session_state
|
|
246
|
+
else:
|
|
247
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
248
|
+
|
|
182
249
|
# Execute with retries
|
|
183
250
|
for attempt in range(self.max_retries + 1):
|
|
184
251
|
try:
|
|
185
252
|
response: Union[RunOutput, TeamRunOutput, StepOutput]
|
|
186
253
|
if self._executor_type == "function":
|
|
187
|
-
if
|
|
188
|
-
self.active_executor
|
|
189
|
-
):
|
|
254
|
+
if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
|
|
190
255
|
raise ValueError("Cannot use async function with synchronous execution")
|
|
191
|
-
if
|
|
256
|
+
if _is_generator_function(self.active_executor):
|
|
192
257
|
content = ""
|
|
193
258
|
final_response = None
|
|
194
259
|
try:
|
|
195
|
-
for chunk in self.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
260
|
+
for chunk in self._call_custom_function(
|
|
261
|
+
self.active_executor,
|
|
262
|
+
step_input,
|
|
263
|
+
session_state_copy, # type: ignore[arg-type]
|
|
264
|
+
run_context,
|
|
265
|
+
): # type: ignore
|
|
266
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
267
|
+
if (
|
|
268
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
269
|
+
and chunk.content is not None
|
|
270
|
+
):
|
|
271
|
+
# Its a regular chunk of content
|
|
272
|
+
if isinstance(chunk.content, str):
|
|
273
|
+
content += chunk.content
|
|
274
|
+
# Its the BaseModel object, set it as the content. Replace any previous content.
|
|
275
|
+
# There should be no previous str content at this point
|
|
276
|
+
elif isinstance(chunk.content, BaseModel):
|
|
277
|
+
content = chunk.content # type: ignore[assignment]
|
|
278
|
+
else:
|
|
279
|
+
# Case when parse_response is False and the content is a dict
|
|
280
|
+
content += str(chunk.content)
|
|
281
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
282
|
+
# This is the final response from the agent/team
|
|
283
|
+
content = chunk.content # type: ignore[assignment]
|
|
284
|
+
# If the chunk is a StepOutput, use it as the final response
|
|
285
|
+
elif isinstance(chunk, StepOutput):
|
|
286
|
+
final_response = chunk
|
|
287
|
+
break
|
|
288
|
+
# Non Agent/Team data structure that was yielded
|
|
202
289
|
else:
|
|
203
290
|
content += str(chunk)
|
|
204
|
-
if isinstance(chunk, StepOutput):
|
|
205
|
-
final_response = chunk
|
|
206
291
|
|
|
207
292
|
except StopIteration as e:
|
|
208
293
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
209
294
|
final_response = e.value
|
|
210
295
|
|
|
296
|
+
# Merge session_state changes back
|
|
297
|
+
if run_context is None and session_state is not None:
|
|
298
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
299
|
+
|
|
211
300
|
if final_response is not None:
|
|
212
301
|
response = final_response
|
|
213
302
|
else:
|
|
214
303
|
response = StepOutput(content=content)
|
|
215
304
|
else:
|
|
216
|
-
# Execute function
|
|
217
|
-
result = self.
|
|
305
|
+
# Execute function with signature inspection for session_state support
|
|
306
|
+
result = self._call_custom_function(
|
|
307
|
+
self.active_executor, # type: ignore[arg-type]
|
|
308
|
+
step_input,
|
|
309
|
+
session_state_copy,
|
|
310
|
+
run_context,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Merge session_state changes back
|
|
314
|
+
if run_context is None and session_state is not None:
|
|
315
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
218
316
|
|
|
219
317
|
# If function returns StepOutput, use it directly
|
|
220
318
|
if isinstance(result, StepOutput):
|
|
221
319
|
response = result
|
|
320
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
321
|
+
response = StepOutput(content=result.content)
|
|
222
322
|
else:
|
|
223
323
|
response = StepOutput(content=str(result))
|
|
224
324
|
else:
|
|
@@ -248,9 +348,22 @@ class Step:
|
|
|
248
348
|
if isinstance(self.active_executor, Team):
|
|
249
349
|
kwargs["store_member_responses"] = True
|
|
250
350
|
|
|
251
|
-
|
|
351
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
352
|
+
|
|
353
|
+
use_history = (
|
|
354
|
+
self.add_workflow_history
|
|
355
|
+
if self.add_workflow_history is not None
|
|
356
|
+
else add_workflow_history_to_steps
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
final_message = message
|
|
360
|
+
if use_history and workflow_session:
|
|
361
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
362
|
+
if history_messages:
|
|
363
|
+
final_message = f"{history_messages}{message}"
|
|
364
|
+
|
|
252
365
|
response = self.active_executor.run( # type: ignore
|
|
253
|
-
input=
|
|
366
|
+
input=final_message, # type: ignore
|
|
254
367
|
images=images,
|
|
255
368
|
videos=videos,
|
|
256
369
|
audio=audios,
|
|
@@ -258,11 +371,13 @@ class Step:
|
|
|
258
371
|
session_id=session_id,
|
|
259
372
|
user_id=user_id,
|
|
260
373
|
session_state=session_state_copy, # Send a copy to the executor
|
|
374
|
+
run_context=run_context,
|
|
261
375
|
**kwargs,
|
|
262
376
|
)
|
|
263
377
|
|
|
264
378
|
# Update workflow session state
|
|
265
|
-
|
|
379
|
+
if run_context is None and session_state is not None:
|
|
380
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
266
381
|
|
|
267
382
|
if store_executor_outputs and workflow_run_response is not None:
|
|
268
383
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -291,25 +406,97 @@ class Step:
|
|
|
291
406
|
|
|
292
407
|
return StepOutput(content=f"Step {self.name} failed but skipped", success=False)
|
|
293
408
|
|
|
409
|
+
def _function_has_run_context_param(self) -> bool:
|
|
410
|
+
"""Check if the custom function has a run_context parameter"""
|
|
411
|
+
if self._executor_type != "function":
|
|
412
|
+
return False
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
sig = inspect.signature(self.active_executor) # type: ignore
|
|
416
|
+
return "run_context" in sig.parameters
|
|
417
|
+
except Exception:
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
def _function_has_session_state_param(self) -> bool:
|
|
421
|
+
"""Check if the custom function has a session_state parameter"""
|
|
422
|
+
if self._executor_type != "function":
|
|
423
|
+
return False
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
sig = inspect.signature(self.active_executor) # type: ignore
|
|
427
|
+
return "session_state" in sig.parameters
|
|
428
|
+
except Exception:
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
def _enrich_event_with_context(
|
|
432
|
+
self,
|
|
433
|
+
event: Any,
|
|
434
|
+
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
435
|
+
step_index: Optional[Union[int, tuple]] = None,
|
|
436
|
+
) -> Any:
|
|
437
|
+
"""Enrich event with step and workflow context information"""
|
|
438
|
+
if workflow_run_response is None:
|
|
439
|
+
return event
|
|
440
|
+
if hasattr(event, "workflow_id"):
|
|
441
|
+
event.workflow_id = workflow_run_response.workflow_id
|
|
442
|
+
if hasattr(event, "workflow_run_id"):
|
|
443
|
+
event.workflow_run_id = workflow_run_response.run_id
|
|
444
|
+
if hasattr(event, "step_id"):
|
|
445
|
+
event.step_id = self.step_id
|
|
446
|
+
if hasattr(event, "step_name") and self.name is not None:
|
|
447
|
+
if getattr(event, "step_name", None) is None:
|
|
448
|
+
event.step_name = self.name
|
|
449
|
+
# Only set step_index if it's not already set (preserve parallel.py's tuples)
|
|
450
|
+
if hasattr(event, "step_index") and step_index is not None:
|
|
451
|
+
if event.step_index is None:
|
|
452
|
+
event.step_index = step_index
|
|
453
|
+
|
|
454
|
+
return event
|
|
455
|
+
|
|
294
456
|
def execute_stream(
|
|
295
457
|
self,
|
|
296
458
|
step_input: StepInput,
|
|
297
459
|
session_id: Optional[str] = None,
|
|
298
460
|
user_id: Optional[str] = None,
|
|
461
|
+
stream_events: bool = False,
|
|
299
462
|
stream_intermediate_steps: bool = False,
|
|
463
|
+
stream_executor_events: bool = True,
|
|
300
464
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
465
|
+
run_context: Optional[RunContext] = None,
|
|
301
466
|
session_state: Optional[Dict[str, Any]] = None,
|
|
302
467
|
step_index: Optional[Union[int, tuple]] = None,
|
|
303
468
|
store_executor_outputs: bool = True,
|
|
304
469
|
parent_step_id: Optional[str] = None,
|
|
470
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
471
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
472
|
+
num_history_runs: int = 3,
|
|
305
473
|
) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
|
|
306
474
|
"""Execute the step with event-driven streaming support"""
|
|
307
475
|
|
|
308
476
|
if step_input.previous_step_outputs:
|
|
309
477
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
310
478
|
|
|
479
|
+
if workflow_session:
|
|
480
|
+
step_input.workflow_session = workflow_session
|
|
481
|
+
|
|
482
|
+
# Create session_state copy once to avoid duplication.
|
|
483
|
+
# Consider both run_context.session_state and session_state.
|
|
484
|
+
if run_context is not None and run_context.session_state is not None:
|
|
485
|
+
session_state_copy = run_context.session_state
|
|
486
|
+
else:
|
|
487
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
488
|
+
|
|
489
|
+
# Considering both stream_events and stream_intermediate_steps (deprecated)
|
|
490
|
+
if stream_intermediate_steps is not None:
|
|
491
|
+
warnings.warn(
|
|
492
|
+
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
493
|
+
DeprecationWarning,
|
|
494
|
+
stacklevel=2,
|
|
495
|
+
)
|
|
496
|
+
stream_events = stream_events or stream_intermediate_steps
|
|
497
|
+
|
|
311
498
|
# Emit StepStartedEvent
|
|
312
|
-
if
|
|
499
|
+
if stream_events and workflow_run_response:
|
|
313
500
|
yield StepStartedEvent(
|
|
314
501
|
run_id=workflow_run_response.run_id or "",
|
|
315
502
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -329,30 +516,48 @@ class Step:
|
|
|
329
516
|
|
|
330
517
|
if self._executor_type == "function":
|
|
331
518
|
log_debug(f"Executing function executor for step: {self.name}")
|
|
332
|
-
|
|
333
|
-
if inspect.iscoroutinefunction(self.active_executor) or inspect.isasyncgenfunction(
|
|
334
|
-
self.active_executor
|
|
335
|
-
):
|
|
519
|
+
if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
|
|
336
520
|
raise ValueError("Cannot use async function with synchronous execution")
|
|
337
|
-
|
|
338
|
-
if inspect.isgeneratorfunction(self.active_executor):
|
|
521
|
+
if _is_generator_function(self.active_executor):
|
|
339
522
|
log_debug("Function returned iterable, streaming events")
|
|
340
523
|
content = ""
|
|
341
524
|
try:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
525
|
+
iterator = self._call_custom_function(
|
|
526
|
+
self.active_executor,
|
|
527
|
+
step_input,
|
|
528
|
+
session_state_copy,
|
|
529
|
+
run_context,
|
|
530
|
+
)
|
|
531
|
+
for event in iterator: # type: ignore
|
|
532
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
533
|
+
if (
|
|
534
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
535
|
+
and event.content is not None
|
|
536
|
+
):
|
|
537
|
+
if isinstance(event.content, str):
|
|
538
|
+
content += event.content
|
|
539
|
+
elif isinstance(event.content, BaseModel):
|
|
540
|
+
content = event.content # type: ignore[assignment]
|
|
541
|
+
else:
|
|
542
|
+
content = str(event.content)
|
|
543
|
+
# Only yield executor events if stream_executor_events is True
|
|
544
|
+
if stream_executor_events:
|
|
545
|
+
enriched_event = self._enrich_event_with_context(
|
|
546
|
+
event, workflow_run_response, step_index
|
|
547
|
+
)
|
|
548
|
+
yield enriched_event # type: ignore[misc]
|
|
549
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
550
|
+
content = event.content # type: ignore[assignment]
|
|
551
|
+
elif isinstance(event, StepOutput):
|
|
352
552
|
final_response = event
|
|
353
553
|
break
|
|
354
554
|
else:
|
|
355
|
-
|
|
555
|
+
content += str(event)
|
|
556
|
+
|
|
557
|
+
# Merge session_state changes back
|
|
558
|
+
if run_context is None and session_state is not None:
|
|
559
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
560
|
+
|
|
356
561
|
if not final_response:
|
|
357
562
|
final_response = StepOutput(content=content)
|
|
358
563
|
except StopIteration as e:
|
|
@@ -360,9 +565,21 @@ class Step:
|
|
|
360
565
|
final_response = e.value
|
|
361
566
|
|
|
362
567
|
else:
|
|
363
|
-
result = self.
|
|
568
|
+
result = self._call_custom_function(
|
|
569
|
+
self.active_executor, # type: ignore[arg-type]
|
|
570
|
+
step_input,
|
|
571
|
+
session_state_copy,
|
|
572
|
+
run_context,
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# Merge session_state changes back
|
|
576
|
+
if run_context is None and session_state is not None:
|
|
577
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
578
|
+
|
|
364
579
|
if isinstance(result, StepOutput):
|
|
365
580
|
final_response = result
|
|
581
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
582
|
+
final_response = StepOutput(content=result.content)
|
|
366
583
|
else:
|
|
367
584
|
final_response = StepOutput(content=str(result))
|
|
368
585
|
log_debug("Function returned non-iterable, created StepOutput")
|
|
@@ -392,9 +609,22 @@ class Step:
|
|
|
392
609
|
if isinstance(self.active_executor, Team):
|
|
393
610
|
kwargs["store_member_responses"] = True
|
|
394
611
|
|
|
395
|
-
|
|
612
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
613
|
+
|
|
614
|
+
use_history = (
|
|
615
|
+
self.add_workflow_history
|
|
616
|
+
if self.add_workflow_history is not None
|
|
617
|
+
else add_workflow_history_to_steps
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
final_message = message
|
|
621
|
+
if use_history and workflow_session:
|
|
622
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
623
|
+
if history_messages:
|
|
624
|
+
final_message = f"{history_messages}{message}"
|
|
625
|
+
|
|
396
626
|
response_stream = self.active_executor.run( # type: ignore[call-overload, misc]
|
|
397
|
-
input=
|
|
627
|
+
input=final_message,
|
|
398
628
|
images=images,
|
|
399
629
|
videos=videos,
|
|
400
630
|
audio=audios,
|
|
@@ -403,16 +633,9 @@ class Step:
|
|
|
403
633
|
user_id=user_id,
|
|
404
634
|
session_state=session_state_copy, # Send a copy to the executor
|
|
405
635
|
stream=True,
|
|
406
|
-
|
|
407
|
-
# Pass workflow context directly via kwargs
|
|
408
|
-
workflow_context={
|
|
409
|
-
"workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
|
|
410
|
-
"workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
|
|
411
|
-
"step_id": self.step_id,
|
|
412
|
-
"step_name": self.name,
|
|
413
|
-
"step_index": step_index,
|
|
414
|
-
},
|
|
636
|
+
stream_events=stream_events,
|
|
415
637
|
yield_run_response=True,
|
|
638
|
+
run_context=run_context,
|
|
416
639
|
**kwargs,
|
|
417
640
|
)
|
|
418
641
|
|
|
@@ -421,15 +644,21 @@ class Step:
|
|
|
421
644
|
if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
|
|
422
645
|
active_executor_run_response = event
|
|
423
646
|
break
|
|
424
|
-
yield
|
|
647
|
+
# Only yield executor events if stream_executor_events is True
|
|
648
|
+
if stream_executor_events:
|
|
649
|
+
enriched_event = self._enrich_event_with_context(
|
|
650
|
+
event, workflow_run_response, step_index
|
|
651
|
+
)
|
|
652
|
+
yield enriched_event # type: ignore[misc]
|
|
425
653
|
|
|
426
654
|
# Update workflow session state
|
|
427
|
-
|
|
655
|
+
if run_context is None and session_state is not None:
|
|
656
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
428
657
|
|
|
429
658
|
if store_executor_outputs and workflow_run_response is not None:
|
|
430
659
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
|
431
660
|
|
|
432
|
-
final_response =
|
|
661
|
+
final_response = active_executor_run_response # type: ignore
|
|
433
662
|
|
|
434
663
|
else:
|
|
435
664
|
raise ValueError(f"Unsupported executor type: {self._executor_type}")
|
|
@@ -443,10 +672,11 @@ class Step:
|
|
|
443
672
|
use_workflow_logger()
|
|
444
673
|
|
|
445
674
|
# Yield the step output
|
|
675
|
+
final_response = self._process_step_output(final_response)
|
|
446
676
|
yield final_response
|
|
447
677
|
|
|
448
678
|
# Emit StepCompletedEvent
|
|
449
|
-
if
|
|
679
|
+
if stream_events and workflow_run_response:
|
|
450
680
|
yield StepCompletedEvent(
|
|
451
681
|
run_id=workflow_run_response.run_id or "",
|
|
452
682
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -484,8 +714,12 @@ class Step:
|
|
|
484
714
|
session_id: Optional[str] = None,
|
|
485
715
|
user_id: Optional[str] = None,
|
|
486
716
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
717
|
+
run_context: Optional[RunContext] = None,
|
|
487
718
|
session_state: Optional[Dict[str, Any]] = None,
|
|
488
719
|
store_executor_outputs: bool = True,
|
|
720
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
721
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
722
|
+
num_history_runs: int = 3,
|
|
489
723
|
) -> StepOutput:
|
|
490
724
|
"""Execute the step with StepInput, returning final StepOutput (non-streaming)"""
|
|
491
725
|
logger.info(f"Executing async step (non-streaming): {self.name}")
|
|
@@ -494,61 +728,118 @@ class Step:
|
|
|
494
728
|
if step_input.previous_step_outputs:
|
|
495
729
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
496
730
|
|
|
731
|
+
if workflow_session:
|
|
732
|
+
step_input.workflow_session = workflow_session
|
|
733
|
+
|
|
734
|
+
# Create session_state copy once to avoid duplication.
|
|
735
|
+
# Consider both run_context.session_state and session_state.
|
|
736
|
+
if run_context is not None and run_context.session_state is not None:
|
|
737
|
+
session_state_copy = run_context.session_state
|
|
738
|
+
else:
|
|
739
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
740
|
+
|
|
497
741
|
# Execute with retries
|
|
498
742
|
for attempt in range(self.max_retries + 1):
|
|
499
743
|
try:
|
|
500
744
|
if self._executor_type == "function":
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if inspect.isgeneratorfunction(self.active_executor) or inspect.isasyncgenfunction(
|
|
745
|
+
if _is_generator_function(self.active_executor) or _is_async_generator_function(
|
|
504
746
|
self.active_executor
|
|
505
747
|
):
|
|
506
748
|
content = ""
|
|
507
749
|
final_response = None
|
|
508
750
|
try:
|
|
509
|
-
if
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
content += str(chunk)
|
|
519
|
-
if isinstance(chunk, StepOutput):
|
|
520
|
-
final_response = chunk
|
|
521
|
-
else:
|
|
522
|
-
if inspect.isasyncgenfunction(self.active_executor):
|
|
523
|
-
async for chunk in self.active_executor(step_input): # type: ignore
|
|
751
|
+
if _is_generator_function(self.active_executor):
|
|
752
|
+
iterator = self._call_custom_function(
|
|
753
|
+
self.active_executor,
|
|
754
|
+
step_input,
|
|
755
|
+
session_state_copy,
|
|
756
|
+
run_context,
|
|
757
|
+
)
|
|
758
|
+
for chunk in iterator: # type: ignore
|
|
759
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
524
760
|
if (
|
|
525
|
-
|
|
761
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
526
762
|
and chunk.content is not None
|
|
527
|
-
and isinstance(chunk.content, str)
|
|
528
763
|
):
|
|
529
|
-
|
|
764
|
+
if isinstance(chunk.content, str):
|
|
765
|
+
content += chunk.content
|
|
766
|
+
elif isinstance(chunk.content, BaseModel):
|
|
767
|
+
content = chunk.content # type: ignore[assignment]
|
|
768
|
+
else:
|
|
769
|
+
content = str(chunk.content)
|
|
770
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
771
|
+
content = chunk.content # type: ignore[assignment]
|
|
772
|
+
elif isinstance(chunk, StepOutput):
|
|
773
|
+
final_response = chunk
|
|
774
|
+
break
|
|
775
|
+
else:
|
|
776
|
+
content += str(chunk)
|
|
777
|
+
|
|
778
|
+
else:
|
|
779
|
+
if _is_async_generator_function(self.active_executor):
|
|
780
|
+
iterator = await self._acall_custom_function(
|
|
781
|
+
self.active_executor,
|
|
782
|
+
step_input,
|
|
783
|
+
session_state_copy,
|
|
784
|
+
run_context,
|
|
785
|
+
)
|
|
786
|
+
async for chunk in iterator: # type: ignore
|
|
787
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
788
|
+
if (
|
|
789
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
790
|
+
and chunk.content is not None
|
|
791
|
+
):
|
|
792
|
+
if isinstance(chunk.content, str):
|
|
793
|
+
content += chunk.content
|
|
794
|
+
elif isinstance(chunk.content, BaseModel):
|
|
795
|
+
content = chunk.content # type: ignore[assignment]
|
|
796
|
+
else:
|
|
797
|
+
content = str(chunk.content)
|
|
798
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
799
|
+
content = chunk.content # type: ignore[assignment]
|
|
800
|
+
elif isinstance(chunk, StepOutput):
|
|
801
|
+
final_response = chunk
|
|
802
|
+
break
|
|
530
803
|
else:
|
|
531
804
|
content += str(chunk)
|
|
532
|
-
if isinstance(chunk, StepOutput):
|
|
533
|
-
final_response = chunk
|
|
534
805
|
|
|
535
806
|
except StopIteration as e:
|
|
536
807
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
537
808
|
final_response = e.value
|
|
538
809
|
|
|
810
|
+
# Merge session_state changes back
|
|
811
|
+
if run_context is None and session_state is not None:
|
|
812
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
813
|
+
|
|
539
814
|
if final_response is not None:
|
|
540
815
|
response = final_response
|
|
541
816
|
else:
|
|
542
817
|
response = StepOutput(content=content)
|
|
543
818
|
else:
|
|
544
|
-
if
|
|
545
|
-
result = await self.
|
|
819
|
+
if _is_async_callable(self.active_executor):
|
|
820
|
+
result = await self._acall_custom_function(
|
|
821
|
+
self.active_executor,
|
|
822
|
+
step_input,
|
|
823
|
+
session_state_copy,
|
|
824
|
+
run_context,
|
|
825
|
+
)
|
|
546
826
|
else:
|
|
547
|
-
result = self.
|
|
827
|
+
result = self._call_custom_function(
|
|
828
|
+
self.active_executor, # type: ignore[arg-type]
|
|
829
|
+
step_input,
|
|
830
|
+
session_state_copy,
|
|
831
|
+
run_context,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
# Merge session_state changes back
|
|
835
|
+
if run_context is None and session_state is not None:
|
|
836
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
548
837
|
|
|
549
838
|
# If function returns StepOutput, use it directly
|
|
550
839
|
if isinstance(result, StepOutput):
|
|
551
840
|
response = result
|
|
841
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
842
|
+
response = StepOutput(content=result.content)
|
|
552
843
|
else:
|
|
553
844
|
response = StepOutput(content=str(result))
|
|
554
845
|
|
|
@@ -579,9 +870,22 @@ class Step:
|
|
|
579
870
|
if isinstance(self.active_executor, Team):
|
|
580
871
|
kwargs["store_member_responses"] = True
|
|
581
872
|
|
|
582
|
-
|
|
873
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
874
|
+
|
|
875
|
+
use_history = (
|
|
876
|
+
self.add_workflow_history
|
|
877
|
+
if self.add_workflow_history is not None
|
|
878
|
+
else add_workflow_history_to_steps
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
final_message = message
|
|
882
|
+
if use_history and workflow_session:
|
|
883
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
884
|
+
if history_messages:
|
|
885
|
+
final_message = f"{history_messages}{message}"
|
|
886
|
+
|
|
583
887
|
response = await self.active_executor.arun( # type: ignore
|
|
584
|
-
input=
|
|
888
|
+
input=final_message, # type: ignore
|
|
585
889
|
images=images,
|
|
586
890
|
videos=videos,
|
|
587
891
|
audio=audios,
|
|
@@ -589,11 +893,13 @@ class Step:
|
|
|
589
893
|
session_id=session_id,
|
|
590
894
|
user_id=user_id,
|
|
591
895
|
session_state=session_state_copy,
|
|
896
|
+
run_context=run_context,
|
|
592
897
|
**kwargs,
|
|
593
898
|
)
|
|
594
899
|
|
|
595
900
|
# Update workflow session state
|
|
596
|
-
|
|
901
|
+
if run_context is None and session_state is not None:
|
|
902
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
597
903
|
|
|
598
904
|
if store_executor_outputs and workflow_run_response is not None:
|
|
599
905
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -627,19 +933,44 @@ class Step:
|
|
|
627
933
|
step_input: StepInput,
|
|
628
934
|
session_id: Optional[str] = None,
|
|
629
935
|
user_id: Optional[str] = None,
|
|
936
|
+
stream_events: bool = False,
|
|
630
937
|
stream_intermediate_steps: bool = False,
|
|
938
|
+
stream_executor_events: bool = True,
|
|
631
939
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
940
|
+
run_context: Optional[RunContext] = None,
|
|
632
941
|
session_state: Optional[Dict[str, Any]] = None,
|
|
633
942
|
step_index: Optional[Union[int, tuple]] = None,
|
|
634
943
|
store_executor_outputs: bool = True,
|
|
635
944
|
parent_step_id: Optional[str] = None,
|
|
945
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
946
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
947
|
+
num_history_runs: int = 3,
|
|
636
948
|
) -> AsyncIterator[Union[WorkflowRunOutputEvent, StepOutput]]:
|
|
637
949
|
"""Execute the step with event-driven streaming support"""
|
|
638
950
|
|
|
639
951
|
if step_input.previous_step_outputs:
|
|
640
952
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
641
953
|
|
|
642
|
-
if
|
|
954
|
+
if workflow_session:
|
|
955
|
+
step_input.workflow_session = workflow_session
|
|
956
|
+
|
|
957
|
+
# Create session_state copy once to avoid duplication.
|
|
958
|
+
# Consider both run_context.session_state and session_state.
|
|
959
|
+
if run_context is not None and run_context.session_state is not None:
|
|
960
|
+
session_state_copy = run_context.session_state
|
|
961
|
+
else:
|
|
962
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
963
|
+
|
|
964
|
+
# Considering both stream_events and stream_intermediate_steps (deprecated)
|
|
965
|
+
if stream_intermediate_steps is not None:
|
|
966
|
+
warnings.warn(
|
|
967
|
+
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
968
|
+
DeprecationWarning,
|
|
969
|
+
stacklevel=2,
|
|
970
|
+
)
|
|
971
|
+
stream_events = stream_events or stream_intermediate_steps
|
|
972
|
+
|
|
973
|
+
if stream_events and workflow_run_response:
|
|
643
974
|
# Emit StepStartedEvent
|
|
644
975
|
yield StepStartedEvent(
|
|
645
976
|
run_id=workflow_run_response.run_id or "",
|
|
@@ -660,61 +991,117 @@ class Step:
|
|
|
660
991
|
|
|
661
992
|
if self._executor_type == "function":
|
|
662
993
|
log_debug(f"Executing async function executor for step: {self.name}")
|
|
663
|
-
import inspect
|
|
664
994
|
|
|
665
995
|
# Check if the function is an async generator
|
|
666
|
-
if
|
|
996
|
+
if _is_async_generator_function(self.active_executor):
|
|
667
997
|
content = ""
|
|
668
998
|
# It's an async generator - iterate over it
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
999
|
+
iterator = await self._acall_custom_function(
|
|
1000
|
+
self.active_executor,
|
|
1001
|
+
step_input,
|
|
1002
|
+
session_state_copy,
|
|
1003
|
+
run_context,
|
|
1004
|
+
)
|
|
1005
|
+
async for event in iterator: # type: ignore
|
|
1006
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
1007
|
+
if (
|
|
1008
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
1009
|
+
and event.content is not None
|
|
1010
|
+
):
|
|
1011
|
+
if isinstance(event.content, str):
|
|
1012
|
+
content += event.content
|
|
1013
|
+
elif isinstance(event.content, BaseModel):
|
|
1014
|
+
content = event.content # type: ignore[assignment]
|
|
1015
|
+
else:
|
|
1016
|
+
content = str(event.content)
|
|
1017
|
+
|
|
1018
|
+
# Only yield executor events if stream_executor_events is True
|
|
1019
|
+
if stream_executor_events:
|
|
1020
|
+
enriched_event = self._enrich_event_with_context(
|
|
1021
|
+
event, workflow_run_response, step_index
|
|
1022
|
+
)
|
|
1023
|
+
yield enriched_event # type: ignore[misc]
|
|
1024
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
1025
|
+
content = event.content # type: ignore[assignment]
|
|
1026
|
+
elif isinstance(event, StepOutput):
|
|
679
1027
|
final_response = event
|
|
680
1028
|
break
|
|
681
1029
|
else:
|
|
682
|
-
|
|
1030
|
+
content += str(event)
|
|
683
1031
|
if not final_response:
|
|
684
1032
|
final_response = StepOutput(content=content)
|
|
685
|
-
elif
|
|
1033
|
+
elif _is_async_callable(self.active_executor):
|
|
686
1034
|
# It's a regular async function - await it
|
|
687
|
-
result = await self.
|
|
1035
|
+
result = await self._acall_custom_function(
|
|
1036
|
+
self.active_executor,
|
|
1037
|
+
step_input,
|
|
1038
|
+
session_state_copy,
|
|
1039
|
+
run_context,
|
|
1040
|
+
)
|
|
688
1041
|
if isinstance(result, StepOutput):
|
|
689
1042
|
final_response = result
|
|
1043
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
1044
|
+
final_response = StepOutput(content=result.content)
|
|
690
1045
|
else:
|
|
691
1046
|
final_response = StepOutput(content=str(result))
|
|
692
|
-
elif
|
|
1047
|
+
elif _is_generator_function(self.active_executor):
|
|
693
1048
|
content = ""
|
|
694
1049
|
# It's a regular generator function - iterate over it
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1050
|
+
iterator = self._call_custom_function(
|
|
1051
|
+
self.active_executor,
|
|
1052
|
+
step_input,
|
|
1053
|
+
session_state_copy,
|
|
1054
|
+
run_context,
|
|
1055
|
+
)
|
|
1056
|
+
for event in iterator: # type: ignore
|
|
1057
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
1058
|
+
if (
|
|
1059
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
1060
|
+
and event.content is not None
|
|
1061
|
+
):
|
|
1062
|
+
if isinstance(event.content, str):
|
|
1063
|
+
content += event.content
|
|
1064
|
+
elif isinstance(event.content, BaseModel):
|
|
1065
|
+
content = event.content # type: ignore[assignment]
|
|
1066
|
+
else:
|
|
1067
|
+
content = str(event.content)
|
|
1068
|
+
|
|
1069
|
+
# Only yield executor events if stream_executor_events is True
|
|
1070
|
+
if stream_executor_events:
|
|
1071
|
+
enriched_event = self._enrich_event_with_context(
|
|
1072
|
+
event, workflow_run_response, step_index
|
|
1073
|
+
)
|
|
1074
|
+
yield enriched_event # type: ignore[misc]
|
|
1075
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
1076
|
+
content = event.content # type: ignore[assignment]
|
|
1077
|
+
elif isinstance(event, StepOutput):
|
|
705
1078
|
final_response = event
|
|
706
1079
|
break
|
|
707
1080
|
else:
|
|
708
|
-
|
|
1081
|
+
if isinstance(content, str):
|
|
1082
|
+
content += str(event)
|
|
1083
|
+
else:
|
|
1084
|
+
content = str(event)
|
|
709
1085
|
if not final_response:
|
|
710
1086
|
final_response = StepOutput(content=content)
|
|
711
1087
|
else:
|
|
712
1088
|
# It's a regular function - call it directly
|
|
713
|
-
result = self.
|
|
1089
|
+
result = self._call_custom_function(
|
|
1090
|
+
self.active_executor, # type: ignore[arg-type]
|
|
1091
|
+
step_input,
|
|
1092
|
+
session_state_copy,
|
|
1093
|
+
run_context,
|
|
1094
|
+
)
|
|
714
1095
|
if isinstance(result, StepOutput):
|
|
715
1096
|
final_response = result
|
|
1097
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
1098
|
+
final_response = StepOutput(content=result.content)
|
|
716
1099
|
else:
|
|
717
1100
|
final_response = StepOutput(content=str(result))
|
|
1101
|
+
|
|
1102
|
+
# Merge session_state changes back
|
|
1103
|
+
if run_context is None and session_state is not None:
|
|
1104
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
718
1105
|
else:
|
|
719
1106
|
# For agents and teams, prepare message with context
|
|
720
1107
|
message = self._prepare_message(
|
|
@@ -741,9 +1128,22 @@ class Step:
|
|
|
741
1128
|
if isinstance(self.active_executor, Team):
|
|
742
1129
|
kwargs["store_member_responses"] = True
|
|
743
1130
|
|
|
744
|
-
|
|
1131
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
1132
|
+
|
|
1133
|
+
use_history = (
|
|
1134
|
+
self.add_workflow_history
|
|
1135
|
+
if self.add_workflow_history is not None
|
|
1136
|
+
else add_workflow_history_to_steps
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
final_message = message
|
|
1140
|
+
if use_history and workflow_session:
|
|
1141
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
1142
|
+
if history_messages:
|
|
1143
|
+
final_message = f"{history_messages}{message}"
|
|
1144
|
+
|
|
745
1145
|
response_stream = self.active_executor.arun( # type: ignore
|
|
746
|
-
input=
|
|
1146
|
+
input=final_message,
|
|
747
1147
|
images=images,
|
|
748
1148
|
videos=videos,
|
|
749
1149
|
audio=audios,
|
|
@@ -752,34 +1152,32 @@ class Step:
|
|
|
752
1152
|
user_id=user_id,
|
|
753
1153
|
session_state=session_state_copy,
|
|
754
1154
|
stream=True,
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
workflow_context={
|
|
758
|
-
"workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
|
|
759
|
-
"workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
|
|
760
|
-
"step_id": self.step_id,
|
|
761
|
-
"step_name": self.name,
|
|
762
|
-
"step_index": step_index,
|
|
763
|
-
},
|
|
1155
|
+
stream_events=stream_events,
|
|
1156
|
+
run_context=run_context,
|
|
764
1157
|
yield_run_response=True,
|
|
765
1158
|
**kwargs,
|
|
766
1159
|
)
|
|
767
1160
|
|
|
768
1161
|
active_executor_run_response = None
|
|
769
1162
|
async for event in response_stream:
|
|
770
|
-
log_debug(f"Received async event from agent: {type(event).__name__}")
|
|
771
1163
|
if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
|
|
772
1164
|
active_executor_run_response = event
|
|
773
1165
|
break
|
|
774
|
-
yield
|
|
1166
|
+
# Only yield executor events if stream_executor_events is True
|
|
1167
|
+
if stream_executor_events:
|
|
1168
|
+
enriched_event = self._enrich_event_with_context(
|
|
1169
|
+
event, workflow_run_response, step_index
|
|
1170
|
+
)
|
|
1171
|
+
yield enriched_event # type: ignore[misc]
|
|
775
1172
|
|
|
776
1173
|
# Update workflow session state
|
|
777
|
-
|
|
1174
|
+
if run_context is None and session_state is not None:
|
|
1175
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
778
1176
|
|
|
779
1177
|
if store_executor_outputs and workflow_run_response is not None:
|
|
780
1178
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
|
781
1179
|
|
|
782
|
-
final_response =
|
|
1180
|
+
final_response = active_executor_run_response # type: ignore
|
|
783
1181
|
else:
|
|
784
1182
|
raise ValueError(f"Unsupported executor type: {self._executor_type}")
|
|
785
1183
|
|
|
@@ -791,9 +1189,10 @@ class Step:
|
|
|
791
1189
|
use_workflow_logger()
|
|
792
1190
|
|
|
793
1191
|
# Yield the final response
|
|
1192
|
+
final_response = self._process_step_output(final_response)
|
|
794
1193
|
yield final_response
|
|
795
1194
|
|
|
796
|
-
if
|
|
1195
|
+
if stream_events and workflow_run_response:
|
|
797
1196
|
# Emit StepCompletedEvent
|
|
798
1197
|
yield StepCompletedEvent(
|
|
799
1198
|
run_id=workflow_run_response.run_id or "",
|
|
@@ -826,6 +1225,83 @@ class Step:
|
|
|
826
1225
|
|
|
827
1226
|
return
|
|
828
1227
|
|
|
1228
|
+
def get_chat_history(self, session_id: str, last_n_runs: Optional[int] = None) -> List[Message]:
|
|
1229
|
+
"""Return the step's Agent or Team chat history for the given session.
|
|
1230
|
+
|
|
1231
|
+
Args:
|
|
1232
|
+
session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
|
|
1233
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
1234
|
+
|
|
1235
|
+
Returns:
|
|
1236
|
+
List[Message]: The step's Agent or Team chat history for the given session.
|
|
1237
|
+
"""
|
|
1238
|
+
session: Union[AgentSession, TeamSession, WorkflowSession, None] = None
|
|
1239
|
+
|
|
1240
|
+
if self.agent:
|
|
1241
|
+
session = self.agent.get_session(session_id=session_id)
|
|
1242
|
+
if not session:
|
|
1243
|
+
log_warning("Session not found")
|
|
1244
|
+
return []
|
|
1245
|
+
|
|
1246
|
+
if not isinstance(session, WorkflowSession):
|
|
1247
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1248
|
+
|
|
1249
|
+
session = cast(WorkflowSession, session)
|
|
1250
|
+
return session.get_messages(last_n_runs=last_n_runs, agent_id=self.agent.id)
|
|
1251
|
+
|
|
1252
|
+
elif self.team:
|
|
1253
|
+
session = self.team.get_session(session_id=session_id)
|
|
1254
|
+
if not session:
|
|
1255
|
+
log_warning("Session not found")
|
|
1256
|
+
return []
|
|
1257
|
+
|
|
1258
|
+
if not isinstance(session, WorkflowSession):
|
|
1259
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1260
|
+
|
|
1261
|
+
session = cast(WorkflowSession, session)
|
|
1262
|
+
return session.get_messages(last_n_runs=last_n_runs, team_id=self.team.id)
|
|
1263
|
+
|
|
1264
|
+
return []
|
|
1265
|
+
|
|
1266
|
+
async def aget_chat_history(
|
|
1267
|
+
self, session_id: Optional[str] = None, last_n_runs: Optional[int] = None
|
|
1268
|
+
) -> List[Message]:
|
|
1269
|
+
"""Return the step's Agent or Team chat history for the given session.
|
|
1270
|
+
|
|
1271
|
+
Args:
|
|
1272
|
+
session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
|
|
1273
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
1274
|
+
|
|
1275
|
+
Returns:
|
|
1276
|
+
List[Message]: The step's Agent or Team chat history for the given session.
|
|
1277
|
+
"""
|
|
1278
|
+
session: Union[AgentSession, TeamSession, WorkflowSession, None] = None
|
|
1279
|
+
|
|
1280
|
+
if self.agent:
|
|
1281
|
+
session = await self.agent.aget_session(session_id=session_id)
|
|
1282
|
+
if not session:
|
|
1283
|
+
log_warning("Session not found")
|
|
1284
|
+
return []
|
|
1285
|
+
|
|
1286
|
+
if not isinstance(session, WorkflowSession):
|
|
1287
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1288
|
+
|
|
1289
|
+
session = cast(WorkflowSession, session)
|
|
1290
|
+
return session.get_messages(last_n_runs=last_n_runs, agent_id=self.agent.id)
|
|
1291
|
+
|
|
1292
|
+
elif self.team:
|
|
1293
|
+
session = await self.team.aget_session(session_id=session_id)
|
|
1294
|
+
if not session:
|
|
1295
|
+
log_warning("Session not found")
|
|
1296
|
+
return []
|
|
1297
|
+
|
|
1298
|
+
if not isinstance(session, WorkflowSession):
|
|
1299
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1300
|
+
|
|
1301
|
+
return session.get_messages(last_n_runs=last_n_runs, team_id=self.team.id)
|
|
1302
|
+
|
|
1303
|
+
return []
|
|
1304
|
+
|
|
829
1305
|
def _store_executor_response(
|
|
830
1306
|
self, workflow_run_response: "WorkflowRunOutput", executor_run_response: Union[RunOutput, TeamRunOutput]
|
|
831
1307
|
) -> None:
|
|
@@ -835,6 +1311,14 @@ class Step:
|
|
|
835
1311
|
executor_run_response.parent_run_id = workflow_run_response.run_id
|
|
836
1312
|
executor_run_response.workflow_step_id = self.step_id
|
|
837
1313
|
|
|
1314
|
+
# Scrub the executor response based on the executor's storage flags before storing
|
|
1315
|
+
if (
|
|
1316
|
+
not self.active_executor.store_media
|
|
1317
|
+
or not self.active_executor.store_tool_messages
|
|
1318
|
+
or not self.active_executor.store_history_messages
|
|
1319
|
+
): # type: ignore
|
|
1320
|
+
self.active_executor._scrub_run_output_for_storage(executor_run_response) # type: ignore
|
|
1321
|
+
|
|
838
1322
|
# Get the raw response from the step's active executor
|
|
839
1323
|
raw_response = executor_run_response
|
|
840
1324
|
if raw_response and isinstance(raw_response, (RunOutput, TeamRunOutput)):
|
|
@@ -931,20 +1415,20 @@ class Step:
|
|
|
931
1415
|
# Convert any other type to string
|
|
932
1416
|
return RunOutput(content=str(result))
|
|
933
1417
|
|
|
934
|
-
def _convert_audio_artifacts_to_audio(self, audio_artifacts: List[
|
|
1418
|
+
def _convert_audio_artifacts_to_audio(self, audio_artifacts: List[Audio]) -> List[Audio]:
|
|
935
1419
|
"""Convert AudioArtifact objects to Audio objects"""
|
|
936
1420
|
audios = []
|
|
937
1421
|
for audio_artifact in audio_artifacts:
|
|
938
1422
|
if audio_artifact.url:
|
|
939
1423
|
audios.append(Audio(url=audio_artifact.url))
|
|
940
|
-
elif audio_artifact.
|
|
941
|
-
audios.append(Audio(content=audio_artifact.
|
|
1424
|
+
elif audio_artifact.content:
|
|
1425
|
+
audios.append(Audio(content=audio_artifact.content))
|
|
942
1426
|
else:
|
|
943
|
-
logger.warning(f"Skipping AudioArtifact with no URL or
|
|
1427
|
+
logger.warning(f"Skipping AudioArtifact with no URL or content: {audio_artifact}")
|
|
944
1428
|
continue
|
|
945
1429
|
return audios
|
|
946
1430
|
|
|
947
|
-
def _convert_image_artifacts_to_images(self, image_artifacts: List[
|
|
1431
|
+
def _convert_image_artifacts_to_images(self, image_artifacts: List[Image]) -> List[Image]:
|
|
948
1432
|
"""
|
|
949
1433
|
Convert ImageArtifact objects to Image objects with proper content handling.
|
|
950
1434
|
|
|
@@ -996,7 +1480,7 @@ class Step:
|
|
|
996
1480
|
|
|
997
1481
|
return images
|
|
998
1482
|
|
|
999
|
-
def _convert_video_artifacts_to_videos(self, video_artifacts: List[
|
|
1483
|
+
def _convert_video_artifacts_to_videos(self, video_artifacts: List[Video]) -> List[Video]:
|
|
1000
1484
|
"""
|
|
1001
1485
|
Convert VideoArtifact objects to Video objects with proper content handling.
|
|
1002
1486
|
|
|
@@ -1021,3 +1505,28 @@ class Step:
|
|
|
1021
1505
|
continue
|
|
1022
1506
|
|
|
1023
1507
|
return videos
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
def _is_async_callable(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1511
|
+
"""Checks if obj is an async callable (coroutine function or callable with async __call__)"""
|
|
1512
|
+
return inspect.iscoroutinefunction(obj) or (callable(obj) and inspect.iscoroutinefunction(obj.__call__))
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def _is_generator_function(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1516
|
+
"""Checks if obj is a generator function, including callable class instances with generator __call__ methods"""
|
|
1517
|
+
if inspect.isgeneratorfunction(obj):
|
|
1518
|
+
return True
|
|
1519
|
+
# Check if it's a callable class instance with a generator __call__ method
|
|
1520
|
+
if callable(obj) and hasattr(obj, "__call__"):
|
|
1521
|
+
return inspect.isgeneratorfunction(obj.__call__)
|
|
1522
|
+
return False
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
def _is_async_generator_function(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1526
|
+
"""Checks if obj is an async generator function, including callable class instances"""
|
|
1527
|
+
if inspect.isasyncgenfunction(obj):
|
|
1528
|
+
return True
|
|
1529
|
+
# Check if it's a callable class instance with an async generator __call__ method
|
|
1530
|
+
if callable(obj) and hasattr(obj, "__call__"):
|
|
1531
|
+
return inspect.isasyncgenfunction(obj.__call__)
|
|
1532
|
+
return False
|