langroid 0.33.4__py3-none-any.whl → 0.33.7__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 (129) hide show
  1. langroid/__init__.py +106 -0
  2. langroid/agent/__init__.py +41 -0
  3. langroid/agent/base.py +1983 -0
  4. langroid/agent/batch.py +398 -0
  5. langroid/agent/callbacks/__init__.py +0 -0
  6. langroid/agent/callbacks/chainlit.py +598 -0
  7. langroid/agent/chat_agent.py +1899 -0
  8. langroid/agent/chat_document.py +454 -0
  9. langroid/agent/openai_assistant.py +882 -0
  10. langroid/agent/special/__init__.py +59 -0
  11. langroid/agent/special/arangodb/__init__.py +0 -0
  12. langroid/agent/special/arangodb/arangodb_agent.py +656 -0
  13. langroid/agent/special/arangodb/system_messages.py +186 -0
  14. langroid/agent/special/arangodb/tools.py +107 -0
  15. langroid/agent/special/arangodb/utils.py +36 -0
  16. langroid/agent/special/doc_chat_agent.py +1466 -0
  17. langroid/agent/special/lance_doc_chat_agent.py +262 -0
  18. langroid/agent/special/lance_rag/__init__.py +9 -0
  19. langroid/agent/special/lance_rag/critic_agent.py +198 -0
  20. langroid/agent/special/lance_rag/lance_rag_task.py +82 -0
  21. langroid/agent/special/lance_rag/query_planner_agent.py +260 -0
  22. langroid/agent/special/lance_tools.py +61 -0
  23. langroid/agent/special/neo4j/__init__.py +0 -0
  24. langroid/agent/special/neo4j/csv_kg_chat.py +174 -0
  25. langroid/agent/special/neo4j/neo4j_chat_agent.py +433 -0
  26. langroid/agent/special/neo4j/system_messages.py +120 -0
  27. langroid/agent/special/neo4j/tools.py +32 -0
  28. langroid/agent/special/relevance_extractor_agent.py +127 -0
  29. langroid/agent/special/retriever_agent.py +56 -0
  30. langroid/agent/special/sql/__init__.py +17 -0
  31. langroid/agent/special/sql/sql_chat_agent.py +654 -0
  32. langroid/agent/special/sql/utils/__init__.py +21 -0
  33. langroid/agent/special/sql/utils/description_extractors.py +190 -0
  34. langroid/agent/special/sql/utils/populate_metadata.py +85 -0
  35. langroid/agent/special/sql/utils/system_message.py +35 -0
  36. langroid/agent/special/sql/utils/tools.py +64 -0
  37. langroid/agent/special/table_chat_agent.py +263 -0
  38. langroid/agent/task.py +2095 -0
  39. langroid/agent/tool_message.py +393 -0
  40. langroid/agent/tools/__init__.py +38 -0
  41. langroid/agent/tools/duckduckgo_search_tool.py +50 -0
  42. langroid/agent/tools/file_tools.py +234 -0
  43. langroid/agent/tools/google_search_tool.py +39 -0
  44. langroid/agent/tools/metaphor_search_tool.py +68 -0
  45. langroid/agent/tools/orchestration.py +303 -0
  46. langroid/agent/tools/recipient_tool.py +235 -0
  47. langroid/agent/tools/retrieval_tool.py +32 -0
  48. langroid/agent/tools/rewind_tool.py +137 -0
  49. langroid/agent/tools/segment_extract_tool.py +41 -0
  50. langroid/agent/xml_tool_message.py +382 -0
  51. langroid/cachedb/__init__.py +17 -0
  52. langroid/cachedb/base.py +58 -0
  53. langroid/cachedb/momento_cachedb.py +108 -0
  54. langroid/cachedb/redis_cachedb.py +153 -0
  55. langroid/embedding_models/__init__.py +39 -0
  56. langroid/embedding_models/base.py +74 -0
  57. langroid/embedding_models/models.py +461 -0
  58. langroid/embedding_models/protoc/__init__.py +0 -0
  59. langroid/embedding_models/protoc/embeddings.proto +19 -0
  60. langroid/embedding_models/protoc/embeddings_pb2.py +33 -0
  61. langroid/embedding_models/protoc/embeddings_pb2.pyi +50 -0
  62. langroid/embedding_models/protoc/embeddings_pb2_grpc.py +79 -0
  63. langroid/embedding_models/remote_embeds.py +153 -0
  64. langroid/exceptions.py +71 -0
  65. langroid/language_models/__init__.py +53 -0
  66. langroid/language_models/azure_openai.py +153 -0
  67. langroid/language_models/base.py +678 -0
  68. langroid/language_models/config.py +18 -0
  69. langroid/language_models/mock_lm.py +124 -0
  70. langroid/language_models/openai_gpt.py +1964 -0
  71. langroid/language_models/prompt_formatter/__init__.py +16 -0
  72. langroid/language_models/prompt_formatter/base.py +40 -0
  73. langroid/language_models/prompt_formatter/hf_formatter.py +132 -0
  74. langroid/language_models/prompt_formatter/llama2_formatter.py +75 -0
  75. langroid/language_models/utils.py +151 -0
  76. langroid/mytypes.py +84 -0
  77. langroid/parsing/__init__.py +52 -0
  78. langroid/parsing/agent_chats.py +38 -0
  79. langroid/parsing/code_parser.py +121 -0
  80. langroid/parsing/document_parser.py +718 -0
  81. langroid/parsing/para_sentence_split.py +62 -0
  82. langroid/parsing/parse_json.py +155 -0
  83. langroid/parsing/parser.py +313 -0
  84. langroid/parsing/repo_loader.py +790 -0
  85. langroid/parsing/routing.py +36 -0
  86. langroid/parsing/search.py +275 -0
  87. langroid/parsing/spider.py +102 -0
  88. langroid/parsing/table_loader.py +94 -0
  89. langroid/parsing/url_loader.py +111 -0
  90. langroid/parsing/urls.py +273 -0
  91. langroid/parsing/utils.py +373 -0
  92. langroid/parsing/web_search.py +156 -0
  93. langroid/prompts/__init__.py +9 -0
  94. langroid/prompts/dialog.py +17 -0
  95. langroid/prompts/prompts_config.py +5 -0
  96. langroid/prompts/templates.py +141 -0
  97. langroid/pydantic_v1/__init__.py +10 -0
  98. langroid/pydantic_v1/main.py +4 -0
  99. langroid/utils/__init__.py +19 -0
  100. langroid/utils/algorithms/__init__.py +3 -0
  101. langroid/utils/algorithms/graph.py +103 -0
  102. langroid/utils/configuration.py +98 -0
  103. langroid/utils/constants.py +30 -0
  104. langroid/utils/git_utils.py +252 -0
  105. langroid/utils/globals.py +49 -0
  106. langroid/utils/logging.py +135 -0
  107. langroid/utils/object_registry.py +66 -0
  108. langroid/utils/output/__init__.py +20 -0
  109. langroid/utils/output/citations.py +41 -0
  110. langroid/utils/output/printing.py +99 -0
  111. langroid/utils/output/status.py +40 -0
  112. langroid/utils/pandas_utils.py +30 -0
  113. langroid/utils/pydantic_utils.py +602 -0
  114. langroid/utils/system.py +286 -0
  115. langroid/utils/types.py +93 -0
  116. langroid/vector_store/__init__.py +50 -0
  117. langroid/vector_store/base.py +359 -0
  118. langroid/vector_store/chromadb.py +214 -0
  119. langroid/vector_store/lancedb.py +406 -0
  120. langroid/vector_store/meilisearch.py +299 -0
  121. langroid/vector_store/momento.py +278 -0
  122. langroid/vector_store/qdrantdb.py +468 -0
  123. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/METADATA +95 -94
  124. langroid-0.33.7.dist-info/RECORD +127 -0
  125. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/WHEEL +1 -1
  126. langroid-0.33.4.dist-info/RECORD +0 -7
  127. langroid-0.33.4.dist-info/entry_points.txt +0 -4
  128. pyproject.toml +0 -356
  129. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,286 @@
1
+ import difflib
2
+ import getpass
3
+ import hashlib
4
+ import importlib
5
+ import importlib.metadata
6
+ import inspect
7
+ import logging
8
+ import shutil
9
+ import socket
10
+ import traceback
11
+ import uuid
12
+ from pathlib import Path
13
+ from typing import Any, Literal
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ DELETION_ALLOWED_PATHS = [
18
+ ".qdrant",
19
+ ".chroma",
20
+ ".lancedb",
21
+ ]
22
+
23
+
24
+ def pydantic_major_version() -> int:
25
+ try:
26
+ pydantic_version = importlib.metadata.version("pydantic")
27
+ major_version = int(pydantic_version.split(".")[0])
28
+ return major_version
29
+ except importlib.metadata.PackageNotFoundError:
30
+ return -1
31
+
32
+
33
+ class LazyLoad:
34
+ """Lazy loading of modules or classes."""
35
+
36
+ def __init__(self, import_path: str) -> None:
37
+ self.import_path = import_path
38
+ self._target = None
39
+ self._is_target_loaded = False
40
+
41
+ def _load_target(self) -> None:
42
+ if not self._is_target_loaded:
43
+ try:
44
+ # Attempt to import as a module
45
+ self._target = importlib.import_module(self.import_path) # type: ignore
46
+ except ImportError:
47
+ # If module import fails, attempt to import as a
48
+ # class or function from a module
49
+ module_path, attr_name = self.import_path.rsplit(".", 1)
50
+ module = importlib.import_module(module_path)
51
+ self._target = getattr(module, attr_name)
52
+ self._is_target_loaded = True
53
+
54
+ def __getattr__(self, name: str) -> Any:
55
+ self._load_target()
56
+ return getattr(self._target, name)
57
+
58
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
59
+ self._load_target()
60
+ if callable(self._target):
61
+ return self._target(*args, **kwargs)
62
+ else:
63
+ raise TypeError(f"{self.import_path!r} object is not callable")
64
+
65
+
66
+ def rmdir(path: str) -> bool:
67
+ """
68
+ Remove a directory recursively.
69
+ Args:
70
+ path (str): path to directory to remove
71
+ Returns:
72
+ True if a dir was removed, false otherwise. Raises error if failed to remove.
73
+ """
74
+ if not any([path.startswith(p) for p in DELETION_ALLOWED_PATHS]):
75
+ raise ValueError(
76
+ f"""
77
+ Removing Dir '{path}' not allowed.
78
+ Must start with one of {DELETION_ALLOWED_PATHS}
79
+ This is a safety measure to prevent accidental deletion of files.
80
+ If you are sure you want to delete this directory, please add it
81
+ to the `DELETION_ALLOWED_PATHS` list in langroid/utils/system.py and
82
+ re-run the command.
83
+ """
84
+ )
85
+
86
+ try:
87
+ shutil.rmtree(path)
88
+ except FileNotFoundError:
89
+ logger.warning(f"Directory '{path}' does not exist. No action taken.")
90
+ return False
91
+ except Exception as e:
92
+ logger.error(f"Error while removing directory '{path}': {e}")
93
+ return True
94
+
95
+
96
+ def caller_name() -> str:
97
+ """
98
+ Who called the function?
99
+ """
100
+ frame = inspect.currentframe()
101
+ if frame is None:
102
+ return ""
103
+
104
+ caller_frame = frame.f_back
105
+
106
+ # If there's no caller frame, the function was called from the global scope
107
+ if caller_frame is None:
108
+ return ""
109
+
110
+ return caller_frame.f_code.co_name
111
+
112
+
113
+ def friendly_error(e: Exception, msg: str = "An error occurred.") -> str:
114
+ tb = traceback.format_exc()
115
+ original_error_message: str = str(e)
116
+ full_error_message: str = (
117
+ f"{msg}\nOriginal error: {original_error_message}\nTraceback:\n{tb}"
118
+ )
119
+ return full_error_message
120
+
121
+
122
+ def generate_user_id(org: str = "") -> str:
123
+ """
124
+ Generate a unique user ID based on the username and machine name.
125
+ Returns:
126
+ """
127
+ # Get the username
128
+ username = getpass.getuser()
129
+
130
+ # Get the machine's name
131
+ machine_name = socket.gethostname()
132
+
133
+ org_pfx = f"{org}_" if org else ""
134
+
135
+ # Create a consistent unique ID based on the username and machine name
136
+ unique_string = f"{org_pfx}{username}@{machine_name}"
137
+
138
+ # Generate a SHA-256 hash of the unique string
139
+ user_id = hashlib.sha256(unique_string.encode()).hexdigest()
140
+
141
+ return user_id
142
+
143
+
144
+ def update_hash(hash: str | None = None, s: str = "") -> str:
145
+ """
146
+ Takes a SHA256 hash string and a new string, updates the hash with the new string,
147
+ and returns the updated hash string.
148
+
149
+ Args:
150
+ hash (str): A SHA256 hash string.
151
+ s (str): A new string to update the hash with.
152
+
153
+ Returns:
154
+ The updated hash in hexadecimal format.
155
+ """
156
+ # Create a new hash object if no hash is provided
157
+ if hash is None:
158
+ hash_obj = hashlib.sha256()
159
+ else:
160
+ # Convert the hexadecimal hash string to a byte object
161
+ hash_bytes = bytes.fromhex(hash)
162
+ hash_obj = hashlib.sha256(hash_bytes)
163
+
164
+ # Update the hash with the new string
165
+ hash_obj.update(s.encode("utf-8"))
166
+
167
+ # Return the updated hash in hexadecimal format and the original string
168
+ return hash_obj.hexdigest()
169
+
170
+
171
+ def hash(s: str) -> str:
172
+ """
173
+ Generate a SHA256 hash of a string.
174
+
175
+ Args:
176
+ s (str): The string to hash.
177
+
178
+ Returns:
179
+ str: The SHA256 hash of the string.
180
+ """
181
+ return update_hash(s=s)
182
+
183
+
184
+ def generate_unique_id() -> str:
185
+ """Generate a unique ID using UUID4."""
186
+ return str(uuid.uuid4())
187
+
188
+
189
+ def create_file(
190
+ filepath: str | Path,
191
+ content: str = "",
192
+ if_exists: Literal["overwrite", "skip", "error", "append"] = "overwrite",
193
+ ) -> None:
194
+ """
195
+ Create, overwrite or append to a file, with the given content
196
+ at the specified filepath.
197
+ If content is empty, it will simply touch to create an empty file.
198
+
199
+ Args:
200
+ filepath (str|Path): The relative path of the file to be created
201
+ content (str): The content to be written to the file
202
+ if_exists (Literal["overwrite", "skip", "error", "append"]):
203
+ Action to take if file exists
204
+ """
205
+ filepath = Path(filepath)
206
+ filepath.parent.mkdir(parents=True, exist_ok=True)
207
+
208
+ if filepath.exists():
209
+ if if_exists == "skip":
210
+ logger.warning(f"File already exists, skipping: {filepath}")
211
+ return
212
+ elif if_exists == "error":
213
+ raise FileExistsError(f"File already exists: {filepath}")
214
+ elif if_exists == "append":
215
+ mode = "a"
216
+ else: # overwrite
217
+ mode = "w"
218
+ else:
219
+ mode = "w"
220
+
221
+ if content == "" and mode in ["a", "w"]:
222
+ filepath.touch()
223
+ logger.warning(f"Empty file created: {filepath}")
224
+ else:
225
+ # the newline = '\n` argument is used to ensure that
226
+ # newlines in the content are written as actual line breaks
227
+ with open(filepath, mode, newline="\n") as f:
228
+ f.write(content)
229
+ action = "appended to" if mode == "a" else "created/updated in"
230
+ logger.warning(f"Content {action}: {filepath}")
231
+
232
+
233
+ def read_file(path: str, line_numbers: bool = False) -> str:
234
+ """
235
+ Read the contents of a file.
236
+
237
+ Args:
238
+ path (str): The path to the file to be read.
239
+ line_numbers (bool, optional): If True, prepend line numbers to each line.
240
+ Defaults to False.
241
+
242
+ Returns:
243
+ str: The contents of the file, optionally with line numbers.
244
+
245
+ Raises:
246
+ FileNotFoundError: If the specified file does not exist.
247
+ """
248
+ # raise an error if the file does not exist
249
+ if not Path(path).exists():
250
+ raise FileNotFoundError(f"File not found: {path}")
251
+ file = Path(path).expanduser()
252
+ content = file.read_text()
253
+ if line_numbers:
254
+ lines = content.splitlines()
255
+ numbered_lines = [f"{i+1}: {line}" for i, line in enumerate(lines)]
256
+ return "\n".join(numbered_lines)
257
+ return content
258
+
259
+
260
+ def diff_files(file1: str, file2: str) -> str:
261
+ """
262
+ Find the diffs between two files, in unified diff format.
263
+ """
264
+ with open(file1, "r") as f1, open(file2, "r") as f2:
265
+ lines1 = f1.readlines()
266
+ lines2 = f2.readlines()
267
+
268
+ differ = difflib.unified_diff(lines1, lines2, fromfile=file1, tofile=file2)
269
+ diff_result = "".join(differ)
270
+ return diff_result
271
+
272
+
273
+ def list_dir(path: str | Path) -> list[str]:
274
+ """
275
+ List the contents of a directory.
276
+
277
+ Args:
278
+ path (str): The path to the directory.
279
+
280
+ Returns:
281
+ list[str]: A list of the files and directories in the specified directory.
282
+ """
283
+ dir_path = Path(path)
284
+ if not dir_path.is_dir():
285
+ raise NotADirectoryError(f"Path is not a directory: {dir_path}")
286
+ return [str(p) for p in dir_path.iterdir()]
@@ -0,0 +1,93 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Optional, Type, TypeVar, Union, get_args, get_origin
4
+
5
+ from langroid.pydantic_v1 import BaseModel
6
+
7
+ logger = logging.getLogger(__name__)
8
+ PrimitiveType = Union[int, float, bool, str]
9
+ T = TypeVar("T")
10
+
11
+
12
+ def is_instance_of(obj: Any, type_hint: Type[T] | Any) -> bool:
13
+ """
14
+ Check if an object is an instance of a type hint, e.g.
15
+ to check whether x is of type `List[ToolMessage]` or type `int`
16
+ """
17
+ if type_hint == Any:
18
+ return True
19
+
20
+ if type_hint is type(obj):
21
+ return True
22
+
23
+ origin = get_origin(type_hint)
24
+ args = get_args(type_hint)
25
+
26
+ if origin is Union:
27
+ return any(is_instance_of(obj, arg) for arg in args)
28
+
29
+ if origin: # e.g. List, Dict, Tuple, Set
30
+ if isinstance(obj, origin):
31
+ # check if all items in obj are of the required types
32
+ if args:
33
+ if isinstance(obj, (list, tuple, set)):
34
+ return all(is_instance_of(item, args[0]) for item in obj)
35
+ if isinstance(obj, dict):
36
+ return all(
37
+ is_instance_of(k, args[0]) and is_instance_of(v, args[1])
38
+ for k, v in obj.items()
39
+ )
40
+ return True
41
+ else:
42
+ return False
43
+
44
+ return isinstance(obj, type_hint)
45
+
46
+
47
+ def to_string(msg: Any) -> str:
48
+ """
49
+ Best-effort conversion of arbitrary msg to str.
50
+ Return empty string if conversion fails.
51
+ """
52
+ if msg is None:
53
+ return ""
54
+ if isinstance(msg, str):
55
+ return msg
56
+ if isinstance(msg, BaseModel):
57
+ return msg.json()
58
+ # last resort: use json.dumps() or str() to make it a str
59
+ try:
60
+ return json.dumps(msg)
61
+ except Exception:
62
+ try:
63
+ return str(msg)
64
+ except Exception as e:
65
+ logger.error(
66
+ f"""
67
+ Error converting msg to str: {e}",
68
+ """,
69
+ exc_info=True,
70
+ )
71
+ return ""
72
+
73
+
74
+ def from_string(
75
+ s: str,
76
+ output_type: Type[PrimitiveType],
77
+ ) -> Optional[PrimitiveType]:
78
+ if output_type is int:
79
+ try:
80
+ return int(s)
81
+ except ValueError:
82
+ return None
83
+ elif output_type is float:
84
+ try:
85
+ return float(s)
86
+ except ValueError:
87
+ return None
88
+ elif output_type is bool:
89
+ return s.lower() in ("true", "yes", "1")
90
+ elif output_type is str:
91
+ return s
92
+ else:
93
+ return None
@@ -0,0 +1,50 @@
1
+ from . import base
2
+
3
+ from . import qdrantdb
4
+
5
+ from .base import VectorStoreConfig, VectorStore
6
+ from .qdrantdb import QdrantDBConfig, QdrantDB
7
+
8
+ __all__ = [
9
+ "base",
10
+ "VectorStore",
11
+ "VectorStoreConfig",
12
+ "qdrantdb",
13
+ "QdrantDBConfig",
14
+ "QdrantDB",
15
+ ]
16
+
17
+
18
+ try:
19
+ from . import meilisearch
20
+ from .meilisearch import MeiliSearch, MeiliSearchConfig
21
+
22
+ meilisearch
23
+ MeiliSearch
24
+ MeiliSearchConfig
25
+ __all__.extend(["meilisearch", "MeiliSearch", "MeiliSearchConfig"])
26
+ except ImportError:
27
+ pass
28
+
29
+
30
+ try:
31
+ from . import lancedb
32
+ from .lancedb import LanceDB, LanceDBConfig
33
+
34
+ lancedb
35
+ LanceDB
36
+ LanceDBConfig
37
+ __all__.extend(["lancedb", "LanceDB", "LanceDBConfig"])
38
+ except ImportError:
39
+ pass
40
+
41
+ try:
42
+ from . import chromadb
43
+ from .chromadb import ChromaDBConfig, ChromaDB
44
+
45
+ chromadb # silence linters
46
+ ChromaDB
47
+ ChromaDBConfig
48
+ __all__.extend(["chromadb", "ChromaDBConfig", "ChromaDB"])
49
+ except ImportError:
50
+ pass