langroid 0.30.1__py3-none-any.whl → 0.31.1__py3-none-any.whl

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.
@@ -450,17 +450,23 @@ class ChatAgent(Agent):
450
450
  return ""
451
451
  instructions = []
452
452
  for msg_cls in enabled_classes:
453
- if (
454
- hasattr(msg_cls, "instructions")
455
- and inspect.ismethod(msg_cls.instructions)
456
- and msg_cls.default_value("request") in self.llm_tools_usable
457
- ):
453
+ if msg_cls.default_value("request") in self.llm_tools_usable:
454
+ class_instructions = ""
455
+ if hasattr(msg_cls, "instructions") and inspect.ismethod(
456
+ msg_cls.instructions
457
+ ):
458
+ class_instructions = msg_cls.instructions()
459
+ if (
460
+ self.config.use_tools
461
+ and hasattr(msg_cls, "langroid_tools_instructions")
462
+ and inspect.ismethod(msg_cls.langroid_tools_instructions)
463
+ ):
464
+ class_instructions += msg_cls.langroid_tools_instructions()
458
465
  # example will be shown in tool_format_rules() when using TOOLs,
459
466
  # so we don't need to show it here.
460
467
  example = "" if self.config.use_tools else (msg_cls.usage_examples())
461
468
  if example != "":
462
469
  example = "EXAMPLES:\n" + example
463
- class_instructions = msg_cls.instructions()
464
470
  guidance = (
465
471
  ""
466
472
  if class_instructions == ""
@@ -126,6 +126,18 @@ class ToolMessage(ABC, BaseModel):
126
126
 
127
127
  @classmethod
128
128
  def instructions(cls) -> str:
129
+ """
130
+ Instructions on tool usage.
131
+ """
132
+ return ""
133
+
134
+ @classmethod
135
+ def langroid_tools_instructions(cls) -> str:
136
+ """
137
+ Instructions on tool usage when `use_tools == True`, i.e.
138
+ when using langroid built-in tools
139
+ (as opposed to OpenAI-like function calls/tools).
140
+ """
129
141
  return """
130
142
  IMPORTANT: When using this or any other tool/function, you MUST include a
131
143
  `request` field and set it equal to the FUNCTION/TOOL NAME you intend to use.
@@ -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):
@@ -6,13 +6,15 @@ from typing import Any, Callable, Dict, List, Optional
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
+ ##########
@@ -6,7 +6,6 @@ from httpx import Timeout
6
6
  from openai import AsyncAzureOpenAI, AzureOpenAI
7
7
 
8
8
  from langroid.language_models.openai_gpt import (
9
- OpenAIChatModel,
10
9
  OpenAIGPT,
11
10
  OpenAIGPTConfig,
12
11
  )
@@ -32,11 +31,12 @@ class AzureConfig(OpenAIGPTConfig):
32
31
  deployment_name (str): can be set in the ``.env`` file as
33
32
  ``AZURE_OPENAI_DEPLOYMENT_NAME`` and should be based the custom name you
34
33
  chose for your deployment when you deployed a model.
35
- model_name (str): can be set in the ``.env`` file as ``AZURE_GPT_MODEL_NAME``
34
+ model_name (str): can be set in the ``.env``
35
+ file as ``AZURE_OPENAI_MODEL_NAME``
36
36
  and should be based on the model name chosen during setup.
37
37
  model_version (str): can be set in the ``.env`` file as
38
- ``AZURE_OPENAI_MODEL_VERSION`` and should be based on the model name
39
- chosen during setup.
38
+ ``AZURE_OPENAI_MODEL_VERSION`` and should be based on the model name
39
+ chosen during setup.
40
40
  """
41
41
 
42
42
  api_key: str = "" # CAUTION: set this ONLY via env var AZURE_OPENAI_API_KEY
@@ -145,70 +145,9 @@ class AzureGPT(OpenAIGPT):
145
145
  )
146
146
 
147
147
  # set the chat model to be the same as the model_name
148
- # This corresponds to the gpt model you chose for your deployment
149
- # when you deployed a model
150
- self.set_chat_model()
148
+ self.config.chat_model = self.config.model_name
151
149
 
152
150
  self.supports_json_schema = (
153
151
  self.config.api_version >= azureStructuredOutputAPIMin
154
152
  and self.config.model_version in azureStructuredOutputList
155
153
  )
156
-
157
- def set_chat_model(self) -> None:
158
- """
159
- Sets the chat model configuration based on the model name specified in the
160
- ``.env``. This function checks the `model_name` in the configuration and sets
161
- the appropriate chat model in the `config.chat_model`. It supports handling for
162
- 'gpt-35-turbo', 'gpt4-turbo', 'gpt-4o' and 'gpt-4o-mini' models. For
163
- 'gpt-4', it further delegates the handling to `handle_gpt4_model` method.
164
- If the model name does not match any predefined models, it defaults to
165
- `OpenAIChatModel.GPT4`.
166
- """
167
- MODEL_35_TURBO_NAMES = ("gpt-35-turbo", "35-turbo")
168
- MODEL_GPT4_TURBO_NAME = "gpt-4-turbo"
169
- MODEL_GPT4o_NAME = "gpt-4o"
170
- MODEL_GPT4o_MINI_NAME = "gpt-4o-mini"
171
- MODEL_GPT4_PREFIX = "gpt-4"
172
-
173
- if self.config.model_name in MODEL_35_TURBO_NAMES:
174
- self.config.chat_model = OpenAIChatModel.GPT3_5_TURBO
175
- elif self.config.model_name == MODEL_GPT4o_NAME:
176
- self.config.chat_model = OpenAIChatModel.GPT4o
177
- elif self.config.model_name == MODEL_GPT4o_MINI_NAME:
178
- self.config.chat_model = OpenAIChatModel.GPT4o_MINI
179
- elif self.config.model_name == MODEL_GPT4_TURBO_NAME:
180
- self.config.chat_model = OpenAIChatModel.GPT4_TURBO
181
- elif isinstance(
182
- self.config.model_name, str
183
- ) and self.config.model_name.startswith(MODEL_GPT4_PREFIX):
184
- self.handle_gpt4_model()
185
- else:
186
- self.config.chat_model = OpenAIChatModel.GPT4
187
-
188
- def handle_gpt4_model(self) -> None:
189
- """
190
- Handles the setting of the GPT-4 model in the configuration.
191
- This function checks the `model_version` in the configuration.
192
- If the version is not set, it raises a ValueError indicating
193
- that the model version needs to be specified in the ``.env``
194
- file. It sets `OpenAIChatMode.GPT4o` if the version is
195
- one of those listed below, and
196
- `OpenAIChatModel.GPT4_TURBO` if
197
- the version is '1106-Preview', otherwise, it defaults to
198
- setting `OpenAIChatModel.GPT4`.
199
- """
200
- VERSIONS_GPT4_TURBO = ("1106-Preview", "2024-04-09")
201
- VERSIONS_GPT4o = ("2024-05-13", "2024-08-06", "2024-11-20")
202
-
203
- if self.config.model_version == "":
204
- raise ValueError(
205
- "AZURE_OPENAI_MODEL_VERSION not set in .env file. "
206
- "Please set it to the chat model version used in your deployment."
207
- )
208
-
209
- if self.config.model_version in VERSIONS_GPT4o:
210
- self.config.chat_model = OpenAIChatModel.GPT4o
211
- elif self.config.model_version in VERSIONS_GPT4_TURBO:
212
- self.config.chat_model = OpenAIChatModel.GPT4_TURBO
213
- else:
214
- self.config.chat_model = OpenAIChatModel.GPT4
@@ -1702,12 +1702,7 @@ class OpenAIGPT(LanguageModel):
1702
1702
  ),
1703
1703
  )
1704
1704
 
1705
- # Azure uses different parameters. It uses ``engine`` instead of ``model``
1706
- # and the value should be the deployment_name not ``self.config.chat_model``
1707
1705
  chat_model = self.config.chat_model
1708
- if self.config.type == "azure":
1709
- if hasattr(self, "deployment_name"):
1710
- chat_model = self.deployment_name
1711
1706
 
1712
1707
  args: Dict[str, Any] = dict(
1713
1708
  model=chat_model,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.30.1
3
+ Version: 0.31.1
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.
@@ -16,7 +16,7 @@ langroid/agent/base.py,sha256=ZgWsRBC9rugcWp9aZLAmFFteU47pqKIEoTy_dgkYtBI,77529
16
16
  langroid/agent/batch.py,sha256=qK3ph6VNj_1sOhfXCZY4r6gh035DglDKU751p8BU0tY,14665
17
17
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  langroid/agent/callbacks/chainlit.py,sha256=C6zzzYC30qC4eMA7al7eFpRoTgoe3475kaMKyXgQM0Q,20695
19
- langroid/agent/chat_agent.py,sha256=qPo-avgv4ng7f3vdU4kCbQHLtSTpT2U8XRZbmE0uVKo,79684
19
+ langroid/agent/chat_agent.py,sha256=Idts_HDO1tW052POVOQ9FvuU37TTB7c1I96YVbnBumo,80030
20
20
  langroid/agent/chat_document.py,sha256=xPUMGzR83rn4iAEXIw2jy5LQ6YJ6Y0TiZ78XRQeDnJQ,17778
21
21
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
@@ -51,7 +51,7 @@ langroid/agent/special/sql/utils/tools.py,sha256=ovCePzq5cmbqw0vsVPBzxdZpUcSUIfT
51
51
  langroid/agent/special/table_chat_agent.py,sha256=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
52
52
  langroid/agent/structured_message.py,sha256=y7pud1EgRNeTFZlJmBkLmwME3yQJ_IYik-Xds9kdZbY,282
53
53
  langroid/agent/task.py,sha256=_iQTpjPIR1OGF0lq-ZFzQ92f_OmdIFmv7LlhoUA1QB8,89729
54
- langroid/agent/tool_message.py,sha256=aaJSypRy2sC8b2qOszc7efgr5TrQva51SUGnxTwKXEg,14406
54
+ langroid/agent/tool_message.py,sha256=HDW_FVQXvZAHI61CtOYNuZet0qlK_WwOnjSYd1g81eo,14742
55
55
  langroid/agent/tools/__init__.py,sha256=IMgCte-_ZIvCkozGQmvMqxIw7_nKLKzD78ccJL1bnQU,804
56
56
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
57
57
  langroid/agent/tools/file_tools.py,sha256=GjPB5YDILucYapElnvvoYpGJuZQ25ecLs2REv7edPEo,7292
@@ -70,9 +70,9 @@ langroid/cachedb/base.py,sha256=ztVjB1DtN6pLCujCWnR6xruHxwVj3XkYniRTYAKKqk0,1354
70
70
  langroid/cachedb/momento_cachedb.py,sha256=YEOJ62hEcV6iIeMr5aGgRYgWQqFYaej9gEDEcY0sm7M,3172
71
71
  langroid/cachedb/redis_cachedb.py,sha256=7kgnbf4b5CKsCrlL97mHWKvdvlLt8zgn7lc528jEpiE,5141
72
72
  langroid/embedding_models/__init__.py,sha256=XhVIMQJbQRpImcnhA9sJR7h6r7QgPo1SKDCvwEUD9j4,851
73
- langroid/embedding_models/base.py,sha256=Ipk6LcBZSmbu8HzfQy6uSp6-xAWCrmzX3MzhOr-jCZI,2208
73
+ langroid/embedding_models/base.py,sha256=DUhvzALoW2UMbtmLxP4eJTfPii99WjUNX7bwFpj_K-0,2395
74
74
  langroid/embedding_models/clustering.py,sha256=tZWElUqXl9Etqla0FAa7og96iDKgjqWjucZR_Egtp-A,6684
75
- langroid/embedding_models/models.py,sha256=pDED-Evwz3Y9tCwo2amOJVIIT0P9uskcbhs6gz6wVcU,13358
75
+ langroid/embedding_models/models.py,sha256=sW6baTvFSeZBZ5w-Kd9Vgo93gokesJ3aHP4x9htoF2E,16776
76
76
  langroid/embedding_models/protoc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  langroid/embedding_models/protoc/embeddings.proto,sha256=_O-SgFpTaylQeOTgSpxhEJ7CUw7PeCQQJLaPqpPYKJg,321
78
78
  langroid/embedding_models/protoc/embeddings_pb2.py,sha256=4Q57PhOunv-uZNJrxYrWBXAI0ZtfnVZXFRhRj5JuRSg,1662
@@ -80,14 +80,15 @@ langroid/embedding_models/protoc/embeddings_pb2.pyi,sha256=UkNy7BrNsmQm0vLb3NtGX
80
80
  langroid/embedding_models/protoc/embeddings_pb2_grpc.py,sha256=9dYQqkW3JPyBpSEjeGXTNpSqAkC-6FPtBHyteVob2Y8,2452
81
81
  langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-nGieNNAo1McHaY,5151
82
82
  langroid/exceptions.py,sha256=G60UVDChkUlBDVWHFr_43zUUszZHSejoU00tX_dfD68,2322
83
+ langroid/experimental/team-save.py,sha256=OHfACxuVMFNiU0Kx0uCClimJCyusZineMDJ0Ehh2jWM,12369
83
84
  langroid/language_models/.chainlit/config.toml,sha256=1t5lHORGzc2E6dkaO9P15jYHu2w-4Kl9pYjpDPc84vs,3716
84
85
  langroid/language_models/.chainlit/translations/en-US.json,sha256=DAFz2HjOFFfboCStrUfKFg2BpplJPK_OOtixwF_GivY,9931
85
86
  langroid/language_models/__init__.py,sha256=8o8D8Lxaq961_oxVpB_bC2iEJ1GXJqYXMlwUcn6OJb8,976
86
- langroid/language_models/azure_openai.py,sha256=0a86-EtjgRsYw9kuGv0Tu6EOFbTCc-d5kb3GCJaJA54,8757
87
+ langroid/language_models/azure_openai.py,sha256=zNQzzsERxNestq-hFfQZbvTzK43G2vjRWnTV3ktm1DQ,5845
87
88
  langroid/language_models/base.py,sha256=6hXR-bclyPif-BvFbyXevP-gEwiawQAJHX3N1AKNei0,23786
88
89
  langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
89
90
  langroid/language_models/mock_lm.py,sha256=5BgHKDVRWFbUwDT_PFgTZXz9-k8wJSA2e3PZmyDgQ1k,4022
90
- langroid/language_models/openai_gpt.py,sha256=mKlsSLxG835S2rvq9uNtapeutqvoM4A33mL3ywvRfDU,75627
91
+ langroid/language_models/openai_gpt.py,sha256=gkF3sQsBrS8_x4x0l6GLdF_5MlNRdsY5hAmRPSsKAKE,75320
91
92
  langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
92
93
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
93
94
  langroid/language_models/prompt_formatter/hf_formatter.py,sha256=PVJppmjRvD-2DF-XNC6mE05vTZ9wbu37SmXwZBQhad0,5055
@@ -154,8 +155,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
154
155
  langroid/vector_store/momento.py,sha256=UNHGT6jXuQtqY9f6MdqGU14bVnS0zHgIJUa30ULpUJo,10474
155
156
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
156
157
  langroid/vector_store/qdrantdb.py,sha256=v7mCsijc2GdRJyil-yFaUVAX4SX5D75mD3vzlpjCMuo,17393
157
- pyproject.toml,sha256=2C5IMt-RiukUCIxH4Zyrp6LDWer61y0IkfW3SN2Wfj0,7533
158
- langroid-0.30.1.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
159
- langroid-0.30.1.dist-info/METADATA,sha256=AJeOXFsiRYLBPiNiWtMCS8JVdnHBRC7oKDIjpycQs2I,57569
160
- langroid-0.30.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
161
- langroid-0.30.1.dist-info/RECORD,,
158
+ pyproject.toml,sha256=4LnvkHuotknbr-AsiwehYqKmmVSs_Xi7bYBbOf0E6X0,7525
159
+ langroid-0.31.1.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
160
+ langroid-0.31.1.dist-info/METADATA,sha256=g593rbues9sKvoTGX88_zLbVE5DqIxvo-ZkmamSnJt8,58250
161
+ langroid-0.31.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
162
+ langroid-0.31.1.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.30.1"
3
+ version = "0.31.1"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
@@ -155,7 +155,7 @@ fastembed = ["fastembed"]
155
155
  black = {extras = ["jupyter"], version = "^24.3.0"}
156
156
  flake8 = "^6.0.0"
157
157
  mypy = "^1.11.2"
158
- ruff = "^0.2.2"
158
+ ruff = "^0.8.4"
159
159
  pre-commit = "^3.3.2"
160
160
  autopep8 = "^2.0.2"
161
161
 
@@ -211,10 +211,10 @@ python_version = "3.11"
211
211
  #mypy_path = ["stubs"]
212
212
 
213
213
  #follow_imports = "skip"
214
- #check_untyped_defs = "True"
215
- disallow_untyped_defs = "True"
216
- ignore_missing_imports = "True"
217
- warn_unused_ignores = "False"
214
+ #check_untyped_defs = true
215
+ disallow_untyped_defs = true
216
+ ignore_missing_imports = true
217
+ warn_unused_ignores = false
218
218
  strict = true
219
219
  exclude = [
220
220
  "docs", ".venv", "venv", "examples", "examples_dev", "langroid/utils/web",