자습서: Azure App Service 및 Azure OpenAI를 사용하여 챗봇 빌드(Spring Boot)

이 자습서에서는 Azure OpenAI를 Java Spring Boot 애플리케이션과 통합하고 Azure App Service에 배포하여 지능형 AI 애플리케이션을 빌드합니다. Azure OpenAI에 쿼리를 보내고 브라우저에 응답을 보내는 Spring Boot 컨트롤러를 만듭니다.

팁 (조언)

이 자습서에서는 Spring Boot를 사용하지만 Azure OpenAI를 사용하여 채팅 애플리케이션을 빌드하는 핵심 개념은 모든 Java 웹 애플리케이션에 적용됩니다. Tomcat 또는 JBoss EAP와 같은 App Service에서 다른 호스팅 옵션을 사용하는 경우 여기에 표시된 인증 패턴 및 Azure SDK 사용량을 기본 프레임워크에 적용할 수 있습니다.

Azure App Service에서 실행되는 챗봇을 보여 주는 스크린샷

이 튜토리얼에서는 다음을 배우게 됩니다:

  • Azure OpenAI 리소스를 만들고 언어 모델을 배포합니다.
  • Azure OpenAI에 연결하는 Spring Boot 애플리케이션을 빌드합니다.
  • 종속성 주입을 사용하여 Azure OpenAI 클라이언트를 구성합니다.
  • Azure App Service에 애플리케이션을 배포합니다.
  • 개발 환경과 Azure에서 암호 없는 보안 인증을 구현합니다.

필수 조건

  • 활성 구독이 있는 Azure 계정
  • GitHub Codespaces를 사용하기 위한 GitHub 계정

1. Azure OpenAI 리소스 만들기

이 섹션에서는 GitHub Codespaces에서 Azure CLI를 사용하여 Azure OpenAI 리소스를 만듭니다.

  1. GitHub 계정으로 GitHub Codespaces에 로그인합니다.

  2. 새로운 빈 코드스페이스를 만들려면 빈 타일에서 이 템플릿 사용 을 선택하세요.

  3. Codespace 터미널에서 Azure CLI를 설치합니다.

    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
    
  4. Azure 계정에 로그인합니다.

    az login
    

    터미널의 지침에 따라 인증합니다.

  5. 리소스 그룹 및 Azure OpenAI 서비스에 대한 이름을 제공하고 적절한 Azure 지역을 위치로 설정하여 환경 변수를 설정합니다.

    export RESOURCE_GROUP="<group-name>"
    export OPENAI_SERVICE_NAME="<azure-openai-name>"
    export APPSERVICE_NAME="<app-name>"
    export LOCATION="<azure-region>"
    

    중요합니다

    위치는 선택한 모델의 지역 가용성에 연결됩니다. 모델 및 배포 유형 가용성은 Azure 지역 및 청구 계층에 따라 다릅니다. 이 자습서에서는 표준 배포 유형에 따라 여러 지역에서 사용할 수 있는 를 사용합니다 gpt-4o-mini.

    위치를 선택하기 전에 모델 요약 및 지역 가용성 테이블을 참조하여 선호하는 지역에서 모델 지원을 확인합니다.

  6. 사용자 지정 도메인을 사용하여 리소스 그룹 및 Azure OpenAI 리소스를 만든 다음, 모델을 추가합니다 gpt-4o-mini .

    # Resource group
    az group create --name $RESOURCE_GROUP --location $LOCATION
    # Azure OpenAI resource
    az cognitiveservices account create \
      --name $OPENAI_SERVICE_NAME \
      --resource-group $RESOURCE_GROUP \
      --location $LOCATION \
      --custom-domain $OPENAI_SERVICE_NAME \
      --kind OpenAI \
      --sku s0
    # gpt-4o-mini model
    az cognitiveservices account deployment create \
      --name $OPENAI_SERVICE_NAME \
      --resource-group $RESOURCE_GROUP \
      --deployment-name gpt-4o-mini \
      --model-name gpt-4o-mini \
      --model-version 2024-07-18 \
      --model-format OpenAI \
      --sku-name Standard \
      --sku-capacity 1
    # Cognitive Services OpenAI User role that lets the signed in Azure user read models from Azure OpenAI
    az role assignment create \
      --assignee $(az ad signed-in-user show --query id -o tsv) \
      --role "Cognitive Services OpenAI User" \
      --scope /subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.CognitiveServices/accounts/$OPENAI_SERVICE_NAME
    

이제 Azure OpenAI 리소스가 있으므로 상호 작용하는 웹 애플리케이션을 만들 수 있습니다.

2. Spring Boot 웹앱 만들기 및 설정

  1. Codespace 터미널에서 Spring Boot REST 샘플을 작업 영역에 복제하고 처음으로 실행해 봅니다.

    git clone https://github.com/rd-1-2022/rest-service .
    mvn spring-boot:run
    

    특정 포트에서 앱을 사용할 수 있음을 나타내는 알림이 GitHub Codespaces에 표시됩니다. 브라우저에서 열기를 선택하여 새 브라우저 탭에서 앱을 시작합니다. 흰색 레이블 오류 페이지가 표시되면 Spring Boot 앱이 작동합니다.

  2. Codespace 터미널로 돌아가서 Ctrl+C를 사용하여 앱을 중지합니다.

  3. pom.xml 열고 다음 종속성을 추가합니다.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-ai-openai</artifactId>
        <version>1.0.0-beta.16</version>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-core</artifactId>
        <version>1.55.3</version>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-identity</artifactId>
        <version>1.16.0</version>
        <scope>compile</scope>
    </dependency>
    
  4. Application.java(src/main/java/com/example/restservice)와 동일한 디렉터리에서 ChatController.java 라는 Java 파일을 추가하고 다음 콘텐츠를 복사합니다.

    package com.example.restservice;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import com.azure.ai.openai.OpenAIAsyncClient;
    import com.azure.ai.openai.models.ChatChoice;
    import com.azure.ai.openai.models.ChatCompletionsOptions;
    import com.azure.ai.openai.models.ChatRequestMessage;
    import com.azure.ai.openai.models.ChatRequestUserMessage;
    import com.azure.ai.openai.models.ChatResponseMessage;
    import com.azure.core.credential.TokenCredential;
    import com.azure.identity.DefaultAzureCredentialBuilder;
    
    @Configuration
    class AzureConfig {
        // Reads the endpoint from environment variable AZURE_OPENAI_ENDPOINT
        @Value("${azure.openai.endpoint}")
        private String openAiEndpoint;
    
        // Provides a credential for local dev and production
        @Bean
        public TokenCredential tokenCredential() {
            return new DefaultAzureCredentialBuilder().build();
        }
    
        // Configures the OpenAIAsyncClient bean
        @Bean
        public OpenAIAsyncClient openAIClient(TokenCredential tokenCredential) {
            return new com.azure.ai.openai.OpenAIClientBuilder()
                    .endpoint(openAiEndpoint)
                    .credential(tokenCredential)
                    .buildAsyncClient();
        }
    }
    
    @Controller
    public class ChatController {
        private final OpenAIAsyncClient openAIClient;
    
        // Inject the OpenAIAsyncClient bean
        public ChatController(OpenAIAsyncClient openAIClient) {
            this.openAIClient = openAIClient;
        }
    
        @RequestMapping(value = "/", method = RequestMethod.GET)
        public String chatFormOrWithMessage(Model model, @RequestParam(value = "userMessage", required = false) String userMessage) {
            String aiResponse = null;
            if (userMessage != null && !userMessage.isBlank()) {
    
                // Create a list of chat messages
                List<ChatRequestMessage> chatMessages = new ArrayList<>();
                chatMessages.add(new ChatRequestUserMessage(userMessage));
    
                // Send the chat completion request
                String deploymentName = "gpt-4o-mini";
                StringBuilder serverResponse = new StringBuilder();
                var chatCompletions = openAIClient.getChatCompletions(
                    deploymentName, 
                    new ChatCompletionsOptions(chatMessages)
                ).block();
                if (chatCompletions != null) {
                    for (ChatChoice choice : chatCompletions.getChoices()) {
                        ChatResponseMessage message = choice.getMessage();
                        serverResponse.append(message.getContent());
                    }
                }
                aiResponse = serverResponse.toString();
            }
            model.addAttribute("aiResponse", aiResponse);
            return "chat";
        }
    }
    

    팁 (조언)

    이 자습서의 파일을 최소화하기 위해 코드는 Spring @Configuration 과 @Controller 클래스를 하나의 파일로 결합합니다. 프로덕션 환경에서는 일반적으로 유지 관리를 위해 구성 및 비즈니스 논리를 분리합니다.

  5. src/main/resources에서 템플릿 디렉터리를 만들고 채팅 인터페이스에 대해 다음 콘텐츠가 포함된 chat.html 추가합니다.

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Azure OpenAI Chat</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
    <div class="container py-4">
        <h2 class="mb-4">Azure OpenAI Chat</h2>
        <form action="/" method="get" class="d-flex mb-3">
            <input name="userMessage" class="form-control me-2" type="text" placeholder="Type your message..." autocomplete="off" required />
            <button class="btn btn-primary" type="submit">Send</button>
        </form>
        <div class="mb-3">
            <div th:if="${aiResponse}" class="alert alert-info">AI: <span th:text="${aiResponse}"></span></div>
        </div>
    </div>
    </body>
    </html>
    
  6. 터미널에서 OpenAI 엔드포인트를 검색합니다.

    az cognitiveservices account show \
      --name $OPENAI_SERVICE_NAME \
      --resource-group $RESOURCE_GROUP \
      --query properties.endpoint \
      --output tsv
    
  7. CLI 출력에서 해당 값을 추가하고 AZURE_OPENAI_ENDPOINT와 함께 앱을 다시 실행합니다.

    AZURE_OPENAI_ENDPOINT=<output-from-previous-cli-command> mvn spring-boot:run
    
  8. 브라우저에서 열기를 선택하여 새 브라우저 탭에서 앱을 시작합니다.

  9. 텍스트 상자에 메시지를 입력하고 보내기를 선택하고 앱에 Azure OpenAI의 메시지로 회신하는 데 몇 초 정도 걸립니다.

애플리케이션은 토큰 인증에 로그인한 Azure CLI 사용자를 자동으로 사용하는 DefaultAzureCredential을 사용합니다. 이 자습서의 뒷부분에서는 Azure App Service에 웹앱을 배포하고 관리 ID를 사용하여 Azure OpenAI 리소스에 안전하게 연결하도록 구성합니다. 코드 내에서 동일한 DefaultAzureCredential 태그로 관리형 ID를 감지하고 이를 인증에 사용할 수 있습니다. 추가 코드가 필요하지 않습니다.

3. Azure App Service에 배포 및 OpenAI 연결 구성

이제 앱이 로컬로 작동하므로 Azure App Service에 배포하고 관리 ID를 사용하여 Azure OpenAI에 대한 서비스 연결을 설정해 보겠습니다.

  1. OpenAI 리소스와 같은 리소스 그룹에서 리눅스 앱 서비스 플랜을 만들고, Java 21 웹 앱을 만드세요.

    az appservice plan create \
      --resource-group $RESOURCE_GROUP \
      --location $LOCATION \
      --name $APPSERVICE_NAME \
      --sku P0v3 \
      --is-linux
    az webapp create \
      --resource-group $RESOURCE_GROUP \
      --name $APPSERVICE_NAME \
      --plan $APPSERVICE_NAME \
      --runtime "JAVA:21-java21"
    
  2. Maven으로 배포용 패키지를 빌드하세요.

    mvn clean package
    
  3. 스프링 부트 JAR을 배포하세요.

    az webapp deploy \
      --resource-group $RESOURCE_GROUP \
      --name $APPSERVICE_NAME \
      --src-path target/rest-service-0.0.1-SNAPSHOT.jar \
      --type jar
    

    배치는 완료하는 데 몇 분이 걸릴 수 있습니다.

  4. 앱이 배포된 후 관리 ID를 사용하여 웹앱과 Azure OpenAI 리소스 간에 서비스 연결을 만듭니다.

    az webapp connection create cognitiveservices \
      --resource-group $RESOURCE_GROUP \
      --name $APPSERVICE_NAME \
      --target-resource-group $RESOURCE_GROUP \
      --account $OPENAI_SERVICE_NAME \
      --system-identity
    

    이 명령은 다음을 통해 웹앱과 Azure OpenAI 리소스 간에 연결을 만듭니다.

    • 웹앱에 대한 시스템 할당 관리 ID 생성
    • Azure OpenAI 리소스의 관리 ID에 Cognitive Services OpenAI 기여자 역할을 추가합니다.
    • 웹앱에 AZURE_OPENAI_ENDPOINT 앱 설정을 추가합니다.
  5. 브라우저에서 배포된 웹앱을 엽니다.

    az webapp browse --resource-group $RESOURCE_GROUP --name $APPSERVICE_NAME
    
  6. 텍스트 상자에 메시지를 입력하고 "보내기를 선택하고 앱에 Azure OpenAI의 메시지로 회신하는 데 몇 초 정도 걸립니다.

    Azure App Service에서 실행되는 챗봇을 보여 주는 스크린샷

이제 앱이 배포되고 관리 ID를 사용하여 Azure OpenAI에 연결됩니다. AZURE_OPENAI_ENDPOINT 삽입을 통해 앱 설정에 액세스하고 있음을 유의하십시오.

자주 묻는 질문

샘플에서 OpenAI 클라이언트에 @Configuration이 사용되는 이유와 Spring 빈의 사용 이유는 무엇인가요?

OpenAIAsyncClient에 Spring 빈을 사용하면 다음이 보장됩니다.

  • 모든 구성 속성(예: 엔드포인트)은 Spring에서 로드되고 삽입됩니다.
  • 자격 증명 및 클라이언트는 애플리케이션 컨텍스트가 완전히 초기화된 후에 만들어집니다.
  • 종속성 주입이 사용되며, 이는 Spring 애플리케이션에서 가장 강력한 표준 패턴입니다.

특히 Azure CLI 인증을 사용하는 DefaultAzureCredential 경우 비동기 클라이언트가 더 강력합니다. 동기 OpenAIClient 는 일부 로컬 개발 시나리오에서 토큰 획득과 관련된 문제가 발생할 수 있습니다. 비동기 클라이언트를 사용하면 이러한 문제를 방지할 수 있으며 권장되는 방법입니다.


Azure OpenAI 대신 OpenAI에 연결하려면 어떻게 해야 하나요?

대신 OpenAI에 연결하려면 다음 코드를 사용합니다.

OpenAIClient client = new OpenAIClientBuilder()
    .credential(new KeyCredential(<openai-api-key>))
    .buildClient();

자세한 내용은 OpenAI API 인증을 참조하세요.

App Service에서 연결 비밀을 사용할 때는 비밀을 코드베이스에 직접 저장하는 대신 Key Vault 참조 를 사용해야 합니다. 이렇게 하면 중요한 정보가 안전하게 유지되고 중앙에서 관리됩니다.


대신 API 키를 사용하여 Azure OpenAI에 연결할 수 있나요?

예, 관리 ID 대신 API 키를 사용하여 Azure OpenAI에 연결할 수 있습니다. 이 방법은 Azure OpenAI SDK 및 의미 체계 커널에서 지원됩니다.

App Service에서 연결 비밀을 사용할 때는 비밀을 코드베이스에 직접 저장하는 대신 Key Vault 참조 를 사용해야 합니다. 이렇게 하면 중요한 정보가 안전하게 유지되고 중앙에서 관리됩니다.


이 자습서에서는 DefaultAzureCredential이 어떻게 작동하나요?

사용 DefaultAzureCredential 가능한 가장 적합한 인증 방법을 자동으로 선택하여 인증을 간소화합니다.

  • 로컬 개발 중: 실행 az login후 로컬 Azure CLI 자격 증명을 사용합니다.
  • Azure App Service에 배포된 경우: 앱의 관리 ID를 보안 암호 없는 인증에 사용합니다.

이 방법을 사용하면 코드를 수정하지 않고 로컬 환경과 클라우드 환경 모두에서 안전하고 원활하게 실행할 수 있습니다.

다음 단계