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:
co-authored by
Evan Mattson
parent
a4c9e43afb
commit
0521f5bed8
@@ -18,7 +18,6 @@ from a2a.types import (
|
||||
FilePart,
|
||||
FileWithBytes,
|
||||
FileWithUri,
|
||||
Message,
|
||||
Task,
|
||||
TaskIdParams,
|
||||
TaskQueryParams,
|
||||
@@ -34,9 +33,9 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Content,
|
||||
ContinuationToken,
|
||||
Message,
|
||||
ResponseStream,
|
||||
normalize_messages,
|
||||
prepend_agent_framework_to_user_agent,
|
||||
@@ -83,7 +82,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
"""Agent2Agent (A2A) protocol implementation.
|
||||
|
||||
Wraps an A2A Client to connect the Agent Framework with external A2A-compliant agents
|
||||
via HTTP/JSON-RPC. Converts framework ChatMessages to A2A Messages on send, and converts
|
||||
via HTTP/JSON-RPC. Converts framework Messages to A2A Messages on send, and converts
|
||||
A2A responses (Messages/Tasks) back to framework types. Inherits BaseAgent capabilities
|
||||
while managing the underlying A2A protocol communication.
|
||||
|
||||
@@ -209,7 +208,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -221,7 +220,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -232,7 +231,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -268,7 +267,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
|
||||
response = ResponseStream(
|
||||
self._map_a2a_stream(a2a_stream, background=background),
|
||||
finalizer=lambda updates: AgentResponse.from_updates(list(updates)),
|
||||
finalizer=AgentResponse.from_updates,
|
||||
)
|
||||
if stream:
|
||||
return response
|
||||
@@ -291,7 +290,8 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
When True, they are yielded with a continuation token.
|
||||
"""
|
||||
async for item in a2a_stream:
|
||||
if isinstance(item, Message):
|
||||
if isinstance(item, A2AMessage):
|
||||
# Process A2A Message
|
||||
contents = self._parse_contents_from_a2a(item.parts)
|
||||
yield AgentResponseUpdate(
|
||||
contents=contents,
|
||||
@@ -377,10 +377,10 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
return AgentResponse.from_updates(updates)
|
||||
return AgentResponse(messages=[], response_id=task.id, raw_representation=task)
|
||||
|
||||
def _prepare_message_for_a2a(self, message: ChatMessage) -> A2AMessage:
|
||||
"""Prepare a ChatMessage for the A2A protocol.
|
||||
def _prepare_message_for_a2a(self, message: Message) -> A2AMessage:
|
||||
"""Prepare a Message for the A2A protocol.
|
||||
|
||||
Transforms Agent Framework ChatMessage objects into A2A protocol Messages by:
|
||||
Transforms Agent Framework Message objects into A2A protocol Messages by:
|
||||
- Converting all message contents to appropriate A2A Part types
|
||||
- Mapping text content to TextPart objects
|
||||
- Converting file references (URI/data/hosted_file) to FilePart objects
|
||||
@@ -389,7 +389,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
"""
|
||||
parts: list[A2APart] = []
|
||||
if not message.contents:
|
||||
raise ValueError("ChatMessage.contents is empty; cannot convert to A2AMessage.")
|
||||
raise ValueError("Message.contents is empty; cannot convert to A2AMessage.")
|
||||
|
||||
# Process ALL contents
|
||||
for content in message.contents:
|
||||
@@ -511,9 +511,9 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
raise ValueError(f"Unknown Part kind: {inner_part.kind}")
|
||||
return contents
|
||||
|
||||
def _parse_messages_from_task(self, task: Task) -> list[ChatMessage]:
|
||||
"""Parse A2A Task artifacts into ChatMessages with ASSISTANT role."""
|
||||
messages: list[ChatMessage] = []
|
||||
def _parse_messages_from_task(self, task: Task) -> list[Message]:
|
||||
"""Parse A2A Task artifacts into Messages with ASSISTANT role."""
|
||||
messages: list[Message] = []
|
||||
|
||||
if task.artifacts is not None:
|
||||
for artifact in task.artifacts:
|
||||
@@ -523,7 +523,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
history_item = task.history[-1]
|
||||
contents = self._parse_contents_from_a2a(history_item.parts)
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant" if history_item.role == A2ARole.agent else "user",
|
||||
contents=contents,
|
||||
raw_representation=history_item,
|
||||
@@ -532,10 +532,10 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
|
||||
|
||||
return messages
|
||||
|
||||
def _parse_message_from_artifact(self, artifact: Artifact) -> ChatMessage:
|
||||
"""Parse A2A Artifact into ChatMessage using part contents."""
|
||||
def _parse_message_from_artifact(self, artifact: Artifact) -> Message:
|
||||
"""Parse A2A Artifact into Message using part contents."""
|
||||
contents = self._parse_contents_from_a2a(artifact.parts)
|
||||
return ChatMessage(
|
||||
return Message(
|
||||
role="assistant",
|
||||
contents=contents,
|
||||
raw_representation=artifact,
|
||||
|
||||
@@ -12,19 +12,19 @@ from a2a.types import (
|
||||
DataPart,
|
||||
FilePart,
|
||||
FileWithUri,
|
||||
Message,
|
||||
Part,
|
||||
Task,
|
||||
TaskState,
|
||||
TaskStatus,
|
||||
TextPart,
|
||||
)
|
||||
from a2a.types import Message as A2AMessage
|
||||
from a2a.types import Role as A2ARole
|
||||
from agent_framework import (
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
)
|
||||
from agent_framework.a2a import A2AAgent
|
||||
from pytest import fixture, raises
|
||||
@@ -49,7 +49,7 @@ class MockA2AClient:
|
||||
text_part = Part(root=TextPart(text=text))
|
||||
|
||||
# Create actual Message instance
|
||||
message = Message(
|
||||
message = A2AMessage(
|
||||
message_id=message_id, role=A2ARole.agent if role == "agent" else A2ARole.user, parts=[text_part]
|
||||
)
|
||||
self.responses.append(message)
|
||||
@@ -281,7 +281,7 @@ def test_parse_message_from_artifact(a2a_agent: A2AAgent) -> None:
|
||||
|
||||
result = a2a_agent._parse_message_from_artifact(artifact)
|
||||
|
||||
assert isinstance(result, ChatMessage)
|
||||
assert isinstance(result, Message)
|
||||
assert result.role == "assistant"
|
||||
assert result.text == "Artifact content"
|
||||
assert result.raw_representation == artifact
|
||||
@@ -324,9 +324,9 @@ def test_parse_contents_from_a2a_conversion(a2a_agent: A2AAgent) -> None:
|
||||
def test_prepare_message_for_a2a_with_error_content(a2a_agent: A2AAgent) -> None:
|
||||
"""Test _prepare_message_for_a2a with ErrorContent."""
|
||||
|
||||
# Create ChatMessage with ErrorContent
|
||||
# Create Message with ErrorContent
|
||||
error_content = Content.from_error(message="Test error message")
|
||||
message = ChatMessage(role="user", contents=[error_content])
|
||||
message = Message(role="user", contents=[error_content])
|
||||
|
||||
# Convert to A2A message
|
||||
a2a_message = a2a_agent._prepare_message_for_a2a(message)
|
||||
@@ -339,9 +339,9 @@ def test_prepare_message_for_a2a_with_error_content(a2a_agent: A2AAgent) -> None
|
||||
def test_prepare_message_for_a2a_with_uri_content(a2a_agent: A2AAgent) -> None:
|
||||
"""Test _prepare_message_for_a2a with UriContent."""
|
||||
|
||||
# Create ChatMessage with UriContent
|
||||
# Create Message with UriContent
|
||||
uri_content = Content.from_uri(uri="http://example.com/file.pdf", media_type="application/pdf")
|
||||
message = ChatMessage(role="user", contents=[uri_content])
|
||||
message = Message(role="user", contents=[uri_content])
|
||||
|
||||
# Convert to A2A message
|
||||
a2a_message = a2a_agent._prepare_message_for_a2a(message)
|
||||
@@ -355,9 +355,9 @@ def test_prepare_message_for_a2a_with_uri_content(a2a_agent: A2AAgent) -> None:
|
||||
def test_prepare_message_for_a2a_with_data_content(a2a_agent: A2AAgent) -> None:
|
||||
"""Test _prepare_message_for_a2a with DataContent."""
|
||||
|
||||
# Create ChatMessage with DataContent (base64 data URI)
|
||||
# Create Message with DataContent (base64 data URI)
|
||||
data_content = Content.from_uri(uri="data:text/plain;base64,SGVsbG8gV29ybGQ=", media_type="text/plain")
|
||||
message = ChatMessage(role="user", contents=[data_content])
|
||||
message = Message(role="user", contents=[data_content])
|
||||
|
||||
# Convert to A2A message
|
||||
a2a_message = a2a_agent._prepare_message_for_a2a(message)
|
||||
@@ -370,11 +370,11 @@ def test_prepare_message_for_a2a_with_data_content(a2a_agent: A2AAgent) -> None:
|
||||
|
||||
def test_prepare_message_for_a2a_empty_contents_raises_error(a2a_agent: A2AAgent) -> None:
|
||||
"""Test _prepare_message_for_a2a with empty contents raises ValueError."""
|
||||
# Create ChatMessage with no contents
|
||||
message = ChatMessage(role="user", contents=[])
|
||||
# Create Message with no contents
|
||||
message = Message(role="user", contents=[])
|
||||
|
||||
# Should raise ValueError for empty contents
|
||||
with raises(ValueError, match="ChatMessage.contents is empty"):
|
||||
with raises(ValueError, match="Message.contents is empty"):
|
||||
a2a_agent._prepare_message_for_a2a(message)
|
||||
|
||||
|
||||
@@ -432,12 +432,12 @@ async def test_context_manager_no_cleanup_when_no_http_client() -> None:
|
||||
|
||||
|
||||
def test_prepare_message_for_a2a_with_multiple_contents() -> None:
|
||||
"""Test conversion of ChatMessage with multiple contents."""
|
||||
"""Test conversion of Message with multiple contents."""
|
||||
|
||||
agent = A2AAgent(client=MagicMock(), _http_client=None)
|
||||
|
||||
# Create message with multiple content types
|
||||
message = ChatMessage(
|
||||
message = Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text(text="Here's the analysis:"),
|
||||
@@ -489,12 +489,12 @@ def test_parse_contents_from_a2a_unknown_part_kind() -> None:
|
||||
|
||||
|
||||
def test_prepare_message_for_a2a_with_hosted_file() -> None:
|
||||
"""Test conversion of ChatMessage with HostedFileContent to A2A message."""
|
||||
"""Test conversion of Message with HostedFileContent to A2A message."""
|
||||
|
||||
agent = A2AAgent(client=MagicMock(), _http_client=None)
|
||||
|
||||
# Create message with hosted file content
|
||||
message = ChatMessage(
|
||||
message = Message(
|
||||
role="user",
|
||||
contents=[Content.from_hosted_file(file_id="hosted://storage/document.pdf")],
|
||||
)
|
||||
|
||||
@@ -14,15 +14,15 @@ pip install agent-framework-ag-ui
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
|
||||
# Create your agent
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="my_agent",
|
||||
instructions="You are a helpful assistant.",
|
||||
chat_client=AzureOpenAIChatClient(
|
||||
client=AzureOpenAIChatClient(
|
||||
endpoint="https://your-resource.openai.azure.com/",
|
||||
deployment_name="gpt-4o-mini",
|
||||
api_key="your-api-key",
|
||||
@@ -58,7 +58,7 @@ The `AGUIChatClient` supports:
|
||||
- Streaming and non-streaming responses
|
||||
- Hybrid tool execution (client-side + server-side tools)
|
||||
- Automatic thread management for conversation continuity
|
||||
- Integration with `ChatAgent` for client-side history management
|
||||
- Integration with `Agent` for client-side history management
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -91,7 +91,7 @@ The AG-UI endpoint does not enforce authentication by default. **For production
|
||||
import os
|
||||
from fastapi import Depends, FastAPI, HTTPException, Security
|
||||
from fastapi.security import APIKeyHeader
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
|
||||
# Configure API key authentication
|
||||
@@ -104,7 +104,7 @@ async def verify_api_key(api_key: str | None = Security(API_KEY_HEADER)) -> None
|
||||
raise HTTPException(status_code=401, detail="Invalid or missing API key")
|
||||
|
||||
# Create agent and app
|
||||
agent = ChatAgent(name="my_agent", instructions="...", chat_client=...)
|
||||
agent = Agent(name="my_agent", instructions="...", client=...)
|
||||
app = FastAPI()
|
||||
|
||||
# Register endpoint WITH authentication
|
||||
|
||||
@@ -15,11 +15,11 @@ from typing import TYPE_CHECKING, Any, Generic, TypedDict, cast
|
||||
import httpx
|
||||
from agent_framework import (
|
||||
BaseChatClient,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
FunctionTool,
|
||||
Message,
|
||||
ResponseStream,
|
||||
)
|
||||
from agent_framework._middleware import ChatMiddlewareLayer
|
||||
@@ -69,10 +69,10 @@ AGUIChatOptionsT = TypeVar(
|
||||
)
|
||||
|
||||
|
||||
def _apply_server_function_call_unwrap(chat_client: BaseChatClientT) -> BaseChatClientT:
|
||||
def _apply_server_function_call_unwrap(client: BaseChatClientT) -> BaseChatClientT:
|
||||
"""Class decorator that unwraps server-side function calls after tool handling."""
|
||||
|
||||
original_get_response = chat_client.get_response
|
||||
original_get_response = client.get_response
|
||||
|
||||
@wraps(original_get_response)
|
||||
def response_wrapper(
|
||||
@@ -105,8 +105,8 @@ def _apply_server_function_call_unwrap(chat_client: BaseChatClientT) -> BaseChat
|
||||
_unwrap_server_function_call_contents(cast(MutableSequence[Content | dict[str, Any]], update.contents))
|
||||
return update
|
||||
|
||||
chat_client.get_response = response_wrapper # type: ignore[assignment]
|
||||
return chat_client
|
||||
client.get_response = response_wrapper # type: ignore[assignment]
|
||||
return client
|
||||
|
||||
|
||||
@_apply_server_function_call_unwrap
|
||||
@@ -130,8 +130,8 @@ class AGUIChatClient(
|
||||
This client sends exactly the messages it receives to the server. It does NOT
|
||||
automatically maintain conversation history. The server must handle history via thread_id.
|
||||
|
||||
For stateless servers: Use ChatAgent wrapper which will send full message history on each
|
||||
request. However, even with ChatAgent, the server must echo back all context for the
|
||||
For stateless servers: Use Agent wrapper which will send full message history on each
|
||||
request. However, even with Agent, the server must echo back all context for the
|
||||
agent to maintain history across turns.
|
||||
|
||||
Important: Tool Handling (Hybrid Execution - matches .NET)
|
||||
@@ -140,7 +140,7 @@ class AGUIChatClient(
|
||||
3. When LLM calls a client tool, function invocation executes it locally
|
||||
4. Both client and server tools work together (hybrid pattern)
|
||||
|
||||
The wrapping ChatAgent's function invocation handles client tool execution
|
||||
The wrapping Agent's function invocation handles client tool execution
|
||||
automatically when the server's LLM decides to call them.
|
||||
|
||||
Examples:
|
||||
@@ -162,18 +162,18 @@ class AGUIChatClient(
|
||||
metadata={"thread_id": thread_id}
|
||||
)
|
||||
|
||||
Recommended usage with ChatAgent (client manages history):
|
||||
Recommended usage with Agent (client manages history):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.ag_ui import AGUIChatClient
|
||||
|
||||
client = AGUIChatClient(endpoint="http://localhost:8888/")
|
||||
agent = ChatAgent(name="assistant", client=client)
|
||||
agent = Agent(name="assistant", client=client)
|
||||
thread = await agent.get_new_thread()
|
||||
|
||||
# ChatAgent automatically maintains history and sends full context
|
||||
# Agent automatically maintains history and sends full context
|
||||
response = await agent.run("Hello!", thread=thread)
|
||||
response2 = await agent.run("How are you?", thread=thread)
|
||||
|
||||
@@ -282,9 +282,7 @@ class AGUIChatClient(
|
||||
logger = get_logger()
|
||||
logger.debug(f"[AGUIChatClient] Registered server placeholder: {tool_name}")
|
||||
|
||||
def _extract_state_from_messages(
|
||||
self, messages: Sequence[ChatMessage]
|
||||
) -> tuple[list[ChatMessage], dict[str, Any] | None]:
|
||||
def _extract_state_from_messages(self, messages: Sequence[Message]) -> tuple[list[Message], dict[str, Any] | None]:
|
||||
"""Extract state from last message if present.
|
||||
|
||||
Args:
|
||||
@@ -319,11 +317,11 @@ class AGUIChatClient(
|
||||
|
||||
return list(messages), None
|
||||
|
||||
def _convert_messages_to_agui_format(self, messages: list[ChatMessage]) -> list[dict[str, Any]]:
|
||||
def _convert_messages_to_agui_format(self, messages: list[Message]) -> list[dict[str, Any]]:
|
||||
"""Convert Agent Framework messages to AG-UI format.
|
||||
|
||||
Args:
|
||||
messages: List of ChatMessage objects
|
||||
messages: List of Message objects
|
||||
|
||||
Returns:
|
||||
List of AG-UI formatted message dictionaries
|
||||
@@ -353,7 +351,7 @@ class AGUIChatClient(
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
stream: bool = False,
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
@@ -393,7 +391,7 @@ class AGUIChatClient(
|
||||
async def _streaming_impl(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterable[ChatResponseUpdate]:
|
||||
@@ -415,7 +413,7 @@ class AGUIChatClient(
|
||||
agui_messages = self._convert_messages_to_agui_format(messages_to_send)
|
||||
|
||||
# Send client tools to server so LLM knows about them
|
||||
# Client tools execute via ChatAgent's function invocation wrapper
|
||||
# Client tools execute via Agent's function invocation wrapper
|
||||
agui_tools = convert_tools_to_agui_format(options.get("tools"))
|
||||
|
||||
# Build set of client tool names (matches .NET clientToolSet)
|
||||
|
||||
@@ -9,8 +9,8 @@ import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
prepare_function_call_results,
|
||||
)
|
||||
|
||||
@@ -25,9 +25,9 @@ from ._utils import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
def _sanitize_tool_history(messages: list[Message]) -> list[Message]:
|
||||
"""Normalize tool ordering and inject synthetic results for AG-UI edge cases."""
|
||||
sanitized: list[ChatMessage] = []
|
||||
sanitized: list[Message] = []
|
||||
pending_tool_call_ids: set[str] | None = None
|
||||
pending_confirm_changes_id: str | None = None
|
||||
|
||||
@@ -60,7 +60,7 @@ def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
]
|
||||
if filtered_contents:
|
||||
# Create a new message without confirm_changes to avoid mutating the input
|
||||
filtered_msg = ChatMessage(role=msg.role, contents=filtered_contents)
|
||||
filtered_msg = Message(role=msg.role, contents=filtered_contents)
|
||||
sanitized.append(filtered_msg)
|
||||
# If no contents left after filtering, don't append anything
|
||||
|
||||
@@ -99,7 +99,7 @@ def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
|
||||
if pending_confirm_changes_id and approval_accepted is not None:
|
||||
logger.info(f"Injecting synthetic tool result for confirm_changes call_id={pending_confirm_changes_id}")
|
||||
synthetic_result = ChatMessage(
|
||||
synthetic_result = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
@@ -128,7 +128,7 @@ def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
logger.info(
|
||||
f"Injecting synthetic tool result for confirm_changes call_id={pending_confirm_changes_id}"
|
||||
)
|
||||
synthetic_result = ChatMessage(
|
||||
synthetic_result = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
@@ -152,7 +152,7 @@ def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
)
|
||||
for pending_call_id in pending_tool_call_ids:
|
||||
logger.info(f"Injecting synthetic tool result for pending call_id={pending_call_id}")
|
||||
synthetic_result = ChatMessage(
|
||||
synthetic_result = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
@@ -196,10 +196,10 @@ def _sanitize_tool_history(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
return sanitized
|
||||
|
||||
|
||||
def _deduplicate_messages(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
def _deduplicate_messages(messages: list[Message]) -> list[Message]:
|
||||
"""Remove duplicate messages while preserving order."""
|
||||
seen_keys: dict[Any, int] = {}
|
||||
unique_messages: list[ChatMessage] = []
|
||||
unique_messages: list[Message] = []
|
||||
|
||||
for idx, msg in enumerate(messages):
|
||||
role_value = get_role_value(msg)
|
||||
@@ -256,7 +256,7 @@ def _deduplicate_messages(messages: list[ChatMessage]) -> list[ChatMessage]:
|
||||
|
||||
def normalize_agui_input_messages(
|
||||
messages: list[dict[str, Any]],
|
||||
) -> tuple[list[ChatMessage], list[dict[str, Any]]]:
|
||||
) -> tuple[list[Message], list[dict[str, Any]]]:
|
||||
"""Normalize raw AG-UI messages into provider and snapshot formats."""
|
||||
provider_messages = agui_messages_to_agent_framework(messages)
|
||||
provider_messages = _sanitize_tool_history(provider_messages)
|
||||
@@ -265,14 +265,14 @@ def normalize_agui_input_messages(
|
||||
return provider_messages, snapshot_messages
|
||||
|
||||
|
||||
def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[ChatMessage]:
|
||||
def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Message]:
|
||||
"""Convert AG-UI messages to Agent Framework format.
|
||||
|
||||
Args:
|
||||
messages: List of AG-UI messages
|
||||
|
||||
Returns:
|
||||
List of Agent Framework ChatMessage objects
|
||||
List of Agent Framework Message objects
|
||||
"""
|
||||
|
||||
def _update_tool_call_arguments(
|
||||
@@ -367,7 +367,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
allowed_keys = set(original_args.keys())
|
||||
return {key: value for key, value in modified_args.items() if key in allowed_keys}
|
||||
|
||||
result: list[ChatMessage] = []
|
||||
result: list[Message] = []
|
||||
for msg in messages:
|
||||
# Handle standard tool result messages early (role="tool") to preserve provider invariants
|
||||
# This path maps AG‑UI tool messages to function_result content with the correct tool_call_id
|
||||
@@ -480,7 +480,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
merged_args["steps"] = merged_steps
|
||||
state_args = merged_args
|
||||
|
||||
# Update the ChatMessage tool call with only enabled steps (for LLM context).
|
||||
# Update the Message tool call with only enabled steps (for LLM context).
|
||||
# The LLM should only see the steps that were actually approved/executed.
|
||||
updated_args_for_llm = (
|
||||
json.dumps(filtered_args)
|
||||
@@ -510,14 +510,14 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
function_call=func_call_for_approval,
|
||||
additional_properties={"ag_ui_state_args": state_args} if state_args else None,
|
||||
)
|
||||
chat_msg = ChatMessage(
|
||||
chat_msg = Message(
|
||||
role="user",
|
||||
contents=[approval_response],
|
||||
)
|
||||
else:
|
||||
# No matching function call found - this is likely a confirm_changes approval
|
||||
# Keep the old behavior for backwards compatibility
|
||||
chat_msg = ChatMessage(
|
||||
chat_msg = Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text=approval_payload_text)],
|
||||
additional_properties={"is_tool_result": True, "tool_call_id": str(tool_call_id or "")},
|
||||
@@ -537,7 +537,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
func_result = result_content
|
||||
else:
|
||||
func_result = str(result_content)
|
||||
chat_msg = ChatMessage(
|
||||
chat_msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id=str(tool_call_id), result=func_result)],
|
||||
)
|
||||
@@ -553,7 +553,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
tool_call_id = msg.get("toolCallId") or msg.get("tool_call_id") or msg.get("actionExecutionId", "")
|
||||
result_content = msg.get("result", msg.get("content", ""))
|
||||
|
||||
chat_msg = ChatMessage(
|
||||
chat_msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id=str(tool_call_id), result=result_content)],
|
||||
)
|
||||
@@ -592,7 +592,7 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
arguments=arguments,
|
||||
)
|
||||
)
|
||||
chat_msg = ChatMessage(role="assistant", contents=contents)
|
||||
chat_msg = Message(role="assistant", contents=contents)
|
||||
if "id" in msg:
|
||||
chat_msg.message_id = msg["id"]
|
||||
result.append(chat_msg)
|
||||
@@ -622,14 +622,14 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
)
|
||||
approval_contents.append(approval_response)
|
||||
|
||||
chat_msg = ChatMessage(role=role, contents=approval_contents) # type: ignore[call-overload]
|
||||
chat_msg = Message(role=role, contents=approval_contents) # type: ignore[call-overload]
|
||||
else:
|
||||
# Regular text message
|
||||
content = msg.get("content", "")
|
||||
if isinstance(content, str):
|
||||
chat_msg = ChatMessage(role=role, contents=[Content.from_text(text=content)]) # type: ignore[call-overload]
|
||||
chat_msg = Message(role=role, contents=[Content.from_text(text=content)]) # type: ignore[call-overload]
|
||||
else:
|
||||
chat_msg = ChatMessage(role=role, contents=[Content.from_text(text=str(content))]) # type: ignore[call-overload]
|
||||
chat_msg = Message(role=role, contents=[Content.from_text(text=str(content))]) # type: ignore[call-overload]
|
||||
|
||||
if "id" in msg:
|
||||
chat_msg.message_id = msg["id"]
|
||||
@@ -639,11 +639,11 @@ def agui_messages_to_agent_framework(messages: list[dict[str, Any]]) -> list[Cha
|
||||
return result
|
||||
|
||||
|
||||
def agent_framework_messages_to_agui(messages: list[ChatMessage] | list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
def agent_framework_messages_to_agui(messages: list[Message] | list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Convert Agent Framework messages to AG-UI format.
|
||||
|
||||
Args:
|
||||
messages: List of Agent Framework ChatMessage objects or AG-UI dicts (already converted)
|
||||
messages: List of Agent Framework Message objects or AG-UI dicts (already converted)
|
||||
|
||||
Returns:
|
||||
List of AG-UI message dictionaries
|
||||
@@ -672,7 +672,7 @@ def agent_framework_messages_to_agui(messages: list[ChatMessage] | list[dict[str
|
||||
result.append(normalized_msg)
|
||||
continue
|
||||
|
||||
# Convert ChatMessage to AG-UI format
|
||||
# Convert Message to AG-UI format
|
||||
role_value: str = msg.role if hasattr(msg.role, "value") else msg.role # type: ignore[assignment]
|
||||
role = FRAMEWORK_TO_AGUI_ROLE.get(role_value, "user")
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
)
|
||||
|
||||
from .._utils import get_role_value
|
||||
@@ -22,7 +22,7 @@ from .._utils import get_role_value
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def pending_tool_call_ids(messages: list[ChatMessage]) -> set[str]:
|
||||
def pending_tool_call_ids(messages: list[Message]) -> set[str]:
|
||||
"""Get IDs of tool calls without corresponding results.
|
||||
|
||||
Args:
|
||||
@@ -42,7 +42,7 @@ def pending_tool_call_ids(messages: list[ChatMessage]) -> set[str]:
|
||||
return pending_ids - resolved_ids
|
||||
|
||||
|
||||
def is_state_context_message(message: ChatMessage) -> bool:
|
||||
def is_state_context_message(message: Message) -> bool:
|
||||
"""Check if a message is a state context system message.
|
||||
|
||||
Args:
|
||||
@@ -178,7 +178,7 @@ def build_safe_metadata(thread_metadata: dict[str, Any] | None) -> dict[str, Any
|
||||
return safe_metadata
|
||||
|
||||
|
||||
def latest_approval_response(messages: list[ChatMessage]) -> Content | None:
|
||||
def latest_approval_response(messages: list[Message]) -> Content | None:
|
||||
"""Get the latest approval response from messages.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -39,7 +39,7 @@ def collect_server_tools(agent: SupportsAgentRun) -> list[Any]:
|
||||
functions need to be included for tool execution during approval flows.
|
||||
|
||||
Args:
|
||||
agent: Agent instance to collect tools from. Works with ChatAgent
|
||||
agent: Agent instance to collect tools from. Works with Agent
|
||||
or any agent with default_options and optional mcp_tools attributes.
|
||||
|
||||
Returns:
|
||||
@@ -53,7 +53,7 @@ def collect_server_tools(agent: SupportsAgentRun) -> list[Any]:
|
||||
tools_from_agent = default_options.get("tools") if isinstance(default_options, dict) else None
|
||||
server_tools = list(tools_from_agent) if tools_from_agent else []
|
||||
|
||||
# Include functions from connected MCP tools (only available on ChatAgent)
|
||||
# Include functions from connected MCP tools (only available on Agent)
|
||||
mcp_tools = getattr(agent, "mcp_tools", None)
|
||||
if mcp_tools:
|
||||
server_tools.extend(_collect_mcp_tool_functions(mcp_tools))
|
||||
@@ -70,19 +70,19 @@ def register_additional_client_tools(agent: SupportsAgentRun, client_tools: list
|
||||
"""Register client tools as additional declaration-only tools to avoid server execution.
|
||||
|
||||
Args:
|
||||
agent: Agent instance to register tools on. Works with ChatAgent
|
||||
or any agent with a chat_client attribute.
|
||||
agent: Agent instance to register tools on. Works with Agent
|
||||
or any agent with a client attribute.
|
||||
client_tools: List of client tools to register.
|
||||
"""
|
||||
if not client_tools:
|
||||
return
|
||||
|
||||
chat_client = getattr(agent, "chat_client", None)
|
||||
if chat_client is None:
|
||||
client = getattr(agent, "client", None)
|
||||
if client is None:
|
||||
return
|
||||
|
||||
if isinstance(chat_client, BaseChatClient) and chat_client.function_invocation_configuration is not None: # type: ignore[attr-defined]
|
||||
chat_client.function_invocation_configuration["additional_tools"] = client_tools # type: ignore[attr-defined]
|
||||
if isinstance(client, BaseChatClient) and client.function_invocation_configuration is not None: # type: ignore[attr-defined]
|
||||
client.function_invocation_configuration["additional_tools"] = client_tools # type: ignore[attr-defined]
|
||||
logger.debug(f"[TOOLS] Registered {len(client_tools)} client tools as additional_tools (declaration-only)")
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ from ag_ui.core import (
|
||||
)
|
||||
from agent_framework import (
|
||||
AgentThread,
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
SupportsAgentRun,
|
||||
prepare_function_call_results,
|
||||
)
|
||||
@@ -195,7 +195,7 @@ class FlowState:
|
||||
def _create_state_context_message(
|
||||
current_state: dict[str, Any],
|
||||
state_schema: dict[str, Any],
|
||||
) -> ChatMessage | None:
|
||||
) -> Message | None:
|
||||
"""Create a system message with current state context.
|
||||
|
||||
This injects the current state into the conversation so the model
|
||||
@@ -206,13 +206,13 @@ def _create_state_context_message(
|
||||
state_schema: The state schema (used to determine if injection is needed)
|
||||
|
||||
Returns:
|
||||
ChatMessage with state context, or None if not needed
|
||||
Message with state context, or None if not needed
|
||||
"""
|
||||
if not current_state or not state_schema:
|
||||
return None
|
||||
|
||||
state_json = json.dumps(current_state, indent=2)
|
||||
return ChatMessage(
|
||||
return Message(
|
||||
role="system",
|
||||
contents=[
|
||||
Content.from_text(
|
||||
@@ -229,10 +229,10 @@ def _create_state_context_message(
|
||||
|
||||
|
||||
def _inject_state_context(
|
||||
messages: list[ChatMessage],
|
||||
messages: list[Message],
|
||||
current_state: dict[str, Any],
|
||||
state_schema: dict[str, Any],
|
||||
) -> list[ChatMessage]:
|
||||
) -> list[Message]:
|
||||
"""Inject state context message into messages if appropriate.
|
||||
|
||||
The state context is injected before the last user message to give
|
||||
@@ -592,7 +592,7 @@ async def _resolve_approval_responses(
|
||||
Args:
|
||||
messages: List of messages (will be modified in place)
|
||||
tools: List of available tools
|
||||
agent: The agent instance (to get chat_client and config)
|
||||
agent: The agent instance (to get client and config)
|
||||
run_kwargs: Kwargs for tool execution
|
||||
"""
|
||||
fcc_todo = _collect_approval_responses(messages)
|
||||
@@ -605,12 +605,10 @@ async def _resolve_approval_responses(
|
||||
|
||||
# Execute approved tool calls
|
||||
if approved_responses and tools:
|
||||
chat_client = getattr(agent, "chat_client", None)
|
||||
config = normalize_function_invocation_configuration(
|
||||
getattr(chat_client, "function_invocation_configuration", None)
|
||||
)
|
||||
client = getattr(agent, "client", None)
|
||||
config = normalize_function_invocation_configuration(getattr(client, "function_invocation_configuration", None))
|
||||
middleware_pipeline = FunctionMiddlewarePipeline(
|
||||
*getattr(chat_client, "function_middleware", ()),
|
||||
*getattr(client, "function_middleware", ()),
|
||||
*run_kwargs.get("middleware", ()),
|
||||
)
|
||||
# Filter out AG-UI-specific kwargs that should not be passed to tool execution
|
||||
@@ -672,7 +670,7 @@ def _convert_approval_results_to_tool_messages(messages: list[Any]) -> None:
|
||||
This modifies the messages list in place.
|
||||
|
||||
Args:
|
||||
messages: List of ChatMessage objects to process
|
||||
messages: List of Message objects to process
|
||||
"""
|
||||
result: list[Any] = []
|
||||
|
||||
@@ -694,11 +692,11 @@ def _convert_approval_results_to_tool_messages(messages: list[Any]) -> None:
|
||||
|
||||
# Tool messages first (right after the preceding assistant message per OpenAI requirements)
|
||||
for func_result in function_results:
|
||||
result.append(ChatMessage(role="tool", contents=[func_result]))
|
||||
result.append(Message(role="tool", contents=[func_result]))
|
||||
|
||||
# Then user message with remaining content (if any)
|
||||
if other_contents:
|
||||
result.append(ChatMessage(role=msg.role, contents=other_contents))
|
||||
result.append(Message(role=msg.role, contents=other_contents))
|
||||
|
||||
messages[:] = result
|
||||
|
||||
@@ -793,9 +791,9 @@ async def run_agent_stream(
|
||||
# Check for structured output mode (skip text content)
|
||||
skip_text = False
|
||||
response_format = None
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
|
||||
if isinstance(agent, ChatAgent):
|
||||
if isinstance(agent, Agent):
|
||||
response_format = agent.default_options.get("response_format")
|
||||
skip_text = response_format is not None
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ pip install agent-framework-ag-ui
|
||||
|
||||
### Using Example Agents with Any Chat Client
|
||||
|
||||
All example agents are factory functions that accept any `ChatClientProtocol`-compatible chat client:
|
||||
All example agents are factory functions that accept any `SupportsChatGetResponse`-compatible chat client:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
@@ -38,15 +38,15 @@ add_agent_framework_fastapi_endpoint(app, weather_agent(openai_client), "/weathe
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
|
||||
# Create your agent
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="my_agent",
|
||||
instructions="You are a helpful assistant.",
|
||||
chat_client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
)
|
||||
|
||||
# Create FastAPI app and add AG-UI endpoint
|
||||
@@ -70,21 +70,21 @@ This integration supports all 7 AG-UI features:
|
||||
|
||||
## Examples
|
||||
|
||||
All example agents are implemented as **factory functions** that accept any chat client implementing `ChatClientProtocol`. This provides maximum flexibility to use Azure OpenAI, OpenAI, Anthropic, or any custom chat client implementation.
|
||||
All example agents are implemented as **factory functions** that accept any chat client implementing `SupportsChatGetResponse`. This provides maximum flexibility to use Azure OpenAI, OpenAI, Anthropic, or any custom chat client implementation.
|
||||
|
||||
### Available Example Agents
|
||||
|
||||
Complete examples for all AG-UI features are available:
|
||||
|
||||
- `simple_agent(chat_client)` - Basic agentic chat (Feature 1)
|
||||
- `weather_agent(chat_client)` - Backend tool rendering (Feature 2)
|
||||
- `human_in_the_loop_agent(chat_client)` - Human-in-the-loop with step customization (Feature 3)
|
||||
- `task_steps_agent_wrapped(chat_client)` - Agentic generative UI with step execution (Feature 4)
|
||||
- `ui_generator_agent(chat_client)` - Tool-based generative UI (Feature 5)
|
||||
- `recipe_agent(chat_client)` - Shared state management (Feature 6)
|
||||
- `document_writer_agent(chat_client)` - Predictive state updates (Feature 7)
|
||||
- `research_assistant_agent(chat_client)` - Research with progress events
|
||||
- `task_planner_agent(chat_client)` - Task planning with approvals
|
||||
- `simple_agent(client)` - Basic agentic chat (Feature 1)
|
||||
- `weather_agent(client)` - Backend tool rendering (Feature 2)
|
||||
- `human_in_the_loop_agent(client)` - Human-in-the-loop with step customization (Feature 3)
|
||||
- `task_steps_agent_wrapped(client)` - Agentic generative UI with step execution (Feature 4)
|
||||
- `ui_generator_agent(client)` - Tool-based generative UI (Feature 5)
|
||||
- `recipe_agent(client)` - Shared state management (Feature 6)
|
||||
- `document_writer_agent(client)` - Predictive state updates (Feature 7)
|
||||
- `research_assistant_agent(client)` - Research with progress events
|
||||
- `task_planner_agent(client)` - Task planning with approvals
|
||||
|
||||
### Using Example Agents
|
||||
|
||||
@@ -97,7 +97,7 @@ from agent_framework_ag_ui_examples.agents import (
|
||||
recipe_agent,
|
||||
)
|
||||
|
||||
# Create a chat client (use any ChatClientProtocol implementation)
|
||||
# Create a chat client (use any SupportsChatGetResponse implementation)
|
||||
azure_client = AzureOpenAIChatClient(model_id="gpt-4")
|
||||
openai_client = OpenAIChatClient(model_id="gpt-4o")
|
||||
|
||||
@@ -150,16 +150,16 @@ from agent_framework_ag_ui_examples.agents import (
|
||||
app = FastAPI(title="AG-UI Examples")
|
||||
|
||||
# Create a chat client (shared across all agents, or create individual ones)
|
||||
chat_client = AzureOpenAIChatClient(model_id="gpt-4")
|
||||
client = AzureOpenAIChatClient(model_id="gpt-4")
|
||||
|
||||
# Add all example endpoints
|
||||
add_agent_framework_fastapi_endpoint(app, simple_agent(chat_client), "/agentic_chat")
|
||||
add_agent_framework_fastapi_endpoint(app, weather_agent(chat_client), "/backend_tool_rendering")
|
||||
add_agent_framework_fastapi_endpoint(app, human_in_the_loop_agent(chat_client), "/human_in_the_loop")
|
||||
add_agent_framework_fastapi_endpoint(app, task_steps_agent_wrapped(chat_client), "/agentic_generative_ui") # type: ignore[arg-type]
|
||||
add_agent_framework_fastapi_endpoint(app, ui_generator_agent(chat_client), "/tool_based_generative_ui")
|
||||
add_agent_framework_fastapi_endpoint(app, recipe_agent(chat_client), "/shared_state")
|
||||
add_agent_framework_fastapi_endpoint(app, document_writer_agent(chat_client), "/predictive_state_updates")
|
||||
add_agent_framework_fastapi_endpoint(app, simple_agent(client), "/agentic_chat")
|
||||
add_agent_framework_fastapi_endpoint(app, weather_agent(client), "/backend_tool_rendering")
|
||||
add_agent_framework_fastapi_endpoint(app, human_in_the_loop_agent(client), "/human_in_the_loop")
|
||||
add_agent_framework_fastapi_endpoint(app, task_steps_agent_wrapped(client), "/agentic_generative_ui") # type: ignore[arg-type]
|
||||
add_agent_framework_fastapi_endpoint(app, ui_generator_agent(client), "/tool_based_generative_ui")
|
||||
add_agent_framework_fastapi_endpoint(app, recipe_agent(client), "/shared_state")
|
||||
add_agent_framework_fastapi_endpoint(app, document_writer_agent(client), "/predictive_state_updates")
|
||||
```
|
||||
|
||||
## Architecture
|
||||
@@ -187,8 +187,8 @@ The package uses a clean, orchestrator-based architecture:
|
||||
You can create your own agent factories following the same pattern as the examples:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework import ChatClientProtocol
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework import SupportsChatGetResponse
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
@tool
|
||||
@@ -196,19 +196,19 @@ def my_tool(param: str) -> str:
|
||||
"""My custom tool."""
|
||||
return f"Result: {param}"
|
||||
|
||||
def my_custom_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
|
||||
def my_custom_agent(client: SupportsChatGetResponse) -> AgentFrameworkAgent:
|
||||
"""Create a custom agent with the specified chat client.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="my_custom_agent",
|
||||
instructions="Custom instructions here",
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[my_tool],
|
||||
)
|
||||
|
||||
@@ -220,8 +220,8 @@ def my_custom_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
|
||||
|
||||
# Use it
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
chat_client = AzureOpenAIChatClient()
|
||||
agent = my_custom_agent(chat_client)
|
||||
client = AzureOpenAIChatClient()
|
||||
agent = my_custom_agent(client)
|
||||
```
|
||||
|
||||
### Shared State
|
||||
@@ -229,14 +229,14 @@ agent = my_custom_agent(chat_client)
|
||||
State is injected as system messages and updated via predictive state updates:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
# Create your agent
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="recipe_agent",
|
||||
chat_client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
)
|
||||
|
||||
state_schema = {
|
||||
@@ -266,14 +266,14 @@ wrapped_agent = AgentFrameworkAgent(
|
||||
Predictive state updates automatically stream tool arguments as optimistic state updates:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
# Create your agent
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="document_writer",
|
||||
chat_client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
client=AzureOpenAIChatClient(model_id="gpt-4o"),
|
||||
)
|
||||
|
||||
predict_state_config = {
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
|
||||
@@ -40,19 +40,19 @@ _DOCUMENT_WRITER_INSTRUCTIONS = (
|
||||
)
|
||||
|
||||
|
||||
def document_writer_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
|
||||
def document_writer_agent(client: SupportsChatGetResponse) -> AgentFrameworkAgent:
|
||||
"""Create a document writer agent with predictive state updates.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance with document writing capabilities
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="document_writer",
|
||||
instructions=_DOCUMENT_WRITER_INSTRUCTIONS,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[write_document],
|
||||
)
|
||||
|
||||
|
||||
+6
-6
@@ -5,7 +5,7 @@
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
@@ -43,16 +43,16 @@ def generate_task_steps(steps: list[TaskStep]) -> str:
|
||||
return f"Generated {len(steps)} execution steps for the task."
|
||||
|
||||
|
||||
def human_in_the_loop_agent(chat_client: ChatClientProtocol[Any]) -> ChatAgent[Any]:
|
||||
def human_in_the_loop_agent(client: SupportsChatGetResponse[Any]) -> Agent[Any]:
|
||||
"""Create a human-in-the-loop agent using tool-based approach for predictive state.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured ChatAgent instance with human-in-the-loop capabilities
|
||||
A configured Agent instance with human-in-the-loop capabilities
|
||||
"""
|
||||
return ChatAgent(
|
||||
return Agent(
|
||||
name="human_in_the_loop_agent",
|
||||
instructions="""You are a helpful assistant that can perform any task by breaking it down into steps.
|
||||
|
||||
@@ -81,6 +81,6 @@ def human_in_the_loop_agent(chat_client: ChatClientProtocol[Any]) -> ChatAgent[A
|
||||
After the user approves and the function executes, THEN provide a brief acknowledgment like:
|
||||
"The plan has been created with X steps selected."
|
||||
""",
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[generate_task_steps],
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -104,19 +104,19 @@ _RECIPE_INSTRUCTIONS = """You are a helpful recipe assistant that creates and mo
|
||||
"""
|
||||
|
||||
|
||||
def recipe_agent(chat_client: ChatClientProtocol[Any]) -> AgentFrameworkAgent:
|
||||
def recipe_agent(client: SupportsChatGetResponse[Any]) -> AgentFrameworkAgent:
|
||||
"""Create a recipe agent with streaming state updates.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance with recipe management
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="recipe_agent",
|
||||
instructions=_RECIPE_INSTRUCTIONS,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[update_recipe],
|
||||
)
|
||||
|
||||
|
||||
+5
-5
@@ -5,7 +5,7 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
|
||||
@@ -88,19 +88,19 @@ _RESEARCH_ASSISTANT_INSTRUCTIONS = (
|
||||
)
|
||||
|
||||
|
||||
def research_assistant_agent(chat_client: ChatClientProtocol[Any]) -> AgentFrameworkAgent:
|
||||
def research_assistant_agent(client: SupportsChatGetResponse[Any]) -> AgentFrameworkAgent:
|
||||
"""Create a research assistant agent.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance with research capabilities
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="research_assistant",
|
||||
instructions=_RESEARCH_ASSISTANT_INSTRUCTIONS,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[research_topic, create_presentation, analyze_data],
|
||||
)
|
||||
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol
|
||||
from agent_framework import Agent, SupportsChatGetResponse
|
||||
|
||||
|
||||
def simple_agent(chat_client: ChatClientProtocol[Any]) -> ChatAgent[Any]:
|
||||
def simple_agent(client: SupportsChatGetResponse[Any]) -> Agent[Any]:
|
||||
"""Create a simple chat agent.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured ChatAgent instance
|
||||
A configured Agent instance
|
||||
"""
|
||||
return ChatAgent[Any](
|
||||
return Agent[Any](
|
||||
name="simple_chat_agent",
|
||||
instructions="You are a helpful assistant. Be concise and friendly.",
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
|
||||
@@ -61,19 +61,19 @@ _TASK_PLANNER_INSTRUCTIONS = (
|
||||
)
|
||||
|
||||
|
||||
def task_planner_agent(chat_client: ChatClientProtocol[Any]) -> AgentFrameworkAgent:
|
||||
def task_planner_agent(client: SupportsChatGetResponse[Any]) -> AgentFrameworkAgent:
|
||||
"""Create a task planner agent with user approval for actions.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance with task planning capabilities
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="task_planner",
|
||||
instructions=_TASK_PLANNER_INSTRUCTIONS,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[create_calendar_event, send_email, book_meeting_room],
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from ag_ui.core import (
|
||||
TextMessageStartEvent,
|
||||
ToolCallStartEvent,
|
||||
)
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, ChatMessage, Content, tool
|
||||
from agent_framework import Agent, Content, Message, SupportsChatGetResponse, tool
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -54,16 +54,16 @@ def generate_task_steps(steps: list[TaskStep]) -> str:
|
||||
return "Steps generated."
|
||||
|
||||
|
||||
def _create_task_steps_agent(chat_client: ChatClientProtocol[Any]) -> AgentFrameworkAgent:
|
||||
def _create_task_steps_agent(client: SupportsChatGetResponse[Any]) -> AgentFrameworkAgent:
|
||||
"""Create the task steps agent using tool-based approach for streaming.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance
|
||||
"""
|
||||
agent = ChatAgent[Any](
|
||||
agent = Agent[Any](
|
||||
name="task_steps_agent",
|
||||
instructions="""You are a helpful assistant that breaks down tasks into actionable steps.
|
||||
|
||||
@@ -83,7 +83,7 @@ def _create_task_steps_agent(chat_client: ChatClientProtocol[Any]) -> AgentFrame
|
||||
- "Installing platform"
|
||||
- "Adding finishing touches"
|
||||
""",
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[generate_task_steps],
|
||||
)
|
||||
|
||||
@@ -220,30 +220,30 @@ class TaskStepsAgentWithExecution:
|
||||
|
||||
# Get the underlying chat agent and client
|
||||
chat_agent = self._base_agent.agent # type: ignore
|
||||
chat_client = chat_agent.chat_client # type: ignore
|
||||
client = chat_agent.client # type: ignore
|
||||
|
||||
# Build messages for summary call
|
||||
|
||||
original_messages = input_data.get("messages", [])
|
||||
|
||||
# Convert to ChatMessage objects if needed
|
||||
messages: list[ChatMessage] = []
|
||||
# Convert to Message objects if needed
|
||||
messages: list[Message] = []
|
||||
for msg in original_messages:
|
||||
if isinstance(msg, dict):
|
||||
content_str = msg.get("content", "")
|
||||
if isinstance(content_str, str):
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role=msg.get("role", "user"),
|
||||
contents=[Content.from_text(text=content_str)],
|
||||
)
|
||||
)
|
||||
elif isinstance(msg, ChatMessage):
|
||||
elif isinstance(msg, Message):
|
||||
messages.append(msg)
|
||||
|
||||
# Add completion message
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text(
|
||||
@@ -270,7 +270,7 @@ class TaskStepsAgentWithExecution:
|
||||
|
||||
# Stream completion
|
||||
accumulated_text = ""
|
||||
async for chunk in chat_client.get_response(messages=messages, stream=True):
|
||||
async for chunk in client.get_response(messages=messages, stream=True):
|
||||
# chunk is ChatResponseUpdate
|
||||
if hasattr(chunk, "text") and chunk.text:
|
||||
accumulated_text += chunk.text
|
||||
@@ -332,14 +332,14 @@ class TaskStepsAgentWithExecution:
|
||||
yield run_finished_event
|
||||
|
||||
|
||||
def task_steps_agent_wrapped(chat_client: ChatClientProtocol[Any]) -> TaskStepsAgentWithExecution:
|
||||
def task_steps_agent_wrapped(client: SupportsChatGetResponse[Any]) -> TaskStepsAgentWithExecution:
|
||||
"""Create a task steps agent with execution simulation.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A wrapped agent instance with step execution simulation
|
||||
"""
|
||||
base_agent = _create_task_steps_agent(chat_client)
|
||||
base_agent = _create_task_steps_agent(client)
|
||||
return TaskStepsAgentWithExecution(base_agent)
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, TypedDict
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, FunctionTool
|
||||
from agent_framework import Agent, FunctionTool, SupportsChatGetResponse
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
@@ -168,19 +168,19 @@ _UI_GENERATOR_INSTRUCTIONS = """You MUST use the provided tools to generate cont
|
||||
OptionsT = TypeVar("OptionsT", bound=TypedDict, default="ChatOptions") # type: ignore[valid-type]
|
||||
|
||||
|
||||
def ui_generator_agent(chat_client: ChatClientProtocol[OptionsT]) -> AgentFrameworkAgent:
|
||||
def ui_generator_agent(client: SupportsChatGetResponse[OptionsT]) -> AgentFrameworkAgent:
|
||||
"""Create a UI generator agent with custom React component rendering.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured AgentFrameworkAgent instance with UI generation capabilities
|
||||
"""
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="ui_generator",
|
||||
instructions=_UI_GENERATOR_INSTRUCTIONS,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[generate_haiku, create_chart, display_timeline, show_comparison_table],
|
||||
# Force tool usage - the LLM MUST call a tool, cannot respond with plain text
|
||||
default_options={"tool_choice": "required"}, # type: ignore
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatClientProtocol, tool
|
||||
from agent_framework import Agent, SupportsChatGetResponse, tool
|
||||
|
||||
|
||||
@tool
|
||||
@@ -59,16 +59,16 @@ def get_forecast(location: str, days: int = 3) -> str:
|
||||
return f"{days}-day forecast for {location}:\n" + "\n".join(forecast)
|
||||
|
||||
|
||||
def weather_agent(chat_client: ChatClientProtocol[Any]) -> ChatAgent[Any]:
|
||||
def weather_agent(client: SupportsChatGetResponse[Any]) -> Agent[Any]:
|
||||
"""Create a weather agent with get_weather and get_forecast tools.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent
|
||||
client: The chat client to use for the agent
|
||||
|
||||
Returns:
|
||||
A configured ChatAgent instance with weather tools
|
||||
A configured Agent instance with weather tools
|
||||
"""
|
||||
return ChatAgent[Any](
|
||||
return Agent[Any](
|
||||
name="weather_agent",
|
||||
instructions=(
|
||||
"You are a helpful weather assistant. "
|
||||
@@ -76,6 +76,6 @@ def weather_agent(chat_client: ChatClientProtocol[Any]) -> ChatAgent[Any]:
|
||||
"Always provide friendly and informative responses. "
|
||||
"First return the weather result, and then return details about the forecast."
|
||||
),
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
tools=[get_weather, get_forecast],
|
||||
)
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from agent_framework._clients import ChatClientProtocol
|
||||
from agent_framework._clients import SupportsChatGetResponse
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from fastapi import FastAPI
|
||||
@@ -19,10 +19,10 @@ def register_backend_tool_rendering(app: FastAPI) -> None:
|
||||
app: The FastAPI application.
|
||||
"""
|
||||
# Create a chat client and call the factory function
|
||||
chat_client = cast(ChatClientProtocol[Any], AzureOpenAIChatClient())
|
||||
client = cast(SupportsChatGetResponse[Any], AzureOpenAIChatClient())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app,
|
||||
weather_agent(chat_client),
|
||||
weather_agent(client),
|
||||
"/backend_tool_rendering",
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import cast
|
||||
|
||||
import uvicorn
|
||||
from agent_framework import ChatOptions
|
||||
from agent_framework._clients import ChatClientProtocol
|
||||
from agent_framework._clients import SupportsChatGetResponse
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
from agent_framework.anthropic import AnthropicClient
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
@@ -67,43 +67,43 @@ app.add_middleware(
|
||||
# Create a shared chat client for all agents
|
||||
# You can use different chat clients for different agents if needed
|
||||
# Set CHAT_CLIENT=anthropic to use Anthropic, defaults to Azure OpenAI
|
||||
chat_client: ChatClientProtocol[ChatOptions] = cast(
|
||||
ChatClientProtocol[ChatOptions],
|
||||
client: SupportsChatGetResponse[ChatOptions] = cast(
|
||||
SupportsChatGetResponse[ChatOptions],
|
||||
AnthropicClient() if os.getenv("CHAT_CLIENT", "").lower() == "anthropic" else AzureOpenAIChatClient(),
|
||||
)
|
||||
|
||||
# Agentic Chat - basic chat agent
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=simple_agent(chat_client),
|
||||
agent=simple_agent(client),
|
||||
path="/agentic_chat",
|
||||
)
|
||||
|
||||
# Backend Tool Rendering - agent with tools
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=weather_agent(chat_client),
|
||||
agent=weather_agent(client),
|
||||
path="/backend_tool_rendering",
|
||||
)
|
||||
|
||||
# Shared State - recipe agent with structured output
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=recipe_agent(chat_client),
|
||||
agent=recipe_agent(client),
|
||||
path="/shared_state",
|
||||
)
|
||||
|
||||
# Predictive State Updates - document writer with predictive state
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=document_writer_agent(chat_client),
|
||||
agent=document_writer_agent(client),
|
||||
path="/predictive_state_updates",
|
||||
)
|
||||
|
||||
# Human in the Loop - human-in-the-loop agent with step customization
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=human_in_the_loop_agent(chat_client),
|
||||
agent=human_in_the_loop_agent(client),
|
||||
path="/human_in_the_loop",
|
||||
state_schema={"steps": {"type": "array"}},
|
||||
predict_state_config={"steps": {"tool": "generate_task_steps", "tool_argument": "steps"}},
|
||||
@@ -112,14 +112,14 @@ add_agent_framework_fastapi_endpoint(
|
||||
# Agentic Generative UI - task steps agent with streaming state updates
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=task_steps_agent_wrapped(chat_client), # type: ignore[arg-type]
|
||||
agent=task_steps_agent_wrapped(client), # type: ignore[arg-type]
|
||||
path="/agentic_generative_ui",
|
||||
)
|
||||
|
||||
# Tool-based Generative UI - UI generator with frontend-rendered tools
|
||||
add_agent_framework_fastapi_endpoint(
|
||||
app=app,
|
||||
agent=ui_generator_agent(chat_client),
|
||||
agent=ui_generator_agent(client),
|
||||
path="/tool_based_generative_ui",
|
||||
)
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ python client_advanced.py
|
||||
|
||||
**Note:** This example shows direct `AGUIChatClient` usage. Tool execution and conversation continuity depend on server-side configuration and capabilities.
|
||||
|
||||
### ChatAgent Integration (`client_with_agent.py`)
|
||||
### Agent Integration (`client_with_agent.py`)
|
||||
|
||||
Best practice example using `ChatAgent` wrapper with **AgentThread**
|
||||
Best practice example using `Agent` wrapper with **AgentThread**
|
||||
- **AgentThread** maintains conversation state
|
||||
- Client-side conversation history management via `thread.message_store`
|
||||
- **Hybrid tool execution**: client-side + server-side tools simultaneously
|
||||
@@ -77,7 +77,7 @@ The AG-UI protocol supports two approaches to conversation history:
|
||||
- Full message history sent with each request
|
||||
- Works with any AG-UI server (stateful or stateless)
|
||||
|
||||
The `ChatAgent` wrapper (used in client_with_agent.py) collects messages from local storage and sends the full history to `AGUIChatClient`, which then forwards everything to the server.
|
||||
The `Agent` wrapper (used in client_with_agent.py) collects messages from local storage and sends the full history to `AGUIChatClient`, which then forwards everything to the server.
|
||||
|
||||
### Tool/Function Calling
|
||||
|
||||
@@ -91,14 +91,14 @@ Client defines: Server defines:
|
||||
|
||||
User: "What's the weather in SF and what time is it?"
|
||||
↓
|
||||
ChatAgent sends: full history + tool definitions for get_weather, read_sensors
|
||||
Agent sends: full history + tool definitions for get_weather, read_sensors
|
||||
↓
|
||||
Server LLM decides: "I need get_weather('SF') and get_current_time()"
|
||||
↓
|
||||
Server executes get_current_time() → "2025-11-11 14:30:00 UTC"
|
||||
Server sends function call request → get_weather('SF')
|
||||
↓
|
||||
ChatAgent intercepts get_weather call → executes locally
|
||||
Agent intercepts get_weather call → executes locally
|
||||
↓
|
||||
Client sends result → "Sunny, 72°F"
|
||||
↓
|
||||
@@ -110,7 +110,7 @@ Client receives final response
|
||||
**How it works:**
|
||||
|
||||
1. **Client-Side Tools** (`client_with_agent.py`):
|
||||
- Tools defined in ChatAgent's `tools` parameter execute locally
|
||||
- Tools defined in Agent's `tools` parameter execute locally
|
||||
- Tool metadata (name, description, schema) sent to server for planning
|
||||
- When server requests client tool → client intercepts → executes locally → sends result
|
||||
|
||||
@@ -126,7 +126,7 @@ Client receives final response
|
||||
- Client tools execute client-side
|
||||
|
||||
**Direct AGUIChatClient Usage** (client_advanced.py):
|
||||
Even without ChatAgent wrapper, client-side tools work:
|
||||
Even without Agent wrapper, client-side tools work:
|
||||
- Tools passed in ChatOptions execute locally
|
||||
- Server can also have its own tools
|
||||
- Hybrid execution works automatically
|
||||
@@ -184,7 +184,7 @@ Create a file named `server.py`:
|
||||
|
||||
import os
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
from fastapi import FastAPI
|
||||
@@ -202,10 +202,10 @@ if not api_key:
|
||||
raise ValueError("AZURE_OPENAI_API_KEY environment variable is required")
|
||||
|
||||
# Create the AI agent
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="AGUIAssistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
chat_client=AzureOpenAIChatClient(
|
||||
client=AzureOpenAIChatClient(
|
||||
endpoint=endpoint,
|
||||
deployment_name=deployment_name,
|
||||
api_key=api_key,
|
||||
@@ -227,7 +227,7 @@ if __name__ == "__main__":
|
||||
### Key Concepts
|
||||
|
||||
- **`add_agent_framework_fastapi_endpoint`**: Registers the AG-UI endpoint with automatic request/response handling and SSE streaming
|
||||
- **`ChatAgent`**: The agent that will handle incoming requests
|
||||
- **`Agent`**: The agent that will handle incoming requests
|
||||
- **FastAPI Integration**: Uses FastAPI's native async support for streaming responses
|
||||
- **Instructions**: The agent is created with default instructions, which can be overridden by client messages
|
||||
- **Configuration**: `AzureOpenAIChatClient` can read from environment variables (`AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_CHAT_DEPLOYMENT_NAME`, `AZURE_OPENAI_API_KEY`) or accept parameters directly
|
||||
@@ -236,10 +236,10 @@ if __name__ == "__main__":
|
||||
|
||||
```python
|
||||
# No need to read environment variables manually
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="AGUIAssistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
chat_client=AzureOpenAIChatClient(), # Reads from environment automatically
|
||||
client=AzureOpenAIChatClient(), # Reads from environment automatically
|
||||
)
|
||||
```
|
||||
|
||||
@@ -354,7 +354,7 @@ if __name__ == "__main__":
|
||||
- **Thread Management**: Pass `thread_id` in metadata to maintain conversation context across requests
|
||||
- **Streaming Responses**: Use `get_response(..., stream=True)` for real-time streaming or `get_response(..., stream=False)` for non-streaming
|
||||
- **Context Manager**: Use `async with` for automatic cleanup of HTTP connections
|
||||
- **Standard Interface**: Works with all Agent Framework patterns (ChatAgent, tools, etc.)
|
||||
- **Standard Interface**: Works with all Agent Framework patterns (Agent, tools, etc.)
|
||||
- **Hybrid Tool Execution**: Supports both client-side and server-side tools executing together in the same conversation
|
||||
|
||||
### Configure and Run the Client
|
||||
|
||||
@@ -114,15 +114,15 @@ async def non_streaming_example(client: AGUIChatClient, thread_id: str | None =
|
||||
async def tool_example(client: AGUIChatClient, thread_id: str | None = None):
|
||||
"""Demonstrate sending tool definitions to the server.
|
||||
|
||||
IMPORTANT: When using AGUIChatClient directly (without ChatAgent wrapper):
|
||||
IMPORTANT: When using AGUIChatClient directly (without Agent wrapper):
|
||||
- Tools are sent as DEFINITIONS only
|
||||
- No automatic client-side execution (no function invocation middleware)
|
||||
- Server must have matching tool implementations to execute them
|
||||
|
||||
For CLIENT-SIDE tool execution (like .NET AGUIClient sample):
|
||||
- Use ChatAgent wrapper with tools
|
||||
- Use Agent wrapper with tools
|
||||
- See client_with_agent.py for the hybrid pattern
|
||||
- ChatAgent middleware intercepts and executes client tools locally
|
||||
- Agent middleware intercepts and executes client tools locally
|
||||
- Server can have its own tools that execute server-side
|
||||
- Both client and server tools work together in same conversation
|
||||
|
||||
@@ -186,7 +186,7 @@ async def conversation_example(client: AGUIChatClient):
|
||||
|
||||
# Check if context was maintained
|
||||
if "alice" not in response2.text.lower():
|
||||
print("\n[Note: Server may not maintain thread context - consider using ChatAgent for history management]")
|
||||
print("\n[Note: Server may not maintain thread context - consider using Agent for history management]")
|
||||
|
||||
# Third turn
|
||||
print("\nUser: Can you also tell me what 10 * 5 is?\n")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Example showing ChatAgent with AGUIChatClient for hybrid tool execution.
|
||||
"""Example showing Agent with AGUIChatClient for hybrid tool execution.
|
||||
|
||||
This demonstrates the HYBRID pattern matching .NET AGUIClient implementation:
|
||||
|
||||
@@ -24,7 +24,7 @@ import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework.ag_ui import AGUIChatClient
|
||||
|
||||
# Enable debug logging
|
||||
@@ -55,7 +55,7 @@ def get_weather(location: str) -> str:
|
||||
|
||||
|
||||
async def main():
|
||||
"""Demonstrate ChatAgent + AGUIChatClient hybrid tool execution.
|
||||
"""Demonstrate Agent + AGUIChatClient hybrid tool execution.
|
||||
|
||||
This matches the .NET pattern from Program.cs where:
|
||||
- AIAgent agent = chatClient.CreateAIAgent(tools: [...])
|
||||
@@ -63,14 +63,14 @@ async def main():
|
||||
- RunStreamingAsync(messages, thread)
|
||||
|
||||
Python equivalent:
|
||||
- agent = ChatAgent(chat_client=AGUIChatClient(...), tools=[...])
|
||||
- agent = Agent(client=AGUIChatClient(...), tools=[...])
|
||||
- thread = agent.get_new_thread() # Creates thread with message_store
|
||||
- agent.run(message, stream=True, thread=thread) # Thread accumulates history
|
||||
"""
|
||||
server_url = os.environ.get("AGUI_SERVER_URL", "http://127.0.0.1:5100/")
|
||||
|
||||
print("=" * 70)
|
||||
print("ChatAgent + AGUIChatClient: Hybrid Tool Execution")
|
||||
print("Agent + AGUIChatClient: Hybrid Tool Execution")
|
||||
print("=" * 70)
|
||||
print(f"\nServer: {server_url}")
|
||||
print("\nThis example demonstrates:")
|
||||
@@ -82,11 +82,11 @@ async def main():
|
||||
try:
|
||||
# Create remote client in async context manager
|
||||
async with AGUIChatClient(endpoint=server_url) as remote_client:
|
||||
# Wrap in ChatAgent for conversation history management
|
||||
agent = ChatAgent(
|
||||
# Wrap in Agent for conversation history management
|
||||
agent = Agent(
|
||||
name="remote_assistant",
|
||||
instructions="You are a helpful assistant. Remember user information across the conversation.",
|
||||
chat_client=remote_client,
|
||||
client=remote_client,
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from dotenv import load_dotenv
|
||||
@@ -116,10 +116,10 @@ def get_time_zone(location: str) -> str:
|
||||
# The client will send get_weather tool metadata so the LLM knows about it,
|
||||
# and the function invocation mixin on AGUIChatClient will execute it client-side.
|
||||
# This matches the .NET AG-UI hybrid execution pattern.
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="AGUIAssistant",
|
||||
instructions="You are a helpful assistant. Use get_weather for weather and get_time_zone for time zones.",
|
||||
chat_client=AzureOpenAIChatClient(
|
||||
client=AzureOpenAIChatClient(
|
||||
endpoint=endpoint,
|
||||
deployment_name=deployment_name,
|
||||
),
|
||||
|
||||
@@ -13,13 +13,13 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
BaseChatClient,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
Message,
|
||||
SupportsAgentRun,
|
||||
SupportsChatGetResponse,
|
||||
)
|
||||
from agent_framework._clients import OptionsCoT
|
||||
from agent_framework._middleware import ChatMiddlewareLayer
|
||||
@@ -43,7 +43,7 @@ class StreamingChatClientStub(
|
||||
BaseChatClient[OptionsCoT],
|
||||
Generic[OptionsCoT],
|
||||
):
|
||||
"""Typed streaming stub that satisfies ChatClientProtocol."""
|
||||
"""Typed streaming stub that satisfies SupportsChatGetResponse."""
|
||||
|
||||
def __init__(self, stream_fn: StreamFn, response_fn: ResponseFn | None = None) -> None:
|
||||
super().__init__(function_middleware=[])
|
||||
@@ -55,7 +55,7 @@ class StreamingChatClientStub(
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[Any],
|
||||
@@ -65,7 +65,7 @@ class StreamingChatClientStub(
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsCoT | ChatOptions[None] | None = ...,
|
||||
@@ -75,7 +75,7 @@ class StreamingChatClientStub(
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsCoT | ChatOptions[Any] | None = ...,
|
||||
@@ -84,7 +84,7 @@ class StreamingChatClientStub(
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -106,7 +106,7 @@ class StreamingChatClientStub(
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
stream: bool = False,
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
@@ -121,7 +121,7 @@ class StreamingChatClientStub(
|
||||
return self._get_response_impl(messages, options, **kwargs)
|
||||
|
||||
async def _get_response_impl(
|
||||
self, messages: Sequence[ChatMessage], options: Mapping[str, Any], **kwargs: Any
|
||||
self, messages: Sequence[Message], options: Mapping[str, Any], **kwargs: Any
|
||||
) -> ChatResponse:
|
||||
"""Non-streaming implementation."""
|
||||
if self._response_fn is not None:
|
||||
@@ -132,7 +132,7 @@ class StreamingChatClientStub(
|
||||
contents.extend(update.contents)
|
||||
|
||||
return ChatResponse(
|
||||
messages=[ChatMessage(role="assistant", contents=contents)],
|
||||
messages=[Message(role="assistant", contents=contents)],
|
||||
response_id="stub-response",
|
||||
)
|
||||
|
||||
@@ -141,7 +141,7 @@ def stream_from_updates(updates: list[ChatResponseUpdate]) -> StreamFn:
|
||||
"""Create a stream function that yields from a static list of updates."""
|
||||
|
||||
async def _stream(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
for update in updates:
|
||||
yield update
|
||||
@@ -159,7 +159,7 @@ class StubAgent(SupportsAgentRun):
|
||||
agent_id: str = "stub-agent",
|
||||
agent_name: str | None = "stub-agent",
|
||||
default_options: Any | None = None,
|
||||
chat_client: Any | None = None,
|
||||
client: Any | None = None,
|
||||
) -> None:
|
||||
self.id = agent_id
|
||||
self.name = agent_name
|
||||
@@ -168,14 +168,14 @@ class StubAgent(SupportsAgentRun):
|
||||
self.default_options: dict[str, Any] = (
|
||||
default_options if isinstance(default_options, dict) else {"tools": None, "response_format": None}
|
||||
)
|
||||
self.chat_client = chat_client or SimpleNamespace(function_invocation_configuration=None)
|
||||
self.client = client or SimpleNamespace(function_invocation_configuration=None)
|
||||
self.messages_received: list[Any] = []
|
||||
self.tools_received: list[Any] | None = None
|
||||
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -185,7 +185,7 @@ class StubAgent(SupportsAgentRun):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -194,7 +194,7 @@ class StubAgent(SupportsAgentRun):
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -226,7 +226,7 @@ class StubAgent(SupportsAgentRun):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def streaming_chat_client_stub() -> type[ChatClientProtocol]:
|
||||
def streaming_chat_client_stub() -> type[SupportsChatGetResponse]:
|
||||
"""Return the StreamingChatClientStub class for creating test instances."""
|
||||
return StreamingChatClientStub # type: ignore[return-value]
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ from collections.abc import AsyncGenerator, Awaitable, MutableSequence
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
Message,
|
||||
ResponseStream,
|
||||
tool,
|
||||
)
|
||||
@@ -29,13 +29,11 @@ class TestableAGUIChatClient(AGUIChatClient):
|
||||
"""Expose http service for monkeypatching."""
|
||||
return self._http_service
|
||||
|
||||
def extract_state_from_messages(
|
||||
self, messages: list[ChatMessage]
|
||||
) -> tuple[list[ChatMessage], dict[str, Any] | None]:
|
||||
def extract_state_from_messages(self, messages: list[Message]) -> tuple[list[Message], dict[str, Any] | None]:
|
||||
"""Expose state extraction helper."""
|
||||
return self._extract_state_from_messages(messages)
|
||||
|
||||
def convert_messages_to_agui_format(self, messages: list[ChatMessage]) -> list[dict[str, Any]]:
|
||||
def convert_messages_to_agui_format(self, messages: list[Message]) -> list[dict[str, Any]]:
|
||||
"""Expose message conversion helper."""
|
||||
return self._convert_messages_to_agui_format(messages)
|
||||
|
||||
@@ -44,7 +42,7 @@ class TestableAGUIChatClient(AGUIChatClient):
|
||||
return self._get_thread_id(options)
|
||||
|
||||
def inner_get_response(
|
||||
self, *, messages: MutableSequence[ChatMessage], options: dict[str, Any], stream: bool = False
|
||||
self, *, messages: MutableSequence[Message], options: dict[str, Any], stream: bool = False
|
||||
) -> Awaitable[ChatResponse] | ResponseStream[ChatResponseUpdate, ChatResponse]:
|
||||
"""Proxy to protected response call."""
|
||||
return self._inner_get_response(messages=messages, options=options, stream=stream)
|
||||
@@ -69,8 +67,8 @@ class TestAGUIChatClient:
|
||||
"""Test state extraction when no state is present."""
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
messages = [
|
||||
ChatMessage(role="user", text="Hello"),
|
||||
ChatMessage(role="assistant", text="Hi there"),
|
||||
Message(role="user", text="Hello"),
|
||||
Message(role="assistant", text="Hi there"),
|
||||
]
|
||||
|
||||
result_messages, state = client.extract_state_from_messages(messages)
|
||||
@@ -89,8 +87,8 @@ class TestAGUIChatClient:
|
||||
state_b64 = base64.b64encode(state_json.encode("utf-8")).decode("utf-8")
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="user", text="Hello"),
|
||||
ChatMessage(
|
||||
Message(role="user", text="Hello"),
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_uri(uri=f"data:application/json;base64,{state_b64}")],
|
||||
),
|
||||
@@ -112,7 +110,7 @@ class TestAGUIChatClient:
|
||||
state_b64 = base64.b64encode(invalid_json.encode("utf-8")).decode("utf-8")
|
||||
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_uri(uri=f"data:application/json;base64,{state_b64}")],
|
||||
),
|
||||
@@ -127,8 +125,8 @@ class TestAGUIChatClient:
|
||||
"""Test message conversion to AG-UI format."""
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
messages = [
|
||||
ChatMessage(role="user", text="What is the weather?"),
|
||||
ChatMessage(role="assistant", text="Let me check.", message_id="msg_123"),
|
||||
Message(role="user", text="What is the weather?"),
|
||||
Message(role="assistant", text="Let me check.", message_id="msg_123"),
|
||||
]
|
||||
|
||||
agui_messages = client.convert_messages_to_agui_format(messages)
|
||||
@@ -175,7 +173,7 @@ class TestAGUIChatClient:
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Test message")]
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
@@ -208,7 +206,7 @@ class TestAGUIChatClient:
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Test message")]
|
||||
messages = [Message(role="user", text="Test message")]
|
||||
chat_options = {}
|
||||
|
||||
response = await client.inner_get_response(messages=messages, options=chat_options)
|
||||
@@ -251,7 +249,7 @@ class TestAGUIChatClient:
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Test with tools")]
|
||||
messages = [Message(role="user", text="Test with tools")]
|
||||
chat_options = ChatOptions(tools=[test_tool])
|
||||
|
||||
response = await client.inner_get_response(messages=messages, options=chat_options)
|
||||
@@ -275,7 +273,7 @@ class TestAGUIChatClient:
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Test server tool execution")]
|
||||
messages = [Message(role="user", text="Test server tool execution")]
|
||||
|
||||
updates: list[ChatResponseUpdate] = []
|
||||
async for update in client.get_response(messages, stream=True):
|
||||
@@ -317,7 +315,7 @@ class TestAGUIChatClient:
|
||||
client = TestableAGUIChatClient(endpoint="http://localhost:8888/")
|
||||
monkeypatch.setattr(client.http_service, "post_run", mock_post_run)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Test server tool execution")]
|
||||
messages = [Message(role="user", text="Test server tool execution")]
|
||||
|
||||
async for _ in client.get_response(
|
||||
messages, stream=True, options={"tool_choice": "auto", "tools": [client_tool]}
|
||||
@@ -333,8 +331,8 @@ class TestAGUIChatClient:
|
||||
state_b64 = base64.b64encode(state_json.encode("utf-8")).decode("utf-8")
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="user", text="Hello"),
|
||||
ChatMessage(
|
||||
Message(role="user", text="Hello"),
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_uri(uri=f"data:application/json;base64,{state_b64}")],
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections.abc import AsyncIterator, MutableSequence
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatAgent, ChatMessage, ChatOptions, ChatResponseUpdate, Content
|
||||
from agent_framework import Agent, ChatOptions, ChatResponseUpdate, Content, Message
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ async def test_agent_initialization_basic(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent[ChatOptions](
|
||||
chat_client=streaming_chat_client_stub(stream_fn),
|
||||
agent = Agent[ChatOptions](
|
||||
client=streaming_chat_client_stub(stream_fn),
|
||||
name="test_agent",
|
||||
instructions="Test",
|
||||
)
|
||||
@@ -38,11 +38,11 @@ async def test_agent_initialization_with_state_schema(streaming_chat_client_stub
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
state_schema: dict[str, dict[str, Any]] = {"document": {"type": "string"}}
|
||||
wrapper = AgentFrameworkAgent(agent=agent, state_schema=state_schema)
|
||||
|
||||
@@ -54,11 +54,11 @@ async def test_agent_initialization_with_predict_state_config(streaming_chat_cli
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
predict_config = {"document": {"tool": "write_doc", "tool_argument": "content"}}
|
||||
wrapper = AgentFrameworkAgent(agent=agent, predict_state_config=predict_config)
|
||||
|
||||
@@ -70,7 +70,7 @@ async def test_agent_initialization_with_pydantic_state_schema(streaming_chat_cl
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
@@ -78,7 +78,7 @@ async def test_agent_initialization_with_pydantic_state_schema(streaming_chat_cl
|
||||
document: str
|
||||
tags: list[str] = []
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
|
||||
wrapper_class_schema = AgentFrameworkAgent(agent=agent, state_schema=MyState)
|
||||
wrapper_instance_schema = AgentFrameworkAgent(agent=agent, state_schema=MyState(document="hi"))
|
||||
@@ -93,11 +93,11 @@ async def test_run_started_event_emission(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data = {"messages": [{"role": "user", "content": "Hi"}]}
|
||||
@@ -117,11 +117,11 @@ async def test_predict_state_custom_event_emission(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
predict_config = {
|
||||
"document": {"tool": "write_doc", "tool_argument": "content"},
|
||||
"summary": {"tool": "summarize", "tool_argument": "text"},
|
||||
@@ -149,11 +149,11 @@ async def test_initial_state_snapshot_with_schema(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
state_schema = {"document": {"type": "string"}}
|
||||
wrapper = AgentFrameworkAgent(agent=agent, state_schema=state_schema)
|
||||
|
||||
@@ -179,11 +179,11 @@ async def test_state_initialization_object_type(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
state_schema: dict[str, dict[str, Any]] = {"recipe": {"type": "object", "properties": {}}}
|
||||
wrapper = AgentFrameworkAgent(agent=agent, state_schema=state_schema)
|
||||
|
||||
@@ -206,11 +206,11 @@ async def test_state_initialization_array_type(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
state_schema: dict[str, dict[str, Any]] = {"steps": {"type": "array", "items": {}}}
|
||||
wrapper = AgentFrameworkAgent(agent=agent, state_schema=state_schema)
|
||||
|
||||
@@ -233,11 +233,11 @@ async def test_run_finished_event_emission(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data = {"messages": [{"role": "user", "content": "Hi"}]}
|
||||
@@ -255,11 +255,11 @@ async def test_tool_result_confirm_changes_accepted(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Document updated")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(
|
||||
agent=agent,
|
||||
state_schema={"document": {"type": "string"}},
|
||||
@@ -302,11 +302,11 @@ async def test_tool_result_confirm_changes_rejected(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="OK")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
# Simulate tool result message with rejection
|
||||
@@ -336,11 +336,11 @@ async def test_tool_result_function_approval_accepted(streaming_chat_client_stub
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="OK")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
# Simulate tool result with multiple steps
|
||||
@@ -382,11 +382,11 @@ async def test_tool_result_function_approval_rejected(streaming_chat_client_stub
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="OK")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
# Simulate tool result rejection with steps
|
||||
@@ -425,13 +425,13 @@ async def test_thread_metadata_tracking(streaming_chat_client_stub):
|
||||
captured_options: dict[str, Any] = {}
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
# Capture options to verify internal keys are NOT passed to chat client
|
||||
captured_options.update(options)
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data = {
|
||||
@@ -445,7 +445,7 @@ async def test_thread_metadata_tracking(streaming_chat_client_stub):
|
||||
events.append(event)
|
||||
|
||||
# AG-UI internal metadata should be stored in thread.metadata
|
||||
thread = agent.chat_client.last_thread
|
||||
thread = agent.client.last_thread
|
||||
thread_metadata = thread.metadata if thread and hasattr(thread, "metadata") else {}
|
||||
assert thread_metadata.get("ag_ui_thread_id") == "test_thread_123"
|
||||
assert thread_metadata.get("ag_ui_run_id") == "test_run_456"
|
||||
@@ -467,13 +467,13 @@ async def test_state_context_injection(streaming_chat_client_stub):
|
||||
captured_options: dict[str, Any] = {}
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
# Capture options to verify internal keys are NOT passed to chat client
|
||||
captured_options.update(options)
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(
|
||||
agent=agent,
|
||||
state_schema={"document": {"type": "string"}},
|
||||
@@ -489,7 +489,7 @@ async def test_state_context_injection(streaming_chat_client_stub):
|
||||
events.append(event)
|
||||
|
||||
# Current state should be stored in thread.metadata
|
||||
thread = agent.chat_client.last_thread
|
||||
thread = agent.client.last_thread
|
||||
thread_metadata = thread.metadata if thread and hasattr(thread, "metadata") else {}
|
||||
current_state = thread_metadata.get("current_state")
|
||||
if isinstance(current_state, str):
|
||||
@@ -506,11 +506,11 @@ async def test_no_messages_provided(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data: dict[str, Any] = {"messages": []}
|
||||
@@ -530,11 +530,11 @@ async def test_message_end_event_emission(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Hello world")])
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data: dict[str, Any] = {"messages": [{"role": "user", "content": "Hi"}]}
|
||||
@@ -558,13 +558,13 @@ async def test_error_handling_with_exception(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
if False:
|
||||
yield ChatResponseUpdate(contents=[])
|
||||
raise RuntimeError("Simulated failure")
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
input_data: dict[str, Any] = {"messages": [{"role": "user", "content": "Hi"}]}
|
||||
@@ -579,13 +579,13 @@ async def test_json_decode_error_in_tool_result(streaming_chat_client_stub):
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: dict[str, Any], **kwargs: Any
|
||||
messages: MutableSequence[Message], options: dict[str, Any], **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
if False:
|
||||
yield ChatResponseUpdate(contents=[])
|
||||
raise AssertionError("ChatClient should not be called with orphaned tool result")
|
||||
|
||||
agent = ChatAgent(name="test_agent", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test_agent", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
# Send invalid JSON as tool result without preceding tool call
|
||||
@@ -618,13 +618,13 @@ async def test_agent_with_use_service_thread_is_false(streaming_chat_client_stub
|
||||
request_service_thread_id: str | None = None
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], chat_options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(
|
||||
contents=[Content.from_text(text="Response")], response_id="resp_67890", conversation_id="conv_12345"
|
||||
)
|
||||
|
||||
agent = ChatAgent(chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent, use_service_thread=False)
|
||||
|
||||
input_data = {"messages": [{"role": "user", "content": "Hi"}], "thread_id": "conv_123456"}
|
||||
@@ -642,7 +642,7 @@ async def test_agent_with_use_service_thread_is_true(streaming_chat_client_stub)
|
||||
request_service_thread_id: str | None = None
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], chat_options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
nonlocal request_service_thread_id
|
||||
thread = kwargs.get("thread")
|
||||
@@ -651,7 +651,7 @@ async def test_agent_with_use_service_thread_is_true(streaming_chat_client_stub)
|
||||
contents=[Content.from_text(text="Response")], response_id="resp_67890", conversation_id="conv_12345"
|
||||
)
|
||||
|
||||
agent = ChatAgent(chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(client=streaming_chat_client_stub(stream_fn))
|
||||
wrapper = AgentFrameworkAgent(agent=agent, use_service_thread=True)
|
||||
|
||||
input_data = {"messages": [{"role": "user", "content": "Hi"}], "thread_id": "conv_123456"}
|
||||
@@ -659,7 +659,7 @@ async def test_agent_with_use_service_thread_is_true(streaming_chat_client_stub)
|
||||
events: list[Any] = []
|
||||
async for event in wrapper.run_agent(input_data):
|
||||
events.append(event)
|
||||
request_service_thread_id = agent.chat_client.last_service_thread_id
|
||||
request_service_thread_id = agent.client.last_service_thread_id
|
||||
assert request_service_thread_id == "conv_123456" # type: ignore[attr-defined] (service_thread_id should be set)
|
||||
|
||||
|
||||
@@ -679,15 +679,15 @@ async def test_function_approval_mode_executes_tool(streaming_chat_client_stub):
|
||||
return "2025/12/01 12:00:00"
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
# Capture the messages received by the chat client
|
||||
messages_received.clear()
|
||||
messages_received.extend(messages)
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Processing completed")])
|
||||
|
||||
agent = ChatAgent(
|
||||
chat_client=streaming_chat_client_stub(stream_fn),
|
||||
agent = Agent(
|
||||
client=streaming_chat_client_stub(stream_fn),
|
||||
name="test_agent",
|
||||
instructions="Test",
|
||||
tools=[get_datetime],
|
||||
@@ -770,17 +770,17 @@ async def test_function_approval_mode_rejection(streaming_chat_client_stub):
|
||||
return "All data deleted"
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
# Capture the messages received by the chat client
|
||||
messages_received.clear()
|
||||
messages_received.extend(messages)
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text="Operation cancelled")])
|
||||
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="test_agent",
|
||||
instructions="Test",
|
||||
chat_client=streaming_chat_client_stub(stream_fn),
|
||||
client=streaming_chat_client_stub(stream_fn),
|
||||
tools=[delete_all_data],
|
||||
)
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatAgent, ChatResponseUpdate, Content
|
||||
from agent_framework import Agent, ChatResponseUpdate, Content
|
||||
from fastapi import FastAPI, Header, HTTPException
|
||||
from fastapi.params import Depends
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -28,7 +28,7 @@ def build_chat_client(streaming_chat_client_stub, stream_from_updates_fixture):
|
||||
async def test_add_endpoint_with_agent_protocol(build_chat_client):
|
||||
"""Test adding endpoint with raw SupportsAgentRun."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/test-agent")
|
||||
|
||||
@@ -42,7 +42,7 @@ async def test_add_endpoint_with_agent_protocol(build_chat_client):
|
||||
async def test_add_endpoint_with_wrapped_agent(build_chat_client):
|
||||
"""Test adding endpoint with pre-wrapped AgentFrameworkAgent."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
wrapped_agent = AgentFrameworkAgent(agent=agent, name="wrapped")
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, wrapped_agent, path="/wrapped-agent")
|
||||
@@ -57,7 +57,7 @@ async def test_add_endpoint_with_wrapped_agent(build_chat_client):
|
||||
async def test_endpoint_with_state_schema(build_chat_client):
|
||||
"""Test endpoint with state_schema parameter."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
state_schema = {"document": {"type": "string"}}
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/stateful", state_schema=state_schema)
|
||||
@@ -73,7 +73,7 @@ async def test_endpoint_with_state_schema(build_chat_client):
|
||||
async def test_endpoint_with_default_state_seed(build_chat_client):
|
||||
"""Test endpoint seeds default state when client omits it."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
state_schema = {"proverbs": {"type": "array"}}
|
||||
default_state = {"proverbs": ["Keep the original."]}
|
||||
|
||||
@@ -100,7 +100,7 @@ async def test_endpoint_with_default_state_seed(build_chat_client):
|
||||
async def test_endpoint_with_predict_state_config(build_chat_client):
|
||||
"""Test endpoint with predict_state_config parameter."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
predict_config = {"document": {"tool": "write_doc", "tool_argument": "content"}}
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/predictive", predict_state_config=predict_config)
|
||||
@@ -114,7 +114,7 @@ async def test_endpoint_with_predict_state_config(build_chat_client):
|
||||
async def test_endpoint_request_logging(build_chat_client):
|
||||
"""Test that endpoint logs request details."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/logged")
|
||||
|
||||
@@ -134,7 +134,7 @@ async def test_endpoint_request_logging(build_chat_client):
|
||||
async def test_endpoint_event_streaming(build_chat_client):
|
||||
"""Test that endpoint streams events correctly."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client("Streamed response"))
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client("Streamed response"))
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/stream")
|
||||
|
||||
@@ -168,7 +168,7 @@ async def test_endpoint_event_streaming(build_chat_client):
|
||||
async def test_endpoint_error_handling(build_chat_client):
|
||||
"""Test endpoint error handling during request parsing."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/failing")
|
||||
|
||||
@@ -184,8 +184,8 @@ async def test_endpoint_error_handling(build_chat_client):
|
||||
async def test_endpoint_multiple_paths(build_chat_client):
|
||||
"""Test adding multiple endpoints with different paths."""
|
||||
app = FastAPI()
|
||||
agent1 = ChatAgent(name="agent1", instructions="First agent", chat_client=build_chat_client("Response 1"))
|
||||
agent2 = ChatAgent(name="agent2", instructions="Second agent", chat_client=build_chat_client("Response 2"))
|
||||
agent1 = Agent(name="agent1", instructions="First agent", client=build_chat_client("Response 1"))
|
||||
agent2 = Agent(name="agent2", instructions="Second agent", client=build_chat_client("Response 2"))
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent1, path="/agent1")
|
||||
add_agent_framework_fastapi_endpoint(app, agent2, path="/agent2")
|
||||
@@ -202,7 +202,7 @@ async def test_endpoint_multiple_paths(build_chat_client):
|
||||
async def test_endpoint_default_path(build_chat_client):
|
||||
"""Test endpoint with default path."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent)
|
||||
|
||||
@@ -215,7 +215,7 @@ async def test_endpoint_default_path(build_chat_client):
|
||||
async def test_endpoint_response_headers(build_chat_client):
|
||||
"""Test that endpoint sets correct response headers."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/headers")
|
||||
|
||||
@@ -231,7 +231,7 @@ async def test_endpoint_response_headers(build_chat_client):
|
||||
async def test_endpoint_empty_messages(build_chat_client):
|
||||
"""Test endpoint with empty messages list."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/empty")
|
||||
|
||||
@@ -244,7 +244,7 @@ async def test_endpoint_empty_messages(build_chat_client):
|
||||
async def test_endpoint_complex_input(build_chat_client):
|
||||
"""Test endpoint with complex input data."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/complex")
|
||||
|
||||
@@ -269,7 +269,7 @@ async def test_endpoint_complex_input(build_chat_client):
|
||||
async def test_endpoint_openapi_schema(build_chat_client):
|
||||
"""Test that endpoint generates proper OpenAPI schema with request model."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/schema-test")
|
||||
|
||||
@@ -313,7 +313,7 @@ async def test_endpoint_openapi_schema(build_chat_client):
|
||||
async def test_endpoint_default_tags(build_chat_client):
|
||||
"""Test that endpoint uses default 'AG-UI' tag."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/default-tags")
|
||||
|
||||
@@ -331,7 +331,7 @@ async def test_endpoint_default_tags(build_chat_client):
|
||||
async def test_endpoint_custom_tags(build_chat_client):
|
||||
"""Test that endpoint accepts custom tags."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/custom-tags", tags=["Custom", "Agent"])
|
||||
|
||||
@@ -349,7 +349,7 @@ async def test_endpoint_custom_tags(build_chat_client):
|
||||
async def test_endpoint_missing_required_field(build_chat_client):
|
||||
"""Test that endpoint validates required fields with Pydantic."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/validation")
|
||||
|
||||
@@ -368,7 +368,7 @@ async def test_endpoint_internal_error_handling(build_chat_client):
|
||||
from unittest.mock import patch
|
||||
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
# Use default_state to trigger the code path that can raise an exception
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/error-test", default_state={"key": "value"})
|
||||
@@ -387,7 +387,7 @@ async def test_endpoint_internal_error_handling(build_chat_client):
|
||||
async def test_endpoint_with_dependencies_blocks_unauthorized(build_chat_client):
|
||||
"""Test that endpoint blocks requests when authentication dependency fails."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
async def require_api_key(x_api_key: str | None = Header(None)):
|
||||
if x_api_key != "secret-key":
|
||||
@@ -406,7 +406,7 @@ async def test_endpoint_with_dependencies_blocks_unauthorized(build_chat_client)
|
||||
async def test_endpoint_with_dependencies_allows_authorized(build_chat_client):
|
||||
"""Test that endpoint allows requests when authentication dependency passes."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
async def require_api_key(x_api_key: str | None = Header(None)):
|
||||
if x_api_key != "secret-key":
|
||||
@@ -429,7 +429,7 @@ async def test_endpoint_with_dependencies_allows_authorized(build_chat_client):
|
||||
async def test_endpoint_with_multiple_dependencies(build_chat_client):
|
||||
"""Test that endpoint supports multiple dependencies."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
execution_order: list[str] = []
|
||||
|
||||
@@ -457,7 +457,7 @@ async def test_endpoint_with_multiple_dependencies(build_chat_client):
|
||||
async def test_endpoint_without_dependencies_is_accessible(build_chat_client):
|
||||
"""Test that endpoint without dependencies remains accessible (backward compatibility)."""
|
||||
app = FastAPI()
|
||||
agent = ChatAgent(name="test", instructions="Test agent", chat_client=build_chat_client())
|
||||
agent = Agent(name="test", instructions="Test agent", client=build_chat_client())
|
||||
|
||||
# No dependencies parameter - should be accessible without auth
|
||||
add_agent_framework_fastapi_endpoint(app, agent, path="/open")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
"""Tests for orchestration helper functions."""
|
||||
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
|
||||
from agent_framework_ag_ui._orchestration._helpers import (
|
||||
approval_steps,
|
||||
@@ -29,8 +29,8 @@ class TestPendingToolCallIds:
|
||||
def test_no_tool_calls(self):
|
||||
"""Returns empty set when no tool calls in messages."""
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[Content.from_text("Hello")]),
|
||||
ChatMessage(role="assistant", contents=[Content.from_text("Hi there")]),
|
||||
Message(role="user", contents=[Content.from_text("Hello")]),
|
||||
Message(role="assistant", contents=[Content.from_text("Hi there")]),
|
||||
]
|
||||
result = pending_tool_call_ids(messages)
|
||||
assert result == set()
|
||||
@@ -38,7 +38,7 @@ class TestPendingToolCallIds:
|
||||
def test_pending_tool_call(self):
|
||||
"""Returns pending tool call ID when no result exists."""
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_function_call(call_id="call_123", name="get_weather", arguments="{}")],
|
||||
),
|
||||
@@ -49,11 +49,11 @@ class TestPendingToolCallIds:
|
||||
def test_resolved_tool_call(self):
|
||||
"""Returns empty set when tool call has result."""
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_function_call(call_id="call_123", name="get_weather", arguments="{}")],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call_123", result="sunny")],
|
||||
),
|
||||
@@ -64,7 +64,7 @@ class TestPendingToolCallIds:
|
||||
def test_multiple_tool_calls_some_resolved(self):
|
||||
"""Returns only unresolved tool call IDs."""
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="call_1", name="tool_a", arguments="{}"),
|
||||
@@ -72,11 +72,11 @@ class TestPendingToolCallIds:
|
||||
Content.from_function_call(call_id="call_3", name="tool_c", arguments="{}"),
|
||||
],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call_1", result="result_a")],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call_3", result="result_c")],
|
||||
),
|
||||
@@ -90,7 +90,7 @@ class TestIsStateContextMessage:
|
||||
|
||||
def test_state_context_message(self):
|
||||
"""Returns True for state context message."""
|
||||
message = ChatMessage(
|
||||
message = Message(
|
||||
role="system",
|
||||
contents=[Content.from_text("Current state of the application: {}")],
|
||||
)
|
||||
@@ -98,7 +98,7 @@ class TestIsStateContextMessage:
|
||||
|
||||
def test_non_system_message(self):
|
||||
"""Returns False for non-system message."""
|
||||
message = ChatMessage(
|
||||
message = Message(
|
||||
role="user",
|
||||
contents=[Content.from_text("Current state of the application: {}")],
|
||||
)
|
||||
@@ -106,7 +106,7 @@ class TestIsStateContextMessage:
|
||||
|
||||
def test_system_message_without_state_prefix(self):
|
||||
"""Returns False for system message without state prefix."""
|
||||
message = ChatMessage(
|
||||
message = Message(
|
||||
role="system",
|
||||
contents=[Content.from_text("You are a helpful assistant.")],
|
||||
)
|
||||
@@ -114,7 +114,7 @@ class TestIsStateContextMessage:
|
||||
|
||||
def test_empty_contents(self):
|
||||
"""Returns False for message with empty contents."""
|
||||
message = ChatMessage(role="system", contents=[])
|
||||
message = Message(role="system", contents=[])
|
||||
assert is_state_context_message(message) is False
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ class TestLatestApprovalResponse:
|
||||
def test_no_approval_response(self):
|
||||
"""Returns None when no approval response in last message."""
|
||||
messages = [
|
||||
ChatMessage(role="assistant", contents=[Content.from_text("Hello")]),
|
||||
Message(role="assistant", contents=[Content.from_text("Hello")]),
|
||||
]
|
||||
result = latest_approval_response(messages)
|
||||
assert result is None
|
||||
@@ -357,7 +357,7 @@ class TestLatestApprovalResponse:
|
||||
function_call=fc,
|
||||
)
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[approval_content]),
|
||||
Message(role="user", contents=[approval_content]),
|
||||
]
|
||||
result = latest_approval_response(messages)
|
||||
assert result is approval_content
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
|
||||
from agent_framework_ag_ui._message_adapters import (
|
||||
agent_framework_messages_to_agui,
|
||||
@@ -24,7 +24,7 @@ def sample_agui_message():
|
||||
@pytest.fixture
|
||||
def sample_agent_framework_message():
|
||||
"""Create a sample Agent Framework message."""
|
||||
return ChatMessage(role="user", contents=[Content.from_text(text="Hello")], message_id="msg-123")
|
||||
return Message(role="user", contents=[Content.from_text(text="Hello")], message_id="msg-123")
|
||||
|
||||
|
||||
def test_agui_to_agent_framework_basic(sample_agui_message):
|
||||
@@ -100,7 +100,7 @@ def test_agui_tool_result_to_agent_framework():
|
||||
def test_agui_tool_approval_updates_tool_call_arguments():
|
||||
"""Tool approval updates matching tool call arguments for snapshots and agent context.
|
||||
|
||||
The LLM context (ChatMessage) should contain only enabled steps, so the LLM
|
||||
The LLM context (Message) should contain only enabled steps, so the LLM
|
||||
generates responses based on what was actually approved/executed.
|
||||
|
||||
The raw messages (for MESSAGES_SNAPSHOT) should contain all steps with status,
|
||||
@@ -446,7 +446,7 @@ def test_agui_with_tool_calls_to_agent_framework():
|
||||
|
||||
def test_agent_framework_to_agui_with_tool_calls():
|
||||
"""Test converting Agent Framework message with tool calls to AG-UI."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_text(text="Calling tool"),
|
||||
@@ -471,7 +471,7 @@ def test_agent_framework_to_agui_with_tool_calls():
|
||||
|
||||
def test_agent_framework_to_agui_multiple_text_contents():
|
||||
"""Test concatenating multiple text contents."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_text(text="Part 1 "), Content.from_text(text="Part 2")],
|
||||
)
|
||||
@@ -484,7 +484,7 @@ def test_agent_framework_to_agui_multiple_text_contents():
|
||||
|
||||
def test_agent_framework_to_agui_no_message_id():
|
||||
"""Test message without message_id - should auto-generate ID."""
|
||||
msg = ChatMessage(role="user", contents=[Content.from_text(text="Hello")])
|
||||
msg = Message(role="user", contents=[Content.from_text(text="Hello")])
|
||||
|
||||
messages = agent_framework_messages_to_agui([msg])
|
||||
|
||||
@@ -496,7 +496,7 @@ def test_agent_framework_to_agui_no_message_id():
|
||||
|
||||
def test_agent_framework_to_agui_system_role():
|
||||
"""Test system role conversion."""
|
||||
msg = ChatMessage(role="system", contents=[Content.from_text(text="System")])
|
||||
msg = Message(role="system", contents=[Content.from_text(text="System")])
|
||||
|
||||
messages = agent_framework_messages_to_agui([msg])
|
||||
|
||||
@@ -541,7 +541,7 @@ def test_extract_text_from_custom_contents():
|
||||
|
||||
def test_agent_framework_to_agui_function_result_dict():
|
||||
"""Test converting FunctionResultContent with dict result to AG-UI."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-123", result={"key": "value", "count": 42})],
|
||||
message_id="msg-789",
|
||||
@@ -558,7 +558,7 @@ def test_agent_framework_to_agui_function_result_dict():
|
||||
|
||||
def test_agent_framework_to_agui_function_result_none():
|
||||
"""Test converting FunctionResultContent with None result to AG-UI."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-123", result=None)],
|
||||
message_id="msg-789",
|
||||
@@ -574,7 +574,7 @@ def test_agent_framework_to_agui_function_result_none():
|
||||
|
||||
def test_agent_framework_to_agui_function_result_string():
|
||||
"""Test converting FunctionResultContent with string result to AG-UI."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-123", result="plain text result")],
|
||||
message_id="msg-789",
|
||||
@@ -589,7 +589,7 @@ def test_agent_framework_to_agui_function_result_string():
|
||||
|
||||
def test_agent_framework_to_agui_function_result_empty_list():
|
||||
"""Test converting FunctionResultContent with empty list result to AG-UI."""
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-123", result=[])],
|
||||
message_id="msg-789",
|
||||
@@ -611,7 +611,7 @@ def test_agent_framework_to_agui_function_result_single_text_content():
|
||||
class MockTextContent:
|
||||
text: str
|
||||
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-123", result=[MockTextContent("Hello from MCP!")])],
|
||||
message_id="msg-789",
|
||||
@@ -633,7 +633,7 @@ def test_agent_framework_to_agui_function_result_multiple_text_contents():
|
||||
class MockTextContent:
|
||||
text: str
|
||||
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
|
||||
from agent_framework_ag_ui._message_adapters import _deduplicate_messages, _sanitize_tool_history
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_sanitize_tool_history_filters_out_confirm_changes_only_message() -> Non
|
||||
tool for the approval UI flow that shouldn't be sent to the LLM.
|
||||
"""
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
@@ -23,7 +23,7 @@ def test_sanitize_tool_history_filters_out_confirm_changes_only_message() -> Non
|
||||
)
|
||||
],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text='{"accepted": true}')],
|
||||
),
|
||||
@@ -44,11 +44,11 @@ def test_sanitize_tool_history_filters_out_confirm_changes_only_message() -> Non
|
||||
|
||||
def test_deduplicate_messages_prefers_non_empty_tool_results() -> None:
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call1", result="")],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call1", result="result data")],
|
||||
),
|
||||
@@ -71,13 +71,13 @@ def test_convert_approval_results_to_tool_messages() -> None:
|
||||
# Simulate what happens after _resolve_approval_responses:
|
||||
# A user message contains function_result content (the executed tool result)
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="call_123", name="my_mcp_tool", arguments="{}"),
|
||||
],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_function_result(call_id="call_123", result="tool execution result"),
|
||||
@@ -109,13 +109,13 @@ def test_convert_approval_results_preserves_other_user_content() -> None:
|
||||
from agent_framework_ag_ui._run import _convert_approval_results_to_tool_messages
|
||||
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="call_123", name="my_tool", arguments="{}"),
|
||||
],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text(text="User also said something"),
|
||||
@@ -152,12 +152,12 @@ def test_sanitize_tool_history_filters_confirm_changes_keeps_other_tools() -> No
|
||||
"""
|
||||
messages = [
|
||||
# User asks something
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text="What time is it?")],
|
||||
),
|
||||
# Assistant calls MCP tool + confirm_changes
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="call_1", name="get_datetime", arguments="{}"),
|
||||
@@ -165,12 +165,12 @@ def test_sanitize_tool_history_filters_confirm_changes_keeps_other_tools() -> No
|
||||
],
|
||||
),
|
||||
# Tool result for the actual MCP tool
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call_1", result="2024-01-01 12:00:00")],
|
||||
),
|
||||
# User asks something else
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text="What's the date?")],
|
||||
),
|
||||
@@ -204,12 +204,12 @@ def test_sanitize_tool_history_filters_confirm_changes_from_assistant_messages()
|
||||
respond with "Here's your 5-step plan" instead of "Here's your 2-step plan".
|
||||
"""
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text="Build a robot")],
|
||||
),
|
||||
# Assistant message with both generate_task_steps and confirm_changes
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
@@ -225,7 +225,7 @@ def test_sanitize_tool_history_filters_confirm_changes_from_assistant_messages()
|
||||
],
|
||||
),
|
||||
# Approval response
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_function_approval_response(
|
||||
|
||||
@@ -6,7 +6,7 @@ from ag_ui.core import (
|
||||
TextMessageEndEvent,
|
||||
TextMessageStartEvent,
|
||||
)
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
|
||||
from agent_framework_ag_ui._run import (
|
||||
FlowState,
|
||||
@@ -212,7 +212,7 @@ class TestInjectStateContext:
|
||||
|
||||
def test_no_state_message(self):
|
||||
"""Returns original messages when no state context needed."""
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text("Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text("Hello")])]
|
||||
result = _inject_state_context(messages, {}, {})
|
||||
assert result == messages
|
||||
|
||||
@@ -224,8 +224,8 @@ class TestInjectStateContext:
|
||||
def test_last_message_not_user(self):
|
||||
"""Returns original messages when last message is not from user."""
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[Content.from_text("Hello")]),
|
||||
ChatMessage(role="assistant", contents=[Content.from_text("Hi")]),
|
||||
Message(role="user", contents=[Content.from_text("Hello")]),
|
||||
Message(role="assistant", contents=[Content.from_text("Hi")]),
|
||||
]
|
||||
state = {"key": "value"}
|
||||
schema = {"properties": {"key": {"type": "string"}}}
|
||||
@@ -237,8 +237,8 @@ class TestInjectStateContext:
|
||||
"""Injects state context before last user message."""
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", contents=[Content.from_text("You are helpful")]),
|
||||
ChatMessage(role="user", contents=[Content.from_text("Hello")]),
|
||||
Message(role="system", contents=[Content.from_text("You are helpful")]),
|
||||
Message(role="user", contents=[Content.from_text("Hello")]),
|
||||
]
|
||||
state = {"document": "content"}
|
||||
schema = {"properties": {"document": {"type": "string"}}}
|
||||
@@ -405,7 +405,7 @@ def test_extract_approved_state_updates_no_handler():
|
||||
"""Test _extract_approved_state_updates returns empty with no handler."""
|
||||
from agent_framework_ag_ui._run import _extract_approved_state_updates
|
||||
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text("Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text("Hello")])]
|
||||
result = _extract_approved_state_updates(messages, None)
|
||||
assert result == {}
|
||||
|
||||
@@ -416,7 +416,7 @@ def test_extract_approved_state_updates_no_approval():
|
||||
from agent_framework_ag_ui._run import _extract_approved_state_updates
|
||||
|
||||
handler = PredictiveStateHandler(predict_state_config={"doc": {"tool": "write", "tool_argument": "content"}})
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text("Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text("Hello")])]
|
||||
result = _extract_approved_state_updates(messages, handler)
|
||||
assert result == {}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
from collections.abc import AsyncIterator, MutableSequence
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ChatAgent, ChatMessage, ChatOptions, ChatResponseUpdate, Content
|
||||
from agent_framework import Agent, ChatOptions, ChatResponseUpdate, Content, Message
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ async def test_structured_output_with_recipe(streaming_chat_client_stub, stream_
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(
|
||||
contents=[Content.from_text(text='{"recipe": {"name": "Pasta"}, "message": "Here is your recipe"}')]
|
||||
)
|
||||
|
||||
agent = ChatAgent(name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
agent.default_options = ChatOptions(response_format=RecipeOutput)
|
||||
|
||||
wrapper = AgentFrameworkAgent(
|
||||
@@ -73,7 +73,7 @@ async def test_structured_output_with_steps(streaming_chat_client_stub, stream_f
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
steps_data = {
|
||||
"steps": [
|
||||
@@ -83,7 +83,7 @@ async def test_structured_output_with_steps(streaming_chat_client_stub, stream_f
|
||||
}
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text=json.dumps(steps_data))])
|
||||
|
||||
agent = ChatAgent(name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
agent.default_options = ChatOptions(response_format=StepsOutput)
|
||||
|
||||
wrapper = AgentFrameworkAgent(
|
||||
@@ -116,8 +116,8 @@ async def test_structured_output_with_no_schema_match(streaming_chat_client_stub
|
||||
ChatResponseUpdate(contents=[Content.from_text(text='{"data": {"key": "value"}}')]),
|
||||
]
|
||||
|
||||
agent = ChatAgent(
|
||||
name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_from_updates_fixture(updates))
|
||||
agent = Agent(
|
||||
name="test", instructions="Test", client=streaming_chat_client_stub(stream_from_updates_fixture(updates))
|
||||
)
|
||||
agent.default_options = ChatOptions(response_format=GenericOutput)
|
||||
|
||||
@@ -149,11 +149,11 @@ async def test_structured_output_without_schema(streaming_chat_client_stub, stre
|
||||
info: str
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text='{"data": {"key": "value"}, "info": "processed"}')])
|
||||
|
||||
agent = ChatAgent(name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
agent.default_options = ChatOptions(response_format=DataOutput)
|
||||
|
||||
wrapper = AgentFrameworkAgent(
|
||||
@@ -182,10 +182,10 @@ async def test_no_structured_output_when_no_response_format(streaming_chat_clien
|
||||
|
||||
updates = [ChatResponseUpdate(contents=[Content.from_text(text="Regular text")])]
|
||||
|
||||
agent = ChatAgent(
|
||||
agent = Agent(
|
||||
name="test",
|
||||
instructions="Test",
|
||||
chat_client=streaming_chat_client_stub(stream_from_updates_fixture(updates)),
|
||||
client=streaming_chat_client_stub(stream_from_updates_fixture(updates)),
|
||||
)
|
||||
# No response_format set
|
||||
|
||||
@@ -208,12 +208,12 @@ async def test_structured_output_with_message_field(streaming_chat_client_stub,
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
output_data = {"recipe": {"name": "Salad"}, "message": "Fresh salad recipe ready"}
|
||||
yield ChatResponseUpdate(contents=[Content.from_text(text=json.dumps(output_data))])
|
||||
|
||||
agent = ChatAgent(name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
agent.default_options = ChatOptions(response_format=RecipeOutput)
|
||||
|
||||
wrapper = AgentFrameworkAgent(
|
||||
@@ -243,12 +243,12 @@ async def test_empty_updates_no_structured_processing(streaming_chat_client_stub
|
||||
from agent_framework.ag_ui import AgentFrameworkAgent
|
||||
|
||||
async def stream_fn(
|
||||
messages: MutableSequence[ChatMessage], options: ChatOptions, **kwargs: Any
|
||||
messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterator[ChatResponseUpdate]:
|
||||
if False:
|
||||
yield ChatResponseUpdate(contents=[])
|
||||
|
||||
agent = ChatAgent(name="test", instructions="Test", chat_client=streaming_chat_client_stub(stream_fn))
|
||||
agent = Agent(name="test", instructions="Test", client=streaming_chat_client_stub(stream_fn))
|
||||
agent.default_options = ChatOptions(response_format=RecipeOutput)
|
||||
|
||||
wrapper = AgentFrameworkAgent(agent=agent)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework import Agent, tool
|
||||
|
||||
from agent_framework_ag_ui._orchestration._tooling import (
|
||||
collect_server_tools,
|
||||
@@ -31,14 +31,14 @@ def regular_tool() -> str:
|
||||
return "result"
|
||||
|
||||
|
||||
def _create_chat_agent_with_tool(tool_name: str = "regular_tool") -> ChatAgent:
|
||||
"""Create a ChatAgent with a mocked chat client and a simple tool.
|
||||
def _create_chat_agent_with_tool(tool_name: str = "regular_tool") -> Agent:
|
||||
"""Create a Agent with a mocked chat client and a simple tool.
|
||||
|
||||
Note: tool_name parameter is kept for API compatibility but the tool
|
||||
will always be named 'regular_tool' since tool uses the function name.
|
||||
"""
|
||||
mock_chat_client = MagicMock()
|
||||
return ChatAgent(chat_client=mock_chat_client, tools=[regular_tool])
|
||||
return Agent(client=mock_chat_client, tools=[regular_tool])
|
||||
|
||||
|
||||
def test_merge_tools_filters_duplicates() -> None:
|
||||
@@ -59,7 +59,7 @@ def test_register_additional_client_tools_assigns_when_configured() -> None:
|
||||
mock_chat_client = MagicMock(spec=BaseChatClient)
|
||||
mock_chat_client.function_invocation_configuration = normalize_function_invocation_configuration(None)
|
||||
|
||||
agent = ChatAgent(chat_client=mock_chat_client)
|
||||
agent = Agent(client=mock_chat_client)
|
||||
|
||||
tools = [DummyTool("x")]
|
||||
register_additional_client_tools(agent, tools)
|
||||
@@ -148,14 +148,14 @@ def test_collect_server_tools_no_default_options() -> None:
|
||||
def test_register_additional_client_tools_no_tools() -> None:
|
||||
"""register_additional_client_tools does nothing with None tools."""
|
||||
mock_chat_client = MagicMock()
|
||||
agent = ChatAgent(chat_client=mock_chat_client)
|
||||
agent = Agent(client=mock_chat_client)
|
||||
|
||||
# Should not raise
|
||||
register_additional_client_tools(agent, None)
|
||||
|
||||
|
||||
def test_register_additional_client_tools_no_chat_client() -> None:
|
||||
"""register_additional_client_tools does nothing when agent has no chat_client."""
|
||||
"""register_additional_client_tools does nothing when agent has no client."""
|
||||
from agent_framework_ag_ui._orchestration._tooling import register_additional_client_tools
|
||||
|
||||
class MockAgent:
|
||||
|
||||
@@ -404,11 +404,11 @@ def test_safe_json_parse_with_none():
|
||||
|
||||
def test_get_role_value_with_enum():
|
||||
"""Test get_role_value with enum role."""
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
|
||||
from agent_framework_ag_ui._utils import get_role_value
|
||||
|
||||
message = ChatMessage(role="user", contents=[Content.from_text("test")])
|
||||
message = Message(role="user", contents=[Content.from_text("test")])
|
||||
result = get_role_value(message)
|
||||
assert result == "user"
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from agent_framework import (
|
||||
Annotation,
|
||||
BaseChatClient,
|
||||
ChatAndFunctionMiddlewareTypes,
|
||||
ChatMessage,
|
||||
ChatMiddlewareLayer,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
@@ -24,6 +23,7 @@ from agent_framework import (
|
||||
HostedCodeInterpreterTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Message,
|
||||
ResponseStream,
|
||||
TextSpanRegion,
|
||||
UsageDetails,
|
||||
@@ -356,7 +356,7 @@ class AnthropicClient(
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -385,7 +385,7 @@ class AnthropicClient(
|
||||
|
||||
def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
@@ -430,7 +430,7 @@ class AnthropicClient(
|
||||
run_options["messages"] = self._prepare_messages_for_anthropic(messages)
|
||||
|
||||
# system message - first system message is passed as instructions
|
||||
if messages and isinstance(messages[0], ChatMessage) and messages[0].role == "system":
|
||||
if messages and isinstance(messages[0], Message) and messages[0].role == "system":
|
||||
run_options["system"] = messages[0].text
|
||||
|
||||
# betas
|
||||
@@ -516,22 +516,22 @@ class AnthropicClient(
|
||||
"schema": schema,
|
||||
}
|
||||
|
||||
def _prepare_messages_for_anthropic(self, messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
|
||||
def _prepare_messages_for_anthropic(self, messages: Sequence[Message]) -> list[dict[str, Any]]:
|
||||
"""Prepare a list of ChatMessages for the Anthropic client.
|
||||
|
||||
This skips the first message if it is a system message,
|
||||
as Anthropic expects system instructions as a separate parameter.
|
||||
"""
|
||||
# first system message is passed as instructions
|
||||
if messages and isinstance(messages[0], ChatMessage) and messages[0].role == "system":
|
||||
if messages and isinstance(messages[0], Message) and messages[0].role == "system":
|
||||
return [self._prepare_message_for_anthropic(msg) for msg in messages[1:]]
|
||||
return [self._prepare_message_for_anthropic(msg) for msg in messages]
|
||||
|
||||
def _prepare_message_for_anthropic(self, message: ChatMessage) -> dict[str, Any]:
|
||||
"""Prepare a ChatMessage for the Anthropic client.
|
||||
def _prepare_message_for_anthropic(self, message: Message) -> dict[str, Any]:
|
||||
"""Prepare a Message for the Anthropic client.
|
||||
|
||||
Args:
|
||||
message: The ChatMessage to convert.
|
||||
message: The Message to convert.
|
||||
|
||||
Returns:
|
||||
A dictionary representing the message in Anthropic format.
|
||||
@@ -693,7 +693,7 @@ class AnthropicClient(
|
||||
return ChatResponse(
|
||||
response_id=message.id,
|
||||
messages=[
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=self._parse_contents_from_anthropic(message.content),
|
||||
raw_representation=message,
|
||||
|
||||
@@ -6,14 +6,14 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import (
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Message,
|
||||
SupportsChatGetResponse,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -98,11 +98,11 @@ def test_anthropic_settings_missing_api_key(anthropic_unit_test_env: dict[str, s
|
||||
|
||||
def test_anthropic_client_init_with_client(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test AnthropicClient initialization with existing anthropic_client."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client, model_id="claude-3-5-sonnet-20241022")
|
||||
client = create_test_anthropic_client(mock_anthropic_client, model_id="claude-3-5-sonnet-20241022")
|
||||
|
||||
assert chat_client.anthropic_client is mock_anthropic_client
|
||||
assert chat_client.model_id == "claude-3-5-sonnet-20241022"
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
assert client.anthropic_client is mock_anthropic_client
|
||||
assert client.model_id == "claude-3-5-sonnet-20241022"
|
||||
assert isinstance(client, SupportsChatGetResponse)
|
||||
|
||||
|
||||
def test_anthropic_client_init_auto_create_client(anthropic_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -138,8 +138,8 @@ def test_anthropic_client_init_validation_error() -> None:
|
||||
|
||||
def test_anthropic_client_service_url(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test service_url method."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
assert chat_client.service_url() == "https://api.anthropic.com"
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
assert client.service_url() == "https://api.anthropic.com"
|
||||
|
||||
|
||||
# Message Conversion Tests
|
||||
@@ -147,10 +147,10 @@ def test_anthropic_client_service_url(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
def test_prepare_message_for_anthropic_text(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting text message to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = ChatMessage(role="user", text="Hello, world!")
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = Message(role="user", text="Hello, world!")
|
||||
|
||||
result = chat_client._prepare_message_for_anthropic(message)
|
||||
result = client._prepare_message_for_anthropic(message)
|
||||
|
||||
assert result["role"] == "user"
|
||||
assert len(result["content"]) == 1
|
||||
@@ -160,8 +160,8 @@ def test_prepare_message_for_anthropic_text(mock_anthropic_client: MagicMock) ->
|
||||
|
||||
def test_prepare_message_for_anthropic_function_call(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting function call message to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = ChatMessage(
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
@@ -172,7 +172,7 @@ def test_prepare_message_for_anthropic_function_call(mock_anthropic_client: Magi
|
||||
],
|
||||
)
|
||||
|
||||
result = chat_client._prepare_message_for_anthropic(message)
|
||||
result = client._prepare_message_for_anthropic(message)
|
||||
|
||||
assert result["role"] == "assistant"
|
||||
assert len(result["content"]) == 1
|
||||
@@ -184,8 +184,8 @@ def test_prepare_message_for_anthropic_function_call(mock_anthropic_client: Magi
|
||||
|
||||
def test_prepare_message_for_anthropic_function_result(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting function result message to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = ChatMessage(
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
@@ -195,7 +195,7 @@ def test_prepare_message_for_anthropic_function_result(mock_anthropic_client: Ma
|
||||
],
|
||||
)
|
||||
|
||||
result = chat_client._prepare_message_for_anthropic(message)
|
||||
result = client._prepare_message_for_anthropic(message)
|
||||
|
||||
assert result["role"] == "user"
|
||||
assert len(result["content"]) == 1
|
||||
@@ -209,13 +209,13 @@ def test_prepare_message_for_anthropic_function_result(mock_anthropic_client: Ma
|
||||
|
||||
def test_prepare_message_for_anthropic_text_reasoning(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting text reasoning message to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = ChatMessage(
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_text_reasoning(text="Let me think about this...")],
|
||||
)
|
||||
|
||||
result = chat_client._prepare_message_for_anthropic(message)
|
||||
result = client._prepare_message_for_anthropic(message)
|
||||
|
||||
assert result["role"] == "assistant"
|
||||
assert len(result["content"]) == 1
|
||||
@@ -225,13 +225,13 @@ def test_prepare_message_for_anthropic_text_reasoning(mock_anthropic_client: Mag
|
||||
|
||||
def test_prepare_messages_for_anthropic_with_system(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting messages list with system message."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
messages = [
|
||||
ChatMessage(role="system", text="You are a helpful assistant."),
|
||||
ChatMessage(role="user", text="Hello!"),
|
||||
Message(role="system", text="You are a helpful assistant."),
|
||||
Message(role="user", text="Hello!"),
|
||||
]
|
||||
|
||||
result = chat_client._prepare_messages_for_anthropic(messages)
|
||||
result = client._prepare_messages_for_anthropic(messages)
|
||||
|
||||
# System message should be skipped
|
||||
assert len(result) == 1
|
||||
@@ -241,13 +241,13 @@ def test_prepare_messages_for_anthropic_with_system(mock_anthropic_client: Magic
|
||||
|
||||
def test_prepare_messages_for_anthropic_without_system(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting messages list without system message."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
messages = [
|
||||
ChatMessage(role="user", text="Hello!"),
|
||||
ChatMessage(role="assistant", text="Hi there!"),
|
||||
Message(role="user", text="Hello!"),
|
||||
Message(role="assistant", text="Hi there!"),
|
||||
]
|
||||
|
||||
result = chat_client._prepare_messages_for_anthropic(messages)
|
||||
result = client._prepare_messages_for_anthropic(messages)
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0]["role"] == "user"
|
||||
@@ -259,7 +259,7 @@ def test_prepare_messages_for_anthropic_without_system(mock_anthropic_client: Ma
|
||||
|
||||
def test_prepare_tools_for_anthropic_tool(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting FunctionTool to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(location: Annotated[str, Field(description="Location to get weather for")]) -> str:
|
||||
@@ -267,7 +267,7 @@ def test_prepare_tools_for_anthropic_tool(mock_anthropic_client: MagicMock) -> N
|
||||
return f"Weather for {location}"
|
||||
|
||||
chat_options = ChatOptions(tools=[get_weather])
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "tools" in result
|
||||
@@ -279,10 +279,10 @@ def test_prepare_tools_for_anthropic_tool(mock_anthropic_client: MagicMock) -> N
|
||||
|
||||
def test_prepare_tools_for_anthropic_web_search(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting HostedWebSearchTool to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions(tools=[HostedWebSearchTool()])
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "tools" in result
|
||||
@@ -293,10 +293,10 @@ def test_prepare_tools_for_anthropic_web_search(mock_anthropic_client: MagicMock
|
||||
|
||||
def test_prepare_tools_for_anthropic_code_interpreter(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting HostedCodeInterpreterTool to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions(tools=[HostedCodeInterpreterTool()])
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "tools" in result
|
||||
@@ -307,10 +307,10 @@ def test_prepare_tools_for_anthropic_code_interpreter(mock_anthropic_client: Mag
|
||||
|
||||
def test_prepare_tools_for_anthropic_mcp_tool(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting HostedMCPTool to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions(tools=[HostedMCPTool(name="test-mcp", url="https://example.com/mcp")])
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "mcp_servers" in result
|
||||
@@ -322,7 +322,7 @@ def test_prepare_tools_for_anthropic_mcp_tool(mock_anthropic_client: MagicMock)
|
||||
|
||||
def test_prepare_tools_for_anthropic_mcp_with_auth(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting HostedMCPTool with authorization headers."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions(
|
||||
tools=[
|
||||
HostedMCPTool(
|
||||
@@ -333,7 +333,7 @@ def test_prepare_tools_for_anthropic_mcp_with_auth(mock_anthropic_client: MagicM
|
||||
]
|
||||
)
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "mcp_servers" in result
|
||||
@@ -344,10 +344,10 @@ def test_prepare_tools_for_anthropic_mcp_with_auth(mock_anthropic_client: MagicM
|
||||
|
||||
def test_prepare_tools_for_anthropic_dict_tool(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting dict tool to Anthropic format."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions(tools=[{"type": "custom", "name": "custom_tool", "description": "A custom tool"}])
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is not None
|
||||
assert "tools" in result
|
||||
@@ -357,10 +357,10 @@ def test_prepare_tools_for_anthropic_dict_tool(mock_anthropic_client: MagicMock)
|
||||
|
||||
def test_prepare_tools_for_anthropic_none(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test converting None tools."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
chat_options = ChatOptions()
|
||||
|
||||
result = chat_client._prepare_tools_for_anthropic(chat_options)
|
||||
result = client._prepare_tools_for_anthropic(chat_options)
|
||||
|
||||
assert result is None
|
||||
|
||||
@@ -370,14 +370,14 @@ def test_prepare_tools_for_anthropic_none(mock_anthropic_client: MagicMock) -> N
|
||||
|
||||
async def test_prepare_options_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with basic ChatOptions."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(max_tokens=100, temperature=0.7)
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["model"] == chat_client.model_id
|
||||
assert run_options["model"] == client.model_id
|
||||
assert run_options["max_tokens"] == 100
|
||||
assert run_options["temperature"] == 0.7
|
||||
assert "messages" in run_options
|
||||
@@ -385,15 +385,15 @@ async def test_prepare_options_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
async def test_prepare_options_with_system_message(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with system message."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", text="You are helpful."),
|
||||
ChatMessage(role="user", text="Hello"),
|
||||
Message(role="system", text="You are helpful."),
|
||||
Message(role="user", text="Hello"),
|
||||
]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["system"] == "You are helpful."
|
||||
assert len(run_options["messages"]) == 1 # System message not in messages list
|
||||
@@ -401,25 +401,25 @@ async def test_prepare_options_with_system_message(mock_anthropic_client: MagicM
|
||||
|
||||
async def test_prepare_options_with_tool_choice_auto(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with auto tool choice."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(tool_choice="auto")
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["tool_choice"]["type"] == "auto"
|
||||
|
||||
|
||||
async def test_prepare_options_with_tool_choice_required(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with required tool choice."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
# For required with specific function, need to pass as dict
|
||||
chat_options = ChatOptions(tool_choice={"mode": "required", "required_function_name": "get_weather"})
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["tool_choice"]["type"] == "tool"
|
||||
assert run_options["tool_choice"]["name"] == "get_weather"
|
||||
@@ -427,29 +427,29 @@ async def test_prepare_options_with_tool_choice_required(mock_anthropic_client:
|
||||
|
||||
async def test_prepare_options_with_tool_choice_none(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with none tool choice."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(tool_choice="none")
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["tool_choice"]["type"] == "none"
|
||||
|
||||
|
||||
async def test_prepare_options_with_tools(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with tools."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(location: str) -> str:
|
||||
"""Get weather for a location."""
|
||||
return f"Weather for {location}"
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(tools=[get_weather])
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert "tools" in run_options
|
||||
assert len(run_options["tools"]) == 1
|
||||
@@ -457,24 +457,24 @@ async def test_prepare_options_with_tools(mock_anthropic_client: MagicMock) -> N
|
||||
|
||||
async def test_prepare_options_with_stop_sequences(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with stop sequences."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(stop=["STOP", "END"])
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["stop_sequences"] == ["STOP", "END"]
|
||||
|
||||
|
||||
async def test_prepare_options_with_top_p(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _prepare_options with top_p."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options = ChatOptions(top_p=0.9)
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options)
|
||||
run_options = client._prepare_options(messages, chat_options)
|
||||
|
||||
assert run_options["top_p"] == 0.9
|
||||
|
||||
@@ -485,9 +485,9 @@ async def test_prepare_options_filters_internal_kwargs(mock_anthropic_client: Ma
|
||||
Internal kwargs like _function_middleware_pipeline, thread, and middleware
|
||||
should be filtered out before being passed to the Anthropic API.
|
||||
"""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
chat_options: ChatOptions = {}
|
||||
|
||||
# Simulate internal kwargs that get passed through the middleware pipeline
|
||||
@@ -499,7 +499,7 @@ async def test_prepare_options_filters_internal_kwargs(mock_anthropic_client: Ma
|
||||
"middleware": [object()],
|
||||
}
|
||||
|
||||
run_options = chat_client._prepare_options(messages, chat_options, **internal_kwargs)
|
||||
run_options = client._prepare_options(messages, chat_options, **internal_kwargs)
|
||||
|
||||
# Internal kwargs should be filtered out
|
||||
assert "_function_middleware_pipeline" not in run_options
|
||||
@@ -514,7 +514,7 @@ async def test_prepare_options_filters_internal_kwargs(mock_anthropic_client: Ma
|
||||
|
||||
def test_process_message_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _process_message with basic text response."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
mock_message = MagicMock(spec=BetaMessage)
|
||||
mock_message.id = "msg_123"
|
||||
@@ -523,7 +523,7 @@ def test_process_message_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
mock_message.usage = BetaUsage(input_tokens=10, output_tokens=5)
|
||||
mock_message.stop_reason = "end_turn"
|
||||
|
||||
response = chat_client._process_message(mock_message, {})
|
||||
response = client._process_message(mock_message, {})
|
||||
|
||||
assert response.response_id == "msg_123"
|
||||
assert response.model_id == "claude-3-5-sonnet-20241022"
|
||||
@@ -540,7 +540,7 @@ def test_process_message_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
def test_process_message_with_tool_use(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _process_message with tool use."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
mock_message = MagicMock(spec=BetaMessage)
|
||||
mock_message.id = "msg_123"
|
||||
@@ -556,7 +556,7 @@ def test_process_message_with_tool_use(mock_anthropic_client: MagicMock) -> None
|
||||
mock_message.usage = BetaUsage(input_tokens=10, output_tokens=5)
|
||||
mock_message.stop_reason = "tool_use"
|
||||
|
||||
response = chat_client._process_message(mock_message, {})
|
||||
response = client._process_message(mock_message, {})
|
||||
|
||||
assert len(response.messages[0].contents) == 1
|
||||
assert response.messages[0].contents[0].type == "function_call"
|
||||
@@ -567,10 +567,10 @@ def test_process_message_with_tool_use(mock_anthropic_client: MagicMock) -> None
|
||||
|
||||
def test_parse_usage_from_anthropic_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _parse_usage_from_anthropic with basic usage."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
usage = BetaUsage(input_tokens=10, output_tokens=5)
|
||||
result = chat_client._parse_usage_from_anthropic(usage)
|
||||
result = client._parse_usage_from_anthropic(usage)
|
||||
|
||||
assert result is not None
|
||||
assert result["input_token_count"] == 10
|
||||
@@ -579,19 +579,19 @@ def test_parse_usage_from_anthropic_basic(mock_anthropic_client: MagicMock) -> N
|
||||
|
||||
def test_parse_usage_from_anthropic_none(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _parse_usage_from_anthropic with None usage."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
result = chat_client._parse_usage_from_anthropic(None)
|
||||
result = client._parse_usage_from_anthropic(None)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_parse_contents_from_anthropic_text(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _parse_contents_from_anthropic with text content."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
content = [BetaTextBlock(type="text", text="Hello!")]
|
||||
result = chat_client._parse_contents_from_anthropic(content)
|
||||
result = client._parse_contents_from_anthropic(content)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "text"
|
||||
@@ -600,7 +600,7 @@ def test_parse_contents_from_anthropic_text(mock_anthropic_client: MagicMock) ->
|
||||
|
||||
def test_parse_contents_from_anthropic_tool_use(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _parse_contents_from_anthropic with tool use."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
content = [
|
||||
BetaToolUseBlock(
|
||||
@@ -610,7 +610,7 @@ def test_parse_contents_from_anthropic_tool_use(mock_anthropic_client: MagicMock
|
||||
input={"location": "SF"},
|
||||
)
|
||||
]
|
||||
result = chat_client._parse_contents_from_anthropic(content)
|
||||
result = client._parse_contents_from_anthropic(content)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "function_call"
|
||||
@@ -625,7 +625,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
and subsequent input_json_delta events should have name="" to prevent
|
||||
ag-ui from emitting duplicate ToolCallStartEvents.
|
||||
"""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
# First, simulate a tool_use event that sets _last_call_id_name
|
||||
tool_use_content = MagicMock()
|
||||
@@ -634,7 +634,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
tool_use_content.name = "get_weather"
|
||||
tool_use_content.input = {}
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([tool_use_content])
|
||||
result = client._parse_contents_from_anthropic([tool_use_content])
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
@@ -645,7 +645,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
delta_content_1.type = "input_json_delta"
|
||||
delta_content_1.partial_json = '{"location":'
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([delta_content_1])
|
||||
result = client._parse_contents_from_anthropic([delta_content_1])
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
@@ -657,7 +657,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
delta_content_2.type = "input_json_delta"
|
||||
delta_content_2.partial_json = '"San Francisco"}'
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([delta_content_2])
|
||||
result = client._parse_contents_from_anthropic([delta_content_2])
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
@@ -670,13 +670,13 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
|
||||
def test_process_stream_event_simple(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _process_stream_event with simple mock event."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
# Test with a basic mock event - the actual implementation will handle real events
|
||||
mock_event = MagicMock()
|
||||
mock_event.type = "message_stop"
|
||||
|
||||
result = chat_client._process_stream_event(mock_event)
|
||||
result = client._process_stream_event(mock_event)
|
||||
|
||||
# message_stop events return None
|
||||
assert result is None
|
||||
@@ -684,7 +684,7 @@ def test_process_stream_event_simple(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
async def test_inner_get_response(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _inner_get_response method."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
# Create a mock message response
|
||||
mock_message = MagicMock(spec=BetaMessage)
|
||||
@@ -696,10 +696,10 @@ async def test_inner_get_response(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_message
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hi")]
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
chat_options = ChatOptions(max_tokens=10)
|
||||
|
||||
response = await chat_client._inner_get_response( # type: ignore[attr-defined]
|
||||
response = await client._inner_get_response( # type: ignore[attr-defined]
|
||||
messages=messages, options=chat_options
|
||||
)
|
||||
|
||||
@@ -710,7 +710,7 @@ async def test_inner_get_response(mock_anthropic_client: MagicMock) -> None:
|
||||
|
||||
async def test_inner_get_response_streaming(mock_anthropic_client: MagicMock) -> None:
|
||||
"""Test _inner_get_response method with streaming."""
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
client = create_test_anthropic_client(mock_anthropic_client)
|
||||
|
||||
# Create mock streaming response
|
||||
async def mock_stream():
|
||||
@@ -720,11 +720,11 @@ async def test_inner_get_response_streaming(mock_anthropic_client: MagicMock) ->
|
||||
|
||||
mock_anthropic_client.beta.messages.create.return_value = mock_stream()
|
||||
|
||||
messages = [ChatMessage(role="user", text="Hi")]
|
||||
messages = [Message(role="user", text="Hi")]
|
||||
chat_options = ChatOptions(max_tokens=10)
|
||||
|
||||
chunks: list[ChatResponseUpdate] = []
|
||||
async for chunk in chat_client._inner_get_response( # type: ignore[attr-defined]
|
||||
async for chunk in client._inner_get_response( # type: ignore[attr-defined]
|
||||
messages=messages, options=chat_options, stream=True
|
||||
):
|
||||
if chunk:
|
||||
@@ -751,7 +751,7 @@ async def test_anthropic_client_integration_basic_chat() -> None:
|
||||
"""Integration test for basic chat completion."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [ChatMessage(role="user", text="Say 'Hello, World!' and nothing else.")]
|
||||
messages = [Message(role="user", text="Say 'Hello, World!' and nothing else.")]
|
||||
|
||||
response = await client.get_response(messages=messages, options={"max_tokens": 50})
|
||||
|
||||
@@ -768,7 +768,7 @@ async def test_anthropic_client_integration_streaming_chat() -> None:
|
||||
"""Integration test for streaming chat completion."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [ChatMessage(role="user", text="Count from 1 to 5.")]
|
||||
messages = [Message(role="user", text="Count from 1 to 5.")]
|
||||
|
||||
chunks = []
|
||||
async for chunk in client.get_response(messages=messages, stream=True, options={"max_tokens": 50}):
|
||||
@@ -784,7 +784,7 @@ async def test_anthropic_client_integration_function_calling() -> None:
|
||||
"""Integration test for function calling."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [ChatMessage(role="user", text="What's the weather in San Francisco?")]
|
||||
messages = [Message(role="user", text="What's the weather in San Francisco?")]
|
||||
tools = [get_weather]
|
||||
|
||||
response = await client.get_response(
|
||||
@@ -804,7 +804,7 @@ async def test_anthropic_client_integration_hosted_tools() -> None:
|
||||
"""Integration test for hosted tools."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [ChatMessage(role="user", text="What tools do you have available?")]
|
||||
messages = [Message(role="user", text="What tools do you have available?")]
|
||||
tools = [
|
||||
HostedWebSearchTool(),
|
||||
HostedCodeInterpreterTool(),
|
||||
@@ -831,8 +831,8 @@ async def test_anthropic_client_integration_with_system_message() -> None:
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", text="You are a pirate. Always respond like a pirate."),
|
||||
ChatMessage(role="user", text="Hello!"),
|
||||
Message(role="system", text="You are a pirate. Always respond like a pirate."),
|
||||
Message(role="user", text="Hello!"),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages, options={"max_tokens": 50})
|
||||
@@ -847,7 +847,7 @@ async def test_anthropic_client_integration_temperature_control() -> None:
|
||||
"""Integration test with temperature control."""
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [ChatMessage(role="user", text="Say hello.")]
|
||||
messages = [Message(role="user", text="Say hello.")]
|
||||
|
||||
response = await client.get_response(
|
||||
messages=messages,
|
||||
@@ -865,11 +865,11 @@ async def test_anthropic_client_integration_ordering() -> None:
|
||||
client = AnthropicClient()
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="user", text="Say hello."),
|
||||
ChatMessage(role="user", text="Then say goodbye."),
|
||||
ChatMessage(role="assistant", text="Thank you for chatting!"),
|
||||
ChatMessage(role="assistant", text="Let me know if I can help."),
|
||||
ChatMessage(role="user", text="Just testing things."),
|
||||
Message(role="user", text="Say hello."),
|
||||
Message(role="user", text="Then say goodbye."),
|
||||
Message(role="assistant", text="Thank you for chatting!"),
|
||||
Message(role="assistant", text="Let me know if I can help."),
|
||||
Message(role="user", text="Just testing things."),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages)
|
||||
@@ -890,7 +890,7 @@ async def test_anthropic_client_integration_images() -> None:
|
||||
image_bytes = img_file.read()
|
||||
|
||||
messages = [
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text(text="Describe this image"),
|
||||
|
||||
@@ -16,7 +16,7 @@ provider = AzureAISearchContextProvider(
|
||||
endpoint="https://your-search.search.windows.net",
|
||||
index_name="your-index",
|
||||
)
|
||||
agent = ChatAgent(..., context_provider=provider)
|
||||
agent = Agent(..., context_provider=provider)
|
||||
```
|
||||
|
||||
## Import Path
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ import sys
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
||||
|
||||
from agent_framework import AGENT_FRAMEWORK_USER_AGENT, ChatMessage
|
||||
from agent_framework import AGENT_FRAMEWORK_USER_AGENT, Message
|
||||
from agent_framework._logging import get_logger
|
||||
from agent_framework._sessions import AgentSession, BaseContextProvider, SessionContext
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -343,8 +343,8 @@ class _AzureAISearchContextProvider(BaseContextProvider):
|
||||
if not search_result_parts:
|
||||
return
|
||||
|
||||
context_messages = [ChatMessage(role="user", text=self.context_prompt)]
|
||||
context_messages.extend([ChatMessage(role="user", text=part) for part in search_result_parts])
|
||||
context_messages = [Message(role="user", text=self.context_prompt)]
|
||||
context_messages.extend([Message(role="user", text=part) for part in search_result_parts])
|
||||
context.extend_messages(self.source_id, context_messages)
|
||||
|
||||
# -- Internal methods (ported from AzureAISearchContextProvider) -----------
|
||||
@@ -546,7 +546,7 @@ class _AzureAISearchContextProvider(BaseContextProvider):
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
|
||||
async def _agentic_search(self, messages: list[ChatMessage]) -> list[str]:
|
||||
async def _agentic_search(self, messages: list[Message]) -> list[str]:
|
||||
"""Perform agentic retrieval with multi-hop reasoning."""
|
||||
await self._ensure_knowledge_base()
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import sys
|
||||
from collections.abc import Awaitable, Callable, MutableSequence
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
||||
|
||||
from agent_framework import AGENT_FRAMEWORK_USER_AGENT, ChatMessage, Context, ContextProvider
|
||||
from agent_framework import AGENT_FRAMEWORK_USER_AGENT, Context, ContextProvider, Message
|
||||
from agent_framework._logging import get_logger
|
||||
from agent_framework._pydantic import AFBaseSettings
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -511,7 +511,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
@override
|
||||
async def invoking(
|
||||
self,
|
||||
messages: ChatMessage | MutableSequence[ChatMessage],
|
||||
messages: Message | MutableSequence[Message],
|
||||
**kwargs: Any,
|
||||
) -> Context:
|
||||
"""Retrieve relevant context from Azure AI Search before model invocation.
|
||||
@@ -524,7 +524,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
Context object with retrieved documents as messages.
|
||||
"""
|
||||
# Convert to list and filter to USER/ASSISTANT messages with text only
|
||||
messages_list = [messages] if isinstance(messages, ChatMessage) else list(messages)
|
||||
messages_list = [messages] if isinstance(messages, Message) else list(messages)
|
||||
|
||||
def get_role_value(role: str | Any) -> str:
|
||||
return role.value if hasattr(role, "value") else str(role)
|
||||
@@ -553,8 +553,8 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
return Context()
|
||||
|
||||
# Create context messages: first message with prompt, then one message per result part
|
||||
context_messages = [ChatMessage(role="user", text=self.context_prompt)]
|
||||
context_messages.extend([ChatMessage(role="user", text=part) for part in search_result_parts])
|
||||
context_messages = [Message(role="user", text=self.context_prompt)]
|
||||
context_messages.extend([Message(role="user", text=part) for part in search_result_parts])
|
||||
|
||||
return Context(messages=context_messages)
|
||||
|
||||
@@ -875,7 +875,7 @@ class AzureAISearchContextProvider(ContextProvider):
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
|
||||
async def _agentic_search(self, messages: list[ChatMessage]) -> list[str]:
|
||||
async def _agentic_search(self, messages: list[Message]) -> list[str]:
|
||||
"""Perform agentic retrieval with multi-hop reasoning using Knowledge Bases.
|
||||
|
||||
This mode uses query planning and is slightly slower than semantic search,
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
from agent_framework._sessions import AgentSession, SessionContext
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
|
||||
@@ -179,7 +179,7 @@ class TestBeforeRunSemantic:
|
||||
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[ChatMessage(role="user", contents=["test query"])],
|
||||
input_messages=[Message(role="user", contents=["test query"])],
|
||||
session_id="s1",
|
||||
)
|
||||
await provider.before_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
@@ -206,7 +206,7 @@ class TestBeforeRunSemantic:
|
||||
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[ChatMessage(role="user", contents=["test query"])],
|
||||
input_messages=[Message(role="user", contents=["test query"])],
|
||||
session_id="s1",
|
||||
)
|
||||
await provider.before_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
@@ -221,7 +221,7 @@ class TestBeforeRunSemantic:
|
||||
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[ChatMessage(role="user", contents=["test query"])],
|
||||
input_messages=[Message(role="user", contents=["test query"])],
|
||||
session_id="s1",
|
||||
)
|
||||
await provider.before_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
@@ -243,8 +243,8 @@ class TestBeforeRunFiltering:
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[
|
||||
ChatMessage(role="system", contents=["system prompt"]),
|
||||
ChatMessage(role="user", contents=["actual question"]),
|
||||
Message(role="system", contents=["system prompt"]),
|
||||
Message(role="user", contents=["actual question"]),
|
||||
],
|
||||
session_id="s1",
|
||||
)
|
||||
@@ -262,7 +262,7 @@ class TestBeforeRunFiltering:
|
||||
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(
|
||||
input_messages=[ChatMessage(role="system", contents=["system prompt"])],
|
||||
input_messages=[Message(role="system", contents=["system prompt"])],
|
||||
session_id="s1",
|
||||
)
|
||||
await provider.before_run(agent=None, session=session, context=ctx, state=session.state) # type: ignore[arg-type]
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatMessage, Context
|
||||
from agent_framework import Context, Message
|
||||
from agent_framework.azure import AzureAISearchContextProvider, AzureAISearchSettings
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
@@ -36,10 +36,10 @@ def mock_index_client() -> AsyncMock:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_messages() -> list[ChatMessage]:
|
||||
def sample_messages() -> list[Message]:
|
||||
"""Create sample chat messages for testing."""
|
||||
return [
|
||||
ChatMessage(role="user", text="What is in the documents?"),
|
||||
Message(role="user", text="What is in the documents?"),
|
||||
]
|
||||
|
||||
|
||||
@@ -276,9 +276,7 @@ class TestSemanticSearch:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch("agent_framework_azure_ai_search._search_provider.SearchClient")
|
||||
async def test_semantic_search_basic(
|
||||
self, mock_search_class: MagicMock, sample_messages: list[ChatMessage]
|
||||
) -> None:
|
||||
async def test_semantic_search_basic(self, mock_search_class: MagicMock, sample_messages: list[Message]) -> None:
|
||||
"""Test basic semantic search without vector search."""
|
||||
# Setup mock
|
||||
mock_search_client = AsyncMock()
|
||||
@@ -318,7 +316,7 @@ class TestSemanticSearch:
|
||||
)
|
||||
|
||||
# Empty message
|
||||
context = await provider.invoking([ChatMessage(role="user", text="")])
|
||||
context = await provider.invoking([Message(role="user", text="")])
|
||||
|
||||
assert isinstance(context, Context)
|
||||
assert len(context.messages) == 0
|
||||
@@ -326,7 +324,7 @@ class TestSemanticSearch:
|
||||
@pytest.mark.asyncio
|
||||
@patch("agent_framework_azure_ai_search._search_provider.SearchClient")
|
||||
async def test_semantic_search_with_vector_query(
|
||||
self, mock_search_class: MagicMock, sample_messages: list[ChatMessage]
|
||||
self, mock_search_class: MagicMock, sample_messages: list[Message]
|
||||
) -> None:
|
||||
"""Test semantic search with vector query."""
|
||||
# Setup mock
|
||||
@@ -520,10 +518,10 @@ class TestMessageFiltering:
|
||||
|
||||
# Mix of message types
|
||||
messages = [
|
||||
ChatMessage(role="system", text="System message"),
|
||||
ChatMessage(role="user", text="User message"),
|
||||
ChatMessage(role="assistant", text="Assistant message"),
|
||||
ChatMessage(role="tool", text="Tool message"),
|
||||
Message(role="system", text="System message"),
|
||||
Message(role="user", text="User message"),
|
||||
Message(role="assistant", text="Assistant message"),
|
||||
Message(role="tool", text="Tool message"),
|
||||
]
|
||||
|
||||
context = await provider.invoking(messages)
|
||||
@@ -548,9 +546,9 @@ class TestMessageFiltering:
|
||||
|
||||
# Messages with empty/whitespace text
|
||||
messages = [
|
||||
ChatMessage(role="user", text=""),
|
||||
ChatMessage(role="user", text=" "),
|
||||
ChatMessage(role="user", text=""), # ChatMessage with None text becomes empty string
|
||||
Message(role="user", text=""),
|
||||
Message(role="user", text=" "),
|
||||
Message(role="user", text=""), # Message with None text becomes empty string
|
||||
]
|
||||
|
||||
context = await provider.invoking(messages)
|
||||
@@ -581,7 +579,7 @@ class TestCitations:
|
||||
mode="semantic",
|
||||
)
|
||||
|
||||
context = await provider.invoking([ChatMessage(role="user", text="test query")])
|
||||
context = await provider.invoking([Message(role="user", text="test query")])
|
||||
|
||||
# Check that citation is included
|
||||
assert isinstance(context, Context)
|
||||
@@ -603,7 +601,7 @@ class TestAgenticSearch:
|
||||
mock_search_class: MagicMock,
|
||||
mock_index_class: MagicMock,
|
||||
mock_retrieval_class: MagicMock,
|
||||
sample_messages: list[ChatMessage],
|
||||
sample_messages: list[Message],
|
||||
) -> None:
|
||||
"""Test basic agentic search with Knowledge Base retrieval."""
|
||||
# Setup search client mock
|
||||
@@ -660,7 +658,7 @@ class TestAgenticSearch:
|
||||
mock_search_class: MagicMock,
|
||||
mock_index_class: MagicMock,
|
||||
mock_retrieval_class: MagicMock,
|
||||
sample_messages: list[ChatMessage],
|
||||
sample_messages: list[Message],
|
||||
) -> None:
|
||||
"""Test agentic search when no results are returned."""
|
||||
# Setup mocks
|
||||
@@ -705,7 +703,7 @@ class TestAgenticSearch:
|
||||
mock_search_class: MagicMock,
|
||||
mock_index_class: MagicMock,
|
||||
mock_retrieval_class: MagicMock,
|
||||
sample_messages: list[ChatMessage],
|
||||
sample_messages: list[Message],
|
||||
) -> None:
|
||||
"""Test agentic search with medium reasoning effort."""
|
||||
# Setup mocks
|
||||
|
||||
@@ -4,11 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable, MutableMapping, Sequence
|
||||
from typing import TYPE_CHECKING, Any, Generic, cast
|
||||
from typing import Any, Generic, cast
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
ChatAgent,
|
||||
Agent,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
MiddlewareTypes,
|
||||
@@ -18,16 +18,14 @@ from agent_framework import (
|
||||
from agent_framework._mcp import MCPTool
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.ai.agents.models import Agent, ResponseFormatJsonSchema, ResponseFormatJsonSchemaType
|
||||
from azure.ai.agents.models import Agent as AzureAgent
|
||||
from azure.ai.agents.models import ResponseFormatJsonSchema, ResponseFormatJsonSchemaType
|
||||
from azure.core.credentials_async import AsyncTokenCredential
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from ._chat_client import AzureAIAgentClient
|
||||
from ._chat_client import AzureAIAgentClient, AzureAIAgentOptions
|
||||
from ._shared import AzureAISettings, from_azure_ai_agent_tools, to_azure_ai_agent_tools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._chat_client import AzureAIAgentOptions
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
from typing import Self, TypeVar # type: ignore # pragma: no cover
|
||||
else:
|
||||
@@ -38,7 +36,7 @@ else:
|
||||
from typing_extensions import TypedDict # type: ignore # pragma: no cover
|
||||
|
||||
|
||||
# Type variable for options - allows typed ChatAgent[OptionsCoT] returns
|
||||
# Type variable for options - allows typed Agent[TOptions] returns
|
||||
# Default matches AzureAIAgentClient's default options type
|
||||
OptionsCoT = TypeVar(
|
||||
"OptionsCoT",
|
||||
@@ -51,7 +49,7 @@ OptionsCoT = TypeVar(
|
||||
class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
"""Provider for Azure AI Agent Service V1 (Persistent Agents API).
|
||||
|
||||
This provider enables creating, retrieving, and wrapping Azure AI agents as ChatAgent
|
||||
This provider enables creating, retrieving, and wrapping Azure AI agents as Agent
|
||||
instances. It manages the underlying AgentsClient lifecycle and provides a high-level
|
||||
interface for agent operations.
|
||||
|
||||
@@ -179,11 +177,11 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a new agent on the Azure AI service and return a ChatAgent.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a new agent on the Azure AI service and return a Agent.
|
||||
|
||||
This method creates a persistent agent on the Azure AI service with the specified
|
||||
configuration and returns a local ChatAgent instance for interaction.
|
||||
configuration and returns a local Agent instance for interaction.
|
||||
|
||||
Args:
|
||||
name: The name for the agent.
|
||||
@@ -200,7 +198,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the created agent.
|
||||
Agent: A Agent instance configured with the created agent.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If model deployment name is not available.
|
||||
@@ -240,7 +238,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
args["response_format"] = self._create_response_format_config(response_format)
|
||||
|
||||
# Normalize and convert tools
|
||||
# Local MCP tools (MCPTool) are handled by ChatAgent at runtime, not stored on the Azure agent
|
||||
# Local MCP tools (MCPTool) are handled by Agent at runtime, not stored on the Azure agent
|
||||
normalized_tools = normalize_tools(tools)
|
||||
if normalized_tools:
|
||||
# Only convert non-MCP tools to Azure AI format
|
||||
@@ -255,7 +253,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
# Create the agent on the service
|
||||
created_agent = await self._agents_client.create_agent(**args)
|
||||
|
||||
# Create ChatAgent wrapper
|
||||
# Create Agent wrapper
|
||||
return self._to_chat_agent_from_agent(
|
||||
created_agent,
|
||||
normalized_tools,
|
||||
@@ -276,11 +274,11 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Retrieve an existing agent from the service and return a ChatAgent.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Retrieve an existing agent from the service and return a Agent.
|
||||
|
||||
This method fetches an agent by ID from the Azure AI service
|
||||
and returns a local ChatAgent instance for interaction.
|
||||
and returns a local Agent instance for interaction.
|
||||
|
||||
Args:
|
||||
id: The ID of the agent to retrieve from the service.
|
||||
@@ -294,7 +292,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the retrieved agent.
|
||||
Agent: A Agent instance configured with the retrieved agent.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If required function tools are not provided.
|
||||
@@ -323,7 +321,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
|
||||
def as_agent(
|
||||
self,
|
||||
agent: Agent,
|
||||
agent: AzureAgent,
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
@@ -332,8 +330,8 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Wrap an existing Agent SDK object as a ChatAgent without making HTTP calls.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Wrap an existing Agent SDK object as a Agent without making HTTP calls.
|
||||
|
||||
Use this method when you already have an Agent object from a previous
|
||||
SDK operation and want to use it with the Agent Framework.
|
||||
@@ -348,7 +346,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the agent.
|
||||
Agent: A Agent instance configured with the agent.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If required function tools are not provided.
|
||||
@@ -363,7 +361,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
instructions="...",
|
||||
)
|
||||
|
||||
# Wrap as ChatAgent
|
||||
# Wrap as Agent
|
||||
chat_agent = provider.as_agent(sdk_agent)
|
||||
"""
|
||||
# Validate function tools
|
||||
@@ -380,13 +378,13 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
|
||||
def _to_chat_agent_from_agent(
|
||||
self,
|
||||
agent: Agent,
|
||||
agent: AzureAgent,
|
||||
provided_tools: Sequence[ToolProtocol | MutableMapping[str, Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a ChatAgent from an Agent SDK object.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a Agent from an Agent SDK object.
|
||||
|
||||
Args:
|
||||
agent: The Agent SDK object.
|
||||
@@ -408,8 +406,8 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
# Merge tools: convert agent's hosted tools + user-provided function tools
|
||||
merged_tools = self._merge_tools(agent.tools, provided_tools)
|
||||
|
||||
return ChatAgent( # type: ignore[return-value]
|
||||
chat_client=client,
|
||||
return Agent( # type: ignore[return-value]
|
||||
client=client,
|
||||
id=agent.id,
|
||||
name=agent.name,
|
||||
description=agent.description,
|
||||
@@ -433,7 +431,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
provided_tools: User-provided tools (Agent Framework format).
|
||||
|
||||
Returns:
|
||||
Combined list of tools for the ChatAgent.
|
||||
Combined list of tools for the Agent.
|
||||
"""
|
||||
merged: list[ToolProtocol | dict[str, Any]] = []
|
||||
|
||||
@@ -452,7 +450,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
if provided_tools:
|
||||
for provided_tool in provided_tools:
|
||||
# FunctionTool - has implementation for function calling
|
||||
# MCPTool - ChatAgent handles MCP connection and tool discovery at runtime
|
||||
# MCPTool - Agent handles MCP connection and tool discovery at runtime
|
||||
if isinstance(provided_tool, (FunctionTool, MCPTool)):
|
||||
merged.append(provided_tool) # type: ignore[reportUnknownArgumentType]
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ from typing import Any, ClassVar, Generic, TypedDict
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
Agent,
|
||||
Annotation,
|
||||
BaseChatClient,
|
||||
ChatAgent,
|
||||
ChatAndFunctionMiddlewareTypes,
|
||||
ChatMessage,
|
||||
ChatMessageStoreProtocol,
|
||||
ChatMiddlewareLayer,
|
||||
ChatOptions,
|
||||
@@ -31,6 +30,7 @@ from agent_framework import (
|
||||
HostedFileSearchTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Message,
|
||||
MiddlewareTypes,
|
||||
ResponseStream,
|
||||
Role,
|
||||
@@ -44,7 +44,9 @@ from agent_framework.exceptions import ServiceInitializationError, ServiceInvali
|
||||
from agent_framework.observability import ChatTelemetryLayer
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.ai.agents.models import (
|
||||
Agent,
|
||||
Agent as AzureAgent,
|
||||
)
|
||||
from azure.ai.agents.models import (
|
||||
AgentsNamedToolChoice,
|
||||
AgentsNamedToolChoiceType,
|
||||
AgentsToolChoiceOptionMode,
|
||||
@@ -346,7 +348,7 @@ class AzureAIAgentClient(
|
||||
self.should_cleanup_agent = should_cleanup_agent # Track whether we should delete the agent
|
||||
self._agent_created = False # Track whether agent was created inside this class
|
||||
self._should_close_client = should_close_client # Track whether we should close client connection
|
||||
self._agent_definition: Agent | None = None # Cached definition for existing agent
|
||||
self._agent_definition: AzureAgent | None = None # Cached definition for existing agent
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
"""Async context manager entry."""
|
||||
@@ -365,7 +367,7 @@ class AzureAIAgentClient(
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -898,7 +900,7 @@ class AzureAIAgentClient(
|
||||
self.agent_id = None
|
||||
self._agent_created = False
|
||||
|
||||
async def _load_agent_definition_if_needed(self) -> Agent | None:
|
||||
async def _load_agent_definition_if_needed(self) -> AzureAgent | None:
|
||||
"""Load and cache agent details if not already loaded."""
|
||||
if self._agent_definition is None and self.agent_id is not None:
|
||||
self._agent_definition = await self.agents_client.get_agent(self.agent_id)
|
||||
@@ -906,7 +908,7 @@ class AzureAIAgentClient(
|
||||
|
||||
async def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> tuple[dict[str, Any], list[Content] | None]:
|
||||
@@ -1020,7 +1022,7 @@ class AzureAIAgentClient(
|
||||
async def _prepare_tool_definitions_and_resources(
|
||||
self,
|
||||
options: Mapping[str, Any],
|
||||
agent_definition: Agent | None,
|
||||
agent_definition: AzureAgent | None,
|
||||
run_options: dict[str, Any],
|
||||
) -> list[ToolDefinition | dict[str, Any]]:
|
||||
"""Prepare tool definitions and resources for the run options."""
|
||||
@@ -1084,7 +1086,7 @@ class AzureAIAgentClient(
|
||||
return mcp_resources
|
||||
|
||||
def _prepare_messages(
|
||||
self, messages: Sequence[ChatMessage]
|
||||
self, messages: Sequence[Message]
|
||||
) -> tuple[
|
||||
list[ThreadMessageOptions] | None,
|
||||
list[str],
|
||||
@@ -1301,10 +1303,10 @@ class AzureAIAgentClient(
|
||||
context_provider: ContextProvider | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatAgent[AzureAIAgentOptionsT]:
|
||||
"""Convert this chat client to a ChatAgent.
|
||||
) -> Agent[AzureAIAgentOptionsT]:
|
||||
"""Convert this chat client to a Agent.
|
||||
|
||||
This method creates a ChatAgent instance with this client pre-configured.
|
||||
This method creates a Agent instance with this client pre-configured.
|
||||
It does NOT create an agent on the Azure AI service - the actual agent
|
||||
will be created on the server during the first invocation (run).
|
||||
|
||||
@@ -1324,7 +1326,7 @@ class AzureAIAgentClient(
|
||||
kwargs: Any additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance configured with this chat client.
|
||||
A Agent instance configured with this chat client.
|
||||
"""
|
||||
return super().as_agent(
|
||||
id=id,
|
||||
|
||||
@@ -8,15 +8,15 @@ from typing import Any, ClassVar, Generic, TypedDict, TypeVar, cast
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
ChatAgent,
|
||||
Agent,
|
||||
ChatAndFunctionMiddlewareTypes,
|
||||
ChatMessage,
|
||||
ChatMessageStoreProtocol,
|
||||
ChatMiddlewareLayer,
|
||||
ContextProvider,
|
||||
FunctionInvocationConfiguration,
|
||||
FunctionInvocationLayer,
|
||||
HostedMCPTool,
|
||||
Message,
|
||||
MiddlewareTypes,
|
||||
ToolProtocol,
|
||||
get_logger,
|
||||
@@ -329,7 +329,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
if self.agent_name is None:
|
||||
raise ServiceInitializationError(
|
||||
"Agent name is required. Provide 'agent_name' when initializing AzureAIClient "
|
||||
"or 'name' when initializing ChatAgent."
|
||||
"or 'name' when initializing Agent."
|
||||
)
|
||||
|
||||
# If no agent_version is provided, either use latest version or create a new agent:
|
||||
@@ -396,7 +396,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
@override
|
||||
async def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
@@ -489,9 +489,9 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
"""Get the current conversation ID from chat options or kwargs."""
|
||||
return options.get("conversation_id") or kwargs.get("conversation_id") or self.conversation_id
|
||||
|
||||
def _prepare_messages_for_azure_ai(self, messages: Sequence[ChatMessage]) -> tuple[list[ChatMessage], str | None]:
|
||||
def _prepare_messages_for_azure_ai(self, messages: Sequence[Message]) -> tuple[list[Message], str | None]:
|
||||
"""Prepare input from messages and convert system/developer messages to instructions."""
|
||||
result: list[ChatMessage] = []
|
||||
result: list[Message] = []
|
||||
instructions_list: list[str] = []
|
||||
instructions: str | None = None
|
||||
|
||||
@@ -575,10 +575,10 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
context_provider: ContextProvider | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatAgent[AzureAIClientOptionsT]:
|
||||
"""Convert this chat client to a ChatAgent.
|
||||
) -> Agent[AzureAIClientOptionsT]:
|
||||
"""Convert this chat client to a Agent.
|
||||
|
||||
This method creates a ChatAgent instance with this client pre-configured.
|
||||
This method creates a Agent instance with this client pre-configured.
|
||||
It does NOT create an agent on the Azure AI service - the actual agent
|
||||
will be created on the server during the first invocation (run).
|
||||
|
||||
@@ -598,7 +598,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
kwargs: Any additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance configured with this chat client.
|
||||
A Agent instance configured with this chat client.
|
||||
"""
|
||||
return super().as_agent(
|
||||
id=id,
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Any, Generic
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
ChatAgent,
|
||||
Agent,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
MiddlewareTypes,
|
||||
@@ -47,7 +47,7 @@ else:
|
||||
logger = get_logger("agent_framework.azure")
|
||||
|
||||
|
||||
# Type variable for options - allows typed ChatAgent[OptionsT] returns
|
||||
# Type variable for options - allows typed Agent[OptionsT] returns
|
||||
# Default matches AzureAIClient's default options type
|
||||
OptionsCoT = TypeVar(
|
||||
"OptionsCoT",
|
||||
@@ -170,8 +170,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a new agent on the Azure AI service and return a local ChatAgent wrapper.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a new agent on the Azure AI service and return a local Agent wrapper.
|
||||
|
||||
Args:
|
||||
name: The name of the agent to create.
|
||||
@@ -186,7 +186,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the created agent.
|
||||
Agent: A Agent instance configured with the created agent.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If required parameters are missing.
|
||||
@@ -272,8 +272,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Retrieve an existing agent from the Azure AI service and return a local ChatAgent wrapper.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Retrieve an existing agent from the Azure AI service and return a local Agent wrapper.
|
||||
|
||||
You must provide either name or reference. Use `as_agent()` if you already have
|
||||
AgentVersionDetails and want to avoid an async call.
|
||||
@@ -288,7 +288,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the retrieved agent.
|
||||
Agent: A Agent instance configured with the retrieved agent.
|
||||
|
||||
Raises:
|
||||
ValueError: If no identifier is provided or required tools are missing.
|
||||
@@ -308,7 +308,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
raise ValueError("Either name or reference must be provided to get an agent.")
|
||||
|
||||
if not isinstance(existing_agent.definition, PromptAgentDefinition):
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to get a ChatAgent.")
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to get a Agent.")
|
||||
|
||||
# Validate that required function tools are provided
|
||||
self._validate_function_tools(existing_agent.definition.tools, tools)
|
||||
@@ -332,8 +332,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Wrap an SDK agent version object into a ChatAgent without making HTTP calls.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Wrap an SDK agent version object into a Agent without making HTTP calls.
|
||||
|
||||
Use this when you already have an AgentVersionDetails from a previous API call.
|
||||
|
||||
@@ -346,13 +346,13 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
|
||||
Returns:
|
||||
ChatAgent: A ChatAgent instance configured with the agent version.
|
||||
Agent: A Agent instance configured with the agent version.
|
||||
|
||||
Raises:
|
||||
ValueError: If the agent definition is not a PromptAgentDefinition or required tools are missing.
|
||||
"""
|
||||
if not isinstance(details.definition, PromptAgentDefinition):
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to create a ChatAgent.")
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to create a Agent.")
|
||||
|
||||
# Validate that required function tools are provided
|
||||
self._validate_function_tools(details.definition.tools, tools)
|
||||
@@ -372,8 +372,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a ChatAgent from an AgentVersionDetails.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a Agent from an AgentVersionDetails.
|
||||
|
||||
Args:
|
||||
details: The AgentVersionDetails containing the agent definition.
|
||||
@@ -385,7 +385,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
context_provider: Context provider to include during agent invocation.
|
||||
"""
|
||||
if not isinstance(details.definition, PromptAgentDefinition):
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to get a ChatAgent.")
|
||||
raise ValueError("Agent definition must be PromptAgentDefinition to get a Agent.")
|
||||
|
||||
client = AzureAIClient(
|
||||
project_client=self._project_client,
|
||||
@@ -400,8 +400,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
# but function tools need the actual implementations from provided_tools
|
||||
merged_tools = self._merge_tools(details.definition.tools, provided_tools)
|
||||
|
||||
return ChatAgent( # type: ignore[return-value]
|
||||
chat_client=client,
|
||||
return Agent( # type: ignore[return-value]
|
||||
client=client,
|
||||
id=details.id,
|
||||
name=details.name,
|
||||
description=details.description,
|
||||
@@ -425,7 +425,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
provided_tools: User-provided tools (Agent Framework format), including function implementations.
|
||||
|
||||
Returns:
|
||||
Combined list of tools for the ChatAgent.
|
||||
Combined list of tools for the Agent.
|
||||
"""
|
||||
merged: list[ToolProtocol | dict[str, Any]] = []
|
||||
|
||||
@@ -442,7 +442,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
if provided_tools:
|
||||
for provided_tool in provided_tools:
|
||||
# FunctionTool - has implementation for function calling
|
||||
# MCPTool - ChatAgent handles MCP connection and tool discovery at runtime
|
||||
# MCPTool - Agent handles MCP connection and tool discovery at runtime
|
||||
if isinstance(provided_tool, (FunctionTool, MCPTool)):
|
||||
merged.append(provided_tool) # type: ignore[reportUnknownArgumentType]
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import (
|
||||
ChatAgent,
|
||||
Agent,
|
||||
Content,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileSearchTool,
|
||||
@@ -16,7 +16,9 @@ from agent_framework import (
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.agents.models import (
|
||||
Agent,
|
||||
Agent as AzureAgent,
|
||||
)
|
||||
from azure.ai.agents.models import (
|
||||
CodeInterpreterToolDefinition,
|
||||
)
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
@@ -156,7 +158,7 @@ async def test_create_agent_basic(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test creating a basic agent."""
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "test-agent-id"
|
||||
mock_agent.name = "TestAgent"
|
||||
mock_agent.description = "A test agent"
|
||||
@@ -175,7 +177,7 @@ async def test_create_agent_basic(
|
||||
description="A test agent",
|
||||
)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "TestAgent"
|
||||
assert agent.id == "test-agent-id"
|
||||
mock_agents_client.create_agent.assert_called_once()
|
||||
@@ -186,7 +188,7 @@ async def test_create_agent_with_model(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test creating an agent with explicit model."""
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "test-agent-id"
|
||||
mock_agent.name = "TestAgent"
|
||||
mock_agent.description = None
|
||||
@@ -210,7 +212,7 @@ async def test_create_agent_with_tools(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test creating an agent with tools."""
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "test-agent-id"
|
||||
mock_agent.name = "TestAgent"
|
||||
mock_agent.description = None
|
||||
@@ -245,7 +247,7 @@ async def test_create_agent_with_response_format(
|
||||
temperature: float
|
||||
description: str
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "test-agent-id"
|
||||
mock_agent.name = "TestAgent"
|
||||
mock_agent.description = None
|
||||
@@ -297,7 +299,7 @@ async def test_get_agent_by_id(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test getting an agent by ID."""
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "existing-agent-id"
|
||||
mock_agent.name = "ExistingAgent"
|
||||
mock_agent.description = "An existing agent"
|
||||
@@ -312,7 +314,7 @@ async def test_get_agent_by_id(
|
||||
|
||||
agent = await provider.get_agent("existing-agent-id")
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.id == "existing-agent-id"
|
||||
mock_agents_client.get_agent.assert_called_once_with("existing-agent-id")
|
||||
|
||||
@@ -327,7 +329,7 @@ async def test_get_agent_with_function_tools(
|
||||
mock_function_tool.function = MagicMock()
|
||||
mock_function_tool.function.name = "get_weather"
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-with-tools"
|
||||
mock_agent.name = "AgentWithTools"
|
||||
mock_agent.description = None
|
||||
@@ -356,7 +358,7 @@ async def test_get_agent_with_provided_function_tools(
|
||||
mock_function_tool.function = MagicMock()
|
||||
mock_function_tool.function.name = "get_weather"
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-with-tools"
|
||||
mock_agent.name = "AgentWithTools"
|
||||
mock_agent.description = None
|
||||
@@ -376,7 +378,7 @@ async def test_get_agent_with_provided_function_tools(
|
||||
|
||||
agent = await provider.get_agent("agent-with-tools", tools=get_weather)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.id == "agent-with-tools"
|
||||
|
||||
|
||||
@@ -391,7 +393,7 @@ def test_as_agent_wraps_without_http(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test as_agent wraps Agent object without making HTTP calls."""
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "wrap-agent-id"
|
||||
mock_agent.name = "WrapAgent"
|
||||
mock_agent.description = "Wrapped agent"
|
||||
@@ -405,7 +407,7 @@ def test_as_agent_wraps_without_http(
|
||||
|
||||
agent = provider.as_agent(mock_agent)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.id == "wrap-agent-id"
|
||||
assert agent.name == "WrapAgent"
|
||||
# Ensure no HTTP calls were made
|
||||
@@ -423,7 +425,7 @@ def test_as_agent_with_function_tools_validates(
|
||||
mock_function_tool.function = MagicMock()
|
||||
mock_function_tool.function.name = "my_function"
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-id"
|
||||
mock_agent.name = "Agent"
|
||||
mock_agent.description = None
|
||||
@@ -449,7 +451,7 @@ def test_as_agent_with_hosted_tools(
|
||||
mock_code_interpreter = MagicMock()
|
||||
mock_code_interpreter.type = "code_interpreter"
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-id"
|
||||
mock_agent.name = "Agent"
|
||||
mock_agent.description = None
|
||||
@@ -463,7 +465,7 @@ def test_as_agent_with_hosted_tools(
|
||||
|
||||
agent = provider.as_agent(mock_agent)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
# Should have HostedCodeInterpreterTool in the default_options tools
|
||||
assert any(isinstance(t, HostedCodeInterpreterTool) for t in (agent.default_options.get("tools") or [])) # type: ignore
|
||||
|
||||
@@ -483,7 +485,7 @@ def test_as_agent_with_dict_function_tools_validates(
|
||||
},
|
||||
}
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-id"
|
||||
mock_agent.name = "Agent"
|
||||
mock_agent.description = None
|
||||
@@ -515,7 +517,7 @@ def test_as_agent_with_dict_function_tools_provided(
|
||||
},
|
||||
}
|
||||
|
||||
mock_agent = MagicMock(spec=Agent)
|
||||
mock_agent = MagicMock(spec=AzureAgent)
|
||||
mock_agent.id = "agent-id"
|
||||
mock_agent.name = "Agent"
|
||||
mock_agent.description = None
|
||||
@@ -534,7 +536,7 @@ def test_as_agent_with_dict_function_tools_provided(
|
||||
|
||||
agent = provider.as_agent(mock_agent, tools=dict_based_function)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.id == "agent-id"
|
||||
|
||||
|
||||
@@ -810,7 +812,7 @@ async def test_integration_create_agent() -> None:
|
||||
)
|
||||
|
||||
try:
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "IntegrationTestAgent"
|
||||
assert agent.id is not None
|
||||
finally:
|
||||
@@ -837,7 +839,7 @@ async def test_integration_get_agent() -> None:
|
||||
# Then get it using the provider
|
||||
agent = await provider.get_agent(created.id)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.id == created.id
|
||||
finally:
|
||||
await provider._agents_client.delete_agent(created.id) # type: ignore
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,8 @@ from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponse,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
Content,
|
||||
@@ -22,6 +20,8 @@ from agent_framework import (
|
||||
HostedFileSearchTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Message,
|
||||
SupportsChatGetResponse,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -88,19 +88,19 @@ async def temporary_chat_client(agent_name: str) -> AsyncIterator[AzureAIClient]
|
||||
"""Async context manager that creates an Azure AI agent and yields an `AzureAIClient`.
|
||||
|
||||
The underlying agent version is cleaned up automatically after use.
|
||||
Tests can construct their own `ChatAgent` instances from the yielded client.
|
||||
Tests can construct their own `Agent` instances from the yielded client.
|
||||
"""
|
||||
endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"]
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
|
||||
):
|
||||
chat_client = AzureAIClient(
|
||||
client = AzureAIClient(
|
||||
project_client=project_client,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
try:
|
||||
yield chat_client
|
||||
yield client
|
||||
finally:
|
||||
await project_client.agents.delete(agent_name=agent_name)
|
||||
|
||||
@@ -179,7 +179,7 @@ def test_init_with_project_client(mock_project_client: MagicMock) -> None:
|
||||
assert client.agent_name == "test-agent"
|
||||
assert client.agent_version == "1.0"
|
||||
assert not client._should_close_client # type: ignore
|
||||
assert isinstance(client, ChatClientProtocol)
|
||||
assert isinstance(client, SupportsChatGetResponse)
|
||||
|
||||
|
||||
def test_init_auto_create_client(
|
||||
@@ -298,9 +298,9 @@ async def test_prepare_messages_for_azure_ai_with_system_messages(
|
||||
client = create_test_azure_ai_client(mock_project_client)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", contents=[Content.from_text(text="You are a helpful assistant.")]),
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="Hello")]),
|
||||
ChatMessage(role="assistant", contents=[Content.from_text(text="System response")]),
|
||||
Message(role="system", contents=[Content.from_text(text="You are a helpful assistant.")]),
|
||||
Message(role="user", contents=[Content.from_text(text="Hello")]),
|
||||
Message(role="assistant", contents=[Content.from_text(text="System response")]),
|
||||
]
|
||||
|
||||
result_messages, instructions = client._prepare_messages_for_azure_ai(messages) # type: ignore
|
||||
@@ -318,8 +318,8 @@ async def test_prepare_messages_for_azure_ai_no_system_messages(
|
||||
client = create_test_azure_ai_client(mock_project_client)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="Hello")]),
|
||||
ChatMessage(role="assistant", contents=[Content.from_text(text="Hi there!")]),
|
||||
Message(role="user", contents=[Content.from_text(text="Hello")]),
|
||||
Message(role="assistant", contents=[Content.from_text(text="Hi there!")]),
|
||||
]
|
||||
|
||||
result_messages, instructions = client._prepare_messages_for_azure_ai(messages) # type: ignore
|
||||
@@ -419,7 +419,7 @@ async def test_prepare_options_basic(mock_project_client: MagicMock) -> None:
|
||||
"""Test prepare_options basic functionality."""
|
||||
client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0")
|
||||
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -456,7 +456,7 @@ async def test_prepare_options_with_application_endpoint(
|
||||
agent_version="1",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -498,7 +498,7 @@ async def test_prepare_options_with_application_project_client(
|
||||
agent_version="1",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -977,7 +977,7 @@ async def test_prepare_options_excludes_response_format(
|
||||
"""Test that prepare_options excludes response_format, text, and text_format from final run options."""
|
||||
client = create_test_azure_ai_client(mock_project_client, agent_name="test-agent", agent_version="1.0")
|
||||
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="Hello")])]
|
||||
chat_options: ChatOptions = {}
|
||||
|
||||
with (
|
||||
@@ -1363,10 +1363,10 @@ async def test_integration_options(
|
||||
# Prepare test message
|
||||
if option_name.startswith("tool_choice"):
|
||||
# Use weather-related prompt for tool tests
|
||||
messages = [ChatMessage(role="user", text="What is the weather in Seattle?")]
|
||||
messages = [Message(role="user", text="What is the weather in Seattle?")]
|
||||
else:
|
||||
# Generic prompt for simple options
|
||||
messages = [ChatMessage(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
|
||||
# Build options dict
|
||||
options: dict[str, Any] = {option_name: option_value, "tools": [get_weather]}
|
||||
@@ -1480,11 +1480,11 @@ async def test_integration_agent_options(
|
||||
# Prepare test message
|
||||
if option_name.startswith("response_format"):
|
||||
# Use prompt that works well with structured output
|
||||
messages = [ChatMessage(role="user", text="The weather in Seattle is sunny")]
|
||||
messages.append(ChatMessage(role="user", text="What is the weather in Seattle?"))
|
||||
messages = [Message(role="user", text="The weather in Seattle is sunny")]
|
||||
messages.append(Message(role="user", text="What is the weather in Seattle?"))
|
||||
else:
|
||||
# Generic prompt for simple options
|
||||
messages = [ChatMessage(role="user", text="Say 'Hello World' briefly.")]
|
||||
messages = [Message(role="user", text="Say 'Hello World' briefly.")]
|
||||
|
||||
# Build options dict
|
||||
options = {option_name: option_value}
|
||||
@@ -1621,8 +1621,8 @@ async def test_integration_agent_existing_thread():
|
||||
|
||||
async with (
|
||||
temporary_chat_client(agent_name="af-int-test-existing-thread") as client,
|
||||
ChatAgent(
|
||||
chat_client=client,
|
||||
Agent(
|
||||
client=client,
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent,
|
||||
):
|
||||
@@ -1640,8 +1640,8 @@ async def test_integration_agent_existing_thread():
|
||||
if preserved_thread:
|
||||
async with (
|
||||
temporary_chat_client(agent_name="af-int-test-existing-thread-2") as client,
|
||||
ChatAgent(
|
||||
chat_client=client,
|
||||
Agent(
|
||||
client=client,
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent,
|
||||
):
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatAgent, FunctionTool
|
||||
from agent_framework import Agent, FunctionTool
|
||||
from agent_framework._mcp import MCPTool
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
@@ -158,7 +158,7 @@ async def test_provider_create_agent(
|
||||
description="Test Agent",
|
||||
)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "test-agent"
|
||||
mock_project_client.agents.create_version.assert_called_once()
|
||||
|
||||
@@ -192,7 +192,7 @@ async def test_provider_create_agent_with_env_model(
|
||||
# Call without model parameter - should use env var
|
||||
agent = await provider.create_agent(name="test-agent")
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
# Verify the model from env var was used
|
||||
call_args = mock_project_client.agents.create_version.call_args
|
||||
assert call_args[1]["definition"].model == azure_ai_unit_test_env["AZURE_AI_MODEL_DEPLOYMENT_NAME"]
|
||||
@@ -322,7 +322,7 @@ async def test_provider_get_agent_with_name(mock_project_client: MagicMock) -> N
|
||||
|
||||
agent = await provider.get_agent(name="test-agent")
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "test-agent"
|
||||
mock_project_client.agents.get.assert_called_with(agent_name="test-agent")
|
||||
|
||||
@@ -350,7 +350,7 @@ async def test_provider_get_agent_with_reference(mock_project_client: MagicMock)
|
||||
agent_reference = AgentReference(name="test-agent", version="1.0")
|
||||
agent = await provider.get_agent(reference=agent_reference)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "test-agent"
|
||||
mock_project_client.agents.get_version.assert_called_with(agent_name="test-agent", agent_version="1.0")
|
||||
|
||||
@@ -410,7 +410,7 @@ def test_provider_as_agent(mock_project_client: MagicMock) -> None:
|
||||
with patch("agent_framework_azure_ai._project_provider.AzureAIClient") as mock_azure_ai_client:
|
||||
agent = provider.as_agent(mock_agent_version)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "test-agent"
|
||||
assert agent.description == "Test Agent"
|
||||
|
||||
@@ -709,7 +709,7 @@ async def test_provider_create_and_get_agent_integration() -> None:
|
||||
instructions="You are a helpful assistant. Always respond with 'Hello from provider!'",
|
||||
)
|
||||
|
||||
assert isinstance(agent, ChatAgent)
|
||||
assert isinstance(agent, Agent)
|
||||
assert agent.name == "ProviderTestAgent"
|
||||
|
||||
# Run the agent
|
||||
|
||||
@@ -12,7 +12,7 @@ from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||
import azure.durable_functions as df
|
||||
import azure.functions as func
|
||||
import pytest
|
||||
from agent_framework import AgentResponse, ChatMessage
|
||||
from agent_framework import AgentResponse, Message
|
||||
from agent_framework_durabletask import (
|
||||
MIMETYPE_APPLICATION_JSON,
|
||||
MIMETYPE_TEXT_PLAIN,
|
||||
@@ -356,7 +356,7 @@ class TestAgentEntityOperations:
|
||||
"""Test that entity can run agent operation."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Test response")])
|
||||
return_value=AgentResponse(messages=[Message(role="assistant", text="Test response")])
|
||||
)
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="test-conv-123"))
|
||||
@@ -373,9 +373,7 @@ class TestAgentEntityOperations:
|
||||
async def test_entity_stores_conversation_history(self) -> None:
|
||||
"""Test that the entity stores conversation history."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response 1")])
|
||||
)
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response 1")]))
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="conv-1"))
|
||||
|
||||
@@ -407,9 +405,7 @@ class TestAgentEntityOperations:
|
||||
async def test_entity_increments_message_count(self) -> None:
|
||||
"""Test that the entity increments the message count."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
|
||||
)
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
|
||||
entity = AgentEntity(mock_agent, state_provider=_InMemoryStateProvider(thread_id="conv-1"))
|
||||
|
||||
@@ -448,9 +444,7 @@ class TestAgentEntityFactory:
|
||||
def test_entity_function_handles_run_operation(self) -> None:
|
||||
"""Test that the entity function handles the run operation."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
|
||||
)
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
|
||||
entity_function = create_agent_entity(mock_agent)
|
||||
|
||||
@@ -475,9 +469,7 @@ class TestAgentEntityFactory:
|
||||
def test_entity_function_handles_run_agent_operation(self) -> None:
|
||||
"""Test that the entity function handles the deprecated run_agent operation for backward compatibility."""
|
||||
mock_agent = Mock()
|
||||
mock_agent.run = AsyncMock(
|
||||
return_value=AgentResponse(messages=[ChatMessage(role="assistant", text="Response")])
|
||||
)
|
||||
mock_agent.run = AsyncMock(return_value=AgentResponse(messages=[Message(role="assistant", text="Response")]))
|
||||
|
||||
entity_function = create_agent_entity(mock_agent)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import Any, TypeVar
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
from agent_framework import AgentResponse, ChatMessage
|
||||
from agent_framework import AgentResponse, Message
|
||||
|
||||
from agent_framework_azurefunctions._entities import create_agent_entity
|
||||
|
||||
@@ -19,7 +19,7 @@ FuncT = TypeVar("FuncT", bound=Callable[..., Any])
|
||||
|
||||
def _agent_response(text: str | None) -> AgentResponse:
|
||||
"""Create an AgentResponse with a single assistant message."""
|
||||
message = ChatMessage(role="assistant", text=text) if text is not None else ChatMessage(role="assistant", text="")
|
||||
message = Message(role="assistant", text=text) if text is not None else Message(role="assistant", text="")
|
||||
return AgentResponse(messages=[message])
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from agent_framework import AgentResponse, ChatMessage
|
||||
from agent_framework import AgentResponse, Message
|
||||
from agent_framework_durabletask import DurableAIAgent
|
||||
from azure.durable_functions.models.Task import TaskBase, TaskState
|
||||
|
||||
@@ -136,7 +136,7 @@ class TestAgentResponseHelpers:
|
||||
|
||||
# Simulate successful entity task completion
|
||||
entity_task.state = TaskState.SUCCEEDED
|
||||
entity_task.result = AgentResponse(messages=[ChatMessage(role="assistant", text="Test response")]).to_dict()
|
||||
entity_task.result = AgentResponse(messages=[Message(role="assistant", text="Test response")]).to_dict()
|
||||
|
||||
# Clear pending_tasks to simulate that parent has processed the child
|
||||
task.pending_tasks.clear()
|
||||
@@ -178,7 +178,7 @@ class TestAgentResponseHelpers:
|
||||
|
||||
# Simulate successful entity task with JSON response
|
||||
entity_task.state = TaskState.SUCCEEDED
|
||||
entity_task.result = AgentResponse(messages=[ChatMessage(role="assistant", text='{"answer": "42"}')]).to_dict()
|
||||
entity_task.result = AgentResponse(messages=[Message(role="assistant", text='{"answer": "42"}')]).to_dict()
|
||||
|
||||
# Clear pending_tasks to simulate that parent has processed the child
|
||||
task.pending_tasks.clear()
|
||||
|
||||
@@ -14,7 +14,6 @@ from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
BaseChatClient,
|
||||
ChatAndFunctionMiddlewareTypes,
|
||||
ChatMessage,
|
||||
ChatMiddlewareLayer,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
@@ -24,6 +23,7 @@ from agent_framework import (
|
||||
FunctionInvocationConfiguration,
|
||||
FunctionInvocationLayer,
|
||||
FunctionTool,
|
||||
Message,
|
||||
ResponseStream,
|
||||
ToolProtocol,
|
||||
UsageDetails,
|
||||
@@ -325,7 +325,7 @@ class BedrockChatClient(
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -359,7 +359,7 @@ class BedrockChatClient(
|
||||
|
||||
def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
@@ -410,7 +410,7 @@ class BedrockChatClient(
|
||||
return run_options
|
||||
|
||||
def _prepare_bedrock_messages(
|
||||
self, messages: Sequence[ChatMessage]
|
||||
self, messages: Sequence[Message]
|
||||
) -> tuple[list[dict[str, str]], list[dict[str, Any]]]:
|
||||
prompts: list[dict[str, str]] = []
|
||||
conversation: list[dict[str, Any]] = []
|
||||
@@ -482,7 +482,7 @@ class BedrockChatClient(
|
||||
|
||||
return aligned_blocks
|
||||
|
||||
def _convert_message_to_content_blocks(self, message: ChatMessage) -> list[dict[str, Any]]:
|
||||
def _convert_message_to_content_blocks(self, message: Message) -> list[dict[str, Any]]:
|
||||
blocks: list[dict[str, Any]] = []
|
||||
for content in message.contents:
|
||||
block = self._convert_content_to_bedrock_block(content)
|
||||
@@ -593,7 +593,7 @@ class BedrockChatClient(
|
||||
message = output.get("message", {})
|
||||
content_blocks = message.get("content", []) or []
|
||||
contents = self._parse_message_contents(content_blocks)
|
||||
chat_message = ChatMessage(role="assistant", contents=contents, raw_representation=message)
|
||||
chat_message = Message(role="assistant", contents=contents, raw_representation=message)
|
||||
usage_details = self._parse_usage(response.get("usage") or output.get("usage"))
|
||||
finish_reason = self._map_finish_reason(output.get("completionReason") or response.get("stopReason"))
|
||||
response_id = response.get("responseId") or message.get("id")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework import Agent, tool
|
||||
|
||||
from agent_framework_bedrock import BedrockChatClient
|
||||
|
||||
@@ -17,8 +17,8 @@ def get_weather(city: str) -> dict[str, str]:
|
||||
|
||||
async def main() -> None:
|
||||
"""Run the Bedrock sample agent, invoke the weather tool, and log the response."""
|
||||
agent = ChatAgent(
|
||||
chat_client=BedrockChatClient(),
|
||||
agent = Agent(
|
||||
client=BedrockChatClient(),
|
||||
instructions="You are a concise travel assistant.",
|
||||
name="BedrockWeatherAgent",
|
||||
tool_choice="auto",
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Content, Message
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
|
||||
from agent_framework_bedrock import BedrockChatClient
|
||||
@@ -41,8 +41,8 @@ async def test_get_response_invokes_bedrock_runtime() -> None:
|
||||
)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role="system", contents=[Content.from_text(text="You are concise.")]),
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="hello")]),
|
||||
Message(role="system", contents=[Content.from_text(text="You are concise.")]),
|
||||
Message(role="user", contents=[Content.from_text(text="hello")]),
|
||||
]
|
||||
|
||||
response = await client.get_response(messages=messages, options={"max_tokens": 32})
|
||||
@@ -62,7 +62,7 @@ def test_build_request_requires_non_system_messages() -> None:
|
||||
client=_StubBedrockRuntime(),
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role="system", contents=[Content.from_text(text="Only system text")])]
|
||||
messages = [Message(role="system", contents=[Content.from_text(text="Only system text")])]
|
||||
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
client._prepare_options(messages, {})
|
||||
|
||||
@@ -6,10 +6,10 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
Content,
|
||||
FunctionTool,
|
||||
Message,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_build_request_includes_tool_config() -> None:
|
||||
"tools": [tool],
|
||||
"tool_choice": {"mode": "required", "required_function_name": "get_weather"},
|
||||
}
|
||||
messages = [ChatMessage(role="user", contents=[Content.from_text(text="hi")])]
|
||||
messages = [Message(role="user", contents=[Content.from_text(text="hi")])]
|
||||
|
||||
request = client._prepare_options(messages, options)
|
||||
|
||||
@@ -58,14 +58,14 @@ def test_build_request_serializes_tool_history() -> None:
|
||||
client = _build_client()
|
||||
options: ChatOptions = {}
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="how's weather?")]),
|
||||
ChatMessage(
|
||||
Message(role="user", contents=[Content.from_text(text="how's weather?")]),
|
||||
Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="call-1", name="get_weather", arguments='{"location": "SEA"}')
|
||||
],
|
||||
),
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="tool",
|
||||
contents=[Content.from_function_result(call_id="call-1", result={"answer": "72F"})],
|
||||
),
|
||||
|
||||
@@ -7,9 +7,9 @@ Specifically, it mirrors the [Agent SDK integration](https://github.com/openai/c
|
||||
- `stream_agent_response`: A helper to convert a streamed `AgentResponseUpdate`
|
||||
from a Microsoft Agent Framework agent that implements `SupportsAgentRun` to ChatKit events.
|
||||
- `ThreadItemConverter`: A extendable helper class to convert ChatKit thread items to
|
||||
`ChatMessage` objects that can be consumed by an Agent Framework agent.
|
||||
`Message` objects that can be consumed by an Agent Framework agent.
|
||||
- `simple_to_agent_input`: A helper function that uses the default implementation
|
||||
of `ThreadItemConverter` to convert a ChatKit thread to a list of `ChatMessage`,
|
||||
of `ThreadItemConverter` to convert a ChatKit thread to a list of `Message`,
|
||||
useful for getting started quickly.
|
||||
|
||||
## Installation
|
||||
@@ -63,7 +63,7 @@ from azure.identity import AzureCliCredential
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import Response, StreamingResponse
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.chatkit import simple_to_agent_input, stream_agent_response
|
||||
|
||||
@@ -74,8 +74,8 @@ from chatkit.types import ThreadMetadata, UserMessageItem, ThreadStreamEvent
|
||||
from your_store import YourStore # type: ignore[import-not-found] # Replace with your Store implementation
|
||||
|
||||
# Define your agent with tools
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
agent = Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
tools=[], # Add your tools here
|
||||
)
|
||||
|
||||
@@ -9,8 +9,8 @@ import sys
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
)
|
||||
from chatkit.types import (
|
||||
AssistantMessageItem,
|
||||
@@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThreadItemConverter:
|
||||
"""Helper class to convert ChatKit thread items to Agent Framework ChatMessage objects.
|
||||
"""Helper class to convert ChatKit thread items to Agent Framework Message objects.
|
||||
|
||||
This class provides a base implementation for converting ChatKit thread items
|
||||
to Agent Framework messages. It can be extended to handle attachments,
|
||||
@@ -64,8 +64,8 @@ class ThreadItemConverter:
|
||||
|
||||
async def user_message_to_input(
|
||||
self, item: UserMessageItem, is_last_message: bool = True
|
||||
) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit UserMessageItem to Agent Framework ChatMessage(s).
|
||||
) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit UserMessageItem to Agent Framework Message(s).
|
||||
|
||||
This method is called internally by `to_agent_input()`. Override this method
|
||||
to customize how user messages are converted.
|
||||
@@ -75,7 +75,7 @@ class ThreadItemConverter:
|
||||
is_last_message: Whether this is the last message in the thread (used for quoted_text handling).
|
||||
|
||||
Returns:
|
||||
A ChatMessage, list of messages, or None to skip.
|
||||
A Message, list of messages, or None to skip.
|
||||
|
||||
Note:
|
||||
Instead of calling this method directly, use `to_agent_input()` which handles
|
||||
@@ -102,19 +102,19 @@ class ThreadItemConverter:
|
||||
|
||||
# If only text and no attachments, use text parameter for simplicity
|
||||
if text_content.strip() and not data_contents:
|
||||
user_message = ChatMessage(role="user", text=text_content.strip())
|
||||
user_message = Message(role="user", text=text_content.strip())
|
||||
else:
|
||||
# Build contents list with both text and attachments
|
||||
contents: list[Content] = []
|
||||
if text_content.strip():
|
||||
contents.append(Content.from_text(text=text_content.strip()))
|
||||
contents.extend(data_contents)
|
||||
user_message = ChatMessage(role="user", contents=contents)
|
||||
user_message = Message(role="user", contents=contents)
|
||||
|
||||
# Handle quoted text if this is the last message
|
||||
messages = [user_message]
|
||||
if item.quoted_text and is_last_message:
|
||||
quoted_context = ChatMessage(
|
||||
quoted_context = Message(
|
||||
role="user",
|
||||
text=f"The user is referring to this in particular:\n{item.quoted_text}",
|
||||
)
|
||||
@@ -179,10 +179,8 @@ class ThreadItemConverter:
|
||||
# Subclasses can override this method to provide custom handling
|
||||
return None
|
||||
|
||||
def hidden_context_to_input(
|
||||
self, item: HiddenContextItem | SDKHiddenContextItem
|
||||
) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit HiddenContextItem or SDKHiddenContextItem to Agent Framework ChatMessage(s).
|
||||
def hidden_context_to_input(self, item: HiddenContextItem | SDKHiddenContextItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit HiddenContextItem or SDKHiddenContextItem to Agent Framework Message(s).
|
||||
|
||||
This method is called internally by `to_agent_input()`. Override this method
|
||||
to customize how hidden context is converted.
|
||||
@@ -195,7 +193,7 @@ class ThreadItemConverter:
|
||||
item: The ChatKit hidden context item to convert.
|
||||
|
||||
Returns:
|
||||
A ChatMessage with system role, a list of messages, or None to skip.
|
||||
A Message with system role, a list of messages, or None to skip.
|
||||
|
||||
Note:
|
||||
Instead of calling this method directly, use `to_agent_input()` which handles
|
||||
@@ -213,9 +211,9 @@ class ThreadItemConverter:
|
||||
content="User's email: user@example.com",
|
||||
)
|
||||
message = converter.hidden_context_to_input(hidden_item)
|
||||
# Returns: ChatMessage(role=SYSTEM, text="<HIDDEN_CONTEXT>User's email: ...</HIDDEN_CONTEXT>")
|
||||
# Returns: Message(role=SYSTEM, text="<HIDDEN_CONTEXT>User's email: ...</HIDDEN_CONTEXT>")
|
||||
"""
|
||||
return ChatMessage(role="system", text=f"<HIDDEN_CONTEXT>{item.content}</HIDDEN_CONTEXT>")
|
||||
return Message(role="system", text=f"<HIDDEN_CONTEXT>{item.content}</HIDDEN_CONTEXT>")
|
||||
|
||||
def tag_to_message_content(self, tag: UserMessageTagContent) -> Content:
|
||||
"""Convert a ChatKit tag (@-mention) to Agent Framework content.
|
||||
@@ -250,8 +248,8 @@ class ThreadItemConverter:
|
||||
name = getattr(tag.data, "name", tag.text if hasattr(tag, "text") else "unknown")
|
||||
return Content.from_text(text=f"<TAG>Name:{name}</TAG>")
|
||||
|
||||
def task_to_input(self, item: TaskItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit TaskItem to Agent Framework ChatMessage(s).
|
||||
def task_to_input(self, item: TaskItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit TaskItem to Agent Framework Message(s).
|
||||
|
||||
This method is called internally by `to_agent_input()`. Override this method
|
||||
to customize how tasks are converted.
|
||||
@@ -263,7 +261,7 @@ class ThreadItemConverter:
|
||||
item: The ChatKit task item to convert.
|
||||
|
||||
Returns:
|
||||
A ChatMessage, a list of messages, or None to skip the task.
|
||||
A Message, a list of messages, or None to skip the task.
|
||||
|
||||
Note:
|
||||
Instead of calling this method directly, use `to_agent_input()` which handles
|
||||
@@ -294,10 +292,10 @@ class ThreadItemConverter:
|
||||
f"A message was displayed to the user that the following task was performed:\n<Task>\n{task_text}\n</Task>"
|
||||
)
|
||||
|
||||
return ChatMessage(role="user", text=text)
|
||||
return Message(role="user", text=text)
|
||||
|
||||
def workflow_to_input(self, item: WorkflowItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit WorkflowItem to Agent Framework ChatMessage(s).
|
||||
def workflow_to_input(self, item: WorkflowItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit WorkflowItem to Agent Framework Message(s).
|
||||
|
||||
This method is called internally by `to_agent_input()`. Override this method
|
||||
to customize how workflows are converted.
|
||||
@@ -336,7 +334,7 @@ class ThreadItemConverter:
|
||||
messages = converter.workflow_to_input(workflow_item)
|
||||
# Returns list of messages for each task
|
||||
"""
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
for task in item.workflow.tasks:
|
||||
if task.type != "custom" or (not task.title and not task.content):
|
||||
continue
|
||||
@@ -349,12 +347,12 @@ class ThreadItemConverter:
|
||||
f"<Task>\n{task_text}\n</Task>"
|
||||
)
|
||||
|
||||
messages.append(ChatMessage(role="user", text=text))
|
||||
messages.append(Message(role="user", text=text))
|
||||
|
||||
return messages if messages else None
|
||||
|
||||
def widget_to_input(self, item: WidgetItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit WidgetItem to Agent Framework ChatMessage(s).
|
||||
def widget_to_input(self, item: WidgetItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit WidgetItem to Agent Framework Message(s).
|
||||
|
||||
This method is called internally by `to_agent_input()`. Override this method
|
||||
to customize how widgets are converted.
|
||||
@@ -367,7 +365,7 @@ class ThreadItemConverter:
|
||||
item: The ChatKit widget item to convert.
|
||||
|
||||
Returns:
|
||||
A ChatMessage describing the widget, or None to skip.
|
||||
A Message describing the widget, or None to skip.
|
||||
|
||||
Note:
|
||||
Instead of calling this method directly, use `to_agent_input()` which handles
|
||||
@@ -391,13 +389,13 @@ class ThreadItemConverter:
|
||||
try:
|
||||
widget_json = item.widget.model_dump_json(exclude_unset=True, exclude_none=True)
|
||||
text = f"The following graphical UI widget (id: {item.id}) was displayed to the user:{widget_json}"
|
||||
return ChatMessage(role="user", text=text)
|
||||
return Message(role="user", text=text)
|
||||
except Exception:
|
||||
# If JSON serialization fails, skip the widget
|
||||
return None
|
||||
|
||||
async def assistant_message_to_input(self, item: AssistantMessageItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit AssistantMessageItem to Agent Framework ChatMessage(s).
|
||||
async def assistant_message_to_input(self, item: AssistantMessageItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit AssistantMessageItem to Agent Framework Message(s).
|
||||
|
||||
The default implementation extracts text from all content parts and creates
|
||||
an assistant message.
|
||||
@@ -406,7 +404,7 @@ class ThreadItemConverter:
|
||||
item: The ChatKit assistant message item to convert.
|
||||
|
||||
Returns:
|
||||
A ChatMessage with assistant role, or None to skip.
|
||||
A Message with assistant role, or None to skip.
|
||||
|
||||
Note:
|
||||
Instead of calling this method directly, use `to_agent_input()` which handles
|
||||
@@ -417,10 +415,10 @@ class ThreadItemConverter:
|
||||
if not text_parts:
|
||||
return None
|
||||
|
||||
return ChatMessage(role="assistant", text="".join(text_parts))
|
||||
return Message(role="assistant", text="".join(text_parts))
|
||||
|
||||
async def client_tool_call_to_input(self, item: ClientToolCallItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit ClientToolCallItem to Agent Framework ChatMessage(s).
|
||||
async def client_tool_call_to_input(self, item: ClientToolCallItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit ClientToolCallItem to Agent Framework Message(s).
|
||||
|
||||
The default implementation converts completed tool calls into function call
|
||||
and result content.
|
||||
@@ -442,7 +440,7 @@ class ThreadItemConverter:
|
||||
import json
|
||||
|
||||
# Create function call message
|
||||
function_call_msg = ChatMessage(
|
||||
function_call_msg = Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(
|
||||
@@ -454,7 +452,7 @@ class ThreadItemConverter:
|
||||
)
|
||||
|
||||
# Create function result message
|
||||
function_result_msg = ChatMessage(
|
||||
function_result_msg = Message(
|
||||
role="tool",
|
||||
contents=[
|
||||
Content.from_function_result(
|
||||
@@ -466,8 +464,8 @@ class ThreadItemConverter:
|
||||
|
||||
return [function_call_msg, function_result_msg]
|
||||
|
||||
async def end_of_turn_to_input(self, item: EndOfTurnItem) -> ChatMessage | list[ChatMessage] | None:
|
||||
"""Convert a ChatKit EndOfTurnItem to Agent Framework ChatMessage(s).
|
||||
async def end_of_turn_to_input(self, item: EndOfTurnItem) -> Message | list[Message] | None:
|
||||
"""Convert a ChatKit EndOfTurnItem to Agent Framework Message(s).
|
||||
|
||||
The default implementation skips end-of-turn markers as they are only UI hints.
|
||||
|
||||
@@ -488,15 +486,15 @@ class ThreadItemConverter:
|
||||
self,
|
||||
item: ThreadItem,
|
||||
is_last_message: bool = True,
|
||||
) -> list[ChatMessage]:
|
||||
"""Internal method to convert a single ThreadItem to ChatMessage(s).
|
||||
) -> list[Message]:
|
||||
"""Internal method to convert a single ThreadItem to Message(s).
|
||||
|
||||
Args:
|
||||
item: The thread item to convert.
|
||||
is_last_message: Whether this is the last item in the thread.
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects (may be empty).
|
||||
A list of Message objects (may be empty).
|
||||
"""
|
||||
match item:
|
||||
case UserMessageItem():
|
||||
@@ -535,7 +533,7 @@ class ThreadItemConverter:
|
||||
async def to_agent_input(
|
||||
self,
|
||||
thread_items: Sequence[ThreadItem] | ThreadItem,
|
||||
) -> list[ChatMessage]:
|
||||
) -> list[Message]:
|
||||
"""Convert ChatKit thread items to Agent Framework ChatMessages.
|
||||
|
||||
This is the main entry point for converting ChatKit thread items. It handles
|
||||
@@ -546,7 +544,7 @@ class ThreadItemConverter:
|
||||
thread_items: A single ThreadItem or a sequence of ThreadItems to convert.
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects that can be sent to an Agent Framework agent.
|
||||
A list of Message objects that can be sent to an Agent Framework agent.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
@@ -562,14 +560,14 @@ class ThreadItemConverter:
|
||||
messages = await converter.to_agent_input([user_message_item, assistant_message_item, task_item])
|
||||
|
||||
# Use with agent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
|
||||
agent = ChatAgent(...)
|
||||
agent = Agent(...)
|
||||
response = await agent.run(messages)
|
||||
"""
|
||||
thread_items = list(thread_items) if isinstance(thread_items, Sequence) else [thread_items]
|
||||
|
||||
output: list[ChatMessage] = []
|
||||
output: list[Message] = []
|
||||
for item in thread_items:
|
||||
output.extend(
|
||||
await self._thread_item_to_input_item(
|
||||
@@ -584,7 +582,7 @@ class ThreadItemConverter:
|
||||
_DEFAULT_CONVERTER = ThreadItemConverter()
|
||||
|
||||
|
||||
async def simple_to_agent_input(thread_items: Sequence[ThreadItem] | ThreadItem) -> list[ChatMessage]:
|
||||
async def simple_to_agent_input(thread_items: Sequence[ThreadItem] | ThreadItem) -> list[Message]:
|
||||
"""Helper function that uses the default ThreadItemConverter.
|
||||
|
||||
This function provides a quick way to get started with ChatKit integration
|
||||
@@ -594,7 +592,7 @@ async def simple_to_agent_input(thread_items: Sequence[ThreadItem] | ThreadItem)
|
||||
thread_items: A single ThreadItem or a sequence of ThreadItems to convert.
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects that can be sent to an Agent Framework agent.
|
||||
A list of Message objects that can be sent to an Agent Framework agent.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
from chatkit.types import UserMessageTextContent
|
||||
|
||||
from agent_framework_chatkit import ThreadItemConverter, simple_to_agent_input
|
||||
@@ -43,7 +43,7 @@ class TestThreadItemConverter:
|
||||
result = await converter.to_agent_input(input_item)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], ChatMessage)
|
||||
assert isinstance(result[0], Message)
|
||||
assert result[0].role == "user"
|
||||
assert result[0].text == "Hello, how can you help me?"
|
||||
|
||||
@@ -110,13 +110,13 @@ class TestThreadItemConverter:
|
||||
assert result[0].text == "Hello world!"
|
||||
|
||||
def test_hidden_context_to_input(self, converter):
|
||||
"""Test converting hidden context item to ChatMessage."""
|
||||
"""Test converting hidden context item to Message."""
|
||||
hidden_item = Mock()
|
||||
hidden_item.content = "This is hidden context information"
|
||||
|
||||
result = converter.hidden_context_to_input(hidden_item)
|
||||
|
||||
assert isinstance(result, ChatMessage)
|
||||
assert isinstance(result, Message)
|
||||
assert result.role == "system"
|
||||
assert result.text == "<HIDDEN_CONTEXT>This is hidden context information</HIDDEN_CONTEXT>"
|
||||
|
||||
@@ -288,7 +288,7 @@ class TestThreadItemConverter:
|
||||
assert message.contents[1].media_type == "application/pdf"
|
||||
|
||||
def test_task_to_input(self, converter):
|
||||
"""Test converting TaskItem to ChatMessage."""
|
||||
"""Test converting TaskItem to Message."""
|
||||
from datetime import datetime
|
||||
|
||||
from chatkit.types import CustomTask, TaskItem
|
||||
@@ -302,7 +302,7 @@ class TestThreadItemConverter:
|
||||
)
|
||||
|
||||
result = converter.task_to_input(task_item)
|
||||
assert isinstance(result, ChatMessage)
|
||||
assert isinstance(result, Message)
|
||||
assert result.role == "user"
|
||||
assert "Analysis: Analyzed the data" in result.text
|
||||
assert "<Task>" in result.text
|
||||
@@ -347,7 +347,7 @@ class TestThreadItemConverter:
|
||||
result = converter.workflow_to_input(workflow_item)
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 2
|
||||
assert all(isinstance(msg, ChatMessage) for msg in result)
|
||||
assert all(isinstance(msg, Message) for msg in result)
|
||||
assert "Step 1: First step" in result[0].text
|
||||
assert "Step 2: Second step" in result[1].text
|
||||
|
||||
@@ -369,7 +369,7 @@ class TestThreadItemConverter:
|
||||
assert result is None
|
||||
|
||||
def test_widget_to_input(self, converter):
|
||||
"""Test converting WidgetItem to ChatMessage."""
|
||||
"""Test converting WidgetItem to Message."""
|
||||
from datetime import datetime
|
||||
|
||||
from chatkit.types import WidgetItem
|
||||
@@ -384,7 +384,7 @@ class TestThreadItemConverter:
|
||||
)
|
||||
|
||||
result = converter.widget_to_input(widget_item)
|
||||
assert isinstance(result, ChatMessage)
|
||||
assert isinstance(result, Message)
|
||||
assert result.role == "user"
|
||||
assert "widget_1" in result.text
|
||||
assert "graphical UI widget" in result.text
|
||||
@@ -417,6 +417,6 @@ class TestSimpleToAgentInput:
|
||||
result = await simple_to_agent_input(input_item)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], ChatMessage)
|
||||
assert isinstance(result[0], Message)
|
||||
assert result[0].role == "user"
|
||||
assert result[0].text == "Test message"
|
||||
|
||||
@@ -14,10 +14,10 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Content,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
Message,
|
||||
ToolProtocol,
|
||||
get_logger,
|
||||
normalize_messages,
|
||||
@@ -541,7 +541,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
if "permission_mode" in options:
|
||||
await self._client.set_permission_mode(options["permission_mode"])
|
||||
|
||||
def _format_prompt(self, messages: list[ChatMessage] | None) -> str:
|
||||
def _format_prompt(self, messages: list[Message] | None) -> str:
|
||||
"""Format messages into a prompt string.
|
||||
|
||||
Args:
|
||||
@@ -557,7 +557,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -568,7 +568,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
@overload
|
||||
async def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -578,7 +578,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -608,7 +608,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
async def _run_non_streaming(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
options: OptionsT | MutableMapping[str, Any] | None = None,
|
||||
@@ -622,7 +622,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
async def _run_streaming(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
options: OptionsT | MutableMapping[str, Any] | None = None,
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import AgentResponseUpdate, AgentThread, ChatMessage, Content, tool
|
||||
from agent_framework import AgentResponseUpdate, AgentThread, Content, Message, tool
|
||||
|
||||
from agent_framework_claude import ClaudeAgent, ClaudeAgentOptions, ClaudeAgentSettings
|
||||
from agent_framework_claude._agent import TOOLS_MCP_SERVER_NAME
|
||||
@@ -686,7 +686,7 @@ class TestFormatPrompt:
|
||||
def test_format_user_message(self) -> None:
|
||||
"""Test formatting user message."""
|
||||
agent = ClaudeAgent()
|
||||
msg = ChatMessage(
|
||||
msg = Message(
|
||||
role="user",
|
||||
contents=[Content.from_text(text="Hello")],
|
||||
)
|
||||
@@ -697,9 +697,9 @@ class TestFormatPrompt:
|
||||
"""Test formatting multiple messages."""
|
||||
agent = ClaudeAgent()
|
||||
messages = [
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="Hi")]),
|
||||
ChatMessage(role="assistant", contents=[Content.from_text(text="Hello!")]),
|
||||
ChatMessage(role="user", contents=[Content.from_text(text="How are you?")]),
|
||||
Message(role="user", contents=[Content.from_text(text="Hi")]),
|
||||
Message(role="assistant", contents=[Content.from_text(text="Hello!")]),
|
||||
Message(role="user", contents=[Content.from_text(text="How are you?")]),
|
||||
]
|
||||
result = agent._format_prompt(messages) # type: ignore[reportPrivateUsage]
|
||||
assert "Hi" in result
|
||||
|
||||
@@ -11,9 +11,9 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Content,
|
||||
ContextProvider,
|
||||
Message,
|
||||
ResponseStream,
|
||||
normalize_messages,
|
||||
)
|
||||
@@ -210,7 +210,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -220,7 +220,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -229,7 +229,7 @@ class CopilotStudioAgent(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,
|
||||
@@ -259,7 +259,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
|
||||
async def _run_impl(
|
||||
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,
|
||||
@@ -274,7 +274,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
question = "\n".join([message.text for message in input_messages])
|
||||
|
||||
activities = self.client.ask_question(question, thread.service_thread_id)
|
||||
response_messages: list[ChatMessage] = []
|
||||
response_messages: list[Message] = []
|
||||
response_id: str | None = None
|
||||
|
||||
response_messages = [message async for message in self._process_activities(activities, streaming=False)]
|
||||
@@ -284,7 +284,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
|
||||
def _run_stream_impl(
|
||||
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,
|
||||
@@ -338,7 +338,7 @@ class CopilotStudioAgent(BaseAgent):
|
||||
|
||||
return conversation_id
|
||||
|
||||
async def _process_activities(self, activities: AsyncIterable[Any], streaming: bool) -> AsyncIterable[ChatMessage]:
|
||||
async def _process_activities(self, activities: AsyncIterable[Any], streaming: bool) -> AsyncIterable[Message]:
|
||||
"""Process activities from the Copilot Studio agent.
|
||||
|
||||
Args:
|
||||
@@ -347,13 +347,13 @@ class CopilotStudioAgent(BaseAgent):
|
||||
or non-streaming (message activities) responses.
|
||||
|
||||
Yields:
|
||||
ChatMessage objects created from the activities.
|
||||
Message objects created from the activities.
|
||||
"""
|
||||
async for activity in activities:
|
||||
if activity.text and (
|
||||
(activity.type == "message" and not streaming) or (activity.type == "typing" and streaming)
|
||||
):
|
||||
yield ChatMessage(
|
||||
yield Message(
|
||||
role="assistant",
|
||||
contents=[Content.from_text(activity.text)],
|
||||
author_name=activity.from_property.name if activity.from_property else None,
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, AgentThread, ChatMessage, Content
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, AgentThread, Content, Message
|
||||
from agent_framework.exceptions import ServiceException, ServiceInitializationError
|
||||
from microsoft_agents.copilotstudio.client import CopilotClient
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestCopilotStudioAgent:
|
||||
assert response.messages[0].role == "assistant"
|
||||
|
||||
async def test_run_with_chat_message(self, mock_copilot_client: MagicMock, mock_activity: MagicMock) -> None:
|
||||
"""Test run method with ChatMessage."""
|
||||
"""Test run method with Message."""
|
||||
agent = CopilotStudioAgent(client=mock_copilot_client)
|
||||
|
||||
conversation_activity = MagicMock()
|
||||
@@ -143,7 +143,7 @@ class TestCopilotStudioAgent:
|
||||
mock_copilot_client.start_conversation.return_value = create_async_generator([conversation_activity])
|
||||
mock_copilot_client.ask_question.return_value = create_async_generator([mock_activity])
|
||||
|
||||
chat_message = ChatMessage(role="user", contents=[Content.from_text("test message")])
|
||||
chat_message = Message(role="user", contents=[Content.from_text("test message")])
|
||||
response = await agent.run(chat_message)
|
||||
|
||||
assert isinstance(response, AgentResponse)
|
||||
|
||||
@@ -9,7 +9,7 @@ agent_framework/
|
||||
├── __init__.py # Public API exports
|
||||
├── _agents.py # Agent implementations
|
||||
├── _clients.py # Chat client base classes and protocols
|
||||
├── _types.py # Core types (ChatMessage, ChatResponse, Content, etc.)
|
||||
├── _types.py # Core types (Message, ChatResponse, Content, etc.)
|
||||
├── _tools.py # Tool definitions and function invocation
|
||||
├── _middleware.py # Middleware system for request/response interception
|
||||
├── _threads.py # AgentThread and message store abstractions
|
||||
@@ -27,16 +27,16 @@ agent_framework/
|
||||
|
||||
- **`SupportsAgentRun`** - Protocol defining the agent interface
|
||||
- **`BaseAgent`** - Abstract base class for agents
|
||||
- **`ChatAgent`** - Main agent class wrapping a chat client with tools, instructions, and middleware
|
||||
- **`Agent`** - Main agent class wrapping a chat client with tools, instructions, and middleware
|
||||
|
||||
### Chat Clients (`_clients.py`)
|
||||
|
||||
- **`ChatClientProtocol`** - Protocol for chat client implementations
|
||||
- **`SupportsChatGetResponse`** - Protocol for chat client implementations
|
||||
- **`BaseChatClient`** - Abstract base class with middleware support; subclasses implement `_inner_get_response()` and `_inner_get_streaming_response()`
|
||||
|
||||
### Types (`_types.py`)
|
||||
|
||||
- **`ChatMessage`** - Represents a chat message with role, content, and metadata
|
||||
- **`Message`** - Represents a chat message with role, content, and metadata
|
||||
- **`ChatResponse`** - Response from a chat client containing messages and usage
|
||||
- **`ChatResponseUpdate`** - Streaming response update
|
||||
- **`AgentResponse`** / **`AgentResponseUpdate`** - Agent-level response wrappers
|
||||
@@ -91,11 +91,11 @@ agent_framework/
|
||||
### Creating an Agent
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
instructions="You are helpful.",
|
||||
tools=[my_function],
|
||||
)
|
||||
@@ -114,7 +114,7 @@ agent = OpenAIChatClient().as_agent(
|
||||
### Middleware Pipeline
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent, AgentMiddleware, AgentContext
|
||||
from agent_framework import Agent, AgentMiddleware, AgentContext
|
||||
|
||||
class LoggingMiddleware(AgentMiddleware):
|
||||
async def process(self, context: AgentContext, call_next) -> None:
|
||||
@@ -122,18 +122,18 @@ class LoggingMiddleware(AgentMiddleware):
|
||||
await call_next(context)
|
||||
print(f"Output: {context.result}")
|
||||
|
||||
agent = ChatAgent(..., middleware=[LoggingMiddleware()])
|
||||
agent = Agent(..., middleware=[LoggingMiddleware()])
|
||||
```
|
||||
|
||||
### Custom Chat Client
|
||||
|
||||
```python
|
||||
from agent_framework import BaseChatClient, ChatResponse, ChatMessage
|
||||
from agent_framework import BaseChatClient, ChatResponse, Message
|
||||
|
||||
class MyClient(BaseChatClient):
|
||||
async def _inner_get_response(self, *, messages, options, **kwargs) -> ChatResponse:
|
||||
# Call your LLM here
|
||||
return ChatResponse(messages=[ChatMessage(role="assistant", text="Hi!")])
|
||||
return ChatResponse(messages=[Message(role="assistant", text="Hi!")])
|
||||
|
||||
async def _inner_get_streaming_response(self, *, messages, options, **kwargs):
|
||||
yield ChatResponseUpdate(...)
|
||||
|
||||
@@ -45,7 +45,7 @@ You can also override environment variables by explicitly passing configuration
|
||||
```python
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
|
||||
chat_client = AzureOpenAIChatClient(
|
||||
client = AzureOpenAIChatClient(
|
||||
api_key="",
|
||||
endpoint="",
|
||||
deployment_name="",
|
||||
@@ -61,12 +61,12 @@ Create agents and invoke them directly:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
async def main():
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
instructions="""
|
||||
1) A robot may not injure a human being...
|
||||
2) A robot must obey orders given it by human beings...
|
||||
@@ -90,14 +90,14 @@ You can use the chat client classes directly for advanced workflows:
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework import ChatMessage, Role
|
||||
from agent_framework import Message, Role
|
||||
|
||||
async def main():
|
||||
client = OpenAIChatClient()
|
||||
|
||||
messages = [
|
||||
ChatMessage("system", ["You are a helpful assistant."]),
|
||||
ChatMessage("user", ["Write a haiku about Agent Framework."])
|
||||
Message("system", ["You are a helpful assistant."]),
|
||||
Message("user", ["Write a haiku about Agent Framework."])
|
||||
]
|
||||
|
||||
response = await client.get_response(messages)
|
||||
@@ -123,7 +123,7 @@ import asyncio
|
||||
from typing import Annotated
|
||||
from random import randint
|
||||
from pydantic import Field
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
@@ -145,8 +145,8 @@ def get_menu_specials() -> str:
|
||||
|
||||
|
||||
async def main():
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
agent = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and restaurant information.",
|
||||
tools=[get_weather, get_menu_specials]
|
||||
)
|
||||
@@ -169,20 +169,20 @@ Coordinate multiple agents to collaborate on complex tasks using orchestration p
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
async def main():
|
||||
# Create specialized agents
|
||||
writer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
writer = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
name="Writer",
|
||||
instructions="You are a creative content writer. Generate and refine slogans based on feedback."
|
||||
)
|
||||
|
||||
reviewer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
reviewer = Agent(
|
||||
client=OpenAIChatClient(),
|
||||
name="Reviewer",
|
||||
instructions="You are a critical reviewer. Provide detailed feedback on proposed slogans."
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ from mcp.server.lowlevel import Server
|
||||
from mcp.shared.exceptions import McpError
|
||||
from pydantic import BaseModel, Field, create_model
|
||||
|
||||
from ._clients import BaseChatClient, ChatClientProtocol
|
||||
from ._clients import BaseChatClient, SupportsChatGetResponse
|
||||
from ._logging import get_logger
|
||||
from ._mcp import LOG_LEVEL_MAPPING, MCPTool
|
||||
from ._memory import Context, ContextProvider
|
||||
@@ -43,9 +43,9 @@ from ._tools import (
|
||||
from ._types import (
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Message,
|
||||
ResponseStream,
|
||||
map_chat_to_agent_update,
|
||||
normalize_messages,
|
||||
@@ -157,15 +157,15 @@ def _sanitize_agent_name(agent_name: str | None) -> str | None:
|
||||
|
||||
class _RunContext(TypedDict):
|
||||
thread: AgentThread
|
||||
input_messages: list[ChatMessage]
|
||||
thread_messages: list[ChatMessage]
|
||||
input_messages: list[Message]
|
||||
thread_messages: list[Message]
|
||||
agent_name: str
|
||||
chat_options: dict[str, Any]
|
||||
filtered_kwargs: dict[str, Any]
|
||||
finalize_kwargs: dict[str, Any]
|
||||
|
||||
|
||||
__all__ = ["BaseAgent", "ChatAgent", "RawChatAgent", "SupportsAgentRun"]
|
||||
__all__ = ["Agent", "BaseAgent", "RawAgent", "SupportsAgentRun"]
|
||||
|
||||
|
||||
# region Agent Protocol
|
||||
@@ -230,7 +230,7 @@ class SupportsAgentRun(Protocol):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -242,7 +242,7 @@ class SupportsAgentRun(Protocol):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -253,7 +253,7 @@ class SupportsAgentRun(Protocol):
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -292,7 +292,7 @@ class BaseAgent(SerializationMixin):
|
||||
"""Base class for all Agent Framework agents.
|
||||
|
||||
This is the minimal base class without middleware or telemetry layers.
|
||||
For most use cases, prefer :class:`ChatAgent` which includes all standard layers.
|
||||
For most use cases, prefer :class:`Agent` which includes all standard layers.
|
||||
|
||||
This class provides core functionality for agent implementations, including
|
||||
context providers, middleware support, and thread management.
|
||||
@@ -300,7 +300,7 @@ class BaseAgent(SerializationMixin):
|
||||
Note:
|
||||
BaseAgent cannot be instantiated directly as it doesn't implement the
|
||||
``run()`` and other methods required by SupportsAgentRun.
|
||||
Use a concrete implementation like ChatAgent or create a subclass.
|
||||
Use a concrete implementation like Agent or create a subclass.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
@@ -380,8 +380,8 @@ class BaseAgent(SerializationMixin):
|
||||
async def _notify_thread_of_new_messages(
|
||||
self,
|
||||
thread: AgentThread,
|
||||
input_messages: ChatMessage | Sequence[ChatMessage],
|
||||
response_messages: ChatMessage | Sequence[ChatMessage],
|
||||
input_messages: Message | Sequence[Message],
|
||||
response_messages: Message | Sequence[Message],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Notify the thread of new messages.
|
||||
@@ -394,9 +394,9 @@ class BaseAgent(SerializationMixin):
|
||||
response_messages: The response messages to notify about.
|
||||
**kwargs: Any extra arguments to pass from the agent run.
|
||||
"""
|
||||
if isinstance(input_messages, ChatMessage) or len(input_messages) > 0:
|
||||
if isinstance(input_messages, Message) or len(input_messages) > 0:
|
||||
await thread.on_new_messages(input_messages)
|
||||
if isinstance(response_messages, ChatMessage) or len(response_messages) > 0:
|
||||
if isinstance(response_messages, Message) or len(response_messages) > 0:
|
||||
await thread.on_new_messages(response_messages)
|
||||
if thread.context_provider:
|
||||
await thread.context_provider.invoked(input_messages, response_messages, **kwargs)
|
||||
@@ -459,16 +459,16 @@ class BaseAgent(SerializationMixin):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
|
||||
# Create an agent
|
||||
agent = ChatAgent(chat_client=client, name="research-agent", description="Performs research tasks")
|
||||
agent = Agent(client=client, name="research-agent", description="Performs research tasks")
|
||||
|
||||
# Convert the agent to a tool
|
||||
research_tool = agent.as_tool()
|
||||
|
||||
# Use the tool with another agent
|
||||
coordinator = ChatAgent(chat_client=client, name="coordinator", tools=research_tool)
|
||||
coordinator = Agent(client=client, name="coordinator", tools=research_tool)
|
||||
"""
|
||||
# Verify that self implements SupportsAgentRun
|
||||
if not isinstance(self, SupportsAgentRun):
|
||||
@@ -523,14 +523,14 @@ class BaseAgent(SerializationMixin):
|
||||
return agent_tool
|
||||
|
||||
|
||||
# region ChatAgent
|
||||
# region Agent
|
||||
|
||||
|
||||
class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
"""A Chat Client Agent without middleware or telemetry layers.
|
||||
|
||||
This is the core chat agent implementation. For most use cases,
|
||||
prefer :class:`ChatAgent` which includes all standard layers.
|
||||
prefer :class:`Agent` which includes all standard layers.
|
||||
|
||||
This is the primary agent implementation that uses a chat client to interact
|
||||
with language models. It supports tools, context providers, middleware, and
|
||||
@@ -544,12 +544,12 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
# Create a basic chat agent
|
||||
client = OpenAIChatClient(model_id="gpt-4")
|
||||
agent = ChatAgent(chat_client=client, name="assistant", description="A helpful assistant")
|
||||
agent = Agent(client=client, name="assistant", description="A helpful assistant")
|
||||
|
||||
# Run the agent with a simple message
|
||||
response = await agent.run("Hello, how are you?")
|
||||
@@ -564,8 +564,8 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
return f"The weather in {location} is sunny."
|
||||
|
||||
|
||||
agent = ChatAgent(
|
||||
chat_client=client,
|
||||
agent = Agent(
|
||||
client=client,
|
||||
name="weather-agent",
|
||||
instructions="You are a weather assistant.",
|
||||
tools=get_weather,
|
||||
@@ -583,12 +583,12 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import Agent
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIChatOptions
|
||||
|
||||
client = OpenAIChatClient(model_id="gpt-4o")
|
||||
agent: ChatAgent[OpenAIChatOptions] = ChatAgent(
|
||||
chat_client=client,
|
||||
agent: Agent[OpenAIChatOptions] = Agent(
|
||||
client=client,
|
||||
name="reasoning-agent",
|
||||
instructions="You are a reasoning assistant.",
|
||||
options={
|
||||
@@ -609,7 +609,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_client: ChatClientProtocol[OptionsCoT],
|
||||
client: SupportsChatGetResponse[OptionsCoT],
|
||||
instructions: str | None = None,
|
||||
*,
|
||||
id: str | None = None,
|
||||
@@ -625,10 +625,10 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
context_provider: ContextProvider | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a ChatAgent instance.
|
||||
"""Initialize a Agent instance.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client to use for the agent.
|
||||
client: The chat client to use for the agent.
|
||||
instructions: Optional instructions for the agent.
|
||||
These will be put into the messages sent to the chat client service as a system message.
|
||||
|
||||
@@ -641,7 +641,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
context_provider: The context providers to include during agent invocation.
|
||||
middleware: List of middleware to intercept agent and function invocations.
|
||||
default_options: A TypedDict containing chat options. When using a typed agent like
|
||||
``ChatAgent[OpenAIChatOptions]``, this enables IDE autocomplete for
|
||||
``Agent[OpenAIChatOptions]``, this enables IDE autocomplete for
|
||||
provider-specific options including temperature, max_tokens, model_id,
|
||||
tool_choice, and provider-specific options like reasoning_effort.
|
||||
You can also create your own TypedDict for custom chat clients.
|
||||
@@ -663,7 +663,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
"Use conversation_id for service-managed threads or chat_message_store_factory for local storage."
|
||||
)
|
||||
|
||||
if not isinstance(chat_client, FunctionInvocationLayer) and isinstance(chat_client, BaseChatClient):
|
||||
if not isinstance(client, FunctionInvocationLayer) and isinstance(client, BaseChatClient):
|
||||
logger.warning(
|
||||
"The provided chat client does not support function invoking, this might limit agent capabilities."
|
||||
)
|
||||
@@ -675,7 +675,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
context_provider=context_provider,
|
||||
**kwargs,
|
||||
)
|
||||
self.chat_client = chat_client
|
||||
self.client = client
|
||||
self.chat_message_store_factory = chat_message_store_factory
|
||||
|
||||
# Get tools from options or named parameter (named param takes precedence)
|
||||
@@ -702,7 +702,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
# Build chat options dict
|
||||
self.default_options: dict[str, Any] = {
|
||||
"model_id": opts.pop("model_id", None) or (getattr(self.chat_client, "model_id", None)),
|
||||
"model_id": opts.pop("model_id", None) or (getattr(self.client, "model_id", None)),
|
||||
"allow_multiple_tool_calls": opts.pop("allow_multiple_tool_calls", None),
|
||||
"conversation_id": conversation_id,
|
||||
"frequency_penalty": opts.pop("frequency_penalty", None),
|
||||
@@ -730,16 +730,16 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
async def __aenter__(self) -> Self:
|
||||
"""Enter the async context manager.
|
||||
|
||||
If any of the chat_client or local_mcp_tools are context managers,
|
||||
If any of the client or local_mcp_tools are context managers,
|
||||
they will be entered into the async exit stack to ensure proper cleanup.
|
||||
|
||||
Note:
|
||||
This list might be extended in the future.
|
||||
|
||||
Returns:
|
||||
The ChatAgent instance.
|
||||
The Agent instance.
|
||||
"""
|
||||
for context_manager in chain([self.chat_client], self.mcp_tools):
|
||||
for context_manager in chain([self.client], self.mcp_tools):
|
||||
if isinstance(context_manager, AbstractAsyncContextManager):
|
||||
await self._async_exit_stack.enter_async_context(context_manager)
|
||||
return self
|
||||
@@ -768,15 +768,15 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
should check if there is already an agent name defined, and if not
|
||||
set it to this value.
|
||||
"""
|
||||
if hasattr(self.chat_client, "_update_agent_name_and_description") and callable(
|
||||
self.chat_client._update_agent_name_and_description
|
||||
if hasattr(self.client, "_update_agent_name_and_description") and callable(
|
||||
self.client._update_agent_name_and_description
|
||||
): # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
self.chat_client._update_agent_name_and_description(self.name, self.description) # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
self.client._update_agent_name_and_description(self.name, self.description) # type: ignore[reportAttributeAccessIssue, attr-defined]
|
||||
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -792,7 +792,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -808,7 +808,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -823,7 +823,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -851,7 +851,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
thread: The thread to use for the agent.
|
||||
tools: The tools to use for this specific run (merged with default tools).
|
||||
options: A TypedDict containing chat options. When using a typed agent like
|
||||
``ChatAgent[OpenAIChatOptions]``, this enables IDE autocomplete for
|
||||
``Agent[OpenAIChatOptions]``, this enables IDE autocomplete for
|
||||
provider-specific options including temperature, max_tokens, model_id,
|
||||
tool_choice, and provider-specific options like reasoning_effort.
|
||||
kwargs: Additional keyword arguments for the agent.
|
||||
@@ -872,7 +872,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
options=options,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
response = await self.chat_client.get_response( # type: ignore[call-overload]
|
||||
response = await self.client.get_response( # type: ignore[call-overload]
|
||||
messages=ctx["thread_messages"],
|
||||
stream=False,
|
||||
options=ctx["chat_options"],
|
||||
@@ -944,7 +944,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
kwargs=kwargs,
|
||||
)
|
||||
ctx: _RunContext = ctx_holder["ctx"] # type: ignore[assignment] # Safe: we just assigned it
|
||||
return self.chat_client.get_response( # type: ignore[call-overload, no-any-return]
|
||||
return self.client.get_response( # type: ignore[call-overload, no-any-return]
|
||||
messages=ctx["thread_messages"],
|
||||
stream=True,
|
||||
options=ctx["chat_options"],
|
||||
@@ -979,7 +979,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
async def _prepare_run_context(
|
||||
self,
|
||||
*,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None,
|
||||
messages: str | Message | Sequence[str | Message] | None,
|
||||
thread: AgentThread | None,
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
@@ -1067,7 +1067,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
response: ChatResponse,
|
||||
agent_name: str,
|
||||
thread: AgentThread,
|
||||
input_messages: list[ChatMessage],
|
||||
input_messages: list[Message],
|
||||
kwargs: dict[str, Any],
|
||||
) -> None:
|
||||
"""Finalize response by updating thread and setting author names.
|
||||
@@ -1287,9 +1287,9 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
self,
|
||||
*,
|
||||
thread: AgentThread | None,
|
||||
input_messages: list[ChatMessage] | None = None,
|
||||
input_messages: list[Message] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> tuple[AgentThread, dict[str, Any], list[ChatMessage]]:
|
||||
) -> tuple[AgentThread, dict[str, Any], list[Message]]:
|
||||
"""Prepare the thread and messages for agent execution.
|
||||
|
||||
This method prepares the conversation thread, merges context provider data,
|
||||
@@ -1325,7 +1325,7 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
thread = thread or self.get_new_thread()
|
||||
if thread.service_thread_id and thread.context_provider:
|
||||
await thread.context_provider.thread_created(thread.service_thread_id)
|
||||
thread_messages: list[ChatMessage] = []
|
||||
thread_messages: list[Message] = []
|
||||
if thread.message_store:
|
||||
thread_messages.extend(await thread.message_store.list_messages() or [])
|
||||
context: Context | None = None
|
||||
@@ -1369,10 +1369,10 @@ class RawChatAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
return self.name or "UnnamedAgent"
|
||||
|
||||
|
||||
class ChatAgent(
|
||||
class Agent(
|
||||
AgentTelemetryLayer,
|
||||
AgentMiddlewareLayer,
|
||||
RawChatAgent[OptionsCoT],
|
||||
RawAgent[OptionsCoT],
|
||||
Generic[OptionsCoT],
|
||||
):
|
||||
"""A Chat Client Agent with middleware, telemetry, and full layer support.
|
||||
@@ -1381,12 +1381,12 @@ class ChatAgent(
|
||||
- Agent middleware support for request/response interception
|
||||
- OpenTelemetry-based telemetry for observability
|
||||
|
||||
For a minimal implementation without these features, use :class:`RawChatAgent`.
|
||||
For a minimal implementation without these features, use :class:`RawAgent`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_client: ChatClientProtocol[OptionsCoT],
|
||||
client: SupportsChatGetResponse[OptionsCoT],
|
||||
instructions: str | None = None,
|
||||
*,
|
||||
id: str | None = None,
|
||||
@@ -1403,9 +1403,9 @@ class ChatAgent(
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a ChatAgent instance."""
|
||||
"""Initialize a Agent instance."""
|
||||
super().__init__(
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
instructions=instructions,
|
||||
id=id,
|
||||
name=name,
|
||||
|
||||
@@ -36,9 +36,9 @@ from ._tools import (
|
||||
ToolProtocol,
|
||||
)
|
||||
from ._types import (
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Message,
|
||||
ResponseStream,
|
||||
prepare_messages,
|
||||
validate_chat_options,
|
||||
@@ -51,7 +51,7 @@ else:
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._agents import ChatAgent
|
||||
from ._agents import Agent
|
||||
from ._middleware import (
|
||||
MiddlewareTypes,
|
||||
)
|
||||
@@ -67,11 +67,11 @@ logger = get_logger()
|
||||
|
||||
__all__ = [
|
||||
"BaseChatClient",
|
||||
"ChatClientProtocol",
|
||||
"SupportsChatGetResponse",
|
||||
]
|
||||
|
||||
|
||||
# region ChatClientProtocol Protocol
|
||||
# region SupportsChatGetResponse Protocol
|
||||
|
||||
# Contravariant for the Protocol
|
||||
OptionsContraT = TypeVar(
|
||||
@@ -86,7 +86,7 @@ ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
class SupportsChatGetResponse(Protocol[OptionsContraT]):
|
||||
"""A protocol for a chat client that can generate responses.
|
||||
|
||||
This protocol defines the interface that all chat clients must implement,
|
||||
@@ -103,7 +103,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatClientProtocol, ChatResponse, ChatMessage
|
||||
from agent_framework import SupportsChatGetResponse, ChatResponse, Message
|
||||
|
||||
|
||||
# Any class implementing the required methods is compatible
|
||||
@@ -128,7 +128,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
|
||||
# Verify the instance satisfies the protocol
|
||||
client = CustomChatClient()
|
||||
assert isinstance(client, ChatClientProtocol)
|
||||
assert isinstance(client, SupportsChatGetResponse)
|
||||
"""
|
||||
|
||||
additional_properties: dict[str, Any]
|
||||
@@ -136,7 +136,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
@@ -146,7 +146,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsContraT | ChatOptions[None] | None = None,
|
||||
@@ -156,7 +156,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsContraT | ChatOptions[Any] | None = None,
|
||||
@@ -165,7 +165,7 @@ class ChatClientProtocol(Protocol[OptionsContraT]):
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsContraT | ChatOptions[Any] | None = None,
|
||||
@@ -226,7 +226,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import BaseChatClient, ChatResponse, ChatMessage
|
||||
from agent_framework import BaseChatClient, ChatResponse, Message
|
||||
from collections.abc import AsyncIterable
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
else:
|
||||
# Non-streaming implementation
|
||||
return ChatResponse(
|
||||
messages=[ChatMessage(role="assistant", text="Hello!")], response_id="custom-response"
|
||||
messages=[Message(role="assistant", text="Hello!")], response_id="custom-response"
|
||||
)
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
stream: bool,
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
@@ -365,7 +365,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
@@ -375,7 +375,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsCoT | ChatOptions[None] | None = None,
|
||||
@@ -385,7 +385,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -394,7 +394,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -448,10 +448,10 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a ChatAgent with this client.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a Agent with this client.
|
||||
|
||||
This is a convenience method that creates a ChatAgent instance with this
|
||||
This is a convenience method that creates a Agent instance with this
|
||||
chat client already configured.
|
||||
|
||||
Keyword Args:
|
||||
@@ -474,7 +474,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
kwargs: Any additional keyword arguments. Will be stored as ``additional_properties``.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance configured with this chat client.
|
||||
A Agent instance configured with this chat client.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
@@ -494,10 +494,10 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
# Run the agent
|
||||
response = await agent.run("Hello!")
|
||||
"""
|
||||
from ._agents import ChatAgent
|
||||
from ._agents import Agent
|
||||
|
||||
return ChatAgent(
|
||||
chat_client=self,
|
||||
return Agent(
|
||||
client=self,
|
||||
id=id,
|
||||
name=name,
|
||||
description=description,
|
||||
|
||||
@@ -32,8 +32,8 @@ from ._tools import (
|
||||
_build_pydantic_model_from_json_schema,
|
||||
)
|
||||
from ._types import (
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
)
|
||||
from .exceptions import ToolException, ToolExecutionException
|
||||
|
||||
@@ -43,7 +43,7 @@ else:
|
||||
from typing_extensions import Self # pragma: no cover
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._clients import SupportsChatGetResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -69,9 +69,9 @@ __all__ = [
|
||||
|
||||
def _parse_message_from_mcp(
|
||||
mcp_type: types.PromptMessage | types.SamplingMessage,
|
||||
) -> ChatMessage:
|
||||
) -> Message:
|
||||
"""Parse an MCP container type into an Agent Framework type."""
|
||||
return ChatMessage(
|
||||
return Message(
|
||||
role=mcp_type.role,
|
||||
contents=_parse_content_from_mcp(mcp_type.content),
|
||||
raw_representation=mcp_type,
|
||||
@@ -256,9 +256,9 @@ def _prepare_content_for_mcp(
|
||||
|
||||
|
||||
def _prepare_message_for_mcp(
|
||||
content: ChatMessage,
|
||||
content: Message,
|
||||
) -> list[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink]:
|
||||
"""Prepare a ChatMessage for MCP format."""
|
||||
"""Prepare a Message for MCP format."""
|
||||
messages: list[
|
||||
types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink
|
||||
] = []
|
||||
@@ -335,7 +335,7 @@ class MCPTool:
|
||||
parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True,
|
||||
session: ClientSession | None = None,
|
||||
request_timeout: int | None = None,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
client: SupportsChatGetResponse | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the MCP Tool base.
|
||||
@@ -356,7 +356,7 @@ class MCPTool:
|
||||
self._exit_stack = AsyncExitStack()
|
||||
self.session = session
|
||||
self.request_timeout = request_timeout
|
||||
self.chat_client = chat_client
|
||||
self.client = client
|
||||
self._functions: list[FunctionTool[Any, Any]] = []
|
||||
self.is_connected: bool = False
|
||||
self._tools_loaded: bool = False
|
||||
@@ -507,17 +507,17 @@ class MCPTool:
|
||||
Returns:
|
||||
Either a CreateMessageResult with the generated message or ErrorData if generation fails.
|
||||
"""
|
||||
if not self.chat_client:
|
||||
if not self.client:
|
||||
return types.ErrorData(
|
||||
code=types.INTERNAL_ERROR,
|
||||
message="No chat client available. Please set a chat client.",
|
||||
)
|
||||
logger.debug("Sampling callback called with params: %s", params)
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
for msg in params.messages:
|
||||
messages.append(_parse_message_from_mcp(msg))
|
||||
try:
|
||||
response = await self.chat_client.get_response(
|
||||
response = await self.client.get_response(
|
||||
messages,
|
||||
temperature=params.temperature,
|
||||
max_tokens=params.maxTokens,
|
||||
@@ -634,7 +634,7 @@ class MCPTool:
|
||||
|
||||
input_model = _get_input_model_from_mcp_prompt(prompt)
|
||||
approval_mode = self._determine_approval_mode(local_name)
|
||||
func: FunctionTool[BaseModel, list[ChatMessage] | Any | types.GetPromptResult] = FunctionTool(
|
||||
func: FunctionTool[BaseModel, list[Message] | Any | types.GetPromptResult] = FunctionTool(
|
||||
func=partial(self.get_prompt, prompt.name),
|
||||
name=local_name,
|
||||
description=prompt.description or "",
|
||||
@@ -801,7 +801,7 @@ class MCPTool:
|
||||
raise ToolExecutionException(f"Failed to call tool '{tool_name}'.", inner_exception=ex) from ex
|
||||
raise ToolExecutionException(f"Failed to call tool '{tool_name}' after retries.")
|
||||
|
||||
async def get_prompt(self, prompt_name: str, **kwargs: Any) -> list[ChatMessage] | Any | types.GetPromptResult:
|
||||
async def get_prompt(self, prompt_name: str, **kwargs: Any) -> list[Message] | Any | types.GetPromptResult:
|
||||
"""Call a prompt with the given arguments.
|
||||
|
||||
Args:
|
||||
@@ -909,7 +909,7 @@ class MCPStdioTool(MCPTool):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import MCPStdioTool, ChatAgent
|
||||
from agent_framework import MCPStdioTool, Agent
|
||||
|
||||
# Create an MCP stdio tool
|
||||
mcp_tool = MCPStdioTool(
|
||||
@@ -921,7 +921,7 @@ class MCPStdioTool(MCPTool):
|
||||
|
||||
# Use with a chat agent
|
||||
async with mcp_tool:
|
||||
agent = ChatAgent(chat_client=client, name="assistant", tools=mcp_tool)
|
||||
agent = Agent(client=client, name="assistant", tools=mcp_tool)
|
||||
response = await agent.run("List files in the directory")
|
||||
"""
|
||||
|
||||
@@ -942,7 +942,7 @@ class MCPStdioTool(MCPTool):
|
||||
args: list[str] | None = None,
|
||||
env: dict[str, str] | None = None,
|
||||
encoding: str | None = None,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
client: SupportsChatGetResponse | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -982,7 +982,7 @@ class MCPStdioTool(MCPTool):
|
||||
args: The arguments to pass to the command.
|
||||
env: The environment variables to set for the command.
|
||||
encoding: The encoding to use for the command output.
|
||||
chat_client: The chat client to use for sampling.
|
||||
client: The chat client to use for sampling.
|
||||
kwargs: Any extra arguments to pass to the stdio client.
|
||||
"""
|
||||
super().__init__(
|
||||
@@ -992,7 +992,7 @@ class MCPStdioTool(MCPTool):
|
||||
allowed_tools=allowed_tools,
|
||||
additional_properties=additional_properties,
|
||||
session=session,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
load_tools=load_tools,
|
||||
parse_tool_results=parse_tool_results,
|
||||
load_prompts=load_prompts,
|
||||
@@ -1031,7 +1031,7 @@ class MCPStreamableHTTPTool(MCPTool):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import MCPStreamableHTTPTool, ChatAgent
|
||||
from agent_framework import MCPStreamableHTTPTool, Agent
|
||||
|
||||
# Create an MCP HTTP tool
|
||||
mcp_tool = MCPStreamableHTTPTool(
|
||||
@@ -1042,7 +1042,7 @@ class MCPStreamableHTTPTool(MCPTool):
|
||||
|
||||
# Use with a chat agent
|
||||
async with mcp_tool:
|
||||
agent = ChatAgent(chat_client=client, name="assistant", tools=mcp_tool)
|
||||
agent = Agent(client=client, name="assistant", tools=mcp_tool)
|
||||
response = await agent.run("Fetch data from the API")
|
||||
"""
|
||||
|
||||
@@ -1061,7 +1061,7 @@ class MCPStreamableHTTPTool(MCPTool):
|
||||
approval_mode: (Literal["always_require", "never_require"] | HostedMCPSpecificApproval | None) = None,
|
||||
allowed_tools: Collection[str] | None = None,
|
||||
terminate_on_close: bool | None = None,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
client: SupportsChatGetResponse | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
http_client: httpx.AsyncClient | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -1101,7 +1101,7 @@ class MCPStreamableHTTPTool(MCPTool):
|
||||
allowed_tools: A list of tools that are allowed to use this tool.
|
||||
additional_properties: Additional properties.
|
||||
terminate_on_close: Close the transport when the MCP client is terminated.
|
||||
chat_client: The chat client to use for sampling.
|
||||
client: The chat client to use for sampling.
|
||||
http_client: Optional httpx.AsyncClient to use. If not provided, the
|
||||
``streamable_http_client`` API will create and manage a default client.
|
||||
To configure headers, timeouts, or other HTTP client settings, create
|
||||
@@ -1115,7 +1115,7 @@ class MCPStreamableHTTPTool(MCPTool):
|
||||
allowed_tools=allowed_tools,
|
||||
additional_properties=additional_properties,
|
||||
session=session,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
load_tools=load_tools,
|
||||
parse_tool_results=parse_tool_results,
|
||||
load_prompts=load_prompts,
|
||||
@@ -1148,7 +1148,7 @@ class MCPWebsocketTool(MCPTool):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import MCPWebsocketTool, ChatAgent
|
||||
from agent_framework import MCPWebsocketTool, Agent
|
||||
|
||||
# Create an MCP WebSocket tool
|
||||
mcp_tool = MCPWebsocketTool(
|
||||
@@ -1157,7 +1157,7 @@ class MCPWebsocketTool(MCPTool):
|
||||
|
||||
# Use with a chat agent
|
||||
async with mcp_tool:
|
||||
agent = ChatAgent(chat_client=client, name="assistant", tools=mcp_tool)
|
||||
agent = Agent(client=client, name="assistant", tools=mcp_tool)
|
||||
response = await agent.run("Connect to the real-time service")
|
||||
"""
|
||||
|
||||
@@ -1175,7 +1175,7 @@ class MCPWebsocketTool(MCPTool):
|
||||
description: str | None = None,
|
||||
approval_mode: (Literal["always_require", "never_require"] | HostedMCPSpecificApproval | None) = None,
|
||||
allowed_tools: Collection[str] | None = None,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
client: SupportsChatGetResponse | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -1213,7 +1213,7 @@ class MCPWebsocketTool(MCPTool):
|
||||
A tool should not be listed in both, if so, it will require approval.
|
||||
allowed_tools: A list of tools that are allowed to use this tool.
|
||||
additional_properties: Additional properties.
|
||||
chat_client: The chat client to use for sampling.
|
||||
client: The chat client to use for sampling.
|
||||
kwargs: Any extra arguments to pass to the WebSocket client.
|
||||
"""
|
||||
super().__init__(
|
||||
@@ -1223,7 +1223,7 @@ class MCPWebsocketTool(MCPTool):
|
||||
allowed_tools=allowed_tools,
|
||||
additional_properties=additional_properties,
|
||||
session=session,
|
||||
chat_client=chat_client,
|
||||
client=client,
|
||||
load_tools=load_tools,
|
||||
parse_tool_results=parse_tool_results,
|
||||
load_prompts=load_prompts,
|
||||
|
||||
@@ -8,7 +8,7 @@ from collections.abc import MutableSequence, Sequence
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
from ._types import ChatMessage
|
||||
from ._types import Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._tools import ToolProtocol
|
||||
@@ -34,12 +34,12 @@ class Context:
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import Context, ChatMessage
|
||||
from agent_framework import Context, Message
|
||||
|
||||
# Create context with instructions
|
||||
context = Context(
|
||||
instructions="Use a professional tone when responding.",
|
||||
messages=[ChatMessage(content="Previous context", role="user")],
|
||||
messages=[Message(content="Previous context", role="user")],
|
||||
tools=[my_tool],
|
||||
)
|
||||
|
||||
@@ -51,7 +51,7 @@ class Context:
|
||||
def __init__(
|
||||
self,
|
||||
instructions: str | None = None,
|
||||
messages: Sequence[ChatMessage] | None = None,
|
||||
messages: Sequence[Message] | None = None,
|
||||
tools: Sequence[ToolProtocol] | None = None,
|
||||
):
|
||||
"""Create a new Context object.
|
||||
@@ -62,7 +62,7 @@ class Context:
|
||||
tools: The list of tools to provide to this run.
|
||||
"""
|
||||
self.instructions = instructions
|
||||
self.messages: Sequence[ChatMessage] = messages or []
|
||||
self.messages: Sequence[Message] = messages or []
|
||||
self.tools: Sequence[ToolProtocol] = tools or []
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class ContextProvider(ABC):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ContextProvider, Context, ChatMessage
|
||||
from agent_framework import ContextProvider, Context, Message
|
||||
|
||||
|
||||
class CustomContextProvider(ContextProvider):
|
||||
@@ -96,7 +96,7 @@ class ContextProvider(ABC):
|
||||
|
||||
# Use with a chat agent
|
||||
async with CustomContextProvider() as provider:
|
||||
agent = ChatAgent(chat_client=client, name="assistant", context_provider=provider)
|
||||
agent = Agent(client=client, name="assistant", context_provider=provider)
|
||||
"""
|
||||
|
||||
# Default prompt to be used by all context providers when assembling memories/instructions
|
||||
@@ -116,8 +116,8 @@ class ContextProvider(ABC):
|
||||
|
||||
async def invoked(
|
||||
self,
|
||||
request_messages: ChatMessage | Sequence[ChatMessage],
|
||||
response_messages: ChatMessage | Sequence[ChatMessage] | None = None,
|
||||
request_messages: Message | Sequence[Message],
|
||||
response_messages: Message | Sequence[Message] | None = None,
|
||||
invoke_exception: Exception | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -136,7 +136,7 @@ class ContextProvider(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
|
||||
async def invoking(self, messages: Message | MutableSequence[Message], **kwargs: Any) -> Context:
|
||||
"""Called just before the model/agent is invoked.
|
||||
|
||||
Implementers can load any additional context required at this time,
|
||||
|
||||
@@ -10,13 +10,13 @@ from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, Sequenc
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, overload
|
||||
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._clients import SupportsChatGetResponse
|
||||
from ._types import (
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Message,
|
||||
ResponseStream,
|
||||
prepare_messages,
|
||||
)
|
||||
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._agents import SupportsAgentRun
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._clients import SupportsChatGetResponse
|
||||
from ._threads import AgentThread
|
||||
from ._tools import FunctionTool
|
||||
from ._types import ChatOptions, ChatResponse, ChatResponseUpdate
|
||||
@@ -155,7 +155,7 @@ class AgentContext:
|
||||
self,
|
||||
*,
|
||||
agent: SupportsAgentRun,
|
||||
messages: list[ChatMessage],
|
||||
messages: list[Message],
|
||||
thread: AgentThread | None = None,
|
||||
options: Mapping[str, Any] | None = None,
|
||||
stream: bool = False,
|
||||
@@ -263,7 +263,7 @@ class ChatContext:
|
||||
about the chat request.
|
||||
|
||||
Attributes:
|
||||
chat_client: The chat client being invoked.
|
||||
client: The chat client being invoked.
|
||||
messages: The messages being sent to the chat client.
|
||||
options: The options for the chat request as a dict.
|
||||
stream: Whether this is a streaming invocation.
|
||||
@@ -302,8 +302,8 @@ class ChatContext:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_client: ChatClientProtocol,
|
||||
messages: Sequence[ChatMessage],
|
||||
client: SupportsChatGetResponse,
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any] | None,
|
||||
stream: bool = False,
|
||||
metadata: Mapping[str, Any] | None = None,
|
||||
@@ -319,7 +319,7 @@ class ChatContext:
|
||||
"""Initialize the ChatContext.
|
||||
|
||||
Args:
|
||||
chat_client: The chat client being invoked.
|
||||
client: The chat client being invoked.
|
||||
messages: The messages being sent to the chat client.
|
||||
options: The options for the chat request as a dict.
|
||||
stream: Whether this is a streaming invocation.
|
||||
@@ -330,7 +330,7 @@ class ChatContext:
|
||||
stream_result_hooks: Result hooks to apply to the finalized streaming response.
|
||||
stream_cleanup_hooks: Cleanup hooks to run after streaming completes.
|
||||
"""
|
||||
self.chat_client = chat_client
|
||||
self.client = client
|
||||
self.messages = messages
|
||||
self.options = options
|
||||
self.stream = stream
|
||||
@@ -356,7 +356,7 @@ class AgentMiddleware(ABC):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import AgentMiddleware, AgentContext, ChatAgent
|
||||
from agent_framework import AgentMiddleware, AgentContext, Agent
|
||||
|
||||
|
||||
class RetryMiddleware(AgentMiddleware):
|
||||
@@ -372,7 +372,7 @@ class AgentMiddleware(ABC):
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(chat_client=client, name="assistant", middleware=[RetryMiddleware()])
|
||||
agent = Agent(client=client, name="assistant", middleware=[RetryMiddleware()])
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@@ -415,7 +415,7 @@ class FunctionMiddleware(ABC):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import FunctionMiddleware, FunctionInvocationContext, ChatAgent
|
||||
from agent_framework import FunctionMiddleware, FunctionInvocationContext, Agent
|
||||
|
||||
|
||||
class CachingMiddleware(FunctionMiddleware):
|
||||
@@ -439,7 +439,7 @@ class FunctionMiddleware(ABC):
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(chat_client=client, name="assistant", middleware=[CachingMiddleware()])
|
||||
agent = Agent(client=client, name="assistant", middleware=[CachingMiddleware()])
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@@ -479,7 +479,7 @@ class ChatMiddleware(ABC):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMiddleware, ChatContext, ChatAgent
|
||||
from agent_framework import ChatMiddleware, ChatContext, Agent
|
||||
|
||||
|
||||
class SystemPromptMiddleware(ChatMiddleware):
|
||||
@@ -488,17 +488,17 @@ class ChatMiddleware(ABC):
|
||||
|
||||
async def process(self, context: ChatContext, call_next):
|
||||
# Add system prompt to messages
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
context.messages.insert(0, ChatMessage(role="system", text=self.system_prompt))
|
||||
context.messages.insert(0, Message(role="system", text=self.system_prompt))
|
||||
|
||||
# Continue execution
|
||||
await call_next(context)
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(
|
||||
chat_client=client,
|
||||
agent = Agent(
|
||||
client=client,
|
||||
name="assistant",
|
||||
middleware=[SystemPromptMiddleware("You are a helpful assistant.")],
|
||||
)
|
||||
@@ -572,7 +572,7 @@ def agent_middleware(func: AgentMiddlewareCallable) -> AgentMiddlewareCallable:
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import agent_middleware, AgentContext, ChatAgent
|
||||
from agent_framework import agent_middleware, AgentContext, Agent
|
||||
|
||||
|
||||
@agent_middleware
|
||||
@@ -583,7 +583,7 @@ def agent_middleware(func: AgentMiddlewareCallable) -> AgentMiddlewareCallable:
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(chat_client=client, name="assistant", middleware=[logging_middleware])
|
||||
agent = Agent(client=client, name="assistant", middleware=[logging_middleware])
|
||||
"""
|
||||
# Add marker attribute to identify this as agent middleware
|
||||
func._middleware_type: MiddlewareType = MiddlewareType.AGENT # type: ignore
|
||||
@@ -605,7 +605,7 @@ def function_middleware(func: FunctionMiddlewareCallable) -> FunctionMiddlewareC
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import function_middleware, FunctionInvocationContext, ChatAgent
|
||||
from agent_framework import function_middleware, FunctionInvocationContext, Agent
|
||||
|
||||
|
||||
@function_middleware
|
||||
@@ -616,7 +616,7 @@ def function_middleware(func: FunctionMiddlewareCallable) -> FunctionMiddlewareC
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(chat_client=client, name="assistant", middleware=[logging_middleware])
|
||||
agent = Agent(client=client, name="assistant", middleware=[logging_middleware])
|
||||
"""
|
||||
# Add marker attribute to identify this as function middleware
|
||||
func._middleware_type: MiddlewareType = MiddlewareType.FUNCTION # type: ignore
|
||||
@@ -638,7 +638,7 @@ def chat_middleware(func: ChatMiddlewareCallable) -> ChatMiddlewareCallable:
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import chat_middleware, ChatContext, ChatAgent
|
||||
from agent_framework import chat_middleware, ChatContext, Agent
|
||||
|
||||
|
||||
@chat_middleware
|
||||
@@ -649,7 +649,7 @@ def chat_middleware(func: ChatMiddlewareCallable) -> ChatMiddlewareCallable:
|
||||
|
||||
|
||||
# Use with an agent
|
||||
agent = ChatAgent(chat_client=client, name="assistant", middleware=[logging_middleware])
|
||||
agent = Agent(client=client, name="assistant", middleware=[logging_middleware])
|
||||
"""
|
||||
# Add marker attribute to identify this as chat middleware
|
||||
func._middleware_type: MiddlewareType = MiddlewareType.CHAT # type: ignore
|
||||
@@ -980,7 +980,7 @@ class ChatMiddlewareLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
@@ -990,7 +990,7 @@ class ChatMiddlewareLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsCoT | ChatOptions[None] | None = None,
|
||||
@@ -1000,7 +1000,7 @@ class ChatMiddlewareLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -1009,7 +1009,7 @@ class ChatMiddlewareLayer(Generic[OptionsCoT]):
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -1035,7 +1035,7 @@ class ChatMiddlewareLayer(Generic[OptionsCoT]):
|
||||
)
|
||||
|
||||
context = ChatContext(
|
||||
chat_client=self, # type: ignore[arg-type]
|
||||
client=self, # type: ignore[arg-type]
|
||||
messages=prepare_messages(messages),
|
||||
options=options,
|
||||
stream=stream,
|
||||
@@ -1090,14 +1090,14 @@ class AgentMiddlewareLayer:
|
||||
self.agent_middleware = middleware_list["agent"]
|
||||
# Pass middleware to super so BaseAgent can store it for dynamic rebuild
|
||||
super().__init__(*args, middleware=middleware, **kwargs) # type: ignore[call-arg]
|
||||
# Note: We intentionally don't extend chat_client's middleware lists here.
|
||||
# Note: We intentionally don't extend client's middleware lists here.
|
||||
# Chat and function middleware is passed to the chat client at runtime via kwargs
|
||||
# in AgentMiddlewareLayer.run(), where it's properly combined with run-level middleware.
|
||||
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1109,7 +1109,7 @@ class AgentMiddlewareLayer:
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1121,7 +1121,7 @@ class AgentMiddlewareLayer:
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1132,7 +1132,7 @@ class AgentMiddlewareLayer:
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
|
||||
@@ -31,16 +31,16 @@ class SerializationProtocol(Protocol):
|
||||
ensuring consistent behavior across the framework.
|
||||
|
||||
Examples:
|
||||
The framework's ``ChatMessage`` class demonstrates the protocol in action:
|
||||
The framework's ``Message`` class demonstrates the protocol in action:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
from agent_framework._serialization import SerializationProtocol
|
||||
|
||||
|
||||
# ChatMessage implements SerializationProtocol via SerializationMixin
|
||||
user_msg = ChatMessage(role="user", text="What's the weather like today?")
|
||||
# Message implements SerializationProtocol via SerializationMixin
|
||||
user_msg = Message(role="user", text="What's the weather like today?")
|
||||
|
||||
# Serialize to dictionary - automatic type identification and nested serialization
|
||||
msg_dict = user_msg.to_dict()
|
||||
@@ -52,8 +52,8 @@ class SerializationProtocol(Protocol):
|
||||
# "additional_properties": {}
|
||||
# }
|
||||
|
||||
# Deserialize back to ChatMessage instance - automatic type reconstruction
|
||||
restored_msg = ChatMessage.from_dict(msg_dict)
|
||||
# Deserialize back to Message instance - automatic type reconstruction
|
||||
restored_msg = Message.from_dict(msg_dict)
|
||||
print(restored_msg.text) # "What's the weather like today?"
|
||||
print(restored_msg.role) # "user"
|
||||
|
||||
@@ -170,15 +170,15 @@ class SerializationMixin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
from agent_framework._threads import AgentThreadState, ChatMessageStoreState
|
||||
|
||||
|
||||
# ChatMessageStoreState handles nested ChatMessage serialization
|
||||
# ChatMessageStoreState handles nested Message serialization
|
||||
store_state = ChatMessageStoreState(
|
||||
messages=[
|
||||
ChatMessage(role="user", text="Hello agent"),
|
||||
ChatMessage(role="assistant", text="Hi! How can I help?"),
|
||||
Message(role="user", text="Hello agent"),
|
||||
Message(role="assistant", text="Hi! How can I help?"),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -443,7 +443,7 @@ class SerializationMixin:
|
||||
dependencies = {"open_ai_chat_client": {"client": openai_client}}
|
||||
|
||||
# The chat client is reconstructed with the OpenAI client injected
|
||||
chat_client = OpenAIChatClient.from_dict(client_data, dependencies=dependencies)
|
||||
client = OpenAIChatClient.from_dict(client_data, dependencies=dependencies)
|
||||
# Now ready to make API calls with the injected client
|
||||
|
||||
**Function Injection for Tools** - FunctionTool runtime dependency:
|
||||
|
||||
@@ -19,7 +19,7 @@ from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ._tools import ToolProtocol
|
||||
from ._types import AgentResponse, ChatMessage
|
||||
from ._types import AgentResponse, Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._agents import SupportsAgentRun
|
||||
@@ -80,7 +80,7 @@ def _deserialize_state(state: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
# Register known types
|
||||
_register_state_type(ChatMessage)
|
||||
_register_state_type(Message)
|
||||
|
||||
|
||||
class SessionContext:
|
||||
@@ -107,8 +107,8 @@ class SessionContext:
|
||||
*,
|
||||
session_id: str | None = None,
|
||||
service_session_id: str | None = None,
|
||||
input_messages: list[ChatMessage],
|
||||
context_messages: dict[str, list[ChatMessage]] | None = None,
|
||||
input_messages: list[Message],
|
||||
context_messages: dict[str, list[Message]] | None = None,
|
||||
instructions: list[str] | None = None,
|
||||
tools: list[ToolProtocol] | None = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
@@ -129,7 +129,7 @@ class SessionContext:
|
||||
self.session_id = session_id
|
||||
self.service_session_id = service_session_id
|
||||
self.input_messages = input_messages
|
||||
self.context_messages: dict[str, list[ChatMessage]] = context_messages or {}
|
||||
self.context_messages: dict[str, list[Message]] = context_messages or {}
|
||||
self.instructions: list[str] = instructions or []
|
||||
self.tools: list[ToolProtocol] = tools or []
|
||||
self._response: AgentResponse | None = None
|
||||
@@ -141,7 +141,7 @@ class SessionContext:
|
||||
"""The agent's response. Set by the framework after invocation, read-only for providers."""
|
||||
return self._response
|
||||
|
||||
def extend_messages(self, source: str | object, messages: Sequence[ChatMessage]) -> None:
|
||||
def extend_messages(self, source: str | object, messages: Sequence[Message]) -> None:
|
||||
"""Add context messages from a specific source.
|
||||
|
||||
Messages are copied before attribution is added, so the caller's
|
||||
@@ -164,7 +164,7 @@ class SessionContext:
|
||||
source_id = source.source_id # type: ignore[attr-defined]
|
||||
attribution = {"source_id": source_id, "source_type": type(source).__name__}
|
||||
|
||||
copied: list[ChatMessage] = []
|
||||
copied: list[Message] = []
|
||||
for message in messages:
|
||||
msg_copy = copy.copy(message)
|
||||
msg_copy.additional_properties = dict(message.additional_properties)
|
||||
@@ -206,7 +206,7 @@ class SessionContext:
|
||||
exclude_sources: set[str] | None = None,
|
||||
include_input: bool = False,
|
||||
include_response: bool = False,
|
||||
) -> list[ChatMessage]:
|
||||
) -> list[Message]:
|
||||
"""Get context messages, optionally filtered and including input/response.
|
||||
|
||||
Returns messages in provider execution order (dict insertion order),
|
||||
@@ -221,7 +221,7 @@ class SessionContext:
|
||||
Returns:
|
||||
Flattened list of messages in conversation order.
|
||||
"""
|
||||
result: list[ChatMessage] = []
|
||||
result: list[Message] = []
|
||||
for source_id, messages in self.context_messages.items():
|
||||
if sources is not None and source_id not in sources:
|
||||
continue
|
||||
@@ -353,7 +353,7 @@ class BaseHistoryProvider(BaseContextProvider):
|
||||
self.store_outputs = store_outputs
|
||||
|
||||
@abstractmethod
|
||||
async def get_messages(self, session_id: str | None, **kwargs: Any) -> list[ChatMessage]:
|
||||
async def get_messages(self, session_id: str | None, **kwargs: Any) -> list[Message]:
|
||||
"""Retrieve stored messages for this session.
|
||||
|
||||
Args:
|
||||
@@ -366,7 +366,7 @@ class BaseHistoryProvider(BaseContextProvider):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def save_messages(self, session_id: str | None, messages: Sequence[ChatMessage], **kwargs: Any) -> None:
|
||||
async def save_messages(self, session_id: str | None, messages: Sequence[Message], **kwargs: Any) -> None:
|
||||
"""Persist messages for this session.
|
||||
|
||||
Args:
|
||||
@@ -376,7 +376,7 @@ class BaseHistoryProvider(BaseContextProvider):
|
||||
"""
|
||||
...
|
||||
|
||||
def _get_context_messages_to_store(self, context: SessionContext) -> list[ChatMessage]:
|
||||
def _get_context_messages_to_store(self, context: SessionContext) -> list[Message]:
|
||||
"""Get context messages that should be stored based on configuration."""
|
||||
if not self.store_context_messages:
|
||||
return []
|
||||
@@ -405,7 +405,7 @@ class BaseHistoryProvider(BaseContextProvider):
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Store messages based on configuration."""
|
||||
messages_to_store: list[ChatMessage] = []
|
||||
messages_to_store: list[Message] = []
|
||||
messages_to_store.extend(self._get_context_messages_to_store(context))
|
||||
if self.store_inputs:
|
||||
messages_to_store.extend(context.input_messages)
|
||||
@@ -487,7 +487,7 @@ class InMemoryHistoryProvider(BaseHistoryProvider):
|
||||
"""Built-in history provider that stores messages in session.state.
|
||||
|
||||
Messages are stored in ``state[source_id]["messages"]`` as a list of
|
||||
``ChatMessage`` objects. Serialization to/from dicts is handled by
|
||||
``Message`` objects. Serialization to/from dicts is handled by
|
||||
``AgentSession.to_dict()``/``from_dict()`` using ``SerializationProtocol``.
|
||||
|
||||
This provider holds no instance state — all data lives in the session's
|
||||
@@ -499,7 +499,7 @@ class InMemoryHistoryProvider(BaseHistoryProvider):
|
||||
|
||||
async def get_messages(
|
||||
self, session_id: str | None, *, state: dict[str, Any] | None = None, **kwargs: Any
|
||||
) -> list[ChatMessage]:
|
||||
) -> list[Message]:
|
||||
"""Retrieve messages from session state."""
|
||||
if state is None:
|
||||
return []
|
||||
@@ -509,7 +509,7 @@ class InMemoryHistoryProvider(BaseHistoryProvider):
|
||||
async def save_messages(
|
||||
self,
|
||||
session_id: str | None,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
*,
|
||||
state: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any, Protocol, TypeVar
|
||||
|
||||
from ._memory import ContextProvider
|
||||
from ._serialization import SerializationMixin
|
||||
from ._types import ChatMessage
|
||||
from ._types import Message
|
||||
from .exceptions import AgentThreadException
|
||||
|
||||
__all__ = ["AgentThread", "ChatMessageStore", "ChatMessageStoreProtocol"]
|
||||
@@ -22,17 +22,17 @@ class ChatMessageStoreProtocol(Protocol):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
|
||||
class MyMessageStore:
|
||||
def __init__(self):
|
||||
self._messages = []
|
||||
|
||||
async def list_messages(self) -> list[ChatMessage]:
|
||||
async def list_messages(self) -> list[Message]:
|
||||
return self._messages
|
||||
|
||||
async def add_messages(self, messages: Sequence[ChatMessage]) -> None:
|
||||
async def add_messages(self, messages: Sequence[Message]) -> None:
|
||||
self._messages.extend(messages)
|
||||
|
||||
@classmethod
|
||||
@@ -52,7 +52,7 @@ class ChatMessageStoreProtocol(Protocol):
|
||||
store = MyMessageStore()
|
||||
"""
|
||||
|
||||
async def list_messages(self) -> list[ChatMessage]:
|
||||
async def list_messages(self) -> list[Message]:
|
||||
"""Gets all the messages from the store that should be used for the next agent invocation.
|
||||
|
||||
Messages are returned in ascending chronological order, with the oldest message first.
|
||||
@@ -65,11 +65,11 @@ class ChatMessageStoreProtocol(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
async def add_messages(self, messages: Sequence[ChatMessage]) -> None:
|
||||
async def add_messages(self, messages: Sequence[Message]) -> None:
|
||||
"""Adds messages to the store.
|
||||
|
||||
Args:
|
||||
messages: The sequence of ChatMessage objects to add to the store.
|
||||
messages: The sequence of Message objects to add to the store.
|
||||
"""
|
||||
...
|
||||
|
||||
@@ -128,7 +128,7 @@ class ChatMessageStoreState(SerializationMixin):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
messages: Sequence[ChatMessage] | Sequence[MutableMapping[str, Any]] | None = None,
|
||||
messages: Sequence[Message] | Sequence[MutableMapping[str, Any]] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Create the store state.
|
||||
@@ -141,16 +141,16 @@ class ChatMessageStoreState(SerializationMixin):
|
||||
|
||||
"""
|
||||
if not messages:
|
||||
self.messages: list[ChatMessage] = []
|
||||
self.messages: list[Message] = []
|
||||
return
|
||||
if not isinstance(messages, list):
|
||||
raise TypeError("Messages should be a list")
|
||||
new_messages: list[ChatMessage] = []
|
||||
new_messages: list[Message] = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, ChatMessage):
|
||||
if isinstance(msg, Message):
|
||||
new_messages.append(msg)
|
||||
else:
|
||||
new_messages.append(ChatMessage.from_dict(msg))
|
||||
new_messages.append(Message.from_dict(msg))
|
||||
self.messages = new_messages
|
||||
|
||||
|
||||
@@ -198,13 +198,13 @@ class ChatMessageStore:
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessageStore, ChatMessage
|
||||
from agent_framework import ChatMessageStore, Message
|
||||
|
||||
# Create an empty store
|
||||
store = ChatMessageStore()
|
||||
|
||||
# Add messages
|
||||
message = ChatMessage(role="user", text="Hello")
|
||||
message = Message(role="user", text="Hello")
|
||||
await store.add_messages([message])
|
||||
|
||||
# Retrieve messages
|
||||
@@ -217,7 +217,7 @@ class ChatMessageStore:
|
||||
restored_store = await ChatMessageStore.deserialize(state)
|
||||
"""
|
||||
|
||||
def __init__(self, messages: Sequence[ChatMessage] | None = None):
|
||||
def __init__(self, messages: Sequence[Message] | None = None):
|
||||
"""Create a ChatMessageStore for use in a thread.
|
||||
|
||||
Args:
|
||||
@@ -225,19 +225,19 @@ class ChatMessageStore:
|
||||
"""
|
||||
self.messages = list(messages) if messages else []
|
||||
|
||||
async def add_messages(self, messages: Sequence[ChatMessage]) -> None:
|
||||
async def add_messages(self, messages: Sequence[Message]) -> None:
|
||||
"""Add messages to the store.
|
||||
|
||||
Args:
|
||||
messages: Sequence of ChatMessage objects to add to the store.
|
||||
messages: Sequence of Message objects to add to the store.
|
||||
"""
|
||||
self.messages.extend(messages)
|
||||
|
||||
async def list_messages(self) -> list[ChatMessage]:
|
||||
async def list_messages(self) -> list[Message]:
|
||||
"""Get all messages from the store in chronological order.
|
||||
|
||||
Returns:
|
||||
List of ChatMessage objects, ordered from oldest to newest.
|
||||
List of Message objects, ordered from oldest to newest.
|
||||
"""
|
||||
return self.messages
|
||||
|
||||
@@ -302,21 +302,21 @@ class AgentThread:
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatAgent, ChatMessageStore
|
||||
from agent_framework import Agent, ChatMessageStore
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
client = OpenAIChatClient(model="gpt-4o")
|
||||
|
||||
# Create agent with service-managed threads using a service_thread_id
|
||||
service_agent = ChatAgent(name="assistant", client=client)
|
||||
service_agent = Agent(name="assistant", client=client)
|
||||
service_thread = await service_agent.get_new_thread(service_thread_id="thread_abc123")
|
||||
|
||||
# Create agent with service-managed threads using conversation_id
|
||||
conversation_agent = ChatAgent(name="assistant", client=client, conversation_id="thread_abc123")
|
||||
conversation_agent = Agent(name="assistant", client=client, conversation_id="thread_abc123")
|
||||
conversation_thread = await conversation_agent.get_new_thread()
|
||||
|
||||
# Create agent with custom message store factory
|
||||
local_agent = ChatAgent(name="assistant", client=client, chat_message_store_factory=ChatMessageStore)
|
||||
local_agent = Agent(name="assistant", client=client, chat_message_store_factory=ChatMessageStore)
|
||||
local_thread = await local_agent.get_new_thread()
|
||||
|
||||
# Serialize and restore thread state
|
||||
@@ -401,11 +401,11 @@ class AgentThread:
|
||||
|
||||
self._message_store = message_store
|
||||
|
||||
async def on_new_messages(self, new_messages: ChatMessage | Sequence[ChatMessage]) -> None:
|
||||
async def on_new_messages(self, new_messages: Message | Sequence[Message]) -> None:
|
||||
"""Invoked when a new message has been contributed to the chat by any participant.
|
||||
|
||||
Args:
|
||||
new_messages: The new ChatMessage or sequence of ChatMessage objects to add to the thread.
|
||||
new_messages: The new Message or sequence of Message objects to add to the thread.
|
||||
"""
|
||||
if self._service_thread_id is not None:
|
||||
# If the thread messages are stored in the service there is nothing to do here,
|
||||
@@ -416,7 +416,7 @@ class AgentThread:
|
||||
# create a default in memory store.
|
||||
self._message_store = ChatMessageStore()
|
||||
# If a store has been provided, we need to add the messages to the store.
|
||||
if isinstance(new_messages, ChatMessage):
|
||||
if isinstance(new_messages, Message):
|
||||
new_messages = [new_messages]
|
||||
await self._message_store.add_messages(new_messages)
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ else:
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._clients import SupportsChatGetResponse
|
||||
from ._middleware import FunctionMiddlewarePipeline, FunctionMiddlewareTypes
|
||||
from ._types import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
Message,
|
||||
ResponseStream,
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ __all__ = [
|
||||
logger = get_logger()
|
||||
DEFAULT_MAX_ITERATIONS: Final[int] = 40
|
||||
DEFAULT_MAX_CONSECUTIVE_ERRORS_PER_REQUEST: Final[int] = 3
|
||||
ChatClientT = TypeVar("ChatClientT", bound="ChatClientProtocol[Any]")
|
||||
ChatClientT = TypeVar("ChatClientT", bound="SupportsChatGetResponse[Any]")
|
||||
# region Helpers
|
||||
|
||||
ArgsT = TypeVar("ArgsT", bound=BaseModel, default=BaseModel)
|
||||
@@ -1857,14 +1857,14 @@ def _extract_tools(options: dict[str, Any] | None) -> Any:
|
||||
|
||||
|
||||
def _collect_approval_responses(
|
||||
messages: list[ChatMessage],
|
||||
messages: list[Message],
|
||||
) -> dict[str, Content]:
|
||||
"""Collect approval responses (both approved and rejected) from messages."""
|
||||
from ._types import ChatMessage
|
||||
from ._types import Message
|
||||
|
||||
fcc_todo: dict[str, Content] = {}
|
||||
for msg in messages:
|
||||
for content in msg.contents if isinstance(msg, ChatMessage) else []:
|
||||
for content in msg.contents if isinstance(msg, Message) else []:
|
||||
# Collect BOTH approved and rejected responses
|
||||
if content.type == "function_approval_response":
|
||||
fcc_todo[content.id] = content # type: ignore[attr-defined, index]
|
||||
@@ -1872,7 +1872,7 @@ def _collect_approval_responses(
|
||||
|
||||
|
||||
def _replace_approval_contents_with_results(
|
||||
messages: list[ChatMessage],
|
||||
messages: list[Message],
|
||||
fcc_todo: dict[str, Content],
|
||||
approved_function_results: list[Content],
|
||||
) -> None:
|
||||
@@ -1941,7 +1941,7 @@ def _extract_function_calls(response: ChatResponse) -> list[Content]:
|
||||
]
|
||||
|
||||
|
||||
def _prepend_fcc_messages(response: ChatResponse, fcc_messages: list[ChatMessage]) -> None:
|
||||
def _prepend_fcc_messages(response: ChatResponse, fcc_messages: list[Message]) -> None:
|
||||
if not fcc_messages:
|
||||
return
|
||||
for msg in reversed(fcc_messages):
|
||||
@@ -1961,7 +1961,7 @@ class FunctionRequestResult(TypedDict, total=False):
|
||||
|
||||
action: Literal["return", "continue", "stop"]
|
||||
errors_in_a_row: int
|
||||
result_message: ChatMessage | None
|
||||
result_message: Message | None
|
||||
update_role: Literal["assistant", "tool"] | None
|
||||
function_call_results: list[Content] | None
|
||||
|
||||
@@ -1970,12 +1970,12 @@ def _handle_function_call_results(
|
||||
*,
|
||||
response: ChatResponse,
|
||||
function_call_results: list[Content],
|
||||
fcc_messages: list[ChatMessage],
|
||||
fcc_messages: list[Message],
|
||||
errors_in_a_row: int,
|
||||
had_errors: bool,
|
||||
max_errors: int,
|
||||
) -> FunctionRequestResult:
|
||||
from ._types import ChatMessage
|
||||
from ._types import Message
|
||||
|
||||
if any(fccr.type in {"function_approval_request", "function_call"} for fccr in function_call_results):
|
||||
# Only add items that aren't already in the message (e.g. function_approval_request wrappers).
|
||||
@@ -1985,7 +1985,7 @@ def _handle_function_call_results(
|
||||
if response.messages and response.messages[0].role == "assistant":
|
||||
response.messages[0].contents.extend(new_items)
|
||||
else:
|
||||
response.messages.append(ChatMessage(role="assistant", contents=new_items))
|
||||
response.messages.append(Message(role="assistant", contents=new_items))
|
||||
return {
|
||||
"action": "return",
|
||||
"errors_in_a_row": errors_in_a_row,
|
||||
@@ -2012,7 +2012,7 @@ def _handle_function_call_results(
|
||||
else:
|
||||
errors_in_a_row = 0
|
||||
|
||||
result_message = ChatMessage(role="tool", contents=function_call_results)
|
||||
result_message = Message(role="tool", contents=function_call_results)
|
||||
response.messages.append(result_message)
|
||||
fcc_messages.extend(response.messages)
|
||||
return {
|
||||
@@ -2027,10 +2027,10 @@ def _handle_function_call_results(
|
||||
async def _process_function_requests(
|
||||
*,
|
||||
response: ChatResponse | None,
|
||||
prepped_messages: list[ChatMessage] | None,
|
||||
prepped_messages: list[Message] | None,
|
||||
tool_options: dict[str, Any] | None,
|
||||
attempt_idx: int,
|
||||
fcc_messages: list[ChatMessage] | None,
|
||||
fcc_messages: list[Message] | None,
|
||||
errors_in_a_row: int,
|
||||
max_errors: int,
|
||||
execute_function_calls: Callable[..., Awaitable[tuple[list[Content], bool, bool]]],
|
||||
@@ -2139,7 +2139,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
@@ -2149,7 +2149,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsCoT | ChatOptions[None] | None = None,
|
||||
@@ -2159,7 +2159,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -2168,7 +2168,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -2213,7 +2213,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
nonlocal filtered_kwargs
|
||||
errors_in_a_row: int = 0
|
||||
prepped_messages = prepare_messages(messages)
|
||||
fcc_messages: list[ChatMessage] = []
|
||||
fcc_messages: list[Message] = []
|
||||
response: ChatResponse | None = None
|
||||
|
||||
for attempt_idx in range(
|
||||
@@ -2307,7 +2307,7 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
nonlocal stream_result_hooks
|
||||
errors_in_a_row: int = 0
|
||||
prepped_messages = prepare_messages(messages)
|
||||
fcc_messages: list[ChatMessage] = []
|
||||
fcc_messages: list[Message] = []
|
||||
response: ChatResponse | None = None
|
||||
|
||||
for attempt_idx in range(
|
||||
|
||||
@@ -31,7 +31,6 @@ __all__ = [
|
||||
"AgentResponse",
|
||||
"AgentResponseUpdate",
|
||||
"Annotation",
|
||||
"ChatMessage",
|
||||
"ChatOptions",
|
||||
"ChatResponse",
|
||||
"ChatResponseUpdate",
|
||||
@@ -40,6 +39,7 @@ __all__ = [
|
||||
"FinalT",
|
||||
"FinishReason",
|
||||
"FinishReasonLiteral",
|
||||
"Message",
|
||||
"OuterFinalT",
|
||||
"OuterUpdateT",
|
||||
"ResponseStream",
|
||||
@@ -1420,14 +1420,14 @@ Known values: "system", "user", "assistant", "tool"
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
# Use string values directly
|
||||
user_msg = ChatMessage("user", ["Hello"])
|
||||
assistant_msg = ChatMessage("assistant", ["Hi there!"])
|
||||
user_msg = Message("user", ["Hello"])
|
||||
assistant_msg = Message("assistant", ["Hi there!"])
|
||||
|
||||
# Custom roles are also supported
|
||||
custom_msg = ChatMessage("custom", ["Custom role message"])
|
||||
custom_msg = Message("custom", ["Custom role message"])
|
||||
|
||||
# Compare roles directly as strings
|
||||
if user_msg.role == "user":
|
||||
@@ -1461,10 +1461,10 @@ Examples:
|
||||
"""
|
||||
|
||||
|
||||
# region ChatMessage
|
||||
# region Message
|
||||
|
||||
|
||||
class ChatMessage(SerializationMixin):
|
||||
class Message(SerializationMixin):
|
||||
"""Represents a chat message.
|
||||
|
||||
Attributes:
|
||||
@@ -1479,17 +1479,17 @@ class ChatMessage(SerializationMixin):
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework import Message, Content
|
||||
|
||||
# Create a message with text content
|
||||
user_msg = ChatMessage("user", ["What's the weather?"])
|
||||
user_msg = Message("user", ["What's the weather?"])
|
||||
print(user_msg.text) # "What's the weather?"
|
||||
|
||||
# Create a system message
|
||||
system_msg = ChatMessage("system", ["You are a helpful assistant."])
|
||||
system_msg = Message("system", ["You are a helpful assistant."])
|
||||
|
||||
# Create a message with mixed content types
|
||||
assistant_msg = ChatMessage(
|
||||
assistant_msg = Message(
|
||||
"assistant",
|
||||
["The weather is sunny!", Content.from_image_uri("https://...")],
|
||||
)
|
||||
@@ -1499,13 +1499,13 @@ class ChatMessage(SerializationMixin):
|
||||
msg_dict = user_msg.to_dict()
|
||||
# {'type': 'chat_message', 'role': 'user',
|
||||
# 'contents': [{'type': 'text', 'text': "What's the weather?"}], 'additional_properties': {}}
|
||||
restored_msg = ChatMessage.from_dict(msg_dict)
|
||||
restored_msg = Message.from_dict(msg_dict)
|
||||
print(restored_msg.text) # "What's the weather?"
|
||||
|
||||
# Serialization - to_json and from_json
|
||||
msg_json = user_msg.to_json()
|
||||
# '{"type": "chat_message", "role": "user", "contents": [...], ...}'
|
||||
restored_from_json = ChatMessage.from_json(msg_json)
|
||||
restored_from_json = Message.from_json(msg_json)
|
||||
print(restored_from_json.role) # "user"
|
||||
|
||||
"""
|
||||
@@ -1523,7 +1523,7 @@ class ChatMessage(SerializationMixin):
|
||||
additional_properties: MutableMapping[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
) -> None:
|
||||
"""Initialize ChatMessage.
|
||||
"""Initialize Message.
|
||||
|
||||
Args:
|
||||
role: The role of the author of the message (e.g., "user", "assistant", "system", "tool").
|
||||
@@ -1568,86 +1568,86 @@ class ChatMessage(SerializationMixin):
|
||||
|
||||
|
||||
def prepare_messages(
|
||||
messages: str | Content | ChatMessage | Sequence[str | Content | ChatMessage],
|
||||
messages: str | Content | Message | Sequence[str | Content | Message],
|
||||
system_instructions: str | Sequence[str] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
"""Convert various message input formats into a list of ChatMessage objects.
|
||||
) -> list[Message]:
|
||||
"""Convert various message input formats into a list of Message objects.
|
||||
|
||||
Args:
|
||||
messages: The input messages in various supported formats. Can be:
|
||||
- A string (converted to a user message)
|
||||
- A Content object (wrapped in a user ChatMessage)
|
||||
- A ChatMessage object
|
||||
- A Content object (wrapped in a user Message)
|
||||
- A Message object
|
||||
- A sequence containing any mix of the above
|
||||
system_instructions: The system instructions. They will be inserted to the start of the messages list.
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects.
|
||||
A list of Message objects.
|
||||
"""
|
||||
if system_instructions is not None:
|
||||
if isinstance(system_instructions, str):
|
||||
system_instructions = [system_instructions]
|
||||
system_instruction_messages = [ChatMessage("system", [instr]) for instr in system_instructions]
|
||||
system_instruction_messages = [Message("system", [instr]) for instr in system_instructions]
|
||||
else:
|
||||
system_instruction_messages = []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [*system_instruction_messages, ChatMessage("user", [messages])]
|
||||
return [*system_instruction_messages, Message("user", [messages])]
|
||||
if isinstance(messages, Content):
|
||||
return [*system_instruction_messages, ChatMessage("user", [messages])]
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [*system_instruction_messages, Message("user", [messages])]
|
||||
if isinstance(messages, Message):
|
||||
return [*system_instruction_messages, messages]
|
||||
|
||||
return_messages: list[ChatMessage] = system_instruction_messages
|
||||
return_messages: list[Message] = system_instruction_messages
|
||||
for msg in messages:
|
||||
if isinstance(msg, (str, Content)):
|
||||
msg = ChatMessage("user", [msg])
|
||||
msg = Message("user", [msg])
|
||||
return_messages.append(msg)
|
||||
return return_messages
|
||||
|
||||
|
||||
def normalize_messages(
|
||||
messages: str | Content | ChatMessage | Sequence[str | Content | ChatMessage] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
"""Normalize message inputs to a list of ChatMessage objects.
|
||||
messages: str | Content | Message | Sequence[str | Content | Message] | None = None,
|
||||
) -> list[Message]:
|
||||
"""Normalize message inputs to a list of Message objects.
|
||||
|
||||
Args:
|
||||
messages: The input messages in various supported formats. Can be:
|
||||
- None (returns empty list)
|
||||
- A string (converted to a user message)
|
||||
- A Content object (wrapped in a user ChatMessage)
|
||||
- A ChatMessage object
|
||||
- A Content object (wrapped in a user Message)
|
||||
- A Message object
|
||||
- A sequence containing any mix of the above
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects.
|
||||
A list of Message objects.
|
||||
"""
|
||||
if messages is None:
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage("user", [messages])]
|
||||
return [Message("user", [messages])]
|
||||
|
||||
if isinstance(messages, Content):
|
||||
return [ChatMessage("user", [messages])]
|
||||
return [Message("user", [messages])]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
if isinstance(messages, Message):
|
||||
return [messages]
|
||||
|
||||
result: list[ChatMessage] = []
|
||||
result: list[Message] = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, (str, Content)):
|
||||
result.append(ChatMessage("user", [msg]))
|
||||
result.append(Message("user", [msg]))
|
||||
else:
|
||||
result.append(msg)
|
||||
return result
|
||||
|
||||
|
||||
def prepend_instructions_to_messages(
|
||||
messages: list[ChatMessage],
|
||||
messages: list[Message],
|
||||
instructions: str | Sequence[str] | None,
|
||||
role: RoleLiteral | str = "system",
|
||||
) -> list[ChatMessage]:
|
||||
) -> list[Message]:
|
||||
"""Prepend instructions to a list of messages with a specified role.
|
||||
|
||||
This is a helper method for chat clients that need to add instructions
|
||||
@@ -1655,7 +1655,7 @@ def prepend_instructions_to_messages(
|
||||
instructions (e.g., OpenAI uses "system", some providers might use "user").
|
||||
|
||||
Args:
|
||||
messages: The existing list of ChatMessage objects.
|
||||
messages: The existing list of Message objects.
|
||||
instructions: The instructions to prepend. Can be a single string or a sequence of strings.
|
||||
role: The role to use for the instruction messages. Defaults to "system".
|
||||
|
||||
@@ -1665,9 +1665,9 @@ def prepend_instructions_to_messages(
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import prepend_instructions_to_messages, ChatMessage
|
||||
from agent_framework import prepend_instructions_to_messages, Message
|
||||
|
||||
messages = [ChatMessage("user", ["Hello"])]
|
||||
messages = [Message("user", ["Hello"])]
|
||||
instructions = "You are a helpful assistant"
|
||||
|
||||
# Prepend as system message (default)
|
||||
@@ -1682,7 +1682,7 @@ def prepend_instructions_to_messages(
|
||||
if isinstance(instructions, str):
|
||||
instructions = [instructions]
|
||||
|
||||
instruction_messages = [ChatMessage(role, [instr]) for instr in instructions]
|
||||
instruction_messages = [Message(role, [instr]) for instr in instructions]
|
||||
return [*instruction_messages, *messages]
|
||||
|
||||
|
||||
@@ -1704,7 +1704,7 @@ def _process_update(response: ChatResponse | AgentResponse, update: ChatResponse
|
||||
is_new_message = True
|
||||
|
||||
if is_new_message:
|
||||
message = ChatMessage("assistant", [])
|
||||
message = Message("assistant", [])
|
||||
response.messages.append(message)
|
||||
else:
|
||||
message = response.messages[-1]
|
||||
@@ -1847,17 +1847,17 @@ class ChatResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
raw_representation: The raw representation of the chat response from an underlying implementation.
|
||||
|
||||
Note:
|
||||
The `author_name` attribute is available on the `ChatMessage` objects inside `messages`,
|
||||
The `author_name` attribute is available on the `Message` objects inside `messages`,
|
||||
not on the `ChatResponse` itself. Use `response.messages[0].author_name` to access
|
||||
the author name of individual messages.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import ChatResponse, ChatMessage
|
||||
from agent_framework import ChatResponse, Message
|
||||
|
||||
# Create a response with messages
|
||||
msg = ChatMessage("assistant", ["The weather is sunny."])
|
||||
msg = Message("assistant", ["The weather is sunny."])
|
||||
response = ChatResponse(
|
||||
messages=[msg],
|
||||
finish_reason="stop",
|
||||
@@ -1887,7 +1887,7 @@ class ChatResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
messages: ChatMessage | Sequence[ChatMessage] | None = None,
|
||||
messages: Message | Sequence[Message] | None = None,
|
||||
response_id: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
model_id: str | None = None,
|
||||
@@ -1903,7 +1903,7 @@ class ChatResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
"""Initializes a ChatResponse with the provided parameters.
|
||||
|
||||
Keyword Args:
|
||||
messages: A single ChatMessage or sequence of ChatMessage objects to include in the response.
|
||||
messages: A single Message or sequence of Message objects to include in the response.
|
||||
response_id: Optional ID of the chat response.
|
||||
conversation_id: Optional identifier for the state of the conversation.
|
||||
model_id: Optional model ID used in the creation of the chat response.
|
||||
@@ -1918,17 +1918,17 @@ class ChatResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
raw_representation: Optional raw representation of the chat response from an underlying implementation.
|
||||
"""
|
||||
if messages is None:
|
||||
self.messages: list[ChatMessage] = []
|
||||
elif isinstance(messages, ChatMessage):
|
||||
self.messages: list[Message] = []
|
||||
elif isinstance(messages, Message):
|
||||
self.messages = [messages]
|
||||
else:
|
||||
# Handle both ChatMessage objects and dicts (for from_dict support)
|
||||
processed_messages: list[ChatMessage] = []
|
||||
# Handle both Message objects and dicts (for from_dict support)
|
||||
processed_messages: list[Message] = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, ChatMessage):
|
||||
if isinstance(msg, Message):
|
||||
processed_messages.append(msg)
|
||||
elif isinstance(msg, dict):
|
||||
processed_messages.append(ChatMessage.from_dict(msg))
|
||||
processed_messages.append(Message.from_dict(msg))
|
||||
else:
|
||||
processed_messages.append(msg)
|
||||
self.messages = processed_messages
|
||||
@@ -2057,7 +2057,7 @@ class ChatResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""Returns the concatenated text of all messages in the response."""
|
||||
return ("\n".join(message.text for message in self.messages if isinstance(message, ChatMessage))).strip()
|
||||
return ("\n".join(message.text for message in self.messages if isinstance(message, Message))).strip()
|
||||
|
||||
@property
|
||||
def value(self) -> ResponseModelT | None:
|
||||
@@ -2096,7 +2096,7 @@ class ChatResponseUpdate(SerializationMixin):
|
||||
author_name: The name of the author of the response update. This is primarily used in
|
||||
multi-agent scenarios to identify which agent or participant generated the response.
|
||||
When updates are combined into a `ChatResponse`, the `author_name` is propagated
|
||||
to the resulting `ChatMessage` objects.
|
||||
to the resulting `Message` objects.
|
||||
response_id: The ID of the response of which this update is a part.
|
||||
message_id: The ID of the message of which this update is a part.
|
||||
conversation_id: An identifier for the state of the conversation of which this update is a part.
|
||||
@@ -2217,17 +2217,17 @@ class AgentResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
messages in scenarios involving function calls, RAG retrievals, or complex logic.
|
||||
|
||||
Note:
|
||||
The `author_name` attribute is available on the `ChatMessage` objects inside `messages`,
|
||||
The `author_name` attribute is available on the `Message` objects inside `messages`,
|
||||
not on the `AgentResponse` itself. Use `response.messages[0].author_name` to access
|
||||
the author name of individual messages.
|
||||
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework import AgentResponse, ChatMessage
|
||||
from agent_framework import AgentResponse, Message
|
||||
|
||||
# Create agent response
|
||||
msg = ChatMessage("assistant", ["Task completed successfully."])
|
||||
msg = Message("assistant", ["Task completed successfully."])
|
||||
response = AgentResponse(messages=[msg], response_id="run_123")
|
||||
print(response.text) # "Task completed successfully."
|
||||
|
||||
@@ -2258,7 +2258,7 @@ class AgentResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
messages: ChatMessage | Sequence[ChatMessage] | None = None,
|
||||
messages: Message | Sequence[Message] | None = None,
|
||||
response_id: str | None = None,
|
||||
agent_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
@@ -2272,7 +2272,7 @@ class AgentResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
"""Initialize an AgentResponse.
|
||||
|
||||
Keyword Args:
|
||||
messages: A single ChatMessage or sequence of ChatMessage objects to include in the response.
|
||||
messages: A single Message or sequence of Message objects to include in the response.
|
||||
response_id: The ID of the chat response.
|
||||
agent_id: The identifier of the agent that produced this response. Useful in multi-agent
|
||||
scenarios to track which agent generated the response.
|
||||
@@ -2286,17 +2286,17 @@ class AgentResponse(SerializationMixin, Generic[ResponseModelT]):
|
||||
raw_representation: The raw representation of the chat response from an underlying implementation.
|
||||
"""
|
||||
if messages is None:
|
||||
self.messages: list[ChatMessage] = []
|
||||
elif isinstance(messages, ChatMessage):
|
||||
self.messages: list[Message] = []
|
||||
elif isinstance(messages, Message):
|
||||
self.messages = [messages]
|
||||
else:
|
||||
# Handle both ChatMessage objects and dicts (for from_dict support)
|
||||
processed_messages: list[ChatMessage] = []
|
||||
# Handle both Message objects and dicts (for from_dict support)
|
||||
processed_messages: list[Message] = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, ChatMessage):
|
||||
if isinstance(msg, Message):
|
||||
processed_messages.append(msg)
|
||||
elif isinstance(msg, dict):
|
||||
processed_messages.append(ChatMessage.from_dict(msg))
|
||||
processed_messages.append(Message.from_dict(msg))
|
||||
else:
|
||||
processed_messages.append(msg)
|
||||
self.messages = processed_messages
|
||||
@@ -2440,7 +2440,7 @@ class AgentResponseUpdate(SerializationMixin):
|
||||
role: The role of the author of the response update.
|
||||
author_name: The name of the author of the response update. In multi-agent scenarios,
|
||||
this identifies which agent generated this update. When updates are combined into
|
||||
an `AgentResponse`, the `author_name` is propagated to the resulting `ChatMessage` objects.
|
||||
an `AgentResponse`, the `author_name` is propagated to the resulting `Message` objects.
|
||||
agent_id: The identifier of the agent that produced this update. Useful in multi-agent
|
||||
scenarios to track which agent generated specific parts of the response.
|
||||
response_id: The ID of the response of which this update is a part.
|
||||
|
||||
@@ -52,8 +52,8 @@ from ._request_info_mixin import response_handler
|
||||
from ._runner import Runner
|
||||
from ._runner_context import (
|
||||
InProcRunnerContext,
|
||||
Message,
|
||||
RunnerContext,
|
||||
WorkflowMessage,
|
||||
)
|
||||
from ._validation import (
|
||||
EdgeDuplicationError,
|
||||
@@ -92,7 +92,6 @@ __all__ = [
|
||||
"GraphConnectivityError",
|
||||
"InMemoryCheckpointStorage",
|
||||
"InProcRunnerContext",
|
||||
"Message",
|
||||
"Runner",
|
||||
"RunnerContext",
|
||||
"SingleEdgeGroup",
|
||||
@@ -117,6 +116,7 @@ __all__ = [
|
||||
"WorkflowEventType",
|
||||
"WorkflowException",
|
||||
"WorkflowExecutor",
|
||||
"WorkflowMessage",
|
||||
"WorkflowRunResult",
|
||||
"WorkflowRunState",
|
||||
"WorkflowRunnerException",
|
||||
|
||||
@@ -16,8 +16,8 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Content,
|
||||
Message,
|
||||
UsageDetails,
|
||||
)
|
||||
|
||||
@@ -107,8 +107,8 @@ class WorkflowAgent(BaseAgent):
|
||||
except KeyError as exc: # Defensive: workflow lacks a configured entry point
|
||||
raise ValueError("Workflow's start executor is not defined.") from exc
|
||||
|
||||
if not any(is_type_compatible(list[ChatMessage], input_type) for input_type in start_executor.input_types):
|
||||
raise ValueError("Workflow's start executor cannot handle list[ChatMessage]")
|
||||
if not any(is_type_compatible(list[Message], input_type) for input_type in start_executor.input_types):
|
||||
raise ValueError("Workflow's start executor cannot handle list[Message]")
|
||||
|
||||
super().__init__(id=id, name=name, description=description, **kwargs)
|
||||
self._workflow: Workflow = workflow
|
||||
@@ -127,7 +127,7 @@ class WorkflowAgent(BaseAgent):
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -139,7 +139,7 @@ class WorkflowAgent(BaseAgent):
|
||||
@overload
|
||||
async def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -150,7 +150,7 @@ class WorkflowAgent(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,
|
||||
@@ -202,7 +202,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
async def _run_non_streaming(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
checkpoint_id: str | None = None,
|
||||
@@ -225,7 +225,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
async def _run_streaming(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | Message | list[str] | list[Message] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
checkpoint_id: str | None = None,
|
||||
@@ -257,7 +257,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
async def _run_impl(
|
||||
self,
|
||||
input_messages: list[ChatMessage],
|
||||
input_messages: list[Message],
|
||||
response_id: str,
|
||||
thread: AgentThread,
|
||||
checkpoint_id: str | None = None,
|
||||
@@ -289,7 +289,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
async def _run_stream_impl(
|
||||
self,
|
||||
input_messages: list[ChatMessage],
|
||||
input_messages: list[Message],
|
||||
response_id: str,
|
||||
thread: AgentThread,
|
||||
checkpoint_id: str | None = None,
|
||||
@@ -319,7 +319,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
async def _run_core(
|
||||
self,
|
||||
input_messages: list[ChatMessage],
|
||||
input_messages: list[Message],
|
||||
thread: AgentThread,
|
||||
checkpoint_id: str | None,
|
||||
checkpoint_storage: CheckpointStorage | None,
|
||||
@@ -393,8 +393,8 @@ class WorkflowAgent(BaseAgent):
|
||||
async def _build_conversation_messages(
|
||||
self,
|
||||
thread: AgentThread,
|
||||
input_messages: list[ChatMessage],
|
||||
) -> list[ChatMessage]:
|
||||
input_messages: list[Message],
|
||||
) -> list[Message]:
|
||||
"""Build the complete conversation by prepending thread history to input messages.
|
||||
|
||||
Args:
|
||||
@@ -402,9 +402,9 @@ class WorkflowAgent(BaseAgent):
|
||||
input_messages: The new input messages to append.
|
||||
|
||||
Returns:
|
||||
A list of ChatMessage objects representing the full conversation.
|
||||
A list of Message objects representing the full conversation.
|
||||
"""
|
||||
conversation_messages: list[ChatMessage] = []
|
||||
conversation_messages: list[Message] = []
|
||||
if thread.message_store:
|
||||
history = await thread.message_store.list_messages()
|
||||
if history:
|
||||
@@ -412,7 +412,7 @@ class WorkflowAgent(BaseAgent):
|
||||
conversation_messages.extend(input_messages)
|
||||
return conversation_messages
|
||||
|
||||
def _process_pending_requests(self, input_messages: list[ChatMessage]) -> dict[str, Any]:
|
||||
def _process_pending_requests(self, input_messages: list[Message]) -> dict[str, Any]:
|
||||
"""Process pending requests by extracting function responses and updating state.
|
||||
|
||||
Args:
|
||||
@@ -444,7 +444,7 @@ class WorkflowAgent(BaseAgent):
|
||||
output_events: list[WorkflowEvent[Any]],
|
||||
) -> AgentResponse:
|
||||
"""Convert a list of workflow output events to an AgentResponse."""
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
raw_representations: list[object] = []
|
||||
merged_usage: UsageDetails | None = None
|
||||
latest_created_at: str | None = None
|
||||
@@ -453,7 +453,7 @@ class WorkflowAgent(BaseAgent):
|
||||
if output_event.type == "request_info":
|
||||
function_call, approval_request = self._process_request_info_event(output_event)
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
contents=[function_call, approval_request],
|
||||
role="assistant",
|
||||
author_name=output_event.source_executor_id,
|
||||
@@ -484,11 +484,11 @@ class WorkflowAgent(BaseAgent):
|
||||
if data.created_at
|
||||
else latest_created_at
|
||||
)
|
||||
elif isinstance(data, ChatMessage):
|
||||
elif isinstance(data, Message):
|
||||
messages.append(data)
|
||||
raw_representations.append(data.raw_representation)
|
||||
elif is_instance_of(data, list[ChatMessage]):
|
||||
chat_messages = cast(list[ChatMessage], data)
|
||||
elif is_instance_of(data, list[Message]):
|
||||
chat_messages = cast(list[Message], data)
|
||||
messages.extend(chat_messages)
|
||||
raw_representations.append(data)
|
||||
else:
|
||||
@@ -497,7 +497,7 @@ class WorkflowAgent(BaseAgent):
|
||||
continue
|
||||
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
contents=contents,
|
||||
role="assistant",
|
||||
author_name=output_event.executor_id,
|
||||
@@ -591,7 +591,7 @@ class WorkflowAgent(BaseAgent):
|
||||
)
|
||||
)
|
||||
return updates
|
||||
if isinstance(data, ChatMessage):
|
||||
if isinstance(data, Message):
|
||||
return [
|
||||
AgentResponseUpdate(
|
||||
contents=list(data.contents),
|
||||
@@ -603,9 +603,9 @@ class WorkflowAgent(BaseAgent):
|
||||
raw_representation=data,
|
||||
)
|
||||
]
|
||||
if is_instance_of(data, list[ChatMessage]):
|
||||
# Convert each ChatMessage to an AgentResponseUpdate
|
||||
chat_messages = cast(list[ChatMessage], data)
|
||||
if is_instance_of(data, list[Message]):
|
||||
# Convert each Message to an AgentResponseUpdate
|
||||
chat_messages = cast(list[Message], data)
|
||||
updates = []
|
||||
for msg in chat_messages:
|
||||
updates.append(
|
||||
@@ -669,7 +669,7 @@ class WorkflowAgent(BaseAgent):
|
||||
# Ignore workflow-internal events
|
||||
return []
|
||||
|
||||
def _extract_function_responses(self, input_messages: list[ChatMessage]) -> dict[str, Any]:
|
||||
def _extract_function_responses(self, input_messages: list[Message]) -> dict[str, Any]:
|
||||
"""Extract function responses from input messages."""
|
||||
function_responses: dict[str, Any] = {}
|
||||
for message in input_messages:
|
||||
@@ -820,7 +820,7 @@ class WorkflowAgent(BaseAgent):
|
||||
)
|
||||
|
||||
# PHASE 2: CONVERT GROUPED UPDATES TO RESPONSES AND MERGE
|
||||
final_messages: list[ChatMessage] = []
|
||||
final_messages: list[Message] = []
|
||||
merged_usage: UsageDetails | None = None
|
||||
latest_created_at: str | None = None
|
||||
merged_additional_properties: dict[str, Any] | None = None
|
||||
|
||||
@@ -11,7 +11,7 @@ from agent_framework import Content
|
||||
|
||||
from .._agents import SupportsAgentRun
|
||||
from .._threads import AgentThread
|
||||
from .._types import AgentResponse, AgentResponseUpdate, ChatMessage
|
||||
from .._types import AgentResponse, AgentResponseUpdate, Message
|
||||
from ._agent_utils import resolve_agent_id
|
||||
from ._checkpoint_encoding import decode_checkpoint_value, encode_checkpoint_value
|
||||
from ._const import WORKFLOW_RUN_KWARGS_KEY
|
||||
@@ -40,7 +40,7 @@ class AgentExecutorRequest:
|
||||
If False, the messages will be saved to the executor's cache but not sent to the agent.
|
||||
"""
|
||||
|
||||
messages: list[ChatMessage]
|
||||
messages: list[Message]
|
||||
should_respond: bool = True
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class AgentExecutorResponse:
|
||||
|
||||
executor_id: str
|
||||
agent_response: AgentResponse
|
||||
full_conversation: list[ChatMessage] | None = None
|
||||
full_conversation: list[Message] | None = None
|
||||
|
||||
|
||||
class AgentExecutor(Executor):
|
||||
@@ -104,9 +104,9 @@ class AgentExecutor(Executor):
|
||||
self._pending_responses_to_agent: list[Content] = []
|
||||
|
||||
# AgentExecutor maintains an internal cache of messages in between runs
|
||||
self._cache: list[ChatMessage] = []
|
||||
self._cache: list[Message] = []
|
||||
# This tracks the full conversation after each run
|
||||
self._full_conversation: list[ChatMessage] = []
|
||||
self._full_conversation: list[Message] = []
|
||||
|
||||
@property
|
||||
def description(self) -> str | None:
|
||||
@@ -157,20 +157,20 @@ class AgentExecutor(Executor):
|
||||
@handler
|
||||
async def from_message(
|
||||
self,
|
||||
message: ChatMessage,
|
||||
message: Message,
|
||||
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse | AgentResponseUpdate],
|
||||
) -> None:
|
||||
"""Accept a single ChatMessage as input."""
|
||||
"""Accept a single Message as input."""
|
||||
self._cache = normalize_messages_input(message)
|
||||
await self._run_agent_and_emit(ctx)
|
||||
|
||||
@handler
|
||||
async def from_messages(
|
||||
self,
|
||||
messages: list[str | ChatMessage],
|
||||
messages: list[str | Message],
|
||||
ctx: WorkflowContext[AgentExecutorResponse, AgentResponse | AgentResponseUpdate],
|
||||
) -> None:
|
||||
"""Accept a list of chat inputs (strings or ChatMessage) as conversation context."""
|
||||
"""Accept a list of chat inputs (strings or Message) as conversation context."""
|
||||
self._cache = normalize_messages_input(messages)
|
||||
await self._run_agent_and_emit(ctx)
|
||||
|
||||
@@ -198,7 +198,7 @@ class AgentExecutor(Executor):
|
||||
# Use role="tool" for function_result responses (from declaration-only tools)
|
||||
# so the LLM receives proper tool results instead of orphaned tool_calls.
|
||||
role = "tool" if all(r.type == "function_result" for r in self._pending_responses_to_agent) else "user"
|
||||
self._cache = normalize_messages_input(ChatMessage(role=role, contents=self._pending_responses_to_agent))
|
||||
self._cache = normalize_messages_input(Message(role=role, contents=self._pending_responses_to_agent))
|
||||
self._pending_responses_to_agent.clear()
|
||||
await self._run_agent_and_emit(ctx)
|
||||
|
||||
@@ -216,8 +216,8 @@ class AgentExecutor(Executor):
|
||||
"""
|
||||
# Check if using AzureAIAgentClient with server-side thread and warn about checkpointing limitations
|
||||
if is_chat_agent(self._agent) and self._agent_thread.service_thread_id is not None:
|
||||
client_class_name = self._agent.chat_client.__class__.__name__
|
||||
client_module = self._agent.chat_client.__class__.__module__
|
||||
client_class_name = self._agent.client.__class__.__name__
|
||||
client_module = self._agent.client.__class__.__module__
|
||||
|
||||
if client_class_name == "AzureAIAgentClient" and "azure_ai" in client_module:
|
||||
logger.warning(
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
"""Helpers for managing chat conversation history.
|
||||
|
||||
These utilities operate on standard `list[ChatMessage]` collections and simple
|
||||
These utilities operate on standard `list[Message]` collections and simple
|
||||
dictionary snapshots so orchestrators can share logic without new mixins.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from .._types import ChatMessage
|
||||
from .._types import Message
|
||||
|
||||
|
||||
def latest_user_message(conversation: Sequence[ChatMessage]) -> ChatMessage:
|
||||
def latest_user_message(conversation: Sequence[Message]) -> Message:
|
||||
"""Return the most recent user-authored message from `conversation`."""
|
||||
for message in reversed(conversation):
|
||||
role_value = getattr(message.role, "value", message.role)
|
||||
@@ -20,7 +20,7 @@ def latest_user_message(conversation: Sequence[ChatMessage]) -> ChatMessage:
|
||||
raise ValueError("No user message in conversation")
|
||||
|
||||
|
||||
def ensure_author(message: ChatMessage, fallback: str) -> ChatMessage:
|
||||
def ensure_author(message: Message, fallback: str) -> Message:
|
||||
"""Attach `fallback` author if message is missing `author_name`."""
|
||||
message.author_name = message.author_name or fallback
|
||||
return message
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, cast
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
from ._checkpoint_encoding import decode_checkpoint_value, encode_checkpoint_value
|
||||
|
||||
"""Utilities for serializing and deserializing chat conversations for persistence.
|
||||
|
||||
These helpers convert rich `ChatMessage` instances to checkpoint-friendly payloads
|
||||
These helpers convert rich `Message` instances to checkpoint-friendly payloads
|
||||
using the same encoding primitives as the workflow runner. This preserves
|
||||
`additional_properties` and other metadata without relying on unsafe mechanisms
|
||||
such as pickling.
|
||||
"""
|
||||
|
||||
|
||||
def encode_chat_messages(messages: Iterable[ChatMessage]) -> list[dict[str, Any]]:
|
||||
def encode_chat_messages(messages: Iterable[Message]) -> list[dict[str, Any]]:
|
||||
"""Serialize chat messages into checkpoint-safe payloads."""
|
||||
encoded: list[dict[str, Any]] = []
|
||||
for message in messages:
|
||||
@@ -32,9 +32,9 @@ def encode_chat_messages(messages: Iterable[ChatMessage]) -> list[dict[str, Any]
|
||||
return encoded
|
||||
|
||||
|
||||
def decode_chat_messages(payload: Iterable[dict[str, Any]]) -> list[ChatMessage]:
|
||||
def decode_chat_messages(payload: Iterable[dict[str, Any]]) -> list[Message]:
|
||||
"""Restore chat messages from checkpoint-safe payloads."""
|
||||
restored: list[ChatMessage] = []
|
||||
restored: list[Message] = []
|
||||
for item in payload:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
@@ -64,7 +64,7 @@ def decode_chat_messages(payload: Iterable[dict[str, Any]]) -> list[ChatMessage]
|
||||
additional[key] = decode_checkpoint_value(value)
|
||||
|
||||
restored.append(
|
||||
ChatMessage( # type: ignore[call-overload]
|
||||
Message( # type: ignore[call-overload]
|
||||
role=role,
|
||||
contents=contents,
|
||||
author_name=item.get("author_name"),
|
||||
|
||||
@@ -18,7 +18,7 @@ from ._edge import (
|
||||
SwitchCaseEdgeGroup,
|
||||
)
|
||||
from ._executor import Executor
|
||||
from ._runner_context import Message, RunnerContext
|
||||
from ._runner_context import RunnerContext, WorkflowMessage
|
||||
from ._state import State
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -38,7 +38,7 @@ class EdgeRunner(ABC):
|
||||
self._executors = executors
|
||||
|
||||
@abstractmethod
|
||||
async def send_message(self, message: Message, state: State, ctx: RunnerContext) -> bool:
|
||||
async def send_message(self, message: WorkflowMessage, state: State, ctx: RunnerContext) -> bool:
|
||||
"""Send a message through the edge group.
|
||||
|
||||
Args:
|
||||
@@ -52,7 +52,7 @@ class EdgeRunner(ABC):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _can_handle(self, executor_id: str, message: Message) -> bool:
|
||||
def _can_handle(self, executor_id: str, message: WorkflowMessage) -> bool:
|
||||
"""Check if an executor can handle the given message data."""
|
||||
if executor_id not in self._executors:
|
||||
return False
|
||||
@@ -62,7 +62,7 @@ class EdgeRunner(ABC):
|
||||
self,
|
||||
target_id: str,
|
||||
source_ids: list[str],
|
||||
message: Message,
|
||||
message: WorkflowMessage,
|
||||
state: State,
|
||||
ctx: RunnerContext,
|
||||
) -> None:
|
||||
@@ -90,7 +90,7 @@ class SingleEdgeRunner(EdgeRunner):
|
||||
super().__init__(edge_group, executors)
|
||||
self._edge = edge_group.edges[0]
|
||||
|
||||
async def send_message(self, message: Message, state: State, ctx: RunnerContext) -> bool:
|
||||
async def send_message(self, message: WorkflowMessage, state: State, ctx: RunnerContext) -> bool:
|
||||
"""Send a message through the single edge."""
|
||||
should_execute = False
|
||||
target_id: str | None = None
|
||||
@@ -162,7 +162,7 @@ class FanOutEdgeRunner(EdgeRunner):
|
||||
Callable[[Any, list[str]], list[str]] | None, getattr(edge_group, "selection_func", None)
|
||||
)
|
||||
|
||||
async def send_message(self, message: Message, state: State, ctx: RunnerContext) -> bool:
|
||||
async def send_message(self, message: WorkflowMessage, state: State, ctx: RunnerContext) -> bool:
|
||||
"""Send a message through all edges in the fan-out edge group."""
|
||||
deliverable_edges: list[Edge] = []
|
||||
single_target_edge: Edge | None = None
|
||||
@@ -283,9 +283,9 @@ class FanInEdgeRunner(EdgeRunner):
|
||||
self._edges = edge_group.edges
|
||||
# Buffer to hold messages before sending them to the target executor
|
||||
# Key is the source executor ID, value is a list of messages
|
||||
self._buffer: dict[str, list[Message]] = defaultdict(list)
|
||||
self._buffer: dict[str, list[WorkflowMessage]] = defaultdict(list)
|
||||
|
||||
async def send_message(self, message: Message, state: State, ctx: RunnerContext) -> bool:
|
||||
async def send_message(self, message: WorkflowMessage, state: State, ctx: RunnerContext) -> bool:
|
||||
"""Send a message through all edges in the fan-in edge group."""
|
||||
execution_data: dict[str, Any] | None = None
|
||||
with create_edge_group_processing_span(
|
||||
@@ -306,7 +306,7 @@ class FanInEdgeRunner(EdgeRunner):
|
||||
|
||||
# Check if target can handle list of message data (fan-in aggregates multiple messages)
|
||||
if self._can_handle(
|
||||
self._edges[0].target_id, Message(data=[message.data], source_id=message.source_id)
|
||||
self._edges[0].target_id, WorkflowMessage(data=[message.data], source_id=message.source_id)
|
||||
):
|
||||
# If the edge can handle the data, buffer the message
|
||||
self._buffer[message.source_id].append(message)
|
||||
@@ -334,7 +334,7 @@ class FanInEdgeRunner(EdgeRunner):
|
||||
source_span_ids = [msg.source_span_id for msg in messages_to_send if msg.source_span_id]
|
||||
|
||||
# Create a new Message object for the aggregated data
|
||||
aggregated_message = Message(
|
||||
aggregated_message = WorkflowMessage(
|
||||
data=aggregated_data,
|
||||
source_id=self._edge_group.__class__.__name__, # This won't be used in self._execute_on_target.
|
||||
trace_contexts=trace_contexts,
|
||||
|
||||
@@ -17,7 +17,7 @@ from ._events import (
|
||||
)
|
||||
from ._model_utils import DictConvertible
|
||||
from ._request_info_mixin import RequestInfoMixin
|
||||
from ._runner_context import Message, MessageType, RunnerContext
|
||||
from ._runner_context import MessageType, RunnerContext, WorkflowMessage
|
||||
from ._state import State
|
||||
from ._typing_utils import is_instance_of, normalize_type_to_list, resolve_type_annotation
|
||||
from ._workflow_context import WorkflowContext, validate_workflow_context_annotation
|
||||
@@ -244,7 +244,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
with create_processing_span(
|
||||
self.id,
|
||||
self.__class__.__name__,
|
||||
str(MessageType.STANDARD if not isinstance(message, Message) else message.type),
|
||||
str(MessageType.STANDARD if not isinstance(message, WorkflowMessage) else message.type),
|
||||
type(message).__name__,
|
||||
source_trace_contexts=trace_contexts,
|
||||
source_span_ids=source_span_ids,
|
||||
@@ -253,7 +253,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
handler = self._find_handler(message)
|
||||
|
||||
original_message = message
|
||||
if isinstance(message, Message):
|
||||
if isinstance(message, WorkflowMessage):
|
||||
# Unwrap raw data for handler call
|
||||
message = message.data
|
||||
|
||||
@@ -265,7 +265,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
trace_contexts=trace_contexts,
|
||||
source_span_ids=source_span_ids,
|
||||
request_id=original_message.original_request_info_event.request_id
|
||||
if isinstance(original_message, Message) and original_message.original_request_info_event
|
||||
if isinstance(original_message, WorkflowMessage) and original_message.original_request_info_event
|
||||
else None,
|
||||
)
|
||||
|
||||
@@ -351,7 +351,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
# Add to unified handler specs list
|
||||
self._handler_specs.append({**handler_spec})
|
||||
|
||||
def can_handle(self, message: Message) -> bool:
|
||||
def can_handle(self, message: WorkflowMessage) -> bool:
|
||||
"""Check if the executor can handle a given message type.
|
||||
|
||||
Args:
|
||||
@@ -460,7 +460,7 @@ class Executor(RequestInfoMixin, DictConvertible):
|
||||
Returns:
|
||||
The handler function if found, None otherwise
|
||||
"""
|
||||
if isinstance(message, Message):
|
||||
if isinstance(message, WorkflowMessage):
|
||||
# Case where Message wrapper is passed instead of raw data
|
||||
# Handler can be a standard handler or a response handler
|
||||
if message.type == MessageType.STANDARD:
|
||||
|
||||
@@ -4,38 +4,38 @@
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
|
||||
def normalize_messages_input(
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
"""Normalize heterogeneous message inputs to a list of ChatMessage objects.
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
) -> list[Message]:
|
||||
"""Normalize heterogeneous message inputs to a list of Message objects.
|
||||
|
||||
Args:
|
||||
messages: String, ChatMessage, or sequence of either. None yields empty list.
|
||||
messages: String, Message, or sequence of either. None yields empty list.
|
||||
|
||||
Returns:
|
||||
List of ChatMessage instances suitable for workflow consumption.
|
||||
List of Message instances suitable for workflow consumption.
|
||||
"""
|
||||
if messages is None:
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage(role="user", text=messages)]
|
||||
return [Message(role="user", text=messages)]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
if isinstance(messages, Message):
|
||||
return [messages]
|
||||
|
||||
normalized: list[ChatMessage] = []
|
||||
normalized: list[Message] = []
|
||||
for item in messages:
|
||||
if isinstance(item, str):
|
||||
normalized.append(ChatMessage(role="user", text=item))
|
||||
elif isinstance(item, ChatMessage):
|
||||
normalized.append(Message(role="user", text=item))
|
||||
elif isinstance(item, Message):
|
||||
normalized.append(item)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Messages sequence must contain only str or ChatMessage instances; found {type(item).__name__}."
|
||||
f"Messages sequence must contain only str or Message instances; found {type(item).__name__}."
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ from ._exceptions import (
|
||||
)
|
||||
from ._executor import Executor
|
||||
from ._runner_context import (
|
||||
Message,
|
||||
RunnerContext,
|
||||
WorkflowMessage,
|
||||
)
|
||||
from ._state import State
|
||||
|
||||
@@ -162,14 +162,14 @@ class Runner:
|
||||
self._running = False
|
||||
|
||||
async def _run_iteration(self) -> None:
|
||||
async def _deliver_messages(source_executor_id: str, messages: list[Message]) -> None:
|
||||
async def _deliver_messages(source_executor_id: str, messages: list[WorkflowMessage]) -> None:
|
||||
"""Outer loop to concurrently deliver messages from all sources to their targets."""
|
||||
|
||||
async def _deliver_message_inner(edge_runner: EdgeRunner, message: Message) -> bool:
|
||||
async def _deliver_message_inner(edge_runner: EdgeRunner, message: WorkflowMessage) -> bool:
|
||||
"""Inner loop to deliver a single message through an edge runner."""
|
||||
return await edge_runner.send_message(message, self._state, self._ctx)
|
||||
|
||||
def _normalize_message_payload(message: Message) -> None:
|
||||
def _normalize_message_payload(message: WorkflowMessage) -> None:
|
||||
data = message.data
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
|
||||
@@ -29,25 +29,25 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
class MessageType(Enum):
|
||||
"""Enumeration of message types in the workflow."""
|
||||
"""Enumeration of WorkflowMessage types in the workflow."""
|
||||
|
||||
STANDARD = "standard"
|
||||
"""A standard message between executors."""
|
||||
"""A standard WorkflowMessage between executors."""
|
||||
|
||||
RESPONSE = "response"
|
||||
"""A response message to a pending request."""
|
||||
"""A response WorkflowMessage to a pending request."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""A class representing a message in the workflow."""
|
||||
class WorkflowMessage:
|
||||
"""A class representing a WorkflowMessage in the workflow."""
|
||||
|
||||
data: Any
|
||||
source_id: str
|
||||
target_id: str | None = None
|
||||
type: MessageType = MessageType.STANDARD
|
||||
|
||||
# OpenTelemetry trace context fields for message propagation
|
||||
# OpenTelemetry trace context fields for WorkflowMessage propagation
|
||||
# These are plural to support fan-in scenarios where multiple messages are aggregated
|
||||
trace_contexts: list[dict[str, str]] | None = None # W3C Trace Context headers from multiple sources
|
||||
source_span_ids: list[str] | None = None # Publishing span IDs for linking from multiple sources
|
||||
@@ -67,7 +67,7 @@ class Message:
|
||||
return self.source_span_ids[0] if self.source_span_ids else None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Convert the Message to a dictionary for serialization."""
|
||||
"""Convert the WorkflowMessage to a dictionary for serialization."""
|
||||
return {
|
||||
"data": encode_checkpoint_value(self.data),
|
||||
"source_id": self.source_id,
|
||||
@@ -79,16 +79,16 @@ class Message:
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, Any]) -> Message:
|
||||
"""Create a Message from a dictionary."""
|
||||
def from_dict(data: dict[str, Any]) -> WorkflowMessage:
|
||||
"""Create a WorkflowMessage from a dictionary."""
|
||||
# Validation
|
||||
if "data" not in data:
|
||||
raise KeyError("Missing 'data' field in Message dictionary.")
|
||||
raise KeyError("Missing 'data' field in WorkflowMessage dictionary.")
|
||||
|
||||
if "source_id" not in data:
|
||||
raise KeyError("Missing 'source_id' field in Message dictionary.")
|
||||
raise KeyError("Missing 'source_id' field in WorkflowMessage dictionary.")
|
||||
|
||||
return Message(
|
||||
return WorkflowMessage(
|
||||
data=decode_checkpoint_value(data["data"]),
|
||||
source_id=data["source_id"],
|
||||
target_id=data.get("target_id"),
|
||||
@@ -119,15 +119,15 @@ class RunnerContext(Protocol):
|
||||
If checkpoint storage is not configured, checkpoint methods may raise.
|
||||
"""
|
||||
|
||||
async def send_message(self, message: Message) -> None:
|
||||
"""Send a message from the executor to the context.
|
||||
async def send_message(self, WorkflowMessage: WorkflowMessage) -> None:
|
||||
"""Send a WorkflowMessage from the executor to the context.
|
||||
|
||||
Args:
|
||||
message: The message to be sent.
|
||||
WorkflowMessage: The WorkflowMessage to be sent.
|
||||
"""
|
||||
...
|
||||
|
||||
async def drain_messages(self) -> dict[str, list[Message]]:
|
||||
async def drain_messages(self) -> dict[str, list[WorkflowMessage]]:
|
||||
"""Drain all messages from the context.
|
||||
|
||||
Returns:
|
||||
@@ -291,7 +291,7 @@ class InProcRunnerContext:
|
||||
Args:
|
||||
checkpoint_storage: Optional storage to enable checkpointing.
|
||||
"""
|
||||
self._messages: dict[str, list[Message]] = {}
|
||||
self._messages: dict[str, list[WorkflowMessage]] = {}
|
||||
# Event queue for immediate streaming of events
|
||||
self._event_queue: asyncio.Queue[WorkflowEvent] = asyncio.Queue()
|
||||
|
||||
@@ -307,11 +307,11 @@ class InProcRunnerContext:
|
||||
self._streaming: bool = False
|
||||
|
||||
# region Messaging and Events
|
||||
async def send_message(self, message: Message) -> None:
|
||||
self._messages.setdefault(message.source_id, [])
|
||||
self._messages[message.source_id].append(message)
|
||||
async def send_message(self, WorkflowMessage: WorkflowMessage) -> None:
|
||||
self._messages.setdefault(WorkflowMessage.source_id, [])
|
||||
self._messages[WorkflowMessage.source_id].append(WorkflowMessage)
|
||||
|
||||
async def drain_messages(self) -> dict[str, list[Message]]:
|
||||
async def drain_messages(self) -> dict[str, list[WorkflowMessage]]:
|
||||
messages = copy(self._messages)
|
||||
self._messages.clear()
|
||||
return messages
|
||||
@@ -422,7 +422,7 @@ class InProcRunnerContext:
|
||||
self._messages.clear()
|
||||
messages_data = checkpoint.messages
|
||||
for source_id, message_list in messages_data.items():
|
||||
self._messages[source_id] = [Message.from_dict(msg) for msg in message_list]
|
||||
self._messages[source_id] = [WorkflowMessage.from_dict(msg) for msg in message_list]
|
||||
|
||||
# Restore pending request info events
|
||||
self._pending_request_info_events.clear()
|
||||
@@ -504,7 +504,7 @@ class InProcRunnerContext:
|
||||
source_executor_id = event.source_executor_id
|
||||
|
||||
# Create ResponseMessage instance
|
||||
response_msg = Message(
|
||||
response_msg = WorkflowMessage(
|
||||
data=response,
|
||||
source_id=INTERNAL_SOURCE_ID(source_executor_id),
|
||||
target_id=source_executor_id,
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
from types import UnionType
|
||||
from typing import Any, TypeGuard, Union, cast, get_args, get_origin
|
||||
|
||||
from .._agents import ChatAgent
|
||||
from .._agents import Agent
|
||||
|
||||
|
||||
def is_chat_agent(agent: Any) -> TypeGuard[ChatAgent]:
|
||||
"""Check if the given agent is a ChatAgent.
|
||||
def is_chat_agent(agent: Any) -> TypeGuard[Agent]:
|
||||
"""Check if the given agent is a Agent.
|
||||
|
||||
Args:
|
||||
agent (Any): The agent to check.
|
||||
|
||||
Returns:
|
||||
TypeGuard[ChatAgent]: True if the agent is a ChatAgent, False otherwise.
|
||||
TypeGuard[Agent]: True if the agent is a Agent, False otherwise.
|
||||
"""
|
||||
return isinstance(agent, ChatAgent)
|
||||
return isinstance(agent, Agent)
|
||||
|
||||
|
||||
def resolve_type_annotation(
|
||||
@@ -255,7 +255,7 @@ def is_type_compatible(source_type: type | UnionType | Any, target_type: type |
|
||||
|
||||
A type is compatible if values of source_type can be assigned to variables of target_type.
|
||||
For example:
|
||||
- list[ChatMessage] is compatible with list[str | ChatMessage]
|
||||
- list[Message] is compatible with list[str | Message]
|
||||
- str is compatible with str | int
|
||||
- int is compatible with Any
|
||||
|
||||
|
||||
@@ -841,14 +841,14 @@ class Workflow(DictConvertible):
|
||||
def as_agent(self, name: str | None = None) -> WorkflowAgent:
|
||||
"""Create a WorkflowAgent that wraps this workflow.
|
||||
|
||||
The returned agent converts standard agent inputs (strings, ChatMessage, or lists of these)
|
||||
into a list[ChatMessage] that is passed to the workflow's start executor. This conversion
|
||||
The returned agent converts standard agent inputs (strings, Message, or lists of these)
|
||||
into a list[Message] that is passed to the workflow's start executor. This conversion
|
||||
happens in WorkflowAgent._normalize_messages() which transforms:
|
||||
- str -> [ChatMessage(USER, [str])]
|
||||
- ChatMessage -> [ChatMessage]
|
||||
- list[str | ChatMessage] -> list[ChatMessage] (with string elements converted)
|
||||
- str -> [Message(USER, [str])]
|
||||
- Message -> [Message]
|
||||
- list[str | Message] -> list[Message] (with string elements converted)
|
||||
|
||||
The workflow's start executor must accept list[ChatMessage] as an input type, otherwise
|
||||
The workflow's start executor must accept list[Message] as an input type, otherwise
|
||||
initialization will fail with a ValueError.
|
||||
|
||||
Args:
|
||||
@@ -858,7 +858,7 @@ class Workflow(DictConvertible):
|
||||
A WorkflowAgent instance that wraps this workflow.
|
||||
|
||||
Raises:
|
||||
ValueError: If the workflow's start executor cannot handle list[ChatMessage] input.
|
||||
ValueError: If the workflow's start executor cannot handle list[Message] input.
|
||||
"""
|
||||
# Import here to avoid circular imports
|
||||
from ._agent import WorkflowAgent
|
||||
|
||||
@@ -19,7 +19,7 @@ from ._events import (
|
||||
WorkflowEventSource,
|
||||
_framework_event_origin, # type: ignore
|
||||
)
|
||||
from ._runner_context import Message, RunnerContext
|
||||
from ._runner_context import RunnerContext, WorkflowMessage
|
||||
from ._state import State
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -321,7 +321,7 @@ class WorkflowContext(Generic[OutT, W_OutT]):
|
||||
attributes[OtelAttr.MESSAGE_DESTINATION_EXECUTOR_ID] = target_id
|
||||
with create_workflow_span(OtelAttr.MESSAGE_SEND_SPAN, attributes, kind=SpanKind.PRODUCER) as span:
|
||||
# Create Message wrapper
|
||||
msg = Message(data=message, source_id=self._executor_id, target_id=target_id)
|
||||
msg = WorkflowMessage(data=message, source_id=self._executor_id, target_id=target_id)
|
||||
|
||||
# Track sent message for executor_completed event (type='executor_completed')
|
||||
self._sent_messages.append(message)
|
||||
|
||||
@@ -19,7 +19,7 @@ from ._events import (
|
||||
)
|
||||
from ._executor import Executor, handler
|
||||
from ._request_info_mixin import response_handler
|
||||
from ._runner_context import Message
|
||||
from ._runner_context import WorkflowMessage
|
||||
from ._typing_utils import is_instance_of
|
||||
from ._workflow import WorkflowRunResult
|
||||
from ._workflow_context import WorkflowContext
|
||||
@@ -340,7 +340,7 @@ class WorkflowExecutor(Executor):
|
||||
data["workflow"] = self.workflow.to_dict()
|
||||
return data
|
||||
|
||||
def can_handle(self, message: Message) -> bool:
|
||||
def can_handle(self, message: WorkflowMessage) -> bool:
|
||||
"""Override can_handle to only accept messages that the wrapped workflow can handle.
|
||||
|
||||
This prevents the WorkflowExecutor from accepting messages that should go to other
|
||||
|
||||
@@ -14,6 +14,7 @@ _IMPORTS = [
|
||||
"OpenAIResponse",
|
||||
"ResponseStreamEvent",
|
||||
"main",
|
||||
"register_cleanup",
|
||||
"serve",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -10,6 +10,7 @@ from agent_framework_devui import (
|
||||
ResponseStreamEvent,
|
||||
__version__,
|
||||
main,
|
||||
register_cleanup,
|
||||
serve,
|
||||
)
|
||||
|
||||
@@ -23,5 +24,6 @@ __all__ = [
|
||||
"ResponseStreamEvent",
|
||||
"__version__",
|
||||
"main",
|
||||
"register_cleanup",
|
||||
"serve",
|
||||
]
|
||||
|
||||
@@ -39,18 +39,18 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._agents import SupportsAgentRun
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._clients import SupportsChatGetResponse
|
||||
from ._threads import AgentThread
|
||||
from ._tools import FunctionTool
|
||||
from ._types import (
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
FinishReason,
|
||||
Message,
|
||||
ResponseStream,
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ __all__ = [
|
||||
|
||||
|
||||
AgentT = TypeVar("AgentT", bound="SupportsAgentRun")
|
||||
ChatClientT = TypeVar("ChatClientT", bound="ChatClientProtocol[Any]")
|
||||
ChatClientT = TypeVar("ChatClientT", bound="SupportsChatGetResponse[Any]")
|
||||
|
||||
|
||||
logger = get_logger()
|
||||
@@ -122,7 +122,7 @@ OPERATION_DURATION_BUCKET_BOUNDARIES: Final[tuple[float, ...]] = (
|
||||
#
|
||||
# This is a workaround, we'll find a generic and better solution - see
|
||||
# https://github.com/open-telemetry/semantic-conventions/issues/1701
|
||||
class ChatMessageListTimestampFilter(logging.Filter):
|
||||
class MessageListTimestampFilter(logging.Filter):
|
||||
"""A filter to increment the timestamp of INFO logs by 1 microsecond."""
|
||||
|
||||
INDEX_KEY: ClassVar[str] = "chat_message_index"
|
||||
@@ -135,7 +135,7 @@ class ChatMessageListTimestampFilter(logging.Filter):
|
||||
return True
|
||||
|
||||
|
||||
logger.addFilter(ChatMessageListTimestampFilter())
|
||||
logger.addFilter(MessageListTimestampFilter())
|
||||
|
||||
|
||||
class OtelAttr(str, Enum):
|
||||
@@ -1070,7 +1070,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
@@ -1080,7 +1080,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
options: OptionsCoT | ChatOptions[None] | None = None,
|
||||
@@ -1090,7 +1090,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
@overload
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: Literal[True],
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -1099,7 +1099,7 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
*,
|
||||
stream: bool = False,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
@@ -1263,7 +1263,7 @@ class AgentTelemetryLayer:
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1273,7 +1273,7 @@ class AgentTelemetryLayer:
|
||||
@overload
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: Literal[True],
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1282,7 +1282,7 @@ class AgentTelemetryLayer:
|
||||
|
||||
def run(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage] | None = None,
|
||||
messages: str | Message | Sequence[str | Message] | None = None,
|
||||
*,
|
||||
stream: bool = False,
|
||||
thread: AgentThread | None = None,
|
||||
@@ -1600,7 +1600,7 @@ def capture_exception(span: trace.Span, exception: Exception, timestamp: int | N
|
||||
def _capture_messages(
|
||||
span: trace.Span,
|
||||
provider_name: str,
|
||||
messages: str | ChatMessage | Sequence[str | ChatMessage],
|
||||
messages: str | Message | Sequence[str | Message],
|
||||
system_instructions: str | list[str] | None = None,
|
||||
output: bool = False,
|
||||
finish_reason: FinishReason | None = None,
|
||||
@@ -1620,7 +1620,7 @@ def _capture_messages(
|
||||
extra={
|
||||
OtelAttr.EVENT_NAME: OtelAttr.CHOICE if output else ROLE_EVENT_MAP.get(message.role),
|
||||
OtelAttr.PROVIDER_NAME: provider_name,
|
||||
ChatMessageListTimestampFilter.INDEX_KEY: index,
|
||||
MessageListTimestampFilter.INDEX_KEY: index,
|
||||
},
|
||||
)
|
||||
if finish_reason:
|
||||
@@ -1633,7 +1633,7 @@ def _capture_messages(
|
||||
span.set_attribute(OtelAttr.SYSTEM_INSTRUCTIONS, json.dumps(otel_sys_instructions))
|
||||
|
||||
|
||||
def _to_otel_message(message: ChatMessage) -> dict[str, Any]:
|
||||
def _to_otel_message(message: Message) -> dict[str, Any]:
|
||||
"""Create a otel representation of a message."""
|
||||
return {"role": message.role, "parts": [_to_otel_part(content) for content in message.contents]}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from openai import AsyncOpenAI
|
||||
from openai.types.beta.assistant import Assistant
|
||||
from pydantic import BaseModel, SecretStr, ValidationError
|
||||
|
||||
from .._agents import ChatAgent
|
||||
from .._agents import Agent
|
||||
from .._memory import ContextProvider
|
||||
from .._middleware import MiddlewareTypes
|
||||
from .._tools import FunctionTool, ToolProtocol
|
||||
@@ -51,10 +51,10 @@ _ToolsType = (
|
||||
|
||||
|
||||
class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
"""Provider for creating ChatAgent instances from OpenAI Assistants API.
|
||||
"""Provider for creating Agent instances from OpenAI Assistants API.
|
||||
|
||||
This provider allows you to create, retrieve, and wrap OpenAI Assistants
|
||||
as ChatAgent instances for use in the agent framework.
|
||||
as Agent instances for use in the agent framework.
|
||||
|
||||
Examples:
|
||||
Basic usage with automatic client creation:
|
||||
@@ -208,11 +208,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a new assistant on OpenAI and return a ChatAgent.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a new assistant on OpenAI and return a Agent.
|
||||
|
||||
This method creates a new assistant on the OpenAI service and wraps it
|
||||
in a ChatAgent instance. The assistant will persist on OpenAI until deleted.
|
||||
in a Agent instance. The assistant will persist on OpenAI until deleted.
|
||||
|
||||
Keyword Args:
|
||||
name: The name of the assistant (required).
|
||||
@@ -228,11 +228,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
default_options: A TypedDict containing default chat options for the agent.
|
||||
These options are applied to every run unless overridden.
|
||||
Include ``response_format`` here for structured output responses.
|
||||
middleware: MiddlewareTypes for the ChatAgent.
|
||||
context_provider: Context provider for the ChatAgent.
|
||||
middleware: MiddlewareTypes for the Agent.
|
||||
context_provider: Context provider for the Agent.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance wrapping the created assistant.
|
||||
A Agent instance wrapping the created assistant.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If assistant creation fails.
|
||||
@@ -297,7 +297,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
|
||||
assistant = await self._client.beta.assistants.create(**create_params)
|
||||
|
||||
# Create ChatAgent - pass default_options which contains response_format
|
||||
# Create Agent - pass default_options which contains response_format
|
||||
return self._create_chat_agent_from_assistant(
|
||||
assistant=assistant,
|
||||
tools=normalized_tools,
|
||||
@@ -316,11 +316,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Retrieve an existing assistant by ID and return a ChatAgent.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Retrieve an existing assistant by ID and return a Agent.
|
||||
|
||||
This method fetches an existing assistant from OpenAI by its ID
|
||||
and wraps it in a ChatAgent instance.
|
||||
and wraps it in a Agent instance.
|
||||
|
||||
Args:
|
||||
assistant_id: The ID of the assistant to retrieve (e.g., "asst_123").
|
||||
@@ -333,11 +333,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
instructions: Override the assistant's instructions (optional).
|
||||
default_options: A TypedDict containing default chat options for the agent.
|
||||
These options are applied to every run unless overridden.
|
||||
middleware: MiddlewareTypes for the ChatAgent.
|
||||
context_provider: Context provider for the ChatAgent.
|
||||
middleware: MiddlewareTypes for the Agent.
|
||||
context_provider: Context provider for the Agent.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance wrapping the retrieved assistant.
|
||||
A Agent instance wrapping the retrieved assistant.
|
||||
|
||||
Raises:
|
||||
ServiceInitializationError: If the assistant cannot be retrieved.
|
||||
@@ -382,11 +382,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_provider: ContextProvider | None = None,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Wrap an existing SDK Assistant object as a ChatAgent.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Wrap an existing SDK Assistant object as a Agent.
|
||||
|
||||
This method does NOT make any HTTP calls. It simply wraps an already-
|
||||
fetched Assistant object in a ChatAgent.
|
||||
fetched Assistant object in a Agent.
|
||||
|
||||
Args:
|
||||
assistant: The OpenAI Assistant SDK object to wrap.
|
||||
@@ -398,11 +398,11 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
instructions: Override the assistant's instructions (optional).
|
||||
default_options: A TypedDict containing default chat options for the agent.
|
||||
These options are applied to every run unless overridden.
|
||||
middleware: MiddlewareTypes for the ChatAgent.
|
||||
context_provider: Context provider for the ChatAgent.
|
||||
middleware: MiddlewareTypes for the Agent.
|
||||
context_provider: Context provider for the Agent.
|
||||
|
||||
Returns:
|
||||
A ChatAgent instance wrapping the assistant.
|
||||
A Agent instance wrapping the assistant.
|
||||
|
||||
Raises:
|
||||
ValueError: If required function tools are missing.
|
||||
@@ -429,7 +429,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
# Merge hosted tools with user-provided function tools
|
||||
merged_tools = self._merge_tools(assistant.tools or [], tools)
|
||||
|
||||
# Create ChatAgent
|
||||
# Create Agent
|
||||
return self._create_chat_agent_from_assistant(
|
||||
assistant=assistant,
|
||||
tools=merged_tools,
|
||||
@@ -526,8 +526,8 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
context_provider: ContextProvider | None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ChatAgent[OptionsCoT]:
|
||||
"""Create a ChatAgent from an Assistant.
|
||||
) -> Agent[OptionsCoT]:
|
||||
"""Create a Agent from an Assistant.
|
||||
|
||||
Args:
|
||||
assistant: The OpenAI Assistant object.
|
||||
@@ -536,13 +536,13 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
middleware: MiddlewareTypes for the agent.
|
||||
context_provider: Context provider for the agent.
|
||||
default_options: Default chat options for the agent (may include response_format).
|
||||
**kwargs: Additional arguments passed to ChatAgent.
|
||||
**kwargs: Additional arguments passed to Agent.
|
||||
|
||||
Returns:
|
||||
A configured ChatAgent instance.
|
||||
A configured Agent instance.
|
||||
"""
|
||||
# Create the chat client with the assistant
|
||||
chat_client = OpenAIAssistantsClient(
|
||||
client = OpenAIAssistantsClient(
|
||||
model_id=assistant.model,
|
||||
assistant_id=assistant.id,
|
||||
assistant_name=assistant.name,
|
||||
@@ -553,9 +553,9 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
# Use instructions from assistant if not overridden
|
||||
final_instructions = instructions if instructions is not None else assistant.instructions
|
||||
|
||||
# Create and return ChatAgent
|
||||
return ChatAgent(
|
||||
chat_client=chat_client,
|
||||
# Create and return Agent
|
||||
return Agent(
|
||||
client=client,
|
||||
id=assistant.id,
|
||||
name=assistant.name,
|
||||
description=assistant.description,
|
||||
|
||||
@@ -39,11 +39,11 @@ from .._tools import (
|
||||
HostedFileSearchTool,
|
||||
)
|
||||
from .._types import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
Message,
|
||||
ResponseStream,
|
||||
UsageDetails,
|
||||
prepare_function_call_results,
|
||||
@@ -352,7 +352,7 @@ class OpenAIAssistantsClient( # type: ignore[misc]
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -605,7 +605,7 @@ class OpenAIAssistantsClient( # type: ignore[misc]
|
||||
|
||||
def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> tuple[dict[str, Any], list[Content] | None]:
|
||||
|
||||
@@ -29,12 +29,12 @@ from .._tools import (
|
||||
ToolProtocol,
|
||||
)
|
||||
from .._types import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
FinishReason,
|
||||
Message,
|
||||
ResponseStream,
|
||||
UsageDetails,
|
||||
prepare_function_call_results,
|
||||
@@ -158,7 +158,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -252,7 +252,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
ret_dict["web_search_options"] = web_search_options
|
||||
return ret_dict
|
||||
|
||||
def _prepare_options(self, messages: Sequence[ChatMessage], options: Mapping[str, Any]) -> dict[str, Any]:
|
||||
def _prepare_options(self, messages: Sequence[Message], options: Mapping[str, Any]) -> dict[str, Any]:
|
||||
# Prepend instructions from options if they exist
|
||||
from .._types import prepend_instructions_to_messages, validate_tool_mode
|
||||
|
||||
@@ -310,7 +310,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
def _parse_response_from_openai(self, response: ChatCompletion, options: Mapping[str, Any]) -> ChatResponse:
|
||||
"""Parse a response from OpenAI into a ChatResponse."""
|
||||
response_metadata = self._get_metadata_from_chat_response(response)
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
finish_reason: FinishReason | None = None
|
||||
for choice in response.choices:
|
||||
response_metadata.update(self._get_metadata_from_chat_choice(choice))
|
||||
@@ -323,7 +323,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
contents.extend(parsed_tool_calls)
|
||||
if reasoning_details := getattr(choice.message, "reasoning_details", None):
|
||||
contents.append(Content.from_text_reasoning(protected_data=json.dumps(reasoning_details)))
|
||||
messages.append(ChatMessage(role="assistant", contents=contents))
|
||||
messages.append(Message(role="assistant", contents=contents))
|
||||
return ChatResponse(
|
||||
response_id=response.id,
|
||||
created_at=datetime.fromtimestamp(response.created, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
@@ -448,7 +448,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
|
||||
def _prepare_messages_for_openai(
|
||||
self,
|
||||
chat_messages: Sequence[ChatMessage],
|
||||
chat_messages: Sequence[Message],
|
||||
role_key: str = "role",
|
||||
content_key: str = "content",
|
||||
) -> list[dict[str, Any]]:
|
||||
@@ -476,7 +476,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
|
||||
# region Parsers
|
||||
|
||||
def _prepare_message_for_openai(self, message: ChatMessage) -> list[dict[str, Any]]:
|
||||
def _prepare_message_for_openai(self, message: Message) -> list[dict[str, Any]]:
|
||||
"""Prepare a chat message for OpenAI."""
|
||||
all_messages: list[dict[str, Any]] = []
|
||||
for content in message.contents:
|
||||
|
||||
@@ -51,12 +51,12 @@ from .._tools import (
|
||||
)
|
||||
from .._types import (
|
||||
Annotation,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
ContinuationToken,
|
||||
Message,
|
||||
ResponseStream,
|
||||
Role,
|
||||
TextSpanRegion,
|
||||
@@ -250,7 +250,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
|
||||
async def _prepare_request(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> tuple[AsyncOpenAI, dict[str, Any], dict[str, Any]]:
|
||||
@@ -280,7 +280,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
def _inner_get_response(
|
||||
self,
|
||||
*,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -567,7 +567,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
|
||||
async def _prepare_options(
|
||||
self,
|
||||
messages: Sequence[ChatMessage],
|
||||
messages: Sequence[Message],
|
||||
options: Mapping[str, Any],
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
@@ -673,7 +673,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
"""
|
||||
return kwargs.get("conversation_id") or options.get("conversation_id")
|
||||
|
||||
def _prepare_messages_for_openai(self, chat_messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
|
||||
def _prepare_messages_for_openai(self, chat_messages: Sequence[Message]) -> list[dict[str, Any]]:
|
||||
"""Prepare the chat messages for a request.
|
||||
|
||||
Allowing customization of the key names for role/author, and optionally overriding the role.
|
||||
@@ -705,7 +705,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
|
||||
def _prepare_message_for_openai(
|
||||
self,
|
||||
message: ChatMessage,
|
||||
message: Message,
|
||||
call_id_to_id: dict[str, str],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Prepare a chat message for the OpenAI Responses API format."""
|
||||
@@ -1095,7 +1095,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
)
|
||||
case _:
|
||||
logger.debug("Unparsed output of type: %s: %s", item.type, item)
|
||||
response_message = ChatMessage(role="assistant", contents=contents)
|
||||
response_message = Message(role="assistant", contents=contents)
|
||||
args: dict[str, Any] = {
|
||||
"response_id": response.id,
|
||||
"created_at": datetime.fromtimestamp(response.created_at, tz=timezone.utc).strftime(
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any
|
||||
|
||||
from pytest import fixture
|
||||
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework import Message
|
||||
|
||||
|
||||
# region: Connector Settings fixtures
|
||||
@@ -58,5 +58,5 @@ def azure_openai_unit_test_env(monkeypatch, exclude_list, override_env_param_dic
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def chat_history() -> list[ChatMessage]:
|
||||
def chat_history() -> list[Message]:
|
||||
return []
|
||||
|
||||
@@ -9,15 +9,15 @@ from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
AgentThread,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
HostedCodeInterpreterTool,
|
||||
Message,
|
||||
SupportsChatGetResponse,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
@@ -83,19 +83,19 @@ def mock_async_azure_openai() -> MagicMock:
|
||||
|
||||
def test_azure_assistants_client_init_with_client(mock_async_azure_openai: MagicMock) -> None:
|
||||
"""Test AzureOpenAIAssistantsClient initialization with existing client."""
|
||||
chat_client = create_test_azure_assistants_client(
|
||||
client = create_test_azure_assistants_client(
|
||||
mock_async_azure_openai,
|
||||
deployment_name="test_chat_deployment",
|
||||
assistant_id="existing-assistant-id",
|
||||
thread_id="test-thread-id",
|
||||
)
|
||||
|
||||
assert chat_client.client is mock_async_azure_openai
|
||||
assert chat_client.model_id == "test_chat_deployment"
|
||||
assert chat_client.assistant_id == "existing-assistant-id"
|
||||
assert chat_client.thread_id == "test-thread-id"
|
||||
assert not chat_client._should_delete_assistant # type: ignore
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
assert client.client is mock_async_azure_openai
|
||||
assert client.model_id == "test_chat_deployment"
|
||||
assert client.assistant_id == "existing-assistant-id"
|
||||
assert client.thread_id == "test-thread-id"
|
||||
assert not client._should_delete_assistant # type: ignore
|
||||
assert isinstance(client, SupportsChatGetResponse)
|
||||
|
||||
|
||||
def test_azure_assistants_client_init_auto_create_client(
|
||||
@@ -103,7 +103,7 @@ def test_azure_assistants_client_init_auto_create_client(
|
||||
mock_async_azure_openai: MagicMock,
|
||||
) -> None:
|
||||
"""Test AzureOpenAIAssistantsClient initialization with auto-created client."""
|
||||
chat_client = AzureOpenAIAssistantsClient(
|
||||
client = AzureOpenAIAssistantsClient(
|
||||
deployment_name=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
|
||||
assistant_name="TestAssistant",
|
||||
api_key=azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"],
|
||||
@@ -111,11 +111,11 @@ def test_azure_assistants_client_init_auto_create_client(
|
||||
async_client=mock_async_azure_openai,
|
||||
)
|
||||
|
||||
assert chat_client.client is mock_async_azure_openai
|
||||
assert chat_client.model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert chat_client.assistant_id is None
|
||||
assert chat_client.assistant_name == "TestAssistant"
|
||||
assert not chat_client._should_delete_assistant # type: ignore
|
||||
assert client.client is mock_async_azure_openai
|
||||
assert client.model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert client.assistant_id is None
|
||||
assert client.assistant_name == "TestAssistant"
|
||||
assert not client._should_delete_assistant # type: ignore
|
||||
|
||||
|
||||
def test_azure_assistants_client_init_validation_fail() -> None:
|
||||
@@ -138,32 +138,32 @@ def test_azure_assistants_client_init_with_default_headers(azure_openai_unit_tes
|
||||
"""Test AzureOpenAIAssistantsClient initialization with default headers."""
|
||||
default_headers = {"X-Unit-Test": "test-guid"}
|
||||
|
||||
chat_client = AzureOpenAIAssistantsClient(
|
||||
client = AzureOpenAIAssistantsClient(
|
||||
deployment_name="test_chat_deployment",
|
||||
api_key=azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"],
|
||||
endpoint=azure_openai_unit_test_env["AZURE_OPENAI_ENDPOINT"],
|
||||
default_headers=default_headers,
|
||||
)
|
||||
|
||||
assert chat_client.model_id == "test_chat_deployment"
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
assert client.model_id == "test_chat_deployment"
|
||||
assert isinstance(client, SupportsChatGetResponse)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
assert key in chat_client.client.default_headers
|
||||
assert chat_client.client.default_headers[key] == value
|
||||
assert key in client.client.default_headers
|
||||
assert client.client.default_headers[key] == value
|
||||
|
||||
|
||||
async def test_azure_assistants_client_get_assistant_id_or_create_existing_assistant(
|
||||
mock_async_azure_openai: MagicMock,
|
||||
) -> None:
|
||||
"""Test _get_assistant_id_or_create when assistant_id is already provided."""
|
||||
chat_client = create_test_azure_assistants_client(mock_async_azure_openai, assistant_id="existing-assistant-id")
|
||||
client = create_test_azure_assistants_client(mock_async_azure_openai, assistant_id="existing-assistant-id")
|
||||
|
||||
assistant_id = await chat_client._get_assistant_id_or_create() # type: ignore
|
||||
assistant_id = await client._get_assistant_id_or_create() # type: ignore
|
||||
|
||||
assert assistant_id == "existing-assistant-id"
|
||||
assert not chat_client._should_delete_assistant # type: ignore
|
||||
assert not client._should_delete_assistant # type: ignore
|
||||
mock_async_azure_openai.beta.assistants.create.assert_not_called()
|
||||
|
||||
|
||||
@@ -171,14 +171,14 @@ async def test_azure_assistants_client_get_assistant_id_or_create_create_new(
|
||||
mock_async_azure_openai: MagicMock,
|
||||
) -> None:
|
||||
"""Test _get_assistant_id_or_create when creating a new assistant."""
|
||||
chat_client = create_test_azure_assistants_client(
|
||||
client = create_test_azure_assistants_client(
|
||||
mock_async_azure_openai, deployment_name="test_chat_deployment", assistant_name="TestAssistant"
|
||||
)
|
||||
|
||||
assistant_id = await chat_client._get_assistant_id_or_create() # type: ignore
|
||||
assistant_id = await client._get_assistant_id_or_create() # type: ignore
|
||||
|
||||
assert assistant_id == "test-assistant-id"
|
||||
assert chat_client._should_delete_assistant # type: ignore
|
||||
assert client._should_delete_assistant # type: ignore
|
||||
mock_async_azure_openai.beta.assistants.create.assert_called_once()
|
||||
|
||||
|
||||
@@ -186,38 +186,38 @@ async def test_azure_assistants_client_aclose_should_not_delete(
|
||||
mock_async_azure_openai: MagicMock,
|
||||
) -> None:
|
||||
"""Test close when assistant should not be deleted."""
|
||||
chat_client = create_test_azure_assistants_client(
|
||||
client = create_test_azure_assistants_client(
|
||||
mock_async_azure_openai, assistant_id="assistant-to-keep", should_delete_assistant=False
|
||||
)
|
||||
|
||||
await chat_client.close() # type: ignore
|
||||
await client.close() # type: ignore
|
||||
|
||||
# Verify assistant deletion was not called
|
||||
mock_async_azure_openai.beta.assistants.delete.assert_not_called()
|
||||
assert not chat_client._should_delete_assistant # type: ignore
|
||||
assert not client._should_delete_assistant # type: ignore
|
||||
|
||||
|
||||
async def test_azure_assistants_client_aclose_should_delete(mock_async_azure_openai: MagicMock) -> None:
|
||||
"""Test close method calls cleanup."""
|
||||
chat_client = create_test_azure_assistants_client(
|
||||
client = create_test_azure_assistants_client(
|
||||
mock_async_azure_openai, assistant_id="assistant-to-delete", should_delete_assistant=True
|
||||
)
|
||||
|
||||
await chat_client.close()
|
||||
await client.close()
|
||||
|
||||
# Verify assistant deletion was called
|
||||
mock_async_azure_openai.beta.assistants.delete.assert_called_once_with("assistant-to-delete")
|
||||
assert not chat_client._should_delete_assistant # type: ignore
|
||||
assert not client._should_delete_assistant # type: ignore
|
||||
|
||||
|
||||
async def test_azure_assistants_client_async_context_manager(mock_async_azure_openai: MagicMock) -> None:
|
||||
"""Test async context manager functionality."""
|
||||
chat_client = create_test_azure_assistants_client(
|
||||
client = create_test_azure_assistants_client(
|
||||
mock_async_azure_openai, assistant_id="assistant-to-delete", should_delete_assistant=True
|
||||
)
|
||||
|
||||
# Test context manager
|
||||
async with chat_client:
|
||||
async with client:
|
||||
pass # Just test that we can enter and exit
|
||||
|
||||
# Verify cleanup was called on exit
|
||||
@@ -229,7 +229,7 @@ def test_azure_assistants_client_serialize(azure_openai_unit_test_env: dict[str,
|
||||
default_headers = {"X-Unit-Test": "test-guid"}
|
||||
|
||||
# Test basic initialization and to_dict
|
||||
chat_client = AzureOpenAIAssistantsClient(
|
||||
client = AzureOpenAIAssistantsClient(
|
||||
deployment_name="test_chat_deployment",
|
||||
assistant_id="test-assistant-id",
|
||||
assistant_name="TestAssistant",
|
||||
@@ -239,7 +239,7 @@ def test_azure_assistants_client_serialize(azure_openai_unit_test_env: dict[str,
|
||||
default_headers=default_headers,
|
||||
)
|
||||
|
||||
dumped_settings = chat_client.to_dict()
|
||||
dumped_settings = client.to_dict()
|
||||
|
||||
assert dumped_settings["model_id"] == "test_chat_deployment"
|
||||
assert dumped_settings["assistant_id"] == "test-assistant-id"
|
||||
@@ -267,17 +267,17 @@ def get_weather(
|
||||
async def test_azure_assistants_client_get_response() -> None:
|
||||
"""Test Azure Assistants Client response."""
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert isinstance(azure_assistants_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
text="The weather in Seattle is currently sunny with a high of 25°C. "
|
||||
"It's a beautiful day for outdoor activities.",
|
||||
)
|
||||
)
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like today?"))
|
||||
messages.append(Message(role="user", text="What's the weather like today?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = await azure_assistants_client.get_response(messages=messages)
|
||||
@@ -292,10 +292,10 @@ async def test_azure_assistants_client_get_response() -> None:
|
||||
async def test_azure_assistants_client_get_response_tools() -> None:
|
||||
"""Test Azure Assistants Client response with tools."""
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert isinstance(azure_assistants_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
messages: list[Message] = []
|
||||
messages.append(Message(role="user", text="What's the weather like in Seattle?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = await azure_assistants_client.get_response(
|
||||
@@ -313,17 +313,17 @@ async def test_azure_assistants_client_get_response_tools() -> None:
|
||||
async def test_azure_assistants_client_streaming() -> None:
|
||||
"""Test Azure Assistants Client streaming response."""
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert isinstance(azure_assistants_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
text="The weather in Seattle is currently sunny with a high of 25°C. "
|
||||
"It's a beautiful day for outdoor activities.",
|
||||
)
|
||||
)
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like today?"))
|
||||
messages.append(Message(role="user", text="What's the weather like today?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = azure_assistants_client.get_response(messages=messages, stream=True)
|
||||
@@ -344,10 +344,10 @@ async def test_azure_assistants_client_streaming() -> None:
|
||||
async def test_azure_assistants_client_streaming_tools() -> None:
|
||||
"""Test Azure Assistants Client streaming response with tools."""
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert isinstance(azure_assistants_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
messages: list[Message] = []
|
||||
messages.append(Message(role="user", text="What's the weather like in Seattle?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = azure_assistants_client.get_response(
|
||||
@@ -373,7 +373,7 @@ async def test_azure_assistants_client_with_existing_assistant() -> None:
|
||||
# First create an assistant to use in the test
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as temp_client:
|
||||
# Get the assistant ID by triggering assistant creation
|
||||
messages = [ChatMessage(role="user", text="Hello")]
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
await temp_client.get_response(messages=messages)
|
||||
assistant_id = temp_client.assistant_id
|
||||
|
||||
@@ -381,10 +381,10 @@ async def test_azure_assistants_client_with_existing_assistant() -> None:
|
||||
async with AzureOpenAIAssistantsClient(
|
||||
assistant_id=assistant_id, credential=AzureCliCredential()
|
||||
) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert isinstance(azure_assistants_client, SupportsChatGetResponse)
|
||||
assert azure_assistants_client.assistant_id == assistant_id
|
||||
|
||||
messages = [ChatMessage(role="user", text="What can you do?")]
|
||||
messages = [Message(role="user", text="What can you do?")]
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = await azure_assistants_client.get_response(messages=messages)
|
||||
@@ -397,9 +397,9 @@ async def test_azure_assistants_client_with_existing_assistant() -> None:
|
||||
@pytest.mark.flaky
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_basic_run():
|
||||
"""Test ChatAgent basic run functionality with AzureOpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
"""Test Agent basic run functionality with AzureOpenAIAssistantsClient."""
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run a simple query
|
||||
response = await agent.run("Hello! Please respond with 'Hello World' exactly.")
|
||||
@@ -414,9 +414,9 @@ async def test_azure_assistants_agent_basic_run():
|
||||
@pytest.mark.flaky
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_basic_run_streaming():
|
||||
"""Test ChatAgent basic streaming functionality with AzureOpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
"""Test Agent basic streaming functionality with AzureOpenAIAssistantsClient."""
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run streaming query
|
||||
full_message: str = ""
|
||||
@@ -434,9 +434,9 @@ async def test_azure_assistants_agent_basic_run_streaming():
|
||||
@pytest.mark.flaky
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_thread_persistence():
|
||||
"""Test ChatAgent thread persistence across runs with AzureOpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
"""Test Agent thread persistence across runs with AzureOpenAIAssistantsClient."""
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
# Create a new thread that will be reused
|
||||
@@ -463,12 +463,12 @@ async def test_azure_assistants_agent_thread_persistence():
|
||||
@pytest.mark.flaky
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_existing_thread_id():
|
||||
"""Test ChatAgent with existing thread ID to continue conversations across agent instances."""
|
||||
"""Test Agent with existing thread ID to continue conversations across agent instances."""
|
||||
# First, create a conversation and capture the thread ID
|
||||
existing_thread_id = None
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
) as agent:
|
||||
@@ -487,8 +487,8 @@ async def test_azure_assistants_agent_existing_thread_id():
|
||||
|
||||
# Now continue with the same thread ID in a new agent instance
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(thread_id=existing_thread_id, credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(thread_id=existing_thread_id, credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
) as agent:
|
||||
@@ -508,10 +508,10 @@ async def test_azure_assistants_agent_existing_thread_id():
|
||||
@pytest.mark.flaky
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_code_interpreter():
|
||||
"""Test ChatAgent with code interpreter through AzureOpenAIAssistantsClient."""
|
||||
"""Test Agent with code interpreter through AzureOpenAIAssistantsClient."""
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can write and execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
) as agent:
|
||||
@@ -530,8 +530,8 @@ async def test_azure_assistants_agent_code_interpreter():
|
||||
async def test_azure_assistants_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with Azure Assistants Client."""
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
) as agent:
|
||||
|
||||
@@ -17,13 +17,13 @@ from openai.types.chat.chat_completion_chunk import ChoiceDelta as ChunkChoiceDe
|
||||
from openai.types.chat.chat_completion_message import ChatCompletionMessage
|
||||
|
||||
from agent_framework import (
|
||||
Agent,
|
||||
AgentResponse,
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Message,
|
||||
SupportsChatGetResponse,
|
||||
tool,
|
||||
)
|
||||
from agent_framework._telemetry import USER_AGENT_KEY
|
||||
@@ -52,7 +52,7 @@ def test_init(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
assert azure_chat_client.client is not None
|
||||
assert isinstance(azure_chat_client.client, AsyncAzureOpenAI)
|
||||
assert azure_chat_client.model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
|
||||
def test_init_client(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -75,7 +75,7 @@ def test_init_base_url(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
assert azure_chat_client.client is not None
|
||||
assert isinstance(azure_chat_client.client, AsyncAzureOpenAI)
|
||||
assert azure_chat_client.model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
for key, value in default_headers.items():
|
||||
assert key in azure_chat_client.client.default_headers
|
||||
assert azure_chat_client.client.default_headers[key] == value
|
||||
@@ -88,7 +88,7 @@ def test_init_endpoint(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
assert azure_chat_client.client is not None
|
||||
assert isinstance(azure_chat_client.client, AsyncAzureOpenAI)
|
||||
assert azure_chat_client.model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]], indirect=True)
|
||||
@@ -178,11 +178,11 @@ def mock_streaming_chat_completion_response() -> AsyncStream[ChatCompletionChunk
|
||||
async def test_cmc(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(ChatMessage(text="hello world", role="user"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
await azure_chat_client.get_response(
|
||||
@@ -199,12 +199,12 @@ async def test_cmc(
|
||||
async def test_cmc_with_logit_bias(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
prompt = "hello world"
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
|
||||
token_bias: dict[str | int, float] = {"1": -100}
|
||||
|
||||
@@ -224,12 +224,12 @@ async def test_cmc_with_logit_bias(
|
||||
async def test_cmc_with_stop(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
prompt = "hello world"
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
|
||||
stop = ["!"]
|
||||
|
||||
@@ -249,7 +249,7 @@ async def test_cmc_with_stop(
|
||||
async def test_azure_on_your_data(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_chat_completion_response.choices = [
|
||||
@@ -277,9 +277,9 @@ async def test_azure_on_your_data(
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
prompt = "hello world"
|
||||
messages_in = chat_history
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
messages_out: list[ChatMessage] = []
|
||||
messages_out.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
messages_out: list[Message] = []
|
||||
messages_out.append(Message(text=prompt, role="user"))
|
||||
|
||||
expected_data_settings = {
|
||||
"data_sources": [
|
||||
@@ -319,7 +319,7 @@ async def test_azure_on_your_data(
|
||||
async def test_azure_on_your_data_string(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_chat_completion_response.choices = [
|
||||
@@ -347,9 +347,9 @@ async def test_azure_on_your_data_string(
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
prompt = "hello world"
|
||||
messages_in = chat_history
|
||||
messages_in.append(ChatMessage(text=prompt, role="user"))
|
||||
messages_out: list[ChatMessage] = []
|
||||
messages_out.append(ChatMessage(text=prompt, role="user"))
|
||||
messages_in.append(Message(text=prompt, role="user"))
|
||||
messages_out: list[Message] = []
|
||||
messages_out.append(Message(text=prompt, role="user"))
|
||||
|
||||
expected_data_settings = {
|
||||
"data_sources": [
|
||||
@@ -389,7 +389,7 @@ async def test_azure_on_your_data_string(
|
||||
async def test_azure_on_your_data_fail(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
mock_chat_completion_response.choices = [
|
||||
@@ -406,9 +406,9 @@ async def test_azure_on_your_data_fail(
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
prompt = "hello world"
|
||||
messages_in = chat_history
|
||||
messages_in.append(ChatMessage(text=prompt, role="user"))
|
||||
messages_out: list[ChatMessage] = []
|
||||
messages_out.append(ChatMessage(text=prompt, role="user"))
|
||||
messages_in.append(Message(text=prompt, role="user"))
|
||||
messages_out: list[Message] = []
|
||||
messages_out.append(Message(text=prompt, role="user"))
|
||||
|
||||
expected_data_settings = {
|
||||
"data_sources": [
|
||||
@@ -459,10 +459,10 @@ CONTENT_FILTERED_ERROR_FULL_MESSAGE = (
|
||||
async def test_content_filtering_raises_correct_exception(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
prompt = "some prompt that would trigger the content filtering"
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
|
||||
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
|
||||
assert test_endpoint is not None
|
||||
@@ -504,10 +504,10 @@ async def test_content_filtering_raises_correct_exception(
|
||||
async def test_content_filtering_without_response_code_raises_with_default_code(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
prompt = "some prompt that would trigger the content filtering"
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
|
||||
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
|
||||
assert test_endpoint is not None
|
||||
@@ -543,10 +543,10 @@ async def test_content_filtering_without_response_code_raises_with_default_code(
|
||||
async def test_bad_request_non_content_filter(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
prompt = "some prompt that would trigger the content filtering"
|
||||
chat_history.append(ChatMessage(text=prompt, role="user"))
|
||||
chat_history.append(Message(text=prompt, role="user"))
|
||||
|
||||
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
|
||||
assert test_endpoint is not None
|
||||
@@ -566,11 +566,11 @@ async def test_bad_request_non_content_filter(
|
||||
async def test_get_streaming(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
mock_streaming_chat_completion_response: AsyncStream[ChatCompletionChunk],
|
||||
) -> None:
|
||||
mock_create.return_value = mock_streaming_chat_completion_response
|
||||
chat_history.append(ChatMessage(text="hello world", role="user"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
async for msg in azure_chat_client.get_response(
|
||||
@@ -595,7 +595,7 @@ async def test_get_streaming(
|
||||
async def test_streaming_with_none_delta(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[ChatMessage],
|
||||
chat_history: list[Message],
|
||||
) -> None:
|
||||
"""Test streaming handles None delta from async content filtering."""
|
||||
# First chunk has None delta (simulates async filtering)
|
||||
@@ -619,7 +619,7 @@ async def test_streaming_with_none_delta(
|
||||
stream.__aiter__.return_value = [chunk_with_none_delta, chunk_with_content]
|
||||
mock_create.return_value = stream
|
||||
|
||||
chat_history.append(ChatMessage(text="hello world", role="user"))
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
|
||||
results: list[ChatResponseUpdate] = []
|
||||
@@ -653,11 +653,11 @@ def get_weather(location: str) -> str:
|
||||
async def test_azure_openai_chat_client_response() -> None:
|
||||
"""Test Azure OpenAI chat completion responses."""
|
||||
azure_chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
text="Emily and David, two passionate scientists, met during a research expedition to Antarctica. "
|
||||
"Bonded by their love for the natural world and shared curiosity, they uncovered a "
|
||||
@@ -665,7 +665,7 @@ async def test_azure_openai_chat_client_response() -> None:
|
||||
"of climate change.",
|
||||
)
|
||||
)
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
messages.append(Message(role="user", text="who are Emily and David?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = await azure_chat_client.get_response(messages=messages)
|
||||
@@ -683,10 +683,10 @@ async def test_azure_openai_chat_client_response() -> None:
|
||||
async def test_azure_openai_chat_client_response_tools() -> None:
|
||||
"""Test AzureOpenAI chat completion responses."""
|
||||
azure_chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
messages: list[Message] = []
|
||||
messages.append(Message(role="user", text="who are Emily and David?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = await azure_chat_client.get_response(
|
||||
@@ -704,11 +704,11 @@ async def test_azure_openai_chat_client_response_tools() -> None:
|
||||
async def test_azure_openai_chat_client_streaming() -> None:
|
||||
"""Test Azure OpenAI chat completion responses."""
|
||||
azure_chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages: list[Message] = []
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
Message(
|
||||
role="user",
|
||||
text="Emily and David, two passionate scientists, met during a research expedition to Antarctica. "
|
||||
"Bonded by their love for the natural world and shared curiosity, they uncovered a "
|
||||
@@ -716,7 +716,7 @@ async def test_azure_openai_chat_client_streaming() -> None:
|
||||
"of climate change.",
|
||||
)
|
||||
)
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
messages.append(Message(role="user", text="who are Emily and David?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = azure_chat_client.get_response(messages=messages, stream=True)
|
||||
@@ -739,10 +739,10 @@ async def test_azure_openai_chat_client_streaming() -> None:
|
||||
async def test_azure_openai_chat_client_streaming_tools() -> None:
|
||||
"""Test AzureOpenAI chat completion responses."""
|
||||
azure_chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
assert isinstance(azure_chat_client, SupportsChatGetResponse)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
messages: list[Message] = []
|
||||
messages.append(Message(role="user", text="who are Emily and David?"))
|
||||
|
||||
# Test that the client can be used to get a response
|
||||
response = azure_chat_client.get_response(
|
||||
@@ -765,8 +765,8 @@ async def test_azure_openai_chat_client_streaming_tools() -> None:
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_openai_chat_client_agent_basic_run():
|
||||
"""Test Azure OpenAI chat client agent basic run functionality with AzureOpenAIChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Test basic run
|
||||
response = await agent.run("Please respond with exactly: 'This is a response test.'")
|
||||
@@ -781,8 +781,8 @@ async def test_azure_openai_chat_client_agent_basic_run():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_openai_chat_client_agent_basic_run_streaming():
|
||||
"""Test Azure OpenAI chat client agent basic streaming functionality with AzureOpenAIChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Test streaming run
|
||||
full_text = ""
|
||||
@@ -799,8 +799,8 @@ async def test_azure_openai_chat_client_agent_basic_run_streaming():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_openai_chat_client_agent_thread_persistence():
|
||||
"""Test Azure OpenAI chat client agent thread persistence across runs with AzureOpenAIChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
# Create a new thread that will be reused
|
||||
@@ -827,8 +827,8 @@ async def test_azure_openai_chat_client_agent_existing_thread():
|
||||
# First conversation - capture the thread
|
||||
preserved_thread = None
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
# Start a conversation and capture the thread
|
||||
@@ -843,8 +843,8 @@ async def test_azure_openai_chat_client_agent_existing_thread():
|
||||
|
||||
# Second conversation - reuse the thread in a new agent instance
|
||||
if preserved_thread:
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
# Reuse the preserved thread
|
||||
@@ -860,8 +860,8 @@ async def test_azure_openai_chat_client_agent_existing_thread():
|
||||
async def test_azure_chat_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with Azure Chat Client."""
|
||||
|
||||
async with ChatAgent(
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
async with Agent(
|
||||
client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
) as agent:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user