Address PR feedback: Improve I18N implementation with centralized instantiation and better validation

Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
Devin AI
2025-04-07 10:37:45 +00:00
parent 8c9b8fff84
commit 448c3e8b67
2 changed files with 68 additions and 12 deletions

View File

@@ -617,7 +617,7 @@ class Crew(BaseModel):
self._interpolate_inputs(inputs) self._interpolate_inputs(inputs)
self._set_tasks_callbacks() self._set_tasks_callbacks()
i18n = I18N(prompt_file=self.prompt_file, language=self.language) i18n = self._create_i18n()
for agent in self.agents: for agent in self.agents:
agent.i18n = i18n agent.i18n = i18n
@@ -758,8 +758,17 @@ class Crew(BaseModel):
self._create_manager_agent() self._create_manager_agent()
return self._execute_tasks(self.tasks) return self._execute_tasks(self.tasks)
def _create_i18n(self) -> I18N:
"""
Create an I18N instance with the crew's configuration.
Returns:
I18N: An internationalization instance configured with the crew's settings.
"""
return I18N(prompt_file=self.prompt_file, language=self.language)
def _create_manager_agent(self): def _create_manager_agent(self):
i18n = I18N(prompt_file=self.prompt_file, language=self.language) i18n = self._create_i18n()
if self.manager_agent is not None: if self.manager_agent is not None:
self.manager_agent.allow_delegation = True self.manager_agent.allow_delegation = True
manager = self.manager_agent manager = self.manager_agent

View File

@@ -1,11 +1,15 @@
import json import json
import os import os
from typing import Dict, Optional, Union from functools import lru_cache
from pathlib import Path
from typing import Dict, Literal, Optional, Union
from pydantic import BaseModel, Field, PrivateAttr, model_validator from pydantic import BaseModel, Field, PrivateAttr, model_validator
"""Internationalization support for CrewAI prompts and messages.""" """Internationalization support for CrewAI prompts and messages."""
SUPPORTED_LANGUAGES = Literal["en", "fr", "es", "pt"]
class I18N(BaseModel): class I18N(BaseModel):
"""Handles loading and retrieving internationalized prompts.""" """Handles loading and retrieving internationalized prompts."""
_prompts: Dict[str, Dict[str, str]] = PrivateAttr() _prompts: Dict[str, Dict[str, str]] = PrivateAttr()
@@ -15,28 +19,55 @@ class I18N(BaseModel):
) )
language: Optional[str] = Field( language: Optional[str] = Field(
default="en", default="en",
description="Language to use for translations", description="Language to use for translations. Defaults to English.",
) )
@model_validator(mode="before")
@classmethod
def validate_language(cls, data):
"""
Validate the language parameter.
If the language is not supported, it will fall back to English.
"""
if isinstance(data, dict) and "language" in data:
lang = data["language"]
if lang and lang not in ["en", "fr", "es", "pt"]:
print(f"Warning: Language '{lang}' not supported. Falling back to English.")
data["language"] = "en"
return data
@model_validator(mode="after") @model_validator(mode="after")
def load_prompts(self) -> "I18N": def load_prompts(self) -> "I18N":
"""Load prompts from a JSON file.""" """
Load prompts from a JSON file.
If prompt_file is provided, loads from that file.
Otherwise, attempts to load from the language-specific translation file.
Falls back to English if the specified language file doesn't exist.
Raises:
Exception: If the prompt file is not found or contains invalid JSON.
Returns:
I18N: The instance with loaded prompts.
"""
try: try:
if self.prompt_file: if self.prompt_file:
with open(self.prompt_file, "r", encoding="utf-8") as f: with open(self.prompt_file, "r", encoding="utf-8") as f:
self._prompts = json.load(f) self._prompts = json.load(f)
else: else:
dir_path = os.path.dirname(os.path.realpath(__file__)) base_path = Path(__file__).parent / "../translations"
lang = self.language or "en" lang = self.language or "en"
prompts_path = os.path.join(dir_path, f"../translations/{lang}.json") lang_file = base_path / f"{lang}.json"
if not os.path.exists(prompts_path): if not lang_file.exists():
prompts_path = os.path.join(dir_path, "../translations/en.json") lang_file = base_path / "en.json"
with open(prompts_path, "r", encoding="utf-8") as f: with open(lang_file.resolve(), "r", encoding="utf-8") as f:
self._prompts = json.load(f) self._prompts = json.load(f)
except FileNotFoundError: except FileNotFoundError:
raise Exception(f"Prompt file '{self.prompt_file}' not found.") raise Exception(f"Prompt file '{self.prompt_file or lang_file}' not found.")
except json.JSONDecodeError: except json.JSONDecodeError:
raise Exception("Error decoding JSON from the prompts file.") raise Exception("Error decoding JSON from the prompts file.")
@@ -46,15 +77,31 @@ class I18N(BaseModel):
return self return self
def slice(self, slice: str) -> str: def slice(self, slice: str) -> str:
"""Get a slice prompt by key."""
return self.retrieve("slices", slice) return self.retrieve("slices", slice)
def errors(self, error: str) -> str: def errors(self, error: str) -> str:
"""Get an error message by key."""
return self.retrieve("errors", error) return self.retrieve("errors", error)
def tools(self, tool: str) -> Union[str, Dict[str, str]]: def tools(self, tool: str) -> Union[str, Dict[str, str]]:
"""Get a tool prompt by key."""
return self.retrieve("tools", tool) return self.retrieve("tools", tool)
def retrieve(self, kind, key) -> str: def retrieve(self, kind: str, key: str) -> str:
"""
Retrieve a prompt by section and key.
Args:
kind: The section in the prompts file (e.g., "slices", "errors")
key: The specific key within the section
Returns:
The prompt text
Raises:
Exception: If the prompt is not found
"""
try: try:
return self._prompts[kind][key] return self._prompts[kind][key]
except Exception as _: except Exception as _: