langroid 0.30.1__tar.gz → 0.31.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 (161) hide show
  1. {langroid-0.30.1 → langroid-0.31.0}/PKG-INFO +9 -1
  2. {langroid-0.30.1 → langroid-0.31.0}/README.md +8 -0
  3. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/base.py +4 -0
  4. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/models.py +105 -11
  5. langroid-0.31.0/langroid/experimental/team-save.py +391 -0
  6. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/azure_openai.py +5 -66
  7. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/openai_gpt.py +0 -5
  8. {langroid-0.30.1 → langroid-0.31.0}/pyproject.toml +6 -6
  9. {langroid-0.30.1 → langroid-0.31.0}/LICENSE +0 -0
  10. {langroid-0.30.1 → langroid-0.31.0}/langroid/__init__.py +0 -0
  11. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/config.toml +0 -0
  12. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/bn.json +0 -0
  13. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/en-US.json +0 -0
  14. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/gu.json +0 -0
  15. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/he-IL.json +0 -0
  16. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/hi.json +0 -0
  17. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/kn.json +0 -0
  18. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/ml.json +0 -0
  19. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/mr.json +0 -0
  20. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/ta.json +0 -0
  21. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/te.json +0 -0
  22. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/.chainlit/translations/zh-CN.json +0 -0
  23. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/__init__.py +0 -0
  24. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/base.py +0 -0
  25. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/batch.py +0 -0
  26. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/callbacks/__init__.py +0 -0
  27. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/callbacks/chainlit.py +0 -0
  28. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/chat_agent.py +0 -0
  29. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/chat_document.py +0 -0
  30. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/helpers.py +0 -0
  31. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/junk +0 -0
  32. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/openai_assistant.py +0 -0
  33. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/__init__.py +0 -0
  34. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/arangodb/__init__.py +0 -0
  35. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/arangodb/arangodb_agent.py +0 -0
  36. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/arangodb/system_messages.py +0 -0
  37. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/arangodb/tools.py +0 -0
  38. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/arangodb/utils.py +0 -0
  39. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/doc_chat_agent.py +0 -0
  40. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  41. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  42. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  43. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  44. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  45. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/lance_tools.py +0 -0
  46. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  47. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  48. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  49. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/neo4j/system_messages.py +0 -0
  50. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/neo4j/tools.py +0 -0
  51. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  52. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/retriever_agent.py +0 -0
  53. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/__init__.py +0 -0
  54. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  55. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  56. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  57. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  58. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  59. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  60. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/special/table_chat_agent.py +0 -0
  61. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/structured_message.py +0 -0
  62. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/task.py +0 -0
  63. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tool_message.py +0 -0
  64. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/__init__.py +0 -0
  65. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  66. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/file_tools.py +0 -0
  67. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/google_search_tool.py +0 -0
  68. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  69. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/orchestration.py +0 -0
  70. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/recipient_tool.py +0 -0
  71. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  72. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/rewind_tool.py +0 -0
  73. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  74. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/typed_task.py +0 -0
  75. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent/xml_tool_message.py +0 -0
  76. {langroid-0.30.1 → langroid-0.31.0}/langroid/agent_config.py +0 -0
  77. {langroid-0.30.1 → langroid-0.31.0}/langroid/cachedb/__init__.py +0 -0
  78. {langroid-0.30.1 → langroid-0.31.0}/langroid/cachedb/base.py +0 -0
  79. {langroid-0.30.1 → langroid-0.31.0}/langroid/cachedb/momento_cachedb.py +0 -0
  80. {langroid-0.30.1 → langroid-0.31.0}/langroid/cachedb/redis_cachedb.py +0 -0
  81. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/__init__.py +0 -0
  82. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/clustering.py +0 -0
  83. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  84. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  85. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  86. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  87. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  88. {langroid-0.30.1 → langroid-0.31.0}/langroid/embedding_models/remote_embeds.py +0 -0
  89. {langroid-0.30.1 → langroid-0.31.0}/langroid/exceptions.py +0 -0
  90. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/.chainlit/config.toml +0 -0
  91. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
  92. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/__init__.py +0 -0
  93. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/base.py +0 -0
  94. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/config.py +0 -0
  95. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/mock_lm.py +0 -0
  96. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  97. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  98. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  99. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  100. {langroid-0.30.1 → langroid-0.31.0}/langroid/language_models/utils.py +0 -0
  101. {langroid-0.30.1 → langroid-0.31.0}/langroid/mytypes.py +0 -0
  102. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/__init__.py +0 -0
  103. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/agent_chats.py +0 -0
  104. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/code-parsing.md +0 -0
  105. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/code_parser.py +0 -0
  106. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/config.py +0 -0
  107. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/document_parser.py +0 -0
  108. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/image_text.py +0 -0
  109. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/para_sentence_split.py +0 -0
  110. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/parse_json.py +0 -0
  111. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/parser.py +0 -0
  112. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/repo_loader.py +0 -0
  113. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/routing.py +0 -0
  114. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/search.py +0 -0
  115. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/spider.py +0 -0
  116. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/table_loader.py +0 -0
  117. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/url_loader.py +0 -0
  118. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/url_loader_cookies.py +0 -0
  119. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/urls.py +0 -0
  120. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/utils.py +0 -0
  121. {langroid-0.30.1 → langroid-0.31.0}/langroid/parsing/web_search.py +0 -0
  122. {langroid-0.30.1 → langroid-0.31.0}/langroid/prompts/__init__.py +0 -0
  123. {langroid-0.30.1 → langroid-0.31.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
  124. {langroid-0.30.1 → langroid-0.31.0}/langroid/prompts/dialog.py +0 -0
  125. {langroid-0.30.1 → langroid-0.31.0}/langroid/prompts/prompts_config.py +0 -0
  126. {langroid-0.30.1 → langroid-0.31.0}/langroid/prompts/templates.py +0 -0
  127. {langroid-0.30.1 → langroid-0.31.0}/langroid/py.typed +0 -0
  128. {langroid-0.30.1 → langroid-0.31.0}/langroid/pydantic_v1/__init__.py +0 -0
  129. {langroid-0.30.1 → langroid-0.31.0}/langroid/pydantic_v1/main.py +0 -0
  130. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/.chainlit/config.toml +0 -0
  131. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/.chainlit/translations/en-US.json +0 -0
  132. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/__init__.py +0 -0
  133. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/algorithms/__init__.py +0 -0
  134. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/algorithms/graph.py +0 -0
  135. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/configuration.py +0 -0
  136. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/constants.py +0 -0
  137. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/docker.py +0 -0
  138. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/git_utils.py +0 -0
  139. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/globals.py +0 -0
  140. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/llms/__init__.py +0 -0
  141. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/llms/strings.py +0 -0
  142. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/logging.py +0 -0
  143. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/object_registry.py +0 -0
  144. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/output/__init__.py +0 -0
  145. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/output/citations.py +0 -0
  146. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/output/printing.py +0 -0
  147. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/output/status.py +0 -0
  148. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/pandas_utils.py +0 -0
  149. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/pydantic_utils.py +0 -0
  150. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/system.py +0 -0
  151. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/types.py +0 -0
  152. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/web/__init__.py +0 -0
  153. {langroid-0.30.1 → langroid-0.31.0}/langroid/utils/web/login.py +0 -0
  154. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/__init__.py +0 -0
  155. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/base.py +0 -0
  156. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/chromadb.py +0 -0
  157. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/lancedb.py +0 -0
  158. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/meilisearch.py +0 -0
  159. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/momento.py +0 -0
  160. {langroid-0.30.1 → langroid-0.31.0}/langroid/vector_store/qdrant_cloud.py +0 -0
  161. {langroid-0.30.1 → langroid-0.31.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.30.1
3
+ Version: 0.31.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -249,6 +249,14 @@ teacher_task.run()
249
249
  <details>
250
250
  <summary> <b>Click to expand</b></summary>
251
251
 
252
+ - **Dec 2024:**
253
+ - [0.30.0](https://github.com/langroid/langroid/releases/tag/0.30.0) Llama-cpp embeddings.
254
+ - [0.29.0](https://github.com/langroid/langroid/releases/tag/0.29.0) Custom Azure OpenAI Client
255
+ - [0.28.0](https://github.com/langroid/langroid/releases/tag/0.28.0) `ToolMessage`: `_handler` field to override
256
+ default handler method name in `request` field.
257
+ - [0.27.0](https://github.com/langroid/langroid/releases/tag/0.27.0) OpenRouter Support.
258
+ - [0.26.0](https://github.com/langroid/langroid/releases/tag/0.26.0) Update to latest Chainlit.
259
+ - [0.25.0](https://github.com/langroid/langroid/releases/tag/0.25.0) True Async Methods for agent and user-response.
252
260
  - **Nov 2024:**
253
261
  - **[0.24.0](https://langroid.github.io/langroid/notes/structured-output/)**:
254
262
  Enables support for `Agent`s with strict JSON schema output format on compatible LLMs and strict mode for the OpenAI tools API.
@@ -135,6 +135,14 @@ teacher_task.run()
135
135
  <details>
136
136
  <summary> <b>Click to expand</b></summary>
137
137
 
138
+ - **Dec 2024:**
139
+ - [0.30.0](https://github.com/langroid/langroid/releases/tag/0.30.0) Llama-cpp embeddings.
140
+ - [0.29.0](https://github.com/langroid/langroid/releases/tag/0.29.0) Custom Azure OpenAI Client
141
+ - [0.28.0](https://github.com/langroid/langroid/releases/tag/0.28.0) `ToolMessage`: `_handler` field to override
142
+ default handler method name in `request` field.
143
+ - [0.27.0](https://github.com/langroid/langroid/releases/tag/0.27.0) OpenRouter Support.
144
+ - [0.26.0](https://github.com/langroid/langroid/releases/tag/0.26.0) Update to latest Chainlit.
145
+ - [0.25.0](https://github.com/langroid/langroid/releases/tag/0.25.0) True Async Methods for agent and user-response.
138
146
  - **Nov 2024:**
139
147
  - **[0.24.0](https://langroid.github.io/langroid/notes/structured-output/)**:
140
148
  Enables support for `Agent`s with strict JSON schema output format on compatible LLMs and strict mode for the OpenAI tools API.
@@ -24,6 +24,8 @@ class EmbeddingModel(ABC):
24
24
  @classmethod
25
25
  def create(cls, config: EmbeddingModelsConfig) -> "EmbeddingModel":
26
26
  from langroid.embedding_models.models import (
27
+ AzureOpenAIEmbeddings,
28
+ AzureOpenAIEmbeddingsConfig,
27
29
  FastEmbedEmbeddings,
28
30
  FastEmbedEmbeddingsConfig,
29
31
  LlamaCppServerEmbeddings,
@@ -42,6 +44,8 @@ class EmbeddingModel(ABC):
42
44
  return RemoteEmbeddings(config)
43
45
  elif isinstance(config, OpenAIEmbeddingsConfig):
44
46
  return OpenAIEmbeddings(config)
47
+ elif isinstance(config, AzureOpenAIEmbeddingsConfig):
48
+ return AzureOpenAIEmbeddings(config)
45
49
  elif isinstance(config, SentenceTransformerEmbeddingsConfig):
46
50
  return SentenceTransformerEmbeddings(config)
47
51
  elif isinstance(config, FastEmbedEmbeddingsConfig):
@@ -1,18 +1,20 @@
1
1
  import atexit
2
2
  import os
3
3
  from functools import cached_property
4
- from typing import Any, Callable, Dict, List, Optional
4
+ from typing import Any, Callable, Dict, List, Optional, Union
5
5
 
6
6
  import requests
7
7
  import tiktoken
8
8
  from dotenv import load_dotenv
9
- from openai import OpenAI
9
+ from openai import AzureOpenAI, OpenAI
10
10
 
11
11
  from langroid.embedding_models.base import EmbeddingModel, EmbeddingModelsConfig
12
12
  from langroid.exceptions import LangroidImportError
13
13
  from langroid.mytypes import Embeddings
14
14
  from langroid.parsing.utils import batched
15
15
 
16
+ AzureADTokenProvider = Callable[[], str]
17
+
16
18
 
17
19
  class OpenAIEmbeddingsConfig(EmbeddingModelsConfig):
18
20
  model_type: str = "openai"
@@ -24,6 +26,26 @@ class OpenAIEmbeddingsConfig(EmbeddingModelsConfig):
24
26
  context_length: int = 8192
25
27
 
26
28
 
29
+ class AzureOpenAIEmbeddingsConfig(EmbeddingModelsConfig):
30
+ model_type: str = "azure-openai"
31
+ model_name: str = "text-embedding-ada-002"
32
+ api_key: str = ""
33
+ api_base: str = ""
34
+ deployment_name: Optional[str] = None
35
+ # api_version defaulted to 2024-06-01 as per https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/embeddings?tabs=python-new
36
+ # change this to required supported version
37
+ api_version: Optional[str] = "2024-06-01"
38
+ # TODO: Add auth support for Azure OpenAI via AzureADTokenProvider
39
+ azure_ad_token: Optional[str] = None
40
+ azure_ad_token_provider: Optional[AzureADTokenProvider] = None
41
+ dims: int = 1536
42
+ context_length: int = 8192
43
+
44
+ class Config:
45
+ # enable auto-loading of env vars with AZURE_OPENAI_ prefix
46
+ env_prefix = "AZURE_OPENAI_"
47
+
48
+
27
49
  class SentenceTransformerEmbeddingsConfig(EmbeddingModelsConfig):
28
50
  model_type: str = "sentence-transformer"
29
51
  model_name: str = "BAAI/bge-large-en-v1.5"
@@ -58,11 +80,11 @@ class LlamaCppServerEmbeddingsConfig(EmbeddingModelsConfig):
58
80
  class EmbeddingFunctionCallable:
59
81
  """
60
82
  A callable class designed to generate embeddings for a list of texts using
61
- the OpenAI API, with automatic retries on failure.
83
+ the OpenAI or Azure OpenAI API, with automatic retries on failure.
62
84
 
63
85
  Attributes:
64
86
  embed_model (EmbeddingModel): An instance of EmbeddingModel that provides
65
- configuration and utilities for generating embeddings.
87
+ configuration and utilities for generating embeddings.
66
88
 
67
89
  Methods:
68
90
  __call__(input: List[str]) -> Embeddings: Generate embeddings for
@@ -74,8 +96,9 @@ class EmbeddingFunctionCallable:
74
96
  Initialize the EmbeddingFunctionCallable with a specific model.
75
97
 
76
98
  Args:
77
- model (OpenAIEmbeddings): An instance of OpenAIEmbeddings to use for
78
- generating embeddings.
99
+ model ( OpenAIEmbeddings or AzureOpenAIEmbeddings): An instance of
100
+ OpenAIEmbeddings or AzureOpenAIEmbeddings to use for
101
+ generating embeddings.
79
102
  batch_size (int): Batch size
80
103
  """
81
104
  self.embed_model = embed_model
@@ -99,8 +122,7 @@ class EmbeddingFunctionCallable:
99
122
  Embeddings: A list of embedding vectors corresponding to the input texts.
100
123
  """
101
124
  embeds = []
102
-
103
- if isinstance(self.embed_model, OpenAIEmbeddings):
125
+ if isinstance(self.embed_model, (OpenAIEmbeddings, AzureOpenAIEmbeddings)):
104
126
  tokenized_texts = self.embed_model.truncate_texts(input)
105
127
 
106
128
  for batch in batched(tokenized_texts, self.batch_size):
@@ -178,6 +200,72 @@ class OpenAIEmbeddings(EmbeddingModel):
178
200
  return self.config.dims
179
201
 
180
202
 
203
+ class AzureOpenAIEmbeddings(EmbeddingModel):
204
+ """
205
+ Azure OpenAI embeddings model implementation.
206
+ """
207
+
208
+ def __init__(
209
+ self, config: AzureOpenAIEmbeddingsConfig = AzureOpenAIEmbeddingsConfig()
210
+ ):
211
+ """
212
+ Initializes Azure OpenAI embeddings model.
213
+
214
+ Args:
215
+ config: Configuration for Azure OpenAI embeddings model.
216
+ Raises:
217
+ ValueError: If required Azure config values are not set.
218
+ """
219
+ super().__init__()
220
+ self.config = config
221
+ load_dotenv()
222
+
223
+ if self.config.api_key == "":
224
+ raise ValueError(
225
+ """AZURE_OPENAI_API_KEY env variable must be set to use
226
+ AzureOpenAIEmbeddings. Please set the AZURE_OPENAI_API_KEY value
227
+ in your .env file."""
228
+ )
229
+
230
+ if self.config.api_base == "":
231
+ raise ValueError(
232
+ """AZURE_OPENAI_API_BASE env variable must be set to use
233
+ AzureOpenAIEmbeddings. Please set the AZURE_OPENAI_API_BASE value
234
+ in your .env file."""
235
+ )
236
+ self.client = AzureOpenAI(
237
+ api_key=self.config.api_key,
238
+ api_version=self.config.api_version,
239
+ azure_endpoint=self.config.api_base,
240
+ azure_deployment=self.config.deployment_name,
241
+ )
242
+ self.tokenizer = tiktoken.encoding_for_model(self.config.model_name)
243
+
244
+ def truncate_texts(self, texts: List[str]) -> List[List[int]]:
245
+ """
246
+ Truncate texts to the embedding model's context length.
247
+ TODO: Maybe we should show warning, and consider doing T5 summarization?
248
+ """
249
+ return [
250
+ self.tokenizer.encode(text, disallowed_special=())[
251
+ : self.config.context_length
252
+ ]
253
+ for text in texts
254
+ ]
255
+
256
+ def embedding_fn(self) -> Callable[[List[str]], Embeddings]:
257
+ """Get the embedding function for Azure OpenAI.
258
+
259
+ Returns:
260
+ Callable that generates embeddings for input texts.
261
+ """
262
+ return EmbeddingFunctionCallable(self, self.config.batch_size)
263
+
264
+ @property
265
+ def embedding_dims(self) -> int:
266
+ return self.config.dims
267
+
268
+
181
269
  STEC = SentenceTransformerEmbeddingsConfig
182
270
 
183
271
 
@@ -352,13 +440,19 @@ class LlamaCppServerEmbeddings(EmbeddingModel):
352
440
  def embedding_model(embedding_fn_type: str = "openai") -> EmbeddingModel:
353
441
  """
354
442
  Args:
355
- embedding_fn_type: "openai" or "fastembed" or
356
- "llamacppserver" or "sentencetransformer" # others soon
443
+ embedding_fn_type: Type of embedding model to use. Options are:
444
+ - "openai",
445
+ - "azure-openai",
446
+ - "sentencetransformer", or
447
+ - "fastembed".
448
+ (others may be added in the future)
357
449
  Returns:
358
- EmbeddingModel
450
+ EmbeddingModel: The corresponding embedding model class.
359
451
  """
360
452
  if embedding_fn_type == "openai":
361
453
  return OpenAIEmbeddings # type: ignore
454
+ elif embedding_fn_type == "azure-openai":
455
+ return AzureOpenAIEmbeddings # type: ignore
362
456
  elif embedding_fn_type == "fastembed":
363
457
  return FastEmbedEmbeddings # type: ignore
364
458
  elif embedding_fn_type == "llamacppserver":
@@ -0,0 +1,391 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from typing import Callable, Dict, List, Optional, Union
4
+
5
+ import langroid as lr
6
+ from langroid.language_models.mock_lm import MockLMConfig
7
+
8
+ # Fix logging level type
9
+ logging.basicConfig(level=logging.WARNING)
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def sum_fn(s: str) -> str:
14
+ """Dummy response for MockLM"""
15
+ nums = [
16
+ int(subpart)
17
+ for part in s.split()
18
+ for subpart in part.split(",")
19
+ if subpart.isdigit()
20
+ ]
21
+ return str(sum(nums) + 1)
22
+
23
+
24
+ def user_message(msg: Union[str, lr.ChatDocument]) -> lr.ChatDocument:
25
+ """Create a user-role msg from a string or ChatDocument"""
26
+ if isinstance(msg, lr.ChatDocument):
27
+ return msg
28
+ return lr.ChatDocument(
29
+ content=msg,
30
+ metadata=lr.ChatDocMetaData(
31
+ sender=lr.Entity.USER,
32
+ sender_name="user",
33
+ ),
34
+ )
35
+
36
+
37
+ class InputContext:
38
+ """Context for a Component to respond to"""
39
+
40
+ def __init__(self) -> None:
41
+ self.messages: List[lr.ChatDocument] = []
42
+
43
+ def add(
44
+ self, results: Union[str, List[str], lr.ChatDocument, List[lr.ChatDocument]]
45
+ ) -> None:
46
+ """
47
+ Add messages to the input messages list
48
+ """
49
+ msgs: List[lr.ChatDocument] = []
50
+ if isinstance(results, str):
51
+ msgs = [user_message(results)]
52
+ elif isinstance(results, lr.ChatDocument):
53
+ msgs = [results]
54
+ elif isinstance(results, list):
55
+ if len(results) == 0:
56
+ return
57
+ if isinstance(results[0], str):
58
+ msgs = [user_message(r) for r in results]
59
+ else:
60
+ msgs = [r for r in results if isinstance(r, lr.ChatDocument)]
61
+ self.messages.extend(msgs)
62
+
63
+ def clear(self) -> None:
64
+ self.messages.clear()
65
+
66
+ def get_context(self) -> lr.ChatDocument:
67
+ """Construct a user-role ChatDocument from the input messages"""
68
+ if len(self.messages) == 0:
69
+ return lr.ChatDocument(content="", metadata={"sender": lr.Entity.USER})
70
+ content = "\n".join(
71
+ f"{msg.metadata.sender_name}: {msg.content}" for msg in self.messages
72
+ )
73
+ return lr.ChatDocument(content=content, metadata={"sender": lr.Entity.USER})
74
+
75
+
76
+ class Scheduler(ABC):
77
+ """Schedule the Components of a Team"""
78
+
79
+ def __init__(self) -> None:
80
+ self.init_state()
81
+
82
+ def init_state(self) -> None:
83
+ self.stepped = False
84
+ self.responders: List[str] = []
85
+ self.responder_counts: Dict[str, int] = {}
86
+ self.current_result: List[lr.ChatDocument] = []
87
+
88
+ @abstractmethod
89
+ def step(self) -> None:
90
+ pass
91
+
92
+ @abstractmethod
93
+ def done(self) -> bool:
94
+ pass
95
+
96
+ @abstractmethod
97
+ def result(self) -> List[lr.ChatDocument]:
98
+ pass
99
+
100
+ def run(self) -> List[lr.ChatDocument]:
101
+ self.init_state()
102
+ while not self.done():
103
+ self.step()
104
+ return self.result()
105
+
106
+
107
+ class Component(ABC):
108
+ """A component of a Team"""
109
+
110
+ def __init__(self) -> None:
111
+ self.input = InputContext()
112
+ self._listeners: List["Component"] = []
113
+ self.name: str = ""
114
+
115
+ @abstractmethod
116
+ def run(self) -> List[lr.ChatDocument]:
117
+ pass
118
+
119
+ def listen(self, component: Union["Component", List["Component"]]) -> None:
120
+ if isinstance(component, list):
121
+ for comp in component:
122
+ comp.listeners.append(self)
123
+ else:
124
+ component.listeners.append(self)
125
+
126
+ @property
127
+ def listeners(self) -> List["Component"]:
128
+ return self._listeners
129
+
130
+ def _notify(self, results: List[lr.ChatDocument]) -> None:
131
+ logger.warning(f"{self.name} Notifying listeners...")
132
+ for listener in self.listeners:
133
+ logger.warning(f"--> Listener {listener.name} notified")
134
+ listener.input.add(results)
135
+
136
+
137
+ class SimpleScheduler(Scheduler):
138
+ def __init__(
139
+ self,
140
+ components: List[Component],
141
+ ) -> None:
142
+ super().__init__()
143
+ self.components = components # Get components from team
144
+ self.stepped: bool = False
145
+
146
+ def step(self) -> None:
147
+ results = []
148
+ for comp in self.components:
149
+ result = comp.run()
150
+ if result:
151
+ results.extend(result)
152
+ self.current_result = results
153
+ self.stepped = True
154
+
155
+ def done(self) -> bool:
156
+ """done after 1 step, i.e. all components have responded"""
157
+ return self.stepped
158
+
159
+ def result(self) -> List[lr.ChatDocument]:
160
+ return self.current_result
161
+
162
+
163
+ class OrElseScheduler(Scheduler):
164
+ """
165
+ Implements "OrElse scheduling", i.e. if the components are A, B, C, then
166
+ in each step, it will try for a valid response from A OrElse B OrElse C,
167
+ i.e. the first component that gives a valid response is chosen.
168
+ In the next step, it will start from the next component in the list,
169
+ cycling back to the first component after the last component.
170
+ (There may be a better name than OrElseScheduler though.)
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ components: List[Component],
176
+ ) -> None:
177
+ super().__init__()
178
+ self.components = components
179
+ self.team: Optional[Team] = None
180
+ self.current_index: int = 0
181
+
182
+ def init_state(self) -> None:
183
+ super().init_state()
184
+ self.current_index = 0
185
+
186
+ def is_valid(self, result: Optional[List[lr.ChatDocument]]) -> bool:
187
+ return result is not None and len(result) > 0
188
+
189
+ def step(self) -> None:
190
+ start_index = self.current_index
191
+ n = len(self.components)
192
+
193
+ for i in range(n):
194
+ idx = (start_index + i) % n
195
+ comp = self.components[idx]
196
+ result = comp.run()
197
+ if self.is_valid(result):
198
+ self.responders.append(comp.name)
199
+ self.responder_counts[comp.name] = (
200
+ self.responder_counts.get(comp.name, 0) + 1
201
+ )
202
+ self.current_result = result
203
+ # cycle to next component
204
+ self.current_index = (idx + 1) % n
205
+ return
206
+
207
+ def done(self) -> bool:
208
+ if self.team is None:
209
+ return False
210
+ return self.team.done(self)
211
+
212
+ def result(self) -> List[lr.ChatDocument]:
213
+ return self.current_result
214
+
215
+
216
+ class Team(Component):
217
+ def __init__(
218
+ self,
219
+ name: str,
220
+ done_condition: Optional[Callable[["Team", Scheduler], bool]] = None,
221
+ ) -> None:
222
+ super().__init__()
223
+ self.name = name
224
+ self.components: List[Component] = []
225
+ self.scheduler: Optional[Scheduler] = None
226
+ self.done_condition = done_condition or Team.default_done_condition
227
+
228
+ def set_done_condition(
229
+ self, done_condition: Callable[["Team", Scheduler], bool]
230
+ ) -> None:
231
+ self.done_condition = done_condition
232
+
233
+ def done(self, scheduler: Scheduler) -> bool:
234
+ return self.done_condition(self, scheduler)
235
+
236
+ def default_done_condition(self, scheduler: Scheduler) -> bool:
237
+ # Default condition, can be overridden
238
+ return False
239
+
240
+ def add_scheduler(self, scheduler_class: type) -> None:
241
+ self.scheduler = scheduler_class(self.components)
242
+ if hasattr(self.scheduler, "team"):
243
+ setattr(self.scheduler, "team", self)
244
+
245
+ def add(self, component: Union[Component, List[Component]]) -> None:
246
+ if isinstance(component, list):
247
+ self.components.extend(component)
248
+ else:
249
+ self.components.append(component)
250
+
251
+ def reset(self) -> None:
252
+ self.input.clear()
253
+ if self.scheduler is not None:
254
+ self.scheduler.init_state()
255
+
256
+ def run(self, input: str | lr.ChatDocument | None = None) -> List[lr.ChatDocument]:
257
+ if input is not None:
258
+ self.input.add(input)
259
+ if self.scheduler is None:
260
+ raise ValueError(
261
+ f"Team '{self.name}' has no scheduler. Call add_scheduler() first."
262
+ )
263
+ input_str = self.input.get_context().content
264
+ logger.warning(f"Running team {self.name}... on input = {input_str}")
265
+ # push the input of self to each component that's a listener of self.
266
+ n_pushed = 0
267
+ for comp in self.components:
268
+
269
+ if comp in self.listeners:
270
+ comp.input.add(self.input.messages)
271
+ n_pushed += 1
272
+ if len(self.input.messages) > 0 and n_pushed == 0:
273
+ logger.warning(
274
+ """
275
+ Warning: Team inputs not pushed to any components!
276
+ You may not be able to run any components unless they have their
277
+ own inputs. Make sure to set up component to listen to parent team
278
+ if needed.
279
+ """
280
+ )
281
+ # clear own input since we've pushed it to internal components
282
+ self.input.clear()
283
+
284
+ result = self.scheduler.run()
285
+ if len(result) > 0:
286
+ self._notify(result)
287
+ result_value = result[0].content if len(result) > 0 else "null"
288
+ logger.warning(f"Team {self.name} done: {result_value}")
289
+ return result
290
+
291
+
292
+ class DummyAgent:
293
+ def __init__(self, name: str) -> None:
294
+ self.name = name
295
+
296
+ def process(self, data: str) -> str:
297
+ return f"{self.name} processed: {data}"
298
+
299
+
300
+ class TaskComponent(Component):
301
+ def __init__(self, task: lr.Task) -> None:
302
+ super().__init__()
303
+ self.task = task
304
+ self.name = task.agent.config.name
305
+
306
+ def run(self, input: str | lr.ChatDocument | None = None) -> List[lr.ChatDocument]:
307
+ if input is not None:
308
+ self.input.add(input)
309
+ input_msg = self.input.get_context()
310
+ if input_msg.content == "":
311
+ return []
312
+ logger.warning(f"Running task {self.name} on input = {input_msg.content}")
313
+ result = self.task.run(input_msg)
314
+ result_value = result.content if result else "null"
315
+ logger.warning(f"Task {self.name} done: {result_value}")
316
+ result_list = [result] if result else []
317
+ if len(result_list) > 0:
318
+ self._notify(result_list)
319
+ self.input.clear() # clear own input since we just consumed it!
320
+ return result_list
321
+
322
+
323
+ def make_task(name: str, sys: str = "") -> TaskComponent:
324
+ llm_config = MockLMConfig(response_fn=sum_fn)
325
+ agent = lr.ChatAgent(
326
+ lr.ChatAgentConfig(
327
+ llm=llm_config,
328
+ name=name,
329
+ )
330
+ )
331
+ # set as single_round since there are no Tools
332
+ task = lr.Task(agent, interactive=False, single_round=True)
333
+ return TaskComponent(task)
334
+
335
+
336
+ if __name__ == "__main__":
337
+ # Create agents, tasks
338
+ t1 = make_task("a1")
339
+ t2 = make_task("a2")
340
+ t3 = make_task("a3")
341
+
342
+ # done conditions for each time
343
+ def team1_done_condition(team: Team, scheduler: Scheduler) -> bool:
344
+ return (
345
+ scheduler.responder_counts.get("a1", 0) >= 2
346
+ and scheduler.responder_counts.get("a2", 0) >= 2
347
+ )
348
+
349
+ def team2_done_condition(team: Team, scheduler: Scheduler) -> bool:
350
+ return "a3" in scheduler.responders
351
+
352
+ def general_team_done_condition(team: Team, scheduler: Scheduler) -> bool:
353
+ # Example: all components have responded at least once
354
+ return len(set(scheduler.responders)) == len(team.components)
355
+
356
+ # Create teams
357
+ team1 = Team("T1", done_condition=team1_done_condition)
358
+ team2 = Team("T2", done_condition=team2_done_condition)
359
+
360
+ team = Team("Team", done_condition=general_team_done_condition)
361
+
362
+ team1.add_scheduler(OrElseScheduler)
363
+ team2.add_scheduler(OrElseScheduler)
364
+ team.add_scheduler(OrElseScheduler)
365
+
366
+ team.add([team1, team2])
367
+
368
+ # Build hierarchy
369
+ team1.add([t1, t2])
370
+ team2.add(t3)
371
+
372
+ # Set up listening
373
+ # team2.listen(team1) # listens to team1 final result
374
+ team1.listen(team)
375
+ t1.listen(team1)
376
+ t2.listen(t1)
377
+ t1.listen(t2)
378
+ # TODO should we forbid listening to a component OUTSIDE the team?
379
+
380
+ # t3 listens to its parent team2 =>
381
+ # any input to team2 gets pushed to t3 when t3 runs
382
+ team2.listen([t1, t2])
383
+ t3.listen(team2)
384
+
385
+ # TODO - we should either define which component of a team gets the teams inputs,
386
+ # or explicitly add messages to a specific component of the team
387
+
388
+ print("Running top-level team...")
389
+ result = team.run("1")
390
+
391
+ ##########