mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: restructure: Python samples into progressive 01-05 layout (#3862)
* restructure: Python samples into progressive 01-05 layout - 01-get-started/: 6 numbered steps (hello agent → hosting) - 02-agents/: all agent concept samples (tools, middleware, providers, etc.) - 03-workflows/: ALL existing workflow samples preserved as-is - 04-hosting/: azure-functions, durabletask, a2a - 05-end-to-end/: demos, evaluation, hosted agents - Old files moved to _to_delete/ for review - Added AGENTS.md with structure documentation - autogen-migration/ and semantic-kernel-migration/ preserved at root * fix: switch to AzureOpenAI Foundry, fix CI failures - Switch all 01-get-started samples to AzureOpenAIResponsesClient with Azure AI Foundry project endpoint (AZURE_AI_PROJECT_ENDPOINT + AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME + AzureCliCredential) - Add _to_delete/ and 05-end-to-end/ to pyrightconfig.samples.json excludes - Fix test paths in packages/ that referenced old getting_started/ dirs: durabletask conftest + streaming test, azurefunctions conftest, devui conftest + capture_messages + openai_sdk_integration - Fix workflow_as_agent_human_in_the_loop.py import (sibling import) - Update hosting READMEs and tool comment paths - Replace root README.md with new structure overview - Update AGENTS.md to document Azure OpenAI Foundry as default provider * cleanup: remove _to_delete folder, copy resource files to active dirs All files in _to_delete/ were either: - Exact duplicates of files in the new structure (240 files) - Same file with only comment path updates (100 files) - One import-fix diff (workflow_as_agent_human_in_the_loop.py) - One superseded minimal_sample.py Resource files (sample.pdf, countries.json, employees.pdf, weather.json) copied to 02-agents/sample_assets/ and 02-agents/resources/ since active samples reference them. * fix: address PR review comments, centralize resources, remove root duplicates - Fix type annotation in 04_memory.py (string union -> proper types) - Fix old sample paths in observability files - Fix grammar/spelling in observability samples - Move sample_assets/ and resources/ to shared/ folder - Remove 8 duplicate observability files from 02-agents root - Update resource path references in multimodal_input and provider samples * fix: update broken links from old getting_started paths to new structure - Update relative paths in READMEs: getting_started/ → 01-get-started/, 02-agents/, 03-workflows/, 04-hosting/, 05-end-to-end/ - Fix absolute GitHub URLs in package READMEs - Fix broken link in ollama package README * fix: convert absolute GitHub URLs to relative paths for link checker Absolute URLs to python/samples/ on main branch 404 until PR merges. Converted to relative paths that linkspector can verify locally. * fix: update link for handoff sample moved to orchestrations/ * fix: update chatkit-integration README path from demos/ to 05-end-to-end/ * fix: update broken links in orchestrations README to match flat directory structure
This commit is contained in:
committed by
GitHub
Unverified
parent
69dcfe31ee
commit
a2856d3b92
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
This sample demonstrates how to configure function invocation settings
|
||||
for an client and use a simple tool as a tool in an agent.
|
||||
|
||||
This behavior is the same for all chat client types.
|
||||
"""
|
||||
|
||||
|
||||
# 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_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def add(
|
||||
x: Annotated[int, "First number"],
|
||||
y: Annotated[int, "Second number"],
|
||||
) -> str:
|
||||
return f"{x} + {y} = {x + y}"
|
||||
|
||||
|
||||
async def main():
|
||||
client = OpenAIResponsesClient()
|
||||
client.function_invocation_configuration["include_detailed_errors"] = True
|
||||
client.function_invocation_configuration["max_iterations"] = 40
|
||||
print(f"Function invocation configured as: \n{client.function_invocation_configuration}")
|
||||
|
||||
agent = client.as_agent(name="ToolAgent", instructions="Use the provided tools.", tools=add)
|
||||
|
||||
print("=" * 60)
|
||||
print("Call add(239847293, 29834)")
|
||||
query = "Add 239847293 and 29834"
|
||||
response = await agent.run(query)
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
|
||||
"""
|
||||
Expected Output:
|
||||
============================================================
|
||||
Function invocation configured as:
|
||||
{
|
||||
"type": "function_invocation_configuration",
|
||||
"enabled": true,
|
||||
"max_iterations": 40,
|
||||
"max_consecutive_errors_per_request": 3,
|
||||
"terminate_on_unknown_calls": false,
|
||||
"additional_tools": [],
|
||||
"include_detailed_errors": true
|
||||
}
|
||||
============================================================
|
||||
Call add(239847293, 29834)
|
||||
Response: 239,877,127
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import FunctionTool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
Example of how to create a function that only consists of a declaration without an implementation.
|
||||
This is useful when you want the agent to use tools that are defined elsewhere or when you want
|
||||
to test the agent's ability to reason about tool usage without executing them.
|
||||
|
||||
The only difference is that you provide a FunctionTool without a function.
|
||||
If you need a input_model, you can still provide that as well.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
function_declaration = FunctionTool(
|
||||
name="get_current_time",
|
||||
description="Get the current time in ISO 8601 format.",
|
||||
)
|
||||
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="DeclarationOnlyToolAgent",
|
||||
instructions="You are a helpful agent that uses tools.",
|
||||
tools=function_declaration,
|
||||
)
|
||||
query = "What is the current time?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Result: {result.to_json(indent=2)}\n")
|
||||
|
||||
|
||||
"""
|
||||
Expected result:
|
||||
User: What is the current time?
|
||||
Result: {
|
||||
"type": "agent_response",
|
||||
"messages": [
|
||||
{
|
||||
"type": "chat_message",
|
||||
"role": {
|
||||
"type": "role",
|
||||
"value": "assistant"
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"type": "function_call",
|
||||
"call_id": "call_0flN9rfGLK8LhORy4uMDiRSC",
|
||||
"name": "get_current_time",
|
||||
"arguments": "{}",
|
||||
"fc_id": "fc_0fd5f269955c589f016904c46584348195b84a8736e61248de"
|
||||
}
|
||||
],
|
||||
"author_name": "DeclarationOnlyToolAgent",
|
||||
"additional_properties": {}
|
||||
}
|
||||
],
|
||||
"response_id": "resp_0fd5f269955c589f016904c462d5cc819599d28384ba067edc",
|
||||
"created_at": "2025-10-31T15:14:58.000000Z",
|
||||
"usage_details": {
|
||||
"type": "usage_details",
|
||||
"input_token_count": 63,
|
||||
"output_token_count": 145,
|
||||
"total_token_count": 208,
|
||||
"openai.reasoning_tokens": 128
|
||||
},
|
||||
"additional_properties": {}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
# type: ignore
|
||||
"""
|
||||
Local Tool with Dependency Injection Example
|
||||
|
||||
This example demonstrates how to create a FunctionTool using the agent framework's
|
||||
dependency injection system. Instead of providing the function at initialization time,
|
||||
the actual callable function is injected during deserialization from a dictionary definition.
|
||||
|
||||
Note:
|
||||
The serialization and deserialization feature used in this example is currently
|
||||
in active development. The API may change in future versions as we continue
|
||||
to improve and extend its functionality. Please refer to the latest documentation
|
||||
for any updates to the dependency injection patterns.
|
||||
|
||||
Usage:
|
||||
Run this script to see how a FunctionTool can be created from a dictionary
|
||||
definition with the function injected at runtime. The agent will use this tool
|
||||
to perform arithmetic operations.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import FunctionTool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
definition = {
|
||||
"type": "function_tool",
|
||||
"name": "add_numbers",
|
||||
"description": "Add two numbers together.",
|
||||
"input_model": {
|
||||
"properties": {
|
||||
"a": {"description": "The first number", "type": "integer"},
|
||||
"b": {"description": "The second number", "type": "integer"},
|
||||
},
|
||||
"required": ["a", "b"],
|
||||
"title": "func_input",
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main function demonstrating creating a tool with an injected function."""
|
||||
|
||||
def func(a, b) -> int:
|
||||
"""Add two numbers together."""
|
||||
return a + b
|
||||
|
||||
# Create the FunctionTool using dependency injection
|
||||
# The 'definition' dictionary contains the serialized tool configuration,
|
||||
# while the actual function implementation is provided via dependencies.
|
||||
#
|
||||
# Dependency structure: {"function_tool": {"name:add_numbers": {"func": func}}}
|
||||
# - "function_tool": matches the tool type identifier
|
||||
# - "name:add_numbers": instance-specific injection targeting tools with name="add_numbers"
|
||||
# - "func": the parameter name that will receive the injected function
|
||||
tool = FunctionTool.from_dict(definition, dependencies={"function_tool": {"name:add_numbers": {"func": func}}})
|
||||
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="FunctionToolAgent", instructions="You are a helpful assistant.", tools=tool
|
||||
)
|
||||
response = await agent.run("What is 5 + 3?")
|
||||
print(f"Response: {response.text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
Tool exceptions handled by returning the error for the agent to recover from.
|
||||
|
||||
Shows how a tool that throws an exception creates gracefull recovery and can keep going.
|
||||
The LLM decides whether to retry the call or to respond with something else, based on the exception.
|
||||
"""
|
||||
|
||||
|
||||
# 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_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def greet(name: Annotated[str, "Name to greet"]) -> str:
|
||||
"""Greet someone."""
|
||||
return f"Hello, {name}!"
|
||||
|
||||
|
||||
# we trick the AI into calling this function with 0 as denominator to trigger the exception
|
||||
@tool(approval_mode="never_require")
|
||||
def safe_divide(
|
||||
a: Annotated[int, "Numerator"],
|
||||
b: Annotated[int, "Denominator"],
|
||||
) -> str:
|
||||
"""Divide two numbers can be used with 0 as denominator."""
|
||||
try:
|
||||
result = a / b # Will raise ZeroDivisionError
|
||||
except ZeroDivisionError as exc:
|
||||
print(f" Tool failed: with error: {exc}")
|
||||
raise
|
||||
|
||||
return f"{a} / {b} = {result}"
|
||||
|
||||
|
||||
async def main():
|
||||
# tools = Tools()
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="ToolAgent",
|
||||
instructions="Use the provided tools.",
|
||||
tools=[greet, safe_divide],
|
||||
)
|
||||
thread = agent.get_new_thread()
|
||||
print("=" * 60)
|
||||
print("Step 1: Call divide(10, 0) - tool raises exception")
|
||||
response = await agent.run("Divide 10 by 0", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print("Step 2: Call greet('Bob') - conversation can keep going.")
|
||||
response = await agent.run("Greet Bob", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print("Replay the conversation:")
|
||||
assert thread.message_store
|
||||
assert thread.message_store.list_messages
|
||||
for idx, msg in enumerate(await thread.message_store.list_messages()):
|
||||
if msg.text:
|
||||
print(f"{idx + 1} {msg.author_name or msg.role}: {msg.text} ")
|
||||
for content in msg.contents:
|
||||
if content.type == "function_call":
|
||||
print(
|
||||
f"{idx + 1} {msg.author_name}: calling function: {content.name} with arguments: {content.arguments}"
|
||||
)
|
||||
if content.type == "function_result":
|
||||
print(f"{idx + 1} {msg.role}: {content.result if content.result else content.exception}")
|
||||
|
||||
|
||||
"""
|
||||
Expected Output:
|
||||
============================================================
|
||||
Step 1: Call divide(10, 0) - tool raises exception
|
||||
Tool failed: with error: division by zero
|
||||
Response: Division by zero is undefined in standard arithmetic, so 10 ÷ 0 has no meaning.
|
||||
|
||||
If you’re curious about limits: as x approaches 0 from the positive side, 10/x tends to +∞; from the negative side,
|
||||
10/x tends to -∞.
|
||||
|
||||
If you want a finite result, try dividing by a nonzero number, e.g., 10 ÷ 2 = 5 or 10 ÷ 0.1 = 100. Want me to compute
|
||||
something else?
|
||||
============================================================
|
||||
Step 2: Call greet('Bob') - conversation can keep going.
|
||||
Response: Hello, Bob!
|
||||
============================================================
|
||||
Replay the conversation:
|
||||
1 user: Divide 10 by 0
|
||||
2 ToolAgent: calling function: safe_divide with arguments: {"a":10,"b":0}
|
||||
3 tool: division by zero
|
||||
4 ToolAgent: Division by zero is undefined in standard arithmetic, so 10 ÷ 0 has no meaning.
|
||||
|
||||
If you’re curious about limits: as x approaches 0 from the positive side, 10/x tends to +∞; from the negative side,
|
||||
10/x tends to -∞.
|
||||
|
||||
If you want a finite result, try dividing by a nonzero number, e.g., 10 ÷ 2 = 5 or 10 ÷ 0.1 = 100. Want me to compute
|
||||
something else?
|
||||
5 user: Greet Bob
|
||||
6 ToolAgent: calling function: greet with arguments: {"name":"Bob"}
|
||||
7 tool: Hello, Bob!
|
||||
8 ToolAgent: Hello, Bob!
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,156 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from random import randrange
|
||||
from typing import TYPE_CHECKING, Annotated, Any
|
||||
|
||||
from agent_framework import Agent, AgentResponse, Message, tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agent_framework import SupportsAgentRun
|
||||
|
||||
"""
|
||||
Demonstration of a tool with approvals.
|
||||
|
||||
This sample demonstrates using AI functions with user approval workflows.
|
||||
It shows how to handle function call approvals without using threads.
|
||||
"""
|
||||
|
||||
conditions = ["sunny", "cloudy", "raining", "snowing", "clear"]
|
||||
|
||||
|
||||
# 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_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
|
||||
"""Get the current weather for a given location."""
|
||||
# Simulate weather data
|
||||
return f"The weather in {location} is {conditions[randrange(0, len(conditions))]} and {randrange(-10, 30)}°C."
|
||||
|
||||
|
||||
# Define a simple weather tool that requires approval
|
||||
@tool(approval_mode="always_require")
|
||||
def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Francisco, CA"]) -> str:
|
||||
"""Get the current weather for a given location."""
|
||||
# Simulate weather data
|
||||
return (
|
||||
f"The weather in {location} is {conditions[randrange(0, len(conditions))]} and {randrange(-10, 30)}°C, "
|
||||
"with a humidity of 88%. "
|
||||
f"Tomorrow will be {conditions[randrange(0, len(conditions))]} with a high of {randrange(-10, 30)}°C."
|
||||
)
|
||||
|
||||
|
||||
async def handle_approvals(query: str, agent: "SupportsAgentRun") -> AgentResponse:
|
||||
"""Handle function call approvals.
|
||||
|
||||
When we don't have a thread, we need to ensure we include the original query,
|
||||
the approval request, and the approval response in each iteration.
|
||||
"""
|
||||
result = await agent.run(query)
|
||||
while len(result.user_input_requests) > 0:
|
||||
# Start with the original query
|
||||
new_inputs: list[Any] = [query]
|
||||
|
||||
for user_input_needed in result.user_input_requests:
|
||||
print(
|
||||
f"\nUser Input Request for function from {agent.name}:"
|
||||
f"\n Function: {user_input_needed.function_call.name}"
|
||||
f"\n Arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
|
||||
# Add the assistant message with the approval request
|
||||
new_inputs.append(Message("assistant", [user_input_needed]))
|
||||
|
||||
# Get user approval
|
||||
user_approval = await asyncio.to_thread(input, "\nApprove function call? (y/n): ")
|
||||
|
||||
# Add the user's approval response
|
||||
new_inputs.append(
|
||||
Message("user", [user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
# Run again with all the context
|
||||
result = await agent.run(new_inputs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def handle_approvals_streaming(query: str, agent: "SupportsAgentRun") -> None:
|
||||
"""Handle function call approvals with streaming responses.
|
||||
|
||||
When we don't have a thread, we need to ensure we include the original query,
|
||||
the approval request, and the approval response in each iteration.
|
||||
"""
|
||||
current_input: str | list[Any] = query
|
||||
has_user_input_requests = True
|
||||
while has_user_input_requests:
|
||||
has_user_input_requests = False
|
||||
user_input_requests: list[Any] = []
|
||||
|
||||
# Stream the response
|
||||
async for chunk in agent.run(current_input, stream=True):
|
||||
if chunk.text:
|
||||
print(chunk.text, end="", flush=True)
|
||||
|
||||
# Collect user input requests from the stream
|
||||
if chunk.user_input_requests:
|
||||
user_input_requests.extend(chunk.user_input_requests)
|
||||
|
||||
if user_input_requests:
|
||||
has_user_input_requests = True
|
||||
# Start with the original query
|
||||
new_inputs: list[Any] = [query]
|
||||
|
||||
for user_input_needed in user_input_requests:
|
||||
print(
|
||||
f"\n\nUser Input Request for function from {agent.name}:"
|
||||
f"\n Function: {user_input_needed.function_call.name}"
|
||||
f"\n Arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
|
||||
# Add the assistant message with the approval request
|
||||
new_inputs.append(Message("assistant", [user_input_needed]))
|
||||
|
||||
# Get user approval
|
||||
user_approval = await asyncio.to_thread(input, "\nApprove function call? (y/n): ")
|
||||
|
||||
# Add the user's approval response
|
||||
new_inputs.append(
|
||||
Message("user", [user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
# Update input with all the context for next iteration
|
||||
current_input = new_inputs
|
||||
|
||||
|
||||
async def run_weather_agent_with_approval(stream: bool) -> None:
|
||||
"""Example showing AI function with approval requirement."""
|
||||
print(f"\n=== Weather Agent with Approval Required ({'Streaming' if stream else 'Non-Streaming'}) ===\n")
|
||||
|
||||
async with Agent(
|
||||
client=OpenAIResponsesClient(),
|
||||
name="WeatherAgent",
|
||||
instructions=("You are a helpful weather assistant. Use the get_weather tool to provide weather information."),
|
||||
tools=[get_weather, get_weather_detail],
|
||||
) as agent:
|
||||
query = "Can you give me an update of the weather in LA and Portland and detailed weather for Seattle?"
|
||||
print(f"User: {query}")
|
||||
|
||||
if stream:
|
||||
print(f"\n{agent.name}: ", end="", flush=True)
|
||||
await handle_approvals_streaming(query, agent)
|
||||
print()
|
||||
else:
|
||||
result = await handle_approvals(query, agent)
|
||||
print(f"\n{agent.name}: {result}\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Demonstration of a tool with approvals ===\n")
|
||||
|
||||
await run_weather_agent_with_approval(stream=False)
|
||||
await run_weather_agent_with_approval(stream=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,102 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import Agent, Message, tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
|
||||
"""
|
||||
Tool Approvals with Threads
|
||||
|
||||
This sample demonstrates using tool approvals with threads.
|
||||
With threads, you don't need to manually pass previous messages -
|
||||
the thread stores and retrieves them automatically.
|
||||
"""
|
||||
|
||||
|
||||
@tool(approval_mode="always_require")
|
||||
def add_to_calendar(
|
||||
event_name: Annotated[str, "Name of the event"], date: Annotated[str, "Date of the event"]
|
||||
) -> str:
|
||||
"""Add an event to the calendar (requires approval)."""
|
||||
print(f">>> EXECUTING: add_to_calendar(event_name='{event_name}', date='{date}')")
|
||||
return f"Added '{event_name}' to calendar on {date}"
|
||||
|
||||
|
||||
async def approval_example() -> None:
|
||||
"""Example showing approval with threads."""
|
||||
print("=== Tool Approval with Thread ===\n")
|
||||
|
||||
agent = Agent(
|
||||
client=AzureOpenAIChatClient(),
|
||||
name="CalendarAgent",
|
||||
instructions="You are a helpful calendar assistant.",
|
||||
tools=[add_to_calendar],
|
||||
)
|
||||
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
# Step 1: Agent requests to call the tool
|
||||
query = "Add a dentist appointment on March 15th"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query, thread=thread)
|
||||
|
||||
# Check for approval requests
|
||||
if result.user_input_requests:
|
||||
for request in result.user_input_requests:
|
||||
print("\nApproval needed:")
|
||||
print(f" Function: {request.function_call.name}")
|
||||
print(f" Arguments: {request.function_call.arguments}")
|
||||
|
||||
# User approves (in real app, this would be user input)
|
||||
approved = True # Change to False to see rejection
|
||||
print(f" Decision: {'Approved' if approved else 'Rejected'}")
|
||||
|
||||
# Step 2: Send approval response
|
||||
approval_response = request.to_function_approval_response(approved=approved)
|
||||
result = await agent.run(Message("user", [approval_response]), thread=thread)
|
||||
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
|
||||
async def rejection_example() -> None:
|
||||
"""Example showing rejection with threads."""
|
||||
print("=== Tool Rejection with Thread ===\n")
|
||||
|
||||
agent = Agent(
|
||||
client=AzureOpenAIChatClient(),
|
||||
name="CalendarAgent",
|
||||
instructions="You are a helpful calendar assistant.",
|
||||
tools=[add_to_calendar],
|
||||
)
|
||||
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
query = "Add a team meeting on December 20th"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query, thread=thread)
|
||||
|
||||
if result.user_input_requests:
|
||||
for request in result.user_input_requests:
|
||||
print("\nApproval needed:")
|
||||
print(f" Function: {request.function_call.name}")
|
||||
print(f" Arguments: {request.function_call.arguments}")
|
||||
|
||||
# User rejects
|
||||
print(" Decision: Rejected")
|
||||
|
||||
# Send rejection response
|
||||
rejection_response = request.to_function_approval_response(approved=False)
|
||||
result = await agent.run(Message("user", [rejection_response]), thread=thread)
|
||||
|
||||
print(f"Agent: {result}\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await approval_example()
|
||||
await rejection_example()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,81 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""
|
||||
Function Tool with Explicit Schema Example
|
||||
|
||||
This example demonstrates how to provide an explicit schema to the @tool decorator
|
||||
using the `schema` parameter, bypassing the automatic inference from the function
|
||||
signature. This is useful when you want full control over the tool's parameter
|
||||
schema that the AI model sees, or when the function signature does not accurately
|
||||
represent the desired schema.
|
||||
|
||||
Two approaches are shown:
|
||||
1. Using a Pydantic BaseModel subclass as the schema
|
||||
2. Using a raw JSON schema dictionary as the schema
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# Approach 1: Pydantic model as explicit schema
|
||||
class WeatherInput(BaseModel):
|
||||
"""Input schema for the weather tool."""
|
||||
|
||||
location: Annotated[str, Field(description="The city name to get weather for")]
|
||||
unit: Annotated[str, Field(description="Temperature unit: celsius or fahrenheit")] = "celsius"
|
||||
|
||||
|
||||
@tool(
|
||||
name="get_weather",
|
||||
description="Get the current weather for a given location.",
|
||||
schema=WeatherInput,
|
||||
approval_mode="never_require",
|
||||
)
|
||||
def get_weather(location: str, unit: str = "celsius") -> str:
|
||||
"""Get the current weather for a location."""
|
||||
return f"The weather in {location} is 22 degrees {unit}."
|
||||
|
||||
|
||||
# Approach 2: JSON schema dictionary as explicit schema
|
||||
get_current_time_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timezone": {"type": "string", "description": "The timezone to get the current time for", "default": "UTC"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@tool(
|
||||
name="get_current_time",
|
||||
description="Get the current time in a given timezone.",
|
||||
schema=get_current_time_schema,
|
||||
approval_mode="never_require",
|
||||
)
|
||||
def get_current_time(timezone: str = "UTC") -> str:
|
||||
"""Get the current time."""
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
return f"The current time in {timezone} is {datetime.now(ZoneInfo(timezone)).isoformat()}"
|
||||
|
||||
|
||||
async def main():
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="AssistantAgent",
|
||||
instructions="You are a helpful assistant. Use the available tools to answer questions.",
|
||||
tools=[get_weather, get_current_time],
|
||||
)
|
||||
|
||||
query = "What is the weather in Seattle and what time is it?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
print(f"Result: {result.text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated, Any
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
AI Function with kwargs Example
|
||||
|
||||
This example demonstrates how to inject custom keyword arguments (kwargs) into an AI function
|
||||
from the agent's run method, without exposing them to the AI model.
|
||||
|
||||
This is useful for passing runtime information like access tokens, user IDs, or
|
||||
request-specific context that the tool needs but the model shouldn't know about
|
||||
or provide.
|
||||
"""
|
||||
|
||||
|
||||
# Define the function tool with **kwargs to accept injected arguments
|
||||
# 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_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
# Extract the injected argument from kwargs
|
||||
user_id = kwargs.get("user_id", "unknown")
|
||||
|
||||
# Simulate using the user_id for logging or personalization
|
||||
print(f"Getting weather for user: {user_id}")
|
||||
|
||||
return f"The weather in {location} is cloudy with a high of 15°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="WeatherAgent",
|
||||
instructions="You are a helpful weather assistant.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
# Pass the injected argument when running the agent
|
||||
# The 'user_id' kwarg will be passed down to the tool execution via **kwargs
|
||||
response = await agent.run("What is the weather like in Amsterdam?", user_id="user_123")
|
||||
|
||||
print(f"Agent: {response.text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,188 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
Some tools are very expensive to run, so you may want to limit the number of times
|
||||
it tries to call them and fails. This sample shows a tool that can only raise exceptions a
|
||||
limited number of times.
|
||||
"""
|
||||
|
||||
|
||||
# we trick the AI into calling this function with 0 as denominator to trigger the exception
|
||||
@tool(max_invocation_exceptions=1)
|
||||
def safe_divide(
|
||||
a: Annotated[int, "Numerator"],
|
||||
b: Annotated[int, "Denominator"],
|
||||
) -> str:
|
||||
"""Divide two numbers can be used with 0 as denominator."""
|
||||
try:
|
||||
result = a / b # Will raise ZeroDivisionError
|
||||
except ZeroDivisionError as exc:
|
||||
print(f" Tool failed with error: {exc}")
|
||||
raise
|
||||
|
||||
return f"{a} / {b} = {result}"
|
||||
|
||||
|
||||
async def main():
|
||||
# tools = Tools()
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="ToolAgent",
|
||||
instructions="Use the provided tools.",
|
||||
tools=[safe_divide],
|
||||
)
|
||||
thread = agent.get_new_thread()
|
||||
print("=" * 60)
|
||||
print("Step 1: Call divide(10, 0) - tool raises exception")
|
||||
response = await agent.run("Divide 10 by 0", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print("Step 2: Call divide(100, 0) - will refuse to execute due to max_invocation_exceptions")
|
||||
response = await agent.run("Divide 100 by 0", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print(f"Number of tool calls attempted: {safe_divide.invocation_count}")
|
||||
print(f"Number of tool calls failed: {safe_divide.invocation_exception_count}")
|
||||
print("Replay the conversation:")
|
||||
assert thread.message_store
|
||||
assert thread.message_store.list_messages
|
||||
for idx, msg in enumerate(await thread.message_store.list_messages()):
|
||||
if msg.text:
|
||||
print(f"{idx + 1} {msg.author_name or msg.role}: {msg.text} ")
|
||||
for content in msg.contents:
|
||||
if content.type == "function_call":
|
||||
print(
|
||||
f"{idx + 1} {msg.author_name}: calling function: {content.name} with arguments: {content.arguments}"
|
||||
)
|
||||
if content.type == "function_result":
|
||||
print(f"{idx + 1} {msg.role}: {content.result if content.result else content.exception}")
|
||||
|
||||
|
||||
"""
|
||||
Expected Output:
|
||||
============================================================
|
||||
Step 1: Call divide(10, 0) - tool raises exception
|
||||
Tool failed with error: division by zero
|
||||
[2025-10-31 15:39:53 - /Users/edvan/Work/agent-framework/python/packages/core/agent_framework/_tools.py:718 - ERROR]
|
||||
Function failed. Error: division by zero
|
||||
Response: Division by zero is undefined in standard arithmetic. There is no finite value for 10 ÷ 0.
|
||||
|
||||
If you want alternatives:
|
||||
- A valid example: 10 ÷ 2 = 5.
|
||||
- To handle safely in code, you can check the denominator first (e.g., in Python: if b == 0:
|
||||
handle error else: compute a/b).
|
||||
- If you’re curious about limits: as x → 0+, 10/x → +∞; as x → 0−, 10/x → −∞; there is no finite limit.
|
||||
|
||||
Would you like me to show a safe division snippet in a specific language, or compute something else?
|
||||
============================================================
|
||||
Step 2: Call divide(100, 0) - will refuse to execute due to max_invocations
|
||||
[2025-10-31 15:40:09 - /Users/edvan/Work/agent-framework/python/packages/core/agent_framework/_tools.py:718 - ERROR]
|
||||
Function failed. Error: Function 'safe_divide' has reached its maximum exception limit, you tried to use this
|
||||
tool too many times and it kept failing.
|
||||
Response: Division by zero is undefined in standard arithmetic, so 100 ÷ 0 has no finite value.
|
||||
|
||||
If you’re coding and want safe handling, here are quick patterns in a few languages:
|
||||
|
||||
- Python
|
||||
def safe_divide(a, b):
|
||||
if b == 0:
|
||||
return None # or raise an exception
|
||||
return a / b
|
||||
|
||||
safe_divide(100, 0) # -> None
|
||||
|
||||
- JavaScript
|
||||
function safeDivide(a, b) {
|
||||
if (b === 0) return undefined; // or throw
|
||||
return a / b;
|
||||
}
|
||||
|
||||
safeDivide(100, 0) // -> undefined
|
||||
|
||||
- Java
|
||||
public static Double safeDivide(double a, double b) {
|
||||
if (b == 0.0) throw new ArithmeticException("Divide by zero");
|
||||
return a / b;
|
||||
}
|
||||
|
||||
safeDivide(100, 0) // -> exception
|
||||
|
||||
- C/C++
|
||||
double safeDivide(double a, double b) {
|
||||
if (b == 0.0) return std::numeric_limits<double>::infinity(); // or handle error
|
||||
return a / b;
|
||||
}
|
||||
|
||||
Note: In many languages, dividing by zero with floating-point numbers yields Infinity (or -Infinity) or NaN,
|
||||
but integer division typically raises an error.
|
||||
|
||||
Would you like a snippet in a specific language or to see a math explanation (limits) for what happens as the
|
||||
divisor approaches zero?
|
||||
============================================================
|
||||
Number of tool calls attempted: 1
|
||||
Number of tool calls failed: 1
|
||||
Replay the conversation:
|
||||
1 user: Divide 10 by 0
|
||||
2 ToolAgent: calling function: safe_divide with arguments: {"a":10,"b":0}
|
||||
3 tool: division by zero
|
||||
4 ToolAgent: Division by zero is undefined in standard arithmetic. There is no finite value for 10 ÷ 0.
|
||||
|
||||
If you want alternatives:
|
||||
- A valid example: 10 ÷ 2 = 5.
|
||||
- To handle safely in code, you can check the denominator first (e.g., in Python: if b == 0:
|
||||
handle error else: compute a/b).
|
||||
- If you’re curious about limits: as x → 0+, 10/x → +∞; as x → 0−, 10/x → −∞; there is no finite limit.
|
||||
|
||||
Would you like me to show a safe division snippet in a specific language, or compute something else?
|
||||
5 user: Divide 100 by 0
|
||||
6 ToolAgent: calling function: safe_divide with arguments: {"a":100,"b":0}
|
||||
7 tool: Function 'safe_divide' has reached its maximum exception limit, you tried to use this tool too many times
|
||||
and it kept failing.
|
||||
8 ToolAgent: Division by zero is undefined in standard arithmetic, so 100 ÷ 0 has no finite value.
|
||||
|
||||
If you’re coding and want safe handling, here are quick patterns in a few languages:
|
||||
|
||||
- Python
|
||||
def safe_divide(a, b):
|
||||
if b == 0:
|
||||
return None # or raise an exception
|
||||
return a / b
|
||||
|
||||
safe_divide(100, 0) # -> None
|
||||
|
||||
- JavaScript
|
||||
function safeDivide(a, b) {
|
||||
if (b === 0) return undefined; // or throw
|
||||
return a / b;
|
||||
}
|
||||
|
||||
safeDivide(100, 0) // -> undefined
|
||||
|
||||
- Java
|
||||
public static Double safeDivide(double a, double b) {
|
||||
if (b == 0.0) throw new ArithmeticException("Divide by zero");
|
||||
return a / b;
|
||||
}
|
||||
|
||||
safeDivide(100, 0) // -> exception
|
||||
|
||||
- C/C++
|
||||
double safeDivide(double a, double b) {
|
||||
if (b == 0.0) return std::numeric_limits<double>::infinity(); // or handle error
|
||||
return a / b;
|
||||
}
|
||||
|
||||
Note: In many languages, dividing by zero with floating-point numbers yields Infinity (or -Infinity) or NaN,
|
||||
but integer division typically raises an error.
|
||||
|
||||
Would you like a snippet in a specific language or to see a math explanation (limits) for what happens as the
|
||||
divisor approaches zero?
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
For tools you can specify if there is a maximum number of invocations allowed.
|
||||
This sample shows a tool that can only be invoked once.
|
||||
"""
|
||||
|
||||
|
||||
@tool(max_invocations=1)
|
||||
def unicorn_function(times: Annotated[int, "The number of unicorns to return."]) -> str:
|
||||
"""This function returns precious unicorns!"""
|
||||
return f"{'🦄' * times}✨"
|
||||
|
||||
|
||||
async def main():
|
||||
# tools = Tools()
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="ToolAgent",
|
||||
instructions="Use the provided tools.",
|
||||
tools=[unicorn_function],
|
||||
)
|
||||
thread = agent.get_new_thread()
|
||||
print("=" * 60)
|
||||
print("Step 1: Call unicorn_function")
|
||||
response = await agent.run("Call 5 unicorns!", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print("Step 2: Call unicorn_function again - will refuse to execute due to max_invocations")
|
||||
response = await agent.run("Call 10 unicorns and use the function to do it.", thread=thread)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print(f"Number of tool calls attempted: {unicorn_function.invocation_count}")
|
||||
print(f"Number of tool calls failed: {unicorn_function.invocation_exception_count}")
|
||||
print("Replay the conversation:")
|
||||
assert thread.message_store
|
||||
assert thread.message_store.list_messages
|
||||
for idx, msg in enumerate(await thread.message_store.list_messages()):
|
||||
if msg.text:
|
||||
print(f"{idx + 1} {msg.author_name or msg.role}: {msg.text} ")
|
||||
for content in msg.contents:
|
||||
if content.type == "function_call":
|
||||
print(
|
||||
f"{idx + 1} {msg.author_name}: calling function: {content.name} with arguments: {content.arguments}"
|
||||
)
|
||||
if content.type == "function_result":
|
||||
print(f"{idx + 1} {msg.role}: {content.result if content.result else content.exception}")
|
||||
|
||||
|
||||
"""
|
||||
Expected Output:
|
||||
============================================================
|
||||
Step 1: Call unicorn_function
|
||||
Response: Five unicorns summoned: 🦄🦄🦄🦄🦄✨
|
||||
============================================================
|
||||
Step 2: Call unicorn_function again - will refuse to execute due to max_invocations
|
||||
[2025-10-31 15:54:40 - /Users/edvan/Work/agent-framework/python/packages/core/agent_framework/_tools.py:718 - ERROR]
|
||||
Function failed. Error: Function 'unicorn_function' has reached its maximum invocation limit,
|
||||
you can no longer use this tool.
|
||||
Response: The unicorn function has reached its maximum invocation limit. I can’t call it again right now.
|
||||
|
||||
Here are 10 unicorns manually: 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄
|
||||
|
||||
Would you like me to try again later, or generate something else?
|
||||
============================================================
|
||||
Number of tool calls attempted: 1
|
||||
Number of tool calls failed: 0
|
||||
Replay the conversation:
|
||||
1 user: Call 5 unicorns!
|
||||
2 ToolAgent: calling function: unicorn_function with arguments: {"times":5}
|
||||
3 tool: 🦄🦄🦄🦄🦄✨
|
||||
4 ToolAgent: Five unicorns summoned: 🦄🦄🦄🦄🦄✨
|
||||
5 user: Call 10 unicorns and use the function to do it.
|
||||
6 ToolAgent: calling function: unicorn_function with arguments: {"times":10}
|
||||
7 tool: Function 'unicorn_function' has reached its maximum invocation limit, you can no longer use this tool.
|
||||
8 ToolAgent: The unicorn function has reached its maximum invocation limit. I can’t call it again right now.
|
||||
|
||||
Here are 10 unicorns manually: 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄 🦄
|
||||
|
||||
Would you like me to try again later, or generate something else?
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated, Any
|
||||
|
||||
from agent_framework import AgentThread, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
AI Function with Thread Injection Example
|
||||
|
||||
This example demonstrates the behavior when passing 'thread' to agent.run()
|
||||
and accessing that thread in AI function.
|
||||
"""
|
||||
|
||||
|
||||
# Define the function tool with **kwargs
|
||||
# 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_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
async def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
# Get thread object from kwargs
|
||||
thread = kwargs.get("thread")
|
||||
if thread and isinstance(thread, AgentThread):
|
||||
if thread.message_store:
|
||||
messages = await thread.message_store.list_messages()
|
||||
print(f"Thread contains {len(messages)} messages.")
|
||||
elif thread.service_thread_id:
|
||||
print(f"Thread ID: {thread.service_thread_id}.")
|
||||
|
||||
return f"The weather in {location} is cloudy."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
agent = OpenAIChatClient().as_agent(
|
||||
name="WeatherAgent", instructions="You are a helpful weather assistant.", tools=[get_weather]
|
||||
)
|
||||
|
||||
# Create a thread
|
||||
thread = agent.get_new_thread()
|
||||
|
||||
# Run the agent with the thread
|
||||
print(f"Agent: {await agent.run('What is the weather in London?', thread=thread)}")
|
||||
print(f"Agent: {await agent.run('What is the weather in Amsterdam?', thread=thread)}")
|
||||
print(f"Agent: {await agent.run('What cities did I ask about?', thread=thread)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,100 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
"""
|
||||
This sample demonstrates using tool within a class,
|
||||
showing how to manage state within the class that affects tool behavior.
|
||||
|
||||
And how to use tool-decorated methods as tools in an agent in order to adjust the behavior of a tool.
|
||||
"""
|
||||
|
||||
|
||||
class MyFunctionClass:
|
||||
def __init__(self, safe: bool = False) -> None:
|
||||
"""Simple class with two tools: divide and add.
|
||||
|
||||
The safe parameter controls whether divide raises on division by zero or returns `infinity` for divide by zero.
|
||||
"""
|
||||
self.safe = safe
|
||||
|
||||
def divide(
|
||||
self,
|
||||
a: Annotated[int, "Numerator"],
|
||||
b: Annotated[int, "Denominator"],
|
||||
) -> str:
|
||||
"""Divide two numbers, safe to use also with 0 as denominator."""
|
||||
result = "∞" if b == 0 and self.safe else a / b
|
||||
return f"{a} / {b} = {result}"
|
||||
|
||||
def add(
|
||||
self,
|
||||
x: Annotated[int, "First number"],
|
||||
y: Annotated[int, "Second number"],
|
||||
) -> str:
|
||||
return f"{x} + {y} = {x + y}"
|
||||
|
||||
|
||||
async def main():
|
||||
# Creating my function class with safe division enabled
|
||||
tools = MyFunctionClass(safe=True)
|
||||
# Applying the tool decorator to one of the methods of the class
|
||||
add_function = tool(description="Add two numbers.")(tools.add)
|
||||
|
||||
agent = OpenAIResponsesClient().as_agent(
|
||||
name="ToolAgent",
|
||||
instructions="Use the provided tools.",
|
||||
)
|
||||
print("=" * 60)
|
||||
print("Step 1: Call divide(10, 0) - tool returns infinity")
|
||||
query = "Divide 10 by 0"
|
||||
response = await agent.run(
|
||||
query,
|
||||
tools=[add_function, tools.divide],
|
||||
)
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
print("Step 2: Call set safe to False and call again")
|
||||
# Disabling safe mode to allow exceptions
|
||||
tools.safe = False
|
||||
response = await agent.run(query, tools=[add_function, tools.divide])
|
||||
print(f"Response: {response.text}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
"""
|
||||
Expected Output:
|
||||
============================================================
|
||||
Step 1: Call divide(10, 0) - tool returns infinity
|
||||
Response: Division by zero is undefined in standard arithmetic. There is no real number that equals 10 divided by 0.
|
||||
|
||||
- If you look at limits: as x → 0+ (denominator approaches 0 from the positive side), 10/x → +∞; as x → 0−, 10/x → −∞.
|
||||
- Some calculators may display "infinity" or give an error, but that's not a real number.
|
||||
|
||||
If you want a numeric surrogate, you can use a small nonzero denominator, e.g., 10/0.001 = 10000. Would you like to
|
||||
see more on limits or handle it with a tiny epsilon?
|
||||
============================================================
|
||||
Step 2: Call set safe to False and call again
|
||||
[2025-10-31 16:17:44 - /Users/edvan/Work/agent-framework/python/packages/core/agent_framework/_tools.py:718 - ERROR]
|
||||
Function failed. Error: division by zero
|
||||
Response: Division by zero is undefined in standard arithmetic. There is no number y such that 0 × y = 10.
|
||||
|
||||
If you’re looking at limits:
|
||||
- as x → 0+, 10/x → +∞
|
||||
- as x → 0−, 10/x → −∞
|
||||
So the limit does not exist.
|
||||
|
||||
In programming, dividing by zero usually raises an error or results in special values (e.g., NaN or ∞) depending
|
||||
on the language.
|
||||
|
||||
If you want, tell me what you’d like to do instead (e.g., compute 10 divided by 2, or handle division by zero safely
|
||||
in code), and I can help with examples.
|
||||
============================================================
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user