Skip to content

Commit 662c168

Browse files
committed
fix(tools): implement correct default and description precedence
1 parent 616ec5a commit 662c168

File tree

1 file changed

+18
-19
lines changed

1 file changed

+18
-19
lines changed

src/strands/tools/decorator.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def my_tool(param1: str, param2: int = 42) -> dict:
4444
import functools
4545
import inspect
4646
import logging
47-
from copy import deepcopy
47+
from copy import copy
4848
from typing import (
4949
Annotated,
5050
Any,
@@ -65,6 +65,7 @@ def my_tool(param1: str, param2: int = 42) -> dict:
6565
import docstring_parser
6666
from pydantic import BaseModel, Field, create_model
6767
from pydantic.fields import FieldInfo
68+
from pydantic_core import PydanticUndefined
6869
from typing_extensions import override
6970

7071
from ..interrupt import InterruptException
@@ -130,34 +131,32 @@ def _extract_annotated_metadata(
130131
if get_origin(annotation) is Annotated:
131132
args = get_args(annotation)
132133
actual_type = args[0]
133-
134-
# Look through metadata for FieldInfo and string descriptions
135134
for meta in args[1:]:
136135
if isinstance(meta, FieldInfo):
137136
field_info = meta
138137
elif isinstance(meta, str):
139138
description = meta
140139

141-
# Determine Final Description
142-
# Priority: 1. Annotated string, 2. FieldInfo description, 3. Docstring, 4. Fallback
143-
final_description = description
144-
145-
if final_description is None:
146-
if field_info and field_info.description:
147-
final_description = field_info.description
148-
else:
149-
final_description = self.param_descriptions.get(param_name)
150-
151-
if final_description is None:
152-
final_description = f"Parameter {param_name}"
140+
# Final description — always a string, never None
141+
final_description = (
142+
description
143+
if description is not None
144+
else (
145+
field_info.description
146+
if field_info and field_info.description is not None
147+
else self.param_descriptions.get(param_name) or f"Parameter {param_name}"
148+
)
149+
)
153150

154-
# Create Final FieldInfo with proper default handling
151+
# Build final FieldInfo
155152
if field_info:
156-
final_field = deepcopy(field_info)
153+
final_field = copy(field_info)
157154
final_field.description = final_description
158155

159-
# Function signature default takes priority
160-
if param_default is not ...:
156+
# ONLY override default if Field has no default AND signature has one.
157+
# Pydantic uses `PydanticUndefined` to signify no default was provided,
158+
# which is distinct from an explicit default of `None`.
159+
if field_info.default is PydanticUndefined and param_default is not ...:
161160
final_field.default = param_default
162161
else:
163162
final_field = Field(default=param_default, description=final_description)

0 commit comments

Comments
 (0)