langroid 0.1.195__py3-none-any.whl → 0.1.197__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.
@@ -225,14 +225,22 @@ class ChatAgent(Agent):
225
225
  enabled_classes: List[Type[ToolMessage]] = list(self.llm_tools_map.values())
226
226
  if len(enabled_classes) == 0:
227
227
  return "You can ask questions in natural language."
228
-
229
228
  json_instructions = "\n\n".join(
230
229
  [
231
- msg_cls.json_instructions()
230
+ msg_cls.json_instructions(tool=self.config.use_tools)
232
231
  for _, msg_cls in enumerate(enabled_classes)
233
232
  if msg_cls.default_value("request") in self.llm_tools_usable
234
233
  ]
235
234
  )
235
+ # if any of the enabled classes has json_group_instructions, then use that,
236
+ # else fall back to ToolMessage.json_group_instructions
237
+ for msg_cls in enabled_classes:
238
+ if hasattr(msg_cls, "json_group_instructions") and callable(
239
+ getattr(msg_cls, "json_group_instructions")
240
+ ):
241
+ return msg_cls.json_group_instructions().format(
242
+ json_instructions=json_instructions
243
+ )
236
244
  return ToolMessage.json_group_instructions().format(
237
245
  json_instructions=json_instructions
238
246
  )
@@ -84,7 +84,7 @@ class ChatDocument(Document):
84
84
  json_data = json.loads(j)
85
85
  tool = json_data.get("request")
86
86
  if tool is not None:
87
- tools.append(tool)
87
+ tools.append(str(tool))
88
88
  return tools
89
89
 
90
90
  def log_fields(self) -> ChatDocLoggerFields:
@@ -22,7 +22,6 @@ from rich import print
22
22
  from rich.console import Console
23
23
  from rich.prompt import Prompt
24
24
 
25
- from langroid.agent.base import Agent
26
25
  from langroid.agent.batch import run_batch_tasks
27
26
  from langroid.agent.chat_agent import ChatAgent, ChatAgentConfig
28
27
  from langroid.agent.chat_document import ChatDocMetaData, ChatDocument
@@ -1219,11 +1218,13 @@ class DocChatAgent(ChatAgent):
1219
1218
  )
1220
1219
  prompt = f"""
1221
1220
  {instruction}
1221
+
1222
+ FULL TEXT:
1222
1223
  {full_text}
1223
1224
  """.strip()
1224
1225
  with StreamingIfAllowed(self.llm):
1225
- summary = Agent.llm_response(self, prompt)
1226
- return summary # type: ignore
1226
+ summary = ChatAgent.llm_response(self, prompt)
1227
+ return summary
1227
1228
 
1228
1229
  def justify_response(self) -> ChatDocument | None:
1229
1230
  """Show evidence for last response"""
@@ -31,10 +31,17 @@ NEO4J_ERROR_MSG = "There was an error in your Cypher Query"
31
31
  # TOOLS to be used by the agent
32
32
 
33
33
 
34
- class CypherQueryTool(ToolMessage):
34
+ class CypherRetrievalTool(ToolMessage):
35
35
  request: str = "retrieval_query"
36
- purpose: str = """Use this tool to send the Generated Cypher query based on
37
- provided text description and schema."""
36
+ purpose: str = """Use this tool to send the Cypher query to retreive data from the
37
+ graph database based provided text description and schema."""
38
+ cypher_query: str
39
+
40
+
41
+ class CypherCreationTool(ToolMessage):
42
+ request: str = "create_query"
43
+ purpose: str = """Use this tool to send the Cypher query to create
44
+ entities/relationships in the graph database."""
38
45
  cypher_query: str
39
46
 
40
47
 
@@ -238,12 +245,12 @@ class Neo4jChatAgent(ChatAgent):
238
245
  else:
239
246
  print("[red]Database is not deleted!")
240
247
 
241
- def retrieval_query(self, msg: CypherQueryTool) -> str:
242
- """
243
- Handle a CypherQueryTool message by executing a Cypher query and
248
+ def retrieval_query(self, msg: CypherRetrievalTool) -> str:
249
+ """ "
250
+ Handle a CypherRetrievalTool message by executing a Cypher query and
244
251
  returning the result.
245
252
  Args:
246
- msg (CypherQueryTool): The tool-message to handle.
253
+ msg (CypherRetrievalTool): The tool-message to handle.
247
254
 
248
255
  Returns:
249
256
  str: The result of executing the cypher_query.
@@ -257,6 +264,25 @@ class Neo4jChatAgent(ChatAgent):
257
264
  else:
258
265
  return str(response.data)
259
266
 
267
+ def create_query(self, msg: CypherCreationTool) -> str:
268
+ """ "
269
+ Handle a CypherCreationTool message by executing a Cypher query and
270
+ returning the result.
271
+ Args:
272
+ msg (CypherCreationTool): The tool-message to handle.
273
+
274
+ Returns:
275
+ str: The result of executing the cypher_query.
276
+ """
277
+ query = msg.cypher_query
278
+
279
+ logger.info(f"Executing Cypher query: {query}")
280
+ response = self.write_query(query)
281
+ if response.success:
282
+ return "Cypher query executed successfully"
283
+ else:
284
+ return str(response.data)
285
+
260
286
  # TODO: There are various ways to get the schema. The current one uses the func
261
287
  # `read_query`, which requires post processing to identify whether the response upon
262
288
  # the schema query is valid. Another way is to isolate this func from `read_query`.
@@ -293,7 +319,8 @@ class Neo4jChatAgent(ChatAgent):
293
319
  message = self._format_message()
294
320
  self.config.system_message = self.config.system_message.format(mode=message)
295
321
  super().__init__(self.config)
296
- self.enable_message(CypherQueryTool)
322
+ self.enable_message(CypherRetrievalTool)
323
+ self.enable_message(CypherCreationTool)
297
324
  self.enable_message(GraphSchemaTool)
298
325
 
299
326
  def _format_message(self) -> str:
@@ -9,14 +9,16 @@ Use the tool/function to learn more about the database schema."""
9
9
 
10
10
  SCHEMA_TOOLS_SYS_MSG = """You are a data scientist and expert in Knowledge Graphs,
11
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
- `retrieval_query` tool/function-call to answer questions.
12
+ You have access to the following tools:
13
+ - `retrieval_query` tool/function-call to retreive infomration from the graph database
14
+ to answer questions.
14
15
 
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.
16
+ - `create_query` tool/function-call to execute cypher query that creates
17
+ entities/relationships in the graph database.
18
+
19
+ - `get_schema` tool/function-call to get all the node labels, relationship
20
+ types, and property keys available in your Neo4j database.
17
21
 
18
- ONLY the node labels, relationship types, and property keys listed in the specified
19
- above should be used in the generated queries.
20
22
  You must be smart about using the right node labels, relationship types, and property
21
23
  keys based on the english description. If you are thinking of using a node label,
22
24
  relationship type, or property key that does not exist, you are probably on the wrong
@@ -31,8 +33,13 @@ You could make a sequence of Neo4j queries to help you write the final query.
31
33
  Also if you receive a null or other unexpected result,
32
34
  (a) make sure you use the available TOOLs correctly, and
33
35
  (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
36
+ or use `retrieval_query` to explore the database contents before submitting your
35
37
  final query.
38
+ (c) USE `create_query` tool/function-call to execute cypher query that creates
39
+ entities/relationships in the graph database.
40
+
41
+ (d) USE `get_schema` tool/function-call to get all the node labels, relationship
42
+ types, and property keys available in your Neo4j database.
36
43
 
37
44
  Start by asking what I would like to know about the data.
38
45
 
@@ -7,11 +7,11 @@ Functionality includes:
7
7
  - asking a question about a SQL schema
8
8
  """
9
9
  import logging
10
- from typing import Any, Dict, Optional, Sequence, Union
10
+ from typing import Any, Dict, List, Optional, Sequence, Union
11
11
 
12
12
  from rich import print
13
13
  from rich.console import Console
14
- from sqlalchemy import MetaData, Row, create_engine, text
14
+ from sqlalchemy import MetaData, Row, create_engine, inspect, text
15
15
  from sqlalchemy.engine import Engine
16
16
  from sqlalchemy.exc import SQLAlchemyError
17
17
  from sqlalchemy.orm import Session, sessionmaker
@@ -73,6 +73,7 @@ class SQLChatAgentConfig(ChatAgentConfig):
73
73
  vecdb: None | VectorStoreConfig = None
74
74
  context_descriptions: Dict[str, Dict[str, Union[str, Dict[str, str]]]] = {}
75
75
  use_schema_tools: bool = False
76
+ multi_schema: bool = False
76
77
 
77
78
  """
78
79
  Optional, but strongly recommended, context descriptions for tables, columns,
@@ -87,6 +88,9 @@ class SQLChatAgentConfig(ChatAgentConfig):
87
88
  is another table name and the value is a description of the relationship to
88
89
  that table.
89
90
 
91
+ If multi_schema support is enabled, the tables names in the description
92
+ should be of the form 'schema_name.table_name'.
93
+
90
94
  For example:
91
95
  {
92
96
  'table1': {
@@ -143,14 +147,37 @@ class SQLChatAgent(ChatAgent):
143
147
  """Initialize the database metadata."""
144
148
  if self.engine is None:
145
149
  raise ValueError("Database engine is None")
150
+ self.metadata: MetaData | List[MetaData] = []
146
151
 
147
- self.metadata = MetaData()
148
- self.metadata.reflect(self.engine)
149
- logger.info(
150
- "SQLChatAgent initialized with database: %s and tables: %s",
151
- self.engine,
152
- self.metadata.tables,
153
- )
152
+ if self.config.multi_schema:
153
+ logger.info(
154
+ "Initializing SQLChatAgent with database: %s",
155
+ self.engine,
156
+ )
157
+
158
+ self.metadata = []
159
+ inspector = inspect(self.engine)
160
+
161
+ for schema in inspector.get_schema_names():
162
+ metadata = MetaData(schema=schema)
163
+ metadata.reflect(self.engine)
164
+ self.metadata.append(metadata)
165
+
166
+ logger.info(
167
+ "Initializing SQLChatAgent with database: %s, schema: %s, "
168
+ "and tables: %s",
169
+ self.engine,
170
+ schema,
171
+ metadata.tables,
172
+ )
173
+ else:
174
+ self.metadata = MetaData()
175
+ self.metadata.reflect(self.engine)
176
+ logger.info(
177
+ "SQLChatAgent initialized with database: %s and tables: %s",
178
+ self.engine,
179
+ self.metadata.tables,
180
+ )
154
181
 
155
182
  def _init_table_metadata(self) -> None:
156
183
  """Initialize metadata for the tables present in the database."""
@@ -319,6 +346,10 @@ class SQLChatAgent(ChatAgent):
319
346
  Returns:
320
347
  str: The names of all tables in the database.
321
348
  """
349
+ if isinstance(self.metadata, list):
350
+ table_names = [", ".join(md.tables.keys()) for md in self.metadata]
351
+ return ", ".join(table_names)
352
+
322
353
  return ", ".join(self.metadata.tables.keys())
323
354
 
324
355
  def get_table_schema(self, msg: GetTableSchemaTool) -> str:
@@ -1,10 +1,11 @@
1
- from typing import Dict, Union
1
+ from typing import Dict, List, Union
2
2
 
3
3
  from sqlalchemy import MetaData
4
4
 
5
5
 
6
6
  def populate_metadata_with_schema_tools(
7
- metadata: MetaData, info: Dict[str, Dict[str, Union[str, Dict[str, str]]]]
7
+ metadata: MetaData | List[MetaData],
8
+ info: Dict[str, Dict[str, Union[str, Dict[str, str]]]],
8
9
  ) -> Dict[str, Dict[str, Union[str, Dict[str, str]]]]:
9
10
  """
10
11
  Extracts information from an SQLAlchemy database's metadata and combines it
@@ -18,28 +19,35 @@ def populate_metadata_with_schema_tools(
18
19
  Returns:
19
20
  Dict[str, Dict[str, Any]]: A dictionary with table and context information.
20
21
  """
21
-
22
22
  db_info: Dict[str, Dict[str, Union[str, Dict[str, str]]]] = {}
23
23
 
24
- # Create empty metadata dictionary with column datatypes
25
- for table_name, table in metadata.tables.items():
26
- # Populate tables with empty descriptions
27
- db_info[str(table_name)] = {
28
- "description": info[table_name]["description"] or "",
29
- "columns": {},
30
- }
31
-
32
- for column in table.columns:
33
- # Populate columns with datatype
34
- db_info[str(table_name)]["columns"][str(column.name)] = ( # type: ignore
35
- str(column.type)
36
- )
24
+ def populate_metadata(md: MetaData) -> None:
25
+ # Create empty metadata dictionary with column datatypes
26
+ for table_name, table in md.tables.items():
27
+ # Populate tables with empty descriptions
28
+ db_info[table_name] = {
29
+ "description": info[table_name]["description"] or "",
30
+ "columns": {},
31
+ }
32
+
33
+ for column in table.columns:
34
+ # Populate columns with datatype
35
+ db_info[table_name]["columns"][str(column.name)] = ( # type: ignore
36
+ str(column.type)
37
+ )
38
+
39
+ if isinstance(metadata, list):
40
+ for md in metadata:
41
+ populate_metadata(md)
42
+ else:
43
+ populate_metadata(metadata)
37
44
 
38
45
  return db_info
39
46
 
40
47
 
41
48
  def populate_metadata(
42
- metadata: MetaData, info: Dict[str, Dict[str, Union[str, Dict[str, str]]]]
49
+ metadata: MetaData | List[MetaData],
50
+ info: Dict[str, Dict[str, Union[str, Dict[str, str]]]],
43
51
  ) -> Dict[str, Dict[str, Union[str, Dict[str, str]]]]:
44
52
  """
45
53
  Populate metadata based on the provided database metadata and additional info.
@@ -51,7 +59,6 @@ def populate_metadata(
51
59
  Returns:
52
60
  Dict: A dictionary containing populated metadata information.
53
61
  """
54
-
55
62
  # Fetch basic metadata info using available tools
56
63
  db_info: Dict[
57
64
  str, Dict[str, Union[str, Dict[str, str]]]
langroid/agent/task.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import logging
5
+ import re
5
6
  from collections import Counter
6
7
  from types import SimpleNamespace
7
8
  from typing import (
@@ -781,17 +782,20 @@ class Task:
781
782
  # handle routing instruction in result if any,
782
783
  # of the form PASS=<recipient>
783
784
  content = msg.content if isinstance(msg, ChatDocument) else msg
785
+ content = content.strip()
784
786
  if PASS in content and PASS_TO not in content:
785
787
  return True, None
786
788
  if PASS_TO in content and content.split(":")[1] != "":
787
789
  return True, content.split(":")[1]
788
- if SEND_TO in content and content.split(":")[1] != "":
789
- recipient = content.split(":")[1]
790
+ if SEND_TO in content and (send_parts := re.split(r"[,: ]", content))[1] != "":
791
+ # assume syntax is SEND_TO:<recipient> <content>
792
+ # or SEND_TO:<recipient>,<content> or SEND_TO:<recipient>:<content>
793
+ recipient = send_parts[1].strip()
790
794
  # get content to send, clean out routing instruction, and
791
795
  # start from 1 char after SEND_TO:<recipient>,
792
796
  # because we expect there is either a blank or some other separator
793
797
  # after the recipient
794
- content_to_send = content.replace(f"{SEND_TO}:{recipient}", "").strip()[1:]
798
+ content_to_send = content.replace(f"{SEND_TO}{recipient}", "").strip()[1:]
795
799
  # if no content then treat same as PASS_TO
796
800
  if content_to_send == "":
797
801
  return True, recipient
@@ -16,7 +16,10 @@ from docstring_parser import parse
16
16
  from pydantic import BaseModel
17
17
 
18
18
  from langroid.language_models.base import LLMFunctionSpec
19
- from langroid.utils.pydantic_utils import _recursive_purge_dict_key
19
+ from langroid.utils.pydantic_utils import (
20
+ _recursive_purge_dict_key,
21
+ generate_simple_schema,
22
+ )
20
23
 
21
24
 
22
25
  class ToolMessage(ABC, BaseModel):
@@ -101,22 +104,30 @@ class ToolMessage(ABC, BaseModel):
101
104
  return properties.get(f, {}).get("default", None)
102
105
 
103
106
  @classmethod
104
- def json_instructions(cls) -> str:
107
+ def json_instructions(cls, tool: bool = False) -> str:
105
108
  """
106
109
  Default Instructions to the LLM showing how to use the tool/function-call.
107
110
  Works for GPT4 but override this for weaker LLMs if needed.
111
+
112
+ Args:
113
+ tool: instructions for Langroid-native tool use? (e.g. for non-OpenAI LLM)
114
+ (or else it would be for OpenAI Function calls)
108
115
  Returns:
109
116
  str: instructions on how to use the message
110
117
  """
118
+ # TODO: when we attempt to use a "simpler schema"
119
+ # (i.e. all nested fields explicit without definitions),
120
+ # we seem to get worse results, so we turn it off for now
121
+ param_dict = (
122
+ # cls.simple_schema() if tool else
123
+ cls.llm_function_schema(request=True).parameters
124
+ )
111
125
  return textwrap.dedent(
112
126
  f"""
113
127
  TOOL: {cls.default_value("request")}
114
128
  PURPOSE: {cls.default_value("purpose")}
115
129
  JSON FORMAT: {
116
- json.dumps(
117
- cls.llm_function_schema(request=True).parameters,
118
- indent=4,
119
- )
130
+ json.dumps(param_dict, indent=4)
120
131
  }
121
132
  {"EXAMPLE: " + cls.usage_example() if cls.examples() else ""}
122
133
  """.lstrip()
@@ -210,3 +221,14 @@ class ToolMessage(ABC, BaseModel):
210
221
  description=cls.default_value("purpose"),
211
222
  parameters=parameters,
212
223
  )
224
+
225
+ @classmethod
226
+ def simple_schema(cls) -> Dict[str, Any]:
227
+ """
228
+ Return a simplified schema for the message, with only the request and
229
+ required fields.
230
+ Returns:
231
+ Dict[str, Any]: simplified schema
232
+ """
233
+ schema = generate_simple_schema(cls, exclude=["result", "purpose"])
234
+ return schema
@@ -39,8 +39,8 @@ class LLMConfig(BaseSettings):
39
39
  chat_model: str = ""
40
40
  completion_model: str = ""
41
41
  temperature: float = 0.0
42
- chat_context_length: int = 1024
43
- completion_context_length: int = 1024
42
+ chat_context_length: int = 8000
43
+ completion_context_length: int = 8000
44
44
  max_output_tokens: int = 1024 # generate at most this many tokens
45
45
  # if input length + max_output_tokens > context length of model,
46
46
  # we will try shortening requested output
langroid/parsing/json.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import re
2
3
  from typing import Any, Iterator, List
3
4
 
4
5
  from pyparsing import nestedExpr, originalTextFor
@@ -44,6 +45,40 @@ def get_json_candidates(s: str) -> List[str]:
44
45
  return []
45
46
 
46
47
 
48
+ def replace_undefined(s: str, undefined_placeholder: str = '"<undefined>"') -> str:
49
+ """
50
+ Replace undefined values in a potential json str with a placeholder.
51
+
52
+ Args:
53
+ - s (str): The potential JSON string to parse.
54
+ - undefined_placeholder (str): The placeholder or error message
55
+ for undefined values.
56
+
57
+ Returns:
58
+ - str: The (potential) JSON string with undefined values
59
+ replaced by the placeholder.
60
+ """
61
+
62
+ # Preprocess the string to replace undefined values with the placeholder
63
+ # This regex looks for patterns like ": <identifier>" and replaces them
64
+ # with the placeholder.
65
+ # It's a simple approach and might need adjustments for complex cases
66
+ # This is an attempt to handle cases where a weak LLM may produce
67
+ # a JSON-like string without quotes around some values, e.g.
68
+ # {"rent": DO-NOT-KNOW }
69
+ preprocessed_s = re.sub(
70
+ r":\s*([a-zA-Z_][a-zA-Z_0-9\-]*)", f": {undefined_placeholder}", s
71
+ )
72
+
73
+ # Now, attempt to parse the preprocessed string as JSON
74
+ try:
75
+ return preprocessed_s
76
+ except Exception:
77
+ # If parsing fails, return an error message instead
78
+ # (this should be rare after preprocessing)
79
+ return s
80
+
81
+
47
82
  def extract_top_level_json(s: str) -> List[str]:
48
83
  """Extract all top-level JSON-formatted substrings from a given string.
49
84
 
@@ -53,15 +88,16 @@ def extract_top_level_json(s: str) -> List[str]:
53
88
  Returns:
54
89
  List[str]: A list of top-level JSON-formatted substrings.
55
90
  """
56
- # Find JSON object and array candidates using regular expressions
91
+ # Find JSON object and array candidates
57
92
  json_candidates = get_json_candidates(s)
58
93
 
59
94
  normalized_candidates = [
60
95
  candidate.replace("\\{", "{").replace("\\}", "}").replace("\\_", "_")
61
96
  for candidate in json_candidates
62
97
  ]
98
+ candidates = [replace_undefined(candidate) for candidate in normalized_candidates]
63
99
  top_level_jsons = [
64
- candidate for candidate in normalized_candidates if is_valid_json(candidate)
100
+ candidate for candidate in candidates if is_valid_json(candidate)
65
101
  ]
66
102
 
67
103
  return top_level_jsons
@@ -135,6 +135,53 @@ def flatten_pydantic_model(
135
135
  return create_model("FlatModel", __base__=base_model, **flattened_fields)
136
136
 
137
137
 
138
+ def get_field_names(model: Type[BaseModel]) -> List[str]:
139
+ """Get all field names from a possibly nested Pydantic model."""
140
+ mdl = flatten_pydantic_model(model)
141
+ fields = list(mdl.__fields__.keys())
142
+ # fields may be like a__b__c , so we only want the last part
143
+ return [f.split("__")[-1] for f in fields]
144
+
145
+
146
+ def generate_simple_schema(
147
+ model: Type[BaseModel], exclude: List[str] = []
148
+ ) -> Dict[str, Any]:
149
+ """
150
+ Generates a JSON schema for a Pydantic model,
151
+ with options to exclude specific fields.
152
+
153
+ This function traverses the Pydantic model's fields, including nested models,
154
+ to generate a dictionary representing the JSON schema. Fields specified in
155
+ the exclude list will not be included in the generated schema.
156
+
157
+ Args:
158
+ model (Type[BaseModel]): The Pydantic model class to generate the schema for.
159
+ exclude (List[str]): A list of string field names to be excluded from the
160
+ generated schema. Defaults to an empty list.
161
+
162
+ Returns:
163
+ Dict[str, Any]: A dictionary representing the JSON schema of the provided model,
164
+ with specified fields excluded.
165
+ """
166
+ if hasattr(model, "__fields__"):
167
+ output: Dict[str, Any] = {}
168
+ for field_name, field in model.__fields__.items():
169
+ if field_name in exclude:
170
+ continue # Skip excluded fields
171
+
172
+ field_type = field.type_
173
+ if issubclass(field_type, BaseModel):
174
+ # Recursively generate schema for nested models
175
+ output[field_name] = generate_simple_schema(field_type, exclude)
176
+ else:
177
+ # Represent the type as a string here
178
+ output[field_name] = {"type": field_type.__name__}
179
+ return output
180
+ else:
181
+ # Non-model type, return a simplified representation
182
+ return {"type": model.__name__}
183
+
184
+
138
185
  def flatten_pydantic_instance(
139
186
  instance: BaseModel,
140
187
  prefix: str = "",
langroid/utils/system.py CHANGED
@@ -1,10 +1,12 @@
1
1
  import getpass
2
2
  import hashlib
3
+ import importlib
3
4
  import inspect
4
5
  import logging
5
6
  import shutil
6
7
  import socket
7
8
  import traceback
9
+ from typing import Any
8
10
 
9
11
  logger = logging.getLogger(__name__)
10
12
 
@@ -15,6 +17,39 @@ DELETION_ALLOWED_PATHS = [
15
17
  ]
16
18
 
17
19
 
20
+ class LazyLoad:
21
+ """Lazy loading of modules or classes."""
22
+
23
+ def __init__(self, import_path: str) -> None:
24
+ self.import_path = import_path
25
+ self._target = None
26
+ self._is_target_loaded = False
27
+
28
+ def _load_target(self) -> None:
29
+ if not self._is_target_loaded:
30
+ try:
31
+ # Attempt to import as a module
32
+ self._target = importlib.import_module(self.import_path) # type: ignore
33
+ except ImportError:
34
+ # If module import fails, attempt to import as a
35
+ # class or function from a module
36
+ module_path, attr_name = self.import_path.rsplit(".", 1)
37
+ module = importlib.import_module(module_path)
38
+ self._target = getattr(module, attr_name)
39
+ self._is_target_loaded = True
40
+
41
+ def __getattr__(self, name: str) -> Any:
42
+ self._load_target()
43
+ return getattr(self._target, name)
44
+
45
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
46
+ self._load_target()
47
+ if callable(self._target):
48
+ return self._target(*args, **kwargs)
49
+ else:
50
+ raise TypeError(f"{self.import_path!r} object is not callable")
51
+
52
+
18
53
  def rmdir(path: str) -> bool:
19
54
  """
20
55
  Remove a directory recursively.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.195
3
+ Version: 0.1.197
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -4,13 +4,13 @@ langroid/agent/base.py,sha256=82nUFCeQ9M71zCFUdNJHihJVytphRWhy81ZyMTNzrXg,35020
4
4
  langroid/agent/batch.py,sha256=8zHdM-863pRD3UoCXUPKEQ4Z4iqjkNVD2xXu1WspBak,6464
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  langroid/agent/callbacks/chainlit.py,sha256=aYuJ8M4VDHr5oymoXL2bpThM7p6P9L45fgJf3MLdkWo,20997
7
- langroid/agent/chat_agent.py,sha256=R1lxMLJ-vXdp9whdj2kn5exdVp3_TKk1Yq64Ux_McQM,38109
8
- langroid/agent/chat_document.py,sha256=MRp2YCy5f3Q_yPoFXVyr1vGu48wz33UGxAUtMn7MJpo,7958
7
+ langroid/agent/chat_agent.py,sha256=QCAA56rObVrnKyCdO5pqklUukxkU4aykoUZzZ2tYvU8,38609
8
+ langroid/agent/chat_document.py,sha256=cxLCFgimnX82sJny3gCDfQGCdRvnerTUfa2zJyd8X28,7963
9
9
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
11
11
  langroid/agent/openai_assistant.py,sha256=yBtxis64XOnxtJzlkwUoTm-wCyvKr4DGo9-laXYMok0,32654
12
12
  langroid/agent/special/__init__.py,sha256=xj4TvQ_oQX_xYPySbhmQAi2CPhuy_3yQPqqwzb4wsc0,943
13
- langroid/agent/special/doc_chat_agent.py,sha256=XGKliuFv1KM58TQsvKeXpD1eAzsRdK1lpr1KVugDQ98,49015
13
+ langroid/agent/special/doc_chat_agent.py,sha256=X-mPVWAleQBQnBy2V1MFV8EsnA1IvUVIU_KhFoNzjLA,48993
14
14
  langroid/agent/special/lance_doc_chat_agent.py,sha256=pAIJchnBOVZnd2fxTteF0KSyZHMzTLKDj8vziTRuUUk,10184
15
15
  langroid/agent/special/lance_rag/__init__.py,sha256=-pq--upe-8vycYoTwxoomBnuUqrcRFUukmW3uBL1cFM,219
16
16
  langroid/agent/special/lance_rag/critic_agent.py,sha256=9izW4keCxVZEqrFOgyVUHD7N1vTXLkRynXYYd1Vpwzw,5785
@@ -19,21 +19,21 @@ langroid/agent/special/lance_rag/lance_tools.py,sha256=WypIS-3ZMDqY_PZEGB2K80-o4
19
19
  langroid/agent/special/lance_rag/query_planner_agent.py,sha256=klWYyMSAaSwLwWBbTUHvZfni-nB7QWfni8vpo3QlfhQ,8043
20
20
  langroid/agent/special/neo4j/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  langroid/agent/special/neo4j/csv_kg_chat.py,sha256=YfuyblH9d307ggzN7fUD2mxLorIdexjVAlZ8uVDZqhY,6394
22
- langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=uJ59qmDZhEx2XSk4d81XZYFBcCa12OcvXJwQ0-LM0pc,12152
22
+ langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=vBr6EQ_eJCYAtqDe-gTSvWHT-jRE_fZOPsGWxuDJe4w,13092
23
23
  langroid/agent/special/neo4j/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- langroid/agent/special/neo4j/utils/system_message.py,sha256=4o71cfyZOimvegUDqXbyZo7KF1hgamA557JMdi6MMtE,1964
24
+ langroid/agent/special/neo4j/utils/system_message.py,sha256=vRpz1P-OYLLiC6OGYYoK6x77yxVzDxMTCEJSsYUIuG4,2242
25
25
  langroid/agent/special/relevance_extractor_agent.py,sha256=uJ1jS_7S_gnJeuOUxEren-IHi1kfVx475B8L4H8RcEM,4795
26
26
  langroid/agent/special/retriever_agent.py,sha256=uu6vqFg85uCVM-_DrXesYe2gH_-WcoHhlsKRlLuZPXk,1867
27
27
  langroid/agent/special/sql/__init__.py,sha256=qUM-b4FfvIt0gYWP7_niyqR3OwVMMkuK2SyqUYWjyxs,207
28
- langroid/agent/special/sql/sql_chat_agent.py,sha256=2PliwwXuYPU17qhtIDgTxsDg73pM7tkUnFCa32dAW70,12541
28
+ langroid/agent/special/sql/sql_chat_agent.py,sha256=q7UuYyVZFF5Z_7mwwyoLM2Y11-HH_FBAY0S18Jht4qM,13689
29
29
  langroid/agent/special/sql/utils/__init__.py,sha256=i8oYoyI4aV1LcYktMOhzi_eYbQ6RVt2e7Dj2bXkCZXc,447
30
30
  langroid/agent/special/sql/utils/description_extractors.py,sha256=GcQ82IhKPInS_3TOqRgD4iRUQt-Ez4WG9vNHsV0V85Q,4494
31
- langroid/agent/special/sql/utils/populate_metadata.py,sha256=zRjw31a1ZXvpx9bcmbtC2mngdHl-bp1ZNHStcPG8_Qk,2712
31
+ langroid/agent/special/sql/utils/populate_metadata.py,sha256=5-tkmMV50iTT4Af_Lu4k4U9szqNIiINg4qpRLKPVNs0,2985
32
32
  langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GSLUYUFASZU8x-ybV67cB68,1885
33
33
  langroid/agent/special/sql/utils/tools.py,sha256=6uB2424SLtmapui9ggcEr0ZTiB6_dL1-JRGgN8RK9Js,1332
34
34
  langroid/agent/special/table_chat_agent.py,sha256=GEUTP-VdtMXq4CcPV80gDQrCEn-ZFb9IhuRMtLN5I1o,9030
35
- langroid/agent/task.py,sha256=i_m7GasCUtGujJsypCnRt3faIZW0CbIaX8FFkIPXoUQ,49496
36
- langroid/agent/tool_message.py,sha256=ngmWdiqMYbjF4Am0hsLyA9zK0Q9QF2ziec6FW0lPD90,7399
35
+ langroid/agent/task.py,sha256=BxMGmwH0ZYbU5lylfQtU9qLMd9D9Qd6qqO1U2V_B0WM,49705
36
+ langroid/agent/tool_message.py,sha256=HXre9B8kVnwcGTv-czO0y-Z0hMDIuf6TKiS16_6djEQ,8207
37
37
  langroid/agent/tools/__init__.py,sha256=q-maq3k2BXhPAU99G0H6-j_ozoRvx15I1RFpPVicQIU,304
38
38
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=lgBFIPGdEffyxFuP6NUqRVBXyqypqHHSQBf-06xWsZE,2460
39
39
  langroid/agent/tools/extract_tool.py,sha256=u5lL9rKBzaLBOrRyLnTAZ97pQ1uxyLP39XsWMnpaZpw,3789
@@ -55,7 +55,7 @@ langroid/embedding_models/clustering.py,sha256=tZWElUqXl9Etqla0FAa7og96iDKgjqWju
55
55
  langroid/embedding_models/models.py,sha256=0bQ8u2ee2ODcopGPusz9WYWI_PjR5Gbdy47qcSU8gCo,4603
56
56
  langroid/language_models/__init__.py,sha256=5L9ndEEC8iLJHjDJmYFTnv6-2-3xsxWUMHcugR8IeDs,821
57
57
  langroid/language_models/azure_openai.py,sha256=ncRCbKooqLVOY-PWQUIo9C3yTuKEFbAwyngXT_M4P7k,5989
58
- langroid/language_models/base.py,sha256=RdXH-BnkFGS8xZTiukdxHTFxqELVSmf546itp9Fa8fs,21008
58
+ langroid/language_models/base.py,sha256=oZskZ9oT-_4kEk1M2515jQ4VOpf31M8NFvPr5knDTEU,21008
59
59
  langroid/language_models/config.py,sha256=5UF3DzO1a-Dfsc3vghE0XGq7g9t_xDsRCsuRiU4dgBg,366
60
60
  langroid/language_models/openai_assistants.py,sha256=9K-DEAL2aSWHeXj2hwCo2RAlK9_1oCPtqX2u1wISCj8,36
61
61
  langroid/language_models/openai_gpt.py,sha256=W2Cxj13qScqnfJCHvZJIqDM9YMNOFAFhnsIuBcnmDac,49327
@@ -71,7 +71,7 @@ langroid/parsing/code-parsing.md,sha256=--cyyNiSZSDlIwcjAV4-shKrSiRe2ytF3AdSoS_h
71
71
  langroid/parsing/code_parser.py,sha256=BbDAzp35wkYQ9U1dpf1ARL0lVyi0tfqEc6_eox2C090,3727
72
72
  langroid/parsing/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  langroid/parsing/document_parser.py,sha256=SEW53fnEsOrsJbVUy9Fq5ygQzF_5UiGB5_Ogkte1u2Y,16697
74
- langroid/parsing/json.py,sha256=OgS3MV1bCegrqop5k2oe79iRj3WJQa_8EZLHeEZ0qSc,2571
74
+ langroid/parsing/json.py,sha256=2eO-0-VAYyBjeUbeB3FNw-8PKUSmnyFWaRb0EzLxoZk,3859
75
75
  langroid/parsing/para_sentence_split.py,sha256=AJBzZojP3zpB-_IMiiHismhqcvkrVBQ3ZINoQyx_bE4,2000
76
76
  langroid/parsing/parser.py,sha256=727QivWlZNlQiRFgkxTZpPoTMqB2yaltOkAGqLZGI_Q,10513
77
77
  langroid/parsing/repo_loader.py,sha256=52jTajXOkq_66NCRKLMNQoGKMJ59H-m2CZB9arMT7Wo,29346
@@ -102,8 +102,8 @@ langroid/utils/logging.py,sha256=R8TN-FqVpwZ4Ajgls9TDMthLvPpQd0QVNXK-PJDj1Z8,391
102
102
  langroid/utils/output/__init__.py,sha256=Z58-2ZKnGpGNaKw_nEjHV_CHTzjMz-WRSRQnazTLrWU,289
103
103
  langroid/utils/output/printing.py,sha256=5EsYB1O4qKhocW19aebOUzK82RD9U5nygbY21yo8gfg,2872
104
104
  langroid/utils/pandas_utils.py,sha256=nSA1tIgOUTkRDn-IKq7HP8XGJcL6bA110LcPfRF7h8I,707
105
- langroid/utils/pydantic_utils.py,sha256=ctv5L6s94_F5CNnNsHybioQKu7Mp_upZcrMWxwmx57o,19827
106
- langroid/utils/system.py,sha256=x9204H1-6EubDe8-9yX87KZSmRCkf0puXQv91QOetF4,3326
105
+ langroid/utils/pydantic_utils.py,sha256=yb-ghaQYL7EIYeiZ0tailvZvAuJZNF7UBXkd3z35OYc,21728
106
+ langroid/utils/system.py,sha256=l-kFqIWkSD9YHOTka02dnihdbnR1mWVrnKSGK3LuEjo,4577
107
107
  langroid/utils/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
108
  langroid/utils/web/login.py,sha256=1iz9eUAHa87vpKIkzwkmFa00avwFWivDSAr7QUhK7U0,2528
109
109
  langroid/utils/web/selenium_login.py,sha256=mYI6EvVmne34N9RajlsxxRqJQJvV-WG4LGp6sEECHPw,1156
@@ -115,7 +115,7 @@ langroid/vector_store/meilisearch.py,sha256=d2huA9P-NoYRuAQ9ZeXJmMKr7ry8u90RUSR2
115
115
  langroid/vector_store/momento.py,sha256=j6Eo6oIDN2fe7lsBOlCXJn3uvvERHHTFL5QJfeREeOM,10044
116
116
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
117
117
  langroid/vector_store/qdrantdb.py,sha256=_egbsP9SWBwmI827EDYSSOqfIQSmwNsmJfFTxrLpWYE,13457
118
- langroid-0.1.195.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
119
- langroid-0.1.195.dist-info/METADATA,sha256=kD6I4XrNgWq9f-hbdt6bIYmYL7jViZvXACLJP1X7A2A,45876
120
- langroid-0.1.195.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
121
- langroid-0.1.195.dist-info/RECORD,,
118
+ langroid-0.1.197.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
119
+ langroid-0.1.197.dist-info/METADATA,sha256=hr_8uh8gl747E4gOZE1EfoK5-B1sxPshYkK18ox7CX8,45876
120
+ langroid-0.1.197.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
121
+ langroid-0.1.197.dist-info/RECORD,,