Skip to content

feat(tools): support @function_tool on class instance methods (fixes #94)#2879

Draft
m1lestones wants to merge 1 commit intoopenai:mainfrom
m1lestones:feat/method-tool-descriptor
Draft

feat(tools): support @function_tool on class instance methods (fixes #94)#2879
m1lestones wants to merge 1 commit intoopenai:mainfrom
m1lestones:feat/method-tool-descriptor

Conversation

@m1lestones
Copy link
Copy Markdown
Contributor

This implements the method tool support requested in #94 — the previous attempt (#2734) was closed after Codex flagged implementation issues; this PR addresses all of them.

What changed

function_schema.py

  • FuncSchema gains a skips_receiver: bool field
  • function_schema() detects an unannotated leading self or cls and strips it from the JSON schema and stored signature — the LLM never sees it, and to_call_args() never tries to populate it from model output
  • A RunContextWrapper/ToolContext immediately after self/cls is handled correctly (takes_context=True); wrong position still raises UserError

tool.py

  • FunctionTool gains a __get__ descriptor method
  • Accessing a method tool on an instance (e.g. instance.my_tool) returns a bound copy whose invoker prepends the instance as the receiver
  • Accessing via the class returns the unbound tool unchanged
  • Calling an unbound method tool returns a descriptive error string via the standard failure handler

Issues from #2734 — all fixed

Issue Fix
Context not immediately after self/cls should raise UserError Only position 1 after receiver accepted; wrong position raises UserError
to_call_args() includes self in stored signature Receiver stripped from stored sig before building FuncSchema
Signature stripping gated on takes_context instead of skips_receiver New skips_receiver flag controls receiver stripping independently
Unbound methods can't be called without receiver __get__ descriptor binds the receiver at instance access time
Stripping ALL params named self/cls Only the single leading parameter is checked
Raising UserError at decoration time No errors at decoration time; unbound call returns graceful error string

Usage

from agents import Agent, Runner, function_tool

class WeatherService:
    def __init__(self, api_key: str):
        self.api_key = api_key

    @function_tool
    def get_weather(self, city: str) -> str:
        """Get current weather for a city.

        Args:
            city: The city name.
        """
        return f"Weather in {city}: sunny"

svc = WeatherService(api_key="secret")
agent = Agent(name="Weather Bot", tools=[svc.get_weather])
result = await Runner.run(agent, "What's the weather in London?")

Test plan

  • 19 new tests in tests/test_method_tool.py covering schema stripping, descriptor binding, sync/async methods, context params, wrong-position context error, and @function_tool(...) with arguments
  • All 2702 existing tests pass
  • make format and make lint clean
  • No new typecheck errors introduced

🤖 Generated with Claude Code

…criptor protocol

Fixes openai#94. Decorating a class method with @function_tool now works correctly:

- `function_schema()` detects an unannotated leading `self` or `cls` parameter
  and sets `skips_receiver=True`, stripping the receiver from both the JSON
  schema and the stored signature so the LLM never sees it and `to_call_args()`
  never tries to populate it from model output.
- `FunctionTool` gains a `__get__` descriptor method.  Accessing a method tool
  on a class instance (e.g. `instance.my_tool`) returns a bound copy whose
  invoker prepends the instance as the first argument, so the underlying method
  receives the correct `self`.  Accessing via the class returns the unbound tool.
- A `_make_impl(receiver)` factory is stored on method tools; `__get__` calls
  it with the instance and wires the result into the copied tool's invoker.
- A `RunContextWrapper`/`ToolContext` parameter immediately after `self`/`cls`
  is handled correctly (`takes_context=True`), as is the case where context is
  in the wrong position (still raises `UserError`).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added enhancement New feature or request feature:core labels Apr 13, 2026
@seratch
Copy link
Copy Markdown
Member

seratch commented Apr 13, 2026

Thanks for working on this. I don't think we should merge this as-is. The main issue is that the implementation is not robust enough for a core tool abstraction change:

  1. Method detection is currently heuristic-based. Treating an unannotated leading self or cls as a receiver can misclassify plain functions and change existing behavior in cases that are not actually methods.
  2. The new path still does not cover all realistic method shapes. In particular, methods with an explicitly annotated receiver are not handled correctly, so the feature remains incomplete.
  3. The implementation adds a fair amount of complexity to FunctionTool and function_schema for what is mostly an ergonomics improvement. We now have descriptor behavior, receiver stripping, and a separate invocation path, which increases long-term maintenance cost and makes future changes harder to reason about.

Also, we're currently working on a different priority, so we may not merge large changes even if they look good.

@seratch seratch marked this pull request as draft April 13, 2026 06:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature:core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants