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秒
相关推荐
QING6181 天前
Jetpack Compose Brush API 简单使用实战 —— 新手指南
android·kotlin·android jetpack
QING6181 天前
Jetpack Compose Brush API 详解 —— 新手指南
android·kotlin·android jetpack
鹿里噜哩1 天前
Spring Authorization Server 打造认证中心(二)自定义数据库表
spring boot·后端·kotlin
用户69371750013841 天前
23.Kotlin 继承:继承的细节:覆盖方法与属性
android·后端·kotlin
Haha_bj1 天前
五、Kotlin——条件控制、循环控制
android·kotlin
Kapaseker1 天前
不卖课,纯干货!Android分层你知多少?
android·kotlin
urkay-2 天前
Android 切换应用语言
android·java·kotlin·iphone·androidx
杀死那个蝈坦2 天前
监听 Canal
java·前端·eclipse·kotlin·bootstrap·html·lua
Yang-Never2 天前
Open GL ES->EGL渲染环境、数据、引擎、线程的创建
android·java·开发语言·kotlin·android studio
urkay-2 天前
Android 全局修改设备的语言设置
android·xml·java·kotlin·iphone