mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix tool normalization and provider sample consolidation (#3953)
* Fix tool normalization and provider samples - restore callable/single-tool normalization paths and unset tool-choice behavior\n- consolidate and expand chat/provider samples (OpenAI/Azure/Anthropic/Ollama/Bedrock)\n- migrate Bedrock lazy import surface to agent_framework.amazon and move provider samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * small fix in sample * Finalize provider, samples, and core cleanup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CopilotTool passthrough in agent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix link --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
ed113f941c
commit
aab621f5eb
@@ -10,6 +10,21 @@ We use [ruff](https://github.com/astral-sh/ruff) for both linting and formatting
|
||||
- **Target Python version**: 3.10+
|
||||
- **Google-style docstrings**: All public functions, classes, and modules should have docstrings following Google conventions
|
||||
|
||||
### Module Docstrings
|
||||
|
||||
Public modules must include a module-level docstring, including `__init__.py` files.
|
||||
|
||||
- Namespace-style `__init__.py` modules (for example under `agent_framework/<provider>/`) should use a structured
|
||||
docstring that includes:
|
||||
- A one-line summary of the namespace
|
||||
- A short "This module lazily re-exports objects from:" section that lists only pip install package names
|
||||
(for example `agent-framework-a2a`)
|
||||
- A short "Supported classes:" (or "Supported classes and functions:") section
|
||||
- The main `agent_framework/__init__.py` should include a concise background-oriented docstring rather than a long
|
||||
per-symbol list.
|
||||
- Core modules with broad surface area, including `agent_framework/exceptions.py` and
|
||||
`agent_framework/observability.py`, should always have explicit module docstrings.
|
||||
|
||||
## Type Annotations
|
||||
|
||||
### Future Annotations
|
||||
|
||||
@@ -720,6 +720,8 @@ class AnthropicClient(
|
||||
if options.get("tool_choice") is None:
|
||||
return result or None
|
||||
tool_mode = validate_tool_mode(options.get("tool_choice"))
|
||||
if tool_mode is None:
|
||||
return result or None
|
||||
allow_multiple = options.get("allow_multiple_tool_calls")
|
||||
match tool_mode.get("mode"):
|
||||
case "auto":
|
||||
|
||||
@@ -16,6 +16,7 @@ from agent_framework import (
|
||||
)
|
||||
from agent_framework._mcp import MCPTool
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import ToolTypes
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.ai.agents.models import Agent as AzureAgent
|
||||
@@ -169,11 +170,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
model: str | None = None,
|
||||
instructions: str | None = None,
|
||||
description: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -242,7 +239,12 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
normalized_tools = normalize_tools(tools)
|
||||
if normalized_tools:
|
||||
# Only convert non-MCP tools to Azure AI format
|
||||
non_mcp_tools = [t for t in normalized_tools if not isinstance(t, MCPTool)]
|
||||
non_mcp_tools: list[FunctionTool | MutableMapping[str, Any]] = []
|
||||
for normalized_tool in normalized_tools:
|
||||
if isinstance(normalized_tool, MCPTool):
|
||||
continue
|
||||
if isinstance(normalized_tool, (FunctionTool, MutableMapping)):
|
||||
non_mcp_tools.append(normalized_tool)
|
||||
if non_mcp_tools:
|
||||
# Pass run_options to capture tool_resources (e.g., for file search vector stores)
|
||||
run_options: dict[str, Any] = {}
|
||||
@@ -266,11 +268,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
self,
|
||||
id: str,
|
||||
*,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -322,11 +320,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
def as_agent(
|
||||
self,
|
||||
agent: AzureAgent,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -379,7 +373,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
def _to_chat_agent_from_agent(
|
||||
self,
|
||||
agent: AzureAgent,
|
||||
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None = None,
|
||||
provided_tools: Sequence[ToolTypes] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -422,8 +416,8 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
def _merge_tools(
|
||||
self,
|
||||
agent_tools: Sequence[Any] | None,
|
||||
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
|
||||
) -> list[FunctionTool | dict[str, Any]]:
|
||||
provided_tools: Sequence[ToolTypes] | None,
|
||||
) -> list[ToolTypes]:
|
||||
"""Merge hosted tools from agent with user-provided function tools.
|
||||
|
||||
Args:
|
||||
@@ -433,7 +427,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
Returns:
|
||||
Combined list of tools for the Agent.
|
||||
"""
|
||||
merged: list[FunctionTool | dict[str, Any]] = []
|
||||
merged: list[ToolTypes] = []
|
||||
|
||||
# Convert hosted tools from agent definition
|
||||
hosted_tools = from_azure_ai_agent_tools(agent_tools)
|
||||
@@ -459,7 +453,7 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
def _validate_function_tools(
|
||||
self,
|
||||
agent_tools: Sequence[Any] | None,
|
||||
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
|
||||
provided_tools: Sequence[ToolTypes] | None,
|
||||
) -> None:
|
||||
"""Validate that required function tools are provided.
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from agent_framework import (
|
||||
UsageDetails,
|
||||
)
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import ToolTypes
|
||||
from agent_framework.exceptions import ServiceInitializationError, ServiceInvalidRequestError, ServiceResponseException
|
||||
from agent_framework.observability import ChatTelemetryLayer
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
@@ -1428,11 +1429,7 @@ class AzureAIAgentClient(
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
instructions: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: AzureAIAgentOptionsT | Mapping[str, Any] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Callable, Mapping, MutableMapping, Sequence
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from contextlib import suppress
|
||||
from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar, cast
|
||||
|
||||
@@ -22,6 +22,7 @@ from agent_framework import (
|
||||
MiddlewareTypes,
|
||||
)
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import ToolTypes
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from agent_framework.observability import ChatTelemetryLayer
|
||||
from agent_framework.openai import OpenAIResponsesOptions
|
||||
@@ -880,11 +881,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
instructions: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: AzureAIClientOptionsT | Mapping[str, Any] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
|
||||
@@ -17,6 +17,7 @@ from agent_framework import (
|
||||
)
|
||||
from agent_framework._mcp import MCPTool
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import ToolTypes
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
@@ -161,11 +162,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
model: str | None = None,
|
||||
instructions: str | None = None,
|
||||
description: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -226,7 +223,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
for tool in normalized_tools:
|
||||
if isinstance(tool, MCPTool):
|
||||
mcp_tools.append(tool)
|
||||
else:
|
||||
elif isinstance(tool, (FunctionTool, MutableMapping)):
|
||||
non_mcp_tools.append(tool)
|
||||
|
||||
# Connect MCP tools and discover their functions BEFORE creating the agent
|
||||
@@ -263,11 +260,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
*,
|
||||
name: str | None = None,
|
||||
reference: AgentReference | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -323,11 +316,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
def as_agent(
|
||||
self,
|
||||
details: AgentVersionDetails,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -367,7 +356,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
def _to_chat_agent_from_details(
|
||||
self,
|
||||
details: AgentVersionDetails,
|
||||
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None = None,
|
||||
provided_tools: Sequence[ToolTypes] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
@@ -415,8 +404,8 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
def _merge_tools(
|
||||
self,
|
||||
definition_tools: Sequence[Any] | None,
|
||||
provided_tools: Sequence[FunctionTool | MutableMapping[str, Any]] | None,
|
||||
) -> list[FunctionTool | dict[str, Any]]:
|
||||
provided_tools: Sequence[ToolTypes] | None,
|
||||
) -> list[ToolTypes]:
|
||||
"""Merge hosted tools from definition with user-provided function tools.
|
||||
|
||||
Args:
|
||||
@@ -426,7 +415,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
Returns:
|
||||
Combined list of tools for the Agent.
|
||||
"""
|
||||
merged: list[FunctionTool | dict[str, Any]] = []
|
||||
merged: list[ToolTypes] = []
|
||||
|
||||
# Convert hosted tools from definition (MCP, code interpreter, file search, web search)
|
||||
# Function tools from the definition are skipped - we use user-provided implementations instead
|
||||
@@ -450,11 +439,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
def _validate_function_tools(
|
||||
self,
|
||||
agent_tools: Sequence[Any] | None,
|
||||
provided_tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None,
|
||||
provided_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> None:
|
||||
"""Validate that required function tools are provided."""
|
||||
# Normalize and validate function tools
|
||||
|
||||
@@ -12,7 +12,7 @@ Integration with AWS Bedrock for LLM inference.
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from agent_framework_bedrock import BedrockChatClient
|
||||
from agent_framework.amazon import BedrockChatClient
|
||||
|
||||
client = BedrockChatClient(model_id="anthropic.claude-3-sonnet-20240229-v1:0")
|
||||
response = await client.get_response("Hello")
|
||||
@@ -21,5 +21,5 @@ response = await client.get_response("Hello")
|
||||
## Import Path
|
||||
|
||||
```python
|
||||
from agent_framework_bedrock import BedrockChatClient
|
||||
from agent_framework.amazon import BedrockChatClient
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ The Bedrock integration enables Microsoft Agent Framework applications to call A
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
See the [Bedrock sample script](samples/bedrock_sample.py) for a runnable end-to-end script that:
|
||||
See the [Bedrock sample](../../samples/02-agents/providers/amazon/bedrock_chat_client.py) for a runnable end-to-end script that:
|
||||
|
||||
- Loads credentials from the `BEDROCK_*` environment variables
|
||||
- Instantiates `BedrockChatClient`
|
||||
|
||||
@@ -260,7 +260,7 @@ class BedrockChatClient(
|
||||
Examples:
|
||||
.. code-block:: python
|
||||
|
||||
from agent_framework.bedrock import BedrockChatClient
|
||||
from agent_framework.amazon import BedrockChatClient
|
||||
|
||||
# Basic usage with default credentials
|
||||
client = BedrockChatClient(model_id="<model name>")
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from agent_framework import Agent, tool
|
||||
|
||||
from agent_framework_bedrock import BedrockChatClient
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(city: str) -> dict[str, str]:
|
||||
"""Return a mock forecast for the requested city."""
|
||||
normalized = city.strip() or "New York"
|
||||
return {"city": normalized, "forecast": "72F and sunny"}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run the Bedrock sample agent, invoke the weather tool, and log the response."""
|
||||
agent = Agent(
|
||||
client=BedrockChatClient(),
|
||||
instructions="You are a concise travel assistant.",
|
||||
name="BedrockWeatherAgent",
|
||||
tool_choice="auto",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
response = await agent.run("Use the weather tool to check the forecast for new york.")
|
||||
logging.info("\nAssistant reply:", response.text or "<no text returned>")
|
||||
logging.info("\nConversation transcript:")
|
||||
for message in response.messages:
|
||||
for idx, content in enumerate(message.contents, start=1):
|
||||
match content.type:
|
||||
case "text":
|
||||
logging.info(f" {idx}. text -> {content.text}")
|
||||
case "function_call":
|
||||
logging.info(f" {idx}. function_call ({content.name}) -> {content.arguments}")
|
||||
case "function_result":
|
||||
logging.info(f" {idx}. function_result ({content.call_id}) -> {content.result}")
|
||||
case _:
|
||||
logging.info(f" {idx}. {content.type}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -23,6 +23,7 @@ from agent_framework import (
|
||||
normalize_messages,
|
||||
)
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import ToolTypes
|
||||
from agent_framework._types import AgentRunInputs, normalize_tools
|
||||
from agent_framework.exceptions import ServiceException
|
||||
from claude_agent_sdk import (
|
||||
@@ -217,12 +218,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
description: str | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| str
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | str]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None = None,
|
||||
default_options: OptionsT | MutableMapping[str, Any] | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
@@ -289,7 +285,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
# Separate built-in tools (strings) from custom tools (callables/FunctionTool)
|
||||
self._builtin_tools: list[str] = []
|
||||
self._custom_tools: list[FunctionTool | MutableMapping[str, Any]] = []
|
||||
self._custom_tools: list[ToolTypes] = []
|
||||
self._normalize_tools(tools)
|
||||
|
||||
self._default_options = opts
|
||||
@@ -298,12 +294,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
def _normalize_tools(
|
||||
self,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| str
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | str]
|
||||
| None,
|
||||
tools: ToolTypes | Callable[..., Any] | str | Sequence[ToolTypes | Callable[..., Any] | str] | None,
|
||||
) -> None:
|
||||
"""Separate built-in tools (strings) from custom tools.
|
||||
|
||||
@@ -316,10 +307,10 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
# Normalize to sequence
|
||||
if isinstance(tools, str):
|
||||
tools_list: Sequence[Any] = [tools]
|
||||
elif isinstance(tools, (FunctionTool, MutableMapping)) or callable(tools):
|
||||
tools_list = [tools]
|
||||
else:
|
||||
elif isinstance(tools, Sequence):
|
||||
tools_list = list(tools)
|
||||
else:
|
||||
tools_list = [tools]
|
||||
|
||||
for tool in tools_list:
|
||||
if isinstance(tool, str):
|
||||
@@ -457,7 +448,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
def _prepare_tools(
|
||||
self,
|
||||
tools: list[FunctionTool | MutableMapping[str, Any]],
|
||||
tools: Sequence[ToolTypes],
|
||||
) -> tuple[Any, list[str]]:
|
||||
"""Convert Agent Framework tools to SDK MCP server.
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Public API surface for Agent Framework core.
|
||||
|
||||
This module exposes the primary abstractions for agents, chat clients, tools, sessions,
|
||||
middleware, observability, and workflows. Connector namespaces such as
|
||||
``agent_framework.azure`` and ``agent_framework.anthropic`` provide provider-specific
|
||||
integrations, many of which are lazy-loaded from optional packages.
|
||||
"""
|
||||
|
||||
import importlib.metadata
|
||||
from typing import Final
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ from ._sessions import AgentSession, BaseContextProvider, BaseHistoryProvider, I
|
||||
from ._tools import (
|
||||
FunctionInvocationLayer,
|
||||
FunctionTool,
|
||||
ToolTypes,
|
||||
normalize_tools,
|
||||
)
|
||||
from ._types import (
|
||||
AgentResponse,
|
||||
@@ -614,12 +616,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
id: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -665,24 +662,14 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
|
||||
# Get tools from options or named parameter (named param takes precedence)
|
||||
tools_ = tools if tools is not None else opts.pop("tools", None)
|
||||
tools_ = cast(
|
||||
FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None,
|
||||
tools_,
|
||||
)
|
||||
|
||||
# Handle instructions - named parameter takes precedence over options
|
||||
instructions_ = instructions if instructions is not None else opts.pop("instructions", None)
|
||||
|
||||
# We ignore the MCP Servers here and store them separately,
|
||||
# we add their functions to the tools list at runtime
|
||||
normalized_tools: list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]] = ( # type:ignore[reportUnknownVariableType]
|
||||
[] if tools_ is None else tools_ if isinstance(tools_, list) else [tools_] # type: ignore[list-item]
|
||||
)
|
||||
self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)] # type: ignore[misc]
|
||||
normalized_tools = normalize_tools(tools_)
|
||||
self.mcp_tools: list[MCPTool] = [tool for tool in normalized_tools if isinstance(tool, MCPTool)]
|
||||
agent_tools = [tool for tool in normalized_tools if not isinstance(tool, MCPTool)]
|
||||
|
||||
# Build chat options dict
|
||||
@@ -765,12 +752,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
session: AgentSession | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
options: ChatOptions[ResponseModelBoundT],
|
||||
**kwargs: Any,
|
||||
) -> Awaitable[AgentResponse[ResponseModelBoundT]]: ...
|
||||
@@ -782,12 +764,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
*,
|
||||
stream: Literal[False] = ...,
|
||||
session: AgentSession | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
options: OptionsCoT | ChatOptions[None] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Awaitable[AgentResponse[Any]]: ...
|
||||
@@ -799,12 +776,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
*,
|
||||
stream: Literal[True],
|
||||
session: AgentSession | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
|
||||
@@ -815,12 +787,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
*,
|
||||
stream: bool = False,
|
||||
session: AgentSession | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
options: OptionsCoT | ChatOptions[Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
|
||||
@@ -1000,12 +967,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
*,
|
||||
messages: AgentRunInputs | None,
|
||||
session: AgentSession | None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
options: Mapping[str, Any] | None,
|
||||
kwargs: dict[str, Any],
|
||||
) -> _RunContext:
|
||||
@@ -1035,9 +997,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
|
||||
)
|
||||
|
||||
# Normalize tools
|
||||
normalized_tools: list[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any] = (
|
||||
[] if tools_ is None else tools_ if isinstance(tools_, list) else [tools_]
|
||||
)
|
||||
normalized_tools = normalize_tools(tools_)
|
||||
agent_name = self._get_agent_name()
|
||||
|
||||
# Resolve final tool list (runtime provided tools + local MCP server tools)
|
||||
@@ -1343,12 +1303,7 @@ class Agent(
|
||||
id: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Any
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any] | Any]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
|
||||
@@ -10,7 +10,6 @@ from collections.abc import (
|
||||
Awaitable,
|
||||
Callable,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
)
|
||||
from typing import (
|
||||
@@ -31,7 +30,7 @@ from pydantic import BaseModel
|
||||
from ._serialization import SerializationMixin
|
||||
from ._tools import (
|
||||
FunctionInvocationConfiguration,
|
||||
FunctionTool,
|
||||
ToolTypes,
|
||||
)
|
||||
from ._types import (
|
||||
ChatResponse,
|
||||
@@ -436,11 +435,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[OptionsCoT]):
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
instructions: str | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsCoT | Mapping[str, Any] | None = None,
|
||||
context_providers: Sequence[Any] | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
|
||||
@@ -12,7 +12,6 @@ from collections.abc import (
|
||||
Awaitable,
|
||||
Callable,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
)
|
||||
from functools import partial, wraps
|
||||
@@ -25,6 +24,7 @@ from typing import (
|
||||
Final,
|
||||
Generic,
|
||||
Literal,
|
||||
TypeAlias,
|
||||
TypedDict,
|
||||
Union,
|
||||
get_args,
|
||||
@@ -58,6 +58,7 @@ else:
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._clients import SupportsChatGetResponse
|
||||
from ._mcp import MCPTool
|
||||
from ._middleware import FunctionMiddlewarePipeline, FunctionMiddlewareTypes
|
||||
from ._types import (
|
||||
ChatOptions,
|
||||
@@ -69,6 +70,8 @@ if TYPE_CHECKING:
|
||||
)
|
||||
|
||||
ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel)
|
||||
else:
|
||||
MCPTool = Any # type: ignore[assignment,misc]
|
||||
|
||||
|
||||
logger = logging.getLogger("agent_framework")
|
||||
@@ -506,9 +509,7 @@ class FunctionTool(SerializationMixin):
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined]
|
||||
attributes.update({
|
||||
OtelAttr.TOOL_ARGUMENTS: (
|
||||
json.dumps(serializable_kwargs, default=str, ensure_ascii=False)
|
||||
if serializable_kwargs
|
||||
else "None"
|
||||
json.dumps(serializable_kwargs, default=str, ensure_ascii=False) if serializable_kwargs else "None"
|
||||
)
|
||||
})
|
||||
with get_function_span(attributes=attributes) as span:
|
||||
@@ -623,14 +624,46 @@ class FunctionTool(SerializationMixin):
|
||||
return as_dict
|
||||
|
||||
|
||||
ToolTypes: TypeAlias = FunctionTool | MCPTool | Mapping[str, Any] | Any
|
||||
|
||||
|
||||
def normalize_tools(
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> list[ToolTypes]:
|
||||
"""Normalize tool inputs while preserving non-callable tool objects.
|
||||
|
||||
Args:
|
||||
tools: A single tool or sequence of tools.
|
||||
|
||||
Returns:
|
||||
A normalized list where callable inputs are converted to ``FunctionTool``
|
||||
using :func:`tool`, and existing tool objects are passed through unchanged.
|
||||
"""
|
||||
if not tools:
|
||||
return []
|
||||
|
||||
tool_items = (
|
||||
list(tools)
|
||||
if isinstance(tools, Sequence) and not isinstance(tools, (str, bytes, bytearray, Mapping))
|
||||
else [tools]
|
||||
)
|
||||
from ._mcp import MCPTool
|
||||
|
||||
normalized: list[ToolTypes] = []
|
||||
for tool_item in tool_items:
|
||||
# check known types, these are also callable, so we need to do that first
|
||||
if isinstance(tool_item, (FunctionTool, Mapping, MCPTool)):
|
||||
normalized.append(tool_item)
|
||||
continue
|
||||
if callable(tool_item):
|
||||
normalized.append(tool(tool_item))
|
||||
continue
|
||||
normalized.append(tool_item)
|
||||
return normalized
|
||||
|
||||
|
||||
def _tools_to_dict(
|
||||
tools: (
|
||||
FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None
|
||||
),
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> list[str | dict[str, Any]] | None:
|
||||
"""Parse the tools to a dict.
|
||||
|
||||
@@ -640,32 +673,20 @@ def _tools_to_dict(
|
||||
Returns:
|
||||
A list of tool specifications as dictionaries, or None if no tools provided.
|
||||
"""
|
||||
if not tools:
|
||||
return None
|
||||
if not isinstance(tools, list):
|
||||
if isinstance(tools, FunctionTool):
|
||||
return [tools.to_json_schema_spec()]
|
||||
if isinstance(tools, SerializationMixin):
|
||||
return [tools.to_dict()]
|
||||
if isinstance(tools, dict):
|
||||
return [tools]
|
||||
if callable(tools):
|
||||
return [tool(tools).to_json_schema_spec()]
|
||||
logger.warning("Can't parse tool.")
|
||||
normalized_tools = normalize_tools(tools)
|
||||
if not normalized_tools:
|
||||
return None
|
||||
|
||||
results: list[str | dict[str, Any]] = []
|
||||
for tool_item in tools:
|
||||
for tool_item in normalized_tools:
|
||||
if isinstance(tool_item, FunctionTool):
|
||||
results.append(tool_item.to_json_schema_spec())
|
||||
continue
|
||||
if isinstance(tool_item, SerializationMixin):
|
||||
results.append(tool_item.to_dict())
|
||||
continue
|
||||
if isinstance(tool_item, dict):
|
||||
results.append(tool_item)
|
||||
continue
|
||||
if callable(tool_item):
|
||||
results.append(tool(tool_item).to_json_schema_spec())
|
||||
if isinstance(tool_item, Mapping):
|
||||
results.append(dict(tool_item))
|
||||
continue
|
||||
logger.warning("Can't parse tool.")
|
||||
return results
|
||||
@@ -1430,20 +1451,12 @@ async def _auto_invoke_function(
|
||||
|
||||
|
||||
def _get_tool_map(
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]],
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]],
|
||||
) -> dict[str, FunctionTool]:
|
||||
tool_list: dict[str, FunctionTool] = {}
|
||||
for tool_item in tools if isinstance(tools, list) else [tools]:
|
||||
for tool_item in normalize_tools(tools):
|
||||
if isinstance(tool_item, FunctionTool):
|
||||
tool_list[tool_item.name] = tool_item
|
||||
continue
|
||||
if callable(tool_item):
|
||||
# Convert to AITool if it's a function or callable
|
||||
ai_tool = tool(tool_item)
|
||||
tool_list[ai_tool.name] = ai_tool
|
||||
return tool_list
|
||||
|
||||
|
||||
@@ -1451,10 +1464,7 @@ async def _try_execute_function_calls(
|
||||
custom_args: dict[str, Any],
|
||||
attempt_idx: int,
|
||||
function_calls: Sequence[Content],
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]],
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]],
|
||||
config: FunctionInvocationConfiguration,
|
||||
middleware_pipeline: Any = None, # Optional MiddlewarePipeline to avoid circular imports
|
||||
) -> tuple[Sequence[Content], bool]:
|
||||
@@ -1633,15 +1643,16 @@ async def _ensure_response_stream(
|
||||
return stream
|
||||
|
||||
|
||||
def _extract_tools(options: dict[str, Any] | None) -> Any:
|
||||
def _extract_tools(
|
||||
options: dict[str, Any] | None,
|
||||
) -> ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None:
|
||||
"""Extract tools from options dict.
|
||||
|
||||
Args:
|
||||
options: The options dict containing chat options.
|
||||
|
||||
Returns:
|
||||
FunctionTool | Callable[..., Any] | MutableMapping[str, Any] |
|
||||
Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]] | None
|
||||
ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None
|
||||
"""
|
||||
if options and isinstance(options, dict):
|
||||
return options.get("tools")
|
||||
@@ -1996,6 +2007,11 @@ class FunctionInvocationLayer(Generic[OptionsCoT]):
|
||||
# Remove additional_function_arguments from options passed to underlying chat client
|
||||
# It's for tool invocation only and not recognized by chat service APIs
|
||||
mutable_options.pop("additional_function_arguments", None)
|
||||
# Support tools passed via kwargs in direct client.get_response(...) calls.
|
||||
if "tools" in filtered_kwargs:
|
||||
if mutable_options.get("tools") is None:
|
||||
mutable_options["tools"] = filtered_kwargs["tools"]
|
||||
filtered_kwargs.pop("tools", None)
|
||||
|
||||
if not stream:
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Final, Generic, Literal, NewTyp
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._serialization import SerializationMixin
|
||||
from ._tools import FunctionTool, tool
|
||||
from ._tools import ToolTypes
|
||||
from ._tools import normalize_tools as _normalize_tools
|
||||
from .exceptions import AdditionItemMismatch, ContentError
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
@@ -2871,10 +2872,9 @@ class _ChatOptionsBase(TypedDict, total=False):
|
||||
|
||||
# Tool configuration (forward reference to avoid circular import)
|
||||
tools: (
|
||||
FunctionTool
|
||||
ToolTypes
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| Sequence[ToolTypes | Callable[..., Any]]
|
||||
| None
|
||||
)
|
||||
tool_choice: ToolMode | Literal["auto", "required", "none"]
|
||||
@@ -2963,18 +2963,11 @@ async def validate_chat_options(options: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def normalize_tools(
|
||||
tools: (
|
||||
FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None
|
||||
),
|
||||
) -> list[FunctionTool | MutableMapping[str, Any]]:
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> list[ToolTypes]:
|
||||
"""Normalize tools into a list.
|
||||
|
||||
Converts callables to FunctionTool objects and ensures all tools are either
|
||||
FunctionTool instances or MutableMappings.
|
||||
Converts callables to FunctionTool objects and preserves existing tool objects.
|
||||
|
||||
Args:
|
||||
tools: Tools to normalize - can be a single tool, callable, or sequence.
|
||||
@@ -2999,37 +2992,16 @@ def normalize_tools(
|
||||
# List of tools
|
||||
tools = normalize_tools([my_tool, another_tool])
|
||||
"""
|
||||
final_tools: list[FunctionTool | MutableMapping[str, Any]] = []
|
||||
if not tools:
|
||||
return final_tools
|
||||
if not isinstance(tools, Sequence) or isinstance(tools, (str, MutableMapping)):
|
||||
# Single tool (not a sequence, or is a mapping which shouldn't be treated as sequence)
|
||||
if not isinstance(tools, (FunctionTool, MutableMapping)):
|
||||
return [tool(tools)]
|
||||
return [tools]
|
||||
for tool_item in tools:
|
||||
if isinstance(tool_item, (FunctionTool, MutableMapping)):
|
||||
final_tools.append(tool_item)
|
||||
else:
|
||||
# Convert callable to FunctionTool
|
||||
final_tools.append(tool(tool_item))
|
||||
return final_tools
|
||||
return _normalize_tools(tools)
|
||||
|
||||
|
||||
async def validate_tools(
|
||||
tools: (
|
||||
FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None
|
||||
),
|
||||
) -> list[FunctionTool | MutableMapping[str, Any]]:
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> list[ToolTypes]:
|
||||
"""Validate and normalize tools into a list.
|
||||
|
||||
Converts callables to FunctionTool objects, expands MCP tools to their constituent
|
||||
functions (connecting them if needed), and ensures all tools are either FunctionTool
|
||||
instances or MutableMappings.
|
||||
functions (connecting them if needed), while preserving non-callable tool objects.
|
||||
|
||||
Args:
|
||||
tools: Tools to validate - can be a single tool, callable, or sequence.
|
||||
@@ -3058,7 +3030,7 @@ async def validate_tools(
|
||||
normalized = normalize_tools(tools)
|
||||
|
||||
# Handle MCP tool expansion (async-only)
|
||||
final_tools: list[FunctionTool | MutableMapping[str, Any]] = []
|
||||
final_tools: list[ToolTypes] = []
|
||||
for tool_ in normalized:
|
||||
# Import MCPTool here to avoid circular imports
|
||||
from ._mcp import MCPTool
|
||||
@@ -3076,20 +3048,21 @@ async def validate_tools(
|
||||
|
||||
def validate_tool_mode(
|
||||
tool_choice: ToolMode | Literal["auto", "required", "none"] | None,
|
||||
) -> ToolMode:
|
||||
) -> ToolMode | None:
|
||||
"""Validate and normalize tool_choice to a ToolMode dict.
|
||||
|
||||
Args:
|
||||
tool_choice: The tool choice value to validate.
|
||||
|
||||
Returns:
|
||||
A ToolMode dict (contains keys: "mode", and optionally "required_function_name").
|
||||
A ToolMode dict (contains keys: "mode", and optionally
|
||||
"required_function_name"), or ``None`` when not provided.
|
||||
|
||||
Raises:
|
||||
ContentError: If the tool_choice string is invalid.
|
||||
"""
|
||||
if not tool_choice:
|
||||
return {"mode": "none"}
|
||||
if tool_choice is None:
|
||||
return None
|
||||
if isinstance(tool_choice, str):
|
||||
if tool_choice not in ("auto", "required", "none"):
|
||||
raise ContentError(f"Invalid tool choice: {tool_choice}")
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Workflow namespace for built-in Agent Framework orchestration primitives.
|
||||
|
||||
This module re-exports objects from workflow implementation modules under
|
||||
``agent_framework._workflows``.
|
||||
|
||||
Supported classes include:
|
||||
- Workflow
|
||||
- WorkflowBuilder
|
||||
- AgentExecutor
|
||||
- Runner
|
||||
- WorkflowExecutor
|
||||
"""
|
||||
|
||||
from ._agent import WorkflowAgent
|
||||
from ._agent_executor import (
|
||||
AgentExecutor,
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""A2A integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-a2a``
|
||||
|
||||
Supported classes:
|
||||
- A2AAgent
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_a2a"
|
||||
PACKAGE_NAME = "agent-framework-a2a"
|
||||
_IMPORTS = ["__version__", "A2AAgent"]
|
||||
_IMPORTS = ["A2AAgent"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
from agent_framework_a2a import (
|
||||
A2AAgent,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"A2AAgent",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""AG-UI integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-ag-ui``
|
||||
|
||||
Supported classes and functions:
|
||||
- AgentFrameworkAgent
|
||||
- AGUIChatClient
|
||||
- AGUIEventConverter
|
||||
- AGUIHttpService
|
||||
- add_agent_framework_fastapi_endpoint
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_ag_ui"
|
||||
PACKAGE_NAME = "agent-framework-ag-ui"
|
||||
_IMPORTS = [
|
||||
"__version__",
|
||||
"AgentFrameworkAgent",
|
||||
"add_agent_framework_fastapi_endpoint",
|
||||
"AGUIChatClient",
|
||||
|
||||
@@ -5,7 +5,6 @@ from agent_framework_ag_ui import (
|
||||
AGUIChatClient,
|
||||
AGUIEventConverter,
|
||||
AGUIHttpService,
|
||||
__version__,
|
||||
add_agent_framework_fastapi_endpoint,
|
||||
)
|
||||
|
||||
@@ -14,6 +13,5 @@ __all__ = [
|
||||
"AGUIEventConverter",
|
||||
"AGUIHttpService",
|
||||
"AgentFrameworkAgent",
|
||||
"__version__",
|
||||
"add_agent_framework_fastapi_endpoint",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Amazon Bedrock integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-bedrock``
|
||||
|
||||
Supported classes:
|
||||
- BedrockChatClient
|
||||
- BedrockChatOptions
|
||||
- BedrockGuardrailConfig
|
||||
- BedrockSettings
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_bedrock"
|
||||
PACKAGE_NAME = "agent-framework-bedrock"
|
||||
_IMPORTS = ["BedrockChatClient", "BedrockChatOptions", "BedrockGuardrailConfig", "BedrockSettings"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _IMPORTS:
|
||||
try:
|
||||
return getattr(importlib.import_module(IMPORT_PATH), name)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ModuleNotFoundError(
|
||||
f"The '{PACKAGE_NAME}' package is not installed, please do `pip install {PACKAGE_NAME}`"
|
||||
) from exc
|
||||
raise AttributeError(f"Module {IMPORT_PATH} has no attribute {name}.")
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return _IMPORTS
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from agent_framework_bedrock import (
|
||||
BedrockChatClient,
|
||||
BedrockChatOptions,
|
||||
BedrockGuardrailConfig,
|
||||
BedrockSettings,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BedrockChatClient",
|
||||
"BedrockChatOptions",
|
||||
"BedrockGuardrailConfig",
|
||||
"BedrockSettings",
|
||||
]
|
||||
@@ -1,23 +1,40 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Anthropic integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-anthropic``
|
||||
- ``agent-framework-claude``
|
||||
|
||||
Supported classes:
|
||||
- AnthropicClient
|
||||
- AnthropicChatOptions
|
||||
- ClaudeAgent
|
||||
- ClaudeAgentOptions
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_anthropic"
|
||||
PACKAGE_NAME = "agent-framework-anthropic"
|
||||
_IMPORTS = ["__version__", "AnthropicClient", "AnthropicChatOptions"]
|
||||
_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"AnthropicClient": ("agent_framework_anthropic", "agent-framework-anthropic"),
|
||||
"AnthropicChatOptions": ("agent_framework_anthropic", "agent-framework-anthropic"),
|
||||
"ClaudeAgent": ("agent_framework_claude", "agent-framework-claude"),
|
||||
"ClaudeAgentOptions": ("agent_framework_claude", "agent-framework-claude"),
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _IMPORTS:
|
||||
import_path, package_name = _IMPORTS[name]
|
||||
try:
|
||||
return getattr(importlib.import_module(IMPORT_PATH), name)
|
||||
return getattr(importlib.import_module(import_path), name)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ModuleNotFoundError(
|
||||
f"The '{PACKAGE_NAME}' package is not installed, please do `pip install {PACKAGE_NAME}`"
|
||||
f"The '{package_name}' package is not installed, please do `pip install {package_name}`"
|
||||
) from exc
|
||||
raise AttributeError(f"Module {IMPORT_PATH} has no attribute {name}.")
|
||||
raise AttributeError(f"Module `anthropic` has no attribute {name}.")
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return _IMPORTS
|
||||
return list(_IMPORTS.keys())
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
from agent_framework_anthropic import (
|
||||
AnthropicChatOptions,
|
||||
AnthropicClient,
|
||||
__version__,
|
||||
)
|
||||
from agent_framework_claude import ClaudeAgent, ClaudeAgentOptions
|
||||
|
||||
__all__ = [
|
||||
"AnthropicChatOptions",
|
||||
"AnthropicClient",
|
||||
"__version__",
|
||||
"ClaudeAgent",
|
||||
"ClaudeAgentOptions",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Azure integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from optional Azure connector packages and
|
||||
built-in core Azure OpenAI modules.
|
||||
|
||||
Supported classes include:
|
||||
- AzureAIClient
|
||||
- AzureAIAgentClient
|
||||
- AzureOpenAIChatClient
|
||||
- AzureOpenAIResponsesClient
|
||||
- AzureAISearchContextProvider
|
||||
- DurableAIAgent
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""ChatKit integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-chatkit``
|
||||
|
||||
Supported classes and functions:
|
||||
- ThreadItemConverter
|
||||
- simple_to_agent_input
|
||||
- stream_agent_response
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_chatkit"
|
||||
PACKAGE_NAME = "agent-framework-chatkit"
|
||||
_IMPORTS = ["__version__", "ThreadItemConverter", "simple_to_agent_input", "stream_agent_response"]
|
||||
_IMPORTS = ["ThreadItemConverter", "simple_to_agent_input", "stream_agent_response"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
from agent_framework_chatkit import (
|
||||
ThreadItemConverter,
|
||||
__version__,
|
||||
simple_to_agent_input,
|
||||
stream_agent_response,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ThreadItemConverter",
|
||||
"__version__",
|
||||
"simple_to_agent_input",
|
||||
"stream_agent_response",
|
||||
]
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Declarative integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-declarative``
|
||||
|
||||
Supported classes include:
|
||||
- AgentFactory
|
||||
- WorkflowFactory
|
||||
- ExternalInputRequest
|
||||
- ExternalInputResponse
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_declarative"
|
||||
PACKAGE_NAME = "agent-framework-declarative"
|
||||
_IMPORTS = [
|
||||
"__version__",
|
||||
"AgentFactory",
|
||||
"AgentExternalInputRequest",
|
||||
"AgentExternalInputResponse",
|
||||
|
||||
@@ -13,7 +13,6 @@ from agent_framework_declarative import (
|
||||
ProviderTypeMapping,
|
||||
WorkflowFactory,
|
||||
WorkflowState,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -29,5 +28,4 @@ __all__ = [
|
||||
"ProviderTypeMapping",
|
||||
"WorkflowFactory",
|
||||
"WorkflowState",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""DevUI integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-devui``
|
||||
|
||||
Supported classes and functions include:
|
||||
- DevServer
|
||||
- AgentFrameworkRequest
|
||||
- DiscoveryResponse
|
||||
- ResponseStreamEvent
|
||||
- serve
|
||||
- main
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
@@ -16,7 +30,6 @@ _IMPORTS = [
|
||||
"main",
|
||||
"register_cleanup",
|
||||
"serve",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from agent_framework_devui import (
|
||||
OpenAIError,
|
||||
OpenAIResponse,
|
||||
ResponseStreamEvent,
|
||||
__version__,
|
||||
main,
|
||||
register_cleanup,
|
||||
serve,
|
||||
@@ -22,7 +21,6 @@ __all__ = [
|
||||
"OpenAIError",
|
||||
"OpenAIResponse",
|
||||
"ResponseStreamEvent",
|
||||
"__version__",
|
||||
"main",
|
||||
"register_cleanup",
|
||||
"serve",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Exception hierarchy used across Agent Framework core and connectors."""
|
||||
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""GitHub integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-github-copilot``
|
||||
|
||||
Supported classes:
|
||||
- GitHubCopilotAgent
|
||||
- GitHubCopilotOptions
|
||||
- GitHubCopilotSettings
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
@@ -7,7 +18,6 @@ _IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"GitHubCopilotAgent": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
|
||||
"GitHubCopilotOptions": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
|
||||
"GitHubCopilotSettings": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
|
||||
"__version__": ("agent_framework_github_copilot", "agent-framework-github-copilot"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@ from agent_framework_github_copilot import (
|
||||
GitHubCopilotAgent,
|
||||
GitHubCopilotOptions,
|
||||
GitHubCopilotSettings,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"GitHubCopilotAgent",
|
||||
"GitHubCopilotOptions",
|
||||
"GitHubCopilotSettings",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Lab namespace package for experimental Agent Framework integrations.
|
||||
|
||||
This module extends the package path so experimental lab integrations can be
|
||||
distributed in separate packages under the ``agent_framework.lab`` namespace.
|
||||
"""
|
||||
|
||||
# This makes agent_framework.lab a namespace package
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Mem0 integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-mem0``
|
||||
|
||||
Supported classes:
|
||||
- Mem0ContextProvider
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_mem0"
|
||||
PACKAGE_NAME = "agent-framework-mem0"
|
||||
_IMPORTS = ["__version__", "Mem0ContextProvider"]
|
||||
_IMPORTS = ["Mem0ContextProvider"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
from agent_framework_mem0 import (
|
||||
Mem0ContextProvider,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Mem0ContextProvider",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Microsoft integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-copilotstudio``
|
||||
- ``agent-framework-purview``
|
||||
- ``agent-framework-foundry-local``
|
||||
|
||||
Supported classes:
|
||||
- CopilotStudioAgent
|
||||
- PurviewPolicyMiddleware
|
||||
- PurviewChatPolicyMiddleware
|
||||
- PurviewSettings
|
||||
- PurviewAppLocation
|
||||
- PurviewLocationType
|
||||
- PurviewAuthenticationError
|
||||
- PurviewPaymentRequiredError
|
||||
- PurviewRateLimitError
|
||||
- PurviewRequestError
|
||||
- PurviewServiceError
|
||||
- CacheProvider
|
||||
- FoundryLocalChatOptions
|
||||
- FoundryLocalClient
|
||||
- FoundryLocalSettings
|
||||
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"CopilotStudioAgent": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
|
||||
"__version__": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
|
||||
"acquire_token": ("agent_framework_copilotstudio", "agent-framework-copilotstudio"),
|
||||
"PurviewPolicyMiddleware": ("agent_framework_purview", "agent-framework-purview"),
|
||||
"PurviewChatPolicyMiddleware": ("agent_framework_purview", "agent-framework-purview"),
|
||||
@@ -18,6 +43,9 @@ _IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"PurviewRequestError": ("agent_framework_purview", "agent-framework-purview"),
|
||||
"PurviewServiceError": ("agent_framework_purview", "agent-framework-purview"),
|
||||
"CacheProvider": ("agent_framework_purview", "agent-framework-purview"),
|
||||
"FoundryLocalChatOptions": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
|
||||
"FoundryLocalClient": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
|
||||
"FoundryLocalSettings": ("agent_framework_foundry_local", "agent-framework-foundry-local"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
|
||||
from agent_framework_copilotstudio import (
|
||||
CopilotStudioAgent,
|
||||
__version__,
|
||||
acquire_token,
|
||||
)
|
||||
from agent_framework_foundry_local import (
|
||||
FoundryLocalChatOptions,
|
||||
FoundryLocalClient,
|
||||
FoundryLocalSettings,
|
||||
)
|
||||
from agent_framework_purview import (
|
||||
CacheProvider,
|
||||
PurviewAppLocation,
|
||||
@@ -22,6 +26,9 @@ from agent_framework_purview import (
|
||||
__all__ = [
|
||||
"CacheProvider",
|
||||
"CopilotStudioAgent",
|
||||
"FoundryLocalChatOptions",
|
||||
"FoundryLocalClient",
|
||||
"FoundryLocalSettings",
|
||||
"PurviewAppLocation",
|
||||
"PurviewAuthenticationError",
|
||||
"PurviewChatPolicyMiddleware",
|
||||
@@ -32,6 +39,5 @@ __all__ = [
|
||||
"PurviewRequestError",
|
||||
"PurviewServiceError",
|
||||
"PurviewSettings",
|
||||
"__version__",
|
||||
"acquire_token",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Observability and OpenTelemetry helpers for Agent Framework.
|
||||
|
||||
Commonly used exports:
|
||||
- enable_instrumentation
|
||||
- configure_otel_providers
|
||||
- AgentTelemetryLayer
|
||||
- ChatTelemetryLayer
|
||||
- get_tracer
|
||||
- get_meter
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
@@ -1128,11 +1139,8 @@ class ChatTelemetryLayer(Generic[OptionsCoT]):
|
||||
opts: dict[str, Any] = options or {} # type: ignore[assignment]
|
||||
provider_name = str(self.otel_provider_name)
|
||||
model_id = kwargs.get("model_id") or opts.get("model_id") or getattr(self, "model_id", None) or "unknown"
|
||||
service_url = str(
|
||||
service_url_func()
|
||||
if (service_url_func := getattr(self, "service_url", None)) and callable(service_url_func)
|
||||
else "unknown"
|
||||
)
|
||||
service_url_func = getattr(self, "service_url", None)
|
||||
service_url = str(service_url_func() if callable(service_url_func) else "unknown")
|
||||
attributes = _get_span_attributes(
|
||||
operation_name=OtelAttr.CHAT_COMPLETION_OPERATION,
|
||||
provider_name=provider_name,
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Ollama integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-ollama``
|
||||
|
||||
Supported classes:
|
||||
- OllamaChatClient
|
||||
- OllamaSettings
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_ollama"
|
||||
PACKAGE_NAME = "agent-framework-ollama"
|
||||
_IMPORTS = ["__version__", "OllamaChatClient", "OllamaSettings"]
|
||||
_IMPORTS = ["OllamaChatClient", "OllamaSettings"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
from agent_framework_ollama import (
|
||||
OllamaChatClient,
|
||||
OllamaSettings,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"OllamaChatClient",
|
||||
"OllamaSettings",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""OpenAI namespace for built-in Agent Framework clients.
|
||||
|
||||
This module re-exports objects from the core OpenAI implementation modules in
|
||||
``agent_framework.openai``.
|
||||
|
||||
Supported classes include:
|
||||
- OpenAIChatClient
|
||||
- OpenAIResponsesClient
|
||||
- OpenAIAssistantsClient
|
||||
- OpenAIAssistantProvider
|
||||
"""
|
||||
|
||||
from ._assistant_provider import OpenAIAssistantProvider
|
||||
from ._assistants_client import (
|
||||
AssistantToolResources,
|
||||
|
||||
@@ -15,8 +15,7 @@ from agent_framework._settings import SecretString, load_settings
|
||||
from .._agents import Agent
|
||||
from .._middleware import MiddlewareTypes
|
||||
from .._sessions import BaseContextProvider
|
||||
from .._tools import FunctionTool
|
||||
from .._types import normalize_tools
|
||||
from .._tools import FunctionTool, ToolTypes, normalize_tools
|
||||
from ..exceptions import ServiceInitializationError
|
||||
from ._assistants_client import OpenAIAssistantsClient
|
||||
from ._shared import OpenAISettings, from_assistant_tools, to_assistant_tools
|
||||
@@ -43,13 +42,6 @@ OptionsCoT = TypeVar(
|
||||
covariant=True,
|
||||
)
|
||||
|
||||
_ToolsType = (
|
||||
FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
)
|
||||
|
||||
|
||||
class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
"""Provider for creating Agent instances from OpenAI Assistants API.
|
||||
@@ -203,7 +195,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
model: str,
|
||||
instructions: str | None = None,
|
||||
description: str | None = None,
|
||||
tools: _ToolsType | None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
metadata: dict[str, str] | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
@@ -259,7 +251,8 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
"""
|
||||
# Normalize tools
|
||||
normalized_tools = normalize_tools(tools)
|
||||
api_tools = to_assistant_tools(normalized_tools) if normalized_tools else []
|
||||
assistant_tools = [tool for tool in normalized_tools if isinstance(tool, (FunctionTool, MutableMapping))]
|
||||
api_tools = to_assistant_tools(assistant_tools) if assistant_tools else []
|
||||
|
||||
# Extract response_format from default_options if present
|
||||
opts = dict(default_options) if default_options else {}
|
||||
@@ -311,7 +304,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
self,
|
||||
assistant_id: str,
|
||||
*,
|
||||
tools: _ToolsType | None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
instructions: str | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
@@ -377,7 +370,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
self,
|
||||
assistant: Assistant,
|
||||
*,
|
||||
tools: _ToolsType | None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
instructions: str | None = None,
|
||||
default_options: OptionsCoT | None = None,
|
||||
middleware: Sequence[MiddlewareTypes] | None = None,
|
||||
@@ -442,7 +435,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
def _validate_function_tools(
|
||||
self,
|
||||
assistant_tools: list[Any],
|
||||
provided_tools: _ToolsType | None,
|
||||
provided_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> None:
|
||||
"""Validate that required function tools are provided.
|
||||
|
||||
@@ -493,8 +486,8 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
def _merge_tools(
|
||||
self,
|
||||
assistant_tools: list[Any],
|
||||
user_tools: _ToolsType | None,
|
||||
) -> list[FunctionTool | MutableMapping[str, Any]]:
|
||||
user_tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> list[FunctionTool | MutableMapping[str, Any] | Any]:
|
||||
"""Merge hosted tools from assistant with user-provided function tools.
|
||||
|
||||
Args:
|
||||
@@ -504,7 +497,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
Returns:
|
||||
A list of all tools (hosted tools + user function implementations).
|
||||
"""
|
||||
merged: list[FunctionTool | MutableMapping[str, Any]] = []
|
||||
merged: list[FunctionTool | MutableMapping[str, Any] | Any] = []
|
||||
|
||||
# Add hosted tools from assistant using shared conversion
|
||||
hosted_tools = from_assistant_tools(assistant_tools)
|
||||
@@ -520,7 +513,7 @@ class OpenAIAssistantProvider(Generic[OptionsCoT]):
|
||||
def _create_chat_agent_from_assistant(
|
||||
self,
|
||||
assistant: Assistant,
|
||||
tools: list[FunctionTool | MutableMapping[str, Any]] | None,
|
||||
tools: list[FunctionTool | MutableMapping[str, Any] | Any] | None,
|
||||
instructions: str | None,
|
||||
middleware: Sequence[MiddlewareTypes] | None,
|
||||
context_providers: Sequence[BaseContextProvider] | None,
|
||||
|
||||
@@ -36,6 +36,7 @@ from .._tools import (
|
||||
FunctionInvocationConfiguration,
|
||||
FunctionInvocationLayer,
|
||||
FunctionTool,
|
||||
normalize_tools,
|
||||
)
|
||||
from .._types import (
|
||||
ChatOptions,
|
||||
@@ -686,26 +687,26 @@ class OpenAIAssistantsClient( # type: ignore[misc]
|
||||
tool_definitions: list[MutableMapping[str, Any]] = []
|
||||
# Always include tools if provided, regardless of tool_choice
|
||||
# tool_choice="none" means the model won't call tools, but tools should still be available
|
||||
if tools is not None:
|
||||
for tool in tools:
|
||||
if isinstance(tool, FunctionTool):
|
||||
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
|
||||
elif isinstance(tool, MutableMapping):
|
||||
# Pass through dict-based tools directly (from static factory methods)
|
||||
tool_definitions.append(tool)
|
||||
for tool in normalize_tools(tools):
|
||||
if isinstance(tool, FunctionTool):
|
||||
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
|
||||
elif isinstance(tool, MutableMapping):
|
||||
# Pass through dict-based tools directly (from static factory methods)
|
||||
tool_definitions.append(tool)
|
||||
|
||||
if len(tool_definitions) > 0:
|
||||
run_options["tools"] = tool_definitions
|
||||
|
||||
if (mode := tool_mode["mode"]) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"function": {"name": func_name},
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
if tool_mode is not None:
|
||||
if (mode := tool_mode["mode"]) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"function": {"name": func_name},
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
|
||||
if response_format is not None:
|
||||
if isinstance(response_format, dict):
|
||||
|
||||
@@ -27,6 +27,8 @@ from .._tools import (
|
||||
FunctionInvocationConfiguration,
|
||||
FunctionInvocationLayer,
|
||||
FunctionTool,
|
||||
ToolTypes,
|
||||
normalize_tools,
|
||||
)
|
||||
from .._types import (
|
||||
ChatOptions,
|
||||
@@ -271,21 +273,24 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
|
||||
# region content creation
|
||||
|
||||
def _prepare_tools_for_openai(self, tools: Sequence[Any]) -> dict[str, Any]:
|
||||
def _prepare_tools_for_openai(
|
||||
self,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Prepare tools for the OpenAI Chat Completions API.
|
||||
|
||||
Converts FunctionTool to JSON schema format. Web search tools are routed
|
||||
to web_search_options parameter. All other tools pass through unchanged.
|
||||
|
||||
Args:
|
||||
tools: Sequence of tools to prepare.
|
||||
tools: Tool(s) to prepare.
|
||||
|
||||
Returns:
|
||||
Dict containing tools and optionally web_search_options.
|
||||
"""
|
||||
chat_tools: list[Any] = []
|
||||
web_search_options: dict[str, Any] | None = None
|
||||
for tool in tools:
|
||||
for tool in normalize_tools(tools):
|
||||
if isinstance(tool, FunctionTool):
|
||||
chat_tools.append(tool.to_json_schema_spec())
|
||||
elif isinstance(tool, MutableMapping) and tool.get("type") == "web_search":
|
||||
@@ -338,15 +343,16 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
run_options.pop("tool_choice", None)
|
||||
elif tool_choice := run_options.pop("tool_choice", None):
|
||||
tool_mode = validate_tool_mode(tool_choice)
|
||||
if (mode := tool_mode.get("mode")) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"function": {"name": func_name},
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
if tool_mode is not None:
|
||||
if (mode := tool_mode.get("mode")) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"function": {"name": func_name},
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
|
||||
# response format
|
||||
if response_format := options.get("response_format"):
|
||||
|
||||
@@ -822,15 +822,16 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
|
||||
# tool_choice: convert ToolMode to appropriate format
|
||||
if tool_choice := options.get("tool_choice"):
|
||||
tool_mode = validate_tool_mode(tool_choice)
|
||||
if (mode := tool_mode.get("mode")) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"name": func_name,
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
if tool_mode is not None:
|
||||
if (mode := tool_mode.get("mode")) == "required" and (
|
||||
func_name := tool_mode.get("required_function_name")
|
||||
) is not None:
|
||||
run_options["tool_choice"] = {
|
||||
"type": "function",
|
||||
"name": func_name,
|
||||
}
|
||||
else:
|
||||
run_options["tool_choice"] = mode
|
||||
else:
|
||||
run_options.pop("parallel_tool_calls", None)
|
||||
run_options.pop("tool_choice", None)
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Orchestrations integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-orchestrations``
|
||||
|
||||
Supported classes include:
|
||||
- SequentialBuilder
|
||||
- ConcurrentBuilder
|
||||
- GroupChatBuilder
|
||||
- MagenticBuilder
|
||||
- HandoffBuilder
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_orchestrations"
|
||||
PACKAGE_NAME = "agent-framework-orchestrations"
|
||||
_IMPORTS = [
|
||||
"__version__",
|
||||
# Sequential
|
||||
"SequentialBuilder",
|
||||
# Concurrent
|
||||
|
||||
@@ -35,7 +35,6 @@ from agent_framework_orchestrations import (
|
||||
MagenticResetSignal,
|
||||
SequentialBuilder,
|
||||
StandardMagenticManager,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -73,5 +72,4 @@ __all__ = [
|
||||
"MagenticResetSignal",
|
||||
"SequentialBuilder",
|
||||
"StandardMagenticManager",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Redis integration namespace for optional Agent Framework connectors.
|
||||
|
||||
This module lazily re-exports objects from:
|
||||
- ``agent-framework-redis``
|
||||
|
||||
Supported classes:
|
||||
- RedisContextProvider
|
||||
- RedisHistoryProvider
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_redis"
|
||||
PACKAGE_NAME = "agent-framework-redis"
|
||||
_IMPORTS = ["__version__", "RedisContextProvider", "RedisHistoryProvider"]
|
||||
_IMPORTS = ["RedisContextProvider", "RedisHistoryProvider"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
from agent_framework_redis import (
|
||||
RedisContextProvider,
|
||||
RedisHistoryProvider,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"RedisContextProvider",
|
||||
"RedisHistoryProvider",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
@@ -45,13 +45,16 @@ all = [
|
||||
"agent-framework-ag-ui",
|
||||
"agent-framework-azure-ai-search",
|
||||
"agent-framework-anthropic",
|
||||
"agent-framework-claude",
|
||||
"agent-framework-azure-ai",
|
||||
"agent-framework-azurefunctions",
|
||||
"agent-framework-bedrock",
|
||||
"agent-framework-chatkit",
|
||||
"agent-framework-copilotstudio",
|
||||
"agent-framework-declarative",
|
||||
"agent-framework-devui",
|
||||
"agent-framework-durabletask",
|
||||
"agent-framework-foundry-local",
|
||||
"agent-framework-github-copilot",
|
||||
"agent-framework-lab",
|
||||
"agent-framework-mem0",
|
||||
|
||||
@@ -56,6 +56,36 @@ async def test_base_client_with_function_calling(chat_client_base: SupportsChatG
|
||||
assert response.messages[2].text == "done"
|
||||
|
||||
|
||||
async def test_base_client_with_function_calling_tools_in_kwargs(chat_client_base: SupportsChatGetResponse):
|
||||
exec_counter = 0
|
||||
|
||||
@tool(name="test_function", approval_mode="never_require")
|
||||
def ai_func(arg1: str) -> str:
|
||||
nonlocal exec_counter
|
||||
exec_counter += 1
|
||||
return f"Processed {arg1}"
|
||||
|
||||
chat_client_base.run_responses = [
|
||||
ChatResponse(
|
||||
messages=Message(
|
||||
role="assistant",
|
||||
contents=[
|
||||
Content.from_function_call(call_id="1", name="test_function", arguments='{"arg1": "value1"}')
|
||||
],
|
||||
)
|
||||
),
|
||||
ChatResponse(messages=Message(role="assistant", text="done")),
|
||||
]
|
||||
|
||||
response = await chat_client_base.get_response("hello", tools=[ai_func])
|
||||
|
||||
assert exec_counter == 1
|
||||
assert len(response.messages) == 3
|
||||
assert response.messages[1].role == "tool"
|
||||
assert response.messages[1].contents[0].type == "function_result"
|
||||
assert response.messages[1].contents[0].result == "Processed value1"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("max_iterations", [3])
|
||||
async def test_base_client_with_function_calling_resets(chat_client_base: SupportsChatGetResponse):
|
||||
exec_counter = 0
|
||||
|
||||
@@ -921,8 +921,8 @@ def test_chat_options_tool_choice_validation():
|
||||
}
|
||||
assert validate_tool_mode({"mode": "none"}) == {"mode": "none"}
|
||||
|
||||
# None should return mode==none
|
||||
assert validate_tool_mode(None) == {"mode": "none"}
|
||||
# None should remain unset
|
||||
assert validate_tool_mode(None) is None
|
||||
|
||||
with raises(ContentError):
|
||||
validate_tool_mode("invalid_mode")
|
||||
|
||||
@@ -701,6 +701,7 @@ def test_prepare_options_basic(mock_async_openai: MagicMock) -> None:
|
||||
assert run_options["model"] == "gpt-4"
|
||||
assert run_options["temperature"] == 0.7
|
||||
assert run_options["top_p"] == 0.9
|
||||
assert "tool_choice" not in run_options
|
||||
assert tool_results is None
|
||||
|
||||
|
||||
@@ -733,6 +734,52 @@ def test_prepare_options_with_tool_tool(mock_async_openai: MagicMock) -> None:
|
||||
assert run_options["tool_choice"] == "auto"
|
||||
|
||||
|
||||
def test_prepare_options_with_tools_without_tool_choice(mock_async_openai: MagicMock) -> None:
|
||||
"""Test _prepare_options keeps tool_choice unset when not provided."""
|
||||
|
||||
client = create_test_openai_assistants_client(mock_async_openai)
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def test_function(query: str) -> str:
|
||||
"""A test function."""
|
||||
return f"Result for {query}"
|
||||
|
||||
options = {
|
||||
"tools": [test_function],
|
||||
}
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
run_options, _ = client._prepare_options(messages, options) # type: ignore
|
||||
|
||||
assert "tools" in run_options
|
||||
assert "tool_choice" not in run_options
|
||||
|
||||
|
||||
def test_prepare_options_with_single_tool_tool(mock_async_openai: MagicMock) -> None:
|
||||
"""Test _prepare_options with a single FunctionTool (non-sequence)."""
|
||||
client = create_test_openai_assistants_client(mock_async_openai)
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def test_function(query: str) -> str:
|
||||
"""A test function."""
|
||||
return f"Result for {query}"
|
||||
|
||||
options = {
|
||||
"tools": test_function,
|
||||
"tool_choice": "auto",
|
||||
}
|
||||
|
||||
messages = [Message(role="user", text="Hello")]
|
||||
run_options, tool_results = client._prepare_options(messages, options) # type: ignore
|
||||
|
||||
assert "tools" in run_options
|
||||
assert len(run_options["tools"]) == 1
|
||||
assert run_options["tools"][0]["type"] == "function"
|
||||
assert "function" in run_options["tools"][0]
|
||||
assert run_options["tool_choice"] == "auto"
|
||||
assert tool_results is None
|
||||
|
||||
|
||||
def test_prepare_options_with_code_interpreter(mock_async_openai: MagicMock) -> None:
|
||||
"""Test _prepare_options with code interpreter tool."""
|
||||
client = create_test_openai_assistants_client(mock_async_openai)
|
||||
|
||||
@@ -190,6 +190,21 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
|
||||
assert result["tools"] == [dict_tool]
|
||||
|
||||
|
||||
def test_prepare_tools_with_single_function_tool(openai_unit_test_env: dict[str, str]) -> None:
|
||||
"""Test that a single FunctionTool is accepted for tool preparation."""
|
||||
client = OpenAIChatClient()
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def test_function(query: str) -> str:
|
||||
"""A test function."""
|
||||
return f"Result for {query}"
|
||||
|
||||
result = client._prepare_tools_for_openai(test_function)
|
||||
assert "tools" in result
|
||||
assert len(result["tools"]) == 1
|
||||
assert result["tools"][0]["type"] == "function"
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_story_text() -> str:
|
||||
"""Returns a story about Emily and David."""
|
||||
|
||||
@@ -7,3 +7,7 @@ pip install agent-framework-foundry-local --pre
|
||||
```
|
||||
|
||||
and see the [README](https://github.com/microsoft/agent-framework/tree/main/python/README.md) for more information.
|
||||
|
||||
## Foundry Local Sample
|
||||
|
||||
See the [Foundry Local provider sample](../../samples/02-agents/providers/foundry_local/foundry_local_agent.py) for a runnable example.
|
||||
|
||||
@@ -22,7 +22,7 @@ from agent_framework import (
|
||||
normalize_messages,
|
||||
)
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework._tools import FunctionTool
|
||||
from agent_framework._tools import FunctionTool, ToolTypes
|
||||
from agent_framework._types import AgentRunInputs, normalize_tools
|
||||
from agent_framework.exceptions import ServiceException
|
||||
from copilot import CopilotClient, CopilotSession
|
||||
@@ -151,11 +151,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
||||
description: str | None = None,
|
||||
context_providers: Sequence[BaseContextProvider] | None = None,
|
||||
middleware: Sequence[AgentMiddlewareTypes] | None = None,
|
||||
tools: FunctionTool
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None = None,
|
||||
tools: ToolTypes | Callable[..., Any] | Sequence[ToolTypes | Callable[..., Any]] | None = None,
|
||||
default_options: OptionsT | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
@@ -478,7 +474,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
||||
|
||||
def _prepare_tools(
|
||||
self,
|
||||
tools: list[FunctionTool | MutableMapping[str, Any]],
|
||||
tools: Sequence[ToolTypes | CopilotTool],
|
||||
) -> list[CopilotTool]:
|
||||
"""Convert Agent Framework tools to Copilot SDK tools.
|
||||
|
||||
@@ -491,10 +487,12 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
||||
copilot_tools: list[CopilotTool] = []
|
||||
|
||||
for tool in tools:
|
||||
if isinstance(tool, FunctionTool):
|
||||
copilot_tools.append(self._tool_to_copilot_tool(tool)) # type: ignore
|
||||
elif isinstance(tool, CopilotTool):
|
||||
if isinstance(tool, CopilotTool):
|
||||
copilot_tools.append(tool)
|
||||
elif isinstance(tool, FunctionTool):
|
||||
copilot_tools.append(self._tool_to_copilot_tool(tool)) # type: ignore
|
||||
elif isinstance(tool, MutableMapping):
|
||||
copilot_tools.append(tool) # type: ignore[arg-type]
|
||||
# Note: Other tool types (e.g., dict-based hosted tools) are skipped
|
||||
|
||||
return copilot_tools
|
||||
|
||||
@@ -12,6 +12,8 @@ Hello Agent — Simplest possible agent
|
||||
This sample creates a minimal agent using AzureOpenAIResponsesClient via an
|
||||
Azure AI Foundry project endpoint, and runs it in both non-streaming and streaming modes.
|
||||
|
||||
There are XML tags in all of the get started samples, those are used to display the same code in the docs repo.
|
||||
|
||||
Environment variables:
|
||||
AZURE_AI_PROJECT_ENDPOINT — Your Azure AI Foundry project endpoint
|
||||
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME — Model deployment name (e.g. gpt-4o)
|
||||
|
||||
@@ -12,8 +12,8 @@ pip install agent-framework --pre
|
||||
Set the required environment variables:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export OPENAI_RESPONSES_MODEL_ID="gpt-4o" # optional, defaults to gpt-4o
|
||||
export AZURE_AI_PROJECT_ENDPOINT="https://your-project-endpoint"
|
||||
export AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME="gpt-4o" # optional, defaults to gpt-4o
|
||||
```
|
||||
|
||||
## Samples
|
||||
@@ -32,3 +32,5 @@ Run any sample with:
|
||||
```bash
|
||||
python 01_hello_agent.py
|
||||
```
|
||||
|
||||
These samples use Azure Foundry models with the Responses API. To switch providers, just replace the client, see [all providers](../02-agents/providers/README.md)
|
||||
|
||||
@@ -1,41 +1,74 @@
|
||||
# Chat Client Examples
|
||||
|
||||
This folder contains simple examples demonstrating direct usage of various chat clients.
|
||||
This folder contains examples for direct chat client usage patterns.
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`azure_assistants_client.py`](azure_assistants_client.py) | Direct usage of Azure Assistants Client for basic chat interactions with Azure OpenAI assistants. |
|
||||
| [`azure_chat_client.py`](azure_chat_client.py) | Direct usage of Azure Chat Client for chat interactions with Azure OpenAI models. |
|
||||
| [`azure_responses_client.py`](azure_responses_client.py) | Direct usage of Azure Responses Client for structured response generation with Azure OpenAI models. |
|
||||
| [`built_in_chat_clients.py`](built_in_chat_clients.py) | Consolidated sample for built-in chat clients. Uses `get_client()` to create the selected client and pass it to `main()`. |
|
||||
| [`chat_response_cancellation.py`](chat_response_cancellation.py) | Demonstrates how to cancel chat responses during streaming, showing proper cancellation handling and cleanup. |
|
||||
| [`azure_ai_chat_client.py`](azure_ai_chat_client.py) | Direct usage of Azure AI Chat Client for chat interactions with Azure AI models. |
|
||||
| [`openai_assistants_client.py`](openai_assistants_client.py) | Direct usage of OpenAI Assistants Client for basic chat interactions with OpenAI assistants. |
|
||||
| [`openai_chat_client.py`](openai_chat_client.py) | Direct usage of OpenAI Chat Client for chat interactions with OpenAI models. |
|
||||
| [`openai_responses_client.py`](openai_responses_client.py) | Direct usage of OpenAI Responses Client for structured response generation with OpenAI models. |
|
||||
| [`custom_chat_client.py`](custom_chat_client.py) | Demonstrates how to create custom chat clients by extending the `BaseChatClient` class. Shows a `EchoingChatClient` implementation and how to integrate it with `Agent` using the `as_agent()` method. |
|
||||
|
||||
## Selecting a built-in client
|
||||
|
||||
`built_in_chat_clients.py` starts with:
|
||||
|
||||
```python
|
||||
asyncio.run(main("openai_chat"))
|
||||
```
|
||||
|
||||
Change the argument to pick a client:
|
||||
|
||||
- `openai_chat`
|
||||
- `openai_responses`
|
||||
- `openai_assistants`
|
||||
- `anthropic`
|
||||
- `ollama`
|
||||
- `bedrock`
|
||||
- `azure_openai_chat`
|
||||
- `azure_openai_responses`
|
||||
- `azure_openai_responses_foundry`
|
||||
- `azure_openai_assistants`
|
||||
- `azure_ai_agent`
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
uv run samples/02-agents/chat_client/built_in_chat_clients.py
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Depending on which client you're using, set the appropriate environment variables:
|
||||
Depending on the selected client, set the appropriate environment variables:
|
||||
|
||||
**For Azure clients:**
|
||||
- `AZURE_OPENAI_ENDPOINT`: Your Azure OpenAI endpoint
|
||||
- `AZURE_OPENAI_CHAT_DEPLOYMENT_NAME`: The name of your Azure OpenAI chat deployment
|
||||
- `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME`: The name of your Azure OpenAI responses deployment
|
||||
|
||||
**For Azure AI client:**
|
||||
**For Azure OpenAI Foundry responses client (`azure_openai_responses_foundry`):**
|
||||
- `AZURE_AI_PROJECT_ENDPOINT`: Your Azure AI project endpoint
|
||||
- `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of your model deployment
|
||||
- `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME`: The name of your Azure OpenAI responses deployment
|
||||
|
||||
**For Azure AI agent client (`azure_ai_agent`):**
|
||||
- `AZURE_AI_PROJECT_ENDPOINT`: Your Azure AI project endpoint
|
||||
- `AZURE_AI_MODEL_DEPLOYMENT_NAME`: The name of your model deployment (used by `azure_ai_agent`)
|
||||
|
||||
**For OpenAI clients:**
|
||||
- `OPENAI_API_KEY`: Your OpenAI API key
|
||||
- `OPENAI_CHAT_MODEL_ID`: The OpenAI model to use for chat clients (e.g., `gpt-4o`, `gpt-4o-mini`, `gpt-3.5-turbo`)
|
||||
- `OPENAI_RESPONSES_MODEL_ID`: The OpenAI model to use for responses clients (e.g., `gpt-4o`, `gpt-4o-mini`, `gpt-3.5-turbo`)
|
||||
- `OPENAI_CHAT_MODEL_ID`: The OpenAI model for `openai_chat` and `openai_assistants`
|
||||
- `OPENAI_RESPONSES_MODEL_ID`: The OpenAI model for `openai_responses`
|
||||
|
||||
**For Ollama client:**
|
||||
- `OLLAMA_HOST`: Your Ollama server URL (defaults to `http://localhost:11434` if not set)
|
||||
- `OLLAMA_MODEL_ID`: The Ollama model to use for chat (e.g., `llama3.2`, `llama2`, `codellama`)
|
||||
**For Anthropic client (`anthropic`):**
|
||||
- `ANTHROPIC_API_KEY`: Your Anthropic API key
|
||||
- `ANTHROPIC_CHAT_MODEL_ID`: The Anthropic model ID (for example, `claude-sonnet-4-5`)
|
||||
|
||||
> **Note**: For Ollama, ensure you have Ollama installed and running locally with at least one model downloaded. Visit [https://ollama.com/](https://ollama.com/) for installation instructions.
|
||||
**For Ollama client (`ollama`):**
|
||||
- `OLLAMA_HOST`: Ollama server URL (defaults to `http://localhost:11434` if unset)
|
||||
- `OLLAMA_MODEL_ID`: Ollama model name (for example, `mistral`, `qwen2.5:8b`)
|
||||
|
||||
**For Bedrock client (`bedrock`):**
|
||||
- `BEDROCK_CHAT_MODEL_ID`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
|
||||
- `BEDROCK_REGION`: AWS region (defaults to `us-east-1` if unset)
|
||||
- AWS credentials via standard environment variables (for example, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Azure AI Chat Client Direct Usage Example
|
||||
|
||||
Demonstrates direct AzureAIChatClient usage for chat interactions with Azure AI models.
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with AzureAIAgentClient(credential=AzureCliCredential()) as client:
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = False
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if str(chunk):
|
||||
print(str(chunk), end="")
|
||||
print("")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,49 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Azure Assistants Client Direct Usage Example
|
||||
|
||||
Demonstrates direct AzureAssistantsClient usage for chat interactions with Azure OpenAI assistants.
|
||||
Shows function calling capabilities and automatic assistant creation.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with AzureOpenAIAssistantsClient(credential=AzureCliCredential()) as client:
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = False
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if str(chunk):
|
||||
print(str(chunk), end="")
|
||||
print("")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,49 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Azure Chat Client Direct Usage Example
|
||||
|
||||
Demonstrates direct AzureChatClient usage for chat interactions with Azure OpenAI models.
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = False
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if str(chunk):
|
||||
print(str(chunk), end="")
|
||||
print("")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,95 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
"""
|
||||
Azure Responses Client Direct Usage Example
|
||||
|
||||
Demonstrates direct AzureResponsesClient usage for structured response generation with Azure OpenAI models.
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, "The location to get the weather for."],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time():
|
||||
"""Get the current time."""
|
||||
from datetime import datetime
|
||||
|
||||
now = datetime.now()
|
||||
return f"The current date time is {now.strftime('%Y-%m-%d - %H:%M:%S')}."
|
||||
|
||||
|
||||
class WeatherDetail(BaseModel):
|
||||
"""Structured output for weather information."""
|
||||
|
||||
location: str
|
||||
weather: str
|
||||
|
||||
|
||||
class Weather(BaseModel):
|
||||
"""Container for multiple outputs."""
|
||||
|
||||
date_time: str
|
||||
weather_details: list[WeatherDetail]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
client = AzureOpenAIResponsesClient(credential=AzureCliCredential(), api_version="preview")
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = True
|
||||
print(f"User: {message}")
|
||||
response = client.get_response(
|
||||
message,
|
||||
options={"response_format": Weather, "tools": [get_weather, get_time]},
|
||||
stream=stream,
|
||||
)
|
||||
if stream:
|
||||
response = await response.get_final_response()
|
||||
else:
|
||||
response = await response
|
||||
if result := response.value:
|
||||
print(f"Assistant: {result.model_dump_json(indent=2)}")
|
||||
else:
|
||||
print(f"Assistant: {response.text}")
|
||||
|
||||
|
||||
# Expected output (time will be different):
|
||||
"""
|
||||
User: What's the weather in Amsterdam and in Paris?
|
||||
Assistant: {
|
||||
"date_time": "2026-02-06 - 13:30:40",
|
||||
"weather_details": [
|
||||
{
|
||||
"location": "Amsterdam",
|
||||
"weather": "The weather in Amsterdam is cloudy with a high of 21°C."
|
||||
},
|
||||
{
|
||||
"location": "Paris",
|
||||
"weather": "The weather in Paris is sunny with a high of 27°C."
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,156 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from random import randint
|
||||
from typing import Annotated, Any, Literal
|
||||
|
||||
from agent_framework import SupportsChatGetResponse, tool
|
||||
from agent_framework.azure import (
|
||||
AzureAIAgentClient,
|
||||
AzureOpenAIAssistantsClient,
|
||||
)
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Built-in Chat Clients Example
|
||||
|
||||
This sample demonstrates how to run the same prompt flow against different built-in
|
||||
chat clients using a single `get_client` factory.
|
||||
|
||||
Select one of these client names:
|
||||
- openai_chat
|
||||
- openai_responses
|
||||
- openai_assistants
|
||||
- anthropic
|
||||
- ollama
|
||||
- bedrock
|
||||
- azure_openai_chat
|
||||
- azure_openai_responses
|
||||
- azure_openai_responses_foundry
|
||||
- azure_openai_assistants
|
||||
- azure_ai_agent
|
||||
"""
|
||||
|
||||
ClientName = Literal[
|
||||
"openai_chat",
|
||||
"openai_responses",
|
||||
"openai_assistants",
|
||||
"anthropic",
|
||||
"ollama",
|
||||
"bedrock",
|
||||
"azure_openai_chat",
|
||||
"azure_openai_responses",
|
||||
"azure_openai_responses_foundry",
|
||||
"azure_openai_assistants",
|
||||
"azure_ai_agent",
|
||||
]
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
def get_client(client_name: ClientName) -> SupportsChatGetResponse[Any]:
|
||||
"""Create a built-in chat client from a name."""
|
||||
from agent_framework.amazon import BedrockChatClient
|
||||
from agent_framework.anthropic import AnthropicClient
|
||||
from agent_framework.azure import (
|
||||
AzureOpenAIChatClient,
|
||||
AzureOpenAIResponsesClient,
|
||||
)
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
|
||||
# 1. Create OpenAI clients.
|
||||
if client_name == "openai_chat":
|
||||
return OpenAIChatClient()
|
||||
if client_name == "openai_responses":
|
||||
return OpenAIResponsesClient()
|
||||
if client_name == "openai_assistants":
|
||||
return OpenAIAssistantsClient()
|
||||
if client_name == "anthropic":
|
||||
return AnthropicClient()
|
||||
if client_name == "ollama":
|
||||
return OllamaChatClient()
|
||||
if client_name == "bedrock":
|
||||
return BedrockChatClient()
|
||||
|
||||
# 2. Create Azure OpenAI clients.
|
||||
if client_name == "azure_openai_chat":
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
if client_name == "azure_openai_responses":
|
||||
return AzureOpenAIResponsesClient(credential=AzureCliCredential(), api_version="preview")
|
||||
if client_name == "azure_openai_responses_foundry":
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
if client_name == "azure_openai_assistants":
|
||||
return AzureOpenAIAssistantsClient(credential=AzureCliCredential())
|
||||
|
||||
# 3. Create Azure AI client.
|
||||
if client_name == "azure_ai_agent":
|
||||
return AzureAIAgentClient(credential=AsyncAzureCliCredential())
|
||||
|
||||
raise ValueError(f"Unsupported client name: {client_name}")
|
||||
|
||||
|
||||
async def main(client_name: ClientName = "openai_chat") -> None:
|
||||
"""Run a basic prompt using a selected built-in client."""
|
||||
client = get_client(client_name)
|
||||
|
||||
# 1. Configure prompt and streaming mode.
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = os.getenv("STREAM", "false").lower() == "true"
|
||||
print(f"Client: {client_name}")
|
||||
print(f"User: {message}")
|
||||
|
||||
# 2. Run with context-managed clients.
|
||||
if isinstance(client, OpenAIAssistantsClient | AzureOpenAIAssistantsClient | AzureAIAgentClient):
|
||||
async with client:
|
||||
if stream:
|
||||
response_stream = client.get_response(message, stream=True, options={"tools": get_weather})
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in response_stream:
|
||||
if chunk.text:
|
||||
print(chunk.text, end="")
|
||||
print("")
|
||||
else:
|
||||
print(f"Assistant: {await client.get_response(message, stream=False, options={'tools': get_weather})}")
|
||||
return
|
||||
|
||||
# 3. Run with non-context-managed clients.
|
||||
if stream:
|
||||
response_stream = client.get_response(message, stream=True, options={"tools": get_weather})
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in response_stream:
|
||||
if chunk.text:
|
||||
print(chunk.text, end="")
|
||||
print("")
|
||||
else:
|
||||
print(f"Assistant: {await client.get_response(message, stream=False, options={'tools': get_weather})}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main("openai_chat"))
|
||||
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
User: What's the weather in Amsterdam and in Paris?
|
||||
Assistant: The weather in Amsterdam is sunny with a high of 25°C.
|
||||
...and in Paris it is cloudy with a high of 19°C.
|
||||
"""
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
import random
|
||||
import sys
|
||||
from collections.abc import AsyncIterable, Awaitable, Mapping, Sequence
|
||||
from typing import Any, ClassVar, Generic
|
||||
from typing import Any, ClassVar, TypeAlias, TypedDict
|
||||
|
||||
from agent_framework import (
|
||||
BaseChatClient,
|
||||
@@ -15,15 +15,9 @@ from agent_framework import (
|
||||
FunctionInvocationLayer,
|
||||
Message,
|
||||
ResponseStream,
|
||||
Role,
|
||||
)
|
||||
from agent_framework._clients import OptionsCoT
|
||||
from agent_framework.observability import ChatTelemetryLayer
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # type: ignore # pragma: no cover
|
||||
else:
|
||||
@@ -38,7 +32,18 @@ middleware, telemetry, and function invocation layers explicitly.
|
||||
"""
|
||||
|
||||
|
||||
class EchoingChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
|
||||
class EchoingChatClientOptions(TypedDict, total=False):
|
||||
"""Custom options for EchoingChatClient."""
|
||||
|
||||
uppercase: bool
|
||||
suffix: str
|
||||
stream_delay_seconds: float
|
||||
|
||||
|
||||
OptionsT: TypeAlias = EchoingChatClientOptions
|
||||
|
||||
|
||||
class EchoingChatClient(BaseChatClient[OptionsT]):
|
||||
"""A custom chat client that echoes messages back with modifications.
|
||||
|
||||
This demonstrates how to implement a custom chat client by extending BaseChatClient
|
||||
@@ -73,7 +78,7 @@ class EchoingChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
|
||||
# Echo the last user message
|
||||
last_user_message = None
|
||||
for message in reversed(messages):
|
||||
if message.role == Role.USER:
|
||||
if message.role == "user":
|
||||
last_user_message = message
|
||||
break
|
||||
|
||||
@@ -82,7 +87,13 @@ class EchoingChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
|
||||
else:
|
||||
response_text = f"{self.prefix} [No text message found]"
|
||||
|
||||
response_message = Message(role=Role.ASSISTANT, contents=[Content.from_text(response_text)])
|
||||
if options.get("uppercase"):
|
||||
response_text = response_text.upper()
|
||||
if suffix := options.get("suffix"):
|
||||
response_text = f"{response_text} {suffix}"
|
||||
stream_delay_seconds = float(options.get("stream_delay_seconds", 0.05))
|
||||
|
||||
response_message = Message(role="assistant", contents=[Content.from_text(response_text)])
|
||||
|
||||
response = ChatResponse(
|
||||
messages=[response_message],
|
||||
@@ -102,21 +113,20 @@ class EchoingChatClient(BaseChatClient[OptionsCoT], Generic[OptionsCoT]):
|
||||
for char in response_text_local:
|
||||
yield ChatResponseUpdate(
|
||||
contents=[Content.from_text(char)],
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
response_id=f"echo-stream-resp-{random.randint(1000, 9999)}",
|
||||
model_id="echo-model-v1",
|
||||
)
|
||||
await asyncio.sleep(0.05)
|
||||
await asyncio.sleep(stream_delay_seconds)
|
||||
|
||||
return ResponseStream(_stream(), finalizer=lambda updates: response)
|
||||
|
||||
|
||||
class EchoingChatClientWithLayers( # type: ignore[misc,type-var]
|
||||
ChatMiddlewareLayer[OptionsCoT],
|
||||
ChatTelemetryLayer[OptionsCoT],
|
||||
FunctionInvocationLayer[OptionsCoT],
|
||||
EchoingChatClient[OptionsCoT],
|
||||
Generic[OptionsCoT],
|
||||
class EchoingChatClientWithLayers( # type: ignore[misc]
|
||||
ChatMiddlewareLayer[OptionsT],
|
||||
ChatTelemetryLayer[OptionsT],
|
||||
FunctionInvocationLayer[OptionsT],
|
||||
EchoingChatClient,
|
||||
):
|
||||
"""Echoing chat client that explicitly composes middleware, telemetry, and function layers."""
|
||||
|
||||
@@ -134,7 +144,14 @@ async def main() -> None:
|
||||
|
||||
# Use the chat client directly
|
||||
print("Using chat client directly:")
|
||||
direct_response = await echo_client.get_response("Hello, custom chat client!")
|
||||
direct_response = await echo_client.get_response(
|
||||
"Hello, custom chat client!",
|
||||
options={
|
||||
"uppercase": True,
|
||||
"suffix": "(CUSTOM OPTIONS)",
|
||||
"stream_delay_seconds": 0.02,
|
||||
},
|
||||
)
|
||||
print(f"Direct response: {direct_response.messages[0].text}")
|
||||
|
||||
# Create an agent using the custom chat client
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
OpenAI Assistants Client Direct Usage Example
|
||||
|
||||
Demonstrates direct OpenAIAssistantsClient usage for chat interactions with OpenAI assistants.
|
||||
Shows function calling capabilities and automatic assistant creation.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with OpenAIAssistantsClient() as client:
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = False
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if str(chunk):
|
||||
print(str(chunk), end="")
|
||||
print("")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,47 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
OpenAI Chat Client Direct Usage Example
|
||||
|
||||
Demonstrates direct OpenAIChatClient usage for chat interactions with OpenAI models.
|
||||
Shows function calling capabilities with custom business logic.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = OpenAIChatClient()
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = True
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
print("Assistant: ", end="")
|
||||
async for chunk in client.get_response(message, tools=get_weather, stream=True):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="")
|
||||
print("")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather)
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,47 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
OpenAI Responses Client Direct Usage Example
|
||||
|
||||
Demonstrates direct OpenAIResponsesClient usage for structured response generation with OpenAI models.
|
||||
Shows function calling capabilities with custom business logic.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
client = OpenAIResponsesClient()
|
||||
message = "What's the weather in Amsterdam and in Paris?"
|
||||
stream = True
|
||||
print(f"User: {message}")
|
||||
print("Assistant: ", end="")
|
||||
response = client.get_response(message, stream=stream, options={"tools": get_weather})
|
||||
if stream:
|
||||
# TODO: review names of the methods, could be related to things like HTTP clients?
|
||||
response.with_transform_hook(lambda chunk: print(chunk.text, end=""))
|
||||
await response.get_final_response()
|
||||
else:
|
||||
response = await response
|
||||
print(f"Assistant: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+2
@@ -75,6 +75,7 @@ async def main() -> None:
|
||||
if knowledge_base_name:
|
||||
# Use existing Knowledge Base - simplest approach
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
api_key=search_key,
|
||||
credential=AzureCliCredential() if not search_key else None,
|
||||
@@ -91,6 +92,7 @@ async def main() -> None:
|
||||
if not azure_openai_resource_url:
|
||||
raise ValueError("AZURE_OPENAI_RESOURCE_URL required when using index_name")
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
index_name=index_name,
|
||||
api_key=search_key,
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ async def main() -> None:
|
||||
# Create Azure AI Search context provider with semantic mode (recommended, fast)
|
||||
print("Using SEMANTIC mode (hybrid search + semantic ranking, fast)\n")
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
index_name=index_name,
|
||||
api_key=search_key, # Use api_key for API key auth, or credential for managed identity
|
||||
|
||||
@@ -39,7 +39,7 @@ async def main() -> None:
|
||||
name="FriendlyAssistant",
|
||||
instructions="You are a friendly assistant.",
|
||||
tools=retrieve_company_report,
|
||||
context_providers=[Mem0ContextProvider(user_id=user_id)],
|
||||
context_providers=[Mem0ContextProvider(source_id="mem0", user_id=user_id)],
|
||||
) as agent,
|
||||
):
|
||||
# First ask the agent to retrieve a company report with no previous context.
|
||||
|
||||
@@ -10,7 +10,9 @@ from azure.identity.aio import AzureCliCredential
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def retrieve_company_report(company_code: str, detailed: bool) -> str:
|
||||
if company_code != "CNTS":
|
||||
@@ -42,7 +44,7 @@ async def main() -> None:
|
||||
name="FriendlyAssistant",
|
||||
instructions="You are a friendly assistant.",
|
||||
tools=retrieve_company_report,
|
||||
context_providers=[Mem0ContextProvider(user_id=user_id, mem0_client=local_mem0_client)],
|
||||
context_providers=[Mem0ContextProvider(source_id="mem0", user_id=user_id, mem0_client=local_mem0_client)],
|
||||
) as agent,
|
||||
):
|
||||
# First ask the agent to retrieve a company report with no previous context.
|
||||
|
||||
@@ -34,11 +34,14 @@ async def example_global_thread_scope() -> None:
|
||||
name="GlobalMemoryAssistant",
|
||||
instructions="You are an assistant that remembers user preferences across conversations.",
|
||||
tools=get_user_preferences,
|
||||
context_providers=[Mem0ContextProvider(
|
||||
user_id=user_id,
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
user_id=user_id,
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)
|
||||
],
|
||||
) as global_agent,
|
||||
):
|
||||
# Store some preferences in the global scope
|
||||
@@ -72,10 +75,13 @@ async def example_per_operation_thread_scope() -> None:
|
||||
name="ScopedMemoryAssistant",
|
||||
instructions="You are an assistant with thread-scoped memory.",
|
||||
tools=get_user_preferences,
|
||||
context_providers=[Mem0ContextProvider(
|
||||
user_id=user_id,
|
||||
scope_to_per_operation_thread_id=True, # Isolate memories per session
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
user_id=user_id,
|
||||
scope_to_per_operation_thread_id=True, # Isolate memories per session
|
||||
)
|
||||
],
|
||||
) as scoped_agent,
|
||||
):
|
||||
# Create a specific session for this scoped provider
|
||||
@@ -119,16 +125,22 @@ async def example_multiple_agents() -> None:
|
||||
AzureAIAgentClient(credential=credential).as_agent(
|
||||
name="PersonalAssistant",
|
||||
instructions="You are a personal assistant that helps with personal tasks.",
|
||||
context_providers=[Mem0ContextProvider(
|
||||
agent_id=agent_id_1,
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
agent_id=agent_id_1,
|
||||
)
|
||||
],
|
||||
) as personal_agent,
|
||||
AzureAIAgentClient(credential=credential).as_agent(
|
||||
name="WorkAssistant",
|
||||
instructions="You are a work assistant that helps with professional tasks.",
|
||||
context_providers=[Mem0ContextProvider(
|
||||
agent_id=agent_id_2,
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
agent_id=agent_id_2,
|
||||
)
|
||||
],
|
||||
) as work_agent,
|
||||
):
|
||||
# Store personal information
|
||||
|
||||
@@ -20,7 +20,8 @@ This folder contains an example demonstrating how to use the Redis context provi
|
||||
|
||||
1. A running Redis with RediSearch (Redis Stack or a managed service)
|
||||
2. Python environment with Agent Framework Redis extra installed
|
||||
3. Optional: OpenAI API key if using vector embeddings
|
||||
3. Azure AI Foundry project endpoint and Azure OpenAI Responses deployment
|
||||
4. Optional: OpenAI API key if using vector embeddings
|
||||
|
||||
### Install the package
|
||||
|
||||
@@ -50,6 +51,8 @@ See quickstart: `https://learn.microsoft.com/azure/redis/quickstart-create-manag
|
||||
|
||||
### Environment variables
|
||||
|
||||
- `AZURE_AI_PROJECT_ENDPOINT` (required): Azure AI Foundry project endpoint for `AzureOpenAIResponsesClient`
|
||||
- `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` (required): Azure OpenAI Responses deployment name
|
||||
- `OPENAI_API_KEY` (optional): Required only if you set `vectorizer_choice="openai"` to enable hybrid search.
|
||||
|
||||
### Provider configuration highlights
|
||||
@@ -70,19 +73,26 @@ The provider supports both full‑text only and hybrid vector search:
|
||||
2. Agent integration: teaches the agent a preference and verifies it is remembered across turns.
|
||||
3. Agent + tool: calls a sample tool (flight search) and then asks the agent to recall details remembered from the tool output.
|
||||
|
||||
It uses OpenAI for both chat (via `OpenAIChatClient`) and, in some steps, optional embeddings for hybrid search.
|
||||
It uses `AzureOpenAIResponsesClient` (Foundry project endpoint setup) for chat and, in some steps, optional OpenAI embeddings for hybrid search.
|
||||
|
||||
## How to run
|
||||
|
||||
1) Start Redis (see options above). For local default, ensure it's reachable at `redis://localhost:6379`.
|
||||
|
||||
2) Set your OpenAI key if using embeddings and for the chat client used in the sample:
|
||||
2) Set Azure Foundry/OpenAI responses environment variables:
|
||||
|
||||
```bash
|
||||
export AZURE_AI_PROJECT_ENDPOINT="https://<resource>.services.ai.azure.com/api/projects/<project>"
|
||||
export AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME="<deployment-name>"
|
||||
```
|
||||
|
||||
3) (Optional) Set your OpenAI key if using embeddings:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="<your key>"
|
||||
```
|
||||
|
||||
3) Run the example:
|
||||
4) Run the example:
|
||||
|
||||
```bash
|
||||
python redis_basics.py
|
||||
@@ -109,5 +119,6 @@ You should see the agent responses and, when using embeddings, context retrieved
|
||||
## Troubleshooting
|
||||
|
||||
- Ensure at least one of `application_id`, `agent_id`, `user_id`, or `thread_id` is set; the provider requires a scope.
|
||||
- Verify `AZURE_AI_PROJECT_ENDPOINT` and `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` are set for the chat client.
|
||||
- If using embeddings, verify `OPENAI_API_KEY` is set and reachable.
|
||||
- Make sure Redis exposes RediSearch (Redis Stack image or managed service with search enabled).
|
||||
|
||||
@@ -13,24 +13,25 @@ Requirements:
|
||||
|
||||
Environment Variables:
|
||||
- AZURE_REDIS_HOST: Your Azure Managed Redis host (e.g., myredis.redis.cache.windows.net)
|
||||
- OPENAI_API_KEY: Your OpenAI API key
|
||||
- OPENAI_CHAT_MODEL_ID: OpenAI model (e.g., gpt-4o-mini)
|
||||
- AZURE_AI_PROJECT_ENDPOINT: Your Azure AI Foundry project endpoint
|
||||
- AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: Azure OpenAI Responses deployment name
|
||||
- AZURE_USER_OBJECT_ID: Your Azure AD User Object ID for authentication
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisHistoryProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from azure.identity import AzureCliCredential
|
||||
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
|
||||
from redis.credentials import CredentialProvider
|
||||
|
||||
|
||||
class AzureCredentialProvider(CredentialProvider):
|
||||
"""Credential provider for Azure AD authentication with Redis Enterprise."""
|
||||
|
||||
def __init__(self, azure_credential: AzureCliCredential, user_object_id: str):
|
||||
def __init__(self, azure_credential: AsyncAzureCliCredential, user_object_id: str):
|
||||
self.azure_credential = azure_credential
|
||||
self.user_object_id = user_object_id
|
||||
|
||||
@@ -57,24 +58,26 @@ async def main() -> None:
|
||||
return
|
||||
|
||||
# Create Azure CLI credential provider (uses 'az login' credentials)
|
||||
azure_credential = AzureCliCredential()
|
||||
azure_credential = AsyncAzureCliCredential()
|
||||
credential_provider = AzureCredentialProvider(azure_credential, user_object_id)
|
||||
|
||||
session_id = "azure_test_session"
|
||||
|
||||
# Create Azure Redis history provider
|
||||
history_provider = RedisHistoryProvider(
|
||||
source_id="redis_memory",
|
||||
credential_provider=credential_provider,
|
||||
host=redis_host,
|
||||
port=10000,
|
||||
ssl=True,
|
||||
thread_id=session_id,
|
||||
key_prefix="chat_messages",
|
||||
max_messages=100,
|
||||
)
|
||||
|
||||
# Create chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agent with Azure Redis history provider
|
||||
agent = client.as_agent(
|
||||
|
||||
@@ -31,13 +31,16 @@ import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import Message, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def search_flights(origin_airport_code: str, destination_airport_code: str, detailed: bool = False) -> str:
|
||||
"""Simulated flight-search tool to demonstrate tool memory.
|
||||
@@ -88,6 +91,15 @@ def search_flights(origin_airport_code: str, destination_airport_code: str, deta
|
||||
)
|
||||
|
||||
|
||||
def create_chat_client() -> AzureOpenAIResponsesClient:
|
||||
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Walk through provider-only, agent integration, and tool-memory scenarios.
|
||||
|
||||
@@ -100,8 +112,8 @@ async def main() -> None:
|
||||
print("-" * 40)
|
||||
# Create a provider with partition scope and OpenAI embeddings
|
||||
|
||||
# Please set the OPENAI_API_KEY and OPENAI_CHAT_MODEL_ID environment variables to use the OpenAI vectorizer
|
||||
# Recommend default for OPENAI_CHAT_MODEL_ID is gpt-4o-mini
|
||||
# Please set OPENAI_API_KEY to use the OpenAI vectorizer.
|
||||
# For chat responses, also set AZURE_AI_PROJECT_ENDPOINT and AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME.
|
||||
|
||||
# We attach an embedding vectorizer so the provider can perform hybrid (text + vector)
|
||||
# retrieval. If you prefer text-only retrieval, instantiate RedisContextProvider without the
|
||||
@@ -115,6 +127,7 @@ async def main() -> None:
|
||||
# scope data for multi-tenant separation; thread_id (set later) narrows to a
|
||||
# specific conversation.
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics",
|
||||
application_id="matrix_of_kermits",
|
||||
@@ -170,6 +183,7 @@ async def main() -> None:
|
||||
)
|
||||
# Recreate a clean index so the next scenario starts fresh
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics_2",
|
||||
prefix="context_2",
|
||||
@@ -183,7 +197,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
# Create chat client for the agent
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = create_chat_client()
|
||||
# Create agent wired to the Redis context provider. The provider automatically
|
||||
# persists conversational details and surfaces relevant context on each turn.
|
||||
agent = client.as_agent(
|
||||
@@ -217,6 +231,7 @@ async def main() -> None:
|
||||
print("-" * 40)
|
||||
# Text-only provider (full-text search only). Omits vectorizer and related params.
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics_3",
|
||||
prefix="context_3",
|
||||
@@ -227,7 +242,7 @@ async def main() -> None:
|
||||
|
||||
# Create agent exposing the flight search tool. Tool outputs are captured by the
|
||||
# provider and become retrievable context for later turns.
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = create_chat_client()
|
||||
agent = client.as_agent(
|
||||
name="MemoryEnhancedAssistant",
|
||||
instructions=(
|
||||
|
||||
@@ -17,8 +17,9 @@ Run:
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
@@ -36,9 +37,8 @@ async def main() -> None:
|
||||
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
|
||||
)
|
||||
|
||||
session_id = "test_session"
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_conversation",
|
||||
prefix="redis_conversation",
|
||||
@@ -49,11 +49,14 @@ async def main() -> None:
|
||||
vector_field_name="vector",
|
||||
vector_algorithm="hnsw",
|
||||
vector_distance_metric="cosine",
|
||||
thread_id=session_id,
|
||||
)
|
||||
|
||||
# Create chat client for the agent
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
# Create agent wired to the Redis context provider. The provider automatically
|
||||
# persists conversational details and surfaces relevant context on each turn.
|
||||
agent = client.as_agent(
|
||||
|
||||
@@ -28,15 +28,24 @@ Run:
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
# Please set the OPENAI_API_KEY and OPENAI_CHAT_MODEL_ID environment variables to use the OpenAI vectorizer
|
||||
# Recommend default for OPENAI_CHAT_MODEL_ID is gpt-4o-mini
|
||||
# Please set OPENAI_API_KEY to use the OpenAI vectorizer.
|
||||
# For chat responses, also set AZURE_AI_PROJECT_ENDPOINT and AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME.
|
||||
|
||||
|
||||
def create_chat_client() -> AzureOpenAIResponsesClient:
|
||||
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
|
||||
async def example_global_thread_scope() -> None:
|
||||
@@ -44,20 +53,15 @@ async def example_global_thread_scope() -> None:
|
||||
print("1. Global Thread Scope Example:")
|
||||
print("-" * 40)
|
||||
|
||||
global_thread_id = str(uuid.uuid4())
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_global",
|
||||
application_id="threads_demo_app",
|
||||
agent_id="threads_demo_agent",
|
||||
user_id="threads_demo_user",
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)
|
||||
|
||||
@@ -97,10 +101,7 @@ async def example_per_operation_thread_scope() -> None:
|
||||
print("2. Per-Operation Thread Scope Example:")
|
||||
print("-" * 40)
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
vectorizer = OpenAITextVectorizer(
|
||||
model="text-embedding-ada-002",
|
||||
@@ -109,6 +110,7 @@ async def example_per_operation_thread_scope() -> None:
|
||||
)
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_dynamic",
|
||||
# overwrite_redis_index=True,
|
||||
@@ -165,10 +167,7 @@ async def example_multiple_agents() -> None:
|
||||
print("3. Multiple Agents with Different Thread Configurations:")
|
||||
print("-" * 40)
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
vectorizer = OpenAITextVectorizer(
|
||||
model="text-embedding-ada-002",
|
||||
@@ -177,6 +176,7 @@ async def example_multiple_agents() -> None:
|
||||
)
|
||||
|
||||
personal_provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_agents",
|
||||
application_id="threads_demo_app",
|
||||
@@ -195,6 +195,7 @@ async def example_multiple_agents() -> None:
|
||||
)
|
||||
|
||||
work_provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_agents",
|
||||
application_id="threads_demo_app",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent, AgentSession, BaseContextProvider, SessionContext, SupportsChatGetResponse
|
||||
from agent_framework.azure import AzureAIClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -15,19 +17,13 @@ class UserInfo(BaseModel):
|
||||
|
||||
|
||||
class UserInfoMemory(BaseContextProvider):
|
||||
def __init__(self, client: SupportsChatGetResponse, user_info: UserInfo | None = None, **kwargs: Any):
|
||||
def __init__(self, source_id: str = "user-info-memory", *, client: SupportsChatGetResponse, **kwargs: Any):
|
||||
"""Create the memory.
|
||||
|
||||
If you pass in kwargs, they will be attempted to be used to create a UserInfo object.
|
||||
"""
|
||||
super().__init__("user-info-memory")
|
||||
super().__init__(source_id)
|
||||
self._chat_client = client
|
||||
if user_info:
|
||||
self.user_info = user_info
|
||||
elif kwargs:
|
||||
self.user_info = UserInfo.model_validate(kwargs)
|
||||
else:
|
||||
self.user_info = UserInfo()
|
||||
|
||||
async def after_run(
|
||||
self,
|
||||
@@ -38,12 +34,15 @@ class UserInfoMemory(BaseContextProvider):
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Extract user information from messages after each agent call."""
|
||||
request_messages = context.get_messages()
|
||||
# ensure you get all the messages you want to parse from, including the input in this case.
|
||||
request_messages = context.get_messages(include_input=True, include_response=True)
|
||||
# Check if we need to extract user info from user messages
|
||||
user_messages = [msg for msg in request_messages if hasattr(msg, "role") and msg.role == "user"] # type: ignore
|
||||
|
||||
if (self.user_info.name is None or self.user_info.age is None) and user_messages:
|
||||
try:
|
||||
if (
|
||||
state[self.source_id]["user_info"].name is None or state[self.source_id]["user_info"].age is None
|
||||
) and user_messages:
|
||||
with suppress(Exception):
|
||||
# Use the chat client to extract structured information
|
||||
result = await self._chat_client.get_response(
|
||||
messages=request_messages, # type: ignore
|
||||
@@ -53,17 +52,12 @@ class UserInfoMemory(BaseContextProvider):
|
||||
)
|
||||
|
||||
# Update user info with extracted data
|
||||
try:
|
||||
with suppress(Exception):
|
||||
extracted = result.value
|
||||
if self.user_info.name is None and extracted.name:
|
||||
self.user_info.name = extracted.name
|
||||
if self.user_info.age is None and extracted.age:
|
||||
self.user_info.age = extracted.age
|
||||
except Exception:
|
||||
pass # Failed to extract, continue without updating
|
||||
|
||||
except Exception:
|
||||
pass # Failed to extract, continue without updating
|
||||
if state[self.source_id]["user_info"].name is None and extracted.name:
|
||||
state[self.source_id]["user_info"].name = extracted.name
|
||||
if state[self.source_id]["user_info"].age is None and extracted.age:
|
||||
state[self.source_id]["user_info"].age = extracted.age
|
||||
|
||||
async def before_run(
|
||||
self,
|
||||
@@ -74,55 +68,52 @@ class UserInfoMemory(BaseContextProvider):
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Provide user information context before each agent call."""
|
||||
instructions: list[str] = []
|
||||
if state.setdefault(self.source_id, None) is None:
|
||||
state[self.source_id] = {"user_info": UserInfo()}
|
||||
|
||||
if self.user_info.name is None:
|
||||
instructions.append(
|
||||
"Ask the user for their name and politely decline to answer any questions until they provide it."
|
||||
)
|
||||
else:
|
||||
instructions.append(f"The user's name is {self.user_info.name}.")
|
||||
|
||||
if self.user_info.age is None:
|
||||
instructions.append(
|
||||
"Ask the user for their age and politely decline to answer any questions until they provide it."
|
||||
)
|
||||
else:
|
||||
instructions.append(f"The user's age is {self.user_info.age}.")
|
||||
|
||||
# Add context with additional instructions
|
||||
context.extend_instructions(self.source_id, " ".join(instructions))
|
||||
|
||||
def serialize(self) -> str:
|
||||
"""Serialize the user info for session persistence."""
|
||||
return self.user_info.model_dump_json()
|
||||
context.extend_instructions(
|
||||
self.source_id,
|
||||
"Ask the user for their name and politely decline to answer any questions until they provide it."
|
||||
if state[self.source_id]["user_info"].name is None
|
||||
else f"The user's name is {state[self.source_id]['user_info'].name}.",
|
||||
)
|
||||
context.extend_instructions(
|
||||
self.source_id,
|
||||
"Ask the user for their age and politely decline to answer any questions until they provide it."
|
||||
if state[self.source_id]["user_info"].age is None
|
||||
else f"The user's age is {state[self.source_id]['user_info'].age}.",
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
async with AzureCliCredential() as credential:
|
||||
client = AzureAIClient(credential=credential)
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create the memory provider
|
||||
memory_provider = UserInfoMemory(client)
|
||||
context_name = "user-info-memory"
|
||||
|
||||
# Create the agent with memory
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a friendly assistant. Always address the user by their name.",
|
||||
context_providers=[memory_provider],
|
||||
) as agent:
|
||||
# Create a new session for the conversation
|
||||
session = agent.create_session()
|
||||
# Create the memory provider
|
||||
memory_provider = UserInfoMemory(context_name, client=client)
|
||||
|
||||
print(await agent.run("Hello, what is the square root of 9?", session=session))
|
||||
print(await agent.run("My name is Ruaidhrí", session=session))
|
||||
print(await agent.run("I am 20 years old", session=session))
|
||||
# Create the agent with memory
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a friendly assistant. Always address the user by their name.",
|
||||
context_providers=[memory_provider],
|
||||
) as agent:
|
||||
# Create a new session for the conversation
|
||||
session = agent.create_session()
|
||||
|
||||
# Access the memory component and inspect the memories
|
||||
if memory_provider:
|
||||
print()
|
||||
print(f"MEMORY - User Name: {memory_provider.user_info.name}")
|
||||
print(f"MEMORY - User Age: {memory_provider.user_info.age}")
|
||||
for msg in ["Hello, what is the square root of 9?", "My name is Ruaidhrí", "I am 20 years old"]:
|
||||
print(f"User: {msg}")
|
||||
print(f"Assistant: {await agent.run(msg, session=session)}")
|
||||
|
||||
# Access the memory component and inspect the memories
|
||||
print()
|
||||
print(f"MEMORY - User Name: {session.state[context_name]['user_info'].name}")
|
||||
print(f"MEMORY - User Age: {session.state[context_name]['user_info'].age}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Provider Samples Overview
|
||||
|
||||
This directory groups provider-specific samples for Agent Framework.
|
||||
|
||||
| Folder | What you will find |
|
||||
| --- | --- |
|
||||
| [`anthropic/`](anthropic/) | Anthropic Claude samples using both `AnthropicClient` and `ClaudeAgent`, including tools, MCP, sessions, and Foundry Anthropic integration. |
|
||||
| [`amazon/`](amazon/) | AWS Bedrock samples using `BedrockChatClient`, including tool-enabled agent usage. |
|
||||
| [`azure_ai/`](azure_ai/) | Azure AI Foundry V2 (`azure-ai-projects`) samples with `AzureAIClient`, from basic setup to advanced patterns like search, memory, A2A, MCP, and provider methods. |
|
||||
| [`azure_ai_agent/`](azure_ai_agent/) | Azure AI Foundry V1 (`azure-ai-agents`) samples with `AzureAIAgentsProvider`, including provider methods and common hosted tool integrations. |
|
||||
| [`azure_openai/`](azure_openai/) | Azure OpenAI samples for Assistants, Chat, and Responses clients, with examples for sessions, tools, MCP, file search, and code interpreter. |
|
||||
| [`copilotstudio/`](copilotstudio/) | Microsoft Copilot Studio agent samples, including required environment/app registration setup and explicit authentication patterns. |
|
||||
| [`custom/`](custom/) | Framework extensibility samples for building custom `BaseAgent` and `BaseChatClient` implementations, including layer-composition guidance. |
|
||||
| [`foundry_local/`](foundry_local/) | Foundry Local samples using `FoundryLocalClient` for local model inference with streaming, non-streaming, and tool-calling patterns. |
|
||||
| [`github_copilot/`](github_copilot/) | `GitHubCopilotAgent` samples showing basic usage, session handling, permission-scoped shell/file/url access, and MCP integration. |
|
||||
| [`ollama/`](ollama/) | Local Ollama samples using `OllamaChatClient` (recommended) plus OpenAI-compatible Ollama setup, including reasoning and multimodal examples. |
|
||||
| [`openai/`](openai/) | OpenAI provider samples for Assistants, Chat, and Responses clients, including tools, structured output, sessions, MCP, web search, and multimodal tasks. |
|
||||
|
||||
Each folder has its own README with setup requirements and file-by-file details.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Bedrock Examples
|
||||
|
||||
This folder contains examples demonstrating how to use AWS Bedrock models with the Agent Framework. The sample
|
||||
uses `BEDROCK_CHAT_MODEL_ID`, `BEDROCK_REGION`, and AWS credentials (`AWS_ACCESS_KEY_ID`,
|
||||
`AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`).
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`bedrock_chat_client.py`](bedrock_chat_client.py) | Uses `BedrockChatClient` with a simple tool-enabled `Agent` to demonstrate direct Bedrock chat integration. |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `BEDROCK_CHAT_MODEL_ID`: Bedrock model ID (for example, `anthropic.claude-3-5-sonnet-20240620-v1:0`)
|
||||
- `BEDROCK_REGION`: AWS region (defaults to `us-east-1` if unset)
|
||||
- AWS credentials via standard variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`)
|
||||
@@ -0,0 +1,61 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import Agent, tool
|
||||
from agent_framework.amazon import BedrockChatClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Bedrock Chat Client Example
|
||||
|
||||
This sample demonstrates using `BedrockChatClient` with an agent and a simple tool.
|
||||
|
||||
Environment variables used:
|
||||
- `BEDROCK_CHAT_MODEL_ID`
|
||||
- `BEDROCK_REGION` (defaults to `us-east-1` if unset)
|
||||
- AWS credentials via standard variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`,
|
||||
optional `AWS_SESSION_TOKEN`)
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
city: Annotated[str, Field(description="The city to get the weather for.")],
|
||||
) -> dict[str, str]:
|
||||
"""Return a mock forecast for the requested city."""
|
||||
normalized_city = city.strip() or "New York"
|
||||
return {"city": normalized_city, "forecast": "72F and sunny"}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run a Bedrock-backed agent with one tool call."""
|
||||
# 1. Create an agent with Bedrock chat client and one tool.
|
||||
agent = Agent(
|
||||
client=BedrockChatClient(),
|
||||
instructions="You are a concise travel assistant.",
|
||||
name="BedrockWeatherAgent",
|
||||
tool_choice="auto",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
# 2. Run a query that uses the weather tool.
|
||||
query = "Use the weather tool to check the forecast for New York."
|
||||
print(f"User: {query}")
|
||||
response = await agent.run(query)
|
||||
print(f"Assistant: {response.text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
"""
|
||||
Sample output:
|
||||
User: Use the weather tool to check the forecast for New York.
|
||||
Assistant: The forecast for New York is 72F and sunny.
|
||||
"""
|
||||
@@ -19,7 +19,7 @@ import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
|
||||
|
||||
@tool
|
||||
|
||||
@@ -19,7 +19,7 @@ servers you trust. Use permission handlers to control what actions are allowed.
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ More permissions mean more potential for unintended actions.
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Shell commands have full access to your system within the permissions of the run
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Available built-in tools:
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
|
||||
@@ -16,7 +16,7 @@ URL fetching allows the agent to access any URL accessible from your network.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework_claude import ClaudeAgent
|
||||
from agent_framework.anthropic import ClaudeAgent
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Foundry Local Examples
|
||||
|
||||
This folder contains examples demonstrating how to run local models with `FoundryLocalClient` via `agent_framework.microsoft`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install Foundry Local and required local runtime components.
|
||||
2. Install the connector package:
|
||||
|
||||
```bash
|
||||
pip install agent-framework-foundry-local --pre
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| [`foundry_local_agent.py`](foundry_local_agent.py) | Basic Foundry Local agent usage with streaming and non-streaming responses, plus function tool calling. |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `FOUNDRY_LOCAL_MODEL_ID`: Optional model alias/ID to use by default when `model_id` is not passed to `FoundryLocalClient`.
|
||||
+1
-1
@@ -7,7 +7,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from agent_framework_foundry_local import FoundryLocalClient
|
||||
from agent_framework.microsoft import FoundryLocalClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agent_framework import Agent
|
||||
+11
-9
@@ -6,7 +6,6 @@ import tempfile
|
||||
import urllib.request as urllib_request
|
||||
from pathlib import Path
|
||||
|
||||
import aiofiles # pyright: ignore[reportMissingModuleSource]
|
||||
from agent_framework import Content
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
@@ -20,8 +19,11 @@ and automated visual asset generation.
|
||||
"""
|
||||
|
||||
|
||||
async def save_image(output: Content) -> None:
|
||||
"""Save the generated image to a temporary directory."""
|
||||
def save_image(output: Content) -> None:
|
||||
"""Save the generated image to a temporary directory.
|
||||
|
||||
This sample is simplified, usually a async aware storing method would be better.
|
||||
"""
|
||||
filename = "generated_image.webp"
|
||||
file_path = Path(tempfile.gettempdir()) / filename
|
||||
|
||||
@@ -37,15 +39,15 @@ async def save_image(output: Content) -> None:
|
||||
data_bytes = None
|
||||
else:
|
||||
try:
|
||||
data_bytes = await asyncio.to_thread(lambda: urllib_request.urlopen(uri).read())
|
||||
data_bytes = urllib_request.urlopen(uri).read()
|
||||
except Exception:
|
||||
data_bytes = None
|
||||
|
||||
if data_bytes is None:
|
||||
raise RuntimeError("Image output present but could not retrieve bytes.")
|
||||
|
||||
async with aiofiles.open(file_path, "wb") as f:
|
||||
await f.write(data_bytes)
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(data_bytes)
|
||||
|
||||
print(f"Image downloaded and saved to: {file_path}")
|
||||
|
||||
@@ -76,15 +78,15 @@ async def main() -> None:
|
||||
image_saved = False
|
||||
for message in result.messages:
|
||||
for content in message.contents:
|
||||
if content.type == "image_generation_tool_result_tool_result" and content.outputs:
|
||||
if content.type == "image_generation_tool_result" and content.outputs:
|
||||
output = content.outputs
|
||||
if isinstance(output, Content) and output.uri:
|
||||
await save_image(output)
|
||||
save_image(output)
|
||||
image_saved = True
|
||||
elif isinstance(output, list):
|
||||
for out in output:
|
||||
if isinstance(out, Content) and out.uri:
|
||||
await save_image(out)
|
||||
save_image(out)
|
||||
image_saved = True
|
||||
break
|
||||
if image_saved:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterable, Sequence
|
||||
|
||||
from agent_framework import ChatResponse, ChatResponseUpdate, Content, ResponseStream, Role
|
||||
from agent_framework import ChatResponse, ChatResponseUpdate, Content, Message, ResponseStream
|
||||
|
||||
"""ResponseStream: A Deep Dive
|
||||
|
||||
@@ -256,8 +256,7 @@ async def main() -> None:
|
||||
"""Result hook that wraps the response text in quotes."""
|
||||
if response.text:
|
||||
return ChatResponse(
|
||||
messages=f'"{response.text}"',
|
||||
role=Role.ASSISTANT,
|
||||
messages=[Message(text=f'"{response.text}"', role="assistant")],
|
||||
additional_properties=response.additional_properties,
|
||||
)
|
||||
return response
|
||||
@@ -294,8 +293,7 @@ async def main() -> None:
|
||||
# In real code, this would create an AgentResponse
|
||||
text = "".join(u.text or "" for u in updates)
|
||||
return ChatResponse(
|
||||
text=f"[AGENT FINAL] {text}",
|
||||
role=Role.ASSISTANT,
|
||||
messages=[Message(text=f"[AGENT FINAL] {text}", role="assistant")],
|
||||
additional_properties={"layer": "agent"},
|
||||
)
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ which provides:
|
||||
|
||||
The sample shows usage with both OpenAI and Anthropic clients, demonstrating
|
||||
how provider-specific options work for ChatClient and Agent. But the same approach works for other providers too.
|
||||
|
||||
The following environment variables are used:
|
||||
- ANTHROPIC_API_KEY=...
|
||||
- OPENAI_API_KEY=...
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -109,14 +114,13 @@ async def demo_openai_chat_client_reasoning_models() -> None:
|
||||
print("\n=== OpenAI ChatClient with TypedDict Options ===\n")
|
||||
|
||||
# Create OpenAI client
|
||||
client = OpenAIChatClient[OpenAIReasoningChatOptions]()
|
||||
client = OpenAIChatClient[OpenAIReasoningChatOptions](model_id="o3")
|
||||
|
||||
# With specific options, you get full IDE autocomplete!
|
||||
# Try typing `client.get_response("Hello", options={` and see the suggestions
|
||||
response = await client.get_response(
|
||||
"What is 2 + 2?",
|
||||
options={
|
||||
"model_id": "o3",
|
||||
"max_tokens": 100,
|
||||
"allow_multiple_tool_calls": True,
|
||||
# OpenAI-specific options work:
|
||||
@@ -140,12 +144,11 @@ async def demo_openai_agent() -> None:
|
||||
# or on the client when constructing the client instance:
|
||||
# client = OpenAIChatClient[OpenAIReasoningChatOptions]()
|
||||
agent = Agent[OpenAIReasoningChatOptions](
|
||||
client=OpenAIChatClient(),
|
||||
client=OpenAIChatClient(model_id="o3"),
|
||||
name="weather-assistant",
|
||||
instructions="You are a helpful assistant. Answer concisely.",
|
||||
# Options can be set at construction time
|
||||
default_options={
|
||||
"model_id": "o3",
|
||||
"max_tokens": 100,
|
||||
"allow_multiple_tool_calls": True,
|
||||
# OpenAI-specific options work:
|
||||
|
||||
Generated
+11
-5
@@ -343,11 +343,14 @@ all = [
|
||||
{ name = "agent-framework-azure-ai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-azure-ai-search", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-azurefunctions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-bedrock", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-chatkit", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-claude", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-copilotstudio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-declarative", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-devui", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-durabletask", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-foundry-local", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-github-copilot", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-lab", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-mem0", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
@@ -365,11 +368,14 @@ requires-dist = [
|
||||
{ name = "agent-framework-azure-ai", marker = "extra == 'all'", editable = "packages/azure-ai" },
|
||||
{ name = "agent-framework-azure-ai-search", marker = "extra == 'all'", editable = "packages/azure-ai-search" },
|
||||
{ name = "agent-framework-azurefunctions", marker = "extra == 'all'", editable = "packages/azurefunctions" },
|
||||
{ name = "agent-framework-bedrock", marker = "extra == 'all'", editable = "packages/bedrock" },
|
||||
{ name = "agent-framework-chatkit", marker = "extra == 'all'", editable = "packages/chatkit" },
|
||||
{ name = "agent-framework-claude", marker = "extra == 'all'", editable = "packages/claude" },
|
||||
{ name = "agent-framework-copilotstudio", marker = "extra == 'all'", editable = "packages/copilotstudio" },
|
||||
{ name = "agent-framework-declarative", marker = "extra == 'all'", editable = "packages/declarative" },
|
||||
{ name = "agent-framework-devui", marker = "extra == 'all'", editable = "packages/devui" },
|
||||
{ name = "agent-framework-durabletask", marker = "extra == 'all'", editable = "packages/durabletask" },
|
||||
{ name = "agent-framework-foundry-local", marker = "extra == 'all'", editable = "packages/foundry_local" },
|
||||
{ name = "agent-framework-github-copilot", marker = "extra == 'all'", editable = "packages/github_copilot" },
|
||||
{ name = "agent-framework-lab", marker = "extra == 'all'", editable = "packages/lab" },
|
||||
{ name = "agent-framework-mem0", marker = "extra == 'all'", editable = "packages/mem0" },
|
||||
@@ -1356,7 +1362,7 @@ name = "clr-loader"
|
||||
version = "0.2.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "cffi", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" }
|
||||
wheels = [
|
||||
@@ -1835,7 +1841,7 @@ name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" },
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
@@ -4571,8 +4577,8 @@ name = "powerfx"
|
||||
version = "0.0.34"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "pythonnet", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "cffi", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" },
|
||||
{ name = "pythonnet", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/6c4bf87e0c74ca1c563921ce89ca1c5785b7576bca932f7255cdf81082a7/powerfx-0.0.34.tar.gz", hash = "sha256:956992e7afd272657ed16d80f4cad24ec95d9e4a79fb9dfa4a068a09e136af32", size = 3237555, upload-time = "2025-12-22T15:50:59.682Z" }
|
||||
wheels = [
|
||||
@@ -5221,7 +5227,7 @@ name = "pythonnet"
|
||||
version = "3.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "clr-loader", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "clr-loader", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" }
|
||||
wheels = [
|
||||
|
||||
Reference in New Issue
Block a user