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

1. 소개

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

이 Codelab에서는 주소 완성 및 표준화 사용 사례를 통해 이 개념을 보여줍니다. 이를 위해 다음 작업을 실행하는 Java Cloud 함수를 빌드하겠습니다.

  1. 위도와 경도 좌표를 취합니다.
  2. Google Maps 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는 사용자의 요청을 분석하여, 내가 제공한 도구를 사용할 수 있는 위치를 파악할 수 있습니다. 그러면 Gemini가 스마트 조정자 역할을 하여 다음 작업을 수행합니다.
  • 정의된 함수를 호출하는 데 필요한 API 매개변수를 생성합니다. Gemini는 사용자를 대신하여 API를 호출하지 않습니다. Gemini 함수 호출에서 생성한 매개변수와 서명을 기반으로 API를 호출해야 합니다.
  • Gemini는 API 호출의 결과를 다시 생성에 피드하는 방식으로 결과를 처리하고 구조화된 정보를 최종 응답에 통합합니다. 신청 시 원하는 방식으로 이 정보를 처리할 수 있습니다.

다음 이미지는 데이터 흐름, 구현 관련 단계, 애플리케이션, LLM, API와 같은 각 단계의 소유자를 보여줍니다.

b9a39f55567072d3.png

빌드할 항목

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

  • 위도 및 경도 좌표를 가져옵니다.
  • Google Maps 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. Create New Application 대화상자에서 Java: Hello World 옵션을 선택합니다.
  5. 프로젝트 경로에 있는 프로젝트의 이름을 입력합니다(예: GeminiFunctionCalling).
  6. Explorer를 클릭하여 프로젝트 구조를 확인한 후 pom.xml 파일을 엽니다. 다음 이미지는 프로젝트 구조를 보여줍니다.

bdf07515f413dd9e.png

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

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

프롬프트 입력

이 예시에서는 What's the address for the latlong value 40.714224,-73.961452라는 입력 프롬프트가 있습니다.

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

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

API 사양

이 예에서는 Reverse 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 제품 문서를 참조하세요.