langroid 0.11.0__tar.gz → 0.13.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.13.0}/PKG-INFO +4 -2
  2. {langroid-0.11.0 → langroid-0.13.0}/README.md +3 -1
  3. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/batch.py +116 -28
  4. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/doc_chat_agent.py +19 -14
  5. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/task.py +2 -1
  6. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/mock_lm.py +23 -3
  7. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/search.py +14 -11
  8. {langroid-0.11.0 → langroid-0.13.0}/pyproject.toml +1 -1
  9. {langroid-0.11.0 → langroid-0.13.0}/LICENSE +0 -0
  10. {langroid-0.11.0 → langroid-0.13.0}/langroid/__init__.py +0 -0
  11. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/__init__.py +0 -0
  12. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/base.py +0 -0
  13. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/callbacks/__init__.py +0 -0
  14. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/callbacks/chainlit.py +0 -0
  15. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/chat_agent.py +0 -0
  16. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/chat_document.py +0 -0
  17. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/helpers.py +0 -0
  18. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/junk +0 -0
  19. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/openai_assistant.py +0 -0
  20. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/__init__.py +0 -0
  21. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  22. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  23. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  24. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  25. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  26. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/lance_tools.py +0 -0
  27. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  28. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  29. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  30. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  31. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  32. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  33. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/retriever_agent.py +0 -0
  34. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/__init__.py +0 -0
  35. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  36. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  37. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  38. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  39. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  40. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  41. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/special/table_chat_agent.py +0 -0
  42. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/structured_message.py +0 -0
  43. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tool_message.py +0 -0
  44. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/__init__.py +0 -0
  45. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  46. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/extract_tool.py +0 -0
  47. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/generator_tool.py +0 -0
  48. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/google_search_tool.py +0 -0
  49. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  50. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/note_tool.py +0 -0
  51. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/orchestration.py +0 -0
  52. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/recipient_tool.py +0 -0
  53. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  54. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/rewind_tool.py +0 -0
  55. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/run_python_code.py +0 -0
  56. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  57. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent/typed_task.py +0 -0
  58. {langroid-0.11.0 → langroid-0.13.0}/langroid/agent_config.py +0 -0
  59. {langroid-0.11.0 → langroid-0.13.0}/langroid/cachedb/__init__.py +0 -0
  60. {langroid-0.11.0 → langroid-0.13.0}/langroid/cachedb/base.py +0 -0
  61. {langroid-0.11.0 → langroid-0.13.0}/langroid/cachedb/momento_cachedb.py +0 -0
  62. {langroid-0.11.0 → langroid-0.13.0}/langroid/cachedb/redis_cachedb.py +0 -0
  63. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/__init__.py +0 -0
  64. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/base.py +0 -0
  65. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/clustering.py +0 -0
  66. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/models.py +0 -0
  67. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  68. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  69. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  70. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  71. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  72. {langroid-0.11.0 → langroid-0.13.0}/langroid/embedding_models/remote_embeds.py +0 -0
  73. {langroid-0.11.0 → langroid-0.13.0}/langroid/exceptions.py +0 -0
  74. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/.chainlit/config.toml +0 -0
  75. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  76. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/__init__.py +0 -0
  77. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/azure_openai.py +0 -0
  78. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/base.py +0 -0
  79. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/config.py +0 -0
  80. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/openai_gpt.py +0 -0
  81. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  82. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  83. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  84. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  85. {langroid-0.11.0 → langroid-0.13.0}/langroid/language_models/utils.py +0 -0
  86. {langroid-0.11.0 → langroid-0.13.0}/langroid/mytypes.py +0 -0
  87. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/__init__.py +0 -0
  88. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/agent_chats.py +0 -0
  89. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/code-parsing.md +0 -0
  90. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/code_parser.py +0 -0
  91. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/config.py +0 -0
  92. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/document_parser.py +0 -0
  93. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/image_text.py +0 -0
  94. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/para_sentence_split.py +0 -0
  95. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/parse_json.py +0 -0
  96. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/parser.py +0 -0
  97. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/repo_loader.py +0 -0
  98. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/routing.py +0 -0
  99. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/spider.py +0 -0
  100. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/table_loader.py +0 -0
  101. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/url_loader.py +0 -0
  102. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/url_loader_cookies.py +0 -0
  103. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/urls.py +0 -0
  104. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/utils.py +0 -0
  105. {langroid-0.11.0 → langroid-0.13.0}/langroid/parsing/web_search.py +0 -0
  106. {langroid-0.11.0 → langroid-0.13.0}/langroid/prompts/__init__.py +0 -0
  107. {langroid-0.11.0 → langroid-0.13.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  108. {langroid-0.11.0 → langroid-0.13.0}/langroid/prompts/dialog.py +0 -0
  109. {langroid-0.11.0 → langroid-0.13.0}/langroid/prompts/prompts_config.py +0 -0
  110. {langroid-0.11.0 → langroid-0.13.0}/langroid/prompts/templates.py +0 -0
  111. {langroid-0.11.0 → langroid-0.13.0}/langroid/pydantic_v1/__init__.py +0 -0
  112. {langroid-0.11.0 → langroid-0.13.0}/langroid/pydantic_v1/main.py +0 -0
  113. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/.chainlit/config.toml +0 -0
  114. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  115. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/__init__.py +0 -0
  116. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/algorithms/__init__.py +0 -0
  117. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/algorithms/graph.py +0 -0
  118. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/configuration.py +0 -0
  119. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/constants.py +0 -0
  120. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/docker.py +0 -0
  121. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/globals.py +0 -0
  122. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/llms/__init__.py +0 -0
  123. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/llms/strings.py +0 -0
  124. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/logging.py +0 -0
  125. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/object_registry.py +0 -0
  126. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/output/__init__.py +0 -0
  127. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/output/citations.py +0 -0
  128. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/output/printing.py +0 -0
  129. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/output/status.py +0 -0
  130. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/pandas_utils.py +0 -0
  131. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/pydantic_utils.py +0 -0
  132. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/system.py +0 -0
  133. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/types.py +0 -0
  134. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/web/__init__.py +0 -0
  135. {langroid-0.11.0 → langroid-0.13.0}/langroid/utils/web/login.py +0 -0
  136. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/__init__.py +0 -0
  137. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/base.py +0 -0
  138. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/chromadb.py +0 -0
  139. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/lancedb.py +0 -0
  140. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/meilisearch.py +0 -0
  141. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/momento.py +0 -0
  142. {langroid-0.11.0 → langroid-0.13.0}/langroid/vector_store/qdrant_cloud.py +0 -0
  143. {langroid-0.11.0 → langroid-0.13.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.13.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
  )
@@ -49,7 +49,6 @@ from langroid.parsing.search import (
49
49
  from langroid.parsing.table_loader import describe_dataframe
50
50
  from langroid.parsing.url_loader import URLLoader
51
51
  from langroid.parsing.urls import get_list_from_user, get_urls_paths_bytes_indices
52
- from langroid.parsing.utils import batched
53
52
  from langroid.prompts.prompts_config import PromptsConfig
54
53
  from langroid.prompts.templates import SUMMARY_ANSWER_PROMPT_GPT4
55
54
  from langroid.utils.constants import NO_ANSWER
@@ -137,7 +136,6 @@ class DocChatAgentConfig(ChatAgentConfig):
137
136
  rerank_diversity: bool = True # rerank to maximize diversity?
138
137
  rerank_periphery: bool = True # rerank to avoid Lost In the Middle effect?
139
138
  rerank_after_adding_context: bool = True # rerank after adding context window?
140
- embed_batch_size: int = 500 # get embedding of at most this many at a time
141
139
  cache: bool = True # cache results
142
140
  debug: bool = False
143
141
  stream: bool = True # allow streaming where needed
@@ -400,7 +398,11 @@ class DocChatAgent(ChatAgent):
400
398
  if split:
401
399
  docs = self.parser.split(docs)
402
400
  else:
403
- self.parser.add_window_ids(docs)
401
+ if self.config.n_neighbor_chunks > 0:
402
+ self.parser.add_window_ids(docs)
403
+ # we're not splitting, so we mark each doc as a chunk
404
+ for d in docs:
405
+ d.metadata.is_chunk = True
404
406
  if self.vecdb is None:
405
407
  raise ValueError("VecDB not set")
406
408
 
@@ -422,10 +424,9 @@ class DocChatAgent(ChatAgent):
422
424
  + d.content
423
425
  )
424
426
  docs = docs[: self.config.parsing.max_chunks]
425
- # add embeddings in batches, to stay under limit of embeddings API
426
- batches = list(batched(docs, self.config.embed_batch_size))
427
- for batch in batches:
428
- self.vecdb.add_documents(batch)
427
+ # vecdb should take care of adding docs in batches;
428
+ # batching can be controlled via vecdb.config.batch_size
429
+ self.vecdb.add_documents(docs)
429
430
  self.original_docs_length = self.doc_length(docs)
430
431
  self.setup_documents(docs, filter=self.config.filter)
431
432
  return len(docs)
@@ -894,7 +895,9 @@ class DocChatAgent(ChatAgent):
894
895
  )
895
896
  return docs_scores
896
897
 
897
- def get_fuzzy_matches(self, query: str, multiple: int) -> List[Document]:
898
+ def get_fuzzy_matches(
899
+ self, query: str, multiple: int
900
+ ) -> List[Tuple[Document, float]]:
898
901
  # find similar docs using fuzzy matching:
899
902
  # these may sometimes be more likely to contain a relevant verbatim extract
900
903
  with status("[cyan]Finding fuzzy matches in chunks..."):
@@ -909,8 +912,8 @@ class DocChatAgent(ChatAgent):
909
912
  self.chunked_docs,
910
913
  self.chunked_docs_clean,
911
914
  k=self.config.parsing.n_similar_docs * multiple,
912
- words_before=self.config.n_fuzzy_neighbor_words,
913
- words_after=self.config.n_fuzzy_neighbor_words,
915
+ words_before=self.config.n_fuzzy_neighbor_words or None,
916
+ words_after=self.config.n_fuzzy_neighbor_words or None,
914
917
  )
915
918
  return fuzzy_match_docs
916
919
 
@@ -1127,12 +1130,14 @@ class DocChatAgent(ChatAgent):
1127
1130
  # ]
1128
1131
 
1129
1132
  if self.config.use_bm25_search:
1133
+ # TODO: Add score threshold in config
1130
1134
  docs_scores = self.get_similar_chunks_bm25(query, retrieval_multiple)
1131
1135
  passages += [d for (d, _) in docs_scores]
1132
1136
 
1133
1137
  if self.config.use_fuzzy_match:
1134
- fuzzy_match_docs = self.get_fuzzy_matches(query, retrieval_multiple)
1135
- passages += fuzzy_match_docs
1138
+ # TODO: Add score threshold in config
1139
+ fuzzy_match_doc_scores = self.get_fuzzy_matches(query, retrieval_multiple)
1140
+ passages += [d for (d, _) in fuzzy_match_doc_scores]
1136
1141
 
1137
1142
  # keep unique passages
1138
1143
  id2passage = {p.id(): p for p in passages}
@@ -1253,12 +1258,12 @@ class DocChatAgent(ChatAgent):
1253
1258
  interactive=False,
1254
1259
  )
1255
1260
 
1256
- extracts = run_batch_tasks(
1261
+ extracts: list[str] = run_batch_tasks(
1257
1262
  task,
1258
1263
  passages,
1259
1264
  input_map=lambda msg: msg.content,
1260
1265
  output_map=lambda ans: ans.content if ans is not None else NO_ANSWER,
1261
- )
1266
+ ) # type: ignore
1262
1267
 
1263
1268
  # Caution: Retain ALL other fields in the Documents (which could be
1264
1269
  # 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
@@ -27,7 +27,7 @@ def find_fuzzy_matches_in_docs(
27
27
  k: int,
28
28
  words_before: int | None = None,
29
29
  words_after: int | None = None,
30
- ) -> List[Document]:
30
+ ) -> List[Tuple[Document, float]]:
31
31
  """
32
32
  Find approximate matches of the query in the docs and return surrounding
33
33
  characters.
@@ -35,6 +35,7 @@ def find_fuzzy_matches_in_docs(
35
35
  Args:
36
36
  query (str): The search string.
37
37
  docs (List[Document]): List of Document objects to search through.
38
+ docs_clean (List[Document]): List of Document objects with cleaned content.
38
39
  k (int): Number of best matches to return.
39
40
  words_before (int|None): Number of words to include before each match.
40
41
  Default None => return max
@@ -42,8 +43,7 @@ def find_fuzzy_matches_in_docs(
42
43
  Default None => return max
43
44
 
44
45
  Returns:
45
- List[Document]: List of Documents containing the matches,
46
- including the given number of words around the match.
46
+ List[Tuple[Document,float]]: List of (Document, score) tuples.
47
47
  """
48
48
  if len(docs) == 0:
49
49
  return []
@@ -54,19 +54,19 @@ def find_fuzzy_matches_in_docs(
54
54
  scorer=fuzz.partial_ratio,
55
55
  )
56
56
 
57
- real_matches = [m for m, score in best_matches if score > 50]
57
+ real_matches = [(m, score) for m, score in best_matches if score > 50]
58
58
  # find the original docs that corresponding to the matches
59
59
  orig_doc_matches = []
60
- for i, m in enumerate(real_matches):
60
+ for i, (m, s) in enumerate(real_matches):
61
61
  for j, doc_clean in enumerate(docs_clean):
62
62
  if m in doc_clean.content:
63
- orig_doc_matches.append(docs[j])
63
+ orig_doc_matches.append((docs[j], s))
64
64
  break
65
65
  if words_after is None and words_before is None:
66
66
  return orig_doc_matches
67
67
  if len(orig_doc_matches) == 0:
68
68
  return []
69
- if set(orig_doc_matches[0].__fields__) != {"content", "metadata"}:
69
+ if set(orig_doc_matches[0][0].__fields__) != {"content", "metadata"}:
70
70
  # If there are fields beyond just content and metadata,
71
71
  # we do NOT want to create new document objects with content fields
72
72
  # based on words_before and words_after, since we don't know how to
@@ -74,7 +74,7 @@ def find_fuzzy_matches_in_docs(
74
74
  return orig_doc_matches
75
75
 
76
76
  contextual_matches = []
77
- for match in orig_doc_matches:
77
+ for match, score in orig_doc_matches:
78
78
  choice_text = match.content
79
79
  contexts = []
80
80
  while choice_text != "":
@@ -89,9 +89,12 @@ def find_fuzzy_matches_in_docs(
89
89
  choice_text = " ".join(words[end_pos:])
90
90
  if len(contexts) > 0:
91
91
  contextual_matches.append(
92
- Document(
93
- content=" ... ".join(contexts),
94
- metadata=match.metadata,
92
+ (
93
+ Document(
94
+ content=" ... ".join(contexts),
95
+ metadata=match.metadata,
96
+ ),
97
+ score,
95
98
  )
96
99
  )
97
100
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.11.0"
3
+ version = "0.13.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