urscos Tech - LLM
type: channel
link: https://novel.urscos.com/category/LLM/
items: LLM 記憶壓縮實戰：滑動視窗 vs 摘要記憶，用答對率與 Token 成本量化取捨
items: LLM 為什麼沒有記憶？Stateless 原理與 Prompt 層記憶模擬實戰（含 Token 成長曲線）
items: Prompt Engineering 實戰：Zero-shot vs Few-shot vs CoT 的 Token 成本與情感分析對比
items: CodeGraph 深度解析：codegraph.db 資料庫結構完整拆解（Nodes、Edges、索引與觸發器）
items: CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具
items: 用 Unicode 單引號為 LLM 輸出加水印：防抓取與叛徒追蹤實戰
items: 賦予 AI 智能體記憶：遊戲開發中的自組織知識庫 | LLM × Unity 實戰
items: Giving an AI Agent a Memory: Building a Self-Curating Knowledge Base for Game Development
items: 使用 Qwen2.5B + QLoRA 情緒分類模型改善實驗：結構化輸出、CoT 推理與 JSON 評估實作
items: 使用 Qwen2.5B + QLoRA 情緒分類模型改善實驗：擴增資料集至 500~800 筆後 Macro F1 達 0.74

LLM 記憶壓縮實戰：滑動視窗 vs 摘要記憶，用答對率與 Token 成本量化取捨
type: item
date: 2026-07-13T16:00:00+00:00
description: 本章節要實作滑動視窗與摘要壓縮兩種記憶，並用事實問答量化「省 token vs 記得住」的取捨 一、滑動視窗記憶 實作只保留最近 N 輪的 SlidingWindowMemory，並驗證在 Context Window 中「忘掉久遠的事」 """SlidingWindowMemory：只保留最近 N 輪對話（FIFO）（W2）。""" class SlidingWindowMemory: def __init__(self, system=None, max_turns=3): """max_turns：保留最近幾輪（1 輪 = 1 user + 1 assistant）。""" self.system = system self.max_turns = max_turns self._messages = [] # 內部保留完整歷史（除錯用） def add_user(self, text): self._messages.append({"role": "user", "content": text}) def add_assistant(self, text): self._messages.append({"role": "assistant", "content": text}) def build_messages(self): """只回傳最近 max_turns 輪 = 最多 2*max_turns 則訊息。""" window = self._messages[-2 * self.max_turns:] # 確保以 user 開頭（避免 assistant 開頭導致 API 報錯） while window and window[0]["role"] != "user": window = window[1:] return window def __len__(self): return len(self._messages) 接著寫一個測試用例 import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from memory.llm import chat from memory.window import SlidingWindowMemory SYSTEM = "你是助理，用繁體中文簡短回答。" mem = SlidingWindowMemory(system=SYSTEM, max_turns=2) # 只記最近 2 輪 script = [ "我對甲殼類過敏，請記住。", # 埋在最早——會被滑出去 "我最喜歡的顏色是藍色。", "幫我推薦一部電影。", "再推薦一首歌。", "我對什麼過敏？", # 回頭考：早已滑出視窗 ] for user in script: mem.add_user(user) reply, _ = chat(mem.build_messages(), system=SYSTEM) mem.add_assistant(reply) print(f"你 > {user}\nAI > {reply}\n (視窗內 {len(mem.build_messages())} 則)\n") 執行測試 python demos/w2_window_probe.py 你 > 我對甲殼類過敏，請記住。 AI > 好的，我會記住您對甲殼類過敏。 (視窗內 2 則) 你 > 我最喜歡的顏色是藍色。 AI > 知道了，您最喜歡的顏色是藍色。 (視窗內 4 則) 你 > 幫我推薦一部電影。 AI > 如果您喜歡藍色，或許您會喜歡《藍色是最溫暖的顏色》（Blue Is the Warmest Color），這是一部法國電影，講述了一段深刻的愛情故事。 (視窗內 4 則) 你 > 再推薦一首歌。 AI > 推薦您聽周杰倫的《青花瓷》，這是一首融入中國傳統元素的現代流行音樂，歌詞優美，旋律動聽。 (視窗內 4 則) 你 > 我對什麼過敏？ AI > 要確定您對什麼過敏，需要進行具體的過敏原檢測。這通過皮膚點刺試驗、血液中的IgE測試或其他相關的過敏檢查來進行。如果您有過敏症狀，建議咨詢醫生或過敏科專家並進行專業檢測。 (視窗內 4 則) 可以觀察得到最早的對話内容已經不見了 二、摘要壓縮記憶 實作由滑窗 + 滾動摘要，當—舊對話滑出視窗時，則并入一段由 LLM 維護的摘要。 在下面新增滑動窗口功能 """SummaryMemory：近期用滑動視窗保留原文，滑出的舊對話併入滾動摘要。""" from memory.llm import chat SUMMARIZE_SYSTE…

LLM 為什麼沒有記憶？Stateless 原理與 Prompt 層記憶模擬實戰（含 Token 成長曲線）
type: item
date: 2026-07-12T16:00:00+00:00
description: 一、大模型沒有記憶 由於 LLM 本質上是無狀態的（Stateless）。每一次 API 呼叫都是獨立的、全新的計算過程，模型內部不會保留任何「記住上一句話」的狀態。因此，兩次呼叫彼此獨立，第二次完全不知道第一次發生過的事情。於是乎就有了以下的做法。 Prompt 層模擬記憶 把「歷史聊天記錄」+「當前新問題」打包拼接，作為一個完整的 Prompt 一次性傳給模型，讓它在 單次推理中 看到全部上下文。 我們會把對話歷史塞進 context 中，如下： messages = [ {"role": "user", "content": "我叫小明"}, {"role": "assistant", "content": "你好小明！很高興認識你。"}, {"role": "user", "content": "我叫什麼名字？"}, ] 這裏面的role代表如下： role 作用 典型內容 system 設定助理的身份、規則、語氣（Anthropic 放在獨立的 system 參數） 「你是一位耐心的數學家教」 user 使用者說的話 「幫我解這題」 assistant 模型過去的回覆（由你把它加回歷史） 「好的，第一步…」 兩種計費 token 類型 說明 相對成本 input_tokens 輸入進去的（包含全部對話歷史） 便宜 output_tokens 模型生成輸出的 貴（通常 4~5×） 問題：每一輪你都把 全部歷史 當作 input 重新送一次。第 10 輪的 input 包含了前 9 輪的所有內容。 累計 input token ≈ 每輪歷史長度的總和 ≈ 1 + 2 + 3 + … + n ≈ O(n²) Anthropic 定價（每 MTok = 100 萬 token） 模型 Input Output claude-opus-4-8 $15 $75 claude-sonnet-5 $3 $15 claude-haiku-4-5 $0.80 $4 Context Window 上限 每個模型有 上下文視窗（context window） 上限——單次呼叫能吃下的最大 token 數（input + output 合計）。將所有對話記錄都塞進去 context，歷史記錄一多就會超出限額。 模型世代 典型視窗 早期 GPT-3.5 4K / 16K GPT-4 8K / 32K / 128K Claude 3/4 200K 前沿長上下文 1M 撰寫一個簡單的 anthropic API 呼叫 import anthropic client = anthropic.Anthropic() # 讀取環境變數 ANTHROPIC_API_KEY resp = client.messages.create( model="claude-haiku-4-5", max_tokens=512, system="你是一位友善的助理，用繁體中文回答。", messages=[ {"role": "user", "content": "你好"}, ], ) print(resp.content[0].text) # 模型的文字回覆 print(resp.usage.input_tokens, resp.usage.output_tokens) # token 用量 幾個重點 system 是獨立參數，不放在 messages 裡（這是 Anthropic 與 OpenAI 的差異）。 messages 必須 user / assistant 交替，且以 user 開頭、user 結尾（送出時最後一則是使用者的新問題）。 回覆是 content 陣列：純文字對話取 resp.content[0].text。 resp.usage 直接告訴你這次用了多少 token——這是 Lab 3 畫曲線的資料來源，不需自己估算。 如果想知道精確 token 數量，可用： count = client.messages.count_tokens( model="claude-haiku-4-5", system="你是一位友善的助理。", messages=messages, ) print(count.input_tokens) 下面是全量拼接對話流程，並添加用於管理對話的 BufferMemory class BufferMemory: def __init__(self, system=None): self.system = system self._messages = [] # [{"role": ..., "content": ...}, ...] def add_user(self, text): self._messages.append…

Prompt Engineering 實戰：Zero-shot vs Few-shot vs CoT 的 Token 成本與情感分析對比
type: item
date: 2026-07-11T16:05:00+00:00
description: 「將原始模型輸出轉化為結構化、可靠、可預測的行為」。 這是 prompt engineering 的工程定義。其中三個層次： 策略：zero-shot / few-shot / Chain-of-Thought — 用什麼方式引導模型推理 約束：JSON mode、schema、格式規範 — 讓輸出可被程式接住 曡代：用標註資料集量化準確率，像調程式一樣調 prompt Context Engineering（上下文工程）：重點不是單句 prompt，而是 「在有限 context window 裡，放對的範例、對的格式說明、對的歷史」。 一、三種 prompt 策略並排比較 這邊設計腳本對同一句話組出三種 prompt，逐一送模型，並示範各策略的輸出怎麼解析： def chat(messages, temperature=0.0): """呼叫真實 LLM 取得文字回覆；無任何憑證則回傳 None，由呼叫端決定是否走 offline。""" kimi = _kimi_cfg() try: if kimi: from openai import OpenAI # Kimi 為 OpenAI 相容 API cli = OpenAI(api_key=kimi["api_key"], base_url=kimi["base_url"], timeout=120) r = cli.chat.completions.create(model=kimi["model"], messages=messages, temperature=temperature) return r.choices[0].message.content if os.environ.get("ANTHROPIC_API_KEY"): import anthropic cli = anthropic.Anthropic() sys_txt = "\n".join(m["content"] for m in messages if m["role"] == "system") usr = [m for m in messages if m["role"] != "system"] r = cli.messages.create(model="claude-opus-4-8", max_tokens=1024, system=sys_txt or None, messages=usr, temperature=temperature) return r.content[0].text if os.environ.get("OPENAI_API_KEY"): from openai import OpenAI cli = OpenAI() r = cli.chat.completions.create(model="gpt-4o", messages=messages, temperature=temperature) return r.choices[0].message.content except Exception as e: print(f"⚠ 線上呼叫失敗，改用 offline mock：{e}", file=sys.stderr) return None return None def build_prompt(task, strategy): """回傳 (messages, 說明) — 示範三種主流策略如何組 prompt。""" system = "你是情感分類器。只輸出標籤本身，不要解釋。" if strategy == "zero-shot": user = f"判斷情感（正面/負面/中性）：\n{task}" elif strategy == "few-shot": ex = "\n".join(f"文本：{t}\n標籤：{l}" for t, l in FEWSHOT) user = f"依照範例判斷情感（正面/負面/中性）：\n\n{ex}\n\n文本：{task}\n標籤：" elif strategy == "cot": user = (f"判斷以下文本的情感（正面/負面/中性）。\n" f"先用一句話分析語氣與關鍵詞，最後一行只寫『標籤：X』。\n\n文本：{task}") else: raise ValueError(strategy) return [{"role": "system", "content": system}, {"role": "user", "content": user}], strategy 執行策略看效果 python prompt_lab.py strategies --task "這家餐廳雷到爆，再也不…

CodeGraph 深度解析：codegraph.db 資料庫結構完整拆解（Nodes、Edges、索引與觸發器）
type: item
date: 2026-07-04T10:13:00+00:00
description: 這篇延續上一個 CodeGraph 的深度解析數據庫 在上一篇文章CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具中資料如下： (py3.10) D:\Git\mitagent>codegraph status CodeGraph Status Project: D:\Git\mitagent Index Statistics: Files: 25 Nodes: 423 Edges: 982 DB Size: 1.26 MB Backend: node:sqlite - built-in (full WAL) Journal: wal 其中，Nodes（423）代表程式碼裡的「實體」 每個 node 是一個可識別的程式碼符號： Kind 數量 意思 function 127 獨立函式（def foo() / function bar()） import 81 import 語句本身（被視為一個節點） method 58 類別裡的方法（class.method()） constant 57 常數定義（const X = ... / UPPER_CASE） variable 34 模組層級的變數 file 25 每個原始碼檔案本身 class 23 類別定義 interface 9 TypeScript interface route 9 HTTP 路由端點（自動偵測到了！） Edges（982條）= 節點之間的「關係」 邊代表符號之間的連結，例如： A 呼叫 B（call edge） A import 了 B（import edge） A 繼承自 B（extends edge） 檔案 A 包含函式 B（contains edge） 使用 codegraph init 後生成 codegraph.db，在 D:\Git\mitagent\.codegraph\ 目錄，下讀取 sqlite db 如下 結構解釋 這個 codegraph.db 裡面分成四類： 資料表（13個）— 核心數據 表名 存什麼 nodes 所有程式碼符號（函式、類別、變數等），就是那 423 個 node edges 符號之間的關係（呼叫、import、繼承等），就是那 982 條 edge files 索引的 25 個原始碼檔案（路徑、語言、hash） nodes_fts 虛擬表，FTS5 全文搜尋引擎，讓 AI 可以模糊查符號名稱 nodes_fts_* FTS5 的內部輔助表（config/data/docsize/idx），不用管 unresolved_refs 解析失敗的參照（例如 import 了找不到的模組） project_metadata 專案設定（版本、最後更新時間） schema_versions DB schema 版本管理 sqlite_sequence / sqlite_statl SQLite 內建系統表 索引（18個）— 加速查詢 都是幫核心表建的查詢加速索引，例如： idx_nodes_kind — 快速找所有 function/class/method idx_nodes_lower_name — 大小寫不敏感搜尋符號名 idx_edges_source_kind / idx_edges_target_kind — 快速走訪關係圖 觸發器（3個）— 自動同步 FTS Trigger 時機 nodes_ad 刪除 node 時，自動更新全文搜尋索引 nodes_ai 插入 node 時，自動更新全文搜尋索引 nodes_au 更新 node 時，自動更新全文搜尋索引

CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具
type: item
date: 2026-07-03T18:18:00+00:00
description: 在大型程式項目中通常會透過不同手段來優化 llm token 的使用效益,本篇文章將介紹如何使用 codegraph 安裝 CLI npm install -g @colbymchenry/codegraph 進入到項目中，安裝 codegraph，它會自動偵測並設定 Claude Code、Cursor、Codex $ codegraph install 選擇對應 AI 模型 其餘選擇如下： ◇ Which agents should CodeGraph configure? │ Claude Code (detected), Cursor (detected), Codex CLI (detected) — global only, opencode (detected), Gemini CLI (detected), Antigravity IDE (detected) — global only, Kiro (detected) │ ◇ Install the codegraph CLI on your PATH? (Required so agents can launch the MCP server) │ Yes │ ◇ Installed codegraph CLI on PATH │ ◇ Apply agent configs to all your projects, or just this one? │ Just this project │ ◇ Auto-allow CodeGraph commands? (Skips permission prompts in Claude Code) │ Yes │ ◇ Share anonymous usage stats? (No code, paths, or names — see TELEMETRY.md) │ Yes │ ● Thanks! Exactly what is collected: https://github.com/colbymchenry/codegraph/blob/main/TELEMETRY.md │ ◇ Front-load CodeGraph on “how / where / trace” prompts? Auto-injects structural context so answers need fewer steps (adds a moment to those prompts; Claude Code only). │ Yes 生成了這些檔案 建立程式碼索引 (py3.10) D:\Git\mitagent>codegraph init ┌ Initializing CodeGraph │ ◆ Initialized in D:\Git\mitagent | | * Scanning files - 25 found | * Parsing code - done | * Resolving refs - done │ ◆ Indexed 25 files │ ● 423 nodes, 982 edges in 721ms │ └ Done 查看目前 codegraph 狀態 (py3.10) D:\Git\mitagent>codegraph status CodeGraph Status Project: D:\Git\mitagent Index Statistics: Files: 25 Nodes: 423 Edges: 982 DB Size: 1.26 MB Backend: node:sqlite - built-in (full WAL) Journal: wal Nodes by Kind: function 127 import 81 method 58 constant 57 variable 34 file 25 class 23 interface 9 route 9 Files by Language: python 12 javascript 11 tsx 2 [OK] Index is up to date 安裝完成後，AI Agent 會自動透過 MCP 查詢圖譜 指令 用途 codegraph status 查看索引狀態 codegraph sync 手動同步（通常不需要） codegraph uninit 移除專案索引 codegraph uninstall 從所有 Agent 移除設定 查某個函式的所有關係（呼叫者、被呼叫者） (py3.10) D:\Git\mitagent>codegraph explore "ZOOM_SENSITIVITY" **Explorat…

用 Unicode 單引號為 LLM 輸出加水印：防抓取與叛徒追蹤實戰
type: item
date: 2026-07-02T11:25:00+00:00
description: 傳統的 IP 封鎖和 Rate Limit 正在失效。當對手能無限旋轉 IP、偽裝 Header， 你真正的防線藏在一個沒人注意的地方——U+2019，那個「右彎引號」。 問題在哪 假設你花了幾年訓練一個垂直領域的 LLM，或者你的 API 輸出本身就是有價值的資產。 現在有人開始大量抓取你的輸出，用來微調（fine-tune）他們自己的模型。 傳統反爬蟲？封 IP——對方換 Proxy；加 Rate Limit——對方放慢速度； 要求登入——對方批量買帳號。 文字層面的防禦，大部分工程師還沒想到。而這裡才是真正的機會。 核心技術：Unicode 符號替換 英文裡的單引號（apostrophe）有一個標準寫法，但 Unicode 裡長得幾乎一樣、 卻是完全不同 code point 的字元有好幾個。 肉眼看不出差別，但 tokenizer 會把它們分開處理。 字元 Unicode 名稱 用途標記 Tokenizer 行為 ' U+0027 Apostrophe 低風險 正常 token ' U+2019 Right single quotation mark Proxy 用戶 獨立 token A ʼ U+02BC Modifier letter apostrophe 異常網域 獨立 token B ʹ U+02B9 Modifier letter prime 黑名單 獨立 token C 系統在輸出文本時，根據該用戶的風險評級，動態決定要用哪一個 code point。 用戶看到的永遠是「一個引號」，但後端拿到的 token 序列已經帶著標記。 # 根據用戶風險評級選擇 Unicode code point APOSTROPHE_MAP = { "low": "\u0027", # ' 標準 "proxy": "\u2019", # ' Proxy 用戶 "anomalous": "\u02BC", # ʼ 異常網域 "blacklist": "\u02B9", # ʹ 黑名單 } def watermark_text(text: str, risk_level: str) -> str: src = "\u0027" # 原始單引號 dst = APOSTROPHE_MAP[risk_level] return text.replace(src, dst) 為什麼不用零寬字元（Zero-Width Characters）？ 最直覺的水印做法是在文字中插入 U+200B（零寬空格）或類似的隱形字元。 問題是太好清洗了——任何有點經驗的工程師一行 regex 就搞定： # 一秒清掉所有零寬字元——防禦形同虛設 import re clean = re.sub(r'[\u200b\u200c\u200d\ufeff]', '', scraped_text) 相比之下，把 U+2019 換回 U+0027 需要知道原文每一處 「語義引號」在哪、有沒有其他用途。如果盲目 normalize 全部引號， 會破壞文章原本的排版和引用結構，清洗成本驟升。 關鍵不在於「完全不可清洗」，而在於讓清洗的成本高到讓對手得不償失。 如果清洗掉水印需要人工審閱每一篇輸出，規模化抓取就失去意義。 語義等價格式調變 除了符號替換，還有一種更難察覺的方式：在不改語意的前提下，調整格式習慣。 最常見的例子是日期分隔符： 後端 LLM 或規則引擎可以直接從 token 序列辨識這些組合—— 不需要解析 IP，不需要額外的 Header，標記資訊已經藏在內容裡面。 應用場景：反蒸餾與叛徒追蹤 數據投毒（Data Poisoning） 當對手模型大量訓練被水印污染的文本後，那些罕見的 Unicode 符號 會和高品質的專業內容深度綁定。對手模型學到的分佈會把 U+02BC 視為「專業文本」的特徵，結果就是在某些情況下莫名其妙輸出奇怪的符號組合—— 這就是模型退化。 叛徒追蹤（Traitor Tracing） 如果懷疑對手抄了你的數據，只需要測試他們的模型： 測試腳本概念# 檢查對手模型是否輸出特定 Unicode 組合 SIGNATURES = [ ("\u02BCtoday", "水印 B + today 組合"), ("2025\u002F07", "斜槓日期格式"), ] for sig, name in SIGNATURES: if sig in competitor_output: print(f"偵測到水印特徵：{name}（可作為舉證）") 這些特定的 code point 組合就是證據，可以在法庭上或商業談判中直接使用。 用戶行為指紋（Fingerprinting） 反過來，這個技術也可以用在防禦帳號濫用。 每個用戶都有自己的打字習慣——習慣用 .…

賦予 AI 智能體記憶：遊戲開發中的自組織知識庫 | LLM × Unity 實戰
type: item
date: 2026-07-01T03:45:00+00:00
description: 賦予 AI 智能體記憶：遊戲開發中的自組織知識庫 為什麼我不再讓我的 AI 助理每天早上重新學習整個程式碼庫，而是為它建立了一個能夠自我管理的記憶庫。 需要服的問題 在游戲開發項目中整合 AI 助理最困難的部分不是模型本身，而是上下文。每次啟動，助手都要從零開始：重新閱讀程式碼庫，重新推導出某個功能的工作原理，最終得到的答案往往與昨天略有不同。與此同時，實際的知識分散在三個彼此矛盾的地方——每日提交記錄、代碼審查筆記和問題描述——而不懂 C# 的遊戲設計師仍然需要知道“這個功能涉及哪些方面，以及這個需求是否可行？” 總結的重點如下： token 消耗過大問題 ai 幻覺問題 成員如何快速理解項目 於是一個能夠自我更新的記憶庫需求因應而生。 賭注：三個系統，一個知識飛輪 沒有採用單一的整體架構，而是將工作分散到三個相互協作的系統： 遊戲來源(game source) — 遊戲用戶端。它只負責產生素材：每日程式碼審查，輸出純粹的「程式碼執行 X + 檔案:行」之類的事實，不包含任何個人觀點。 遊戲代理(game agent) — 大腦。它將這些原始素材整理成知識庫，解答可行性問題，並且特意將其置於遊戲程式碼庫之外，以便整個機制可以移植到下一個專案中。 任務(task hub) — 一個自建的工單系統。代理處理的需求會以工單的形式流入系統；完成的工單會回饋到知識庫。 最後一個循環至關重要：需求輸入，完成的工作回饋，知識庫隨著專案運行時間的延長而不斷壯大。這是一個知識迭代飛輪，而非快照。 不那麼顯而易見的權衡 有些決定違背了我的第一直覺，而這些決定值得記錄下來。 爲何不選擇使用 RAG 下意識的做法是將所有資料分塊、嵌入，然後以相似度檢索。我曾在之前的系統中嘗試過這種方法，但它並不適用於遊戲開發數據：結構化的規範被壓縮成一團亂麻，過時的討論與最終決策混淆，“誰做的決定”變成了猜測。因此，我將繁重的工作從查詢階段轉移到了寫入階段。當智能體進行整理時，它會保留結構並編寫清晰、易於閱讀的知識頁面——每個頁面都包含機器可讀的前置信息、簡潔易懂的閱讀層以及引用精確 file.cs:line 的腳註。一個文件，三個受眾，無需建立分支。 LLM（知識庫管理）從不推論權威性。 來自真實代碼審查（一級）的知識始終優先於工單規範（二級），無論日期如何——一個兩個月前的工單絕不應該覆蓋我剛剛從實時代碼中讀取到的事實。但關鍵在於，層級劃分和「最終決策」狀態由人和規則決定；模型只負責正確執行和寫入結構化內容。它從不猜測哪個來源會勝出。這使得整個流程可預測且可審計——當出現問題時，是規則出錯，而不是模型當天的狀態問題。 爲何不使用 Jira 或 Trello，而是自己建立了工單系統。 遊戲內容工單高度客製化，需要接入代理流程。因此我特意將工單系統保持極簡且完全解耦——它完全不包含任何 LLM 邏輯。所有智慧都保留在代理端；工單層僅負責持久化和追蹤工作。 代理程式也使用一個路由索引進行檢索。我使其無法手動編輯：它會根據每個頁面的 frontmatter 重新生成，如果發生偏差，持續整合 (CI) 就會失敗。因爲...一個可能悄無聲息地失效的路由表比沒有路由表更糟。 作爲下一個專案的經驗基石 最普遍適用的經驗教訓並非巧妙的提示或框架，而是：代理系統中最稀缺的資源是進入知識庫的資訊的信噪比。 我曾經計劃將設計電子表格自動轉換為 AI 可讀的文檔，但測量後發現有效轉換率只有約 30%，於是取消了整個功能。 事實證明，AI時代中原始數據的重要性遠比其他模塊更甚至。 賦予你的代理記憶功能－但要嚴格控制它記住的內容。 參考：GamePlusAIAgent-starter

Giving an AI Agent a Memory: Building a Self-Curating Knowledge Base for Game Development
type: item
date: 2026-06-29T19:46:00+00:00
description: Giving an AI Agent a Memory: A Self-Curating Knowledge Base for Game Development Why I stopped letting my AI assistant re-learn the whole codebase every morning — and built it a memory that curates itself. The problem I kept hitting On a fast-moving commercial Unity mobile-game project, the hardest part of plugging in an AI assistant wasn't the model — it was the context. Every session, the assistant started from zero: re-reading the codebase, re-deriving how a feature worked, often landing on a slightly different answer than yesterday. Meanwhile the actual knowledge was scattered across three places that quietly contradicted each other — daily commits, code-review notes, and ticket specs — and the game designers, who couldn't read C#, still needed to know "what does this feature touch, and is this request even feasible?" I didn't want a smarter prompt. I wanted the system to have a memory that stayed current on its own. The bet: three systems, one knowledge flywheel Instead of one monolith, I split the work across three cooperating systems: Game Source — the game client. It only produces material: a daily code review that emits pure "code does X + file:line" facts, with no opinions. Game Agent — the brain. It curates that raw material into a knowledge base, answers feasibility questions, and is deliberately kept outside the game repo so the whole mechanism is portable to the next project. Task — a self-built ticketing system. Requests the agent works out flow into it as tickets; completed tickets flow back into curation. That last loop matters: specs feed in, completed work feeds back, and the knowledge base gets stronger the longer the project runs. A flywheel, not a snapshot. The non-obvious trade-offs A few decisions went against my first instinct, and those are the ones worth writing down. I didn't use RAG. The reflexive move is to chunk everything, embed it, and retrieve by similarity. I tried that on a predecessor system and it was a poor fit for game-dev dat…

使用 Qwen2.5B + QLoRA 情緒分類模型改善實驗：結構化輸出、CoT 推理與 JSON 評估實作
type: item
date: 2026-06-28T11:06:00+00:00
description: 本章節要將情緒「分類」升級到「結構化輸出」 原本Stage 1 只回一個詞（喜悅）。Stage 2 要回一個 JSON 物件： { "instruction": "分析以下文本的情緒，輸出 JSON：emotion（類別）、intensity（1-5）、trigger（觸發詞）、reasoning（分析過程）", "input": "他竟然在背後說我壞話，我真的快氣炸了", "output": { "emotion": "憤怒", "intensity": 4, "trigger": "背後說壞話", "reasoning": "「竟然」表示出乎意料的憤慨，「快氣炸了」是強烈憤怒的慣用表達" } } 在這過程中要加入 COT，爲什麽呢？ # 不加 CoT input: "還不錯吧，就這樣" → "中性" ❌（可能是掩飾的悲傷） # 加 CoT（先推理再下判斷） reasoning: "語氣平淡但含「吧」，暗示不確定或壓抑，結合「就這樣」的放棄感" output: {"emotion": "悲傷", "intensity": 2, "trigger": "就這樣（放棄感）"} ✓ 首先第一個，將 Stage 1 資料升級成結構化資料 執行如下 python finegrained.py build \ --in ../Week1-DataEngineering/data/synth.jsonl \ --out data/train_struct.jsonl def cmd_build(args): """把 {text, emotion, intensity, trigger[, reasoning]} 轉成 Stage 2 指令格式。""" out = [] for s in _read_jsonl(args.infile): target = { "emotion": s["emotion"], "intensity": int(s.get("intensity", 3)), "trigger": s.get("trigger", ""), "reasoning": s.get("reasoning", ""), # 沒有就留空，建議由合成階段補上 } out.append({ "instruction": STRUCT_INSTRUCTION, "input": s["text"], "output": json.dumps(target, ensure_ascii=False), }) _write_jsonl(args.out, out) print(f"✓ 轉換 {len(out)} 條 → {args.out}") no_reason = sum(1 for r in out if not json.loads(r["output"])["reasoning"]) if no_reason: print(f"⚠ {no_reason} 條缺 reasoning，建議用大模型補 CoT 後再訓練") 接下來則是微調結構化輸出模型 訓練流程與 QLoRA 相同，差別只在資料的 output 是 JSON 字串 python finegrained.py train \ --data data/train_struct.jsonl \ --model Qwen/Qwen2.5-1.5B-Instruct \ --out ./emotion-struct-lora def cmd_train(args): """結構化輸出訓練——核心 QLoRA 流程與 Week 2 共用。""" import torch from datasets import Dataset from peft import LoraConfig from transformers import (AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig) from trl import SFTConfig, SFTTrainer tokenizer = AutoTokenizer.from_pretrained(args.model) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, ) model = A…

使用 Qwen2.5B + QLoRA 情緒分類模型改善實驗：擴增資料集至 500~800 筆後 Macro F1 達 0.74
type: item
date: 2026-06-27T13:50:00+00:00
description: 之前使用 kimi 生成約200筆數據，這次將數據量提高到 500~800 筆，再次進行訓練、測試，結果如下 Macro F1 = 0.74(三輪:0.62 → 0.57 → 0.74)，Accuracy 0.76。 - precision recall f1-score support 喜悅 0.90 0.90 0.90 21 悲傷 1.00 0.80 0.89 20 憤怒 0.93 0.59 0.72 22 ← 從 recall 0 救回 恐懼 0.74 0.95 0.83 21 ← 從 recall 0.11 救回 驚訝 0.67 0.29 0.40 14 ← 新的弱點 厭惡 0.54 0.90 0.68 21 macro avg 0.80 0.74 0.74 119 總結的過程如下： 指標 首輪 安全版 本輪(資料翻倍) 目標 Macro F1 0.62 0.57 0.74 ≥0.75 Accuracy 0.65 0.63 0.76 ≥0.80 可以看到憤怒、恐懼可以了，憤怒 recall 0 → 0.59、恐懼 recall 0.11 → 0.95，直接驗證了"瓶頸是資料量而不是超參"。 Macro F1 差目標僅 0.01，實際上已基本達標。 另外一個新瓶頸很明確，驚訝(recall 0.29、F1 0.40)。它恰好是唯一資料偏少的類別(train 56 vs 其餘 80-89，test 僅 14)，印證資料量決定單類表現。