Android के लिए, एआई एज फ़ंक्शन कॉलिंग की गाइड

एआई एज फ़ंक्शन कॉलिंग एसडीके (एफसी एसडीके) एक लाइब्रेरी है. इससे डेवलपर, डिवाइस पर मौजूद एलएलएम के साथ फ़ंक्शन कॉलिंग का इस्तेमाल कर सकते हैं. फ़ंक्शन कॉलिंग की सुविधा की मदद से, मॉडल को बाहरी टूल और एपीआई से कनेक्ट किया जा सकता है. इससे मॉडल, असल ज़िंदगी में होने वाली कार्रवाइयों को पूरा करने के लिए, ज़रूरी पैरामीटर के साथ खास फ़ंक्शन कॉल कर पाते हैं.

FC SDK का इस्तेमाल करने वाला एलएलएम, सिर्फ़ टेक्स्ट जनरेट करने के बजाय, किसी फ़ंक्शन को स्ट्रक्चर्ड कॉल जनरेट कर सकता है. यह फ़ंक्शन कोई कार्रवाई करता है. जैसे, अप-टू-डेट जानकारी खोजना, अलार्म सेट करना या बुकिंग करना.

इस गाइड में, Android ऐप्लिकेशन में FC SDK के साथ LLM Inference API जोड़ने के बारे में बुनियादी जानकारी दी गई है. इस गाइड में, डिवाइस पर मौजूद एलएलएम में फ़ंक्शन कॉल करने की सुविधाएं जोड़ने के बारे में बताया गया है. LLM Inference API इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, Android के लिए LLM Inference गाइड देखें.

क्विकस्टार्ट

अपने Android ऐप्लिकेशन में FC SDK का इस्तेमाल करने के लिए, यह तरीका अपनाएं. इस क्विकस्टार्ट में, Hammer 2.1 (1.5B) के साथ LLM Inference API का इस्तेमाल किया गया है. LLM Inference API को Pixel 8 और Samsung S23 या इसके बाद के वर्शन जैसे हाई-एंड Android डिवाइसों के लिए ऑप्टिमाइज़ किया गया है. यह डिवाइस इम्यूलेटर के साथ ठीक से काम नहीं करता.

डिपेंडेंसी जोड़ें

FC SDK, com.google.ai.edge.localagents:localagents-fc लाइब्रेरी का इस्तेमाल करता है. वहीं, LLM Inference 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 (एपीआई 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 से, Hammer 1B को 8-बिट क्वांटाइज़्ड फ़ॉर्मैट में डाउनलोड करें. उपलब्ध मॉडल के बारे में ज़्यादा जानकारी के लिए, मॉडल का दस्तावेज़ देखें.

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 को कॉल करते हैं या डेटाबेस से जानकारी पाते हैं.

यहां getWeather और getTime फ़ंक्शन के बारे में बताया गया है:

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 Inference 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());

ज़्यादा जानकारी के लिए, एलएलएम इन्फ़रेंस कॉन्फ़िगरेशन के विकल्प देखें.

मॉडल को इंस्टैंशिएट करना

इन्फ़रेंस बैकएंड, सिस्टम प्रॉम्प्ट, और टूल को कनेक्ट करने के लिए, 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 Inference API के साथ काम करता हो.

फ़ॉर्मैटिंग और पार्सिंग

फ़ंक्शन कॉल करने की सुविधा के लिए, प्रॉम्प्ट को फ़ॉर्मैट करना और मॉडल के आउटपुट को पार्स करना ज़रूरी है. ये दोनों अलग-अलग प्रोसेस हैं. हालांकि, FC SDK, ModelFormatter इंटरफ़ेस की मदद से फ़ॉर्मैटिंग और पार्सिंग, दोनों को मैनेज करता है.

फ़ॉर्मेटर, स्ट्रक्चर्ड फ़ंक्शन के एलान को टेक्स्ट में बदलने, फ़ंक्शन के जवाबों को फ़ॉर्मैट करने, और टोकन डालने का काम करता है. इन टोकन से बातचीत के टर्न की शुरुआत और खत्म होने के साथ-साथ, उन टर्न की भूमिकाओं (जैसे, "उपयोगकर्ता", "मॉडल") के बारे में पता चलता है.

पार्सर यह पता लगाने के लिए ज़िम्मेदार होता है कि मॉडल के जवाब में फ़ंक्शन कॉल शामिल है या नहीं. अगर पार्सर को कोई फ़ंक्शन कॉल मिलता है, तो वह उसे स्ट्रक्चर्ड डेटा टाइप में पार्स करता है. ऐसा न होने पर, यह टेक्स्ट को आम बोलचाल वाली भाषा में जवाब के तौर पर लेता है.

डिकोडिंग पर पाबंदी

कॉन्स्ट्रेंट डिकोडिंग एक ऐसी तकनीक है जो एलएलएम को आउटपुट जनरेट करने में मदद करती है. इससे यह पक्का किया जाता है कि आउटपुट, पहले से तय किए गए स्ट्रक्चर्ड फ़ॉर्मैट के मुताबिक हो. जैसे, 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();