langroid 0.1.195__tar.gz → 0.1.197__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langroid-0.1.195 → langroid-0.1.197}/PKG-INFO +1 -1
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/chat_agent.py +10 -2
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/chat_document.py +1 -1
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/doc_chat_agent.py +4 -3
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/neo4j/neo4j_chat_agent.py +35 -8
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/neo4j/utils/system_message.py +14 -7
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/sql_chat_agent.py +40 -9
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/populate_metadata.py +25 -18
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/task.py +7 -3
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tool_message.py +28 -6
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/base.py +2 -2
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/json.py +38 -2
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/pydantic_utils.py +47 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/system.py +35 -0
- {langroid-0.1.195 → langroid-0.1.197}/pyproject.toml +1 -1
- {langroid-0.1.195 → langroid-0.1.197}/LICENSE +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/README.md +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/base.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/batch.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/callbacks/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/callbacks/chainlit.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/helpers.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/junk +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/openai_assistant.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/lance_tools.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/neo4j/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/relevance_extractor_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/retriever_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/system_message.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/tools.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/table_chat_agent.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/extract_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/generator_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/google_search_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/metaphor_search_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/recipient_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/run_python_code.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/sciphi_search_rag_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent/tools/segment_extract_tool.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/agent_config.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/cachedb/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/cachedb/base.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/cachedb/momento_cachedb.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/cachedb/redis_cachedb.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/embedding_models/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/embedding_models/base.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/embedding_models/clustering.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/embedding_models/models.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/azure_openai.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/config.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/openai_assistants.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/openai_gpt.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/base.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/utils.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/mytypes.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/agent_chats.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/code-parsing.md +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/code_parser.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/config.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/document_parser.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/para_sentence_split.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/parser.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/repo_loader.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/search.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/spider.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/table_loader.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/url_loader.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/url_loader_cookies.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/urls.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/utils.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/parsing/web_search.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/dialog.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/prompts_config.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/templates.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/prompts/transforms.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/algorithms/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/algorithms/graph.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/configuration.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/constants.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/docker.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/globals.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/llms/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/llms/strings.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/logging.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/output/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/output/printing.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/pandas_utils.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/web/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/web/login.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/utils/web/selenium_login.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/__init__.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/base.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/chromadb.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/lancedb.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/meilisearch.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/momento.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/qdrant_cloud.py +0 -0
- {langroid-0.1.195 → langroid-0.1.197}/langroid/vector_store/qdrantdb.py +0 -0
@@ -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
|
)
|
@@ -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 =
|
1226
|
-
return summary
|
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
|
34
|
+
class CypherRetrievalTool(ToolMessage):
|
35
35
|
request: str = "retrieval_query"
|
36
|
-
purpose: str = """Use this tool to send the
|
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:
|
242
|
-
"""
|
243
|
-
Handle a
|
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 (
|
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(
|
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
|
13
|
-
`retrieval_query` tool/function-call to
|
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
|
-
|
16
|
-
|
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 `
|
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.
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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]]]
|
@@ -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
|
789
|
-
recipient
|
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}
|
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
|
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 =
|
43
|
-
completion_context_length: int =
|
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
|
@@ -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
|
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
|
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 = "",
|
@@ -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.
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/lance_rag/query_planner_agent.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{langroid-0.1.195 → langroid-0.1.197}/langroid/agent/special/sql/utils/description_extractors.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/hf_formatter.py
RENAMED
File without changes
|
{langroid-0.1.195 → langroid-0.1.197}/langroid/language_models/prompt_formatter/llama2_formatter.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|