Skip to content

Commit 3bd0d44

Browse files
switch function tool to always_require
1 parent de3b950 commit 3bd0d44

File tree

163 files changed

+413
-145
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

163 files changed

+413
-145
lines changed

python/packages/ag-ui/agent_framework_ag_ui_examples/agents/ui_generator_agent.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
"""Example agent demonstrating Tool-based Generative UI (Feature 5)."""
44

55
import sys
6-
from typing import Any, TypedDict
6+
from typing import TYPE_CHECKING, Any, TypedDict
77

8-
from agent_framework import ChatAgent, ChatClientProtocol, ChatOptions, FunctionTool
8+
from agent_framework import ChatAgent, ChatClientProtocol, FunctionTool
99
from agent_framework.ag_ui import AgentFrameworkAgent
1010

1111
if sys.version_info >= (3, 13):
1212
from typing import TypeVar # type: ignore # pragma: no cover
1313
else:
1414
from typing_extensions import TypeVar # type: ignore # pragma: no cover
1515

16+
if TYPE_CHECKING:
17+
from agent_framework import ChatOptions
18+
1619
# Declaration-only tools (func=None) - actual rendering happens on the client side
1720
generate_haiku = FunctionTool[Any, str](
1821
name="generate_haiku",

python/packages/core/agent_framework/_tools.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
runtime_checkable,
3434
)
3535

36-
from opentelemetry.metrics import Histogram
36+
from opentelemetry.metrics import Histogram, NoOpHistogram
3737
from pydantic import AnyUrl, BaseModel, Field, ValidationError, create_model
3838

3939
from ._logging import get_logger
@@ -68,7 +68,9 @@
6868
if sys.version_info >= (3, 13):
6969
from typing import TypeVar as TypeVarWithDefaults # type: ignore # pragma: no cover
7070
else:
71-
from typing_extensions import TypeVar as TypeVarWithDefaults # type: ignore[import] # pragma: no cover
71+
from typing_extensions import (
72+
TypeVar as TypeVarWithDefaults, # type: ignore[import] # pragma: no cover
73+
)
7274

7375
logger = get_logger()
7476

@@ -99,14 +101,6 @@
99101
ReturnT = TypeVarWithDefaults("ReturnT", default=Any)
100102

101103

102-
class _NoOpHistogram:
103-
def record(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover - trivial
104-
return None
105-
106-
107-
_NOOP_HISTOGRAM = _NoOpHistogram()
108-
109-
110104
def _parse_inputs(
111105
inputs: "Content | dict[str, Any] | str | list[Content | dict[str, Any] | str] | None",
112106
) -> list["Content"]:
@@ -549,7 +543,7 @@ def _default_histogram() -> Histogram:
549543
from .observability import OBSERVABILITY_SETTINGS # local import to avoid circulars
550544

551545
if not OBSERVABILITY_SETTINGS.ENABLED: # type: ignore[name-defined]
552-
return _NOOP_HISTOGRAM # type: ignore[return-value]
546+
return NoOpHistogram() # type: ignore[return-value]
553547
meter = get_meter()
554548
try:
555549
return meter.create_histogram(
@@ -588,7 +582,7 @@ class FunctionTool(BaseTool, Generic[ArgsT, ReturnT]):
588582
589583
590584
# Using the decorator with string annotations
591-
@tool
585+
@tool(approval_mode="never_require")
592586
def get_weather(
593587
location: Annotated[str, "The city name"],
594588
unit: Annotated[str, "Temperature unit"] = "celsius",
@@ -637,7 +631,7 @@ def __init__(
637631
name: The name of the function.
638632
description: A description of the function.
639633
approval_mode: Whether or not approval is required to run this tool.
640-
Default is that approval is not needed.
634+
Default is that approval is required.
641635
max_invocations: The maximum number of times this function can be invoked.
642636
If None, there is no limit. Should be at least 1.
643637
max_invocation_exceptions: The maximum number of exceptions allowed during invocations.
@@ -658,8 +652,8 @@ def __init__(
658652
self.func = func
659653
self._instance = None # Store the instance for bound methods
660654
self.input_model = self._resolve_input_model(input_model)
661-
self._cached_parameters: dict[str, Any] | None = None # Cache for model_json_schema()
662-
self.approval_mode = approval_mode or "never_require"
655+
self._cached_parameters: dict[str, Any] | None = None
656+
self.approval_mode = approval_mode or "always_require"
663657
if max_invocations is not None and max_invocations < 1:
664658
raise ValueError("max_invocations must be at least 1 or None.")
665659
if max_invocation_exceptions is not None and max_invocation_exceptions < 1:
@@ -965,8 +959,6 @@ def _parse_annotation(annotation: Any) -> Any:
965959
def _create_input_model_from_func(func: Callable[..., Any], name: str) -> type[BaseModel]:
966960
"""Create a Pydantic model from a function's signature."""
967961
# Unwrap FunctionTool objects to get the underlying function
968-
from agent_framework._tools import FunctionTool
969-
970962
if isinstance(func, FunctionTool):
971963
func = func.func # type: ignore[assignment]
972964

@@ -1272,7 +1264,7 @@ def tool(
12721264
description: A description of the function. If not provided, the function's
12731265
docstring will be used.
12741266
approval_mode: Whether or not approval is required to run this tool.
1275-
Default is that approval is not needed.
1267+
Default is that approval is required.
12761268
max_invocations: The maximum number of times this function can be invoked.
12771269
If None, there is no limit, should be at least 1.
12781270
max_invocation_exceptions: The maximum number of exceptions allowed during invocations.
@@ -1293,7 +1285,7 @@ def tool(
12931285
from typing import Annotated
12941286
12951287
1296-
@tool
1288+
@tool(approval_mode="never_require")
12971289
def tool_example(
12981290
arg1: Annotated[str, "The first argument"],
12991291
arg2: Annotated[int, "The second argument"],
@@ -1319,7 +1311,7 @@ def another_weather_func(location: str) -> str:
13191311
13201312
13211313
# Async functions are also supported
1322-
@tool
1314+
@tool(approval_mode="never_require")
13231315
async def async_get_weather(location: str) -> str:
13241316
'''Get weather asynchronously.'''
13251317
# Simulate async operation

python/samples/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ These samples demonstrate durable agent hosting using the Durable Task Scheduler
292292

293293
## Tools
294294

295+
Note: Many tool samples set `approval_mode="never_require"` to keep the examples concise. For production scenarios,
296+
keep `approval_mode="always_require"` unless you are confident in the tool behavior and approval flow. See
297+
`getting_started/tools/function_tool_with_approval.py` and
298+
`getting_started/tools/function_tool_with_approval_and_threads.py`, plus the workflow approval samples in
299+
`getting_started/workflows/tool-approval/`, for end-to-end approval handling.
300+
295301
| File | Description |
296302
|------|-------------|
297303
| [`getting_started/tools/function_tool_declaration_only.py`](./getting_started/tools/function_tool_declaration_only.py) | Function declarations without implementations for testing agent reasoning |

python/samples/autogen-migration/orchestrations/01_round_robin_group_chat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ async def run_agent_framework_with_cycle() -> None:
103103
WorkflowContext,
104104
WorkflowOutputEvent,
105105
executor,
106+
tool,
106107
)
107108
from agent_framework.openai import OpenAIChatClient
108109

python/samples/autogen-migration/orchestrations/03_swarm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ async def run_agent_framework() -> None:
102102
RequestInfoEvent,
103103
WorkflowRunState,
104104
WorkflowStatusEvent,
105+
tool,
105106
)
106107
from agent_framework.openai import OpenAIChatClient
107108

python/samples/autogen-migration/orchestrations/04_magentic_one.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ async def run_agent_framework() -> None:
6363
MagenticBuilder,
6464
MagenticFinalResultEvent,
6565
MagenticOrchestratorMessageEvent,
66+
tool,
6667
)
6768
from agent_framework.openai import OpenAIChatClient
6869

python/samples/autogen-migration/single_agent/02_assistant_agent_with_tool.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ async def run_agent_framework() -> None:
5151
from agent_framework.openai import OpenAIChatClient
5252

5353
# Define tool with @tool decorator (automatic schema inference)
54-
@tool
54+
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
55+
@tool(approval_mode="never_require")
5556
def get_weather(location: str) -> str:
5657
"""Get the weather for a location.
5758

python/samples/demos/chatkit-integration/app.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
# Agent Framework imports
2121
from agent_framework import AgentResponseUpdate, ChatAgent, ChatMessage, FunctionResultContent, Role
22+
from agent_framework import tool
2223
from agent_framework.azure import AzureOpenAIChatClient
2324

2425
# Agent Framework ChatKit integration
@@ -130,7 +131,8 @@ async def stream_widget(
130131

131132
yield ThreadItemDoneEvent(type="thread.item.done", item=widget_item)
132133

133-
134+
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
135+
@tool(approval_mode="never_require")
134136
def get_weather(
135137
location: Annotated[str, Field(description="The location to get the weather for.")],
136138
) -> str:
@@ -168,14 +170,14 @@ def get_weather(
168170
)
169171
return WeatherResponse(text, weather_data)
170172

171-
173+
@tool(approval_mode="never_require")
172174
def get_time() -> str:
173175
"""Get the current UTC time."""
174176
current_time = datetime.now(timezone.utc)
175177
logger.info("Getting current UTC time")
176178
return f"Current UTC time: {current_time.strftime('%Y-%m-%d %H:%M:%S')} UTC"
177179

178-
180+
@tool(approval_mode="never_require")
179181
def show_city_selector() -> str:
180182
"""Show an interactive city selector widget to the user.
181183

python/samples/demos/m365-agent/m365_agent_demo/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import Annotated
1818

1919
from agent_framework import ChatAgent
20+
from agent_framework import tool
2021
from agent_framework.openai import OpenAIChatClient
2122
from aiohttp import web
2223
from aiohttp.web_middlewares import middleware
@@ -76,7 +77,8 @@ def load_app_config() -> AppConfig:
7677
port = 3978
7778
return AppConfig(use_anonymous_mode=use_anonymous_mode, port=port, agents_sdk_config=agents_sdk_config)
7879

79-
80+
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
81+
@tool(approval_mode="never_require")
8082
def get_weather(
8183
location: Annotated[str, Field(description="The location to get the weather for.")],
8284
) -> str:

python/samples/demos/workflow_evaluation/create_workflow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
WorkflowOutputEvent,
5858
executor,
5959
handler,
60+
tool,
6061
)
6162
from agent_framework.azure import AzureAIClient
6263
from azure.ai.projects.aio import AIProjectClient

0 commit comments

Comments
 (0)