langroid 0.18.2__tar.gz → 0.19.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.18.2 → langroid-0.19.0}/PKG-INFO +2 -2
  2. {langroid-0.18.2 → langroid-0.19.0}/README.md +1 -1
  3. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/base.py +5 -0
  4. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/callbacks/chainlit.py +27 -0
  5. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/chat_agent.py +5 -5
  6. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/xml_tool_message.py +121 -24
  7. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/base.py +6 -0
  8. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/openai_gpt.py +98 -9
  9. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/git_utils.py +3 -2
  10. {langroid-0.18.2 → langroid-0.19.0}/pyproject.toml +4 -1
  11. {langroid-0.18.2 → langroid-0.19.0}/LICENSE +0 -0
  12. {langroid-0.18.2 → langroid-0.19.0}/langroid/__init__.py +0 -0
  13. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/__init__.py +0 -0
  14. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/batch.py +0 -0
  15. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/callbacks/__init__.py +0 -0
  16. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/chat_document.py +0 -0
  17. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/helpers.py +0 -0
  18. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/junk +0 -0
  19. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/openai_assistant.py +0 -0
  20. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/__init__.py +0 -0
  21. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/doc_chat_agent.py +0 -0
  22. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  23. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  24. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  25. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  26. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  27. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/lance_tools.py +0 -0
  28. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  29. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  30. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  31. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  32. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  33. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  34. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/retriever_agent.py +0 -0
  35. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/__init__.py +0 -0
  36. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  37. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  38. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  39. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  40. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  41. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  42. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/special/table_chat_agent.py +0 -0
  43. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/structured_message.py +0 -0
  44. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/task.py +0 -0
  45. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tool_message.py +0 -0
  46. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/__init__.py +0 -0
  47. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  48. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/file_tools.py +0 -0
  49. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/google_search_tool.py +0 -0
  50. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  51. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/orchestration.py +0 -0
  52. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/recipient_tool.py +0 -0
  53. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  54. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/rewind_tool.py +0 -0
  55. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  56. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent/typed_task.py +0 -0
  57. {langroid-0.18.2 → langroid-0.19.0}/langroid/agent_config.py +0 -0
  58. {langroid-0.18.2 → langroid-0.19.0}/langroid/cachedb/__init__.py +0 -0
  59. {langroid-0.18.2 → langroid-0.19.0}/langroid/cachedb/base.py +0 -0
  60. {langroid-0.18.2 → langroid-0.19.0}/langroid/cachedb/momento_cachedb.py +0 -0
  61. {langroid-0.18.2 → langroid-0.19.0}/langroid/cachedb/redis_cachedb.py +0 -0
  62. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/__init__.py +0 -0
  63. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/base.py +0 -0
  64. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/clustering.py +0 -0
  65. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/models.py +0 -0
  66. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  67. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  68. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  69. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  70. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  71. {langroid-0.18.2 → langroid-0.19.0}/langroid/embedding_models/remote_embeds.py +0 -0
  72. {langroid-0.18.2 → langroid-0.19.0}/langroid/exceptions.py +0 -0
  73. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/.chainlit/config.toml +0 -0
  74. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  75. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/__init__.py +0 -0
  76. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/azure_openai.py +0 -0
  77. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/config.py +0 -0
  78. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/mock_lm.py +0 -0
  79. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  80. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  81. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  82. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  83. {langroid-0.18.2 → langroid-0.19.0}/langroid/language_models/utils.py +0 -0
  84. {langroid-0.18.2 → langroid-0.19.0}/langroid/mytypes.py +0 -0
  85. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/__init__.py +0 -0
  86. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/agent_chats.py +0 -0
  87. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/code-parsing.md +0 -0
  88. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/code_parser.py +0 -0
  89. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/config.py +0 -0
  90. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/document_parser.py +0 -0
  91. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/image_text.py +0 -0
  92. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/para_sentence_split.py +0 -0
  93. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/parse_json.py +0 -0
  94. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/parser.py +0 -0
  95. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/repo_loader.py +0 -0
  96. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/routing.py +0 -0
  97. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/search.py +0 -0
  98. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/spider.py +0 -0
  99. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/table_loader.py +0 -0
  100. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/url_loader.py +0 -0
  101. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/url_loader_cookies.py +0 -0
  102. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/urls.py +0 -0
  103. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/utils.py +0 -0
  104. {langroid-0.18.2 → langroid-0.19.0}/langroid/parsing/web_search.py +0 -0
  105. {langroid-0.18.2 → langroid-0.19.0}/langroid/prompts/__init__.py +0 -0
  106. {langroid-0.18.2 → langroid-0.19.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  107. {langroid-0.18.2 → langroid-0.19.0}/langroid/prompts/dialog.py +0 -0
  108. {langroid-0.18.2 → langroid-0.19.0}/langroid/prompts/prompts_config.py +0 -0
  109. {langroid-0.18.2 → langroid-0.19.0}/langroid/prompts/templates.py +0 -0
  110. {langroid-0.18.2 → langroid-0.19.0}/langroid/py.typed +0 -0
  111. {langroid-0.18.2 → langroid-0.19.0}/langroid/pydantic_v1/__init__.py +0 -0
  112. {langroid-0.18.2 → langroid-0.19.0}/langroid/pydantic_v1/main.py +0 -0
  113. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/.chainlit/config.toml +0 -0
  114. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  115. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/__init__.py +0 -0
  116. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/algorithms/__init__.py +0 -0
  117. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/algorithms/graph.py +0 -0
  118. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/configuration.py +0 -0
  119. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/constants.py +0 -0
  120. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/docker.py +0 -0
  121. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/globals.py +0 -0
  122. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/llms/__init__.py +0 -0
  123. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/llms/strings.py +0 -0
  124. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/logging.py +0 -0
  125. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/object_registry.py +0 -0
  126. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/output/__init__.py +0 -0
  127. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/output/citations.py +0 -0
  128. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/output/printing.py +0 -0
  129. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/output/status.py +0 -0
  130. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/pandas_utils.py +0 -0
  131. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/pydantic_utils.py +0 -0
  132. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/system.py +0 -0
  133. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/types.py +0 -0
  134. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/web/__init__.py +0 -0
  135. {langroid-0.18.2 → langroid-0.19.0}/langroid/utils/web/login.py +0 -0
  136. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/__init__.py +0 -0
  137. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/base.py +0 -0
  138. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/chromadb.py +0 -0
  139. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/lancedb.py +0 -0
  140. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/meilisearch.py +0 -0
  141. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/momento.py +0 -0
  142. {langroid-0.18.2 → langroid-0.19.0}/langroid/vector_store/qdrant_cloud.py +0 -0
  143. {langroid-0.18.2 → langroid-0.19.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.18.2
3
+ Version: 0.19.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -170,7 +170,7 @@ blog post from the LanceDB team
170
170
  pharmacovigilance, see [blog post](https://langroid.github.io/langroid/blog/2024/08/12/malade-multi-agent-architecture-for-pharmacovigilance/)
171
171
 
172
172
 
173
- We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
173
+ We welcome contributions: See the [contributions](./CONTRIBUTING.md) document
174
174
  for ideas on what to contribute.
175
175
 
176
176
  Are you building LLM Applications, or want help with Langroid for your company,
@@ -61,7 +61,7 @@ blog post from the LanceDB team
61
61
  pharmacovigilance, see [blog post](https://langroid.github.io/langroid/blog/2024/08/12/malade-multi-agent-architecture-for-pharmacovigilance/)
62
62
 
63
63
 
64
- We welcome contributions -- See the [contributions](./CONTRIBUTING.md) document
64
+ We welcome contributions: See the [contributions](./CONTRIBUTING.md) document
65
65
  for ideas on what to contribute.
66
66
 
67
67
  Are you building LLM Applications, or want help with Langroid for your company,
@@ -108,6 +108,10 @@ def noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
108
108
  pass
109
109
 
110
110
 
111
+ async def async_noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
112
+ pass
113
+
114
+
111
115
  class Agent(ABC):
112
116
  """
113
117
  An Agent is an abstraction that encapsulates mainly two components:
@@ -154,6 +158,7 @@ class Agent(ABC):
154
158
 
155
159
  self.callbacks = SimpleNamespace(
156
160
  start_llm_stream=lambda: noop_fn,
161
+ start_llm_stream_async=async_noop_fn,
157
162
  cancel_llm_stream=noop_fn,
158
163
  finish_llm_stream=noop_fn,
159
164
  show_llm_response=noop_fn,
@@ -234,6 +234,7 @@ class ChainlitAgentCallbacks:
234
234
  so we can alter the display of the first user message.
235
235
  """
236
236
  agent.callbacks.start_llm_stream = self.start_llm_stream
237
+ agent.callbacks.start_llm_stream_async = self.start_llm_stream_async
237
238
  agent.callbacks.cancel_llm_stream = self.cancel_llm_stream
238
239
  agent.callbacks.finish_llm_stream = self.finish_llm_stream
239
240
  agent.callbacks.show_llm_response = self.show_llm_response
@@ -304,6 +305,32 @@ class ChainlitAgentCallbacks:
304
305
 
305
306
  return stream_token
306
307
 
308
+ async def start_llm_stream_async(self) -> Callable[[str], None]:
309
+ """Returns a streaming fn that can be passed to the LLM class"""
310
+ self.stream = cl.Step(
311
+ id=self.curr_step.id if self.curr_step is not None else None,
312
+ name=self._entity_name("llm"),
313
+ type="llm",
314
+ parent_id=self._get_parent_id(),
315
+ )
316
+ self.last_step = self.stream
317
+ self.curr_step = None
318
+ logger.info(
319
+ f"""
320
+ Starting LLM stream for {self.agent.config.name}
321
+ id = {self.stream.id}
322
+ under parent {self._get_parent_id()}
323
+ """
324
+ )
325
+ await self.stream.send() # type: ignore
326
+
327
+ async def stream_token(t: str) -> None:
328
+ if self.stream is None:
329
+ raise ValueError("Stream not initialized")
330
+ await self.stream.stream_token(t)
331
+
332
+ return stream_token
333
+
307
334
  def cancel_llm_stream(self) -> None:
308
335
  """Called when cached response found."""
309
336
  self.last_step = None
@@ -9,7 +9,7 @@ from rich import print
9
9
  from rich.console import Console
10
10
  from rich.markup import escape
11
11
 
12
- from langroid.agent.base import Agent, AgentConfig, noop_fn
12
+ from langroid.agent.base import Agent, AgentConfig, async_noop_fn, noop_fn
13
13
  from langroid.agent.chat_document import ChatDocument
14
14
  from langroid.agent.tool_message import ToolMessage
15
15
  from langroid.agent.xml_tool_message import XMLToolMessage
@@ -969,10 +969,10 @@ class ChatAgent(Agent):
969
969
  functions, fun_call, tools, force_tool = self._function_args()
970
970
  assert self.llm is not None
971
971
 
972
- streamer = noop_fn
972
+ streamer_async = async_noop_fn
973
973
  if self.llm.get_stream():
974
- streamer = self.callbacks.start_llm_stream()
975
- self.llm.config.streamer = streamer
974
+ streamer_async = await self.callbacks.start_llm_stream_async()
975
+ self.llm.config.streamer_async = streamer_async
976
976
 
977
977
  response = await self.llm.achat(
978
978
  messages,
@@ -989,7 +989,7 @@ class ChatAgent(Agent):
989
989
  ChatDocument.from_LLMResponse(response, displayed=True),
990
990
  ),
991
991
  )
992
- self.llm.config.streamer = noop_fn
992
+ self.llm.config.streamer_async = async_noop_fn
993
993
  if response.cached:
994
994
  self.callbacks.cancel_llm_stream()
995
995
  self._render_llm_response(response)
@@ -3,12 +3,12 @@ from typing import Any, Dict, List, Optional
3
3
  from lxml import etree
4
4
 
5
5
  from langroid.agent.tool_message import ToolMessage
6
+ from langroid.pydantic_v1 import BaseModel
6
7
 
7
8
 
8
9
  class XMLToolMessage(ToolMessage):
9
10
  """
10
11
  Abstract class for tools formatted using XML instead of JSON.
11
- Mainly tested for non-nested tool structures.
12
12
 
13
13
  When a subclass defines a field with the attribute `verbatim=True`,
14
14
  instructions are sent to the LLM to ensure the field's content is:
@@ -50,8 +50,11 @@ class XMLToolMessage(ToolMessage):
50
50
  parser = etree.XMLParser(strip_cdata=False)
51
51
  root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)
52
52
 
53
- def parse_element(element: etree._Element) -> Any | Dict[str, Any] | str:
53
+ def parse_element(element: etree._Element) -> Any:
54
54
  # Skip elements starting with underscore
55
+ if element.tag.startswith("_"):
56
+ return {}
57
+
55
58
  field_info = cls.__fields__.get(element.tag)
56
59
  is_verbatim = field_info and field_info.field_info.extra.get(
57
60
  "verbatim", False
@@ -72,8 +75,18 @@ class XMLToolMessage(ToolMessage):
72
75
  # For non-code leaf elements, strip whitespace
73
76
  return element.text.strip() if element.text else ""
74
77
  else:
75
- # For branch elements, recurse
76
- return {child.tag: parse_element(child) for child in element}
78
+ # For branch elements, handle potential lists or nested structures
79
+ children = [parse_element(child) for child in element]
80
+ if all(child.tag == element[0].tag for child in element):
81
+ # If all children have the same tag, treat as a list
82
+ return children
83
+ else:
84
+ # Otherwise, treat as a dictionary
85
+ result = {child.tag: parse_element(child) for child in element}
86
+ # Check if this corresponds to a nested Pydantic model
87
+ if field_info and issubclass(field_info.type_, BaseModel):
88
+ return field_info.type_(**result)
89
+ return result
77
90
 
78
91
  result = parse_element(root)
79
92
  if not isinstance(result, dict):
@@ -100,6 +113,24 @@ class XMLToolMessage(ToolMessage):
100
113
  # Use Pydantic's parse_obj to create and validate the instance
101
114
  return cls.parse_obj(parsed_data)
102
115
 
116
+ @classmethod
117
+ def find_verbatim_fields(
118
+ cls, prefix: str = "", parent_cls: Optional["BaseModel"] = None
119
+ ) -> List[str]:
120
+ verbatim_fields = []
121
+ for field_name, field_info in (parent_cls or cls).__fields__.items():
122
+ full_name = f"{prefix}.{field_name}" if prefix else field_name
123
+ if (
124
+ field_info.field_info.extra.get("verbatim", False)
125
+ or field_name == "code"
126
+ ):
127
+ verbatim_fields.append(full_name)
128
+ if issubclass(field_info.type_, BaseModel):
129
+ verbatim_fields.extend(
130
+ cls.find_verbatim_fields(full_name, field_info.type_)
131
+ )
132
+ return verbatim_fields
133
+
103
134
  @classmethod
104
135
  def format_instructions(cls, tool: bool = False) -> str:
105
136
  """
@@ -123,14 +154,69 @@ class XMLToolMessage(ToolMessage):
123
154
  """
124
155
 
125
156
  preamble = "Placeholders:\n"
157
+ xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
158
+
159
+ def format_field(
160
+ field_name: str,
161
+ field_type: type,
162
+ indent: str = "",
163
+ path: str = "",
164
+ ) -> None:
165
+ nonlocal preamble, xml_format
166
+ current_path = f"{path}.{field_name}" if path else field_name
167
+
168
+ if issubclass(field_type, BaseModel):
169
+ preamble += (
170
+ f"{field_name.upper()} = [nested structure for {field_name}]\n"
171
+ )
172
+ xml_format += f"{indent}<{field_name}>\n"
173
+ for sub_field, sub_field_info in field_type.__fields__.items():
174
+ format_field(
175
+ sub_field, sub_field_info.type_, indent + " ", current_path
176
+ )
177
+ xml_format += f"{indent}</{field_name}>\n"
178
+ elif issubclass(field_type, List):
179
+ item_type = getattr(field_type, "__args__", [Any])[0]
180
+ preamble += f"{field_name.upper()} = [list of {item_type.__name__}]\n"
181
+ xml_format += f"{indent}<{field_name}>\n"
182
+ xml_format += f"{indent} <item>[{item_type.__name__} value]</item>\n"
183
+ xml_format += f"{indent} ...\n"
184
+ xml_format += f"{indent}</{field_name}>\n"
185
+ elif issubclass(field_type, Dict):
186
+ key_type, value_type = getattr(field_type, "__args__", [Any, Any])
187
+ preamble += (
188
+ f"{field_name.upper()} = "
189
+ f"[dictionary with {key_type.__name__} keys and "
190
+ f"{value_type.__name__} values]\n"
191
+ )
192
+ xml_format += f"{indent}<{field_name}>\n"
193
+ xml_format += (
194
+ f"{indent} <{key_type.__name__}>"
195
+ f"[{value_type.__name__} value]"
196
+ f"</{key_type.__name__}>\n"
197
+ )
198
+ xml_format += f"{indent} ...\n"
199
+ xml_format += f"{indent}</{field_name}>\n"
200
+ else:
201
+ preamble += f"{field_name.upper()} = [value for {field_name}]\n"
202
+ if current_path in verbatim_fields:
203
+ xml_format += (
204
+ f"{indent}<{field_name}>"
205
+ f"<![CDATA[{{{field_name.upper()}}}]]></{field_name}>\n"
206
+ )
207
+ else:
208
+ xml_format += (
209
+ f"{indent}<{field_name}>"
210
+ f"{{{field_name.upper()}}}</{field_name}>\n"
211
+ )
212
+
213
+ verbatim_fields = cls.find_verbatim_fields()
214
+
126
215
  for field in fields:
127
- preamble += f"{field.upper()} = [value for {field}]\n"
216
+ field_type = cls.__fields__[field].type_
217
+ format_field(field, field_type)
128
218
 
129
- verbatim_fields = [
130
- field
131
- for field, field_info in cls.__fields__.items()
132
- if field_info.field_info.extra.get("verbatim", False)
133
- ]
219
+ xml_format += f"</{cls.Config.root_element}>"
134
220
 
135
221
  verbatim_alert = ""
136
222
  if len(verbatim_fields) > 0:
@@ -141,13 +227,6 @@ class XMLToolMessage(ToolMessage):
141
227
  must be written verbatim WITHOUT any modifications or escaping,
142
228
  such as spaces, tabs, indents, newlines, quotes, etc.
143
229
  """
144
- xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
145
- for field in fields:
146
- if field == "code":
147
- xml_format += f" <{field}><![CDATA[{{{field.upper()}}}]]></{field}>\n"
148
- else:
149
- xml_format += f" <{field}>{{{field.upper()}}}</{field}>\n"
150
- xml_format += f"</{cls.Config.root_element}>"
151
230
 
152
231
  examples_str = ""
153
232
  if cls.examples():
@@ -177,17 +256,35 @@ class XMLToolMessage(ToolMessage):
177
256
  Raises:
178
257
  ValueError: If the result from etree.tostring is not a string.
179
258
  """
259
+
260
+ def create_element(
261
+ parent: etree._Element, name: str, value: Any, path: str = ""
262
+ ) -> None:
263
+ elem = etree.SubElement(parent, name)
264
+ current_path = f"{path}.{name}" if path else name
265
+
266
+ if isinstance(value, list):
267
+ for item in value:
268
+ create_element(elem, "item", item, current_path)
269
+ elif isinstance(value, dict):
270
+ for k, v in value.items():
271
+ create_element(elem, k, v, current_path)
272
+ elif isinstance(value, BaseModel):
273
+ # Handle nested Pydantic models
274
+ for field_name, field_value in value.dict().items():
275
+ create_element(elem, field_name, field_value, current_path)
276
+ else:
277
+ if current_path in self.__class__.find_verbatim_fields():
278
+ elem.text = etree.CDATA(str(value))
279
+ else:
280
+ elem.text = str(value)
281
+
180
282
  root = etree.Element(self.Config.root_element)
181
283
  exclude_fields = self.Config.schema_extra.get("exclude", set())
182
284
  for name, value in self.dict().items():
183
285
  if name not in exclude_fields:
184
- elem = etree.SubElement(root, name)
185
- field_info = self.__class__.__fields__[name]
186
- is_verbatim = field_info.field_info.extra.get("verbatim", False)
187
- if is_verbatim:
188
- elem.text = etree.CDATA(str(value))
189
- else:
190
- elem.text = str(value)
286
+ create_element(root, name, value)
287
+
191
288
  result = etree.tostring(root, encoding="unicode", pretty_print=True)
192
289
  if not isinstance(result, str):
193
290
  raise ValueError("Unexpected non-string result from etree.tostring")
@@ -6,6 +6,7 @@ from datetime import datetime
6
6
  from enum import Enum
7
7
  from typing import (
8
8
  Any,
9
+ Awaitable,
9
10
  Callable,
10
11
  Dict,
11
12
  List,
@@ -33,6 +34,10 @@ def noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
33
34
  pass
34
35
 
35
36
 
37
+ async def async_noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
38
+ pass
39
+
40
+
36
41
  FunctionCallTypes = Literal["none", "auto"]
37
42
  ToolChoiceTypes = Literal["none", "auto", "required"]
38
43
  ToolTypes = Literal["function"]
@@ -45,6 +50,7 @@ class LLMConfig(BaseSettings):
45
50
 
46
51
  type: str = "openai"
47
52
  streamer: Optional[Callable[[Any], None]] = noop_fn
53
+ streamer_async: Optional[Callable[..., Awaitable[None]]] = async_noop_fn
48
54
  api_base: str | None = None
49
55
  formatter: None | str = None
50
56
  timeout: int = 20 # timeout for API requests
@@ -688,7 +688,6 @@ class OpenAIGPT(LanguageModel):
688
688
  completion: str = "",
689
689
  function_args: str = "",
690
690
  function_name: str = "",
691
- is_async: bool = False,
692
691
  ) -> Tuple[bool, bool, str, str]:
693
692
  """Process state vars while processing a streaming API response.
694
693
  Returns a tuple consisting of:
@@ -708,7 +707,98 @@ class OpenAIGPT(LanguageModel):
708
707
  event_args = ""
709
708
  event_fn_name = ""
710
709
  event_tool_deltas: Optional[List[Dict[str, Any]]] = None
711
- silent = is_async and self.config.async_stream_quiet
710
+ # The first two events in the stream of Azure OpenAI is useless.
711
+ # In the 1st: choices list is empty, in the 2nd: the dict delta has null content
712
+ if chat:
713
+ delta = choices[0].get("delta", {})
714
+ event_text = delta.get("content", "")
715
+ if "function_call" in delta and delta["function_call"] is not None:
716
+ if "name" in delta["function_call"]:
717
+ event_fn_name = delta["function_call"]["name"]
718
+ if "arguments" in delta["function_call"]:
719
+ event_args = delta["function_call"]["arguments"]
720
+ if "tool_calls" in delta and delta["tool_calls"] is not None:
721
+ # it's a list of deltas, usually just one
722
+ event_tool_deltas = delta["tool_calls"]
723
+ tool_deltas += event_tool_deltas
724
+ else:
725
+ event_text = choices[0]["text"]
726
+ if event_text:
727
+ completion += event_text
728
+ sys.stdout.write(Colors().GREEN + event_text)
729
+ sys.stdout.flush()
730
+ self.config.streamer(event_text)
731
+ if event_fn_name:
732
+ function_name = event_fn_name
733
+ has_function = True
734
+ sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
735
+ sys.stdout.flush()
736
+ self.config.streamer(event_fn_name)
737
+
738
+ if event_args:
739
+ function_args += event_args
740
+ sys.stdout.write(Colors().GREEN + event_args)
741
+ sys.stdout.flush()
742
+ self.config.streamer(event_args)
743
+
744
+ if event_tool_deltas is not None:
745
+ # print out streaming tool calls, if not async
746
+ for td in event_tool_deltas:
747
+ if td["function"]["name"] is not None:
748
+ tool_fn_name = td["function"]["name"]
749
+ sys.stdout.write(
750
+ Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
751
+ )
752
+ sys.stdout.flush()
753
+ self.config.streamer(tool_fn_name)
754
+ if td["function"]["arguments"] != "":
755
+ tool_fn_args = td["function"]["arguments"]
756
+ sys.stdout.write(Colors().GREEN + tool_fn_args)
757
+ sys.stdout.flush()
758
+ self.config.streamer(tool_fn_args)
759
+
760
+ # show this delta in the stream
761
+ if choices[0].get("finish_reason", "") in [
762
+ "stop",
763
+ "function_call",
764
+ "tool_calls",
765
+ ]:
766
+ # for function_call, finish_reason does not necessarily
767
+ # contain "function_call" as mentioned in the docs.
768
+ # So we check for "stop" or "function_call" here.
769
+ return True, has_function, function_name, function_args, completion
770
+ return False, has_function, function_name, function_args, completion
771
+
772
+ @no_type_check
773
+ async def _process_stream_event_async(
774
+ self,
775
+ event,
776
+ chat: bool = False,
777
+ tool_deltas: List[Dict[str, Any]] = [],
778
+ has_function: bool = False,
779
+ completion: str = "",
780
+ function_args: str = "",
781
+ function_name: str = "",
782
+ ) -> Tuple[bool, bool, str, str]:
783
+ """Process state vars while processing a streaming API response.
784
+ Returns a tuple consisting of:
785
+ - is_break: whether to break out of the loop
786
+ - has_function: whether the response contains a function_call
787
+ - function_name: name of the function
788
+ - function_args: args of the function
789
+ """
790
+ # convert event obj (of type ChatCompletionChunk) to dict so rest of code,
791
+ # which expects dicts, works as it did before switching to openai v1.x
792
+ if not isinstance(event, dict):
793
+ event = event.model_dump()
794
+
795
+ choices = event.get("choices", [{}])
796
+ if len(choices) == 0:
797
+ choices = [{}]
798
+ event_args = ""
799
+ event_fn_name = ""
800
+ event_tool_deltas: Optional[List[Dict[str, Any]]] = None
801
+ silent = self.config.async_stream_quiet
712
802
  # The first two events in the stream of Azure OpenAI is useless.
713
803
  # In the 1st: choices list is empty, in the 2nd: the dict delta has null content
714
804
  if chat:
@@ -730,21 +820,21 @@ class OpenAIGPT(LanguageModel):
730
820
  if not silent:
731
821
  sys.stdout.write(Colors().GREEN + event_text)
732
822
  sys.stdout.flush()
733
- self.config.streamer(event_text)
823
+ await self.config.streamer_async(event_text)
734
824
  if event_fn_name:
735
825
  function_name = event_fn_name
736
826
  has_function = True
737
827
  if not silent:
738
828
  sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
739
829
  sys.stdout.flush()
740
- self.config.streamer(event_fn_name)
830
+ await self.config.streamer_async(event_fn_name)
741
831
 
742
832
  if event_args:
743
833
  function_args += event_args
744
834
  if not silent:
745
835
  sys.stdout.write(Colors().GREEN + event_args)
746
836
  sys.stdout.flush()
747
- self.config.streamer(event_args)
837
+ await self.config.streamer_async(event_args)
748
838
 
749
839
  if event_tool_deltas is not None and not silent:
750
840
  # print out streaming tool calls, if not async
@@ -755,12 +845,12 @@ class OpenAIGPT(LanguageModel):
755
845
  Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
756
846
  )
757
847
  sys.stdout.flush()
758
- self.config.streamer(tool_fn_name)
848
+ await self.config.streamer_async(tool_fn_name)
759
849
  if td["function"]["arguments"] != "":
760
850
  tool_fn_args = td["function"]["arguments"]
761
851
  sys.stdout.write(Colors().GREEN + tool_fn_args)
762
852
  sys.stdout.flush()
763
- self.config.streamer(tool_fn_args)
853
+ await self.config.streamer_async(tool_fn_args)
764
854
 
765
855
  # show this delta in the stream
766
856
  if choices[0].get("finish_reason", "") in [
@@ -862,7 +952,7 @@ class OpenAIGPT(LanguageModel):
862
952
  function_name,
863
953
  function_args,
864
954
  completion,
865
- ) = self._process_stream_event(
955
+ ) = await self._process_stream_event_async(
866
956
  event,
867
957
  chat=chat,
868
958
  tool_deltas=tool_deltas,
@@ -870,7 +960,6 @@ class OpenAIGPT(LanguageModel):
870
960
  completion=completion,
871
961
  function_args=function_args,
872
962
  function_name=function_name,
873
- is_async=True,
874
963
  )
875
964
  if is_break:
876
965
  break
@@ -127,8 +127,9 @@ def git_commit_file(repo: git.Repo, filepath: str, msg: str) -> None:
127
127
  """
128
128
  try:
129
129
  repo.index.add([filepath])
130
- repo.index.commit(f"{msg}; Updated {filepath}")
131
- logger.info(f"Successfully committed {filepath}")
130
+ commit_msg = msg or f"Updated {filepath}"
131
+ repo.index.commit(commit_msg)
132
+ logger.info(f"Successfully committed {filepath}: {commit_msg}")
132
133
  except git.GitCommandError as e:
133
134
  logger.error(f"An error occurred while committing: {e}")
134
135
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.18.2"
3
+ version = "0.19.0"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
@@ -163,6 +163,9 @@ pytest-asyncio = "^0.21.1"
163
163
  pytest-postgresql = "^5.0.0"
164
164
  pytest-mysql = "^2.4.2"
165
165
  coverage = "^7.2.5"
166
+ pytest-xdist = "^3.6.1"
167
+ pytest-timeout = "^2.3.1"
168
+ pytest-cov = "^5.0.0"
166
169
 
167
170
  [tool.poetry.group.docs]
168
171
  optional = true
File without changes
File without changes
File without changes
File without changes