├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── check.sh ├── pyproject.toml └── src └── davinci_functions ├── __init__.py ├── _explain.py ├── _explain_test.py ├── _function.py ├── _function_test.py ├── _judge.py ├── _judge_test.py ├── _list.py ├── _list_test.py └── _version.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: ["**"] 9 | 10 | jobs: 11 | unit-tests: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.10", "3.11"] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install -e ".[dev]" 26 | - name: Test 27 | run: python -m pytest src 28 | 29 | black: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Set up Python 34 | uses: actions/setup-python@v4 35 | with: 36 | python-version: "3.x" 37 | - name: Install dependencies 38 | run: | 39 | python -m pip install --upgrade pip 40 | python -m pip install black 41 | - name: Check 42 | run: python -m black -v --check src 43 | 44 | flake8: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | - name: Set up Python 49 | uses: actions/setup-python@v4 50 | with: 51 | python-version: "3.x" 52 | - name: Install dependencies 53 | run: | 54 | python -m pip install --upgrade pip 55 | python -m pip install pyproject-flake8 56 | - name: Check 57 | run: pflake8 -v src 58 | 59 | isort: 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v3 63 | - name: Set up Python 64 | uses: actions/setup-python@v4 65 | with: 66 | python-version: "3.x" 67 | - name: Install dependencies 68 | run: | 69 | python -m pip install --upgrade pip 70 | python -m pip install isort 71 | - name: Check 72 | run: python -m isort --check src 73 | 74 | mypy: 75 | runs-on: ubuntu-latest 76 | steps: 77 | - uses: actions/checkout@v3 78 | - name: Set up Python 79 | uses: actions/setup-python@v4 80 | with: 81 | python-version: "3.x" 82 | - name: Install dependencies 83 | run: | 84 | python -m pip install --upgrade pip 85 | python -m pip install '.[mypy]' 86 | - name: Check 87 | run: python -m mypy src 88 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release workflow 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0123456789].*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@v3 14 | - name: setup python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: "3.x" 18 | - name: build 19 | run: | 20 | python -m pip install --upgrade build hatch 21 | python -m hatch version "${GITHUB_REF_NAME}" 22 | python -m build 23 | - name: publish 24 | uses: pypa/gh-action-pypi-publish@release/v1 25 | with: 26 | password: ${{ secrets.PYPI_API_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yusuke Oda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # davinci-functions 2 | Library to ask OpenAI GPT for generating objects on the Python runtime. 3 | 4 | This library is prepared to record prompts that would be useful for Python programs. 5 | If you developed something, let's make a pull request! 6 | 7 | 8 | ## Getting started 9 | 10 | ### Installation 11 | 12 | To install `davinci-functions`, you can simply type the following command: 13 | 14 | ```shell 15 | pip instal davinci-functions 16 | ``` 17 | 18 | ### Using the library 19 | 20 | Set your OpenAI Organization ID and API key before using this library. 21 | Then invoke the functions in the library. 22 | 23 | ```python 24 | import openai 25 | import davinci_functions 26 | 27 | openai.organization = "YOUR_ORG_ID" 28 | openai.api_key = "YOUR_API_KEY" 29 | 30 | prompt = """\ 31 | Output the list of 10 random city names in the United States. 32 | """ 33 | 34 | for val in davinci_functions.list(prompt): 35 | print(val) 36 | ``` 37 | 38 | This script will print something like: 39 | 40 | ``` 41 | New York 42 | Los Angeles 43 | Chicago 44 | Houston 45 | Phoenix 46 | Philadelphia 47 | San Antonio 48 | San Diego 49 | Dallas 50 | San Jose 51 | ``` 52 | 53 | 54 | ## References 55 | 56 | ### `davinci_functions.list` 57 | 58 | Returns the list of something. 59 | 60 | ```python 61 | >>> davinci_functions.list("say hello.") 62 | ['Hello'] 63 | >>> davinci_functions.list("say hello world.") 64 | ['Hello', 'world'] 65 | >>> davinci_functions.list("Output first 5 prime numbers.") 66 | [2, 3, 5, 7, 11] 67 | >>> davinci_functions.list("5 random countries") 68 | ['Japan', 'Australia', 'Brazil', 'India', 'China'] 69 | ``` 70 | 71 | Solving some tasks (e.g., named entity recognition): 72 | 73 | ```python 74 | >>> prompt=""" 75 | ... Extract all named entities in the following paragraph: 76 | ... 77 | ... Google is founded by Larry Page and Sergey Brin in 1998. 78 | ... The headquarter is located in Mountain View, Carifornia, United States. 79 | ... """ 80 | >>> davinci_functions.list(prompt) 81 | ['Google', 'Larry Page', 'Sergey Brin', 'Mountain View', 'Carifornia', 'United States'] 82 | ``` 83 | 84 | Other language (Japanese): 85 | 86 | ```python 87 | >>> davinci_functions.list("日本語の単語を5個") 88 | ['日本語', '単語', '文字', '言葉', '意味'] 89 | >>> davinci_functions.list("1から10までの数字のリスト。ただし3で割り切れるときはFizzにしてください。") 90 | [1, 2, 'Fizz', 4, 5, 'Fizz', 7, 8, 'Fizz', 10] 91 | >>> davinci_functions.list("「明日は明日の風が吹く」の形態素の一覧") 92 | ['明日', 'は', '明日', 'の', '風', 'が', '吹く'] 93 | ``` 94 | 95 | ### `davinci_functions.judge` 96 | 97 | Returns the truth of something. 98 | 99 | ```python 100 | >>> davinci_functions.judge("The sum of 2 and 3 is 5.") 101 | True 102 | >>> davinci_functions.judge("The sum of 2 and 3 is 6.") 103 | False 104 | >>> davinci_functions.judge("San Francisco is the capital of the United States.") 105 | False 106 | >>> davinci_functions.judge("New York is the capital of the United States.") 107 | True # Wrong answer! This kind of mistakes happens very often: please take care. 108 | >>> davinci_functions.judge("Washington D.C. is the capital of the United States. How about New York?") 109 | False 110 | ``` 111 | 112 | ### `davinci_functions.function` 113 | 114 | Synthesizes a Python function described in the prompt. 115 | 116 | **This function is not secure. Do not use this function in real products.** 117 | 118 | ```python 119 | >>> f = davinci_functions.function("Multiply the argument x by 2.") 120 | >>> f(3) 121 | 6 122 | >>> f = davinci_functions.function("Arithmetic mean of all elements in the list x.") 123 | >>> f([1, 2, 3]) 124 | 2.0 125 | >>> f = davinci_functions.function("""\ 126 | ... Given a list of unique integers x, return two positions so that the sum of the 127 | ... numbers on that positions is equal to the argument y. 128 | ... The function must be efficient as well as possible. 129 | ... """) 130 | >>> f([1, 4, 9, 16, 25], 25) 131 | (3, 2) 132 | ``` 133 | 134 | ### `davinci_functions.explain` 135 | 136 | Describes the behavior of given functions. 137 | 138 | ```python 139 | >>> def f(x): 140 | ... return x * 3 141 | ... 142 | >>> davinci_functions.explain(f) 143 | 'This function takes a variable x and multiplies it by 3, then returns the result.' 144 | >>> def f(a, b, c): 145 | ... return (-b + math.sqrt(b**2 - 4.0 * a * c)) / (2.0 * a) 146 | ... 147 | >>> davinci_functions.explain(f) 148 | 'This function implements the Quadratic Formula to calculate the solution of a ... 149 | quadratic equation. The equation is of the form ax^2 + bx + c = 0. The function ... 150 | takes three parameters a, b, and c, which are the coefficients of the equation. It ... 151 | then calculates the solution using the formula (-b + sqrt(b^2 - 4ac)) / (2a) and ... 152 | returns the result.' 153 | ``` 154 | 155 | 156 | ## Caveats 157 | 158 | Right now, this library doesn't consider prompt injection and validity of the returned 159 | expression by GPT. Please don't use this library in the real products that needs to 160 | take care about consistency and security. 161 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: 4 | # On the root directory of the repository: 5 | # $ python3 -m venv venv 6 | # $ source venv/bin/activate 7 | # $ python -m pip install '.[dev]' 8 | # $ ./check.sh 9 | 10 | GREEN='\033[1;32m' 11 | RED='\033[1;31m' 12 | NC='\033[0m' 13 | CLEAR='\033[2K\r' 14 | 15 | run() { 16 | echo -n RUNNING: $@ 17 | LOG_FILE=$(mktemp) 18 | $@ >${LOG_FILE} 2>${LOG_FILE} 19 | if [ $? -eq 0 ]; then 20 | echo -e ${CLEAR}${GREEN}PASSED: $@${NC} 21 | else 22 | echo -e ${CLEAR}${RED}FAILED: $@${NC} 23 | echo 24 | cat ${LOG_FILE} 25 | echo 26 | fi 27 | rm ${LOG_FILE} 28 | } 29 | 30 | run python -m pytest src -vv 31 | run python -m black --check src 32 | run python -m pflake8 src 33 | run python -m isort --check src 34 | run python -m mypy src 35 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "hatchling", 4 | ] 5 | build-backend = "hatchling.build" 6 | 7 | [project] 8 | name = "davinci-functions" 9 | description = "Library to ask OpenAI GPT for generating objects on the Python runtime." 10 | readme = "README.md" 11 | requires-python = ">=3.8, <3.12" 12 | license = {text = "MIT License"} 13 | authors = [ 14 | {name = "Yusuke Oda", email = "odashi@inspiredco.ai"} 15 | ] 16 | keywords = [ 17 | "cloud storage", 18 | "gcloud", 19 | "google", 20 | "google cloud", 21 | "storage", 22 | ] 23 | classifiers = [ 24 | "License :: OSI Approved :: MIT License", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | ] 30 | dependencies = [ 31 | "dill>=0.3.6", 32 | "openai>=0.27.0", 33 | ] 34 | dynamic = [ 35 | "version" 36 | ] 37 | 38 | [project.optional-dependencies] 39 | dev = [ 40 | "black>=22.10", 41 | "flake8>=5.0", 42 | "isort>=5.10", 43 | "mypy>=0.991", 44 | "pyproject-flake8>=5.0", 45 | "pytest>=7.1", 46 | "pytest-mock>=3.10.0", 47 | ] 48 | mypy = [ 49 | "mypy>=0.991", 50 | "pytest>=7.1", 51 | "pytest-mock>=3.10.0", 52 | ] 53 | 54 | [project.urls] 55 | Homepage = "https://github.com/odashi/davinci-functions" 56 | "Bug Tracker" = "https://github.com/odashi/davinci-functions/issues" 57 | 58 | [tool.hatch.build] 59 | include = [ 60 | "*.py", 61 | ] 62 | exclude = [ 63 | "*_test.py", 64 | ] 65 | only-packages = true 66 | 67 | [tool.hatch.build.targets.wheel] 68 | packages = ["src/davinci_functions"] 69 | 70 | [tool.hatch.version] 71 | path = "src/davinci_functions/_version.py" 72 | 73 | [tool.flake8] 74 | max-line-length = 88 75 | extend-ignore = "E203" 76 | 77 | [tool.isort] 78 | profile = "black" 79 | 80 | [tool.mypy] 81 | strict = true 82 | -------------------------------------------------------------------------------- /src/davinci_functions/__init__.py: -------------------------------------------------------------------------------- 1 | """Package definition.""" 2 | 3 | from __future__ import annotations 4 | 5 | try: 6 | from davinci_functions import _version 7 | 8 | __version__ = _version.__version__ 9 | except Exception: 10 | __version__ = "" 11 | 12 | from davinci_functions._explain import explain 13 | from davinci_functions._function import function 14 | from davinci_functions._judge import judge 15 | from davinci_functions._list import list 16 | 17 | __all__ = [ 18 | "explain", 19 | "function", 20 | "judge", 21 | "list", 22 | ] 23 | -------------------------------------------------------------------------------- /src/davinci_functions/_explain.py: -------------------------------------------------------------------------------- 1 | """implementation of `explain` function.""" 2 | 3 | from __future__ import annotations 4 | 5 | import textwrap 6 | from collections.abc import Callable 7 | from typing import Any 8 | 9 | import dill # type: ignore[import] 10 | import openai 11 | 12 | 13 | def explain(fn: Callable[..., Any]) -> str: 14 | """Describes the behavior of the given function. 15 | 16 | Args: 17 | fn: A callable. The corresponding source code must be associated to this object. 18 | i.e., inspect.getsource(fn) should work. 19 | 20 | Returns: 21 | The description of `fn` written by GPT. 22 | 23 | Example: 24 | >>> def my_function(x): 25 | ... if x == 0: 26 | ... return 1.0 27 | ... else: 28 | ... return math.sin(x) / x 29 | ... 30 | >>> f = davinci_functions.explain(my_function) 31 | 'This function takes a single argument x and returns a float value. If x is ... 32 | 0, it returns 1.0, otherwise it returns the result of the sine of x divided ... 33 | by x. This is known as the sinc function.' 34 | """ 35 | src = dill.source.getsource(fn) 36 | base_prompt = textwrap.dedent( 37 | """\ 38 | Describe the detailed behavior of the following Python function. 39 | The question may have multiple lines. 40 | It is written between "BEGIN_QUESTION" and "END_QUESTION". 41 | The answer is written after "ANSWER". 42 | The result may have multiple lines. 43 | It is recommended to write as detailed explanation as possible. 44 | If the algorithm written in the function has some contexts, describe it as well. 45 | For example, if the algorithm has a name, it should be written in the answer. 46 | 47 | BEGIN_QUESTION 48 | def sum(a, b): 49 | return a + b 50 | END_QUESTION 51 | 52 | ANSWER 53 | This function adds two variables a and b and returns its result. 54 | 55 | BEGIN_QUESTION 56 | {} 57 | END_QUESTION 58 | 59 | ANSWER 60 | """ 61 | ) 62 | 63 | return openai.Completion.create( # type: ignore[no-any-return, no-untyped-call] 64 | model="text-davinci-003", 65 | prompt=base_prompt.format(src), 66 | max_tokens=2048, 67 | temperature=0, 68 | )["choices"][0]["text"] 69 | -------------------------------------------------------------------------------- /src/davinci_functions/_explain_test.py: -------------------------------------------------------------------------------- 1 | """Tests for davinci_functions._explain.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest_mock 6 | 7 | from davinci_functions._explain import explain 8 | 9 | 10 | def test_explain(mocker: pytest_mock.MockerFixture) -> None: 11 | mocker.patch( 12 | "openai.Completion.create", 13 | return_value={"choices": [{"text": "This is a test."}]}, 14 | ) 15 | 16 | def dummy() -> int: 17 | return 42 18 | 19 | assert explain(dummy) == "This is a test." 20 | -------------------------------------------------------------------------------- /src/davinci_functions/_function.py: -------------------------------------------------------------------------------- 1 | """implementation of `function` function.""" 2 | 3 | from __future__ import annotations 4 | 5 | import textwrap 6 | from collections.abc import Callable 7 | from typing import Any 8 | 9 | import openai 10 | 11 | 12 | def function(prompt: str) -> Callable[..., Any]: 13 | """Synthesizes a function by asking GPT to write it. 14 | 15 | This function is not secure as it calls `exec` internally. 16 | Please don't use this function in the real product. 17 | 18 | Args: 19 | prompt: The prompt describing the behavior of the function. 20 | Users basically don't need to write a complete prompt to describe the task 21 | that the GPT solves. 22 | 23 | Returns: 24 | A callable object that may represent the behavior of the prompt. 25 | 26 | Raises: 27 | SyntaxError: GPT didn't return a meaningful Python program. 28 | TypeError: GPT didn't return a Python callable. 29 | 30 | Example: 31 | >>> f = davinci_functions.list("Multiply the argument x by 2.") 32 | >>> f(3) 33 | 6 34 | """ 35 | 36 | base_prompt = textwrap.dedent( 37 | """\ 38 | Write a single Python function that processes the following task. 39 | You must output only a single function definition in Python. 40 | You must not output examples, description, remarks, 41 | or anything else that are not related to the function itself. 42 | The name of the function must be "func". 43 | The function must have positional arguments if it is specified in the question. 44 | The function may return some values if necessary. 45 | 46 | QUESTION: 47 | The sum of two integer arguments x and y. 48 | 49 | ANSWER: 50 | def func(x, y): 51 | return x + y 52 | 53 | QUESTION: 54 | {} 55 | 56 | ANSWER: 57 | """ 58 | ) 59 | 60 | response = openai.Completion.create( # type: ignore[no-untyped-call] 61 | model="text-davinci-003", 62 | prompt=base_prompt.format(prompt), 63 | max_tokens=2048, 64 | temperature=0, 65 | ) 66 | src = response["choices"][0]["text"] 67 | 68 | try: 69 | locals: dict[str, Any] = {} 70 | exec(src, globals(), locals) 71 | func = locals["func"] 72 | except SyntaxError: 73 | raise SyntaxError(f"GPT didn't return a meaningful Python program. {src=}") 74 | 75 | if not callable(func): 76 | raise TypeError(f"GPT didn't return a Python Callable. {src=}") 77 | 78 | return func # type: ignore[no-any-return] 79 | -------------------------------------------------------------------------------- /src/davinci_functions/_function_test.py: -------------------------------------------------------------------------------- 1 | """Tests for davinci_functions._function.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_mock 7 | 8 | from davinci_functions._function import function 9 | 10 | 11 | def test_function(mocker: pytest_mock.MockerFixture) -> None: 12 | mocker.patch( 13 | "openai.Completion.create", 14 | return_value={"choices": [{"text": "def func(x):\n return x * 2"}]}, 15 | ) 16 | 17 | f = function("This is a test.") 18 | assert callable(f) 19 | assert f(3) == 6 20 | 21 | 22 | def test_function_non_python(mocker: pytest_mock.MockerFixture) -> None: 23 | mocker.patch( 24 | "openai.Completion.create", 25 | return_value={"choices": [{"text": "Error."}]}, 26 | ) 27 | 28 | with pytest.raises(SyntaxError, match=r"^GPT didn't return a meaningful Python"): 29 | function("This is a test.") 30 | 31 | 32 | def test_function_non_function(mocker: pytest_mock.MockerFixture) -> None: 33 | mocker.patch( 34 | "openai.Completion.create", 35 | return_value={"choices": [{"text": "func = 42"}]}, 36 | ) 37 | 38 | with pytest.raises(TypeError, match=r"^GPT didn't return a Python Callable"): 39 | function("This is a test.") 40 | -------------------------------------------------------------------------------- /src/davinci_functions/_judge.py: -------------------------------------------------------------------------------- 1 | """implementation of `judge` function.""" 2 | 3 | from __future__ import annotations 4 | 5 | import ast 6 | import textwrap 7 | 8 | import openai 9 | 10 | 11 | def judge(prompt: str) -> bool: 12 | """Obtains a judgment of something from GPT. 13 | 14 | Args: 15 | prompt: The prompt describing the question. 16 | Users basically don't need to write a complete prompt to describe the task 17 | that the GPT solves. 18 | 19 | Returns: 20 | A Boolean representing the truth of the given prompt. 21 | Since this is the result of an LLM, the value may not be correct in many cases. 22 | 23 | Raises: 24 | RuntimeError: Something went wrong. 25 | 26 | Example: 27 | >>> davinci_functions.judge( 28 | ... "San Francisco is the capital of the United States." 29 | ... ) 30 | False 31 | """ 32 | 33 | base_prompt = textwrap.dedent( 34 | """\ 35 | Complete the following task. 36 | You must write a single answer to follow the last question. 37 | Do not output anything else, including programs, description, and remarks. 38 | The output must be a Boolean literal in Python: True or False. 39 | 40 | QUESTION: 41 | The sum of 2 and 3 is 5. 42 | 43 | ANSWER: 44 | True 45 | 46 | QUESTION: 47 | Antarctica is the largest continent in the world. 48 | 49 | ANSWER: 50 | False 51 | 52 | QUESTION: 53 | {} 54 | 55 | ANSWER: 56 | """ 57 | ) 58 | 59 | response = openai.Completion.create( # type: ignore[no-untyped-call] 60 | model="text-davinci-003", 61 | prompt=base_prompt.format(prompt), 62 | max_tokens=1024, 63 | temperature=0, 64 | ) 65 | text = response["choices"][0]["text"] 66 | 67 | try: 68 | answer = ast.literal_eval(text) 69 | except SyntaxError: 70 | raise SyntaxError(f"GPT didn't return a Python literal. {text=}") 71 | 72 | if not isinstance(answer, bool): 73 | raise TypeError(f"GPT didn't return a Python Boolean. {text=}") 74 | 75 | return answer 76 | -------------------------------------------------------------------------------- /src/davinci_functions/_judge_test.py: -------------------------------------------------------------------------------- 1 | """Tests for davinci_functions._judge.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_mock 7 | 8 | from davinci_functions._judge import judge 9 | 10 | 11 | def test_judge_true(mocker: pytest_mock.MockerFixture) -> None: 12 | mocker.patch( 13 | "openai.Completion.create", 14 | return_value={"choices": [{"text": "True"}]}, 15 | ) 16 | 17 | assert judge("This is a test.") is True 18 | 19 | 20 | def test_judge_false(mocker: pytest_mock.MockerFixture) -> None: 21 | mocker.patch( 22 | "openai.Completion.create", 23 | return_value={"choices": [{"text": "False"}]}, 24 | ) 25 | 26 | assert judge("This is a test.") is False 27 | 28 | 29 | def test_judge_non_python(mocker: pytest_mock.MockerFixture) -> None: 30 | mocker.patch( 31 | "openai.Completion.create", 32 | return_value={"choices": [{"text": "Error."}]}, 33 | ) 34 | 35 | with pytest.raises(SyntaxError, match=r"^GPT didn't return a Python literal"): 36 | judge("This is a test.") 37 | 38 | 39 | def test_judge_non_bool(mocker: pytest_mock.MockerFixture) -> None: 40 | mocker.patch( 41 | "openai.Completion.create", 42 | return_value={"choices": [{"text": "[1, 2, 3]"}]}, 43 | ) 44 | 45 | with pytest.raises(TypeError, match=r"^GPT didn't return a Python Boolean"): 46 | judge("This is a test.") 47 | -------------------------------------------------------------------------------- /src/davinci_functions/_list.py: -------------------------------------------------------------------------------- 1 | """implementation of `list` function.""" 2 | 3 | from __future__ import annotations 4 | 5 | import ast 6 | import builtins 7 | import textwrap 8 | from typing import Any 9 | 10 | import openai 11 | 12 | 13 | def list(prompt: str) -> builtins.list[Any]: 14 | """Obtains a list of something from GPT. 15 | 16 | Args: 17 | prompt: The prompt describing the values. 18 | Users basically don't need to write a complete prompt to describe the task 19 | that the GPT solves. 20 | 21 | Returns: 22 | A list of something you described in the prompt. 23 | Since this is the result of an LLM, the value may not be correct in many cases. 24 | 25 | Raises: 26 | SyntaxError: GPT didn't return a Python literal. 27 | TypeError: GPT didn't return a Python list. 28 | 29 | Example: 30 | >>> davinci_functions.list("5 random countries") 31 | ['Japan', 'Australia', 'Brazil', 'India', 'China'] 32 | """ 33 | 34 | base_prompt = textwrap.dedent( 35 | """\ 36 | Complete the following task. 37 | You must write a single answer to follow the last question. 38 | Do not output anything else, including programs, description, and remarks. 39 | The output must be a Python list of literals. 40 | 41 | QUESTION: 42 | The first 5 positive integers. 43 | 44 | ANSWER: 45 | [1, 2, 3, 4, 5] 46 | 47 | QUESTION: 48 | List of 5 random English nouns starting with "a". 49 | 50 | ANSWER: 51 | ["apple", "alphabet", "ankle", "ant", "art"] 52 | 53 | QUESTION: 54 | {} 55 | 56 | ANSWER: 57 | """ 58 | ) 59 | 60 | response = openai.Completion.create( # type: ignore[no-untyped-call] 61 | model="text-davinci-003", 62 | prompt=base_prompt.format(prompt), 63 | max_tokens=1024, 64 | temperature=0, 65 | ) 66 | text = response["choices"][0]["text"] 67 | 68 | try: 69 | answer = ast.literal_eval(text) 70 | except SyntaxError: 71 | raise SyntaxError(f"GPT didn't return a Python literal. {text=}") 72 | 73 | if not isinstance(answer, builtins.list): 74 | raise TypeError(f"GPT didn't return a Python list. {text=}") 75 | 76 | return answer 77 | -------------------------------------------------------------------------------- /src/davinci_functions/_list_test.py: -------------------------------------------------------------------------------- 1 | """Tests for davinci_functions._list.""" 2 | 3 | from __future__ import annotations 4 | 5 | import pytest 6 | import pytest_mock 7 | 8 | from davinci_functions._list import list 9 | 10 | 11 | def test_list_integers(mocker: pytest_mock.MockerFixture) -> None: 12 | mocker.patch( 13 | "openai.Completion.create", 14 | return_value={"choices": [{"text": "[1, 2, 3]"}]}, 15 | ) 16 | 17 | assert list("This is a test.") == [1, 2, 3] 18 | 19 | 20 | def test_list_strings(mocker: pytest_mock.MockerFixture) -> None: 21 | mocker.patch( 22 | "openai.Completion.create", 23 | return_value={"choices": [{"text": "['foo', 'bar', 'baz']"}]}, 24 | ) 25 | 26 | assert list("This is a test.") == ["foo", "bar", "baz"] 27 | 28 | 29 | def test_list_mixture(mocker: pytest_mock.MockerFixture) -> None: 30 | mocker.patch( 31 | "openai.Completion.create", 32 | return_value={"choices": [{"text": "['foo', 123, True]"}]}, 33 | ) 34 | 35 | assert list("This is a test.") == ["foo", 123, True] 36 | 37 | 38 | def test_list_non_python(mocker: pytest_mock.MockerFixture) -> None: 39 | mocker.patch( 40 | "openai.Completion.create", 41 | return_value={"choices": [{"text": "Error."}]}, 42 | ) 43 | 44 | with pytest.raises(SyntaxError, match=r"^GPT didn't return a Python literal"): 45 | list("This is a test.") 46 | 47 | 48 | def test_list_non_list(mocker: pytest_mock.MockerFixture) -> None: 49 | mocker.patch( 50 | "openai.Completion.create", 51 | return_value={"choices": [{"text": "True"}]}, 52 | ) 53 | 54 | with pytest.raises(TypeError, match=r"^GPT didn't return a Python list"): 55 | list("This is a test.") 56 | -------------------------------------------------------------------------------- /src/davinci_functions/_version.py: -------------------------------------------------------------------------------- 1 | """Version specifier. 2 | 3 | DON'T TOUCH THIS FILE. 4 | This file is replaced during the release process. 5 | """ 6 | __version__ = "0.0.0a0" 7 | --------------------------------------------------------------------------------