Skip to content

Support for returning response directly from tool #1189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
droidnoob opened this issue Mar 20, 2025 · 6 comments · May be fixed by #1463
Open

Support for returning response directly from tool #1189

droidnoob opened this issue Mar 20, 2025 · 6 comments · May be fixed by #1463

Comments

@droidnoob
Copy link

Description

I would like to request for responding directly from tool without going back to the model. Kind of like what what langchain has in their tool.

Currently, when an agent calls a tool, the response is typically returned to the model for further processing. Having an option in the tool to respond directly with going back to the model loop might be better in some cases.

Use Cases

  • Privacy Concerns: Some tools return responses containing PII, and I want to avoid exposing them to the model.
  • Pre-formatted Responses: The tool response may already be in the required format, I want to avoid giving it to the model to parrot it back
  • Large Responses: The tool might be giving out large responses and exhausting the token limit

I think this might be possible with using Graph and End right now (have not tried it out yet). With langgraph I did the same with to achieve the direct tool response. But having a flag in tool decorator arguments will be great and be a great dev experience.

@agent.tool_plain(direct=True) # or a pydantic type 
async def documentation_queries():
    """
    Just a simple tool that says returns a joke
    """
    joke = await JokeGenerator()
    return joke

If there is a different way to do this, please let me know.

References

https://python.langchain.com/api_reference/core/tools/langchain_core.tools.convert.tool.html

@gvsi
Copy link

gvsi commented Mar 21, 2025

Also facing the same issue. It's a waste of input and output tokens to do a final model round, and adds unnecessary latency and it could be prone to LLM errors after the model regurgitates the output.

Also saw #847 which is the same discussion.

Would love to see if folks have found a short-term workaround for this

@Strider1990
Copy link

In terms of a short term work around, I've found success by

  1. Modifying the result_tool_description for less complex use cases.
  2. Using few-shot prompting and using result_type, the few shot prompt would look something like this:
messages = [
    {
        "role": "system",
        "content": "You are an AI assistant.",
    },
    {
        "role": "user",
        "content": "I have the following data: ...",
    },
    {
        "role": "assistant",
        "tool_calls": [
            {
                "id": "call_0_generated_uuid",
                "type": "function",
                "function": {
                    "name": "final_result",
                    "arguments": '{"response":["complex_foo": "bar"]}',
                },
            }
        ],
    },
    {
        "role": "tool",
        "tool_call_id": "call_0_generated_uuid",
        "content": '["complex_foo": "bar"]',
    },
    {
        "role": "user",
        "content": 'Your actual user query.',
    },
]
  1. Using agent.result_validator as talked about in Returning pydantic result of tool as final_result #847
async def foo_result_validator(
    ctx: GraphRunContext[FooState, FooDeps],
    result: IntermediateFoo,
):
    data = # Your actual tool call here
    return data

I haven't tried, but you may be able to use iter as well, iterate through the nodes and look for the specific ToolCallPart within CallToolsNode, then search through agent._function_tools and terminate with that call.

@droidnoob
Copy link
Author

Yeah, seems like iter with termination at tool node should work. Still having an option in tool decorator would be a better developer experience.

@kjrjay
Copy link

kjrjay commented Mar 26, 2025

I also spent lot of time investigating how to achieve this.

One work around is abusing deps by storing some state value like the output of tool into it. And send only some bland message like 'executed toolx' as output of tool back to LLM, so it saves on tokens.
But it feels 'dirty' to abuse the dependency injection mechanism. Maybe a state as part of context, in addition to deps would make for a clean solution.

Another solution would be to use pydantic graph itself, and have some sort of root agent, which takes user query and returns a structured output of agent_types.
Based on agent_type, one could either call the function itself and return the output to user. Or use some part of function output to send to another agent. The graph has a state, and this way, there is full control of what to have as part of store and what to send to LLM and then use some combination of state and LLM output to send to user.

@IngLP
Copy link

IngLP commented Apr 25, 2025

+1!

@xtfocus
Copy link
Contributor

xtfocus commented Apr 28, 2025

This is so important, I would love to have something like this in pydantic-ai

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 a pull request may close this issue.

6 participants