├── demo ├── .env.example ├── prompts │ ├── summarize_for_a_2nd_grader_with_variables.txt │ ├── what_is_the_favorite_food_of.txt │ ├── animals.txt │ └── summarize_for_a_2nd_grader.txt ├── step_3.py ├── step_2.py ├── README.md └── step_1.py ├── tests ├── prompts │ ├── completion-prompt.txt │ └── search-prompt.txt ├── .DS_Store └── workflow.py ├── .gitignore ├── chronological ├── __pycache__ │ └── __init__.cpython-39.pyc └── __init__.py ├── setup.py ├── LICENSE └── README.md /demo/.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="MY_API_KEY" -------------------------------------------------------------------------------- /tests/prompts/completion-prompt.txt: -------------------------------------------------------------------------------- 1 | Simon Says: {0} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | /dist 3 | /build 4 | /chronological.egg-info -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OthersideAI/chronology/HEAD/tests/.DS_Store -------------------------------------------------------------------------------- /chronological/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OthersideAI/chronology/HEAD/chronological/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /tests/workflow.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | # TODO import __init__ above 3 | 4 | async def logic(): 5 | # TODO add tests 6 | pass 7 | 8 | 9 | # main(logic) 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/prompts/summarize_for_a_2nd_grader_with_variables.txt: -------------------------------------------------------------------------------- 1 | My second grader asked me what this passage means: 2 | """ 3 | 4 | {0} 5 | """ 6 | 7 | I rephrased it for him, in plain language a second grader can understand: 8 | """ 9 | -------------------------------------------------------------------------------- /demo/prompts/what_is_the_favorite_food_of.txt: -------------------------------------------------------------------------------- 1 | Q: What is an elephant's favorite food? 2 | A: Tree bark 3 | 4 | ### 5 | 6 | Q: What is a otter's favorite food? 7 | A: Crabs 8 | 9 | ### 10 | 11 | Q: What is a {0}'s favorite food? 12 | A: -------------------------------------------------------------------------------- /demo/prompts/animals.txt: -------------------------------------------------------------------------------- 1 | Amur Leopard, Black Rhino, Cross River Gorilla,Eastern Lowland Gorilla,Hawksbill Turtle,Javan Rhino,Saola,Sumatran Elephant,Sumatran Orangutan,Sumatran Rhino,Sunda Tiger,Vaquita,Western Lowland Gorilla,Yangtze Finless Porpoise,African Wild Dog,Asian Elephant,Black-footed Ferret,Blue Whale,Bluefin Tuna,Bonobo,Bornean Elephant,Chimpanzee,Fin Whale,Galápagos Penguin,Ganges River Dolphin,Green Turtle,Hector's Dolphin,Humphead Wrasse Cheilinus,Indian Elephant Elephas,Indus River Dolphin,Irrawaddy Dolphin,Mountain Gorilla -------------------------------------------------------------------------------- /tests/prompts/search-prompt.txt: -------------------------------------------------------------------------------- 1 | Amur Leopard 2 | Black Rhino 3 | Bornean Orangutan 4 | Cross River Gorilla 5 | Eastern Lowland Gorilla 6 | Hawksbill Turtle 7 | Javan Rhino 8 | Orangutan 9 | Saola 10 | Sumatran Elephant 11 | Sumatran Orangutan 12 | Sumatran Rhino 13 | Sunda Tiger 14 | Vaquita 15 | Western Lowland Gorilla 16 | Yangtze Finless Porpoise 17 | African Wild Dog 18 | Asian Elephant 19 | Black-footed Ferret 20 | Blue Whale 21 | Bluefin Tuna 22 | Bonobo 23 | Bornean Elephant 24 | Chimpanzee 25 | Fin Whale 26 | Galápagos Penguin 27 | Ganges River Dolphin 28 | Green Turtle 29 | Hector's Dolphin 30 | Humphead Wrasse Cheilinus 31 | Indian Elephant Elephas 32 | Indus River Dolphin 33 | Irrawaddy Dolphin 34 | Mountain Gorilla -------------------------------------------------------------------------------- /demo/prompts/summarize_for_a_2nd_grader.txt: -------------------------------------------------------------------------------- 1 | My second grader asked me what this passage means: 2 | """ 3 | 4 | Olive oil is a liquid fat obtained from olives (the fruit of Olea europaea; family Oleaceae), a traditional tree crop of the Mediterranean Basin, produced by pressing whole olives and extracting the oil. Olive oil is the most common vegetable oil. It is commonly used in cooking, for frying foods or as a salad dressing. It is also used in cosmetics, pharmaceuticals, and soaps, and as a fuel for traditional oil lamps, and has additional uses in some religions. The olive is one of three core food plants in Mediterranean cuisine; the other two are wheat and grapes. Olive trees have been grown around the Mediterranean since the 8th millennium BC. 5 | """ 6 | 7 | I rephrased it for him, in plain language a second grader can understand: 8 | """ 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='chronological', 8 | version='0.1.1', 9 | description='Chain GPT calls like a pro!', 10 | url='https://github.com/OthersideAI/chronology', 11 | author='Otherside AI', 12 | author_email='bram@othersideai.com', 13 | license='unlicense', 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | packages=find_packages(), 17 | install_requires=['openai', 'python-dotenv', 'loguru'], 18 | zip_safe=False, 19 | python_requires='>=3.7', 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ], 25 | ) -------------------------------------------------------------------------------- /demo/step_3.py: -------------------------------------------------------------------------------- 1 | from chronological import read_prompt, fetch_max_search_doc, main, cleaned_completion 2 | 3 | async def fetch_three_top_animals(query, n): 4 | # splitting on ',' -- similar to what you might do with a csv file 5 | prompt_animals = read_prompt('animals').split(',') 6 | return await fetch_max_search_doc(query, prompt_animals, engine="ada", n=n) 7 | 8 | 9 | async def find_each_animals_favorite_food(animals): 10 | prompt_what_is_the_favorite_food_of = read_prompt('what_is_the_favorite_food_of') 11 | favorite_foods = [] 12 | for animal in animals: 13 | res = await cleaned_completion(prompt_what_is_the_favorite_food_of.format(animal), engine="davinci", temperature=0.2, stop=["\n\n"]) 14 | favorite_foods.append({ animal: res}) 15 | return favorite_foods 16 | 17 | async def logic(): 18 | animals = await fetch_three_top_animals('monkey', 3) 19 | favorite_foods = await find_each_animals_favorite_food(animals) 20 | print(favorite_foods) 21 | 22 | main(logic) -------------------------------------------------------------------------------- /demo/step_2.py: -------------------------------------------------------------------------------- 1 | from chronological import read_prompt, fetch_max_search_doc, main 2 | 3 | async def fetch_top_animal(query): 4 | # splitting on ',' -- similar to what you might do with a csv file 5 | prompt_animals = read_prompt('animals').split(',') 6 | return await fetch_max_search_doc(query, prompt_animals, engine="ada") 7 | 8 | async def fetch_three_top_animals(query, n): 9 | # splitting on ',' -- similar to what you might do with a csv file 10 | prompt_animals = read_prompt('animals').split(',') 11 | return await fetch_max_search_doc(query, prompt_animals, engine="ada", n=n) 12 | 13 | async def logic(): 14 | fetch_top_animal_res = await fetch_top_animal("monkey") 15 | fetch_top_three_animals_res = await fetch_three_top_animals("monkey", 3) 16 | 17 | print('-------------------------') 18 | print('Fetch Top Animal Response: {0}'.format(fetch_top_animal_res)) 19 | print('-------------------------') 20 | print('Fetch Top Three Animals Response: {0}'.format(fetch_top_three_animals_res)) 21 | print('-------------------------') 22 | 23 | main(logic) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 OthersideAI 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 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # How To Use Chronology -- A Walkthrough Demo 2 | 3 | # [WATCH THE DEMO LIVESTREAM HERE](https://youtu.be/G3PctszbrrE) 4 | 5 | ## Step 0 - Installation 6 | 7 | 1. Download Chronology using command (*you can also find it on pip [here](https://pypi.org/project/chronological/)*): 8 | ```python 9 | python3 -m pip install chronological 10 | ``` 11 | 2. Clone this repo to your local machine 12 | 3. Create a `.env` file and add your OpenAI API key to it in the format: `OPENAI_API_KEY="MY_API_KEY"` 13 | 4. Run each step with the command: 14 | ```python 15 | python3 step_1.py 16 | ``` 17 | 18 | ## Step 1 - Completions 19 | 20 | Completions are the bread and butter of the OpenAI API, and the first thing most people use when they use the playground. 21 | 22 | In this step, we'll examine how to: 23 | 24 | 1. Read in a prompt from a text file 25 | 2. Use the prompt to make an `awaited completion` from the `davinci` engine 26 | 3. Load in two variables and attach them to the same prompt 27 | 4. Create completions with each variable-formatted prompt 28 | 29 | ## Step 2 - Searching 30 | 31 | The Semantic Search API is a powerful tool to be able to filter a set of documents based on a query. 32 | 33 | In this step, we'll examine how to: 34 | 35 | 1. Read in a list of animals from a text file 36 | 2. Return the top document that matches our query by using an `awaited search` from the `ada` engine 37 | 3. Return the top three documents that match the same query 38 | 39 | ## Step 3 - Linking it All Together: Extra Logic and Using Different Engines 40 | 41 | The power of Chronology starts to show with this step. We'll meld the power of programming logic with the power of the OpenAI API. 42 | 43 | In this step, we'll examine how to: 44 | 1. Capture the result from a search call to OpenAI using the `ada` engine 45 | 2. Apply extra logic to that search call 46 | 3. Run a completion with the result of the search call + logic using the `davinci` engine 47 | 48 | ## Extra: Using the UI 49 | 50 | If you would prefer to build Chronology chains without writing code, I have great news! There is a `no-code UI` for Chronology, called the [Chronology UI](https://chronology-ui.vercel.app/#). 51 | 52 | Here is a [in depth Loom video tutorial on how to use Chronology UI with Chronology](https://www.loom.com/share/47cb8d328ebd446db4d98ea1c0cac2c7) 53 | 54 | *They say a picture is worth 1000 words, so what's a video worth!* 55 | 56 | *You can see the GitHub repo for Chronology UI [here](https://github.com/OthersideAI/chronology-ui)* -------------------------------------------------------------------------------- /demo/step_1.py: -------------------------------------------------------------------------------- 1 | from chronological import read_prompt, cleaned_completion, main 2 | 3 | # basic example -- playground reconstruction 4 | # prompt src: https://en.wikipedia.org/wiki/Olive_oil 5 | async def basic_example(): 6 | prompt_summarize_for_a_2nd_grader = read_prompt('summarize_for_a_2nd_grader') 7 | completion_summarize_for_a_2nd_grader = await cleaned_completion(prompt_summarize_for_a_2nd_grader, max_tokens=100, engine="davinci", temperature=0.5, top_p=1, frequency_penalty=0.2, stop=["\n\n"]) 8 | 9 | return completion_summarize_for_a_2nd_grader 10 | 11 | # basic example with variables -- playground reconstruction 12 | # prompt src: https://en.wikipedia.org/wiki/Olive_oil, https://en.wikipedia.org/wiki/Interior_design 13 | async def basic_example_with_variables(): 14 | var_olive_oil = "Olive oil is a liquid fat obtained from olives (the fruit of Olea europaea; family Oleaceae), a traditional tree crop of the Mediterranean Basin, produced by pressing whole olives and extracting the oil. Olive oil is the most common vegetable oil. It is commonly used in cooking, for frying foods or as a salad dressing. It is also used in cosmetics, pharmaceuticals, and soaps, and as a fuel for traditional oil lamps, and has additional uses in some religions. The olive is one of three core food plants in Mediterranean cuisine; the other two are wheat and grapes. Olive trees have been grown around the Mediterranean since the 8th millennium BC." 15 | var_interior_design = "Interior design is the art and science of enhancing the interior of a building to achieve a healthier and more aesthetically pleasing environment for the people using the space. An interior designer is someone who plans, researches, coordinates, and manages such enhancement projects. Interior design is a multifaceted profession that includes conceptual development, space planning, site inspections, programming, research, communicating with the stakeholders of a project, construction management, and execution of the design." 16 | 17 | prompt_summarize_for_a_2nd_grader_with_variables = read_prompt("summarize_for_a_2nd_grader_with_variables") 18 | 19 | # use the Python3 `format()` method to replace {0} with our variables 20 | prompt_var_olive_oil = prompt_summarize_for_a_2nd_grader_with_variables.format(var_olive_oil) 21 | prompt_var_interior_design = prompt_summarize_for_a_2nd_grader_with_variables.format(var_interior_design) 22 | 23 | completion_var_olive_oil = await cleaned_completion(prompt_var_olive_oil, max_tokens=100, engine="davinci", temperature=0.5, top_p=1, frequency_penalty=0.2, stop=["\n\n"]) 24 | completion_var_interior_design = await cleaned_completion(prompt_var_interior_design, max_tokens=100, engine="davinci", temperature=0.5, top_p=1, frequency_penalty=0.2, stop=["\n\n"]) 25 | 26 | return (completion_var_olive_oil, completion_var_interior_design) 27 | 28 | 29 | async def logic(): 30 | basic_example_res = await basic_example() 31 | basic_example_with_vars_res = await basic_example_with_variables() 32 | 33 | print('-------------------------') 34 | print('Basic Example Response: {0}'.format(basic_example_res)) 35 | print('-------------------------') 36 | print('Basic Example with Variables Responses: {0}'.format(basic_example_with_vars_res)) 37 | print('-------------------------') 38 | 39 | 40 | # invoke Chronology to run the async logic 41 | main(logic) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chronology 2 | 3 | Chronology is a library that enables users of OpenAI's GPT-3 language model to more easily build complex language-powered applications. 4 | 5 | It provides a simple and intuitive interface for working with GPT-3. 6 | 7 | We built this at OthersideAI to help mitigate some of the monotonous work we had to do when developing with GPT-3. Our library has the following features: 8 | 9 | - Asynchronously call GPT-3, enabling multiple prompts to generate at the same time 10 | - Easy creation and modification of prompts 11 | - Chain prompts together, feeding output from one or multiple prompts into another prompt, allowing for highly-complex systems to be built quickly 12 | 13 | We built this library to be as intuitive as possible. There are no complicated concepts to master. 14 | 15 | # Installation 16 | 17 | chronology is hosted on PyPI. 18 | 19 | Chronology is supported on Python 3.6 and above. 20 | 21 | To install chronology: 22 | 23 | `pip install chronological` 24 | 25 | This project also depends on the following packages: 26 | * [`openai-api`](https://github.com/openai/openai-python) 27 | * [`python-dotenv`](https://pypi.org/project/python-dotenv/) 28 | * [`loguru`](https://github.com/Delgan/loguru) 29 | * [`asyncio`](https://docs.python.org/3/library/asyncio.html) 30 | 31 | # Usage 32 | 33 | After you have downloaded the package, create a `.env` file at the root of your project and put your OpenAI API key in as: 34 | 35 | `OPENAI_API_KEY = "MY_API_KEY"` 36 | 37 | You now have a few options. You can use the UI to generate the chain or you can use the API directly. 38 | 39 | ## [Using ChronologyUI](https://github.com/OthersideAI/chronology-ui) 40 | 41 | Here is a [Loom video](https://www.loom.com/share/47cb8d328ebd446db4d98ea1c0cac2c7?sharedAppSource=personal_library) showing how to use the UI with the Python [`chronology`](https://github.com/OthersideAI/chronology) package. 42 | 43 | ## Using the API Directly 44 | 45 | ### [`main`](#main) 46 | 47 | The `main` function is an async function that holds all of your business logic. You then invoke this logic by passing it as an argument to `main`. **Required** 48 | 49 | ## Example: 50 | 51 | ``` 52 | # you can name this function anything you want, the name "logic" is arbitrary 53 | async def logic(): 54 | # you call the Chronology functions, awaiting the ones that are marked await 55 | prompt = read_prompt('example_prompt') 56 | completion = await cleaned_completion(prompt, max_tokens=100, engine="davinci", temperature=0.5, top_p=1, frequency_penalty=0.2, stop=["\n\n"]) 57 | 58 | print('Completion Response: {0}'.format(completion)) 59 | 60 | # you can also run whatever you want in this function 61 | for i in range(4): 62 | print("hello") 63 | 64 | 65 | # invoke the Chronology main fn to run the async logic 66 | main(logic) 67 | ``` 68 | 69 | ### [`fetch_max_search_doc`](#fetch_max_search_doc) 70 | #### **Must be awaited** 71 | 72 | Fetch document value with max score. Wrapper for OpenAI API Search. 73 | 74 | Optional: 75 | 76 | min_score_cutoff = if maximum score is less than cutoff, None will be returned. Defaults to -1 77 | 78 | full_doc = return whole response with max, but doesn't grab doc for you. Defaults to False. [doc, doc.index, doc.score] 79 | 80 | ### [`raw_completion`](#raw_completion) 81 | #### **Must be awaited** 82 | 83 | Wrapper for OpenAI API completion. Returns raw result from GPT-3. 84 | 85 | ### [`cleaned_completion`](#cleaned_completion) 86 | #### **Must be awaited** 87 | 88 | Wrapper for OpenAI API completion. Returns whitespace trimmed result from GPT-3. 89 | 90 | ### [`gather`](#gather) 91 | #### **Must be awaited** 92 | 93 | Run methods in parallel (they don't need to wait for each other to finish). 94 | 95 | Requires method argumets to be async. 96 | 97 | Example: await gather(fetch_max_search_doc(query_1, docs), fetch_max_search_doc(query_2, docs)) 98 | 99 | ### [`read_prompt`](#read_prompt) 100 | 101 | Looks in prompts/ directory for a text file. Pass in file name only, not extension. 102 | 103 | Example: prompts/hello-world.txt -> read_prompt('hello-world') 104 | 105 | 106 | ### [`add_new_lines_start`](#add_new_lines_start) 107 | 108 | Add N new lines to the start of a string. 109 | 110 | ### [`add_new_lines_end`](#add_new_lines_end) 111 | 112 | Add N new lines to the end of a string. 113 | 114 | ### [`append_prompt`](#append_prompt) 115 | 116 | Add new content to the end of a string. 117 | 118 | ### [`prepend_prompt`](#prepend_prompt) 119 | 120 | Add new content to the start of a string. 121 | 122 | ### [`set_api_key`](#set_api_key) 123 | 124 | Set your OpenAI API key in the code. 125 | 126 | ## Contributing 127 | 128 | Chronology & ChronologyUI are both open source! 129 | 130 | This project is an evolving use case and we welcome any contribution or feedback. 131 | 132 | ### Open Bouties: 133 | 134 | - [ ] adding all the fields the OpenAI Python API accepts to Chronology 135 | - [ ] adding a test suite that calls different length chains 136 | - [ ] extending `fetch_max_search_doc` to have smarter logic around minimium scores 137 | - [ ] make `gather` run faster, using [threads](https://docs.python.org/3/library/asyncio-task.html#running-in-threads) 138 | 139 | ## Learn More 140 | 141 | Chronology is the backbone of https://OthersideAI.com. We use it to chain prompt calls and asyncronously call GPT-3. Our application is highly complex, and has many steps. Chronology allows us to parallelize those steps, significantly cutting down the time it takes to generate an email. 142 | 143 | To learn more about OthersideAI, take a look at the following resources: 144 | 145 | - [Our Homepage](https://www.othersideai.com/) 146 | - [Our Twitter](https://twitter.com/othersideai) 147 | 148 | Contact: info@othersideai.com 149 | -------------------------------------------------------------------------------- /chronological/__init__.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | import time 3 | import asyncio 4 | import openai 5 | import os 6 | from pathlib import Path 7 | from loguru import logger 8 | from dotenv import load_dotenv 9 | env_path = Path('.') / '.env' 10 | load_dotenv(dotenv_path=env_path) 11 | 12 | openai.api_key = os.getenv("OPENAI_API_KEY") 13 | MAX_SEARCH_DOCUMENT_QUANTITY = 200 14 | 15 | async def set_api_key(api_key): 16 | openai.api_key = api_key 17 | 18 | # oai 19 | 20 | 21 | async def _search(q, docs, engine="ada"): 22 | logger.debug("""CONFIG: 23 | Query: {0} 24 | Docs: {1} 25 | Engine: {2} 26 | """.format(q, docs, engine)) 27 | response = openai.Engine(engine).search( 28 | documents=docs, 29 | query=q 30 | ) 31 | logger.debug("GPT-3 Search Result: {0}".format(response)) 32 | return response 33 | 34 | 35 | async def _completion(prompt, engine="ada", max_tokens=64, temperature=0.7, top_p=1, stop=None, presence_penalty=0, frequency_penalty=0, echo=False, n=1, stream=False, logprobs=None, best_of=1, logit_bias={}): 36 | logger.debug("""CONFIG: 37 | Prompt: {0} 38 | Temperature: {1} 39 | Engine: {2} 40 | Max Tokens: {3} 41 | Top-P: {4} 42 | Stop: {5} 43 | Presence Penalty {6} 44 | Frequency Penalty: {7} 45 | Echo: {8} 46 | N: {9} 47 | Stream: {10} 48 | Log-Probs: {11} 49 | Best Of: {12} 50 | Logit Bias: {13}""" 51 | .format(repr(prompt), temperature, engine, max_tokens, top_p, stop, presence_penalty, frequency_penalty, echo, n, stream, logprobs, best_of, logit_bias)) 52 | response = openai.Completion.create(engine=engine, 53 | prompt=prompt, 54 | max_tokens=max_tokens, 55 | temperature=temperature, 56 | top_p=top_p, 57 | presence_penalty=presence_penalty, 58 | frequency_penalty=frequency_penalty, 59 | echo=echo, 60 | stop=stop, 61 | n=n, 62 | stream=stream, 63 | logprobs=logprobs, 64 | best_of=best_of, 65 | logit_bias=logit_bias) 66 | logger.debug("GPT-3 Completion Result: {0}".format(response)) 67 | return response 68 | 69 | # Helpers 70 | 71 | def _batch_docs(docs, n): 72 | """Yield successive n-sized batches from list of docs.""" 73 | for i in range(0, len(docs), n): 74 | yield docs[i:i + n] 75 | 76 | def _max_search_doc(resp, n): 77 | max_docs = heapq.nlargest(n, resp['data'], key=lambda x: x['score']) 78 | return max_docs 79 | 80 | 81 | def _fetch_response(resp, n): 82 | if n == 1: 83 | return resp.choices[0].text 84 | else: 85 | logger.debug('_fetch_response :: returning {0} responses from GPT-3'.format(n)) 86 | texts = [] 87 | for idx in range(0, n): 88 | texts.append(resp.choices[idx].text) 89 | return texts 90 | 91 | 92 | def _trimmed_fetch_response(resp, n): 93 | if n == 1: 94 | return resp.choices[0].text.strip() 95 | else: 96 | logger.debug('_trimmed_fetch_response :: returning {0} responses from GPT-3'.format(n)) 97 | texts = [] 98 | for idx in range(0, n): 99 | texts.append(resp.choices[idx].text.strip()) 100 | return texts 101 | 102 | 103 | def prepend_prompt(new_stuff, prompt): 104 | ''' 105 | Add new content to the start of a string. 106 | ''' 107 | return "{0}{1}".format(new_stuff, prompt) 108 | 109 | 110 | def append_prompt(new_stuff, prompt): 111 | ''' 112 | Add new content to the end of a string. 113 | ''' 114 | return "{1}{0}".format(new_stuff, prompt) 115 | 116 | 117 | def add_new_lines_end(prompt, count): 118 | ''' 119 | Add N new lines to the end of a string. 120 | ''' 121 | return "{0}{1}".format(prompt, "\n"*count) 122 | 123 | 124 | def add_new_lines_start(prompt, count): 125 | ''' 126 | Add N new lines to the start of a string. 127 | ''' 128 | return "{1}{0}".format(prompt, "\n"*count) 129 | 130 | 131 | def read_prompt(filename): 132 | ''' 133 | Looks in prompts/ directory for a text file. Pass in file name only, not extension. 134 | 135 | Example: prompts/hello-world.txt -> read_prompt('hello-world') 136 | ''' 137 | return Path('./prompts/{0}.txt'.format(filename)).read_text() 138 | 139 | 140 | async def gather(*args): 141 | ''' 142 | Run methods in parallel (they don't need to wait for each other to finish). 143 | 144 | Requires method argumets to be async. 145 | 146 | Example: await gather(fetch_max_search_doc(query_1, docs), fetch_max_search_doc(query_2, docs)) 147 | ''' 148 | return await asyncio.gather(*args) 149 | 150 | # Wrappers 151 | 152 | 153 | async def cleaned_completion(prompt, engine="ada", max_tokens=64, temperature=0.7, top_p=1, stop=None, presence_penalty=0, frequency_penalty=0, echo=False, n=1, stream=False, logprobs=None, best_of=1, logit_bias={}): 154 | ''' 155 | Wrapper for OpenAI API completion. Returns whitespace trimmed result from GPT-3. 156 | ''' 157 | resp = await _completion(prompt, 158 | engine=engine, 159 | max_tokens=max_tokens, 160 | temperature=temperature, 161 | top_p=top_p, 162 | presence_penalty=presence_penalty, 163 | frequency_penalty=frequency_penalty, 164 | echo=echo, 165 | stop=stop, 166 | n=n, 167 | stream=stream, 168 | logprobs=logprobs, 169 | best_of=best_of, 170 | logit_bias=logit_bias) 171 | return _trimmed_fetch_response(resp, n) 172 | 173 | 174 | async def raw_completion(prompt, engine="ada", max_tokens=64, temperature=0.7, top_p=1, stop=None, presence_penalty=0, frequency_penalty=0, echo=False, n=1, stream=False, logprobs=None, best_of=1, logit_bias={}): 175 | ''' 176 | Wrapper for OpenAI API completion. Returns raw result from GPT-3. 177 | ''' 178 | resp = await _completion(prompt, 179 | engine=engine, 180 | max_tokens=max_tokens, 181 | temperature=temperature, 182 | top_p=top_p, 183 | presence_penalty=presence_penalty, 184 | frequency_penalty=frequency_penalty, 185 | echo=echo, 186 | stop=stop, 187 | n=n, 188 | stream=stream, 189 | logprobs=logprobs, 190 | best_of=best_of, 191 | logit_bias=logit_bias) 192 | return _fetch_response(resp, n) 193 | 194 | 195 | async def fetch_max_search_doc(q, docs, engine="ada", min_score_cutoff=-1, full_doc=False, n=1): 196 | ''' 197 | Fetch document value with max score. Wrapper for OpenAI API Search. 198 | 199 | Optional: 200 | 201 | min_score_cutoff = if maximum score is less than cutoff, None will be returned. Defaults to -1 202 | 203 | full_doc = return whole response with max, but doesn't grab doc for you. Defaults to False. [doc, doc.index, doc.score] 204 | 205 | n = return top n most similar documents. Defaults to 1. 206 | ''' 207 | if n > len(docs): 208 | return 'N > # of docs' 209 | 210 | resp = {'data':[]} 211 | for batch in _batch_docs(docs, MAX_SEARCH_DOCUMENT_QUANTITY): 212 | resp['data'].extend((await _search(q, batch, engine=engine))['data']) 213 | 214 | if not full_doc: 215 | max_docs = _max_search_doc(resp, n) 216 | max_docs_filtered = [] 217 | for doc in max_docs: 218 | if float(doc['score']) > min_score_cutoff: 219 | max_docs_filtered.append(docs[doc['document']]) 220 | if len(max_docs_filtered) > 0: 221 | return max_docs_filtered 222 | else: 223 | return None 224 | else: 225 | max_docs = _max_search_doc(resp, n) 226 | max_docs_filtered = [] 227 | for doc in max_docs: 228 | if float(doc['score']) > min_score_cutoff: 229 | max_docs_filtered.append(doc) 230 | if len(max_docs_filtered) > 0: 231 | return max_docs_filtered 232 | else: 233 | return None 234 | 235 | 236 | def main(fn, **args): 237 | ''' 238 | Main function that runs logic. Accepts a function implemented on your end! 239 | ''' 240 | tic = time.perf_counter() 241 | asyncio.run(fn(**args)) 242 | toc = time.perf_counter() 243 | logger.debug(f"FINISHED WORKFLOW IN {toc - tic:0.4f} SECONDS") 244 | --------------------------------------------------------------------------------