langroid 0.56.19__py3-none-any.whl → 0.57.0__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/chat_document.py +67 -19
- langroid/agent/task.py +96 -1
- langroid/utils/html_logger.py +825 -0
- {langroid-0.56.19.dist-info → langroid-0.57.0.dist-info}/METADATA +1 -1
- {langroid-0.56.19.dist-info → langroid-0.57.0.dist-info}/RECORD +7 -6
- {langroid-0.56.19.dist-info → langroid-0.57.0.dist-info}/WHEEL +0 -0
- {langroid-0.56.19.dist-info → langroid-0.57.0.dist-info}/licenses/LICENSE +0 -0
langroid/agent/chat_document.py
CHANGED
@@ -217,25 +217,29 @@ class ChatDocument(Document):
|
|
217
217
|
"""
|
218
218
|
tool_type = "" # FUNC or TOOL
|
219
219
|
tool = "" # tool name or function name
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
220
|
+
|
221
|
+
# Skip tool detection for system messages - they contain tool instructions,
|
222
|
+
# not actual tool calls
|
223
|
+
if self.metadata.sender != Entity.SYSTEM:
|
224
|
+
oai_tools = (
|
225
|
+
[]
|
226
|
+
if self.oai_tool_calls is None
|
227
|
+
else [t for t in self.oai_tool_calls if t.function is not None]
|
228
|
+
)
|
229
|
+
if self.function_call is not None:
|
230
|
+
tool_type = "FUNC"
|
231
|
+
tool = self.function_call.name
|
232
|
+
elif len(oai_tools) > 0:
|
233
|
+
tool_type = "OAI_TOOL"
|
234
|
+
tool = ",".join(t.function.name for t in oai_tools) # type: ignore
|
235
|
+
else:
|
236
|
+
try:
|
237
|
+
json_tools = self.get_tool_names()
|
238
|
+
except Exception:
|
239
|
+
json_tools = []
|
240
|
+
if json_tools != []:
|
241
|
+
tool_type = "TOOL"
|
242
|
+
tool = json_tools[0]
|
239
243
|
recipient = self.metadata.recipient
|
240
244
|
content = self.content
|
241
245
|
sender_entity = self.metadata.sender
|
@@ -340,6 +344,50 @@ class ChatDocument(Document):
|
|
340
344
|
),
|
341
345
|
)
|
342
346
|
|
347
|
+
@staticmethod
|
348
|
+
def from_LLMMessage(
|
349
|
+
message: LLMMessage,
|
350
|
+
sender_name: str = "",
|
351
|
+
recipient: str = "",
|
352
|
+
) -> "ChatDocument":
|
353
|
+
"""
|
354
|
+
Convert LLMMessage to ChatDocument.
|
355
|
+
|
356
|
+
Args:
|
357
|
+
message (LLMMessage): LLMMessage to convert.
|
358
|
+
sender_name (str): Name of the sender. Defaults to "".
|
359
|
+
recipient (str): Name of the recipient. Defaults to "".
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
ChatDocument: ChatDocument representation of this LLMMessage.
|
363
|
+
"""
|
364
|
+
# Map LLMMessage Role to ChatDocument Entity
|
365
|
+
role_to_entity = {
|
366
|
+
Role.USER: Entity.USER,
|
367
|
+
Role.SYSTEM: Entity.SYSTEM,
|
368
|
+
Role.ASSISTANT: Entity.LLM,
|
369
|
+
Role.FUNCTION: Entity.LLM,
|
370
|
+
Role.TOOL: Entity.LLM,
|
371
|
+
}
|
372
|
+
|
373
|
+
sender_entity = role_to_entity.get(message.role, Entity.USER)
|
374
|
+
|
375
|
+
return ChatDocument(
|
376
|
+
content=message.content or "",
|
377
|
+
content_any=message.content,
|
378
|
+
files=message.files,
|
379
|
+
function_call=message.function_call,
|
380
|
+
oai_tool_calls=message.tool_calls,
|
381
|
+
metadata=ChatDocMetaData(
|
382
|
+
source=sender_entity,
|
383
|
+
sender=sender_entity,
|
384
|
+
sender_name=sender_name,
|
385
|
+
recipient=recipient,
|
386
|
+
oai_tool_id=message.tool_call_id,
|
387
|
+
tool_ids=[message.tool_id] if message.tool_id else [],
|
388
|
+
),
|
389
|
+
)
|
390
|
+
|
343
391
|
@staticmethod
|
344
392
|
def to_LLMMessage(
|
345
393
|
message: Union[str, "ChatDocument"],
|
langroid/agent/task.py
CHANGED
@@ -55,6 +55,7 @@ from langroid.utils.constants import (
|
|
55
55
|
SEND_TO,
|
56
56
|
USER_QUIT_STRINGS,
|
57
57
|
)
|
58
|
+
from langroid.utils.html_logger import HTMLLogger
|
58
59
|
from langroid.utils.logging import RichFileLogger, setup_file_logger
|
59
60
|
from langroid.utils.object_registry import scheduled_cleanup
|
60
61
|
from langroid.utils.system import hash
|
@@ -154,6 +155,7 @@ class TaskConfig(BaseModel):
|
|
154
155
|
restart_as_subtask: bool = False
|
155
156
|
logs_dir: str = "logs"
|
156
157
|
enable_loggers: bool = True
|
158
|
+
enable_html_logging: bool = True
|
157
159
|
addressing_prefix: str = ""
|
158
160
|
allow_subtask_multi_oai_tools: bool = True
|
159
161
|
recognize_string_signals: bool = True
|
@@ -343,6 +345,7 @@ class Task:
|
|
343
345
|
self.session_id: str = ""
|
344
346
|
self.logger: None | RichFileLogger = None
|
345
347
|
self.tsv_logger: None | logging.Logger = None
|
348
|
+
self.html_logger: Optional[HTMLLogger] = None
|
346
349
|
self.color_log: bool = False if settings.notebook else True
|
347
350
|
|
348
351
|
self.n_stalled_steps = 0 # how many consecutive steps with no progress?
|
@@ -637,7 +640,20 @@ class Task:
|
|
637
640
|
|
638
641
|
self._show_pending_message_if_debug()
|
639
642
|
self.init_loggers()
|
640
|
-
|
643
|
+
# Log system message if it exists
|
644
|
+
if (
|
645
|
+
hasattr(self.agent, "_create_system_and_tools_message")
|
646
|
+
and hasattr(self.agent, "system_message")
|
647
|
+
and self.agent.system_message
|
648
|
+
):
|
649
|
+
system_msg = self.agent._create_system_and_tools_message()
|
650
|
+
system_message_chat_doc = ChatDocument.from_LLMMessage(
|
651
|
+
system_msg,
|
652
|
+
sender_name=self.name or "system",
|
653
|
+
)
|
654
|
+
# log the system message
|
655
|
+
self.log_message(Entity.SYSTEM, system_message_chat_doc, mark=True)
|
656
|
+
self.log_message(Entity.USER, self.pending_message, mark=True)
|
641
657
|
return self.pending_message
|
642
658
|
|
643
659
|
def init_loggers(self) -> None:
|
@@ -667,6 +683,34 @@ class Task:
|
|
667
683
|
header = ChatDocLoggerFields().tsv_header()
|
668
684
|
self.tsv_logger.info(f" \tTask\tResponder\t{header}")
|
669
685
|
|
686
|
+
# HTML logger
|
687
|
+
if self.config.enable_html_logging:
|
688
|
+
if (
|
689
|
+
self.caller is not None
|
690
|
+
and hasattr(self.caller, "html_logger")
|
691
|
+
and self.caller.html_logger is not None
|
692
|
+
):
|
693
|
+
self.html_logger = self.caller.html_logger
|
694
|
+
elif not hasattr(self, "html_logger") or self.html_logger is None:
|
695
|
+
from langroid.utils.html_logger import HTMLLogger
|
696
|
+
|
697
|
+
model_info = ""
|
698
|
+
if (
|
699
|
+
hasattr(self, "agent")
|
700
|
+
and hasattr(self.agent, "config")
|
701
|
+
and hasattr(self.agent.config, "llm")
|
702
|
+
):
|
703
|
+
model_info = getattr(self.agent.config.llm, "chat_model", "")
|
704
|
+
self.html_logger = HTMLLogger(
|
705
|
+
filename=self.name,
|
706
|
+
log_dir=self.config.logs_dir,
|
707
|
+
model_info=model_info,
|
708
|
+
append=False,
|
709
|
+
)
|
710
|
+
# Log clickable file:// link to the HTML log
|
711
|
+
html_log_path = self.html_logger.file_path.resolve()
|
712
|
+
logger.warning(f"📊 HTML Log: file://{html_log_path}")
|
713
|
+
|
670
714
|
def reset_all_sub_tasks(self) -> None:
|
671
715
|
"""
|
672
716
|
Recursively reset message history & state of own agent and
|
@@ -2037,6 +2081,8 @@ class Task:
|
|
2037
2081
|
mark (bool, optional): Whether to mark the message as the final result of
|
2038
2082
|
a `task.step()` call. Defaults to False.
|
2039
2083
|
"""
|
2084
|
+
from langroid.agent.chat_document import ChatDocLoggerFields
|
2085
|
+
|
2040
2086
|
default_values = ChatDocLoggerFields().dict().values()
|
2041
2087
|
msg_str_tsv = "\t".join(str(v) for v in default_values)
|
2042
2088
|
if msg is not None:
|
@@ -2077,6 +2123,48 @@ class Task:
|
|
2077
2123
|
resp_str = str(resp)
|
2078
2124
|
self.tsv_logger.info(f"{mark_str}\t{task_name}\t{resp_str}\t{msg_str_tsv}")
|
2079
2125
|
|
2126
|
+
# HTML logger
|
2127
|
+
if self.html_logger is not None:
|
2128
|
+
if msg is None:
|
2129
|
+
# Create a minimal fields object for None messages
|
2130
|
+
from langroid.agent.chat_document import ChatDocLoggerFields
|
2131
|
+
|
2132
|
+
fields_dict = {
|
2133
|
+
"responder": str(resp),
|
2134
|
+
"mark": "*" if mark else "",
|
2135
|
+
"task_name": self.name or "root",
|
2136
|
+
"content": "",
|
2137
|
+
"sender_entity": str(resp),
|
2138
|
+
"sender_name": "",
|
2139
|
+
"recipient": "",
|
2140
|
+
"block": None,
|
2141
|
+
"tool_type": "",
|
2142
|
+
"tool": "",
|
2143
|
+
}
|
2144
|
+
else:
|
2145
|
+
# Get fields from the message
|
2146
|
+
fields = msg.log_fields()
|
2147
|
+
fields_dict = fields.dict()
|
2148
|
+
fields_dict.update(
|
2149
|
+
{
|
2150
|
+
"responder": str(resp),
|
2151
|
+
"mark": "*" if mark else "",
|
2152
|
+
"task_name": self.name or "root",
|
2153
|
+
}
|
2154
|
+
)
|
2155
|
+
|
2156
|
+
# Create a ChatDocLoggerFields-like object for the HTML logger
|
2157
|
+
# Create a simple BaseModel subclass dynamically
|
2158
|
+
from langroid.pydantic_v1 import BaseModel
|
2159
|
+
|
2160
|
+
class LogFields(BaseModel):
|
2161
|
+
class Config:
|
2162
|
+
extra = "allow" # Allow extra fields
|
2163
|
+
|
2164
|
+
# Create instance with the fields from fields_dict
|
2165
|
+
log_obj = LogFields(**fields_dict)
|
2166
|
+
self.html_logger.log(log_obj)
|
2167
|
+
|
2080
2168
|
def _valid_recipient(self, recipient: str) -> bool:
|
2081
2169
|
"""
|
2082
2170
|
Is the recipient among the list of responders?
|
@@ -2335,6 +2423,13 @@ class Task:
|
|
2335
2423
|
# Check if we matched the entire sequence
|
2336
2424
|
return seq_idx == len(sequence.events)
|
2337
2425
|
|
2426
|
+
def close_loggers(self) -> None:
|
2427
|
+
"""Close all loggers to ensure clean shutdown."""
|
2428
|
+
if hasattr(self, "logger") and self.logger is not None:
|
2429
|
+
self.logger.close()
|
2430
|
+
if hasattr(self, "html_logger") and self.html_logger is not None:
|
2431
|
+
self.html_logger.close()
|
2432
|
+
|
2338
2433
|
def _matches_sequence_with_current(
|
2339
2434
|
self,
|
2340
2435
|
msg_chain: List[ChatDocument],
|
@@ -0,0 +1,825 @@
|
|
1
|
+
"""HTML Logger for Langroid Task System.
|
2
|
+
|
3
|
+
This module provides an HTML logger that creates self-contained HTML files
|
4
|
+
with collapsible log entries for better visualization of agent interactions.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import html
|
8
|
+
import json
|
9
|
+
from datetime import datetime
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Any, Dict, List
|
12
|
+
|
13
|
+
from langroid.pydantic_v1 import BaseModel
|
14
|
+
from langroid.utils.logging import setup_logger
|
15
|
+
|
16
|
+
|
17
|
+
class HTMLLogger:
|
18
|
+
"""Logger that outputs task logs as interactive HTML files."""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
filename: str,
|
23
|
+
log_dir: str = "logs",
|
24
|
+
model_info: str = "",
|
25
|
+
append: bool = False,
|
26
|
+
):
|
27
|
+
"""Initialize the HTML logger.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
filename: Base name for the log file (without extension)
|
31
|
+
log_dir: Directory to store log files
|
32
|
+
model_info: Information about the model being used
|
33
|
+
append: Whether to append to existing file
|
34
|
+
"""
|
35
|
+
self.filename = filename
|
36
|
+
self.log_dir = Path(log_dir)
|
37
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
38
|
+
self.file_path = self.log_dir / f"{filename}.html"
|
39
|
+
self.model_info = model_info
|
40
|
+
self.entries: List[Dict[str, Any]] = []
|
41
|
+
self.entry_counter = 0
|
42
|
+
self.tool_counter = 0
|
43
|
+
|
44
|
+
# Logger for errors
|
45
|
+
self.logger = setup_logger(__name__)
|
46
|
+
|
47
|
+
if not append or not self.file_path.exists():
|
48
|
+
self._write_header()
|
49
|
+
|
50
|
+
def _write_header(self) -> None:
|
51
|
+
"""Write the HTML header with CSS and JavaScript."""
|
52
|
+
timestamp = datetime.now().strftime("%m/%d/%Y, %I:%M:%S %p")
|
53
|
+
|
54
|
+
html_content = f"""<!DOCTYPE html>
|
55
|
+
<html lang="en">
|
56
|
+
<head>
|
57
|
+
<meta charset="UTF-8">
|
58
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
59
|
+
<meta http-equiv="refresh" content="2">
|
60
|
+
<title>{self.filename} - Langroid Task Log</title>
|
61
|
+
<style>
|
62
|
+
body {{
|
63
|
+
background-color: #1e1e1e;
|
64
|
+
color: #f0f0f0;
|
65
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
66
|
+
font-size: 14px;
|
67
|
+
margin: 0;
|
68
|
+
padding: 20px;
|
69
|
+
line-height: 1.6;
|
70
|
+
}}
|
71
|
+
|
72
|
+
.header {{
|
73
|
+
border: 2px solid #d4a017;
|
74
|
+
padding: 15px;
|
75
|
+
margin-bottom: 20px;
|
76
|
+
color: #d4a017;
|
77
|
+
background-color: #2b2b2b;
|
78
|
+
border-radius: 5px;
|
79
|
+
}}
|
80
|
+
|
81
|
+
.header-line {{
|
82
|
+
display: flex;
|
83
|
+
justify-content: space-between;
|
84
|
+
align-items: center;
|
85
|
+
}}
|
86
|
+
|
87
|
+
.separator {{
|
88
|
+
border-bottom: 2px solid #d4a017;
|
89
|
+
margin: 20px 0;
|
90
|
+
}}
|
91
|
+
|
92
|
+
.controls {{
|
93
|
+
margin-bottom: 20px;
|
94
|
+
}}
|
95
|
+
|
96
|
+
.controls {{
|
97
|
+
display: flex;
|
98
|
+
align-items: center;
|
99
|
+
gap: 20px;
|
100
|
+
}}
|
101
|
+
|
102
|
+
.controls button {{
|
103
|
+
background-color: #333;
|
104
|
+
color: #f0f0f0;
|
105
|
+
border: 1px solid #555;
|
106
|
+
padding: 8px 16px;
|
107
|
+
cursor: pointer;
|
108
|
+
border-radius: 3px;
|
109
|
+
font-family: inherit;
|
110
|
+
}}
|
111
|
+
|
112
|
+
.controls button:hover {{
|
113
|
+
background-color: #444;
|
114
|
+
border-color: #d4a017;
|
115
|
+
}}
|
116
|
+
|
117
|
+
.controls label {{
|
118
|
+
color: #f0f0f0;
|
119
|
+
display: flex;
|
120
|
+
align-items: center;
|
121
|
+
gap: 8px;
|
122
|
+
cursor: pointer;
|
123
|
+
}}
|
124
|
+
|
125
|
+
.controls input[type="checkbox"] {{
|
126
|
+
cursor: pointer;
|
127
|
+
}}
|
128
|
+
|
129
|
+
.hidden {{
|
130
|
+
display: none !important;
|
131
|
+
}}
|
132
|
+
|
133
|
+
.entry {{
|
134
|
+
margin-bottom: 15px;
|
135
|
+
padding-left: 10px;
|
136
|
+
}}
|
137
|
+
|
138
|
+
.entry.faded {{
|
139
|
+
opacity: 0.4;
|
140
|
+
}}
|
141
|
+
|
142
|
+
.entry.important {{
|
143
|
+
opacity: 1.0;
|
144
|
+
}}
|
145
|
+
|
146
|
+
.entry.user .entity-header {{
|
147
|
+
color: #00bfff;
|
148
|
+
}}
|
149
|
+
|
150
|
+
.entry.assistant .entity-header {{
|
151
|
+
color: #ff6b6b;
|
152
|
+
}}
|
153
|
+
|
154
|
+
.entry.llm .entity-header {{
|
155
|
+
color: #00ff00;
|
156
|
+
}}
|
157
|
+
|
158
|
+
.entry.agent .entity-header {{
|
159
|
+
color: #ff9500;
|
160
|
+
}}
|
161
|
+
|
162
|
+
.entry.system .entity-header {{
|
163
|
+
color: #888;
|
164
|
+
}}
|
165
|
+
|
166
|
+
.entry.other .entity-header {{
|
167
|
+
color: #999;
|
168
|
+
}}
|
169
|
+
|
170
|
+
.entity-header {{
|
171
|
+
font-weight: bold;
|
172
|
+
margin-bottom: 5px;
|
173
|
+
cursor: pointer;
|
174
|
+
}}
|
175
|
+
|
176
|
+
.entity-header:hover {{
|
177
|
+
opacity: 0.8;
|
178
|
+
}}
|
179
|
+
|
180
|
+
.header-main {{
|
181
|
+
/* Removed text-transform to preserve tool name casing */
|
182
|
+
display: inline;
|
183
|
+
}}
|
184
|
+
|
185
|
+
.header-content {{
|
186
|
+
margin-left: 30px;
|
187
|
+
opacity: 0.7;
|
188
|
+
font-weight: normal;
|
189
|
+
font-style: italic;
|
190
|
+
display: block;
|
191
|
+
}}
|
192
|
+
|
193
|
+
.entry-content {{
|
194
|
+
margin-left: 20px;
|
195
|
+
margin-top: 5px;
|
196
|
+
}}
|
197
|
+
|
198
|
+
.entry-content.collapsed {{
|
199
|
+
display: none;
|
200
|
+
}}
|
201
|
+
|
202
|
+
.collapsible {{
|
203
|
+
margin: 5px 0;
|
204
|
+
margin-left: 20px;
|
205
|
+
}}
|
206
|
+
|
207
|
+
.toggle {{
|
208
|
+
cursor: pointer;
|
209
|
+
user-select: none;
|
210
|
+
color: #00ff00;
|
211
|
+
display: inline-block;
|
212
|
+
width: 25px;
|
213
|
+
font-family: monospace;
|
214
|
+
margin-right: 5px;
|
215
|
+
}}
|
216
|
+
|
217
|
+
.toggle:hover {{
|
218
|
+
color: #00ff00;
|
219
|
+
text-shadow: 0 0 5px #00ff00;
|
220
|
+
}}
|
221
|
+
|
222
|
+
.content {{
|
223
|
+
margin-left: 25px;
|
224
|
+
margin-top: 5px;
|
225
|
+
white-space: pre-wrap;
|
226
|
+
word-wrap: break-word;
|
227
|
+
}}
|
228
|
+
|
229
|
+
.main-content {{
|
230
|
+
margin-top: 10px;
|
231
|
+
white-space: pre-wrap;
|
232
|
+
word-wrap: break-word;
|
233
|
+
}}
|
234
|
+
|
235
|
+
.collapsed .content {{
|
236
|
+
display: none;
|
237
|
+
}}
|
238
|
+
|
239
|
+
.tool-section {{
|
240
|
+
margin: 10px 0;
|
241
|
+
margin-left: 20px;
|
242
|
+
}}
|
243
|
+
|
244
|
+
.tool-name {{
|
245
|
+
color: #d4a017;
|
246
|
+
font-weight: bold;
|
247
|
+
}}
|
248
|
+
|
249
|
+
.tool-result {{
|
250
|
+
margin-left: 25px;
|
251
|
+
}}
|
252
|
+
|
253
|
+
.tool-result.success {{
|
254
|
+
color: #00ff00;
|
255
|
+
}}
|
256
|
+
|
257
|
+
.tool-result.error {{
|
258
|
+
color: #ff0000;
|
259
|
+
}}
|
260
|
+
|
261
|
+
.code-block {{
|
262
|
+
background-color: #2b2b2b;
|
263
|
+
border: 1px solid #444;
|
264
|
+
padding: 10px;
|
265
|
+
margin: 5px 0;
|
266
|
+
border-radius: 3px;
|
267
|
+
overflow-x: auto;
|
268
|
+
}}
|
269
|
+
|
270
|
+
.metadata {{
|
271
|
+
color: #888;
|
272
|
+
font-size: 0.9em;
|
273
|
+
margin-left: 25px;
|
274
|
+
}}
|
275
|
+
|
276
|
+
|
277
|
+
pre {{
|
278
|
+
margin: 0;
|
279
|
+
white-space: pre-wrap;
|
280
|
+
word-wrap: break-word;
|
281
|
+
}}
|
282
|
+
</style>
|
283
|
+
<script>
|
284
|
+
function toggleEntry(entryId) {{
|
285
|
+
const contentElement = document.getElementById(entryId + '_content');
|
286
|
+
const toggleElement = document.querySelector(
|
287
|
+
'#' + entryId + ' .entity-header .toggle'
|
288
|
+
);
|
289
|
+
|
290
|
+
if (!contentElement || !toggleElement) return;
|
291
|
+
|
292
|
+
if (contentElement.classList.contains('collapsed')) {{
|
293
|
+
contentElement.classList.remove('collapsed');
|
294
|
+
toggleElement.textContent = '[-]';
|
295
|
+
// Save expanded state
|
296
|
+
localStorage.setItem('expanded_' + entryId, 'true');
|
297
|
+
}} else {{
|
298
|
+
contentElement.classList.add('collapsed');
|
299
|
+
toggleElement.textContent = '[+]';
|
300
|
+
// Save collapsed state
|
301
|
+
localStorage.setItem('expanded_' + entryId, 'false');
|
302
|
+
}}
|
303
|
+
}}
|
304
|
+
|
305
|
+
function toggle(id) {{
|
306
|
+
const element = document.getElementById(id);
|
307
|
+
if (!element) return;
|
308
|
+
|
309
|
+
element.classList.toggle('collapsed');
|
310
|
+
const toggle = element.querySelector('.toggle');
|
311
|
+
if (toggle) {{
|
312
|
+
toggle.textContent = element.classList.contains('collapsed')
|
313
|
+
? '[+]' : '[-]';
|
314
|
+
}}
|
315
|
+
|
316
|
+
// Save collapsed state for collapsible sections
|
317
|
+
localStorage.setItem(
|
318
|
+
'collapsed_' + id, element.classList.contains('collapsed')
|
319
|
+
);
|
320
|
+
}}
|
321
|
+
|
322
|
+
let allExpanded = false;
|
323
|
+
|
324
|
+
function toggleAll() {{
|
325
|
+
const btn = document.getElementById('toggleAllBtn');
|
326
|
+
if (allExpanded) {{
|
327
|
+
collapseAll();
|
328
|
+
btn.textContent = 'Expand All';
|
329
|
+
allExpanded = false;
|
330
|
+
}} else {{
|
331
|
+
expandAll();
|
332
|
+
btn.textContent = 'Collapse All';
|
333
|
+
allExpanded = true;
|
334
|
+
}}
|
335
|
+
}}
|
336
|
+
|
337
|
+
function expandAll() {{
|
338
|
+
// Expand all visible main entries
|
339
|
+
const entries = document.querySelectorAll(
|
340
|
+
'.entry:not(.hidden) .entry-content'
|
341
|
+
);
|
342
|
+
entries.forEach(element => {{
|
343
|
+
element.classList.remove('collapsed');
|
344
|
+
}});
|
345
|
+
|
346
|
+
// Update all visible main entry toggles
|
347
|
+
const entryToggles = document.querySelectorAll(
|
348
|
+
'.entry:not(.hidden) .entity-header .toggle'
|
349
|
+
);
|
350
|
+
entryToggles.forEach(toggle => {{
|
351
|
+
toggle.textContent = '[-]';
|
352
|
+
}});
|
353
|
+
|
354
|
+
// Expand all visible sub-sections
|
355
|
+
const collapsibles = document.querySelectorAll(
|
356
|
+
'.entry:not(.hidden) .collapsible'
|
357
|
+
);
|
358
|
+
collapsibles.forEach(element => {{
|
359
|
+
element.classList.remove('collapsed');
|
360
|
+
const toggle = element.querySelector('.toggle');
|
361
|
+
if (toggle) {{
|
362
|
+
toggle.textContent = '[-]';
|
363
|
+
}}
|
364
|
+
}});
|
365
|
+
}}
|
366
|
+
|
367
|
+
function collapseAll() {{
|
368
|
+
// Collapse all visible entries
|
369
|
+
const entries = document.querySelectorAll(
|
370
|
+
'.entry:not(.hidden) .entry-content'
|
371
|
+
);
|
372
|
+
entries.forEach(element => {{
|
373
|
+
element.classList.add('collapsed');
|
374
|
+
}});
|
375
|
+
|
376
|
+
// Update all visible entry toggles
|
377
|
+
const entryToggles = document.querySelectorAll(
|
378
|
+
'.entry:not(.hidden) .entity-header .toggle'
|
379
|
+
);
|
380
|
+
entryToggles.forEach(toggle => {{
|
381
|
+
toggle.textContent = '[+]';
|
382
|
+
}});
|
383
|
+
|
384
|
+
// Collapse all visible sub-sections
|
385
|
+
const collapsibles = document.querySelectorAll(
|
386
|
+
'.entry:not(.hidden) .collapsible'
|
387
|
+
);
|
388
|
+
collapsibles.forEach(element => {{
|
389
|
+
element.classList.add('collapsed');
|
390
|
+
const toggle = element.querySelector('.toggle');
|
391
|
+
if (toggle) {{
|
392
|
+
toggle.textContent = '[+]';
|
393
|
+
}}
|
394
|
+
}});
|
395
|
+
}}
|
396
|
+
|
397
|
+
function filterEntries() {{
|
398
|
+
const checkbox = document.getElementById('filterCheckbox');
|
399
|
+
const entries = document.querySelectorAll('.entry');
|
400
|
+
|
401
|
+
// Save checkbox state to localStorage
|
402
|
+
localStorage.setItem('filterImportant', checkbox.checked);
|
403
|
+
|
404
|
+
if (checkbox.checked) {{
|
405
|
+
// Show only important entries
|
406
|
+
entries.forEach(entry => {{
|
407
|
+
const isImportant = entry.classList.contains('important');
|
408
|
+
if (isImportant) {{
|
409
|
+
entry.classList.remove('hidden');
|
410
|
+
}} else {{
|
411
|
+
entry.classList.add('hidden');
|
412
|
+
}}
|
413
|
+
}});
|
414
|
+
}} else {{
|
415
|
+
// Show all entries
|
416
|
+
entries.forEach(entry => {{
|
417
|
+
entry.classList.remove('hidden');
|
418
|
+
}});
|
419
|
+
}}
|
420
|
+
|
421
|
+
// Reset toggle button state
|
422
|
+
allExpanded = false;
|
423
|
+
document.getElementById('toggleAllBtn').textContent = 'Expand All';
|
424
|
+
}}
|
425
|
+
|
426
|
+
// Initialize all as collapsed on load
|
427
|
+
document.addEventListener('DOMContentLoaded', function() {{
|
428
|
+
collapseAll();
|
429
|
+
|
430
|
+
// Restore checkbox state from localStorage
|
431
|
+
const checkbox = document.getElementById('filterCheckbox');
|
432
|
+
const savedState = localStorage.getItem('filterImportant');
|
433
|
+
if (savedState !== null) {{
|
434
|
+
// Use saved state if it exists
|
435
|
+
checkbox.checked = savedState === 'true';
|
436
|
+
}}
|
437
|
+
// Apply filter based on checkbox state (default is checked)
|
438
|
+
if (checkbox.checked) {{
|
439
|
+
filterEntries();
|
440
|
+
}}
|
441
|
+
|
442
|
+
// Restore expanded states from localStorage
|
443
|
+
const entries = document.querySelectorAll('.entry');
|
444
|
+
entries.forEach(entry => {{
|
445
|
+
const entryId = entry.id;
|
446
|
+
const expandedState = localStorage.getItem('expanded_' + entryId);
|
447
|
+
if (expandedState === 'true') {{
|
448
|
+
const contentElement = document.getElementById(
|
449
|
+
entryId + '_content'
|
450
|
+
);
|
451
|
+
const toggleElement = entry.querySelector('.entity-header .toggle');
|
452
|
+
if (contentElement && toggleElement) {{
|
453
|
+
contentElement.classList.remove('collapsed');
|
454
|
+
toggleElement.textContent = '[-]';
|
455
|
+
}}
|
456
|
+
}}
|
457
|
+
}});
|
458
|
+
|
459
|
+
// Restore collapsible section states
|
460
|
+
const collapsibles = document.querySelectorAll('.collapsible');
|
461
|
+
collapsibles.forEach(collapsible => {{
|
462
|
+
const id = collapsible.id;
|
463
|
+
const collapsedState = localStorage.getItem('collapsed_' + id);
|
464
|
+
if (collapsedState === 'false') {{
|
465
|
+
collapsible.classList.remove('collapsed');
|
466
|
+
const toggle = collapsible.querySelector('.toggle');
|
467
|
+
if (toggle) {{
|
468
|
+
toggle.textContent = '[-]';
|
469
|
+
}}
|
470
|
+
}}
|
471
|
+
}});
|
472
|
+
}});
|
473
|
+
</script>
|
474
|
+
</head>
|
475
|
+
<body>
|
476
|
+
<div class="header">
|
477
|
+
<div class="header-line">
|
478
|
+
<div>{self.filename}</div>
|
479
|
+
<div id="timestamp">{timestamp}</div>
|
480
|
+
</div>
|
481
|
+
</div>
|
482
|
+
|
483
|
+
<div class="separator"></div>
|
484
|
+
|
485
|
+
<div class="controls">
|
486
|
+
<button id="toggleAllBtn" onclick="toggleAll()">Expand All</button>
|
487
|
+
<label style="margin-left: 20px;">
|
488
|
+
<input type="checkbox" id="filterCheckbox"
|
489
|
+
onchange="filterEntries()" checked>
|
490
|
+
Show only important responses
|
491
|
+
</label>
|
492
|
+
</div>
|
493
|
+
|
494
|
+
<div id="content">
|
495
|
+
"""
|
496
|
+
try:
|
497
|
+
with open(self.file_path, "w", encoding="utf-8") as f:
|
498
|
+
f.write(html_content)
|
499
|
+
except Exception as e:
|
500
|
+
self.logger.error(f"Failed to write HTML header: {e}")
|
501
|
+
|
502
|
+
def log(self, fields: BaseModel) -> None:
|
503
|
+
"""Log a message entry.
|
504
|
+
|
505
|
+
Args:
|
506
|
+
fields: ChatDocLoggerFields containing all log information
|
507
|
+
"""
|
508
|
+
try:
|
509
|
+
entry_html = self._format_entry(fields)
|
510
|
+
self._append_to_file(entry_html)
|
511
|
+
self.entry_counter += 1
|
512
|
+
except Exception as e:
|
513
|
+
self.logger.error(f"Failed to log entry: {e}")
|
514
|
+
|
515
|
+
def _format_entry(self, fields: BaseModel) -> str:
|
516
|
+
"""Format a log entry as HTML.
|
517
|
+
|
518
|
+
Args:
|
519
|
+
fields: ChatDocLoggerFields containing all log information
|
520
|
+
|
521
|
+
Returns:
|
522
|
+
HTML string for the entry
|
523
|
+
"""
|
524
|
+
entry_id = f"entry_{self.entry_counter}"
|
525
|
+
|
526
|
+
# Get all relevant fields
|
527
|
+
responder = str(getattr(fields, "responder", "UNKNOWN"))
|
528
|
+
task_name = getattr(fields, "task_name", "root")
|
529
|
+
# TODO (CLAUDE) display sender_entity in parens right after responder,
|
530
|
+
# other than LLM, e.g. AGENT (USER)
|
531
|
+
sender_entity = str(getattr(fields, "sender_entity", ""))
|
532
|
+
tool = getattr(fields, "tool", "")
|
533
|
+
tool_type = getattr(fields, "tool_type", "")
|
534
|
+
content = getattr(fields, "content", "")
|
535
|
+
recipient = getattr(fields, "recipient", "")
|
536
|
+
|
537
|
+
# Determine CSS class based on responder
|
538
|
+
responder_upper = responder.upper()
|
539
|
+
if "USER" in responder_upper:
|
540
|
+
css_class = "user"
|
541
|
+
elif "LLM" in responder_upper:
|
542
|
+
css_class = "llm"
|
543
|
+
elif "AGENT" in responder_upper:
|
544
|
+
css_class = "agent"
|
545
|
+
elif "SYSTEM" in responder_upper:
|
546
|
+
css_class = "system"
|
547
|
+
else:
|
548
|
+
css_class = "other"
|
549
|
+
|
550
|
+
# Determine opacity class based on mark
|
551
|
+
mark = getattr(fields, "mark", "")
|
552
|
+
opacity_class = "important" if mark == "*" else "faded"
|
553
|
+
|
554
|
+
# Start building the entry
|
555
|
+
html_parts = [
|
556
|
+
f'<div class="entry {css_class} {opacity_class}" id="{entry_id}">'
|
557
|
+
]
|
558
|
+
|
559
|
+
# Build smart header
|
560
|
+
entity_parts = [] # Main header line with entity info
|
561
|
+
content_preview = "" # Second line with content preview
|
562
|
+
|
563
|
+
# Add task name if not root
|
564
|
+
if task_name and task_name != "root":
|
565
|
+
entity_parts.append(task_name)
|
566
|
+
|
567
|
+
# Handle different responder types
|
568
|
+
if "USER" in responder_upper:
|
569
|
+
# Add responder with sender_entity in parens if different
|
570
|
+
if sender_entity and sender_entity != responder:
|
571
|
+
entity_parts.append(f"USER ({sender_entity})")
|
572
|
+
else:
|
573
|
+
entity_parts.append("USER")
|
574
|
+
# Show user input preview on second line
|
575
|
+
if content:
|
576
|
+
preview = content.replace("\n", " ")[:60]
|
577
|
+
if len(content) > 60:
|
578
|
+
preview += "..."
|
579
|
+
content_preview = f'"{preview}"'
|
580
|
+
|
581
|
+
elif "LLM" in responder_upper:
|
582
|
+
# Get model info from instance - don't uppercase it
|
583
|
+
model_label = "LLM"
|
584
|
+
if self.model_info:
|
585
|
+
model_label = f"LLM ({self.model_info})"
|
586
|
+
|
587
|
+
if tool and tool_type:
|
588
|
+
# LLM making a tool call - don't uppercase tool names
|
589
|
+
entity_parts.append(f"{model_label} → {tool_type}[{tool}]")
|
590
|
+
else:
|
591
|
+
# LLM generating plain text response
|
592
|
+
entity_parts.append(model_label)
|
593
|
+
if content:
|
594
|
+
# Show first line or first 60 chars on second line
|
595
|
+
first_line = content.split("\n")[0].strip()
|
596
|
+
if first_line:
|
597
|
+
preview = first_line[:60]
|
598
|
+
if len(first_line) > 60:
|
599
|
+
preview += "..."
|
600
|
+
content_preview = f'"{preview}"'
|
601
|
+
|
602
|
+
elif "AGENT" in responder_upper:
|
603
|
+
# Add responder with sender_entity in parens if different
|
604
|
+
agent_label = "AGENT"
|
605
|
+
if sender_entity and sender_entity != responder:
|
606
|
+
agent_label = f"AGENT ({sender_entity})"
|
607
|
+
|
608
|
+
# Agent responding (usually tool handling)
|
609
|
+
if tool:
|
610
|
+
entity_parts.append(f"{agent_label}[{tool}]")
|
611
|
+
# Show tool result preview on second line if available
|
612
|
+
if content:
|
613
|
+
preview = content.replace("\n", " ")[:40]
|
614
|
+
if len(content) > 40:
|
615
|
+
preview += "..."
|
616
|
+
content_preview = f"→ {preview}"
|
617
|
+
else:
|
618
|
+
entity_parts.append(agent_label)
|
619
|
+
if content:
|
620
|
+
preview = content[:50]
|
621
|
+
if len(content) > 50:
|
622
|
+
preview += "..."
|
623
|
+
content_preview = f'"{preview}"'
|
624
|
+
|
625
|
+
elif "SYSTEM" in responder_upper:
|
626
|
+
entity_parts.append("SYSTEM")
|
627
|
+
if content:
|
628
|
+
preview = content[:50]
|
629
|
+
if len(content) > 50:
|
630
|
+
preview += "..."
|
631
|
+
content_preview = f'"{preview}"'
|
632
|
+
else:
|
633
|
+
# Other responder types (like Task)
|
634
|
+
entity_parts.append(responder)
|
635
|
+
|
636
|
+
# Add recipient info if present
|
637
|
+
if recipient:
|
638
|
+
entity_parts.append(f"→ {recipient}")
|
639
|
+
|
640
|
+
# Construct the two-line header
|
641
|
+
header_main = " ".join(entity_parts)
|
642
|
+
|
643
|
+
# Build the header HTML with toggle, mark, and main content on same line
|
644
|
+
header_html = '<span class="toggle">[+]</span> '
|
645
|
+
|
646
|
+
# Note: opacity_class already determined above
|
647
|
+
|
648
|
+
# Add the main header content
|
649
|
+
header_html += f'<span class="header-main">{html.escape(header_main)}</span>'
|
650
|
+
|
651
|
+
# Add preview on second line if present
|
652
|
+
if content_preview:
|
653
|
+
header_html += (
|
654
|
+
f'\n <div class="header-content">'
|
655
|
+
f"{html.escape(content_preview)}</div>"
|
656
|
+
)
|
657
|
+
|
658
|
+
# Add expandable header
|
659
|
+
html_parts.append(
|
660
|
+
f"""
|
661
|
+
<div class="entity-header" onclick="toggleEntry('{entry_id}')">
|
662
|
+
{header_html}
|
663
|
+
</div>
|
664
|
+
<div id="{entry_id}_content" class="entry-content collapsed">"""
|
665
|
+
)
|
666
|
+
|
667
|
+
# Add collapsible sections
|
668
|
+
|
669
|
+
# System messages (if any)
|
670
|
+
system_content = self._extract_system_content(fields)
|
671
|
+
if system_content:
|
672
|
+
for idx, (label, content) in enumerate(system_content):
|
673
|
+
section_id = f"{entry_id}_system_{idx}"
|
674
|
+
html_parts.append(
|
675
|
+
self._create_collapsible_section(section_id, label, content)
|
676
|
+
)
|
677
|
+
|
678
|
+
# Tool information
|
679
|
+
tool = getattr(fields, "tool", None)
|
680
|
+
# Only add tool section if tool exists and is not empty
|
681
|
+
if tool and tool.strip():
|
682
|
+
tool_html = self._format_tool_section(fields, entry_id)
|
683
|
+
html_parts.append(tool_html)
|
684
|
+
|
685
|
+
# Main content
|
686
|
+
content = getattr(fields, "content", "")
|
687
|
+
if content and not (
|
688
|
+
tool and tool.strip()
|
689
|
+
): # Don't duplicate content if it's a tool
|
690
|
+
html_parts.append(f'<div class="main-content">{html.escape(content)}</div>')
|
691
|
+
|
692
|
+
# Metadata (recipient, blocked)
|
693
|
+
metadata_parts = []
|
694
|
+
recipient = getattr(fields, "recipient", None)
|
695
|
+
if recipient:
|
696
|
+
metadata_parts.append(f"Recipient: {recipient}")
|
697
|
+
|
698
|
+
block = getattr(fields, "block", None)
|
699
|
+
if block:
|
700
|
+
metadata_parts.append(f"Blocked: {block}")
|
701
|
+
|
702
|
+
if metadata_parts:
|
703
|
+
html_parts.append(
|
704
|
+
f'<div class="metadata">{" | ".join(metadata_parts)}</div>'
|
705
|
+
)
|
706
|
+
|
707
|
+
# Close entry content div
|
708
|
+
html_parts.append("</div>") # Close entry-content
|
709
|
+
html_parts.append("</div>") # Close entry
|
710
|
+
return "\n".join(html_parts)
|
711
|
+
|
712
|
+
def _extract_system_content(self, fields: BaseModel) -> List[tuple[str, str]]:
|
713
|
+
"""Extract system-related content from fields.
|
714
|
+
|
715
|
+
Returns:
|
716
|
+
List of (label, content) tuples
|
717
|
+
"""
|
718
|
+
system_content = []
|
719
|
+
|
720
|
+
# Check for common system message patterns in content
|
721
|
+
content = getattr(fields, "content", "")
|
722
|
+
if content:
|
723
|
+
# Look for patterns like "[System Prompt]" or "System Reminder:"
|
724
|
+
if "[System Prompt]" in content or "System Prompt" in content:
|
725
|
+
system_content.append(("System Prompt", content))
|
726
|
+
elif "[System Reminder]" in content or "System Reminder" in content:
|
727
|
+
system_content.append(("System Reminder", content))
|
728
|
+
|
729
|
+
return system_content
|
730
|
+
|
731
|
+
def _create_collapsible_section(
|
732
|
+
self, section_id: str, label: str, content: str
|
733
|
+
) -> str:
|
734
|
+
"""Create a collapsible section.
|
735
|
+
|
736
|
+
Args:
|
737
|
+
section_id: Unique ID for the section
|
738
|
+
label: Label to display
|
739
|
+
content: Content to show when expanded
|
740
|
+
|
741
|
+
Returns:
|
742
|
+
HTML string for the collapsible section
|
743
|
+
"""
|
744
|
+
return f"""
|
745
|
+
<div class="collapsible collapsed" id="{section_id}">
|
746
|
+
<span class="toggle" onclick="toggle('{section_id}')">[+]</span> {label}
|
747
|
+
<div class="content">{html.escape(content)}</div>
|
748
|
+
</div>"""
|
749
|
+
|
750
|
+
def _format_tool_section(self, fields: BaseModel, entry_id: str) -> str:
|
751
|
+
"""Format tool-related information.
|
752
|
+
|
753
|
+
Args:
|
754
|
+
fields: ChatDocLoggerFields containing tool information
|
755
|
+
entry_id: Parent entry ID
|
756
|
+
|
757
|
+
Returns:
|
758
|
+
HTML string for the tool section
|
759
|
+
"""
|
760
|
+
tool = getattr(fields, "tool", "")
|
761
|
+
tool_type = getattr(fields, "tool_type", "")
|
762
|
+
content = getattr(fields, "content", "")
|
763
|
+
|
764
|
+
tool_id = f"{entry_id}_tool_{self.tool_counter}"
|
765
|
+
self.tool_counter += 1
|
766
|
+
|
767
|
+
# Try to parse content as JSON for better formatting
|
768
|
+
try:
|
769
|
+
if content.strip().startswith("{"):
|
770
|
+
content_dict = json.loads(content)
|
771
|
+
formatted_content = json.dumps(content_dict, indent=2)
|
772
|
+
content_html = (
|
773
|
+
f'<pre class="code-block">{html.escape(formatted_content)}</pre>'
|
774
|
+
)
|
775
|
+
else:
|
776
|
+
content_html = html.escape(content)
|
777
|
+
except Exception:
|
778
|
+
content_html = html.escape(content)
|
779
|
+
|
780
|
+
# Build tool section
|
781
|
+
tool_name = f"{tool_type}({tool})" if tool_type else tool
|
782
|
+
|
783
|
+
return f"""
|
784
|
+
<div class="tool-section">
|
785
|
+
<div class="collapsible collapsed" id="{tool_id}">
|
786
|
+
<span class="toggle" onclick="toggle('{tool_id}')">[+]</span>
|
787
|
+
<span class="tool-name">{html.escape(tool_name)}</span>
|
788
|
+
<div class="content">{content_html}</div>
|
789
|
+
</div>
|
790
|
+
</div>"""
|
791
|
+
|
792
|
+
def _append_to_file(self, content: str) -> None:
|
793
|
+
"""Append content to the HTML file.
|
794
|
+
|
795
|
+
Args:
|
796
|
+
content: HTML content to append
|
797
|
+
"""
|
798
|
+
try:
|
799
|
+
with open(self.file_path, "a", encoding="utf-8") as f:
|
800
|
+
f.write(content + "\n")
|
801
|
+
f.flush()
|
802
|
+
except Exception as e:
|
803
|
+
self.logger.error(f"Failed to append to file: {e}")
|
804
|
+
|
805
|
+
def close(self) -> None:
|
806
|
+
"""Close the HTML file with footer."""
|
807
|
+
footer = """
|
808
|
+
</div>
|
809
|
+
<script>
|
810
|
+
// Update message count
|
811
|
+
const header = document.querySelector('.header-line div:last-child');
|
812
|
+
if (header) {
|
813
|
+
const messageCount = document.querySelectorAll('.entry').length;
|
814
|
+
header.textContent = header.textContent.replace(
|
815
|
+
/\\d+ messages/, messageCount + ' messages'
|
816
|
+
);
|
817
|
+
}
|
818
|
+
</script>
|
819
|
+
</body>
|
820
|
+
</html>"""
|
821
|
+
try:
|
822
|
+
with open(self.file_path, "a", encoding="utf-8") as f:
|
823
|
+
f.write(footer)
|
824
|
+
except Exception as e:
|
825
|
+
self.logger.error(f"Failed to write HTML footer: {e}")
|
@@ -6,10 +6,10 @@ langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,78
|
|
6
6
|
langroid/agent/base.py,sha256=exiOhO0L1StZ8ziPBnunHYiKFvEnRyaLnMpBrc8tyMw,86263
|
7
7
|
langroid/agent/batch.py,sha256=wpE9RqCNDVDhAXkCB7wEqfCIEAi6qKcrhaZ-Zr9T4C0,21375
|
8
8
|
langroid/agent/chat_agent.py,sha256=H_6IgyIcyZ1SPUpagxVZcWVTQrQw0EJaCeTX3yiwREU,88954
|
9
|
-
langroid/agent/chat_document.py,sha256=
|
9
|
+
langroid/agent/chat_document.py,sha256=t18sfN7e-cxQVINHjIBIPhr37EJJe-1u7mugq10J9i8,19755
|
10
10
|
langroid/agent/done_sequence_parser.py,sha256=oUPzQCkkAo-5qos3ndSV47Lre7O_LoGWwTybjE9sCwc,4381
|
11
11
|
langroid/agent/openai_assistant.py,sha256=JkAcs02bIrgPNVvUWVR06VCthc5-ulla2QMBzux_q6o,34340
|
12
|
-
langroid/agent/task.py,sha256=
|
12
|
+
langroid/agent/task.py,sha256=DUv4Q_VqLtwMejzJPd9QREx04RCIkoqI5G1755cLy1E,105932
|
13
13
|
langroid/agent/tool_message.py,sha256=BhjP-_TfQ2tgxuY4Yo_JHLOwwt0mJ4BwjPnREvEY4vk,14744
|
14
14
|
langroid/agent/xml_tool_message.py,sha256=oeBKnJNoGaKdtz39XoWGMTNlVyXew2MWH5lgtYeh8wQ,15496
|
15
15
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -117,6 +117,7 @@ langroid/utils/configuration.py,sha256=ZkHHkEeWuS-o3_S4g0SE0wz-UK_of23NOWve1kpQi
|
|
117
117
|
langroid/utils/constants.py,sha256=CK09kda9bNDEhnwClq7ZTWZOh38guJlfcZ5hKUS1Ijo,1075
|
118
118
|
langroid/utils/git_utils.py,sha256=WnflJ3R3owhlD0LNdSJakcKhExcEehE1UW5jYVQl8JY,7955
|
119
119
|
langroid/utils/globals.py,sha256=Az9dOFqR6n9CoTYSqa2kLikQWS0oCQ9DFQIQAnG-2q8,1355
|
120
|
+
langroid/utils/html_logger.py,sha256=9FhSvg3-5wJp-wmuqykT3Ur8GHQa-BodVPUya-uyIrQ,27029
|
120
121
|
langroid/utils/logging.py,sha256=RgfmWRWe178rQSd0gFrrSizHzD0mG-SA5FR92kNZ9Gk,7280
|
121
122
|
langroid/utils/object_registry.py,sha256=iPz9GHzvmCeVoidB3JdAMEKcxJEqTdUr0otQEexDZ5s,2100
|
122
123
|
langroid/utils/pandas_utils.py,sha256=IaEtdy4IkIh6fjc7XXpczwjhgWodoGmJX50LxoYSEeI,7280
|
@@ -138,7 +139,7 @@ langroid/vector_store/pineconedb.py,sha256=otxXZNaBKb9f_H75HTaU3lMHiaR2NUp5MqwLZ
|
|
138
139
|
langroid/vector_store/postgres.py,sha256=wHPtIi2qM4fhO4pMQr95pz1ZCe7dTb2hxl4VYspGZoA,16104
|
139
140
|
langroid/vector_store/qdrantdb.py,sha256=ZYrT9mxoUCx_67Qzb5xnkWuFG12rfe30yAg4NgG2ueA,19168
|
140
141
|
langroid/vector_store/weaviatedb.py,sha256=Yn8pg139gOy3zkaPfoTbMXEEBCiLiYa1MU5d_3UA1K4,11847
|
141
|
-
langroid-0.
|
142
|
-
langroid-0.
|
143
|
-
langroid-0.
|
144
|
-
langroid-0.
|
142
|
+
langroid-0.57.0.dist-info/METADATA,sha256=y3GSqdrxEQMaV2iMTmioT9KkTGBIo4oHoPRQexgCXdw,65744
|
143
|
+
langroid-0.57.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
144
|
+
langroid-0.57.0.dist-info/licenses/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
145
|
+
langroid-0.57.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|