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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 22 additions & 18 deletions python/packages/core/agent_framework/_workflows/_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -135,17 +135,15 @@ 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.'}"
)
rewritten_raw = yield writer.run(
messages=rewrite_prompt,
thread=writer_thread,
response_format=GeneratedContent,
options={"response_format": GeneratedContent},
)

rewritten_value = rewritten_raw.value
Expand All @@ -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).")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}")


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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},
)


Expand All @@ -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},
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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},
)


Expand All @@ -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},
)


Expand All @@ -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},
)


Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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},
)


Expand All @@ -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},
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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},
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand All @@ -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",
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading