From e43fc8ccece565810f0789b79bf8ad88bb2851cd Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 31 Mar 2026 23:32:30 -0700 Subject: [PATCH] Python: Fix migration samples (#5015) * Fix migration samples * Fix migration samples 2 * Fix formatting * Comments --- python/samples/autogen-migration/README.md | 8 +-- .../01_round_robin_group_chat.py | 4 +- .../orchestrations/04_magentic_one.py | 2 +- ...c_assistant_agent.py => 01_basic_agent.py} | 0 ...ent_with_tool.py => 02_agent_with_tool.py} | 0 ...tream.py => 03_agent_thread_and_stream.py} | 0 .../02_chat_completion_with_tool.py | 15 +---- .../01_basic_responses_agent.py | 5 +- .../02_responses_agent_with_tool.py | 5 +- .../03_responses_agent_structured_output.py | 7 +- .../orchestrations/concurrent_basic.py | 8 ++- .../orchestrations/group_chat.py | 66 +++++++++++-------- .../orchestrations/handoff.py | 18 ++--- .../orchestrations/magentic.py | 46 ++++++++----- 14 files changed, 100 insertions(+), 84 deletions(-) rename python/samples/autogen-migration/single_agent/{01_basic_assistant_agent.py => 01_basic_agent.py} (100%) rename python/samples/autogen-migration/single_agent/{02_assistant_agent_with_tool.py => 02_agent_with_tool.py} (100%) rename python/samples/autogen-migration/single_agent/{03_assistant_agent_thread_and_stream.py => 03_agent_thread_and_stream.py} (100%) diff --git a/python/samples/autogen-migration/README.md b/python/samples/autogen-migration/README.md index 2bfa229183..30f03e3fea 100644 --- a/python/samples/autogen-migration/README.md +++ b/python/samples/autogen-migration/README.md @@ -6,9 +6,9 @@ This gallery helps AutoGen developers move to the Microsoft Agent Framework (AF) ### Single-Agent Parity -- [01_basic_assistant_agent.py](single_agent/01_basic_assistant_agent.py) — Minimal AutoGen `AssistantAgent` and AF `Agent` comparison. -- [02_assistant_agent_with_tool.py](single_agent/02_assistant_agent_with_tool.py) — Function tool integration in both SDKs. -- [03_assistant_agent_thread_and_stream.py](single_agent/03_assistant_agent_thread_and_stream.py) — Session management and streaming responses. +- [01_basic_agent.py](single_agent/01_basic_agent.py) — Minimal AutoGen `AssistantAgent` and AF `Agent` comparison. +- [02_agent_with_tool.py](single_agent/02_agent_with_tool.py) — Function tool integration in both SDKs. +- [03_agent_thread_and_stream.py](single_agent/03_agent_thread_and_stream.py) — Session management and streaming responses. - [04_agent_as_tool.py](single_agent/04_agent_as_tool.py) — Using agents as tools (hierarchical agent pattern) and streaming with tools. ### Multi-Agent Orchestration @@ -35,7 +35,7 @@ Each script is fully async and the `main()` routine runs both implementations ba From the repository root: ```bash -python samples/autogen-migration/single_agent/01_basic_assistant_agent.py +python samples/autogen-migration/single_agent/01_basic_agent.py ``` Every script accepts no CLI arguments and will first call the AutoGen implementation, followed by the AF version. Adjust the prompt or credentials inside the file as necessary before running. diff --git a/python/samples/autogen-migration/orchestrations/01_round_robin_group_chat.py b/python/samples/autogen-migration/orchestrations/01_round_robin_group_chat.py index ae78061260..ec2a2adb60 100644 --- a/python/samples/autogen-migration/orchestrations/01_round_robin_group_chat.py +++ b/python/samples/autogen-migration/orchestrations/01_round_robin_group_chat.py @@ -65,7 +65,7 @@ async def run_agent_framework() -> None: from agent_framework.openai import OpenAIChatClient from agent_framework.orchestrations import SequentialBuilder - client = OpenAIChatClient(model_id="gpt-4.1-mini") + client = OpenAIChatClient(model="gpt-4.1-mini") # Create specialized agents researcher = Agent( @@ -112,7 +112,7 @@ async def run_agent_framework_with_cycle() -> None: ) from agent_framework.openai import OpenAIChatClient - client = OpenAIChatClient(model_id="gpt-4.1-mini") + client = OpenAIChatClient(model="gpt-4.1-mini") # Create specialized agents researcher = Agent( diff --git a/python/samples/autogen-migration/orchestrations/04_magentic_one.py b/python/samples/autogen-migration/orchestrations/04_magentic_one.py index e10636b10f..9a7dec30ee 100644 --- a/python/samples/autogen-migration/orchestrations/04_magentic_one.py +++ b/python/samples/autogen-migration/orchestrations/04_magentic_one.py @@ -76,7 +76,7 @@ async def run_agent_framework() -> None: from agent_framework.openai import OpenAIChatClient from agent_framework.orchestrations import MagenticBuilder - client = OpenAIChatClient(model_id="gpt-4.1-mini") + client = OpenAIChatClient(model="gpt-4.1-mini") # Create specialized agents researcher = Agent( diff --git a/python/samples/autogen-migration/single_agent/01_basic_assistant_agent.py b/python/samples/autogen-migration/single_agent/01_basic_agent.py similarity index 100% rename from python/samples/autogen-migration/single_agent/01_basic_assistant_agent.py rename to python/samples/autogen-migration/single_agent/01_basic_agent.py diff --git a/python/samples/autogen-migration/single_agent/02_assistant_agent_with_tool.py b/python/samples/autogen-migration/single_agent/02_agent_with_tool.py similarity index 100% rename from python/samples/autogen-migration/single_agent/02_assistant_agent_with_tool.py rename to python/samples/autogen-migration/single_agent/02_agent_with_tool.py diff --git a/python/samples/autogen-migration/single_agent/03_assistant_agent_thread_and_stream.py b/python/samples/autogen-migration/single_agent/03_agent_thread_and_stream.py similarity index 100% rename from python/samples/autogen-migration/single_agent/03_assistant_agent_thread_and_stream.py rename to python/samples/autogen-migration/single_agent/03_agent_thread_and_stream.py diff --git a/python/samples/semantic-kernel-migration/chat_completion/02_chat_completion_with_tool.py b/python/samples/semantic-kernel-migration/chat_completion/02_chat_completion_with_tool.py index d84e560eb0..d5b1203518 100644 --- a/python/samples/semantic-kernel-migration/chat_completion/02_chat_completion_with_tool.py +++ b/python/samples/semantic-kernel-migration/chat_completion/02_chat_completion_with_tool.py @@ -23,7 +23,7 @@ load_dotenv() async def run_semantic_kernel() -> None: - from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread + from semantic_kernel.agents import ChatCompletionAgent from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.functions import kernel_function @@ -39,11 +39,7 @@ async def run_semantic_kernel() -> None: instructions="Answer menu questions accurately.", plugins=[SpecialsPlugin()], ) - thread = ChatHistoryAgentThread() - response = await agent.get_response( - messages="What soup can I order today?", - thread=thread, - ) + response = await agent.get_response("What soup can I order today?") print("[SK]", response.message.content) @@ -62,12 +58,7 @@ async def run_agent_framework() -> None: instructions="Answer menu questions accurately.", tools=[specials], ) - session = chat_agent.create_session() - reply = await chat_agent.run( - "What soup can I order today?", - session=session, - tool_choice="auto", - ) + reply = await chat_agent.run("What soup can I order today?") print("[AF]", reply.text) diff --git a/python/samples/semantic-kernel-migration/openai_responses/01_basic_responses_agent.py b/python/samples/semantic-kernel-migration/openai_responses/01_basic_responses_agent.py index 036d793c3e..d74487d1e8 100644 --- a/python/samples/semantic-kernel-migration/openai_responses/01_basic_responses_agent.py +++ b/python/samples/semantic-kernel-migration/openai_responses/01_basic_responses_agent.py @@ -22,10 +22,13 @@ async def run_semantic_kernel() -> None: from semantic_kernel.agents import OpenAIResponsesAgent from semantic_kernel.connectors.ai.open_ai import OpenAISettings + openai_settings = OpenAISettings() + assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings" + client = OpenAIResponsesAgent.create_client() # SK response agents wrap OpenAI's hosted Responses API. agent = OpenAIResponsesAgent( - ai_model=OpenAISettings().responses_model_id, + ai_model_id=openai_settings.responses_model_id, client=client, instructions="Answer in one concise sentence.", name="Expert", diff --git a/python/samples/semantic-kernel-migration/openai_responses/02_responses_agent_with_tool.py b/python/samples/semantic-kernel-migration/openai_responses/02_responses_agent_with_tool.py index 4145d74e31..01b783aff9 100644 --- a/python/samples/semantic-kernel-migration/openai_responses/02_responses_agent_with_tool.py +++ b/python/samples/semantic-kernel-migration/openai_responses/02_responses_agent_with_tool.py @@ -28,10 +28,13 @@ async def run_semantic_kernel() -> None: def add(self, a: float, b: float) -> float: return a + b + openai_settings = OpenAISettings() + assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings" + client = OpenAIResponsesAgent.create_client() # Plugins advertise callable tools to the Responses agent. agent = OpenAIResponsesAgent( - ai_model=OpenAISettings().responses_model_id, + ai_model_id=openai_settings.responses_model_id, client=client, instructions="Use the add tool when math is required.", name="MathExpert", diff --git a/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py b/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py index e1caec599c..cbfbf470a0 100644 --- a/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py +++ b/python/samples/semantic-kernel-migration/openai_responses/03_responses_agent_structured_output.py @@ -29,14 +29,17 @@ async def run_semantic_kernel() -> None: from semantic_kernel.agents import OpenAIResponsesAgent from semantic_kernel.connectors.ai.open_ai import OpenAISettings + openai_settings = OpenAISettings() + assert openai_settings.responses_model_id is not None, "Responses model ID must be set in OpenAISettings" + client = OpenAIResponsesAgent.create_client() # response_format requests schema-constrained output from the model. agent = OpenAIResponsesAgent( - ai_model=OpenAISettings().responses_model_id, + ai_model_id=openai_settings.responses_model_id, client=client, instructions="Return launch briefs as structured JSON.", name="ProductMarketer", - text=OpenAIResponsesAgent.configure_response_format(ReleaseBrief), + text=OpenAIResponsesAgent.configure_response_format(ReleaseBrief), # type: ignore ) response = await agent.get_response( "Draft a launch brief for the Contoso Note app.", diff --git a/python/samples/semantic-kernel-migration/orchestrations/concurrent_basic.py b/python/samples/semantic-kernel-migration/orchestrations/concurrent_basic.py index fb60da9f3d..11140aa875 100644 --- a/python/samples/semantic-kernel-migration/orchestrations/concurrent_basic.py +++ b/python/samples/semantic-kernel-migration/orchestrations/concurrent_basic.py @@ -55,7 +55,7 @@ def build_semantic_kernel_agents() -> list[ChatCompletionAgent]: async def run_semantic_kernel_example(prompt: str) -> Sequence[ChatMessageContent]: - concurrent_orchestration = ConcurrentOrchestration(members=build_semantic_kernel_agents()) + concurrent_orchestration = ConcurrentOrchestration(members=build_semantic_kernel_agents()) # type: ignore runtime = InProcessRuntime() runtime.start() @@ -91,12 +91,14 @@ def _print_semantic_kernel_outputs(outputs: Sequence[ChatMessageContent]) -> Non async def run_agent_framework_example(prompt: str) -> Sequence[list[Message]]: client = OpenAIChatCompletionClient(credential=AzureCliCredential()) - physics = Agent(client=client, + physics = Agent( + client=client, instructions=("You are an expert in physics. Answer questions from a physics perspective."), name="physics", ) - chemistry = Agent(client=client, + chemistry = Agent( + client=client, instructions=("You are an expert in chemistry. Answer questions from a chemistry perspective."), name="chemistry", ) diff --git a/python/samples/semantic-kernel-migration/orchestrations/group_chat.py b/python/samples/semantic-kernel-migration/orchestrations/group_chat.py index fa539a98b4..51252a1786 100644 --- a/python/samples/semantic-kernel-migration/orchestrations/group_chat.py +++ b/python/samples/semantic-kernel-migration/orchestrations/group_chat.py @@ -16,8 +16,8 @@ import sys from collections.abc import Sequence from typing import Any, cast -from agent_framework import Agent, Message -from agent_framework.foundry import FoundryChatClient +from agent_framework import Agent, AgentResponseUpdate, Message +from agent_framework.openai import OpenAIChatCompletionClient from agent_framework.orchestrations import GroupChatBuilder from azure.identity import AzureCliCredential from dotenv import load_dotenv @@ -82,9 +82,6 @@ def build_semantic_kernel_agents() -> list[ChatCompletionAgent]: class ChatCompletionGroupChatManager(GroupChatManager): """Group chat manager that delegates orchestration decisions to an Azure OpenAI deployment.""" - service: ChatCompletionClientBase - topic: str - termination_prompt: str = ( "You are coordinating a conversation about '{{$topic}}'. " "Decide if the discussion has produced a solid answer. " @@ -104,8 +101,11 @@ class ChatCompletionGroupChatManager(GroupChatManager): ) def __init__(self, *, topic: str, service: ChatCompletionClientBase, max_rounds: int | None = None) -> None: - super().__init__(topic=topic, service=service, max_rounds=max_rounds) + super().__init__(max_rounds=max_rounds) + self._round_robin_index = 0 + self._topic = topic + self._service = service async def _render_prompt(self, template: str, **kwargs: Any) -> str: prompt_template = KernelPromptTemplate(prompt_template_config=PromptTemplateConfig(template=template)) @@ -117,7 +117,7 @@ class ChatCompletionGroupChatManager(GroupChatManager): @override async def should_terminate(self, chat_history: ChatHistory) -> BooleanResult: - rendered_prompt = await self._render_prompt(self.termination_prompt, topic=self.topic) + rendered_prompt = await self._render_prompt(self.termination_prompt, topic=self._topic) chat_history.messages.insert( 0, ChatMessageContent(role=AuthorRole.SYSTEM, content=rendered_prompt), @@ -126,11 +126,11 @@ class ChatCompletionGroupChatManager(GroupChatManager): ChatMessageContent(role=AuthorRole.USER, content="Decide if the discussion is complete."), ) - response = await self.service.get_chat_message_content( + response = await self._service.get_chat_message_content( chat_history, settings=PromptExecutionSettings(response_format=BooleanResult), ) - return BooleanResult.model_validate_json(response.content) + return BooleanResult.model_validate_json(response.content) # type: ignore @override async def select_next_agent( @@ -140,7 +140,7 @@ class ChatCompletionGroupChatManager(GroupChatManager): ) -> StringResult: rendered_prompt = await self._render_prompt( self.selection_prompt, - topic=self.topic, + topic=self._topic, participants=", ".join(participant_descriptions.keys()), ) chat_history.messages.insert( @@ -151,18 +151,18 @@ class ChatCompletionGroupChatManager(GroupChatManager): ChatMessageContent(role=AuthorRole.USER, content="Pick the next participant to speak."), ) - response = await self.service.get_chat_message_content( + response = await self._service.get_chat_message_content( chat_history, settings=PromptExecutionSettings(response_format=StringResult), ) - result = StringResult.model_validate_json(response.content) + result = StringResult.model_validate_json(response.content) # type: ignore if result.result not in participant_descriptions: raise RuntimeError(f"Unknown participant selected: {result.result}") return result @override async def filter_results(self, chat_history: ChatHistory) -> MessageResult: - rendered_prompt = await self._render_prompt(self.summary_prompt, topic=self.topic) + rendered_prompt = await self._render_prompt(self.summary_prompt, topic=self._topic) chat_history.messages.insert( 0, ChatMessageContent(role=AuthorRole.SYSTEM, content=rendered_prompt), @@ -171,11 +171,11 @@ class ChatCompletionGroupChatManager(GroupChatManager): ChatMessageContent(role=AuthorRole.USER, content="Summarize the plan."), ) - response = await self.service.get_chat_message_content( + response = await self._service.get_chat_message_content( chat_history, settings=PromptExecutionSettings(response_format=StringResult), ) - string_result = StringResult.model_validate_json(response.content) + string_result = StringResult.model_validate_json(response.content) # type: ignore return MessageResult( result=ChatMessageContent(role=AuthorRole.ASSISTANT, content=string_result.result), reason=string_result.reason, @@ -197,7 +197,7 @@ async def sk_agent_response_callback(message: ChatMessageContent | Sequence[Chat async def run_semantic_kernel_example(task: str) -> str: credential = AzureCliCredential() orchestration = GroupChatOrchestration( - members=build_semantic_kernel_agents(), + members=build_semantic_kernel_agents(), # type: ignore manager=ChatCompletionGroupChatManager( topic=DISCUSSION_TOPIC, service=AzureChatCompletion(credential=credential), @@ -225,7 +225,7 @@ async def run_semantic_kernel_example(task: str) -> str: async def run_agent_framework_example(task: str) -> str: - credential = AzureCliCredential() + client = OpenAIChatCompletionClient(credential=AzureCliCredential()) researcher = Agent( name="Researcher", @@ -234,32 +234,42 @@ async def run_agent_framework_example(task: str) -> str: "Gather concise facts or considerations that help plan a community hackathon. " "Keep your responses factual and scannable." ), - client=FoundryChatClient(credential=credential), + client=client, ) planner = Agent( name="Planner", description="Turns the collected notes into a concrete action plan.", instructions=("Propose a structured action plan that accounts for logistics, roles, and timeline."), - client=FoundryChatClient(credential=credential), + client=client, ) workflow = GroupChatBuilder( participants=[researcher, planner], - orchestrator_agent=Agent(client=FoundryChatClient(credential=credential)), + orchestrator_agent=Agent(client=client), + max_rounds=8, + intermediate_outputs=True, ).build() - final_response = "" + output_messages: list[Message] = [] + last_message_id: str | None = None async for event in workflow.run(task, stream=True): if event.type == "output": - data = event.data - if isinstance(data, list) and len(data) > 0: - # Get the final message from the conversation - final_message = data[-1] - final_response = final_message.text or "" if isinstance(final_message, Message) else str(data) + if isinstance(event.data, AgentResponseUpdate): + if event.data.message_id != last_message_id: + last_message_id = event.data.message_id + print(f"{event.data.author_name}: {event.data.text}", end="") + else: + print(event.data.text, end="") else: - final_response = str(data) - return final_response + output_messages.extend(cast(list[Message], event.data)) + for message in output_messages: + print(f"[{message.author_name}] {message.text}") + + if output_messages: + return output_messages[-1].text + + return "" async def main() -> None: diff --git a/python/samples/semantic-kernel-migration/orchestrations/handoff.py b/python/samples/semantic-kernel-migration/orchestrations/handoff.py index 689de88bde..f506664c60 100644 --- a/python/samples/semantic-kernel-migration/orchestrations/handoff.py +++ b/python/samples/semantic-kernel-migration/orchestrations/handoff.py @@ -11,15 +11,14 @@ """Side-by-side handoff orchestrations for Semantic Kernel and Agent Framework.""" import asyncio -import sys -from collections.abc import AsyncIterable, Iterator, Sequence +from collections.abc import AsyncIterable, Callable, Iterator, Sequence from agent_framework import ( Agent, Message, WorkflowEvent, ) -from agent_framework.foundry import FoundryChatClient +from agent_framework.openai import OpenAIChatCompletionClient from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder from azure.identity import AzureCliCredential from dotenv import load_dotenv @@ -36,11 +35,6 @@ from semantic_kernel.contents import ( ) from semantic_kernel.functions import kernel_function -if sys.version_info >= (3, 12): - pass # pragma: no cover -else: - pass # pragma: no cover - # Load environment variables from .env file load_dotenv() @@ -149,7 +143,7 @@ def _sk_streaming_callback(message: StreamingChatMessageContent, is_final: bool) _sk_new_message = True -def _make_sk_human_responder(script: Iterator[str]) -> callable: +def _make_sk_human_responder(script: Iterator[str]) -> Callable[[], ChatMessageContent]: def _responder() -> ChatMessageContent: try: user_text = next(script) @@ -190,7 +184,7 @@ async def run_semantic_kernel_example(initial_task: str, scripted_responses: Seq ###################################################################### -def _create_af_agents(client: FoundryChatClient): +def _create_af_agents(client: OpenAIChatCompletionClient): triage = Agent( client=client, name="triage_agent", @@ -245,7 +239,7 @@ def _extract_final_conversation(events: list[WorkflowEvent]) -> list[Message]: async def run_agent_framework_example(initial_task: str, scripted_responses: Sequence[str]) -> str: - client = FoundryChatClient(credential=AzureCliCredential()) + client = OpenAIChatCompletionClient(credential=AzureCliCredential()) triage, refund, status, returns = _create_af_agents(client) workflow = ( @@ -281,7 +275,7 @@ async def run_agent_framework_example(initial_task: str, scripted_responses: Seq return "" # Render final transcript succinctly. - lines = [] + lines: list[str] = [] for message in conversation: text = message.text or "" if not text.strip(): diff --git a/python/samples/semantic-kernel-migration/orchestrations/magentic.py b/python/samples/semantic-kernel-migration/orchestrations/magentic.py index 314ce04271..b5360e2130 100644 --- a/python/samples/semantic-kernel-migration/orchestrations/magentic.py +++ b/python/samples/semantic-kernel-migration/orchestrations/magentic.py @@ -13,8 +13,9 @@ import asyncio from collections.abc import Sequence +from typing import cast -from agent_framework import Agent +from agent_framework import Agent, AgentResponseUpdate, Message from agent_framework.openai import OpenAIChatClient from agent_framework.orchestrations import MagenticBuilder from dotenv import load_dotenv @@ -46,21 +47,21 @@ PROMPT = ( ###################################################################### -async def build_semantic_kernel_agents() -> list: +async def build_semantic_kernel_agents() -> list[ChatCompletionAgent | OpenAIAssistantAgent]: research_agent = ChatCompletionAgent( name="ResearchAgent", description="A helpful assistant with access to web search. Ask it to perform web searches.", instructions=( "You are a Researcher. You find information without additional computation or quantitative analysis." ), - service=OpenAIChatCompletion(ai_model_id="gpt-4o-search-preview"), + service=OpenAIChatCompletion(ai_model_id="gpt-4o-mini-search-preview"), ) client = OpenAIAssistantAgent.create_client() code_interpreter_tool, code_interpreter_tool_resources = OpenAIAssistantAgent.configure_code_interpreter_tool() openai_settings = OpenAISettings() model_id = openai_settings.chat_model_id if openai_settings.chat_model_id else "gpt-5" - definition = await client.beta.assistants.create( + definition = await client.beta.assistants.create( # pyright: ignore[reportDeprecated] model=model_id, name="CoderAgent", description="A helpful assistant that writes and executes code to process and analyze data.", @@ -94,7 +95,7 @@ def sk_agent_response_callback( async def run_semantic_kernel_example(prompt: str) -> Sequence[ChatMessageContent]: agents = await build_semantic_kernel_agents() magentic_orchestration = MagenticOrchestration( - members=agents, + members=agents, # type: ignore manager=StandardMagenticManager(chat_completion_service=OpenAIChatCompletion()), agent_response_callback=sk_agent_response_callback, ) @@ -137,7 +138,7 @@ async def run_agent_framework_example(prompt: str) -> str | None: instructions=( "You are a Researcher. You find information without additional computation or quantitative analysis." ), - client=OpenAIChatClient(model="gpt-4o-search-preview"), + client=OpenAIChatClient(model="gpt-4o-mini-search-preview"), ) # Create code interpreter tool using static method @@ -160,22 +161,31 @@ async def run_agent_framework_example(prompt: str) -> str | None: client=OpenAIChatClient(), ) - workflow = MagenticBuilder(participants=[researcher, coder], manager_agent=manager_agent).build() + workflow = MagenticBuilder( + participants=[researcher, coder], + manager_agent=manager_agent, # type: ignore + intermediate_outputs=True, + ).build() - final_text: str | None = None + output_messages: list[Message] = [] + last_message_id: str | None = None async for event in workflow.run(prompt, stream=True): if event.type == "output": - data = event.data - if isinstance(data, str): - final_text = data - elif isinstance(data, list): - # Extract text from the last assistant message - for msg in reversed(data): - if hasattr(msg, "text") and msg.text: - final_text = msg.text - break + if isinstance(event.data, AgentResponseUpdate): + if event.data.message_id != last_message_id: + last_message_id = event.data.message_id + print(f"{event.data.author_name}: {event.data.text}", end="") + else: + print(event.data.text, end="") + else: + output_messages.extend(cast(list[Message], event.data)) + for message in output_messages: + print(f"[{message.author_name}] {message.text}") - return final_text + if output_messages: + return output_messages[-1].text + + return None def _print_agent_framework_output(result: str | None) -> None: