Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,10 +795,7 @@ def _prepare_output_config(self, response_format: Any | None) -> dict[str, Any]
schema = copy.deepcopy(schema_src)
else:
if not isinstance(response_format, type) or not issubclass(response_format, BaseModel):
raise TypeError(
"response_format must be None, a dict JSON schema, "
"or a Pydantic BaseModel subclass."
)
raise TypeError("response_format must be None, a dict JSON schema, or a Pydantic BaseModel subclass.")
# response_format is a Pydantic model class
schema = response_format.model_json_schema()
name = response_format.__name__
Expand All @@ -817,9 +814,7 @@ def _prepare_output_config(self, response_format: Any | None) -> dict[str, Any]
return {
"textFormat": {
"type": "json_schema",
"structure": {
"jsonSchema": json_schema
},
"structure": {"jsonSchema": json_schema},
}
}

Expand All @@ -840,9 +835,7 @@ def walk(node: Any) -> None:
if node_id in visited:
return
visited.add(node_id)
if node.get("type") == "object" or (
"properties" in node and "type" not in node
):
if node.get("type") == "object" or ("properties" in node and "type" not in node):
existing = node.get("additionalProperties")
if existing is None or existing is True:
node["additionalProperties"] = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ async def test_chat_response_value_populated_streaming() -> None:

async def test_unsupported_model_validation_exception() -> None:
"""When a model doesn't support outputConfig, a clear error should be raised."""

class _FailingStubBedrockRuntime:
def converse(self, **kwargs: Any) -> dict[str, Any]:
# Simulate botocore ClientError for ValidationException
Expand Down
8 changes: 2 additions & 6 deletions python/packages/foundry_hosting/tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2118,15 +2118,11 @@ async def test_hosted_mcp_call_round_trip_does_not_orphan_function_call_output(s
assert resp2.json()["status"] == "completed"

second_call_messages = agent.run.call_args_list[1].kwargs["messages"]
mcp_call_contents = [
c for m in second_call_messages for c in m.contents if c.type == "mcp_server_tool_call"
]
mcp_call_contents = [c for m in second_call_messages for c in m.contents if c.type == "mcp_server_tool_call"]
mcp_result_contents = [
c for m in second_call_messages for c in m.contents if c.type == "mcp_server_tool_result"
]
function_result_contents = [
c for m in second_call_messages for c in m.contents if c.type == "function_result"
]
function_result_contents = [c for m in second_call_messages for c in m.contents if c.type == "function_result"]

assert len(mcp_call_contents) >= 1
assert len(mcp_result_contents) >= 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from ._orchestration_request_info import AgentApprovalExecutor
from ._participant_output_config import (
_MISSING, # pyright: ignore[reportPrivateUsage]
UNSET,
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
_coerce_intermediate_output_from, # pyright: ignore[reportPrivateUsage]
_ParticipantIntermediateOutputSelection, # pyright: ignore[reportPrivateUsage]
Expand Down Expand Up @@ -213,7 +213,7 @@ def __init__(
*,
participants: Sequence[SupportsAgentRun | Executor],
checkpoint_storage: CheckpointStorage | None = None,
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, _MISSING),
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, UNSET),
intermediate_output_from: _ParticipantIntermediateOutputSelection = None,
) -> None:
"""Initialize the ConcurrentBuilder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from ._orchestration_request_info import AgentApprovalExecutor
from ._orchestrator_helpers import clean_conversation_for_handoff
from ._participant_output_config import (
_MISSING, # pyright: ignore[reportPrivateUsage]
UNSET,
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
_coerce_intermediate_output_from, # pyright: ignore[reportPrivateUsage]
_ParticipantIntermediateOutputSelection, # pyright: ignore[reportPrivateUsage]
Expand Down Expand Up @@ -626,7 +626,7 @@ def __init__(
termination_condition: TerminationCondition | None = None,
max_rounds: int | None = None,
checkpoint_storage: CheckpointStorage | None = None,
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, _MISSING),
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, UNSET),
intermediate_output_from: _ParticipantIntermediateOutputSelection = None,
) -> None:
"""Initialize the GroupChatBuilder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
from ._base_group_chat_orchestrator import TerminationCondition
from ._orchestrator_helpers import clean_conversation_for_handoff
from ._participant_output_config import (
_MISSING, # pyright: ignore[reportPrivateUsage]
UNSET,
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
_coerce_intermediate_output_from, # pyright: ignore[reportPrivateUsage]
_ParticipantIntermediateOutputSelection, # pyright: ignore[reportPrivateUsage]
Expand Down Expand Up @@ -597,7 +597,7 @@ def __init__(
description: str | None = None,
checkpoint_storage: CheckpointStorage | None = None,
termination_condition: TerminationCondition | None = None,
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, _MISSING),
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, UNSET),
intermediate_output_from: _ParticipantIntermediateOutputSelection = None,
) -> None:
r"""Initialize a HandoffBuilder for creating conversational handoff workflows.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from agent_framework._workflows._workflow import Workflow
from agent_framework._workflows._workflow_builder import WorkflowBuilder
from agent_framework._workflows._workflow_context import WorkflowContext
from typing_extensions import Never
from typing_extensions import Never, Sentinel

from ._base_group_chat_orchestrator import (
BaseGroupChatOrchestrator,
Expand All @@ -39,7 +39,7 @@
ParticipantRegistry,
)
from ._participant_output_config import (
_MISSING, # pyright: ignore[reportPrivateUsage]
UNSET,
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
_coerce_intermediate_output_from, # pyright: ignore[reportPrivateUsage]
_ParticipantIntermediateOutputSelection, # pyright: ignore[reportPrivateUsage]
Expand Down Expand Up @@ -1411,13 +1411,13 @@ def __init__(
task_ledger_plan_update_prompt: str | None = None,
progress_ledger_prompt: str | None = None,
final_answer_prompt: str | None = None,
max_stall_count: int = 3,
max_stall_count: int | Sentinel = UNSET,
max_reset_count: int | None = None,
max_round_count: int | None = None,
# Existing params
enable_plan_review: bool = False,
checkpoint_storage: CheckpointStorage | None = None,
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, _MISSING),
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, UNSET),
Comment thread
moonbox3 marked this conversation as resolved.
intermediate_output_from: _ParticipantIntermediateOutputSelection = None,
) -> None:
"""Initialize the Magentic workflow builder.
Expand Down Expand Up @@ -1621,7 +1621,7 @@ def _set_manager(
progress_ledger_prompt: str | None = None,
final_answer_prompt: str | None = None,
# Limits
max_stall_count: int = 3,
max_stall_count: int | Sentinel = UNSET,
max_reset_count: int | None = None,
max_round_count: int | None = None,
) -> None:
Expand Down Expand Up @@ -1656,8 +1656,10 @@ def _set_manager(
"Exactly one of manager, manager_agent, manager_factory, or manager_agent_factory must be provided."
)

resolved_max_stall_count: int = 3 if max_stall_count is UNSET else cast(int, max_stall_count)

def _log_warning_if_constructor_args_provided() -> None:
if any(
if max_stall_count is not UNSET or any(
arg is not None
for arg in [
task_ledger,
Expand All @@ -1668,7 +1670,6 @@ def _log_warning_if_constructor_args_provided() -> None:
task_ledger_plan_update_prompt,
progress_ledger_prompt,
final_answer_prompt,
max_stall_count,
max_reset_count,
max_round_count,
]
Expand All @@ -1689,7 +1690,7 @@ def _log_warning_if_constructor_args_provided() -> None:
task_ledger_plan_update_prompt=task_ledger_plan_update_prompt,
progress_ledger_prompt=progress_ledger_prompt,
final_answer_prompt=final_answer_prompt,
max_stall_count=max_stall_count,
max_stall_count=resolved_max_stall_count,
max_reset_count=max_reset_count,
max_round_count=max_round_count,
)
Expand All @@ -1707,7 +1708,7 @@ def _log_warning_if_constructor_args_provided() -> None:
"task_ledger_plan_update_prompt": task_ledger_plan_update_prompt,
"progress_ledger_prompt": progress_ledger_prompt,
"final_answer_prompt": final_answer_prompt,
"max_stall_count": max_stall_count,
"max_stall_count": resolved_max_stall_count,
"max_reset_count": max_reset_count,
"max_round_count": max_round_count,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from agent_framework import SupportsAgentRun
from agent_framework._workflows._agent_utils import resolve_agent_id
from agent_framework._workflows._executor import Executor
from typing_extensions import Sentinel

_MISSING = object()
UNSET = Sentinel("UNSET")
_ALL_OUTPUTS: Literal["all"] = "all"
_ALL_OTHER_OUTPUTS: Literal["all_other"] = "all_other"
_ParticipantOutputSpecifier = str | SupportsAgentRun | Executor
Expand All @@ -20,10 +21,10 @@

def _coalesce_output_from( # pyright: ignore[reportUnusedFunction]
*,
output_from: Any = _MISSING,
output_from: Any = UNSET,
) -> _ParticipantOutputSelection:
"""Resolve orchestration output selection to ``output_from``."""
if output_from is not _MISSING:
if output_from is not UNSET:
return _coerce_output_from(output_from)
return None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from ._orchestration_request_info import AgentApprovalExecutor
from ._participant_output_config import (
_MISSING, # pyright: ignore[reportPrivateUsage]
UNSET,
_coalesce_output_from, # pyright: ignore[reportPrivateUsage]
_coerce_intermediate_output_from, # pyright: ignore[reportPrivateUsage]
_ParticipantIntermediateOutputSelection, # pyright: ignore[reportPrivateUsage]
Expand Down Expand Up @@ -99,7 +99,7 @@ def __init__(
participants: Sequence[SupportsAgentRun | Executor],
checkpoint_storage: CheckpointStorage | None = None,
chain_only_agent_responses: bool = False,
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, _MISSING),
output_from: Sequence[_ParticipantOutputSpecifier] | Literal["all"] | None = cast(Any, UNSET),
intermediate_output_from: _ParticipantIntermediateOutputSelection = None,
) -> None:
"""Initialize the SequentialBuilder.
Expand Down
42 changes: 42 additions & 0 deletions python/packages/orchestrations/tests/test_magentic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.

import logging
import sys
from collections.abc import AsyncIterable, Awaitable, Sequence
from dataclasses import dataclass
Expand Down Expand Up @@ -987,6 +988,33 @@ def manager_factory() -> MagenticManagerBase:
MagenticBuilder(participants=[agent], manager=manager, manager_factory=manager_factory)


def test_magentic_with_custom_manager_does_not_warn_without_standard_manager_options(caplog: Any) -> None:
caplog.set_level(logging.WARNING, logger="agent_framework_orchestrations._magentic")

MagenticBuilder(participants=[StubAgent("agentA", "reply")], manager=FakeManager())

assert "Custom manager provided; all other manager arguments will be ignored." not in caplog.text


def test_magentic_with_custom_manager_factory_does_not_warn_without_standard_manager_options(caplog: Any) -> None:
caplog.set_level(logging.WARNING, logger="agent_framework_orchestrations._magentic")

def manager_factory() -> MagenticManagerBase:
return FakeManager()

MagenticBuilder(participants=[StubAgent("agentA", "reply")], manager_factory=manager_factory)

assert "Custom manager provided; all other manager arguments will be ignored." not in caplog.text


def test_magentic_with_custom_manager_warns_when_standard_manager_option_is_provided(caplog: Any) -> None:
caplog.set_level(logging.WARNING, logger="agent_framework_orchestrations._magentic")

MagenticBuilder(participants=[StubAgent("agentA", "reply")], manager=FakeManager(), max_stall_count=3)

assert "Custom manager provided; all other manager arguments will be ignored." in caplog.text


async def test_magentic_with_manager_factory():
"""Test workflow creation using manager_factory."""
factory_call_count = 0
Expand Down Expand Up @@ -1037,6 +1065,20 @@ def agent_factory() -> SupportsAgentRun:
assert event_count > 0


def test_magentic_agent_factory_uses_default_max_stall_count() -> None:
def agent_factory() -> SupportsAgentRun:
return cast(SupportsAgentRun, StubManagerAgent())

participant = StubAgent("agentA", "reply from agentA")
workflow = MagenticBuilder(participants=[participant], manager_agent_factory=agent_factory).build()

orchestrator = next(e for e in workflow.executors.values() if isinstance(e, MagenticOrchestrator))
manager = orchestrator._manager # type: ignore[reportPrivateUsage]

assert isinstance(manager, StandardMagenticManager)
assert manager.max_stall_count == 3


async def test_magentic_manager_factory_reusable_builder():
"""Test that the builder can be reused to build multiple workflows with manager factory."""
factory_call_count = 0
Expand Down
Loading
Loading