<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Artifacts on Tarragon</title><link>https://tarrragon.github.io/blog/tags/artifacts/</link><description>Recent content in Artifacts on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 12 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/artifacts/index.xml" rel="self" type="application/rss+xml"/><item><title>4.10 衍生產物管理原理：什麼進 git、什麼不該</title><link>https://tarrragon.github.io/blog/llm/04-applications/artifact-management/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/artifact-management/</guid><description>&lt;p>LLM 應用的 codebase 不只 source code、還含 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding&lt;/a> index、cache、model weights、prompt config、lockfile、log 等各種「衍生」或「外部」產物。每個產物該不該進 git、有沒有共通邏輯？&lt;/p>
&lt;p>本章寫的是「&lt;strong>source / derived / external 三類產物的判讀框架&lt;/strong>」、跟「production deployment 怎麼處理 share + reproducibility 取捨」。對應到 hands-on 系列實際遇到的問題——為什麼 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG&lt;/a> demo 的 &lt;code>index.pkl&lt;/code> 進 &lt;code>.gitignore&lt;/code>、Hugging Face model weights 為什麼不能塞進 repo、prompt template 該怎麼版本管理。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning&lt;/a> 對應「production 怎麼跑」、本章對應「production 怎麼版本控制 + 部署」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後你能：&lt;/p>
&lt;ol>
&lt;li>用「source / derived / external」三分類判讀任何產物該不該進 git。&lt;/li>
&lt;li>看到 &lt;code>.gitignore&lt;/code> 設計、能解釋每條規則的邏輯。&lt;/li>
&lt;li>在 reproducibility 跟 repo 大小之間做合理取捨。&lt;/li>
&lt;li>知道 derived / external 產物該用什麼機制 share（registry、build script、artifact storage）。&lt;/li>
&lt;/ol>
&lt;h2 id="三類產物-framework">三類產物 framework&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類別&lt;/th>
 &lt;th>定義&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;th>該進 git？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Source&lt;/strong>&lt;/td>
 &lt;td>人類撰寫、是真理來源&lt;/td>
 &lt;td>code、prompt template、test fixture、config schema&lt;/td>
 &lt;td>必須&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Derived&lt;/strong>&lt;/td>
 &lt;td>從 source 自動產出、可重建&lt;/td>
 &lt;td>binary、index、cache、compiled output、generated docs&lt;/td>
 &lt;td>不該&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>External&lt;/strong>&lt;/td>
 &lt;td>從外部下載、跟 source 解耦&lt;/td>
 &lt;td>model weights、dependency package、dataset&lt;/td>
 &lt;td>用 registry / manifest&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀問題：「&lt;strong>刪掉重來、用什麼能 reconstruct 一模一樣？&lt;/strong>」&lt;/p>
&lt;ul>
&lt;li>用人手寫 → source、必須 commit&lt;/li>
&lt;li>用 build script + source → derived、commit manifest（如 lockfile）不 commit output&lt;/li>
&lt;li>用 download script + URL → external、commit URL 不 commit content&lt;/li>
&lt;/ul>
&lt;p>這個 framework 跨任何技術 stack 都成立（不只 LLM）、但 LLM 應用尤其放大 derived / external 比例。&lt;/p>
&lt;h2 id="llm-應用具體對應">LLM 應用具體對應&lt;/h2>
&lt;h3 id="source進-git">Source（進 git）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>程式 source code&lt;/td>
 &lt;td>wrapper script、framework 整合 code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Prompt template&lt;/td>
 &lt;td>system prompt、few-shot example、prompt structure&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Config schema&lt;/td>
 &lt;td>哪些參數可調、合法範圍、default value&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Test fixture&lt;/td>
 &lt;td>測試輸入 / 預期輸出 pair&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Markdown content（如本 blog）&lt;/td>
 &lt;td>文章本身就是 source&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>.gitignore&lt;/code> / lock file 規則&lt;/td>
 &lt;td>描述哪些不進 git 也是 source&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build script&lt;/td>
 &lt;td>&lt;code>ingest.py&lt;/code>、&lt;code>build.sh&lt;/code>、能從 source 重建 derived&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="derived不進-git但-build-path-進-git">Derived（不進 git、但 build path 進 git）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>為什麼不 commit&lt;/th>
 &lt;th>怎麼 share&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>index.pkl&lt;/code>（RAG embedding index）&lt;/td>
 &lt;td>從 corpus + embedding model 重建、跟 model 版本綁、3.7 MB-GB 級&lt;/td>
 &lt;td>&lt;code>ingest.py&lt;/code> script、跑一次就 reconstruct&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Embedding cache（per-document hash）&lt;/td>
 &lt;td>跑時動態建、避免重 embed 同 chunk&lt;/td>
 &lt;td>不 share、各自 rebuild&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python &lt;code>__pycache__/&lt;/code>&lt;/td>
 &lt;td>跑時自動產、Python 版本敏感&lt;/td>
 &lt;td>不 share、各自 rebuild&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Compiled binary（如 &lt;code>bin/mdtools&lt;/code>）&lt;/td>
 &lt;td>從 Go source build、平台敏感&lt;/td>
 &lt;td>source + build instructions、可選 release page 提供&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Generated docs（如 Hugo &lt;code>public/&lt;/code>）&lt;/td>
 &lt;td>從 markdown source build、deploy 時自動生&lt;/td>
 &lt;td>source + deploy pipeline&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Log files&lt;/td>
 &lt;td>runtime output、量大、有 PII 風險&lt;/td>
 &lt;td>不 share、log retention 政策另立&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="external不進-git用-manifest--registry">External（不進 git、用 manifest / registry）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>產物&lt;/th>
 &lt;th>Manifest / registry&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>LLM model weights&lt;/td>
 &lt;td>Hugging Face / Ollama registry tag&lt;/td>
 &lt;td>&lt;code>nomic-embed-text:latest&lt;/code>、&lt;code>sd_xl_base_1.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python dependency&lt;/td>
 &lt;td>&lt;code>requirements.txt&lt;/code> / &lt;code>pyproject.toml&lt;/code>&lt;/td>
 &lt;td>&lt;code>requests==2.31.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Node modules&lt;/td>
 &lt;td>&lt;code>package.json&lt;/code> + &lt;code>package-lock.json&lt;/code>&lt;/td>
 &lt;td>&lt;code>react@18.2.0&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dataset&lt;/td>
 &lt;td>&lt;code>data.dvc&lt;/code> / S3 URL + checksum&lt;/td>
 &lt;td>training data、eval set&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Docker image&lt;/td>
 &lt;td>&lt;code>Dockerfile&lt;/code> + image tag&lt;/td>
 &lt;td>&lt;code>python:3.11-slim&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>External 跟 derived 的差別：external 來自 git 外的 source、derived 來自 git 內的 source。&lt;strong>機制上都用同套路徑&lt;/strong>——manifest 進 git、實際 bytes 存 registry、避免大檔直接進 commit history。&lt;/p></description><content:encoded><![CDATA[<p>LLM 應用的 codebase 不只 source code、還含 <a href="/blog/llm/knowledge-cards/embedding-model/" data-link-title="Embedding Model" data-link-desc="把文字轉成向量的模型：用於 codebase 索引與語意搜尋">embedding</a> index、cache、model weights、prompt config、lockfile、log 等各種「衍生」或「外部」產物。每個產物該不該進 git、有沒有共通邏輯？</p>
<p>本章寫的是「<strong>source / derived / external 三類產物的判讀框架</strong>」、跟「production deployment 怎麼處理 share + reproducibility 取捨」。對應到 hands-on 系列實際遇到的問題——為什麼 <a href="/blog/llm/knowledge-cards/rag/" data-link-title="RAG" data-link-desc="Retrieval-Augmented Generation：動態外掛知識給 LLM、繞開模型參數記憶的靜態限制">RAG</a> demo 的 <code>index.pkl</code> 進 <code>.gitignore</code>、Hugging Face model weights 為什麼不能塞進 repo、prompt template 該怎麼版本管理。</p>
<p>跟 <a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning</a> 對應「production 怎麼跑」、本章對應「production 怎麼版本控制 + 部署」。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後你能：</p>
<ol>
<li>用「source / derived / external」三分類判讀任何產物該不該進 git。</li>
<li>看到 <code>.gitignore</code> 設計、能解釋每條規則的邏輯。</li>
<li>在 reproducibility 跟 repo 大小之間做合理取捨。</li>
<li>知道 derived / external 產物該用什麼機制 share（registry、build script、artifact storage）。</li>
</ol>
<h2 id="三類產物-framework">三類產物 framework</h2>
<table>
  <thead>
      <tr>
          <th>類別</th>
          <th>定義</th>
          <th>例子</th>
          <th>該進 git？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Source</strong></td>
          <td>人類撰寫、是真理來源</td>
          <td>code、prompt template、test fixture、config schema</td>
          <td>必須</td>
      </tr>
      <tr>
          <td><strong>Derived</strong></td>
          <td>從 source 自動產出、可重建</td>
          <td>binary、index、cache、compiled output、generated docs</td>
          <td>不該</td>
      </tr>
      <tr>
          <td><strong>External</strong></td>
          <td>從外部下載、跟 source 解耦</td>
          <td>model weights、dependency package、dataset</td>
          <td>用 registry / manifest</td>
      </tr>
  </tbody>
</table>
<p>判讀問題：「<strong>刪掉重來、用什麼能 reconstruct 一模一樣？</strong>」</p>
<ul>
<li>用人手寫 → source、必須 commit</li>
<li>用 build script + source → derived、commit manifest（如 lockfile）不 commit output</li>
<li>用 download script + URL → external、commit URL 不 commit content</li>
</ul>
<p>這個 framework 跨任何技術 stack 都成立（不只 LLM）、但 LLM 應用尤其放大 derived / external 比例。</p>
<h2 id="llm-應用具體對應">LLM 應用具體對應</h2>
<h3 id="source進-git">Source（進 git）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式 source code</td>
          <td>wrapper script、framework 整合 code</td>
      </tr>
      <tr>
          <td>Prompt template</td>
          <td>system prompt、few-shot example、prompt structure</td>
      </tr>
      <tr>
          <td>Config schema</td>
          <td>哪些參數可調、合法範圍、default value</td>
      </tr>
      <tr>
          <td>Test fixture</td>
          <td>測試輸入 / 預期輸出 pair</td>
      </tr>
      <tr>
          <td>Markdown content（如本 blog）</td>
          <td>文章本身就是 source</td>
      </tr>
      <tr>
          <td><code>.gitignore</code> / lock file 規則</td>
          <td>描述哪些不進 git 也是 source</td>
      </tr>
      <tr>
          <td>Build script</td>
          <td><code>ingest.py</code>、<code>build.sh</code>、能從 source 重建 derived</td>
      </tr>
  </tbody>
</table>
<h3 id="derived不進-git但-build-path-進-git">Derived（不進 git、但 build path 進 git）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>為什麼不 commit</th>
          <th>怎麼 share</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>index.pkl</code>（RAG embedding index）</td>
          <td>從 corpus + embedding model 重建、跟 model 版本綁、3.7 MB-GB 級</td>
          <td><code>ingest.py</code> script、跑一次就 reconstruct</td>
      </tr>
      <tr>
          <td>Embedding cache（per-document hash）</td>
          <td>跑時動態建、避免重 embed 同 chunk</td>
          <td>不 share、各自 rebuild</td>
      </tr>
      <tr>
          <td>Python <code>__pycache__/</code></td>
          <td>跑時自動產、Python 版本敏感</td>
          <td>不 share、各自 rebuild</td>
      </tr>
      <tr>
          <td>Compiled binary（如 <code>bin/mdtools</code>）</td>
          <td>從 Go source build、平台敏感</td>
          <td>source + build instructions、可選 release page 提供</td>
      </tr>
      <tr>
          <td>Generated docs（如 Hugo <code>public/</code>）</td>
          <td>從 markdown source build、deploy 時自動生</td>
          <td>source + deploy pipeline</td>
      </tr>
      <tr>
          <td>Log files</td>
          <td>runtime output、量大、有 PII 風險</td>
          <td>不 share、log retention 政策另立</td>
      </tr>
  </tbody>
</table>
<h3 id="external不進-git用-manifest--registry">External（不進 git、用 manifest / registry）</h3>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>Manifest / registry</th>
          <th>例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LLM model weights</td>
          <td>Hugging Face / Ollama registry tag</td>
          <td><code>nomic-embed-text:latest</code>、<code>sd_xl_base_1.0</code></td>
      </tr>
      <tr>
          <td>Python dependency</td>
          <td><code>requirements.txt</code> / <code>pyproject.toml</code></td>
          <td><code>requests==2.31.0</code></td>
      </tr>
      <tr>
          <td>Node modules</td>
          <td><code>package.json</code> + <code>package-lock.json</code></td>
          <td><code>react@18.2.0</code></td>
      </tr>
      <tr>
          <td>Dataset</td>
          <td><code>data.dvc</code> / S3 URL + checksum</td>
          <td>training data、eval set</td>
      </tr>
      <tr>
          <td>Docker image</td>
          <td><code>Dockerfile</code> + image tag</td>
          <td><code>python:3.11-slim</code></td>
      </tr>
  </tbody>
</table>
<p>External 跟 derived 的差別：external 來自 git 外的 source、derived 來自 git 內的 source。<strong>機制上都用同套路徑</strong>——manifest 進 git、實際 bytes 存 registry、避免大檔直接進 commit history。</p>
<h2 id="為什麼-derived--external-不該進-git">為什麼 derived / external 不該進 git</h2>
<p>每條限制有具體技術理由：</p>
<h3 id="size">Size</h3>
<p>Git 設計給 source code（小、純文字、頻繁 diff）。Derived / external 通常大、binary、不適合：</p>
<ul>
<li>Git 對 large binary 沒有有效 delta 演算法、每次小改 → 完整 copy 進 history</li>
<li>Repo size 線性漲、clone 變慢、CI cache 爆炸</li>
<li>GitHub 等服務有 file size 上限（GitHub 100 MB / file）</li>
</ul>
<p>實例：<code>scripts/rag-demo/index.pkl</code> 3.7 MB、每次 corpus 改 → 重 ingest → 整檔變。Commit 100 次 = git history 多 370 MB。Clone 痛。</p>
<h3 id="reproducibility反直覺">Reproducibility（反直覺）</h3>
<p>直覺：「commit derived 保證每個 clone 都拿到一樣的 output」——錯。</p>
<p>實際：</p>
<ul>
<li>Derived 跟 build env 綁（Python 3.13 build 的 pickle 在 3.14 不一定能 load）</li>
<li>Embedding index 跟 model version 綁（pull 不同 model 結果不同）</li>
<li>用舊 commit 的 derived 跑在新 env 反而比 rebuild 更脆弱</li>
</ul>
<p>正確 reproducibility 機制：commit <strong>build instruction + lockfile</strong>、別人 rebuild 時用同樣輸入產同樣 output。</p>
<h3 id="update-frequency-mismatch">Update frequency mismatch</h3>
<p>Source 改慢、derived 改快。<code>content/</code> 加一句話、<code>index.pkl</code> 整個重建。如果都進 git：</p>
<ul>
<li>90% 的 commit 是「rebuild artifact」、語意上不是真正的「source change」</li>
<li>git log 看不出真正 source 改動</li>
<li>diff review 被 derived noise 淹沒</li>
</ul>
<h3 id="cost--performance">Cost / Performance</h3>
<p>CI / CD pipeline 通常自動 rebuild derived。不 commit 反而：</p>
<ul>
<li>Source-only PR 較易 review（沒 generated diff）</li>
<li>CI build cache 重用、不需從 git 拉 derived</li>
<li>Deploy artifact registry 跟 git 分離、各自 scale</li>
</ul>
<h2 id="llm-應用-gitignore-設計模式">LLM 應用 <code>.gitignore</code> 設計模式</h2>
<p>LLM 應用典型 <code>.gitignore</code> 結構：</p>





<pre tabindex="0"><code class="language-gitignore" data-lang="gitignore"># === Source-side build output (derived) ===
# Compiled binaries
bin/
dist/
build/
*.pyc
__pycache__/

# Hugo / static site generators
public/
.hugo_build.lock
resources/

# RAG / vector indexes (regenerable)
scripts/rag-demo/index.pkl
*.pkl
*.index

# Embedding caches
.embedding_cache/
.vector_cache/

# === External-bound (don&#39;t commit, use manifest) ===
# Python deps (commit requirements.txt instead)
.venv/
venv/
env/

# Node deps
node_modules/

# Model weights / large files
*.safetensors
*.gguf
*.onnx
*.bin

# Datasets
data/raw/
data/processed/

# === Runtime / Local ===
# Logs
*.log
logs/

# OS / IDE
.DS_Store
.vscode/
.idea/

# Local secrets / API keys
.env
.env.local
*.key

# Temp / cache
*.tmp
.cache/</code></pre><h3 id="邊界-case-思考">邊界 case 思考</h3>
<p>幾個容易誤判的：</p>
<table>
  <thead>
      <tr>
          <th>產物</th>
          <th>該不該 commit</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>package-lock.json</code> / <code>poetry.lock</code></td>
          <td>commit</td>
          <td>是 manifest、保證 reproducibility</td>
      </tr>
      <tr>
          <td><code>node_modules/</code></td>
          <td>不 commit</td>
          <td>是 derived、可從 lockfile 重建</td>
      </tr>
      <tr>
          <td>小型 fixture data（&lt; 1 MB）</td>
          <td>commit（作 source）</td>
          <td>是 test 的一部分、不 reconstruct</td>
      </tr>
      <tr>
          <td>大型 eval dataset（&gt; 100 MB）</td>
          <td>用 dvc / S3 manifest</td>
          <td>量大、改用 dvc / S3 manifest 管理</td>
      </tr>
      <tr>
          <td>Pre-built model 用於 demo</td>
          <td>用 release artifact / Hugging Face</td>
          <td>量大、版本要可追蹤</td>
      </tr>
      <tr>
          <td>Prompt template (markdown / yaml)</td>
          <td>commit</td>
          <td>是 source、影響行為、要 diff</td>
      </tr>
      <tr>
          <td>從 LLM 生的 sample output</td>
          <td>不 commit（除非當 fixture）</td>
          <td>是 demo artifact、不 reconstruct 來源</td>
      </tr>
  </tbody>
</table>
<p>判讀 heuristic：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">這個檔案、半年後 production deploy 時要不要存在？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├─ 要：source 或 manifest 進 git
</span></span><span class="line"><span class="ln">3</span><span class="cl">└─ 不要：runtime / 開發環境 only、用 .gitignore</span></span></code></pre></div><h3 id="三分類的退化情境">三分類的退化情境</h3>
<p>三分類是 default framework、實務上有幾類「該不該 commit 的判讀走兩條岔路」的情境、需要特別判讀：</p>
<ul>
<li><strong>Generated client SDK in monorepo</strong>：protobuf / OpenAPI spec 產出的 client code 屬於 derived（從 .proto / .yaml 生）、但 monorepo 場景常 commit 進去、目的是「跨語言版本對齊 + CI 不用每次重生」。判讀：若 .proto / spec 改動頻率低 + 跨語言一致性比 build 速度重要、commit；變動頻繁就回到 derived 路徑。</li>
<li><strong>Jupyter notebook 的 output cell</strong>：技術上是 derived（執行 notebook 產出）、但語意上常被視為 source 的一部分（教學、demo、結果展示）。判讀：教學 / 展示 / 帶 figures 的 notebook 通常 commit 含 output；機械化的 batch run / CI notebook 走 derived、用 nbstripout 清掉 output 再 commit。</li>
<li><strong>Git LFS / git-annex 介於 commit 跟 manifest 之間</strong>：把大檔案 commit 進 git 但實際 bytes 存 LFS server、worktree 看起來像直接 commit、metadata 卻是 manifest pointer。判讀：適合「需要在 git history 中追蹤大檔案版本、但不想讓 repo 體積爆炸」的場景（如 game asset、訓練資料集 snapshot）。介於 commit 跟 dvc / S3 manifest 之間的折衷選項。</li>
<li><strong>Lockfile vs build artifact 的灰色帶</strong>：<code>yarn-error.log</code> 算 log（不 commit）還是 derived 但對 debug 重要（commit）？實務上多數選 .gitignore、但若團隊在 CI 失敗時要 reproduce 環境、保留少量 build log 也合理。</li>
</ul>
<p>判讀原則：三分類給 default、灰色帶用「reproducibility + 變動頻率 + 團隊協作需求」三軸決定具體路徑。</p>
<h2 id="source--derived--external-的-share-機制">Source / Derived / External 的 share 機制</h2>
<p>不 commit 不代表不 share、只是用對的 channel。</p>
<h3 id="source-share--git">Source share = git</h3>
<p>直接 clone 即可。</p>
<h3 id="derived-share-三種模式">Derived share 三種模式</h3>
<ol>
<li><strong>Build script in repo</strong>：別人 clone 後跑 script 重建（本 blog 用這條：<code>ingest.py</code> 重建 index）
<ul>
<li>優點：無外部依賴、self-contained</li>
<li>缺點：每個 clone 都要重跑、累積 compute time</li>
</ul>
</li>
<li><strong>Release artifact</strong>：把 build output 上傳 GitHub Releases / S3、clone 後下載
<ul>
<li>優點：clone 快、不用各自 rebuild</li>
<li>缺點：要 maintain release pipeline、artifact 版本管理另立</li>
</ul>
</li>
<li><strong>Artifact registry</strong>：用 OCI registry、Docker registry、artifact storage（如 GitHub Packages / JFrog Artifactory）
<ul>
<li>優點：production-grade、跨 team / 跨 org share</li>
<li>缺點：複雜、配 auth、cost</li>
</ul>
</li>
</ol>
<p>選擇：小專案用 script、中型用 release、大型 / 多人 collaboration 用 registry。</p>
<h3 id="external-share--manifest">External share = manifest</h3>
<p>把「<strong>從哪下載 + checksum</strong>」commit 進 git、實際 content 不進。常見 manifest format：</p>
<table>
  <thead>
      <tr>
          <th>Manifest</th>
          <th>描述</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>requirements.txt</code> / <code>pyproject.toml</code></td>
          <td>Python deps + version</td>
      </tr>
      <tr>
          <td><code>package.json</code> + <code>package-lock.json</code></td>
          <td>Node deps + exact version + integrity hash</td>
      </tr>
      <tr>
          <td><code>Dockerfile</code></td>
          <td>OS + 環境 + 依賴 + entrypoint</td>
      </tr>
      <tr>
          <td><code>dvc.yaml</code> + <code>dvc.lock</code></td>
          <td>dataset + model version</td>
      </tr>
      <tr>
          <td>Ollama Modelfile（如果寫了）</td>
          <td>LLM model + system prompt 組合</td>
      </tr>
      <tr>
          <td><code>Cargo.lock</code> / <code>go.sum</code></td>
          <td>Rust / Go 的 dep checksum</td>
      </tr>
  </tbody>
</table>
<p>Manifest 自己是 source（人寫、進 git）、它指向的 external content 不進 git（用 download script 取回）。</p>
<h2 id="prompt-跟-config-的版本控制">Prompt 跟 config 的版本控制</h2>
<p>LLM 應用特有的問題：<strong>prompt template 是 source、但 prompt 改變影響行為跟 derived 改變不同</strong>。</p>
<table>
  <thead>
      <tr>
          <th>Prompt 操作</th>
          <th>git 行為</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改一個字</td>
          <td>一個 commit</td>
          <td>模型行為可能大變、要重跑 eval</td>
      </tr>
      <tr>
          <td>加 few-shot example</td>
          <td>一個 commit</td>
          <td>同上</td>
      </tr>
      <tr>
          <td>換不同模型（在 config）</td>
          <td>config commit</td>
          <td>用 prompt 沒變、行為變</td>
      </tr>
  </tbody>
</table>
<p>Prompt + model 是一對組合、行為相依、改一個都要重 test。建議在 commit message / PR description 描述「這個 prompt 改動的 expected behavior change」、用規格層級的 review 對待、勿視為 trivial 小改。</p>
<h3 id="prompt-跟-evaluation-一起管理">Prompt 跟 evaluation 一起管理</h3>
<p>進階做法：每個 prompt 配 evaluation set、commit 在同 PR：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">prompts/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── code_review.md           ← prompt template
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── code_review_eval.json    ← input + expected output pair
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── code_review_history.md   ← 改動記錄 + 對應 eval score</span></span></code></pre></div><p>每次改 prompt、跑 eval、比較 score、進 commit message。這比「改完 push 看看效果」可控很多、是 prompt engineering 的基本姿勢。</p>
<h2 id="production-deployment-的對接">Production deployment 的對接</h2>
<p>本地 hands-on 跟 production 對應：</p>
<table>
  <thead>
      <tr>
          <th>本地 hands-on</th>
          <th>Production</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>python ingest.py</code> build index</td>
          <td>Build pipeline 跑同樣 script、output 進 artifact storage</td>
      </tr>
      <tr>
          <td><code>ollama pull nomic-embed-text</code></td>
          <td>Container image 預載 model 或 mount volume</td>
      </tr>
      <tr>
          <td><code>.gitignore</code> 排除 index.pkl</td>
          <td>CI 自動 rebuild、deploy 時讀 artifact storage</td>
      </tr>
      <tr>
          <td>Source code 進 git</td>
          <td>Source 觸發 CI、build &amp; deploy</td>
      </tr>
  </tbody>
</table>
<p>成熟的 LLM 應用部署 pipeline：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Source change → git push
</span></span><span class="line"><span class="ln">2</span><span class="cl">              → CI triggered
</span></span><span class="line"><span class="ln">3</span><span class="cl">              → Build derived artifacts (index, container image)
</span></span><span class="line"><span class="ln">4</span><span class="cl">              → Run evaluation suite (prompt + model behavior tests)
</span></span><span class="line"><span class="ln">5</span><span class="cl">              → Push artifacts to registry
</span></span><span class="line"><span class="ln">6</span><span class="cl">              → Deploy with manifest pointing to specific artifact version
</span></span><span class="line"><span class="ln">7</span><span class="cl">              → Smoke test against production data
</span></span><span class="line"><span class="ln">8</span><span class="cl">              → Auto-rollback if metrics regress</span></span></code></pre></div><p>每一步都要 commit-able 的 manifest。在可審計 / 多人協作 / 有 SLA 承諾的場景、「手動 build 完 ssh 進 prod scp」這種 ad-hoc 流程會破壞 reproducibility、出問題時無法 revert 到具體 build；早期 prototype / 單人專案 / 一次性 demo 可接受 ad-hoc 流程、進入 production 前再改成 manifest-based。Manifest 是 reproducibility 跟 audit 的基礎。</p>
<h2 id="何時這篇會過時">何時這篇會過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Source / derived / external 三分類 framework</li>
<li>「commit manifest、不 commit content」核心原則</li>
<li><code>.gitignore</code> 通用模式</li>
<li>Reproducibility 來自 build instruction、不來自 commit derived</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>具體 manifest format（半年一個新 lockfile 格式）</li>
<li>Artifact registry 主流（OCI / Conda / npm 等都會演化）</li>
<li>LLM model registry（Hugging Face / Ollama 都會演化）</li>
</ul>
<p>新 lock 格式 / registry 出來時、回到三分類問：它解的是哪類產物？我能用它 commit manifest 不 commit content 嗎？通常答案 yes。</p>
<h2 id="跟其他章節的關係">跟其他章節的關係</h2>
<ul>
<li><a href="https://github.com/tarrragon/blog/blob/main/scripts/README.md">scripts/README.md</a>：本章原理的實作 reference</li>
<li><a href="/blog/llm/01-local-llm-services/hands-on/quickstart/" data-link-title="Hands-on Quickstart：clone repo 後跑通所有 demo" data-link-desc="4 步驟跑通 RAG / MCP / permission demo 的 setup 跟驗證指令、整合 hands-on 系列所有章節的 prerequisite">Hands-on quickstart</a>：跑通 demo 步驟、為什麼要 rebuild <code>index.pkl</code></li>
<li><a href="/blog/llm/04-applications/production-resource-planning/" data-link-title="4.9 Production 部署的資源評估原理" data-link-desc="從本地單 user 到 production multi-tenant：concurrent users、cost model、observability、SLA、capacity planning 的設計取捨">4.9 Production resource planning</a>：production runtime 視角、本章是 deployment 視角</li>
<li><a href="/blog/llm/00-foundations/privacy-data-flow/" data-link-title="0.7 隱私 / 資安的資料流原理" data-link-desc="從「位置」到「資料流」的思考升級：信任邊界、合約模型、零信任原則套用到 LLM 工作流">0.7 隱私資料流原理</a>：什麼可以離開機器、本章是「什麼可以進 git」的 sibling</li>
<li><a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a>：本章把 embedding index 判為 derived（不進 git、<code>ingest.py</code> 重建）、該章接手 vector index 存進 backend 之後的生命週期管理</li>
</ul>
]]></content:encoded></item></channel></rss>