diff --git a/README.md b/README.md index ec9bfbd..8d1fab1 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,27 @@ Synthesizes a Python function described in the prompt. (3, 2) ``` +### `davinci_functions.explain` + +Describes the behavior of given functions. + +```python +>>> def f(x): +... return x * 3 +... +>>> davinci_functions.explain(f) +'This function takes a variable x and multiplies it by 3, then returns the result.' +>>> def f(a, b, c): +... return (-b + math.sqrt(b**2 - 4.0 * a * c)) / (2.0 * a) +... +>>> davinci_functions.explain(f) +'This function implements the Quadratic Formula to calculate the solution of a ... + quadratic equation. The equation is of the form ax^2 + bx + c = 0. The function ... + takes three parameters a, b, and c, which are the coefficients of the equation. It ... + then calculates the solution using the formula (-b + sqrt(b^2 - 4ac)) / (2a) and ... + returns the result.' +``` + ## Caveats diff --git a/pyproject.toml b/pyproject.toml index 5502aaa..ce46616 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ + "dill>=0.3.6", "openai>=0.27.0", ] dynamic = [ diff --git a/src/davinci_functions/__init__.py b/src/davinci_functions/__init__.py index 47d8d61..3032e34 100644 --- a/src/davinci_functions/__init__.py +++ b/src/davinci_functions/__init__.py @@ -9,11 +9,13 @@ except Exception: __version__ = "" +from davinci_functions._explain import explain from davinci_functions._function import function from davinci_functions._judge import judge from davinci_functions._list import list __all__ = [ + "explain", "function", "judge", "list", diff --git a/src/davinci_functions/_explain.py b/src/davinci_functions/_explain.py new file mode 100644 index 0000000..1712f45 --- /dev/null +++ b/src/davinci_functions/_explain.py @@ -0,0 +1,68 @@ +"""implementation of `explain` function.""" + +from __future__ import annotations + +import textwrap +from collections.abc import Callable +from typing import Any + +import dill # type: ignore[import] +import openai + + +def explain(fn: Callable[..., Any]) -> str: + """Describes the behavior of the given function. + + Args: + fn: A callable. The corresponding source code must be associated to this object. + i.e., inspect.getsource(fn) should work. + + Returns: + The description of `fn` written by GPT. + + Example: + >>> def my_function(x): + ... if x == 0: + ... return 1.0 + ... else: + ... return math.sin(x) / x + ... + >>> f = davinci_functions.explain(my_function) + 'This function takes a single argument x and returns a float value. If x is ... + 0, it returns 1.0, otherwise it returns the result of the sine of x divided ... + by x. This is known as the sinc function.' + """ + src = dill.source.getsource(fn) + base_prompt = textwrap.dedent( + """\ + Describe the detailed behavior of the following Python function. + The question may have multiple lines. + It is written between "BEGIN_QUESTION" and "END_QUESTION". + The answer is written after "ANSWER". + The result may have multiple lines. + It is recommended to write as detailed explanation as possible. + If the algorithm written in the function has some contexts, describe it as well. + For example, if the algorithm has a name, it should be written in the answer. + + BEGIN_QUESTION + def sum(a, b): + return a + b + END_QUESTION + + ANSWER + This function adds two variables a and b and returns its result. + + BEGIN_QUESTION + {} + END_QUESTION + + ANSWER + """ + ) + + return openai.Completion.create( # type: ignore[no-any-return, no-untyped-call] + model="text-davinci-003", + prompt=base_prompt.format(src), + max_tokens=2048, + temperature=0, + )["choices"][0]["text"] diff --git a/src/davinci_functions/_explain_test.py b/src/davinci_functions/_explain_test.py new file mode 100644 index 0000000..f2f60b7 --- /dev/null +++ b/src/davinci_functions/_explain_test.py @@ -0,0 +1,19 @@ +"""Tests for davinci_functions._explain.""" + +from __future__ import annotations + +import pytest_mock + +from davinci_functions._explain import explain + + +def test_explain(mocker: pytest_mock.MockerFixture) -> None: + mocker.patch( + "openai.Completion.create", + return_value={"choices": [{"text": "This is a test."}]}, + ) + + def dummy() -> int: + return 42 + + assert explain(dummy) == "This is a test." diff --git a/src/davinci_functions/_function.py b/src/davinci_functions/_function.py index 699ed2b..15ad98a 100644 --- a/src/davinci_functions/_function.py +++ b/src/davinci_functions/_function.py @@ -1,4 +1,4 @@ -"""implementation of `list` function.""" +"""implementation of `function` function.""" from __future__ import annotations @@ -24,7 +24,8 @@ def function(prompt: str) -> Callable[..., Any]: A callable object that may represent the behavior of the prompt. Raises: - SyntaxError: GPT didn't return a Python literal. + SyntaxError: GPT didn't return a meaningful Python program. + TypeError: GPT didn't return a Python callable. Example: >>> f = davinci_functions.list("Multiply the argument x by 2.") diff --git a/src/davinci_functions/_function_test.py b/src/davinci_functions/_function_test.py index 629458f..7a49306 100644 --- a/src/davinci_functions/_function_test.py +++ b/src/davinci_functions/_function_test.py @@ -8,7 +8,7 @@ from davinci_functions._function import function -def test_judge_true(mocker: pytest_mock.MockerFixture) -> None: +def test_function(mocker: pytest_mock.MockerFixture) -> None: mocker.patch( "openai.Completion.create", return_value={"choices": [{"text": "def func(x):\n return x * 2"}]}, @@ -19,7 +19,7 @@ def test_judge_true(mocker: pytest_mock.MockerFixture) -> None: assert f(3) == 6 -def test_judge_non_python(mocker: pytest_mock.MockerFixture) -> None: +def test_function_non_python(mocker: pytest_mock.MockerFixture) -> None: mocker.patch( "openai.Completion.create", return_value={"choices": [{"text": "Error."}]}, @@ -29,7 +29,7 @@ def test_judge_non_python(mocker: pytest_mock.MockerFixture) -> None: function("This is a test.") -def test_judge_non_function(mocker: pytest_mock.MockerFixture) -> None: +def test_function_non_function(mocker: pytest_mock.MockerFixture) -> None: mocker.patch( "openai.Completion.create", return_value={"choices": [{"text": "func = 42"}]},