5353)
5454
5555
56- def _first_docstring_paragraph (obj : Any ) -> str :
56+ def _extract_docstring_summary (obj : Any ) -> str :
57+ """Return leading descriptive docstring text before Args/Returns-style sections."""
5758 doc = inspect .getdoc (obj )
5859 if not doc :
5960 return ""
@@ -66,7 +67,7 @@ def _first_docstring_paragraph(obj: Any) -> str:
6667
6768
6869def extract_function_description (fn : Callable [..., Any ]) -> str :
69- """Return the first paragraph of *fn*'s docstring, stopping before Args/Returns sections ."""
70+ """Return a short description for *fn* from its leading docstring text ."""
7071 # Unwrap partials to get the underlying function's docstring.
7172 if isinstance (fn , functools .partial ):
7273 return extract_function_description (fn .func )
@@ -75,12 +76,12 @@ def extract_function_description(fn: Callable[..., Any]) -> str:
7576 # Prefer __call__ docstring (what calling does), then class docstring, then class name.
7677 if not hasattr (fn , "__name__" ):
7778 return (
78- _first_docstring_paragraph (type (fn ).__call__ )
79- or _first_docstring_paragraph (fn )
79+ _extract_docstring_summary (type (fn ).__call__ )
80+ or _extract_docstring_summary (fn )
8081 or type (fn ).__name__
8182 )
8283
83- return _first_docstring_paragraph (fn ) or fn .__name__
84+ return _extract_docstring_summary (fn ) or fn .__name__
8485
8586
8687def build_function_json_schema (fn : Callable [..., Any ]) -> dict [str , Any ]:
@@ -92,11 +93,10 @@ def build_function_json_schema(fn: Callable[..., Any]) -> dict[str, Any]:
9293 Falls back to an empty object schema on any introspection failure.
9394
9495 ``self``, ``cls``, ``*args``, and ``**kwargs`` are excluded.
96+ Positional-only params are rejected because tool callables must accept
97+ keyword arguments matching the generated schema.
9598 For ``functools.partial``, only the remaining free parameters appear.
9699 """
97- if inspect .isbuiltin (fn ):
98- return _EMPTY_OBJECT_SCHEMA
99-
100100 # Partials: sig from partial (bound args already removed), hints from inner fn.
101101 hint_source : Callable [..., Any ] = fn
102102 if isinstance (fn , functools .partial ):
@@ -118,6 +118,17 @@ def build_function_json_schema(fn: Callable[..., Any]) -> dict[str, Any]:
118118 for param_name , param in sig .parameters .items ():
119119 if param_name in _SKIP_PARAMS :
120120 continue
121+ if param .kind is inspect .Parameter .POSITIONAL_ONLY :
122+ # Auto-generated tool schemas describe named JSON object fields,
123+ # and tool frameworks invoke the callable with keyword arguments
124+ # derived from those fields. A positional-only parameter cannot be
125+ # satisfied by that contract, so fail fast with a clear error.
126+ name = getattr (fn , "__name__" , type (fn ).__name__ )
127+ raise ValueError (
128+ f"Cannot build a tool schema for { name } : "
129+ f"parameter { param_name !r} is positional-only. "
130+ "Tool parameters must be callable by keyword."
131+ )
121132 if param .kind in (inspect .Parameter .VAR_POSITIONAL , inspect .Parameter .VAR_KEYWORD ):
122133 continue
123134
0 commit comments