langroid 0.18.3__tar.gz → 0.19.1__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.3 → langroid-0.19.1}/PKG-INFO +3 -2
  2. {langroid-0.18.3 → langroid-0.19.1}/README.md +2 -1
  3. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/base.py +5 -0
  4. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/callbacks/chainlit.py +27 -0
  5. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/chat_agent.py +5 -5
  6. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/xml_tool_message.py +49 -24
  7. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/base.py +6 -0
  8. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/openai_gpt.py +98 -9
  9. {langroid-0.18.3 → langroid-0.19.1}/pyproject.toml +1 -1
  10. {langroid-0.18.3 → langroid-0.19.1}/LICENSE +0 -0
  11. {langroid-0.18.3 → langroid-0.19.1}/langroid/__init__.py +0 -0
  12. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/__init__.py +0 -0
  13. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/batch.py +0 -0
  14. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/callbacks/__init__.py +0 -0
  15. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/chat_document.py +0 -0
  16. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/helpers.py +0 -0
  17. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/junk +0 -0
  18. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/openai_assistant.py +0 -0
  19. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/__init__.py +0 -0
  20. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/doc_chat_agent.py +0 -0
  21. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  22. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_rag/__init__.py +0 -0
  23. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  24. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  25. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  26. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/lance_tools.py +0 -0
  27. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/neo4j/__init__.py +0 -0
  28. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  29. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  30. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
  31. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
  32. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  33. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/retriever_agent.py +0 -0
  34. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/__init__.py +0 -0
  35. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  36. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/utils/__init__.py +0 -0
  37. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  38. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  39. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/utils/system_message.py +0 -0
  40. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/sql/utils/tools.py +0 -0
  41. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/special/table_chat_agent.py +0 -0
  42. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/structured_message.py +0 -0
  43. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/task.py +0 -0
  44. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tool_message.py +0 -0
  45. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/__init__.py +0 -0
  46. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  47. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/file_tools.py +0 -0
  48. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/google_search_tool.py +0 -0
  49. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  50. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/orchestration.py +0 -0
  51. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/recipient_tool.py +0 -0
  52. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/retrieval_tool.py +0 -0
  53. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/rewind_tool.py +0 -0
  54. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/tools/segment_extract_tool.py +0 -0
  55. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent/typed_task.py +0 -0
  56. {langroid-0.18.3 → langroid-0.19.1}/langroid/agent_config.py +0 -0
  57. {langroid-0.18.3 → langroid-0.19.1}/langroid/cachedb/__init__.py +0 -0
  58. {langroid-0.18.3 → langroid-0.19.1}/langroid/cachedb/base.py +0 -0
  59. {langroid-0.18.3 → langroid-0.19.1}/langroid/cachedb/momento_cachedb.py +0 -0
  60. {langroid-0.18.3 → langroid-0.19.1}/langroid/cachedb/redis_cachedb.py +0 -0
  61. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/__init__.py +0 -0
  62. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/base.py +0 -0
  63. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/clustering.py +0 -0
  64. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/models.py +0 -0
  65. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/protoc/__init__.py +0 -0
  66. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  67. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  68. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  69. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  70. {langroid-0.18.3 → langroid-0.19.1}/langroid/embedding_models/remote_embeds.py +0 -0
  71. {langroid-0.18.3 → langroid-0.19.1}/langroid/exceptions.py +0 -0
  72. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/.chainlit/config.toml +0 -0
  73. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  74. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/__init__.py +0 -0
  75. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/azure_openai.py +0 -0
  76. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/config.py +0 -0
  77. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/mock_lm.py +0 -0
  78. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  79. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/prompt_formatter/base.py +0 -0
  80. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  81. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  82. {langroid-0.18.3 → langroid-0.19.1}/langroid/language_models/utils.py +0 -0
  83. {langroid-0.18.3 → langroid-0.19.1}/langroid/mytypes.py +0 -0
  84. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/__init__.py +0 -0
  85. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/agent_chats.py +0 -0
  86. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/code-parsing.md +0 -0
  87. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/code_parser.py +0 -0
  88. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/config.py +0 -0
  89. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/document_parser.py +0 -0
  90. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/image_text.py +0 -0
  91. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/para_sentence_split.py +0 -0
  92. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/parse_json.py +0 -0
  93. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/parser.py +0 -0
  94. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/repo_loader.py +0 -0
  95. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/routing.py +0 -0
  96. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/search.py +0 -0
  97. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/spider.py +0 -0
  98. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/table_loader.py +0 -0
  99. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/url_loader.py +0 -0
  100. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/url_loader_cookies.py +0 -0
  101. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/urls.py +0 -0
  102. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/utils.py +0 -0
  103. {langroid-0.18.3 → langroid-0.19.1}/langroid/parsing/web_search.py +0 -0
  104. {langroid-0.18.3 → langroid-0.19.1}/langroid/prompts/__init__.py +0 -0
  105. {langroid-0.18.3 → langroid-0.19.1}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  106. {langroid-0.18.3 → langroid-0.19.1}/langroid/prompts/dialog.py +0 -0
  107. {langroid-0.18.3 → langroid-0.19.1}/langroid/prompts/prompts_config.py +0 -0
  108. {langroid-0.18.3 → langroid-0.19.1}/langroid/prompts/templates.py +0 -0
  109. {langroid-0.18.3 → langroid-0.19.1}/langroid/py.typed +0 -0
  110. {langroid-0.18.3 → langroid-0.19.1}/langroid/pydantic_v1/__init__.py +0 -0
  111. {langroid-0.18.3 → langroid-0.19.1}/langroid/pydantic_v1/main.py +0 -0
  112. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/.chainlit/config.toml +0 -0
  113. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  114. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/__init__.py +0 -0
  115. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/algorithms/__init__.py +0 -0
  116. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/algorithms/graph.py +0 -0
  117. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/configuration.py +0 -0
  118. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/constants.py +0 -0
  119. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/docker.py +0 -0
  120. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/git_utils.py +0 -0
  121. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/globals.py +0 -0
  122. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/llms/__init__.py +0 -0
  123. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/llms/strings.py +0 -0
  124. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/logging.py +0 -0
  125. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/object_registry.py +0 -0
  126. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/output/__init__.py +0 -0
  127. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/output/citations.py +0 -0
  128. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/output/printing.py +0 -0
  129. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/output/status.py +0 -0
  130. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/pandas_utils.py +0 -0
  131. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/pydantic_utils.py +0 -0
  132. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/system.py +0 -0
  133. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/types.py +0 -0
  134. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/web/__init__.py +0 -0
  135. {langroid-0.18.3 → langroid-0.19.1}/langroid/utils/web/login.py +0 -0
  136. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/__init__.py +0 -0
  137. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/base.py +0 -0
  138. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/chromadb.py +0 -0
  139. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/lancedb.py +0 -0
  140. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/meilisearch.py +0 -0
  141. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/momento.py +0 -0
  142. {langroid-0.18.3 → langroid-0.19.1}/langroid/vector_store/qdrant_cloud.py +0 -0
  143. {langroid-0.18.3 → langroid-0.19.1}/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.3
3
+ Version: 0.19.1
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -153,7 +153,8 @@ This Multi-Agent paradigm is inspired by the
153
153
  (but you do not need to know anything about this!).
154
154
 
155
155
  `Langroid` is a fresh take on LLM app-development, where considerable thought has gone
156
- into simplifying the developer experience; it does not use `Langchain`.
156
+ into simplifying the developer experience;
157
+ it does not use `Langchain`, or any other LLM framework.
157
158
 
158
159
  :fire: Read the (WIP) [overview of the langroid architecture](https://langroid.github.io/langroid/blog/2024/08/15/overview-of-langroids-multi-agent-architecture-prelim/)
159
160
 
@@ -44,7 +44,8 @@ This Multi-Agent paradigm is inspired by the
44
44
  (but you do not need to know anything about this!).
45
45
 
46
46
  `Langroid` is a fresh take on LLM app-development, where considerable thought has gone
47
- into simplifying the developer experience; it does not use `Langchain`.
47
+ into simplifying the developer experience;
48
+ it does not use `Langchain`, or any other LLM framework.
48
49
 
49
50
  :fire: Read the (WIP) [overview of the langroid architecture](https://langroid.github.io/langroid/blog/2024/08/15/overview-of-langroids-multi-agent-architecture-prelim/)
50
51
 
@@ -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)
@@ -1,4 +1,5 @@
1
- from typing import Any, Dict, List, Optional
1
+ from collections.abc import Mapping
2
+ from typing import Any, Dict, List, Optional, get_args, get_origin
2
3
 
3
4
  from lxml import etree
4
5
 
@@ -133,15 +134,6 @@ class XMLToolMessage(ToolMessage):
133
134
 
134
135
  @classmethod
135
136
  def format_instructions(cls, tool: bool = False) -> str:
136
- """
137
- Instructions to the LLM showing how to use the XML tool.
138
-
139
- Args:
140
- tool: Not used in this implementation, kept for compatibility.
141
-
142
- Returns:
143
- str: instructions on how to use the XML message
144
- """
145
137
  fields = [
146
138
  f
147
139
  for f in cls.__fields__.keys()
@@ -165,35 +157,62 @@ class XMLToolMessage(ToolMessage):
165
157
  nonlocal preamble, xml_format
166
158
  current_path = f"{path}.{field_name}" if path else field_name
167
159
 
168
- if issubclass(field_type, BaseModel):
160
+ origin = get_origin(field_type)
161
+ args = get_args(field_type)
162
+
163
+ if (
164
+ origin is None
165
+ and isinstance(field_type, type)
166
+ and issubclass(field_type, BaseModel)
167
+ ):
169
168
  preamble += (
170
169
  f"{field_name.upper()} = [nested structure for {field_name}]\n"
171
170
  )
172
171
  xml_format += f"{indent}<{field_name}>\n"
173
172
  for sub_field, sub_field_info in field_type.__fields__.items():
174
173
  format_field(
175
- sub_field, sub_field_info.type_, indent + " ", current_path
174
+ sub_field,
175
+ sub_field_info.outer_type_,
176
+ indent + " ",
177
+ current_path,
176
178
  )
177
179
  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"
180
+ elif origin in (list, List) or (field_type is list):
181
+ item_type = args[0] if args else Any
182
+ if isinstance(item_type, type) and issubclass(item_type, BaseModel):
183
+ preamble += (
184
+ f"{field_name.upper()} = "
185
+ f"[list of nested structures for {field_name}]\n"
186
+ )
187
+ else:
188
+ preamble += (
189
+ f"{field_name.upper()} = "
190
+ f"[list of {getattr(item_type, '__name__', str(item_type))} "
191
+ f"for {field_name}]\n"
192
+ )
181
193
  xml_format += f"{indent}<{field_name}>\n"
182
- xml_format += f"{indent} <item>[{item_type.__name__} value]</item>\n"
194
+ xml_format += (
195
+ f"{indent} <item>"
196
+ f"[{getattr(item_type, '__name__', str(item_type))} value]"
197
+ f"</item>\n"
198
+ )
183
199
  xml_format += f"{indent} ...\n"
184
200
  xml_format += f"{indent}</{field_name}>\n"
185
- elif issubclass(field_type, Dict):
186
- key_type, value_type = getattr(field_type, "__args__", [Any, Any])
201
+ elif origin in (dict, Dict) or (
202
+ isinstance(field_type, type) and issubclass(field_type, Mapping)
203
+ ):
204
+ key_type, value_type = args if len(args) == 2 else (Any, Any)
187
205
  preamble += (
188
206
  f"{field_name.upper()} = "
189
- f"[dictionary with {key_type.__name__} keys and "
190
- f"{value_type.__name__} values]\n"
207
+ f"[dictionary with "
208
+ f"{getattr(key_type, '__name__', str(key_type))} keys and "
209
+ f"{getattr(value_type, '__name__', str(value_type))} values]\n"
191
210
  )
192
211
  xml_format += f"{indent}<{field_name}>\n"
193
212
  xml_format += (
194
- f"{indent} <{key_type.__name__}>"
195
- f"[{value_type.__name__} value]"
196
- f"</{key_type.__name__}>\n"
213
+ f"{indent} <{getattr(key_type, '__name__', str(key_type))}>"
214
+ f"[{getattr(value_type, '__name__', str(value_type))} value]"
215
+ f"</{getattr(key_type, '__name__', str(key_type))}>\n"
197
216
  )
198
217
  xml_format += f"{indent} ...\n"
199
218
  xml_format += f"{indent}</{field_name}>\n"
@@ -213,7 +232,10 @@ class XMLToolMessage(ToolMessage):
213
232
  verbatim_fields = cls.find_verbatim_fields()
214
233
 
215
234
  for field in fields:
216
- field_type = cls.__fields__[field].type_
235
+ field_info = cls.__fields__[field]
236
+ field_type = (
237
+ field_info.outer_type_
238
+ ) # Use outer_type_ to get the actual type including List, etc.
217
239
  format_field(field, field_type)
218
240
 
219
241
  xml_format += f"</{cls.Config.root_element}>"
@@ -260,6 +282,9 @@ class XMLToolMessage(ToolMessage):
260
282
  def create_element(
261
283
  parent: etree._Element, name: str, value: Any, path: str = ""
262
284
  ) -> None:
285
+ if value is None:
286
+ return
287
+
263
288
  elem = etree.SubElement(parent, name)
264
289
  current_path = f"{path}.{name}" if path else name
265
290
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.18.3"
3
+ version = "0.19.1"
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
File without changes