mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: name changes executed (#607)
* name changes executed * updated adr to accepted * renamed openai base config * renamed openai config to mixin * added renames in user docs * reverted mcperror * fix tests * remove sse from tests
This commit is contained in:
+5
-2
@@ -1,7 +1,7 @@
|
||||
---
|
||||
status: proposed
|
||||
status: accepted
|
||||
contact: eavanvalkenburg
|
||||
date: 2025-09-03
|
||||
date: 2025-09-04
|
||||
deciders: markwallace-microsoft, dmytrostruk, peterychang, ekzhu, sphenry
|
||||
consulted: taochenosu, alliscode, moonbox3, johanste
|
||||
---
|
||||
@@ -49,6 +49,9 @@ The table below represents the majority of the naming changes discussed in issue
|
||||
| ChatFinishReason | FinishReason | accepted | More concise while still clear in context. | None |
|
||||
| AIContent | BaseContent | accepted | More accurate as it serves as the base class for all content types. | Content, too generic. |
|
||||
| AIContents | Contents | accepted | This is the annotated typing object that is the union of all concrete content types, so plural makes sense and since this is used as a type hint, the generic nature of the name is acceptable. | None |
|
||||
| AIAnnotations | Annotations | accepted | In sync with contents | None |
|
||||
| AIAnnotation | BaseAnnotation | accepted | In sync with contents | None |
|
||||
| *Mcp* & *Http* | *MCP* & *HTTP* | accepted | Acronyms should be uppercased in class names, according to PEP 8. | None |
|
||||
| `agent.run_streaming` | `agent.run_stream` | accepted | Shorter and more closely aligns with AutoGen and Semantic Kernel names for the same methods. | None |
|
||||
| `workflow.run_streaming` | `workflow.run_stream` | accepted | In sync with `agent.run_stream` and shorter and more closely aligns with AutoGen and Semantic Kernel names for the same methods. | None |
|
||||
| AgentRunResponse & AgentRunResponseUpdate | AgentResponse & AgentResponseUpdate | rejected | Rejected, because it is the response to a run invocation and AgentResponse is too generic. | None |
|
||||
@@ -1,5 +1,6 @@
|
||||
# Microsoft Agent Framework Agent Types
|
||||
|
||||
::: zone pivot="programming-language-csharp"
|
||||
The Microsoft Agent Framework provides support for several types of agents to accommodate different use cases and requirements.
|
||||
|
||||
All agents are derived from a common base class, `AIAgent`, which provides a consistent interface for all agent types. This allows for building common, agent agnostic, higher level functionality such as multi-agent orchestrations.
|
||||
@@ -20,7 +21,6 @@ These agents support a wide range of functionality out of the box:
|
||||
|
||||
To create one of these agents, simply construct a `ChatClientAgent` using the ChatClient implementation of your choice.
|
||||
|
||||
::: zone pivot="programming-language-csharp"
|
||||
|
||||
```csharp
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -30,20 +30,171 @@ var agent = new ChatClientAgent(chatClient, instructions: "You are a helpful ass
|
||||
|
||||
For examples on how to construct `ChatClientAgents` with various `IChatClient` implementations, see the [Agent setup samples](../../../dotnet/samples/AgentSetup).
|
||||
|
||||
::: zone-end
|
||||
::: zone pivot="programming-language-python"
|
||||
::: zone-end
|
||||
|
||||
## Complex custom agents
|
||||
|
||||
It is also possible to create fully custom agents, that are not just wrappers around a ChatClient.
|
||||
The agent framework provides the `AIAgent` base type, which when subclassed allows for complete control over the agent's behavior and capabilities.
|
||||
The agent framework provides the `AgentProtocol` base type, which when subclassed allows for complete control over the agent's behavior and capabilities.
|
||||
|
||||
## Remote agents
|
||||
|
||||
The agent framework provides out of the box `AIAgent` subclasses for common service hosted agent protocols,
|
||||
The agent framework provides out of the box `AgentProtocol` subclasses for common service hosted agent protocols,
|
||||
such as A2A.
|
||||
|
||||
## Pre-built agents
|
||||
|
||||
To be added.
|
||||
::: zone-end
|
||||
::: zone pivot="programming-language-python"
|
||||
|
||||
The Microsoft Agent Framework provides support for several types of agents to accommodate different use cases and requirements.
|
||||
|
||||
All agents implement a common protocol, `AgentProtocol`, which provides a consistent interface for all agent types. This allows for building common, agent agnostic, higher level functionality such as multi-agent orchestrations.
|
||||
|
||||
Let's dive into each agent type in more detail.
|
||||
|
||||
## Simple agents based on inference services
|
||||
|
||||
The agent framework makes it easy to create simple agents based on many different inference services.
|
||||
Any inference service that provides a chat client implementation can be used to build these agents.
|
||||
|
||||
These agents support a wide range of functionality out of the box:
|
||||
|
||||
1. Function calling
|
||||
1. Multi-turn conversations with local chat history management or service provided chat history management
|
||||
1. Custom service provided tools (e.g. MCP, Code Execution)
|
||||
1. Structured output
|
||||
1. Streaming responses
|
||||
|
||||
To create one of these agents, simply construct a `ChatAgent` using the chat client implementation of your choice.
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant"
|
||||
) as agent
|
||||
):
|
||||
response = await agent.run("Hello!")
|
||||
```
|
||||
|
||||
Alternatively, you can use the convenience method on the chat client:
|
||||
|
||||
```python
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async with AzureCliCredential() as credential:
|
||||
agent = FoundryChatClient(async_credential=credential).create_agent(
|
||||
instructions="You are a helpful assistant"
|
||||
)
|
||||
```
|
||||
|
||||
For detailed examples, see:
|
||||
- [Basic Foundry agent](../../../python/samples/getting_started/agents/foundry/foundry_basic.py)
|
||||
- [Foundry with explicit settings](../../../python/samples/getting_started/agents/foundry/foundry_with_explicit_settings.py)
|
||||
- [Using existing Foundry agent](../../../python/samples/getting_started/agents/foundry/foundry_with_existing_agent.py)
|
||||
|
||||
### Function Tools
|
||||
|
||||
You can provide function tools to agents for enhanced capabilities:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from pydantic import Field
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
def get_weather(location: Annotated[str, Field(description="The location to get the weather for.")]) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
return f"The weather in {location} is sunny with a high of 25°C."
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
FoundryChatClient(async_credential=credential).create_agent(
|
||||
instructions="You are a helpful weather assistant.",
|
||||
tools=get_weather
|
||||
) as agent
|
||||
):
|
||||
response = await agent.run("What's the weather in Seattle?")
|
||||
```
|
||||
|
||||
For complete examples with function tools, see:
|
||||
- [Foundry with function tools](../../../python/samples/getting_started/agents/foundry/foundry_with_function_tools.py)
|
||||
|
||||
### Streaming Responses
|
||||
|
||||
Agents support both regular and streaming responses:
|
||||
|
||||
```python
|
||||
# Regular response (wait for complete result)
|
||||
response = await agent.run("What's the weather like in Seattle?")
|
||||
print(response.text)
|
||||
|
||||
# Streaming response (get results as they are generated)
|
||||
async for chunk in agent.run_stream("What's the weather like in Portland?"):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
```
|
||||
|
||||
For streaming examples, see:
|
||||
- [Foundry streaming examples](../../../python/samples/getting_started/agents/foundry/foundry_basic.py)
|
||||
|
||||
### Code Interpreter Tools
|
||||
|
||||
Foundry agents support hosted code interpreter tools for executing Python code:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatAgent, HostedCodeInterpreterTool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant that can execute Python code.",
|
||||
tools=HostedCodeInterpreterTool()
|
||||
) as agent
|
||||
):
|
||||
response = await agent.run("Calculate the factorial of 100 using Python")
|
||||
```
|
||||
|
||||
For code interpreter examples, see:
|
||||
- [Foundry with code interpreter](../../../python/samples/getting_started/agents/foundry/foundry_with_code_interpreter.py)
|
||||
|
||||
## Custom agents
|
||||
|
||||
It is also possible to create fully custom agents that are not just wrappers around a chat client.
|
||||
Agent Framework provides the `AgentProtocol` protocol and `BaseAgent` base class, which when implemented/subclassed allows for complete control over the agent's behavior and capabilities.
|
||||
|
||||
```python
|
||||
from agent_framework import BaseAgent, AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
|
||||
from collections.abc import AsyncIterable
|
||||
|
||||
class CustomAgent(BaseAgent):
|
||||
async def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentRunResponse:
|
||||
# Custom agent implementation
|
||||
pass
|
||||
|
||||
def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterable[AgentRunResponseUpdate]:
|
||||
# Custom streaming implementation
|
||||
pass
|
||||
```
|
||||
|
||||
::: zone-end
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ The package follows a flat import structure:
|
||||
|
||||
- **Core**: Import directly from `agent_framework`
|
||||
```python
|
||||
from agent_framework import ChatClientAgent, ai_function
|
||||
from agent_framework import ChatAgent, ai_function
|
||||
```
|
||||
|
||||
- **Components**: Import from `agent_framework.<component>`
|
||||
@@ -319,7 +319,7 @@ uv run poe docs-check
|
||||
Use Google-style docstrings for all public APIs:
|
||||
|
||||
```python
|
||||
def create_agent(name: str, chat_client: ChatClient) -> Agent:
|
||||
def create_agent(name: str, chat_client: ChatClientProtocol) -> Agent:
|
||||
"""Create a new agent with the specified configuration.
|
||||
|
||||
Args:
|
||||
|
||||
+10
-10
@@ -53,11 +53,11 @@ Create agents and invoke them directly:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
async def main():
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="""
|
||||
1) A robot may not injure a human being...
|
||||
@@ -81,15 +81,15 @@ You can use the chat client classes directly for advanced workflows:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework import ChatMessage, ChatRole
|
||||
|
||||
async def main():
|
||||
client = OpenAIChatClient()
|
||||
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.SYSTEM, text="You are a helpful assistant."),
|
||||
ChatMessage(role=ChatRole.USER, text="Write a haiku about Agent Framework.")
|
||||
ChatMessage(role="system", text="You are a helpful assistant."),
|
||||
ChatMessage(role="user", text="Write a haiku about Agent Framework.")
|
||||
]
|
||||
|
||||
response = await client.get_response(messages)
|
||||
@@ -115,7 +115,7 @@ import asyncio
|
||||
from typing import Annotated
|
||||
from random import randint
|
||||
from pydantic import Field
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ def get_menu_specials() -> str:
|
||||
|
||||
|
||||
async def main():
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and restaurant information.",
|
||||
tools=[get_weather, get_menu_specials]
|
||||
@@ -164,19 +164,19 @@ Coordinate multiple agents to collaborate on complex tasks using orchestration p
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
async def main():
|
||||
# Create specialized agents
|
||||
writer = ChatClientAgent(
|
||||
writer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
name="Writer",
|
||||
instructions="You are a creative content writer. Generate and refine slogans based on feedback."
|
||||
)
|
||||
|
||||
reviewer = ChatClientAgent(
|
||||
reviewer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
name="Reviewer",
|
||||
instructions="You are a critical reviewer. Provide detailed feedback on proposed slogans."
|
||||
|
||||
@@ -13,7 +13,7 @@ from agent_framework import (
|
||||
TextContent,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from agent_framework.openai._chat_client import OpenAIChatClientBase
|
||||
from agent_framework.openai._chat_client import OpenAIBaseChatClient
|
||||
from azure.core.credentials import TokenCredential
|
||||
from openai.lib.azure import AsyncAzureADTokenProvider, AsyncAzureOpenAI
|
||||
from openai.types.chat.chat_completion import Choice
|
||||
@@ -22,7 +22,7 @@ from pydantic import SecretStr, ValidationError
|
||||
from pydantic.networks import AnyUrl
|
||||
|
||||
from ._shared import (
|
||||
AzureOpenAIConfigBase,
|
||||
AzureOpenAIConfigMixin,
|
||||
AzureOpenAISettings,
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ TChatResponse = TypeVar("TChatResponse", ChatResponse, ChatResponseUpdate)
|
||||
TAzureChatClient = TypeVar("TAzureChatClient", bound="AzureChatClient")
|
||||
|
||||
|
||||
class AzureChatClient(AzureOpenAIConfigBase, OpenAIChatClientBase):
|
||||
class AzureChatClient(AzureOpenAIConfigMixin, OpenAIBaseChatClient):
|
||||
"""Azure Chat completion class."""
|
||||
|
||||
def __init__(
|
||||
@@ -143,7 +143,7 @@ class AzureChatClient(AzureOpenAIConfigBase, OpenAIChatClientBase):
|
||||
def _parse_text_from_choice(self, choice: Choice | ChunkChoice) -> TextContent | None:
|
||||
"""Parse the choice into a TextContent object.
|
||||
|
||||
Overwritten from OpenAIChatClientBase to deal with Azure On Your Data function.
|
||||
Overwritten from OpenAIBaseChatClient to deal with Azure On Your Data function.
|
||||
For docs see:
|
||||
https://learn.microsoft.com/en-us/azure/ai-foundry/openai/references/on-your-data?tabs=python#context
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,7 @@ from urllib.parse import urljoin
|
||||
|
||||
from agent_framework import use_tool_calling
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from agent_framework.openai._responses_client import OpenAIResponsesClientBase
|
||||
from agent_framework.openai._responses_client import OpenAIBaseResponsesClient
|
||||
from agent_framework.telemetry import use_telemetry
|
||||
from azure.core.credentials import TokenCredential
|
||||
from openai.lib.azure import AsyncAzureADTokenProvider, AsyncAzureOpenAI
|
||||
@@ -14,7 +14,7 @@ from pydantic import SecretStr, ValidationError
|
||||
from pydantic.networks import AnyUrl
|
||||
|
||||
from ._shared import (
|
||||
AzureOpenAIConfigBase,
|
||||
AzureOpenAIConfigMixin,
|
||||
AzureOpenAISettings,
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ TAzureResponsesClient = TypeVar("TAzureResponsesClient", bound="AzureResponsesCl
|
||||
|
||||
@use_telemetry
|
||||
@use_tool_calling
|
||||
class AzureResponsesClient(AzureOpenAIConfigBase, OpenAIResponsesClientBase):
|
||||
class AzureResponsesClient(AzureOpenAIConfigMixin, OpenAIBaseResponsesClient):
|
||||
"""Azure Responses completion class."""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -6,9 +6,9 @@ from collections.abc import Awaitable, Callable, Mapping
|
||||
from copy import copy
|
||||
from typing import Any, ClassVar, Final
|
||||
|
||||
from agent_framework._pydantic import AFBaseSettings, HttpsUrl
|
||||
from agent_framework._pydantic import AFBaseSettings, HTTPsUrl
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from agent_framework.openai._shared import OpenAIHandler
|
||||
from agent_framework.openai._shared import OpenAIBase
|
||||
from agent_framework.telemetry import USER_AGENT_KEY
|
||||
from azure.core.credentials import TokenCredential
|
||||
from openai.lib.azure import AsyncAzureOpenAI
|
||||
@@ -126,8 +126,8 @@ class AzureOpenAISettings(AFBaseSettings):
|
||||
audio_to_text_deployment_name: str | None = None
|
||||
text_to_audio_deployment_name: str | None = None
|
||||
realtime_deployment_name: str | None = None
|
||||
endpoint: HttpsUrl | None = None
|
||||
base_url: HttpsUrl | None = None
|
||||
endpoint: HTTPsUrl | None = None
|
||||
base_url: HTTPsUrl | None = None
|
||||
api_key: SecretStr | None = None
|
||||
api_version: str | None = None
|
||||
token_endpoint: str | None = None
|
||||
@@ -165,7 +165,7 @@ class AzureOpenAISettings(AFBaseSettings):
|
||||
return self
|
||||
|
||||
|
||||
class AzureOpenAIConfigBase(OpenAIHandler):
|
||||
class AzureOpenAIConfigMixin(OpenAIBase):
|
||||
"""Internal class for configuring a connection to an Azure OpenAI service."""
|
||||
|
||||
MODEL_PROVIDER_NAME: ClassVar[str] = "azure_openai" # type: ignore[reportIncompatibleVariableOverride, misc]
|
||||
@@ -174,8 +174,8 @@ class AzureOpenAIConfigBase(OpenAIHandler):
|
||||
def __init__(
|
||||
self,
|
||||
deployment_name: str,
|
||||
endpoint: HttpsUrl | None = None,
|
||||
base_url: HttpsUrl | None = None,
|
||||
endpoint: HTTPsUrl | None = None,
|
||||
base_url: HTTPsUrl | None = None,
|
||||
api_version: str = DEFAULT_AZURE_API_VERSION,
|
||||
api_key: str | None = None,
|
||||
ad_token: str | None = None,
|
||||
@@ -190,7 +190,7 @@ class AzureOpenAIConfigBase(OpenAIHandler):
|
||||
"""Internal class for configuring a connection to an Azure OpenAI service.
|
||||
|
||||
The `validate_call` decorator is used with a configuration that allows arbitrary types.
|
||||
This is necessary for types like `HttpsUrl` and `OpenAIModelTypes`.
|
||||
This is necessary for types like `HTTPsUrl` and `OpenAIModelTypes`.
|
||||
|
||||
Args:
|
||||
deployment_name: Name of the deployment.
|
||||
|
||||
@@ -9,8 +9,8 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
@@ -93,7 +93,7 @@ def test_azure_assistants_client_init_with_client(mock_async_azure_openai: Magic
|
||||
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, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_azure_assistants_client_init_auto_create_client(
|
||||
@@ -147,7 +147,7 @@ def test_azure_assistants_client_init_with_default_headers(azure_openai_unit_tes
|
||||
)
|
||||
|
||||
assert chat_client.ai_model_id == "test_chat_deployment"
|
||||
assert isinstance(chat_client, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
@@ -267,7 +267,7 @@ def get_weather(
|
||||
async def test_azure_assistants_client_get_response() -> None:
|
||||
"""Test Azure Assistants Client response."""
|
||||
async with AzureAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClient)
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -291,7 +291,7 @@ 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 AzureAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClient)
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -312,7 +312,7 @@ 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 AzureAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClient)
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -342,7 +342,7 @@ 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 AzureAssistantsClient(credential=AzureCliCredential()) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClient)
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -378,7 +378,7 @@ async def test_azure_assistants_client_with_existing_assistant() -> None:
|
||||
async with AzureAssistantsClient(
|
||||
assistant_id=assistant_id, credential=AzureCliCredential()
|
||||
) as azure_assistants_client:
|
||||
assert isinstance(azure_assistants_client, ChatClient)
|
||||
assert isinstance(azure_assistants_client, ChatClientProtocol)
|
||||
assert azure_assistants_client.assistant_id == assistant_id
|
||||
|
||||
messages = [ChatMessage(role="user", text="What can you do?")]
|
||||
@@ -393,8 +393,8 @@ async def test_azure_assistants_client_with_existing_assistant() -> None:
|
||||
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_basic_run():
|
||||
"""Test ChatClientAgent basic run functionality with AzureAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic run functionality with AzureAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run a simple query
|
||||
@@ -409,13 +409,13 @@ async def test_azure_assistants_agent_basic_run():
|
||||
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_basic_run_streaming():
|
||||
"""Test ChatClientAgent basic streaming functionality with AzureAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic streaming functionality with AzureAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run streaming query
|
||||
full_message: str = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert chunk is not None
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
@@ -428,8 +428,8 @@ async def test_azure_assistants_agent_basic_run_streaming():
|
||||
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_thread_persistence():
|
||||
"""Test ChatClientAgent thread persistence across runs with AzureAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent thread persistence across runs with AzureAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -456,11 +456,11 @@ async def test_azure_assistants_agent_thread_persistence():
|
||||
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_existing_thread_id():
|
||||
"""Test ChatClientAgent with existing thread ID to continue conversations across agent instances."""
|
||||
"""Test ChatAgent 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 ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
@@ -480,7 +480,7 @@ async def test_azure_assistants_agent_existing_thread_id():
|
||||
|
||||
# Now continue with the same thread ID in a new agent instance
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(thread_id=existing_thread_id, credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
@@ -500,9 +500,9 @@ async def test_azure_assistants_agent_existing_thread_id():
|
||||
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_assistants_agent_code_interpreter():
|
||||
"""Test ChatClientAgent with code interpreter through AzureAssistantsClient."""
|
||||
"""Test ChatAgent with code interpreter through AzureAssistantsClient."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can write and execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
@@ -521,7 +521,7 @@ 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 ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -556,7 +556,7 @@ async def test_azure_assistants_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -10,9 +10,9 @@ import pytest
|
||||
from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatClientBase,
|
||||
BaseChatClient,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
@@ -55,7 +55,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.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientBase)
|
||||
assert isinstance(azure_chat_client, BaseChatClient)
|
||||
|
||||
|
||||
def test_init_client(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -78,7 +78,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.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientBase)
|
||||
assert isinstance(azure_chat_client, BaseChatClient)
|
||||
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
|
||||
@@ -91,7 +91,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.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_chat_client, ChatClientBase)
|
||||
assert isinstance(azure_chat_client, BaseChatClient)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]], indirect=True)
|
||||
@@ -614,7 +614,7 @@ def get_weather(location: str) -> str:
|
||||
async def test_azure_openai_chat_client_response() -> None:
|
||||
"""Test Azure OpenAI chat completion responses."""
|
||||
azure_chat_client = AzureChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClient)
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -643,7 +643,7 @@ 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 = AzureChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClient)
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
@@ -664,7 +664,7 @@ 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 = AzureChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClient)
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -698,7 +698,7 @@ 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 = AzureChatClient(credential=AzureCliCredential())
|
||||
assert isinstance(azure_chat_client, ChatClient)
|
||||
assert isinstance(azure_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
@@ -723,7 +723,7 @@ 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 AzureChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Test basic run
|
||||
@@ -738,12 +738,12 @@ 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 AzureChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Test streaming run
|
||||
full_text = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
full_text += chunk.text
|
||||
@@ -755,7 +755,7 @@ 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 AzureChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -782,7 +782,7 @@ async def test_azure_openai_chat_client_agent_existing_thread():
|
||||
# First conversation - capture the thread
|
||||
preserved_thread = None
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
@@ -798,7 +798,7 @@ 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 ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
@@ -814,7 +814,7 @@ 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 ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -849,7 +849,7 @@ async def test_azure_chat_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -8,8 +8,8 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
@@ -50,7 +50,7 @@ def test_init(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
azure_responses_client = AzureResponsesClient()
|
||||
|
||||
assert azure_responses_client.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_validation_fail() -> None:
|
||||
@@ -65,7 +65,7 @@ def test_init_ai_model_id_constructor(azure_openai_unit_test_env: dict[str, str]
|
||||
azure_responses_client = AzureResponsesClient(deployment_name=ai_model_id)
|
||||
|
||||
assert azure_responses_client.ai_model_id == ai_model_id
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_with_default_header(azure_openai_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -77,7 +77,7 @@ def test_init_with_default_header(azure_openai_unit_test_env: dict[str, str]) ->
|
||||
)
|
||||
|
||||
assert azure_responses_client.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"]
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
@@ -119,7 +119,7 @@ async def test_azure_responses_client_response() -> None:
|
||||
"""Test azure responses client responses."""
|
||||
azure_responses_client = AzureResponsesClient(credential=AzureCliCredential())
|
||||
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -162,7 +162,7 @@ async def test_azure_responses_client_response_tools() -> None:
|
||||
"""Test azure responses client tools."""
|
||||
azure_responses_client = AzureResponsesClient(credential=AzureCliCredential())
|
||||
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What is the weather in New York?"))
|
||||
@@ -201,7 +201,7 @@ async def test_azure_responses_client_streaming() -> None:
|
||||
"""Test Azure azure responses client streaming responses."""
|
||||
azure_responses_client = AzureResponsesClient(credential=AzureCliCredential())
|
||||
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -251,7 +251,7 @@ async def test_azure_responses_client_streaming_tools() -> None:
|
||||
"""Test azure responses client streaming tools."""
|
||||
azure_responses_client = AzureResponsesClient(credential=AzureCliCredential())
|
||||
|
||||
assert isinstance(azure_responses_client, ChatClient)
|
||||
assert isinstance(azure_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = [ChatMessage(role="user", text="What is the weather in Seattle?")]
|
||||
|
||||
@@ -312,12 +312,12 @@ async def test_azure_responses_client_agent_basic_run():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_responses_client_agent_basic_run_streaming():
|
||||
"""Test Azure Responses Client agent basic streaming functionality with AzureResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Test streaming run
|
||||
full_text = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
full_text += chunk.text
|
||||
@@ -329,7 +329,7 @@ async def test_azure_responses_client_agent_basic_run_streaming():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_responses_client_agent_thread_persistence():
|
||||
"""Test Azure Responses Client agent thread persistence across runs with AzureResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -352,7 +352,7 @@ async def test_azure_responses_client_agent_thread_persistence():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_responses_client_agent_thread_storage_with_store_true():
|
||||
"""Test Azure Responses Client agent with store=True to verify service_thread_id is returned."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
@@ -386,7 +386,7 @@ async def test_azure_responses_client_agent_existing_thread():
|
||||
# First conversation - capture the thread
|
||||
preserved_thread = None
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
@@ -402,7 +402,7 @@ async def test_azure_responses_client_agent_existing_thread():
|
||||
|
||||
# Second conversation - reuse the thread in a new agent instance
|
||||
if preserved_thread:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
@@ -417,7 +417,7 @@ async def test_azure_responses_client_agent_existing_thread():
|
||||
@skip_if_azure_integration_tests_disabled
|
||||
async def test_azure_responses_client_agent_hosted_code_interpreter_tool():
|
||||
"""Test Azure Responses Client agent with HostedCodeInterpreterTool through AzureResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
@@ -439,7 +439,7 @@ async def test_azure_responses_client_agent_hosted_code_interpreter_tool():
|
||||
async def test_azure_responses_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with Azure Responses Client."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -474,7 +474,7 @@ async def test_azure_responses_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -7,19 +7,19 @@ from collections.abc import AsyncIterable, MutableMapping, MutableSequence
|
||||
from typing import Any, ClassVar, TypeVar
|
||||
|
||||
from agent_framework import (
|
||||
AIContents,
|
||||
AIFunction,
|
||||
ChatClientBase,
|
||||
BaseChatClient,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
ChatToolMode,
|
||||
Contents,
|
||||
DataContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
Role,
|
||||
TextContent,
|
||||
UriContent,
|
||||
UsageContent,
|
||||
@@ -98,7 +98,7 @@ TFoundryChatClient = TypeVar("TFoundryChatClient", bound="FoundryChatClient")
|
||||
|
||||
@use_telemetry
|
||||
@use_tool_calling
|
||||
class FoundryChatClient(ChatClientBase):
|
||||
class FoundryChatClient(BaseChatClient):
|
||||
"""Azure AI Foundry Chat client."""
|
||||
|
||||
MODEL_PROVIDER_NAME: ClassVar[str] = "azure_ai_foundry" # type: ignore[reportIncompatibleVariableOverride, misc]
|
||||
@@ -385,12 +385,12 @@ class FoundryChatClient(ChatClientBase):
|
||||
message_id=response_id,
|
||||
raw_representation=event_data,
|
||||
response_id=response_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
elif event_type == AgentStreamEvent.THREAD_RUN_STEP_CREATED and isinstance(event_data, RunStep):
|
||||
response_id = event_data.run_id
|
||||
elif event_type == AgentStreamEvent.THREAD_MESSAGE_DELTA and isinstance(event_data, MessageDeltaChunk):
|
||||
role = ChatRole.USER if event_data.delta.role == MessageRole.USER else ChatRole.ASSISTANT
|
||||
role = Role.USER if event_data.delta.role == MessageRole.USER else Role.ASSISTANT
|
||||
yield ChatResponseUpdate(
|
||||
role=role,
|
||||
text=event_data.text,
|
||||
@@ -407,7 +407,7 @@ class FoundryChatClient(ChatClientBase):
|
||||
contents = self._create_function_call_contents(event_data, response_id)
|
||||
if contents:
|
||||
yield ChatResponseUpdate(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
contents=contents,
|
||||
conversation_id=thread_id,
|
||||
message_id=response_id,
|
||||
@@ -427,7 +427,7 @@ class FoundryChatClient(ChatClientBase):
|
||||
)
|
||||
)
|
||||
yield ChatResponseUpdate(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
contents=[usage_content],
|
||||
conversation_id=thread_id,
|
||||
message_id=response_id,
|
||||
@@ -447,12 +447,12 @@ class FoundryChatClient(ChatClientBase):
|
||||
message_id=response_id,
|
||||
raw_representation=event_data, # type: ignore
|
||||
response_id=response_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
|
||||
def _create_function_call_contents(self, event_data: ThreadRun, response_id: str | None) -> list[AIContents]:
|
||||
def _create_function_call_contents(self, event_data: ThreadRun, response_id: str | None) -> list[Contents]:
|
||||
"""Create function call contents from a tool action event."""
|
||||
contents: list[AIContents] = []
|
||||
contents: list[Contents] = []
|
||||
|
||||
if isinstance(event_data.required_action, SubmitToolOutputsAction):
|
||||
for tool_call in event_data.required_action.submit_tool_outputs.tool_calls:
|
||||
@@ -563,7 +563,7 @@ class FoundryChatClient(ChatClientBase):
|
||||
additional_messages = []
|
||||
additional_messages.append(
|
||||
ThreadMessageOptions(
|
||||
role=MessageRole.AGENT if chat_message.role == ChatRole.ASSISTANT else MessageRole.USER,
|
||||
role=MessageRole.AGENT if chat_message.role == Role.ASSISTANT else MessageRole.USER,
|
||||
content=message_contents,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,16 +9,16 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
Role,
|
||||
TextContent,
|
||||
UriContent,
|
||||
ai_function,
|
||||
@@ -98,7 +98,7 @@ def test_foundry_chat_client_init_with_client(mock_ai_project_client: MagicMock)
|
||||
assert chat_client.agent_id == "existing-agent-id"
|
||||
assert chat_client.thread_id == "test-thread-id"
|
||||
assert not chat_client._should_delete_agent # type: ignore
|
||||
assert isinstance(chat_client, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_foundry_chat_client_init_auto_create_client(
|
||||
@@ -252,9 +252,9 @@ async def test_foundry_chat_client_tool_results_without_thread_error_via_public_
|
||||
|
||||
# Create messages with tool results but no thread/conversation ID
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.USER, text="Hello"),
|
||||
ChatMessage(role=Role.USER, text="Hello"),
|
||||
ChatMessage(
|
||||
role=ChatRole.TOOL, contents=[FunctionResultContent(call_id='["run_123", "call_456"]', result="Result")]
|
||||
role=Role.TOOL, contents=[FunctionResultContent(call_id='["run_123", "call_456"]', result="Result")]
|
||||
),
|
||||
]
|
||||
|
||||
@@ -283,7 +283,7 @@ async def test_foundry_chat_client_thread_management_through_public_api(mock_ai_
|
||||
mock_stream.__aenter__ = AsyncMock(return_value=empty_async_iter())
|
||||
mock_stream.__aexit__ = AsyncMock(return_value=None)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
# Call without existing thread - should create new one
|
||||
response = chat_client.get_streaming_response(messages)
|
||||
@@ -379,7 +379,7 @@ def test_foundry_chat_client_create_run_options_basic(mock_ai_project_client: Ma
|
||||
"""Test _create_run_options with basic ChatOptions."""
|
||||
chat_client = create_test_foundry_chat_client(mock_ai_project_client)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
chat_options = ChatOptions(max_tokens=100, temperature=0.7)
|
||||
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -392,7 +392,7 @@ def test_foundry_chat_client_create_run_options_no_chat_options(mock_ai_project_
|
||||
"""Test _create_run_options with no ChatOptions."""
|
||||
chat_client = create_test_foundry_chat_client(mock_ai_project_client)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
run_options, tool_results = chat_client._create_run_options(messages, None) # type: ignore
|
||||
|
||||
@@ -406,7 +406,7 @@ def test_foundry_chat_client_create_run_options_with_image_content(mock_ai_proje
|
||||
chat_client = create_test_foundry_chat_client(mock_ai_project_client, agent_id="test-agent")
|
||||
|
||||
image_content = UriContent(uri="https://example.com/image.jpg", media_type="image/jpeg")
|
||||
messages = [ChatMessage(role=ChatRole.USER, contents=[image_content])]
|
||||
messages = [ChatMessage(role=Role.USER, contents=[image_content])]
|
||||
|
||||
run_options, _ = chat_client._create_run_options(messages, None) # type: ignore
|
||||
|
||||
@@ -502,8 +502,8 @@ def test_foundry_chat_client_create_run_options_with_messages(mock_ai_project_cl
|
||||
|
||||
# Test with system message (becomes instruction)
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.SYSTEM, text="You are a helpful assistant"),
|
||||
ChatMessage(role=ChatRole.USER, text="Hello"),
|
||||
ChatMessage(role=Role.SYSTEM, text="You are a helpful assistant"),
|
||||
ChatMessage(role=Role.USER, text="Hello"),
|
||||
]
|
||||
|
||||
run_options, _ = chat_client._create_run_options(messages, None) # type: ignore
|
||||
@@ -517,17 +517,17 @@ def test_foundry_chat_client_create_run_options_with_messages(mock_ai_project_cl
|
||||
async def test_foundry_chat_client_inner_get_response(mock_ai_project_client: MagicMock) -> None:
|
||||
"""Test _inner_get_response method."""
|
||||
chat_client = create_test_foundry_chat_client(mock_ai_project_client, agent_id="test-agent")
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
async def mock_streaming_response():
|
||||
yield ChatResponseUpdate(role=ChatRole.ASSISTANT, text="Hello back")
|
||||
yield ChatResponseUpdate(role=Role.ASSISTANT, text="Hello back")
|
||||
|
||||
with (
|
||||
patch.object(chat_client, "_inner_get_streaming_response", return_value=mock_streaming_response()),
|
||||
patch("agent_framework.ChatResponse.from_chat_response_generator") as mock_from_generator,
|
||||
):
|
||||
mock_response = ChatResponse(role=ChatRole.ASSISTANT, text="Hello back")
|
||||
mock_response = ChatResponse(role=Role.ASSISTANT, text="Hello back")
|
||||
mock_from_generator.return_value = mock_response
|
||||
|
||||
result = await chat_client._inner_get_response(messages=messages, chat_options=chat_options) # type: ignore
|
||||
@@ -667,7 +667,7 @@ def get_weather(
|
||||
async def test_foundry_chat_client_get_response() -> None:
|
||||
"""Test Foundry Chat Client response."""
|
||||
async with FoundryChatClient(async_credential=AzureCliCredential()) as foundry_chat_client:
|
||||
assert isinstance(foundry_chat_client, ChatClient)
|
||||
assert isinstance(foundry_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -691,7 +691,7 @@ async def test_foundry_chat_client_get_response() -> None:
|
||||
async def test_foundry_chat_client_get_response_tools() -> None:
|
||||
"""Test Foundry Chat Client response with tools."""
|
||||
async with FoundryChatClient(async_credential=AzureCliCredential()) as foundry_chat_client:
|
||||
assert isinstance(foundry_chat_client, ChatClient)
|
||||
assert isinstance(foundry_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -712,7 +712,7 @@ async def test_foundry_chat_client_get_response_tools() -> None:
|
||||
async def test_foundry_chat_client_streaming() -> None:
|
||||
"""Test Foundry Chat Client streaming response."""
|
||||
async with FoundryChatClient(async_credential=AzureCliCredential()) as foundry_chat_client:
|
||||
assert isinstance(foundry_chat_client, ChatClient)
|
||||
assert isinstance(foundry_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -742,7 +742,7 @@ async def test_foundry_chat_client_streaming() -> None:
|
||||
async def test_foundry_chat_client_streaming_tools() -> None:
|
||||
"""Test Foundry Chat Client streaming response with tools."""
|
||||
async with FoundryChatClient(async_credential=AzureCliCredential()) as foundry_chat_client:
|
||||
assert isinstance(foundry_chat_client, ChatClient)
|
||||
assert isinstance(foundry_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -766,8 +766,8 @@ async def test_foundry_chat_client_streaming_tools() -> None:
|
||||
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_basic_run() -> None:
|
||||
"""Test ChatClientAgent basic run functionality with FoundryChatClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic run functionality with FoundryChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run a simple query
|
||||
@@ -782,13 +782,13 @@ async def test_foundry_chat_client_agent_basic_run() -> None:
|
||||
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_basic_run_streaming() -> None:
|
||||
"""Test ChatClientAgent basic streaming functionality with FoundryChatClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic streaming functionality with FoundryChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
) as agent:
|
||||
# Run streaming query
|
||||
full_message: str = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert chunk is not None
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
@@ -801,8 +801,8 @@ async def test_foundry_chat_client_agent_basic_run_streaming() -> None:
|
||||
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_thread_persistence() -> None:
|
||||
"""Test ChatClientAgent thread persistence across runs with FoundryChatClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent thread persistence across runs with FoundryChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -826,8 +826,8 @@ async def test_foundry_chat_client_agent_thread_persistence() -> None:
|
||||
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_existing_thread_id() -> None:
|
||||
"""Test ChatClientAgent existing thread ID functionality with FoundryChatClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent existing thread ID functionality with FoundryChatClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
@@ -844,7 +844,7 @@ async def test_foundry_chat_client_agent_existing_thread_id() -> None:
|
||||
assert existing_thread_id is not None
|
||||
|
||||
# Now continue with the same thread ID in a new agent instance
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(thread_id=existing_thread_id, async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
@@ -863,9 +863,9 @@ async def test_foundry_chat_client_agent_existing_thread_id() -> None:
|
||||
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_code_interpreter():
|
||||
"""Test ChatClientAgent with code interpreter through FoundryChatClient."""
|
||||
"""Test ChatAgent with code interpreter through FoundryChatClient."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can write and execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
@@ -883,7 +883,7 @@ async def test_foundry_chat_client_agent_code_interpreter():
|
||||
@skip_if_foundry_integration_tests_disabled
|
||||
async def test_foundry_chat_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with FoundryChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather],
|
||||
@@ -918,7 +918,7 @@ async def test_foundry_chat_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 25°C."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -65,11 +65,11 @@ Create agents and invoke them directly:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
async def main():
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="""
|
||||
1) A robot may not injure a human being...
|
||||
@@ -94,14 +94,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, ChatRole
|
||||
from agent_framework import ChatMessage, Role
|
||||
|
||||
async def main():
|
||||
client = OpenAIChatClient()
|
||||
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.SYSTEM, text="You are a helpful assistant."),
|
||||
ChatMessage(role=ChatRole.USER, text="Write a haiku about Agent Framework.")
|
||||
ChatMessage(role=Role.SYSTEM, text="You are a helpful assistant."),
|
||||
ChatMessage(role=Role.USER, text="Write a haiku about Agent Framework.")
|
||||
]
|
||||
|
||||
response = await client.get_response(messages)
|
||||
@@ -127,7 +127,7 @@ import asyncio
|
||||
from typing import Annotated
|
||||
from random import randint
|
||||
from pydantic import Field
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ def get_menu_specials() -> str:
|
||||
|
||||
|
||||
async def main():
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and restaurant information.",
|
||||
tools=[get_weather, get_menu_specials]
|
||||
@@ -173,19 +173,19 @@ Coordinate multiple agents to collaborate on complex tasks using orchestration p
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
async def main():
|
||||
# Create specialized agents
|
||||
writer = ChatClientAgent(
|
||||
writer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
name="Writer",
|
||||
instructions="You are a creative content writer. Generate and refine slogans based on feedback."
|
||||
)
|
||||
|
||||
reviewer = ChatClientAgent(
|
||||
reviewer = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
name="Reviewer",
|
||||
instructions="You are a critical reviewer. Provide detailed feedback on proposed slogans."
|
||||
|
||||
@@ -9,11 +9,11 @@ from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel, Field, PrivateAttr
|
||||
|
||||
from ._clients import ChatClient
|
||||
from ._mcp import McpTool
|
||||
from ._clients import ChatClientProtocol
|
||||
from ._mcp import MCPTool
|
||||
from ._pydantic import AFBaseModel
|
||||
from ._threads import AgentThread, ChatMessageStore, deserialize_thread_state, thread_on_new_messages
|
||||
from ._tools import AITool
|
||||
from ._tools import ToolProtocol
|
||||
from ._types import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
@@ -21,8 +21,8 @@ from ._types import (
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
ChatToolMode,
|
||||
Role,
|
||||
)
|
||||
from .exceptions import AgentExecutionException
|
||||
from .telemetry import use_agent_telemetry
|
||||
@@ -34,14 +34,14 @@ else:
|
||||
|
||||
TThreadType = TypeVar("TThreadType", bound="AgentThread")
|
||||
|
||||
__all__ = ["AIAgent", "AgentBase", "ChatClientAgent"]
|
||||
__all__ = ["AgentProtocol", "BaseAgent", "ChatAgent"]
|
||||
|
||||
|
||||
# region Agent Protocol
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AIAgent(Protocol):
|
||||
class AgentProtocol(Protocol):
|
||||
"""A protocol for an agent that can be invoked."""
|
||||
|
||||
@property
|
||||
@@ -93,7 +93,7 @@ class AIAgent(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def run_streaming(
|
||||
def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
@@ -122,10 +122,10 @@ class AIAgent(Protocol):
|
||||
...
|
||||
|
||||
|
||||
# region AgentBase
|
||||
# region BaseAgent
|
||||
|
||||
|
||||
class AgentBase(AFBaseModel):
|
||||
class BaseAgent(AFBaseModel):
|
||||
"""Base class for all Agent Framework agents.
|
||||
|
||||
Attributes:
|
||||
@@ -167,24 +167,24 @@ class AgentBase(AFBaseModel):
|
||||
return thread
|
||||
|
||||
|
||||
# region ChatClientAgent
|
||||
# region ChatAgent
|
||||
|
||||
|
||||
@use_agent_telemetry
|
||||
class ChatClientAgent(AgentBase):
|
||||
class ChatAgent(BaseAgent):
|
||||
"""A Chat Client Agent."""
|
||||
|
||||
AGENT_SYSTEM_NAME: ClassVar[str] = "microsoft.agent_framework"
|
||||
chat_client: ChatClient
|
||||
chat_client: ChatClientProtocol
|
||||
instructions: str | None = None
|
||||
chat_options: ChatOptions
|
||||
chat_message_store_factory: Callable[[], ChatMessageStore] | None = None
|
||||
_local_mcp_tools: list[McpTool] = PrivateAttr(default_factory=list) # type: ignore[reportUnknownVariableType]
|
||||
_local_mcp_tools: list[MCPTool] = PrivateAttr(default_factory=list) # type: ignore[reportUnknownVariableType]
|
||||
_async_exit_stack: AsyncExitStack = PrivateAttr(default_factory=AsyncExitStack)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_client: ChatClient,
|
||||
chat_client: ChatClientProtocol,
|
||||
instructions: str | None = None,
|
||||
*,
|
||||
id: str | None = None,
|
||||
@@ -202,10 +202,10 @@ class ChatClientAgent(AgentBase):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -213,7 +213,7 @@ class ChatClientAgent(AgentBase):
|
||||
chat_message_store_factory: Callable[[], ChatMessageStore] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Create a ChatClientAgent.
|
||||
"""Create a ChatAgent.
|
||||
|
||||
Remarks:
|
||||
The set of attributes from frequency_penalty to additional_properties are used to
|
||||
@@ -253,8 +253,8 @@ class ChatClientAgent(AgentBase):
|
||||
# We ignore the MCP Servers here and store them separately,
|
||||
# we add their functions to the tools list at runtime
|
||||
normalized_tools = [] if tools is None else tools if isinstance(tools, list) else [tools]
|
||||
local_mcp_tools = [tool for tool in normalized_tools if isinstance(tool, McpTool)]
|
||||
final_tools = [tool for tool in normalized_tools if not isinstance(tool, McpTool)]
|
||||
local_mcp_tools = [tool for tool in normalized_tools if isinstance(tool, MCPTool)]
|
||||
final_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)]
|
||||
args: dict[str, Any] = {
|
||||
"chat_client": chat_client,
|
||||
"chat_message_store_factory": chat_message_store_factory,
|
||||
@@ -337,8 +337,8 @@ class ChatClientAgent(AgentBase):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = None,
|
||||
tools: AITool
|
||||
| list[AITool]
|
||||
tools: ToolProtocol
|
||||
| list[ToolProtocol]
|
||||
| Callable[..., Any]
|
||||
| list[Callable[..., Any]]
|
||||
| MutableMapping[str, Any]
|
||||
@@ -384,11 +384,11 @@ class ChatClientAgent(AgentBase):
|
||||
agent_name = self._get_agent_name()
|
||||
|
||||
# Resolve final tool list (runtime provided tools + local MCP server tools)
|
||||
final_tools: list[AITool | Callable[..., Any] | dict[str, Any]] = []
|
||||
final_tools: list[ToolProtocol | Callable[..., Any] | dict[str, Any]] = []
|
||||
# Normalize tools argument to a list without mutating the original parameter
|
||||
normalized_tools = [] if tools is None else tools if isinstance(tools, list) else [tools]
|
||||
for tool in normalized_tools:
|
||||
if isinstance(tool, McpTool):
|
||||
if isinstance(tool, MCPTool):
|
||||
final_tools.extend(tool.functions) # type: ignore
|
||||
else:
|
||||
final_tools.append(tool) # type: ignore
|
||||
@@ -442,7 +442,7 @@ class ChatClientAgent(AgentBase):
|
||||
additional_properties=response.additional_properties,
|
||||
)
|
||||
|
||||
async def run_streaming(
|
||||
async def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
@@ -459,10 +459,10 @@ class ChatClientAgent(AgentBase):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = None,
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -472,7 +472,7 @@ class ChatClientAgent(AgentBase):
|
||||
"""Stream the agent with the given messages and options.
|
||||
|
||||
Remarks:
|
||||
Since you won't always call the agent.run_streaming directly, but it get's called
|
||||
Since you won't always call the agent.run_stream directly, but it get's called
|
||||
through orchestration, it is advised to set your default values for
|
||||
all the chat client parameters in the agent constructor.
|
||||
If both parameters are used, the ones passed to the run methods take precedence.
|
||||
@@ -506,11 +506,11 @@ class ChatClientAgent(AgentBase):
|
||||
response_updates: list[ChatResponseUpdate] = []
|
||||
|
||||
# Resolve final tool list (runtime provided tools + local MCP server tools)
|
||||
final_tools: list[AITool | MutableMapping[str, Any] | Callable[..., Any]] = []
|
||||
final_tools: list[ToolProtocol | MutableMapping[str, Any] | Callable[..., Any]] = []
|
||||
# Normalize tools argument to a list without mutating the original parameter
|
||||
normalized_tools = [] if tools is None else tools if isinstance(tools, list) else [tools]
|
||||
for tool in normalized_tools:
|
||||
if isinstance(tool, McpTool):
|
||||
if isinstance(tool, MCPTool):
|
||||
final_tools.extend(tool.functions) # type: ignore
|
||||
else:
|
||||
final_tools.append(tool)
|
||||
@@ -627,7 +627,7 @@ class ChatClientAgent(AgentBase):
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
if self.instructions:
|
||||
messages.append(ChatMessage(role=ChatRole.SYSTEM, text=self.instructions))
|
||||
messages.append(ChatMessage(role=Role.SYSTEM, text=self.instructions))
|
||||
if thread.message_store:
|
||||
messages.extend(await thread.message_store.list_messages() or [])
|
||||
messages.extend(input_messages or [])
|
||||
@@ -641,12 +641,12 @@ class ChatClientAgent(AgentBase):
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage(role=ChatRole.USER, text=messages)]
|
||||
return [ChatMessage(role=Role.USER, text=messages)]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [messages]
|
||||
|
||||
return [ChatMessage(role=ChatRole.USER, text=msg) if isinstance(msg, str) else msg for msg in messages]
|
||||
return [ChatMessage(role=Role.USER, text=msg) if isinstance(msg, str) else msg for msg in messages]
|
||||
|
||||
def _get_agent_name(self) -> str:
|
||||
return self.name or "UnnamedAgent"
|
||||
|
||||
@@ -11,31 +11,31 @@ from pydantic import BaseModel
|
||||
from ._logging import get_logger
|
||||
from ._pydantic import AFBaseModel
|
||||
from ._threads import ChatMessageStore
|
||||
from ._tools import AIFunction, AITool
|
||||
from ._tools import AIFunction, ToolProtocol
|
||||
from ._types import (
|
||||
AIContents,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatToolMode,
|
||||
Contents,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
GeneratedEmbeddings,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._agents import ChatClientAgent
|
||||
from ._agents import ChatAgent
|
||||
|
||||
TInput = TypeVar("TInput", contravariant=True)
|
||||
TEmbedding = TypeVar("TEmbedding")
|
||||
TChatClientBase = TypeVar("TChatClientBase", bound="ChatClientBase")
|
||||
TBaseChatClient = TypeVar("TBaseChatClient", bound="BaseChatClient")
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
__all__ = [
|
||||
"ChatClient",
|
||||
"ChatClientBase",
|
||||
"BaseChatClient",
|
||||
"ChatClientProtocol",
|
||||
"EmbeddingGenerator",
|
||||
"use_tool_calling",
|
||||
]
|
||||
@@ -50,7 +50,7 @@ async def _auto_invoke_function(
|
||||
tool_map: dict[str, AIFunction[BaseModel, Any]],
|
||||
sequence_index: int | None = None,
|
||||
request_index: int | None = None,
|
||||
) -> AIContents:
|
||||
) -> Contents:
|
||||
"""Invoke a function call requested by the agent, applying filters that are defined in the agent."""
|
||||
tool: AIFunction[BaseModel, Any] | None = tool_map.get(function_call_content.name)
|
||||
if tool is None:
|
||||
@@ -81,7 +81,7 @@ def _tool_call_non_streaming(
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(
|
||||
self: "ChatClientBase",
|
||||
self: "BaseChatClient",
|
||||
*,
|
||||
messages: MutableSequence[ChatMessage],
|
||||
chat_options: ChatOptions,
|
||||
@@ -157,7 +157,7 @@ def _tool_call_streaming(
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(
|
||||
self: "ChatClientBase",
|
||||
self: "BaseChatClient",
|
||||
*,
|
||||
messages: MutableSequence[ChatMessage],
|
||||
chat_options: ChatOptions,
|
||||
@@ -217,11 +217,11 @@ def _tool_call_streaming(
|
||||
return wrapper
|
||||
|
||||
|
||||
def use_tool_calling(cls: type[TChatClientBase]) -> type[TChatClientBase]:
|
||||
def use_tool_calling(cls: type[TBaseChatClient]) -> type[TBaseChatClient]:
|
||||
"""Class decorator that enables tool calling for a chat client.
|
||||
|
||||
Remarks:
|
||||
This only works on classes that derive from ChatClientBase
|
||||
This only works on classes that derive from BaseChatClient
|
||||
and the `_inner_get_response`
|
||||
and `_inner_get_streaming_response` methods.
|
||||
It also sets a `__maximum_iterations_per_request` attribute on the class.
|
||||
@@ -247,11 +247,11 @@ def use_tool_calling(cls: type[TChatClientBase]) -> type[TChatClientBase]:
|
||||
return cls
|
||||
|
||||
|
||||
# region ChatClient Protocol
|
||||
# region ChatClientProtocol Protocol
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ChatClient(Protocol):
|
||||
class ChatClientProtocol(Protocol):
|
||||
"""A protocol for a chat client that can generate responses."""
|
||||
|
||||
async def get_response(
|
||||
@@ -270,10 +270,10 @@ class ChatClient(Protocol):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -327,10 +327,10 @@ class ChatClient(Protocol):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -370,7 +370,7 @@ class ChatClient(Protocol):
|
||||
...
|
||||
|
||||
|
||||
class ChatClientBase(AFBaseModel, ABC):
|
||||
class BaseChatClient(AFBaseModel, ABC):
|
||||
"""Base class for chat clients."""
|
||||
|
||||
MODEL_PROVIDER_NAME: str = "unknown"
|
||||
@@ -457,10 +457,10 @@ class ChatClientBase(AFBaseModel, ABC):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -537,10 +537,10 @@ class ChatClientBase(AFBaseModel, ABC):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -633,14 +633,14 @@ class ChatClientBase(AFBaseModel, ABC):
|
||||
*,
|
||||
name: str | None = None,
|
||||
instructions: str | None = None,
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
chat_message_store_factory: Callable[[], ChatMessageStore] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> "ChatClientAgent":
|
||||
) -> "ChatAgent":
|
||||
"""Create an agent with the given name and instructions.
|
||||
|
||||
Args:
|
||||
@@ -650,14 +650,14 @@ class ChatClientBase(AFBaseModel, ABC):
|
||||
chat_message_store_factory: Factory function to create an instance of ChatMessageStore. If not provided,
|
||||
the default in-memory store will be used.
|
||||
**kwargs: Additional keyword arguments to pass to the agent.
|
||||
See ChatClientAgent for all the available options.
|
||||
See ChatAgent for all the available options.
|
||||
|
||||
Returns:
|
||||
An instance of ChatClientAgent.
|
||||
An instance of ChatAgent.
|
||||
"""
|
||||
from ._agents import ChatClientAgent
|
||||
from ._agents import ChatAgent
|
||||
|
||||
return ChatClientAgent(
|
||||
return ChatAgent(
|
||||
chat_client=self,
|
||||
name=name,
|
||||
instructions=instructions,
|
||||
|
||||
@@ -22,7 +22,7 @@ from mcp.shared.session import RequestResponder
|
||||
from pydantic import BaseModel, create_model
|
||||
|
||||
from ._tools import AIFunction
|
||||
from ._types import AIContents, ChatMessage, ChatRole, DataContent, TextContent, UriContent
|
||||
from ._types import ChatMessage, Contents, DataContent, Role, TextContent, UriContent
|
||||
from .exceptions import ToolException, ToolExecutionException
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -31,7 +31,7 @@ else:
|
||||
from typing_extensions import Self # pragma: no cover
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._clients import ChatClient
|
||||
from ._clients import ChatClientProtocol
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,10 +49,10 @@ LOG_LEVEL_MAPPING: dict[types.LoggingLevel, int] = {
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
"McpSseTools",
|
||||
"McpStdioTool",
|
||||
"McpStreamableHttpTool",
|
||||
"McpWebsocketTool",
|
||||
"MCPSseTools",
|
||||
"MCPStdioTool",
|
||||
"MCPStreamableHTTPTool",
|
||||
"MCPWebsocketTool",
|
||||
]
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ def _mcp_prompt_message_to_chat_message(
|
||||
) -> ChatMessage:
|
||||
"""Convert a MCP container type to a Agent Framework type."""
|
||||
return ChatMessage(
|
||||
role=ChatRole(value=mcp_type.role),
|
||||
role=Role(value=mcp_type.role),
|
||||
contents=[_mcp_type_to_ai_content(mcp_type.content)], # type: ignore[call-arg]
|
||||
raw_representation=mcp_type,
|
||||
)
|
||||
@@ -69,14 +69,14 @@ def _mcp_prompt_message_to_chat_message(
|
||||
|
||||
def _mcp_call_tool_result_to_ai_contents(
|
||||
mcp_type: types.CallToolResult,
|
||||
) -> list[AIContents]:
|
||||
) -> list[Contents]:
|
||||
"""Convert a MCP container type to a Agent Framework type."""
|
||||
return [_mcp_type_to_ai_content(item) for item in mcp_type.content]
|
||||
|
||||
|
||||
def _mcp_type_to_ai_content(
|
||||
mcp_type: types.ImageContent | types.TextContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink,
|
||||
) -> AIContents:
|
||||
) -> Contents:
|
||||
"""Convert a MCP type to a Agent Framework type."""
|
||||
match mcp_type:
|
||||
case types.TextContent():
|
||||
@@ -105,9 +105,9 @@ def _mcp_type_to_ai_content(
|
||||
|
||||
|
||||
def _ai_content_to_mcp_types(
|
||||
content: AIContents,
|
||||
content: Contents,
|
||||
) -> types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource | types.ResourceLink | None:
|
||||
"""Convert a AIContent type to a MCP type."""
|
||||
"""Convert a BaseContent type to a MCP type."""
|
||||
match content:
|
||||
case TextContent():
|
||||
return types.TextContent(type="text", text=content.text)
|
||||
@@ -223,7 +223,7 @@ def _normalize_mcp_name(name: str) -> str:
|
||||
# region: MCP Plugin
|
||||
|
||||
|
||||
class McpTool:
|
||||
class MCPTool:
|
||||
"""Base class with the MCP logic."""
|
||||
|
||||
def __init__(
|
||||
@@ -235,7 +235,7 @@ class McpTool:
|
||||
load_prompts: bool = True,
|
||||
session: ClientSession | None = None,
|
||||
request_timeout: int | None = None,
|
||||
chat_client: "ChatClient | None" = None,
|
||||
chat_client: "ChatClientProtocol | None" = None,
|
||||
) -> None:
|
||||
"""Initialize the MCP Plugin Base."""
|
||||
self.name = name
|
||||
@@ -250,7 +250,7 @@ class McpTool:
|
||||
self.functions: list[AIFunction[Any, Any]] = []
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"McpTool(name={self.name}, description={self.description})"
|
||||
return f"MCPTool(name={self.name}, description={self.description})"
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Connect to the MCP server."""
|
||||
@@ -424,7 +424,7 @@ class McpTool:
|
||||
local_name = _normalize_mcp_name(tool.name)
|
||||
input_model = _get_input_model_from_mcp_tool(tool)
|
||||
# Create AIFunctions out of each tool
|
||||
func: AIFunction[BaseModel, list[AIContents]] = AIFunction(
|
||||
func: AIFunction[BaseModel, list[Contents]] = AIFunction(
|
||||
func=partial(self.call_tool, tool.name),
|
||||
name=local_name,
|
||||
description=tool.description or "",
|
||||
@@ -442,7 +442,7 @@ class McpTool:
|
||||
"""Get an MCP client."""
|
||||
pass
|
||||
|
||||
async def call_tool(self, tool_name: str, **kwargs: Any) -> list[AIContents]:
|
||||
async def call_tool(self, tool_name: str, **kwargs: Any) -> list[Contents]:
|
||||
"""Call a tool with the given arguments."""
|
||||
if not self.session:
|
||||
raise ToolExecutionException("MCP server not connected, please call connect() before using this method.")
|
||||
@@ -494,7 +494,7 @@ class McpTool:
|
||||
# region: MCP Plugin Implementations
|
||||
|
||||
|
||||
class McpStdioTool(McpTool):
|
||||
class MCPStdioTool(MCPTool):
|
||||
"""MCP stdio server configuration."""
|
||||
|
||||
def __init__(
|
||||
@@ -511,7 +511,7 @@ class McpStdioTool(McpTool):
|
||||
args: list[str] | None = None,
|
||||
env: dict[str, str] | None = None,
|
||||
encoding: str | None = None,
|
||||
chat_client: "ChatClient | None" = None,
|
||||
chat_client: "ChatClientProtocol | None" = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the MCP stdio plugin.
|
||||
@@ -567,7 +567,7 @@ class McpStdioTool(McpTool):
|
||||
return stdio_client(server=StdioServerParameters(**args))
|
||||
|
||||
|
||||
class McpSseTools(McpTool):
|
||||
class MCPSseTools(MCPTool):
|
||||
"""MCP sse server configuration."""
|
||||
|
||||
def __init__(
|
||||
@@ -584,7 +584,7 @@ class McpSseTools(McpTool):
|
||||
headers: dict[str, Any] | None = None,
|
||||
timeout: float | None = None,
|
||||
sse_read_timeout: float | None = None,
|
||||
chat_client: "ChatClient | None" = None,
|
||||
chat_client: "ChatClientProtocol | None" = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the MCP sse plugin.
|
||||
@@ -643,7 +643,7 @@ class McpSseTools(McpTool):
|
||||
return sse_client(**args)
|
||||
|
||||
|
||||
class McpStreamableHttpTool(McpTool):
|
||||
class MCPStreamableHTTPTool(MCPTool):
|
||||
"""MCP streamable http server configuration."""
|
||||
|
||||
def __init__(
|
||||
@@ -661,7 +661,7 @@ class McpStreamableHttpTool(McpTool):
|
||||
timeout: float | None = None,
|
||||
sse_read_timeout: float | None = None,
|
||||
terminate_on_close: bool | None = None,
|
||||
chat_client: "ChatClient | None" = None,
|
||||
chat_client: "ChatClientProtocol | None" = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the MCP streamable http plugin.
|
||||
@@ -723,7 +723,7 @@ class McpStreamableHttpTool(McpTool):
|
||||
return streamablehttp_client(**args)
|
||||
|
||||
|
||||
class McpWebsocketTool(McpTool):
|
||||
class MCPWebsocketTool(MCPTool):
|
||||
"""MCP websocket server configuration."""
|
||||
|
||||
def __init__(
|
||||
@@ -737,7 +737,7 @@ class McpWebsocketTool(McpTool):
|
||||
session: ClientSession | None = None,
|
||||
description: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
chat_client: "ChatClient | None" = None,
|
||||
chat_client: "ChatClientProtocol | None" = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the MCP websocket plugin.
|
||||
|
||||
@@ -7,9 +7,9 @@ from pydantic import BaseModel, ConfigDict, Field, UrlConstraints
|
||||
from pydantic.networks import AnyUrl
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
HttpsUrl = Annotated[AnyUrl, UrlConstraints(max_length=2083, allowed_schemes=["https"])]
|
||||
HTTPsUrl = Annotated[AnyUrl, UrlConstraints(max_length=2083, allowed_schemes=["https"])]
|
||||
|
||||
__all__ = ["AFBaseModel", "AFBaseSettings", "HttpsUrl"]
|
||||
__all__ = ["AFBaseModel", "AFBaseSettings", "HTTPsUrl"]
|
||||
|
||||
|
||||
class AFBaseModel(BaseModel):
|
||||
|
||||
@@ -24,7 +24,7 @@ from ._pydantic import AFBaseModel
|
||||
from .telemetry import GenAIAttributes, start_as_current_span
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._types import AIContents
|
||||
from ._types import Contents
|
||||
|
||||
tracer: trace.Tracer = trace.get_tracer("agent_framework")
|
||||
meter: metrics.Meter = metrics.get_meter_provider().get_meter("agent_framework")
|
||||
@@ -32,24 +32,24 @@ logger = get_logger()
|
||||
|
||||
__all__ = [
|
||||
"AIFunction",
|
||||
"AITool",
|
||||
"HostedCodeInterpreterTool",
|
||||
"HostedFileSearchTool",
|
||||
"HostedWebSearchTool",
|
||||
"ToolProtocol",
|
||||
"ai_function",
|
||||
]
|
||||
|
||||
|
||||
def _parse_inputs(
|
||||
inputs: "AIContents | dict[str, Any] | str | list[AIContents | dict[str, Any] | str] | None",
|
||||
) -> list["AIContents"]:
|
||||
"""Parse the inputs for a tool, ensuring they are of type AIContents."""
|
||||
inputs: "Contents | dict[str, Any] | str | list[Contents | dict[str, Any] | str] | None",
|
||||
) -> list["Contents"]:
|
||||
"""Parse the inputs for a tool, ensuring they are of type Contents."""
|
||||
if inputs is None:
|
||||
return []
|
||||
|
||||
from ._types import AIContent, DataContent, HostedFileContent, HostedVectorStoreContent, UriContent
|
||||
from ._types import BaseContent, DataContent, HostedFileContent, HostedVectorStoreContent, UriContent
|
||||
|
||||
parsed_inputs: list["AIContents"] = []
|
||||
parsed_inputs: list["Contents"] = []
|
||||
if not isinstance(inputs, list):
|
||||
inputs = [inputs]
|
||||
for input_item in inputs:
|
||||
@@ -75,15 +75,15 @@ def _parse_inputs(
|
||||
parsed_inputs.append(DataContent(**input_item))
|
||||
else:
|
||||
raise ValueError(f"Unsupported input type: {input_item}")
|
||||
elif isinstance(input_item, AIContent):
|
||||
elif isinstance(input_item, BaseContent):
|
||||
parsed_inputs.append(input_item)
|
||||
else:
|
||||
raise TypeError(f"Unsupported input type: {type(input_item).__name__}. Expected AIContents or dict.")
|
||||
raise TypeError(f"Unsupported input type: {type(input_item).__name__}. Expected Contents or dict.")
|
||||
return parsed_inputs
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AITool(Protocol):
|
||||
class ToolProtocol(Protocol):
|
||||
"""Represents a generic tool that can be specified to an AI service.
|
||||
|
||||
Attributes:
|
||||
@@ -111,7 +111,7 @@ ArgsT = TypeVar("ArgsT", bound=BaseModel)
|
||||
ReturnT = TypeVar("ReturnT")
|
||||
|
||||
|
||||
class AIToolBase(AFBaseModel):
|
||||
class BaseTool(AFBaseModel):
|
||||
"""Base class for AI tools, providing common attributes and methods.
|
||||
|
||||
Args:
|
||||
@@ -131,7 +131,7 @@ class AIToolBase(AFBaseModel):
|
||||
return f"{self.__class__.__name__}(name={self.name})"
|
||||
|
||||
|
||||
class HostedCodeInterpreterTool(AIToolBase):
|
||||
class HostedCodeInterpreterTool(BaseTool):
|
||||
"""Represents a hosted tool that can be specified to an AI service to enable it to execute generated code.
|
||||
|
||||
This tool does not implement code interpretation itself. It serves as a marker to inform a service
|
||||
@@ -143,7 +143,7 @@ class HostedCodeInterpreterTool(AIToolBase):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
inputs: "AIContents | dict[str, Any] | str | list[AIContents | dict[str, Any] | str] | None" = None,
|
||||
inputs: "Contents | dict[str, Any] | str | list[Contents | dict[str, Any] | str] | None" = None,
|
||||
description: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -155,8 +155,8 @@ class HostedCodeInterpreterTool(AIToolBase):
|
||||
This should mostly be HostedFileContent or HostedVectorStoreContent.
|
||||
Can also be DataContent, depending on the service used.
|
||||
When supplying a list, it can contain:
|
||||
- AIContents instances
|
||||
- dicts with properties for AIContents (e.g., {"uri": "http://example.com", "media_type": "text/html"})
|
||||
- Contents instances
|
||||
- dicts with properties for Contents (e.g., {"uri": "http://example.com", "media_type": "text/html"})
|
||||
- strings (which will be converted to UriContent with media_type "text/plain").
|
||||
If None, defaults to an empty list.
|
||||
description: A description of the tool.
|
||||
@@ -177,7 +177,7 @@ class HostedCodeInterpreterTool(AIToolBase):
|
||||
super().__init__(**args, **kwargs)
|
||||
|
||||
|
||||
class HostedWebSearchTool(AIToolBase):
|
||||
class HostedWebSearchTool(BaseTool):
|
||||
"""Represents a web search tool that can be specified to an AI service to enable it to perform web searches."""
|
||||
|
||||
def __init__(
|
||||
@@ -206,7 +206,7 @@ class HostedWebSearchTool(AIToolBase):
|
||||
super().__init__(**args, **kwargs)
|
||||
|
||||
|
||||
class HostedFileSearchTool(AIToolBase):
|
||||
class HostedFileSearchTool(BaseTool):
|
||||
"""Represents a file search tool that can be specified to an AI service to enable it to perform file searches."""
|
||||
|
||||
inputs: list[Any] | None = None
|
||||
@@ -214,7 +214,7 @@ class HostedFileSearchTool(AIToolBase):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inputs: "AIContents | dict[str, Any] | str | list[AIContents | dict[str, Any] | str] | None" = None,
|
||||
inputs: "Contents | dict[str, Any] | str | list[Contents | dict[str, Any] | str] | None" = None,
|
||||
max_results: int | None = None,
|
||||
description: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
@@ -226,8 +226,8 @@ class HostedFileSearchTool(AIToolBase):
|
||||
inputs: A list of contents that the tool can accept as input. Defaults to None.
|
||||
This should be one or more HostedVectorStoreContents.
|
||||
When supplying a list, it can contain:
|
||||
- AIContents instances
|
||||
- dicts with properties for AIContents (e.g., {"uri": "http://example.com", "media_type": "text/html"})
|
||||
- Contents instances
|
||||
- dicts with properties for Contents (e.g., {"uri": "http://example.com", "media_type": "text/html"})
|
||||
- strings (which will be converted to UriContent with media_type "text/plain").
|
||||
If None, defaults to an empty list.
|
||||
max_results: The maximum number of results to return from the file search.
|
||||
@@ -252,8 +252,8 @@ class HostedFileSearchTool(AIToolBase):
|
||||
super().__init__(**args, **kwargs)
|
||||
|
||||
|
||||
class AIFunction(AIToolBase, Generic[ArgsT, ReturnT]):
|
||||
"""A AITool that is callable as code.
|
||||
class AIFunction(BaseTool, Generic[ArgsT, ReturnT]):
|
||||
"""A ToolProtocol that is callable as code.
|
||||
|
||||
Args:
|
||||
name: The name of the function.
|
||||
|
||||
@@ -28,7 +28,7 @@ from pydantic import (
|
||||
|
||||
from ._logging import get_logger
|
||||
from ._pydantic import AFBaseModel
|
||||
from ._tools import AITool, ai_function
|
||||
from ._tools import ToolProtocol, ai_function
|
||||
from .exceptions import AgentFrameworkException
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -77,29 +77,28 @@ KNOWN_MEDIA_TYPES = [
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AIAnnotation",
|
||||
"AIAnnotations",
|
||||
"AIContent",
|
||||
"AIContents",
|
||||
"AgentRunResponse",
|
||||
"AgentRunResponseUpdate",
|
||||
"AnnotatedRegion",
|
||||
"AnnotatedRegions",
|
||||
"ChatFinishReason",
|
||||
"Annotations",
|
||||
"BaseAnnotation",
|
||||
"BaseContent",
|
||||
"ChatMessage",
|
||||
"ChatOptions",
|
||||
"ChatResponse",
|
||||
"ChatResponseUpdate",
|
||||
"ChatRole",
|
||||
"ChatToolMode",
|
||||
"CitationAnnotation",
|
||||
"Contents",
|
||||
"DataContent",
|
||||
"ErrorContent",
|
||||
"FinishReason",
|
||||
"FunctionCallContent",
|
||||
"FunctionResultContent",
|
||||
"GeneratedEmbeddings",
|
||||
"HostedFileContent",
|
||||
"HostedVectorStoreContent",
|
||||
"Role",
|
||||
"SpeechToTextOptions",
|
||||
"TextContent",
|
||||
"TextReasoningContent",
|
||||
@@ -231,7 +230,7 @@ def _process_update(
|
||||
is_new_message = True
|
||||
|
||||
if is_new_message:
|
||||
message = ChatMessage(role=ChatRole.ASSISTANT, contents=[])
|
||||
message = ChatMessage(role=Role.ASSISTANT, contents=[])
|
||||
response.messages.append(message)
|
||||
else:
|
||||
message = response.messages[-1]
|
||||
@@ -278,12 +277,12 @@ def _process_update(
|
||||
|
||||
|
||||
def _coalesce_text_content(
|
||||
contents: list["AIContents"], type_: type["TextContent"] | type["TextReasoningContent"]
|
||||
contents: list["Contents"], type_: type["TextContent"] | type["TextReasoningContent"]
|
||||
) -> None:
|
||||
"""Take any subsequence Text or TextReasoningContent items and coalesce them into a single item."""
|
||||
if not contents:
|
||||
return
|
||||
coalesced_contents: list["AIContents"] = []
|
||||
coalesced_contents: list["Contents"] = []
|
||||
first_new_content: Any | None = None
|
||||
for content in contents:
|
||||
if isinstance(content, type_):
|
||||
@@ -313,22 +312,10 @@ def _finalize_response(response: "ChatResponse | AgentRunResponse") -> None:
|
||||
_coalesce_text_content(msg.contents, TextReasoningContent)
|
||||
|
||||
|
||||
# region AIAnnotation
|
||||
# region BaseAnnotation
|
||||
|
||||
|
||||
class AnnotatedRegion(AFBaseModel):
|
||||
"""Represents a collection of annotated regions.
|
||||
|
||||
Attributes:
|
||||
regions: A list of regions that have been annotated.
|
||||
additional_properties: Optional additional properties associated with the content.
|
||||
raw_representation: Optional raw representation of the content from an underlying implementation.
|
||||
"""
|
||||
|
||||
type: Literal["annotated_regions"] = "annotated_regions" # type: ignore[assignment]
|
||||
|
||||
|
||||
class TextSpanRegion(AnnotatedRegion):
|
||||
class TextSpanRegion(AFBaseModel):
|
||||
"""Represents a region of text that has been annotated."""
|
||||
|
||||
type: Literal["text_span"] = "text_span" # type: ignore[assignment]
|
||||
@@ -337,28 +324,26 @@ class TextSpanRegion(AnnotatedRegion):
|
||||
|
||||
|
||||
AnnotatedRegions = Annotated[
|
||||
TextSpanRegion | AnnotatedRegion,
|
||||
TextSpanRegion,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
class AIAnnotation(AFBaseModel):
|
||||
class BaseAnnotation(AFBaseModel):
|
||||
"""Base class for all AI Annotation types.
|
||||
|
||||
Args:
|
||||
type: The type of content, which is always "ai_annotation" for this class.
|
||||
additional_properties: Optional additional properties associated with the content.
|
||||
raw_representation: Optional raw representation of the content from an underlying implementation.
|
||||
|
||||
"""
|
||||
|
||||
type: Literal["ai_annotation"] = "ai_annotation"
|
||||
annotated_regions: list[AnnotatedRegions] | None = None
|
||||
additional_properties: dict[str, Any] | None = None
|
||||
raw_representation: Any | None = Field(default=None, repr=False)
|
||||
|
||||
|
||||
class CitationAnnotation(AIAnnotation):
|
||||
class CitationAnnotation(BaseAnnotation):
|
||||
"""Represents a citation annotation.
|
||||
|
||||
Attributes:
|
||||
@@ -381,33 +366,31 @@ class CitationAnnotation(AIAnnotation):
|
||||
snippet: str | None = None
|
||||
|
||||
|
||||
AIAnnotations = Annotated[
|
||||
CitationAnnotation | AIAnnotation,
|
||||
Annotations = Annotated[
|
||||
CitationAnnotation,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
# region AIContent
|
||||
# region BaseContent
|
||||
|
||||
|
||||
class AIContent(AFBaseModel):
|
||||
class BaseContent(AFBaseModel):
|
||||
"""Represents content used by AI services.
|
||||
|
||||
Attributes:
|
||||
type: The type of content, which is always "ai" for this class.
|
||||
annotations: Optional annotations associated with the content.
|
||||
additional_properties: Optional additional properties associated with the content.
|
||||
raw_representation: Optional raw representation of the content from an underlying implementation.
|
||||
|
||||
"""
|
||||
|
||||
type: Literal["ai"] = "ai"
|
||||
annotations: list[AIAnnotations] | None = None
|
||||
annotations: list[Annotations] | None = None
|
||||
additional_properties: dict[str, Any] | None = None
|
||||
raw_representation: Any | None = Field(default=None, repr=False, exclude=True)
|
||||
|
||||
|
||||
class TextContent(AIContent):
|
||||
class TextContent(BaseContent):
|
||||
"""Represents text content in a chat.
|
||||
|
||||
Attributes:
|
||||
@@ -508,7 +491,7 @@ class TextContent(AIContent):
|
||||
return self
|
||||
|
||||
|
||||
class TextReasoningContent(AIContent):
|
||||
class TextReasoningContent(BaseContent):
|
||||
"""Represents text reasoning content in a chat.
|
||||
|
||||
Remarks:
|
||||
@@ -609,7 +592,7 @@ class TextReasoningContent(AIContent):
|
||||
return self
|
||||
|
||||
|
||||
class DataContent(AIContent):
|
||||
class DataContent(BaseContent):
|
||||
"""Represents binary data content with an associated media type (also known as a MIME type).
|
||||
|
||||
Attributes:
|
||||
@@ -632,7 +615,7 @@ class DataContent(AIContent):
|
||||
self,
|
||||
*,
|
||||
uri: str,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -658,7 +641,7 @@ class DataContent(AIContent):
|
||||
*,
|
||||
data: bytes,
|
||||
media_type: str,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -685,7 +668,7 @@ class DataContent(AIContent):
|
||||
uri: str | None = None,
|
||||
data: bytes | None = None,
|
||||
media_type: str | None = None,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -739,7 +722,7 @@ class DataContent(AIContent):
|
||||
return _has_top_level_media_type(self.media_type, top_level_media_type)
|
||||
|
||||
|
||||
class UriContent(AIContent):
|
||||
class UriContent(BaseContent):
|
||||
"""Represents a URI content.
|
||||
|
||||
Remarks:
|
||||
@@ -765,7 +748,7 @@ class UriContent(AIContent):
|
||||
uri: str,
|
||||
media_type: str,
|
||||
*,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -807,7 +790,7 @@ def _has_top_level_media_type(media_type: str | None, top_level_media_type: str)
|
||||
return span.lower() == top_level_media_type.lower()
|
||||
|
||||
|
||||
class ErrorContent(AIContent):
|
||||
class ErrorContent(BaseContent):
|
||||
"""Represents an error.
|
||||
|
||||
Remarks:
|
||||
@@ -837,7 +820,7 @@ class ErrorContent(AIContent):
|
||||
message: str | None = None,
|
||||
error_code: str | None = None,
|
||||
details: str | None = None,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -868,7 +851,7 @@ class ErrorContent(AIContent):
|
||||
return f"Error {self.error_code}: {self.message}" if self.error_code else self.message or "Unknown error"
|
||||
|
||||
|
||||
class FunctionCallContent(AIContent):
|
||||
class FunctionCallContent(BaseContent):
|
||||
"""Represents a function call request.
|
||||
|
||||
Attributes:
|
||||
@@ -896,7 +879,7 @@ class FunctionCallContent(AIContent):
|
||||
name: str,
|
||||
arguments: str | dict[str, Any | None] | None = None,
|
||||
exception: Exception | None = None,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -962,7 +945,7 @@ class FunctionCallContent(AIContent):
|
||||
)
|
||||
|
||||
|
||||
class FunctionResultContent(AIContent):
|
||||
class FunctionResultContent(BaseContent):
|
||||
"""Represents the result of a function call.
|
||||
|
||||
Attributes:
|
||||
@@ -987,7 +970,7 @@ class FunctionResultContent(AIContent):
|
||||
call_id: str,
|
||||
result: Any | None = None,
|
||||
exception: Exception | None = None,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -1014,7 +997,7 @@ class FunctionResultContent(AIContent):
|
||||
)
|
||||
|
||||
|
||||
class UsageContent(AIContent):
|
||||
class UsageContent(BaseContent):
|
||||
"""Represents usage information associated with a chat request and response.
|
||||
|
||||
Attributes:
|
||||
@@ -1033,7 +1016,7 @@ class UsageContent(AIContent):
|
||||
self,
|
||||
details: UsageDetails,
|
||||
*,
|
||||
annotations: list[AIAnnotations] | None = None,
|
||||
annotations: list[Annotations] | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -1048,7 +1031,7 @@ class UsageContent(AIContent):
|
||||
)
|
||||
|
||||
|
||||
class HostedFileContent(AIContent):
|
||||
class HostedFileContent(BaseContent):
|
||||
"""Represents a hosted file content.
|
||||
|
||||
Attributes:
|
||||
@@ -1079,7 +1062,7 @@ class HostedFileContent(AIContent):
|
||||
)
|
||||
|
||||
|
||||
class HostedVectorStoreContent(AIContent):
|
||||
class HostedVectorStoreContent(BaseContent):
|
||||
"""Represents a hosted vector store content.
|
||||
|
||||
Attributes:
|
||||
@@ -1110,7 +1093,7 @@ class HostedVectorStoreContent(AIContent):
|
||||
)
|
||||
|
||||
|
||||
AIContents = Annotated[
|
||||
Contents = Annotated[
|
||||
TextContent
|
||||
| DataContent
|
||||
| TextReasoningContent
|
||||
@@ -1127,7 +1110,7 @@ AIContents = Annotated[
|
||||
# region Chat Response constants
|
||||
|
||||
|
||||
class ChatRole(AFBaseModel):
|
||||
class Role(AFBaseModel):
|
||||
"""Describes the intended purpose of a message within a chat interaction.
|
||||
|
||||
Attributes:
|
||||
@@ -1157,19 +1140,19 @@ class ChatRole(AFBaseModel):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Returns the string representation of the role."""
|
||||
return f"ChatRole(value={self.value!r})"
|
||||
return f"Role(value={self.value!r})"
|
||||
|
||||
|
||||
# Note: ClassVar is used to indicate that these are class-level constants, not instance attributes.
|
||||
# The type: ignore[assignment] is used to suppress the type checker warning about assigning to a ClassVar,
|
||||
# it gets assigned immediately after the class definition.
|
||||
ChatRole.SYSTEM = ChatRole(value="system") # type: ignore[assignment]
|
||||
ChatRole.USER = ChatRole(value="user") # type: ignore[assignment]
|
||||
ChatRole.ASSISTANT = ChatRole(value="assistant") # type: ignore[assignment]
|
||||
ChatRole.TOOL = ChatRole(value="tool") # type: ignore[assignment]
|
||||
Role.SYSTEM = Role(value="system") # type: ignore[assignment]
|
||||
Role.USER = Role(value="user") # type: ignore[assignment]
|
||||
Role.ASSISTANT = Role(value="assistant") # type: ignore[assignment]
|
||||
Role.TOOL = Role(value="tool") # type: ignore[assignment]
|
||||
|
||||
|
||||
class ChatFinishReason(AFBaseModel):
|
||||
class FinishReason(AFBaseModel):
|
||||
"""Represents the reason a chat response completed.
|
||||
|
||||
Attributes:
|
||||
@@ -1179,21 +1162,21 @@ class ChatFinishReason(AFBaseModel):
|
||||
value: str
|
||||
|
||||
CONTENT_FILTER: ClassVar[Self] # type: ignore[assignment]
|
||||
"""A ChatFinishReason representing the model filtering content, whether for safety, prohibited content,
|
||||
"""A FinishReason representing the model filtering content, whether for safety, prohibited content,
|
||||
sensitive content, or other such issues."""
|
||||
LENGTH: ClassVar[Self] # type: ignore[assignment]
|
||||
"""A ChatFinishReason representing the model reaching the maximum length allowed for the request and/or
|
||||
"""A FinishReason representing the model reaching the maximum length allowed for the request and/or
|
||||
response (typically in terms of tokens)."""
|
||||
STOP: ClassVar[Self] # type: ignore[assignment]
|
||||
"""A ChatFinishReason representing the model encountering a natural stop point or provided stop sequence."""
|
||||
"""A FinishReason representing the model encountering a natural stop point or provided stop sequence."""
|
||||
TOOL_CALLS: ClassVar[Self] # type: ignore[assignment]
|
||||
"""A ChatFinishReason representing the model requesting the use of a tool that was defined in the request."""
|
||||
"""A FinishReason representing the model requesting the use of a tool that was defined in the request."""
|
||||
|
||||
|
||||
ChatFinishReason.CONTENT_FILTER = ChatFinishReason(value="content_filter") # type: ignore[assignment]
|
||||
ChatFinishReason.LENGTH = ChatFinishReason(value="length") # type: ignore[assignment]
|
||||
ChatFinishReason.STOP = ChatFinishReason(value="stop") # type: ignore[assignment]
|
||||
ChatFinishReason.TOOL_CALLS = ChatFinishReason(value="tool_calls") # type: ignore[assignment]
|
||||
FinishReason.CONTENT_FILTER = FinishReason(value="content_filter") # type: ignore[assignment]
|
||||
FinishReason.LENGTH = FinishReason(value="length") # type: ignore[assignment]
|
||||
FinishReason.STOP = FinishReason(value="stop") # type: ignore[assignment]
|
||||
FinishReason.TOOL_CALLS = FinishReason(value="tool_calls") # type: ignore[assignment]
|
||||
|
||||
# region ChatMessage
|
||||
|
||||
@@ -1211,9 +1194,9 @@ class ChatMessage(AFBaseModel):
|
||||
|
||||
"""
|
||||
|
||||
role: ChatRole
|
||||
role: Role
|
||||
"""The role of the author of the message."""
|
||||
contents: list[AIContents]
|
||||
contents: list[Contents]
|
||||
"""The chat message content items."""
|
||||
author_name: str | None
|
||||
"""The name of the author of the message."""
|
||||
@@ -1227,7 +1210,7 @@ class ChatMessage(AFBaseModel):
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"],
|
||||
role: Role | Literal["system", "user", "assistant", "tool"],
|
||||
*,
|
||||
text: str,
|
||||
author_name: str | None = None,
|
||||
@@ -1249,9 +1232,9 @@ class ChatMessage(AFBaseModel):
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"],
|
||||
role: Role | Literal["system", "user", "assistant", "tool"],
|
||||
*,
|
||||
contents: MutableSequence[AIContents],
|
||||
contents: MutableSequence[Contents],
|
||||
author_name: str | None = None,
|
||||
message_id: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
@@ -1261,7 +1244,7 @@ class ChatMessage(AFBaseModel):
|
||||
|
||||
Args:
|
||||
role: The role of the author of the message.
|
||||
contents: Optional list of AIContent items to include in the message.
|
||||
contents: Optional list of BaseContent items to include in the message.
|
||||
author_name: Optional name of the author of the message.
|
||||
message_id: Optional ID of the chat message.
|
||||
additional_properties: Optional additional properties associated with the chat message.
|
||||
@@ -1270,10 +1253,10 @@ class ChatMessage(AFBaseModel):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"],
|
||||
role: Role | Literal["system", "user", "assistant", "tool"],
|
||||
*,
|
||||
text: str | None = None,
|
||||
contents: MutableSequence[AIContents] | None = None,
|
||||
contents: MutableSequence[Contents] | None = None,
|
||||
author_name: str | None = None,
|
||||
message_id: str | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
@@ -1284,7 +1267,7 @@ class ChatMessage(AFBaseModel):
|
||||
if text is not None:
|
||||
contents.append(TextContent(text=text))
|
||||
if isinstance(role, str):
|
||||
role = ChatRole(value=role)
|
||||
role = Role(value=role)
|
||||
super().__init__(
|
||||
role=role, # type: ignore[reportCallIssue]
|
||||
contents=contents, # type: ignore[reportCallIssue]
|
||||
@@ -1334,7 +1317,7 @@ class ChatResponse(AFBaseModel):
|
||||
"""The model ID used in the creation of the chat response."""
|
||||
created_at: CreatedAtT | None = None # use a datetimeoffset type?
|
||||
"""A timestamp for the chat response."""
|
||||
finish_reason: ChatFinishReason | None = None
|
||||
finish_reason: FinishReason | None = None
|
||||
"""The reason for the chat response."""
|
||||
usage_details: UsageDetails | None = None
|
||||
"""The usage details for the chat response."""
|
||||
@@ -1354,7 +1337,7 @@ class ChatResponse(AFBaseModel):
|
||||
conversation_id: str | None = None,
|
||||
model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
usage_details: UsageDetails | None = None,
|
||||
value: Any | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
@@ -1389,7 +1372,7 @@ class ChatResponse(AFBaseModel):
|
||||
conversation_id: str | None = None,
|
||||
model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
usage_details: UsageDetails | None = None,
|
||||
value: Any | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
@@ -1424,7 +1407,7 @@ class ChatResponse(AFBaseModel):
|
||||
conversation_id: str | None = None,
|
||||
model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
usage_details: UsageDetails | None = None,
|
||||
value: Any | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
@@ -1440,7 +1423,7 @@ class ChatResponse(AFBaseModel):
|
||||
if text is not None:
|
||||
if isinstance(text, str):
|
||||
text = TextContent(text=text)
|
||||
messages.append(ChatMessage(role=ChatRole.ASSISTANT, contents=[text]))
|
||||
messages.append(ChatMessage(role=Role.ASSISTANT, contents=[text]))
|
||||
|
||||
super().__init__(
|
||||
messages=messages, # type: ignore[reportCallIssue]
|
||||
@@ -1528,10 +1511,10 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
|
||||
"""
|
||||
|
||||
contents: list[AIContents]
|
||||
contents: list[Contents]
|
||||
"""The chat response update content items."""
|
||||
|
||||
role: ChatRole | None = None
|
||||
role: Role | None = None
|
||||
"""The role of the author of the response update."""
|
||||
author_name: str | None = None
|
||||
"""The name of the author of the response update."""
|
||||
@@ -1546,7 +1529,7 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
"""The model ID associated with this response update."""
|
||||
created_at: CreatedAtT | None = None # use a datetimeoffset type?
|
||||
"""A timestamp for the chat response update."""
|
||||
finish_reason: ChatFinishReason | None = None
|
||||
finish_reason: FinishReason | None = None
|
||||
"""The finish reason for the operation."""
|
||||
|
||||
additional_properties: dict[str, Any] | None = None
|
||||
@@ -1558,15 +1541,15 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
contents: list[AIContents],
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
contents: list[Contents],
|
||||
role: Role | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
author_name: str | None = None,
|
||||
response_id: str | None = None,
|
||||
message_id: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
ai_model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
) -> None:
|
||||
@@ -1577,14 +1560,14 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
self,
|
||||
*,
|
||||
text: TextContent | str,
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
role: Role | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
author_name: str | None = None,
|
||||
response_id: str | None = None,
|
||||
message_id: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
ai_model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
) -> None:
|
||||
@@ -1593,16 +1576,16 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
contents: list[AIContents] | None = None,
|
||||
contents: list[Contents] | None = None,
|
||||
text: TextContent | str | None = None,
|
||||
role: ChatRole | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
role: Role | Literal["system", "user", "assistant", "tool"] | None = None,
|
||||
author_name: str | None = None,
|
||||
response_id: str | None = None,
|
||||
message_id: str | None = None,
|
||||
conversation_id: str | None = None,
|
||||
ai_model_id: str | None = None,
|
||||
created_at: CreatedAtT | None = None,
|
||||
finish_reason: ChatFinishReason | None = None,
|
||||
finish_reason: FinishReason | None = None,
|
||||
additional_properties: dict[str, Any] | None = None,
|
||||
raw_representation: Any | None = None,
|
||||
) -> None:
|
||||
@@ -1614,7 +1597,7 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
text = TextContent(text=text)
|
||||
contents.append(text)
|
||||
if role and isinstance(role, str):
|
||||
role = ChatRole(value=role)
|
||||
role = Role(value=role)
|
||||
super().__init__(
|
||||
contents=contents, # type: ignore[reportCallIssue]
|
||||
additional_properties=additional_properties, # type: ignore[reportCallIssue]
|
||||
@@ -1637,7 +1620,7 @@ class ChatResponseUpdate(AFBaseModel):
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
def with_(self, contents: list[AIContent] | None = None, message_id: str | None = None) -> Self:
|
||||
def with_(self, contents: list[BaseContent] | None = None, message_id: str | None = None) -> Self:
|
||||
"""Returns a new instance with the specified contents and message_id."""
|
||||
if contents is None:
|
||||
contents = []
|
||||
@@ -1709,7 +1692,7 @@ class ChatOptions(AFBaseModel):
|
||||
store: bool | None = None
|
||||
temperature: Annotated[float | None, Field(ge=0.0, le=2.0)] = None
|
||||
tool_choice: ChatToolMode | Literal["auto", "required", "none"] | Mapping[str, Any] | None = None
|
||||
tools: list[AITool | MutableMapping[str, Any]] | None = None
|
||||
tools: list[ToolProtocol | MutableMapping[str, Any]] | None = None
|
||||
top_p: Annotated[float | None, Field(ge=0.0, le=1.0)] = None
|
||||
user: str | None = None
|
||||
|
||||
@@ -1718,21 +1701,21 @@ class ChatOptions(AFBaseModel):
|
||||
def _validate_tools(
|
||||
cls,
|
||||
tools: (
|
||||
AITool
|
||||
ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None
|
||||
),
|
||||
) -> list[AITool | MutableMapping[str, Any]] | None:
|
||||
) -> list[ToolProtocol | MutableMapping[str, Any]] | None:
|
||||
"""Parse the tools field."""
|
||||
if not tools:
|
||||
return None
|
||||
if not isinstance(tools, list):
|
||||
tools = [tools] # type: ignore[reportAssignmentType, assignment]
|
||||
for idx, tool in enumerate(tools): # type: ignore[reportArgumentType, arg-type]
|
||||
if not isinstance(tool, (AITool, MutableMapping)):
|
||||
# Convert to AITool if it's a function or callable
|
||||
if not isinstance(tool, (ToolProtocol, MutableMapping)):
|
||||
# Convert to ToolProtocol if it's a function or callable
|
||||
tools[idx] = ai_function(tool) # type: ignore[reportIndexIssues, reportCallIssue, reportArgumentType, index, call-overload, arg-type]
|
||||
return tools # type: ignore[reportReturnType, return-value]
|
||||
|
||||
@@ -2006,8 +1989,8 @@ class AgentRunResponse(AFBaseModel):
|
||||
class AgentRunResponseUpdate(AFBaseModel):
|
||||
"""Represents a single streaming response chunk from an Agent."""
|
||||
|
||||
contents: list[AIContents] = Field(default_factory=list[AIContents])
|
||||
role: ChatRole | None = None
|
||||
contents: list[Contents] = Field(default_factory=list[Contents])
|
||||
role: Role | None = None
|
||||
author_name: str | None = None
|
||||
response_id: str | None = None
|
||||
message_id: str | None = None
|
||||
|
||||
@@ -20,18 +20,18 @@ from openai.types.beta.threads.run_submit_tool_outputs_params import ToolOutput
|
||||
from openai.types.beta.threads.runs import RunStep
|
||||
from pydantic import Field, PrivateAttr, SecretStr, ValidationError
|
||||
|
||||
from .._clients import ChatClientBase, use_tool_calling
|
||||
from .._clients import BaseChatClient, use_tool_calling
|
||||
from .._tools import AIFunction, HostedCodeInterpreterTool, HostedFileSearchTool
|
||||
from .._types import (
|
||||
AIContents,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
ChatToolMode,
|
||||
Contents,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UriContent,
|
||||
UsageContent,
|
||||
@@ -39,7 +39,7 @@ from .._types import (
|
||||
)
|
||||
from ..exceptions import ServiceInitializationError
|
||||
from ..telemetry import use_telemetry
|
||||
from ._shared import OpenAIConfigBase, OpenAISettings
|
||||
from ._shared import OpenAIConfigMixin, OpenAISettings
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import Self # pragma: no cover
|
||||
@@ -52,7 +52,7 @@ __all__ = ["OpenAIAssistantsClient"]
|
||||
|
||||
@use_telemetry
|
||||
@use_tool_calling
|
||||
class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
class OpenAIAssistantsClient(OpenAIConfigMixin, BaseChatClient):
|
||||
"""OpenAI Assistants client."""
|
||||
|
||||
assistant_id: str | None = Field(default=None)
|
||||
@@ -274,13 +274,13 @@ class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
message_id=response_id,
|
||||
raw_representation=response.data,
|
||||
response_id=response_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
elif response.event == "thread.run.step.created" and isinstance(response.data, RunStep):
|
||||
response_id = response.data.run_id
|
||||
elif response.event == "thread.message.delta" and isinstance(response.data, MessageDeltaEvent):
|
||||
delta = response.data.delta
|
||||
role = ChatRole.USER if delta.role == "user" else ChatRole.ASSISTANT
|
||||
role = Role.USER if delta.role == "user" else Role.ASSISTANT
|
||||
|
||||
for delta_block in delta.content or []:
|
||||
if isinstance(delta_block, TextDeltaBlock) and delta_block.text and delta_block.text.value:
|
||||
@@ -296,7 +296,7 @@ class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
contents = self._create_function_call_contents(response.data, response_id)
|
||||
if contents:
|
||||
yield ChatResponseUpdate(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
contents=contents,
|
||||
conversation_id=thread_id,
|
||||
message_id=response_id,
|
||||
@@ -317,7 +317,7 @@ class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
)
|
||||
)
|
||||
yield ChatResponseUpdate(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
contents=[usage_content],
|
||||
conversation_id=thread_id,
|
||||
message_id=response_id,
|
||||
@@ -331,12 +331,12 @@ class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
message_id=response_id,
|
||||
raw_representation=response.data,
|
||||
response_id=response_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
|
||||
def _create_function_call_contents(self, event_data: Run, response_id: str | None) -> list[AIContents]:
|
||||
def _create_function_call_contents(self, event_data: Run, response_id: str | None) -> list[Contents]:
|
||||
"""Create function call contents from a tool action event."""
|
||||
contents: list[AIContents] = []
|
||||
contents: list[Contents] = []
|
||||
|
||||
if event_data.required_action is not None:
|
||||
for tool_call in event_data.required_action.submit_tool_outputs.tool_calls:
|
||||
@@ -437,7 +437,7 @@ class OpenAIAssistantsClient(OpenAIConfigBase, ChatClientBase):
|
||||
additional_messages = []
|
||||
additional_messages.append(
|
||||
AdditionalMessage(
|
||||
role="assistant" if chat_message.role == ChatRole.ASSISTANT else "user",
|
||||
role="assistant" if chat_message.role == Role.ASSISTANT else "user",
|
||||
content=message_contents,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -15,19 +15,19 @@ from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice
|
||||
from openai.types.chat.chat_completion_message_custom_tool_call import ChatCompletionMessageCustomToolCall
|
||||
from pydantic import BaseModel, SecretStr, ValidationError
|
||||
|
||||
from .._clients import ChatClientBase, use_tool_calling
|
||||
from .._clients import BaseChatClient, use_tool_calling
|
||||
from .._logging import get_logger
|
||||
from .._tools import AIFunction, AITool, HostedWebSearchTool
|
||||
from .._tools import AIFunction, HostedWebSearchTool, ToolProtocol
|
||||
from .._types import (
|
||||
AIContents,
|
||||
ChatFinishReason,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
Contents,
|
||||
FinishReason,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UsageContent,
|
||||
UsageDetails,
|
||||
@@ -39,7 +39,7 @@ from ..exceptions import (
|
||||
)
|
||||
from ..telemetry import use_telemetry
|
||||
from ._exceptions import OpenAIContentFilterException
|
||||
from ._shared import OpenAIConfigBase, OpenAIHandler, OpenAISettings, prepare_function_call_results
|
||||
from ._shared import OpenAIBase, OpenAIConfigMixin, OpenAISettings, prepare_function_call_results
|
||||
|
||||
__all__ = ["OpenAIChatClient"]
|
||||
|
||||
@@ -49,7 +49,7 @@ logger = get_logger("agent_framework.openai")
|
||||
# region Base Client
|
||||
@use_telemetry
|
||||
@use_tool_calling
|
||||
class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
class OpenAIBaseChatClient(OpenAIBase, BaseChatClient):
|
||||
"""OpenAI Chat completion class."""
|
||||
|
||||
async def _inner_get_response(
|
||||
@@ -112,10 +112,10 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
|
||||
# region content creation
|
||||
|
||||
def _chat_to_tool_spec(self, tools: list[AITool | MutableMapping[str, Any]]) -> list[dict[str, Any]]:
|
||||
def _chat_to_tool_spec(self, tools: list[ToolProtocol | MutableMapping[str, Any]]) -> list[dict[str, Any]]:
|
||||
chat_tools: list[dict[str, Any]] = []
|
||||
for tool in tools:
|
||||
if isinstance(tool, AITool):
|
||||
if isinstance(tool, ToolProtocol):
|
||||
match tool:
|
||||
case AIFunction():
|
||||
chat_tools.append(tool.to_json_schema_spec())
|
||||
@@ -125,7 +125,7 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
chat_tools.append(tool if isinstance(tool, dict) else dict(tool))
|
||||
return chat_tools
|
||||
|
||||
def _process_web_search_tool(self, tools: list[AITool | MutableMapping[str, Any]]) -> dict[str, Any] | None:
|
||||
def _process_web_search_tool(self, tools: list[ToolProtocol | MutableMapping[str, Any]]) -> dict[str, Any] | None:
|
||||
for tool in tools:
|
||||
if isinstance(tool, HostedWebSearchTool):
|
||||
# Web search tool requires special handling
|
||||
@@ -173,12 +173,12 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
"""Create a chat message content object from a choice."""
|
||||
response_metadata = self._get_metadata_from_chat_response(response)
|
||||
messages: list[ChatMessage] = []
|
||||
finish_reason: ChatFinishReason | None = None
|
||||
finish_reason: FinishReason | None = None
|
||||
for choice in response.choices:
|
||||
response_metadata.update(self._get_metadata_from_chat_choice(choice))
|
||||
if choice.finish_reason:
|
||||
finish_reason = ChatFinishReason(value=choice.finish_reason)
|
||||
contents: list[AIContents] = []
|
||||
finish_reason = FinishReason(value=choice.finish_reason)
|
||||
contents: list[Contents] = []
|
||||
if parsed_tool_calls := [tool for tool in self._get_tool_calls_from_chat_choice(choice)]:
|
||||
contents.extend(parsed_tool_calls)
|
||||
if text_content := self._parse_text_from_choice(choice):
|
||||
@@ -203,27 +203,27 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
chunk_metadata = self._get_metadata_from_streaming_chat_response(chunk)
|
||||
if chunk.usage:
|
||||
return ChatResponseUpdate(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
contents=[UsageContent(details=self._usage_details_from_openai(chunk.usage), raw_representation=chunk)],
|
||||
ai_model_id=chunk.model,
|
||||
additional_properties=chunk_metadata,
|
||||
response_id=chunk.id,
|
||||
message_id=chunk.id,
|
||||
)
|
||||
contents: list[AIContents] = []
|
||||
finish_reason: ChatFinishReason | None = None
|
||||
contents: list[Contents] = []
|
||||
finish_reason: FinishReason | None = None
|
||||
for choice in chunk.choices:
|
||||
chunk_metadata.update(self._get_metadata_from_chat_choice(choice))
|
||||
contents.extend(self._get_tool_calls_from_chat_choice(choice))
|
||||
if choice.finish_reason:
|
||||
finish_reason = ChatFinishReason(value=choice.finish_reason)
|
||||
finish_reason = FinishReason(value=choice.finish_reason)
|
||||
|
||||
if text_content := self._parse_text_from_choice(choice):
|
||||
contents.append(text_content)
|
||||
return ChatResponseUpdate(
|
||||
created_at=datetime.fromtimestamp(chunk.created).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
contents=contents,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
ai_model_id=chunk.model,
|
||||
additional_properties=chunk_metadata,
|
||||
finish_reason=finish_reason,
|
||||
@@ -266,9 +266,9 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
"logprobs": getattr(choice, "logprobs", None),
|
||||
}
|
||||
|
||||
def _get_tool_calls_from_chat_choice(self, choice: Choice | ChunkChoice) -> list[AIContents]:
|
||||
def _get_tool_calls_from_chat_choice(self, choice: Choice | ChunkChoice) -> list[Contents]:
|
||||
"""Get tool calls from a chat choice."""
|
||||
resp: list[AIContents] = []
|
||||
resp: list[Contents] = []
|
||||
content = choice.message if isinstance(choice, Choice) else choice.delta
|
||||
if content and content.tool_calls:
|
||||
for tool in content.tool_calls:
|
||||
@@ -295,7 +295,7 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
|
||||
Allowing customization of the key names for role/author, and optionally overriding the role.
|
||||
|
||||
ChatRole.TOOL messages need to be formatted different than system/user/assistant messages:
|
||||
Role.TOOL messages need to be formatted different than system/user/assistant messages:
|
||||
They require a "tool_call_id" and (function) "name" key, and the "metadata" key should
|
||||
be removed. The "encoding" key should also be removed.
|
||||
|
||||
@@ -320,7 +320,7 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
all_messages: list[dict[str, Any]] = []
|
||||
for content in message.contents:
|
||||
args: dict[str, Any] = {
|
||||
"role": message.role.value if isinstance(message.role, ChatRole) else message.role,
|
||||
"role": message.role.value if isinstance(message.role, Role) else message.role,
|
||||
}
|
||||
if message.additional_properties:
|
||||
args["metadata"] = message.additional_properties
|
||||
@@ -344,7 +344,7 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
all_messages.append(args)
|
||||
return all_messages
|
||||
|
||||
def _openai_content_parser(self, content: AIContents) -> dict[str, Any]:
|
||||
def _openai_content_parser(self, content: Contents) -> dict[str, Any]:
|
||||
"""Parse contents into the openai format."""
|
||||
match content:
|
||||
case FunctionCallContent():
|
||||
@@ -376,7 +376,7 @@ class OpenAIChatClientBase(OpenAIHandler, ChatClientBase):
|
||||
TOpenAIChatClient = TypeVar("TOpenAIChatClient", bound="OpenAIChatClient")
|
||||
|
||||
|
||||
class OpenAIChatClient(OpenAIConfigBase, OpenAIChatClientBase):
|
||||
class OpenAIChatClient(OpenAIConfigMixin, OpenAIBaseChatClient):
|
||||
"""OpenAI Chat completion class."""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -31,22 +31,22 @@ from openai.types.responses.web_search_tool_param import UserLocation as WebSear
|
||||
from openai.types.responses.web_search_tool_param import WebSearchToolParam
|
||||
from pydantic import BaseModel, SecretStr, ValidationError
|
||||
|
||||
from .._clients import ChatClientBase, use_tool_calling
|
||||
from .._clients import BaseChatClient, use_tool_calling
|
||||
from .._logging import get_logger
|
||||
from .._tools import AIFunction, AITool, HostedCodeInterpreterTool, HostedFileSearchTool, HostedWebSearchTool
|
||||
from .._tools import AIFunction, HostedCodeInterpreterTool, HostedFileSearchTool, HostedWebSearchTool, ToolProtocol
|
||||
from .._types import (
|
||||
AIContents,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
CitationAnnotation,
|
||||
Contents,
|
||||
DataContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedFileContent,
|
||||
HostedVectorStoreContent,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
TextSpanRegion,
|
||||
@@ -61,7 +61,7 @@ from ..exceptions import (
|
||||
)
|
||||
from ..telemetry import use_telemetry
|
||||
from ._exceptions import OpenAIContentFilterException
|
||||
from ._shared import OpenAIConfigBase, OpenAIHandler, OpenAISettings, prepare_function_call_results
|
||||
from ._shared import OpenAIBase, OpenAIConfigMixin, OpenAISettings, prepare_function_call_results
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # type: ignore # pragma: no cover
|
||||
@@ -81,7 +81,7 @@ __all__ = ["OpenAIResponsesClient"]
|
||||
# region ResponsesClient
|
||||
|
||||
|
||||
class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
|
||||
"""Base class for all OpenAI Responses based API's."""
|
||||
|
||||
FILE_SEARCH_MAX_RESULTS: int = 50
|
||||
@@ -110,10 +110,10 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: "ChatToolMode" | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -200,10 +200,10 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
store: bool | None = None,
|
||||
temperature: float | None = None,
|
||||
tool_choice: "ChatToolMode" | Literal["auto", "required", "none"] | dict[str, Any] | None = "auto",
|
||||
tools: AITool
|
||||
tools: ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[AITool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
top_p: float | None = None,
|
||||
user: str | None = None,
|
||||
@@ -365,11 +365,11 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
# region Prep methods
|
||||
|
||||
def _chat_to_response_tool_spec(
|
||||
self, tools: list[AITool | MutableMapping[str, Any]]
|
||||
self, tools: list[ToolProtocol | MutableMapping[str, Any]]
|
||||
) -> list[ToolParam | dict[str, Any]]:
|
||||
response_tools: list[ToolParam | dict[str, Any]] = []
|
||||
for tool in tools:
|
||||
if isinstance(tool, AITool):
|
||||
if isinstance(tool, ToolProtocol):
|
||||
match tool:
|
||||
case HostedCodeInterpreterTool():
|
||||
tool_args: dict[str, Any] = {"type": "auto"}
|
||||
@@ -471,7 +471,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
|
||||
Allowing customization of the key names for role/author, and optionally overriding the role.
|
||||
|
||||
ChatRole.TOOL messages need to be formatted different than system/user/assistant messages:
|
||||
Role.TOOL messages need to be formatted different than system/user/assistant messages:
|
||||
They require a "tool_call_id" and (function) "name" key, and the "metadata" key should
|
||||
be removed. The "encoding" key should also be removed.
|
||||
|
||||
@@ -507,7 +507,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
structured_response: BaseModel | None = response.output_parsed if isinstance(response, ParsedResponse) else None # type: ignore[reportUnknownMemberType]
|
||||
|
||||
metadata: dict[str, Any] = response.metadata or {}
|
||||
contents: list[AIContents] = []
|
||||
contents: list[Contents] = []
|
||||
for item in response.output: # type: ignore[reportUnknownMemberType]
|
||||
match item.type:
|
||||
# types:
|
||||
@@ -517,12 +517,12 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
# ResponseFunctionWebSearch |
|
||||
# ResponseComputerToolCall |
|
||||
# ResponseReasoningItem |
|
||||
# McpCall |
|
||||
# McpApprovalRequest |
|
||||
# MCPCall |
|
||||
# MCPApprovalRequest |
|
||||
# ImageGenerationCall |
|
||||
# LocalShellCall |
|
||||
# LocalShellCallAction |
|
||||
# McpListTools |
|
||||
# MCPListTools |
|
||||
# ResponseCodeInterpreterToolCall |
|
||||
# ResponseCustomToolCall |
|
||||
# ParsedResponseOutputMessage[BaseModel] |
|
||||
@@ -677,7 +677,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
) -> ChatResponseUpdate:
|
||||
"""Create a streaming chat message content object from a choice."""
|
||||
metadata: dict[str, Any] = {}
|
||||
items: list[AIContents] = []
|
||||
items: list[Contents] = []
|
||||
conversation_id: str | None = None
|
||||
model = self.ai_model_id
|
||||
# TODO(peterychang): Add support for other content types
|
||||
@@ -720,7 +720,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
return ChatResponseUpdate(
|
||||
contents=items,
|
||||
conversation_id=conversation_id,
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
ai_model_id=model,
|
||||
additional_properties=metadata,
|
||||
raw_representation=event,
|
||||
@@ -746,7 +746,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
"""Parse a chat message into the openai format."""
|
||||
all_messages: list[dict[str, Any]] = []
|
||||
args: dict[str, Any] = {
|
||||
"role": message.role.value if isinstance(message.role, ChatRole) else message.role,
|
||||
"role": message.role.value if isinstance(message.role, Role) else message.role,
|
||||
}
|
||||
if message.additional_properties:
|
||||
args["metadata"] = message.additional_properties
|
||||
@@ -769,8 +769,8 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
|
||||
def _openai_content_parser(
|
||||
self,
|
||||
role: ChatRole,
|
||||
content: AIContents,
|
||||
role: Role,
|
||||
content: Contents,
|
||||
call_id_to_id: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
"""Parse contents into the openai format."""
|
||||
@@ -794,7 +794,7 @@ class OpenAIResponsesClientBase(OpenAIHandler, ChatClientBase):
|
||||
return args
|
||||
case TextContent():
|
||||
return {
|
||||
"type": "output_text" if role == ChatRole.ASSISTANT else "input_text",
|
||||
"type": "output_text" if role == Role.ASSISTANT else "input_text",
|
||||
"text": content.text,
|
||||
}
|
||||
# TODO(peterychang): We'll probably need to specialize the other content types as well
|
||||
@@ -815,7 +815,7 @@ TOpenAIResponsesClient = TypeVar("TOpenAIResponsesClient", bound="OpenAIResponse
|
||||
|
||||
@use_telemetry
|
||||
@use_tool_calling
|
||||
class OpenAIResponsesClient(OpenAIConfigBase, OpenAIResponsesClientBase):
|
||||
class OpenAIResponsesClient(OpenAIConfigMixin, OpenAIBaseResponsesClient):
|
||||
"""OpenAI Responses client class."""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -22,7 +22,7 @@ from pydantic.types import StringConstraints
|
||||
|
||||
from .._logging import get_logger
|
||||
from .._pydantic import AFBaseModel, AFBaseSettings
|
||||
from .._types import AIContents, ChatOptions, SpeechToTextOptions, TextToSpeechOptions
|
||||
from .._types import ChatOptions, Contents, SpeechToTextOptions, TextToSpeechOptions
|
||||
from ..exceptions import ServiceInitializationError
|
||||
from ..telemetry import APP_INFO, USER_AGENT_KEY, prepend_agent_framework_to_user_agent
|
||||
|
||||
@@ -50,7 +50,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
def prepare_function_call_results(content: AIContents | Any | list[AIContents | Any]) -> str | list[str]:
|
||||
def prepare_function_call_results(content: Contents | Any | list[Contents | Any]) -> str | list[str]:
|
||||
"""Prepare the values of the function call results."""
|
||||
if isinstance(content, list):
|
||||
results: list[str] = []
|
||||
@@ -117,14 +117,14 @@ class OpenAISettings(AFBaseSettings):
|
||||
realtime_model_id: str | None = None
|
||||
|
||||
|
||||
class OpenAIHandler(AFBaseModel):
|
||||
class OpenAIBase(AFBaseModel):
|
||||
"""Base class for OpenAI Clients."""
|
||||
|
||||
client: AsyncOpenAI
|
||||
ai_model_id: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
|
||||
|
||||
|
||||
class OpenAIConfigBase(OpenAIHandler):
|
||||
class OpenAIConfigMixin(OpenAIBase):
|
||||
"""Internal class for configuring a connection to an OpenAI service."""
|
||||
|
||||
MODEL_PROVIDER_NAME: ClassVar[str] = "openai" # type: ignore[reportIncompatibleVariableOverride, misc]
|
||||
|
||||
@@ -18,8 +18,8 @@ from ._pydantic import AFBaseSettings
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from opentelemetry.util._decorator import _AgnosticContextManager # type: ignore[reportPrivateUsage]
|
||||
|
||||
from ._agents import AIAgent, ChatClientAgent
|
||||
from ._clients import ChatClientBase
|
||||
from ._agents import AgentProtocol, ChatAgent
|
||||
from ._clients import BaseChatClient
|
||||
from ._threads import AgentThread
|
||||
from ._tools import AIFunction
|
||||
from ._types import (
|
||||
@@ -31,8 +31,8 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
ChatResponseUpdate,
|
||||
)
|
||||
|
||||
TChatClientBase = TypeVar("TChatClientBase", bound="ChatClientBase")
|
||||
TChatClientAgent = TypeVar("TChatClientAgent", bound="ChatClientAgent")
|
||||
TBaseChatClient = TypeVar("TBaseChatClient", bound="BaseChatClient")
|
||||
TChatClientAgent = TypeVar("TChatClientAgent", bound="ChatAgent")
|
||||
|
||||
tracer = get_tracer("agent_framework")
|
||||
logger = get_logger()
|
||||
@@ -269,7 +269,7 @@ def _set_error(span: Span, error: Exception) -> None:
|
||||
span.set_status(StatusCode.ERROR, repr(error))
|
||||
|
||||
|
||||
# region ChatClient
|
||||
# region ChatClientProtocol
|
||||
|
||||
|
||||
def _trace_chat_get_response(
|
||||
@@ -283,7 +283,7 @@ def _trace_chat_get_response(
|
||||
|
||||
@functools.wraps(completion_func)
|
||||
async def wrap_inner_get_response(
|
||||
self: "ChatClientBase",
|
||||
self: "BaseChatClient",
|
||||
*,
|
||||
messages: MutableSequence["ChatMessage"],
|
||||
chat_options: "ChatOptions",
|
||||
@@ -334,7 +334,7 @@ def _trace_chat_get_streaming_response(
|
||||
|
||||
@functools.wraps(completion_func)
|
||||
async def wrap_inner_get_streaming_response(
|
||||
self: "ChatClientBase", *, messages: MutableSequence["ChatMessage"], chat_options: "ChatOptions", **kwargs: Any
|
||||
self: "BaseChatClient", *, messages: MutableSequence["ChatMessage"], chat_options: "ChatOptions", **kwargs: Any
|
||||
) -> AsyncIterable["ChatResponseUpdate"]:
|
||||
if not MODEL_DIAGNOSTICS_SETTINGS.ENABLED:
|
||||
# If model diagnostics are not enabled, just return the completion
|
||||
@@ -375,11 +375,11 @@ def _trace_chat_get_streaming_response(
|
||||
return wrap_inner_get_streaming_response
|
||||
|
||||
|
||||
def use_telemetry(cls: type[TChatClientBase]) -> type[TChatClientBase]:
|
||||
def use_telemetry(cls: type[TBaseChatClient]) -> type[TBaseChatClient]:
|
||||
"""Class decorator that enables telemetry for a chat client.
|
||||
|
||||
Remarks:
|
||||
This only works on classes that derive from ChatClientBase
|
||||
This only works on classes that derive from BaseChatClient
|
||||
and the _inner_get_response
|
||||
and _inner_get_streaming_response methods.
|
||||
It also relies on the presence of the MODEL_PROVIDER_NAME class variable.
|
||||
@@ -520,7 +520,7 @@ def _trace_agent_run(
|
||||
|
||||
@functools.wraps(run_func)
|
||||
async def wrap_run(
|
||||
self: "ChatClientAgent",
|
||||
self: "ChatAgent",
|
||||
messages: "str | ChatMessage | list[str] | list[ChatMessage] | None" = None,
|
||||
*,
|
||||
thread: "AgentThread | None" = None,
|
||||
@@ -560,7 +560,7 @@ def _trace_agent_run(
|
||||
return wrap_run
|
||||
|
||||
|
||||
def _trace_agent_run_streaming(
|
||||
def _trace_agent_run_stream(
|
||||
run_func: Callable[..., AsyncIterable["AgentRunResponseUpdate"]],
|
||||
) -> Callable[..., AsyncIterable["AgentRunResponseUpdate"]]:
|
||||
"""Decorator to trace streaming agent run activities.
|
||||
@@ -570,8 +570,8 @@ def _trace_agent_run_streaming(
|
||||
"""
|
||||
|
||||
@functools.wraps(run_func)
|
||||
async def wrap_run_streaming(
|
||||
self: "ChatClientAgent",
|
||||
async def wrap_run_stream(
|
||||
self: "ChatAgent",
|
||||
messages: "str | ChatMessage | list[str] | list[ChatMessage] | None" = None,
|
||||
*,
|
||||
thread: "AgentThread | None" = None,
|
||||
@@ -610,23 +610,23 @@ def _trace_agent_run_streaming(
|
||||
raise
|
||||
|
||||
# Mark the wrapper decorator as a streaming agent run decorator
|
||||
wrap_run_streaming.__model_diagnostics_streaming_agent_run__ = True # type: ignore
|
||||
return wrap_run_streaming
|
||||
wrap_run_stream.__model_diagnostics_streaming_agent_run__ = True # type: ignore
|
||||
return wrap_run_stream
|
||||
|
||||
|
||||
def use_agent_telemetry(cls: type[TChatClientAgent]) -> type[TChatClientAgent]:
|
||||
"""Class decorator that enables telemetry for an agent."""
|
||||
if run := getattr(cls, "run", None):
|
||||
cls.run = _trace_agent_run(run) # type: ignore
|
||||
if run_streaming := getattr(cls, "run_streaming", None):
|
||||
cls.run_streaming = _trace_agent_run_streaming(run_streaming) # type: ignore
|
||||
if run_stream := getattr(cls, "run_stream", None):
|
||||
cls.run_stream = _trace_agent_run_stream(run_stream) # type: ignore
|
||||
return cls
|
||||
|
||||
|
||||
def _get_agent_run_span(
|
||||
*,
|
||||
operation_name: str,
|
||||
agent: "AIAgent",
|
||||
agent: "AgentProtocol",
|
||||
system: str,
|
||||
thread: "AgentThread | None",
|
||||
**kwargs: Any,
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from pydantic import BaseModel
|
||||
from pytest import fixture
|
||||
|
||||
from agent_framework import AITool, ChatMessage, ai_function
|
||||
from agent_framework import ChatMessage, ToolProtocol, ai_function
|
||||
from agent_framework.telemetry import ModelDiagnosticSettings
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ def chat_history() -> list[ChatMessage]:
|
||||
|
||||
|
||||
@fixture
|
||||
def ai_tool() -> AITool:
|
||||
"""Returns a generic AITool."""
|
||||
def ai_tool() -> ToolProtocol:
|
||||
"""Returns a generic ToolProtocol."""
|
||||
|
||||
class GenericTool(BaseModel):
|
||||
name: str
|
||||
@@ -32,8 +32,8 @@ def ai_tool() -> AITool:
|
||||
|
||||
|
||||
@fixture
|
||||
def ai_function_tool() -> AITool:
|
||||
"""Returns a executable AITool."""
|
||||
def ai_function_tool() -> ToolProtocol:
|
||||
"""Returns a executable ToolProtocol."""
|
||||
|
||||
@ai_function
|
||||
def simple_function(x: int, y: int) -> int:
|
||||
|
||||
@@ -7,19 +7,19 @@ from uuid import uuid4
|
||||
from pytest import fixture, raises
|
||||
|
||||
from agent_framework import (
|
||||
AgentProtocol,
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
AIAgent,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatClientBase,
|
||||
BaseChatClient,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatMessageList,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
Role,
|
||||
TextContent,
|
||||
)
|
||||
from agent_framework.exceptions import AgentExecutionException
|
||||
@@ -31,7 +31,7 @@ class MockAgentThread(AgentThread):
|
||||
|
||||
|
||||
# Mock Agent implementation for testing
|
||||
class MockAgent(AIAgent):
|
||||
class MockAgent(AgentProtocol):
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return str(uuid4())
|
||||
@@ -57,9 +57,9 @@ class MockAgent(AIAgent):
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentRunResponse:
|
||||
return AgentRunResponse(messages=[ChatMessage(role=ChatRole.ASSISTANT, contents=[TextContent("Response")])])
|
||||
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent("Response")])])
|
||||
|
||||
async def run_streaming(
|
||||
async def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
@@ -72,8 +72,8 @@ class MockAgent(AIAgent):
|
||||
return MockAgentThread()
|
||||
|
||||
|
||||
# Mock ChatClient implementation for testing
|
||||
class MockChatClient(ChatClientBase):
|
||||
# Mock ChatClientProtocol implementation for testing
|
||||
class MockChatClient(BaseChatClient):
|
||||
_mock_response: ChatResponse | None = None
|
||||
|
||||
def __init__(self, mock_response: ChatResponse | None = None) -> None:
|
||||
@@ -89,7 +89,7 @@ class MockChatClient(ChatClientBase):
|
||||
return (
|
||||
self._mock_response
|
||||
if self._mock_response
|
||||
else ChatResponse(messages=ChatMessage(role=ChatRole.ASSISTANT, text="test response"))
|
||||
else ChatResponse(messages=ChatMessage(role=Role.ASSISTANT, text="test response"))
|
||||
)
|
||||
|
||||
async def _inner_get_streaming_response(
|
||||
@@ -99,7 +99,7 @@ class MockChatClient(ChatClientBase):
|
||||
chat_options: ChatOptions,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterable[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(role=ChatRole.ASSISTANT, text=TextContent(text="test streaming response"))
|
||||
yield ChatResponseUpdate(role=Role.ASSISTANT, text=TextContent(text="test streaming response"))
|
||||
|
||||
|
||||
@fixture
|
||||
@@ -108,12 +108,12 @@ def agent_thread() -> AgentThread:
|
||||
|
||||
|
||||
@fixture
|
||||
def agent() -> AIAgent:
|
||||
def agent() -> AgentProtocol:
|
||||
return MockAgent()
|
||||
|
||||
|
||||
@fixture
|
||||
def chat_client() -> ChatClientBase:
|
||||
def chat_client() -> BaseChatClient:
|
||||
return MockChatClient()
|
||||
|
||||
|
||||
@@ -121,33 +121,33 @@ def test_agent_thread_type(agent_thread: AgentThread) -> None:
|
||||
assert isinstance(agent_thread, AgentThread)
|
||||
|
||||
|
||||
def test_agent_type(agent: AIAgent) -> None:
|
||||
assert isinstance(agent, AIAgent)
|
||||
def test_agent_type(agent: AgentProtocol) -> None:
|
||||
assert isinstance(agent, AgentProtocol)
|
||||
|
||||
|
||||
async def test_agent_run(agent: AIAgent) -> None:
|
||||
async def test_agent_run(agent: AgentProtocol) -> None:
|
||||
response = await agent.run("test")
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == "Response"
|
||||
|
||||
|
||||
async def test_agent_run_streaming(agent: AIAgent) -> None:
|
||||
async def test_agent_run_streaming(agent: AgentProtocol) -> None:
|
||||
async def collect_updates(updates: AsyncIterable[AgentRunResponseUpdate]) -> list[AgentRunResponseUpdate]:
|
||||
return [u async for u in updates]
|
||||
|
||||
updates = await collect_updates(agent.run_streaming(messages="test"))
|
||||
updates = await collect_updates(agent.run_stream(messages="test"))
|
||||
assert len(updates) == 1
|
||||
assert updates[0].text == "Response"
|
||||
|
||||
|
||||
def test_chat_client_agent_type(chat_client: ChatClient) -> None:
|
||||
chat_client_agent = ChatClientAgent(chat_client=chat_client)
|
||||
assert isinstance(chat_client_agent, AIAgent)
|
||||
def test_chat_client_agent_type(chat_client: ChatClientProtocol) -> None:
|
||||
chat_client_agent = ChatAgent(chat_client=chat_client)
|
||||
assert isinstance(chat_client_agent, AgentProtocol)
|
||||
|
||||
|
||||
async def test_chat_client_agent_init(chat_client: ChatClient) -> None:
|
||||
async def test_chat_client_agent_init(chat_client: ChatClientProtocol) -> None:
|
||||
agent_id = str(uuid4())
|
||||
agent = ChatClientAgent(chat_client=chat_client, id=agent_id, description="Test")
|
||||
agent = ChatAgent(chat_client=chat_client, id=agent_id, description="Test")
|
||||
|
||||
assert agent.id == agent_id
|
||||
assert agent.name is None
|
||||
@@ -155,9 +155,9 @@ async def test_chat_client_agent_init(chat_client: ChatClient) -> None:
|
||||
assert agent.display_name == agent_id # Display name defaults to id if name is None
|
||||
|
||||
|
||||
async def test_chat_client_agent_init_with_name(chat_client: ChatClient) -> None:
|
||||
async def test_chat_client_agent_init_with_name(chat_client: ChatClientProtocol) -> None:
|
||||
agent_id = str(uuid4())
|
||||
agent = ChatClientAgent(chat_client=chat_client, id=agent_id, name="Test Agent", description="Test")
|
||||
agent = ChatAgent(chat_client=chat_client, id=agent_id, name="Test Agent", description="Test")
|
||||
|
||||
assert agent.id == agent_id
|
||||
assert agent.name == "Test Agent"
|
||||
@@ -165,37 +165,37 @@ async def test_chat_client_agent_init_with_name(chat_client: ChatClient) -> None
|
||||
assert agent.display_name == "Test Agent" # Display name is the name if present
|
||||
|
||||
|
||||
async def test_chat_client_agent_run(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
async def test_chat_client_agent_run(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
|
||||
result = await agent.run("Hello")
|
||||
|
||||
assert result.text == "test response"
|
||||
|
||||
|
||||
async def test_chat_client_agent_run_streaming(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
async def test_chat_client_agent_run_streaming(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
|
||||
result = await AgentRunResponse.from_agent_response_generator(agent.run_streaming("Hello"))
|
||||
result = await AgentRunResponse.from_agent_response_generator(agent.run_stream("Hello"))
|
||||
|
||||
assert result.text == "test streaming response"
|
||||
|
||||
|
||||
async def test_chat_client_agent_get_new_thread(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
async def test_chat_client_agent_get_new_thread(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
assert isinstance(thread, AgentThread)
|
||||
|
||||
|
||||
async def test_chat_client_agent_prepare_thread_and_messages(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
message = ChatMessage(role=ChatRole.USER, text="Hello")
|
||||
async def test_chat_client_agent_prepare_thread_and_messages(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
message = ChatMessage(role=Role.USER, text="Hello")
|
||||
thread = AgentThread(message_store=ChatMessageList(messages=[message]))
|
||||
|
||||
_, result_messages = await agent._prepare_thread_and_messages( # type: ignore[reportPrivateUsage]
|
||||
thread=thread,
|
||||
input_messages=[ChatMessage(role=ChatRole.USER, text="Test")],
|
||||
input_messages=[ChatMessage(role=Role.USER, text="Test")],
|
||||
)
|
||||
|
||||
assert len(result_messages) == 2
|
||||
@@ -206,11 +206,11 @@ async def test_chat_client_agent_prepare_thread_and_messages(chat_client: ChatCl
|
||||
async def test_chat_client_agent_update_thread_id() -> None:
|
||||
chat_client = MockChatClient(
|
||||
mock_response=ChatResponse(
|
||||
messages=[ChatMessage(role=ChatRole.ASSISTANT, contents=[TextContent("test response")])],
|
||||
messages=[ChatMessage(role=Role.ASSISTANT, contents=[TextContent("test response")])],
|
||||
conversation_id="123",
|
||||
)
|
||||
)
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
result = await agent.run("Hello", thread=thread)
|
||||
@@ -219,8 +219,8 @@ async def test_chat_client_agent_update_thread_id() -> None:
|
||||
assert thread.service_thread_id == "123"
|
||||
|
||||
|
||||
async def test_chat_client_agent_update_thread_messages(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
async def test_chat_client_agent_update_thread_messages(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
result = await agent.run("Hello", thread=thread)
|
||||
@@ -237,26 +237,26 @@ async def test_chat_client_agent_update_thread_messages(chat_client: ChatClient)
|
||||
assert chat_messages[1].text == "test response"
|
||||
|
||||
|
||||
async def test_chat_client_agent_update_thread_conversation_id_missing(chat_client: ChatClient) -> None:
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
async def test_chat_client_agent_update_thread_conversation_id_missing(chat_client: ChatClientProtocol) -> None:
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
thread = AgentThread(service_thread_id="123")
|
||||
|
||||
with raises(AgentExecutionException, match="Service did not return a valid conversation id"):
|
||||
agent._update_thread_with_type_and_conversation_id(thread, None) # type: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
async def test_chat_client_agent_default_author_name(chat_client: ChatClient) -> None:
|
||||
async def test_chat_client_agent_default_author_name(chat_client: ChatClientProtocol) -> None:
|
||||
# Name is not specified here, so default name should be used
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
|
||||
result = await agent.run("Hello")
|
||||
assert result.text == "test response"
|
||||
assert result.messages[0].author_name == "UnnamedAgent"
|
||||
|
||||
|
||||
async def test_chat_client_agent_author_name_as_agent_name(chat_client: ChatClient) -> None:
|
||||
async def test_chat_client_agent_author_name_as_agent_name(chat_client: ChatClientProtocol) -> None:
|
||||
# Name is specified here, so it should be used as author name
|
||||
agent = ChatClientAgent(chat_client=chat_client, name="TestAgent")
|
||||
agent = ChatAgent(chat_client=chat_client, name="TestAgent")
|
||||
|
||||
result = await agent.run("Hello")
|
||||
assert result.text == "test response"
|
||||
@@ -267,11 +267,11 @@ async def test_chat_client_agent_author_name_is_used_from_response() -> None:
|
||||
chat_client = MockChatClient(
|
||||
mock_response=ChatResponse(
|
||||
messages=[
|
||||
ChatMessage(role=ChatRole.ASSISTANT, contents=[TextContent("test response")], author_name="TestAuthor")
|
||||
ChatMessage(role=Role.ASSISTANT, contents=[TextContent("test response")], author_name="TestAuthor")
|
||||
]
|
||||
)
|
||||
)
|
||||
agent = ChatClientAgent(chat_client=chat_client)
|
||||
agent = ChatAgent(chat_client=chat_client)
|
||||
|
||||
result = await agent.run("Hello")
|
||||
assert result.text == "test response"
|
||||
|
||||
@@ -9,17 +9,17 @@ from pydantic import Field
|
||||
from pytest import fixture
|
||||
|
||||
from agent_framework import (
|
||||
ChatClient,
|
||||
ChatClientBase,
|
||||
BaseChatClient,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
EmbeddingGenerator,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
GeneratedEmbeddings,
|
||||
Role,
|
||||
TextContent,
|
||||
ai_function,
|
||||
use_tool_calling,
|
||||
@@ -54,8 +54,8 @@ class MockChatClient:
|
||||
|
||||
|
||||
@use_tool_calling
|
||||
class MockChatClientBase(ChatClientBase):
|
||||
"""Mock implementation of the ChatClientBase."""
|
||||
class MockBaseChatClient(BaseChatClient):
|
||||
"""Mock implementation of the BaseChatClient."""
|
||||
|
||||
run_responses: list[ChatResponse] = Field(default_factory=list)
|
||||
streaming_responses: list[list[ChatResponseUpdate]] = Field(default_factory=list)
|
||||
@@ -120,8 +120,8 @@ def chat_client() -> MockChatClient:
|
||||
|
||||
|
||||
@fixture
|
||||
def chat_client_base() -> MockChatClientBase:
|
||||
return MockChatClientBase()
|
||||
def chat_client_base() -> MockBaseChatClient:
|
||||
return MockBaseChatClient()
|
||||
|
||||
|
||||
@fixture
|
||||
@@ -131,19 +131,19 @@ def embedding_generator() -> MockEmbeddingGenerator:
|
||||
|
||||
|
||||
def test_chat_client_type(chat_client: MockChatClient):
|
||||
assert isinstance(chat_client, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
|
||||
async def test_chat_client_get_response(chat_client: MockChatClient):
|
||||
response = await chat_client.get_response(ChatMessage(role="user", text="Hello"))
|
||||
assert response.text == "test response"
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
|
||||
|
||||
async def test_chat_client_get_streaming_response(chat_client: MockChatClient):
|
||||
async for update in chat_client.get_streaming_response(ChatMessage(role="user", text="Hello")):
|
||||
assert update.text == "test streaming response" or update.text == "another update"
|
||||
assert update.role == ChatRole.ASSISTANT
|
||||
assert update.role == Role.ASSISTANT
|
||||
|
||||
|
||||
def test_embedding_generator_type(embedding_generator: MockEmbeddingGenerator):
|
||||
@@ -158,23 +158,23 @@ async def test_embedding_generator_generate(embedding_generator: MockEmbeddingGe
|
||||
assert len(emb) == 5
|
||||
|
||||
|
||||
def test_base_client(chat_client_base: MockChatClientBase):
|
||||
assert isinstance(chat_client_base, ChatClientBase)
|
||||
assert isinstance(chat_client_base, ChatClient)
|
||||
def test_base_client(chat_client_base: MockBaseChatClient):
|
||||
assert isinstance(chat_client_base, BaseChatClient)
|
||||
assert isinstance(chat_client_base, ChatClientProtocol)
|
||||
|
||||
|
||||
async def test_base_client_get_response(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_get_response(chat_client_base: MockBaseChatClient):
|
||||
response = await chat_client_base.get_response(ChatMessage(role="user", text="Hello"))
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == "test response - Hello"
|
||||
|
||||
|
||||
async def test_base_client_get_streaming_response(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_get_streaming_response(chat_client_base: MockBaseChatClient):
|
||||
async for update in chat_client_base.get_streaming_response(ChatMessage(role="user", text="Hello")):
|
||||
assert update.text == "update - Hello" or update.text == "another update"
|
||||
|
||||
|
||||
async def test_base_client_with_function_calling(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_with_function_calling(chat_client_base: MockBaseChatClient):
|
||||
exec_counter = 0
|
||||
|
||||
@ai_function(name="test_function")
|
||||
@@ -195,20 +195,20 @@ async def test_base_client_with_function_calling(chat_client_base: MockChatClien
|
||||
response = await chat_client_base.get_response("hello", tool_choice="auto", tools=[ai_func])
|
||||
assert exec_counter == 1
|
||||
assert len(response.messages) == 3
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert isinstance(response.messages[0].contents[0], FunctionCallContent)
|
||||
assert response.messages[0].contents[0].name == "test_function"
|
||||
assert response.messages[0].contents[0].arguments == '{"arg1": "value1"}'
|
||||
assert response.messages[0].contents[0].call_id == "1"
|
||||
assert response.messages[1].role == ChatRole.TOOL
|
||||
assert response.messages[1].role == Role.TOOL
|
||||
assert isinstance(response.messages[1].contents[0], FunctionResultContent)
|
||||
assert response.messages[1].contents[0].call_id == "1"
|
||||
assert response.messages[1].contents[0].result == "Processed value1"
|
||||
assert response.messages[2].role == ChatRole.ASSISTANT
|
||||
assert response.messages[2].role == Role.ASSISTANT
|
||||
assert response.messages[2].text == "done"
|
||||
|
||||
|
||||
async def test_base_client_with_function_calling_disabled(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_with_function_calling_disabled(chat_client_base: MockBaseChatClient):
|
||||
chat_client_base.__maximum_iterations_per_request = 0
|
||||
exec_counter = 0
|
||||
|
||||
@@ -230,11 +230,11 @@ async def test_base_client_with_function_calling_disabled(chat_client_base: Mock
|
||||
response = await chat_client_base.get_response("hello", tool_choice="auto", tools=[ai_func])
|
||||
assert exec_counter == 0
|
||||
assert len(response.messages) == 1
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == "test response - hello"
|
||||
|
||||
|
||||
async def test_base_client_with_streaming_function_calling(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_with_streaming_function_calling(chat_client_base: MockBaseChatClient):
|
||||
exec_counter = 0
|
||||
|
||||
@ai_function(name="test_function")
|
||||
@@ -272,7 +272,7 @@ async def test_base_client_with_streaming_function_calling(chat_client_base: Moc
|
||||
assert exec_counter == 1
|
||||
|
||||
|
||||
async def test_base_client_with_streaming_function_calling_disabled(chat_client_base: MockChatClientBase):
|
||||
async def test_base_client_with_streaming_function_calling_disabled(chat_client_base: MockBaseChatClient):
|
||||
chat_client_base.__maximum_iterations_per_request = 0
|
||||
exec_counter = 0
|
||||
|
||||
|
||||
@@ -12,19 +12,18 @@ from mcp.shared.exceptions import McpError
|
||||
from pydantic import AnyUrl, ValidationError
|
||||
|
||||
from agent_framework import (
|
||||
AITool,
|
||||
ChatMessage,
|
||||
ChatRole,
|
||||
DataContent,
|
||||
McpSseTools,
|
||||
McpStdioTool,
|
||||
McpStreamableHttpTool,
|
||||
McpWebsocketTool,
|
||||
MCPStdioTool,
|
||||
MCPStreamableHTTPTool,
|
||||
MCPWebsocketTool,
|
||||
Role,
|
||||
TextContent,
|
||||
ToolProtocol,
|
||||
UriContent,
|
||||
)
|
||||
from agent_framework._mcp import (
|
||||
McpTool,
|
||||
MCPTool,
|
||||
_ai_content_to_mcp_types,
|
||||
_chat_message_to_mcp_types,
|
||||
_get_input_model_from_mcp_prompt,
|
||||
@@ -275,20 +274,20 @@ def test_get_input_model_from_mcp_prompt():
|
||||
model(arg2="optional")
|
||||
|
||||
|
||||
# McpTool tests
|
||||
# MCPTool tests
|
||||
async def test_local_mcp_server_initialization():
|
||||
"""Test McpTool initialization."""
|
||||
server = McpTool(name="test_server")
|
||||
assert isinstance(server, AITool)
|
||||
"""Test MCPTool initialization."""
|
||||
server = MCPTool(name="test_server")
|
||||
assert isinstance(server, ToolProtocol)
|
||||
assert server.name == "test_server"
|
||||
assert server.session is None
|
||||
assert server.functions == []
|
||||
|
||||
|
||||
async def test_local_mcp_server_context_manager():
|
||||
"""Test McpTool as context manager."""
|
||||
"""Test MCPTool as context manager."""
|
||||
|
||||
class TestServer(McpTool):
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
# Mock connection
|
||||
self.session = Mock(spec=ClientSession)
|
||||
@@ -306,7 +305,7 @@ async def test_local_mcp_server_context_manager():
|
||||
async def test_local_mcp_server_load_functions():
|
||||
"""Test loading functions from MCP server."""
|
||||
|
||||
class TestServer(McpTool):
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
# Mock tools list response
|
||||
@@ -330,7 +329,7 @@ async def test_local_mcp_server_load_functions():
|
||||
return None
|
||||
|
||||
server = TestServer(name="test_server")
|
||||
assert isinstance(server, AITool)
|
||||
assert isinstance(server, ToolProtocol)
|
||||
async with server:
|
||||
await server.load_tools()
|
||||
assert len(server.functions) == 1
|
||||
@@ -340,7 +339,7 @@ async def test_local_mcp_server_load_functions():
|
||||
async def test_local_mcp_server_load_prompts():
|
||||
"""Test loading prompts from MCP server."""
|
||||
|
||||
class TestServer(McpTool):
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
# Mock prompts list response
|
||||
@@ -369,7 +368,7 @@ async def test_local_mcp_server_load_prompts():
|
||||
async def test_local_mcp_server_function_execution():
|
||||
"""Test function execution through MCP server."""
|
||||
|
||||
class TestServer(McpTool):
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
self.session.list_tools = AsyncMock(
|
||||
@@ -410,7 +409,7 @@ async def test_local_mcp_server_function_execution():
|
||||
async def test_local_mcp_server_function_execution_error():
|
||||
"""Test function execution error handling."""
|
||||
|
||||
class TestServer(McpTool):
|
||||
class TestServer(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
self.session.list_tools = AsyncMock(
|
||||
@@ -448,7 +447,7 @@ async def test_local_mcp_server_function_execution_error():
|
||||
async def test_local_mcp_server_prompt_execution():
|
||||
"""Test prompt execution through MCP server."""
|
||||
|
||||
class TestMcpTool(McpTool):
|
||||
class TestMCPTool(MCPTool):
|
||||
async def connect(self):
|
||||
self.session = Mock(spec=ClientSession)
|
||||
self.session.list_prompts = AsyncMock(
|
||||
@@ -474,7 +473,7 @@ async def test_local_mcp_server_prompt_execution():
|
||||
def get_mcp_client(self) -> _AsyncGeneratorContextManager[Any, None]:
|
||||
return None
|
||||
|
||||
server = TestMcpTool(name="test_server")
|
||||
server = TestMCPTool(name="test_server")
|
||||
async with server:
|
||||
await server.load_prompts()
|
||||
prompt = server.functions[0]
|
||||
@@ -482,37 +481,30 @@ async def test_local_mcp_server_prompt_execution():
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], ChatMessage)
|
||||
assert result[0].role == ChatRole.USER
|
||||
assert result[0].role == Role.USER
|
||||
assert len(result[0].contents) == 1
|
||||
assert result[0].contents[0].text == "Test message"
|
||||
|
||||
|
||||
# Server implementation tests
|
||||
def test_local_mcp_stdio_tool_init():
|
||||
"""Test McpStdioTool initialization."""
|
||||
tool = McpStdioTool(name="test", command="echo", args=["hello"])
|
||||
"""Test MCPStdioTool initialization."""
|
||||
tool = MCPStdioTool(name="test", command="echo", args=["hello"])
|
||||
assert tool.name == "test"
|
||||
assert tool.command == "echo"
|
||||
assert tool.args == ["hello"]
|
||||
|
||||
|
||||
def test_local_mcp_sse_tools_init():
|
||||
"""Test McpSseTools initialization."""
|
||||
tool = McpSseTools(name="test", url="http://localhost:8080")
|
||||
assert tool.name == "test"
|
||||
assert tool.url == "http://localhost:8080"
|
||||
|
||||
|
||||
def test_local_mcp_websocket_tool_init():
|
||||
"""Test McpWebsocketTool initialization."""
|
||||
tool = McpWebsocketTool(name="test", url="ws://localhost:8080")
|
||||
"""Test MCPWebsocketTool initialization."""
|
||||
tool = MCPWebsocketTool(name="test", url="ws://localhost:8080")
|
||||
assert tool.name == "test"
|
||||
assert tool.url == "ws://localhost:8080"
|
||||
|
||||
|
||||
def test_local_mcp_streamable_http_tool_init():
|
||||
"""Test McpStreamableHttpTool initialization."""
|
||||
tool = McpStreamableHttpTool(name="test", url="http://localhost:8080")
|
||||
"""Test MCPStreamableHTTPTool initialization."""
|
||||
tool = MCPStreamableHTTPTool(name="test", url="http://localhost:8080")
|
||||
assert tool.name == "test"
|
||||
assert tool.url == "http://localhost:8080"
|
||||
|
||||
@@ -525,7 +517,7 @@ async def test_streamable_http_integration():
|
||||
if not url.startswith("http"):
|
||||
pytest.skip("LOCAL_MCP_URL is not an HTTP URL")
|
||||
|
||||
tool = McpStreamableHttpTool(name="integration_test", url=url)
|
||||
tool = MCPStreamableHTTPTool(name="integration_test", url=url)
|
||||
|
||||
async with tool:
|
||||
# Test that we can connect and load tools
|
||||
|
||||
@@ -13,7 +13,7 @@ from agent_framework import (
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
Role,
|
||||
UsageDetails,
|
||||
)
|
||||
from agent_framework.telemetry import (
|
||||
@@ -301,7 +301,7 @@ def test_start_span_empty_metadata():
|
||||
|
||||
|
||||
def test_decorator_with_valid_class():
|
||||
"""Test that decorator works with a valid ChatClientBase-like class."""
|
||||
"""Test that decorator works with a valid BaseChatClient-like class."""
|
||||
|
||||
# Create a mock class with the required methods
|
||||
class MockChatClient:
|
||||
@@ -373,7 +373,7 @@ def mock_chat_client():
|
||||
self, *, messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
):
|
||||
return ChatResponse(
|
||||
messages=[ChatMessage(role=ChatRole.ASSISTANT, text="Test response")],
|
||||
messages=[ChatMessage(role=Role.ASSISTANT, text="Test response")],
|
||||
usage_details=UsageDetails(input_token_count=10, output_token_count=20),
|
||||
finish_reason=None,
|
||||
)
|
||||
@@ -381,8 +381,8 @@ def mock_chat_client():
|
||||
async def _inner_get_streaming_response(
|
||||
self, *, messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
):
|
||||
yield ChatResponseUpdate(text="Hello", role=ChatRole.ASSISTANT)
|
||||
yield ChatResponseUpdate(text=" world", role=ChatRole.ASSISTANT)
|
||||
yield ChatResponseUpdate(text="Hello", role=Role.ASSISTANT)
|
||||
yield ChatResponseUpdate(text=" world", role=Role.ASSISTANT)
|
||||
|
||||
return MockChatClient()
|
||||
|
||||
@@ -393,7 +393,7 @@ async def test_telemetry_disabled_bypasses_instrumentation(mock_chat_client, mod
|
||||
decorated_class = use_telemetry(type(mock_chat_client))
|
||||
client = decorated_class()
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test message")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test message")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -412,7 +412,7 @@ async def test_instrumentation_enabled(mock_chat_client, model_diagnostic_settin
|
||||
decorated_class = use_telemetry(type(mock_chat_client))
|
||||
client = decorated_class()
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test message")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test message")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -432,7 +432,7 @@ async def test_streaming_response_with_diagnostics_enabled_via_decorator(mock_ch
|
||||
"""Test streaming telemetry through the use_telemetry decorator."""
|
||||
decorated_class = use_telemetry(type(mock_chat_client))
|
||||
client = decorated_class()
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -470,7 +470,7 @@ async def test_streaming_response_with_exception_via_decorator(mock_chat_client,
|
||||
async def _inner_get_streaming_response(
|
||||
self, *, messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterable[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(text="Partial", role=ChatRole.ASSISTANT)
|
||||
yield ChatResponseUpdate(text="Partial", role=Role.ASSISTANT)
|
||||
raise ValueError("Test streaming error")
|
||||
|
||||
type(mock_chat_client)._inner_get_streaming_response = _inner_get_streaming_response
|
||||
@@ -478,7 +478,7 @@ async def test_streaming_response_with_exception_via_decorator(mock_chat_client,
|
||||
decorated_class = use_telemetry(type(mock_chat_client))
|
||||
client = decorated_class()
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -513,12 +513,12 @@ async def test_streaming_response_diagnostics_disabled_via_decorator(model_diagn
|
||||
async def _inner_get_streaming_response(
|
||||
self, *, messages: MutableSequence[ChatMessage], chat_options: ChatOptions, **kwargs: Any
|
||||
) -> AsyncIterable[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(text="Test", role=ChatRole.ASSISTANT)
|
||||
yield ChatResponseUpdate(text="Test", role=Role.ASSISTANT)
|
||||
|
||||
decorated_class = use_telemetry(MockStreamingClientNoDiagnostics)
|
||||
client = decorated_class()
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -561,7 +561,7 @@ async def test_empty_streaming_response_via_decorator(model_diagnostic_settings)
|
||||
decorated_class = use_telemetry(MockEmptyStreamingClient)
|
||||
client = decorated_class()
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Test")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Test")]
|
||||
chat_options = ChatOptions()
|
||||
|
||||
with (
|
||||
@@ -617,7 +617,7 @@ def test_prepend_user_agent_with_none_value():
|
||||
|
||||
|
||||
def test_agent_decorator_with_valid_class():
|
||||
"""Test that agent decorator works with a valid ChatClientAgent-like class."""
|
||||
"""Test that agent decorator works with a valid ChatAgent-like class."""
|
||||
from agent_framework.telemetry import use_agent_telemetry
|
||||
|
||||
# Create a mock class with the required methods
|
||||
@@ -633,7 +633,7 @@ def test_agent_decorator_with_valid_class():
|
||||
async def run(self, messages=None, *, thread=None, **kwargs):
|
||||
return Mock()
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs):
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs):
|
||||
async def gen():
|
||||
yield Mock()
|
||||
|
||||
@@ -644,7 +644,7 @@ def test_agent_decorator_with_valid_class():
|
||||
|
||||
# Check that the methods were wrapped
|
||||
assert hasattr(decorated_class.run, "__model_diagnostics_agent_run__")
|
||||
assert hasattr(decorated_class.run_streaming, "__model_diagnostics_streaming_agent_run__")
|
||||
assert hasattr(decorated_class.run_stream, "__model_diagnostics_streaming_agent_run__")
|
||||
|
||||
|
||||
def test_agent_decorator_with_missing_methods():
|
||||
@@ -680,7 +680,7 @@ def test_agent_decorator_with_partial_methods():
|
||||
|
||||
# Only the present method should be wrapped
|
||||
assert hasattr(decorated_class.run, "__model_diagnostics_agent_run__")
|
||||
assert not hasattr(decorated_class, "run_streaming")
|
||||
assert not hasattr(decorated_class, "run_stream")
|
||||
|
||||
|
||||
# region Test agent telemetry decorator with mock agent
|
||||
@@ -689,7 +689,7 @@ def test_agent_decorator_with_partial_methods():
|
||||
@pytest.fixture
|
||||
def mock_chat_client_agent():
|
||||
"""Create a mock chat client agent for testing."""
|
||||
from agent_framework import AgentRunResponse, ChatMessage, ChatRole, UsageDetails
|
||||
from agent_framework import AgentRunResponse, ChatMessage, Role, UsageDetails
|
||||
|
||||
class MockChatClientAgent:
|
||||
AGENT_SYSTEM_NAME = "test_agent_system"
|
||||
@@ -702,17 +702,17 @@ def mock_chat_client_agent():
|
||||
|
||||
async def run(self, messages=None, *, thread=None, **kwargs):
|
||||
return AgentRunResponse(
|
||||
messages=[ChatMessage(role=ChatRole.ASSISTANT, text="Agent response")],
|
||||
messages=[ChatMessage(role=Role.ASSISTANT, text="Agent response")],
|
||||
usage_details=UsageDetails(input_token_count=15, output_token_count=25),
|
||||
response_id="test_response_id",
|
||||
raw_representation=Mock(finish_reason=Mock(value="stop")),
|
||||
)
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs):
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs):
|
||||
from agent_framework import AgentRunResponseUpdate
|
||||
|
||||
yield AgentRunResponseUpdate(text="Hello", role=ChatRole.ASSISTANT)
|
||||
yield AgentRunResponseUpdate(text=" from agent", role=ChatRole.ASSISTANT)
|
||||
yield AgentRunResponseUpdate(text="Hello", role=Role.ASSISTANT)
|
||||
yield AgentRunResponseUpdate(text=" from agent", role=Role.ASSISTANT)
|
||||
|
||||
return MockChatClientAgent()
|
||||
|
||||
@@ -778,7 +778,7 @@ async def test_agent_streaming_response_with_diagnostics_enabled_via_decorator(
|
||||
|
||||
# Collect all yielded updates
|
||||
updates = []
|
||||
async for update in agent.run_streaming("Test message"):
|
||||
async for update in agent.run_stream("Test message"):
|
||||
updates.append(update)
|
||||
|
||||
# Verify we got the expected updates
|
||||
@@ -795,13 +795,13 @@ async def test_agent_streaming_response_with_exception_via_decorator(mock_chat_c
|
||||
"""Test agent streaming telemetry exception handling through decorator."""
|
||||
from agent_framework.telemetry import use_agent_telemetry
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs):
|
||||
from agent_framework import AgentRunResponseUpdate, ChatRole
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs):
|
||||
from agent_framework import AgentRunResponseUpdate, Role
|
||||
|
||||
yield AgentRunResponseUpdate(text="Partial", role=ChatRole.ASSISTANT)
|
||||
yield AgentRunResponseUpdate(text="Partial", role=Role.ASSISTANT)
|
||||
raise ValueError("Test agent streaming error")
|
||||
|
||||
type(mock_chat_client_agent).run_streaming = run_streaming
|
||||
type(mock_chat_client_agent).run_stream = run_stream
|
||||
|
||||
decorated_class = use_agent_telemetry(type(mock_chat_client_agent))
|
||||
agent = decorated_class()
|
||||
@@ -819,7 +819,7 @@ async def test_agent_streaming_response_with_exception_via_decorator(mock_chat_c
|
||||
|
||||
# Should raise the exception and call error handler
|
||||
with pytest.raises(ValueError, match="Test agent streaming error"):
|
||||
async for _ in agent.run_streaming("Test message"):
|
||||
async for _ in agent.run_stream("Test message"):
|
||||
pass
|
||||
|
||||
# Verify error was recorded
|
||||
@@ -830,7 +830,7 @@ async def test_agent_streaming_response_with_exception_via_decorator(mock_chat_c
|
||||
@pytest.mark.parametrize("model_diagnostic_settings", [(False, False)], indirect=True)
|
||||
async def test_agent_streaming_response_diagnostics_disabled_via_decorator(model_diagnostic_settings):
|
||||
"""Test agent streaming response when diagnostics are disabled."""
|
||||
from agent_framework import AgentRunResponseUpdate, ChatRole
|
||||
from agent_framework import AgentRunResponseUpdate, Role
|
||||
from agent_framework.telemetry import use_agent_telemetry
|
||||
|
||||
class MockStreamingAgentNoDiagnostics:
|
||||
@@ -841,8 +841,8 @@ async def test_agent_streaming_response_diagnostics_disabled_via_decorator(model
|
||||
self.name = "test_agent"
|
||||
self.display_name = "Test Agent"
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs):
|
||||
yield AgentRunResponseUpdate(text="Test", role=ChatRole.ASSISTANT)
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs):
|
||||
yield AgentRunResponseUpdate(text="Test", role=Role.ASSISTANT)
|
||||
|
||||
decorated_class = use_agent_telemetry(MockStreamingAgentNoDiagnostics)
|
||||
agent = decorated_class()
|
||||
@@ -853,7 +853,7 @@ async def test_agent_streaming_response_diagnostics_disabled_via_decorator(model
|
||||
):
|
||||
# Should not create spans when diagnostics are disabled
|
||||
updates = []
|
||||
async for update in agent.run_streaming("Test message"):
|
||||
async for update in agent.run_stream("Test message"):
|
||||
updates.append(update)
|
||||
|
||||
assert len(updates) == 1
|
||||
@@ -874,7 +874,7 @@ async def test_agent_empty_streaming_response_via_decorator(model_diagnostic_set
|
||||
self.name = "test_agent"
|
||||
self.display_name = "Test Agent"
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs):
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs):
|
||||
# Return empty stream
|
||||
return
|
||||
yield # This will never be reached
|
||||
@@ -895,7 +895,7 @@ async def test_agent_empty_streaming_response_via_decorator(model_diagnostic_set
|
||||
|
||||
# Should handle empty stream gracefully
|
||||
updates = []
|
||||
async for update in agent.run_streaming("Test message"):
|
||||
async for update in agent.run_stream("Test message"):
|
||||
updates.append(update)
|
||||
|
||||
assert len(updates) == 0
|
||||
@@ -943,16 +943,16 @@ async def test_agent_run_with_thread_and_kwargs(mock_chat_client_agent, model_di
|
||||
@pytest.mark.parametrize("model_diagnostic_settings", [(True, False)], indirect=True)
|
||||
async def test_agent_run_with_list_messages(mock_chat_client_agent, model_diagnostic_settings):
|
||||
"""Test agent run with list of messages."""
|
||||
from agent_framework import ChatMessage, ChatRole
|
||||
from agent_framework import ChatMessage, Role
|
||||
from agent_framework.telemetry import use_agent_telemetry
|
||||
|
||||
decorated_class = use_agent_telemetry(type(mock_chat_client_agent))
|
||||
agent = decorated_class()
|
||||
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.USER, text="First message"),
|
||||
ChatMessage(role=ChatRole.ASSISTANT, text="Response"),
|
||||
ChatMessage(role=ChatRole.USER, text="Second message"),
|
||||
ChatMessage(role=Role.USER, text="First message"),
|
||||
ChatMessage(role=Role.ASSISTANT, text="Response"),
|
||||
ChatMessage(role=Role.USER, text="Second message"),
|
||||
]
|
||||
|
||||
with (
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from agent_framework import AgentThread, ChatMessage, ChatMessageList, ChatRole
|
||||
from agent_framework import AgentThread, ChatMessage, ChatMessageList, Role
|
||||
from agent_framework._threads import StoreState, ThreadState, deserialize_thread_state, thread_on_new_messages
|
||||
|
||||
|
||||
@@ -37,16 +37,16 @@ class MockChatMessageStore:
|
||||
def sample_messages() -> list[ChatMessage]:
|
||||
"""Fixture providing sample chat messages for testing."""
|
||||
return [
|
||||
ChatMessage(role=ChatRole.USER, text="Hello", message_id="msg1"),
|
||||
ChatMessage(role=ChatRole.ASSISTANT, text="Hi there!", message_id="msg2"),
|
||||
ChatMessage(role=ChatRole.USER, text="How are you?", message_id="msg3"),
|
||||
ChatMessage(role=Role.USER, text="Hello", message_id="msg1"),
|
||||
ChatMessage(role=Role.ASSISTANT, text="Hi there!", message_id="msg2"),
|
||||
ChatMessage(role=Role.USER, text="How are you?", message_id="msg3"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_message() -> ChatMessage:
|
||||
"""Fixture providing a single sample chat message for testing."""
|
||||
return ChatMessage(role=ChatRole.USER, text="Test message", message_id="test1")
|
||||
return ChatMessage(role=Role.USER, text="Test message", message_id="test1")
|
||||
|
||||
|
||||
class TestAgentThread:
|
||||
@@ -171,7 +171,7 @@ class TestAgentThread:
|
||||
|
||||
async def test_on_new_messages_with_existing_store(self, sample_message: ChatMessage) -> None:
|
||||
"""Test _on_new_messages adds to existing message store."""
|
||||
initial_messages = [ChatMessage(role=ChatRole.USER, text="Initial", message_id="init1")]
|
||||
initial_messages = [ChatMessage(role=Role.USER, text="Initial", message_id="init1")]
|
||||
store = ChatMessageList(initial_messages)
|
||||
thread = AgentThread(message_store=store)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agent_framework import AIFunction, AITool, HostedCodeInterpreterTool, ai_function
|
||||
from agent_framework import AIFunction, HostedCodeInterpreterTool, ToolProtocol, ai_function
|
||||
from agent_framework._tools import _parse_inputs
|
||||
from agent_framework.telemetry import GenAIAttributes
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_ai_function_decorator():
|
||||
"""A simple function that adds two numbers."""
|
||||
return x + y
|
||||
|
||||
assert isinstance(test_tool, AITool)
|
||||
assert isinstance(test_tool, ToolProtocol)
|
||||
assert isinstance(test_tool, AIFunction)
|
||||
assert test_tool.name == "test_tool"
|
||||
assert test_tool.description == "A test tool"
|
||||
@@ -39,7 +39,7 @@ def test_ai_function_decorator_without_args():
|
||||
"""A simple function that adds two numbers."""
|
||||
return x + y
|
||||
|
||||
assert isinstance(test_tool, AITool)
|
||||
assert isinstance(test_tool, ToolProtocol)
|
||||
assert isinstance(test_tool, AIFunction)
|
||||
assert test_tool.name == "test_tool"
|
||||
assert test_tool.description == "A simple function that adds two numbers."
|
||||
@@ -60,7 +60,7 @@ async def test_ai_function_decorator_with_async():
|
||||
"""An async function that adds two numbers."""
|
||||
return x + y
|
||||
|
||||
assert isinstance(async_test_tool, AITool)
|
||||
assert isinstance(async_test_tool, ToolProtocol)
|
||||
assert isinstance(async_test_tool, AIFunction)
|
||||
assert async_test_tool.name == "async_test_tool"
|
||||
assert async_test_tool.description == "An async test tool"
|
||||
@@ -399,7 +399,7 @@ def test_parse_inputs_data_dict():
|
||||
|
||||
|
||||
def test_parse_inputs_ai_contents_instance():
|
||||
"""Test _parse_inputs with AIContents instance."""
|
||||
"""Test _parse_inputs with Contents instance."""
|
||||
from agent_framework import TextContent
|
||||
|
||||
text_content = TextContent(text="Hello, world!")
|
||||
@@ -418,7 +418,7 @@ def test_parse_inputs_mixed_list():
|
||||
"http://example.com", # string
|
||||
{"uri": "https://test.org", "media_type": "text/html"}, # URI dict
|
||||
{"file_id": "file-456"}, # hosted file dict
|
||||
TextContent(text="Hello"), # AIContents instance
|
||||
TextContent(text="Hello"), # Contents instance
|
||||
]
|
||||
|
||||
result = _parse_inputs(inputs)
|
||||
@@ -477,7 +477,7 @@ def test_hosted_code_interpreter_tool_with_dict_inputs():
|
||||
|
||||
|
||||
def test_hosted_code_interpreter_tool_with_ai_contents():
|
||||
"""Test HostedCodeInterpreterTool with AIContents instances."""
|
||||
"""Test HostedCodeInterpreterTool with Contents instances."""
|
||||
from agent_framework import DataContent, TextContent
|
||||
|
||||
inputs = [TextContent(text="Hello, world!"), DataContent(data=b"test", media_type="text/plain")]
|
||||
|
||||
@@ -9,32 +9,30 @@ from pytest import fixture, mark, raises
|
||||
from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AIAnnotation,
|
||||
AIContent,
|
||||
AIContents,
|
||||
AIFunction,
|
||||
AITool,
|
||||
AnnotatedRegion,
|
||||
ChatFinishReason,
|
||||
BaseContent,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
ChatToolMode,
|
||||
CitationAnnotation,
|
||||
Contents,
|
||||
DataContent,
|
||||
ErrorContent,
|
||||
FinishReason,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
GeneratedEmbeddings,
|
||||
HostedFileContent,
|
||||
HostedVectorStoreContent,
|
||||
Role,
|
||||
SpeechToTextOptions,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
TextSpanRegion,
|
||||
TextToSpeechOptions,
|
||||
ToolProtocol,
|
||||
UriContent,
|
||||
UsageContent,
|
||||
UsageDetails,
|
||||
@@ -43,8 +41,8 @@ from agent_framework import (
|
||||
|
||||
|
||||
@fixture
|
||||
def ai_tool() -> AITool:
|
||||
"""Returns a generic AITool."""
|
||||
def ai_tool() -> ToolProtocol:
|
||||
"""Returns a generic ToolProtocol."""
|
||||
|
||||
class GenericTool(BaseModel):
|
||||
name: str
|
||||
@@ -61,8 +59,8 @@ def ai_tool() -> AITool:
|
||||
|
||||
|
||||
@fixture
|
||||
def ai_function_tool() -> AITool:
|
||||
"""Returns a executable AITool."""
|
||||
def ai_function_tool() -> ToolProtocol:
|
||||
"""Returns a executable ToolProtocol."""
|
||||
|
||||
@ai_function
|
||||
def simple_function(x: int, y: int) -> int:
|
||||
@@ -76,7 +74,7 @@ def ai_function_tool() -> AITool:
|
||||
|
||||
|
||||
def test_text_content_positional():
|
||||
"""Test the TextContent class to ensure it initializes correctly and inherits from AIContent."""
|
||||
"""Test the TextContent class to ensure it initializes correctly and inherits from BaseContent."""
|
||||
# Create an instance of TextContent
|
||||
content = TextContent("Hello, world!", raw_representation="Hello, world!", additional_properties={"version": 1})
|
||||
|
||||
@@ -85,14 +83,14 @@ def test_text_content_positional():
|
||||
assert content.text == "Hello, world!"
|
||||
assert content.raw_representation == "Hello, world!"
|
||||
assert content.additional_properties["version"] == 1
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
with raises(ValidationError):
|
||||
content.type = "ai"
|
||||
|
||||
|
||||
def test_text_content_keyword():
|
||||
"""Test the TextContent class to ensure it initializes correctly and inherits from AIContent."""
|
||||
"""Test the TextContent class to ensure it initializes correctly and inherits from BaseContent."""
|
||||
# Create an instance of TextContent
|
||||
content = TextContent(
|
||||
text="Hello, world!", raw_representation="Hello, world!", additional_properties={"version": 1}
|
||||
@@ -103,8 +101,8 @@ def test_text_content_keyword():
|
||||
assert content.text == "Hello, world!"
|
||||
assert content.raw_representation == "Hello, world!"
|
||||
assert content.additional_properties["version"] == 1
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
with raises(ValidationError):
|
||||
content.type = "ai"
|
||||
|
||||
@@ -124,8 +122,8 @@ def test_data_content_bytes():
|
||||
assert content.has_top_level_media_type("image") is False
|
||||
assert content.additional_properties["version"] == 1
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
def test_data_content_uri():
|
||||
@@ -140,8 +138,8 @@ def test_data_content_uri():
|
||||
assert content.has_top_level_media_type("application") is False
|
||||
assert content.additional_properties["version"] == 1
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
def test_data_content_invalid():
|
||||
@@ -185,8 +183,8 @@ def test_uri_content():
|
||||
assert content.has_top_level_media_type("application") is False
|
||||
assert content.additional_properties["version"] == 1
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
# region: HostedFileContent
|
||||
@@ -201,8 +199,8 @@ def test_hosted_file_content():
|
||||
assert content.file_id == "file-123"
|
||||
assert content.additional_properties["version"] == 1
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
def test_hosted_file_content_minimal():
|
||||
@@ -215,8 +213,8 @@ def test_hosted_file_content_minimal():
|
||||
assert content.additional_properties is None
|
||||
assert content.raw_representation is None
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
# region: HostedVectorStoreContent
|
||||
@@ -231,9 +229,9 @@ def test_hosted_vector_store_content():
|
||||
assert content.vector_store_id == "vs-789"
|
||||
assert content.additional_properties["version"] == 1
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, HostedVectorStoreContent)
|
||||
assert isinstance(content, AIContent)
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
def test_hosted_vector_store_content_minimal():
|
||||
@@ -246,9 +244,9 @@ def test_hosted_vector_store_content_minimal():
|
||||
assert content.additional_properties is None
|
||||
assert content.raw_representation is None
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, HostedVectorStoreContent)
|
||||
assert isinstance(content, AIContent)
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
# region FunctionCallContent
|
||||
@@ -263,8 +261,8 @@ def test_function_call_content():
|
||||
assert content.name == "example_function"
|
||||
assert content.arguments == {"param1": "value1"}
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
def test_function_call_content_parse_arguments():
|
||||
@@ -315,8 +313,8 @@ def test_function_result_content():
|
||||
assert content.type == "function_result"
|
||||
assert content.result == {"param1": "value1"}
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(content, AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(content, BaseContent)
|
||||
|
||||
|
||||
# region UsageDetails
|
||||
@@ -381,7 +379,7 @@ def test_usage_details_add_with_none_and_type_errors():
|
||||
u += 42 # type: ignore[arg-type]
|
||||
|
||||
|
||||
# region AIContent Serialization
|
||||
# region BaseContent Serialization
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
@@ -396,14 +394,14 @@ def test_usage_details_add_with_none_and_type_errors():
|
||||
(HostedVectorStoreContent, {"vector_store_id": "vs-789"}),
|
||||
],
|
||||
)
|
||||
def test_ai_content_serialization(content_type: type[AIContent], args: dict):
|
||||
def test_ai_content_serialization(content_type: type[BaseContent], args: dict):
|
||||
content = content_type(**args)
|
||||
serialized = content.model_dump()
|
||||
deserialized = content_type.model_validate(serialized)
|
||||
assert deserialized == content
|
||||
|
||||
class TestModel(BaseModel):
|
||||
content: AIContents
|
||||
content: Contents
|
||||
|
||||
test_item = TestModel.model_validate({"content": serialized})
|
||||
|
||||
@@ -419,14 +417,14 @@ def test_chat_message_text():
|
||||
message = ChatMessage(role="user", text="Hello, how are you?")
|
||||
|
||||
# Check the type and content
|
||||
assert message.role == ChatRole.USER
|
||||
assert message.role == Role.USER
|
||||
assert len(message.contents) == 1
|
||||
assert isinstance(message.contents[0], TextContent)
|
||||
assert message.contents[0].text == "Hello, how are you?"
|
||||
assert message.text == "Hello, how are you?"
|
||||
|
||||
# Ensure the instance is of type AIContent
|
||||
assert isinstance(message.contents[0], AIContent)
|
||||
# Ensure the instance is of type BaseContent
|
||||
assert isinstance(message.contents[0], BaseContent)
|
||||
|
||||
|
||||
def test_chat_message_contents():
|
||||
@@ -437,7 +435,7 @@ def test_chat_message_contents():
|
||||
message = ChatMessage(role="user", contents=[content1, content2])
|
||||
|
||||
# Check the type and content
|
||||
assert message.role == ChatRole.USER
|
||||
assert message.role == Role.USER
|
||||
assert len(message.contents) == 2
|
||||
assert isinstance(message.contents[0], TextContent)
|
||||
assert isinstance(message.contents[1], TextContent)
|
||||
@@ -447,8 +445,8 @@ def test_chat_message_contents():
|
||||
|
||||
|
||||
def test_chat_message_with_chatrole_instance():
|
||||
m = ChatMessage(role=ChatRole.USER, text="hi")
|
||||
assert m.role == ChatRole.USER
|
||||
m = ChatMessage(role=Role.USER, text="hi")
|
||||
assert m.role == Role.USER
|
||||
assert m.text == "hi"
|
||||
|
||||
|
||||
@@ -464,7 +462,7 @@ def test_chat_response():
|
||||
response = ChatResponse(messages=message)
|
||||
|
||||
# Check the type and content
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == "I'm doing well, thank you!"
|
||||
assert isinstance(response.messages[0], ChatMessage)
|
||||
# __str__ returns text
|
||||
@@ -484,7 +482,7 @@ def test_chat_response_with_format():
|
||||
response = ChatResponse(messages=message)
|
||||
|
||||
# Check the type and content
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == '{"response": "Hello"}'
|
||||
assert isinstance(response.messages[0], ChatMessage)
|
||||
assert response.text == '{"response": "Hello"}'
|
||||
@@ -503,7 +501,7 @@ def test_chat_response_with_format_init():
|
||||
response = ChatResponse(messages=message, response_format=OutputModel)
|
||||
|
||||
# Check the type and content
|
||||
assert response.messages[0].role == ChatRole.ASSISTANT
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert response.messages[0].text == '{"response": "Hello"}'
|
||||
assert isinstance(response.messages[0], ChatMessage)
|
||||
assert response.text == '{"response": "Hello"}'
|
||||
@@ -767,7 +765,7 @@ def test_chat_options_init_with_args(ai_function_tool, ai_tool) -> None:
|
||||
assert options.frequency_penalty == 0.0
|
||||
assert options.user == "user-123"
|
||||
for tool in options.tools:
|
||||
assert isinstance(tool, AITool)
|
||||
assert isinstance(tool, ToolProtocol)
|
||||
assert tool.name is not None
|
||||
assert tool.description is not None
|
||||
if isinstance(tool, AIFunction):
|
||||
@@ -809,7 +807,7 @@ def test_chat_options_and(ai_function_tool, ai_tool) -> None:
|
||||
|
||||
@fixture
|
||||
def chat_message() -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.USER, text="Hello")
|
||||
return ChatMessage(role=Role.USER, text="Hello")
|
||||
|
||||
|
||||
@fixture
|
||||
@@ -824,7 +822,7 @@ def agent_run_response(chat_message: ChatMessage) -> AgentRunResponse:
|
||||
|
||||
@fixture
|
||||
def agent_run_response_update(text_content: TextContent) -> AgentRunResponseUpdate:
|
||||
return AgentRunResponseUpdate(role=ChatRole.ASSISTANT, contents=[text_content])
|
||||
return AgentRunResponseUpdate(role=Role.ASSISTANT, contents=[text_content])
|
||||
|
||||
|
||||
# region AgentRunResponse
|
||||
@@ -914,18 +912,16 @@ def test_error_content_str():
|
||||
|
||||
def test_annotations_models_and_roundtrip():
|
||||
span = TextSpanRegion(start_index=0, end_index=5)
|
||||
base_region = AnnotatedRegion()
|
||||
ann: AIAnnotation = AIAnnotation(annotated_regions=[span, base_region])
|
||||
cit = CitationAnnotation(title="Doc", url="http://example.com", snippet="Snippet", annotated_regions=[span])
|
||||
|
||||
# Attach to content
|
||||
content = TextContent(text="hello", additional_properties={"v": 1})
|
||||
content.annotations = [ann, cit]
|
||||
content.annotations = [cit]
|
||||
|
||||
dumped = content.model_dump()
|
||||
loaded = TextContent.model_validate(dumped)
|
||||
assert isinstance(loaded.annotations, list)
|
||||
assert len(loaded.annotations) == 2
|
||||
assert len(loaded.annotations) == 1
|
||||
assert isinstance(loaded.annotations[0], dict) is False # pydantic parsed into models
|
||||
# discriminators preserved
|
||||
assert any(getattr(a, "type", None) == "citation" for a in loaded.annotations)
|
||||
@@ -1032,16 +1028,16 @@ def test_generated_embeddings_operations():
|
||||
assert g.additional_properties == {}
|
||||
|
||||
|
||||
# region ChatRole & ChatFinishReason basics
|
||||
# region Role & FinishReason basics
|
||||
|
||||
|
||||
def test_chat_role_str_and_repr():
|
||||
assert str(ChatRole.USER) == "user"
|
||||
assert "ChatRole(value=" in repr(ChatRole.USER)
|
||||
assert str(Role.USER) == "user"
|
||||
assert "Role(value=" in repr(Role.USER)
|
||||
|
||||
|
||||
def test_chat_finish_reason_constants():
|
||||
assert ChatFinishReason.STOP.value == "stop"
|
||||
assert FinishReason.STOP.value == "stop"
|
||||
|
||||
|
||||
def test_response_update_propagates_fields_and_metadata():
|
||||
@@ -1054,7 +1050,7 @@ def test_response_update_propagates_fields_and_metadata():
|
||||
conversation_id="cid",
|
||||
ai_model_id="model-x",
|
||||
created_at="t0",
|
||||
finish_reason=ChatFinishReason.STOP,
|
||||
finish_reason=FinishReason.STOP,
|
||||
additional_properties={"k": "v"},
|
||||
)
|
||||
resp = ChatResponse.from_chat_response_updates([upd])
|
||||
@@ -1062,9 +1058,9 @@ def test_response_update_propagates_fields_and_metadata():
|
||||
assert resp.created_at == "t0"
|
||||
assert resp.conversation_id == "cid"
|
||||
assert resp.ai_model_id == "model-x"
|
||||
assert resp.finish_reason == ChatFinishReason.STOP
|
||||
assert resp.finish_reason == FinishReason.STOP
|
||||
assert resp.additional_properties and resp.additional_properties["k"] == "v"
|
||||
assert resp.messages[0].role == ChatRole.ASSISTANT
|
||||
assert resp.messages[0].role == Role.ASSISTANT
|
||||
assert resp.messages[0].author_name == "bot"
|
||||
assert resp.messages[0].message_id == "mid"
|
||||
|
||||
|
||||
@@ -14,19 +14,19 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
ChatToolMode,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileSearchTool,
|
||||
HostedVectorStoreContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UriContent,
|
||||
UsageContent,
|
||||
@@ -125,7 +125,7 @@ def test_openai_assistants_client_init_with_client(mock_async_openai: MagicMock)
|
||||
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, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_openai_assistants_client_init_auto_create_client(
|
||||
@@ -185,7 +185,7 @@ def test_openai_assistants_client_init_with_default_headers(openai_unit_test_env
|
||||
)
|
||||
|
||||
assert chat_client.ai_model_id == "gpt-4"
|
||||
assert isinstance(chat_client, ChatClient)
|
||||
assert isinstance(chat_client, ChatClientProtocol)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
@@ -412,7 +412,7 @@ async def test_openai_assistants_client_process_stream_events_thread_run_created
|
||||
update = updates[0]
|
||||
assert isinstance(update, ChatResponseUpdate)
|
||||
assert update.conversation_id == thread_id
|
||||
assert update.role == ChatRole.ASSISTANT
|
||||
assert update.role == Role.ASSISTANT
|
||||
assert update.contents == []
|
||||
assert update.raw_representation == mock_response.data
|
||||
|
||||
@@ -457,7 +457,7 @@ async def test_openai_assistants_client_process_stream_events_message_delta_text
|
||||
update = updates[0]
|
||||
assert isinstance(update, ChatResponseUpdate)
|
||||
assert update.conversation_id == thread_id
|
||||
assert update.role == ChatRole.ASSISTANT
|
||||
assert update.role == Role.ASSISTANT
|
||||
assert update.text == "Hello from assistant"
|
||||
assert update.raw_representation == mock_message_delta
|
||||
|
||||
@@ -497,7 +497,7 @@ async def test_openai_assistants_client_process_stream_events_requires_action(mo
|
||||
update = updates[0]
|
||||
assert isinstance(update, ChatResponseUpdate)
|
||||
assert update.conversation_id == thread_id
|
||||
assert update.role == ChatRole.ASSISTANT
|
||||
assert update.role == Role.ASSISTANT
|
||||
assert len(update.contents) == 1
|
||||
assert update.contents[0] == test_function_content
|
||||
assert update.raw_representation == mock_run
|
||||
@@ -579,7 +579,7 @@ async def test_openai_assistants_client_process_stream_events_run_completed_with
|
||||
update = updates[0]
|
||||
assert isinstance(update, ChatResponseUpdate)
|
||||
assert update.conversation_id == thread_id
|
||||
assert update.role == ChatRole.ASSISTANT
|
||||
assert update.role == Role.ASSISTANT
|
||||
assert len(update.contents) == 1
|
||||
|
||||
# Check the usage content
|
||||
@@ -632,7 +632,7 @@ def test_openai_assistants_client_create_run_options_basic(mock_async_openai: Ma
|
||||
top_p=0.9,
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -661,7 +661,7 @@ def test_openai_assistants_client_create_run_options_with_ai_function_tool(mock_
|
||||
tool_choice="auto",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -686,7 +686,7 @@ def test_openai_assistants_client_create_run_options_with_code_interpreter(mock_
|
||||
tool_choice="auto",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Calculate something")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Calculate something")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -706,7 +706,7 @@ def test_openai_assistants_client_create_run_options_tool_choice_none(mock_async
|
||||
tool_choice="none",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -727,7 +727,7 @@ def test_openai_assistants_client_create_run_options_required_function(mock_asyn
|
||||
tool_choice=tool_choice,
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Hello")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Hello")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -753,7 +753,7 @@ def test_openai_assistants_client_create_run_options_with_file_search_tool(mock_
|
||||
tool_choice="auto",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Search for information")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Search for information")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -778,7 +778,7 @@ def test_openai_assistants_client_create_run_options_with_mapping_tool(mock_asyn
|
||||
tool_choice="auto",
|
||||
)
|
||||
|
||||
messages = [ChatMessage(role=ChatRole.USER, text="Use custom tool")]
|
||||
messages = [ChatMessage(role=Role.USER, text="Use custom tool")]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, chat_options) # type: ignore
|
||||
@@ -795,8 +795,8 @@ def test_openai_assistants_client_create_run_options_with_system_message(mock_as
|
||||
chat_client = create_test_openai_assistants_client(mock_async_openai)
|
||||
|
||||
messages = [
|
||||
ChatMessage(role=ChatRole.SYSTEM, text="You are a helpful assistant."),
|
||||
ChatMessage(role=ChatRole.USER, text="Hello"),
|
||||
ChatMessage(role=Role.SYSTEM, text="You are a helpful assistant."),
|
||||
ChatMessage(role=Role.USER, text="Hello"),
|
||||
]
|
||||
|
||||
# Call the method
|
||||
@@ -816,7 +816,7 @@ def test_openai_assistants_client_create_run_options_with_image_content(mock_asy
|
||||
|
||||
# Create message with image content
|
||||
image_content = UriContent(uri="https://example.com/image.jpg", media_type="image/jpeg")
|
||||
messages = [ChatMessage(role=ChatRole.USER, contents=[image_content])]
|
||||
messages = [ChatMessage(role=Role.USER, contents=[image_content])]
|
||||
|
||||
# Call the method
|
||||
run_options, tool_results = chat_client._create_run_options(messages, None) # type: ignore
|
||||
@@ -924,7 +924,7 @@ def get_weather(
|
||||
async def test_openai_assistants_client_get_response() -> None:
|
||||
"""Test OpenAI Assistants Client response."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -948,7 +948,7 @@ async def test_openai_assistants_client_get_response() -> None:
|
||||
async def test_openai_assistants_client_get_response_tools() -> None:
|
||||
"""Test OpenAI Assistants Client response with tools."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -969,7 +969,7 @@ async def test_openai_assistants_client_get_response_tools() -> None:
|
||||
async def test_openai_assistants_client_streaming() -> None:
|
||||
"""Test OpenAI Assistants Client streaming response."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -999,7 +999,7 @@ async def test_openai_assistants_client_streaming() -> None:
|
||||
async def test_openai_assistants_client_streaming_tools() -> None:
|
||||
"""Test OpenAI Assistants Client streaming response with tools."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like in Seattle?"))
|
||||
@@ -1035,7 +1035,7 @@ async def test_openai_assistants_client_with_existing_assistant() -> None:
|
||||
async with OpenAIAssistantsClient(
|
||||
ai_model_id="gpt-4o-mini", assistant_id=assistant_id
|
||||
) as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
assert openai_assistants_client.assistant_id == assistant_id
|
||||
|
||||
messages = [ChatMessage(role="user", text="What can you do?")]
|
||||
@@ -1052,7 +1052,7 @@ async def test_openai_assistants_client_with_existing_assistant() -> None:
|
||||
async def test_openai_assistants_client_file_search() -> None:
|
||||
"""Test OpenAI Assistants Client response."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like today?"))
|
||||
@@ -1074,7 +1074,7 @@ async def test_openai_assistants_client_file_search() -> None:
|
||||
async def test_openai_assistants_client_file_search_streaming() -> None:
|
||||
"""Test OpenAI Assistants Client response."""
|
||||
async with OpenAIAssistantsClient() as openai_assistants_client:
|
||||
assert isinstance(openai_assistants_client, ChatClient)
|
||||
assert isinstance(openai_assistants_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What's the weather like today?"))
|
||||
@@ -1101,8 +1101,8 @@ async def test_openai_assistants_client_file_search_streaming() -> None:
|
||||
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_assistants_agent_basic_run():
|
||||
"""Test ChatClientAgent basic run functionality with OpenAIAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic run functionality with OpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
) as agent:
|
||||
# Run a simple query
|
||||
@@ -1117,13 +1117,13 @@ async def test_openai_assistants_agent_basic_run():
|
||||
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_assistants_agent_basic_run_streaming():
|
||||
"""Test ChatClientAgent basic streaming functionality with OpenAIAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent basic streaming functionality with OpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
) as agent:
|
||||
# Run streaming query
|
||||
full_message: str = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert chunk is not None
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
@@ -1136,8 +1136,8 @@ async def test_openai_assistants_agent_basic_run_streaming():
|
||||
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_assistants_agent_thread_persistence():
|
||||
"""Test ChatClientAgent thread persistence across runs with OpenAIAssistantsClient."""
|
||||
async with ChatClientAgent(
|
||||
"""Test ChatAgent thread persistence across runs with OpenAIAssistantsClient."""
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -1164,11 +1164,11 @@ async def test_openai_assistants_agent_thread_persistence():
|
||||
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_assistants_agent_existing_thread_id():
|
||||
"""Test ChatClientAgent with existing thread ID to continue conversations across agent instances."""
|
||||
"""Test ChatAgent 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 ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
@@ -1188,7 +1188,7 @@ async def test_openai_assistants_agent_existing_thread_id():
|
||||
|
||||
# Now continue with the same thread ID in a new agent instance
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(thread_id=existing_thread_id),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=[get_weather],
|
||||
@@ -1208,9 +1208,9 @@ async def test_openai_assistants_agent_existing_thread_id():
|
||||
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_assistants_agent_code_interpreter():
|
||||
"""Test ChatClientAgent with code interpreter through OpenAIAssistantsClient."""
|
||||
"""Test ChatAgent with code interpreter through OpenAIAssistantsClient."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant that can write and execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
@@ -1229,7 +1229,7 @@ async def test_openai_assistants_agent_code_interpreter():
|
||||
async def test_openai_assistants_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with OpenAI Assistants Client."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -1264,7 +1264,7 @@ async def test_openai_assistants_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -10,15 +10,15 @@ from openai import BadRequestError
|
||||
from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AITool,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
HostedWebSearchTool,
|
||||
TextContent,
|
||||
ToolProtocol,
|
||||
ai_function,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError, ServiceResponseException
|
||||
@@ -39,7 +39,7 @@ def test_init(openai_unit_test_env: dict[str, str]) -> None:
|
||||
open_ai_chat_completion = OpenAIChatClient()
|
||||
|
||||
assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
|
||||
assert isinstance(open_ai_chat_completion, ChatClient)
|
||||
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_validation_fail() -> None:
|
||||
@@ -54,7 +54,7 @@ def test_init_ai_model_id_constructor(openai_unit_test_env: dict[str, str]) -> N
|
||||
open_ai_chat_completion = OpenAIChatClient(ai_model_id=ai_model_id)
|
||||
|
||||
assert open_ai_chat_completion.ai_model_id == ai_model_id
|
||||
assert isinstance(open_ai_chat_completion, ChatClient)
|
||||
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -66,7 +66,7 @@ def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
|
||||
)
|
||||
|
||||
assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
|
||||
assert isinstance(open_ai_chat_completion, ChatClient)
|
||||
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
@@ -154,15 +154,15 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
|
||||
"""Test that unsupported tool types are handled correctly."""
|
||||
client = OpenAIChatClient()
|
||||
|
||||
# Create a mock AITool that's not an AIFunction
|
||||
unsupported_tool = MagicMock(spec=AITool)
|
||||
# Create a mock ToolProtocol that's not an AIFunction
|
||||
unsupported_tool = MagicMock(spec=ToolProtocol)
|
||||
unsupported_tool.__class__.__name__ = "UnsupportedAITool"
|
||||
|
||||
# This should ignore the unsupported AITool and return empty list
|
||||
# This should ignore the unsupported ToolProtocol and return empty list
|
||||
result = client._chat_to_tool_spec([unsupported_tool]) # type: ignore
|
||||
assert result == []
|
||||
|
||||
# Also test with a non-AITool that should be converted to dict
|
||||
# Also test with a non-ToolProtocol that should be converted to dict
|
||||
dict_tool = {"type": "function", "name": "test"}
|
||||
result = client._chat_to_tool_spec([dict_tool]) # type: ignore
|
||||
assert result == [dict_tool]
|
||||
@@ -190,7 +190,7 @@ async def test_openai_chat_completion_response() -> None:
|
||||
"""Test OpenAI chat completion responses."""
|
||||
openai_chat_client = OpenAIChatClient()
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -217,7 +217,7 @@ async def test_openai_chat_completion_response_tools() -> None:
|
||||
"""Test OpenAI chat completion responses."""
|
||||
openai_chat_client = OpenAIChatClient()
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
@@ -239,7 +239,7 @@ async def test_openai_chat_client_streaming() -> None:
|
||||
"""Test Azure OpenAI chat completion responses."""
|
||||
openai_chat_client = OpenAIChatClient()
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -274,7 +274,7 @@ async def test_openai_chat_client_streaming_tools() -> None:
|
||||
"""Test AzureOpenAI chat completion responses."""
|
||||
openai_chat_client = OpenAIChatClient()
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="who are Emily and David?"))
|
||||
@@ -301,7 +301,7 @@ async def test_openai_chat_client_web_search() -> None:
|
||||
# Currently only a select few models support web search tool calls
|
||||
openai_chat_client = OpenAIChatClient(ai_model_id="gpt-4o-search-preview")
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
# Test that the client will use the web search tool
|
||||
response = await openai_chat_client.get_response(
|
||||
@@ -340,7 +340,7 @@ async def test_openai_chat_client_web_search() -> None:
|
||||
async def test_openai_chat_client_web_search_streaming() -> None:
|
||||
openai_chat_client = OpenAIChatClient(ai_model_id="gpt-4o-search-preview")
|
||||
|
||||
assert isinstance(openai_chat_client, ChatClient)
|
||||
assert isinstance(openai_chat_client, ChatClientProtocol)
|
||||
|
||||
# Test that the client will use the web search tool
|
||||
response = openai_chat_client.get_streaming_response(
|
||||
@@ -392,7 +392,7 @@ async def test_openai_chat_client_web_search_streaming() -> None:
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_chat_client_agent_basic_run():
|
||||
"""Test OpenAI chat client agent basic run functionality with OpenAIChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
) as agent:
|
||||
# Test basic run
|
||||
@@ -407,12 +407,12 @@ async def test_openai_chat_client_agent_basic_run():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_chat_client_agent_basic_run_streaming():
|
||||
"""Test OpenAI chat client agent basic streaming functionality with OpenAIChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
) as agent:
|
||||
# Test streaming run
|
||||
full_text = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
full_text += chunk.text
|
||||
@@ -424,7 +424,7 @@ async def test_openai_chat_client_agent_basic_run_streaming():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_chat_client_agent_thread_persistence():
|
||||
"""Test OpenAI chat client agent thread persistence across runs with OpenAIChatClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -451,7 +451,7 @@ async def test_openai_chat_client_agent_existing_thread():
|
||||
# First conversation - capture the thread
|
||||
preserved_thread = None
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
@@ -467,7 +467,7 @@ async def test_openai_chat_client_agent_existing_thread():
|
||||
|
||||
# Second conversation - reuse the thread in a new agent instance
|
||||
if preserved_thread:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
@@ -483,7 +483,7 @@ async def test_openai_chat_client_agent_existing_thread():
|
||||
async def test_openai_chat_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with OpenAI Chat Client."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4.1"),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -518,7 +518,7 @@ async def test_openai_chat_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4.1"),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -13,12 +13,11 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
ChatClient,
|
||||
ChatClientAgent,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
@@ -26,6 +25,7 @@ from agent_framework import (
|
||||
HostedFileSearchTool,
|
||||
HostedVectorStoreContent,
|
||||
HostedWebSearchTool,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
UriContent,
|
||||
@@ -87,7 +87,7 @@ def test_init(openai_unit_test_env: dict[str, str]) -> None:
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert openai_responses_client.ai_model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_validation_fail() -> None:
|
||||
@@ -102,7 +102,7 @@ def test_init_ai_model_id_constructor(openai_unit_test_env: dict[str, str]) -> N
|
||||
openai_responses_client = OpenAIResponsesClient(ai_model_id=ai_model_id)
|
||||
|
||||
assert openai_responses_client.ai_model_id == ai_model_id
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
|
||||
def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
|
||||
@@ -114,7 +114,7 @@ def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
|
||||
)
|
||||
|
||||
assert openai_responses_client.ai_model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
# Assert that the default header we added is present in the client's default headers
|
||||
for key, value in default_headers.items():
|
||||
@@ -731,7 +731,7 @@ def test_streaming_response_basic_structure() -> None:
|
||||
|
||||
# Should get a valid ChatResponseUpdate structure
|
||||
assert isinstance(response, ChatResponseUpdate)
|
||||
assert response.role == ChatRole.ASSISTANT
|
||||
assert response.role == Role.ASSISTANT
|
||||
assert response.ai_model_id == "test-model"
|
||||
assert isinstance(response.contents, list)
|
||||
assert response.raw_representation is mock_event
|
||||
@@ -742,7 +742,7 @@ async def test_openai_responses_client_response() -> None:
|
||||
"""Test OpenAI chat completion responses."""
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -785,7 +785,7 @@ async def test_openai_responses_client_response_tools() -> None:
|
||||
"""Test OpenAI chat completion responses."""
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(ChatMessage(role="user", text="What is the weather in New York?"))
|
||||
@@ -824,7 +824,7 @@ async def test_openai_responses_client_streaming() -> None:
|
||||
"""Test Azure OpenAI chat completion responses."""
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = []
|
||||
messages.append(
|
||||
@@ -877,7 +877,7 @@ async def test_openai_responses_client_streaming_tools() -> None:
|
||||
"""Test OpenAI chat completion responses."""
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
messages: list[ChatMessage] = [ChatMessage(role="user", text="What is the weather in Seattle?")]
|
||||
|
||||
@@ -923,7 +923,7 @@ async def test_openai_responses_client_streaming_tools() -> None:
|
||||
async def test_openai_responses_client_web_search() -> None:
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
# Test that the client will use the web search tool
|
||||
response = await openai_responses_client.get_response(
|
||||
@@ -962,7 +962,7 @@ async def test_openai_responses_client_web_search() -> None:
|
||||
async def test_openai_responses_client_web_search_streaming() -> None:
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
# Test that the client will use the web search tool
|
||||
response = openai_responses_client.get_streaming_response(
|
||||
@@ -1015,7 +1015,7 @@ async def test_openai_responses_client_web_search_streaming() -> None:
|
||||
async def test_openai_responses_client_file_search() -> None:
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
file_id, vector_store = await create_vector_store(openai_responses_client)
|
||||
# Test that the client will use the web search tool
|
||||
@@ -1039,7 +1039,7 @@ async def test_openai_responses_client_file_search() -> None:
|
||||
async def test_openai_responses_client_streaming_file_search() -> None:
|
||||
openai_responses_client = OpenAIResponsesClient()
|
||||
|
||||
assert isinstance(openai_responses_client, ChatClient)
|
||||
assert isinstance(openai_responses_client, ChatClientProtocol)
|
||||
|
||||
file_id, vector_store = await create_vector_store(openai_responses_client)
|
||||
# Test that the client will use the web search tool
|
||||
@@ -1088,12 +1088,12 @@ async def test_openai_responses_client_agent_basic_run():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_responses_client_agent_basic_run_streaming():
|
||||
"""Test OpenAI Responses Client agent basic streaming functionality with OpenAIResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
) as agent:
|
||||
# Test streaming run
|
||||
full_text = ""
|
||||
async for chunk in agent.run_streaming("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
async for chunk in agent.run_stream("Please respond with exactly: 'This is a streaming response test.'"):
|
||||
assert isinstance(chunk, AgentRunResponseUpdate)
|
||||
if chunk.text:
|
||||
full_text += chunk.text
|
||||
@@ -1105,7 +1105,7 @@ async def test_openai_responses_client_agent_basic_run_streaming():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_responses_client_agent_thread_persistence():
|
||||
"""Test OpenAI Responses Client agent thread persistence across runs with OpenAIResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as agent:
|
||||
@@ -1128,7 +1128,7 @@ async def test_openai_responses_client_agent_thread_persistence():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_responses_client_agent_thread_storage_with_store_true():
|
||||
"""Test OpenAI Responses Client agent with store=True to verify service_thread_id is returned."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
@@ -1162,7 +1162,7 @@ async def test_openai_responses_client_agent_existing_thread():
|
||||
# First conversation - capture the thread
|
||||
preserved_thread = None
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as first_agent:
|
||||
@@ -1178,7 +1178,7 @@ async def test_openai_responses_client_agent_existing_thread():
|
||||
|
||||
# Second conversation - reuse the thread in a new agent instance
|
||||
if preserved_thread:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant with good memory.",
|
||||
) as second_agent:
|
||||
@@ -1193,7 +1193,7 @@ async def test_openai_responses_client_agent_existing_thread():
|
||||
@skip_if_openai_integration_tests_disabled
|
||||
async def test_openai_responses_client_agent_hosted_code_interpreter_tool():
|
||||
"""Test OpenAI Responses Client agent with HostedCodeInterpreterTool through OpenAIResponsesClient."""
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that can execute Python code.",
|
||||
tools=[HostedCodeInterpreterTool()],
|
||||
@@ -1215,7 +1215,7 @@ async def test_openai_responses_client_agent_hosted_code_interpreter_tool():
|
||||
async def test_openai_responses_client_agent_level_tool_persistence():
|
||||
"""Test that agent-level tools persist across multiple runs with OpenAI Responses Client."""
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that uses available tools.",
|
||||
tools=[get_weather], # Agent-level tool
|
||||
@@ -1250,7 +1250,7 @@ async def test_openai_responses_client_run_level_tool_isolation():
|
||||
call_count += 1
|
||||
return f"The weather in {location} is sunny and 72°F."
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
) as agent:
|
||||
|
||||
@@ -7,14 +7,14 @@ from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentBase,
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
ChatRole,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UsageDetails,
|
||||
)
|
||||
@@ -34,8 +34,8 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WorkflowAgent(AgentBase):
|
||||
"""An `AIAgent` subclass that wraps a workflow and exposes it as an agent."""
|
||||
class WorkflowAgent(BaseAgent):
|
||||
"""An `Agent` subclass that wraps a workflow and exposes it as an agent."""
|
||||
|
||||
# Class variable for the request info function name
|
||||
REQUEST_INFO_FUNCTION_NAME: ClassVar[str] = "request_info"
|
||||
@@ -65,11 +65,11 @@ class WorkflowAgent(AgentBase):
|
||||
id: Unique identifier for the agent. If None, will be generated.
|
||||
name: Optional name for the agent.
|
||||
description: Optional description of the agent.
|
||||
**kwargs: Additional keyword arguments passed to AgentBase.
|
||||
**kwargs: Additional keyword arguments passed to BaseAgent.
|
||||
"""
|
||||
if id is None:
|
||||
id = f"WorkflowAgent_{uuid.uuid4().hex[:8]}"
|
||||
# Initialize with standard AgentBase parameters first
|
||||
# Initialize with standard BaseAgent parameters first
|
||||
kwargs["workflow"] = workflow
|
||||
|
||||
# Validate the workflow's start executor can handle agent-facing message inputs
|
||||
@@ -107,7 +107,7 @@ class WorkflowAgent(AgentBase):
|
||||
thread = thread or self.get_new_thread()
|
||||
response_id = str(uuid.uuid4())
|
||||
|
||||
async for update in self._run_streaming_impl(input_messages, response_id):
|
||||
async for update in self._run_stream_impl(input_messages, response_id):
|
||||
response_updates.append(update)
|
||||
|
||||
# Convert updates to final response.
|
||||
@@ -119,7 +119,7 @@ class WorkflowAgent(AgentBase):
|
||||
|
||||
return response
|
||||
|
||||
async def run_streaming(
|
||||
async def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
@@ -141,7 +141,7 @@ class WorkflowAgent(AgentBase):
|
||||
response_updates: list[AgentRunResponseUpdate] = []
|
||||
response_id = str(uuid.uuid4())
|
||||
|
||||
async for update in self._run_streaming_impl(input_messages, response_id):
|
||||
async for update in self._run_stream_impl(input_messages, response_id):
|
||||
response_updates.append(update)
|
||||
yield update
|
||||
|
||||
@@ -152,7 +152,7 @@ class WorkflowAgent(AgentBase):
|
||||
await self._notify_thread_of_new_messages(thread, input_messages)
|
||||
await self._notify_thread_of_new_messages(thread, response.messages)
|
||||
|
||||
async def _run_streaming_impl(
|
||||
async def _run_stream_impl(
|
||||
self,
|
||||
input_messages: list[ChatMessage],
|
||||
response_id: str,
|
||||
@@ -188,7 +188,7 @@ class WorkflowAgent(AgentBase):
|
||||
else:
|
||||
# Execute workflow with streaming (initial run or no function responses)
|
||||
# Pass the new input messages directly to the workflow
|
||||
event_stream = self.workflow.run_streaming(input_messages)
|
||||
event_stream = self.workflow.run_stream(input_messages)
|
||||
|
||||
# Process events from the stream
|
||||
async for event in event_stream:
|
||||
@@ -206,7 +206,7 @@ class WorkflowAgent(AgentBase):
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage(role=ChatRole.USER, contents=[TextContent(text=messages)])]
|
||||
return [ChatMessage(role=Role.USER, contents=[TextContent(text=messages)])]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [messages]
|
||||
@@ -214,7 +214,7 @@ class WorkflowAgent(AgentBase):
|
||||
normalized = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, str):
|
||||
normalized.append(ChatMessage(role=ChatRole.USER, contents=[TextContent(text=msg)]))
|
||||
normalized.append(ChatMessage(role=Role.USER, contents=[TextContent(text=msg)]))
|
||||
elif isinstance(msg, ChatMessage):
|
||||
normalized.append(msg)
|
||||
return normalized
|
||||
@@ -250,7 +250,7 @@ class WorkflowAgent(AgentBase):
|
||||
)
|
||||
return AgentRunResponseUpdate(
|
||||
contents=[function_call],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
author_name=self.name,
|
||||
response_id=response_id,
|
||||
message_id=str(uuid.uuid4()),
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, get_args, get_or
|
||||
if TYPE_CHECKING:
|
||||
from ._workflow import Workflow
|
||||
|
||||
from agent_framework import AgentRunResponse, AgentRunResponseUpdate, AgentThread, AIAgent, ChatMessage
|
||||
from agent_framework import AgentProtocol, AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
|
||||
from agent_framework._pydantic import AFBaseModel
|
||||
from pydantic import Field
|
||||
|
||||
@@ -789,7 +789,7 @@ class AgentExecutor(Executor):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agent: AIAgent,
|
||||
agent: AgentProtocol,
|
||||
*,
|
||||
agent_thread: AgentThread | None = None,
|
||||
streaming: bool = False,
|
||||
@@ -818,7 +818,7 @@ class AgentExecutor(Executor):
|
||||
if request.should_respond:
|
||||
if self._streaming:
|
||||
updates: list[AgentRunResponseUpdate] = []
|
||||
async for update in self._agent.run_streaming(
|
||||
async for update in self._agent.run_stream(
|
||||
self._cache,
|
||||
thread=self._agent_thread,
|
||||
):
|
||||
@@ -894,7 +894,7 @@ class WorkflowExecutor(Executor):
|
||||
|
||||
try:
|
||||
# Run the sub-workflow and collect all events
|
||||
events = [event async for event in self.workflow.run_streaming(input_data)]
|
||||
events = [event async for event in self.workflow.run_stream(input_data)]
|
||||
|
||||
# Count requests and initialize response tracking
|
||||
request_count = 0
|
||||
|
||||
@@ -14,16 +14,16 @@ from typing import Annotated, Any, Literal, Protocol, TypeVar, Union, cast
|
||||
from uuid import uuid4
|
||||
|
||||
from agent_framework import (
|
||||
AgentProtocol,
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AIAgent,
|
||||
ChatClient,
|
||||
ChatClientProtocol,
|
||||
ChatMessage,
|
||||
ChatRole,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
)
|
||||
from agent_framework._agents import AgentBase
|
||||
from agent_framework._agents import BaseAgent
|
||||
from agent_framework._pydantic import AFBaseModel
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
@@ -83,7 +83,7 @@ class MagenticAgentDeltaEvent:
|
||||
function_call_arguments: Any | None = None
|
||||
function_result_id: str | None = None
|
||||
function_result: Any | None = None
|
||||
role: ChatRole | None = None
|
||||
role: Role | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -289,7 +289,7 @@ class MagenticStartMessage:
|
||||
Returns:
|
||||
A MagenticStartMessage with the string converted to a ChatMessage.
|
||||
"""
|
||||
return cls(task=ChatMessage(role=ChatRole.USER, text=task_text))
|
||||
return cls(task=ChatMessage(role=Role.USER, text=task_text))
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -401,7 +401,7 @@ def _team_block(participants: dict[str, str]) -> str:
|
||||
|
||||
def _first_assistant(messages: list[ChatMessage]) -> ChatMessage | None:
|
||||
for msg in reversed(messages):
|
||||
if msg.role == ChatRole.ASSISTANT:
|
||||
if msg.role == Role.ASSISTANT:
|
||||
return msg
|
||||
return None
|
||||
|
||||
@@ -409,7 +409,7 @@ def _first_assistant(messages: list[ChatMessage]) -> ChatMessage | None:
|
||||
def _extract_json(text: str) -> dict[str, Any]:
|
||||
"""Potentially temp helper method.
|
||||
|
||||
Note: this method is required right now because the ChatClient, when calling
|
||||
Note: this method is required right now because the ChatClientProtocol, when calling
|
||||
response.text, returns duplicate JSON payloads - need to figure out why.
|
||||
|
||||
The `text` method is concatenating multiple text contents from diff msgs into a single string.
|
||||
@@ -497,7 +497,7 @@ class MagenticManagerBase(AFBaseModel, ABC):
|
||||
|
||||
|
||||
class StandardMagenticManager(MagenticManagerBase):
|
||||
"""Standard Magentic manager that performs real LLM calls via a ChatClientAgent.
|
||||
"""Standard Magentic manager that performs real LLM calls via a ChatAgent.
|
||||
|
||||
The manager constructs prompts that mirror the original Magentic One orchestration:
|
||||
- Facts gathering
|
||||
@@ -509,7 +509,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
chat_client: ChatClient
|
||||
chat_client: ChatClientProtocol
|
||||
task_ledger: MagenticTaskLedger | None = None
|
||||
instructions: str | None = None
|
||||
|
||||
@@ -526,7 +526,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chat_client: ChatClient,
|
||||
chat_client: ChatClientProtocol,
|
||||
task_ledger: MagenticTaskLedger | None = None,
|
||||
*,
|
||||
instructions: str | None = None,
|
||||
@@ -597,7 +597,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
*,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
) -> ChatMessage:
|
||||
"""Call the underlying ChatClient directly and return the last assistant message.
|
||||
"""Call the underlying ChatClientProtocol directly and return the last assistant message.
|
||||
|
||||
If manager instructions are provided, they are injected as a SYSTEM message
|
||||
at the start of the request to guide the model consistently without needing
|
||||
@@ -606,7 +606,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
# Prepend system instructions if present
|
||||
request_messages: list[ChatMessage] = []
|
||||
if self.instructions:
|
||||
request_messages.append(ChatMessage(role=ChatRole.SYSTEM, text=self.instructions))
|
||||
request_messages.append(ChatMessage(role=Role.SYSTEM, text=self.instructions))
|
||||
request_messages.extend(messages)
|
||||
|
||||
# Invoke the chat client non-streaming API
|
||||
@@ -619,13 +619,13 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
if out_messages:
|
||||
last = out_messages[-1]
|
||||
return ChatMessage(
|
||||
role=last.role or ChatRole.ASSISTANT,
|
||||
role=last.role or Role.ASSISTANT,
|
||||
text=last.text or "",
|
||||
author_name=last.author_name or MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
|
||||
# Fallback if no messages
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="No output produced.", author_name=MAGENTIC_MANAGER_NAME)
|
||||
return ChatMessage(role=Role.ASSISTANT, text="No output produced.", author_name=MAGENTIC_MANAGER_NAME)
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
"""Create facts and plan using the model, then render a combined task ledger as a single assistant message."""
|
||||
@@ -634,14 +634,14 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
|
||||
# Gather facts
|
||||
facts_user = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=self.task_ledger_facts_prompt.format(task=task_text),
|
||||
)
|
||||
facts_msg = await self._complete([*magentic_context.chat_history, facts_user])
|
||||
|
||||
# Create plan
|
||||
plan_user = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=self.task_ledger_plan_prompt.format(team=team_text),
|
||||
)
|
||||
plan_msg = await self._complete([*magentic_context.chat_history, facts_user, facts_msg, plan_user])
|
||||
@@ -659,7 +659,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
facts=facts_msg.text,
|
||||
plan=plan_msg.text,
|
||||
)
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
return ChatMessage(role=Role.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
"""Update facts and plan when stalling or looping has been detected."""
|
||||
@@ -671,14 +671,14 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
|
||||
# Update facts
|
||||
facts_update_user = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=self.task_ledger_facts_update_prompt.format(task=task_text, old_facts=self.task_ledger.facts.text),
|
||||
)
|
||||
updated_facts = await self._complete([*magentic_context.chat_history, facts_update_user])
|
||||
|
||||
# Update plan
|
||||
plan_update_user = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=self.task_ledger_plan_update_prompt.format(team=team_text),
|
||||
)
|
||||
updated_plan = await self._complete([
|
||||
@@ -701,7 +701,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
facts=updated_facts.text,
|
||||
plan=updated_plan.text,
|
||||
)
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
return ChatMessage(role=Role.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
"""Use the model to produce a JSON progress ledger based on the conversation so far.
|
||||
@@ -721,7 +721,7 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
team=team_text,
|
||||
names=names_csv,
|
||||
)
|
||||
user_message = ChatMessage(role=ChatRole.USER, text=prompt)
|
||||
user_message = ChatMessage(role=Role.USER, text=prompt)
|
||||
|
||||
# Include full context to help the model decide current stage, with small retry loop
|
||||
attempts = 0
|
||||
@@ -751,11 +751,11 @@ class StandardMagenticManager(MagenticManagerBase):
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
"""Ask the model to produce the final answer addressed to the user."""
|
||||
prompt = self.final_answer_prompt.format(task=magentic_context.task.text)
|
||||
user_message = ChatMessage(role=ChatRole.USER, text=prompt)
|
||||
user_message = ChatMessage(role=Role.USER, text=prompt)
|
||||
response = await self._complete([*magentic_context.chat_history, user_message])
|
||||
# Ensure role is assistant
|
||||
return ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=response.text,
|
||||
author_name=response.author_name or MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
@@ -896,9 +896,9 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
logger.debug("Magentic Orchestrator: Received response from agent")
|
||||
|
||||
# Add transfer message if needed
|
||||
if message.body.role != ChatRole.USER:
|
||||
if message.body.role != Role.USER:
|
||||
transfer_msg = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=f"Transferred to {getattr(message.body, 'author_name', 'agent')}",
|
||||
)
|
||||
self._context.chat_history.append(transfer_msg)
|
||||
@@ -945,7 +945,7 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
plan=human.edited_plan_text,
|
||||
)
|
||||
self._task_ledger = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=combined,
|
||||
author_name=MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
@@ -953,7 +953,7 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
elif human.comments:
|
||||
# Record the human feedback for grounding
|
||||
self._context.chat_history.append(
|
||||
ChatMessage(role=ChatRole.USER, text=f"Human plan feedback: {human.comments}")
|
||||
ChatMessage(role=Role.USER, text=f"Human plan feedback: {human.comments}")
|
||||
)
|
||||
# Ask the manager to replan based on comments; proceed immediately
|
||||
self._task_ledger = await self._manager.replan(self._context.model_copy(deep=True))
|
||||
@@ -981,7 +981,7 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
self._require_plan_signoff = False
|
||||
# Add a clear note to the conversation so users know review is closed
|
||||
notice = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=(
|
||||
"Plan review closed after max rounds. Proceeding with the current plan and will no longer "
|
||||
"prompt for plan approval."
|
||||
@@ -1015,14 +1015,14 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
facts=(mgr_ledger2.facts.text if mgr_ledger2 else ""),
|
||||
plan=human.edited_plan_text,
|
||||
)
|
||||
self._task_ledger = ChatMessage(role=ChatRole.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
self._task_ledger = ChatMessage(role=Role.ASSISTANT, text=combined, author_name=MAGENTIC_MANAGER_NAME)
|
||||
await self._send_plan_review_request(context)
|
||||
return
|
||||
|
||||
# Else pass comments into the chat history and replan with the manager
|
||||
if human.comments:
|
||||
self._context.chat_history.append(
|
||||
ChatMessage(role=ChatRole.USER, text=f"Human plan feedback: {human.comments}")
|
||||
ChatMessage(role=Role.USER, text=f"Human plan feedback: {human.comments}")
|
||||
)
|
||||
|
||||
# Ask the manager to replan; this only adjusts the plan stage, not a full reset
|
||||
@@ -1127,7 +1127,7 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
|
||||
# Add instruction to conversation (assistant guidance)
|
||||
instruction_msg = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=str(instruction),
|
||||
author_name=MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
@@ -1215,7 +1215,7 @@ class MagenticOrchestratorExecutor(Executor):
|
||||
partial_result = _first_assistant(ctx.chat_history)
|
||||
if partial_result is None:
|
||||
partial_result = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=f"Stopped due to {limit_type} limit. No partial result available.",
|
||||
author_name=MAGENTIC_MANAGER_NAME,
|
||||
)
|
||||
@@ -1262,7 +1262,7 @@ class MagenticAgentExecutor(Executor):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agent: AIAgent | Executor,
|
||||
agent: AgentProtocol | Executor,
|
||||
agent_id: str,
|
||||
agent_response_callback: Callable[[str, ChatMessage], Awaitable[None]] | None = None,
|
||||
streaming_agent_response_callback: Callable[[str, AgentRunResponseUpdate, bool], Awaitable[None]] | None = None,
|
||||
@@ -1288,9 +1288,9 @@ class MagenticAgentExecutor(Executor):
|
||||
return
|
||||
|
||||
# Add transfer message if needed
|
||||
if message.body.role != ChatRole.USER:
|
||||
if message.body.role != Role.USER:
|
||||
transfer_msg = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
text=f"Transferred to {getattr(message.body, 'author_name', 'agent')}",
|
||||
)
|
||||
self._chat_history.append(transfer_msg)
|
||||
@@ -1298,18 +1298,18 @@ class MagenticAgentExecutor(Executor):
|
||||
# Add message to agent's history
|
||||
self._chat_history.append(message.body)
|
||||
|
||||
def _get_persona_adoption_role(self) -> ChatRole:
|
||||
def _get_persona_adoption_role(self) -> Role:
|
||||
"""Determine the best role for persona adoption messages.
|
||||
|
||||
Uses SYSTEM role if the agent supports it, otherwise falls back to USER.
|
||||
"""
|
||||
# Only AgentBase-derived agents are assumed to support SYSTEM messages reliably.
|
||||
from agent_framework import AgentBase as _AF_AgentBase # local import to avoid cycles
|
||||
# Only BaseAgent-derived agents are assumed to support SYSTEM messages reliably.
|
||||
from agent_framework import BaseAgent as _AF_AgentBase # local import to avoid cycles
|
||||
|
||||
if isinstance(self._agent, _AF_AgentBase) and hasattr(self._agent, "chat_client"):
|
||||
return ChatRole.SYSTEM
|
||||
return Role.SYSTEM
|
||||
# For other agent types or when we can't determine support, use USER
|
||||
return ChatRole.USER
|
||||
return Role.USER
|
||||
|
||||
@handler
|
||||
async def handle_request_message(
|
||||
@@ -1331,14 +1331,14 @@ class MagenticAgentExecutor(Executor):
|
||||
|
||||
# Add the orchestrator's instruction as a USER message so the agent treats it as the prompt
|
||||
if message.instruction:
|
||||
self._chat_history.append(ChatMessage(role=ChatRole.USER, text=message.instruction))
|
||||
self._chat_history.append(ChatMessage(role=Role.USER, text=message.instruction))
|
||||
try:
|
||||
# If the participant is not an invokable AgentBase, return a no-op response.
|
||||
from agent_framework import AgentBase as _AF_AgentBase # local import to avoid cycles
|
||||
# If the participant is not an invokable BaseAgent, return a no-op response.
|
||||
from agent_framework import BaseAgent as _AF_AgentBase # local import to avoid cycles
|
||||
|
||||
if not isinstance(self._agent, _AF_AgentBase):
|
||||
response = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=f"{self._agent_id} is a workflow executor and cannot be invoked directly.",
|
||||
author_name=self._agent_id,
|
||||
)
|
||||
@@ -1354,7 +1354,7 @@ class MagenticAgentExecutor(Executor):
|
||||
logger.warning("Agent %s invoke failed: %s", self._agent_id, e)
|
||||
# Fallback response
|
||||
response = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=f"Agent {self._agent_id}: Error processing request - {str(e)[:100]}",
|
||||
)
|
||||
self._chat_history.append(response)
|
||||
@@ -1370,9 +1370,9 @@ class MagenticAgentExecutor(Executor):
|
||||
logger.debug(f"Agent {self._agent_id}: Running with {len(self._chat_history)} messages")
|
||||
|
||||
updates: list[AgentRunResponseUpdate] = []
|
||||
# The wrapped participant is guaranteed to be an AgentBase when this is called.
|
||||
agent = cast("AIAgent", self._agent)
|
||||
async for update in agent.run_streaming(messages=self._chat_history): # type: ignore[attr-defined]
|
||||
# The wrapped participant is guaranteed to be an BaseAgent when this is called.
|
||||
agent = cast("AgentProtocol", self._agent)
|
||||
async for update in agent.run_stream(messages=self._chat_history): # type: ignore[attr-defined]
|
||||
updates.append(update)
|
||||
if self._streaming_agent_response_callback is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
@@ -1394,7 +1394,7 @@ class MagenticAgentExecutor(Executor):
|
||||
if messages and len(messages) > 0:
|
||||
last: ChatMessage = messages[-1]
|
||||
author = last.author_name or self._agent_id
|
||||
role: ChatRole = last.role if last.role else ChatRole.ASSISTANT
|
||||
role: Role = last.role if last.role else Role.ASSISTANT
|
||||
text = last.text or str(last)
|
||||
msg = ChatMessage(role=role, text=text, author_name=author)
|
||||
if self._agent_response_callback is not None:
|
||||
@@ -1403,7 +1403,7 @@ class MagenticAgentExecutor(Executor):
|
||||
return msg
|
||||
|
||||
msg = ChatMessage(
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
text=f"Agent {self._agent_id}: No output produced",
|
||||
author_name=self._agent_id,
|
||||
)
|
||||
@@ -1422,7 +1422,7 @@ class MagenticBuilder:
|
||||
"""High-level builder for creating Magentic One workflows."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._participants: dict[str, AIAgent | Executor] = {}
|
||||
self._participants: dict[str, AgentProtocol | Executor] = {}
|
||||
self._manager: MagenticManagerBase | None = None
|
||||
self._exception_callback: Callable[[Exception], None] | None = None
|
||||
self._result_callback: Callable[[ChatMessage], Awaitable[None]] | None = None
|
||||
@@ -1435,7 +1435,7 @@ class MagenticBuilder:
|
||||
self._unified_callback: CallbackSink | None = None
|
||||
self._callback_mode: MagenticCallbackMode | None = None
|
||||
|
||||
def participants(self, **participants: AIAgent | Executor) -> Self:
|
||||
def participants(self, **participants: AgentProtocol | Executor) -> Self:
|
||||
"""Add participants (agents) to the workflow."""
|
||||
self._participants.update(participants)
|
||||
return self
|
||||
@@ -1450,7 +1450,7 @@ class MagenticBuilder:
|
||||
manager: MagenticManagerBase | None = None,
|
||||
*,
|
||||
# Constructor args for StandardMagenticManager when manager is not provided
|
||||
chat_client: ChatClient | None = None,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
task_ledger: MagenticTaskLedger | None = None,
|
||||
instructions: str | None = None,
|
||||
# Prompt overrides
|
||||
@@ -1540,7 +1540,7 @@ class MagenticBuilder:
|
||||
# Create participant descriptions
|
||||
participant_descriptions: dict[str, str] = {}
|
||||
for name, participant in self._participants.items():
|
||||
if isinstance(participant, AgentBase):
|
||||
if isinstance(participant, BaseAgent):
|
||||
description = getattr(participant, "description", None) or f"Agent {name}"
|
||||
else:
|
||||
description = f"Executor {name}"
|
||||
@@ -1745,7 +1745,7 @@ class MagenticWorkflow:
|
||||
WorkflowEvent: The events generated during the workflow execution.
|
||||
"""
|
||||
start_message = MagenticStartMessage.from_string(task_text)
|
||||
async for event in self._workflow.run_streaming(start_message):
|
||||
async for event in self._workflow.run_stream(start_message):
|
||||
yield event
|
||||
|
||||
async def run_streaming_with_message(self, task_message: ChatMessage) -> AsyncIterable[WorkflowEvent]:
|
||||
@@ -1758,10 +1758,10 @@ class MagenticWorkflow:
|
||||
WorkflowEvent: The events generated during the workflow execution.
|
||||
"""
|
||||
start_message = MagenticStartMessage(task=task_message)
|
||||
async for event in self._workflow.run_streaming(start_message):
|
||||
async for event in self._workflow.run_stream(start_message):
|
||||
yield event
|
||||
|
||||
async def run_streaming(self, message: Any | None = None) -> AsyncIterable[WorkflowEvent]:
|
||||
async def run_stream(self, message: Any | None = None) -> AsyncIterable[WorkflowEvent]:
|
||||
"""Run the workflow with either a message object or the preset task string.
|
||||
|
||||
Args:
|
||||
@@ -1780,7 +1780,7 @@ class MagenticWorkflow:
|
||||
elif isinstance(message, ChatMessage):
|
||||
message = MagenticStartMessage(task=message)
|
||||
|
||||
async for event in self._workflow.run_streaming(message):
|
||||
async for event in self._workflow.run_stream(message):
|
||||
yield event
|
||||
|
||||
async def run_with_string(self, task_text: str) -> WorkflowRunResult:
|
||||
@@ -1822,7 +1822,7 @@ class MagenticWorkflow:
|
||||
WorkflowRunResult: All events generated during the workflow execution.
|
||||
"""
|
||||
events: list[WorkflowEvent] = []
|
||||
async for event in self.run_streaming(message):
|
||||
async for event in self.run_stream(message):
|
||||
events.append(event)
|
||||
return WorkflowRunResult(events)
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ class Workflow(AFBaseModel):
|
||||
workflow_tracer.add_workflow_error_event(e)
|
||||
raise
|
||||
|
||||
async def run_streaming(self, message: Any) -> AsyncIterable[WorkflowEvent]:
|
||||
async def run_stream(self, message: Any) -> AsyncIterable[WorkflowEvent]:
|
||||
"""Run the workflow with a starting message and stream events.
|
||||
|
||||
Args:
|
||||
@@ -252,7 +252,7 @@ class Workflow(AFBaseModel):
|
||||
async for event in self._run_workflow_with_tracing(initial_executor_fn=initial_execution, reset_context=True):
|
||||
yield event
|
||||
|
||||
async def run_streaming_from_checkpoint(
|
||||
async def run_stream_from_checkpoint(
|
||||
self,
|
||||
checkpoint_id: str,
|
||||
checkpoint_storage: CheckpointStorage | None = None,
|
||||
@@ -368,7 +368,7 @@ class Workflow(AFBaseModel):
|
||||
Returns:
|
||||
A WorkflowRunResult instance containing a list of events generated during the workflow execution.
|
||||
"""
|
||||
events = [event async for event in self.run_streaming(message)]
|
||||
events = [event async for event in self.run_stream(message)]
|
||||
return WorkflowRunResult(events)
|
||||
|
||||
async def run_from_checkpoint(
|
||||
@@ -394,7 +394,7 @@ class Workflow(AFBaseModel):
|
||||
RuntimeError: If checkpoint restoration fails.
|
||||
"""
|
||||
events = [
|
||||
event async for event in self.run_streaming_from_checkpoint(checkpoint_id, checkpoint_storage, responses)
|
||||
event async for event in self.run_stream_from_checkpoint(checkpoint_id, checkpoint_storage, responses)
|
||||
]
|
||||
return WorkflowRunResult(events)
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@ from agent_framework import (
|
||||
ChatMessage,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
ChatRole,
|
||||
Role,
|
||||
TextContent,
|
||||
)
|
||||
from agent_framework._agents import AgentBase
|
||||
from agent_framework._clients import ChatClient as AFChatClient
|
||||
from agent_framework._agents import BaseAgent
|
||||
from agent_framework._clients import ChatClientProtocol as AFChatClient
|
||||
|
||||
from agent_framework_workflow import (
|
||||
Executor,
|
||||
@@ -42,7 +42,7 @@ def test_magentic_start_message_from_string():
|
||||
msg = MagenticStartMessage.from_string("Do the thing")
|
||||
assert isinstance(msg, MagenticStartMessage)
|
||||
assert isinstance(msg.task, ChatMessage)
|
||||
assert msg.task.role == ChatRole.USER
|
||||
assert msg.task.role == Role.USER
|
||||
assert msg.task.text == "Do the thing"
|
||||
|
||||
|
||||
@@ -67,11 +67,11 @@ def test_plan_review_request_defaults_and_reply_variants():
|
||||
|
||||
def test_magentic_context_reset_behavior():
|
||||
ctx = MagenticContext(
|
||||
task=ChatMessage(role=ChatRole.USER, text="task"),
|
||||
task=ChatMessage(role=Role.USER, text="task"),
|
||||
participant_descriptions={"Alice": "Researcher"},
|
||||
)
|
||||
# seed context state
|
||||
ctx.chat_history.append(ChatMessage(role=ChatRole.ASSISTANT, text="draft"))
|
||||
ctx.chat_history.append(ChatMessage(role=Role.ASSISTANT, text="draft"))
|
||||
ctx.stall_count = 2
|
||||
prev_reset = ctx.reset_count
|
||||
|
||||
@@ -97,18 +97,18 @@ class FakeManager(MagenticManagerBase):
|
||||
instruction_text: str = "Proceed with step 1"
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
facts = ChatMessage(role=ChatRole.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- A\n")
|
||||
plan = ChatMessage(role=ChatRole.ASSISTANT, text="- Do X\n- Do Y\n")
|
||||
facts = ChatMessage(role=Role.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- A\n")
|
||||
plan = ChatMessage(role=Role.ASSISTANT, text="- Do X\n- Do Y\n")
|
||||
self.task_ledger = _SimpleLedger(facts=facts, plan=plan)
|
||||
combined = f"Task: {magentic_context.task.text}\n\nFacts:\n{facts.text}\n\nPlan:\n{plan.text}"
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text=combined, author_name="magentic_manager")
|
||||
return ChatMessage(role=Role.ASSISTANT, text=combined, author_name="magentic_manager")
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
facts = ChatMessage(role=ChatRole.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- A2\n")
|
||||
plan = ChatMessage(role=ChatRole.ASSISTANT, text="- Do Z\n")
|
||||
facts = ChatMessage(role=Role.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- A2\n")
|
||||
plan = ChatMessage(role=Role.ASSISTANT, text="- Do Z\n")
|
||||
self.task_ledger = _SimpleLedger(facts=facts, plan=plan)
|
||||
combined = f"Task: {magentic_context.task.text}\n\nFacts:\n{facts.text}\n\nPlan:\n{plan.text}"
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text=combined, author_name="magentic_manager")
|
||||
return ChatMessage(role=Role.ASSISTANT, text=combined, author_name="magentic_manager")
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
is_satisfied = self.satisfied_after_signoff and len(magentic_context.chat_history) > 0
|
||||
@@ -121,18 +121,18 @@ class FakeManager(MagenticManagerBase):
|
||||
)
|
||||
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="FINAL", author_name="magentic_manager")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="FINAL", author_name="magentic_manager")
|
||||
|
||||
|
||||
async def test_standard_manager_plan_and_replan_combined_ledger():
|
||||
manager = FakeManager(max_round_count=10, max_stall_count=3, max_reset_count=2)
|
||||
ctx = MagenticContext(
|
||||
task=ChatMessage(role=ChatRole.USER, text="demo task"),
|
||||
task=ChatMessage(role=Role.USER, text="demo task"),
|
||||
participant_descriptions={"agentA": "Agent A"},
|
||||
)
|
||||
|
||||
first = await manager.plan(ctx.model_copy(deep=True))
|
||||
assert first.role == ChatRole.ASSISTANT and "Facts:" in first.text and "Plan:" in first.text
|
||||
assert first.role == Role.ASSISTANT and "Facts:" in first.text and "Plan:" in first.text
|
||||
assert manager.task_ledger is not None
|
||||
|
||||
replanned = await manager.replan(ctx.model_copy(deep=True))
|
||||
@@ -142,7 +142,7 @@ async def test_standard_manager_plan_and_replan_combined_ledger():
|
||||
async def test_standard_manager_progress_ledger_and_fallback():
|
||||
manager = FakeManager(max_round_count=10)
|
||||
ctx = MagenticContext(
|
||||
task=ChatMessage(role=ChatRole.USER, text="demo"),
|
||||
task=ChatMessage(role=Role.USER, text="demo"),
|
||||
participant_descriptions={"agentA": "Agent A"},
|
||||
)
|
||||
|
||||
@@ -166,7 +166,7 @@ async def test_magentic_workflow_plan_review_approval_to_completion():
|
||||
)
|
||||
|
||||
req_event: RequestInfoEvent | None = None
|
||||
async for ev in wf.run_streaming("do work"):
|
||||
async for ev in wf.run_stream("do work"):
|
||||
if isinstance(ev, RequestInfoEvent) and ev.request_type is MagenticPlanReviewRequest:
|
||||
req_event = ev
|
||||
assert req_event is not None
|
||||
@@ -205,7 +205,7 @@ async def test_magentic_plan_review_approve_with_comments_replans_and_proceeds()
|
||||
|
||||
# Wait for the initial plan review request
|
||||
req_event: RequestInfoEvent | None = None
|
||||
async for ev in wf.run_streaming("do work"):
|
||||
async for ev in wf.run_stream("do work"):
|
||||
if isinstance(ev, RequestInfoEvent) and ev.request_type is MagenticPlanReviewRequest:
|
||||
req_event = ev
|
||||
assert req_event is not None
|
||||
@@ -242,7 +242,7 @@ async def test_magentic_orchestrator_round_limit_produces_partial_result():
|
||||
from agent_framework_workflow import WorkflowEvent # type: ignore
|
||||
|
||||
events: list[WorkflowEvent] = []
|
||||
async for ev in wf.run_streaming("round limit test"):
|
||||
async for ev in wf.run_stream("round limit test"):
|
||||
events.append(ev)
|
||||
if len(events) > 50:
|
||||
break
|
||||
@@ -251,7 +251,7 @@ async def test_magentic_orchestrator_round_limit_produces_partial_result():
|
||||
assert completed is not None
|
||||
data = getattr(completed, "data", None)
|
||||
assert isinstance(data, ChatMessage)
|
||||
assert data.role == ChatRole.ASSISTANT
|
||||
assert data.role == Role.ASSISTANT
|
||||
|
||||
|
||||
class _DummyExec(Executor):
|
||||
@@ -268,7 +268,7 @@ from agent_framework_workflow import StandardMagenticManager # noqa: E402
|
||||
|
||||
class _StubChatClient(AFChatClient):
|
||||
async def get_response(self, messages, **kwargs): # type: ignore[override]
|
||||
return ChatResponse(messages=[ChatMessage(role=ChatRole.ASSISTANT, text="ok")])
|
||||
return ChatResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="ok")])
|
||||
|
||||
def get_streaming_response(self, messages, **kwargs) -> AsyncIterable[ChatResponseUpdate]: # type: ignore[override]
|
||||
async def _gen():
|
||||
@@ -284,14 +284,14 @@ async def test_standard_manager_plan_and_replan_via_complete_monkeypatch():
|
||||
async def fake_complete_plan(messages: list[ChatMessage], **kwargs: Any) -> ChatMessage:
|
||||
# Return a different response depending on call order length
|
||||
if any("FACTS" in (m.text or "") for m in messages):
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="- step A\n- step B")
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- fact1")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="- step A\n- step B")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- fact1")
|
||||
|
||||
# First, patch to produce facts then plan
|
||||
mgr._complete = fake_complete_plan # type: ignore[attr-defined]
|
||||
|
||||
ctx = MagenticContext(
|
||||
task=ChatMessage(role=ChatRole.USER, text="T"),
|
||||
task=ChatMessage(role=Role.USER, text="T"),
|
||||
participant_descriptions={"A": "desc"},
|
||||
)
|
||||
combined = await mgr.plan(ctx.model_copy(deep=True))
|
||||
@@ -303,8 +303,8 @@ async def test_standard_manager_plan_and_replan_via_complete_monkeypatch():
|
||||
# Now replan with new outputs
|
||||
async def fake_complete_replan(messages: list[ChatMessage], **kwargs: Any) -> ChatMessage:
|
||||
if any("Please briefly explain" in (m.text or "") for m in messages):
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="- new step")
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- updated")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="- new step")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="GIVEN OR VERIFIED FACTS\n- updated")
|
||||
|
||||
mgr._complete = fake_complete_replan # type: ignore[attr-defined]
|
||||
combined2 = await mgr.replan(ctx.model_copy(deep=True))
|
||||
@@ -314,7 +314,7 @@ async def test_standard_manager_plan_and_replan_via_complete_monkeypatch():
|
||||
async def test_standard_manager_progress_ledger_success_and_error():
|
||||
mgr = StandardMagenticManager(chat_client=_StubChatClient())
|
||||
ctx = MagenticContext(
|
||||
task=ChatMessage(role=ChatRole.USER, text="task"),
|
||||
task=ChatMessage(role=Role.USER, text="task"),
|
||||
participant_descriptions={"alice": "desc"},
|
||||
)
|
||||
|
||||
@@ -327,7 +327,7 @@ async def test_standard_manager_progress_ledger_success_and_error():
|
||||
'"next_speaker": {"reason": "r", "answer": "alice"}, '
|
||||
'"instruction_or_question": {"reason": "r", "answer": "do"}}'
|
||||
)
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text=json_text)
|
||||
return ChatMessage(role=Role.ASSISTANT, text=json_text)
|
||||
|
||||
mgr._complete = fake_complete_ok # type: ignore[attr-defined]
|
||||
ledger = await mgr.create_progress_ledger(ctx.model_copy(deep=True))
|
||||
@@ -335,7 +335,7 @@ async def test_standard_manager_progress_ledger_success_and_error():
|
||||
|
||||
# Error path: invalid JSON now raises to avoid emitting planner-oriented instructions to agents
|
||||
async def fake_complete_bad(messages: list[ChatMessage], **kwargs: Any) -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="not-json")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="not-json")
|
||||
|
||||
mgr._complete = fake_complete_bad # type: ignore[attr-defined]
|
||||
with pytest.raises(RuntimeError):
|
||||
@@ -348,10 +348,10 @@ class InvokeOnceManager(MagenticManagerBase):
|
||||
self._invoked = False
|
||||
|
||||
async def plan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="ledger")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="ledger")
|
||||
|
||||
async def replan(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="re-ledger")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="re-ledger")
|
||||
|
||||
async def create_progress_ledger(self, magentic_context: MagenticContext) -> MagenticProgressLedger:
|
||||
if not self._invoked:
|
||||
@@ -374,43 +374,41 @@ class InvokeOnceManager(MagenticManagerBase):
|
||||
)
|
||||
|
||||
async def prepare_final_answer(self, magentic_context: MagenticContext) -> ChatMessage:
|
||||
return ChatMessage(role=ChatRole.ASSISTANT, text="final")
|
||||
return ChatMessage(role=Role.ASSISTANT, text="final")
|
||||
|
||||
|
||||
class StubThreadAgent(AgentBase):
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
class StubThreadAgent(BaseAgent):
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
yield AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="thread-ok")],
|
||||
author_name="agentA",
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
|
||||
async def run(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
return AgentRunResponse(messages=[ChatMessage(role=ChatRole.ASSISTANT, text="thread-ok", author_name="agentA")])
|
||||
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="thread-ok", author_name="agentA")])
|
||||
|
||||
|
||||
class StubAssistantsClient:
|
||||
pass # class name used for branch detection
|
||||
|
||||
|
||||
class StubAssistantsAgent(AgentBase):
|
||||
class StubAssistantsAgent(BaseAgent):
|
||||
chat_client: object | None = None # allow assignment via Pydantic field
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.chat_client = StubAssistantsClient() # type name contains 'AssistantsClient'
|
||||
|
||||
async def run_streaming(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
async def run_stream(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
yield AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="assistants-ok")],
|
||||
author_name="agentA",
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
)
|
||||
|
||||
async def run(self, messages=None, *, thread=None, **kwargs): # type: ignore[override]
|
||||
return AgentRunResponse(
|
||||
messages=[ChatMessage(role=ChatRole.ASSISTANT, text="assistants-ok", author_name="agentA")]
|
||||
)
|
||||
return AgentRunResponse(messages=[ChatMessage(role=Role.ASSISTANT, text="assistants-ok", author_name="agentA")])
|
||||
|
||||
|
||||
async def _collect_agent_responses_setup(participant_obj: object):
|
||||
@@ -432,7 +430,7 @@ async def _collect_agent_responses_setup(participant_obj: object):
|
||||
|
||||
# Run a bounded stream to allow one invoke and then completion
|
||||
events: list[WorkflowEvent] = []
|
||||
async for ev in wf.run_streaming("task"): # plan review disabled
|
||||
async for ev in wf.run_stream("task"): # plan review disabled
|
||||
events.append(ev)
|
||||
if len(events) > 50:
|
||||
break
|
||||
|
||||
@@ -343,7 +343,7 @@ async def test_end_to_end_workflow_tracing(tracing_enabled: Any, span_exporter:
|
||||
|
||||
# Run workflow (this should create run spans)
|
||||
events = []
|
||||
async for event in workflow.run_streaming("test input"):
|
||||
async for event in workflow.run_stream("test input"):
|
||||
events.append(event)
|
||||
|
||||
# Verify workflow executed correctly
|
||||
@@ -444,7 +444,7 @@ async def test_workflow_error_handling_in_tracing(tracing_enabled: Any, span_exp
|
||||
|
||||
# Run workflow and expect error
|
||||
with pytest.raises(ValueError, match="Test error"):
|
||||
async for _ in workflow.run_streaming("test input"):
|
||||
async for _ in workflow.run_stream("test input"):
|
||||
pass
|
||||
|
||||
spans = span_exporter.get_finished_spans()
|
||||
|
||||
@@ -95,7 +95,7 @@ async def test_workflow_run_streaming():
|
||||
)
|
||||
|
||||
result: int | None = None
|
||||
async for event in workflow.run_streaming(NumberMessage(data=0)):
|
||||
async for event in workflow.run_stream(NumberMessage(data=0)):
|
||||
assert isinstance(event, WorkflowEvent)
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
result = event.data
|
||||
@@ -118,7 +118,7 @@ async def test_workflow_run_stream_not_completed():
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
async for _ in workflow.run_streaming(NumberMessage(data=0)):
|
||||
async for _ in workflow.run_stream(NumberMessage(data=0)):
|
||||
pass
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ async def test_workflow_send_responses_streaming():
|
||||
)
|
||||
|
||||
request_info_event: RequestInfoEvent | None = None
|
||||
async for event in workflow.run_streaming(NumberMessage(data=0)):
|
||||
async for event in workflow.run_stream(NumberMessage(data=0)):
|
||||
if isinstance(event, RequestInfoEvent):
|
||||
request_info_event = event
|
||||
|
||||
@@ -326,7 +326,7 @@ async def test_workflow_checkpointing_not_enabled_for_external_restore(simple_ex
|
||||
|
||||
# Attempt to restore from checkpoint without providing external storage should fail
|
||||
try:
|
||||
[event async for event in workflow.run_streaming_from_checkpoint("fake-checkpoint-id")]
|
||||
[event async for event in workflow.run_stream_from_checkpoint("fake-checkpoint-id")]
|
||||
raise AssertionError("Expected ValueError to be raised")
|
||||
except ValueError as e:
|
||||
assert "Cannot restore from checkpoint" in str(e)
|
||||
@@ -344,7 +344,7 @@ async def test_workflow_run_stream_from_checkpoint_no_checkpointing_enabled(simp
|
||||
|
||||
# Attempt to run from checkpoint should fail
|
||||
try:
|
||||
async for _ in workflow.run_streaming_from_checkpoint("fake_checkpoint_id"):
|
||||
async for _ in workflow.run_stream_from_checkpoint("fake_checkpoint_id"):
|
||||
pass
|
||||
raise AssertionError("Expected ValueError to be raised")
|
||||
except ValueError as e:
|
||||
@@ -368,7 +368,7 @@ async def test_workflow_run_stream_from_checkpoint_invalid_checkpoint(simple_exe
|
||||
|
||||
# Attempt to run from non-existent checkpoint should fail
|
||||
try:
|
||||
async for _ in workflow.run_streaming_from_checkpoint("nonexistent_checkpoint_id"):
|
||||
async for _ in workflow.run_stream_from_checkpoint("nonexistent_checkpoint_id"):
|
||||
pass
|
||||
raise AssertionError("Expected RuntimeError to be raised")
|
||||
except RuntimeError as e:
|
||||
@@ -401,7 +401,7 @@ async def test_workflow_run_stream_from_checkpoint_with_external_storage(simple_
|
||||
# Resume from checkpoint using external storage parameter
|
||||
try:
|
||||
events: list[WorkflowEvent] = []
|
||||
async for event in workflow_without_checkpointing.run_streaming_from_checkpoint(
|
||||
async for event in workflow_without_checkpointing.run_stream_from_checkpoint(
|
||||
checkpoint_id, checkpoint_storage=storage
|
||||
):
|
||||
events.append(event)
|
||||
@@ -446,7 +446,7 @@ async def test_workflow_run_from_checkpoint_non_streaming(simple_executor: Execu
|
||||
|
||||
|
||||
async def test_workflow_run_stream_from_checkpoint_with_responses(simple_executor: Executor):
|
||||
"""Test that run_streaming_from_checkpoint accepts responses parameter."""
|
||||
"""Test that run_stream_from_checkpoint accepts responses parameter."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = FileCheckpointStorage(temp_dir)
|
||||
|
||||
@@ -477,7 +477,7 @@ async def test_workflow_run_stream_from_checkpoint_with_responses(simple_executo
|
||||
|
||||
try:
|
||||
events: list[WorkflowEvent] = []
|
||||
async for event in workflow.run_streaming_from_checkpoint(checkpoint_id, responses=responses):
|
||||
async for event in workflow.run_stream_from_checkpoint(checkpoint_id, responses=responses):
|
||||
events.append(event)
|
||||
if len(events) >= 2: # Limit to avoid infinite loops
|
||||
break
|
||||
|
||||
@@ -8,8 +8,8 @@ from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
ChatMessage,
|
||||
ChatRole,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
UsageContent,
|
||||
UsageDetails,
|
||||
@@ -43,11 +43,11 @@ class SimpleExecutor(Executor):
|
||||
response_text = f"{self.response_text}: {input_text}"
|
||||
|
||||
# Create response message for both streaming and non-streaming cases
|
||||
response_message = ChatMessage(role=ChatRole.ASSISTANT, contents=[TextContent(text=response_text)])
|
||||
response_message = ChatMessage(role=Role.ASSISTANT, contents=[TextContent(text=response_text)])
|
||||
|
||||
# Emit update event.
|
||||
streaming_update = AgentRunResponseUpdate(
|
||||
contents=[TextContent(text=response_text)], role=ChatRole.ASSISTANT, message_id=str(uuid.uuid4())
|
||||
contents=[TextContent(text=response_text)], role=Role.ASSISTANT, message_id=str(uuid.uuid4())
|
||||
)
|
||||
await ctx.add_event(AgentRunUpdateEvent(executor_id=self.id, data=streaming_update))
|
||||
|
||||
@@ -68,7 +68,7 @@ class RequestingExecutor(Executor):
|
||||
# Handle the response and emit completion response
|
||||
update = AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="Request completed successfully")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
message_id=str(uuid.uuid4()),
|
||||
)
|
||||
await ctx.add_event(AgentRunUpdateEvent(executor_id=self.id, data=update))
|
||||
@@ -132,7 +132,7 @@ class TestWorkflowAgent:
|
||||
|
||||
# Execute workflow streaming to capture streaming events
|
||||
updates = []
|
||||
async for update in agent.run_streaming("Test input"):
|
||||
async for update in agent.run_stream("Test input"):
|
||||
updates.append(update)
|
||||
|
||||
# Should have received at least one streaming update
|
||||
@@ -165,7 +165,7 @@ class TestWorkflowAgent:
|
||||
|
||||
# Execute workflow streaming to get request info event
|
||||
updates = []
|
||||
async for update in agent.run_streaming("Start request"):
|
||||
async for update in agent.run_stream("Start request"):
|
||||
updates.append(update)
|
||||
# Should have received a function call for the request info
|
||||
assert len(updates) > 0
|
||||
@@ -192,7 +192,7 @@ class TestWorkflowAgent:
|
||||
|
||||
# Now provide a function result response to test continuation
|
||||
response_message = ChatMessage(
|
||||
role=ChatRole.USER,
|
||||
role=Role.USER,
|
||||
contents=[FunctionResultContent(call_id=function_call.call_id, result="User provided answer")],
|
||||
)
|
||||
|
||||
@@ -252,7 +252,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
# Response B, Message 2 (latest in resp B)
|
||||
AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="RespB-Msg2")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-b",
|
||||
message_id="msg-2",
|
||||
created_at="2024-01-01T12:02:00Z",
|
||||
@@ -260,7 +260,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
# Response A, Message 1 (earliest overall)
|
||||
AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="RespA-Msg1")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-a",
|
||||
message_id="msg-1",
|
||||
created_at="2024-01-01T12:00:00Z",
|
||||
@@ -268,7 +268,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
# Response B, Message 1 (earlier in resp B)
|
||||
AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="RespB-Msg1")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-b",
|
||||
message_id="msg-1",
|
||||
created_at="2024-01-01T12:01:00Z",
|
||||
@@ -276,7 +276,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
# Response A, Message 2 (later in resp A)
|
||||
AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="RespA-Msg2")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-a",
|
||||
message_id="msg-2",
|
||||
created_at="2024-01-01T12:00:30Z",
|
||||
@@ -284,7 +284,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
# Global dangling update (no response_id) - should go at end
|
||||
AgentRunResponseUpdate(
|
||||
contents=[TextContent(text="Global-Dangling")],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id=None,
|
||||
message_id="msg-global",
|
||||
created_at="2024-01-01T11:59:00Z", # Earliest timestamp but should be last
|
||||
@@ -360,7 +360,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
details=UsageDetails(input_token_count=10, output_token_count=5, total_token_count=15)
|
||||
),
|
||||
],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-1",
|
||||
message_id="msg-1",
|
||||
created_at="2024-01-01T12:00:00Z",
|
||||
@@ -373,7 +373,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
details=UsageDetails(input_token_count=20, output_token_count=8, total_token_count=28)
|
||||
),
|
||||
],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-2",
|
||||
message_id="msg-2",
|
||||
created_at="2024-01-01T12:01:00Z", # Later timestamp
|
||||
@@ -384,7 +384,7 @@ class TestWorkflowAgentMergeUpdates:
|
||||
TextContent(text="Third"),
|
||||
UsageContent(details=UsageDetails(input_token_count=5, output_token_count=3, total_token_count=8)),
|
||||
],
|
||||
role=ChatRole.ASSISTANT,
|
||||
role=Role.ASSISTANT,
|
||||
response_id="resp-1", # Same response_id as first
|
||||
message_id="msg-3",
|
||||
created_at="2024-01-01T11:59:00Z", # Earlier timestamp
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`azure_assistants_basic.py`](azure_assistants_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `AzureAssistantsClient`. Shows both streaming and non-streaming responses with automatic assistant creation and cleanup. |
|
||||
| [`azure_assistants_basic.py`](azure_assistants_basic.py) | The simplest way to create an agent using `ChatAgent` with `AzureAssistantsClient`. Shows both streaming and non-streaming responses with automatic assistant creation and cleanup. |
|
||||
| [`azure_assistants_with_existing_assistant.py`](azure_assistants_with_existing_assistant.py) | Shows how to work with a pre-existing assistant by providing the assistant ID to the Azure Assistants client. Demonstrates proper cleanup of manually created assistants. |
|
||||
| [`azure_assistants_with_explicit_settings.py`](azure_assistants_with_explicit_settings.py) | Shows how to initialize an agent with a specific assistants client, configuring settings explicitly including endpoint and deployment name. |
|
||||
| [`azure_assistants_with_function_tools.py`](azure_assistants_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentRunResponseUpdate, ChatClientAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework import AgentRunResponseUpdate, ChatAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework.azure import AzureAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from openai.types.beta.threads.runs import (
|
||||
@@ -39,7 +39,7 @@ async def main() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
@@ -48,7 +48,7 @@ async def main() -> None:
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
generated_code = ""
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
code_interpreter_chunk = get_code_interpreter_chunk(chunk)
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.azure import AzureAssistantsClient
|
||||
from azure.identity import AzureCliCredential, get_bearer_token_provider
|
||||
from openai import AsyncAzureOpenAI
|
||||
@@ -37,7 +37,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
try:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(async_client=client, assistant_id=created_assistant.id),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.azure import AzureAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -33,7 +33,7 @@ async def tools_on_agent_level() -> None:
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -64,7 +64,7 @@ async def tools_on_run_level() -> None:
|
||||
# Agent created without tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -95,7 +95,7 @@ async def mixed_tools_example() -> None:
|
||||
# Agent created with some base tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework.azure import AzureAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -24,7 +24,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -50,7 +50,7 @@ async def example_with_thread_persistence() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -88,7 +88,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -108,7 +108,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
print("\n--- Continuing with the same thread ID in a new agent instance ---")
|
||||
|
||||
# Create a new agent instance but use the existing thread ID
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=AzureAssistantsClient(thread_id=existing_thread_id, credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`azure_chat_client_basic.py`](azure_chat_client_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `AzureChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with Azure OpenAI models. |
|
||||
| [`azure_chat_client_basic.py`](azure_chat_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `AzureChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with Azure OpenAI models. |
|
||||
| [`azure_chat_client_with_explicit_settings.py`](azure_chat_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific chat client, configuring settings explicitly including endpoint and deployment name. |
|
||||
| [`azure_chat_client_with_function_tools.py`](azure_chat_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
| [`azure_chat_client_with_thread.py`](azure_chat_client_with_thread.py) | Demonstrates thread management with Azure agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
|
||||
|
||||
@@ -50,7 +50,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.azure import AzureChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -33,7 +33,7 @@ async def tools_on_agent_level() -> None:
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -65,7 +65,7 @@ async def tools_on_run_level() -> None:
|
||||
# Agent created without tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -97,7 +97,7 @@ async def mixed_tools_example() -> None:
|
||||
# Agent created with some base tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent, ChatMessageList
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageList
|
||||
from agent_framework.azure import AzureChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -24,7 +24,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -51,7 +51,7 @@ async def example_with_thread_persistence() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -86,7 +86,7 @@ async def example_with_existing_thread_messages() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -108,7 +108,7 @@ async def example_with_existing_thread_messages() -> None:
|
||||
print("\n--- Continuing with the same thread in a new agent instance ---")
|
||||
|
||||
# Create a new agent instance but use the existing thread with its message history
|
||||
new_agent = ChatClientAgent(
|
||||
new_agent = ChatAgent(
|
||||
chat_client=AzureChatClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`azure_responses_client_basic.py`](azure_responses_client_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `AzureResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with Azure OpenAI models. |
|
||||
| [`azure_responses_client_basic.py`](azure_responses_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `AzureResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with Azure OpenAI models. |
|
||||
| [`azure_responses_client_with_explicit_settings.py`](azure_responses_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific responses client, configuring settings explicitly including endpoint and deployment name. |
|
||||
| [`azure_responses_client_with_function_tools.py`](azure_responses_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
| [`azure_responses_client_with_code_interpreter.py`](azure_responses_client_with_code_interpreter.py) | Shows how to use the HostedCodeInterpreterTool with Azure agents to write and execute Python code. Includes helper methods for accessing code interpreter data from response chunks. |
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, ChatResponse, HostedCodeInterpreterTool
|
||||
from agent_framework import ChatAgent, ChatResponse, HostedCodeInterpreterTool
|
||||
from agent_framework.azure import AzureResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from openai.types.responses.response import Response as OpenAIResponse
|
||||
@@ -15,7 +15,7 @@ async def main() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.azure import AzureResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -33,7 +33,7 @@ async def tools_on_agent_level() -> None:
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -65,7 +65,7 @@ async def tools_on_run_level() -> None:
|
||||
# Agent created without tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -97,7 +97,7 @@ async def mixed_tools_example() -> None:
|
||||
# Agent created with some base tools
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework.azure import AzureResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -24,7 +24,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -53,7 +53,7 @@ async def example_with_thread_persistence_in_memory() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -94,7 +94,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -116,7 +116,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
if existing_thread_id:
|
||||
print("\n--- Continuing with the same thread ID in a new agent instance ---")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=AzureResponsesClient(credential=AzureCliCredential()),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`foundry_basic.py`](foundry_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `FoundryChatClient`. It automatically handles all configuration using environment variables. |
|
||||
| [`foundry_basic.py`](foundry_basic.py) | The simplest way to create an agent using `ChatAgent` with `FoundryChatClient`. It automatically handles all configuration using environment variables. |
|
||||
| [`foundry_with_explicit_settings.py`](foundry_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `FoundryChatClient` settings, including project endpoint, model deployment, credentials, and agent name. |
|
||||
| [`foundry_with_existing_agent.py`](foundry_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent ID to the Foundry chat client. This example also demonstrates proper cleanup of manually created agents. |
|
||||
| [`foundry_with_function_tools.py`](foundry_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
|
||||
@@ -58,7 +58,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentRunResponseUpdate, ChatClientAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework import AgentRunResponseUpdate, ChatAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.ai.agents.models import (
|
||||
RunStepDelta,
|
||||
@@ -41,7 +41,7 @@ async def main() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
@@ -51,7 +51,7 @@ async def main() -> None:
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
generated_code = ""
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
code_interpreter_chunk = get_code_interpreter_chunk(chunk)
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
@@ -34,7 +34,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
try:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
# passing in the client is optional here, so if you take the agent_id from the portal
|
||||
# you can use it directly without the two lines above.
|
||||
chat_client=FoundryChatClient(client=client, agent_id=created_agent.id),
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -28,7 +28,7 @@ async def main() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(
|
||||
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
|
||||
model_deployment_name=os.environ["FOUNDRY_MODEL_DEPLOYMENT_NAME"],
|
||||
|
||||
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -35,7 +35,7 @@ async def tools_on_agent_level() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -69,7 +69,7 @@ async def tools_on_run_level() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -103,7 +103,7 @@ async def mixed_tools_example() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -26,7 +26,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -55,7 +55,7 @@ async def example_with_thread_persistence() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -96,7 +96,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -119,7 +119,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
# Create a new agent instance but use the existing thread ID
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(thread_id=existing_thread_id, async_credential=credential),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`openai_assistants_basic.py`](openai_assistants_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `OpenAIAssistantsClient`. Shows both streaming and non-streaming responses with automatic assistant creation and cleanup. |
|
||||
| [`openai_assistants_basic.py`](openai_assistants_basic.py) | The simplest way to create an agent using `ChatAgent` with `OpenAIAssistantsClient`. Shows both streaming and non-streaming responses with automatic assistant creation and cleanup. |
|
||||
| [`openai_assistants_with_existing_assistant.py`](openai_assistants_with_existing_assistant.py) | Shows how to work with a pre-existing assistant by providing the assistant ID to the OpenAI Assistants client. Demonstrates proper cleanup of manually created assistants. |
|
||||
| [`openai_assistants_with_explicit_settings.py`](openai_assistants_with_explicit_settings.py) | Shows how to initialize an agent with a specific assistants client, configuring settings explicitly including API key and model ID. |
|
||||
| [`openai_assistants_with_function_tools.py`](openai_assistants_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentRunResponseUpdate, ChatClientAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework import AgentRunResponseUpdate, ChatAgent, ChatResponseUpdate, HostedCodeInterpreterTool
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from openai.types.beta.threads.runs import (
|
||||
CodeInterpreterToolCallDelta,
|
||||
@@ -36,7 +36,7 @@ async def main() -> None:
|
||||
"""Example showing how to use the HostedCodeInterpreterTool with OpenAI Assistants."""
|
||||
print("=== OpenAI Assistants Agent with Code Interpreter Example ===")
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
@@ -45,7 +45,7 @@ async def main() -> None:
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
generated_code = ""
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
code_interpreter_chunk = get_code_interpreter_chunk(chunk)
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
@@ -31,7 +31,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
try:
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(async_client=client, assistant_id=created_assistant.id),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, HostedFileSearchTool, HostedVectorStoreContent
|
||||
from agent_framework import ChatAgent, HostedFileSearchTool, HostedVectorStoreContent
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
|
||||
# Helper functions
|
||||
@@ -33,7 +33,7 @@ async def delete_vector_store(client: OpenAIAssistantsClient, file_id: str, vect
|
||||
|
||||
async def main() -> None:
|
||||
client = OpenAIAssistantsClient()
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=client,
|
||||
instructions="You are a helpful assistant that searches files in a knowledge base.",
|
||||
tools=HostedFileSearchTool(),
|
||||
@@ -43,7 +43,7 @@ async def main() -> None:
|
||||
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(
|
||||
async for chunk in agent.run_stream(
|
||||
query, tool_resources={"file_search": {"vector_store_ids": [vector_store.vector_store_id]}}
|
||||
):
|
||||
if chunk.text:
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -30,7 +30,7 @@ async def tools_on_agent_level() -> None:
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -59,7 +59,7 @@ async def tools_on_run_level() -> None:
|
||||
print("=== Tools Passed to Run Method ===")
|
||||
|
||||
# Agent created without tools
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -88,7 +88,7 @@ async def mixed_tools_example() -> None:
|
||||
print("=== Mixed Tools Example (Agent + Run Method) ===")
|
||||
|
||||
# Agent created with some base tools
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -21,7 +21,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
"""Example showing automatic thread creation (service-managed thread)."""
|
||||
print("=== Automatic Thread Creation Example ===")
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -45,7 +45,7 @@ async def example_with_thread_persistence() -> None:
|
||||
print("=== Thread Persistence Example ===")
|
||||
print("Using the same thread across multiple conversations to maintain context.\n")
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -81,7 +81,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
# First, create a conversation and capture the thread ID
|
||||
existing_thread_id = None
|
||||
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -101,7 +101,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
print("\n--- Continuing with the same thread ID in a new agent instance ---")
|
||||
|
||||
# Create a new agent instance but use the existing thread ID
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIAssistantsClient(thread_id=existing_thread_id),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`openai_chat_client_basic.py`](openai_chat_client_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `OpenAIChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with OpenAI models. |
|
||||
| [`openai_chat_client_basic.py`](openai_chat_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `OpenAIChatClient`. Shows both streaming and non-streaming responses for chat-based interactions with OpenAI models. |
|
||||
| [`openai_chat_client_with_explicit_settings.py`](openai_chat_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific chat client, configuring settings explicitly including API key and model ID. |
|
||||
| [`openai_chat_client_with_function_tools.py`](openai_chat_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
| [`openai_chat_client_with_local_mcp.py`](openai_chat_client_with_local_mcp.py) | Shows how to integrate OpenAI agents with local Model Context Protocol (MCP) servers for enhanced functionality and tool integration. |
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -30,7 +30,7 @@ async def tools_on_agent_level() -> None:
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -60,7 +60,7 @@ async def tools_on_run_level() -> None:
|
||||
print("=== Tools Passed to Run Method ===")
|
||||
|
||||
# Agent created without tools
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -90,7 +90,7 @@ async def mixed_tools_example() -> None:
|
||||
print("=== Mixed Tools Example (Agent + Run Method) ===")
|
||||
|
||||
# Agent created with some base tools
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, McpStreamableHttpTool
|
||||
from agent_framework import ChatAgent, MCPStreamableHTTPTool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ async def mcp_tools_on_run_level() -> None:
|
||||
# This means we have to ensure we connect to the MCP server before running the agent
|
||||
# and pass the tools to the run method.
|
||||
async with (
|
||||
McpStreamableHttpTool(
|
||||
MCPStreamableHTTPTool(
|
||||
name="Microsoft Learn MCP",
|
||||
url="https://learn.microsoft.com/api/mcp",
|
||||
) as mcp_server,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
name="DocsAgent",
|
||||
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
|
||||
@@ -47,7 +47,7 @@ async def mcp_tools_on_agent_level() -> None:
|
||||
async with OpenAIChatClient().create_agent(
|
||||
name="DocsAgent",
|
||||
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
|
||||
tools=McpStreamableHttpTool( # Tools defined at agent creation
|
||||
tools=MCPStreamableHTTPTool( # Tools defined at agent creation
|
||||
name="Microsoft Learn MCP",
|
||||
url="https://learn.microsoft.com/api/mcp",
|
||||
),
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent, ChatMessageList
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageList
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -21,7 +21,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
"""Example showing automatic thread creation (service-managed thread)."""
|
||||
print("=== Automatic Thread Creation Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -46,7 +46,7 @@ async def example_with_thread_persistence() -> None:
|
||||
print("=== Thread Persistence Example ===")
|
||||
print("Using the same thread across multiple conversations to maintain context.\n")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -79,7 +79,7 @@ async def example_with_existing_thread_messages() -> None:
|
||||
"""Example showing how to work with existing thread messages for OpenAI."""
|
||||
print("=== Existing Thread Messages Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -101,7 +101,7 @@ async def example_with_existing_thread_messages() -> None:
|
||||
print("\n--- Continuing with the same thread in a new agent instance ---")
|
||||
|
||||
# Create a new agent instance but use the existing thread with its message history
|
||||
new_agent = ChatClientAgent(
|
||||
new_agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -6,7 +6,7 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`openai_responses_client_basic.py`](openai_responses_client_basic.py) | The simplest way to create an agent using `ChatClientAgent` with `OpenAIResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with OpenAI models. |
|
||||
| [`openai_responses_client_basic.py`](openai_responses_client_basic.py) | The simplest way to create an agent using `ChatAgent` with `OpenAIResponsesClient`. Shows both streaming and non-streaming responses for structured response generation with OpenAI models. |
|
||||
| [`openai_responses_client_reasoning.py`](openai_responses_client_reasoning.py) | Demonstrates how to use reasoning capabilities with OpenAI agents, showing how the agent can provide detailed reasoning for its responses. |
|
||||
| [`openai_responses_client_with_explicit_settings.py`](openai_responses_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific responses client, configuring settings explicitly including API key and model ID. |
|
||||
| [`openai_responses_client_with_function_tools.py`](openai_responses_client_with_function_tools.py) | Demonstrates how to use function tools with agents. Shows both agent-level tools (defined when creating the agent) and query-level tools (provided with specific queries). |
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -21,7 +21,7 @@ async def non_streaming_example() -> None:
|
||||
"""Example of non-streaming response (get the complete result at once)."""
|
||||
print("=== Non-streaming Response Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -37,7 +37,7 @@ async def streaming_example() -> None:
|
||||
"""Example of streaming response (get results as they are generated)."""
|
||||
print("=== Streaming Response Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -46,7 +46,7 @@ async def streaming_example() -> None:
|
||||
query = "What's the weather like in Portland?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ async def reasoning_example() -> None:
|
||||
print(f"User: {query}")
|
||||
print(f"{agent.name}: ", end="", flush=True)
|
||||
usage = None
|
||||
async for chunk in agent.run_streaming(query):
|
||||
async for chunk in agent.run_stream(query):
|
||||
if chunk.contents:
|
||||
for content in chunk.contents:
|
||||
if isinstance(content, TextReasoningContent):
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, ChatResponse, HostedCodeInterpreterTool
|
||||
from agent_framework import ChatAgent, ChatResponse, HostedCodeInterpreterTool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from openai.types.responses.response import Response as OpenAIResponse
|
||||
from openai.types.responses.response_code_interpreter_tool_call import ResponseCodeInterpreterToolCall
|
||||
@@ -12,7 +12,7 @@ async def main() -> None:
|
||||
"""Example showing how to use the HostedCodeInterpreterTool with OpenAI Responses."""
|
||||
print("=== OpenAI Responses Agent with Code Interpreter Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that can write and execute Python code to solve problems.",
|
||||
tools=HostedCodeInterpreterTool(),
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -30,7 +30,7 @@ async def tools_on_agent_level() -> None:
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant that can provide weather and time information.",
|
||||
tools=[get_weather, get_time], # Tools defined at agent creation
|
||||
@@ -60,7 +60,7 @@ async def tools_on_run_level() -> None:
|
||||
print("=== Tools Passed to Run Method ===")
|
||||
|
||||
# Agent created without tools
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful assistant.",
|
||||
# No tools defined here
|
||||
@@ -90,7 +90,7 @@ async def mixed_tools_example() -> None:
|
||||
print("=== Mixed Tools Example (Agent + Run Method) ===")
|
||||
|
||||
# Agent created with some base tools
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a comprehensive assistant that can help with various information requests.",
|
||||
tools=[get_weather], # Base tool available for all queries
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatClientAgent, McpStreamableHttpTool
|
||||
from agent_framework import ChatAgent, MCPStreamableHTTPTool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ async def streaming_with_mcp(show_raw_stream: bool = False) -> None:
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
name="DocsAgent",
|
||||
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
|
||||
tools=McpStreamableHttpTool( # Tools defined at agent creation
|
||||
tools=MCPStreamableHTTPTool( # Tools defined at agent creation
|
||||
name="Microsoft Learn MCP",
|
||||
url="https://learn.microsoft.com/api/mcp",
|
||||
),
|
||||
@@ -29,7 +29,7 @@ async def streaming_with_mcp(show_raw_stream: bool = False) -> None:
|
||||
query1 = "How to create an Azure storage account using az cli?"
|
||||
print(f"User: {query1}")
|
||||
print(f"{agent.name}: ", end="")
|
||||
async for chunk in agent.run_streaming(query1):
|
||||
async for chunk in agent.run_stream(query1):
|
||||
if show_raw_stream:
|
||||
print("Streamed event: ", chunk.raw_representation.raw_representation) # type:ignore
|
||||
elif chunk.text:
|
||||
@@ -40,7 +40,7 @@ async def streaming_with_mcp(show_raw_stream: bool = False) -> None:
|
||||
query2 = "What is Microsoft Semantic Kernel?"
|
||||
print(f"User: {query2}")
|
||||
print(f"{agent.name}: ", end="")
|
||||
async for chunk in agent.run_streaming(query2):
|
||||
async for chunk in agent.run_stream(query2):
|
||||
if show_raw_stream:
|
||||
print("Streamed event: ", chunk.raw_representation.raw_representation) # type:ignore
|
||||
elif chunk.text:
|
||||
@@ -54,11 +54,11 @@ async def run_with_mcp() -> None:
|
||||
|
||||
# Tools are provided when creating the agent
|
||||
# The agent can use these tools for any query during its lifetime
|
||||
async with ChatClientAgent(
|
||||
async with ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
name="DocsAgent",
|
||||
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
|
||||
tools=McpStreamableHttpTool( # Tools defined at agent creation
|
||||
tools=MCPStreamableHTTPTool( # Tools defined at agent creation
|
||||
name="Microsoft Learn MCP",
|
||||
url="https://learn.microsoft.com/api/mcp",
|
||||
),
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -21,7 +21,7 @@ async def example_with_automatic_thread_creation() -> None:
|
||||
"""Example showing automatic thread creation."""
|
||||
print("=== Automatic Thread Creation Example ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -48,7 +48,7 @@ async def example_with_thread_persistence_in_memory() -> None:
|
||||
"""
|
||||
print("=== Thread Persistence Example (In-Memory) ===")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -87,7 +87,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
# First, create a conversation and capture the thread ID
|
||||
existing_thread_id = None
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
@@ -109,7 +109,7 @@ async def example_with_existing_thread_id() -> None:
|
||||
if existing_thread_id:
|
||||
print("\n--- Continuing with the same thread ID in a new agent instance ---")
|
||||
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIResponsesClient(),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from azure.monitor.opentelemetry import configure_azure_monitor
|
||||
from opentelemetry import trace
|
||||
@@ -163,7 +163,7 @@ async def main():
|
||||
with tracer.start_as_current_span("Scenario: Agent Chat", kind=SpanKind.CLIENT) as current_span:
|
||||
print("Running scenario: Agent Chat")
|
||||
print("Welcome to the chat, type 'exit' to quit.")
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=OpenAIChatClient(),
|
||||
tools=get_weather,
|
||||
name="WeatherAgent",
|
||||
@@ -174,7 +174,7 @@ async def main():
|
||||
try:
|
||||
while message.lower() != "exit":
|
||||
print(f"{agent.display_name}: ", end="")
|
||||
async for update in agent.run_streaming(
|
||||
async for update in agent.run_stream(
|
||||
message,
|
||||
thread=thread,
|
||||
):
|
||||
|
||||
@@ -217,7 +217,7 @@ async def run_sequential_workflow() -> None:
|
||||
print(f"Starting workflow with input: '{input_text}'")
|
||||
|
||||
completion_event = None
|
||||
async for event in workflow.run_streaming(input_text):
|
||||
async for event in workflow.run_stream(input_text):
|
||||
print(f"Event: {event}")
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
# The WorkflowCompletedEvent contains the final result.
|
||||
|
||||
@@ -58,7 +58,7 @@ async def main():
|
||||
|
||||
# Step 3: Run the workflow with an initial message.
|
||||
completion_event = None
|
||||
async for event in workflow.run_streaming("hello world"):
|
||||
async for event in workflow.run_stream("hello world"):
|
||||
print(f"Event: {event}")
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
# The WorkflowCompletedEvent contains the final result.
|
||||
|
||||
@@ -110,7 +110,7 @@ async def main():
|
||||
)
|
||||
|
||||
# Step 3: Run the workflow with an input message.
|
||||
async for event in workflow.run_streaming("This is a spam."):
|
||||
async for event in workflow.run_stream("This is a spam."):
|
||||
print(f"Event: {event}")
|
||||
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ async def main():
|
||||
|
||||
# Step 3: Run the workflow and print the events.
|
||||
iterations = 0
|
||||
async for event in workflow.run_streaming(NumberSignal.INIT):
|
||||
async for event in workflow.run_stream(NumberSignal.INIT):
|
||||
if isinstance(event, ExecutorCompletedEvent) and event.executor_id == guess_number_executor.id:
|
||||
iterations += 1
|
||||
print(f"Event: {event}")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatMessage, ChatRole
|
||||
from agent_framework import ChatMessage, Role
|
||||
from agent_framework.azure import AzureChatClient
|
||||
from agent_framework.workflow import (
|
||||
AgentExecutor,
|
||||
@@ -36,7 +36,7 @@ class RoundRobinGroupChatManager(Executor):
|
||||
@handler
|
||||
async def start(self, task: str, ctx: WorkflowContext[AgentExecutorRequest]) -> None:
|
||||
"""Execute the task by sending messages to the next executor in the round-robin sequence."""
|
||||
initial_message = ChatMessage(ChatRole.USER, text=task)
|
||||
initial_message = ChatMessage(Role.USER, text=task)
|
||||
|
||||
# Send the initial message to the members
|
||||
await asyncio.gather(*[
|
||||
@@ -132,7 +132,7 @@ async def main():
|
||||
|
||||
# Step 3: Run the workflow with an initial message.
|
||||
completion_event = None
|
||||
async for event in workflow.run_streaming(
|
||||
async for event in workflow.run_stream(
|
||||
"Create a slogan for a new electric SUV that is affordable and fun to drive."
|
||||
):
|
||||
if isinstance(event, AgentRunEvent):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatMessage, ChatRole
|
||||
from agent_framework import ChatMessage, Role
|
||||
from agent_framework.azure import AzureChatClient
|
||||
from agent_framework.workflow import (
|
||||
AgentExecutor,
|
||||
@@ -39,7 +39,7 @@ class CriticGroupChatManager(Executor):
|
||||
@handler
|
||||
async def start(self, task: str, ctx: WorkflowContext[AgentExecutorRequest]) -> None:
|
||||
"""Handler that starts the group chat with an initial task."""
|
||||
initial_message = ChatMessage(ChatRole.USER, text=task)
|
||||
initial_message = ChatMessage(Role.USER, text=task)
|
||||
|
||||
# Send the initial message to the members
|
||||
await asyncio.gather(*[
|
||||
@@ -129,7 +129,7 @@ class CriticGroupChatManager(Executor):
|
||||
return False
|
||||
|
||||
last_message = self._chat_history[-1]
|
||||
return bool(last_message.role == ChatRole.USER and "approve" in last_message.text.lower())
|
||||
return bool(last_message.role == Role.USER and "approve" in last_message.text.lower())
|
||||
|
||||
def _should_request_info(self) -> bool:
|
||||
"""Determine if the group chat should request HIL based on the last message."""
|
||||
@@ -137,7 +137,7 @@ class CriticGroupChatManager(Executor):
|
||||
return True
|
||||
|
||||
last_message = self._chat_history[-1]
|
||||
return last_message.role == ChatRole.ASSISTANT
|
||||
return last_message.role == Role.ASSISTANT
|
||||
|
||||
def _get_next_member(self) -> str:
|
||||
"""Get the next member in the round-robin sequence."""
|
||||
@@ -200,12 +200,12 @@ async def main():
|
||||
# Depending on whether we have a RequestInfoEvent event, we either
|
||||
# run the workflow normally or send the message to the HIL executor.
|
||||
if not request_info_event:
|
||||
response_stream = workflow.run_streaming(
|
||||
response_stream = workflow.run_stream(
|
||||
"Create a slogan for a new electric SUV that is affordable and fun to drive."
|
||||
)
|
||||
else:
|
||||
response_stream = workflow.send_responses_streaming({
|
||||
request_info_event.request_id: [ChatMessage(ChatRole.USER, text=user_input)]
|
||||
request_info_event.request_id: [ChatMessage(Role.USER, text=user_input)]
|
||||
})
|
||||
request_info_event = None
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ async def main():
|
||||
|
||||
# Step 4: Run the workflow with the raw text as input.
|
||||
completion_event = None
|
||||
async for event in workflow.run_streaming(raw_text):
|
||||
async for event in workflow.run_stream(raw_text):
|
||||
print(f"Event: {event}")
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
completion_event = event
|
||||
|
||||
@@ -135,7 +135,7 @@ async def main():
|
||||
)
|
||||
|
||||
print("Running workflow with initial message...")
|
||||
async for event in workflow.run_streaming(message="hello world"):
|
||||
async for event in workflow.run_stream(message="hello world"):
|
||||
print(f"Event: {event}")
|
||||
|
||||
# Inspect checkpoints
|
||||
@@ -179,7 +179,7 @@ async def main():
|
||||
)
|
||||
|
||||
print(f"\nResuming from checkpoint: {checkpoint_id}")
|
||||
async for event in new_workflow.run_streaming_from_checkpoint(checkpoint_id, checkpoint_storage=checkpoint_storage):
|
||||
async for event in new_workflow.run_stream_from_checkpoint(checkpoint_id, checkpoint_storage=checkpoint_storage):
|
||||
print(f"Resumed Event: {event}")
|
||||
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from agent_framework import ChatClientAgent, HostedCodeInterpreterTool
|
||||
from agent_framework import ChatAgent, HostedCodeInterpreterTool
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework_workflow import (
|
||||
MagenticAgentDeltaEvent,
|
||||
@@ -25,9 +25,9 @@ Magentic Workflow (multi-agent) sample.
|
||||
This sample shows how to orchestrate multiple agents using the
|
||||
MagenticBuilder:
|
||||
|
||||
- ResearcherAgent (ChatClientAgent backed by an OpenAI chat client) for
|
||||
- ResearcherAgent (ChatAgent backed by an OpenAI chat client) for
|
||||
finding information.
|
||||
- CoderAgent (ChatClientAgent backed by OpenAI Assistants with the hosted
|
||||
- CoderAgent (ChatAgent backed by OpenAI Assistants with the hosted
|
||||
code interpreter tool) for analysis and computation.
|
||||
|
||||
The workflow is configured with:
|
||||
@@ -42,7 +42,7 @@ events to the console, and prints the final aggregated answer at completion.
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
researcher_agent = ChatClientAgent(
|
||||
researcher_agent = ChatAgent(
|
||||
name="ResearcherAgent",
|
||||
description="Specialist in research and information gathering",
|
||||
instructions=(
|
||||
@@ -50,11 +50,11 @@ async def main() -> None:
|
||||
),
|
||||
# This agent requires the gpt-4o-search-preview model to perform web searches.
|
||||
# Feel free to explore with other agents that support web search, for example,
|
||||
# the `OpenAIResponseAgent` or `AzureAIAgent` with bing grounding.
|
||||
# the `OpenAIResponseAgent` or `AzureAgentProtocol` with bing grounding.
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
)
|
||||
|
||||
coder_agent = ChatClientAgent(
|
||||
coder_agent = ChatAgent(
|
||||
name="CoderAgent",
|
||||
description="A helpful assistant that writes and executes code to process and analyze data.",
|
||||
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
|
||||
@@ -129,7 +129,7 @@ async def main() -> None:
|
||||
|
||||
try:
|
||||
completion_event = None
|
||||
async for event in workflow.run_streaming(task):
|
||||
async for event in workflow.run_stream(task):
|
||||
print(f"Event: {event}")
|
||||
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import ChatClientAgent, HostedCodeInterpreterTool
|
||||
from agent_framework import ChatAgent, HostedCodeInterpreterTool
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework_workflow import (
|
||||
MagenticAgentDeltaEvent,
|
||||
@@ -30,8 +30,8 @@ Magentic workflow with human-in-the-loop plan review and update.
|
||||
This sample builds a Magentic workflow with two cooperating agents and enables
|
||||
plan review so a human can approve or revise the plan before execution:
|
||||
|
||||
- researcher: ChatClientAgent backed by OpenAIChatClient (web/search-capable model)
|
||||
- coder: ChatClientAgent backed by OpenAIAssistantsClient with the Hosted Code Interpreter tool
|
||||
- researcher: ChatAgent backed by OpenAIChatClient (web/search-capable model)
|
||||
- coder: ChatAgent backed by OpenAIAssistantsClient with the Hosted Code Interpreter tool
|
||||
|
||||
Key behaviors demonstrated:
|
||||
- with_plan_review(): requests a PlanReviewRequest before coordination begins
|
||||
@@ -46,7 +46,7 @@ clients can run. You can swap clients/models as needed.
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
researcher_agent = ChatClientAgent(
|
||||
researcher_agent = ChatAgent(
|
||||
name="ResearcherAgent",
|
||||
description="Specialist in research and information gathering",
|
||||
instructions=(
|
||||
@@ -54,11 +54,11 @@ async def main() -> None:
|
||||
),
|
||||
# This agent requires the gpt-4o-search-preview model to perform web searches.
|
||||
# Feel free to explore with other agents that support web search, for example,
|
||||
# the `OpenAIResponseAgent` or `AzureAIAgent` with bing grounding.
|
||||
# the `OpenAIResponseAgent` or `AzureAgentProtocol` with bing grounding.
|
||||
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
|
||||
)
|
||||
|
||||
coder_agent = ChatClientAgent(
|
||||
coder_agent = ChatAgent(
|
||||
name="CoderAgent",
|
||||
description="A helpful assistant that writes and executes code to process and analyze data.",
|
||||
instructions="You solve questions using code. Please provide detailed analysis and computation process.",
|
||||
@@ -140,7 +140,7 @@ async def main() -> None:
|
||||
while True:
|
||||
# Phase 1: run until either completion or a HIL request
|
||||
if pending_request is None:
|
||||
async for event in workflow.run_streaming(task):
|
||||
async for event in workflow.run_stream(task):
|
||||
print(f"Event: {event}")
|
||||
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
|
||||
+11
-11
@@ -4,7 +4,7 @@ import asyncio
|
||||
from dataclasses import dataclass
|
||||
from uuid import uuid4
|
||||
|
||||
from agent_framework import AgentRunResponseUpdate, AIContents, ChatClient, ChatMessage, ChatRole
|
||||
from agent_framework import AgentRunResponseUpdate, ChatClientProtocol, ChatMessage, Contents, Role
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.workflow import AgentRunUpdateEvent, Executor, WorkflowBuilder, WorkflowContext, handler
|
||||
from pydantic import BaseModel
|
||||
@@ -51,7 +51,7 @@ class ReviewResponse:
|
||||
class Reviewer(Executor):
|
||||
"""An executor that reviews messages and provides feedback."""
|
||||
|
||||
def __init__(self, chat_client: ChatClient) -> None:
|
||||
def __init__(self, chat_client: ChatClientProtocol) -> None:
|
||||
super().__init__()
|
||||
self._chat_client = chat_client
|
||||
|
||||
@@ -69,7 +69,7 @@ class Reviewer(Executor):
|
||||
# Define the system prompt.
|
||||
messages = [
|
||||
ChatMessage(
|
||||
role=ChatRole.SYSTEM,
|
||||
role=Role.SYSTEM,
|
||||
text="You are a reviewer for an AI agent, please provide feedback on the "
|
||||
"following exchange between a user and the AI agent, "
|
||||
"and indicate if the agent's responses are approved or not.\n"
|
||||
@@ -91,7 +91,7 @@ class Reviewer(Executor):
|
||||
|
||||
# Add add one more instruction for the assistant to follow.
|
||||
messages.append(
|
||||
ChatMessage(role=ChatRole.USER, text="Please provide a review of the agent's responses to the user.")
|
||||
ChatMessage(role=Role.USER, text="Please provide a review of the agent's responses to the user.")
|
||||
)
|
||||
|
||||
print("🔍 Reviewer: Sending review request to LLM...")
|
||||
@@ -113,7 +113,7 @@ class Reviewer(Executor):
|
||||
class Worker(Executor):
|
||||
"""An executor that performs tasks for the user."""
|
||||
|
||||
def __init__(self, chat_client: ChatClient) -> None:
|
||||
def __init__(self, chat_client: ChatClientProtocol) -> None:
|
||||
super().__init__()
|
||||
self._chat_client = chat_client
|
||||
self._pending_requests: dict[str, tuple[ReviewRequest, list[ChatMessage]]] = {}
|
||||
@@ -124,7 +124,7 @@ class Worker(Executor):
|
||||
|
||||
# Handle user messages and prepare a review request for the reviewer.
|
||||
# Define the system prompt.
|
||||
messages = [ChatMessage(role=ChatRole.SYSTEM, text="You are a helpful assistant.")]
|
||||
messages = [ChatMessage(role=Role.SYSTEM, text="You are a helpful assistant.")]
|
||||
|
||||
# Add user messages.
|
||||
messages.extend(user_messages)
|
||||
@@ -163,14 +163,14 @@ class Worker(Executor):
|
||||
print("✅ Worker: Response approved! Emitting to external consumer...")
|
||||
# If approved, emit the agent run response update to the workflow's
|
||||
# external consumer.
|
||||
contents: list[AIContents] = []
|
||||
contents: list[Contents] = []
|
||||
for message in request.agent_messages:
|
||||
contents.extend(message.contents)
|
||||
# Emitting an AgentRunUpdateEvent in a workflow wrapped by a WorkflowAgent
|
||||
# will send the AgentRunResponseUpdate to the WorkflowAgent's
|
||||
# event stream.
|
||||
await ctx.add_event(
|
||||
AgentRunUpdateEvent(self.id, data=AgentRunResponseUpdate(contents=contents, role=ChatRole.ASSISTANT))
|
||||
AgentRunUpdateEvent(self.id, data=AgentRunResponseUpdate(contents=contents, role=Role.ASSISTANT))
|
||||
)
|
||||
return
|
||||
|
||||
@@ -178,12 +178,12 @@ class Worker(Executor):
|
||||
print("🔧 Worker: Incorporating feedback and regenerating response...")
|
||||
|
||||
# Construct new messages with feedback.
|
||||
messages.append(ChatMessage(role=ChatRole.SYSTEM, text=review.feedback))
|
||||
messages.append(ChatMessage(role=Role.SYSTEM, text=review.feedback))
|
||||
|
||||
# Add additional instruction to address the feedback.
|
||||
messages.append(
|
||||
ChatMessage(
|
||||
role=ChatRole.SYSTEM,
|
||||
role=Role.SYSTEM,
|
||||
text="Please incorporate the feedback above, and provide a response to user's next message.",
|
||||
)
|
||||
)
|
||||
@@ -234,7 +234,7 @@ async def main() -> None:
|
||||
print("-" * 50)
|
||||
|
||||
# Run the agent and stream events.
|
||||
async for event in agent.run_streaming(
|
||||
async for event in agent.run_stream(
|
||||
"Write code for parallel reading 1 million files on disk and write to a sorted output file."
|
||||
):
|
||||
print(f"📤 Agent Response: {event}")
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@ from dataclasses import dataclass
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
ChatRole,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Role,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.workflow import (
|
||||
@@ -133,7 +133,7 @@ async def main() -> None:
|
||||
result=human_response,
|
||||
)
|
||||
# Send the human review result back to the agent.
|
||||
response = await agent.run(ChatMessage(role=ChatRole.TOOL, contents=[human_review_function_result]))
|
||||
response = await agent.run(ChatMessage(role=Role.TOOL, contents=[human_review_function_result]))
|
||||
print(f"📤 Agent Response: {response.messages[-1].text}")
|
||||
|
||||
print("=" * 50)
|
||||
|
||||
@@ -14,7 +14,7 @@ Before you begin, ensure you have the following:
|
||||
|
||||
## Running a Basic Agent Sample
|
||||
|
||||
This sample demonstrates how to create and use a simple AI agent with Azure AI Foundry as the backend. It will create a basic agent using `ChatClientAgent` with `FoundryChatClient` and custom instructions.
|
||||
This sample demonstrates how to create and use a simple AI agent with Azure AI Foundry as the backend. It will create a basic agent using `ChatAgent` with `FoundryChatClient` and custom instructions.
|
||||
|
||||
Make sure to set the following environment variables:
|
||||
- `FOUNDRY_PROJECT_ENDPOINT`: Your Azure AI Foundry project endpoint
|
||||
@@ -26,14 +26,14 @@ For detailed information about different ways to run examples and configure envi
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async def main():
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are good at telling jokes."
|
||||
) as agent,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The Microsoft Agent Framework provides support for several types of agents to accommodate different use cases and requirements.
|
||||
|
||||
All agents implement a common protocol, `AIAgent`, which provides a consistent interface for all agent types. This allows for building common, agent agnostic, higher level functionality such as multi-agent orchestrations.
|
||||
All agents implement a common protocol, `AgentProtocol`, which provides a consistent interface for all agent types. This allows for building common, agent agnostic, higher level functionality such as multi-agent orchestrations.
|
||||
|
||||
Let's dive into each agent type in more detail.
|
||||
|
||||
@@ -19,16 +19,16 @@ These agents support a wide range of functionality out of the box:
|
||||
1. Structured output
|
||||
1. Streaming responses
|
||||
|
||||
To create one of these agents, simply construct a `ChatClientAgent` using the chat client implementation of your choice.
|
||||
To create one of these agents, simply construct a `ChatAgent` using the chat client implementation of your choice.
|
||||
|
||||
```python
|
||||
from agent_framework import ChatClientAgent
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant"
|
||||
) as agent
|
||||
@@ -89,7 +89,7 @@ response = await agent.run("What's the weather like in Seattle?")
|
||||
print(response.text)
|
||||
|
||||
# Streaming response (get results as they are generated)
|
||||
async for chunk in agent.run_streaming("What's the weather like in Portland?"):
|
||||
async for chunk in agent.run_stream("What's the weather like in Portland?"):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
```
|
||||
@@ -102,13 +102,13 @@ For streaming examples, see:
|
||||
Foundry agents support hosted code interpreter tools for executing Python code:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatClientAgent, HostedCodeInterpreterTool
|
||||
from agent_framework import ChatAgent, HostedCodeInterpreterTool
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant that can execute Python code.",
|
||||
tools=HostedCodeInterpreterTool()
|
||||
@@ -123,13 +123,13 @@ For code interpreter examples, see:
|
||||
## Custom agents
|
||||
|
||||
It is also possible to create fully custom agents that are not just wrappers around a chat client.
|
||||
Agent Framework provides the `AIAgent` protocol and `AgentBase` base class, which when implemented/subclassed allows for complete control over the agent's behavior and capabilities.
|
||||
Agent Framework provides the `AgentProtocol` protocol and `BaseAgent` base class, which when implemented/subclassed allows for complete control over the agent's behavior and capabilities.
|
||||
|
||||
```python
|
||||
from agent_framework import AgentBase, AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
|
||||
from agent_framework import BaseAgent, AgentRunResponse, AgentRunResponseUpdate, AgentThread, ChatMessage
|
||||
from collections.abc import AsyncIterable
|
||||
|
||||
class CustomAgent(AgentBase):
|
||||
class CustomAgent(BaseAgent):
|
||||
async def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
@@ -139,8 +139,8 @@ class CustomAgent(AgentBase):
|
||||
) -> AgentRunResponse:
|
||||
# Custom agent implementation
|
||||
pass
|
||||
|
||||
def run_streaming(
|
||||
|
||||
def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The Microsoft Agent Framework provides built-in support for managing multi-turn conversations with AI agents. This includes maintaining context across multiple interactions. Different agent types and underlying services that are used to build agents may support different threading types, and the Agent Framework abstracts these differences away, providing a consistent interface for developers.
|
||||
|
||||
For example, when using a `ChatClientAgent` based on a Foundry agent, the conversation history is persisted in the service. While when using a `ChatClientAgent` based on chat completion with gpt-4, the conversation history is in-memory and managed by the agent.
|
||||
For example, when using a `ChatAgent` based on a Foundry agent, the conversation history is persisted in the service. While when using a `ChatAgent` based on chat completion with gpt-4, the conversation history is in-memory and managed by the agent.
|
||||
|
||||
The differences between the underlying threading models are abstracted away via the `AgentThread` type.
|
||||
|
||||
@@ -56,7 +56,7 @@ response = await agent.run("Hello, how are you?", thread=resumed_thread)
|
||||
For in-memory threads, you can provide a custom message store implementation to control how messages are stored and retrieved:
|
||||
|
||||
```python
|
||||
from agent_framework import AgentThread, ChatMessageList, ChatClientAgent
|
||||
from agent_framework import AgentThread, ChatMessageList, ChatAgent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
@@ -71,7 +71,7 @@ def custom_message_store_factory():
|
||||
return ChatMessageList() # or your custom implementation
|
||||
|
||||
async with AzureCliCredential() as credential:
|
||||
agent = ChatClientAgent(
|
||||
agent = ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant",
|
||||
chat_message_store_factory=custom_message_store_factory
|
||||
@@ -82,7 +82,7 @@ async with AzureCliCredential() as credential:
|
||||
|
||||
`AIAgent` instances are stateless and the same agent instance can be used with multiple `AgentThread` instances.
|
||||
|
||||
Not all agents support all thread types though. For example if you are using a `ChatClientAgent` with the responses service, `AgentThread` instances created by this agent, will not work with a `ChatClientAgent` using the Foundry Agent service.
|
||||
Not all agents support all thread types though. For example if you are using a `ChatAgent` with the responses service, `AgentThread` instances created by this agent, will not work with a `ChatAgent` using the Foundry Agent service.
|
||||
This is because these services both support saving the conversation history in the service, and the `AgentThread`
|
||||
only has a reference to this service managed thread.
|
||||
|
||||
@@ -93,32 +93,32 @@ It is therefore considered unsafe to use an `AgentThread` instance that was crea
|
||||
Here's a complete example showing how to maintain context across multiple interactions:
|
||||
|
||||
```python
|
||||
from agent_framework import ChatClientAgent, AgentThread
|
||||
from agent_framework import ChatAgent, AgentThread
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
async def foundry_multi_turn_example():
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
ChatClientAgent(
|
||||
ChatAgent(
|
||||
chat_client=FoundryChatClient(async_credential=credential),
|
||||
instructions="You are a helpful assistant"
|
||||
) as agent
|
||||
):
|
||||
# Create a thread for persistent conversation
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
|
||||
# First interaction
|
||||
response1 = await agent.run("My name is Alice", thread=thread)
|
||||
print(f"Agent: {response1.text}")
|
||||
|
||||
|
||||
# Second interaction - agent remembers the name
|
||||
response2 = await agent.run("What's my name?", thread=thread)
|
||||
print(f"Agent: {response2.text}") # Should mention "Alice"
|
||||
|
||||
|
||||
# Serialize thread for storage
|
||||
serialized = await thread.serialize()
|
||||
|
||||
|
||||
# Later, deserialize and continue conversation
|
||||
new_thread = await agent.deserialize_thread(serialized)
|
||||
response3 = await agent.run("What did we talk about?", thread=new_thread)
|
||||
|
||||
Reference in New Issue
Block a user