Android용 AI Edge 함수 호출 가이드

AI Edge 함수 호출 SDK (FC SDK)는 개발자가 기기 내 LLM으로 함수 호출을 사용할 수 있는 라이브러리입니다. 함수 호출을 사용하면 모델을 외부 도구 및 API에 연결하여 모델이 실제 작업을 실행하는 데 필요한 매개변수를 사용하여 특정 함수를 호출할 수 있습니다.

FC SDK를 사용하는 LLM은 텍스트를 생성하는 대신 최신 정보를 검색하거나 알람을 설정하거나 예약을 하는 등의 작업을 실행하는 함수에 대한 구조화된 호출을 생성할 수 있습니다.

이 가이드에서는 Android 애플리케이션에 FC SDK와 함께 LLM 추론 API를 추가하는 기본 빠른 시작을 안내합니다. 이 가이드에서는 기기 내 LLM에 함수 호출 기능을 추가하는 방법을 중점적으로 설명합니다. LLM Inference API 사용에 관한 자세한 내용은 Android용 LLM 추론 가이드를 참고하세요.

빠른 시작

Android 애플리케이션에서 FC SDK를 사용하려면 다음 단계를 따르세요. 이 빠른 시작에서는 Hammer 2.1(1.5B)과 함께 LLM 추론 API를 사용합니다. LLM 추론 API는 Pixel 8 및 삼성 S23 이상과 같은 고급 Android 기기에 최적화되어 있으며 기기 에뮬레이터를 안정적으로 지원하지 않습니다.

종속 항목 추가

FC SDK는 com.google.ai.edge.localagents:localagents-fc 라이브러리를 사용하고 LLM 추론 API는 com.google.mediapipe:tasks-genai 라이브러리를 사용합니다. Android 앱의 build.gradle 파일에 두 종속 항목을 모두 추가합니다.

dependencies {
    implementation 'com.google.mediapipe:tasks-genai:0.10.24'
    implementation 'com.google.ai.edge.localagents:localagents-fc:0.1.0'
}

Android 12 (API 31) 이상을 실행하는 기기의 경우 네이티브 OpenCL 라이브러리 종속 항목을 추가합니다. 자세한 내용은 uses-native-library 태그 문서를 참고하세요.

AndroidManifest.xml 파일에 다음 uses-native-library 태그를 추가합니다.

<uses-native-library android:name="libOpenCL.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-car.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-pixel.so" android:required="false"/>

모델 다운로드

Hugging Face에서 8비트 양자화 형식의 Hammer 1B를 다운로드합니다. 사용 가능한 모델에 관한 자세한 내용은 모델 문서를 참고하세요.

hammer2.1_1.5b_q8_ekv4096.task 폴더의 콘텐츠를 Android 기기로 푸시합니다.

$ adb shell rm -r /data/local/tmp/llm/ # Remove any previously loaded models
$ adb shell mkdir -p /data/local/tmp/llm/
$ adb push hammer2.1_1.5b_q8_ekv4096.task /data/local/tmp/llm/hammer2.1_1.5b_q8_ekv4096.task

함수 정의 선언

모델에 제공할 함수를 정의합니다. 이 프로세스를 보여주기 위해 이 빠른 시작에는 하드코딩된 응답을 반환하는 정적 메서드로 두 가지 함수가 포함되어 있습니다. 보다 실용적인 구현은 REST API를 호출하거나 데이터베이스에서 정보를 검색하는 함수를 정의하는 것입니다.

다음은 getWeathergetTime 함수를 정의합니다.

class ToolsForLlm {
    public static String getWeather(String location) {
        return "Cloudy, 56°F";
    }

    public static String getTime(String timezone) {
        return "7:00 PM " + timezone;
    }

    private ToolsForLlm() {}
}

FunctionDeclaration를 사용하여 각 함수를 설명하고 각 함수에 이름과 설명을 지정하고 유형을 지정합니다. 이렇게 하면 모델에 함수의 기능과 함수 호출 시기가 알려집니다.

var getWeather = FunctionDeclaration.newBuilder()
    .setName("getWeather")
    .setDescription("Returns the weather conditions at a location.")
    .setParameters(
        Schema.newBuilder()
            .setType(Type.OBJECT)
            .putProperties(
                "location",
                Schema.newBuilder()
                    .setType(Type.STRING)
                    .setDescription("The location for the weather report.")
                    .build())
            .build())
    .build();
var getTime = FunctionDeclaration.newBuilder()
    .setName("getTime")
    .setDescription("Returns the current time in the given timezone.")

    .setParameters(
        Schema.newBuilder()
            .setType(Type.OBJECT)
            .putProperties(
                "timezone",
                Schema.newBuilder()
                    .setType(Type.STRING)
                    .setDescription("The timezone to get the time from.")
                    .build())
            .build())
    .build();

Tool 객체에 함수 선언을 추가합니다.

var tool = Tool.newBuilder()
    .addFunctionDeclarations(getWeather)
    .addFunctionDeclarations(getTime)
    .build();

추론 백엔드 만들기

LLM 추론 API를 사용하여 추론 백엔드를 만들고 모델의 형식 지정자 객체를 전달합니다. FC SDK 형식 지정 도구 (ModelFormatter)는 형식 지정 도구와 파서 역할을 합니다. 이 빠른 시작에서는 Gemma-3 1B를 사용하므로 GemmaFormatter를 사용합니다.

var llmInferenceOptions = LlmInferenceOptions.builder()
    .setModelPath(modelFile.getAbsolutePath())
    .build();
var llmInference = LlmInference.createFromOptions(context, llmInferenceOptions);
var llmInferenceBackend = new llmInferenceBackend(llmInference, new GemmaFormatter());

자세한 내용은 LLM 추론 구성 옵션을 참고하세요.

모델 인스턴스화

GenerativeModel 객체를 사용하여 추론 백엔드, 시스템 프롬프트, 도구를 연결합니다. 이미 추론 백엔드와 도구가 있으므로 시스템 프롬프트만 만들면 됩니다.

var systemInstruction = Content.newBuilder()
      .setRole("system")
      .addParts(Part.newBuilder().setText("You are a helpful assistant."))
      .build();

GenerativeModel로 모델을 인스턴스화합니다.

var generativeModel = new GenerativeModel(
    llmInferenceBackend,
    systemInstruction,
    List.of(tool),
)

채팅 세션 시작

편의를 위해 이 빠른 시작에서는 단일 채팅 세션을 시작합니다. 독립적인 세션을 여러 개 만들 수도 있습니다.

GenerativeModel 인스턴스를 사용하여 채팅 세션을 시작합니다.

var chat = generativeModel.startChat();

sendMessage 메서드를 사용하여 채팅 세션을 통해 모델에 프롬프트를 전송합니다.

var response = chat.sendMessage("How's the weather in San Francisco?");

모델 응답 파싱

프롬프트를 모델에 전달한 후 애플리케이션은 응답을 검사하여 함수를 호출할지 아니면 자연어 텍스트를 출력할지 결정해야 합니다.

// Extract the model's message from the response.
var message = response.getCandidates(0).getContent().getParts(0);

// If the message contains a function call, execute the function.
if (message.hasFunctionCall()) {
  var functionCall = message.getFunctionCall();
  var args = functionCall.getArgs().getFieldsMap();
  var result = null;

  // Call the appropriate function.
  switch (functionCall.getName()) {
    case "getWeather":
      result = ToolsForLlm.getWeather(args.get("location").getStringValue());
      break;
    case "getTime":
      result = ToolsForLlm.getWeather(args.get("timezone").getStringValue());
      break;
    default:
      throw new Exception("Function does not exist:" + functionCall.getName());
  }
  // Return the result of the function call to the model.
  var functionResponse =
      FunctionResponse.newBuilder()
          .setName(functionCall.getName())
          .setResponse(
              Struct.newBuilder()
                  .putFields("result", Value.newBuilder().setStringValue(result).build()))
          .build();
  var functionResponseContent = Content.newBuilder()
        .setRole("user")
        .addParts(Part.newBuilder().setFunctionResponse(functionResponse))
        .build();
  var response = chat.sendMessage(functionResponseContent);
} else if (message.hasText()) {
  Log.i(message.getText());
}

샘플 코드는 지나치게 단순화된 구현입니다. 애플리케이션이 모델 응답을 검사하는 방법에 관한 자세한 내용은 형식 지정 및 파싱을 참고하세요.

작동 방식

이 섹션에서는 Android용 Function Calling SDK의 핵심 개념과 구성요소에 관한 심층 정보를 제공합니다.

모델

함수 호출 SDK에는 형식 지정자와 파서가 있는 모델이 필요합니다. FC SDK에는 다음 모델을 위한 내장 형식 지정자와 파서가 포함되어 있습니다.

  • Gemma: GemmaFormatter를 사용합니다.
  • Llama: LlamaFormatter를 사용합니다.
  • 해머: HammerFormatter를 사용합니다.

FC SDK에서 다른 모델을 사용하려면 LLM 추론 API와 호환되는 자체 형식 지정자와 파서를 개발해야 합니다.

형식 지정 및 파싱

함수 호출 지원의 핵심은 프롬프트 형식 지정 및 모델 출력 파싱입니다. 형식 지정과 파싱은 서로 다른 두 프로세스이지만 FC SDK는 ModelFormatter 인터페이스로 형식 지정과 파싱을 모두 처리합니다.

형식 지정자는 구조화된 함수 선언을 텍스트로 변환하고, 함수 응답의 형식을 지정하고, 대화 차례의 시작과 끝을 나타내는 토큰과 이러한 차례의 역할 (예: '사용자', '모델')을 삽입합니다.

파서는 모델 응답에 함수 호출이 포함되어 있는지 감지합니다. 파서가 함수 호출을 감지하면 이를 구조화된 데이터 유형으로 파싱합니다. 그렇지 않으면 텍스트를 자연어 응답으로 취급합니다.

제약된 디코딩

제약된 디코딩은 LLM의 출력 생성을 안내하여 JSON 객체 또는 Python 함수 호출과 같은 사전 정의된 구조화된 형식을 준수하도록 하는 기법입니다. 이러한 제약조건을 적용하면 모델은 사전 정의된 함수와 해당 매개변수 유형에 맞게 출력 형식을 지정합니다.

제약된 디코딩을 사용 설정하려면 ConstraintOptions 객체에 제약 조건을 정의하고 ChatSession 인스턴스의 enableConstraint 메서드를 호출합니다. 이 제약조건을 사용 설정하면 응답에 GenerativeModel와 연결된 도구만 포함되도록 제한됩니다.

다음 예는 도구 호출에 대한 응답을 제한하도록 제약된 디코딩을 구성하는 방법을 보여줍니다. 도구 호출이 ```tool_code\n 접두사로 시작하고 \n``` 접미사로 끝나도록 제한합니다.

ConstraintOptions constraintOptions = ConstraintOptions.newBuilder()
  .setToolCallOnly( ConstraintOptions.ToolCallOnly.newBuilder()
  .setConstraintPrefix("```tool_code\n")
  .setConstraintSuffix("\n```"))
  .build();
chatSession.enableConstraint(constraintOptions);

동일한 세션 내에서 활성 제약 조건을 사용 중지하려면 disableConstraint 메서드를 사용합니다.

chatSession.disableConstraint();