langroid 0.54.1__tar.gz → 0.55.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 (141) hide show
  1. {langroid-0.54.1 → langroid-0.55.0}/PKG-INFO +2 -2
  2. {langroid-0.54.1 → langroid-0.55.0}/README.md +1 -1
  3. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/base.py +190 -27
  4. langroid-0.55.0/langroid/agent/done_sequence_parser.py +151 -0
  5. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/task.py +261 -0
  6. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/mcp/fastmcp_client.py +143 -33
  7. {langroid-0.54.1 → langroid-0.55.0}/pyproject.toml +1 -1
  8. {langroid-0.54.1 → langroid-0.55.0}/.gitignore +0 -0
  9. {langroid-0.54.1 → langroid-0.55.0}/LICENSE +0 -0
  10. {langroid-0.54.1 → langroid-0.55.0}/langroid/__init__.py +0 -0
  11. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/__init__.py +0 -0
  12. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/batch.py +0 -0
  13. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/callbacks/__init__.py +0 -0
  14. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/callbacks/chainlit.py +0 -0
  15. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/chat_agent.py +0 -0
  16. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/chat_document.py +0 -0
  17. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/openai_assistant.py +0 -0
  18. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/__init__.py +0 -0
  19. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/arangodb/__init__.py +0 -0
  20. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/arangodb/arangodb_agent.py +0 -0
  21. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/arangodb/system_messages.py +0 -0
  22. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/arangodb/tools.py +0 -0
  23. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/arangodb/utils.py +0 -0
  24. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/doc_chat_agent.py +0 -0
  25. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/doc_chat_task.py +0 -0
  26. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  27. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  28. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  29. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  30. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  31. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/lance_tools.py +0 -0
  32. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  33. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  34. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  35. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/neo4j/system_messages.py +0 -0
  36. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/neo4j/tools.py +0 -0
  37. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  38. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/retriever_agent.py +0 -0
  39. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/__init__.py +0 -0
  40. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  41. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  42. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  43. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  44. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  45. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  46. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/special/table_chat_agent.py +0 -0
  47. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tool_message.py +0 -0
  48. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/__init__.py +0 -0
  49. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  50. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/exa_search_tool.py +0 -0
  51. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/file_tools.py +0 -0
  52. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/google_search_tool.py +0 -0
  53. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/mcp/__init__.py +0 -0
  54. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/mcp/decorators.py +0 -0
  55. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  56. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/orchestration.py +0 -0
  57. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/recipient_tool.py +0 -0
  58. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  59. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/rewind_tool.py +0 -0
  60. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  61. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/tools/tavily_search_tool.py +0 -0
  62. {langroid-0.54.1 → langroid-0.55.0}/langroid/agent/xml_tool_message.py +0 -0
  63. {langroid-0.54.1 → langroid-0.55.0}/langroid/cachedb/__init__.py +0 -0
  64. {langroid-0.54.1 → langroid-0.55.0}/langroid/cachedb/base.py +0 -0
  65. {langroid-0.54.1 → langroid-0.55.0}/langroid/cachedb/redis_cachedb.py +0 -0
  66. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/__init__.py +0 -0
  67. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/base.py +0 -0
  68. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/models.py +0 -0
  69. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  70. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  71. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  72. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  73. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  74. {langroid-0.54.1 → langroid-0.55.0}/langroid/embedding_models/remote_embeds.py +0 -0
  75. {langroid-0.54.1 → langroid-0.55.0}/langroid/exceptions.py +0 -0
  76. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/__init__.py +0 -0
  77. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/azure_openai.py +0 -0
  78. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/base.py +0 -0
  79. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/config.py +0 -0
  80. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/mock_lm.py +0 -0
  81. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/model_info.py +0 -0
  82. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/openai_gpt.py +0 -0
  83. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  84. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  85. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  86. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  87. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/provider_params.py +0 -0
  88. {langroid-0.54.1 → langroid-0.55.0}/langroid/language_models/utils.py +0 -0
  89. {langroid-0.54.1 → langroid-0.55.0}/langroid/mytypes.py +0 -0
  90. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/__init__.py +0 -0
  91. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/agent_chats.py +0 -0
  92. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/code_parser.py +0 -0
  93. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/document_parser.py +0 -0
  94. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/file_attachment.py +0 -0
  95. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/md_parser.py +0 -0
  96. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/para_sentence_split.py +0 -0
  97. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/parse_json.py +0 -0
  98. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/parser.py +0 -0
  99. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/pdf_utils.py +0 -0
  100. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/repo_loader.py +0 -0
  101. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/routing.py +0 -0
  102. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/search.py +0 -0
  103. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/spider.py +0 -0
  104. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/table_loader.py +0 -0
  105. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/url_loader.py +0 -0
  106. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/urls.py +0 -0
  107. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/utils.py +0 -0
  108. {langroid-0.54.1 → langroid-0.55.0}/langroid/parsing/web_search.py +0 -0
  109. {langroid-0.54.1 → langroid-0.55.0}/langroid/prompts/__init__.py +0 -0
  110. {langroid-0.54.1 → langroid-0.55.0}/langroid/prompts/dialog.py +0 -0
  111. {langroid-0.54.1 → langroid-0.55.0}/langroid/prompts/prompts_config.py +0 -0
  112. {langroid-0.54.1 → langroid-0.55.0}/langroid/prompts/templates.py +0 -0
  113. {langroid-0.54.1 → langroid-0.55.0}/langroid/py.typed +0 -0
  114. {langroid-0.54.1 → langroid-0.55.0}/langroid/pydantic_v1/__init__.py +0 -0
  115. {langroid-0.54.1 → langroid-0.55.0}/langroid/pydantic_v1/main.py +0 -0
  116. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/__init__.py +0 -0
  117. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/algorithms/__init__.py +0 -0
  118. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/algorithms/graph.py +0 -0
  119. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/configuration.py +0 -0
  120. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/constants.py +0 -0
  121. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/git_utils.py +0 -0
  122. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/globals.py +0 -0
  123. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/logging.py +0 -0
  124. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/object_registry.py +0 -0
  125. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/output/__init__.py +0 -0
  126. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/output/citations.py +0 -0
  127. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/output/printing.py +0 -0
  128. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/output/status.py +0 -0
  129. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/pandas_utils.py +0 -0
  130. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/pydantic_utils.py +0 -0
  131. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/system.py +0 -0
  132. {langroid-0.54.1 → langroid-0.55.0}/langroid/utils/types.py +0 -0
  133. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/__init__.py +0 -0
  134. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/base.py +0 -0
  135. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/chromadb.py +0 -0
  136. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/lancedb.py +0 -0
  137. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/meilisearch.py +0 -0
  138. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/pineconedb.py +0 -0
  139. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/postgres.py +0 -0
  140. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/qdrantdb.py +0 -0
  141. {langroid-0.54.1 → langroid-0.55.0}/langroid/vector_store/weaviatedb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.54.1
3
+ Version: 0.55.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -209,7 +209,7 @@ Description-Content-Type: text/markdown
209
209
  [![PyPI - Version](https://img.shields.io/pypi/v/langroid)](https://pypi.org/project/langroid/)
210
210
  [![Downloads](https://img.shields.io/pypi/dm/langroid)](https://pypi.org/project/langroid/)
211
211
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
212
- [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
212
+ [![codecov](https://codecov.io/gh/langroid/langroid/graph/badge.svg)](https://codecov.io/gh/langroid/langroid)
213
213
  [![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)
214
214
 
215
215
  [![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](https://langroid.github.io/langroid)
@@ -8,7 +8,7 @@
8
8
  [![PyPI - Version](https://img.shields.io/pypi/v/langroid)](https://pypi.org/project/langroid/)
9
9
  [![Downloads](https://img.shields.io/pypi/dm/langroid)](https://pypi.org/project/langroid/)
10
10
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
11
- [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
11
+ [![codecov](https://codecov.io/gh/langroid/langroid/graph/badge.svg)](https://codecov.io/gh/langroid/langroid)
12
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)
13
13
 
14
14
  [![Static Badge](https://img.shields.io/badge/Documentation-blue?link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F&link=https%3A%2F%2Flangroid.github.io%2Flangroid%2F)](https://langroid.github.io/langroid)
@@ -251,6 +251,172 @@ class Agent(ABC):
251
251
  def clear_dialog(self) -> None:
252
252
  self.dialog = []
253
253
 
254
+ def _analyze_handler_params(
255
+ self, handler_method: Any
256
+ ) -> Tuple[bool, Optional[str], Optional[str]]:
257
+ """
258
+ Analyze parameters of a handler method to determine their types.
259
+
260
+ Returns:
261
+ Tuple of (has_annotations, agent_param_name, chat_doc_param_name)
262
+ - has_annotations: True if useful type annotations were found
263
+ - agent_param_name: Name of the agent parameter if found
264
+ - chat_doc_param_name: Name of the chat_doc parameter if found
265
+ """
266
+ sig = inspect.signature(handler_method)
267
+ params = list(sig.parameters.values())
268
+ # Remove 'self' parameter
269
+ params = [p for p in params if p.name != "self"]
270
+
271
+ agent_param = None
272
+ chat_doc_param = None
273
+ has_annotations = False
274
+
275
+ for param in params:
276
+ # First try type annotations
277
+ if param.annotation != inspect.Parameter.empty:
278
+ ann_str = str(param.annotation)
279
+ # Check for Agent-like types
280
+ if (
281
+ param.annotation == self.__class__
282
+ or "Agent" in ann_str
283
+ or (
284
+ hasattr(param.annotation, "__name__")
285
+ and "Agent" in param.annotation.__name__
286
+ )
287
+ ):
288
+ agent_param = param.name
289
+ has_annotations = True
290
+ # Check for ChatDocument-like types
291
+ elif "ChatDocument" in ann_str or "ChatDoc" in ann_str:
292
+ chat_doc_param = param.name
293
+ has_annotations = True
294
+
295
+ # Fallback to parameter names
296
+ elif param.name == "agent":
297
+ agent_param = param.name
298
+ elif param.name == "chat_doc":
299
+ chat_doc_param = param.name
300
+
301
+ return has_annotations, agent_param, chat_doc_param
302
+
303
+ @no_type_check
304
+ def _create_handler_wrapper(
305
+ self,
306
+ message_class: Type[ToolMessage],
307
+ handler_method: Any,
308
+ is_async: bool = False,
309
+ ) -> Any:
310
+ """
311
+ Create a wrapper function for a handler method based on its signature.
312
+
313
+ Args:
314
+ message_class: The ToolMessage class
315
+ handler_method: The handle/handle_async method
316
+ is_async: Whether this is for an async handler
317
+
318
+ Returns:
319
+ Appropriate wrapper function
320
+ """
321
+ sig = inspect.signature(handler_method)
322
+ params = list(sig.parameters.values())
323
+ params = [p for p in params if p.name != "self"]
324
+
325
+ has_annotations, agent_param, chat_doc_param = self._analyze_handler_params(
326
+ handler_method,
327
+ )
328
+
329
+ # Build wrapper based on found parameters
330
+ if len(params) == 0:
331
+ if is_async:
332
+
333
+ async def wrapper(obj: Any) -> Any:
334
+ return await obj.handle_async()
335
+
336
+ else:
337
+
338
+ def wrapper(obj: Any) -> Any:
339
+ return obj.handle()
340
+
341
+ elif agent_param and chat_doc_param:
342
+ # Both parameters present - build wrapper respecting their order
343
+ param_names = [p.name for p in params]
344
+ if param_names.index(agent_param) < param_names.index(chat_doc_param):
345
+ # agent is first parameter
346
+ if is_async:
347
+
348
+ async def wrapper(obj: Any, chat_doc: Any) -> Any:
349
+ return await obj.handle_async(self, chat_doc)
350
+
351
+ else:
352
+
353
+ def wrapper(obj: Any, chat_doc: Any) -> Any:
354
+ return obj.handle(self, chat_doc)
355
+
356
+ else:
357
+ # chat_doc is first parameter
358
+ if is_async:
359
+
360
+ async def wrapper(obj: Any, chat_doc: Any) -> Any:
361
+ return await obj.handle_async(chat_doc, self)
362
+
363
+ else:
364
+
365
+ def wrapper(obj: Any, chat_doc: Any) -> Any:
366
+ return obj.handle(chat_doc, self)
367
+
368
+ elif agent_param and not chat_doc_param:
369
+ # Only agent parameter
370
+ if is_async:
371
+
372
+ async def wrapper(obj: Any) -> Any:
373
+ return await obj.handle_async(self)
374
+
375
+ else:
376
+
377
+ def wrapper(obj: Any) -> Any:
378
+ return obj.handle(self)
379
+
380
+ elif chat_doc_param and not agent_param:
381
+ # Only chat_doc parameter
382
+ if is_async:
383
+
384
+ async def wrapper(obj: Any, chat_doc: Any) -> Any:
385
+ return await obj.handle_async(chat_doc)
386
+
387
+ else:
388
+
389
+ def wrapper(obj: Any, chat_doc: Any) -> Any:
390
+ return obj.handle(chat_doc)
391
+
392
+ else:
393
+ # No recognized parameters - backward compatibility
394
+ # Assume single parameter is chat_doc (legacy behavior)
395
+ if len(params) == 1:
396
+ if is_async:
397
+
398
+ async def wrapper(obj: Any, chat_doc: Any) -> Any:
399
+ return await obj.handle_async(chat_doc)
400
+
401
+ else:
402
+
403
+ def wrapper(obj: Any, chat_doc: Any) -> Any:
404
+ return obj.handle(chat_doc)
405
+
406
+ else:
407
+ # Multiple unrecognized parameters - best guess
408
+ if is_async:
409
+
410
+ async def wrapper(obj: Any, chat_doc: Any) -> Any:
411
+ return await obj.handle_async(chat_doc)
412
+
413
+ else:
414
+
415
+ def wrapper(obj: Any, chat_doc: Any) -> Any:
416
+ return obj.handle(chat_doc)
417
+
418
+ return wrapper
419
+
254
420
  def _get_tool_list(
255
421
  self, message_class: Optional[Type[ToolMessage]] = None
256
422
  ) -> List[str]:
@@ -304,13 +470,12 @@ class Agent(ABC):
304
470
  in one place, i.e. in the message class.
305
471
  See `tests/main/test_stateless_tool_messages.py` for an example.
306
472
  """
307
- has_chat_doc_arg = (
308
- len(inspect.signature(message_class.handle).parameters) > 1
473
+ wrapper = self._create_handler_wrapper(
474
+ message_class,
475
+ message_class.handle,
476
+ is_async=False,
309
477
  )
310
- if has_chat_doc_arg:
311
- setattr(self, handler, lambda obj, chat_doc: obj.handle(chat_doc))
312
- else:
313
- setattr(self, handler, lambda obj: obj.handle())
478
+ setattr(self, handler, wrapper)
314
479
  elif (
315
480
  hasattr(message_class, "response")
316
481
  and inspect.isfunction(message_class.response)
@@ -320,11 +485,17 @@ class Agent(ABC):
320
485
  len(inspect.signature(message_class.response).parameters) > 2
321
486
  )
322
487
  if has_chat_doc_arg:
323
- setattr(
324
- self, handler, lambda obj, chat_doc: obj.response(self, chat_doc)
325
- )
488
+
489
+ def response_wrapper_with_chat_doc(obj: Any, chat_doc: Any) -> Any:
490
+ return obj.response(self, chat_doc)
491
+
492
+ setattr(self, handler, response_wrapper_with_chat_doc)
326
493
  else:
327
- setattr(self, handler, lambda obj: obj.response(self))
494
+
495
+ def response_wrapper_no_chat_doc(obj: Any) -> Any:
496
+ return obj.response(self)
497
+
498
+ setattr(self, handler, response_wrapper_no_chat_doc)
328
499
 
329
500
  if hasattr(message_class, "handle_message_fallback") and (
330
501
  inspect.isfunction(message_class.handle_message_fallback)
@@ -334,10 +505,13 @@ class Agent(ABC):
334
505
  # `handle_message_fallback` method (which does nothing).
335
506
  # It's possible multiple tool messages have a `handle_message_fallback`,
336
507
  # in which case, the last one inserted will be used.
508
+ def fallback_wrapper(msg: Any) -> Any:
509
+ return message_class.handle_message_fallback(self, msg)
510
+
337
511
  setattr(
338
512
  self,
339
513
  "handle_message_fallback",
340
- lambda msg: message_class.handle_message_fallback(self, msg),
514
+ fallback_wrapper,
341
515
  )
342
516
 
343
517
  async_handler_name = f"{handler}_async"
@@ -346,23 +520,12 @@ class Agent(ABC):
346
520
  and inspect.isfunction(message_class.handle_async)
347
521
  and not hasattr(self, async_handler_name)
348
522
  ):
349
- has_chat_doc_arg = (
350
- len(inspect.signature(message_class.handle_async).parameters) > 1
523
+ wrapper = self._create_handler_wrapper(
524
+ message_class,
525
+ message_class.handle_async,
526
+ is_async=True,
351
527
  )
352
-
353
- if has_chat_doc_arg:
354
-
355
- @no_type_check
356
- async def handler(obj, chat_doc):
357
- return await obj.handle_async(chat_doc)
358
-
359
- else:
360
-
361
- @no_type_check
362
- async def handler(obj):
363
- return await obj.handle_async()
364
-
365
- setattr(self, async_handler_name, handler)
528
+ setattr(self, async_handler_name, wrapper)
366
529
  elif (
367
530
  hasattr(message_class, "response_async")
368
531
  and inspect.isfunction(message_class.response_async)
@@ -0,0 +1,151 @@
1
+ """Parser for done sequence DSL (Domain Specific Language).
2
+
3
+ Converts string patterns into DoneSequence objects for convenient task completion
4
+ configuration.
5
+
6
+ Examples:
7
+ "T, A" -> Tool followed by Agent response
8
+ "T[calculator], A" -> Specific tool 'calculator' followed by Agent response
9
+ "L, T, A, L" -> LLM, Tool, Agent, LLM sequence
10
+ "C[quit|exit]" -> Content matching regex pattern
11
+ """
12
+
13
+ import re
14
+ from typing import List, Union
15
+
16
+ from langroid.pydantic_v1 import BaseModel
17
+
18
+ from .task import AgentEvent, DoneSequence, EventType
19
+
20
+
21
+ def parse_done_sequence(sequence: Union[str, DoneSequence]) -> DoneSequence:
22
+ """Parse a string pattern or return existing DoneSequence unchanged.
23
+
24
+ Args:
25
+ sequence: Either a DoneSequence object or a string pattern to parse
26
+
27
+ Returns:
28
+ DoneSequence object
29
+
30
+ Raises:
31
+ ValueError: If the string pattern is invalid
32
+ """
33
+ if isinstance(sequence, DoneSequence):
34
+ return sequence
35
+
36
+ if not isinstance(sequence, str):
37
+ raise ValueError(f"Expected string or DoneSequence, got {type(sequence)}")
38
+
39
+ events = _parse_string_pattern(sequence)
40
+ return DoneSequence(events=events)
41
+
42
+
43
+ def _parse_string_pattern(pattern: str) -> List[AgentEvent]:
44
+ """Parse a string pattern into a list of AgentEvent objects.
45
+
46
+ Pattern format:
47
+ - Single letter codes: T, A, L, U, N, C
48
+ - Specific tools: T[tool_name]
49
+ - Content match: C[regex_pattern]
50
+ - Separated by commas, spaces allowed
51
+
52
+ Args:
53
+ pattern: String pattern to parse
54
+
55
+ Returns:
56
+ List of AgentEvent objects
57
+
58
+ Raises:
59
+ ValueError: If pattern is invalid
60
+ """
61
+ events = []
62
+
63
+ # Split by comma and strip whitespace
64
+ parts = [p.strip() for p in pattern.split(",")]
65
+
66
+ for part in parts:
67
+ if not part:
68
+ continue
69
+
70
+ event = _parse_event_token(part)
71
+ events.append(event)
72
+
73
+ if not events:
74
+ raise ValueError(f"No valid events found in pattern: {pattern}")
75
+
76
+ return events
77
+
78
+
79
+ def _parse_event_token(token: str) -> AgentEvent:
80
+ """Parse a single event token into an AgentEvent.
81
+
82
+ Args:
83
+ token: Single event token (e.g., "T", "T[calc]", "C[quit|exit]")
84
+
85
+ Returns:
86
+ AgentEvent object
87
+
88
+ Raises:
89
+ ValueError: If token is invalid
90
+ """
91
+ # Check for bracket notation
92
+ bracket_match = re.match(r"^([A-Z])\[([^\]]+)\]$", token)
93
+
94
+ if bracket_match:
95
+ event_code = bracket_match.group(1)
96
+ param = bracket_match.group(2)
97
+
98
+ if event_code == "T":
99
+ # Specific tool: T[tool_name]
100
+ return AgentEvent(event_type=EventType.SPECIFIC_TOOL, tool_name=param)
101
+ elif event_code == "C":
102
+ # Content match: C[regex_pattern]
103
+ return AgentEvent(event_type=EventType.CONTENT_MATCH, content_pattern=param)
104
+ else:
105
+ raise ValueError(
106
+ f"Invalid event code with brackets: {event_code}. "
107
+ "Only T[tool] and C[pattern] are supported."
108
+ )
109
+
110
+ # Simple single-letter codes
111
+ event_map = {
112
+ "T": EventType.TOOL,
113
+ "A": EventType.AGENT_RESPONSE,
114
+ "L": EventType.LLM_RESPONSE,
115
+ "U": EventType.USER_RESPONSE,
116
+ "N": EventType.NO_RESPONSE,
117
+ "C": EventType.CONTENT_MATCH, # C without brackets matches any content
118
+ }
119
+
120
+ if token in event_map:
121
+ return AgentEvent(event_type=event_map[token])
122
+
123
+ # If not a single letter, could be a full event type name
124
+ token_upper = token.upper()
125
+ if token_upper == "TOOL":
126
+ return AgentEvent(event_type=EventType.TOOL)
127
+ elif token_upper == "AGENT":
128
+ return AgentEvent(event_type=EventType.AGENT_RESPONSE)
129
+ elif token_upper == "LLM":
130
+ return AgentEvent(event_type=EventType.LLM_RESPONSE)
131
+ elif token_upper == "USER":
132
+ return AgentEvent(event_type=EventType.USER_RESPONSE)
133
+ else:
134
+ raise ValueError(
135
+ f"Invalid event token: '{token}'. "
136
+ "Valid tokens are: T, A, L, U, N, C, or T[tool_name], C[pattern]"
137
+ )
138
+
139
+
140
+ def parse_done_sequences(
141
+ sequences: List[Union[str, DoneSequence]]
142
+ ) -> List[DoneSequence]:
143
+ """Parse a list of mixed string patterns and DoneSequence objects.
144
+
145
+ Args:
146
+ sequences: List containing strings and/or DoneSequence objects
147
+
148
+ Returns:
149
+ List of DoneSequence objects
150
+ """
151
+ return [parse_done_sequence(seq) for seq in sequences]