【Spring AI】整合 Ollama

1,735 阅读4分钟

项目搭建

前提:Spring AI 支持 Spring Boot 3.2.x 和 3.3.x。

环境配置:JDK:Java 17;大模型:Qwen;SpringBoot:3.2.3;

SpringBoot父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.3</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

添加 Spring AI 依赖

Spring AI BOM 声明了给定版本的 Spring AI 使用的所有依赖项的推荐版本。从应用程序的构建脚本中使用 BOM 可以避免你自己指定和维护依赖项版本。相反,你使用的 BOM 版本决定了使用的依赖项版本。它还确保你默认使用受支持且经过测试的依赖项版本,除非你选择覆盖它们。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

添加 Milestone 和 Snapshot 仓库

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

整合 Ollama

启动 Docker 容器中的 Ollama

image.png

引入 spring ai ollama 依赖。Spring AI BOM 已经定义了 Ollama 相关的依赖,直接引入即可。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

配置文件:

# 显式标明默认配置值,只要引入了 ollama-starter 依赖即启用
spring.ai.ollama.chat.enabled=true
# 显式标明默认配置值,只要引入了 ollama-starter 依赖即启用
spring.ai.ollama.base-url=http://localhost:11434
# 配置 ollama 使用的模型产品名称
spring.ai.ollama.chat.options.model=qwen2:1.5b

默认 Ollama api 会监听 11434 端口,可以使用命令进行查看 netstat -ano | findstr 11434

image.png

示例代码

示例一:通过文本串提示调用大模型

@GetMapping(value = "call01", name = "通过文本串提示调用大模型")
public String call01(@RequestParam(value = "message", defaultValue = "蒙古四大汗国之间的关系") String message) {
    return ollamaChatModel.call(message);
}

单元测试

@Test
public void call1Test() throws Exception {
    String url = "/ollama/call01";
    String param = "message";
    String content = "蒙古四大汗国之间的关系?";
    mockMvc.perform(getRequestBuilder(url, param, content)).andExpect(MockMvcResultMatchers.status().isOk()).andDo(print());
}

执行结果

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"335"]
     Content type = text/plain;charset=UTF-8
             Body = 12世纪,西辽、金朝灭亡后,中国北方出现了若干可汗王国。在这些可汗政权中,有三个国家形成了一个联盟体系:即以成吉思汗(忽必烈)建立的元朝作为盟主,由察合台汗国和窝阔台汗国组成的西夏(金朝)为从属国,在蒙古帝国的影响下逐步形成。
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

示例二:通过封装提示词对象调用大模型

@GetMapping(value = "call02", name = "通过封装提示词对象调用大模型")
public ChatResponse call02(@RequestParam(value = "message", defaultValue = "蒙古四大汗国之间的关系") String message) {
    return ollamaChatModel.call(new Prompt(new UserMessage(message)));
}

单元测试

@Test
public void call2Test() throws Exception {
    String url = "/ollama/call02";
    String param = "message";
    String content = "蒙古四大汗国之间的关系?";
    mockMvc.perform(getRequestBuilder(url, param, content)).andExpect(MockMvcResultMatchers.status().isOk()).andDo(print());
}

执行结果

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"result":{"metadata":{"finishReason":"stop","contentFilterMetadata":null},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"content":"蒙古帝国的四大汗国是:\n\n1. 金帐汗国(Kipchak Khanate)\n2. 玛吉斯汗国(Masqu Khanate)\n3. 希瓦利汗国(Shivekh Khanate)\n4. 托雷汗国(Tore Khanate)\n\n这四大汗国之间的关系是,每个汗国的统治者通过继承的方式传承王位。但到了一定时期后,新的统治者会试图征服其他三个竞争对手,并最终建立一个统一的帝国。\n\n在蒙古帝国灭亡后,四大汗国中的托雷汗国和希瓦利汗国逐渐被其他势力所吞并,金帐汗国和玛吉斯汗国则成为了后来俄罗斯和波兰立陶宛联邦的一部分。"}},"metadata":{"id":"","model":"qwen2:1.5b","rateLimit":{"tokensRemaining":0,"tokensLimit":0,"tokensReset":"PT0S","requestsLimit":0,"requestsReset":"PT0S","requestsRemaining":0},"usage":{"promptTokens":15,"generationTokens":162,"totalTokens":177},"promptMetadata":[],"empty":false},"results":[{"metadata":{"finishReason":"stop","contentFilterMetadata":null},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"content":"蒙古帝国的四大汗国是:\n\n1. 金帐汗国(Kipchak Khanate)\n2. 玛吉斯汗国(Masqu Khanate)\n3. 希瓦利汗国(Shivekh Khanate)\n4. 托雷汗国(Tore Khanate)\n\n这四大汗国之间的关系是,每个汗国的统治者通过继承的方式传承王位。但到了一定时期后,新的统治者会试图征服其他三个竞争对手,并最终建立一个统一的帝国。\n\n在蒙古帝国灭亡后,四大汗国中的托雷汗国和希瓦利汗国逐渐被其他势力所吞并,金帐汗国和玛吉斯汗国则成为了后来俄罗斯和波兰立陶宛联邦的一部分。"}}]}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

示例三:流处理返回结果

@GetMapping(value = "call03", name = "流处理返回结果")
public Flux<ChatResponse> call03(@RequestParam(value = "message", defaultValue = "蒙古四大汗国之间的关系") String message) {
    return ollamaChatModel.stream(new Prompt(new UserMessage(message)));
}

单元测试

@RunWith(SpringRunner.class)
@AutoConfigureWebTestClient(timeout = "300000")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OllamaControllerFluxTest {

    @Resource
    private WebTestClient webClient;

    @Test
    public void streamTest() throws Exception {
        String url = "/ollama/call03";
        String param = "message";
        String content = "蒙古四大汗国之间的关系?";
        webClient.get().uri(build -> build.path(url).queryParam(param, content).build()).header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).exchange().expectStatus().isOk();
    }
}

参考文档