langroid 0.53.10__py3-none-any.whl → 0.53.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.
langroid/agent/task.py CHANGED
@@ -578,19 +578,24 @@ class Task:
578
578
  return self.pending_message
579
579
 
580
580
  def init_loggers(self) -> None:
581
+ """Initialise per-task Rich and TSV loggers."""
582
+ from langroid.utils.logging import RichFileLogger
583
+
581
584
  if self.caller is not None and self.caller.logger is not None:
582
585
  self.logger = self.caller.logger
583
586
  elif self.logger is None:
584
587
  self.logger = RichFileLogger(
585
588
  str(Path(self.config.logs_dir) / f"{self.name}.log"),
589
+ append=True,
586
590
  color=self.color_log,
587
591
  )
588
592
 
589
593
  if self.caller is not None and self.caller.tsv_logger is not None:
590
594
  self.tsv_logger = self.caller.tsv_logger
591
595
  elif self.tsv_logger is None:
596
+ # unique logger name ensures a distinct `logging.Logger` object
592
597
  self.tsv_logger = setup_file_logger(
593
- "tsv_logger",
598
+ f"tsv_logger.{self.name}.{id(self)}",
594
599
  str(Path(self.config.logs_dir) / f"{self.name}.tsv"),
595
600
  )
596
601
  header = ChatDocLoggerFields().tsv_header()
@@ -521,7 +521,7 @@ class GeminiEmbeddings(EmbeddingModel):
521
521
  for batch in batched(texts, self.config.batch_size):
522
522
  result = self.client.models.embed_content( # type: ignore[attr-defined]
523
523
  model=self.config.model_name,
524
- contents=batch,
524
+ contents=batch, # type: ignore
525
525
  )
526
526
 
527
527
  if not hasattr(result, "embeddings") or not isinstance(
@@ -532,7 +532,9 @@ class GeminiEmbeddings(EmbeddingModel):
532
532
  )
533
533
 
534
534
  # Extract .values from ContentEmbedding objects
535
- all_embeddings.extend([emb.values for emb in result.embeddings])
535
+ all_embeddings.extend(
536
+ [emb.values for emb in result.embeddings] # type: ignore
537
+ )
536
538
 
537
539
  return all_embeddings
538
540
 
langroid/utils/logging.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import logging
2
+ import os
2
3
  import os.path
3
- from typing import no_type_check
4
+ import threading
5
+ from typing import ClassVar, Dict, no_type_check
4
6
 
5
7
  import colorlog
6
8
  from rich.console import Console
@@ -114,22 +116,86 @@ def setup_loggers_for_package(package_name: str, level: int) -> None:
114
116
 
115
117
 
116
118
  class RichFileLogger:
117
- def __init__(self, log_file: str, append: bool = False, color: bool = True):
118
- os.makedirs(os.path.dirname(log_file), exist_ok=True)
119
- self.log_file = log_file
120
- if not append:
121
- if os.path.exists(self.log_file):
122
- os.remove(self.log_file)
123
- self.file = None
124
- self.console = None
125
- self.append = append
126
- self.color = color
119
+ """Singleton-per-path, ref-counted, thread-safe file logger.
127
120
 
121
+ • Any number of calls to `RichFileLogger(path)` yield the same object.
122
+ • A per-instance lock guarantees that the underlying file is opened only
123
+ once, even when many threads construct the logger concurrently.
124
+ • A reference counter tracks how many parts of the program are using the
125
+ logger; the FD is closed only when the counter reaches zero.
126
+ • All writes are serialised with a dedicated write-lock.
127
+ """
128
+
129
+ _instances: ClassVar[Dict[str, "RichFileLogger"]] = {}
130
+ _ref_counts: ClassVar[Dict[str, int]] = {}
131
+ # guards _instances & _ref_counts
132
+ _class_lock: ClassVar[threading.Lock] = threading.Lock()
133
+
134
+ # ------------------------------------------------------------------ #
135
+ # construction / destruction
136
+ # ------------------------------------------------------------------ #
137
+ def __new__(
138
+ cls, log_file: str, append: bool = False, color: bool = True
139
+ ) -> "RichFileLogger":
140
+ with cls._class_lock:
141
+ if log_file in cls._instances:
142
+ cls._ref_counts[log_file] += 1
143
+ return cls._instances[log_file]
144
+
145
+ inst = super().__new__(cls)
146
+ # create the per-instance init-lock *before* releasing class-lock
147
+ inst._init_lock = threading.Lock()
148
+ cls._instances[log_file] = inst
149
+ cls._ref_counts[log_file] = 1
150
+ return inst
151
+
152
+ def __init__(self, log_file: str, append: bool = False, color: bool = True) -> None:
153
+ # Double-checked locking: perform heavy init exactly once.
154
+ if getattr(self, "_init_done", False):
155
+ return
156
+
157
+ if not hasattr(self, "_init_lock"):
158
+ self._init_lock: threading.Lock = threading.Lock()
159
+
160
+ with self._init_lock:
161
+ if getattr(self, "_init_done", False):
162
+ return
163
+
164
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
165
+ mode = "a" if append else "w"
166
+ self.file = open(log_file, mode, buffering=1, encoding="utf-8")
167
+ self.log_file: str = log_file
168
+ self.color: bool = color
169
+ self.console: Console | None = (
170
+ Console(file=self.file, force_terminal=True, width=200)
171
+ if color
172
+ else None
173
+ )
174
+ self._write_lock = threading.Lock()
175
+ self._init_done = True # set last
176
+
177
+ # ------------------------------------------------------------------ #
178
+ # public API
179
+ # ------------------------------------------------------------------ #
128
180
  @no_type_check
129
181
  def log(self, message: str) -> None:
130
- with open(self.log_file, "a") as f:
131
- if self.color:
132
- console = Console(file=f, force_terminal=True, width=200)
133
- console.print(escape(message))
182
+ """Thread-safe write to the log file."""
183
+ with self._write_lock:
184
+ if self.color and self.console is not None:
185
+ self.console.print(escape(message))
186
+ else:
187
+ print(message, file=self.file)
188
+ self.file.flush()
189
+
190
+ def close(self) -> None:
191
+ """Decrease ref-count; close FD only when last user is done."""
192
+ with self._class_lock:
193
+ count = self._ref_counts.get(self.log_file, 0) - 1
194
+ if count <= 0:
195
+ self._ref_counts.pop(self.log_file, None)
196
+ self._instances.pop(self.log_file, None)
197
+ with self._write_lock:
198
+ if not self.file.closed:
199
+ self.file.close()
134
200
  else:
135
- print(message, file=f)
201
+ self._ref_counts[self.log_file] = count
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.53.10
3
+ Version: 0.53.12
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,7 @@ langroid/agent/batch.py,sha256=vi1r5i1-vN80WfqHDSwjEym_KfGsqPGUtwktmiK1nuk,20635
8
8
  langroid/agent/chat_agent.py,sha256=2HIYzYxkrGkRIS97ioKfIqjaW3RbX89M39LjzBobBEY,88381
9
9
  langroid/agent/chat_document.py,sha256=6O20Fp4QrquykaF2jFtwNHkvcoDte1LLwVZNk9mVH9c,18057
10
10
  langroid/agent/openai_assistant.py,sha256=JkAcs02bIrgPNVvUWVR06VCthc5-ulla2QMBzux_q6o,34340
11
- langroid/agent/task.py,sha256=HB6N-Jn80HFqCf0ZYOC1v3Bn3oO7NLjShHQJJFwW0q4,90557
11
+ langroid/agent/task.py,sha256=dJ2nRKc9-ulZyzNaABMKb9PKJzWRdZTonH9TPswpdbc,90801
12
12
  langroid/agent/tool_message.py,sha256=BhjP-_TfQ2tgxuY4Yo_JHLOwwt0mJ4BwjPnREvEY4vk,14744
13
13
  langroid/agent/xml_tool_message.py,sha256=oeBKnJNoGaKdtz39XoWGMTNlVyXew2MWH5lgtYeh8wQ,15496
14
14
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -62,7 +62,7 @@ langroid/cachedb/base.py,sha256=ztVjB1DtN6pLCujCWnR6xruHxwVj3XkYniRTYAKKqk0,1354
62
62
  langroid/cachedb/redis_cachedb.py,sha256=7kgnbf4b5CKsCrlL97mHWKvdvlLt8zgn7lc528jEpiE,5141
63
63
  langroid/embedding_models/__init__.py,sha256=KyYxR3jDFUCfYjSuCL86qjAmrq6mXXjOT4lFNOKVj6Y,955
64
64
  langroid/embedding_models/base.py,sha256=Ml7oA6PzQm0wZmIYn3fhF7dvZCi-amviWUwOeBegH3A,2562
65
- langroid/embedding_models/models.py,sha256=iGRrQR7ehDunA_7cPMu3CiHFugYWDkauOsiqHH-bv9s,20725
65
+ langroid/embedding_models/models.py,sha256=g6BdBon6JzCIg5hYN07y6aCcWRJqCpGwCLkvXDPEeew,20787
66
66
  langroid/embedding_models/remote_embeds.py,sha256=6_kjXByVbqhY9cGwl9R83ZcYC2km-nGieNNAo1McHaY,5151
67
67
  langroid/embedding_models/protoc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
68
  langroid/embedding_models/protoc/embeddings.proto,sha256=_O-SgFpTaylQeOTgSpxhEJ7CUw7PeCQQJLaPqpPYKJg,321
@@ -112,7 +112,7 @@ langroid/utils/configuration.py,sha256=ZkHHkEeWuS-o3_S4g0SE0wz-UK_of23NOWve1kpQi
112
112
  langroid/utils/constants.py,sha256=CK09kda9bNDEhnwClq7ZTWZOh38guJlfcZ5hKUS1Ijo,1075
113
113
  langroid/utils/git_utils.py,sha256=WnflJ3R3owhlD0LNdSJakcKhExcEehE1UW5jYVQl8JY,7955
114
114
  langroid/utils/globals.py,sha256=Az9dOFqR6n9CoTYSqa2kLikQWS0oCQ9DFQIQAnG-2q8,1355
115
- langroid/utils/logging.py,sha256=mwxHimq1wtVQ64PvDyfJJ7Upj-rjHLNHgx8EC2wClvo,4024
115
+ langroid/utils/logging.py,sha256=zTcsd0Gsmar-BMUCoVyRJUOvKE6Nb54PRFu5coqCAmA,6844
116
116
  langroid/utils/object_registry.py,sha256=iPz9GHzvmCeVoidB3JdAMEKcxJEqTdUr0otQEexDZ5s,2100
117
117
  langroid/utils/pandas_utils.py,sha256=UctS986Jtl_MvU5rA7-GfrjEHXP7MNu8ePhepv0bTn0,755
118
118
  langroid/utils/pydantic_utils.py,sha256=R7Ps8VP56-eSo-LYHWllFo-SJ2zDmdItuuYpUq2gGJ8,20854
@@ -133,7 +133,7 @@ langroid/vector_store/pineconedb.py,sha256=otxXZNaBKb9f_H75HTaU3lMHiaR2NUp5MqwLZ
133
133
  langroid/vector_store/postgres.py,sha256=wHPtIi2qM4fhO4pMQr95pz1ZCe7dTb2hxl4VYspGZoA,16104
134
134
  langroid/vector_store/qdrantdb.py,sha256=O6dSBoDZ0jzfeVBd7LLvsXu083xs2fxXtPa9gGX3JX4,18443
135
135
  langroid/vector_store/weaviatedb.py,sha256=Yn8pg139gOy3zkaPfoTbMXEEBCiLiYa1MU5d_3UA1K4,11847
136
- langroid-0.53.10.dist-info/METADATA,sha256=LT3U04tElPa-24L_12M3ncmAtvvcD_3228Pf2lG3Yi8,64946
137
- langroid-0.53.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
138
- langroid-0.53.10.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
139
- langroid-0.53.10.dist-info/RECORD,,
136
+ langroid-0.53.12.dist-info/METADATA,sha256=oEsmu11j0aZ18Qqz7-HRIGJNNhGccECY4UmAVjQLHeU,64946
137
+ langroid-0.53.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
138
+ langroid-0.53.12.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
139
+ langroid-0.53.12.dist-info/RECORD,,