langroid 0.1.258__tar.gz → 0.1.261__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 (132) hide show
  1. {langroid-0.1.258 → langroid-0.1.261}/PKG-INFO +3 -2
  2. {langroid-0.1.258 → langroid-0.1.261}/README.md +2 -1
  3. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/callbacks/chainlit.py +45 -7
  4. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/chat_document.py +0 -2
  5. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/task.py +51 -46
  6. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/openai_gpt.py +3 -1
  7. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/qdrantdb.py +1 -1
  8. {langroid-0.1.258 → langroid-0.1.261}/pyproject.toml +1 -1
  9. {langroid-0.1.258 → langroid-0.1.261}/LICENSE +0 -0
  10. {langroid-0.1.258 → langroid-0.1.261}/langroid/__init__.py +0 -0
  11. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/__init__.py +0 -0
  12. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/base.py +0 -0
  13. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/batch.py +0 -0
  14. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/callbacks/__init__.py +0 -0
  15. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/chat_agent.py +0 -0
  16. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/helpers.py +0 -0
  17. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/junk +0 -0
  18. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/openai_assistant.py +0 -0
  19. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/__init__.py +0 -0
  20. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/doc_chat_agent.py +0 -0
  21. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  22. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_rag/__init__.py +0 -0
  23. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  24. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  25. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  26. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/lance_tools.py +0 -0
  27. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/neo4j/__init__.py +0 -0
  28. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  29. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  30. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  31. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  32. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  33. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/retriever_agent.py +0 -0
  34. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/__init__.py +0 -0
  35. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  36. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/utils/__init__.py +0 -0
  37. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  38. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  39. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/utils/system_message.py +0 -0
  40. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/sql/utils/tools.py +0 -0
  41. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/special/table_chat_agent.py +0 -0
  42. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tool_message.py +0 -0
  43. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/__init__.py +0 -0
  44. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  45. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/extract_tool.py +0 -0
  46. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/generator_tool.py +0 -0
  47. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/google_search_tool.py +0 -0
  48. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  49. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/recipient_tool.py +0 -0
  50. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/retrieval_tool.py +0 -0
  51. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/run_python_code.py +0 -0
  52. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent/tools/segment_extract_tool.py +0 -0
  53. {langroid-0.1.258 → langroid-0.1.261}/langroid/agent_config.py +0 -0
  54. {langroid-0.1.258 → langroid-0.1.261}/langroid/cachedb/__init__.py +0 -0
  55. {langroid-0.1.258 → langroid-0.1.261}/langroid/cachedb/base.py +0 -0
  56. {langroid-0.1.258 → langroid-0.1.261}/langroid/cachedb/momento_cachedb.py +0 -0
  57. {langroid-0.1.258 → langroid-0.1.261}/langroid/cachedb/redis_cachedb.py +0 -0
  58. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/__init__.py +0 -0
  59. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/base.py +0 -0
  60. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/clustering.py +0 -0
  61. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/models.py +0 -0
  62. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/protoc/__init__.py +0 -0
  63. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  64. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  65. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  66. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  67. {langroid-0.1.258 → langroid-0.1.261}/langroid/embedding_models/remote_embeds.py +0 -0
  68. {langroid-0.1.258 → langroid-0.1.261}/langroid/exceptions.py +0 -0
  69. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/__init__.py +0 -0
  70. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/azure_openai.py +0 -0
  71. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/base.py +0 -0
  72. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/config.py +0 -0
  73. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/openai_assistants.py +0 -0
  74. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  75. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/prompt_formatter/base.py +0 -0
  76. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  77. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  78. {langroid-0.1.258 → langroid-0.1.261}/langroid/language_models/utils.py +0 -0
  79. {langroid-0.1.258 → langroid-0.1.261}/langroid/mytypes.py +0 -0
  80. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/__init__.py +0 -0
  81. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/agent_chats.py +0 -0
  82. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/code-parsing.md +0 -0
  83. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/code_parser.py +0 -0
  84. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/config.py +0 -0
  85. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/document_parser.py +0 -0
  86. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/image_text.py +0 -0
  87. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/para_sentence_split.py +0 -0
  88. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/parse_json.py +0 -0
  89. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/parser.py +0 -0
  90. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/parser.pyi +0 -0
  91. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/repo_loader.py +0 -0
  92. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/routing.py +0 -0
  93. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/search.py +0 -0
  94. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/spider.py +0 -0
  95. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/table_loader.py +0 -0
  96. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/url_loader.py +0 -0
  97. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/url_loader_cookies.py +0 -0
  98. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/urls.py +0 -0
  99. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/utils.py +0 -0
  100. {langroid-0.1.258 → langroid-0.1.261}/langroid/parsing/web_search.py +0 -0
  101. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/__init__.py +0 -0
  102. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  103. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/dialog.py +0 -0
  104. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/prompts_config.py +0 -0
  105. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/templates.py +0 -0
  106. {langroid-0.1.258 → langroid-0.1.261}/langroid/prompts/transforms.py +0 -0
  107. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/__init__.py +0 -0
  108. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/algorithms/__init__.py +0 -0
  109. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/algorithms/graph.py +0 -0
  110. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/configuration.py +0 -0
  111. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/constants.py +0 -0
  112. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/docker.py +0 -0
  113. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/globals.py +0 -0
  114. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/llms/__init__.py +0 -0
  115. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/llms/strings.py +0 -0
  116. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/logging.py +0 -0
  117. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/output/__init__.py +0 -0
  118. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/output/citations.py +0 -0
  119. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/output/printing.py +0 -0
  120. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/output/status.py +0 -0
  121. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/pandas_utils.py +0 -0
  122. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/pydantic_utils.py +0 -0
  123. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/system.py +0 -0
  124. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/web/__init__.py +0 -0
  125. {langroid-0.1.258 → langroid-0.1.261}/langroid/utils/web/login.py +0 -0
  126. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/__init__.py +0 -0
  127. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/base.py +0 -0
  128. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/chromadb.py +0 -0
  129. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/lancedb.py +0 -0
  130. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/meilisearch.py +0 -0
  131. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/momento.py +0 -0
  132. {langroid-0.1.258 → langroid-0.1.261}/langroid/vector_store/qdrant_cloud.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.258
3
+ Version: 0.1.261
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -110,6 +110,7 @@ Description-Content-Type: text/markdown
110
110
  <div align="center">
111
111
 
112
112
  [![PyPI - Version](https://img.shields.io/pypi/v/langroid)](https://pypi.org/project/langroid/)
113
+ [![Downloads](https://img.shields.io/pypi/dm/langroid)](https://pypi.org/project/langroid/)
113
114
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
114
115
  [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
115
116
  [![Multi-Architecture DockerHub](https://github.com/langroid/langroid/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/docker-publish.yml)
@@ -436,7 +437,7 @@ section above)
436
437
  a task of an agent can delegate to other sub-tasks: from the point of view of a Task,
437
438
  sub-tasks are simply additional responders, to be used in a round-robin fashion
438
439
  after the agent's own responders.
439
- - **Modularity, Reusabilily, Loose coupling:** The `Agent` and `Task` abstractions allow users to design
440
+ - **Modularity, Reusability, Loose coupling:** The `Agent` and `Task` abstractions allow users to design
440
441
  Agents with specific skills, wrap them in Tasks, and combine tasks in a flexible way.
441
442
  - **LLM Support**: Langroid supports OpenAI LLMs as well as LLMs from hundreds of
442
443
  providers (local/open or remote/commercial) via proxy libraries and local model servers
@@ -6,6 +6,7 @@
6
6
  <div align="center">
7
7
 
8
8
  [![PyPI - Version](https://img.shields.io/pypi/v/langroid)](https://pypi.org/project/langroid/)
9
+ [![Downloads](https://img.shields.io/pypi/dm/langroid)](https://pypi.org/project/langroid/)
9
10
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
10
11
  [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
11
12
  [![Multi-Architecture DockerHub](https://github.com/langroid/langroid/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/docker-publish.yml)
@@ -332,7 +333,7 @@ section above)
332
333
  a task of an agent can delegate to other sub-tasks: from the point of view of a Task,
333
334
  sub-tasks are simply additional responders, to be used in a round-robin fashion
334
335
  after the agent's own responders.
335
- - **Modularity, Reusabilily, Loose coupling:** The `Agent` and `Task` abstractions allow users to design
336
+ - **Modularity, Reusability, Loose coupling:** The `Agent` and `Task` abstractions allow users to design
336
337
  Agents with specific skills, wrap them in Tasks, and combine tasks in a flexible way.
337
338
  - **LLM Support**: Langroid supports OpenAI LLMs as well as LLMs from hundreds of
338
339
  providers (local/open or remote/commercial) via proxy libraries and local model servers
@@ -214,6 +214,7 @@ def wrap_text_preserving_structure(text: str, width: int = 90) -> str:
214
214
 
215
215
  class ChainlitCallbackConfig(BaseSettings):
216
216
  user_has_agent_name: bool = True # show agent name in front of "YOU" ?
217
+ show_subtask_response: bool = True # show sub-task response as a step?
217
218
 
218
219
 
219
220
  class ChainlitAgentCallbacks:
@@ -277,12 +278,6 @@ class ChainlitAgentCallbacks:
277
278
 
278
279
  def start_llm_stream(self) -> Callable[[str], None]:
279
280
  """Returns a streaming fn that can be passed to the LLM class"""
280
- logger.info(
281
- f"""
282
- Starting LLM stream for {self.agent.config.name}
283
- under parent {self._get_parent_id()}
284
- """
285
- )
286
281
  self.stream = cl.Step(
287
282
  id=self.curr_step.id if self.curr_step is not None else None,
288
283
  name=self._entity_name("llm"),
@@ -291,6 +286,13 @@ class ChainlitAgentCallbacks:
291
286
  )
292
287
  self.last_step = self.stream
293
288
  self.curr_step = None
289
+ logger.info(
290
+ f"""
291
+ Starting LLM stream for {self.agent.config.name}
292
+ id = {self.stream.id}
293
+ under parent {self._get_parent_id()}
294
+ """
295
+ )
294
296
  run_sync(self.stream.send()) # type: ignore
295
297
 
296
298
  def stream_token(t: str) -> None:
@@ -323,6 +325,13 @@ class ChainlitAgentCallbacks:
323
325
  language="json" if is_tool else None,
324
326
  )
325
327
  step.output = textwrap.dedent(content) or NO_ANSWER
328
+ logger.info(
329
+ f"""
330
+ Finish STREAM LLM response for {self.agent.config.name}
331
+ id = {step.id}
332
+ under parent {self._get_parent_id()}
333
+ """
334
+ )
326
335
  run_sync(step.update()) # type: ignore
327
336
 
328
337
  def show_llm_response(
@@ -343,6 +352,13 @@ class ChainlitAgentCallbacks:
343
352
  self.last_step = step
344
353
  self.curr_step = None
345
354
  step.output = textwrap.dedent(content) or NO_ANSWER
355
+ logger.info(
356
+ f"""
357
+ Showing NON-STREAM LLM response for {self.agent.config.name}
358
+ id = {step.id}
359
+ under parent {self._get_parent_id()}
360
+ """
361
+ )
346
362
  run_sync(step.send()) # type: ignore
347
363
 
348
364
  def show_error_message(self, error: str) -> None:
@@ -374,6 +390,13 @@ class ChainlitAgentCallbacks:
374
390
  self.last_step = step
375
391
  self.curr_step = None
376
392
  step.output = content
393
+ logger.info(
394
+ f"""
395
+ Showing AGENT response for {self.agent.config.name}
396
+ id = {step.id}
397
+ under parent {self._get_parent_id()}
398
+ """
399
+ )
377
400
  run_sync(step.send()) # type: ignore
378
401
 
379
402
  def show_start_response(self, entity: str) -> None:
@@ -390,6 +413,13 @@ class ChainlitAgentCallbacks:
390
413
  step.output = ""
391
414
  self.last_step = step
392
415
  self.curr_step = step
416
+ logger.info(
417
+ f"""
418
+ Showing START response for {self.agent.config.name} ({entity})
419
+ id = {step.id}
420
+ under parent {self._get_parent_id()}
421
+ """
422
+ )
393
423
  run_sync(step.send()) # type: ignore
394
424
 
395
425
  def _entity_name(
@@ -459,6 +489,13 @@ class ChainlitAgentCallbacks:
459
489
  parent_id=self._get_parent_id(),
460
490
  )
461
491
  step.output = message
492
+ logger.info(
493
+ f"""
494
+ Showing USER response for {self.agent.config.name}
495
+ id = {step.id}
496
+ under parent {self._get_parent_id()}
497
+ """
498
+ )
462
499
  run_sync(step.send())
463
500
 
464
501
  def show_first_user_message(self, msg: cl.Message):
@@ -575,7 +612,8 @@ class ChainlitTaskCallbacks(ChainlitAgentCallbacks):
575
612
  super().__init__(task.agent, msg, config)
576
613
  self._inject_callbacks(task)
577
614
  self.task = task
578
- self.task.callbacks.show_subtask_response = self.show_subtask_response
615
+ if config.show_subtask_response:
616
+ self.task.callbacks.show_subtask_response = self.show_subtask_response
579
617
 
580
618
  @classmethod
581
619
  def _inject_callbacks(
@@ -45,8 +45,6 @@ class ChatDocMetaData(DocMetaData):
45
45
  parent: Optional["ChatDocument"] = None
46
46
  sender: Entity
47
47
  tool_ids: List[str] = [] # stack of tool_ids; used by OpenAIAssistant
48
- # when result returns to parent, pretend message is from this entity
49
- parent_responder: None | Entity = None
50
48
  block: None | Entity = None
51
49
  sender_name: str = ""
52
50
  recipient: str = ""
@@ -72,7 +72,7 @@ class TaskConfig(BaseModel):
72
72
 
73
73
  inf_loop_cycle_len: int = 10
74
74
  inf_loop_dominance_factor: float = 1.5
75
- inf_loop_wait_factor: float = 5.0
75
+ inf_loop_wait_factor: int = 5
76
76
 
77
77
 
78
78
  class Task:
@@ -156,6 +156,11 @@ class Task:
156
156
  interactive (bool): if true, wait for human input after each non-human
157
157
  response (prevents infinite loop of non-human responses).
158
158
  Default is true. If false, then `default_human_response` is set to ""
159
+ Note: When interactive = False, the one exception is when the user
160
+ is explicitly addressed, via "@user" or using RecipientTool, in which
161
+ case the system will wait for a user response. In other words, use
162
+ `interactive=False` when you want a "largely non-interactive"
163
+ run, with the exception of explicit user addressing.
159
164
  only_user_quits_root (bool): if true, only user can quit the root task.
160
165
  [This param is ignored & deprecated; Keeping for backward compatibility.
161
166
  Instead of this, setting `interactive` suffices]
@@ -185,7 +190,11 @@ class Task:
185
190
  # counts of distinct pending messages in history,
186
191
  # to help detect (exact) infinite loops
187
192
  self.message_counter: Counter[str] = Counter()
188
- self.history_count: Deque[int] = deque(maxlen=self.config.inf_loop_cycle_len)
193
+ self._init_message_counter()
194
+
195
+ self.history: Deque[str] = deque(
196
+ maxlen=self.config.inf_loop_cycle_len * self.config.inf_loop_wait_factor
197
+ )
189
198
  # copy the agent's config, so that we don't modify the original agent's config,
190
199
  # which may be shared by other agents.
191
200
  try:
@@ -329,6 +338,12 @@ class Task:
329
338
  def __str__(self) -> str:
330
339
  return f"{self.name}"
331
340
 
341
+ def _init_message_counter(self) -> None:
342
+ self.message_counter.clear()
343
+ # create a unique string that will not likely be in any message,
344
+ # so we always have a message with count=1
345
+ self.message_counter.update([hash("___NO_MESSAGE___")])
346
+
332
347
  def _cache_session_store(self, key: str, value: str) -> None:
333
348
  """
334
349
  Cache a key-value pair for the current session.
@@ -440,7 +455,7 @@ class Task:
440
455
  ),
441
456
  )
442
457
  else:
443
- self.pending_message = msg
458
+ self.pending_message = copy.deepcopy(msg)
444
459
  if self.pending_message is not None and self.caller is not None:
445
460
  # msg may have come from `caller`, so we pretend this is from
446
461
  # the CURRENT task's USER entity
@@ -480,8 +495,8 @@ class Task:
480
495
  self.max_tokens = max_tokens
481
496
  self.session_id = session_id
482
497
  self._set_alive()
483
- self.message_counter.clear()
484
- self.history_count.clear()
498
+ self._init_message_counter()
499
+ self.history.clear()
485
500
 
486
501
  assert (
487
502
  msg is None or isinstance(msg, str) or isinstance(msg, ChatDocument)
@@ -580,8 +595,8 @@ class Task:
580
595
  self.max_tokens = max_tokens
581
596
  self.session_id = session_id
582
597
  self._set_alive()
583
- self.message_counter.clear()
584
- self.history_count.clear()
598
+ self._init_message_counter()
599
+ self.history.clear()
585
600
 
586
601
  if (
587
602
  isinstance(msg, ChatDocument)
@@ -661,9 +676,7 @@ class Task:
661
676
  )
662
677
  # TODO decide on whether or not to print, based on is_async
663
678
  llm_model = (
664
- "no-LLM"
665
- if self.agent.config.llm is None
666
- else self.agent.config.llm.chat_model
679
+ "no-LLM" if self.agent.llm is None else self.agent.llm.config.chat_model
667
680
  )
668
681
  if not settings.quiet:
669
682
  print(
@@ -774,7 +787,7 @@ class Task:
774
787
  # skip trying other responders in this step
775
788
  break
776
789
  if not found_response:
777
- self._process_invalid_step_result(parent)
790
+ self._process_invalid_step_result()
778
791
  self._show_pending_message_if_debug()
779
792
  return self.pending_message
780
793
 
@@ -866,7 +879,7 @@ class Task:
866
879
  # skip trying other responders in this step
867
880
  break
868
881
  if not found_response:
869
- self._process_invalid_step_result(parent)
882
+ self._process_invalid_step_result()
870
883
  self._show_pending_message_if_debug()
871
884
  return self.pending_message
872
885
 
@@ -901,30 +914,13 @@ class Task:
901
914
  if self.pending_message is not None:
902
915
  hashed_msg = hash(str(self.pending_message))
903
916
  self.message_counter.update([hashed_msg])
904
- self.history_count.append(self.message_counter[hashed_msg])
917
+ self.history.append(hashed_msg)
905
918
 
906
- def _process_invalid_step_result(self, parent: ChatDocument | None) -> None:
919
+ def _process_invalid_step_result(self) -> None:
907
920
  """
908
- Since step had no valid result from any responder, decide whether to update the
909
- self.pending_message to a NO_ANSWER message from the opposite entity,
910
- or leave it as is.
911
- Args:
912
- parent (ChatDocument|None): parent message of the current message
921
+ No valid result from any responder => increment stalled counter.
913
922
  """
914
923
  self.n_stalled_steps += 1
915
- if (not self.task_progress or self.allow_null_result) and not self.is_pass_thru:
916
- # There has been no progress at all in this task, so we
917
- # update the pending_message to a dummy NO_ANSWER msg
918
- # from the entity 'opposite' to the current pending_sender,
919
- # so we show "progress" and avoid getting stuck in an infinite loop.
920
- responder = (
921
- Entity.LLM if self.pending_sender == Entity.USER else Entity.USER
922
- )
923
- self.pending_message = ChatDocument(
924
- content=NO_ANSWER,
925
- metadata=ChatDocMetaData(sender=responder, parent=parent),
926
- )
927
- self.pending_sender = responder
928
924
  self.log_message(self.pending_sender, self.pending_message, mark=True)
929
925
 
930
926
  def _show_pending_message_if_debug(self) -> None:
@@ -1059,7 +1055,6 @@ class Task:
1059
1055
  tool_messages = result_msg.tool_messages if result_msg else []
1060
1056
  block = result_msg.metadata.block if result_msg else None
1061
1057
  recipient = result_msg.metadata.recipient if result_msg else None
1062
- responder = result_msg.metadata.parent_responder if result_msg else None
1063
1058
  tool_ids = result_msg.metadata.tool_ids if result_msg else []
1064
1059
  status = result_msg.metadata.status if result_msg else None
1065
1060
 
@@ -1075,7 +1070,6 @@ class Task:
1075
1070
  sender=Entity.USER,
1076
1071
  block=block,
1077
1072
  status=status,
1078
- parent_responder=responder,
1079
1073
  sender_name=self.name,
1080
1074
  recipient=recipient,
1081
1075
  tool_ids=tool_ids,
@@ -1145,9 +1139,9 @@ class Task:
1145
1139
  So if you plot these frequencies in decreasing order,
1146
1140
  you will see a big drop in the plot, from m to m+1.
1147
1141
  We call the freqs until m the "dominant" freqs.
1148
- 2. Say we found m such dominant frequencies.
1149
- If these are the same as the freqs of the last m messages,
1150
- then we are likely in a loop.
1142
+ 2. Say we found m such dominant messages
1143
+ If the set of last (W * m) messages are the same as the
1144
+ set of m dominant messages, then we are likely in a loop.
1151
1145
  """
1152
1146
  max_cycle_len = self.config.inf_loop_cycle_len
1153
1147
  if max_cycle_len <= 0:
@@ -1158,6 +1152,7 @@ class Task:
1158
1152
  # we haven't seen enough messages to detect a loop
1159
1153
  return False
1160
1154
 
1155
+ # recall there's always a dummy msg with freq = 1
1161
1156
  most_common_msg_counts: List[Tuple[str, int]] = (
1162
1157
  self.message_counter.most_common(max_cycle_len + 1)
1163
1158
  )
@@ -1170,7 +1165,7 @@ class Task:
1170
1165
  ratios = counts[:-1] / counts[1:]
1171
1166
  diffs = counts[:-1] - counts[1:]
1172
1167
  indices = np.where((ratios > F) & (diffs > wait_factor))[0]
1173
- m = indices[0] if indices.size > 0 else -1
1168
+ m = indices[-1] if indices.size > 0 else -1
1174
1169
  if m < 0:
1175
1170
  # no dominance found, but...
1176
1171
  if len(most_common_msg_counts) <= max_cycle_len:
@@ -1186,12 +1181,13 @@ class Task:
1186
1181
  return False
1187
1182
 
1188
1183
  dominant_msg_counts = most_common_msg_counts[: m + 1]
1189
- # if the dominant m message counts are the same as the
1190
- # counts of the last m messages, then we are likely in a loop
1191
- dominant_counts = sorted([c for _, c in dominant_msg_counts])
1192
- recent_counts = sorted(list(self.history_count)[-(m + 1) :])
1193
-
1194
- return dominant_counts == recent_counts
1184
+ # if the SET of dominant m messages is the same as the
1185
+ # the SET of last m*w messages, (where w = config.inf_loop_wait_factor),
1186
+ # then we are likely in a loop
1187
+ dominant_msgs = set([msg for msg, _ in dominant_msg_counts])
1188
+ lookback = wait_factor * (m + 1)
1189
+ recent_msgs = set(list(self.history)[-lookback:])
1190
+ return dominant_msgs == recent_msgs
1195
1191
 
1196
1192
  def done(
1197
1193
  self, result: ChatDocument | None = None, r: Responder | None = None
@@ -1375,9 +1371,18 @@ class Task:
1375
1371
  )
1376
1372
 
1377
1373
  def _can_respond(self, e: Responder) -> bool:
1378
- if self.pending_sender == e:
1379
- # Responder cannot respond to its own message
1374
+ user_can_respond = self.interactive or (
1375
+ # regardless of self.interactive, if a msg is explicitly addressed to
1376
+ # user, then wait for user response
1377
+ self.pending_message is not None
1378
+ and self.pending_message.metadata.recipient == Entity.USER
1379
+ )
1380
+
1381
+ if self.pending_sender == e or (e == Entity.USER and not user_can_respond):
1382
+ # sender is same as e (an entity cannot respond to its own msg),
1383
+ # or user cannot respond
1380
1384
  return False
1385
+
1381
1386
  if self.pending_message is None:
1382
1387
  return True
1383
1388
  if self._recipient_mismatch(e):
@@ -421,7 +421,9 @@ class OpenAIGPT(LanguageModel):
421
421
  self.api_base = "http://" + self.api_base
422
422
  elif self.config.chat_model.startswith("ollama/"):
423
423
  self.config.ollama = True
424
- self.api_base = OLLAMA_BASE_URL
424
+
425
+ # use api_base from config if set, else fall back on OLLAMA_BASE_URL
426
+ self.api_base = self.config.api_base or OLLAMA_BASE_URL
425
427
  self.api_key = OLLAMA_API_KEY
426
428
  self.config.chat_model = self.config.chat_model.replace("ollama/", "")
427
429
  else:
@@ -68,7 +68,7 @@ class QdrantDBConfig(VectorStoreConfig):
68
68
  embedding: EmbeddingModelsConfig = OpenAIEmbeddingsConfig()
69
69
  distance: str = Distance.COSINE
70
70
  use_sparse_embeddings: bool = False
71
- sparse_embedding_model: str = ""
71
+ sparse_embedding_model: str = "naver/splade-v3-distilbert"
72
72
  sparse_limit: int = 3
73
73
 
74
74
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.1.258"
3
+ version = "0.1.261"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
File without changes