Java로 Gemini 함수 호출을 사용하는 결정론적 생성형 AI

1. 소개

생성형 AI 모델은 자연어를 이해하고 응답하는 데 탁월합니다. 하지만 주소 표준화와 같은 중요한 작업에 정확하고 예측 가능한 출력이 필요한 경우는 어떻게 해야 할까요? 기존 생성형 모델은 동일한 프롬프트에 대해 때때로 다른 응답을 제공하여 불일치를 초래할 수 있습니다. 이때 Gemini의 함수 호출 기능이 빛을 발하며 AI 응답의 요소를 결정론적으로 제어할 수 있습니다.

이 Codelab에서는 주소 자동 완성 및 표준화 사용 사례를 통해 이 개념을 설명합니다. 이를 위해 다음 작업을 실행하는 Java Cloud 함수를 빌드합니다.

  1. 위도 및 경도 좌표를 가져옵니다.
  2. Google 지도 Geocoding API를 호출하여 해당 주소를 가져옵니다.
  3. Gemini 1.0 Pro 함수 호출 기능을 사용하여 필요한 특정 형식으로 이러한 주소를 결정론적으로 표준화하고 요약합니다.

그럼 자세히 살펴보죠.

2. Gemini 함수 호출

Gemini 함수 호출은 생성형 언어 모델의 유연성과 기존 프로그래밍의 정밀도를 결합할 수 있으므로 생성형 AI 시대에 두각을 나타냅니다.

Gemini 함수 호출을 구현하려면 다음 작업을 완료해야 합니다.

  1. 함수 정의: 함수를 명확하게 설명합니다. 설명에는 다음 정보가 포함되어야 합니다.
  • 함수 이름(예: getAddress)
  • 함수가 예상하는 매개변수(예: latlng을 문자열로)
  • 함수가 반환하는 데이터 유형(예: 주소 문자열 목록)
  1. Gemini용 도구 만들기: API 사양 형식의 함수 설명을 도구로 패키징합니다. 도구는 Gemini가 API의 기능을 이해하는 데 사용할 수 있는 특수 도구 상자라고 생각하면 됩니다.
  2. Gemini를 사용하여 API 오케스트레이션: Gemini에 프롬프트를 보내면 요청을 분석하고 제공한 도구를 사용할 수 있는 위치를 인식할 수 있습니다. 그러면 Gemini는 다음 작업을 실행하여 스마트 오케스트레이터 역할을 합니다.
  • 정의된 함수를 호출하는 데 필요한 API 매개변수를 생성합니다. Gemini는 사용자를 대신하여 API를 호출하지 않습니다. Gemini 함수 호출에서 생성한 매개변수와 서명을 기반으로 API를 호출해야 합니다.
  • Gemini는 API 호출 결과를 다시 생성에 제공하여 결과를 처리하고 구조화된 정보를 최종 응답에 통합합니다. 애플리케이션에 원하는 방식으로 이 정보를 처리할 수 있습니다.

다음 이미지는 데이터 흐름, 구현과 관련된 단계, 각 단계의 소유자(예: 애플리케이션, LLM 또는 API)를 보여줍니다.

b9a39f55567072d3.png

빌드할 항목

다음 작업을 실행하는 Java Cloud 함수를 만들고 배포합니다.

  • 위도 및 경도 좌표를 가져옵니다.
  • Google 지도 Geocoding API를 호출하여 해당 주소를 가져옵니다.
  • Gemini 1.0 Pro 함수 호출 기능을 사용하여 이러한 주소를 특정 형식으로 결정론적으로 표준화하고 요약합니다.

3. 요구사항

  • 브라우저(Chrome, Firefox 등)
  • 결제가 사용 설정된 Google Cloud 프로젝트.

4. 시작하기 전에

  1. Google Cloud 콘솔의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.
  2. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 프로젝트에 결제가 사용 설정되어 있는지 확인하는 방법을 알아보세요.
  3. Google Cloud 콘솔에서 Cloud Shell을 활성화합니다. 자세한 내용은 Cloud Shell 사용을 참고하세요.
  4. 프로젝트가 설정되지 않은 경우 다음 명령어를 사용하여 프로젝트를 설정합니다.
gcloud config set project <YOUR_PROJECT_ID>
  1. Cloud Shell에서 다음 환경 변수를 설정합니다.
export GCP_PROJECT=<YOUR_PROJECT_ID>
export GCP_REGION=us-central1
  1. Cloud Shell에서 다음 명령어를 실행하여 필요한 Google Cloud API를 사용 설정합니다.
gcloud services enable cloudbuild.googleapis.com cloudfunctions.googleapis.com run.googleapis.com logging.googleapis.com storage-component.googleapis.com cloudaicompanion.googleapis.com aiplatform.googleapis.com
  1. Cloud Shell 편집기를 열고 확장 프로그램을 클릭한 다음 Gemini + Google Cloud Code 확장 프로그램을 설치합니다.

5. Cloud 함수 구현

  1. Cloud Shell 편집기를 실행합니다.
  2. Cloud Code 를 클릭한 다음 Cloud Functions 섹션을 펼칩니다.
  3. 함수 만들기 (+) 아이콘을 클릭합니다.
  4. 새 애플리케이션 만들기 대화상자에서 Java: Hello World 옵션을 선택합니다.
  5. 프로젝트 경로에 프로젝트 이름을 제공합니다(예: GeminiFunctionCalling).
  6. 탐색기 를 클릭하여 프로젝트 구조를 확인한 다음 pom.xml 파일을 엽니다. 다음 이미지는 프로젝트 구조를 보여줍니다.

bdf07515f413dd9e.png

  1. pom.xml 파일의 <dependencies>... </dependencies> 태그 내에 필요한 종속 항목을 추가합니다. 이 프로젝트의 GitHub 저장소에서 전체 pom.xml에 액세스할 수 있습니다. 거기에서 pom.xml을 현재 수정 중인 프로젝트의 pom.xml 파일에 복사합니다.
  2. HelloWorld.java 클래스를 GeminiFunctionCalling GitHub 링크에서 복사합니다. API_KEYproject_id를 각각 Geocoding API 키와 Google Cloud 프로젝트 ID로 업데이트해야 합니다.

6. HelloWorld.java 클래스를 사용하여 함수 호출 이해

프롬프트 입력

이 예에서 입력 프롬프트는 latlong 값 40.714224,-73.961452의 주소는 무엇인가요?입니다.

다음은 파일의 입력 프롬프트에 해당하는 코드 스니펫입니다.

String promptText = "What's the address for the latlong value '" + latlngString + "'?"; //40.714224,-73.961452

API 사양

이 예에서는 역 Geocoding API가 사용됩니다. 다음은 API 사양입니다.

/* Declare the function for the API to invoke (Geo coding API) */ 
FunctionDeclaration functionDeclaration =
    FunctionDeclaration.newBuilder()
        .setName("getAddress")
        .setDescription("Get the address for the given latitude and longitude value.")
        .setParameters(
            Schema.newBuilder()
                .setType(Type.OBJECT)
                .putProperties(
                    "latlng",
                    Schema.newBuilder()
                        .setType(Type.STRING)
                        .setDescription("This must be a string of latitude and longitude coordinates separated by comma")
                        .build())
                .addRequired("latlng")
                .build())
        .build();

Gemini로 프롬프트 오케스트레이션

프롬프트 입력 및 API 사양이 Gemini로 전송됩니다.

// Add the function to a "tool"
Tool tool = Tool.newBuilder()
.addFunctionDeclarations(functionDeclaration)
.build();

// Invoke the Gemini model with the use of the tool to generate the API parameters from the prompt input.
GenerativeModel model = GenerativeModel.newBuilder()
.setModelName(modelName)
.setVertexAi(vertexAI)
.setTools(Arrays.asList(tool))
.build();
GenerateContentResponse response = model.generateContent(promptText);
Content responseJSONCnt = response.getCandidates(0).getContent();

이 응답은 API에 대한 오케스트레이션된 매개변수 JSON입니다. 다음은 출력 예시입니다.

role: "model"
parts {
 function_call {
   name: "getAddress"
   args {
     fields {
       key: "latlng"
       value {
         string_value: "40.714224,-73.961452"
       }
     }
   }
 }
}

다음 매개변수를 Reverse Geocoding API에 전달합니다. "latlng=40.714224,-73.961452"

오케스트레이션된 결과를 "latlng=VALUE" 형식과 일치시킵니다.

API 호출

다음은 API를 호출하는 코드 섹션입니다.

// Create a request
     String url = API_STRING + "?key=" + API_KEY + params;
     java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
         .uri(URI.create(url))
         .GET()
         .build();
     // Send the request and get the response
     java.net.http.HttpResponse<String> httpresponse = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
     // Save the response
     String jsonResult =  httpresponse.body().toString();

문자열 jsonResult에는 역 Geocoding API의 응답이 포함됩니다. 다음은 출력의 형식이 지정된 버전입니다.

"...277 Bedford Ave, Brooklyn, NY 11211, USA; 279 Bedford Ave, Brooklyn, NY 11211, USA; 277 Bedford Ave, Brooklyn, NY 11211, USA;..."

API 응답 처리 및 프롬프트 준비

다음 코드는 API의 응답을 처리하고 응답 처리 방법에 관한 안내와 함께 프롬프트를 준비합니다.

// Provide an answer to the model so that it knows what the result
     // of a "function call" is.
     String promptString =
     "You are an AI address standardizer for assisting with standardizing addresses accurately. Your job is to give the accurate address in the standard format as a JSON object containing the fields DOOR_NUMBER, STREET_ADDRESS, AREA, CITY, TOWN, COUNTY, STATE, COUNTRY, ZIPCODE, LANDMARK by leveraging the address string that follows in the end. Remember the response cannot be empty or null. ";

Content content =
         ContentMaker.fromMultiModalData(
             PartMaker.fromFunctionResponse(
                 "getAddress",
                 Collections.singletonMap("address", formattedAddress)));
     String contentString = content.toString();
     String address = contentString.substring(contentString.indexOf("string_value: \"") + "string_value: \"".length(), contentString.indexOf('"', contentString.indexOf("string_value: \"") + "string_value: \"".length()));

     List<SafetySetting> safetySettings = Arrays.asList(
       SafetySetting.newBuilder()
           .setCategory(HarmCategory.HARM_CATEGORY_HATE_SPEECH)
           .setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH)
           .build(),
       SafetySetting.newBuilder()
           .setCategory(HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT)
           .setThreshold(SafetySetting.HarmBlockThreshold.BLOCK_ONLY_HIGH)
           .build()
   );

Gemini 호출 및 표준화된 주소 반환

다음 코드는 이전 단계에서 처리된 출력을 Gemini에 프롬프트로 전달합니다.

GenerativeModel modelForFinalResponse = GenerativeModel.newBuilder()
     .setModelName(modelName)
     .setVertexAi(vertexAI)
     .build();
     GenerateContentResponse finalResponse = modelForFinalResponse.generateContent(promptString + ": " + address, safetySettings);
      System.out.println("promptString + content: " + promptString + ": " + address);
       // See what the model replies now
       System.out.println("Print response: ");
       System.out.println(finalResponse.toString());
       String finalAnswer = ResponseHandler.getText(finalResponse);
       System.out.println(finalAnswer);

finalAnswer 변수에는 표준화된 주소가 JSON 형식으로 포함됩니다. 다음은 출력 예시입니다.

{"replies":["{ \"DOOR_NUMBER\": null, \"STREET_ADDRESS\": \"277 Bedford Ave\", \"AREA\": \"Brooklyn\", \"CITY\": \"New York\", \"TOWN\": null, \"COUNTY\": null, \"STATE\": \"NY\", \"COUNTRY\": \"USA\", \"ZIPCODE\": \"11211\", \"LANDMARK\": null} null}"]}

이제 주소 표준화 사용 사례에서 Gemini 함수 호출이 작동하는 방식을 이해했으므로 Cloud 함수를 배포할 수 있습니다.

7. 배포 및 테스트

  1. 이미 GeminiFunctionCalling 프로젝트를 만들고 Cloud 함수를 구현한 경우 2단계로 진행합니다. 프로젝트를 만들지 않은 경우 Cloud Shell 터미널로 이동하여 이 저장소를 클론합니다. git clone https://github.com/AbiramiSukumaran/GeminiFunctionCalling
  2. 프로젝트 폴더로 이동합니다. cd GeminiFunctionCalling
  3. 다음 문을 실행하여 Cloud 함수를 빌드하고 배포합니다.
gcloud functions deploy gemini-fn-calling --gen2 --region=us-central1 --runtime=java11 --source=. --entry-point=cloudcode.helloworld.HelloWorld --trigger-http

다음은 배포 후 URL 형식입니다. https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/gemini-fn-calling

  1. 터미널에서 다음 명령어를 실행하여 Cloud 함수를 테스트합니다.
gcloud functions call gemini-fn-calling --region=us-central1 --gen2 --data '{"calls":[["40.714224,-73.961452"]]}'

다음은 임의 샘플 프롬프트에 대한 응답입니다. '{"replies":["{ "DOOR_NUMBER": "277", "STREET_ADDRESS": "Bedford Ave", "AREA": null, "CITY": "Brooklyn", "TOWN": null, "COUNTY": "Kings County", "STATE": "NY", "COUNTRY": "USA", "ZIPCODE": "11211", "LANDMARK": null}}```"]}'

8. 삭제

이 게시물에서 사용한 리소스의 비용이 Google Cloud 계정에 청구되지 않도록 하려면 다음 단계를 수행하세요.

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다.
  2. 프로젝트 목록에서 삭제하려는 프로젝트를 선택한 후 '삭제'를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.
  4. 프로젝트를 유지하려면 위의 단계를 건너뛰고 Cloud Functions로 이동하여 함수 목록에서 삭제하려는 함수를 선택한 후 삭제를 클릭하여 Cloud 함수를 삭제합니다.

9. 축하합니다

축하합니다. Java 애플리케이션에서 Gemini 함수 호출 기능을 성공적으로 사용하고 생성형 AI 작업을 결정론적이고 안정적인 프로세스로 전환했습니다. 사용 가능한 모델에 대한 자세한 내용은 Vertex AI LLM 제품 문서를 참고하세요.