前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringAI+Ollama三部曲之二:细说开发

SpringAI+Ollama三部曲之二:细说开发

作者头像
程序员欣宸
发布2024-05-26 14:35:31
3130
发布2024-05-26 14:35:31
举报
文章被收录于专栏:实战docker实战docker
欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

Spring AI实战全系列链接
  1. Spring AI实战之一:快速体验(OpenAI)
  2. SpringAI+Ollama三部曲之一:极速体验
  3. SpringAI+Ollama三部曲之二:细说开发
本篇概览
  • 本文是《SpringAI+Ollama三部曲》系列的第二篇,前文咱们通过简单的操作完成了以下系统的部署,并体验了基于大模型的聊天功能,想必您已迫不及待的想知道具体的实现细节,本篇就是为爱学习的您准备的,一起来了解完整的开发过程吧
  • 今天的开发过程如下图所示
源码下载(觉得作者啰嗦的,直接在这里下载)

名称

链接

备注

项目主页

该项目在GitHub上的主页

git仓库地址(https)

该项目源码的仓库地址,https协议

git仓库地址(ssh)

git@github.com:zq2599/blog_demos.git

该项目源码的仓库地址,ssh协议

  • 这个git项目中有多个文件夹,本篇的源码在leader-tutorials文件夹下,如下图红色箭头所示:
  • tutorials目录下有多个项目,整个《SpringAI实战》系列的源码在springai-tutorials,这是个maven工程,里面有多个子工程,今天的实战就是子工程ollama-chat
Java开发(新建工程)
  • 《Spring AI实战之一:快速体验(OpenAI)》一文中创建了一个名为springai-tutorials的maven父工程,用来管理所有SpringAI有关的源码,今天要写的代码也放在这里面统一管理
  • 新建名为ollama-chat的maven工程,这是springai-tutorials的子工程,pom.xml内容如下
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springai-tutorials</artifactId>
        <groupId>com.bolingcavalry</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ollama-chat</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
    		<groupId>io.projectreactor</groupId>
    		<artifactId>reactor-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

    <build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
  • 从上述pom.xml可见,对接ollama的关键是spring-ai-ollama-spring-boot-starter库的使用,另外为了达到不间断输出内容到前端的效果,还依赖了webflux和reactor,以及渲染网页所需的thymeleaf库
  • 既然是子工程,那么就要父工程的pom.xml中配置好,这个别漏了
Java开发(配置文件)
  • 前文中,咱们在部署的时候创建了application.properties文件,这是为了方便配置,您也可以选择不额外配置,直接把配置文件放在传统的src/main/resources目录中
代码语言:javascript
复制
# ollama的通信地址,如果当前应用和ollama通过docker-compose打包部署,host就可以直接写ollama的容器名
spring.ai.ollama.base-url=http://ollama:11434
# 指定大模型,这里用的是通义千问1.8b
spring.ai.ollama.chat.options.model=qwen:1.8b
# 值越小回答越严谨,值越大回答越有创造性
spring.ai.ollama.chat.options.temperature=0.7
# 响应式web服务
spring.main.web-application-type=reactive
Java编码(后端)
  • 接下来是本篇的核心:后端代码,功能是收到前端发来的问题,通过SpringAI调用ollama,将大模型的响应返回给前端
  • 首先是启动类Application.java,平凡无奇
代码语言:javascript
复制
package com.bolingcavalry.ollamachat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 然后是RestClient的配置类,没有的话可能会启动失败
代码语言:javascript
复制
package com.bolingcavalry.ollamachat;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {
    @Bean
    public RestClient.Builder restClientBuilder() {
        return RestClient.builder();
    }
}
  1. 接下来是最核心的代码,响应前端请求的controller类,尽管重要,但是代码还是很简单而且充满了套路,依赖注入client bean,从controller入参得到前端的请求参数,调用ollama client的API就完成提问,收到的返回值不是ollama的响应,而是对流的封装bean,这里用到的就是springboot的webflux,将流对象返回给前端
代码语言:javascript
复制
package com.bolingcavalry.ollamachat.controller;

import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.ai.chat.messages.UserMessage;

import reactor.core.publisher.Flux;

@RestController
public class ChatController {

    private final OllamaChatClient chatClient;


    public ChatController(OllamaChatClient chatClient) {
        // 依赖注入ollama的客户端类
        this.chatClient = chatClient;
    }

    @GetMapping(value = "/ai/streamresp", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
	public Flux<String> streamResp(@RequestParam(value = "message", defaultValue = "Hello!") String message) throws InterruptedException {
        // 提示词类,包裹了用户发来的问题
        Prompt prompt = new Prompt(new UserMessage(message));
        // 调用ollama的客户端类的API,将问题发到ollama,并获取流对象,Ollama响应的数据就会通过这个流对象持续输出
        Flux<ChatResponse> chatResp = chatClient.stream(prompt);
        // ollama客户端返回的数据包含了多个内容,例如system,assistant等,需要做一次变换,取大模型的回答内容返回给前端
        return chatResp.map(chatObj -> chatObj.getResult().getOutput().getContent());
    }
}
  • 至此,后端代码就写完了,没错就只有这么点
前端代码
  • 前文的体验是在网页上进行的,这就说明今天的开发也少不了前端代码,接下来就一起开发前端代码吧
  • 实话实说,欣宸的前端水平离及格还差很远,所以这里把基本功能跑通已经算超水平发挥了,代码和效果都很差劲,希望您能海涵…
  • 在src/main/resources/templates目录下新建index.html文件,内容如下,其实也很简单,就是绑定按钮的事件,然后把收到的内容展示在指定div
代码语言:javascript
复制
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Reactive Flux Display</title>
    <script src="https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js"></script>
    <script th:src="@{/js/custom.js}"></script>
</head>
<body>
    <div>
        <input type="text" id="inputField" placeholder="Enter something...">
        <button id="sendButton">Submit</button>
        <div id="displayArea"></div>
    </div>
    <script>
        // 自定义的JavaScript代码将放在这里
        document.getElementById('sendButton').addEventListener('click', function() {
    const inputField = document.getElementById('inputField');
    const displayArea = document.getElementById('displayArea');
    
    const input = inputField.value;
    if (input) {
        displayArea.textContent = ""
        const url = `/ai/streamresp?message=${encodeURIComponent(input)}`;
        
        fetch(url)
            .then(response => {
                if (response.ok) return response.body.pipeThrough(new TextDecoderStream()).pipeTo(new WritableStream({
                    write(chunk) {
                        displayArea.textContent += chunk.replace(/data:/g, "").trim();
                    }
                }));
            })
            .catch(error => console.error('Error:', error));
    }
});
    </script>
</body>
</html>
  • 现在代码都写完了,这个java应用也能正常运行起来了,但是要达到前文的效果,还需要将工程制作成docker镜像,接下来就是制作过程
制作docker镜像(编写Dockerfile)
  • 要制作docker镜像就要编写制作镜像的脚本,在pom.xml文件所在目录创建名为Dockerfile的文件,内容如下
代码语言:javascript
复制
# 使用Spring Boot官方镜像作为基础镜像
FROM openjdk:17-jdk-slim

# 设置环境变量
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
    JHIPSTER_SLEEP=0 \
    JAVA_OPTS=""

# 复制项目jar文件到Docker镜像中
COPY target/*.jar /app.jar

# 运行应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar --spring.config.location=file:/app/application.properties"]
  • 可见制作镜像的步骤也很简单,就是常规的复制jar文件,并且启动命令中指定了properties文件的位置
制作docker镜像(构建镜像)
  • 首先要编译java工程生成jar文件,在父工程的pom.xml所在目录执行以下命令即可完成编译构建
代码语言:javascript
复制
mvn clean compile package -U -DskipTests
  • 编译成功后,在ollama-chat/target目录下生成了ollama-chat-1.0-SNAPSHOT.jar文件
  • 进入Dockerfile文件所在目录,执行以下命令即可完成docker镜像制作(注意最后有个点号,不要漏掉了),注意bolingcavalry/ollam-tutorial:0.0.1-SNAPSHOT 是我为这个镜像起的名字,bolingcavalry是我的docker镜像账号,这样写符合docker的规范,稍后可以推送到hub.docker.com的镜像仓库中,这样算是在网上公开了,大家都可以使用
代码语言:javascript
复制
docker build -t bolingcavalry/ollam-tutorial:0.0.1-SNAPSHOT .
  • 用docker images命令查看本地镜像,可见已经生成了新镜像
上传镜像(可选)
  • 如果您希望自己的镜像保存在公共仓库,以便更多人用到,可以先用docker login命令登录后,再执行以下命令推送本地镜像,注意镜像名的前缀要换成自己的docker账号
代码语言:javascript
复制
docker push bolingcavalry/ollam-tutorial:0.0.1-SNAPSHOT
docker-compose配置
  • 最后就是docker-compose.yml了,这个在前文已经制作过一次,再来回顾一下,可见一共三个容器:ollama、webui、java应用,前两个直接使用官方镜像即可,最后一个是咱们刚才做出来的
代码语言:javascript
复制
version: '3.8'
services:
  ollama:
    image: ollama/ollama:latest
    ports:
      - 11434:11434
    volumes:
      - /home/will/data/ollama:/root/.ollama
    container_name: ollama
    pull_policy: if_not_present
    tty: true
    restart: always
    networks:
      - ollama-docker

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    pull_policy: if_not_present
    volumes:
      - /home/will/data/webui:/app/backend/data
    depends_on:
      - ollama
    ports:
      - 13000:8080
    environment:
      - 'OLLAMA_BASE_URL=http://ollama:11434'
      - 'WEBUI_SECRET_KEY=123456'
      - 'HF_ENDPOINT=https://hf-mirror.com'
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped
    networks:
      - ollama-docker

  java-app:
    image: bolingcavalry/ollam-tutorial:0.0.1-SNAPSHOT
    volumes:
      - /home/will/temp/202405/15/application.properties:/app/application.properties
    container_name: java-app
    pull_policy: if_not_present
    depends_on:
      - ollama
    ports:
      - 18080:8080
    restart: always
    networks:
      - ollama-docker

networks:
  ollama-docker:
    external: false
  • 至此,整个开发过程算是完成了,接下来按照前文的步骤部署和启动,就是使用大模型的能力了
  • 通过本文可见,基于SpringAI的封装后,使用大模型的对话能力是非常简单的事情,当然了,ollama并非如此简单,SpringAI对ollama的能力封装也不可能只有这么一个API调用,那么接下来的文章中,咱们会继续深入,通过SpringAI了解ollama的更多能力
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 欢迎访问我的GitHub
  • Spring AI实战全系列链接
  • 本篇概览
  • 源码下载(觉得作者啰嗦的,直接在这里下载)
  • Java开发(新建工程)
  • Java开发(配置文件)
  • Java编码(后端)
  • 前端代码
  • 制作docker镜像(编写Dockerfile)
  • 制作docker镜像(构建镜像)
  • 上传镜像(可选)
  • docker-compose配置
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档