langroid 0.58.2__py3-none-any.whl → 0.59.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. langroid/agent/base.py +39 -17
  2. langroid/agent/base.py-e +2216 -0
  3. langroid/agent/callbacks/chainlit.py +2 -1
  4. langroid/agent/chat_agent.py +73 -55
  5. langroid/agent/chat_agent.py-e +2086 -0
  6. langroid/agent/chat_document.py +7 -7
  7. langroid/agent/chat_document.py-e +513 -0
  8. langroid/agent/openai_assistant.py +9 -9
  9. langroid/agent/openai_assistant.py-e +882 -0
  10. langroid/agent/special/arangodb/arangodb_agent.py +10 -18
  11. langroid/agent/special/arangodb/arangodb_agent.py-e +648 -0
  12. langroid/agent/special/arangodb/tools.py +3 -3
  13. langroid/agent/special/doc_chat_agent.py +16 -14
  14. langroid/agent/special/lance_rag/critic_agent.py +2 -2
  15. langroid/agent/special/lance_rag/query_planner_agent.py +4 -4
  16. langroid/agent/special/lance_tools.py +6 -5
  17. langroid/agent/special/lance_tools.py-e +61 -0
  18. langroid/agent/special/neo4j/neo4j_chat_agent.py +3 -7
  19. langroid/agent/special/neo4j/neo4j_chat_agent.py-e +430 -0
  20. langroid/agent/special/relevance_extractor_agent.py +1 -1
  21. langroid/agent/special/sql/sql_chat_agent.py +11 -3
  22. langroid/agent/task.py +9 -87
  23. langroid/agent/task.py-e +2418 -0
  24. langroid/agent/tool_message.py +33 -17
  25. langroid/agent/tool_message.py-e +400 -0
  26. langroid/agent/tools/file_tools.py +4 -2
  27. langroid/agent/tools/file_tools.py-e +234 -0
  28. langroid/agent/tools/mcp/fastmcp_client.py +19 -6
  29. langroid/agent/tools/mcp/fastmcp_client.py-e +584 -0
  30. langroid/agent/tools/orchestration.py +22 -17
  31. langroid/agent/tools/orchestration.py-e +301 -0
  32. langroid/agent/tools/recipient_tool.py +3 -3
  33. langroid/agent/tools/task_tool.py +22 -16
  34. langroid/agent/tools/task_tool.py-e +249 -0
  35. langroid/agent/xml_tool_message.py +90 -35
  36. langroid/agent/xml_tool_message.py-e +392 -0
  37. langroid/cachedb/base.py +1 -1
  38. langroid/embedding_models/base.py +2 -2
  39. langroid/embedding_models/models.py +3 -7
  40. langroid/embedding_models/models.py-e +563 -0
  41. langroid/exceptions.py +4 -1
  42. langroid/language_models/azure_openai.py +2 -2
  43. langroid/language_models/azure_openai.py-e +134 -0
  44. langroid/language_models/base.py +6 -4
  45. langroid/language_models/base.py-e +812 -0
  46. langroid/language_models/client_cache.py +64 -0
  47. langroid/language_models/config.py +2 -4
  48. langroid/language_models/config.py-e +18 -0
  49. langroid/language_models/model_info.py +9 -1
  50. langroid/language_models/model_info.py-e +483 -0
  51. langroid/language_models/openai_gpt.py +119 -20
  52. langroid/language_models/openai_gpt.py-e +2280 -0
  53. langroid/language_models/provider_params.py +3 -22
  54. langroid/language_models/provider_params.py-e +153 -0
  55. langroid/mytypes.py +11 -4
  56. langroid/mytypes.py-e +132 -0
  57. langroid/parsing/code_parser.py +1 -1
  58. langroid/parsing/file_attachment.py +1 -1
  59. langroid/parsing/file_attachment.py-e +246 -0
  60. langroid/parsing/md_parser.py +14 -4
  61. langroid/parsing/md_parser.py-e +574 -0
  62. langroid/parsing/parser.py +22 -7
  63. langroid/parsing/parser.py-e +410 -0
  64. langroid/parsing/repo_loader.py +3 -1
  65. langroid/parsing/repo_loader.py-e +812 -0
  66. langroid/parsing/search.py +1 -1
  67. langroid/parsing/url_loader.py +17 -51
  68. langroid/parsing/url_loader.py-e +683 -0
  69. langroid/parsing/urls.py +5 -4
  70. langroid/parsing/urls.py-e +279 -0
  71. langroid/prompts/prompts_config.py +1 -1
  72. langroid/pydantic_v1/__init__.py +45 -6
  73. langroid/pydantic_v1/__init__.py-e +36 -0
  74. langroid/pydantic_v1/main.py +11 -4
  75. langroid/pydantic_v1/main.py-e +11 -0
  76. langroid/utils/configuration.py +13 -11
  77. langroid/utils/configuration.py-e +141 -0
  78. langroid/utils/constants.py +1 -1
  79. langroid/utils/constants.py-e +32 -0
  80. langroid/utils/globals.py +21 -5
  81. langroid/utils/globals.py-e +49 -0
  82. langroid/utils/html_logger.py +2 -1
  83. langroid/utils/html_logger.py-e +825 -0
  84. langroid/utils/object_registry.py +1 -1
  85. langroid/utils/object_registry.py-e +66 -0
  86. langroid/utils/pydantic_utils.py +55 -28
  87. langroid/utils/pydantic_utils.py-e +602 -0
  88. langroid/utils/types.py +2 -2
  89. langroid/utils/types.py-e +113 -0
  90. langroid/vector_store/base.py +3 -3
  91. langroid/vector_store/lancedb.py +5 -5
  92. langroid/vector_store/lancedb.py-e +404 -0
  93. langroid/vector_store/meilisearch.py +2 -2
  94. langroid/vector_store/pineconedb.py +4 -4
  95. langroid/vector_store/pineconedb.py-e +427 -0
  96. langroid/vector_store/postgres.py +1 -1
  97. langroid/vector_store/qdrantdb.py +3 -3
  98. langroid/vector_store/weaviatedb.py +1 -1
  99. {langroid-0.58.2.dist-info → langroid-0.59.0b1.dist-info}/METADATA +3 -2
  100. langroid-0.59.0b1.dist-info/RECORD +181 -0
  101. langroid/agent/special/doc_chat_task.py +0 -0
  102. langroid/mcp/__init__.py +0 -1
  103. langroid/mcp/server/__init__.py +0 -1
  104. langroid-0.58.2.dist-info/RECORD +0 -145
  105. {langroid-0.58.2.dist-info → langroid-0.59.0b1.dist-info}/WHEEL +0 -0
  106. {langroid-0.58.2.dist-info → langroid-0.59.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -13,8 +13,8 @@ class AQLRetrievalTool(ToolMessage):
13
13
  """
14
14
  aql_query: str
15
15
 
16
- _max_result_tokens = 500
17
- _max_retained_tokens = 200
16
+ _max_result_tokens: int = 500
17
+ _max_retained_tokens: int = 200
18
18
 
19
19
  @classmethod
20
20
  def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
@@ -101,7 +101,7 @@ class ArangoSchemaTool(ToolMessage):
101
101
  properties: bool = True
102
102
  collections: List[str] | None = None
103
103
 
104
- _max_result_tokens = 500
104
+ _max_result_tokens: int = 500
105
105
 
106
106
 
107
107
  arango_schema_tool_name = ArangoSchemaTool.default_value("request")
@@ -388,14 +388,14 @@ class DocChatAgent(ChatAgent):
388
388
  p: (
389
389
  m
390
390
  if isinstance(m, dict)
391
- else (isinstance(m, DocMetaData) and m.dict())
391
+ else (isinstance(m, DocMetaData) and m.model_dump())
392
392
  ) # appease mypy
393
393
  for p, m in zip(idxs, metadata)
394
394
  }
395
395
  elif isinstance(metadata, dict):
396
396
  idx2meta = {p: metadata for p in idxs}
397
397
  else:
398
- idx2meta = {p: metadata.dict() for p in idxs}
398
+ idx2meta = {p: metadata.model_dump() for p in idxs}
399
399
  urls_meta = {u: idx2meta[u] for u in url_idxs}
400
400
  paths_meta = {p: idx2meta[p] for p in path_idxs}
401
401
  docs: List[Document] = []
@@ -412,7 +412,7 @@ class DocChatAgent(ChatAgent):
412
412
  # update metadata of each doc with meta
413
413
  for d in url_docs:
414
414
  orig_source = d.metadata.source
415
- d.metadata = d.metadata.copy(update=meta)
415
+ d.metadata = d.metadata.model_copy(update=meta)
416
416
  d.metadata.source = _append_metadata_source(
417
417
  orig_source, meta.get("source", "")
418
418
  )
@@ -429,7 +429,7 @@ class DocChatAgent(ChatAgent):
429
429
  # update metadata of each doc with meta
430
430
  for d in path_docs:
431
431
  orig_source = d.metadata.source
432
- d.metadata = d.metadata.copy(update=meta)
432
+ d.metadata = d.metadata.model_copy(update=meta)
433
433
  d.metadata.source = _append_metadata_source(
434
434
  orig_source, meta.get("source", "")
435
435
  )
@@ -474,22 +474,22 @@ class DocChatAgent(ChatAgent):
474
474
  if isinstance(metadata, list) and len(metadata) > 0:
475
475
  for d, m in zip(docs, metadata):
476
476
  orig_source = d.metadata.source
477
- m_dict = m if isinstance(m, dict) else m.dict() # type: ignore
478
- d.metadata = d.metadata.copy(update=m_dict) # type: ignore
477
+ m_dict = m if isinstance(m, dict) else m.model_dump() # type: ignore
478
+ d.metadata = d.metadata.model_copy(update=m_dict) # type: ignore
479
479
  d.metadata.source = _append_metadata_source(
480
480
  orig_source, m_dict.get("source", "")
481
481
  )
482
482
  elif isinstance(metadata, dict):
483
483
  for d in docs:
484
484
  orig_source = d.metadata.source
485
- d.metadata = d.metadata.copy(update=metadata)
485
+ d.metadata = d.metadata.model_copy(update=metadata)
486
486
  d.metadata.source = _append_metadata_source(
487
487
  orig_source, metadata.get("source", "")
488
488
  )
489
489
  elif isinstance(metadata, DocMetaData):
490
490
  for d in docs:
491
491
  orig_source = d.metadata.source
492
- d.metadata = d.metadata.copy(update=metadata.dict())
492
+ d.metadata = d.metadata.model_copy(update=metadata.model_dump())
493
493
  d.metadata.source = _append_metadata_source(
494
494
  orig_source, metadata.source
495
495
  )
@@ -1025,10 +1025,12 @@ class DocChatAgent(ChatAgent):
1025
1025
  f"{doc.content}{enrichment_config.delimiter}{enrichment}"
1026
1026
  )
1027
1027
 
1028
- new_doc = doc.copy(
1028
+ new_doc = doc.model_copy(
1029
1029
  update={
1030
1030
  "content": combined_content,
1031
- "metadata": doc.metadata.copy(update={"has_enrichment": True}),
1031
+ "metadata": doc.metadata.model_copy(
1032
+ update={"has_enrichment": True}
1033
+ ),
1032
1034
  }
1033
1035
  )
1034
1036
  augmented_docs.append(new_doc)
@@ -1212,7 +1214,7 @@ class DocChatAgent(ChatAgent):
1212
1214
  return docs_scores
1213
1215
  if len(docs_scores) == 0:
1214
1216
  return []
1215
- if set(docs_scores[0][0].__fields__) != {"content", "metadata"}:
1217
+ if set(docs_scores[0][0].model_fields) != {"content", "metadata"}:
1216
1218
  # Do not add context window when there are other fields besides just
1217
1219
  # content and metadata, since we do not know how to set those other fields
1218
1220
  # for newly created docs with combined content.
@@ -1515,7 +1517,7 @@ class DocChatAgent(ChatAgent):
1515
1517
  delimiter = self.config.chunk_enrichment_config.delimiter
1516
1518
  return [
1517
1519
  (
1518
- doc.copy(update={"content": doc.content.split(delimiter)[0]})
1520
+ doc.model_copy(update={"content": doc.content.split(delimiter)[0]})
1519
1521
  if doc.content and getattr(doc.metadata, "has_enrichment", False)
1520
1522
  else doc
1521
1523
  )
@@ -1572,7 +1574,7 @@ class DocChatAgent(ChatAgent):
1572
1574
  for p, e in zip(passages, extracts):
1573
1575
  if e == NO_ANSWER or len(e) == 0:
1574
1576
  continue
1575
- p_copy = p.copy()
1577
+ p_copy = p.model_copy()
1576
1578
  p_copy.content = e
1577
1579
  passage_extracts.append(p_copy)
1578
1580
 
@@ -1607,7 +1609,7 @@ class DocChatAgent(ChatAgent):
1607
1609
  sender=Entity.LLM,
1608
1610
  )
1609
1611
  # copy metadata from first doc, unclear what to do here.
1610
- meta.update(extracts[0].metadata)
1612
+ meta.update(extracts[0].metadata.model_dump())
1611
1613
  return ChatDocument(
1612
1614
  content="\n\n".join([e.content for e in extracts]),
1613
1615
  metadata=ChatDocMetaData(**meta), # type: ignore
@@ -33,8 +33,8 @@ logger = logging.getLogger(__name__)
33
33
 
34
34
 
35
35
  class QueryPlanCriticConfig(LanceQueryPlanAgentConfig):
36
- name = "QueryPlanCritic"
37
- system_message = f"""
36
+ name: str = "QueryPlanCritic"
37
+ system_message: str = f"""
38
38
  You are an expert at carefully planning a query that needs to be answered
39
39
  based on a large collection of documents. These docs have a special `content` field
40
40
  and additional FILTERABLE fields in the SCHEMA below, along with the
@@ -37,11 +37,11 @@ class LanceQueryPlanAgentConfig(ChatAgentConfig):
37
37
  critic_name: str = "QueryPlanCritic"
38
38
  doc_agent_name: str = "LanceRAG"
39
39
  doc_schema: str = ""
40
- use_tools = False
40
+ use_tools: bool = False
41
41
  max_retries: int = 5 # max number of retries for query plan
42
- use_functions_api = True
42
+ use_functions_api: bool = True
43
43
 
44
- system_message = """
44
+ system_message: str = """
45
45
  You will receive a QUERY, to be answered based on an EXTREMELY LARGE collection
46
46
  of documents you DO NOT have access to, but your ASSISTANT does.
47
47
  You only know that these documents have a special `content` field
@@ -174,7 +174,7 @@ class LanceQueryPlanAgent(ChatAgent):
174
174
 
175
175
  # (a) insert `recipient` in the QueryPlanTool:
176
176
  # QPWithRecipient = QueryPlanTool.require_recipient()
177
- # qp = QPWithRecipient(**msg.dict(), recipient=self.config.doc_agent_name)
177
+ # qp = QPWithRecipient(**msg.model_dump(), recipient=self.config.doc_agent_name)
178
178
  # return qp
179
179
  #
180
180
  # OR
@@ -1,7 +1,8 @@
1
1
  import logging
2
2
 
3
+ from pydantic import BaseModel, Field
4
+
3
5
  from langroid.agent.tool_message import ToolMessage
4
- from langroid.pydantic_v1 import BaseModel, Field
5
6
 
6
7
  logger = logging.getLogger(__name__)
7
8
 
@@ -19,8 +20,8 @@ class QueryPlan(BaseModel):
19
20
 
20
21
 
21
22
  class QueryPlanTool(ToolMessage):
22
- request = "query_plan" # the agent method name that handles this tool
23
- purpose = """
23
+ request: str = "query_plan" # the agent method name that handles this tool
24
+ purpose: str = """
24
25
  Given a user's query, generate a query <plan> consisting of:
25
26
  - <original_query> - the original query for reference
26
27
  - <filter> condition if needed (or empty string if no filter is needed)
@@ -52,8 +53,8 @@ class QueryPlanAnswerTool(ToolMessage):
52
53
 
53
54
 
54
55
  class QueryPlanFeedbackTool(ToolMessage):
55
- request = "query_plan_feedback"
56
- purpose = """
56
+ request: str = "query_plan_feedback"
57
+ purpose: str = """
57
58
  To give <feedback> regarding the query plan,
58
59
  along with a <suggested_fix> if any (empty string if no fix is suggested).
59
60
  """
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ from langroid.agent.tool_message import ToolMessage
4
+ from pydantic import BaseModel, Field
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class QueryPlan(BaseModel):
10
+ original_query: str = Field(..., description="The original query for reference")
11
+ query: str = Field(..., description="A possibly NON-EMPTY rephrased query")
12
+ filter: str = Field(
13
+ "",
14
+ description="Filter condition if needed (or empty if no filter is needed)",
15
+ )
16
+ dataframe_calc: str = Field(
17
+ "", description="An optional Pandas-dataframe calculation/aggregation string"
18
+ )
19
+
20
+
21
+ class QueryPlanTool(ToolMessage):
22
+ request = "query_plan" # the agent method name that handles this tool
23
+ purpose = """
24
+ Given a user's query, generate a query <plan> consisting of:
25
+ - <original_query> - the original query for reference
26
+ - <filter> condition if needed (or empty string if no filter is needed)
27
+ - <query> - a possibly NON-EMPTY rephrased query that can be used to match the
28
+ CONTENT of the documents
29
+ (can be same as <original_query> if no rephrasing is needed)
30
+ - <dataframe_calc> - a Pandas-dataframe calculation/aggregation string
31
+ that can be used to calculate the answer
32
+ (or empty string if no calculation is needed).
33
+ """
34
+ plan: QueryPlan
35
+
36
+
37
+ class AnswerTool(ToolMessage):
38
+ """Wrapper for answer from LanceDocChatAgent"""
39
+
40
+ purpose: str = "To package the answer from LanceDocChatAgent"
41
+ request: str = "answer_tool"
42
+ answer: str
43
+
44
+
45
+ class QueryPlanAnswerTool(ToolMessage):
46
+ request: str = "query_plan_answer" # the agent method name that handles this tool
47
+ purpose: str = """
48
+ Assemble query <plan> and <answer>
49
+ """
50
+ plan: QueryPlan
51
+ answer: str = Field(..., description="The answer received from the assistant")
52
+
53
+
54
+ class QueryPlanFeedbackTool(ToolMessage):
55
+ request = "query_plan_feedback"
56
+ purpose = """
57
+ To give <feedback> regarding the query plan,
58
+ along with a <suggested_fix> if any (empty string if no fix is suggested).
59
+ """
60
+ feedback: str
61
+ suggested_fix: str
@@ -1,11 +1,11 @@
1
1
  import logging
2
2
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
3
3
 
4
+ from pydantic import BaseModel
5
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
6
  from rich import print
5
7
  from rich.console import Console
6
8
 
7
- from langroid.pydantic_v1 import BaseModel, BaseSettings
8
-
9
9
  if TYPE_CHECKING:
10
10
  import neo4j
11
11
 
@@ -47,11 +47,7 @@ class Neo4jSettings(BaseSettings):
47
47
  password: str = ""
48
48
  database: str = ""
49
49
 
50
- class Config:
51
- # This enables the use of environment variables to set the settings,
52
- # e.g. NEO4J_URI, NEO4J_USERNAME, etc.,
53
- # which can either be set in a .env file or in the shell via export cmds.
54
- env_prefix = "NEO4J_"
50
+ model_config = SettingsConfigDict(env_prefix="NEO4J_")
55
51
 
56
52
 
57
53
  class QueryResult(BaseModel):