Spring Boot와 Spring AI Alibaba를 이용한 알리바바 클라우드 DashScope 에이전트 통합

아키텍처 및 기술 스택

본 가이드는 Spring Boot 환경에서 알리바바 클라우드의 대규모 언어 모델(LLM) 서비스인 DashScope(백련) 에이전트를 연동하는 방법을 다룹니다. 동기식 요청과 Server-Sent Events(SSE)를 활용한 비동기 스트리밍 응답 두 가지 방식을 구현합니다.

  • 프레임워크: Spring Boot 3.x, Spring WebFlux
  • 언어: Java 17
  • AI 라이브러리: Spring AI Alibaba (DashScope Starter)
  • 로깅: Log4j2

프로젝트 초기 설정

Spring Initializr를 사용하여 기본 프로젝트를 생성한 후, pom.xml에 필요한 의존성을 추가합니다. 기본 로깅 프레임워크인 Logback을 제외하고 Log4j2를 사용하기 위해 exclusion을 설정합니다.

<dependencies>
    <!-- Spring Boot Core -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <!-- Spring AI Alibaba DashScope -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        <version>1.0.0.2</version>
    </dependency>

    <!-- Utilities -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
</dependencies>

환경 변수 및 설정

알리바바 클라우드 콘솔에서 발급받은 API Key와 대시보드에서 생성한 애플리케이션 ID를 application.yml에 매핑합니다. 보안을 위해 실제 운영 환경에서는 환경 변수를 활용하는 것이 좋습니다.

spring:
  application:
    name: dashscope-integration-service
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY:your-api-key-here}
      agent:
        app-id: ${DASHSCOPE_APP_ID:your-app-id-here}

동기식 에이전트 호출 구현

동기식 인터페이스는 사용자의 입력을 받아 DashScope 에이전트에 전달하고, 완전한 응답이 생성될 때까지 대기한 후 결과를 반환합니다. 응답 객체에서 텍스트 콘텐츠뿐만 아니라 참조된 문서(docReferences)와 추론 과정(thoughts)과 같은 메타데이터도 추출하여 로깅합니다.

package com.example.ai.controller;

import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgent;
import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgentOptions;
import com.alibaba.cloud.ai.dashscope.api.DashScopeAgentApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

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

    private static final Logger log = LoggerFactory.getLogger(SynchronousChatController.class);
    private final DashScopeAgent dashScopeAgent;

    @Value("${spring.ai.dashscope.agent.app-id}")
    private String applicationId;

    public SynchronousChatController(DashScopeAgentApi agentApi) {
        this.dashScopeAgent = new DashScopeAgent(agentApi);
    }

    @GetMapping("/sync")
    public String executeSynchronousCall(
            @RequestParam(value = "query", defaultValue = "안녕하세요!") String query) {
        
        DashScopeAgentOptions options = DashScopeAgentOptions.builder()
                .withAppId(applicationId)
                .build();
                
        ChatResponse chatResponse = dashScopeAgent.call(new Prompt(query, options));
        
        if (chatResponse == null || chatResponse.getResult() == null) {
            return "응답을 생성할 수 없습니다.";
        }

        AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
        String finalText = assistantMessage.getText();
        
        // 메타데이터 파싱 및 로깅
        Object metadataOutput = assistantMessage.getMetadata().get("output");
        if (metadataOutput instanceof DashScopeAgentApi.DashScopeAgentResponse.DashScopeAgentResponseOutput output) {
            log.debug("추론 과정: {}", output.thoughts());
            log.debug("참조 문서: {}", output.docReferences());
        }

        log.info("동기식 응답 완료: {}", finalText);
        return finalText;
    }
}

반응형 스트리밍 에이전트 호출 구현

스트리밍 방식은 Spring WebFlux의 Flux를 사용하여 Server-Sent Events(SSE) 형태로 데이터를 실시간으로 전송합니다. incrementalOutput 옵션을 활성화하여 중복되는 텍스트 없이 새로 생성된 토큰만 순차적으로 클라이언트에 푸시합니다.

package com.example.ai.controller;

import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgent;
import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgentOptions;
import com.alibaba.cloud.ai.dashscope.api.DashScopeAgentApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

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

    private static final Logger log = LoggerFactory.getLogger(StreamingChatController.class);
    private final DashScopeAgent dashScopeAgent;

    @Value("${spring.ai.dashscope.agent.app-id}")
    private String applicationId;

    public StreamingChatController(DashScopeAgentApi agentApi) {
        DashScopeAgentOptions defaultOptions = DashScopeAgentOptions.builder()
                .withSessionId("global-session-id")
                .withIncrementalOutput(true)
                .withHasThoughts(true)
                .build();
        this.dashScopeAgent = new DashScopeAgent(agentApi, defaultOptions);
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> executeStreamingCall(
            @RequestParam(value = "query", defaultValue = "안녕하세요!") String query) {
        
        DashScopeAgentOptions requestOptions = DashScopeAgentOptions.builder()
                .withAppId(applicationId)
                .build();

        return dashScopeAgent.stream(new Prompt(query, requestOptions))
                .map(chatResponse -> {
                    if (chatResponse == null || chatResponse.getResult() == null) {
                        return "";
                    }
                    AssistantMessage message = chatResponse.getResult().getOutput();
                    String chunk = message.getText();
                    log.trace("스트리밍 청크 수신: {}", chunk);
                    return chunk != null ? chunk : "";
                })
                .filter(chunk -> !chunk.isEmpty());
    }
}

애플리케이션 진입점

Spring Boot 애플리케이션을 실행하기 위한 메인 클래스입니다.

package com.example.ai;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AiIntegrationApplication {

    private static final Logger log = LoggerFactory.getLogger(AiIntegrationApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(AiIntegrationApplication.class, args);
        log.info("AI 통합 서비스가 성공적으로 시작되었습니다.");
    }
}

태그: SpringBoot SpringAI DashScope AlibabaCloud java

6월 11일 22:12에 게시됨