From 402e6cf7bf0010caebc9bb87204abe296aefd93b Mon Sep 17 00:00:00 2001 From: jaegeral Date: Fri, 31 Oct 2025 14:10:30 +0000 Subject: [PATCH 1/5] in case timeksetch is in debug mode, write the secgemini result json in /tmp/ --- .../providers/secgemini_log_analyzer_agent.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py index 137e64277a..5b12be27df 100644 --- a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py +++ b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py @@ -21,6 +21,8 @@ from typing import Any, Dict, Generator, Iterable, Optional from timesketch.lib.llms.providers import interface from timesketch.lib.llms.providers import manager +from flask import current_app +from datetime import datetime has_required_deps = True try: @@ -100,6 +102,7 @@ async def _run_async_stream(self, log_path, prompt): model=self.model, enable_logging=self.enable_logging ) self.session_id = self._session.id + # TODO: Could we check if the API key has logging enabled and if not ERR logger.info("Started new SecGemini session: '%s'", self._session.id) self._session.upload_and_attach_logs( log_path, custom_fields_mapping=self.custom_fields_mapping @@ -121,12 +124,31 @@ async def _run_async_stream(self, log_path, prompt): "log are expected. The client automatically reconnects during " "long-running analysis." ) + + full_response_content = [] async for response in self._session.stream(prompt): if ( response.message_type == MessageType.RESULT and response.actor == "summarization_agent" ): - yield response.content + content_chunk = response.content + full_response_content.append(content_chunk) + yield content_chunk + + if current_app.config.get("DEBUG"): + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_file_path = f"/tmp/secgemini_response_{timestamp}_{self.session_id}.log" + try: + with open(log_file_path, "w", encoding="utf-8") as f: + f.write("".join(full_response_content)) + logger.debug("SecGemini raw response saved to %s", log_file_path) + except IOError as e: + logger.error( + "Failed to write SecGemini debug log to %s: %s", + log_file_path, + e, + exc_info=True, + ) def generate_stream_from_logs( self, From bfbbcd093d93041da54ef30b8421636b75d2bd3b Mon Sep 17 00:00:00 2001 From: jaegeral Date: Fri, 31 Oct 2025 14:19:18 +0000 Subject: [PATCH 2/5] lint --- timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py index 5b12be27df..d2c2443fc6 100644 --- a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py +++ b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py @@ -19,10 +19,10 @@ import pathlib import tempfile from typing import Any, Dict, Generator, Iterable, Optional -from timesketch.lib.llms.providers import interface -from timesketch.lib.llms.providers import manager from flask import current_app from datetime import datetime +from timesketch.lib.llms.providers import interface +from timesketch.lib.llms.providers import manager has_required_deps = True try: From b0847adc5c0a6b5a30012950ccfb46f2a9345542 Mon Sep 17 00:00:00 2001 From: Alexander J <741037+jaegeral@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:22:20 +0100 Subject: [PATCH 3/5] Update timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py index d2c2443fc6..815768fa80 100644 --- a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py +++ b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py @@ -18,9 +18,11 @@ import asyncio import pathlib import tempfile +from datetime import datetime from typing import Any, Dict, Generator, Iterable, Optional + from flask import current_app -from datetime import datetime + from timesketch.lib.llms.providers import interface from timesketch.lib.llms.providers import manager From f4ac5e2d09af93a6b493707b599cb84aa9364575 Mon Sep 17 00:00:00 2001 From: jaegeral Date: Fri, 31 Oct 2025 14:24:25 +0000 Subject: [PATCH 4/5] improve --- .../lib/llms/providers/secgemini_log_analyzer_agent.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py index 815768fa80..a93f9fc747 100644 --- a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py +++ b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py @@ -139,12 +139,16 @@ async def _run_async_stream(self, log_path, prompt): if current_app.config.get("DEBUG"): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - log_file_path = f"/tmp/secgemini_response_{timestamp}_{self.session_id}.log" + log_filename = f"secgemini_response_{timestamp}_{self.session_id}.log" + log_file_path = os.path.join(tempfile.gettempdir(), log_filename) + flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL try: - with open(log_file_path, "w", encoding="utf-8") as f: + with os.fdopen( + os.open(log_file_path, flags, 0o600), "w", encoding="utf-8" + ) as f: f.write("".join(full_response_content)) logger.debug("SecGemini raw response saved to %s", log_file_path) - except IOError as e: + except (IOError, FileExistsError) as e: logger.error( "Failed to write SecGemini debug log to %s: %s", log_file_path, From 2089508c73b079fcaeef002625eb6d2853053f70 Mon Sep 17 00:00:00 2001 From: janosch Date: Wed, 5 Nov 2025 18:25:42 +0000 Subject: [PATCH 5/5] Fix recording of secgemini conversation in debug mode: * Stream conversation directly into a file to save memory * Ensure all responses are recorded and not just the last for debugging * Store data as json where possible for easier debugging --- .../providers/secgemini_log_analyzer_agent.py | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py index a93f9fc747..f8e0752242 100644 --- a/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py +++ b/timesketch/lib/llms/providers/secgemini_log_analyzer_agent.py @@ -92,6 +92,7 @@ async def _run_async_stream(self, log_path, prompt): 1. Creates a new SecGemini session. 2. Uploads the local log file to the session. 3. Streams the analysis results for the given prompt. + 4. If debugging is enabled, streams the raw sec-gemini response to a log. Args: log_path (Path): The local filesystem path to the JSONL log file. @@ -127,34 +128,58 @@ async def _run_async_stream(self, log_path, prompt): "long-running analysis." ) - full_response_content = [] - async for response in self._session.stream(prompt): - if ( - response.message_type == MessageType.RESULT - and response.actor == "summarization_agent" - ): - content_chunk = response.content - full_response_content.append(content_chunk) - yield content_chunk - + debug_log_file = None if current_app.config.get("DEBUG"): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_filename = f"secgemini_response_{timestamp}_{self.session_id}.log" log_file_path = os.path.join(tempfile.gettempdir(), log_filename) flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL try: - with os.fdopen( + debug_log_file = os.fdopen( os.open(log_file_path, flags, 0o600), "w", encoding="utf-8" - ) as f: - f.write("".join(full_response_content)) - logger.debug("SecGemini raw response saved to %s", log_file_path) + ) + logger.info( + "SecGemini raw response is being streamed to: %s", log_file_path + ) except (IOError, FileExistsError) as e: logger.error( - "Failed to write SecGemini debug log to %s: %s", + "Failed to create SecGemini debug log at %s: %s", log_file_path, e, exc_info=True, ) + debug_log_file = None + + try: + async for response in self._session.stream(prompt): + if debug_log_file: + try: + if hasattr(response, "to_json") and callable( + getattr(response, "to_json") + ): + json_bytes = response.to_json() + json_string = json_bytes.decode("utf-8") + debug_log_file.write(json_string + "\n") + else: + debug_log_file.write(str(response) + "\n") + debug_log_file.flush() + except IOError as e: + logger.error( + "Failed to write to SecGemini debug log: %s", + e, + exc_info=True, + ) + + if ( + response.message_type == MessageType.RESULT + and response.actor == "summarization_agent" + ): + content_chunk = response.content + yield content_chunk + finally: + if debug_log_file: + debug_log_file.close() + logger.info("Finished writing SecGemini debug log: %s", log_file_path) def generate_stream_from_logs( self,