从零到一:基于Spring AI与智谱AI构建企业级智能对话服务实战指南

在当今这个技术快速迭代的时代,将大型语言模型(LLM)的能力无缝集成到现有应用系统中,已成为众多开发者面临的核心挑战。面对复杂的API调用、上下文管理、流式响应和函数调用等需求,一个成熟、高效的开发框架能显著降低集成门槛,加速产品落地。Spring AI正是为此而生的利器,它并非一个独立的AI模型,而是一个旨在简化AI应用开发的Spring生态扩展。它提供了一套标准化的抽象接口,让开发者能以熟悉的Spring方式,轻松对接包括智谱AI在内的多种主流大模型服务,将重心从繁琐的底层通信转移到业务逻辑的创新上。

本文面向有一定Spring Boot开发经验,希望快速、稳健地将智能对话能力嵌入到Java应用中的工程师。我们将摒弃枯燥的理论罗列和API文档复述,直接切入实战,通过一个完整的项目案例,手把手带你完成从环境搭建、服务集成、功能增强到生产级部署的全过程。你会发现,借助Spring AI的“约定大于配置”理念和智谱AI强大的模型能力,在5分钟内启动一个对话机器人原型只是起点,构建一个健壮、可扩展的企业级智能服务才是我们的目标。

1. 项目初始化与环境配置

在开始编码之前,我们需要一个干净的项目基础。Spring AI目前仍处于快速发展的里程碑(Milestone)阶段,因此依赖管理是第一步,也是避免后续版本冲突的关键。

1.1 创建Spring Boot项目与依赖管理

推荐使用 Spring Initializr 生成项目骨架。选择 Gradle (Groovy) 或 Maven 作为构建工具,语言为 Java,Spring Boot版本建议选择 3.2.x 或更高。除了基础的 Spring Web 依赖,我们还需要手动添加Spring AI的物料清单(BOM)和智谱AI的Starter。

对于Maven项目,在你的 pom.xml 中,首先添加Spring AI的仓库和BOM:

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>0.8.1</version> <!-- 请使用最新稳定版本 -->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    ...
</project>

然后,在 <dependencies> 部分添加核心依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI 智谱AI集成 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
    </dependency>
    <!-- 开发工具,可选但推荐 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

对于Gradle项目,在 build.gradle 中进行类似配置:

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.ai:spring-ai-bom:0.8.1"
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.ai:spring-ai-zhipuai-spring-boot-starter'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

注意:Spring AI版本更新较快,请务必访问其官方GitHub仓库或文档,确认当前最新的稳定版本号进行替换。

1.2 获取并安全配置智谱AI API Key

一切服务调用的前提是身份认证。你需要前往智谱AI开放平台注册账号并创建API Key。

  1. 访问智谱AI开放平台官网,完成注册与实名认证。
  2. 进入控制台,在“API密钥”管理页面,创建一个新的密钥。
  3. 至关重要的一步:安全地管理这个密钥。 绝对不要将它硬编码在源码中或提交到版本控制系统(如Git)。

Spring Boot提供了多种优雅的配置方式,优先级从高到低如下:

  • 操作系统环境变量:最安全,适用于生产环境。
    # Linux/macOS
    export SPRING_AI_ZHIPUAI_API_KEY=your_api_key_here
    
    # Windows (PowerShell)
    $env:SPRING_AI_ZHIPUAI_API_KEY="your_api_key_here"
    
  • 应用启动参数:
    java -jar your-app.jar --spring.ai.zhipuai.api-key=your_api_key_here
    
  • 配置文件(application.yml 或 application.properties):适用于本地开发,但务必确保配置文件不被提交。
    # application.yml 示例
    spring:
      ai:
        zhipuai:
          api-key: ${ZHIPU_API_KEY:} # 推荐:引用环境变量,本地可设默认值
          chat:
            options:
              model: glm-3-turbo
              temperature: 0.7
    
    或者使用 application.properties:
    spring.ai.zhipuai.api-key=${ZHIPU_API_KEY:}
    spring.ai.zhipuai.chat.options.model=glm-3-turbo
    spring.ai.zhipuai.chat.options.temperature=0.7
    

在本地开发时,我习惯在 ~/.bashrc 或 ~/.zshrc 中设置环境变量,并在IDE的运行配置里注入。对于团队协作,可以使用 .env 文件配合 spring-dotenv 这样的库,但记得将 .env 加入 .gitignore。

2. 核心对话服务构建与接口设计

环境就绪后,我们开始编写业务代码。Spring AI的自动配置(Auto-Configuration)会为我们创建好 ZhiPuAiChatModel 的Bean,直接注入使用即可。

2.1 注入ChatModel与基础对话接口

首先,创建一个简单的REST控制器来暴露对话接口。这里我们会实现一个同步响应和一个流式响应的端点,以满足不同场景的需求。

import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final ZhiPuAiChatModel chatModel;

    // 构造器注入,推荐方式
    public ChatController(ZhiPuAiChatModel chatModel) {
        this.chatModel = chatModel;
    }

    /**
     * 同步对话接口
     * @param message 用户输入
     * @return 完整的AI回复
     */
    @GetMapping("/completion")
    public ChatCompletionResponse generate(@RequestParam String message) {
        // 调用ChatModel,传入用户消息
        ChatResponse response = chatModel.call(message);
        String content = response.getResult().getOutput().getContent();
        return new ChatCompletionResponse(content);
    }

    /**
     * 流式对话接口 (Server-Sent Events)
     * @param message 用户输入
     * @return 流式的文本块
     */
    @GetMapping(value = "/stream", produces = "text/event-stream")
    public Flux<String> generateStream(@RequestParam String message) {
        Prompt prompt = new Prompt(new UserMessage(message));
        return chatModel.stream(prompt)
                .map(chunk -> chunk.getResult().getOutput().getContent())
                .filter(text -> text != null && !text.isEmpty());
    }

    // 简单的响应DTO
    public record ChatCompletionResponse(String reply) {}
}

启动应用,访问 http://localhost:8080/api/chat/completion?message=你好,请介绍一下你自己,你应该能立刻收到智谱AI模型的回复。流式接口则可以通过前端使用 EventSource 来连接,体验逐词输出的效果。

2.2 深入理解与配置ChatOptions

基础的调用已经实现,但一个健壮的服务需要更精细的控制。ZhiPuAiChatOptions 封装了所有与模型行为相关的参数。我们可以在配置文件进行全局默认设置,也可以在运行时针对每次请求进行动态覆盖。

全局配置(application.yml)示例:

spring:
  ai:
    zhipuai:
      api-key: ${ZHIPU_API_KEY:}
      chat:
        options:
          model: glm-4 # 使用GLM-4模型
          temperature: 0.3 # 降低随机性,使输出更确定、专业
          max-tokens: 1024 # 限制单次回复的最大长度
          top-p: 0.9 # 核采样参数,与temperature二选一调整
          frequency-penalty: 0.5 # 降低重复用词的概率
          presence-penalty: 0.3 # 鼓励谈论新话题

运行时动态选项覆盖:

有时,我们需要根据用户请求的上下文调整参数。例如,在创意写作场景提高 temperature,在代码生成场景降低它。

@PostMapping("/advanced")
public ChatCompletionResponse generateWithOptions(@RequestBody AdvancedChatRequest request) {
    // 构建动态选项
    ZhiPuAiChatOptions runtimeOptions = ZhiPuAiChatOptions.builder()
            .withTemperature(request.creative() ? 0.9f : 0.2f) // 根据创意标志调整
            .withMaxTokens(request.maxLength())
            .withModel("glm-3-turbo") // 临时切换模型
            .build();

    // 将动态选项注入Prompt
    Prompt prompt = new Prompt(
            new UserMessage(request.message()),
            runtimeOptions // 这里传入的选项会覆盖全局配置
    );

    ChatResponse response = chatModel.call(prompt);
    return new ChatCompletionResponse(response.getResult().getOutput().getContent());
}

public record AdvancedChatRequest(String message, boolean creative, int maxLength) {}

通过这种方式,我们可以为不同的功能模块(如客服、内容生成、代码助手)定义不同的参数模板,实现灵活的模型行为管理。

3. 实现高级功能:函数调用(Function Calling)

函数调用是大模型与外部世界交互的桥梁,也是构建智能助理类应用的核心。Spring AI极大地简化了这一过程的实现。其核心思想是:你定义好Java函数(工具),并描述其功能,模型在对话中会根据需要决定是否调用以及传入什么参数,然后将执行结果返回给模型,由模型组织最终的自然语言回复给用户。

3.1 定义工具函数

假设我们要为聊天机器人添加一个查询天气的能力。首先,我们定义一个服务类作为“工具”。

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

// 请求参数记录
@JsonClassDescription("根据城市名称查询该城市的实时天气信息") // 关键:用于生成函数描述
public record WeatherRequest(
        @JsonProperty(required = true)
        @JsonPropertyDescription("城市的名称,例如:北京、上海、San Francisco")
        String location,

        @JsonPropertyDescription("温度单位,C代表摄氏度,F代表华氏度")
        Unit unit
) {
    public enum Unit { C, F }
}

// 响应记录
public record WeatherResponse(double temperature, WeatherRequest.Unit unit, String condition) {}

// 工具函数实现
@Service
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {

    // 模拟一个第三方天气API的调用
    @Override
    public WeatherResponse apply(WeatherRequest request) {
        // 这里应该是真实的HTTP调用,例如调用和风天气、OpenWeatherMap等API
        // 为了演示,我们返回模拟数据
        double temp = switch (request.location().toLowerCase()) {
            case "北京" -> 22.5;
            case "上海" -> 25.0;
            case "旧金山", "san francisco" -> 18.0;
            default -> 20.0 + (Math.random() * 10); // 随机温度
        };

        String condition = switch ((int) (Math.random() * 4)) {
            case 0 -> "晴天";
            case 1 -> "多云";
            case 2 -> "小雨";
            default -> "阴天";
        };

        return new WeatherResponse(temp, request.unit(), condition);
    }
}

注意 @JsonClassDescription 和 @JsonPropertyDescription 注解,它们为模型提供了关于函数功能和参数含义的清晰描述,是函数能否被正确调用的关键。

3.2 注册函数并启用调用

接下来,我们需要将这个工具函数注册到Spring上下文中,并让 ChatModel 知道它的存在。

方法一:通过 @Bean 定义自动注册(推荐)

在配置类中,将我们的 WeatherService 声明为一个Bean。Spring AI会自动扫描并包装它。

@Configuration
public class FunctionConfig {

    @Bean
    @Description("获取指定城市的当前天气情况") // 另一种提供函数描述的方式
    public Function<WeatherRequest, WeatherResponse> weatherFunction(WeatherService weatherService) {
        return weatherService; // 直接返回服务实例
    }
}

方法二:在请求中动态注册(更灵活)

对于需要根据上下文动态选择工具的场景,可以在每次请求的 Prompt 中指定。

@PostMapping("/chat-with-weather")
public Flux<String> chatWithFunction(@RequestBody UserQuery query) {

    // 1. 创建函数回调包装器
    FunctionCallback weatherCallback = FunctionCallbackWrapper.builder()
            .withName("getCurrentWeather") // 模型调用时使用的函数名
            .withDescription("获取指定城市的当前天气情况")
            .withResponseConverter((response) -> 
                String.format("温度 %.1f°%s, 天气状况: %s", 
                    response.temperature(), 
                    response.unit().name(), 
                    response.condition())
            ) // 将响应对象转换为模型可读的文本
            .withFunction(new WeatherService()) // 实际的函数
            .build();

    // 2. 构建包含函数调用的选项
    ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
            .withFunctionCallbacks(List.of(weatherCallback)) // 注册回调
            .build();

    // 3. 创建Prompt并调用
    Prompt prompt = new Prompt(new UserMessage(query.message()), options);
    return chatModel.stream(prompt)
            .map(chunk -> chunk.getResult().getOutput().getContent());
}

现在,当你向 /chat-with-weather 发送消息“北京今天天气怎么样?”时,模型会识别出查询天气的意图,自动调用 getCurrentWeather 函数,并将 location 参数设为“北京”。函数执行后返回的天气文本会被模型接收,并最终生成类似“北京今天温度22.5°C,天气晴朗,适合外出。”的自然语言回复。整个过程对前端用户是完全透明的,他们感知到的只是一个更智能、能获取实时信息的对话机器人。

4. 生产环境考量与最佳实践

让服务在本地运行起来只是第一步,要将其部署到生产环境,还需要考虑稳定性、可观测性和性能。

4.1 配置重试与连接管理

网络波动和API限流是线上服务的常客。Spring AI内置了基于Spring Retry的重试机制,我们可以通过配置来增强服务的韧性。

spring:
  ai:
    retry:
      max-attempts: 3 # 最大重试次数(含首次调用)
      backoff:
        initial-interval: 1000ms # 首次重试间隔
        multiplier: 2.0 # 间隔乘数(指数退避)
        max-interval: 10s # 最大重试间隔
      on-client-errors: false # 4xx错误不重试(如认证失败、参数错误)
      exclude-on-http-codes: 429, 401 # 针对特定HTTP状态码不重试(限流、未授权)

同时,配置HTTP客户端连接池和超时设置也至关重要,这通常通过底层的 RestClient 或 WebClient 配置实现。虽然Spring AI Starter可能提供了默认配置,但在高并发场景下需要调整。

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient.Builder restClientBuilder() {
        return RestClient.builder()
                .requestFactory(new HttpComponentsClientHttpRequestFactory())
                .requestInterceptor((request, body, execution) -> {
                    // 可以在这里添加统一的请求头,如User-Agent
                    request.getHeaders().set("X-Custom-Source", "spring-ai-app");
                    return execution.execute(request, body);
                });
    }
}
// 注意:Spring AI内部使用的客户端配置方式可能因版本而异,需查阅对应文档。

4.2 日志、监控与异常处理

清晰的日志是调试和监控的基石。确保为 org.springframework.ai 包设置 DEBUG 或 TRACE 级别日志,以查看详细的请求和响应信息(注意生产环境慎用,避免日志泛滥)。

logging:
  level:
    org.springframework.ai: DEBUG
    org.springframework.ai.zhipuai: DEBUG

全局异常处理能提供友好的错误信息,避免将内部细节暴露给客户端。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AiClientException.class) // Spring AI定义的客户端异常
    public ResponseEntity<ErrorResponse> handleAiClientException(AiClientException e) {
        // 记录错误日志
        log.error("AI服务调用失败: {}", e.getMessage(), e);
        // 返回结构化的错误信息
        return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .body(new ErrorResponse("AI_SERVICE_UNAVAILABLE", "智能服务暂时不可用,请稍后重试"));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
        log.error("系统内部错误: ", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("INTERNAL_ERROR", "系统内部错误"));
    }

    public record ErrorResponse(String code, String message) {}
}

4.3 性能优化与成本控制

使用大模型API会产生费用,优化调用是控制成本的关键。

  • 缓存策略:对于常见、结果变化不频繁的查询(如“什么是Spring Boot?”),可以在应用层或使用Redis等缓存中间件缓存模型的回复。注意设置合理的TTL(生存时间)。
  • 上下文长度管理:智谱AI模型有上下文窗口限制。在构建多轮对话系统时,需要设计策略来维护或摘要历史消息,避免无限增长导致API调用失败或成本激增。可以考虑只保留最近N轮对话,或使用更小的模型对长历史进行摘要。
  • 异步与非阻塞:对于流式响应或耗时较长的复杂推理,务必使用 chatModel.stream() 并配合Spring WebFlux(如本例中的 Flux)实现非阻塞响应,避免占用宝贵的Servlet容器线程。
  • 参数调优:如前所述,合理设置 max-tokens 可以防止生成过长内容,temperature 和 top-p 的调整也能影响生成质量和可预测性。

走到这里,你已经拥有了一个功能完整、具备生产潜力的智能对话服务后端。从项目初始化、依赖注入、基础对话到高级的函数调用,我们一步步拆解了集成过程中的关键环节。Spring AI的抽象层让我们几乎不用关心HTTP请求的细节,而智谱AI强大的模型能力则为应用提供了坚实的智能内核。

在实际部署时,记得将API密钥等敏感信息移入环境变量或专业的密钥管理服务(如HashiCorp Vault、AWS Secrets Manager)。对于更复杂的场景,如多模型路由、A/B测试、复杂的对话状态管理,你可以考虑在 ChatModel 之上再封装一层服务层,实现更灵活的业务逻辑。技术的价值在于解决实际问题,现在,你可以基于这个坚实的起点,去构建真正改变用户体验的智能应用了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐