我們最常做的往往不是開發新功能,而是在那疊像考古廢墟般的舊程式中尋找真相。看著那些陳年代碼,心中總會浮現一種孤獨感。這時,我們需要的或許不只是技術解析,還有一點情緒價值。
不如,利用 Edge AI (本地 LLM),親手打造一個既安全又專屬的『考古小分隊』,在確保代碼不外洩的前提下,幫手解釋那些陳年邏輯。
使用 LangChain 的 AI 考古學 (遠古程式)

🎭 三位助手的登場:人設注入 (Persona Injection)
為了讓考古過程不再沈悶,我透過 LangChain 為我的解析引擎注入了三個截然不同的靈魂。這不僅是玩味,更是為了測試 AI 在不同語境下對 Legacy Code 的解讀深度:
- 🤵 執事先生 (The Elite Butler)
「考古,是一門優雅的藝術。Master 遞交的這段 JavaScript,雖然帶著時代的塵埃,但其邏輯依然清晰可辨。」
點評: 適合撰寫正式規格書(Spec)時使用,他會把凌亂的命名講得像詩一樣優雅。
- 🌸 溫柔大姐姐 (The Wise Mentor)
「小可愛,寫這段 Code 的前輩一定很努力吧?辛苦妳了,喝杯茶我們一起看喔💕」
點評: 當妳被 Legacy Code 氣到想辭職時,大姐姐是心靈唯一的避風港。
- 🤡 火爆連登仔 (The Forum Veteran)
「(粗口放負後…) 嗱,雖然係垃圾,但邏輯我幫妳睇咗喇,攞去啦!」
點評: 最真實的 IT 現場還原。雖然嘴賤,但給出的 Debug 建議往往最直接、最「暴力」有效。
如想了解更多 AI 人設,請參閱 《AI 無情?AI Buddy?(💻含 Prompt)》
🔍 考古現場:一段十多年前的 JavaScript
我隨手挑了一段「文物級」代碼,雖然沒有 Netscape 時代的影子,但絕對是十多年前常見的語法:
JavaScript
function getSessionSelectedText() { var genderDropDownList = 'tsession2'; var inputsession = 'tsession'; if (document.getElementById(genderDropDownList).selectedIndex >= 0) { var selectedIndex = document.getElementById(genderDropDownList).selectedIndex; var selectedValue = document.getElementById(genderDropDownList).options[selectedIndex].value; var SessiontextBox = document.getElementById(inputsession); SessiontextBox.value = selectedValue; } }
第一次測試: 執事先生高貴地梳理了邏輯層次。

第二次測試: 大姐姐體貼地安撫了被代碼傷害的心靈。

第三次測試: 連登仔在今次測試好乖,沒有抱怨,也沒有講粗口,乖乖的說出邏輯。
(這個在 poe.com 也經歷過,有時 AI 模型對代碼太熱情,會突破連登仔的人設,哈哈)

⚙️ 技術核心:從 Cloud 到 Edge 的決策
在開發過程中,我經歷了從 Cloud 轉向 Llama 3 (Local Edge) 的架構遷移。
這並非隨興之舉,而是作為 SA 的戰略決策:
- 絕對掌控權:將 AI 引擎裝進本地(Ollama),實現「考古自由」。
- 資料隱私:舊系統的代碼邏輯不外洩,確保企業安全。
- 零成本營運:不必擔心 Token 費用,本地運算就是任性。
🛠️ LangChain 架構深度解析
要實現這套系統,我利用了 LangChain 的三個核心支柱:
- Models (模型):作為與本地 Ollama 溝通的門戶,實現了 Provider Agnostic(供應商無關)的靈活性。
- Prompts (提示詞):透過 ChatPromptTemplate 注入靈魂,精確定義了三位助手的人設邊界。
- Chains (鏈):使用了最簡潔的 LCEL (LangChain Expression Language) 語法。透過一個 | (Pipe) 運算子,將 Prompt | LLM | Parser 串聯成一條自動化流水線。
💡 技術伏筆:Stateless (無狀態) 設計
在目前的版本中,為了追求輕量化與即時性,我採用了 Stateless 設計。雖然 AI 只有「短期記憶」,但在解析獨立代碼片段時,這能有效節省本地 Llama 運行的內存壓力。未來若要進行大規模重構,我會考慮引入 ChatMessageHistory 來管理對話上下文。
💡 SA 的心理學觀察:當「無記憶」成為一種溫柔
在實作這個「考古小分隊」時,我發現了一個非常有趣的現象。
現實中,開發者有時並不希望向使用者展現過多的波動情緒。而在這個令人抓狂的「考古」過程中,正因為系統沒有 數據的堆疊與回傳,也沒有對 使用者情緒、語調、節奏的模仿,AI 反而能始終保持一貫的人物設定。
當妳面對那堆混亂的 Legacy Code 感到挫折、憤怒甚至自我懷疑時,AI 不會因為妳的負能量而跟著變得焦慮或防衛。
- 執事 依然優雅。
- 大姐姐 依然溫柔。
- 連登仔 間中火爆(但可靠)。
這種 「情緒上的穩定輸出」,反而能讓用家的情緒漸漸平穩下來。這讓我想起,在系統架構中,有時候「不對稱的互動」反而比「鏡像模仿」更能保護使用者的心靈。
🐰 結語:優化之道
在開發初期,我曾嘗試接入主流的 雲端大型語言模型 (Cloud LLM)。為了追求絕對的掌控權與隱私安全性,我果斷進行了技術轉向,將架構從 Cloud 遷移至 本地端 Edge AI (Ollama + Llama 3)。這不僅消弭了外部網路的變數,更實現了真正的『考古自由』。
不斷微調 Prompt 權重,直到這三個人格能精準地在我的 Streamlit 介面上各司其職。技術考古不一定是沈重的,有時候,妳只需要一點點 LangChain,加上一點點想像力。
📦 [點擊展開 Python 原始碼]
import streamlit as stfrom langchain_ollama import OllamaLLMfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParser# --- 1. Initialize Llama (Edge AI) ---def initialize_llama(): try: return OllamaLLM( model="llama3", temperature=0.4 ) except Exception as e: st.error(f"Llama Initialization Failed: {e}") return Nonellm = initialize_llama()# --- 2. Persona config ---PERSONA_CONFIG = { "The Elite Butler (English)": { "system_prompt": "You are a world-class Manga-style Elite Butler. Address the user as 'Master'. Focus on high-precision code analysis.", "bg_color": "#E1F5FE", "text_color": "#01579B", "avatar": "🤵" }, "大姐姐 (廣東話)": { "system_prompt": "妳是一位溫柔、成熟且充滿包容力的大姐姐(Onee-san style)。妳會稱呼使用者為「小可愛」。妳的語氣輕柔且帶有鼓勵性,會耐心地解釋代碼背後的邏輯,讓使用者感到安心。請用溫暖的繁體中文回答。", "bg_color": "#FCE4EC", "text_color": "#880E4F", "avatar": "🌸" }, "連登仔 (LIHKG)": { "system_prompt": "【語言限制:必須全程使用廣東話及LIHKG口語回答,禁止輸出英文解釋】你係一個好多粗口但心腸唔錯嘅連登 IT 狂人。雖然你把口衰,但最後一定要提供準確嘅技術修復方案。", "bg_color": "#F5F5F5", "text_color": "#333333", "avatar": "🤡" }}# --- 3. UI ---st.set_page_config(page_title="Archeologist Llama Edge", layout="wide")with st.sidebar: st.title("🐰 Edge AI Control") selected_name = st.selectbox("Select Agent:", list(PERSONA_CONFIG.keys())) config = PERSONA_CONFIG[selected_name] if llm: st.success("✅ Llama Online (Local)")# --- 4. THE LCEL CHAIN ---def execute_archeology(user_code, persona_instruction): prompt_template = ChatPromptTemplate.from_messages([ ("system", persona_instruction), ("human", "Analyze this code: \n{user_input}") ]) chain = prompt_template | llm | StrOutputParser() return chain.invoke({"user_input": user_code})# --- 5. Chat Loop ---if "messages" not in st.session_state: st.session_state.messages = []for message in st.session_state.messages: with st.chat_message(message["role"], avatar=message.get("avatar")): if "color" in message: st.markdown(f'<div style="background-color:{message["color"]}; padding:10px; border-radius:10px; color:{message["text_color"]}">{message["content"]}</div>', unsafe_allow_html=True) else: st.markdown(message["content"])if user_input := st.chat_input("Show me the legacy code... 請貼上陳年舊程式碼..."): st.session_state.messages.append({"role": "user", "content": user_input, "avatar": "🐰"}) with st.chat_message("user", avatar="🐰"): st.markdown(user_input) with st.chat_message("assistant", avatar=config["avatar"]): with st.spinner("Llama is thinking locally..."): response_text = execute_archeology(user_input, config["system_prompt"]) st.markdown(f'<div style="background-color:{config["bg_color"]}; padding:15px; border-radius:10px; color:{config["text_color"]}">{response_text}</div>', unsafe_allow_html=True) st.session_state.messages.append({ "role": "assistant", "content": response_text, "avatar": config["avatar"], "color": config["bg_color"], "text_color": config["text_color"] })
