目錄

Ollama 文字嵌入與 RAG:讓模型根據你的文件回答

語言模型不知道你公司的規章、產品手冊或個人筆記。RAG(Retrieval-Augmented Generation,檢索增強生成) 的做法是:先從你的文件中找出和問題相關的段落,再把這些段落連同問題一起交給模型,讓它根據資料回答。

問題 ──▶ 找出最相關的文件段落 ──▶ 「根據以下資料回答:……」 ──▶ 模型 ──▶ 答案

找出「相關段落」的關鍵技術就是文字嵌入(embedding)。

什麼是文字嵌入

嵌入模型會把一段文字轉成一串數字(向量)。意思相近的文字,向量也會相近,即使用字完全不同:

  • 「如何申請請假?」
  • 「休假要怎麼提出?」

這兩句的向量會很接近,因此可以做到關鍵字搜尋做不到的「語意搜尋」。

下載嵌入模型

ollama pull bge-m3            # 多語言,中文表現好
ollama pull nomic-embed-text  # 輕量,英文為主

嵌入模型不能拿來聊天,只能產生向量。

產生嵌入

curl http://localhost:11434/api/embed -d '{
  "model": "bge-m3",
  "input": ["如何申請請假?", "休假要怎麼提出?"]
}'
import ollama

result = ollama.embed(model="bge-m3", input=["如何申請請假?", "休假要怎麼提出?"])
vectors = result.embeddings
print(len(vectors), len(vectors[0]))  # 2 筆,每筆 1024 維

input 可以是單一字串或字串清單,批次處理比一筆一筆送快很多。

計算相似度

最常用的是餘弦相似度,值介於 -1 到 1,越接近 1 越相似:

import math


def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))


print(cosine(vectors[0], vectors[1]))

實作:本機知識庫問答

假設有一份公司規章 handbook.txt。完整程式:

import math
import ollama

EMBED_MODEL = "bge-m3"
CHAT_MODEL = "qwen3:8b"


def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))


# 1. 讀取文件並切成段落(以空行分段)
text = open("handbook.txt", encoding="utf-8").read()
chunks = [c.strip() for c in text.split("\n\n") if c.strip()]

# 2. 為每個段落產生嵌入(只需做一次,可以存起來重複使用)
chunk_vectors = ollama.embed(model=EMBED_MODEL, input=chunks).embeddings


def ask(question: str, top_k: int = 3) -> str:
    # 3. 找出和問題最相似的段落
    q_vec = ollama.embed(model=EMBED_MODEL, input=question).embeddings[0]
    ranked = sorted(zip(chunks, chunk_vectors), key=lambda p: cosine(q_vec, p[1]), reverse=True)
    context = "\n\n".join(c for c, _ in ranked[:top_k])

    # 4. 把段落和問題一起交給模型
    response = ollama.chat(
        model=CHAT_MODEL,
        messages=[
            {
                "role": "system",
                "content": "你是公司的內部助理。只根據提供的資料,用繁體中文回答。"
                "如果資料中沒有答案,請回答「資料中沒有相關資訊」,不要自行推測。",
            },
            {"role": "user", "content": f"資料:\n{context}\n\n問題:{question}"},
        ],
        options={"temperature": 0.2},
    )
    return response.message.content


while True:
    q = input("\n問題:").strip()
    if not q:
        break
    print(ask(q))

這就是一個最基本、但完整可用的 RAG 系統,所有資料都留在本機。

改進方向

切塊(chunking)

切得太大,會混入不相關的內容;切得太小,會失去上下文。常見做法:

  • 每塊約 300~800 個字,相鄰兩塊重疊 50~100 字。
  • 依照文件結構切(標題、段落),比固定字數切更好。
  • 每塊前面加上所屬的標題,例如「第三章 請假規定 > 特休」。

使用向量資料庫

段落多時,每次都和所有向量比對會很慢,而且每次重啟都要重新計算。可以改用向量資料庫儲存與搜尋:

工具特色
ChromaPython 中最容易上手,可存在本機資料夾
SQLite + sqlite-vec單一檔案,適合小型專案
PostgreSQL + pgvector已經在用 PostgreSQL 時的首選
Qdrant、Milvus大量資料、獨立服務

混合搜尋

語意搜尋不擅長精確比對(例如產品型號、人名)。結合關鍵字搜尋(BM25)與語意搜尋,通常效果更好。

注意上下文長度

把多個段落塞進提示詞時,總長度不能超過模型的上下文長度,否則前面的內容會被截掉。必要時調高 num_ctx,見 環境變數與設定。

不想自己寫程式?

Open WebUI 內建了文件上傳與知識庫功能,可以直接上傳 PDF、Word 檔,讓模型根據文件回答。

更新於 2026年10月12日