langroid 0.11.0__tar.gz → 0.12.0__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 (143) hide show
  1. {langroid-0.11.0 → langroid-0.12.0}/PKG-INFO +4 -2
  2. {langroid-0.11.0 → langroid-0.12.0}/README.md +3 -1
  3. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/batch.py +116 -28
  4. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/doc_chat_agent.py +2 -2
  5. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/task.py +2 -1
  6. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/mock_lm.py +23 -3
  7. {langroid-0.11.0 → langroid-0.12.0}/pyproject.toml +1 -1
  8. {langroid-0.11.0 → langroid-0.12.0}/LICENSE +0 -0
  9. {langroid-0.11.0 → langroid-0.12.0}/langroid/__init__.py +0 -0
  10. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/__init__.py +0 -0
  11. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/base.py +0 -0
  12. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/callbacks/__init__.py +0 -0
  13. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/callbacks/chainlit.py +0 -0
  14. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/chat_agent.py +0 -0
  15. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/chat_document.py +0 -0
  16. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/helpers.py +0 -0
  17. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/junk +0 -0
  18. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/openai_assistant.py +0 -0
  19. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/__init__.py +0 -0
  20. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  21. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  22. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  23. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  24. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  25. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/lance_tools.py +0 -0
  26. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  27. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  28. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  29. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  30. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  31. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  32. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/retriever_agent.py +0 -0
  33. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/__init__.py +0 -0
  34. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  35. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  36. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  37. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  38. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  39. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  40. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/special/table_chat_agent.py +0 -0
  41. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/structured_message.py +0 -0
  42. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tool_message.py +0 -0
  43. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/__init__.py +0 -0
  44. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  45. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/extract_tool.py +0 -0
  46. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/generator_tool.py +0 -0
  47. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/google_search_tool.py +0 -0
  48. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  49. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/note_tool.py +0 -0
  50. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/orchestration.py +0 -0
  51. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/recipient_tool.py +0 -0
  52. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  53. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/rewind_tool.py +0 -0
  54. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/run_python_code.py +0 -0
  55. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  56. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent/typed_task.py +0 -0
  57. {langroid-0.11.0 → langroid-0.12.0}/langroid/agent_config.py +0 -0
  58. {langroid-0.11.0 → langroid-0.12.0}/langroid/cachedb/__init__.py +0 -0
  59. {langroid-0.11.0 → langroid-0.12.0}/langroid/cachedb/base.py +0 -0
  60. {langroid-0.11.0 → langroid-0.12.0}/langroid/cachedb/momento_cachedb.py +0 -0
  61. {langroid-0.11.0 → langroid-0.12.0}/langroid/cachedb/redis_cachedb.py +0 -0
  62. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/__init__.py +0 -0
  63. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/base.py +0 -0
  64. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/clustering.py +0 -0
  65. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/models.py +0 -0
  66. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  67. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  68. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  69. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  70. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  71. {langroid-0.11.0 → langroid-0.12.0}/langroid/embedding_models/remote_embeds.py +0 -0
  72. {langroid-0.11.0 → langroid-0.12.0}/langroid/exceptions.py +0 -0
  73. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/.chainlit/config.toml +0 -0
  74. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  75. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/__init__.py +0 -0
  76. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/azure_openai.py +0 -0
  77. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/base.py +0 -0
  78. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/config.py +0 -0
  79. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/openai_gpt.py +0 -0
  80. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  81. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  82. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  83. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  84. {langroid-0.11.0 → langroid-0.12.0}/langroid/language_models/utils.py +0 -0
  85. {langroid-0.11.0 → langroid-0.12.0}/langroid/mytypes.py +0 -0
  86. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/__init__.py +0 -0
  87. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/agent_chats.py +0 -0
  88. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/code-parsing.md +0 -0
  89. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/code_parser.py +0 -0
  90. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/config.py +0 -0
  91. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/document_parser.py +0 -0
  92. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/image_text.py +0 -0
  93. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/para_sentence_split.py +0 -0
  94. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/parse_json.py +0 -0
  95. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/parser.py +0 -0
  96. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/repo_loader.py +0 -0
  97. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/routing.py +0 -0
  98. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/search.py +0 -0
  99. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/spider.py +0 -0
  100. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/table_loader.py +0 -0
  101. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/url_loader.py +0 -0
  102. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/url_loader_cookies.py +0 -0
  103. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/urls.py +0 -0
  104. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/utils.py +0 -0
  105. {langroid-0.11.0 → langroid-0.12.0}/langroid/parsing/web_search.py +0 -0
  106. {langroid-0.11.0 → langroid-0.12.0}/langroid/prompts/__init__.py +0 -0
  107. {langroid-0.11.0 → langroid-0.12.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  108. {langroid-0.11.0 → langroid-0.12.0}/langroid/prompts/dialog.py +0 -0
  109. {langroid-0.11.0 → langroid-0.12.0}/langroid/prompts/prompts_config.py +0 -0
  110. {langroid-0.11.0 → langroid-0.12.0}/langroid/prompts/templates.py +0 -0
  111. {langroid-0.11.0 → langroid-0.12.0}/langroid/pydantic_v1/__init__.py +0 -0
  112. {langroid-0.11.0 → langroid-0.12.0}/langroid/pydantic_v1/main.py +0 -0
  113. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/.chainlit/config.toml +0 -0
  114. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  115. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/__init__.py +0 -0
  116. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/algorithms/__init__.py +0 -0
  117. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/algorithms/graph.py +0 -0
  118. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/configuration.py +0 -0
  119. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/constants.py +0 -0
  120. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/docker.py +0 -0
  121. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/globals.py +0 -0
  122. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/llms/__init__.py +0 -0
  123. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/llms/strings.py +0 -0
  124. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/logging.py +0 -0
  125. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/object_registry.py +0 -0
  126. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/output/__init__.py +0 -0
  127. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/output/citations.py +0 -0
  128. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/output/printing.py +0 -0
  129. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/output/status.py +0 -0
  130. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/pandas_utils.py +0 -0
  131. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/pydantic_utils.py +0 -0
  132. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/system.py +0 -0
  133. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/types.py +0 -0
  134. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/web/__init__.py +0 -0
  135. {langroid-0.11.0 → langroid-0.12.0}/langroid/utils/web/login.py +0 -0
  136. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/__init__.py +0 -0
  137. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/base.py +0 -0
  138. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/chromadb.py +0 -0
  139. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/lancedb.py +0 -0
  140. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/meilisearch.py +0 -0
  141. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/momento.py +0 -0
  142. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/qdrant_cloud.py +0 -0
  143. {langroid-0.11.0 → langroid-0.12.0}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -239,7 +239,9 @@ teacher_task.run()
239
239
  <details>
240
240
  <summary> <b>Click to expand</b></summary>
241
241
 
242
- - **Aug 2024:**
242
+ - **Aug 2024:**
243
+ - **[0.11.0](https://github.com/langroid/langroid/releases/tag/0.11.0)** Polymorphic `Task.run(), Task.run_async`.
244
+ - **[0.10.0](https://github.com/langroid/langroid/releases/tag/0.10.0)** Allow tool handlers to return arbitrary result type, including other tools.
243
245
  - **[0.9.0](https://github.com/langroid/langroid/releases/tag/0.9.0)** Orchestration Tools, to signal various task statuses, and to pass messages between agents.
244
246
  - **[0.7.0](https://github.com/langroid/langroid/releases/tag/0.7.0)** OpenAI tools API support, including multi-tools.
245
247
  - **Jul 2024:**
@@ -132,7 +132,9 @@ teacher_task.run()
132
132
  <details>
133
133
  <summary> <b>Click to expand</b></summary>
134
134
 
135
- - **Aug 2024:**
135
+ - **Aug 2024:**
136
+ - **[0.11.0](https://github.com/langroid/langroid/releases/tag/0.11.0)** Polymorphic `Task.run(), Task.run_async`.
137
+ - **[0.10.0](https://github.com/langroid/langroid/releases/tag/0.10.0)** Allow tool handlers to return arbitrary result type, including other tools.
136
138
  - **[0.9.0](https://github.com/langroid/langroid/releases/tag/0.9.0)** Orchestration Tools, to signal various task statuses, and to pass messages between agents.
137
139
  - **[0.7.0](https://github.com/langroid/langroid/releases/tag/0.7.0)** OpenAI tools API support, including multi-tools.
138
140
  - **Jul 2024:**
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import copy
3
3
  import inspect
4
- from typing import Any, Callable, Coroutine, Iterable, List, Optional, TypeVar
4
+ from typing import Any, Callable, Coroutine, Iterable, List, Optional, TypeVar, cast
5
5
 
6
6
  from dotenv import load_dotenv
7
7
 
@@ -26,6 +26,7 @@ def run_batch_task_gen(
26
26
  items: list[T],
27
27
  input_map: Callable[[T], str | ChatDocument] = lambda x: str(x),
28
28
  output_map: Callable[[ChatDocument | None], U] = lambda x: x, # type: ignore
29
+ stop_on_first_result: bool = False,
29
30
  sequential: bool = True,
30
31
  batch_size: Optional[int] = None,
31
32
  turns: int = -1,
@@ -33,7 +34,7 @@ def run_batch_task_gen(
33
34
  handle_exceptions: bool = False,
34
35
  max_cost: float = 0.0,
35
36
  max_tokens: int = 0,
36
- ) -> list[U]:
37
+ ) -> list[Optional[U]]:
37
38
  """
38
39
  Generate and run copies of a task async/concurrently one per item in `items` list.
39
40
  For each item, apply `input_map` to get the initial message to process.
@@ -44,7 +45,13 @@ def run_batch_task_gen(
44
45
  input_map (Callable[[T], str|ChatDocument]): function to map item to
45
46
  initial message to process
46
47
  output_map (Callable[[ChatDocument|str], U]): function to map result
47
- to final result
48
+ to final result. If stop_on_first_result is enabled, then
49
+ map any invalid output to None. We continue until some non-None
50
+ result is obtained.
51
+ stop_on_first_result (bool): whether to stop after the first valid
52
+ (not-None) result. In this case all other tasks are
53
+ cancelled, and their corresponding result is None in the
54
+ returned list.
48
55
  sequential (bool): whether to run sequentially
49
56
  (e.g. some APIs such as ooba don't support concurrent requests)
50
57
  batch_size (Optional[int]): The number of tasks to run at a time,
@@ -57,39 +64,91 @@ def run_batch_task_gen(
57
64
 
58
65
 
59
66
  Returns:
60
- list[Any]: list of final results
67
+ list[Optional[U]]: list of final results. Always list[U] if
68
+ `stop_on_first_result` is disabled
61
69
  """
62
70
  inputs = [input_map(item) for item in items]
63
71
 
64
- async def _do_task(input: str | ChatDocument, i: int) -> Optional[ChatDocument]:
72
+ async def _do_task(
73
+ input: str | ChatDocument,
74
+ i: int,
75
+ return_idx: Optional[int] = None,
76
+ ) -> BaseException | Optional[ChatDocument] | tuple[int, Optional[ChatDocument]]:
65
77
  task_i = gen_task(i)
66
78
  if task_i.agent.llm is not None:
67
79
  task_i.agent.llm.set_stream(False)
68
80
  task_i.agent.config.show_stats = False
69
-
70
- result = await task_i.run_async(
71
- input, turns=turns, max_cost=max_cost, max_tokens=max_tokens
72
- )
73
- return result
81
+ try:
82
+ result = await task_i.run_async(
83
+ input, turns=turns, max_cost=max_cost, max_tokens=max_tokens
84
+ )
85
+ if return_idx is not None:
86
+ return return_idx, result
87
+ else:
88
+ return result
89
+ except asyncio.CancelledError as e:
90
+ task_i.kill()
91
+ if handle_exceptions:
92
+ return e
93
+ else:
94
+ raise e
95
+ except BaseException as e:
96
+ if handle_exceptions:
97
+ return e
98
+ else:
99
+ raise e
74
100
 
75
101
  async def _do_all(
76
102
  inputs: Iterable[str | ChatDocument], start_idx: int = 0
77
- ) -> list[U]:
103
+ ) -> list[Optional[U]]:
78
104
  results: list[Optional[ChatDocument]] = []
79
- if sequential:
80
- for i, input in enumerate(inputs):
105
+ if stop_on_first_result:
106
+ outputs: list[Optional[U]] = [None] * len(list(inputs))
107
+ tasks = set(
108
+ asyncio.create_task(_do_task(input, i + start_idx, return_idx=i))
109
+ for i, input in enumerate(inputs)
110
+ )
111
+ while tasks:
81
112
  try:
82
- result = await _do_task(input, i + start_idx)
83
- except BaseException as e:
84
- if handle_exceptions:
85
- result = None
86
- else:
87
- raise e
113
+ done, tasks = await asyncio.wait(
114
+ tasks, return_when=asyncio.FIRST_COMPLETED
115
+ )
116
+ for task in done:
117
+ idx_result = task.result()
118
+ if not isinstance(idx_result, tuple):
119
+ continue
120
+ index, output = idx_result
121
+ outputs[index] = output_map(output)
122
+
123
+ if any(r is not None for r in outputs):
124
+ return outputs
125
+ finally:
126
+ # Cancel all remaining tasks
127
+ for task in tasks:
128
+ task.cancel()
129
+ # Wait for cancellations to complete
130
+ try:
131
+ await asyncio.gather(*tasks, return_exceptions=True)
132
+ except BaseException as e:
133
+ if not handle_exceptions:
134
+ raise e
135
+ return outputs
136
+ elif sequential:
137
+ for i, input in enumerate(inputs):
138
+ result: Optional[ChatDocument] | BaseException = await _do_task(
139
+ input, i + start_idx
140
+ ) # type: ignore
141
+
142
+ if isinstance(result, BaseException):
143
+ result = None
144
+
88
145
  results.append(result)
89
146
  else:
90
- results_with_exceptions = await asyncio.gather(
91
- *(_do_task(input, i + start_idx) for i, input in enumerate(inputs)),
92
- return_exceptions=handle_exceptions,
147
+ results_with_exceptions = cast(
148
+ list[Optional[ChatDocument | BaseException]],
149
+ await asyncio.gather(
150
+ *(_do_task(input, i + start_idx) for i, input in enumerate(inputs)),
151
+ ),
93
152
  )
94
153
 
95
154
  results = [
@@ -99,7 +158,7 @@ def run_batch_task_gen(
99
158
 
100
159
  return list(map(output_map, results))
101
160
 
102
- results: List[U] = []
161
+ results: List[Optional[U]] = []
103
162
  if batch_size is None:
104
163
  msg = message or f"[bold green]Running {len(items)} tasks:"
105
164
 
@@ -113,8 +172,11 @@ def run_batch_task_gen(
113
172
  complete_str = f", {start_idx} complete" if start_idx > 0 else ""
114
173
  msg = message or f"[bold green]Running {len(items)} tasks{complete_str}:"
115
174
 
116
- with status(msg), SuppressLoggerWarnings():
117
- results.extend(asyncio.run(_do_all(batch, start_idx=start_idx)))
175
+ if stop_on_first_result and any(r is not None for r in results):
176
+ results.extend([None] * len(batch))
177
+ else:
178
+ with status(msg), SuppressLoggerWarnings():
179
+ results.extend(asyncio.run(_do_all(batch, start_idx=start_idx)))
118
180
 
119
181
  return results
120
182
 
@@ -124,12 +186,13 @@ def run_batch_tasks(
124
186
  items: list[T],
125
187
  input_map: Callable[[T], str | ChatDocument] = lambda x: str(x),
126
188
  output_map: Callable[[ChatDocument | None], U] = lambda x: x, # type: ignore
189
+ stop_on_first_result: bool = False,
127
190
  sequential: bool = True,
128
191
  batch_size: Optional[int] = None,
129
192
  turns: int = -1,
130
193
  max_cost: float = 0.0,
131
194
  max_tokens: int = 0,
132
- ) -> List[U]:
195
+ ) -> List[Optional[U]]:
133
196
  """
134
197
  Run copies of `task` async/concurrently one per item in `items` list.
135
198
  For each item, apply `input_map` to get the initial message to process.
@@ -150,7 +213,8 @@ def run_batch_tasks(
150
213
  max_tokens: int: maximum token usage (in and out) (default 0 for unlimited)
151
214
 
152
215
  Returns:
153
- list[Any]: list of final results
216
+ list[Optional[U]]: list of final results. Always list[U] if
217
+ `stop_on_first_result` is disabled
154
218
  """
155
219
  message = f"[bold green]Running {len(items)} copies of {task.name}..."
156
220
  return run_batch_task_gen(
@@ -158,6 +222,7 @@ def run_batch_tasks(
158
222
  items,
159
223
  input_map,
160
224
  output_map,
225
+ stop_on_first_result,
161
226
  sequential,
162
227
  batch_size,
163
228
  turns,
@@ -176,6 +241,7 @@ def run_batch_agent_method(
176
241
  input_map: Callable[[Any], str | ChatDocument] = lambda x: str(x),
177
242
  output_map: Callable[[ChatDocument | None], Any] = lambda x: x,
178
243
  sequential: bool = True,
244
+ stop_on_first_result: bool = False,
179
245
  ) -> List[Any]:
180
246
  """
181
247
  Run the `method` on copies of `agent`, async/concurrently one per
@@ -225,7 +291,25 @@ def run_batch_agent_method(
225
291
  return output_map(result)
226
292
 
227
293
  async def _do_all() -> List[Any]:
228
- if sequential:
294
+ if stop_on_first_result:
295
+ tasks = [
296
+ asyncio.create_task(_do_task(input, i))
297
+ for i, input in enumerate(inputs)
298
+ ]
299
+ results = [None] * len(tasks)
300
+ try:
301
+ done, pending = await asyncio.wait(
302
+ tasks, return_when=asyncio.FIRST_COMPLETED
303
+ )
304
+ for task in done:
305
+ index = tasks.index(task)
306
+ results[index] = await task
307
+ finally:
308
+ for task in pending:
309
+ task.cancel()
310
+ await asyncio.gather(*pending, return_exceptions=True)
311
+ return results
312
+ elif sequential:
229
313
  results = []
230
314
  for i, input in enumerate(inputs):
231
315
  result = await _do_task(input, i)
@@ -249,6 +333,7 @@ def llm_response_batch(
249
333
  input_map: Callable[[Any], str | ChatDocument] = lambda x: str(x),
250
334
  output_map: Callable[[ChatDocument | None], Any] = lambda x: x,
251
335
  sequential: bool = True,
336
+ stop_on_first_result: bool = False,
252
337
  ) -> List[Any]:
253
338
  return run_batch_agent_method(
254
339
  agent,
@@ -257,6 +342,7 @@ def llm_response_batch(
257
342
  input_map=input_map,
258
343
  output_map=output_map,
259
344
  sequential=sequential,
345
+ stop_on_first_result=stop_on_first_result,
260
346
  )
261
347
 
262
348
 
@@ -266,6 +352,7 @@ def agent_response_batch(
266
352
  input_map: Callable[[Any], str | ChatDocument] = lambda x: str(x),
267
353
  output_map: Callable[[ChatDocument | None], Any] = lambda x: x,
268
354
  sequential: bool = True,
355
+ stop_on_first_result: bool = False,
269
356
  ) -> List[Any]:
270
357
  return run_batch_agent_method(
271
358
  agent,
@@ -274,4 +361,5 @@ def agent_response_batch(
274
361
  input_map=input_map,
275
362
  output_map=output_map,
276
363
  sequential=sequential,
364
+ stop_on_first_result=stop_on_first_result,
277
365
  )
@@ -1253,12 +1253,12 @@ class DocChatAgent(ChatAgent):
1253
1253
  interactive=False,
1254
1254
  )
1255
1255
 
1256
- extracts = run_batch_tasks(
1256
+ extracts: list[str] = run_batch_tasks(
1257
1257
  task,
1258
1258
  passages,
1259
1259
  input_map=lambda msg: msg.content,
1260
1260
  output_map=lambda ans: ans.content if ans is not None else NO_ANSWER,
1261
- )
1261
+ ) # type: ignore
1262
1262
 
1263
1263
  # Caution: Retain ALL other fields in the Documents (which could be
1264
1264
  # other than just `content` and `metadata`), while simply replacing
@@ -1000,9 +1000,10 @@ class Task:
1000
1000
  and not self.human_tried
1001
1001
  and not self.agent.has_tool_message_attempt(self.pending_message)
1002
1002
  ):
1003
- # When in interactive mode,
1004
1003
  # Give human first chance if they haven't been tried in last step,
1005
1004
  # and the msg is not a tool-call attempt;
1005
+ # (When `interactive=False`, human is only allowed to respond only if
1006
+ # if explicitly addressed)
1006
1007
  # This ensures human gets a chance to respond,
1007
1008
  # other than to a LLM tool-call.
1008
1009
  # When there's a tool msg attempt we want the
@@ -1,6 +1,6 @@
1
1
  """Mock Language Model for testing"""
2
2
 
3
- from typing import Callable, Dict, List, Optional, Union
3
+ from typing import Awaitable, Callable, Dict, List, Optional, Union
4
4
 
5
5
  import langroid.language_models as lm
6
6
  from langroid.language_models import LLMResponse
@@ -28,6 +28,7 @@ class MockLMConfig(LLMConfig):
28
28
 
29
29
  response_dict: Dict[str, str] = {}
30
30
  response_fn: Callable[[str], None | str] = none_fn
31
+ response_fn_async: Optional[Callable[[str], Awaitable[Optional[str]]]] = None
31
32
  default_response: str = "Mock response"
32
33
 
33
34
  type: str = "mock"
@@ -52,6 +53,25 @@ class MockLM(LanguageModel):
52
53
  cached=False,
53
54
  )
54
55
 
56
+ async def _response_async(self, msg: str) -> LLMResponse:
57
+ # response is based on this fallback order:
58
+ # - response_dict
59
+ # - response_fn_async
60
+ # - response_fn
61
+ # - default_response
62
+ if self.config.response_fn_async is not None:
63
+ response = await self.config.response_fn_async(msg)
64
+ else:
65
+ response = self.config.response_fn(msg)
66
+
67
+ mapped_response = self.config.response_dict.get(
68
+ msg, response or self.config.default_response
69
+ )
70
+ return lm.LLMResponse(
71
+ message=to_string(mapped_response),
72
+ cached=False,
73
+ )
74
+
55
75
  def chat(
56
76
  self,
57
77
  messages: Union[str, List[lm.LLMMessage]],
@@ -80,7 +100,7 @@ class MockLM(LanguageModel):
80
100
  Mock chat function for testing
81
101
  """
82
102
  last_msg = messages[-1].content if isinstance(messages, list) else messages
83
- return self._response(last_msg)
103
+ return await self._response_async(last_msg)
84
104
 
85
105
  def generate(self, prompt: str, max_tokens: int = 200) -> lm.LLMResponse:
86
106
  """
@@ -92,7 +112,7 @@ class MockLM(LanguageModel):
92
112
  """
93
113
  Mock generate function for testing
94
114
  """
95
- return self._response(prompt)
115
+ return await self._response_async(prompt)
96
116
 
97
117
  def get_stream(self) -> bool:
98
118
  return False
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.11.0"
3
+ version = "0.12.0"
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
File without changes
File without changes