-
Notifications
You must be signed in to change notification settings - Fork 71
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
Enable tool schemas to support more dynamic generation #278
Comments
@off6atomic I'm also wondering if I was off about wanting dynamic generation at runtime vs. just templating at definition time? |
If we want maximum flexibility like when calling OpenAI API, I think all the data sent to OpenAI should be modifiable in any way that users want at runtime. This means that these needs to modifiable at runtime:
One approach that could work is that Mirascope should provide convenience in defining the default base values at definition time but then let users modify them at runtime however they want. As a user who seeks least magical behavior, I want to be able to intercept the data immediately preceding the OpenAI API call, modify it, and then pass it back into the call. This data could be in a dictionary form or some kind of class object. When I debug machine learning models, it's really crucial for me to inspect the processed data that gets sent to the model because that's how I learn what's going on and find bugs effectively. So I think the same ability for inspection is crucial here. Something like At this point I think This is the rough API I expect for now: from mirascope.openai import OpenAICall, OpenAICallParams, OpenAITool
from pydantic import Field
class MyTool(OpenAITool):
"""This is the overall tool description of my tool that gets {template_var_to_insert}."""
foo: str = Field(
...,
description=f"This is the field specific description that gets {template_var_to_insert}."
)
class MyCall(OpenAICall):
prompt_template = "..."
call_params = OpenAICallParams(tools=[MyTool])
my_call = MyCall()
# access and modify the tool
# this would create a copy of the MyTool for users to modify; could be a dict or an object; this copy would always be a copy of the base tool
my_tool = my_call.get_tool("MyTool")
# users could print it to see all the attributes they can modify
print(my_tool)
# the reason we allow users access to the docstring is so that they might set it to empty or generate entirely new string
my_tool.docstring = my_tool.docstring.format(template_var_to_insert="INSERT SCHEMA HERE")
# user can change type from string to int
my_tool.foo.type = "int"
response = my_call.call(update_tools=[my_tool]) Maybe we should even allow modifying the list of tools at runtime, but I'm not sure if that kind of flexibility would hurt other principles of Mirascope. If it's not hurting anything then we should do that. The question would be "Should the tool being returned be a dict or an object?" if it's a dict, then might as well just be a dict in OpenAI format directly. If it's an object, maybe it can be in Mirascope proprietary format that is easy for users to understand. I think if we want to enforce less abstraction onto the user, then OpenAI dict should be used (especially when the class itself is called OpenAITool anyway) This kind of idea of interception might be applicable to other variables as well e.g. the concept of message = call.get_user_message()
message["content"].append('an image url')
response = call.call(user_message=user_message) |
Ok I see what you're saying. Something like a As for updating the tool, I'm thinking the best path forward here would be to simply rely on the It might be worth adding a convenience wrapper around Ultimately this would result is something like |
Thinking through this further, you can technically already do this without any changes to the existing library. The only thing missing is potential added convenience. Right now you could:
|
When I print the value of This is problematic because it's hard to create a new tool given the old tool function when I don't see the attributes of the function being printed. Maybe your approach only works if the tool is created using class instead of function? |
Ah, in this case you can use the |
I believe that updates we'll make in #294 should make this easier to implement / improve the interface. Still needs to be further fleshed out alongside the other features. |
From #322 moved here: from mirascope.openai import openai_call, OpenAITool
from pydantic import Field
class PrintBook(OpenAITool):
"""Prints the title and author of a {genre} book."""
title: str = Field(..., description="The title of a {genre} book."
author: str = Field(..., description="The author of {genre} book `title`.")
def call(self):
return f"{title} by {author}"
@openai_call(model="gpt-4o")
def recommend_book(genre: str) -> CallReturn:
"""Recommend a {genre} book"""
return { "tools": [PrintBook.tool_schema(genre=genre)] } |
@koxudaxi's response about typing: Yes. In the same way type checking is no longer possible. It looks a bit redundant, but from a type-checking point of view this looks better. class PrintBook(OpenAITool):
"""Prints the title and author of a {genre} book."""
title: str = Field(..., description="The title of a {genre} book.")
author: str = Field(..., description="The author of {genre} book `title`.")
def call(self):
return f"{self.title} by {self.author}"
@tool_schema(PrintBook)
def print_book(genre: str) -> ...:
...
print_book_tool = print_book("fantasy")
assert isinstance(print_book_tool, PrintBook)
assert print_book_tool.__doc__ == "Prints the title and author of a fantasy book." |
My primary concern with this approach is I want Consider the following function as a tool: def print_book(title: str, author: str):
"""Prints the title and author of a {genre} book.""" However we dynamically template def set_tool_template(**kwargs):
def decorator(fn):
fn.__doc__ = fn.__doc__.format(**kwargs)
return fn
return decorator
def print_book(title: str, author: str):
"""Prints the title and author of a {genre} book."""
print(f"{title} by {author}")
@openai_call(model="gpt-4o")
def recommend_book(genre: str):
"""Recommend a {genre} book."""
return { "tools": [set_tool_template(genre=genre)(print_book)] } There's no type hints in this example, but it showcases what I want -- the output is still just Can you think of a way to implement something like this but with proper type hinting? |
The function doesn't have the type-hint for def print_book(title: str, author: str):
"""Prints the title and author of a {genre} book."""
print(f"{title} by {author}") Also, Does this description method make sense? return { "tools": [set_tool_template(genre=genre)(print_book)] } If I can define the type somewhere, I can give tools to the decorator to check the type. I don't think it is wise to let the user create a higher-order function, but here is an image of what it might look like. def print_book(genre: str):
def inner(title: str, author: str):
"""Prints the title and author of a {genre} book."""
print(f"{title} by {author}")
return inner
def openai_call(model: str, tools: list[Callable[P, Any]] | None = None, **kwargs):
def decorator(fn: Callable[P, Any]):
def inner(*args: P.args, **kwargs: P.kwargs) -> Any:
return fn(*args, **kwargs)
return inner
return decorator
@openai_call(model="gpt-4o", tools=[print_book])
def recommend_book(genre: str):
"""Recommend a {genre} book.""" |
The snippet you've shared still has the same issue -- Also, it looks like the tool in your snippet would then require that all tools share the same param spec as the main function, which may not necessarily be the case. For example: def print_book(title: str, author: str):
"""Prints the title and author of a book.
User reading level: {reading_level}
"""
print(f"{title} by {author}")
@openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "intermediate", "advanced"]):
"""Recommend a {genre} book for a user with {reading_level} reading level."""
return { "tools": [set_tool_template(reading_level=reading_level)(print_book)] } Here the output of |
Given new features coming soon + the new interface, I guess you could do something like this (although it forces the tool to be scoped within the function using it): @openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
class PrintBook(OpenAITool):
@classmethod
def description(cls) -> str:
return dedent(f"""
Prints the title and author of a book.
User reading level: {reading_level}
""").strip()
title: str = Field(..., description=f"The title of a {reading_level} {genre} book.")
author: str = Field(..., description=f"The author of the {reading_level} {genre} book `title`.")
return { "tools": [PrintBook] } One concern here is that every call to We could do this more simply but without type hints: class PrintBook(OpenAITool):
"""Prints the title and author of a book.
User reading level: {reading_level}
"""
title: str = Field(..., description=f"The title of a {reading_level} {genre} book.")
author: str = Field(..., description=f"The author of the {reading_level} {genre} book `title`.")
@openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
return { "tools": [PrintBook.templated(genre=genre, reading_level=reading_level)] } The idea would be that Of course, we could still make this fail at runtime by failing in attempts to template variables that aren't correct, but we won't catch missing variables. For the function case, it would look like this? (although honestly I find this gross) @openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
print_book_docstr = f"""Prints the title and author of a book.
User reading level: {reading_level}
Args:
title: The title of a {reading_level} {genre} book.
author: The author of the {reading_level} {genre} book `title`.
"""
@set_docstr(print_book_docstr) # this just returns the wrapped function with `fn.__doc__` set since we can't f-string the docstr
def print_book(title: str, author: str):
...
return { "tools": [print_book] } We could make this better but not sure if we could type hint it: @openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
@template_docstr(genre=genre, reading_level=reading_level)
def print_book(title: str, author: str):
"""Prints the title and author of a book.
User reading level: {reading_level}
Args:
title: The title of a {reading_level} {genre} book.
author: The author of the {reading_level} {genre} book `title`.
"""
return { "tools": [print_book] } |
There seems to be some misunderstanding, so I will explain. def print_book(genre: str):
def inner(title: str, author: str):
"""Prints the title and author of a {genre} book."""
print(f"{title} by {author}")
return inner
def openai_call(model: str, tools: list[Callable[P, Any]] | None = None):
def decorator(fn: Callable[P, Any]):
def inner(*args: P.args, **kwargs: P.kwargs) -> Any:
if tools:
result = {"tools": [set_tool_template(tool(*args, **kwargs)) for tool in tools]}
else:
result = {}
# do something with result
return fn(*args, **kwargs)
return inner
return decorator
@openai_call(model="gpt-4o", tools=[print_book])
def recommend_book(genre: str):
"""Recommend a {genre} book.""" This decorator does not work correctly as logic, but the image looks like this. return { "tools": [set_tool_template(genre=genre)(print_book)] }
It is assumed that all arguments such as genre, reading_level, etc. are passed to the tool or that only the necessary ones are picked up. I am most concerned about. return { "tools": [set_tool_template(reading_level=reading_level)(print_book)] }
Admittedly, this is a bit of a problem.
Users find it difficult to understand. class PrintBook(OpenAITool):
"""Prints the title and author of a book.
User reading level: {reading_level}
"""
title: str = Field(..., description="The title of a {reading_level} {genre} book.")
author: str = Field(..., description="The author of the {reading_level} {genre} book `title`.")
@openai_call(model="gpt-4o", tool_templates=[PrintBook])
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book.""" Similar to my earlier suggestion, how about this one? Of course these should be done inside the decorator, not by the user.
Do you think this will not detect missing variables even with my proposal? Regardless of this proposal, it would be difficult to check for type hints in the correct sense, as templated variables are not typed defined anywhere. |
I think that the decorator approach with def print_book(genre: str):
def inner(title: str, author: str):
"""Prints the title and author of a {genre} book."""
print(f"{title} by {author}")
return inner In this example, we call I'm thinking we need to differentiate between dynamic and non-dynamic tools. Users should be able to generate the dynamic tool definition agnostic to any call. The decorator should just provide proper type hinting when using dynamic tools. I also want to ensure that the naming conventions are clear. Honestly, I wonder if we're overthinking this and we should put a little bit more on the user to get proper type hinting instead of trying to do everything internally. Here are my thoughts dumped. I'm going to limit this to functional definitions of tools, but I think we could fairly easily expand this to the class based definition approach. I'll leave that to the imagination for this discussion. First, we create a decorator for templating a docstring that lives in the mirascope library: from typing import Any, Callable, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def format_docstr(**kwargs) -> Callable[[Callable[P, R]], Callable[P, R]]:
def decorator(fn: Callable[P, Any]) -> Callable[P, R]:
doc = fn.__doc__
if not doc:
raise ValueError("No docstring")
fn.__doc__ = doc.format(**kwargs)
return fn
return decorator Now the user can define a generator function to generate a dynamic tool like this: from typing import Literal
from mirascope.base import format_docstr
def dynamic_format_book(reading_level: Literal["beginner", "advanced"]):
"""Dynamically generates the `format_book` function using `reading_level`."""
# no type hints, but I think that's ok in this instance since it's an f-string template
@format_docstr(reading_level=reading_level)
def format_book(title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
return f"{title} by {author}"
return format_book
format_book = dynamic_format_book(reading_level="beginner") In this example it's clear that The user can also at any point agnostic to any call run Inside of our call, we can then differentiate between tools and dynamic tools: @openai_call(model="gpt-4o", dynamic_tools=[dynamic_format_book])
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book.""" This will explicitly be separated from the As you've mentioned, we can create the |
I would love to do something like this: from typing import Any, Callable, Literal, ParamSpec, TypedDict, TypeVar, Unpack
class ToolTemplate(TypedDict):
pass
ToolTemplateT = TypeVar("ToolTemplateT", bound=ToolTemplate)
P = ParamSpec("P")
R = TypeVar("R")
# this would be internal to mirascope and imported by the user
def dynamic_tool(
template: type[ToolTemplateT],
) -> Callable[[Callable[P, R]], Callable[[Unpack[ToolTemplateT]], Callable[P, R]]]:
def inner(fn: Callable[P, R]) -> Callable[[Unpack[ToolTemplateT]], Callable[P, R]]:
def wrapper(**kwargs: Unpack[ToolTemplateT]) -> Callable[P, R]:
return format_docstr(**kwargs)(fn)
return wrapper
return inner
class FormatBookTemplate(ToolTemplate):
reading_level: Literal["beginner", "advanced"]
@dynamic_tool(FormatBookTemplate)
def format_book(title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
return f"{title} by {author}"
tool = format_book(reading_level="beginner") This works except for typing since unfortunately this runs into the same issues as described in python/typing#1399 |
I know this is not the essence of the discussion, but I think we can do whatever we want to operate within the decorator. templated = print_book("fantasy")
templated.__name__ = print_book.__name__
assert templated.__name__ == "print_book" However, as you say, it is a hassle for the user, but it is good to be explicit to let the user do the correct naming or inject by using this decorator.
If we match the recommend_book and dynamic_tool call arguments in this way, we can correctly call dynamic_tool with the appropriate arguments. import inspect
def openai_call(model: str, dynamic_tools: list[Callable[P, Any]] | None = None):
def decorator(fn: Callable[P, Any]):
function_signature_parameters = {k: v.default for k, v in inspect.signature(fn).parameters.items()}
def inner(*args: P.args, **kwargs: P.kwargs) -> Any:
if dynamic_tools:
# Collect all arguments when calling the function
call_kwargs = {}
for function_signature_parameters_key, arg in zip(function_signature_parameters.keys(), args):
call_kwargs[function_signature_parameters_key] = arg
remaining_kwargs = {k: kwargs.get(k, v) for k, v in function_signature_parameters.items() if
k not in call_kwargs}
call_kwargs.update(remaining_kwargs)
# Fill arguments for dynamic tools with the call_kwargs
tools = []
for dynamic_tool in dynamic_tools:
dynamic_tool_signature = inspect.signature(dynamic_tool)
tool_kwargs = {k: call_kwargs[k] for k in dynamic_tool_signature.parameters.keys()}
tools.append(dynamic_tool(**tool_kwargs))
# do something with result
return inner
return decorator
def uses_reading_level(reading_level: Literal["beginner", "advanced"]):
...
def uses_genre(genre: str):
...
@openai_call(model="gpt-4o", dynamic_tools=[uses_reading_level, uses_genre])
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
recommend_book("fantasy", reading_level="beginner")
I agree with you. from typing import Callable, Literal, ParamSpec, TypeVar, TypeAlias
from pydantic import BaseModel
class ToolTemplate(BaseModel):
pass
P = ParamSpec("P")
R = TypeVar("R")
WrappedFunc: TypeAlias = Callable[P, R]
ToolTemplateP = ParamSpec("ToolTemplateP")
ToolTemplateT = TypeVar("ToolTemplateT", bound=ToolTemplate)
def dynamic_tool(
template: Callable[ToolTemplateP, ToolTemplateT]
) -> Callable[[WrappedFunc], Callable[ToolTemplateP, WrappedFunc]]:
def inner(fn: WrappedFunc) -> Callable[ToolTemplateP, WrappedFunc]:
def wrapper(*args: ToolTemplateP.args, **kwargs: ToolTemplateP.kwargs) -> WrappedFunc:
return format_docstr(**kwargs)(fn) # type: ignore
return wrapper
return inner
class FormatBookTemplate(ToolTemplate):
reading_level: Literal["beginner", "advanced"]
@dynamic_tool(FormatBookTemplate)
def format_book(title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
return f"{title} by {author}"
tool = format_book(reading_level="beginner")
tool = format_book(reading_level="beginner", xyz="") $ mypy main.py
main4.py:43: error: Unexpected keyword argument "xyz" for "format_book" [call-arg]
Found 1 error in 1 file (checked 1 source file)
$ pyright main.py
/Users/koudai/PycharmProjects/pythonProject1/main.py
/Users/koudai/PycharmProjects/pythonProject1/main.py:43:46 - error: No parameter named "xyz" (reportCallIssue)
1 error, 0 warnings, 0 informations |
Ok yes this is exactly what I want. I like this design we should implement it like this :) |
For class based tool definitions, I think we can just recommend doing it yourself like this: from typing import Literal
from mirascope.base import format_docstr
def dynamic_format_book(reading_level: Literal["beginner", "advanced"]):
"""Dynamically generates the `FormatBook` tool using `reading_level`."""
class FormatBook(OpenAITool):
__doc__ = """Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
title: str = Field(..., description=f"The title of a {reading_level} book.")
author: str = Field(..., description=f"The author of the {reading_level} book `title`.")
return FormatBook
FormatBook = dynamic_format_book(reading_level="beginner") This way when we add the automatic handling of dynamic tools for the new interface it will support both this and the functional definition styles. |
Thinking about this a bit more, could we update the decorator to also work on the class? I'm imagining something like this: @dynamic_tool(FormatBookTemplate)
class DynamicFormatBook(OpenAITool):
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
title: str
author: str
FormatBook = DynamicFormatBook(reading_level="beginner") |
I tested it. pyright works fine. But, mypy doesn't work correctly. ToolTemplateP = ParamSpec("ToolTemplateP")
ToolTemplateT = TypeVar("ToolTemplateT", bound=ToolTemplate)
OpenAIToolT = TypeVar("OpenAIToolT", bound=OpenAITool)
def dynamic_tool(
template: Callable[ToolTemplateP, ToolTemplateT]
) -> Callable[[type[OpenAIToolT]], Callable[ToolTemplateP, type[OpenAIToolT]]]:
def func(cls: type[OpenAIToolT]) -> Callable[ToolTemplateP, type[OpenAIToolT]]:
def inner(*args: ToolTemplateP.args, **kwargs: ToolTemplateP.kwargs) -> type[OpenAIToolT]:
return cls
return inner
return func
class FormatBookTemplate(ToolTemplate):
reading_level: Literal["beginner", "advanced"]
@dynamic_tool(FormatBookTemplate)
class DynamicFormatBook(OpenAITool):
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
title: str
author: str
FormatBook = DynamicFormatBook(reading_level="beginner")
FormatBook = DynamicFormatBook(reading_level="beginner", xyz="") pyright main.py
/Users/koudai/PycharmProjects/pythonProject1/main.py
/Users/koudai/PycharmProjects/pythonProject1/main.py:71:58 - error: No parameter named "xyz" (reportCallIssue)
1 error, 0 warnings, 0 informations mypy main.py
main.py:65: error: Unexpected keyword argument "reading_level" for "DynamicFormatBook" [call-arg]
main.py:66: error: Unexpected keyword argument "reading_level" for "DynamicFormatBook" [call-arg]
main.py:66: error: Unexpected keyword argument "xyz" for "DynamicFormatBook" [call-arg]
Found 3 errors in 1 file (checked 1 source file)
|
Ok, I think that's fine. If it works for pyright that's fine by me. We can just mention that mypy doesn't properly support it and recommend using pyright. In fact we should probably switch to use pyright internally anyway since mypy is generally quite slow... |
I think that's good. If you really want to support mypy, you could create a mypy plugin like pydantic and customise mypy. I think I sent a PR for a mypy plugin to the pydantic repository to support the pydantic dataclass a long time ago. |
@willbakst class FormatBookTemplate(ToolTemplate):
reading_level: Literal["beginner", "advanced"]
@dynamic_tool(FormatBookTemplate)
class DynamicFormatBook(OpenAITool):
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
title: str = Field(..., description='title for {reading_level}')
author: str However, is there a case where there are too many of one or the other? In other words, check if there are variables that are resolved at call time rather than at tool creation time. for example, the class FormatBookTemplate(ToolTemplate):
reading_level: Literal["beginner", "advanced"]
@dynamic_tool(FormatBookTemplate)
class DynamicFormatBook(OpenAITool):
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
Genre: {genre}
"""
title: str = Field(..., description='title for {genre}')
author: str |
Also, Do we need the baseTool type for function decoration? @dynamic_tool(FormatBookTemplate, base=OpenAITool)
def format_book(title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {reading_level}
"""
return f"{title} by {author}"``` |
I had a thought for potentially a better solution for dynamic tools with the new interface. What do you think about the following? from mirascope.core import BaseTool, Toolkit
class BookRecommendationTools(Toolkit):
"""A toolkit for recommending books."""
reading_level: Literal["beginner", "advanced"]
def format_book(self, title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {self.reading_level}
"""
return f"{title} by {author}" Then, with the new interface we would do something like this: from mirascope.core.openai import openai_call
@openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
toolkit = BookRecommendationTools(reading_level=reading_level)
return {"tools": [toolkit.create_tools()]} We will also fail at runtime before making any API calls if there are any missing template variables. |
Is the idea to clarify the namespace of variables like What about making it inherit some class that inherits from BaseModel instead of Toolkit?
Or, Does it indicate a newly created class rather than an existing Toolkit? |
Yeah, we would clarify the namespace. Maybe better to call it |
Looks good. |
The user flow would be the following: from mirascope.core import BaseTool, Toolkit
from mirascope.core.openai import openai_call
class BookRecommendationTools(Toolkit):
"""A toolkit for recommending books."""
reading_level: Literal["beginner", "advanced"]
# Could be namespaced as `BookRecommendationTools.format_book`
# Should also provide a `namespace` ClassVar that the user can override or set to `None`
def format_book(self, title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {self.reading_level}
"""
return f"{title} by {author}"
@openai_call(model="gpt-4o")
def recommend_book(genre: str, reading_level: Literal["beginner", "advanced"]):
"""Recommend a {genre} book."""
toolkit = BookRecommendationTools(reading_level=reading_level)
return {"tools": [toolkit.create_tools()]}
response = recommend_book("fantasy", "beginner")
if tool := response.tool:
output = tool.call() # this is calling `format_book` as it's the only available tool
print(output)
#> The Name of the Wind by Patrick Rothfuss
else:
print(response.content)
#> Sure! I would recommend... |
Okay, so you want to dynamically detect that method. @abstractmethod
def format(self, **kwargs) -> str: I am not enforcing this proposal. |
What if we added a decorator to make it clear like this: class BookRecommendationTools(Toolkit):
"""A toolkit for recommending books."""
reading_level: Literal["beginner", "advanced"]
@toolkit_tool() # not sure about this naming...
def format_book(self, title: str, author: str) -> str:
"""Returns the title and author of a book nicely formatted.
Reading level: {self.reading_level}
"""
return f"{title} by {author}" |
It is good to be explicit. |
This is implemented as part of v1. Thank you @koxudaxi!! :) |
Description
Consider the following tool:
What happens when we want the descriptions to be more dynamic with templating like we have for
prompt_template
? For example:While the above would work for
foo
, it doesn't work for the docstring. Furthermore, this only works if you have the variable available at the time of definition. What happens if you want to update the template variable dynamically at run time with multiple different options? Especially since you pass tools into thecall_params
classvar.My initial instinct would be to enable dynamically updating the schema at generation time and then surfacing something like a config that can be passed in at run time to actually go and update the generation. Something like:
I don't love this exact design, but I think it's headed in the direction of what would potentially work.
@off6atomic I would love your thoughts here.
The text was updated successfully, but these errors were encountered: