mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: (samples): adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs (#3873)
* adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs * Updates * add comment
This commit is contained in:
committed by
GitHub
Unverified
parent
8457533c69
commit
1b10b051fd
@@ -2,6 +2,7 @@
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -292,10 +293,10 @@ class AgentExecutor(Executor):
|
||||
# Non-streaming mode: use run() and emit single event
|
||||
response = await self._run_agent(cast(WorkflowContext[Never, AgentResponse], ctx))
|
||||
|
||||
# Always extend full conversation with cached messages plus agent outputs
|
||||
# (agent_response.messages) after each run. This is to avoid losing context
|
||||
# when agent did not complete and the cache is cleared when responses come back.
|
||||
self._full_conversation.extend(list(self._cache) + (list(response.messages) if response else []))
|
||||
# Snapshot current conversation as cache + latest agent outputs.
|
||||
# Do not append to prior snapshots: callers may provide full-history messages
|
||||
# in request.messages, and extending would duplicate prior turns.
|
||||
self._full_conversation = list(self._cache) + (list(response.messages) if response else [])
|
||||
|
||||
if response is None:
|
||||
# Agent did not complete (e.g., waiting for user input); do not emit response
|
||||
@@ -315,12 +316,7 @@ class AgentExecutor(Executor):
|
||||
Returns:
|
||||
The complete AgentResponse, or None if waiting for user input.
|
||||
"""
|
||||
run_kwargs: dict[str, Any] = ctx.get_state(WORKFLOW_RUN_KWARGS_KEY, {})
|
||||
|
||||
# Build options dict with additional_function_arguments for tool kwargs propagation
|
||||
options: dict[str, Any] | None = None
|
||||
if run_kwargs:
|
||||
options = {"additional_function_arguments": run_kwargs}
|
||||
run_kwargs, options = self._prepare_agent_run_args(ctx.get_state(WORKFLOW_RUN_KWARGS_KEY, {}))
|
||||
|
||||
response = await self._agent.run(
|
||||
self._cache,
|
||||
@@ -349,12 +345,7 @@ class AgentExecutor(Executor):
|
||||
Returns:
|
||||
The complete AgentResponse, or None if waiting for user input.
|
||||
"""
|
||||
run_kwargs: dict[str, Any] = ctx.get_state(WORKFLOW_RUN_KWARGS_KEY) or {}
|
||||
|
||||
# Build options dict with additional_function_arguments for tool kwargs propagation
|
||||
options: dict[str, Any] | None = None
|
||||
if run_kwargs:
|
||||
options = {"additional_function_arguments": run_kwargs}
|
||||
run_kwargs, options = self._prepare_agent_run_args(ctx.get_state(WORKFLOW_RUN_KWARGS_KEY) or {})
|
||||
|
||||
updates: list[AgentResponseUpdate] = []
|
||||
user_input_requests: list[Content] = []
|
||||
@@ -389,3 +380,55 @@ class AgentExecutor(Executor):
|
||||
return None
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _prepare_agent_run_args(raw_run_kwargs: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any] | None]:
|
||||
"""Prepare kwargs and options for agent.run(), avoiding duplicate option passing.
|
||||
|
||||
Workflow-level kwargs are propagated to tool calls through
|
||||
`options.additional_function_arguments`. If workflow kwargs include an
|
||||
`options` key, merge it into the final options object and remove it from
|
||||
kwargs before spreading `**run_kwargs`.
|
||||
"""
|
||||
run_kwargs = dict(raw_run_kwargs)
|
||||
options_from_workflow = run_kwargs.pop("options", None)
|
||||
workflow_additional_args = run_kwargs.pop("additional_function_arguments", None)
|
||||
|
||||
options: dict[str, Any] = {}
|
||||
if options_from_workflow is not None:
|
||||
if isinstance(options_from_workflow, Mapping):
|
||||
for key, value in options_from_workflow.items():
|
||||
if isinstance(key, str):
|
||||
options[key] = value
|
||||
else:
|
||||
logger.warning(
|
||||
"Ignoring non-mapping workflow 'options' kwarg of type %s for AgentExecutor %s.",
|
||||
type(options_from_workflow).__name__,
|
||||
AgentExecutor.__name__,
|
||||
)
|
||||
|
||||
existing_additional_args = options.get("additional_function_arguments")
|
||||
if isinstance(existing_additional_args, Mapping):
|
||||
additional_args = {key: value for key, value in existing_additional_args.items() if isinstance(key, str)}
|
||||
else:
|
||||
additional_args = {}
|
||||
|
||||
if workflow_additional_args is not None:
|
||||
if isinstance(workflow_additional_args, Mapping):
|
||||
additional_args.update({
|
||||
key: value for key, value in workflow_additional_args.items() if isinstance(key, str)
|
||||
})
|
||||
else:
|
||||
logger.warning(
|
||||
"Ignoring non-mapping workflow 'additional_function_arguments' kwarg of type %s for AgentExecutor %s.", # noqa: E501
|
||||
type(workflow_additional_args).__name__,
|
||||
AgentExecutor.__name__,
|
||||
)
|
||||
|
||||
if run_kwargs:
|
||||
additional_args.update(run_kwargs)
|
||||
|
||||
if additional_args:
|
||||
options["additional_function_arguments"] = additional_args
|
||||
|
||||
return run_kwargs, options or None
|
||||
|
||||
@@ -190,6 +190,10 @@ class Runner:
|
||||
# Save executor states into the shared state before creating the checkpoint,
|
||||
# so that they are included in the checkpoint payload.
|
||||
await self._save_executor_states()
|
||||
# `on_checkpoint_save()` writes via State.set(), which stages values in the
|
||||
# pending buffer. Checkpoints serialize committed state only, so commit here
|
||||
# to ensure executor snapshots are captured in this checkpoint.
|
||||
self._state.commit()
|
||||
|
||||
checkpoint_id = await self._ctx.create_checkpoint(
|
||||
self._workflow_name,
|
||||
|
||||
@@ -942,8 +942,16 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
"""Prepare content for the OpenAI Responses API format."""
|
||||
match content.type:
|
||||
case "text":
|
||||
if role == "assistant":
|
||||
# Assistant history is represented as output text items; Azure validation
|
||||
# requires `annotations` to be present for this type.
|
||||
return {
|
||||
"type": "output_text",
|
||||
"text": content.text,
|
||||
"annotations": [],
|
||||
}
|
||||
return {
|
||||
"type": "output_text" if role == "assistant" else "input_text",
|
||||
"type": "input_text",
|
||||
"text": content.text,
|
||||
}
|
||||
case "text_reasoning":
|
||||
|
||||
@@ -677,6 +677,40 @@ def test_prepare_content_for_openai_hosted_vector_store_content() -> None:
|
||||
assert result == {}
|
||||
|
||||
|
||||
def test_prepare_content_for_openai_text_uses_role_specific_type() -> None:
|
||||
"""Text content should use input_text for user and output_text for assistant."""
|
||||
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
|
||||
|
||||
text_content = Content.from_text(text="hello")
|
||||
|
||||
user_result = client._prepare_content_for_openai("user", text_content, {})
|
||||
assistant_result = client._prepare_content_for_openai("assistant", text_content, {})
|
||||
|
||||
assert user_result["type"] == "input_text"
|
||||
assert assistant_result["type"] == "output_text"
|
||||
assert assistant_result["annotations"] == []
|
||||
assert user_result["text"] == "hello"
|
||||
assert assistant_result["text"] == "hello"
|
||||
|
||||
|
||||
def test_prepare_messages_for_openai_assistant_history_uses_output_text_with_annotations() -> None:
|
||||
"""Assistant history should be output_text and include required annotations."""
|
||||
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
|
||||
|
||||
messages = [
|
||||
Message(role="user", text="What is async/await?"),
|
||||
Message(role="assistant", text="Async/await enables non-blocking concurrency."),
|
||||
]
|
||||
|
||||
prepared = client._prepare_messages_for_openai(messages)
|
||||
|
||||
assert prepared[0]["role"] == "user"
|
||||
assert prepared[0]["content"][0]["type"] == "input_text"
|
||||
assert prepared[1]["role"] == "assistant"
|
||||
assert prepared[1]["content"][0]["type"] == "output_text"
|
||||
assert prepared[1]["content"][0]["annotations"] == []
|
||||
|
||||
|
||||
def test_parse_response_from_openai_with_mcp_server_tool_result() -> None:
|
||||
"""Test _parse_response_from_openai with MCP server tool result."""
|
||||
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing_extensions import Never
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
@@ -150,3 +151,64 @@ async def test_sequential_adapter_uses_full_conversation() -> None:
|
||||
assert len(seen) == 2
|
||||
assert seen[0].role == "user" and "hello seq" in (seen[0].text or "")
|
||||
assert seen[1].role == "assistant" and "A1 reply" in (seen[1].text or "")
|
||||
|
||||
|
||||
class _RoundTripCoordinator(Executor):
|
||||
"""Loops once back to the same agent with full conversation + feedback."""
|
||||
|
||||
def __init__(self, *, target_agent_id: str, id: str = "round_trip_coordinator") -> None:
|
||||
super().__init__(id=id)
|
||||
self._target_agent_id = target_agent_id
|
||||
self._seen = 0
|
||||
|
||||
@handler
|
||||
async def handle_response(
|
||||
self,
|
||||
response: AgentExecutorResponse,
|
||||
ctx: WorkflowContext[Never, dict[str, Any]],
|
||||
) -> None:
|
||||
self._seen += 1
|
||||
if self._seen == 1:
|
||||
assert response.full_conversation is not None
|
||||
await ctx.send_message(
|
||||
AgentExecutorRequest(
|
||||
messages=list(response.full_conversation) + [Message(role="user", text="apply feedback")],
|
||||
should_respond=True,
|
||||
),
|
||||
target_id=self._target_agent_id,
|
||||
)
|
||||
return
|
||||
|
||||
assert response.full_conversation is not None
|
||||
await ctx.yield_output({
|
||||
"roles": [m.role for m in response.full_conversation],
|
||||
"texts": [m.text for m in response.full_conversation],
|
||||
})
|
||||
|
||||
|
||||
async def test_agent_executor_full_conversation_round_trip_does_not_duplicate_history() -> None:
|
||||
"""When full history is replayed, AgentExecutor should not duplicate prior turns."""
|
||||
agent = _SimpleAgent(id="writer_agent", name="Writer", reply_text="draft reply")
|
||||
agent_exec = AgentExecutor(agent, id="writer_agent")
|
||||
coordinator = _RoundTripCoordinator(target_agent_id="writer_agent")
|
||||
|
||||
wf = (
|
||||
WorkflowBuilder(start_executor=agent_exec, output_executors=[coordinator])
|
||||
.add_edge(agent_exec, coordinator)
|
||||
.add_edge(coordinator, agent_exec)
|
||||
.build()
|
||||
)
|
||||
|
||||
result = await wf.run("initial prompt")
|
||||
outputs = result.get_outputs()
|
||||
assert len(outputs) == 1
|
||||
payload = outputs[0]
|
||||
assert isinstance(payload, dict)
|
||||
|
||||
# Expected conversation after one loop:
|
||||
# user(initial), assistant(first reply), user(feedback), assistant(second reply)
|
||||
assert payload["roles"] == ["user", "assistant", "user", "assistant"]
|
||||
assert payload["texts"][0] == "initial prompt"
|
||||
assert payload["texts"][1] == "draft reply"
|
||||
assert payload["texts"][2] == "apply feedback"
|
||||
assert payload["texts"][3] == "draft reply"
|
||||
|
||||
@@ -72,6 +72,41 @@ class _KwargsCapturingAgent(BaseAgent):
|
||||
return _run()
|
||||
|
||||
|
||||
class _OptionsAwareAgent(BaseAgent):
|
||||
"""Test agent that captures explicit `options` and kwargs passed to run()."""
|
||||
|
||||
captured_options: list[dict[str, Any] | None]
|
||||
captured_kwargs: list[dict[str, Any]]
|
||||
|
||||
def __init__(self, name: str = "options_agent") -> None:
|
||||
super().__init__(name=name, description="Test agent for options capture")
|
||||
self.captured_options = []
|
||||
self.captured_kwargs = []
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]:
|
||||
self.captured_options.append(dict(options) if options is not None else None)
|
||||
self.captured_kwargs.append(dict(kwargs))
|
||||
if stream:
|
||||
|
||||
async def _stream() -> AsyncIterable[AgentResponseUpdate]:
|
||||
yield AgentResponseUpdate(contents=[Content.from_text(text=f"{self.name} response")])
|
||||
|
||||
return ResponseStream(_stream(), finalizer=AgentResponse.from_updates)
|
||||
|
||||
async def _run() -> AgentResponse:
|
||||
return AgentResponse(messages=[Message("assistant", [f"{self.name} response"])])
|
||||
|
||||
return _run()
|
||||
|
||||
|
||||
# region Sequential Builder Tests
|
||||
|
||||
|
||||
@@ -131,6 +166,106 @@ async def test_sequential_run_kwargs_flow() -> None:
|
||||
assert agent.captured_kwargs[0].get("custom_data") == {"test": True}
|
||||
|
||||
|
||||
async def test_sequential_run_options_does_not_conflict_with_agent_options() -> None:
|
||||
"""Test workflow.run(options=...) does not conflict with Agent.run(options=...)."""
|
||||
agent = _OptionsAwareAgent(name="options_agent")
|
||||
workflow = SequentialBuilder(participants=[agent]).build()
|
||||
|
||||
custom_data = {"session_id": "abc123"}
|
||||
user_token = {"user_name": "alice"}
|
||||
provided_options = {
|
||||
"store": False,
|
||||
"additional_function_arguments": {"source": "workflow-options"},
|
||||
}
|
||||
|
||||
async for event in workflow.run(
|
||||
"test message",
|
||||
stream=True,
|
||||
options=provided_options,
|
||||
custom_data=custom_data,
|
||||
user_token=user_token,
|
||||
):
|
||||
if event.type == "status" and event.state == WorkflowRunState.IDLE:
|
||||
break
|
||||
|
||||
assert len(agent.captured_options) >= 1
|
||||
captured_options = agent.captured_options[0]
|
||||
assert captured_options is not None
|
||||
assert captured_options.get("store") is False
|
||||
|
||||
additional_args = captured_options.get("additional_function_arguments")
|
||||
assert isinstance(additional_args, dict)
|
||||
assert additional_args.get("source") == "workflow-options"
|
||||
assert additional_args.get("custom_data") == custom_data
|
||||
assert additional_args.get("user_token") == user_token
|
||||
|
||||
# "options" should be passed once via the dedicated options parameter,
|
||||
# not duplicated in **kwargs.
|
||||
assert len(agent.captured_kwargs) >= 1
|
||||
captured_kwargs = agent.captured_kwargs[0]
|
||||
assert "options" not in captured_kwargs
|
||||
assert captured_kwargs.get("custom_data") == custom_data
|
||||
assert captured_kwargs.get("user_token") == user_token
|
||||
|
||||
|
||||
async def test_sequential_run_additional_function_arguments_flattened() -> None:
|
||||
"""Test workflow.run(additional_function_arguments=...) maps directly to tool kwargs."""
|
||||
agent = _OptionsAwareAgent(name="options_agent")
|
||||
workflow = SequentialBuilder(participants=[agent]).build()
|
||||
|
||||
custom_data = {"session_id": "abc123"}
|
||||
user_token = {"user_name": "alice"}
|
||||
|
||||
async for event in workflow.run(
|
||||
"test message",
|
||||
stream=True,
|
||||
additional_function_arguments={"custom_data": custom_data, "user_token": user_token},
|
||||
):
|
||||
if event.type == "status" and event.state == WorkflowRunState.IDLE:
|
||||
break
|
||||
|
||||
assert len(agent.captured_options) >= 1
|
||||
captured_options = agent.captured_options[0]
|
||||
assert captured_options is not None
|
||||
|
||||
additional_args = captured_options.get("additional_function_arguments")
|
||||
assert isinstance(additional_args, dict)
|
||||
assert additional_args.get("custom_data") == custom_data
|
||||
assert additional_args.get("user_token") == user_token
|
||||
assert "additional_function_arguments" not in additional_args
|
||||
|
||||
assert len(agent.captured_kwargs) >= 1
|
||||
captured_kwargs = agent.captured_kwargs[0]
|
||||
assert "additional_function_arguments" not in captured_kwargs
|
||||
|
||||
|
||||
async def test_sequential_run_additional_function_arguments_merges_with_options() -> None:
|
||||
"""Test workflow additional_function_arguments merges with workflow options."""
|
||||
agent = _OptionsAwareAgent(name="options_agent")
|
||||
workflow = SequentialBuilder(participants=[agent]).build()
|
||||
|
||||
async for event in workflow.run(
|
||||
"test message",
|
||||
stream=True,
|
||||
options={"additional_function_arguments": {"source": "workflow-options"}},
|
||||
additional_function_arguments={"custom_data": {"session_id": "abc123"}},
|
||||
user_token={"user_name": "alice"},
|
||||
):
|
||||
if event.type == "status" and event.state == WorkflowRunState.IDLE:
|
||||
break
|
||||
|
||||
assert len(agent.captured_options) >= 1
|
||||
captured_options = agent.captured_options[0]
|
||||
assert captured_options is not None
|
||||
|
||||
additional_args = captured_options.get("additional_function_arguments")
|
||||
assert isinstance(additional_args, dict)
|
||||
assert additional_args.get("source") == "workflow-options"
|
||||
assert additional_args.get("custom_data") == {"session_id": "abc123"}
|
||||
assert additional_args.get("user_token") == {"user_name": "alice"}
|
||||
assert "additional_function_arguments" not in additional_args
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ existing observability and streaming semantics continue to apply.
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
@@ -393,6 +394,76 @@ class AgentBasedGroupChatOrchestrator(BaseGroupChatOrchestrator):
|
||||
)
|
||||
self._increment_round()
|
||||
|
||||
@staticmethod
|
||||
def _parse_last_json_object(text: str) -> AgentOrchestrationOutput | None:
|
||||
"""Best-effort parser for concatenated JSON and return the last object.
|
||||
|
||||
Stop-gap workaround:
|
||||
In some runs, the orchestrator manager text can contain multiple JSON objects
|
||||
concatenated back-to-back (for example: `{...}{...}`), which causes
|
||||
`model_validate_json` to fail with trailing characters. Until the root cause
|
||||
is fully understood and fixed, decode sequential top-level JSON values and
|
||||
validate the last one.
|
||||
"""
|
||||
decoder = json.JSONDecoder()
|
||||
index = 0
|
||||
parsed: Any | None = None
|
||||
|
||||
while index < len(text):
|
||||
while index < len(text) and text[index].isspace():
|
||||
index += 1
|
||||
if index >= len(text):
|
||||
break
|
||||
parsed, index = decoder.raw_decode(text, index)
|
||||
|
||||
if parsed is None:
|
||||
return None
|
||||
return AgentOrchestrationOutput.model_validate(parsed)
|
||||
|
||||
@classmethod
|
||||
def _parse_agent_output(cls, agent_response: Any) -> AgentOrchestrationOutput:
|
||||
"""Parse manager output with defensive fallbacks.
|
||||
|
||||
Preferred path is structured output (`agent_response.value`) when available.
|
||||
If only text is available, first attempt strict JSON parsing and then apply a
|
||||
temporary concatenated-JSON fallback as a stop-gap.
|
||||
"""
|
||||
try:
|
||||
structured_value = agent_response.value
|
||||
except Exception:
|
||||
structured_value = None
|
||||
|
||||
if structured_value is not None:
|
||||
return AgentOrchestrationOutput.model_validate(structured_value)
|
||||
|
||||
text_candidates: list[str] = []
|
||||
for message in reversed(agent_response.messages):
|
||||
if message.role == "assistant" and message.text.strip():
|
||||
text_candidates.append(message.text.strip())
|
||||
break
|
||||
|
||||
response_text = agent_response.text.strip()
|
||||
if response_text and response_text not in text_candidates:
|
||||
text_candidates.append(response_text)
|
||||
|
||||
last_error: Exception | None = None
|
||||
for candidate in text_candidates:
|
||||
try:
|
||||
return AgentOrchestrationOutput.model_validate_json(candidate)
|
||||
except Exception as ex:
|
||||
last_error = ex
|
||||
|
||||
try:
|
||||
# Stop-gap fallback for rare cases where multiple JSON objects are
|
||||
# returned in one text payload (concatenated with no separator).
|
||||
parsed = cls._parse_last_json_object(candidate)
|
||||
if parsed is not None:
|
||||
return parsed
|
||||
except Exception as ex:
|
||||
last_error = ex
|
||||
|
||||
raise ValueError("Failed to parse agent orchestration output.") from last_error
|
||||
|
||||
async def _invoke_agent(self) -> AgentOrchestrationOutput:
|
||||
"""Invoke the orchestrator agent to determine the next speaker and termination."""
|
||||
|
||||
@@ -404,7 +475,7 @@ class AgentBasedGroupChatOrchestrator(BaseGroupChatOrchestrator):
|
||||
options={"response_format": AgentOrchestrationOutput},
|
||||
)
|
||||
# Parse and validate the structured output
|
||||
agent_orchestration_output = AgentOrchestrationOutput.model_validate_json(agent_response.text)
|
||||
agent_orchestration_output = self._parse_agent_output(agent_response)
|
||||
|
||||
if not agent_orchestration_output.terminate and not agent_orchestration_output.next_speaker:
|
||||
raise ValueError("next_speaker must be provided if not terminating the conversation.")
|
||||
|
||||
@@ -121,6 +121,51 @@ class StubManagerAgent(Agent):
|
||||
)
|
||||
|
||||
|
||||
class ConcatenatedJsonManagerAgent(Agent):
|
||||
"""Manager agent that emits concatenated JSON in a single assistant message."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(client=MockChatClient(), name="concat_manager", description="Concatenated JSON manager")
|
||||
self._call_count = 0
|
||||
|
||||
async def run(
|
||||
self,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentResponse:
|
||||
if self._call_count == 0:
|
||||
self._call_count += 1
|
||||
return AgentResponse(
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": false, "reason": "invalid candidate", '
|
||||
'"next_speaker": "unknown", "final_message": null} '
|
||||
'{"terminate": false, "reason": "pick known participant", '
|
||||
'"next_speaker": "agent", "final_message": null}'
|
||||
),
|
||||
author_name=self.name,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
return AgentResponse(
|
||||
messages=[
|
||||
Message(
|
||||
role="assistant",
|
||||
text=(
|
||||
'{"terminate": true, "reason": "Task complete", '
|
||||
'"next_speaker": null, "final_message": "concatenated manager final"}'
|
||||
),
|
||||
author_name=self.name,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def make_sequence_selector() -> Callable[[GroupChatState], str]:
|
||||
state_counter = {"value": 0}
|
||||
|
||||
@@ -221,6 +266,29 @@ async def test_group_chat_as_agent_accepts_conversation() -> None:
|
||||
assert response.messages, "Expected agent conversation output"
|
||||
|
||||
|
||||
async def test_agent_manager_handles_concatenated_json_output() -> None:
|
||||
manager = ConcatenatedJsonManagerAgent()
|
||||
worker = StubAgent("agent", "worker response")
|
||||
|
||||
workflow = GroupChatBuilder(
|
||||
participants=[worker],
|
||||
orchestrator_agent=manager,
|
||||
).build()
|
||||
|
||||
outputs: list[list[Message]] = []
|
||||
async for event in workflow.run("coordinate task", stream=True):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, list):
|
||||
outputs.append(cast(list[Message], data))
|
||||
|
||||
assert outputs
|
||||
conversation = outputs[-1]
|
||||
assert any(msg.author_name == "agent" and msg.text == "worker response" for msg in conversation)
|
||||
assert conversation[-1].author_name == manager.name
|
||||
assert conversation[-1].text == "concatenated manager final"
|
||||
|
||||
|
||||
# Comprehensive tests for group chat functionality
|
||||
|
||||
|
||||
|
||||
@@ -366,6 +366,11 @@ async def test_magentic_checkpoint_resume_round_trip():
|
||||
assert checkpoints
|
||||
checkpoints.sort(key=lambda cp: cp.timestamp)
|
||||
resume_checkpoint = checkpoints[-1]
|
||||
loaded_checkpoint = await storage.load(resume_checkpoint.checkpoint_id)
|
||||
assert loaded_checkpoint is not None
|
||||
# Regression check: checkpoints with pending request_info must include executor state.
|
||||
assert "_executor_state" in loaded_checkpoint.state
|
||||
assert "magentic_orchestrator" in loaded_checkpoint.state["_executor_state"]
|
||||
|
||||
manager2 = FakeManager()
|
||||
wf_resume = MagenticBuilder(
|
||||
@@ -378,7 +383,7 @@ async def test_magentic_checkpoint_resume_round_trip():
|
||||
completed: WorkflowEvent | None = None
|
||||
req_event = None
|
||||
async for event in wf_resume.run(
|
||||
resume_checkpoint.checkpoint_id,
|
||||
checkpoint_id=resume_checkpoint.checkpoint_id,
|
||||
stream=True,
|
||||
):
|
||||
if event.type == "request_info" and event.request_type is MagenticPlanReviewRequest:
|
||||
|
||||
@@ -26,24 +26,58 @@ from agent_framework.orchestrations import (
|
||||
)
|
||||
```
|
||||
|
||||
## Samples Overview
|
||||
## Samples Overview (by directory)
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| Concurrent Orchestration (Default Aggregator) | [concurrent_agents.py](./concurrent_agents.py) | Fan-out to multiple agents; fan-in with default aggregator returning combined Messages |
|
||||
| Concurrent Orchestration (Custom Aggregator) | [concurrent_custom_aggregator.py](./concurrent_custom_aggregator.py) | Override aggregator via callback; summarize results with an LLM |
|
||||
| Concurrent Orchestration (Custom Agent Executors) | [concurrent_custom_agent_executors.py](./concurrent_custom_agent_executors.py) | Child executors own Agents; concurrent fan-out/fan-in via ConcurrentBuilder |
|
||||
| Group Chat with Agent Manager | [group_chat_agent_manager.py](./group_chat_agent_manager.py) | Agent-based manager using `with_orchestrator(agent=)` to select next speaker |
|
||||
| Group Chat Philosophical Debate | [group_chat_philosophical_debate.py](./group_chat_philosophical_debate.py) | Agent manager moderates long-form, multi-round debate across diverse participants |
|
||||
| Group Chat with Simple Function Selector | [group_chat_simple_selector.py](./group_chat_simple_selector.py) | Group chat with a simple function selector for next speaker |
|
||||
| Handoff (Simple) | [handoff_simple.py](./handoff_simple.py) | Single-tier routing: triage agent routes to specialists, control returns to user after each specialist response |
|
||||
| Handoff (Autonomous) | [handoff_autonomous.py](./handoff_autonomous.py) | Autonomous mode: specialists iterate independently until invoking a handoff tool using `.with_autonomous_mode()` |
|
||||
| Handoff with Code Interpreter | [handoff_with_code_interpreter_file.py](./handoff_with_code_interpreter_file.py) | Retrieve file IDs from code interpreter output in handoff workflow |
|
||||
| Magentic Workflow (Multi-Agent) | [magentic.py](./magentic.py) | Orchestrate multiple agents with Magentic manager and streaming |
|
||||
| Magentic + Human Plan Review | [magentic_human_plan_review.py](./magentic_human_plan_review.py) | Human reviews/updates the plan before execution |
|
||||
| Magentic + Checkpoint Resume | [magentic_checkpoint.py](./magentic_checkpoint.py) | Resume Magentic orchestration from saved checkpoints |
|
||||
| Sequential Orchestration (Agents) | [sequential_agents.py](./sequential_agents.py) | Chain agents sequentially with shared conversation context |
|
||||
| Sequential Orchestration (Custom Executor) | [sequential_custom_executors.py](./sequential_custom_executors.py) | Mix agents with a summarizer that appends a compact summary |
|
||||
### concurrent
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| Concurrent Orchestration (Default Aggregator) | [concurrent/concurrent_agents.py](./concurrent/concurrent_agents.py) | Fan-out to multiple agents; fan-in with default aggregator returning combined Messages |
|
||||
| Concurrent Orchestration (Custom Aggregator) | [concurrent/concurrent_custom_aggregator.py](./concurrent/concurrent_custom_aggregator.py) | Override aggregator via callback; summarize results with an LLM |
|
||||
| Concurrent Orchestration (Custom Agent Executors) | [concurrent/concurrent_custom_agent_executors.py](./concurrent/concurrent_custom_agent_executors.py) | Child executors own Agents; concurrent fan-out/fan-in via ConcurrentBuilder |
|
||||
| Concurrent Orchestration as Agent | [concurrent/concurrent_workflow_as_agent.py](./concurrent/concurrent_workflow_as_agent.py) | Build a ConcurrentBuilder workflow and expose it as an agent via `workflow.as_agent(...)` |
|
||||
| Tool Approval with ConcurrentBuilder | [concurrent/concurrent_builder_tool_approval.py](./concurrent/concurrent_builder_tool_approval.py) | Require human approval for sensitive tools across concurrent participants |
|
||||
| ConcurrentBuilder Request Info | [concurrent/concurrent_request_info.py](./concurrent/concurrent_request_info.py) | Review concurrent agent outputs before aggregation using `.with_request_info()` |
|
||||
|
||||
### sequential
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| Sequential Orchestration (Agents) | [sequential/sequential_agents.py](./sequential/sequential_agents.py) | Chain agents sequentially with shared conversation context |
|
||||
| Sequential Orchestration (Custom Executor) | [sequential/sequential_custom_executors.py](./sequential/sequential_custom_executors.py) | Mix agents with a summarizer that appends a compact summary |
|
||||
| Sequential Orchestration as Agent | [sequential/sequential_workflow_as_agent.py](./sequential/sequential_workflow_as_agent.py) | Build a SequentialBuilder workflow and expose it as an agent via `workflow.as_agent(...)` |
|
||||
| Tool Approval with SequentialBuilder | [sequential/sequential_builder_tool_approval.py](./sequential/sequential_builder_tool_approval.py) | Require human approval for sensitive tools in SequentialBuilder workflows |
|
||||
| SequentialBuilder Request Info | [sequential/sequential_request_info.py](./sequential/sequential_request_info.py) | Request info for agent responses mid-orchestration using `.with_request_info()` |
|
||||
|
||||
### group-chat
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| Group Chat with Agent Manager | [group-chat/group_chat_agent_manager.py](./group-chat/group_chat_agent_manager.py) | Agent-based manager using `with_orchestrator(agent=)` to select next speaker |
|
||||
| Group Chat Philosophical Debate | [group-chat/group_chat_philosophical_debate.py](./group-chat/group_chat_philosophical_debate.py) | Agent manager moderates long-form, multi-round debate across diverse participants |
|
||||
| Group Chat with Simple Selector | [group-chat/group_chat_simple_selector.py](./group-chat/group_chat_simple_selector.py) | Group chat with a simple function selector for next speaker |
|
||||
| Group Chat Orchestration as Agent | [group-chat/group_chat_workflow_as_agent.py](./group-chat/group_chat_workflow_as_agent.py) | Build a GroupChatBuilder workflow and wrap it as an agent for composition |
|
||||
| Tool Approval with GroupChatBuilder | [group-chat/group_chat_builder_tool_approval.py](./group-chat/group_chat_builder_tool_approval.py) | Require human approval for sensitive tools in group chat orchestration |
|
||||
| GroupChatBuilder Request Info | [group-chat/group_chat_request_info.py](./group-chat/group_chat_request_info.py) | Steer group discussions with periodic guidance using `.with_request_info()` |
|
||||
|
||||
### handoff
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| Handoff (Simple) | [handoff/handoff_simple.py](./handoff/handoff_simple.py) | Single-tier routing: triage agent routes to specialists, control returns to user after each specialist response |
|
||||
| Handoff (Autonomous) | [handoff/handoff_autonomous.py](./handoff/handoff_autonomous.py) | Autonomous mode: specialists iterate independently until invoking a handoff tool using `.with_autonomous_mode()` |
|
||||
| Handoff with Code Interpreter | [handoff/handoff_with_code_interpreter_file.py](./handoff/handoff_with_code_interpreter_file.py) | Retrieve file IDs from code interpreter output in handoff workflow |
|
||||
| Handoff with Tool Approval + Checkpoint | [handoff/handoff_with_tool_approval_checkpoint_resume.py](./handoff/handoff_with_tool_approval_checkpoint_resume.py) | Capture tool-approval decisions in checkpoints and resume from persisted state |
|
||||
| Handoff Orchestration as Agent | [handoff/handoff_workflow_as_agent.py](./handoff/handoff_workflow_as_agent.py) | Build a HandoffBuilder workflow and expose it as an agent, including HITL request/response flow |
|
||||
|
||||
### magentic
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
|
||||
| Magentic Workflow | [magentic/magentic.py](./magentic/magentic.py) | Orchestrate multiple agents with a Magentic manager and streaming |
|
||||
| Magentic + Human Plan Review | [magentic/magentic_human_plan_review.py](./magentic/magentic_human_plan_review.py) | Human reviews or updates the plan before execution |
|
||||
| Magentic + Checkpoint Resume | [magentic/magentic_checkpoint.py](./magentic/magentic_checkpoint.py) | Resume Magentic orchestration from saved checkpoints |
|
||||
| Magentic Orchestration as Agent | [magentic/magentic_workflow_as_agent.py](./magentic/magentic_workflow_as_agent.py) | Build a MagenticBuilder workflow and reuse it as an agent |
|
||||
|
||||
## Tips
|
||||
|
||||
@@ -60,8 +94,9 @@ These may appear in event streams (executor_invoked/executor_completed). They're
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- **AzureOpenAIChatClient**: Set Azure OpenAI environment variables as documented [here](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/chat_client/README.md#environment-variables).
|
||||
Orchestration samples that use `AzureOpenAIResponsesClient` expect:
|
||||
|
||||
- **OpenAI** (used in some orchestration samples):
|
||||
- [OpenAIChatClient env vars](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/agents/openai_chat_client/README.md)
|
||||
- [OpenAIResponsesClient env vars](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/agents/openai_responses_client/README.md)
|
||||
- `AZURE_AI_PROJECT_ENDPOINT` (Azure AI Foundry Agent Service (V2) project endpoint)
|
||||
- `AZURE_AI_MODEL_DEPLOYMENT_NAME` (model deployment name)
|
||||
|
||||
These values are passed directly into the client constructor via `os.getenv()` in sample code.
|
||||
|
||||
+10
-4
@@ -1,10 +1,11 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Message
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -22,14 +23,19 @@ Demonstrates:
|
||||
- Workflow completion when idle with no pending work
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient (use az login + env vars)
|
||||
- Familiarity with Workflow events (WorkflowEvent)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create three domain agents using AzureOpenAIChatClient
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
# 1) Create three domain agents using AzureOpenAIResponsesClient
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
researcher = client.as_agent(
|
||||
instructions=(
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import Annotated
|
||||
|
||||
@@ -10,8 +11,9 @@ from agent_framework import (
|
||||
WorkflowEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Concurrent Workflow with Tool Approval Requests
|
||||
@@ -38,6 +40,7 @@ Demonstrate:
|
||||
- Understanding that approval pauses only the agent that triggered it, not all agents.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI or Azure OpenAI configured with the required environment variables.
|
||||
- Basic familiarity with ConcurrentBuilder and streaming workflow events.
|
||||
"""
|
||||
@@ -126,7 +129,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
async def main() -> None:
|
||||
# 3. Create two agents focused on different stocks but with the same tool sets
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
microsoft_agent = client.as_agent(
|
||||
name="MicrosoftAgent",
|
||||
+13
-7
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
@@ -12,7 +13,7 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -25,21 +26,22 @@ and emit AgentExecutorResponse outputs, which allows reuse of the high-level
|
||||
ConcurrentBuilder API and the default aggregator.
|
||||
|
||||
Demonstrates:
|
||||
- Executors that create their Agent in __init__ (via AzureOpenAIChatClient)
|
||||
- Executors that create their Agent in __init__ (via AzureOpenAIResponsesClient)
|
||||
- A @handler that converts AgentExecutorRequest -> AgentExecutorResponse
|
||||
- ConcurrentBuilder(participants=[...]) to build fan-out/fan-in
|
||||
- Default aggregator returning list[Message] (one user + one assistant per agent)
|
||||
- Workflow completion when all participants become idle
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient (az login + required env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient (az login + required env vars)
|
||||
"""
|
||||
|
||||
|
||||
class ResearcherExec(Executor):
|
||||
agent: Agent
|
||||
|
||||
def __init__(self, client: AzureOpenAIChatClient, id: str = "researcher"):
|
||||
def __init__(self, client: AzureOpenAIResponsesClient, id: str = "researcher"):
|
||||
self.agent = client.as_agent(
|
||||
instructions=(
|
||||
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
||||
@@ -59,7 +61,7 @@ class ResearcherExec(Executor):
|
||||
class MarketerExec(Executor):
|
||||
agent: Agent
|
||||
|
||||
def __init__(self, client: AzureOpenAIChatClient, id: str = "marketer"):
|
||||
def __init__(self, client: AzureOpenAIResponsesClient, id: str = "marketer"):
|
||||
self.agent = client.as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
@@ -79,7 +81,7 @@ class MarketerExec(Executor):
|
||||
class LegalExec(Executor):
|
||||
agent: Agent
|
||||
|
||||
def __init__(self, client: AzureOpenAIChatClient, id: str = "legal"):
|
||||
def __init__(self, client: AzureOpenAIResponsesClient, id: str = "legal"):
|
||||
self.agent = client.as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
@@ -97,7 +99,11 @@ class LegalExec(Executor):
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
researcher = ResearcherExec(client)
|
||||
marketer = MarketerExec(client)
|
||||
+11
-7
@@ -1,10 +1,11 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Message
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -13,7 +14,7 @@ Sample: Concurrent Orchestration with Custom Aggregator
|
||||
|
||||
Build a concurrent workflow with ConcurrentBuilder that fans out one prompt to
|
||||
multiple domain agents and fans in their responses. Override the default
|
||||
aggregator with a custom async callback that uses AzureOpenAIChatClient.get_response()
|
||||
aggregator with a custom async callback that uses AzureOpenAIResponsesClient.get_response()
|
||||
to synthesize a concise, consolidated summary from the experts' outputs.
|
||||
The workflow completes when all participants become idle.
|
||||
|
||||
@@ -24,12 +25,17 @@ Demonstrates:
|
||||
- Workflow output yielded with the synthesized summary string
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient (az login + required env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient (az login + required env vars)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
researcher = client.as_agent(
|
||||
instructions=(
|
||||
@@ -86,9 +92,7 @@ async def main() -> None:
|
||||
# • Default aggregator -> returns list[Message] (one user + one assistant per agent)
|
||||
# • Custom callback -> return value becomes workflow output (string here)
|
||||
# The callback can be sync or async; it receives list[AgentExecutorResponse].
|
||||
workflow = (
|
||||
ConcurrentBuilder(participants=[researcher, marketer, legal]).with_aggregator(summarize_results).build()
|
||||
)
|
||||
workflow = ConcurrentBuilder(participants=[researcher, marketer, legal]).with_aggregator(summarize_results).build()
|
||||
|
||||
events = await workflow.run("We are launching a new budget-friendly electric bike for urban commuters.")
|
||||
outputs = events.get_outputs()
|
||||
+10
-4
@@ -17,11 +17,13 @@ Demonstrate:
|
||||
- Injecting human guidance for specific agents before aggregation
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables
|
||||
- Authentication via azure-identity (run az login before executing)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import Any
|
||||
|
||||
@@ -30,12 +32,12 @@ from agent_framework import (
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import AgentRequestInfoResponse, ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
# Store chat client at module level for aggregator access
|
||||
_chat_client: AzureOpenAIChatClient | None = None
|
||||
_chat_client: AzureOpenAIResponsesClient | None = None
|
||||
|
||||
|
||||
async def aggregate_with_synthesis(results: list[AgentExecutorResponse]) -> Any:
|
||||
@@ -142,7 +144,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
async def main() -> None:
|
||||
global _chat_client
|
||||
_chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
_chat_client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents that analyze from different perspectives
|
||||
technical_analyst = _chat_client.as_agent(
|
||||
+10
-4
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -19,7 +20,8 @@ Demonstrates:
|
||||
- Workflow completion when idle with no pending work
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient (use az login + env vars)
|
||||
- Familiarity with Workflow events (WorkflowEvent with type "output")
|
||||
"""
|
||||
|
||||
@@ -37,8 +39,12 @@ def clear_and_redraw(buffers: dict[str, str], agent_order: list[str]) -> None:
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create three domain agents using AzureOpenAIChatClient
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
# 1) Create three domain agents using AzureOpenAIResponsesClient
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
researcher = client.as_agent(
|
||||
instructions=(
|
||||
+37
-22
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -8,7 +9,7 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -21,7 +22,8 @@ What it does:
|
||||
- Coordinates a researcher and writer agent to solve tasks collaboratively
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
ORCHESTRATOR_AGENT_INSTRUCTIONS = """
|
||||
@@ -36,7 +38,11 @@ Guidelines:
|
||||
|
||||
async def main() -> None:
|
||||
# Create a chat client using Azure OpenAI and Azure CLI credentials for all agents
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Orchestrator agent that manages the conversation
|
||||
# Note: This agent (and the underlying chat client) must support structured outputs.
|
||||
@@ -88,26 +94,35 @@ async def main() -> None:
|
||||
print(f"TASK: {task}\n")
|
||||
print("=" * 80)
|
||||
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
# Track current speaker for readable streaming output.
|
||||
pending_speaker: str | None = None
|
||||
current_speaker: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
if rid != last_response_id:
|
||||
if last_response_id is not None:
|
||||
print("\n")
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
if event.type != "output":
|
||||
continue
|
||||
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
if data.author_name:
|
||||
pending_speaker = data.author_name
|
||||
if not data.text:
|
||||
continue
|
||||
|
||||
speaker = data.author_name or pending_speaker or "assistant"
|
||||
if speaker != current_speaker:
|
||||
if current_speaker is not None:
|
||||
print("\n")
|
||||
print(f"{speaker}:", end=" ", flush=True)
|
||||
current_speaker = speaker
|
||||
print(data.text, end="", flush=True)
|
||||
continue
|
||||
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import Annotated, cast
|
||||
|
||||
@@ -10,8 +11,9 @@ from agent_framework import (
|
||||
WorkflowEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat Workflow with Tool Approval Requests
|
||||
@@ -37,6 +39,7 @@ Demonstrate:
|
||||
- Multi-round group chat with tool approval interruption and resumption.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI or Azure OpenAI configured with the required environment variables.
|
||||
- Basic familiarity with GroupChatBuilder and streaming workflow events.
|
||||
"""
|
||||
@@ -126,7 +129,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
async def main() -> None:
|
||||
# 3. Create specialized agents
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
qa_engineer = client.as_agent(
|
||||
name="QAEngineer",
|
||||
+38
-23
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -9,7 +10,7 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -37,12 +38,17 @@ Participants represent:
|
||||
- Doctor from Scandinavia (public health, equity, societal support)
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
|
||||
def _get_chat_client() -> AzureOpenAIChatClient:
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
def _get_chat_client() -> AzureOpenAIResponsesClient:
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -240,26 +246,35 @@ Share your perspective authentically. Feel free to:
|
||||
print("DISCUSSION BEGINS")
|
||||
print("=" * 80 + "\n")
|
||||
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
# Track current speaker for readable streaming output.
|
||||
pending_speaker: str | None = None
|
||||
current_speaker: str | None = None
|
||||
async for event in workflow.run(f"Please begin the discussion on: {topic}", stream=True):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
if rid != last_response_id:
|
||||
if last_response_id is not None:
|
||||
print("\n")
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
if event.type != "output":
|
||||
continue
|
||||
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
if data.author_name:
|
||||
pending_speaker = data.author_name
|
||||
if not data.text:
|
||||
continue
|
||||
|
||||
speaker = data.author_name or pending_speaker or "assistant"
|
||||
if speaker != current_speaker:
|
||||
if current_speaker is not None:
|
||||
print("\n")
|
||||
print(f"{speaker}:", end=" ", flush=True)
|
||||
current_speaker = speaker
|
||||
print(data.text, end="", flush=True)
|
||||
continue
|
||||
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[Message], data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
+9
-3
@@ -18,11 +18,13 @@ Demonstrate:
|
||||
- Steering agent behavior with pre-agent human input
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables
|
||||
- Authentication via azure-identity (run az login before executing)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import cast
|
||||
|
||||
@@ -31,7 +33,7 @@ from agent_framework import (
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import AgentRequestInfoResponse, GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -91,7 +93,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents for a group discussion
|
||||
optimist = client.as_agent(
|
||||
+9
-3
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -8,7 +9,7 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -20,7 +21,8 @@ What it does:
|
||||
- Uses a pure Python function to control speaker selection based on conversation state
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
|
||||
@@ -33,7 +35,11 @@ def round_robin_selector(state: GroupChatState) -> str:
|
||||
|
||||
async def main() -> None:
|
||||
# Create a chat client using Azure OpenAI and Azure CLI credentials for all agents
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Participant agents
|
||||
expert = Agent(
|
||||
+20
-5
@@ -1,10 +1,12 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Group Chat Orchestration
|
||||
@@ -14,7 +16,8 @@ What it does:
|
||||
- The orchestrator coordinates a researcher (chat completions) and a writer (responses API) to solve a task.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for `OpenAIChatClient` and `OpenAIResponsesClient`.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
|
||||
@@ -23,14 +26,22 @@ async def main() -> None:
|
||||
name="Researcher",
|
||||
description="Collects relevant background information.",
|
||||
instructions="Gather concise facts that help a teammate answer the question.",
|
||||
client=OpenAIChatClient(model_id="gpt-4o-mini"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="Writer",
|
||||
description="Synthesizes a polished answer using the gathered notes.",
|
||||
instructions="Compose clear and structured answers using any notes provided.",
|
||||
client=OpenAIResponsesClient(),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
|
||||
@@ -38,7 +49,11 @@ async def main() -> None:
|
||||
workflow = GroupChatBuilder(
|
||||
participants=[researcher, writer],
|
||||
intermediate_outputs=True,
|
||||
orchestrator_agent=OpenAIChatClient().as_agent(
|
||||
orchestrator_agent=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="Orchestrator",
|
||||
instructions="You coordinate a team conversation to solve the user's task.",
|
||||
),
|
||||
+10
-4
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -10,7 +11,7 @@ from agent_framework import (
|
||||
Message,
|
||||
resolve_agent_id,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -27,8 +28,9 @@ Routing Pattern:
|
||||
User -> Coordinator -> Specialist (iterates N times) -> Handoff -> Final Output
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- `az login` (Azure CLI authentication)
|
||||
- Environment variables for AzureOpenAIChatClient (AZURE_OPENAI_ENDPOINT, etc.)
|
||||
- Environment variables for AzureOpenAIResponsesClient (AZURE_AI_MODEL_DEPLOYMENT_NAME)
|
||||
|
||||
Key Concepts:
|
||||
- Autonomous interaction mode: agents iterate until they handoff
|
||||
@@ -37,7 +39,7 @@ Key Concepts:
|
||||
|
||||
|
||||
def create_agents(
|
||||
client: AzureOpenAIChatClient,
|
||||
client: AzureOpenAIResponsesClient,
|
||||
) -> tuple[Agent, Agent, Agent]:
|
||||
"""Create coordinator and specialists for autonomous iteration."""
|
||||
coordinator = client.as_agent(
|
||||
@@ -73,7 +75,11 @@ def create_agents(
|
||||
|
||||
async def main() -> None:
|
||||
"""Run an autonomous handoff workflow with specialist iteration enabled."""
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
coordinator, research_agent, summary_agent = create_agents(client)
|
||||
|
||||
# Build the workflow with autonomous mode
|
||||
+11
-5
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Annotated, cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -11,7 +12,7 @@ from agent_framework import (
|
||||
WorkflowRunState,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -21,8 +22,9 @@ A handoff workflow defines a pattern that assembles agents in a mesh topology, a
|
||||
them to transfer control to each other based on the conversation context.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- `az login` (Azure CLI authentication)
|
||||
- Environment variables configured for AzureOpenAIChatClient (AZURE_OPENAI_ENDPOINT, etc.)
|
||||
- Environment variables configured for AzureOpenAIResponsesClient (AZURE_AI_MODEL_DEPLOYMENT_NAME)
|
||||
|
||||
Key Concepts:
|
||||
- Auto-registered handoff tools: HandoffBuilder automatically creates handoff tools
|
||||
@@ -54,11 +56,11 @@ def process_return(order_number: Annotated[str, "Order number to process return
|
||||
return f"Return initiated successfully for order {order_number}. You will receive return instructions via email."
|
||||
|
||||
|
||||
def create_agents(client: AzureOpenAIChatClient) -> tuple[Agent, Agent, Agent, Agent]:
|
||||
def create_agents(client: AzureOpenAIResponsesClient) -> tuple[Agent, Agent, Agent, Agent]:
|
||||
"""Create and configure the triage and specialist agents.
|
||||
|
||||
Args:
|
||||
client: The AzureOpenAIChatClient to use for creating agents.
|
||||
client: The AzureOpenAIResponsesClient to use for creating agents.
|
||||
|
||||
Returns:
|
||||
Tuple of (triage_agent, refund_agent, order_agent, return_agent)
|
||||
@@ -189,7 +191,11 @@ async def main() -> None:
|
||||
replace the scripted_responses with actual user input collection.
|
||||
"""
|
||||
# Initialize the Azure OpenAI chat client
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create all agents: triage + specialists
|
||||
triage, refund, order, support = create_agents(client)
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""
|
||||
Handoff Workflow with Code Interpreter File Generation Sample
|
||||
|
||||
This sample demonstrates retrieving file IDs from code interpreter output
|
||||
in a handoff workflow context. A triage agent routes to a code specialist
|
||||
that generates a text file, and we verify the file_id is captured correctly
|
||||
from the streaming workflow events.
|
||||
|
||||
Verifies GitHub issue #2718: files generated by code interpreter in
|
||||
HandoffBuilder workflows can be properly retrieved.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- `az login` (Azure CLI authentication)
|
||||
- AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
WorkflowRunState,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
|
||||
async def _drain(stream: AsyncIterable[WorkflowEvent]) -> list[WorkflowEvent]:
|
||||
"""Collect all events from an async stream."""
|
||||
return [event async for event in stream]
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> tuple[list[WorkflowEvent[HandoffAgentUserRequest]], list[str]]:
|
||||
"""Process workflow events and extract file IDs and pending requests.
|
||||
|
||||
Returns:
|
||||
Tuple of (pending_requests, file_ids_found)
|
||||
"""
|
||||
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
file_ids: list[str] = []
|
||||
|
||||
for event in events:
|
||||
if event.type == "handoff_sent":
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
print(f"[status] {event.state}")
|
||||
elif event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
elif event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
for content in data.contents:
|
||||
if content.type == "hosted_file":
|
||||
file_ids.append(content.file_id) # type: ignore
|
||||
print(f"[Found HostedFileContent: file_id={content.file_id}]")
|
||||
elif content.type == "text" and content.annotations:
|
||||
for annotation in content.annotations:
|
||||
file_id = annotation["file_id"] # type: ignore
|
||||
file_ids.append(file_id)
|
||||
print(f"[Found file annotation: file_id={file_id}]")
|
||||
elif isinstance(data, list):
|
||||
conversation = cast(list[Message], data)
|
||||
if isinstance(conversation, list):
|
||||
print("\n=== Final Conversation Snapshot ===")
|
||||
for message in conversation:
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
|
||||
return requests, file_ids
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run a simple handoff workflow with code interpreter file generation."""
|
||||
print("=== Handoff Workflow with Code Interpreter File Generation ===\n")
|
||||
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
triage = client.as_agent(
|
||||
name="triage_agent",
|
||||
instructions=(
|
||||
"You are a triage agent. Route code-related requests to the code_specialist. "
|
||||
"When the user asks to create or generate files, hand off to code_specialist "
|
||||
"by calling handoff_to_code_specialist."
|
||||
),
|
||||
)
|
||||
|
||||
code_interpreter_tool = client.get_code_interpreter_tool()
|
||||
|
||||
code_specialist = client.as_agent(
|
||||
name="code_specialist",
|
||||
instructions=(
|
||||
"You are a Python code specialist. Use the code interpreter to execute Python code "
|
||||
"and create files when requested. Always save files to /mnt/data/ directory."
|
||||
),
|
||||
tools=[code_interpreter_tool],
|
||||
)
|
||||
|
||||
workflow = (
|
||||
HandoffBuilder(
|
||||
termination_condition=lambda conv: sum(1 for msg in conv if msg.role == "user") >= 2,
|
||||
)
|
||||
.participants([triage, code_specialist])
|
||||
.with_start_agent(triage)
|
||||
.build()
|
||||
)
|
||||
|
||||
user_inputs = [
|
||||
"Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.",
|
||||
"exit",
|
||||
]
|
||||
input_index = 0
|
||||
all_file_ids: list[str] = []
|
||||
|
||||
print(f"User: {user_inputs[0]}")
|
||||
events = await _drain(workflow.run(user_inputs[0], stream=True))
|
||||
requests, file_ids = _handle_events(events)
|
||||
all_file_ids.extend(file_ids)
|
||||
input_index += 1
|
||||
|
||||
while requests:
|
||||
request = requests[0]
|
||||
if input_index >= len(user_inputs):
|
||||
break
|
||||
user_input = user_inputs[input_index]
|
||||
print(f"\nUser: {user_input}")
|
||||
|
||||
responses = {request.request_id: HandoffAgentUserRequest.create_response(user_input)}
|
||||
events = await _drain(workflow.run(stream=True, responses=responses))
|
||||
requests, file_ids = _handle_events(events)
|
||||
all_file_ids.extend(file_ids)
|
||||
input_index += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if all_file_ids:
|
||||
print(f"SUCCESS: Found {len(all_file_ids)} file ID(s) in handoff workflow:")
|
||||
for fid in all_file_ids:
|
||||
print(f" - {fid}")
|
||||
else:
|
||||
print("WARNING: No file IDs captured from the handoff workflow.")
|
||||
print("=" * 50)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
User: Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.
|
||||
[Found HostedFileContent: file_id=assistant-JT1sA...]
|
||||
|
||||
=== Conversation So Far ===
|
||||
- user: Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.
|
||||
- triage_agent: I am handing off your request to create the text file "hello.txt" with the specified content to the code specialist. They will assist you shortly.
|
||||
- code_specialist: The file "hello.txt" has been created with the content "Hello from handoff workflow!". You can download it using the link below:
|
||||
|
||||
[hello.txt](sandbox:/mnt/data/hello.txt)
|
||||
===========================
|
||||
|
||||
[status] IDLE_WITH_PENDING_REQUESTS
|
||||
|
||||
User: exit
|
||||
[status] IDLE
|
||||
|
||||
==================================================
|
||||
SUCCESS: Found 1 file ID(s) in handoff workflow:
|
||||
- assistant-JT1sA...
|
||||
==================================================
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+10
-4
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -12,7 +13,7 @@ from agent_framework import (
|
||||
Workflow,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -39,8 +40,9 @@ Pattern:
|
||||
workflow.run(stream=True, checkpoint_id=..., responses=responses).)
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure CLI authentication (az login).
|
||||
- Environment variables configured for AzureOpenAIChatClient.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient.
|
||||
"""
|
||||
|
||||
CHECKPOINT_DIR = Path(__file__).parent / "tmp" / "handoff_checkpoints"
|
||||
@@ -53,7 +55,7 @@ def submit_refund(refund_description: str, amount: str, order_id: str) -> str:
|
||||
return f"refund recorded for order {order_id} (amount: {amount}) with details: {refund_description}"
|
||||
|
||||
|
||||
def create_agents(client: AzureOpenAIChatClient) -> tuple[Agent, Agent, Agent]:
|
||||
def create_agents(client: AzureOpenAIResponsesClient) -> tuple[Agent, Agent, Agent]:
|
||||
"""Create a simple handoff scenario: triage, refund, and order specialists."""
|
||||
|
||||
triage = client.as_agent(
|
||||
@@ -90,7 +92,11 @@ def create_agents(client: AzureOpenAIChatClient) -> tuple[Agent, Agent, Agent]:
|
||||
def create_workflow(checkpoint_storage: FileCheckpointStorage) -> Workflow:
|
||||
"""Build the handoff workflow with checkpointing enabled."""
|
||||
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
triage, refund, order = create_agents(client)
|
||||
|
||||
# checkpoint_storage: Enable checkpointing for resume
|
||||
+11
-5
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import (
|
||||
@@ -11,7 +12,7 @@ from agent_framework import (
|
||||
WorkflowAgent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -24,8 +25,9 @@ A handoff workflow defines a pattern that assembles agents in a mesh topology, a
|
||||
them to transfer control to each other based on the conversation context.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- `az login` (Azure CLI authentication)
|
||||
- Environment variables configured for AzureOpenAIChatClient (AZURE_OPENAI_ENDPOINT, etc.)
|
||||
- Environment variables configured for AzureOpenAIResponsesClient (AZURE_AI_MODEL_DEPLOYMENT_NAME)
|
||||
|
||||
Key Concepts:
|
||||
- Auto-registered handoff tools: HandoffBuilder automatically creates handoff tools
|
||||
@@ -57,11 +59,11 @@ def process_return(order_number: Annotated[str, "Order number to process return
|
||||
return f"Return initiated successfully for order {order_number}. You will receive return instructions via email."
|
||||
|
||||
|
||||
def create_agents(client: AzureOpenAIChatClient) -> tuple[Agent, Agent, Agent, Agent]:
|
||||
def create_agents(client: AzureOpenAIResponsesClient) -> tuple[Agent, Agent, Agent, Agent]:
|
||||
"""Create and configure the triage and specialist agents.
|
||||
|
||||
Args:
|
||||
client: The AzureOpenAIChatClient to use for creating agents.
|
||||
client: The AzureOpenAIResponsesClient to use for creating agents.
|
||||
|
||||
Returns:
|
||||
Tuple of (triage_agent, refund_agent, order_agent, return_agent)
|
||||
@@ -147,7 +149,11 @@ async def main() -> None:
|
||||
replace the scripted_responses with actual user input collection.
|
||||
"""
|
||||
# Initialize the Azure OpenAI chat client
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create all agents: triage + specialists
|
||||
triage, refund, order, support = create_agents(client)
|
||||
@@ -1,241 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""
|
||||
Handoff Workflow with Code Interpreter File Generation Sample
|
||||
|
||||
This sample demonstrates retrieving file IDs from code interpreter output
|
||||
in a handoff workflow context. A triage agent routes to a code specialist
|
||||
that generates a text file, and we verify the file_id is captured correctly
|
||||
from the streaming workflow events.
|
||||
|
||||
Verifies GitHub issue #2718: files generated by code interpreter in
|
||||
HandoffBuilder workflows can be properly retrieved.
|
||||
|
||||
Toggle USE_V2_CLIENT to switch between:
|
||||
- V1: AzureAIAgentClient (azure-ai-agents SDK)
|
||||
- V2: AzureAIClient (azure-ai-projects 2.x with Responses API)
|
||||
|
||||
IMPORTANT: When using V2 AzureAIClient with HandoffBuilder, each agent must
|
||||
have its own client instance. The V2 client binds to a single server-side
|
||||
agent name, so sharing a client between agents causes routing issues.
|
||||
|
||||
Prerequisites:
|
||||
- `az login` (Azure CLI authentication)
|
||||
- V1: AZURE_AI_AGENT_PROJECT_CONNECTION_STRING
|
||||
- V2: AZURE_AI_PROJECT_ENDPOINT, AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterable, AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
WorkflowRunState,
|
||||
)
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
# Toggle between V1 (AzureAIAgentClient) and V2 (AzureAIClient)
|
||||
USE_V2_CLIENT = False
|
||||
|
||||
|
||||
async def _drain(stream: AsyncIterable[WorkflowEvent]) -> list[WorkflowEvent]:
|
||||
"""Collect all events from an async stream."""
|
||||
return [event async for event in stream]
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> tuple[list[WorkflowEvent[HandoffAgentUserRequest]], list[str]]:
|
||||
"""Process workflow events and extract file IDs and pending requests.
|
||||
|
||||
Returns:
|
||||
Tuple of (pending_requests, file_ids_found)
|
||||
"""
|
||||
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
file_ids: list[str] = []
|
||||
|
||||
for event in events:
|
||||
if event.type == "handoff_sent":
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
print(f"[status] {event.state.name}")
|
||||
elif event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
elif event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
for content in data.contents:
|
||||
if content.type == "hosted_file":
|
||||
file_ids.append(content.file_id) # type: ignore
|
||||
print(f"[Found HostedFileContent: file_id={content.file_id}]")
|
||||
elif content.type == "text" and content.annotations:
|
||||
for annotation in content.annotations:
|
||||
file_id = annotation["file_id"] # type: ignore
|
||||
file_ids.append(file_id)
|
||||
print(f"[Found file annotation: file_id={file_id}]")
|
||||
elif event.type == "output":
|
||||
conversation = cast(list[Message], event.data)
|
||||
if isinstance(conversation, list):
|
||||
print("\n=== Final Conversation Snapshot ===")
|
||||
for message in conversation:
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
|
||||
return requests, file_ids
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def create_agents_v1(credential: AzureCliCredential) -> AsyncIterator[tuple[Agent, Agent]]:
|
||||
"""Create agents using V1 AzureAIAgentClient."""
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
|
||||
async with AzureAIAgentClient(credential=credential) as client:
|
||||
triage = client.as_agent(
|
||||
name="triage_agent",
|
||||
instructions=(
|
||||
"You are a triage agent. Route code-related requests to the code_specialist. "
|
||||
"When the user asks to create or generate files, hand off to code_specialist "
|
||||
"by calling handoff_to_code_specialist."
|
||||
),
|
||||
)
|
||||
|
||||
# Create code interpreter tool using instance method
|
||||
code_interpreter_tool = client.get_code_interpreter_tool()
|
||||
|
||||
code_specialist = client.as_agent(
|
||||
name="code_specialist",
|
||||
instructions=(
|
||||
"You are a Python code specialist. Use the code interpreter to execute Python code "
|
||||
"and create files when requested. Always save files to /mnt/data/ directory."
|
||||
),
|
||||
tools=[code_interpreter_tool],
|
||||
)
|
||||
|
||||
yield triage, code_specialist # type: ignore
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def create_agents_v2(credential: AzureCliCredential) -> AsyncIterator[tuple[Agent, Agent]]:
|
||||
"""Create agents using V2 AzureAIClient.
|
||||
|
||||
Each agent needs its own client instance because the V2 client binds
|
||||
to a single server-side agent name.
|
||||
"""
|
||||
from agent_framework.azure import AzureAIClient
|
||||
|
||||
async with (
|
||||
AzureAIClient(credential=credential) as triage_client,
|
||||
AzureAIClient(credential=credential) as code_client,
|
||||
):
|
||||
triage = triage_client.as_agent(
|
||||
name="TriageAgent",
|
||||
instructions="You are a triage agent. Your ONLY job is to route requests to the appropriate specialist.",
|
||||
)
|
||||
|
||||
# Create code interpreter tool using instance method
|
||||
code_interpreter_tool = code_client.get_code_interpreter_tool()
|
||||
|
||||
code_specialist = code_client.as_agent(
|
||||
name="CodeSpecialist",
|
||||
instructions=(
|
||||
"You are a Python code specialist. You have access to a code interpreter tool. "
|
||||
"Use the code interpreter to execute Python code and create files. "
|
||||
"Always save files to /mnt/data/ directory. "
|
||||
"Do NOT discuss handoffs or routing - just complete the coding task directly."
|
||||
),
|
||||
tools=[code_interpreter_tool],
|
||||
)
|
||||
|
||||
yield triage, code_specialist
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run a simple handoff workflow with code interpreter file generation."""
|
||||
client_version = "V2 (AzureAIClient)" if USE_V2_CLIENT else "V1 (AzureAIAgentClient)"
|
||||
print(f"=== Handoff Workflow with Code Interpreter File Generation [{client_version}] ===\n")
|
||||
|
||||
async with AzureCliCredential() as credential:
|
||||
create_agents = create_agents_v2 if USE_V2_CLIENT else create_agents_v1
|
||||
|
||||
async with create_agents(credential) as (triage, code_specialist):
|
||||
workflow = (
|
||||
HandoffBuilder(
|
||||
termination_condition=lambda conv: sum(1 for msg in conv if msg.role == "user") >= 2,
|
||||
)
|
||||
.participants([triage, code_specialist])
|
||||
.with_start_agent(triage)
|
||||
.build()
|
||||
)
|
||||
|
||||
user_inputs = [
|
||||
"Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.",
|
||||
"exit",
|
||||
]
|
||||
input_index = 0
|
||||
all_file_ids: list[str] = []
|
||||
|
||||
print(f"User: {user_inputs[0]}")
|
||||
events = await _drain(workflow.run(user_inputs[0], stream=True))
|
||||
requests, file_ids = _handle_events(events)
|
||||
all_file_ids.extend(file_ids)
|
||||
input_index += 1
|
||||
|
||||
while requests:
|
||||
request = requests[0]
|
||||
if input_index >= len(user_inputs):
|
||||
break
|
||||
user_input = user_inputs[input_index]
|
||||
print(f"\nUser: {user_input}")
|
||||
|
||||
responses = {request.request_id: HandoffAgentUserRequest.create_response(user_input)}
|
||||
events = await _drain(workflow.run(stream=True, responses=responses))
|
||||
requests, file_ids = _handle_events(events)
|
||||
all_file_ids.extend(file_ids)
|
||||
input_index += 1
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if all_file_ids:
|
||||
print(f"SUCCESS: Found {len(all_file_ids)} file ID(s) in handoff workflow:")
|
||||
for fid in all_file_ids:
|
||||
print(f" - {fid}")
|
||||
else:
|
||||
print("WARNING: No file IDs captured from the handoff workflow.")
|
||||
print("=" * 50)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
User: Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.
|
||||
[Found HostedFileContent: file_id=assistant-JT1sA...]
|
||||
|
||||
=== Conversation So Far ===
|
||||
- user: Please create a text file called hello.txt with 'Hello from handoff workflow!' inside it.
|
||||
- triage_agent: I am handing off your request to create the text file "hello.txt" with the specified content to the code specialist. They will assist you shortly.
|
||||
- code_specialist: The file "hello.txt" has been created with the content "Hello from handoff workflow!". You can download it using the link below:
|
||||
|
||||
[hello.txt](sandbox:/mnt/data/hello.txt)
|
||||
===========================
|
||||
|
||||
[status] IDLE_WITH_PENDING_REQUESTS
|
||||
|
||||
User: exit
|
||||
[status] IDLE
|
||||
|
||||
==================================================
|
||||
SUCCESS: Found 1 file ID(s) in handoff workflow:
|
||||
- assistant-JT1sA...
|
||||
==================================================
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+20
-5
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import (
|
||||
@@ -11,8 +12,9 @@ from agent_framework import (
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import GroupChatRequestSentEvent, MagenticBuilder, MagenticProgressLedger
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -38,7 +40,8 @@ energy efficiency and CO2 emissions of several ML models, streams intermediate
|
||||
events, and prints the final answer. The workflow completes when idle.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI credentials configured for `OpenAIChatClient` and `OpenAIResponsesClient`.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI credentials configured for `AzureOpenAIResponsesClient` and `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
|
||||
@@ -50,11 +53,19 @@ async def main() -> None:
|
||||
"You are a Researcher. You find information without additional computation or quantitative analysis."
|
||||
),
|
||||
# This agent requires the gpt-4o-search-preview model to perform web searches.
|
||||
client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# Create code interpreter tool using instance method
|
||||
coder_client = OpenAIResponsesClient()
|
||||
coder_client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
code_interpreter_tool = coder_client.get_code_interpreter_tool()
|
||||
|
||||
coder_agent = Agent(
|
||||
@@ -70,7 +81,11 @@ async def main() -> None:
|
||||
name="MagenticManager",
|
||||
description="Orchestrator that coordinates the research and coding workflow",
|
||||
instructions="You coordinate a team to complete complex tasks efficiently.",
|
||||
client=OpenAIChatClient(),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
print("\nBuilding Magentic Workflow...")
|
||||
+20
-6
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
@@ -14,9 +15,9 @@ from agent_framework import (
|
||||
WorkflowEvent,
|
||||
WorkflowRunState,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import MagenticBuilder, MagenticPlanReviewRequest
|
||||
from azure.identity._credentials import AzureCliCredential
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Magentic Orchestration + Checkpointing
|
||||
@@ -34,7 +35,8 @@ Concepts highlighted here:
|
||||
`responses` mapping so we can inject the stored human reply during restoration.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for `OpenAIChatClient`.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
TASK = (
|
||||
@@ -57,14 +59,22 @@ def build_workflow(checkpoint_storage: FileCheckpointStorage):
|
||||
name="ResearcherAgent",
|
||||
description="Collects background facts and references for the project.",
|
||||
instructions=("You are the research lead. Gather crisp bullet points the team should know."),
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
writer = Agent(
|
||||
name="WriterAgent",
|
||||
description="Synthesizes the final brief for stakeholders.",
|
||||
instructions=("You convert the research notes into a structured brief with milestones and risks."),
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# Create a manager agent for orchestration
|
||||
@@ -72,7 +82,11 @@ def build_workflow(checkpoint_storage: FileCheckpointStorage):
|
||||
name="MagenticManager",
|
||||
description="Orchestrator that coordinates the research and writing workflow",
|
||||
instructions="You coordinate a team to complete complex tasks efficiently.",
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# The builder wires in the Magentic orchestrator, sets the plan review path, and
|
||||
+20
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import cast
|
||||
|
||||
@@ -11,8 +12,9 @@ from agent_framework import (
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import MagenticBuilder, MagenticPlanReviewRequest, MagenticPlanReviewResponse
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Magentic Orchestration with Human Plan Review
|
||||
@@ -31,7 +33,8 @@ Plan review options:
|
||||
- revise(feedback): Provide textual feedback to modify the plan
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI credentials configured for `OpenAIChatClient`.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI credentials configured for `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
@@ -96,21 +99,33 @@ async def main() -> None:
|
||||
name="ResearcherAgent",
|
||||
description="Specialist in research and information gathering",
|
||||
instructions="You are a Researcher. You find information and gather facts.",
|
||||
client=OpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
analyst_agent = Agent(
|
||||
name="AnalystAgent",
|
||||
description="Data analyst who processes and summarizes research findings",
|
||||
instructions="You are an Analyst. You analyze findings and create summaries.",
|
||||
client=OpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
manager_agent = Agent(
|
||||
name="MagenticManager",
|
||||
description="Orchestrator that coordinates the workflow",
|
||||
instructions="You coordinate a team to complete tasks efficiently.",
|
||||
client=OpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
print("\nBuilding Magentic Workflow with Human Plan Review...")
|
||||
+20
-5
@@ -1,12 +1,14 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import MagenticBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Build a Magentic orchestration and wrap it as an agent.
|
||||
@@ -16,7 +18,8 @@ orchestration through `workflow.as_agent(...)` so the entire Magentic loop can b
|
||||
like any other agent while still emitting callback telemetry.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI credentials configured for `OpenAIChatClient` and `OpenAIResponsesClient`.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI credentials configured for `AzureOpenAIResponsesClient` and `AzureOpenAIResponsesClient`.
|
||||
"""
|
||||
|
||||
|
||||
@@ -28,11 +31,19 @@ async def main() -> None:
|
||||
"You are a Researcher. You find information without additional computation or quantitative analysis."
|
||||
),
|
||||
# This agent requires the gpt-4o-search-preview model to perform web searches.
|
||||
client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
# Create code interpreter tool using instance method
|
||||
coder_client = OpenAIResponsesClient()
|
||||
coder_client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
code_interpreter_tool = coder_client.get_code_interpreter_tool()
|
||||
|
||||
coder_agent = Agent(
|
||||
@@ -48,7 +59,11 @@ async def main() -> None:
|
||||
name="MagenticManager",
|
||||
description="Orchestrator that coordinates the research and coding workflow",
|
||||
instructions="You coordinate a team to complete complex tasks efficiently.",
|
||||
client=OpenAIChatClient(),
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
print("\nBuilding Magentic Workflow...")
|
||||
+9
-3
@@ -1,10 +1,11 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import Message
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -24,13 +25,18 @@ Note on internal adapters:
|
||||
You can safely ignore them when focusing on agent progress.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient (use az login + env vars)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create agents
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
writer = client.as_agent(
|
||||
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
|
||||
+9
-2
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import Annotated, cast
|
||||
|
||||
@@ -10,8 +11,9 @@ from agent_framework import (
|
||||
WorkflowEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Sequential Workflow with Tool Approval Requests
|
||||
@@ -38,6 +40,7 @@ Demonstrate:
|
||||
- Resuming workflow execution after approval via run(responses=..., stream=True).
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI or Azure OpenAI configured with the required environment variables.
|
||||
- Basic familiarity with SequentialBuilder and streaming workflow events.
|
||||
"""
|
||||
@@ -99,7 +102,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
async def main() -> None:
|
||||
# 2. Create the agent with tools (approval mode is set per-tool via decorator)
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
database_agent = client.as_agent(
|
||||
name="DatabaseAgent",
|
||||
instructions=(
|
||||
+9
-3
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
@@ -10,7 +11,7 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -28,7 +29,8 @@ Custom executor contract:
|
||||
- Emit the updated conversation via ctx.send_message([...])
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient (use az login + env vars)
|
||||
"""
|
||||
|
||||
|
||||
@@ -58,7 +60,11 @@ class Summarizer(Executor):
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create a content agent
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
content = client.as_agent(
|
||||
instructions="Produce a concise paragraph answering the user's request.",
|
||||
name="content",
|
||||
+9
-3
@@ -17,11 +17,13 @@ Demonstrate:
|
||||
- Injecting responses back into the workflow via run(responses=..., stream=True)
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables
|
||||
- Authentication via azure-identity (run az login before executing)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from typing import cast
|
||||
|
||||
@@ -30,7 +32,7 @@ from agent_framework import (
|
||||
Message,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import AgentRequestInfoResponse, SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -88,7 +90,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents for a sequential document review workflow
|
||||
drafter = client.as_agent(
|
||||
+9
-3
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -21,13 +22,18 @@ Note on internal adapters:
|
||||
You can safely ignore them when focusing on agent progress.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient (use az login + env vars)
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create agents
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
writer = client.as_agent(
|
||||
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
|
||||
@@ -38,14 +38,10 @@ Once comfortable with these, explore the rest of the samples below.
|
||||
| Azure AI Agents (Streaming) | [agents/azure_ai_agents_streaming.py](./agents/azure_ai_agents_streaming.py) | Add Azure AI agents as edges and handle streaming events |
|
||||
| Azure AI Agents (Shared Thread) | [agents/azure_ai_agents_with_shared_thread.py](./agents/azure_ai_agents_with_shared_thread.py) | Share a common message thread between multiple Azure AI agents in a workflow |
|
||||
| Custom Agent Executors | [agents/custom_agent_executors.py](./agents/custom_agent_executors.py) | Create executors to handle agent run methods |
|
||||
| Sequential Workflow as Agent | [agents/sequential_workflow_as_agent.py](./agents/sequential_workflow_as_agent.py) | Build a sequential workflow orchestrating agents, then expose it as a reusable agent |
|
||||
| Concurrent Workflow as Agent | [agents/concurrent_workflow_as_agent.py](./agents/concurrent_workflow_as_agent.py) | Build a concurrent fan-out/fan-in workflow, then expose it as a reusable agent |
|
||||
| Magentic Workflow as Agent | [agents/magentic_workflow_as_agent.py](./agents/magentic_workflow_as_agent.py) | Configure Magentic orchestration with callbacks, then expose the workflow as an agent |
|
||||
| Workflow as Agent (Reflection Pattern) | [agents/workflow_as_agent_reflection_pattern.py](./agents/workflow_as_agent_reflection_pattern.py) | Wrap a workflow so it can behave like an agent (reflection pattern) |
|
||||
| Workflow as Agent + HITL | [agents/workflow_as_agent_human_in_the_loop.py](./agents/workflow_as_agent_human_in_the_loop.py) | Extend workflow-as-agent with human-in-the-loop capability |
|
||||
| Workflow as Agent with Thread | [agents/workflow_as_agent_with_thread.py](./agents/workflow_as_agent_with_thread.py) | Use AgentThread to maintain conversation history across workflow-as-agent invocations |
|
||||
| Workflow as Agent kwargs | [agents/workflow_as_agent_kwargs.py](./agents/workflow_as_agent_kwargs.py) | Pass custom context (data, user tokens) via kwargs through workflow.as_agent() to @ai_function tools |
|
||||
| Handoff Workflow as Agent | [agents/handoff_workflow_as_agent.py](./agents/handoff_workflow_as_agent.py) | Use a HandoffBuilder workflow as an agent with HITL via FunctionCallContent/FunctionResultContent |
|
||||
|
||||
### checkpoint
|
||||
|
||||
@@ -54,7 +50,7 @@ Once comfortable with these, explore the rest of the samples below.
|
||||
| Checkpoint & Resume | [checkpoint/checkpoint_with_resume.py](./checkpoint/checkpoint_with_resume.py) | Create checkpoints, inspect them, and resume execution |
|
||||
| Checkpoint & HITL Resume | [checkpoint/checkpoint_with_human_in_the_loop.py](./checkpoint/checkpoint_with_human_in_the_loop.py) | Combine checkpointing with human approvals and resume pending HITL requests |
|
||||
| Checkpointed Sub-Workflow | [checkpoint/sub_workflow_checkpoint.py](./checkpoint/sub_workflow_checkpoint.py) | Save and resume a sub-workflow that pauses for human approval |
|
||||
| Handoff + Tool Approval Resume | [checkpoint/handoff_with_tool_approval_checkpoint_resume.py](./checkpoint/handoff_with_tool_approval_checkpoint_resume.py) | Handoff workflow that captures tool-call approvals in checkpoints and resumes with human decisions |
|
||||
| Handoff + Tool Approval Resume | Moved to orchestration samples | Handoff workflow that captures tool-call approvals in checkpoints and resumes with human decisions |
|
||||
| Workflow as Agent Checkpoint | [checkpoint/workflow_as_agent_checkpoint.py](./checkpoint/workflow_as_agent_checkpoint.py) | Enable checkpointing when using workflow.as_agent() with checkpoint_storage parameter |
|
||||
|
||||
### composition
|
||||
@@ -85,19 +81,13 @@ Once comfortable with these, explore the rest of the samples below.
|
||||
| Human-In-The-Loop (Guessing Game) | [human-in-the-loop/guessing_game_with_human_input.py](./human-in-the-loop/guessing_game_with_human_input.py) | Interactive request/response prompts with a human via `ctx.request_info()` |
|
||||
| Agents with Approval Requests in Workflows | [human-in-the-loop/agents_with_approval_requests.py](./human-in-the-loop/agents_with_approval_requests.py) | Agents that create approval requests during workflow execution and wait for human approval to proceed |
|
||||
| Agents with Declaration-Only Tools | [human-in-the-loop/agents_with_declaration_only_tools.py](./human-in-the-loop/agents_with_declaration_only_tools.py) | Workflow pauses when agent calls a client-side tool (`func=None`), caller supplies the result |
|
||||
| SequentialBuilder Request Info | [human-in-the-loop/sequential_request_info.py](./human-in-the-loop/sequential_request_info.py) | Request info for agent responses mid-workflow using `.with_request_info()` on SequentialBuilder |
|
||||
| ConcurrentBuilder Request Info | [human-in-the-loop/concurrent_request_info.py](./human-in-the-loop/concurrent_request_info.py) | Review concurrent agent outputs before aggregation using `.with_request_info()` on ConcurrentBuilder |
|
||||
| GroupChatBuilder Request Info | [human-in-the-loop/group_chat_request_info.py](./human-in-the-loop/group_chat_request_info.py) | Steer group discussions with periodic guidance using `.with_request_info()` on GroupChatBuilder |
|
||||
|
||||
Builder-oriented request-info samples are maintained in the orchestration sample set
|
||||
(sequential, concurrent, and group-chat builder variants).
|
||||
|
||||
### tool-approval
|
||||
|
||||
Tool approval samples demonstrate using `@tool(approval_mode="always_require")` to gate sensitive tool executions with human approval. These work with the high-level builder APIs.
|
||||
|
||||
| Sample | File | Concepts |
|
||||
| ------------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| SequentialBuilder Tool Approval | [tool-approval/sequential_builder_tool_approval.py](./tool-approval/sequential_builder_tool_approval.py) | Sequential workflow with tool approval gates for sensitive operations |
|
||||
| ConcurrentBuilder Tool Approval | [tool-approval/concurrent_builder_tool_approval.py](./tool-approval/concurrent_builder_tool_approval.py) | Concurrent workflow with tool approvals across parallel agents |
|
||||
| GroupChatBuilder Tool Approval | [tool-approval/group_chat_builder_tool_approval.py](./tool-approval/group_chat_builder_tool_approval.py) | Group chat workflow with tool approval for multi-agent collaboration |
|
||||
Builder-based tool approval samples are maintained in the orchestration sample set.
|
||||
|
||||
### observability
|
||||
|
||||
@@ -109,7 +99,8 @@ For additional observability samples in Agent Framework, see the [observability
|
||||
|
||||
### orchestration
|
||||
|
||||
Orchestration samples (Sequential, Concurrent, Handoff, GroupChat, Magentic) have moved to the dedicated [orchestrations samples directory](../orchestrations/README.md).
|
||||
Orchestration-focused samples (Sequential, Concurrent, Handoff, GroupChat, Magentic), including builder-based
|
||||
`workflow.as_agent(...)` variants, are documented in the [orchestrations](../orchestrations/README.md) directory.
|
||||
|
||||
### parallelism
|
||||
|
||||
@@ -169,9 +160,9 @@ Sequential orchestration uses a few small adapter nodes for plumbing:
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- **AzureOpenAIChatClient**: Set Azure OpenAI environment variables as documented [here](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/chat_client/README.md#environment-variables).
|
||||
These variables are required for samples that construct `AzureOpenAIChatClient`
|
||||
Workflow samples that use `AzureOpenAIResponsesClient` expect:
|
||||
|
||||
- **OpenAI** (used in orchestration samples):
|
||||
- [OpenAIChatClient env vars](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/agents/openai_chat_client/README.md)
|
||||
- [OpenAIResponsesClient env vars](https://github.com/microsoft/agent-framework/blob/main/python/samples/getting_started/agents/openai_responses_client/README.md)
|
||||
- `AZURE_AI_PROJECT_ENDPOINT` (Azure AI Foundry Agent Service (V2) project endpoint)
|
||||
- `AZURE_AI_MODEL_DEPLOYMENT_NAME` (model deployment name)
|
||||
|
||||
These values are passed directly into the client constructor via `os.getenv()` in sample code.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import AgentResponse, WorkflowBuilder
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -14,11 +15,12 @@ This sample creates two agents: a Writer agent creates or edits content, and a R
|
||||
evaluates and provides feedback.
|
||||
|
||||
Purpose:
|
||||
Show how to create agents from AzureOpenAIChatClient and use them directly in a workflow. Demonstrate
|
||||
Show how to create agents from AzureOpenAIResponsesClient and use them directly in a workflow. Demonstrate
|
||||
how agents can be used in a workflow.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, edges, events, and streaming or non-streaming runs.
|
||||
"""
|
||||
@@ -27,7 +29,11 @@ Prerequisites:
|
||||
async def main():
|
||||
"""Build and run a simple two node agent workflow: Writer then Reviewer."""
|
||||
# Create the Azure chat client. AzureCliCredential uses your current az login.
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
writer_agent = client.as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import AgentResponseUpdate, Message, WorkflowBuilder
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -13,11 +14,12 @@ This sample creates two agents: a Writer agent creates or edits content, and a R
|
||||
evaluates and provides feedback.
|
||||
|
||||
Purpose:
|
||||
Show how to create agents from AzureOpenAIChatClient and use them directly in a workflow. Demonstrate
|
||||
Show how to create agents from AzureOpenAIResponsesClient and use them directly in a workflow. Demonstrate
|
||||
how agents can be used in a workflow.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, executors, edges, events, and streaming runs.
|
||||
"""
|
||||
@@ -26,7 +28,11 @@ Prerequisites:
|
||||
async def main():
|
||||
"""Build the two node workflow and run it with streaming to observe events."""
|
||||
# Create the Azure chat client. AzureCliCredential uses your current az login.
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
writer_agent = client.as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
|
||||
@@ -1,65 +1,70 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import AgentResponseUpdate, WorkflowBuilder
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Azure AI Agents in a Workflow with Streaming
|
||||
|
||||
This sample shows how to create Azure AI Agents and use them in a workflow with streaming.
|
||||
This sample shows how to create agents backed by Azure OpenAI Responses and use them in a workflow with streaming.
|
||||
|
||||
Prerequisites:
|
||||
- Azure AI Agent Service configured, along with the required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- AZURE_AI_MODEL_DEPLOYMENT_NAME must be set to your Azure OpenAI model deployment name.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, edges, events, and streaming runs.
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with AzureCliCredential() as cred, AzureAIAgentClient(credential=cred) as client:
|
||||
# Create two agents: a Writer and a Reviewer.
|
||||
writer_agent = client.as_agent(
|
||||
name="Writer",
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
)
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
reviewer_agent = client.as_agent(
|
||||
name="Reviewer",
|
||||
instructions=(
|
||||
"You are an excellent content reviewer. "
|
||||
"Provide actionable feedback to the writer about the provided content. "
|
||||
"Provide the feedback in the most concise manner possible."
|
||||
),
|
||||
)
|
||||
# Create two agents: a Writer and a Reviewer.
|
||||
writer_agent = client.as_agent(
|
||||
name="Writer",
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
)
|
||||
|
||||
# Build the workflow by adding agents directly as edges.
|
||||
# Agents adapt to workflow mode: run(stream=True) for incremental updates, run() for complete responses.
|
||||
workflow = WorkflowBuilder(start_executor=writer_agent).add_edge(writer_agent, reviewer_agent).build()
|
||||
reviewer_agent = client.as_agent(
|
||||
name="Reviewer",
|
||||
instructions=(
|
||||
"You are an excellent content reviewer. "
|
||||
"Provide actionable feedback to the writer about the provided content. "
|
||||
"Provide the feedback in the most concise manner possible."
|
||||
),
|
||||
)
|
||||
|
||||
# Track the last author to format streaming output.
|
||||
last_author: str | None = None
|
||||
# Build the workflow by adding agents directly as edges.
|
||||
# Agents adapt to workflow mode: run(stream=True) for incremental updates, run() for complete responses.
|
||||
workflow = WorkflowBuilder(start_executor=writer_agent).add_edge(writer_agent, reviewer_agent).build()
|
||||
|
||||
events = workflow.run(
|
||||
"Create a slogan for a new electric SUV that is affordable and fun to drive.", stream=True
|
||||
)
|
||||
async for event in events:
|
||||
# The outputs of the workflow are whatever the agents produce. So the events are expected to
|
||||
# contain `AgentResponseUpdate` from the agents in the workflow.
|
||||
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
|
||||
update = event.data
|
||||
author = update.author_name
|
||||
if author != last_author:
|
||||
if last_author is not None:
|
||||
print() # Newline between different authors
|
||||
print(f"{author}: {update.text}", end="", flush=True)
|
||||
last_author = author
|
||||
else:
|
||||
print(update.text, end="", flush=True)
|
||||
# Track the last author to format streaming output.
|
||||
last_author: str | None = None
|
||||
|
||||
events = workflow.run("Create a slogan for a new electric SUV that is affordable and fun to drive.", stream=True)
|
||||
async for event in events:
|
||||
# The outputs of the workflow are whatever the agents produce. So the events are expected to
|
||||
# contain `AgentResponseUpdate` from the agents in the workflow.
|
||||
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
|
||||
update = event.data
|
||||
author = update.author_name
|
||||
if author != last_author:
|
||||
if last_author is not None:
|
||||
print() # Newline between different authors
|
||||
print(f"{author}: {update.text}", end="", flush=True)
|
||||
last_author = author
|
||||
else:
|
||||
print(update.text, end="", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+43
-41
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
@@ -12,8 +13,8 @@ from agent_framework import (
|
||||
WorkflowRunState,
|
||||
executor,
|
||||
)
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Agents with a shared thread in a workflow
|
||||
@@ -28,11 +29,12 @@ Notes:
|
||||
- Not all agents can share threads; usually only the same type of agents can share threads.
|
||||
|
||||
Demonstrate:
|
||||
- Creating multiple agents with Azure AI Agent Service (V2 API).
|
||||
- Creating multiple agents with AzureOpenAIResponsesClient.
|
||||
- Setting up a shared thread between agents.
|
||||
|
||||
Prerequisites:
|
||||
- Azure AI Agent Service configured, along with the required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- AZURE_AI_MODEL_DEPLOYMENT_NAME must be set to your Azure OpenAI model deployment name.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with agents, workflows, and executors in the agent framework.
|
||||
"""
|
||||
@@ -51,49 +53,49 @@ async def intercept_agent_response(
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AzureAIProjectAgentProvider(credential=credential) as provider,
|
||||
):
|
||||
writer = await provider.create_agent(
|
||||
instructions=(
|
||||
"You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."
|
||||
),
|
||||
name="writer",
|
||||
)
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
reviewer = await provider.create_agent(
|
||||
instructions=("You are a thoughtful reviewer. Give brief feedback on the previous assistant message."),
|
||||
name="reviewer",
|
||||
)
|
||||
writer = client.as_agent(
|
||||
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
|
||||
name="writer",
|
||||
)
|
||||
|
||||
shared_thread = writer.get_new_thread()
|
||||
# Set the message store to store messages in memory.
|
||||
shared_thread.message_store = ChatMessageStore()
|
||||
reviewer = client.as_agent(
|
||||
instructions=("You are a thoughtful reviewer. Give brief feedback on the previous assistant message."),
|
||||
name="reviewer",
|
||||
)
|
||||
|
||||
writer_executor = AgentExecutor(writer, agent_thread=shared_thread)
|
||||
reviewer_executor = AgentExecutor(reviewer, agent_thread=shared_thread)
|
||||
shared_thread = writer.get_new_thread()
|
||||
# Set the message store to store messages in memory.
|
||||
shared_thread.message_store = ChatMessageStore()
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor=writer_executor)
|
||||
.add_chain([writer_executor, intercept_agent_response, reviewer_executor])
|
||||
.build()
|
||||
)
|
||||
writer_executor = AgentExecutor(writer, agent_thread=shared_thread)
|
||||
reviewer_executor = AgentExecutor(reviewer, agent_thread=shared_thread)
|
||||
|
||||
result = await workflow.run(
|
||||
"Write a tagline for a budget-friendly eBike.",
|
||||
# Keyword arguments will be passed to each agent call.
|
||||
# Setting store=False to avoid storing messages in the service for this example.
|
||||
options={"store": False},
|
||||
)
|
||||
# The final state should be IDLE since the workflow no longer has messages to
|
||||
# process after the reviewer agent responds.
|
||||
assert result.get_final_state() == WorkflowRunState.IDLE
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor=writer_executor)
|
||||
.add_chain([writer_executor, intercept_agent_response, reviewer_executor])
|
||||
.build()
|
||||
)
|
||||
|
||||
# The shared thread now contains the conversation between the writer and reviewer. Print it out.
|
||||
print("=== Shared Thread Conversation ===")
|
||||
for message in shared_thread.message_store.messages:
|
||||
print(f"{message.author_name or message.role}: {message.text}")
|
||||
result = await workflow.run(
|
||||
"Write a tagline for a budget-friendly eBike.",
|
||||
# Keyword arguments will be passed to each agent call.
|
||||
# Setting store=False to avoid storing messages in the service for this example.
|
||||
options={"store": False},
|
||||
)
|
||||
# The final state should be IDLE since the workflow no longer has messages to
|
||||
# process after the reviewer agent responds.
|
||||
assert result.get_final_state() == WorkflowRunState.IDLE
|
||||
|
||||
# The shared thread now contains the conversation between the writer and reviewer. Print it out.
|
||||
print("=== Shared Thread Conversation ===")
|
||||
for message in shared_thread.message_store.messages:
|
||||
print(f"{message.author_name or message.role}: {message.text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Final
|
||||
|
||||
from agent_framework import (
|
||||
@@ -12,7 +13,7 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
executor,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -30,7 +31,8 @@ Demonstrates:
|
||||
- Consuming an AgentExecutorResponse and forwarding an AgentExecutorRequest for the next agent.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Run `az login` before executing.
|
||||
"""
|
||||
|
||||
@@ -94,14 +96,22 @@ async def enrich_with_references(
|
||||
async def main() -> None:
|
||||
"""Run the workflow and stream combined updates from both agents."""
|
||||
# Create the agents
|
||||
research_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
research_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="research_agent",
|
||||
instructions=(
|
||||
"Produce a short, bullet-style briefing with two actionable ideas. Label the section as 'Initial Draft'."
|
||||
),
|
||||
)
|
||||
|
||||
final_editor_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
final_editor_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="final_editor_agent",
|
||||
instructions=(
|
||||
"Use all conversation context (including external notes) to produce the final answer. "
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import AgentResponseUpdate, WorkflowBuilder
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -12,7 +13,8 @@ Sample: AzureOpenAI Chat Agents in a Workflow with Streaming
|
||||
This sample shows how to create AzureOpenAI Chat Agents and use them in a workflow with streaming.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, edges, events, and streaming runs.
|
||||
"""
|
||||
@@ -21,14 +23,22 @@ Prerequisites:
|
||||
async def main():
|
||||
"""Build and run a simple two node agent workflow: Writer then Reviewer."""
|
||||
# Create the agents
|
||||
writer_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
writer_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
name="writer",
|
||||
)
|
||||
|
||||
reviewer_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
reviewer_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content reviewer."
|
||||
"Provide actionable feedback to the writer about the provided content."
|
||||
|
||||
+14
-4
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated
|
||||
|
||||
@@ -21,7 +22,7 @@ from agent_framework import (
|
||||
response_handler,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from typing_extensions import Never
|
||||
@@ -43,7 +44,8 @@ Demonstrates:
|
||||
- Streaming AgentRunUpdateEvent updates alongside human-in-the-loop pauses.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Run `az login` before executing.
|
||||
"""
|
||||
|
||||
@@ -170,7 +172,11 @@ class Coordinator(Executor):
|
||||
|
||||
def create_writer_agent() -> Agent:
|
||||
"""Creates a writer agent with tools."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="writer_agent",
|
||||
instructions=(
|
||||
"You are a marketing writer. Call the available tools before drafting copy so you are precise. "
|
||||
@@ -184,7 +190,11 @@ def create_writer_agent() -> Agent:
|
||||
|
||||
def create_final_editor_agent() -> Agent:
|
||||
"""Creates a final editor agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="final_editor_agent",
|
||||
instructions=(
|
||||
"You are an editor who polishes marketing copy after human approval. "
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
@@ -10,7 +11,7 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -20,14 +21,15 @@ This sample uses two custom executors. A Writer agent creates or edits content,
|
||||
then hands the conversation to a Reviewer agent which evaluates and finalizes the result.
|
||||
|
||||
Purpose:
|
||||
Show how to wrap chat agents created by AzureOpenAIChatClient inside workflow executors. Demonstrate the @handler
|
||||
Show how to wrap chat agents created by AzureOpenAIResponsesClient inside workflow executors. Demonstrate the @handler
|
||||
pattern with typed inputs and typed WorkflowContext[T] outputs, connect executors with the fluent WorkflowBuilder,
|
||||
and finish by yielding outputs from the terminal node.
|
||||
|
||||
Note: When an agent is passed to a workflow, the workflow essenatially wrap the agent in a more sophisticated executor.
|
||||
Note: When an agent is passed to a workflow, the workflow wraps the agent in a more sophisticated executor.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, executors, edges, events, and streaming or non streaming runs.
|
||||
"""
|
||||
@@ -44,8 +46,12 @@ class Writer(Executor):
|
||||
agent: Agent
|
||||
|
||||
def __init__(self, id: str = "writer"):
|
||||
# Create a domain specific agent using your configured AzureOpenAIChatClient.
|
||||
self.agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
# Create a domain specific agent using your configured AzureOpenAIResponsesClient.
|
||||
self.agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content writer. You create new content and edit contents based on the feedback."
|
||||
),
|
||||
@@ -87,7 +93,11 @@ class Reviewer(Executor):
|
||||
|
||||
def __init__(self, id: str = "reviewer"):
|
||||
# Create a domain specific agent that evaluates and refines content.
|
||||
self.agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
self.agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an excellent content reviewer. You review the content and provide feedback to the writer."
|
||||
),
|
||||
|
||||
+9
-3
@@ -1,13 +1,14 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
# Ensure local getting_started package can be imported when running as a script.
|
||||
@@ -42,7 +43,8 @@ to a human, receives the human response, and then forwards that response back
|
||||
to the Worker. The workflow completes when idle.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI account configured and accessible for OpenAIChatClient.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI account configured and accessible for AzureOpenAIResponsesClient.
|
||||
- Familiarity with WorkflowBuilder, Executor, and WorkflowContext from agent_framework.
|
||||
- Understanding of request-response message handling in executors.
|
||||
- (Optional) Review of reflection and escalation patterns, such as those in
|
||||
@@ -100,7 +102,11 @@ async def main() -> None:
|
||||
# and escalation paths for human review.
|
||||
worker = Worker(
|
||||
id="worker",
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
chat_client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
reviewer = ReviewerWithHumanInTheLoop(worker_id="worker")
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Annotated, Any
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
@@ -28,7 +30,8 @@ When to use workflow.as_agent():
|
||||
- To maintain a consistent agent interface for callers
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured
|
||||
"""
|
||||
|
||||
|
||||
@@ -80,7 +83,11 @@ async def main() -> None:
|
||||
print("=" * 70)
|
||||
|
||||
# Create chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agent with tools that use kwargs
|
||||
agent = client.as_agent(
|
||||
|
||||
+21
-4
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -13,7 +14,8 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
"""
|
||||
@@ -33,7 +35,8 @@ Key Concepts Demonstrated:
|
||||
- State management for pending requests and retry logic.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI account configured and accessible for OpenAIChatClient.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- OpenAI account configured and accessible for AzureOpenAIResponsesClient.
|
||||
- Familiarity with WorkflowBuilder, Executor, WorkflowContext, and event handling.
|
||||
- Understanding of how agent messages are generated, reviewed, and re-submitted.
|
||||
"""
|
||||
@@ -186,8 +189,22 @@ async def main() -> None:
|
||||
print("=" * 50)
|
||||
|
||||
print("Building workflow with Worker ↔ Reviewer cycle...")
|
||||
worker = Worker(id="worker", chat_client=OpenAIChatClient(model_id="gpt-4.1-nano"))
|
||||
reviewer = Reviewer(id="reviewer", chat_client=OpenAIChatClient(model_id="gpt-4.1"))
|
||||
worker = Worker(
|
||||
id="worker",
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
reviewer = Reviewer(
|
||||
id="reviewer",
|
||||
client=AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
),
|
||||
)
|
||||
|
||||
agent = (
|
||||
WorkflowBuilder(start_executor=worker)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import AgentThread, ChatMessageStore
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Workflow as Agent with Thread Conversation History and Checkpointing
|
||||
@@ -31,13 +33,18 @@ Use cases:
|
||||
- Long-running workflows that need pause/resume capability
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create a chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
assistant = client.as_agent(
|
||||
name="assistant",
|
||||
@@ -119,7 +126,11 @@ async def demonstrate_thread_serialization() -> None:
|
||||
This shows how conversation history can be persisted and restored,
|
||||
enabling long-running conversational workflows.
|
||||
"""
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
memory_assistant = client.as_agent(
|
||||
name="memory_assistant",
|
||||
|
||||
+9
-3
@@ -1,12 +1,15 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # type: ignore # pragma: no cover
|
||||
else:
|
||||
@@ -30,8 +33,7 @@ from agent_framework import (
|
||||
handler,
|
||||
response_handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
|
||||
"""
|
||||
Sample: Checkpoint + human-in-the-loop quickstart.
|
||||
@@ -178,7 +180,11 @@ def create_workflow(checkpoint_storage: FileCheckpointStorage) -> Workflow:
|
||||
# Wire the workflow DAG. Edges mirror the numbered steps described in the
|
||||
# module docstring. Because `WorkflowBuilder` is declarative, reading these
|
||||
# edges is often the quickest way to understand execution order.
|
||||
writer_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
writer_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions="Write concise, warm release notes that sound human and helpful.",
|
||||
name="writer",
|
||||
)
|
||||
|
||||
+20
-5
@@ -20,18 +20,21 @@ Key concepts:
|
||||
- These are complementary: threads track conversation, checkpoints track workflow state
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured for OpenAIChatClient
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured for AzureOpenAIResponsesClient
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import (
|
||||
AgentThread,
|
||||
ChatMessageStore,
|
||||
InMemoryCheckpointStorage,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
|
||||
async def basic_checkpointing() -> None:
|
||||
@@ -40,7 +43,11 @@ async def basic_checkpointing() -> None:
|
||||
print("Basic Checkpointing with Workflow as Agent")
|
||||
print("=" * 60)
|
||||
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
assistant = client.as_agent(
|
||||
name="assistant",
|
||||
@@ -81,7 +88,11 @@ async def checkpointing_with_thread() -> None:
|
||||
print("Checkpointing with Thread Conversation History")
|
||||
print("=" * 60)
|
||||
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
assistant = client.as_agent(
|
||||
name="memory_assistant",
|
||||
@@ -124,7 +135,11 @@ async def streaming_with_checkpoints() -> None:
|
||||
print("Streaming with Checkpointing")
|
||||
print("=" * 60)
|
||||
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
assistant = client.as_agent(
|
||||
name="streaming_assistant",
|
||||
|
||||
@@ -58,13 +58,13 @@ class TextProcessor(Executor):
|
||||
) -> None:
|
||||
"""Process a text string and return statistics."""
|
||||
text_preview = f"'{request.text[:50]}{'...' if len(request.text) > 50 else ''}'"
|
||||
print(f"🔍 Sub-workflow processing text (Task {request.task_id}): {text_preview}")
|
||||
print(f"Sub-workflow processing text (Task {request.task_id}): {text_preview}")
|
||||
|
||||
# Simple text processing
|
||||
word_count = len(request.text.split()) if request.text.strip() else 0
|
||||
char_count = len(request.text)
|
||||
|
||||
print(f"📊 Task {request.task_id}: {word_count} words, {char_count} characters")
|
||||
print(f"Task {request.task_id}: {word_count} words, {char_count} characters")
|
||||
|
||||
# Create result
|
||||
result = TextProcessingResult(
|
||||
@@ -74,7 +74,7 @@ class TextProcessor(Executor):
|
||||
char_count=char_count,
|
||||
)
|
||||
|
||||
print(f"✅ Sub-workflow completed task {request.task_id}")
|
||||
print(f"Sub-workflow completed task {request.task_id}")
|
||||
# Signal completion by yielding the result
|
||||
await ctx.yield_output(result)
|
||||
|
||||
@@ -92,7 +92,7 @@ class TextProcessingOrchestrator(Executor):
|
||||
@handler
|
||||
async def start_processing(self, texts: list[str], ctx: WorkflowContext[TextProcessingRequest]) -> None:
|
||||
"""Start processing multiple text strings."""
|
||||
print(f"📄 Starting processing of {len(texts)} text strings")
|
||||
print(f"Starting processing of {len(texts)} text strings")
|
||||
print("=" * 60)
|
||||
|
||||
self.expected_count = len(texts)
|
||||
@@ -101,7 +101,7 @@ class TextProcessingOrchestrator(Executor):
|
||||
for i, text in enumerate(texts):
|
||||
task_id = f"task_{i + 1}"
|
||||
request = TextProcessingRequest(text=text, task_id=task_id)
|
||||
print(f"📤 Dispatching {task_id} to sub-workflow")
|
||||
print(f"Dispatching {task_id} to sub-workflow")
|
||||
await ctx.send_message(request, target_id="text_processor_workflow")
|
||||
|
||||
@handler
|
||||
@@ -111,12 +111,12 @@ class TextProcessingOrchestrator(Executor):
|
||||
ctx: WorkflowContext[Never, list[TextProcessingResult]],
|
||||
) -> None:
|
||||
"""Collect results from sub-workflows."""
|
||||
print(f"📥 Collected result from {result.task_id}")
|
||||
print(f"Collected result from {result.task_id}")
|
||||
self.results.append(result)
|
||||
|
||||
# Check if all results are collected
|
||||
if len(self.results) == self.expected_count:
|
||||
print("\n🎉 All tasks completed!")
|
||||
print("\nAll tasks completed!")
|
||||
await ctx.yield_output(self.results)
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ def get_result_summary(results: list[TextProcessingResult]) -> dict[str, Any]:
|
||||
|
||||
def create_sub_workflow() -> WorkflowExecutor:
|
||||
"""Create the text processing sub-workflow."""
|
||||
print("🚀 Setting up sub-workflow...")
|
||||
print("Setting up sub-workflow...")
|
||||
|
||||
text_processor = TextProcessor()
|
||||
processing_workflow = (
|
||||
@@ -151,7 +151,7 @@ def create_sub_workflow() -> WorkflowExecutor:
|
||||
|
||||
async def main():
|
||||
"""Main function to run the basic sub-workflow example."""
|
||||
print("🔧 Setting up parent workflow...")
|
||||
print("Setting up parent workflow...")
|
||||
# Step 1: Create the parent workflow
|
||||
orchestrator = TextProcessingOrchestrator()
|
||||
sub_workflow_executor = create_sub_workflow()
|
||||
@@ -172,14 +172,14 @@ async def main():
|
||||
" Spaces around text ",
|
||||
]
|
||||
|
||||
print(f"\n🧪 Testing with {len(test_texts)} text strings")
|
||||
print(f"\nTesting with {len(test_texts)} text strings")
|
||||
print("=" * 60)
|
||||
|
||||
# Step 3: Run the workflow
|
||||
result = await main_workflow.run(test_texts)
|
||||
|
||||
# Step 4: Display results
|
||||
print("\n📊 Processing Results:")
|
||||
print("\nProcessing Results:")
|
||||
print("=" * 60)
|
||||
|
||||
# Sort results by task_id for consistent display
|
||||
@@ -190,19 +190,19 @@ async def main():
|
||||
for result in sorted_results:
|
||||
preview = result.text[:30] + "..." if len(result.text) > 30 else result.text
|
||||
preview = preview.replace("\n", " ").strip() or "(empty)"
|
||||
print(f"✅ {result.task_id}: '{preview}' -> {result.word_count} words, {result.char_count} chars")
|
||||
print(f"{result.task_id}: '{preview}' -> {result.word_count} words, {result.char_count} chars")
|
||||
|
||||
# Step 6: Display summary
|
||||
summary = get_result_summary(sorted_results)
|
||||
print("\n📈 Summary:")
|
||||
print("\nSummary:")
|
||||
print("=" * 60)
|
||||
print(f"📄 Total texts processed: {summary['total_texts']}")
|
||||
print(f"📝 Total words: {summary['total_words']}")
|
||||
print(f"🔤 Total characters: {summary['total_characters']}")
|
||||
print(f"📊 Average words per text: {summary['average_words_per_text']}")
|
||||
print(f"📏 Average characters per text: {summary['average_characters_per_text']}")
|
||||
print(f"Total texts processed: {summary['total_texts']}")
|
||||
print(f"Total words: {summary['total_words']}")
|
||||
print(f"Total characters: {summary['total_characters']}")
|
||||
print(f"Average words per text: {summary['average_words_per_text']}")
|
||||
print(f"Average characters per text: {summary['average_characters_per_text']}")
|
||||
|
||||
print("\n🏁 Processing complete!")
|
||||
print("\nProcessing complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Annotated, Any
|
||||
|
||||
from agent_framework import (
|
||||
@@ -9,8 +10,9 @@ from agent_framework import (
|
||||
WorkflowExecutor,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Sub-Workflow kwargs Propagation
|
||||
@@ -26,7 +28,8 @@ Key Concepts:
|
||||
- Useful for passing authentication tokens, configuration, or request context
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured
|
||||
"""
|
||||
|
||||
|
||||
@@ -74,7 +77,11 @@ async def main() -> None:
|
||||
print("=" * 70)
|
||||
|
||||
# Create chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create an agent with tools that use kwargs
|
||||
inner_agent = client.as_agent(
|
||||
|
||||
+3
-3
@@ -319,14 +319,14 @@ async def main() -> None:
|
||||
]
|
||||
|
||||
# Run the workflow
|
||||
print(f"🧪 Testing with {len(test_requests)} mixed requests.")
|
||||
print("🚀 Starting main workflow...")
|
||||
print(f"Testing with {len(test_requests)} mixed requests.")
|
||||
print("Starting main workflow...")
|
||||
run_result = await main_workflow.run(test_requests)
|
||||
|
||||
# Handle request info events
|
||||
request_info_events = run_result.get_request_info_events()
|
||||
if request_info_events:
|
||||
print(f"\n🔍 Handling {len(request_info_events)} request info events...\n")
|
||||
print(f"\nHandling {len(request_info_events)} request info events...\n")
|
||||
|
||||
responses: dict[str, ResourceResponse | PolicyResponse] = {}
|
||||
for event in request_info_events:
|
||||
|
||||
+16
-16
@@ -73,7 +73,7 @@ def build_email_address_validation_workflow() -> Workflow:
|
||||
email address to the next executor in the workflow.
|
||||
"""
|
||||
sanitized = email_address.strip()
|
||||
print(f"✂️ Sanitized email address: '{sanitized}'")
|
||||
print(f"Sanitized email address: '{sanitized}'")
|
||||
await ctx.send_message(SanitizedEmailResult(original=email_address, sanitized=sanitized, is_valid=False))
|
||||
|
||||
class EmailFormatValidator(Executor):
|
||||
@@ -91,14 +91,14 @@ def build_email_address_validation_workflow() -> Workflow:
|
||||
When the format is valid, it sends the validated email address to the next executor in the workflow.
|
||||
"""
|
||||
if "@" not in partial_result.sanitized or "." not in partial_result.sanitized.split("@")[-1]:
|
||||
print(f"❌ Invalid email format: '{partial_result.sanitized}'")
|
||||
print(f"Invalid email format: '{partial_result.sanitized}'")
|
||||
await ctx.yield_output(
|
||||
SanitizedEmailResult(
|
||||
original=partial_result.original, sanitized=partial_result.sanitized, is_valid=False
|
||||
)
|
||||
)
|
||||
return
|
||||
print(f"✅ Validated email format: '{partial_result.sanitized}'")
|
||||
print(f"Validated email format: '{partial_result.sanitized}'")
|
||||
await ctx.send_message(
|
||||
SanitizedEmailResult(
|
||||
original=partial_result.original, sanitized=partial_result.sanitized, is_valid=False
|
||||
@@ -120,7 +120,7 @@ def build_email_address_validation_workflow() -> Workflow:
|
||||
to an external system to user for validation.
|
||||
"""
|
||||
domain = partial_result.sanitized.split("@")[-1]
|
||||
print(f"🔍 Validating domain: '{domain}'")
|
||||
print(f"Validating domain: '{domain}'")
|
||||
self._pending_domains[domain] = partial_result
|
||||
# Send a request to the external system via the request_info mechanism
|
||||
await ctx.request_info(request_data=domain, response_type=bool)
|
||||
@@ -138,14 +138,14 @@ def build_email_address_validation_workflow() -> Workflow:
|
||||
raise ValueError(f"Received response for unknown domain: '{original_request}'")
|
||||
partial_result = self._pending_domains.pop(original_request)
|
||||
if is_valid:
|
||||
print(f"✅ Domain '{original_request}' is valid.")
|
||||
print(f"Domain '{original_request}' is valid.")
|
||||
await ctx.yield_output(
|
||||
SanitizedEmailResult(
|
||||
original=partial_result.original, sanitized=partial_result.sanitized, is_valid=True
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(f"❌ Domain '{original_request}' is invalid.")
|
||||
print(f"Domain '{original_request}' is invalid.")
|
||||
await ctx.yield_output(
|
||||
SanitizedEmailResult(
|
||||
original=partial_result.original, sanitized=partial_result.sanitized, is_valid=False
|
||||
@@ -201,15 +201,15 @@ class SmartEmailOrchestrator(Executor):
|
||||
"""
|
||||
recipient = email.recipient
|
||||
if recipient in self._approved_recipients:
|
||||
print(f"📧 Recipient '{recipient}' has been previously approved.")
|
||||
print(f"Recipient '{recipient}' has been previously approved.")
|
||||
await ctx.send_message(email)
|
||||
return
|
||||
if recipient in self._disapproved_recipients:
|
||||
print(f"🚫 Blocking email to previously disapproved recipient: '{recipient}'")
|
||||
print(f"Blocking email to previously disapproved recipient: '{recipient}'")
|
||||
await ctx.yield_output(False)
|
||||
return
|
||||
|
||||
print(f"🔍 Validating new recipient email address: '{recipient}'")
|
||||
print(f"Validating new recipient email address: '{recipient}'")
|
||||
self._pending_emails[recipient] = email
|
||||
await ctx.send_message(recipient)
|
||||
|
||||
@@ -227,7 +227,7 @@ class SmartEmailOrchestrator(Executor):
|
||||
raise TypeError(f"Expected domain string, got {type(request.source_event.data)}")
|
||||
domain = request.source_event.data
|
||||
is_valid = domain in self._approved_domains
|
||||
print(f"🌐 External domain validation for '{domain}': {'valid' if is_valid else 'invalid'}")
|
||||
print(f"External domain validation for '{domain}': {'valid' if is_valid else 'invalid'}")
|
||||
await ctx.send_message(request.create_response(is_valid), target_id=request.executor_id)
|
||||
|
||||
@handler
|
||||
@@ -243,11 +243,11 @@ class SmartEmailOrchestrator(Executor):
|
||||
email = self._pending_emails.pop(result.original)
|
||||
email.recipient = result.sanitized # Use the sanitized email address
|
||||
if result.is_valid:
|
||||
print(f"✅ Email address '{result.original}' is valid.")
|
||||
print(f"Email address '{result.original}' is valid.")
|
||||
self._approved_recipients.add(result.original)
|
||||
await ctx.send_message(email)
|
||||
else:
|
||||
print(f"🚫 Email address '{result.original}' is invalid. Blocking email.")
|
||||
print(f"Email address '{result.original}' is invalid. Blocking email.")
|
||||
self._disapproved_recipients.add(result.original)
|
||||
await ctx.yield_output(False)
|
||||
|
||||
@@ -258,9 +258,9 @@ class EmailDelivery(Executor):
|
||||
@handler
|
||||
async def handle(self, email: Email, ctx: WorkflowContext[Never, bool]) -> None:
|
||||
"""Simulate sending the email and yield True as the final result."""
|
||||
print(f"📤 Sending email to '{email.recipient}' with subject '{email.subject}'")
|
||||
print(f"Sending email to '{email.recipient}' with subject '{email.subject}'")
|
||||
await asyncio.sleep(1) # Simulate network delay
|
||||
print(f"✅ Email sent to '{email.recipient}' successfully.")
|
||||
print(f"Email sent to '{email.recipient}' successfully.")
|
||||
await ctx.yield_output(True)
|
||||
|
||||
|
||||
@@ -294,10 +294,10 @@ async def main() -> None:
|
||||
|
||||
# Execute the workflow
|
||||
for email in test_emails:
|
||||
print(f"\n🚀 Processing email to '{email.recipient}'")
|
||||
print(f"\nProcessing email to '{email.recipient}'")
|
||||
async for event in workflow.run(email, stream=True):
|
||||
if event.type == "output":
|
||||
print(f"🎉 Final result for '{email.recipient}': {'Delivered' if event.data else 'Blocked'}")
|
||||
print(f"Final result for '{email.recipient}': {'Delivered' if event.data else 'Blocked'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -14,7 +14,7 @@ from agent_framework import ( # Core chat primitives used to build requests
|
||||
WorkflowContext, # Per-run context and event bus
|
||||
executor, # Decorator to declare a Python function as a workflow executor
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient # Thin client wrapper for Azure OpenAI chat models
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient # Thin client wrapper for Azure OpenAI chat models
|
||||
from azure.identity import AzureCliCredential # Uses your az CLI login for credentials
|
||||
from pydantic import BaseModel # Structured outputs for safer parsing
|
||||
from typing_extensions import Never
|
||||
@@ -32,10 +32,11 @@ Purpose:
|
||||
- Illustrate how to transform one agent's structured result into a new AgentExecutorRequest for a downstream agent.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- You understand the basics of WorkflowBuilder, executors, and events in this framework.
|
||||
- You know the concept of edge conditions and how they gate routes using a predicate function.
|
||||
- Azure OpenAI access is configured for AzureOpenAIChatClient. You should be logged in with Azure CLI (AzureCliCredential)
|
||||
and have the Azure OpenAI environment variables set as documented in the getting started chat client README.
|
||||
- Azure OpenAI access is configured for AzureOpenAIResponsesClient. You should be logged in with Azure CLI (AzureCliCredential)
|
||||
and have the Foundry V2 Project environment variables set as documented in the getting started chat client README.
|
||||
- The sample email resource file exists at workflow/resources/email.txt.
|
||||
|
||||
High level flow:
|
||||
@@ -131,7 +132,11 @@ async def to_email_assistant_request(
|
||||
def create_spam_detector_agent() -> Agent:
|
||||
"""Helper to create a spam detection agent."""
|
||||
# AzureCliCredential uses your current az login. This avoids embedding secrets in code.
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Always return JSON with fields is_spam (bool), reason (string), and email_content (string). "
|
||||
@@ -145,7 +150,11 @@ def create_spam_detector_agent() -> Agent:
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Helper to create an email assistant agent."""
|
||||
# AzureCliCredential uses your current az login. This avoids embedding secrets in code.
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an email assistant that helps users draft professional responses to emails. "
|
||||
"Your input may be a JSON object that includes 'email_content'; base your reply on that content. "
|
||||
@@ -178,7 +187,7 @@ async def main() -> None:
|
||||
|
||||
# Read Email content from the sample resource file.
|
||||
# This keeps the sample deterministic since the model sees the same email every run.
|
||||
email_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "email.txt")
|
||||
email_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "email.txt") # noqa: ASYNC240
|
||||
|
||||
with open(email_path) as email_file: # noqa: ASYNC230
|
||||
email = email_file.read()
|
||||
|
||||
+23
-5
@@ -13,13 +13,14 @@ from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowEvent,
|
||||
executor,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Never
|
||||
@@ -42,6 +43,7 @@ Show how to:
|
||||
- Apply conditional persistence logic (short vs long emails).
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Familiarity with WorkflowBuilder, executors, edges, and events.
|
||||
- Understanding of multi-selection edge groups and how their selection function maps to target ids.
|
||||
- Experience with workflow state for persisting and reusing objects.
|
||||
@@ -177,12 +179,16 @@ async def handle_uncertain(analysis: AnalysisResult, ctx: WorkflowContext[Never,
|
||||
async def database_access(analysis: AnalysisResult, ctx: WorkflowContext[Never, str]) -> None:
|
||||
# Simulate DB writes for email and analysis (and summary if present)
|
||||
await asyncio.sleep(0.05)
|
||||
await ctx.add_event(DatabaseEvent(f"Email {analysis.email_id} saved to database."))
|
||||
await ctx.add_event(DatabaseEvent(type="database_event", data=f"Email {analysis.email_id} saved to database.")) # type: ignore
|
||||
|
||||
|
||||
def create_email_analysis_agent() -> Agent:
|
||||
"""Creates the email analysis agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Always return JSON with fields 'spam_decision' (one of NotSpam, Spam, Uncertain) "
|
||||
@@ -195,7 +201,11 @@ def create_email_analysis_agent() -> Agent:
|
||||
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Creates the email assistant agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an email assistant that helps users draft responses to emails with professionalism."),
|
||||
name="email_assistant_agent",
|
||||
default_options={"response_format": EmailResponse},
|
||||
@@ -204,7 +214,11 @@ def create_email_assistant_agent() -> Agent:
|
||||
|
||||
def create_email_summary_agent() -> Agent:
|
||||
"""Creates the email summary agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an assistant that helps users summarize emails."),
|
||||
name="email_summary_agent",
|
||||
default_options={"response_format": EmailSummaryModel},
|
||||
@@ -267,6 +281,10 @@ async def main() -> None:
|
||||
if isinstance(event, DatabaseEvent):
|
||||
print(f"{event}")
|
||||
elif event.type == "output":
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
# Agent executors stream token-level updates. Skip these to keep sample
|
||||
# output focused on final workflow results.
|
||||
continue
|
||||
print(f"Workflow output: {event.data}")
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from enum import Enum
|
||||
|
||||
from agent_framework import (
|
||||
@@ -8,13 +9,14 @@ from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponseUpdate,
|
||||
Executor,
|
||||
Message,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -26,7 +28,8 @@ What it does:
|
||||
- The workflow completes when the correct number is guessed.
|
||||
|
||||
Prerequisites:
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIChatClient` agent.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIResponsesClient` agent.
|
||||
- Authentication via `azure-identity` — uses `AzureCliCredential()` (run `az login`).
|
||||
"""
|
||||
|
||||
@@ -116,7 +119,11 @@ class ParseJudgeResponse(Executor):
|
||||
|
||||
def create_judge_agent() -> Agent:
|
||||
"""Create a judge agent that evaluates guesses."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You strictly respond with one of: MATCHED, ABOVE, BELOW based on the given target and guess."),
|
||||
name="judge_agent",
|
||||
)
|
||||
@@ -140,12 +147,16 @@ async def main():
|
||||
.build()
|
||||
)
|
||||
|
||||
# Step 2: Run the workflow and print the events.
|
||||
# Step 2: Run the workflow with concise streaming output.
|
||||
iterations = 0
|
||||
async for event in workflow.run(NumberSignal.INIT, stream=True):
|
||||
if event.type == "executor_completed" and event.executor_id == "guess_number":
|
||||
iterations += 1
|
||||
print(f"Event: {event}")
|
||||
elif event.type == "output":
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
# Agent executor streams token-level updates; skip to avoid noisy logs.
|
||||
continue
|
||||
print(f"Workflow output: {event.data}")
|
||||
|
||||
# This is essentially a binary search, so the number of iterations should be logarithmic.
|
||||
# The maximum number of iterations is [log2(range size)]. For a range of 1 to 100, this is log2(100) which is 7.
|
||||
|
||||
@@ -18,7 +18,7 @@ from agent_framework import ( # Core chat primitives used to form LLM requests
|
||||
WorkflowContext, # Per-run context and event bus
|
||||
executor, # Decorator to turn a function into a workflow executor
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient # Thin client for Azure OpenAI chat models
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient # Thin client for Azure OpenAI chat models
|
||||
from azure.identity import AzureCliCredential # Uses your az CLI login for credentials
|
||||
from pydantic import BaseModel # Structured outputs with validation
|
||||
from typing_extensions import Never
|
||||
@@ -39,9 +39,10 @@ on that type.
|
||||
- Use ctx.yield_output() to provide workflow results - the workflow completes when idle with no pending work.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Familiarity with WorkflowBuilder, executors, edges, and events.
|
||||
- Understanding of switch-case edge groups and how Case and Default are evaluated in order.
|
||||
- Working Azure OpenAI configuration for AzureOpenAIChatClient, with Azure CLI login and required environment variables.
|
||||
- Working Azure OpenAI configuration for AzureOpenAIResponsesClient, with Azure CLI login and required environment variables.
|
||||
- Access to workflow/resources/ambiguous_email.txt, or accept the inline fallback string.
|
||||
"""
|
||||
|
||||
@@ -154,7 +155,11 @@ async def handle_uncertain(detection: DetectionResult, ctx: WorkflowContext[Neve
|
||||
|
||||
def create_spam_detection_agent() -> Agent:
|
||||
"""Create and return the spam detection agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Be less confident in your assessments. "
|
||||
@@ -168,7 +173,11 @@ def create_spam_detection_agent() -> Agent:
|
||||
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Create and return the email assistant agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an email assistant that helps users draft responses to emails with professionalism."),
|
||||
name="email_assistant_agent",
|
||||
default_options={"response_format": EmailResponse},
|
||||
|
||||
@@ -23,10 +23,11 @@ The workflow:
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.declarative import (
|
||||
AgentExternalInputRequest,
|
||||
AgentExternalInputResponse,
|
||||
@@ -164,7 +165,11 @@ async def main() -> None:
|
||||
plugin = TicketingPlugin()
|
||||
|
||||
# Create Azure OpenAI client
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents with structured outputs
|
||||
self_service_agent = client.as_agent(
|
||||
@@ -260,7 +265,9 @@ async def main() -> None:
|
||||
async for event in stream:
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
source_id = getattr(event, "source_executor_id", "")
|
||||
# source_executor_id is only available on request_info events.
|
||||
# For output events, use executor_id to identify the emitting node.
|
||||
source_id = event.executor_id or ""
|
||||
|
||||
# Check if this is a SendActivity output (activity text from log_ticket, log_route, etc.)
|
||||
if "log_" in source_id.lower():
|
||||
|
||||
@@ -22,9 +22,10 @@ Usage:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.declarative import WorkflowFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -122,7 +123,11 @@ class ManagerResponse(BaseModel):
|
||||
async def main() -> None:
|
||||
"""Run the deep research workflow."""
|
||||
# Create Azure OpenAI client
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agents
|
||||
research_agent = client.as_agent(
|
||||
|
||||
@@ -6,12 +6,13 @@ function tools assigned. Exits the loop when the user enters "exit".
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
from agent_framework import FileCheckpointStorage, tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework_declarative import ExternalInputRequest, ExternalInputResponse, WorkflowFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -62,7 +63,11 @@ def get_item_price(name: Annotated[str, Field(description="Menu item name")]) ->
|
||||
|
||||
async def main():
|
||||
# Create agent with tools
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
menu_agent = client.as_agent(
|
||||
name="MenuAgent",
|
||||
instructions="Answer questions about menu items, specials, and prices.",
|
||||
|
||||
@@ -13,9 +13,10 @@ Demonstrates sequential multi-agent pipeline:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.declarative import WorkflowFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -49,7 +50,11 @@ Return the final polished version."""
|
||||
|
||||
async def main() -> None:
|
||||
"""Run the marketing workflow with real Azure AI agents."""
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
analyst_agent = client.as_agent(
|
||||
name="AnalystAgent",
|
||||
|
||||
@@ -15,14 +15,15 @@ The workflow loops until the teacher gives congratulations or max turns reached.
|
||||
Prerequisites:
|
||||
- Azure OpenAI deployment with chat completion capability
|
||||
- Environment variables:
|
||||
AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
|
||||
AZURE_OPENAI_DEPLOYMENT_NAME: Your deployment name (optional, defaults to gpt-4o)
|
||||
AZURE_AI_PROJECT_ENDPOINT: Your Azure AI Foundry Agent Service (V2) project endpoint
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME: Your model deployment name
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.declarative import WorkflowFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -51,7 +52,11 @@ Focus on building understanding, not just getting the right answer."""
|
||||
async def main() -> None:
|
||||
"""Run the student-teacher workflow with real Azure AI agents."""
|
||||
# Create chat client
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create student and teacher agents
|
||||
student_agent = client.as_agent(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@@ -17,7 +18,7 @@ from agent_framework import (
|
||||
handler,
|
||||
response_handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from typing_extensions import Never
|
||||
|
||||
@@ -37,7 +38,8 @@ Demonstrates:
|
||||
- Handling human feedback and routing it to the appropriate agents.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Run `az login` before executing.
|
||||
"""
|
||||
|
||||
@@ -161,13 +163,21 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
async def main() -> None:
|
||||
"""Run the workflow and bridge human feedback between two agents."""
|
||||
# Create the agents
|
||||
writer_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
writer_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="writer_agent",
|
||||
instructions=("You are a marketing writer."),
|
||||
tool_choice="required",
|
||||
)
|
||||
|
||||
final_editor_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
final_editor_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="final_editor_agent",
|
||||
instructions=(
|
||||
"You are an editor who polishes marketing copy after human approval. "
|
||||
|
||||
+20
-6
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
@@ -15,7 +16,8 @@ from agent_framework import (
|
||||
handler,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from typing_extensions import Never
|
||||
|
||||
"""
|
||||
@@ -45,6 +47,7 @@ Demonstrate:
|
||||
- Handling approval requests during workflow execution.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure AI Agent Service configured, along with the required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, edges, events, request_info events (type='request_info'), and streaming runs.
|
||||
@@ -193,13 +196,20 @@ class EmailPreprocessor(Executor):
|
||||
@handler
|
||||
async def preprocess(self, email: Email, ctx: WorkflowContext[str]) -> None:
|
||||
"""Preprocess the incoming email."""
|
||||
message = str(email)
|
||||
email_payload = (
|
||||
f"Incoming email:\n"
|
||||
f"From: {email.sender}\n"
|
||||
f"Subject: {email.subject}\n"
|
||||
f"Body: {email.body}"
|
||||
)
|
||||
message = email_payload
|
||||
if email.sender in self.special_email_addresses:
|
||||
note = (
|
||||
"Pay special attention to this sender. This email is very important. "
|
||||
"Gather relevant information from all previous emails within my team before responding."
|
||||
"Priority sender context: this message is business-critical. "
|
||||
"If additional context is needed, use available tools to retrieve only the minimum relevant "
|
||||
"prior team communication related to this request."
|
||||
)
|
||||
message = f"{note}\n\n{message}"
|
||||
message = f"{note}\n\n{email_payload}"
|
||||
|
||||
await ctx.send_message(message)
|
||||
|
||||
@@ -215,7 +225,11 @@ async def conclude_workflow(
|
||||
|
||||
async def main() -> None:
|
||||
# Create agent
|
||||
email_writer_agent = OpenAIChatClient().as_agent(
|
||||
email_writer_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="EmailWriter",
|
||||
instructions=("You are an excellent email assistant. You respond to incoming emails."),
|
||||
# tools with `approval_mode="always_require"` will trigger approval requests
|
||||
|
||||
+8
-2
@@ -16,16 +16,18 @@ Flow:
|
||||
4. The workflow resumes — the agent sees the tool result and finishes.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI endpoint configured via environment variables.
|
||||
- `az login` for AzureCliCredential.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Content, FunctionTool, WorkflowBuilder
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
# A declaration-only tool: the schema is sent to the LLM, but the framework
|
||||
@@ -45,7 +47,11 @@ get_user_location = FunctionTool(
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="WeatherBot",
|
||||
instructions=(
|
||||
"You are a helpful weather assistant. "
|
||||
|
||||
+9
-3
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from collections.abc import AsyncIterable
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -16,7 +17,7 @@ from agent_framework import (
|
||||
handler,
|
||||
response_handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -37,7 +38,8 @@ Demonstrate:
|
||||
- Driving the loop in application code with run and responses parameter.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Basic familiarity with WorkflowBuilder, executors, edges, events, and streaming runs.
|
||||
"""
|
||||
@@ -183,7 +185,11 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
async def main() -> None:
|
||||
"""Run the human-in-the-loop guessing game workflow."""
|
||||
# Create agent and executor
|
||||
guessing_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
guessing_agent = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
name="GuessingAgent",
|
||||
instructions=(
|
||||
"You guess a number between 1 and 10. "
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor, # Wraps a ChatAgent as an Executor for use in workflows
|
||||
AgentExecutorRequest, # The message bundle sent to an AgentExecutor
|
||||
AgentExecutorResponse, # The structured result returned by an AgentExecutor
|
||||
AgentResponseUpdate,
|
||||
Executor, # Base class for custom Python executors
|
||||
Message, # Chat message structure
|
||||
WorkflowBuilder, # Fluent builder for wiring the workflow graph
|
||||
WorkflowContext, # Per run context and event bus
|
||||
handler, # Decorator to mark an Executor method as invokable
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential # Uses your az CLI login for credentials
|
||||
from typing_extensions import Never
|
||||
|
||||
@@ -29,8 +31,9 @@ Show how to construct a parallel branch pattern in workflows. Demonstrate:
|
||||
- Fan in by collecting a list of AgentExecutorResponse objects and reducing them to a single result.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Familiarity with WorkflowBuilder, executors, edges, events, and streaming runs.
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient. Log in with Azure CLI and set any required environment variables.
|
||||
- Azure OpenAI access configured for AzureOpenAIResponsesClient. Log in with Azure CLI and set any required environment variables.
|
||||
- Comfort reading AgentExecutorResponse.agent_response.text for assistant output aggregation.
|
||||
"""
|
||||
|
||||
@@ -87,13 +90,31 @@ class AggregateInsights(Executor):
|
||||
await ctx.yield_output(consolidated)
|
||||
|
||||
|
||||
def render_live_streams(buffers: dict[str, str], order: list[str], completed: set[str]) -> None:
|
||||
"""Render concurrent agent streams in separate sections."""
|
||||
# Clear terminal and move cursor to top-left for a live dashboard effect.
|
||||
print("\033[2J\033[H", end="")
|
||||
print("=== Expert Streams (Live) ===")
|
||||
print("Concurrent agent updates are shown below as they stream.\n")
|
||||
for agent_id in order:
|
||||
state = "completed" if agent_id in completed else "streaming"
|
||||
print(f"[{agent_id}] ({state})")
|
||||
print(buffers.get(agent_id, ""))
|
||||
print("-" * 80)
|
||||
print("", end="", flush=True)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create executor and agent instances
|
||||
dispatcher = DispatchToExperts(id="dispatcher")
|
||||
aggregator = AggregateInsights(id="aggregator")
|
||||
|
||||
researcher = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
||||
" opportunities, and risks."
|
||||
@@ -102,7 +123,11 @@ async def main() -> None:
|
||||
)
|
||||
)
|
||||
marketer = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
" aligned to the prompt."
|
||||
@@ -111,7 +136,11 @@ async def main() -> None:
|
||||
)
|
||||
)
|
||||
legal = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
" based on the prompt."
|
||||
@@ -128,18 +157,32 @@ async def main() -> None:
|
||||
.build()
|
||||
)
|
||||
|
||||
# 3) Run with a single prompt and print progress plus the final consolidated output
|
||||
# 3) Run with a single prompt and render live expert streams plus final consolidated output.
|
||||
expert_order = ["researcher", "marketer", "legal"]
|
||||
expert_buffers: dict[str, str] = {expert_id: "" for expert_id in expert_order}
|
||||
completed_experts: set[str] = set()
|
||||
final_output: str | None = None
|
||||
|
||||
async for event in workflow.run(
|
||||
"We are launching a new budget-friendly electric bike for urban commuters.", stream=True
|
||||
):
|
||||
if event.type == "executor_invoked":
|
||||
# Show when executors are invoked and completed for lightweight observability.
|
||||
print(f"{event.executor_id} invoked")
|
||||
elif event.type == "executor_completed":
|
||||
print(f"{event.executor_id} completed")
|
||||
if event.type == "executor_completed" and event.executor_id in expert_buffers:
|
||||
completed_experts.add(event.executor_id)
|
||||
render_live_streams(expert_buffers, expert_order, completed_experts)
|
||||
elif event.type == "output":
|
||||
print("===== Final Aggregated Output =====")
|
||||
print(event.data)
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
executor_id = event.executor_id or ""
|
||||
if executor_id in expert_buffers:
|
||||
expert_buffers[executor_id] += event.data.text
|
||||
render_live_streams(expert_buffers, expert_order, completed_experts)
|
||||
continue
|
||||
|
||||
if event.executor_id == "aggregator":
|
||||
final_output = str(event.data)
|
||||
|
||||
if final_output:
|
||||
print("\n=== Final Consolidated Output ===\n")
|
||||
print(final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -15,7 +16,7 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
executor,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Never
|
||||
@@ -34,7 +35,8 @@ Show how to:
|
||||
- Compose agent backed executors with function style executors and yield the final output when the workflow completes.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient with required environment variables.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure OpenAI configured for AzureOpenAIResponsesClient with required environment variables.
|
||||
- Authentication via azure-identity. Use AzureCliCredential and run az login before executing the sample.
|
||||
- Familiarity with WorkflowBuilder, executors, conditional edges, and streaming runs.
|
||||
"""
|
||||
@@ -156,7 +158,11 @@ async def handle_spam(detection: DetectionResult, ctx: WorkflowContext[Never, st
|
||||
|
||||
def create_spam_detection_agent() -> Agent:
|
||||
"""Creates a spam detection agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Always return JSON with fields is_spam (bool) and reason (string)."
|
||||
@@ -169,7 +175,11 @@ def create_spam_detection_agent() -> Agent:
|
||||
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Creates an email assistant agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an email assistant that helps users draft responses to emails with professionalism. "
|
||||
"Return JSON with a single field 'response' containing the drafted reply."
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Annotated, Any, cast
|
||||
|
||||
from agent_framework import Message, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
@@ -22,7 +24,8 @@ Key Concepts:
|
||||
- Works with Sequential, Concurrent, GroupChat, Handoff, and Magentic patterns
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI environment variables configured
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Environment variables configured
|
||||
"""
|
||||
|
||||
|
||||
@@ -74,7 +77,11 @@ async def main() -> None:
|
||||
print("=" * 70)
|
||||
|
||||
# Create chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agent with tools that use kwargs
|
||||
agent = client.as_agent(
|
||||
|
||||
+19
-5
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agent_framework import (
|
||||
@@ -14,7 +15,7 @@ from agent_framework import (
|
||||
WorkflowViz,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from typing_extensions import Never
|
||||
|
||||
@@ -27,7 +28,8 @@ What it does:
|
||||
- Visualization: generate Mermaid and GraphViz representations via `WorkflowViz` and optionally export SVG.
|
||||
|
||||
Prerequisites:
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIChatClient` agents.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIResponsesClient` agents.
|
||||
- Authentication via `azure-identity` — uses `AzureCliCredential()` (run `az login`).
|
||||
- For visualization export: `pip install graphviz>=0.20.0` and install GraphViz binaries.
|
||||
"""
|
||||
@@ -90,7 +92,11 @@ async def main() -> None:
|
||||
|
||||
# Create agent instances
|
||||
researcher = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
||||
" opportunities, and risks."
|
||||
@@ -100,7 +106,11 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
marketer = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
" aligned to the prompt."
|
||||
@@ -110,7 +120,11 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
legal = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
" based on the prompt."
|
||||
|
||||
Reference in New Issue
Block a user