langroid 0.27.4__py3-none-any.whl → 0.28.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.
langroid/agent/base.py CHANGED
@@ -282,17 +282,27 @@ class Agent(ABC):
282
282
  if not issubclass(message_class, ToolMessage):
283
283
  raise ValueError("message_class must be a subclass of ToolMessage")
284
284
  tool = message_class.default_value("request")
285
+
286
+ """
287
+ if tool has handler method explicitly defined - use it,
288
+ otherwise use the tool name as the handler
289
+ """
290
+ if hasattr(message_class, "_handler"):
291
+ handler = getattr(message_class, "_handler", tool)
292
+ else:
293
+ handler = tool
294
+
285
295
  self.llm_tools_map[tool] = message_class
286
296
  if (
287
297
  hasattr(message_class, "handle")
288
298
  and inspect.isfunction(message_class.handle)
289
- and not hasattr(self, tool)
299
+ and not hasattr(self, handler)
290
300
  ):
291
301
  """
292
302
  If the message class has a `handle` method,
293
- and agent does NOT have a method with the same name as the tool,
303
+ and agent does NOT have a tool handler method,
294
304
  then we create a method for the agent whose name
295
- is the value of `tool`, and whose body is the `handle` method.
305
+ is the value of `handler`, and whose body is the `handle` method.
296
306
  This removes a separate step of having to define this method
297
307
  for the agent, and also keeps the tool definition AND handling
298
308
  in one place, i.e. in the message class.
@@ -302,21 +312,23 @@ class Agent(ABC):
302
312
  len(inspect.signature(message_class.handle).parameters) > 1
303
313
  )
304
314
  if has_chat_doc_arg:
305
- setattr(self, tool, lambda obj, chat_doc: obj.handle(chat_doc))
315
+ setattr(self, handler, lambda obj, chat_doc: obj.handle(chat_doc))
306
316
  else:
307
- setattr(self, tool, lambda obj: obj.handle())
317
+ setattr(self, handler, lambda obj: obj.handle())
308
318
  elif (
309
319
  hasattr(message_class, "response")
310
320
  and inspect.isfunction(message_class.response)
311
- and not hasattr(self, tool)
321
+ and not hasattr(self, handler)
312
322
  ):
313
323
  has_chat_doc_arg = (
314
324
  len(inspect.signature(message_class.response).parameters) > 2
315
325
  )
316
326
  if has_chat_doc_arg:
317
- setattr(self, tool, lambda obj, chat_doc: obj.response(self, chat_doc))
327
+ setattr(
328
+ self, handler, lambda obj, chat_doc: obj.response(self, chat_doc)
329
+ )
318
330
  else:
319
- setattr(self, tool, lambda obj: obj.response(self))
331
+ setattr(self, handler, lambda obj: obj.response(self))
320
332
 
321
333
  if hasattr(message_class, "handle_message_fallback") and (
322
334
  inspect.isfunction(message_class.handle_message_fallback)
@@ -327,11 +339,11 @@ class Agent(ABC):
327
339
  lambda msg: message_class.handle_message_fallback(self, msg),
328
340
  )
329
341
 
330
- async_tool_name = f"{tool}_async"
342
+ async_handler_name = f"{handler}_async"
331
343
  if (
332
344
  hasattr(message_class, "handle_async")
333
345
  and inspect.isfunction(message_class.handle_async)
334
- and not hasattr(self, async_tool_name)
346
+ and not hasattr(self, async_handler_name)
335
347
  ):
336
348
  has_chat_doc_arg = (
337
349
  len(inspect.signature(message_class.handle_async).parameters) > 1
@@ -349,11 +361,11 @@ class Agent(ABC):
349
361
  async def handler(obj):
350
362
  return await obj.handle_async()
351
363
 
352
- setattr(self, async_tool_name, handler)
364
+ setattr(self, async_handler_name, handler)
353
365
  elif (
354
366
  hasattr(message_class, "response_async")
355
367
  and inspect.isfunction(message_class.response_async)
356
- and not hasattr(self, async_tool_name)
368
+ and not hasattr(self, async_handler_name)
357
369
  ):
358
370
  has_chat_doc_arg = (
359
371
  len(inspect.signature(message_class.response_async).parameters) > 2
@@ -371,7 +383,7 @@ class Agent(ABC):
371
383
  async def handler(obj):
372
384
  return await obj.response_async(self)
373
385
 
374
- setattr(self, async_tool_name, handler)
386
+ setattr(self, async_handler_name, handler)
375
387
 
376
388
  return [tool]
377
389
 
@@ -1742,7 +1754,11 @@ class Agent(ABC):
1742
1754
  Asynch version of `handle_tool_message`. See there for details.
1743
1755
  """
1744
1756
  tool_name = tool.default_value("request")
1745
- handler_method = getattr(self, tool_name + "_async", None)
1757
+ if hasattr(tool, "_handler"):
1758
+ handler_name = getattr(tool, "_handler", tool_name)
1759
+ else:
1760
+ handler_name = tool_name
1761
+ handler_method = getattr(self, handler_name + "_async", None)
1746
1762
  if handler_method is None:
1747
1763
  return self.handle_tool_message(tool, chat_doc=chat_doc)
1748
1764
  has_chat_doc_arg = (
@@ -1781,7 +1797,11 @@ class Agent(ABC):
1781
1797
 
1782
1798
  """
1783
1799
  tool_name = tool.default_value("request")
1784
- handler_method = getattr(self, tool_name, None)
1800
+ if hasattr(tool, "_handler"):
1801
+ handler_name = getattr(tool, "_handler", tool_name)
1802
+ else:
1803
+ handler_name = tool_name
1804
+ handler_method = getattr(self, handler_name, None)
1785
1805
  if handler_method is None:
1786
1806
  return None
1787
1807
  has_chat_doc_arg = (
@@ -105,6 +105,7 @@ class GeminiModel(str, Enum):
105
105
  GEMINI_1_5_FLASH = "gemini/gemini-1.5-flash"
106
106
  GEMINI_1_5_FLASH_8B = "gemini/gemini-1.5-flash-8b"
107
107
  GEMINI_1_5_PRO = "gemini/gemini-1.5-pro"
108
+ GEMINI_2_FLASH = "gemini/gemini-2.0-flash-exp"
108
109
 
109
110
 
110
111
  class OpenAICompletionModel(str, Enum):
@@ -443,6 +444,7 @@ class OpenAIGPT(LanguageModel):
443
444
  config = config.copy()
444
445
  super().__init__(config)
445
446
  self.config: OpenAIGPTConfig = config
447
+ self.chat_model_orig = self.config.chat_model
446
448
 
447
449
  # Run the first time the model is used
448
450
  self.run_on_first_use = cache(self.config.run_on_first_use)
@@ -451,6 +453,7 @@ class OpenAIGPT(LanguageModel):
451
453
  # to allow quick testing with other models
452
454
  if settings.chat_model != "":
453
455
  self.config.chat_model = settings.chat_model
456
+ self.chat_model_orig = settings.chat_model
454
457
  self.config.completion_model = settings.chat_model
455
458
 
456
459
  if len(parts := self.config.chat_model.split("//")) > 1:
@@ -565,7 +568,7 @@ class OpenAIGPT(LanguageModel):
565
568
 
566
569
  self.is_groq = self.config.chat_model.startswith("groq/")
567
570
  self.is_cerebras = self.config.chat_model.startswith("cerebras/")
568
- self.is_gemini = self.config.chat_model.startswith("gemini/")
571
+ self.is_gemini = self.is_gemini_model()
569
572
  self.is_glhf = self.config.chat_model.startswith("glhf/")
570
573
  self.is_openrouter = self.config.chat_model.startswith("openrouter/")
571
574
 
@@ -690,6 +693,20 @@ class OpenAIGPT(LanguageModel):
690
693
  openai_completion_models = [e.value for e in OpenAICompletionModel]
691
694
  return self.config.completion_model in openai_completion_models
692
695
 
696
+ def is_gemini_model(self) -> bool:
697
+ gemini_models = [e.value for e in GeminiModel]
698
+ return self.chat_model_orig in gemini_models or self.chat_model_orig.startswith(
699
+ "gemini/"
700
+ )
701
+
702
+ def requires_first_user_message(self) -> bool:
703
+ """
704
+ Does the chat_model require a non-empty first user message?
705
+ TODO: Add other models here; we know gemini requires a non-empty
706
+ user message, after the system message.
707
+ """
708
+ return self.is_gemini_model()
709
+
693
710
  def unsupported_params(self) -> List[str]:
694
711
  """
695
712
  List of params that are not supported by the current model
@@ -1663,6 +1680,20 @@ class OpenAIGPT(LanguageModel):
1663
1680
  ]
1664
1681
  else:
1665
1682
  llm_messages = messages
1683
+ if (
1684
+ len(llm_messages) == 1
1685
+ and llm_messages[0].role == Role.SYSTEM
1686
+ and self.requires_first_user_message()
1687
+ ):
1688
+ # some LLMs, notable Gemini as of 12/11/24,
1689
+ # require the first message to be from the user,
1690
+ # so insert a dummy user msg if needed.
1691
+ llm_messages.insert(
1692
+ 1,
1693
+ LLMMessage(
1694
+ role=Role.USER, content="Follow the above instructions."
1695
+ ),
1696
+ )
1666
1697
 
1667
1698
  # Azure uses different parameters. It uses ``engine`` instead of ``model``
1668
1699
  # and the value should be the deployment_name not ``self.config.chat_model``
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.27.4
3
+ Version: 0.28.1
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -83,8 +83,9 @@ Requires-Dist: pygments (>=2.15.1,<3.0.0)
83
83
  Requires-Dist: pymupdf (>=1.23.3,<2.0.0) ; extra == "doc-chat" or extra == "all" or extra == "pdf-parsers"
84
84
  Requires-Dist: pymysql (>=1.1.0,<2.0.0) ; extra == "db" or extra == "all" or extra == "mysql" or extra == "sql"
85
85
  Requires-Dist: pyparsing (>=3.0.9,<4.0.0)
86
- Requires-Dist: pypdf (>=3.12.2,<4.0.0) ; extra == "doc-chat" or extra == "all" or extra == "pdf-parsers"
86
+ Requires-Dist: pypdf (>=5.1.0) ; extra == "doc-chat" or extra == "all" or extra == "pdf-parsers"
87
87
  Requires-Dist: pytesseract (>=0.3.10,<0.4.0) ; extra == "doc-chat" or extra == "all" or extra == "pdf-parsers"
88
+ Requires-Dist: pytest-rerunfailures (>=15.0,<16.0)
88
89
  Requires-Dist: python-arango (>=8.1.2,<9.0.0) ; extra == "all" or extra == "arango"
89
90
  Requires-Dist: python-docx (>=1.1.0,<2.0.0) ; extra == "doc-chat" or extra == "all" or extra == "docx"
90
91
  Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
@@ -1,6 +1,6 @@
1
1
  langroid/__init__.py,sha256=z_fCOLQJPOw3LLRPBlFB5-2HyCjpPgQa4m4iY5Fvb8Y,1800
2
2
  langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
3
- langroid/agent/base.py,sha256=UIsiM8SQjDVShOyaFJZbPkBStM5bMexFfyd0M1j4u_w,76866
3
+ langroid/agent/base.py,sha256=ZgWsRBC9rugcWp9aZLAmFFteU47pqKIEoTy_dgkYtBI,77529
4
4
  langroid/agent/batch.py,sha256=qK3ph6VNj_1sOhfXCZY4r6gh035DglDKU751p8BU0tY,14665
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  langroid/agent/callbacks/chainlit.py,sha256=C6zzzYC30qC4eMA7al7eFpRoTgoe3475kaMKyXgQM0Q,20695
@@ -75,7 +75,7 @@ langroid/language_models/azure_openai.py,sha256=DdWq_pRFI9PT5LwUy9TXDox90TY-mkP5
75
75
  langroid/language_models/base.py,sha256=6hXR-bclyPif-BvFbyXevP-gEwiawQAJHX3N1AKNei0,23786
76
76
  langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
77
77
  langroid/language_models/mock_lm.py,sha256=5BgHKDVRWFbUwDT_PFgTZXz9-k8wJSA2e3PZmyDgQ1k,4022
78
- langroid/language_models/openai_gpt.py,sha256=jejB-LbMFMqzw2GMOLBVl_FdP6vwYbFj7g4nZqxQdOI,73970
78
+ langroid/language_models/openai_gpt.py,sha256=IPZ5bOyfQO-pWHharQ5fMsBuW87Cpwfy1Ywce6ARbfQ,75204
79
79
  langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
80
80
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
81
81
  langroid/language_models/prompt_formatter/hf_formatter.py,sha256=PVJppmjRvD-2DF-XNC6mE05vTZ9wbu37SmXwZBQhad0,5055
@@ -142,8 +142,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
142
142
  langroid/vector_store/momento.py,sha256=UNHGT6jXuQtqY9f6MdqGU14bVnS0zHgIJUa30ULpUJo,10474
143
143
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
144
144
  langroid/vector_store/qdrantdb.py,sha256=v7mCsijc2GdRJyil-yFaUVAX4SX5D75mD3vzlpjCMuo,17393
145
- pyproject.toml,sha256=GVys_bKiJSCE7MbEyywTx7ppSormv_BXLe0VcAA2EwU,7501
146
- langroid-0.27.4.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
147
- langroid-0.27.4.dist-info/METADATA,sha256=shORd7_WDwpblucBYtuKwvyNFa4vVRBHosTuY7Xiqlw,57526
148
- langroid-0.27.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
149
- langroid-0.27.4.dist-info/RECORD,,
145
+ pyproject.toml,sha256=YO18NE-r-Ark_n-cjgvflJWmA3uqXXo2Ee5dQzT_Qw8,7532
146
+ langroid-0.28.1.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
147
+ langroid-0.28.1.dist-info/METADATA,sha256=IExM86cC-ymkK90BCMiULmuodg9E7amGcyi0g3aN_j0,57569
148
+ langroid-0.28.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
149
+ langroid-0.28.1.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.27.4"
3
+ version = "0.28.1"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"
@@ -29,7 +29,7 @@ huggingface-hub = {version="^0.21.2", optional=true}
29
29
  transformers = {version="^4.40.1", optional=true}
30
30
  lancedb = {version="^0.8.2", optional=true}
31
31
  tantivy = {version="^0.21.0", optional=true}
32
- pypdf = {version="^3.12.2", optional=true}
32
+ pypdf = {version=">=5.1.0", optional=true}
33
33
  pymupdf = {version="^1.23.3", optional=true}
34
34
  pdf2image = {version="^1.17.0", optional=true}
35
35
  pytesseract = {version="^0.3.10", optional=true}
@@ -93,6 +93,7 @@ gitpython = "^3.1.43"
93
93
  python-arango = {version="^8.1.2", optional=true}
94
94
  arango-datasets = {version="^1.2.2", optional=true}
95
95
  adb-cloud-connector = "^1.0.2"
96
+ pytest-rerunfailures = "^15.0"
96
97
 
97
98
 
98
99
  [tool.poetry.extras]