AI 技术发展的确实太快了,去年年底还跟小伙伴们分享如何利用在线的工具来提升自己的开发效率,现在又开始卷本地了。因为数据的安全性,私有化本地部署成了使用者更看重的方式。
虽然市面上有很多本地部署的工具,这里抛开其他,主要介绍下 Ollama 这个工具。
Ollama
Ollama 官网的介绍很简单,就一句 Get up and running with large language models
.(启动并运行大型语言模型)。logo 很可爱,无形中增加了不少的亲和力。
下载/安装
选择一个自己的系统下载,目前支持的平台还算完善,基本都能部署起来
安装
安装过程很简单
最后只需要在控制台输入 ollama run llama3.1
就可以运行本地模型了,接近傻瓜式的操作让 Ollama 成为了一个很受欢迎的工具。
这里为了节省时间,把命令换成了 ollama run gemma2:2b
,而且现在小模型的发展比大模型还要快,麻雀虽小五章俱全。
启动
也是很自然的流程,模型下载完毕之后会自动启动,然后就可以直接对话了。
提供接口调用
如果模型只能在终端使用,那就太局限了,所以 Ollama 提供了 REST API
curl http://localhost:11434/api/generate -d '{
"model": "gemma2:2b",
"prompt":"Why is the sky blue?"
}'
在终端输入上面的代码,如果能正常返回数据,说明 API 服务已经可以正常运行,接着我们就要利用这个 API 来实现一个简单的网页应用。
本地模型 AI 应用
访问 http://localhost:11434
再次确认服务正常运营
本地 IP 访问
如果提供局域网服务,那么 http://localhost:11434
就要换成类似 http://192.168.1.1:11434
,但是当我们用 IP 访问本地服务的时候却无法正常访问。
这里根据自己的经验结合搜索引擎找到了答案,Mac 下在控制台里输入下面的代码,然后重启 Ollama 服务即可。
launchctl setenv OLLAMA_HOST "0.0.0.0"
然后就可以通过 IP 访问到 Ollama 的服务了
跨域问题
可是当我真的去使用的时候,却不合时宜的出现了跨域的问题
于是按照之前的思路又找到了相关配置,设置下跨域,为了调试方便,这里直接配置为 “*"。
launchctl setenv OLLAMA_ORIGINS "*"
在浏览器里输入测试代码测试一下
fetch('http://localhost:11434/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gemma2:2b',
messages: [
{ "role": "user", "content": "您好" }
]
stream: false
})
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
此时接口已经可以正常使用了!
应用
至此,基本的工作已经做完了,下面只要借助 ChatGPT 就可以完成简单的前端代码。
贴下相关代码,反正也不是我写的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>本地模型部署测试</title>
<style>
pre {
font-size: 20px;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
/* display: flex; */
justify-content: center;
height: 100vh;
margin: 0;
}
#output {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 100%;
line-height: 1.6;
font-size: 18px;
color: #333;
white-space: pre-wrap;
word-wrap: break-word;
margin: 20px auto;;
min-height: 200px;
}
.search-container {
display: flex;
justify-content: center;
align-items: center;
margin: 50px auto;
}
.search-box {
width: 600px;
min-height: 70px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 3px;
outline: none;
font-size: 16px;
transition: border-color 0.3s;
}
.search-box:focus {
border-color: #999;
}
.search-button {
padding: 8px 20px;
margin-left: 5px;
border: 1px solid #ccc;
border-radius: 3px;
background-color: #f8f8f8;
cursor: pointer;
outline: none;
font-size: 16px;
transition: background-color 0.3s, border-color 0.3s;
}
.search-button:hover {
background-color: #e8e8e8;
}
.search-button:focus {
border-color: #666;
}
</style>
</head>
<body>
<pre id="output"></pre>
<div class="search-container">
<input type="text" class="search-box" placeholder="请输入问题">
<button class="search-button">发送</button>
</div>
<script>
function getQueryParam(param) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(param);
}
var flag = true;
async function fetchStream(val) {
if (flag == false) {
console.log("return");
return;
}
const promptValue = getQueryParam('prompt');
// 这里的参数要改成自己的服务地址
const response = await fetch('http://0.0.0.0:11434/api/chat', {
method: 'POST',
mode:"cors",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gemma2:2b',
messages: [
{ "role": "system", "content": '请用中文回答' },
{ "role": "user", "content": val }
] ,
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
const outputElement = document.getElementById('output');
while (true) {
const { done, value } = await reader.read();
if (done) {
flag = true;
break;
}
const chunk = decoder.decode(value, { stream: true });
console.log(JSON.parse(chunk));
for (const char of JSON.parse(chunk).message.content) {
outputElement.textContent += char;
await new Promise(resolve => setTimeout(resolve, 20));
}
}
}
// fetchStream();
document.querySelector('.search-button').addEventListener('click', function () {
const promptValue = document.querySelector('.search-box').value;
fetchStream(promptValue);
flag = false;
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
document.querySelector('.search-button').click();
}
});
if (location.protocol == "https:") {
alert("内网服务需要页面地址手动改成 http:// 访问使用")
}
</script>
</body>
</html>
到此我们就拥有了一个本地模型,并且可以简单的聊天了,如果说想还想接入自己的知识库使用,那就是下一篇文章的内容了。