ss900371tw commited on
Commit
718ae12
·
verified ·
1 Parent(s): 1d58568

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +164 -198
src/streamlit_app.py CHANGED
@@ -40,23 +40,13 @@ st.title("🛡️ Foundation-Sec-1.1-8B-Instruct with FAISS RAG & Batch Analysis
40
  st.markdown("已啟用:**IndexFlatIP** + **L2 正規化** + **Hugging Face LLM**。上傳 JSON 執行批量分析,上傳其他檔案作為 RAG 參考庫。")
41
 
42
  # 設定模型 ID (替換為 Hugging Face 模型名稱)
43
- MODEL_ID = "openai/gpt-oss-20b"
44
-
45
  WINDOW_SIZE = 8
46
 
47
  # --- 側邊欄設定 ---
48
  with st.sidebar:
49
  st.header("⚙️ 設定")
50
 
51
- # === 新增 Hugging Face Token 輸入框 (重要) ===
52
- hf_token = st.text_input(
53
- "Hugging Face Access Token (必要)",
54
- type="password",
55
- key="hf_token_input",
56
- help=f"載入 {MODEL_ID} 等受限模型需要您的 Hugging Face 存取 Token。"
57
- )
58
- # ============================================
59
-
60
  # === 替換為 Hugging Face 模型名稱顯示 (移除 API Key 輸入) ===
61
  st.info(f"LLM 模型:**{MODEL_ID}** (Hugging Face Model)")
62
  st.warning("⚠️ **注意**: 8B 模型需要大量 RAM/VRAM 和算力。運行可能較慢或失敗。")
@@ -86,15 +76,11 @@ with st.sidebar:
86
  )
87
  st.markdown("此指令將對 JSON 檔案中的**每一個 Log 條目**執行一次獨立分析。")
88
 
89
- # 檢查 Token 和檔案是否都已準備好
90
- can_execute = json_uploaded_file and hf_token
91
-
92
- if st.button("🚀 執行批量分析", disabled=not can_execute):
93
- st.session_state.execute_batch_analysis = True
94
- elif not json_uploaded_file:
95
  st.info("請上傳 JSON 檔案以啟用批量分析按鈕。")
96
- elif not hf_token:
97
- st.info("請輸入 Hugging Face Access Token 以載入模型。")
98
 
99
  st.divider()
100
 
@@ -109,45 +95,38 @@ with st.sidebar:
109
  st.subheader("模型參數")
110
  system_prompt = st.text_area("System Prompt (LLM 使用)", value="You are a Senior Security Analyst. Be professional.", height=100)
111
  max_output_tokens = st.slider("Max Output Tokens", 128, 4096, 2048, 128)
112
- temperature = st.slider("Temperature", 0.0, 1.0, 0.1, 0.1)
113
  top_p = st.slider("Top P", 0.1, 1.0, 0.95, 0.05)
114
 
115
  st.divider()
116
  if st.button("🗑️ 清除所有紀錄"):
117
  for key in list(st.session_state.keys()):
118
- if key not in ['hf_token_input']: # 保留 Token 輸入
119
  del st.session_state[key]
120
  st.rerun()
121
 
122
- # --- 初始化 Hugging Face LLM Client (重大替換:使用 Token 和 CausalLM) ---
123
  @st.cache_resource
124
- def load_huggingface_llm(model_id, hf_token):
125
  if AutoModelForCausalLM is None:
126
  st.error("無法載入 Hugging Face 依賴,請安裝:pip install transformers torch accelerate bitsandbytes")
127
  return None
128
  try:
129
  # 使用量化 (4-bit) 減少記憶體消耗,這是運行 8B 模型的常見做法
130
- tokenizer = AutoTokenizer.from_pretrained(
131
- model_id,
132
- token=hf_token, # 傳遞 Token
133
- trust_remote_code=True
134
- )
135
-
136
- # 確保使用 AutoModelForCausalLM
137
  model = AutoModelForCausalLM.from_pretrained(
138
- model_id,
139
- token=hf_token, # 傳遞 Token
140
- torch_dtype=torch.bfloat16, # 建議使用 bfloat16 float16 減少記憶體
141
- device_map="auto" ,# 自動將模型分配到 GPU/CPU
142
- local_files_only=True
143
  )
144
-
145
  # 使用 pipeline 簡化呼叫
146
  llm_pipeline = pipeline(
147
  "text-generation",
148
  model=model,
149
  tokenizer=tokenizer,
150
- model_kwargs={"torch_dtype": torch.bfloat16}
151
  )
152
  st.success(f"Hugging Face 模型 **{model_id}** 載入成功。")
153
  return llm_pipeline
@@ -157,14 +136,12 @@ def load_huggingface_llm(model_id, hf_token):
157
 
158
  # 在 main 區塊外初始化 pipeline
159
  llm_pipeline = None
160
- if AutoModelForCausalLM is not None and hf_token: # 只有當 Token 存在時才嘗試載入
161
  with st.spinner(f"正在載入 LLM 模型: {MODEL_ID} (8B)... (可能需要數分鐘)"):
162
- llm_pipeline = load_huggingface_llm(MODEL_ID, hf_token) # 傳遞 Token
163
 
164
- if llm_pipeline is None and hf_token:
165
- st.warning("Hugging Face LLM 無法載入。請檢查依賴、環境資源或 Token 是否有效。")
166
- elif not hf_token:
167
- st.info("請在左側邊欄輸入 Hugging Face Access Token 以載入模型。")
168
  # =======================================================================
169
 
170
 
@@ -260,13 +237,13 @@ def faiss_cosine_search_all(vector_store, query, threshold):
260
  selected.sort(key=lambda x: x[1], reverse=True)
261
  return selected
262
 
263
- # === Hugging Face 生成單一 Log 分析回答 (核心批量處理函數) (重大替換:使用 ChatML) ===
264
  def generate_rag_response_hf_for_log(llm_pipeline, model_id, log_sequence_text, user_prompt, sys_prompt, vector_store, threshold, max_output_tokens, temperature, top_p):
265
  """
266
  使用 Hugging Face LLM 執行 RAG 增強的 Log 序列分析。
267
  """
268
  if llm_pipeline is None:
269
- return "ERROR: Hugging Face LLM Pipeline 未載入或 Token 無效。", ""
270
 
271
  context_text = ""
272
  # 1. RAG 檢索邏輯
@@ -290,26 +267,16 @@ Based on the provided LOG SEQUENCE and REFERENCE CONTEXT, you must analyze the *
290
  {log_sequence_text}
291
  === END LOG SEQUENCE ==="""
292
 
293
- # 3. 整合 System Prompt、RAG、和 Log 內容,使用 ChatML 格式
294
- user_message_content = (
295
- f"RAG & ANALYSIS INSTRUCTION:\n{rag_instruction}\n\n"
296
- f"LOG DATA:\n{log_content_section}\n\n"
297
- f"RESPONSE:"
298
- )
299
-
300
- messages = [
301
- {"role": "system", "content": sys_prompt},
302
- {"role": "user", "content": user_message_content}
303
- ]
304
-
305
- # 轉換為 Llama 3/ChatML 格式
306
- full_prompt = llm_pipeline.tokenizer.apply_chat_template(
307
- messages,
308
- tokenize=False,
309
- add_generation_prompt=True
310
  )
311
 
312
- # 4. 呼叫 Hugging Face Pipeline
313
  try:
314
  # Pipeline 參數設定
315
  response = llm_pipeline(
@@ -397,153 +364,152 @@ if st.session_state.execute_batch_analysis and 'json_data_for_batch' in st.sessi
397
  st.session_state.batch_results = []
398
 
399
  if llm_pipeline is None:
400
- st.error("Hugging Face LLM Pipeline 未載入或 Token 無效,無法執行批量分析。")
 
401
  st.session_state.execute_batch_analysis = False
402
-
403
- else: # 只有當 LLM 載入成功時才繼續
404
- data_to_process = st.session_state.json_data_for_batch
405
-
406
- # 提取 Log 列表的邏輯 (保持不變)
407
- logs_list = []
408
- if isinstance(data_to_process, list):
409
- logs_list = data_to_process
410
- elif isinstance(data_to_process, dict):
411
- if all(isinstance(v, (dict, str, list)) for v in data_to_process.values()):
412
- logs_list = list(data_to_process.values())
413
- elif 'alerts' in data_to_process and isinstance(data_to_process['alerts'], list):
414
- logs_list = data_to_process['alerts']
415
- elif 'logs' in data_to_process and isinstance(data_to_process['logs'], list):
416
- logs_list = data_to_process['logs']
417
- else:
418
- logs_list = [data_to_process]
419
  else:
420
  logs_list = [data_to_process]
 
 
 
 
 
 
 
 
 
421
 
422
- if logs_list:
423
- vs = st.session_state.get("vector_store", None)
424
- if vs:
425
- st.success("✅ RAG 知識庫已啟用並用於分析。")
426
- else:
427
- st.warning("⚠️ RAG 知識庫未載入,將單純執行 Log 分析。")
428
-
429
- # --- 新增:創建平移視窗序列 ---
 
 
 
430
 
431
- # 將所有 Log 轉換為 JSON 格式化字串列表,以便後續拼接
432
- formatted_logs = [json.dumps(log, indent=2, ensure_ascii=False) for log in logs_list]
433
 
434
- # 創建要分析的序列 (Sliding Window) 列表
435
- analysis_sequences = []
 
 
 
436
 
437
- for i in range(len(formatted_logs)):
438
- start_index = max(0, i - WINDOW_SIZE + 1)
439
- end_index = i + 1 # 終點為當前 Log
440
-
441
- current_window = formatted_logs[start_index:end_index]
442
-
443
- sequence_text = []
444
- for j, log_str in enumerate(current_window):
445
- is_target = " <<< TARGET LOG TO ANALYZE" if j == len(current_window) - 1 else ""
446
- # 使用 i-len(current_window)+j+1 來計算原始索引
447
- sequence_text.append(f"--- Log Index {i - len(current_window) + j + 1} ({len(current_window)-j} prior logs){is_target} ---\n{log_str}")
448
-
449
- analysis_sequences.append({
450
- "sequence_text": "\n\n".join(sequence_text),
451
- "target_log_id": i + 1, # 該序列的分析目標是原始列表中的第 i+1 條 Log
452
- "original_log_entry": logs_list[i]
453
- })
454
-
455
- total_sequences = len(analysis_sequences)
456
- if total_sequences < WINDOW_SIZE:
457
- st.warning(f"Log 總數 ({total_sequences}) 少於視窗大小 ({WINDOW_SIZE}),分析的結果可能較不準確。")
458
-
459
- # --- 執行序列分析 ---
460
- st.header(f"⚡ 批量分析執行中 (平移視窗 $N={WINDOW_SIZE}$)...")
461
- progress_bar = st.progress(0, text=f"準備處理 {total_sequences} 個序列...")
462
- results_container = st.container()
463
- full_report_chunks = ["## Cybersecurity Batch Analysis Report\n\n"]
464
 
465
- priority_keyword = "Criticality/Priority:"
 
 
466
 
467
- for i, seq_data in enumerate(analysis_sequences):
468
- log_id = seq_data["target_log_id"]
469
- progress_bar.progress((i + 1) / total_sequences, text=f"已處理 {i + 1}/{total_sequences} 個序列 (目標 Log #{log_id})...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
 
471
- try:
472
- # *** 替換為 Hugging Face 呼叫函數 ***
473
- response, retrieved_ctx = generate_rag_response_hf_for_log(
474
- llm_pipeline=llm_pipeline, # <--- 新的 LLM pipeline
475
- model_id=MODEL_ID,
476
- log_sequence_text=seq_data["sequence_text"],
477
- user_prompt=analysis_prompt,
478
- sys_prompt=system_prompt,
479
- vector_store=vs,
480
- threshold=similarity_threshold,
481
- max_output_tokens=max_output_tokens,
482
- temperature=temperature,
483
- top_p=top_p
484
- )
485
-
486
- # 儲存結果
487
- item = {
488
- "log_id": log_id,
489
- "log_content": seq_data["original_log_entry"], # 記錄原始 Log 條目
490
- "sequence_analyzed": seq_data["sequence_text"], # 記錄分析的序列
491
- "analysis_result": response,
492
- "context": retrieved_ctx
493
- }
494
- st.session_state.batch_results.append(item)
495
-
496
- # 結果顯示邏輯
497
- with results_container:
498
- st.subheader(f"Log/Alert #{item['log_id']} (序列分析完成)")
499
- with st.expander(f"序列內容 (包含 {len(seq_data['sequence_text'].split('--- Log Index'))-1} 條 Log)"):
500
- st.code(item["sequence_analyzed"], language='text')
501
-
502
- # 顏色控制:
503
- is_high_priority = False
504
- if 'criticality/priority:' in response.lower():
505
- try:
506
- # 嘗試從 LLM 回應中提取優先級
507
- priority_section = response.split('criticality/priority:')[1].split('\n')[0].strip()
508
- if 'high' in priority_section.lower() or 'medium' in priority_section.lower() or 'yes' in priority_section.lower():
509
- is_high_priority = True
510
- except IndexError:
511
- pass
512
-
513
- st.markdown(f"### 🤖 分析結果 (針對 Log #{log_id})")
514
- if is_high_priority:
515
- st.error(item['analysis_result'])
516
- else:
517
- st.info(item['analysis_result'])
518
-
519
- if item['context']:
520
- with st.expander("參考的 RAG 知識庫片段"):
521
- st.code(item['context'])
522
- st.markdown("---")
523
-
524
- # 報告 chunks
525
- log_content_str_for_report = json.dumps(item["log_content"], indent=2, ensure_ascii=False).replace("`", "\\`")
526
- full_report_chunks.append(f"---\n\n### Log/Alert #{item['log_id']} (序列分析)\n\n#### 分析的序列內容\n```\n{seq_data['sequence_text']}\n```\n\n#### LLM 分析結果\n{item['analysis_result']}\n")
527
 
528
- except Exception as e:
529
- error_message = f"ERROR: Log {log_id} 序列處理失敗: {e}"
530
- st.session_state.batch_results.append({
531
- "log_id": log_id,
532
- "log_content": seq_data["original_log_entry"],
533
- "sequence_analyzed": seq_data["sequence_text"],
534
- "analysis_result": error_message,
535
- "context": ""
536
- })
537
- with results_container:
538
- st.error(error_message)
 
 
 
 
539
 
540
- end_time = time.time()
541
- progress_bar.empty()
542
- st.success(f"批量分析完成!共處理 {total_sequences} 個 Log 序列,耗時 {end_time - start_time:.2f} 秒。")
543
- st.divider()
544
-
545
- else:
546
- st.error("無法從上傳的 JSON 檔案中提取 Log 列表或有效的 Log 條目。請檢查檔案結構。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
  # === 顯示結果 (歷史紀錄) (保持不變) ===
549
  if st.session_state.batch_results and not st.session_state.execute_batch_analysis:
 
40
  st.markdown("已啟用:**IndexFlatIP** + **L2 正規化** + **Hugging Face LLM**。上傳 JSON 執行批量分析,上傳其他檔案作為 RAG 參考庫。")
41
 
42
  # 設定模型 ID (替換為 Hugging Face 模型名稱)
43
+ MODEL_ID = "Qwen/Qwen3-VL-8B-Instruct"
 
44
  WINDOW_SIZE = 8
45
 
46
  # --- 側邊欄設定 ---
47
  with st.sidebar:
48
  st.header("⚙️ 設定")
49
 
 
 
 
 
 
 
 
 
 
50
  # === 替換為 Hugging Face 模型名稱顯示 (移除 API Key 輸入) ===
51
  st.info(f"LLM 模型:**{MODEL_ID}** (Hugging Face Model)")
52
  st.warning("⚠️ **注意**: 8B 模型需要大量 RAM/VRAM 和算力。運行可能較慢或失敗。")
 
76
  )
77
  st.markdown("此指令將對 JSON 檔案中的**每一個 Log 條目**執行一次獨立分析。")
78
 
79
+ if json_uploaded_file: # 移除 API Key 檢查
80
+ if st.button("🚀 執行批量分析"):
81
+ st.session_state.execute_batch_analysis = True
82
+ else:
 
 
83
  st.info("請上傳 JSON 檔案以啟用批量分析按鈕。")
 
 
84
 
85
  st.divider()
86
 
 
95
  st.subheader("模型參數")
96
  system_prompt = st.text_area("System Prompt (LLM 使用)", value="You are a Senior Security Analyst. Be professional.", height=100)
97
  max_output_tokens = st.slider("Max Output Tokens", 128, 4096, 2048, 128)
98
+ temperature = st.slider("Temperature", 0.0, 1.0, 0.1, 0.1)
99
  top_p = st.slider("Top P", 0.1, 1.0, 0.95, 0.05)
100
 
101
  st.divider()
102
  if st.button("🗑️ 清除所有紀錄"):
103
  for key in list(st.session_state.keys()):
104
+ if key not in []:
105
  del st.session_state[key]
106
  st.rerun()
107
 
108
+ # --- 初始化 Hugging Face LLM Client (重大替換) ---
109
  @st.cache_resource
110
+ def load_huggingface_llm(model_id):
111
  if AutoModelForCausalLM is None:
112
  st.error("無法載入 Hugging Face 依賴,請安裝:pip install transformers torch accelerate bitsandbytes")
113
  return None
114
  try:
115
  # 使用量化 (4-bit) 減少記憶體消耗,這是運行 8B 模型的常見做法
116
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
 
 
 
 
 
 
117
  model = AutoModelForCausalLM.from_pretrained(
118
+ model_id,
119
+ torch_dtype=torch.bfloat16 if torch.cuda.is_available() else None,
120
+ device_map="auto", # <--- accelerate 管理裝置
121
+ trust_remote_code=True,
122
+ # load_in_4bit=True # 如果需要 4-bit 量化
123
  )
 
124
  # 使用 pipeline 簡化呼叫
125
  llm_pipeline = pipeline(
126
  "text-generation",
127
  model=model,
128
  tokenizer=tokenizer,
129
+ # device=(0 if torch.cuda.is_available() else -1) # <--- **移除此參數**
130
  )
131
  st.success(f"Hugging Face 模型 **{model_id}** 載入成功。")
132
  return llm_pipeline
 
136
 
137
  # 在 main 區塊外初始化 pipeline
138
  llm_pipeline = None
139
+ if AutoModelForCausalLM is not None:
140
  with st.spinner(f"正在載入 LLM 模型: {MODEL_ID} (8B)... (可能需要數分鐘)"):
141
+ llm_pipeline = load_huggingface_llm(MODEL_ID)
142
 
143
+ if llm_pipeline is None:
144
+ st.warning("Hugging Face LLM 無法載入。請檢查依賴和環境資源。")
 
 
145
  # =======================================================================
146
 
147
 
 
237
  selected.sort(key=lambda x: x[1], reverse=True)
238
  return selected
239
 
240
+ # === Hugging Face 生成單一 Log 分析回答 (核心批量處理函數) (重大替換) ===
241
  def generate_rag_response_hf_for_log(llm_pipeline, model_id, log_sequence_text, user_prompt, sys_prompt, vector_store, threshold, max_output_tokens, temperature, top_p):
242
  """
243
  使用 Hugging Face LLM 執行 RAG 增強的 Log 序列分析。
244
  """
245
  if llm_pipeline is None:
246
+ return "ERROR: Hugging Face LLM Pipeline 未載入。", ""
247
 
248
  context_text = ""
249
  # 1. RAG 檢索邏輯
 
267
  {log_sequence_text}
268
  === END LOG SEQUENCE ==="""
269
 
270
+ # 整合 System Prompt、RAG、和 Log 內容
271
+ # 注意:fdtn-ai/Foundation-Sec-1.1-8B-Instruct 遵循 ChatML 格式,但此處使用簡化的 instruction-tuning 格式
272
+ full_prompt = (
273
+ f"**SYSTEM INSTRUCTION**: {sys_prompt}\n\n"
274
+ f"**RAG & ANALYSIS INSTRUCTION**:\n{rag_instruction}\n\n"
275
+ f"**LOG DATA**:\n{log_content_section}\n\n"
276
+ f"**RESPONSE**:"
 
 
 
 
 
 
 
 
 
 
277
  )
278
 
279
+ # 3. 呼叫 Hugging Face Pipeline
280
  try:
281
  # Pipeline 參數設定
282
  response = llm_pipeline(
 
364
  st.session_state.batch_results = []
365
 
366
  if llm_pipeline is None:
367
+ st.error("Hugging Face LLM Pipeline 未載入,請檢查依賴和環境資源,無法執行批量分析。")
368
+ # 由於這是一個 Streamlit App,我們不直接 st.stop(),讓使用者可以檢查設定
369
  st.session_state.execute_batch_analysis = False
370
+
371
+ data_to_process = st.session_state.json_data_for_batch
372
+
373
+ # 提取 Log 列表的邏輯 (保持不變)
374
+ logs_list = []
375
+ if isinstance(data_to_process, list):
376
+ logs_list = data_to_process
377
+ elif isinstance(data_to_process, dict):
378
+ if all(isinstance(v, (dict, str, list)) for v in data_to_process.values()):
379
+ logs_list = list(data_to_process.values())
380
+ elif 'alerts' in data_to_process and isinstance(data_to_process['alerts'], list):
381
+ logs_list = data_to_process['alerts']
382
+ elif 'logs' in data_to_process and isinstance(data_to_process['logs'], list):
383
+ logs_list = data_to_process['logs']
 
 
 
384
  else:
385
  logs_list = [data_to_process]
386
+ else:
387
+ logs_list = [data_to_process]
388
+
389
+ if logs_list:
390
+ vs = st.session_state.get("vector_store", None)
391
+ if vs:
392
+ st.success("✅ RAG 知識庫已啟用並用於分析。")
393
+ else:
394
+ st.warning("⚠️ RAG 知識庫未載入,將單純執行 Log 分析。")
395
 
396
+ # --- 新增:創建平移視窗序列 ---
397
+
398
+ # 將所有 Log 轉換為 JSON 格式化字串列表,以便後續拼接
399
+ formatted_logs = [json.dumps(log, indent=2, ensure_ascii=False) for log in logs_list]
400
+
401
+ # 創建要分析的序列 (Sliding Window) 列表
402
+ analysis_sequences = []
403
+
404
+ for i in range(len(formatted_logs)):
405
+ start_index = max(0, i - WINDOW_SIZE + 1)
406
+ end_index = i + 1 # 終點為當前 Log
407
 
408
+ current_window = formatted_logs[start_index:end_index]
 
409
 
410
+ sequence_text = []
411
+ for j, log_str in enumerate(current_window):
412
+ is_target = " <<< TARGET LOG TO ANALYZE" if j == len(current_window) - 1 else ""
413
+ # 使用 i-len(current_window)+j+1 來計算原始索引
414
+ sequence_text.append(f"--- Log Index {i - len(current_window) + j + 1} ({len(current_window)-j} prior logs){is_target} ---\n{log_str}")
415
 
416
+ analysis_sequences.append({
417
+ "sequence_text": "\n\n".join(sequence_text),
418
+ "target_log_id": i + 1, # 該序列的分析目標是原始列表中的第 i+1 條 Log
419
+ "original_log_entry": logs_list[i]
420
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
+ total_sequences = len(analysis_sequences)
423
+ if total_sequences < WINDOW_SIZE:
424
+ st.warning(f"Log 總數 ({total_sequences}) 少於視窗大小 ({WINDOW_SIZE}),分析的結果可能較不準確。")
425
 
426
+ # --- 執行序列分析 ---
427
+ st.header(f"⚡ 批量分析執行中 (平移視窗 $N={WINDOW_SIZE}$)...")
428
+ progress_bar = st.progress(0, text=f"準備處理 {total_sequences} 個序列...")
429
+ results_container = st.container()
430
+ full_report_chunks = ["## Cybersecurity Batch Analysis Report\n\n"]
431
+
432
+ priority_keyword = "Criticality/Priority:"
433
+
434
+ for i, seq_data in enumerate(analysis_sequences):
435
+ log_id = seq_data["target_log_id"]
436
+ progress_bar.progress((i + 1) / total_sequences, text=f"已處理 {i + 1}/{total_sequences} 個序列 (目標 Log #{log_id})...")
437
+
438
+ try:
439
+ # *** 替換為 Hugging Face 呼叫函數 ***
440
+ response, retrieved_ctx = generate_rag_response_hf_for_log(
441
+ llm_pipeline=llm_pipeline, # <--- 新的 LLM pipeline
442
+ model_id=MODEL_ID,
443
+ log_sequence_text=seq_data["sequence_text"],
444
+ user_prompt=analysis_prompt,
445
+ sys_prompt=system_prompt,
446
+ vector_store=vs,
447
+ threshold=similarity_threshold,
448
+ max_output_tokens=max_output_tokens,
449
+ temperature=temperature,
450
+ top_p=top_p
451
+ )
452
 
453
+ # 儲存結果
454
+ item = {
455
+ "log_id": log_id,
456
+ "log_content": seq_data["original_log_entry"], # 記錄原始 Log 條目
457
+ "sequence_analyzed": seq_data["sequence_text"], # 記錄分析的序列
458
+ "analysis_result": response,
459
+ "context": retrieved_ctx
460
+ }
461
+ st.session_state.batch_results.append(item)
462
+
463
+ # 結果顯示邏輯
464
+ with results_container:
465
+ st.subheader(f"Log/Alert #{item['log_id']} (序列分析完成)")
466
+ with st.expander(f"序列內容 (包含 {len(seq_data['sequence_text'].split('--- Log Index'))-1} 條 Log)"):
467
+ st.code(item["sequence_analyzed"], language='text')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
 
469
+ # 顏色控制:
470
+ is_high_priority = False
471
+ if 'criticality/priority:' in response.lower():
472
+ try:
473
+ priority_section = response.split('Criticality/Priority:')[1].split('\n')[0].strip()
474
+ if 'high' in priority_section.lower() or 'medium' in priority_section.lower() or 'yes' in priority_section.lower():
475
+ is_high_priority = True
476
+ except IndexError:
477
+ pass
478
+
479
+ st.markdown(f"### 🤖 分析結果 (針對 Log #{log_id})")
480
+ if is_high_priority:
481
+ st.error(item['analysis_result'])
482
+ else:
483
+ st.info(item['analysis_result'])
484
 
485
+ if item['context']:
486
+ with st.expander("參考的 RAG 知識庫片段"):
487
+ st.code(item['context'])
488
+ st.markdown("---")
489
+
490
+ # 報告 chunks
491
+ log_content_str_for_report = json.dumps(item["log_content"], indent=2, ensure_ascii=False).replace("`", "\\`")
492
+ full_report_chunks.append(f"---\n\n### Log/Alert #{item['log_id']} (序列分析)\n\n#### 分析的序列內容\n```\n{seq_data['sequence_text']}\n```\n\n#### LLM 分析結果\n{item['analysis_result']}\n")
493
+
494
+ except Exception as e:
495
+ error_message = f"ERROR: Log {log_id} 序列處理失敗: {e}"
496
+ st.session_state.batch_results.append({
497
+ "log_id": log_id,
498
+ "log_content": seq_data["original_log_entry"],
499
+ "sequence_analyzed": seq_data["sequence_text"],
500
+ "analysis_result": error_message,
501
+ "context": ""
502
+ })
503
+ with results_container:
504
+ st.error(error_message)
505
+
506
+ end_time = time.time()
507
+ progress_bar.empty()
508
+ st.success(f"批量分析完成!共處理 {total_sequences} 個 Log 序列,耗時 {end_time - start_time:.2f} 秒。")
509
+ st.divider()
510
+
511
+ else:
512
+ st.error("無法從上傳的 JSON 檔案中提取 Log 列表或有效的 Log 條目。請檢查檔案結構。")
513
 
514
  # === 顯示結果 (歷史紀錄) (保持不變) ===
515
  if st.session_state.batch_results and not st.session_state.execute_batch_analysis: