langroid 0.1.39__py3-none-any.whl → 0.1.40__py3-none-any.whl
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/agent/base.py +2 -1
- langroid/agent/special/doc_chat_agent.py +40 -10
- langroid/agent/special/retriever_agent.py +2 -2
- langroid/agent/special/sql_chat_agent.py +3 -3
- langroid/agent/stateless_tools/google_search_tool.py +12 -70
- langroid/parsing/web_search.py +79 -0
- {langroid-0.1.39.dist-info → langroid-0.1.40.dist-info}/METADATA +9 -10
- {langroid-0.1.39.dist-info → langroid-0.1.40.dist-info}/RECORD +10 -9
- {langroid-0.1.39.dist-info → langroid-0.1.40.dist-info}/LICENSE +0 -0
- {langroid-0.1.39.dist-info → langroid-0.1.40.dist-info}/WHEEL +0 -0
langroid/agent/base.py
CHANGED
@@ -128,9 +128,10 @@ class Agent(ABC):
|
|
128
128
|
raise ValueError("message_class must be a subclass of ToolMessage")
|
129
129
|
tool = message_class.default_value("request")
|
130
130
|
self.llm_tools_map[tool] = message_class
|
131
|
-
if hasattr(message_class, "handle"):
|
131
|
+
if hasattr(message_class, "handle") and not hasattr(self, tool):
|
132
132
|
"""
|
133
133
|
If the message class has a `handle` method,
|
134
|
+
and does NOT have a method with the same name as the tool,
|
134
135
|
then we create a method for the agent whose name
|
135
136
|
is the value of `tool`, and whose body is the `handle` method.
|
136
137
|
This removes a separate step of having to define this method
|
@@ -304,14 +304,21 @@ class DocChatAgent(ChatAgent):
|
|
304
304
|
)
|
305
305
|
|
306
306
|
@no_type_check
|
307
|
-
def
|
308
|
-
"""
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
307
|
+
def get_relevant_extracts(self, query: str) -> List[Document]:
|
308
|
+
"""
|
309
|
+
Get list of docs or extracts relevant to a query. These could be:
|
310
|
+
- the original docs, if they exist and are not too long, or
|
311
|
+
- a list of doc-chunks retrieved from the VecDB
|
312
|
+
that are "relevant" to the query, if these are not too long, or
|
313
|
+
- a list of relevant extracts from these doc-chunks
|
314
|
+
|
315
|
+
Args:
|
316
|
+
query (str): query to search for
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
List[Document]: list of relevant docs
|
320
|
+
|
321
|
+
"""
|
315
322
|
if len(self.dialog) > 0 and not self.config.conversation_mode:
|
316
323
|
# In conversation mode, we let self.message_history accumulate
|
317
324
|
# and do not need to convert to standalone query
|
@@ -324,7 +331,7 @@ class DocChatAgent(ChatAgent):
|
|
324
331
|
|
325
332
|
passages = self.original_docs
|
326
333
|
|
327
|
-
# if original docs too long, no need to look for relevant parts.
|
334
|
+
# if original docs not too long, no need to look for relevant parts.
|
328
335
|
if (
|
329
336
|
passages is None
|
330
337
|
or self.original_docs_length > self.config.max_context_tokens
|
@@ -335,7 +342,7 @@ class DocChatAgent(ChatAgent):
|
|
335
342
|
k=self.config.parsing.n_similar_docs,
|
336
343
|
)
|
337
344
|
if len(docs_and_scores) == 0:
|
338
|
-
return
|
345
|
+
return []
|
339
346
|
passages = [
|
340
347
|
Document(content=d.content, metadata=d.metadata)
|
341
348
|
for (d, _) in docs_and_scores
|
@@ -347,6 +354,29 @@ class DocChatAgent(ChatAgent):
|
|
347
354
|
with console.status("[cyan]LLM Extracting verbatim passages..."):
|
348
355
|
with StreamingIfAllowed(self.llm, False):
|
349
356
|
extracts = self.llm.get_verbatim_extracts(query, passages)
|
357
|
+
|
358
|
+
return extracts
|
359
|
+
|
360
|
+
@no_type_check
|
361
|
+
def answer_from_docs(self, query: str) -> Document:
|
362
|
+
"""
|
363
|
+
Answer query based on relevant docs from the VecDB
|
364
|
+
|
365
|
+
Args:
|
366
|
+
query (str): query to answer
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
Document: answer
|
370
|
+
"""
|
371
|
+
response = Document(
|
372
|
+
content=NO_ANSWER,
|
373
|
+
metadata=DocMetaData(
|
374
|
+
source="None",
|
375
|
+
),
|
376
|
+
)
|
377
|
+
extracts = self.get_relevant_extracts(query)
|
378
|
+
if len(extracts) == 0:
|
379
|
+
return response
|
350
380
|
with ExitStack() as stack:
|
351
381
|
# conditionally use Streaming or rich console context
|
352
382
|
cm = (
|
@@ -97,7 +97,7 @@ class RetrieverAgent(DocChatAgent, ABC):
|
|
97
97
|
query_str = query.content
|
98
98
|
else:
|
99
99
|
query_str = query
|
100
|
-
docs = self.
|
100
|
+
docs = self.get_relevant_extracts(query_str)
|
101
101
|
if len(docs) == 0:
|
102
102
|
return None
|
103
103
|
content = "\n\n".join([d.content for d in docs])
|
@@ -135,7 +135,7 @@ class RetrieverAgent(DocChatAgent, ABC):
|
|
135
135
|
]
|
136
136
|
return docs
|
137
137
|
|
138
|
-
def
|
138
|
+
def get_relevant_extracts(self, query: str) -> List[Document]:
|
139
139
|
"""
|
140
140
|
Given a query, get the records/docs whose contents are most relevant to the
|
141
141
|
query. First get nearest docs from vector store, then select the best
|
@@ -28,9 +28,9 @@ console = Console()
|
|
28
28
|
|
29
29
|
DEFAULT_SQL_CHAT_SYSTEM_MESSAGE = """
|
30
30
|
You are a savvy data scientist/database administrator, with expertise in
|
31
|
-
answering questions by querying a
|
31
|
+
answering questions by querying a {dialect} database.
|
32
32
|
You do not have access to the database 'db' directly, so you will need to use the
|
33
|
-
`run_query` tool/function-call to answer
|
33
|
+
`run_query` tool/function-call to answer questions.
|
34
34
|
|
35
35
|
The below JSON schema maps the SQL database structure. It outlines tables, each
|
36
36
|
with a description and columns. Each table is identified by a key,
|
@@ -146,7 +146,7 @@ class SQLChatAgent(ChatAgent):
|
|
146
146
|
|
147
147
|
# Update the system message with the table information
|
148
148
|
self.config.system_message = self.config.system_message.format(
|
149
|
-
schema_dict=schema_dict
|
149
|
+
schema_dict=schema_dict, dialect=self.engine.dialect.name
|
150
150
|
)
|
151
151
|
|
152
152
|
super().__init__(config)
|
@@ -1,61 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""
|
2
|
+
A tool to trigger a Google search for a given query, and return the top results with
|
3
|
+
their titles, links, summaries. Since the tool is stateless (i.e. does not need
|
4
|
+
access to agent state), it can be enabled for any agent, without having to define a
|
5
|
+
special method inside the agent: `agent.enable_message(GoogleSearchTool)`
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
from requests.models import Response
|
7
|
+
NOTE: Using this tool requires setting the GOOGLE_API_KEY and GOOGLE_CSE_ID
|
8
|
+
environment variables in your `.env` file, as explained in the
|
9
|
+
[README](https://github.com/langroid/langroid#gear-installation-and-setup).
|
10
|
+
"""
|
9
11
|
|
10
12
|
from langroid.agent.tool_message import ToolMessage
|
11
|
-
|
12
|
-
|
13
|
-
class GoogleSearchResult:
|
14
|
-
"""
|
15
|
-
Class representing a Google Search result, containing the title, link,
|
16
|
-
summary and full content of the result.
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(
|
20
|
-
self,
|
21
|
-
title: str,
|
22
|
-
link: str,
|
23
|
-
max_content_length: int = 3500,
|
24
|
-
max_summary_length: int = 300,
|
25
|
-
):
|
26
|
-
"""
|
27
|
-
Args:
|
28
|
-
title (str): The title of the search result.
|
29
|
-
link (str): The link to the search result.
|
30
|
-
max_content_length (int): The maximum length of the full content.
|
31
|
-
max_summary_length (int): The maximum length of the summary.
|
32
|
-
"""
|
33
|
-
self.title = title
|
34
|
-
self.link = link
|
35
|
-
self.max_content_length = max_content_length
|
36
|
-
self.max_summary_length = max_summary_length
|
37
|
-
self.full_content = self.get_full_content()
|
38
|
-
self.summary = self.get_summary()
|
39
|
-
|
40
|
-
def get_summary(self) -> str:
|
41
|
-
return self.full_content[: self.max_summary_length]
|
42
|
-
|
43
|
-
def get_full_content(self) -> str:
|
44
|
-
response: Response = requests.get(self.link)
|
45
|
-
soup: BeautifulSoup = BeautifulSoup(response.text, "lxml")
|
46
|
-
text = " ".join(soup.stripped_strings)
|
47
|
-
return text[: self.max_content_length]
|
48
|
-
|
49
|
-
def __str__(self) -> str:
|
50
|
-
return f"Title: {self.title}\nLink: {self.link}\nSummary: {self.summary}"
|
51
|
-
|
52
|
-
def to_dict(self) -> Dict[str, str]:
|
53
|
-
return {
|
54
|
-
"title": self.title,
|
55
|
-
"link": self.link,
|
56
|
-
"summary": self.summary,
|
57
|
-
"full_content": self.full_content,
|
58
|
-
}
|
13
|
+
from langroid.parsing.web_search import google_search
|
59
14
|
|
60
15
|
|
61
16
|
class GoogleSearchTool(ToolMessage):
|
@@ -68,19 +23,6 @@ class GoogleSearchTool(ToolMessage):
|
|
68
23
|
num_results: int
|
69
24
|
|
70
25
|
def handle(self) -> str:
|
71
|
-
|
72
|
-
|
73
|
-
cse_id = os.getenv("GOOGLE_CSE_ID")
|
74
|
-
service: Resource = build("customsearch", "v1", developerKey=api_key)
|
75
|
-
raw_results = (
|
76
|
-
service.cse()
|
77
|
-
.list(q=self.query, cx=cse_id, num=self.num_results)
|
78
|
-
.execute()["items"]
|
79
|
-
)
|
80
|
-
|
81
|
-
search_results: List[GoogleSearchResult] = [
|
82
|
-
GoogleSearchResult(result["title"], result["link"], 3500, 300)
|
83
|
-
for result in raw_results
|
84
|
-
]
|
85
|
-
# return Title and Link of each result, separated by two newlines
|
26
|
+
search_results = google_search(self.query, self.num_results)
|
27
|
+
# return Title, Link, Summary of each result, separated by two newlines
|
86
28
|
return "\n\n".join(str(result) for result in search_results)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
"""
|
2
|
+
Utilities for web search.
|
3
|
+
|
4
|
+
NOTE: Using Google Search requires setting the GOOGLE_API_KEY and GOOGLE_CSE_ID
|
5
|
+
environment variables in your `.env` file, as explained in the
|
6
|
+
[README](https://github.com/langroid/langroid#gear-installation-and-setup).
|
7
|
+
"""
|
8
|
+
|
9
|
+
import os
|
10
|
+
from typing import Dict, List
|
11
|
+
|
12
|
+
import requests
|
13
|
+
from bs4 import BeautifulSoup
|
14
|
+
from dotenv import load_dotenv
|
15
|
+
from googleapiclient.discovery import Resource, build
|
16
|
+
from requests.models import Response
|
17
|
+
|
18
|
+
|
19
|
+
class WebSearchResult:
|
20
|
+
"""
|
21
|
+
Class representing a Web Search result, containing the title, link,
|
22
|
+
summary and full content of the result.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(
|
26
|
+
self,
|
27
|
+
title: str,
|
28
|
+
link: str,
|
29
|
+
max_content_length: int = 3500,
|
30
|
+
max_summary_length: int = 300,
|
31
|
+
):
|
32
|
+
"""
|
33
|
+
Args:
|
34
|
+
title (str): The title of the search result.
|
35
|
+
link (str): The link to the search result.
|
36
|
+
max_content_length (int): The maximum length of the full content.
|
37
|
+
max_summary_length (int): The maximum length of the summary.
|
38
|
+
"""
|
39
|
+
self.title = title
|
40
|
+
self.link = link
|
41
|
+
self.max_content_length = max_content_length
|
42
|
+
self.max_summary_length = max_summary_length
|
43
|
+
self.full_content = self.get_full_content()
|
44
|
+
self.summary = self.get_summary()
|
45
|
+
|
46
|
+
def get_summary(self) -> str:
|
47
|
+
return self.full_content[: self.max_summary_length]
|
48
|
+
|
49
|
+
def get_full_content(self) -> str:
|
50
|
+
response: Response = requests.get(self.link)
|
51
|
+
soup: BeautifulSoup = BeautifulSoup(response.text, "lxml")
|
52
|
+
text = " ".join(soup.stripped_strings)
|
53
|
+
return text[: self.max_content_length]
|
54
|
+
|
55
|
+
def __str__(self) -> str:
|
56
|
+
return f"Title: {self.title}\nLink: {self.link}\nSummary: {self.summary}"
|
57
|
+
|
58
|
+
def to_dict(self) -> Dict[str, str]:
|
59
|
+
return {
|
60
|
+
"title": self.title,
|
61
|
+
"link": self.link,
|
62
|
+
"summary": self.summary,
|
63
|
+
"full_content": self.full_content,
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
def google_search(query: str, num_results: int = 5) -> List[WebSearchResult]:
|
68
|
+
load_dotenv()
|
69
|
+
api_key = os.getenv("GOOGLE_API_KEY")
|
70
|
+
cse_id = os.getenv("GOOGLE_CSE_ID")
|
71
|
+
service: Resource = build("customsearch", "v1", developerKey=api_key)
|
72
|
+
raw_results = (
|
73
|
+
service.cse().list(q=query, cx=cse_id, num=num_results).execute()["items"]
|
74
|
+
)
|
75
|
+
|
76
|
+
return [
|
77
|
+
WebSearchResult(result["title"], result["link"], 3500, 300)
|
78
|
+
for result in raw_results
|
79
|
+
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.40
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -114,7 +114,7 @@ for ideas on what to contribute.
|
|
114
114
|
<summary> <b>:fire: Updates/Releases</b></summary>
|
115
115
|
|
116
116
|
- **Aug 2023:**
|
117
|
-
- **0.1.
|
117
|
+
- **0.1.39:** [`GoogleSearchTool`](langroid/agent/stateless_tools/google_search_tool.py) to enable Agents (their LLM) to do Google searches via function-calling/tools.
|
118
118
|
See [this chat example](examples/basic/chat-search.py) for how easy it is to add this tool to an agent.
|
119
119
|
- **Colab notebook** to try the quick-start examples: [](https://colab.research.google.com/github/langroid/langroid/blob/main/examples/langroid_quick_examples.ipynb)
|
120
120
|
- **0.1.37:** Added [`SQLChatAgent`](langroid/agent/special/sql_chat_agent.py) -- thanks to our latest contributor [Rithwik Babu](https://github.com/rithwikbabu)!
|
@@ -232,7 +232,8 @@ export OPENAI_API_KEY=your-key-here-without-quotes
|
|
232
232
|
<details>
|
233
233
|
<summary><b>Optional Setup Instructions (click to expand) </b></summary>
|
234
234
|
|
235
|
-
All of the
|
235
|
+
All of the following environment variable settings are optional, and some are only needed
|
236
|
+
to use specific features (as noted below).
|
236
237
|
|
237
238
|
- **Qdrant** Vector Store API Key, URL. This is only required if you want to use Qdrant cloud.
|
238
239
|
You can sign up for a free 1GB account at [Qdrant cloud](https://cloud.qdrant.io).
|
@@ -357,8 +358,6 @@ task.run() # ... a loop seeking response from LLM or User at each turn
|
|
357
358
|
<details>
|
358
359
|
<summary><b> Three communicating agents </b></summary>
|
359
360
|
|
360
|
-
```python
|
361
|
-
|
362
361
|
A toy numbers game, where when given a number `n`:
|
363
362
|
- `repeater_agent`'s LLM simply returns `n`,
|
364
363
|
- `even_agent`'s LLM returns `n/2` if `n` is even, else says "DO-NOT-KNOW"
|
@@ -665,11 +664,11 @@ script in the `langroid-examples` repo.
|
|
665
664
|
|
666
665
|
If you like this repo, don't forget to leave a star :star: !
|
667
666
|
|
668
|
-
#
|
667
|
+
# Langroid Co-Founders
|
668
|
+
|
669
|
+
- [Prasad Chalasani](https://www.linkedin.com/in/pchalasani/) (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)
|
670
|
+
- [Somesh Jha](https://www.linkedin.com/in/somesh-jha-80208015/) (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)
|
671
|
+
|
669
672
|
|
670
|
-
- Prasad Chalasani (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)
|
671
|
-
- Somesh Jha (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)
|
672
|
-
- Mohannad Alhanahnah (Research Associate, U Wisc at Madison)
|
673
|
-
- Ashish Hooda (IIT BTech/CS; PhD Candidate, U Wisc at Madison)
|
674
673
|
|
675
674
|
|
@@ -1,18 +1,18 @@
|
|
1
1
|
langroid/__init__.py,sha256=sEKJ_5WJBAMZApevfeE3gxLK-eotVzJMJlT83G0rAko,30
|
2
2
|
langroid/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
langroid/agent/base.py,sha256=
|
3
|
+
langroid/agent/base.py,sha256=fWlSdA1omU9TcuphtRzREwvBZ_ZoMo7yOowplpsadN0,22569
|
4
4
|
langroid/agent/chat_agent.py,sha256=mLCHlYxU1lB7PGOLjjkaEQUqMNKcq0-HOjlG4ZcDjQE,20522
|
5
5
|
langroid/agent/chat_document.py,sha256=Rj7Hfp_FrNjuKsTMA3KyZhno5zKpmvnPPk7WgAuAF2Y,5745
|
6
6
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
8
8
|
langroid/agent/special/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
langroid/agent/special/doc_chat_agent.py,sha256=
|
9
|
+
langroid/agent/special/doc_chat_agent.py,sha256=FjuHxV6OyHvPcHuPgsm9kXMIL8qVhOM8oa-oYdzXzN8,16568
|
10
10
|
langroid/agent/special/recipient_validator_agent.py,sha256=x2UprcGlh-fyxQCZbb_fkKrruU5Om0mgOnNzk_PYBNM,4527
|
11
|
-
langroid/agent/special/retriever_agent.py,sha256=
|
12
|
-
langroid/agent/special/sql_chat_agent.py,sha256=
|
11
|
+
langroid/agent/special/retriever_agent.py,sha256=DeOB5crFjXBvDEZT9k9ZVinOfFM2VgS6tQWWFyXSk9o,7204
|
12
|
+
langroid/agent/special/sql_chat_agent.py,sha256=2pw8o-0ul909HAOUO3G7SVVAaB3qVBkjtaXneNzy1x0,10215
|
13
13
|
langroid/agent/special/table_chat_agent.py,sha256=FRkeEMvJxFievRgwschphIVNYYTLmheEyn7RQCggXdg,4953
|
14
14
|
langroid/agent/stateless_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
langroid/agent/stateless_tools/google_search_tool.py,sha256=
|
15
|
+
langroid/agent/stateless_tools/google_search_tool.py,sha256=64F9oMNdS237BBOitrvYXN4Il_ES_fNrHkh35tBEDfA,1160
|
16
16
|
langroid/agent/task.py,sha256=KJZt6lSfCAVctyFfBJWLFcfys73kjrQJV4Y3aMVwO1M,26233
|
17
17
|
langroid/agent/tool_message.py,sha256=7OdVcV7UyOZD2ihYgV1C_1fIwiWM-2pR8FFxoA1IgOo,5379
|
18
18
|
langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -43,6 +43,7 @@ langroid/parsing/url_loader.py,sha256=tdYPMC2V9zI_B207LFdU4aroZzJW4sY9y794XePFAV
|
|
43
43
|
langroid/parsing/url_loader_cookies.py,sha256=Lg4sNpRz9MByWq2mde6T0hKv68VZSV3mtMjNEHuFeSU,2327
|
44
44
|
langroid/parsing/urls.py,sha256=_Bcf1iRdT7cQrQ8hnbPX0Jtzxc0lVFaucTS5rJoKA14,3709
|
45
45
|
langroid/parsing/utils.py,sha256=__1Z6mFHk_TqHQY-9uU1aV_bIXeYw8H7NEXagUOEX0I,1818
|
46
|
+
langroid/parsing/web_search.py,sha256=hGUVoSJNdpoT5rsm-ikAteMiUropHrzKaxN8EVVqO2U,2496
|
46
47
|
langroid/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
48
|
langroid/prompts/dialog.py,sha256=SpfiSyofSgy2pwD1YboHR_yHO3LEEMbv6j2sm874jKo,331
|
48
49
|
langroid/prompts/prompts_config.py,sha256=EMK1Fm7EmS8y3CV4AkrVgn5K4NipiM4m7J8819W1KeM,98
|
@@ -67,7 +68,7 @@ langroid/vector_store/base.py,sha256=QZx3NUNwf2I0r3A7iuoUHIRGbqt_pFGD0hq1R-Yg8iM
|
|
67
68
|
langroid/vector_store/chromadb.py,sha256=s5pQkKjaMP-Tt5A8M10EInFzttaALPbJAq7q4gf0TKg,5235
|
68
69
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
69
70
|
langroid/vector_store/qdrantdb.py,sha256=KRvIIj1IZG2zFqejofMnRs2hT86B-27LgBEnuczdqOU,9072
|
70
|
-
langroid-0.1.
|
71
|
-
langroid-0.1.
|
72
|
-
langroid-0.1.
|
73
|
-
langroid-0.1.
|
71
|
+
langroid-0.1.40.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
72
|
+
langroid-0.1.40.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
73
|
+
langroid-0.1.40.dist-info/METADATA,sha256=ql9pYkR-oovH5Db91QIShwjPAq0bxXEWE7yxjwvvEiY,27547
|
74
|
+
langroid-0.1.40.dist-info/RECORD,,
|
File without changes
|
File without changes
|