langroid 0.50.10__py3-none-any.whl → 0.50.12__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.
@@ -157,7 +157,7 @@ class AccessWarning(Warning):
157
157
  def gpt_3_5_warning() -> None:
158
158
  warnings.warn(
159
159
  f"""
160
- {OpenAIChatModel.GPT4o} is not available,
160
+ {OpenAIChatModel.GPT4o} is not available,
161
161
  falling back to {OpenAIChatModel.GPT3_5_TURBO}.
162
162
  Examples may not work properly and unexpected behavior may occur.
163
163
  Adjustments to prompts may be necessary.
@@ -435,8 +435,8 @@ class OpenAIGPT(LanguageModel):
435
435
  self.config.formatter = formatter
436
436
  logging.warning(
437
437
  f"""
438
- Using completions (not chat) endpoint with HuggingFace
439
- chat_template for {formatter} for
438
+ Using completions (not chat) endpoint with HuggingFace
439
+ chat_template for {formatter} for
440
440
  model {self.config.chat_model}
441
441
  """
442
442
  )
@@ -758,13 +758,8 @@ class OpenAIGPT(LanguageModel):
758
758
  return tmp
759
759
 
760
760
  def get_stream(self) -> bool:
761
- """Get streaming status. Note we disable streaming in quiet mode."""
762
- return (
763
- self.config.stream
764
- and settings.stream
765
- and self.info().allows_streaming
766
- and not settings.quiet
767
- )
761
+ """Get streaming status."""
762
+ return self.config.stream and settings.stream and self.info().allows_streaming
768
763
 
769
764
  @no_type_check
770
765
  def _process_stream_event(
@@ -813,6 +808,7 @@ class OpenAIGPT(LanguageModel):
813
808
  event_args = ""
814
809
  event_fn_name = ""
815
810
  event_tool_deltas: Optional[List[Dict[str, Any]]] = None
811
+ silent = settings.quiet
816
812
  # The first two events in the stream of Azure OpenAI is useless.
817
813
  # In the 1st: choices list is empty, in the 2nd: the dict delta has null content
818
814
  if chat:
@@ -852,25 +848,29 @@ class OpenAIGPT(LanguageModel):
852
848
 
853
849
  if event_text:
854
850
  completion += event_text
855
- sys.stdout.write(Colors().GREEN + event_text)
856
- sys.stdout.flush()
851
+ if not silent:
852
+ sys.stdout.write(Colors().GREEN + event_text)
853
+ sys.stdout.flush()
857
854
  self.config.streamer(event_text, StreamEventType.TEXT)
858
855
  if event_reasoning:
859
856
  reasoning += event_reasoning
860
- sys.stdout.write(Colors().GREEN_DIM + event_reasoning)
861
- sys.stdout.flush()
857
+ if not silent:
858
+ sys.stdout.write(Colors().GREEN_DIM + event_reasoning)
859
+ sys.stdout.flush()
862
860
  self.config.streamer(event_reasoning, StreamEventType.TEXT)
863
861
  if event_fn_name:
864
862
  function_name = event_fn_name
865
863
  has_function = True
866
- sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
867
- sys.stdout.flush()
864
+ if not silent:
865
+ sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
866
+ sys.stdout.flush()
868
867
  self.config.streamer(event_fn_name, StreamEventType.FUNC_NAME)
869
868
 
870
869
  if event_args:
871
870
  function_args += event_args
872
- sys.stdout.write(Colors().GREEN + event_args)
873
- sys.stdout.flush()
871
+ if not silent:
872
+ sys.stdout.write(Colors().GREEN + event_args)
873
+ sys.stdout.flush()
874
874
  self.config.streamer(event_args, StreamEventType.FUNC_ARGS)
875
875
 
876
876
  if event_tool_deltas is not None:
@@ -878,15 +878,17 @@ class OpenAIGPT(LanguageModel):
878
878
  for td in event_tool_deltas:
879
879
  if td["function"]["name"] is not None:
880
880
  tool_fn_name = td["function"]["name"]
881
- sys.stdout.write(
882
- Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
883
- )
884
- sys.stdout.flush()
881
+ if not silent:
882
+ sys.stdout.write(
883
+ Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
884
+ )
885
+ sys.stdout.flush()
885
886
  self.config.streamer(tool_fn_name, StreamEventType.TOOL_NAME)
886
887
  if td["function"]["arguments"] != "":
887
888
  tool_fn_args = td["function"]["arguments"]
888
- sys.stdout.write(Colors().GREEN + tool_fn_args)
889
- sys.stdout.flush()
889
+ if not silent:
890
+ sys.stdout.write(Colors().GREEN + tool_fn_args)
891
+ sys.stdout.flush()
890
892
  self.config.streamer(tool_fn_args, StreamEventType.TOOL_ARGS)
891
893
 
892
894
  # show this delta in the stream
@@ -953,7 +955,7 @@ class OpenAIGPT(LanguageModel):
953
955
  event_args = ""
954
956
  event_fn_name = ""
955
957
  event_tool_deltas: Optional[List[Dict[str, Any]]] = None
956
- silent = self.config.async_stream_quiet
958
+ silent = self.config.async_stream_quiet or settings.quiet
957
959
  # The first two events in the stream of Azure OpenAI is useless.
958
960
  # In the 1st: choices list is empty, in the 2nd: the dict delta has null content
959
961
  if chat:
@@ -980,46 +982,46 @@ class OpenAIGPT(LanguageModel):
980
982
  if not silent:
981
983
  sys.stdout.write(Colors().GREEN + event_text)
982
984
  sys.stdout.flush()
983
- await self.config.streamer_async(event_text, StreamEventType.TEXT)
985
+ await self.config.streamer_async(event_text, StreamEventType.TEXT)
984
986
  if event_reasoning:
985
987
  reasoning += event_reasoning
986
988
  if not silent:
987
989
  sys.stdout.write(Colors().GREEN + event_reasoning)
988
990
  sys.stdout.flush()
989
- await self.config.streamer_async(event_reasoning, StreamEventType.TEXT)
991
+ await self.config.streamer_async(event_reasoning, StreamEventType.TEXT)
990
992
  if event_fn_name:
991
993
  function_name = event_fn_name
992
994
  has_function = True
993
995
  if not silent:
994
996
  sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
995
997
  sys.stdout.flush()
996
- await self.config.streamer_async(
997
- event_fn_name, StreamEventType.FUNC_NAME
998
- )
998
+ await self.config.streamer_async(event_fn_name, StreamEventType.FUNC_NAME)
999
999
 
1000
1000
  if event_args:
1001
1001
  function_args += event_args
1002
1002
  if not silent:
1003
1003
  sys.stdout.write(Colors().GREEN + event_args)
1004
1004
  sys.stdout.flush()
1005
- await self.config.streamer_async(event_args, StreamEventType.FUNC_ARGS)
1005
+ await self.config.streamer_async(event_args, StreamEventType.FUNC_ARGS)
1006
1006
 
1007
- if event_tool_deltas is not None and not silent:
1007
+ if event_tool_deltas is not None:
1008
1008
  # print out streaming tool calls, if not async
1009
1009
  for td in event_tool_deltas:
1010
1010
  if td["function"]["name"] is not None:
1011
1011
  tool_fn_name = td["function"]["name"]
1012
- sys.stdout.write(
1013
- Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
1014
- )
1015
- sys.stdout.flush()
1012
+ if not silent:
1013
+ sys.stdout.write(
1014
+ Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
1015
+ )
1016
+ sys.stdout.flush()
1016
1017
  await self.config.streamer_async(
1017
1018
  tool_fn_name, StreamEventType.TOOL_NAME
1018
1019
  )
1019
1020
  if td["function"]["arguments"] != "":
1020
1021
  tool_fn_args = td["function"]["arguments"]
1021
- sys.stdout.write(Colors().GREEN + tool_fn_args)
1022
- sys.stdout.flush()
1022
+ if not silent:
1023
+ sys.stdout.write(Colors().GREEN + tool_fn_args)
1024
+ sys.stdout.flush()
1023
1025
  await self.config.streamer_async(
1024
1026
  tool_fn_args, StreamEventType.TOOL_ARGS
1025
1027
  )
@@ -1103,7 +1105,8 @@ class OpenAIGPT(LanguageModel):
1103
1105
  except Exception as e:
1104
1106
  logging.warning("Error while processing stream response: %s", str(e))
1105
1107
 
1106
- print("")
1108
+ if not settings.quiet:
1109
+ print("")
1107
1110
  # TODO- get usage info in stream mode (?)
1108
1111
 
1109
1112
  return self._create_stream_response(
@@ -1178,7 +1181,8 @@ class OpenAIGPT(LanguageModel):
1178
1181
  except Exception as e:
1179
1182
  logging.warning("Error while processing stream response: %s", str(e))
1180
1183
 
1181
- print("")
1184
+ if not settings.quiet:
1185
+ print("")
1182
1186
  # TODO- get usage info in stream mode (?)
1183
1187
 
1184
1188
  return self._create_stream_response(
@@ -1300,7 +1304,7 @@ class OpenAIGPT(LanguageModel):
1300
1304
  if not isinstance(dict_or_list, dict):
1301
1305
  raise ValueError(
1302
1306
  f"""
1303
- Invalid function args: {stripped_fn_args}
1307
+ Invalid function args: {stripped_fn_args}
1304
1308
  parsed as {dict_or_list},
1305
1309
  which is not a valid dict.
1306
1310
  """
@@ -1,16 +1,17 @@
1
- import copy
2
1
  import os
2
+ import threading
3
3
  from contextlib import contextmanager
4
- from typing import Iterator, List, Literal
4
+ from typing import Any, Dict, Iterator, List, Literal, cast
5
5
 
6
6
  from dotenv import find_dotenv, load_dotenv
7
7
 
8
8
  from langroid.pydantic_v1 import BaseSettings
9
9
 
10
+ # Global reentrant lock to serialize any modifications to the global settings.
11
+ _global_lock = threading.RLock()
12
+
10
13
 
11
14
  class Settings(BaseSettings):
12
- # NOTE all of these can be overridden in your .env file with upper-case names,
13
- # for example CACHE_TYPE=momento
14
15
  debug: bool = False # show debug messages?
15
16
  max_turns: int = -1 # maximum number of turns in a task (to avoid inf loop)
16
17
  progress: bool = False # show progress spinners/bars?
@@ -25,74 +26,112 @@ class Settings(BaseSettings):
25
26
  extra = "forbid"
26
27
 
27
28
 
28
- load_dotenv(find_dotenv(usecwd=True)) # get settings from .env file
29
- settings = Settings()
29
+ # Load environment variables from .env file.
30
+ load_dotenv(find_dotenv(usecwd=True))
31
+
32
+ # The global (default) settings instance.
33
+ # This is updated by update_global_settings() and set_global().
34
+ _global_settings = Settings()
35
+
36
+ # Thread-local storage for temporary (per-thread) settings overrides.
37
+ _thread_local = threading.local()
38
+
39
+
40
+ class SettingsProxy:
41
+ """
42
+ A proxy for the settings that returns a thread‐local override if set,
43
+ or else falls back to the global settings.
44
+ """
45
+
46
+ def __getattr__(self, name: str) -> Any:
47
+ # If the calling thread has set an override, use that.
48
+ if hasattr(_thread_local, "override"):
49
+ return getattr(_thread_local.override, name)
50
+ return getattr(_global_settings, name)
51
+
52
+ def __setattr__(self, name: str, value: Any) -> None:
53
+ # All writes go to the global settings.
54
+ setattr(_global_settings, name, value)
55
+
56
+ def update(self, new_settings: Settings) -> None:
57
+ _global_settings.__dict__.update(new_settings.__dict__)
58
+
59
+ def dict(self) -> Dict[str, Any]:
60
+ # Return a dict view of the settings as seen by the caller.
61
+ # Note that temporary overrides are not “merged” with global settings.
62
+ if hasattr(_thread_local, "override"):
63
+ return cast(Dict[str, Any], cast(Settings, _thread_local.override.dict()))
64
+ return _global_settings.dict()
65
+
66
+
67
+ settings = SettingsProxy()
30
68
 
31
69
 
32
70
  def update_global_settings(cfg: BaseSettings, keys: List[str]) -> None:
33
71
  """
34
- Update global settings so modules can access them via (as an example):
35
- ```
36
- from langroid.utils.configuration import settings
37
- if settings.debug...
38
- ```
39
- Caution we do not want to have too many such global settings!
40
- Args:
41
- cfg: pydantic config, typically from a main script
42
- keys: which keys from cfg to use, to update the global settings object
72
+ Update global settings so that modules can later access them via, e.g.,
73
+
74
+ from langroid.utils.configuration import settings
75
+ if settings.debug: ...
76
+
77
+ This updates the global default.
43
78
  """
44
79
  config_dict = cfg.dict()
45
-
46
- # Filter the config_dict based on the keys
47
80
  filtered_config = {key: config_dict[key] for key in keys if key in config_dict}
48
-
49
- # create a new Settings() object to let pydantic validate it
50
81
  new_settings = Settings(**filtered_config)
51
-
52
- # Update the unique global settings object
53
- settings.__dict__.update(new_settings.__dict__)
82
+ _global_settings.__dict__.update(new_settings.__dict__)
54
83
 
55
84
 
56
85
  def set_global(key_vals: Settings) -> None:
57
- """Update the unique global settings object"""
58
- settings.__dict__.update(key_vals.__dict__)
86
+ """
87
+ Update the global settings object.
88
+ """
89
+ _global_settings.__dict__.update(key_vals.__dict__)
59
90
 
60
91
 
61
92
  @contextmanager
62
93
  def temporary_settings(temp_settings: Settings) -> Iterator[None]:
63
- """Temporarily update the global settings and restore them afterward."""
64
- original_settings = copy.deepcopy(settings)
65
-
66
- set_global(temp_settings)
94
+ """
95
+ Temporarily override the settings for the calling thread.
67
96
 
97
+ Within the context, any access to "settings" will use the provided temporary
98
+ settings. Once the context is exited, the thread reverts to the global settings.
99
+ """
100
+ saved = getattr(_thread_local, "override", None)
101
+ _thread_local.override = temp_settings
68
102
  try:
69
103
  yield
70
104
  finally:
71
- settings.__dict__.update(original_settings.__dict__)
105
+ if saved is not None:
106
+ _thread_local.override = saved
107
+ else:
108
+ del _thread_local.override
72
109
 
73
110
 
74
111
  @contextmanager
75
112
  def quiet_mode(quiet: bool = True) -> Iterator[None]:
76
- """Temporarily set quiet=True in global settings and restore afterward."""
77
- original_settings = copy.deepcopy(settings)
78
- if quiet:
79
- temp_settings = original_settings.copy(update={"quiet": True})
80
- set_global(temp_settings)
81
-
82
- try:
113
+ """
114
+ Temporarily override settings.quiet for the current thread.
115
+ This implementation builds on the thread‑local temporary_settings context manager.
116
+ The effective quiet mode is merged:
117
+ if quiet is already True (from an outer context),
118
+ then it remains True even if a nested context passes quiet=False.
119
+ """
120
+ current_effective = settings.dict() # get the current thread's effective settings
121
+ # Create a new settings instance from the current effective state.
122
+ temp = Settings(**current_effective)
123
+ # Merge the new flag: once quiet is enabled, it stays enabled.
124
+ temp.quiet = settings.quiet or quiet
125
+ with temporary_settings(temp):
83
126
  yield
84
- finally:
85
- if quiet:
86
- settings.__dict__.update(original_settings.__dict__)
87
127
 
88
128
 
89
- def set_env(settings: BaseSettings) -> None:
129
+ def set_env(settings_instance: BaseSettings) -> None:
90
130
  """
91
- Set environment variables from a BaseSettings instance
92
- Args:
93
- settings (BaseSettings): desired settings
94
- Returns:
131
+ Set environment variables from a BaseSettings instance.
132
+
133
+ Each field in the settings is written to os.environ.
95
134
  """
96
- for field_name, field in settings.__class__.__fields__.items():
135
+ for field_name, field in settings_instance.__class__.__fields__.items():
97
136
  env_var_name = field.field_info.extra.get("env", field_name).upper()
98
- os.environ[env_var_name] = str(settings.dict()[field_name])
137
+ os.environ[env_var_name] = str(settings_instance.dict()[field_name])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.50.10
3
+ Version: 0.50.12
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -73,7 +73,7 @@ langroid/language_models/base.py,sha256=sCDC02hqIgjY73KnCvc-YGxZJm_LAs4Z1VVQpIFW
73
73
  langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
74
74
  langroid/language_models/mock_lm.py,sha256=5BgHKDVRWFbUwDT_PFgTZXz9-k8wJSA2e3PZmyDgQ1k,4022
75
75
  langroid/language_models/model_info.py,sha256=tfBBxL0iUf2mVN6CjcvqflzFUVg2oZqOJZexZ8jHTYA,12216
76
- langroid/language_models/openai_gpt.py,sha256=t5oFJFxfm0ZwbD5kS73TY8Hal6aCFUWX7O7dBZc-7fw,84480
76
+ langroid/language_models/openai_gpt.py,sha256=FG3eMWedko0kN-n-SkSbwnrm5hSxoW2wmJSBAvOAOYU,84731
77
77
  langroid/language_models/utils.py,sha256=hC5p61P_Qlrowkm5wMap1A1b5ZUCwK_XhPIzAQk1T1s,5483
78
78
  langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
79
79
  langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
@@ -104,7 +104,7 @@ langroid/prompts/templates.py,sha256=VV84HVf_amOx6xdWQyIsN9i5dNfrbl8rsfFp6hyfOss
104
104
  langroid/pydantic_v1/__init__.py,sha256=HxPGVERapVueRUORgSpj2JX_vTZxVlVbWvhpQlpjygE,283
105
105
  langroid/pydantic_v1/main.py,sha256=p_k7kDY9eDrsA5dxNNqXusKLgx7mS_icGnS7fu4goqY,147
106
106
  langroid/utils/__init__.py,sha256=Sruos2tB4G7Tn0vlblvYlX9PEGR0plI2uE0PJ4d_EC4,353
107
- langroid/utils/configuration.py,sha256=V3RS8OP7AC0_bDKczxfortD0F5H3cnsZL0ulKBxuoHU,3213
107
+ langroid/utils/configuration.py,sha256=FJVoseN3mkqx5vzpmtkzCLuQEYlWZEK_11YJ-Ge-L5o,4812
108
108
  langroid/utils/constants.py,sha256=CK09kda9bNDEhnwClq7ZTWZOh38guJlfcZ5hKUS1Ijo,1075
109
109
  langroid/utils/git_utils.py,sha256=WnflJ3R3owhlD0LNdSJakcKhExcEehE1UW5jYVQl8JY,7955
110
110
  langroid/utils/globals.py,sha256=Az9dOFqR6n9CoTYSqa2kLikQWS0oCQ9DFQIQAnG-2q8,1355
@@ -129,7 +129,7 @@ langroid/vector_store/pineconedb.py,sha256=otxXZNaBKb9f_H75HTaU3lMHiaR2NUp5MqwLZ
129
129
  langroid/vector_store/postgres.py,sha256=wHPtIi2qM4fhO4pMQr95pz1ZCe7dTb2hxl4VYspGZoA,16104
130
130
  langroid/vector_store/qdrantdb.py,sha256=O6dSBoDZ0jzfeVBd7LLvsXu083xs2fxXtPa9gGX3JX4,18443
131
131
  langroid/vector_store/weaviatedb.py,sha256=Yn8pg139gOy3zkaPfoTbMXEEBCiLiYa1MU5d_3UA1K4,11847
132
- langroid-0.50.10.dist-info/METADATA,sha256=XAXiPJuTaCrL_WyihMNOfVxm1BZxk_aAxxg_OCQSQqQ,63642
133
- langroid-0.50.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
134
- langroid-0.50.10.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
135
- langroid-0.50.10.dist-info/RECORD,,
132
+ langroid-0.50.12.dist-info/METADATA,sha256=b1vQBIkydfimg9r80ud7w07d7540XJAdhpegeqAPPTw,63642
133
+ langroid-0.50.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
134
+ langroid-0.50.12.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
135
+ langroid-0.50.12.dist-info/RECORD,,