概述
想要用LLM构建个人知识库,大致有3条路径:提示词工程(prompt engineering)、检索增强RAG(embeding)和微调Fine tuning。提示词工程就像给LLM大模型写作文,尽量给LLM提供足够多的信息便于给出准确的答案,掌握了足够好的prompt也能收获想要的答案,可以做到简洁高效。本项目是为一个私人眼科医院搭建的在线智能客服抽取出来的源代码。当初想使用dify、Go-RAG之类的健全的系统,但客户是一个眼科医生非码农出生,且不想重写现有的系统,也不想了解那么深那么复杂的RAG系统,客户反正是这也不要那也不要,毕竟年级大了。为追求时髦,必须要一个使用了DeepSeek的智能客服。如是自己不情愿的手搓了一个tinyRAG。只要拷贝运行程序和目录,就能提供基于RAG本地知识库的在线客服,确实简单了很多。本项目使用Go语言+Ollama构建一个简单的prompt提示词工程RAG,大模型使用的是deepseek。项目代码地址为:git@gitee.com:misujiatensorflow_admin/go_ollama_tiny_rag.git 运行效果如下:
一、系统架构
前端UA ===> Go+Gin ==>Ollama ===>DeepSeek.
用户在前端输入问题后,后端(Go+Gin)将本地的知识库,通过prompt的方式和用户提交的问题一起推送给Ollama,Ollama将DeepSeek的结果通过后端服务器推送给前端。为啥没有使用数据库存放知识库,因为客户年级大了,不熟悉电脑操作,只好教他复制粘贴,因为这个简单。将做好的知识库,丢到knowledge_base目录即可。
二、安装与运行
1.安装Ollama
下载Ollama, https://ollama.com/ Ollama是一个用于管理和部署机器学习模型的工具。下载和安装过程中的问题,可自行百度。
2.下载DeepSeek模型
ollama run deepseek-r1:1.5b
这里为了演示,选取了deepseek中比较简洁的版本。可以根据自身的硬件环境选取合适的版本。
3.编译Go语言代码并运行
git clone git@gitee.com:misujiatensorflow_admin/go_ollama_tiny_rag.git
cd go_ollama_tiny_rag #源码目录,这里是go_ollama_tiny_rag
go build #编译
./rag.exe #运行
三、源代码分析
tree /F
│ .env.local #需要配置的运行参数
│ go.mod
│ go.sum
│ knowledge_base.go #知识库的相关函数
│ LICENSE
│ main.go
│ README.en.md
│ README.md
│ result.go
│ setting.go # 配置文件读取
│
├─knowledge_base #默认的知识库目录,目录下的txt文档为知识库
│ knowledge1.txt
│ knowledge2.txt
│
└─web
index.htm #测试程序入口
1..env.local 配置文件
#GIN_MODE=release
DEBUG=true
HOST=127.0.0.1
PORT=80
DEFAULT_OLLAMA_URL=http://localhost:11434/api/chat #LLM访问地址
KNOWLEDGE_BASE_DIR=./knowledge_base #知识库目录路径
LLM_MODEL=deepseek-r1:1.5b #模型
LLM_AGENT_NOTICE=你是一个知识渊博的助手,请根据提供的知识库内容回答问题。 #Agent提示
可以配置RAG系统的运行地址和端口,设置Ollama运行可以访问的接口,知识库目录,LLM模型,系统提示词。
2.加载知识库关键函数
func loadKnowledgeBase(dir string) (string, error) {
var knowledgeBuilder strings.Builder
// 遍历知识库目录
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".txt") {
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
knowledgeBuilder.WriteString(fmt.Sprintf("文件: %s\n内容:\n%s\n\n", info.Name(), content))
}
return nil
})
if err != nil {
return "", err
}
return knowledgeBuilder.String(), nil
}
3.Ollama请求就deepseek反馈结果
func talkToOllama(url string, ollamaReq Request) (*Response, error) {
js, err := json.Marshal(&ollamaReq)
if err != nil {
return nil, err
}
client := http.Client{Timeout: 180 * time.Second} // 增加超时时间
httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(js))
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
httpResp, err := client.Do(httpReq)
if err != nil {
return nil, err
}
defer httpResp.Body.Close()
if httpResp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(httpResp.Body)
return nil, fmt.Errorf("API 请求失败: %s, 响应: %s", httpResp.Status, body)
}
ollamaResp := Response{}
err = json.NewDecoder(httpResp.Body).Decode(&ollamaResp)
return &ollamaResp, err
}
3.交互接口
// 添加格式指令(放在主提示之后,知识库之前)
if body.Format == "json" {
messages = append(messages, Message{
Role: "system",
Content: "请始终使用以下JSON格式回答:\n{\"answer\": \"简明回答\", \"details\": {\"key1\": \"说明1\", \"key2\": \"说明2\"}}",
})
}
// 添加知识库和用户问题
messages = append(messages,
Message{
Role: "system",
Content: "知识库内容:\n" + knowledge,
},
Message{
Role: "user",
Content: body.Search,
},
)
fmt.Printf("Final messages: %+v\n", messages)
req := Request{
Model: MyConfig.LLMModel, // 大语言模型
Stream: false,
Messages: messages, // 助手提示+知识库+问题
Format: body.Format, // 使用传入的format参数
}
fmt.Printf("Ollama请求参数: %+v\n", req)