abf-openai 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ # Keys
2
+ openai_key.txt
3
+
4
+ # Mac file
5
+ .DS_Store
6
+
7
+ # Byte-compiled / optimized / DLL files
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+
12
+ # C extensions
13
+ *.so
14
+
15
+ # Distribution / packaging
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ share/python-wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+ MANIFEST
34
+
35
+ # PyInstaller
36
+ # Usually these files are written by a python script from a template
37
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
38
+ *.manifest
39
+ *.spec
40
+
41
+ # Installer logs
42
+ pip-log.txt
43
+ pip-delete-this-directory.txt
44
+
45
+ # Unit test / coverage reports
46
+ htmlcov/
47
+ .tox/
48
+ .nox/
49
+ .coverage
50
+ .coverage.*
51
+ .cache
52
+ nosetests.xml
53
+ coverage.xml
54
+ *.cover
55
+ *.py,cover
56
+ .hypothesis/
57
+ .pytest_cache/
58
+ cover/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ .pybuilder/
82
+ target/
83
+
84
+ # Jupyter Notebook
85
+ .ipynb_checkpoints
86
+
87
+ # IPython
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # pyenv
92
+ # For a library or package, you might want to ignore these files since the code is
93
+ # intended to run in multiple environments; otherwise, check them in:
94
+ # .python-version
95
+
96
+ # pipenv
97
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
99
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
100
+ # install all needed dependencies.
101
+ #Pipfile.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Daniel Guetta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: abf_openai
3
+ Version: 0.0.1
4
+ Summary: A cached wrapper of the OpenAI library for the CBS and WSP 'AI in Finance and Business' course
5
+ Project-URL: Homepage, https://github.com/danguetta/cached_openai
6
+ Project-URL: Issues, https://github.com/danguetta/cached_openai/issues
7
+ Author-email: Daniel Guetta <daniel@guetta.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: openai>=1.66.2
14
+ Requires-Dist: requests>=2.32.2
15
+ Requires-Dist: tqdm>=4.67.1
16
+ Description-Content-Type: text/markdown
17
+
18
+ # cached_openai
19
+
20
+ `cached_openai` is a simple Python library that mimics the `OpenAI` python library, but can draw from a cache instead of sending the request to Open AI.
21
+
22
+ When it is run in **dev mode**, it caches responses to all requests made from OpenAI, and returns the cached value if the request is made again. When it is run in **production mode**, no new cache entries are created, but if any requests stored in the cache are made again, they are returned therefrom.
23
+
24
+ It is able to cache several different responses for a single request, to mimic the way the OpenAI API returns different responses when it receives the same query twice. It is also able to handle requests that return images or sound clips.
25
+
26
+ The user experience with `cached_open` is identical to that with the original `openai` package. Consider, for example, the following piece of code using the "regular" `openai` api:
27
+ ```
28
+ import openai
29
+ client = openai.OpenAI(api_key=...)
30
+ response = client.chat.completions.create(...)
31
+ ```
32
+
33
+ In `cached_openai`, the **only** change required would be to change the first line to `import cached_openai as openai`. That's it - everything else works identically. If the query is cached, the cached version will be returned. If not, the `OpenAI` API will be queries as usual.
34
+
35
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
36
+
37
+ The package also works with the async version of the OpenAI API - just use `openai.AsyncOpenAI(...)`.
38
+
39
+ ## Why `cached_openai`?
40
+
41
+ I designed this package for teaching purposes - when I teach classes that use the OpenAI API, I often provide code to students which they run on their machines before modifying it. This isn't ideal for two reasons
42
+ 1. Every student has to pay to run the API requests in the code - there can be hundreds or thousands of these requests, and the costs can add up.
43
+ 2. Every student is likely to get different answers out of the API - it makes it difficult to teach the class when everyone is looking at a different answer.
44
+
45
+ Instead, I can now distributed `cached_openai` with a cache I prepared - students can then run the entire code for free, and get the same answers as I did. They can then modify the code to run their own version, and query OpenAI as usual.
46
+
47
+ # User manual
48
+
49
+ This user manual is divided into two parts - preparing a cache, and distributing it.
50
+
51
+ ## Preparing a cache
52
+
53
+ To prepare a cache, you need to run `cached_openai` in **dev mode**. To do this, set an environment variable called `CACHED_OPENAI_DEV_MODE` to any value before you load the package. I also recommended you set an environment variable called `CACHED_OPENAI_VERBOSE` to any value to print debugging messages as the package runs. The easiest way too do this is in your code, before you import the package
54
+
55
+ ```
56
+ import os
57
+ os.environ['CACHED_OPENAI_VERBOSE'] = 'True'
58
+ os.environ['CACHED_OPENAI_DEV_MODE'] = 'True'
59
+ import cached_openai
60
+ ```
61
+
62
+ You should then use `cached_openai` exactly as you would `OpenAI` - every request you make will be cached. In dev mode, the package creates three files in your working directory
63
+ - `openai_cache_temp.bin` is a temporary cache file - it is updated every time a call is made to the OpenAI API to store the results of that call. It can be updated very quickly (so as not to slow down the call), but stores data in an inneficient format.
64
+ - `openai_cache.cache` is the main cache file which contains all cached requests in an efficient format (a dictionary). Every time the package is loaded, the data in `openai_cache_temp.bin` is integrated into this permanent cache file, and the temporary file is deleted.
65
+ - `openai_cache_used.txt` stores all the keys of requests that have been made or saved since the file was last created; we'll explain the purpose of this file later.
66
+
67
+ ### Determining delays
68
+
69
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
70
+
71
+ By default, the package will be configured to return the result immediately. If you would, instead, like it to replicate the delay that was initially observed when the request was made, simply set an environment variable called `CACHED_OPENAI_DELAY_RESPONSES` to any value before you load the package.
72
+
73
+ ### Repeated requests
74
+
75
+ When you use `cached_openai` to run a request that has already been run, a new request will not be made to OpenAI - the *cached* result is returned instead, for free.
76
+
77
+ This does mean, however, that that result will be the same every time. What if you want to store several different responses to a single request? All you need to do is provide a `seed` parameter when you run that same request - if the function has been run with that identical seed before, that result is returned, but if not, the query is run again against the OpenAI API. If the underlying OpenAI API function has a seed parameter (eg: `chat.completions.create`), the seed is passed to that function, and if not (eg: `audio.speech.create`), it is stripped before it is sent.
78
+
79
+ When the request is next called *without* a seed, it will return each of the seeded responses in succession, which will make it look like it is returning different results every time, just like the original API. Here's an example that might clarify this, using chat completions:
80
+
81
+ - **Input**: please give me a random number. **Output**: 42
82
+ - *This is the first time the request has been made, so it is sent to the OpenAI API.*\
83
+ - **Input**: please give me a random number (`seed=1`). **Output**: 25
84
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
85
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
86
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
87
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
88
+ - *This request has been made with this seed before, the cached result is returned*
89
+ - **Input**: please give me a random number. **Output**: 42
90
+ - *This request has been made before and is being called again for the first time; return the first saved response.*
91
+ - **Input**: please give me a random number. **Output**: 25
92
+ - *This request has been made before and is being called again for the second time; return the second saved response.*
93
+ - **Input**: please give me a random number. **Output**: 126
94
+ - *This request has been made before and is being called again for the third time; return the third saved response.*
95
+ - **Input**: please give me a random number. **Output**: 42
96
+ - *This request has been made before and is being called again for the four time; there is no fourth response stored, so loop back to the first stored answer.*
97
+
98
+ ### Images
99
+
100
+ Image requests that return image URLs require specific handling, because OpenAI does not keep these image URLs active forever - they are deleted a few hours after the request is made. Thus, simply storing the original response with the original URL would return a broken link when the cached response was returned.
101
+
102
+ To solve this problem, `cached_openai` downloads the image from the URL before returning the result, and stores it in the cache. When the same request is later called and retrieved from the cache, the image is extracted into an `image` folder in the working directory, and the URLs in the response are replaced with local URLs pointing to those extracted images.
103
+
104
+ ### Audio
105
+
106
+ Audio requests suffer from a similar problem - OpenAI returns a file stream which then needs to be downloaded. `cached_openai` handles this seamlessly by downloading the file at request time, saving it in the cache, and simulating a file stream when the request is called again and needs to be retrieved from the cache.
107
+
108
+ ## Distributing the cache
109
+
110
+ Once you have created the cache for your class, there are three different ways to distribute it to students
111
+ - Get students to install `cached_openai` directly from pypi, and distribute a cache file that they can put in their working directory containing the cache data; you can also publish this file to a URL and `cached_openai` will ask them for a URL from which to download (and potentially decompress) the file.
112
+ - Create your own package on pypi that forks `cached_openai` and includes your own cache file in it (note that this only works if your cache file is small enough to be uploaded to pypi).
113
+ - Create a self-contained `.py` file that contains not only the `cached_openai` code, but also the cache itself, embedded in the file. You can then simply distribute this `.py` file as a "batteries included" package.
114
+
115
+ In all three cases, the first step is to call the `materialize` function, which will package the cache for you; see the function docstring for details.
@@ -0,0 +1,98 @@
1
+ # cached_openai
2
+
3
+ `cached_openai` is a simple Python library that mimics the `OpenAI` python library, but can draw from a cache instead of sending the request to Open AI.
4
+
5
+ When it is run in **dev mode**, it caches responses to all requests made from OpenAI, and returns the cached value if the request is made again. When it is run in **production mode**, no new cache entries are created, but if any requests stored in the cache are made again, they are returned therefrom.
6
+
7
+ It is able to cache several different responses for a single request, to mimic the way the OpenAI API returns different responses when it receives the same query twice. It is also able to handle requests that return images or sound clips.
8
+
9
+ The user experience with `cached_open` is identical to that with the original `openai` package. Consider, for example, the following piece of code using the "regular" `openai` api:
10
+ ```
11
+ import openai
12
+ client = openai.OpenAI(api_key=...)
13
+ response = client.chat.completions.create(...)
14
+ ```
15
+
16
+ In `cached_openai`, the **only** change required would be to change the first line to `import cached_openai as openai`. That's it - everything else works identically. If the query is cached, the cached version will be returned. If not, the `OpenAI` API will be queries as usual.
17
+
18
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
19
+
20
+ The package also works with the async version of the OpenAI API - just use `openai.AsyncOpenAI(...)`.
21
+
22
+ ## Why `cached_openai`?
23
+
24
+ I designed this package for teaching purposes - when I teach classes that use the OpenAI API, I often provide code to students which they run on their machines before modifying it. This isn't ideal for two reasons
25
+ 1. Every student has to pay to run the API requests in the code - there can be hundreds or thousands of these requests, and the costs can add up.
26
+ 2. Every student is likely to get different answers out of the API - it makes it difficult to teach the class when everyone is looking at a different answer.
27
+
28
+ Instead, I can now distributed `cached_openai` with a cache I prepared - students can then run the entire code for free, and get the same answers as I did. They can then modify the code to run their own version, and query OpenAI as usual.
29
+
30
+ # User manual
31
+
32
+ This user manual is divided into two parts - preparing a cache, and distributing it.
33
+
34
+ ## Preparing a cache
35
+
36
+ To prepare a cache, you need to run `cached_openai` in **dev mode**. To do this, set an environment variable called `CACHED_OPENAI_DEV_MODE` to any value before you load the package. I also recommended you set an environment variable called `CACHED_OPENAI_VERBOSE` to any value to print debugging messages as the package runs. The easiest way too do this is in your code, before you import the package
37
+
38
+ ```
39
+ import os
40
+ os.environ['CACHED_OPENAI_VERBOSE'] = 'True'
41
+ os.environ['CACHED_OPENAI_DEV_MODE'] = 'True'
42
+ import cached_openai
43
+ ```
44
+
45
+ You should then use `cached_openai` exactly as you would `OpenAI` - every request you make will be cached. In dev mode, the package creates three files in your working directory
46
+ - `openai_cache_temp.bin` is a temporary cache file - it is updated every time a call is made to the OpenAI API to store the results of that call. It can be updated very quickly (so as not to slow down the call), but stores data in an inneficient format.
47
+ - `openai_cache.cache` is the main cache file which contains all cached requests in an efficient format (a dictionary). Every time the package is loaded, the data in `openai_cache_temp.bin` is integrated into this permanent cache file, and the temporary file is deleted.
48
+ - `openai_cache_used.txt` stores all the keys of requests that have been made or saved since the file was last created; we'll explain the purpose of this file later.
49
+
50
+ ### Determining delays
51
+
52
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
53
+
54
+ By default, the package will be configured to return the result immediately. If you would, instead, like it to replicate the delay that was initially observed when the request was made, simply set an environment variable called `CACHED_OPENAI_DELAY_RESPONSES` to any value before you load the package.
55
+
56
+ ### Repeated requests
57
+
58
+ When you use `cached_openai` to run a request that has already been run, a new request will not be made to OpenAI - the *cached* result is returned instead, for free.
59
+
60
+ This does mean, however, that that result will be the same every time. What if you want to store several different responses to a single request? All you need to do is provide a `seed` parameter when you run that same request - if the function has been run with that identical seed before, that result is returned, but if not, the query is run again against the OpenAI API. If the underlying OpenAI API function has a seed parameter (eg: `chat.completions.create`), the seed is passed to that function, and if not (eg: `audio.speech.create`), it is stripped before it is sent.
61
+
62
+ When the request is next called *without* a seed, it will return each of the seeded responses in succession, which will make it look like it is returning different results every time, just like the original API. Here's an example that might clarify this, using chat completions:
63
+
64
+ - **Input**: please give me a random number. **Output**: 42
65
+ - *This is the first time the request has been made, so it is sent to the OpenAI API.*\
66
+ - **Input**: please give me a random number (`seed=1`). **Output**: 25
67
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
68
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
69
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
70
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
71
+ - *This request has been made with this seed before, the cached result is returned*
72
+ - **Input**: please give me a random number. **Output**: 42
73
+ - *This request has been made before and is being called again for the first time; return the first saved response.*
74
+ - **Input**: please give me a random number. **Output**: 25
75
+ - *This request has been made before and is being called again for the second time; return the second saved response.*
76
+ - **Input**: please give me a random number. **Output**: 126
77
+ - *This request has been made before and is being called again for the third time; return the third saved response.*
78
+ - **Input**: please give me a random number. **Output**: 42
79
+ - *This request has been made before and is being called again for the four time; there is no fourth response stored, so loop back to the first stored answer.*
80
+
81
+ ### Images
82
+
83
+ Image requests that return image URLs require specific handling, because OpenAI does not keep these image URLs active forever - they are deleted a few hours after the request is made. Thus, simply storing the original response with the original URL would return a broken link when the cached response was returned.
84
+
85
+ To solve this problem, `cached_openai` downloads the image from the URL before returning the result, and stores it in the cache. When the same request is later called and retrieved from the cache, the image is extracted into an `image` folder in the working directory, and the URLs in the response are replaced with local URLs pointing to those extracted images.
86
+
87
+ ### Audio
88
+
89
+ Audio requests suffer from a similar problem - OpenAI returns a file stream which then needs to be downloaded. `cached_openai` handles this seamlessly by downloading the file at request time, saving it in the cache, and simulating a file stream when the request is called again and needs to be retrieved from the cache.
90
+
91
+ ## Distributing the cache
92
+
93
+ Once you have created the cache for your class, there are three different ways to distribute it to students
94
+ - Get students to install `cached_openai` directly from pypi, and distribute a cache file that they can put in their working directory containing the cache data; you can also publish this file to a URL and `cached_openai` will ask them for a URL from which to download (and potentially decompress) the file.
95
+ - Create your own package on pypi that forks `cached_openai` and includes your own cache file in it (note that this only works if your cache file is small enough to be uploaded to pypi).
96
+ - Create a self-contained `.py` file that contains not only the `cached_openai` code, but also the cache itself, embedded in the file. You can then simply distribute this `.py` file as a "batteries included" package.
97
+
98
+ In all three cases, the first step is to call the `materialize` function, which will package the cache for you; see the function docstring for details.
Binary file
@@ -0,0 +1,7 @@
1
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o"}, "stem": ["chat", "completions", "create"]}
2
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o", "seed": 0}, "stem": ["chat", "completions", "create"]}
3
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o"}, "stem": ["chat", "completions", "create"]}
4
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o", "seed": 1}, "stem": ["chat", "completions", "create"]}
5
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o"}, "stem": ["chat", "completions", "create"]}
6
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o", "seed": 2}, "stem": ["chat", "completions", "create"]}
7
+ {"kwargs": {"messages": [{"content": "give me a random color", "role": "user"}], "model": "gpt-4o"}, "stem": ["chat", "completions", "create"]}
@@ -0,0 +1,39 @@
1
+ # To publish a new version of the pacakge
2
+
3
+ Ensure build is installed
4
+
5
+ ```pip install --upgrade build```
6
+
7
+ Do the build in the directory with `pyproject.toml`
8
+
9
+ ```python -m build```
10
+
11
+ Ensure twine is installed
12
+
13
+ ```pip install --upgrade twine```
14
+
15
+ ## Test publishing
16
+ Get an API token from (select "entire account" as the scope)
17
+
18
+ ```https://test.pypi.org/manage/account/#api-tokens```
19
+
20
+ Publish
21
+
22
+ ```python3 -m twine upload --repository testpypi dist/*```
23
+
24
+ Test install (don't use dependencies because test pypi might not have the same packages available)
25
+
26
+ ```pip install --index-url https://test.pypi.org/simple/ --no-deps cached_openai```
27
+
28
+ ## Real publishing
29
+ Get an API token from (select "entire account" as the scope)
30
+
31
+ ```https://pypi.org/manage/account/#api-tokens```
32
+
33
+ Publish
34
+
35
+ ```python3 -m twine upload dist/*```
36
+
37
+ Test install (don't use dependencies because test pypi might not have the same packages available)
38
+
39
+ ```pip install --index-url https://test.pypi.org/simple/ --no-deps cached_openai```
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "abf_openai"
7
+ version = "0.0.1"
8
+ dependencies = [
9
+ "requests>=2.32.2",
10
+ "tqdm>=4.67.1",
11
+ "openai>=1.66.2",
12
+ ]
13
+ authors = [
14
+ { name="Daniel Guetta", email="daniel@guetta.com" },
15
+ ]
16
+ description = "A cached wrapper of the OpenAI library for the CBS and WSP 'AI in Finance and Business' course"
17
+ readme = "README.md"
18
+ requires-python = ">=3.8"
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+ license = "MIT"
24
+ license-files = ["LICEN[CS]E*"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/danguetta/cached_openai"
28
+ Issues = "https://github.com/danguetta/cached_openai/issues"
@@ -0,0 +1,98 @@
1
+ # cached_openai
2
+
3
+ `cached_openai` is a simple Python library that mimics the `OpenAI` python library, but can draw from a cache instead of sending the request to Open AI.
4
+
5
+ When it is run in **dev mode**, it caches responses to all requests made from OpenAI, and returns the cached value if the request is made again. When it is run in **production mode**, no new cache entries are created, but if any requests stored in the cache are made again, they are returned therefrom.
6
+
7
+ It is able to cache several different responses for a single request, to mimic the way the OpenAI API returns different responses when it receives the same query twice. It is also able to handle requests that return images or sound clips.
8
+
9
+ The user experience with `cached_open` is identical to that with the original `openai` package. Consider, for example, the following piece of code using the "regular" `openai` api:
10
+ ```
11
+ import openai
12
+ client = openai.OpenAI(api_key=...)
13
+ response = client.chat.completions.create(...)
14
+ ```
15
+
16
+ In `cached_openai`, the **only** change required would be to change the first line to `import cached_openai as openai`. That's it - everything else works identically. If the query is cached, the cached version will be returned. If not, the `OpenAI` API will be queries as usual.
17
+
18
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
19
+
20
+ The package also works with the async version of the OpenAI API - just use `openai.AsyncOpenAI(...)`.
21
+
22
+ ## Why `cached_openai`?
23
+
24
+ I designed this package for teaching purposes - when I teach classes that use the OpenAI API, I often provide code to students which they run on their machines before modifying it. This isn't ideal for two reasons
25
+ 1. Every student has to pay to run the API requests in the code - there can be hundreds or thousands of these requests, and the costs can add up.
26
+ 2. Every student is likely to get different answers out of the API - it makes it difficult to teach the class when everyone is looking at a different answer.
27
+
28
+ Instead, I can now distributed `cached_openai` with a cache I prepared - students can then run the entire code for free, and get the same answers as I did. They can then modify the code to run their own version, and query OpenAI as usual.
29
+
30
+ # User manual
31
+
32
+ This user manual is divided into two parts - preparing a cache, and distributing it.
33
+
34
+ ## Preparing a cache
35
+
36
+ To prepare a cache, you need to run `cached_openai` in **dev mode**. To do this, set an environment variable called `CACHED_OPENAI_DEV_MODE` to any value before you load the package. I also recommended you set an environment variable called `CACHED_OPENAI_VERBOSE` to any value to print debugging messages as the package runs. The easiest way too do this is in your code, before you import the package
37
+
38
+ ```
39
+ import os
40
+ os.environ['CACHED_OPENAI_VERBOSE'] = 'True'
41
+ os.environ['CACHED_OPENAI_DEV_MODE'] = 'True'
42
+ import cached_openai
43
+ ```
44
+
45
+ You should then use `cached_openai` exactly as you would `OpenAI` - every request you make will be cached. In dev mode, the package creates three files in your working directory
46
+ - `openai_cache_temp.bin` is a temporary cache file - it is updated every time a call is made to the OpenAI API to store the results of that call. It can be updated very quickly (so as not to slow down the call), but stores data in an inneficient format.
47
+ - `openai_cache.cache` is the main cache file which contains all cached requests in an efficient format (a dictionary). Every time the package is loaded, the data in `openai_cache_temp.bin` is integrated into this permanent cache file, and the temporary file is deleted.
48
+ - `openai_cache_used.txt` stores all the keys of requests that have been made or saved since the file was last created; we'll explain the purpose of this file later.
49
+
50
+ ### Determining delays
51
+
52
+ The package can be configured to return the cached entry immediately, *or* to replicate the delay that would occur if the request were to be made from OpenAI directly.
53
+
54
+ By default, the package will be configured to return the result immediately. If you would, instead, like it to replicate the delay that was initially observed when the request was made, simply set an environment variable called `CACHED_OPENAI_DELAY_RESPONSES` to any value before you load the package.
55
+
56
+ ### Repeated requests
57
+
58
+ When you use `cached_openai` to run a request that has already been run, a new request will not be made to OpenAI - the *cached* result is returned instead, for free.
59
+
60
+ This does mean, however, that that result will be the same every time. What if you want to store several different responses to a single request? All you need to do is provide a `seed` parameter when you run that same request - if the function has been run with that identical seed before, that result is returned, but if not, the query is run again against the OpenAI API. If the underlying OpenAI API function has a seed parameter (eg: `chat.completions.create`), the seed is passed to that function, and if not (eg: `audio.speech.create`), it is stripped before it is sent.
61
+
62
+ When the request is next called *without* a seed, it will return each of the seeded responses in succession, which will make it look like it is returning different results every time, just like the original API. Here's an example that might clarify this, using chat completions:
63
+
64
+ - **Input**: please give me a random number. **Output**: 42
65
+ - *This is the first time the request has been made, so it is sent to the OpenAI API.*\
66
+ - **Input**: please give me a random number (`seed=1`). **Output**: 25
67
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
68
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
69
+ - *This is the first time the request has been made with this seed, so it is sent to the OpenAI API.*
70
+ - **Input**: please give me a random number (`seed=2`). **Output**: 126
71
+ - *This request has been made with this seed before, the cached result is returned*
72
+ - **Input**: please give me a random number. **Output**: 42
73
+ - *This request has been made before and is being called again for the first time; return the first saved response.*
74
+ - **Input**: please give me a random number. **Output**: 25
75
+ - *This request has been made before and is being called again for the second time; return the second saved response.*
76
+ - **Input**: please give me a random number. **Output**: 126
77
+ - *This request has been made before and is being called again for the third time; return the third saved response.*
78
+ - **Input**: please give me a random number. **Output**: 42
79
+ - *This request has been made before and is being called again for the four time; there is no fourth response stored, so loop back to the first stored answer.*
80
+
81
+ ### Images
82
+
83
+ Image requests that return image URLs require specific handling, because OpenAI does not keep these image URLs active forever - they are deleted a few hours after the request is made. Thus, simply storing the original response with the original URL would return a broken link when the cached response was returned.
84
+
85
+ To solve this problem, `cached_openai` downloads the image from the URL before returning the result, and stores it in the cache. When the same request is later called and retrieved from the cache, the image is extracted into an `image` folder in the working directory, and the URLs in the response are replaced with local URLs pointing to those extracted images.
86
+
87
+ ### Audio
88
+
89
+ Audio requests suffer from a similar problem - OpenAI returns a file stream which then needs to be downloaded. `cached_openai` handles this seamlessly by downloading the file at request time, saving it in the cache, and simulating a file stream when the request is called again and needs to be retrieved from the cache.
90
+
91
+ ## Distributing the cache
92
+
93
+ Once you have created the cache for your class, there are three different ways to distribute it to students
94
+ - Get students to install `cached_openai` directly from pypi, and distribute a cache file that they can put in their working directory containing the cache data; you can also publish this file to a URL and `cached_openai` will ask them for a URL from which to download (and potentially decompress) the file.
95
+ - Create your own package on pypi that forks `cached_openai` and includes your own cache file in it (note that this only works if your cache file is small enough to be uploaded to pypi).
96
+ - Create a self-contained `.py` file that contains not only the `cached_openai` code, but also the cache itself, embedded in the file. You can then simply distribute this `.py` file as a "batteries included" package.
97
+
98
+ In all three cases, the first step is to call the `materialize` function, which will package the cache for you; see the function docstring for details.
@@ -0,0 +1,10 @@
1
+ # Make the main entrypoints into the package available at the top level of the package
2
+ from .main import OpenAI, AsyncOpenAI, DEV_MODE
3
+
4
+ # Make the materialization functions available at the top level of the package, if
5
+ # we're in dev mode
6
+ if DEV_MODE:
7
+ from .main import materialize
8
+
9
+ # Remove DEV_MODE from the namespace
10
+ del DEV_MODE