├── .gitignore ├── LICENSE ├── README.md ├── examples ├── __init__.py ├── code_self_refinement.py ├── knowledge_base.py ├── natural_language_condition.py ├── replay_criticize_refine.py ├── replay_expand_memory.py ├── teach_remember_refine.py ├── teach_this_library.py └── teach_tunisian.py ├── pyproject.toml └── thinkgpt ├── __init__.py ├── abstract.py ├── condition.py ├── gpt_select.py ├── helper.py ├── infer.py ├── llm.py ├── memory.py ├── refine.py └── summarize.py /.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 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThinkGPT 🧠🤖 2 | 3 | 4 | 5 | ThinkGPT is a Python library aimed at implementing Chain of Thoughts for Large Language Models (LLMs), prompting the model to think, reason, and to create generative agents. 6 | The library aims to help with the following: 7 | * solve limited context with long memory and compressed knowledge 8 | * enhance LLMs' one-shot reasoning with higher order reasoning primitives 9 | * add intelligent decisions to your code base 10 | 11 | 12 | ## Key Features ✨ 13 | * Thinking building blocks 🧱: 14 | * Memory 🧠: GPTs that can remember experience 15 | * Self-refinement 🔧: Improve model-generated content by addressing critics 16 | * Compress knowledge 🌐: Compress knowledge and fit it in LLM's context either by anstracring rules out of observations or summarize large content 17 | * Inference 💡️: Make educated guesses based on available information 18 | * Natural Language Conditions 📝: Easily express choices and conditions in natural language 19 | * Efficient and Measurable GPT context length 📐 20 | * Extremely easy setup and pythonic API 🎯 thanks to [DocArray](https://github.com/docarray/docarray) 21 | 22 | ## Installation 💻 23 | You can install ThinkGPT using pip: 24 | 25 | ```shell 26 | pip install git+https://github.com/alaeddine-13/thinkgpt.git 27 | ``` 28 | 29 | ## API Documentation 📚 30 | ### Basic usage: 31 | ```python 32 | from thinkgpt.llm import ThinkGPT 33 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 34 | # Make the llm object learn new concepts 35 | llm.memorize(['DocArray is a library for representing, sending and storing multi-modal data.']) 36 | llm.predict('what is DocArray ?', remember=llm.remember('DocArray definition')) 37 | ``` 38 | 39 | ### Memorizing and Remembering information 40 | ```python 41 | llm.memorize([ 42 | 'DocArray allows you to send your data, in an ML-native way.', 43 | 'This means there is native support for Protobuf and gRPC, on top of HTTP and serialization to JSON, JSONSchema, Base64, and Bytes.', 44 | ]) 45 | 46 | print(llm.remember('Sending data with DocArray', limit=1)) 47 | ``` 48 | ```text 49 | ['DocArray allows you to send your data, in an ML-native way.'] 50 | ``` 51 | 52 | Use the `limit` parameter to specify the maximum number of documents to retrieve. 53 | In case you want to fit documents into a certain context size, you can also use the `max_tokens` parameter to specify the maximum number of tokens to retrieve. 54 | For instance: 55 | ```python 56 | from examples.knowledge_base import knowledge 57 | from thinkgpt.helper import get_n_tokens 58 | 59 | llm.memorize(knowledge) 60 | results = llm.remember('hello', max_tokens=1000, limit=1000) 61 | print(get_n_tokens(''.join(results))) 62 | ``` 63 | ```text 64 | 1000 65 | ``` 66 | However, keep in mind that concatenating documents with a separator will add more tokens to the final result. 67 | The `remember` method does not account for those tokens. 68 | 69 | ### Predicting with context from long memory 70 | ```python 71 | from examples.knowledge_base import knowledge 72 | llm.memorize(knowledge) 73 | llm.predict('Implement a DocArray schema with 2 fields: image and TorchTensor', remember=llm.remember('DocArray schemas and types')) 74 | ``` 75 | 76 | ### Self-refinement 77 | 78 | ```python 79 | print(llm.refine( 80 | content=""" 81 | import re 82 | print('hello world') 83 | """, 84 | critics=[ 85 | 'File "/Users/user/PyCharm2022.3/scratches/scratch_166.py", line 2', 86 | " print('hello world')", 87 | 'IndentationError: unexpected indent' 88 | ], 89 | instruction_hint="Fix the code snippet based on the error provided. Only provide the fixed code snippet between `` and nothing else.")) 90 | 91 | ``` 92 | 93 | ```text 94 | import re 95 | print('hello world') 96 | ``` 97 | 98 | One of the applications is self-healing code generation implemented by projects like [gptdeploy](https://github.com/jina-ai/gptdeploy) and [wolverine](https://github.com/biobootloader/wolverine) 99 | 100 | ### Compressing knowledge 101 | In case you want your knowledge to fit into the LLM's context, you can use the following techniques to compress it: 102 | #### Summarize content 103 | Summarize content using the LLM itself. 104 | We offer 2 methods 105 | 1. one-shot summarization using the LLM 106 | ```python 107 | llm.summarize( 108 | large_content, 109 | max_tokens= 1000, 110 | instruction_hint= 'Pay attention to code snippets, links and scientific terms.' 111 | ) 112 | ``` 113 | Since this technique relies on summarizing using a single LLM call, you can only pass content that does not exceed the LLM's context length. 114 | 115 | 2. Chunked summarization 116 | ```python 117 | llm.chunked_summarize( 118 | very_large_content, 119 | max_tokens= 4096, 120 | instruction_hint= 'Pay attention to code snippets, links and scientific terms.' 121 | ) 122 | ``` 123 | This technique relies on splitting the content into different chunks, summarizing each of those chunks and then combining them all together using an LLM. 124 | 125 | #### Induce rules from observations 126 | Amount to higher level and more general observations from current observations: 127 | ```python 128 | llm.abstract(observations=[ 129 | "in tunisian, I did not eat is \"ma khditech\"", 130 | "I did not work is \"ma khdemtech\"", 131 | "I did not go is \"ma mchitech\"", 132 | ]) 133 | ``` 134 | 135 | ```text 136 | ['Negation in Tunisian Arabic uses "ma" + verb + "tech" where "ma" means "not" and "tech" at the end indicates the negation in the past tense.'] 137 | ``` 138 | 139 | This can help you end up with compressed knowledge that fits better the limited context length of LLMs. 140 | For instance, instead of trying to fit code examples in the LLM's context, use this to prompt it to understand high level rules and fit them in the context. 141 | 142 | ### Natural language condition 143 | Introduce intelligent conditions to your code and let the LLM make decisions 144 | ```python 145 | llm.condition(f'Does this represent an error message ? "IndentationError: unexpected indent"') 146 | ``` 147 | ```text 148 | True 149 | ``` 150 | ### Natural language select 151 | Alternatively, let the LLM choose among a list of options: 152 | ```python 153 | llm.select( 154 | question="Which animal is the king of the jungle?", 155 | options=["Lion", "Elephant", "Tiger", "Giraffe"] 156 | ) 157 | ``` 158 | ```text 159 | ['Lion'] 160 | ``` 161 | 162 | You can also prompt the LLM to choose an exact number of answers using `num_choices`. By default, it's set to `None` which means the LLM will select any number he thinks it's correct. 163 | ## Use Cases 🚀 164 | Find out below example demos you can do with `thinkgpt` 165 | ### Teaching ThinkGPT a new language 166 | ```python 167 | from thinkgpt.llm import ThinkGPT 168 | 169 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 170 | 171 | rules = llm.abstract(observations=[ 172 | "in tunisian, I did not eat is \"ma khditech\"", 173 | "I did not work is \"ma khdemtech\"", 174 | "I did not go is \"ma mchitech\"", 175 | ], instruction_hint="output the rule in french") 176 | llm.memorize(rules) 177 | 178 | llm.memorize("in tunisian, I studied is \"9rit\"") 179 | 180 | task = "translate to Tunisian: I didn't study" 181 | llm.predict(task, remember=llm.remember(task)) 182 | ``` 183 | ```text 184 | The translation of "I didn't study" to Tunisian language would be "ma 9ritech". 185 | ``` 186 | 187 | ### Teaching ThinkGPT how to code with `thinkgpt` library 188 | ```python 189 | from thinkgpt.llm import ThinkGPT 190 | from examples.knowledge_base import knowledge 191 | 192 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 193 | 194 | llm.memorize(knowledge) 195 | 196 | task = 'Implement python code that uses thinkgpt to learn about docarray v2 code and then predict with remembered information about docarray v2. Only give the code between `` and nothing else' 197 | print(llm.predict(task, remember=llm.remember(task, limit=10, sort_by_order=True))) 198 | ``` 199 | 200 | Code generated by the LLM: 201 | ```text 202 | from thinkgpt.llm import ThinkGPT 203 | from docarray import BaseDoc 204 | from docarray.typing import TorchTensor, ImageUrl 205 | 206 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 207 | 208 | # Memorize information 209 | llm.memorize('DocArray V2 allows you to represent your data, in an ML-native way') 210 | 211 | 212 | # Predict with the memory 213 | memory = llm.remember('DocArray V2') 214 | llm.predict('write python code about DocArray v2', remember=memory) 215 | ``` 216 | ### Replay Agent memory and infer new observations 217 | Refer to the following script for an example of an Agent that replays its memory and induces new observations. 218 | This concept was introduced in [the Generative Agents: Interactive Simulacra of Human Behavior paper](https://arxiv.org/abs/2304.03442). 219 | 220 | ```shell 221 | python -m examples.replay_expand_memory 222 | ``` 223 | ```text 224 | new thoughts: 225 | Klaus Mueller is interested in multiple topics 226 | Klaus Mueller may have a diverse range of interests and hobbies 227 | ``` 228 | 229 | ### Replay Agent memory, criticize and refine the knowledge in memory 230 | Refer to the following script for an example of an Agent that replays its memory, performs self-criticism and adjusts its memory knowledge based on the criticism. 231 | ```shell 232 | python -m examples.replay_criticize_refine 233 | ``` 234 | ```text 235 | refined "the second number in Fibonacci sequence is 2" into "Observation: The second number in the Fibonacci sequence is actually 1, not 2, and the sequence starts with 0, 1." 236 | ... 237 | ``` 238 | This technique was mainly implemented in the [the Self-Refine: Iterative Refinement with Self-Feedback paper](https://arxiv.org/abs/2303.17651) 239 | 240 | 241 | For more detailed usage and code examples check `./examples`. 242 | 243 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/thinkgpt/59dd7e8a881d7c6cf280c140344a72321bc289d6/examples/__init__.py -------------------------------------------------------------------------------- /examples/code_self_refinement.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.llm import ThinkGPT 2 | 3 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 4 | 5 | print(llm.refine( 6 | content=""" 7 | import re 8 | print('hello world') 9 | """, 10 | critics=[ 11 | 'File "/Users/alaeddine/Library/Application Support/JetBrains/PyCharm2022.3/scratches/scratch_166.py", line 2', 12 | " print('hello world')", 13 | 'IndentationError: unexpected indent' 14 | ], 15 | instruction_hint="Fix the code snippet based on the error provided. Only provide the fixed code snippet between `` and nothing else.")) 16 | -------------------------------------------------------------------------------- /examples/knowledge_base.py: -------------------------------------------------------------------------------- 1 | knowledge = [ 2 | """ 3 | LangChain is a python framework for developing applications powered by language models. 4 | """, 5 | """ 6 | Langchain offers the OpenAI class, use it when you want to send a request to OpenAI LLMs 7 | """, 8 | """ 9 | You can import OpenAI class from langchain like this: from langchain.llms import OpenAI 10 | """, 11 | """ 12 | Use the OpenAI class like so: 13 | llm = OpenAI(model_name="text-ada-001", n=2, best_of=2 14 | llm("Tell me a joke" 15 | 'Why did the chicken cross the road? To get to the other side.' 16 | """, 17 | """ 18 | Available model names in open ai: "text-ada-001", "gpt-3.5-turbo", "gpt-4" 19 | """, 20 | """ 21 | something else that you will almost never need 22 | """, 23 | """ 24 | DocArray allows you to represent your data, in an ML-native way. Put simply, DocArray lets you represent your data in a dataclass-like way, with ML as a first class citizen 25 | """, 26 | """ 27 | Generate a DocARray v2 schema: 28 | from docarray import BaseDoc 29 | from docarray.typing import TorchTensor, ImageUrl 30 | 31 | # Define your data model 32 | class MyDocument(BaseDoc: 33 | description: str 34 | image_url: ImageUrl # could also be VideoUrl, AudioUrl, etc. 35 | image_tensor: TorchTensor[1704, 2272, 3] # you can express tensor shapes! 36 | """, 37 | """ 38 | thinkgpt is a library to prompt LLMs to perform thinking actions 39 | """, 40 | """ 41 | import ThinkGPT from thinkgpt: from thinkgpt.llm import ThinkGPT 42 | """, 43 | """ 44 | instantiate an llm instance from thinkgpt: llm = ThinkGPT(model_name="gpt-3.5-turbo") 45 | """, 46 | """ 47 | memorize information: llm.memorize('DocArray V2 allows you to represent your data, in an ML-native way') 48 | """, 49 | """ 50 | Finally predict with the memory: llm.predict('write python code about DocArray v2', remember=llm.remember('DocArray V2')) 51 | """ 52 | ] 53 | 54 | -------------------------------------------------------------------------------- /examples/natural_language_condition.py: -------------------------------------------------------------------------------- 1 | from examples.knowledge_base import knowledge 2 | from thinkgpt.llm import ThinkGPT 3 | 4 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 5 | 6 | task = 'generate python code that uses langchain to send a request to OpenAI LLM and ask it suggest 3 recipes using gpt-4 model' 7 | if not llm.condition(f'Do you know all the requirements to achieve the following task ? {task}'): 8 | print('learning knowledge') 9 | llm.memorize(knowledge) 10 | 11 | print(llm.predict(task, remember=llm.remember(task, sort_by_order=True, limit=5))) 12 | -------------------------------------------------------------------------------- /examples/replay_criticize_refine.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.llm import ThinkGPT 2 | 3 | llm = ThinkGPT(model_name="gpt-3.5-turbo", verbose=False) 4 | 5 | # Load old memory 6 | old_memory = [ 7 | "I learned about the Fibonacci sequence last week.", 8 | "The Fibonacci sequence starts with 0, 1 and then each subsequent number is the sum of the two preceding ones.", 9 | "I used the Fibonacci sequence in a coding challenge.", 10 | 'the next number in the Fibonacci sequence after 1 is 1', 11 | 'the fifth number in the Fibonacci sequence is 3 (0, 1, 1, 2, 3)', 12 | 'the Fibonacci sequence can be used in mathematical patterns and algorithms.', 13 | 'the second number in Fibonacci sequence is 2' 14 | ] 15 | 16 | # Teach the LLM the old memory 17 | llm.memorize(old_memory) 18 | 19 | refined_memory = [] 20 | for observation in llm.remember(limit=10): 21 | # Reflect on the old memory and self-criticize 22 | critic = llm.predict(f"What critic can you say about the following observation? If there is no critic, just say nothing. Observation: {observation}") 23 | choices = llm.select('given the following observation and criticism, would you remove, refine or keep the observation?\n' 24 | f'Observation: {observation}\n' 25 | f'Critics: {critic}', options=['refine', 'remove', 'keep'], num_choices=1) 26 | 27 | choice = choices[0] if len(choices) == 1 else None 28 | 29 | if choice.lower() == 'refine': 30 | new_observation = llm.refine(content=observation, critics=[critic], instruction_hint='Just give the new observation and nothing else') 31 | print(f'refined "{observation}" into "{new_observation}"') 32 | refined_memory.append(new_observation) 33 | elif choice.lower() == 'remove': 34 | print(f'removed "{observation}"') 35 | pass 36 | elif choice.lower() == 'keep': 37 | refined_memory.append(observation) 38 | 39 | print('------------ new memory ------------') 40 | print('\n'.join(refined_memory)) 41 | 42 | 43 | # Output 44 | # refined "the second number in Fibonacci sequence is 2" into "The corrected observation is that the second number in the Fibonacci sequence is 1." 45 | # ------------ new memory ------------ 46 | # I learned about the Fibonacci sequence last week. 47 | # The Fibonacci sequence starts with 0, 1 and then each subsequent number is the sum of the two preceding ones. 48 | # I used the Fibonacci sequence in a coding challenge. 49 | # the next number in the Fibonacci sequence after 1 is 1 50 | # the fifth number in the Fibonacci sequence is 3 (0, 1, 1, 2, 3) 51 | # the Fibonacci sequence can be used in mathematical patterns and algorithms. 52 | # The corrected observation is that the second number in the Fibonacci sequence is 1. 53 | -------------------------------------------------------------------------------- /examples/replay_expand_memory.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.llm import ThinkGPT 2 | 3 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 4 | 5 | # Load old memory 6 | old_memory = [ 7 | "Klaus Mueller is writing a research paper", 8 | "Klaus Mueller enjoys reading a book on gentrification", 9 | "Klaus Mueller is conversing with Ayesha Khan about exercising" 10 | ] 11 | 12 | # Teach the LLM the old memory 13 | llm.memorize(old_memory) 14 | 15 | # Induce reflections based on the old memory 16 | new_observations = llm.infer(facts=llm.remember()) 17 | print('new thoughts:') 18 | print('\n'.join(new_observations)) 19 | 20 | llm.memorize(new_observations) 21 | -------------------------------------------------------------------------------- /examples/teach_remember_refine.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.helper import PythonREPL 2 | from thinkgpt.llm import ThinkGPT 3 | from examples.knowledge_base import knowledge 4 | 5 | llm = ThinkGPT(model_name="gpt-3.5-turbo", verbose=False) 6 | 7 | llm.memorize(knowledge) 8 | 9 | task = 'generate python code that uses langchain to send a request to OpenAI LLM and ask it suggest 3 recipes using gpt-4 model, Only give the code between `` and nothing else' 10 | 11 | code = llm.predict(task, remember=llm.remember(task, limit=10, sort_by_order=True)) 12 | output, error = PythonREPL().run(code.strip('`')) 13 | if error: 14 | print('found an error to be refined:', error) 15 | code = llm.refine( 16 | code, 17 | instruction_hint='Fix the provided code. Only print the fixed code between `` and nothing else.', 18 | critics=[error], 19 | ) 20 | print(code) 21 | -------------------------------------------------------------------------------- /examples/teach_this_library.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.llm import ThinkGPT 2 | from examples.knowledge_base import knowledge 3 | 4 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 5 | 6 | llm.memorize(knowledge) 7 | 8 | task = 'Implement python code that uses thinkgpt to learn about docarray v2 code and then predict with remembered information about docarray v2. Only give the code between `` and nothing else' 9 | print(llm.predict(task, remember=llm.remember(task, limit=10, sort_by_order=True))) -------------------------------------------------------------------------------- /examples/teach_tunisian.py: -------------------------------------------------------------------------------- 1 | from thinkgpt.llm import ThinkGPT 2 | 3 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 4 | 5 | rules = llm.abstract(observations=[ 6 | "in tunisian, I did not eat is \"ma khditech\"", 7 | "I did not work is \"ma khdemtech\"", 8 | "I did not go is \"ma mchitech\"", 9 | ]) 10 | llm.memorize(rules) 11 | 12 | llm.memorize("in tunisian, I studied is \"9rit\"") 13 | 14 | task = "translate to Tunisian: I didn't study" 15 | print(llm.predict(task, remember=llm.remember(task))) 16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "thinkgpt" 3 | version = "0.0.4" 4 | description = "ThinkGPT is a Python library aimed at implementing Chain of Thoughts for Large Language Models (LLMs), prompting the model to think, reason, and to create generative agents." 5 | authors = ["Alaeddine Abdessalem "] 6 | license = "Apache 2.0" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = ">=3.7,<4.0" 11 | openai = "^0.27.4" 12 | tiktoken = "^0.3.3" 13 | docarray = "<=0.21.0" 14 | langchain = "^0.0.141" 15 | 16 | 17 | [build-system] 18 | requires = ["poetry-core"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /thinkgpt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/thinkgpt/59dd7e8a881d7c6cf280c140344a72321bc289d6/thinkgpt/__init__.py -------------------------------------------------------------------------------- /thinkgpt/abstract.py: -------------------------------------------------------------------------------- 1 | import re 2 | import warnings 3 | from typing import Dict, List, Any 4 | 5 | import numpy as np 6 | from langchain import PromptTemplate, LLMChain 7 | from langchain.schema import LLMResult, BaseOutputParser, Generation 8 | from langchain.llms import BaseLLM, OpenAI 9 | from langchain.chat_models import ChatOpenAI 10 | from langchain.embeddings import OpenAIEmbeddings 11 | from langchain.prompts.few_shot import FewShotPromptTemplate 12 | from docarray import Document, DocumentArray 13 | 14 | from thinkgpt.helper import LineSeparatorOutputParser 15 | 16 | examples = [ 17 | { 18 | "observations": '\n'.join([ 19 | "father stops at red traffic lights", 20 | "cars start moving when the light turns green", 21 | "bike yield to pedestrians at crosswalks with pedestrian signals", 22 | "truck stops at red traffic lights", 23 | ]), 24 | "rules": '\n'.join([ 25 | "drivers must stop at a red traffic light and can move in green lights", 26 | "drivers must yield to pedestrians at designated crosswalks", 27 | ]) 28 | }, 29 | { 30 | "observations": '\n'.join([ 31 | "Consider A a set of (X, Y) pairs", 32 | "first element is (1, 3)", 33 | "second element is (2, 5)", 34 | "third element is (3, 7)", 35 | "forth element is (4, 9)", 36 | ]), 37 | "rules": '\n'.join([ 38 | "The relationship between the first element X and the second element Y in the set A can be described by a function: y = f(x) = 2x - 1", 39 | ]) 40 | }, 41 | { 42 | "observations": '\n'.join([ 43 | "Fridge of mass 70 kg falls in 3 sec from height 4 meters", 44 | "pillow of mass 0.5 kg falls in 3 sec from height 4 meters", 45 | "rock of mass 1 kg falls in 1 sec from height 2 meters", 46 | "paper of mass 10 gram falls in 1 sec from height 2 meters", 47 | ]), 48 | "rules": '\n'.join([ 49 | "all objects fall at the same rate in a vacuum, regardless of their mass", 50 | ]) 51 | }, 52 | ] 53 | 54 | ABSTRACTION_EXAMPLE_PROMPT = PromptTemplate(template=""" 55 | Observations: 56 | {observations} 57 | 58 | Rules: 59 | {rules} 60 | --------- 61 | """, input_variables=["observations", "rules"]) 62 | 63 | 64 | ABSTRACTION_PROMPT = FewShotPromptTemplate( 65 | prefix="Extract rules from the following observations. Put each rule in a separate line. {instruction_hint}", 66 | # TODO: examples should be closes to the prefix/goal using example selector so they are easily applicable to specific use cases 67 | examples=examples, 68 | example_prompt=ABSTRACTION_EXAMPLE_PROMPT, 69 | suffix="Observations:\n{observations}\nRules:", 70 | input_variables=["instruction_hint", "observations"] 71 | ) 72 | 73 | 74 | class RememberOutputParser(BaseOutputParser[Dict]): 75 | 76 | def parse(self, text: str) -> Dict: 77 | # Greedy search for 1st json candidate. 78 | match = re.match(r"^REMEMBER\((.*)\)$", text.strip().strip('"\'.'), re.MULTILINE | re.IGNORECASE | re.DOTALL) 79 | if match: 80 | return {'action': 'REMEMBER', 'value': match.group(1)} 81 | else: 82 | return {'action': 'FINISH', 'value': text.strip()} 83 | 84 | class AbstractChain(LLMChain): 85 | """Prompts the LLM to request to remember memory as needed""" 86 | def __init__(self, **kwargs): 87 | super().__init__(prompt=ABSTRACTION_PROMPT, **kwargs) 88 | 89 | def predict(self, instruction_hint: str = '', **kwargs: Any) -> str: 90 | return super().predict(instruction_hint=instruction_hint, **kwargs) 91 | 92 | 93 | class AbstractMixin: 94 | abstract_chain: AbstractChain 95 | 96 | def abstract(self, observations: List[str], instruction_hint: str = '') -> List[str]: 97 | result = self.abstract_chain.predict( 98 | observations="\n".join(observations), instruction_hint=instruction_hint 99 | ) 100 | return LineSeparatorOutputParser().parse(result) 101 | 102 | 103 | if __name__ == '__main__': 104 | chain = AbstractChain(llm=ChatOpenAI(model_name="gpt-3.5-turbo")) 105 | print(chain.predict(observations="\n".join([ 106 | "in tunisian, I did not eat is \"ma khditech\"", 107 | "I did not work is \"ma khdemtech\"", 108 | "I did not go is \"ma mchitech\"", 109 | ]), instruction_hint="output the rule in french")) 110 | -------------------------------------------------------------------------------- /thinkgpt/condition.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any, Union 2 | 3 | from langchain import PromptTemplate, LLMChain 4 | from langchain.schema import LLMResult, BaseOutputParser, Generation 5 | from langchain.llms import OpenAI, BaseLLM 6 | from langchain.prompts.few_shot import FewShotPromptTemplate 7 | from langchain.chat_models import ChatOpenAI 8 | 9 | CONDITION_EXAMPLES = [ 10 | { 11 | "question": "Is the sky blue?", 12 | "answer": "True" 13 | }, 14 | { 15 | "question": "Do humans have three arms?", 16 | "answer": "False" 17 | }, 18 | { 19 | "question": "Is water wet?", 20 | "answer": "True" 21 | }, 22 | ] 23 | 24 | CONDITION_EXAMPLE_PROMPT = PromptTemplate(template=""" 25 | Question: 26 | {question} 27 | 28 | Answer: 29 | {answer} 30 | --------- 31 | """, input_variables=["question", "answer"]) 32 | 33 | CONDITION_PROMPT = FewShotPromptTemplate( 34 | prefix="Determine whether the following statement is true or false. Only reply by true or false and nothing else. {instruction_hint}", 35 | examples=CONDITION_EXAMPLES, 36 | example_prompt=CONDITION_EXAMPLE_PROMPT, 37 | suffix="Question:\n{question}\nAnswer:", 38 | input_variables=["instruction_hint", "question"] 39 | ) 40 | 41 | 42 | class ConditionOutputParser(BaseOutputParser[Union[bool, str]]): 43 | 44 | def parse(self, text: str) -> Union[bool, str]: 45 | text = text.strip().lower() 46 | if text == "true": 47 | return True 48 | elif text == "false": 49 | return False 50 | else: 51 | return "Wrong format of the answer" 52 | 53 | 54 | class ConditionChain(LLMChain): 55 | def __init__(self, **kwargs): 56 | super().__init__(prompt=CONDITION_PROMPT, **kwargs) 57 | 58 | def predict(self, question: str, instruction_hint: str = '', **kwargs: Any) -> bool: 59 | result = super().predict(question=question, instruction_hint=instruction_hint, **kwargs) 60 | return ConditionOutputParser().parse(result) 61 | 62 | 63 | class ConditionMixin: 64 | condition_chain: ConditionChain 65 | 66 | def condition(self, question: str, instruction_hint: str = '') -> bool: 67 | return self.condition_chain.predict(question=question, instruction_hint=instruction_hint) 68 | 69 | 70 | if __name__ == '__main__': 71 | chain = ConditionChain(llm=ChatOpenAI(model_name="gpt-3.5-turbo")) 72 | print(chain.predict(question="Is 2+2 equal to 4?", instruction_hint="")) 73 | -------------------------------------------------------------------------------- /thinkgpt/gpt_select.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any, Union, Optional 2 | 3 | from langchain import PromptTemplate, LLMChain 4 | from langchain.schema import LLMResult, BaseOutputParser, Generation 5 | from langchain.llms import OpenAI, BaseLLM 6 | from langchain.prompts.few_shot import FewShotPromptTemplate 7 | from langchain.chat_models import ChatOpenAI 8 | 9 | SELECT_EXAMPLES = [ 10 | { 11 | "question": "Which animal is known as the king of the jungle?", 12 | "options_text": '\n'.join(["Lion", "Elephant", "Tiger", "Giraffe"]), 13 | "answer": "Lion" 14 | }, 15 | { 16 | "question": "What color do you get when you mix blue and yellow?", 17 | "options_text": '\n'.join(["Green", "Orange", "Purple", "Brown"]), 18 | "answer": "Green" 19 | }, 20 | { 21 | "question": "Which animal is a carnivore?", 22 | "options_text": '\n'.join(["Lion", "Elephant", "Tiger", "Giraffe"]), 23 | "answer": "Lion\nTiger" 24 | }, 25 | { 26 | "question": "Which of these are plants?", 27 | "options_text": '\n'.join(["Lily", "Giraffe", "Cactus", "Rose"]), 28 | "answer": "Lily\nGiraffe\nCactus\nRose" 29 | }, 30 | ] 31 | 32 | SELECT_EXAMPLE_PROMPT = PromptTemplate(template=""" 33 | Question: 34 | {question} 35 | 36 | Options: 37 | {options_text} 38 | 39 | Answer: 40 | {answer} 41 | --------- 42 | """, input_variables=["question", "options_text", "answer"]) 43 | 44 | 45 | def format_options(options: List[str]) -> str: 46 | return "\n".join(options) 47 | 48 | 49 | 50 | 51 | class SelectOutputParser(BaseOutputParser[List[str]]): 52 | options: List[str] 53 | 54 | def parse(self, text: str) -> List[str]: 55 | results = [] 56 | answers = text.split('\n') 57 | for answer in answers: 58 | if answer.strip().lower() in [option.lower() for option in self.options]: 59 | results.append(answer.strip()) 60 | return results 61 | 62 | 63 | class SelectChain(LLMChain): 64 | def __init__(self, select_examples: Optional[List] = None, **kwargs): 65 | SELECT_PROMPT = FewShotPromptTemplate( 66 | prefix="Choose the correct answer(s) for the following question from the provided options. If there are " 67 | "many answers, return each one in a separate line. {num_choices_hint}", 68 | examples=select_examples or SELECT_EXAMPLES, 69 | example_prompt=SELECT_EXAMPLE_PROMPT, 70 | suffix="{instruction_hint}\nQuestion:\n{question}\nOptions:\n{options_text}\nAnswer:\n", 71 | input_variables=["instruction_hint", "question", "options_text", "num_choices_hint"] 72 | ) 73 | super().__init__(prompt=SELECT_PROMPT, **kwargs) 74 | 75 | def predict(self, question: str, options: List[str], instruction_hint: str = '', num_choices: int = None, **kwargs: Any) -> List[str]: 76 | num_choices_hint = f"Return exactly {num_choices} answer" if num_choices else '' 77 | options_text = format_options(options) 78 | result = super().predict(question=question, options_text=options_text, instruction_hint=instruction_hint, num_choices_hint=num_choices_hint, 79 | **kwargs) 80 | return SelectOutputParser(options=options).parse(result) 81 | 82 | 83 | class SelectMixin: 84 | select_chain: SelectChain 85 | 86 | def select(self, question: str, options: List[str], instruction_hint: str = '', select_chain: Optional[SelectChain] = None, num_choices: int = None) -> List[str]: 87 | chain = select_chain or self.select_chain 88 | return chain.predict(question=question, options=options, instruction_hint=instruction_hint, num_choices=num_choices) 89 | 90 | 91 | if __name__ == '__main__': 92 | chain = SelectChain(llm=ChatOpenAI(model_name="gpt-3.5-turbo")) 93 | print(chain.predict(question="Which animal is known as the king of the jungle?", 94 | options=["Lion", "Elephant", "Tiger", "Giraffe"], instruction_hint="")) 95 | -------------------------------------------------------------------------------- /thinkgpt/helper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from io import StringIO 3 | from typing import List, Optional, Dict, Tuple 4 | 5 | from langchain.schema import LLMResult, BaseOutputParser 6 | from pydantic.fields import Field 7 | from pydantic.main import BaseModel 8 | 9 | 10 | class LineSeparatorOutputParser(BaseOutputParser[List]): 11 | 12 | def parse(self, text: str) -> List[str]: 13 | return text.split('\n') 14 | 15 | 16 | class PythonREPL(BaseModel): 17 | """Simulates a standalone Python REPL.""" 18 | 19 | globals: Optional[Dict] = Field(default_factory=dict, alias="_globals") 20 | locals: Optional[Dict] = Field(default_factory=dict, alias="_locals") 21 | 22 | def run(self, command: str) -> Tuple[str, Optional[str]]: 23 | """Run command with own globals/locals and returns anything printed.""" 24 | old_stdout = sys.stdout 25 | sys.stdout = mystdout = StringIO() 26 | error = None 27 | try: 28 | exec(command, self.globals, self.locals) 29 | sys.stdout = old_stdout 30 | output = mystdout.getvalue() 31 | except Exception as e: 32 | sys.stdout = old_stdout 33 | output = mystdout.getvalue() 34 | # TODO: need stack trace as well 35 | error = str(e) 36 | return output, error 37 | 38 | 39 | def get_n_tokens(input: str, model_name: str = 'gpt-3.5-turbo'): 40 | import tiktoken 41 | enc = tiktoken.encoding_for_model(model_name) 42 | res = enc.encode(input) 43 | return len(res) 44 | 45 | 46 | def fit_context(text_elememts: List[str], max_tokens: int): 47 | results = [] 48 | total_tokens = 0 49 | for element in text_elememts: 50 | total_tokens += get_n_tokens(element) 51 | if total_tokens <= max_tokens: 52 | results.append(element) 53 | else: 54 | break 55 | return results -------------------------------------------------------------------------------- /thinkgpt/infer.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Dict, List, Any 3 | 4 | from langchain import PromptTemplate, LLMChain 5 | from langchain.llms import OpenAI, BaseLLM 6 | from langchain.prompts.few_shot import FewShotPromptTemplate 7 | from langchain.chat_models import ChatOpenAI 8 | 9 | from thinkgpt.helper import LineSeparatorOutputParser 10 | 11 | INFERENCE_EXAMPLE_PROMPT = PromptTemplate(template=""" 12 | Facts: 13 | {facts} 14 | 15 | New Observations: 16 | {new_observations} 17 | --------- 18 | """, input_variables=["facts", "new_observations"]) 19 | 20 | examples = [ 21 | { 22 | "facts": '\n'.join([ 23 | "water boils at 100 degrees Celsius", 24 | "water freezes at 0 degrees Celsius", 25 | ]), 26 | "new_observations": '\n'.join([ 27 | "ice can be formed by cooling water to 0 degrees Celsius or below", 28 | "steam can be produced by heating water to 100 degrees Celsius or above", 29 | ]) 30 | }, 31 | { 32 | "facts": '\n'.join([ 33 | "plants need sunlight, water, and carbon dioxide to perform photosynthesis", 34 | "photosynthesis is the process by which plants produce glucose and oxygen", 35 | ]), 36 | "new_observations": '\n'.join([ 37 | "a plant placed in a dark room will not be able to perform photosynthesis and may eventually die", 38 | "providing a plant with enough sunlight, water, and carbon dioxide will help it grow and produce oxygen", 39 | ]) 40 | }, 41 | ] 42 | 43 | INFERENCE_PROMPT = FewShotPromptTemplate( 44 | prefix="Based on the following facts, infer new observations. Put each new observation in a separate line. {instruction_hint}", 45 | examples=examples, 46 | example_prompt=INFERENCE_EXAMPLE_PROMPT, 47 | suffix="Facts:\n{facts}\nNew Observations:", 48 | input_variables=["instruction_hint", "facts"] 49 | ) 50 | 51 | 52 | class InferChain(LLMChain): 53 | """Prompts the LLM to generate new observations based on the given facts""" 54 | def __init__(self, **kwargs): 55 | super().__init__(prompt=INFERENCE_PROMPT, **kwargs) 56 | 57 | def predict(self, instruction_hint: str = '', **kwargs: Any) -> str: 58 | return super().predict(instruction_hint=instruction_hint, **kwargs) 59 | 60 | 61 | class InferMixin: 62 | infer_chain: InferChain 63 | 64 | def infer(self, facts: List[str], instruction_hint: str = '') -> List[str]: 65 | result = self.infer_chain.predict( 66 | facts="\n".join(facts), instruction_hint=instruction_hint 67 | ) 68 | return LineSeparatorOutputParser().parse(result) 69 | 70 | 71 | if __name__ == '__main__': 72 | chain = InferChain(llm=ChatOpenAI(model_name="gpt-3.5-turbo")) 73 | # examples from the paper https://arxiv.org/abs/2304.03442 74 | print(chain.predict(facts="\n".join([ 75 | "Klaus Mueller is writing a research paper", 76 | "Klaus Mueller enjoys reading a book on gentrification", 77 | "Klaus Mueller is conversing with Ayesha Khan about exercising" 78 | ]))) 79 | -------------------------------------------------------------------------------- /thinkgpt/llm.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List, Optional, Type, Dict, Any, Union, Iterable 3 | 4 | from langchain.llms import OpenAI 5 | from langchain.chat_models import ChatOpenAI 6 | from langchain.schema import LLMResult, BaseOutputParser, Generation 7 | from langchain.embeddings import OpenAIEmbeddings 8 | from docarray import DocumentArray, Document 9 | from pydantic.config import Extra 10 | from thinkgpt.helper import PythonREPL 11 | 12 | from thinkgpt.abstract import AbstractMixin, AbstractChain 13 | from thinkgpt.condition import ConditionMixin, ConditionChain 14 | from thinkgpt.infer import InferMixin, InferChain 15 | from thinkgpt.memory import MemoryMixin, ExecuteWithContextChain 16 | from thinkgpt.refine import RefineMixin, RefineChain 17 | from thinkgpt.gpt_select import SelectChain, SelectMixin 18 | 19 | from thinkgpt.summarize import SummarizeMixin, SummarizeChain 20 | 21 | 22 | 23 | class ThinkGPT(ChatOpenAI, MemoryMixin, AbstractMixin, RefineMixin, ConditionMixin, SelectMixin, InferMixin, SummarizeMixin, extra=Extra.allow): 24 | """Wrapper around OpenAI large language models to augment it with memory 25 | 26 | To use, you should have the ``openai`` python package installed, and the 27 | environment variable ``OPENAI_API_KEY`` set with your API key. 28 | """ 29 | 30 | def __init__(self, 31 | memory: DocumentArray = None, 32 | execute_with_context_chain: ExecuteWithContextChain = None, 33 | abstract_chain: AbstractChain = None, 34 | refine_chain: RefineChain = None, 35 | condition_chain: ConditionChain = None, 36 | select_chain: SelectChain = None, 37 | infer_chain: InferChain = None, 38 | summarize_chain: SummarizeChain = None, 39 | verbose=True, 40 | # TODO: model name can be specified per mixin 41 | **kwargs 42 | ): 43 | super().__init__(**kwargs) 44 | # TODO: offer more docarray backends 45 | self.memory = memory or DocumentArray() 46 | self.embeddings_model = OpenAIEmbeddings() 47 | self.openai = ChatOpenAI(**kwargs) 48 | self.execute_with_context_chain = execute_with_context_chain or ExecuteWithContextChain( 49 | llm=self.openai, verbose=verbose) 50 | self.abstract_chain = abstract_chain or AbstractChain( 51 | llm=self.openai, verbose=verbose) 52 | self.refine_chain = refine_chain or RefineChain( 53 | llm=self.openai, verbose=verbose) 54 | self.condition_chain = condition_chain or ConditionChain( 55 | llm=self.openai, verbose=verbose) 56 | self.select_chain = select_chain or SelectChain(llm=self.openai, verbose=verbose) 57 | self.infer_chain = infer_chain or InferChain(llm=self.openai, verbose=verbose) 58 | self.summarize_chain = summarize_chain or SummarizeChain(llm=self.openai, verbose=verbose) # Add this line 59 | self.mem_cnt = 0 60 | 61 | 62 | def generate( 63 | self, prompts: List[List[str]], stop: Optional[List[str]] = None, remember: Union[int, List[str]] = 0 64 | ) -> LLMResult: 65 | # only works for single prompt 66 | if len(prompts) > 1: 67 | raise Exception('only works for a single prompt') 68 | prompt = prompts[0][0] 69 | if isinstance(remember, int) and remember > 0: 70 | remembered_elements = self.remember(prompt, limit=5) 71 | result = self.execute_with_context_chain.predict(prompt=prompt, context='\n'.join(remembered_elements) if remembered_elements else 'Nothing') 72 | elif isinstance(remember, list): 73 | result = self.execute_with_context_chain.predict(prompt=prompt, context='\n'.join(remember)) 74 | else: 75 | result = self.execute_with_context_chain.predict(prompt=prompt, context='Nothing') 76 | 77 | return LLMResult(generations=[[Generation(text=result)]]) 78 | 79 | def predict( 80 | self, prompt: str, stop: Optional[List[str]] = None, remember: Union[int, List[str]] = 0 81 | ) -> str: 82 | return self.generate([[prompt]], remember=remember).generations[0][0].text 83 | 84 | 85 | if __name__ == '__main__': 86 | llm = ThinkGPT(model_name="gpt-3.5-turbo") 87 | 88 | rules = llm.abstract(observations=[ 89 | "in tunisian, I did not eat is \"ma khditech\"", 90 | "I did not work is \"ma khdemtech\"", 91 | "I did not go is \"ma mchitech\"", 92 | ], instruction_hint="output the rule in french") 93 | llm.memorize(rules) 94 | 95 | llm.memorize("in tunisian, I went is \"mchit\"") 96 | 97 | task = "translate to Tunisian: I didn't go" 98 | print(llm.predict(task, remember=llm.remember(task))) 99 | -------------------------------------------------------------------------------- /thinkgpt/memory.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Dict, List, Union, Optional 3 | 4 | import numpy as np 5 | from langchain import PromptTemplate, LLMChain 6 | from langchain.embeddings import OpenAIEmbeddings 7 | from langchain.llms import OpenAI, BaseLLM 8 | from docarray import Document, DocumentArray 9 | 10 | from thinkgpt.helper import get_n_tokens, fit_context 11 | 12 | EXECUTE_WITH_CONTEXT_PROMPT = PromptTemplate(template=""" 13 | Given a context information, reply to the provided request 14 | Context: {context} 15 | User request: {prompt} 16 | """, input_variables=["prompt", "context"], ) 17 | 18 | 19 | 20 | class ExecuteWithContextChain(LLMChain): 21 | """Prompts the LLM to execute a request with potential context""" 22 | def __init__(self, **kwargs): 23 | super().__init__(prompt=EXECUTE_WITH_CONTEXT_PROMPT, **kwargs) 24 | 25 | 26 | class MemoryMixin: 27 | memory: DocumentArray 28 | mem_cnt: int 29 | embeddings_model: OpenAIEmbeddings 30 | 31 | def memorize(self, concept: Union[str, Document, DocumentArray, List]): 32 | self.mem_cnt += 1 33 | if isinstance(concept, str): 34 | doc = Document(text=concept, embedding=np.asarray(self.embeddings_model.embed_query(concept)), tags={'mem_cnt': self.mem_cnt}) 35 | self.memory.append(doc) 36 | elif isinstance(concept, Document): 37 | assert concept.embedding is not None 38 | concept.tags['mem_cnt'] = self.mem_cnt 39 | self.memory.append(concept) 40 | elif isinstance(concept, (DocumentArray, list)): 41 | for doc in concept: 42 | self.memorize(doc) 43 | else: 44 | raise ValueError('wrong type, must be either str, Document, DocumentArray, List') 45 | 46 | def remember(self, concept: Union[str, Document] = None, limit: int = 5, sort_by_order: bool = False, max_tokens: Optional[int] = None) -> List[str]: 47 | if len(self.memory) == 0: 48 | return [] 49 | if concept is None: 50 | return [doc.text for doc in self.memory[-limit:]] 51 | elif isinstance(concept, str): 52 | query_input = Document(embedding=np.asarray(self.embeddings_model.embed_query(concept))) 53 | elif isinstance(concept, Document): 54 | assert concept.embedding is not None 55 | query_input = concept 56 | else: 57 | raise ValueError('wrong type, must be either str or Document') 58 | 59 | docs = self.memory.find(query_input, limit=limit)[0] 60 | # memory needs to be sorted in chronological order 61 | if sort_by_order: 62 | docs = sorted(docs, key=lambda doc: doc.tags['mem_cnt']) 63 | text_results = [doc.text for doc in docs] 64 | if max_tokens: 65 | text_results = fit_context(text_results, max_tokens) 66 | return text_results 67 | -------------------------------------------------------------------------------- /thinkgpt/refine.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Dict, List, Any 3 | 4 | from langchain import PromptTemplate, LLMChain 5 | from langchain.llms import OpenAI, BaseLLM 6 | from langchain.chat_models import ChatOpenAI 7 | 8 | 9 | REFINE_PROMPT = PromptTemplate(template=""" 10 | Based on the critics, fix the content provided to you. {instruction_hint}: 11 | content: 12 | {content} 13 | --------- 14 | critics: 15 | {critics} 16 | --------- 17 | """, input_variables=["critics", "content", "instruction_hint"]) 18 | 19 | 20 | class RefineChain(LLMChain): 21 | """Prompts the LLM to request to remember memory as needed""" 22 | def __init__(self, **kwargs): 23 | super().__init__(prompt=REFINE_PROMPT, **kwargs) 24 | 25 | 26 | def predict(self, instruction_hint: str = '', **kwargs: Any) -> str: 27 | return super().predict(instruction_hint=instruction_hint, **kwargs) 28 | 29 | 30 | class RefineMixin: 31 | refine_chain: RefineChain 32 | 33 | def refine(self, content: str, critics: List[str], instruction_hint: str = '') -> str: 34 | return self.refine_chain.predict( 35 | content=content, critics="\n".join(critics), instruction_hint=instruction_hint 36 | ) 37 | 38 | 39 | if __name__ == '__main__': 40 | chain = RefineChain(llm=ChatOpenAI(model_name='gpt-3.5-turbo')) 41 | print(chain.predict( 42 | content=""" 43 | import re 44 | print('hello world') 45 | """, 46 | critics=""" 47 | File "/Users/alaeddine/Library/Application Support/JetBrains/PyCharm2022.3/scratches/scratch_166.py", line 2 48 | print('hello world') 49 | IndentationError: unexpected indent 50 | """, instruction_hint="Fix the code snippet based on the error provided. Only provide the fixed code snippet between `` and nothing else.")) 51 | -------------------------------------------------------------------------------- /thinkgpt/summarize.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | from pydantic.config import Extra 4 | import warnings 5 | from typing import Dict, List, Any 6 | 7 | from langchain import PromptTemplate, LLMChain 8 | from langchain.llms import OpenAI, BaseLLM 9 | from langchain.chat_models import ChatOpenAI 10 | 11 | SUMMARIZE_PROMPT = PromptTemplate(template=""" 12 | Shorten the following memory chunk of an autonomous agent from a first person perspective, using at most {max_tokens} tokens. {instruction_hint}: 13 | content: 14 | {content} 15 | --------- 16 | """, input_variables=["content", "instruction_hint", "max_tokens"]) 17 | 18 | 19 | class SummarizeChain(LLMChain, extra=Extra.allow): 20 | """Prompts the LLM to summarize content as needed""" 21 | def __init__(self, 22 | summarizer_chunk_size: int = 3000, 23 | **kwargs 24 | ): 25 | super().__init__(prompt=SUMMARIZE_PROMPT, **kwargs) 26 | self.summarizer_chunk_size = summarizer_chunk_size 27 | 28 | def predict(self, content, **kwargs: Any) -> str: 29 | return super().predict( 30 | content=content, 31 | **kwargs) 32 | 33 | 34 | class SummarizeMixin: 35 | summarize_chain: SummarizeChain 36 | 37 | def summarize(self, content: str, max_tokens: int = 4096, instruction_hint: str = '') -> str: 38 | response = self.summarize_chain.predict( 39 | # TODO: should retrieve max tokens from the llm if None 40 | content=content, instruction_hint=instruction_hint, max_tokens=max_tokens 41 | ) 42 | return response 43 | 44 | def chunked_summarize(self, content: str, max_tokens: int = 4096, instruction_hint: str = '') -> str: 45 | num_tokens = self.summarize_chain.llm.get_num_tokens(content) 46 | 47 | if num_tokens > max_tokens: 48 | avg_chars_per_token = len(content) / num_tokens 49 | chunk_size = int(avg_chars_per_token * self.summarize_chain.summarizer_chunk_size) 50 | chunks = textwrap.wrap(content, chunk_size) 51 | summary_size = int(max_tokens / len(chunks)) 52 | result = "" 53 | 54 | 55 | for chunk in chunks: 56 | result += self.summarize(content=chunk, max_tokens=summary_size, instruction_hint=instruction_hint) 57 | else: 58 | return content 59 | return result 60 | 61 | 62 | if __name__ == '__main__': 63 | chain = SummarizeChain(llm=ChatOpenAI(model_name="gpt-3.5-turbo")) 64 | print(chain.predict( 65 | content="""Artificial intelligence (AI) is intelligence demonstrated by machines, 66 | unlike the natural intelligence displayed by humans and animals, which involves 67 | consciousness and emotionality. The distinction between the former and the latter 68 | categories is often revealed by the acronym chosen. 'Strong' AI is usually labelled 69 | as AGI (Artificial General Intelligence) while attempts to emulate 'natural' 70 | intelligence have been called ABI (Artificial Biological Intelligence).""", 71 | instruction_hint="Keep the summary concise and within 50 words.", 72 | max_tokens=50)) 73 | --------------------------------------------------------------------------------