[English Version]

我們最常做的往往不是開發新功能,而是在那疊像考古廢墟般的舊程式中尋找真相。看著那些陳年代碼,心中總會浮現一種孤獨感。這時,我們需要的或許不只是技術解析,還有一點情緒價值

不如,利用 Edge AI (本地 LLM),親手打造一個既安全又專屬的『考古小分隊』,在確保代碼不外洩的前提下,幫手解釋那些陳年邏輯。


使用 LangChain 的 AI 考古學 (遠古程式)

LangChain Ollama

🎭 三位助手的登場:人設注入 (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 的戰略決策

  1. 絕對掌控權:將 AI 引擎裝進本地(Ollama),實現「考古自由」。
  2. 資料隱私:舊系統的代碼邏輯不外洩,確保企業安全。
  3. 零成本營運:不必擔心 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 st
from langchain_ollama import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from 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 None
llm = 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"]
})

LangChain Ollama