From 7ad0357eaa51d18adf744f49a1b4bafd0f994823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Thu, 2 May 2024 04:25:11 -0300 Subject: [PATCH] adding initial support for external prompt file --- src/crewai/crew.py | 11 ++++----- src/crewai/telemetry/telemetry.py | 6 ++--- src/crewai/utilities/i18n.py | 38 ++++++++++++----------------- src/crewai/utilities/prompts.py | 2 +- tests/utilities/prompts.json | 40 +++++++++++++++++++++++++++++++ tests/utilities/test_i18n.py | 29 +++++++++++++--------- 6 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 tests/utilities/prompts.json diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 3c3cdbbfb..86d5b8be3 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -44,6 +44,7 @@ class Crew(BaseModel): verbose: Indicates the verbosity level for logging during execution. config: Configuration settings for the crew. max_rpm: Maximum number of requests per minute for the crew execution to be respected. + prompt_file: Path to the prompt json file to be used for the crew. id: A unique identifier for the crew instance. full_output: Whether the crew should return the full output with all tasks outputs or just the final output. task_callback: Callback to be executed after each task for every agents execution. @@ -111,13 +112,9 @@ class Crew(BaseModel): default=None, description="Maximum number of requests per minute for the crew execution to be respected.", ) - language: str = Field( - default="en", - description="Language used for the crew, defaults to English.", - ) - language_file: str = Field( + prompt_file: str = Field( default=None, - description="Path to the language file to be used for the crew.", + description="Path to the prompt json file to be used for the crew.", ) output_log_file: Optional[Union[bool, str]] = Field( default=False, @@ -238,7 +235,7 @@ class Crew(BaseModel): self._interpolate_inputs(inputs) self._set_tasks_callbacks() - i18n = I18N(language=self.language, language_file=self.language_file) + i18n = I18N(prompt_file=self.prompt_file) for agent in self.agents: agent.i18n = i18n diff --git a/src/crewai/telemetry/telemetry.py b/src/crewai/telemetry/telemetry.py index 45c04e70d..75d7b023d 100644 --- a/src/crewai/telemetry/telemetry.py +++ b/src/crewai/telemetry/telemetry.py @@ -88,7 +88,7 @@ class Telemetry: self._add_attribute(span, "python_version", platform.python_version()) self._add_attribute(span, "crew_id", str(crew.id)) self._add_attribute(span, "crew_process", crew.process) - self._add_attribute(span, "crew_language", crew.language) + self._add_attribute(span, "crew_language", crew.prompt_file) self._add_attribute(span, "crew_memory", crew.memory) self._add_attribute(span, "crew_number_of_tasks", len(crew.tasks)) self._add_attribute(span, "crew_number_of_agents", len(crew.agents)) @@ -103,7 +103,7 @@ class Telemetry: "verbose?": agent.verbose, "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, - "i18n": agent.i18n.language, + "i18n": agent.i18n.prompt_file, "llm": json.dumps(self._safe_llm_attributes(agent.llm)), "delegation_enabled?": agent.allow_delegation, "tools_names": [ @@ -232,7 +232,7 @@ class Telemetry: "verbose?": agent.verbose, "max_iter": agent.max_iter, "max_rpm": agent.max_rpm, - "i18n": agent.i18n.language, + "i18n": agent.i18n.prompt_file, "llm": json.dumps(self._safe_llm_attributes(agent.llm)), "delegation_enabled?": agent.allow_delegation, "tools_names": [ diff --git a/src/crewai/utilities/i18n.py b/src/crewai/utilities/i18n.py index 649f58bef..9b909783a 100644 --- a/src/crewai/utilities/i18n.py +++ b/src/crewai/utilities/i18n.py @@ -6,40 +6,32 @@ from pydantic import BaseModel, Field, PrivateAttr, ValidationError, model_valid class I18N(BaseModel): - _translations: Dict[str, Dict[str, str]] = PrivateAttr() - language_file: Optional[str] = Field( + _prompts: Dict[str, Dict[str, str]] = PrivateAttr() + prompt_file: Optional[str] = Field( default=None, - description="Path to the translation file to load", - ) - language: Optional[str] = Field( - default="en", - description="Language used to load translations", + description="Path to the prompt_file file to load", ) @model_validator(mode="after") - def load_translation(self) -> "I18N": - """Load translations from a JSON file based on the specified language.""" + def load_prompts(self) -> "I18N": + """Load prompts from a JSON file.""" try: - if self.language_file: - with open(self.language_file, "r") as f: - self._translations = json.load(f) + if self.prompt_file: + with open(self.prompt_file, "r") as f: + self._prompts = json.load(f) else: dir_path = os.path.dirname(os.path.realpath(__file__)) - prompts_path = os.path.join( - dir_path, f"../translations/{self.language}.json" - ) + prompts_path = os.path.join(dir_path, f"../translations/en.json") with open(prompts_path, "r") as f: - self._translations = json.load(f) + self._prompts = json.load(f) except FileNotFoundError: - raise ValidationError( - f"Translation file for language '{self.language}' not found." - ) + raise ValidationError(f"Prompt file '{self.prompt_file}' not found.") except json.JSONDecodeError: raise ValidationError(f"Error decoding JSON from the prompts file.") - if not self._translations: - self._translations = {} + if not self._prompts: + self._prompts = {} return self @@ -54,6 +46,6 @@ class I18N(BaseModel): def retrieve(self, kind, key) -> str: try: - return self._translations[kind][key] + return self._prompts[kind][key] except: - raise ValidationError(f"Translation for '{kind}':'{key}' not found.") + raise ValidationError(f"Prompt for '{kind}':'{key}' not found.") diff --git a/src/crewai/utilities/prompts.py b/src/crewai/utilities/prompts.py index 48c3f5a22..9df311233 100644 --- a/src/crewai/utilities/prompts.py +++ b/src/crewai/utilities/prompts.py @@ -7,7 +7,7 @@ from crewai.utilities import I18N class Prompts(BaseModel): - """Manages and generates prompts for a generic agent with support for different languages.""" + """Manages and generates prompts for a generic agent.""" i18n: I18N = Field(default=I18N()) tools: list[Any] = Field(default=[]) diff --git a/tests/utilities/prompts.json b/tests/utilities/prompts.json new file mode 100644 index 000000000..55bbfae8f --- /dev/null +++ b/tests/utilities/prompts.json @@ -0,0 +1,40 @@ +{ + "hierarchical_manager_agent": { + "role": "Lorem ipsum dolor sit amet", + "goal": "Lorem ipsum dolor sit amet", + "backstory": "Lorem ipsum dolor sit amet." + }, + "planning_manager_agent": { + "role": "Lorem ipsum dolor sit amet", + "goal": "Lorem ipsum dolor sit amet", + "backstory": "Lorem ipsum dolor sit amet." + }, + "slices": { + "observation": "Lorem ipsum dolor sit amet", + "task": "Lorem ipsum dolor sit amet", + "memory": "Lorem ipsum dolor sit amet", + "role_playing": "Lorem ipsum dolor sit amet", + "tools": "Lorem ipsum dolor sit amet", + "no_tools": "Lorem ipsum dolor sit amet", + "format": "Lorem ipsum dolor sit amet", + "final_answer_format": "Lorem ipsum dolor sit amet", + "format_without_tools": "Lorem ipsum dolor sit amet", + "task_with_context": "Lorem ipsum dolor sit amet", + "expected_output": "Lorem ipsum dolor sit amet", + "human_feedback": "Lorem ipsum dolor sit amet", + "getting_input": "Lorem ipsum dolor sit amet " + }, + "errors": { + "force_final_answer": "Lorem ipsum dolor sit amet", + "agent_tool_unexsiting_coworker": "Lorem ipsum dolor sit amet", + "task_repeated_usage": "Lorem ipsum dolor sit amet", + "tool_usage_error": "Lorem ipsum dolor sit amet", + "tool_arguments_error": "Lorem ipsum dolor sit amet", + "wrong_tool_name": "Lorem ipsum dolor sit amet", + "tool_usage_exception": "Lorem ipsum dolor sit amet" + }, + "tools": { + "delegate_work": "Lorem ipsum dolor sit amet", + "ask_question": "Lorem ipsum dolor sit amet" + } +} diff --git a/tests/utilities/test_i18n.py b/tests/utilities/test_i18n.py index 54836dd42..2e8a2bee1 100644 --- a/tests/utilities/test_i18n.py +++ b/tests/utilities/test_i18n.py @@ -3,32 +3,39 @@ import pytest from crewai.utilities.i18n import I18N -def test_load_translation(): - i18n = I18N(language="en") - i18n.load_translation() +def test_load_prompts(): + i18n = I18N() + i18n.load_prompts() assert i18n._translations is not None def test_slice(): - i18n = I18N(language="en") - i18n.load_translation() + i18n = I18N() + i18n.load_prompts() assert isinstance(i18n.slice("role_playing"), str) def test_tools(): - i18n = I18N(language="en") - i18n.load_translation() + i18n = I18N() + i18n.load_prompts() assert isinstance(i18n.tools("ask_question"), str) def test_retrieve(): - i18n = I18N(language="en") - i18n.load_translation() + i18n = I18N() + i18n.load_prompts() assert isinstance(i18n.retrieve("slices", "role_playing"), str) def test_retrieve_not_found(): - i18n = I18N(language="en") - i18n.load_translation() + i18n = I18N() + i18n.load_prompts() with pytest.raises(Exception): i18n.retrieve("nonexistent_kind", "nonexistent_key") + + +def test_prompt_file(): + i18n = I18N(prompt_file="tests/utilities/en.json") + i18n.load_prompts() + assert isinstance(i18n.retrieve("slices", "role_playing"), str) + assert i18n.retrieve("slices", "role_playing") == "Lorem ipsum dolor sit amet"