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:
co-authored by
Copilot
parent
ed113f941c
commit
aab621f5eb
@@ -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`.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
# ruff: noqa
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from random import randint
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from agent_framework.microsoft import FoundryLocalClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agent_framework import Agent
|
||||
|
||||
"""
|
||||
This sample demonstrates basic usage of the FoundryLocalClient.
|
||||
Shows both streaming and non-streaming responses with function tools.
|
||||
|
||||
Running this sample the first time will be slow, as the model needs to be
|
||||
downloaded and initialized.
|
||||
|
||||
Also, not every model supports function calling, so be sure to check the
|
||||
model capabilities in the Foundry catalog, or pick one from the list printed
|
||||
when running this sample.
|
||||
"""
|
||||
|
||||
|
||||
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."
|
||||
|
||||
|
||||
async def non_streaming_example(agent: Agent) -> None:
|
||||
"""Example of non-streaming response (get the complete result at once)."""
|
||||
print("=== Non-streaming Response Example ===")
|
||||
|
||||
query = "What's the weather like in Seattle?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
|
||||
async def streaming_example(agent: Agent) -> None:
|
||||
"""Example of streaming response (get results as they are generated)."""
|
||||
print("=== Streaming Response Example ===")
|
||||
|
||||
query = "What's the weather like in Amsterdam?"
|
||||
print(f"User: {query}")
|
||||
print("Agent: ", end="", flush=True)
|
||||
async for chunk in agent.run(query, stream=True):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
print("\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Basic Foundry Local Client Agent Example ===")
|
||||
|
||||
client = FoundryLocalClient(model_id="phi-4-mini")
|
||||
print(f"Client Model ID: {client.model_id}\n")
|
||||
print("Other available models (tool calling supported only):")
|
||||
for model in client.manager.list_catalog_models():
|
||||
if model.supports_tool_calling:
|
||||
print(
|
||||
f"- {model.alias} for {model.task} - id={model.id} - {(model.file_size_mb / 1000):.2f} GB - {model.license}"
|
||||
)
|
||||
agent = client.as_agent(
|
||||
name="LocalAgent",
|
||||
instructions="You are a helpful agent.",
|
||||
tools=get_weather,
|
||||
)
|
||||
await non_streaming_example(agent)
|
||||
await streaming_example(agent)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+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:
|
||||
|
||||
Reference in New Issue
Block a user