Skip to content

Commit b8cbd87

Browse files
committed
Identify parent_flow of a crew
This commit adds a new crew field called `parent_flow`, evaluated when the `Crew` instance is instantiated. The stacktrace is traversed to look up if the caller is an instance of `Flow`, and if so, it fills in the field. Other alternatives were considered, such as a global context or even a new field to be manually filled, however, this is the most **magical** solution that was thread-safe and did not require public API changes.
1 parent 25c8155 commit b8cbd87

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

src/crewai/crew.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import inspect
23
import json
34
import re
45
import uuid
@@ -24,6 +25,7 @@
2425
from crewai.agents.agent_builder.base_agent import BaseAgent
2526
from crewai.agents.cache import CacheHandler
2627
from crewai.crews.crew_output import CrewOutput
28+
from crewai.flow import Flow
2729
from crewai.knowledge.knowledge import Knowledge
2830
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
2931
from crewai.llm import LLM, BaseLLM
@@ -233,6 +235,10 @@ class Crew(BaseModel):
233235
default_factory=SecurityConfig,
234236
description="Security configuration for the crew, including fingerprinting.",
235237
)
238+
parent_flow: Optional[InstanceOf[Flow]] = Field(
239+
default=None,
240+
description="The parent flow of the crew, if the crew was created inside a flow.",
241+
)
236242

237243
@field_validator("id", mode="before")
238244
@classmethod
@@ -275,6 +281,29 @@ def set_private_attrs(self) -> "Crew":
275281

276282
return self
277283

284+
@model_validator(mode="after")
285+
def set_parent_flow(self, max_depth: int = 5) -> Optional[Flow]:
286+
"""Find the nearest Flow instance in the call stack.
287+
288+
Args:
289+
max_depth: Maximum frames to traverse up the call stack.
290+
291+
Returns:
292+
The first Flow instance found in the call stack, or None.
293+
"""
294+
stack = inspect.stack(context=0)[1 : max_depth + 1]
295+
try:
296+
for frame_info in stack:
297+
candidate = frame_info.frame.f_locals.get("self")
298+
if isinstance(candidate, Flow):
299+
self.parent_flow = candidate
300+
break
301+
else:
302+
self.parent_flow = None
303+
finally:
304+
del stack
305+
return self
306+
278307
def _initialize_user_memory(self):
279308
if (
280309
self.memory_config

tests/crew_test.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from crewai.agents.crew_agent_executor import CrewAgentExecutor
1818
from crewai.crew import Crew
1919
from crewai.crews.crew_output import CrewOutput
20+
from crewai.flow import Flow, listen, start
2021
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
2122
from crewai.llm import LLM
2223
from crewai.memory.contextual.contextual_memory import ContextualMemory
@@ -2153,7 +2154,6 @@ def cache_func(args, result):
21532154
with patch.object(
21542155
CacheHandler, "add", wraps=crew._cache_handler.add
21552156
) as add_to_cache:
2156-
21572157
result = crew.kickoff()
21582158

21592159
# Check that add_to_cache was called exactly twice
@@ -4339,3 +4339,35 @@ def test_crew_copy_with_memory():
43394339
raise e # Re-raise other validation errors
43404340
except Exception as e:
43414341
pytest.fail(f"Copying crew raised an unexpected exception: {e}")
4342+
4343+
4344+
def test_sets_parent_flow_when_outside_flow():
4345+
crew = Crew(
4346+
agents=[researcher, writer],
4347+
process=Process.sequential,
4348+
tasks=[
4349+
Task(description="Task 1", expected_output="output", agent=researcher),
4350+
Task(description="Task 2", expected_output="output", agent=writer),
4351+
],
4352+
)
4353+
assert crew.parent_flow is None
4354+
4355+
4356+
def test_sets_parent_flow_when_inside_flow():
4357+
class MyFlow(Flow):
4358+
@start()
4359+
def start(self):
4360+
return Crew(
4361+
agents=[researcher, writer],
4362+
process=Process.sequential,
4363+
tasks=[
4364+
Task(
4365+
description="Task 1", expected_output="output", agent=researcher
4366+
),
4367+
Task(description="Task 2", expected_output="output", agent=writer),
4368+
],
4369+
)
4370+
4371+
flow = MyFlow()
4372+
result = flow.kickoff()
4373+
assert result.parent_flow is flow

0 commit comments

Comments
 (0)