langroid 0.1.53__py3-none-any.whl → 0.1.54__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.
langroid/agent/base.py CHANGED
@@ -1,9 +1,20 @@
1
1
  import inspect
2
2
  import json
3
3
  import logging
4
+ import textwrap
4
5
  from abc import ABC
5
6
  from contextlib import ExitStack
6
- from typing import Callable, Dict, List, Optional, Set, Tuple, Type, cast, no_type_check
7
+ from typing import (
8
+ Callable,
9
+ Dict,
10
+ List,
11
+ Optional,
12
+ Set,
13
+ Tuple,
14
+ Type,
15
+ cast,
16
+ no_type_check,
17
+ )
7
18
 
8
19
  from pydantic import BaseSettings, ValidationError
9
20
  from rich import print
@@ -15,6 +26,9 @@ from langroid.agent.tool_message import INSTRUCTION, ToolMessage
15
26
  from langroid.language_models.base import (
16
27
  LanguageModel,
17
28
  LLMConfig,
29
+ LLMMessage,
30
+ LLMResponse,
31
+ LLMTokenUsage,
18
32
  )
19
33
  from langroid.mytypes import DocMetaData, Entity
20
34
  from langroid.parsing.json import extract_top_level_json
@@ -60,6 +74,8 @@ class Agent(ABC):
60
74
  self.llm_tools_map: Dict[str, Type[ToolMessage]] = {}
61
75
  self.llm_tools_handled: Set[str] = set()
62
76
  self.llm_tools_usable: Set[str] = set()
77
+ self.total_llm_token_cost = 0.0
78
+ self.total_llm_token_usage = 0
63
79
  self.default_human_response: Optional[str] = None
64
80
  self._indent = ""
65
81
  self.llm = LanguageModel.create(config.llm)
@@ -315,7 +331,7 @@ class Agent(ABC):
315
331
  else:
316
332
  user_msg = Prompt.ask(
317
333
  f"[blue]{self.indent}Human "
318
- f"(respond or q, x to exit current level, "
334
+ "(respond or q, x to exit current level, "
319
335
  f"or hit enter to continue)\n{self.indent}",
320
336
  ).strip()
321
337
 
@@ -410,6 +426,7 @@ class Agent(ABC):
410
426
  if self.llm.get_stream():
411
427
  console.print(f"[green]{self.indent}", end="")
412
428
  response = self.llm.generate(prompt, output_len)
429
+
413
430
  displayed = False
414
431
  if not self.llm.get_stream() or response.cached:
415
432
  # we would have already displayed the msg "live" ONLY if
@@ -417,7 +434,7 @@ class Agent(ABC):
417
434
  console.print(f"[green]{self.indent}", end="")
418
435
  print("[green]" + response.message)
419
436
  displayed = True
420
-
437
+ self.update_token_usage(response, prompt, self.llm.get_stream())
421
438
  return ChatDocument.from_LLMResponse(response, displayed)
422
439
 
423
440
  def get_tool_messages(self, msg: str | ChatDocument) -> List[ToolMessage]:
@@ -594,10 +611,66 @@ class Agent(ABC):
594
611
  result = f"Error in tool/function-call {tool_name} usage: {type(e)}: {e}"
595
612
  return result # type: ignore
596
613
 
597
- def num_tokens(self, prompt: str) -> int:
614
+ def num_tokens(self, prompt: str | List[LLMMessage]) -> int:
598
615
  if self.parser is None:
599
616
  raise ValueError("Parser must be set, to count tokens")
600
- return self.parser.num_tokens(prompt)
617
+ if isinstance(prompt, str):
618
+ return self.parser.num_tokens(prompt)
619
+ else:
620
+ return sum([self.parser.num_tokens(m.content) for m in prompt])
621
+
622
+ def update_token_usage(
623
+ self, response: LLMResponse, prompt: str | List[LLMMessage], stream: bool
624
+ ) -> None:
625
+ """
626
+ Updates `response.usage` obj (token usage and cost fields).the usage memebr
627
+ It updates the cost after checking the cache and updates the
628
+ tokens (prompts and completion) if the response stream is True, because OpenAI
629
+ doesn't returns these fields.
630
+
631
+ Args:
632
+ response (LLMResponse): LLMResponse object
633
+ prompt (str | List[LLMMessage]): prompt or list of LLMMessage objects
634
+ stream (bool): whether to update the usage in the response object
635
+ if the response is not cached.
636
+ """
637
+ if response is not None:
638
+ # Note: If response was not streamed, then
639
+ # `response.usage` would already have been set by the API,
640
+ # so we only need to update in the stream case.
641
+ if stream:
642
+ # usage, cost = 0 when response is from cache
643
+ prompt_tokens = 0
644
+ completion_tokens = 0
645
+ cost = 0.0
646
+ if not response.cached:
647
+ prompt_tokens = self.num_tokens(prompt)
648
+ completion_tokens = self.num_tokens(response.message)
649
+ cost = self.compute_token_cost(prompt_tokens, completion_tokens)
650
+ response.usage = LLMTokenUsage(
651
+ prompt_tokens=prompt_tokens,
652
+ completion_tokens=completion_tokens,
653
+ cost=cost,
654
+ )
655
+
656
+ if settings.debug and response.usage is not None:
657
+ print(
658
+ textwrap.dedent(
659
+ f"""
660
+ Stream: {stream}
661
+ prompt_tokens: {response.usage.prompt_tokens}
662
+ completion_tokens: {response.usage.completion_tokens}
663
+ """.lstrip()
664
+ )
665
+ )
666
+ # update total counters
667
+ if response.usage is not None:
668
+ self.total_llm_token_cost += response.usage.cost
669
+ self.total_llm_token_usage += response.usage.total_tokens
670
+
671
+ def compute_token_cost(self, prompt: int, completion: int) -> float:
672
+ price = cast(LanguageModel, self.llm).chat_cost()
673
+ return (price[0] * prompt + price[1] * completion) / 1000
601
674
 
602
675
  def ask_agent(
603
676
  self,
@@ -453,7 +453,8 @@ class ChatAgent(Agent):
453
453
  else:
454
454
  response_str = response.message
455
455
  print(cached + "[green]" + response_str)
456
-
456
+ stream = self.llm.get_stream() # type: ignore
457
+ self.update_token_usage(response, messages, stream)
457
458
  return ChatDocument.from_LLMResponse(response, displayed)
458
459
 
459
460
  def _llm_response_temp_context(self, message: str, prompt: str) -> ChatDocument:
@@ -7,6 +7,7 @@ from langroid.language_models.base import (
7
7
  LLMFunctionCall,
8
8
  LLMMessage,
9
9
  LLMResponse,
10
+ LLMTokenUsage,
10
11
  Role,
11
12
  )
12
13
  from langroid.mytypes import DocMetaData, Document, Entity
@@ -29,7 +30,7 @@ class ChatDocMetaData(DocMetaData):
29
30
  block: None | Entity = None
30
31
  sender_name: str = ""
31
32
  recipient: str = ""
32
- usage: int = 0
33
+ usage: Optional[LLMTokenUsage]
33
34
  cached: bool = False
34
35
  displayed: bool = False
35
36
 
@@ -119,7 +120,8 @@ class ChatDocument(Document):
119
120
 
120
121
  @staticmethod
121
122
  def from_LLMResponse(
122
- response: LLMResponse, displayed: bool = False
123
+ response: LLMResponse,
124
+ displayed: bool = False,
123
125
  ) -> "ChatDocument":
124
126
  recipient, message = response.get_recipient_and_message()
125
127
  return ChatDocument(
@@ -183,7 +185,10 @@ class ChatDocument(Document):
183
185
  content = message
184
186
 
185
187
  return LLMMessage(
186
- role=sender_role, content=content, function_call=fun_call, name=sender_name
188
+ role=sender_role,
189
+ content=content,
190
+ function_call=fun_call,
191
+ name=sender_name,
187
192
  )
188
193
 
189
194
 
@@ -7,7 +7,7 @@ Functionality includes:
7
7
  """
8
8
  import logging
9
9
  from contextlib import ExitStack
10
- from typing import List, Optional, no_type_check
10
+ from typing import List, Optional, Tuple, no_type_check
11
11
 
12
12
  from rich import print
13
13
  from rich.console import Console
@@ -304,7 +304,7 @@ class DocChatAgent(ChatAgent):
304
304
  )
305
305
 
306
306
  @no_type_check
307
- def get_relevant_extracts(self, query: str) -> List[Document]:
307
+ def get_relevant_extracts(self, query: str) -> Tuple[str, List[Document]]:
308
308
  """
309
309
  Get list of docs or extracts relevant to a query. These could be:
310
310
  - the original docs, if they exist and are not too long, or
@@ -316,6 +316,7 @@ class DocChatAgent(ChatAgent):
316
316
  query (str): query to search for
317
317
 
318
318
  Returns:
319
+ query (str): stand-alone version of input query
319
320
  List[Document]: list of relevant docs
320
321
 
321
322
  """
@@ -341,20 +342,18 @@ class DocChatAgent(ChatAgent):
341
342
  k=self.config.parsing.n_similar_docs,
342
343
  )
343
344
  if len(docs_and_scores) == 0:
344
- return []
345
+ return query, []
345
346
  passages = [
346
347
  Document(content=d.content, metadata=d.metadata)
347
348
  for (d, _) in docs_and_scores
348
349
  ]
349
350
 
350
- # if passages not too long, no need to extract relevant verbatim text
351
- extracts = passages
352
- if self.doc_length(passages) > self.config.max_context_tokens:
353
- with console.status("[cyan]LLM Extracting verbatim passages..."):
354
- with StreamingIfAllowed(self.llm, False):
355
- extracts = self.llm.get_verbatim_extracts(query, passages)
351
+ with console.status("[cyan]LLM Extracting verbatim passages..."):
352
+ with StreamingIfAllowed(self.llm, False):
353
+ extracts = self.llm.get_verbatim_extracts(query, passages)
354
+ extracts = [e for e in extracts if e.content != NO_ANSWER]
356
355
 
357
- return extracts
356
+ return query, extracts
358
357
 
359
358
  @no_type_check
360
359
  def answer_from_docs(self, query: str) -> Document:
@@ -373,7 +372,8 @@ class DocChatAgent(ChatAgent):
373
372
  source="None",
374
373
  ),
375
374
  )
376
- extracts = self.get_relevant_extracts(query)
375
+ # query may be updated to a stand-alone version
376
+ query, extracts = self.get_relevant_extracts(query)
377
377
  if len(extracts) == 0:
378
378
  return response
379
379
  with ExitStack() as stack:
langroid/io/base.py ADDED
File without changes
File without changes
langroid/io/refs.md ADDED
@@ -0,0 +1 @@
1
+ https://chat.openai.com/share/7c440b3f-ddbf-4ae6-a26f-ac28d947d403
File without changes
@@ -0,0 +1,72 @@
1
+ import os
2
+
3
+ import openai
4
+ from dotenv import load_dotenv
5
+
6
+ from langroid.language_models.openai_gpt import OpenAIGPT, OpenAIGPTConfig
7
+
8
+
9
+ class AzureConfig(OpenAIGPTConfig):
10
+ """
11
+ Configuration for Azure OpenAI GPT. You need to supply the env vars listed in
12
+ ``.azure_env_template`` after renaming the file to ``.azure_env``. Because this file
13
+ is used by this class to find the env vars.
14
+ Attributes:
15
+ type (str): should be ``azure``
16
+ api_version (str): can be set inside the ``.azure_env``
17
+ deployment_name (str): can be set inside the ``.azure_env`` and should be based
18
+ the custom name you chose for your deployment when you deployed a model
19
+ """
20
+
21
+ type: str = "azure"
22
+ api_version: str = "2023-07-01-preview"
23
+ deployment_name: str = ""
24
+
25
+
26
+ class AzureGPT(OpenAIGPT):
27
+ """
28
+ Class to access OpenAI LLMs via Azure. These env variables can be obtained from the
29
+ file `.azure_env`. Azure OpenAI doesn't support ``completion``
30
+ Attributes:
31
+ config: AzureConfig object
32
+ api_key: Azure API key
33
+ api_base: Azure API base url
34
+ api_version: Azure API version
35
+ """
36
+
37
+ def __init__(self, config: AzureConfig):
38
+ super().__init__(config)
39
+ self.config: AzureConfig = config
40
+ self.api_type = config.type
41
+ openai.api_type = self.api_type
42
+ load_dotenv(dotenv_path=".azure_env")
43
+ self.api_key = os.getenv("AZURE_API_KEY", "")
44
+ if self.api_key == "":
45
+ raise ValueError(
46
+ """
47
+ AZURE_API_KEY not set in .env file,
48
+ please set it to your Azure API key."""
49
+ )
50
+
51
+ self.api_base = os.getenv("OPENAI_API_BASE", "")
52
+ if self.api_base == "":
53
+ raise ValueError(
54
+ """
55
+ OPENAI_API_BASE not set in .env file,
56
+ please set it to your Azure API key."""
57
+ )
58
+ # we don't need this for ``api_key`` because it's handled inside
59
+ # ``openai_gpt.py`` methods before invoking chat/completion calls
60
+ else:
61
+ openai.api_base = self.api_base
62
+
63
+ self.api_version = os.getenv("OPENAI_API_VERSION", "") or config.api_version
64
+ openai.api_version = self.api_version
65
+
66
+ self.deployment_name = os.getenv("OPENAI_DEPLOYMENT_NAME", "")
67
+ if self.deployment_name == "":
68
+ raise ValueError(
69
+ """
70
+ OPENAI_DEPLOYMENT_NAME not set in .env file,
71
+ please set it to your Azure API key."""
72
+ )
@@ -36,6 +36,9 @@ class LLMConfig(BaseSettings):
36
36
  stream: bool = False # stream output from API?
37
37
  cache_config: None | RedisCacheConfig | MomentoCacheConfig = None
38
38
 
39
+ # Dict of model -> (input/prompt cost, output/completion cost)
40
+ cost_per_1k_tokens: Optional[Dict[str, Tuple[float, float]]] = None
41
+
39
42
 
40
43
  class LLMFunctionCall(BaseModel):
41
44
  """
@@ -63,6 +66,16 @@ class LLMFunctionSpec(BaseModel):
63
66
  parameters: Dict[str, Any]
64
67
 
65
68
 
69
+ class LLMTokenUsage(BaseModel):
70
+ prompt_tokens: int = 0
71
+ completion_tokens: int = 0
72
+ cost: float = 0.0
73
+
74
+ @property
75
+ def total_tokens(self) -> int:
76
+ return self.prompt_tokens + self.completion_tokens
77
+
78
+
66
79
  class Role(str, Enum):
67
80
  USER = "user"
68
81
  SYSTEM = "system"
@@ -116,7 +129,7 @@ class LLMResponse(BaseModel):
116
129
 
117
130
  message: str
118
131
  function_call: Optional[LLMFunctionCall] = None
119
- usage: int
132
+ usage: Optional[LLMTokenUsage]
120
133
  cached: bool = False
121
134
 
122
135
  def to_LLMMessage(self) -> LLMMessage:
@@ -193,13 +206,21 @@ class LanguageModel(ABC):
193
206
  config: configuration for language model
194
207
  Returns: instance of language model
195
208
  """
209
+ from langroid.language_models.azure_openai import AzureGPT
196
210
  from langroid.language_models.openai_gpt import OpenAIGPT
197
211
 
198
212
  if config is None or config.type is None:
199
213
  return None
214
+
215
+ openai: Union[Type[AzureGPT], Type[OpenAIGPT]]
216
+
217
+ if config.type == "azure":
218
+ openai = AzureGPT
219
+ else:
220
+ openai = OpenAIGPT
200
221
  cls = dict(
201
- openai=OpenAIGPT,
202
- ).get(config.type, OpenAIGPT)
222
+ openai=openai,
223
+ ).get(config.type, openai)
203
224
  return cls(config) # type: ignore
204
225
 
205
226
  @abstractmethod
@@ -248,6 +269,13 @@ class LanguageModel(ABC):
248
269
  raise ValueError("No context length specified")
249
270
  return self.config.context_length[self.config.completion_model]
250
271
 
272
+ def chat_cost(self) -> Tuple[float, float]:
273
+ if self.config.chat_model is None:
274
+ raise ValueError("No chat model specified")
275
+ if self.config.cost_per_1k_tokens is None:
276
+ raise ValueError("No cost per 1k tokens specified")
277
+ return self.config.cost_per_1k_tokens[self.config.chat_model]
278
+
251
279
  def followup_to_standalone(
252
280
  self, chat_history: List[Tuple[str, str]], question: str
253
281
  ) -> str:
@@ -368,7 +396,10 @@ class LanguageModel(ABC):
368
396
  sources = ""
369
397
  return Document(
370
398
  content=content,
371
- metadata={"source": "SOURCE: " + sources, "cached": llm_response.cached},
399
+ metadata={
400
+ "source": "SOURCE: " + sources,
401
+ "cached": llm_response.cached,
402
+ },
372
403
  )
373
404
 
374
405
 
@@ -20,6 +20,7 @@ from langroid.language_models.base import (
20
20
  LLMFunctionSpec,
21
21
  LLMMessage,
22
22
  LLMResponse,
23
+ LLMTokenUsage,
23
24
  Role,
24
25
  )
25
26
  from langroid.language_models.utils import (
@@ -62,6 +63,12 @@ class OpenAIGPTConfig(LLMConfig):
62
63
  OpenAIChatModel.GPT4_NOFUNC: 8192,
63
64
  OpenAICompletionModel.TEXT_DA_VINCI_003: 4096,
64
65
  }
66
+ cost_per_1k_tokens: Dict[str, Tuple[float, float]] = {
67
+ # (input/prompt cost, output/completion cost)
68
+ OpenAIChatModel.GPT3_5_TURBO: (0.0015, 0.002),
69
+ OpenAIChatModel.GPT4: (0.03, 0.06), # 8K context
70
+ OpenAIChatModel.GPT4_NOFUNC: (0.03, 0.06),
71
+ }
65
72
 
66
73
 
67
74
  class OpenAIResponse(BaseModel):
@@ -208,7 +215,6 @@ class OpenAIGPT(LanguageModel):
208
215
  return ( # type: ignore
209
216
  LLMResponse(
210
217
  message=completion,
211
- usage=0,
212
218
  cached=False,
213
219
  function_call=function_call if has_function else None,
214
220
  ),
@@ -229,6 +235,31 @@ class OpenAIGPT(LanguageModel):
229
235
  # Try to get the result from the cache
230
236
  return hashed_key, self.cache.retrieve(hashed_key)
231
237
 
238
+ def _cost_chat_model(self, prompt: int, completion: int) -> float:
239
+ price = self.chat_cost()
240
+ return (price[0] * prompt + price[1] * completion) / 1000
241
+
242
+ def _handle_token_usage(
243
+ self, cached: bool, response: Dict[str, Any]
244
+ ) -> LLMTokenUsage:
245
+ cost = 0.0
246
+ prompt_tokens = 0
247
+ completion_tokens = 0
248
+ if not cached and not self.config.stream:
249
+ prompt_tokens = response["usage"]["prompt_tokens"]
250
+ completion_tokens = response["usage"]["completion_tokens"]
251
+ cost = self._cost_chat_model(
252
+ response["usage"]["prompt_tokens"],
253
+ response["usage"]["completion_tokens"],
254
+ )
255
+ # if not self.config.stream:
256
+ # prompt_tokens = response["usage"]["prompt_tokens"]
257
+ # completion_tokens = response["usage"]["completion_tokens"]
258
+
259
+ return LLMTokenUsage(
260
+ prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, cost=cost
261
+ )
262
+
232
263
  def generate(self, prompt: str, max_tokens: int) -> LLMResponse:
233
264
  try:
234
265
  return self._generate(prompt, max_tokens)
@@ -236,7 +267,7 @@ class OpenAIGPT(LanguageModel):
236
267
  # capture exceptions not handled by retry, so we don't crash
237
268
  err_msg = str(e)[:500]
238
269
  logging.error(f"OpenAI API error: {err_msg}")
239
- return LLMResponse(message=NO_ANSWER, usage=0, cached=False)
270
+ return LLMResponse(message=NO_ANSWER, cached=False)
240
271
 
241
272
  def _generate(self, prompt: str, max_tokens: int) -> LLMResponse:
242
273
  if self.config.use_chat_for_completion:
@@ -265,8 +296,9 @@ class OpenAIGPT(LanguageModel):
265
296
  self.cache.store(hashed_key, result)
266
297
  return cached, hashed_key, result
267
298
 
299
+ key_name = "engine" if self.config.type == "azure" else "model"
268
300
  cached, hashed_key, response = completions_with_backoff(
269
- model=self.config.completion_model,
301
+ **{key_name: self.config.completion_model},
270
302
  prompt=prompt,
271
303
  max_tokens=max_tokens, # for output/completion
272
304
  request_timeout=self.config.timeout,
@@ -275,9 +307,8 @@ class OpenAIGPT(LanguageModel):
275
307
  stream=self.config.stream,
276
308
  )
277
309
 
278
- usage = response["usage"]["total_tokens"]
279
310
  msg = response["choices"][0]["text"].strip()
280
- return LLMResponse(message=msg, usage=usage, cached=cached)
311
+ return LLMResponse(message=msg, cached=cached)
281
312
 
282
313
  async def agenerate(self, prompt: str, max_tokens: int) -> LLMResponse:
283
314
  try:
@@ -286,7 +317,7 @@ class OpenAIGPT(LanguageModel):
286
317
  # capture exceptions not handled by retry, so we don't crash
287
318
  err_msg = str(e)[:500]
288
319
  logging.error(f"OpenAI API error: {err_msg}")
289
- return LLMResponse(message=NO_ANSWER, usage=0, cached=False)
320
+ return LLMResponse(message=NO_ANSWER, cached=False)
290
321
 
291
322
  async def _agenerate(self, prompt: str, max_tokens: int) -> LLMResponse:
292
323
  openai.api_key = self.api_key
@@ -324,7 +355,6 @@ class OpenAIGPT(LanguageModel):
324
355
  temperature=self.config.temperature,
325
356
  stream=self.config.stream,
326
357
  )
327
- usage = response["usage"]["total_tokens"]
328
358
  msg = response["choices"][0]["message"]["content"].strip()
329
359
  else:
330
360
 
@@ -349,9 +379,8 @@ class OpenAIGPT(LanguageModel):
349
379
  echo=False,
350
380
  stream=self.config.stream,
351
381
  )
352
- usage = response["usage"]["total_tokens"]
353
382
  msg = response["choices"][0]["text"].strip()
354
- return LLMResponse(message=msg, usage=usage, cached=cached)
383
+ return LLMResponse(message=msg, cached=cached)
355
384
 
356
385
  def chat(
357
386
  self,
@@ -366,7 +395,7 @@ class OpenAIGPT(LanguageModel):
366
395
  # capture exceptions not handled by retry, so we don't crash
367
396
  err_msg = str(e)[:500]
368
397
  logging.error(f"OpenAI API error: {err_msg}")
369
- return LLMResponse(message=NO_ANSWER, usage=0, cached=False)
398
+ return LLMResponse(message=NO_ANSWER, cached=False)
370
399
 
371
400
  def _chat(
372
401
  self,
@@ -421,8 +450,14 @@ class OpenAIGPT(LanguageModel):
421
450
  self.cache.store(hashed_key, result)
422
451
  return cached, hashed_key, result
423
452
 
453
+ if self.config.type == "azure":
454
+ key_name = "engine"
455
+ if hasattr(self, "deployment_name"):
456
+ self.config.chat_model = self.deployment_name
457
+ else:
458
+ key_name = "model"
424
459
  args: Dict[str, Any] = dict(
425
- model=self.config.chat_model,
460
+ **{key_name: self.config.chat_model},
426
461
  messages=[m.api_dict() for m in llm_messages],
427
462
  max_tokens=max_tokens,
428
463
  n=1,
@@ -447,7 +482,6 @@ class OpenAIGPT(LanguageModel):
447
482
  self.cache.store(hashed_key, openai_response)
448
483
  return llm_response
449
484
 
450
- usage = response["usage"]["total_tokens"]
451
485
  # openAI response will look like this:
452
486
  """
453
487
  {
@@ -477,7 +511,6 @@ class OpenAIGPT(LanguageModel):
477
511
  }
478
512
  }
479
513
  """
480
-
481
514
  message = response["choices"][0]["message"]
482
515
  msg = message["content"] or ""
483
516
  if message.get("function_call") is None:
@@ -489,10 +522,10 @@ class OpenAIGPT(LanguageModel):
489
522
  fun_call.arguments = fun_args
490
523
  except (ValueError, SyntaxError):
491
524
  logging.warning(
492
- f"Could not parse function arguments: "
525
+ "Could not parse function arguments: "
493
526
  f"{message['function_call']['arguments']} "
494
527
  f"for function {message['function_call']['name']} "
495
- f"treating as normal non-function message"
528
+ "treating as normal non-function message"
496
529
  )
497
530
  fun_call = None
498
531
  msg = message["content"] + message["function_call"]["arguments"]
@@ -500,6 +533,6 @@ class OpenAIGPT(LanguageModel):
500
533
  return LLMResponse(
501
534
  message=msg.strip() if msg is not None else "",
502
535
  function_call=fun_call,
503
- usage=usage,
504
536
  cached=cached,
537
+ usage=self._handle_token_usage(cached, response),
505
538
  )
@@ -8,15 +8,16 @@ EXTRACT_RELEVANT = """
8
8
  Question:{question}
9
9
  Relevant text, if any: """.strip()
10
10
 
11
- EXTRACTION_PROMPT_GPT4 = """
11
+ EXTRACTION_PROMPT_GPT4 = f"""
12
12
  Given the content and question below, extract COMPLETE SENTENCES OR PHRASES
13
13
  VERBATIM from the content, that are relevant to answering the question (if such text
14
14
  exists), even if it contradicts your knowledge, and even if it is factually incorrect.
15
15
  Do not make up an answer that is not supported by the content.
16
- When you answer, be concise, no need to explain anything.
16
+ When you answer, be concise, no need to explain anything. If there is no relevant text,
17
+ simply say {NO_ANSWER}.
17
18
 
18
- Content: {content}
19
- Question: {question}
19
+ Content: {{content}}
20
+ Question: {{question}}
20
21
  Relevant text, if any:
21
22
  """
22
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.53
3
+ Version: 0.1.54
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -75,7 +75,7 @@ Description-Content-Type: text/markdown
75
75
 
76
76
  <div align="center">
77
77
 
78
- [![PyPI version](https://badge.fury.io/py/langroid.svg)](https://badge.fury.io/py/langroid)
78
+ [![PyPI - Version](https://img.shields.io/pypi/v/langroid)](https://pypi.org/project/langroid/)
79
79
  [![Pytest](https://github.com/langroid/langroid/actions/workflows/pytest.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/pytest.yml)
80
80
  [![codecov](https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE)](https://codecov.io/gh/langroid/langroid)
81
81
  [![Lint](https://github.com/langroid/langroid/actions/workflows/validate.yml/badge.svg)](https://github.com/langroid/langroid/actions/workflows/validate.yml)
@@ -135,6 +135,7 @@ for ideas on what to contribute.
135
135
  <summary> <b>:fire: Updates/Releases</b></summary>
136
136
 
137
137
  - **Aug 2023:**
138
+ - **[Hierarchical computation](https://langroid.github.io/langroid/examples/agent-tree/)** example using Langroid agents and task orchestration.
138
139
  - **0.1.51:** Support for global state, see [test_global_state.py](tests/main/test_global_state.py).
139
140
  - **:whale: Langroid Docker image**, available, see instructions below.
140
141
  - [**RecipientTool**](langroid/agent/tools/recipient_tool.py) enables (+ enforces) LLM to
@@ -328,6 +329,26 @@ GOOGLE_CSE_ID=your-cse-id
328
329
  ```
329
330
  </details>
330
331
 
332
+ <details>
333
+ <summary><b>Setup instructions for Microsoft Azure OpenAI(click to expand)</b></summary>
334
+ In the root of the repo, copy the `.azure_env_template` file to a new file `.azure_env`:
335
+
336
+ ```bash
337
+ cp .azure_env_template .azure_env
338
+ ```
339
+
340
+ The file `.azure_env` contains four environment variables that are required to use Azure OpenAI: `AZURE_API_KEY`, `OPENAI_API_BASE`, `OPENAI_API_VERSION`, and `OPENAI_DEPLOYMENT_NAME`
341
+
342
+ This page [Microsoft Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart?tabs=command-line&pivots=programming-language-python#environment-variables)
343
+ provides more information, and you can set each environment variable as follows:
344
+
345
+ - `AZURE_API_KEY`, from the value of `API_KEY`
346
+ - `OPENAI_API_BASE` from the value of `ENDPOINT`, typically looks like `https://your.domain.azure.com`.
347
+ - For `OPENAI_API_VERSION`, you can use the default value in `.azure_env_template`, and latest version can be found [here](https://learn.microsoft.com/en-us/azure/ai-services/openai/whats-new#azure-openai-chat-completion-general-availability-ga)
348
+ - `OPENAI_DEPLOYMENT_NAME` is the deployment name you chose when you deployed the GPT-35-Turbo or GPT-4 models.
349
+
350
+ </details>
351
+
331
352
  ---
332
353
 
333
354
  # :whale: Docker Instructions
@@ -1,12 +1,12 @@
1
1
  langroid/__init__.py,sha256=sEKJ_5WJBAMZApevfeE3gxLK-eotVzJMJlT83G0rAko,30
2
2
  langroid/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- langroid/agent/base.py,sha256=_qlyXDf4MOZ7ZyPlaVyDIMPyE4gO2huf-rUJI6FgodE,23717
4
- langroid/agent/chat_agent.py,sha256=SAU06R5kv6bisQ8B5UdkMdHa89Xji6GvMrkjx9Ly0Uo,22529
5
- langroid/agent/chat_document.py,sha256=bh1xKUcTkikSrNbJ1mm0NP1aGVvJZr94LMZvEPM2nY4,6072
3
+ langroid/agent/base.py,sha256=bnqa_PZsw1_RWDv1w67g1rMrhbGTdt_mTPWcZ_uAZIk,26530
4
+ langroid/agent/chat_agent.py,sha256=Sma0-5XPHDzBOcduthwwlWBmkBgqpk8gGzStF8rcrps,22643
5
+ langroid/agent/chat_document.py,sha256=apaYj38sDu7ALCnsA8tJwoj3Z8zLNmIsNPd4-IujnGk,6153
6
6
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
8
8
  langroid/agent/special/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- langroid/agent/special/doc_chat_agent.py,sha256=KveFWGpiz3Md0_Zy_Q_AdLG-5rHl5wSparwhKOD2tOE,16486
9
+ langroid/agent/special/doc_chat_agent.py,sha256=yyf_kXj0rFmNWY_TjllGstZBg_hfFNDLiZ4p5P_P5Rg,16528
10
10
  langroid/agent/special/recipient_validator_agent.py,sha256=R3Rit93BNWQar_9stuDBGzmLr2W-IYOQ7oq-tlNNlps,6035
11
11
  langroid/agent/special/retriever_agent.py,sha256=DeOB5crFjXBvDEZT9k9ZVinOfFM2VgS6tQWWFyXSk9o,7204
12
12
  langroid/agent/special/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,9 +28,14 @@ langroid/embedding_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
28
28
  langroid/embedding_models/base.py,sha256=176jDrjEAAhNzdFCG8pfossd8SAhvHR8Q5Y8pOOm0LI,983
29
29
  langroid/embedding_models/clustering.py,sha256=tZWElUqXl9Etqla0FAa7og96iDKgjqWjucZR_Egtp-A,6684
30
30
  langroid/embedding_models/models.py,sha256=1xcv9hqmCTsbUbS8v7XeZRsf25Tu79JUoSipIYpvNoo,2765
31
+ langroid/io/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ langroid/io/cmd_line.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ langroid/io/refs.md,sha256=B1lrcHhg0IOWm74OHHZrv8o41pVvX6hSiG5pJjpDF90,67
34
+ langroid/io/websocket.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
35
  langroid/language_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- langroid/language_models/base.py,sha256=B11jZ6fLSbg1rS1AXM0i_McLeeKpmXmR9vwjY2oOiKc,13751
33
- langroid/language_models/openai_gpt.py,sha256=eeX3gUFzQxUS2v-O5vMrLqiMO1-_T8mqM4mtVerL_UM,19377
36
+ langroid/language_models/azure_openai.py,sha256=Axzg-oysiMwKgJR6uGLBcnqVFf3NgykVN0b0Mex2rGY,2543
37
+ langroid/language_models/base.py,sha256=CHSMWJd9kFwMsI38pLmFcPtgkBUUQ3a47sj77kD8-bw,14743
38
+ langroid/language_models/openai_gpt.py,sha256=-qiQV2OldX9PShHX7UqknGTnudPpCn2C7n1NL2S-Be4,20748
34
39
  langroid/language_models/utils.py,sha256=rmnSn-sJ3aKl_wBdeLPkck0Li4Ed6zkCxZYYl7n1V34,4668
35
40
  langroid/mytypes.py,sha256=YA42IJcooJnTxAwk-B4FmZ1hqzIIF1ZZKcpUKzBTGGo,1537
36
41
  langroid/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -51,7 +56,7 @@ langroid/parsing/web_search.py,sha256=hGUVoSJNdpoT5rsm-ikAteMiUropHrzKaxN8EVVqO2
51
56
  langroid/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
57
  langroid/prompts/dialog.py,sha256=SpfiSyofSgy2pwD1YboHR_yHO3LEEMbv6j2sm874jKo,331
53
58
  langroid/prompts/prompts_config.py,sha256=EMK1Fm7EmS8y3CV4AkrVgn5K4NipiM4m7J8819W1KeM,98
54
- langroid/prompts/templates.py,sha256=4OAujKJzKIhqt4SQL2GE1aPahlZuDotlT_dZAKx0bqE,6311
59
+ langroid/prompts/templates.py,sha256=4X-07tnmUQ8Z_zaWRQAUUyKiErGztp3tERujqnG8sGA,6369
55
60
  langroid/prompts/transforms.py,sha256=GsQo1klGxUy0fACh6j0lTblk6XEl2erRnhRWlN2M4-c,2706
56
61
  langroid/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
62
  langroid/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -74,7 +79,7 @@ langroid/vector_store/base.py,sha256=QZx3NUNwf2I0r3A7iuoUHIRGbqt_pFGD0hq1R-Yg8iM
74
79
  langroid/vector_store/chromadb.py,sha256=s5pQkKjaMP-Tt5A8M10EInFzttaALPbJAq7q4gf0TKg,5235
75
80
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
76
81
  langroid/vector_store/qdrantdb.py,sha256=KRvIIj1IZG2zFqejofMnRs2hT86B-27LgBEnuczdqOU,9072
77
- langroid-0.1.53.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
78
- langroid-0.1.53.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
79
- langroid-0.1.53.dist-info/METADATA,sha256=yDFzb9ZiXGpQ7DyeazZFV-8_srC_0xIs6SkGZLvkjnU,33607
80
- langroid-0.1.53.dist-info/RECORD,,
82
+ langroid-0.1.54.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
83
+ langroid-0.1.54.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
84
+ langroid-0.1.54.dist-info/METADATA,sha256=ZNXtQxEqxLa1l-4Bd2WHksnrCwAuv2Xf3zpapwZUahM,34974
85
+ langroid-0.1.54.dist-info/RECORD,,