Skip to content

Conversation

TomeHirata
Copy link
Collaborator

@TomeHirata TomeHirata commented Sep 18, 2025

This PR adds a helper function to ToolCall so that users can run tool calls with less boilerplate.

import dspy

class ToolSignature(dspy.Signature):
    """Signature for manual tool handling."""
    question: str = dspy.InputField()
    tools: list[dspy.Tool] = dspy.InputField()
    outputs: dspy.ToolCalls = dspy.OutputField()

def weather(city: str) -> str:
    """Get weather information for a city."""
    return f"The weather in {city} is sunny"

def calculator(expression: str) -> str:
    """Evaluate a mathematical expression."""
    try:
        result = eval(expression)  # Note: Use safely in production
        return f"The result is {result}"
    except:
        return "Invalid expression"

# Create predictor
predictor = dspy.Predict(ToolSignature)

# Make a prediction
response = predictor(
    question="What's the weather in New York?",
    tools=[dspy.Tool(weather), dspy.Tool(calculator)]
)


# Before
tools = {
    "weather": dspy.Tool(weather),
    "calculator": dspy.Tool(calculator)
}
for call in response.outputs.tool_calls:
    # Execute the tool call
    result = tools[call.name](**call.args)

# After
for call in response.outputs.tool_calls:
    result = call.execute()

@TomeHirata TomeHirata requested a review from Copilot September 18, 2025 07:27
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a convenient execute method to ToolCall objects that allows direct execution of tool calls without requiring manual function lookup and parameter passing boilerplate.

  • Adds execute method to ToolCall class for direct tool execution
  • Supports multiple function lookup modes including automatic discovery from caller's scope
  • Provides comprehensive test coverage for the new execution functionality

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
dspy/adapters/types/tool.py Implements the execute method with flexible function lookup
tests/adapters/test_tool.py Adds comprehensive test cases for the new execute functionality
docs/docs/learn/programming/tools.md Updates documentation to demonstrate the new execute method usage

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

print(f"Result: {result}")
# Execute individual tool calls with different options:

# Option 1: Pass tools as a dict (most explicit)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like Option 3! Option 1 and 2 will be much less popular IMO though. My understanding is they handle the cases:

  1. users forget to include the tool in runtime, in which case weather will be None as well.
  2. Users want to search functions in a tailored namespace, which is cool but I have never seen any use cases.

Can we remove the functions arg from execute?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option 3 cannot cover some usecases where the tool name is different from the local variable name too. I like 3 too and this is the main intention, but can we keep the function arg for flexibility? Since it's optional, it doesn't increase friction and I can change the order in this tutorial and explain option 3 first. what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the late reply.

I am curious about "where the tool name is different from the local variable name", why is this a valid case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this totally possible? In this case LM recognizes this tool as "weather" while it's registered as "my_tool" in globals.

my_tool = dspy.Tool(name="weather", func: lambda city: f"the weather in {city} is rainy")

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is possible, but doesn't seem to be a valid use case IMO. When users want to set name="weather", I don't see why they want to change the tool instance name, and pass it in functions later on.

Since this is not breaking and doesn't add too much complexity, I don't want to block on this, and will let you make the call

Copy link
Collaborator

@chenmoneygithub chenmoneygithub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few nonblocking comments

print(f"Result: {result}")
# Execute individual tool calls with different options:

# Option 1: Pass tools as a dict (most explicit)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is possible, but doesn't seem to be a valid use case IMO. When users want to set name="weather", I don't see why they want to change the tool instance name, and pass it in functions later on.

Since this is not breaking and doesn't add too much complexity, I don't want to block on this, and will let you make the call


# Test locals take precedence over globals
try:
globals()["local_add"] = lambda a, b: a + b + 1000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we define it as a function before def main()?

Copy link
Collaborator Author

@TomeHirata TomeHirata Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's still registered in locals in that case since globals are module-level variables.

@TomeHirata TomeHirata merged commit 228cf12 into stanfordnlp:main Sep 30, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants