langroid 0.20.0__tar.gz → 0.20.1__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.
Files changed (149) hide show
  1. {langroid-0.20.0 → langroid-0.20.1}/PKG-INFO +1 -1
  2. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/base.py +10 -1
  3. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/arangodb/arangodb_agent.py +204 -69
  4. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/arangodb/system_messages.py +33 -7
  5. langroid-0.20.1/langroid/agent/special/arangodb/tools.py +102 -0
  6. langroid-0.20.1/langroid/agent/special/arangodb/utils.py +36 -0
  7. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/task.py +77 -55
  8. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/orchestration.py +7 -7
  9. {langroid-0.20.0 → langroid-0.20.1}/pyproject.toml +1 -1
  10. langroid-0.20.0/langroid/agent/special/arangodb/tools.py +0 -39
  11. {langroid-0.20.0 → langroid-0.20.1}/LICENSE +0 -0
  12. {langroid-0.20.0 → langroid-0.20.1}/README.md +0 -0
  13. {langroid-0.20.0 → langroid-0.20.1}/langroid/__init__.py +0 -0
  14. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/__init__.py +0 -0
  15. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/batch.py +0 -0
  16. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/callbacks/__init__.py +0 -0
  17. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/callbacks/chainlit.py +0 -0
  18. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/chat_agent.py +0 -0
  19. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/chat_document.py +0 -0
  20. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/helpers.py +0 -0
  21. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/junk +0 -0
  22. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/openai_assistant.py +0 -0
  23. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/__init__.py +0 -0
  24. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/arangodb/__init__.py +0 -0
  25. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/doc_chat_agent.py +0 -0
  26. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  27. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_rag/__init__.py +0 -0
  28. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  29. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  30. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  31. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/lance_tools.py +0 -0
  32. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/neo4j/__init__.py +0 -0
  33. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  34. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  35. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/neo4j/system_messages.py +0 -0
  36. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/neo4j/tools.py +0 -0
  37. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  38. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/retriever_agent.py +0 -0
  39. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/__init__.py +0 -0
  40. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  41. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/utils/__init__.py +0 -0
  42. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  43. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  44. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/utils/system_message.py +0 -0
  45. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/sql/utils/tools.py +0 -0
  46. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/special/table_chat_agent.py +0 -0
  47. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/structured_message.py +0 -0
  48. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tool_message.py +0 -0
  49. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/__init__.py +0 -0
  50. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  51. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/file_tools.py +0 -0
  52. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/google_search_tool.py +0 -0
  53. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  54. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/recipient_tool.py +0 -0
  55. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/retrieval_tool.py +0 -0
  56. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/rewind_tool.py +0 -0
  57. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/tools/segment_extract_tool.py +0 -0
  58. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/typed_task.py +0 -0
  59. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent/xml_tool_message.py +0 -0
  60. {langroid-0.20.0 → langroid-0.20.1}/langroid/agent_config.py +0 -0
  61. {langroid-0.20.0 → langroid-0.20.1}/langroid/cachedb/__init__.py +0 -0
  62. {langroid-0.20.0 → langroid-0.20.1}/langroid/cachedb/base.py +0 -0
  63. {langroid-0.20.0 → langroid-0.20.1}/langroid/cachedb/momento_cachedb.py +0 -0
  64. {langroid-0.20.0 → langroid-0.20.1}/langroid/cachedb/redis_cachedb.py +0 -0
  65. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/__init__.py +0 -0
  66. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/base.py +0 -0
  67. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/clustering.py +0 -0
  68. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/models.py +0 -0
  69. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/protoc/__init__.py +0 -0
  70. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  71. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  72. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  73. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  74. {langroid-0.20.0 → langroid-0.20.1}/langroid/embedding_models/remote_embeds.py +0 -0
  75. {langroid-0.20.0 → langroid-0.20.1}/langroid/exceptions.py +0 -0
  76. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/.chainlit/config.toml +0 -0
  77. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  78. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/__init__.py +0 -0
  79. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/azure_openai.py +0 -0
  80. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/base.py +0 -0
  81. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/config.py +0 -0
  82. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/mock_lm.py +0 -0
  83. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/openai_gpt.py +0 -0
  84. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  85. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/prompt_formatter/base.py +0 -0
  86. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  87. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  88. {langroid-0.20.0 → langroid-0.20.1}/langroid/language_models/utils.py +0 -0
  89. {langroid-0.20.0 → langroid-0.20.1}/langroid/mytypes.py +0 -0
  90. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/__init__.py +0 -0
  91. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/agent_chats.py +0 -0
  92. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/code-parsing.md +0 -0
  93. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/code_parser.py +0 -0
  94. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/config.py +0 -0
  95. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/document_parser.py +0 -0
  96. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/image_text.py +0 -0
  97. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/para_sentence_split.py +0 -0
  98. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/parse_json.py +0 -0
  99. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/parser.py +0 -0
  100. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/repo_loader.py +0 -0
  101. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/routing.py +0 -0
  102. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/search.py +0 -0
  103. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/spider.py +0 -0
  104. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/table_loader.py +0 -0
  105. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/url_loader.py +0 -0
  106. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/url_loader_cookies.py +0 -0
  107. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/urls.py +0 -0
  108. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/utils.py +0 -0
  109. {langroid-0.20.0 → langroid-0.20.1}/langroid/parsing/web_search.py +0 -0
  110. {langroid-0.20.0 → langroid-0.20.1}/langroid/prompts/__init__.py +0 -0
  111. {langroid-0.20.0 → langroid-0.20.1}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  112. {langroid-0.20.0 → langroid-0.20.1}/langroid/prompts/dialog.py +0 -0
  113. {langroid-0.20.0 → langroid-0.20.1}/langroid/prompts/prompts_config.py +0 -0
  114. {langroid-0.20.0 → langroid-0.20.1}/langroid/prompts/templates.py +0 -0
  115. {langroid-0.20.0 → langroid-0.20.1}/langroid/py.typed +0 -0
  116. {langroid-0.20.0 → langroid-0.20.1}/langroid/pydantic_v1/__init__.py +0 -0
  117. {langroid-0.20.0 → langroid-0.20.1}/langroid/pydantic_v1/main.py +0 -0
  118. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/.chainlit/config.toml +0 -0
  119. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  120. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/__init__.py +0 -0
  121. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/algorithms/__init__.py +0 -0
  122. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/algorithms/graph.py +0 -0
  123. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/configuration.py +0 -0
  124. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/constants.py +0 -0
  125. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/docker.py +0 -0
  126. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/git_utils.py +0 -0
  127. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/globals.py +0 -0
  128. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/llms/__init__.py +0 -0
  129. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/llms/strings.py +0 -0
  130. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/logging.py +0 -0
  131. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/object_registry.py +0 -0
  132. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/output/__init__.py +0 -0
  133. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/output/citations.py +0 -0
  134. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/output/printing.py +0 -0
  135. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/output/status.py +0 -0
  136. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/pandas_utils.py +0 -0
  137. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/pydantic_utils.py +0 -0
  138. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/system.py +0 -0
  139. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/types.py +0 -0
  140. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/web/__init__.py +0 -0
  141. {langroid-0.20.0 → langroid-0.20.1}/langroid/utils/web/login.py +0 -0
  142. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/__init__.py +0 -0
  143. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/base.py +0 -0
  144. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/chromadb.py +0 -0
  145. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/lancedb.py +0 -0
  146. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/meilisearch.py +0 -0
  147. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/momento.py +0 -0
  148. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/qdrant_cloud.py +0 -0
  149. {langroid-0.20.0 → langroid-0.20.1}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -880,7 +880,13 @@ class Agent(ABC):
880
880
  return cdoc
881
881
 
882
882
  def has_tool_message_attempt(self, msg: str | ChatDocument | None) -> bool:
883
- """Check whether msg contains a Tool/fn-call attempt (by the LLM)"""
883
+ """
884
+ Check whether msg contains a Tool/fn-call attempt (by the LLM).
885
+
886
+ CAUTION: This uses self.get_tool_messages(msg) which as a side-effect
887
+ may update msg.tool_messages when msg is a ChatDocument, if there are
888
+ any tools in msg.
889
+ """
884
890
  if msg is None:
885
891
  return False
886
892
  try:
@@ -921,6 +927,9 @@ class Agent(ABC):
921
927
  ) -> List[ToolMessage]:
922
928
  """
923
929
  Get ToolMessages recognized in msg, handle-able by this agent.
930
+ NOTE: as a side-effect, this will update msg.tool_messages
931
+ when msg is a ChatDocument and msg contains tool messages.
932
+
924
933
  If all_tools is True:
925
934
  - return all tools, i.e. any tool in self.llm_tools_known,
926
935
  whether it is handled by this agent or not;
@@ -27,6 +27,7 @@ from langroid.agent.special.arangodb.tools import (
27
27
  aql_retrieval_tool_name,
28
28
  arango_schema_tool_name,
29
29
  )
30
+ from langroid.agent.special.arangodb.utils import count_fields, trim_schema
30
31
  from langroid.agent.tools.orchestration import DoneTool, ForwardTool
31
32
  from langroid.exceptions import LangroidImportError
32
33
  from langroid.mytypes import Entity
@@ -88,11 +89,14 @@ class QueryResult(BaseModel):
88
89
  class ArangoChatAgentConfig(ChatAgentConfig):
89
90
  arango_settings: ArangoSettings = ArangoSettings()
90
91
  system_message: str = DEFAULT_ARANGO_CHAT_SYSTEM_MESSAGE
91
- kg_schema: Optional[Dict[str, List[Dict[str, Any]]]] = None
92
+ kg_schema: str | Dict[str, List[Dict[str, Any]]] | None = None
92
93
  database_created: bool = False
93
- use_schema_tools: bool = True
94
+ prepopulate_schema: bool = True
94
95
  use_functions_api: bool = True
96
+ max_num_results: int = 10 # how many results to return from AQL query
95
97
  max_result_tokens: int = 1000 # truncate long results to this many tokens
98
+ max_schema_fields: int = 500 # max fields to show in schema
99
+ max_tries: int = 10 # how many attempts to answer user question
96
100
  use_tools: bool = False
97
101
  schema_sample_pct: float = 0
98
102
  # whether the agent is used in a continuous chat with user,
@@ -103,16 +107,65 @@ class ArangoChatAgentConfig(ChatAgentConfig):
103
107
 
104
108
  class ArangoChatAgent(ChatAgent):
105
109
  def __init__(self, config: ArangoChatAgentConfig):
110
+ super().__init__(config)
106
111
  self.config: ArangoChatAgentConfig = config
112
+ self.init_state()
107
113
  self._validate_config()
108
114
  self._import_arango()
109
115
  self._initialize_db()
110
116
  self._init_tools_sys_message()
111
- self.init_state()
112
117
 
113
118
  def init_state(self) -> None:
114
119
  super().init_state()
115
120
  self.current_retrieval_aql_query: str = ""
121
+ self.num_tries = 0 # how many attempts to answer user question
122
+
123
+ def user_response(
124
+ self,
125
+ msg: Optional[str | ChatDocument] = None,
126
+ ) -> Optional[ChatDocument]:
127
+ response = super().user_response(msg)
128
+ response_str = response.content if response is not None else ""
129
+ if response_str != "":
130
+ self.num_tries = 0 # reset number of tries if user responds
131
+ return response
132
+
133
+ def llm_response(
134
+ self, message: Optional[str | ChatDocument] = None
135
+ ) -> Optional[ChatDocument]:
136
+ if self.num_tries > self.config.max_tries:
137
+ if self.config.chat_mode:
138
+ return self.create_llm_response(
139
+ content=f"""
140
+ {self.config.addressing_prefix}User
141
+ I give up, since I have exceeded the
142
+ maximum number of tries ({self.config.max_tries}).
143
+ Feel free to give me some hints!
144
+ """
145
+ )
146
+ else:
147
+ return self.create_llm_response(
148
+ tool_messages=[
149
+ DoneTool(
150
+ content=f"""
151
+ Exceeded maximum number of tries ({self.config.max_tries}).
152
+ """
153
+ )
154
+ ]
155
+ )
156
+
157
+ if isinstance(message, ChatDocument) and message.metadata.sender == Entity.USER:
158
+ message.content = (
159
+ message.content
160
+ + "\n"
161
+ + """
162
+ (REMEMBER, Do NOT use more than ONE TOOL/FUNCTION at a time!
163
+ you must WAIT for a helper to send you the RESULT(S) before
164
+ making another TOOL/FUNCTION call)
165
+ """
166
+ )
167
+
168
+ return super().llm_response(message)
116
169
 
117
170
  def _validate_config(self) -> None:
118
171
  assert isinstance(self.config, ArangoChatAgentConfig)
@@ -230,6 +283,7 @@ class ArangoChatAgent(ChatAgent):
230
283
  try:
231
284
  cursor = self.db.aql.execute(query, bind_vars=bind_vars)
232
285
  records = [doc for doc in cursor] # type: ignore
286
+ records = records[: self.config.max_num_results]
233
287
  logger.warning(f"Records retrieved: {records}")
234
288
  return QueryResult(success=True, data=records if records else [])
235
289
  except Exception as e:
@@ -273,6 +327,28 @@ class ArangoChatAgent(ChatAgent):
273
327
  success=False, data=f"Failed after max retries: {str(e)}"
274
328
  )
275
329
 
330
+ def _limit_tokens(self, text: str) -> str:
331
+ result = text
332
+ n_toks = self.num_tokens(result)
333
+ if n_toks > self.config.max_result_tokens:
334
+ logger.warning(
335
+ f"""
336
+ Your query resulted in a large result of
337
+ {n_toks} tokens,
338
+ which will be truncated to {self.config.max_result_tokens} tokens.
339
+ If this does not give satisfactory results,
340
+ please retry with a more focused query.
341
+ """
342
+ )
343
+ if self.parser is not None:
344
+ result = self.parser.truncate_tokens(
345
+ result,
346
+ self.config.max_result_tokens,
347
+ )
348
+ else:
349
+ result = result[: self.config.max_result_tokens * 4] # truncate roughly
350
+ return result
351
+
276
352
  def aql_retrieval_tool(self, msg: AQLRetrievalTool) -> str:
277
353
  """Handle AQL query for data retrieval"""
278
354
  if not self.tried_schema:
@@ -285,6 +361,7 @@ class ArangoChatAgent(ChatAgent):
285
361
  return """
286
362
  You need to create the database first using `{aql_creation_tool_name}`.
287
363
  """
364
+ self.num_tries += 1
288
365
  query = msg.aql_query
289
366
  self.current_retrieval_aql_query = query
290
367
  logger.info(f"Executing AQL query: {query}")
@@ -299,28 +376,11 @@ class ArangoChatAgent(ChatAgent):
299
376
  """
300
377
  # truncate long results
301
378
  result = str(response.data)
302
- n_toks = self.num_tokens(result)
303
- if n_toks > self.config.max_result_tokens:
304
- logger.warning(
305
- f"""
306
- Your query resulted in a large result of
307
- {n_toks} tokens,
308
- which will be truncated to {self.config.max_result_tokens} tokens.
309
- If this does not give satisfactory results,
310
- please retry with a more focused query.
311
- """
312
- )
313
- if self.parser is not None:
314
- result = self.parser.truncate_tokens(
315
- result,
316
- self.config.max_result_tokens,
317
- )
318
- else:
319
- result = result[: self.config.max_result_tokens * 4] # truncate roughly
320
- return result
379
+ return self._limit_tokens(result)
321
380
 
322
381
  def aql_creation_tool(self, msg: AQLCreationTool) -> str:
323
382
  """Handle AQL query for creating data"""
383
+ self.num_tries += 1
324
384
  query = msg.aql_query
325
385
  logger.info(f"Executing AQL query: {query}")
326
386
  response = self.write_query(query)
@@ -334,12 +394,34 @@ class ArangoChatAgent(ChatAgent):
334
394
  self,
335
395
  msg: ArangoSchemaTool | None,
336
396
  ) -> Dict[str, List[Dict[str, Any]]] | str:
337
- """Get database schema including collections, properties, and relationships"""
397
+ """Get database schema. If collections=None, include all collections.
398
+ If properties=False, show only connection info,
399
+ else show all properties and example-docs.
400
+ """
401
+
402
+ if msg is not None:
403
+ collections = msg.collections
404
+ properties = msg.properties
405
+ else:
406
+ collections = None
407
+ properties = True
338
408
  self.tried_schema = True
339
- if self.config.kg_schema is not None and len(self.config.kg_schema) > 0:
409
+ if (
410
+ self.config.kg_schema is not None
411
+ and len(self.config.kg_schema) > 0
412
+ and msg is None
413
+ ):
414
+ # we are trying to pre-populate full schema before the agent runs,
415
+ # so get it if it's already available
416
+ # (Note of course that this "full schema" may actually be incomplete)
340
417
  return self.config.kg_schema
418
+
419
+ # increment tries only if the LLM is asking for the schema,
420
+ # in which case msg will not be None
421
+ self.num_tries += msg is not None
422
+
341
423
  try:
342
- # Get graph schemas
424
+ # Get graph schemas (keeping full graph info)
343
425
  graph_schema = [
344
426
  {"graph_name": g["name"], "edge_definitions": g["edge_definitions"]}
345
427
  for g in self.db.graphs() # type: ignore
@@ -348,57 +430,78 @@ class ArangoChatAgent(ChatAgent):
348
430
  # Get collection schemas
349
431
  collection_schema = []
350
432
  for collection in self.db.collections(): # type: ignore
351
- if collection["name"].startswith("_"): # Skip system collections
433
+ if collection["name"].startswith("_"):
352
434
  continue
353
435
 
354
436
  col_name = collection["name"]
437
+ if collections and col_name not in collections:
438
+ continue
439
+
355
440
  col_type = collection["type"]
356
441
  col_size = self.db.collection(col_name).count()
357
442
 
358
- if col_size == 0: # Skip empty collections
443
+ if col_size == 0:
359
444
  continue
360
445
 
361
- # Calculate sample size
362
- limit_amount = (
363
- ceil(
364
- self.config.schema_sample_pct * col_size / 100.0 # type: ignore
365
- )
366
- or 1
367
- )
368
-
369
- # Query to get sample documents and their properties
370
- sample_query = f"""
371
- FOR doc in {col_name}
372
- LIMIT {limit_amount}
373
- RETURN doc
374
- """
446
+ if properties:
447
+ # Full property collection with sampling
448
+ lim = self.config.schema_sample_pct * col_size # type: ignore
449
+ limit_amount = ceil(lim / 100.0) or 1
450
+ sample_query = f"""
451
+ FOR doc in {col_name}
452
+ LIMIT {limit_amount}
453
+ RETURN doc
454
+ """
375
455
 
376
- properties = []
377
- example_doc = None
378
-
379
- def simplify_doc(doc: Any) -> Any:
380
- if isinstance(doc, list) and len(doc) > 0:
381
- return [simplify_doc(doc[0])]
382
- if isinstance(doc, dict):
383
- return {k: simplify_doc(v) for k, v in doc.items()}
384
- return doc
385
-
386
- for doc in self.db.aql.execute(sample_query): # type: ignore
387
- if example_doc is None:
388
- example_doc = simplify_doc(doc)
389
- for key, value in doc.items():
390
- prop = {"name": key, "type": type(value).__name__}
391
- if prop not in properties:
392
- properties.append(prop)
393
-
394
- collection_schema.append(
395
- {
456
+ properties_list = []
457
+ example_doc = None
458
+
459
+ def simplify_doc(doc: Any) -> Any:
460
+ if isinstance(doc, list) and len(doc) > 0:
461
+ return [simplify_doc(doc[0])]
462
+ if isinstance(doc, dict):
463
+ return {k: simplify_doc(v) for k, v in doc.items()}
464
+ return doc
465
+
466
+ for doc in self.db.aql.execute(sample_query): # type: ignore
467
+ if example_doc is None:
468
+ example_doc = simplify_doc(doc)
469
+ for key, value in doc.items():
470
+ prop = {"name": key, "type": type(value).__name__}
471
+ if prop not in properties_list:
472
+ properties_list.append(prop)
473
+
474
+ collection_schema.append(
475
+ {
476
+ "collection_name": col_name,
477
+ "collection_type": col_type,
478
+ f"{col_type}_properties": properties_list,
479
+ f"example_{col_type}": example_doc,
480
+ }
481
+ )
482
+ else:
483
+ # Basic info + from/to for edges only
484
+ collection_info = {
396
485
  "collection_name": col_name,
397
486
  "collection_type": col_type,
398
- f"{col_type}_properties": properties,
399
- f"example_{col_type}": example_doc,
400
487
  }
401
- )
488
+ if col_type == "edge":
489
+ # Get a sample edge to extract from/to fields
490
+ sample_edge = next(
491
+ self.db.aql.execute( # type: ignore
492
+ f"FOR e IN {col_name} LIMIT 1 RETURN e"
493
+ ),
494
+ None,
495
+ )
496
+ if sample_edge:
497
+ collection_info["from_collection"] = sample_edge[
498
+ "_from"
499
+ ].split("/")[0]
500
+ collection_info["to_collection"] = sample_edge["_to"].split(
501
+ "/"
502
+ )[0]
503
+
504
+ collection_schema.append(collection_info)
402
505
 
403
506
  schema = {
404
507
  "Graph Schema": graph_schema,
@@ -406,10 +509,41 @@ class ArangoChatAgent(ChatAgent):
406
509
  }
407
510
  schema_str = json.dumps(schema, indent=2)
408
511
  logger.warning(f"Schema retrieved:\n{schema_str}")
409
- # save schema to file "logs/arangoo-schema.json"
410
512
  with open("logs/arango-schema.json", "w") as f:
411
513
  f.write(schema_str)
412
- self.config.kg_schema = schema # type: ignore
514
+ if (n_fields := count_fields(schema)) > self.config.max_schema_fields:
515
+ logger.warning(
516
+ f"""
517
+ Schema has {n_fields} fields, which exceeds the maximum of
518
+ {self.config.max_schema_fields}. Showing a trimmed version
519
+ that only includes edge info and no other properties.
520
+ """
521
+ )
522
+ schema = trim_schema(schema)
523
+ n_fields = count_fields(schema)
524
+ logger.warning(f"Schema trimmed down to {n_fields} fields.")
525
+ schema_str = (
526
+ json.dumps(schema)
527
+ + "\n"
528
+ + f"""
529
+
530
+ CAUTION: The requested schema was too large, so
531
+ the schema has been trimmed down to show only all collection names,
532
+ their types,
533
+ and edge relationships (from/to collections) without any properties.
534
+ To find out more about the schema, you can EITHER:
535
+ - Use the `{arango_schema_tool_name}` tool again with the
536
+ `properties` arg set to True, and `collections` arg set to
537
+ specific collections you want to know more about, OR
538
+ - Use the `{aql_retrieval_tool_name}` tool to learn more about
539
+ the schema by querying the database.
540
+
541
+ """
542
+ )
543
+ if msg is None:
544
+ self.config.kg_schema = schema_str
545
+ return schema_str
546
+ self.config.kg_schema = schema
413
547
  return schema
414
548
 
415
549
  except Exception as e:
@@ -432,9 +566,10 @@ class ArangoChatAgent(ChatAgent):
432
566
 
433
567
  super().__init__(self.config)
434
568
  # Note we are enabling GraphSchemaTool regardless of whether
435
- # self.config.use_schema_tools is True or False, because
569
+ # self.config.prepopulate_schema is True or False, because
436
570
  # even when schema provided, the agent may later want to get the schema,
437
- # e.g. if the db evolves, or if it needs to bring in the schema
571
+ # e.g. if the db evolves, or schema was trimmed due to size, or
572
+ # if it needs to bring in the schema into recent context.
438
573
 
439
574
  self.enable_message(
440
575
  [
@@ -454,7 +589,7 @@ class ArangoChatAgent(ChatAgent):
454
589
  assert isinstance(self.config, ArangoChatAgentConfig)
455
590
  return (
456
591
  SCHEMA_TOOLS_SYS_MSG
457
- if self.config.use_schema_tools
592
+ if not self.config.prepopulate_schema
458
593
  else SCHEMA_PROVIDED_SYS_MSG.format(schema=self.arango_schema_tool(None))
459
594
  )
460
595
 
@@ -9,7 +9,7 @@ done_tool_name = DoneTool.default_value("request")
9
9
 
10
10
  arango_schema_tool_description = f"""
11
11
  `{arango_schema_tool_name}` tool/function-call to find the schema
12
- of the graph database, i.e. get all the collections
12
+ of the graph database, or for some SPECIFIC collections, i.e. get information on
13
13
  (document and edge), their attributes, and graph definitions available in your
14
14
  ArangoDB database. You MUST use this tool BEFORE attempting to use the
15
15
  `{aql_retrieval_tool_name}` tool/function-call, to ensure that you are using the
@@ -18,7 +18,8 @@ correct collection names and attributes in your `{aql_retrieval_tool_name}` tool
18
18
 
19
19
  aql_retrieval_tool_description = f"""
20
20
  `{aql_retrieval_tool_name}` tool/function-call to retrieve information from
21
- the database using AQL (ArangoDB Query Language) queries.
21
+ the database using AQL (ArangoDB Query Language) queries, to answer
22
+ the user's questions, OR for you to learn more about the SCHEMA of the database.
22
23
  """
23
24
 
24
25
  aql_creation_tool_description = f"""
@@ -26,6 +27,29 @@ aql_creation_tool_description = f"""
26
27
  documents/edges in the database.
27
28
  """
28
29
 
30
+ aql_retrieval_query_example = """
31
+ EXAMPLE:
32
+ Suppose you are asked this question "Does Bob have a father?".
33
+ Then you will go through the following steps, where YOU indicates
34
+ the message YOU will be sending, and RESULTS indicates the RESULTS
35
+ you will receive from the helper executing the query:
36
+
37
+ 1. YOU:
38
+ {{ "request": "aql_retrieval_tool",
39
+ "aql_query": "FOR v, e, p in ... [query truncated for brevity]..."}}
40
+
41
+ 2. RESULTS:
42
+ [.. results from the query...]
43
+ 3. YOU: [ since results were not satisfactory, you try ANOTHER query]
44
+ {{ "request": "aql_retrieval_tool",
45
+ "aql_query": "blah blah ... [query truncated for brevity]..."}}
46
+ }}
47
+ 4. RESULTS:
48
+ [.. results from the query...]
49
+ 5. YOU: [ now you have the answer, you can generate your response ]
50
+ The answer is YES, Bob has a father, and his name is John.
51
+ """
52
+
29
53
  aql_query_instructions = """
30
54
  When writing AQL queries:
31
55
  1. Use the exact property names shown in the schema
@@ -63,6 +87,7 @@ REMEMBER:
63
87
  with your response. DO NOT MAKE UP RESULTS FROM A TOOL!
64
88
  [3] YOU MUST NOT ANSWER queries from your OWN KNOWLEDGE; ALWAYS RELY ON
65
89
  the result of a TOOL/FUNCTION to compose your response.
90
+ [4] Use ONLY ONE TOOL/FUNCTION at a TIME!
66
91
  """
67
92
  # sys msg to use when schema already provided initially,
68
93
  # so agent should not use schema tool
@@ -77,6 +102,7 @@ and their attribute keys available in your ArangoDB database.
77
102
  {{schema}}
78
103
  === END SCHEMA ===
79
104
 
105
+
80
106
  To help with the user's question or database update/creation request,
81
107
  you have access to these tools:
82
108
 
@@ -84,10 +110,6 @@ you have access to these tools:
84
110
 
85
111
  - {aql_creation_tool_description}
86
112
 
87
- Since the schema has been provided, you may not need to use the tool below,
88
- but you may use it if you need to remind yourself about the schema:
89
-
90
- - {arango_schema_tool_description}
91
113
 
92
114
  {tool_result_instruction}
93
115
  """
@@ -113,7 +135,9 @@ DEFAULT_ARANGO_CHAT_SYSTEM_MESSAGE = f"""
113
135
  {{mode}}
114
136
 
115
137
  You do not need to be able to answer a question with just one query.
116
- You could make a sequence of AQL queries to find the answer to the question.
138
+ You can make a query, WAIT for the result,
139
+ THEN make ANOTHER query, WAIT for result,
140
+ THEN make ANOTHER query, and so on, until you have the answer.
117
141
 
118
142
  {aql_query_instructions}
119
143
 
@@ -134,6 +158,8 @@ If you receive a null or other unexpected result,
134
158
  Start by asking what the user needs help with.
135
159
 
136
160
  {tool_result_instruction}
161
+
162
+ {aql_retrieval_query_example}
137
163
  """
138
164
 
139
165
  ADDRESSING_INSTRUCTION = """
@@ -0,0 +1,102 @@
1
+ from typing import List, Tuple
2
+
3
+ from langroid.agent.tool_message import ToolMessage
4
+
5
+
6
+ class AQLRetrievalTool(ToolMessage):
7
+ request: str = "aql_retrieval_tool"
8
+ purpose: str = """
9
+ To send an <aql_query> in response to a user's request/question,
10
+ OR to find SCHEMA information,
11
+ and WAIT for results of the <aql_query> BEFORE continuing with response.
12
+ You will receive RESULTS from this tool, and ONLY THEN you can continue.
13
+ """
14
+ aql_query: str
15
+
16
+ @classmethod
17
+ def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
18
+ """Few-shot examples to include in tool instructions."""
19
+ return [
20
+ (
21
+ "I want to see who Bob's Father is",
22
+ cls(
23
+ aql_query="""
24
+ FOR v, e, p IN 1..1 OUTBOUND 'users/Bob' GRAPH 'family_tree'
25
+ FILTER p.edges[0].type == 'father'
26
+ RETURN v
27
+ """
28
+ ),
29
+ ),
30
+ (
31
+ "I want to know the properties of the Actor node",
32
+ cls(
33
+ aql_query="""
34
+ FOR doc IN Actor
35
+ LIMIT 1
36
+ RETURN ATTRIBUTES(doc)
37
+ """
38
+ ),
39
+ ),
40
+ ]
41
+
42
+ @classmethod
43
+ def instructions(cls) -> str:
44
+ return """
45
+ When using this TOOL/Function-call, you must WAIT to receive the RESULTS
46
+ of the AQL query, before continuing your response!
47
+ DO NOT ASSUME YOU KNOW THE RESULTs BEFORE RECEIVING THEM.
48
+ """
49
+
50
+
51
+ aql_retrieval_tool_name = AQLRetrievalTool.default_value("request")
52
+
53
+
54
+ class AQLCreationTool(ToolMessage):
55
+ request: str = "aql_creation_tool"
56
+ purpose: str = """
57
+ To send the <aql_query> to create documents/edges in the graph database.
58
+ IMPORTANT: YOU MUST WAIT FOR THE RESULT OF THE TOOL BEFORE CONTINUING.
59
+ You will receive RESULTS from this tool, and ONLY THEN you can continue.
60
+ """
61
+ aql_query: str
62
+
63
+ @classmethod
64
+ def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
65
+ """Few-shot examples to include in tool instructions."""
66
+ return [
67
+ (
68
+ "Create a new document in the collection 'users'",
69
+ cls(
70
+ aql_query="""
71
+ INSERT {
72
+ "name": "Alice",
73
+ "age": 30
74
+ } INTO users
75
+ """
76
+ ),
77
+ ),
78
+ ]
79
+
80
+
81
+ aql_creation_tool_name = AQLCreationTool.default_value("request")
82
+
83
+
84
+ class ArangoSchemaTool(ToolMessage):
85
+ request: str = "arango_schema_tool"
86
+ purpose: str = """
87
+ To get the schema of the Arango graph database,
88
+ or some part of it. Follow these instructions:
89
+ 1. Set <properties> to True to get the properties of the collections,
90
+ and False if you only want to see the graph structure and get only the
91
+ from/to relations of the edges.
92
+ 2. Set <collections> to a list of collection names if you want to see,
93
+ or leave it as None to see all ALL collections.
94
+ IMPORTANT: YOU MUST WAIT FOR THE RESULT OF THE TOOL BEFORE CONTINUING.
95
+ You will receive RESULTS from this tool, and ONLY THEN you can continue.
96
+ """
97
+
98
+ properties: bool = True
99
+ collections: List[str] | None = None
100
+
101
+
102
+ arango_schema_tool_name = ArangoSchemaTool.default_value("request")
@@ -0,0 +1,36 @@
1
+ from typing import Any, Dict, List
2
+
3
+
4
+ def count_fields(schema: Dict[str, List[Dict[str, Any]]]) -> int:
5
+ total = 0
6
+ for coll in schema["Collection Schema"]:
7
+ # Count all keys in each collection's dict
8
+ total += len(coll)
9
+ # Also count properties if they exist
10
+ props = coll.get(f"{coll['collection_type']}_properties", [])
11
+ total += len(props)
12
+ return total
13
+
14
+
15
+ def trim_schema(
16
+ schema: Dict[str, List[Dict[str, Any]]]
17
+ ) -> Dict[str, List[Dict[str, Any]]]:
18
+ """Keep only edge connection info, remove properties and examples"""
19
+ trimmed: Dict[str, List[Dict[str, Any]]] = {
20
+ "Graph Schema": schema["Graph Schema"],
21
+ "Collection Schema": [],
22
+ }
23
+ for coll in schema["Collection Schema"]:
24
+ col_info: Dict[str, Any] = {
25
+ "collection_name": coll["collection_name"],
26
+ "collection_type": coll["collection_type"],
27
+ }
28
+ if coll["collection_type"] == "edge":
29
+ # preserve from/to info if present
30
+ if f"example_{coll['collection_type']}" in coll:
31
+ example = coll[f"example_{coll['collection_type']}"]
32
+ if example and "_from" in example:
33
+ col_info["from_collection"] = example["_from"].split("/")[0]
34
+ col_info["to_collection"] = example["_to"].split("/")[0]
35
+ trimmed["Collection Schema"].append(col_info)
36
+ return trimmed