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/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
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:
|
|
@@ -169,32 +185,37 @@ class Step:
|
|
|
169
185
|
func: Callable,
|
|
170
186
|
step_input: StepInput,
|
|
171
187
|
session_state: Optional[Dict[str, Any]] = None,
|
|
188
|
+
run_context: Optional[RunContext] = None,
|
|
172
189
|
) -> Any:
|
|
173
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
|
|
174
195
|
if session_state is not None and self._function_has_session_state_param():
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
kwargs["session_state"] = session_state
|
|
197
|
+
|
|
198
|
+
return func(step_input, **kwargs)
|
|
178
199
|
|
|
179
200
|
async def _acall_custom_function(
|
|
180
201
|
self,
|
|
181
202
|
func: Callable,
|
|
182
203
|
step_input: StepInput,
|
|
183
204
|
session_state: Optional[Dict[str, Any]] = None,
|
|
205
|
+
run_context: Optional[RunContext] = None,
|
|
184
206
|
) -> Any:
|
|
185
207
|
"""Call custom async function with session_state support if the function accepts it"""
|
|
186
|
-
import inspect
|
|
187
208
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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)
|
|
193
217
|
else:
|
|
194
|
-
|
|
195
|
-
return await func(step_input, session_state)
|
|
196
|
-
else:
|
|
197
|
-
return await func(step_input)
|
|
218
|
+
return await func(step_input, **kwargs)
|
|
198
219
|
|
|
199
220
|
def execute(
|
|
200
221
|
self,
|
|
@@ -202,8 +223,13 @@ class Step:
|
|
|
202
223
|
session_id: Optional[str] = None,
|
|
203
224
|
user_id: Optional[str] = None,
|
|
204
225
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
226
|
+
run_context: Optional[RunContext] = None,
|
|
205
227
|
session_state: Optional[Dict[str, Any]] = None,
|
|
206
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,
|
|
232
|
+
background_tasks: Optional[Any] = None,
|
|
207
233
|
) -> StepOutput:
|
|
208
234
|
"""Execute the step with StepInput, returning final StepOutput (non-streaming)"""
|
|
209
235
|
log_debug(f"Executing step: {self.name}")
|
|
@@ -211,41 +237,65 @@ class Step:
|
|
|
211
237
|
if step_input.previous_step_outputs:
|
|
212
238
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
213
239
|
|
|
214
|
-
|
|
240
|
+
if workflow_session:
|
|
241
|
+
step_input.workflow_session = workflow_session
|
|
242
|
+
|
|
243
|
+
# Create session_state copy once to avoid duplication.
|
|
244
|
+
# Consider both run_context.session_state and session_state.
|
|
245
|
+
if run_context is not None and run_context.session_state is not None:
|
|
246
|
+
session_state_copy = run_context.session_state
|
|
247
|
+
else:
|
|
248
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
215
249
|
|
|
216
250
|
# Execute with retries
|
|
217
251
|
for attempt in range(self.max_retries + 1):
|
|
218
252
|
try:
|
|
219
253
|
response: Union[RunOutput, TeamRunOutput, StepOutput]
|
|
220
254
|
if self._executor_type == "function":
|
|
221
|
-
if
|
|
222
|
-
self.active_executor
|
|
223
|
-
):
|
|
255
|
+
if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
|
|
224
256
|
raise ValueError("Cannot use async function with synchronous execution")
|
|
225
|
-
if
|
|
257
|
+
if _is_generator_function(self.active_executor):
|
|
226
258
|
content = ""
|
|
227
259
|
final_response = None
|
|
228
260
|
try:
|
|
229
261
|
for chunk in self._call_custom_function(
|
|
230
|
-
self.active_executor,
|
|
262
|
+
self.active_executor,
|
|
263
|
+
step_input,
|
|
264
|
+
session_state_copy, # type: ignore[arg-type]
|
|
265
|
+
run_context,
|
|
231
266
|
): # type: ignore
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
267
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
268
|
+
if (
|
|
269
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
270
|
+
and chunk.content is not None
|
|
271
|
+
):
|
|
272
|
+
# Its a regular chunk of content
|
|
273
|
+
if isinstance(chunk.content, str):
|
|
274
|
+
content += chunk.content
|
|
275
|
+
# Its the BaseModel object, set it as the content. Replace any previous content.
|
|
276
|
+
# There should be no previous str content at this point
|
|
277
|
+
elif isinstance(chunk.content, BaseModel):
|
|
278
|
+
content = chunk.content # type: ignore[assignment]
|
|
279
|
+
else:
|
|
280
|
+
# Case when parse_response is False and the content is a dict
|
|
281
|
+
content += str(chunk.content)
|
|
282
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
283
|
+
# This is the final response from the agent/team
|
|
284
|
+
content = chunk.content # type: ignore[assignment]
|
|
285
|
+
# If the chunk is a StepOutput, use it as the final response
|
|
286
|
+
elif isinstance(chunk, StepOutput):
|
|
287
|
+
final_response = chunk
|
|
288
|
+
break
|
|
289
|
+
# Non Agent/Team data structure that was yielded
|
|
238
290
|
else:
|
|
239
291
|
content += str(chunk)
|
|
240
|
-
if isinstance(chunk, StepOutput):
|
|
241
|
-
final_response = chunk
|
|
242
292
|
|
|
243
293
|
except StopIteration as e:
|
|
244
294
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
245
295
|
final_response = e.value
|
|
246
296
|
|
|
247
297
|
# Merge session_state changes back
|
|
248
|
-
if session_state is not None:
|
|
298
|
+
if run_context is None and session_state is not None:
|
|
249
299
|
merge_dictionaries(session_state, session_state_copy)
|
|
250
300
|
|
|
251
301
|
if final_response is not None:
|
|
@@ -254,15 +304,22 @@ class Step:
|
|
|
254
304
|
response = StepOutput(content=content)
|
|
255
305
|
else:
|
|
256
306
|
# Execute function with signature inspection for session_state support
|
|
257
|
-
result = self._call_custom_function(
|
|
307
|
+
result = self._call_custom_function(
|
|
308
|
+
self.active_executor, # type: ignore[arg-type]
|
|
309
|
+
step_input,
|
|
310
|
+
session_state_copy,
|
|
311
|
+
run_context,
|
|
312
|
+
)
|
|
258
313
|
|
|
259
314
|
# Merge session_state changes back
|
|
260
|
-
if session_state is not None:
|
|
315
|
+
if run_context is None and session_state is not None:
|
|
261
316
|
merge_dictionaries(session_state, session_state_copy)
|
|
262
317
|
|
|
263
318
|
# If function returns StepOutput, use it directly
|
|
264
319
|
if isinstance(result, StepOutput):
|
|
265
320
|
response = result
|
|
321
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
322
|
+
response = StepOutput(content=result.content)
|
|
266
323
|
else:
|
|
267
324
|
response = StepOutput(content=str(result))
|
|
268
325
|
else:
|
|
@@ -292,8 +349,26 @@ class Step:
|
|
|
292
349
|
if isinstance(self.active_executor, Team):
|
|
293
350
|
kwargs["store_member_responses"] = True
|
|
294
351
|
|
|
352
|
+
# Forward background_tasks if provided
|
|
353
|
+
if background_tasks is not None:
|
|
354
|
+
kwargs["background_tasks"] = background_tasks
|
|
355
|
+
|
|
356
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
357
|
+
|
|
358
|
+
use_history = (
|
|
359
|
+
self.add_workflow_history
|
|
360
|
+
if self.add_workflow_history is not None
|
|
361
|
+
else add_workflow_history_to_steps
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
final_message = message
|
|
365
|
+
if use_history and workflow_session:
|
|
366
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
367
|
+
if history_messages:
|
|
368
|
+
final_message = f"{history_messages}{message}"
|
|
369
|
+
|
|
295
370
|
response = self.active_executor.run( # type: ignore
|
|
296
|
-
input=
|
|
371
|
+
input=final_message, # type: ignore
|
|
297
372
|
images=images,
|
|
298
373
|
videos=videos,
|
|
299
374
|
audio=audios,
|
|
@@ -301,12 +376,13 @@ class Step:
|
|
|
301
376
|
session_id=session_id,
|
|
302
377
|
user_id=user_id,
|
|
303
378
|
session_state=session_state_copy, # Send a copy to the executor
|
|
379
|
+
run_context=run_context,
|
|
304
380
|
**kwargs,
|
|
305
381
|
)
|
|
306
382
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
383
|
+
# Update workflow session state
|
|
384
|
+
if run_context is None and session_state is not None:
|
|
385
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
310
386
|
|
|
311
387
|
if store_executor_outputs and workflow_run_response is not None:
|
|
312
388
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -335,41 +411,98 @@ class Step:
|
|
|
335
411
|
|
|
336
412
|
return StepOutput(content=f"Step {self.name} failed but skipped", success=False)
|
|
337
413
|
|
|
414
|
+
def _function_has_run_context_param(self) -> bool:
|
|
415
|
+
"""Check if the custom function has a run_context parameter"""
|
|
416
|
+
if self._executor_type != "function":
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
sig = inspect.signature(self.active_executor) # type: ignore
|
|
421
|
+
return "run_context" in sig.parameters
|
|
422
|
+
except Exception:
|
|
423
|
+
return False
|
|
424
|
+
|
|
338
425
|
def _function_has_session_state_param(self) -> bool:
|
|
339
426
|
"""Check if the custom function has a session_state parameter"""
|
|
340
427
|
if self._executor_type != "function":
|
|
341
428
|
return False
|
|
342
429
|
|
|
343
430
|
try:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
sig = signature(self.active_executor) # type: ignore
|
|
431
|
+
sig = inspect.signature(self.active_executor) # type: ignore
|
|
347
432
|
return "session_state" in sig.parameters
|
|
348
433
|
except Exception:
|
|
349
434
|
return False
|
|
350
435
|
|
|
436
|
+
def _enrich_event_with_context(
|
|
437
|
+
self,
|
|
438
|
+
event: Any,
|
|
439
|
+
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
440
|
+
step_index: Optional[Union[int, tuple]] = None,
|
|
441
|
+
) -> Any:
|
|
442
|
+
"""Enrich event with step and workflow context information"""
|
|
443
|
+
if workflow_run_response is None:
|
|
444
|
+
return event
|
|
445
|
+
if hasattr(event, "workflow_id"):
|
|
446
|
+
event.workflow_id = workflow_run_response.workflow_id
|
|
447
|
+
if hasattr(event, "workflow_run_id"):
|
|
448
|
+
event.workflow_run_id = workflow_run_response.run_id
|
|
449
|
+
if hasattr(event, "step_id"):
|
|
450
|
+
event.step_id = self.step_id
|
|
451
|
+
if hasattr(event, "step_name") and self.name is not None:
|
|
452
|
+
if getattr(event, "step_name", None) is None:
|
|
453
|
+
event.step_name = self.name
|
|
454
|
+
# Only set step_index if it's not already set (preserve parallel.py's tuples)
|
|
455
|
+
if hasattr(event, "step_index") and step_index is not None:
|
|
456
|
+
if event.step_index is None:
|
|
457
|
+
event.step_index = step_index
|
|
458
|
+
|
|
459
|
+
return event
|
|
460
|
+
|
|
351
461
|
def execute_stream(
|
|
352
462
|
self,
|
|
353
463
|
step_input: StepInput,
|
|
354
464
|
session_id: Optional[str] = None,
|
|
355
465
|
user_id: Optional[str] = None,
|
|
466
|
+
stream_events: bool = False,
|
|
356
467
|
stream_intermediate_steps: bool = False,
|
|
468
|
+
stream_executor_events: bool = True,
|
|
357
469
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
470
|
+
run_context: Optional[RunContext] = None,
|
|
358
471
|
session_state: Optional[Dict[str, Any]] = None,
|
|
359
472
|
step_index: Optional[Union[int, tuple]] = None,
|
|
360
473
|
store_executor_outputs: bool = True,
|
|
361
474
|
parent_step_id: Optional[str] = None,
|
|
475
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
476
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
477
|
+
num_history_runs: int = 3,
|
|
478
|
+
background_tasks: Optional[Any] = None,
|
|
362
479
|
) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
|
|
363
480
|
"""Execute the step with event-driven streaming support"""
|
|
364
481
|
|
|
365
482
|
if step_input.previous_step_outputs:
|
|
366
483
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
367
484
|
|
|
368
|
-
|
|
369
|
-
|
|
485
|
+
if workflow_session:
|
|
486
|
+
step_input.workflow_session = workflow_session
|
|
487
|
+
|
|
488
|
+
# Create session_state copy once to avoid duplication.
|
|
489
|
+
# Consider both run_context.session_state and session_state.
|
|
490
|
+
if run_context is not None and run_context.session_state is not None:
|
|
491
|
+
session_state_copy = run_context.session_state
|
|
492
|
+
else:
|
|
493
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
494
|
+
|
|
495
|
+
# Considering both stream_events and stream_intermediate_steps (deprecated)
|
|
496
|
+
if stream_intermediate_steps is not None:
|
|
497
|
+
warnings.warn(
|
|
498
|
+
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
499
|
+
DeprecationWarning,
|
|
500
|
+
stacklevel=2,
|
|
501
|
+
)
|
|
502
|
+
stream_events = stream_events or stream_intermediate_steps
|
|
370
503
|
|
|
371
504
|
# Emit StepStartedEvent
|
|
372
|
-
if
|
|
505
|
+
if stream_events and workflow_run_response:
|
|
373
506
|
yield StepStartedEvent(
|
|
374
507
|
run_id=workflow_run_response.run_id or "",
|
|
375
508
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -389,34 +522,46 @@ class Step:
|
|
|
389
522
|
|
|
390
523
|
if self._executor_type == "function":
|
|
391
524
|
log_debug(f"Executing function executor for step: {self.name}")
|
|
392
|
-
|
|
393
|
-
if inspect.iscoroutinefunction(self.active_executor) or inspect.isasyncgenfunction(
|
|
394
|
-
self.active_executor
|
|
395
|
-
):
|
|
525
|
+
if _is_async_callable(self.active_executor) or _is_async_generator_function(self.active_executor):
|
|
396
526
|
raise ValueError("Cannot use async function with synchronous execution")
|
|
397
|
-
|
|
398
|
-
if inspect.isgeneratorfunction(self.active_executor):
|
|
527
|
+
if _is_generator_function(self.active_executor):
|
|
399
528
|
log_debug("Function returned iterable, streaming events")
|
|
400
529
|
content = ""
|
|
401
530
|
try:
|
|
402
|
-
iterator = self._call_custom_function(
|
|
531
|
+
iterator = self._call_custom_function(
|
|
532
|
+
self.active_executor,
|
|
533
|
+
step_input,
|
|
534
|
+
session_state_copy,
|
|
535
|
+
run_context,
|
|
536
|
+
)
|
|
403
537
|
for event in iterator: # type: ignore
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
538
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
539
|
+
if (
|
|
540
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
541
|
+
and event.content is not None
|
|
542
|
+
):
|
|
543
|
+
if isinstance(event.content, str):
|
|
544
|
+
content += event.content
|
|
545
|
+
elif isinstance(event.content, BaseModel):
|
|
546
|
+
content = event.content # type: ignore[assignment]
|
|
547
|
+
else:
|
|
548
|
+
content = str(event.content)
|
|
549
|
+
# Only yield executor events if stream_executor_events is True
|
|
550
|
+
if stream_executor_events:
|
|
551
|
+
enriched_event = self._enrich_event_with_context(
|
|
552
|
+
event, workflow_run_response, step_index
|
|
553
|
+
)
|
|
554
|
+
yield enriched_event # type: ignore[misc]
|
|
555
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
556
|
+
content = event.content # type: ignore[assignment]
|
|
557
|
+
elif isinstance(event, StepOutput):
|
|
413
558
|
final_response = event
|
|
414
559
|
break
|
|
415
560
|
else:
|
|
416
|
-
|
|
561
|
+
content += str(event)
|
|
417
562
|
|
|
418
563
|
# Merge session_state changes back
|
|
419
|
-
if session_state is not None:
|
|
564
|
+
if run_context is None and session_state is not None:
|
|
420
565
|
merge_dictionaries(session_state, session_state_copy)
|
|
421
566
|
|
|
422
567
|
if not final_response:
|
|
@@ -426,14 +571,21 @@ class Step:
|
|
|
426
571
|
final_response = e.value
|
|
427
572
|
|
|
428
573
|
else:
|
|
429
|
-
result = self._call_custom_function(
|
|
574
|
+
result = self._call_custom_function(
|
|
575
|
+
self.active_executor, # type: ignore[arg-type]
|
|
576
|
+
step_input,
|
|
577
|
+
session_state_copy,
|
|
578
|
+
run_context,
|
|
579
|
+
)
|
|
430
580
|
|
|
431
581
|
# Merge session_state changes back
|
|
432
|
-
if session_state is not None:
|
|
582
|
+
if run_context is None and session_state is not None:
|
|
433
583
|
merge_dictionaries(session_state, session_state_copy)
|
|
434
584
|
|
|
435
585
|
if isinstance(result, StepOutput):
|
|
436
586
|
final_response = result
|
|
587
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
588
|
+
final_response = StepOutput(content=result.content)
|
|
437
589
|
else:
|
|
438
590
|
final_response = StepOutput(content=str(result))
|
|
439
591
|
log_debug("Function returned non-iterable, created StepOutput")
|
|
@@ -463,8 +615,26 @@ class Step:
|
|
|
463
615
|
if isinstance(self.active_executor, Team):
|
|
464
616
|
kwargs["store_member_responses"] = True
|
|
465
617
|
|
|
618
|
+
# Forward background_tasks if provided
|
|
619
|
+
if background_tasks is not None:
|
|
620
|
+
kwargs["background_tasks"] = background_tasks
|
|
621
|
+
|
|
622
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
623
|
+
|
|
624
|
+
use_history = (
|
|
625
|
+
self.add_workflow_history
|
|
626
|
+
if self.add_workflow_history is not None
|
|
627
|
+
else add_workflow_history_to_steps
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
final_message = message
|
|
631
|
+
if use_history and workflow_session:
|
|
632
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
633
|
+
if history_messages:
|
|
634
|
+
final_message = f"{history_messages}{message}"
|
|
635
|
+
|
|
466
636
|
response_stream = self.active_executor.run( # type: ignore[call-overload, misc]
|
|
467
|
-
input=
|
|
637
|
+
input=final_message,
|
|
468
638
|
images=images,
|
|
469
639
|
videos=videos,
|
|
470
640
|
audio=audios,
|
|
@@ -473,16 +643,9 @@ class Step:
|
|
|
473
643
|
user_id=user_id,
|
|
474
644
|
session_state=session_state_copy, # Send a copy to the executor
|
|
475
645
|
stream=True,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
"workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
|
|
480
|
-
"workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
|
|
481
|
-
"step_id": self.step_id,
|
|
482
|
-
"step_name": self.name,
|
|
483
|
-
"step_index": step_index,
|
|
484
|
-
},
|
|
485
|
-
yield_run_response=True,
|
|
646
|
+
stream_events=stream_events,
|
|
647
|
+
yield_run_output=True,
|
|
648
|
+
run_context=run_context,
|
|
486
649
|
**kwargs,
|
|
487
650
|
)
|
|
488
651
|
|
|
@@ -490,12 +653,17 @@ class Step:
|
|
|
490
653
|
for event in response_stream:
|
|
491
654
|
if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
|
|
492
655
|
active_executor_run_response = event
|
|
493
|
-
|
|
494
|
-
yield
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
656
|
+
continue
|
|
657
|
+
# Only yield executor events if stream_executor_events is True
|
|
658
|
+
if stream_executor_events:
|
|
659
|
+
enriched_event = self._enrich_event_with_context(
|
|
660
|
+
event, workflow_run_response, step_index
|
|
661
|
+
)
|
|
662
|
+
yield enriched_event # type: ignore[misc]
|
|
663
|
+
|
|
664
|
+
# Update workflow session state
|
|
665
|
+
if run_context is None and session_state is not None:
|
|
666
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
499
667
|
|
|
500
668
|
if store_executor_outputs and workflow_run_response is not None:
|
|
501
669
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
|
@@ -518,7 +686,7 @@ class Step:
|
|
|
518
686
|
yield final_response
|
|
519
687
|
|
|
520
688
|
# Emit StepCompletedEvent
|
|
521
|
-
if
|
|
689
|
+
if stream_events and workflow_run_response:
|
|
522
690
|
yield StepCompletedEvent(
|
|
523
691
|
run_id=workflow_run_response.run_id or "",
|
|
524
692
|
workflow_name=workflow_run_response.workflow_name or "",
|
|
@@ -556,8 +724,13 @@ class Step:
|
|
|
556
724
|
session_id: Optional[str] = None,
|
|
557
725
|
user_id: Optional[str] = None,
|
|
558
726
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
727
|
+
run_context: Optional[RunContext] = None,
|
|
559
728
|
session_state: Optional[Dict[str, Any]] = None,
|
|
560
729
|
store_executor_outputs: bool = True,
|
|
730
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
731
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
732
|
+
num_history_runs: int = 3,
|
|
733
|
+
background_tasks: Optional[Any] = None,
|
|
561
734
|
) -> StepOutput:
|
|
562
735
|
"""Execute the step with StepInput, returning final StepOutput (non-streaming)"""
|
|
563
736
|
logger.info(f"Executing async step (non-streaming): {self.name}")
|
|
@@ -566,59 +739,87 @@ class Step:
|
|
|
566
739
|
if step_input.previous_step_outputs:
|
|
567
740
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
568
741
|
|
|
569
|
-
|
|
570
|
-
|
|
742
|
+
if workflow_session:
|
|
743
|
+
step_input.workflow_session = workflow_session
|
|
744
|
+
|
|
745
|
+
# Create session_state copy once to avoid duplication.
|
|
746
|
+
# Consider both run_context.session_state and session_state.
|
|
747
|
+
if run_context is not None and run_context.session_state is not None:
|
|
748
|
+
session_state_copy = run_context.session_state
|
|
749
|
+
else:
|
|
750
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
571
751
|
|
|
572
752
|
# Execute with retries
|
|
573
753
|
for attempt in range(self.max_retries + 1):
|
|
574
754
|
try:
|
|
575
755
|
if self._executor_type == "function":
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if inspect.isgeneratorfunction(self.active_executor) or inspect.isasyncgenfunction(
|
|
756
|
+
if _is_generator_function(self.active_executor) or _is_async_generator_function(
|
|
579
757
|
self.active_executor
|
|
580
758
|
):
|
|
581
759
|
content = ""
|
|
582
760
|
final_response = None
|
|
583
761
|
try:
|
|
584
|
-
if
|
|
762
|
+
if _is_generator_function(self.active_executor):
|
|
585
763
|
iterator = self._call_custom_function(
|
|
586
|
-
self.active_executor,
|
|
587
|
-
|
|
764
|
+
self.active_executor,
|
|
765
|
+
step_input,
|
|
766
|
+
session_state_copy,
|
|
767
|
+
run_context,
|
|
768
|
+
)
|
|
588
769
|
for chunk in iterator: # type: ignore
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
770
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
771
|
+
if (
|
|
772
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
773
|
+
and chunk.content is not None
|
|
774
|
+
):
|
|
775
|
+
if isinstance(chunk.content, str):
|
|
776
|
+
content += chunk.content
|
|
777
|
+
elif isinstance(chunk.content, BaseModel):
|
|
778
|
+
content = chunk.content # type: ignore[assignment]
|
|
779
|
+
else:
|
|
780
|
+
content = str(chunk.content)
|
|
781
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
782
|
+
content = chunk.content # type: ignore[assignment]
|
|
783
|
+
elif isinstance(chunk, StepOutput):
|
|
784
|
+
final_response = chunk
|
|
785
|
+
break
|
|
595
786
|
else:
|
|
596
787
|
content += str(chunk)
|
|
597
|
-
|
|
598
|
-
final_response = chunk
|
|
788
|
+
|
|
599
789
|
else:
|
|
600
|
-
if
|
|
790
|
+
if _is_async_generator_function(self.active_executor):
|
|
601
791
|
iterator = await self._acall_custom_function(
|
|
602
|
-
self.active_executor,
|
|
603
|
-
|
|
792
|
+
self.active_executor,
|
|
793
|
+
step_input,
|
|
794
|
+
session_state_copy,
|
|
795
|
+
run_context,
|
|
796
|
+
)
|
|
604
797
|
async for chunk in iterator: # type: ignore
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
798
|
+
if isinstance(chunk, (BaseRunOutputEvent)):
|
|
799
|
+
if (
|
|
800
|
+
isinstance(chunk, (RunContentEvent, TeamRunContentEvent))
|
|
801
|
+
and chunk.content is not None
|
|
802
|
+
):
|
|
803
|
+
if isinstance(chunk.content, str):
|
|
804
|
+
content += chunk.content
|
|
805
|
+
elif isinstance(chunk.content, BaseModel):
|
|
806
|
+
content = chunk.content # type: ignore[assignment]
|
|
807
|
+
else:
|
|
808
|
+
content = str(chunk.content)
|
|
809
|
+
elif isinstance(chunk, (RunOutput, TeamRunOutput)):
|
|
810
|
+
content = chunk.content # type: ignore[assignment]
|
|
811
|
+
elif isinstance(chunk, StepOutput):
|
|
812
|
+
final_response = chunk
|
|
813
|
+
break
|
|
611
814
|
else:
|
|
612
815
|
content += str(chunk)
|
|
613
|
-
if isinstance(chunk, StepOutput):
|
|
614
|
-
final_response = chunk
|
|
615
816
|
|
|
616
817
|
except StopIteration as e:
|
|
617
818
|
if hasattr(e, "value") and isinstance(e.value, StepOutput):
|
|
618
819
|
final_response = e.value
|
|
619
820
|
|
|
620
821
|
# Merge session_state changes back
|
|
621
|
-
if session_state is not None:
|
|
822
|
+
if run_context is None and session_state is not None:
|
|
622
823
|
merge_dictionaries(session_state, session_state_copy)
|
|
623
824
|
|
|
624
825
|
if final_response is not None:
|
|
@@ -626,20 +827,30 @@ class Step:
|
|
|
626
827
|
else:
|
|
627
828
|
response = StepOutput(content=content)
|
|
628
829
|
else:
|
|
629
|
-
if
|
|
830
|
+
if _is_async_callable(self.active_executor):
|
|
630
831
|
result = await self._acall_custom_function(
|
|
631
|
-
self.active_executor,
|
|
632
|
-
|
|
832
|
+
self.active_executor,
|
|
833
|
+
step_input,
|
|
834
|
+
session_state_copy,
|
|
835
|
+
run_context,
|
|
836
|
+
)
|
|
633
837
|
else:
|
|
634
|
-
result = self._call_custom_function(
|
|
838
|
+
result = self._call_custom_function(
|
|
839
|
+
self.active_executor, # type: ignore[arg-type]
|
|
840
|
+
step_input,
|
|
841
|
+
session_state_copy,
|
|
842
|
+
run_context,
|
|
843
|
+
)
|
|
635
844
|
|
|
636
845
|
# Merge session_state changes back
|
|
637
|
-
if session_state is not None:
|
|
846
|
+
if run_context is None and session_state is not None:
|
|
638
847
|
merge_dictionaries(session_state, session_state_copy)
|
|
639
848
|
|
|
640
849
|
# If function returns StepOutput, use it directly
|
|
641
850
|
if isinstance(result, StepOutput):
|
|
642
851
|
response = result
|
|
852
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
853
|
+
response = StepOutput(content=result.content)
|
|
643
854
|
else:
|
|
644
855
|
response = StepOutput(content=str(result))
|
|
645
856
|
|
|
@@ -670,8 +881,26 @@ class Step:
|
|
|
670
881
|
if isinstance(self.active_executor, Team):
|
|
671
882
|
kwargs["store_member_responses"] = True
|
|
672
883
|
|
|
884
|
+
# Forward background_tasks if provided
|
|
885
|
+
if background_tasks is not None:
|
|
886
|
+
kwargs["background_tasks"] = background_tasks
|
|
887
|
+
|
|
888
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
889
|
+
|
|
890
|
+
use_history = (
|
|
891
|
+
self.add_workflow_history
|
|
892
|
+
if self.add_workflow_history is not None
|
|
893
|
+
else add_workflow_history_to_steps
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
final_message = message
|
|
897
|
+
if use_history and workflow_session:
|
|
898
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
899
|
+
if history_messages:
|
|
900
|
+
final_message = f"{history_messages}{message}"
|
|
901
|
+
|
|
673
902
|
response = await self.active_executor.arun( # type: ignore
|
|
674
|
-
input=
|
|
903
|
+
input=final_message, # type: ignore
|
|
675
904
|
images=images,
|
|
676
905
|
videos=videos,
|
|
677
906
|
audio=audios,
|
|
@@ -679,12 +908,13 @@ class Step:
|
|
|
679
908
|
session_id=session_id,
|
|
680
909
|
user_id=user_id,
|
|
681
910
|
session_state=session_state_copy,
|
|
911
|
+
run_context=run_context,
|
|
682
912
|
**kwargs,
|
|
683
913
|
)
|
|
684
914
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
merge_dictionaries(session_state, session_state_copy)
|
|
915
|
+
# Update workflow session state
|
|
916
|
+
if run_context is None and session_state is not None:
|
|
917
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
688
918
|
|
|
689
919
|
if store_executor_outputs and workflow_run_response is not None:
|
|
690
920
|
self._store_executor_response(workflow_run_response, response) # type: ignore
|
|
@@ -718,22 +948,45 @@ class Step:
|
|
|
718
948
|
step_input: StepInput,
|
|
719
949
|
session_id: Optional[str] = None,
|
|
720
950
|
user_id: Optional[str] = None,
|
|
951
|
+
stream_events: bool = False,
|
|
721
952
|
stream_intermediate_steps: bool = False,
|
|
953
|
+
stream_executor_events: bool = True,
|
|
722
954
|
workflow_run_response: Optional["WorkflowRunOutput"] = None,
|
|
955
|
+
run_context: Optional[RunContext] = None,
|
|
723
956
|
session_state: Optional[Dict[str, Any]] = None,
|
|
724
957
|
step_index: Optional[Union[int, tuple]] = None,
|
|
725
958
|
store_executor_outputs: bool = True,
|
|
726
959
|
parent_step_id: Optional[str] = None,
|
|
960
|
+
workflow_session: Optional["WorkflowSession"] = None,
|
|
961
|
+
add_workflow_history_to_steps: Optional[bool] = False,
|
|
962
|
+
num_history_runs: int = 3,
|
|
963
|
+
background_tasks: Optional[Any] = None,
|
|
727
964
|
) -> AsyncIterator[Union[WorkflowRunOutputEvent, StepOutput]]:
|
|
728
965
|
"""Execute the step with event-driven streaming support"""
|
|
729
966
|
|
|
730
967
|
if step_input.previous_step_outputs:
|
|
731
968
|
step_input.previous_step_content = step_input.get_last_step_content()
|
|
732
969
|
|
|
733
|
-
|
|
734
|
-
|
|
970
|
+
if workflow_session:
|
|
971
|
+
step_input.workflow_session = workflow_session
|
|
972
|
+
|
|
973
|
+
# Create session_state copy once to avoid duplication.
|
|
974
|
+
# Consider both run_context.session_state and session_state.
|
|
975
|
+
if run_context is not None and run_context.session_state is not None:
|
|
976
|
+
session_state_copy = run_context.session_state
|
|
977
|
+
else:
|
|
978
|
+
session_state_copy = copy(session_state) if session_state is not None else {}
|
|
979
|
+
|
|
980
|
+
# Considering both stream_events and stream_intermediate_steps (deprecated)
|
|
981
|
+
if stream_intermediate_steps is not None:
|
|
982
|
+
warnings.warn(
|
|
983
|
+
"The 'stream_intermediate_steps' parameter is deprecated and will be removed in future versions. Use 'stream_events' instead.",
|
|
984
|
+
DeprecationWarning,
|
|
985
|
+
stacklevel=2,
|
|
986
|
+
)
|
|
987
|
+
stream_events = stream_events or stream_intermediate_steps
|
|
735
988
|
|
|
736
|
-
if
|
|
989
|
+
if stream_events and workflow_run_response:
|
|
737
990
|
# Emit StepStartedEvent
|
|
738
991
|
yield StepStartedEvent(
|
|
739
992
|
run_id=workflow_run_response.run_id or "",
|
|
@@ -754,68 +1007,116 @@ class Step:
|
|
|
754
1007
|
|
|
755
1008
|
if self._executor_type == "function":
|
|
756
1009
|
log_debug(f"Executing async function executor for step: {self.name}")
|
|
757
|
-
import inspect
|
|
758
1010
|
|
|
759
1011
|
# Check if the function is an async generator
|
|
760
|
-
if
|
|
1012
|
+
if _is_async_generator_function(self.active_executor):
|
|
761
1013
|
content = ""
|
|
762
1014
|
# It's an async generator - iterate over it
|
|
763
1015
|
iterator = await self._acall_custom_function(
|
|
764
|
-
self.active_executor,
|
|
765
|
-
|
|
1016
|
+
self.active_executor,
|
|
1017
|
+
step_input,
|
|
1018
|
+
session_state_copy,
|
|
1019
|
+
run_context,
|
|
1020
|
+
)
|
|
766
1021
|
async for event in iterator: # type: ignore
|
|
767
|
-
if (
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1022
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
1023
|
+
if (
|
|
1024
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
1025
|
+
and event.content is not None
|
|
1026
|
+
):
|
|
1027
|
+
if isinstance(event.content, str):
|
|
1028
|
+
content += event.content
|
|
1029
|
+
elif isinstance(event.content, BaseModel):
|
|
1030
|
+
content = event.content # type: ignore[assignment]
|
|
1031
|
+
else:
|
|
1032
|
+
content = str(event.content)
|
|
1033
|
+
|
|
1034
|
+
# Only yield executor events if stream_executor_events is True
|
|
1035
|
+
if stream_executor_events:
|
|
1036
|
+
enriched_event = self._enrich_event_with_context(
|
|
1037
|
+
event, workflow_run_response, step_index
|
|
1038
|
+
)
|
|
1039
|
+
yield enriched_event # type: ignore[misc]
|
|
1040
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
1041
|
+
content = event.content # type: ignore[assignment]
|
|
1042
|
+
elif isinstance(event, StepOutput):
|
|
776
1043
|
final_response = event
|
|
777
1044
|
break
|
|
778
1045
|
else:
|
|
779
|
-
|
|
1046
|
+
content += str(event)
|
|
780
1047
|
if not final_response:
|
|
781
1048
|
final_response = StepOutput(content=content)
|
|
782
|
-
elif
|
|
1049
|
+
elif _is_async_callable(self.active_executor):
|
|
783
1050
|
# It's a regular async function - await it
|
|
784
|
-
result = await self._acall_custom_function(
|
|
1051
|
+
result = await self._acall_custom_function(
|
|
1052
|
+
self.active_executor,
|
|
1053
|
+
step_input,
|
|
1054
|
+
session_state_copy,
|
|
1055
|
+
run_context,
|
|
1056
|
+
)
|
|
785
1057
|
if isinstance(result, StepOutput):
|
|
786
1058
|
final_response = result
|
|
1059
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
1060
|
+
final_response = StepOutput(content=result.content)
|
|
787
1061
|
else:
|
|
788
1062
|
final_response = StepOutput(content=str(result))
|
|
789
|
-
elif
|
|
1063
|
+
elif _is_generator_function(self.active_executor):
|
|
790
1064
|
content = ""
|
|
791
1065
|
# It's a regular generator function - iterate over it
|
|
792
|
-
iterator = self._call_custom_function(
|
|
1066
|
+
iterator = self._call_custom_function(
|
|
1067
|
+
self.active_executor,
|
|
1068
|
+
step_input,
|
|
1069
|
+
session_state_copy,
|
|
1070
|
+
run_context,
|
|
1071
|
+
)
|
|
793
1072
|
for event in iterator: # type: ignore
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1073
|
+
if isinstance(event, (BaseRunOutputEvent)):
|
|
1074
|
+
if (
|
|
1075
|
+
isinstance(event, (RunContentEvent, TeamRunContentEvent))
|
|
1076
|
+
and event.content is not None
|
|
1077
|
+
):
|
|
1078
|
+
if isinstance(event.content, str):
|
|
1079
|
+
content += event.content
|
|
1080
|
+
elif isinstance(event.content, BaseModel):
|
|
1081
|
+
content = event.content # type: ignore[assignment]
|
|
1082
|
+
else:
|
|
1083
|
+
content = str(event.content)
|
|
1084
|
+
|
|
1085
|
+
# Only yield executor events if stream_executor_events is True
|
|
1086
|
+
if stream_executor_events:
|
|
1087
|
+
enriched_event = self._enrich_event_with_context(
|
|
1088
|
+
event, workflow_run_response, step_index
|
|
1089
|
+
)
|
|
1090
|
+
yield enriched_event # type: ignore[misc]
|
|
1091
|
+
elif isinstance(event, (RunOutput, TeamRunOutput)):
|
|
1092
|
+
content = event.content # type: ignore[assignment]
|
|
1093
|
+
elif isinstance(event, StepOutput):
|
|
803
1094
|
final_response = event
|
|
804
1095
|
break
|
|
805
1096
|
else:
|
|
806
|
-
|
|
1097
|
+
if isinstance(content, str):
|
|
1098
|
+
content += str(event)
|
|
1099
|
+
else:
|
|
1100
|
+
content = str(event)
|
|
807
1101
|
if not final_response:
|
|
808
1102
|
final_response = StepOutput(content=content)
|
|
809
1103
|
else:
|
|
810
1104
|
# It's a regular function - call it directly
|
|
811
|
-
result = self._call_custom_function(
|
|
1105
|
+
result = self._call_custom_function(
|
|
1106
|
+
self.active_executor, # type: ignore[arg-type]
|
|
1107
|
+
step_input,
|
|
1108
|
+
session_state_copy,
|
|
1109
|
+
run_context,
|
|
1110
|
+
)
|
|
812
1111
|
if isinstance(result, StepOutput):
|
|
813
1112
|
final_response = result
|
|
1113
|
+
elif isinstance(result, (RunOutput, TeamRunOutput)):
|
|
1114
|
+
final_response = StepOutput(content=result.content)
|
|
814
1115
|
else:
|
|
815
1116
|
final_response = StepOutput(content=str(result))
|
|
816
1117
|
|
|
817
1118
|
# Merge session_state changes back
|
|
818
|
-
if session_state is not None:
|
|
1119
|
+
if run_context is None and session_state is not None:
|
|
819
1120
|
merge_dictionaries(session_state, session_state_copy)
|
|
820
1121
|
else:
|
|
821
1122
|
# For agents and teams, prepare message with context
|
|
@@ -843,8 +1144,26 @@ class Step:
|
|
|
843
1144
|
if isinstance(self.active_executor, Team):
|
|
844
1145
|
kwargs["store_member_responses"] = True
|
|
845
1146
|
|
|
1147
|
+
# Forward background_tasks if provided
|
|
1148
|
+
if background_tasks is not None:
|
|
1149
|
+
kwargs["background_tasks"] = background_tasks
|
|
1150
|
+
|
|
1151
|
+
num_history_runs = self.num_history_runs if self.num_history_runs else num_history_runs
|
|
1152
|
+
|
|
1153
|
+
use_history = (
|
|
1154
|
+
self.add_workflow_history
|
|
1155
|
+
if self.add_workflow_history is not None
|
|
1156
|
+
else add_workflow_history_to_steps
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
final_message = message
|
|
1160
|
+
if use_history and workflow_session:
|
|
1161
|
+
history_messages = workflow_session.get_workflow_history_context(num_runs=num_history_runs)
|
|
1162
|
+
if history_messages:
|
|
1163
|
+
final_message = f"{history_messages}{message}"
|
|
1164
|
+
|
|
846
1165
|
response_stream = self.active_executor.arun( # type: ignore
|
|
847
|
-
input=
|
|
1166
|
+
input=final_message,
|
|
848
1167
|
images=images,
|
|
849
1168
|
videos=videos,
|
|
850
1169
|
audio=audios,
|
|
@@ -853,16 +1172,9 @@ class Step:
|
|
|
853
1172
|
user_id=user_id,
|
|
854
1173
|
session_state=session_state_copy,
|
|
855
1174
|
stream=True,
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
"workflow_id": workflow_run_response.workflow_id if workflow_run_response else None,
|
|
860
|
-
"workflow_run_id": workflow_run_response.run_id if workflow_run_response else None,
|
|
861
|
-
"step_id": self.step_id,
|
|
862
|
-
"step_name": self.name,
|
|
863
|
-
"step_index": step_index,
|
|
864
|
-
},
|
|
865
|
-
yield_run_response=True,
|
|
1175
|
+
stream_events=stream_events,
|
|
1176
|
+
run_context=run_context,
|
|
1177
|
+
yield_run_output=True,
|
|
866
1178
|
**kwargs,
|
|
867
1179
|
)
|
|
868
1180
|
|
|
@@ -871,11 +1183,16 @@ class Step:
|
|
|
871
1183
|
if isinstance(event, RunOutput) or isinstance(event, TeamRunOutput):
|
|
872
1184
|
active_executor_run_response = event
|
|
873
1185
|
break
|
|
874
|
-
yield
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1186
|
+
# Only yield executor events if stream_executor_events is True
|
|
1187
|
+
if stream_executor_events:
|
|
1188
|
+
enriched_event = self._enrich_event_with_context(
|
|
1189
|
+
event, workflow_run_response, step_index
|
|
1190
|
+
)
|
|
1191
|
+
yield enriched_event # type: ignore[misc]
|
|
1192
|
+
|
|
1193
|
+
# Update workflow session state
|
|
1194
|
+
if run_context is None and session_state is not None:
|
|
1195
|
+
merge_dictionaries(session_state, session_state_copy)
|
|
879
1196
|
|
|
880
1197
|
if store_executor_outputs and workflow_run_response is not None:
|
|
881
1198
|
self._store_executor_response(workflow_run_response, active_executor_run_response) # type: ignore
|
|
@@ -895,7 +1212,7 @@ class Step:
|
|
|
895
1212
|
final_response = self._process_step_output(final_response)
|
|
896
1213
|
yield final_response
|
|
897
1214
|
|
|
898
|
-
if
|
|
1215
|
+
if stream_events and workflow_run_response:
|
|
899
1216
|
# Emit StepCompletedEvent
|
|
900
1217
|
yield StepCompletedEvent(
|
|
901
1218
|
run_id=workflow_run_response.run_id or "",
|
|
@@ -928,6 +1245,83 @@ class Step:
|
|
|
928
1245
|
|
|
929
1246
|
return
|
|
930
1247
|
|
|
1248
|
+
def get_chat_history(self, session_id: str, last_n_runs: Optional[int] = None) -> List[Message]:
|
|
1249
|
+
"""Return the step's Agent or Team chat history for the given session.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
|
|
1253
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
1254
|
+
|
|
1255
|
+
Returns:
|
|
1256
|
+
List[Message]: The step's Agent or Team chat history for the given session.
|
|
1257
|
+
"""
|
|
1258
|
+
session: Union[AgentSession, TeamSession, WorkflowSession, None] = None
|
|
1259
|
+
|
|
1260
|
+
if self.agent:
|
|
1261
|
+
session = self.agent.get_session(session_id=session_id)
|
|
1262
|
+
if not session:
|
|
1263
|
+
log_warning("Session not found")
|
|
1264
|
+
return []
|
|
1265
|
+
|
|
1266
|
+
if not isinstance(session, WorkflowSession):
|
|
1267
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1268
|
+
|
|
1269
|
+
session = cast(WorkflowSession, session)
|
|
1270
|
+
return session.get_messages(last_n_runs=last_n_runs, agent_id=self.agent.id)
|
|
1271
|
+
|
|
1272
|
+
elif self.team:
|
|
1273
|
+
session = self.team.get_session(session_id=session_id)
|
|
1274
|
+
if not session:
|
|
1275
|
+
log_warning("Session not found")
|
|
1276
|
+
return []
|
|
1277
|
+
|
|
1278
|
+
if not isinstance(session, WorkflowSession):
|
|
1279
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1280
|
+
|
|
1281
|
+
session = cast(WorkflowSession, session)
|
|
1282
|
+
return session.get_messages(last_n_runs=last_n_runs, team_id=self.team.id)
|
|
1283
|
+
|
|
1284
|
+
return []
|
|
1285
|
+
|
|
1286
|
+
async def aget_chat_history(
|
|
1287
|
+
self, session_id: Optional[str] = None, last_n_runs: Optional[int] = None
|
|
1288
|
+
) -> List[Message]:
|
|
1289
|
+
"""Return the step's Agent or Team chat history for the given session.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
|
|
1293
|
+
last_n_runs: Number of recent runs to include. If None, all runs will be considered.
|
|
1294
|
+
|
|
1295
|
+
Returns:
|
|
1296
|
+
List[Message]: The step's Agent or Team chat history for the given session.
|
|
1297
|
+
"""
|
|
1298
|
+
session: Union[AgentSession, TeamSession, WorkflowSession, None] = None
|
|
1299
|
+
|
|
1300
|
+
if self.agent:
|
|
1301
|
+
session = await self.agent.aget_session(session_id=session_id)
|
|
1302
|
+
if not session:
|
|
1303
|
+
log_warning("Session not found")
|
|
1304
|
+
return []
|
|
1305
|
+
|
|
1306
|
+
if not isinstance(session, WorkflowSession):
|
|
1307
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1308
|
+
|
|
1309
|
+
session = cast(WorkflowSession, session)
|
|
1310
|
+
return session.get_messages(last_n_runs=last_n_runs, agent_id=self.agent.id)
|
|
1311
|
+
|
|
1312
|
+
elif self.team:
|
|
1313
|
+
session = await self.team.aget_session(session_id=session_id)
|
|
1314
|
+
if not session:
|
|
1315
|
+
log_warning("Session not found")
|
|
1316
|
+
return []
|
|
1317
|
+
|
|
1318
|
+
if not isinstance(session, WorkflowSession):
|
|
1319
|
+
raise ValueError("The provided session is not a WorkflowSession")
|
|
1320
|
+
|
|
1321
|
+
return session.get_messages(last_n_runs=last_n_runs, team_id=self.team.id)
|
|
1322
|
+
|
|
1323
|
+
return []
|
|
1324
|
+
|
|
931
1325
|
def _store_executor_response(
|
|
932
1326
|
self, workflow_run_response: "WorkflowRunOutput", executor_run_response: Union[RunOutput, TeamRunOutput]
|
|
933
1327
|
) -> None:
|
|
@@ -937,6 +1331,14 @@ class Step:
|
|
|
937
1331
|
executor_run_response.parent_run_id = workflow_run_response.run_id
|
|
938
1332
|
executor_run_response.workflow_step_id = self.step_id
|
|
939
1333
|
|
|
1334
|
+
# Scrub the executor response based on the executor's storage flags before storing
|
|
1335
|
+
if (
|
|
1336
|
+
not self.active_executor.store_media
|
|
1337
|
+
or not self.active_executor.store_tool_messages
|
|
1338
|
+
or not self.active_executor.store_history_messages
|
|
1339
|
+
): # type: ignore
|
|
1340
|
+
self.active_executor._scrub_run_output_for_storage(executor_run_response) # type: ignore
|
|
1341
|
+
|
|
940
1342
|
# Get the raw response from the step's active executor
|
|
941
1343
|
raw_response = executor_run_response
|
|
942
1344
|
if raw_response and isinstance(raw_response, (RunOutput, TeamRunOutput)):
|
|
@@ -961,10 +1363,22 @@ class Step:
|
|
|
961
1363
|
|
|
962
1364
|
For container steps (Steps, Router, Loop, etc.), this will recursively find the content from the
|
|
963
1365
|
last actual step rather than using the generic container message.
|
|
1366
|
+
|
|
1367
|
+
For Parallel steps, aggregates content from ALL inner steps (not just the last one).
|
|
964
1368
|
"""
|
|
965
|
-
# If this step has nested steps (like Steps, Condition, Router, Loop, etc.)
|
|
1369
|
+
# If this step has nested steps (like Steps, Condition, Router, Loop, Parallel, etc.)
|
|
966
1370
|
if hasattr(step_output, "steps") and step_output.steps and len(step_output.steps) > 0:
|
|
967
|
-
#
|
|
1371
|
+
# For Parallel steps, aggregate content from ALL inner steps
|
|
1372
|
+
if step_output.step_type == StepType.PARALLEL:
|
|
1373
|
+
aggregated_parts = []
|
|
1374
|
+
for i, inner_step in enumerate(step_output.steps):
|
|
1375
|
+
inner_content = self._get_deepest_content_from_step_output(inner_step)
|
|
1376
|
+
if inner_content:
|
|
1377
|
+
step_name = inner_step.step_name or f"Step {i + 1}"
|
|
1378
|
+
aggregated_parts.append(f"=== {step_name} ===\n{inner_content}")
|
|
1379
|
+
return "\n\n".join(aggregated_parts) if aggregated_parts else step_output.content # type: ignore
|
|
1380
|
+
|
|
1381
|
+
# For other nested step types, recursively get content from the last nested step
|
|
968
1382
|
return self._get_deepest_content_from_step_output(step_output.steps[-1])
|
|
969
1383
|
|
|
970
1384
|
# For regular steps, return their content
|
|
@@ -1123,3 +1537,28 @@ class Step:
|
|
|
1123
1537
|
continue
|
|
1124
1538
|
|
|
1125
1539
|
return videos
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def _is_async_callable(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1543
|
+
"""Checks if obj is an async callable (coroutine function or callable with async __call__)"""
|
|
1544
|
+
return inspect.iscoroutinefunction(obj) or (callable(obj) and inspect.iscoroutinefunction(obj.__call__))
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
def _is_generator_function(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1548
|
+
"""Checks if obj is a generator function, including callable class instances with generator __call__ methods"""
|
|
1549
|
+
if inspect.isgeneratorfunction(obj):
|
|
1550
|
+
return True
|
|
1551
|
+
# Check if it's a callable class instance with a generator __call__ method
|
|
1552
|
+
if callable(obj) and hasattr(obj, "__call__"):
|
|
1553
|
+
return inspect.isgeneratorfunction(obj.__call__)
|
|
1554
|
+
return False
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
def _is_async_generator_function(obj: Any) -> TypeGuard[Callable[..., Any]]:
|
|
1558
|
+
"""Checks if obj is an async generator function, including callable class instances"""
|
|
1559
|
+
if inspect.isasyncgenfunction(obj):
|
|
1560
|
+
return True
|
|
1561
|
+
# Check if it's a callable class instance with an async generator __call__ method
|
|
1562
|
+
if callable(obj) and hasattr(obj, "__call__"):
|
|
1563
|
+
return inspect.isasyncgenfunction(obj.__call__)
|
|
1564
|
+
return False
|