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
@@ -15,14 +15,14 @@ This folder contains examples demonstrating different ways to create and use age
| [`openai_assistants_with_function_tools.py`](openai_assistants_with_function_tools.py) | Function tools with `OpenAIAssistantProvider` at both agent-level and query-level. |
| [`openai_assistants_with_response_format.py`](openai_assistants_with_response_format.py) | Structured outputs with `OpenAIAssistantProvider` using Pydantic models. |
| [`openai_assistants_with_thread.py`](openai_assistants_with_thread.py) | Thread management with `OpenAIAssistantProvider` for conversation context persistence. |
| [`openai_chat_client_basic.py`](openai_chat_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `OpenAIChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with OpenAI models. |
| [`openai_chat_client_basic.py`](openai_chat_client_basic.py) | The simplest way to create an agent using `Agent` with `OpenAIChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with OpenAI models. |
| [`openai_chat_client_with_explicit_settings.py`](openai_chat_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific chat client, configuring settings explicitly including API key and model ID. |
| [`openai_chat_client_with_function_tools.py`](openai_chat_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
| [`openai_chat_client_with_local_mcp.py`](openai_chat_client_with_local_mcp.py) | Shows how to integrate OpenAI agents with local Model Context Protocol (MCP) servers for enhanced functionality and tool integration. |
| [`openai_chat_client_with_thread.py`](openai_chat_client_with_thread.py) | Demonstrates thread management with OpenAI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
| [`openai_chat_client_with_web_search.py`](openai_chat_client_with_web_search.py) | Shows how to use web search capabilities with OpenAI agents to retrieve and use information from the internet in responses. |
| [`openai_chat_client_with_runtime_json_schema.py`](openai_chat_client_with_runtime_json_schema.py) | Shows how to supply a runtime JSON Schema via `additional_chat_options` for structured output without defining a Pydantic model. |
| [`openai_responses_client_basic.py`](openai_responses_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `OpenAIResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with OpenAI models. |
| [`openai_responses_client_basic.py`](openai_responses_client_basic.py) | The simplest way to create an agent using `Agent` with `OpenAIResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with OpenAI models. |
| [`openai_responses_client_image_analysis.py`](openai_responses_client_image_analysis.py) | Demonstrates how to use vision capabilities with agents to analyze images. |
| [`openai_responses_client_image_generation.py`](openai_responses_client_image_generation.py) | Demonstrates how to use image generation capabilities with OpenAI agents to create images based on text descriptions. Requires PIL (Pillow) for image display. |
| [`openai_responses_client_reasoning.py`](openai_responses_client_reasoning.py) | Demonstrates how to use reasoning capabilities with OpenAI agents, showing how the agent can provide detailed reasoning for its responses. |
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
from random import randint
from typing import Annotated
from agent_framework import ChatAgent, tool
from agent_framework import Agent, tool
from agent_framework.openai import OpenAIChatClient
from pydantic import Field
@@ -40,8 +40,8 @@ async def tools_on_agent_level() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful assistant that can provide weather and time information.",
tools=[get_weather, get_time], # Tools defined at agent creation
)
@@ -70,8 +70,8 @@ async def tools_on_run_level() -> None:
print("=== Tools Passed to Run Method ===")
# Agent created without tools
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful assistant.",
# No tools defined here
)
@@ -100,8 +100,8 @@ async def mixed_tools_example() -> None:
print("=== Mixed Tools Example (Agent + Run Method) ===")
# Agent created with some base tools
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a comprehensive assistant that can help with various information requests.",
tools=[get_weather], # Base tool available for all queries
)
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, MCPStreamableHTTPTool
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.openai import OpenAIChatClient
"""
@@ -29,8 +29,8 @@ async def mcp_tools_on_run_level() -> None:
name="Microsoft Learn MCP",
url="https://learn.microsoft.com/api/mcp",
) as mcp_server,
ChatAgent(
chat_client=OpenAIChatClient(),
Agent(
client=OpenAIChatClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
) as agent,
@@ -4,7 +4,7 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import AgentThread, ChatAgent, ChatMessageStore, tool
from agent_framework import Agent, AgentThread, ChatMessageStore, tool
from agent_framework.openai import OpenAIChatClient
from pydantic import Field
@@ -30,8 +30,8 @@ async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation (service-managed thread)."""
print("=== Automatic Thread Creation Example ===")
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -55,8 +55,8 @@ async def example_with_thread_persistence() -> None:
print("=== Thread Persistence Example ===")
print("Using the same thread across multiple conversations to maintain context.\n")
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -88,8 +88,8 @@ async def example_with_existing_thread_messages() -> None:
"""Example showing how to work with existing thread messages for OpenAI."""
print("=== Existing Thread Messages Example ===")
agent = ChatAgent(
chat_client=OpenAIChatClient(),
agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -110,8 +110,8 @@ async def example_with_existing_thread_messages() -> None:
print("\n--- Continuing with the same thread in a new agent instance ---")
# Create a new agent instance but use the existing thread with its message history
new_agent = ChatAgent(
chat_client=OpenAIChatClient(),
new_agent = Agent(
client=OpenAIChatClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, HostedWebSearchTool
from agent_framework import Agent, HostedWebSearchTool
from agent_framework.openai import OpenAIChatClient
"""
@@ -22,8 +22,8 @@ async def main() -> None:
}
}
agent = ChatAgent(
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
agent = Agent(
client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
instructions="You are a helpful assistant that can search the web for current information.",
tools=[HostedWebSearchTool(additional_properties=additional_properties)],
)
@@ -6,11 +6,12 @@ from random import randint
from typing import Annotated
from agent_framework import (
ChatAgent,
Agent,
ChatContext,
ChatMessage,
ChatResponse,
Message,
MiddlewareTermination,
Role,
chat_middleware,
tool,
)
@@ -46,8 +47,8 @@ async def security_and_override_middleware(
# Override the response instead of calling AI
context.result = ChatResponse(
messages=[
ChatMessage(
role="assistant",
Message(
role=Role.ASSISTANT,
text="I cannot process requests containing sensitive information. "
"Please rephrase your question without including passwords, secrets, or other "
"sensitive data.",
@@ -55,8 +56,8 @@ async def security_and_override_middleware(
]
)
# Set terminate flag to stop execution
raise MiddlewareTermination
# Terminate middleware execution with the blocked response
raise MiddlewareTermination(result=context.result)
# Continue to next middleware or AI execution
await call_next(context)
@@ -79,8 +80,8 @@ async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -95,8 +96,8 @@ async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(
agent = Agent(
client=OpenAIResponsesClient(
middleware=[security_and_override_middleware],
),
instructions="You are a helpful weather agent.",
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatMessage, Content
from agent_framework import Content, Message
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -23,7 +23,7 @@ async def main():
)
# 2. Create a simple message with both text and image content
user_message = ChatMessage(
user_message = Message(
role="user",
contents=[
Content.from_text(text="What do you see in this image?"),
@@ -3,7 +3,7 @@
import asyncio
from agent_framework import (
ChatAgent,
Agent,
HostedCodeInterpreterTool,
)
from agent_framework.openai import OpenAIResponsesClient
@@ -20,8 +20,8 @@ async def main() -> None:
"""Example showing how to use the HostedCodeInterpreterTool with OpenAI Responses."""
print("=== OpenAI Responses Agent with Code Interpreter Example ===")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
tools=HostedCodeInterpreterTool(),
)
@@ -4,7 +4,7 @@ import asyncio
import os
import tempfile
from agent_framework import ChatAgent, HostedCodeInterpreterTool
from agent_framework import Agent, HostedCodeInterpreterTool
from agent_framework.openai import OpenAIResponsesClient
from openai import AsyncOpenAI
@@ -66,8 +66,8 @@ async def main() -> None:
temp_file_path, file_id = await create_sample_file_and_upload(openai_client)
# Create agent using OpenAI Responses client
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful assistant that can analyze data files using Python code.",
tools=HostedCodeInterpreterTool(inputs=[{"file_id": file_id}]),
)
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, Content, HostedFileSearchTool
from agent_framework import Agent, Content, HostedFileSearchTool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -47,8 +47,8 @@ async def main() -> None:
print(f"User: {message}")
file_id, vector_store = await create_vector_store(client)
agent = ChatAgent(
chat_client=client,
agent = Agent(
client=client,
instructions="You are a helpful assistant that can search through files to find information.",
tools=[HostedFileSearchTool(inputs=vector_store)],
)
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
from random import randint
from typing import Annotated
from agent_framework import ChatAgent, tool
from agent_framework import Agent, tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
@@ -40,8 +40,8 @@ async def tools_on_agent_level() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful assistant that can provide weather and time information.",
tools=[get_weather, get_time], # Tools defined at agent creation
)
@@ -70,8 +70,8 @@ async def tools_on_run_level() -> None:
print("=== Tools Passed to Run Method ===")
# Agent created without tools
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful assistant.",
# No tools defined here
)
@@ -100,8 +100,8 @@ async def mixed_tools_example() -> None:
print("=== Mixed Tools Example (Agent + Run Method) ===")
# Agent created with some base tools
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a comprehensive assistant that can help with various information requests.",
tools=[get_weather], # Base tool available for all queries
)
@@ -3,7 +3,7 @@
import asyncio
from typing import TYPE_CHECKING, Any
from agent_framework import ChatAgent, HostedMCPTool
from agent_framework import Agent, HostedMCPTool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun"):
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
from agent_framework import ChatMessage
from agent_framework import Message
result = await agent.run(query)
while len(result.user_input_requests) > 0:
@@ -29,10 +29,10 @@ async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun")
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
f" with arguments: {user_input_needed.function_call.arguments}"
)
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
new_inputs.append(Message(role="assistant", contents=[user_input_needed]))
user_approval = input("Approve function call? (y/n): ")
new_inputs.append(
ChatMessage(role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
Message(role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
)
result = await agent.run(new_inputs)
@@ -41,7 +41,7 @@ async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun")
async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", thread: "AgentThread"):
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
from agent_framework import ChatMessage
from agent_framework import Message
result = await agent.run(query, thread=thread, store=True)
while len(result.user_input_requests) > 0:
@@ -53,7 +53,7 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
)
user_approval = input("Approve function call? (y/n): ")
new_input.append(
ChatMessage(
Message(
role="user",
contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")],
)
@@ -64,13 +64,13 @@ async def handle_approvals_with_thread(query: str, agent: "SupportsAgentRun", th
async def handle_approvals_with_thread_streaming(query: str, agent: "SupportsAgentRun", thread: "AgentThread"):
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
from agent_framework import ChatMessage
from agent_framework import Message
new_input: list[ChatMessage] = []
new_input: list[Message] = []
new_input_added = True
while new_input_added:
new_input_added = False
new_input.append(ChatMessage(role="user", text=query))
new_input.append(Message(role="user", text=query))
async for update in agent.run(new_input, thread=thread, stream=True, options={"store": True}):
if update.user_input_requests:
for user_input_needed in update.user_input_requests:
@@ -80,7 +80,7 @@ async def handle_approvals_with_thread_streaming(query: str, agent: "SupportsAge
)
user_approval = input("Approve function call? (y/n): ")
new_input.append(
ChatMessage(
Message(
role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")]
)
)
@@ -95,8 +95,8 @@ async def run_hosted_mcp_without_thread_and_specific_approval() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=HostedMCPTool(
@@ -126,8 +126,8 @@ async def run_hosted_mcp_without_approval() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=HostedMCPTool(
@@ -158,8 +158,8 @@ async def run_hosted_mcp_with_thread() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=HostedMCPTool(
@@ -189,8 +189,8 @@ async def run_hosted_mcp_with_thread_streaming() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=HostedMCPTool(
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, MCPStreamableHTTPTool
from agent_framework import Agent, MCPStreamableHTTPTool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -22,8 +22,8 @@ async def streaming_with_mcp(show_raw_stream: bool = False) -> None:
print("=== Tools Defined on Agent Level ===")
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=MCPStreamableHTTPTool( # Tools defined at agent creation
@@ -60,8 +60,8 @@ async def run_with_mcp() -> None:
# Tools are provided when creating the agent
# The agent can use these tools for any query during its lifetime
async with ChatAgent(
chat_client=OpenAIResponsesClient(),
async with Agent(
client=OpenAIResponsesClient(),
name="DocsAgent",
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
tools=MCPStreamableHTTPTool( # Tools defined at agent creation
@@ -4,7 +4,7 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import AgentThread, ChatAgent, tool
from agent_framework import Agent, AgentThread, tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
@@ -30,8 +30,8 @@ async def example_with_automatic_thread_creation() -> None:
"""Example showing automatic thread creation."""
print("=== Automatic Thread Creation Example ===")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -57,8 +57,8 @@ async def example_with_thread_persistence_in_memory() -> None:
"""
print("=== Thread Persistence Example (In-Memory) ===")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -96,8 +96,8 @@ async def example_with_existing_thread_id() -> None:
# First, create a conversation and capture the thread ID
existing_thread_id = None
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -117,8 +117,8 @@ async def example_with_existing_thread_id() -> None:
if existing_thread_id:
print("\n--- Continuing with the same thread ID in a new agent instance ---")
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, HostedWebSearchTool
from agent_framework import Agent, HostedWebSearchTool
from agent_framework.openai import OpenAIResponsesClient
"""
@@ -22,8 +22,8 @@ async def main() -> None:
}
}
agent = ChatAgent(
chat_client=OpenAIResponsesClient(),
agent = Agent(
client=OpenAIResponsesClient(),
instructions="You are a helpful assistant that can search the web for current information.",
tools=[HostedWebSearchTool(additional_properties=additional_properties)],
)