Python: [BREAKING] Simplify API: ChatAgent -> Agent, ChatMessage -> Message (#3747)

* [BREAKING] Rename ChatAgent -> Agent, ChatMessage -> Message, ChatClientProtocol -> SupportsChatGetResponse

Simplify the public API by removing redundant 'Chat' prefix from core types:
- ChatAgent -> Agent
- RawChatAgent -> RawAgent
- ChatMessage -> Message
- ChatClientProtocol -> SupportsChatGetResponse

Also renamed internal WorkflowMessage (was Message in _runner_context) to avoid collision.

No backward compatibility aliases - this is a clean breaking change.

* [BREAKING] Rename Agent chat_client parameter to client

* Fix rebase issues: WorkflowMessage references and broken markdown links

* Fix formatting and lint issues from code quality checks

* Fix import ordering in workflow sample files

* fixed rebase

* Fix test failures: use WorkflowMessage and A2AMessage after ChatMessage→Message rename

- Replace Message(data=..., source_id=...) with WorkflowMessage(...) in workflow tests
- Fix isinstance check in A2A agent to use A2AMessage instead of Message
- Fix import in test_workflow_observability.py (Message→WorkflowMessage)

* Fix lint, fmt, and sample errors after ChatMessage→Message rename

- Auto-fix 70+ ruff lint issues across samples (ChatMessage→Message refs)
- Fix HostedVectorStoreContent→Content.from_hosted_vector_store in file search sample
- Fix _normalize_messages→normalize_messages in custom agent sample
- Fix context.terminate→raise MiddlewareTermination in middleware samples
- Fix with_update_hook→with_transform_hook in override middleware sample
- Add TOptions_co import back to custom_chat_client sample
- Add noqa for FastAPI File() default in chatkit sample
- Fix B023 loop variable capture in weather agent sample

* fix: update Agent constructor calls from chat_client to client in declaration-only tool tests

* fix: add register_cleanup to devui lazy-loading proxy and type stub

* fixed tests and updated new pieces

* fix agui typevar

* fix merge errors

* fix merge conflicts

* fiux merge

* Remove unused links

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
This commit is contained in:
Eduard van Valkenburg
2026-02-11 00:04:32 +01:00
committed by GitHub
Unverified
parent a4c9e43afb
commit 0521f5bed8
418 changed files with 5385 additions and 5389 deletions
@@ -7,7 +7,7 @@ from agent_framework import (
AgentExecutorRequest,
AgentExecutorResponse,
AgentResponseUpdate,
ChatMessage,
Message,
WorkflowBuilder,
WorkflowContext,
executor,
@@ -84,7 +84,7 @@ async def enrich_with_references(
f"{external_note}\n\n"
"Please update the prior assistant answer so it weaves this note into the guidance."
)
conversation.append(ChatMessage("user", [follow_up]))
conversation.append(Message("user", [follow_up]))
# Output a new AgentExecutorRequest for the next agent in the workflow.
# Agents in workflows handle this type and will generate a response based on the request.
@@ -6,14 +6,14 @@ from dataclasses import dataclass, field
from typing import Annotated
from agent_framework import (
Agent,
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
AgentResponse,
AgentResponseUpdate,
ChatAgent,
ChatMessage,
Executor,
Message,
WorkflowBuilder,
WorkflowContext,
WorkflowEvent,
@@ -90,7 +90,7 @@ class DraftFeedbackRequest:
prompt: str = ""
draft_text: str = ""
conversation: list[ChatMessage] = field(default_factory=list) # type: ignore[reportUnknownVariableType]
conversation: list[Message] = field(default_factory=list) # type: ignore[reportUnknownVariableType]
class Coordinator(Executor):
@@ -116,7 +116,7 @@ class Coordinator(Executor):
# Writer agent response; request human feedback.
# Preserve the full conversation so the final editor
# can see tool traces and the initial prompt.
conversation: list[ChatMessage]
conversation: list[Message]
if draft.full_conversation is not None:
conversation = list(draft.full_conversation)
else:
@@ -147,7 +147,7 @@ class Coordinator(Executor):
# Human approved the draft as-is; forward it unchanged.
await ctx.send_message(
AgentExecutorRequest(
messages=original_request.conversation + [ChatMessage("user", text="The draft is approved as-is.")],
messages=original_request.conversation + [Message("user", text="The draft is approved as-is.")],
should_respond=True,
),
target_id=self.final_editor_id,
@@ -155,20 +155,20 @@ class Coordinator(Executor):
return
# Human provided feedback; prompt the writer to revise.
conversation: list[ChatMessage] = list(original_request.conversation)
conversation: list[Message] = list(original_request.conversation)
instruction = (
"A human reviewer shared the following guidance:\n"
f"{note or 'No specific guidance provided.'}\n\n"
"Rewrite the draft from the previous assistant message into a polished final version. "
"Keep the response under 120 words and reflect any requested tone adjustments."
)
conversation.append(ChatMessage("user", text=instruction))
conversation.append(Message("user", text=instruction))
await ctx.send_message(
AgentExecutorRequest(messages=conversation, should_respond=True), target_id=self.writer_id
)
def create_writer_agent() -> ChatAgent:
def create_writer_agent() -> Agent:
"""Creates a writer agent with tools."""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
name="writer_agent",
@@ -182,7 +182,7 @@ def create_writer_agent() -> ChatAgent:
)
def create_final_editor_agent() -> ChatAgent:
def create_final_editor_agent() -> Agent:
"""Creates a final editor agent."""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
name="final_editor_agent",
@@ -38,9 +38,9 @@ def clear_and_redraw(buffers: dict[str, str], agent_order: list[str]) -> None:
async def main() -> None:
# 1) Create three domain agents using AzureOpenAIChatClient
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
client = AzureOpenAIChatClient(credential=AzureCliCredential())
researcher = chat_client.as_agent(
researcher = client.as_agent(
instructions=(
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
" opportunities, and risks."
@@ -48,7 +48,7 @@ async def main() -> None:
name="researcher",
)
marketer = chat_client.as_agent(
marketer = client.as_agent(
instructions=(
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
" aligned to the prompt."
@@ -56,7 +56,7 @@ async def main() -> None:
name="marketer",
)
legal = chat_client.as_agent(
legal = client.as_agent(
instructions=(
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
" based on the prompt."
@@ -3,9 +3,9 @@
import asyncio
from agent_framework import (
ChatAgent,
ChatMessage,
Agent,
Executor,
Message,
WorkflowBuilder,
WorkflowContext,
handler,
@@ -37,11 +37,11 @@ class Writer(Executor):
"""Custom executor that owns a domain specific agent responsible for generating content.
This class demonstrates:
- Attaching a ChatAgent to an Executor so it participates as a node in a workflow.
- Attaching a Agent to an Executor so it participates as a node in a workflow.
- Using a @handler method to accept a typed input and forward a typed output via ctx.send_message.
"""
agent: ChatAgent
agent: Agent
def __init__(self, id: str = "writer"):
# Create a domain specific agent using your configured AzureOpenAIChatClient.
@@ -54,12 +54,12 @@ class Writer(Executor):
super().__init__(id=id)
@handler
async def handle(self, message: ChatMessage, ctx: WorkflowContext[list[ChatMessage], str]) -> None:
async def handle(self, message: Message, ctx: WorkflowContext[list[Message], str]) -> None:
"""Generate content using the agent and forward the updated conversation.
Contract for this handler:
- message is the inbound user ChatMessage.
- ctx is a WorkflowContext that expects a list[ChatMessage] to be sent downstream.
- message is the inbound user Message.
- ctx is a WorkflowContext that expects a list[Message] to be sent downstream.
Pattern shown here:
1) Seed the conversation with the inbound message.
@@ -67,7 +67,7 @@ class Writer(Executor):
3) Forward the cumulative messages to the next executor with ctx.send_message.
"""
# Start the conversation with the incoming user message.
messages: list[ChatMessage] = [message]
messages: list[Message] = [message]
# Run the agent and extend the conversation with the agent's messages.
response = await self.agent.run(messages)
messages.extend(response.messages)
@@ -83,7 +83,7 @@ class Reviewer(Executor):
- Yielding the final text outcome to complete the workflow.
"""
agent: ChatAgent
agent: Agent
def __init__(self, id: str = "reviewer"):
# Create a domain specific agent that evaluates and refines content.
@@ -95,7 +95,7 @@ class Reviewer(Executor):
super().__init__(id=id)
@handler
async def handle(self, messages: list[ChatMessage], ctx: WorkflowContext[list[ChatMessage], str]) -> None:
async def handle(self, messages: list[Message], ctx: WorkflowContext[list[Message], str]) -> None:
"""Review the full conversation transcript and complete with a final string.
This node consumes all messages so far. It uses its agent to produce the final text,
@@ -118,7 +118,7 @@ async def main():
# Run the workflow with the user's initial message.
# For foundational clarity, use run (non streaming) and print the workflow output.
events = await workflow.run(
ChatMessage("user", ["Create a slogan for a new electric SUV that is affordable and fun to drive."])
Message("user", ["Create a slogan for a new electric SUV that is affordable and fun to drive."])
)
# The terminal node yields output; print its contents.
outputs = events.get_outputs()
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
from agent_framework.orchestrations import GroupChatBuilder
@@ -19,18 +19,18 @@ Prerequisites:
async def main() -> None:
researcher = ChatAgent(
researcher = Agent(
name="Researcher",
description="Collects relevant background information.",
instructions="Gather concise facts that help a teammate answer the question.",
chat_client=OpenAIChatClient(model_id="gpt-4o-mini"),
client=OpenAIChatClient(model_id="gpt-4o-mini"),
)
writer = ChatAgent(
writer = Agent(
name="Writer",
description="Synthesizes a polished answer using the gathered notes.",
instructions="Compose clear and structured answers using any notes provided.",
chat_client=OpenAIResponsesClient(),
client=OpenAIResponsesClient(),
)
# intermediate_outputs=True: Enable intermediate outputs to observe the conversation as it unfolds
@@ -4,10 +4,10 @@ import asyncio
from typing import Annotated
from agent_framework import (
Agent,
AgentResponse,
ChatAgent,
ChatMessage,
Content,
Message,
WorkflowAgent,
tool,
)
@@ -57,17 +57,17 @@ 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(chat_client: AzureOpenAIChatClient) -> tuple[ChatAgent, ChatAgent, ChatAgent, ChatAgent]:
def create_agents(client: AzureOpenAIChatClient) -> tuple[Agent, Agent, Agent, Agent]:
"""Create and configure the triage and specialist agents.
Args:
chat_client: The AzureOpenAIChatClient to use for creating agents.
client: The AzureOpenAIChatClient to use for creating agents.
Returns:
Tuple of (triage_agent, refund_agent, order_agent, return_agent)
"""
# Triage agent: Acts as the frontline dispatcher
triage_agent = chat_client.as_agent(
triage_agent = client.as_agent(
instructions=(
"You are frontline support triage. Route customer issues to the appropriate specialist agents "
"based on the problem described."
@@ -76,7 +76,7 @@ def create_agents(chat_client: AzureOpenAIChatClient) -> tuple[ChatAgent, ChatAg
)
# Refund specialist: Handles refund requests
refund_agent = chat_client.as_agent(
refund_agent = client.as_agent(
instructions="You process refund requests.",
name="refund_agent",
# In a real application, an agent can have multiple tools; here we keep it simple
@@ -84,7 +84,7 @@ def create_agents(chat_client: AzureOpenAIChatClient) -> tuple[ChatAgent, ChatAg
)
# Order/shipping specialist: Resolves delivery issues
order_agent = chat_client.as_agent(
order_agent = client.as_agent(
instructions="You handle order and shipping inquiries.",
name="order_agent",
# In a real application, an agent can have multiple tools; here we keep it simple
@@ -92,7 +92,7 @@ def create_agents(chat_client: AzureOpenAIChatClient) -> tuple[ChatAgent, ChatAg
)
# Return specialist: Handles return requests
return_agent = chat_client.as_agent(
return_agent = client.as_agent(
instructions="You manage product return requests.",
name="return_agent",
# In a real application, an agent can have multiple tools; here we keep it simple
@@ -147,10 +147,10 @@ async def main() -> None:
replace the scripted_responses with actual user input collection.
"""
# Initialize the Azure OpenAI chat client
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
client = AzureOpenAIChatClient(credential=AzureCliCredential())
# Create all agents: triage + specialists
triage, refund, order, support = create_agents(chat_client)
triage, refund, order, support = create_agents(client)
# Build the handoff workflow
# - participants: All agents that can participate in the workflow
@@ -213,7 +213,7 @@ async def main() -> None:
function_results = [
Content.from_function_result(call_id=req_id, result=response) for req_id, response in responses.items()
]
response = await agent.run(ChatMessage("tool", function_results))
response = await agent.run(Message("tool", function_results))
pending_requests = handle_response_and_requests(response)
@@ -3,7 +3,7 @@
import asyncio
from agent_framework import (
ChatAgent,
Agent,
HostedCodeInterpreterTool,
)
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
@@ -22,30 +22,30 @@ Prerequisites:
async def main() -> None:
researcher_agent = ChatAgent(
researcher_agent = Agent(
name="ResearcherAgent",
description="Specialist in research and information gathering",
instructions=(
"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.
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
)
coder_agent = ChatAgent(
coder_agent = Agent(
name="CoderAgent",
description="A helpful assistant that writes and executes code to process and analyze data.",
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
chat_client=OpenAIResponsesClient(),
client=OpenAIResponsesClient(),
tools=HostedCodeInterpreterTool(),
)
# Create a manager agent for orchestration
manager_agent = ChatAgent(
manager_agent = Agent(
name="MagenticManager",
description="Orchestrator that coordinates the research and coding workflow",
instructions="You coordinate a team to complete complex tasks efficiently.",
chat_client=OpenAIChatClient(),
client=OpenAIChatClient(),
)
print("\nBuilding Magentic Workflow...")
@@ -27,14 +27,14 @@ Prerequisites:
async def main() -> None:
# 1) Create agents
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
client = AzureOpenAIChatClient(credential=AzureCliCredential())
writer = chat_client.as_agent(
writer = client.as_agent(
instructions=("You are a concise copywriter. Provide a single, punchy marketing sentence based on the prompt."),
name="writer",
)
reviewer = chat_client.as_agent(
reviewer = client.as_agent(
instructions=("You are a thoughtful reviewer. Give brief feedback on the previous assistant message."),
name="reviewer",
)
@@ -16,9 +16,9 @@ if str(_SAMPLES_ROOT) not in sys.path:
sys.path.insert(0, str(_SAMPLES_ROOT))
from agent_framework import ( # noqa: E402
ChatMessage,
Content,
Executor,
Message,
WorkflowAgent,
WorkflowBuilder,
WorkflowContext,
@@ -159,7 +159,7 @@ async def main() -> None:
result=human_response,
)
# Send the human review result back to the agent.
response = await agent.run(ChatMessage("tool", [human_review_function_result]))
response = await agent.run(Message("tool", [human_review_function_result]))
print(f"📤 Agent Response: {response.messages[-1].text}")
print("=" * 50)
@@ -80,10 +80,10 @@ async def main() -> None:
print("=" * 70)
# Create chat client
chat_client = OpenAIChatClient()
client = OpenAIChatClient()
# Create agent with tools that use kwargs
agent = chat_client.as_agent(
agent = client.as_agent(
name="assistant",
instructions=(
"You are a helpful assistant. Use the available tools to help users. "
@@ -6,9 +6,9 @@ from uuid import uuid4
from agent_framework import (
AgentResponse,
ChatClientProtocol,
ChatMessage,
Executor,
Message,
SupportsChatGetResponse,
WorkflowBuilder,
WorkflowContext,
handler,
@@ -44,8 +44,8 @@ class ReviewRequest:
"""Structured request passed from Worker to Reviewer for evaluation."""
request_id: str
user_messages: list[ChatMessage]
agent_messages: list[ChatMessage]
user_messages: list[Message]
agent_messages: list[Message]
@dataclass
@@ -60,9 +60,9 @@ class ReviewResponse:
class Reviewer(Executor):
"""Executor that reviews agent responses and provides structured feedback."""
def __init__(self, id: str, chat_client: ChatClientProtocol) -> None:
def __init__(self, id: str, client: SupportsChatGetResponse) -> None:
super().__init__(id=id)
self._chat_client = chat_client
self._chat_client = client
@handler
async def review(self, request: ReviewRequest, ctx: WorkflowContext[ReviewResponse]) -> None:
@@ -75,7 +75,7 @@ class Reviewer(Executor):
# Construct review instructions and context.
messages = [
ChatMessage(
Message(
role="system",
text=(
"You are a reviewer for an AI agent. Provide feedback on the "
@@ -93,7 +93,7 @@ class Reviewer(Executor):
messages.extend(request.agent_messages)
# Add explicit review instruction.
messages.append(ChatMessage("user", ["Please review the agent's responses."]))
messages.append(Message("user", ["Please review the agent's responses."]))
print("Reviewer: Sending review request to LLM...")
response = await self._chat_client.get_response(messages=messages, options={"response_format": _Response})
@@ -112,17 +112,17 @@ class Reviewer(Executor):
class Worker(Executor):
"""Executor that generates responses and incorporates feedback when necessary."""
def __init__(self, id: str, chat_client: ChatClientProtocol) -> None:
def __init__(self, id: str, client: SupportsChatGetResponse) -> None:
super().__init__(id=id)
self._chat_client = chat_client
self._pending_requests: dict[str, tuple[ReviewRequest, list[ChatMessage]]] = {}
self._chat_client = client
self._pending_requests: dict[str, tuple[ReviewRequest, list[Message]]] = {}
@handler
async def handle_user_messages(self, user_messages: list[ChatMessage], ctx: WorkflowContext[ReviewRequest]) -> None:
async def handle_user_messages(self, user_messages: list[Message], ctx: WorkflowContext[ReviewRequest]) -> None:
print("Worker: Received user messages, generating response...")
# Initialize chat with system prompt.
messages = [ChatMessage("system", ["You are a helpful assistant."])]
messages = [Message("system", ["You are a helpful assistant."])]
messages.extend(user_messages)
print("Worker: Calling LLM to generate response...")
@@ -161,8 +161,8 @@ class Worker(Executor):
print("Worker: Regenerating response with feedback...")
# Incorporate review feedback.
messages.append(ChatMessage("system", [review.feedback]))
messages.append(ChatMessage("system", ["Please incorporate the feedback and regenerate the response."]))
messages.append(Message("system", [review.feedback]))
messages.append(Message("system", ["Please incorporate the feedback and regenerate the response."]))
messages.extend(request.user_messages)
# Retry with updated prompt.
@@ -37,9 +37,9 @@ Prerequisites:
async def main() -> None:
# Create a chat client
chat_client = OpenAIChatClient()
client = OpenAIChatClient()
assistant = chat_client.as_agent(
assistant = client.as_agent(
name="assistant",
instructions=(
"You are a helpful assistant. Answer questions based on the conversation "
@@ -47,7 +47,7 @@ async def main() -> None:
),
)
summarizer = chat_client.as_agent(
summarizer = client.as_agent(
name="summarizer",
instructions=(
"You are a summarizer. After the assistant responds, provide a brief "
@@ -119,9 +119,9 @@ async def demonstrate_thread_serialization() -> None:
This shows how conversation history can be persisted and restored,
enabling long-running conversational workflows.
"""
chat_client = OpenAIChatClient()
client = OpenAIChatClient()
memory_assistant = chat_client.as_agent(
memory_assistant = client.as_agent(
name="memory_assistant",
instructions="You are a helpful assistant with good memory. Remember details from our conversation.",
)