豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/agents/function_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ def function_schema(
takes_context = False
filtered_params = []

# Track whether the first real (non-self/cls) parameter has been processed for context check
self_or_cls_skipped = False

if params:
first_name, first_param = params[0]
# Prefer the evaluated type hint if available
Expand All @@ -297,15 +300,23 @@ def function_schema(
takes_context = True # Mark that the function takes context
else:
filtered_params.append((first_name, first_param))
elif first_name in ("self", "cls"):
self_or_cls_skipped = True # Skip bound method receiver parameter
Comment thread
seratch marked this conversation as resolved.
Outdated
else:
filtered_params.append((first_name, first_param))

# For parameters other than the first, raise error if any use RunContextWrapper or ToolContext.
# For parameters other than the first, raise error if any use RunContextWrapper or ToolContext
# (unless self/cls was skipped, in which case the second param is the effective first param).
for name, param in params[1:]:
ann = type_hints.get(name, param.annotation)
if ann != inspect._empty:
origin = get_origin(ann) or ann
if origin is RunContextWrapper or origin is ToolContext:
if self_or_cls_skipped and not takes_context:
# self/cls was the first param, so this is the effective first param
takes_context = True
self_or_cls_skipped = False
continue
Comment thread
seratch marked this conversation as resolved.
Outdated
raise UserError(
f"RunContextWrapper/ToolContext param found at non-first position in function"
f" {func.__name__}"
Expand Down
58 changes: 58 additions & 0 deletions tests/test_function_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,3 +885,61 @@ def func_with_annotated_multiple_field_constraints(

with pytest.raises(ValidationError): # zero factor
fs.params_pydantic_model(**{"score": 50, "factor": 0.0})


def test_method_self_param_skipped():
"""Test that self parameter is skipped for class methods."""

class MyTools:
def greet(self, name: str) -> str:
return f"Hello, {name}"

obj = MyTools()
fs = function_schema(obj.greet, use_docstring_info=False)
props = fs.params_json_schema.get("properties", {})
assert "self" not in props
assert "name" in props
assert fs.params_json_schema.get("required") == ["name"]


def test_classmethod_cls_param_skipped():
"""Test that cls parameter is skipped for classmethods passed as unbound."""

# Simulate a function whose first param is named cls with no annotation
code = compile("def greet(cls, name: str) -> str: ...", "<test>", "exec")
ns: dict[str, Any] = {}
exec(code, ns) # noqa: S102
fn = ns["greet"]
fn.__annotations__ = {"name": str, "return": str}

fs = function_schema(fn, use_docstring_info=False)
props = fs.params_json_schema.get("properties", {})
assert "cls" not in props
assert "name" in props


def test_method_self_with_context_second_param():
"""Test that self is skipped and RunContextWrapper as second param is recognized."""

class MyTools:
def greet(self, ctx: RunContextWrapper[None], name: str) -> str:
return f"Hello, {name}"

obj = MyTools()
fs = function_schema(obj.greet, use_docstring_info=False)
props = fs.params_json_schema.get("properties", {})
assert "self" not in props
assert "ctx" not in props
assert "name" in props
assert fs.takes_context is True


def test_regular_unannotated_first_param_still_included():
"""Test that a regular unannotated first param (not self/cls) is still included."""

def process(data, flag: bool = False) -> str:
return str(data)

fs = function_schema(process, use_docstring_info=False)
props = fs.params_json_schema.get("properties", {})
assert "data" in props