diff --git a/src/crewai/task.py b/src/crewai/task.py index 030bce779..c96d1aa5a 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -431,7 +431,9 @@ class Task(BaseModel): content = ( json_output if json_output - else pydantic_output.model_dump_json() if pydantic_output else result + else pydantic_output.model_dump_json() + if pydantic_output + else result ) self._save_file(content) @@ -452,7 +454,7 @@ class Task(BaseModel): return "\n".join(tasks_slices) def interpolate_inputs_and_add_conversation_history( - self, inputs: Dict[str, Union[str, int, float]] + self, inputs: Dict[str, Union[str, int, float, dict, list]] ) -> None: """Interpolate inputs into the task description, expected output, and output file path. Add conversation history if present. @@ -524,7 +526,9 @@ class Task(BaseModel): ) def interpolate_only( - self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]] + self, + input_string: Optional[str], + inputs: Dict[str, Union[str, int, float, dict, list]], ) -> str: """Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched. @@ -551,14 +555,16 @@ class Task(BaseModel): raise ValueError( "Inputs dictionary cannot be empty when interpolating variables" ) - try: # Validate input types for key, value in inputs.items(): - if not isinstance(value, (str, int, float)): + print("key", key, value) + if not isinstance(value, (str, int, float, dict, list)): raise ValueError( - f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}" + f"Value for key '{key}' must be a string, integer, float, dict, or list, got {type(value).__name__}" ) + if isinstance(value, (dict, list)): + inputs[key] = json.dumps(value, ensure_ascii=False) escaped_string = input_string.replace("{", "{{").replace("}", "}}") diff --git a/tests/task_test.py b/tests/task_test.py index 59e58dcca..f31895e24 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -441,9 +441,9 @@ def test_output_pydantic_to_another_task(): crew = Crew(agents=[scorer], tasks=[task1, task2], verbose=True) result = crew.kickoff() pydantic_result = result.pydantic - assert isinstance( - pydantic_result, ScoreOutput - ), "Expected pydantic result to be of type ScoreOutput" + assert isinstance(pydantic_result, ScoreOutput), ( + "Expected pydantic result to be of type ScoreOutput" + ) assert pydantic_result.score == 5 @@ -779,6 +779,43 @@ def test_interpolate_only(): assert result == no_placeholders +def test_interpolate_only_with_dict_inside_expected_output(): + """Test the interpolate_only method for various scenarios including JSON structure preservation.""" + task = Task( + description="Unused in this test", + expected_output="Unused in this test: {questions}", + ) + + json_string = '{"questions": {"main_question": "What is the user\'s name?", "secondary_question": "What is the user\'s age?"}}' + result = task.interpolate_only( + input_string=json_string, + inputs={ + "questions": { + "main_question": "What is the user's name?", + "secondary_question": "What is the user's age?", + } + }, + ) + assert '"main_question": "What is the user\'s name?"' in result + assert '"secondary_question": "What is the user\'s age?"' in result + assert result == json_string + + normal_string = "Hello {name}, welcome to {place}!" + result = task.interpolate_only( + input_string=normal_string, inputs={"name": "John", "place": "CrewAI"} + ) + assert result == "Hello John, welcome to CrewAI!" + + result = task.interpolate_only(input_string="", inputs={"unused": "value"}) + assert result == "" + + no_placeholders = "Hello, this is a test" + result = task.interpolate_only( + input_string=no_placeholders, inputs={"unused": "value"} + ) + assert result == no_placeholders + + def test_task_output_str_with_pydantic(): from crewai.tasks.output_format import OutputFormat @@ -870,9 +907,9 @@ def test_key(): assert task.key == hash, "The key should be the hash of the description." task.interpolate_inputs_and_add_conversation_history(inputs={"topic": "AI"}) - assert ( - task.key == hash - ), "The key should be the hash of the non-interpolated description." + assert task.key == hash, ( + "The key should be the hash of the non-interpolated description." + ) def test_output_file_validation():