自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從 LLM 到 RAG:探索基于 DeepSeek 開(kāi)發(fā)本地知識(shí)庫(kù)應(yīng)用

人工智能
我們可以把 LLM(Large Language Model,大語(yǔ)言模型)想象成是一個(gè)讀了海量書(shū)籍的“數(shù)字大腦”,它的訓(xùn)練數(shù)據(jù)來(lái)自互聯(lián)網(wǎng)上的海量文本,讓它具備了理解語(yǔ)言、生成文本、分析邏輯的能力。

LLM:會(huì)“思考”的 AI

我們可以把 LLM(Large Language Model,大語(yǔ)言模型)想象成是一個(gè)讀了海量書(shū)籍的“數(shù)字大腦”,它的訓(xùn)練數(shù)據(jù)來(lái)自互聯(lián)網(wǎng)上的海量文本,讓它具備了理解語(yǔ)言、生成文本、分析邏輯的能力。

假設(shè)你想知道 Kubernetes 最新的發(fā)行版本,你直接向 DeepSeek 詢問(wèn):

“Kubernetes 最新發(fā)行版本是什么”

圖片圖片

不幸的是,由于 LLM 訓(xùn)練數(shù)據(jù)是靜態(tài)的,并引入了其所掌握知識(shí)的截止日期,它只能告訴你一個(gè)過(guò)時(shí)的版本。

RAG:讓 AI 變得更“聰明”

這時(shí),RAG(Retrieval-Augmented Generation,檢索增強(qiáng)生成)技術(shù)就派上了用場(chǎng)。

我們可以利用 RAG 來(lái)提高 LLM 的回答準(zhǔn)確性,同時(shí)避免“幻覺(jué)(Hallucination)”問(wèn)題。它的工作原理如下:

1、創(chuàng)建知識(shí)庫(kù)

如數(shù)據(jù)庫(kù)、文檔、網(wǎng)頁(yè)等在 LLM 原始訓(xùn)練數(shù)據(jù)之外的數(shù)據(jù)都稱為外部數(shù)據(jù),結(jié)合向量模型(Embedding)可以將這些外部的文本數(shù)據(jù)轉(zhuǎn)換為向量數(shù)據(jù)并將其存儲(chǔ)在向量數(shù)據(jù)庫(kù)中。這個(gè)過(guò)程就創(chuàng)建了一個(gè)知識(shí)庫(kù)。

2、檢索相關(guān)信息

同樣借助向量模型(Embedding)將用戶查詢的問(wèn)題轉(zhuǎn)換為向量表示形式,然后從向量數(shù)據(jù)庫(kù)中檢索出相關(guān)度高的內(nèi)容。

3、增強(qiáng) LLM 提示

最后,通過(guò)在 LLM 上下文中添加檢索到的相關(guān)數(shù)據(jù)來(lái)增強(qiáng)用戶輸入問(wèn)題或提示,為用戶查詢生成更加準(zhǔn)確的答案。

大致的交互流程如下:

圖片圖片

在 DeepSeek 官網(wǎng)中,聯(lián)網(wǎng)搜索和上傳文件,就是 RAG 技術(shù)的體現(xiàn):

圖片圖片

可以看到,RAG 能夠讓 LLM 變得像一個(gè)“實(shí)時(shí)更新的百科全書(shū)”,可以隨時(shí)查找最新答案。

回到技術(shù)本身,RAG 說(shuō)白了就是結(jié)合了 LLM 和向量數(shù)據(jù)庫(kù)的一種知識(shí)問(wèn)答的技術(shù)體系。這個(gè)過(guò)程會(huì)圍繞著數(shù)據(jù)解析、內(nèi)容分塊、數(shù)據(jù)向量化(Embedding 模型)、結(jié)果重排(Rerank 模型)等問(wèn)題。

接下來(lái),我們逐步探索如何使用 Go 語(yǔ)言開(kāi)發(fā)一個(gè)完全本地化的 RAG 知識(shí)庫(kù)問(wèn)答系統(tǒng)。通過(guò)結(jié)合 DeepSeek 大語(yǔ)言模型和向量數(shù)據(jù)庫(kù),實(shí)現(xiàn)一個(gè)可以根據(jù)網(wǎng)頁(yè)內(nèi)容回答問(wèn)題的智能問(wèn)答系統(tǒng)。

準(zhǔn)備階段:Ollama 讓大模型在本地運(yùn)行

Ollama 是一個(gè)本地大模型部署工具,它讓你可以在自己的電腦或服務(wù)器上運(yùn)行 LLM,不用依賴外部服務(wù)。

在 https://ollama.com/ 官網(wǎng)下載并安裝 Ollama 后,就會(huì)在本地啟動(dòng)一個(gè) Ollama Server 默認(rèn)監(jiān)聽(tīng) 11434 端口,往后我們所有的交互都是與該地址通信:

$ curl http://localhost:11434
Ollama is running

我們把所有需要用到的模型都拉取到本地,語(yǔ)言模型選擇最小的 deepseek-r1:1.5b ,向量模型選擇 nomic-embed-text:latest ,至于重排模型,目前 Ollama 并未支持,我們就不進(jìn)行結(jié)果重排了:

ollama pull deepseek-r1:1.5b
ollama pull nomic-embed-text:latest

準(zhǔn)備階段:部署向量數(shù)據(jù)庫(kù)

向量數(shù)據(jù)庫(kù)有很多種選擇,有 Chroma、Milvus、pgvector、Qdrant 等,我們選擇 pgvector ,采用 Docker 部署方式:

docker run -d --name pgvector17 \
  -e POSTGRES_USER=pgvector \
  -e POSTGRES_PASSWORD=pgvector \
  -e POSTGRES_DB=llm-test \
  -v pgvector_data:/var/lib/postgresql/data \
  -p 5432:5432 \
  pgvector/pgvector:pg17

LangChainGo:LLM 應(yīng)用開(kāi)發(fā)框架 Go 版本

LangChain 是一個(gè)非常流行的基于 LLM 開(kāi)發(fā)應(yīng)用程序的 Python 框架,本文選用 LangChainGo ,即 Go 版本的 LLM 開(kāi)發(fā)框架。

現(xiàn)在正式進(jìn)入開(kāi)發(fā)環(huán)節(jié),整個(gè)系統(tǒng)的設(shè)計(jì)思路就是將非結(jié)構(gòu)化的網(wǎng)頁(yè)內(nèi)容轉(zhuǎn)換為結(jié)構(gòu)化的知識(shí),并通過(guò)向量檢索和大語(yǔ)言模型的結(jié)合,實(shí)現(xiàn)準(zhǔn)確的問(wèn)答功能。

1、網(wǎng)頁(yè)內(nèi)容解析與分塊

RAG 系統(tǒng)首先需要獲取外部知識(shí),而網(wǎng)頁(yè)是最常見(jiàn)的知識(shí)來(lái)源,我們可以使用 goquery 來(lái)解析和提取網(wǎng)頁(yè)的 HTML 內(nèi)容:

func loadAndSplitWebContent(url string) ([]schema.Document, error) {
 // 發(fā)送HTTP GET請(qǐng)求獲取網(wǎng)頁(yè)內(nèi)容
 resp, err := http.Get(url)
 if err != nil {
  return nil, err
 }
 defer resp.Body.Close()

 // 使用goquery解析HTML文檔
 doc, err := goquery.NewDocumentFromReader(resp.Body)
 if err != nil {
  return nil, err
 }

 var content strings.Builder

 // 移除script和style標(biāo)簽,避免抓取無(wú)關(guān)內(nèi)容
 doc.Find("script,style").Remove()
 // 提取body中的所有文本內(nèi)容
 doc.Find("body").Each(func(i int, s *goquery.Selection) {
  text := strings.TrimSpace(s.Text())
  if text != "" {
   content.WriteString(text)
   content.WriteString("\n")
  }
 })

 // ......
}

接著使用 textsplitter 對(duì)提取到的文本內(nèi)容進(jìn)行分塊,設(shè)置 ChunkSize 塊大?。?12)和 ChunkOverlap 重疊大小(0),并為每個(gè)塊添加元數(shù)據(jù)以后續(xù)引用時(shí)可以標(biāo)記來(lái)源:

func loadAndSplitWebContent(url string) ([]schema.Document, error) {
 // ......

 // 將文本分割成多個(gè)塊,設(shè)置塊大小為512字符,無(wú)重疊
 splitter := textsplitter.NewRecursiveCharacter(
  textsplitter.WithChunkSize(512),
  textsplitter.WithChunkOverlap(0),
 )
 chunks, err := splitter.SplitText(content.String())
 if err != nil {
  return nil, err
 }

 // 為每個(gè)文本塊創(chuàng)建Document對(duì)象,包含元數(shù)據(jù)
 documents := make([]schema.Document, 0)
 for i, chunk := range chunks {
  documents = append(documents, schema.Document{
   PageContent: chunk,
   Metadata: map[string]any{
    "source": url,                  // 記錄文本來(lái)源URL
    "chunk":  fmt.Sprintf("%d", i), // 記錄塊的序號(hào)
   },
  })
 }
 return documents, nil
}

其中塊大小代表我們將內(nèi)容切分為單個(gè)塊的最大字符數(shù)或單詞數(shù),而重疊大小代表相鄰塊之間的重疊字符數(shù)或單詞數(shù),可以在調(diào)試過(guò)程不斷調(diào)整這兩個(gè)參數(shù)來(lái)提升 RAG 的表現(xiàn)。

這樣,我們就得到了原始的塊內(nèi)容。

比如,以 Kubernetes 的發(fā)行版本頁(yè)面:https://kubernetes.io/zh-cn/releases/ 為例,可以通過(guò)該函數(shù)解析并切分為 4 個(gè)塊:

圖片

2、向量化與存儲(chǔ)

由于文本無(wú)法直接比較語(yǔ)義相似度,我們需要對(duì)塊內(nèi)容進(jìn)行文本向量化后存入向量數(shù)據(jù)庫(kù),也就是使用 Ollama 的 nomic-embed-text:latest 向量模型進(jìn)行文本向量化,首先初始化該向量模型:

const (
 // DefaultOllamaServer 默認(rèn)的Ollama服務(wù)器地址
 DefaultOllamaServer = "http://localhost:11434"
 // DefaultEmbeddingModel 用于生成文本向量的默認(rèn)模型
 DefaultEmbeddingModel = "nomic-embed-text:latest"
)

func initEmbedder() (embeddings.Embedder, error) {
 embedModel, err := ollama.New(
  ollama.WithServerURL(DefaultOllamaServer),
  ollama.WithModel(DefaultEmbeddingModel),
 )
 if err != nil {
  return nil, fmt.Errorf("創(chuàng)建embedding模型失敗: %v", err)
 }

 embedder, err := embeddings.NewEmbedder(embedModel)
 if err != nil {
  return nil, fmt.Errorf("初始化embedding模型失敗: %v", err)
 }
 return embedder, nil
}

接著配置向量數(shù)據(jù)庫(kù),使用 pgvector 作為向量存儲(chǔ),并將上面初始化好的向量模型綁定到 pgvector 實(shí)例中:

const (
 // DefaultPGVectorURL PostgreSQL向量數(shù)據(jù)庫(kù)的連接URL
 DefaultPGVectorURL = "postgres://pgvector:pgvector@localhost:5432/llm-test?sslmode=disable"
)

func initVectorStore(embedder embeddings.Embedder) (vectorstores.VectorStore, error) {
 store, err := pgvector.New(
  context.Background(),
  pgvector.WithConnectionURL(DefaultPGVectorURL),
  pgvector.WithEmbedder(embedder), // 綁定向量模型
  pgvector.WithCollectionName(uuid.NewString()),
 )
 if err != nil {
  return nil, fmt.Errorf("初始化向量存儲(chǔ)失敗: %v", err)
 }
 return &store, nil
}

然后就可以通過(guò) store.AddDocuments 方法批量地將文本向量化后存儲(chǔ)到向量數(shù)據(jù)庫(kù)中:

func addDocumentsToStore(store vectorstores.VectorStore, allDocs []schema.Document) {
 // 設(shè)置批處理大小,避免一次處理太多文檔
 batchSize := 10
 totalDocs := len(allDocs)
 processedDocs := 0

 // 分批處理所有文檔
 for i := 0; i < totalDocs; i += batchSize {
  end := i + batchSize
  if end > totalDocs {
   end = totalDocs
  }

  batch := allDocs[i:end]
  // 將文檔添加到向量存儲(chǔ)
  _, err := store.AddDocuments(context.Background(), batch)
  if err != nil {
   fmt.Printf("\n添加文檔到向量存儲(chǔ)失敗: %v\n", err)
   continue
  }

  processedDocs += len(batch)
  progress := float64(processedDocs) / float64(totalDocs) * 100
  fmt.Printf("\r正在添加文檔到向量存儲(chǔ): %.1f%% (%d/%d)", progress, processedDocs, totalDocs)
 }
 fmt.Printf("\n成功加載 %d 個(gè)文檔片段到向量存儲(chǔ)\n", totalDocs)
}

這一步,我們就得到了一個(gè)知識(shí)庫(kù)。如下,所有的塊內(nèi)容都會(huì)被向量化存儲(chǔ)到數(shù)據(jù)庫(kù)中:

圖片

3、大語(yǔ)言模型集成

為了可以理解并回答用戶的問(wèn)題,我們開(kāi)始集成 deepseek-r1:1.5b 模型,和向量模型的初始化類似,也需要對(duì)語(yǔ)言模型進(jìn)行初始化:

const (
 // DefaultOllamaServer 默認(rèn)的Ollama服務(wù)器地址
 DefaultOllamaServer = "http://localhost:11434"
 // DefaultLLMModel 用于生成回答的默認(rèn)大語(yǔ)言模型
 DefaultLLMModel = "deepseek-r1:1.5b"
)

func initLLM() (llms.Model, error) {
 llm, err := ollama.New(
  ollama.WithServerURL(DefaultOllamaServer),
  ollama.WithModel(DefaultLLMModel),
 )
 if err != nil {
  return nil, fmt.Errorf("初始化LLM失敗: %v", err)
 }
 return llm, nil
}

4、獲取用戶問(wèn)題并進(jìn)行語(yǔ)義檢索

用戶提問(wèn)后,首先通過(guò) store.SimilaritySearch 方法在向量數(shù)據(jù)庫(kù)中查找與用戶問(wèn)題(question)語(yǔ)義相似的文檔作為參考信息:

func handleQuestion(store vectorstores.VectorStore, llm llms.Model, question string) {
 // 在向量數(shù)據(jù)庫(kù)中搜索相關(guān)文檔
 // 參數(shù):最多返回5個(gè)結(jié)果,相似度閾值0.7
 results, err := store.SimilaritySearch(
  context.Background(),
  question,
  5,
  vectorstores.WithScoreThreshold(0.7),
 )
 if err != nil {
  fmt.Printf("搜索相關(guān)文檔失敗: %v\n", err)
  return
 }

 if len(results) == 0 {
  fmt.Println("\n未找到相關(guān)的參考信息,請(qǐng)換個(gè)問(wèn)題試試。")
  return
 }

 // 顯示檢索到的文檔
 displaySearchResults(results)
 // 將相關(guān)文檔作為上下文提供給大語(yǔ)言模型并生成問(wèn)題的回答
 generateAnswer(llm, question, results)
}

需要注意的是,該步驟也需要調(diào)用向量模型將問(wèn)題進(jìn)行向量化。如下,當(dāng)用戶提問(wèn)后,可以顯示檢索到的文檔,因?yàn)槲覀兿薅讼嗨贫乳撝禐?0.7 ,所以只檢索到 2 個(gè)分塊:

圖片圖片

5、包裝 Prompt 結(jié)合參考信息交由大語(yǔ)言模型回答

最后我們只需要設(shè)計(jì)合適的提示詞模板,填充參考信息,調(diào)用上面初始化好的 DeepSeek 本地模型就可以回答用戶問(wèn)題了:

func generateAnswer(llm llms.Model, question string, results []schema.Document) {
 var references strings.Builder
 for i, doc := range results {
  score := 1 - doc.Score
  references.WriteString(fmt.Sprintf("%d. [相似度:%f] %s\n", i+1, score, doc.PageContent))
 }

 messages := []llms.MessageContent{
  {
   // 系統(tǒng)提示,設(shè)置助手角色和行為規(guī)則
   Role: llms.ChatMessageTypeSystem,
   Parts: []llms.ContentPart{
    llms.TextContent{
     Text: fmt.Sprintf(
      "你是一個(gè)專業(yè)的知識(shí)庫(kù)問(wèn)答助手。以下是基于向量相似度檢索到的相關(guān)文檔:\n\n%s\n"+
       "請(qǐng)基于以上參考信息回答用戶問(wèn)題?;卮饡r(shí)請(qǐng)注意:\n"+
       "1. 優(yōu)先使用相關(guān)度更高的參考信息\n"+
       "2. 如果參考信息不足以完整回答問(wèn)題,請(qǐng)明確指出",
      references.String(),
     ),
    },
   },
  },
  {
   // 用戶問(wèn)題
   Role: llms.ChatMessageTypeHuman,
   Parts: []llms.ContentPart{
    llms.TextContent{
     Text: question,
    },
   },
  },
 }

 fmt.Printf("生成回答中...\n\n")

 _, err := llm.GenerateContent(
  context.Background(),
  messages,
  llms.WithTemperature(0.8), // 設(shè)置溫度為0.8,增加回答的多樣性
  llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
   fmt.Print(string(chunk))
   return nil
  }),
 )
 if err != nil {
  fmt.Printf("生成回答失敗: %v\n", err)
  return
 }

 fmt.Println()
}

可以看到,現(xiàn)在即使是本地的 deepseek-r1:1.5b 模型,有了 RAG 的加成,也可以正確回答我們的問(wèn)題:

圖片圖片

附上完整代碼:https://github.com/togettoyou/rag-demo

至此,我們就實(shí)現(xiàn)了一個(gè)功能完整的本地知識(shí)庫(kù)問(wèn)答系統(tǒng)。它幾乎包含了 RAG 應(yīng)用的所有核心要素:

  • 文本處理:網(wǎng)頁(yè)抓取和分塊
  • 向量化:文本向量化和存儲(chǔ)
  • 知識(shí)檢索:相似度搜索
  • 答案生成:LLM 回答生成

而在此基礎(chǔ)上,還有更多的優(yōu)化沒(méi)做:

  • 添加更多數(shù)據(jù)源支持(PDF、Word 等)
  • 優(yōu)化文本分塊策略
  • 實(shí)現(xiàn)結(jié)果重排(Rerank 模型)
  • 語(yǔ)義檢索和語(yǔ)言模型結(jié)合的增強(qiáng)處理

最后推薦一些 RAG 領(lǐng)域的開(kāi)源項(xiàng)目:Dify、FastGPT、QAnything 等,這些都集成了知識(shí)庫(kù)功能,而且基本都對(duì)接了各家的語(yǔ)言模型、向量模型、重排模型等,如果是完全本地化,也可以嘗試 Page Assist 瀏覽器插件,可以直接連接本地的 Ollama 實(shí)現(xiàn)知識(shí)庫(kù)對(duì)話。

本文轉(zhuǎn)載自微信公眾號(hào)「gopher云原生」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系gopher云原生公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: gopher云原生
相關(guān)推薦

2025-02-11 12:15:57

2023-06-12 07:43:05

知識(shí)庫(kù)性能優(yōu)化

2011-12-13 18:00:54

2024-04-30 09:48:33

LLMRAG人工智能

2024-09-03 16:15:36

2025-03-26 08:50:00

OllamaFastGPTDeepseek

2025-03-27 12:25:03

DeepSeekRAG人工智能

2025-02-12 12:12:59

2024-05-28 09:24:32

2025-03-04 09:26:37

2024-10-07 08:49:25

2025-03-07 07:57:56

SpringDeepSeek智能

2025-04-01 07:30:00

2025-02-28 07:11:20

2024-08-12 08:28:53

2024-11-27 15:49:46

字符串Python

2025-01-09 10:52:23

RAG知識(shí)圖譜人工智能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)