使用 Ollama 本地部署 AI 聊天应用
有没有一种方式,让我们既能享受大模型的强大能力,又能完全掌控自己的数据和环境?答案是肯定的Ollama,这个专为本地部署大模型而生的工具,正在让"大模型自由"成为现实。
什么是 Ollama
Ollama 是一个轻量级、易用的开源工具,它可以简化大型语言模型在本地设备上的部署与运行。它支持多种系统,并且只需几条命令即可启动一个功能完整的本地大模型服务。
Ollama 的核心优势在于:
- 极简安装:一键安装,无需复杂的环境配置。
- 模型即服务:通过 REST API 提供模型推理能力,便于集成到各类应用中。
- 多模型支持:支持 Llama 系列、DeepSeek、Qwen、Mistral 等主流开源模型。
- GPU 加速:自动识别并利用本地 GPU 资源,提升推理速度。
- 跨平台兼容:无论是个人笔记本还是服务器,都能轻松运行。
部署本地模型
1、安装 Ollama
根据你的操作系统,前往 Ollama 官网 下载并安装客户端:
bash
# Linux 用户可通过命令行一键安装
curl -fsSL https://ollama.com/install.sh | sh
安装完成后,Ollama 会自动在本地启动服务,默认监听 http://localhost:11434
。
2、下载并运行模型
Ollama 提供了简洁的命令行接口来管理模型。例如,下载并运行 DeepSeek 的 1.5B 参数模型:
bash
ollama pull deepseek-r1:1.5b 下载大模型
ollama run deepseek-r1:1.5b 运行大模型
该命令会自动从仓库拉取模型,并加载到内存中。你也可以通过 ollama list
查看已安装的模型列表。
构建一个基于 Next.js 的本地聊天应用demo
接下来,我们将使用 Ollama 和 Next.js 构建一个完整的本地聊天应用,实现与 DeepSeek 模型的实时对话。
1、项目结构
bash
/chat-app
├── app/
│ ├── page.tsx # 前端页面
│ └── api/chat/route.ts # 后端 API 路由
├── types/chat.ts # 类型定义
2、类型定义(types/chat.ts
)
ts
export type Message = {
role:'user' | 'assistant' | 'system' | 'tool'
content:string
}
/**
* 聊天响应数据类型
*/
export type ChatResponse = {
model: string; // 使用的模型名称
created_at: string; // 响应生成的时间戳
message: Message; // 核心:模型返回的消息内容
done: boolean; // 流式传输结束标志
total_duration: number; // 整个请求的总耗时(纳秒)
load_duration: number; // 模型加载耗时(纳秒)
prompt_eval_count: number; // 提示词(prompt)处理的 token 数量
prompt_eval_duration: number; // 处理提示词的耗时(纳秒)
eval_count: number; // 生成回复的 token 数量
eval_duration: number; // 生成回复的耗时(纳秒)
}
// 总耗费是 提示词(prompt)处理的 token 数量 + 生成回复的 token 数量
export type ChatRequest = {
model:string
messages:Message[]
stream ?:boolean // 可选的 是否流式响应 默认是false
}
3、后端 API(app/api/chat/route.ts
)
- 首先通过 request.json() 解析客户端发送的请求体,获取其中包含的消息列表。
- 构建 Ollama 请求 :根据解析得到的消息列表,构建符合 ChatRequest 类型的请求体,包含模型名、消息列表以及设置 stream: false 表示非流式响应。
- 调用 Ollama API :使用 fetch 方法向 Ollama API 发送 POST 请求,设置请求头的 Content-Type 为 application/json ,并将构建好的请求体转换为 JSON 字符串作为请求体内容。
- 响应状态检查 :检查 Ollama API 的响应状态是否正常( response.ok ),若不正常则获取错误信息文本并返回包含错误信息的响应。
- 数据解析与返回 :若响应正常,则通过 response.json() 解析 Ollama API 返回的数据,并使用 NextResponse.json() 将解析后的数据返回给客户端。
ts
import { NextResponse } from "next/server";
import { Message, ChatRequest, ChatResponse } from "@/types/chat";
const OLLAMA_API_URL = "http://localhost:11434/api/chat";
const MODEL_NAME = "deepseek-r1:1.5b";
/**
* 处理 POST 请求,与 Ollama API 进行交互以获取聊天响应
* @param request - 传入的请求对象
* @returns 返回包含聊天响应或错误信息的响应对象
*/
export async function POST(request: Request) {
try {
// 解析请求体,获取消息列表
const body: { messages: Message[] } = await request.json();
// 构建发送给 Ollama API 的请求体
const ollamaRequestBody: ChatRequest = {
model: MODEL_NAME,
messages: body.messages,
stream: false,
};
// 调用 Ollama API
const response = await fetch(OLLAMA_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(ollamaRequestBody),
});
// 检查 API 响应是否正常
if (!response.ok) {
// 获取错误信息
const errorText = await response.text();
// 返回包含错误信息的响应
return NextResponse.json({
error: `Ollama API ERROR `,
});
}
// 解析 Ollama API 返回的数据
const ollamaData: ChatResponse = await response.json();
// 返回 Ollama API 的响应数据
return NextResponse.json(ollamaData);
} catch (err) {
// 捕获并打印错误信息
console.error("Chat API Error:", err);
// 返回服务器内部错误响应
return NextResponse.json({
status: 500,
});
}
}
4、前端页面(app/page.tsx
)
tsx
'use client';
import { useState } from 'react';
import type { Message } from '@/types/chat';
export default function Home() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage: Message = {
role: 'user',
content: input
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
const res = await fetch('/api/chat', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ messages: [...messages, userMessage] })
});
const data = await res.json();
const assistantMessage: Message = data.message;
setMessages(prev => ([...prev, assistantMessage]));
} catch (error) {
console.error("Fetch error:", error);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col h-screen bg-gray-100">
{/* 对话区域 */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 ? (
<p className="text-center text-gray-500 mt-10">
开始与 DeepSeek 模型聊天吧
</p>
) : (
messages.map((msg, idx) => (
<div
key={idx}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg
${msg.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-800 shadow'
}`}
>
<p>{msg.content}</p>
</div>
</div>
))
)}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white text-gray-800 shadow px-4 py-2 rounded-lg max-w-xs lg:max-w-md">
<p>DeepSeek 正在思考....</p>
</div>
</div>
)}
</div>
{/* 输入区域 */}
<div className="p-4 bg-white border-t">
<form onSubmit={handleSubmit} className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入你的消息"
className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-blue-300 disabled:cursor-not-allowed"
>
{isLoading ? '发送中...' : '发送'}
</button>
</form>
</div>
</div>
);
}
运行应用
-
确保 Ollama 已启动,并已下载
deepseek-r1:1.5b
模型。 -
启动 Next.js 项目:
bashnpm run dev
-
打开浏览器访问
http://localhost:3000
,即可与本地运行的 DeepSeek 模型进行对话。
总结
通过 Ollama,我们成功在本地搭建了一个完整的 AI 聊天应用。整个过程无需依赖任何外部 API ,所有数据都在本地处理,真正实现了数据隐私 与模型可控。