langroid 0.33.8__py3-none-any.whl → 0.33.10__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.
@@ -539,6 +539,20 @@ class ChatAgent(Agent):
539
539
  self.message_history[i].content = message
540
540
  break
541
541
 
542
+ def delete_last_message(self, role: str = Role.USER) -> None:
543
+ """
544
+ Delete the last message that has role `role` from the message history.
545
+ Args:
546
+ role (str): role of message to delete
547
+ """
548
+ if len(self.message_history) == 0:
549
+ return
550
+ # find last message in self.message_history with role `role`
551
+ for i in range(len(self.message_history) - 1, -1, -1):
552
+ if self.message_history[i].role == role:
553
+ self.message_history.pop(i)
554
+ break
555
+
542
556
  def _create_system_and_tools_message(self) -> LLMMessage:
543
557
  """
544
558
  (Re-)Create the system message for the LLM of the agent,
@@ -1219,15 +1233,25 @@ class ChatAgent(Agent):
1219
1233
  instructions=True,
1220
1234
  )
1221
1235
  recovery_message = self._strict_recovery_instructions(AnyTool)
1236
+ augmented_message = message
1237
+ if augmented_message is None:
1238
+ augmented_message = recovery_message
1239
+ elif isinstance(augmented_message, str):
1240
+ augmented_message = augmented_message + recovery_message
1241
+ else:
1242
+ augmented_message.content = augmented_message.content + recovery_message
1222
1243
 
1244
+ # only use the augmented message for this one response...
1245
+ result = self.llm_response(augmented_message)
1246
+ # ... restore the original user message so that the AnyTool recover
1247
+ # instructions don't persist in the message history
1248
+ # (this can cause the LLM to use the AnyTool directly as a tool)
1223
1249
  if message is None:
1224
- message = recovery_message
1225
- elif isinstance(message, str):
1226
- message = message + recovery_message
1250
+ self.delete_last_message(role=Role.USER)
1227
1251
  else:
1228
- message.content = message.content + recovery_message
1229
-
1230
- return self.llm_response(message)
1252
+ msg = message if isinstance(message, str) else message.content
1253
+ self.update_last_message(msg, role=Role.USER)
1254
+ return result
1231
1255
 
1232
1256
  hist, output_len = self._prep_llm_messages(message)
1233
1257
  if len(hist) == 0:
@@ -1294,15 +1318,25 @@ class ChatAgent(Agent):
1294
1318
  instructions=True,
1295
1319
  )
1296
1320
  recovery_message = self._strict_recovery_instructions(AnyTool)
1321
+ augmented_message = message
1322
+ if augmented_message is None:
1323
+ augmented_message = recovery_message
1324
+ elif isinstance(augmented_message, str):
1325
+ augmented_message = augmented_message + recovery_message
1326
+ else:
1327
+ augmented_message.content = augmented_message.content + recovery_message
1297
1328
 
1329
+ # only use the augmented message for this one response...
1330
+ result = self.llm_response(augmented_message)
1331
+ # ... restore the original user message so that the AnyTool recover
1332
+ # instructions don't persist in the message history
1333
+ # (this can cause the LLM to use the AnyTool directly as a tool)
1298
1334
  if message is None:
1299
- message = recovery_message
1300
- elif isinstance(message, str):
1301
- message = message + recovery_message
1335
+ self.delete_last_message(role=Role.USER)
1302
1336
  else:
1303
- message.content = message.content + recovery_message
1304
-
1305
- return self.llm_response(message)
1337
+ msg = message if isinstance(message, str) else message.content
1338
+ self.update_last_message(msg, role=Role.USER)
1339
+ return result
1306
1340
 
1307
1341
  hist, output_len = self._prep_llm_messages(message)
1308
1342
  if len(hist) == 0:
@@ -13,6 +13,7 @@ from typing import Any, Dict, List, Optional, Sequence, Union
13
13
  from rich.console import Console
14
14
 
15
15
  from langroid.exceptions import LangroidImportError
16
+ from langroid.mytypes import Entity
16
17
  from langroid.utils.constants import SEND_TO
17
18
 
18
19
  try:
@@ -43,10 +44,12 @@ from langroid.agent.special.sql.utils.tools import (
43
44
  RunQueryTool,
44
45
  )
45
46
  from langroid.agent.tools.orchestration import (
47
+ DonePassTool,
46
48
  DoneTool,
47
49
  ForwardTool,
48
50
  PassTool,
49
51
  )
52
+ from langroid.language_models.base import Role
50
53
  from langroid.vector_store.base import VectorStoreConfig
51
54
 
52
55
  logger = logging.getLogger(__name__)
@@ -72,7 +75,8 @@ Start by asking what I would like to know about the data.
72
75
 
73
76
  ADDRESSING_INSTRUCTION = """
74
77
  IMPORTANT - Whenever you are NOT writing a SQL query, make sure you address the user
75
- using {prefix}User. You MUST use the EXACT syntax {prefix} !!!
78
+ using {prefix}User (NO SPACE between {prefix} and User).
79
+ You MUST use the EXACT syntax {prefix}User !!!
76
80
 
77
81
  In other words, you ALWAYS write EITHER:
78
82
  - a SQL query using the `run_query` tool,
@@ -110,6 +114,8 @@ class SQLChatAgentConfig(ChatAgentConfig):
110
114
  # as opposed to returning a result from the task.run()
111
115
  chat_mode: bool = False
112
116
  addressing_prefix: str = ""
117
+ max_result_rows: int | None = None # limit query results to this
118
+ max_retained_tokens: int | None = None # limit history of query results to this
113
119
 
114
120
  """
115
121
  Optional, but strongly recommended, context descriptions for tables, columns,
@@ -181,6 +187,7 @@ class SQLChatAgent(ChatAgent):
181
187
  self.helper_config = self.config.copy()
182
188
  self.helper_config.is_helper = True
183
189
  self.helper_config.use_helper = False
190
+ self.helper_config.chat_mode = False
184
191
  self.helper_agent = SQLHelperAgent(self.helper_config)
185
192
 
186
193
  def _validate_config(self, config: "SQLChatAgentConfig") -> None:
@@ -264,11 +271,13 @@ class SQLChatAgent(ChatAgent):
264
271
 
265
272
  def _init_tools(self) -> None:
266
273
  """Initialize sys msg and tools."""
274
+ RunQueryTool._max_retained_tokens = self.config.max_retained_tokens
267
275
  self.enable_message([RunQueryTool, ForwardTool])
268
276
  if self.config.use_schema_tools:
269
277
  self._enable_schema_tools()
270
278
  if not self.config.chat_mode:
271
279
  self.enable_message(DoneTool)
280
+ self.enable_message(DonePassTool)
272
281
 
273
282
  def _format_message(self) -> str:
274
283
  if self.engine is None:
@@ -311,14 +320,11 @@ class SQLChatAgent(ChatAgent):
311
320
  """
312
321
  if self.config.chat_mode:
313
322
  return f"""
314
- you must use the `{ForwardTool.name()}` with the `agent`
323
+ you must use the TOOL `{ForwardTool.name()}` with the `agent`
315
324
  parameter set to "User"
316
325
  """
317
326
  else:
318
- return f"""
319
- you must use the `{DoneTool.name()}` with the `content`
320
- set to the answer or result
321
- """
327
+ return f"you must use the TOOL `{DonePassTool.name()}`"
322
328
 
323
329
  def _clarifying_message(self) -> str:
324
330
  tools_instruction = f"""
@@ -343,23 +349,24 @@ class SQLChatAgent(ChatAgent):
343
349
  self, message: str | ChatDocument
344
350
  ) -> str | ForwardTool | ChatDocument | None:
345
351
  """
346
- Handle the scenario where current msg is not a tool.
347
- Special handling is only needed if the message was from the LLM
348
- (as indicated by self.llm_responded).
352
+ We'd end up here if the current msg has no tool.
353
+ If this is from LLM, we may need to handle the scenario where
354
+ it may have "forgotten" to generate a tool.
349
355
  """
350
- if not self.llm_responded:
356
+ if (
357
+ not isinstance(message, ChatDocument)
358
+ or message.metadata.sender != Entity.LLM
359
+ ):
351
360
  return None
352
- if self.interactive:
353
- # self.interactive will be set to True by the Task,
354
- # when chat_mode=True, so in this case
355
- # we send any Non-tool msg to the user
361
+ if self.config.chat_mode:
362
+ # send any Non-tool msg to the user
356
363
  return ForwardTool(agent="User")
357
364
  # Agent intent not clear => use the helper agent to
358
365
  # do what this agent should have done, e.g. generate tool, etc.
359
366
  # This is likelier to succeed since this agent has no "baggage" of
360
367
  # prior conversation, other than the system msg, and special
361
368
  # "Intent-interpretation" instructions.
362
- if self._json_schema_available():
369
+ if self._json_schema_available() and self.config.strict_recovery:
363
370
  AnyTool = self._get_any_tool_message(optional=False)
364
371
  self.set_output_format(
365
372
  AnyTool,
@@ -371,15 +378,18 @@ class SQLChatAgent(ChatAgent):
371
378
  recovery_message = self._strict_recovery_instructions(
372
379
  AnyTool, optional=False
373
380
  )
374
- return self.llm_response(recovery_message)
375
- else:
381
+ result = self.llm_response(recovery_message)
382
+ # remove the recovery_message (it has User role) from the chat history,
383
+ # else it may cause the LLM to directly use the AnyTool.
384
+ self.delete_last_message(role=Role.USER) # delete last User-role msg
385
+ return result
386
+ elif self.config.use_helper:
376
387
  response = self.helper_agent.llm_response(message)
377
388
  tools = self.try_get_tool_messages(response)
378
389
  if tools:
379
390
  return response
380
- else:
381
- # fall back on the clarification message
382
- return self._clarifying_message()
391
+ # fall back on the clarification message
392
+ return self._clarifying_message()
383
393
 
384
394
  def retry_query(self, e: Exception, query: str) -> str:
385
395
  """
@@ -466,6 +476,14 @@ class SQLChatAgent(ChatAgent):
466
476
  try:
467
477
  # attempt to fetch results: should work for normal SELECT queries
468
478
  rows = query_result.fetchall()
479
+ n_rows = len(rows)
480
+ if self.config.max_result_rows and n_rows > self.config.max_result_rows:
481
+ rows = rows[: self.config.max_result_rows]
482
+ logger.warning(
483
+ f"SQL query produced {n_rows} rows, "
484
+ f"limiting to {self.config.max_result_rows}"
485
+ )
486
+
469
487
  response_message = self._format_rows(rows)
470
488
  except ResourceClosedError:
471
489
  # If we get here, it's a non-SELECT query (UPDATE, INSERT, DELETE)
@@ -101,7 +101,7 @@ class ToolMessage(ABC, BaseModel):
101
101
  # Some tools can have large results that we may not want to fully retain,
102
102
  # e.g. result of a db query, which the LLM later reduces to a summary, so
103
103
  # in subsequent dialog we may only want to retain the summary,
104
- # and replace this raw result truncated to _max_result_tokens.
104
+ # and replace this raw result truncated to _max_retained_tokens.
105
105
  # Important to note: unlike _max_result_tokens, this param is used
106
106
  # NOT used to immediately truncate the result;
107
107
  # it is only used to truncate what is retained in msg history AFTER the
@@ -156,7 +156,7 @@ class PassTool(ToolMessage):
156
156
  def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
157
157
  """When this tool is enabled for an Agent, this will result in a method
158
158
  added to the Agent with signature:
159
- `forward_tool(self, tool: PassTool, chat_doc: ChatDocument) -> ChatDocument:`
159
+ `pass_tool(self, tool: PassTool, chat_doc: ChatDocument) -> ChatDocument:`
160
160
  """
161
161
  # if PassTool is in chat_doc, pass its parent, else pass chat_doc itself
162
162
  doc = chat_doc
@@ -2,6 +2,7 @@ import hashlib
2
2
  import json
3
3
  import logging
4
4
  import os
5
+ import time
5
6
  import uuid
6
7
  from typing import Dict, List, Optional, Sequence, Tuple, TypeVar
7
8
 
@@ -323,6 +324,26 @@ class QdrantDB(VectorStore):
323
324
  }
324
325
  if self.config.use_sparse_embeddings:
325
326
  vectors["text-sparse"] = sparse_embedding_vecs[i : i + b]
327
+ coll_found: bool = False
328
+ for _ in range(3):
329
+ # poll until collection is ready
330
+ if (
331
+ self.client.collection_exists(self.config.collection_name)
332
+ and self.client.get_collection(self.config.collection_name).status
333
+ == CollectionStatus.GREEN
334
+ ):
335
+ coll_found = True
336
+ break
337
+ time.sleep(1)
338
+
339
+ if not coll_found:
340
+ raise ValueError(
341
+ f"""
342
+ QdrantDB Collection {self.config.collection_name}
343
+ not found or not ready
344
+ """
345
+ )
346
+
326
347
  self.client.upsert(
327
348
  collection_name=self.config.collection_name,
328
349
  points=Batch(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.33.8
3
+ Version: 0.33.10
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -5,11 +5,11 @@ langroid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
6
6
  langroid/agent/base.py,sha256=-wColF3AGsbIm-uiTLfu8cyGUqMRCzZETVirvgZgYGQ,77642
7
7
  langroid/agent/batch.py,sha256=qK3ph6VNj_1sOhfXCZY4r6gh035DglDKU751p8BU0tY,14665
8
- langroid/agent/chat_agent.py,sha256=Idts_HDO1tW052POVOQ9FvuU37TTB7c1I96YVbnBumo,80030
8
+ langroid/agent/chat_agent.py,sha256=X-XhVpUKd-O4K8Q-5Kc0x97l-Ry-fUgPEg9D0oM6U98,81949
9
9
  langroid/agent/chat_document.py,sha256=xPUMGzR83rn4iAEXIw2jy5LQ6YJ6Y0TiZ78XRQeDnJQ,17778
10
10
  langroid/agent/openai_assistant.py,sha256=JkAcs02bIrgPNVvUWVR06VCthc5-ulla2QMBzux_q6o,34340
11
11
  langroid/agent/task.py,sha256=c_Ih0Cc_iiyFKBmKMSdirHcW4RX_35JkCFp05jDAEiM,89994
12
- langroid/agent/tool_message.py,sha256=HDW_FVQXvZAHI61CtOYNuZet0qlK_WwOnjSYd1g81eo,14742
12
+ langroid/agent/tool_message.py,sha256=BhjP-_TfQ2tgxuY4Yo_JHLOwwt0mJ4BwjPnREvEY4vk,14744
13
13
  langroid/agent/xml_tool_message.py,sha256=6SshYZJKIfi4mkE-gIoSwjkEYekQ8GwcSiCv7a5uO9E,15054
14
14
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  langroid/agent/callbacks/chainlit.py,sha256=C6zzzYC30qC4eMA7al7eFpRoTgoe3475kaMKyXgQM0Q,20695
@@ -35,7 +35,7 @@ langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=1RMKupJra0KZ-hA7AiiR662S
35
35
  langroid/agent/special/neo4j/system_messages.py,sha256=m2jsVayey6E_88F5B_gW2WbWKBJvIeDUoVCRBbNs97o,4522
36
36
  langroid/agent/special/neo4j/tools.py,sha256=Vw3HvtDfG2c4_bUHgt4_ZbJq48lpIQstbjjwhh1BjrQ,905
37
37
  langroid/agent/special/sql/__init__.py,sha256=mWfmm1QpXCezpFOS2eI57M0L_Ok3q5_ukG8tXBnBrEA,319
38
- langroid/agent/special/sql/sql_chat_agent.py,sha256=IsVyFLpMinXsPd_HzUIyPC2wIdVc8SLuqNX04X0jyfs,24618
38
+ langroid/agent/special/sql/sql_chat_agent.py,sha256=CJ-vQFbtcGhmOM-GBQvG2quUmicXXW0XPK1pj52E-54,25639
39
39
  langroid/agent/special/sql/utils/__init__.py,sha256=JFif6CRTrN-bc91uuAI4K9fe2ndIWSNMVxJ0WA68--M,446
40
40
  langroid/agent/special/sql/utils/description_extractors.py,sha256=cX8TIpmTPXZXQTMpIi3OUFwFsPywxFFdurpx717Kq0I,6529
41
41
  langroid/agent/special/sql/utils/populate_metadata.py,sha256=1J22UsyEPKzwK0XlJZtYn9r6kYc0FXIr8-lZrndYlhc,3131
@@ -46,7 +46,7 @@ langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6
46
46
  langroid/agent/tools/file_tools.py,sha256=GjPB5YDILucYapElnvvoYpGJuZQ25ecLs2REv7edPEo,7292
47
47
  langroid/agent/tools/google_search_tool.py,sha256=y7b-3FtgXf0lfF4AYxrZ3K5pH2dhidvibUOAGBE--WI,1456
48
48
  langroid/agent/tools/metaphor_search_tool.py,sha256=ccyEhkShH5MxW6-sx1n0BLpD_GForQddS_nNvBZ67Ik,2561
49
- langroid/agent/tools/orchestration.py,sha256=851nZQOE1HpGBwH5om_TNP_qCMxxatXYWFZUrpjSfKk,11421
49
+ langroid/agent/tools/orchestration.py,sha256=EaL_z9dmKuqhhHZEh9N-ieMP-Jr9jGjDprUCHdyldZs,11418
50
50
  langroid/agent/tools/recipient_tool.py,sha256=dr0yTxgNEIoxUYxH6TtaExC4G_8WdJ0xGohIa4dFLhY,9808
51
51
  langroid/agent/tools/retrieval_tool.py,sha256=zcAV20PP_6VzSd-UE-IJcabaBseFL_QNz59Bnig8-lE,946
52
52
  langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
@@ -120,8 +120,8 @@ langroid/vector_store/chromadb.py,sha256=9WXW9IoSnhOmGEtMruVhEtVWL_VO6NXnPIz-nzh
120
120
  langroid/vector_store/lancedb.py,sha256=b3_vWkTjG8mweZ7ZNlUD-NjmQP_rLBZfyKWcxt2vosA,14855
121
121
  langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3HmhHQICXLs,11663
122
122
  langroid/vector_store/momento.py,sha256=UNHGT6jXuQtqY9f6MdqGU14bVnS0zHgIJUa30ULpUJo,10474
123
- langroid/vector_store/qdrantdb.py,sha256=v7mCsijc2GdRJyil-yFaUVAX4SX5D75mD3vzlpjCMuo,17393
124
- langroid-0.33.8.dist-info/METADATA,sha256=B8l5KwLOOVWwTQ7143cYVLffIQYRc3m0UtMbkyoE7N0,59015
125
- langroid-0.33.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
126
- langroid-0.33.8.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
127
- langroid-0.33.8.dist-info/RECORD,,
123
+ langroid/vector_store/qdrantdb.py,sha256=HRLCt-FG8y4718omwpFaQZnWeYxPj0XCwS4tjokI1sU,18116
124
+ langroid-0.33.10.dist-info/METADATA,sha256=GhNSgQ8V-bQdSE0FF66MgZklXy83UusYlwmwnAfBUyA,59016
125
+ langroid-0.33.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
126
+ langroid-0.33.10.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
127
+ langroid-0.33.10.dist-info/RECORD,,