langroid 0.48.3__py3-none-any.whl → 0.49.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.
@@ -227,6 +227,16 @@ class OpenAICallParams(BaseModel):
227
227
  return {k: v for k, v in self.dict().items() if v is not None}
228
228
 
229
229
 
230
+ class LiteLLMProxyConfig(BaseSettings):
231
+ """Configuration for LiteLLM proxy connection."""
232
+
233
+ api_key: str = "" # read from env var LITELLM_API_KEY if set
234
+ api_base: str = "" # read from env var LITELLM_API_BASE if set
235
+
236
+ class Config:
237
+ env_prefix = "LITELLM_"
238
+
239
+
230
240
  class OpenAIGPTConfig(LLMConfig):
231
241
  """
232
242
  Class for any LLM with an OpenAI-like API: besides the OpenAI models this includes:
@@ -250,6 +260,7 @@ class OpenAIGPTConfig(LLMConfig):
250
260
  organization: str = ""
251
261
  api_base: str | None = None # used for local or other non-OpenAI models
252
262
  litellm: bool = False # use litellm api?
263
+ litellm_proxy: LiteLLMProxyConfig = LiteLLMProxyConfig()
253
264
  ollama: bool = False # use ollama's OpenAI-compatible endpoint?
254
265
  min_output_tokens: int = 1
255
266
  use_chat_for_completion = True # do not change this, for OpenAI models!
@@ -520,6 +531,7 @@ class OpenAIGPT(LanguageModel):
520
531
  self.is_glhf = self.config.chat_model.startswith("glhf/")
521
532
  self.is_openrouter = self.config.chat_model.startswith("openrouter/")
522
533
  self.is_langdb = self.config.chat_model.startswith("langdb/")
534
+ self.is_litellm_proxy = self.config.chat_model.startswith("litellm-proxy/")
523
535
 
524
536
  if self.is_groq:
525
537
  # use groq-specific client
@@ -546,7 +558,14 @@ class OpenAIGPT(LanguageModel):
546
558
  )
547
559
  else:
548
560
  # in these cases, there's no specific client: OpenAI python client suffices
549
- if self.is_gemini:
561
+ if self.is_litellm_proxy:
562
+ self.config.chat_model = self.config.chat_model.replace(
563
+ "litellm-proxy/", ""
564
+ )
565
+ if self.api_key == OPENAI_API_KEY:
566
+ self.api_key = self.config.litellm_proxy.api_key or self.api_key
567
+ self.api_base = self.config.litellm_proxy.api_base or self.api_base
568
+ elif self.is_gemini:
550
569
  self.config.chat_model = self.config.chat_model.replace("gemini/", "")
551
570
  if self.api_key == OPENAI_API_KEY:
552
571
  self.api_key = os.getenv("GEMINI_API_KEY", DUMMY_API_KEY)
@@ -1040,8 +1059,8 @@ class OpenAIGPT(LanguageModel):
1040
1059
  )
1041
1060
  if is_break:
1042
1061
  break
1043
- except Exception:
1044
- pass
1062
+ except Exception as e:
1063
+ logging.warning("Error while processing stream response: %s", str(e))
1045
1064
 
1046
1065
  print("")
1047
1066
  # TODO- get usage info in stream mode (?)
@@ -1102,8 +1121,8 @@ class OpenAIGPT(LanguageModel):
1102
1121
  )
1103
1122
  if is_break:
1104
1123
  break
1105
- except Exception:
1106
- pass
1124
+ except Exception as e:
1125
+ logging.warning("Error while processing stream response: %s", str(e))
1107
1126
 
1108
1127
  print("")
1109
1128
  # TODO- get usage info in stream mode (?)
@@ -1684,11 +1703,32 @@ class OpenAIGPT(LanguageModel):
1684
1703
  if self.config.litellm and settings.debug:
1685
1704
  kwargs["logger_fn"] = litellm_logging_fn
1686
1705
  result = completion_call(**kwargs)
1687
- if not self.get_stream():
1688
- # if streaming, cannot cache result
1706
+
1707
+ if self.get_stream():
1708
+ # If streaming, cannot cache result
1689
1709
  # since it is a generator. Instead,
1690
1710
  # we hold on to the hashed_key and
1691
1711
  # cache the result later
1712
+
1713
+ # Test if this is a stream with an exception by
1714
+ # trying to get first chunk: Some providers like LiteLLM
1715
+ # produce a valid stream object `result` instead of throwing a
1716
+ # rate-limit error, and if we don't catch it here,
1717
+ # we end up returning an empty response and not
1718
+ # using the retry mechanism in the decorator.
1719
+ try:
1720
+ # try to get the first chunk to check for errors
1721
+ test_iter = iter(result)
1722
+ first_chunk = next(test_iter)
1723
+ # If we get here without error, recreate the stream
1724
+ result = chain([first_chunk], test_iter)
1725
+ except StopIteration:
1726
+ # Empty stream is fine
1727
+ pass
1728
+ except Exception as e:
1729
+ # Propagate any errors in the stream
1730
+ raise e
1731
+ else:
1692
1732
  self._cache_store(hashed_key, result.model_dump())
1693
1733
  return cached, hashed_key, result
1694
1734
 
@@ -1715,7 +1755,35 @@ class OpenAIGPT(LanguageModel):
1715
1755
  kwargs["logger_fn"] = litellm_logging_fn
1716
1756
  # If it's not in the cache, call the API
1717
1757
  result = await acompletion_call(**kwargs)
1718
- if not self.get_stream():
1758
+ if self.get_stream():
1759
+ try:
1760
+ # Try to peek at the first chunk to immediately catch any errors
1761
+ # Store the original result (the stream)
1762
+ original_stream = result
1763
+
1764
+ # Manually create and advance the iterator to check for errors
1765
+ stream_iter = original_stream.__aiter__()
1766
+ try:
1767
+ # This will raise an exception if the stream is invalid
1768
+ first_chunk = await anext(stream_iter)
1769
+
1770
+ # If we reach here, the stream started successfully
1771
+ # Now recreate a fresh stream from the original API result
1772
+ # Otherwise, return a new stream that yields the first chunk
1773
+ # and remaining items
1774
+ async def combined_stream(): # type: ignore
1775
+ yield first_chunk
1776
+ async for chunk in stream_iter:
1777
+ yield chunk
1778
+
1779
+ result = combined_stream() # type: ignore
1780
+ except StopAsyncIteration:
1781
+ # Empty stream is normal - nothing to do
1782
+ pass
1783
+ except Exception as e:
1784
+ # Any exception here should be raised to trigger the retry mechanism
1785
+ raise e
1786
+ else:
1719
1787
  self._cache_store(hashed_key, result.model_dump())
1720
1788
  return cached, hashed_key, result
1721
1789
 
langroid/mytypes.py CHANGED
@@ -65,6 +65,30 @@ class DocMetaData(BaseModel):
65
65
 
66
66
  return original_dict
67
67
 
68
+ def __str__(self) -> str:
69
+ title_str = (
70
+ ""
71
+ if "unknown" in self.title.lower() or self.title.strip() == ""
72
+ else f"Title: {self.title}"
73
+ )
74
+ date_str = ""
75
+ if (
76
+ "unknown" not in self.published_date.lower()
77
+ and self.published_date.strip() != ""
78
+ ):
79
+ try:
80
+ from dateutil import parser
81
+
82
+ # Try to parse the date string
83
+ date_obj = parser.parse(self.published_date)
84
+ # Format to include only the date part (year-month-day)
85
+ date_only = date_obj.strftime("%Y-%m-%d")
86
+ date_str = f"Date: {date_only}"
87
+ except (ValueError, ImportError, TypeError):
88
+ # If parsing fails, just use the original date
89
+ date_str = f"Date: {self.published_date}"
90
+ return f"{self.source} {title_str} {date_str}".strip()
91
+
68
92
  class Config:
69
93
  extra = Extra.allow
70
94
 
@@ -93,7 +117,7 @@ class Document(BaseModel):
93
117
  return dedent(
94
118
  f"""
95
119
  CONTENT: {self.content}
96
- SOURCE:{self.metadata.source}
120
+ SOURCE:{str(self.metadata)}
97
121
  """
98
122
  )
99
123
 
@@ -97,7 +97,7 @@ def format_cited_references(
97
97
  # source and content for each citation
98
98
  full_citations_str = "\n".join(
99
99
  [
100
- f"[^{c}] {passages[c-1].metadata.source}"
100
+ f"[^{c}] {str(passages[c-1].metadata)}"
101
101
  f"\n{format_footnote_text(passages[c-1].content)}"
102
102
  for c in good_citations
103
103
  ]
@@ -105,6 +105,6 @@ def format_cited_references(
105
105
 
106
106
  # source for each citation
107
107
  citations_str = "\n".join(
108
- [f"[^{c}] {passages[c-1].metadata.source}" for c in good_citations]
108
+ [f"[^{c}] {str(passages[c-1].metadata)}" for c in good_citations]
109
109
  )
110
110
  return full_citations_str, citations_str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.48.3
3
+ Version: 0.49.1
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  langroid/__init__.py,sha256=z_fCOLQJPOw3LLRPBlFB5-2HyCjpPgQa4m4iY5Fvb8Y,1800
2
2
  langroid/exceptions.py,sha256=OPjece_8cwg94DLPcOGA1ddzy5bGh65pxzcHMnssTz8,2995
3
- langroid/mytypes.py,sha256=1mt21xiczAu1WgfZIptBKN9WpqYgpVeo8cAXvs6drKA,2986
3
+ langroid/mytypes.py,sha256=ezj_6FFDkJZiVx1SS9eJvh23dH76Ti7mJbePi8ldkAI,3919
4
4
  langroid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
6
6
  langroid/agent/base.py,sha256=U-UjdpxIFqkzRIB5-LYwHrhMSNI3sDbfnNRqIhrtsyI,79568
@@ -72,7 +72,7 @@ langroid/language_models/base.py,sha256=mDYmFCBCLdq8_Uvws4MiewwEgcOCP8Qb0e5yUXr3
72
72
  langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
73
73
  langroid/language_models/mock_lm.py,sha256=5BgHKDVRWFbUwDT_PFgTZXz9-k8wJSA2e3PZmyDgQ1k,4022
74
74
  langroid/language_models/model_info.py,sha256=tfBBxL0iUf2mVN6CjcvqflzFUVg2oZqOJZexZ8jHTYA,12216
75
- langroid/language_models/openai_gpt.py,sha256=JJXLw7iuQFbW0CkcKiYjX4agRllRhDaGT9Q8OCdEK-8,79559
75
+ langroid/language_models/openai_gpt.py,sha256=M_jp97Ksp5r3U-d0jCLPLjVmn7IK1mC8Ry4t7k6A5tc,82906
76
76
  langroid/language_models/utils.py,sha256=L4_CbihDMTGcsg0TOG1Yd5JFEto46--h7CX_14m89sQ,5016
77
77
  langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
78
78
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
@@ -115,7 +115,7 @@ langroid/utils/types.py,sha256=-BvyIf_LmAJ5jR9NC7S4CSVNEr3XayAaxJ5o0TiIej0,2992
115
115
  langroid/utils/algorithms/__init__.py,sha256=WylYoZymA0fnzpB4vrsH_0n7WsoLhmuZq8qxsOCjUpM,41
116
116
  langroid/utils/algorithms/graph.py,sha256=JbdpPnUOhw4-D6O7ou101JLA3xPCD0Lr3qaPoFCaRfo,2866
117
117
  langroid/utils/output/__init__.py,sha256=7P0f--4IZneNsTxXY5fd6d6iW-CeVe-KSsl-87sbBPc,340
118
- langroid/utils/output/citations.py,sha256=5mhg2-kpBF7qgV82gJbIiUHfoJcpJiK1sAWdDF0o0Wc,3866
118
+ langroid/utils/output/citations.py,sha256=cEiqSH7DJ5q4M2z_6eFjCj9Ohnf68i6sivjeRFuFAtk,3862
119
119
  langroid/utils/output/printing.py,sha256=yzPJZN-8_jyOJmI9N_oLwEDfjMwVgk3IDiwnZ4eK_AE,2962
120
120
  langroid/utils/output/status.py,sha256=rzbE7mDJcgNNvdtylCseQcPGCGghtJvVq3lB-OPJ49E,1049
121
121
  langroid/vector_store/__init__.py,sha256=8ktJUVsVUoc7FMmkUFpFBZu7VMWUqQY9zpm4kEJ8yTs,1537
@@ -127,7 +127,7 @@ langroid/vector_store/pineconedb.py,sha256=otxXZNaBKb9f_H75HTaU3lMHiaR2NUp5MqwLZ
127
127
  langroid/vector_store/postgres.py,sha256=wHPtIi2qM4fhO4pMQr95pz1ZCe7dTb2hxl4VYspGZoA,16104
128
128
  langroid/vector_store/qdrantdb.py,sha256=O6dSBoDZ0jzfeVBd7LLvsXu083xs2fxXtPa9gGX3JX4,18443
129
129
  langroid/vector_store/weaviatedb.py,sha256=Yn8pg139gOy3zkaPfoTbMXEEBCiLiYa1MU5d_3UA1K4,11847
130
- langroid-0.48.3.dist-info/METADATA,sha256=Dhbb_Of_lBJcDtv1ezx1zjTkuW5jFi2NegQ4LA_fRkU,63606
131
- langroid-0.48.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
- langroid-0.48.3.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
133
- langroid-0.48.3.dist-info/RECORD,,
130
+ langroid-0.49.1.dist-info/METADATA,sha256=a2cArSN5YfRq4GRH37MkO6h-fvXbXEFkoo-qDMyVTzA,63606
131
+ langroid-0.49.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
+ langroid-0.49.1.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
133
+ langroid-0.49.1.dist-info/RECORD,,