langroid 0.1.260__py3-none-any.whl → 0.1.261__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.
@@ -45,8 +45,6 @@ class ChatDocMetaData(DocMetaData):
45
45
  parent: Optional["ChatDocument"] = None
46
46
  sender: Entity
47
47
  tool_ids: List[str] = [] # stack of tool_ids; used by OpenAIAssistant
48
- # when result returns to parent, pretend message is from this entity
49
- parent_responder: None | Entity = None
50
48
  block: None | Entity = None
51
49
  sender_name: str = ""
52
50
  recipient: str = ""
langroid/agent/task.py CHANGED
@@ -72,7 +72,7 @@ class TaskConfig(BaseModel):
72
72
 
73
73
  inf_loop_cycle_len: int = 10
74
74
  inf_loop_dominance_factor: float = 1.5
75
- inf_loop_wait_factor: float = 5.0
75
+ inf_loop_wait_factor: int = 5
76
76
 
77
77
 
78
78
  class Task:
@@ -190,7 +190,11 @@ class Task:
190
190
  # counts of distinct pending messages in history,
191
191
  # to help detect (exact) infinite loops
192
192
  self.message_counter: Counter[str] = Counter()
193
- self.history_count: Deque[int] = deque(maxlen=self.config.inf_loop_cycle_len)
193
+ self._init_message_counter()
194
+
195
+ self.history: Deque[str] = deque(
196
+ maxlen=self.config.inf_loop_cycle_len * self.config.inf_loop_wait_factor
197
+ )
194
198
  # copy the agent's config, so that we don't modify the original agent's config,
195
199
  # which may be shared by other agents.
196
200
  try:
@@ -334,6 +338,12 @@ class Task:
334
338
  def __str__(self) -> str:
335
339
  return f"{self.name}"
336
340
 
341
+ def _init_message_counter(self) -> None:
342
+ self.message_counter.clear()
343
+ # create a unique string that will not likely be in any message,
344
+ # so we always have a message with count=1
345
+ self.message_counter.update([hash("___NO_MESSAGE___")])
346
+
337
347
  def _cache_session_store(self, key: str, value: str) -> None:
338
348
  """
339
349
  Cache a key-value pair for the current session.
@@ -445,7 +455,7 @@ class Task:
445
455
  ),
446
456
  )
447
457
  else:
448
- self.pending_message = msg
458
+ self.pending_message = copy.deepcopy(msg)
449
459
  if self.pending_message is not None and self.caller is not None:
450
460
  # msg may have come from `caller`, so we pretend this is from
451
461
  # the CURRENT task's USER entity
@@ -485,8 +495,8 @@ class Task:
485
495
  self.max_tokens = max_tokens
486
496
  self.session_id = session_id
487
497
  self._set_alive()
488
- self.message_counter.clear()
489
- self.history_count.clear()
498
+ self._init_message_counter()
499
+ self.history.clear()
490
500
 
491
501
  assert (
492
502
  msg is None or isinstance(msg, str) or isinstance(msg, ChatDocument)
@@ -585,8 +595,8 @@ class Task:
585
595
  self.max_tokens = max_tokens
586
596
  self.session_id = session_id
587
597
  self._set_alive()
588
- self.message_counter.clear()
589
- self.history_count.clear()
598
+ self._init_message_counter()
599
+ self.history.clear()
590
600
 
591
601
  if (
592
602
  isinstance(msg, ChatDocument)
@@ -666,9 +676,7 @@ class Task:
666
676
  )
667
677
  # TODO decide on whether or not to print, based on is_async
668
678
  llm_model = (
669
- "no-LLM"
670
- if self.agent.config.llm is None
671
- else self.agent.config.llm.chat_model
679
+ "no-LLM" if self.agent.llm is None else self.agent.llm.config.chat_model
672
680
  )
673
681
  if not settings.quiet:
674
682
  print(
@@ -779,7 +787,7 @@ class Task:
779
787
  # skip trying other responders in this step
780
788
  break
781
789
  if not found_response:
782
- self._process_invalid_step_result(parent)
790
+ self._process_invalid_step_result()
783
791
  self._show_pending_message_if_debug()
784
792
  return self.pending_message
785
793
 
@@ -871,7 +879,7 @@ class Task:
871
879
  # skip trying other responders in this step
872
880
  break
873
881
  if not found_response:
874
- self._process_invalid_step_result(parent)
882
+ self._process_invalid_step_result()
875
883
  self._show_pending_message_if_debug()
876
884
  return self.pending_message
877
885
 
@@ -906,34 +914,13 @@ class Task:
906
914
  if self.pending_message is not None:
907
915
  hashed_msg = hash(str(self.pending_message))
908
916
  self.message_counter.update([hashed_msg])
909
- self.history_count.append(self.message_counter[hashed_msg])
917
+ self.history.append(hashed_msg)
910
918
 
911
- def _process_invalid_step_result(self, parent: ChatDocument | None) -> None:
919
+ def _process_invalid_step_result(self) -> None:
912
920
  """
913
- Since step had no valid result from any responder, decide whether to update the
914
- self.pending_message to a NO_ANSWER message from the opposite entity,
915
- or leave it as is.
916
- Args:
917
- parent (ChatDocument|None): parent message of the current message
921
+ No valid result from any responder => increment stalled counter.
918
922
  """
919
923
  self.n_stalled_steps += 1
920
- user_dummy_response = self.pending_sender != Entity.USER and self.interactive
921
- if (not self.is_pass_thru) and (
922
- not self.task_progress or self.allow_null_result or user_dummy_response
923
- ):
924
-
925
- # There has been no progress at all in this task, so we
926
- # update the pending_message to a dummy NO_ANSWER msg
927
- # from the entity 'opposite' to the current pending_sender,
928
- # so we show "progress" and avoid getting stuck in an infinite loop.
929
- responder = (
930
- Entity.LLM if self.pending_sender == Entity.USER else Entity.USER
931
- )
932
- self.pending_message = ChatDocument(
933
- content=NO_ANSWER,
934
- metadata=ChatDocMetaData(sender=responder, parent=parent),
935
- )
936
- self.pending_sender = responder
937
924
  self.log_message(self.pending_sender, self.pending_message, mark=True)
938
925
 
939
926
  def _show_pending_message_if_debug(self) -> None:
@@ -1068,7 +1055,6 @@ class Task:
1068
1055
  tool_messages = result_msg.tool_messages if result_msg else []
1069
1056
  block = result_msg.metadata.block if result_msg else None
1070
1057
  recipient = result_msg.metadata.recipient if result_msg else None
1071
- responder = result_msg.metadata.parent_responder if result_msg else None
1072
1058
  tool_ids = result_msg.metadata.tool_ids if result_msg else []
1073
1059
  status = result_msg.metadata.status if result_msg else None
1074
1060
 
@@ -1084,7 +1070,6 @@ class Task:
1084
1070
  sender=Entity.USER,
1085
1071
  block=block,
1086
1072
  status=status,
1087
- parent_responder=responder,
1088
1073
  sender_name=self.name,
1089
1074
  recipient=recipient,
1090
1075
  tool_ids=tool_ids,
@@ -1154,9 +1139,9 @@ class Task:
1154
1139
  So if you plot these frequencies in decreasing order,
1155
1140
  you will see a big drop in the plot, from m to m+1.
1156
1141
  We call the freqs until m the "dominant" freqs.
1157
- 2. Say we found m such dominant frequencies.
1158
- If these are the same as the freqs of the last m messages,
1159
- then we are likely in a loop.
1142
+ 2. Say we found m such dominant messages
1143
+ If the set of last (W * m) messages are the same as the
1144
+ set of m dominant messages, then we are likely in a loop.
1160
1145
  """
1161
1146
  max_cycle_len = self.config.inf_loop_cycle_len
1162
1147
  if max_cycle_len <= 0:
@@ -1167,6 +1152,7 @@ class Task:
1167
1152
  # we haven't seen enough messages to detect a loop
1168
1153
  return False
1169
1154
 
1155
+ # recall there's always a dummy msg with freq = 1
1170
1156
  most_common_msg_counts: List[Tuple[str, int]] = (
1171
1157
  self.message_counter.most_common(max_cycle_len + 1)
1172
1158
  )
@@ -1179,7 +1165,7 @@ class Task:
1179
1165
  ratios = counts[:-1] / counts[1:]
1180
1166
  diffs = counts[:-1] - counts[1:]
1181
1167
  indices = np.where((ratios > F) & (diffs > wait_factor))[0]
1182
- m = indices[0] if indices.size > 0 else -1
1168
+ m = indices[-1] if indices.size > 0 else -1
1183
1169
  if m < 0:
1184
1170
  # no dominance found, but...
1185
1171
  if len(most_common_msg_counts) <= max_cycle_len:
@@ -1195,12 +1181,13 @@ class Task:
1195
1181
  return False
1196
1182
 
1197
1183
  dominant_msg_counts = most_common_msg_counts[: m + 1]
1198
- # if the dominant m message counts are the same as the
1199
- # counts of the last m messages, then we are likely in a loop
1200
- dominant_counts = sorted([c for _, c in dominant_msg_counts])
1201
- recent_counts = sorted(list(self.history_count)[-(m + 1) :])
1202
-
1203
- return dominant_counts == recent_counts
1184
+ # if the SET of dominant m messages is the same as the
1185
+ # the SET of last m*w messages, (where w = config.inf_loop_wait_factor),
1186
+ # then we are likely in a loop
1187
+ dominant_msgs = set([msg for msg, _ in dominant_msg_counts])
1188
+ lookback = wait_factor * (m + 1)
1189
+ recent_msgs = set(list(self.history)[-lookback:])
1190
+ return dominant_msgs == recent_msgs
1204
1191
 
1205
1192
  def done(
1206
1193
  self, result: ChatDocument | None = None, r: Responder | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langroid
3
- Version: 0.1.260
3
+ Version: 0.1.261
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  License: MIT
6
6
  Author: Prasad Chalasani
@@ -5,7 +5,7 @@ langroid/agent/batch.py,sha256=feRA_yRG768ElOQjrKEefcRv6Aefd_yY7qktuYUQDwc,10040
5
5
  langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  langroid/agent/callbacks/chainlit.py,sha256=6gkk9Qf_i4fOD13w8ZdUfMcgKYPzLMw30hzFUN60AIc,22044
7
7
  langroid/agent/chat_agent.py,sha256=hnmeOxdi4i5w8WaL2kPjQOEpenoRW_hG5EfeMWuuVsQ,39478
8
- langroid/agent/chat_document.py,sha256=uwCq53SHRyxQw6qyhjzPYuJG48VHBgOf2122Ew3fk6c,9316
8
+ langroid/agent/chat_document.py,sha256=Xx4uFVI77YxDc99x5P4JGprDINT5umqvTzCgHsvJpDc,9200
9
9
  langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
11
11
  langroid/agent/openai_assistant.py,sha256=kIVDI4r-xGvplLU5s0nShPVHs6Jq-wOsfWE0kcMhAdQ,33056
@@ -32,7 +32,7 @@ langroid/agent/special/sql/utils/populate_metadata.py,sha256=1J22UsyEPKzwK0XlJZt
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=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
35
- langroid/agent/task.py,sha256=Su1TpEmt3aPVTzXnTvJdyjSqZsXmUUcHN12KAs-gMY0,60408
35
+ langroid/agent/task.py,sha256=acGBUrme5txxzyLRvwvoMmB-xFK1UXoBMOvw5_nmMyY,59636
36
36
  langroid/agent/tool_message.py,sha256=7t-UGEbykosKHAvaLI0Rm59sgxvN31IO3-P7bg7gLug,9730
37
37
  langroid/agent/tools/__init__.py,sha256=8Pc9BlGCB5FQ2IDGKS_WPpHCoWp5jblMU8EHJwwikAY,303
38
38
  langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
@@ -126,8 +126,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
126
126
  langroid/vector_store/momento.py,sha256=QaPzUnTwlswoawGB-paLtUPyLRvckFXLfLDfvbTzjNQ,10505
127
127
  langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
128
128
  langroid/vector_store/qdrantdb.py,sha256=wYOuu5c2vIKn9ZgvTXcAiZXMpV8AOXEWFAzI8S8UP-0,16828
129
- pyproject.toml,sha256=6Vqvoq2dWP_JzMKXoKVlTteWCZGmWPW4tFQV0KfRtVQ,7026
130
- langroid-0.1.260.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
131
- langroid-0.1.260.dist-info/METADATA,sha256=ftMLe9Jwmp1GkP9bsiO4kdxABtcgFuR1hsOdaXrKNkI,52506
132
- langroid-0.1.260.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
133
- langroid-0.1.260.dist-info/RECORD,,
129
+ pyproject.toml,sha256=TKd6Vvp5TeV62PB_3u78Iyv6yeiQpl3vdeer0p52uuI,7026
130
+ langroid-0.1.261.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
131
+ langroid-0.1.261.dist-info/METADATA,sha256=Mlx16iPiQulcoWucigfFbJnJ4Br47qofiV_A7lPDSUE,52506
132
+ langroid-0.1.261.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
133
+ langroid-0.1.261.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langroid"
3
- version = "0.1.260"
3
+ version = "0.1.261"
4
4
  description = "Harness LLMs with Multi-Agent Programming"
5
5
  authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
6
6
  readme = "README.md"