├── .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 |
--------------------------------------------------------------------------------