diff --git a/lib/crewai/src/crewai/agent/utils.py b/lib/crewai/src/crewai/agent/utils.py index 20eb06b47..297322a0c 100644 --- a/lib/crewai/src/crewai/agent/utils.py +++ b/lib/crewai/src/crewai/agent/utils.py @@ -74,13 +74,17 @@ def build_task_prompt_with_schema(task: Task, task_prompt: str) -> str: 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_dict = generate_model_description( + task.output_json, strip_null_types=False + ) schema = json.dumps(schema_dict["json_schema"]["schema"], indent=2) 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_dict = generate_model_description( + task.output_pydantic, strip_null_types=False + ) schema = json.dumps(schema_dict["json_schema"]["schema"], indent=2) task_prompt += "\n" + I18N_DEFAULT.slice( "formatted_task_instructions" diff --git a/lib/crewai/tests/agents/test_agent_utils.py b/lib/crewai/tests/agents/test_agent_utils.py new file mode 100644 index 000000000..3d2a2152e --- /dev/null +++ b/lib/crewai/tests/agents/test_agent_utils.py @@ -0,0 +1,41 @@ +"""Tests for crewai.agent.utils prompt-building helpers.""" + +from __future__ import annotations + +import json + +import pytest +from pydantic import BaseModel, Field + +from crewai import Task +from crewai.agent.utils import build_task_prompt_with_schema + + +class _Output(BaseModel): + name: str + note: str | None = Field(default=None) + + +@pytest.mark.parametrize("output_attribute", ["output_pydantic", "output_json"]) +def test_optional_fields_stay_nullable_in_the_prompt_schema( + output_attribute: str, +) -> None: + """An Optional field must still be expressible as null in the prompt schema. + + The provider-side response schema generated from the same model allows null, + so stripping it here hands the model two contradictory contracts and leaves + it no way to say "not applicable". Both output attributes embed a schema in + the prompt, so both are pinned. + """ + task = Task(description="d", expected_output="e", **{output_attribute: _Output}) + + prompt = build_task_prompt_with_schema(task, "") + + start = prompt.index("{") + end = prompt.rindex("}", start) + 1 + schema = json.loads(prompt[start:end]) + + assert {entry["type"] for entry in schema["properties"]["note"]["anyOf"]} == { + "string", + "null", + } diff --git a/lib/crewai/tests/llms/bedrock/test_bedrock.py b/lib/crewai/tests/llms/bedrock/test_bedrock.py index 0127ffd62..87c0d6c09 100644 --- a/lib/crewai/tests/llms/bedrock/test_bedrock.py +++ b/lib/crewai/tests/llms/bedrock/test_bedrock.py @@ -602,24 +602,31 @@ def test_bedrock_tool_conversion(): assert "inputSchema" in bedrock_tools[0]["toolSpec"] -def test_bedrock_environment_variable_credentials(bedrock_mocks): - """ - Test that AWS credentials are properly loaded from environment - """ - mock_session_class, _ = bedrock_mocks +def test_bedrock_environment_variable_credentials(): + """Pass AWS credentials and region from the environment to boto3.""" + with ( + patch.dict( + os.environ, + { + "AWS_ACCESS_KEY_ID": "test-access-key-123", + "AWS_SECRET_ACCESS_KEY": "test-secret-key-456", + "AWS_DEFAULT_REGION": "eu-west-1", + }, + clear=False, + ), + patch( + "crewai.llms.providers.bedrock.completion.Session" + ) as mock_session_class, + ): + mock_session_class.return_value.client.return_value = MagicMock() + LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0") - mock_session_class.reset_mock() - - with patch.dict(os.environ, { - "AWS_ACCESS_KEY_ID": "test-access-key-123", - "AWS_SECRET_ACCESS_KEY": "test-secret-key-456" - }): - llm = LLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0") - - assert mock_session_class.called - call_kwargs = mock_session_class.call_args[1] if mock_session_class.call_args else {} - assert call_kwargs.get('aws_access_key_id') == "test-access-key-123" - assert call_kwargs.get('aws_secret_access_key') == "test-secret-key-456" + mock_session_class.assert_called_once_with( + aws_access_key_id="test-access-key-123", + aws_secret_access_key="test-secret-key-456", + aws_session_token=None, + region_name="eu-west-1", + ) def test_bedrock_token_usage_tracking():