langroid 0.1.26__tar.gz → 0.1.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langroid-0.1.26 → langroid-0.1.28}/PKG-INFO +32 -14
- {langroid-0.1.26 → langroid-0.1.28}/README.md +30 -13
- langroid-0.1.28/langroid/cachedb/momento_cachedb.py +78 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/base.py +2 -4
- {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/openai_gpt.py +9 -2
- langroid-0.1.28/langroid/prompts/transforms.py +84 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/configuration.py +1 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/logging.py +3 -5
- {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/chromadb.py +2 -3
- {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/qdrantdb.py +2 -3
- {langroid-0.1.26 → langroid-0.1.28}/pyproject.toml +3 -2
- langroid-0.1.28/setup.py +87 -0
- langroid-0.1.26/langroid/prompts/transforms.py +0 -236
- langroid-0.1.26/setup.py +0 -86
- {langroid-0.1.26 → langroid-0.1.28}/LICENSE +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/base.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/chat_agent.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/chat_document.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/helpers.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/junk +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/doc_chat_agent.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/recipient_validator_agent.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/retriever_agent.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/task.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/tool_message.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/agent_config.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/base.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/redis_cachedb.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/base.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/clustering.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/models.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/utils.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/mytypes.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/agent_chats.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/code-parsing.md +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/code_parser.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/json.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/para_sentence_split.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/parser.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/pdf_parser.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/repo_loader.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/url_loader.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/url_loader_cookies.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/urls.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/utils.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/dialog.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/prompts_config.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/templates.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/scripts/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/constants.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/docker.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/llms/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/llms/strings.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/output/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/output/printing.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/system.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/login.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/selenium_login.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/__init__.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/base.py +0 -0
- {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/qdrant_cloud.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.28
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -30,6 +30,7 @@ Requires-Dist: mkdocs-literate-nav (>=0.6.0,<0.7.0)
|
|
30
30
|
Requires-Dist: mkdocs-material (>=9.1.5,<10.0.0)
|
31
31
|
Requires-Dist: mkdocs-section-index (>=0.3.5,<0.4.0)
|
32
32
|
Requires-Dist: mkdocstrings[python] (>=0.21.2,<0.22.0)
|
33
|
+
Requires-Dist: momento (>=1.7.0,<2.0.0)
|
33
34
|
Requires-Dist: mypy (>=1.2.0,<2.0.0)
|
34
35
|
Requires-Dist: nltk (>=3.8.1,<4.0.0)
|
35
36
|
Requires-Dist: openai (>=0.27.5,<0.28.0)
|
@@ -64,6 +65,7 @@ Description-Content-Type: text/markdown
|
|
64
65
|
<div align="center">
|
65
66
|
|
66
67
|
[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
|
68
|
+
[](https://codecov.io/gh/langroid/langroid)
|
67
69
|
[](https://github.com/langroid/langroid/actions/workflows/validate.yml)
|
68
70
|
[](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)
|
69
71
|
[](https://langroid.github.io/langroid)
|
@@ -74,7 +76,7 @@ Description-Content-Type: text/markdown
|
|
74
76
|
<h3 align="center">
|
75
77
|
<a target="_blank"
|
76
78
|
href="https://langroid.github.io/langroid/" rel="dofollow">
|
77
|
-
<strong>
|
79
|
+
<strong>Documentation</strong></a>
|
78
80
|
·
|
79
81
|
<a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">
|
80
82
|
<strong>Examples Repo</strong></a>
|
@@ -96,17 +98,19 @@ collaboratively solve a problem by exchanging messages.
|
|
96
98
|
This Multi-Agent paradigm is inspired by the
|
97
99
|
[Actor Framework](https://en.wikipedia.org/wiki/Actor_model)
|
98
100
|
(but you do not need to know anything about this!).
|
101
|
+
We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
|
102
|
+
for ideas on what to contribute.
|
99
103
|
|
100
104
|
|
101
105
|
# :rocket: Demo
|
102
106
|
Suppose you want to extract structured information about the key terms
|
103
107
|
of a commercial lease document. You can easily do this with Langroid using a two-agent system,
|
104
108
|
as we show in the [langroid-examples](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py) repo.
|
105
|
-
The demo showcases
|
109
|
+
The demo showcases just a few of the many features of Langroid, such as:
|
106
110
|
- Multi-agent collaboration: `LeaseExtractor` is in charge of the task, and its LLM (GPT4) generates questions
|
107
111
|
to be answered by the `DocAgent`.
|
108
|
-
- Retrieval augmented question-answering
|
109
|
-
answer the `LeaseExtractor`'s questions.
|
112
|
+
- Retrieval augmented question-answering, with **source-citation**: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to
|
113
|
+
answer the `LeaseExtractor`'s questions, cites the specific excerpt supporting the answer.
|
110
114
|
- Function-calling (also known as tool/plugin): When it has all the information it
|
111
115
|
needs, the `LeaseExtractor` LLM presents the information in a structured
|
112
116
|
format using a Function-call.
|
@@ -157,7 +161,8 @@ Here is what it looks like in action:
|
|
157
161
|
|
158
162
|
# :gear: Installation and Setup
|
159
163
|
|
160
|
-
### Install `langroid`
|
164
|
+
### Install `langroid`
|
165
|
+
Langroid requires Python 3.11+. We recommend using a virtual environment.
|
161
166
|
Use `pip` to install `langroid` (from PyPi) to your virtual environment:
|
162
167
|
```bash
|
163
168
|
pip install langroid
|
@@ -172,19 +177,30 @@ Note that this will install `torch` and `sentence-transformers` libraries.
|
|
172
177
|
|
173
178
|
### Set up environment variables (API keys, etc)
|
174
179
|
|
180
|
+
To get started, all you need is an OpenAI API Key.
|
181
|
+
If you don't have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).
|
182
|
+
Currently only OpenAI models are supported. Others will be added later
|
183
|
+
(Pull Requests welcome!).
|
184
|
+
|
175
185
|
In the root of the repo, copy the `.env-template` file to a new file `.env`:
|
176
186
|
```bash
|
177
187
|
cp .env-template .env
|
178
188
|
```
|
179
|
-
Then insert your OpenAI API Key.
|
189
|
+
Then insert your OpenAI API Key.
|
180
190
|
Your `.env` file should look like this:
|
181
|
-
|
182
191
|
```bash
|
183
192
|
OPENAI_API_KEY=your-key-here-without-quotes
|
184
193
|
````
|
185
194
|
|
186
|
-
|
187
|
-
(
|
195
|
+
Alternatively, you can set this as an environment variable in your shell
|
196
|
+
(you will need to do this every time you open a new shell):
|
197
|
+
```bash
|
198
|
+
export OPENAI_API_KEY=your-key-here-without-quotes
|
199
|
+
```
|
200
|
+
|
201
|
+
|
202
|
+
<details>
|
203
|
+
<summary><b>Optional Setup Instructions (click to expand) </b></summary>
|
188
204
|
|
189
205
|
All of the below are optional and not strictly needed to run any of the examples.
|
190
206
|
|
@@ -198,6 +214,9 @@ All of the below are optional and not strictly needed to run any of the examples
|
|
198
214
|
which is more than sufficient to try out Langroid and even beyond.
|
199
215
|
If you don't set up these, Langroid will use a pure-python
|
200
216
|
Redis in-memory cache via the [Fakeredis](https://fakeredis.readthedocs.io/en/latest/) library.
|
217
|
+
- **Momento** Serverless Caching of LLM API responses (as an alternative to Redis).
|
218
|
+
To use Momento instead of Redis, simply enter your Momento Token in the `.env` file,
|
219
|
+
as the value of `MOMENTO_AUTH_TOKEN` (see example file below).
|
201
220
|
- **GitHub** Personal Access Token (required for apps that need to analyze git
|
202
221
|
repos; token-based API calls are less rate-limited). See this
|
203
222
|
[GitHub page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
|
@@ -209,10 +228,11 @@ GITHUB_ACCESS_TOKEN=your-personal-access-token-no-quotes
|
|
209
228
|
REDIS_PASSWORD=your-redis-password-no-quotes
|
210
229
|
REDIS_HOST=your-redis-hostname-no-quotes
|
211
230
|
REDIS_PORT=your-redis-port-no-quotes
|
231
|
+
MOMENTO_AUTH_TOKEN=your-momento-token-no-quotes # instead of REDIS* variables
|
212
232
|
QDRANT_API_KEY=your-key
|
213
233
|
QDRANT_API_URL=https://your.url.here:6333 # note port number must be included
|
214
234
|
```
|
215
|
-
|
235
|
+
</details>
|
216
236
|
|
217
237
|
---
|
218
238
|
|
@@ -533,9 +553,7 @@ folder of the `langroid-examples` repo.
|
|
533
553
|
|
534
554
|
---
|
535
555
|
|
536
|
-
# :heart: Thank you to our supporters
|
537
|
-
|
538
|
-
[](https://github.com/langroid/langroid/stargazers)
|
556
|
+
# :heart: Thank you to our [supporters](https://github.com/langroid/langroid/stargazers)
|
539
557
|
|
540
558
|
# Contributors
|
541
559
|
|
@@ -6,6 +6,7 @@
|
|
6
6
|
<div align="center">
|
7
7
|
|
8
8
|
[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
|
9
|
+
[](https://codecov.io/gh/langroid/langroid)
|
9
10
|
[](https://github.com/langroid/langroid/actions/workflows/validate.yml)
|
10
11
|
[](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)
|
11
12
|
[](https://langroid.github.io/langroid)
|
@@ -16,7 +17,7 @@
|
|
16
17
|
<h3 align="center">
|
17
18
|
<a target="_blank"
|
18
19
|
href="https://langroid.github.io/langroid/" rel="dofollow">
|
19
|
-
<strong>
|
20
|
+
<strong>Documentation</strong></a>
|
20
21
|
·
|
21
22
|
<a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">
|
22
23
|
<strong>Examples Repo</strong></a>
|
@@ -38,17 +39,19 @@ collaboratively solve a problem by exchanging messages.
|
|
38
39
|
This Multi-Agent paradigm is inspired by the
|
39
40
|
[Actor Framework](https://en.wikipedia.org/wiki/Actor_model)
|
40
41
|
(but you do not need to know anything about this!).
|
42
|
+
We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
|
43
|
+
for ideas on what to contribute.
|
41
44
|
|
42
45
|
|
43
46
|
# :rocket: Demo
|
44
47
|
Suppose you want to extract structured information about the key terms
|
45
48
|
of a commercial lease document. You can easily do this with Langroid using a two-agent system,
|
46
49
|
as we show in the [langroid-examples](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py) repo.
|
47
|
-
The demo showcases
|
50
|
+
The demo showcases just a few of the many features of Langroid, such as:
|
48
51
|
- Multi-agent collaboration: `LeaseExtractor` is in charge of the task, and its LLM (GPT4) generates questions
|
49
52
|
to be answered by the `DocAgent`.
|
50
|
-
- Retrieval augmented question-answering
|
51
|
-
answer the `LeaseExtractor`'s questions.
|
53
|
+
- Retrieval augmented question-answering, with **source-citation**: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to
|
54
|
+
answer the `LeaseExtractor`'s questions, cites the specific excerpt supporting the answer.
|
52
55
|
- Function-calling (also known as tool/plugin): When it has all the information it
|
53
56
|
needs, the `LeaseExtractor` LLM presents the information in a structured
|
54
57
|
format using a Function-call.
|
@@ -99,7 +102,8 @@ Here is what it looks like in action:
|
|
99
102
|
|
100
103
|
# :gear: Installation and Setup
|
101
104
|
|
102
|
-
### Install `langroid`
|
105
|
+
### Install `langroid`
|
106
|
+
Langroid requires Python 3.11+. We recommend using a virtual environment.
|
103
107
|
Use `pip` to install `langroid` (from PyPi) to your virtual environment:
|
104
108
|
```bash
|
105
109
|
pip install langroid
|
@@ -114,19 +118,30 @@ Note that this will install `torch` and `sentence-transformers` libraries.
|
|
114
118
|
|
115
119
|
### Set up environment variables (API keys, etc)
|
116
120
|
|
121
|
+
To get started, all you need is an OpenAI API Key.
|
122
|
+
If you don't have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).
|
123
|
+
Currently only OpenAI models are supported. Others will be added later
|
124
|
+
(Pull Requests welcome!).
|
125
|
+
|
117
126
|
In the root of the repo, copy the `.env-template` file to a new file `.env`:
|
118
127
|
```bash
|
119
128
|
cp .env-template .env
|
120
129
|
```
|
121
|
-
Then insert your OpenAI API Key.
|
130
|
+
Then insert your OpenAI API Key.
|
122
131
|
Your `.env` file should look like this:
|
123
|
-
|
124
132
|
```bash
|
125
133
|
OPENAI_API_KEY=your-key-here-without-quotes
|
126
134
|
````
|
127
135
|
|
128
|
-
|
129
|
-
(
|
136
|
+
Alternatively, you can set this as an environment variable in your shell
|
137
|
+
(you will need to do this every time you open a new shell):
|
138
|
+
```bash
|
139
|
+
export OPENAI_API_KEY=your-key-here-without-quotes
|
140
|
+
```
|
141
|
+
|
142
|
+
|
143
|
+
<details>
|
144
|
+
<summary><b>Optional Setup Instructions (click to expand) </b></summary>
|
130
145
|
|
131
146
|
All of the below are optional and not strictly needed to run any of the examples.
|
132
147
|
|
@@ -140,6 +155,9 @@ All of the below are optional and not strictly needed to run any of the examples
|
|
140
155
|
which is more than sufficient to try out Langroid and even beyond.
|
141
156
|
If you don't set up these, Langroid will use a pure-python
|
142
157
|
Redis in-memory cache via the [Fakeredis](https://fakeredis.readthedocs.io/en/latest/) library.
|
158
|
+
- **Momento** Serverless Caching of LLM API responses (as an alternative to Redis).
|
159
|
+
To use Momento instead of Redis, simply enter your Momento Token in the `.env` file,
|
160
|
+
as the value of `MOMENTO_AUTH_TOKEN` (see example file below).
|
143
161
|
- **GitHub** Personal Access Token (required for apps that need to analyze git
|
144
162
|
repos; token-based API calls are less rate-limited). See this
|
145
163
|
[GitHub page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
|
@@ -151,10 +169,11 @@ GITHUB_ACCESS_TOKEN=your-personal-access-token-no-quotes
|
|
151
169
|
REDIS_PASSWORD=your-redis-password-no-quotes
|
152
170
|
REDIS_HOST=your-redis-hostname-no-quotes
|
153
171
|
REDIS_PORT=your-redis-port-no-quotes
|
172
|
+
MOMENTO_AUTH_TOKEN=your-momento-token-no-quotes # instead of REDIS* variables
|
154
173
|
QDRANT_API_KEY=your-key
|
155
174
|
QDRANT_API_URL=https://your.url.here:6333 # note port number must be included
|
156
175
|
```
|
157
|
-
|
176
|
+
</details>
|
158
177
|
|
159
178
|
---
|
160
179
|
|
@@ -475,9 +494,7 @@ folder of the `langroid-examples` repo.
|
|
475
494
|
|
476
495
|
---
|
477
496
|
|
478
|
-
# :heart: Thank you to our supporters
|
479
|
-
|
480
|
-
[](https://github.com/langroid/langroid/stargazers)
|
497
|
+
# :heart: Thank you to our [supporters](https://github.com/langroid/langroid/stargazers)
|
481
498
|
|
482
499
|
# Contributors
|
483
500
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
from datetime import timedelta
|
5
|
+
from typing import Any, Dict, Optional
|
6
|
+
|
7
|
+
import momento
|
8
|
+
from dotenv import load_dotenv
|
9
|
+
from momento.responses import CacheGet
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
from langroid.cachedb.base import CacheDB
|
13
|
+
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
|
17
|
+
class MomentoCacheConfig(BaseModel):
|
18
|
+
"""Configuration model for RedisCache."""
|
19
|
+
|
20
|
+
ttl: int = 60 * 60 * 24 * 7 # 1 week
|
21
|
+
cachename: str = "langroid_momento_cache"
|
22
|
+
|
23
|
+
|
24
|
+
class MomentoCache(CacheDB):
|
25
|
+
"""Momento implementation of the CacheDB."""
|
26
|
+
|
27
|
+
def __init__(self, config: MomentoCacheConfig):
|
28
|
+
"""
|
29
|
+
Initialize a MomentoCache with the given config.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
config (MomentoCacheConfig): The configuration to use.
|
33
|
+
"""
|
34
|
+
self.config = config
|
35
|
+
load_dotenv()
|
36
|
+
|
37
|
+
momento_token = os.getenv("MOMENTO_AUTH_TOKEN")
|
38
|
+
if momento_token is None:
|
39
|
+
raise ValueError("""MOMENTO_AUTH_TOKEN not set in .env file""")
|
40
|
+
else:
|
41
|
+
self.client = momento.CacheClient(
|
42
|
+
configuration=momento.Configurations.Laptop.v1(),
|
43
|
+
credential_provider=momento.CredentialProvider.from_environment_variable(
|
44
|
+
"MOMENTO_AUTH_TOKEN"
|
45
|
+
),
|
46
|
+
default_ttl=timedelta(seconds=self.config.ttl),
|
47
|
+
)
|
48
|
+
self.client.create_cache(self.config.cachename)
|
49
|
+
|
50
|
+
def clear(self) -> None:
|
51
|
+
"""Clear keys from current db."""
|
52
|
+
self.client.flush_cache(self.config.cachename)
|
53
|
+
|
54
|
+
def store(self, key: str, value: Any) -> None:
|
55
|
+
"""
|
56
|
+
Store a value associated with a key.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
key (str): The key under which to store the value.
|
60
|
+
value (Any): The value to store.
|
61
|
+
"""
|
62
|
+
self.client.set(self.config.cachename, key, json.dumps(value))
|
63
|
+
|
64
|
+
def retrieve(self, key: str) -> Optional[Dict[str, Any]]:
|
65
|
+
"""
|
66
|
+
Retrieve the value associated with a key.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
key (str): The key to retrieve the value for.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
dict: The value associated with the key.
|
73
|
+
"""
|
74
|
+
value = self.client.get(self.config.cachename, key)
|
75
|
+
if isinstance(value, CacheGet.Hit):
|
76
|
+
return json.loads(value.value_string) # type: ignore
|
77
|
+
else:
|
78
|
+
return None
|
@@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
7
7
|
import aiohttp
|
8
8
|
from pydantic import BaseModel, BaseSettings
|
9
9
|
|
10
|
+
from langroid.cachedb.momento_cachedb import MomentoCacheConfig
|
10
11
|
from langroid.cachedb.redis_cachedb import RedisCacheConfig
|
11
12
|
from langroid.mytypes import Document
|
12
13
|
from langroid.parsing.agent_chats import parse_message
|
@@ -32,10 +33,7 @@ class LLMConfig(BaseSettings):
|
|
32
33
|
min_output_tokens: int = 64
|
33
34
|
use_chat_for_completion: bool = True # use chat model for completion?
|
34
35
|
stream: bool = False # stream output from API?
|
35
|
-
cache_config: RedisCacheConfig =
|
36
|
-
hostname="redis-11524.c251.east-us-mz.azure.cloud.redislabs.com",
|
37
|
-
port=11524,
|
38
|
-
)
|
36
|
+
cache_config: None | RedisCacheConfig | MomentoCacheConfig = None
|
39
37
|
|
40
38
|
|
41
39
|
class LLMFunctionCall(BaseModel):
|
@@ -11,7 +11,8 @@ from dotenv import load_dotenv
|
|
11
11
|
from pydantic import BaseModel
|
12
12
|
from rich import print
|
13
13
|
|
14
|
-
from langroid.cachedb.
|
14
|
+
from langroid.cachedb.momento_cachedb import MomentoCache, MomentoCacheConfig
|
15
|
+
from langroid.cachedb.redis_cachedb import RedisCache, RedisCacheConfig
|
15
16
|
from langroid.language_models.base import (
|
16
17
|
LanguageModel,
|
17
18
|
LLMConfig,
|
@@ -92,7 +93,13 @@ class OpenAIGPT(LanguageModel):
|
|
92
93
|
OPENAI_API_KEY not set in .env file,
|
93
94
|
please set it to your OpenAI API key."""
|
94
95
|
)
|
95
|
-
self.cache
|
96
|
+
self.cache: MomentoCache | RedisCache
|
97
|
+
if settings.cache_type == "momento":
|
98
|
+
config.cache_config = MomentoCacheConfig()
|
99
|
+
self.cache = MomentoCache(config.cache_config)
|
100
|
+
else:
|
101
|
+
config.cache_config = RedisCacheConfig()
|
102
|
+
self.cache = RedisCache(config.cache_config)
|
96
103
|
|
97
104
|
def set_stream(self, stream: bool) -> bool:
|
98
105
|
"""Enable or disable streaming output from API.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import List, Tuple
|
3
|
+
|
4
|
+
import aiohttp
|
5
|
+
|
6
|
+
from langroid.language_models.base import LanguageModel
|
7
|
+
from langroid.mytypes import Document
|
8
|
+
from langroid.prompts.dialog import collate_chat_history
|
9
|
+
from langroid.prompts.templates import EXTRACTION_PROMPT
|
10
|
+
|
11
|
+
|
12
|
+
async def get_verbatim_extract_async(
|
13
|
+
question: str,
|
14
|
+
passage: Document,
|
15
|
+
LLM: LanguageModel,
|
16
|
+
) -> str:
|
17
|
+
"""
|
18
|
+
Asynchronously, get verbatim extract from passage that is relevant to a question.
|
19
|
+
"""
|
20
|
+
async with aiohttp.ClientSession():
|
21
|
+
templatized_prompt = EXTRACTION_PROMPT
|
22
|
+
final_prompt = templatized_prompt.format(question=question, content=passage)
|
23
|
+
final_extract = await LLM.agenerate(prompt=final_prompt, max_tokens=1024)
|
24
|
+
|
25
|
+
return final_extract.message.strip()
|
26
|
+
|
27
|
+
|
28
|
+
async def _get_verbatim_extracts(
|
29
|
+
question: str,
|
30
|
+
passages: List[Document],
|
31
|
+
LLM: LanguageModel,
|
32
|
+
) -> List[Document]:
|
33
|
+
async with aiohttp.ClientSession():
|
34
|
+
verbatim_extracts = await asyncio.gather(
|
35
|
+
*(get_verbatim_extract_async(question, P, LLM) for P in passages)
|
36
|
+
)
|
37
|
+
metadatas = [P.metadata for P in passages]
|
38
|
+
# return with metadata so we can use it downstream, e.g. to cite sources
|
39
|
+
return [
|
40
|
+
Document(content=e, metadata=m) for e, m in zip(verbatim_extracts, metadatas)
|
41
|
+
]
|
42
|
+
|
43
|
+
|
44
|
+
def get_verbatim_extracts(
|
45
|
+
question: str,
|
46
|
+
passages: List[Document],
|
47
|
+
LLM: LanguageModel,
|
48
|
+
) -> List[Document]:
|
49
|
+
"""
|
50
|
+
From each passage, extract verbatim text that is relevant to a question,
|
51
|
+
using concurrent API calls to the LLM.
|
52
|
+
Args:
|
53
|
+
question: question to be answered
|
54
|
+
passages: list of passages from which to extract relevant verbatim text
|
55
|
+
LLM: LanguageModel to use for generating the prompt and extract
|
56
|
+
Returns:
|
57
|
+
list of verbatim extracts (Documents) from passages that are relevant to
|
58
|
+
question
|
59
|
+
"""
|
60
|
+
return asyncio.run(_get_verbatim_extracts(question, passages, LLM))
|
61
|
+
|
62
|
+
|
63
|
+
def followup_to_standalone(
|
64
|
+
LLM: LanguageModel, chat_history: List[Tuple[str, str]], question: str
|
65
|
+
) -> str:
|
66
|
+
"""
|
67
|
+
Given a chat history and a question, convert it to a standalone question.
|
68
|
+
Args:
|
69
|
+
chat_history: list of tuples of (question, answer)
|
70
|
+
query: follow-up question
|
71
|
+
|
72
|
+
Returns: standalone version of the question
|
73
|
+
"""
|
74
|
+
history = collate_chat_history(chat_history)
|
75
|
+
|
76
|
+
prompt = f"""
|
77
|
+
Given the conversationn below, and a follow-up question, rephrase the follow-up
|
78
|
+
question as a standalone question.
|
79
|
+
|
80
|
+
Chat history: {history}
|
81
|
+
Follow-up question: {question}
|
82
|
+
""".strip()
|
83
|
+
standalone = LLM.generate(prompt=prompt, max_tokens=1024).message.strip()
|
84
|
+
return standalone
|
@@ -8,6 +8,7 @@ class Settings(BaseSettings):
|
|
8
8
|
progress: bool = False # show progress spinners/bars?
|
9
9
|
stream: bool = True # stream output?
|
10
10
|
cache: bool = True # use cache?
|
11
|
+
cache_type: str = "redis" # cache type: "redis" or "momento"
|
11
12
|
interactive: bool = True # interactive mode?
|
12
13
|
gpt3_5: bool = True # use GPT-3.5?
|
13
14
|
nofunc: bool = False # use model without function_call? (i.e. gpt-4)
|
@@ -124,8 +124,6 @@ class RichFileLogger:
|
|
124
124
|
|
125
125
|
@no_type_check
|
126
126
|
def log(self, message: str) -> None:
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
self.file.flush()
|
131
|
-
self.file.close()
|
127
|
+
with open(self.log_file, "a") as f:
|
128
|
+
console = Console(file=f, force_terminal=True, width=200)
|
129
|
+
console.print(message)
|
@@ -7,6 +7,7 @@ from langroid.embedding_models.base import (
|
|
7
7
|
EmbeddingModel,
|
8
8
|
EmbeddingModelsConfig,
|
9
9
|
)
|
10
|
+
from langroid.embedding_models.models import OpenAIEmbeddingsConfig
|
10
11
|
from langroid.mytypes import DocMetaData, Document
|
11
12
|
from langroid.utils.configuration import settings
|
12
13
|
from langroid.utils.output.printing import print_long_text
|
@@ -19,9 +20,7 @@ class ChromaDBConfig(VectorStoreConfig):
|
|
19
20
|
type: str = "chroma"
|
20
21
|
collection_name: str = "chroma-langroid"
|
21
22
|
storage_path: str = ".chroma/data"
|
22
|
-
embedding: EmbeddingModelsConfig =
|
23
|
-
model_type="openai",
|
24
|
-
)
|
23
|
+
embedding: EmbeddingModelsConfig = OpenAIEmbeddingsConfig()
|
25
24
|
host: str = "127.0.0.1"
|
26
25
|
port: int = 6333
|
27
26
|
|
@@ -19,6 +19,7 @@ from langroid.embedding_models.base import (
|
|
19
19
|
EmbeddingModel,
|
20
20
|
EmbeddingModelsConfig,
|
21
21
|
)
|
22
|
+
from langroid.embedding_models.models import OpenAIEmbeddingsConfig
|
22
23
|
from langroid.mytypes import Document
|
23
24
|
from langroid.utils.configuration import settings
|
24
25
|
from langroid.vector_store.base import VectorStore, VectorStoreConfig
|
@@ -32,9 +33,7 @@ class QdrantDBConfig(VectorStoreConfig):
|
|
32
33
|
|
33
34
|
collection_name: str | None = None
|
34
35
|
storage_path: str = ".qdrant/data"
|
35
|
-
embedding: EmbeddingModelsConfig =
|
36
|
-
model_type="openai",
|
37
|
-
)
|
36
|
+
embedding: EmbeddingModelsConfig = OpenAIEmbeddingsConfig()
|
38
37
|
distance: str = Distance.COSINE
|
39
38
|
|
40
39
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "langroid"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.28"
|
4
4
|
description = "Harness LLMs with Multi-Agent Programming"
|
5
5
|
authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -53,6 +53,7 @@ torch = {version="2.0.0", optional=true}
|
|
53
53
|
qdrant-client = "^1.3.1"
|
54
54
|
pydantic = "1.10.11"
|
55
55
|
pypdf = "^3.12.2"
|
56
|
+
momento = "^1.7.0"
|
56
57
|
|
57
58
|
[tool.poetry.group.dev.dependencies]
|
58
59
|
pytest = "^7.3.1"
|
@@ -106,4 +107,4 @@ unfixable = []
|
|
106
107
|
|
107
108
|
|
108
109
|
[tool.pytest.ini_options]
|
109
|
-
filterwarnings = ["ignore::DeprecationWarning"]
|
110
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
langroid-0.1.28/setup.py
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
from setuptools import setup
|
3
|
+
|
4
|
+
packages = \
|
5
|
+
['langroid',
|
6
|
+
'langroid.agent',
|
7
|
+
'langroid.agent.special',
|
8
|
+
'langroid.cachedb',
|
9
|
+
'langroid.embedding_models',
|
10
|
+
'langroid.language_models',
|
11
|
+
'langroid.parsing',
|
12
|
+
'langroid.prompts',
|
13
|
+
'langroid.scripts',
|
14
|
+
'langroid.utils',
|
15
|
+
'langroid.utils.llms',
|
16
|
+
'langroid.utils.output',
|
17
|
+
'langroid.utils.web',
|
18
|
+
'langroid.vector_store']
|
19
|
+
|
20
|
+
package_data = \
|
21
|
+
{'': ['*']}
|
22
|
+
|
23
|
+
install_requires = \
|
24
|
+
['autopep8>=2.0.2,<3.0.0',
|
25
|
+
'black[jupyter]>=23.3.0,<24.0.0',
|
26
|
+
'bs4>=0.0.1,<0.0.2',
|
27
|
+
'chromadb>=0.3.21,<0.4.0',
|
28
|
+
'colorlog>=6.7.0,<7.0.0',
|
29
|
+
'faker>=18.9.0,<19.0.0',
|
30
|
+
'fakeredis>=2.12.1,<3.0.0',
|
31
|
+
'fire>=0.5.0,<0.6.0',
|
32
|
+
'flake8>=6.0.0,<7.0.0',
|
33
|
+
'halo>=0.0.31,<0.0.32',
|
34
|
+
'mkdocs-awesome-pages-plugin>=2.8.0,<3.0.0',
|
35
|
+
'mkdocs-gen-files>=0.4.0,<0.5.0',
|
36
|
+
'mkdocs-jupyter>=0.24.1,<0.25.0',
|
37
|
+
'mkdocs-literate-nav>=0.6.0,<0.7.0',
|
38
|
+
'mkdocs-material>=9.1.5,<10.0.0',
|
39
|
+
'mkdocs-section-index>=0.3.5,<0.4.0',
|
40
|
+
'mkdocs>=1.4.2,<2.0.0',
|
41
|
+
'mkdocstrings[python]>=0.21.2,<0.22.0',
|
42
|
+
'momento>=1.7.0,<2.0.0',
|
43
|
+
'mypy>=1.2.0,<2.0.0',
|
44
|
+
'nltk>=3.8.1,<4.0.0',
|
45
|
+
'openai>=0.27.5,<0.28.0',
|
46
|
+
'pre-commit>=3.3.2,<4.0.0',
|
47
|
+
'pydantic==1.10.11',
|
48
|
+
'pygithub>=1.58.1,<2.0.0',
|
49
|
+
'pygments>=2.15.1,<3.0.0',
|
50
|
+
'pyparsing>=3.0.9,<4.0.0',
|
51
|
+
'pypdf>=3.12.2,<4.0.0',
|
52
|
+
'python-dotenv>=1.0.0,<2.0.0',
|
53
|
+
'qdrant-client>=1.3.1,<2.0.0',
|
54
|
+
'redis>=4.5.5,<5.0.0',
|
55
|
+
'requests-oauthlib>=1.3.1,<2.0.0',
|
56
|
+
'requests>=2.31.0,<3.0.0',
|
57
|
+
'rich>=13.3.4,<14.0.0',
|
58
|
+
'ruff>=0.0.270,<0.0.271',
|
59
|
+
'tiktoken>=0.3.3,<0.4.0',
|
60
|
+
'trafilatura>=1.5.0,<2.0.0',
|
61
|
+
'typer>=0.7.0,<0.8.0',
|
62
|
+
'types-redis>=4.5.5.2,<5.0.0.0',
|
63
|
+
'types-requests>=2.31.0.1,<3.0.0.0',
|
64
|
+
'wget>=3.2,<4.0']
|
65
|
+
|
66
|
+
extras_require = \
|
67
|
+
{'hf-embeddings': ['sentence-transformers==2.2.2', 'torch==2.0.0']}
|
68
|
+
|
69
|
+
setup_kwargs = {
|
70
|
+
'name': 'langroid',
|
71
|
+
'version': '0.1.28',
|
72
|
+
'description': 'Harness LLMs with Multi-Agent Programming',
|
73
|
+
'long_description': '<div align="center">\n <img src="docs/assets/langroid-card-lambda-ossem-rust-1200-630.png" alt="Logo" \n width="400" align="center">\n</div>\n\n<div align="center">\n\n[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)\n[](https://codecov.io/gh/langroid/langroid)\n[](https://github.com/langroid/langroid/actions/workflows/validate.yml)\n[](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)\n[](https://langroid.github.io/langroid)\n[](https://discord.gg/g3nAXCbZ)\n\n</div>\n\n<h3 align="center">\n <a target="_blank" \n href="https://langroid.github.io/langroid/" rel="dofollow">\n <strong>Documentation</strong></a>\n ·\n <a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">\n <strong>Examples Repo</strong></a>\n ·\n <a target="_blank" href="https://discord.gg/g3nAXCbZ" rel="dofollow">\n <strong>Discord</strong></a>\n ·\n <a target="_blank" href="./CONTRIBUTING.md" rel="dofollow">\n <strong>Contributing</strong></a>\n\n <br />\n</h3>\n\n`Langroid` is an intuitive, lightweight, extensible and principled\nPython framework to easily build LLM-powered applications. \nYou set up Agents, equip them with optional components (LLM, \nvector-store and methods), assign them tasks, and have them \ncollaboratively solve a problem by exchanging messages. \nThis Multi-Agent paradigm is inspired by the\n[Actor Framework](https://en.wikipedia.org/wiki/Actor_model)\n(but you do not need to know anything about this!).\nWe welcome contributions -- See the [contributions](./CONTRIBUTING.md) document\nfor ideas on what to contribute.\n\n\n# :rocket: Demo\nSuppose you want to extract structured information about the key terms \nof a commercial lease document. You can easily do this with Langroid using a two-agent system,\nas we show in the [langroid-examples](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py) repo.\nThe demo showcases just a few of the many features of Langroid, such as:\n- Multi-agent collaboration: `LeaseExtractor` is in charge of the task, and its LLM (GPT4) generates questions \nto be answered by the `DocAgent`.\n- Retrieval augmented question-answering, with **source-citation**: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to \nanswer the `LeaseExtractor`\'s questions, cites the specific excerpt supporting the answer. \n- Function-calling (also known as tool/plugin): When it has all the information it \nneeds, the `LeaseExtractor` LLM presents the information in a structured \nformat using a Function-call. \n\nHere is what it looks like in action:\n\n\n\n\n# :zap: Highlights\n\n- **Agents as first-class citizens:** The [Agent](https://langroid.github.io/langroid/reference/agent/base/#langroid.agent.base.Agent) class encapsulates LLM conversation state,\n and optionally a vector-store and tools. Agents are a core abstraction in Langroid;\n Agents act as _message transformers_, and by default provide 3 _responder_ methods, one corresponding to each entity: LLM, Agent, User.\n- **Tasks:** A [Task](https://langroid.github.io/langroid/reference/agent/task/) class wraps an Agent, and gives the agent instructions (or roles, or goals), \n manages iteration over an Agent\'s responder methods, \n and orchestrates multi-agent interactions via hierarchical, recursive\n task-delegation. The `Task.run()` method has the same \n type-signature as an Agent\'s responder\'s methods, and this is key to how \n a task of an agent can delegate to other sub-tasks: from the point of view of a Task,\n sub-tasks are simply additional responders, to be used in a round-robin fashion \n after the agent\'s own responders.\n- **Modularity, Reusabilily, Loose coupling:** The `Agent` and `Task` abstractions allow users to design\n Agents with specific skills, wrap them in Tasks, and combine tasks in a flexible way.\n- **LLM Support**: Langroid supports OpenAI LLMs including GPT-3.5-Turbo,\n GPT-4-0613\n- **Caching of LLM prompts, responses:** Langroid uses [Redis](https://redis.com/try-free/) for caching.\n- **Vector-stores**: [Qdrant](https://qdrant.tech/) and [Chroma](https://www.trychroma.com/) are currently supported.\n Vector stores allow for Retrieval-Augmented-Generation (RAG).\n- **Grounding and source-citation:** Access to external documents via vector-stores \n allows for grounding and source-citation.\n- **Observability, Logging, Lineage:** Langroid generates detailed logs of multi-agent interactions and\n maintains provenance/lineage of messages, so that you can trace back\n the origin of a message.\n- **Tools/Plugins/Function-calling**: Langroid supports OpenAI\'s recently\n released [function calling](https://platform.openai.com/docs/guides/gpt/function-calling)\n feature. In addition, Langroid has its own native equivalent, which we\n call **tools** (also known as "plugins" in other contexts). Function\n calling and tools have the same developer-facing interface, implemented\n using [Pydantic](https://docs.pydantic.dev/latest/),\n which makes it very easy to define tools/functions and enable agents\n to use them. Benefits of using Pydantic are that you never have to write\n complex JSON specs for function calling, and when the LLM\n hallucinates malformed JSON, the Pydantic error message is sent back to\n the LLM so it can fix it!\n\n--- \n\n# :gear: Installation and Setup\n\n### Install `langroid`\nLangroid requires Python 3.11+. We recommend using a virtual environment.\nUse `pip` to install `langroid` (from PyPi) to your virtual environment:\n```bash\npip install langroid\n```\nThe core Langroid package lets you use OpenAI Embeddings models via their API. \nIf you instead want to use the `all-MiniLM-L6-v2` embeddings model\nfrom from HuggingFace, install Langroid like this:\n```bash\npip install langroid[hf-embeddings]\n```\nNote that this will install `torch` and `sentence-transformers` libraries.\n\n### Set up environment variables (API keys, etc)\n\nTo get started, all you need is an OpenAI API Key.\nIf you don\'t have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).\nCurrently only OpenAI models are supported. Others will be added later\n(Pull Requests welcome!).\n\nIn the root of the repo, copy the `.env-template` file to a new file `.env`: \n```bash\ncp .env-template .env\n```\nThen insert your OpenAI API Key. \nYour `.env` file should look like this:\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\n````\n\nAlternatively, you can set this as an environment variable in your shell\n(you will need to do this every time you open a new shell):\n```bash\nexport OPENAI_API_KEY=your-key-here-without-quotes\n```\n\n\n<details>\n<summary><b>Optional Setup Instructions (click to expand) </b></summary>\n\nAll of the below are optional and not strictly needed to run any of the examples.\n\n- **Qdrant** Vector Store API Key, URL. This is only required if you want to use Qdrant cloud.\n You can sign up for a free 1GB account at [Qdrant cloud](https://cloud.qdrant.io).\n If you skip setting up these, Langroid will use Qdrant in local-storage mode.\n Alternatively [Chroma](https://docs.trychroma.com/) is also currently supported. \n We use the local-storage version of Chroma, so there is no need for an API key.\n- **Redis** Password, host, port: This is optional, and only needed to cache LLM API responses\n using Redis Cloud. Redis [offers](https://redis.com/try-free/) a free 30MB Redis account\n which is more than sufficient to try out Langroid and even beyond.\n If you don\'t set up these, Langroid will use a pure-python \n Redis in-memory cache via the [Fakeredis](https://fakeredis.readthedocs.io/en/latest/) library.\n- **Momento** Serverless Caching of LLM API responses (as an alternative to Redis). \n To use Momento instead of Redis, simply enter your Momento Token in the `.env` file,\n as the value of `MOMENTO_AUTH_TOKEN` (see example file below).\n- **GitHub** Personal Access Token (required for apps that need to analyze git\n repos; token-based API calls are less rate-limited). See this\n [GitHub page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n\nIf you add all of these optional variables, your `.env` file should look like this:\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\nGITHUB_ACCESS_TOKEN=your-personal-access-token-no-quotes\nREDIS_PASSWORD=your-redis-password-no-quotes\nREDIS_HOST=your-redis-hostname-no-quotes\nREDIS_PORT=your-redis-port-no-quotes\nMOMENTO_AUTH_TOKEN=your-momento-token-no-quotes # instead of REDIS* variables\nQDRANT_API_KEY=your-key\nQDRANT_API_URL=https://your.url.here:6333 # note port number must be included\n```\n</details>\n\n---\n\n# :tada: Usage Examples\n\nThese are quick teasers to give a glimpse of what you can do with Langroid\nand how your code would look. \n\n:warning: The code snippets below are intended to give a flavor of the code\nand they are **not** complete runnable examples! For that we encourage you to \nconsult the [`langroid-examples`](https://github.com/langroid/langroid-examples) \nrepository.\n\n:information_source: The various LLM prompts and instructions in Langroid\nhave been tested to work well with GPT4.\nSwitching to GPT3.5-Turbo is easy via a config flag\n(e.g., `cfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT3_5_TURBO)`),\nand may suffice for some applications, but in general you may see inferior results.\n\n\n:book: Also see the\n[`Getting Started Guide`](https://langroid.github.io/langroid/quick-start/)\nfor a detailed tutorial.\n\nClick to expand any of the code examples below:\n\n<details>\n<summary> <b> Direct interaction with OpenAI LLM </b> </summary>\n\n```python\nfrom langroid.language_models.openai_gpt import ( \n OpenAIGPTConfig, OpenAIChatModel, OpenAIGPT,\n)\nfrom langroid.language_models.base import LLMMessage, Role\n\ncfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT4)\n\nmdl = OpenAIGPT(cfg)\n\nmessages = [\n LLMMessage(content="You are a helpful assistant", role=Role.SYSTEM), \n LLMMessage(content="What is the capital of Ontario?", role=Role.USER),\n]\nresponse = mdl.chat(messages, max_tokens=200)\nprint(response.message)\n```\n</details>\n\n<details>\n<summary> <b> Define an agent, set up a task, and run it </b> </summary>\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\n\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None, # no vector store\n)\nagent = ChatAgent(config)\n# get response from agent\'s LLM, and put this in an interactive loop...\n# answer = agent.llm_response("What is the capital of Ontario?")\n # ... OR instead, set up a task (which has a built-in loop) and run it\ntask = Task(agent, name="Bot") \ntask.run() # ... a loop seeking response from LLM or User at each turn\n```\n</details>\n\n<details>\n<summary><b> Three communicating agents </b></summary>\n\n```python\n\nA toy numbers game, where when given a number `n`:\n- `repeater_agent`\'s LLM simply returns `n`,\n- `even_agent`\'s LLM returns `n/2` if `n` is even, else says "DO-NOT-KNOW"\n- `odd_agent`\'s LLM returns `3*n+1` if `n` is odd, else says "DO-NOT-KNOW"\n\nFirst define the 3 agents, and set up their tasks with instructions:\n\n```python\nfrom langroid.utils.constants import NO_ANSWER\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb = None,\n)\nrepeater_agent = ChatAgent(config)\nrepeater_task = Task(\n repeater_agent,\n name = "Repeater",\n system_message="""\n Your job is to repeat whatever number you receive.\n """,\n llm_delegate=True, # LLM takes charge of task\n single_round=False, \n)\neven_agent = ChatAgent(config)\neven_task = Task(\n even_agent,\n name = "EvenHandler",\n system_message=f"""\n You will be given a number. \n If it is even, divide by 2 and say the result, nothing else.\n If it is odd, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n\nodd_agent = ChatAgent(config)\nodd_task = Task(\n odd_agent,\n name = "OddHandler",\n system_message=f"""\n You will be given a number n. \n If it is odd, return (n*3+1), say nothing else. \n If it is even, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n```\nThen add the `even_task` and `odd_task` as sub-tasks of `repeater_task`, \nand run the `repeater_task`, kicking it off with a number as input:\n```python\nrepeater_task.add_sub_task([even_task, odd_task])\nrepeater_task.run("3")\n```\n\n</details>\n\n<details>\n<summary><b> Simple Tool/Function-calling example </b></summary>\n\nLangroid leverages Pydantic to support OpenAI\'s\n[Function-calling API](https://platform.openai.com/docs/guides/gpt/function-calling)\nas well as its own native tools. The benefits are that you don\'t have to write\nany JSON to specify the schema, and also if the LLM hallucinates a malformed\ntool syntax, Langroid sends the Pydantic validation error (suitiably sanitized) \nto the LLM so it can fix it!\n\nSimple example: Say the agent has a secret list of numbers, \nand we want the LLM to find the smallest number in the list. \nWe want to give the LLM a `probe` tool/function which takes a\nsingle number `n` as argument. The tool handler method in the agent\nreturns how many numbers in its list are at most `n`.\n\nFirst define the tool using Langroid\'s `ToolMessage` class:\n\n\n```python\nfrom langroid.agent.tool_message import ToolMessage\nclass ProbeTool(ToolMessage):\n request: str = "probe" # specifies which agent method handles this tool\n purpose: str = """\n To find how many numbers in my list are less than or equal to \n the <number> you specify.\n """ # description used to instruct the LLM on when/how to use the tool\n number: int # required argument to the tool\n```\n\nThen define a `SpyGameAgent` as a subclass of `ChatAgent`, \nwith a method `probe` that handles this tool:\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nclass SpyGameAgent(ChatAgent):\n def __init__(self, config: ChatAgentConfig):\n super().__init__(config)\n self.numbers = [3, 4, 8, 11, 15, 25, 40, 80, 90]\n\n def probe(self, msg: ProbeTool) -> str:\n # return how many numbers in self.numbers are less or equal to msg.number\n return str(len([n for n in self.numbers if n <= msg.number]))\n```\n\nWe then instantiate the agent and enable it to use and respond to the tool:\n\n```python\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nspy_game_agent = SpyGameAgent(\n ChatAgentConfig(\n name="Spy",\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None,\n use_tools=False, # don\'t use Langroid native tool\n use_functions_api=True, # use OpenAI function-call API\n )\n)\nspy_game_agent.enable_message(ProbeTool)\n```\n\nFor a full working example see the\n[chat-agent-tool.py](https://github.com/langroid/langroid-examples/blob/main/examples/quick-start/chat-agent-tool.py)\nscript in the `langroid-examples` repo.\n</details>\n\n<details>\n<summary> <b>Tool/Function-calling to extract structured information from text </b> </summary>\n\nSuppose you want an agent to extract \nthe key terms of a lease, from a lease document, as a nested JSON structure.\nFirst define the desired structure via Pydantic models:\n\n```python\nfrom pydantic import BaseModel\nclass LeasePeriod(BaseModel):\n start_date: str\n end_date: str\n\n\nclass LeaseFinancials(BaseModel):\n monthly_rent: str\n deposit: str\n\nclass Lease(BaseModel):\n period: LeasePeriod\n financials: LeaseFinancials\n address: str\n```\n\nThen define the `LeaseMessage` tool as a subclass of Langroid\'s `ToolMessage`.\nNote the tool has a required argument `terms` of type `Lease`:\n\n```python\nclass LeaseMessage(ToolMessage):\n request: str = "lease_info"\n purpose: str = """\n Collect information about a Commercial Lease.\n """\n terms: Lease\n```\n\nThen define a `LeaseExtractorAgent` with a method `lease_info` that handles this tool,\ninstantiate the agent, and enable it to use and respond to this tool:\n\n```python\nclass LeaseExtractorAgent(ChatAgent):\n def lease_info(self, message: LeaseMessage) -> str:\n print(\n f"""\n DONE! Successfully extracted Lease Info:\n {message.terms}\n """\n )\n return json.dumps(message.terms.dict())\n \nlease_extractor_agent = LeaseExtractorAgent(\n ChatAgentConfig(\n llm=OpenAIGPTConfig(),\n use_functions_api=False,\n use_tools=True,\n )\n)\nlease_extractor_agent.enable_message(LeaseMessage)\n```\n\nSee the [`chat_multi_extract.py`](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py)\nscript in the `langroid-examples` repo for a full working example.\n</details>\n\n<details>\n<summary><b> Chat with documents (file paths, URLs, etc) </b></summary>\n\nLangroid provides a specialized agent class `DocChatAgent` for this purpose.\nIt incorporates document sharding, embedding, storage in a vector-DB, \nand retrieval-augmented query-answer generation.\nUsing this class to chat with a collection of documents is easy.\nFirst create a `DocChatAgentConfig` instance, with a \n`doc_paths` field that specifies the documents to chat with.\n\n```python\nfrom langroid.agent.doc_chat_agent import DocChatAgentConfig\nconfig = DocChatAgentConfig(\n doc_paths = [\n "https://en.wikipedia.org/wiki/Language_model",\n "https://en.wikipedia.org/wiki/N-gram_language_model",\n "/path/to/my/notes-on-language-models.txt",\n ]\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=VectorStoreConfig(\n type="qdrant",\n ),\n)\n```\n\nThen instantiate the `DocChatAgent`, ingest the docs into the vector-store:\n\n```python\nagent = DocChatAgent(config)\nagent.ingest()\n```\nThen we can either ask the agent one-off questions,\n```python\nagent.chat("What is a language model?")\n```\nor wrap it in a `Task` and run an interactive loop with the user:\n```python\nfrom langroid.task import Task\ntask = Task(agent)\ntask.run()\n```\n\nSee full working scripts in the \n[`docqa`](https://github.com/langroid/langroid-examples/tree/main/examples/docqa)\nfolder of the `langroid-examples` repo.\n</details>\n\n---\n\n# :heart: Thank you to our [supporters](https://github.com/langroid/langroid/stargazers)\n\n# Contributors\n\n- Prasad Chalasani (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)\n- Somesh Jha (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)\n- Mohannad Alhanahnah (Research Associate, U Wisc at Madison)\n- Ashish Hooda (IIT BTech/CS; PhD Candidate, U Wisc at Madison)\n\n',
|
74
|
+
'author': 'Prasad Chalasani',
|
75
|
+
'author_email': 'pchalasani@gmail.com',
|
76
|
+
'maintainer': 'None',
|
77
|
+
'maintainer_email': 'None',
|
78
|
+
'url': 'None',
|
79
|
+
'packages': packages,
|
80
|
+
'package_data': package_data,
|
81
|
+
'install_requires': install_requires,
|
82
|
+
'extras_require': extras_require,
|
83
|
+
'python_requires': '>=3.8.1,<3.12',
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
setup(**setup_kwargs)
|
@@ -1,236 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
from typing import List, Tuple
|
3
|
-
|
4
|
-
import aiohttp
|
5
|
-
|
6
|
-
from langroid.language_models.base import LanguageModel
|
7
|
-
from langroid.mytypes import DocMetaData, Document
|
8
|
-
from langroid.prompts.dialog import collate_chat_history
|
9
|
-
from langroid.prompts.templates import EXTRACTION_PROMPT
|
10
|
-
|
11
|
-
|
12
|
-
async def get_verbatim_extract_async(
|
13
|
-
question: str,
|
14
|
-
passage: Document,
|
15
|
-
LLM: LanguageModel,
|
16
|
-
) -> str:
|
17
|
-
"""
|
18
|
-
Asynchronously, get verbatim extract from passage that is relevant to a question.
|
19
|
-
"""
|
20
|
-
async with aiohttp.ClientSession():
|
21
|
-
templatized_prompt = EXTRACTION_PROMPT
|
22
|
-
final_prompt = templatized_prompt.format(question=question, content=passage)
|
23
|
-
final_extract = await LLM.agenerate(prompt=final_prompt, max_tokens=1024)
|
24
|
-
|
25
|
-
return final_extract.message.strip()
|
26
|
-
|
27
|
-
|
28
|
-
async def _get_verbatim_extracts(
|
29
|
-
question: str,
|
30
|
-
passages: List[Document],
|
31
|
-
LLM: LanguageModel,
|
32
|
-
) -> List[Document]:
|
33
|
-
async with aiohttp.ClientSession():
|
34
|
-
verbatim_extracts = await asyncio.gather(
|
35
|
-
*(get_verbatim_extract_async(question, P, LLM) for P in passages)
|
36
|
-
)
|
37
|
-
metadatas = [P.metadata for P in passages]
|
38
|
-
# return with metadata so we can use it downstream, e.g. to cite sources
|
39
|
-
return [
|
40
|
-
Document(content=e, metadata=m) for e, m in zip(verbatim_extracts, metadatas)
|
41
|
-
]
|
42
|
-
|
43
|
-
|
44
|
-
def get_verbatim_extracts(
|
45
|
-
question: str,
|
46
|
-
passages: List[Document],
|
47
|
-
LLM: LanguageModel,
|
48
|
-
) -> List[Document]:
|
49
|
-
"""
|
50
|
-
From each passage, extract verbatim text that is relevant to a question,
|
51
|
-
using concurrent API calls to the LLM.
|
52
|
-
Args:
|
53
|
-
question: question to be answered
|
54
|
-
passages: list of passages from which to extract relevant verbatim text
|
55
|
-
LLM: LanguageModel to use for generating the prompt and extract
|
56
|
-
Returns:
|
57
|
-
list of verbatim extracts (Documents) from passages that are relevant to
|
58
|
-
question
|
59
|
-
"""
|
60
|
-
return asyncio.run(_get_verbatim_extracts(question, passages, LLM))
|
61
|
-
|
62
|
-
|
63
|
-
def generate_summarizer_prompt(question: str, texts: List[str], k: int = 1) -> str:
|
64
|
-
# Request for k demonstrations
|
65
|
-
demo_request = f"""
|
66
|
-
Please provide {k} demonstrations of synthesizing answers based on
|
67
|
-
relevant text fragments for different questions. Include the question,
|
68
|
-
relevant text fragments, and the final synthesized answer for each
|
69
|
-
demonstration.
|
70
|
-
"""
|
71
|
-
|
72
|
-
# Placeholder for demonstrations
|
73
|
-
demo_placeholder = "\n".join(
|
74
|
-
[
|
75
|
-
f"Question: [Question {i}]\n-----------\n"
|
76
|
-
f"Content: [Relevant text {i}]\n-----------\nFinal Answer: [Answer {i}]\n"
|
77
|
-
for i in range(1, k + 1)
|
78
|
-
]
|
79
|
-
)
|
80
|
-
|
81
|
-
# Format the actual question and texts
|
82
|
-
actual_question_str = f"Question: {question}\n-----------\n"
|
83
|
-
content_lines = "\n".join([f"Content: {text}" for text in texts])
|
84
|
-
actual_question_str += content_lines + "\n-----------\nFinal Answer:\n"
|
85
|
-
|
86
|
-
# Combine the request, demonstrations, and
|
87
|
-
# actual question to form the complete prompt
|
88
|
-
complete_prompt = demo_request + demo_placeholder + "\n" + actual_question_str
|
89
|
-
return complete_prompt
|
90
|
-
|
91
|
-
|
92
|
-
def make_summarizer_demos(k: int) -> str:
|
93
|
-
# Define modified original question for LLM.generate
|
94
|
-
# templatized_prompt = f"""
|
95
|
-
# generate {k} few-shot demos of answering a question based on a list of
|
96
|
-
# text contents extracted from a long document, where some or all
|
97
|
-
# contents may be irrelevant to the question. When there is no relevant
|
98
|
-
# text, the answer should be "I don't know". Each demo should be structured as
|
99
|
-
# Question:, Content:, Content:, and so on, and Final Answer: Use 1-3
|
100
|
-
# sentences for each piece of content.
|
101
|
-
# """
|
102
|
-
idk_instruction = ""
|
103
|
-
if k > 1:
|
104
|
-
idk_instruction = (
|
105
|
-
"At least one of the demos should have an " "'I don't know' answer. "
|
106
|
-
)
|
107
|
-
|
108
|
-
meta_prompt = (
|
109
|
-
f"""
|
110
|
-
Generate a templatized prompt for answering questions based on document extracts.
|
111
|
-
The prompt should include clear instructions, {k} few-shot demos, and placeholders
|
112
|
-
for the input question and extracts.
|
113
|
-
|
114
|
-
The instructions should specify that the answer must be based solely on the
|
115
|
-
provided extracts. Making up an answer should be discouraged if the information
|
116
|
-
is not in the extracts. If none of the extracts are relevant to the question,
|
117
|
-
the response should be 'I don't know'.
|
118
|
-
|
119
|
-
Each demo should consist of:
|
120
|
-
- A sample question (Question:)
|
121
|
-
- A series of extracts from a document (Extract:, Extract:, ...),
|
122
|
-
with each extract being 1-5 sentences long.
|
123
|
-
- A sample answer (Answer:)
|
124
|
-
|
125
|
-
{idk_instruction}
|
126
|
-
The final prompt should include placeholders:
|
127
|
-
- A placeholder {{Question}} for the input question
|
128
|
-
- A placeholder {{Extracts}} for the input extracts
|
129
|
-
|
130
|
-
The final prompt should end with 'Answer:' to provide the response.
|
131
|
-
"""
|
132
|
-
).strip()
|
133
|
-
return meta_prompt
|
134
|
-
|
135
|
-
|
136
|
-
def get_summary_answer(
|
137
|
-
question: str, passages: List[Document], LLM: LanguageModel, k: int = 1
|
138
|
-
) -> Document:
|
139
|
-
templatized_prompt = """
|
140
|
-
Use the provided extracts (with sources) to answer the question. If there's not
|
141
|
-
enough information, respond with "I don't know." Justify your answer by citing
|
142
|
-
your sources, as in these examples:
|
143
|
-
|
144
|
-
Extract: The tree species in the garden include oak, maple, and birch.
|
145
|
-
Source: https://en.wikipedia.org/wiki/Tree
|
146
|
-
Extract: The oak trees are known for their longevity and strength.
|
147
|
-
Source: https://en.wikipedia.org/wiki/Oak
|
148
|
-
Question: What types of trees are in the garden?
|
149
|
-
Answer: The types of trees in the garden include oak, maple, and birch.
|
150
|
-
SOURCE: https://en.wikipedia.org/wiki/Tree
|
151
|
-
TEXT: The tree species in the garden include oak, maple, and birch.
|
152
|
-
|
153
|
-
Extract: The experiment involved three groups: control, low dose, and high dose.
|
154
|
-
Source: https://en.wikipedia.org/wiki/Experiment
|
155
|
-
Extract: The high dose group showed significant improvement in symptoms.
|
156
|
-
Source: https://en.wikipedia.org/wiki/Experiment
|
157
|
-
Extract: The control group did not receive any treatment and served as a baseline.
|
158
|
-
Source: https://en.wikipedia.org/wiki/Experiment
|
159
|
-
Question: How many groups were involved which group showed significant improvement?
|
160
|
-
Answer: There were three groups and the high dose group showed significant
|
161
|
-
improvement in symptoms.
|
162
|
-
SOURCE: https://en.wikipedia.org/wiki/Experiment
|
163
|
-
TEXT: The experiment involved three groups: control, low dose, and high dose.
|
164
|
-
SOURCE: https://en.wikipedia.org/wiki/Experiment
|
165
|
-
TEXT: The high dose group showed significant improvement in symptoms.
|
166
|
-
|
167
|
-
|
168
|
-
Extract: The CEO announced several new initiatives during the company meeting.
|
169
|
-
Source: https://en.wikipedia.org/wiki/CEO
|
170
|
-
Extract: The financial performance of the company has been strong this year.
|
171
|
-
Source: https://en.wikipedia.org/wiki/CEO
|
172
|
-
Question: What new initiatives did the CEO announce?
|
173
|
-
Answer: I don't know.
|
174
|
-
|
175
|
-
{extracts}
|
176
|
-
{question}
|
177
|
-
Answer:
|
178
|
-
""".strip()
|
179
|
-
|
180
|
-
# templatized_prompt = LLM.generate(prompt=prompt, max_tokens=1024)
|
181
|
-
# Define an auxiliary function to transform the list of
|
182
|
-
# passages into a single string
|
183
|
-
def stringify_passages(passages: List[Document]) -> str:
|
184
|
-
return "\n".join(
|
185
|
-
[
|
186
|
-
f"""
|
187
|
-
Extract: {p.content}
|
188
|
-
Source: {p.metadata.source}
|
189
|
-
"""
|
190
|
-
for p in passages
|
191
|
-
]
|
192
|
-
)
|
193
|
-
|
194
|
-
passages_str = stringify_passages(passages)
|
195
|
-
# Substitute Q and P into the templatized prompt
|
196
|
-
final_prompt = templatized_prompt.format(
|
197
|
-
question=f"Question:{question}", extracts=passages_str
|
198
|
-
)
|
199
|
-
|
200
|
-
# Generate the final verbatim extract based on the final prompt
|
201
|
-
final_answer = LLM.generate(prompt=final_prompt, max_tokens=1024).message.strip()
|
202
|
-
parts = final_answer.split("SOURCE:", maxsplit=1)
|
203
|
-
if len(parts) > 1:
|
204
|
-
content = parts[0].strip()
|
205
|
-
sources = parts[1].strip()
|
206
|
-
else:
|
207
|
-
content = final_answer
|
208
|
-
sources = ""
|
209
|
-
return Document(
|
210
|
-
content=content,
|
211
|
-
metadata=DocMetaData(source="SOURCE: " + sources),
|
212
|
-
)
|
213
|
-
|
214
|
-
|
215
|
-
def followup_to_standalone(
|
216
|
-
LLM: LanguageModel, chat_history: List[Tuple[str, str]], question: str
|
217
|
-
) -> str:
|
218
|
-
"""
|
219
|
-
Given a chat history and a question, convert it to a standalone question.
|
220
|
-
Args:
|
221
|
-
chat_history: list of tuples of (question, answer)
|
222
|
-
query: follow-up question
|
223
|
-
|
224
|
-
Returns: standalone version of the question
|
225
|
-
"""
|
226
|
-
history = collate_chat_history(chat_history)
|
227
|
-
|
228
|
-
prompt = f"""
|
229
|
-
Given the conversationn below, and a follow-up question, rephrase the follow-up
|
230
|
-
question as a standalone question.
|
231
|
-
|
232
|
-
Chat history: {history}
|
233
|
-
Follow-up question: {question}
|
234
|
-
""".strip()
|
235
|
-
standalone = LLM.generate(prompt=prompt, max_tokens=1024).message.strip()
|
236
|
-
return standalone
|
langroid-0.1.26/setup.py
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from setuptools import setup
|
3
|
-
|
4
|
-
packages = \
|
5
|
-
['langroid',
|
6
|
-
'langroid.agent',
|
7
|
-
'langroid.agent.special',
|
8
|
-
'langroid.cachedb',
|
9
|
-
'langroid.embedding_models',
|
10
|
-
'langroid.language_models',
|
11
|
-
'langroid.parsing',
|
12
|
-
'langroid.prompts',
|
13
|
-
'langroid.scripts',
|
14
|
-
'langroid.utils',
|
15
|
-
'langroid.utils.llms',
|
16
|
-
'langroid.utils.output',
|
17
|
-
'langroid.utils.web',
|
18
|
-
'langroid.vector_store']
|
19
|
-
|
20
|
-
package_data = \
|
21
|
-
{'': ['*']}
|
22
|
-
|
23
|
-
install_requires = \
|
24
|
-
['autopep8>=2.0.2,<3.0.0',
|
25
|
-
'black[jupyter]>=23.3.0,<24.0.0',
|
26
|
-
'bs4>=0.0.1,<0.0.2',
|
27
|
-
'chromadb>=0.3.21,<0.4.0',
|
28
|
-
'colorlog>=6.7.0,<7.0.0',
|
29
|
-
'faker>=18.9.0,<19.0.0',
|
30
|
-
'fakeredis>=2.12.1,<3.0.0',
|
31
|
-
'fire>=0.5.0,<0.6.0',
|
32
|
-
'flake8>=6.0.0,<7.0.0',
|
33
|
-
'halo>=0.0.31,<0.0.32',
|
34
|
-
'mkdocs-awesome-pages-plugin>=2.8.0,<3.0.0',
|
35
|
-
'mkdocs-gen-files>=0.4.0,<0.5.0',
|
36
|
-
'mkdocs-jupyter>=0.24.1,<0.25.0',
|
37
|
-
'mkdocs-literate-nav>=0.6.0,<0.7.0',
|
38
|
-
'mkdocs-material>=9.1.5,<10.0.0',
|
39
|
-
'mkdocs-section-index>=0.3.5,<0.4.0',
|
40
|
-
'mkdocs>=1.4.2,<2.0.0',
|
41
|
-
'mkdocstrings[python]>=0.21.2,<0.22.0',
|
42
|
-
'mypy>=1.2.0,<2.0.0',
|
43
|
-
'nltk>=3.8.1,<4.0.0',
|
44
|
-
'openai>=0.27.5,<0.28.0',
|
45
|
-
'pre-commit>=3.3.2,<4.0.0',
|
46
|
-
'pydantic==1.10.11',
|
47
|
-
'pygithub>=1.58.1,<2.0.0',
|
48
|
-
'pygments>=2.15.1,<3.0.0',
|
49
|
-
'pyparsing>=3.0.9,<4.0.0',
|
50
|
-
'pypdf>=3.12.2,<4.0.0',
|
51
|
-
'python-dotenv>=1.0.0,<2.0.0',
|
52
|
-
'qdrant-client>=1.3.1,<2.0.0',
|
53
|
-
'redis>=4.5.5,<5.0.0',
|
54
|
-
'requests-oauthlib>=1.3.1,<2.0.0',
|
55
|
-
'requests>=2.31.0,<3.0.0',
|
56
|
-
'rich>=13.3.4,<14.0.0',
|
57
|
-
'ruff>=0.0.270,<0.0.271',
|
58
|
-
'tiktoken>=0.3.3,<0.4.0',
|
59
|
-
'trafilatura>=1.5.0,<2.0.0',
|
60
|
-
'typer>=0.7.0,<0.8.0',
|
61
|
-
'types-redis>=4.5.5.2,<5.0.0.0',
|
62
|
-
'types-requests>=2.31.0.1,<3.0.0.0',
|
63
|
-
'wget>=3.2,<4.0']
|
64
|
-
|
65
|
-
extras_require = \
|
66
|
-
{'hf-embeddings': ['sentence-transformers==2.2.2', 'torch==2.0.0']}
|
67
|
-
|
68
|
-
setup_kwargs = {
|
69
|
-
'name': 'langroid',
|
70
|
-
'version': '0.1.26',
|
71
|
-
'description': 'Harness LLMs with Multi-Agent Programming',
|
72
|
-
'long_description': '<div align="center">\n <img src="docs/assets/langroid-card-lambda-ossem-rust-1200-630.png" alt="Logo" \n width="400" align="center">\n</div>\n\n<div align="center">\n\n[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)\n[](https://github.com/langroid/langroid/actions/workflows/validate.yml)\n[](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)\n[](https://langroid.github.io/langroid)\n[](https://discord.gg/g3nAXCbZ)\n\n</div>\n\n<h3 align="center">\n <a target="_blank" \n href="https://langroid.github.io/langroid/" rel="dofollow">\n <strong>Explore the docs</strong></a>\n ·\n <a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">\n <strong>Examples Repo</strong></a>\n ·\n <a target="_blank" href="https://discord.gg/g3nAXCbZ" rel="dofollow">\n <strong>Discord</strong></a>\n ·\n <a target="_blank" href="./CONTRIBUTING.md" rel="dofollow">\n <strong>Contributing</strong></a>\n\n <br />\n</h3>\n\n`Langroid` is an intuitive, lightweight, extensible and principled\nPython framework to easily build LLM-powered applications. \nYou set up Agents, equip them with optional components (LLM, \nvector-store and methods), assign them tasks, and have them \ncollaboratively solve a problem by exchanging messages. \nThis Multi-Agent paradigm is inspired by the\n[Actor Framework](https://en.wikipedia.org/wiki/Actor_model)\n(but you do not need to know anything about this!).\n\n\n# :rocket: Demo\nSuppose you want to extract structured information about the key terms \nof a commercial lease document. You can easily do this with Langroid using a two-agent system,\nas we show in the [langroid-examples](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py) repo.\nThe demo showcases several features of Langroid:\n- Multi-agent collaboration: `LeaseExtractor` is in charge of the task, and its LLM (GPT4) generates questions \nto be answered by the `DocAgent`.\n- Retrieval augmented question-answering: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to \nanswer the `LeaseExtractor`\'s questions.\n- Function-calling (also known as tool/plugin): When it has all the information it \nneeds, the `LeaseExtractor` LLM presents the information in a structured \nformat using a Function-call. \n\nHere is what it looks like in action:\n\n\n\n\n# :zap: Highlights\n\n- **Agents as first-class citizens:** The [Agent](https://langroid.github.io/langroid/reference/agent/base/#langroid.agent.base.Agent) class encapsulates LLM conversation state,\n and optionally a vector-store and tools. Agents are a core abstraction in Langroid;\n Agents act as _message transformers_, and by default provide 3 _responder_ methods, one corresponding to each entity: LLM, Agent, User.\n- **Tasks:** A [Task](https://langroid.github.io/langroid/reference/agent/task/) class wraps an Agent, and gives the agent instructions (or roles, or goals), \n manages iteration over an Agent\'s responder methods, \n and orchestrates multi-agent interactions via hierarchical, recursive\n task-delegation. The `Task.run()` method has the same \n type-signature as an Agent\'s responder\'s methods, and this is key to how \n a task of an agent can delegate to other sub-tasks: from the point of view of a Task,\n sub-tasks are simply additional responders, to be used in a round-robin fashion \n after the agent\'s own responders.\n- **Modularity, Reusabilily, Loose coupling:** The `Agent` and `Task` abstractions allow users to design\n Agents with specific skills, wrap them in Tasks, and combine tasks in a flexible way.\n- **LLM Support**: Langroid supports OpenAI LLMs including GPT-3.5-Turbo,\n GPT-4-0613\n- **Caching of LLM prompts, responses:** Langroid uses [Redis](https://redis.com/try-free/) for caching.\n- **Vector-stores**: [Qdrant](https://qdrant.tech/) and [Chroma](https://www.trychroma.com/) are currently supported.\n Vector stores allow for Retrieval-Augmented-Generation (RAG).\n- **Grounding and source-citation:** Access to external documents via vector-stores \n allows for grounding and source-citation.\n- **Observability, Logging, Lineage:** Langroid generates detailed logs of multi-agent interactions and\n maintains provenance/lineage of messages, so that you can trace back\n the origin of a message.\n- **Tools/Plugins/Function-calling**: Langroid supports OpenAI\'s recently\n released [function calling](https://platform.openai.com/docs/guides/gpt/function-calling)\n feature. In addition, Langroid has its own native equivalent, which we\n call **tools** (also known as "plugins" in other contexts). Function\n calling and tools have the same developer-facing interface, implemented\n using [Pydantic](https://docs.pydantic.dev/latest/),\n which makes it very easy to define tools/functions and enable agents\n to use them. Benefits of using Pydantic are that you never have to write\n complex JSON specs for function calling, and when the LLM\n hallucinates malformed JSON, the Pydantic error message is sent back to\n the LLM so it can fix it!\n\n--- \n\n# :gear: Installation and Setup\n\n### Install `langroid` \nUse `pip` to install `langroid` (from PyPi) to your virtual environment:\n```bash\npip install langroid\n```\nThe core Langroid package lets you use OpenAI Embeddings models via their API. \nIf you instead want to use the `all-MiniLM-L6-v2` embeddings model\nfrom from HuggingFace, install Langroid like this:\n```bash\npip install langroid[hf-embeddings]\n```\nNote that this will install `torch` and `sentence-transformers` libraries.\n\n### Set up environment variables (API keys, etc)\n\nIn the root of the repo, copy the `.env-template` file to a new file `.env`: \n```bash\ncp .env-template .env\n```\nThen insert your OpenAI API Key. If you don\'t have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).\nYour `.env` file should look like this:\n\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\n````\n\nCurrently only OpenAI models are supported. Others will be added later\n(Pull Requests welcome!).\n\nAll of the below are optional and not strictly needed to run any of the examples.\n\n- **Qdrant** Vector Store API Key, URL. This is only required if you want to use Qdrant cloud.\n You can sign up for a free 1GB account at [Qdrant cloud](https://cloud.qdrant.io).\n If you skip setting up these, Langroid will use Qdrant in local-storage mode.\n Alternatively [Chroma](https://docs.trychroma.com/) is also currently supported. \n We use the local-storage version of Chroma, so there is no need for an API key.\n- **Redis** Password, host, port: This is optional, and only needed to cache LLM API responses\n using Redis Cloud. Redis [offers](https://redis.com/try-free/) a free 30MB Redis account\n which is more than sufficient to try out Langroid and even beyond.\n If you don\'t set up these, Langroid will use a pure-python \n Redis in-memory cache via the [Fakeredis](https://fakeredis.readthedocs.io/en/latest/) library.\n- **GitHub** Personal Access Token (required for apps that need to analyze git\n repos; token-based API calls are less rate-limited). See this\n [GitHub page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n\nIf you add all of these optional variables, your `.env` file should look like this:\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\nGITHUB_ACCESS_TOKEN=your-personal-access-token-no-quotes\nREDIS_PASSWORD=your-redis-password-no-quotes\nREDIS_HOST=your-redis-hostname-no-quotes\nREDIS_PORT=your-redis-port-no-quotes\nQDRANT_API_KEY=your-key\nQDRANT_API_URL=https://your.url.here:6333 # note port number must be included\n```\n\n\n---\n\n# :tada: Usage Examples\n\nThese are quick teasers to give a glimpse of what you can do with Langroid\nand how your code would look. \n\n:warning: The code snippets below are intended to give a flavor of the code\nand they are **not** complete runnable examples! For that we encourage you to \nconsult the [`langroid-examples`](https://github.com/langroid/langroid-examples) \nrepository.\n\n:information_source: The various LLM prompts and instructions in Langroid\nhave been tested to work well with GPT4.\nSwitching to GPT3.5-Turbo is easy via a config flag\n(e.g., `cfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT3_5_TURBO)`),\nand may suffice for some applications, but in general you may see inferior results.\n\n\n:book: Also see the\n[`Getting Started Guide`](https://langroid.github.io/langroid/quick-start/)\nfor a detailed tutorial.\n\nClick to expand any of the code examples below:\n\n<details>\n<summary> <b> Direct interaction with OpenAI LLM </b> </summary>\n\n```python\nfrom langroid.language_models.openai_gpt import ( \n OpenAIGPTConfig, OpenAIChatModel, OpenAIGPT,\n)\nfrom langroid.language_models.base import LLMMessage, Role\n\ncfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT4)\n\nmdl = OpenAIGPT(cfg)\n\nmessages = [\n LLMMessage(content="You are a helpful assistant", role=Role.SYSTEM), \n LLMMessage(content="What is the capital of Ontario?", role=Role.USER),\n]\nresponse = mdl.chat(messages, max_tokens=200)\nprint(response.message)\n```\n</details>\n\n<details>\n<summary> <b> Define an agent, set up a task, and run it </b> </summary>\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\n\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None, # no vector store\n)\nagent = ChatAgent(config)\n# get response from agent\'s LLM, and put this in an interactive loop...\n# answer = agent.llm_response("What is the capital of Ontario?")\n # ... OR instead, set up a task (which has a built-in loop) and run it\ntask = Task(agent, name="Bot") \ntask.run() # ... a loop seeking response from LLM or User at each turn\n```\n</details>\n\n<details>\n<summary><b> Three communicating agents </b></summary>\n\n```python\n\nA toy numbers game, where when given a number `n`:\n- `repeater_agent`\'s LLM simply returns `n`,\n- `even_agent`\'s LLM returns `n/2` if `n` is even, else says "DO-NOT-KNOW"\n- `odd_agent`\'s LLM returns `3*n+1` if `n` is odd, else says "DO-NOT-KNOW"\n\nFirst define the 3 agents, and set up their tasks with instructions:\n\n```python\nfrom langroid.utils.constants import NO_ANSWER\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb = None,\n)\nrepeater_agent = ChatAgent(config)\nrepeater_task = Task(\n repeater_agent,\n name = "Repeater",\n system_message="""\n Your job is to repeat whatever number you receive.\n """,\n llm_delegate=True, # LLM takes charge of task\n single_round=False, \n)\neven_agent = ChatAgent(config)\neven_task = Task(\n even_agent,\n name = "EvenHandler",\n system_message=f"""\n You will be given a number. \n If it is even, divide by 2 and say the result, nothing else.\n If it is odd, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n\nodd_agent = ChatAgent(config)\nodd_task = Task(\n odd_agent,\n name = "OddHandler",\n system_message=f"""\n You will be given a number n. \n If it is odd, return (n*3+1), say nothing else. \n If it is even, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n```\nThen add the `even_task` and `odd_task` as sub-tasks of `repeater_task`, \nand run the `repeater_task`, kicking it off with a number as input:\n```python\nrepeater_task.add_sub_task([even_task, odd_task])\nrepeater_task.run("3")\n```\n\n</details>\n\n<details>\n<summary><b> Simple Tool/Function-calling example </b></summary>\n\nLangroid leverages Pydantic to support OpenAI\'s\n[Function-calling API](https://platform.openai.com/docs/guides/gpt/function-calling)\nas well as its own native tools. The benefits are that you don\'t have to write\nany JSON to specify the schema, and also if the LLM hallucinates a malformed\ntool syntax, Langroid sends the Pydantic validation error (suitiably sanitized) \nto the LLM so it can fix it!\n\nSimple example: Say the agent has a secret list of numbers, \nand we want the LLM to find the smallest number in the list. \nWe want to give the LLM a `probe` tool/function which takes a\nsingle number `n` as argument. The tool handler method in the agent\nreturns how many numbers in its list are at most `n`.\n\nFirst define the tool using Langroid\'s `ToolMessage` class:\n\n\n```python\nfrom langroid.agent.tool_message import ToolMessage\nclass ProbeTool(ToolMessage):\n request: str = "probe" # specifies which agent method handles this tool\n purpose: str = """\n To find how many numbers in my list are less than or equal to \n the <number> you specify.\n """ # description used to instruct the LLM on when/how to use the tool\n number: int # required argument to the tool\n```\n\nThen define a `SpyGameAgent` as a subclass of `ChatAgent`, \nwith a method `probe` that handles this tool:\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nclass SpyGameAgent(ChatAgent):\n def __init__(self, config: ChatAgentConfig):\n super().__init__(config)\n self.numbers = [3, 4, 8, 11, 15, 25, 40, 80, 90]\n\n def probe(self, msg: ProbeTool) -> str:\n # return how many numbers in self.numbers are less or equal to msg.number\n return str(len([n for n in self.numbers if n <= msg.number]))\n```\n\nWe then instantiate the agent and enable it to use and respond to the tool:\n\n```python\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nspy_game_agent = SpyGameAgent(\n ChatAgentConfig(\n name="Spy",\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None,\n use_tools=False, # don\'t use Langroid native tool\n use_functions_api=True, # use OpenAI function-call API\n )\n)\nspy_game_agent.enable_message(ProbeTool)\n```\n\nFor a full working example see the\n[chat-agent-tool.py](https://github.com/langroid/langroid-examples/blob/main/examples/quick-start/chat-agent-tool.py)\nscript in the `langroid-examples` repo.\n</details>\n\n<details>\n<summary> <b>Tool/Function-calling to extract structured information from text </b> </summary>\n\nSuppose you want an agent to extract \nthe key terms of a lease, from a lease document, as a nested JSON structure.\nFirst define the desired structure via Pydantic models:\n\n```python\nfrom pydantic import BaseModel\nclass LeasePeriod(BaseModel):\n start_date: str\n end_date: str\n\n\nclass LeaseFinancials(BaseModel):\n monthly_rent: str\n deposit: str\n\nclass Lease(BaseModel):\n period: LeasePeriod\n financials: LeaseFinancials\n address: str\n```\n\nThen define the `LeaseMessage` tool as a subclass of Langroid\'s `ToolMessage`.\nNote the tool has a required argument `terms` of type `Lease`:\n\n```python\nclass LeaseMessage(ToolMessage):\n request: str = "lease_info"\n purpose: str = """\n Collect information about a Commercial Lease.\n """\n terms: Lease\n```\n\nThen define a `LeaseExtractorAgent` with a method `lease_info` that handles this tool,\ninstantiate the agent, and enable it to use and respond to this tool:\n\n```python\nclass LeaseExtractorAgent(ChatAgent):\n def lease_info(self, message: LeaseMessage) -> str:\n print(\n f"""\n DONE! Successfully extracted Lease Info:\n {message.terms}\n """\n )\n return json.dumps(message.terms.dict())\n \nlease_extractor_agent = LeaseExtractorAgent(\n ChatAgentConfig(\n llm=OpenAIGPTConfig(),\n use_functions_api=False,\n use_tools=True,\n )\n)\nlease_extractor_agent.enable_message(LeaseMessage)\n```\n\nSee the [`chat_multi_extract.py`](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py)\nscript in the `langroid-examples` repo for a full working example.\n</details>\n\n<details>\n<summary><b> Chat with documents (file paths, URLs, etc) </b></summary>\n\nLangroid provides a specialized agent class `DocChatAgent` for this purpose.\nIt incorporates document sharding, embedding, storage in a vector-DB, \nand retrieval-augmented query-answer generation.\nUsing this class to chat with a collection of documents is easy.\nFirst create a `DocChatAgentConfig` instance, with a \n`doc_paths` field that specifies the documents to chat with.\n\n```python\nfrom langroid.agent.doc_chat_agent import DocChatAgentConfig\nconfig = DocChatAgentConfig(\n doc_paths = [\n "https://en.wikipedia.org/wiki/Language_model",\n "https://en.wikipedia.org/wiki/N-gram_language_model",\n "/path/to/my/notes-on-language-models.txt",\n ]\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=VectorStoreConfig(\n type="qdrant",\n ),\n)\n```\n\nThen instantiate the `DocChatAgent`, ingest the docs into the vector-store:\n\n```python\nagent = DocChatAgent(config)\nagent.ingest()\n```\nThen we can either ask the agent one-off questions,\n```python\nagent.chat("What is a language model?")\n```\nor wrap it in a `Task` and run an interactive loop with the user:\n```python\nfrom langroid.task import Task\ntask = Task(agent)\ntask.run()\n```\n\nSee full working scripts in the \n[`docqa`](https://github.com/langroid/langroid-examples/tree/main/examples/docqa)\nfolder of the `langroid-examples` repo.\n</details>\n\n---\n\n# :heart: Thank you to our supporters!\n\n[](https://github.com/langroid/langroid/stargazers)\n\n# Contributors\n\n- Prasad Chalasani (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)\n- Somesh Jha (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)\n- Mohannad Alhanahnah (Research Associate, U Wisc at Madison)\n- Ashish Hooda (IIT BTech/CS; PhD Candidate, U Wisc at Madison)\n\n',
|
73
|
-
'author': 'Prasad Chalasani',
|
74
|
-
'author_email': 'pchalasani@gmail.com',
|
75
|
-
'maintainer': 'None',
|
76
|
-
'maintainer_email': 'None',
|
77
|
-
'url': 'None',
|
78
|
-
'packages': packages,
|
79
|
-
'package_data': package_data,
|
80
|
-
'install_requires': install_requires,
|
81
|
-
'extras_require': extras_require,
|
82
|
-
'python_requires': '>=3.8.1,<3.12',
|
83
|
-
}
|
84
|
-
|
85
|
-
|
86
|
-
setup(**setup_kwargs)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|