Open ethanglide opened 1 week ago
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
langchain | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Nov 17, 2024 2:28am |
The CI checks have to do with me having trouble replacing the return types of the tool
function and inner functions. The linter is upset for good reason but its better to get this draft out and get my idea reviewed, then those return types can get fixed up soon.
Hello @efriis just following up on this from the issue. I would appreciate it if any of the maintainers were able to give this a review. The functionality is ready, just the linter is upset about a few things.
On that note, I am having trouble appeasing mypy and was wondering if I could get some help with making it happy here. Upon running make lint
I get this output from mypy:
langchain_core/tools/structured.py:65: error: TypedDict "ToolCall" has no key "self" [typeddict-unknown-key]
langchain_core/tools/convert.py:259: error: "property" used with a non-method [misc]
langchain_core/tools/convert.py:306: error: Incompatible return value type (got "Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool | Callable[[Callable[..., Any]], BaseTool]]", expected "Callable[[Callable[..., Any] | Runnable[Any, Any]], BaseTool]") [return-value]
Found 3 errors in 2 files (checked 292 source files)
The first error comes from this new helper function in StructuredTool
:
def _add_outer_self(self, input: Union[str, dict, ToolCall]) -> dict | ToolCall:
"""Add outer self into arguments for method tools."""
# If input is a string, then it is the first argument
if isinstance(input, str):
args = {"self": self.outer_self}
for x in self.args: # loop should only happen once
args[x] = input
return args
# ToolCall
if "type" in input and input["type"] == "tool_call":
input["args"]["self"] = self.outer_self
return input
# Dict
input["self"] = self.outer_self # <-- ERROR HERE
return input
I am not able to use isinstance(input, ToolCall)
since it gives an error that TypedDict
does not work with isinstance
. How can I make mypy okay with this?
The second error comes from here inside the tool
function:
@property # <-- ERROR HERE
def method_tool(self: Callable) -> StructuredTool:
return StructuredTool.from_function(
func,
coroutine,
name=tool_name,
description=description,
return_direct=return_direct,
args_schema=schema,
infer_schema=infer_schema,
response_format=response_format,
parse_docstring=parse_docstring,
error_on_invalid_docstring=error_on_invalid_docstring,
outer_self=self,
)
Where @property
is used so that the tool can be accessed like a.foo
instead of a.foo()
(assuming foo
is a method decorated by @tool
). Is there any way to either get mypy to relax or change around the code to get the same result without using @property
?
The third error is because the return types are changing. This one is being really difficult because every time I fix the return type on one function it will change the return type of the parent function it is nested within, and so on and so on. I am not an expert on the types in Python so cleaning this up has proven to be hard thing to do. I figured that someone with a bit more knowledge on the codebase would be able to clean up this refactor easily, so any potential help would be appreciated. That, or a way to get the same functionality without needing to change the return types somehow could save some time.
Some documentation for this feature to help maintainers:
When the @tool
is placed above a method who's first parameter is self
, it will detect that it is a method and create a method tool. A method tool is actually a function that takes in a self
parameter and will give back a StructuredTool
with the new outer_self
field populated by that self
parameter. This function is decorated with @property
to make it accessible without using parenthesis like other tools.
When you call .args
on a StructuredTool
with the outer_self
field set, it will return the arguments without the self
included since this tool will add that in automatically. When you call .invoke
or .ainvoke
then it will automatically pass in the outer_self
field as the self
argument to the tool.
Once that self
argument gets passed in, there are some difficulties with keeping it the whole time. The argument must be called self
because the arguments will get checked before the tool is run, but the argument also must NOT be called self
because when these arguments are turned into a tuple and passed into run
an error will be thrown since there are two arguments called self
. The solution is to change the name of any self
arguments into outer_self
AFTER type checking, and then back into self
directly before being put into the tool's underlying func
or coroutine
.
Add support for methods to the
@tool
decorator. Previously using the@tool
decorator on methods was not possible. Now the@tool
decorator will have the same behaviours on methods as on regular functions.Resolves #27471
Example:
Note that no
self
argument had to be passed intoa.foo.invoke
. This would not work in the past even ifself
was passed in manually.