SpringBoot集成Ollama本地模型

SpringBoot集成Ollama本地模型

目录

  1. 项目准备
  2. 创建Ollama服务客户端
  3. 创建控制器
  4. 配置应用属性
  5. 创建前端界面
  6. 添加静态资源支持
  7. 完整项目结构
  8. 启动应用
  9. 高级功能扩展
  10. 部署注意事项
  11. 性能优化

1. 项目准备

  1. 创建一个SpringBoot项目,可以使用Spring Initializr或IDE创建
  2. 添加必要的依赖到pom.xml:
    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- WebClient -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>
    

2. 创建Ollama服务客户端

package com.example.ollama.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class OllamaService {
    
    
    
    private final WebClient webClient;
    
    public OllamaService(@Value("${ollama.api.url:http://localhost:11434}") String ollamaApiUrl) {
    
    
        this.webClient = WebClient.builder()
                .baseUrl(ollamaApiUrl)
                .build();
    }
    
    /**
     * 生成文本
     */
    public Mono<String> generateText(String model, String prompt, Double temperature) {
    
    
        Map<String, Object> requestBody = Map.of(
                "model", model,
                "prompt", prompt,
                "temperature", temperature != null ? temperature : 0.7
        );
        
        return webClient.post()
                .uri("/api/generate")
                .bodyValue(requestBody)
                .retrieve()
                .bodyToMono(Map.class)
                .map(response -> (String) response.get("response"));
    }
    
    /**
     * 聊天对话
     */
    public Mono<String> chat(String model, List<Map<String, String>> messages) {
    
    
        Map<String, Object> requestBody = Map.of(
                "model", model,
                "messages", messages
        );
        
        return webClient.post()
                .uri("/api/chat")
                .bodyValue(requestBody)
                .retrieve()
                .bodyToMono(Map.class)
                .map(response -> {
    
    
                    Map<String, Object> message = (Map<String, Object>) response.get("message");
                    return (String) message.get("content");
                });
    }
    
    /**
     * 流式生成文本
     */
    public Flux<String> streamGenerateText(String model, String prompt, Double temperature) {
    
    
        Map<String, Object> requestBody = Map.of(
                "model", model,
                "prompt", prompt,
                "temperature", temperature != null ? temperature : 0.7,
                "stream", true
        );
        
        return webClient.post()
                .uri("/api/generate")
                .bodyValue(requestBody)
                .retrieve()
                .bodyToFlux(Map.class)
                .map(response -> (String) response.get("response"));
    }
}

3. 创建控制器

package com.example.ollama.controller;

import com.example.ollama.service.OllamaService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/ollama")
@RequiredArgsConstructor
public class OllamaController {
    
    
    
    private final OllamaService ollamaService;
    
    @PostMapping("/generate")
    public Mono<String> generateText(
            @RequestParam(defaultValue = "llama2") String model,
            @RequestParam String prompt,
            @RequestParam(required = false) Double temperature) {
    
    
        return ollamaService.generateText(model, prompt, temperature);
    }
    
    @PostMapping("/chat")
    public Mono<String> chat(
            @RequestParam(defaultValue = "llama2") String model,
            @RequestBody List<Map<String, String>> messages) {
    
    
        return ollamaService.chat(model, messages);
    }
    
    @PostMapping("/stream")
    public Flux<String> streamGenerateText(
            @RequestParam(defaultValue = "llama2") String model,
            @RequestParam String prompt,
            @RequestParam(required = false) Double temperature) {
    
    
        return ollamaService.streamGenerateText(model, prompt, temperature);
    }
}

4. 配置应用属性

application.propertiesapplication.yml中添加配置:

# Ollama API配置
ollama.api.url=http://localhost:11434

# 服务器配置
server.port=8080

5. 创建前端界面

创建一个简单的HTML页面用于与Ollama交互:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ollama聊天界面</title>
    <style>
        body {
      
      
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .chat-container {
      
      
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 20px;
            margin-bottom: 20px;
            height: 400px;
            overflow-y: auto;
        }
        .message {
      
      
            margin-bottom: 10px;
            padding: 10px;
            border-radius: 5px;
        }
        .user-message {
      
      
            background-color: #e6f7ff;
            margin-left: 20%;
        }
        .ai-message {
      
      
            background-color: #f0f0f0;
            margin-right: 20%;
        }
        .input-container {
      
      
            display: flex;
        }
        #message-input {
      
      
            flex-grow: 1;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            margin-right: 10px;
        }
        button {
      
      
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
      
      
            background-color: #45a049;
        }
    </style>
</head>
<body>
    <h1>Ollama聊天界面</h1>
    <div class="chat-container" id="chat-container"></div>
    <div class="input-container">
        <input type="text" id="message-input" placeholder="输入消息...">
        <button onclick="sendMessage()">发送</button>
    </div>

    <script>
        const chatContainer = document.getElementById('chat-container');
        const messageInput = document.getElementById('message-input');
        
        // 按Enter键发送消息
        messageInput.addEventListener('keypress', function(e) {
      
      
            if (e.key === 'Enter') {
      
      
                sendMessage();
            }
        });
        
        function sendMessage() {
      
      
            const message = messageInput.value.trim();
            if (!message) return;
            
            // 添加用户消息到聊天界面
            addMessage(message, 'user');
            messageInput.value = '';
            
            // 发送消息到后端
            fetch('/api/ollama/chat', {
      
      
                method: 'POST',
                headers: {
      
      
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify([
                    {
      
      
                        'role': 'user',
                        'content': message
                    }
                ])
            })
            .then(response => response.text())
            .then(response => {
      
      
                // 添加AI回复到聊天界面
                addMessage(response, 'ai');
            })
            .catch(error => {
      
      
                console.error('Error:', error);
                addMessage('发生错误,请稍后重试。', 'ai');
            });
        }
        
        function addMessage(message, sender) {
      
      
            const messageDiv = document.createElement('div');
            messageDiv.classList.add('message');
            messageDiv.classList.add(sender === 'user' ? 'user-message' : 'ai-message');
            messageDiv.textContent = message;
            chatContainer.appendChild(messageDiv);
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }
    </script>
</body>
</html>

6. 添加静态资源支持

在SpringBoot应用中添加静态资源支持:

package com.example.ollama.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/");
    }
}

7. 完整项目结构

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── ollama/
│   │               ├── OllamaApplication.java
│   │               ├── controller/
│   │               │   └── OllamaController.java
│   │               ├── service/
│   │               │   └── OllamaService.java
│   │               └── config/
│   │                   └── WebConfig.java
│   └── resources/
│       ├── static/
│       │   └── index.html
│       └── application.properties

8. 启动应用

  1. 确保Ollama服务已启动并运行在默认端口(11434)
  2. 运行SpringBoot应用
  3. 访问http://localhost:8080查看聊天界面

9. 高级功能扩展

9.1 添加模型选择功能

@GetMapping("/models")
public Mono<List<String>> listModels() {
    
    
    return webClient.get()
            .uri("/api/tags")
            .retrieve()
            .bodyToMono(Map.class)
            .map(response -> {
    
    
                List<Map<String, Object>> models = (List<Map<String, Object>>) response.get("models");
                return models.stream()
                        .map(model -> (String) model.get("name"))
                        .collect(Collectors.toList());
            });
}

9.2 添加流式响应支持

@GetMapping(value = "/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(
        @RequestParam(defaultValue = "llama2") String model,
        @RequestParam String message) {
    
    
    
    return ollamaService.streamGenerateText(model, message, 0.7)
            .map(response -> ServerSentEvent.<String>builder()
                    .data(response)
                    .build());
}

9.3 添加会话管理

@Service
public class ChatSessionService {
    
    
    
    private final Map<String, List<Map<String, String>>> sessions = new ConcurrentHashMap<>();
    
    public List<Map<String, String>> getOrCreateSession(String sessionId) {
    
    
        return sessions.computeIfAbsent(sessionId, k -> new ArrayList<>());
    }
    
    public void addMessage(String sessionId, String role, String content) {
    
    
        List<Map<String, String>> messages = getOrCreateSession(sessionId);
        messages.add(Map.of("role", role, "content", content));
    }
    
    public void clearSession(String sessionId) {
    
    
        sessions.remove(sessionId);
    }
}

10. 部署注意事项

  1. 确保服务器上已安装并运行Ollama服务
  2. 配置适当的防火墙规则,允许SpringBoot应用访问Ollama服务
  3. 在生产环境中使用HTTPS保护API通信
  4. 考虑添加身份验证和授权机制
  5. 监控Ollama服务的资源使用情况,避免过载

11. 性能优化

  1. 使用连接池管理WebClient连接
  2. 实现请求缓存,避免重复请求
  3. 使用异步处理提高并发能力
  4. 考虑使用响应式编程模式处理流式响应