From 5704ea08ebc4a724b7c2b3af74efe94c1f06cef4 Mon Sep 17 00:00:00 2001 From: monkscode <87177225+monkscode@users.noreply.github.com> Date: Thu, 10 Sep 2026 13:46:16 +0530 Subject: [PATCH 1/2] fix(agents): keep null in the task output schema embedded in the prompt (#6775) * fix(agents): keep null in the task output schema embedded in the prompt build_task_prompt_with_schema embeds the task output schema into the prompt via generate_model_description, whose strip_null_types defaults to True. Combined with ensure_all_properties_required, an Optional[str] = None field reaches the model as a required, non-nullable string, contradicting the provider-side response schema generated from the same model. That sanitizer targets OpenAI strict function-calling schemas. This call site produces prompt prose, where those constraints do not apply. Pass strip_null_types=False, matching the existing call for tool schemas in utilities/agent_utils.py. Fixes #6774 * test(agents): cover the output_json branch of the prompt schema build_task_prompt_with_schema embeds a schema on both the output_json and the output_pydantic branch, and this PR changes both. The regression test only built a Task with output_pydantic, so the output_json branch shipped unpinned. Parameterize over both output attributes. Checked on this branch with the fix reverted: both cases fail on the missing anyOf, and both pass with it. Also compare the anyOf members as a set, so member order is not part of the test contract. --------- Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> --- lib/crewai/src/crewai/agent/utils.py | 8 +++- lib/crewai/tests/agents/test_agent_utils.py | 41 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 lib/crewai/tests/agents/test_agent_utils.py 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", + } From c5759ce854337167e19ab96621c0565bc820e468 Mon Sep 17 00:00:00 2001 From: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:37:50 +0530 Subject: [PATCH 2/2] test(bedrock): verify environment credentials (#7375) * test(bedrock): verify environment credentials * test(bedrock): isolate credential environment test --- lib/crewai/tests/llms/bedrock/test_bedrock.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) 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():