diff --git a/python/packages/core/agent_framework/_workflows/_group_chat.py b/python/packages/core/agent_framework/_workflows/_group_chat.py index e1ba5156ee..d75b805514 100644 --- a/python/packages/core/agent_framework/_workflows/_group_chat.py +++ b/python/packages/core/agent_framework/_workflows/_group_chat.py @@ -397,7 +397,7 @@ async def _invoke_agent_helper(conversation: list[ChatMessage]) -> AgentOrchestr agent_response = await self._agent.run( messages=conversation, thread=self._thread, - response_format=AgentOrchestrationOutput, + options={"response_format": AgentOrchestrationOutput}, ) # Parse and validate the structured output agent_orchestration_output = AgentOrchestrationOutput.model_validate_json(agent_response.text) diff --git a/python/packages/core/agent_framework/_workflows/_handoff.py b/python/packages/core/agent_framework/_workflows/_handoff.py index 9e9e115bd4..9db4ce8607 100644 --- a/python/packages/core/agent_framework/_workflows/_handoff.py +++ b/python/packages/core/agent_framework/_workflows/_handoff.py @@ -286,32 +286,36 @@ def _clone_chat_agent(self, agent: ChatAgent) -> ChatAgent: logit_bias = options.get("logit_bias") metadata = options.get("metadata") + # Disable parallel tool calls to prevent the agent from invoking multiple handoff tools at once. + cloned_options: dict[str, Any] = { + "allow_multiple_tool_calls": False, + "frequency_penalty": options.get("frequency_penalty"), + "instructions": options.get("instructions"), + "logit_bias": dict(logit_bias) if logit_bias else None, + "max_tokens": options.get("max_tokens"), + "metadata": dict(metadata) if metadata else None, + "model_id": options.get("model_id"), + "presence_penalty": options.get("presence_penalty"), + "response_format": options.get("response_format"), + "seed": options.get("seed"), + "stop": options.get("stop"), + "store": options.get("store"), + "temperature": options.get("temperature"), + "tool_choice": options.get("tool_choice"), + "tools": all_tools if all_tools else None, + "top_p": options.get("top_p"), + "user": options.get("user"), + } + return ChatAgent( chat_client=agent.chat_client, - instructions=options.get("instructions"), id=agent.id, name=agent.name, description=agent.description, chat_message_store_factory=agent.chat_message_store_factory, context_providers=agent.context_provider, middleware=middleware, - # Disable parallel tool calls to prevent the agent from invoking multiple handoff tools at once. - allow_multiple_tool_calls=False, - frequency_penalty=options.get("frequency_penalty"), - logit_bias=dict(logit_bias) if logit_bias else None, - max_tokens=options.get("max_tokens"), - metadata=dict(metadata) if metadata else None, - model_id=options.get("model_id"), - presence_penalty=options.get("presence_penalty"), - response_format=options.get("response_format"), - seed=options.get("seed"), - stop=options.get("stop"), - store=options.get("store"), - temperature=options.get("temperature"), - tool_choice=options.get("tool_choice"), # type: ignore[arg-type] - tools=all_tools if all_tools else None, - top_p=options.get("top_p"), - user=options.get("user"), + default_options=cloned_options, # type: ignore[arg-type] ) def _apply_auto_tools(self, agent: ChatAgent, targets: Sequence[HandoffConfiguration]) -> None: diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_response_format.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_response_format.py index 7b2550f52c..91065b75fe 100644 --- a/python/samples/getting_started/agents/azure_ai/azure_ai_with_response_format.py +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_response_format.py @@ -34,7 +34,7 @@ async def main() -> None: name="ProductMarketerAgent", instructions="Return launch briefs as structured JSON.", # Specify type to use as response - response_format=ReleaseBrief, + options={"response_format": ReleaseBrief}, ) query = "Draft a launch brief for the Contoso Note app." diff --git a/python/samples/getting_started/agents/openai/openai_responses_client_with_structured_output.py b/python/samples/getting_started/agents/openai/openai_responses_client_with_structured_output.py index b84a7b5d97..ba208da94b 100644 --- a/python/samples/getting_started/agents/openai/openai_responses_client_with_structured_output.py +++ b/python/samples/getting_started/agents/openai/openai_responses_client_with_structured_output.py @@ -35,7 +35,7 @@ async def non_streaming_example() -> None: print(f"User: {query}") # Get structured response from the agent using response_format parameter - result = await agent.run(query, response_format=OutputStruct) + result = await agent.run(query, options={"response_format": OutputStruct}) # Access the structured output directly from the response value if result.value: @@ -63,7 +63,7 @@ async def streaming_example() -> None: # Get structured response from streaming agent using AgentResponse.from_agent_response_generator # This method collects all streaming updates and combines them into a single AgentResponse result = await AgentResponse.from_agent_response_generator( - agent.run_stream(query, response_format=OutputStruct), + agent.run_stream(query, options={"response_format": OutputStruct}), output_format_type=OutputStruct, ) diff --git a/python/samples/getting_started/azure_functions/06_multi_agent_orchestration_conditionals/function_app.py b/python/samples/getting_started/azure_functions/06_multi_agent_orchestration_conditionals/function_app.py index 2ee445d423..adae82d1f0 100644 --- a/python/samples/getting_started/azure_functions/06_multi_agent_orchestration_conditionals/function_app.py +++ b/python/samples/getting_started/azure_functions/06_multi_agent_orchestration_conditionals/function_app.py @@ -99,7 +99,7 @@ def spam_detection_orchestration(context: DurableOrchestrationContext): spam_result_raw = yield spam_agent.run( messages=spam_prompt, thread=spam_thread, - response_format=SpamDetectionResult, + options={"response_format": SpamDetectionResult}, ) spam_result = cast(SpamDetectionResult, spam_result_raw.value) @@ -120,7 +120,7 @@ def spam_detection_orchestration(context: DurableOrchestrationContext): email_result_raw = yield email_agent.run( messages=email_prompt, thread=email_thread, - response_format=EmailResponse, + options={"response_format": EmailResponse}, ) email_result = cast(EmailResponse, email_result_raw.value) diff --git a/python/samples/getting_started/azure_functions/07_single_agent_orchestration_hitl/function_app.py b/python/samples/getting_started/azure_functions/07_single_agent_orchestration_hitl/function_app.py index c8e2bbaa9c..e6c1c25d47 100644 --- a/python/samples/getting_started/azure_functions/07_single_agent_orchestration_hitl/function_app.py +++ b/python/samples/getting_started/azure_functions/07_single_agent_orchestration_hitl/function_app.py @@ -98,7 +98,7 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext): initial_raw = yield writer.run( messages=f"Write a short article about '{payload.topic}'.", thread=writer_thread, - response_format=GeneratedContent, + options={"response_format": GeneratedContent}, ) content = initial_raw.value @@ -135,9 +135,7 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext): ) return {"content": content.content} - context.set_custom_status( - "Content rejected by human reviewer. Incorporating feedback and regenerating..." - ) + context.set_custom_status("Content rejected by human reviewer. Incorporating feedback and regenerating...") rewrite_prompt = ( "The content was rejected by a human reviewer. Please rewrite the article incorporating their feedback.\n\n" f"Human Feedback: {approval_payload.feedback or 'No feedback provided.'}" @@ -145,7 +143,7 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext): rewritten_raw = yield writer.run( messages=rewrite_prompt, thread=writer_thread, - response_format=GeneratedContent, + options={"response_format": GeneratedContent}, ) rewritten_value = rewritten_raw.value @@ -157,9 +155,7 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext): context.set_custom_status( f"Human approval timed out after {payload.approval_timeout_hours} hour(s). Treating as rejection." ) - raise TimeoutError( - f"Human approval timed out after {payload.approval_timeout_hours} hour(s)." - ) + raise TimeoutError(f"Human approval timed out after {payload.approval_timeout_hours} hour(s).") raise RuntimeError(f"Content could not be approved after {payload.max_review_attempts} iteration(s).") diff --git a/python/samples/getting_started/chat_client/azure_responses_client.py b/python/samples/getting_started/chat_client/azure_responses_client.py index 158a2eb78c..ec15ee7723 100644 --- a/python/samples/getting_started/chat_client/azure_responses_client.py +++ b/python/samples/getting_started/chat_client/azure_responses_client.py @@ -41,13 +41,13 @@ async def main() -> None: print(f"User: {message}") if stream: response = await ChatResponse.from_chat_response_generator( - client.get_streaming_response(message, tools=get_weather, response_format=OutputStruct), + client.get_streaming_response(message, tools=get_weather, options={"response_format": OutputStruct}), output_format_type=OutputStruct, ) print(f"Assistant: {response.value}") else: - response = await client.get_response(message, tools=get_weather, response_format=OutputStruct) + response = await client.get_response(message, tools=get_weather, options={"response_format": OutputStruct}) print(f"Assistant: {response.value}") diff --git a/python/samples/getting_started/context_providers/simple_context_provider.py b/python/samples/getting_started/context_providers/simple_context_provider.py index 9e0eaefabd..f69d5b96c6 100644 --- a/python/samples/getting_started/context_providers/simple_context_provider.py +++ b/python/samples/getting_started/context_providers/simple_context_provider.py @@ -48,7 +48,7 @@ async def invoked( messages=request_messages, # type: ignore instructions="Extract the user's name and age from the message if present. " "If not present return nulls.", - response_format=UserInfo, + options={"response_format": UserInfo}, ) # Update user info with extracted data diff --git a/python/samples/getting_started/devui/workflow_agents/workflow.py b/python/samples/getting_started/devui/workflow_agents/workflow.py index 3b304fd690..dc70112783 100644 --- a/python/samples/getting_started/devui/workflow_agents/workflow.py +++ b/python/samples/getting_started/devui/workflow_agents/workflow.py @@ -86,7 +86,7 @@ def is_approved(message: Any) -> bool: "- feedback: concise, actionable feedback\n" "- clarity, completeness, accuracy, structure: individual scores (0-100)" ), - response_format=ReviewResult, + default_options={"response_format": ReviewResult}, ) # Create Editor agent - improves content based on feedback diff --git a/python/samples/getting_started/workflows/agents/workflow_as_agent_reflection_pattern.py b/python/samples/getting_started/workflows/agents/workflow_as_agent_reflection_pattern.py index aa9da00201..bafe55dd4e 100644 --- a/python/samples/getting_started/workflows/agents/workflow_as_agent_reflection_pattern.py +++ b/python/samples/getting_started/workflows/agents/workflow_as_agent_reflection_pattern.py @@ -100,7 +100,7 @@ class _Response(BaseModel): messages.append(ChatMessage(role=Role.USER, text="Please review the agent's responses.")) print("Reviewer: Sending review request to LLM...") - response = await self._chat_client.get_response(messages=messages, response_format=_Response) + response = await self._chat_client.get_response(messages=messages, options={"response_format": _Response}) parsed = _Response.model_validate_json(response.messages[-1].text) diff --git a/python/samples/getting_started/workflows/control-flow/edge_condition.py b/python/samples/getting_started/workflows/control-flow/edge_condition.py index b8459480cd..061fcf0a1d 100644 --- a/python/samples/getting_started/workflows/control-flow/edge_condition.py +++ b/python/samples/getting_started/workflows/control-flow/edge_condition.py @@ -138,7 +138,7 @@ def create_spam_detector_agent() -> ChatAgent: "Include the original email content in email_content." ), name="spam_detection_agent", - response_format=DetectionResult, + default_options={"response_format": DetectionResult}, ) @@ -152,7 +152,7 @@ def create_email_assistant_agent() -> ChatAgent: "Return JSON with a single field 'response' containing the drafted reply." ), name="email_assistant_agent", - response_format=EmailResponse, + default_options={"response_format": EmailResponse}, ) diff --git a/python/samples/getting_started/workflows/control-flow/multi_selection_edge_group.py b/python/samples/getting_started/workflows/control-flow/multi_selection_edge_group.py index bd60cf449d..9b33f7d979 100644 --- a/python/samples/getting_started/workflows/control-flow/multi_selection_edge_group.py +++ b/python/samples/getting_started/workflows/control-flow/multi_selection_edge_group.py @@ -190,7 +190,7 @@ def create_email_analysis_agent() -> ChatAgent: "and 'reason' (string)." ), name="email_analysis_agent", - response_format=AnalysisResultAgent, + default_options={"response_format": AnalysisResultAgent}, ) @@ -199,7 +199,7 @@ def create_email_assistant_agent() -> ChatAgent: return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent( instructions=("You are an email assistant that helps users draft responses to emails with professionalism."), name="email_assistant_agent", - response_format=EmailResponse, + default_options={"response_format": EmailResponse}, ) @@ -208,7 +208,7 @@ def create_email_summary_agent() -> ChatAgent: return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent( instructions=("You are an assistant that helps users summarize emails."), name="email_summary_agent", - response_format=EmailSummaryModel, + default_options={"response_format": EmailSummaryModel}, ) @@ -243,7 +243,8 @@ def select_targets(analysis: AnalysisResult, target_ids: list[str]) -> list[str] ) workflow = ( - workflow_builder.set_start_executor("store_email") + workflow_builder + .set_start_executor("store_email") .add_edge("store_email", "email_analysis_agent") .add_edge("email_analysis_agent", "to_analysis_result") .add_multi_selection_edge_group( diff --git a/python/samples/getting_started/workflows/control-flow/switch_case_edge_group.py b/python/samples/getting_started/workflows/control-flow/switch_case_edge_group.py index 63ea8796cd..1e0b92257d 100644 --- a/python/samples/getting_started/workflows/control-flow/switch_case_edge_group.py +++ b/python/samples/getting_started/workflows/control-flow/switch_case_edge_group.py @@ -162,7 +162,7 @@ def create_spam_detection_agent() -> ChatAgent: "and 'reason' (string)." ), name="spam_detection_agent", - response_format=DetectionResultAgent, + default_options={"response_format": DetectionResultAgent}, ) @@ -171,7 +171,7 @@ def create_email_assistant_agent() -> ChatAgent: return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent( instructions=("You are an email assistant that helps users draft responses to emails with professionalism."), name="email_assistant_agent", - response_format=EmailResponse, + default_options={"response_format": EmailResponse}, ) diff --git a/python/samples/getting_started/workflows/declarative/customer_support/main.py b/python/samples/getting_started/workflows/declarative/customer_support/main.py index f3c8c69c10..9310d6b1f2 100644 --- a/python/samples/getting_started/workflows/declarative/customer_support/main.py +++ b/python/samples/getting_started/workflows/declarative/customer_support/main.py @@ -171,28 +171,28 @@ async def main() -> None: self_service_agent = chat_client.create_agent( name="SelfServiceAgent", instructions=SELF_SERVICE_INSTRUCTIONS, - response_format=SelfServiceResponse, + default_options={"response_format": SelfServiceResponse}, ) ticketing_agent = chat_client.create_agent( name="TicketingAgent", instructions=TICKETING_INSTRUCTIONS, tools=plugin.get_functions(), - response_format=TicketingResponse, + default_options={"response_format": TicketingResponse}, ) routing_agent = chat_client.create_agent( name="TicketRoutingAgent", instructions=TICKET_ROUTING_INSTRUCTIONS, tools=[plugin.get_ticket], - response_format=RoutingResponse, + default_options={"response_format": RoutingResponse}, ) windows_support_agent = chat_client.create_agent( name="WindowsSupportAgent", instructions=WINDOWS_SUPPORT_INSTRUCTIONS, tools=[plugin.get_ticket], - response_format=SupportResponse, + default_options={"response_format": SupportResponse}, ) resolution_agent = chat_client.create_agent( @@ -205,7 +205,7 @@ async def main() -> None: name="TicketEscalationAgent", instructions=ESCALATION_INSTRUCTIONS, tools=[plugin.get_ticket, plugin.send_notification], - response_format=EscalationResponse, + default_options={"response_format": EscalationResponse}, ) # Agent registry for lookup diff --git a/python/samples/getting_started/workflows/declarative/deep_research/main.py b/python/samples/getting_started/workflows/declarative/deep_research/main.py index 7d1ea8bc0f..f5b085f31d 100644 --- a/python/samples/getting_started/workflows/declarative/deep_research/main.py +++ b/python/samples/getting_started/workflows/declarative/deep_research/main.py @@ -139,7 +139,7 @@ async def main() -> None: manager_agent = chat_client.create_agent( name="ManagerAgent", instructions=MANAGER_INSTRUCTIONS, - response_format=ManagerResponse, + default_options={"response_format": ManagerResponse}, ) summary_agent = chat_client.create_agent( diff --git a/python/samples/getting_started/workflows/human-in-the-loop/guessing_game_with_human_input.py b/python/samples/getting_started/workflows/human-in-the-loop/guessing_game_with_human_input.py index 1afb1223af..ec634a622b 100644 --- a/python/samples/getting_started/workflows/human-in-the-loop/guessing_game_with_human_input.py +++ b/python/samples/getting_started/workflows/human-in-the-loop/guessing_game_with_human_input.py @@ -154,7 +154,7 @@ def create_guessing_agent() -> ChatAgent: "No explanations or additional text." ), # response_format enforces that the model produces JSON compatible with GuessOutput. - response_format=GuessOutput, + default_options={"response_format": GuessOutput}, ) diff --git a/python/samples/getting_started/workflows/state-management/shared_states_with_agents.py b/python/samples/getting_started/workflows/state-management/shared_states_with_agents.py index 0ab5b9a9dd..e9d38d3161 100644 --- a/python/samples/getting_started/workflows/state-management/shared_states_with_agents.py +++ b/python/samples/getting_started/workflows/state-management/shared_states_with_agents.py @@ -162,7 +162,7 @@ def create_spam_detection_agent() -> ChatAgent: "You are a spam detection assistant that identifies spam emails. " "Always return JSON with fields is_spam (bool) and reason (string)." ), - response_format=DetectionResultAgent, + default_options={"response_format": DetectionResultAgent}, # response_format enforces structured JSON from each agent. name="spam_detection_agent", ) @@ -176,7 +176,7 @@ def create_email_assistant_agent() -> ChatAgent: "Return JSON with a single field 'response' containing the drafted reply." ), # response_format enforces structured JSON from each agent. - response_format=EmailResponse, + default_options={"response_format": EmailResponse}, name="email_assistant_agent", ) diff --git a/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py b/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py index ffc1bf1713..b124e5f0f1 100644 --- a/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py +++ b/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py @@ -49,7 +49,7 @@ async def run_agent_framework() -> None: # AF forwards the same response_format payload at invocation time. reply = await chat_agent.run( "Draft a launch brief for the Contoso Note app.", - response_format=ReleaseBrief, + options={"response_format": ReleaseBrief}, ) print("[AF]", reply.text)