<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>紙魚ㄉ部落格</title>
<link>https://paperfishblog.netlify.app/llm.html</link>
<atom:link href="https://paperfishblog.netlify.app/llm.xml" rel="self" type="application/rss+xml"/>
<description>紀錄與 LLM 相關的知識與應用。</description>
<generator>quarto-1.7.32</generator>
<lastBuildDate>Thu, 06 Aug 2026 16:00:00 GMT</lastBuildDate>
<item>
  <title>不平衡資料下的小型LLM實驗.</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/poc-verify2/</link>
  <description><![CDATA[ 






<div data-ai-tag="2,7,8">

</div>
<script src="../../../asserts/box.js" defer=""></script>
<section id="上次的後續" class="level1">
<h1>上次的後續</h1>
<p>後來我斷斷續續又做了 3 次的實驗，每次實驗的手法都不同，唯一的共同點是我希望能夠驗證：LLM 可以做為 機器學習 (ML) 預測失準時的第二道防線。</p>
</section>
<section id="資料來源設計差異" class="level1">
<h1>資料來源設計差異</h1>
<p>跟上一次的資料相比，這次的資料來源還是從 Faker 套件生成，不同的是為了符合現實情境，我改成不平衡資料，而且加入更多的攻擊跟正常流量的樣態，讓隨機森林模型容易預測出 FN 。此外後期為了實驗設置的需要，我把生成資料改成正確標記 label，實驗的結果也以觀察 FN 的資料是否改善為中心。</p>
</section>
<section id="實驗-a-不做任何前處理直接比較不同情形下的給訊息策略" class="level1">
<h1>實驗 A: 不做任何前處理，直接比較不同情形下的給訊息策略</h1>
<p>這個實驗是基於上一次的 Poc 流程設計，比較哪一種策略最好，因此還沒調整故意標錯 lebel 的設定。分成以下：</p>
<ol type="1">
<li><p>由 ML 預測(based line)</p></li>
<li><p>由 LLM 預測</p></li>
<li><p>由 ML 先檢測，預測為 0 (正常)的資料交由 LLM 再驗證</p></li>
<li><p>由 ML 先檢測，預測為 1 (異常)的資料交由 LLM 再驗證</p></li>
<li><p>由 ML 先檢測，全部的資料交由 LLM 再驗證</p></li>
</ol>
<ul>
<li>註：ML 在這裡一樣是隨機森林模型。</li>
</ul>
<p>LLM 下達 Prompt</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"""</span></span>
<span id="cb1-2"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">You are an expert in network intrusion detection.</span></span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Your task is to classify network traffic as:</span></span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">0 = normal</span></span>
<span id="cb1-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1 = abnormal</span></span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">This is a high-risk classification task.</span></span>
<span id="cb1-10"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Missing an abnormal attack is more costly than generating a false positive.</span></span>
<span id="cb1-11"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Pay particular attention to features indicating malicious activity.</span></span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Traffic Features:</span></span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Minute:</span></span>
<span id="cb1-16"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-17"></span>
<span id="cb1-18"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Requests per minute:</span></span>
<span id="cb1-19"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-20"></span>
<span id="cb1-21"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Unique IPs:</span></span>
<span id="cb1-22"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'unique_ips'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-23"></span>
<span id="cb1-24"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">IP density index:</span></span>
<span id="cb1-25"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ip_density_index'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-26"></span>
<span id="cb1-27"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Average response time:</span></span>
<span id="cb1-28"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'avg_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-29"></span>
<span id="cb1-30"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Maximum response time:</span></span>
<span id="cb1-31"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'max_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-32"></span>
<span id="cb1-33"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">95th percentile response time:</span></span>
<span id="cb1-34"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-35"></span>
<span id="cb1-36"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">4xx ratio:</span></span>
<span id="cb1-37"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-38"></span>
<span id="cb1-39"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">5xx ratio:</span></span>
<span id="cb1-40"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-41"></span>
<span id="cb1-42"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Login failure rate:</span></span>
<span id="cb1-43"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'login_fail_rate'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-44"></span>
<span id="cb1-45"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Bot attack User-Agent ratio:</span></span>
<span id="cb1-46"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bot_attack_ua_ratio'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-47"></span>
<span id="cb1-48"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Raw logs:</span></span>
<span id="cb1-49"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'raw_logs'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-50"></span>
<span id="cb1-51"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Output exactly one digit:</span></span>
<span id="cb1-52"></span>
<span id="cb1-53"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-54"></span>
<span id="cb1-55"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">or</span></span>
<span id="cb1-56"></span>
<span id="cb1-57"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-58"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span></code></pre></div>
<section id="實驗結果重點" class="level2">
<h2 class="anchored" data-anchor-id="實驗結果重點">實驗結果重點</h2>
<ul>
<li><p>第 1 組在 1241 筆資料中有 92 筆資料為 FN， 7 筆為 FP</p></li>
<li><p>第 2 組(LLM only)沒有抓出這 92 筆FN，準確度看似比第 1 組高，但實際上只是有幾組沒有正面回答背強制判定為正常流量(0)</p></li>
<li><p>第 3 組(ML+ 正常資料給LLM)不僅沒抓出來FN，準確度還下降</p></li>
<li><p>第 4 組(ML + 異常資料給LLM)結果跟第 2 組一模一樣，沒有抓出這 92 筆FN</p></li>
<li><p>第 5 組(ML + 全給LLM)結果也跟第 2 組一模一樣，沒有抓出這 92 筆FN</p></li>
</ul>
<p>只能能說太悽慘了QQ</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
完整實驗數據 Output
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<section id="第-1-組" class="level6">
<h6 class="anchored" data-anchor-id="第-1-組">第 1 組</h6>
<pre><code>Accuracy : 0.9202256244963739
Precision: 0.0
Recall   : 0.0
F1 Score : 0.0

Confusion Matrix
[[1142    7]
 [  92    0]]

Classification Report
              precision    recall  f1-score   support

           0       0.93      0.99      0.96      1149
           1       0.00      0.00      0.00        92

    accuracy                           0.92      1241
   macro avg       0.46      0.50      0.48      1241
weighted avg       0.86      0.92      0.89      1241</code></pre>
</section>
<section id="第-2-組llm-only" class="level5">
<h5 class="anchored" data-anchor-id="第-2-組llm-only">第 2 組(LLM only)</h5>
<pre><code>===== LLM Prediction Result =====
Accuracy : 0.9258662369057212
Precision: 0.0
Recall   : 0.0
F1 Score : 0.0

Confusion Matrix
[[1149    0]
 [  92    0]]

Classification Report
              precision    recall  f1-score   support

           0       0.93      1.00      0.96      1149
           1       0.00      0.00      0.00        92

    accuracy                           0.93      1241
   macro avg       0.46      0.50      0.48      1241
weighted avg       0.86      0.93      0.89      1241

/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))</code></pre>
</section>
<section id="第-3-組ml-正常資料給llm再驗證" class="level5">
<h5 class="anchored" data-anchor-id="第-3-組ml-正常資料給llm再驗證">第 3 組(ML+ 正常資料給LLM再驗證)</h5>
<pre><code>Accuracy: 0.9186140209508461
Precision: 0.0
Recall   : 0.0
F1 Score : 0.0

Confusion Matrix
[[1140    9]
 [  92    0]]

Classification Report
              precision    recall  f1-score   support

           0       0.93      0.99      0.96      1149
           1       0.00      0.00      0.00        92

    accuracy                           0.92      1241
   macro avg       0.46      0.50      0.48      1241
weighted avg       0.86      0.92      0.89      1241</code></pre>
</section>
<section id="第-4-組ml-異常資料給llm" class="level5">
<h5 class="anchored" data-anchor-id="第-4-組ml-異常資料給llm">第 4 組(ML + 異常資料給LLM)</h5>
<pre><code>Accuracy: 0.9258662369057212
Precision: 0.0
Recall   : 0.0
F1 Score : 0.0

Confusion Matrix
[[1149    0]
 [  92    0]]

Classification Report
              precision    recall  f1-score   support

           0       0.93      1.00      0.96      1149
           1       0.00      0.00      0.00        92

    accuracy                           0.93      1241
   macro avg       0.46      0.50      0.48      1241
weighted avg       0.86      0.93      0.89      1241

/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))</code></pre>
</section>
<section id="第-5-組全給llm再驗證" class="level5">
<h5 class="anchored" data-anchor-id="第-5-組全給llm再驗證">第 5 組(全給LLM再驗證)</h5>
<pre><code>Total samples: 1241
Send to LLM: 304
Use RF directly: 937
LLM Predict: 100%|██████████| 304/304 [04:28&lt;00:00,  1.13it/s]
========== Result ==========
Accuracy: 0.9258662369057212
Precision: 0.0
Recall: 0.0
F1: 0.0

Confusion Matrix
[[1149    0]
 [  92    0]]

Classification Report
              precision    recall  f1-score   support

           0       0.93      1.00      0.96      1149
           1       0.00      0.00      0.00        92

    accuracy                           0.93      1241
   macro avg       0.46      0.50      0.48      1241
weighted avg       0.86      0.93      0.89      1241


========== Time ==========
RF time: 0.066s
LLM time: 268.241s
Total time: 268.307s

/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))
/root/miniconda3/envs/workenv/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, f"{metric.capitalize()} is", len(result))</code></pre>
</section>
</div>
</div>
</div>
</section>
</section>
<section id="實驗-b-嘗試用微調處理" class="level1">
<h1>實驗 B: 嘗試用微調處理</h1>
<p>一開始想說要提升 LLM 做為第二道防線的能力，用微調處理似乎會比較好，所以嘗試了幾次，以下是其中一個實驗結果，策略為</p>
<ol type="1">
<li><p>生成不平衡資料</p></li>
<li><p>資料增強</p></li>
<li><p>轉 json、分訓練即跟驗證集</p></li>
</ol>
<p>4.. 使用 LLaMA Factory 進行微調(對象：llama 3.2:3b)</p>
<ol start="5" type="1">
<li>檢測效果、觀察 FN 資料是否預測變好</li>
</ol>
<p>餵給 LLM 判斷的資料與給隨機森林的稍有不同，因為隨機森林只能從數值(例如流量，請求結果)判斷是否有流量異常，但是 LLM 可以綜合原始 log 判斷，因此餵給 LLM 的資料欄位除了原有的數值欄位以外，多了 <code>row_logs</code> 這個包含相關原始資料的欄位。因此整體欄位有 ：‘minute’、‘requests_per_min’、<code>unique_ips</code>、<code>ip_density_index</code>、<code>avg_response_time</code>、<code>max_response_time</code>、<code>p95_response_time</code>、<code>4xx_ratio</code>、<code>5xx_ratio</code>、<code>login_fail_rate</code>、<code>bot_attack_ua_ratio</code>、<code>raw_logs</code>跟<code>label</code>。</p>
<p>因為關注在 LLM 微調後是否改善，所以先不考慮隨機森林預測後的結果。資料增強考慮了幾種方法後，決定用最簡單的方式，先挑出異常資料複製，再針對餵給隨機森林的數值指標做擾動，使數值部分稍有差異。</p>
<p>資料增強前</p>
<p>label 0 11718 1 683</p>
<p>資料增強後</p>
<p>label 0 11718 1 3515</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Thinking
</div>
</div>
<div class="callout-body-container callout-body">
<p>後來想想，這樣其實不太好。因為即使我的擾動並沒有改動太多，數值跟原始資料並沒有對上。</p>
</div>
</div>
<section id="實驗結果重點-1" class="level2">
<h2 class="anchored" data-anchor-id="實驗結果重點-1">實驗結果重點</h2>
<p>LLM 的 prompt 設定如下</p>
<p>LLM 的微調設定如下</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-3-contents" aria-controls="callout-3" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
LLM 微調參數設定(訓練階段)
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-3" class="callout-3-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cutoff_len</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span></span>
<span id="cb7-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dataset</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> security_train</span></span>
<span id="cb7-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dataset_dir</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> data</span></span>
<span id="cb7-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ddp_timeout</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180000000</span></span>
<span id="cb7-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">do_train</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb7-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">finetuning_type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> lora</span></span>
<span id="cb7-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">flash_attn</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> auto</span></span>
<span id="cb7-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fp16</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb7-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gradient_accumulation_steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb7-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">include_num_input_tokens_seen</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb7-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">learning_rate</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0e-05</span></span>
<span id="cb7-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">logging_steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span>
<span id="cb7-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lora_alpha</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb7-14"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lora_dropout</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb7-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lora_rank</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb7-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lora_target</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> all</span></span>
<span id="cb7-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lr_scheduler_type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> cosine</span></span>
<span id="cb7-18"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max_grad_norm</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb7-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max_samples</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100000</span></span>
<span id="cb7-20"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">model_name_or_path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> meta-llama/Llama-3.2-3B</span></span>
<span id="cb7-21"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">num_train_epochs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb7-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">optim</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> adamw_torch</span></span>
<span id="cb7-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">output_dir</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> saves/llama3.2-security-test3</span></span>
<span id="cb7-24"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">packing</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">false</span></span>
<span id="cb7-25"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">per_device_train_batch_size</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span></span>
<span id="cb7-26"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot_loss</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb7-27"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">preprocessing_num_workers</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span>
<span id="cb7-28"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">report_to</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> none</span></span>
<span id="cb7-29"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">save_steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb7-30"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stage</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> sft</span></span>
<span id="cb7-31"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">template</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> default</span></span>
<span id="cb7-32"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">trust_remote_code</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb7-33"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">warmup_steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span></code></pre></div>
</div>
</div>
</div>
<p>看看訓練階段的成果</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://paperfishblog.netlify.app/posts/llm/poc-verify2/training_loss.png" class="img-fluid figure-img"></p>
<figcaption>訓練損失圖</figcaption>
</figure>
</div>
<p>跟之前玩其他資料集的結果相比訓練狀況並不是非常理想，再來是重頭戲驗證階段。</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-4-contents" aria-controls="callout-4" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
LLM 微調參數設定(驗證階段)
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-4" class="callout-4-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.batch_size</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span></span>
<span id="cb8-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.cutoff_len</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span></span>
<span id="cb8-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.dataset</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> security_eval</span></span>
<span id="cb8-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.dataset_dir</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> data</span></span>
<span id="cb8-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.max_new_tokens</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span></span>
<span id="cb8-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.max_samples</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'100000'</span></span>
<span id="cb8-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.output_dir</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> eval_2026-07-22-04-06-23-2</span></span>
<span id="cb8-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.predict</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">true</span></span>
<span id="cb8-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.temperature</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span></span>
<span id="cb8-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">eval.top_p</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span></span>
<span id="cb8-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.booster</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> auto</span></span>
<span id="cb8-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.checkpoint_path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb8-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> train_2026-07-22-04-06-23</span></span>
<span id="cb8-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.finetuning_type</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> lora</span></span>
<span id="cb8-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.model_name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Llama-3.2-3B</span></span>
<span id="cb8-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.quantization_bit</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> none</span></span>
<span id="cb8-18"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.quantization_method</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> bnb</span></span>
<span id="cb8-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.rope_scaling</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> none</span></span>
<span id="cb8-20"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">top.template</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> default</span></span></code></pre></div>
</div>
</div>
</div>
<p>分類結果另外寫了程式來驗證，比較好分辨</p>
<pre><code>[[2276  166]
 [ 536   31]]
              precision    recall  f1-score   support

      normal       0.81      0.93      0.87      2442
      attack       0.16      0.05      0.08       567

    accuracy                           0.77      3009
   macro avg       0.48      0.49      0.47      3009
weighted avg       0.69      0.77      0.72      3009</code></pre>
<p>536 筆異常資料被判定正常，太慘了TT。</p>
<p>跟 AI 討論了下為什麼訓練結果如此糟糕，推測原因可能是以下這些造成：</p>
<ol type="1">
<li><p>加了 <code>row_logs</code> 後的 prompt太長，超出 context windows範圍。</p></li>
<li><p>要 LLM 生成數字(0 或 1)，但 LLM 的強項是生成文字(normal/attack)。</p></li>
<li><p>雖然我在 prompt 中要求 LLM 在 label 產出數字(0 或 1)，但對 LLM 來說它不明白這些數字的意義是分類答案，因此產出無效答案(根本不知道要分類)。</p></li>
<li><p><code>row_logs</code>這個欄位沒有處理好，正確答案混進去了，不過因為 context windows 爆掉，影響反而較小。</p></li>
</ol>
</section>
</section>
<section id="實驗-c-直接修改流程觀察生成效果" class="level1">
<h1>實驗 C: 直接修改流程觀察生成效果</h1>
<p>仔細想想，前面的實驗都沒有完全發揮 LLM 的潛力，所以又重新設計了一次流程：</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
A["Raw Logs"]--&gt;B["Context Builder"]--&gt;C["Evidence"]--&gt;D["LLM"]--&gt;E["Reasoning"]
A --&gt; O["features.csv"] --&gt; M["RF Forecasting"]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>其中 Context Builder 是要把原始 log 資料另外轉成餵給 LLM 的資訊，跟前面單純只是數值特徵+原始 row logs 的形式不同，它扮演的腳色比較像是第一輪摘要，減少 context window 上限不足的壓力。大致上像這樣：</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A[Raw HTTP Logs]
    B[Split by Minute]
    C[EventContextBuilder]
    H[LLM]

    A --&gt; B
    B --&gt; C

    C --&gt; H

</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>其中 <code>EventContextBuilder</code>生成 timeline 相關資訊</p>
<ul>
<li><code>traffic_summary</code>：基本等同於餵給隨機森林的數值特徵，正確答案拿掉。</li>
<li><code>behavior_patterns</code>：將大量 log 壓縮成自然語言，避免 context windows 爆掉。</li>
<li><code>timeline</code>：按時間排序</li>
<li><code>representative_logs</code>：部分原始 log。</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-5-contents" aria-controls="callout-5" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Context Builder 程式( <code>builder.py</code> )
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-5" class="callout-5-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="cb10-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter, defaultdict</span>
<span id="cb10-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> datetime <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> datetime</span>
<span id="cb10-4"></span>
<span id="cb10-5"></span>
<span id="cb10-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> EventContextBuilder:</span>
<span id="cb10-7"></span>
<span id="cb10-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb10-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Convert raw HTTP logs into LLM-friendly security evidence.</span></span>
<span id="cb10-10"></span>
<span id="cb10-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    IMPORTANT:</span></span>
<span id="cb10-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    - No label</span></span>
<span id="cb10-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    - No attack prediction</span></span>
<span id="cb10-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    - No risk score</span></span>
<span id="cb10-15"></span>
<span id="cb10-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Only summarize observable evidence.</span></span>
<span id="cb10-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb10-18"></span>
<span id="cb10-19"></span>
<span id="cb10-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>):</span>
<span id="cb10-21">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sample_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sample_size</span>
<span id="cb10-22"></span>
<span id="cb10-23"></span>
<span id="cb10-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> parse_minute(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, timestamp):</span>
<span id="cb10-25"></span>
<span id="cb10-26">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> datetime.strptime(</span>
<span id="cb10-27">            timestamp,</span>
<span id="cb10-28">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%Y-%m-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> %H:%M:%S.</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%f</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb10-29">        ).strftime(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%Y-%m-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> %H:%M"</span>)</span>
<span id="cb10-30"></span>
<span id="cb10-31"></span>
<span id="cb10-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, events):</span>
<span id="cb10-33"></span>
<span id="cb10-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> events:</span>
<span id="cb10-35">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {}</span>
<span id="cb10-36"></span>
<span id="cb10-37"></span>
<span id="cb10-38">        context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb10-39"></span>
<span id="cb10-40">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time_window"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.parse_minute(</span>
<span id="cb10-41">                events[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>]</span>
<span id="cb10-42">            ),</span>
<span id="cb10-43"></span>
<span id="cb10-44">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"traffic_summary"</span>:</span>
<span id="cb10-45">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.build_summary(events),</span>
<span id="cb10-46"></span>
<span id="cb10-47"></span>
<span id="cb10-48">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"behavior_patterns"</span>:</span>
<span id="cb10-49">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.extract_patterns(events),</span>
<span id="cb10-50"></span>
<span id="cb10-51"></span>
<span id="cb10-52">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timeline"</span>:</span>
<span id="cb10-53">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.build_timeline(events),</span>
<span id="cb10-54"></span>
<span id="cb10-55"></span>
<span id="cb10-56">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"representative_logs"</span>:</span>
<span id="cb10-57">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sample_logs(events)</span>
<span id="cb10-58"></span>
<span id="cb10-59">        }</span>
<span id="cb10-60"></span>
<span id="cb10-61"></span>
<span id="cb10-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> context</span>
<span id="cb10-63"></span>
<span id="cb10-64"></span>
<span id="cb10-65"></span>
<span id="cb10-66">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-67">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 基本統計</span></span>
<span id="cb10-68">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-69"></span>
<span id="cb10-70">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_summary(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, events):</span>
<span id="cb10-71"></span>
<span id="cb10-72">        ips <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-73">            e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>]</span>
<span id="cb10-74">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-75">        ]</span>
<span id="cb10-76"></span>
<span id="cb10-77">        uris <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-78">            e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>]</span>
<span id="cb10-79">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-80">        ]</span>
<span id="cb10-81"></span>
<span id="cb10-82">        status <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-83">            e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>]</span>
<span id="cb10-84">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-85">        ]</span>
<span id="cb10-86"></span>
<span id="cb10-87">        ua <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-88">            e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_agent"</span>]</span>
<span id="cb10-89">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-90">        ]</span>
<span id="cb10-91"></span>
<span id="cb10-92"></span>
<span id="cb10-93">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb10-94"></span>
<span id="cb10-95">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_requests"</span>:</span>
<span id="cb10-96">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(events),</span>
<span id="cb10-97"></span>
<span id="cb10-98"></span>
<span id="cb10-99">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"unique_ips"</span>:</span>
<span id="cb10-100">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(ips)),</span>
<span id="cb10-101"></span>
<span id="cb10-102"></span>
<span id="cb10-103">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top_uris"</span>:</span>
<span id="cb10-104">                Counter(uris).most_common(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>),</span>
<span id="cb10-105"></span>
<span id="cb10-106"></span>
<span id="cb10-107">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_distribution"</span>:</span>
<span id="cb10-108">                Counter(status),</span>
<span id="cb10-109"></span>
<span id="cb10-110"></span>
<span id="cb10-111">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"top_user_agents"</span>:</span>
<span id="cb10-112">                Counter(ua).most_common(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>),</span>
<span id="cb10-113"></span>
<span id="cb10-114"></span>
<span id="cb10-115">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"average_response_time"</span>:</span>
<span id="cb10-116"></span>
<span id="cb10-117">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(</span>
<span id="cb10-118">                    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(</span>
<span id="cb10-119">                        e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"response_time_ms"</span>]</span>
<span id="cb10-120">                        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-121">                    )</span>
<span id="cb10-122">                    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span></span>
<span id="cb10-123">                    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(events),</span>
<span id="cb10-124">                    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb10-125">                )</span>
<span id="cb10-126"></span>
<span id="cb10-127">        }</span>
<span id="cb10-128"></span>
<span id="cb10-129"></span>
<span id="cb10-130"></span>
<span id="cb10-131">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-132">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 找行為模式</span></span>
<span id="cb10-133">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 注意：</span></span>
<span id="cb10-134">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這裡只描述，不判斷攻擊</span></span>
<span id="cb10-135">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-136"></span>
<span id="cb10-137">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> extract_patterns(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, events):</span>
<span id="cb10-138"></span>
<span id="cb10-139">        patterns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb10-140"></span>
<span id="cb10-141"></span>
<span id="cb10-142">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Login 行為</span></span>
<span id="cb10-143"></span>
<span id="cb10-144">        login_events <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-145"></span>
<span id="cb10-146">            e <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-147"></span>
<span id="cb10-148">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"login"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>]</span>
<span id="cb10-149"></span>
<span id="cb10-150">        ]</span>
<span id="cb10-151"></span>
<span id="cb10-152"></span>
<span id="cb10-153">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(login_events) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb10-154"></span>
<span id="cb10-155">            patterns.append(</span>
<span id="cb10-156">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(login_events)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> requests targeted login-related endpoints"</span></span>
<span id="cb10-157">            )</span>
<span id="cb10-158"></span>
<span id="cb10-159"></span>
<span id="cb10-160">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 401比例</span></span>
<span id="cb10-161"></span>
<span id="cb10-162">        fail_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(</span>
<span id="cb10-163"></span>
<span id="cb10-164">            <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-165"></span>
<span id="cb10-166">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">401</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">403</span>]</span>
<span id="cb10-167"></span>
<span id="cb10-168">        )</span>
<span id="cb10-169"></span>
<span id="cb10-170"></span>
<span id="cb10-171">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> fail_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb10-172"></span>
<span id="cb10-173">            patterns.append(</span>
<span id="cb10-174">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>fail_count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> requests returned authentication-related errors"</span></span>
<span id="cb10-175">            )</span>
<span id="cb10-176"></span>
<span id="cb10-177"></span>
<span id="cb10-178">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 同UA多IP</span></span>
<span id="cb10-179"></span>
<span id="cb10-180">        ua_map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>defaultdict(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>)</span>
<span id="cb10-181"></span>
<span id="cb10-182"></span>
<span id="cb10-183">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events:</span>
<span id="cb10-184"></span>
<span id="cb10-185">            ua_map[e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_agent"</span>]].add(</span>
<span id="cb10-186">                e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>]</span>
<span id="cb10-187">            )</span>
<span id="cb10-188"></span>
<span id="cb10-189"></span>
<span id="cb10-190">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> ua, ips <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ua_map.items():</span>
<span id="cb10-191"></span>
<span id="cb10-192">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ips)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>:</span>
<span id="cb10-193"></span>
<span id="cb10-194">                patterns.append(</span>
<span id="cb10-195">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"One user-agent appeared across "</span></span>
<span id="cb10-196">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ips)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> different IP addresses"</span></span>
<span id="cb10-197">                )</span>
<span id="cb10-198"></span>
<span id="cb10-199">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb10-200"></span>
<span id="cb10-201"></span>
<span id="cb10-202"></span>
<span id="cb10-203">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># URI diversity</span></span>
<span id="cb10-204"></span>
<span id="cb10-205">        unique_uri<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(</span>
<span id="cb10-206">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(</span>
<span id="cb10-207">                e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>]</span>
<span id="cb10-208">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-209">            )</span>
<span id="cb10-210">        )</span>
<span id="cb10-211"></span>
<span id="cb10-212"></span>
<span id="cb10-213">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> unique_uri<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>:</span>
<span id="cb10-214"></span>
<span id="cb10-215">            patterns.append(</span>
<span id="cb10-216">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"High URI diversity detected (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>unique_uri<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> unique paths)"</span></span>
<span id="cb10-217">            )</span>
<span id="cb10-218"></span>
<span id="cb10-219"></span>
<span id="cb10-220">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> patterns</span>
<span id="cb10-221"></span>
<span id="cb10-222"></span>
<span id="cb10-223"></span>
<span id="cb10-224">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-225">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 時序摘要</span></span>
<span id="cb10-226">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-227"></span>
<span id="cb10-228">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_timeline(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, events):</span>
<span id="cb10-229"></span>
<span id="cb10-230">        timeline<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb10-231"></span>
<span id="cb10-232"></span>
<span id="cb10-233">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(</span>
<span id="cb10-234">            events,</span>
<span id="cb10-235">            key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x:x[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>]</span>
<span id="cb10-236">        )[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>]:</span>
<span id="cb10-237"></span>
<span id="cb10-238"></span>
<span id="cb10-239">            timeline.append({</span>
<span id="cb10-240"></span>
<span id="cb10-241">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>:</span>
<span id="cb10-242">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>],</span>
<span id="cb10-243"></span>
<span id="cb10-244">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>:</span>
<span id="cb10-245">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>],</span>
<span id="cb10-246"></span>
<span id="cb10-247">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>:</span>
<span id="cb10-248">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>],</span>
<span id="cb10-249"></span>
<span id="cb10-250">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status"</span>:</span>
<span id="cb10-251">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>]</span>
<span id="cb10-252"></span>
<span id="cb10-253">            })</span>
<span id="cb10-254"></span>
<span id="cb10-255"></span>
<span id="cb10-256">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> timeline</span>
<span id="cb10-257"></span>
<span id="cb10-258"></span>
<span id="cb10-259"></span>
<span id="cb10-260">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-261">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 取代表事件</span></span>
<span id="cb10-262">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 不送全部log給LLM</span></span>
<span id="cb10-263">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -----------------------------------------</span></span>
<span id="cb10-264"></span>
<span id="cb10-265">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sample_logs(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, events):</span>
<span id="cb10-266"></span>
<span id="cb10-267"></span>
<span id="cb10-268">        selected<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb10-269"></span>
<span id="cb10-270"></span>
<span id="cb10-271">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 優先取錯誤狀態</span></span>
<span id="cb10-272"></span>
<span id="cb10-273">        error_logs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb10-274"></span>
<span id="cb10-275">            e <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> events</span>
<span id="cb10-276"></span>
<span id="cb10-277">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span></span>
<span id="cb10-278"></span>
<span id="cb10-279">        ]</span>
<span id="cb10-280"></span>
<span id="cb10-281"></span>
<span id="cb10-282">        selected.extend(</span>
<span id="cb10-283">            error_logs[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>]</span>
<span id="cb10-284">        )</span>
<span id="cb10-285"></span>
<span id="cb10-286"></span>
<span id="cb10-287">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 補正常案例</span></span>
<span id="cb10-288"></span>
<span id="cb10-289">        remaining<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.sample_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(selected)</span>
<span id="cb10-290"></span>
<span id="cb10-291"></span>
<span id="cb10-292">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> remaining<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb10-293"></span>
<span id="cb10-294">            selected.extend(</span>
<span id="cb10-295">                events[:remaining]</span>
<span id="cb10-296">            )</span>
<span id="cb10-297"></span>
<span id="cb10-298"></span>
<span id="cb10-299">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 移除label</span></span>
<span id="cb10-300"></span>
<span id="cb10-301">        clean<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[]</span>
<span id="cb10-302"></span>
<span id="cb10-303"></span>
<span id="cb10-304">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> e <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> selected:</span>
<span id="cb10-305"></span>
<span id="cb10-306">            clean.append({</span>
<span id="cb10-307"></span>
<span id="cb10-308">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>:</span>
<span id="cb10-309">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>],</span>
<span id="cb10-310"></span>
<span id="cb10-311">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>:</span>
<span id="cb10-312">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ip"</span>],</span>
<span id="cb10-313"></span>
<span id="cb10-314">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>:</span>
<span id="cb10-315">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uri"</span>],</span>
<span id="cb10-316"></span>
<span id="cb10-317">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>:</span>
<span id="cb10-318">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status_code"</span>],</span>
<span id="cb10-319"></span>
<span id="cb10-320">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"response_time_ms"</span>:</span>
<span id="cb10-321">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"response_time_ms"</span>],</span>
<span id="cb10-322"></span>
<span id="cb10-323">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_agent"</span>:</span>
<span id="cb10-324">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_agent"</span>],</span>
<span id="cb10-325"></span>
<span id="cb10-326">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"method"</span>:</span>
<span id="cb10-327">                    e[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"method"</span>]</span>
<span id="cb10-328"></span>
<span id="cb10-329">            })</span>
<span id="cb10-330"></span>
<span id="cb10-331"></span>
<span id="cb10-332">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> clean</span>
<span id="cb10-333"></span>
<span id="cb10-334"></span>
<span id="cb10-335"></span>
<span id="cb10-336"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb10-337"></span>
<span id="cb10-338"></span>
<span id="cb10-339">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(</span>
<span id="cb10-340">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"raw_traffic.json"</span>,</span>
<span id="cb10-341">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>,</span>
<span id="cb10-342">        encoding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span></span>
<span id="cb10-343">    ) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb10-344"></span>
<span id="cb10-345">        logs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>json.load(f)</span>
<span id="cb10-346"></span>
<span id="cb10-347"></span>
<span id="cb10-348"></span>
<span id="cb10-349">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 測試第一分鐘</span></span>
<span id="cb10-350"></span>
<span id="cb10-351">    minute<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>logs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>][:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>]</span>
<span id="cb10-352"></span>
<span id="cb10-353"></span>
<span id="cb10-354">    events<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb10-355"></span>
<span id="cb10-356">        x <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> x <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> logs</span>
<span id="cb10-357"></span>
<span id="cb10-358">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> x[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"timestamp"</span>].startswith(minute)</span>
<span id="cb10-359"></span>
<span id="cb10-360">    ]</span>
<span id="cb10-361"></span>
<span id="cb10-362"></span>
<span id="cb10-363">    builder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>EventContextBuilder()</span>
<span id="cb10-364"></span>
<span id="cb10-365"></span>
<span id="cb10-366">    context<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>builder.build(events)</span>
<span id="cb10-367"></span>
<span id="cb10-368"></span>
<span id="cb10-369">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb10-370">        json.dumps(</span>
<span id="cb10-371">            context,</span>
<span id="cb10-372">            indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb10-373">            ensure_ascii<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb10-374">        )</span>
<span id="cb10-375">    )</span></code></pre></div>
</div>
</div>
</div>
<section id="不做調整的前提下" class="level3">
<h3 class="anchored" data-anchor-id="不做調整的前提下">不做調整的前提下</h3>
<p>RF 模型在訓練集效果</p>
<pre><code>Classification Report
              precision    recall  f1-score   support

           0       0.98      1.00      0.99      2818
           1       1.00      0.11      0.20        63

    accuracy                           0.98      2881
   macro avg       0.99      0.56      0.60      2881
weighted avg       0.98      0.98      0.97      2881

ROC-AUC: 0.753221918055133
PR-AUC: 0.19574386687274742
Confusion Matrix
[[2818    0]
 [  56    7]]</code></pre>
<p>也就是說</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th>模型判正常</th>
<th>模型判攻擊</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>正常流量</td>
<td>2818 (TN)</td>
<td>0 (FP)</td>
</tr>
<tr class="even">
<td>攻擊流量</td>
<td>56 (FN)</td>
<td>7 (TP)</td>
</tr>
</tbody>
</table>
<p>雖然準確率在模擬階段高達 98%，但是漏報的筆數過多( 56 筆)，ROC-AUC 跟 PR-AUC 不過顯示模型還是有學到特徵，但這不是這次實驗的重點。</p>
<p>這 50 幾筆的 FN 資料會另外轉成 csv 供 LLM 分析。</p>
</section>
<section id="lallma-3.23b-結果" class="level3">
<h3 class="anchored" data-anchor-id="lallma-3.23b-結果">Lallma 3.2:3B 結果</h3>
<p>回報資料範例</p>
<pre><code>
{
  "risk_level": "LOW",
  "rf_concern": false,
  "evidence": [
    {
      "type": "login failure ratio",
      "value": 0.85
    },
    {
      "type": "user agent",
      "value": "Mozilla/5.0 (Windows; U; Windows NT 6.2) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/4.1 Safari/534.29.5"
    }
  ],
  "analysis": "The login failure ratio of 0.85 is abnormally high, indicating a potential security issue. However, the user agent string suggests that it may be a legitimate browser. Therefore, further investigation is required to determine the cause of the high login failure rate.",
  "recommendation": "Investigate the cause of the high login failure rate and implement measures to prevent brute-force attacks."
}</code></pre>
<p>以這組案例而言，在風險判定為 LOW 的前提下 LLM 傾向判定會往 RF 結果靠攏。</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-6-contents" aria-controls="callout-6" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
所有結果
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-6" class="callout-6-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<pre><code>===========

{
  "risk_level": "MEDIUM",
  "rf_concern": true,
  "evidence": [
    {
      "type": "login_failure_ratio",
      "value": 0.85
    },
    {
      "type": "authentication_abuse_patterns",
      "value": [
        "/login",
        "/login/again"
      ]
    }
  ],
  "analysis": "The high login failure ratio and authentication abuse patterns suggest malicious behavior, but the attack intent cannot be confirmed without further evidence. The presence of suspicious user agents and response times also warrants investigation.",
  "recommendation": "Investigate the source of the login failures and authentication abuse patterns to determine if they are indicative of a brute force attack or another type of malicious activity."
}

===========

{
  "risk_level": "LOW",
  "rf_concern": false,
  "evidence": [
    {
      "type": "login failure ratio",
      "value": 0.85
    },
    {
      "type": "user agent",
      "value": "Mozilla/5.0 (Windows; U; Windows NT 6.2) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/4.1 Safari/534.29.5"
    }
  ],
  "analysis": "The login failure ratio of 0.85 is abnormally high, indicating a potential security issue. However, the user agent string suggests that it may be a legitimate browser. Therefore, further investigation is required to determine the cause of the high login failure rate.",
  "recommendation": "Investigate the cause of the high login failure rate and implement measures to prevent brute-force attacks."
}

===========

{
  "risk_level": "LOW",
  "rf_concern": false,
  "evidence": [
    "No meaningful abnormal behavior detected in the provided features."
  ],
  "analysis": "The provided data does not show any significant anomalies that would suggest malicious behavior. The login failure ratio is normal, and there are no suspicious user agent patterns or high response times. The evidence suggests that the system is functioning as expected.",
  "recommendation": "No further action required at this time."
}

===========

{
  "risk_level": "LOW",
  "rf_concern": false,
  "evidence": [
    "No meaningful abnormal behavior detected in the provided features."
  ],
  "analysis": "The provided data does not show any significant anomalies that strongly suggest malicious behavior. The login failure ratio is within normal limits, and there are no suspicious authentication abuse patterns or correlated abnormal traffic features.",
  "recommendation": "Continue monitoring the system for any unusual activity, but do not take immediate action based on this review."
}

===========


{
  "risk_level": "LOW",
  "rf_concern": false,
  "evidence": [
    "No meaningful abnormal behavior detected in the provided features."
  ],
  "analysis": "The provided data does not show any significant anomalies that strongly suggest malicious behavior. The login failure ratio is 0.85, which is abnormally high, but this could be due to a legitimate issue with user authentication rather than an attack. Additionally, there are no suspicious patterns in the traffic features or user agent strings that would indicate malicious activity.",
  "recommendation": "Continue monitoring the system for any unusual activity and consider implementing additional security measures to improve user authentication and access control."
}

===========

{
  "risk_level": "MEDIUM",
  "rf_concern": true,
  "evidence": [
    {
      "type": "login failure ratio",
      "value": 0.85
    },
    {
      "type": "correlated abnormal traffic features",
      "features": [
        "/login",
        "/api/login"
      ]
    }
  ],
  "analysis": "The high login failure ratio and correlated abnormal traffic features suggest malicious behavior, but the attack intent cannot be confirmed without further evidence. The presence of multiple GET/POST requests for the same resource may indicate a brute force attack, but it is not conclusive evidence. Therefore, I conclude that there is a medium-level risk of malicious activity.",
  "recommendation": "Monitor login failure ratios and abnormal traffic patterns closely, and consider implementing additional security measures such as rate limiting or IP blocking to prevent potential attacks."
}

===========

{
  "risk_level": "MEDIUM",
  "rf_concern": true,
  "evidence": [
    {
      "type": "login_failure_ratio",
      "value": 0.85
    },
    {
      "type": "correlated_abnormal_traffic_features",
      "features": [
        "/api/login",
        "/product/123"
      ]
    }
  ],
  "analysis": "The high login failure ratio and correlated abnormal traffic features suggest malicious behavior, but the attack intent cannot be confirmed. The presence of multiple abnormal indicators increases the risk level to MEDIUM.",
  "recommendation": "Further investigation is required to determine the cause of these anomalies and to confirm whether they are indicative of a security threat."
}

===========

{
  "risk_level": "MEDIUM",
  "rf_concern": true,
  "evidence": [
    {
      "type": "login failure ratio",
      "value": 0.85
    },
    {
      "type": "correlated abnormal traffic features",
      "features": [
        "/product/123 GET request with high response time (8591ms)",
        "/login PUT request with high response time (2850ms)"
      ]
    }
  ],
  "analysis": "The login failure ratio is abnormally high, indicating potential authentication abuse. Additionally, the correlated abnormal traffic features suggest that there may be a coordinated attack on the system. However, without more information about the specific behavior or intent behind these anomalies, it is difficult to confirm whether this is a malicious attack.",
  "recommendation": "Continue monitoring the system for further signs of suspicious activity and consider implementing additional security measures to prevent potential attacks."
}

===========

{
  "risk_level": "MEDIUM",
  "rf_concern": true,
  "evidence": [
    {
      "type": "login_failure_ratio",
      "value": 0.85
    },
    {
      "type": "abnormal_traffic_features",
      "features": [
        {
          "feature": "high_response_time",
          "value": 5683
        },
        {
          "feature": "multiple_correlated_abnormal_traffic_features"
        }
      ]
    }
  ],
  "analysis": "The high login failure ratio (0.85) and multiple abnormal traffic features, including a high response time (5683ms), suggest potential malicious behavior. However, without further evidence, it is difficult to confirm the attack intent. The presence of multiple correlated abnormal traffic features increases the risk level, but it does not necessarily indicate an attack.",
  "recommendation": "Continue monitoring login failure ratios and abnormal traffic features to gather more evidence. Consider implementing additional security measures, such as rate limiting and IP blocking, to mitigate potential attacks."
}</code></pre>
</div>
</div>
</div>
</section>
<section id="llama3-8b-結果" class="level3">
<h3 class="anchored" data-anchor-id="llama3-8b-結果">Llama3 8b 結果</h3>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-7-contents" aria-controls="callout-7" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
註釋
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-7" class="callout-7-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>================================================== Minute: 2026-07-29 20:13 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple correlated abnormal traffic features, including high login failure ratio and authentication abuse patterns.”, “Unusual user agent patterns and multiple GET/POST requests to the same URI.” ], “analysis”: “The provided data shows a combination of abnormal indicators that suggest malicious behavior. The high login failure ratio and authentication abuse patterns are strong evidence of potential attack attempts. Additionally, the unusual user agent patterns and multiple GET/POST requests to the same URI further support this conclusion.”, “recommendation”: “Further investigation is required to confirm the attack intent and identify the source of the attacks. This may involve analyzing network logs, system logs, and other security-related data.” } ================================================== Minute: 2026-07-29 20:14 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple GET requests to /search with high response times (8465, 4917) and unusual user agents.”, “Several POST requests to /login with high response times (2896, 1557) and suspicious IP addresses (175.112.54.116).”, “Unusual traffic patterns from IP address 97.67.104.128, including multiple GET requests to /products and /product/123.” ], “analysis”: “The provided log data shows a mix of normal and abnormal behavior. While there are no clear indicators of malicious activity, the combination of high response times, unusual user agents, and suspicious IP addresses suggests that some users may be attempting to exploit vulnerabilities or engage in unauthorized access.”, “recommendation”: “Further investigation is needed to determine the nature and scope of these activities. I recommend reviewing system logs for additional context, monitoring network traffic for patterns, and conducting a thorough analysis of user behavior.” } ================================================== Minute: 2026-07-29 20:15 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple correlated abnormal traffic features, including high login failure ratio and authentication abuse patterns.”, “Unusual user agent patterns and multiple GET/POST requests to the same URI.” ], “analysis”: “The provided log data shows a combination of unusual behavior that suggests potential malicious activity. The high login failure ratio and authentication abuse patterns indicate attempts to bypass security measures, while the unusual user agent patterns and multiple GET/POST requests suggest attempts to gather information or exploit vulnerabilities.”, “recommendation”: “Further investigation is needed to confirm the attack intent and identify the root cause of the issue. This may involve analyzing additional log data, reviewing system configurations, and conducting network traffic analysis.” } ================================================== Minute: 2026-07-29 20:16 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple abnormal traffic features, including high login failure ratio and authentication abuse patterns.”, “Correlated suspicious anomalies, such as multiple GET/POST requests with unusual user agents.” ], “analysis”: “The provided data shows a mix of normal and abnormal behavior. While there are no clear signs of malicious intent, the presence of correlated suspicious anomalies suggests that further investigation is warranted to determine the root cause of these events.”, “recommendation”: “Conduct a deeper analysis of the traffic patterns and user behavior to identify potential security threats and implement additional monitoring and logging mechanisms to detect and prevent future incidents.” } ================================================== Minute: 2026-07-29 20:17 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple login attempts with high response times (1386, 1458 ms) and varying user agents.”, “Abnormal traffic patterns: multiple GET requests to /dashboard and /product/123 within a short time frame.” ], “analysis”: “The provided data shows unusual behavior that may indicate an attempt to brute-force the login system or gather information about the application. The high response times and varied user agents suggest that the attacker is trying to evade detection. Additionally, the traffic patterns are abnormal, which could be indicative of a reconnaissance phase.”, “recommendation”: “Monitor the system for further suspicious activity and consider implementing additional security measures, such as rate limiting or IP blocking.” } ================================================== Minute: 2026-07-29 20:18 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple abnormal traffic features, including high login failure ratio and authentication abuse patterns.”, “Correlated anomalies in user agent and response time.” ], “analysis”: “The provided data shows a combination of unusual behavior that suggests potential malicious activity. The high login failure ratio and authentication abuse patterns indicate attempts to bypass security measures, while the correlated anomalies in user agent and response time suggest an attempt to evade detection. Although the attack intent cannot be confirmed, the cumulative evidence suggests a medium-level risk.”, “recommendation”: “Further investigation is required to confirm the nature of the activity. I recommend monitoring the system for additional suspicious behavior and implementing additional security measures to prevent unauthorized access.” } ================================================== Minute: 2026-07-29 20:19 { “risk_level”: “MEDIUM”, “rf_concern”: false, “evidence”: [ “Multiple login attempts with different user agents and IP addresses within a short time frame.”, “High response times for certain URLs, indicating potential resource exhaustion attacks.” ], “analysis”: “The provided data shows some abnormal behavior, such as multiple login attempts from different sources. While this could be legitimate activity, it’s also possible that an attacker is attempting to gain unauthorized access. The high response times for certain URLs may indicate a denial-of-service (DoS) or distributed denial-of-service (DDoS) attack. However, without more information, I cannot confirm the intent behind these actions.”, “recommendation”: “Further investigation and monitoring are necessary to determine the nature of this activity. Consider implementing additional security measures, such as rate limiting or IP blocking, to prevent potential attacks.” } ================================================== Minute: 2026-07-29 20:20 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple login attempts with high response times (500) and varying user agents.”, “Correlated abnormal traffic features, such as multiple GET requests to /login and /wp-admin.” ], “analysis”: “The provided data shows a pattern of repeated login attempts with unusual response times and varied user agents. While this behavior is not conclusively malicious, it does warrant further investigation and monitoring to determine the intent behind these actions.”, “recommendation”: “Implement additional logging and monitoring to capture more detailed information about these login attempts, such as IP addresses and device types. This will help to better understand the nature of these events and inform any subsequent security measures.” } ================================================== Minute: 2026-07-29 20:21 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple login attempts with incorrect credentials (14 out of 20 attempts failed)”, “Unusual user agent patterns ( Opera/8.48.(Windows NT 5.0; fa-IR) Presto/2.9.177 Version/11.00 )”, “High response time for API login requests (average response time: 2387ms)” ], “analysis”: “The provided evidence suggests that there may be an attempt to brute-force the login credentials or exploit a vulnerability in the system’s authentication mechanism. The high failure ratio and unusual user agent patterns are indicative of abnormal behavior, while the slow response times for API login requests could indicate a denial-of-service (DoS) attack.”, “recommendation”: “Further investigation is required to determine the root cause of these issues. This may involve analyzing network traffic, reviewing system logs, and conducting additional security testing.” } ================================================== Minute: 2026-07-29 20:22 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple abnormal traffic features detected, including high login failure ratio and authentication abuse patterns.”, “Correlated anomalies observed across multiple IP addresses.” ], “analysis”: “The provided data shows a mix of normal and abnormal behavior. While there are no clear signs of malicious intent, the presence of correlated anomalies and high login failure ratio suggests that further investigation is warranted to determine the root cause of these events.”, “recommendation”: “Conduct a deeper analysis of the traffic patterns and user behavior to determine if any specific users or IP addresses are exhibiting suspicious activity. Additionally, consider implementing additional security measures to detect and prevent potential attacks.” }</p>
</div>
</div>
</div>
<p>看起來似乎比 3b 好很多，丟給 AI 分析</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-8-contents" aria-controls="callout-8" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
AI 分析結果
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-8" class="callout-8-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>這組結果很有研究價值，因為<strong>在 Prompt 完全不變的情況下，只把模型從 Llama 3.2:3B 換成 8B，就出現了明顯的行為差異</strong>。這表示模型能力本身，而不是 Prompt，是造成前面結果的重要因素。</p>
<section id="一最大的改善推理一致性提升" class="level4">
<h4 class="anchored" data-anchor-id="一最大的改善推理一致性提升">一、最大的改善：推理一致性提升</h4>
<p>3B 的輸出常見：</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb14-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb14-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"risk_level"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LOW"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb14-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"analysis"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"login_failure_ratio is abnormally high..."</span></span>
<span id="cb14-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div>
<p>也就是：</p>
<blockquote class="blockquote">
<p>Analysis 說有異常，但 Risk 卻是 LOW。</p>
</blockquote>
<p>這是一種<strong>邏輯不一致</strong>。</p>
<p>而 8B 幾乎全部都是：</p>
<pre><code>Evidence
↓
Analysis
↓
Risk = MEDIUM</code></pre>
<p>例如：</p>
<pre><code>Evidence:
- high login failure ratio
- authentication abuse

↓

Analysis:
suggest malicious behavior

↓

Risk:
MEDIUM</code></pre>
<p>三者是一致的。</p>
</section>
<section id="二evidence-品質明顯提升" class="level4">
<h4 class="anchored" data-anchor-id="二evidence-品質明顯提升">二、Evidence 品質明顯提升</h4>
<p>3B：</p>
<pre><code>login_failure_ratio = 0.85
response_time = 3832</code></pre>
<p>偏向只是把 feature 重述一次。</p>
<p>8B：</p>
<pre><code>Multiple login attempts
Authentication abuse
Correlated abnormal traffic features</code></pre>
<p>它開始做：</p>
<blockquote class="blockquote">
<p>多個 feature → 安全事件</p>
</blockquote>
<p>這就是 SOC analyst 的推理方式。</p>
</section>
<section id="三開始做-feature-correlation" class="level4">
<h4 class="anchored" data-anchor-id="三開始做-feature-correlation">三、開始做 Feature Correlation</h4>
<p>例如：</p>
<pre><code>high login failure ratio
+
multiple login URI
+
high response time
↓

authentication abuse</code></pre>
<p>3B 大多只能：</p>
<pre><code>feature A
feature B</code></pre>
<p>8B 已經開始建立：</p>
<pre><code>A + B + C
↓

同一個攻擊行為</code></pre>
<p>這是最大的能力提升。</p>
</section>
<section id="四rf_concern-判斷更合理" class="level4">
<h4 class="anchored" data-anchor-id="四rf_concern-判斷更合理">四、rf_concern 判斷更合理</h4>
<p>3B：</p>
<pre><code>幾乎都是 false</code></pre>
<p>8B：</p>
<pre><code>true
true
true
false
true
...</code></pre>
<p>代表：</p>
<p>LLM 真的開始比較：</p>
<pre><code>RF prediction

vs

Feature evidence</code></pre>
<p>而不是固定回答。</p>
</section>
<section id="五但仍然有兩個問題" class="level4">
<h4 class="anchored" data-anchor-id="五但仍然有兩個問題">五、但仍然有兩個問題</h4>
<section id="hallucination仍然存在" class="level5">
<h5 class="anchored" data-anchor-id="hallucination仍然存在">(1) Hallucination（仍然存在）</h5>
<p>例如：</p>
<pre><code>authentication abuse patterns</code></pre>
<p>如果你的 context 沒有這個欄位，</p>
<p>就是 LLM 自己總結。</p>
<p>又例如：</p>
<pre><code>Reconnaissance phase</code></pre>
<pre><code>DoS attack</code></pre>
<pre><code>resource exhaustion</code></pre>
<p>這些都是推論。</p>
<p>不是 observation。</p>
<p>如果你的 prompt：</p>
<pre><code>Evidence must reference exact fields.</code></pre>
<p>那這些仍然違反規則。</p>
</section>
<section id="high-消失" class="level5">
<h5 class="anchored" data-anchor-id="high-消失">(2) HIGH 消失</h5>
<p>全部都是：</p>
<pre><code>MEDIUM</code></pre>
<p>沒有：</p>
<pre><code>HIGH</code></pre>
<p>代表：</p>
<p>8B 很保守。</p>
<p>它幾乎都說：</p>
<pre><code>potential

may

suggest

cannot confirm</code></pre>
<p>所以：</p>
<pre><code>MEDIUM</code></pre>
<p>一直不變。</p>
</section>
</section>
<section id="六prompt-可能限制了-high" class="level4">
<h4 class="anchored" data-anchor-id="六prompt-可能限制了-high">六、Prompt 可能限制了 HIGH</h4>
<p>你目前一直看到：</p>
<pre><code>Further investigation required.

Attack intent cannot be confirmed.</code></pre>
<p>代表 Prompt 的：</p>
<pre><code>Do not invent information.

Only use provided evidence.</code></pre>
<p>限制很成功。</p>
<p>但副作用：</p>
<p>LLM 不敢給 HIGH。</p>
<p>可以考慮：</p>
<pre><code>HIGH does not require confirmed attack.

HIGH means
multiple abnormal indicators
strongly increase security risk.</code></pre>
<p>而不是：</p>
<pre><code>confirmed malicious attack.</code></pre>
</section>
<section id="七但另一個值得注意的現象" class="level4">
<h4 class="anchored" data-anchor-id="七但另一個值得注意的現象">七、但另一個值得注意的現象</h4>
<p>8B 開始大量引用：</p>
<pre><code>User Agent</code></pre>
<p>例如：</p>
<pre><code>unusual user agent</code></pre>
<p>可是你之前 Prompt 明確寫：</p>
<pre><code>Do NOT assume
unusual user agent = attack</code></pre>
<p>它沒有直接說：</p>
<pre><code>UA = attack</code></pre>
<p>但是：</p>
<p>它開始把：</p>
<pre><code>UA

+

login

+

response time

↓

attack</code></pre>
<p>這其實是合理推理。</p>
<p>但是如果：</p>
<p>你的資料集裡：</p>
<pre><code>UA

根本沒有 discriminative power</code></pre>
<p>那 LLM 就被誤導。</p>
</section>
<section id="八整體改變" class="level4">
<h4 class="anchored" data-anchor-id="八整體改變">八、整體改變</h4>
<section id="b" class="level5">
<h5 class="anchored" data-anchor-id="b">3B</h5>
<p>特徵：</p>
<ul>
<li>逐一看 feature</li>
<li>推理不一致</li>
<li>常 LOW</li>
<li>rf_concern 不穩定</li>
</ul>
</section>
<section id="b-1" class="level5">
<h5 class="anchored" data-anchor-id="b-1">8B</h5>
<p>特徵：</p>
<ul>
<li>開始做 correlation</li>
<li>可以整合 context</li>
<li>risk 判斷一致</li>
<li>JSON 品質較好</li>
<li>hallucination 較少</li>
<li>但偏保守</li>
</ul>
<p>這可以形成一個很好的討論：</p>
<blockquote class="blockquote">
<p><strong>較大的 LLM 並非只是生成更長的分析，而是在相同 Prompt 下展現出更好的多特徵整合能力與推理一致性。</strong></p>
</blockquote>
</section>
</section>
</div>
</div>
</div>
</section>
<section id="lamma3-70b-效果" class="level3">
<h3 class="anchored" data-anchor-id="lamma3-70b-效果">Lamma3 70B 效果</h3>
<p>Lamma3 70B我是在另一個環境生成的</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-9-contents" aria-controls="callout-9" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
註釋
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-9" class="callout-9-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>================================================== Minute: 2026-07-29 22:33 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple 404 status codes for admin-related URIs (/admin, /.env)”, “High frequency of login attempts with varying user agents”, “Unusual user agent strings (e.g., ‘Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3 like Mac OS X; ms-MY) AppleWebKit/534.30.5 (KHTML, like Gecko) Version/4.0.5 Mobile/8B111 Safari/6534.30.5’)” ], “analysis”: “The provided logs show a pattern of suspicious activity, including multiple attempts to access admin-related resources and a high frequency of login attempts with varying user agents. While these indicators do not conclusively prove malicious behavior, they warrant further investigation.”, “recommendation”: “Implement additional logging and monitoring for admin-related resources, and consider implementing rate limiting or IP blocking for excessive login attempts.” } ================================================== Minute: 2026-07-29 22:34 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple 403 status codes from different IPs”, “High response time for some requests”, “Unusual user agents and HTTP methods” ], “analysis”: “The provided logs show a mix of successful and failed login attempts, as well as requests to various endpoints. While there are no extremely high login failure ratios or clear authentication abuse patterns, the presence of multiple 403 status codes from different IPs suggests potential unauthorized access attempts. Additionally, some requests have high response times, which could indicate resource exhaustion or scanning activities. The use of unusual user agents and HTTP methods (e.g., PUT, DELETE) adds to the suspicion. However, without more concrete evidence, it’s difficult to confirm malicious intent.”, “recommendation”: “Further investigation is recommended to determine the nature of these requests and to identify potential security vulnerabilities. Implementing additional logging and monitoring mechanisms can help gather more information about these events.” } ================================================== Minute: 2026-07-29 22:35 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple login attempts with 403 and 401 status codes from IP address 150.53.207.213”, “High frequency of POST requests to /login endpoint from the same IP address”, “Unusual user agent strings in some requests” ], “analysis”: “The provided logs show a high number of login attempts with 403 and 401 status codes from a single IP address (150.53.207.213). This could indicate a brute-force attack or credential stuffing attempt. Additionally, the high frequency of POST requests to the /login endpoint from the same IP address suggests automated activity. While the unusual user agent strings in some requests do not necessarily imply malicious behavior, they add to the overall suspicious nature of the traffic. However, without more concrete evidence, it is difficult to confirm attack intent.”, “recommendation”: “Monitor the IP address 150.53.207.213 for further suspicious activity and consider implementing rate limiting or IP blocking measures to prevent potential brute-force attacks.” } ================================================== Minute: 2026-07-29 22:36 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple login failures with 401 status code from IP address 150.53.207.213”, “High frequency of requests to /login endpoint from IP address 150.53.207.213”, “Unusual user agent ‘FxiOS/18.4g2186.0 Mobile/64S634 Safari/533.1’ used in multiple requests” ], “analysis”: “The evidence suggests that there may be an attempt to brute-force or exploit the login functionality of the application. The high frequency of requests to the /login endpoint and the multiple login failures with 401 status code from a single IP address (150.53.207.213) are indicative of abnormal behavior. Additionally, the unusual user agent used in multiple requests may be an attempt to evade detection or exploit a vulnerability. While this evidence does not confirm a successful attack, it warrants further investigation and monitoring.”, “recommendation”: “Implement rate limiting on the /login endpoint and monitor the IP address 150.53.207.213 for further suspicious activity. Additionally, consider implementing additional security measures such as CAPTCHA or two-factor authentication to prevent brute-force attacks.” } ================================================== Minute: 2026-07-29 22:37 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “High frequency of login failures from multiple IPs (165.196.212.69, 200.163.69.20)”, “Unusual user agents and HTTP methods used (OPTIONS, PUT)”, “Multiple 404 errors for different product IDs” ], “analysis”: “The provided logs show a high frequency of login failures from multiple IPs, which could indicate a brute-force attack or credential stuffing attempt. Additionally, the use of unusual user agents and HTTP methods may suggest an automated tool is being used to interact with the application. The multiple 404 errors for different product IDs could be indicative of a scanner or crawler attempting to identify vulnerabilities. While these indicators do not confirm malicious behavior, they warrant further investigation.”, “recommendation”: “Implement rate limiting on login attempts and monitor user agent strings for suspicious patterns. Also, review product ID handling to ensure it is secure and not vulnerable to exploitation.” } ================================================== Minute: 2026-07-29 22:38 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple 403 Forbidden responses from IP address 183.203.102.176”, “High response time (1178ms) for a GET request to /search from IP address 135.11.231.26”, “Unusual user agent ‘Opera/9.30.(Windows NT 10.0; et-EE) Presto/2.9.169 Version/12.00’ used in multiple requests” ], “analysis”: “The provided logs show some suspicious behavior, such as multiple 403 Forbidden responses from a single IP address, high response times for certain requests, and unusual user agents. While these indicators do not necessarily prove malicious intent, they are abnormal and warrant further investigation.”, “recommendation”: “Monitor the identified IP addresses and user agents for continued suspicious activity, and consider implementing rate limiting or IP blocking measures to prevent potential abuse.” } ================================================== Minute: 2026-07-29 22:39 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “.env file access attempts”, “High frequency of login attempts from different IPs”, “Unusual user agents and HTTP methods” ], “analysis”: “The provided logs show several indicators of potential malicious activity. The repeated attempts to access the .env file, which typically contains sensitive environment variables, raise concerns about information disclosure or exploitation. Additionally, the high frequency of login attempts from different IPs may indicate a brute-force attack or credential stuffing. Furthermore, the presence of unusual user agents and HTTP methods, such as DELETE and PUT, could be indicative of malicious activity. While these indicators do not conclusively prove an attack, they warrant further investigation and monitoring.”, “recommendation”: “Implement additional security measures to monitor and block suspicious traffic patterns, such as IP blocking or rate limiting. Also, consider implementing a Web Application Firewall (WAF) to detect and prevent common web attacks.” } ================================================== Minute: 2026-07-29 22:40 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Multiple 404 errors from different IPs”, “High frequency of login attempts with varying user agents”, “Unusual URI requests (e.g., .env, /wp-admin)” ], “analysis”: “The provided logs show a mix of suspicious and benign traffic. While there are no clear indicators of a successful attack, the frequency and diversity of login attempts, along with unusual URI requests, suggest potential reconnaissance or probing activity. The multiple 404 errors from different IPs may indicate scanning or enumeration attempts.”, “recommendation”: “Implement additional logging and monitoring to track these suspicious activities. Consider enabling rate limiting or IP blocking for excessive login attempts. Perform a thorough review of system configurations and access controls to ensure they are up-to-date and secure.” } ================================================== Minute: 2026-07-29 22:41 { “risk_level”: “MEDIUM”, “rf_concern”: true, “evidence”: [ “Unusual user agent patterns (e.g., Opera/8.30.(X11; Linux i686; nan-TW) Presto/2.9.169 Version/11.00)”, “High response time for certain requests (e.g., 1080ms, 1190ms)”, “Multiple requests from the same IP address with different user agents” ], “analysis”: “The provided logs show some unusual patterns that cannot be explained by normal user behavior. The presence of multiple user agents and high response times suggests potential malicious activity. However, without more concrete evidence, it is difficult to confirm attack intent.”, “recommendation”: “Further investigation is recommended to determine the source and intent behind these unusual requests. This may involve collecting additional logs or implementing more granular monitoring.” } ================================================== Minute: 2026-07-29 22:42 { “risk_level”: “LOW”, “rf_concern”: false, “evidence”: [ “Multiple 404 status codes for different resources”, “High response time for some requests” ], “analysis”: “The provided logs show some anomalies, such as multiple 404 status codes and high response times. However, these indicators do not strongly suggest malicious behavior. The user agents and request methods appear to be legitimate, and there is no evidence of authentication abuse or correlated abnormal traffic features.”, “recommendation”: “Monitor the system for further anomalies and investigate the causes of the high response times and 404 status codes to ensure they are not indicative of a larger issue.” }</p>
</div>
</div>
</div>
<p>此案例 AI 回饋也是偏正面，只是幻覺問題也變明顯。但基本上可初步斷定：<strong>越小的模型越傾向保守判斷，無法成為真正的第二道防線；相對的，越大的模型越有好的推論能力，但要增加其他手段避免幻覺問題</strong>。</p>
</section>
<section id="結論" class="level2">
<h2 class="anchored" data-anchor-id="結論">結論</h2>
<p>拖了老半天終於寫完了，過程中我很明顯地感覺到，這個案例微調完成的難度較高(也可能是我對微調不夠熟)，反而升級模型效果會更好(雖然這會犧牲掉回應生成時間，還有幻覺風險)。不過我的微調要求的 output 還是數字，不利於LLM 分析，是下次可改進的方向，<del>前提是之後還有機會用好設備就是了:(</del>。</p>



</section>
</section>

<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div> ]]></description>
  <category>LLM</category>
  <category>Random Forest</category>
  <category>time series</category>
  <category>abnormal analysis</category>
  <category>Fine-tuning</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/poc-verify2/</guid>
  <pubDate>Thu, 06 Aug 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/poc-verify2/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>結合 LLM 跟 ML 技術來生網路流量異常的解釋報告!不過還可以再更好？</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/poc-verify/</link>
  <description><![CDATA[ 






<div data-ai-tag="2,7">

</div>
<script src="../../../asserts/box.js" defer=""></script>
<section id="緣起" class="level1">
<h1>緣起</h1>
<p>期末的時候 LLM 資安課程有個期末報告是要做 Poc (Proof of concept)，每個組別要研究市面上或是學術裡的文獻，針對現有的不足之處提出改進措施，再做出產品驗證構想，主題必須要跟資安相關。<sup>1</sup>我苦思了很久要做什麼，只知道我比較想做跟 Web 有關的，然後想到跟時間序列有關的就網路流量異常偵測，但不確定具體的落地情境。最後在倒數第 2 週，老師分享了他在 IBM 做資安分析的組織結構跟每個職位負責的內容，突然靈光一閃想到不如利用這個情境來設想可能會有的需求。於是結合前面的構想想出了「可解釋性網絡流量異常檢測系統」的 Poc，它的最初概念是這樣</p>
<blockquote class="blockquote">
<p>使用機器學習來偵測網路流量異常的系統很多，結合 LLM 可以用來生報告，此外還可以再檢視一次資料，確定資料符合異常標準。</p>
</blockquote>
<p>如果用統計學的角度來看，LLM 扮演的是另一個 Selection Criteria 角色，使預測結果更加精確，使用兩個Selection Criteria 在統計學也不是甚麼罕見的手法，理論上應該可行，只是 LLM 會不會因為輕判或其他因素判別錯誤是另一個議題。</p>
<p>不過，因為看起來可行極高，市面也有類似產品。<sup>2</sup>再加上我在找資料的過程中發現可以走破電腦也能執行的路線，所以最後直接用 3b小模型 + 隨機森林的組合完成我的報告交出去，<del>Credit 也好好的被省下來了</del>。但最近再檢視一次我才發現，設計的不好XD，於是先來回顧一次我的設計，以及哪裡出了問題。</p>
</section>
<section id="系統架構" class="level1">
<h1>系統架構</h1>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR

    subgraph Client["Client"]
        User["使用者"]
    end

    subgraph Frontend["Open WebUI"]
        WebUI["Chat UI"]
        Tool["Tools / Function"]
        LLM["LLM"]
    end

    subgraph AIService["Anomaly Detection Service"]
        RFAPI["RF Model API&lt;br/&gt;(FastAPI)"]
        Logs["Logs 時間與相關指標"]
    end

    User --&gt;|"查某時的系統狀態"| WebUI
    WebUI --&gt;|"呼叫 Tool"| Tool
    Tool --&gt;|"HTTP POST"| RFAPI

    RFAPI --&gt; Logs
    Logs --&gt; RFAPI

    RFAPI --&gt;|"異常結果"| Tool
    Tool --&gt;|"組成 Prompt"| LLM

    LLM --&gt;|"分析報告(給Soc參考)"| WebUI
    WebUI --&gt; User
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
名詞 &amp; 細部解釋
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><p>Open WebUI：一個自定義極強的開源 WebUI，可以連 Ollama 下載下來的模型，<a href="https://github.com/open-webui/open-webui">官方 github 在此</a>。這裡用的環境是 Docker 連本機 Ollama。</p></li>
<li><p>Logs 時間與相關指標：這裡隨機森林拿來預測的其實不是網站的原始登入 log 資訊，而是經過轉化再計算的流量指標，下節會提到。</p></li>
<li><p>Soc：資安團隊中的基層，負責回報異常事件，本次提供的資料(理論上)可以協助他們撰寫 Security Event Analysis 的報告，減少他們的文書壓力。</p></li>
</ul>
</div>
</div>
<p>這裡之所以使用隨機森林，主要是訓練速度快、預測結果也不輸其他知名的好模型，<del>以及我的資料集是模擬資料不必用太好的模型效果也會很好</del>。另外使用 FastAPI 原因是為了讓兩邊的系統服務可以藉由 API 好好串一起，同時保有各自獨立的功能，架構設計較穩。</p>
</section>
<section id="製作過程" class="level1">
<h1>製作過程</h1>
<section id="使用-python-faker-套件產出假資料" class="level2">
<h2 class="anchored" data-anchor-id="使用-python-faker-套件產出假資料">1. 使用 python faker 套件產出假資料</h2>
<p>產出假資料的訴求為「貼近真實使用情境」，因此讓生成資料有週期性，模擬系統真實登入情況。同時也在上 label 加入 「label 不一定正確」的機制，考驗模型在特徵混亂時的反應。</p>
<div class="callout callout-style-simple callout-warning no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
🤫 悄悄話
</div>
</div>
<div class="callout-body-container callout-body">
<p>其實是因為套件生成的假 log 模式還是太簡單太單一了，隨機森林會捕捉得太好(準確率 99% <img src="https://latex.codecogs.com/png.latex?%5Cuparrow">)，所以混入 Label 標錯的資料讓隨機森林的預測能力下降至接近文獻的程度。</p>
</div>
</div>
<p>混淆類型有:</p>
<ol type="1">
<li><p>漏報：有 6% 的進階潛伏攻擊行為在標籤上被標記為正常(0)</p></li>
<li><p>誤報：有 4% 的普通日常/維運人潮，因為特徵太像攻擊，標籤被意外標記為異常(1)</p></li>
</ol>
<p>最後生成 5000 筆資料，正常與異常的標示為：</p>
<ul>
<li><p>總計統計分鐘數: 5000</p></li>
<li><p>正常狀態分鐘數(Label 0): 2526</p></li>
<li><p>異常狀態分鐘數(Label 1): 2474</p></li>
</ul>
<p>將資料儲存成 json 跟 csv 檔，其中 csv 用於訓練模型。</p>
<p>json 資料範例</p>
<pre><code>  {
    "timestamp": "2026-06-02 08:25:49",
    "ip": "41.61.225.212",
    "uri": "/products",
    "status_code": 200,
    "response_time_ms": 1665,
    "user_agent": "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_9_2 rv:2.0; ti-ER) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/5.0 Safari/534.29.5",
    "method": "GET",
    "is_anomaly": 0
  },</code></pre>
<table class="caption-top table">
<colgroup>
<col style="width: 21%">
<col style="width: 78%">
</colgroup>
<thead>
<tr class="header">
<th>欄位</th>
<th>解說</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>timestamp</code></td>
<td>請求發生時間（伺服器記錄時間）</td>
</tr>
<tr class="even">
<td><code>ip</code></td>
<td>發送請求的用戶端 IP 位址，可用於流量分析、地理位置判斷或安全稽核。</td>
</tr>
<tr class="odd">
<td><code>uri</code></td>
<td>使用者存取的 API 或網頁路徑：<code>/search</code>，表示執行搜尋功能。</td>
</tr>
<tr class="even">
<td><code>status_code</code></td>
<td>HTTP 回應狀態碼，200 代表請求成功並正常回傳結果。</td>
</tr>
<tr class="odd">
<td><code>response_time_ms</code></td>
<td>伺服器處理並回應此請求所花費的時間）。</td>
</tr>
<tr class="even">
<td><code>user_agent</code></td>
<td>用戶端瀏覽器與裝置資訊。</td>
</tr>
<tr class="odd">
<td><code>method</code></td>
<td>HTTP 請求方法：GET，表示向伺服器讀取資料而非新增或修改資料。</td>
</tr>
<tr class="even">
<td><code>is_anomaly</code></td>
<td>正常(0)或異常(1)流量</td>
</tr>
</tbody>
</table>
<p>整理成 CSV 後欄位後，欄位變成更便於隨機森林分類的形式:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 14%">
<col style="width: 25%">
<col style="width: 34%">
</colgroup>
<thead>
<tr class="header">
<th>欄位名稱</th>
<th>中文名稱</th>
<th>計算方式</th>
<th>功用與意義</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>minute</code></td>
<td>分鐘時間窗</td>
<td>YYYY-MM-DD HH:MM</td>
<td>每筆資料代表 1 分鐘的流量統計結果</td>
</tr>
<tr class="even">
<td><code>requests_per_min</code></td>
<td>每分鐘請求數</td>
<td>該分鐘內 Request 總數</td>
<td>衡量流量大小，突增可能代表 DDoS、爬蟲或掃描行為</td>
</tr>
<tr class="odd">
<td><code>unique_ips</code></td>
<td>獨立 IP 數</td>
<td>不重複 IP 數量</td>
<td>觀察流量來源是否分散，多 IP 可能代表殭屍網路攻擊</td>
</tr>
<tr class="even">
<td><code>ip_density_index</code></td>
<td>IP 密度指數</td>
<td>Requests ÷ Unique IPs</td>
<td>衡量單一 IP 平均發送多少請求，高值通常表示集中式攻擊</td>
</tr>
<tr class="odd">
<td><code>avg_response_time</code></td>
<td>平均回應時間</td>
<td>平均 Response Time</td>
<td>系統負載增加時通常會上升</td>
</tr>
<tr class="even">
<td><code>max_response_time</code></td>
<td>最大回應時間</td>
<td>Max(Response Time)</td>
<td>偵測極端延遲事件</td>
</tr>
<tr class="odd">
<td><code>p95_response_time</code></td>
<td>P95 回應時間</td>
<td>95 百分位數 Response Time</td>
<td>衡量大部分使用者體驗，比平均值更能反映效能瓶頸</td>
</tr>
<tr class="even">
<td><code>4xx_ratio</code></td>
<td>4xx 錯誤比例</td>
<td>4xx 數量 ÷ 總請求數</td>
<td>掃描、爆破登入、錯誤 API 呼叫時常增加</td>
</tr>
<tr class="odd">
<td><code>5xx_ratio</code></td>
<td>5xx 錯誤比例</td>
<td>5xx 數量 ÷ 總請求數</td>
<td>伺服器異常、系統崩潰或過載指標</td>
</tr>
<tr class="even">
<td><code>login_fail_rate</code></td>
<td>登入失敗率</td>
<td>失敗登入 ÷ 登入請求數</td>
<td>偵測暴力破解、帳號猜測攻擊</td>
</tr>
<tr class="odd">
<td><code>bot_attack_ua_ratio</code></td>
<td>可疑 Bot UA 比例</td>
<td>可疑 UA 數量 ÷ 總請求數</td>
<td>偵測自動化攻擊工具與掃描器</td>
</tr>
<tr class="even">
<td><code>label</code></td>
<td>異常標籤</td>
<td>0 或 1</td>
<td>機器學習預測目標，1=異常、0=正常</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
名詞解釋
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li>可疑 Bot UA：UA 的全名為 User-Agent，即範例資料中的<code>"user_agent": "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_9_2 rv:2.0; ti-ER) AppleWebKit/534.29.5 (KHTML, like Gecko) Version/5.0 Safari/534.29.5"</code>部份。每個 HTTP Request 都會帶有一個 User-Agent Header，用來表示發出請求的客戶端。</li>
</ul>
</div>
</div>
</section>
<section id="訓練隨機森林模型" class="level2">
<h2 class="anchored" data-anchor-id="訓練隨機森林模型">2. 訓練隨機森林模型</h2>
<p>將資料的 70 % 拿去訓練，並且在測試集測試，結果數據如下</p>
<ul>
<li><p>Accuracy: 0.954</p></li>
<li><p>Confusion Matrix:</p></li>
</ul>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Actual &nbsp;Predicted</th>
<th>Class 0</th>
<th>Class 1</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Class 0</strong></td>
<td>707</td>
<td>39</td>
</tr>
<tr class="even">
<td><strong>Class 1</strong></td>
<td>30</td>
<td>724</td>
</tr>
</tbody>
</table>
<ul>
<li>Report:</li>
</ul>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Class</th>
<th style="text-align: right;">Precision</th>
<th style="text-align: right;">Recall</th>
<th style="text-align: right;">F1-Score</th>
<th style="text-align: right;">Support</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>0</td>
<td style="text-align: right;">0.96</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">746</td>
</tr>
<tr class="even">
<td>1</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.96</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">754</td>
</tr>
</tbody>
</table>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Metric</th>
<th style="text-align: right;">Precision</th>
<th style="text-align: right;">Recall</th>
<th style="text-align: right;">F1-Score</th>
<th style="text-align: right;">Support</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Accuracy</strong></td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;"><strong>0.95</strong></td>
<td style="text-align: right;">1500</td>
</tr>
<tr class="even">
<td><strong>Macro Avg</strong></td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">1500</td>
</tr>
<tr class="odd">
<td><strong>Weighted Avg</strong></td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">0.95</td>
<td style="text-align: right;">1500</td>
</tr>
</tbody>
</table>
<p>即使資料有混淆的情況，模型的預測效果依然有 95.4 %，已經非常接近實務上的數據。從 Confusion Matrix 來看，只有 39 筆的正常資料備判定異常；30 筆異常資料被判定為正常。且對於正常資料的判定正確率有 96 %，代表多數情況應可用隨機森林解決，少部分誤判資料可以再用 LLM 檢視一次。</p>
<p>隨機森林擷取的特徵，以 IP 密度指數最高。</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Rank</th>
<th>Feature</th>
<th style="text-align: right;">Importance</th>
<th style="text-align: right;">Contribution (%)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td><code>ip_density_index</code></td>
<td style="text-align: right;">0.499463</td>
<td style="text-align: right;">49.95%</td>
</tr>
<tr class="even">
<td>2</td>
<td><code>bot_attack_ua_ratio</code></td>
<td style="text-align: right;">0.287956</td>
<td style="text-align: right;">28.80%</td>
</tr>
<tr class="odd">
<td>3</td>
<td><code>avg_response_time</code></td>
<td style="text-align: right;">0.059619</td>
<td style="text-align: right;">5.96%</td>
</tr>
<tr class="even">
<td>4</td>
<td><code>p95_response_time</code></td>
<td style="text-align: right;">0.036047</td>
<td style="text-align: right;">3.60%</td>
</tr>
<tr class="odd">
<td>5</td>
<td><code>4xx_ratio</code></td>
<td style="text-align: right;">0.035068</td>
<td style="text-align: right;">3.51%</td>
</tr>
<tr class="even">
<td>6</td>
<td><code>requests_per_min</code></td>
<td style="text-align: right;">0.027547</td>
<td style="text-align: right;">2.75%</td>
</tr>
<tr class="odd">
<td>7</td>
<td><code>unique_ips</code></td>
<td style="text-align: right;">0.023760</td>
<td style="text-align: right;">2.38%</td>
</tr>
<tr class="even">
<td>8</td>
<td><code>max_response_time</code></td>
<td style="text-align: right;">0.018792</td>
<td style="text-align: right;">1.88%</td>
</tr>
<tr class="odd">
<td>9</td>
<td><code>login_fail_rate</code></td>
<td style="text-align: right;">0.010998</td>
<td style="text-align: right;">1.10%</td>
</tr>
<tr class="even">
<td>10</td>
<td><code>5xx_ratio</code></td>
<td style="text-align: right;">0.000750</td>
<td style="text-align: right;">0.08%</td>
</tr>
</tbody>
</table>
</section>
<section id="儲存-rf-模型" class="level2">
<h2 class="anchored" data-anchor-id="儲存-rf-模型">3. 儲存 RF 模型</h2>
<ul>
<li><p>模型本體 <code>rf_model.pkl</code></p></li>
<li><p>模型訓練參數 <code>rf_training_feature.pkl</code></p></li>
</ul>
</section>
<section id="建立腳本-rf-api.py" class="level2">
<h2 class="anchored" data-anchor-id="建立腳本-rf-api.py">4. 建立腳本 <code>rf-api.py</code></h2>
<p>用於建立 API 服務，只要將前面生成的 CSV 與此腳本放在同一資料夾下，每次開始使用時，只要 API 啟動 ，LLM 會藉由此 api 呼叫訓練好的隨機森林模型。</p>
<p><strong>步驟</strong></p>
<ol type="1">
<li>安裝套件</li>
</ol>
<pre><code>pip install fastapi uvicorn</code></pre>
<ol start="2" type="1">
<li>寫腳本</li>
</ol>
<p>重點在於當使用者指定時間點時，LLM 會將其轉成符合格式的字串，經由 API 傳給隨機森林，隨機森林計算的數值跟發現的異常部分要回傳給 LLM 。</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FastAPI, Query, HTTPException</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> joblib</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> datetime <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> datetime</span>
<span id="cb3-5"></span>
<span id="cb3-6">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FastAPI()</span>
<span id="cb3-7"></span>
<span id="cb3-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 載入你訓練好的隨機森林模型</span></span>
<span id="cb3-9">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> joblib.load(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rf_model.pkl"</span>)</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這是要觀測的 CSV 檔案路徑</span></span>
<span id="cb3-12">CSV_FILE_PATH <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"features-test.csv"</span></span>
<span id="cb3-13"></span>
<span id="cb3-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_dynamic_data(start_str: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, end_str: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb3-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""從 CSV 中動態篩選出指定時間區間的資料"""</span></span>
<span id="cb3-16">    df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(CSV_FILE_PATH)</span>
<span id="cb3-17">    df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.to_datetime(df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>])</span>
<span id="cb3-18">    </span>
<span id="cb3-19">    start_dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.to_datetime(start_str)</span>
<span id="cb3-20">    end_dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.to_datetime(end_str) </span>
<span id="cb3-21">    </span>
<span id="cb3-22">    mask <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> start_dt) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> end_dt)</span>
<span id="cb3-23">    filtered_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.loc[mask].copy()</span>
<span id="cb3-24">    </span>
<span id="cb3-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> filtered_df</span>
<span id="cb3-26"></span>
<span id="cb3-27"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/check_status"</span>)</span>
<span id="cb3-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> check_status(</span>
<span id="cb3-29">    start_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Query(..., description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"開始時間，如: 2026-06-06 08:00"</span>),</span>
<span id="cb3-30">    end_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Query(..., description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"結束時間，如: 2026-06-06 12:00"</span>)</span>
<span id="cb3-31">):</span>
<span id="cb3-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb3-33">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 動態撈取該時段的 CSV 資料</span></span>
<span id="cb3-34">        df_period <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_dynamic_data(start_time, end_time)</span>
<span id="cb3-35">        </span>
<span id="cb3-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> df_period.empty:</span>
<span id="cb3-37">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb3-38">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no_data"</span>, </span>
<span id="cb3-39">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"在 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>start_time<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 到 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>end_time<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 之間找不到任何監控紀錄。"</span></span>
<span id="cb3-40">            }</span>
<span id="cb3-41">        </span>
<span id="cb3-42">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 準備特徵矩陣 (X)</span></span>
<span id="cb3-43">        feature_cols <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb3-44">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'unique_ips'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ip_density_index'</span>, </span>
<span id="cb3-45">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'avg_response_time'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'max_response_time'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>, </span>
<span id="cb3-46">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'login_fail_rate'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bot_attack_ua_ratio'</span></span>
<span id="cb3-47">        ]</span>
<span id="cb3-48">        X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df_period[feature_cols]</span>
<span id="cb3-49">        </span>
<span id="cb3-50">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 模型預測</span></span>
<span id="cb3-51">        predictions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.predict(X)</span>
<span id="cb3-52">        df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pred_label'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> predictions</span>
<span id="cb3-53">        </span>
<span id="cb3-54">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 計算總時間長度（分鐘數）與真實異常率</span></span>
<span id="cb3-55">        total_minutes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(df_period)</span>
<span id="cb3-56">        anomaly_minutes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pred_label'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()</span>
<span id="cb3-57">        actual_anomaly_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (anomaly_minutes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> total_minutes) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb3-58">        </span>
<span id="cb3-59">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 提取統計數據（不管 RF 判斷為何，這些都是硬證據）</span></span>
<span id="cb3-60">        total_requests <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>())</span>
<span id="cb3-61">        avg_unique_ips <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'unique_ips'</span>].mean())</span>
<span id="cb3-62">        avg_p95_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>].mean())</span>
<span id="cb3-63">        max_4xx_ratio <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb3-64">        max_5xx_ratio <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb3-65">        avg_bot_ratio <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bot_attack_ua_ratio'</span>].mean()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb3-66">        </span>
<span id="cb3-67">        summary_stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb3-68">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total_requests"</span>: total_requests,</span>
<span id="cb3-69">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"avg_unique_ips"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(avg_unique_ips, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>),</span>
<span id="cb3-70">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"actual_anomaly_rate_percent"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(actual_anomaly_rate, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-71">        }</span>
<span id="cb3-72">        </span>
<span id="cb3-73">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. 格式化「隨機森林認為異常」的時間點日誌，翻譯成白話文</span></span>
<span id="cb3-74">        anomalies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df_period[df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pred_label'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-75">        log_lines <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb3-76">        </span>
<span id="cb3-77">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> anomalies.empty:</span>
<span id="cb3-78">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 如果誤報過多導致行數破表，我們只挑選回應時間最慘或錯誤率最高的前 5 筆，防止 LLM 看暈</span></span>
<span id="cb3-79">            top_anomalies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> anomalies.sort_values(by<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>], ascending<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>).head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb3-80">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx, row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> top_anomalies.iterrows():</span>
<span id="cb3-81">                time_str <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>].strftime(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%Y-%m-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%d</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> %H:%M'</span>)</span>
<span id="cb3-82">                line <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb3-83">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  - [</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>time_str<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">] -&gt; 流量: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">次, "</span></span>
<span id="cb3-84">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"不重複IP數: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'unique_ips'</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">個, "</span></span>
<span id="cb3-85">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"P95回應時間: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">ms, "</span></span>
<span id="cb3-86">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"4xx錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%, "</span></span>
<span id="cb3-87">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"5xx錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%, "</span></span>
<span id="cb3-88">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"惡意爬蟲UA比例: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bot_attack_ua_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span></span>
<span id="cb3-89">                )</span>
<span id="cb3-90">                log_lines.append(line)</span>
<span id="cb3-91">        </span>
<span id="cb3-92">        anomaly_summary <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(log_lines) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> log_lines <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"（此時段內無任何分鐘被隨機森林標記為異常）"</span></span>
<span id="cb3-93">        </span>
<span id="cb3-94">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  統一回傳 success，將主導權交還給前端 Tool 的系統提示詞</span></span>
<span id="cb3-95">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb3-96">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"success"</span>,</span>
<span id="cb3-97">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rf_decision"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ANOMALY_DETECTED"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> anomaly_minutes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SYSTEM_NORMAL"</span>,</span>
<span id="cb3-98">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"隨機森林模型判定該區間有 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>actual_anomaly_rate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">% 的分鐘數據處於異常狀態。"</span>,</span>
<span id="cb3-99">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stats"</span>: summary_stats,</span>
<span id="cb3-100">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"logs"</span>: (</span>
<span id="cb3-101">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"【整體查詢時段監控指標概況】:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-102">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 總請求流量: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total_requests<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 次</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-103">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 平均不重複連線 IP 數: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(avg_unique_ips, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 個</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-104">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 全時段平均 P95 回應時間: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">round</span>(avg_p95_time, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-105">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 最高 4xx 客戶端錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>max_4xx_ratio<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-106">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 最高 5xx 伺服器錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>max_5xx_ratio<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-107">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 平均惡意爬蟲 UA 比例: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>avg_bot_ratio<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-108">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"【隨機森林模型標記為 1 (異常) 的時間點詳細數據摘要】:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>anomaly_summary<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-109">            )</span>
<span id="cb3-110">        }</span>
<span id="cb3-111">            </span>
<span id="cb3-112">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb3-113">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> HTTPException(status_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>, detail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"系統錯誤: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
<p>其中<code>CSV_FILE_PATH = "features-test.csv"</code>的部分是並未用於訓練或測試，完全重新生成的資料，與 <code>rf-api.py</code>放在同一個資料夾下。</p>
<ol start="3" type="1">
<li>儲存後啟動 api</li>
</ol>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uvicorn</span> rf-api:app <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--reload</span></span></code></pre></div>
<p>網址應為 http://127.0.0.1:8000 ，在 /docs 檢查 API 運作，請求有回應表示正常。</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/poc-verify/api-check.png" class="img-fluid"></p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
註釋
</div>
</div>
<div class="callout-body-container callout-body">
<p>在 VM 執行的話沒有辦法直接訪問網址，解法是在 VM 環境裡用 python 腳本初步驗證：</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb5-2"></span>
<span id="cb5-3">r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/openapi.json"</span>)</span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(r.json()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"paths"</span>])</span></code></pre></div>
</div>
</div>
</section>
<section id="在-open-web-ui-設定" class="level2">
<h2 class="anchored" data-anchor-id="在-open-web-ui-設定">5. 在 Open Web UI 設定</h2>
<ol type="1">
<li>到工作區&gt;工具&gt;建立 Tool</li>
</ol>
<p>使用以下的程式碼</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb6-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb6-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Optional</span>
<span id="cb6-4"></span>
<span id="cb6-5"></span>
<span id="cb6-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Tools:</span>
<span id="cb6-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span>
<span id="cb6-9"></span>
<span id="cb6-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> check_system_anomaly(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, start_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, end_time: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        當使用者詢問某個特定時間區間系統有沒有異常、流量狀態或需要分析日誌時呼叫此工具。</span></span>
<span id="cb6-13"></span>
<span id="cb6-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        :param start_time: 開始時間，格式為 'YYYY-MM-DD HH:MM' (例如: '2026-06-06 08:00')。</span></span>
<span id="cb6-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        :param end_time: 結束時間，格式為 'YYYY-MM-DD HH:MM' (例如: '2026-06-06 12:00')。</span></span>
<span id="cb6-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb6-17">        url <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://host.docker.internal:8000/check_status"</span></span>
<span id="cb6-18">        params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"start_time"</span>: start_time, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"end_time"</span>: end_time}</span>
<span id="cb6-19"></span>
<span id="cb6-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb6-21">            response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(url, params<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>params, timeout<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb6-22"></span>
<span id="cb6-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> response.status_code <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>:</span>
<span id="cb6-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"系統 API 回傳錯誤碼: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>response<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>status_code<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">，請確認 FastAPI 終端機狀態。"</span></span>
<span id="cb6-25"></span>
<span id="cb6-26">            data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.json()</span>
<span id="cb6-27"></span>
<span id="cb6-28">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 處理無監控數據的防呆情況</span></span>
<span id="cb6-29">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> data.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no_data"</span>:</span>
<span id="cb6-30">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (</span>
<span id="cb6-31">                    <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"系統回報：在 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>start_time<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 到 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>end_time<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 之間找不到任何監控數據。"</span></span>
<span id="cb6-32">                )</span>
<span id="cb6-33"></span>
<span id="cb6-34">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 獲取核心數據結構</span></span>
<span id="cb6-35">            stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"stats"</span>, {})</span>
<span id="cb6-36">            logs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"logs"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="cb6-37">            rf_decision <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rf_decision"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UNKNOWN"</span>)</span>
<span id="cb6-38"></span>
<span id="cb6-39">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 完美包裝雙重審計任務，將最終的推推翻權力交給 LLM</span></span>
<span id="cb6-40">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (</span>
<span id="cb6-41">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"【系統維運數據雙重審計任務】</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-42">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"你現在扮演資深雲端架構師與資深運維專家（SRE）。請嚴格根據以下真實數據進行繁體中文分析，絕對禁止憑空捏造任何未提及的數字、具體IP地址（如192.168.x.x）或特定未知的狀態碼。</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-43">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"🤖 [1. 隨機森林模型預測結論]:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-44">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 模型標記為異常的時間比例：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>stats<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'actual_anomaly_rate_percent'</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-45">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"- 模型基礎判定狀態：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>rf_decision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-46">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 [2. 基礎監控日誌數據（鐵證）]:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-47">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>logs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-48">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📝 【請嚴格遵循以下運維邏輯產出最終報告】：</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-49">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"1. **數據真實性審查（核心判斷）**：</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-50">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   - 仔細檢查日誌中的『全時段平均 P95 回應時間』。如果是常態毫秒級（例如低於 500ms）且『最高 5xx 錯誤率』為 0.00%，即使隨機森林模型的異常比例很高，你也必須主動指出：『**隨機森林模型在此處判斷為誤報（False Positive）**，實際系統各項指標均非常健康正常。』，並宣告系統無風險。</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-51">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   - 如果回應時間確實破千（ms）或 5xx 錯誤率明顯飆高，請認同隨機森林的判斷，並開始深入分析真正的效能瓶頸。</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-52">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"2. **最終報告結構**：</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-53">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   - 【模型判定與稽核結論】（必須明確點出是否屬於模型誤報）</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-54">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   - 【數據指標解讀】（引用數據中的總流量、回應時間、錯誤率進行白話說明）</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-55">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   - 【專家建議應變措施】（若健康則寫無須動作、持續監控即可；若異常則給出優化方向）"</span></span>
<span id="cb6-56">            )</span>
<span id="cb6-57"></span>
<span id="cb6-58">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb6-59">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"連線到異常偵測 API 失敗，錯誤原因: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">。請確認 FastAPI 有跑起來。"</span></span></code></pre></div>
<p>其中 <code>url = "http://host.docker.internal:8000/check_status"</code>，是因為本次的執行環境設定在 Docker 的 Open WebUI，但是剛剛做好的 rf-api 不在 docker 裡，故調整語法讓它能接到。</p>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
🤫 悄悄話
</div>
</div>
<div class="callout-body-container callout-body">
<p>如果 Open WebUI 不是用docker 建立的話填 api 建立的網址就好，如以下範例</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">url <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/check_status"</span></span></code></pre></div>
</div>
</div>
<p>儲存為 <code>rf-api.py</code></p>
<ol start="2" type="1">
<li>在模型介面中指定模型(這裡指定的是 <code>llama3.2.3b</code>)，輸入以下 prompt，並勾選到剛才建立的 rf-api</li>
</ol>
<pre><code># 角色與任務
你是一位資深的 SRE 與 AI-Ops 專家。你的任務是接收「隨機森林模型（Random Forest）」的流量檢測結果，並在模型判定異常時，結合系統撈取的「網站 Log 紀錄」，撰寫一份精準的網站流量異常原因分析報告。

# 工作流程
當用戶詢問網站流量狀態時，你必須依序執行以下動作：
1. 【呼叫檢測工具】：調用工具執行隨機森林模型，檢查過去 1 小時的流量是否異常。
2. 【分支判斷】：
   - 若模型判定【正常】：簡短回報系統安全，並附上模型的核心指標（如流量預測機率）。
   - 若模型判定【異常】：必須觸發 Log 查詢機制，分析日誌中的關鍵錯誤訊息。
3. 【撰寫報告】：依據 Log 內容，推導出真正的故障根因，並產出結構化報告。

# ⚠️ 重要注意事項：

工具回傳的 extracted_logs 是動態生成的。請務必仔細閱讀 Log 中的 IP 地址、錯誤訊息（如 429 Too Many Requests 或 Database Timeout），並在報告的「3. 異常根因分析」中，精準指出是哪一種故障（例如：區分出到底是外部惡意攻擊，還是內部資料庫塞車）。

# 報告輸出格式（僅在異常時使用）
🚨 【AI-Ops 流量異常與 Log 根因分析報告】
---
### 1. 檢測概述
- **發生時間**：[填寫時間]
- **AI 模型判定**：⚠️ 隨機森林檢測到異常流量（異常機率：XX%）

### 2. 關鍵 Log 診斷
請摘要列出導致異常的關鍵 Log 紀錄（例如 HTTP 狀態碼分佈、重複的 Error Stack Trace）：
&gt; [在此處貼上或摘要關鍵 Log 內容]

### 3. 異常根因分析 (Root Cause)
- [結合隨機森林的流量特徵與 Log 訊息，用專業角度解釋發生了什麼事。例如：IP 192.168.x.x 在短時間內發送大量 POST 請求導致資料庫鎖定 / 大量 404 代表有惡意爬蟲在掃描後台目錄]

### 4. 建議應變措施
1. 🛠️ [具體動作 1]
2. 🛠️ [具體動作 2]</code></pre>
<p><img src="https://paperfishblog.netlify.app/posts/llm/poc-verify/setting1.png" class="img-fluid"></p>
<p>成功的話對話介面會顯示可用工具的數量圖示，即可開始測試！</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/poc-verify/icon.png" class="img-fluid"></p>
</section>
<section id="測試案例" class="level2">
<h2 class="anchored" data-anchor-id="測試案例">測試案例</h2>
<p>在對話框中輸入「幫我查 2026-06-06 08:00 到 12:00 的系統狀態，並產出預警分析報告。」</p>
<p>系統產生以下對話</p>
<pre><code>根據提供的真實數據，首先，我們需要進一步檢視 2026-06-06 08:00 到 12:00 之間的系統狀態。

📊 [2. 基礎監控日誌數據（鐵證）]:
【時段查詢時段監控指標概況】：
- 總請求流量: 25349 次
- 平均不重複連線 IP 數: 87.8 個
- 全時段平均 P95 回應時間: 428.23 ms
- 最高 4xx 客戶端錯誤率: 62.21%
- 最高 5xx 伺服器錯誤率: 10.92%

根據上述數據，系統在此時段已經完成了大量的請求流量，平均 P95 回應時間為 428.23 ms。雖然這個值稍超過常態的毫秒級，但我們必須進一步分析這些數據以確定是否屬於模型誤報（False Positive）。

📝 【請嚴格遵循以下運維邏輯產出最終報告】：
1. **數據真實性審查（核心判斷）**：
   - 仔細檢看日誌中的『全時段平均 P95 回應時間』。因為此值雖稍超常規，但『最高 5xx 錯誤率』仍為 10.92%，且模型判定為異常的時間比例為 78.42%。這些數據與系統實際狀況的一致性不完全符合模型的預測，需要深入分析。
   - 我們將針對 2026-06-06 08:00 到 12:00 這一時間段進行進一步分析。

📊 [2. 基礎監控日誌數據（鐵證）] (持續):
- 08:00 到 10:00：請求流量為 10121 次，平均不重複連線 IP 數為 93.3 個。
- 10:00 到 12:00：請求流量為 7528 次，平均不重複連線 IP 數為 84.5 個。

由此，我們可以觀察到 08:00 到 10:00 的時間段，系統的請求流量和 P95 回應時間相對穩定，但是在 10:00 到 12:00 的時間段，卻出現了明顯的波動。這個變化可能與惡意爬蟲或其他故障原因有關。

📝 【最後的建議和預警分析】：
- 因為系統在此時段出現了波動，我們建議立即開始監控並調整系統，避免因過度負擔而導致故障。
- 我們需要考慮是否有惡意爬蟲或其他影響系統狀態的原因，進一步分析 Log 內容，以確定真正的原因。

🚨【AI-Ops 流量異常與 Log 根因分析報告】

### 1. 檢測概述
- **發生時間**：2026-06-06 08:00 到 12:00
- **AI 模型判定**：⚠️ Random Forest 检测到異常流量（異常機率：78.42%）

### 2. 關鍵 Log 診斷
請摘要列出導致異常的關鍵 Log 紀錄（例如 HTTP 狀態碼分佈、重複的 Error Stack Trace）：
&gt; - 2026-06-06 09:47 -&gt; 流量: 187次, 不重複IP數: 105個, P95回應時間: 2661.5ms, 4xx錯誤率: 32.09%, 惡意爬蟲UA比例: 19.79%
- 2026-06-06 10:01 -&gt; 流量: 147次, 不重複IP數: 57個, P95回應時間: 2937.0ms, 4xx錯誤率: 54.42%, 5xx錯誤率: 8.16%, 惡意爲行UA比例: 31.29%

### 3. 異常根因分析 (Root Cause)
由於 Log 內容顯示出多次異常的 P95 回應時間，且最低 4xx 錯誤率明顯增加，我們需要懷疑是否存在惡意爬蟲或其他影響系統狀態的原因。這些數據與系統實際狀況的一致性不完全符合模型的預測，需要進一步分析。

### 4. 建議應變措施
1. 🛠️ [具體動作 1]：立即增加伺服器的 CPU 和 RAM 的資源，以處理過多請求流量。
2. 🛠️ [具體動作 2]：啟用防範惡意爬蟲的安全機制，例如 IP 對應限制和防止異常的 HTTP Request。</code></pre>
<p><img src="https://paperfishblog.netlify.app/posts/llm/poc-verify/Q1.png" class="img-fluid"></p>
<p>代表隨機森林傳來的是可能真的是異常的資料。在 jupyter notebook 上寫一支驗證腳本確認是否可信。</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb10-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb10-3"></span>
<span id="cb10-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 載入資料 (請確保路徑正確)</span></span>
<span id="cb10-5">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"features-test.csv"</span>)</span>
<span id="cb10-6">df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.to_datetime(df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>])</span>
<span id="cb10-7"></span>
<span id="cb10-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 鎖定目標時段</span></span>
<span id="cb10-9">start_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-06-06 08:00:00"</span></span>
<span id="cb10-10">end_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-06-06 12:00:00"</span></span>
<span id="cb10-11">mask <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> start_time) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (df[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> end_time)</span>
<span id="cb10-12">df_period <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.loc[mask].copy()</span>
<span id="cb10-13"></span>
<span id="cb10-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"=== 🛠️ 驗證 LLM 報告中的全時段指標概況 ==="</span>)</span>
<span id="cb10-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 實際總請求流量：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 次 (LLM 宣稱: 25349 次)"</span>)</span>
<span id="cb10-16"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 平均不重複 IP 數：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'unique_ips'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 個 (LLM 宣稱: 87.8 個)"</span>)</span>
<span id="cb10-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 全時段平均 P95 回應時間：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>mean()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms (LLM 宣稱: 428.23 ms)"</span>)</span>
<span id="cb10-18"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 最高 4xx 客戶端錯誤率：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">% (LLM 宣稱: 62.21%)"</span>)</span>
<span id="cb10-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"📊 最高 5xx 伺服器錯誤率：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">% (LLM 宣稱: 10.92%)"</span>)</span>
<span id="cb10-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>)</span>
<span id="cb10-21"></span>
<span id="cb10-22"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">=== 🔍 深入稽核：LLM 抓出的關鍵出事時間點 ==="</span>)</span>
<span id="cb10-23">target_minutes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-06-06 09:47:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-06-06 10:01:00"</span>]</span>
<span id="cb10-24">df_targets <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df_period[df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>].isin(target_minutes)]</span>
<span id="cb10-25"></span>
<span id="cb10-26"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> df_targets.empty:</span>
<span id="cb10-27">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"❌ 警告：這兩個時間點在 CSV 裡找不到，可能是 LLM 幻覺！"</span>)</span>
<span id="cb10-28"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb10-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx, row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> df_targets.iterrows():</span>
<span id="cb10-30">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">⏰ 時間點: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'minute'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-31">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   -&gt; 實際流量: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'requests_per_min'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 次"</span>)</span>
<span id="cb10-32">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   -&gt; P95 回應時間: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ms"</span>)</span>
<span id="cb10-33">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   -&gt; 4xx 錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb10-34">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   -&gt; 5xx 錯誤率: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb10-35">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"   -&gt; 惡意爬蟲 UA 比例: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bot_attack_ua_ratio'</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">%"</span>)</span>
<span id="cb10-36">        </span>
<span id="cb10-37">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 判定是否為誤報</span></span>
<span id="cb10-38">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'p95_response_time'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'4xx_ratio'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'5xx_ratio'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>:</span>
<span id="cb10-39">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"   🔥 稽核結論：這不是誤報！系統此時真的在慘叫，隨機森林抓得對！"</span>)</span>
<span id="cb10-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb10-41">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"   🟢 稽核結論：指標很健康，此處確實是模型誤報（False Positive）。"</span>)</span></code></pre></div>
<p>結果為</p>
<pre><code>=== 🛠️ 驗證 LLM 報告中的全時段指標概況 ===
📊 實際總請求流量：25349 次 (LLM 宣稱: 25349 次)
📊 平均不重複 IP 數：87.75 個 (LLM 宣稱: 87.8 個)
📊 全時段平均 P95 回應時間：428.23 ms (LLM 宣稱: 428.23 ms)
📊 最高 4xx 客戶端錯誤率：62.21% (LLM 宣稱: 62.21%)
📊 最高 5xx 伺服器錯誤率：10.92% (LLM 宣稱: 10.92%)
------------------------------------------------------------

=== 🔍 深入稽核：LLM 抓出的關鍵出事時間點 ===

⏰ 時間點: 2026-06-06 09:47:00
   -&gt; 實際流量: 187 次
   -&gt; P95 回應時間: 2661.5 ms
   -&gt; 4xx 錯誤率: 32.09%
   -&gt; 5xx 錯誤率: 9.09%
   -&gt; 惡意爬蟲 UA 比例: 19.79%
   🔥 稽核結論：這不是誤報！系統此時真的在慘叫，隨機森林抓得對！

⏰ 時間點: 2026-06-06 10:01:00
   -&gt; 實際流量: 147 次
   -&gt; P95 回應時間: 2937.0 ms
   -&gt; 4xx 錯誤率: 54.42%
   -&gt; 5xx 錯誤率: 8.16%
   -&gt; 惡意爬蟲 UA 比例: 31.29%
   🔥 稽核結論：這不是誤報！系統此時真的在慘叫，隨機森林抓得對！</code></pre>
<p>代表隨機森林預測正確，系統也有確實抓出。</p>
</section>
</section>
<section id="所以我的設計出了什麼問題" class="level1">
<h1>所以我的設計出了什麼問題？</h1>
<section id="第一個最大的錯" class="level2">
<h2 class="anchored" data-anchor-id="第一個最大的錯">第一個(最大的錯)</h2>
<p>前面的功能都是能正常實作出來的，功能也都是正常的。不過讓我們回顧混淆矩陣的概念</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 37%">
<col style="width: 37%">
</colgroup>
<thead>
<tr class="header">
<th>實際情況 &nbsp;預測結果</th>
<th>預測為 0（正常流量）</th>
<th>預測為 1（異常流量）</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>實際為 0（正常流量）</td>
<td>True Positive (TP) 真陽性</td>
<td><em>False Positive (FP) 假陽性</em></td>
</tr>
<tr class="even">
<td>實際為 1（異常流量）</td>
<td><em>False Negative (FN) 假陰性</em></td>
<td>True Negative (TN) 真陰性</td>
</tr>
</tbody>
</table>
<p>斜體字的部份極為預測錯誤，其中 FN 造成損害最為嚴重。再來看看前面的 API 程式中的這一段:</p>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. 格式化「隨機森林認為異常」的時間點日誌，翻譯成白話文</span></span>
<span id="cb12-2">        anomalies <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df_period[df_period[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pred_label'</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb12-3">        log_lines <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb12-4">        </span>
<span id="cb12-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> anomalies.empty:</span>
<span id="cb12-6">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># bra bra bra </span></span></code></pre></div>
<p><strong>FN 的資料根本沒有傳給 LLM !!!(哭)</strong></p>
<p>也就是說，隨機森林的預測如果有誤判，只有實際是正常但誤判為異常的資料有機會被抓出來，真正是異常卻誤判的資料不會抓到。當初我的設計理念是 LLM 的分類預測效果不見得有比隨機森林或其他 ML 技術可靠，所以才這樣設計，結果反而錯過最需要避免的情況 QQ。除非這是允許沒發現異常的情境，不然這是最嚴重的錯誤，需要改掉QQ。</p>
</section>
<section id="第二個" class="level2">
<h2 class="anchored" data-anchor-id="第二個">第二個</h2>
<p>跟第一個相比這個錯誤相對輕微許多，只是因為 LLM 隨機森林預測的結果跟相關指標，其實完全看不到原始 log。但是按照老師上課時的分享，最終目標是要找出原因，會需要知道可疑 IP 或被攻擊的目標(子網域)等等，這些資訊因為給隨機森林的時候沒有列出，所以最終報告沒有分析到，也就是說對於 Soc 的文書報告生產效率影響有限。不過這點對於 Poc 並不是必要條件，只是我希望它的使用情境能更貼近現實一點才列入。</p>
</section>
</section>
<section id="結論" class="level1">
<h1>結論</h1>
<p>要解決前面兩個問題，第一個需要先處理 API 回傳格式，Prompt 也要視情況調整。第二個屬於擴充功能的範圍，查了資料發現可能更費工，需要等第一個錯誤確定後再來改<del>不知道有沒有空改</del>。另外我也想試試測量 LLM 具體可能提升的準確率，說不定 LLM 的分類結果沒有我想像中差？甚至說不定<del>其實根本不需要隨機森林，LLM 自己就能做出良好預測了</del>？<sup>3</sup> 總之檢討先就到這，希望七月底前可以至少再寫一篇後續~~</p>



</section>


<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">腳註</h2>

<ol>
<li id="fn1"><p>我一開始還以為是要做新產品(Phototype)，結果就往還有哪些產品還沒被創造出來的方向想，好糗…↩︎</p></li>
<li id="fn2"><p>不過好像都是儀錶板(dashboard)為主，LLM 為輔(沒有真的使用過每個產品所以我也不敢很確定)，沒有靠對話生成報告的產品。↩︎</p></li>
<li id="fn3"><p>但老實說我覺得不太可能，有機會再寫一篇文章說說為什麼。↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>LLM</category>
  <category>Random Forest</category>
  <category>time series</category>
  <category>abnormal analysis</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/poc-verify/</guid>
  <pubDate>Tue, 14 Jul 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/poc-verify/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>在免費 google colab 跟 glow.ai 上的模型微調體驗 &amp; 流程紀錄</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/fintune-test/</link>
  <description><![CDATA[ 






<section id="緣起" class="level1">
<h1>緣起</h1>
<p>接續<a href="https://paperfishblog.netlify.app/posts/llm/play-langgraph-in-linux/">上一篇文章</a>，一開始為了寫 LLM cybersecurity 的作業又不想花 credit，於是我用免費版 google colab 來完成。<sup>1</sup>成為 credit 富翁後再來用用 glow.ai 上的設備，體驗大幅上升。這篇文章便來記錄我在免費 google colab 跟 glow.ai 上的流程跟心得。</p>
</section>
<section id="免費-google-colab-的執行流程" class="level1">
<h1>免費 google colab 的執行流程</h1>
<section id="資料集介紹-目標" class="level2">
<h2 class="anchored" data-anchor-id="資料集介紹-目標">資料集介紹 &amp; 目標</h2>
<ul>
<li><p>資料集：<a href="https://www.kaggle.com/datasets/subhajournal/phishingemails">kaggle 上的 Phishing Email Detection 資料集</a>，裡面只有兩個欄位。</p></li>
<li><p>Email Text: 含信件標題與內文</p></li>
<li><p>Email Type: 兩種類別(Safe Email / Phishing Email)</p></li>
</ul>
<p>資料集共 18650 筆資料。我們的目標是使用微調 LoRA 技術，讓離線 LLM 可以成功分類釣魚信件。</p>
</section>
<section id="執行步驟google-colab-版" class="level2">
<h2 class="anchored" data-anchor-id="執行步驟google-colab-版">執行步驟(Google colab 版)</h2>
<blockquote class="blockquote">
<p>記得要開 GPU</p>
</blockquote>
<section id="啟動-llama-factory" class="level3">
<h3 class="anchored" data-anchor-id="啟動-llama-factory">啟動 LLaMA Factory</h3>
<ol type="1">
<li>開啟新的 google colab 裝取所需環境套件</li>
</ol>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 下載 Llama-Factory</span></span>
<span id="cb1-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>git clone <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span>depth <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> https:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span>github.com<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>hiyouga<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>LLaMA<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>Factory.git</span>
<span id="cb1-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span>cd LLaMA<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>Factory</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 安裝依賴環境</span></span>
<span id="cb1-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>pip install <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>e <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".[torch,metrics]"</span></span>
<span id="cb1-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>pip install <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span>no<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>deps unsloth <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 推薦安裝 unsloth 加速</span></span>
<span id="cb1-8"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>pip install bitsandbytes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.39.0</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 為了設定 Quantization bit 用</span></span></code></pre></div>
<ol start="2" type="1">
<li>打開 google colab 的終端機輸入</li>
</ol>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">llamafactory-cli</span> webui <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--share</span> </span></code></pre></div>
<ol start="3" type="1">
<li>回到 notebook 介面，新的 cell 輸入</li>
</ol>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> google.colab.output <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> eval_js</span>
<span id="cb3-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"請點擊下方網址進入 Llama-Factory 介面："</span>)</span>
<span id="cb3-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(eval_js(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"google.colab.kernel.proxyPort(7860)"</span>))</span></code></pre></div>
<p>這段程式會強制 Colab 將它內部的 7860 port（也就是 Llama-Factory 跑起來的地方）映射到一個可以點擊的外部網址。</p>
</section>
<section id="資料前處理-上傳到-llama-factory-可以看到的地方" class="level3">
<h3 class="anchored" data-anchor-id="資料前處理-上傳到-llama-factory-可以看到的地方">資料前處理 &amp; 上傳到 LLaMA Factory 可以看到的地方</h3>
<ol type="1">
<li>使用 kaggle 上的資料集下載檔後，再輸入以下</li>
</ol>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 讀取 Kaggle 資料集 (請確保路徑正確，此例檔名是 phishing_emails.csv)</span></span>
<span id="cb4-6">csv_file <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Phishing_Email.csv"</span>) </span>
<span id="cb4-7">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(csv_file)</span>
<span id="cb4-8"></span>
<span id="cb4-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 轉換為 Llama-Factory 格式 (這裡假設欄位名稱，請根據你的 CSV 調整)</span></span>
<span id="cb4-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 假設 "Email Text" 是內容，"Label" 是標籤</span></span>
<span id="cb4-11">formatted_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb4-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> df.iterrows():</span>
<span id="cb4-13">    formatted_data.append({</span>
<span id="cb4-14">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"instruction"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"判斷這封郵件是否為釣魚郵件："</span>,</span>
<span id="cb4-15">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Email Text"</span>]),</span>
<span id="cb4-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Email Type"</span>])</span>
<span id="cb4-17">    })</span>
<span id="cb4-18"></span>
<span id="cb4-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 儲存到 Llama-Factory 的 data 資料夾</span></span>
<span id="cb4-20">save_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/content/LLaMA-Factory/data/phishing_data.json"</span></span>
<span id="cb4-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(save_path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"w"</span>, encoding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb4-22">    json.dump(formatted_data, f, indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ensure_ascii<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb4-23"></span>
<span id="cb4-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"資料集已轉換並儲存至: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>save_path<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
<p>這會將資料從 <code>.csv</code> 轉成 <code>.json</code>，並且處理成 LLaMA Factory 可接受格式。</p>
</section>
<section id="llama-factory-訓練設定" class="level3">
<h3 class="anchored" data-anchor-id="llama-factory-訓練設定">LLaMA Factory 訓練設定</h3>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/trainng-set.png" class="img-fluid"></p>
<ul>
<li><p>模型 : Qwen2.5-7B-Instruct ，因為只是辨認是否為釣魚信件(2 種可能)，不必用非常大的模型應該就能處理</p></li>
<li><p>Fine-tuning method : lora</p></li>
<li><p>Quantization bit : 4，這個參數可以將模型壓縮到原來的<img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B4%7D"></p></li>
<li><p>Cutoff length: 512，主要避免內存爆掉，其次是觀察釣魚信件的特徵，關鍵通常都藏在 1-2 句話中</p></li>
<li><p>Learning rate: 5e-5</p></li>
<li><p>Epochs: 2.0 ，有足夠樣本數的情況下可以不用這麼多次</p></li>
<li><p>Batch size: 1，避免內存爆掉</p></li>
<li><p>Gradient accumulation: 32，影響 GPU 每次處理的資料</p></li>
</ul>
<p>使用免費版 google colab 最需要注意內存爆掉的問題，因此設定要盡量設小，模型也要選小一點。未來如果還有需要用免費板 colab 訓練，以上設定也可以參考。<sup>2</sup></p>
</section>
<section id="執行結果" class="level3">
<h3 class="anchored" data-anchor-id="執行結果">執行結果</h3>
<p>Loss 訓練圖</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/下載.webp" class="img-fluid"></p>
<p>這裡 x 軸的 Step 計算方式為</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BTotal%20Steps%7D%20=%20%5Cfrac%7B%5Ctext%7B%E8%B3%87%E6%96%99%E7%B8%BD%E6%95%B8%7D%20%5Ctimes%20%5Ctext%7BEpochs%7D%7D%7B%5Ctext%7BTotal%20Batch%20Size%7D%7D"></p>
<p>主要變化</p>
<ol type="1">
<li><p>11.54 (Epoch 0.01，186 筆)</p></li>
<li><p>7.24 (Epoch 0.02，371 筆)</p></li>
<li><p>3.60 (Epoch 0.03，457 筆)</p></li>
<li><p>2.29(Epoch 0.04，752 筆)</p></li>
<li><p>1.6 (Epoch 0.05，927 筆)</p></li>
</ol>
<p>最後怕觸發免費 colab 的使用限制而提早停止，最終訓練到的資料共 927 筆，耗時約 2 小時，好久：（。</p>
<p><strong>信件測試：</strong></p>
<p>到 Chat 區載入 checkpoint path(相當於執行結果模型)，進行測試</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/check-path.png" class="img-fluid"></p>
<p>安全信件測試</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/s-test.png" class="img-fluid"></p>
<p>釣魚信件測試</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/p-test.png" class="img-fluid"></p>
<p>以 chat 區的結果來說全對，但當時沒用數據看結果，可惜了TT 但如果看訓練圖，會發現其實還有不小的改進空間。</p>
</section>
</section>
</section>
<section id="在-glows.ai-上的執行流程" class="level1">
<h1>在 Glows.ai 上的執行流程</h1>
<div class="callout callout-style-simple callout-note">
<div class="callout-body d-flex">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-body-container">
<p>這次使用的機器是 NVIDIA GeForce RTX 4090，可以用 HTTP 連線了！</p>
</div>
</div>
</div>
<section id="資料集介紹-目標-1" class="level2">
<h2 class="anchored" data-anchor-id="資料集介紹-目標-1">資料集介紹 &amp; 目標</h2>
<ul>
<li><p>資料集：<a href="https://www.kaggle.com/datasets/antobenedetti/finance-news-sentiments/data?utm_source=chatgpt.com">Kaggle 上的 Finance News Sentiments 資料集</a>，裡面也是有兩個欄位。</p></li>
<li><p>News Text: 金融新聞內容（包含標題與一段內文）</p></li>
<li><p>Sentiment Label: 三種類別（Positive / Negative / Neutral），用於表示新聞對市場情緒的影響</p></li>
</ul>
<p>共 32,569 筆，一樣使用微調 LoRA 技術，讓離線 LLM 可以成功分類新聞情緒。</p>
</section>
<section id="執行步驟glows.ai-版" class="level2">
<h2 class="anchored" data-anchor-id="執行步驟glows.ai-版">執行步驟(Glows.ai 版)</h2>
<ol type="1">
<li><p>開啟 HTTP port 連線</p></li>
<li><p>導入資料集、對資料做前處理</p></li>
</ol>
<p>這一步與 google colab 類似，快速快過</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>pip install kagglehub[pandas<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>datasets]</span></code></pre></div>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> kagglehub</span>
<span id="cb6-2"></span>
<span id="cb6-3">path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> kagglehub.dataset_download(</span>
<span id="cb6-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"antobenedetti/finance-news-sentiments"</span></span>
<span id="cb6-5">)</span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(path)</span>
<span id="cb6-8"></span>
<span id="cb6-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb6-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(os.listdir(path))</span></code></pre></div>
<p>確定檔名後測試能不能導入</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb7-2"></span>
<span id="cb7-3">csv_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/dataset.csv"</span></span>
<span id="cb7-4"></span>
<span id="cb7-5">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(csv_path)</span>
<span id="cb7-6"></span>
<span id="cb7-7">df.head()</span></code></pre></div>
<p>確定 OK 後調整資料集成 LLaMA Factory 可接受格式</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb8-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="cb8-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb8-4"></span>
<span id="cb8-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 讀取 Kaggle 資料集 (請確保路徑正確)</span></span>
<span id="cb8-6">csv_file <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dataset.csv"</span>)</span>
<span id="cb8-7">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.read_csv(csv_file)</span>
<span id="cb8-8"></span>
<span id="cb8-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 轉換為 Llama-Factory 格式 (這裡假設欄位名稱，請根據你的 CSV 調整)</span></span>
<span id="cb8-10">formatted_data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb8-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _, row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> df.iterrows():</span>
<span id="cb8-12">    formatted_data.append({</span>
<span id="cb8-13">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"instruction"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"判斷這則財經新聞的情緒："</span>,</span>
<span id="cb8-14">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>]),</span>
<span id="cb8-15">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sentiment"</span>])</span>
<span id="cb8-16">    })</span>
<span id="cb8-17"></span>
<span id="cb8-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 儲存到 Llama-Factory 的 data 資料夾</span></span>
<span id="cb8-19">save_path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/LLaMA-Factory/data/news_data.json"</span></span>
<span id="cb8-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(save_path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"w"</span>, encoding<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb8-21">    json.dump(formatted_data, f, indent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ensure_ascii<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb8-22"></span>
<span id="cb8-23"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"資料集已轉換並儲存至: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>save_path<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
<ol start="3" type="1">
<li>調整 <code>LLaMA-Factory\data</code> 裡的 <code>dataset_infon.json</code></li>
</ol>
<pre><code>{
  "news_data": {
    "file_name": "news_data.json",
    "formatting": "alpaca",
    "columns": {
      "prompt": "instruction",
      "query": "input",
      "response": "output"
    }
  },</code></pre>
<section id="在-llama-factory-訓練" class="level3">
<h3 class="anchored" data-anchor-id="在-llama-factory-訓練">在 LLaMA Factory 訓練</h3>
<p>打開 LLaMA Factory 的 HTTP Port，輸入剛剛建立好的 dataset 後即可開始訓練，最終訓練設定如下：</p>
<ul>
<li><p>Model: Qwen2.5-7B-Instruct</p></li>
<li><p>learning rate: 2e-4</p></li>
<li><p>Epochs: 1</p></li>
<li><p>Maximum gradient norm: 1</p></li>
<li><p>Max samples: 100000</p></li>
<li><p>Compute type: fp16 (重要，選錯可能會跑不動)</p></li>
<li><p>cutoff length: 256</p></li>
<li><p>batch: 16</p></li>
<li><p>Gradient accumulation: 4</p></li>
</ul>
<blockquote class="blockquote">
<p>速度: 2$$4=8</p>
</blockquote>
<ul>
<li><p>val_size: 0.2</p></li>
<li><p>LR scheduler: cosine</p></li>
</ul>
<p>因為現在資源比較多，設定可以奢侈一點~這個設定只花沒幾分鐘就練好了，讚。</p>
</section>
<section id="訓練結果" class="level3">
<h3 class="anchored" data-anchor-id="訓練結果">訓練結果</h3>
<p><img src="https://paperfishblog.netlify.app/posts/llm/fintune-test/finish2.webp" class="img-fluid"></p>
<p>看起來不錯，至少比在 Colab 上的情況要好很多。</p>
<p>再來看看 perdition</p>
<p>setting</p>
<ul>
<li><p>cutoff: 256</p></li>
<li><p>Max samples: 100000</p></li>
<li><p>Batch size: 16</p></li>
<li><p>Maximum new tokens: 8</p></li>
<li><p>Top-p: 0.7</p></li>
<li><p>Temperature: 0.95</p></li>
</ul>
<p>評估 result:</p>
<p>{ “predict_bleu-4”: 0.6883751680324095, “predict_model_preparation_time”: 0.0022, “predict_rouge-1”: 0.7306242519105055, “predict_rouge-2”: 0.0, “predict_rouge-l”: 0.7260624436055612, “predict_runtime”: 266.5178, “predict_samples_per_second”: 122.255, “predict_steps_per_second”: 7.643 }</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
指標解釋
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>AI 附的快速簡單看法</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>指標</th>
<th>本質</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>BLEU-4</td>
<td>1~4-gram precision + brevity penalty</td>
</tr>
<tr class="even">
<td>ROUGE-1</td>
<td>unigram recall</td>
</tr>
<tr class="odd">
<td>ROUGE-2</td>
<td>bigram recall</td>
</tr>
<tr class="even">
<td>ROUGE-L</td>
<td>LCS-based F1</td>
</tr>
</tbody>
</table>
<ul>
<li>BLEU：偏「寫得像不像原句（precision）」，越高越好。</li>
<li>ROUGE：偏「有沒有講到重點（recall）」，越高越好。</li>
<li>ROUGE-L：更偏「句子結構是否一致」</li>
</ul>
<p><strong>BLEU-4（predict_bleu-4）</strong></p>
<p>BLEU 是 <em>precision-based n-gram overlap 與 brevity penalty</em>的結合，基本公式</p>
<p><img src="https://latex.codecogs.com/png.latex?BLEU%20=%20BP%20%5Ccdot%20%5Cexp%5Cleft(%5Csum_%7Bn=1%7D%5E%7B4%7D%20w_n%20%5Clog%20p_n%5Cright)"></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?p_n">：n-gram precision</li>
<li><img src="https://latex.codecogs.com/png.latex?w_n">：權重（通常 <img src="https://latex.codecogs.com/png.latex?w_n%20=%201/4">）</li>
<li>BP：brevity penalty（避免生成太短的懲罰項）</li>
</ul>
<p>其中</p>
<p>n-gram precision (<img src="https://latex.codecogs.com/png.latex?p_n">) 為</p>
<p><img src="https://latex.codecogs.com/png.latex?p_n%20=%20%5Cfrac%7B%5Csum%20%5Ctext%7Bclip(count%20of%20n-gram)%7D%7D%7B%5Csum%20%5Ctext%7Bcount%20of%20n-gram%20in%20candidate%7D%7D"></p>
<p>Brevity Penalty (BP) 為</p>
<p><img src="https://latex.codecogs.com/png.latex?%0ABP%20=%0A%5Cbegin%7Bcases%7D%0A1%20&amp;%20c%20%3E%20r%20%5C%5C%0Ae%5E%7B(1%20-%20r/c)%7D%20&amp;%20c%20%5Cle%20r%0A%5Cend%7Bcases%7D%0A"></p>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?c">：生成句長度</li>
<li><img src="https://latex.codecogs.com/png.latex?r">：參考句長度</li>
</ul>
<p><strong>ROUGE-N（ROUGE-1 / ROUGE-2）</strong></p>
<p>ROUGE 是 <em>recall-oriented overlap</em>的意思，公式</p>
<ol type="1">
<li>ROUGE-N recall（核心公式）</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?ROUGE%5Ctext%7B-%7DN%20=%20%5Cfrac%7B%5Csum%20%5Ctext%7Boverlap%20n-grams%7D%7D%7B%5Csum%20%5Ctext%7Bn-grams%20in%20reference%7D%7D"></p>
<ol start="2" type="1">
<li>ROUGE-N precision</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%20=%20%5Cfrac%7B%5Ctext%7Boverlap%20n-grams%7D%7D%7B%5Ctext%7Bn-grams%20in%20candidate%7D%7D%0A"></p>
<ol start="3" type="1">
<li>ROUGE-N F1（最常用）</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?F1%20=%20%5Cfrac%7B2PR%7D%7BP%20+%20R%7D"></p>
<p>特例</p>
<section id="rouge-1" class="level3">
<h3 class="anchored" data-anchor-id="rouge-1">ROUGE-1</h3>
<ul>
<li>n=1（unigram）</li>
</ul>
</section>
<section id="rouge-2" class="level3">
<h3 class="anchored" data-anchor-id="rouge-2">ROUGE-2</h3>
<ul>
<li>n=2（bigram）</li>
</ul>
<hr>
<p><strong>ROUGE-L（最長共同子序列 LCS）</strong></p>
<ol type="1">
<li>LCS 定義</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?%0ALCS%20=%20%5Ctext%7Blongest%20common%20subsequence%7D%0A"></p>
<hr>
<ol start="2" type="1">
<li>Recall</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?R_%7BLCS%7D%20=%20%5Cfrac%7BLCS(ref,%20cand)%7D%7B%7Cref%7C%7D"></p>
<ol start="3" type="1">
<li>Precision</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?P_%7BLCS%7D%20=%20%5Cfrac%7BLCS(ref,%20cand)%7D%7B%7Ccand%7C%7D"></p>
<ol start="4" type="1">
<li>ROUGE-L F1</li>
</ol>
<p><img src="https://latex.codecogs.com/png.latex?ROUGE%5Ctext%7B-%7DL%20=%20%5Cfrac%7B(1%20+%20%5Cbeta%5E2)%20P%20R%7D%7BR%20+%20%5Cbeta%5E2%20P%7D"></p>
<ul>
<li>通常 <img src="https://latex.codecogs.com/png.latex?%5Cbeta%20=%201"></li>
</ul>
<p>所以簡化版：</p>
<p><img src="https://latex.codecogs.com/png.latex?ROUGE%5Ctext%7B-%7DL%20=%20%5Cfrac%7B2PR%7D%7BP%20+%20R%7D"></p>
<p><strong>ROUGE-W（加權 LCS）</strong></p>
<p>（有些 toolkit 會用）</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AROUGE%5Ctext%7B-%7DW%20=%20%5Cfrac%7B(1%20+%20%5Cbeta%5E2)%20W_%7BLCS%7D%7D%7B...%7D%0A"></p>
<ul>
<li>強化「連續匹配片段」</li>
</ul>
<p><strong>ROUGE-S（skip-bigram）</strong></p>
<p>就是允許跳詞的 bigram：</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AROUGE%5Ctext%7B-%7DS%20=%20%5Cfrac%7B%5Ctext%7Bskip%20bigram%20overlap%7D%7D%7B%5Ctext%7Breference%20skip%20bigrams%7D%7D%0A"></p>
<p><strong>ROUGE-SU（skip-bigram + unigram）</strong></p>
<p><img src="https://latex.codecogs.com/png.latex?%0AROUGE%5Ctext%7B-%7DSU%20=%20ROUGE%5Ctext%7B-%7DS%20+%20ROUGE%5Ctext%7B-%7D1%0A"></p>
</section>
</div>
</div>
</div>
<p>整體看起來是還可以的結果。</p>
</section>
</section>
</section>
<section id="總結" class="level1">
<h1>總結</h1>
<p>微調有個缺點是耗時較長，需求配備也較高，用免費版的 Colab 幾乎很難做出有用的結果TT，相較之下有花$$的 glow.ai 就好多了！</p>
<p>原理部分，LoRA 微調的原理跟從頭訓練機器學習模型的過程頗為類似，只是除了跑的時候要注意訓練情況、跟數值結果外，實際跑一次也滿重要的。</p>



</section>


<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">腳註</h2>

<ol>
<li id="fn1"><p>但也是因為課堂給的作業目標比較簡單，不需要訓練很多筆資料就能有一定的效果，不然目標複雜一點就不行了：（。↩︎</p></li>
<li id="fn2"><p>不過應該很少人想做這件事…。↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>note</category>
  <category>LLM</category>
  <category>Fine-tuning</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/fintune-test/</guid>
  <pubDate>Tue, 07 Jul 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/fintune-test/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>在 Glow.ai 上的 VM 試玩 LangGraph</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/play-langgraph-in-linux/</link>
  <description><![CDATA[ 






<div data-ai-tag="1,2">

</div>
<script src="../../../asserts/box.js" defer=""></script>
<section id="緣起" class="level1">
<h1>緣起</h1>
<p>這學習我修了 LLM cybersecurity 這門課，有送 Glow.ai 的 credit 讓我們實作。不過老師一直說 credit 要省著用，於是我也努力發揮勤儉的精神，幾乎都在我的筆電、 google Colab 或是用學校的桌電做作業，就這樣做到期末才發現我的專案因為走一個破電腦也能執行的路線(?)，在本機上執行就好。於是乎我現在有超多 credit ，根本就是 credit 富翁！</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://paperfishblog.netlify.app/posts/llm/play-langgraph-in-linux/rich.png" class="img-fluid figure-img"></p>
<figcaption>截圖紀念一下</figcaption>
</figure>
</div>
<p>不過如果 credit 一直不用，最後也會因為帳號到期而用不了，這樣勤儉持家的意義就沒了 QQ。所以我就想了一些我想做的事情，其中一個就是摸一下之前有看沒有懂的 LangGraph。請 AI 幫我規劃、實作簡單的範本看看，於是就有這篇文章。</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
簡單介紹 Glow.ai
</div>
</div>
<div class="callout-body-container callout-body">
<blockquote class="blockquote">
<p><a href="https://docs.glows.ai/zh-TW/docs/overview">官方的文件介紹</a></p>
</blockquote>
<p>Glow.ai 是類似 AWS 的雲端廠商，提供虛擬機(VM)租借服務。這次租用的計費方式看起來是預先購買 credit 給有team權限的管理員，分發給同一個 team 下的所有成員(學生)，成員再依照需求租用自己需要的機器。不同規格的機器每花的 credit 不同，這次我用的是最最便宜的 、只有 CPU 的 Intel(R) Xeon(R) Platinum 8468 (0.180 credit / Hour)，裡面有 Ubuntu 24.04 的 image。</p>
</div>
</div>
</section>
<section id="glow.ai-下的執行步驟" class="level1">
<h1>Glow.ai 下的執行步驟</h1>
<section id="ssh-port-連線限定的部分" class="level2">
<h2 class="anchored" data-anchor-id="ssh-port-連線限定的部分">ssh port 連線限定的部分</h2>
<p>這台機器( Intel(R) Xeon(R) Platinum 8468 )因為只能用 ssh port 連線的，我一開始看不懂，所以紀錄一下。</p>
<ol type="1">
<li><p>申請好機器後，在 Access 下可以看到系統給你一組 ssh 指令跟密碼(被遮住的樣子)</p></li>
<li><p>在 Windows 系統裡，開啟 CMD(命令提示字元)</p></li>
<li><p>先輸入 ssh 指令，成功時系統會要求輸入密碼</p></li>
<li><p>複製密碼在 CMD 貼上(Ctrl + V)，<strong>貼上後沒有出現密碼本人是正常的，直接 Enter</strong></p></li>
<li><p>上一部成功會出現 <code>Are you sure you want to continue connecting (yes/no/[fingerprint])?</code>，輸 yes</p></li>
<li><p>commend line 前面變成以 <code>glows</code> 開頭，以 <code>~$</code> 結尾的東西可以輸入就代表成功進入 VM 裡了。</p></li>
</ol>
</section>
<section id="linux-部分" class="level2">
<h2 class="anchored" data-anchor-id="linux-部分">Linux 部分</h2>
<p>進入後先不要急著裝套件，因為這台 VM 甚麼都沒有，apt 套件(Linux 的套件管理工具)也過期了，所以先 update</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> apt update</span></code></pre></div>
<p>再安裝 pip</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sudo</span> apt install python3-pip</span></code></pre></div>
<p>沒有任何 Error 訊息的話，就可以裝 python 套件了！</p>
</section>
<section id="建專案環境跟套件部份" class="level2">
<h2 class="anchored" data-anchor-id="建專案環境跟套件部份">建專案環境跟套件部份</h2>
<section id="虛擬環境-套件" class="level3">
<h3 class="anchored" data-anchor-id="虛擬環境-套件">虛擬環境 &amp; 套件</h3>
<p>先建 venv 虛擬環境</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python3</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv venv</span></code></pre></div>
<p>再啟動</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> venv/bin/activate</span></code></pre></div>
<p>看到 commend line 前面變成多了<code>(venv)</code>就成功了。接著就可以自由安裝本次用到的套件：</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install langgraph langchain langchain-openai python-dotenv langchain-groq</span></code></pre></div>
<p>可用以下指令確認套件有沒有都安裝成功</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> list <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grep</span> lang</span></code></pre></div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
裝到的版本紀錄
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<pre><code>langchain            1.3.9
langchain-core       1.4.7
langchain-openai     1.3.2
langchain-protocol   0.0.17
langgraph            1.2.5
langgraph-checkpoint 4.1.1
langgraph-prebuilt   1.1.0
langgraph-sdk        0.4.2
langsmith            0.8.16</code></pre>
</div>
</div>
</div>
</section>
<section id="專案資料夾-進入建檔" class="level3">
<h3 class="anchored" data-anchor-id="專案資料夾-進入建檔">專案資料夾 &amp; 進入建檔</h3>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> ~/langgraph-demo</span>
<span id="cb8-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> ~/langgraph-demo</span></code></pre></div>
<p>這邊其實跟在 Windows 上的指令一樣。</p>
</section>
</section>
</section>
<section id="簡單的-langgraph-專案測試" class="level1">
<h1>簡單的 LangGraph 專案測試</h1>
<section id="先建專案" class="level2">
<h2 class="anchored" data-anchor-id="先建專案">先建專案</h2>
<p>建立第一個檔案</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nano</span> app.py</span></code></pre></div>
<p>這個指令會進入 <code>app.py</code> 這個檔案裡面編輯，可以貼上以下這段程式碼：</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> TypedDict</span>
<span id="cb10-2"></span>
<span id="cb10-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.graph <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StateGraph, END</span>
<span id="cb10-4"></span>
<span id="cb10-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb10-6">    message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb10-7"></span>
<span id="cb10-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> process(state: State):</span>
<span id="cb10-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb10-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" -&gt; LangGraph works!"</span></span>
<span id="cb10-11">    }</span>
<span id="cb10-12"></span>
<span id="cb10-13">builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb10-14"></span>
<span id="cb10-15">builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>, process)</span>
<span id="cb10-16"></span>
<span id="cb10-17">builder.set_entry_point(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>)</span>
<span id="cb10-18">builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>, END)</span>
<span id="cb10-19"></span>
<span id="cb10-20">graph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span>
<span id="cb10-21"></span>
<span id="cb10-22">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> graph.invoke({</span>
<span id="cb10-23">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hello"</span></span>
<span id="cb10-24">})</span>
<span id="cb10-25"></span>
<span id="cb10-26"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result)</span></code></pre></div>
<p>在編輯模式下可以看到下面的快捷鍵提示，先打 <code>Ctrl+O</code> (Write out) 後下面的狀態會變 File Name to Write: app.py，先按 <code>Enter</code>，<code>app.py</code>就存好了。</p>
<p>關閉檔案則是按 <code>Ctrl+X</code>，就可以回到原本的 commend line 模式，執行 <code>app.py</code></p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> app.py</span></code></pre></div>
<p>就能看到 LangGraph 傳來的訊息</p>
<pre><code>{'message': 'hello -&gt; LangGraph works!'}</code></pre>
</section>
<section id="所以剛剛發生什麼事" class="level2">
<h2 class="anchored" data-anchor-id="所以剛剛發生什麼事">所以剛剛發生什麼事?</h2>
<p>這個專案是用 LangGraph 來建立一個簡單的 State Graph，就像是可以動態跑一次的流程圖：</p>
<pre><code>開始 → 收到訊息(message) → 回傳 message 裡的訊息</code></pre>
<p>藉由定義 State、Node 跟 Edge 的方式實現。</p>
<p>State 就是在這個地方：</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> TypedDict</span>
<span id="cb14-2"></span>
<span id="cb14-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb14-4">    message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span></code></pre></div>
<p>目的是定義 graph 裡流動的資料格式，input 固定為有型別檢查的字典格式(TypedDict)，這裡只會吐出 message，也會是字典格式。</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
為什麼要固定使用有型別檢查的字典格式?
</div>
</div>
<div class="callout-body-container callout-body">
<p>LangGraph 的 state 本質是「可序列化、可逐步更新的資料流」，不是物件導向的封裝結構。單純 class 的設計沒有辦法依照不同狀態更新欄位。相對的，TypedDict 結合 一般字典(dict) 的彈性，自動型別檢查也可避免寫錯 key 。</p>
</div>
</div>
<p>Node 則是在</p>
<div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> process(state: State):</span>
<span id="cb15-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb15-3">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" -&gt; LangGraph works!"</span></span>
<span id="cb15-4">    }</span></code></pre></div>
<p>Node 是函數形式，將從 State 那裡收到的狀態做更新。這裡的函數定義是：在原本的 message 後面加上 ” -&gt; LangGraph works!“。</p>
<p>接下來的這一段是建立 Graph 並讓它運作的過程</p>
<div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 定義建構器</span></span>
<span id="cb16-2">builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#這個 graph 的資料結構是 State</span></span>
<span id="cb16-3"></span>
<span id="cb16-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 加入設定好的 node，要附上自訂名稱及函數名稱</span></span>
<span id="cb16-5">builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>, process)</span>
<span id="cb16-6"></span>
<span id="cb16-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 設定入口點（entry point），代表從 process 開始執行</span></span>
<span id="cb16-8">builder.set_entry_point(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>)</span>
<span id="cb16-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 設定結束點（edge point），這裡設定一個流程結束後不再執行所以 </span><span class="re">END</span></span>
<span id="cb16-10">builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"process"</span>, END)</span>
<span id="cb16-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 編譯 graph</span></span>
<span id="cb16-12">graph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span>
<span id="cb16-13"></span>
<span id="cb16-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 執行 graph</span></span>
<span id="cb16-15">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> graph.invoke({</span>
<span id="cb16-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"hello"</span></span>
<span id="cb16-17">})</span>
<span id="cb16-18"></span>
<span id="cb16-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result)</span></code></pre></div>
<p>於是結果就是</p>
<pre><code>{'message': 'hello -&gt; LangGraph works!'}</code></pre>
<p>以上可知</p>
</section>
</section>
<section id="線上版聊天-ai" class="level1">
<h1>線上版聊天 AI</h1>
<p>來做個對話式 AI 吧。這裡一樣是用 Grop 的免費 API 來做示範，教學請參閱<a href="https://paperfishblog.netlify.app/posts/llm/llm-course-note1/#%E7%94%B3%E8%AB%8B-groq-%E5%85%8D%E8%B2%BB-api-key-%E4%B8%A6%E6%B8%AC%E8%A9%A6">另一篇文章</a></p>
<ol type="1">
<li>申請好 API key後，建<code>env</code>檔</li>
</ol>
<div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nano</span> .env</span></code></pre></div>
<p>在編輯模式下建立變數與API Key</p>
<pre><code>GROQ_API_KEY=你的GroqKey</code></pre>
<ol start="2" type="1">
<li>在同樣的專案資料夾下建立 <code>chat_agent.py</code></li>
</ol>
<div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb20-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nano</span> chat_agent.py</span></code></pre></div>
<div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb21-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb21-3"></span>
<span id="cb21-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_groq <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatGroq</span>
<span id="cb21-5"></span>
<span id="cb21-6">load_dotenv()</span>
<span id="cb21-7"></span>
<span id="cb21-8">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatGroq(</span>
<span id="cb21-9">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,</span>
<span id="cb21-10">    api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GROQ_API_KEY"</span>)</span>
<span id="cb21-11">)</span>
<span id="cb21-12"></span>
<span id="cb21-13">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"用一句話解釋 LangGraph 是什麼"</span>)</span>
<span id="cb21-14"></span>
<span id="cb21-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response.content)</span></code></pre></div>
<p><del><em>特地換成高級一點的模型玩玩</em></del></p>
<ol start="3" type="1">
<li>測試</li>
</ol>
<div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb22-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> chat_agent.py</span></code></pre></div>
<p>有成功回應問題（例如”用一句話解釋 LangGraph 是什麼”）就表示成功。</p>
</section>
<section id="加入角色分工" class="level1">
<h1>加入角色分工</h1>
<p>LangGraph 強大的地方在於它可以一次讓 AI 扮演不同角色，每個角色有不同的專業，可以一起完成被分配的任務。這裡以兩個 AI 角色：PM 跟工程師(PG)為例，我們的目標是建立一個使用者提需求，PM 審核後再交給工程師產 code ，整體結構如下：</p>
<pre><code>User提需求 → PM提供初步系統架構設計 → PG提供正式架構 → Final 報告</code></pre>
<section id="串-llm-與建立-prompt" class="level2">
<h2 class="anchored" data-anchor-id="串-llm-與建立-prompt">串 LLM 與建立 prompt</h2>
<p>因為架構跟上一個比起來有點小複雜，所以換成兩支腳本。</p>
<section id="串-llm-部分" class="level3">
<h3 class="anchored" data-anchor-id="串-llm-部分">串 LLM 部分</h3>
<p>先 nano 一個 <code>llm.py</code>，讓整體架構更清晰。</p>
<div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nano</span> llm/py</span></code></pre></div>
<div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb25-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb25-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_groq <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatGroq</span>
<span id="cb25-4"></span>
<span id="cb25-5">load_dotenv()</span>
<span id="cb25-6"></span>
<span id="cb25-7">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatGroq(</span>
<span id="cb25-8">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.1-8b-instant"</span>,</span>
<span id="cb25-9">    api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GROQ_API_KEY"</span>)</span>
<span id="cb25-10">)</span></code></pre></div>
<p><del>發現模型感覺不用太高級又換回來了</del></p>
</section>
<section id="建立-prompt-部分" class="level3">
<h3 class="anchored" data-anchor-id="建立-prompt-部分">建立 prompt 部分</h3>
<div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1">PM_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb26-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是產品經理（PM）。</span></span>
<span id="cb26-3"></span>
<span id="cb26-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">使用者需求：</span></span>
<span id="cb26-5"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_input}</span></span>
<span id="cb26-6"></span>
<span id="cb26-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">請輸出：</span></span>
<span id="cb26-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 清楚需求描述</span></span>
<span id="cb26-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. 可執行 task list（用條列）</span></span>
<span id="cb26-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb26-11"></span>
<span id="cb26-12">ENGINEER_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb26-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是資深後端工程師（Senior Engineer）。</span></span>
<span id="cb26-14"></span>
<span id="cb26-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">請根據以下系統設計，產出：</span></span>
<span id="cb26-16"></span>
<span id="cb26-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 專案資料夾結構</span></span>
<span id="cb26-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. Python FastAPI code</span></span>
<span id="cb26-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">3. requirements.txt</span></span>
<span id="cb26-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">4. 如何啟動專案（指令）</span></span>
<span id="cb26-21"></span>
<span id="cb26-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">系統設計如下：</span></span>
<span id="cb26-23"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{pm_output}</span></span>
<span id="cb26-24"></span>
<span id="cb26-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">使用者需求：</span></span>
<span id="cb26-26"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_input}</span></span>
<span id="cb26-27"></span>
<span id="cb26-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">請輸出可直接執行的內容。</span></span>
<span id="cb26-29"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span></code></pre></div>
</section>
</section>
<section id="修改-agent-裡的流程" class="level2">
<h2 class="anchored" data-anchor-id="修改-agent-裡的流程">修改 agent 裡的流程</h2>
<p>修改 <code>agent.py</code></p>
<div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> subprocess</span>
<span id="cb27-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> TypedDict</span>
<span id="cb27-3"></span>
<span id="cb27-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.graph <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StateGraph, END</span>
<span id="cb27-5"></span>
<span id="cb27-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> llm <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> llm</span>
<span id="cb27-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> prompts <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> PM_PROMPT, ANALYZER_PROMPT</span>
<span id="cb27-8"></span>
<span id="cb27-9"></span>
<span id="cb27-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 角色變多 state 的 input 也會變多</span></span>
<span id="cb27-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb27-12">    user_input: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb27-13">    pm_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb27-14">    engineer_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb27-15">    final: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb27-16"></span>
<span id="cb27-17"></span>
<span id="cb27-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ---------------- PM ----------------</span></span>
<span id="cb27-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pm_node(state: State):</span>
<span id="cb27-20"></span>
<span id="cb27-21">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> PM_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb27-22">        user_input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>]</span>
<span id="cb27-23">    )</span>
<span id="cb27-24"></span>
<span id="cb27-25">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb27-26"></span>
<span id="cb27-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb27-28">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>: res.content</span>
<span id="cb27-29">    }</span>
<span id="cb27-30"></span>
<span id="cb27-31"></span>
<span id="cb27-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Engineer --------------</span></span>
<span id="cb27-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 工程師負責產 code</span></span>
<span id="cb27-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> engineer_node(state: State):</span>
<span id="cb27-35"></span>
<span id="cb27-36">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ENGINEER_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb27-37">        user_input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>],</span>
<span id="cb27-38">        pm_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>],</span>
<span id="cb27-39">    )</span>
<span id="cb27-40"></span>
<span id="cb27-41">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb27-42"></span>
<span id="cb27-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb27-44">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer_output"</span>: res.content</span>
<span id="cb27-45">    }</span>
<span id="cb27-46">    </span>
<span id="cb27-47"></span>
<span id="cb27-48"></span>
<span id="cb27-49"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Final node --------------</span></span>
<span id="cb27-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 最後要產出報告</span></span>
<span id="cb27-51"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> final_node(state: State):</span>
<span id="cb27-52">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb27-53">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"""</span></span>
<span id="cb27-54"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">===== 🧑‍💼 PM 設計 =====</span></span>
<span id="cb27-55"></span>
<span id="cb27-56"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pm_output'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb27-57"></span>
<span id="cb27-58"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">===== 👷 Engineer 實作 =====</span></span>
<span id="cb27-59"></span>
<span id="cb27-60"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'engineer_output'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb27-61"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb27-62">    }</span>
<span id="cb27-63"></span>
<span id="cb27-64"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Build Graph --------------</span></span>
<span id="cb27-65"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_graph():</span>
<span id="cb27-66">    builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb27-67"></span>
<span id="cb27-68">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>, pm_node)</span>
<span id="cb27-69">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>, engineer_node)</span>
<span id="cb27-70">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>, final_node)</span>
<span id="cb27-71"></span>
<span id="cb27-72">    builder.set_entry_point(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>)</span>
<span id="cb27-73"></span>
<span id="cb27-74">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>)</span>
<span id="cb27-75">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>)</span>
<span id="cb27-76">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>, END)</span>
<span id="cb27-77"></span>
<span id="cb27-78">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span>
<span id="cb27-79"></span>
<span id="cb27-80"></span>
<span id="cb27-81">graph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_graph()</span></code></pre></div>
</section>
<section id="修改-app.py" class="level2">
<h2 class="anchored" data-anchor-id="修改-app.py">修改 <code>app.py</code></h2>
<p>讓它可以成功吐出報告結果。</p>
<div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb28-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> agent <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> graph</span>
<span id="cb28-2"></span>
<span id="cb28-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb28-4"></span>
<span id="cb28-5">    user_input <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Enter your problem: "</span>)</span>
<span id="cb28-6"></span>
<span id="cb28-7">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> graph.invoke({</span>
<span id="cb28-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>: user_input,</span>
<span id="cb28-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"spec"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb28-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tasks"</span>: [],</span>
<span id="cb28-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"logs"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>,</span>
<span id="cb28-12">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"result"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span></span>
<span id="cb28-13">    })</span>
<span id="cb28-14"></span>
<span id="cb28-15">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">===== FINAL REPORT =====</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb28-16">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>])</span></code></pre></div>
<p>完成後回到 terminal 模式再跑一次</p>
<div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb29-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> app.py</span></code></pre></div>
<p>可出現對話，這時你可以隨便下個需求，讓 PM 設計系統，工程師角色產 code。不過很快就會發現，需求下得很不合理的時候最後很可能會產出不合時宜的 code，需要設定一個「可能會打槍」的流程。</p>
</section>
</section>
<section id="設計迴圈流程" class="level1">
<h1>設計迴圈流程</h1>
<p>這次把流程改成這樣：</p>
<pre><code>        ┌─────────────────┐
        │                 ↓
User → PM → Business → Engineer
        ↑                 │
        │                 ↓
        └──── Reviewer ←－┘
                │
          通過? / 打槍?(2次後進 Final)
                │
              Final</code></pre>
<p>多 2 個角色</p>
<ul>
<li><p>Business: 確定 PM 開的規格符合商業價值</p></li>
<li><p>Reviewer: 審核專案的可行性</p></li>
</ul>
<section id="修改-agent.py" class="level2">
<h2 class="anchored" data-anchor-id="修改-agent.py">修改 <code>agent.py</code></h2>
<p>首先 State 要修改 State 的部份</p>
<div class="sourceCode" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb31-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#多了business角色</span></span>
<span id="cb31-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 迴圈部分也要定義</span></span>
<span id="cb31-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb31-4">    user_input: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-5">    pm_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-6">    business_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-7">    engineer_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-8">    review_result: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-9">    retry_count: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb31-10">    final: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span></code></pre></div>
<p>再來新增角色</p>
</section>
<section id="到-prompts.py-更新-prompt" class="level2">
<h2 class="anchored" data-anchor-id="到-prompts.py-更新-prompt">到 <code>prompts.py</code> 更新 prompt</h2>
<div class="sourceCode" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb32-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nano</span> prompts.py</span></code></pre></div>
<p>改成</p>
<div class="sourceCode" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb33-1">PM_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是資深產品經理（PM）。</span></span>
<span id="cb33-3"></span>
<span id="cb33-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">使用者需求：</span></span>
<span id="cb33-5"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_input}</span></span>
<span id="cb33-6"></span>
<span id="cb33-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">請輸出：</span></span>
<span id="cb33-8"></span>
<span id="cb33-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 系統目標</span></span>
<span id="cb33-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. 使用者需求整理</span></span>
<span id="cb33-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">3. 功能需求</span></span>
<span id="cb33-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">4. 開發範圍</span></span>
<span id="cb33-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">5. 建議優先順序</span></span>
<span id="cb33-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-15"></span>
<span id="cb33-16"></span>
<span id="cb33-17">BUSINESS_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是商業數據分析師（Business Analyst）。</span></span>
<span id="cb33-19"></span>
<span id="cb33-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">請評估以下專案：</span></span>
<span id="cb33-21"></span>
<span id="cb33-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">使用者需求：</span></span>
<span id="cb33-23"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_input}</span></span>
<span id="cb33-24"></span>
<span id="cb33-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">PM 規劃：</span></span>
<span id="cb33-26"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{pm_output}</span></span>
<span id="cb33-27"></span>
<span id="cb33-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">分析：</span></span>
<span id="cb33-29"></span>
<span id="cb33-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 商業價值</span></span>
<span id="cb33-31"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. 目標客群</span></span>
<span id="cb33-32"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">3. 解決痛點</span></span>
<span id="cb33-33"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">4. 市場機會</span></span>
<span id="cb33-34"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">5. ROI</span></span>
<span id="cb33-35"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">6. KPI</span></span>
<span id="cb33-36"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">7. 開發風險</span></span>
<span id="cb33-37"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">8. 是否值得投入</span></span>
<span id="cb33-38"></span>
<span id="cb33-39"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">最後給出：</span></span>
<span id="cb33-40"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">VALUE_SCORE: 1-10</span></span>
<span id="cb33-41"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-42"></span>
<span id="cb33-43"></span>
<span id="cb33-44">ENGINEER_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-45"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是資深後端工程師。</span></span>
<span id="cb33-46"></span>
<span id="cb33-47"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">根據：</span></span>
<span id="cb33-48"></span>
<span id="cb33-49"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">需求：</span></span>
<span id="cb33-50"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_input}</span></span>
<span id="cb33-51"></span>
<span id="cb33-52"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">PM:</span></span>
<span id="cb33-53"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{pm_output}</span></span>
<span id="cb33-54"></span>
<span id="cb33-55"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">商業分析:</span></span>
<span id="cb33-56"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{business_output}</span></span>
<span id="cb33-57"></span>
<span id="cb33-58"></span>
<span id="cb33-59"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">產出：</span></span>
<span id="cb33-60"></span>
<span id="cb33-61"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 系統架構</span></span>
<span id="cb33-62"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. 技術選型</span></span>
<span id="cb33-63"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">3. 專案結構</span></span>
<span id="cb33-64"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">4. API 設計</span></span>
<span id="cb33-65"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">5. 開發步驟</span></span>
<span id="cb33-66"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-67"></span>
<span id="cb33-68"></span>
<span id="cb33-69">REVIEW_PROMPT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb33-70"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你是專案審查委員。</span></span>
<span id="cb33-71"></span>
<span id="cb33-72"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">審查：</span></span>
<span id="cb33-73"></span>
<span id="cb33-74"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">PM:</span></span>
<span id="cb33-75"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{pm_output}</span></span>
<span id="cb33-76"></span>
<span id="cb33-77"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Business:</span></span>
<span id="cb33-78"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{business_output}</span></span>
<span id="cb33-79"></span>
<span id="cb33-80"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Engineer:</span></span>
<span id="cb33-81"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{engineer_output}</span></span>
<span id="cb33-82"></span>
<span id="cb33-83"></span>
<span id="cb33-84"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">判斷：</span></span>
<span id="cb33-85"></span>
<span id="cb33-86"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">商業價值是否足夠？</span></span>
<span id="cb33-87"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">技術是否可行？</span></span>
<span id="cb33-88"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">需求是否清楚？</span></span>
<span id="cb33-89"></span>
<span id="cb33-90"></span>
<span id="cb33-91"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">如果通過輸出：</span></span>
<span id="cb33-92"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">APPROVE</span></span>
<span id="cb33-93"></span>
<span id="cb33-94"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">如果不通過輸出：</span></span>
<span id="cb33-95"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">REJECT</span></span>
<span id="cb33-96"></span>
<span id="cb33-97"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">並說明原因。</span></span>
<span id="cb33-98"></span>
<span id="cb33-99"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">只要出現以下情況 → 必須 REJECT：</span></span>
<span id="cb33-100"></span>
<span id="cb33-101"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">1. 技術名詞被錯誤定義（例如 RAG 不是 Retrieval-Augmented Generation）</span></span>
<span id="cb33-102"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">2. 系統需求與技術實作不一致</span></span>
<span id="cb33-103"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">3. 架構包含明顯幻想（不存在的 AI training flow / 自行學習但無方法）</span></span>
<span id="cb33-104"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span></code></pre></div>
<p>主要更新的內容就是除了每個角色增加都要定義一次函數外，設定 reject 次數也需要定義。</p>
</section>
<section id="回到-agent.py" class="level2">
<h2 class="anchored" data-anchor-id="回到-agent.py">回到 <code>agent.py</code></h2>
<p>修改成以下</p>
<div class="sourceCode" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb34-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> subprocess</span>
<span id="cb34-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> TypedDict</span>
<span id="cb34-3"></span>
<span id="cb34-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.graph <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StateGraph, END</span>
<span id="cb34-5"></span>
<span id="cb34-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> llm <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> llm</span>
<span id="cb34-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> prompts <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> (</span>
<span id="cb34-8">    PM_PROMPT,</span>
<span id="cb34-9">    BUSINESS_PROMPT,</span>
<span id="cb34-10">    ENGINEER_PROMPT,</span>
<span id="cb34-11">    REVIEWER_PROMPT</span>
<span id="cb34-12">)</span>
<span id="cb34-13"></span>
<span id="cb34-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#多了business 跟 reviewer角色</span></span>
<span id="cb34-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 打槍幾次部分也要定義</span></span>
<span id="cb34-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb34-17">    user_input: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-18">    pm_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-19">    business_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-20">    engineer_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-21">    reviewer_output: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-22">    review_count: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb34-23"></span>
<span id="cb34-24">    final: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb34-25"></span>
<span id="cb34-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ---------------- PM ----------------</span></span>
<span id="cb34-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> pm_node(state: State):</span>
<span id="cb34-28"></span>
<span id="cb34-29">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> PM_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb34-30">        user_input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>]</span>
<span id="cb34-31">    )</span>
<span id="cb34-32"></span>
<span id="cb34-33">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb34-34"></span>
<span id="cb34-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb34-36">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>: res.content</span>
<span id="cb34-37">    }</span>
<span id="cb34-38"></span>
<span id="cb34-39"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Business --------------</span></span>
<span id="cb34-40"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> business_node(state: State):</span>
<span id="cb34-41"></span>
<span id="cb34-42">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BUSINESS_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb34-43">        user_input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>],</span>
<span id="cb34-44">        pm_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>]</span>
<span id="cb34-45">    )</span>
<span id="cb34-46"></span>
<span id="cb34-47">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb34-48"></span>
<span id="cb34-49">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb34-50">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business_output"</span>: res.content</span>
<span id="cb34-51">    }</span>
<span id="cb34-52"></span>
<span id="cb34-53"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Engineer --------------</span></span>
<span id="cb34-54"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 工程師負責產 code</span></span>
<span id="cb34-55"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> engineer_node(state: State):</span>
<span id="cb34-56"></span>
<span id="cb34-57">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ENGINEER_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb34-58">        user_input<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_input"</span>],</span>
<span id="cb34-59">        pm_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>],</span>
<span id="cb34-60">        business_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business_output"</span>],</span>
<span id="cb34-61">    )</span>
<span id="cb34-62"></span>
<span id="cb34-63">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb34-64"></span>
<span id="cb34-65">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb34-66">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer_output"</span>: res.content</span>
<span id="cb34-67">    }</span>
<span id="cb34-68">    </span>
<span id="cb34-69"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Reviewer --------------</span></span>
<span id="cb34-70"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reviewer_node(state):</span>
<span id="cb34-71"></span>
<span id="cb34-72">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> REVIEWER_PROMPT.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(</span>
<span id="cb34-73">        pm_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm_output"</span>],</span>
<span id="cb34-74">        business_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business_output"</span>],</span>
<span id="cb34-75">        engineer_output<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer_output"</span>]</span>
<span id="cb34-76">    )</span>
<span id="cb34-77"></span>
<span id="cb34-78">    res <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.invoke(prompt)</span>
<span id="cb34-79"></span>
<span id="cb34-80">    review_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_count"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb34-81"></span>
<span id="cb34-82">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PASS"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> res.content:</span>
<span id="cb34-83">        review_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb34-84"></span>
<span id="cb34-85">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb34-86">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reviewer_output"</span>: res.content,</span>
<span id="cb34-87">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_count"</span>: review_count</span>
<span id="cb34-88">    }</span>
<span id="cb34-89"></span>
<span id="cb34-90"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Review router --------------</span></span>
<span id="cb34-91"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 打槍次數也需要設定</span></span>
<span id="cb34-92"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> review_router(state):</span>
<span id="cb34-93"></span>
<span id="cb34-94">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PASS"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reviewer_output"</span>]:</span>
<span id="cb34-95">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span></span>
<span id="cb34-96"></span>
<span id="cb34-97">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_count"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb34-98">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span></span>
<span id="cb34-99"></span>
<span id="cb34-100">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span></span>
<span id="cb34-101"></span>
<span id="cb34-102"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Final node --------------</span></span>
<span id="cb34-103"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 最後要產出報告</span></span>
<span id="cb34-104"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> final_node(state: State):</span>
<span id="cb34-105">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb34-106">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"""</span></span>
<span id="cb34-107"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">===== 🧑‍💼 PM 設計 =====</span></span>
<span id="cb34-108"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pm_output'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb34-109"></span>
<span id="cb34-110"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">===== 👷 Engineer 實作 =====</span></span>
<span id="cb34-111"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'engineer_output'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb34-112"></span>
<span id="cb34-113"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">===== 👷 Reviewer 結果 =====</span></span>
<span id="cb34-114"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'reviewer_output'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb34-115"></span>
<span id="cb34-116"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb34-117">    }</span>
<span id="cb34-118"></span>
<span id="cb34-119"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -------------- Build Graph --------------</span></span>
<span id="cb34-120"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_graph():</span>
<span id="cb34-121">    builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb34-122">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>, pm_node)</span>
<span id="cb34-123">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business"</span>, business_node)</span>
<span id="cb34-124">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>, engineer_node)</span>
<span id="cb34-125">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reviewer"</span>, reviewer_node)</span>
<span id="cb34-126">    builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>, final_node)</span>
<span id="cb34-127"></span>
<span id="cb34-128">    builder.set_entry_point(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>)</span>
<span id="cb34-129"></span>
<span id="cb34-130">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business"</span>)</span>
<span id="cb34-131">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"business"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>)</span>
<span id="cb34-132">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"engineer"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reviewer"</span>)</span>
<span id="cb34-133"></span>
<span id="cb34-134">    builder.add_conditional_edges(</span>
<span id="cb34-135">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reviewer"</span>,</span>
<span id="cb34-136">        review_router,</span>
<span id="cb34-137">        {</span>
<span id="cb34-138">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pm"</span>,</span>
<span id="cb34-139">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>,</span>
<span id="cb34-140">        },</span>
<span id="cb34-141">    )</span>
<span id="cb34-142"></span>
<span id="cb34-143">    builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final"</span>, END)</span>
<span id="cb34-144"></span>
<span id="cb34-145">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span>
<span id="cb34-146"></span>
<span id="cb34-147"></span>
<span id="cb34-148"></span>
<span id="cb34-149">graph <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> build_graph()</span></code></pre></div>
</section>
</section>
<section id="測試玩具" class="level1">
<h1>測試玩具</h1>
<p>只要在終端機叫 <code>app.py</code> 就可以測試</p>
<div class="sourceCode" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb35-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> app.py</span></code></pre></div>
<section id="正常需求" class="level2">
<h2 class="anchored" data-anchor-id="正常需求">正常需求</h2>
<p>先來一個正常、定義較為明確的需求。</p>
<pre><code>做一個RAG系統檢索公司技術文件</code></pre>
<p>結果回應在下面</p>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-4-contents" aria-controls="callout-4" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
結果回應
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-4" class="callout-4-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>===== FINAL REPORT =====</p>
<p>===== 🧑‍💼 PM 設計 ===== <strong>需求描述</strong></p>
<p><strong>系統名稱:</strong> RAG 技術文件檢索系統</p>
<p><strong>目的:</strong> 建立一個系統用於檢索公司技術文件，提高查詢效率和便捷性。</p>
<p><strong>功能需求:</strong></p>
<ol type="1">
<li><strong>文件管理:</strong> 能夠儲存和管理公司技術文件，包括文檔、圖片、視頻等多種格式。</li>
<li><strong>查詢功能:</strong> 提供多種查詢方式，包括：</li>
</ol>
<ul>
<li>名稱查詢</li>
<li>說明查詢</li>
<li>代碼查詢</li>
<li>日期查詢</li>
</ul>
<ol start="3" type="1">
<li><strong>篩選功能:</strong> 能夠根據使用者的需求篩選查詢結果，包括：</li>
</ol>
<ul>
<li>根據文件類型篩選</li>
<li>根據文件日期篩選</li>
</ul>
<ol start="4" type="1">
<li><strong>排序功能:</strong> 能夠根據使用者的需求排序查詢結果，包括：</li>
</ol>
<ul>
<li>根據文件名稱排序</li>
<li>根據文件日期排序</li>
</ul>
<ol start="5" type="1">
<li><strong>附件功能:</strong> 能夠上傳和下載文件附件。</li>
<li><strong>使用者管理:</strong> 能夠管理使用者角色和權限，包括：</li>
</ol>
<ul>
<li>系統管理者角色</li>
<li>一般使用者角色</li>
</ul>
<ol start="7" type="1">
<li><strong>安全功能:</strong> 能夠設定安全控制，包括：</li>
</ol>
<ul>
<li>登錄認證</li>
<li>文件加密</li>
<li>存取控制</li>
</ul>
<p><strong>非功能需求:</strong></p>
<ol type="1">
<li><strong>使用者體驗:</strong> 系統應該提供友好的使用者界面和簡單的操作流程。</li>
<li><strong>效能:</strong> 系統應該能夠高效地處理查詢和儲存文件。</li>
<li><strong>穩定性:</strong> 系統應該能夠穩定地運行和處理需求。</li>
</ol>
<p><strong>技術需求:</strong></p>
<ol type="1">
<li><strong>框架:</strong> 使用現成的開源框架（例如 Django 或 Flask）建設系統。</li>
<li><strong>資料庫:</strong> 使用 MySQL 或 PostgreSQL 資料庫儲存文件和使用者資料。</li>
<li><strong>安全:</strong> 使用 SSL/TLS 來實現安全連接和文件加密。</li>
<li><strong>部署:</strong> 系統應該能夠在雲平台（例如 AWS 或 Azure）部署和運行。</li>
</ol>
<p><strong>任務清單:</strong></p>
<ol type="1">
<li><strong>需求收集和分析:</strong></li>
</ol>
<ul>
<li>收集和分析公司技術文件需求</li>
<li>設計系統架構和功能需求</li>
</ul>
<ol start="2" type="1">
<li><strong>系統設計:</strong></li>
</ol>
<ul>
<li>設計系統框架和架構</li>
<li>設計資料庫結構和安全功能</li>
</ul>
<ol start="3" type="1">
<li><strong>代碼實現:</strong></li>
</ol>
<ul>
<li>實現查詢和篩選功能</li>
<li>實現排序和附件功能</li>
<li>實現使用者管理和安全功能</li>
</ul>
<ol start="4" type="1">
<li><strong>測試和驗證:</strong></li>
</ol>
<ul>
<li>實現單元測試和統一測試</li>
<li>驗證系統功能和安全性</li>
</ul>
<ol start="5" type="1">
<li><strong>部署和上線:</strong></li>
</ol>
<ul>
<li>部署系統到雲平台</li>
<li>上線系統並測試功能和穩定性</li>
</ul>
<ol start="6" type="1">
<li><strong>維護和升級:</strong></li>
</ol>
<ul>
<li>實現系統維護和升級功能</li>
<li>實現安全更新和維護</li>
</ul>
<p>===== 🧑‍💼 商業價值評估 ===== 基於提供的專案資料，以下是我的分析：</p>
<p><strong>1. 商業價值</strong></p>
<p>該系統能夠提高公司技術文件的查詢效率和便捷性，減少工作時間和成本。它還能夠提供安全和穩定地存儲和管理文件。</p>
<p><strong>2. 目標客群</strong></p>
<p>公司內的所有人員，特別是技術人員和管理人員。</p>
<p><strong>3. 解決痛點</strong></p>
<ul>
<li>查詢效率低下</li>
<li>文件管理不方便</li>
<li>安全和穩定性問題</li>
</ul>
<p><strong>4. 市場機會</strong></p>
<ul>
<li>公司內的其他部門可能也需要類似的系統</li>
<li>業界中其他公司可能也需要類似的系統</li>
</ul>
<p><strong>5. ROI</strong></p>
<p>系統能夠幫助公司節省工作時間和成本，提高效率和產出。預計 ROI 可能在 1-2 年內達到。</p>
<p><strong>6. KPI</strong></p>
<ul>
<li>系統的查詢效率和便捷性</li>
<li>系統的安全和穩定性</li>
<li>系統的使用率和滿意度</li>
</ul>
<p><strong>7. 開發風險</strong></p>
<ul>
<li>系統的設計和實現是否符合公司的需求</li>
<li>系統的安全和穩定性是否能夠滿足公司的要求</li>
<li>系統的部署和上線是否能夠順暢</li>
</ul>
<p><strong>8. 是否值得投入</strong></p>
<p>基於分析，該系統的商業價值和 ROI 都是正面的。雖然開發風險存在，但它們可以通過充分的需求收集、系統設計和測試來降低。因此，我們可以給該系統的 VALUE_SCORE 評分為 <strong>8</strong>。</p>
<p>===== 👷 Engineer 實作 ===== <strong>專案資料夾結構:</strong></p>
<div class="sourceCode" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb37-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">rag-system/</span></span>
<span id="cb37-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">app/</span></span>
<span id="cb37-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">main.py</span></span>
<span id="cb37-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">models</span></span>
<span id="cb37-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">file.py</span></span>
<span id="cb37-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">user.py</span></span>
<span id="cb37-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">routes</span></span>
<span id="cb37-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">auth.py</span></span>
<span id="cb37-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">file.py</span></span>
<span id="cb37-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">user.py</span></span>
<span id="cb37-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">schemas</span></span>
<span id="cb37-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-16"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">file.py</span></span>
<span id="cb37-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">user.py</span></span>
<span id="cb37-18"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">utils</span></span>
<span id="cb37-19"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">auth.py</span></span>
<span id="cb37-21"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">file.py</span></span>
<span id="cb37-22"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">requirements.txt</span></span>
<span id="cb37-23"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tests</span></span>
<span id="cb37-24"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb37-25"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">test_auth.py</span></span>
<span id="cb37-26"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">test_file.py</span></span>
<span id="cb37-27"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">test_user.py</span></span>
<span id="cb37-28"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">config.py</span></span>
<span id="cb37-29"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">README.md</span></span></code></pre></div>
<p><strong>Python FastAPI code:</strong></p>
<p><strong>app/main.py:</strong></p>
<div class="sourceCode" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb38-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FastAPI</span>
<span id="cb38-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.responses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> JSONResponse</span>
<span id="cb38-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.requests <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Request</span>
<span id="cb38-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb38-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb38-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> uvicorn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> run</span>
<span id="cb38-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.routes.auth <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> auth_router</span>
<span id="cb38-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.routes.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> file_router</span>
<span id="cb38-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.routes.user <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> user_router</span>
<span id="cb38-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.auth <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_current_user, get_user</span>
<span id="cb38-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_file</span>
<span id="cb38-12"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.user <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_user</span>
<span id="cb38-13"></span>
<span id="cb38-14">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FastAPI()</span>
<span id="cb38-15"></span>
<span id="cb38-16">app.include_router(auth_router)</span>
<span id="cb38-17">app.include_router(file_router)</span>
<span id="cb38-18">app.include_router(user_router)</span>
<span id="cb38-19"></span>
<span id="cb38-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API Root</span></span>
<span id="cb38-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Root"</span>])</span>
<span id="cb38-22"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> read_root():</span>
<span id="cb38-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Welcome to RAG System"</span>}</span>
<span id="cb38-24"></span>
<span id="cb38-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API Login</span></span>
<span id="cb38-26"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/login"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Auth"</span>])</span>
<span id="cb38-27"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> login(username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb38-28">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user(username, password)</span>
<span id="cb38-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb38-30">        access_token <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> user.generate_access_token()</span>
<span id="cb38-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"access_token"</span>: access_token}</span>
<span id="cb38-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb38-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Invalid username or password"</span>}</span>
<span id="cb38-34"></span>
<span id="cb38-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API Register</span></span>
<span id="cb38-36"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/register"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Auth"</span>])</span>
<span id="cb38-37"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> register(username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, email: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb38-38">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user(username, password, email)</span>
<span id="cb38-39">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb38-40">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> user</span>
<span id="cb38-41">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb38-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Failed to register"</span>}</span>
<span id="cb38-43"></span>
<span id="cb38-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API File</span></span>
<span id="cb38-45"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb38-46"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_files():</span>
<span id="cb38-47">    files <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file()</span>
<span id="cb38-48">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> files</span>
<span id="cb38-49"></span>
<span id="cb38-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API File Upload</span></span>
<span id="cb38-51"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files/upload"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb38-52"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bytes</span>, filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb38-53">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file().upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>, filename)</span>
<span id="cb38-54">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span></span>
<span id="cb38-55"></span>
<span id="cb38-56"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API File Download</span></span>
<span id="cb38-57"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{file_id}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb38-58"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> download_file(file_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb38-59">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file().get_file(file_id)</span>
<span id="cb38-60">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>:</span>
<span id="cb38-61">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span></span>
<span id="cb38-62">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb38-63">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File not found"</span>}</span>
<span id="cb38-64"></span>
<span id="cb38-65"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API User</span></span>
<span id="cb38-66"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/users"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User"</span>])</span>
<span id="cb38-67"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_users():</span>
<span id="cb38-68">    users <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user()</span>
<span id="cb38-69">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> users</span>
<span id="cb38-70"></span>
<span id="cb38-71"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># API User Profile</span></span>
<span id="cb38-72"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/users/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_id}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User"</span>])</span>
<span id="cb38-73"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user_profile(user_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb38-74">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user().get_user(user_id)</span>
<span id="cb38-75">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb38-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> user</span>
<span id="cb38-77">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb38-78">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User not found"</span>}</span></code></pre></div>
<p><strong>app/routes/auth.py:</strong></p>
<div class="sourceCode" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb39-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> APIRouter, Depends, HTTPException</span>
<span id="cb39-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.security <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OAuth2PasswordBearer, OAuth2PasswordRequestForm</span>
<span id="cb39-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb39-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb39-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.auth <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_current_user, get_user</span>
<span id="cb39-6"></span>
<span id="cb39-7">auth_router <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> APIRouter()</span>
<span id="cb39-8"></span>
<span id="cb39-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@auth_router.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/login"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Auth"</span>])</span>
<span id="cb39-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> login(form_data: OAuth2PasswordRequestForm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Depends()):</span>
<span id="cb39-11">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user(form_data.username, form_data.password)</span>
<span id="cb39-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb39-13">        access_token <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> user.generate_access_token()</span>
<span id="cb39-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"access_token"</span>: access_token}</span>
<span id="cb39-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb39-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> HTTPException(status_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">401</span>, detail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Invalid username or password"</span>)</span>
<span id="cb39-17"></span>
<span id="cb39-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@auth_router.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/register"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Auth"</span>])</span>
<span id="cb39-19"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> register(username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, email: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb39-20">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user(username, password, email)</span>
<span id="cb39-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb39-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> user</span>
<span id="cb39-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb39-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> HTTPException(status_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span>, detail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Failed to register"</span>)</span></code></pre></div>
<p><strong>app/routes/file.py:</strong></p>
<div class="sourceCode" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb40-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> APIRouter, Depends, HTTPException</span>
<span id="cb40-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.responses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> JSONResponse</span>
<span id="cb40-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb40-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb40-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_file</span>
<span id="cb40-6"></span>
<span id="cb40-7">file_router <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> APIRouter()</span>
<span id="cb40-8"></span>
<span id="cb40-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@file_router.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb40-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_files():</span>
<span id="cb40-11">    files <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file()</span>
<span id="cb40-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> files</span>
<span id="cb40-13"></span>
<span id="cb40-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@file_router.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files/upload"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb40-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bytes</span>, filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb40-16">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file().upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>, filename)</span>
<span id="cb40-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span></span>
<span id="cb40-18"></span>
<span id="cb40-19"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@file_router.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/files/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{file_id}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File"</span>])</span>
<span id="cb40-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> download_file(file_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb40-21">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_file().get_file(file_id)</span>
<span id="cb40-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>:</span>
<span id="cb40-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span></span>
<span id="cb40-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb40-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> HTTPException(status_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">404</span>, detail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"File not found"</span>)</span></code></pre></div>
<p><strong>app/routes/user.py:</strong></p>
<div class="sourceCode" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb41-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> APIRouter, Depends, HTTPException</span>
<span id="cb41-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.responses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> JSONResponse</span>
<span id="cb41-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb41-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb41-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.utils.user <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_user</span>
<span id="cb41-6"></span>
<span id="cb41-7">user_router <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> APIRouter()</span>
<span id="cb41-8"></span>
<span id="cb41-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@user_router.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/users"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User"</span>])</span>
<span id="cb41-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_users():</span>
<span id="cb41-11">    users <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user()</span>
<span id="cb41-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> users</span>
<span id="cb41-13"></span>
<span id="cb41-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@user_router.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/users/</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_id}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, tags<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User"</span>])</span>
<span id="cb41-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user_profile(user_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb41-16">    user <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_user().get_user(user_id)</span>
<span id="cb41-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user:</span>
<span id="cb41-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> user</span>
<span id="cb41-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb41-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> HTTPException(status_code<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">404</span>, detail<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"User not found"</span>)</span></code></pre></div>
<p><strong>app/utils/auth.py:</strong></p>
<div class="sourceCode" id="cb42" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb42-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.security <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OAuth2PasswordBearer, OAuth2PasswordRequestForm</span>
<span id="cb42-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Depends, HTTPException</span>
<span id="cb42-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb42-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb42-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.models.user <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> User</span>
<span id="cb42-6"></span>
<span id="cb42-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_current_user():</span>
<span id="cb42-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the current user based on the access token</span></span>
<span id="cb42-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy user</span></span>
<span id="cb42-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> User(username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, password<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, email<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy@example.com"</span>)</span>
<span id="cb42-11"></span>
<span id="cb42-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user(username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb42-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the user based on the username and password</span></span>
<span id="cb42-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy user</span></span>
<span id="cb42-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> User(username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>username, password<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>password, email<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy@example.com"</span>)</span>
<span id="cb42-16"></span>
<span id="cb42-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user_by_id(user_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb42-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the user based on the user ID</span></span>
<span id="cb42-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy user</span></span>
<span id="cb42-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> User(username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, password<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, email<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy@example.com"</span>)</span></code></pre></div>
<p><strong>app/utils/file.py:</strong></p>
<div class="sourceCode" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb43-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Depends, HTTPException</span>
<span id="cb43-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb43-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb43-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.models.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> File</span>
<span id="cb43-5"></span>
<span id="cb43-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_file():</span>
<span id="cb43-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the file based on the file ID</span></span>
<span id="cb43-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy file</span></span>
<span id="cb43-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> File(file_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, filename<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy_file.txt"</span>, content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy file content"</span>)</span>
<span id="cb43-10"></span>
<span id="cb43-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bytes</span>, filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb43-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should upload the file and return the file object</span></span>
<span id="cb43-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy file</span></span>
<span id="cb43-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> File(file_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, filename<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>filename, content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>)</span></code></pre></div>
<p><strong>app/utils/user.py:</strong></p>
<div class="sourceCode" id="cb44" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb44-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Depends, HTTPException</span>
<span id="cb44-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb44-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb44-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> app.models.user <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> User</span>
<span id="cb44-5"></span>
<span id="cb44-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user(username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb44-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the user based on the username and password</span></span>
<span id="cb44-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy user</span></span>
<span id="cb44-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> User(username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>username, password<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>password, email<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy@example.com"</span>)</span>
<span id="cb44-10"></span>
<span id="cb44-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_user_by_id(user_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb44-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This function should return the user based on the user ID</span></span>
<span id="cb44-13">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For simplicity, we will just return a dummy user</span></span>
<span id="cb44-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> User(username<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, password<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy"</span>, email<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dummy@example.com"</span>)</span></code></pre></div>
<p><strong>app/models/file.py:</strong></p>
<div class="sourceCode" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb45-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb45-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb45-3"></span>
<span id="cb45-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> File(BaseModel):</span>
<span id="cb45-5">    file_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb45-6">    filename: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-7">    content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-8"></span>
<span id="cb45-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Config:</span>
<span id="cb45-10">        orm_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span></code></pre></div>
<p><strong>app/models/user.py:</strong></p>
<div class="sourceCode" id="cb46" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb46-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb46-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Optional</span>
<span id="cb46-3"></span>
<span id="cb46-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> User(BaseModel):</span>
<span id="cb46-5">    username: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb46-6">    password: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb46-7">    email: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb46-8"></span>
<span id="cb46-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Config:</span>
<span id="cb46-10">        orm_mode <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span></code></pre></div>
<p><strong>requirements.txt:</strong></p>
<div class="sourceCode" id="cb47" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb47-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fastapi</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=0.95.3</span>
<span id="cb47-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">uvicorn</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=0.20.0</span>
<span id="cb47-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python-dotenv==0.19.2</span></span>
<span id="cb47-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">sqlalchemy</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=1.4.34</span>
<span id="cb47-5"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pytest</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=7.1.2</span>
<span id="cb47-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pytest-cov==2.12.1</span></span></code></pre></div>
<p><strong>如何啟動專案:</strong></p>
<ol type="1">
<li>安裝所需的庫: <code>pip install -r requirements.txt</code></li>
<li>啟動 FastAPI: <code>uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload</code></li>
<li>開啟瀏覽器並瀏覽 <code>http://localhost:8000/docs</code> 來檢視 API 文件</li>
</ol>
<p>注意: 這是一個簡化的範例，需要根據實際需求進行擴展和修改。</p>
<p>===== 👷 Reviewer 結果 ===== <strong>審查結果:</strong></p>
<p>經過審查，我們判斷該系統的商業價值、技術可行性和需求清楚性如下：</p>
<p><strong>商業價值:</strong></p>
<p>該系統能夠提高公司技術文件的查詢效率和便捷性，減少工作時間和成本。這對於公司的業務運營和效率提升具有重要意義。因此，我們判斷該系統的商業價值是足夠的。</p>
<p><strong>技術可行性:</strong></p>
<p>系統的架構和設計看起來是合理的，使用了現成的開源框架（FastAPI）和資料庫（MySQL或PostgreSQL）。安全控制和文件管理也被合理地設計和實現。因此，我們判斷該系統的技術可行性是足夠的。</p>
<p><strong>需求清楚性:</strong></p>
<p>需求描述清晰明了，涵蓋了系統的主要功能和非功能性需求。系統的使用場景和使用者群也被清晰地定義。因此，我們判斷該系統的需求清楚性是足夠的。</p>
<p><strong>最終判斷:</strong></p>
<p>基於上述分析，我們判斷該系統的商業價值、技術可行性和需求清楚性都是足夠的。因此，我們 APPROVE 這個系統。</p>
<p><strong>建議:</strong></p>
<ol type="1">
<li>進一步優化系統的性能和穩定性。</li>
<li>實現系統的持續整合和部署。</li>
<li>提供系統的使用者手冊和教學資源。</li>
</ol>
</div>
</div>
</div>
<p>初步看下來除了技術都用 python 實現這點不完全符合現實專案長期運作的要件外，整體看起來正常。</p>
</section>
<section id="不合理的需求" class="level2">
<h2 class="anchored" data-anchor-id="不合理的需求">不合理的需求</h2>
<p>測試一個完全不合常理的需求</p>
<pre><code>我要做一個基於 LLM 的 RAG 系統，在筆電上 3 秒內完成回答。</code></pre>
<p>結果</p>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-5-contents" aria-controls="callout-5" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
結果回應
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-5" class="callout-5-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>===== FINAL REPORT =====</p>
<p>===== 🧑‍💼 PM 設計 ===== <strong>需求描述</strong></p>
<p>基於 LLM 的 RAG (Risk, Action, Goal) 系統，旨在於筆電上快速回答相關問題。系統的要求如下：</p>
<ul>
<li>使用 LLM 技術作為核心引擎</li>
<li>必須在 3 秒內完成回答</li>
<li>提供 RAG 結構的回答，包含風險、行動和目標資訊</li>
</ul>
<p><strong>執行任務列表</strong></p>
<p>下列為系統開發的任務列表：</p>
<ol type="1">
<li><strong>選擇 LLM 引擎</strong></li>
</ol>
<ul>
<li>選擇合適的 LLM 型別（例如 BERT, RoBERTa 等）</li>
<li>設定 LLM 變數，包括模型大小、精度等</li>
</ul>
<ol start="2" type="1">
<li><strong>構建 RAG 結構</strong></li>
</ol>
<ul>
<li>設計 RAG 結構的資料模型，包含風險、行動和目標資訊</li>
<li>實現 RAG 結構的資料儲存和查詢功能</li>
</ul>
<ol start="3" type="1">
<li><strong>開發快速回答引擎</strong></li>
</ol>
<ul>
<li>實現 LLM 和 RAG 結構的整合</li>
<li>實現快速回答的算法和模型</li>
</ul>
<ol start="4" type="1">
<li><strong>優化系統性能</strong></li>
</ol>
<ul>
<li>優化系統的計算效率，達到 3 秒內完成回答的速度</li>
<li>實現系統的穩定性和安全性</li>
</ul>
<ol start="5" type="1">
<li><strong>測試和驗證</strong></li>
</ol>
<ul>
<li>進行系統的測試和驗證，確保系統的正確性和穩定性</li>
</ul>
<ol start="6" type="1">
<li><strong>優化和迭代</strong></li>
</ol>
<ul>
<li>根據使用者反饋和系統的性能進行優化和迭代</li>
</ul>
<p><strong>注意</strong></p>
<ul>
<li>這個需求描述和任務列表可能需要進一步細化和修改，根據系統開發的具體情況進行調整。</li>
</ul>
<p>===== 🧑‍💼 商業價值評估 ===== 基於 LLM 的 RAG 系統是一個非常有前途的專案，以下是我對其的評估：</p>
<p><strong>1. 商業價值</strong></p>
<p>此專案具有很高的商業價值，因為它可以提供快速和準確的風險評估和管理服務，幫助企業更好地管理風險和達到目標。</p>
<p><strong>2. 目標客群</strong></p>
<p>目標客群包括企業、金融機構、政府機構等，需要風險評估和管理的組織。</p>
<p><strong>3. 解決痛點</strong></p>
<p>此專案可以解決以下痛點：</p>
<ul>
<li>快速和準確的風險評估和管理</li>
<li>提供風險、行動和目標資訊，幫助企業做出明智的決策</li>
<li>優化系統性能，達到 3 秒內完成回答的速度</li>
</ul>
<p><strong>4. 市場機會</strong></p>
<p>市場機會包括：</p>
<ul>
<li>風險管理和評估市場的快速發展</li>
<li>組織對於風險評估和管理的需求日益增長</li>
<li>LLM 技術的快速發展和應用</li>
</ul>
<p><strong>5. ROI</strong></p>
<p>ROI 可能非常高，因為此專案可以幫助企業更好地管理風險和達到目標，從而提高企業的收益和效率。</p>
<p><strong>6. KPI</strong></p>
<p>KPI 可能包括：</p>
<ul>
<li>系統的使用率和滿意度</li>
<li>風險評估和管理的準確度和速度</li>
<li>組織對於風險評估和管理的滿意度</li>
</ul>
<p><strong>7. 開發風險</strong></p>
<p>開發風險包括：</p>
<ul>
<li>LLM 引擎的選擇和配置</li>
<li>RAG 結構的設計和實現</li>
<li>系統的性能和穩定性</li>
<li>使用者反饋和系統的迭代和優化</li>
</ul>
<p><strong>8. 是否值得投入</strong></p>
<p>基於 LLM 的 RAG 系統是一個值得投入的專案，因為它具有很高的商業價值和市場機會，同時也能夠解決企業的痛點。</p>
<p><strong>VALUE_SCORE:</strong> 9/10</p>
<p>我給予此專案 9 分，因為它具有很高的商業價值和市場機會，同時也能夠解決企業的痛點。但是，我也認為開發風險需要嚴格控制和監測，以確保系統的穩定性和安全性。</p>
<p>===== 👷 Engineer 實作 ===== 根據系統設計，我們可以產出以下內容：</p>
<p><strong>專案資料夾結構</strong></p>
<div class="sourceCode" id="cb49" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb49-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">rag-system/</span></span>
<span id="cb49-2">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">app/</span></span>
<span id="cb49-3">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb49-4">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">main.py</span></span>
<span id="cb49-5">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">models/</span></span>
<span id="cb49-6">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb49-7">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">rag_model.py</span></span>
<span id="cb49-8">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">llama_model.py</span></span>
<span id="cb49-9">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">routes/</span></span>
<span id="cb49-10">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb49-11">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">rag_routes.py</span></span>
<span id="cb49-12">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">utils/</span></span>
<span id="cb49-13">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb49-14">            <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">llama_utils.py</span></span>
<span id="cb49-15">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">requirements.txt</span></span>
<span id="cb49-16">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">tests/</span></span>
<span id="cb49-17">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">__init__.py</span></span>
<span id="cb49-18">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">test_rag_model.py</span></span>
<span id="cb49-19">        <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">test_llama_model.py</span></span>
<span id="cb49-20">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">.gitignore</span></span>
<span id="cb49-21">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">README.md</span></span></code></pre></div>
<p><strong>Python FastAPI code</strong></p>
<div class="sourceCode" id="cb50" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb50-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># app/main.py</span></span>
<span id="cb50-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FastAPI</span>
<span id="cb50-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb50-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> models.rag_model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> RAGModel</span>
<span id="cb50-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> models.llama_model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> LlamaModel</span>
<span id="cb50-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> utils.llama_utils <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> get_llama_response</span>
<span id="cb50-7"></span>
<span id="cb50-8">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FastAPI()</span>
<span id="cb50-9"></span>
<span id="cb50-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> RAGRequest(BaseModel):</span>
<span id="cb50-11">    prompt: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb50-12"></span>
<span id="cb50-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/rag"</span>)</span>
<span id="cb50-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_rag_response(request: RAGRequest):</span>
<span id="cb50-15">    llama_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LlamaModel()</span>
<span id="cb50-16">    rag_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RAGModel()</span>
<span id="cb50-17">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_llama_response(request.prompt, llama_model)</span>
<span id="cb50-18">    rag_response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rag_model.build_rag_response(response)</span>
<span id="cb50-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> rag_response</span></code></pre></div>
<div class="sourceCode" id="cb51" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb51-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># app/models/rag_model.py</span></span>
<span id="cb51-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> RAGModel:</span>
<span id="cb51-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> build_rag_response(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, response):</span>
<span id="cb51-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 建立 RAG 結構的資料模型</span></span>
<span id="cb51-5">        rag_response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb51-6">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"risk"</span>: response[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"risk"</span>],</span>
<span id="cb51-7">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"action"</span>: response[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"action"</span>],</span>
<span id="cb51-8">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"goal"</span>: response[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"goal"</span>]</span>
<span id="cb51-9">        }</span>
<span id="cb51-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> rag_response</span></code></pre></div>
<div class="sourceCode" id="cb52" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb52-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># app/models/llama_model.py</span></span>
<span id="cb52-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LlamaModel:</span>
<span id="cb52-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb52-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 設定 LLM 變數，包括模型大小、精度等</span></span>
<span id="cb52-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.model_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"large"</span></span>
<span id="cb52-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span></span>
<span id="cb52-7"></span>
<span id="cb52-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_llama_response(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, prompt):</span>
<span id="cb52-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 使用 LLM 技術取得答案</span></span>
<span id="cb52-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這裡假設使用 Hugging Face 的 Transformers 序列化</span></span>
<span id="cb52-11">        <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> LLaMAForConditionalGeneration</span>
<span id="cb52-12">        <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb52-13"></span>
<span id="cb52-14">        model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LLaMAForConditionalGeneration.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"facebook/llama-large-32k"</span>)</span>
<span id="cb52-15">        tokenizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.tokenizer</span>
<span id="cb52-16"></span>
<span id="cb52-17">        inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokenizer(prompt, return_tensors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pt"</span>)</span>
<span id="cb52-18">        outputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.generate(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>inputs, num_beams<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, early_stopping<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb52-19">        response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokenizer.decode(outputs[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], skip_special_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb52-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span></code></pre></div>
<div class="sourceCode" id="cb53" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb53-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># app/utils/llama_utils.py</span></span>
<span id="cb53-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb53-3"></span>
<span id="cb53-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_llama_response(prompt, llama_model):</span>
<span id="cb53-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 取得 LLM 的答案</span></span>
<span id="cb53-6">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llama_model.get_llama_response(prompt)</span>
<span id="cb53-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span></code></pre></div>
<p><strong>requirements.txt</strong></p>
<div class="sourceCode" id="cb54" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb54-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">fastapi</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=0.93.0</span>
<span id="cb54-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">pydantic</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=1.9.1</span>
<span id="cb54-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">huggingface-transformers==4.21.3</span></span>
<span id="cb54-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">torch</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>=1.10.0</span></code></pre></div>
<p><strong>啟動專案指令</strong></p>
<div class="sourceCode" id="cb55" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb55-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv rag-env</span>
<span id="cb55-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> rag-env/bin/activate</span>
<span id="cb55-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-r</span> requirements.txt</span>
<span id="cb55-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uvicorn</span> app.main:app <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--port</span> 8000 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--host</span> 0.0.0.0</span></code></pre></div>
<p>這樣就可以啟動專案並測試 RAG 系統了！</p>
<p>===== 👷 Reviewer 結果 ===== 判斷結果：REJECT</p>
<p>原因：</p>
<ol type="1">
<li>系統需求與技術實作不一致：系統需求中要求 3 秒內完成回答，但技術實作中並沒有明確的優化策略來達到這個目標。例如，實際上需要花費更長的時間來訓練 LLM 模型和建立 RAG 結構，導致系統無法在 3 秒內完成回答。</li>
<li>缺乏明確的開發風險控制和監測：雖然 Business 部分有提到開發風險，但並沒有提供明確的控制和監測措施來確保系統的穩定性和安全性。</li>
<li>架構包含明顯的幻想：雖然 Engineer 部分提供了一個基本的架構，但並沒有明確的方法來處理 LLM 的回答結果和 RAG 結構的建立。例如，實際上需要花費更多的時間和資源來訓練 LLM 模型和建立 RAG 結構。</li>
</ol>
<p>建議：</p>
<ul>
<li>進一步細化和修改需求描述和任務列表，確保系統需求與技術實作一致。</li>
<li>提供明確的開發風險控制和監測措施來確保系統的穩定性和安全性。</li>
<li>追加明確的方法來處理 LLM 的回答結果和 RAG 結構的建立。</li>
</ul>
<p>通過這些修改和改進，將可以提高系統的可行性和商業價值。</p>
</div>
</div>
</div>
</section>
</section>
<section id="總結" class="level1">
<h1>總結</h1>
<p>雖然多測幾次後，看起來整體還是有 LLM 太溫和、容易 APPROVE 的問題。<sup>1</sup>不過本文的重點是學習 LangGraph 的特色用法<del>順便摸一下 Linux</del>，初步體驗後可以感受到它的設定比起 LangChain 有更多能自由發揮的地方，也有點像是 coding 版的 n8n ，可以自由設定節點跟事件。</p>



</section>


<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">腳註</h2>

<ol>
<li id="fn1"><p>這個問題遇到好多次了：（，未來在設計系統的時候，可能要再多加思考如何讓容易輕判的 LLM 發揮最大效益。↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Python</category>
  <category>LLM</category>
  <category>LangGraph</category>
  <category>linux</category>
  <category>just play</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/play-langgraph-in-linux/</guid>
  <pubDate>Thu, 02 Jul 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/play-langgraph-in-linux/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>vibe coding 初體驗！用不熟悉的框架建自己要用的本機 LLM WebUI 心得</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/llm-course-note3/</link>
  <description><![CDATA[ 






<div data-ai-tag="6">

</div>
<script src="../../../asserts/box.js" defer=""></script>
<section id="緣起" class="level1">
<h1>緣起</h1>
<p>我的論文是用英文寫的，最近身陷論文不斷來回修改的輪迴中，但我的英文沒到非常好，所以後來摸索出的工作模式是：先用自己的畫寫一段、交給 LLM 確認有沒有文法錯誤或更好的寫法、驗證後再改寫成我自己的語調；想不到單字可以用哪些就請它丟幾個範例給我；懶得看文獻英文時，叫它翻給我。因為這些動作不太需要很好的模型就能處理，所以網路不穩時，我都直接開 ollama 的 GUI 對話介面處理。</p>
<p>雖然這些動作看似簡單，但是我常常要不斷重複下不同的 prompt，然後等 LLM 吐出一大堆其實我也不需要看的文字。 ollama 還會貼心的儲存我的對話記錄，可是我真的不需要，用完還要手動刪除，好麻煩 ==。以上種種的體驗讓我覺得 <del>寫論文好麻煩，間接耽誤寫作進度</del>。</p>
<p>有一天，我又寫論文寫到卡住時，突然想到：ㄟˊ我還沒用過之前申請過的 github copilot education 的免費額度開發專案，不如來做個玩具吧應該很快。於是參考之前參加過的講座跟課程，使用新的 workflow:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
A[寫 spec 文件] --&gt; B[Agent 執行 spec]
B --&gt; C{測試是否達成目標}
C --&gt;|是| D[git 儲存]
C --&gt;|否| E[git 還原並修改 spec 文件]
D --&gt; F[完成]
E --&gt; B
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>之前都是直接用網頁服務，用把資料去識別化的方式跟溝通產 code，想說這樣比較能學到東西。這次我先跟網頁上的 AI 討論，決定要哪些技術後，再請它寫個給 Vscode 上的 Agent 看的 spec，交由因為 github copilot education 方案所以可以免費使用一下的 Claude Haiku 4.5 執行開發，我再輕鬆驗收，不行就用 git 回到上一版。因為這次的專案完全交給 Agent 開發，細節技術目前沒有要深入鑽研的意思，所以當 AI 說我的需求很適合用輕量級的 Node.js+ Express 開發時我接受了，想說順便體驗看看不同的技術也好。不過網頁部分我還是請它用基本的 HTML/CSS/JS 就好，怕出問題的時候我看不懂問題出在哪 XD。</p>
<p>以下以 spec 為單位，紀錄一下當時我在想甚麼。</p>
</section>
<section id="開發過程" class="level1">
<h1>開發過程</h1>
<section id="順利期" class="level2">
<h2 class="anchored" data-anchor-id="順利期">順利期</h2>
<section id="spec-1" class="level3">
<h3 class="anchored" data-anchor-id="spec-1">spec 1</h3>
<p>請 AI 寫的文件長這樣</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb1-1">You are a senior full-stack engineer. Your task is to build a **minimal working Local LLM WebUI**.</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 Objective</span></span>
<span id="cb1-4"></span>
<span id="cb1-5">Create a simple web interface that allows users to:</span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>Select a local model (via Ollama)</span>
<span id="cb1-8"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>Choose a task type (summary / translation / normal Q&amp;A)</span>
<span id="cb1-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>Input text and receive a response from the model</span>
<span id="cb1-10"></span>
<span id="cb1-11">---</span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧩 Technical Constraints (MUST FOLLOW)</span></span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Frontend: HTML + Vanilla JavaScript ONLY (no frameworks)</span>
<span id="cb1-16"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Backend: Node.js with Express</span>
<span id="cb1-17"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>No database</span>
<span id="cb1-18"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>No authentication</span>
<span id="cb1-19"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>No styling required (keep UI minimal)</span>
<span id="cb1-20"></span>
<span id="cb1-21">---</span>
<span id="cb1-22"></span>
<span id="cb1-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🏗️ Architecture</span></span>
<span id="cb1-24"></span>
<span id="cb1-25">Browser (HTML + JS)</span>
<span id="cb1-26">→ calls backend API (Node.js)</span>
<span id="cb1-27">→ backend calls Ollama API at http://localhost:11434</span>
<span id="cb1-28"></span>
<span id="cb1-29">---</span>
<span id="cb1-30"></span>
<span id="cb1-31"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📡 Backend API Specification</span></span>
<span id="cb1-32"></span>
<span id="cb1-33"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## 1️⃣ GET /models</span></span>
<span id="cb1-34"></span>
<span id="cb1-35">Purpose: Retrieve available models from Ollama</span>
<span id="cb1-36"></span>
<span id="cb1-37">Implementation:</span>
<span id="cb1-38"></span>
<span id="cb1-39"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Call: GET http://localhost:11434/api/tags</span>
<span id="cb1-40"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Return the raw JSON response</span>
<span id="cb1-41"></span>
<span id="cb1-42">---</span>
<span id="cb1-43"></span>
<span id="cb1-44"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## 2️⃣ POST /ask</span></span>
<span id="cb1-45"></span>
<span id="cb1-46">Purpose: Send a request to the model</span>
<span id="cb1-47"></span>
<span id="cb1-48">Request JSON:</span>
<span id="cb1-49">{</span>
<span id="cb1-50">"model": "llama3:8b",</span>
<span id="cb1-51">"type": "summary | translate | normal",</span>
<span id="cb1-52">"input": "user input"</span>
<span id="cb1-53">}</span>
<span id="cb1-54"></span>
<span id="cb1-55">---</span>
<span id="cb1-56"></span>
<span id="cb1-57"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧠 Prompt Processing Logic (IMPORTANT)</span></span>
<span id="cb1-58"></span>
<span id="cb1-59">Implement a function:</span>
<span id="cb1-60"></span>
<span id="cb1-61">function buildPrompt(type, input)</span>
<span id="cb1-62"></span>
<span id="cb1-63">Rules:</span>
<span id="cb1-64"></span>
<span id="cb1-65"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>summary:</span>
<span id="cb1-66">  "Summarize the following in bullet points:\n" + input</span>
<span id="cb1-67"></span>
<span id="cb1-68"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>translate:</span>
<span id="cb1-69">  "Translate the following into English:\n" + input</span>
<span id="cb1-70"></span>
<span id="cb1-71"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>normal:</span>
<span id="cb1-72">  return input as-is</span>
<span id="cb1-73"></span>
<span id="cb1-74">---</span>
<span id="cb1-75"></span>
<span id="cb1-76"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🤖 Call Ollama API</span></span>
<span id="cb1-77"></span>
<span id="cb1-78">POST http://localhost:11434/api/generate</span>
<span id="cb1-79"></span>
<span id="cb1-80">Body:</span>
<span id="cb1-81">{</span>
<span id="cb1-82">"model": model,</span>
<span id="cb1-83">"prompt": prompt,</span>
<span id="cb1-84">"stream": false</span>
<span id="cb1-85">}</span>
<span id="cb1-86"></span>
<span id="cb1-87">---</span>
<span id="cb1-88"></span>
<span id="cb1-89"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🖥️ Frontend Requirements</span></span>
<span id="cb1-90"></span>
<span id="cb1-91"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Initialization</span></span>
<span id="cb1-92"></span>
<span id="cb1-93"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>On page load, call /models</span>
<span id="cb1-94"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Populate the model <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">select</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span> dropdown</span>
<span id="cb1-95"></span>
<span id="cb1-96">---</span>
<span id="cb1-97"></span>
<span id="cb1-98"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## UI Elements (REQUIRED)</span></span>
<span id="cb1-99"></span>
<span id="cb1-100"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>Model selector (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">select</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>)</span>
<span id="cb1-101"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>Task type selector (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">select</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>)</span>
<span id="cb1-102"></span>
<span id="cb1-103"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   * </span>normal</span>
<span id="cb1-104"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   * </span>summary</span>
<span id="cb1-105"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">   * </span>translate</span>
<span id="cb1-106"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>Text input (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">textarea</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>)</span>
<span id="cb1-107"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">4. </span>Submit button</span>
<span id="cb1-108"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">5. </span>Output display area (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pre</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span> or <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>)</span>
<span id="cb1-109"></span>
<span id="cb1-110">---</span>
<span id="cb1-111"></span>
<span id="cb1-112"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## User Flow</span></span>
<span id="cb1-113"></span>
<span id="cb1-114"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>User enters text</span>
<span id="cb1-115"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Clicks submit</span>
<span id="cb1-116"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Frontend calls POST /ask</span>
<span id="cb1-117"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Display the response</span>
<span id="cb1-118"></span>
<span id="cb1-119">---</span>
<span id="cb1-120"></span>
<span id="cb1-121"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># ⚠️ Constraints &amp; Notes</span></span>
<span id="cb1-122"></span>
<span id="cb1-123"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Frontend MUST NOT call Ollama directly (avoid CORS issues)</span>
<span id="cb1-124"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>All Ollama requests must go through backend</span>
<span id="cb1-125"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Keep the implementation simple and minimal</span>
<span id="cb1-126"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>No need for error handling (keep MVP scope)</span>
<span id="cb1-127"></span>
<span id="cb1-128">---</span>
<span id="cb1-129"></span>
<span id="cb1-130"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📦 Expected Output Files</span></span>
<span id="cb1-131"></span>
<span id="cb1-132">Provide complete code for:</span>
<span id="cb1-133"></span>
<span id="cb1-134"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>index.html</span>
<span id="cb1-135"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>app.js (frontend logic)</span>
<span id="cb1-136"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>server.js (backend)</span>
<span id="cb1-137"></span>
<span id="cb1-138">---</span>
<span id="cb1-139"></span>
<span id="cb1-140"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># ✅ Success Criteria</span></span>
<span id="cb1-141"></span>
<span id="cb1-142">The app should:</span>
<span id="cb1-143"></span>
<span id="cb1-144"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Display available Ollama models</span>
<span id="cb1-145"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Allow model selection</span>
<span id="cb1-146"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Accept user input and return model responses</span>
<span id="cb1-147"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Work without CORS errors</span>
<span id="cb1-148"></span>
<span id="cb1-149">---</span>
<span id="cb1-150"></span>
<span id="cb1-151"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🚀 Bonus (Optional, if easy)</span></span>
<span id="cb1-152"></span>
<span id="cb1-153"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Ctrl + Enter to submit</span>
<span id="cb1-154"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Show "Thinking..." before response arrives</span>
<span id="cb1-155"></span>
<span id="cb1-156">---</span>
<span id="cb1-157"></span>
<span id="cb1-158">Output ONLY the complete code files. Do not include explanations.</span></code></pre></div>
<p>因為是第一次做，不是很確定 Claude Haiku 4.5 的能力到哪，想說用英文可能比較不會出錯，所以文件就用英文。連同開發選項也有所調整，以便觀察看看成品如何。不過實際執行起來是我多心了，Claude Haiku 4.5 連同 Bonus 的要求都一併完成，技術限制也有好好遵守，相當厲害。</p>
</section>
<section id="spec-2" class="level3">
<h3 class="anchored" data-anchor-id="spec-2">spec 2</h3>
<p>發現用中文下 prompt 也沒差後，這次就用中文寫文件了。</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb2-1">你是一位資深全端工程師，請幫修改本資料夾下的本地 LLM WebUI。</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 專案目標</span></span>
<span id="cb2-4"></span>
<span id="cb2-5">將目前的本地 LLM WebUI 改成以繁體中文查詢英文相關的 LLM WebUI </span>
<span id="cb2-6"></span>
<span id="cb2-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>修改功能：原本的 task (summary / translation / normal Q&amp;A)，請改成(查單字/檢查文法/翻譯/其他)</span>
<span id="cb2-8"></span>
<span id="cb2-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>加入 CSS 樣式，可調整 light mode &amp; dark mode</span>
<span id="cb2-10"></span>
<span id="cb2-11">---</span>
<span id="cb2-12"></span>
<span id="cb2-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧩 技術限制（務必遵守）</span></span>
<span id="cb2-14"></span>
<span id="cb2-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>前端只能使用：HTML + Vanilla JavaScript（不可用框架）</span>
<span id="cb2-16"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>後端使用：Node.js + Express</span>
<span id="cb2-17"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>不需要資料庫</span>
<span id="cb2-18"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>不需要登入系統</span>
<span id="cb2-19"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>系統架構請盡量不要更動</span>
<span id="cb2-20"></span>
<span id="cb2-21">---</span>
<span id="cb2-22"></span>
<span id="cb2-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🔌 系統架構(保持不變)</span></span>
<span id="cb2-24"></span>
<span id="cb2-25">Browser (HTML + JS)</span>
<span id="cb2-26">→ 呼叫 backend API (Node.js)</span>
<span id="cb2-27">→ backend 呼叫 Ollama API (http://localhost:11434)</span>
<span id="cb2-28"></span>
<span id="cb2-29"></span>
<span id="cb2-30"></span>
<span id="cb2-31"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧠 Prompt 處理邏輯（非常重要）</span></span>
<span id="cb2-32"></span>
<span id="cb2-33">請將以下函式規則修改為：</span>
<span id="cb2-34"></span>
<span id="cb2-35">function buildPrompt(type, input)</span>
<span id="cb2-36"></span>
<span id="cb2-37">規則如下：</span>
<span id="cb2-38"></span>
<span id="cb2-39"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>find-word：</span>
<span id="cb2-40">  "查出具有以下意思的英文單字1-5個，並用繁體中文解釋用法：\n" + input</span>
<span id="cb2-41"></span>
<span id="cb2-42"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>check-grammar:</span>
<span id="cb2-43">  "Check if there is any grammar mistake：\n" + input</span>
<span id="cb2-44"></span>
<span id="cb2-45"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>translate：</span>
<span id="cb2-46">  "請翻譯成繁體中文：\n" + input</span>
<span id="cb2-47"></span>
<span id="cb2-48"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>normal：</span>
<span id="cb2-49">  直接使用 input</span>
<span id="cb2-50"></span>
<span id="cb2-51"></span>
<span id="cb2-52"></span>
<span id="cb2-53"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># ⚠️ 限制與注意事項</span></span>
<span id="cb2-54"></span>
<span id="cb2-55"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>程式碼需簡單清楚，不要過度設計</span>
<span id="cb2-56"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>不需要錯誤處理（保持最小可行）</span>
<span id="cb2-57"></span>
<span id="cb2-58">---</span>
<span id="cb2-59"></span>
<span id="cb2-60"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📦 最終輸出要求</span></span>
<span id="cb2-61"></span>
<span id="cb2-62">請修改以下檔案：</span>
<span id="cb2-63"></span>
<span id="cb2-64"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>index.html</span>
<span id="cb2-65"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>app.js（前端）</span>
<span id="cb2-66"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>server.js（後端）</span>
<span id="cb2-67"></span>
<span id="cb2-68">---</span>
<span id="cb2-69"></span>
<span id="cb2-70"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 成功標準</span></span>
<span id="cb2-71"></span>
<span id="cb2-72">完成後應可：</span>
<span id="cb2-73"></span>
<span id="cb2-74"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>正確顯示 Ollama 模型列表</span>
<span id="cb2-75"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>可選擇模型</span>
<span id="cb2-76"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>可輸入文字並取得回應</span>
<span id="cb2-77"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>不出現 CORS 錯誤</span>
<span id="cb2-78"></span>
<span id="cb2-79"></span>
<span id="cb2-80"></span>
<span id="cb2-81">---</span>
<span id="cb2-82"></span>
<span id="cb2-83">請直接輸出完整程式碼，不需要解釋。</span></code></pre></div>
<p>因為上一個 spece 的最低要求只是生出大概的架構，這版的目標是進一步按照我的需求做調整。所以我盡量把會用到的核心功能都寫出來。結果功能部份產出順利，但是網頁排版因為一開始就沒有特別下，結果排版超怪，開始進入撞牆期了 XD</p>
</section>
</section>
<section id="微撞牆期" class="level2">
<h2 class="anchored" data-anchor-id="微撞牆期">微撞牆期</h2>
<p>這裡開始感覺下 prompt 熟練許多，要改動的地方也只有一個檔案而已，所以我直接跳過跟 AI 討論，自己改之前的 spec 來寫。</p>
<section id="spec-3" class="level3">
<h3 class="anchored" data-anchor-id="spec-3">spec 3</h3>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb3-1">你是一位資深全端工程師，請幫忙修改本資料夾下的本地 LLM WebUI。</span>
<span id="cb3-2"></span>
<span id="cb3-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 專案目標</span></span>
<span id="cb3-4"></span>
<span id="cb3-5">將目前的以繁體中文查詢英文相關的 LLM WebUI 的前端介面做以下修改：</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>切換淺色模式移到更右上角，與標題平行</span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>"查詢類型"與"輸入文字"區域並排，輸入文字區域在"輸入文字"右邊，輸入框可以比現在小一點。</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>整體版面配置縮成筆電不須滑動的格式</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧩 技術限制（務必遵守）</span></span>
<span id="cb3-14"></span>
<span id="cb3-15"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>前端只能使用：HTML + Vanilla JavaScript（不可用框架）</span>
<span id="cb3-16"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>只更動前端，其他不要作任何更動</span>
<span id="cb3-17"></span>
<span id="cb3-18"></span>
<span id="cb3-19">---</span>
<span id="cb3-20"></span>
<span id="cb3-21">請直接輸出完整程式碼，不需要解釋。</span></code></pre></div>
<p>結果這版的指示太不具體了，根本沒有辦法改出我要的版面 QQ</p>
</section>
<section id="spec-4" class="level3">
<h3 class="anchored" data-anchor-id="spec-4">spec 4</h3>
<p>記取了上一個 spece 的教訓，這次我嘗試另一種寫法：</p>
<pre class="markdwon"><code>你是一位資深全端工程師，請幫忙修改本資料夾下的本地 LLM WebUI。

# 🎯 專案目標

將目前的以繁體中文查詢英文相關的 LLM WebUI 的前端介面做以下修改：

1. "選擇模型"與"查詢類型"區域並排，比例 8:2

2. "輸入文字"與送出按鈕並排，比例 8:2。

3. 送出按鈕顯示字串改為"送出( Ctrl + Enter)"

4. 整體版面配置保持縮成筆電不須滑動的格式


# 🚀 Optional

如果簡單可以再加入:

* 預設背景為自動記憶本機設定，若之前已設定暗色則下次載入時為暗色背景

* 回應渲染markdown語法成對應 html 格式



# 🧩 技術限制（務必遵守）

* 前端只能使用：HTML + Vanilla JavaScript（不可用框架）
* 只更動前端，其他不要作任何更動


---

請直接輸出完整程式碼，不需要解釋。

</code></pre>
<p>結果比剛才好一點，不過「“輸入文字”與送出按鈕並排」被理解成按鈕的上邊框跟寫”輸入文字”的文字區域平行，但我其實是希望按鈕是跟”輸入文字”下方的輸入區域並排，恩，如果是真正的工程師大概也會誤解我的意思，My bad。還好追加要求後順利解決！</p>
</section>
</section>
<section id="感覺要收尾但其實沒有期" class="level2">
<h2 class="anchored" data-anchor-id="感覺要收尾但其實沒有期">感覺要收尾但其實沒有期</h2>
<p>這裡我當時覺得能收尾了，於是進入寫文件 &amp; 包 docker 的階段。</p>
<section id="spec-5" class="level3">
<h3 class="anchored" data-anchor-id="spec-5">spec 5</h3>
<pre class="markdwon"><code>你是一位資深系統分析師，請分析本資料夾下的程式檔案結構，並撰寫系統文件


# 目標

分析系統架構與檔案規格，撰寫 markdown ，內容必須要涵蓋以下：

1. 整體系統架構，包含當使用者操作時的正常流程圖、程式腳本互動。

2. 逐檔案解釋用途、程式架構

3. 本機系統規格、測試方式建議

# 輸出文件

1 個說明文件在本資料夾下：

1. 說明文件.md
</code></pre>
<p>結果可能是我下得不夠具體，一開始 Claude 寫的文件很簡單，流程圖還是我後面要求它補上的。</p>
</section>
<section id="spec-6" class="level3">
<h3 class="anchored" data-anchor-id="spec-6">spec 6</h3>
<p>這版動了包 docker 丟到 github 上的念頭，但我對包 docker 但模型連本機的 ollama 的設定還不是很熟練，所以還是請 AI 寫了 spec。</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb6-1">You are a senior DevOps engineer.</span>
<span id="cb6-2"></span>
<span id="cb6-3">Your task is to design and implement **only the Docker-related setup** for a Local LLM WebUI project using Ollama.</span>
<span id="cb6-4"></span>
<span id="cb6-5">---</span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 Goal</span></span>
<span id="cb6-8"></span>
<span id="cb6-9">Provide Docker support for a web application that connects to:</span>
<span id="cb6-10"></span>
<span id="cb6-11">Ollama</span>
<span id="cb6-12"></span>
<span id="cb6-13">The system must support TWO deployment modes:</span>
<span id="cb6-14"></span>
<span id="cb6-15">---</span>
<span id="cb6-16"></span>
<span id="cb6-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🟢 Mode 1: docker run (Quick Start)</span></span>
<span id="cb6-18"></span>
<span id="cb6-19">A single-container setup where:</span>
<span id="cb6-20"></span>
<span id="cb6-21"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Web UI (HTML + JS)</span>
<span id="cb6-22"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Backend (Node.js + Express)</span>
<span id="cb6-23"></span>
<span id="cb6-24">run inside one container.</span>
<span id="cb6-25"></span>
<span id="cb6-26">Ollama is assumed to run externally on the host machine.</span>
<span id="cb6-27"></span>
<span id="cb6-28">---</span>
<span id="cb6-29"></span>
<span id="cb6-30"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Requirements for docker run mode</span></span>
<span id="cb6-31"></span>
<span id="cb6-32"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Expose port 3000</span>
<span id="cb6-33"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Backend must connect to Ollama via configurable environment variable:</span>
<span id="cb6-34"></span>
<span id="cb6-35">OLLAMA_BASE=http://host.docker.internal:11434</span>
<span id="cb6-36"></span>
<span id="cb6-37"></span>
<span id="cb6-38"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Must include a Dockerfile that builds the full app</span>
<span id="cb6-39"></span>
<span id="cb6-40">---</span>
<span id="cb6-41"></span>
<span id="cb6-42"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🔵 Mode 2: docker compose (Full Stack)</span></span>
<span id="cb6-43"></span>
<span id="cb6-44">Provide a docker-compose.yml that runs:</span>
<span id="cb6-45"></span>
<span id="cb6-46"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">### Services:</span></span>
<span id="cb6-47"></span>
<span id="cb6-48"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>webui (your app)</span>
<span id="cb6-49"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>ollama (official ollama image)</span>
<span id="cb6-50"></span>
<span id="cb6-51">---</span>
<span id="cb6-52"></span>
<span id="cb6-53"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## Requirements for docker compose mode</span></span>
<span id="cb6-54"></span>
<span id="cb6-55"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>WebUI service must connect to Ollama via:</span>
<span id="cb6-56"></span>
<span id="cb6-57"></span>
<span id="cb6-58">http://ollama:11434</span>
<span id="cb6-59"></span>
<span id="cb6-60"></span>
<span id="cb6-61"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Ollama service must persist models using volume</span>
<span id="cb6-62"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Both services must be in same Docker network</span>
<span id="cb6-63"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>WebUI depends on Ollama (depends_on)</span>
<span id="cb6-64"></span>
<span id="cb6-65">---</span>
<span id="cb6-66"></span>
<span id="cb6-67"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📦 Required Files to Generate</span></span>
<span id="cb6-68"></span>
<span id="cb6-69">You MUST output:</span>
<span id="cb6-70"></span>
<span id="cb6-71"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## 1️⃣ Dockerfile</span></span>
<span id="cb6-72"></span>
<span id="cb6-73"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Node.js based</span>
<span id="cb6-74"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>installs dependencies</span>
<span id="cb6-75"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>builds WebUI + backend</span>
<span id="cb6-76"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>exposes port 3001</span>
<span id="cb6-77"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>starts server</span>
<span id="cb6-78"></span>
<span id="cb6-79">---</span>
<span id="cb6-80"></span>
<span id="cb6-81"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## 2️⃣ docker-compose.yml</span></span>
<span id="cb6-82"></span>
<span id="cb6-83">Must include:</span>
<span id="cb6-84"></span>
<span id="cb6-85"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>webui service</span>
<span id="cb6-86"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>ollama service (official image)</span>
<span id="cb6-87"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>volume for Ollama models</span>
<span id="cb6-88"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>correct networking</span>
<span id="cb6-89"></span>
<span id="cb6-90">---</span>
<span id="cb6-91"></span>
<span id="cb6-92"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">## 3️⃣ .env.example (optional but preferred)</span></span>
<span id="cb6-93"></span>
<span id="cb6-94">Include:</span>
<span id="cb6-95"></span>
<span id="cb6-96">OLLAMA_BASE=http://localhost:11434</span>
<span id="cb6-97"></span>
<span id="cb6-98"></span>
<span id="cb6-99">for run mode compatibility</span>
<span id="cb6-100"></span>
<span id="cb6-101">---</span>
<span id="cb6-102"></span>
<span id="cb6-103"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># ⚠️ Important Rules</span></span>
<span id="cb6-104"></span>
<span id="cb6-105"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Do NOT include frontend code or backend logic</span>
<span id="cb6-106"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>ONLY Docker-related files</span>
<span id="cb6-107"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Keep configuration minimal and production-friendly</span>
<span id="cb6-108"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>Ensure compatibility between both modes</span>
<span id="cb6-109"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>No unnecessary complexity</span>
<span id="cb6-110"></span>
<span id="cb6-111">---</span>
<span id="cb6-112"></span>
<span id="cb6-113"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 Success Criteria</span></span>
<span id="cb6-114"></span>
<span id="cb6-115">The setup is correct if:</span>
<span id="cb6-116"></span>
<span id="cb6-117">✔ docker run works with external Ollama</span>
<span id="cb6-118">✔ docker compose starts full system</span>
<span id="cb6-119">✔ WebUI can communicate with Ollama in both modes</span>
<span id="cb6-120">✔ No hardcoded localhost inside containers</span>
<span id="cb6-121"></span>
<span id="cb6-122">---</span>
<span id="cb6-123"></span>
<span id="cb6-124"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📘 Output Format</span></span>
<span id="cb6-125"></span>
<span id="cb6-126">Return ONLY:</span>
<span id="cb6-127"></span>
<span id="cb6-128"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>Dockerfile</span>
<span id="cb6-129"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>docker-compose.yml</span>
<span id="cb6-130"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>(optional) .env.example</span>
<span id="cb6-131"></span>
<span id="cb6-132">No explanations.</span></code></pre></div>
<p>順利產出檔案，但測試時突然發現怪怪的。點開 local host 會先觸發 500: Internal Error，雖然重新整理後就沒事了，但一定有地方出問題。</p>
</section>
</section>
<section id="撞牆期" class="level2">
<h2 class="anchored" data-anchor-id="撞牆期">撞牆期</h2>
<p>本來以為只是簡單的小問題，也許多一條 if 判斷就好了，殊不知這是痛苦找問題的開始。</p>
<section id="spec-7" class="level3">
<h3 class="anchored" data-anchor-id="spec-7">spec 7</h3>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb7-1">你是一位資深全端工程師，請幫忙檢查本資料夾下的本地 LLM WebUI。</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 專案目標</span></span>
<span id="cb7-4"></span>
<span id="cb7-5">目前本系統檔案在專案資料夾下，請調查以下問題原因</span>
<span id="cb7-6"></span>
<span id="cb7-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>首次開啟 Local Host 網頁時，不會立刻進入網站，而是先顯示 "500: Internal Error"，網址顯示"http://localhost:3000/error"。</span>
<span id="cb7-8"></span>
<span id="cb7-9">目前初步觀察</span>
<span id="cb7-10"></span>
<span id="cb7-11"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>重新整理後可以進入網站，但網址仍維持"http://localhost:3000/error"</span>
<span id="cb7-12"></span>
<span id="cb7-13"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>Chrome Devtool Console 錯誤顯示</span>
<span id="cb7-14"></span>
<span id="cb7-15">Manifest: Line: 1, column: 1, Syntax error.Understand this error</span>
<span id="cb7-16">loader.js:1 Uncaught SyntaxError: Unexpected token '&lt;'Understand this error</span>
<span id="cb7-17">BlockingPageContent.js:2  action SET_STORAGE_DATA @ 22<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:37:</span>54.574</span>
<span id="cb7-18">BlockingPageContent.js:2  prev state Object</span>
<span id="cb7-19">BlockingPageContent.js:2  action     Object</span>
<span id="cb7-20">BlockingPageContent.js:2  next state Object</span>
<span id="cb7-21">NotificationContent.js:2  action SET_LOCALE @ 22<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:37:</span>54.595</span>
<span id="cb7-22">NotificationContent.js:2  prev state Object</span>
<span id="cb7-23">NotificationContent.js:2  action     Object</span>
<span id="cb7-24">NotificationContent.js:2  next state Object</span>
<span id="cb7-25">NotificationContent.js:2  action SET_STORAGE_DATA @ 22<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:37:</span>54.606</span>
<span id="cb7-26">NotificationContent.js:2  prev state Object</span>
<span id="cb7-27">NotificationContent.js:2  action     Object</span>
<span id="cb7-28">NotificationContent.js:2  next state Object</span>
<span id="cb7-29">manifest.json:1 Manifest: Line: 1, column: 1, Syntax error.Understand this error</span>
<span id="cb7-30">48.BnLRXdVV.js:1 Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.Understand this error</span>
<span id="cb7-31"></span>
<span id="cb7-32"></span>
<span id="cb7-33"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>發生問題的<span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`manifest.json`</span>裡面內容是類似 <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`index.html`</span> 的html 原始碼，但沒有完全一樣。</span>
<span id="cb7-34"></span>
<span id="cb7-35"></span>
<span id="cb7-36"></span>
<span id="cb7-37"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 輸出文件</span></span>
<span id="cb7-38"></span>
<span id="cb7-39">請將上述問題原因調查清楚，寫入 "reason.md" 在本資料夾下。</span>
<span id="cb7-40"></span>
<span id="cb7-41"></span>
<span id="cb7-42"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧩 包含段落（務必遵守）</span></span>
<span id="cb7-43"></span>
<span id="cb7-44"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>問題原因</span>
<span id="cb7-45"></span>
<span id="cb7-46"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">- </span>如何排解</span>
<span id="cb7-47"></span>
<span id="cb7-48"></span>
<span id="cb7-49"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🚀 Optional</span></span>
<span id="cb7-50"></span>
<span id="cb7-51"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>嘗試直接解決此問題</span>
<span id="cb7-52"></span>
<span id="cb7-53"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>將解決方法與步驟寫入"reason.md" 裡的"執行步驟"</span></code></pre></div>
<p>為了讓 Claude 可以順利找到問題，我努力的把能從 google chrome dev tool 中得到的錯誤訊息餵給它。結果它沒有真正解決問題，最後寫進 <code>reason.md</code>的錯誤實際上不是真正的原因，解決方式也只有在 app 端多寫了一段 if 連接不到 ollama 模型的判斷，居然在選單仿造一個假的模型名單，虫合==</p>
<p>因為實在是查不出原因，反覆在修改 spece 跟回到上一個狀態輪迴的我做出最壞的選擇：逼它修復，然後觀察它用甚麼方法。結果發現它一下 killer local host 的其他服務，一下在改 port ，然後測試後跟我說 OK 了。感覺不太對勁的我打開 docker desktop 才發現，我的容器有之前裝的 Open WebUI 服務，每次開啟電腦它都會自動跑在 local host 3000，跟我現在 llm 服務設定的 port 位置一樣，所以才需要改 port！至於其他之前有 build 過但是剛好當時沒開的容器全沒了，喔不==，本來我想說這個選擇再怎麼壞僅限在專案資料夾下，大錯特錯==。</p>
<p>這個慘劇告訴我以後最好還是仔細檢查每個生成 shell 指令的效果，大概看過去會出事 == 。還有我發現貼錯誤訊息不見得可以幫助 claude 找出問題，不管有沒有貼它的解法都一樣，這點感覺跟真人工程師不同，不過目前想不出更好的做法協助它 debug ，也只能多多嘗試了QQ</p>
</section>
<section id="spec-8" class="level3">
<h3 class="anchored" data-anchor-id="spec-8">spec 8</h3>
<p>付出極大的代價學了一課的我這次決定改 port</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb8-1">你是一位資深全端工程師，請幫忙調整本資料夾下的本地 LLM WebUI。</span>
<span id="cb8-2"></span>
<span id="cb8-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 專案目標</span></span>
<span id="cb8-4"></span>
<span id="cb8-5"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">1. </span>將 local host 的 port 從 3000 改成 3001</span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">2. </span>將 ollama model 與 web 的連接跟著 port 的改變而調整，使它可以順利連接新的 local host 仍可以連接</span>
<span id="cb8-8"></span>
<span id="cb8-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3. </span>docker 設定也要將 port 設定從 3000 改成 3001</span>
<span id="cb8-10"></span>
<span id="cb8-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🧩 技術限制（務必遵守）</span></span>
<span id="cb8-12"></span>
<span id="cb8-13"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>前端只能使用：HTML + Vanilla JavaScript（不可用框架）</span>
<span id="cb8-14"></span>
<span id="cb8-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 📦 最終輸出要求</span></span>
<span id="cb8-16"></span>
<span id="cb8-17">請修改本資料夾下，可能有 port 設定與 ollama model 連接相關的檔案</span>
<span id="cb8-18"></span>
<span id="cb8-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;"># 🎯 成功標準</span></span>
<span id="cb8-20"></span>
<span id="cb8-21">完成後應可：</span>
<span id="cb8-22"></span>
<span id="cb8-23"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>正確顯示 Ollama 模型列表</span>
<span id="cb8-24"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>可選擇模型</span>
<span id="cb8-25"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>可輸入文字並取得回應</span>
<span id="cb8-26"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">* </span>不出現 CORS 錯誤</span>
<span id="cb8-27"></span>
<span id="cb8-28">---</span>
<span id="cb8-29"></span>
<span id="cb8-30">請直接輸出完整程式碼，不需要解釋。</span></code></pre></div>
<p>結果大部分是好的，用 <code>npm start</code> 可以正常載入，但 docker 那邊沒有改好，最後我乾脆刪除舊的檔案，然後修改 spec 6 上的 port 編號，解決。</p>
</section>
</section>
</section>
<section id="心得" class="level1">
<h1>心得</h1>
<p>雖然用 claude 生專案速度比起我之前邊做邊學的方式要快很多，不過我覺得它需要更高的資工知識，才能更好的排解問題。此外，因為我不懂 Node.js 跟 Express 的相關知識，這個專案要是將來繼續加功能我可能無法 handle，算是另一個可惜的地方。</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/llm-course-note3/finish.png" class="img-fluid"></p>
<p><em>最後的成品，目前滿好用的，生成的內容簡單。如果之後不好用的話再改一下 prompt 就好~</em></p>



</section>

<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div> ]]></description>
  <category>toys</category>
  <category>LLM</category>
  <category>RAG</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/llm-course-note3/</guid>
  <pubDate>Sat, 09 May 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/llm-course-note3/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>懶人包－Open WebUI + Ollama 的本機安裝流程( Windows )</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/note-install-webui-ollama/</link>
  <description><![CDATA[ 






<section id="webui" class="level1">
<h1>WebUI</h1>
<blockquote class="blockquote">
<p>前置要求：安裝 python 3.11</p>
</blockquote>
<ol type="1">
<li>要確認 pip install 可以在 Powershell 顯示</li>
</ol>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install open-webui</span></code></pre></div>
<ol start="2" type="1">
<li>如果不行但是 python 指令可，可以換</li>
</ol>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> pip install open-webui</span></code></pre></div>
<ol start="3" type="1">
<li>試試在一個不需要系統管理員權限的地方建資料夾</li>
</ol>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> %USERPROFILE%</span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> openwebui</span>
<span id="cb3-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> openwebui</span>
<span id="cb3-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">open-webui</span> serve</span></code></pre></div>
<p>其中</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">open-webui</span> serve</span></code></pre></div>
<p>執行成功的話開啟 http://localhost:8080 可以進入首頁</p>
</section>
<section id="ollama" class="level1">
<h1>Ollama</h1>
<ol type="1">
<li>開 Powershell</li>
</ol>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">irm</span> https://ollama.com/install.ps1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">iex</span></span></code></pre></div>
<ol start="2" type="1">
<li>下載一個簡單的模型</li>
</ol>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ollama</span> pull tinyllama</span></code></pre></div>
</section>
<section id="開啟測試" class="level1">
<h1>開啟測試</h1>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">open-webui</span> serve</span></code></pre></div>
<p>成功的話開啟 http://localhost:8080 可以進入首頁</p>
<ol start="2" type="1">
<li><p>建立管理員帳密(可以亂設)</p></li>
<li><p>找到左上角，找到剛 pull 的模型</p></li>
<li><p>對話測試</p></li>
</ol>
<p><img src="https://paperfishblog.netlify.app/posts/llm/note-install-webui-ollama/scuess.png" class="img-fluid"></p>
</section>
<section id="官方連結" class="level1">
<h1>官方連結</h1>
<ol type="1">
<li><p><a href="https://github.com/open-webui/open-webui">Open WebUI 的 Github</a></p></li>
<li><p><a href="https://ollama.com/">Ollama 官方網站</a></p></li>
</ol>



</section>

<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div> ]]></description>
  <category>LLM</category>
  <category>Ollama</category>
  <category>Open WebUI</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/note-install-webui-ollama/</guid>
  <pubDate>Sun, 22 Mar 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/note-install-webui-ollama/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>讓 AI 教我製作簡易 RAG 網頁服務(下)</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/llm-course-note2/</link>
  <description><![CDATA[ 






<!-- 使用 AI -->
<div data-ai-tag="2">

</div>
<script src="../../../../asserts/box.js" defer=""></script>
<p>拖到開學才寫完：P。</p>
<hr>
<section id="讓使用者上傳-pdf-並即時學習" class="level1">
<h1>讓使用者上傳 PDF 並即時學習</h1>
<p>要實作的功能是：使用者在網頁上丟一個 PDF 檔案，系統自動讀取、切割文字、轉成向量，然後 AI 下一秒就能回答裡面的內容。</p>
<section id="裝套件" class="level2">
<h2 class="anchored" data-anchor-id="裝套件">裝套件</h2>
<p>要裝兩個東西</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install pypdf python-multipart</span></code></pre></div>
<ul>
<li><p><code>pypdf</code>: 用來讀取 PDF 檔案的文字。</p></li>
<li><p><code>python-multipart</code>: 讓 FastAPI 可以接收檔案上傳。</p></li>
</ul>
</section>
<section id="改-rag-服務的函數" class="level2">
<h2 class="anchored" data-anchor-id="改-rag-服務的函數">改 RAG 服務的函數</h2>
<p>再來要調整 <code>rag_service.py</code>，讓它可以吃 PDF。</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sentence_transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> SentenceTransformer</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.metrics.pairwise <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cosine_similarity</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pypdf <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> PdfReader <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW]</span></span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb2-5"></span>
<span id="cb2-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"正在載入 Embedding 模型..."</span>)</span>
<span id="cb2-7">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SentenceTransformer(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'paraphrase-multilingual-MiniLM-L12-v2'</span>)</span>
<span id="cb2-8"></span>
<span id="cb2-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這次我們不預設資料，改用一個空的列表，等使用者上傳</span></span>
<span id="cb2-10">knowledge_base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-11">knowledge_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb2-12"></span>
<span id="cb2-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> add_pdf_to_knowledge_base(file_stream):</span>
<span id="cb2-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    讀取 PDF -&gt; 抓文字 -&gt; 切割 -&gt; 轉向量 -&gt; 存入知識庫</span></span>
<span id="cb2-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">global</span> knowledge_base, knowledge_embeddings</span>
<span id="cb2-18">    </span>
<span id="cb2-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 讀取 PDF 文字</span></span>
<span id="cb2-20">    reader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> PdfReader(file_stream)</span>
<span id="cb2-21">    text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span></span>
<span id="cb2-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> page <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> reader.pages:</span>
<span id="cb2-23">        text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> page.extract_text() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb2-24">    </span>
<span id="cb2-25">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 文字切割 (Chunking)</span></span>
<span id="cb2-26">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># LLM 一次讀不完這麽多字，我們要切成小塊 (例如每 200 字一塊)</span></span>
<span id="cb2-27">    chunk_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span></span>
<span id="cb2-28">    chunks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [text[i:i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span>chunk_size] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(text), chunk_size)]</span>
<span id="cb2-29">    </span>
<span id="cb2-30">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"PDF 處理完畢，切成 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(chunks)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 個片段"</span>)</span>
<span id="cb2-31"></span>
<span id="cb2-32">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 更新知識庫</span></span>
<span id="cb2-33">    new_items <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(knowledge_base) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: chunk} <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(chunks)]</span>
<span id="cb2-34">    knowledge_base.extend(new_items)</span>
<span id="cb2-35">    </span>
<span id="cb2-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 重新計算向量索引 (簡單暴力法：全部重算)</span></span>
<span id="cb2-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 在真實系統中，我們會用 Vector DB (如 ChromaDB) 來處理，不用每次重算</span></span>
<span id="cb2-38">    texts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [item[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> knowledge_base]</span>
<span id="cb2-39">    knowledge_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.encode(texts)</span>
<span id="cb2-40">    </span>
<span id="cb2-41">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(chunks)</span>
<span id="cb2-42"></span>
<span id="cb2-43"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> search_knowledge_base(query, top_k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb2-44">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-45"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    搜尋功能 (跟之前一樣，但加了防呆機制)</span></span>
<span id="cb2-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb2-47">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">global</span> knowledge_embeddings</span>
<span id="cb2-48">    </span>
<span id="cb2-49">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 如果知識庫是空的，直接回傳空</span></span>
<span id="cb2-50">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> knowledge_base <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> knowledge_embeddings <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb2-51">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> []</span>
<span id="cb2-52"></span>
<span id="cb2-53">    query_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.encode([query])</span>
<span id="cb2-54">    similarities <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cosine_similarity(query_embedding, knowledge_embeddings)</span>
<span id="cb2-55">    top_indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> similarities[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].argsort()[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>top_k:][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-56">    </span>
<span id="cb2-57">    results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-58">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> top_indices:</span>
<span id="cb2-59">        score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> similarities[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][idx]</span>
<span id="cb2-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>: </span>
<span id="cb2-61">            results.append(knowledge_base[idx][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>])</span>
<span id="cb2-62">            </span>
<span id="cb2-63">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> results</span></code></pre></div>
</section>
<section id="改後端" class="level2">
<h2 class="anchored" data-anchor-id="改後端">改後端</h2>
<p>使用者上傳的檔案要用 API 才能傳到後端，因此要修改 <code>server.py</code></p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (前面的 import 不變)</span></span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> UploadFile, File <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入檔案處理模組</span></span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> rag_service <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> search_knowledge_base, add_pdf_to_knowledge_base <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入新函式</span></span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (app = FastAPI... 中間設定不變) ...</span></span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 上傳檔案的 API</span></span>
<span id="cb3-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/upload"</span>)</span>
<span id="cb3-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> upload_file(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>: UploadFile <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> File(...)):</span>
<span id="cb3-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb3-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 把檔案內容傳給 rag_service 處理</span></span>
<span id="cb3-12">        chunk_count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> add_pdf_to_knowledge_base(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span>)</span>
<span id="cb3-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span>: <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"成功讀取 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>filename<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">，新增了 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>chunk_count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> 個知識片段！"</span>}</span>
<span id="cb3-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb3-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)}</span>
<span id="cb3-16"></span>
<span id="cb3-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (chat_with_ai 函式保持不變) ...</span></span>
<span id="cb3-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 注意：記得確認 chat_with_ai 裡面有呼叫 search_knowledge_base</span></span></code></pre></div>
</section>
<section id="改前端" class="level2">
<h2 class="anchored" data-anchor-id="改前端">改前端</h2>
<p>最後修改前端，先在 HTML 區建一個讓使用者可以按下「📚 知識庫上傳」的按鈕，上傳 PDF</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode html code-with-copy"><code class="sourceCode html"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;!-- 在標題下方 --&gt;</span></span>
<span id="cb4-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"margin-bottom: 10px; padding: 10px; background: #fff3cd; border: 1px solid #ffeeba; border-radius: 5px;"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">h3</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>📚 知識庫上傳<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">h3</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">input</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"file"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pdf-upload"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> accept</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".pdf"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">button</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> onclick</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uploadPDF()"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"background-color: #ffc107; color: black;"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>上傳 PDF<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">button</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">span</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upload-status"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"margin-left: 10px; font-size: 0.9em; color: #666;"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">span</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-7"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-8"></span>
<span id="cb4-9"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-10">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div>
<p>再到 js 區新增接收檔案傳進後端的函數</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uploadPDF</span>() {</span>
<span id="cb5-2">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> fileInput <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pdf-upload"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-3">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> statusText <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"upload-status"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-4">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> file <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fileInput<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">files</span>[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-5"></span>
<span id="cb5-6">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>file) {</span>
<span id="cb5-7">                <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">alert</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"請先選擇 PDF 檔案！"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-8">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-9">            }</span>
<span id="cb5-10"></span>
<span id="cb5-11">            statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"正在處理中... (請稍候)"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-12"></span>
<span id="cb5-13">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> formData <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">FormData</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-14">            formData<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">append</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"file"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> file)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-15"></span>
<span id="cb5-16">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span> {</span>
<span id="cb5-17">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/upload"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {</span>
<span id="cb5-18">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POST"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb5-19">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> formData</span>
<span id="cb5-20">                })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-21">                </span>
<span id="cb5-22">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> response<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">json</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-23">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">message</span>) {</span>
<span id="cb5-24">                    statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"✅ "</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">message</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-25">                    statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"green"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-26">                } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<span id="cb5-27">                    statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"❌ 上傳失敗："</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">error</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-28">                    statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">style</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"red"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-29">                }</span>
<span id="cb5-30">            } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">catch</span> (error) {</span>
<span id="cb5-31">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">console</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">error</span>(error)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-32">                statusText<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"❌ 連線錯誤"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb5-33">            }</span>
<span id="cb5-34">        }</span></code></pre></div>
</section>
<section id="測試" class="level2">
<h2 class="anchored" data-anchor-id="測試">測試</h2>
<ol type="1">
<li><p>準備好一個有專屬資訊的PDF (例如課綱)</p></li>
<li><p>啟動後端 <code>uvicorn server:app --reload</code>，確認正常運作</p></li>
<li><p>開啟網站</p></li>
<li><p>先輸入一個只有 PDF 有寫的問題，預期輸出為不知道</p></li>
<li><p>上傳檔案，再問一次，預期輸出要能回答相關問題</p></li>
</ol>
</section>
</section>
<section id="將系統打包成-docker-容器" class="level1">
<h1>將系統打包成 Docker 容器</h1>
<p>讓系統無論在哪都可以跑。</p>
<section id="安裝-docker" class="level2">
<h2 class="anchored" data-anchor-id="安裝-docker">安裝 Docker</h2>
<p>首次使用需要此步，Windows 安裝前提條件：</p>
<ul>
<li>Windows 10 64-bit：版本 1903 或更新版</li>
<li>啟用 <strong>WSL 2（Windows Subsystem for Linux）</strong></li>
</ul>
<section id="安裝步驟" class="level3">
<h3 class="anchored" data-anchor-id="安裝步驟">安裝步驟：</h3>
<ol type="1">
<li><p><strong>安裝 WSL 2（如尚未安裝）</strong></p>
<ul>
<li>開啟 PowerShell（以系統管理員身份）</li>
</ul>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode powershell code-with-copy"><code class="sourceCode powershell"><span id="cb6-1">wsl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">--</span>install</span></code></pre></div>
<ul>
<li>安裝完成後重新啟動電腦。</li>
</ul></li>
<li><p><strong>下載 Docker Desktop for Windows</strong></p>
<ul>
<li>官方下載連結：<a href="https://www.docker.com/products/docker-desktop">https://www.docker.com/products/docker-desktop</a></li>
</ul></li>
<li><p><strong>安裝並啟動 Docker Desktop</strong></p>
<ul>
<li>執行安裝檔</li>
<li>勾選「使用 WSL 2」作為後端</li>
<li>安裝完成後啟動 Docker Desktop 並登入 Docker Hub（可註冊免費帳號）</li>
</ul></li>
<li><p><strong>確認 Docker 安裝是否成功</strong></p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb7-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--version</span></span>
<span id="cb7-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run hello-world</span></code></pre></div></li>
</ol>
</section>
</section>
<section id="產出-requirements.txt" class="level2">
<h2 class="anchored" data-anchor-id="產出-requirements.txt">產出 <code>requirements.txt</code></h2>
<p>用來告訴 Docker 內的乾淨環境：需要安裝哪些東西。</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb8-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> freeze <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> requirements.txt</span></code></pre></div>
<p>會看到資料夾多了一個 <code>requirements.txt</code> 檔案，打開來檢查，裡面應該有 fastapi, uvicorn, openai, sentence-transformers 等等。</p>
</section>
<section id="寫-dockerfile" class="level2">
<h2 class="anchored" data-anchor-id="寫-dockerfile">寫 Dockerfile</h2>
<p>寫一個腳本，告訴 Docker 怎麼打包。</p>
<ol type="1">
<li><p>在專案根目錄建立一個新檔案，檔名就叫 <code>Dockerfile</code> (沒有副檔名)。</p></li>
<li><p>貼上以下內容：</p></li>
</ol>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode dockerfile code-with-copy"><code class="sourceCode dockerfile"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 選擇基底映像檔 (就像選擇要灌什麼作業系統)</span></span>
<span id="cb9-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 我們選一個輕量級的 Python 3.12 環境</span></span>
<span id="cb9-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> python:3.12-slim</span>
<span id="cb9-4"></span>
<span id="cb9-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 設定工作目錄 (在箱子裡建立一個 /app 資料夾)</span></span>
<span id="cb9-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">WORKDIR</span> /app</span>
<span id="cb9-7"></span>
<span id="cb9-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 把你電腦裡的 requirements.txt 複製進箱子</span></span>
<span id="cb9-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">COPY</span> requirements.txt .</span>
<span id="cb9-10"></span>
<span id="cb9-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 在箱子裡安裝套件</span></span>
<span id="cb9-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># --no-cache-dir 可以讓箱子小一點</span></span>
<span id="cb9-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">RUN</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-cache-dir</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-r</span> requirements.txt</span>
<span id="cb9-14"></span>
<span id="cb9-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. 把你電腦裡的所有程式碼 (main.py, server.py...) 複製進箱子</span></span>
<span id="cb9-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">COPY</span> . .</span>
<span id="cb9-17"></span>
<span id="cb9-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 6. 告訴 Docker，這個程式會開 8000 port</span></span>
<span id="cb9-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">EXPOSE</span> 8000</span>
<span id="cb9-20"></span>
<span id="cb9-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 7. 當箱子啟動時，要執行的指令</span></span>
<span id="cb9-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 注意：這裡的 --host 0.0.0.0 是關鍵，一定要設成這樣外部才連得進去</span></span>
<span id="cb9-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">CMD</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uvicorn"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"server:app"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--host"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"0.0.0.0"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--port"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"8000"</span>]</span></code></pre></div>
<p>用指令開始 build</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> build <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t</span> my-ai-system .</span></code></pre></div>
<p><code>my-ai-system</code> 就是會在 Docker 桌面版的 images 頁面裡看到的名稱。</p>
<section id="build-常見問題" class="level3 call-out.tips">
<h3 class="anchored" data-anchor-id="build-常見問題">Build 常見問題</h3>
<section id="安裝不了套件" class="level4">
<h4 class="anchored" data-anchor-id="安裝不了套件">安裝不了套件</h4>
<p>有時候前面都很正常但裝到某些套件時突然說裝不了，因為套件的版本沒有這麼新的。這時只要先確認目前使用的虛擬環境的 python 版本，將對應 version 的內容修改成與虛擬環境一致，通常就沒問題了。</p>
<p>如果還有問題可以上<a href="https://hub.docker.com/_/python">DockerHub 上 Ptyhon 官方 image</a>找出對應的版本，除非安裝的版本太新，不然應該都會有。</p>
</section>
</section>
</section>
</section>
<section id="目前的問題" class="level1">
<h1>目前的問題</h1>
<section id="docker-包起來的容量很大" class="level2">
<h2 class="anchored" data-anchor-id="docker-包起來的容量很大">Docker 包起來的容量很大</h2>
<p>最後一次包起來在 Docker images page 上居然顯示 13 GB ，好扯==。因為沒有任何向量資料庫，大成這樣其實很不尋常。</p>
</section>
<section id="要如何測試-rag-效能" class="level2">
<h2 class="anchored" data-anchor-id="要如何測試-rag-效能">要如何測試 RAG 效能</h2>
<p>目前這個的玩具的設定對稍微長一點的文本檢索機制不佳，回應也有幻覺問題。本來想說既然 AI 幫我生出這個小玩具，那我再加入 LangChain 或 Huggingface 的生態系、改一改模型、加入不同的檢索策略跟 Reranker 機制，以及針對特定需求作客製化後，就會是個實用小玩具了，可是要怎麼好好評估這些機制實在很困擾我。雖然之前有寫過製作 RAG 的作業，也跑過 kaggle 上的競賽，但這些都有提供一定數量的測試集，裡面都會附上問題與解答，但現在這個小玩具的情境裡：</p>
<ol type="1">
<li><p>我需要針對我落地的情境設計問題與確定解答，但我沒有很多時間製作測試集，可能只能針對提問的種類設計幾個問題與解答，再就這些小樣本推論回應品質。</p></li>
<li><p>文本要自行上傳，且資料格式是沒有標準儲存格式的 PDF，<sup>1</sup>如果讓給其他使用者使用、上傳其他來源的資料，回應的品質會變得難以掌握。除非我能列出所有可能面臨的使用情境，並且設計出好的解決策略，讓使用體驗良好，但這對初次設計的我來說太不切實際了。</p></li>
</ol>
<p>我覺得第二個問題是最棘手的，所以我後來決定重新做一個不能上傳資料的封閉式 RAG 系統，先解決資料格式與來源不一的問題。這個小玩具就先放著，以後再想想看要怎麼調整。</p>
</section>
</section>
<section id="後續" class="level1">
<h1>後續</h1>
<p>後來我就爬了一些舊聞，做了網路考古學家的小玩具，<del>目標是滿足當歷史學家的慾望</del>。不過還是有遇到一些問題，<del>加上後來過年了就偷懶</del>，但還是希望能趕快解決，順利上線後再分享。</p>
<p>然後雖然最後沒辦法上生成式AI應用系統與工程，不過昨天透過學校的 Email 提供的連結旁聽了一下，想不到居然是著重跟 AI Agent 協作的課，跟我(還有 Geimei)之前想的完全不一樣 XD，滿有趣的。雖然我猜這要應用在職場上還需要一段時間(取決於公司)，不過有機會還是看看有沒有甚麼小撇步可以偷學XD</p>



</section>


<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">腳註</h2>

<ol>
<li id="fn1"><p>舉例來說，有的 PDF 是圖片轉成的，沒有辦法擷取出文字；也有的 PDF 會有反爬蟲機制，試圖用程式開啟並擷取PDF會爬到一堆亂碼。這些都會影響模型生成回答的正確性。↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>toys</category>
  <category>LLM</category>
  <category>RAG</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/llm-course-note2/</guid>
  <pubDate>Wed, 25 Feb 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/llm-course-note2/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>讓 AI 教我製作簡易 RAG 網頁服務(上)</title>
  <dc:creator>紙魚 </dc:creator>
  <link>https://paperfishblog.netlify.app/posts/llm/llm-course-note1/</link>
  <description><![CDATA[ 






<!-- 使用 AI -->
<div data-ai-tag="2">

</div>
<script src="../../../../asserts/box.js" defer=""></script>
<section id="緣起" class="level1">
<h1>緣起</h1>
<p>最近發現 AI 聯盟開了「生成式AI應用系統與工程」，課綱很吸引我，但跟統研的必修衝堂了 QQ ，所以叫 Gemini 根據課綱教我，看看它能教到甚麼程度。</p>
<p>目前發現它教的都是最簡單的部分XD，比較複雜的東西以前端為例，課綱裡列的 Next.js / React / Tailwind 都被自刻 Html/CSS/JavaScript 取代，然後還吹得我好像會了甚麼很厲害的東西一樣 == 。不過它也有教我沒接觸過的 FastAPI，雖然講得超級淺，但能 Build 出自己的 API 服務滿有成就感的，除此之外還可以觀察有使用 LLM 的系統服務是怎麼串的，所以有了這篇文章。</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
建議前置步驟
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<ol type="1">
<li>建立新的專案資料夾</li>
<li>建立新的乾淨虛擬環境</li>
</ol>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
環境與套件版本
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<ul>
<li><p>IDE: Vscode</p></li>
<li><p>Python version: 3.12.5 (venv 虛擬環境)</p></li>
</ul>
<p>另外，也記錄本次 <code>requirement.txt</code>，實際上我是跟著 Gemini 的指示做 pip install，所以我覺得沒必要先安裝好這些，但可以參考。</p>
<pre><code>annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
certifi==2026.1.4
charset-normalizer==3.4.4
click==8.3.1
colorama==0.4.6
distro==1.9.0
fastapi==0.128.0
filelock==3.20.3
fsspec==2026.1.0
groq==1.0.0
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
huggingface-hub==0.36.0
idna==3.11
Jinja2==3.1.6
jiter==0.12.0
joblib==1.5.3
MarkupSafe==3.0.3
mpmath==1.3.0
networkx==3.6.1
numpy==2.4.1
openai==2.15.0
packaging==25.0
pydantic==2.12.5
pydantic_core==2.41.5
PyYAML==6.0.3
regex==2026.1.15
requests==2.32.5
safetensors==0.7.0
scikit-learn==1.8.0
scipy==1.17.0
sentence-transformers==5.2.0
setuptools==80.9.0
sniffio==1.3.1
starlette==0.50.0
sympy==1.14.0
threadpoolctl==3.6.0
tokenizers==0.22.2
torch==2.9.1
tqdm==4.67.1
transformers==4.57.6
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.6.3
uvicorn==0.40.0</code></pre>
</div>
</div>
</div>
</section>
<section id="申請-groq-免費-api-key-並測試" class="level1">
<h1>申請 Groq 免費 API Key 並測試</h1>
<p>因為 OpenAI API 要 $$，所以我叫它改用免費的 Groq :P。</p>
<section id="申請步驟" class="level2">
<h2 class="anchored" data-anchor-id="申請步驟">申請步驟</h2>
<p>完整申請步驟如下：<sup>1</sup></p>
<ol type="1">
<li><p>上去<a href="https://groq.com/">官網</a>，點選右上角的 Start Building</p></li>
<li><p>建立帳號登入</p></li>
<li><p>點選右上方的 API Keys，進入後選擇大大的「+ Create API Key」</p></li>
<li><p>給 API 命名後 Submit，copy API。</p></li>
</ol>
</section>
<section id="測試" class="level2">
<h2 class="anchored" data-anchor-id="測試">測試</h2>
<p>接著在乾淨的 venv 環境下安裝套件</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install openai</span></code></pre></div>
<p>然後在專案資料夾下建立 <code>main.py</code> 或其他 py 檔，輸入以下程式（記得修改 API KEY）：</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"></span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAI</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 設定連線資訊</span></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># base_url 是關鍵，這行告訴程式：「不要連去 OpenAI，改連去 Groq」</span></span>
<span id="cb3-6">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI(</span>
<span id="cb3-7">    api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"剛剛申請的API KEY"</span>, </span>
<span id="cb3-8">    base_url<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://api.groq.com/openai/v1"</span></span>
<span id="cb3-9">)</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--- 免費 AI 聊天機器人 (Groq Llama3) 啟動 ---"</span>)</span>
<span id="cb3-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--- 輸入 'quit' 可以離開程式 ---"</span>)</span>
<span id="cb3-13"></span>
<span id="cb3-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>:</span>
<span id="cb3-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 讓使用者輸入</span></span>
<span id="cb3-16">    user_input <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">你："</span>)</span>
<span id="cb3-17">    </span>
<span id="cb3-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user_input.lower() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"quit"</span>:</span>
<span id="cb3-19">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"再見！"</span>)</span>
<span id="cb3-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb3-21"></span>
<span id="cb3-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 呼叫 AI (這裡使用開源模型 Llama3-8b)</span></span>
<span id="cb3-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb3-24">        completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(</span>
<span id="cb3-25">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 指定模型名稱</span></span>
<span id="cb3-26">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb3-27">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你是一個繁體中文的 AI 助教，回答簡潔有力。"</span>},</span>
<span id="cb3-28">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: user_input}</span>
<span id="cb3-29">            ]</span>
<span id="cb3-30">        )</span>
<span id="cb3-31"></span>
<span id="cb3-32">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 取得回答</span></span>
<span id="cb3-33">        ai_response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> completion.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content</span>
<span id="cb3-34">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"AI：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ai_response<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb3-35">        </span>
<span id="cb3-36">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb3-37">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"發生錯誤：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div>
<p>然後跑跑看腳本，如果能在 Terminal 跟模型對話就成功了，如果失敗，則有可能是 API Key 錯誤或是模型不支援要更換。這裡我們用的模型是<code>llama-3.3-70b-versatile</code>。</p>
<p>如果點進去 Groq 官網，會發現它們用的是 <code>Groq()</code>而非 <code>OpenAI()</code>建立 Client，這主要是因為：</p>
<ol type="1">
<li><p>時序上 OpenAI 較早提供 API 服務，在使用者增加後其他公司才後續跟進</p></li>
<li><p><code>OpenAI()</code>也支援連接其他 LLM 的 API 服務，泛用性較高</p></li>
<li><p>觀察網路上的常見的 LLM 服務課程，開發程式碼也以<code>OpenAI()</code>為主</p></li>
</ol>
<p>所以就用<code>OpenAI()</code>囉！</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-3-contents" aria-controls="callout-3" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
其他問題：<code>base_url</code> 是啥
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-3" class="callout-3-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p><code>base_url</code> 就是伺服器的地址。想像你在寄信（發送請求）：</p>
<ul>
<li><p>信的內容 (Payload)： 你的問題（例如：“你好”）。</p></li>
<li><p>信的格式 (Schema)： OpenAI 規定的格式（JSON 格式）。</p></li>
<li><p>收件地址 (base_url)： 這就是關鍵所在。</p></li>
</ul>
<p>預設情況 (不寫 base_url)：當你只寫 client = OpenAI(api_key=“…”) 時，程式預設的 base_url 是　https://api.openai.com/v1 (直接寄去 OpenAI 總部)。</p>
<p>我們的情況 (修改 base_url)：當你加上 base_url=“https://api.groq.com/openai/v1” 時，發生了這件事：你的程式依然使用 OpenAI 的格式打包信件，但是信件被攔截，轉送到了 Groq 的伺服器。Groq 的伺服器看懂了這個格式 (因為它相容)，於是處理後回傳結果。</p>
<p>同理，假設我們今天要改成在本地跑 Ollama 模型，可以把 <code>base_url</code>改成自己的電腦地址，ex <code>base_url="http://localhost:11111/v1"</code>。</p>
</div>
</div>
</div>
</section>
</section>
<section id="建立後端-api-服務" class="level1">
<h1>建立後端 API 服務</h1>
<p>首先，在乾淨的 venv 環境下安裝套件</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install fastapi uvicorn</span></code></pre></div>
<p>說明：</p>
<ol type="1">
<li><p>FastAPI: 用來快速建立網頁伺服器的框架。</p></li>
<li><p>Uvicorn: 用來啟動這個伺服器的工具。</p></li>
</ol>
<p>在專案資料夾下建立 <code>server.py</code>，內容打一下（一樣要修改API KEY）：</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FastAPI</span>
<span id="cb5-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb5-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAI</span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 初始化 FastAPI APP</span></span>
<span id="cb5-6">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FastAPI()</span>
<span id="cb5-7"></span>
<span id="cb5-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 設定 Groq 連線 (跟上一步一樣)</span></span>
<span id="cb5-9">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI(</span>
<span id="cb5-10">    api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你申請的API Key"</span>, </span>
<span id="cb5-11">    base_url<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://api.groq.com/openai/v1"</span></span>
<span id="cb5-12">)</span>
<span id="cb5-13"></span>
<span id="cb5-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 定義資料格式：告訴程式，別人傳進來的資料應該長什麼樣子</span></span>
<span id="cb5-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這裡規定：一定要有一個欄位叫做 "message" 且是文字 (str)</span></span>
<span id="cb5-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> UserInput(BaseModel):</span>
<span id="cb5-17">    message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb5-18"></span>
<span id="cb5-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 建立一個 "路徑" (API Endpoint)</span></span>
<span id="cb5-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 當有人對網址 /chat 發送 POST 請求時，會執行這個函式</span></span>
<span id="cb5-21"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/chat"</span>)</span>
<span id="cb5-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> chat_with_ai(data: UserInput):</span>
<span id="cb5-23">    user_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data.message</span>
<span id="cb5-24">    </span>
<span id="cb5-25">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"收到訊息：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>user_message<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 讓你在後台看得到</span></span>
<span id="cb5-26"></span>
<span id="cb5-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb5-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 呼叫 Groq</span></span>
<span id="cb5-29">        completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(</span>
<span id="cb5-30">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,</span>
<span id="cb5-31">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb5-32">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你是一個繁體中文 AI 助教。"</span>},</span>
<span id="cb5-33">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: user_message}</span>
<span id="cb5-34">            ]</span>
<span id="cb5-35">        )</span>
<span id="cb5-36">        ai_reply <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> completion.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content</span>
<span id="cb5-37">        </span>
<span id="cb5-38">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 回傳結果給前端</span></span>
<span id="cb5-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reply"</span>: ai_reply}</span>
<span id="cb5-40">        </span>
<span id="cb5-41">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb5-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"error"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)}</span>
<span id="cb5-43"></span>
<span id="cb5-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. 測試首頁 (確認伺服器活著)</span></span>
<span id="cb5-45"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.get</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/"</span>)</span>
<span id="cb5-46"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> read_root():</span>
<span id="cb5-47">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"status"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Server is running!"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"course"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Generative AI Engineering"</span>}</span></code></pre></div>
<p>這裡使用的測試模型一樣是<code>llama-3.3-70b-versatile</code>。然後用</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uvicorn</span> server:app <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--reload</span></span></code></pre></div>
<p>啟動伺服器，成功時 Terminal 會顯示的訊息</p>
<pre><code>INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
...
...
...
INFO:     ... - "GET / HTTP/1.1" 200 OK
INFO:     ... - "GET /docs HTTP/1.1" 200 OK
INFO:     ... - "GET /openapi.json HTTP/1.1" 200 OK</code></pre>
<p>且到 <a href="http://127.0.0.1:8000">http://127.0.0.1:8000</a>時，畫面會像這樣</p>
<p><img src="https://paperfishblog.netlify.app/posts/llm/llm-course-note1/scuess.png" class="img-fluid"></p>
<p>這個成功畫面顯示的就是第五步的 code，但其實第五步不是必須，只是可以直接確認伺服器可正常運作，完全不寫的話 INFO 會跑出 404 Not Found 的訊息，打開網站也會跑出 404 的訊息，但我們可以用一個簡單的方式確認：</p>
<ol type="1">
<li><p>首先打開 API 文件 <a href="http://127.0.0.1:8000/docs">http://127.0.0.1:8000/docs</a>。</p></li>
<li><p>依序點擊</p></li>
</ol>
<p><img src="https://paperfishblog.netlify.app/posts/llm/llm-course-note1/post.png" class="img-fluid"> 3. 修改 “string”部分(模仿使用者輸入問題)</p>
<pre><code>{
  "message": "string"
}</code></pre>
<ol start="4" type="1">
<li><p>按下 Execute 按鈕</p></li>
<li><p>查看下方 Response 部分， Code 200 代表正常運作，且可以看到 Response Body 有模型回應跟 Response Header 紀錄回應時間等資訊。</p></li>
</ol>
<p>實務上 API 文件在開發跟檢查問題時都很需要，學起來不是壞事：）</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-4-contents" aria-controls="callout-4" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
AI 沒教的事：隱藏 API KEY
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-4" class="callout-4-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>如果連 API KEY 都不能隱藏的話，要怎麼上線！首先再來安裝一個套件</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install dotenv</span></code></pre></div>
<p>接著在同專案資料夾下建立名為 <code>.env</code> 的檔案，建立變數：</p>
<pre class="env"><code>API_KEY="你申請的API KEY"</code></pre>
<p>可以用以下腳本試跑看看 Terminal 可不可以成功顯示 <code>API_KEY</code>裡的參數。</p>
<div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb11-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb11-3"></span>
<span id="cb11-4">load_dotenv() <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 讀取 .env</span></span>
<span id="cb11-5"></span>
<span id="cb11-6">API_KEY <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"API_KEY"</span>) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 取得 API_KEY 這個參書內容</span></span>
<span id="cb11-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(API_KEY)</span></code></pre></div>
<p>成功的話再把這段程式貼入 <code>server.py</code> 裡。</p>
</div>
</div>
</div>
</section>
<section id="網頁服務的建立" class="level1">
<h1>網頁服務的建立</h1>
<p>上一個 part 的程式只能在後端運作，使用者用不了，所以需要前端讓使用者輸入東西，傳給後端處理。這裡使用比較常見的網頁服務來實作，而且是用最簡單的 HTML 包 CSS 跟 JS 的形式。</p>
<p>不過首先要先讓網頁跟伺服器可以溝通，就要先解決瀏覽器的安全機制 (CORS)。</p>
<p>所謂的 CORS 就是，網頁檔案 (HTML) 和伺服器 (FastAPI) 雖然都在同一台電腦，但在瀏覽器眼中它們是「不同的來源」。如果沒有特別允許，瀏覽器會禁止網頁去讀取伺服器的資料。所以要修改 <code>server.py</code>變成:</p>
<div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 建立 web</span></span>
<span id="cb12-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> FastAPI</span>
<span id="cb12-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.middleware.cors <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> CORSMiddleware  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入 CORS 套件</span></span>
<span id="cb12-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb12-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAI</span>
<span id="cb12-6"></span>
<span id="cb12-7">app <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FastAPI()</span>
<span id="cb12-8"></span>
<span id="cb12-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 設定 CORS，允許所有來源 (為了開發方便，我們先全開)</span></span>
<span id="cb12-10">app.add_middleware(</span>
<span id="cb12-11">    CORSMiddleware,</span>
<span id="cb12-12">    allow_origins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span>],  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 允許任何網址呼叫這個 API</span></span>
<span id="cb12-13">    allow_credentials<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,</span>
<span id="cb12-14">    allow_methods<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span>],  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 允許任何方法 (GET, POST...)</span></span>
<span id="cb12-15">    allow_headers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*"</span>],</span>
<span id="cb12-16">)</span>
<span id="cb12-17"></span>
<span id="cb12-18">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI(</span>
<span id="cb12-19">    api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"APIKEY"</span>, </span>
<span id="cb12-20">    base_url<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://api.groq.com/openai/v1"</span></span>
<span id="cb12-21">)</span>
<span id="cb12-22"></span>
<span id="cb12-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 後面都一樣</span></span></code></pre></div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-5-contents" aria-controls="callout-5" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
CORS 各設定詳解
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-5" class="callout-5-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<table class="caption-top table">
<colgroup>
<col style="width: 44%">
<col style="width: 33%">
<col style="width: 22%">
</colgroup>
<thead>
<tr class="header">
<th>參數名稱</th>
<th>設定值</th>
<th>功能說明</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>allow_origins</td>
<td>[“*”]</td>
<td>允許的來源網域。[“*”] 代表「允許所有網站」存取你的 API。在開發階段很方便，但在正式上線（Production）時，建議指定具體的網址（例如 [“https://www.your-app.com”]）以策安全。</td>
</tr>
<tr class="even">
<td>allow_credentials</td>
<td>True</td>
<td>是否允許攜帶憑證。當設為 True 時，跨來源請求可以包含 Cookies、HTTP 認證（Authentication）或 TLS 用戶端憑證。注意：如果此項為 True，allow_origins 最好不要設為 [“*”]，有些瀏覽器會因為安全限制而報錯。</td>
</tr>
<tr class="odd">
<td>allow_methods</td>
<td>[“*”]</td>
<td>允許的 HTTP 方法。[“*”] 代表允許所有方法，包括 GET、POST、PUT、DELETE 等。你目前的程式碼中使用了 POST 方法來處理 <code>/chat</code> 和 <code>/upload</code>。</td>
</tr>
<tr class="even">
<td>allow_headers</td>
<td>[“*”]</td>
<td>允許的 HTTP 標頭。[“*”] 代表允許請求攜帶任何自定義標頭（Headers），例如 Content-Type、Authorization 等。</td>
</tr>
</tbody>
</table>
<p>AI 目前給的設定都是安全最低的選擇，有部署需要時需要調整(尤其是<code>allow_origins</code>)。</p>
</div>
</div>
</div>
<p>然後建立網頁<code>index.html</code></p>
<div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode html code-with-copy"><code class="sourceCode html"><span id="cb13-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;!DOCTYPE</span> html<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">html</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> lang</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"zh-TW"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-3"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">head</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">meta</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> charset</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UTF-8"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">meta</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> name</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"viewport"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"width=device-width, initial-scale=1.0"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">title</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>我的 AI 聊天室<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">title</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">style</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* 簡單的 CSS 樣式，讓畫面好看一點 */</span></span>
<span id="cb13-9">        body { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">font-family</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">sans-serif</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">max-width</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">600</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">margin</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">padding</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-10">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.chat-box</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">solid</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#ccc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">height</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">overflow-y</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">scroll</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">padding</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">margin-bottom</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border-radius</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#f9f9f9</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-11">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.message</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">margin</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">padding</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border-radius</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">width</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">fit-content</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">max-width</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">%</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-12">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.user</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background-color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#007bff</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">white</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">margin-left</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">text-align</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">right</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> } <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* 使用者訊息靠右 */</span></span>
<span id="cb13-13">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.ai</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background-color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#e9ecef</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">black</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">margin-right</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">auto</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> } <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* AI 訊息靠左 */</span></span>
<span id="cb13-14">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">.input-area</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">display</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">flex</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">gap</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-15">        input { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">flex</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">padding</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border-radius</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">solid</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#ddd</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-16">        button { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">padding</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background-color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#28a745</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">white</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">none</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">border-radius</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">px</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">cursor</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">pointer</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-17">        button<span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:hover</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background-color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#218838</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-18">        button<span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:disabled</span> { <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">background-color</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">#ccc</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> }</span>
<span id="cb13-19">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">style</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-20"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">head</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-21"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">body</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-22"></span>
<span id="cb13-23">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">h2</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>🤖 我的生成式 AI 助手<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">h2</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-24">    </span>
<span id="cb13-25">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-26">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message ai"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>你好！我是你的 AI 助教，有什麼我可以幫你的嗎？<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-27">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-28"></span>
<span id="cb13-29">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> class</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input-area"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-30">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">input</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> type</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user-input"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> placeholder</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"輸入你的問題..."</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> onkeypress</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"handleEnter(event)"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-31">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">button</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> onclick</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sendMessage()"</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;"> id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"send-btn"</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span>發送<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">button</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-32">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">div</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-33"></span>
<span id="cb13-34">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-35">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 3. JavaScript 邏輯區：負責跟後端講話</span></span>
<span id="cb13-36">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>() {</span>
<span id="cb13-37">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> inputField <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user-input"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-38">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> sendBtn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"send-btn"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-39">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-40">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">trim</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-41"></span>
<span id="cb13-42">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>message) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 如果沒輸入字就不理會</span></span>
<span id="cb13-43"></span>
<span id="cb13-44">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 顯示使用者的訊息</span></span>
<span id="cb13-45">            <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-46">            inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 清空輸入框</span></span>
<span id="cb13-47">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 發送中禁止按按鈕</span></span>
<span id="cb13-48">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"思考中..."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-49"></span>
<span id="cb13-50">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span> {</span>
<span id="cb13-51">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// --- 關鍵步驟：呼叫你的 FastAPI 後端 ---</span></span>
<span id="cb13-52">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/chat"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {</span>
<span id="cb13-53">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POST"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-54">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">headers</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> { <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Content-Type"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"application/json"</span> }<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb13-55">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">JSON</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stringify</span>({ <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">message</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> message })</span>
<span id="cb13-56">                })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-57"></span>
<span id="cb13-58">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> response<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">json</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-59">                </span>
<span id="cb13-60">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 顯示 AI 的回答</span></span>
<span id="cb13-61">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">reply</span>) {</span>
<span id="cb13-62">                    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">reply</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ai"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-63">                } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<span id="cb13-64">                    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"錯誤："</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">JSON</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stringify</span>(data)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ai"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-65">                }</span>
<span id="cb13-66"></span>
<span id="cb13-67">            } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">catch</span> (error) {</span>
<span id="cb13-68">                <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"連線失敗，請確認後端伺服器有開啟。"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ai"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-69">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">console</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">error</span>(error)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-70">            }</span>
<span id="cb13-71"></span>
<span id="cb13-72">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 恢復按鈕狀態</span></span>
<span id="cb13-73">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-74">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"發送"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-75">        }</span>
<span id="cb13-76"></span>
<span id="cb13-77">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 輔助功能：把訊息加到畫面上</span></span>
<span id="cb13-78">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender) {</span>
<span id="cb13-79">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-80">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> div <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">createElement</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"div"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-81">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">classList</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-82">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-83">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendChild</span>(div)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-84">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollTop</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollHeight</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 自動捲動到底部</span></span>
<span id="cb13-85">        }</span>
<span id="cb13-86"></span>
<span id="cb13-87">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 輔助功能：按 Enter 鍵也能發送</span></span>
<span id="cb13-88">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">handleEnter</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span>) {</span>
<span id="cb13-89">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">key</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">===</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Enter"</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb13-90">        }</span>
<span id="cb13-91">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-92"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">body</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb13-93"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">html</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div>
<p>然後在伺服器已啟動的前提下用瀏覽器開啟網頁，成功時可以正常跟它對話，正常流程為：</p>
<ol type="1">
<li><p>Frontend (HTML/JS): 瀏覽器抓取你輸入的文字。</p></li>
<li><p>API Call: 瀏覽器發送請求給 http://127.0.0.1:8000/chat。</p></li>
<li><p>Backend (FastAPI): 你的 Python 程式收到請求，轉頭去問 Groq。</p></li>
<li><p>LLM: Groq 運算完，把結果傳回 Python。</p></li>
<li><p>Render: Python 把結果傳回瀏覽器，JavaScript 把文字貼在畫面上。</p></li>
</ol>
<p>但它還不具備逐字載入與上下文記憶功能。</p>
</section>
<section id="細節微調" class="level1">
<h1>細節微調</h1>
<section id="讓網頁逐字載入回應" class="level2">
<h2 class="anchored" data-anchor-id="讓網頁逐字載入回應">讓網頁逐字載入回應</h2>
<p>先修改 <code>serve.py</code>，首先在套件部分加入</p>
<div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> fastapi.responses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StreamingResponse <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入串流回應</span></span></code></pre></div>
<p>再去修改回傳函數</p>
<div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 這裡我們不直接回傳，而是寫一個 "產生器 (Generator)"</span></span>
<span id="cb15-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 它的功能是：一邊收 Groq 的資料，一邊吐給前端</span></span>
<span id="cb15-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_ai_response_stream(user_message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb15-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb15-5">        completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(</span>
<span id="cb15-6">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,</span>
<span id="cb15-7">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb15-8">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你是一個繁體中文 AI 助教。"</span>},</span>
<span id="cb15-9">                {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: user_message}</span>
<span id="cb15-10">            ],</span>
<span id="cb15-11">            stream<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [關鍵] 告訴 Groq 我們要用串流模式</span></span>
<span id="cb15-12">        )</span>
<span id="cb15-13">        </span>
<span id="cb15-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> completion:</span>
<span id="cb15-15">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 檢查這個碎片有沒有內容</span></span>
<span id="cb15-16">            content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chunk.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].delta.content</span>
<span id="cb15-17">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> content:</span>
<span id="cb15-18">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> content  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "yield" 意思是產出一個碎片就馬上送出去</span></span>
<span id="cb15-19">                </span>
<span id="cb15-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb15-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb15-22"></span>
<span id="cb15-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/chat"</span>)</span>
<span id="cb15-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> chat_with_ai(data: UserInput):</span>
<span id="cb15-25">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"收到訊息 (串流模式)：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>data<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>message<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb15-26">    </span>
<span id="cb15-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 回傳一個 "串流回應"，而不是一般的 JSON</span></span>
<span id="cb15-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> StreamingResponse(</span>
<span id="cb15-29">        get_ai_response_stream(data.message), </span>
<span id="cb15-30">        media_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text/plain"</span></span>
<span id="cb15-31">    )</span></code></pre></div>
<p>再來改前端部分，主要是要修改 Javascripts 部分，讓回應可以產生逐字載入的動畫效果：</p>
<div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode html code-with-copy"><code class="sourceCode html"><span id="cb16-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb16-2">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>() {</span>
<span id="cb16-3">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> inputField <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user-input"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-4">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> sendBtn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"send-btn"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-5">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-6">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">trim</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-7"></span>
<span id="cb16-8">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>message) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-9"></span>
<span id="cb16-10">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 1. 顯示使用者訊息</span></span>
<span id="cb16-11">            <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-12">            inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-13">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-14">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"思考中..."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-15"></span>
<span id="cb16-16">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 2. 準備接收 AI 的回答 (先建立一個空的對話框)</span></span>
<span id="cb16-17">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> aiMessageDiv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ai"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> </span>
<span id="cb16-18"></span>
<span id="cb16-19">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span> {</span>
<span id="cb16-20">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/chat"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {</span>
<span id="cb16-21">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POST"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb16-22">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">headers</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> { <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Content-Type"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"application/json"</span> }<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb16-23">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">JSON</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stringify</span>({ <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">message</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> message })</span>
<span id="cb16-24">                })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-25"></span>
<span id="cb16-26">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [NEW] 處理串流資料的關鍵邏輯</span></span>
<span id="cb16-27">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> reader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getReader</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-28">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> decoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">TextDecoder</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-29"></span>
<span id="cb16-30">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span>) {</span>
<span id="cb16-31">                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> { done<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> value } <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> reader<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-32">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (done) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 如果讀完了就跳出迴圈</span></span>
<span id="cb16-33"></span>
<span id="cb16-34">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 把讀到的二進位資料轉成文字</span></span>
<span id="cb16-35">                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chunk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">decode</span>(value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> { <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stream</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-36">                    </span>
<span id="cb16-37">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 把文字 "追加" 到目前的對話框裡，而不是覆蓋</span></span>
<span id="cb16-38">                    aiMessageDiv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> chunk<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-39">                    </span>
<span id="cb16-40">                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 自動捲動到底部</span></span>
<span id="cb16-41">                    chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollTop</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollHeight</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-42">                }</span>
<span id="cb16-43"></span>
<span id="cb16-44">            } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">catch</span> (error) {</span>
<span id="cb16-45">                aiMessageDiv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"連線發生錯誤。"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-46">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">console</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">error</span>(error)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-47">            }</span>
<span id="cb16-48"></span>
<span id="cb16-49">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-50">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"發送"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-51">        }</span>
<span id="cb16-52"></span>
<span id="cb16-53">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 輔助功能：建立訊息框並回傳該元素 (讓我們可以持續更新它)</span></span>
<span id="cb16-54">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender) {</span>
<span id="cb16-55">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-56">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> div <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">createElement</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"div"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-57">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">classList</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-58">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-59">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendChild</span>(div)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-60">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollTop</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollHeight</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-61">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 回傳這個 div，讓外面的程式可以繼續塞字進去</span></span>
<span id="cb16-62">        }</span>
<span id="cb16-63"></span>
<span id="cb16-64">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">handleEnter</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span>) {</span>
<span id="cb16-65">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">key</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">===</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Enter"</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb16-66">        }</span>
<span id="cb16-67">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div>
<p><del>不意外的超級長，根本可以拆成獨立檔案了</del></p>
<p>測試有沒有成功跟前面一樣，只要確定伺服器有再跑，網頁重整後輸入 Prompt 觀察即可。</p>
</section>
<section id="上下文記憶" class="level2">
<h2 class="anchored" data-anchor-id="上下文記憶">上下文記憶</h2>
<p>LLM 本質上是 「無狀態 (Stateless)」 的，意思就是說：每一次呼叫 API，對它來說都是全新的開始，它完全不記得上一秒發生了什麼。所以我們需要把之前的對話紀錄，全部打包再一次寄給它，讓它看起來好像具備了記憶能力。</p>
<p>一樣先修改 <code>serve.py</code>，首先在套件部分加入</p>
<div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> List, Dict <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入型別定義</span></span></code></pre></div>
<p>再去修改資料模型與回傳函數：</p>
<div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 修改資料模型：不再只是單一字串，而是接受一個列表 (List)</span></span>
<span id="cb18-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 格式會像這樣：[{"role": "user", "content": "hi"}, {"role": "assistant", "content": "hello"}]</span></span>
<span id="cb18-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> UserInput(BaseModel):</span>
<span id="cb18-4">    messages: List[Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]</span>
<span id="cb18-5"></span>
<span id="cb18-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_ai_response_stream(messages_history: List[Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]):</span>
<span id="cb18-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb18-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 這裡不再是寫死的 system prompt + user message</span></span>
<span id="cb18-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 而是直接把前端傳來的 "整包歷史紀錄" 丟給 Groq</span></span>
<span id="cb18-10">        </span>
<span id="cb18-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 我們可以在最前面偷偷加一個 System Prompt 設定 AI 人設</span></span>
<span id="cb18-12">        system_prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你是一個繁體中文 AI 助教，記憶力很好。"</span>}]</span>
<span id="cb18-13">        full_context <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> system_prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> messages_history</span>
<span id="cb18-14"></span>
<span id="cb18-15">        completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(</span>
<span id="cb18-16">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,</span>
<span id="cb18-17">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>full_context, <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這裡放入完整的對話歷史</span></span>
<span id="cb18-18">            stream<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb18-19">        )</span>
<span id="cb18-20">        </span>
<span id="cb18-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> completion:</span>
<span id="cb18-22">            content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chunk.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].delta.content</span>
<span id="cb18-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> content:</span>
<span id="cb18-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> content</span>
<span id="cb18-25"></span>
<span id="cb18-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb18-27">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb18-28"></span>
<span id="cb18-29"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@app.post</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/chat"</span>)</span>
<span id="cb18-30"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> chat_with_ai(data: UserInput):</span>
<span id="cb18-31">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># print(f"收到歷史對話，長度：{len(data.messages)}") # 除錯用</span></span>
<span id="cb18-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> StreamingResponse(</span>
<span id="cb18-33">        get_ai_response_stream(data.messages), </span>
<span id="cb18-34">        media_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text/plain"</span></span>
<span id="cb18-35">    )</span></code></pre></div>
<p>可以觀察到<del>除了程式越來越長以外</del>，送給模型的不再只有 System Prompt，也有歷史對話訊息。</p>
<p>再來需要修改前端，讓它可以記錄之前的對話訊息，送入後端，一樣改<code>index.html</code>的 JavaScripts 部分：</p>
<div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode html code-with-copy"><code class="sourceCode html"><span id="cb19-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb19-2">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [NEW] 用來暫存對話紀錄的變數</span></span>
<span id="cb19-3">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> conversationHistory <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-4"></span>
<span id="cb19-5">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>() {</span>
<span id="cb19-6">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> inputField <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user-input"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-7">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> sendBtn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"send-btn"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-8">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-9">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">trim</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-10"></span>
<span id="cb19-11">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>message) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-12"></span>
<span id="cb19-13">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 1. 顯示並紀錄使用者的訊息</span></span>
<span id="cb19-14">            <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-15">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [NEW] 把使用者的話加入歷史紀錄</span></span>
<span id="cb19-16">            conversationHistory<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({ <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> message })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-17"></span>
<span id="cb19-18">            inputField<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">value</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-19">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-20">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"思考中..."</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-21"></span>
<span id="cb19-22">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> aiMessageDiv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ai"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> </span>
<span id="cb19-23">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">let</span> fullAiResponse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 用來收集 AI 的完整回答</span></span>
<span id="cb19-24"></span>
<span id="cb19-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span> {</span>
<span id="cb19-26">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [NEW] 傳送 "整包歷史紀錄" 給後端</span></span>
<span id="cb19-27">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"http://127.0.0.1:8000/chat"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> {</span>
<span id="cb19-28">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">method</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POST"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-29">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">headers</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> { <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Content-Type"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"application/json"</span> }<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb19-30">                    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">JSON</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stringify</span>({ <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">messages</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> conversationHistory }) </span>
<span id="cb19-31">                })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-32"></span>
<span id="cb19-33">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> reader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">body</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getReader</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-34">                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> decoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">TextDecoder</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-35"></span>
<span id="cb19-36">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> (<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span>) {</span>
<span id="cb19-37">                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> { done<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> value } <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> reader<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-38">                    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (done) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-39"></span>
<span id="cb19-40">                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chunk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">decode</span>(value<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> { <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">stream</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">true</span> })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-41">                    aiMessageDiv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> chunk<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-42">                    fullAiResponse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> chunk<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// 收集碎片</span></span>
<span id="cb19-43">                    chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollTop</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollHeight</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-44">                }</span>
<span id="cb19-45">                </span>
<span id="cb19-46">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// [NEW] AI 講完後，把它的話也加入歷史紀錄</span></span>
<span id="cb19-47">                conversationHistory<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">push</span>({ <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assistant"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> fullAiResponse })<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-48"></span>
<span id="cb19-49">            } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">catch</span> (error) {</span>
<span id="cb19-50">                aiMessageDiv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"連線發生錯誤。"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-51">                <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">console</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">error</span>(error)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-52">            }</span>
<span id="cb19-53"></span>
<span id="cb19-54">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">disabled</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">false</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-55">            sendBtn<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"發送"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-56">        }</span>
<span id="cb19-57"></span>
<span id="cb19-58">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendMessage</span>(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender) {</span>
<span id="cb19-59">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> chatBox <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getElementById</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chat-box"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-60">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">const</span> div <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">document</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">createElement</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"div"</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-61">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">classList</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">add</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"message"</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> sender)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-62">            div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">innerText</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-63">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">appendChild</span>(div)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-64">            chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollTop</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chatBox<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scrollHeight</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-65">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> div<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-66">        }</span>
<span id="cb19-67"></span>
<span id="cb19-68">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">handleEnter</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span>) {</span>
<span id="cb19-69">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">event</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">key</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">===</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Enter"</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sendMessage</span>()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span>
<span id="cb19-70">        }</span>
<span id="cb19-71">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&lt;/</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">script</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div>
<p>最後重整網頁，輸入需要上下文記憶的問題，看它能不能正確回應、順順的跟你聊下去。</p>
</section>
</section>
<section id="rag-系統" class="level1">
<h1>RAG 系統</h1>
<p>重點終於來啦，RAG 的概念因為我已經學過，就不贅述，直接進入程式部分。</p>
<section id="先建立簡單的-rag" class="level2">
<h2 class="anchored" data-anchor-id="先建立簡單的-rag">先建立簡單的 RAG</h2>
<p>安裝套件</p>
<div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb20-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install sentence-transformers scikit-learn</span></code></pre></div>
<p>然後建立一個簡單的資料來源<code>faq_data.py</code></p>
<div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這只是一個簡單的模擬資料庫 (Dictionary)</span></span>
<span id="cb21-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 實務上這裡會是 PDF 檔案或資料庫</span></span>
<span id="cb21-3">knowledge_base <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb21-4">    {</span>
<span id="cb21-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb21-6">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"關於請假：本公司員工每年享有 14 天特休，請假需提前 3 天在 HR 系統申請。"</span></span>
<span id="cb21-7">    },</span>
<span id="cb21-8">    {</span>
<span id="cb21-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,</span>
<span id="cb21-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"關於加班費：平日加班費為 1.33 倍，假日加班費為 1.66 倍，需經理核准。"</span></span>
<span id="cb21-11">    },</span>
<span id="cb21-12">    {</span>
<span id="cb21-13">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>,</span>
<span id="cb21-14">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"關於午餐：公司每日中午 12:00 提供免費便當，素食者需在 10:00 前登記。"</span></span>
<span id="cb21-15">    },</span>
<span id="cb21-16">    {</span>
<span id="cb21-17">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>,</span>
<span id="cb21-18">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"關於在家工作 (WFH)：每週三為固定 WFH 日，其餘時間需進辦公室。"</span></span>
<span id="cb21-19">    }</span>
<span id="cb21-20">]</span></code></pre></div>
<p>建立一個新的 <code>rag_service.py</code></p>
<div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb22-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sentence_transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> SentenceTransformer</span>
<span id="cb22-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.metrics.pairwise <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cosine_similarity</span>
<span id="cb22-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> faq_data <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> knowledge_base</span>
<span id="cb22-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb22-5"></span>
<span id="cb22-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"正在載入 Embedding 模型 (第一次會比較久)..."</span>)</span>
<span id="cb22-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 下載一個輕量級的中文模型 (這會存在你的電腦裡)</span></span>
<span id="cb22-8">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SentenceTransformer(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'paraphrase-multilingual-MiniLM-L12-v2'</span>)</span>
<span id="cb22-9"></span>
<span id="cb22-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 先把知識庫裡的每一條資料，都轉換成「向量 (Vector)」並存起來</span></span>
<span id="cb22-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這樣待會搜尋速度才會快</span></span>
<span id="cb22-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"正在建立索引..."</span>)</span>
<span id="cb22-13">knowledge_texts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [item[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> item <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> knowledge_base]</span>
<span id="cb22-14">knowledge_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.encode(knowledge_texts)</span>
<span id="cb22-15"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"索引建立完成！"</span>)</span>
<span id="cb22-16"></span>
<span id="cb22-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> search_knowledge_base(query, top_k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb22-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb22-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    輸入使用者的問題 (query)，回傳最相似的 k 筆資料</span></span>
<span id="cb22-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb22-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. 把使用者的問題也變成向量</span></span>
<span id="cb22-22">    query_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.encode([query])</span>
<span id="cb22-23">    </span>
<span id="cb22-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 計算相似度 (Cosine Similarity)</span></span>
<span id="cb22-25">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 比較「問題向量」跟所有「知識庫向量」的距離</span></span>
<span id="cb22-26">    similarities <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cosine_similarity(query_embedding, knowledge_embeddings)</span>
<span id="cb22-27">    </span>
<span id="cb22-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. 找出分數最高的 top_k 個結果</span></span>
<span id="cb22-29">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># argsort 會回傳排序後的索引，我們取最後 top_k 個 (分數最高的)</span></span>
<span id="cb22-30">    top_indices <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> similarities[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].argsort()[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>top_k:][::<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb22-31">    </span>
<span id="cb22-32">    results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb22-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> top_indices:</span>
<span id="cb22-34">        score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> similarities[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][idx]</span>
<span id="cb22-35">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.3</span>: <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 設定一個門檻，相關度太低就不採用 (避免誤導)</span></span>
<span id="cb22-36">            results.append(knowledge_base[idx][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>])</span>
<span id="cb22-37">            </span>
<span id="cb22-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> results</span>
<span id="cb22-39"></span>
<span id="cb22-40"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 測試用 (當你直接執行這個檔案時會跑這段)</span></span>
<span id="cb22-41"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__main__"</span>:</span>
<span id="cb22-42">    test_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"請問加班有錢拿嗎？"</span></span>
<span id="cb22-43">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">測試搜尋：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>test_query<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb22-44">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(search_knowledge_base(test_query))</span></code></pre></div>
<p>跑這支腳本，成功的話會出現資料庫裡，加班費的計算方式。</p>
</section>
<section id="整合進後端" class="level2">
<h2 class="anchored" data-anchor-id="整合進後端">整合進後端</h2>
<p>修改 <code>server.py</code> 的套件部分跟<code>get_ai_response_stream()</code>：</p>
<div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> rag_service <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> search_knowledge_base <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入搜尋功能</span></span></code></pre></div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-6-contents" aria-controls="callout-6" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
提示
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-6" class="callout-6-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>這裡抓的是同資料夾下，<code>rag_service.py</code> 的 <code>search_knowledge_base()</code> 函數，如果是放在專案資料夾下的另一個資料夾(假設叫 <code>another_folder</code> )則需這樣讀取</p>
<div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> another_folder.rag_service <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> search_knowledge_base <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [NEW] 引入搜尋功能</span></span></code></pre></div>
</div>
</div>
</div>
<div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (中間設定不變)</span></span>
<span id="cb25-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_ai_response_stream(messages_history: List[Dict[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]]):</span>
<span id="cb25-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb25-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. 抓出使用者最新的一句話</span></span>
<span id="cb25-5">        latest_user_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> messages_history[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>]</span>
<span id="cb25-6">        </span>
<span id="cb25-7">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. [RAG 核心] 先去知識庫搜尋有沒有相關資料</span></span>
<span id="cb25-8">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"正在搜尋知識庫：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>latest_user_message<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-9">        retrieved_info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> search_knowledge_base(latest_user_message)</span>
<span id="cb25-10">        </span>
<span id="cb25-11">        system_instruction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"你是一個繁體中文 AI 助教。"</span></span>
<span id="cb25-12">        </span>
<span id="cb25-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. 如果有搜到資料，就把它塞進 System Prompt 裡</span></span>
<span id="cb25-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> retrieved_info:</span>
<span id="cb25-15">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"搜到資料：</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>retrieved_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb25-16">            context_str <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(retrieved_info)</span>
<span id="cb25-17">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 這裡就是 RAG 的精隨：把資料貼給 AI 看</span></span>
<span id="cb25-18">            system_instruction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">【參考資料】：</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>context_str<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">請根據上述【參考資料】回答使用者的問題。如果資料裡沒有答案，請說不知道，不要瞎掰。"</span></span>
<span id="cb25-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb25-20">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"沒搜到相關資料"</span>)</span>
<span id="cb25-21"></span>
<span id="cb25-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 重新組裝 messages</span></span>
<span id="cb25-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 我們把原本的 messages_history 保留，但把第一句 system prompt 換掉</span></span>
<span id="cb25-24">        current_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: system_instruction}] </span>
<span id="cb25-25">        </span>
<span id="cb25-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 把使用者之前的對話紀錄接在後面 (除了第一句 system prompt)</span></span>
<span id="cb25-27">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 注意：這裡邏輯要小心，我們假設 messages_history 都是 user/assistant 的對話</span></span>
<span id="cb25-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> msg <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> messages_history:</span>
<span id="cb25-29">             <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> msg[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>:</span>
<span id="cb25-30">                 current_messages.append(msg)</span>
<span id="cb25-31"></span>
<span id="cb25-32">        completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(</span>
<span id="cb25-33">            model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama-3.3-70b-versatile"</span>,</span>
<span id="cb25-34">            messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>current_messages,</span>
<span id="cb25-35">            stream<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb25-36">        )</span>
<span id="cb25-37">        </span>
<span id="cb25-38">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> completion:</span>
<span id="cb25-39">            content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chunk.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].delta.content</span>
<span id="cb25-40">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> content:</span>
<span id="cb25-41">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> content</span>
<span id="cb25-42"></span>
<span id="cb25-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb25-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb25-45"></span>
<span id="cb25-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... (後面不變)</span></span></code></pre></div>
<p>前端不用改，直接進入網頁測試</p>
<ol type="1">
<li><p>測試 1 (基本題): 問它：「你好。」(它應該正常回答)</p></li>
<li><p>測試 2 (RAG 題): 問它：「請問公司中午有便當吃嗎？」</p></li>
<li><p>測試 3 (RAG 題): 問它：「我想申請 WFH，星期幾可以？」</p></li>
</ol>
<p>若有成功按照資料庫回答問題表示成功。</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
警告
</div>
</div>
<div class="callout-body-container callout-body">
<p>啟動伺服器時，首次執行會進行 Embedding 步驟，耗時較長，需耐心等候，如果要加快速度，實務上會用 Chrome DB 儲存切好 chunk的資料。</p>
</div>
</div>
</section>
</section>
<section id="暫時性結論" class="level1">
<h1>暫時性結論</h1>
<p>其實它後面還介紹了上傳 PDF 、包成 Docker 之類的服務，<del>但我累惹先停在這</del>，不過還是沒有介紹到課綱裡的前端框架跟服務，感覺它完全自己走自己的==。</p>
<p>後面的東西有空再來繼續，希望可以實現包 Docker 成功初體驗~~</p>
</section>
<section id="有用的參考資料" class="level1">
<h1>有用的參考資料</h1>
<p>官方文件絕對是最好的一手資料來源：</p>
<ul>
<li><p><a href="https://console.groq.com/docs/overview">Groq</a></p></li>
<li><p><a href="https://fastapi.tiangolo.com/#create-it">FastAPI</a></p></li>
</ul>
<p>其次是其他工程師寫過的文章</p>
<ul>
<li><p><a href="https://ithelp.ithome.com.tw/m/articles/10320028">IT鐵人幫-FastAPI介紹</a></p></li>
<li><p><a href="https://realnewbie.com/posts/complete-guide-to-managing-environment-variables-with-python-dotenv">讀取 .env 的重要性+實作</a></p></li>
</ul>



</section>


<div class="quarto-listing quarto-listing-container-default" id="listing-listing">
<div class="list quarto-listing-default">

</div>
<div class="listing-no-matching d-none">無符合的項目</div>
</div><div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">腳註</h2>

<ol>
<li id="fn1"><p>AI 多半會直接給，不過我還是列一下從搜尋結果進入官網的步驟。↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>toys</category>
  <category>LLM</category>
  <category>RAG</category>
  <guid>https://paperfishblog.netlify.app/posts/llm/llm-course-note1/</guid>
  <pubDate>Sun, 25 Jan 2026 16:00:00 GMT</pubDate>
  <media:content url="https://paperfishblog.netlify.app/posts/llm/llm-course-note1/image.png" medium="image" type="image/png" height="81" width="144"/>
</item>
</channel>
</rss>
