目錄
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 字。
- 依照文件結構切(標題、段落),比固定字數切更好。
- 每塊前面加上所屬的標題,例如「第三章 請假規定 > 特休」。
使用向量資料庫
段落多時,每次都和所有向量比對會很慢,而且每次重啟都要重新計算。可以改用向量資料庫儲存與搜尋:
| 工具 | 特色 |
|---|---|
| Chroma | Python 中最容易上手,可存在本機資料夾 |
| SQLite + sqlite-vec | 單一檔案,適合小型專案 |
| PostgreSQL + pgvector | 已經在用 PostgreSQL 時的首選 |
| Qdrant、Milvus | 大量資料、獨立服務 |
混合搜尋
語意搜尋不擅長精確比對(例如產品型號、人名)。結合關鍵字搜尋(BM25)與語意搜尋,通常效果更好。
注意上下文長度
把多個段落塞進提示詞時,總長度不能超過模型的上下文長度,否則前面的內容會被截掉。必要時調高 num_ctx,見 環境變數與設定。
不想自己寫程式?
Open WebUI 內建了文件上傳與知識庫功能,可以直接上傳 PDF、Word 檔,讓模型根據文件回答。
更新於 2026年10月12日