Update to have graceful handling and not raise - added tests as well

This commit is contained in:
ParthSareen 2024-11-15 11:50:08 -08:00
parent 7c5c294b6b
commit 16c868a937
2 changed files with 53 additions and 7 deletions

View File

@ -1,12 +1,15 @@
from __future__ import annotations
from typing import Any, Callable, get_args
from typing import Any, Callable, Union, get_args
from ollama._json_type_map import is_union
from ollama._types import Tool
from typing import Dict
def _parse_docstring(func: Callable, doc_string: str) -> tuple[str, Dict[str, str]]:
def _parse_docstring(func: Callable, doc_string: Union[str, None]) -> tuple[str, Dict[str, str]]:
# Extract description from docstring - get all lines before Args:
if not doc_string:
return '', {}
description_lines = []
for line in doc_string.split('\n'):
line = line.strip()
@ -18,7 +21,7 @@ def _parse_docstring(func: Callable, doc_string: str) -> tuple[str, Dict[str, st
description = ' '.join(description_lines).strip()
if 'Args:' not in doc_string:
raise ValueError(f'Function {func.__name__} docstring must have an Args section in Google format')
return description, {}
args_section = doc_string.split('Args:')[1]
if 'Returns:' in args_section:
@ -69,7 +72,7 @@ def _parse_docstring(func: Callable, doc_string: str) -> tuple[str, Dict[str, st
if param_name == 'return':
continue
if param_name not in param_descriptions:
raise ValueError(f'Parameter {param_name} must have a description in the Args section')
param_descriptions[param_name] = ''
return description, param_descriptions
@ -83,8 +86,6 @@ def is_optional_type(python_type: Any) -> bool:
def convert_function_to_tool(func: Callable) -> Tool:
doc_string = func.__doc__
if not doc_string:
raise ValueError(f'Function {func.__name__} must have a docstring in Google format. Example:\n' '"""Add two numbers.\n\n' 'Args:\n' ' a: First number\n' ' b: Second number\n\n' 'Returns:\n' ' int: Sum of the numbers\n' '"""')
description, param_descriptions = _parse_docstring(func, doc_string)
@ -94,7 +95,7 @@ def convert_function_to_tool(func: Callable) -> Tool:
if param_name == 'return':
continue
parameters.properties[param_name] = Tool.Function.Parameters.Property(type=param_type, description=param_descriptions[param_name])
parameters.properties[param_name] = Tool.Function.Parameters.Property(type=param_type, description=param_descriptions.get(param_name, ''))
if not is_optional_type(param_type):
parameters.required.append(param_name)

View File

@ -125,3 +125,48 @@ def test_skewed_docstring_parsing():
tool = convert_function_to_tool(add_two_numbers).model_dump()
assert tool['function']['parameters']['properties']['x']['description'] == ': The first number'
assert tool['function']['parameters']['properties']['y']['description'] == 'The second number'
def test_function_with_no_docstring():
def no_docstring():
pass
def no_docstring_with_args(x: int, y: int):
pass
tool = convert_function_to_tool(no_docstring).model_dump()
assert tool['function']['description'] == ''
tool = convert_function_to_tool(no_docstring_with_args).model_dump()
assert tool['function']['description'] == ''
assert tool['function']['parameters']['properties']['x']['description'] == ''
assert tool['function']['parameters']['properties']['y']['description'] == ''
def test_function_with_only_description():
def only_description():
"""
A function with only a description.
"""
pass
tool = convert_function_to_tool(only_description).model_dump()
assert tool['function']['description'] == 'A function with only a description.'
assert tool['function']['parameters'] == {'type': 'object', 'properties': {}, 'required': []}
def only_description_with_args(x: int, y: int):
"""
A function with only a description.
"""
pass
tool = convert_function_to_tool(only_description_with_args).model_dump()
assert tool['function']['description'] == 'A function with only a description.'
assert tool['function']['parameters'] == {
'type': 'object',
'properties': {
'x': {'type': 'integer', 'description': ''},
'y': {'type': 'integer', 'description': ''},
},
'required': ['x', 'y'],
}