在 Docker 环境中部署 Ollama 并使用 Spring AI 框架实现结构化输出,你可以按照以下步骤进行操作:
1. 部署 Ollama 模型
首先,需要在 Docker 中部署 Ollama 并下载 deepseek-r1:1.5b
模型。
1.1 准备部署文件
version: '3.8'services:ollama:volumes:# 如果需要可以打开:将本地文件夹挂载到容器中的 /root/.ollama 目录 (模型下载位置)- ./models:/root/.ollama container_name: spring-ai-ollamapull_policy: alwaystty: truerestart: unless-stoppedimage: ollama/ollama:latestports:- 11434:11434 # Ollama API 端口
1.2 下载 LLM 模型
docker exec -it spring-ai-ollama bash# 进入容器执行
ollama run deepseek-r1:1.5b
2. 使用 Spring AI 框架实现结构化输出
接下来,在 Spring Boot 项目中使用 Spring AI 框架与 Ollama 服务进行交互,并实现结构化输出。
2.1 添加依赖
在 pom.xml
添加依赖:
<dependencies><!-- Spring AI Ollama --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency></dependencies>
2.2 配置 Ollama 客户端信息
spring:application:name: spring-ai-structured-outai:ollama:base-url: http://localhost:11434chat:model: deepseek-r1:1.5b
2.3 创建控制器
创建一个 Spring Boot 控制器来处理 HTTP 请求并返回结构化输出:
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@RestController
@RequestMapping("/ollama/chat-model")
public class OllamaChatModelController {private static final Logger log = LoggerFactory.getLogger(OllamaChatModelController.class);private static final String DEFAULT_PROMPT = "你好,介绍下你自己吧。请用中文回答。";private final ChatModel ollamaChatModel;private final ChatClient chatClient;public OllamaChatModelController(ChatModel chatModel, ChatClient.Builder builder) {this.ollamaChatModel = chatModel;this.chatClient = builder.build();}@GetMapping("/simple/chat")public String simpleChat() {return ollamaChatModel.call(new Prompt(DEFAULT_PROMPT)).getResult().getOutput().getContent();}@GetMapping("/stream/chat")public Flux<String> streamChat(HttpServletResponse response) {// 避免返回乱码response.setCharacterEncoding("UTF-8");Flux<ChatResponse> stream = ollamaChatModel.stream(new Prompt(DEFAULT_PROMPT));return stream.map(resp -> resp.getResult().getOutput().getContent());}@GetMapping("/structuredOut")public StreamToBeanEntity structuredOut(String topic) {var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<StreamToBeanEntity>() {});// 构建提示词,明确要求 JSON 格式String promptText = """请根据主题 '{topic}' 生成一篇文章,返回一个 JSON 对象,包含以下字段:- title: 文章标题- content: 文章正文内容返回格式必须只有 JSON 格式,包含字段 title 和 content""";PromptTemplate promptTemplate = new PromptTemplate(promptText);Prompt prompt = promptTemplate.create(Map.of("topic", topic));String output = chatClient.prompt(prompt).call().content();log.info("输出:\n{}", output);String json = extractJson(output);log.info("提取的 JSON:\n{}", json);try {return converter.convert(json);} catch (Exception e) {log.error("转换失败", e);}return null;}private String extractJson(String output) {// 使用正则表达式提取 JSON 部分Pattern pattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL);Matcher matcher = pattern.matcher(output);if (matcher.find()) {return matcher.group(0);}throw new RuntimeException("无法从模型输出中提取 JSON 内容");}}
3. 结构化输出测试