From 8dfb32d5f0324c45aecb3ce713b312d2cc52c2da Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 9 Apr 2026 03:13:39 +0800 Subject: [PATCH] refactor: use shared I18N_DEFAULT singleton --- lib/crewai/src/crewai/agent/core.py | 20 ++++----- lib/crewai/src/crewai/agent/utils.py | 27 +++++++----- .../langgraph/langgraph_adapter.py | 3 +- .../openai_agents/openai_adapter.py | 3 +- .../structured_output_converter.py | 8 ++-- .../crewai/agents/agent_builder/base_agent.py | 6 +-- .../agent_builder/base_agent_executor.py | 2 - .../src/crewai/agents/crew_agent_executor.py | 23 +++------- lib/crewai/src/crewai/agents/parser.py | 5 +-- .../src/crewai/agents/planner_observer.py | 9 ++-- lib/crewai/src/crewai/agents/step_executor.py | 25 +++++------ .../src/crewai/experimental/agent_executor.py | 21 +++------ lib/crewai/src/crewai/flow/flow.py | 6 +-- lib/crewai/src/crewai/flow/human_feedback.py | 4 +- lib/crewai/src/crewai/lite_agent.py | 20 ++++----- lib/crewai/src/crewai/memory/analyze.py | 4 +- lib/crewai/src/crewai/task.py | 11 +++-- lib/crewai/src/crewai/telemetry/telemetry.py | 5 ++- .../tools/agent_tools/add_image_tool.py | 11 ++--- .../crewai/tools/agent_tools/agent_tools.py | 12 ++--- .../tools/agent_tools/base_agent_tools.py | 14 +++--- lib/crewai/src/crewai/tools/memory_tools.py | 7 ++- lib/crewai/src/crewai/tools/tool_usage.py | 27 ++++++------ .../src/crewai/utilities/agent_utils.py | 30 +++++-------- lib/crewai/src/crewai/utilities/converter.py | 4 +- .../utilities/evaluators/task_evaluator.py | 18 +++----- lib/crewai/src/crewai/utilities/i18n.py | 3 ++ lib/crewai/src/crewai/utilities/prompts.py | 9 ++-- .../src/crewai/utilities/reasoning_handler.py | 17 +++---- lib/crewai/src/crewai/utilities/tool_utils.py | 10 ++--- lib/crewai/tests/agents/test_agent.py | 10 ++--- .../tests/agents/test_agent_executor.py | 2 - lib/crewai/tests/tools/test_tool_usage.py | 14 ------ .../tests/utilities/test_agent_utils.py | 44 ++++++------------- .../utilities/test_structured_planning.py | 2 - .../utilities/test_summarize_integration.py | 21 +++++---- 36 files changed, 179 insertions(+), 278 deletions(-) diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index c86d7112c..eb73ba719 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -98,6 +98,7 @@ from crewai.utilities.converter import Converter, ConverterError from crewai.utilities.env import get_env_context from crewai.utilities.guardrail import process_guardrail from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType +from crewai.utilities.i18n import I18N_DEFAULT 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 @@ -499,8 +500,8 @@ class Agent(BaseAgent): self.tools_handler.last_used_tool = None task_prompt = task.prompt() - task_prompt = build_task_prompt_with_schema(task, task_prompt, self.i18n) - task_prompt = format_task_with_context(task_prompt, context, self.i18n) + task_prompt = build_task_prompt_with_schema(task, task_prompt) + task_prompt = format_task_with_context(task_prompt, context) return self._retrieve_memory_context(task, task_prompt) def _finalize_task_prompt( @@ -562,7 +563,7 @@ class Agent(BaseAgent): m.format() for m in matches ) if memory.strip() != "": - task_prompt += self.i18n.slice("memory").format(memory=memory) + task_prompt += I18N_DEFAULT.slice("memory").format(memory=memory) crewai_event_bus.emit( self, @@ -968,14 +969,13 @@ class Agent(BaseAgent): agent=self, has_tools=len(raw_tools) > 0, use_native_tool_calling=use_native_tool_calling, - i18n=self.i18n, use_system_prompt=self.use_system_prompt, system_template=self.system_template, prompt_template=self.prompt_template, response_template=self.response_template, ).task_execution() - stop_words = [self.i18n.slice("observation")] + stop_words = [I18N_DEFAULT.slice("observation")] if self.response_template: stop_words.append( self.response_template.split("{{ .Response }}")[1].strip() @@ -1017,7 +1017,6 @@ class Agent(BaseAgent): self.agent_executor = self.executor_class( llm=self.llm, task=task, - i18n=self.i18n, agent=self, crew=self.crew, tools=parsed_tools, @@ -1262,10 +1261,10 @@ class Agent(BaseAgent): from_agent=self, ), ) - query = self.i18n.slice("knowledge_search_query").format( + query = I18N_DEFAULT.slice("knowledge_search_query").format( task_prompt=task_prompt ) - rewriter_prompt = self.i18n.slice("knowledge_search_query_system_prompt") + rewriter_prompt = I18N_DEFAULT.slice("knowledge_search_query_system_prompt") if not isinstance(self.llm, BaseLLM): self._logger.log( "warning", @@ -1384,7 +1383,6 @@ class Agent(BaseAgent): request_within_rpm_limit=rpm_limit_fn, callbacks=[TokenCalcHandler(self._token_process)], response_model=response_format, - i18n=self.i18n, ) all_files: dict[str, Any] = {} @@ -1420,7 +1418,7 @@ class Agent(BaseAgent): m.format() for m in matches ) if memory_block: - formatted_messages += "\n\n" + self.i18n.slice("memory").format( + formatted_messages += "\n\n" + I18N_DEFAULT.slice("memory").format( memory=memory_block ) crewai_event_bus.emit( @@ -1624,7 +1622,7 @@ class Agent(BaseAgent): try: model_schema = generate_model_description(response_format) schema = json.dumps(model_schema, indent=2) - instructions = self.i18n.slice("formatted_task_instructions").format( + instructions = I18N_DEFAULT.slice("formatted_task_instructions").format( output_format=schema ) diff --git a/lib/crewai/src/crewai/agent/utils.py b/lib/crewai/src/crewai/agent/utils.py index 8690c8faf..31f08903f 100644 --- a/lib/crewai/src/crewai/agent/utils.py +++ b/lib/crewai/src/crewai/agent/utils.py @@ -24,7 +24,6 @@ if TYPE_CHECKING: from crewai.agent.core import Agent from crewai.task import Task from crewai.tools.base_tool import BaseTool - from crewai.utilities.i18n import I18N def handle_reasoning(agent: Agent, task: Task) -> None: @@ -59,46 +58,50 @@ def handle_reasoning(agent: Agent, task: Task) -> None: agent._logger.log("error", f"Error during planning: {e!s}") -def build_task_prompt_with_schema(task: Task, task_prompt: str, i18n: I18N) -> str: +def build_task_prompt_with_schema(task: Task, task_prompt: str) -> str: """Build task prompt with JSON/Pydantic schema instructions if applicable. Args: task: The task being executed. task_prompt: The initial task prompt. - i18n: Internationalization instance. Returns: The task prompt potentially augmented with schema instructions. """ + from crewai.utilities.i18n import I18N_DEFAULT + 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.slice("formatted_task_instructions").format( - output_format=schema - ) + task_prompt += "\n" + I18N_DEFAULT.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.slice("formatted_task_instructions").format( - output_format=schema - ) + task_prompt += "\n" + I18N_DEFAULT.slice( + "formatted_task_instructions" + ).format(output_format=schema) return task_prompt -def format_task_with_context(task_prompt: str, context: str | None, i18n: I18N) -> str: +def format_task_with_context(task_prompt: str, context: str | None) -> str: """Format task prompt with context if provided. Args: task_prompt: The task prompt. context: Optional context string. - i18n: Internationalization instance. Returns: The task prompt formatted with context if provided. """ + from crewai.utilities.i18n import I18N_DEFAULT + if context: - return i18n.slice("task_with_context").format(task=task_prompt, context=context) + return I18N_DEFAULT.slice("task_with_context").format( + task=task_prompt, context=context + ) return task_prompt diff --git a/lib/crewai/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py b/lib/crewai/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py index 1710b56cb..33a705728 100644 --- a/lib/crewai/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py +++ b/lib/crewai/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py @@ -33,6 +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.import_utils import require @@ -186,7 +187,7 @@ class LangGraphAgentAdapter(BaseAgentAdapter): task_prompt = task.prompt() if hasattr(task, "prompt") else str(task) if context: - task_prompt = self.i18n.slice("task_with_context").format( + task_prompt = I18N_DEFAULT.slice("task_with_context").format( task=task_prompt, context=context ) diff --git a/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/openai_adapter.py b/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/openai_adapter.py index 82eb8640b..169d65af5 100644 --- a/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/openai_adapter.py +++ b/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/openai_adapter.py @@ -32,6 +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.import_utils import require @@ -133,7 +134,7 @@ class OpenAIAgentAdapter(BaseAgentAdapter): try: task_prompt: str = task.prompt() if context: - task_prompt = self.i18n.slice("task_with_context").format( + task_prompt = I18N_DEFAULT.slice("task_with_context").format( task=task_prompt, context=context ) crewai_event_bus.emit( diff --git a/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/structured_output_converter.py b/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/structured_output_converter.py index 4033c8d50..358281cac 100644 --- a/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/structured_output_converter.py +++ b/lib/crewai/src/crewai/agents/agent_adapters/openai_agents/structured_output_converter.py @@ -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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT class OpenAIConverterAdapter(BaseConverterAdapter): @@ -59,10 +59,8 @@ class OpenAIConverterAdapter(BaseConverterAdapter): if not self._output_format: return base_prompt - output_schema: str = ( - get_i18n() - .slice("formatted_task_instructions") - .format(output_format=json.dumps(self._schema, indent=2)) + output_schema: str = I18N_DEFAULT.slice("formatted_task_instructions").format( + output_format=json.dumps(self._schema, indent=2) ) return f"{base_prompt}\n\n{output_schema}" diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py index de9379d09..c55f5e417 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py @@ -43,7 +43,6 @@ from crewai.state.checkpoint_config import CheckpointConfig, _coerce_checkpoint from crewai.tools.base_tool import BaseTool, Tool from crewai.types.callback import SerializableCallable from crewai.utilities.config import process_config -from crewai.utilities.i18n import I18N, get_i18n from crewai.utilities.logger import Logger from crewai.utilities.rpm_controller import RPMController from crewai.utilities.string_utils import interpolate_only @@ -179,7 +178,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta): agent_executor: An instance of the CrewAgentExecutor class. llm (Any): Language model that will run the agent. crew (Any): Crew to which the agent belongs. - i18n (I18N): Internationalization settings. + cache_handler ([CacheHandler]): An instance of the CacheHandler class. tools_handler ([ToolsHandler]): An instance of the ToolsHandler class. max_tokens: Maximum number of tokens for the agent to generate in a response. @@ -269,9 +268,6 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta): _serialize_crew_ref, return_type=str | None, when_used="always" ), ] = Field(default=None, description="Crew to which the agent belongs.") - i18n: I18N = Field( - default_factory=get_i18n, description="Internationalization settings." - ) cache_handler: CacheHandler | None = Field( default=None, description="An instance of the CacheHandler class." ) diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor.py index a44b81fc3..d251b1d36 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor.py @@ -14,7 +14,6 @@ if TYPE_CHECKING: from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.crew import Crew from crewai.task import Task - from crewai.utilities.i18n import I18N class BaseAgentExecutor(BaseModel): @@ -28,7 +27,6 @@ class BaseAgentExecutor(BaseModel): max_iter: int = Field(default=25) messages: list[LLMMessage] = Field(default_factory=list) _resuming: bool = PrivateAttr(default=False) - _i18n: I18N | None = PrivateAttr(default=None) def _save_to_memory(self, output: AgentFinish) -> None: """Save task result to unified memory (memory or crew._memory).""" diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 6307d5b9c..909fa5c66 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -67,7 +67,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, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.printer import PRINTER from crewai.utilities.string_utils import sanitize_tool_name from crewai.utilities.token_counter_callback import TokenCalcHandler @@ -135,9 +135,8 @@ class CrewAgentExecutor(BaseAgentExecutor): model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True) - def __init__(self, i18n: I18N | None = None, **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._i18n = i18n or get_i18n() if not self.before_llm_call_hooks: self.before_llm_call_hooks.extend(get_before_llm_call_hooks()) if not self.after_llm_call_hooks: @@ -328,7 +327,6 @@ class CrewAgentExecutor(BaseAgentExecutor): formatted_answer = handle_max_iterations_exceeded( formatted_answer, printer=PRINTER, - i18n=self._i18n, messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, @@ -401,7 +399,6 @@ class CrewAgentExecutor(BaseAgentExecutor): agent_action=formatted_answer, fingerprint_context=fingerprint_context, tools=self.tools, - i18n=self._i18n, agent_key=self.agent.key if self.agent else None, agent_role=self.agent.role if self.agent else None, tools_handler=self.tools_handler, @@ -438,7 +435,6 @@ class CrewAgentExecutor(BaseAgentExecutor): messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, - i18n=self._i18n, verbose=self.agent.verbose, ) continue @@ -484,7 +480,6 @@ class CrewAgentExecutor(BaseAgentExecutor): formatted_answer = handle_max_iterations_exceeded( None, printer=PRINTER, - i18n=self._i18n, messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, @@ -575,7 +570,6 @@ class CrewAgentExecutor(BaseAgentExecutor): messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, - i18n=self._i18n, verbose=self.agent.verbose, ) continue @@ -771,7 +765,7 @@ class CrewAgentExecutor(BaseAgentExecutor): if tool_finish: return tool_finish - reasoning_prompt = self._i18n.slice("post_tool_reasoning") + reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning") reasoning_message: LLMMessage = { "role": "user", "content": reasoning_prompt, @@ -795,7 +789,7 @@ class CrewAgentExecutor(BaseAgentExecutor): if tool_finish: return tool_finish - reasoning_prompt = self._i18n.slice("post_tool_reasoning") + reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning") reasoning_message = { "role": "user", "content": reasoning_prompt, @@ -1170,7 +1164,6 @@ class CrewAgentExecutor(BaseAgentExecutor): formatted_answer = handle_max_iterations_exceeded( formatted_answer, printer=PRINTER, - i18n=self._i18n, messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, @@ -1242,7 +1235,6 @@ class CrewAgentExecutor(BaseAgentExecutor): agent_action=formatted_answer, fingerprint_context=fingerprint_context, tools=self.tools, - i18n=self._i18n, agent_key=self.agent.key if self.agent else None, agent_role=self.agent.role if self.agent else None, tools_handler=self.tools_handler, @@ -1278,7 +1270,6 @@ class CrewAgentExecutor(BaseAgentExecutor): messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, - i18n=self._i18n, verbose=self.agent.verbose, ) continue @@ -1318,7 +1309,6 @@ class CrewAgentExecutor(BaseAgentExecutor): formatted_answer = handle_max_iterations_exceeded( None, printer=PRINTER, - i18n=self._i18n, messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, @@ -1408,7 +1398,6 @@ class CrewAgentExecutor(BaseAgentExecutor): messages=self.messages, llm=cast("BaseLLM", self.llm), callbacks=self.callbacks, - i18n=self._i18n, verbose=self.agent.verbose, ) continue @@ -1467,7 +1456,7 @@ class CrewAgentExecutor(BaseAgentExecutor): Updated action or final answer. """ # Special case for add_image_tool - add_image_tool = self._i18n.tools("add_image") + add_image_tool = I18N_DEFAULT.tools("add_image") if ( isinstance(add_image_tool, dict) and formatted_answer.tool.casefold().strip() @@ -1673,5 +1662,5 @@ class CrewAgentExecutor(BaseAgentExecutor): Formatted message dict. """ return format_message_for_llm( - self._i18n.slice("feedback_instructions").format(feedback=feedback) + I18N_DEFAULT.slice("feedback_instructions").format(feedback=feedback) ) diff --git a/lib/crewai/src/crewai/agents/parser.py b/lib/crewai/src/crewai/agents/parser.py index 365443b45..bfe3a28b5 100644 --- a/lib/crewai/src/crewai/agents/parser.py +++ b/lib/crewai/src/crewai/agents/parser.py @@ -19,10 +19,7 @@ from crewai.agents.constants import ( MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE, UNABLE_TO_REPAIR_JSON_RESULTS, ) -from crewai.utilities.i18n import get_i18n - - -_I18N = get_i18n() +from crewai.utilities.i18n import I18N_DEFAULT as _I18N @dataclass diff --git a/lib/crewai/src/crewai/agents/planner_observer.py b/lib/crewai/src/crewai/agents/planner_observer.py index 16d1a747e..0705d3013 100644 --- a/lib/crewai/src/crewai/agents/planner_observer.py +++ b/lib/crewai/src/crewai/agents/planner_observer.py @@ -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, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.llm_utils import create_llm from crewai.utilities.planning_types import StepObservation, TodoItem from crewai.utilities.types import LLMMessage @@ -64,7 +64,6 @@ class PlannerObserver: self.task = task self.kickoff_input = kickoff_input self.llm = self._resolve_llm() - self._i18n: I18N = get_i18n() def _resolve_llm(self) -> Any: """Resolve which LLM to use for observation/planning. @@ -246,7 +245,7 @@ class PlannerObserver: task_desc = extract_task_section(self.kickoff_input) task_goal = "Complete the task successfully" - system_prompt = self._i18n.retrieve("planning", "observation_system_prompt") + system_prompt = I18N_DEFAULT.retrieve("planning", "observation_system_prompt") # Build context of what's been done completed_summary = "" @@ -273,7 +272,9 @@ class PlannerObserver: remaining_lines ) - user_prompt = self._i18n.retrieve("planning", "observation_user_prompt").format( + user_prompt = I18N_DEFAULT.retrieve( + "planning", "observation_user_prompt" + ).format( task_description=task_desc, task_goal=task_goal, completed_summary=completed_summary, diff --git a/lib/crewai/src/crewai/agents/step_executor.py b/lib/crewai/src/crewai/agents/step_executor.py index 48592efb4..3348692b6 100644 --- a/lib/crewai/src/crewai/agents/step_executor.py +++ b/lib/crewai/src/crewai/agents/step_executor.py @@ -38,7 +38,7 @@ from crewai.utilities.agent_utils import ( process_llm_response, setup_native_tools, ) -from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.planning_types import TodoItem from crewai.utilities.printer import PRINTER from crewai.utilities.step_execution_context import StepExecutionContext, StepResult @@ -81,7 +81,7 @@ class StepExecutor: function_calling_llm: Optional separate LLM for function calling. request_within_rpm_limit: Optional RPM limit function. callbacks: Optional list of callbacks. - i18n: Optional i18n instance. + """ def __init__( @@ -96,7 +96,6 @@ class StepExecutor: function_calling_llm: BaseLLM | None = None, request_within_rpm_limit: Callable[[], bool] | None = None, callbacks: list[Any] | None = None, - i18n: I18N | None = None, ) -> None: self.llm = llm self.tools = tools @@ -108,7 +107,6 @@ class StepExecutor: self.function_calling_llm = function_calling_llm self.request_within_rpm_limit = request_within_rpm_limit self.callbacks = callbacks or [] - self._i18n: I18N = i18n or get_i18n() # Native tool support — set up once self._use_native_tools = check_native_tool_support( @@ -221,14 +219,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 = self._i18n.retrieve( + tools_section = I18N_DEFAULT.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 self._i18n.retrieve("planning", "step_executor_system_prompt").format( + return I18N_DEFAULT.retrieve("planning", "step_executor_system_prompt").format( role=role, backstory=backstory, goal=goal, @@ -247,7 +245,7 @@ class StepExecutor: task_section = extract_task_section(context.task_description) if task_section: parts.append( - self._i18n.retrieve( + I18N_DEFAULT.retrieve( "planning", "step_executor_task_context" ).format( task_context=task_section, @@ -255,14 +253,16 @@ class StepExecutor: ) parts.append( - self._i18n.retrieve("planning", "step_executor_user_prompt").format( + I18N_DEFAULT.retrieve("planning", "step_executor_user_prompt").format( step_description=todo.description, ) ) if todo.tool_to_use: parts.append( - self._i18n.retrieve("planning", "step_executor_suggested_tool").format( + I18N_DEFAULT.retrieve( + "planning", "step_executor_suggested_tool" + ).format( tool_to_use=todo.tool_to_use, ) ) @@ -270,16 +270,16 @@ class StepExecutor: # Include dependency results (final results only, no traces) if context.dependency_results: parts.append( - self._i18n.retrieve("planning", "step_executor_context_header") + I18N_DEFAULT.retrieve("planning", "step_executor_context_header") ) for step_num, result in sorted(context.dependency_results.items()): parts.append( - self._i18n.retrieve( + I18N_DEFAULT.retrieve( "planning", "step_executor_context_entry" ).format(step_number=step_num, result=result) ) - parts.append(self._i18n.retrieve("planning", "step_executor_complete_step")) + parts.append(I18N_DEFAULT.retrieve("planning", "step_executor_complete_step")) return "\n".join(parts) @@ -375,7 +375,6 @@ class StepExecutor: agent_action=formatted, fingerprint_context=fingerprint_context, tools=self.tools, - i18n=self._i18n, agent_key=self.agent.key if self.agent else None, agent_role=self.agent.role if self.agent else None, tools_handler=self.tools_handler, diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 72b732766..ef33fab43 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -91,7 +91,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, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.planning_types import ( PlanStep, StepObservation, @@ -189,7 +189,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor ) callbacks: list[Any] = Field(default_factory=list, exclude=True) response_model: type[BaseModel] | None = Field(default=None, exclude=True) - i18n: I18N | None = Field(default=None, exclude=True) log_error_after: int = Field(default=3, exclude=True) before_llm_call_hooks: list[BeforeLLMCallHookType | BeforeLLMCallHookCallable] = ( Field(default_factory=list, exclude=True) @@ -198,7 +197,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor default_factory=list, exclude=True ) - _i18n: I18N = PrivateAttr(default_factory=get_i18n) _console: Console = PrivateAttr(default_factory=Console) _last_parser_error: OutputParserError | None = PrivateAttr(default=None) _last_context_error: Exception | None = PrivateAttr(default=None) @@ -214,7 +212,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor @model_validator(mode="after") def _setup_executor(self) -> Self: """Configure executor after Pydantic field initialization.""" - self._i18n = self.i18n or get_i18n() self.before_llm_call_hooks.extend(get_before_llm_call_hooks()) self.after_llm_call_hooks.extend(get_after_llm_call_hooks()) @@ -363,7 +360,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor function_calling_llm=self.function_calling_llm, request_within_rpm_limit=self.request_within_rpm_limit, callbacks=self.callbacks, - i18n=self._i18n, ) return self._step_executor @@ -1203,7 +1199,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor formatted_answer = handle_max_iterations_exceeded( formatted_answer=None, printer=PRINTER, - i18n=self._i18n, messages=list(self.state.messages), llm=self.llm, callbacks=self.callbacks, @@ -1430,7 +1425,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor agent_action=action, fingerprint_context=fingerprint_context, tools=self.tools, - i18n=self._i18n, agent_key=self.agent.key if self.agent else None, agent_role=self.agent.role if self.agent else None, tools_handler=self.tools_handler, @@ -1450,7 +1444,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor action.result = str(e) self._append_message_to_state(action.text) - reasoning_prompt = self._i18n.slice("post_tool_reasoning") + reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning") reasoning_message: LLMMessage = { "role": "user", "content": reasoning_prompt, @@ -1471,7 +1465,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor self.state.is_finished = True return "tool_result_is_final" - reasoning_prompt = self._i18n.slice("post_tool_reasoning") + reasoning_prompt = I18N_DEFAULT.slice("post_tool_reasoning") reasoning_message_post: LLMMessage = { "role": "user", "content": reasoning_prompt, @@ -2222,10 +2216,10 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor # Build synthesis prompt role = self.agent.role if self.agent else "Assistant" - system_prompt = self._i18n.retrieve( + system_prompt = I18N_DEFAULT.retrieve( "planning", "synthesis_system_prompt" ).format(role=role) - user_prompt = self._i18n.retrieve("planning", "synthesis_user_prompt").format( + user_prompt = I18N_DEFAULT.retrieve("planning", "synthesis_user_prompt").format( task_description=task_description, combined_steps=combined_steps, ) @@ -2472,7 +2466,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor self.task.description if self.task else getattr(self, "_kickoff_input", "") ) - enhancement = self._i18n.retrieve( + enhancement = I18N_DEFAULT.retrieve( "planning", "replan_enhancement_prompt" ).format(previous_context=previous_context) @@ -2535,7 +2529,6 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor messages=self.state.messages, llm=self.llm, callbacks=self.callbacks, - i18n=self._i18n, verbose=self.agent.verbose, ) @@ -2746,7 +2739,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor Returns: Updated action or final answer. """ - add_image_tool = self._i18n.tools("add_image") + add_image_tool = I18N_DEFAULT.tools("add_image") if ( isinstance(add_image_tool, dict) and formatted_answer.tool.casefold().strip() diff --git a/lib/crewai/src/crewai/flow/flow.py b/lib/crewai/src/crewai/flow/flow.py index 97e6bdf20..16cc88db2 100644 --- a/lib/crewai/src/crewai/flow/flow.py +++ b/lib/crewai/src/crewai/flow/flow.py @@ -3194,7 +3194,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 get_i18n + from crewai.utilities.i18n import I18N_DEFAULT llm_instance: BaseLLMClass if isinstance(llm, str): @@ -3214,9 +3214,7 @@ class Flow(BaseModel, Generic[T], metaclass=FlowMeta): description=f"The outcome that best matches the feedback. Must be one of: {', '.join(outcomes)}" ) - # Load prompt from translations (using cached instance) - i18n = get_i18n() - prompt_template = i18n.slice("human_feedback_collapse") + prompt_template = I18N_DEFAULT.slice("human_feedback_collapse") prompt = prompt_template.format( feedback=feedback, diff --git a/lib/crewai/src/crewai/flow/human_feedback.py b/lib/crewai/src/crewai/flow/human_feedback.py index 5fedbd3a2..e6a51d9da 100644 --- a/lib/crewai/src/crewai/flow/human_feedback.py +++ b/lib/crewai/src/crewai/flow/human_feedback.py @@ -350,9 +350,9 @@ def human_feedback( def _get_hitl_prompt(key: str) -> str: """Read a HITL prompt from the i18n translations.""" - from crewai.utilities.i18n import get_i18n + from crewai.utilities.i18n import I18N_DEFAULT - return get_i18n().slice(key) + return I18N_DEFAULT.slice(key) def _resolve_llm_instance() -> Any: """Resolve the ``llm`` parameter to a BaseLLM instance. diff --git a/lib/crewai/src/crewai/lite_agent.py b/lib/crewai/src/crewai/lite_agent.py index f96c84493..07813b60f 100644 --- a/lib/crewai/src/crewai/lite_agent.py +++ b/lib/crewai/src/crewai/lite_agent.py @@ -89,7 +89,7 @@ from crewai.utilities.converter import ( ) from crewai.utilities.guardrail import process_guardrail from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType -from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.llm_utils import create_llm from crewai.utilities.printer import PRINTER from crewai.utilities.pydantic_schema_utils import generate_model_description @@ -227,9 +227,6 @@ class LiteAgent(FlowTrackable, BaseModel): default=None, description="Callback to check if the request is within the RPM8 limit", ) - i18n: I18N = Field( - default_factory=get_i18n, description="Internationalization settings." - ) response_format: type[BaseModel] | None = Field( default=None, description="Pydantic model for structured output" ) @@ -571,7 +568,7 @@ class LiteAgent(FlowTrackable, BaseModel): f"- {m.record.content}" for m in matches ) if memory_block: - formatted = self.i18n.slice("memory").format(memory=memory_block) + formatted = I18N_DEFAULT.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): @@ -644,7 +641,7 @@ class LiteAgent(FlowTrackable, BaseModel): try: model_schema = generate_model_description(active_response_format) schema = json.dumps(model_schema, indent=2) - instructions = self.i18n.slice("formatted_task_instructions").format( + instructions = I18N_DEFAULT.slice("formatted_task_instructions").format( output_format=schema ) @@ -793,7 +790,9 @@ class LiteAgent(FlowTrackable, BaseModel): base_prompt = "" if self._parsed_tools: # Use the prompt template for agents with tools - base_prompt = self.i18n.slice("lite_agent_system_prompt_with_tools").format( + base_prompt = I18N_DEFAULT.slice( + "lite_agent_system_prompt_with_tools" + ).format( role=self.role, backstory=self.backstory, goal=self.goal, @@ -802,7 +801,7 @@ class LiteAgent(FlowTrackable, BaseModel): ) else: # Use the prompt template for agents without tools - base_prompt = self.i18n.slice( + base_prompt = I18N_DEFAULT.slice( "lite_agent_system_prompt_without_tools" ).format( role=self.role, @@ -814,7 +813,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 += self.i18n.slice("lite_agent_response_format").format( + base_prompt += I18N_DEFAULT.slice("lite_agent_response_format").format( response_format=schema_json ) @@ -875,7 +874,6 @@ class LiteAgent(FlowTrackable, BaseModel): formatted_answer = handle_max_iterations_exceeded( formatted_answer, printer=PRINTER, - i18n=self.i18n, messages=self._messages, llm=cast(LLM, self.llm), callbacks=self._callbacks, @@ -914,7 +912,6 @@ class LiteAgent(FlowTrackable, BaseModel): tool_result = execute_tool_and_check_finality( agent_action=formatted_answer, tools=self._parsed_tools, - i18n=self.i18n, agent_key=self.key, agent_role=self.role, agent=self.original_agent, @@ -956,7 +953,6 @@ class LiteAgent(FlowTrackable, BaseModel): messages=self._messages, llm=cast(LLM, self.llm), callbacks=self._callbacks, - i18n=self.i18n, verbose=self.verbose, ) continue diff --git a/lib/crewai/src/crewai/memory/analyze.py b/lib/crewai/src/crewai/memory/analyze.py index e700f4281..65d671d0d 100644 --- a/lib/crewai/src/crewai/memory/analyze.py +++ b/lib/crewai/src/crewai/memory/analyze.py @@ -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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT _logger = logging.getLogger(__name__) @@ -149,7 +149,7 @@ def _get_prompt(key: str) -> str: Returns: The prompt string. """ - return get_i18n().memory(key) + return I18N_DEFAULT.memory(key) def extract_memories_from_content(content: str, llm: Any) -> list[str]: diff --git a/lib/crewai/src/crewai/task.py b/lib/crewai/src/crewai/task.py index 5671282dc..4224828e3 100644 --- a/lib/crewai/src/crewai/task.py +++ b/lib/crewai/src/crewai/task.py @@ -80,7 +80,7 @@ from crewai.utilities.guardrail_types import ( GuardrailType, GuardrailsType, ) -from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.printer import PRINTER from crewai.utilities.string_utils import interpolate_only @@ -115,7 +115,6 @@ class Task(BaseModel): used_tools: int = 0 tools_errors: int = 0 delegations: int = 0 - i18n: I18N = Field(default_factory=get_i18n) name: str | None = Field(default=None) prompt_context: str | None = None description: str = Field(description="Description of the actual task.") @@ -896,7 +895,7 @@ class Task(BaseModel): tasks_slices = [description] - output = self.i18n.slice("expected_output").format( + output = I18N_DEFAULT.slice("expected_output").format( expected_output=self.expected_output ) tasks_slices = [description, output] @@ -968,7 +967,7 @@ Follow these guidelines: raise ValueError(f"Error interpolating output_file path: {e!s}") from e if inputs.get("crew_chat_messages"): - conversation_instruction = self.i18n.slice( + conversation_instruction = I18N_DEFAULT.slice( "conversation_history_instruction" ) @@ -1219,7 +1218,7 @@ Follow these guidelines: self.retry_count += 1 current_retry_count = self.retry_count - context = self.i18n.errors("validation_error").format( + context = I18N_DEFAULT.errors("validation_error").format( guardrail_result_error=guardrail_result.error, task_output=task_output.raw, ) @@ -1316,7 +1315,7 @@ Follow these guidelines: self.retry_count += 1 current_retry_count = self.retry_count - context = self.i18n.errors("validation_error").format( + context = I18N_DEFAULT.errors("validation_error").format( guardrail_result_error=guardrail_result.error, task_output=task_output.raw, ) diff --git a/lib/crewai/src/crewai/telemetry/telemetry.py b/lib/crewai/src/crewai/telemetry/telemetry.py index 7809c5b4c..94939bb7a 100644 --- a/lib/crewai/src/crewai/telemetry/telemetry.py +++ b/lib/crewai/src/crewai/telemetry/telemetry.py @@ -52,6 +52,7 @@ from crewai.telemetry.utils import ( add_crew_attributes, close_span, ) +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.logger_utils import suppress_warnings from crewai.utilities.string_utils import sanitize_tool_name @@ -314,7 +315,7 @@ class Telemetry: "verbose?": agent.verbose, "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, - "i18n": agent.i18n.prompt_file, + "i18n": I18N_DEFAULT.prompt_file, "function_calling_llm": ( getattr( getattr(agent, "function_calling_llm", None), @@ -844,7 +845,7 @@ class Telemetry: "verbose?": agent.verbose, "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, - "i18n": agent.i18n.prompt_file, + "i18n": I18N_DEFAULT.prompt_file, "llm": agent.llm.model if isinstance(agent.llm, BaseLLM) else str(agent.llm), diff --git a/lib/crewai/src/crewai/tools/agent_tools/add_image_tool.py b/lib/crewai/src/crewai/tools/agent_tools/add_image_tool.py index e9ef66e81..8191144d9 100644 --- a/lib/crewai/src/crewai/tools/agent_tools/add_image_tool.py +++ b/lib/crewai/src/crewai/tools/agent_tools/add_image_tool.py @@ -3,10 +3,7 @@ from typing import Any from pydantic import BaseModel, Field from crewai.tools.base_tool import BaseTool -from crewai.utilities import I18N - - -i18n = I18N() +from crewai.utilities.i18n import I18N_DEFAULT class AddImageToolSchema(BaseModel): @@ -19,9 +16,9 @@ class AddImageToolSchema(BaseModel): class AddImageTool(BaseTool): """Tool for adding images to the content""" - name: str = Field(default_factory=lambda: i18n.tools("add_image")["name"]) # type: ignore[index] + name: str = Field(default_factory=lambda: I18N_DEFAULT.tools("add_image")["name"]) # type: ignore[index] description: str = Field( - default_factory=lambda: i18n.tools("add_image")["description"] # type: ignore[index] + default_factory=lambda: I18N_DEFAULT.tools("add_image")["description"] # type: ignore[index] ) args_schema: type[BaseModel] = AddImageToolSchema @@ -31,7 +28,7 @@ class AddImageTool(BaseTool): action: str | None = None, **kwargs: Any, ) -> dict[str, Any]: - action = action or i18n.tools("add_image")["default_action"] # type: ignore + action = action or I18N_DEFAULT.tools("add_image")["default_action"] # type: ignore content = [ {"type": "text", "text": action}, { diff --git a/lib/crewai/src/crewai/tools/agent_tools/agent_tools.py b/lib/crewai/src/crewai/tools/agent_tools/agent_tools.py index 51552f7a8..533217456 100644 --- a/lib/crewai/src/crewai/tools/agent_tools/agent_tools.py +++ b/lib/crewai/src/crewai/tools/agent_tools/agent_tools.py @@ -5,21 +5,19 @@ 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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT if TYPE_CHECKING: from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.tools.base_tool import BaseTool - from crewai.utilities.i18n import I18N class AgentTools: """Manager class for agent-related tools""" - def __init__(self, agents: Sequence[BaseAgent], i18n: I18N | None = None) -> None: + def __init__(self, agents: Sequence[BaseAgent]) -> None: self.agents = agents - self.i18n = i18n if i18n is not None else get_i18n() def tools(self) -> list[BaseTool]: """Get all available agent tools""" @@ -27,14 +25,12 @@ class AgentTools: delegate_tool = DelegateWorkTool( agents=self.agents, - i18n=self.i18n, - description=self.i18n.tools("delegate_work").format(coworkers=coworkers), # type: ignore + description=I18N_DEFAULT.tools("delegate_work").format(coworkers=coworkers), # type: ignore ) ask_tool = AskQuestionTool( agents=self.agents, - i18n=self.i18n, - description=self.i18n.tools("ask_question").format(coworkers=coworkers), # type: ignore + description=I18N_DEFAULT.tools("ask_question").format(coworkers=coworkers), # type: ignore ) return [delegate_tool, ask_tool] diff --git a/lib/crewai/src/crewai/tools/agent_tools/base_agent_tools.py b/lib/crewai/src/crewai/tools/agent_tools/base_agent_tools.py index 8e5b959a4..17e44e57a 100644 --- a/lib/crewai/src/crewai/tools/agent_tools/base_agent_tools.py +++ b/lib/crewai/src/crewai/tools/agent_tools/base_agent_tools.py @@ -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, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT logger = logging.getLogger(__name__) @@ -16,9 +16,6 @@ class BaseAgentTool(BaseTool): """Base class for agent-related tools""" agents: list[BaseAgent] = Field(description="List of available agents") - i18n: I18N = Field( - default_factory=get_i18n, description="Internationalization settings" - ) def sanitize_agent_name(self, name: str) -> str: """ @@ -93,7 +90,7 @@ class BaseAgentTool(BaseTool): ) except (AttributeError, ValueError) as e: # Handle specific exceptions that might occur during role name processing - return self.i18n.errors("agent_tool_unexisting_coworker").format( + return I18N_DEFAULT.errors("agent_tool_unexisting_coworker").format( coworkers="\n".join( [ f"- {self.sanitize_agent_name(agent.role)}" @@ -105,7 +102,7 @@ class BaseAgentTool(BaseTool): if not agent: # No matching agent found after sanitization - return self.i18n.errors("agent_tool_unexisting_coworker").format( + return I18N_DEFAULT.errors("agent_tool_unexisting_coworker").format( coworkers="\n".join( [ f"- {self.sanitize_agent_name(agent.role)}" @@ -120,8 +117,7 @@ class BaseAgentTool(BaseTool): task_with_assigned_agent = Task( description=task, agent=selected_agent, - expected_output=selected_agent.i18n.slice("manager_request"), - i18n=selected_agent.i18n, + expected_output=I18N_DEFAULT.slice("manager_request"), ) logger.debug( f"Created task for agent '{self.sanitize_agent_name(selected_agent.role)}': {task}" @@ -129,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 self.i18n.errors("agent_tool_execution_error").format( + return I18N_DEFAULT.errors("agent_tool_execution_error").format( agent_role=self.sanitize_agent_name(selected_agent.role), error=str(e) ) diff --git a/lib/crewai/src/crewai/tools/memory_tools.py b/lib/crewai/src/crewai/tools/memory_tools.py index c1874a532..e790c93f1 100644 --- a/lib/crewai/src/crewai/tools/memory_tools.py +++ b/lib/crewai/src/crewai/tools/memory_tools.py @@ -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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT class RecallMemorySchema(BaseModel): @@ -114,18 +114,17 @@ def create_memory_tools(memory: Any) -> list[BaseTool]: Returns: List containing a RecallMemoryTool and, if not read-only, a RememberTool. """ - i18n = get_i18n() tools: list[BaseTool] = [ RecallMemoryTool( memory=memory, - description=i18n.tools("recall_memory"), + description=I18N_DEFAULT.tools("recall_memory"), ), ] if not memory.read_only: tools.append( RememberTool( memory=memory, - description=i18n.tools("save_to_memory"), + description=I18N_DEFAULT.tools("save_to_memory"), ) ) return tools diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index c99b32cf5..09b44be17 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -28,7 +28,7 @@ from crewai.utilities.agent_utils import ( render_text_description_and_args, ) from crewai.utilities.converter import Converter -from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.printer import PRINTER from crewai.utilities.string_utils import sanitize_tool_name @@ -93,7 +93,6 @@ class ToolUsage: action: Any = None, fingerprint_context: dict[str, str] | None = None, ) -> None: - self._i18n: I18N = agent.i18n if agent else get_i18n() self._telemetry: Telemetry = Telemetry() self._run_attempts: int = 1 self._max_parsing_attempts: int = 3 @@ -146,7 +145,7 @@ class ToolUsage: if ( isinstance(tool, CrewStructuredTool) and sanitize_tool_name(tool.name) - == sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore + == sanitize_tool_name(I18N_DEFAULT.tools("add_image")["name"]) # type: ignore ): try: return self._use(tool_string=tool_string, tool=tool, calling=calling) @@ -194,7 +193,7 @@ class ToolUsage: if ( isinstance(tool, CrewStructuredTool) and sanitize_tool_name(tool.name) - == sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore + == sanitize_tool_name(I18N_DEFAULT.tools("add_image")["name"]) # type: ignore ): try: return await self._ause( @@ -230,7 +229,7 @@ class ToolUsage: """ if self._check_tool_repeated_usage(calling=calling): try: - result = self._i18n.errors("task_repeated_usage").format( + result = I18N_DEFAULT.errors("task_repeated_usage").format( tool_names=self.tools_names ) self._telemetry.tool_repeated_usage( @@ -415,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 = self._i18n.errors( + error_message = I18N_DEFAULT.errors( "tool_usage_exception" ).format( error=e, @@ -423,7 +422,7 @@ class ToolUsage: tool_inputs=tool.description, ) result = ToolUsageError( - f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}" + f"\n{error_message}.\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}" ).message if self.task: self.task.increment_tools_errors() @@ -461,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 = self._i18n.errors("task_repeated_usage").format( + result = I18N_DEFAULT.errors("task_repeated_usage").format( tool_names=self.tools_names ) self._telemetry.tool_repeated_usage( @@ -648,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 = self._i18n.errors( + error_message = I18N_DEFAULT.errors( "tool_usage_exception" ).format( error=e, @@ -656,7 +655,7 @@ class ToolUsage: tool_inputs=tool.description, ) result = ToolUsageError( - f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}" + f"\n{error_message}.\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}" ).message if self.task: self.task.increment_tools_errors() @@ -699,7 +698,7 @@ class ToolUsage: def _remember_format(self, result: str) -> str: result = str(result) - result += "\n\n" + self._i18n.slice("tools").format( + result += "\n\n" + I18N_DEFAULT.slice("tools").format( tools=self.tools_description, tool_names=self.tools_names ) return result @@ -825,12 +824,12 @@ class ToolUsage: except Exception: if raise_error: raise - return ToolUsageError(f"{self._i18n.errors('tool_arguments_error')}") + return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") if not isinstance(arguments, dict): if raise_error: raise - return ToolUsageError(f"{self._i18n.errors('tool_arguments_error')}") + return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") return ToolCalling( tool_name=sanitize_tool_name(tool.name), @@ -856,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"{self._i18n.errors('tool_usage_error').format(error=e)}\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}" + f"{I18N_DEFAULT.errors('tool_usage_error').format(error=e)}\nMoving on then. {I18N_DEFAULT.slice('format').format(tool_names=self.tools_names)}" ) return self._tool_calling(tool_string) diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index d448cd162..684fd9287 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -31,7 +31,7 @@ from crewai.utilities.errors import AgentRepositoryError from crewai.utilities.exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededError, ) -from crewai.utilities.i18n import I18N +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.printer import PRINTER, ColoredText, Printer from crewai.utilities.pydantic_schema_utils import generate_model_description from crewai.utilities.string_utils import sanitize_tool_name @@ -254,7 +254,6 @@ def has_reached_max_iterations(iterations: int, max_iterations: int) -> bool: def handle_max_iterations_exceeded( formatted_answer: AgentAction | AgentFinish | None, printer: Printer, - i18n: I18N, messages: list[LLMMessage], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], @@ -265,7 +264,6 @@ def handle_max_iterations_exceeded( Args: formatted_answer: The last formatted answer from the agent. printer: Printer instance for output. - i18n: I18N instance for internationalization. messages: List of messages to send to the LLM. llm: The LLM instance to call. callbacks: List of callbacks for the LLM call. @@ -282,10 +280,10 @@ def handle_max_iterations_exceeded( if formatted_answer and hasattr(formatted_answer, "text"): assistant_message = ( - formatted_answer.text + f"\n{i18n.errors('force_final_answer')}" + formatted_answer.text + f"\n{I18N_DEFAULT.errors('force_final_answer')}" ) else: - assistant_message = i18n.errors("force_final_answer") + assistant_message = I18N_DEFAULT.errors("force_final_answer") messages.append(format_message_for_llm(assistant_message, role="assistant")) @@ -687,7 +685,6 @@ def handle_context_length( messages: list[LLMMessage], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], - i18n: I18N, verbose: bool = True, ) -> None: """Handle context length exceeded by either summarizing or raising an error. @@ -698,7 +695,6 @@ def handle_context_length( messages: List of messages to summarize llm: LLM instance for summarization callbacks: List of callbacks for LLM - i18n: I18N instance for messages Raises: SystemExit: If context length is exceeded and user opts not to summarize @@ -710,7 +706,7 @@ def handle_context_length( color="yellow", ) summarize_messages( - messages=messages, llm=llm, callbacks=callbacks, i18n=i18n, verbose=verbose + messages=messages, llm=llm, callbacks=callbacks, verbose=verbose ) else: if verbose: @@ -863,7 +859,6 @@ async def _asummarize_chunks( chunks: list[list[LLMMessage]], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], - i18n: I18N, ) -> list[SummaryContent]: """Summarize multiple message chunks concurrently using asyncio. @@ -871,7 +866,6 @@ async def _asummarize_chunks( chunks: List of message chunks to summarize. llm: LLM instance (must support ``acall``). callbacks: List of callbacks for the LLM. - i18n: I18N instance for prompt templates. Returns: Ordered list of summary contents, one per chunk. @@ -881,10 +875,10 @@ async def _asummarize_chunks( conversation_text = _format_messages_for_summary(chunk) summarization_messages = [ format_message_for_llm( - i18n.slice("summarizer_system_message"), role="system" + I18N_DEFAULT.slice("summarizer_system_message"), role="system" ), format_message_for_llm( - i18n.slice("summarize_instruction").format( + I18N_DEFAULT.slice("summarize_instruction").format( conversation=conversation_text ), ), @@ -901,7 +895,6 @@ def summarize_messages( messages: list[LLMMessage], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], - i18n: I18N, verbose: bool = True, ) -> None: """Summarize messages to fit within context window. @@ -917,7 +910,6 @@ def summarize_messages( messages: List of messages to summarize (modified in-place) llm: LLM instance for summarization callbacks: List of callbacks for LLM - i18n: I18N instance for messages verbose: Whether to print progress. """ # 1. Extract & preserve file attachments from user messages @@ -953,10 +945,10 @@ def summarize_messages( conversation_text = _format_messages_for_summary(chunk) summarization_messages = [ format_message_for_llm( - i18n.slice("summarizer_system_message"), role="system" + I18N_DEFAULT.slice("summarizer_system_message"), role="system" ), format_message_for_llm( - i18n.slice("summarize_instruction").format( + I18N_DEFAULT.slice("summarize_instruction").format( conversation=conversation_text ), ), @@ -971,9 +963,7 @@ def summarize_messages( content=f"Summarizing {total_chunks} chunks in parallel...", color="yellow", ) - coro = _asummarize_chunks( - chunks=chunks, llm=llm, callbacks=callbacks, i18n=i18n - ) + coro = _asummarize_chunks(chunks=chunks, llm=llm, callbacks=callbacks) if is_inside_event_loop(): ctx = contextvars.copy_context() with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: @@ -988,7 +978,7 @@ def summarize_messages( messages.extend(system_messages) summary_message = format_message_for_llm( - i18n.slice("summary").format(merged_summary=merged_summary) + I18N_DEFAULT.slice("summary").format(merged_summary=merged_summary) ) if preserved_files: summary_message["files"] = preserved_files diff --git a/lib/crewai/src/crewai/utilities/converter.py b/lib/crewai/src/crewai/utilities/converter.py index 328ecbdf9..26dce6bd0 100644 --- a/lib/crewai/src/crewai/utilities/converter.py +++ b/lib/crewai/src/crewai/utilities/converter.py @@ -8,7 +8,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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.internal_instructor import InternalInstructor from crewai.utilities.printer import PRINTER from crewai.utilities.pydantic_schema_utils import generate_model_description @@ -21,7 +21,7 @@ if TYPE_CHECKING: from crewai.llms.base_llm import BaseLLM _JSON_PATTERN: Final[re.Pattern[str]] = re.compile(r"({.*})", re.DOTALL) -_I18N = get_i18n() +_I18N = I18N_DEFAULT class ConverterError(Exception): diff --git a/lib/crewai/src/crewai/utilities/evaluators/task_evaluator.py b/lib/crewai/src/crewai/utilities/evaluators/task_evaluator.py index 0a76c2a6c..5915c2346 100644 --- a/lib/crewai/src/crewai/utilities/evaluators/task_evaluator.py +++ b/lib/crewai/src/crewai/utilities/evaluators/task_evaluator.py @@ -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 get_i18n +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.pydantic_schema_utils import generate_model_description from crewai.utilities.training_converter import TrainingConverter @@ -98,11 +98,9 @@ class TaskEvaluator: if not self.llm.supports_function_calling(): # type: ignore[union-attr] schema_dict = generate_model_description(TaskEvaluation) - output_schema: str = ( - get_i18n() - .slice("formatted_task_instructions") - .format(output_format=json.dumps(schema_dict, indent=2)) - ) + output_schema: str = I18N_DEFAULT.slice( + "formatted_task_instructions" + ).format(output_format=json.dumps(schema_dict, indent=2)) instructions = f"{instructions}\n\n{output_schema}" converter = Converter( @@ -174,11 +172,9 @@ class TaskEvaluator: if not self.llm.supports_function_calling(): # type: ignore[union-attr] schema_dict = generate_model_description(TrainingTaskEvaluation) - output_schema: str = ( - get_i18n() - .slice("formatted_task_instructions") - .format(output_format=json.dumps(schema_dict, indent=2)) - ) + output_schema: str = I18N_DEFAULT.slice( + "formatted_task_instructions" + ).format(output_format=json.dumps(schema_dict, indent=2)) instructions = f"{instructions}\n\n{output_schema}" converter = TrainingConverter( diff --git a/lib/crewai/src/crewai/utilities/i18n.py b/lib/crewai/src/crewai/utilities/i18n.py index 623d8a22e..8d091dd52 100644 --- a/lib/crewai/src/crewai/utilities/i18n.py +++ b/lib/crewai/src/crewai/utilities/i18n.py @@ -142,3 +142,6 @@ def get_i18n(prompt_file: str | None = None) -> I18N: Cached I18N instance. """ return I18N(prompt_file=prompt_file) + + +I18N_DEFAULT: I18N = get_i18n() diff --git a/lib/crewai/src/crewai/utilities/prompts.py b/lib/crewai/src/crewai/utilities/prompts.py index 821623b89..31c1a1b27 100644 --- a/lib/crewai/src/crewai/utilities/prompts.py +++ b/lib/crewai/src/crewai/utilities/prompts.py @@ -6,7 +6,7 @@ from typing import Any, Literal from pydantic import BaseModel, Field -from crewai.utilities.i18n import I18N, get_i18n +from crewai.utilities.i18n import I18N_DEFAULT class StandardPromptResult(BaseModel): @@ -49,7 +49,6 @@ class Prompts(BaseModel): - Need to refactor so that prompt is not tightly coupled to agent. """ - i18n: I18N = Field(default_factory=get_i18n) has_tools: bool = Field( default=False, description="Indicates if the agent has access to tools" ) @@ -140,13 +139,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] = [ - self.i18n.slice(component) for component in components + I18N_DEFAULT.slice(component) for component in components ] prompt = "".join(prompt_parts) else: # All templates are provided, use them template_parts: list[str] = [ - self.i18n.slice(component) + I18N_DEFAULT.slice(component) for component in components if component != "task" ] @@ -154,7 +153,7 @@ class Prompts(BaseModel): "{{ .System }}", "".join(template_parts) ) prompt = prompt_template.replace( - "{{ .Prompt }}", "".join(self.i18n.slice("task")) + "{{ .Prompt }}", "".join(I18N_DEFAULT.slice("task")) ) # Handle missing response_template if response_template: diff --git a/lib/crewai/src/crewai/utilities/reasoning_handler.py b/lib/crewai/src/crewai/utilities/reasoning_handler.py index eecd8ee9a..ab3cbba16 100644 --- a/lib/crewai/src/crewai/utilities/reasoning_handler.py +++ b/lib/crewai/src/crewai/utilities/reasoning_handler.py @@ -15,6 +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.llm_utils import create_llm from crewai.utilities.planning_types import PlanStep from crewai.utilities.string_utils import sanitize_tool_name @@ -481,17 +482,17 @@ class AgentReasoning: """Get the system prompt for planning. Returns: - The system prompt, either custom or from i18n. + The system prompt, either custom or from I18N_DEFAULT. """ 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 self.agent.i18n.retrieve("planning", "system_prompt") + return I18N_DEFAULT.retrieve("planning", "system_prompt") except (KeyError, AttributeError): # Fallback to reasoning section for backward compatibility - return self.agent.i18n.retrieve("reasoning", "initial_plan").format( + return I18N_DEFAULT.retrieve("reasoning", "initial_plan").format( role=self.agent.role, goal=self.agent.goal, backstory=self._get_agent_backstory(), @@ -527,7 +528,7 @@ class AgentReasoning: # Try new "planning" section first try: - return self.agent.i18n.retrieve("planning", "create_plan_prompt").format( + return I18N_DEFAULT.retrieve("planning", "create_plan_prompt").format( description=self.description, expected_output=self.expected_output, tools=available_tools, @@ -535,7 +536,7 @@ class AgentReasoning: ) except (KeyError, AttributeError): # Fallback to reasoning section for backward compatibility - return self.agent.i18n.retrieve("reasoning", "create_plan_prompt").format( + return I18N_DEFAULT.retrieve("reasoning", "create_plan_prompt").format( role=self.agent.role, goal=self.agent.goal, backstory=self._get_agent_backstory(), @@ -584,12 +585,12 @@ class AgentReasoning: # Try new "planning" section first try: - return self.agent.i18n.retrieve("planning", "refine_plan_prompt").format( + return I18N_DEFAULT.retrieve("planning", "refine_plan_prompt").format( current_plan=current_plan, ) except (KeyError, AttributeError): # Fallback to reasoning section for backward compatibility - return self.agent.i18n.retrieve("reasoning", "refine_plan_prompt").format( + return I18N_DEFAULT.retrieve("reasoning", "refine_plan_prompt").format( role=self.agent.role, goal=self.agent.goal, backstory=self._get_agent_backstory(), @@ -642,7 +643,7 @@ def _call_llm_with_reasoning_prompt( Returns: The LLM response. """ - system_prompt = reasoning_agent.i18n.retrieve("reasoning", plan_type).format( + system_prompt = I18N_DEFAULT.retrieve("reasoning", plan_type).format( role=reasoning_agent.role, goal=reasoning_agent.goal, backstory=backstory, diff --git a/lib/crewai/src/crewai/utilities/tool_utils.py b/lib/crewai/src/crewai/utilities/tool_utils.py index b77eb9192..c7a469468 100644 --- a/lib/crewai/src/crewai/utilities/tool_utils.py +++ b/lib/crewai/src/crewai/utilities/tool_utils.py @@ -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 +from crewai.utilities.i18n import I18N_DEFAULT from crewai.utilities.logger import Logger from crewai.utilities.string_utils import sanitize_tool_name @@ -30,7 +30,6 @@ if TYPE_CHECKING: async def aexecute_tool_and_check_finality( agent_action: AgentAction, tools: list[CrewStructuredTool], - i18n: I18N, agent_key: str | None = None, agent_role: str | None = None, tools_handler: ToolsHandler | None = None, @@ -49,7 +48,6 @@ async def aexecute_tool_and_check_finality( Args: agent_action: The action containing the tool to execute. tools: List of available tools. - i18n: Internationalization settings. agent_key: Optional key for event emission. agent_role: Optional role for event emission. tools_handler: Optional tools handler for tool execution. @@ -142,7 +140,7 @@ async def aexecute_tool_and_check_finality( return ToolResult(modified_result, tool.result_as_answer) - tool_result = i18n.errors("wrong_tool_name").format( + tool_result = I18N_DEFAULT.errors("wrong_tool_name").format( tool=sanitized_tool_name, tools=", ".join(tool_name_to_tool_map.keys()), ) @@ -152,7 +150,6 @@ async def aexecute_tool_and_check_finality( def execute_tool_and_check_finality( agent_action: AgentAction, tools: list[CrewStructuredTool], - i18n: I18N, agent_key: str | None = None, agent_role: str | None = None, tools_handler: ToolsHandler | None = None, @@ -170,7 +167,6 @@ def execute_tool_and_check_finality( Args: agent_action: The action containing the tool to execute tools: List of available tools - i18n: Internationalization settings agent_key: Optional key for event emission agent_role: Optional role for event emission tools_handler: Optional tools handler for tool execution @@ -263,7 +259,7 @@ def execute_tool_and_check_finality( return ToolResult(modified_result, tool.result_as_answer) - tool_result = i18n.errors("wrong_tool_name").format( + tool_result = I18N_DEFAULT.errors("wrong_tool_name").format( tool=sanitized_tool_name, tools=", ".join(tool_name_to_tool_map.keys()), ) diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 7706f9ade..4681c8842 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -1208,12 +1208,10 @@ def test_llm_call_with_error(): def test_handle_context_length_exceeds_limit(): # Import necessary modules from crewai.utilities.agent_utils import handle_context_length - from crewai.utilities.i18n import I18N from crewai.utilities.printer import Printer # Create mocks for dependencies printer = Printer() - i18n = I18N() # Create an agent just for its LLM agent = Agent( @@ -1249,7 +1247,6 @@ def test_handle_context_length_exceeds_limit(): messages=messages, llm=llm, callbacks=callbacks, - i18n=i18n, ) # Verify our patch was called and raised the correct error @@ -1994,7 +1991,7 @@ def test_litellm_anthropic_error_handling(): @pytest.mark.vcr() def test_get_knowledge_search_query(): """Test that _get_knowledge_search_query calls the LLM with the correct prompts.""" - from crewai.utilities.i18n import I18N + from crewai.utilities.i18n import I18N_DEFAULT content = "The capital of France is Paris." string_source = StringKnowledgeSource(content=content) @@ -2013,7 +2010,6 @@ def test_get_knowledge_search_query(): agent=agent, ) - i18n = I18N() task_prompt = task.prompt() with ( @@ -2050,13 +2046,13 @@ def test_get_knowledge_search_query(): [ { "role": "system", - "content": i18n.slice( + "content": I18N_DEFAULT.slice( "knowledge_search_query_system_prompt" ).format(task_prompt=task.description), }, { "role": "user", - "content": i18n.slice("knowledge_search_query").format( + "content": I18N_DEFAULT.slice("knowledge_search_query").format( task_prompt=task_prompt ), }, diff --git a/lib/crewai/tests/agents/test_agent_executor.py b/lib/crewai/tests/agents/test_agent_executor.py index 7a6260a44..3413e30ac 100644 --- a/lib/crewai/tests/agents/test_agent_executor.py +++ b/lib/crewai/tests/agents/test_agent_executor.py @@ -48,8 +48,6 @@ def _build_executor(**kwargs: Any) -> AgentExecutor: executor._last_context_error = None executor._step_executor = None executor._planner_observer = None - from crewai.utilities.i18n import get_i18n - executor._i18n = kwargs.get("i18n") or get_i18n() return executor from crewai.agents.planner_observer import PlannerObserver from crewai.experimental.agent_executor import ( diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py index ba2e797d9..c7754e6ac 100644 --- a/lib/crewai/tests/tools/test_tool_usage.py +++ b/lib/crewai/tests/tools/test_tool_usage.py @@ -308,7 +308,6 @@ def test_validate_tool_input_invalid_input(): mock_agent.key = "test_agent_key" # Must be a string mock_agent.role = "test_agent_role" # Must be a string mock_agent._original_role = "test_agent_role" # Must be a string - mock_agent.i18n = MagicMock() mock_agent.verbose = False # Create mock action with proper string value @@ -443,7 +442,6 @@ def test_tool_selection_error_event_direct(): mock_agent = MagicMock() mock_agent.key = "test_key" mock_agent.role = "test_role" - mock_agent.i18n = MagicMock() mock_agent.verbose = False mock_task = MagicMock() @@ -518,13 +516,6 @@ def test_tool_validate_input_error_event(): mock_agent.verbose = False mock_agent._original_role = "test_role" - # Mock i18n with error message - mock_i18n = MagicMock() - mock_i18n.errors.return_value = ( - "Tool input must be a valid dictionary in JSON or Python literal format" - ) - mock_agent.i18n = mock_i18n - # Mock task and tools handler mock_task = MagicMock() mock_tools_handler = MagicMock() @@ -590,7 +581,6 @@ def test_tool_usage_finished_event_with_result(): mock_agent.key = "test_agent_key" mock_agent.role = "test_agent_role" mock_agent._original_role = "test_agent_role" - mock_agent.i18n = MagicMock() mock_agent.verbose = False # Create mock task @@ -670,7 +660,6 @@ def test_tool_usage_finished_event_with_cached_result(): mock_agent.key = "test_agent_key" mock_agent.role = "test_agent_role" mock_agent._original_role = "test_agent_role" - mock_agent.i18n = MagicMock() mock_agent.verbose = False # Create mock task @@ -761,9 +750,6 @@ def test_tool_error_does_not_emit_finished_event(): mock_agent._original_role = "test_agent_role" mock_agent.verbose = False mock_agent.fingerprint = None - mock_agent.i18n.tools.return_value = {"name": "Add Image"} - mock_agent.i18n.errors.return_value = "Error: {error}" - mock_agent.i18n.slice.return_value = "Available tools: {tool_names}" mock_task = MagicMock() mock_task.delegations = 0 diff --git a/lib/crewai/tests/utilities/test_agent_utils.py b/lib/crewai/tests/utilities/test_agent_utils.py index 3d249906a..42de64fe6 100644 --- a/lib/crewai/tests/utilities/test_agent_utils.py +++ b/lib/crewai/tests/utilities/test_agent_utils.py @@ -225,16 +225,6 @@ class TestConvertToolsToOpenaiSchema: assert max_results_prop["default"] == 10 -def _make_mock_i18n() -> MagicMock: - """Create a mock i18n with the new structured prompt keys.""" - mock_i18n = MagicMock() - mock_i18n.slice.side_effect = lambda key: { - "summarizer_system_message": "You are a precise assistant that creates structured summaries.", - "summarize_instruction": "Summarize the conversation:\n{conversation}", - "summary": "\n{merged_summary}\n\nContinue the task.", - }.get(key, "") - return mock_i18n - class MCPStyleInput(BaseModel): """Input schema mimicking an MCP tool with optional fields.""" @@ -330,7 +320,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # System message preserved + summary message = 2 @@ -361,7 +351,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert len(messages) == 1 @@ -387,7 +377,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert len(messages) == 1 @@ -410,7 +400,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert id(messages) == original_list_id @@ -432,7 +422,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert len(messages) == 2 @@ -456,7 +446,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # Check what was passed to llm.call @@ -482,7 +472,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert "The extracted summary content." in messages[0]["content"] @@ -506,7 +496,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # Verify the conversation text sent to LLM contains tool labels @@ -528,7 +518,7 @@ class TestSummarizeMessages: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # No LLM call should have been made @@ -733,7 +723,7 @@ class TestParallelSummarization: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # acall should have been awaited once per chunk @@ -757,7 +747,7 @@ class TestParallelSummarization: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) mock_llm.call.assert_called_once() @@ -788,7 +778,7 @@ class TestParallelSummarization: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) # The final summary message should have A, B, C in order @@ -816,7 +806,7 @@ class TestParallelSummarization: chunks=[chunk_a, chunk_b], llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) ) @@ -843,7 +833,7 @@ class TestParallelSummarization: messages=messages, llm=mock_llm, callbacks=[], - i18n=_make_mock_i18n(), + ) assert mock_llm.acall.await_count == 2 @@ -940,10 +930,8 @@ class TestParallelSummarizationVCR: def test_parallel_summarize_openai(self) -> None: """Test that parallel summarization with gpt-4o-mini produces a valid summary.""" from crewai.llm import LLM - from crewai.utilities.i18n import I18N llm = LLM(model="gpt-4o-mini", temperature=0) - i18n = I18N() messages = _build_long_conversation() original_system = messages[0]["content"] @@ -959,7 +947,6 @@ class TestParallelSummarizationVCR: messages=messages, llm=llm, callbacks=[], - i18n=i18n, ) # System message preserved @@ -975,10 +962,8 @@ class TestParallelSummarizationVCR: def test_parallel_summarize_preserves_files(self) -> None: """Test that file references survive parallel summarization.""" from crewai.llm import LLM - from crewai.utilities.i18n import I18N llm = LLM(model="gpt-4o-mini", temperature=0) - i18n = I18N() messages = _build_long_conversation() mock_file = MagicMock() @@ -989,7 +974,6 @@ class TestParallelSummarizationVCR: messages=messages, llm=llm, callbacks=[], - i18n=i18n, ) summary_msg = messages[-1] diff --git a/lib/crewai/tests/utilities/test_structured_planning.py b/lib/crewai/tests/utilities/test_structured_planning.py index 91bca9c0d..b76d9af5c 100644 --- a/lib/crewai/tests/utilities/test_structured_planning.py +++ b/lib/crewai/tests/utilities/test_structured_planning.py @@ -147,8 +147,6 @@ class TestAgentReasoningWithMockedLLM: agent.backstory = "Test backstory" agent.verbose = False agent.planning_config = PlanningConfig() - agent.i18n = MagicMock() - agent.i18n.retrieve.return_value = "Test prompt: {description}" # Mock the llm attribute agent.llm = MagicMock() agent.llm.supports_function_calling.return_value = True diff --git a/lib/crewai/tests/utilities/test_summarize_integration.py b/lib/crewai/tests/utilities/test_summarize_integration.py index 5b3e39d07..a5da3a108 100644 --- a/lib/crewai/tests/utilities/test_summarize_integration.py +++ b/lib/crewai/tests/utilities/test_summarize_integration.py @@ -14,7 +14,6 @@ from crewai.crew import Crew from crewai.llm import LLM from crewai.task import Task from crewai.utilities.agent_utils import summarize_messages -from crewai.utilities.i18n import I18N def _build_conversation_messages( @@ -90,7 +89,7 @@ class TestSummarizeDirectOpenAI: def test_summarize_direct_openai(self) -> None: """Test summarize_messages with gpt-4o-mini preserves system messages.""" llm = LLM(model="gpt-4o-mini", temperature=0) - i18n = I18N() + messages = _build_conversation_messages(include_system=True) original_system_content = messages[0]["content"] @@ -99,7 +98,7 @@ class TestSummarizeDirectOpenAI: messages=messages, llm=llm, callbacks=[], - i18n=i18n, + ) # System message should be preserved @@ -122,14 +121,14 @@ class TestSummarizeDirectAnthropic: def test_summarize_direct_anthropic(self) -> None: """Test summarize_messages with claude-3-5-haiku.""" llm = LLM(model="anthropic/claude-3-5-haiku-latest", temperature=0) - i18n = I18N() + messages = _build_conversation_messages(include_system=True) summarize_messages( messages=messages, llm=llm, callbacks=[], - i18n=i18n, + ) assert len(messages) >= 2 @@ -148,14 +147,14 @@ class TestSummarizeDirectGemini: def test_summarize_direct_gemini(self) -> None: """Test summarize_messages with gemini-2.0-flash.""" llm = LLM(model="gemini/gemini-2.0-flash", temperature=0) - i18n = I18N() + messages = _build_conversation_messages(include_system=True) summarize_messages( messages=messages, llm=llm, callbacks=[], - i18n=i18n, + ) assert len(messages) >= 2 @@ -174,14 +173,14 @@ class TestSummarizeDirectAzure: def test_summarize_direct_azure(self) -> None: """Test summarize_messages with azure/gpt-4o-mini.""" llm = LLM(model="azure/gpt-4o-mini", temperature=0) - i18n = I18N() + messages = _build_conversation_messages(include_system=True) summarize_messages( messages=messages, llm=llm, callbacks=[], - i18n=i18n, + ) assert len(messages) >= 2 @@ -261,7 +260,7 @@ class TestSummarizePreservesFiles: def test_summarize_preserves_files_integration(self) -> None: """Test that file references survive a real summarization call.""" llm = LLM(model="gpt-4o-mini", temperature=0) - i18n = I18N() + messages = _build_conversation_messages( include_system=True, include_files=True ) @@ -270,7 +269,7 @@ class TestSummarizePreservesFiles: messages=messages, llm=llm, callbacks=[], - i18n=i18n, + ) # System message preserved