Skip to content

Commit c5c61a3

Browse files
committed
Better docstring parsing and some fixes
1 parent 6d9c156 commit c5c61a3

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

ollama/_utils.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections import defaultdict
33
import inspect
44
from typing import Callable, Union
5+
import re
56

67
import pydantic
78
from ollama._types import Tool
@@ -14,7 +15,7 @@ def _parse_docstring(doc_string: Union[str, None]) -> dict[str, str]:
1415

1516
key = hash(doc_string)
1617
for line in doc_string.splitlines():
17-
lowered_line = line.lower()
18+
lowered_line = line.lower().strip()
1819
if lowered_line.startswith('args:'):
1920
key = 'args'
2021
elif lowered_line.startswith('returns:') or lowered_line.startswith('yields:') or lowered_line.startswith('raises:'):
@@ -27,14 +28,21 @@ def _parse_docstring(doc_string: Union[str, None]) -> dict[str, str]:
2728
last_key = None
2829
for line in parsed_docstring['args'].splitlines():
2930
line = line.strip()
30-
if ':' in line and not line.lower().startswith('args:'):
31-
# Split on first occurrence of '(' or ':' to separate arg name from description
32-
split_char = '(' if '(' in line else ':'
33-
arg_name, rest = line.split(split_char, 1)
34-
35-
last_key = arg_name.strip()
36-
# Get description after the colon
37-
arg_description = rest.split(':', 1)[1].strip() if split_char == '(' else rest.strip()
31+
if ':' in line:
32+
# Split the line on either:
33+
# 1. A parenthetical expression like (integer) - captured in group 1
34+
# 2. A colon :
35+
# Followed by optional whitespace. Only split on first occurrence.
36+
parts = re.split(r'(?:\(([^)]*)\)|:)\s*', line, maxsplit=1)
37+
38+
arg_name = parts[0].strip()
39+
last_key = arg_name
40+
41+
# Get the description - will be in parts[1] if parenthetical or parts[-1] if after colon
42+
arg_description = parts[-1].strip()
43+
if len(parts) > 2 and parts[1]: # Has parenthetical content
44+
arg_description = parts[-1].split(':', 1)[-1].strip()
45+
3846
parsed_docstring[last_key] = arg_description
3947

4048
elif last_key and line:

tests/test_utils.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def add_two_numbers(x: int, y: int) -> int:
147147
"""
148148
Add two numbers together.
149149
Args:
150-
x (integer):: The first number
150+
x (integer): : The first number
151151
152152
153153
@@ -238,3 +238,33 @@ def no_types(a, b):
238238
tool = convert_function_to_tool(no_types).model_dump()
239239
assert tool['function']['parameters']['properties']['a']['type'] == 'string'
240240
assert tool['function']['parameters']['properties']['b']['type'] == 'string'
241+
242+
243+
def test_function_with_parentheses():
244+
def func_with_parentheses(a: int, b: int) -> int:
245+
"""
246+
A function with parentheses.
247+
Args:
248+
a: First (:thing) number to add
249+
b: Second number to add
250+
Returns:
251+
int: The sum of a and b
252+
"""
253+
pass
254+
255+
def func_with_parentheses_and_args(a: int, b: int):
256+
"""
257+
A function with parentheses and args.
258+
Args:
259+
a(integer) : First (:thing) number to add
260+
b(integer) :Second number to add
261+
"""
262+
pass
263+
264+
tool = convert_function_to_tool(func_with_parentheses).model_dump()
265+
assert tool['function']['parameters']['properties']['a']['description'] == 'First (:thing) number to add'
266+
assert tool['function']['parameters']['properties']['b']['description'] == 'Second number to add'
267+
268+
tool = convert_function_to_tool(func_with_parentheses_and_args).model_dump()
269+
assert tool['function']['parameters']['properties']['a']['description'] == 'First (:thing) number to add'
270+
assert tool['function']['parameters']['properties']['b']['description'] == 'Second number to add'

0 commit comments

Comments
 (0)