langroid 0.1.229__py3-none-any.whl → 0.1.231__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/batch.py CHANGED
@@ -31,6 +31,7 @@ def run_batch_task_gen(
31
31
  turns: int = -1,
32
32
  message: Optional[str] = None,
33
33
  handle_exceptions: bool = False,
34
+ max_cost: float = 0.0,
34
35
  ) -> list[U]:
35
36
  """
36
37
  Generate and run copies of a task async/concurrently one per item in `items` list.
@@ -50,6 +51,7 @@ def run_batch_task_gen(
50
51
  turns (int): number of turns to run, -1 for infinite
51
52
  message (Optional[str]): optionally overrides the console status messages
52
53
  handle_exceptions: bool: Whether to replace exceptions with outputs of None
54
+ max_cost: float: maximum cost to run the task (default 0.0 for unlimited)
53
55
 
54
56
  Returns:
55
57
  list[Any]: list of final results
@@ -62,7 +64,7 @@ def run_batch_task_gen(
62
64
  task_i.agent.llm.set_stream(False)
63
65
  task_i.agent.config.show_stats = False
64
66
 
65
- result = await task_i.run_async(input, turns=turns)
67
+ result = await task_i.run_async(input, turns=turns, max_cost=max_cost)
66
68
  return result
67
69
 
68
70
  async def _do_all(
@@ -120,6 +122,7 @@ def run_batch_tasks(
120
122
  sequential: bool = True,
121
123
  batch_size: Optional[int] = None,
122
124
  turns: int = -1,
125
+ max_cost: float = 0.0,
123
126
  ) -> List[U]:
124
127
  """
125
128
  Run copies of `task` async/concurrently one per item in `items` list.
@@ -137,6 +140,7 @@ def run_batch_tasks(
137
140
  batch_size (Optional[int]): The number of tasks to run at a time,
138
141
  if None, unbatched
139
142
  turns (int): number of turns to run, -1 for infinite
143
+ max_cost: float: maximum cost to run the task (default 0.0 for unlimited)
140
144
 
141
145
  Returns:
142
146
  list[Any]: list of final results
@@ -151,6 +155,7 @@ def run_batch_tasks(
151
155
  batch_size,
152
156
  turns,
153
157
  message,
158
+ max_cost=max_cost,
154
159
  )
155
160
 
156
161
 
langroid/agent/task.py CHANGED
@@ -170,7 +170,7 @@ class Task:
170
170
  agent.set_system_message(system_message)
171
171
  if user_message:
172
172
  agent.set_user_message(user_message)
173
-
173
+ self.max_cost: float = 0
174
174
  self.logger: None | RichFileLogger = None
175
175
  self.tsv_logger: None | logging.Logger = None
176
176
  self.color_log: bool = False if settings.notebook else True
@@ -375,11 +375,13 @@ class Task:
375
375
  msg: Optional[str | ChatDocument] = None,
376
376
  turns: int = -1,
377
377
  caller: None | Task = None,
378
+ max_cost: float = 0,
378
379
  ) -> Optional[ChatDocument]:
379
380
  """Synchronous version of `run_async()`.
380
381
  See `run_async()` for details."""
381
382
  self.task_progress = False
382
383
  self.n_stalled_steps = 0
384
+ self.max_cost = max_cost
383
385
  assert (
384
386
  msg is None or isinstance(msg, str) or isinstance(msg, ChatDocument)
385
387
  ), f"msg arg in Task.run() must be None, str, or ChatDocument, not {type(msg)}"
@@ -418,6 +420,7 @@ class Task:
418
420
  msg: Optional[str | ChatDocument] = None,
419
421
  turns: int = -1,
420
422
  caller: None | Task = None,
423
+ max_cost: float = 0,
421
424
  ) -> Optional[ChatDocument]:
422
425
  """
423
426
  Loop over `step()` until task is considered done or `turns` is reached.
@@ -434,6 +437,7 @@ class Task:
434
437
  turns (int): number of turns to run the task for;
435
438
  default is -1, which means run until task is done.
436
439
  caller (Task|None): the calling task, if any
440
+ max_cost (float): maximum cost allowed for the task (default 0 -> no limit)
437
441
 
438
442
  Returns:
439
443
  Optional[ChatDocument]: valid result of the task.
@@ -444,6 +448,8 @@ class Task:
444
448
  # message can be considered to be from the USER
445
449
  # (from the POV of this agent's LLM).
446
450
  self.task_progress = False
451
+ self.n_stalled_steps = 0
452
+ self.max_cost = max_cost
447
453
  if (
448
454
  isinstance(msg, ChatDocument)
449
455
  and msg.metadata.recipient != ""
@@ -819,6 +825,7 @@ class Task:
819
825
  self.pending_message,
820
826
  turns=actual_turns,
821
827
  caller=self,
828
+ max_cost=self.max_cost,
822
829
  )
823
830
  result_str = str(ChatDocument.to_LLMMessage(result))
824
831
  maybe_tool = len(extract_top_level_json(result_str)) > 0
@@ -1051,6 +1058,16 @@ class Task:
1051
1058
  )
1052
1059
  return True
1053
1060
 
1061
+ if self.max_cost > 0 and self.agent.llm is not None:
1062
+ try:
1063
+ if self.agent.llm.tot_tokens_cost()[1] > self.max_cost:
1064
+ logger.warning(
1065
+ f"Task {self.name} exceeded max cost {self.max_cost}; exiting."
1066
+ )
1067
+ return True
1068
+ except Exception:
1069
+ pass
1070
+
1054
1071
  return (
1055
1072
  # no valid response from any entity/agent in current turn
1056
1073
  result is None
@@ -449,6 +449,18 @@ class LanguageModel(ABC):
449
449
  s += f"{model}: {counter}\n"
450
450
  return s
451
451
 
452
+ @classmethod
453
+ def tot_tokens_cost(cls) -> Tuple[int, float]:
454
+ """
455
+ Return total tokens used and total cost across all models.
456
+ """
457
+ total_tokens = 0
458
+ total_cost = 0.0
459
+ for counter in cls.usage_cost_dict.values():
460
+ total_tokens += counter.total_tokens
461
+ total_cost += counter.cost
462
+ return total_tokens, total_cost
463
+
452
464
  def followup_to_standalone(
453
465
  self, chat_history: List[Tuple[str, str]], question: str
454
466
  ) -> str:
@@ -49,7 +49,7 @@ from langroid.language_models.utils import (
49
49
  retry_with_exponential_backoff,
50
50
  )
51
51
  from langroid.utils.configuration import settings
52
- from langroid.utils.constants import NO_ANSWER, Colors
52
+ from langroid.utils.constants import Colors
53
53
  from langroid.utils.system import friendly_error
54
54
 
55
55
  logging.getLogger("openai").setLevel(logging.ERROR)
@@ -847,9 +847,9 @@ class OpenAIGPT(LanguageModel):
847
847
  try:
848
848
  return self._generate(prompt, max_tokens)
849
849
  except Exception as e:
850
- # capture exceptions not handled by retry, so we don't crash
850
+ # log and re-raise exception
851
851
  logging.error(friendly_error(e, "Error in OpenAIGPT.generate: "))
852
- return LLMResponse(message=NO_ANSWER, cached=False)
852
+ raise e
853
853
 
854
854
  def _generate(self, prompt: str, max_tokens: int) -> LLMResponse:
855
855
  if self.config.use_chat_for_completion:
@@ -917,9 +917,9 @@ class OpenAIGPT(LanguageModel):
917
917
  try:
918
918
  return await self._agenerate(prompt, max_tokens)
919
919
  except Exception as e:
920
- # capture exceptions not handled by retry, so we don't crash
920
+ # log and re-raise exception
921
921
  logging.error(friendly_error(e, "Error in OpenAIGPT.agenerate: "))
922
- return LLMResponse(message=NO_ANSWER, cached=False)
922
+ raise e
923
923
 
924
924
  async def _agenerate(self, prompt: str, max_tokens: int) -> LLMResponse:
925
925
  # note we typically will not have self.config.stream = True
@@ -1019,9 +1019,9 @@ class OpenAIGPT(LanguageModel):
1019
1019
  try:
1020
1020
  return self._chat(messages, max_tokens, functions, function_call)
1021
1021
  except Exception as e:
1022
- # capture exceptions not handled by retry, so we don't crash
1022
+ # log and re-raise exception
1023
1023
  logging.error(friendly_error(e, "Error in OpenAIGPT.chat: "))
1024
- return LLMResponse(message=NO_ANSWER, cached=False)
1024
+ raise e
1025
1025
 
1026
1026
  async def achat(
1027
1027
  self,
@@ -1072,9 +1072,9 @@ class OpenAIGPT(LanguageModel):
1072
1072
  result = await self._achat(messages, max_tokens, functions, function_call)
1073
1073
  return result
1074
1074
  except Exception as e:
1075
- # capture exceptions not handled by retry, so we don't crash
1075
+ # log and re-raise exception
1076
1076
  logging.error(friendly_error(e, "Error in OpenAIGPT.achat: "))
1077
- return LLMResponse(message=NO_ANSWER, cached=False)
1077
+ raise e
1078
1078
 
1079
1079
  @retry_with_exponential_backoff
1080
1080
  def _chat_completions_with_backoff(self, **kwargs): # type: ignore
@@ -62,6 +62,7 @@ def retry_with_exponential_backoff(
62
62
  if num_retries > max_retries:
63
63
  raise Exception(
64
64
  f"Maximum number of retries ({max_retries}) exceeded."
65
+ f" Last error: {e}."
65
66
  )
66
67
 
67
68
  # Increment the delay
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.229
3
+ Version: 0.1.231
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -1,7 +1,7 @@
1
1
  langroid/__init__.py,sha256=qgY-OqzYSWOc6EytQJN9sH2PwDp1UIzP9lXhrYH6aLU,1645
2
2
  langroid/agent/__init__.py,sha256=_D8dxnfdr92ch1CIrUkKjrB5HVvsQdn62b1Fb2kBxV8,785
3
3
  langroid/agent/base.py,sha256=jyGFmojrFuOy81lUkNsJlR6mLIOY6kOD20P9dhEcEuw,35059
4
- langroid/agent/batch.py,sha256=T9dgSPThrmIWxQxqDlGwhHa7yw3XIKE_U30bLMRDpNQ,9481
4
+ langroid/agent/batch.py,sha256=H64Vy0P5-Y1KNftNW7NhM1zLC5d8bFG_AcXC9nji23s,9745
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  langroid/agent/callbacks/chainlit.py,sha256=aYuJ8M4VDHr5oymoXL2bpThM7p6P9L45fgJf3MLdkWo,20997
7
7
  langroid/agent/chat_agent.py,sha256=X5uVMm9qdw3j-FRf4hbN8k8ByaSdtQCTuU8olKE0sbs,38750
@@ -32,7 +32,7 @@ langroid/agent/special/sql/utils/populate_metadata.py,sha256=x2OMKfmIBnJESBG3qKt
32
32
  langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GSLUYUFASZU8x-ybV67cB68,1885
33
33
  langroid/agent/special/sql/utils/tools.py,sha256=6uB2424SLtmapui9ggcEr0ZTiB6_dL1-JRGgN8RK9Js,1332
34
34
  langroid/agent/special/table_chat_agent.py,sha256=-Qtqr2FP8VcyYcA-Pzqa9ucSl1-nXudbNsv_qakSSco,9041
35
- langroid/agent/task.py,sha256=sFncTES0L_O2IpbnHRrNzad0HhtsbHoQ7j3Cjc9eTt0,49711
35
+ langroid/agent/task.py,sha256=YlWCNWG7vX_HoMvhqoQzoEghNGJK8Yskx4pbnnWDpcc,50408
36
36
  langroid/agent/tool_message.py,sha256=2kPsQUwi3ZzINTUNj10huKnZLjLp5SXmefacTHx8QDc,8304
37
37
  langroid/agent/tools/__init__.py,sha256=q-maq3k2BXhPAU99G0H6-j_ozoRvx15I1RFpPVicQIU,304
38
38
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=mLGhlgs6pwbYZIwrOs9shfh1dMBVT4DtkR29pYL3cCQ,1900
@@ -59,15 +59,15 @@ langroid/embedding_models/protoc/embeddings_pb2_grpc.py,sha256=9dYQqkW3JPyBpSEje
59
59
  langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-nGieNNAo1McHaY,5151
60
60
  langroid/language_models/__init__.py,sha256=5L9ndEEC8iLJHjDJmYFTnv6-2-3xsxWUMHcugR8IeDs,821
61
61
  langroid/language_models/azure_openai.py,sha256=ncRCbKooqLVOY-PWQUIo9C3yTuKEFbAwyngXT_M4P7k,5989
62
- langroid/language_models/base.py,sha256=Yy_6TP9Qj5CmNtDVQfbcyfytCsvGyow0e1OeqhWGY0A,20638
62
+ langroid/language_models/base.py,sha256=B6dX43ZR65mIvjD95W4RcfpT-WpmiuEcstR3eMrr56Y,21029
63
63
  langroid/language_models/config.py,sha256=5UF3DzO1a-Dfsc3vghE0XGq7g9t_xDsRCsuRiU4dgBg,366
64
64
  langroid/language_models/openai_assistants.py,sha256=9K-DEAL2aSWHeXj2hwCo2RAlK9_1oCPtqX2u1wISCj8,36
65
- langroid/language_models/openai_gpt.py,sha256=3W0gi7_Ja0c0vuT8SDv8ioOWXyUKs7zJORx8BV-QT2g,49672
65
+ langroid/language_models/openai_gpt.py,sha256=hR0ibmumIq1TnOGdb80SW0EN6YZn6DZazd5waFHKqGk,49357
66
66
  langroid/language_models/prompt_formatter/__init__.py,sha256=9JXFF22QNMmbQV1q4nrIeQVTtA3Tx8tEZABLtLBdFyc,352
67
67
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
68
68
  langroid/language_models/prompt_formatter/hf_formatter.py,sha256=TFL6ppmeQWnzr6CKQzRZFYY810zE1mr8DZnhw6i85ok,5217
69
69
  langroid/language_models/prompt_formatter/llama2_formatter.py,sha256=YdcO88qyBeuMENVIVvVqSYuEpvYSTndUe_jd6hVTko4,2899
70
- langroid/language_models/utils.py,sha256=u-VZImPsEKFc10yI-AfRhKkU9_RVCVqyQbwzR2Xc_WE,4758
70
+ langroid/language_models/utils.py,sha256=j8xEEm__-2b9eql1oTiWQk5dHW59UwmrRKs5kMHaGGo,4803
71
71
  langroid/mytypes.py,sha256=opL488mtHKob1uJeK_h1-kNjU5GZwkgCfXhBQCsONWU,2614
72
72
  langroid/parsing/__init__.py,sha256=2O5HOW8nDE3v-JInc5z2wIbFGejf4h5ZTdPqxsFtaWE,870
73
73
  langroid/parsing/agent_chats.py,sha256=sbZRV9ujdM5QXvvuHVjIi2ysYSYlap-uqfMMUKulrW0,1068
@@ -120,7 +120,7 @@ langroid/vector_store/meilisearch.py,sha256=d2huA9P-NoYRuAQ9ZeXJmMKr7ry8u90RUSR2
120
120
  langroid/vector_store/momento.py,sha256=9cui31TTrILid2KIzUpBkN2Ey3g_CZWOQVdaFsA4Ors,10045
121
121
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
122
122
  langroid/vector_store/qdrantdb.py,sha256=foKRxRv0BBony6S4Vt0Vav9Rn9HMxZvcIh1cE7nosFE,13524
123
- langroid-0.1.229.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
124
- langroid-0.1.229.dist-info/METADATA,sha256=2FXDkWjDrhYNvrCDCdshNanmBCAzW8N1t_4UUR9iNVI,47863
125
- langroid-0.1.229.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
126
- langroid-0.1.229.dist-info/RECORD,,
123
+ langroid-0.1.231.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
124
+ langroid-0.1.231.dist-info/METADATA,sha256=uomAdAunAEaqZaWqjmDyaOsL_WN_J4AYH06YNnFNREQ,47863
125
+ langroid-0.1.231.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
126
+ langroid-0.1.231.dist-info/RECORD,,