本地配置ollama AI助手

332 阅读4分钟

前言

随着AI大模型的快速发展,在本地部署自己的AI助手,既保证数据安全,又避免高昂的API费用。本文将详细介绍如何使用Ollama在本地部署开源大语言模型,并开发一个美观实用的AI助手应用,全程使用TypeScript、React和Node.js技术栈。

一、Ollama简介与优势

Ollama是一个轻量级框架,可让你在本地电脑上轻松运行、自定义和共享大型语言模型。

主要优势:

  • 隐私保护:所有数据和交互都在本地完成,无需担心敏感信息泄露
  • 零成本使用:无需支付API调用费用
  • 低延迟体验:本地部署避免了网络延迟
  • 丰富模型选择:支持Llama 2、Mistral、Gemma等众多开源模型
  • 简单易用:安装配置极其简便,无需复杂设置

二、本地环境配置

1. 安装Ollama

macOS:

brew install ollama

如果未安装Homebrew,请先执行:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Windows:从Ollama官网下载安装包安装。

Linux:

curl -fsSL https://ollama.ai/install.sh | shcurl -fsSL https://ollama.ai/install.sh | sh

2. 启动Ollama服务

ollama serve

注意:首次启动时,Ollama会自动创建必要的文件和配置

3. 拉取你的第一个模型

ollama pull llama4:latest

可选的其他优秀模型:

  • mistral:性能出众的7B模型
  • gemma:2b、gemma:7b:Google开源的Gemma模型
  • phi:Microsoft的小而强大的Phi-2模型
  • llama2-uncensored:内容限制较少的Llama 2版本

4. 验证模型可用性

ollama run llama4:latest

输入一些测试问题,验证模型是否正常工作。 到这里本地ollama配置就完成了,ollama官网地址

接下来是自己开发一个AI助手,连接到本地的ollama服务

三、后端服务开发

现在,让我们开始构建AI助手应用的核心部分。

1. 项目初始化

mkdir CodeGeniusHub
cd CodeGeniusHub
mkdir -p frontend backend

2. 配置后端项目

cd backend
pnpm init
pnpm add express cors axios typescript @types/node @types/express @types/cors
pnpm add -D nodemon ts-node

3. 实现Ollama API代理服务

后端核心代码(src/index.ts):

import express from 'express';
import cors from 'cors';
import axios from 'axios';

const app = express();
const port = 3001;
const OLLAMA_API = 'http://localhost:11434/api';

// 中间件
app.use(cors());
app.use(express.json());

// 获取可用模型列表
app.get('/api/models', async (req, res) => {
  try {
    const response = await axios.get(`${OLLAMA_API}/tags`);
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: '获取模型失败' });
  }
});

// 聊天接口
app.post('/api/chat', async (req, res) => {
  try {
    const { model, messages } = req.body;
    const response = await axios.post(`${OLLAMA_API}/chat`, {
      model,
      messages
    });
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: '聊天请求失败' });
  }
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

四、前端界面实现

1. 初始化前端项目

cd ../frontend
pnpm create vite . --template react-ts
pnpm install
pnpm add antd @ant-design/icons zustand axios sass react-markdown

2. 创建API服务层

API服务核心代码(src/services/api.ts):

import axios from 'axios';

// 创建API客户端
const apiClient = axios.create({
  baseURL: 'http://localhost:3001/api',
  headers: {
    'Content-Type': 'application/json',
  },
});

// 定义消息类型
export interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

// API服务
export const apiService = {
  // 获取可用模型
  getModels: async () => {
    const response = await apiClient.get('/models');
    return response.data;
  },
  
  // 聊天
  chat: async (model: string, messages: Message[]) => {
    const response = await apiClient.post('/chat', {
      model,
      messages,
    });
    return response.data;
  }
};

3. 实现状态管理

Zustand状态管理核心代码(src/stores/chatStore.ts):

import { create } from 'zustand';
import { Message, apiService } from '../services/api';

interface ChatState {
  models: string[];
  selectedModel: string;
  messages: Message[];
  loading: boolean;
  
  // 方法
  setModels: (models: string[]) => void;
  setSelectedModel: (model: string) => void;
  addMessage: (message: Message) => void;
  sendMessage: (content: string) => Promise<void>;
  clearMessages: () => void;
}

export const useChatStore = create<ChatState>((set, get) => ({
  models: [],
  selectedModel: 'llama2',
  messages: [],
  loading: false,
  
  setModels: (models) => set({ models }),
  setSelectedModel: (model) => set({ selectedModel: model }),
  
  addMessage: (message) => set((state) => ({
    messages: [...state.messages, message]
  })),
  
  sendMessage: async (content) => {
    const userMessage: Message = { role: 'user', content };
    
    // 添加用户消息
    set((state) => ({
      messages: [...state.messages, userMessage],
      loading: true
    }));
    
    try {
      // 发送聊天请求
      const response = await apiService.chat(
        get().selectedModel,
        [...get().messages, userMessage]
      );
      
      // 添加助手回复
      const assistantMessage: Message = {
        role: 'assistant',
        content: response.message.content
      };
      
      set((state) => ({
        messages: [...state.messages, assistantMessage],
        loading: false
      }));
    } catch (error) {
      console.error('发送消息失败:', error);
      set({ loading: false });
    }
  },
  
  clearMessages: () => set({ messages: [] })
}));

4. 开发聊天界面组件

Chat组件核心代码(src/components/Chat.tsx):

import React, { useState, useEffect, useRef } from 'react';
import { Input, Button, Select, List, Typography, Divider } from 'antd';
import { SendOutlined, DeleteOutlined } from '@ant-design/icons';
import ReactMarkdown from 'react-markdown';
import { useChatStore } from '../stores/chatStore';
import { apiService } from '../services/api';
import '../styles/Chat.scss';

const { Option } = Select;
const { Title, Paragraph } = Typography;

const Chat: React.FC = () => {
  // 获取全局状态
  const {
    models,
    selectedModel,
    messages,
    loading,
    setModels,
    setSelectedModel,
    addMessage,
    clearMessages
  } = useChatStore();
  
  // 本地状态
  const [input, setInput] = useState('');
  const messagesEndRef = useRef<HTMLDivElement>(null);
  
  // 加载模型列表
  useEffect(() => {
    const fetchModels = async () => {
      try {
        const data = await apiService.getModels();
        if (data.models) {
          const modelNames = data.models.map((model: any) => model.name);
          setModels(modelNames);
        }
      } catch (error) {
        console.error('获取模型失败:', error);
      }
    };
    
    fetchModels();
  }, []);
  
  // 滚动到底部
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);
  
  // 发送消息
  const handleSendMessage = async () => {
    if (!input.trim()) return;
    
    // 添加用户消息
    addMessage({
      role: 'user',
      content: input
    });
    
    setInput('');
    
    try {
      // 添加助手消息占位
      addMessage({
        role: 'assistant',
        content: '思考中...'
      });
      
      // 发送聊天请求
      const response = await apiService.chat(
        selectedModel,
        messages.concat({ role: 'user', content: input })
      );
      
      // 更新助手消息
      const assistantMessage = {
        role: 'assistant' as const,
        content: response.message.content
      };
      
      // 替换占位消息
      const updatedMessages = [...messages, { role: 'user', content: input }, assistantMessage];
      useChatStore.setState({ messages: updatedMessages });
      
    } catch (error) {
      console.error('发送消息失败:', error);
    }
  };
  
  return (
    <div className="chat-container">
      <div className="chat-header">
        <Title level={3}>Ollama AI助手</Title>
        <div className="model-selector">
          <span>模型:</span>
          <Select
            value={selectedModel}
            onChange={setSelectedModel}
            style={{ width: 180 }}
          >
            {models.map(model => (
              <Option key={model} value={model}>{model}</Option>
            ))}
          </Select>
          <Button
            icon={<DeleteOutlined />}
            onClick={clearMessages}
            danger
          >
            清空对话
          </Button>
        </div>
      </div>
      
      <Divider />
      
      <div className="messages-container">
        {messages.length === 0 ? (
          <div className="empty-messages">
            <Paragraph>开始与AI助手对话吧!</Paragraph>
          </div>
        ) : (
          <List
            itemLayout="horizontal"
            dataSource={messages}
            renderItem={(message) => (
              <List.Item className={`message ${message.role}`}>
                <div className="message-content">
                  <div className="message-header">
                    {message.role === 'user' ? '用户' : 'AI助手'}
                  </div>
                  <div className="message-body">
                    <ReactMarkdown>{message.content}</ReactMarkdown>
                  </div>
                </div>
              </List.Item>
            )}
          />
        )}
        <div ref={messagesEndRef} />
      </div>
      
      <div className="input-container">
        <Input.TextArea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="输入你的问题..."
          autoSize={{ minRows: 2, maxRows: 6 }}
          onPressEnter={(e) => {
            if (!e.shiftKey) {
              e.preventDefault();
              handleSendMessage();
            }
          }}
        />
        <Button
          type="primary"
          icon={<SendOutlined />}
          onClick={handleSendMessage}
          loading={loading}
        >
          发送
        </Button>
      </div>
    </div>
  );
};

export default Chat;

五、运行项目

1. 启动后端服务

cd backend
pnpm dev

2. 启动前端应用

cd frontend
pnpm dev

六、项目优化与拓展

1. 流式响应实现

核心代码:

// 流式响应处理
const handleStreamResponse = (model, messages, onUpdate, onDone) => {
  const source = new EventSource(`/api/chat?stream=true&model=${model}`);
  
  source.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);
      if (data === '[DONE]') {
        source.close();
        onDone();
        return;
      }
      
      onUpdate(data.message?.content || '');
    } catch (e) {
      console.error('解析流数据失败:', e);
    }
  };
  
  return () => source.close();
};

3. 保存聊天历史

使用zustand持久化:

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useChatStore = create(
  persist(
    (set, get) => ({
      // 状态和方法...
    }),
    {
      name: 'chat-storage',
      getStorage: () => localStorage,
    }
  )
);

七、总结与思考

通过本教程,我们实现了一个基于Ollama的本地AI助手应用,它具有以下特点:

  • 完全本地化部署,保护数据隐私
  • 零API费用,适合长期使用
  • 现代化的UI设计和用户体验
  • 可扩展的架构,支持进一步功能增强

未来扩展方向

  1. 多模型管理:支持同时加载多个模型,根据任务自动切换
  1. 知识库集成:添加文档向量化和RAG(检索增强生成)功能
  1. 语音交互:集成语音识别和语音合成能力
  1. 桌面应用打包:使用Electron将应用打包为桌面应用
  1. 模型微调:实现简单的LoRA微调界面,让模型更符合个人使用习惯
  1. 如果想更方便可以做成插件,放在vscode或者cursor进行使用

性能优化建议

  • 对于性能较弱的电脑,建议使用较小的模型如phi或gemma:2b
  • 增加模型参数调整功能,可以通过降低温度等参数提高响应速度
  • 考虑启用模型量化(ollama pull llama2:8b-q4_0)以降低内存占用

如果你觉得这篇文章有用,欢迎点赞👍、收藏⭐和评论💬!也欢迎在评论区分享你基于Ollama开发AI助手的经验和遇到的问题。