AI Edge Function Calling SDK (FC SDK) היא ספרייה שמאפשרת למפתחים להשתמש בקריאה לפונקציות עם LLMs במכשיר. קריאה לפונקציות מאפשרת לקשר מודלים לכלים חיצוניים ולממשקי API, וכך לאפשר למודלים לבצע קריאה לפונקציות ספציפיות עם הפרמטרים הנדרשים כדי לבצע פעולות בעולם האמיתי.
במקום ליצור רק טקסט, LLM שמשתמש ב-FC SDK יכול ליצור קריאה מובנית לפונקציה שמבצעת פעולה, כמו חיפוש מידע עדכני, הגדרת התראות או ביצוע הזמנות.
במדריך הזה נסביר איך להוסיף לאפליקציה ל-Android את LLM Inference API עם FC SDK. המדריך הזה מתמקד בהוספת יכולות של קריאה לפונקציות ל-LLM במכשיר. למידע נוסף על השימוש ב-LLM Inference API, אפשר לעיין במדריך ל-LLM Inference ל-Android.
מדריך למתחילים
כדי להשתמש ב-FC SDK באפליקציה ל-Android, פועלים לפי השלבים הבאים. במדריך למתחילים הזה נעשה שימוש ב-LLM Inference API עם Hammer 2.1 (1.5B). ממשק ה-API של LLM Inference מותאם למכשירי Android מתקדמים, כמו Pixel 8 ו-Samsung S23 ואילך, והוא לא תומך באופן מהימן במהדמרים של מכשירים.
הוספת יחסי תלות
ה-FC SDK משתמש בספרייה com.google.ai.edge.localagents:localagents-fc, ו-LLM Inference API משתמש בספרייה com.google.mediapipe:tasks-genai. מוסיפים את שני יחסי התלות לקובץ build.gradle של אפליקציית Android:
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"/>
הורדת מודל
מורידים את Hammer 1B בפורמט 8 ביט מ-Hugging Face. מידע נוסף על המודלים הזמינים זמין במסמכי התיעוד בנושא מודלים.
מעבירים את התוכן של התיקייה 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
הצהרת הגדרות פונקציות
מגדירים את הפונקציות שיהיו זמינות למודל. כדי להמחיש את התהליך, המדריך למתחילים הזה כולל שתי פונקציות כשימוש בשיטות סטטיות שמחזירות תשובות שהוגדרו מראש. יישום מעשי יותר יהיה הגדרת פונקציות שמבצעות קריאה ל-API ל-REST או מאחזרות מידע ממסד נתונים.
הפונקציות 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 ומעבירים לו אובייקט של מעצב (formatter) עבור המודל. הפורמט של 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 Inference.
יצירת מודל
משתמשים באובייקט 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());
}
הקוד לדוגמה הוא הטמעה פשוטה מדי. למידע נוסף על האופן שבו אפליקציה יכולה לבדוק את התגובות של המודל, ראו עיצוב ופירוק.
איך זה עובד
בקטע הזה נספק מידע מעמיק יותר על המושגים והרכיבים המרכזיים של Function Calling SDK ל-Android.
דגמים
כדי להשתמש ב-Function Calling SDK, צריך דגם עם מעבד טקסט ופורמט. ה-FC SDK מכיל מעבד ופורמט מובנים למודלים הבאים:
כדי להשתמש במודל אחר עם FC SDK, צריך לפתח פורמטטור וניתוח תוכן משלכם שתואמים ל-LLM Inference API.
עיצוב וניתוח
חלק חשוב בתמיכה בקריאה לפונקציות הוא הפורמט של ההנחיות והניתוח של הפלט של המודל. אלה שני תהליכים נפרדים, אבל ה-FC SDK מטפל גם בפורמט וגם בניתוח באמצעות הממשק ModelFormatter.
הפורמט הוא זה שאחראי על המרת ההצהרות המובנות של הפונקציות לטקסט, על עיצוב התשובות של הפונקציות ועל הוספת אסימונים כדי לציין את ההתחלה והסיום של סבבי השיחה, וגם את התפקידים של סבבי השיחה האלה (למשל 'משתמש', 'מודל').
המנתח אחראי לזהות אם תגובת המודל מכילה קריאה לפונקציה. אם המנתח מזהה קריאה לפונקציה, הוא מנתח אותה לסוג נתונים מובנה. אחרת, המערכת תתייחס לטקסט כאל תגובה בשפה טבעית.
פענוח מוגבל
פענוח מוגבל הוא טכניקה שמנחה את יצירת הפלט של מודלים של LLM כדי לוודא שהוא עומד בפורמט מובנה מוגדר מראש, כמו אובייקטים של JSON או קריאות לפונקציות של Python. אכיפת האילוצים האלה מאפשרת למודל לעצב את הפלט שלו באופן שמתאים לפונקציות המוגדרות מראש ולסוגי הפרמטרים התואמים שלהן.
כדי להפעיל פענוח מוגבל, מגדירים את האילוצים באובייקט ConstraintOptions ומפעילים את השיטה enableConstraint של מופע ChatSession.
כשהאילוץ הזה מופעל, התגובה תכלול רק את הכלים שמשויכים ל-GenerativeModel.
בדוגמה הבאה מוסבר איך להגדיר פענוח מוגבל כדי להגביל את התגובה לקריאות לכלי. היא מגבילה את הקריאה לכלי כך שתתחיל בתחילית ```tool_code\n ותסתיים בסיומת \n```.
ConstraintOptions constraintOptions = ConstraintOptions.newBuilder()
.setToolCallOnly( ConstraintOptions.ToolCallOnly.newBuilder()
.setConstraintPrefix("```tool_code\n")
.setConstraintSuffix("\n```"))
.build();
chatSession.enableConstraint(constraintOptions);
כדי להשבית את האילוץ הפעיל באותו סשן, משתמשים ב-method disableConstraint:
chatSession.disableConstraint();