langroid 0.1.38__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 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 answer_from_docs(self, query: str) -> Document:
308
- """Answer query based on docs in vecdb, and conv history"""
309
- response = Document(
310
- content=NO_ANSWER,
311
- metadata=DocMetaData(
312
- source="None",
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 response
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.get_relevant_docs(query_str)
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 get_relevant_docs(self, query: str) -> List[Document]:
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 SQL database.
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 the question.
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)
File without changes
@@ -1,61 +1,16 @@
1
- import os
2
- from typing import Dict, List
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
- import requests
5
- from bs4 import BeautifulSoup
6
- from dotenv import load_dotenv
7
- from googleapiclient.discovery import Resource, build
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
- load_dotenv()
72
- api_key = os.getenv("GOOGLE_API_KEY")
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.38
3
+ Version: 0.1.40
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -114,6 +114,8 @@ for ideas on what to contribute.
114
114
  <summary> <b>:fire: Updates/Releases</b></summary>
115
115
 
116
116
  - **Aug 2023:**
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
+ See [this chat example](examples/basic/chat-search.py) for how easy it is to add this tool to an agent.
117
119
  - **Colab notebook** to try the quick-start examples: [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langroid/langroid/blob/main/examples/langroid_quick_examples.ipynb)
118
120
  - **0.1.37:** Added [`SQLChatAgent`](langroid/agent/special/sql_chat_agent.py) -- thanks to our latest contributor [Rithwik Babu](https://github.com/rithwikbabu)!
119
121
  - Multi-agent Example: [Autocorrect chat](examples/basic/autocorrect.py)
@@ -230,7 +232,8 @@ export OPENAI_API_KEY=your-key-here-without-quotes
230
232
  <details>
231
233
  <summary><b>Optional Setup Instructions (click to expand) </b></summary>
232
234
 
233
- All of the below are optional and not strictly needed to run any of the examples.
235
+ All of the following environment variable settings are optional, and some are only needed
236
+ to use specific features (as noted below).
234
237
 
235
238
  - **Qdrant** Vector Store API Key, URL. This is only required if you want to use Qdrant cloud.
236
239
  You can sign up for a free 1GB account at [Qdrant cloud](https://cloud.qdrant.io).
@@ -259,8 +262,9 @@ All of the below are optional and not strictly needed to run any of the examples
259
262
  After obtaining these credentials, store them as values of
260
263
  `GOOGLE_API_KEY` and `GOOGLE_CSE_ID` in your `.env` file.
261
264
  Full documentation on using this (and other such "stateless" tools) is coming soon, but
262
- in the meantime take a peek at the test
263
- [`tests/main/test_google_search_tool.py`](tests/main/test_google_search_tool.py) to see how to use it.
265
+ in the meantime take a peek at this [chat example](examples/basic/chat-search.py), which
266
+ shows how you can easily equip an Agent with a `GoogleSearchtool`.
267
+
264
268
 
265
269
 
266
270
  If you add all of these optional variables, your `.env` file should look like this:
@@ -354,8 +358,6 @@ task.run() # ... a loop seeking response from LLM or User at each turn
354
358
  <details>
355
359
  <summary><b> Three communicating agents </b></summary>
356
360
 
357
- ```python
358
-
359
361
  A toy numbers game, where when given a number `n`:
360
362
  - `repeater_agent`'s LLM simply returns `n`,
361
363
  - `even_agent`'s LLM returns `n/2` if `n` is even, else says "DO-NOT-KNOW"
@@ -662,11 +664,11 @@ script in the `langroid-examples` repo.
662
664
 
663
665
  If you like this repo, don't forget to leave a star :star: !
664
666
 
665
- # Contributors
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
+
666
672
 
667
- - Prasad Chalasani (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)
668
- - Somesh Jha (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)
669
- - Mohannad Alhanahnah (Research Associate, U Wisc at Madison)
670
- - Ashish Hooda (IIT BTech/CS; PhD Candidate, U Wisc at Madison)
671
673
 
672
674
 
@@ -1,17 +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=TpCp7I1480RcCLzHJ4g1TUS2wH3kz5o2KzElFPehuBc,22466
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=NhoS02rlLr3nAfRvLB1YNcmfsWZJH24K8O-m-uYnb-I,15741
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=dkG_-QdL5KvpeS4K4jnx1kV9h_KxVEhgHz2rzdilZss,7196
12
- langroid/agent/special/sql_chat_agent.py,sha256=DNbz60O1HvGgWpsgYgCKE4GU7FqBoUeyZxfTbBHQ7gg,10178
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
- langroid/agent/stateless_tools/google_search_tool.py,sha256=riNFJEMNVdJuy4hSUUaH8Iz1hJqI_rGyrdcT6XXp_Sc,2770
14
+ langroid/agent/stateless_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ langroid/agent/stateless_tools/google_search_tool.py,sha256=64F9oMNdS237BBOitrvYXN4Il_ES_fNrHkh35tBEDfA,1160
15
16
  langroid/agent/task.py,sha256=KJZt6lSfCAVctyFfBJWLFcfys73kjrQJV4Y3aMVwO1M,26233
16
17
  langroid/agent/tool_message.py,sha256=7OdVcV7UyOZD2ihYgV1C_1fIwiWM-2pR8FFxoA1IgOo,5379
17
18
  langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -42,6 +43,7 @@ langroid/parsing/url_loader.py,sha256=tdYPMC2V9zI_B207LFdU4aroZzJW4sY9y794XePFAV
42
43
  langroid/parsing/url_loader_cookies.py,sha256=Lg4sNpRz9MByWq2mde6T0hKv68VZSV3mtMjNEHuFeSU,2327
43
44
  langroid/parsing/urls.py,sha256=_Bcf1iRdT7cQrQ8hnbPX0Jtzxc0lVFaucTS5rJoKA14,3709
44
45
  langroid/parsing/utils.py,sha256=__1Z6mFHk_TqHQY-9uU1aV_bIXeYw8H7NEXagUOEX0I,1818
46
+ langroid/parsing/web_search.py,sha256=hGUVoSJNdpoT5rsm-ikAteMiUropHrzKaxN8EVVqO2U,2496
45
47
  langroid/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
48
  langroid/prompts/dialog.py,sha256=SpfiSyofSgy2pwD1YboHR_yHO3LEEMbv6j2sm874jKo,331
47
49
  langroid/prompts/prompts_config.py,sha256=EMK1Fm7EmS8y3CV4AkrVgn5K4NipiM4m7J8819W1KeM,98
@@ -66,7 +68,7 @@ langroid/vector_store/base.py,sha256=QZx3NUNwf2I0r3A7iuoUHIRGbqt_pFGD0hq1R-Yg8iM
66
68
  langroid/vector_store/chromadb.py,sha256=s5pQkKjaMP-Tt5A8M10EInFzttaALPbJAq7q4gf0TKg,5235
67
69
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
68
70
  langroid/vector_store/qdrantdb.py,sha256=KRvIIj1IZG2zFqejofMnRs2hT86B-27LgBEnuczdqOU,9072
69
- langroid-0.1.38.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
70
- langroid-0.1.38.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
71
- langroid-0.1.38.dist-info/METADATA,sha256=xtuh9ZekUsUvWV5q4mGAbJNBEY7nVxQi3OzxFazoEto,27233
72
- langroid-0.1.38.dist-info/RECORD,,
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,,