mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-24 23:58:15 +00:00
Consistently storing async and sync output for context
This commit is contained in:
@@ -6,15 +6,15 @@ from typing import Any, Dict, List, Optional, Tuple, Union
|
|||||||
|
|
||||||
from langchain_core.callbacks import BaseCallbackHandler
|
from langchain_core.callbacks import BaseCallbackHandler
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
UUID4,
|
UUID4,
|
||||||
BaseModel,
|
BaseModel,
|
||||||
ConfigDict,
|
ConfigDict,
|
||||||
Field,
|
Field,
|
||||||
InstanceOf,
|
InstanceOf,
|
||||||
Json,
|
Json,
|
||||||
PrivateAttr,
|
PrivateAttr,
|
||||||
field_validator,
|
field_validator,
|
||||||
model_validator,
|
model_validator,
|
||||||
)
|
)
|
||||||
from pydantic_core import PydanticCustomError
|
from pydantic_core import PydanticCustomError
|
||||||
|
|
||||||
@@ -338,8 +338,7 @@ class Crew(BaseModel):
|
|||||||
"""Executes tasks sequentially and returns the final output."""
|
"""Executes tasks sequentially and returns the final output."""
|
||||||
# TODO: Check to see if we need to be clearing task output after each task
|
# TODO: Check to see if we need to be clearing task output after each task
|
||||||
task_output = ""
|
task_output = ""
|
||||||
futures: List[Tuple[int, Task, Future]] = []
|
futures: List[Tuple[Task, Future]] = []
|
||||||
task_results = {}
|
|
||||||
|
|
||||||
def _process_task_result(task, output):
|
def _process_task_result(task, output):
|
||||||
role = task.agent.role if task.agent is not None else "None"
|
role = task.agent.role if task.agent is not None else "None"
|
||||||
@@ -347,7 +346,7 @@ class Crew(BaseModel):
|
|||||||
if self.output_log_file:
|
if self.output_log_file:
|
||||||
self._file_handler.log(agent=role, task=output, status="completed")
|
self._file_handler.log(agent=role, task=output, status="completed")
|
||||||
|
|
||||||
for index, task in enumerate(self.tasks):
|
for task in self.tasks:
|
||||||
if task.agent.allow_delegation: # type: ignore # Item "None" of "Agent | None" has no attribute "allow_delegation"
|
if task.agent.allow_delegation: # type: ignore # Item "None" of "Agent | None" has no attribute "allow_delegation"
|
||||||
agents_for_delegation = [
|
agents_for_delegation = [
|
||||||
agent for agent in self.agents if agent != task.agent
|
agent for agent in self.agents if agent != task.agent
|
||||||
@@ -370,13 +369,14 @@ class Crew(BaseModel):
|
|||||||
future = task.execute_async(
|
future = task.execute_async(
|
||||||
agent=task.agent, context=task_output, tools=task.tools
|
agent=task.agent, context=task_output, tools=task.tools
|
||||||
)
|
)
|
||||||
futures.append((index, task, future))
|
futures.append(( task, future))
|
||||||
else:
|
else:
|
||||||
# Before executing a synchronous task, wait for all async tasks to complete
|
# Before executing a synchronous task, wait for all async tasks to complete
|
||||||
if futures:
|
if futures:
|
||||||
for future_index, future_task, future in futures:
|
task_output = ""
|
||||||
|
for future_task, future in futures:
|
||||||
output = future.result()
|
output = future.result()
|
||||||
task_results[future_index] = output
|
task_output += output + "\n\n\n"
|
||||||
_process_task_result(future_task, output)
|
_process_task_result(future_task, output)
|
||||||
|
|
||||||
# Clear the futures list after processing all async results
|
# Clear the futures list after processing all async results
|
||||||
@@ -385,16 +385,18 @@ class Crew(BaseModel):
|
|||||||
output = task.execute_sync(
|
output = task.execute_sync(
|
||||||
agent=task.agent, context=task_output, tools=task.tools
|
agent=task.agent, context=task_output, tools=task.tools
|
||||||
)
|
)
|
||||||
task_results[index] = output
|
|
||||||
task_output = output
|
task_output = output
|
||||||
_process_task_result(task, output)
|
_process_task_result(task, output)
|
||||||
|
|
||||||
# TODO: Check with Joao to see if we want to add or ignore outputs from async tasks
|
# TODO: Check with Joao to see if we want to add or ignore outputs from async tasks
|
||||||
# Process any remaining async results
|
# Process any remaining async results
|
||||||
for future_index, future_task, future in futures:
|
|
||||||
output = future.result()
|
if futures:
|
||||||
task_results[future_index] = output
|
task_output = ""
|
||||||
_process_task_result(future_task, output)
|
for future_task, future in futures:
|
||||||
|
output = future.result()
|
||||||
|
task_output += output + "\n\n\n"
|
||||||
|
_process_task_result(future_task, output)
|
||||||
|
|
||||||
self._finish_execution(task_output)
|
self._finish_execution(task_output)
|
||||||
# type: ignore # Item "None" of "Agent | None" has no attribute "_token_process"
|
# type: ignore # Item "None" of "Agent | None" has no attribute "_token_process"
|
||||||
@@ -423,10 +425,15 @@ class Crew(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
task_output = ""
|
task_output = ""
|
||||||
futures: List[Tuple[int, Task, Future]] = []
|
futures: List[Tuple[Task, Future]] = []
|
||||||
task_results = {}
|
|
||||||
|
|
||||||
for index, task in enumerate(self.tasks):
|
def _process_task_result(task, output):
|
||||||
|
role = task.agent.role if task.agent is not None else "None"
|
||||||
|
self._logger.log("debug", f"== [{role}] Task output: {output}\n\n")
|
||||||
|
if self.output_log_file:
|
||||||
|
self._file_handler.log(agent=role, task=output, status="completed")
|
||||||
|
|
||||||
|
for task in self.tasks:
|
||||||
self._logger.log("debug", f"Working Agent: {manager.role}")
|
self._logger.log("debug", f"Working Agent: {manager.role}")
|
||||||
self._logger.log("info", f"Starting Task: {task.description}")
|
self._logger.log("info", f"Starting Task: {task.description}")
|
||||||
|
|
||||||
@@ -439,38 +446,34 @@ class Crew(BaseModel):
|
|||||||
future = task.execute_async(
|
future = task.execute_async(
|
||||||
agent=manager, context=task_output, tools=manager.tools
|
agent=manager, context=task_output, tools=manager.tools
|
||||||
)
|
)
|
||||||
futures.append((index, task, future))
|
futures.append((task, future))
|
||||||
else:
|
else:
|
||||||
|
# Before executing a synchronous task, wait for all async tasks to complete
|
||||||
|
if futures:
|
||||||
|
task_output = "" # Clear task_output before processing async tasks
|
||||||
|
for future_task, future in futures:
|
||||||
|
output = future.result()
|
||||||
|
task_output += output + "\n\n\n"
|
||||||
|
_process_task_result(future_task, output)
|
||||||
|
|
||||||
|
# Clear the futures list after processing all async results
|
||||||
|
futures.clear()
|
||||||
|
|
||||||
output = task.execute_sync(
|
output = task.execute_sync(
|
||||||
agent=manager, context=task_output, tools=manager.tools
|
agent=manager, context=task_output, tools=manager.tools
|
||||||
)
|
)
|
||||||
task_results[index] = output
|
task_output = output # Set task_output to the new result for sync tasks
|
||||||
|
_process_task_result(task, output)
|
||||||
|
|
||||||
self._logger.log("debug", f"[{manager.role}] Task output: {output}")
|
# Process any remaining async results
|
||||||
|
if futures:
|
||||||
if self.output_log_file:
|
task_output = "" # Clear task_output before processing async tasks
|
||||||
self._file_handler.log(
|
for future_task, future in futures:
|
||||||
agent=manager.role, task=output, status="completed"
|
output = future.result()
|
||||||
)
|
task_output += output + "\n\n\n"
|
||||||
|
_process_task_result(future_task, output)
|
||||||
# Process async results in order
|
|
||||||
for index, task, future in sorted(futures):
|
|
||||||
output = future.result()
|
|
||||||
task_results[index] = output
|
|
||||||
|
|
||||||
role = manager.role
|
|
||||||
self._logger.log("debug", f"== [{role}] Task output: {output}\n\n")
|
|
||||||
|
|
||||||
if self.output_log_file:
|
|
||||||
self._file_handler.log(agent=role, task=output, status="completed")
|
|
||||||
|
|
||||||
# Get the final task_output from the last task result
|
|
||||||
final_index = len(self.tasks) - 1
|
|
||||||
if final_index in task_results:
|
|
||||||
task_output = task_results[final_index]
|
|
||||||
|
|
||||||
self._finish_execution(task_output)
|
self._finish_execution(task_output)
|
||||||
|
|
||||||
manager_token_usage = manager._token_process.get_summary()
|
manager_token_usage = manager._token_process.get_summary()
|
||||||
return (
|
return (
|
||||||
self._format_output(task_output, manager_token_usage),
|
self._format_output(task_output, manager_token_usage),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
|
|||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Potentially add in JSON_OUTPUT, PYDANTIC_OUTPUT, etc.
|
||||||
class CrewOutput(BaseModel):
|
class CrewOutput(BaseModel):
|
||||||
final_output: str = Field(description="Final output of the crew")
|
final_output: str = Field(description="Final output of the crew")
|
||||||
tasks_output: list[TaskOutput] = Field(
|
tasks_output: list[TaskOutput] = Field(
|
||||||
|
|||||||
1804
tests/cassettes/test_async_execution_single_task.yaml
Normal file
1804
tests/cassettes/test_async_execution_single_task.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -521,6 +521,36 @@ def test_async_task_execution_completion():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Improve this test once I get feedback from Joao.
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_async_execution_single_task():
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
researcher_agent = Agent(
|
||||||
|
role="Researcher",
|
||||||
|
goal="Make the best research and analysis on content about AI and AI agents",
|
||||||
|
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
|
||||||
|
allow_delegation=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
list_ideas = Task(
|
||||||
|
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
|
||||||
|
expected_output="Bullet point list of 5 important events.",
|
||||||
|
agent=researcher_agent,
|
||||||
|
async_execution=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
crew = Crew(
|
||||||
|
agents=[researcher_agent],
|
||||||
|
process=Process.sequential,
|
||||||
|
tasks=[list_ideas],
|
||||||
|
)
|
||||||
|
|
||||||
|
result = crew.kickoff()
|
||||||
|
print(result)
|
||||||
|
assert result.final_output == ""
|
||||||
|
|
||||||
|
|
||||||
# TODO: Make sure sync and async task execution keeps right order of expected outputs
|
# TODO: Make sure sync and async task execution keeps right order of expected outputs
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user