使用 Ollama 本地部署 AI 聊天应用

使用 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

  1. 首先通过 request.json() 解析客户端发送的请求体,获取其中包含的消息列表。
  2. 构建 Ollama 请求 :根据解析得到的消息列表,构建符合 ChatRequest 类型的请求体,包含模型名、消息列表以及设置 stream: false 表示非流式响应。
  3. 调用 Ollama API :使用 fetch 方法向 Ollama API 发送 POST 请求,设置请求头的 Content-Type 为 application/json ,并将构建好的请求体转换为 JSON 字符串作为请求体内容。
  4. 响应状态检查 :检查 Ollama API 的响应状态是否正常( response.ok ),若不正常则获取错误信息文本并返回包含错误信息的响应。
  5. 数据解析与返回 :若响应正常,则通过 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>
  );
}

运行应用

  1. 确保 Ollama 已启动,并已下载 deepseek-r1:1.5b 模型。

  2. 启动 Next.js 项目:

    bash 复制代码
    npm run dev
  3. 打开浏览器访问 http://localhost:3000,即可与本地运行的 DeepSeek 模型进行对话。

总结

通过 Ollama,我们成功在本地搭建了一个完整的 AI 聊天应用。整个过程无需依赖任何外部 API ,所有数据都在本地处理,真正实现了数据隐私模型可控

相关推荐
FogLetter3 小时前
Prisma + Next.js 全栈开发初体验:像操作对象一样玩转数据库
前端·后端·next.js
Mintopia10 小时前
🛡️ Next.js 中间件权限验证与 API 保护的奇幻冒险
前端·javascript·next.js
SuperheroMan8246610 小时前
《从零搭建 Next.js + NestJS 全栈项目:踩坑实录》
next.js
AI大模型1 天前
重磅!Ollama发布UI界面,告别命令窗口!
程序员·llm·ollama
EndingCoder2 天前
React 19 与 Next.js:利用最新 React 功能
前端·javascript·后端·react.js·前端框架·全栈·next.js
Mintopia2 天前
🔐 使用 NextAuth 实现 OAuth / Credentials 登录 —— Web 身份的流浪与归宿
前端·javascript·next.js
Mintopia3 天前
🎬《Next 全栈 CRUD 的百老汇》
前端·后端·next.js
我想说一句3 天前
轻松搞定Next.js+Prisma全栈开发
前端·前端框架·next.js
浩瀚蓝天dep4 天前
使用Ollama部署自己的本地模型
ai大模型·ollama·deepseek