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,9 +7,9 @@ from typing import Annotated
from agent_framework import (
ChatContext,
ChatMessage,
ChatMiddleware,
ChatResponse,
Message,
MiddlewareTermination,
chat_middleware,
tool,
@@ -69,7 +69,7 @@ class InputObserverMiddleware(ChatMiddleware):
print(f"[InputObserverMiddleware] Total messages: {len(context.messages)}")
# Modify user messages by creating new messages with enhanced text
modified_messages: list[ChatMessage] = []
modified_messages: list[Message] = []
modified_count = 0
for message in context.messages:
@@ -81,7 +81,7 @@ class InputObserverMiddleware(ChatMiddleware):
updated_text = self.replacement
print(f"[InputObserverMiddleware] Updated: '{original_text}' -> '{updated_text}'")
modified_message = ChatMessage(message.role, [updated_text])
modified_message = Message(message.role, [updated_text])
modified_messages.append(modified_message)
modified_count += 1
else:
@@ -118,7 +118,7 @@ async def security_and_override_middleware(
# Override the response instead of calling AI
context.result = ChatResponse(
messages=[
ChatMessage(
Message(
role="assistant",
text="I cannot process requests containing sensitive information. "
"Please rephrase your question without including passwords, secrets, or other "
@@ -10,9 +10,9 @@ from agent_framework import (
AgentContext,
AgentMiddleware,
AgentResponse,
ChatMessage,
FunctionInvocationContext,
FunctionMiddleware,
Message,
tool,
)
from agent_framework.azure import AzureAIAgentClient
@@ -61,7 +61,7 @@ class SecurityAgentMiddleware(AgentMiddleware):
print("[SecurityAgentMiddleware] Security Warning: Detected sensitive information, blocking request.")
# Override the result with warning message
context.result = AgentResponse(
messages=[ChatMessage("assistant", ["Detected sensitive information, the request is blocked."])]
messages=[Message("assistant", ["Detected sensitive information, the request is blocked."])]
)
# Simply don't call call_next() to prevent execution
return
@@ -9,7 +9,7 @@ from agent_framework import (
AgentContext,
AgentMiddleware,
AgentResponse,
ChatMessage,
Message,
MiddlewareTermination,
tool,
)
@@ -62,7 +62,7 @@ class PreTerminationMiddleware(AgentMiddleware):
# Set a custom response
context.result = AgentResponse(
messages=[
ChatMessage(
Message(
role="assistant",
text=(
f"Sorry, I cannot process requests containing '{blocked_word}'. "
@@ -72,8 +72,8 @@ class PreTerminationMiddleware(AgentMiddleware):
]
)
# Set terminate flag to prevent further processing
raise MiddlewareTermination
# Terminate to prevent further processing
raise MiddlewareTermination(result=context.result)
await call_next(context)
@@ -11,10 +11,11 @@ from agent_framework import (
AgentResponse,
AgentResponseUpdate,
ChatContext,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
Message,
ResponseStream,
Role,
tool,
)
from agent_framework.openai import OpenAIResponsesClient
@@ -78,9 +79,9 @@ async def weather_override_middleware(context: ChatContext, call_next: Callable[
context.result.with_transform_hook(_update_hook)
else:
# For non-streaming: just replace with a new message
current_text = context.result.text or "" # type: ignore
current_text = context.result.text if isinstance(context.result, ChatResponse) else ""
custom_message = f"Weather Advisory: [0] {''.join(chunks)} Original message was: {current_text}"
context.result = ChatResponse(messages=[ChatMessage(role="assistant", text=custom_message)])
context.result = ChatResponse(messages=[Message(role=Role.ASSISTANT, text=custom_message)])
async def validate_weather_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None:
@@ -95,12 +96,12 @@ async def validate_weather_middleware(context: ChatContext, call_next: Callable[
if context.stream and isinstance(context.result, ResponseStream):
def _append_validation_note(response: ChatResponse) -> ChatResponse:
response.messages.append(ChatMessage(role="assistant", text=validation_note))
response.messages.append(Message(role=Role.ASSISTANT, text=validation_note))
return response
context.result.with_result_hook(_append_validation_note)
context.result.with_finalizer(_append_validation_note)
elif isinstance(context.result, ChatResponse):
context.result.messages.append(ChatMessage(role="assistant", text=validation_note))
context.result.messages.append(Message(role=Role.ASSISTANT, text=validation_note))
async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None:
@@ -117,7 +118,7 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[A
def _sanitize(response: AgentResponse) -> AgentResponse:
found_prefix = state["found_prefix"]
found_validation = False
cleaned_messages: list[ChatMessage] = []
cleaned_messages: list[Message] = []
for message in response.messages:
text = message.text
@@ -138,7 +139,7 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[A
text = re.sub(r"\[\d+\]\s*", "", text)
cleaned_messages.append(
ChatMessage(
Message(
role=message.role,
text=text.strip(),
author_name=message.author_name,
@@ -153,7 +154,7 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[A
if not found_validation:
raise RuntimeError("Expected validation note not found in agent response.")
cleaned_messages.append(ChatMessage(role="assistant", text=" Agent: OK"))
cleaned_messages.append(Message(role=Role.ASSISTANT, text=" Agent: OK"))
response.messages = cleaned_messages
return response
@@ -172,7 +173,7 @@ async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[A
return update
context.result.with_transform_hook(_clean_update)
context.result.with_result_hook(_sanitize)
context.result.with_finalizer(_sanitize)
elif isinstance(context.result, AgentResponse):
context.result = _sanitize(context.result)
@@ -191,19 +192,6 @@ async def main() -> None:
tools=get_weather,
middleware=[agent_cleanup_middleware],
)
# Streaming example
print("\n--- Streaming Example ---")
query = "What's the weather like in Portland?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
response = agent.run(query, stream=True)
# add the hooks to print what you want to see
response.with_transform_hook(lambda chunk: print(chunk.text, end="", flush=True)).with_result_hook(
lambda final: print(f"\nFinal streamed response: {final.text}", flush=True)
)
# consume the stream to trigger the hooks
await response.get_final_response()
# Non-streaming example
print("\n--- Non-streaming Example ---")
query = "What's the weather like in Seattle?"
@@ -211,6 +199,18 @@ async def main() -> None:
result = await agent.run(query)
print(f"Agent: {result}")
# Streaming example
print("\n--- Streaming Example ---")
query = "What's the weather like in Portland?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
response = agent.run(query, stream=True)
async for chunk in response:
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
print(f"Final Result: {(await response.get_final_response()).text}")
if __name__ == "__main__":
asyncio.run(main())