前言

如果你想建一個有 AI 能力的後端 API — 比如客服機器人、文件問答系統、或是智慧助手 — LangChain 是目前最主流的框架。它幫你把 LLM、Prompt、記憶、工具呼叫這些東西封裝好,讓你可以用比較結構化的方式組裝 AI 應用。

老實說,LangChain 的學習曲線不算低,抽象層很多,文件也經常跟不上程式碼的更新速度。但一旦搞懂它的核心概念,開發效率確實會提升很多。這篇文章會帶你從 LangChain 的核心概念開始,一路做到一個完整的對話式 API,搭配 FastAPI 部署。

LangChain 核心概念

架構總覽

LangChain 核心元件:

[LLM/ChatModel] 最底層,負責文字生成 │ [PromptTemplate] 管理 prompt 的模板 │ [Chain] 把多個步驟串起來 │ [Agent] 有「決策能力」的 Chain,可以選擇用什麼工具 │ [Tool] Agent 可以呼叫的外部功能(搜尋、計算、API...) │ [Memory] 對話記憶,讓 AI 記住上下文

安裝

# 基本安裝
pip install langchain langchain-community langchain-core

# 搭配 OpenAI pip install langchain-openai

# 搭配 Ollama(本地 LLM) pip install langchain-ollama

# 搭配 FastAPI pip install fastapi uvicorn pydantic

# 完整安裝 pip install langchain langchain-community langchain-openai \ langchain-ollama fastapi uvicorn pydantic redis

從基礎開始

基本的 LLM 呼叫

from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, SystemMessage

# 初始化 LLM(使用本地 Ollama) llm = ChatOllama( model="llama3.1", temperature=0.7, base_url="http://localhost:11434", )

# 最簡單的呼叫 response = llm.invoke("用一句話解釋什麼是 Docker") print(response.content)

# 帶系統 prompt 的呼叫 messages = [ SystemMessage(content="你是一位資深的後端工程師,用繁體中文簡潔地回答問題。"), HumanMessage(content="Redis 的主要用途有哪些?"), ] response = llm.invoke(messages) print(response.content)

Prompt Template

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# 基本模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位 {role},擅長 {specialty}。用繁體中文回答。"), ("human", "{question}"), ])

# 使用模板 chain = prompt | llm response = chain.invoke({ "role": "後端工程師", "specialty": "分散式系統設計", "question": "設計一個高可用的訊息佇列系統要考慮什麼?" }) print(response.content)

Chain:串接多個步驟

from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate

# 範例:程式碼 review chain # Step 1: 分析程式碼 analyze_prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位程式碼審查專家。分析以下程式碼的潛在問題。"), ("human", "

{language}\n{code}\n“"),
])

# Step 2: 生成改善建議
suggest_prompt = ChatPromptTemplate.from_messages([
("system", "根據以下程式碼分析結果,提供具體的改善建議和修改後的程式碼。"),
("human", "原始分析:\n{analysis}\n\n請提供改善建議。"),
])

# 串接
analysis_chain = analyze_prompt | llm | StrOutputParser()
suggestion_chain = suggest_prompt | llm | StrOutputParser()

# 完整 chain
def code_review(language: str, code: str) -> str:
analysis = analysis_chain.invoke({
"language": language,
"code": code,
})
suggestion = suggestion_chain.invoke({
"analysis": analysis,
})
return suggestion

# 使用
result = code_review("python", """
def get_user(id):
conn = psycopg2.connect("dbname=mydb user=admin password=123")
cur = conn.cursor()
cur.execute(f"SELECT * FROM users WHERE id = {id}")
return cur.fetchone()
""")
print(result)
<pre><code>## Agent 和 Tool

Agent 是 LangChain 最強大的功能之一。它讓 LLM 自己決定要使用哪些工具來完成任務。</code></pre>python
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import subprocess
import httpx

# 定義工具
@tool
def run_python(code: str) -> str:
"""執行 Python 程式碼並回傳結果。用於需要計算或資料處理的場景。"""
try:
result = subprocess.run(
["python3", "-c", code],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0:
return result.stdout or "(執行成功,無輸出)"
else:
return f"錯誤:{result.stderr}"
except subprocess.TimeoutExpired:
return "錯誤:執行超時"

@tool
def search_docs(query: str) -> str:
"""搜尋內部技術文件。用於回答關於專案架構、API 規格等問題。"""
# 這裡簡化處理,實際可以接 RAG 系統
docs = {
"api": "我們的 API 使用 REST 架構,基本路徑是 /api/v1/",
"auth": "認證使用 JWT token,有效期限 24 小時",
"database": "主資料庫是 PostgreSQL 15,快取用 Redis 7",
}
results = []
for key, value in docs.items():
if key in query.lower():
results.append(value)
return "\n".join(results) if results else "找不到相關文件"

@tool
def check_service_status(service_name: str) -> str:
"""檢查服務的運行狀態。可以檢查 api、database、cache 等服務。"""
# 模擬檢查
statuses = {
"api": "running (response time: 45ms)",
"database": "running (connections: 23/100)",
"cache": "running (memory: 256MB/1GB)",
}
return statuses.get(
service_name.lower(),
f"未知服務: {service_name}"
)

# 建立 Agent
tools = [run_python, search_docs, check_service_status]

agent_prompt = ChatPromptTemplate.from_messages([
("system", """你是一位 DevOps 助手,可以幫忙查詢系統狀態、搜尋文件、
和執行簡單的計算。用繁體中文回答。
在回答之前,請先使用適當的工具獲取資訊。"""),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# 注意:tool calling agent 需要支援 tool calling 的模型
# 如果用 Ollama,確保模型支援(如 llama3.1)
agent = create_tool_calling_agent(llm, tools, agent_prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 印出思考過程
max_iterations=5,
)

# 使用
result = agent_executor.invoke({
"input": "檢查一下所有服務的狀態,然後告訴我資料庫的連線數是否正常"
})
print(result["output"])
<pre><code>## 對話記憶

<h3>基本記憶</code></pre>python</h3>
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# 記憶存儲(In-Memory)
store = {}

def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]

# 帶記憶的 chain
prompt_with_history = ChatPromptTemplate.from_messages([
("system", "你是一位友善的技術助手。用繁體中文回答。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])

chain = prompt_with_history | llm | StrOutputParser()

chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)

# 多輪對話
config = {"configurable": {"session_id": "user-123"}}

r1 = chain_with_history.invoke({"input": "我叫小明"}, config=config)
print(r1) # "你好小明!..."

r2 = chain_with_history.invoke({"input": "我叫什麼名字?"}, config=config)
print(r2) # "你叫小明..."
<pre><code>### Redis 持久化記憶</code></pre>python
from langchain_community.chat_message_histories import RedisChatMessageHistory

def get_redis_history(session_id: str):
return RedisChatMessageHistory(
session_id=session_id,
url="redis://localhost:6379/0",
ttl=3600, # 1 小時後過期
)

# 其他程式碼跟上面一樣,只是換了 history 的 backend
chain_with_redis = RunnableWithMessageHistory(
chain,
get_redis_history,
input_messages_key="input",
history_messages_key="history",
)
<pre><code>## 整合 FastAPI

這是最終的目標 — 把所有東西包成一個 REST API:</code></pre>python
# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from contextlib import asynccontextmanager

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# ─── 初始化 ───
store = {}

def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]

@asynccontextmanager
async def lifespan(app: FastAPI):
# 啟動時初始化
print("AI Chat API 已啟動")
yield
# 關閉時清理
print("AI Chat API 已關閉")

app = FastAPI(title="AI Chat API", lifespan=lifespan)

# ─── LLM 設定 ───
llm = ChatOllama(
model="llama3.1",
temperature=0.7,
base_url="http://localhost:11434",
)

prompt = ChatPromptTemplate.from_messages([
("system", """你是一位專業的技術助手。
- 用繁體中文回答
- 回答要簡潔但完整
- 如果不確定,請明確告知
- 提供程式碼範例時使用 markdown 格式"""),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])

chain = prompt | llm | StrOutputParser()

chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)

# ─── API Models ───
class ChatRequest(BaseModel):
message: str
session_id: str = "default"

class ChatResponse(BaseModel):
reply: str
session_id: str

class HistoryResponse(BaseModel):
session_id: str
messages: list[dict]

# ─── API Endpoints ───
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""對話端點"""
try:
config = {"configurable": {"session_id": request.session_id}}
reply = chain_with_history.invoke(
{"input": request.message},
config=config,
)
return ChatResponse(reply=reply, session_id=request.session_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

@app.get("/history/{session_id}", response_model=HistoryResponse)
async def get_history(session_id: str):
"""取得對話歷史"""
if session_id not in store:
return HistoryResponse(session_id=session_id, messages=[])

history = store[session_id]
messages = [
{"role": msg.type, "content": msg.content}
for msg in history.messages
]
return HistoryResponse(session_id=session_id, messages=messages)

@app.delete("/history/{session_id}")
async def clear_history(session_id: str):
"""清除對話歷史"""
if session_id in store:
del store[session_id]
return {"message": f"Session {session_id} cleared"}

@app.get("/health")
async def health():
"""健康檢查"""
return {"status": "ok", "model": "llama3.1"}
<pre><code></code></pre>bash
# 啟動 API
uvicorn app:app --host 0.0.0.0 --port 8000 --reload

# 測試
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "什麼是 Docker?", "session_id": "user-1"}'

# 繼續對話
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "它跟虛擬機有什麼差別?", "session_id": "user-1"}'

# 查看對話歷史
curl http://localhost:8000/history/user-1
<pre><code>### Streaming 支援</code></pre>python
from fastapi.responses import StreamingResponse
from langchain_core.output_parsers import StrOutputParser

@app.post("/chat/stream")
async def chat_stream(request: ChatRequest):
"""串流回應端點"""
config = {"configurable": {"session_id": request.session_id}}

async def generate():
async for chunk in chain_with_history.astream(
{"input": request.message},
config=config,
):
if chunk:
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"

return StreamingResponse(
generate(),
media_type="text/event-stream",
)
<pre><code></code></pre>javascript
// 前端接收串流
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: '解釋微服務架構', session_id: 'user-1' }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const text = decoder.decode(value);
const lines = text.split('\n').filter(l => l.startsWith('data: '));
for (const line of lines) {
const content = line.replace('data: ', '');
if (content === '[DONE]') break;
process.stdout.write(content);
}
}

小結

LangChain 的生態系很大,這篇文章只涵蓋了最核心的部分。但有了這些基礎,你已經能建立一個可用的對話式 API 了。

幾個實戰建議:

  1. 從簡單的 Chain 開始,不要一上來就用 Agent
  2. 記憶管理很重要,對話太長會超過 context window,需要做 summarization 或 sliding window
  3. 錯誤處理不能省,LLM 的回應不穩定,API 要能優雅地處理各種異常
  4. 監控和日誌,用 LangSmith 或自己的 logging 追蹤每次呼叫的 token 使用和回應品質

延伸閱讀:

  • LangChain 官方文件:https://python.langchain.com
  • LangSmith — LangChain 的監控平台
  • LangGraph — 更複雜的 Agent workflow
  • LangServe — LangChain 的 API 部署工具