mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into copilot/port-magentic-orchestration-sample
This commit is contained in:
@@ -9,8 +9,9 @@ import logging
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Generator, Mapping, Sequence
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Generator, Sequence
|
||||
from contextlib import suppress
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from pathlib import Path
|
||||
from contextlib import AbstractAsyncContextManager, AsyncExitStack, suppress
|
||||
from typing import Protocol, cast
|
||||
@@ -1505,11 +1506,20 @@ def _convert_message_content(content: MessageContent) -> Content:
|
||||
# region Output Item Conversion
|
||||
|
||||
|
||||
def _arguments_to_str(arguments: str | Mapping[str, Any] | None) -> str:
|
||||
def _argument_json_default(value: Any) -> Any:
|
||||
if is_dataclass(value) and not isinstance(value, type):
|
||||
return asdict(value)
|
||||
to_dict = getattr(value, "to_dict", None)
|
||||
if callable(to_dict):
|
||||
return to_dict()
|
||||
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")
|
||||
|
||||
|
||||
def _arguments_to_str(arguments: Any | None) -> str:
|
||||
"""Convert arguments to a JSON string.
|
||||
|
||||
Args:
|
||||
arguments: The arguments to convert, can be a string, mapping, or None.
|
||||
arguments: The arguments to convert, can be a string, JSON-like object, or None.
|
||||
|
||||
Returns:
|
||||
The arguments as a JSON string.
|
||||
@@ -1518,7 +1528,7 @@ def _arguments_to_str(arguments: str | Mapping[str, Any] | None) -> str:
|
||||
return ""
|
||||
if isinstance(arguments, str):
|
||||
return arguments
|
||||
return json.dumps(arguments)
|
||||
return json.dumps(arguments, default=_argument_json_default)
|
||||
|
||||
|
||||
async def _to_outputs(
|
||||
|
||||
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import httpx
|
||||
@@ -405,6 +406,36 @@ class TestStreaming:
|
||||
assert len(args_done) == 1
|
||||
assert args_done[0]["data"]["arguments"] == '{"q": "hello"}'
|
||||
|
||||
async def test_function_call_streaming_serializes_dataclass_arguments(self) -> None:
|
||||
@dataclass
|
||||
class HandoffLikeRequest:
|
||||
agent_response: AgentResponse
|
||||
|
||||
request = HandoffLikeRequest(
|
||||
agent_response=AgentResponse(
|
||||
messages=[Message(role="assistant", contents=[Content.from_text("Need more details")])]
|
||||
)
|
||||
)
|
||||
agent = _make_agent(
|
||||
stream_updates=[
|
||||
AgentResponseUpdate(
|
||||
contents=[Content.from_function_call("call_1", "handoff_to_refund", arguments=request)],
|
||||
role="assistant",
|
||||
),
|
||||
]
|
||||
)
|
||||
server = _make_server(agent)
|
||||
resp = await _post(server, stream=True)
|
||||
|
||||
assert resp.status_code == 200
|
||||
events = _parse_sse_events(resp.text)
|
||||
args_done = [e for e in events if e["event"] == "response.function_call_arguments.done"]
|
||||
assert len(args_done) == 1
|
||||
|
||||
payload = json.loads(args_done[0]["data"]["arguments"])
|
||||
assert payload["agent_response"]["type"] == "agent_response"
|
||||
assert payload["agent_response"]["messages"][0]["contents"][0]["text"] == "Need more details"
|
||||
|
||||
async def test_alternating_text_and_function_call(self) -> None:
|
||||
agent = _make_agent(
|
||||
stream_updates=[
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import Agent, Message
|
||||
from agent_framework import Agent, AgentResponse, Message
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
@@ -17,9 +17,9 @@ load_dotenv()
|
||||
Sample: Sequential workflow (agent-focused API) with shared conversation context
|
||||
|
||||
Build a high-level sequential workflow using SequentialBuilder and two domain agents.
|
||||
The shared conversation (list[Message]) flows through each participant. Each agent
|
||||
appends its assistant message to the context. The workflow outputs the final conversation
|
||||
list when complete.
|
||||
The shared conversation flows through each participant. Each agent appends its
|
||||
assistant message to the context. The sample prints the original user message plus
|
||||
the visible outputs from both agents.
|
||||
|
||||
Note on internal adapters:
|
||||
- Sequential orchestration includes small adapter nodes for input normalization
|
||||
@@ -56,17 +56,19 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
# 2) Build sequential workflow: writer -> reviewer
|
||||
workflow = SequentialBuilder(participants=[writer, reviewer]).build()
|
||||
workflow = SequentialBuilder(participants=[writer, reviewer], output_from="all").build()
|
||||
|
||||
# 3) Run and collect outputs
|
||||
outputs: list[list[Message]] = []
|
||||
async for event in workflow.run("Write a tagline for a budget-friendly eBike.", stream=True):
|
||||
if event.type == "output":
|
||||
outputs.append(cast(list[Message], event.data))
|
||||
prompt = "Write a tagline for a budget-friendly eBike."
|
||||
result = await workflow.run(prompt)
|
||||
conversation = [Message(role="user", contents=[prompt])]
|
||||
for output in result.get_outputs():
|
||||
response = cast(AgentResponse, output)
|
||||
conversation.extend(response.messages)
|
||||
|
||||
if outputs:
|
||||
if conversation:
|
||||
print("===== Final Conversation =====")
|
||||
for i, msg in enumerate(outputs[-1], start=1):
|
||||
for i, msg in enumerate(conversation, start=1):
|
||||
name = msg.author_name or ("assistant" if msg.role == "assistant" else "user")
|
||||
print(f"{'-' * 60}\n{i:02d} [{name}]\n{msg.text}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user