langroid 0.58.3__py3-none-any.whl → 0.59.0__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 (63) hide show
  1. langroid/agent/base.py +39 -17
  2. langroid/agent/callbacks/chainlit.py +2 -1
  3. langroid/agent/chat_agent.py +73 -55
  4. langroid/agent/chat_document.py +7 -7
  5. langroid/agent/done_sequence_parser.py +46 -11
  6. langroid/agent/openai_assistant.py +9 -9
  7. langroid/agent/special/arangodb/arangodb_agent.py +10 -18
  8. langroid/agent/special/arangodb/tools.py +3 -3
  9. langroid/agent/special/doc_chat_agent.py +16 -14
  10. langroid/agent/special/lance_rag/critic_agent.py +2 -2
  11. langroid/agent/special/lance_rag/query_planner_agent.py +4 -4
  12. langroid/agent/special/lance_tools.py +6 -5
  13. langroid/agent/special/neo4j/neo4j_chat_agent.py +3 -7
  14. langroid/agent/special/relevance_extractor_agent.py +1 -1
  15. langroid/agent/special/sql/sql_chat_agent.py +11 -3
  16. langroid/agent/task.py +53 -94
  17. langroid/agent/tool_message.py +33 -17
  18. langroid/agent/tools/file_tools.py +4 -2
  19. langroid/agent/tools/mcp/fastmcp_client.py +19 -6
  20. langroid/agent/tools/orchestration.py +22 -17
  21. langroid/agent/tools/recipient_tool.py +3 -3
  22. langroid/agent/tools/task_tool.py +22 -16
  23. langroid/agent/xml_tool_message.py +90 -35
  24. langroid/cachedb/base.py +1 -1
  25. langroid/embedding_models/base.py +2 -2
  26. langroid/embedding_models/models.py +3 -7
  27. langroid/exceptions.py +4 -1
  28. langroid/language_models/azure_openai.py +2 -2
  29. langroid/language_models/base.py +6 -4
  30. langroid/language_models/config.py +2 -4
  31. langroid/language_models/model_info.py +9 -1
  32. langroid/language_models/openai_gpt.py +53 -18
  33. langroid/language_models/provider_params.py +3 -22
  34. langroid/mytypes.py +11 -4
  35. langroid/parsing/code_parser.py +1 -1
  36. langroid/parsing/file_attachment.py +1 -1
  37. langroid/parsing/md_parser.py +14 -4
  38. langroid/parsing/parser.py +22 -7
  39. langroid/parsing/repo_loader.py +3 -1
  40. langroid/parsing/search.py +1 -1
  41. langroid/parsing/url_loader.py +17 -51
  42. langroid/parsing/urls.py +5 -4
  43. langroid/prompts/prompts_config.py +1 -1
  44. langroid/pydantic_v1/__init__.py +61 -4
  45. langroid/pydantic_v1/main.py +10 -4
  46. langroid/utils/configuration.py +13 -11
  47. langroid/utils/constants.py +1 -1
  48. langroid/utils/globals.py +21 -5
  49. langroid/utils/html_logger.py +2 -1
  50. langroid/utils/object_registry.py +1 -1
  51. langroid/utils/pydantic_utils.py +55 -28
  52. langroid/utils/types.py +2 -2
  53. langroid/vector_store/base.py +3 -3
  54. langroid/vector_store/lancedb.py +5 -5
  55. langroid/vector_store/meilisearch.py +2 -2
  56. langroid/vector_store/pineconedb.py +4 -4
  57. langroid/vector_store/postgres.py +1 -1
  58. langroid/vector_store/qdrantdb.py +3 -3
  59. langroid/vector_store/weaviatedb.py +1 -1
  60. {langroid-0.58.3.dist-info → langroid-0.59.0.dist-info}/METADATA +3 -2
  61. {langroid-0.58.3.dist-info → langroid-0.59.0.dist-info}/RECORD +63 -63
  62. {langroid-0.58.3.dist-info → langroid-0.59.0.dist-info}/WHEEL +0 -0
  63. {langroid-0.58.3.dist-info → langroid-0.59.0.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,8 @@ from arango.client import ArangoClient
8
8
  from arango.database import StandardDatabase
9
9
  from arango.exceptions import ArangoError, ServerConnectionError
10
10
  from numpy import ceil
11
+ from pydantic import BaseModel, ConfigDict
12
+ from pydantic_settings import BaseSettings, SettingsConfigDict
11
13
  from rich import print
12
14
  from rich.console import Console
13
15
 
@@ -31,7 +33,6 @@ from langroid.agent.special.arangodb.utils import count_fields, trim_schema
31
33
  from langroid.agent.tools.orchestration import DoneTool, ForwardTool
32
34
  from langroid.exceptions import LangroidImportError
33
35
  from langroid.mytypes import Entity
34
- from langroid.pydantic_v1 import BaseModel, BaseSettings
35
36
  from langroid.utils.constants import SEND_TO
36
37
 
37
38
  logger = logging.getLogger(__name__)
@@ -49,8 +50,7 @@ class ArangoSettings(BaseSettings):
49
50
  password: str = ""
50
51
  database: str = ""
51
52
 
52
- class Config:
53
- env_prefix = "ARANGO_"
53
+ model_config = SettingsConfigDict(env_prefix="ARANGO_")
54
54
 
55
55
 
56
56
  class QueryResult(BaseModel):
@@ -68,22 +68,14 @@ class QueryResult(BaseModel):
68
68
  ]
69
69
  ] = None
70
70
 
71
- class Config:
72
- # Allow arbitrary types for flexibility
73
- arbitrary_types_allowed = True
74
-
75
- # Handle JSON serialization of special types
76
- json_encoders = {
77
- # Add custom encoders if needed, e.g.:
71
+ model_config = ConfigDict(
72
+ arbitrary_types_allowed=True,
73
+ json_encoders={
78
74
  datetime.datetime: lambda v: v.isoformat(),
79
- # Could add others for specific ArangoDB types
80
- }
81
-
82
- # Validate all assignments
83
- validate_assignment = True
84
-
85
- # Frozen=True if we want immutability
86
- frozen = False
75
+ },
76
+ validate_assignment=True,
77
+ frozen=False,
78
+ )
87
79
 
88
80
 
89
81
  class ArangoChatAgentConfig(ChatAgentConfig):
@@ -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
  """
@@ -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):
@@ -26,7 +26,7 @@ class RelevanceExtractorAgentConfig(ChatAgentConfig):
26
26
  llm: LLMConfig | None = OpenAIGPTConfig()
27
27
  segment_length: int = 1 # number of sentences per segment
28
28
  query: str = "" # query for relevance extraction
29
- system_message = """
29
+ system_message: str = """
30
30
  The user will give you a PASSAGE containing segments numbered as
31
31
  <#1#>, <#2#>, <#3#>, etc.,
32
32
  followed by a QUERY. Extract ONLY the segment-numbers from
@@ -184,7 +184,7 @@ class SQLChatAgent(ChatAgent):
184
184
  if self.config.use_helper:
185
185
  # helper_config.system_message is now the fully-populated sys msg of
186
186
  # the main SQLAgent.
187
- self.helper_config = self.config.copy()
187
+ self.helper_config = self.config.model_copy()
188
188
  self.helper_config.is_helper = True
189
189
  self.helper_config.use_helper = False
190
190
  self.helper_config.chat_mode = False
@@ -271,8 +271,16 @@ class SQLChatAgent(ChatAgent):
271
271
 
272
272
  def _init_tools(self) -> None:
273
273
  """Initialize sys msg and tools."""
274
- RunQueryTool._max_retained_tokens = self.config.max_retained_tokens
275
- self.enable_message([RunQueryTool, ForwardTool])
274
+ # Create a custom RunQueryTool class with the desired max_retained_tokens
275
+ if self.config.max_retained_tokens is not None:
276
+
277
+ class CustomRunQueryTool(RunQueryTool):
278
+ _max_retained_tokens = self.config.max_retained_tokens
279
+
280
+ self.enable_message([CustomRunQueryTool, ForwardTool])
281
+ else:
282
+ self.enable_message([RunQueryTool, ForwardTool])
283
+
276
284
  if self.config.use_schema_tools:
277
285
  self._enable_schema_tools()
278
286
  if not self.config.chat_mode: