mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
a4c9e43afb
commit
0521f5bed8
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""Background Responses Sample.
|
||||
@@ -22,10 +22,10 @@ Prerequisites:
|
||||
|
||||
|
||||
# 1. Create the agent with an OpenAI Responses client.
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="researcher",
|
||||
instructions="You are a helpful research assistant. Be concise.",
|
||||
chat_client=OpenAIResponsesClient(model_id="o3"),
|
||||
client=OpenAIResponsesClient(model_id="o3"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -94,9 +94,9 @@ final = await response_stream.get_final_response() # Get the aggregated result
|
||||
|
||||
=== Chaining with .map() and .with_finalizer() ===
|
||||
|
||||
When building a ChatAgent on top of a ChatClient, we face a challenge:
|
||||
When building a Agent on top of a ChatClient, we face a challenge:
|
||||
- The ChatClient returns a ResponseStream[ChatResponseUpdate, ChatResponse]
|
||||
- The ChatAgent needs to return a ResponseStream[AgentResponseUpdate, AgentResponse]
|
||||
- The Agent needs to return a ResponseStream[AgentResponseUpdate, AgentResponse]
|
||||
- We can't iterate the ChatClient's stream twice!
|
||||
|
||||
The `.map()` and `.with_finalizer()` methods solve this by creating new ResponseStreams that:
|
||||
@@ -123,8 +123,8 @@ provider notifications, telemetry, thread updates) are still executed even when
|
||||
stream is wrapped/mapped.
|
||||
|
||||
```python
|
||||
# ChatAgent does something like this internally:
|
||||
chat_stream = chat_client.get_response(messages, stream=True)
|
||||
# Agent does something like this internally:
|
||||
chat_stream = client.get_response(messages, stream=True)
|
||||
agent_stream = (
|
||||
chat_stream
|
||||
.map(_to_agent_update, _to_agent_response)
|
||||
@@ -135,7 +135,7 @@ agent_stream = (
|
||||
This ensures:
|
||||
- The underlying ChatClient stream is only consumed once
|
||||
- The agent can add its own transform hooks, result hooks, and cleanup logic
|
||||
- Each layer (ChatClient, ChatAgent, middleware) can add independent behavior
|
||||
- Each layer (ChatClient, Agent, middleware) can add independent behavior
|
||||
- Inner stream post-processing (like context provider notification) still runs
|
||||
- Types flow naturally through the chain
|
||||
"""
|
||||
@@ -281,7 +281,7 @@ async def main() -> None:
|
||||
# Simulate what ChatClient returns
|
||||
inner_stream = ResponseStream(generate_updates(), finalizer=combine_updates)
|
||||
|
||||
# Simulate what ChatAgent does: wrap the inner stream
|
||||
# Simulate what Agent does: wrap the inner stream
|
||||
def to_agent_format(update: ChatResponseUpdate) -> ChatResponseUpdate:
|
||||
"""Map ChatResponseUpdate to agent format (simulated transformation)."""
|
||||
# In real code, this would convert to AgentResponseUpdate
|
||||
|
||||
@@ -20,7 +20,7 @@ sequenceDiagram
|
||||
participant Agent as Agent.run()
|
||||
participant AML as AgentMiddlewareLayer
|
||||
participant AMP as AgentMiddlewarePipeline
|
||||
participant RawAgent as RawChatAgent.run()
|
||||
participant RawAgent as RawAgent.run()
|
||||
participant CML as ChatMiddlewareLayer
|
||||
participant CMP as ChatMiddlewarePipeline
|
||||
participant FIL as FunctionInvocationLayer
|
||||
@@ -46,14 +46,14 @@ sequenceDiagram
|
||||
alt Non-Streaming (stream=False)
|
||||
RawAgent->>RawAgent: _prepare_run_context() [async]
|
||||
Note right of RawAgent: Builds: thread_messages, chat_options, tools
|
||||
RawAgent->>CML: chat_client.get_response(stream=False)
|
||||
RawAgent->>CML: client.get_response(stream=False)
|
||||
else Streaming (stream=True)
|
||||
RawAgent->>RawAgent: ResponseStream.from_awaitable()
|
||||
Note right of RawAgent: Defers async prep to stream consumption
|
||||
RawAgent-->>User: Returns ResponseStream immediately
|
||||
Note over RawAgent,CML: Async work happens on iteration
|
||||
RawAgent->>RawAgent: _prepare_run_context() [deferred]
|
||||
RawAgent->>CML: chat_client.get_response(stream=True)
|
||||
RawAgent->>CML: client.get_response(stream=True)
|
||||
end
|
||||
|
||||
Note over CML,CMP: Chat Middleware Layer
|
||||
@@ -132,7 +132,7 @@ sequenceDiagram
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `agent` | `SupportsAgentRun` | The agent being invoked |
|
||||
| `messages` | `list[ChatMessage]` | Input messages (mutable) |
|
||||
| `messages` | `list[Message]` | Input messages (mutable) |
|
||||
| `thread` | `AgentThread \| None` | Conversation thread |
|
||||
| `options` | `Mapping[str, Any]` | Chat options dict |
|
||||
| `stream` | `bool` | Whether streaming is enabled |
|
||||
@@ -142,9 +142,9 @@ sequenceDiagram
|
||||
|
||||
**Key Operations:**
|
||||
1. `categorize_middleware()` separates middleware by type (agent, chat, function)
|
||||
2. Chat and function middleware are forwarded to `chat_client`
|
||||
2. Chat and function middleware are forwarded to `client`
|
||||
3. `AgentMiddlewarePipeline.execute()` runs the agent middleware chain
|
||||
4. Final handler calls `RawChatAgent.run()`
|
||||
4. Final handler calls `RawAgent.run()`
|
||||
|
||||
**What Can Be Modified:**
|
||||
- `context.messages` - Add, remove, or modify input messages
|
||||
@@ -154,14 +154,14 @@ sequenceDiagram
|
||||
|
||||
### 2. Chat Middleware Layer (`ChatMiddlewareLayer`)
|
||||
|
||||
**Entry Point:** `chat_client.get_response(messages, options)`
|
||||
**Entry Point:** `client.get_response(messages, options)`
|
||||
|
||||
**Context Object:** `ChatContext`
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `chat_client` | `ChatClientProtocol` | The chat client |
|
||||
| `messages` | `Sequence[ChatMessage]` | Messages to send |
|
||||
| `client` | `SupportsChatGetResponse` | The chat client |
|
||||
| `messages` | `Sequence[Message]` | Messages to send |
|
||||
| `options` | `Mapping[str, Any]` | Chat options |
|
||||
| `stream` | `bool` | Whether streaming |
|
||||
| `metadata` | `dict` | Shared data between middleware |
|
||||
@@ -275,7 +275,7 @@ class TerminatingMiddleware(FunctionMiddleware):
|
||||
### Agent Layer → Chat Layer
|
||||
|
||||
```python
|
||||
# RawChatAgent._prepare_run_context() builds:
|
||||
# RawAgent._prepare_run_context() builds:
|
||||
{
|
||||
"thread": AgentThread, # Validated/created thread
|
||||
"input_messages": [...], # Normalized input messages
|
||||
@@ -463,7 +463,7 @@ Returns `Awaitable[AgentResponse]`:
|
||||
```python
|
||||
async def _run_non_streaming():
|
||||
ctx = await self._prepare_run_context(...) # Async preparation
|
||||
response = await self.chat_client.get_response(stream=False, ...)
|
||||
response = await self.client.get_response(stream=False, ...)
|
||||
await self._finalize_response_and_update_thread(...)
|
||||
return AgentResponse(...)
|
||||
```
|
||||
@@ -476,7 +476,7 @@ Returns `ResponseStream[AgentResponseUpdate, AgentResponse]` **synchronously**:
|
||||
# Async preparation is deferred using ResponseStream.from_awaitable()
|
||||
async def _get_stream():
|
||||
ctx = await self._prepare_run_context(...) # Deferred until iteration
|
||||
return self.chat_client.get_response(stream=True, ...)
|
||||
return self.client.get_response(stream=True, ...)
|
||||
|
||||
return (
|
||||
ResponseStream.from_awaitable(_get_stream())
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import asyncio
|
||||
from typing import Literal
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.anthropic import AnthropicClient
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIChatOptions
|
||||
|
||||
"""TypedDict-based Chat Options.
|
||||
|
||||
In Agent Framework, we have made ChatClient and ChatAgent generic over a ChatOptions typeddict, this means that
|
||||
In Agent Framework, we have made ChatClient and Agent generic over a ChatOptions typeddict, this means that
|
||||
you can override which options are available for a given client or agent by providing your own TypedDict subclass.
|
||||
And we include the most common options for all ChatClient providers out of the box.
|
||||
|
||||
@@ -21,7 +21,7 @@ which provides:
|
||||
including overriding unsupported options.
|
||||
|
||||
The sample shows usage with both OpenAI and Anthropic clients, demonstrating
|
||||
how provider-specific options work for ChatClient and ChatAgent. But the same approach works for other providers too.
|
||||
how provider-specific options work for ChatClient and Agent. But the same approach works for other providers too.
|
||||
"""
|
||||
|
||||
|
||||
@@ -49,14 +49,14 @@ async def demo_anthropic_chat_client() -> None:
|
||||
|
||||
|
||||
async def demo_anthropic_agent() -> None:
|
||||
"""Demonstrate ChatAgent with Anthropic client and typed options."""
|
||||
print("\n=== ChatAgent with Anthropic and Typed Options ===\n")
|
||||
"""Demonstrate Agent with Anthropic client and typed options."""
|
||||
print("\n=== Agent with Anthropic and Typed Options ===\n")
|
||||
|
||||
client = AnthropicClient(model_id="claude-sonnet-4-5-20250929")
|
||||
|
||||
# Create a typed agent for Anthropic - IDE knows Anthropic-specific options!
|
||||
agent = ChatAgent(
|
||||
chat_client=client,
|
||||
agent = Agent(
|
||||
client=client,
|
||||
name="claude-assistant",
|
||||
instructions="You are a helpful assistant powered by Claude. Be concise.",
|
||||
default_options={
|
||||
@@ -132,15 +132,15 @@ async def demo_openai_chat_client_reasoning_models() -> None:
|
||||
|
||||
|
||||
async def demo_openai_agent() -> None:
|
||||
"""Demonstrate ChatAgent with OpenAI client and typed options."""
|
||||
print("\n=== ChatAgent with OpenAI and Typed Options ===\n")
|
||||
"""Demonstrate Agent with OpenAI client and typed options."""
|
||||
print("\n=== Agent with OpenAI and Typed Options ===\n")
|
||||
|
||||
# Create a typed agent - IDE will autocomplete options!
|
||||
# The type annotation can be done either on the agent like below,
|
||||
# or on the client when constructing the client instance:
|
||||
# client = OpenAIChatClient[OpenAIReasoningChatOptions]()
|
||||
agent = ChatAgent[OpenAIReasoningChatOptions](
|
||||
chat_client=OpenAIChatClient(),
|
||||
agent = Agent[OpenAIReasoningChatOptions](
|
||||
client=OpenAIChatClient(),
|
||||
name="weather-assistant",
|
||||
instructions="You are a helpful assistant. Answer concisely.",
|
||||
# Options can be set at construction time
|
||||
|
||||
Reference in New Issue
Block a user