Administrator
Published on 2025-01-03 / 305 Visits
0

JAVA SpringBoot Spring AI 对接 OLLAMA,实现流式返回和function calling

一、pom文件

各个组件的版本信息

<properties>
    <java.version>23</java.version>
    <spring-ai.version>1.0.0-M5</spring-ai.version>
</properties>

用到的包信息

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.53</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.36</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

包下载地址信息,由于spring ai的包都没传到中央库,这里需要spring的私有库下载包

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

二、yml文件

server:
  port: 8080
spring:
  ai:
    ollama:
      chat:
        model: modelName:modelVersion
      base-url: http://localhost:11434

这里需要注意的是modelName:modelVersion需要改为自己的模型名称和模型版本

base-url 是ollama的连接地址,也需要改为自己的,因为我是本地调试所以用的localhost

三、controller代码

@RestController
public class TestController {
    private final ChatClient chatClient;
    public TestController(ChatClient.Builder chatClient){
        this.chatClient=chatClient.build();
    }

    @GetMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(String msg) {
        Prompt prompt = new Prompt(msg,OllamaOptions.builder()
                .build());
        return chatClient.prompt(
                        prompt
                )
                .stream().content();
    }
}

四、前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>本地chat测试</title>
    <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script>
</head>
<body>
    <div style="width: 500px;height: 500px" id="messages"></div>
    <input type="text" id="msg" value=""/>
    <button onclick="massage($('#msg').val())">发送</button>
</body>

<script type="text/javascript">
    function massage(msg) {
        $('#messages').append("<span style='color: red'>"+msg+"</span>");
        $('#messages').append("<br>");
        if(typeof(EventSource)!=="undefined") {
            var source = new EventSource("/chat?msg="+msg);
            // 当接收到消息时更新页面
            source.onmessage = function(event) {
                $('#messages').append(event.data);
            };

            // 错误处理
            source.onerror = function(event) {
                console.error("错误或者返回结束.", event);
                $('#messages').append("<br>");
                // 在发生错误时关闭连接
                source.close();
            };
        } else {
            $('#messages').text("当前浏览器不支持流式返回");
        }
    }
</script>
</html>

这里用的比较老的原生html+jquery做的,使用其他框架的宝儿们自己优化一下。

运行后的效果

五、function calling实现

在平时的开发中,可能会出现一些系统和AI交互的情况

比如:

需要AI再数据库中查询我还有多少积分

需要AI给谁发一个电子邮件

需要查询一下系统中某个商品的价格

这里我们就需要使用的大模型的function calling功能,这里需要注意的是,老一点的模型可能会不支持这个功能。

这里我们以查询商品价格为例做一个demo

service

import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.Data;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;

import java.util.function.Function;

@Description("商品价格检索")
@Service
public class ProductPriceSearchService implements Function<ProductPriceSearchService.SearchRequest, ProductPriceSearchService.SearchResponse> {

    @Data
    public static class SearchRequest {
        @JsonPropertyDescription(value = "商品名称")
        String goodsName;
    }

    public record SearchResponse(String price) {
    }

    @Override
    public SearchResponse apply(SearchRequest searchRequest) {
        System.out.println(searchRequest.goodsName);
        return new SearchResponse("大份100中份80小份20");
    }
}

controller
    @GetMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(String msg, @RequestHeader("referer") String referer) {
        Prompt prompt = new Prompt(msg,OllamaOptions.builder()
                .build());

        return chatClient.prompt(
                prompt
        )
                .functions("productPriceSearchService")
                .stream().content();

    }

这里相比之前的controller就多了一个functions,这里可以传多个service的名称进去 他会根据service里注解相关信息自动提内容。

得到返回值后系统会自动根据你返回的数据组织语言回答给用户

OVER !~