mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-27 09:08:14 +00:00
feat: add input_files support to Task and Crew
- Add input_files parameter to Task for file attachments - Add file_handling mode to Crew for processing behavior - Integrate file injection in CrewAgentExecutor - Update prepare_kickoff to handle KickoffInputs type
This commit is contained in:
@@ -43,6 +43,8 @@ from crewai.utilities.agent_utils import (
|
|||||||
process_llm_response,
|
process_llm_response,
|
||||||
)
|
)
|
||||||
from crewai.utilities.constants import TRAINING_DATA_FILE
|
from crewai.utilities.constants import TRAINING_DATA_FILE
|
||||||
|
from crewai.utilities.file_store import get_all_files
|
||||||
|
from crewai.utilities.files import FileProcessor
|
||||||
from crewai.utilities.i18n import I18N, get_i18n
|
from crewai.utilities.i18n import I18N, get_i18n
|
||||||
from crewai.utilities.printer import Printer
|
from crewai.utilities.printer import Printer
|
||||||
from crewai.utilities.tool_utils import (
|
from crewai.utilities.tool_utils import (
|
||||||
@@ -188,6 +190,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
|
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
|
||||||
self.messages.append(format_message_for_llm(user_prompt))
|
self.messages.append(format_message_for_llm(user_prompt))
|
||||||
|
|
||||||
|
self._inject_multimodal_files()
|
||||||
|
|
||||||
self._show_start_logs()
|
self._show_start_logs()
|
||||||
|
|
||||||
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
||||||
@@ -212,6 +216,48 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self._create_external_memory(formatted_answer)
|
self._create_external_memory(formatted_answer)
|
||||||
return {"output": formatted_answer.output}
|
return {"output": formatted_answer.output}
|
||||||
|
|
||||||
|
def _inject_multimodal_files(self) -> None:
|
||||||
|
"""Inject files as multimodal content into messages.
|
||||||
|
|
||||||
|
For crews with input files and LLMs that support multimodal,
|
||||||
|
processes files according to provider constraints and file handling mode,
|
||||||
|
then delegates to the LLM's format_multimodal_content method to
|
||||||
|
generate provider-specific content blocks.
|
||||||
|
"""
|
||||||
|
if not self.crew or not self.task:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.llm.supports_multimodal():
|
||||||
|
return
|
||||||
|
|
||||||
|
files = get_all_files(self.crew.id, self.task.id)
|
||||||
|
if not files:
|
||||||
|
return
|
||||||
|
|
||||||
|
provider = getattr(self.llm, "provider", None) or getattr(self.llm, "model", "")
|
||||||
|
processor = FileProcessor(constraints=provider)
|
||||||
|
files = processor.process_files(files)
|
||||||
|
|
||||||
|
from crewai.utilities.files import get_upload_cache
|
||||||
|
|
||||||
|
upload_cache = get_upload_cache()
|
||||||
|
content_blocks = self.llm.format_multimodal_content(
|
||||||
|
files, upload_cache=upload_cache
|
||||||
|
)
|
||||||
|
if not content_blocks:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(len(self.messages) - 1, -1, -1):
|
||||||
|
msg = self.messages[i]
|
||||||
|
if msg.get("role") == "user":
|
||||||
|
existing_content = msg.get("content", "")
|
||||||
|
if isinstance(existing_content, str):
|
||||||
|
msg["content"] = [
|
||||||
|
self.llm.format_text_content(existing_content),
|
||||||
|
*content_blocks,
|
||||||
|
]
|
||||||
|
break
|
||||||
|
|
||||||
def _invoke_loop(self) -> AgentFinish:
|
def _invoke_loop(self) -> AgentFinish:
|
||||||
"""Execute agent loop until completion.
|
"""Execute agent loop until completion.
|
||||||
|
|
||||||
@@ -355,6 +401,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
|
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
|
||||||
self.messages.append(format_message_for_llm(user_prompt))
|
self.messages.append(format_message_for_llm(user_prompt))
|
||||||
|
|
||||||
|
self._inject_multimodal_files()
|
||||||
|
|
||||||
self._show_start_logs()
|
self._show_start_logs()
|
||||||
|
|
||||||
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ from crewai.task import Task
|
|||||||
from crewai.tasks.conditional_task import ConditionalTask
|
from crewai.tasks.conditional_task import ConditionalTask
|
||||||
from crewai.tasks.task_output import TaskOutput
|
from crewai.tasks.task_output import TaskOutput
|
||||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||||
|
from crewai.tools.agent_tools.read_file_tool import ReadFileTool
|
||||||
from crewai.tools.base_tool import BaseTool
|
from crewai.tools.base_tool import BaseTool
|
||||||
from crewai.types.streaming import CrewStreamingOutput
|
from crewai.types.streaming import CrewStreamingOutput
|
||||||
from crewai.types.usage_metrics import UsageMetrics
|
from crewai.types.usage_metrics import UsageMetrics
|
||||||
@@ -88,6 +89,7 @@ from crewai.utilities.crew.models import CrewContext
|
|||||||
from crewai.utilities.evaluators.crew_evaluator_handler import CrewEvaluator
|
from crewai.utilities.evaluators.crew_evaluator_handler import CrewEvaluator
|
||||||
from crewai.utilities.evaluators.task_evaluator import TaskEvaluator
|
from crewai.utilities.evaluators.task_evaluator import TaskEvaluator
|
||||||
from crewai.utilities.file_handler import FileHandler
|
from crewai.utilities.file_handler import FileHandler
|
||||||
|
from crewai.utilities.file_store import clear_files, get_all_files
|
||||||
from crewai.utilities.formatter import (
|
from crewai.utilities.formatter import (
|
||||||
aggregate_raw_outputs_from_task_outputs,
|
aggregate_raw_outputs_from_task_outputs,
|
||||||
aggregate_raw_outputs_from_tasks,
|
aggregate_raw_outputs_from_tasks,
|
||||||
@@ -106,6 +108,7 @@ from crewai.utilities.streaming import (
|
|||||||
)
|
)
|
||||||
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
|
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
|
||||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||||
|
from crewai.utilities.types import KickoffInputs
|
||||||
|
|
||||||
|
|
||||||
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
||||||
@@ -675,7 +678,7 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
|
|
||||||
def kickoff(
|
def kickoff(
|
||||||
self,
|
self,
|
||||||
inputs: dict[str, Any] | None = None,
|
inputs: KickoffInputs | dict[str, Any] | None = None,
|
||||||
) -> CrewOutput | CrewStreamingOutput:
|
) -> CrewOutput | CrewStreamingOutput:
|
||||||
if self.stream:
|
if self.stream:
|
||||||
enable_agent_streaming(self.agents)
|
enable_agent_streaming(self.agents)
|
||||||
@@ -732,6 +735,7 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
clear_files(self.id)
|
||||||
detach(token)
|
detach(token)
|
||||||
|
|
||||||
def kickoff_for_each(
|
def kickoff_for_each(
|
||||||
@@ -762,7 +766,7 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
async def kickoff_async(
|
async def kickoff_async(
|
||||||
self, inputs: dict[str, Any] | None = None
|
self, inputs: KickoffInputs | dict[str, Any] | None = None
|
||||||
) -> CrewOutput | CrewStreamingOutput:
|
) -> CrewOutput | CrewStreamingOutput:
|
||||||
"""Asynchronous kickoff method to start the crew execution.
|
"""Asynchronous kickoff method to start the crew execution.
|
||||||
|
|
||||||
@@ -817,7 +821,7 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
return await run_for_each_async(self, inputs, kickoff_fn)
|
return await run_for_each_async(self, inputs, kickoff_fn)
|
||||||
|
|
||||||
async def akickoff(
|
async def akickoff(
|
||||||
self, inputs: dict[str, Any] | None = None
|
self, inputs: KickoffInputs | dict[str, Any] | None = None
|
||||||
) -> CrewOutput | CrewStreamingOutput:
|
) -> CrewOutput | CrewStreamingOutput:
|
||||||
"""Native async kickoff method using async task execution throughout.
|
"""Native async kickoff method using async task execution throughout.
|
||||||
|
|
||||||
@@ -880,6 +884,7 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
clear_files(self.id)
|
||||||
detach(token)
|
detach(token)
|
||||||
|
|
||||||
async def akickoff_for_each(
|
async def akickoff_for_each(
|
||||||
@@ -1215,7 +1220,8 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
and hasattr(agent, "multimodal")
|
and hasattr(agent, "multimodal")
|
||||||
and getattr(agent, "multimodal", False)
|
and getattr(agent, "multimodal", False)
|
||||||
):
|
):
|
||||||
tools = self._add_multimodal_tools(agent, tools)
|
if not (agent.llm and agent.llm.supports_multimodal()):
|
||||||
|
tools = self._add_multimodal_tools(agent, tools)
|
||||||
|
|
||||||
if agent and (hasattr(agent, "apps") and getattr(agent, "apps", None)):
|
if agent and (hasattr(agent, "apps") and getattr(agent, "apps", None)):
|
||||||
tools = self._add_platform_tools(task, tools)
|
tools = self._add_platform_tools(task, tools)
|
||||||
@@ -1223,7 +1229,24 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
if agent and (hasattr(agent, "mcps") and getattr(agent, "mcps", None)):
|
if agent and (hasattr(agent, "mcps") and getattr(agent, "mcps", None)):
|
||||||
tools = self._add_mcp_tools(task, tools)
|
tools = self._add_mcp_tools(task, tools)
|
||||||
|
|
||||||
# Return a list[BaseTool] compatible with Task.execute_sync and execute_async
|
files = get_all_files(self.id, task.id)
|
||||||
|
if files:
|
||||||
|
supported_types: list[str] = []
|
||||||
|
if agent and agent.llm and agent.llm.supports_multimodal():
|
||||||
|
supported_types = agent.llm.supported_multimodal_content_types()
|
||||||
|
|
||||||
|
def is_auto_injected(content_type: str) -> bool:
|
||||||
|
return any(content_type.startswith(t) for t in supported_types)
|
||||||
|
|
||||||
|
# Only add read_file tool if there are files that need it
|
||||||
|
files_needing_tool = {
|
||||||
|
name: f
|
||||||
|
for name, f in files.items()
|
||||||
|
if not is_auto_injected(f.content_type)
|
||||||
|
}
|
||||||
|
if files_needing_tool:
|
||||||
|
tools = self._add_file_tools(tools, files_needing_tool)
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
def _get_agent_to_use(self, task: Task) -> BaseAgent | None:
|
def _get_agent_to_use(self, task: Task) -> BaseAgent | None:
|
||||||
@@ -1303,6 +1326,22 @@ class Crew(FlowTrackable, BaseModel):
|
|||||||
return self._merge_tools(tools, cast(list[BaseTool], code_tools))
|
return self._merge_tools(tools, cast(list[BaseTool], code_tools))
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
|
def _add_file_tools(
|
||||||
|
self, tools: list[BaseTool], files: dict[str, Any]
|
||||||
|
) -> list[BaseTool]:
|
||||||
|
"""Add file reading tool when input files are available.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tools: Current list of tools.
|
||||||
|
files: Dictionary of input files.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated list with file tool added.
|
||||||
|
"""
|
||||||
|
read_file_tool = ReadFileTool()
|
||||||
|
read_file_tool.set_files(files)
|
||||||
|
return self._merge_tools(tools, [read_file_tool])
|
||||||
|
|
||||||
def _add_delegation_tools(
|
def _add_delegation_tools(
|
||||||
self, task: Task, tools: list[BaseTool]
|
self, task: Task, tools: list[BaseTool]
|
||||||
) -> list[BaseTool]:
|
) -> list[BaseTool]:
|
||||||
|
|||||||
@@ -10,11 +10,20 @@ from crewai.agents.agent_builder.base_agent import BaseAgent
|
|||||||
from crewai.crews.crew_output import CrewOutput
|
from crewai.crews.crew_output import CrewOutput
|
||||||
from crewai.rag.embeddings.types import EmbedderConfig
|
from crewai.rag.embeddings.types import EmbedderConfig
|
||||||
from crewai.types.streaming import CrewStreamingOutput, FlowStreamingOutput
|
from crewai.types.streaming import CrewStreamingOutput, FlowStreamingOutput
|
||||||
|
from crewai.utilities.file_store import store_files
|
||||||
|
from crewai.utilities.files import (
|
||||||
|
AudioFile,
|
||||||
|
ImageFile,
|
||||||
|
PDFFile,
|
||||||
|
TextFile,
|
||||||
|
VideoFile,
|
||||||
|
)
|
||||||
from crewai.utilities.streaming import (
|
from crewai.utilities.streaming import (
|
||||||
StreamingState,
|
StreamingState,
|
||||||
TaskInfo,
|
TaskInfo,
|
||||||
create_streaming_state,
|
create_streaming_state,
|
||||||
)
|
)
|
||||||
|
from crewai.utilities.types import KickoffInputs
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -176,7 +185,36 @@ def check_conditional_skip(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def prepare_kickoff(crew: Crew, inputs: dict[str, Any] | None) -> dict[str, Any] | None:
|
def _extract_files_from_inputs(inputs: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Extract file objects from inputs dict.
|
||||||
|
|
||||||
|
Scans inputs for FileInput objects (ImageFile, TextFile, etc.) and
|
||||||
|
extracts them into a separate dict.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inputs: The inputs dictionary to scan.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of extracted file objects.
|
||||||
|
"""
|
||||||
|
file_types = (AudioFile, ImageFile, PDFFile, TextFile, VideoFile)
|
||||||
|
files: dict[str, Any] = {}
|
||||||
|
keys_to_remove: list[str] = []
|
||||||
|
|
||||||
|
for key, value in inputs.items():
|
||||||
|
if isinstance(value, file_types):
|
||||||
|
files[key] = value
|
||||||
|
keys_to_remove.append(key)
|
||||||
|
|
||||||
|
for key in keys_to_remove:
|
||||||
|
del inputs[key]
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_kickoff(
|
||||||
|
crew: Crew, inputs: KickoffInputs | dict[str, Any] | None
|
||||||
|
) -> dict[str, Any] | None:
|
||||||
"""Prepare crew for kickoff execution.
|
"""Prepare crew for kickoff execution.
|
||||||
|
|
||||||
Handles before callbacks, event emission, task handler reset, input
|
Handles before callbacks, event emission, task handler reset, input
|
||||||
@@ -192,14 +230,17 @@ def prepare_kickoff(crew: Crew, inputs: dict[str, Any] | None) -> dict[str, Any]
|
|||||||
from crewai.events.event_bus import crewai_event_bus
|
from crewai.events.event_bus import crewai_event_bus
|
||||||
from crewai.events.types.crew_events import CrewKickoffStartedEvent
|
from crewai.events.types.crew_events import CrewKickoffStartedEvent
|
||||||
|
|
||||||
|
# Normalize inputs to dict[str, Any] for internal processing
|
||||||
|
normalized: dict[str, Any] | None = dict(inputs) if inputs is not None else None
|
||||||
|
|
||||||
for before_callback in crew.before_kickoff_callbacks:
|
for before_callback in crew.before_kickoff_callbacks:
|
||||||
if inputs is None:
|
if normalized is None:
|
||||||
inputs = {}
|
normalized = {}
|
||||||
inputs = before_callback(inputs)
|
normalized = before_callback(normalized)
|
||||||
|
|
||||||
future = crewai_event_bus.emit(
|
future = crewai_event_bus.emit(
|
||||||
crew,
|
crew,
|
||||||
CrewKickoffStartedEvent(crew_name=crew.name, inputs=inputs),
|
CrewKickoffStartedEvent(crew_name=crew.name, inputs=normalized),
|
||||||
)
|
)
|
||||||
if future is not None:
|
if future is not None:
|
||||||
try:
|
try:
|
||||||
@@ -210,9 +251,20 @@ def prepare_kickoff(crew: Crew, inputs: dict[str, Any] | None) -> dict[str, Any]
|
|||||||
crew._task_output_handler.reset()
|
crew._task_output_handler.reset()
|
||||||
crew._logging_color = "bold_purple"
|
crew._logging_color = "bold_purple"
|
||||||
|
|
||||||
if inputs is not None:
|
if normalized is not None:
|
||||||
crew._inputs = inputs
|
# Extract files from dedicated "files" key
|
||||||
crew._interpolate_inputs(inputs)
|
files = normalized.pop("files", None) or {}
|
||||||
|
|
||||||
|
# Extract file objects unpacked directly into inputs
|
||||||
|
unpacked_files = _extract_files_from_inputs(normalized)
|
||||||
|
|
||||||
|
# Merge files (unpacked files take precedence over explicit files dict)
|
||||||
|
all_files = {**files, **unpacked_files}
|
||||||
|
if all_files:
|
||||||
|
store_files(crew.id, all_files)
|
||||||
|
|
||||||
|
crew._inputs = normalized
|
||||||
|
crew._interpolate_inputs(normalized)
|
||||||
crew._set_tasks_callbacks()
|
crew._set_tasks_callbacks()
|
||||||
crew._set_allow_crewai_trigger_context_for_first_task()
|
crew._set_allow_crewai_trigger_context_for_first_task()
|
||||||
|
|
||||||
@@ -227,7 +279,7 @@ def prepare_kickoff(crew: Crew, inputs: dict[str, Any] | None) -> dict[str, Any]
|
|||||||
if crew.planning:
|
if crew.planning:
|
||||||
crew._handle_crew_planning()
|
crew._handle_crew_planning()
|
||||||
|
|
||||||
return inputs
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
class StreamingContext:
|
class StreamingContext:
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ from crewai.tools.base_tool import BaseTool
|
|||||||
from crewai.utilities.config import process_config
|
from crewai.utilities.config import process_config
|
||||||
from crewai.utilities.constants import NOT_SPECIFIED, _NotSpecified
|
from crewai.utilities.constants import NOT_SPECIFIED, _NotSpecified
|
||||||
from crewai.utilities.converter import Converter, convert_to_model
|
from crewai.utilities.converter import Converter, convert_to_model
|
||||||
|
from crewai.utilities.file_store import (
|
||||||
|
clear_task_files,
|
||||||
|
get_all_files,
|
||||||
|
store_task_files,
|
||||||
|
)
|
||||||
|
from crewai.utilities.files import (
|
||||||
|
FileInput,
|
||||||
|
FilePath,
|
||||||
|
FileSourceInput,
|
||||||
|
normalize_input_files,
|
||||||
|
)
|
||||||
from crewai.utilities.guardrail import (
|
from crewai.utilities.guardrail import (
|
||||||
process_guardrail,
|
process_guardrail,
|
||||||
)
|
)
|
||||||
@@ -142,6 +153,10 @@ class Task(BaseModel):
|
|||||||
default_factory=list,
|
default_factory=list,
|
||||||
description="Tools the agent is limited to use for this task.",
|
description="Tools the agent is limited to use for this task.",
|
||||||
)
|
)
|
||||||
|
input_files: list[FileSourceInput | FileInput] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description="List of input files for this task. Accepts paths, bytes, or File objects.",
|
||||||
|
)
|
||||||
security_config: SecurityConfig = Field(
|
security_config: SecurityConfig = Field(
|
||||||
default_factory=SecurityConfig,
|
default_factory=SecurityConfig,
|
||||||
description="Security configuration for the task.",
|
description="Security configuration for the task.",
|
||||||
@@ -357,6 +372,21 @@ class Task(BaseModel):
|
|||||||
"may_not_set_field", "This field is not to be set by the user.", {}
|
"may_not_set_field", "This field is not to be set by the user.", {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@field_validator("input_files", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _normalize_input_files(cls, v: list[Any]) -> list[Any]:
|
||||||
|
"""Convert string paths to FilePath objects."""
|
||||||
|
if not v:
|
||||||
|
return v
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for item in v:
|
||||||
|
if isinstance(item, str):
|
||||||
|
result.append(FilePath(path=Path(item)))
|
||||||
|
else:
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|
||||||
@field_validator("output_file")
|
@field_validator("output_file")
|
||||||
@classmethod
|
@classmethod
|
||||||
def output_file_validation(cls, value: str | None) -> str | None:
|
def output_file_validation(cls, value: str | None) -> str | None:
|
||||||
@@ -495,10 +525,10 @@ class Task(BaseModel):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Execute the task asynchronously with context handling."""
|
"""Execute the task asynchronously with context handling."""
|
||||||
try:
|
try:
|
||||||
result = self._execute_core(agent, context, tools)
|
result = self._execute_core(agent, context, tools)
|
||||||
future.set_result(result)
|
future.set_result(result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
future.set_exception(e)
|
future.set_exception(e)
|
||||||
|
|
||||||
async def aexecute_sync(
|
async def aexecute_sync(
|
||||||
self,
|
self,
|
||||||
@@ -516,6 +546,7 @@ class Task(BaseModel):
|
|||||||
tools: list[Any] | None,
|
tools: list[Any] | None,
|
||||||
) -> TaskOutput:
|
) -> TaskOutput:
|
||||||
"""Run the core execution logic of the task asynchronously."""
|
"""Run the core execution logic of the task asynchronously."""
|
||||||
|
self._store_input_files()
|
||||||
try:
|
try:
|
||||||
agent = agent or self.agent
|
agent = agent or self.agent
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
@@ -600,6 +631,8 @@ class Task(BaseModel):
|
|||||||
self.end_time = datetime.datetime.now()
|
self.end_time = datetime.datetime.now()
|
||||||
crewai_event_bus.emit(self, TaskFailedEvent(error=str(e), task=self)) # type: ignore[no-untyped-call]
|
crewai_event_bus.emit(self, TaskFailedEvent(error=str(e), task=self)) # type: ignore[no-untyped-call]
|
||||||
raise e # Re-raise the exception after emitting the event
|
raise e # Re-raise the exception after emitting the event
|
||||||
|
finally:
|
||||||
|
clear_task_files(self.id)
|
||||||
|
|
||||||
def _execute_core(
|
def _execute_core(
|
||||||
self,
|
self,
|
||||||
@@ -608,6 +641,7 @@ class Task(BaseModel):
|
|||||||
tools: list[Any] | None,
|
tools: list[Any] | None,
|
||||||
) -> TaskOutput:
|
) -> TaskOutput:
|
||||||
"""Run the core execution logic of the task."""
|
"""Run the core execution logic of the task."""
|
||||||
|
self._store_input_files()
|
||||||
try:
|
try:
|
||||||
agent = agent or self.agent
|
agent = agent or self.agent
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
@@ -693,6 +727,8 @@ class Task(BaseModel):
|
|||||||
self.end_time = datetime.datetime.now()
|
self.end_time = datetime.datetime.now()
|
||||||
crewai_event_bus.emit(self, TaskFailedEvent(error=str(e), task=self)) # type: ignore[no-untyped-call]
|
crewai_event_bus.emit(self, TaskFailedEvent(error=str(e), task=self)) # type: ignore[no-untyped-call]
|
||||||
raise e # Re-raise the exception after emitting the event
|
raise e # Re-raise the exception after emitting the event
|
||||||
|
finally:
|
||||||
|
clear_task_files(self.id)
|
||||||
|
|
||||||
def prompt(self) -> str:
|
def prompt(self) -> str:
|
||||||
"""Generates the task prompt with optional markdown formatting.
|
"""Generates the task prompt with optional markdown formatting.
|
||||||
@@ -715,6 +751,51 @@ class Task(BaseModel):
|
|||||||
if trigger_payload is not None:
|
if trigger_payload is not None:
|
||||||
description += f"\n\nTrigger Payload: {trigger_payload}"
|
description += f"\n\nTrigger Payload: {trigger_payload}"
|
||||||
|
|
||||||
|
if self.agent and self.agent.crew:
|
||||||
|
files = get_all_files(self.agent.crew.id, self.id)
|
||||||
|
if files:
|
||||||
|
supported_types: list[str] = []
|
||||||
|
if self.agent.llm and self.agent.llm.supports_multimodal():
|
||||||
|
supported_types = (
|
||||||
|
self.agent.llm.supported_multimodal_content_types()
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_auto_injected(content_type: str) -> bool:
|
||||||
|
return any(content_type.startswith(t) for t in supported_types)
|
||||||
|
|
||||||
|
auto_injected_files = {
|
||||||
|
name: f_input
|
||||||
|
for name, f_input in files.items()
|
||||||
|
if is_auto_injected(f_input.content_type)
|
||||||
|
}
|
||||||
|
tool_files = {
|
||||||
|
name: f_input
|
||||||
|
for name, f_input in files.items()
|
||||||
|
if not is_auto_injected(f_input.content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
file_lines: list[str] = []
|
||||||
|
|
||||||
|
if auto_injected_files:
|
||||||
|
file_lines.append(
|
||||||
|
"Input files (content already loaded in conversation):"
|
||||||
|
)
|
||||||
|
for name, file_input in auto_injected_files.items():
|
||||||
|
filename = file_input.filename or name
|
||||||
|
file_lines.append(f' - "{name}" ({filename})')
|
||||||
|
|
||||||
|
if tool_files:
|
||||||
|
file_lines.append(
|
||||||
|
"Available input files (use the name in quotes with read_file tool):"
|
||||||
|
)
|
||||||
|
for name, file_input in tool_files.items():
|
||||||
|
filename = file_input.filename or name
|
||||||
|
content_type = file_input.content_type
|
||||||
|
file_lines.append(f' - "{name}" ({filename}, {content_type})')
|
||||||
|
|
||||||
|
if file_lines:
|
||||||
|
description += "\n\n" + "\n".join(file_lines)
|
||||||
|
|
||||||
tasks_slices = [description]
|
tasks_slices = [description]
|
||||||
|
|
||||||
output = self.i18n.slice("expected_output").format(
|
output = self.i18n.slice("expected_output").format(
|
||||||
@@ -948,6 +1029,18 @@ Follow these guidelines:
|
|||||||
) from e
|
) from e
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _store_input_files(self) -> None:
|
||||||
|
"""Store task input files in the file store.
|
||||||
|
|
||||||
|
Converts input_files list to a named dict and stores under task ID.
|
||||||
|
"""
|
||||||
|
if not self.input_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
files_dict = normalize_input_files(self.input_files)
|
||||||
|
if files_dict:
|
||||||
|
store_task_files(self.id, files_dict)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Task(description={self.description}, expected_output={self.expected_output})"
|
return f"Task(description={self.description}, expected_output={self.expected_output})"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user