浅谈人工智能之基于ollama本地大模型结合本地知识库搭建智能客服
摘要
在AI这件事上,数据安全始终是一个绕不开的话题。大模型再厉害,如果每次对话都要把数据传输到云端,对很多注重隐私的企业来说,这步子根本就迈不出去。好在,技术方案已经足够成熟了。利用Ollama在本地部署大模型,再结合自建的知识库,完全可以在保障数据不出门的前提下,搭建一个高效、定制化的智能客服系统。这篇内容,就是把从零开始的完整过程拆开来讲,希望能给正在选型或落地的团队一些实实在在的参考。

1. 引言
1.1 Ollama简介
先说说Ollama。它是一个开源项目,核心价值就是让本地部署和运行大模型这件事变得特别简单。有了它,你不用把数据送到云端,直接在本地服务器上就能跑Llama系列等主流模型,数据的隐私和可控性完全掌握在自己手里。安装过程就不展开了,不同的操作系统有对应的部署方式,网上资料很全,按步骤来就好。
1.2 本地知识库的重要性
智能客服能不能回答得准,核心在于它背后有没有一套“懂行”的知识库。所谓本地知识库,说白了,就是把公司内部的FAQ、产品手册、客服政策这些数据,全部存在自己的服务器上,形成一个专门的知识集合。跟放在云端的方案相比,本地知识库最大的优势就是权限可控、数据不外流,敏感信息的安全性有了根本保障。
2. 系统架构概述
2.1 技术栈
整个系统主要依赖几个组件:
●
Ollama
●
LangChain
●
Embedding
2.2 工作流程
整个系统的运作流程其实并不复杂,可以分为几个清晰的步骤:
- :对准备的知识库数据进行初步处理和索引构建。
模型训练
- :用户通过前端界面提交问题。
用户提问
- :API网关接收请求,并做解析。
请求处理
- :系统根据问题,在本地知识库中检索最相关的信息片段。
知识检索
- :Ollama结合用户的问题和检索到的知识,生成精准的回答。
模型推理
- :最终答案通过API网关返回给前端,展示给用户。
结果返回
3. 应用实例
3.1 模型下载
这次我们用到的Embedding模型是
bce-embedding-vase_v1
D:vecbce-embedding-vase_v1 路径下。3.2 构建本地知识库
构建知识库的第一步,是准备好原始文档。假设我们有一个常见的Git问题FAQ,内容大概是这样:
问题1:git克隆失败-文件名太长答案1:查看错误信息中是否包含关键字:Filename too long问题2:git克隆失败-access_token失效答案2:查看错误信息中是否包含关键字:Authentication failed for、 Access denied、invalid_token。解决方法:重新生成一个有效的access_toekn问题3:git克隆失败-网络超时答案3:查看错误信息中是否包含关键字:Connection timed out、Unknown error、Could not resolve host等。可能是执行机与yfgitlab网络不通或者网络波动导致,执行机dns配置有问题,不能访问yfgitlab的域名问题4:git克隆失败-CI站点上配置的工程信息异常答案4:查看错误信息中是否包含关键字:FETCH_HEAD error: Sparse checkout lea ves no entry on working directory。可能原因:1)、工程信息中填写的脚本路径斜杠反了,比如不正确的写法:TESTStabilityITC,正确的写法:TEST/Stability/ITC;2)多个脚本路径用英文分号拼接,不能使用其他符号,比如TEST/PI/SD,TEST/PI/COMMON使用逗号拼接的是异常的问题5:git克隆失败-‘git’ 不是内部或外部命令,也不是可运行的程序答案5:1、检查下执行机上是否安装了git;2、可能链接jenkins的时候没有安装git,链接成功后才安装的git,联系工厂客服删除节点,用户重新部署执行机。问题6:git克隆失败-分支名中带有.号答案6:查看错误信息中是否包含关键字:Invalid argument Cloning into 。换一个没有.号的分支名问题7:git克隆失败-git链接不是以.git结尾答案7:查看错误信息中是否包含关键字:没有找到项目名称(xxx)对应的项目id。需要填写完整的git地址问题8:git克隆失败-out of memory答案8:查看错误信息中是否包含关键字:out of memory、No space left on device。用户需要检查下执行机硬盘空间
有了文档,下一步就是把它转成向量数据库。用到的代码片段如下:
# coding=utf-8
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings importHuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
# 导入文本
loader = TextLoader(r"D:vecdocumenttest.txt")
# 将文本转成 Document 对象
data = loader.load()
print(f'documents:{len(data)}')
# 初始化加载器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
# 切割加载的 document
split_docs = text_splitter.split_documents(data)
print("split_docs size:",len(split_docs))
model_name = r"D:vecbce-embedding-vase_v1"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
# 保存向量数据库部分
# 初始化数据库
db = Chroma.from_documents(split_docs, embeddings, persist_directory=r"D:vecvecdb")
# 持久化
db.persist()
# 对数据进行加载
db = Chroma(persist_directory=r"D:vecvecdb", embedding_function=embeddings)
question = "out of memory"
# 测试寻找四个相似的样本
similarDocs = db.similarity_search(question,k=4)
3.3 集成Ollama与知识库
知识库建好之后,跟Ollama的集成其实就一行核心代码的事。LangChain的RetrievalQA链直接把检索和问答串了起来:
# coding=utf-8
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings importHuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
retriever = db.as_retriever()
qa = RetrievalQA.from_chain_type(llm=Ollama(base_url='http://XX.XX.XX.XXX:9999',model="Qwen2-7b:latest"),retriever=retriever)
query = "我在做持续集成的时候,发现在git克隆的时候出现out of memory"
print(qa.run(query))
3.4 开发前后端
为了方便演示,我把训练、检索和接口全写在一个文件里了。后端使用Flask,暴露一个简单的API接口:
from flask import Flask, request, jsonify
from flask_cors import CORS
# 上面的代码保持不变
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
# 导入文本
loader = TextLoader(r"D:vecdocumenttest.txt")
data = loader.load()
print(f'documents:{len(data)}')
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
split_docs = text_splitter.split_documents(data)
print("split_docs size:", len(split_docs))
model_name = r"D:vecbce-embedding-vase_v1"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}
embeddings = HuggingFaceEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
db = Chroma.from_documents(split_docs, embeddings, persist_directory=r"D:vecvecdb")
db.persist()
db = Chroma(persist_directory=r"D:vecvecdb", embedding_function=embeddings)
retriever = db.as_retriever()
qa = RetrievalQA.from_chain_type(llm=Ollama(base_url='http://xx.xx.xxx.xxx:9999', model="Qwen2-7b:latest"), retriever=retriever)
# 创建FastAPI应用
app =Flask(__name__)
CORS(app)
@app.route('/query', methods=['POST'])
async def query_endpoint():
# 使用qa.run处理请求中的查询
data = request.get_json()
messages = data.get('query')
result = qa.run(messages)
return jsonify({"response": result})
if __name__ == '__main__':
app.run(debug=True)
前端就更简单了,一个基本的HTML页面,包含输入框和展示区,支持Markdown渲染:
Chat Interface with Markdown
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.12/marked.min.js"></script>
<script>
function sendMessage() {
const userInput = document.getElementById('userInput');
const chatHistory = document.getElementById('chatHistory');
// 获取用户输入并清空输入框
const userMessage = userInput.value;
userInput.value = '';
// 构建请求体
const data = {
"query": userMessage
};
// 发送POST请求到后端接口
fetch('http://127.0.0.1:5000/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
// 假设后端返回的数据格式为 { "response": "回答内容" }
const botResponse = data.response;
// 在聊天历史中添加用户的问题
chatHistory.innerHTML += `你:
${userMessage}`;
// 将Markdown转换为HTML并添加机器人的回答
if (botResponse) {
const htmlResponse = marked.parse(botResponse);
chatHistory.innerHTML += `机器人:
${htmlResponse}`;
} else {
console.error("没有从后端接收到有效的回答");
}
// 滚动到底部以便查看最新消息
chatHistory.scrollTop = chatHistory.scrollHeight;
})
.catch(error => {
console.error('Error:', error);
// 可以在这里添加错误信息的展示逻辑
chatHistory.innerHTML += `错误:
发生了一个错误,请稍后再试。`;
});
}
</script>
3.5 测试
启动后端服务后,在前端输入“分支名中带有.号”,系统会迅速从本地知识库中找到对应的答案。整个过程丝滑流畅,数据全程没有离开本地。
4. 安全与维护
方案搭完了,后续的运维也不能忽视。这块儿没太多好说的,几个要点:
●
数据加密
●
访问控制
●
定期更新
5. 结论
总的来说,通过Ollama加本地知识库这条路,企业完全可以构建一套既能充分发挥大模型能力、又具备高度自主可控性的智能客服系统。这种方案在数据安全、响应速度和定制化程度上都有明显优势。从实际落地的角度来看,随着整个技术栈的不断成熟,本地部署的智能客服系统会越来越普及,也会成为企业数字化转型过程中一个非常务实的选择。