SpringAI Kotlin 本地调用 Ollama

这里写目录标题

  • [1 Ollama 软件下载](#1 Ollama 软件下载)
  • [2 Ollama 模型下载](#2 Ollama 模型下载)
    • [2.1 Ollama下载脚本](#2.1 Ollama下载脚本)
    • [2.3 Ollama模型列表](#2.3 Ollama模型列表)
    • [2.3 Ollama模型运行](#2.3 Ollama模型运行)
  • [3 Kotlin SpringAI 配置](#3 Kotlin SpringAI 配置)
    • [1 build.gradle.kts](#1 build.gradle.kts)
    • [2 settings.gradle.kts](#2 settings.gradle.kts)
    • [3 application.yml](#3 application.yml)
  • [4 Kotlin SpringAI 代码](#4 Kotlin SpringAI 代码)
    • [1 LogUtil.kt](#1 LogUtil.kt)
    • [2 MemConf.kt](#2 MemConf.kt)
    • [3 OllamaController.kt](#3 OllamaController.kt)
    • [4 Application.kt](#4 Application.kt)
  • [5 Kotlin SpringAI 结果](#5 Kotlin SpringAI 结果)

LangChain4j VS Spring AI

维度 LangChain4j Spring AI
定位 专注于 LLM 应用开发的工具链,类似 Java 版 LangChain Spring 生态的官方 AI 扩展,提供标准化集成
生态依赖 独立框架,可与任何 Java 框架集成(包括 Spring) 深度依赖 Spring 生态(如 Spring Boot、Spring Cloud)
核心能力 提供完整的 LLM 应用组件(链、记忆、工具等) 聚焦于模型接口标准化、数据处理、集成能力
模型支持 支持主流闭源/开源模型,配置灵活 支持主流模型,通过统一接口抽象,符合 Spring 风格
易用性 需手动组合组件,灵活性高,但学习成本略高 遵循 Spring 约定优于配置,开箱即用,适合 Spring 开发者
企业级特性 需自行集成安全、监控等特性 天然支持 Spring 生态的企业级特性(如安全、审计、监控)
社区与更新 社区活跃,更新频率中等 官方背书,更新频繁,与 Spring 版本同步演进
适用场景 复杂 LLM 应用(如多链协作、自定义记忆策略) 快速集成 LLM 到 Spring 应用,追求标准化和稳定性

选择建议

  • 若项目已基于 Spring 生态(如 Spring Boot),且希望快速集成 LLM 并利用 Spring 的企业级特性,优先选择 Spring AI
  • 若需要更灵活的 LLM 应用构建能力,或项目不依赖 Spring 框架,LangChain4j 是更合适的选择。
  • 若需实现复杂的 RAG、多轮对话记忆或工具调用逻辑,LangChain4j 的组件化设计可能更具优势。

1 Ollama 软件下载

Ollama

2 Ollama 模型下载

2.1 Ollama下载脚本

Win11 下载脚本

复制内容到ollama_download.ps1文件中

powershell 复制代码
# 定义模型名称避免硬编码
$modelName = "gemma3:27b"

# 设置重试参数
$maxRetries = 50
$retryInterval = 3  # 秒
$downloadTimeout = 80  # 秒

for ($retry = 1; $retry -le $maxRetries; $retry++) {
    # 精确检查模型是否存在
    $modelExists = ollama list | Where-Object { $_ -match "\b$modelName\b" }
    
    if ($modelExists) {
        Write-Host "[$(Get-Date)] model is already downloaded!"
        exit 0
    }

    Write-Host "[$(Get-Date)] start $retry times downloading..."
    
    # 启动进程并显示实时输出
    $process = Start-Process -FilePath "ollama" `
        -ArgumentList "run", $modelName `
        -PassThru `
        -NoNewWindow

    # 等待下载完成或超时
    try {
        $process | Wait-Process -Timeout $downloadTimeout -ErrorAction Stop
    } catch {
        # 超时处理
        Write-Host "download timeout, safe terminate process..."
        $process | Stop-Process -Force
    }

    if (-not $process.HasExited) {
        $process | Stop-Process -Force
        Write-Host "process terminated due to timeout."
    } else {
        # 检查退出代码
        if ($process.ExitCode -eq 0) {
            Write-Host "download success!"
            exit 0
        }
        Write-Host "download failed, exit code: $($process.ExitCode)"
    }

    Start-Sleep -Seconds $retryInterval
}

Write-Host "exceeded maximum retries ($maxRetries), download failed."
exit 1
powershell 复制代码
PS C:\Users\xuhya\Desktop> .\ollama_download.ps1
[10/09/2025 21:42:20] start 1 times downloading...
pulling manifest
pulling e796792eba26:  98% ▕████████████████████████████████████████████████████████  ▏  17 GB/ 17 GB  1.7 MB/s   3m20sdownload timeout, safe terminate process...
download failed, exit code: -1
[10/09/2025 21:43:43] start 2 times downloading...
pulling manifest
pulling e796792eba26: 100% ▕██████████████████████████████████████████████████████████▏  17 GB
pulling e0a42594d802: 100% ▕██████████████████████████████████████████████████████████▏  358 B
pulling dd084c7d92a3: 100% ▕██████████████████████████████████████████████████████████▏ 8.4 KB
pulling 3116c5225075: 100% ▕██████████████████████████████████████████████████████████▏   77 B
pulling f838f048d368: 100% ▕██████████████████████████████████████████████████████████▏  490 B
verifying sha256 digest ⠸ download timeout, safe terminate process...
download failed, exit code: -1
[10/09/2025 21:45:06] start 3 times downloading...
pulling manifest
pulling e796792eba26: 100% ▕██████████████████████████████████████████████████████████▏  17 GB
pulling e0a42594d802: 100% ▕██████████████████████████████████████████████████████████▏  358 B
pulling dd084c7d92a3: 100% ▕██████████████████████████████████████████████████████████▏ 8.4 KB
pulling 3116c5225075: 100% ▕██████████████████████████████████████████████████████████▏   77 B
pulling f838f048d368: 100% ▕██████████████████████████████████████████████████████████▏  490 B
verifying sha256 digest
writing manifest
success
⠏ download timeout, safe terminate process...
download failed, exit code: -1
[10/09/2025 21:46:29] model is already downloaded!
PS C:\Users\xuhya\Desktop>

2.3 Ollama模型列表

powershell 复制代码
PS C:\Users\hyacinth\Desktop> ollama list
NAME                      ID              SIZE      MODIFIED
llava-llama3:latest       44c161b1f465    5.5 GB    4 days ago
qwen3:latest              500a1f067a9f    5.2 GB    4 days ago
llama3.2-vision:latest    6f2f9757ae97    7.8 GB    4 days ago
deepseek-r1:latest        6995872bfe4c    5.2 GB    4 days ago
deepseek-coder:6.7b       ce298d984115    3.8 GB    4 days ago
gemma3:4b                 a2af6cc3eb7f    3.3 GB    4 days ago
llama3.2:latest           a80c4f17acd5    2.0 GB    4 days ago
PS C:\Users\hyacinth\Desktop>

2.3 Ollama模型运行

powershell 复制代码
PS C:\Users\hyacinth\Desktop> ollama ps
NAME                      ID              SIZE     PROCESSOR    CONTEXT    UNTIL
llama3.2-vision:latest    6f2f9757ae97    34 GB    100% CPU     262144     2 minutes from now
PS C:\Users\hyacinth\Desktop>

3 Kotlin SpringAI 配置

1 build.gradle.kts

kts 复制代码
plugins {
    // Kotlin JVM 插件,用于支持 Kotlin 语言编译到 JVM 字节码,版本 1.9.25。
	kotlin("jvm") version "1.9.25"
    // Kotlin 官方提供的 Spring 插件,用于增强 Kotlin 与 Spring 的兼容性(例如自动开启 open 类支持,因为 Spring AOP 需要类可继承),版本与 Kotlin 一致。
	kotlin("plugin.spring") version "1.9.25"
    // Spring Boot 插件,提供 Spring Boot 特有的构建功能(如打包可执行 JAR、自动配置依赖版本等),版本 3.5.6。
	id("org.springframework.boot") version "3.5.6"
    // Spring 依赖管理插件,用于统一管理项目依赖的版本(配合 dependencyManagement 块使用),版本 1.1.7。
	id("io.spring.dependency-management") version "1.1.7"
}

group = "com.xu"
version = "1.0.0"
description = "Kotlin AI 示例项目,集成 Spring AI 和 Ollama 大模型"

// 定义项目使用的 Java 版本为 25,Gradle 会自动寻找或下载对应版本的 JDK 进行编译、运行。
//java {
//	toolchain {
//        // toolchain 是 Gradle 推荐的配置方式,比传统的 sourceCompatibility 更灵活,支持自动管理 JDK 版本。
//		languageVersion = JavaLanguageVersion.of(25)
//	}
//}


configurations {
    // 这里配置 compileOnly 继承 annotationProcessor 配置,确保注解处理器(如 Lombok)在编译时能被正确识别。
	compileOnly {
		extendsFrom(configurations.annotationProcessor.get())
	}
}

repositories {
    // 声明项目依赖的仓库为 Maven Central(中央仓库),Gradle 会从这里下载所需的依赖包。
	mavenCentral()
}

// 定义一个名为 springAiVersion 的全局属性,值为 1.0.3,用于统一管理 Spring AI 相关依赖的版本(后续可通过 ${property("springAiVersion")} 引用)。
extra["springAiVersion"] = "1.0.3"

dependencies {
    // Spring Boot Web  Starter(包含Spring MVC、Tomcat等Web相关依赖)
    implementation("org.springframework.boot:spring-boot-starter-web")
    // Jackson Kotlin模块(支持Kotlin数据类与JSON的序列化/反序列化)
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    // Kotlin反射库(Spring等框架需要反射支持)
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    // Spring AI Ollama Starter(集成Ollama大模型的依赖)
    implementation("org.springframework.ai:spring-ai-starter-model-ollama")
    // Lombok(编译时生成getter/setter等代码,仅编译时需要)
    compileOnly("org.projectlombok:lombok")
    // Spring Boot开发工具(热部署等开发期功能,仅开发环境需要)
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    // Lombok注解处理器(编译时处理Lombok注解)
    annotationProcessor("org.projectlombok:lombok")
    // Spring Boot测试 Starter(包含JUnit、Mockito等测试依赖)
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    // Kotlin测试与JUnit5集成
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    // JUnit平台启动器(支持IDE运行JUnit5测试)
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

// 通过导入 Maven BOM(Bill of Materials) 统一管理 Spring AI 相关依赖的版本。
dependencyManagement {
	imports {
		mavenBom("org.springframework.ai:spring-ai-bom:${property("springAiVersion")}")
	}
}

// 配置 Kotlin 编译器选项:
kotlin {
    // -Xjsr305=strict:启用 JSR-305 注解(如 @Nullable、@Nonnull)的严格检查,增强代码的空安全。
	compilerOptions {
		freeCompilerArgs.addAll("-Xjsr305=strict")
	}
}

// 单元测试
tasks.withType<Test> {
	useJUnitPlatform()
}

2 settings.gradle.kts

kts 复制代码
rootProject.name = "kotlin-ai"

3 application.yml

yml 复制代码
spring:
  application:
    name: spring-ai
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: llama3.2-vision:latest
      init:
        timeout: 50m
  servlet:
    multipart:
      enabled: true
      max-file-size: 10MB
      max-request-size: 10MB
      file-size-threshold: 10MB

4 Kotlin SpringAI 代码

1 LogUtil.kt

java 复制代码
package com.xu.conf

import org.slf4j.Logger
import org.slf4j.LoggerFactory

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogUtil(){
    companion object{
        val <reified T> T.log: Logger
            inline get() = LoggerFactory.getLogger(T::class.java)
    }
}

2 MemConf.kt

java 复制代码
package com.xu.conf

import com.xu.conf.LogUtil.Companion.log
import org.springframework.ai.chat.memory.ChatMemory
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository
import org.springframework.ai.chat.memory.MessageWindowChatMemory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@LogUtil
@Configuration
class MemConf {

    @Bean
    fun chatMemory(): ChatMemory {
        log.info("初始化聊天记忆成功!!!")
        return MessageWindowChatMemory.builder()
            .chatMemoryRepository(InMemoryChatMemoryRepository())
            .maxMessages(20)
            .build()
    }

}

3 OllamaController.kt

java 复制代码
package com.xu.controller

import com.xu.conf.LogUtil
import com.xu.conf.LogUtil.Companion.log
import org.springframework.ai.chat.memory.ChatMemory
import org.springframework.ai.chat.messages.UserMessage
import org.springframework.ai.chat.prompt.Prompt
import org.springframework.ai.content.Media
import org.springframework.ai.ollama.OllamaChatModel
import org.springframework.http.MediaType
import org.springframework.util.MimeType
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
import reactor.core.publisher.Flux

@LogUtil
@RestController
@RequestMapping("/ollama")
class OllamaController(val chatMemory: ChatMemory, val model: OllamaChatModel) {

    /**
     * 聊天
     *
     * @param content 聊天内容
     * @return 聊天结果
     */
    @RequestMapping("/common/chat")
    fun chat(content: String?): Any? {
        return model.call(content)
    }

    /**
     * 聊天记忆
     *
     * @param content 聊天内容
     * @return 聊天结果
     */
    @RequestMapping("/common/memory")
    fun memory(content: String, msgId: String): Any? {
        val message = UserMessage.builder().text(content).build()
        chatMemory.add(msgId, message)
        val response = model.call(Prompt(message))
        chatMemory.add(msgId, response.result.output)
        return response.result.output.text
    }

    /**
     * 聊天图片识别
     *
     * @param content 聊天内容
     * @return 聊天结果
     */
    @RequestMapping("/common/image")
    fun image(@RequestParam("content") content: String, @RequestParam("file") file: MultipartFile): Any? {
        val t1 = System.currentTimeMillis()
        log.info("聊天图片识别开始...")
        if (file.isEmpty) {
            return model.call(content)
        }
        val media = Media(MimeType("image", "png"), file.resource)
        val prompt = Prompt(UserMessage.builder().media(media).text(content).build())
        val response = model.call(prompt)
        val t2 = System.currentTimeMillis()
        log.info("聊天图片识别结束...${(t2 - t1) / 1000.0}秒")
        return response.result.output.text
    }

    /**
     * 聊天流式
     *
     * @param content 聊天内容
     * @return 聊天结果
     */
    @RequestMapping(value = ["/stream/chat"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun stream(content: String?): Flux<String?>? {
        log.info("开始流式聊天...")
        return model.stream(content)
    }

}

4 Application.kt

kotlin 复制代码
package com.xu

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class KotlinAiApplication

fun main(args: Array<String>) {
    runApplication<KotlinAiApplication>(*args)
    println("当前 Java 版本: " + System.getProperty("java.version"))
    println("当前 Kotlin 版本: " + KotlinVersion.CURRENT)
}

5 Kotlin SpringAI 结果

java 复制代码
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.5.6)

2025-10-21T15:29:48.706+08:00  INFO 26288 --- [spring-ai] [  restartedMain] com.xu.ApplicationKt                     : Starting ApplicationKt using Java 25 with PID 26288 (D:\SourceCode\KotlinLearn\kotlin-ai\build\classes\kotlin\main started by hyacinth in D:\SourceCode\KotlinLearn\kotlin-ai)
2025-10-21T15:29:48.708+08:00  INFO 26288 --- [spring-ai] [  restartedMain] com.xu.ApplicationKt                     : No active profile set, falling back to 1 default profile: "default"
2025-10-21T15:29:48.738+08:00  INFO 26288 --- [spring-ai] [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-10-21T15:29:48.738+08:00  INFO 26288 --- [spring-ai] [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2025-10-21T15:29:49.487+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-10-21T15:29:49.501+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-10-21T15:29:49.501+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.46]
2025-10-21T15:29:49.537+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-10-21T15:29:49.537+08:00  INFO 26288 --- [spring-ai] [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 799 ms
2025-10-21T15:29:49.653+08:00  INFO 26288 --- [spring-ai] [  restartedMain] com.xu.conf.MemConf                      : 初始化聊天记忆成功!!!
2025-10-21T15:29:50.434+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2025-10-21T15:29:50.452+08:00  INFO 26288 --- [spring-ai] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-10-21T15:29:51.464+08:00  INFO 26288 --- [spring-ai] [  restartedMain] com.xu.ApplicationKt                     : Started ApplicationKt in 3.459 seconds (process running for 4.469)
当前 Java 版本: 25
当前 Kotlin 版本: 2.1.20
2025-10-21T15:29:52.290+08:00  INFO 26288 --- [spring-ai] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-10-21T15:29:52.290+08:00  INFO 26288 --- [spring-ai] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-10-21T15:29:52.291+08:00  INFO 26288 --- [spring-ai] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2025-10-21T15:29:52.368+08:00  INFO 26288 --- [spring-ai] [nio-8080-exec-1] com.xu.controller.OllamaController       : 聊天图片识别开始...
2025-10-21T15:31:36.664+08:00  INFO 26288 --- [spring-ai] [nio-8080-exec-1] com.xu.controller.OllamaController       : 聊天图片识别结束...104.295秒
相关推荐
alexhilton1 天前
理解retain{}的内部机制:Jetpack Compose中基于作用域的状态保存
android·kotlin·android jetpack
Sky#boy1 天前
Kotion 常见用法注意事项(持续更新...)
kotlin
奥陌陌1 天前
kotlin className.() 类名点花括号 T.() 这种是什么意思?
kotlin
Coffeeee1 天前
Labubu很难买?那是因为还没有用Compose来画一个
前端·kotlin·android jetpack
纳于大麓2 天前
Kotlin基础语法
linux·windows·kotlin
小张课程2 天前
Kotlin协程完全教程-从基础实践到进阶再到专家(已完结)
kotlin
小张课程2 天前
Kotlin协程完全教程-从基础实践到进阶再到专家 扔物线教程下载
kotlin
小张课程2 天前
Kotlin协程完全教程-从基础实践到进阶再到专家
kotlin
AsiaLYF2 天前
kotlin中MutableStateFlow和MutableSharedFlow的区别是什么?
android·开发语言·kotlin