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.
Files changed (71) hide show
  1. {langroid-0.1.26 → langroid-0.1.28}/PKG-INFO +32 -14
  2. {langroid-0.1.26 → langroid-0.1.28}/README.md +30 -13
  3. langroid-0.1.28/langroid/cachedb/momento_cachedb.py +78 -0
  4. {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/base.py +2 -4
  5. {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/openai_gpt.py +9 -2
  6. langroid-0.1.28/langroid/prompts/transforms.py +84 -0
  7. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/configuration.py +1 -0
  8. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/logging.py +3 -5
  9. {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/chromadb.py +2 -3
  10. {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/qdrantdb.py +2 -3
  11. {langroid-0.1.26 → langroid-0.1.28}/pyproject.toml +3 -2
  12. langroid-0.1.28/setup.py +87 -0
  13. langroid-0.1.26/langroid/prompts/transforms.py +0 -236
  14. langroid-0.1.26/setup.py +0 -86
  15. {langroid-0.1.26 → langroid-0.1.28}/LICENSE +0 -0
  16. {langroid-0.1.26 → langroid-0.1.28}/langroid/__init__.py +0 -0
  17. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/__init__.py +0 -0
  18. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/base.py +0 -0
  19. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/chat_agent.py +0 -0
  20. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/chat_document.py +0 -0
  21. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/helpers.py +0 -0
  22. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/junk +0 -0
  23. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/__init__.py +0 -0
  24. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/doc_chat_agent.py +0 -0
  25. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/recipient_validator_agent.py +0 -0
  26. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/special/retriever_agent.py +0 -0
  27. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/task.py +0 -0
  28. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent/tool_message.py +0 -0
  29. {langroid-0.1.26 → langroid-0.1.28}/langroid/agent_config.py +0 -0
  30. {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/__init__.py +0 -0
  31. {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/base.py +0 -0
  32. {langroid-0.1.26 → langroid-0.1.28}/langroid/cachedb/redis_cachedb.py +0 -0
  33. {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/__init__.py +0 -0
  34. {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/base.py +0 -0
  35. {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/clustering.py +0 -0
  36. {langroid-0.1.26 → langroid-0.1.28}/langroid/embedding_models/models.py +0 -0
  37. {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/__init__.py +0 -0
  38. {langroid-0.1.26 → langroid-0.1.28}/langroid/language_models/utils.py +0 -0
  39. {langroid-0.1.26 → langroid-0.1.28}/langroid/mytypes.py +0 -0
  40. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/__init__.py +0 -0
  41. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/agent_chats.py +0 -0
  42. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/code-parsing.md +0 -0
  43. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/code_parser.py +0 -0
  44. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/json.py +0 -0
  45. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/para_sentence_split.py +0 -0
  46. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/parser.py +0 -0
  47. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/pdf_parser.py +0 -0
  48. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/repo_loader.py +0 -0
  49. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/url_loader.py +0 -0
  50. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/url_loader_cookies.py +0 -0
  51. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/urls.py +0 -0
  52. {langroid-0.1.26 → langroid-0.1.28}/langroid/parsing/utils.py +0 -0
  53. {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/__init__.py +0 -0
  54. {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/dialog.py +0 -0
  55. {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/prompts_config.py +0 -0
  56. {langroid-0.1.26 → langroid-0.1.28}/langroid/prompts/templates.py +0 -0
  57. {langroid-0.1.26 → langroid-0.1.28}/langroid/scripts/__init__.py +0 -0
  58. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/__init__.py +0 -0
  59. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/constants.py +0 -0
  60. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/docker.py +0 -0
  61. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/llms/__init__.py +0 -0
  62. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/llms/strings.py +0 -0
  63. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/output/__init__.py +0 -0
  64. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/output/printing.py +0 -0
  65. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/system.py +0 -0
  66. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/__init__.py +0 -0
  67. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/login.py +0 -0
  68. {langroid-0.1.26 → langroid-0.1.28}/langroid/utils/web/selenium_login.py +0 -0
  69. {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/__init__.py +0 -0
  70. {langroid-0.1.26 → langroid-0.1.28}/langroid/vector_store/base.py +0 -0
  71. {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.26
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
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
68
+ [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
67
69
  [![Lint](https://github.com/langroid/langroid/actions/workflows/validate.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/validate.yml)
68
70
  [![Docs](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)
69
71
  [![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](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>Explore the docs</strong></a>
79
+ <strong>Documentation</strong></a>
78
80
  &middot;
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 several features of Langroid:
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: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to
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. If you don't have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).
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
- Currently only OpenAI models are supported. Others will be added later
187
- (Pull Requests welcome!).
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
- [![Stargazers repo roster for @langroid/langroid](https://reporoster.com/stars/langroid/langroid)](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
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
9
+ [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
9
10
  [![Lint](https://github.com/langroid/langroid/actions/workflows/validate.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/validate.yml)
10
11
  [![Docs](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)
11
12
  [![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](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>Explore the docs</strong></a>
20
+ <strong>Documentation</strong></a>
20
21
  &middot;
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 several features of Langroid:
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: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to
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. If you don't have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).
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
- Currently only OpenAI models are supported. Others will be added later
129
- (Pull Requests welcome!).
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
- [![Stargazers repo roster for @langroid/langroid](https://reporoster.com/stars/langroid/langroid)](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 = 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.redis_cachedb import RedisCache
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 = RedisCache(config.cache_config)
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
- self.file = open(self.log_file, "a")
128
- self.console = Console(file=self.file, force_terminal=True, width=200)
129
- self.console.print(message)
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 = 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 = 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.26"
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"]
@@ -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[![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)\n[![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)\n[![Lint](https://github.com/langroid/langroid/actions/workflows/validate.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/validate.yml)\n[![Docs](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)\n[![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](https://langroid.github.io/langroid)\n[![Static Badge](https://img.shields.io/badge/Discord-Orange?link=https%3A%2F%2Fdiscord.gg%2Fg3nAXCbZ&link=https%3A%2F%2Fdiscord.gg%2Fg3nAXCbZ)](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 &middot;\n <a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">\n <strong>Examples Repo</strong></a>\n &middot;\n <a target="_blank" href="https://discord.gg/g3nAXCbZ" rel="dofollow">\n <strong>Discord</strong></a>\n &middot;\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![Demo](docs/assets/demos/lease-extractor-demo.gif)\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[![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)\n[![Lint](https://github.com/langroid/langroid/actions/workflows/validate.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/validate.yml)\n[![Docs](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)\n[![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](https://langroid.github.io/langroid)\n[![Static Badge](https://img.shields.io/badge/Discord-Orange?link=https%3A%2F%2Fdiscord.gg%2Fg3nAXCbZ&link=https%3A%2F%2Fdiscord.gg%2Fg3nAXCbZ)](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 &middot;\n <a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">\n <strong>Examples Repo</strong></a>\n &middot;\n <a target="_blank" href="https://discord.gg/g3nAXCbZ" rel="dofollow">\n <strong>Discord</strong></a>\n &middot;\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![Demo](docs/assets/demos/lease-extractor-demo.gif)\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[![Stargazers repo roster for @langroid/langroid](https://reporoster.com/stars/langroid/langroid)](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