Merge branch 'main' into feature/hosted-dwf

This commit is contained in:
Evan Mattson
2026-04-29 09:46:47 +09:00
committed by GitHub
Unverified
75 changed files with 9812 additions and 495 deletions
@@ -535,6 +535,7 @@ class WorkflowAgent(BaseAgent):
raw_representations.append(output_event)
else:
data = output_event.data
if isinstance(data, AgentResponseUpdate):
# We cannot support AgentResponseUpdate in non-streaming mode. This is because the message
# sequence cannot be guaranteed when there are streaming updates in between non-streaming
@@ -635,16 +636,23 @@ class WorkflowAgent(BaseAgent):
A list of AgentResponseUpdate objects. Empty list if the event is not relevant.
"""
if event.type == "output":
# Convert workflow output to agent response updates.
# Handle different data types appropriately.
data = event.data
executor_id = event.executor_id
if isinstance(data, AgentResponseUpdate):
# Pass through AgentResponseUpdate directly (streaming from AgentExecutor)
if not data.author_name:
data.author_name = executor_id
return [data]
# Construct a fresh AgentResponseUpdate so we don't mutate a payload
# that AgentExecutor still holds a reference to in its `updates` list.
return [
AgentResponseUpdate(
contents=list(data.contents),
role=data.role,
author_name=data.author_name or executor_id,
response_id=data.response_id,
message_id=data.message_id,
created_at=data.created_at,
raw_representation=data.raw_representation,
)
]
if isinstance(data, AgentResponse):
# Convert each message in AgentResponse to an AgentResponseUpdate
updates: list[AgentResponseUpdate] = []
@@ -156,8 +156,9 @@ class AgentExecutor(Executor):
the agent run.
- "custom": use the provided context_filter function to determine which messages to include
as context for the agent run.
context_filter: An optional function for filtering conversation context when context_mode is set
to "custom".
context_filter: A function that takes the full conversation (list of Messages) as input and returns
a filtered list of Messages to be used as context for the agent run. This is required
if context_mode is set to "custom".
"""
# Prefer provided id; else use agent.name if present; else generate deterministic prefix
exec_id = id or resolve_agent_id(agent)
@@ -361,7 +361,7 @@ class WorkflowExecutor(Executor):
return any(is_instance_of(message.data, input_type) for input_type in self.workflow.input_types)
@handler
async def process_workflow(self, input_data: object, ctx: WorkflowContext[Any]) -> None:
async def process_workflow(self, input_data: object, ctx: WorkflowContext[Any, Any]) -> None:
"""Execute the sub-workflow with raw input data.
This handler starts a new sub-workflow execution. When the sub-workflow
@@ -428,7 +428,7 @@ class WorkflowExecutor(Executor):
async def handle_message_wrapped_request_response(
self,
response: SubWorkflowResponseMessage,
ctx: WorkflowContext[Any],
ctx: WorkflowContext[Any, Any],
) -> None:
"""Handle response from parent for a forwarded request.
@@ -4,6 +4,7 @@
This module lazily re-exports objects from:
- ``agent-framework-anthropic``
- ``agent-framework-azure-contentunderstanding``
- ``agent-framework-foundry``
- ``agent-framework-foundry-local``
"""
@@ -12,7 +13,12 @@ import importlib
from typing import Any
_IMPORTS: dict[str, tuple[str, str]] = {
"AnalysisSection": ("agent_framework_azure_contentunderstanding", "agent-framework-azure-contentunderstanding"),
"AnthropicFoundryClient": ("agent_framework_anthropic", "agent-framework-anthropic"),
"ContentUnderstandingContextProvider": ("agent_framework_azure_contentunderstanding", "agent-framework-azure-contentunderstanding"),
"DocumentStatus": ("agent_framework_azure_contentunderstanding", "agent-framework-azure-contentunderstanding"),
"FileSearchBackend": ("agent_framework_azure_contentunderstanding", "agent-framework-azure-contentunderstanding"),
"FileSearchConfig": ("agent_framework_azure_contentunderstanding", "agent-framework-azure-contentunderstanding"),
"FoundryAgent": ("agent_framework_foundry", "agent-framework-foundry"),
"FoundryAgentOptions": ("agent_framework_foundry", "agent-framework-foundry"),
"FoundryChatClient": ("agent_framework_foundry", "agent-framework-foundry"),
@@ -4,6 +4,13 @@
# Install the relevant packages for full type support.
from agent_framework_anthropic import AnthropicFoundryClient, RawAnthropicFoundryClient
from agent_framework_azure_contentunderstanding import (
AnalysisSection,
ContentUnderstandingContextProvider,
DocumentStatus,
FileSearchBackend,
FileSearchConfig,
)
from agent_framework_foundry import (
FoundryAgent,
FoundryChatClient,
@@ -31,7 +38,12 @@ from agent_framework_foundry_local import (
)
__all__ = [
"AnalysisSection",
"AnthropicFoundryClient",
"ContentUnderstandingContextProvider",
"DocumentStatus",
"FileSearchBackend",
"FileSearchConfig",
"FoundryAgent",
"FoundryChatClient",
"FoundryChatOptions",
@@ -232,16 +232,18 @@ async def test_groupchat_kwargs_flow_to_agents() -> None:
async def test_kwargs_stored_in_state() -> None:
"""Test that function_invocation_kwargs are stored in State with the correct key."""
from agent_framework import Executor, WorkflowContext, handler
from typing_extensions import Never
from agent_framework import AgentResponse, Executor, WorkflowContext, handler
stored_kwargs: dict[str, Any] | None = None
class _StateInspector(Executor):
@handler
async def inspect(self, msgs: list[Message], ctx: WorkflowContext[list[Message]]) -> None:
async def inspect(self, msgs: list[Message], ctx: WorkflowContext[Never, AgentResponse]) -> None:
nonlocal stored_kwargs
stored_kwargs = ctx.get_state(WORKFLOW_RUN_KWARGS_KEY)
await ctx.send_message(msgs)
await ctx.yield_output(AgentResponse(messages=msgs))
inspector = _StateInspector(id="inspector")
workflow = SequentialBuilder(participants=[inspector]).build()
@@ -256,16 +258,18 @@ async def test_kwargs_stored_in_state() -> None:
async def test_empty_kwargs_stored_as_empty_dict() -> None:
"""Test that empty kwargs are stored as empty dict in State."""
from agent_framework import Executor, WorkflowContext, handler
from typing_extensions import Never
from agent_framework import AgentResponse, Executor, WorkflowContext, handler
stored_kwargs: Any = "NOT_CHECKED"
class _StateChecker(Executor):
@handler
async def check(self, msgs: list[Message], ctx: WorkflowContext[list[Message]]) -> None:
async def check(self, msgs: list[Message], ctx: WorkflowContext[Never, AgentResponse]) -> None:
nonlocal stored_kwargs
stored_kwargs = ctx.get_state(WORKFLOW_RUN_KWARGS_KEY)
await ctx.send_message(msgs)
await ctx.yield_output(AgentResponse(messages=msgs))
checker = _StateChecker(id="checker")
workflow = SequentialBuilder(participants=[checker]).build()
@@ -695,7 +699,9 @@ async def test_subworkflow_kwargs_accessible_via_state() -> None:
Verifies that WORKFLOW_RUN_KWARGS_KEY is populated in the subworkflow's State
with kwargs from the parent workflow.
"""
from agent_framework import Executor, WorkflowContext, handler
from typing_extensions import Never
from agent_framework import AgentResponse, Executor, WorkflowContext, handler
from agent_framework._workflows._workflow_executor import WorkflowExecutor
captured_kwargs_from_state: list[dict[str, Any]] = []
@@ -704,10 +710,10 @@ async def test_subworkflow_kwargs_accessible_via_state() -> None:
"""Executor that reads kwargs from State for verification."""
@handler
async def read_kwargs(self, msgs: list[Message], ctx: WorkflowContext[list[Message]]) -> None:
async def read_kwargs(self, msgs: list[Message], ctx: WorkflowContext[Never, AgentResponse]) -> None:
kwargs_from_state = ctx.get_state(WORKFLOW_RUN_KWARGS_KEY)
captured_kwargs_from_state.append(kwargs_from_state or {})
await ctx.send_message(msgs)
await ctx.yield_output(AgentResponse(messages=msgs))
# Build inner workflow with State reader
state_reader = _StateReader(id="state_reader")