refactor: use shared I18N_DEFAULT singleton

This commit is contained in:
Greyson LaLonde
2026-04-09 03:13:39 +08:00
parent 15f5bff043
commit 8dfb32d5f0
36 changed files with 179 additions and 278 deletions

View File

@@ -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
)

View File

@@ -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

View File

@@ -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
)

View File

@@ -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(

View File

@@ -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}"

View File

@@ -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."
)

View File

@@ -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)."""

View File

@@ -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)
)

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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()

View File

@@ -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,

View File

@@ -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.

View File

@@ -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

View File

@@ -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]:

View File

@@ -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,
)

View File

@@ -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),

View File

@@ -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},
{

View File

@@ -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]

View File

@@ -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)
)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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(

View File

@@ -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()

View File

@@ -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:

View File

@@ -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,

View File

@@ -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()),
)

View File

@@ -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
),
},

View File

@@ -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 (

View File

@@ -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

View File

@@ -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": "<summary>\n{merged_summary}\n</summary>\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]

View File

@@ -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

View File

@@ -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