langroid 0.1.165__py3-none-any.whl → 0.1.167__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.
@@ -207,26 +207,8 @@ class ChatAgent(Agent):
207
207
  if msg_cls.default_value("request") in self.llm_tools_usable
208
208
  ]
209
209
  )
210
- return textwrap.dedent(
211
- f"""
212
- === ALL AVAILABLE TOOLS and THEIR JSON FORMAT INSTRUCTIONS ===
213
- You have access to the following TOOLS to accomplish your task:
214
-
215
- {json_instructions}
216
-
217
- When one of the above TOOLs is applicable, you must express your
218
- request as "TOOL:" followed by the request in the above JSON format.
219
- """
220
- + """
221
- The JSON format will be:
222
- \\{
223
- "request": "<tool_name>",
224
- "<arg1>": <value1>,
225
- "<arg2>": <value2>,
226
- ...
227
- \\}
228
- ----------------------------
229
- """.lstrip()
210
+ return ToolMessage.json_group_instructions().format(
211
+ json_instructions=json_instructions
230
212
  )
231
213
 
232
214
  def tool_instructions(self) -> str:
@@ -192,6 +192,16 @@ class ChatDocument(Document):
192
192
  if isinstance(message, ChatDocument):
193
193
  content = message.content
194
194
  fun_call = message.function_call
195
+ if message.metadata.sender == Entity.USER and fun_call is not None:
196
+ # This may happen when a (parent agent's) LLM generates a
197
+ # a Function-call, and it ends up being sent to the current task's
198
+ # LLM (possibly because the function-call is mis-named or has other
199
+ # issues and couldn't be handled by handler methods).
200
+ # But a function-call can only be generated by an entity with
201
+ # Role.ASSISTANT, so we instead put the content of the function-call
202
+ # in the content of the message.
203
+ content += " " + str(fun_call)
204
+ fun_call = None
195
205
  sender_name = message.metadata.sender_name
196
206
  tool_ids = message.metadata.tool_ids
197
207
  tool_id = tool_ids[-1] if len(tool_ids) > 0 else ""
@@ -0,0 +1,325 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+
4
+ from pydantic import BaseSettings
5
+ from rich import print
6
+ from rich.console import Console
7
+
8
+ if TYPE_CHECKING:
9
+ import neo4j
10
+
11
+
12
+ from langroid.agent.chat_agent import ChatAgent, ChatAgentConfig
13
+ from langroid.agent.chat_document import ChatDocMetaData, ChatDocument
14
+ from langroid.agent.special.neo4j.utils.system_message import (
15
+ DEFAULT_NEO4J_CHAT_SYSTEM_MESSAGE,
16
+ DEFAULT_SYS_MSG,
17
+ SCHEMA_TOOLS_SYS_MSG,
18
+ )
19
+ from langroid.agent.special.neo4j.utils.tools import (
20
+ GenerateCypherQueries,
21
+ GraphDatabaseSchema,
22
+ )
23
+ from langroid.mytypes import Entity
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ console = Console()
28
+
29
+ NEO4J_ERROR_MSG = "There was an error in your Cypher Query"
30
+
31
+ empty_nodes = "'nodes': []"
32
+ empty_relationships = "'relationships': []"
33
+ not_valid_query_response = [
34
+ empty_nodes,
35
+ empty_relationships,
36
+ NEO4J_ERROR_MSG,
37
+ ]
38
+
39
+
40
+ class Neo4jSettings(BaseSettings):
41
+ uri: str = ""
42
+ username: str = ""
43
+ password: str = ""
44
+ database: str = ""
45
+
46
+ class Config:
47
+ # This enables the use of environment variables to set the settings,
48
+ # e.g. NEO4J_URI, NEO4J_USERNAME, etc.,
49
+ # which can either be set in a .env file or in the shell via export cmds.
50
+ env_prefix = "NEO4J_"
51
+
52
+
53
+ class Neo4jChatAgentConfig(ChatAgentConfig):
54
+ neo4j_settings: Neo4jSettings = Neo4jSettings()
55
+ system_message: str = DEFAULT_NEO4J_CHAT_SYSTEM_MESSAGE
56
+ kg_schema: Optional[List[Dict[str, Any]]]
57
+ database_created: bool = False
58
+ use_schema_tools: bool = False
59
+ use_functions_api: bool = True
60
+ use_tools: bool = False
61
+
62
+
63
+ class Neo4jChatAgent(ChatAgent):
64
+ def __init__(self, config: Neo4jChatAgentConfig):
65
+ """Initialize the Neo4jChatAgent.
66
+
67
+ Raises:
68
+ ValueError: If database information is not provided in the config.
69
+ """
70
+ self.config = config
71
+ self._validate_config()
72
+ self._import_neo4j()
73
+ self._initialize_connection()
74
+ self._init_tool_messages()
75
+
76
+ def _validate_config(self) -> None:
77
+ """Validate the configuration to ensure all necessary fields are present."""
78
+ assert isinstance(self.config, Neo4jChatAgentConfig)
79
+ if (
80
+ self.config.neo4j_settings.username is None
81
+ and self.config.neo4j_settings.password is None
82
+ and self.config.neo4j_settings.database
83
+ ):
84
+ raise ValueError("Neo4j env information must be provided")
85
+
86
+ def _import_neo4j(self) -> None:
87
+ """Dynamically imports the Neo4j module and sets it as a global variable."""
88
+ global neo4j
89
+ try:
90
+ import neo4j
91
+ except ImportError:
92
+ raise ImportError(
93
+ """
94
+ neo4j not installed. Please install it via:
95
+ pip install neo4j.
96
+ Or when installing langroid, install it with the `neo4j` extra:
97
+ pip install langroid[neo4j]
98
+ """
99
+ )
100
+
101
+ def _initialize_connection(self) -> None:
102
+ """
103
+ Initializes a connection to the Neo4j database using the configuration settings.
104
+ """
105
+ try:
106
+ assert isinstance(self.config, Neo4jChatAgentConfig)
107
+ self.driver = neo4j.GraphDatabase.driver(
108
+ self.config.neo4j_settings.uri,
109
+ auth=(
110
+ self.config.neo4j_settings.username,
111
+ self.config.neo4j_settings.password,
112
+ ),
113
+ )
114
+ except Exception as e:
115
+ raise ConnectionError(f"Failed to initialize Neo4j connection: {e}")
116
+
117
+ def close(self) -> None:
118
+ """close the connection"""
119
+ if self.driver:
120
+ self.driver.close()
121
+
122
+ def retry_query(self, e: Exception, query: str) -> str:
123
+ """
124
+ Generate an error message for a failed Cypher query and return it.
125
+
126
+ Args:
127
+ e (Exception): The exception raised during the Cypher query execution.
128
+ query (str): The Cypher query that failed.
129
+
130
+ Returns:
131
+ str: The error message.
132
+ """
133
+ logger.error(f"Cypher Query failed: {query}\nException: {e}")
134
+
135
+ # Construct the error message
136
+ error_message_template = f"""\
137
+ {NEO4J_ERROR_MSG}: '{query}'
138
+ {str(e)}
139
+ Run a new query, correcting the errors.
140
+ """
141
+
142
+ return error_message_template
143
+
144
+ def read_query(
145
+ self, query: str, parameters: Optional[Dict[Any, Any]] = None
146
+ ) -> str:
147
+ """
148
+ Executes a given Cypher query with parameters on the Neo4j database.
149
+
150
+ Args:
151
+ query (str): The Cypher query string to be executed.
152
+ parameters (Optional[Dict[Any, Any]]): A dictionary of parameters for the
153
+ query. Defaults to None.
154
+
155
+ Returns:
156
+ str: The result of executing the Cypher query.
157
+ """
158
+ response_message = ""
159
+ if not self.driver:
160
+ raise ValueError("No database connection is established.")
161
+
162
+ try:
163
+ assert isinstance(self.config, Neo4jChatAgentConfig)
164
+ with self.driver.session(
165
+ database=self.config.neo4j_settings.database
166
+ ) as session:
167
+ result = session.run(query, parameters)
168
+ # Check if there are records in the result
169
+ if result.peek():
170
+ response_message = ", ".join(
171
+ [str(record.data()) for record in result]
172
+ )
173
+ else:
174
+ response_message = "No records found."
175
+ except Exception as e:
176
+ logger.error(f"Failed to execute query: {query}\n{e}")
177
+ response_message = self.retry_query(e, query)
178
+ finally:
179
+ self.close()
180
+
181
+ return response_message
182
+
183
+ def write_query(
184
+ self, query: str, parameters: Optional[Dict[Any, Any]] = None
185
+ ) -> bool:
186
+ """
187
+ Executes a write transaction using a given Cypher query on the Neo4j database.
188
+ This method should be used for queries that modify the database.
189
+
190
+ Args:
191
+ query (str): The Cypher query string to be executed.
192
+ parameters (dict, optional): A dict of parameters for the Cypher query
193
+
194
+ Returns:
195
+ bool: True if the query was executed successfully, False otherwise.
196
+ """
197
+ if not self.driver:
198
+ raise ValueError("No database connection is established.")
199
+ response = False
200
+ try:
201
+ assert isinstance(self.config, Neo4jChatAgentConfig)
202
+ with self.driver.session(
203
+ database=self.config.neo4j_settings.database
204
+ ) as session:
205
+ # Execute the query within a write transaction
206
+ session.write_transaction(lambda tx: tx.run(query, parameters))
207
+ response = True
208
+ except Exception as e:
209
+ logging.warning(
210
+ f"An unexpected error occurred while executing the write query: {e}"
211
+ )
212
+ finally:
213
+ self.close()
214
+ return response
215
+
216
+ # TODO: test under enterprise edition because community edition doesn't allow
217
+ # database creation/deletion
218
+ def remove_database(self) -> None:
219
+ """Deletes all nodes and relationships from the current Neo4j database."""
220
+ delete_query = """
221
+ MATCH (n)
222
+ DETACH DELETE n
223
+ """
224
+ if self.write_query(delete_query):
225
+ print("[green]Database is deleted!")
226
+ else:
227
+ print("[red]Database is not deleted!")
228
+
229
+ def make_query(self, msg: GenerateCypherQueries) -> str:
230
+ """ "
231
+ Handle a GenerateCypherQueries message by executing a Cypher query and
232
+ returning the result.
233
+ Args:
234
+ msg (GenerateCypherQueries): The tool-message to handle.
235
+
236
+ Returns:
237
+ str: The result of executing the Cypherquery.
238
+ """
239
+ query = msg.cypherQuery
240
+
241
+ logger.info(f"Executing Cypher query: {query}")
242
+ return self.read_query(query)
243
+
244
+ # TODO: There are various ways to get the schema. The current one uses the func
245
+ # `read_query`, which requires post processing to identify whether the response upon
246
+ # the schema query is valid. Another way is to isolate this func from `read_query`.
247
+ # The current query works well. But we could use the queries here:
248
+ # https://github.com/neo4j/NaLLM/blob/1af09cd117ba0777d81075c597a5081583568f9f/api/
249
+ # src/driver/neo4j.py#L30
250
+ def get_schema(self, msg: GraphDatabaseSchema | None) -> str:
251
+ """
252
+ Retrieves the schema of a Neo4j graph database.
253
+
254
+ Args:
255
+ msg (GraphDatabaseSchema): An instance of GraphDatabaseSchema, typically
256
+ containing information or parameters needed for the database query.
257
+
258
+ Returns:
259
+ str: The visual representation of the database schema as a string, or a
260
+ message stating that the database schema is empty or not valid.
261
+
262
+ Raises:
263
+ This function does not explicitly raise exceptions but depends on the
264
+ behavior of 'self.read_query' method, which might raise exceptions related
265
+ to database connectivity or query execution.
266
+ """
267
+ schema = self.read_query("CALL db.schema.visualization()")
268
+ if not any(element in schema for element in not_valid_query_response):
269
+ return schema
270
+ else:
271
+ return "The database schema does not have any nodes or relationships."
272
+
273
+ def _init_tool_messages(self) -> None:
274
+ """Initialize message tools used for chatting."""
275
+ message = self._format_message()
276
+ self.config.system_message = self.config.system_message.format(mode=message)
277
+ super().__init__(self.config)
278
+ self.enable_message(GenerateCypherQueries)
279
+ self.enable_message(GraphDatabaseSchema)
280
+
281
+ def _format_message(self) -> str:
282
+ if self.driver is None:
283
+ raise ValueError("Database driver None")
284
+ assert isinstance(self.config, Neo4jChatAgentConfig)
285
+ return (
286
+ SCHEMA_TOOLS_SYS_MSG.format(schema=self.get_schema(None))
287
+ if self.config.use_schema_tools
288
+ else DEFAULT_SYS_MSG
289
+ )
290
+
291
+ def agent_response(
292
+ self,
293
+ msg: Optional[str | ChatDocument] = None,
294
+ ) -> Optional[ChatDocument]:
295
+ if msg is None:
296
+ return None
297
+
298
+ results = self.handle_message(msg)
299
+ if results is None:
300
+ return None
301
+
302
+ output = results
303
+ if NEO4J_ERROR_MSG in output:
304
+ output = "There was an error in the Cypher Query. Press enter to retry."
305
+
306
+ console.print(f"[red]{self.indent}", end="")
307
+ print(f"[red]Agent: {output}")
308
+ sender_name = self.config.name
309
+ if isinstance(msg, ChatDocument) and msg.function_call is not None:
310
+ sender_name = msg.function_call.name
311
+
312
+ content = results.content if isinstance(results, ChatDocument) else results
313
+ recipient = (
314
+ results.metadata.recipient if isinstance(results, ChatDocument) else ""
315
+ )
316
+
317
+ return ChatDocument(
318
+ content=content,
319
+ metadata=ChatDocMetaData(
320
+ # source=Entity.AGENT,
321
+ sender=Entity.LLM,
322
+ sender_name=sender_name,
323
+ recipient=recipient,
324
+ ),
325
+ )
@@ -0,0 +1,39 @@
1
+ SCHEMA_TOOLS_SYS_MSG = """You are a data scientist and expert in Knolwedge Graphs,
2
+ with expertise in answering questions by interacting with a Neo4j graph database.
3
+
4
+ The schema maps the Neo4j database structure. node labels, relationship types,
5
+ and property keys available in your Neo4j database.
6
+ {schema}
7
+ Do not make assumptions about the database schema before using the tools.
8
+ Use the tool/function to learn more about the database schema."""
9
+
10
+ DEFAULT_SYS_MSG = """You are a data scientist and expert in Knolwedge Graphs,
11
+ with expertise in answering questions by querying Neo4j database.
12
+ You do not have access to the database directly, so you will need to use the
13
+ `make_query` tool/function-call to answer questions.
14
+
15
+ Use the `get_schema` tool/function-call to get all the node labels, relationship types,
16
+ and property keys available in your Neo4j database.
17
+
18
+ ONLY the node labels, relationship types, and property keys listed in the specified
19
+ above should be used in the generated queries.
20
+ You must be smart about using the right node labels, relationship types, and property
21
+ keys based on the english description. If you are thinking of using a node label,
22
+ relationship type, or property key that does not exist, you are probably on the wrong
23
+ track, so you should try your best to answer based on an existing table or column.
24
+ DO NOT assume any nodes or relationships other than those above."""
25
+
26
+ DEFAULT_NEO4J_CHAT_SYSTEM_MESSAGE = """
27
+ {mode}
28
+
29
+ You do not need to attempt answering a question with just one query.
30
+ You could make a sequence of Neo4j queries to help you write the final query.
31
+ Also if you receive a null or other unexpected result,
32
+ (a) make sure you use the available TOOLs correctly, and
33
+ (b) see if you have made an assumption in your Neo4j query, and try another way,
34
+ or use `run_query` to explore the database contents before submitting your
35
+ final query.
36
+
37
+ Start by asking what I would like to know about the data.
38
+
39
+ """
@@ -0,0 +1,13 @@
1
+ from langroid.agent.tool_message import ToolMessage
2
+
3
+
4
+ class GenerateCypherQueries(ToolMessage):
5
+ request: str = "make_query"
6
+ purpose: str = """Use this tool to send me the Generated Cypher query based on
7
+ text description and schema that I will provide you."""
8
+ cypherQuery: str
9
+
10
+
11
+ class GraphDatabaseSchema(ToolMessage):
12
+ request: str = "get_schema"
13
+ purpose: str = """Use this tool to get me the schema of the graph database."""
@@ -103,7 +103,7 @@ class ToolMessage(ABC, BaseModel):
103
103
  @classmethod
104
104
  def json_instructions(cls) -> str:
105
105
  """
106
- Default Instructions to the LLM showing how to use the message.
106
+ Default Instructions to the LLM showing how to use the tool/function-call.
107
107
  Works for GPT4 but override this for weaker LLMs if needed.
108
108
  Returns:
109
109
  str: instructions on how to use the message
@@ -122,6 +122,23 @@ class ToolMessage(ABC, BaseModel):
122
122
  """.lstrip()
123
123
  )
124
124
 
125
+ @staticmethod
126
+ def json_group_instructions() -> str:
127
+ """Template for instructions for a group of tools.
128
+ Works with GPT4 but override this for weaker LLMs if needed.
129
+ """
130
+ return textwrap.dedent(
131
+ """
132
+ === ALL AVAILABLE TOOLS and THEIR JSON FORMAT INSTRUCTIONS ===
133
+ You have access to the following TOOLS to accomplish your task:
134
+
135
+ {json_instructions}
136
+
137
+ When one of the above TOOLs is applicable, you must express your
138
+ request as "TOOL:" followed by the request in the above JSON format.
139
+ """
140
+ )
141
+
125
142
  @classmethod
126
143
  def llm_function_schema(
127
144
  cls,
@@ -3,11 +3,12 @@ import asyncio
3
3
  import json
4
4
  import logging
5
5
  from abc import ABC, abstractmethod
6
+ from datetime import datetime
6
7
  from enum import Enum
7
8
  from typing import Any, Dict, List, Optional, Tuple, Type, Union
8
9
 
9
10
  import aiohttp
10
- from pydantic import BaseModel, BaseSettings
11
+ from pydantic import BaseModel, BaseSettings, Field
11
12
 
12
13
  from langroid.cachedb.momento_cachedb import MomentoCacheConfig
13
14
  from langroid.cachedb.redis_cachedb import RedisCacheConfig
@@ -135,6 +136,7 @@ class LLMMessage(BaseModel):
135
136
  tool_id: str = "" # used by OpenAIAssistant
136
137
  content: str
137
138
  function_call: Optional[LLMFunctionCall] = None
139
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
138
140
 
139
141
  def api_dict(self) -> Dict[str, Any]:
140
142
  """
@@ -157,6 +159,7 @@ class LLMMessage(BaseModel):
157
159
  dict_no_none["function_call"]["arguments"]
158
160
  )
159
161
  dict_no_none.pop("tool_id", None)
162
+ dict_no_none.pop("timestamp", None)
160
163
  return dict_no_none
161
164
 
162
165
  def __str__(self) -> str:
@@ -931,7 +931,7 @@ class OpenAIGPT(LanguageModel):
931
931
  self._cache_store(hashed_key, result.model_dump())
932
932
  return cached, hashed_key, result
933
933
 
934
- @retry_with_exponential_backoff
934
+ @async_retry_with_exponential_backoff
935
935
  async def _achat_completions_with_backoff(self, **kwargs): # type: ignore
936
936
  cached = False
937
937
  hashed_key, result = self._cache_lookup("Completion", **kwargs)
@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
19
19
  class DocumentType(str, Enum):
20
20
  PDF = "pdf"
21
21
  DOCX = "docx"
22
+ DOC = "doc"
22
23
 
23
24
 
24
25
  class DocumentParser(Parser):
@@ -68,6 +69,8 @@ class DocumentParser(Parser):
68
69
  raise ValueError(
69
70
  f"Unsupported DOCX library specified: {config.docx.library}"
70
71
  )
72
+ elif DocumentParser._document_type(source) == DocumentType.DOC:
73
+ return UnstructuredDocParser(source, config)
71
74
  else:
72
75
  raise ValueError(f"Unsupported document type: {source}")
73
76
 
@@ -98,6 +101,8 @@ class DocumentParser(Parser):
98
101
  return DocumentType.PDF
99
102
  elif source.lower().endswith(".docx"):
100
103
  return DocumentType.DOCX
104
+ elif source.lower().endswith(".doc"):
105
+ return DocumentType.DOC
101
106
  else:
102
107
  raise ValueError(f"Unsupported document type: {source}")
103
108
 
@@ -440,6 +445,27 @@ class UnstructuredDocxParser(DocumentParser):
440
445
  return self.fix_text(text)
441
446
 
442
447
 
448
+ class UnstructuredDocParser(UnstructuredDocxParser):
449
+ def iterate_pages(self) -> Generator[Tuple[int, Any], None, None]: # type: ignore
450
+ from unstructured.partition.doc import partition_doc
451
+
452
+ elements = partition_doc(filename=self.source, include_page_breaks=True)
453
+
454
+ page_number = 1
455
+ page_elements = [] # type: ignore
456
+ for el in elements:
457
+ if el.category == "PageBreak":
458
+ if page_elements: # Avoid yielding empty pages at the start
459
+ yield page_number, page_elements
460
+ page_number += 1
461
+ page_elements = []
462
+ else:
463
+ page_elements.append(el)
464
+ # Yield the last page if it's not empty
465
+ if page_elements:
466
+ yield page_number, page_elements
467
+
468
+
443
469
  class PythonDocxParser(DocumentParser):
444
470
  """
445
471
  Parser for processing DOCX files using the `python-docx` library.
@@ -28,6 +28,10 @@ class DocxParsingConfig(BaseSettings):
28
28
  library: Literal["python-docx", "unstructured"] = "unstructured"
29
29
 
30
30
 
31
+ class DocParsingConfig(BaseSettings):
32
+ library: Literal["unstructured"] = "unstructured"
33
+
34
+
31
35
  class ParsingConfig(BaseSettings):
32
36
  splitter: str = Splitter.TOKENS
33
37
  chunk_size: int = 200 # aim for this many tokens per chunk
@@ -42,6 +46,7 @@ class ParsingConfig(BaseSettings):
42
46
  token_encoding_model: str = "text-embedding-ada-002"
43
47
  pdf: PdfParsingConfig = PdfParsingConfig()
44
48
  docx: DocxParsingConfig = DocxParsingConfig()
49
+ doc: DocParsingConfig = DocParsingConfig()
45
50
 
46
51
 
47
52
  class Parser:
@@ -545,7 +545,7 @@ class RepoLoader:
545
545
 
546
546
  for file_path in file_paths:
547
547
  _, file_extension = os.path.splitext(file_path)
548
- if file_extension.lower() in [".pdf", ".docx"]:
548
+ if file_extension.lower() in [".pdf", ".docx", ".doc"]:
549
549
  doc_parser = DocumentParser.create(
550
550
  file_path,
551
551
  parser.config,
@@ -44,7 +44,11 @@ class URLLoader:
44
44
  sleep_time=5,
45
45
  )
46
46
  for url, result in buffered_downloads(buffer, threads):
47
- if url.lower().endswith(".pdf") or url.lower().endswith(".docx"):
47
+ if (
48
+ url.lower().endswith(".pdf")
49
+ or url.lower().endswith(".docx")
50
+ or url.lower().endswith(".doc")
51
+ ):
48
52
  doc_parser = DocumentParser.create(
49
53
  url,
50
54
  self.parser.config,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.165
3
+ Version: 0.1.167
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
13
13
  Provides-Extra: hf-embeddings
14
14
  Provides-Extra: litellm
15
15
  Provides-Extra: mysql
16
+ Provides-Extra: neo4j
16
17
  Provides-Extra: postgres
17
18
  Provides-Extra: sciphi
18
19
  Requires-Dist: agent-search (>=0.0.7,<0.0.8) ; extra == "sciphi"
@@ -48,6 +49,7 @@ Requires-Dist: mkdocs-section-index (>=0.3.5,<0.4.0)
48
49
  Requires-Dist: mkdocstrings[python] (>=0.21.2,<0.22.0)
49
50
  Requires-Dist: momento (>=1.10.2,<2.0.0)
50
51
  Requires-Dist: mypy (>=1.7.0,<2.0.0)
52
+ Requires-Dist: neo4j (>=5.14.1,<6.0.0) ; extra == "neo4j"
51
53
  Requires-Dist: nltk (>=3.8.1,<4.0.0)
52
54
  Requires-Dist: onnxruntime (==1.16.1)
53
55
  Requires-Dist: openai (>=1.2.3,<2.0.0)
@@ -138,6 +140,10 @@ This Multi-Agent paradigm is inspired by the
138
140
  `Langroid` is a fresh take on LLM app-development, where considerable thought has gone
139
141
  into simplifying the developer experience; it does not use `Langchain`.
140
142
 
143
+ :fire: See this [Intro to Langroid](https://lancedb.substack.com/p/langoid-multi-agent-programming-framework)
144
+ blog post from the LanceDB team
145
+
146
+
141
147
  We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
142
148
  for ideas on what to contribute.
143
149
 
@@ -2,8 +2,8 @@ langroid/__init__.py,sha256=I9edNDkpmfd5C4WvTONaGaTFzIlvFyp5GpFEaMCAKMk,778
2
2
  langroid/agent/__init__.py,sha256=w2pap-rHrp41gMzdtzur2YY_m62LqQhF2Du-AmoIQi4,752
3
3
  langroid/agent/base.py,sha256=ZMZvrNsYPmOYUxuaDTGGP3I9sZUYpIl6QZ-n3bceAoQ,33238
4
4
  langroid/agent/batch.py,sha256=Cg7Qv1yGi_M9rMl38_4-hjXPsoLlZrOSXDhbOFqUcKY,5593
5
- langroid/agent/chat_agent.py,sha256=SBzVJ0EcEHg6sRcbkPMAbxU_m8EIK9569vO0I8E_im4,35665
6
- langroid/agent/chat_document.py,sha256=kbep6196vfTwEKNeMw6PB656W6wf-AFDT01IJxL9tIM,7181
5
+ langroid/agent/chat_agent.py,sha256=yi6Vyh94fXnOQ2nmHvLaTOEV_k7esFs2K0vCpDdg2vs,35029
6
+ langroid/agent/chat_document.py,sha256=4BQADxqj7Gbn8dr5NeTB5EObzosOJ5eF0azvHKoHUog,7863
7
7
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
9
9
  langroid/agent/openai_assistant.py,sha256=yBtxis64XOnxtJzlkwUoTm-wCyvKr4DGo9-laXYMok0,32654
@@ -15,6 +15,9 @@ langroid/agent/special/lance_rag/critic_agent.py,sha256=9izW4keCxVZEqrFOgyVUHD7N
15
15
  langroid/agent/special/lance_rag/lance_rag_task.py,sha256=l_HQgrYY-CX2FwIsS961aEF3bYog3GDYo98fj0C0mSk,2889
16
16
  langroid/agent/special/lance_rag/lance_tools.py,sha256=WypIS-3ZMDqY_PZEGB2K80-o4RfS43_OnER0dyFlsDY,1339
17
17
  langroid/agent/special/lance_rag/query_planner_agent.py,sha256=dZXVano2NbRZy91nBcEW6LrvedsHfxL1oNCgMQEHZ-U,8016
18
+ langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=LowGbQyaKlU7DCn6zwAVe_VmuhbPWArhVuh3Flls6dU,11435
19
+ langroid/agent/special/neo4j/utils/system_message.py,sha256=7rwq4CYpb1-1AvGLJBI6-X8UIu5RP5LiWU9re-umXxs,1959
20
+ langroid/agent/special/neo4j/utils/tools.py,sha256=0iRiYbh-tSM9l6UolkKhbL3y_NIdArmBN6JRtjB_4gk,450
18
21
  langroid/agent/special/relevance_extractor_agent.py,sha256=lychVpSEhMzi_CdeLN1yO-O2AVOe2W0F3Ha8aC3nd9M,4624
19
22
  langroid/agent/special/retriever_agent.py,sha256=uu6vqFg85uCVM-_DrXesYe2gH_-WcoHhlsKRlLuZPXk,1867
20
23
  langroid/agent/special/sql/__init__.py,sha256=qUM-b4FfvIt0gYWP7_niyqR3OwVMMkuK2SyqUYWjyxs,207
@@ -26,7 +29,7 @@ langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GS
26
29
  langroid/agent/special/sql/utils/tools.py,sha256=6uB2424SLtmapui9ggcEr0ZTiB6_dL1-JRGgN8RK9Js,1332
27
30
  langroid/agent/special/table_chat_agent.py,sha256=Gj_Iuq21_Ldyvzc8qhgsVuBLKqt0VIqivxCw_tH9Cu4,7772
28
31
  langroid/agent/task.py,sha256=mrZz4LZuCBpurm7j4BvyvKzEpJS5XhTisRQhkPa8j5M,46576
29
- langroid/agent/tool_message.py,sha256=uAxBU2B0_WUH04hn8zbVFMo9_mltVEGr5kkjE7irORo,6757
32
+ langroid/agent/tool_message.py,sha256=ngmWdiqMYbjF4Am0hsLyA9zK0Q9QF2ziec6FW0lPD90,7399
30
33
  langroid/agent/tools/__init__.py,sha256=q-maq3k2BXhPAU99G0H6-j_ozoRvx15I1RFpPVicQIU,304
31
34
  langroid/agent/tools/extract_tool.py,sha256=u5lL9rKBzaLBOrRyLnTAZ97pQ1uxyLP39XsWMnpaZpw,3789
32
35
  langroid/agent/tools/generator_tool.py,sha256=y0fB0ZObjA0b3L0uSTtrqRCKHDUR95arBftqiUeKD2o,663
@@ -46,10 +49,10 @@ langroid/embedding_models/clustering.py,sha256=tZWElUqXl9Etqla0FAa7og96iDKgjqWju
46
49
  langroid/embedding_models/models.py,sha256=0bQ8u2ee2ODcopGPusz9WYWI_PjR5Gbdy47qcSU8gCo,4603
47
50
  langroid/language_models/__init__.py,sha256=5L9ndEEC8iLJHjDJmYFTnv6-2-3xsxWUMHcugR8IeDs,821
48
51
  langroid/language_models/azure_openai.py,sha256=ncRCbKooqLVOY-PWQUIo9C3yTuKEFbAwyngXT_M4P7k,5989
49
- langroid/language_models/base.py,sha256=jUEUqDWJBVxIxmG6U4Ysg2QKGOnP_CLmRuEMicsSwUw,20596
52
+ langroid/language_models/base.py,sha256=8i_HiJ8jgP7t-tkpBt17fx28kbQet6XRcKIoQWGWD-I,20742
50
53
  langroid/language_models/config.py,sha256=PXcmEUq52GCDj2sekt8F9E1flWyyNjP2S0LTRs7T6Kg,269
51
54
  langroid/language_models/openai_assistants.py,sha256=9K-DEAL2aSWHeXj2hwCo2RAlK9_1oCPtqX2u1wISCj8,36
52
- langroid/language_models/openai_gpt.py,sha256=ozCad82LRWPVSAH1gg9QjW77omvSFydvMb1-WUMOXx0,42376
55
+ langroid/language_models/openai_gpt.py,sha256=VBIEBSVs09Jip6LUIKqojnK-jDCt1dKJf9M3w1JDKME,42382
53
56
  langroid/language_models/prompt_formatter/__init__.py,sha256=9JXFF22QNMmbQV1q4nrIeQVTtA3Tx8tEZABLtLBdFyc,352
54
57
  langroid/language_models/prompt_formatter/base.py,sha256=2y_GcwhstvB5ih3haS7l5Fv79jVnFJ_vEw1jqWJzB9k,1247
55
58
  langroid/language_models/prompt_formatter/llama2_formatter.py,sha256=YdcO88qyBeuMENVIVvVqSYuEpvYSTndUe_jd6hVTko4,2899
@@ -60,15 +63,15 @@ langroid/parsing/agent_chats.py,sha256=sbZRV9ujdM5QXvvuHVjIi2ysYSYlap-uqfMMUKulr
60
63
  langroid/parsing/code-parsing.md,sha256=--cyyNiSZSDlIwcjAV4-shKrSiRe2ytF3AdSoS_hD2g,3294
61
64
  langroid/parsing/code_parser.py,sha256=BbDAzp35wkYQ9U1dpf1ARL0lVyi0tfqEc6_eox2C090,3727
62
65
  langroid/parsing/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- langroid/parsing/document_parser.py,sha256=Msv8acFzVDex-nKPNxyGOvTw4eNKswrSQluYOa1qfAE,15670
66
+ langroid/parsing/document_parser.py,sha256=SEW53fnEsOrsJbVUy9Fq5ygQzF_5UiGB5_Ogkte1u2Y,16697
64
67
  langroid/parsing/json.py,sha256=KfIIma_6IurQ09WTUyBn3mbSK67QeXZ8eHGDxGlOsv0,2551
65
68
  langroid/parsing/para_sentence_split.py,sha256=AJBzZojP3zpB-_IMiiHismhqcvkrVBQ3ZINoQyx_bE4,2000
66
- langroid/parsing/parser.py,sha256=0QDDfRrcO9jUwEj9WQiWi8ayVZ19MRC1xjwTLDrCKwg,10372
67
- langroid/parsing/repo_loader.py,sha256=hhMfQBBSo-HvsZDQEcgmk_idKQQAeDQ_MMPd38x2ACU,29338
69
+ langroid/parsing/parser.py,sha256=727QivWlZNlQiRFgkxTZpPoTMqB2yaltOkAGqLZGI_Q,10513
70
+ langroid/parsing/repo_loader.py,sha256=52jTajXOkq_66NCRKLMNQoGKMJ59H-m2CZB9arMT7Wo,29346
68
71
  langroid/parsing/search.py,sha256=xmQdAdTIwZ0REEUeQVFlGZlqf7k8Poah7-ALuyW7Ov0,8440
69
72
  langroid/parsing/spider.py,sha256=w_mHR1B4KOmxsBLoVI8kMkMTEbwTzeK3ath9fOMJrTk,3043
70
73
  langroid/parsing/table_loader.py,sha256=qNM4obT_0Y4tjrxNBCNUYjKQ9oETCZ7FbolKBTcz-GM,3410
71
- langroid/parsing/url_loader.py,sha256=RZCX1RJuQpTatJjBOU74_gJ5Ab7xwarRmFh5ON4n_G4,2279
74
+ langroid/parsing/url_loader.py,sha256=54c6yt9grfUyImauSdM5UM4_ulU4JEz0ehdugAkxKI4,2391
72
75
  langroid/parsing/url_loader_cookies.py,sha256=Lg4sNpRz9MByWq2mde6T0hKv68VZSV3mtMjNEHuFeSU,2327
73
76
  langroid/parsing/urls.py,sha256=Nv4yCWQLLBEjaiRdaZZVQNBEl_cfK_V6cVuPm91wGtU,7686
74
77
  langroid/parsing/utils.py,sha256=NVX4D43taqjnQJ0P4tRKB5tX6iXfVXWKxWgGNhQsc5c,10030
@@ -104,7 +107,7 @@ langroid/vector_store/meilisearch.py,sha256=d2huA9P-NoYRuAQ9ZeXJmMKr7ry8u90RUSR2
104
107
  langroid/vector_store/momento.py,sha256=j6Eo6oIDN2fe7lsBOlCXJn3uvvERHHTFL5QJfeREeOM,10044
105
108
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
106
109
  langroid/vector_store/qdrantdb.py,sha256=qt7Dye6rcgoe0551WzmOxRGIlJfL87D4MX7HdqxuEok,13393
107
- langroid-0.1.165.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
108
- langroid-0.1.165.dist-info/METADATA,sha256=fWOsVYjX-yS4ynFAI0BTHjRXZMIqWxj_72Xka7e80o0,43389
109
- langroid-0.1.165.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
110
- langroid-0.1.165.dist-info/RECORD,,
110
+ langroid-0.1.167.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
111
+ langroid-0.1.167.dist-info/METADATA,sha256=rvYS1MJQd2Btl9vWDBwM6jOaovQQ5YZ1mfLE0zMJWP0,43613
112
+ langroid-0.1.167.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
113
+ langroid-0.1.167.dist-info/RECORD,,