Google Gemini AI API JSON 통신관련 이슈

배지원·2024년 9월 6일
0

에러

목록 보기
7/7

AI API JSON 통신 관련 이슈

Google Gemini AI API와 JSON 형태로 원하는 구조로 통신하는데 있어 문제 발생

상황

Google Gemini AI를 통해 질문을 내가 원하는 형태로 보내고 받는 과정에서 문제 발생

Content 기반 API 구조 - 텍스트 전용 입력

curl https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=$GOOGLE_API_KEY \
    -H 'Content-Type: application/json' \
    -X POST \
    -d '{
      "contents": [{
        "parts":[{
          "text": "Write a story about a magic backpack."}]}]}' 2> /dev/null

Request Body 구조

{
  "contents": [
      {
      "parts":
          {
          "text": "치킨집 소개글 작성해줘"
          }
      }
   ],
  "generationConfig": {
      "candidate_count":1,
      "max_output_tokens":1000,
      "temperature": 0.7
  }
}

Response Body 구조

{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "In the quaint town of Willow Creek, nestled amidst rolling hills and whispering willows, there lived an ordinary boy named Ethan. Ethan's life took an extraordinary turn the day he stumbled upon an enigmatic backpack hidden in the depths of his attic.\n\nCuriosity ignited within Ethan as he lifted the worn leather straps and unzipped its mysterious contents. Inside lay a shimmering array of vibrant objects and peculiar trinkets. There was a glowing orb that pulsated with an ethereal glow, a feather that seemed to have a life of its own, and a small, enigmatic key.\n\nAs Ethan explored each item, he realized they possessed astonishing abilities. The orb illuminated his path, casting a warm glow in the darkest of nights. The feather granted him the power of flight, allowing him to soar through the skies with newfound freedom. And the key opened a portal to a hidden world, a realm of endless wonder.\n\nArmed with his magical backpack, Ethan embarked on countless adventures. He flew over the towering mountains of Willow Creek, exploring their hidden secrets. He navigated the treacherous depths of the Enchanted Forest, where he encountered mythical creatures and ancient spirits. And he ventured into distant, unknown lands, uncovering lost civilizations and forgotten treasures.\n\nWith each adventure, Ethan's knowledge and abilities grew. He learned to harness the power of his backpack wisely, using its magic to help others and protect the world from evil forces. The backpack became an extension of himself, a symbol of hope and wonder in the face of adversity.\n\nAs the years went by, Ethan's reputation as the boy with the magic backpack spread far and wide. People from all walks of life came to him, seeking his guidance and protection. And Ethan never hesitated to lend a helping hand, using his extraordinary abilities to make the world a better place.\n\nIn the end, the magic backpack became more than just a collection of objects. It was a representation of Ethan's unwavering spirit, his boundless imagination, and his unwavering belief in the power of dreams. And as long as Ethan carried it with him, the magic of Willow Creek would live on, illuminating the darkest corners of the world with hope, wonder, and the limitless possibilities that resided within the heart of a child."
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0,
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ]
}
  • (1) RequestDto 매핑 관련 에러
    public GeminiRequest(String text) {
        Part part = new TextPart(text);
        Content content = new Content((List<Part>) part);
        this.contents = Arrays.asList(content);
    }
    • Content { Part { Text } } } 구조로 구성하기 위해 Content ( List) part )를 통해 객체 생성하는 과정에서 에러 발생
  • (2) Rest API 주소를 통해 Gemini API와 연동하는 URL 작성 변경 기존에는 UriComponets를 통해 간단하게 RestAPI를 구성하여 통신할려고 하였다. 하지만 간단하고 기본적인 Param값이 아닌 RequestBody를 통해 JSON형식으로 통신을 해야하는 문제 발생
    UriComponents uri = UriComponentsBuilder.newInstance()          
            .scheme("https")
            .host("generativelanguage.googleapis.com")
            .path("/v1beta/models/gemini-pro:generateContent")
            .queryParam("key",GOOGLE_API_KEY)
            .build();

원인 파악 과정

  • (1) RequestDto 매핑 관련 에러

    1. cannot be cast to class java.util.List 오류메세지 확인
    2. part는 TextPart의 인스턴스이므로, 이를 List로 직접 캐스팅할 수 없음
    3. 이를 위해 part 객체를 포함하는 단일 요소 리스트 생성 필요
  • (2) Rest API 주소를 통해 Gemini API와 연동하는 URL 작성 변경

    1. Rest API URL에 @Param을 통해 값을 외부 API로 보내 연결한다면 UriComponents를 사용하는데 이점이 있을 것이라 예상
    2. 하지만, Request Body를 구성하여 JSON 형식으로 보내야 하므로 DTO 파일 필요
    3. 또한, 기존 API에서 요구하는 구조가 아닌 내가 원하는 구조로 전송하기 위해 좀 더 복잡한 구조를 사용할 수 있는 다른 방법을 탐색

해결 과정

  • (1) RequestDto 매핑 관련 에러

    public GeminiRequest(String text) {
        Part part = new TextPart(text);
        Content content = new Content(Collections.singletonList(part));
        this.contents = Arrays.asList(content);
    }
    • 단일 요소 리스트를 만들어 주기 위해 Collections.singletonList 사용
    • singletonList(T o) → 단일 요소 리스트 생성 : 객체 o를 포함하는 불변의 리스트를 반환 → 불변성 : singletonList로 생성된 리스트는 불변으로, 리스트의 크기를 변경하거나 요소를 추가/삭제할 수 없음
    • 이러한 이유로 Collection.singletonList 사용하게 됨
  • (2) Rest API 주소를 통해 Gemini API와 연동하는 URL 작성 변경

    • JSON 통신을 하고 내가 원하는 DTO 방식으로 Request / Response를 API 통신하기 위해 HttpInterface 사용
    • HttpInterface : HTTP 요청을 위한 서비스를 자바 인터페이스와 어노테이션으로 정의할 수 있도록 도와준다. 그리고 해당 서비스를 구현하는 프록시 객체를 생성하면 이를 통해 손쉽게 HTTP 요청을 보낼 수 있다.
    1. 인터페이스 구성

      @HttpExchange("/v1beta/models")
      public interface GeminiInterface {
      
          @PostExchange("{model}:generateContent")
          GeminiResponse getCompletion(
                  @PathVariable String model,
                  @RequestBody GeminiRequest request
          );
      }
    • 현재 Post를 통해 Google Gemini AI API에 Json 형태의 RequestDto를 보내줘야 하므로 @PostExchange를 통해 Url을 구성해주었다.
    1. Config 구성

      @Bean
      public RestClient geminiRestClient(@Value("${gemini.baseurl}") String baseUrl,
                                         @Value("${googleai.api.key}") String apikey) {
          return RestClient.builder()
                  .baseUrl(baseUrl)
                  .defaultHeader("x-goog-api-key", apikey)
                  .defaultHeader("Content-Type", "application/json")
                  .defaultHeader("Accept", "application/json")
                  .build();
      }
      
      @Bean
      public GeminiInterface geminiInterface(@Qualifier("geminiRestClient") RestClient restClient) {
          RestClientAdapter adapter = RestClientAdapter.create(restClient);
          HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
          return factory.createClient(GeminiInterface.class);
      }
    • RestClient Builder를 통해 Rest 클라이언트를 구성
    • RestClient를 사용하여 RestClientAdapter를 생성
      • RestClientAdapter : Rest 클라이언트를 HTTP 서비스 프록시로 변환하는 역할
    • RestClientAdapter를 기반으로 프록시 생성
    • 마지막으로 인터페이스 구현체를 생성하여 Gemini API와 상호작용을 정의
      2번에서 만든 host 값 & Param 값
      baseUrl : https://generativelanguage.googleapis.com
      apikey : google.api.key
          
      1번에서 만든 path 값
      /v1beta/models/{model}:generateContent
      2개의 URL을 합쳐 https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=$GOOGLE_API_KEY를 만들어 외부 API와 연동 가능

느낀점

처음으로 외부 API와 연동하면서 어떤 방식으로 구성 해야 할지 생각을 많이 해봤다.
요청하는 방식에 따라 구성하는 코드와 사용하는 라이브러리가 변경이 되고 어떤 방식으로 통신하게 되는지 조금은 알 수 있었던 것 같다.

이 외에도 다른 여러 외부 API들과 연동하면서 통신 방식에 대해 공부해 나가야겠다.

profile
Web Developer

0개의 댓글