掘金 人工智能 07月04日 14:28
Spring AI 实战指南:模型集成与调优
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了如何使用Spring AI框架选择和调优AI模型,以满足不同的业务需求。文章首先介绍了Spring AI支持的三种主要模型类型:Chat模型、Embedding模型和Image模型,并提供了清晰的示例项目结构和代码片段。接着,文章对比了本地模型和云端模型的优劣,为开发者提供了明智的决策框架。最后,通过实战演示,展示了如何配置和切换不同模型,以及如何通过调整参数使AI更符合需求。

💬 **Chat模型是核心**:Spring AI中的Chat模型专注于文本对话、问答和内容生成,所有Chat模型均实现ChatModel接口,支持OpenAI、DeepSeek、Anthropic、Azure OpenAI和Ollama等提供商。

🔎 **Embedding模型用于语义理解**:Embedding模型将文本转化为数值向量,用于检索、相似度计算和文档分类等场景,是构建RAG系统的基础,例如文档相似度搜索、智能推荐系统、文本分类和聚类、RAG系统的向量检索等。

🖼️ **Image模型实现视觉智能**:Image模型能够理解和生成图像,实现图像理解和生成,支持多模态交互,OpenAI的GPT-4 Vision, DALL-E和StabilityAI的Stable Diffusion 系列。

⚙️ **本地模型与云端模型抉择**:云端模型性能强劲、开箱即用,但成本较高且依赖网络;本地模型成本可控、数据安全,但对硬件要求较高且部署复杂,需要根据应用场景权衡选择。

🔧 **参数调优是关键**:Temperature、Top P 和 Max Tokens等参数会显著影响AI的行为表现,温度(Temperature)控制输出的随机性和创造性,核采样(Top P)控制候选词汇的范围,最大令牌数(Max Tokens)限制输出的长度。

模型集成与调优:选择最适合你的 AI 大脑

在前两篇文章中,我们已经成功搭建了第一个 Spring AI 应用,并学会了 Prompt 工程的基本技巧。今天,我们将进入一个更加核心的话题:如何选择和调优最适合你业务需求的 AI 模型

如果说 Prompt 是与 AI 对话的语言,那么模型就是 AI 的"大脑"。不同的模型有着不同的"性格":有的擅长文本对话,有的专精图像理解,有的则在代码生成方面表现出色。选择合适的模型,就像为不同的任务挑选合适的工具一样重要。

更重要的是,Spring AI 的设计理念让模型切换变得如同更换汽车引擎一样简单——你不需要重写整个应用,只需要调整配置文件即可。这种"模型即插即用"的特性,为我们的应用带来了极大的灵活性。

3.1 Spring AI 支持的模型类型全景

Spring AI 作为一个企业级的 AI 集成框架,支持三大类型的 AI 模型,每一类都有其独特的应用场景:

3.1.1 Chat 模型:AI 对话的核心

Chat 模型是我们最熟悉的类型,它专门用于文本对话、问答、内容生成等任务。在 Spring AI 中,所有的 Chat 模型都实现了统一的 ChatModel 接口。

主要特点:

支持的提供商:

3.1.2 Embedding 模型:文本的"指纹识别器"

Embedding 模型将文本转换为数值向量,这些向量捕获了文本的语义信息。虽然它不能生成文本,但在检索、相似度计算、文档分类等场景中不可或缺。

主要特点:

典型应用场景:

3.1.3 Image 模型:视觉理解的智能

Image 模型可以理解和生成图像,为我们的应用增加视觉智能。

主要功能:

支持的提供商:

3.2 创建模型集成演示项目

理论说得再多,不如实际动手感受。让我们创建一个全新的项目,演示如何在 Spring AI 中集成和使用不同类型的模型。

项目结构设计

model-integration-demo/├── src/main/java/com/example/modelintegration/│   ├── ModelIntegrationDemoApplication.java│   ├── config/│   │   ├── ChatClientFactory.java             # ChatClient 工厂类│   │   ├── ChatClientFactoryConfig.java       # 工厂配置类│   │   ├── EmbeddingConfig.java               # Embedding 配置类│   │   ├── EmbeddingService.java              # Embedding 服务│   │   └── SystemPrompts.java                 # 系统提示常量│   ├── controller/│   │   ├── ChatModelController.java           # Chat 模型演示│   │   ├── EmbeddingModelController.java      # Embedding 模型演示│   │   ├── ModelSelectionController.java      # 模型选择演示│   │   └── ParameterTuningController.java     # 参数调优控制器│   └── service/│       ├── ModelSelectionService.java         # 模型选择服务│       ├── ParameterTuningService.java        # 参数调优服务│       └── ChatMetrics.java                   # 聊天指标监控├── src/main/resources/│   ├── application.properties                 # 基础配置│   ├── application-openai.properties          # OpenAI 配置│   ├── application-deepseek.properties        # DeepSeek 配置│   └── application-ollama.properties          # 本地 Ollama 配置├── test_apis.sh                               # API 测试脚本├── run.sh                                     # 启动脚本├── README.md                                  # 项目说明├── CLAUDE.md                                  # Claude 使用指南└── pom.xml

Maven 依赖配置

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>3.4.7</version>        <relativePath/>    </parent>    <groupId>com.example</groupId>    <artifactId>model-integration-demo</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>model-integration-demo</name>    <description>Spring AI Model Integration and Tuning Demo</description>    <properties>        <java.version>21</java.version>        <spring-ai.version>1.0.0</spring-ai.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>                <!-- Spring Boot Actuator for monitoring -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-actuator</artifactId>        </dependency>        <!-- Micrometer for metrics -->        <dependency>            <groupId>io.micrometer</groupId>            <artifactId>micrometer-registry-prometheus</artifactId>        </dependency>        <!-- Spring AI Chat 模型支持 -->        <dependency>            <groupId>org.springframework.ai</groupId>            <artifactId>spring-ai-starter-model-deepseek</artifactId>        </dependency>        <!-- 根据需求切换模型 -->        <!--        <dependency>-->        <!--            <groupId>org.springframework.ai</groupId>-->        <!--            <artifactId>spring-ai-starter-model-ollama</artifactId>-->        <!--        </dependency>-->        <!--        <dependency>-->        <!--            <groupId>org.springframework.ai</groupId>-->        <!--            &lt;!&ndash; model OpenAI 已集成 Embedding&ndash;&gt;-->        <!--            <artifactId>spring-ai-starter-model-openai</artifactId>-->        <!--        </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></project>

3.3 本地模型 vs 云端模型:如何做出明智选择

在选择 AI 模型时,我们面临的第一个重要决策就是:使用本地模型还是云端模型?这个选择会影响到成本、性能、隐私、部署复杂度等多个方面。

3.3.1 云端模型的优势与劣势

优势:

    性能强劲:云端模型通常参数量更大,性能更强开箱即用:无需担心硬件要求,配置简单持续更新:模型提供商会定期优化和更新模型多样化选择:可以根据需求选择不同特色的模型

劣势:

    成本累积:按使用量付费,大规模使用成本较高网络依赖:需要稳定的网络连接数据隐私:需要将数据发送到第三方服务器服务限制:可能面临速率限制、服务中断等问题

3.3.2 本地模型的优势与劣势

优势:

    成本可控:一次性部署成本,不按使用量付费数据安全:数据不离开本地环境可定制性:可以对模型进行微调和优化网络独立:不依赖外部网络连接

劣势:

    硬件要求:需要较高的 GPU 资源部署复杂:需要更多的技术投入维护成本:需要自行管理模型更新和优化性能限制:受本地硬件限制,可能不如云端模型

3.3.3 决策框架:如何选择

使用场景推荐方案理由
原型开发/学习云端模型快速上手,专注业务逻辑
小规模生产应用云端模型成本可控,运维简单
大规模生产应用混合方案核心功能用本地,辅助功能用云端
高隐私要求本地模型数据不出本地环境
低延迟要求本地模型避免网络延迟
成本敏感本地模型长期使用成本更低

3.4 实战演示:多模型配置与切换

让我们通过实际代码演示如何在 Spring AI 中配置和切换不同的模型。

3.4.1 配置文件设计

首先,我们为不同的模型提供商创建独立的配置文件:

application.properties(基础配置):

spring.application.name=model-integration-demo# 激活特定的配置档案(可通过环境变量覆盖)spring.profiles.active=deepseek# 应用端口server.port=8080# 日志配置logging.level.org.springframework.ai=DEBUGlogging.level.com.example.modelintegration=DEBUG# Actuator 监控端点配置management.endpoints.web.exposure.include=health,info,metrics,prometheusmanagement.endpoint.health.show-details=when-authorizedmanagement.info.env.enabled=true# 应用信息info.app.name=@project.name@info.app.description=@project.description@info.app.version=@project.version@info.app.encoding=@project.build.sourceEncoding@info.app.java.version=@java.version@

application-deepseek.properties(DeepSeek 配置):

# DeepSeek Chat 模型配置spring.ai.deepseek.api-key=${DEEPSEEK_API_KEY}spring.ai.deepseek.chat.options.model=deepseek-chatspring.ai.deepseek.chat.options.temperature=0.7spring.ai.deepseek.chat.options.max-tokens=1000# 重试配置spring.ai.deepseek.retry.max-attempts=3spring.ai.deepseek.retry.backoff.delay=1sspring.ai.deepseek.retry.backoff.multiplier=2# 超时配置spring.ai.deepseek.chat.options.timeout=30s# 环境信息info.model.provider=DeepSeekinfo.model.name=deepseek-chatinfo.model.type=Chat Model

application-openai.properties(OpenAI 配置):

# OpenAI Chat 模型配置spring.ai.openai.api-key=${OPENAI_API_KEY}spring.ai.openai.chat.options.model=gpt-3.5-turbospring.ai.openai.chat.options.temperature=0.7spring.ai.openai.chat.options.max-tokens=1000# OpenAI Embedding 模型配置spring.ai.openai.embedding.enabled=truespring.ai.openai.embedding.options.model=text-embedding-3-small# 重试配置spring.ai.openai.retry.max-attempts=3spring.ai.openai.retry.backoff.delay=1sspring.ai.openai.retry.backoff.multiplier=2# 超时配置spring.ai.openai.chat.options.timeout=30s# 禁用其他模型配置(避免冲突)spring.ai.deepseek.enabled=falsespring.ai.ollama.enabled=false# 环境信息info.model.provider=OpenAIinfo.model.name=gpt-3.5-turboinfo.model.type=Chat Model + Embedding Model

application-ollama.properties(本地 Ollama 配置):

# Ollama 本地模型配置spring.ai.ollama.base-url=http://localhost:11434spring.ai.ollama.chat.options.model=llama2spring.ai.ollama.chat.options.temperature=0.7# 环境信息info.model.provider=Ollama (Local)info.model.name=llama2info.model.type=Local Chat Model

3.4.2 ChatClient 工厂类和配置

SystemPrompts.java(系统提示常量):

package com.example.modelintegration.config;/** * 系统提示常量类 * 集中管理不同模型和用途的系统提示 */public class SystemPrompts {    public static final String PRIMARY =            "你是一个专业的 AI 助手,擅长技术解答和代码分析。请用简洁明了的语言回答问题。";    public static final String CODE = """            你是一个资深的软件工程师,专门负责代码审查和分析。            请用专业但通俗易懂的语言回答问题,并提供具体的代码示例。            回答时请包含以下方面:            1. 代码功能分析            2. 潜在问题识别            3. 改进建议            4. 最佳实践推荐            """;    public static final String CREATIVE = """            你是一个富有创造力的作家,擅长创意写作和内容创作。            请用生动有趣的语言风格回答问题,适当使用比喻和形象化的表达。            """;}

ChatClientFactory.java(ChatClient 工厂类):

package com.example.modelintegration.config;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.model.ChatModel;/** * ChatClient 工厂类 * 用于创建不同类型的 ChatClient 实例 */public class ChatClientFactory {    private final String provider;    private final ChatModel chatModel;    private final ChatClient primaryChatClient;    private final ChatClient codeChatClient;    private final ChatClient creativeChatClient;    public ChatClientFactory(String provider, ChatModel chatModel) {        this.provider = provider;        this.chatModel = chatModel;        primaryChatClient = createChatClient(SystemPrompts.PRIMARY);        codeChatClient = createChatClient(SystemPrompts.CODE);        creativeChatClient = createChatClient(SystemPrompts.CREATIVE);    }    public ChatClient getPrimaryChatClient() {        return primaryChatClient;    }    public ChatClient getCodeChatClient() {        return codeChatClient;    }    public ChatClient getCreativeChatClient() {        return creativeChatClient;    }    public ChatModel getChatModel() {        return chatModel;    }    private ChatClient createChatClient(String prompt) {        return ChatClient.builder(chatModel)                .defaultSystem(prompt)                .build();    }    public String getProviderName() {        return provider;    }}

ChatClientFactoryConfig.java(工厂配置类):

package com.example.modelintegration.config;import org.springframework.ai.chat.model.ChatModel;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class ChatClientFactoryConfig {    @Value("${info.model.provider:Unknown}")    private String provider;    @Bean    public ChatClientFactory chatClientFactory(ChatModel chatModel) {        return new ChatClientFactory(provider, chatModel);    }}

3.4.3 Chat 模型演示控制器

package com.example.modelintegration.controller;import com.example.modelintegration.config.ChatClientFactory;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.model.ChatResponse;import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController@RequestMapping("/api/chat")public class ChatModelController {    private final ChatClient primaryChatClient;    private final ChatClient codeChatClient;    private final ChatClient creativeChatClient;    public ChatModelController(ChatClientFactory factory) {        this.primaryChatClient = factory.getPrimaryChatClient();        this.codeChatClient = factory.getCodeChatClient();        this.creativeChatClient = factory.getCreativeChatClient();    }    /**     * 基础对话接口     */    @PostMapping("/basic")    public Map<String, Object> basicChat(@RequestBody Map<String, String> request) {        String message = request.get("message");        ChatResponse response = primaryChatClient.prompt()                .user(message)                .call()                .chatResponse();        return Map.of(                "response", response.getResult().getOutput().getText(),                "model", response.getMetadata().getModel(),                "usage", response.getMetadata().getUsage(),                "timestamp", System.currentTimeMillis()        );    }    /**     * 代码分析专用接口     */    @PostMapping("/code-analysis")    public Map<String, Object> analyzeCode(@RequestBody Map<String, String> request) {        String code = request.get("code");        String question = request.get("question");        String prompt = String.format("""                请分析以下代码:                                \`\`\`                %s                \`\`\`                                问题:%s                                请提供详细的分析和建议。                """, code, question);        ChatResponse response = codeChatClient.prompt()                .user(prompt)                .call()                .chatResponse();        return Map.of(                "analysis", response.getResult().getOutput().getText(),                "model", response.getMetadata().getModel(),                "usage", response.getMetadata().getUsage(),                "codeLength", code.length(),                "timestamp", System.currentTimeMillis()        );    }    /**     * 创意写作接口     */    @PostMapping("/creative-writing")    public Map<String, Object> creativeWriting(@RequestBody Map<String, String> request) {        String topic = request.get("topic");        String style = request.getOrDefault("style", "随意");        String prompt = String.format("""                请以'%s'风格,围绕'%s'这个主题进行创作。                可以是诗歌、小故事、散文或其他创意形式。                """, style, topic);        ChatResponse response = creativeChatClient.prompt()                .user(prompt)                .call()                .chatResponse();        return Map.of(                "content", response.getResult().getOutput().getText(),                "topic", topic,                "style", style,                "model", response.getMetadata().getModel(),                "usage", response.getMetadata().getUsage(),                "timestamp", System.currentTimeMillis()        );    }    /**     * 简单的健康检查接口     */    @GetMapping("/health")    public Map<String, Object> health() {        return Map.of(                "status", "OK",                "service", "Chat Model Controller",                "timestamp", System.currentTimeMillis()        );    }}

3.5 参数调优:让 AI 更听话

不同的模型参数会显著影响 AI 的行为表现。理解这些参数的作用,是调优模型的关键。

3.5.1 核心参数详解

Temperature(温度)

作用: 控制输出的随机性和创造性

Top P(核采样)

作用: 控制候选词汇的范围

Max Tokens(最大令牌数)

作用: 限制输出的长度

3.5.2 参数调优服务实现

package com.example.modelintegration.service;import com.example.modelintegration.config.ChatClientFactory;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.chat.prompt.ChatOptions;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@Servicepublic class ParameterTuningService {    private final ChatModel chatModel;    public ParameterTuningService(ChatClientFactory factory) {        this.chatModel = factory.getChatModel();    }    /**     * 测试不同 Temperature 值的效果     */    public List<Map<String, Object>> testTemperatureEffects(String prompt) {        double[] temperatures = {0.0, 0.3, 0.7, 1.0, 1.5};        List<Map<String, Object>> results = new ArrayList<>();        for (double temperature : temperatures) {            try {                ChatClient client = ChatClient.builder(chatModel)                        .defaultOptions(ChatOptions.builder()                                .temperature(temperature)                                .maxTokens(200)                                .build())                        .build();                long startTime = System.currentTimeMillis();                String response = client.prompt()                        .user(prompt)                        .call()                        .content();                long duration = System.currentTimeMillis() - startTime;                results.add(Map.of(                        "temperature", temperature,                        "response", response,                        "responseLength", response.length(),                        "durationMs", duration,                        "status", "success"                ));            } catch (Exception e) {                results.add(Map.of(                        "temperature", temperature,                        "error", e.getMessage(),                        "status", "failed"                ));            }        }        return results;    }    /**     * 对比不同参数组合的效果     */    public Map<String, Object> compareParameterCombinations(String prompt) {        Map<String, Object> results = new HashMap<>();        // 保守配置:适合需要准确性的场景        results.put("conservative", testConfiguration(prompt, 0.0, 0.1, 200,                 "保守配置 - 适合需要准确答案的场景"));        // 平衡配置:适合一般对话场景        results.put("balanced", testConfiguration(prompt, 0.7, 0.9, 500,                "平衡配置 - 适合日常对话场景"));        // 创造性配置:适合内容创作场景        results.put("creative", testConfiguration(prompt, 1.2, 0.95, 800,                "创造性配置 - 适合内容创作场景"));        return results;    }    /**     * 获取参数调优建议     */    public Map<String, Object> getParameterRecommendations(String useCase) {        Map<String, Object> recommendations = new HashMap<>();        switch (useCase.toLowerCase()) {            case "qa":                recommendations.put("temperature", 0.0);                recommendations.put("topP", 0.1);                recommendations.put("maxTokens", 200);                recommendations.put("description", "问答系统 - 需要准确、一致的答案");                break;            case "creative":                recommendations.put("temperature", 1.2);                recommendations.put("topP", 0.95);                recommendations.put("maxTokens", 800);                recommendations.put("description", "创意写作 - 需要创造性和多样性");                break;            case "chat":                recommendations.put("temperature", 0.7);                recommendations.put("topP", 0.9);                recommendations.put("maxTokens", 500);                recommendations.put("description", "日常对话 - 平衡准确性和自然性");                break;            case "code":                recommendations.put("temperature", 0.2);                recommendations.put("topP", 0.3);                recommendations.put("maxTokens", 1000);                recommendations.put("description", "代码生成 - 需要精确的语法和逻辑");                break;            default:                recommendations.put("temperature", 0.7);                recommendations.put("topP", 0.9);                recommendations.put("maxTokens", 500);                recommendations.put("description", "通用场景 - 平衡配置");        }        recommendations.put("useCase", useCase);        recommendations.put("timestamp", System.currentTimeMillis());        return recommendations;    }    private Map<String, Object> testConfiguration(String prompt, double temperature,                                                   double topP, int maxTokens, String description) {        try {            ChatClient client = ChatClient.builder(chatModel)                    .defaultOptions(ChatOptions.builder()                            .temperature(temperature)                            .topP(topP)                            .maxTokens(maxTokens)                            .build())                    .build();            long startTime = System.currentTimeMillis();            String response = client.prompt()                    .user(prompt)                    .call()                    .content();            long duration = System.currentTimeMillis() - startTime;            return Map.of(                    "description", description,                    "parameters", Map.of(                            "temperature", temperature,                            "topP", topP,                            "maxTokens", maxTokens                    ),                    "response", response,                    "responseLength", response.length(),                    "durationMs", duration,                    "status", "success"            );        } catch (Exception e) {            return Map.of(                    "description", description,                    "parameters", Map.of(                            "temperature", temperature,                            "topP", topP,                            "maxTokens", maxTokens                    ),                    "error", e.getMessage(),                    "status", "failed"            );        }    }}

3.5.3 参数调优演示控制器

package com.example.modelintegration.controller;import com.example.modelintegration.service.ParameterTuningService;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.Map;@RestController@RequestMapping("/api/tuning")public class ParameterTuningController {    private final ParameterTuningService tuningService;    public ParameterTuningController(ParameterTuningService tuningService) {        this.tuningService = tuningService;    }    /**     * 测试不同 Temperature 的效果     */    @PostMapping("/temperature-test")    public Map<String, Object> testTemperature(@RequestBody Map<String, String> request) {        String prompt = request.get("prompt");                if (prompt == null || prompt.trim().isEmpty()) {            return Map.of(                "error", "Prompt cannot be empty",                "status", "failed"            );        }                List<Map<String, Object>> results = tuningService.testTemperatureEffects(prompt);                return Map.of(            "prompt", prompt,            "results", results,            "totalTests", results.size(),            "description", "不同temperature值对输出随机性和创造性的影响测试",            "timestamp", System.currentTimeMillis()        );    }    /**     * 对比不同参数组合     */    @PostMapping("/parameter-comparison")    public Map<String, Object> compareParameters(@RequestBody Map<String, String> request) {        String prompt = request.get("prompt");                if (prompt == null || prompt.trim().isEmpty()) {            return Map.of(                "error", "Prompt cannot be empty",                "status", "failed"            );        }                Map<String, Object> results = tuningService.compareParameterCombinations(prompt);                return Map.of(            "prompt", prompt,            "configurations", results,            "description", "保守、平衡、创造性三种参数配置的对比测试",            "recommendations", Map.of(                "conservative", "适合需要准确答案的场景,如问答系统、事实查询",                "balanced", "适合日常对话场景,平衡准确性和自然性",                "creative", "适合创意写作、头脑风暴等需要发散思维的场景"            ),            "timestamp", System.currentTimeMillis()        );    }    /**     * 获取参数调优建议     */    @GetMapping("/recommendations/{useCase}")    public Map<String, Object> getRecommendations(@PathVariable String useCase) {        return tuningService.getParameterRecommendations(useCase);    }    /**     * 获取所有支持的用例类型     */    @GetMapping("/use-cases")    public Map<String, Object> getUseCases() {        return Map.of(            "useCases", Map.of(                "qa", "问答系统 - 需要准确、一致的答案",                "creative", "创意写作 - 需要创造性和多样性",                "chat", "日常对话 - 平衡准确性和自然性",                "code", "代码生成 - 需要精确的语法和逻辑"            ),            "parameterGuide", Map.of(                "temperature", "控制输出随机性,0.0最确定,2.0最随机",                "topP", "核采样,控制候选词汇范围,0.1最集中,1.0最多样",                "maxTokens", "最大输出长度,根据需求设置"            ),            "timestamp", System.currentTimeMillis()        );    }    /**     * 健康检查     */    @GetMapping("/health")    public Map<String, Object> health() {        return Map.of(            "status", "OK",            "service", "Parameter Tuning Controller",            "timestamp", System.currentTimeMillis()        );    }}

3.6 模型路由切换

3.6.1 模型选择服务

package com.example.modelintegration.service;import java.util.HashMap;import java.util.Map;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ConcurrentHashMap;import com.example.modelintegration.config.ChatClientFactory;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;@Servicepublic class ModelSelectionService {    private final Map<String, ChatClient> chatClients;    private final Map<String, String> modelDescriptions;    public ModelSelectionService(ChatClientFactory clientFactory) {        // 创建不同配置的客户端        this.chatClients = new HashMap<>();        this.modelDescriptions = new HashMap<>();        // 基础模型        this.chatClients.put("default", clientFactory.getPrimaryChatClient());        this.modelDescriptions.put("default", "通用助手,适合日常问答");        // 代码分析模型        this.chatClients.put("technical", clientFactory.getCodeChatClient());        this.modelDescriptions.put("technical", "技术专家,专注代码分析和技术解答");        // 创意写作模型        this.chatClients.put("creative", clientFactory.getCreativeChatClient());        this.modelDescriptions.put("creative", "创意作家,擅长文学创作和内容创意");        // 业务咨询模型        this.chatClients.put("business", ChatClient.builder(clientFactory.getChatModel())                .defaultSystem("""                        你是一个资深的商业顾问,擅长商业分析、市场策略和企业管理。                        请用专业且易懂的语言回答商业相关问题。                        """)                .build());        this.modelDescriptions.put("business", "商业顾问,专注商业分析和策略规划");    }    /**     * 根据任务类型选择合适的模型配置     */    public String processWithBestModel(String input, String taskType) {        ChatClient selectedClient = selectModelForTask(taskType);        return selectedClient.prompt()                .user(input)                .call()                .content();    }    /**     * 智能任务路由 - 根据输入内容自动选择最合适的模型     */    public Map<String, Object> smartTaskRouting(String input) {        String taskType = detectTaskType(input);        ChatClient selectedClient = selectModelForTask(taskType);        long startTime = System.currentTimeMillis();        String response = selectedClient.prompt()                .user(input)                .call()                .content();        long duration = System.currentTimeMillis() - startTime;        return Map.of(                "input", input,                "detectedTaskType", taskType,                "selectedModel", getModelNameForTask(taskType),                "response", response,                "duration", duration,                "timestamp", System.currentTimeMillis()        );    }    /**     * 获取所有可用的模型信息     */    public Map<String, Object> getAvailableModels() {        Map<String, Object> models = new HashMap<>();        chatClients.keySet().forEach(modelType -> {            models.put(modelType, Map.of(                    "name", getModelNameForTask(modelType),                    "description", modelDescriptions.get(modelType),                    "suitableFor", getSuitableTasksForModel(modelType)            ));        });        return Map.of(                "models", models,                "totalCount", models.size(),                "timestamp", System.currentTimeMillis()        );    }    private ChatClient selectModelForTask(String taskType) {        return switch (taskType.toLowerCase()) {            case "creative", "writing", "story" -> chatClients.get("creative");            case "technical", "code", "programming" -> chatClients.get("technical");            case "business", "strategy", "analysis" -> chatClients.get("business");            default -> chatClients.get("default");        };    }    private String detectTaskType(String input) {        String lowerInput = input.toLowerCase();        // 代码相关关键词        if (lowerInput.contains("代码") || lowerInput.contains("程序") ||                lowerInput.contains("函数") || lowerInput.contains("bug") ||                lowerInput.contains("编程") || lowerInput.contains("算法")) {            return "technical";        }        // 创意写作相关关键词        if (lowerInput.contains("写作") || lowerInput.contains("故事") ||                lowerInput.contains("诗歌") || lowerInput.contains("创意") ||                lowerInput.contains("小说") || lowerInput.contains("文章")) {            return "creative";        }        // 商业相关关键词        if (lowerInput.contains("商业") || lowerInput.contains("市场") ||                lowerInput.contains("策略") || lowerInput.contains("管理") ||                lowerInput.contains("营销") || lowerInput.contains("分析")) {            return "business";        }        return "default";    }    private String getModelNameForTask(String taskType) {        return switch (taskType.toLowerCase()) {            case "creative" -> "Creative Writing Assistant";            case "technical" -> "Technical Expert";            case "business" -> "Business Consultant";            default -> "General Assistant";        };    }    private String[] getSuitableTasksForModel(String modelType) {        return switch (modelType.toLowerCase()) {            case "creative" -> new String[]{"创意写作", "内容创作", "故事编写", "诗歌创作"};            case "technical" -> new String[]{"代码分析", "技术咨询", "程序调试", "架构设计"};            case "business" -> new String[]{"商业分析", "市场策略", "管理咨询", "数据分析"};            default -> new String[]{"日常问答", "通用咨询", "信息查询", "学习辅导"};        };    }}

3.6.2 模型路由切换演示

package com.example.modelintegration.controller;import com.example.modelintegration.service.ModelSelectionService;import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController@RequestMapping("/api/model-selecting")public class ModelSelectionController {    private final ModelSelectionService modelService;    public ModelSelectionController(ModelSelectionService modelService) {        this.modelService = modelService;    }    /**     * 智能模型选择     */    @PostMapping("/smart-selection")    public Map<String, Object> smartSelection(@RequestBody Map<String, String> request) {        String input = request.get("input");        String taskType = request.get("taskType");        if (taskType != null && !taskType.trim().isEmpty()) {            // 手动指定任务类型            String response = modelService.processWithBestModel(input, taskType);            return Map.of(                    "input", input,                    "taskType", taskType,                    "response", response,                    "selectedModel", getModelNameForTask(taskType),                    "mode", "manual"            );        } else {            // 自动检测任务类型            return modelService.smartTaskRouting(input);        }    }    /**     * 获取可用模型信息     */    @GetMapping("/available-models")    public Map<String, Object> getAvailableModels() {        return modelService.getAvailableModels();    }    /**     * 健康检查     */    @GetMapping("/health")    public Map<String, Object> health() {        return Map.of(                "status", "OK",                "service", "Model Selection Controller",                "timestamp", System.currentTimeMillis()        );    }    private String getModelNameForTask(String taskType) {        if (taskType == null) return "General Assistant";        return switch (taskType.toLowerCase()) {            case "creative", "writing" -> "Creative Writing Assistant";            case "technical", "code" -> "Technical Expert";            case "business" -> "Business Consultant";            default -> "General Assistant";        };    }}

3.7 启动脚本和使用指南

run.sh(启动脚本):

#!/bin/bash# Model Integration Demo 启动脚本echo "🚀 正在启动 Model Integration Demo..."# 检查并加载 .env 文件if [ -f ".env" ]; then    echo "📄 正在加载 .env 文件中的环境变量..."    # 读取 .env 文件并设置环境变量    while IFS= read -r line; do        # 跳过注释行和空行        if [[ ! "$line" =~ ^[[:space:]]*# ]] && [[ -n "$line" ]]; then            export "$line"        fi    done < .env    echo "✅ 环境变量加载完成"else    echo "⚠️  警告:未找到 .env 文件,将使用默认环境变量"    echo "💡 提示:复制 .env.example 为 .env 并配置你的 API Keys"fi# 检查环境变量check_env_var() {    if [ -z "${!1}" ]; then        echo "⚠️  警告:环境变量 $1 未设置"        return 1    else        echo "✅ 环境变量 $1 已设置"        return 0    fi}echo "📋 检查环境变量..."# 根据激活的 profile 检查相应的环境变量ACTIVE_PROFILE=${SPRING_PROFILES_ACTIVE:-deepseek}case $ACTIVE_PROFILE in    "openai")        check_env_var "OPENAI_API_KEY"        ;;    "deepseek")        check_env_var "DEEPSEEK_API_KEY"        ;;    "ollama")        echo "ℹ️  使用本地 Ollama,请确保 Ollama 服务已启动"        if ! curl -s http://localhost:11434/api/tags > /dev/null; then            echo "❌ Ollama 服务未启动或不可访问"            echo "请先启动 Ollama: ollama serve"            exit 1        else            echo "✅ Ollama 服务运行正常"        fi        ;;    *)        echo "⚠️  未知的 profile: $ACTIVE_PROFILE"        echo "支持的 profiles: openai, deepseek, ollama"        ;;esac# 检查是否存在 pom.xml 文件if [ ! -f "pom.xml" ]; then    echo "❌ 错误:未找到 pom.xml 文件,请确保在正确的项目目录中运行此脚本"    exit 1fiecho "🔧 使用 profile: $ACTIVE_PROFILE"echo "🔧 启动应用..."# 设置 JVM 参数export MAVEN_OPTS="-Xmx2g -Xms1g"# 启动应用export SPRING_PROFILES_ACTIVE=$ACTIVE_PROFILEmvn spring-boot:runif [ $? -ne 0 ]; then    echo "❌ 应用启动失败"    exit 1fi

test_apis.sh(API 测试脚本):

#!/bin/bash# API 测试脚本BASE_URL="http://localhost:8080"echo "🧪 Spring AI 模型集成演示 - API 测试脚本"echo "========================================"# 检查服务是否运行echo "📡 检查服务状态..."if ! curl -s "$BASE_URL/api/chat/health" > /dev/null; then    echo "❌ 服务未启动或不可访问,请先启动应用"    exit 1fiecho "✅ 服务运行正常"echo# 测试基础聊天echo "💬 测试基础聊天功能..."curl -X POST "$BASE_URL/api/chat/basic" \  -H "Content-Type: application/json" \  -d '{"message": "你好,请简单介绍一下 Spring AI"}' \  -s | jq '.'echoecho "---"# 测试代码分析echo "🔍 测试代码分析功能..."curl -X POST "$BASE_URL/api/chat/code-analysis" \  -H "Content-Type: application/json" \  -d '{    "code": "public class Calculator { public int add(int a, int b) { return a + b; } }",    "question": "这个类有什么可以改进的地方?"  }' \  -s | jq '.analysis' -rechoecho "---"# 测试创意写作echo "✍️ 测试创意写作功能..."curl -X POST "$BASE_URL/api/chat/creative-writing" \  -H "Content-Type: application/json" \  -d '{    "topic": "人工智能的未来",    "style": "科幻小说片段"  }' \  -s | jq '.content' -rechoecho "---"# 测试参数调优echo "🎛️ 测试参数调优功能..."curl -X POST "$BASE_URL/api/tuning/temperature-test" \  -H "Content-Type: application/json" \  -d '{"prompt": "描述一个理想的工作日"}' \  -s | jq '.results[0:2]'echoecho "---"# 测试智能模型选择echo "🤖 测试智能模型选择..."curl -X POST "$BASE_URL/api/model-selecting/smart-selection" \  -H "Content-Type: application/json" \  -d '{"input": "请帮我分析这段 Java 代码的性能问题"}' \  -s | jq '{"detectedTaskType": .detectedTaskType, "selectedModel": .selectedModel}'echoecho "---"# 测试获取可用模型echo "📋 获取可用模型信息..."curl -X GET "$BASE_URL/api/model-selecting/available-models" \  -s | jq '.models | keys'echoecho "---"# 测试参数建议echo "💡 获取参数调优建议..."curl -X GET "$BASE_URL/api/tuning/recommendations/code" \  -s | jq '.recommendation'echoecho "---"# 如果启用了 OpenAI embedding,测试 embedding 功能echo "🔤 测试文本嵌入功能(如果可用)..."EMBED_RESPONSE=$(curl -X POST "$BASE_URL/api/embedding/embed" \  -H "Content-Type: application/json" \  -d '{"text": "Spring AI 很棒"}' \  -s 2>/dev/null)if echo "$EMBED_RESPONSE" | jq -e '.embedding' > /dev/null 2>&1; then    echo "✅ Embedding 功能可用"    echo "$EMBED_RESPONSE" | jq '{"text": .text, "dimensions": .dimensions}'        # 测试相似度计算    echo    echo "📏 测试文本相似度计算..."    curl -X POST "$BASE_URL/api/embedding/similarity" \      -H "Content-Type: application/json" \      -d '{        "text1": "Spring AI 是一个强大的框架",        "text2": "Spring AI 是用于 AI 开发的工具"      }' \      -s | jq '{"similarity": .similarity, "percentage": .similarityPercentage}'else    echo "ℹ️  Embedding 功能未启用(需要 OpenAI profile)"fiechoecho "---"echo "🎉 测试完成!所有主要功能都已验证。"echoecho "💡 提示:"echo "- 可以通过修改环境变量 SPRING_PROFILES_ACTIVE 切换不同的模型"echo "- 访问 http://localhost:8080/actuator/health 查看应用健康状态"echo "- 访问 http://localhost:8080/actuator/metrics 查看监控指标"

3.9 使用示例

切换到 DeepSeek

export SPRING_PROFILES_ACTIVE=deepseekexport DEEPSEEK_API_KEY=your_api_key./run.sh

切换到 OpenAI

export SPRING_PROFILES_ACTIVE=openaiexport OPENAI_API_KEY=your_api_key./run.sh

API 测试示例

基础功能测试:

# 基础对话curl -X POST http://localhost:8080/api/chat/basic \  -H "Content-Type: application/json" \  -d '{"message": "解释一下什么是 Spring AI"}'# 代码分析curl -X POST http://localhost:8080/api/chat/code-analysis \  -H "Content-Type: application/json" \  -d '{    "code": "public class Calculator { public int add(int a, int b) { return a + b; } }",    "question": "这个类有什么可以改进的地方?"  }'# 创意写作curl -X POST http://localhost:8080/api/chat/creative-writing \  -H "Content-Type: application/json" \  -d '{    "topic": "人工智能的未来",    "style": "科幻小说片段"  }'

参数调优测试:

# 温度参数测试curl -X POST http://localhost:8080/api/tuning/temperature-test \  -H "Content-Type: application/json" \  -d '{"prompt": "写一首关于春天的诗"}'# 参数组合对比curl -X POST http://localhost:8080/api/tuning/parameter-comparison \  -H "Content-Type: application/json" \  -d '{"prompt": "如何优化 Java 应用的性能?"}'# 获取参数建议curl -X GET http://localhost:8080/api/tuning/recommendations/creative# 获取所有用例类型curl -X GET http://localhost:8080/api/tuning/use-cases

模型切换和对比:

# 智能模型选择(自动检测)curl -X POST http://localhost:8080/api/model-selecting/smart-selection \  -H "Content-Type: application/json" \  -d '{"input": "请帮我分析这段 Java 代码的性能问题"}'# 智能模型选择(手动指定)curl -X POST http://localhost:8080/api/model-selecting/smart-selection \  -H "Content-Type: application/json" \  -d '{"input": "写一个关于春天的故事", "taskType": "creative"}'# 获取可用模型curl -X GET http://localhost:8080/api/model-selecting/available-models

Embedding 功能测试(需要 OpenAI profile):

# 文本嵌入curl -X POST http://localhost:8080/api/embedding/embed \  -H "Content-Type: application/json" \  -d '{"text": "Spring AI 很棒"}'# 批量文本嵌入curl -X POST http://localhost:8080/api/embedding/embed-batch \  -H "Content-Type: application/json" \  -d '{    "texts": ["Spring AI 很棒", "Java 开发框架", "人工智能应用"]  }'# 相似度计算curl -X POST http://localhost:8080/api/embedding/similarity \  -H "Content-Type: application/json" \  -d '{    "text1": "Spring AI 是一个强大的框架",    "text2": "Spring AI 是用于 AI 开发的工具"  }'

监控和健康检查:

# 健康检查curl -X GET http://localhost:8080/api/chat/healthcurl -X GET http://localhost:8080/api/tuning/healthcurl -X GET http://localhost:8080/api/model-selecting/health# 应用信息curl -X GET http://localhost:8080/actuator/info# 监控指标curl -X GET http://localhost:8080/actuator/metricscurl -X GET http://localhost:8080/actuator/health

3.10 Embedding 模型演示

在 Spring AI 中,除了 Chat 模型之外,Embedding 模型也是重要的组成部分。当使用 OpenAI 配置时,我们可以使用 Embedding 功能进行文本向量化处理。

3.10.1 Embedding 控制器实现

package com.example.modelintegration.controller;import java.util.List;import java.util.Map;import com.example.modelintegration.config.EmbeddingService;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/api/embedding")@ConditionalOnBean(EmbeddingService.class)public class EmbeddingModelController {    private final EmbeddingService embeddingService;    public EmbeddingModelController(EmbeddingService embeddingService) {        this.embeddingService = embeddingService;    }    /**     * 单个文本嵌入     */    @PostMapping("/embed")    public Map<String, Object> embedText(@RequestBody Map<String, String> request) {        String text = request.get("text");        float[] embedding = embeddingService.embed(text);        return Map.of(                "text", text,                "embedding", embedding,                "dimensions", embedding.length,                "timestamp", System.currentTimeMillis()        );    }    /**     * 批量文本嵌入     */    @PostMapping("/embed-batch")    public Map<String, Object> embedBatch(@RequestBody Map<String, List<String>> request) {        List<String> texts = request.get("texts");        Map<String, float[]> embeddings = embeddingService.embedBatch(texts);        return Map.of(                "texts", texts,                "embeddings", embeddings,                "count", texts.size(),                "timestamp", System.currentTimeMillis()        );    }    /**     * 文本相似度计算     */    @PostMapping("/similarity")    public Map<String, Object> calculateSimilarity(@RequestBody Map<String, String> request) {        String text1 = request.get("text1");        String text2 = request.get("text2");        double similarity = embeddingService.calculateSimilarity(text1, text2);        return Map.of(                "text1", text1,                "text2", text2,                "similarity", similarity,                "similarityPercentage", String.format("%.2f%%", similarity * 100),                "timestamp", System.currentTimeMillis()        );    }    /**     * 健康检查     */    @GetMapping("/health")    public Map<String, Object> health() {        return Map.of(                "status", "OK",                "service", "Embedding Model Controller",                "timestamp", System.currentTimeMillis()        );    }}

3.10.2 项目特性总结

在这个演示项目中,我们实现了以下核心特性:

    智能任务路由:根据用户输入自动选择最适合的模型配置多模型支持:支持 OpenAI、DeepSeek、Ollama 等多种模型提供商参数调优:提供温度、Top-P 等参数的调优建议和测试Embedding 功能:文本向量化、相似度计算(OpenAI 配置时可用)监控和健康检查:集成 Actuator 提供完整的监控体系错误处理:完善的输入验证和错误处理机制灵活配置:支持环境变量和多 profile 配置管理

3.11 小结与展望

通过这篇文章,我们深入探讨了 Spring AI 的模型集成和调优能力。我们学会了:

    理解不同模型类型:Chat、Embedding、Image 模型各有其用武之地模型选择策略:根据需求在本地模型和云端模型之间做出明智选择参数调优技巧:通过调整 Temperature、Top P 等参数优化模型表现无缝切换提供商:在不修改业务代码的情况下切换不同的 AI 提供商智能任务路由:自动识别任务类型并选择最适合的模型配置Embedding 功能:文本向量化和相似度计算的实际应用参数调优建议系统:基于用例提供最佳参数配置建议监控和健康检查:全面的性能监控和健康检查机制错误处理和输入验证:完善的错误处理和用户输入验证生产环境最佳实践:配置管理、容错处理、监控指标等企业级特性

实际项目亮点

在这个演示项目中,我们实现了以下核心特性:

Spring AI 的模型抽象设计让我们能够像使用数据库一样使用 AI 模型——通过配置切换"数据源",而业务逻辑保持不变。这种设计哲学大大降低了 AI 应用的开发和维护成本。

实际使用建议

通过这个项目的实践,我们可以总结出以下使用建议:

    选择合适的模型提供商:根据具体需求选择 OpenAI、DeepSeek 或本地 Ollama合理配置参数:使用我们提供的参数调优工具找到最佳配置利用智能路由:让系统自动选择最适合的模型配置监控应用性能:利用 Actuator 端点监控应用健康状态处理错误情况:确保应用在网络异常或模型服务不可用时能够优雅处理

在下一篇文章中,我们将探讨 Chat Memory 机制,学习如何让 AI 拥有"记忆",实现真正的多轮对话体验。我们将深入了解短期记忆、长期记忆的实现,以及如何在有限的上下文窗口内高效管理对话历史。

记住,选择合适的模型就像选择合适的工具一样重要。没有万能的模型,只有最适合特定场景的模型。多实践、多测试,你就能找到最适合你业务需求的"AI 大脑"。


相关资源:

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI AI模型 模型调优 Chat模型 Embedding模型
相关文章