Skip to content

Commit

Permalink
Docstring parsing (#83)
Browse files Browse the repository at this point in the history
* adds description for params

* add test for missing param description

* adds docstring parsing to OpenAISchema

* docs

* fix tests, highlighting

---------

Co-authored-by: Jason Liu <[email protected]>
  • Loading branch information
AIexanderDicke and jxnl authored Aug 24, 2023
1 parent 64e7f51 commit 6d0a1a0
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 1,738 deletions.
34 changes: 24 additions & 10 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,39 +130,49 @@ from instructor import OpenAISchema
from pydantic import Field

class UserDetails(OpenAISchema):
"Correctly extracted user information"
""""
Correctly extracted user information
:param age: age of the user
"""
name: str = Field(..., description="User's full name")
age: int
```

In this updated schema, we use the `Field` class from `pydantic` to add descriptions to the `name` field. The description provides information about the field, giving even more context to the language model.

In this updated schema, we use the `Field` class from `pydantic` to add descriptions to the `name` field. Moreover, we use the docstring to add information for the parameter `age`.
In both cases, the description provides information about the fields, giving even more context to the language model.
Information from the docstring is extracted using [docstring-parser](https://github.com/rr-/docstring_parser) which supports different docstring styles.
Note that if the `Field` contains a description for a parameter as well as the docstring, the `Field`'s description is used.

!!! note "Code, schema, and prompt"
We can run `openai_schema` to see exactly what the API will see, notice how the docstrings, attributes, types, and field descriptions are now part of the schema. This describes on this library's core philosophies.
We can run `openai_schema` to see exactly what the API will see, notice how the docstrings, attributes, types, and parameter descriptions are now part of the schema. This describes on this library's core philosophies.

```python hl_lines="2 3"
```python hl_lines="3 5 7"
class UserDetails(OpenAISchema):
"Correctly extracted user information"
"""
Correctly extracted user information
:param name: the user's full name
:param age: age of the user
"""
name: str = Field(..., description="User's full name")
age: int

UserDetails.openai_schema
```

```json hl_lines="3 8"
```json hl_lines="3 9 13"
{
"name": "UserDetails",
"description": "Correctly extracted user information",
"parameters": {
"type": "object",
"properties": {
"name": {
"description": "User's full name",
"type": "string"
"type": "string",
"description": "User's full name"
},
"age": {
"type": "integer"
"description": "age of the user"
}
},
"required": [
Expand All @@ -182,7 +192,11 @@ from instructor import OpenAISchema
from pydantic import Field

class UserDetails(OpenAISchema):
"Correctly extracted user information"
"""
Correctly extracted user information
:param name: the user's full name
:param age: age of the user
"""
name: str = Field(..., description="User's full name")
age: int

Expand Down
22 changes: 18 additions & 4 deletions instructor/function_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# SOFTWARE.

import json
from docstring_parser import parse
from functools import wraps
from typing import Any, Callable
from pydantic import BaseModel, validate_arguments
Expand Down Expand Up @@ -65,20 +66,25 @@ def sum(a: int, b: int) -> int:
def __init__(self, func: Callable) -> None:
self.func = func
self.validate_func = validate_arguments(func)
self.docstring = parse(self.func.__doc__)

parameters = self.validate_func.model.model_json_schema()
parameters["properties"] = {
k: v
for k, v in parameters["properties"].items()
if k not in ("v__duplicate_kwargs", "args", "kwargs")
}
for param in self.docstring.params:
if (name := param.arg_name) in parameters["properties"] and (description := param.description):
parameters["properties"][name]["description"] = description
parameters["required"] = sorted(
k for k, v in parameters["properties"].items() if not "default" in v
)
_remove_a_key(parameters, "additionalProperties")
_remove_a_key(parameters, "title")
self.openai_schema = {
"name": self.func.__name__,
"description": self.func.__doc__,
"description": self.docstring.short_description,
"parameters": parameters,
}
self.model = self.validate_func.model
Expand Down Expand Up @@ -128,17 +134,25 @@ def openai_schema(cls):
model_json_schema (dict): A dictionary in the format of OpenAI's schema as jsonschema
"""
schema = cls.model_json_schema()
docstring = parse(cls.__doc__)
parameters = {
k: v for k, v in schema.items() if k not in ("title", "description")
}
for param in docstring.params:
if (name := param.arg_name) in parameters["properties"] and (description := param.description):
if "description" not in parameters["properties"][name]:
parameters["properties"][name]["description"] = description

parameters["required"] = sorted(
k for k, v in parameters["properties"].items() if not "default" in v
)

if "description" not in schema:
schema[
"description"
] = f"Correctly extracted `{cls.__name__}` with all the required parameters with correct types"
if docstring.short_description:
schema["description"] = docstring.short_description
else:
schema["description"] = f"Correctly extracted `{cls.__name__}` with all " \
f"the required parameters with correct types"

_remove_a_key(parameters, "additionalProperties")
_remove_a_key(parameters, "title")
Expand Down
Loading

0 comments on commit 6d0a1a0

Please sign in to comment.