iT邦幫忙

0

LangChain 入門教學(地端 Ollama 版)

  • 分享至 

  • xImage
  •  

用 8 支可直接執行的 Python 腳本,從呼叫模型一路學到 Agent 與 RAG,全程只用地端 Ollama,不需雲端 API Key。

準備

需要一台跑 Ollama 的主機,裝好對話模型 qwen3.8:27b 與向量模型 bge-m3;本機需 Python 3.11 以上。

pip install langchain langchain-ollama langgraph pypdf
ollama pull qwen3.8:27b
ollama pull bge-m3
課 檔案 主題 重點
0 config.py 共用設定 模型只在這裡設定一次
1 01_hello.py invoke / stream 跟模型講話的兩種方式
2 02_chain.py Prompt 模板 + LCEL 用 | 把步驟串成管線
3 03_structured.py 結構化輸出 讓模型直接回 Python 物件
4 04_tools.py Tool calling 模型決定要呼叫哪個函式
5 05_agent.py Agent 自動跑「呼叫工具、看結果、再決定」
6 06_memory.py 對話記憶 多輪對話記得前文
7 07_rag_basic.py RAG 原理 先找資料再回答
8 08_rag_docs.py 文件 RAG 拿真實 PDF 做問答

每課都可直接執行:python 01_hello.py。中文亂碼時先設環境變數 PYTHONIOENCODING=utf-8。

第 0 課:共用設定 config.py

from langchain_ollama import ChatOllama, OllamaEmbeddings

OLLAMA_URL = "http://<你的 Ollama 主機>:11434"
MODEL = "qwen3.8:27b"
EMBED_MODEL = "bge-m3"

def get_llm(reasoning: bool = False, temperature: float = 0.3) -> ChatOllama:
    return ChatOllama(base_url=OLLAMA_URL, model=MODEL,
                      temperature=temperature, reasoning=reasoning, keep_alive="30m")

def get_embeddings() -> OllamaEmbeddings:
    return OllamaEmbeddings(base_url=OLLAMA_URL, model=EMBED_MODEL)
  • ChatOllama:換成 ChatOpenAI、ChatAnthropic 等只需改這一行,後面各課不用動,這是 LangChain 最大的好處。
  • temperature:0 穩定、1 有創意。查資料與抽取用低值,寫文案用高值。
  • reasoning:qwen3.8 是會思考的模型。False 直接回答較快(本教學預設);True 先推理再回答,較準但慢數倍,思考內容在 msg.additional_kwargs["reasoning_content"]。
  • keep_alive="30m":模型常駐 GPU 30 分鐘,第一次呼叫要載入模型會較慢。

第 1 課:invoke 與 stream

invoke 等全部生完才回傳,適合程式處理結果;stream 邊生成邊吐字,適合聊天介面。

from langchain_core.messages import HumanMessage, SystemMessage
from config import get_llm

llm = get_llm()
msg = llm.invoke([
    SystemMessage("你是簡潔的助理,用繁體中文回答。"),
    HumanMessage("用一句話解釋什麼是 LangChain。"),
])
print(msg.content)          # 回答文字
print(msg.usage_metadata)   # token 用量

for chunk in llm.stream("列出三個學 LangChain 的重點"):
    print(chunk.content, end="", flush=True)
類別 角色 用途
SystemMessage system 設定人設、規則、輸出語言
HumanMessage user 使用者說的話
AIMessage assistant 模型的回答,invoke 回傳的就是它

只傳一個字串也可以(llm.invoke("你好")),會自動包成 HumanMessage。

練習

  1. 把 system 改成「你是海盜,說話要帶『啊哈』」,看語氣變化。
  2. 改用 get_llm(reasoning=True),印出 msg.additional_kwargs 看思考過程,並比較速度。

第 2 課:Prompt 模板與 LCEL 管線

| 是 LangChain 的核心語法:把上一步的輸出交給下一步當輸入。

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

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是 {role},用繁體中文、{style} 的語氣回答。"),
    ("human", "{question}"),
])
chain = prompt | get_llm() | StrOutputParser()

chain.invoke({"role": "資深 MIS 工程師", "style": "口語", "question": "VLAN 是什麼?"})

資料流向:dict → prompt(訊息串列)→ llm(AIMessage)→ StrOutputParser(純字串)。

每個元件都是 Runnable,共用同一組方法:

方法 用途
.invoke(x) 跑一次
.stream(x) 串流輸出
.batch([x1, x2]) 一次跑多組,平行送出
.ainvoke(x) 非同步版本,搭配 FastAPI 等

用模板而不用 f-string 的好處:少給變數會直接報錯;模板可重複使用、單獨測試,也能存成檔案讓非工程師修改。

練習

  1. 在管線最後接 | (lambda s: s[:100]),只取前 100 字(一般函式也能串)。
  2. 寫一條翻譯 chain:輸入 {text, lang},輸出翻譯結果。

第 3 課:結構化輸出

實務上最常用的功能:讓模型直接回傳 Pydantic 物件,不用自己解析文字。

from pydantic import BaseModel, Field

class Applicant(BaseModel):
    name: str | None = Field(description="應徵者姓名")
    age: int | None = Field(description="年齡")
    skills: list[str] = Field(description="技能清單")
    summary: str = Field(description="一句話摘要")

llm = get_llm().with_structured_output(Applicant)
result = llm.invoke(f"從下列履歷抽出資料:\n{text}")
result.skills   # ['Windows Server', 'AD', 'VMware', 'C#', 'SQL']

原理:Pydantic 類別轉成 JSON Schema 交給 Ollama,Ollama 強制模型輸出符合格式的 JSON,再轉回物件。

  • Field(description=...) 很重要,模型靠描述理解欄位意思。
  • 可能找不到的欄位用 str | None,否則模型會硬編一個值。
  • 限定選項用 Literal:level: Literal["高", "中", "低"]。

常見應用:履歷抽取、發票或報價單辨識、客訴分類、把自然語言轉成查詢條件。

練習

  1. 加欄位 fit: Literal["適合", "待評估", "不適合"],在 prompt 附上職缺需求,讓模型順便評估。
  2. 改做客訴分類:抽出 product、issue_type、urgency。

第 4 課:Tool calling 與手寫工具迴圈

模型只決定要呼叫哪個函式、帶什麼參數;函式由你的程式執行,所以工具裡可以查資料庫或內部系統。

from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool

@tool
def get_stock(part_no: str) -> int:
    """查詢料號的庫存數量。"""      # docstring 就是給模型看的說明,必寫
    return {"A001": 120, "B205": 0}.get(part_no, -1)

llm = get_llm().bind_tools([get_stock, multiply])
messages = [HumanMessage("A001 庫存多少?每個 35 元總價多少?")]

while True:
    ai = llm.invoke(messages)
    messages.append(ai)
    if not ai.tool_calls:          # 不再要求工具 → 結束
        break
    for call in ai.tool_calls:
        output = tools[call["name"]].invoke(call["args"])
        messages.append(ToolMessage(str(output), tool_call_id=call["id"]))

實際執行的流程:

  1. 模型要求呼叫 get_stock(part_no="A001")。
  2. 程式執行,得到 120,包成 ToolMessage 傳回。
  3. 模型要求呼叫 multiply(a=120, b=35)。
  4. 程式執行,得到 4200 傳回。
  5. 模型不再要求工具,回答「總價值 4,200 元」。
  • @tool 從函式名稱、型別標註、docstring 自動產生工具說明,三者都要寫清楚。
  • LLM 心算容易出錯,計算交給工具。

第 5 課:create_agent

create_agent 幫你跑完第 4 課的 while 迴圈。

from langchain.agents import create_agent

agent = create_agent(
    model=get_llm(),
    tools=[get_stock, get_price],
    system_prompt="你是倉管助理,用繁體中文回答,需要資料時一定要呼叫工具,不要猜。",
)
result = agent.invoke({"messages": [{"role": "user", "content": "A001、B205、C330 哪個缺貨?"}]})

for m in result["messages"]:
    m.pretty_print()   # 問題 → 工具呼叫 → 工具結果 → 回答

實測時模型對 3 個料號各查庫存與單價,再整理成表格並指出缺貨料號。

手寫迴圈(第 4 課) create_agent(第 5 課)
程式碼量 多 少
控制度 完全掌控每一步 交給框架
進階功能 自己寫 內建記憶、middleware、串流
適合 學原理、特殊流程 大多數實務情境
  • system prompt 明確寫「一定要呼叫工具,不要猜」,可大幅減少亂編數字。
  • create_agent 底層是 LangGraph;需要分支、人工審核、平行處理時再直接學 LangGraph。

第 6 課:多輪對話記憶

LLM 本身沒有記憶;所謂記憶,是把之前的對話一起送進去。

from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(model=get_llm(), tools=[],
                     system_prompt="...", checkpointer=InMemorySaver())

cfg = {"configurable": {"thread_id": "demo-1"}}
agent.invoke({"messages": [{"role": "user", "content": "我叫小明"}]}, cfg)
agent.invoke({"messages": [{"role": "user", "content": "我叫什麼?"}]}, cfg)  # → 小明
  • checkpointer 保存對話狀態。InMemorySaver 存在記憶體,程式關掉就消失;正式系統換 SQLite 或 Postgres 版本。
  • thread_id 區分不同對話,實務上通常是使用者 ID 或對話 ID。換一個就是全新對話。
  • 對話越長送出的 token 越多、越慢,長對話要做摘要或截斷(可用 agent 的 middleware)。

練習:把 thread_id 改成用 input() 輸入,模擬兩個使用者輪流聊天,確認記憶不會混在一起。

第 7 課:RAG 原理

RAG(檢索增強生成):模型不知道內部資料,所以先找出相關段落、塞進 prompt,再請它根據內容回答。

  1. 事先準備:文件切塊,用 bge-m3 轉成向量,存進向量資料庫。
  2. 提問時:問題也轉成向量,找出最相近的 k 段。
  3. 把這 k 段塞進 prompt,交給 qwen3.8:27b 回答。

bge-m3 把一段文字轉成 1024 個數字,意思相近的文字向量也相近,所以搜尋看的是語意而非關鍵字。實測問「我想在家連公司電腦」,排第一的是 VPN 申請規定,原文並沒有「在家」二字。

from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.runnables import RunnablePassthrough

store = InMemoryVectorStore.from_documents(docs, get_embeddings())
retriever = store.as_retriever(search_kwargs={"k": 2})

prompt = ChatPromptTemplate.from_template(
    "只根據下列資料回答,資料沒有就說「資料中沒有」,並註明出處。\n\n"
    "資料:\n{context}\n\n問題:{question}")

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt | get_llm() | StrOutputParser()
)
  • dict 裡的兩個欄位同時計算:context 由檢索得到,RunnablePassthrough() 把原問題原封不動往下傳。
  • 防止亂編的關鍵在 prompt。實測問知識庫沒有的事(例如尾牙日期),模型回答「資料中沒有」。

第 8 課:真實文件 RAG 與調參

把 PDF、TXT、MD 放進 docs 資料夾即可問答;索引存成 index.json,下次不用重算。

python 08_rag_docs.py --rebuild            # 第一次,或文件有變動時
python 08_rag_docs.py                      # 互動問答,輸入 q 離開
python 08_rag_docs.py "密碼規定是什麼?"    # 直接問
  1. 載入:PDF 用 pypdf 逐頁讀取,記下檔名與頁碼供回答註明出處。
  2. 切塊:見下方設定。
  3. 建索引並存檔:store.dump("index.json"),之後 InMemoryVectorStore.load(...)。
  4. 檢索並回答:取最相近 4 段,編號後交給模型,要求列出引用來源。
RecursiveCharacterTextSplitter(
    chunk_size=300,      # 每塊約幾個字
    chunk_overlap=50,    # 相鄰兩塊重疊,避免句子被切斷
    separators=["\n\n", "\n", "。", ";", ",", ""],   # 中文優先在段落、句號切開
)
現象 調整方向
答案缺頭缺尾 加大 chunk_size 或 chunk_overlap
混進不相關內容 減小 chunk_size 或減少 k
文件有卻說沒有 增加 k;先單獨用 similarity_search 看檢索結果
某份 PDF 完全問不到 可能是掃描版,pypdf 抽不出文字,需先 OCR

RAG 答錯時先檢查檢索結果,再看模型;多數問題出在檢索。

練習

  1. 放一份真實規章 PDF,--rebuild 後實際問問看。
  2. chunk_size 改成 150 與 800,用同一個問題比較答案。
  3. 修改 prompt,讓模型回答「文件中沒有相關規定」時不再列出來源編號。

常見問題與下一步

問題 原因與處理
第一次執行很慢 模型約 17GB,要從硬碟載入 GPU,之後常駐 30 分鐘
Connection refused 瀏覽器開 http://<Ollama 主機>:11434/api/tags,確認看得到兩個模型
回答變簡體中文 system prompt 寫明「使用繁體中文(台灣用語)」
想換雲端模型 只改 config.py 的 get_llm(),例如 ChatOpenAI(model="gpt-4o-mini"),API Key 用環境變數設定,不要寫進程式

下一步

  1. 接內部資料:把 ERP 或表單系統的查詢包成 @tool,做能用中文查庫存、訂單的 agent(第 5 課實戰版)。
  2. LangGraph:自己設計流程,例如判斷問題類型、分流處理、人工確認後再繼續。
  3. 做成服務:用 FastAPI 包成 API,給網站或聊天機器人呼叫。

參考資料


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言