mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-07 16:09:30 +00:00
Fix #5931: Propagate Crew.prompt_file to all components during execution
Previously, setting prompt_file on a Crew had no effect on agents, tasks, tools, or any other components. The custom I18N instance was only used for the manager agent's role/goal/backstory in hierarchical mode, while all other code used the hardcoded I18N_DEFAULT singleton. This fix introduces a contextvars-based mechanism that scopes a custom I18N instance to the crew's execution: - Added get_crew_i18n() / set_crew_i18n() / reset_crew_i18n() to i18n.py using a ContextVar for thread-safe crew-scoped I18N overrides - prepare_kickoff() now sets the crew's I18N when prompt_file is provided - Crew.kickoff() resets the I18N context in its finally block (even on errors) - Replaced all I18N_DEFAULT references with get_crew_i18n() calls across 29 source files so every component (Prompts, Task, agent tools, tool usage, memory, reasoning, etc.) respects the crew's prompt_file Added 12 new tests covering: - Context variable set/get/reset/nesting behavior - Prompts class using custom I18N - Task.prompt() using custom I18N - AgentTools delegation tools using custom I18N - Full Crew.kickoff() integration (sets context, resets on success/exception) Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -102,7 +102,7 @@ from crewai.utilities.converter import Converter, ConverterError
|
||||
from crewai.utilities.env import get_env_context
|
||||
from crewai.utilities.guardrail import process_guardrail, serialize_guardrail_for_json
|
||||
from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.prompts import Prompts, StandardPromptResult, SystemPromptResult
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
@@ -608,7 +608,7 @@ class Agent(BaseAgent):
|
||||
m.format() for m in matches
|
||||
)
|
||||
if memory.strip() != "":
|
||||
task_prompt += I18N_DEFAULT.slice("memory").format(memory=memory)
|
||||
task_prompt += get_crew_i18n().slice("memory").format(memory=memory)
|
||||
|
||||
crewai_event_bus.emit(
|
||||
self,
|
||||
@@ -1024,7 +1024,7 @@ class Agent(BaseAgent):
|
||||
response_template=self.response_template,
|
||||
).task_execution()
|
||||
|
||||
stop_words = [I18N_DEFAULT.slice("observation")]
|
||||
stop_words = [get_crew_i18n().slice("observation")]
|
||||
if self.response_template:
|
||||
stop_words.append(
|
||||
self.response_template.split("{{ .Response }}")[1].strip()
|
||||
@@ -1310,10 +1310,10 @@ class Agent(BaseAgent):
|
||||
from_agent=self,
|
||||
),
|
||||
)
|
||||
query = I18N_DEFAULT.slice("knowledge_search_query").format(
|
||||
query = get_crew_i18n().slice("knowledge_search_query").format(
|
||||
task_prompt=task_prompt
|
||||
)
|
||||
rewriter_prompt = I18N_DEFAULT.slice("knowledge_search_query_system_prompt")
|
||||
rewriter_prompt = get_crew_i18n().slice("knowledge_search_query_system_prompt")
|
||||
if not isinstance(self.llm, BaseLLM):
|
||||
self._logger.log(
|
||||
"warning",
|
||||
@@ -1488,7 +1488,7 @@ class Agent(BaseAgent):
|
||||
m.format() for m in matches
|
||||
)
|
||||
if memory_block:
|
||||
formatted_messages += "\n\n" + I18N_DEFAULT.slice("memory").format(
|
||||
formatted_messages += "\n\n" + get_crew_i18n().slice("memory").format(
|
||||
memory=memory_block
|
||||
)
|
||||
crewai_event_bus.emit(
|
||||
@@ -1703,7 +1703,7 @@ class Agent(BaseAgent):
|
||||
try:
|
||||
model_schema = generate_model_description(response_format)
|
||||
schema = json.dumps(model_schema, indent=2)
|
||||
instructions = I18N_DEFAULT.slice("formatted_task_instructions").format(
|
||||
instructions = get_crew_i18n().slice("formatted_task_instructions").format(
|
||||
output_format=schema
|
||||
)
|
||||
|
||||
|
||||
@@ -67,19 +67,19 @@ def build_task_prompt_with_schema(task: Task, task_prompt: str) -> str:
|
||||
Returns:
|
||||
The task prompt potentially augmented with schema instructions.
|
||||
"""
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
if (task.output_json or task.output_pydantic) and not task.response_model:
|
||||
if task.output_json:
|
||||
schema_dict = generate_model_description(task.output_json)
|
||||
schema = json.dumps(schema_dict["json_schema"]["schema"], indent=2)
|
||||
task_prompt += "\n" + I18N_DEFAULT.slice(
|
||||
task_prompt += "\n" + get_crew_i18n().slice(
|
||||
"formatted_task_instructions"
|
||||
).format(output_format=schema)
|
||||
elif task.output_pydantic:
|
||||
schema_dict = generate_model_description(task.output_pydantic)
|
||||
schema = json.dumps(schema_dict["json_schema"]["schema"], indent=2)
|
||||
task_prompt += "\n" + I18N_DEFAULT.slice(
|
||||
task_prompt += "\n" + get_crew_i18n().slice(
|
||||
"formatted_task_instructions"
|
||||
).format(output_format=schema)
|
||||
return task_prompt
|
||||
@@ -95,10 +95,10 @@ def format_task_with_context(task_prompt: str, context: str | None) -> str:
|
||||
Returns:
|
||||
The task prompt formatted with context if provided.
|
||||
"""
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
if context:
|
||||
return I18N_DEFAULT.slice("task_with_context").format(
|
||||
return get_crew_i18n().slice("task_with_context").format(
|
||||
task=task_prompt, context=context
|
||||
)
|
||||
return task_prompt
|
||||
|
||||
@@ -33,7 +33,7 @@ from crewai.tools.base_tool import BaseTool
|
||||
from crewai.types.callback import SerializableCallable
|
||||
from crewai.utilities import Logger
|
||||
from crewai.utilities.converter import Converter
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.import_utils import require
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class LangGraphAgentAdapter(BaseAgentAdapter):
|
||||
task_prompt = task.prompt() if hasattr(task, "prompt") else str(task)
|
||||
|
||||
if context:
|
||||
task_prompt = I18N_DEFAULT.slice("task_with_context").format(
|
||||
task_prompt = get_crew_i18n().slice("task_with_context").format(
|
||||
task=task_prompt, context=context
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from crewai.events.types.agent_events import (
|
||||
from crewai.tools import BaseTool
|
||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
from crewai.utilities import Logger
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.import_utils import require
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class OpenAIAgentAdapter(BaseAgentAdapter):
|
||||
try:
|
||||
task_prompt: str = task.prompt()
|
||||
if context:
|
||||
task_prompt = I18N_DEFAULT.slice("task_with_context").format(
|
||||
task_prompt = get_crew_i18n().slice("task_with_context").format(
|
||||
task=task_prompt, context=context
|
||||
)
|
||||
crewai_event_bus.emit(
|
||||
|
||||
@@ -8,7 +8,7 @@ import json
|
||||
from typing import Any
|
||||
|
||||
from crewai.agents.agent_adapters.base_converter_adapter import BaseConverterAdapter
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
class OpenAIConverterAdapter(BaseConverterAdapter):
|
||||
@@ -59,7 +59,7 @@ class OpenAIConverterAdapter(BaseConverterAdapter):
|
||||
if not self._output_format:
|
||||
return base_prompt
|
||||
|
||||
output_schema: str = I18N_DEFAULT.slice("formatted_task_instructions").format(
|
||||
output_schema: str = get_crew_i18n().slice("formatted_task_instructions").format(
|
||||
output_format=json.dumps(self._schema, indent=2)
|
||||
)
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ from crewai.utilities.agent_utils import (
|
||||
)
|
||||
from crewai.utilities.constants import TRAINING_DATA_FILE
|
||||
from crewai.utilities.file_store import aget_all_files, get_all_files
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
from crewai.utilities.tool_utils import (
|
||||
@@ -751,7 +751,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
if tool_finish:
|
||||
return tool_finish
|
||||
|
||||
reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning")
|
||||
reasoning_prompt = get_crew_i18n().slice("post_tool_reasoning")
|
||||
reasoning_message: LLMMessage = {
|
||||
"role": "user",
|
||||
"content": reasoning_prompt,
|
||||
@@ -774,7 +774,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
if tool_finish:
|
||||
return tool_finish
|
||||
|
||||
reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning")
|
||||
reasoning_prompt = get_crew_i18n().slice("post_tool_reasoning")
|
||||
reasoning_message = {
|
||||
"role": "user",
|
||||
"content": reasoning_prompt,
|
||||
@@ -1430,7 +1430,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
Returns:
|
||||
Updated action or final answer.
|
||||
"""
|
||||
add_image_tool = I18N_DEFAULT.tools("add_image")
|
||||
add_image_tool = get_crew_i18n().tools("add_image")
|
||||
if (
|
||||
isinstance(add_image_tool, dict)
|
||||
and formatted_answer.tool.casefold().strip()
|
||||
@@ -1632,5 +1632,5 @@ class CrewAgentExecutor(BaseAgentExecutor):
|
||||
Formatted message dict.
|
||||
"""
|
||||
return format_message_for_llm(
|
||||
I18N_DEFAULT.slice("feedback_instructions").format(feedback=feedback)
|
||||
get_crew_i18n().slice("feedback_instructions").format(feedback=feedback)
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ from crewai.agents.constants import (
|
||||
MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE,
|
||||
UNABLE_TO_REPAIR_JSON_RESULTS,
|
||||
)
|
||||
from crewai.utilities.i18n import I18N_DEFAULT as _I18N
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -115,13 +115,13 @@ def parse(text: str) -> AgentAction | AgentFinish:
|
||||
|
||||
if not ACTION_REGEX.search(text):
|
||||
raise OutputParserError(
|
||||
f"{MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE}\n{_I18N.slice('final_answer_format')}",
|
||||
f"{MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE}\n{get_crew_i18n().slice('final_answer_format')}",
|
||||
)
|
||||
if not ACTION_INPUT_ONLY_REGEX.search(text):
|
||||
raise OutputParserError(
|
||||
MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE,
|
||||
)
|
||||
err_format = _I18N.slice("format_without_tools")
|
||||
err_format = get_crew_i18n().slice("format_without_tools")
|
||||
error = f"{err_format}"
|
||||
raise OutputParserError(
|
||||
error,
|
||||
|
||||
@@ -23,7 +23,7 @@ from crewai.events.types.observation_events import (
|
||||
StepObservationStartedEvent,
|
||||
)
|
||||
from crewai.utilities.agent_utils import extract_task_section
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.planning_types import StepObservation, TodoItem
|
||||
from crewai.utilities.types import LLMMessage
|
||||
@@ -231,7 +231,7 @@ class PlannerObserver:
|
||||
task_desc = extract_task_section(self.kickoff_input)
|
||||
task_goal = "Complete the task successfully"
|
||||
|
||||
system_prompt = I18N_DEFAULT.retrieve("planning", "observation_system_prompt")
|
||||
system_prompt = get_crew_i18n().retrieve("planning", "observation_system_prompt")
|
||||
|
||||
completed_summary = ""
|
||||
if all_completed:
|
||||
@@ -256,7 +256,7 @@ class PlannerObserver:
|
||||
remaining_lines
|
||||
)
|
||||
|
||||
user_prompt = I18N_DEFAULT.retrieve(
|
||||
user_prompt = get_crew_i18n().retrieve(
|
||||
"planning", "observation_user_prompt"
|
||||
).format(
|
||||
task_description=task_desc,
|
||||
|
||||
@@ -39,7 +39,7 @@ from crewai.utilities.agent_utils import (
|
||||
process_llm_response,
|
||||
setup_native_tools,
|
||||
)
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.planning_types import TodoItem
|
||||
from crewai.utilities.step_execution_context import StepExecutionContext, StepResult
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
@@ -210,14 +210,14 @@ class StepExecutor:
|
||||
tools_section = ""
|
||||
if self.tools and not self._use_native_tools:
|
||||
tool_names = ", ".join(sanitize_tool_name(t.name) for t in self.tools)
|
||||
tools_section = I18N_DEFAULT.retrieve(
|
||||
tools_section = get_crew_i18n().retrieve(
|
||||
"planning", "step_executor_tools_section"
|
||||
).format(tool_names=tool_names)
|
||||
elif self.tools:
|
||||
tool_names = ", ".join(sanitize_tool_name(t.name) for t in self.tools)
|
||||
tools_section = f"\n\nAvailable tools: {tool_names}"
|
||||
|
||||
return I18N_DEFAULT.retrieve("planning", "step_executor_system_prompt").format(
|
||||
return get_crew_i18n().retrieve("planning", "step_executor_system_prompt").format(
|
||||
role=role,
|
||||
backstory=backstory,
|
||||
goal=goal,
|
||||
@@ -232,7 +232,7 @@ class StepExecutor:
|
||||
task_section = extract_task_section(context.task_description)
|
||||
if task_section:
|
||||
parts.append(
|
||||
I18N_DEFAULT.retrieve(
|
||||
get_crew_i18n().retrieve(
|
||||
"planning", "step_executor_task_context"
|
||||
).format(
|
||||
task_context=task_section,
|
||||
@@ -240,14 +240,14 @@ class StepExecutor:
|
||||
)
|
||||
|
||||
parts.append(
|
||||
I18N_DEFAULT.retrieve("planning", "step_executor_user_prompt").format(
|
||||
get_crew_i18n().retrieve("planning", "step_executor_user_prompt").format(
|
||||
step_description=todo.description,
|
||||
)
|
||||
)
|
||||
|
||||
if todo.tool_to_use:
|
||||
parts.append(
|
||||
I18N_DEFAULT.retrieve(
|
||||
get_crew_i18n().retrieve(
|
||||
"planning", "step_executor_suggested_tool"
|
||||
).format(
|
||||
tool_to_use=todo.tool_to_use,
|
||||
@@ -256,16 +256,16 @@ class StepExecutor:
|
||||
|
||||
if context.dependency_results:
|
||||
parts.append(
|
||||
I18N_DEFAULT.retrieve("planning", "step_executor_context_header")
|
||||
get_crew_i18n().retrieve("planning", "step_executor_context_header")
|
||||
)
|
||||
for step_num, result in sorted(context.dependency_results.items()):
|
||||
parts.append(
|
||||
I18N_DEFAULT.retrieve(
|
||||
get_crew_i18n().retrieve(
|
||||
"planning", "step_executor_context_entry"
|
||||
).format(step_number=step_num, result=result)
|
||||
)
|
||||
|
||||
parts.append(I18N_DEFAULT.retrieve("planning", "step_executor_complete_step"))
|
||||
parts.append(get_crew_i18n().retrieve("planning", "step_executor_complete_step"))
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ class Crew(FlowTrackable, BaseModel):
|
||||
default_factory=TaskOutputStorageHandler
|
||||
)
|
||||
_kickoff_event_id: str | None = PrivateAttr(default=None)
|
||||
_i18n_token: Any = PrivateAttr(default=None)
|
||||
|
||||
name: str | None = Field(default="crew")
|
||||
cache: bool = Field(default=True)
|
||||
@@ -1044,6 +1045,11 @@ class Crew(FlowTrackable, BaseModel):
|
||||
if self._memory is not None and hasattr(self._memory, "drain_writes"):
|
||||
self._memory.drain_writes()
|
||||
clear_files(self.id)
|
||||
if self._i18n_token is not None:
|
||||
from crewai.utilities.i18n import reset_crew_i18n
|
||||
|
||||
reset_crew_i18n(self._i18n_token)
|
||||
self._i18n_token = None
|
||||
detach(token)
|
||||
|
||||
def _post_kickoff(self, result: CrewOutput) -> CrewOutput:
|
||||
|
||||
@@ -354,6 +354,11 @@ def prepare_kickoff(
|
||||
crew._set_tasks_callbacks()
|
||||
crew._set_allow_crewai_trigger_context_for_first_task()
|
||||
|
||||
if crew.prompt_file:
|
||||
from crewai.utilities.i18n import get_i18n, set_crew_i18n
|
||||
|
||||
crew._i18n_token = set_crew_i18n(get_i18n(crew.prompt_file))
|
||||
|
||||
agents_to_setup: list[BaseAgent] = list(crew.agents)
|
||||
seen_agent_ids: set[int] = {id(agent) for agent in agents_to_setup}
|
||||
for task in crew.tasks:
|
||||
|
||||
@@ -93,7 +93,7 @@ from crewai.utilities.agent_utils import (
|
||||
track_delegation_if_needed,
|
||||
)
|
||||
from crewai.utilities.constants import TRAINING_DATA_FILE
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.planning_types import (
|
||||
PlanStep,
|
||||
StepObservation,
|
||||
@@ -1448,7 +1448,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
action.result = str(e)
|
||||
self._append_message_to_state(action.text)
|
||||
|
||||
reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning")
|
||||
reasoning_prompt = get_crew_i18n().slice("post_tool_reasoning")
|
||||
reasoning_message: LLMMessage = {
|
||||
"role": "user",
|
||||
"content": reasoning_prompt,
|
||||
@@ -1469,7 +1469,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
self.state.is_finished = True
|
||||
return "tool_result_is_final"
|
||||
|
||||
reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning")
|
||||
reasoning_prompt = get_crew_i18n().slice("post_tool_reasoning")
|
||||
reasoning_message_post: LLMMessage = {
|
||||
"role": "user",
|
||||
"content": reasoning_prompt,
|
||||
@@ -2220,10 +2220,10 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
# Build synthesis prompt
|
||||
role = self.agent.role if self.agent else "Assistant"
|
||||
|
||||
system_prompt = I18N_DEFAULT.retrieve(
|
||||
system_prompt = get_crew_i18n().retrieve(
|
||||
"planning", "synthesis_system_prompt"
|
||||
).format(role=role)
|
||||
user_prompt = I18N_DEFAULT.retrieve("planning", "synthesis_user_prompt").format(
|
||||
user_prompt = get_crew_i18n().retrieve("planning", "synthesis_user_prompt").format(
|
||||
task_description=task_description,
|
||||
combined_steps=combined_steps,
|
||||
)
|
||||
@@ -2470,7 +2470,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
self.task.description if self.task else getattr(self, "_kickoff_input", "")
|
||||
)
|
||||
|
||||
enhancement = I18N_DEFAULT.retrieve(
|
||||
enhancement = get_crew_i18n().retrieve(
|
||||
"planning", "replan_enhancement_prompt"
|
||||
).format(previous_context=previous_context)
|
||||
|
||||
@@ -2776,7 +2776,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
|
||||
Returns:
|
||||
Updated action or final answer.
|
||||
"""
|
||||
add_image_tool = I18N_DEFAULT.tools("add_image")
|
||||
add_image_tool = get_crew_i18n().tools("add_image")
|
||||
if (
|
||||
isinstance(add_image_tool, dict)
|
||||
and formatted_answer.tool.casefold().strip()
|
||||
|
||||
@@ -3511,7 +3511,7 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta):
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM as BaseLLMClass
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
llm_instance: BaseLLMClass
|
||||
if isinstance(llm, str):
|
||||
@@ -3531,7 +3531,7 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta):
|
||||
description=f"The outcome that best matches the feedback. Must be one of: {', '.join(outcomes)}"
|
||||
)
|
||||
|
||||
prompt_template = I18N_DEFAULT.slice("human_feedback_collapse")
|
||||
prompt_template = get_crew_i18n().slice("human_feedback_collapse")
|
||||
|
||||
prompt = prompt_template.format(
|
||||
feedback=feedback,
|
||||
|
||||
@@ -363,9 +363,9 @@ def human_feedback(
|
||||
|
||||
def _get_hitl_prompt(key: str) -> str:
|
||||
"""Read a HITL prompt from the i18n translations."""
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
return I18N_DEFAULT.slice(key)
|
||||
return get_crew_i18n().slice(key)
|
||||
|
||||
def _resolve_llm_instance() -> Any:
|
||||
"""Resolve the ``llm`` parameter to a BaseLLM instance.
|
||||
|
||||
@@ -93,7 +93,7 @@ from crewai.utilities.converter import (
|
||||
)
|
||||
from crewai.utilities.guardrail import process_guardrail, serialize_guardrail_for_json
|
||||
from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.pydantic_schema_utils import (
|
||||
generate_model_description,
|
||||
@@ -577,7 +577,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
f"- {m.record.content}" for m in matches
|
||||
)
|
||||
if memory_block:
|
||||
formatted = I18N_DEFAULT.slice("memory").format(memory=memory_block)
|
||||
formatted = get_crew_i18n().slice("memory").format(memory=memory_block)
|
||||
if self._messages and self._messages[0].get("role") == "system":
|
||||
existing_content = self._messages[0].get("content", "")
|
||||
if not isinstance(existing_content, str):
|
||||
@@ -650,7 +650,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
try:
|
||||
model_schema = generate_model_description(active_response_format)
|
||||
schema = json.dumps(model_schema, indent=2)
|
||||
instructions = I18N_DEFAULT.slice("formatted_task_instructions").format(
|
||||
instructions = get_crew_i18n().slice("formatted_task_instructions").format(
|
||||
output_format=schema
|
||||
)
|
||||
|
||||
@@ -799,7 +799,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
base_prompt = ""
|
||||
if self._parsed_tools:
|
||||
# Use the prompt template for agents with tools
|
||||
base_prompt = I18N_DEFAULT.slice(
|
||||
base_prompt = get_crew_i18n().slice(
|
||||
"lite_agent_system_prompt_with_tools"
|
||||
).format(
|
||||
role=self.role,
|
||||
@@ -810,7 +810,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
)
|
||||
else:
|
||||
# Use the prompt template for agents without tools
|
||||
base_prompt = I18N_DEFAULT.slice(
|
||||
base_prompt = get_crew_i18n().slice(
|
||||
"lite_agent_system_prompt_without_tools"
|
||||
).format(
|
||||
role=self.role,
|
||||
@@ -822,7 +822,7 @@ class LiteAgent(FlowTrackable, BaseModel):
|
||||
if active_response_format:
|
||||
model_description = generate_model_description(active_response_format)
|
||||
schema_json = json.dumps(model_description, indent=2)
|
||||
base_prompt += I18N_DEFAULT.slice("lite_agent_response_format").format(
|
||||
base_prompt += get_crew_i18n().slice("lite_agent_response_format").format(
|
||||
response_format=schema_json
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Any
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from crewai.memory.types import MemoryRecord, ScopeInfo
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -149,7 +149,7 @@ def _get_prompt(key: str) -> str:
|
||||
Returns:
|
||||
The prompt string.
|
||||
"""
|
||||
return I18N_DEFAULT.memory(key)
|
||||
return get_crew_i18n().memory(key)
|
||||
|
||||
|
||||
def extract_memories_from_content(content: str, llm: Any) -> list[str]:
|
||||
|
||||
@@ -91,7 +91,7 @@ from crewai.utilities.guardrail_types import (
|
||||
GuardrailType,
|
||||
GuardrailsType,
|
||||
)
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.string_utils import interpolate_only
|
||||
|
||||
|
||||
@@ -970,7 +970,7 @@ class Task(BaseModel):
|
||||
|
||||
tasks_slices = [description]
|
||||
|
||||
output = I18N_DEFAULT.slice("expected_output").format(
|
||||
output = get_crew_i18n().slice("expected_output").format(
|
||||
expected_output=self.expected_output
|
||||
)
|
||||
tasks_slices = [description, output]
|
||||
@@ -1042,7 +1042,7 @@ Follow these guidelines:
|
||||
raise ValueError(f"Error interpolating output_file path: {e!s}") from e
|
||||
|
||||
if inputs.get("crew_chat_messages"):
|
||||
conversation_instruction = I18N_DEFAULT.slice(
|
||||
conversation_instruction = get_crew_i18n().slice(
|
||||
"conversation_history_instruction"
|
||||
)
|
||||
|
||||
@@ -1318,7 +1318,7 @@ Follow these guidelines:
|
||||
self.retry_count += 1
|
||||
current_retry_count = self.retry_count
|
||||
|
||||
context = I18N_DEFAULT.errors("validation_error").format(
|
||||
context = get_crew_i18n().errors("validation_error").format(
|
||||
guardrail_result_error=guardrail_result.error,
|
||||
task_output=task_output.raw,
|
||||
)
|
||||
@@ -1429,7 +1429,7 @@ Follow these guidelines:
|
||||
self.retry_count += 1
|
||||
current_retry_count = self.retry_count
|
||||
|
||||
context = I18N_DEFAULT.errors("validation_error").format(
|
||||
context = get_crew_i18n().errors("validation_error").format(
|
||||
guardrail_result_error=guardrail_result.error,
|
||||
task_output=task_output.raw,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
class AddImageToolSchema(BaseModel):
|
||||
@@ -16,9 +16,9 @@ class AddImageToolSchema(BaseModel):
|
||||
class AddImageTool(BaseTool):
|
||||
"""Tool for adding images to the content"""
|
||||
|
||||
name: str = Field(default_factory=lambda: I18N_DEFAULT.tools("add_image")["name"]) # type: ignore[index]
|
||||
name: str = Field(default_factory=lambda: get_crew_i18n().tools("add_image")["name"]) # type: ignore[index]
|
||||
description: str = Field(
|
||||
default_factory=lambda: I18N_DEFAULT.tools("add_image")["description"] # type: ignore[index]
|
||||
default_factory=lambda: get_crew_i18n().tools("add_image")["description"] # type: ignore[index]
|
||||
)
|
||||
args_schema: type[BaseModel] = AddImageToolSchema
|
||||
|
||||
@@ -28,7 +28,7 @@ class AddImageTool(BaseTool):
|
||||
action: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
action = action or I18N_DEFAULT.tools("add_image")["default_action"] # type: ignore
|
||||
action = action or get_crew_i18n().tools("add_image")["default_action"] # type: ignore
|
||||
content = [
|
||||
{"type": "text", "text": action},
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from crewai.tools.agent_tools.ask_question_tool import AskQuestionTool
|
||||
from crewai.tools.agent_tools.delegate_work_tool import DelegateWorkTool
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -25,12 +25,12 @@ class AgentTools:
|
||||
|
||||
delegate_tool = DelegateWorkTool(
|
||||
agents=self.agents,
|
||||
description=I18N_DEFAULT.tools("delegate_work").format(coworkers=coworkers), # type: ignore
|
||||
description=get_crew_i18n().tools("delegate_work").format(coworkers=coworkers), # type: ignore
|
||||
)
|
||||
|
||||
ask_tool = AskQuestionTool(
|
||||
agents=self.agents,
|
||||
description=I18N_DEFAULT.tools("ask_question").format(coworkers=coworkers), # type: ignore
|
||||
description=get_crew_i18n().tools("ask_question").format(coworkers=coworkers), # type: ignore
|
||||
)
|
||||
|
||||
return [delegate_tool, ask_tool]
|
||||
|
||||
@@ -6,7 +6,7 @@ from pydantic import Field
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -90,7 +90,7 @@ class BaseAgentTool(BaseTool):
|
||||
)
|
||||
except (AttributeError, ValueError) as e:
|
||||
# Handle specific exceptions that might occur during role name processing
|
||||
return I18N_DEFAULT.errors("agent_tool_unexisting_coworker").format(
|
||||
return get_crew_i18n().errors("agent_tool_unexisting_coworker").format(
|
||||
coworkers="\n".join(
|
||||
[
|
||||
f"- {self.sanitize_agent_name(agent.role)}"
|
||||
@@ -102,7 +102,7 @@ class BaseAgentTool(BaseTool):
|
||||
|
||||
if not agent:
|
||||
# No matching agent found after sanitization
|
||||
return I18N_DEFAULT.errors("agent_tool_unexisting_coworker").format(
|
||||
return get_crew_i18n().errors("agent_tool_unexisting_coworker").format(
|
||||
coworkers="\n".join(
|
||||
[
|
||||
f"- {self.sanitize_agent_name(agent.role)}"
|
||||
@@ -117,7 +117,7 @@ class BaseAgentTool(BaseTool):
|
||||
task_with_assigned_agent = Task(
|
||||
description=task,
|
||||
agent=selected_agent,
|
||||
expected_output=I18N_DEFAULT.slice("manager_request"),
|
||||
expected_output=get_crew_i18n().slice("manager_request"),
|
||||
)
|
||||
logger.debug(
|
||||
f"Created task for agent '{self.sanitize_agent_name(selected_agent.role)}': {task}"
|
||||
@@ -125,6 +125,6 @@ class BaseAgentTool(BaseTool):
|
||||
return selected_agent.execute_task(task_with_assigned_agent, context)
|
||||
except Exception as e:
|
||||
# Handle task creation or execution errors
|
||||
return I18N_DEFAULT.errors("agent_tool_execution_error").format(
|
||||
return get_crew_i18n().errors("agent_tool_execution_error").format(
|
||||
agent_role=self.sanitize_agent_name(selected_agent.role), error=str(e)
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
class RecallMemorySchema(BaseModel):
|
||||
@@ -117,14 +117,14 @@ def create_memory_tools(memory: Any) -> list[BaseTool]:
|
||||
tools: list[BaseTool] = [
|
||||
RecallMemoryTool(
|
||||
memory=memory,
|
||||
description=I18N_DEFAULT.tools("recall_memory"),
|
||||
description=get_crew_i18n().tools("recall_memory"),
|
||||
),
|
||||
]
|
||||
if not memory.read_only:
|
||||
tools.append(
|
||||
RememberTool(
|
||||
memory=memory,
|
||||
description=I18N_DEFAULT.tools("save_to_memory"),
|
||||
description=get_crew_i18n().tools("save_to_memory"),
|
||||
)
|
||||
)
|
||||
return tools
|
||||
|
||||
@@ -29,7 +29,7 @@ from crewai.utilities.agent_utils import (
|
||||
render_text_description_and_args,
|
||||
)
|
||||
from crewai.utilities.converter import Converter
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class ToolUsage:
|
||||
if (
|
||||
isinstance(tool, CrewStructuredTool)
|
||||
and sanitize_tool_name(tool.name)
|
||||
== sanitize_tool_name(I18N_DEFAULT.tools("add_image")["name"]) # type: ignore
|
||||
== sanitize_tool_name(get_crew_i18n().tools("add_image")["name"]) # type: ignore
|
||||
):
|
||||
try:
|
||||
return self._use(tool_string=tool_string, tool=tool, calling=calling)
|
||||
@@ -193,7 +193,7 @@ class ToolUsage:
|
||||
if (
|
||||
isinstance(tool, CrewStructuredTool)
|
||||
and sanitize_tool_name(tool.name)
|
||||
== sanitize_tool_name(I18N_DEFAULT.tools("add_image")["name"]) # type: ignore
|
||||
== sanitize_tool_name(get_crew_i18n().tools("add_image")["name"]) # type: ignore
|
||||
):
|
||||
try:
|
||||
return await self._ause(
|
||||
@@ -229,7 +229,7 @@ class ToolUsage:
|
||||
"""
|
||||
if self._check_tool_repeated_usage(calling=calling):
|
||||
try:
|
||||
result = I18N_DEFAULT.errors("task_repeated_usage").format(
|
||||
result = get_crew_i18n().errors("task_repeated_usage").format(
|
||||
tool_names=self.tools_names
|
||||
)
|
||||
self._telemetry.tool_repeated_usage(
|
||||
@@ -414,7 +414,7 @@ class ToolUsage:
|
||||
self._run_attempts += 1
|
||||
if self._run_attempts > self._max_parsing_attempts:
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
error_message = I18N_DEFAULT.errors(
|
||||
error_message = get_crew_i18n().errors(
|
||||
"tool_usage_exception"
|
||||
).format(
|
||||
error=e,
|
||||
@@ -422,7 +422,7 @@ class ToolUsage:
|
||||
tool_inputs=tool.description,
|
||||
)
|
||||
result = ToolUsageError(
|
||||
f"\n{error_message}.\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}"
|
||||
f"\n{error_message}.\nMoving on then. {get_crew_i18n().slice('format').format(tool_names=self.tools_names)}"
|
||||
).message
|
||||
if self.task:
|
||||
self.task.increment_tools_errors()
|
||||
@@ -460,7 +460,7 @@ class ToolUsage:
|
||||
# Repeated usage check happens before event emission - safe to return early
|
||||
if self._check_tool_repeated_usage(calling=calling):
|
||||
try:
|
||||
result = I18N_DEFAULT.errors("task_repeated_usage").format(
|
||||
result = get_crew_i18n().errors("task_repeated_usage").format(
|
||||
tool_names=self.tools_names
|
||||
)
|
||||
self._telemetry.tool_repeated_usage(
|
||||
@@ -647,7 +647,7 @@ class ToolUsage:
|
||||
self._run_attempts += 1
|
||||
if self._run_attempts > self._max_parsing_attempts:
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
error_message = I18N_DEFAULT.errors(
|
||||
error_message = get_crew_i18n().errors(
|
||||
"tool_usage_exception"
|
||||
).format(
|
||||
error=e,
|
||||
@@ -655,7 +655,7 @@ class ToolUsage:
|
||||
tool_inputs=tool.description,
|
||||
)
|
||||
result = ToolUsageError(
|
||||
f"\n{error_message}.\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}"
|
||||
f"\n{error_message}.\nMoving on then. {get_crew_i18n().slice('format').format(tool_names=self.tools_names)}"
|
||||
).message
|
||||
if self.task:
|
||||
self.task.increment_tools_errors()
|
||||
@@ -698,7 +698,7 @@ class ToolUsage:
|
||||
|
||||
def _remember_format(self, result: str) -> str:
|
||||
result = str(result)
|
||||
result += "\n\n" + I18N_DEFAULT.slice("tools").format(
|
||||
result += "\n\n" + get_crew_i18n().slice("tools").format(
|
||||
tools=self.tools_description, tool_names=self.tools_names
|
||||
)
|
||||
return result
|
||||
@@ -824,12 +824,12 @@ class ToolUsage:
|
||||
except Exception:
|
||||
if raise_error:
|
||||
raise
|
||||
return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")
|
||||
return ToolUsageError(f"{get_crew_i18n().errors('tool_arguments_error')}")
|
||||
|
||||
if not isinstance(arguments, dict):
|
||||
if raise_error:
|
||||
raise
|
||||
return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")
|
||||
return ToolUsageError(f"{get_crew_i18n().errors('tool_arguments_error')}")
|
||||
|
||||
return ToolCalling(
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
@@ -855,7 +855,7 @@ class ToolUsage:
|
||||
if self.agent and self.agent.verbose:
|
||||
PRINTER.print(content=f"\n\n{e}\n", color="red")
|
||||
return ToolUsageError(
|
||||
f"{I18N_DEFAULT.errors('tool_usage_error').format(error=e)}\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}"
|
||||
f"{get_crew_i18n().errors('tool_usage_error').format(error=e)}\nMoving on then. {get_crew_i18n().slice('format').format(tool_names=self.tools_names)}"
|
||||
)
|
||||
return self._tool_calling(tool_string)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from crewai.utilities.errors import AgentRepositoryError
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
)
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
@@ -313,10 +313,10 @@ def handle_max_iterations_exceeded(
|
||||
|
||||
if formatted_answer and hasattr(formatted_answer, "text"):
|
||||
assistant_message = (
|
||||
formatted_answer.text + f"\n{I18N_DEFAULT.errors('force_final_answer')}"
|
||||
formatted_answer.text + f"\n{get_crew_i18n().errors('force_final_answer')}"
|
||||
)
|
||||
else:
|
||||
assistant_message = I18N_DEFAULT.errors("force_final_answer")
|
||||
assistant_message = get_crew_i18n().errors("force_final_answer")
|
||||
|
||||
messages.append(format_message_for_llm(assistant_message, role="assistant"))
|
||||
|
||||
@@ -902,10 +902,10 @@ async def _asummarize_chunks(
|
||||
conversation_text = _format_messages_for_summary(chunk)
|
||||
summarization_messages = [
|
||||
format_message_for_llm(
|
||||
I18N_DEFAULT.slice("summarizer_system_message"), role="system"
|
||||
get_crew_i18n().slice("summarizer_system_message"), role="system"
|
||||
),
|
||||
format_message_for_llm(
|
||||
I18N_DEFAULT.slice("summarize_instruction").format(
|
||||
get_crew_i18n().slice("summarize_instruction").format(
|
||||
conversation=conversation_text
|
||||
),
|
||||
),
|
||||
@@ -972,10 +972,10 @@ def summarize_messages(
|
||||
conversation_text = _format_messages_for_summary(chunk)
|
||||
summarization_messages = [
|
||||
format_message_for_llm(
|
||||
I18N_DEFAULT.slice("summarizer_system_message"), role="system"
|
||||
get_crew_i18n().slice("summarizer_system_message"), role="system"
|
||||
),
|
||||
format_message_for_llm(
|
||||
I18N_DEFAULT.slice("summarize_instruction").format(
|
||||
get_crew_i18n().slice("summarize_instruction").format(
|
||||
conversation=conversation_text
|
||||
),
|
||||
),
|
||||
@@ -1005,7 +1005,7 @@ def summarize_messages(
|
||||
messages.extend(system_messages)
|
||||
|
||||
summary_message = format_message_for_llm(
|
||||
I18N_DEFAULT.slice("summary").format(merged_summary=merged_summary)
|
||||
get_crew_i18n().slice("summary").format(merged_summary=merged_summary)
|
||||
)
|
||||
if preserved_files:
|
||||
summary_message["files"] = preserved_files
|
||||
|
||||
@@ -10,7 +10,7 @@ from pydantic import BaseModel, ValidationError
|
||||
from typing_extensions import Unpack
|
||||
|
||||
from crewai.agents.agent_builder.utilities.base_output_converter import OutputConverter
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.internal_instructor import InternalInstructor
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
|
||||
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
|
||||
_JSON_PATTERN: Final[re.Pattern[str]] = re.compile(r"({.*})", re.DOTALL)
|
||||
_I18N = I18N_DEFAULT
|
||||
|
||||
|
||||
|
||||
class ConverterError(Exception):
|
||||
@@ -548,14 +548,14 @@ def get_conversion_instructions(
|
||||
):
|
||||
schema_dict = generate_model_description(model)
|
||||
schema = json.dumps(schema_dict, indent=2)
|
||||
formatted_task_instructions = _I18N.slice("formatted_task_instructions").format(
|
||||
formatted_task_instructions = get_crew_i18n().slice("formatted_task_instructions").format(
|
||||
output_format=schema
|
||||
)
|
||||
instructions += formatted_task_instructions
|
||||
else:
|
||||
model_description = generate_model_description(model)
|
||||
schema_json = json.dumps(model_description, indent=2)
|
||||
formatted_task_instructions = _I18N.slice("formatted_task_instructions").format(
|
||||
formatted_task_instructions = get_crew_i18n().slice("formatted_task_instructions").format(
|
||||
output_format=schema_json
|
||||
)
|
||||
instructions += formatted_task_instructions
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic import BaseModel, Field
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.task_events import TaskEvaluationEvent
|
||||
from crewai.utilities.converter import Converter
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
from crewai.utilities.training_converter import TrainingConverter
|
||||
|
||||
@@ -98,7 +98,7 @@ class TaskEvaluator:
|
||||
|
||||
if not self.llm.supports_function_calling(): # type: ignore[union-attr]
|
||||
schema_dict = generate_model_description(TaskEvaluation)
|
||||
output_schema: str = I18N_DEFAULT.slice(
|
||||
output_schema: str = get_crew_i18n().slice(
|
||||
"formatted_task_instructions"
|
||||
).format(output_format=json.dumps(schema_dict, indent=2))
|
||||
instructions = f"{instructions}\n\n{output_schema}"
|
||||
@@ -172,7 +172,7 @@ class TaskEvaluator:
|
||||
|
||||
if not self.llm.supports_function_calling(): # type: ignore[union-attr]
|
||||
schema_dict = generate_model_description(TrainingTaskEvaluation)
|
||||
output_schema: str = I18N_DEFAULT.slice(
|
||||
output_schema: str = get_crew_i18n().slice(
|
||||
"formatted_task_instructions"
|
||||
).format(output_format=json.dumps(schema_dict, indent=2))
|
||||
instructions = f"{instructions}\n\n{output_schema}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Internationalization support for CrewAI prompts and messages."""
|
||||
|
||||
import contextvars
|
||||
from functools import lru_cache
|
||||
import json
|
||||
import os
|
||||
@@ -145,3 +146,33 @@ def get_i18n(prompt_file: str | None = None) -> I18N:
|
||||
|
||||
|
||||
I18N_DEFAULT: I18N = get_i18n()
|
||||
|
||||
|
||||
_crew_i18n_var: contextvars.ContextVar[I18N | None] = contextvars.ContextVar(
|
||||
"_crew_i18n", default=None
|
||||
)
|
||||
|
||||
|
||||
def get_crew_i18n() -> I18N:
|
||||
"""Return the crew-scoped I18N override if set, otherwise ``I18N_DEFAULT``.
|
||||
|
||||
When a :class:`Crew` with a ``prompt_file`` is kicked off, the custom
|
||||
I18N instance is pushed into a :class:`contextvars.ContextVar` so that
|
||||
all code running during that crew execution automatically picks up the
|
||||
custom prompts.
|
||||
"""
|
||||
return _crew_i18n_var.get() or I18N_DEFAULT
|
||||
|
||||
|
||||
def set_crew_i18n(i18n: I18N | None) -> contextvars.Token[I18N | None]:
|
||||
"""Set (or clear) the crew-scoped I18N override.
|
||||
|
||||
Returns a token that can be passed to :func:`reset_crew_i18n` for
|
||||
proper cleanup.
|
||||
"""
|
||||
return _crew_i18n_var.set(i18n)
|
||||
|
||||
|
||||
def reset_crew_i18n(token: contextvars.Token[I18N | None]) -> None:
|
||||
"""Reset the crew-scoped I18N override to its previous value."""
|
||||
_crew_i18n_var.reset(token)
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
|
||||
|
||||
class StandardPromptResult(BaseModel):
|
||||
@@ -158,13 +158,13 @@ class Prompts(BaseModel):
|
||||
if not system_template or not prompt_template:
|
||||
# If any of the required templates are missing, fall back to the default format
|
||||
prompt_parts: list[str] = [
|
||||
I18N_DEFAULT.slice(component) for component in components
|
||||
get_crew_i18n().slice(component) for component in components
|
||||
]
|
||||
prompt = "".join(prompt_parts)
|
||||
else:
|
||||
# All templates are provided, use them
|
||||
template_parts: list[str] = [
|
||||
I18N_DEFAULT.slice(component)
|
||||
get_crew_i18n().slice(component)
|
||||
for component in components
|
||||
if component != "task"
|
||||
]
|
||||
@@ -172,7 +172,7 @@ class Prompts(BaseModel):
|
||||
"{{ .System }}", "".join(template_parts)
|
||||
)
|
||||
prompt = prompt_template.replace(
|
||||
"{{ .Prompt }}", "".join(I18N_DEFAULT.slice("task"))
|
||||
"{{ .Prompt }}", "".join(get_crew_i18n().slice("task"))
|
||||
)
|
||||
# Handle missing response_template
|
||||
if response_template:
|
||||
|
||||
@@ -15,7 +15,7 @@ from crewai.events.types.reasoning_events import (
|
||||
AgentReasoningStartedEvent,
|
||||
)
|
||||
from crewai.llm import LLM
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.planning_types import PlanStep
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
@@ -482,17 +482,17 @@ class AgentReasoning:
|
||||
"""Get the system prompt for planning.
|
||||
|
||||
Returns:
|
||||
The system prompt, either custom or from I18N_DEFAULT.
|
||||
The system prompt, either custom or from get_crew_i18n().
|
||||
"""
|
||||
if self.config.system_prompt is not None:
|
||||
return self.config.system_prompt
|
||||
|
||||
# Try new "planning" section first, fall back to "reasoning" for compatibility
|
||||
try:
|
||||
return I18N_DEFAULT.retrieve("planning", "system_prompt")
|
||||
return get_crew_i18n().retrieve("planning", "system_prompt")
|
||||
except (KeyError, AttributeError):
|
||||
# Fallback to reasoning section for backward compatibility
|
||||
return I18N_DEFAULT.retrieve("reasoning", "initial_plan").format(
|
||||
return get_crew_i18n().retrieve("reasoning", "initial_plan").format(
|
||||
role=self.agent.role,
|
||||
goal=self.agent.goal,
|
||||
backstory=self._get_agent_backstory(),
|
||||
@@ -528,7 +528,7 @@ class AgentReasoning:
|
||||
|
||||
# Try new "planning" section first
|
||||
try:
|
||||
return I18N_DEFAULT.retrieve("planning", "create_plan_prompt").format(
|
||||
return get_crew_i18n().retrieve("planning", "create_plan_prompt").format(
|
||||
description=self.description,
|
||||
expected_output=self.expected_output,
|
||||
tools=available_tools,
|
||||
@@ -536,7 +536,7 @@ class AgentReasoning:
|
||||
)
|
||||
except (KeyError, AttributeError):
|
||||
# Fallback to reasoning section for backward compatibility
|
||||
return I18N_DEFAULT.retrieve("reasoning", "create_plan_prompt").format(
|
||||
return get_crew_i18n().retrieve("reasoning", "create_plan_prompt").format(
|
||||
role=self.agent.role,
|
||||
goal=self.agent.goal,
|
||||
backstory=self._get_agent_backstory(),
|
||||
@@ -585,12 +585,12 @@ class AgentReasoning:
|
||||
|
||||
# Try new "planning" section first
|
||||
try:
|
||||
return I18N_DEFAULT.retrieve("planning", "refine_plan_prompt").format(
|
||||
return get_crew_i18n().retrieve("planning", "refine_plan_prompt").format(
|
||||
current_plan=current_plan,
|
||||
)
|
||||
except (KeyError, AttributeError):
|
||||
# Fallback to reasoning section for backward compatibility
|
||||
return I18N_DEFAULT.retrieve("reasoning", "refine_plan_prompt").format(
|
||||
return get_crew_i18n().retrieve("reasoning", "refine_plan_prompt").format(
|
||||
role=self.agent.role,
|
||||
goal=self.agent.goal,
|
||||
backstory=self._get_agent_backstory(),
|
||||
@@ -643,7 +643,7 @@ def _call_llm_with_reasoning_prompt(
|
||||
Returns:
|
||||
The LLM response.
|
||||
"""
|
||||
system_prompt = I18N_DEFAULT.retrieve("reasoning", plan_type).format(
|
||||
system_prompt = get_crew_i18n().retrieve("reasoning", plan_type).format(
|
||||
role=reasoning_agent.role,
|
||||
goal=reasoning_agent.goal,
|
||||
backstory=backstory,
|
||||
|
||||
@@ -13,7 +13,7 @@ from crewai.security.fingerprint import Fingerprint
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
from crewai.tools.tool_types import ToolResult
|
||||
from crewai.tools.tool_usage import ToolUsage, ToolUsageError
|
||||
from crewai.utilities.i18n import I18N_DEFAULT
|
||||
from crewai.utilities.i18n import get_crew_i18n
|
||||
from crewai.utilities.logger import Logger
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
@@ -140,7 +140,7 @@ async def aexecute_tool_and_check_finality(
|
||||
|
||||
return ToolResult(modified_result, tool.result_as_answer)
|
||||
|
||||
tool_result = I18N_DEFAULT.errors("wrong_tool_name").format(
|
||||
tool_result = get_crew_i18n().errors("wrong_tool_name").format(
|
||||
tool=sanitized_tool_name,
|
||||
tools=", ".join(tool_name_to_tool_map.keys()),
|
||||
)
|
||||
@@ -259,7 +259,7 @@ def execute_tool_and_check_finality(
|
||||
|
||||
return ToolResult(modified_result, tool.result_as_answer)
|
||||
|
||||
tool_result = I18N_DEFAULT.errors("wrong_tool_name").format(
|
||||
tool_result = get_crew_i18n().errors("wrong_tool_name").format(
|
||||
tool=sanitized_tool_name,
|
||||
tools=", ".join(tool_name_to_tool_map.keys()),
|
||||
)
|
||||
|
||||
101
lib/crewai/tests/utilities/custom_prompts.json
Normal file
101
lib/crewai/tests/utilities/custom_prompts.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"hierarchical_manager_agent": {
|
||||
"role": "CUSTOM_role",
|
||||
"goal": "CUSTOM_goal",
|
||||
"backstory": "CUSTOM_backstory"
|
||||
},
|
||||
"slices": {
|
||||
"observation": "CUSTOM_observation",
|
||||
"task": "CUSTOM_task",
|
||||
"memory": "CUSTOM_memory",
|
||||
"role_playing": "CUSTOM_role_playing",
|
||||
"tools": "CUSTOM_tools",
|
||||
"no_tools": "CUSTOM_no_tools",
|
||||
"task_no_tools": "CUSTOM_task_no_tools",
|
||||
"native_tools": "CUSTOM_native_tools",
|
||||
"native_task": "CUSTOM_native_task",
|
||||
"post_tool_reasoning": "CUSTOM_post_tool_reasoning",
|
||||
"format": "CUSTOM_format",
|
||||
"final_answer_format": "CUSTOM_final_answer_format",
|
||||
"format_without_tools": "CUSTOM_format_without_tools",
|
||||
"task_with_context": "CUSTOM_task_with_context",
|
||||
"expected_output": "CUSTOM_expected_output",
|
||||
"human_feedback": "CUSTOM_human_feedback",
|
||||
"getting_input": "CUSTOM_getting_input",
|
||||
"summarizer_system_message": "CUSTOM_summarizer_system_message",
|
||||
"summarize_instruction": "CUSTOM_summarize_instruction",
|
||||
"summary": "CUSTOM_summary",
|
||||
"manager_request": "CUSTOM_manager_request",
|
||||
"formatted_task_instructions": "CUSTOM_formatted_task_instructions",
|
||||
"conversation_history_instruction": "CUSTOM_conversation_history_instruction",
|
||||
"feedback_instructions": "CUSTOM_feedback_instructions",
|
||||
"lite_agent_system_prompt_with_tools": "CUSTOM_lite_agent_system_prompt_with_tools",
|
||||
"lite_agent_system_prompt_without_tools": "CUSTOM_lite_agent_system_prompt_without_tools",
|
||||
"lite_agent_response_format": "CUSTOM_lite_agent_response_format",
|
||||
"knowledge_search_query": "CUSTOM_knowledge_search_query",
|
||||
"knowledge_search_query_system_prompt": "CUSTOM_knowledge_search_query_system_prompt",
|
||||
"human_feedback_collapse": "CUSTOM_human_feedback_collapse",
|
||||
"hitl_pre_review_system": "CUSTOM_hitl_pre_review_system",
|
||||
"hitl_pre_review_user": "CUSTOM_hitl_pre_review_user",
|
||||
"hitl_distill_system": "CUSTOM_hitl_distill_system",
|
||||
"hitl_distill_user": "CUSTOM_hitl_distill_user"
|
||||
},
|
||||
"errors": {
|
||||
"force_final_answer_error": "CUSTOM_force_final_answer_error",
|
||||
"force_final_answer": "CUSTOM_force_final_answer",
|
||||
"agent_tool_unexisting_coworker": "CUSTOM_agent_tool_unexisting_coworker",
|
||||
"task_repeated_usage": "CUSTOM_task_repeated_usage",
|
||||
"tool_usage_error": "CUSTOM_tool_usage_error",
|
||||
"tool_arguments_error": "CUSTOM_tool_arguments_error",
|
||||
"wrong_tool_name": "CUSTOM_wrong_tool_name",
|
||||
"tool_usage_exception": "CUSTOM_tool_usage_exception",
|
||||
"agent_tool_execution_error": "CUSTOM_agent_tool_execution_error",
|
||||
"validation_error": "CUSTOM_validation_error"
|
||||
},
|
||||
"tools": {
|
||||
"delegate_work": "CUSTOM_delegate_work",
|
||||
"ask_question": "CUSTOM_ask_question",
|
||||
"add_image": {
|
||||
"name": "CUSTOM_name",
|
||||
"description": "CUSTOM_description",
|
||||
"default_action": "CUSTOM_default_action"
|
||||
},
|
||||
"recall_memory": "CUSTOM_recall_memory",
|
||||
"save_to_memory": "CUSTOM_save_to_memory"
|
||||
},
|
||||
"memory": {
|
||||
"query_system": "CUSTOM_query_system",
|
||||
"extract_memories_system": "CUSTOM_extract_memories_system",
|
||||
"extract_memories_user": "CUSTOM_extract_memories_user",
|
||||
"query_user": "CUSTOM_query_user",
|
||||
"save_system": "CUSTOM_save_system",
|
||||
"save_user": "CUSTOM_save_user",
|
||||
"consolidation_system": "CUSTOM_consolidation_system",
|
||||
"consolidation_user": "CUSTOM_consolidation_user"
|
||||
},
|
||||
"reasoning": {
|
||||
"initial_plan": "CUSTOM_initial_plan",
|
||||
"refine_plan": "CUSTOM_refine_plan",
|
||||
"create_plan_prompt": "CUSTOM_create_plan_prompt",
|
||||
"refine_plan_prompt": "CUSTOM_refine_plan_prompt"
|
||||
},
|
||||
"planning": {
|
||||
"system_prompt": "CUSTOM_system_prompt",
|
||||
"create_plan_prompt": "CUSTOM_create_plan_prompt",
|
||||
"refine_plan_prompt": "CUSTOM_refine_plan_prompt",
|
||||
"observation_system_prompt": "CUSTOM_observation_system_prompt",
|
||||
"observation_user_prompt": "CUSTOM_observation_user_prompt",
|
||||
"step_executor_system_prompt": "CUSTOM_step_executor_system_prompt",
|
||||
"step_executor_tools_section": "CUSTOM_step_executor_tools_section",
|
||||
"step_executor_user_prompt": "CUSTOM_step_executor_user_prompt",
|
||||
"step_executor_suggested_tool": "CUSTOM_step_executor_suggested_tool",
|
||||
"step_executor_context_header": "CUSTOM_step_executor_context_header",
|
||||
"step_executor_context_entry": "CUSTOM_step_executor_context_entry",
|
||||
"step_executor_complete_step": "CUSTOM_step_executor_complete_step",
|
||||
"todo_system_prompt": "CUSTOM_todo_system_prompt",
|
||||
"synthesis_system_prompt": "CUSTOM_synthesis_system_prompt",
|
||||
"synthesis_user_prompt": "CUSTOM_synthesis_user_prompt",
|
||||
"replan_enhancement_prompt": "CUSTOM_replan_enhancement_prompt",
|
||||
"step_executor_task_context": "CUSTOM_step_executor_task_context"
|
||||
}
|
||||
}
|
||||
293
lib/crewai/tests/utilities/test_prompt_file_propagation.py
Normal file
293
lib/crewai/tests/utilities/test_prompt_file_propagation.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""Tests for Crew.prompt_file propagation to all components.
|
||||
|
||||
Verifies fix for https://github.com/crewAIInc/crewAI/issues/5931
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai.utilities.i18n import (
|
||||
I18N,
|
||||
I18N_DEFAULT,
|
||||
get_crew_i18n,
|
||||
get_i18n,
|
||||
reset_crew_i18n,
|
||||
set_crew_i18n,
|
||||
)
|
||||
|
||||
CUSTOM_PROMPTS = os.path.join(os.path.dirname(__file__), "custom_prompts.json")
|
||||
|
||||
|
||||
class TestCrewI18nContextVar:
|
||||
"""Test the context variable mechanism for crew-scoped I18N."""
|
||||
|
||||
def test_get_crew_i18n_returns_default_when_no_override(self):
|
||||
assert get_crew_i18n() is I18N_DEFAULT
|
||||
|
||||
def test_set_and_get_crew_i18n(self):
|
||||
custom = get_i18n(CUSTOM_PROMPTS)
|
||||
token = set_crew_i18n(custom)
|
||||
try:
|
||||
assert get_crew_i18n() is custom
|
||||
assert get_crew_i18n().slice("role_playing") == "CUSTOM_role_playing"
|
||||
finally:
|
||||
reset_crew_i18n(token)
|
||||
|
||||
def test_reset_crew_i18n_restores_default(self):
|
||||
custom = get_i18n(CUSTOM_PROMPTS)
|
||||
token = set_crew_i18n(custom)
|
||||
reset_crew_i18n(token)
|
||||
assert get_crew_i18n() is I18N_DEFAULT
|
||||
|
||||
def test_nested_set_and_reset(self):
|
||||
custom1 = I18N(prompt_file=CUSTOM_PROMPTS)
|
||||
custom2 = I18N(prompt_file=CUSTOM_PROMPTS)
|
||||
|
||||
token1 = set_crew_i18n(custom1)
|
||||
assert get_crew_i18n() is custom1
|
||||
|
||||
token2 = set_crew_i18n(custom2)
|
||||
assert get_crew_i18n() is custom2
|
||||
|
||||
reset_crew_i18n(token2)
|
||||
assert get_crew_i18n() is custom1
|
||||
|
||||
reset_crew_i18n(token1)
|
||||
assert get_crew_i18n() is I18N_DEFAULT
|
||||
|
||||
|
||||
class TestPromptsUsesCrewI18n:
|
||||
"""Test that the Prompts class picks up the crew-scoped I18N."""
|
||||
|
||||
def test_prompts_build_prompt_uses_crew_i18n(self):
|
||||
from crewai.utilities.prompts import Prompts
|
||||
|
||||
agent = MagicMock()
|
||||
agent.role = "TestRole"
|
||||
agent.goal = "TestGoal"
|
||||
agent.backstory = "TestBackstory"
|
||||
agent.skills = []
|
||||
|
||||
custom = get_i18n(CUSTOM_PROMPTS)
|
||||
token = set_crew_i18n(custom)
|
||||
try:
|
||||
prompts = Prompts(agent=agent, has_tools=False)
|
||||
result = prompts.task_execution()
|
||||
assert "CUSTOM_role_playing" in result.prompt
|
||||
assert "CUSTOM_no_tools" in result.prompt
|
||||
finally:
|
||||
reset_crew_i18n(token)
|
||||
|
||||
def test_prompts_uses_default_when_no_override(self):
|
||||
from crewai.utilities.prompts import Prompts
|
||||
|
||||
agent = MagicMock()
|
||||
agent.role = "TestRole"
|
||||
agent.goal = "TestGoal"
|
||||
agent.backstory = "TestBackstory"
|
||||
agent.skills = []
|
||||
|
||||
prompts = Prompts(agent=agent, has_tools=False)
|
||||
result = prompts.task_execution()
|
||||
# Should use default en.json prompts, not custom ones
|
||||
assert "CUSTOM_" not in result.prompt
|
||||
assert "TestRole" in result.prompt
|
||||
|
||||
|
||||
class TestTaskPromptUsesCrewI18n:
|
||||
"""Test that Task.prompt() picks up the crew-scoped I18N."""
|
||||
|
||||
def test_task_prompt_uses_crew_i18n(self):
|
||||
from crewai.task import Task
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
)
|
||||
|
||||
custom = get_i18n(CUSTOM_PROMPTS)
|
||||
token = set_crew_i18n(custom)
|
||||
try:
|
||||
prompt = task.prompt()
|
||||
assert "CUSTOM_expected_output" in prompt
|
||||
finally:
|
||||
reset_crew_i18n(token)
|
||||
|
||||
def test_task_prompt_uses_default_when_no_override(self):
|
||||
from crewai.task import Task
|
||||
|
||||
task = Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
)
|
||||
prompt = task.prompt()
|
||||
assert "CUSTOM_" not in prompt
|
||||
assert "Test output" in prompt
|
||||
|
||||
|
||||
class TestAgentToolsUseCrewI18n:
|
||||
"""Test that delegation tools pick up the crew-scoped I18N."""
|
||||
|
||||
def test_agent_tools_use_crew_i18n(self):
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
|
||||
agent = Agent(
|
||||
role="TestAgent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory",
|
||||
llm="gpt-4o",
|
||||
)
|
||||
|
||||
custom = get_i18n(CUSTOM_PROMPTS)
|
||||
token = set_crew_i18n(custom)
|
||||
try:
|
||||
tools = AgentTools(agents=[agent]).tools()
|
||||
descriptions = [t.description for t in tools]
|
||||
assert any("CUSTOM_delegate_work" in d for d in descriptions)
|
||||
assert any("CUSTOM_ask_question" in d for d in descriptions)
|
||||
finally:
|
||||
reset_crew_i18n(token)
|
||||
|
||||
|
||||
class TestCrewKickoffSetsI18nContext:
|
||||
"""Test that Crew.kickoff() properly sets and resets the I18N context."""
|
||||
|
||||
@patch("crewai.crew.Crew._run_sequential_process")
|
||||
@patch("crewai.crews.utils.setup_agents")
|
||||
@patch("crewai.crew.Crew.calculate_usage_metrics")
|
||||
def test_kickoff_sets_i18n_for_custom_prompt_file(
|
||||
self, mock_metrics, mock_setup, mock_seq
|
||||
):
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.crews.crew_output import CrewOutput
|
||||
from crewai.task import Task
|
||||
|
||||
mock_metrics.return_value = MagicMock()
|
||||
|
||||
from crewai.types.usage_metrics import UsageMetrics
|
||||
|
||||
captured_i18n = []
|
||||
|
||||
def capture_i18n(*args, **kwargs):
|
||||
captured_i18n.append(get_crew_i18n())
|
||||
return CrewOutput(
|
||||
raw="done",
|
||||
tasks_output=[],
|
||||
json_dict=None,
|
||||
pydantic=None,
|
||||
token_usage=UsageMetrics(),
|
||||
)
|
||||
|
||||
mock_seq.side_effect = capture_i18n
|
||||
|
||||
agent = Agent(
|
||||
role="Researcher",
|
||||
goal="Research stuff",
|
||||
backstory="Expert researcher",
|
||||
llm="gpt-4o",
|
||||
)
|
||||
task = Task(
|
||||
description="Do research",
|
||||
expected_output="A report",
|
||||
agent=agent,
|
||||
)
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
prompt_file=CUSTOM_PROMPTS,
|
||||
)
|
||||
|
||||
crew.kickoff()
|
||||
|
||||
assert len(captured_i18n) == 1
|
||||
assert captured_i18n[0].slice("role_playing") == "CUSTOM_role_playing"
|
||||
# After kickoff, context should be reset
|
||||
assert get_crew_i18n() is I18N_DEFAULT
|
||||
|
||||
@patch("crewai.crew.Crew._run_sequential_process")
|
||||
@patch("crewai.crews.utils.setup_agents")
|
||||
@patch("crewai.crew.Crew.calculate_usage_metrics")
|
||||
def test_kickoff_resets_i18n_on_exception(
|
||||
self, mock_metrics, mock_setup, mock_seq
|
||||
):
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.task import Task
|
||||
|
||||
mock_seq.side_effect = RuntimeError("boom")
|
||||
|
||||
agent = Agent(
|
||||
role="Researcher",
|
||||
goal="Research stuff",
|
||||
backstory="Expert researcher",
|
||||
llm="gpt-4o",
|
||||
)
|
||||
task = Task(
|
||||
description="Do research",
|
||||
expected_output="A report",
|
||||
agent=agent,
|
||||
)
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
prompt_file=CUSTOM_PROMPTS,
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
crew.kickoff()
|
||||
|
||||
# Context must be reset even on exception
|
||||
assert get_crew_i18n() is I18N_DEFAULT
|
||||
|
||||
@patch("crewai.crew.Crew._run_sequential_process")
|
||||
@patch("crewai.crews.utils.setup_agents")
|
||||
@patch("crewai.crew.Crew.calculate_usage_metrics")
|
||||
def test_kickoff_without_prompt_file_uses_default(
|
||||
self, mock_metrics, mock_setup, mock_seq
|
||||
):
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.crew import Crew
|
||||
from crewai.crews.crew_output import CrewOutput
|
||||
from crewai.task import Task
|
||||
from crewai.types.usage_metrics import UsageMetrics
|
||||
|
||||
mock_metrics.return_value = MagicMock()
|
||||
|
||||
captured_i18n = []
|
||||
|
||||
def capture_i18n(*args, **kwargs):
|
||||
captured_i18n.append(get_crew_i18n())
|
||||
return CrewOutput(
|
||||
raw="done",
|
||||
tasks_output=[],
|
||||
json_dict=None,
|
||||
pydantic=None,
|
||||
token_usage=UsageMetrics(),
|
||||
)
|
||||
|
||||
mock_seq.side_effect = capture_i18n
|
||||
|
||||
agent = Agent(
|
||||
role="Researcher",
|
||||
goal="Research stuff",
|
||||
backstory="Expert researcher",
|
||||
llm="gpt-4o",
|
||||
)
|
||||
task = Task(
|
||||
description="Do research",
|
||||
expected_output="A report",
|
||||
agent=agent,
|
||||
)
|
||||
crew = Crew(
|
||||
agents=[agent],
|
||||
tasks=[task],
|
||||
)
|
||||
|
||||
crew.kickoff()
|
||||
|
||||
assert len(captured_i18n) == 1
|
||||
assert captured_i18n[0] is I18N_DEFAULT
|
||||
Reference in New Issue
Block a user