langroid 0.56.18__tar.gz → 0.57.0__tar.gz

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.
Files changed (146) hide show
  1. {langroid-0.56.18 → langroid-0.57.0}/PKG-INFO +1 -1
  2. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/chat_agent.py +1 -1
  3. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/chat_document.py +67 -19
  4. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/task.py +96 -1
  5. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/openai_gpt.py +1 -1
  6. langroid-0.57.0/langroid/utils/html_logger.py +825 -0
  7. {langroid-0.56.18 → langroid-0.57.0}/pyproject.toml +1 -1
  8. {langroid-0.56.18 → langroid-0.57.0}/.gitignore +0 -0
  9. {langroid-0.56.18 → langroid-0.57.0}/LICENSE +0 -0
  10. {langroid-0.56.18 → langroid-0.57.0}/README.md +0 -0
  11. {langroid-0.56.18 → langroid-0.57.0}/langroid/__init__.py +0 -0
  12. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/__init__.py +0 -0
  13. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/base.py +0 -0
  14. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/batch.py +0 -0
  15. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/callbacks/__init__.py +0 -0
  16. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/callbacks/chainlit.py +0 -0
  17. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/done_sequence_parser.py +0 -0
  18. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/openai_assistant.py +0 -0
  19. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/__init__.py +0 -0
  20. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/arangodb/__init__.py +0 -0
  21. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/arangodb/arangodb_agent.py +0 -0
  22. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/arangodb/system_messages.py +0 -0
  23. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/arangodb/tools.py +0 -0
  24. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/arangodb/utils.py +0 -0
  25. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/doc_chat_agent.py +0 -0
  26. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/doc_chat_task.py +0 -0
  27. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
  28. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
  29. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
  30. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
  31. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
  32. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/lance_tools.py +0 -0
  33. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/neo4j/__init__.py +0 -0
  34. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
  35. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
  36. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/neo4j/system_messages.py +0 -0
  37. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/neo4j/tools.py +0 -0
  38. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
  39. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/retriever_agent.py +0 -0
  40. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/__init__.py +0 -0
  41. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
  42. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
  43. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
  44. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
  45. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
  46. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/sql/utils/tools.py +0 -0
  47. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/special/table_chat_agent.py +0 -0
  48. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tool_message.py +0 -0
  49. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/__init__.py +0 -0
  50. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
  51. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/exa_search_tool.py +0 -0
  52. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/file_tools.py +0 -0
  53. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/google_search_tool.py +0 -0
  54. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/mcp/__init__.py +0 -0
  55. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/mcp/decorators.py +0 -0
  56. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/mcp/fastmcp_client.py +0 -0
  57. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
  58. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/orchestration.py +0 -0
  59. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/recipient_tool.py +0 -0
  60. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/retrieval_tool.py +0 -0
  61. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/rewind_tool.py +0 -0
  62. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
  63. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/task_tool.py +0 -0
  64. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/tools/tavily_search_tool.py +0 -0
  65. {langroid-0.56.18 → langroid-0.57.0}/langroid/agent/xml_tool_message.py +0 -0
  66. {langroid-0.56.18 → langroid-0.57.0}/langroid/cachedb/__init__.py +0 -0
  67. {langroid-0.56.18 → langroid-0.57.0}/langroid/cachedb/base.py +0 -0
  68. {langroid-0.56.18 → langroid-0.57.0}/langroid/cachedb/redis_cachedb.py +0 -0
  69. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/__init__.py +0 -0
  70. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/base.py +0 -0
  71. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/models.py +0 -0
  72. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/protoc/__init__.py +0 -0
  73. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
  74. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
  75. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
  76. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
  77. {langroid-0.56.18 → langroid-0.57.0}/langroid/embedding_models/remote_embeds.py +0 -0
  78. {langroid-0.56.18 → langroid-0.57.0}/langroid/exceptions.py +0 -0
  79. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/__init__.py +0 -0
  80. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/azure_openai.py +0 -0
  81. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/base.py +0 -0
  82. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/client_cache.py +0 -0
  83. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/config.py +0 -0
  84. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/mock_lm.py +0 -0
  85. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/model_info.py +0 -0
  86. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
  87. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/prompt_formatter/base.py +0 -0
  88. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
  89. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
  90. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/provider_params.py +0 -0
  91. {langroid-0.56.18 → langroid-0.57.0}/langroid/language_models/utils.py +0 -0
  92. {langroid-0.56.18 → langroid-0.57.0}/langroid/mcp/__init__.py +0 -0
  93. {langroid-0.56.18 → langroid-0.57.0}/langroid/mcp/server/__init__.py +0 -0
  94. {langroid-0.56.18 → langroid-0.57.0}/langroid/mytypes.py +0 -0
  95. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/__init__.py +0 -0
  96. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/agent_chats.py +0 -0
  97. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/code_parser.py +0 -0
  98. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/document_parser.py +0 -0
  99. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/file_attachment.py +0 -0
  100. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/md_parser.py +0 -0
  101. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/para_sentence_split.py +0 -0
  102. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/parse_json.py +0 -0
  103. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/parser.py +0 -0
  104. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/pdf_utils.py +0 -0
  105. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/repo_loader.py +0 -0
  106. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/routing.py +0 -0
  107. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/search.py +0 -0
  108. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/spider.py +0 -0
  109. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/table_loader.py +0 -0
  110. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/url_loader.py +0 -0
  111. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/urls.py +0 -0
  112. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/utils.py +0 -0
  113. {langroid-0.56.18 → langroid-0.57.0}/langroid/parsing/web_search.py +0 -0
  114. {langroid-0.56.18 → langroid-0.57.0}/langroid/prompts/__init__.py +0 -0
  115. {langroid-0.56.18 → langroid-0.57.0}/langroid/prompts/dialog.py +0 -0
  116. {langroid-0.56.18 → langroid-0.57.0}/langroid/prompts/prompts_config.py +0 -0
  117. {langroid-0.56.18 → langroid-0.57.0}/langroid/prompts/templates.py +0 -0
  118. {langroid-0.56.18 → langroid-0.57.0}/langroid/py.typed +0 -0
  119. {langroid-0.56.18 → langroid-0.57.0}/langroid/pydantic_v1/__init__.py +0 -0
  120. {langroid-0.56.18 → langroid-0.57.0}/langroid/pydantic_v1/main.py +0 -0
  121. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/__init__.py +0 -0
  122. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/algorithms/__init__.py +0 -0
  123. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/algorithms/graph.py +0 -0
  124. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/configuration.py +0 -0
  125. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/constants.py +0 -0
  126. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/git_utils.py +0 -0
  127. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/globals.py +0 -0
  128. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/logging.py +0 -0
  129. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/object_registry.py +0 -0
  130. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/output/__init__.py +0 -0
  131. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/output/citations.py +0 -0
  132. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/output/printing.py +0 -0
  133. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/output/status.py +0 -0
  134. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/pandas_utils.py +0 -0
  135. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/pydantic_utils.py +0 -0
  136. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/system.py +0 -0
  137. {langroid-0.56.18 → langroid-0.57.0}/langroid/utils/types.py +0 -0
  138. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/__init__.py +0 -0
  139. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/base.py +0 -0
  140. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/chromadb.py +0 -0
  141. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/lancedb.py +0 -0
  142. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/meilisearch.py +0 -0
  143. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/pineconedb.py +0 -0
  144. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/postgres.py +0 -0
  145. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/qdrantdb.py +0 -0
  146. {langroid-0.56.18 → langroid-0.57.0}/langroid/vector_store/weaviatedb.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langroid
3
- Version: 0.56.18
3
+ Version: 0.57.0
4
4
  Summary: Harness LLMs with Multi-Agent Programming
5
5
  Author-email: Prasad Chalasani <pchalasani@gmail.com>
6
6
  License: MIT
@@ -1565,7 +1565,7 @@ class ChatAgent(Agent):
1565
1565
  - CHAT_HISTORY_BUFFER
1566
1566
  )
1567
1567
  if output_len > self.config.llm.min_output_tokens:
1568
- logger.warning(
1568
+ logger.debug(
1569
1569
  f"""
1570
1570
  Chat Model context length is {self.llm.chat_context_length()},
1571
1571
  but the current message history is {self.chat_num_tokens(hist)}
@@ -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
- oai_tools = (
221
- []
222
- if self.oai_tool_calls is None
223
- else [t for t in self.oai_tool_calls if t.function is not None]
224
- )
225
- if self.function_call is not None:
226
- tool_type = "FUNC"
227
- tool = self.function_call.name
228
- elif len(oai_tools) > 0:
229
- tool_type = "OAI_TOOL"
230
- tool = ",".join(t.function.name for t in oai_tools) # type: ignore
231
- else:
232
- try:
233
- json_tools = self.get_tool_names()
234
- except Exception:
235
- json_tools = []
236
- if json_tools != []:
237
- tool_type = "TOOL"
238
- tool = json_tools[0]
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"],
@@ -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
- self.log_message(Entity.USER, self.pending_message)
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],
@@ -1526,7 +1526,7 @@ class OpenAIGPT(LanguageModel):
1526
1526
  usage = response.get("usage")
1527
1527
  if not cached and not self.get_stream() and usage is not None:
1528
1528
  prompt_tokens = usage.get("prompt_tokens") or 0
1529
- prompt_tokens_details = usage.get("prompt_tokens_details", {})
1529
+ prompt_tokens_details = usage.get("prompt_tokens_details", {}) or {}
1530
1530
  cached_tokens = prompt_tokens_details.get("cached_tokens") or 0
1531
1531
  completion_tokens = usage.get("completion_tokens") or 0
1532
1532
  cost = self._cost_chat_model(