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
+1 -1
View File
@@ -20,7 +20,7 @@ Interactive developer UI for testing and debugging agents and workflows.
```python
from agent_framework.devui import serve
agent = ChatAgent(...)
agent = Agent(...)
serve(entities=[agent], port=8080, auto_open=True)
```
+8 -8
View File
@@ -17,7 +17,7 @@ pip install agent-framework-devui --pre
You can also launch it programmatically
```python
from agent_framework import ChatAgent
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework.devui import serve
@@ -26,9 +26,9 @@ def get_weather(location: str) -> str:
return f"Weather in {location}: 72°F and sunny"
# Create your agent
agent = ChatAgent(
agent = Agent(
name="WeatherAgent",
chat_client=OpenAIChatClient(),
client=OpenAIChatClient(),
tools=[get_weather]
)
@@ -55,8 +55,8 @@ When DevUI starts with no discovered entities, it displays a **sample entity gal
```python
# ✅ Correct - DevUI handles cleanup automatically
mcp_tool = MCPStreamableHTTPTool(url="http://localhost:8011/mcp", chat_client=chat_client)
agent = ChatAgent(tools=mcp_tool)
mcp_tool = MCPStreamableHTTPTool(url="http://localhost:8011/mcp", client=client)
agent = Agent(tools=mcp_tool)
serve(entities=[agent])
```
@@ -68,13 +68,13 @@ Register cleanup hooks to properly close credentials and resources on shutdown:
```python
from azure.identity.aio import DefaultAzureCredential
from agent_framework import ChatAgent
from agent_framework import Agent
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework_devui import register_cleanup, serve
credential = DefaultAzureCredential()
client = AzureOpenAIChatClient()
agent = ChatAgent(name="MyAgent", chat_client=client)
agent = Agent(name="MyAgent", client=client)
# Register cleanup hook - credential will be closed on shutdown
register_cleanup(agent, credential.close)
@@ -92,7 +92,7 @@ For your agents to be discovered by the DevUI, they must be organized in a direc
```
agents/
├── weather_agent/
│ ├── __init__.py # Must export: agent = ChatAgent(...)
│ ├── __init__.py # Must export: agent = Agent(...)
│ ├── agent.py
│ └── .env # Optional: API keys, config vars
├── my_workflow/
@@ -41,7 +41,7 @@ def register_cleanup(entity: Any, *hooks: Callable[[], Any]) -> None:
Single cleanup hook:
>>> from agent_framework.devui import serve, register_cleanup
>>> credential = DefaultAzureCredential()
>>> agent = ChatAgent(...)
>>> agent = Agent(...)
>>> register_cleanup(agent, credential.close)
>>> serve(entities=[agent])
@@ -52,7 +52,7 @@ def register_cleanup(entity: Any, *hooks: Callable[[], Any]) -> None:
>>> # In agents/my_agent/agent.py
>>> from agent_framework.devui import register_cleanup
>>> credential = DefaultAzureCredential()
>>> agent = ChatAgent(...)
>>> agent = Agent(...)
>>> register_cleanup(agent, credential.close)
>>> # Run: devui ./agents
"""
@@ -13,11 +13,11 @@ import uuid
from abc import ABC, abstractmethod
from typing import Any, Literal, cast
from agent_framework import AgentThread, ChatMessage
from agent_framework import AgentThread, Message
from agent_framework._workflows._checkpoint import InMemoryCheckpointStorage
from openai.types.conversations import Conversation, ConversationDeletedResource
from openai.types.conversations.conversation_item import ConversationItem
from openai.types.conversations.message import Message
from openai.types.conversations.message import Message as OpenAIMessage
from openai.types.conversations.text_content import TextContent
from openai.types.responses import (
ResponseFunctionToolCallItem,
@@ -305,7 +305,7 @@ class InMemoryConversationStore(ConversationStore):
content = item.get("content", [])
text = content[0].get("text", "") if content else ""
chat_msg = ChatMessage(role=role, text=text) # type: ignore[arg-type]
chat_msg = Message(role=role, text=text) # type: ignore[arg-type]
chat_messages.append(chat_msg)
# Add messages to AgentThread
@@ -320,7 +320,7 @@ class InMemoryConversationStore(ConversationStore):
role_str = msg.role if hasattr(msg.role, "value") else str(msg.role)
role = cast(MessageRole, role_str) # Safe: Agent Framework roles match OpenAI roles
# Convert ChatMessage contents to OpenAI TextContent format
# Convert Message contents to OpenAI TextContent format
message_content = []
for content_item in msg.contents:
if content_item.type == "text":
@@ -329,7 +329,7 @@ class InMemoryConversationStore(ConversationStore):
message_content.append(TextContent(type="text", text=text_value))
# Create Message object (concrete type from ConversationItem union)
message = Message(
message = OpenAIMessage(
id=item_id,
type="message", # Required discriminator for union
role=role,
@@ -372,14 +372,14 @@ class InMemoryConversationStore(ConversationStore):
if thread.message_store:
af_messages = await thread.message_store.list_messages()
# Convert each AgentFramework ChatMessage to appropriate ConversationItem type(s)
# Convert each AgentFramework Message to appropriate ConversationItem type(s)
for i, msg in enumerate(af_messages):
item_id = f"item_{i}"
role_str = msg.role if hasattr(msg.role, "value") else str(msg.role)
role = cast(MessageRole, role_str) # Safe: Agent Framework roles match OpenAI roles
# Process each content item in the message
# A single ChatMessage may produce multiple ConversationItems
# A single Message may produce multiple ConversationItems
# (e.g., a message with both text and a function call)
message_contents: list[TextContent | ResponseInputImage | ResponseInputFile] = []
function_calls = []
@@ -464,7 +464,7 @@ class InMemoryConversationStore(ConversationStore):
# Create ConversationItems based on what we found
# If message has text/images/files, create a Message item
if message_contents:
message = Message(
message = OpenAIMessage(
id=item_id,
type="message",
role=role, # type: ignore
@@ -541,7 +541,7 @@ class EntityDiscovery:
"""Check if a Python file has entity exports (agent or workflow) using AST parsing.
This safely checks for module-level assignments like:
- agent = ChatAgent(...)
- agent = Agent(...)
- workflow = WorkflowBuilder(start_executor=...)...
Args:
@@ -305,7 +305,7 @@ class AgentFrameworkExecutor:
yield AgentStartedEvent()
# Convert input to proper ChatMessage or string
# Convert input to proper Message or string
user_message = self._convert_input_to_chat_message(request.input)
# Get thread from conversation parameter (OpenAI standard!)
@@ -321,7 +321,7 @@ class AgentFrameworkExecutor:
if isinstance(user_message, str):
logger.debug(f"Executing agent with text input: {user_message[:100]}...")
else:
logger.debug(f"Executing agent with multimodal ChatMessage: {type(user_message)}")
logger.debug(f"Executing agent with multimodal Message: {type(user_message)}")
# Workaround for MCP tool stale connection bug (GitHub issue pending)
# When HTTP streaming ends, GeneratorExit can close MCP stdio streams
@@ -534,7 +534,7 @@ class AgentFrameworkExecutor:
yield {"type": "error", "message": f"Workflow execution error: {e!s}"}
def _convert_input_to_chat_message(self, input_data: Any) -> Any:
"""Convert OpenAI Responses API input to Agent Framework ChatMessage or string.
"""Convert OpenAI Responses API input to Agent Framework Message or string.
Handles various input formats including text, images, files, and multimodal content.
Falls back to string extraction for simple cases.
@@ -543,11 +543,11 @@ class AgentFrameworkExecutor:
input_data: OpenAI ResponseInputParam (List[ResponseInputItemParam])
Returns:
ChatMessage for multimodal content, or string for simple text
Message for multimodal content, or string for simple text
"""
# Import Agent Framework types
try:
from agent_framework import ChatMessage, Role
from agent_framework import Message, Role
except ImportError:
# Fallback to string extraction if Agent Framework not available
return self._extract_user_message_fallback(input_data)
@@ -558,24 +558,24 @@ class AgentFrameworkExecutor:
# Handle OpenAI ResponseInputParam (List[ResponseInputItemParam])
if isinstance(input_data, list):
return self._convert_openai_input_to_chat_message(input_data, ChatMessage, Role)
return self._convert_openai_input_to_chat_message(input_data, Message, Role)
# Fallback for other formats
return self._extract_user_message_fallback(input_data)
def _convert_openai_input_to_chat_message(self, input_items: list[Any], ChatMessage: Any, Role: Any) -> Any:
"""Convert OpenAI ResponseInputParam to Agent Framework ChatMessage.
def _convert_openai_input_to_chat_message(self, input_items: list[Any], Message: Any, Role: Any) -> Any:
"""Convert OpenAI ResponseInputParam to Agent Framework Message.
Processes text, images, files, and other content types from OpenAI format
to Agent Framework ChatMessage with appropriate content objects.
to Agent Framework Message with appropriate content objects.
Args:
input_items: List of OpenAI ResponseInputItemParam objects (dicts or objects)
ChatMessage: ChatMessage class for creating chat messages
Message: Message class for creating chat messages
Role: Role enum for message roles
Returns:
ChatMessage with converted content
Message with converted content
"""
contents: list[Content] = []
@@ -705,9 +705,9 @@ class AgentFrameworkExecutor:
if not contents:
contents.append(Content.from_text(text=""))
chat_message = ChatMessage(role="user", contents=contents)
chat_message = Message(role="user", contents=contents)
logger.info(f"Created ChatMessage with {len(contents)} contents:")
logger.info(f"Created Message with {len(contents)} contents:")
for idx, content in enumerate(contents):
content_type = content.__class__.__name__
if hasattr(content, "media_type"):
@@ -772,9 +772,9 @@ class AgentFrameworkExecutor:
pass
# Check for OpenAI multimodal format (list with type: "message")
# This handles ChatMessage inputs with images, files, etc.
# This handles Message inputs with images, files, etc.
if self._is_openai_multimodal_format(raw_input):
logger.debug("Detected OpenAI multimodal format, converting to ChatMessage")
logger.debug("Detected OpenAI multimodal format, converting to Message")
return self._convert_input_to_chat_message(raw_input)
# Handle structured input (dict)
@@ -14,7 +14,7 @@ from datetime import datetime
from typing import Any, Union
from uuid import uuid4
from agent_framework import ChatMessage, Content
from agent_framework import Content, Message
from openai.types.responses import (
Response,
ResponseContentPartAddedEvent,
@@ -453,7 +453,7 @@ class MessageMapper:
Handles:
- Primitives (str, int, float, bool, None)
- Collections (list, tuple, set, dict)
- SerializationMixin objects (ChatMessage, etc.) - calls to_dict()
- SerializationMixin objects (Message, etc.) - calls to_dict()
- Pydantic models - calls model_dump()
- Dataclasses - recursively serializes with asdict()
- Enums - extracts value
@@ -502,7 +502,7 @@ class MessageMapper:
if isinstance(value, dict):
return {k: self._serialize_value(v) for k, v in value.items()}
# Handle SerializationMixin (like ChatMessage) - call to_dict()
# Handle SerializationMixin (like Message) - call to_dict()
if hasattr(value, "to_dict") and callable(getattr(value, "to_dict", None)):
try:
return value.to_dict() # type: ignore[attr-defined, no-any-return]
@@ -536,7 +536,7 @@ class MessageMapper:
def _serialize_request_data(self, request_data: Any) -> dict[str, Any]:
"""Serialize RequestInfoMessage to dict for JSON transmission.
Handles nested SerializationMixin objects (like ChatMessage) within dataclasses.
Handles nested SerializationMixin objects (like Message) within dataclasses.
Args:
request_data: The RequestInfoMessage instance
@@ -554,7 +554,7 @@ class MessageMapper:
return {k: self._serialize_value(v) for k, v in request_data.items()}
# Handle dataclasses with nested SerializationMixin objects
# We can't use asdict() directly because it doesn't handle ChatMessage
# We can't use asdict() directly because it doesn't handle Message
if is_dataclass(request_data) and not isinstance(request_data, type):
try:
# Manually serialize each field to handle nested SerializationMixin
@@ -892,17 +892,17 @@ class MessageMapper:
# Extract text from output data based on type
text = None
if isinstance(output_data, ChatMessage):
# Handle ChatMessage (from Magentic and AgentExecutor with output_response=True)
if isinstance(output_data, Message):
# Handle Message (from Magentic and AgentExecutor with output_response=True)
text = getattr(output_data, "text", None)
if not text:
# Fallback to string representation
text = str(output_data)
elif isinstance(output_data, list):
# Handle list of ChatMessage objects (from Magentic yield_output([final_answer]))
# Handle list of Message objects (from Magentic yield_output([final_answer]))
text_parts = []
for item in output_data:
if isinstance(item, ChatMessage):
if isinstance(item, Message):
item_text = getattr(item, "text", None)
if item_text:
text_parts.append(item_text)
@@ -1047,7 +1047,7 @@ class MessageMapper:
# Create ExecutorActionItem with completed status
# executor_completed event (type='executor_completed') uses 'data' field, not 'result'
# Serialize the result data to ensure it's JSON-serializable
# (AgentExecutorResponse contains AgentResponse/ChatMessage which are SerializationMixin)
# (AgentExecutorResponse contains AgentResponse/Message which are SerializationMixin)
raw_result = getattr(event, "data", None)
serialized_result = self._serialize_value(raw_result) if raw_result is not None else None
executor_item = ExecutorActionItem(
@@ -222,8 +222,8 @@ class DevServer:
# Step 2: Close chat clients and their credentials (EXISTING)
entity_obj = self.executor.entity_discovery.get_entity_object(entity_id)
if entity_obj and hasattr(entity_obj, "chat_client"):
client = entity_obj.chat_client
if entity_obj and hasattr(entity_obj, "client"):
client = entity_obj.client
# Close the chat client itself
if hasattr(client, "close") and callable(client.close):
@@ -9,7 +9,7 @@ from dataclasses import fields, is_dataclass
from types import UnionType
from typing import Any, Union, get_args, get_origin, get_type_hints
from agent_framework import ChatMessage
from agent_framework import Message
logger = logging.getLogger(__name__)
@@ -45,7 +45,7 @@ def extract_agent_metadata(entity_object: Any) -> dict[str, Any]:
elif hasattr(chat_opts, "instructions"):
metadata["instructions"] = chat_opts.instructions
# Try to get model - check both default_options and chat_client
# Try to get model - check both default_options and client
if hasattr(entity_object, "default_options"):
chat_opts = entity_object.default_options
if isinstance(chat_opts, dict):
@@ -53,16 +53,12 @@ def extract_agent_metadata(entity_object: Any) -> dict[str, Any]:
metadata["model"] = chat_opts.get("model_id")
elif hasattr(chat_opts, "model_id") and chat_opts.model_id:
metadata["model"] = chat_opts.model_id
if (
metadata["model"] is None
and hasattr(entity_object, "chat_client")
and hasattr(entity_object.chat_client, "model_id")
):
metadata["model"] = entity_object.chat_client.model_id
if metadata["model"] is None and hasattr(entity_object, "client") and hasattr(entity_object.client, "model_id"):
metadata["model"] = entity_object.client.model_id
# Try to get chat client type
if hasattr(entity_object, "chat_client"):
metadata["chat_client_type"] = entity_object.chat_client.__class__.__name__
if hasattr(entity_object, "client"):
metadata["chat_client_type"] = entity_object.client.__class__.__name__
# Try to get context providers
if (
@@ -124,8 +120,8 @@ def extract_executor_message_types(executor: Any) -> list[Any]:
def _contains_chat_message(type_hint: Any) -> bool:
"""Check whether the provided type hint directly or indirectly references ChatMessage."""
if type_hint is ChatMessage:
"""Check whether the provided type hint directly or indirectly references Message."""
if type_hint is Message:
return True
origin = get_origin(type_hint)
@@ -141,7 +137,7 @@ def _contains_chat_message(type_hint: Any) -> bool:
def select_primary_input_type(message_types: list[Any]) -> Any | None:
"""Choose the most user-friendly input type for workflow inputs.
Prefers ChatMessage (or containers thereof) and then falls back to primitives.
Prefers Message (or containers thereof) and then falls back to primitives.
Args:
message_types: List of possible message types
@@ -154,7 +150,7 @@ def select_primary_input_type(message_types: list[Any]) -> Any | None:
for message_type in message_types:
if _contains_chat_message(message_type):
return ChatMessage
return Message
preferred = (str, dict)
@@ -427,7 +423,7 @@ def generate_input_schema(input_type: type) -> dict[str, Any]:
if hasattr(input_type, "model_json_schema"):
return input_type.model_json_schema() # type: ignore
# 3. SerializationMixin classes (ChatMessage, etc.)
# 3. SerializationMixin classes (Message, etc.)
if is_serialization_mixin(input_type):
return generate_schema_from_serialization_mixin(input_type)
@@ -521,7 +517,7 @@ def _parse_string_input(input_str: str, target_type: type) -> Any:
except Exception as e:
logger.debug(f"Failed to parse string as Pydantic model: {e}")
# SerializationMixin (like ChatMessage)
# SerializationMixin (like Message)
if is_serialization_mixin(target_type):
try:
# Try parsing as JSON dict first
@@ -531,7 +527,7 @@ def _parse_string_input(input_str: str, target_type: type) -> Any:
return target_type.from_dict(data) # type: ignore
return target_type(**data) # type: ignore
# For ChatMessage specifically: create from text
# For Message specifically: create from text
# Try common field patterns
common_fields = ["text", "message", "content"]
sig = inspect.signature(target_type)
File diff suppressed because one or more lines are too long
@@ -76,7 +76,7 @@ export function RunWorkflowButton({
// Analyze input requirements
const inputAnalysis = useMemo(() => {
// Check if this is a ChatMessage schema (for AgentExecutor workflows)
// Check if this is a Message schema (for AgentExecutor workflows)
const isChatMessage = isChatMessageSchema(inputSchema);
if (!inputSchema)
@@ -115,7 +115,7 @@ export function getFieldColumnSpan(
}
// ============================================================================
// ChatMessage Pattern Detection (exported for reuse)
// Message Pattern Detection (exported for reuse)
// ============================================================================
export function detectChatMessagePattern(
@@ -436,7 +436,7 @@ export function SchemaFormRenderer({
(name) => !hideFields.includes(name)
);
// Detect ChatMessage pattern
// Detect Message pattern
const isChatMessageLike = detectChatMessagePattern(schema, requiredFields);
// Separate required and optional fields
@@ -449,7 +449,7 @@ export function SchemaFormRenderer({
(name) => !requiredFields.includes(name)
);
// For ChatMessage: prioritize text/message/content
// For Message: prioritize text/message/content
const sortedOptionalFields = isChatMessageLike
? [...optionalFieldNames].sort((a, b) => {
const priority = (name: string) =>
@@ -48,7 +48,7 @@ export function WorkflowInputForm({
const requiredFields = inputSchema.required || [];
const isSimpleInput = inputSchema.type === "string" && !inputSchema.enum;
// Detect ChatMessage-like pattern for auto-filling role
// Detect Message-like pattern for auto-filling role
const isChatMessageLike = detectChatMessagePattern(inputSchema, requiredFields);
// Validation: check if required fields are filled
@@ -82,7 +82,7 @@ export function WorkflowInputForm({
}
});
// Auto-fill role="user" for ChatMessage-like inputs
// Auto-fill role="user" for Message-like inputs
if (isChatMessageLike && !initialData["role"]) {
initialData["role"] = "user";
}
@@ -1340,7 +1340,7 @@ function TraceTreeNode({ node, depth = 0 }: { node: TraceNode; depth?: number })
{/* Operation badge */}
<span className={`text-xs px-1.5 py-0.5 rounded font-medium ${getOperationColor(operationName)}`}>
{operationName.replace("ChatAgent.", "").replace("invoke_agent ", "")}
{operationName.replace("Agent.", "").replace("invoke_agent ", "")}
</span>
{/* Duration */}
@@ -223,7 +223,7 @@ export interface AgentResponseUpdate {
// Agent run response (final)
export interface AgentResponse {
messages: ChatMessage[];
messages: Message[];
response_id?: string;
created_at?: CreatedAtT;
usage_details?: UsageDetails;
@@ -232,7 +232,7 @@ export interface AgentResponse {
}
// Chat message
export interface ChatMessage {
export interface Message {
contents: Content[];
role?: Role;
author_name?: string;
@@ -185,7 +185,7 @@ export interface MetaResponse {
}
// Chat message types matching Agent Framework
export interface ChatMessage {
export interface Message {
id: string;
role: "user" | "assistant" | "system" | "tool";
contents: import("./agent-framework").Content[];
@@ -212,7 +212,7 @@ export interface AppState {
}
export interface ChatState {
messages: ChatMessage[];
messages: Message[];
isStreaming: boolean;
// streamEvents removed - use OpenAI events directly instead
}
@@ -15,8 +15,8 @@ import type { Workflow } from "@/types/workflow";
import { getTypedWorkflow } from "@/types/workflow";
/**
* Detects if a JSON schema represents a ChatMessage input type.
* ChatMessage schemas typically have:
* Detects if a JSON schema represents a Message input type.
* Message schemas typically have:
* - type: "object"
* - properties with "text" (required string) and "role" (optional string)
*
@@ -24,7 +24,7 @@ import { getTypedWorkflow } from "@/types/workflow";
* component for workflows that start with an AgentExecutor.
*
* @param schema - The JSON schema to check
* @returns true if the schema represents a ChatMessage-like input
* @returns true if the schema represents a Message-like input
*/
export function isChatMessageSchema(schema: JSONSchemaProperty | undefined): boolean {
if (!schema) return false;
@@ -37,13 +37,13 @@ export function isChatMessageSchema(schema: JSONSchemaProperty | undefined): boo
const props = schema.properties;
// ChatMessage has "text" property (the main content)
// Message has "text" property (the main content)
const hasText = "text" in props && props.text?.type === "string";
// ChatMessage has "role" property (user, assistant, system)
// Message has "role" property (user, assistant, system)
const hasRole = "role" in props && props.role?.type === "string";
// If it has both text and role, it's likely a ChatMessage
// If it has both text and role, it's likely a Message
if (hasText && hasRole) {
return true;
}
+44 -44
View File
@@ -17,16 +17,16 @@ from typing import Any, Generic
import pytest
import pytest_asyncio
from agent_framework import (
Agent,
AgentResponse,
AgentResponseUpdate,
AgentThread,
BaseAgent,
BaseChatClient,
ChatAgent,
ChatMessage,
ChatResponse,
ChatResponseUpdate,
Content,
Message,
ResponseStream,
)
from agent_framework._clients import OptionsCoT
@@ -67,17 +67,17 @@ class MockChatClient:
async def get_response(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage],
messages: str | Message | list[str] | list[Message],
**kwargs: Any,
) -> ChatResponse:
self.call_count += 1
if self.responses:
return self.responses.pop(0)
return ChatResponse(messages=ChatMessage("assistant", ["test response"]))
return ChatResponse(messages=Message("assistant", ["test response"]))
async def get_streaming_response(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage],
messages: str | Message | list[str] | list[Message],
**kwargs: Any,
) -> AsyncIterable[ChatResponseUpdate]:
self.call_count += 1
@@ -101,13 +101,13 @@ class MockBaseChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
self.run_responses: list[ChatResponse] = []
self.streaming_responses: list[list[ChatResponseUpdate]] = []
self.call_count: int = 0
self.received_messages: list[list[ChatMessage]] = []
self.received_messages: list[list[Message]] = []
@override
def _inner_get_response(
self,
*,
messages: Sequence[ChatMessage],
messages: Sequence[Message],
stream: bool,
options: Mapping[str, Any],
**kwargs: Any,
@@ -120,11 +120,11 @@ class MockBaseChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
self.received_messages.append(list(messages))
if self.run_responses:
return self.run_responses.pop(0)
return ChatResponse(messages=ChatMessage("assistant", ["Mock response from ChatAgent"]))
return ChatResponse(messages=Message("assistant", ["Mock response from Agent"]))
return _get()
async def _stream_impl(self, messages: Sequence[ChatMessage]) -> AsyncIterable[ChatResponseUpdate]:
async def _stream_impl(self, messages: Sequence[Message]) -> AsyncIterable[ChatResponseUpdate]:
self.call_count += 1
self.received_messages.append(list(messages))
if self.streaming_responses:
@@ -135,7 +135,7 @@ class MockBaseChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
yield ChatResponseUpdate(contents=[Content.from_text(text="Mock ")], role="assistant")
yield ChatResponseUpdate(contents=[Content.from_text(text="streaming ")], role="assistant")
yield ChatResponseUpdate(contents=[Content.from_text(text="response ")], role="assistant")
yield ChatResponseUpdate(contents=[Content.from_text(text="from ChatAgent")], role="assistant")
yield ChatResponseUpdate(contents=[Content.from_text(text="from Agent")], role="assistant")
# =============================================================================
@@ -159,7 +159,7 @@ class MockAgent(BaseAgent):
def run(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
stream: bool = False,
thread: AgentThread | None = None,
@@ -172,17 +172,17 @@ class MockAgent(BaseAgent):
async def _run(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentResponse:
self.call_count += 1
return AgentResponse(messages=[ChatMessage("assistant", [Content.from_text(text=self.response_text)])])
return AgentResponse(messages=[Message("assistant", [Content.from_text(text=self.response_text)])])
def _run_stream(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
**kwargs: Any,
@@ -205,7 +205,7 @@ class MockToolCallingAgent(BaseAgent):
def run(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
stream: bool = False,
thread: AgentThread | None = None,
@@ -218,16 +218,16 @@ class MockToolCallingAgent(BaseAgent):
async def _run(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
**kwargs: Any,
) -> AgentResponse:
return AgentResponse(messages=[ChatMessage("assistant", ["done"])])
return AgentResponse(messages=[Message("assistant", ["done"])])
def _run_stream(
self,
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
messages: str | Message | list[str] | list[Message] | None = None,
*,
thread: AgentThread | None = None,
**kwargs: Any,
@@ -275,7 +275,7 @@ class MockToolCallingAgent(BaseAgent):
def _create_agent_run_response(text: str = "Test response") -> AgentResponse:
"""Create an AgentResponse with the given text."""
return AgentResponse(messages=[ChatMessage("assistant", [Content.from_text(text=text)])])
return AgentResponse(messages=[Message("assistant", [Content.from_text(text=text)])])
def _create_agent_executor_response(
@@ -289,8 +289,8 @@ def _create_agent_executor_response(
executor_id=executor_id,
agent_response=agent_response,
full_conversation=[
ChatMessage("user", [Content.from_text(text="User input")]),
ChatMessage("assistant", [Content.from_text(text=response_text)]),
Message("user", [Content.from_text(text="User input")]),
Message("assistant", [Content.from_text(text=response_text)]),
],
)
@@ -318,7 +318,7 @@ def create_executor_completed_event(
This creates the exact data structure that caused the serialization bug:
WorkflowEvent.data contains AgentExecutorResponse which contains
AgentResponse and ChatMessage objects (SerializationMixin, not Pydantic).
AgentResponse and Message objects (SerializationMixin, not Pydantic).
"""
data = _create_agent_executor_response(executor_id) if with_agent_response else {"simple": "dict"}
return WorkflowEvent.executor_completed(executor_id=executor_id, data=data)
@@ -390,7 +390,7 @@ def executor_completed_event() -> WorkflowEvent[Any]:
This creates the exact data structure that caused the serialization bug:
executor_completed event (type='executor_completed').data contains AgentExecutorResponse which contains
AgentResponse and ChatMessage objects (SerializationMixin, not Pydantic).
AgentResponse and Message objects (SerializationMixin, not Pydantic).
"""
data = _create_agent_executor_response("test_executor")
return WorkflowEvent.executor_completed(executor_id="test_executor", data=data)
@@ -425,10 +425,10 @@ def test_entities_dir() -> str:
@pytest_asyncio.fixture
async def executor_with_real_agent() -> tuple[AgentFrameworkExecutor, str, MockBaseChatClient]:
"""Create an executor with a REAL ChatAgent using mock chat client.
"""Create an executor with a REAL Agent using mock chat client.
This tests the full execution pipeline:
- Real ChatAgent class
- Real Agent class
- Real message handling and normalization
- Real middleware pipeline
- Only the LLM call is mocked
@@ -440,12 +440,12 @@ async def executor_with_real_agent() -> tuple[AgentFrameworkExecutor, str, MockB
mapper = MessageMapper()
executor = AgentFrameworkExecutor(discovery, mapper)
# Create a REAL ChatAgent with mock client
agent = ChatAgent(
# Create a REAL Agent with mock client
agent = Agent(
id="test_chat_agent",
name="Test Chat Agent",
description="A real ChatAgent for testing execution flow",
chat_client=mock_client,
description="A real Agent for testing execution flow",
client=mock_client,
system_message="You are a helpful test assistant.",
)
@@ -469,22 +469,22 @@ async def sequential_workflow() -> tuple[AgentFrameworkExecutor, str, MockBaseCh
"""
mock_client = MockBaseChatClient()
mock_client.run_responses = [
ChatResponse(messages=ChatMessage("assistant", ["Here's the draft content about the topic."])),
ChatResponse(messages=ChatMessage("assistant", ["Review: Content is clear and well-structured."])),
ChatResponse(messages=Message("assistant", ["Here's the draft content about the topic."])),
ChatResponse(messages=Message("assistant", ["Review: Content is clear and well-structured."])),
]
writer = ChatAgent(
writer = Agent(
id="writer",
name="Writer",
description="Content writer agent",
chat_client=mock_client,
client=mock_client,
system_message="You are a content writer. Create clear, engaging content.",
)
reviewer = ChatAgent(
reviewer = Agent(
id="reviewer",
name="Reviewer",
description="Content reviewer agent",
chat_client=mock_client,
client=mock_client,
system_message="You are a reviewer. Provide constructive feedback.",
)
@@ -513,30 +513,30 @@ async def concurrent_workflow() -> tuple[AgentFrameworkExecutor, str, MockBaseCh
"""
mock_client = MockBaseChatClient()
mock_client.run_responses = [
ChatResponse(messages=ChatMessage("assistant", ["Research findings: Key data points identified."])),
ChatResponse(messages=ChatMessage("assistant", ["Analysis: Trends indicate positive growth."])),
ChatResponse(messages=ChatMessage("assistant", ["Summary: Overall outlook is favorable."])),
ChatResponse(messages=Message("assistant", ["Research findings: Key data points identified."])),
ChatResponse(messages=Message("assistant", ["Analysis: Trends indicate positive growth."])),
ChatResponse(messages=Message("assistant", ["Summary: Overall outlook is favorable."])),
]
researcher = ChatAgent(
researcher = Agent(
id="researcher",
name="Researcher",
description="Research agent",
chat_client=mock_client,
client=mock_client,
system_message="You are a researcher. Find key data and insights.",
)
analyst = ChatAgent(
analyst = Agent(
id="analyst",
name="Analyst",
description="Analysis agent",
chat_client=mock_client,
client=mock_client,
system_message="You are an analyst. Identify trends and patterns.",
)
summarizer = ChatAgent(
summarizer = Agent(
id="summarizer",
name="Summarizer",
description="Summary agent",
chat_client=mock_client,
client=mock_client,
system_message="You are a summarizer. Provide concise summaries.",
)
@@ -7,7 +7,7 @@ import tempfile
from pathlib import Path
import pytest
from agent_framework import AgentResponse, ChatMessage, Content
from agent_framework import AgentResponse, Content, Message
from agent_framework_devui import register_cleanup
from agent_framework_devui._discovery import EntityDiscovery
@@ -39,12 +39,12 @@ class MockAgent:
async def _stream():
yield AgentResponse(
messages=[ChatMessage(role="assistant", contents=[Content.from_text(text="Test response")])],
messages=[Message(role="assistant", contents=[Content.from_text(text="Test response")])],
)
return _stream()
return AgentResponse(
messages=[ChatMessage(role="assistant", contents=[Content.from_text(text="Test response")])],
messages=[Message(role="assistant", contents=[Content.from_text(text="Test response")])],
)
@@ -267,7 +267,7 @@ async def test_cleanup_with_file_based_discovery():
# Write agent module with cleanup registration
agent_file = agent_dir / "__init__.py"
agent_file.write_text("""
from agent_framework import AgentResponse, ChatMessage, Role, Content
from agent_framework import AgentResponse, Message, Role, Content
from agent_framework_devui import register_cleanup
class MockCredential:
@@ -289,12 +289,12 @@ class TestAgent:
if stream:
async def _stream():
yield AgentResponse(
messages=[ChatMessage(role="assistant", content=[Content.from_text(text="Test")])],
messages=[Message(role="assistant", content=[Content.from_text(text="Test")])],
inner_messages=[],
)
return _stream()
return AgentResponse(
messages=[ChatMessage(role="assistant", content=[Content.from_text(text="Test")])],
messages=[Message(role="assistant", content=[Content.from_text(text="Test")])],
inner_messages=[],
)
@@ -199,7 +199,7 @@ async def test_list_items_pagination():
@pytest.mark.asyncio
async def test_list_items_converts_function_calls():
"""Test that list_items properly converts function calls to ResponseFunctionToolCallItem."""
from agent_framework import ChatMessage, ChatMessageStore
from agent_framework import ChatMessageStore, Message
store = InMemoryConversationStore()
@@ -216,8 +216,8 @@ async def test_list_items_converts_function_calls():
# Simulate messages from agent execution with function calls
messages = [
ChatMessage(role="user", contents=[{"type": "text", "text": "What's the weather in SF?"}]),
ChatMessage(
Message(role="user", contents=[{"type": "text", "text": "What's the weather in SF?"}]),
Message(
role="assistant",
contents=[
{
@@ -228,7 +228,7 @@ async def test_list_items_converts_function_calls():
}
],
),
ChatMessage(
Message(
role="tool",
contents=[
{
@@ -238,7 +238,7 @@ async def test_list_items_converts_function_calls():
}
],
),
ChatMessage(role="assistant", contents=[{"type": "text", "text": "The weather is sunny, 65°F"}]),
Message(role="assistant", contents=[{"type": "text", "text": "The weather is sunny, 65°F"}]),
]
# Add messages to thread
@@ -284,7 +284,7 @@ async def test_list_items_converts_function_calls():
@pytest.mark.asyncio
async def test_list_items_handles_images_and_files():
"""Test that list_items properly converts data content (images/files) to OpenAI types."""
from agent_framework import ChatMessage, ChatMessageStore
from agent_framework import ChatMessageStore, Message
store = InMemoryConversationStore()
@@ -300,7 +300,7 @@ async def test_list_items_handles_images_and_files():
# Simulate message with image and file
messages = [
ChatMessage(
Message(
role="user",
contents=[
{"type": "text", "text": "Check this image and PDF"},
@@ -74,7 +74,7 @@ async def test_discovery_accepts_agents_with_only_run():
init_file = agent_dir / "__init__.py"
init_file.write_text("""
from agent_framework import AgentResponse, AgentThread, ChatMessage, Role, Content
from agent_framework import AgentResponse, AgentThread, Message, Role, Content
class NonStreamingAgent:
id = "non_streaming"
@@ -83,7 +83,7 @@ class NonStreamingAgent:
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentResponse(
messages=[ChatMessage(
messages=[Message(
role="assistant",
contents=[Content.from_text(text="response")]
)],
@@ -188,14 +188,14 @@ workflow = WorkflowBuilder(start_executor=executor).build()
agent_dir = temp_path / "my_agent"
agent_dir.mkdir()
(agent_dir / "agent.py").write_text("""
from agent_framework import AgentResponse, AgentThread, ChatMessage, Role, TextContent
from agent_framework import AgentResponse, AgentThread, Message, Role, TextContent
class TestAgent:
name = "Test Agent"
async def run(self, messages=None, *, thread=None, **kwargs):
return AgentResponse(
messages=[ChatMessage(role="assistant", contents=[Content.from_text(text="test")])],
messages=[Message(role="assistant", contents=[Content.from_text(text="test")])],
response_id="test"
)
@@ -4,7 +4,7 @@
Tests include:
- Entity discovery and info retrieval
- Agent execution (sync and streaming) using real ChatAgent with mock LLM
- Agent execution (sync and streaming) using real Agent with mock LLM
- Workflow execution using real WorkflowBuilder with FunctionExecutor
- Edge cases like non-streaming agents
"""
@@ -15,7 +15,7 @@ from pathlib import Path
from typing import Any
import pytest
from agent_framework import AgentExecutor, ChatAgent, FunctionExecutor, WorkflowBuilder
from agent_framework import Agent, AgentExecutor, FunctionExecutor, WorkflowBuilder
# Import mock classes from conftest for direct use in some tests
from conftest import MockBaseChatClient
@@ -77,15 +77,15 @@ async def test_executor_get_entity_info(executor):
# =============================================================================
# Agent Execution Tests (using real ChatAgent with mock LLM)
# Agent Execution Tests (using real Agent with mock LLM)
# =============================================================================
async def test_agent_sync_execution(executor_with_real_agent):
"""Test synchronous agent execution with REAL ChatAgent (mock LLM).
"""Test synchronous agent execution with REAL Agent (mock LLM).
This tests the full execution pipeline without needing an API key:
- Real ChatAgent class with middleware
- Real Agent class with middleware
- Real message normalization
- Mock chat client for LLM calls
"""
@@ -130,7 +130,7 @@ async def test_agent_sync_execution_respects_model_field(executor_with_real_agen
async def test_chat_client_receives_correct_messages(executor_with_real_agent):
"""Verify the mock chat client receives properly formatted messages.
This tests that the REAL ChatAgent properly:
This tests that the REAL Agent properly:
- Normalizes input messages
- Formats messages for the chat client
"""
@@ -297,18 +297,18 @@ async def test_full_pipeline_workflow_events_are_json_serializable():
This is particularly important for workflows with AgentExecutor because:
- AgentExecutor produces executor_completed event (type='executor_completed') with AgentExecutorResponse
- AgentExecutorResponse contains AgentResponse and ChatMessage objects
- AgentExecutorResponse contains AgentResponse and Message objects
- These are SerializationMixin objects, not Pydantic, which caused the original bug
This test ensures the ENTIRE streaming pipeline works end-to-end.
"""
# Create a workflow with AgentExecutor (the problematic case)
mock_client = MockBaseChatClient()
agent = ChatAgent(
agent = Agent(
id="serialization_test_agent",
name="Serialization Test Agent",
description="Agent for testing serialization",
chat_client=mock_client,
client=mock_client,
system_message="You are a test assistant.",
)
@@ -466,15 +466,15 @@ async def test_executor_parse_raw_string_for_string_workflow():
@pytest.mark.asyncio
async def test_executor_parse_converts_to_chat_message_for_sequential_workflow(sequential_workflow):
"""Sequential workflows convert string input to ChatMessage."""
from agent_framework import ChatMessage
"""Sequential workflows convert string input to Message."""
from agent_framework import Message
executor, _entity_id, _mock_client, workflow = sequential_workflow
# Sequential workflows expect ChatMessage, so raw string becomes ChatMessage
# Sequential workflows expect Message, so raw string becomes Message
parsed = executor._parse_raw_workflow_input(workflow, "hello")
assert isinstance(parsed, ChatMessage)
assert isinstance(parsed, Message)
assert parsed.text == "hello"
@@ -538,7 +538,7 @@ def test_extract_workflow_hil_responses_handles_stringified_json():
async def test_executor_handles_streaming_agent():
"""Test executor handles agents with run(stream=True) method."""
from agent_framework import AgentResponse, AgentResponseUpdate, AgentThread, ChatMessage, Content
from agent_framework import AgentResponse, AgentResponseUpdate, AgentThread, Content, Message
class StreamingAgent:
"""Agent with run() method supporting stream parameter."""
@@ -556,7 +556,7 @@ async def test_executor_handles_streaming_agent():
async def _run_impl(self, messages):
return AgentResponse(
messages=[ChatMessage(role="assistant", contents=[Content.from_text(text=f"Processed: {messages}")])],
messages=[Message(role="assistant", contents=[Content.from_text(text=f"Processed: {messages}")])],
response_id="test_123",
)
@@ -304,7 +304,7 @@ async def test_executor_completed_event_with_agent_response(
This is a REGRESSION TEST for the serialization bug where
WorkflowEvent.data contained AgentExecutorResponse with nested
AgentResponse and ChatMessage objects (SerializationMixin) that
AgentResponse and Message objects (SerializationMixin) that
Pydantic couldn't serialize.
"""
# Create event with realistic nested data - the exact structure that caused the bug
@@ -579,13 +579,13 @@ async def test_workflow_output_event(mapper: MessageMapper, test_request: AgentF
async def test_workflow_output_event_with_list_data(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test output event (type='output') with list data (common for sequential/concurrent workflows)."""
from agent_framework import ChatMessage
from agent_framework import Message
from agent_framework._workflows._events import WorkflowEvent
# Sequential/Concurrent workflows often output list[ChatMessage]
# Sequential/Concurrent workflows often output list[Message]
messages = [
ChatMessage(role="user", contents=[Content.from_text(text="Hello")]),
ChatMessage(role="assistant", contents=[Content.from_text(text="World")]),
Message(role="user", contents=[Content.from_text(text="Hello")]),
Message(role="assistant", contents=[Content.from_text(text="World")]),
]
event = WorkflowEvent.output(executor_id="complete", data=messages)
events = await mapper.convert_event(event, test_request)
@@ -48,8 +48,8 @@ class TestMultimodalWorkflowInput:
assert executor._is_openai_multimodal_format([{"foo": "bar"}]) is False # no type field
def test_convert_openai_input_to_chat_message_with_image(self):
"""Test that OpenAI format with image is converted to ChatMessage with DataContent."""
from agent_framework import ChatMessage
"""Test that OpenAI format with image is converted to Message with DataContent."""
from agent_framework import Message
discovery = MagicMock(spec=EntityDiscovery)
mapper = MagicMock(spec=MessageMapper)
@@ -67,11 +67,11 @@ class TestMultimodalWorkflowInput:
}
]
# Convert to ChatMessage
# Convert to Message
result = executor._convert_input_to_chat_message(openai_input)
# Verify result is ChatMessage
assert isinstance(result, ChatMessage), f"Expected ChatMessage, got {type(result)}"
# Verify result is Message
assert isinstance(result, Message), f"Expected Message, got {type(result)}"
assert result.role == "user"
# Verify contents
@@ -89,7 +89,7 @@ class TestMultimodalWorkflowInput:
async def test_parse_workflow_input_handles_json_string_with_multimodal(self):
"""Test that _parse_workflow_input correctly handles JSON string with multimodal content."""
from agent_framework import ChatMessage
from agent_framework import Message
discovery = MagicMock(spec=EntityDiscovery)
mapper = MagicMock(spec=MessageMapper)
@@ -114,8 +114,8 @@ class TestMultimodalWorkflowInput:
# Parse the input
result = await executor._parse_workflow_input(mock_workflow, json_string_input)
# Verify result is ChatMessage with multimodal content
assert isinstance(result, ChatMessage), f"Expected ChatMessage, got {type(result)}"
# Verify result is Message with multimodal content
assert isinstance(result, Message), f"Expected Message, got {type(result)}"
assert len(result.contents) == 2
# Verify text content
@@ -129,7 +129,7 @@ class TestMultimodalWorkflowInput:
async def test_parse_workflow_input_still_handles_simple_dict(self):
"""Test that simple dict input still works (backward compatibility)."""
from agent_framework import ChatMessage
from agent_framework import Message
discovery = MagicMock(spec=EntityDiscovery)
mapper = MagicMock(spec=MessageMapper)
@@ -139,14 +139,14 @@ class TestMultimodalWorkflowInput:
simple_input = {"text": "Hello world", "role": "user"}
json_string_input = json.dumps(simple_input)
# Mock workflow with ChatMessage input type
# Mock workflow with Message input type
mock_workflow = MagicMock()
mock_executor = MagicMock()
mock_executor.input_types = [ChatMessage]
mock_executor.input_types = [Message]
mock_workflow.get_start_executor.return_value = mock_executor
# Parse the input
result = await executor._parse_workflow_input(mock_workflow, json_string_input)
# Result should be ChatMessage (from _parse_structured_workflow_input)
assert isinstance(result, ChatMessage), f"Expected ChatMessage, got {type(result)}"
# Result should be Message (from _parse_structured_workflow_input)
assert isinstance(result, Message), f"Expected Message, got {type(result)}"
@@ -67,16 +67,16 @@ def test_dataclass_schema_generation():
def test_chat_message_schema_generation():
"""Test schema generation for ChatMessage (SerializationMixin)."""
"""Test schema generation for Message (SerializationMixin)."""
try:
from agent_framework import ChatMessage
from agent_framework import Message
schema = generate_input_schema(ChatMessage)
schema = generate_input_schema(Message)
assert schema is not None
assert isinstance(schema, dict)
except ImportError:
pytest.skip("ChatMessage not available - agent_framework not installed")
pytest.skip("Message not available - agent_framework not installed")
def test_pydantic_model_schema_generation():
@@ -142,7 +142,7 @@ async def test_credential_cleanup() -> None:
"""Test that async credentials are properly closed during server cleanup."""
from unittest.mock import AsyncMock, Mock
from agent_framework import ChatAgent
from agent_framework import Agent
# Create mock credential with async close
mock_credential = AsyncMock()
@@ -155,7 +155,7 @@ async def test_credential_cleanup() -> None:
mock_client.function_invocation_configuration = None
# Create agent with mock client
agent = ChatAgent(name="TestAgent", chat_client=mock_client, instructions="Test agent")
agent = Agent(name="TestAgent", client=mock_client, instructions="Test agent")
# Create DevUI server with agent
server = DevServer()
@@ -175,7 +175,7 @@ async def test_credential_cleanup_error_handling() -> None:
"""Test that credential cleanup errors are handled gracefully."""
from unittest.mock import AsyncMock, Mock
from agent_framework import ChatAgent
from agent_framework import Agent
# Create mock credential that raises error on close
mock_credential = AsyncMock()
@@ -188,7 +188,7 @@ async def test_credential_cleanup_error_handling() -> None:
mock_client.function_invocation_configuration = None
# Create agent with mock client
agent = ChatAgent(name="TestAgent", chat_client=mock_client, instructions="Test agent")
agent = Agent(name="TestAgent", client=mock_client, instructions="Test agent")
# Create DevUI server with agent
server = DevServer()
@@ -207,7 +207,7 @@ async def test_multiple_credential_attributes() -> None:
"""Test that we check all common credential attribute names."""
from unittest.mock import AsyncMock, Mock
from agent_framework import ChatAgent
from agent_framework import Agent
# Create mock credentials
mock_cred1 = Mock()
@@ -223,7 +223,7 @@ async def test_multiple_credential_attributes() -> None:
mock_client.function_invocation_configuration = None
# Create agent with mock client
agent = ChatAgent(name="TestAgent", chat_client=mock_client, instructions="Test agent")
agent = Agent(name="TestAgent", client=mock_client, instructions="Test agent")
# Create DevUI server with agent
server = DevServer()