-
Notifications
You must be signed in to change notification settings - Fork 13
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
fix: Sanitize function names and trim descriptions for LLM compatibility #41
Conversation
Pull Request Test Coverage Report for Build 10042587214Details
💛 - Coveralls |
|
||
def sanitize(data: Dict[str, Any]) -> Dict[str, Any]: | ||
""" | ||
Sanitizes the given function calling definition by adjusting its properties to LLM requirements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LLM requirements
is rather vague. Is there some documentation that defines what that means, i.e., where are these requirements coming from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shadeMe I couldn't find this in documentation but you hit it in the error reports returned by LLM once you use enough openapi specs. For example:
# BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'tools[0].function.name': string does not match pattern. Expected a string that matches the pattern '^[a-zA-Z0-9_-]+$'.", 'type': 'invalid_request_error', 'param': 'tools[0].function.name', 'code': 'invalid_value'}}
Once you apply this pattern to method is starts to work. Same for length of description field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which LLM? Which provider? Are there providers/models that don't accept underscores? If this code doesn't follow an official spec, that we should not make any assumptions and perform this sanitization (normalization, actually) on a per-LLM provider basis at the least.
Also, this kind of impl. detail of LLM-specific requirements shouldn't leak into code that handles OpenAPI - one should perform this normalization just-in-time, i.e, just before passing it to the generator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll close this PR and rethink it now
:param data: The function calling definition(s) to sanitize. | ||
:return: A sanitized function calling definition. | ||
""" | ||
if isinstance(data, dict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this check required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is required for recursive search of the function calling dict structure and sanitize recursive call might not be called on dict. We need recursion to find all those nested description keys and make sure they are "sanitized"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then the check should be on line 58.
if isinstance(data, dict): | ||
sanitized_data: Dict[str, Any] = {} | ||
for key, value in data.items(): | ||
if key == "name" and isinstance(value, str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the type check needed here? What are the expected types of the value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes a parameter in function calling def can have a name name. And this parameter is not a string but another dict
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we formally codify this in some manner? I feel like we have far too many Any
type hints w.r.t the OpenAPI specific code, and having some kind of schema (or at least better type annotation) would help make this more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the sentiment @shadeMe . We have nicely, as much as needed imho, typed OpenAPISpec itself. Here we are dealing with these tools (function) definitions specified by various LLM providers. They look like this:
- Cohere https://docs.cohere.com/docs/tool-use#step-1
- Anthropic https://docs.anthropic.com/en/docs/build-with-claude/tool-use#tool-use-examples
- OpenAI https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models#basic-concepts
In other words they are just dictionaries. And although it would be nice to bring some order and typing perhaps it is not worth it given how they change and get expanded with new options. Maybe just providing references to these dictionary structures would help a reader quite a lot.
for key, value in data.items(): | ||
if key == "name" and isinstance(value, str): | ||
sanitized_data[key] = sanitize_function_name(value) | ||
elif key == "description" and isinstance(value, str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes exactly
return sanitize_function_name(path + "_" + http_method.lower()) | ||
|
||
|
||
def sanitize(data: Dict[str, Any]) -> Dict[str, Any]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with a more explicit name - sanitize_function_calling_definition
?
return data | ||
|
||
|
||
def sanitize_function_name(name: str) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More explicit naming.
Co-authored-by: Madeesh Kannan <[email protected]>
Co-authored-by: Madeesh Kannan <[email protected]>
I'll rethink how to do this in a better way outside of OpenAPISpec across LLMs so we can reuse it in the future. Closing |
Why:
Fixes subtle naming bugs to meet LLM naming requirements for function naming patterns and description length limitations.
Not all operation names from OpenAPI spec and long descriptions are accepted by LLMs.
What:
^[a-zA-Z0-9_-]+$
How:
How did you test it:
Notes for the reviewer: