langroid 0.1.85__py3-none-any.whl → 0.1.219__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.
Files changed (107) hide show
  1. langroid/__init__.py +95 -0
  2. langroid/agent/__init__.py +40 -0
  3. langroid/agent/base.py +222 -91
  4. langroid/agent/batch.py +264 -0
  5. langroid/agent/callbacks/chainlit.py +608 -0
  6. langroid/agent/chat_agent.py +247 -101
  7. langroid/agent/chat_document.py +41 -4
  8. langroid/agent/openai_assistant.py +842 -0
  9. langroid/agent/special/__init__.py +50 -0
  10. langroid/agent/special/doc_chat_agent.py +837 -141
  11. langroid/agent/special/lance_doc_chat_agent.py +258 -0
  12. langroid/agent/special/lance_rag/__init__.py +9 -0
  13. langroid/agent/special/lance_rag/critic_agent.py +136 -0
  14. langroid/agent/special/lance_rag/lance_rag_task.py +80 -0
  15. langroid/agent/special/lance_rag/query_planner_agent.py +180 -0
  16. langroid/agent/special/lance_tools.py +44 -0
  17. langroid/agent/special/neo4j/__init__.py +0 -0
  18. langroid/agent/special/neo4j/csv_kg_chat.py +174 -0
  19. langroid/agent/special/neo4j/neo4j_chat_agent.py +370 -0
  20. langroid/agent/special/neo4j/utils/__init__.py +0 -0
  21. langroid/agent/special/neo4j/utils/system_message.py +46 -0
  22. langroid/agent/special/relevance_extractor_agent.py +127 -0
  23. langroid/agent/special/retriever_agent.py +32 -198
  24. langroid/agent/special/sql/__init__.py +11 -0
  25. langroid/agent/special/sql/sql_chat_agent.py +47 -23
  26. langroid/agent/special/sql/utils/__init__.py +22 -0
  27. langroid/agent/special/sql/utils/description_extractors.py +95 -46
  28. langroid/agent/special/sql/utils/populate_metadata.py +28 -21
  29. langroid/agent/special/table_chat_agent.py +43 -9
  30. langroid/agent/task.py +475 -122
  31. langroid/agent/tool_message.py +75 -13
  32. langroid/agent/tools/__init__.py +13 -0
  33. langroid/agent/tools/duckduckgo_search_tool.py +66 -0
  34. langroid/agent/tools/google_search_tool.py +11 -0
  35. langroid/agent/tools/metaphor_search_tool.py +67 -0
  36. langroid/agent/tools/recipient_tool.py +16 -29
  37. langroid/agent/tools/run_python_code.py +60 -0
  38. langroid/agent/tools/sciphi_search_rag_tool.py +79 -0
  39. langroid/agent/tools/segment_extract_tool.py +36 -0
  40. langroid/cachedb/__init__.py +9 -0
  41. langroid/cachedb/base.py +22 -2
  42. langroid/cachedb/momento_cachedb.py +26 -2
  43. langroid/cachedb/redis_cachedb.py +78 -11
  44. langroid/embedding_models/__init__.py +34 -0
  45. langroid/embedding_models/base.py +21 -2
  46. langroid/embedding_models/models.py +120 -18
  47. langroid/embedding_models/protoc/embeddings.proto +19 -0
  48. langroid/embedding_models/protoc/embeddings_pb2.py +33 -0
  49. langroid/embedding_models/protoc/embeddings_pb2.pyi +50 -0
  50. langroid/embedding_models/protoc/embeddings_pb2_grpc.py +79 -0
  51. langroid/embedding_models/remote_embeds.py +153 -0
  52. langroid/language_models/__init__.py +45 -0
  53. langroid/language_models/azure_openai.py +80 -27
  54. langroid/language_models/base.py +117 -12
  55. langroid/language_models/config.py +5 -0
  56. langroid/language_models/openai_assistants.py +3 -0
  57. langroid/language_models/openai_gpt.py +558 -174
  58. langroid/language_models/prompt_formatter/__init__.py +15 -0
  59. langroid/language_models/prompt_formatter/base.py +4 -6
  60. langroid/language_models/prompt_formatter/hf_formatter.py +135 -0
  61. langroid/language_models/utils.py +18 -21
  62. langroid/mytypes.py +25 -8
  63. langroid/parsing/__init__.py +46 -0
  64. langroid/parsing/document_parser.py +260 -63
  65. langroid/parsing/image_text.py +32 -0
  66. langroid/parsing/parse_json.py +143 -0
  67. langroid/parsing/parser.py +122 -59
  68. langroid/parsing/repo_loader.py +114 -52
  69. langroid/parsing/search.py +68 -63
  70. langroid/parsing/spider.py +3 -2
  71. langroid/parsing/table_loader.py +44 -0
  72. langroid/parsing/url_loader.py +59 -11
  73. langroid/parsing/urls.py +85 -37
  74. langroid/parsing/utils.py +298 -4
  75. langroid/parsing/web_search.py +73 -0
  76. langroid/prompts/__init__.py +11 -0
  77. langroid/prompts/chat-gpt4-system-prompt.md +68 -0
  78. langroid/prompts/prompts_config.py +1 -1
  79. langroid/utils/__init__.py +17 -0
  80. langroid/utils/algorithms/__init__.py +3 -0
  81. langroid/utils/algorithms/graph.py +103 -0
  82. langroid/utils/configuration.py +36 -5
  83. langroid/utils/constants.py +4 -0
  84. langroid/utils/globals.py +2 -2
  85. langroid/utils/logging.py +2 -5
  86. langroid/utils/output/__init__.py +21 -0
  87. langroid/utils/output/printing.py +47 -1
  88. langroid/utils/output/status.py +33 -0
  89. langroid/utils/pandas_utils.py +30 -0
  90. langroid/utils/pydantic_utils.py +616 -2
  91. langroid/utils/system.py +98 -0
  92. langroid/vector_store/__init__.py +40 -0
  93. langroid/vector_store/base.py +203 -6
  94. langroid/vector_store/chromadb.py +59 -32
  95. langroid/vector_store/lancedb.py +463 -0
  96. langroid/vector_store/meilisearch.py +10 -7
  97. langroid/vector_store/momento.py +262 -0
  98. langroid/vector_store/qdrantdb.py +104 -22
  99. {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/METADATA +329 -149
  100. langroid-0.1.219.dist-info/RECORD +127 -0
  101. {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/WHEEL +1 -1
  102. langroid/agent/special/recipient_validator_agent.py +0 -157
  103. langroid/parsing/json.py +0 -64
  104. langroid/utils/web/selenium_login.py +0 -36
  105. langroid-0.1.85.dist-info/RECORD +0 -94
  106. /langroid/{scripts → agent/callbacks}/__init__.py +0 -0
  107. {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/LICENSE +0 -0
@@ -6,6 +6,8 @@ an agent. The messages could represent, for example:
6
6
  - request to run a method of the agent
7
7
  """
8
8
 
9
+ import json
10
+ import textwrap
9
11
  from abc import ABC
10
12
  from random import choice
11
13
  from typing import Any, Dict, List, Type
@@ -14,16 +16,10 @@ from docstring_parser import parse
14
16
  from pydantic import BaseModel
15
17
 
16
18
  from langroid.language_models.base import LLMFunctionSpec
17
-
18
-
19
- def _recursive_purge_dict_key(d: Dict[str, Any], k: str) -> None:
20
- """Remove a key from a dictionary recursively"""
21
- if isinstance(d, dict):
22
- for key in list(d.keys()):
23
- if key == k and "type" in d.keys():
24
- del d[key]
25
- else:
26
- _recursive_purge_dict_key(d[key], k)
19
+ from langroid.utils.pydantic_utils import (
20
+ _recursive_purge_dict_key,
21
+ generate_simple_schema,
22
+ )
27
23
 
28
24
 
29
25
  class ToolMessage(ABC, BaseModel):
@@ -45,7 +41,6 @@ class ToolMessage(ABC, BaseModel):
45
41
  request: str
46
42
  purpose: str
47
43
  result: str = ""
48
- recipient: str = "" # default is empty string, so it is optional
49
44
 
50
45
  class Config:
51
46
  arbitrary_types_allowed = False
@@ -87,6 +82,9 @@ class ToolMessage(ABC, BaseModel):
87
82
  ex = choice(cls.examples())
88
83
  return ex.json_example()
89
84
 
85
+ def to_json(self) -> str:
86
+ return self.json(indent=4, exclude={"result", "purpose"})
87
+
90
88
  def json_example(self) -> str:
91
89
  return self.json(indent=4, exclude={"result", "purpose"})
92
90
 
@@ -109,7 +107,58 @@ class ToolMessage(ABC, BaseModel):
109
107
  return properties.get(f, {}).get("default", None)
110
108
 
111
109
  @classmethod
112
- def llm_function_schema(cls, request: bool = False) -> LLMFunctionSpec:
110
+ def json_instructions(cls, tool: bool = False) -> str:
111
+ """
112
+ Default Instructions to the LLM showing how to use the tool/function-call.
113
+ Works for GPT4 but override this for weaker LLMs if needed.
114
+
115
+ Args:
116
+ tool: instructions for Langroid-native tool use? (e.g. for non-OpenAI LLM)
117
+ (or else it would be for OpenAI Function calls)
118
+ Returns:
119
+ str: instructions on how to use the message
120
+ """
121
+ # TODO: when we attempt to use a "simpler schema"
122
+ # (i.e. all nested fields explicit without definitions),
123
+ # we seem to get worse results, so we turn it off for now
124
+ param_dict = (
125
+ # cls.simple_schema() if tool else
126
+ cls.llm_function_schema(request=True).parameters
127
+ )
128
+ return textwrap.dedent(
129
+ f"""
130
+ TOOL: {cls.default_value("request")}
131
+ PURPOSE: {cls.default_value("purpose")}
132
+ JSON FORMAT: {
133
+ json.dumps(param_dict, indent=4)
134
+ }
135
+ {"EXAMPLE: " + cls.usage_example() if cls.examples() else ""}
136
+ """.lstrip()
137
+ )
138
+
139
+ @staticmethod
140
+ def json_group_instructions() -> str:
141
+ """Template for instructions for a group of tools.
142
+ Works with GPT4 but override this for weaker LLMs if needed.
143
+ """
144
+ return textwrap.dedent(
145
+ """
146
+ === ALL AVAILABLE TOOLS and THEIR JSON FORMAT INSTRUCTIONS ===
147
+ You have access to the following TOOLS to accomplish your task:
148
+
149
+ {json_instructions}
150
+
151
+ When one of the above TOOLs is applicable, you must express your
152
+ request as "TOOL:" followed by the request in the above JSON format.
153
+ """
154
+ )
155
+
156
+ @classmethod
157
+ def llm_function_schema(
158
+ cls,
159
+ request: bool = False,
160
+ defaults: bool = True,
161
+ ) -> LLMFunctionSpec:
113
162
  """
114
163
  Clean up the schema of the Pydantic class (which can recursively contain
115
164
  other Pydantic classes), to create a version compatible with OpenAI
@@ -122,6 +171,8 @@ class ToolMessage(ABC, BaseModel):
122
171
  request: whether to include the "request" field in the schema.
123
172
  (we set this to True when using Langroid-native TOOLs as opposed to
124
173
  OpenAI Function calls)
174
+ defaults: whether to include fields with default values in the schema,
175
+ in the "properties" section.
125
176
 
126
177
  Returns:
127
178
  LLMFunctionSpec: the schema as an LLMFunctionSpec
@@ -146,7 +197,7 @@ class ToolMessage(ABC, BaseModel):
146
197
  parameters["properties"] = {
147
198
  field: details
148
199
  for field, details in parameters["properties"].items()
149
- if field not in excludes
200
+ if field not in excludes and (defaults or details.get("default") is None)
150
201
  }
151
202
  parameters["required"] = sorted(
152
203
  k
@@ -173,3 +224,14 @@ class ToolMessage(ABC, BaseModel):
173
224
  description=cls.default_value("purpose"),
174
225
  parameters=parameters,
175
226
  )
227
+
228
+ @classmethod
229
+ def simple_schema(cls) -> Dict[str, Any]:
230
+ """
231
+ Return a simplified schema for the message, with only the request and
232
+ required fields.
233
+ Returns:
234
+ Dict[str, Any]: simplified schema
235
+ """
236
+ schema = generate_simple_schema(cls, exclude=["result", "purpose"])
237
+ return schema
@@ -0,0 +1,13 @@
1
+ from .google_search_tool import GoogleSearchTool
2
+ from .recipient_tool import AddRecipientTool, RecipientTool
3
+
4
+ from . import google_search_tool
5
+ from . import recipient_tool
6
+
7
+ __all__ = [
8
+ "GoogleSearchTool",
9
+ "AddRecipientTool",
10
+ "RecipientTool",
11
+ "google_search_tool",
12
+ "recipient_tool",
13
+ ]
@@ -0,0 +1,66 @@
1
+ """
2
+ A tool to trigger a Metaphor search for a given query,
3
+ (https://docs.exa.ai/reference/getting-started)
4
+ and return the top results with their titles, links, summaries.
5
+ Since the tool is stateless (i.e. does not need
6
+ access to agent state), it can be enabled for any agent, without having to define a
7
+ special method inside the agent: `agent.enable_message(MetaphorSearchTool)`
8
+
9
+ NOTE: To use this tool, you need to:
10
+
11
+ * set the METAPHOR_API_KEY environment variables in
12
+ your `.env` file, e.g. `METAPHOR_API_KEY=your_api_key_here`
13
+ (Note as of 28 Jan 2023, Metaphor renamed to Exa, so you can also use
14
+ `EXA_API_KEY=your_api_key_here`)
15
+
16
+ * install langroid with the `metaphor` extra, e.g.
17
+ `pip install langroid[metaphor]` or `poetry add langroid[metaphor]`
18
+ (it installs the `metaphor-python` package from pypi).
19
+
20
+ For more information, please refer to the official docs:
21
+ https://metaphor.systems/
22
+ """
23
+
24
+ from typing import List
25
+
26
+ from langroid.agent.tool_message import ToolMessage
27
+ from langroid.parsing.web_search import duckduckgo_search
28
+
29
+
30
+ class DuckduckgoSearchTool(ToolMessage):
31
+ request: str = "duckduckgo_search"
32
+ purpose: str = """
33
+ To search the web and return up to <num_results>
34
+ links relevant to the given <query>. When using this tool,
35
+ ONLY show the required JSON, DO NOT SAY ANYTHING ELSE.
36
+ Wait for the results of the web search, and then use them to
37
+ compose your response.
38
+ """
39
+ query: str
40
+ num_results: int
41
+
42
+ def handle(self) -> str:
43
+ """
44
+ Conducts a search using the metaphor API based on the provided query
45
+ and number of results by triggering a metaphor_search.
46
+
47
+ Returns:
48
+ str: A formatted string containing the titles, links, and
49
+ summaries of each search result, separated by two newlines.
50
+ """
51
+ search_results = duckduckgo_search(self.query, self.num_results)
52
+ # return Title, Link, Summary of each result, separated by two newlines
53
+ results_str = "\n\n".join(str(result) for result in search_results)
54
+ return f"""
55
+ BELOW ARE THE RESULTS FROM THE WEB SEARCH. USE THESE TO COMPOSE YOUR RESPONSE:
56
+ {results_str}
57
+ """
58
+
59
+ @classmethod
60
+ def examples(cls) -> List["ToolMessage"]:
61
+ return [
62
+ cls(
63
+ query="When was the Llama2 Large Language Model (LLM) released?",
64
+ num_results=3,
65
+ ),
66
+ ]
@@ -9,6 +9,8 @@ environment variables in your `.env` file, as explained in the
9
9
  [README](https://github.com/langroid/langroid#gear-installation-and-setup).
10
10
  """
11
11
 
12
+ from typing import List
13
+
12
14
  from langroid.agent.tool_message import ToolMessage
13
15
  from langroid.parsing.web_search import google_search
14
16
 
@@ -26,3 +28,12 @@ class GoogleSearchTool(ToolMessage):
26
28
  search_results = google_search(self.query, self.num_results)
27
29
  # return Title, Link, Summary of each result, separated by two newlines
28
30
  return "\n\n".join(str(result) for result in search_results)
31
+
32
+ @classmethod
33
+ def examples(cls) -> List["ToolMessage"]:
34
+ return [
35
+ cls(
36
+ query="When was the Llama2 Large Language Model (LLM) released?",
37
+ num_results=3,
38
+ ),
39
+ ]
@@ -0,0 +1,67 @@
1
+ """
2
+ A tool to trigger a Metaphor search for a given query,
3
+ (https://docs.exa.ai/reference/getting-started)
4
+ and return the top results with their titles, links, summaries.
5
+ Since the tool is stateless (i.e. does not need
6
+ access to agent state), it can be enabled for any agent, without having to define a
7
+ special method inside the agent: `agent.enable_message(MetaphorSearchTool)`
8
+
9
+ NOTE: To use this tool, you need to:
10
+
11
+ * set the METAPHOR_API_KEY environment variables in
12
+ your `.env` file, e.g. `METAPHOR_API_KEY=your_api_key_here`
13
+ (Note as of 28 Jan 2023, Metaphor renamed to Exa, so you can also use
14
+ `EXA_API_KEY=your_api_key_here`)
15
+
16
+ * install langroid with the `metaphor` extra, e.g.
17
+ `pip install langroid[metaphor]` or `poetry add langroid[metaphor]`
18
+ (it installs the `metaphor-python` package from pypi).
19
+
20
+ For more information, please refer to the official docs:
21
+ https://metaphor.systems/
22
+ """
23
+
24
+ from typing import List
25
+
26
+ from langroid.agent.tool_message import ToolMessage
27
+ from langroid.parsing.web_search import metaphor_search
28
+
29
+
30
+ class MetaphorSearchTool(ToolMessage):
31
+ request: str = "metaphor_search"
32
+ purpose: str = """
33
+ To search the web and return up to <num_results>
34
+ links relevant to the given <query>. When using this tool,
35
+ ONLY show the required JSON, DO NOT SAY ANYTHING ELSE.
36
+ Wait for the results of the web search, and then use them to
37
+ compose your response.
38
+ """
39
+ query: str
40
+ num_results: int
41
+
42
+ def handle(self) -> str:
43
+ """
44
+ Conducts a search using the metaphor API based on the provided query
45
+ and number of results by triggering a metaphor_search.
46
+
47
+ Returns:
48
+ str: A formatted string containing the titles, links, and
49
+ summaries of each search result, separated by two newlines.
50
+ """
51
+
52
+ search_results = metaphor_search(self.query, self.num_results)
53
+ # return Title, Link, Summary of each result, separated by two newlines
54
+ results_str = "\n\n".join(str(result) for result in search_results)
55
+ return f"""
56
+ BELOW ARE THE RESULTS FROM THE WEB SEARCH. USE THESE TO COMPOSE YOUR RESPONSE:
57
+ {results_str}
58
+ """
59
+
60
+ @classmethod
61
+ def examples(cls) -> List["ToolMessage"]:
62
+ return [
63
+ cls(
64
+ query="When was the Llama2 Large Language Model (LLM) released?",
65
+ num_results=3,
66
+ ),
67
+ ]
@@ -6,25 +6,8 @@ the method `_get_tool_list()`).
6
6
 
7
7
  See usage examples in `tests/main/test_multi_agent_complex.py` and
8
8
  `tests/main/test_recipient_tool.py`.
9
-
10
- Previously we were using RecipientValidatorAgent to enforce proper
11
- recipient specifiction, but the preferred method is to use the
12
- `RecipientTool` class. This has numerous advantages:
13
- - it uses the tool/function-call mechanism to specify a recipient in a JSON-structured
14
- string, which is more consistent with the rest of the system, and does not require
15
- inventing a new syntax like `TO:<recipient>` (which the RecipientValidatorAgent
16
- uses).
17
- - it removes the need for any special parsing of the message content, since we leverage
18
- the built-in JSON tool-matching in `Agent.handle_message()` and downstream code.
19
- - it does not require setting the `parent_responder` field in the `ChatDocument`
20
- metadata, which is somewhat hacky.
21
- - it appears to be less brittle than requiring the LLM to use TO:<recipient> syntax:
22
- The LLM almost never forgets to use the RecipientTool as instructed.
23
- - The RecipientTool class acts as a specification of the required syntax, and also
24
- contains mechanisms to enforce this syntax.
25
- - For a developer who needs to enforce recipient specification for an agent, they only
26
- need to do `agent.enable_message(RecipientTool)`, and the rest is taken care of.
27
9
  """
10
+
28
11
  from typing import List, Type
29
12
 
30
13
  from rich import print
@@ -62,20 +45,22 @@ class AddRecipientTool(ToolMessage):
62
45
  (ChatDocument): with content set to self.content and
63
46
  metadata.recipient set to self.recipient.
64
47
  """
65
- print(f"[red]RecipientTool: Added recipient {self.recipient} to message.")
48
+ print(
49
+ "[red]RecipientTool: "
50
+ f"Added recipient {self.intended_recipient} to message."
51
+ )
66
52
  if self.__class__.saved_content == "":
67
53
  recipient_request_name = RecipientTool.default_value("request")
68
- raise ValueError(
69
- f"""
54
+ content = f"""
70
55
  Recipient specified but content is empty!
71
56
  This could be because the `{self.request}` tool/function was used
72
57
  before using `{recipient_request_name}` tool/function.
73
58
  Resend the message using `{recipient_request_name}` tool/function.
74
59
  """
75
- )
76
- content = self.__class__.saved_content # use class-level attrib value
77
- # erase content since we just used it.
78
- self.__class__.saved_content = ""
60
+ else:
61
+ content = self.__class__.saved_content # use class-level attrib value
62
+ # erase content since we just used it.
63
+ self.__class__.saved_content = ""
79
64
  return ChatDocument(
80
65
  content=content,
81
66
  metadata=ChatDocMetaData(
@@ -91,9 +76,10 @@ class RecipientTool(ToolMessage):
91
76
  Used by LLM to send a message to a specific recipient.
92
77
 
93
78
  Useful in cases where an LLM is talking to 2 or more
94
- agents, and needs to specify which agent (task) its message is intended for.
95
- The recipient name should be the name of a task (which is normally the name of
96
- the agent that the task wraps, although the task can have its own name).
79
+ agents (or an Agent and human user), and needs to specify which agent (task)
80
+ its message is intended for. The recipient name should be the name of a task
81
+ (which is normally the name of the agent that the task wraps, although the task
82
+ can have its own name).
97
83
 
98
84
  To use this tool/function-call, LLM must generate a JSON structure
99
85
  with these fields:
@@ -102,6 +88,7 @@ class RecipientTool(ToolMessage):
102
88
  "intended_recipient": <name_of_recipient_task_or_entity>,
103
89
  "content": <content>
104
90
  }
91
+ The effect of this is that `content` will be sent to the `intended_recipient` task.
105
92
  """
106
93
 
107
94
  request: str = "recipient_message"
@@ -181,7 +168,7 @@ class RecipientTool(ToolMessage):
181
168
  content=self.content,
182
169
  metadata=ChatDocMetaData(
183
170
  recipient=self.intended_recipient,
184
- # we are constructing this so it looks as it msg is from LLM
171
+ # we are constructing this so it looks as if msg is from LLM
185
172
  sender=Entity.LLM,
186
173
  ),
187
174
  )
@@ -0,0 +1,60 @@
1
+ import io
2
+ import sys
3
+ from typing import List
4
+
5
+ from langroid.agent.tool_message import ToolMessage
6
+
7
+
8
+ class RunPythonCodeTool(ToolMessage):
9
+ """
10
+ Tool/function to run code generated by the LLM.
11
+ The code is assumed to be self-contained, i.e. it contains all necessary imports
12
+ and does not depend on any external variables.
13
+ """
14
+
15
+ request: str = "run_python_code"
16
+ purpose: str = """
17
+ To run python <code> and return the results to answer a question.
18
+ """
19
+ code: str
20
+
21
+ @classmethod
22
+ def examples(cls) -> List["ToolMessage"]:
23
+ return [
24
+ cls(code="import numpy as np\nnp.square(9)"),
25
+ ]
26
+
27
+ def handle(self) -> str:
28
+ """
29
+ Handle a RunPythonCodeTool message by running the code and returning the result.
30
+ Returns:
31
+ str: The result of running the code along with any print output.
32
+ """
33
+ code = self.code
34
+
35
+ # Create a string-based I/O stream
36
+ code_out = io.StringIO()
37
+
38
+ # Temporarily redirect standard output to our string-based I/O stream
39
+ sys.stdout = code_out
40
+
41
+ try:
42
+ eval_result = eval(code, {})
43
+ except Exception as e:
44
+ eval_result = f"ERROR: {type(e)}: {e}"
45
+
46
+ if eval_result is None:
47
+ eval_result = ""
48
+
49
+ # Always restore the original standard output
50
+ sys.stdout = sys.__stdout__
51
+
52
+ # Get the resulting string from the I/O stream
53
+ print_result = code_out.getvalue() or ""
54
+ sep = "\n" if print_result else ""
55
+ # Combine the print and eval results
56
+ result = f"{print_result}{sep}{eval_result}"
57
+ if result == "":
58
+ result = "No result"
59
+ # Return the result
60
+ return result
@@ -0,0 +1,79 @@
1
+ """
2
+ A tool which returns a Search RAG response from the SciPhi API.
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(SciPhiSearchRAGTool)`
6
+
7
+ Example return output appears as follows below:
8
+
9
+ <-- Query -->
10
+ ```
11
+ Find 3 results on the internet about the LK-99 superconducting material.
12
+ ``
13
+
14
+ <-- Response (compressed for this example)-->
15
+ ```
16
+ [ result1 ]
17
+
18
+ [ result2 ]
19
+
20
+ [ result3 ]
21
+
22
+ ```
23
+
24
+ NOTE: Using this tool requires getting an API key from sciphi.ai.
25
+ Setup is as simple as shown below
26
+ # Get a free API key at https://www.sciphi.ai/account
27
+ # export SCIPHI_API_KEY=$MY_SCIPHI_API_KEY before running the agent
28
+ # OR add SCIPHI_API_KEY=$MY_SCIPHI_API_KEY to your .env file
29
+
30
+ This tool requires installing langroid with the `sciphi` extra, e.g.
31
+ `pip install langroid[sciphi]` or `poetry add langroid[sciphi]`
32
+ (it installs the `agent-search` package from pypi).
33
+
34
+ For more information, please refer to the official docs:
35
+ https://agent-search.readthedocs.io/en/latest/
36
+ """
37
+
38
+ from typing import List
39
+
40
+ try:
41
+ from agent_search import SciPhi
42
+ except ImportError:
43
+ raise ImportError(
44
+ "You are attempting to use the `agent-search` library;"
45
+ "To use it, please install langroid with the `sciphi` extra, e.g. "
46
+ "`pip install langroid[sciphi]` or `poetry add langroid[sciphi]` "
47
+ "(it installs the `agent-search` package from pypi)."
48
+ )
49
+
50
+ from langroid.agent.tool_message import ToolMessage
51
+
52
+
53
+ class SciPhiSearchRAGTool(ToolMessage):
54
+ request: str = "web_search_rag"
55
+ purpose: str = """
56
+ To search the web with provider <search_provider> and
57
+ return a response summary with llm model <llm_model> the given <query>.
58
+ """
59
+ query: str
60
+
61
+ def handle(self) -> str:
62
+ rag_response = SciPhi().get_search_rag_response(
63
+ query=self.query, search_provider="bing", llm_model="SciPhi/Sensei-7B-V1"
64
+ )
65
+ result = rag_response["response"]
66
+ result = (
67
+ f"### RAG Response:\n{result}\n\n"
68
+ + "### Related Queries:\n"
69
+ + "\n".join(rag_response["related_queries"])
70
+ )
71
+ return result # type: ignore
72
+
73
+ @classmethod
74
+ def examples(cls) -> List["ToolMessage"]:
75
+ return [
76
+ cls(
77
+ query="When was the Llama2 Large Language Model (LLM) released?",
78
+ ),
79
+ ]
@@ -0,0 +1,36 @@
1
+ """
2
+ A tool to extract segment numbers from the last user message,
3
+ containing numbered segments.
4
+
5
+ The idea is that when an LLM wants to (or is asked to) simply extract
6
+ portions of a message verbatim, it should use this tool/function to
7
+ SPECIFY what should be extracted, rather than actually extracting it.
8
+ The output will be in the form of a list of segment numbers or ranges.
9
+ This will usually be much cheaper and faster than actually writing out the extracted
10
+ text. The handler of this tool/function will then extract the text and send it back.
11
+ """
12
+
13
+ from typing import List
14
+
15
+ from langroid.agent.tool_message import ToolMessage
16
+
17
+
18
+ class SegmentExtractTool(ToolMessage):
19
+ request: str = "extract_segments"
20
+ purpose: str = """
21
+ To extract segments from a body of text containing numbered
22
+ segments, in the form of a <segment_list> which is a list of segment
23
+ numbers or ranges, like "10,12,14-17".
24
+ """
25
+ segment_list: str
26
+
27
+ @classmethod
28
+ def examples(cls) -> List["ToolMessage"]:
29
+ return [cls(segment_list="1,3,5-7")]
30
+
31
+ @classmethod
32
+ def instructions(cls) -> str:
33
+ return """
34
+ Use this tool/function to indicate certain segments from
35
+ a body of text containing numbered segments.
36
+ """
@@ -0,0 +1,9 @@
1
+ from . import base
2
+ from . import momento_cachedb
3
+ from . import redis_cachedb
4
+
5
+ __all__ = [
6
+ "base",
7
+ "momento_cachedb",
8
+ "redis_cachedb",
9
+ ]
langroid/cachedb/base.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, Dict, Optional
2
+ from typing import Any, Dict, List
3
3
 
4
4
 
5
5
  class CacheDB(ABC):
@@ -17,7 +17,7 @@ class CacheDB(ABC):
17
17
  pass
18
18
 
19
19
  @abstractmethod
20
- def retrieve(self, key: str) -> Optional[Dict[str, Any]]:
20
+ def retrieve(self, key: str) -> Dict[str, Any] | str | None:
21
21
  """
22
22
  Abstract method to retrieve the value associated with a key.
23
23
 
@@ -28,3 +28,23 @@ class CacheDB(ABC):
28
28
  dict: The value associated with the key.
29
29
  """
30
30
  pass
31
+
32
+ @abstractmethod
33
+ def delete_keys(self, keys: List[str]) -> None:
34
+ """
35
+ Delete the keys from the cache.
36
+
37
+ Args:
38
+ keys (List[str]): The keys to delete.
39
+ """
40
+ pass
41
+
42
+ @abstractmethod
43
+ def delete_keys_pattern(self, pattern: str) -> None:
44
+ """
45
+ Delete all keys with the given pattern
46
+
47
+ Args:
48
+ prefix (str): The pattern to match.
49
+ """
50
+ pass
@@ -2,7 +2,7 @@ import json
2
2
  import logging
3
3
  import os
4
4
  from datetime import timedelta
5
- from typing import Any, Dict, Optional
5
+ from typing import Any, Dict, List
6
6
 
7
7
  import momento
8
8
  from dotenv import load_dotenv
@@ -61,7 +61,7 @@ class MomentoCache(CacheDB):
61
61
  """
62
62
  self.client.set(self.config.cachename, key, json.dumps(value))
63
63
 
64
- def retrieve(self, key: str) -> Optional[Dict[str, Any]]:
64
+ def retrieve(self, key: str) -> Dict[str, Any] | str | None:
65
65
  """
66
66
  Retrieve the value associated with a key.
67
67
 
@@ -76,3 +76,27 @@ class MomentoCache(CacheDB):
76
76
  return json.loads(value.value_string) # type: ignore
77
77
  else:
78
78
  return None
79
+
80
+ def delete_keys(self, keys: List[str]) -> None:
81
+ """
82
+ Delete the keys from the cache.
83
+
84
+ Args:
85
+ keys (List[str]): The keys to delete.
86
+ """
87
+ for key in keys:
88
+ self.client.delete(self.config.cachename, key)
89
+
90
+ def delete_keys_pattern(self, pattern: str) -> None:
91
+ """
92
+ Delete the keys from the cache with the given pattern.
93
+
94
+ Args:
95
+ prefix (str): The pattern to match.
96
+ """
97
+ raise NotImplementedError(
98
+ """
99
+ MomentoCache does not support delete_keys_pattern.
100
+ Please use RedisCache instead.
101
+ """
102
+ )