langroid 0.2.2__tar.gz → 0.2.4__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 (135) hide show
  1. {langroid-0.2.2 → langroid-0.2.4}/PKG-INFO +7 -1
  2. {langroid-0.2.2 → langroid-0.2.4}/README.md +6 -0
  3. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/task.py +42 -26
  4. {langroid-0.2.2 → langroid-0.2.4}/langroid/cachedb/redis_cachedb.py +1 -1
  5. langroid-0.2.4/langroid/parsing/routing.py +36 -0
  6. {langroid-0.2.2 → langroid-0.2.4}/pyproject.toml +2 -1
  7. langroid-0.2.2/langroid/parsing/routing.py +0 -27
  8. {langroid-0.2.2 → langroid-0.2.4}/LICENSE +0 -0
  9. {langroid-0.2.2 → langroid-0.2.4}/langroid/__init__.py +0 -0
  10. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/__init__.py +0 -0
  11. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/base.py +0 -0
  12. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/batch.py +0 -0
  13. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/callbacks/__init__.py +0 -0
  14. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/callbacks/chainlit.py +0 -0
  15. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/chat_agent.py +0 -0
  16. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/chat_document.py +0 -0
  17. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/helpers.py +0 -0
  18. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/junk +0 -0
  19. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/openai_assistant.py +0 -0
  20. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/__init__.py +0 -0
  21. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/doc_chat_agent.py +0 -0
  22. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  23. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_rag/__init__.py +0 -0
  24. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  25. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  26. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  27. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/lance_tools.py +0 -0
  28. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/neo4j/__init__.py +0 -0
  29. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  30. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  31. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  32. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  33. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  34. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/retriever_agent.py +0 -0
  35. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/__init__.py +0 -0
  36. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  37. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/utils/__init__.py +0 -0
  38. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  39. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  40. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/utils/system_message.py +0 -0
  41. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/sql/utils/tools.py +0 -0
  42. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/special/table_chat_agent.py +0 -0
  43. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tool_message.py +0 -0
  44. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/__init__.py +0 -0
  45. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  46. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/extract_tool.py +0 -0
  47. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/generator_tool.py +0 -0
  48. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/google_search_tool.py +0 -0
  49. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  50. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/recipient_tool.py +0 -0
  51. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/retrieval_tool.py +0 -0
  52. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/rewind_tool.py +0 -0
  53. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/run_python_code.py +0 -0
  54. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent/tools/segment_extract_tool.py +0 -0
  55. {langroid-0.2.2 → langroid-0.2.4}/langroid/agent_config.py +0 -0
  56. {langroid-0.2.2 → langroid-0.2.4}/langroid/cachedb/__init__.py +0 -0
  57. {langroid-0.2.2 → langroid-0.2.4}/langroid/cachedb/base.py +0 -0
  58. {langroid-0.2.2 → langroid-0.2.4}/langroid/cachedb/momento_cachedb.py +0 -0
  59. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/__init__.py +0 -0
  60. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/base.py +0 -0
  61. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/clustering.py +0 -0
  62. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/models.py +0 -0
  63. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/protoc/__init__.py +0 -0
  64. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  65. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  66. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  67. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  68. {langroid-0.2.2 → langroid-0.2.4}/langroid/embedding_models/remote_embeds.py +0 -0
  69. {langroid-0.2.2 → langroid-0.2.4}/langroid/exceptions.py +0 -0
  70. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/__init__.py +0 -0
  71. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/azure_openai.py +0 -0
  72. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/base.py +0 -0
  73. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/config.py +0 -0
  74. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/mock_lm.py +0 -0
  75. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/openai_gpt.py +0 -0
  76. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  77. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/prompt_formatter/base.py +0 -0
  78. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  79. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  80. {langroid-0.2.2 → langroid-0.2.4}/langroid/language_models/utils.py +0 -0
  81. {langroid-0.2.2 → langroid-0.2.4}/langroid/mytypes.py +0 -0
  82. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/__init__.py +0 -0
  83. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/agent_chats.py +0 -0
  84. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/code-parsing.md +0 -0
  85. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/code_parser.py +0 -0
  86. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/config.py +0 -0
  87. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/document_parser.py +0 -0
  88. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/image_text.py +0 -0
  89. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/para_sentence_split.py +0 -0
  90. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/parse_json.py +0 -0
  91. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/parser.py +0 -0
  92. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/repo_loader.py +0 -0
  93. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/search.py +0 -0
  94. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/spider.py +0 -0
  95. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/table_loader.py +0 -0
  96. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/url_loader.py +0 -0
  97. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/url_loader_cookies.py +0 -0
  98. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/urls.py +0 -0
  99. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/utils.py +0 -0
  100. {langroid-0.2.2 → langroid-0.2.4}/langroid/parsing/web_search.py +0 -0
  101. {langroid-0.2.2 → langroid-0.2.4}/langroid/prompts/__init__.py +0 -0
  102. {langroid-0.2.2 → langroid-0.2.4}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  103. {langroid-0.2.2 → langroid-0.2.4}/langroid/prompts/dialog.py +0 -0
  104. {langroid-0.2.2 → langroid-0.2.4}/langroid/prompts/prompts_config.py +0 -0
  105. {langroid-0.2.2 → langroid-0.2.4}/langroid/prompts/templates.py +0 -0
  106. {langroid-0.2.2 → langroid-0.2.4}/langroid/pydantic_v1/__init__.py +0 -0
  107. {langroid-0.2.2 → langroid-0.2.4}/langroid/pydantic_v1/main.py +0 -0
  108. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/__init__.py +0 -0
  109. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/algorithms/__init__.py +0 -0
  110. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/algorithms/graph.py +0 -0
  111. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/configuration.py +0 -0
  112. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/constants.py +0 -0
  113. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/docker.py +0 -0
  114. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/globals.py +0 -0
  115. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/llms/__init__.py +0 -0
  116. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/llms/strings.py +0 -0
  117. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/logging.py +0 -0
  118. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/object_registry.py +0 -0
  119. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/output/__init__.py +0 -0
  120. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/output/citations.py +0 -0
  121. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/output/printing.py +0 -0
  122. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/output/status.py +0 -0
  123. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/pandas_utils.py +0 -0
  124. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/pydantic_utils.py +0 -0
  125. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/system.py +0 -0
  126. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/web/__init__.py +0 -0
  127. {langroid-0.2.2 → langroid-0.2.4}/langroid/utils/web/login.py +0 -0
  128. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/__init__.py +0 -0
  129. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/base.py +0 -0
  130. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/chromadb.py +0 -0
  131. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/lancedb.py +0 -0
  132. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/meilisearch.py +0 -0
  133. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/momento.py +0 -0
  134. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/qdrant_cloud.py +0 -0
  135. {langroid-0.2.2 → langroid-0.2.4}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -149,6 +149,12 @@ This Multi-Agent paradigm is inspired by the
149
149
  `Langroid` is a fresh take on LLM app-development, where considerable thought has gone
150
150
  into simplifying the developer experience; it does not use `Langchain`.
151
151
 
152
+ Companies are using/adapting Langroid in production. Here is a quote from one of them:
153
+
154
+ >[Nullify](https://www.nullify.ai) uses AI Agents for secure software development.
155
+ > It finds, prioritizes and fixes vulnerabilities. We have internally adapted Langroid's multi-agent orchestration framework in production, after evaluating CrewAI, Autogen, LangChain, Langflow, etc. We found Langroid to be far superior to those frameworks in terms of ease of setup and flexibility. Langroid's Agent and Task abstractions are intuitive, well thought out, and provide a great developer experience. We wanted the quickest way to get something in production. With other frameworks it would have taken us weeks, but with Langroid we got to good results in minutes. Highly recommended! <br> -- Jacky Wong, Head of AI at Nullify.
156
+
157
+
152
158
  :fire: See this [Intro to Langroid](https://lancedb.substack.com/p/langoid-multi-agent-programming-framework)
153
159
  blog post from the LanceDB team
154
160
 
@@ -46,6 +46,12 @@ This Multi-Agent paradigm is inspired by the
46
46
  `Langroid` is a fresh take on LLM app-development, where considerable thought has gone
47
47
  into simplifying the developer experience; it does not use `Langchain`.
48
48
 
49
+ Companies are using/adapting Langroid in production. Here is a quote from one of them:
50
+
51
+ >[Nullify](https://www.nullify.ai) uses AI Agents for secure software development.
52
+ > It finds, prioritizes and fixes vulnerabilities. We have internally adapted Langroid's multi-agent orchestration framework in production, after evaluating CrewAI, Autogen, LangChain, Langflow, etc. We found Langroid to be far superior to those frameworks in terms of ease of setup and flexibility. Langroid's Agent and Task abstractions are intuitive, well thought out, and provide a great developer experience. We wanted the quickest way to get something in production. With other frameworks it would have taken us weeks, but with Langroid we got to good results in minutes. Highly recommended! <br> -- Jacky Wong, Head of AI at Nullify.
53
+
54
+
49
55
  :fire: See this [Intro to Langroid](https://lancedb.substack.com/p/langoid-multi-agent-programming-framework)
50
56
  blog post from the LanceDB team
51
57
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import copy
5
5
  import logging
6
+ import re
6
7
  import threading
7
8
  from collections import Counter, deque
8
9
  from pathlib import Path
@@ -184,8 +185,8 @@ class Task:
184
185
  Note: erasing can reduce prompt sizes, but results in repetitive
185
186
  sub-task delegation.
186
187
  allow_null_result (bool):
187
- If true, allow null (empty or NO_ANSWER) as the result of a step or
188
- overall task result.
188
+ If true, create dummy NO_ANSWER response when no valid response is found
189
+ in a step.
189
190
  Optional, default is False.
190
191
  *Note:* In non-interactive mode, when this is set to True,
191
192
  you can have a situation where an LLM generates (non-tool) text,
@@ -193,7 +194,9 @@ class Task:
193
194
  is inserted as a dummy response from the User entity, so the LLM
194
195
  will now respond to this Null result, and this will continue
195
196
  until the LLM emits a DONE signal (if instructed to do so),
196
- otherwise it can result in an infinite loop.
197
+ otherwise langroid detects a potential infinite loop after
198
+ a certain number of such steps (= `TaskConfig.inf_loop_wait_factor`)
199
+ and will raise an InfiniteLoopException.
197
200
  max_stalled_steps (int): task considered done after this many consecutive
198
201
  steps with no progress. Default is 3.
199
202
  done_if_no_response (List[Responder]): consider task done if NULL
@@ -254,7 +257,7 @@ class Task:
254
257
  # how many 2-step-apart alternations of no_answer step-result have we had,
255
258
  # i.e. x1, N/A, x2, N/A, x3, N/A ...
256
259
  self.n_no_answer_alternations = 0
257
- self._no_answer_step: int = -1
260
+ self._no_answer_step: int = -5
258
261
  self._step_idx = -1 # current step index
259
262
  self.max_stalled_steps = max_stalled_steps
260
263
  self.done_if_response = [r.value for r in done_if_response]
@@ -577,7 +580,7 @@ class Task:
577
580
  self.reset_all_sub_tasks()
578
581
 
579
582
  self.n_stalled_steps = 0
580
- self._no_answer_step = -1 # last step where the best explicit response was N/A
583
+ self._no_answer_step = -5 # last step where the best explicit response was N/A
581
584
  # how many N/A alternations have we had so far? (for Inf loop detection)
582
585
  self.n_no_answer_alternations = 0
583
586
  self.max_cost = max_cost
@@ -637,6 +640,7 @@ class Task:
637
640
  self.config.inf_loop_cycle_len > 0
638
641
  and i % self.config.inf_loop_cycle_len == 0
639
642
  and self._maybe_infinite_loop()
643
+ or self.n_no_answer_alternations > self.config.inf_loop_wait_factor
640
644
  ):
641
645
  raise InfiniteLoopException(
642
646
  """Possible infinite loop detected!
@@ -702,7 +706,7 @@ class Task:
702
706
  self.reset_all_sub_tasks()
703
707
 
704
708
  self.n_stalled_steps = 0
705
- self._no_answer_step = -1 # last step where the best explicit response was N/A
709
+ self._no_answer_step = -5 # last step where the best explicit response was N/A
706
710
  # how many N/A alternations have we had so far? (for Inf loop detection)
707
711
  self.n_no_answer_alternations = 0
708
712
  self.max_cost = max_cost
@@ -759,6 +763,7 @@ class Task:
759
763
  self.config.inf_loop_cycle_len > 0
760
764
  and i % self.config.inf_loop_cycle_len == 0
761
765
  and self._maybe_infinite_loop()
766
+ or self.n_no_answer_alternations > self.config.inf_loop_wait_factor
762
767
  ):
763
768
  raise InfiniteLoopException(
764
769
  """Possible infinite loop detected!
@@ -878,7 +883,6 @@ class Task:
878
883
 
879
884
  if (
880
885
  Entity.USER in self.responders
881
- and self.interactive
882
886
  and not self.human_tried
883
887
  and not self.agent.has_tool_message_attempt(self.pending_message)
884
888
  ):
@@ -930,7 +934,7 @@ class Task:
930
934
  if self.is_done:
931
935
  # skip trying other responders in this step
932
936
  break
933
- if not found_response: # did not find a Non-NO_ANSWER response
937
+ if not found_response: # did not find a valid response
934
938
  if no_answer_response:
935
939
  # even though there was no valid response from anyone in this step,
936
940
  # if there was at least one who EXPLICITLY said NO_ANSWER, then
@@ -986,7 +990,6 @@ class Task:
986
990
 
987
991
  if (
988
992
  Entity.USER in self.responders
989
- and self.interactive
990
993
  and not self.human_tried
991
994
  and not self.agent.has_tool_message_attempt(self.pending_message)
992
995
  ):
@@ -1047,15 +1050,10 @@ class Task:
1047
1050
  self._show_pending_message_if_debug()
1048
1051
  return self.pending_message
1049
1052
 
1050
- def _process_valid_responder_result(
1051
- self,
1052
- r: Responder,
1053
- parent: ChatDocument | None,
1054
- result: ChatDocument,
1055
- ) -> None:
1056
- """Processes valid result from a responder, during a step"""
1053
+ def _update_no_answer_vars(self, result: ChatDocument) -> None:
1054
+ """Update variables related to NO_ANSWER responses, to aid
1055
+ in alternating NO_ANSWER infinite-loop detection."""
1057
1056
 
1058
- # in case the valid response was a NO_ANSWER,
1059
1057
  if NO_ANSWER in result.content:
1060
1058
  if self._no_answer_step == self._step_idx - 2:
1061
1059
  # N/A two steps ago
@@ -1067,6 +1065,16 @@ class Task:
1067
1065
  # record the last step where the best explicit response was N/A
1068
1066
  self._no_answer_step = self._step_idx
1069
1067
 
1068
+ def _process_valid_responder_result(
1069
+ self,
1070
+ r: Responder,
1071
+ parent: ChatDocument | None,
1072
+ result: ChatDocument,
1073
+ ) -> None:
1074
+ """Processes valid result from a responder, during a step"""
1075
+
1076
+ self._update_no_answer_vars(result)
1077
+
1070
1078
  # pending_sender is of type Responder,
1071
1079
  # i.e. it is either one of the agent's entities
1072
1080
  # OR a sub-task, that has produced a valid response.
@@ -1131,6 +1139,7 @@ class Task:
1131
1139
  metadata=ChatDocMetaData(sender=responder, parent_id=parent_id),
1132
1140
  )
1133
1141
  self.pending_sender = responder
1142
+ self._update_no_answer_vars(self.pending_message)
1134
1143
  self.log_message(self.pending_sender, self.pending_message, mark=True)
1135
1144
 
1136
1145
  def _show_pending_message_if_debug(self) -> None:
@@ -1346,13 +1355,20 @@ class Task:
1346
1355
  def _maybe_infinite_loop(self) -> bool:
1347
1356
  """
1348
1357
  Detect possible infinite loop based on message frequencies.
1349
- NOTE: This only (attempts to) detect "exact" loops, i.e. a cycle
1350
- of messages that repeats exactly, e.g.
1358
+ NOTE: This detects two types of loops:
1359
+ - Alternating NO_ANSWER loops, specifically of the form
1360
+ x1 NO_ANSWER x2 NO_ANSWER x3 NO_ANSWER...
1361
+ (e.g. an LLM repeatedly saying something different, and another responder
1362
+ or sub-task saying NO_ANSWER -- i.e. "DO-NOT-KNOW")
1363
+
1364
+ - "exact" loops, i.e. a cycle of messages that repeats exactly, e.g.
1351
1365
  a r b i t r a t e r a t e r a t e r a t e ...
1352
1366
 
1353
- [It does not detect "approximate" loops, where the LLM is generating a
1354
- sequence of messages that are similar, but not exactly the same.]
1367
+ [It does not detect more general "approximate" loops, where two entities are
1368
+ responding to each other potentially forever, with (slightly) different
1369
+ messages each time]
1355
1370
 
1371
+ Here is the logic for the exact-loop detection:
1356
1372
  Intuition: when you look at a sufficiently long sequence with an m-message
1357
1373
  loop, then the frequencies of these m messages will "dominate" those
1358
1374
  of all other messages.
@@ -1370,8 +1386,6 @@ class Task:
1370
1386
  If the set of last (W * m) messages are the same as the
1371
1387
  set of m dominant messages, then we are likely in a loop.
1372
1388
  """
1373
- if self.n_no_answer_alternations > self.config.inf_loop_wait_factor:
1374
- return True
1375
1389
 
1376
1390
  max_cycle_len = self.config.inf_loop_cycle_len
1377
1391
  if max_cycle_len <= 0:
@@ -1438,7 +1452,7 @@ class Task:
1438
1452
  result = result or self.pending_message
1439
1453
  user_quit = (
1440
1454
  result is not None
1441
- and result.content in USER_QUIT_STRINGS
1455
+ and (result.content in USER_QUIT_STRINGS or DONE in result.content)
1442
1456
  and result.metadata.sender == Entity.USER
1443
1457
  )
1444
1458
  if self._level == 0 and self.interactive and self.only_user_quits_root:
@@ -1509,7 +1523,9 @@ class Task:
1509
1523
  return (
1510
1524
  result is not None
1511
1525
  and not self._is_empty_message(result)
1512
- and result.content.strip() != NO_ANSWER
1526
+ # some weaker LLMs, including even GPT-4o, may say "DO-NOT-KNOW."
1527
+ # (with a punctuation at the end), so need to strip out punctuation
1528
+ and re.sub(r"[,.!?:]", "", result.content.strip()) != NO_ANSWER
1513
1529
  )
1514
1530
 
1515
1531
  def log_message(
@@ -1590,7 +1606,7 @@ class Task:
1590
1606
  return (
1591
1607
  self.pending_message is not None
1592
1608
  and (recipient := self.pending_message.metadata.recipient) != ""
1593
- and recipient != e # case insensitive
1609
+ and not (recipient == e) # case insensitive for entities
1594
1610
  and recipient != e.name
1595
1611
  and recipient != self.name # case sensitive
1596
1612
  )
@@ -54,7 +54,7 @@ class RedisCache(CacheDB):
54
54
  host=redis_host,
55
55
  port=redis_port,
56
56
  password=redis_password,
57
- max_connections=50,
57
+ max_connections=500,
58
58
  socket_timeout=5,
59
59
  socket_keepalive=True,
60
60
  retry_on_timeout=True,
@@ -0,0 +1,36 @@
1
+ import re
2
+ from typing import Optional, Tuple
3
+
4
+
5
+ def parse_addressed_message(
6
+ content: str, addressing: str = "@"
7
+ ) -> Tuple[Optional[str], str]:
8
+ """In a message-string containing possibly multiple @<recipient> occurrences,
9
+ find the last addressee and extract their name,
10
+ and the message content following it.
11
+
12
+ E.g. "thank you @bob, now I will ask @alice again. @alice, where is the mirror?" =>
13
+ ("alice", "where is the mirror?")
14
+
15
+ Args:
16
+ content (str): The message content.
17
+ addressing (str, optional): The addressing character. Defaults to "@".
18
+
19
+ Returns:
20
+ Tuple[Optional[str], str]:
21
+ A tuple containing the last addressee and the subsequent message content.
22
+ """
23
+ # Regex to find all occurrences of the pattern
24
+ pattern = re.compile(rf"{re.escape(addressing)}(\w+)[^\w]")
25
+ matches = list(pattern.finditer(content))
26
+
27
+ if not matches:
28
+ return None, content # No addressee found, return None and original content
29
+
30
+ # Get the last match
31
+ last_match = matches[-1]
32
+ last_addressee = last_match.group(1)
33
+ # Extract content after the last addressee
34
+ content_after = content[last_match.end() :].strip()
35
+
36
+ return last_addressee, content_after
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
@@ -231,3 +231,4 @@ lint.extend-ignore = ["F821"]
231
231
 
232
232
  [tool.pytest.ini_options]
233
233
  filterwarnings = ["ignore::DeprecationWarning"]
234
+
@@ -1,27 +0,0 @@
1
- import re
2
- from typing import Optional, Tuple
3
-
4
-
5
- def parse_addressed_message(
6
- content: str, addressing: str = "@"
7
- ) -> Tuple[Optional[str], str]:
8
- # escape special characters in addressing prefix for regex use
9
- addressing_escaped = re.escape(addressing)
10
- pattern = rf"{addressing_escaped}(\w+)[,:\s]?"
11
- # Regular expression to find a username prefixed by addressing character or string
12
- match = re.findall(pattern, content)
13
-
14
- addressee = None
15
- if match:
16
- # select the last match as the addressee
17
- addressee = match[-1]
18
-
19
- # Remove the last occurrence of the addressing prefix followed by the
20
- # username and optional punctuation or whitespace
21
- # To remove only the last occurrence, we'll construct a new pattern that
22
- # specifically matches the last addressee
23
- last_occurrence_pattern = rf"{addressing_escaped}{addressee}[,:\\s]?"
24
- # Replace the last occurrence found in the content
25
- content = re.sub(last_occurrence_pattern, "", content, count=1).strip()
26
-
27
- return addressee, content
File without changes
File without changes
File without changes
File without changes