ערכת ה-SDK של AI Edge Function Calling (FC SDK) היא ספרייה שמאפשרת למפתחים להשתמש ב-Function Calling עם מודלים גדולים של שפה (LLM) במכשיר. התכונה 'קריאה לפונקציות' מאפשרת לכם לקשר מודלים לכלים חיצוניים ולממשקי API, וכך המודלים יכולים לקרוא לפונקציות ספציפיות עם הפרמטרים הנדרשים כדי לבצע פעולות בעולם האמיתי.
במקום רק ליצור טקסט, מודל LLM שמשתמש ב-FC SDK יכול ליצור קריאה מובנית לפונקציה שמבצעת פעולה, כמו חיפוש מידע עדכני, הגדרת שעונים מעוררים או ביצוע הזמנות.
במדריך הזה מוסבר איך להוסיף את LLM Inference API (ממשק API להסקת מסקנות של מודל שפה גדול) לאפליקציית Android באמצעות 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.
מוסיפים את התגים הבאים uses-native-library לקובץ AndroidManifest.xml:
<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
הצהרה על הגדרות של פונקציות
מגדירים את הפונקציות שיהיו זמינות למודל. כדי להמחיש את התהליך, במדריך למתחילים הזה יש שתי פונקציות כשיטות סטטיות שמחזירות תשובות שמוגדרות מראש. יישום פרקטי יותר יגדיר פונקציות שקוראות ל-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());
מידע נוסף זמין במאמר בנושא אפשרויות ההגדרה של הסקת מסקנות מ-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();
שולחים הנחיות למודל דרך סשן הצ'אט באמצעות ה-method sendMessage
method:
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.
מודלים
ה-SDK של Function Calling דורש מודל עם מעצב ומנתח. ה-FC SDK מכיל מעצב ומנתח מובנים לדגמים הבאים:
- Gemma: משתמשים ב-
GemmaFormatter. - Llama: use
the
LlamaFormatter. - Hammer: משתמשים ב-
HammerFormatter.
כדי להשתמש במודל אחר עם FC SDK, צריך לפתח מעצב ומנתח משלך שתואמים ל-LLM Inference API.
עיצוב וניתוח
חלק חשוב בתמיכה בהפעלת פונקציות הוא עיצוב ההנחיות וניתוח הפלט של המודל. אלה שני תהליכים נפרדים, אבל FC SDK מטפל גם בעיצוב וגם בניתוח באמצעות הממשק ModelFormatter.
המעצב אחראי להמיר את הצהרות הפונקציה המובנות לטקסט, לעצב את התשובות של הפונקציה ולהוסיף טוקנים כדי לציין את ההתחלה והסוף של תורות בשיחה, וגם את התפקידים של התורות האלה (למשל, 'משתמש', 'מודל').
הניתוח אחראי לזיהוי אם התגובה של המודל מכילה קריאה לפונקציה. אם מנתח התחביר מזהה קריאה לפונקציה, הוא מנתח אותה לסוג נתונים מובנה. אחרת, הוא מתייחס לטקסט כתגובה בשפה טבעית.
פענוח מוגבל
פענוח מוגבל הוא טכניקה שמנחה את יצירת הפלט של מודלים גדולים של שפה (LLM) כדי להבטיח שהוא תואם לפורמט מובנה מוגדר מראש, כמו אובייקטים של JSON או קריאות לפונקציות של Python. האילוצים האלה מאפשרים למודל לעצב את הפלט שלו באופן שתואם לפונקציות המוגדרות מראש ולסוגי הפרמטרים התואמים שלהן.
כדי להפעיל פענוח מוגבל, מגדירים את המגבלות באובייקט ConstraintOptions ומפעילים את method 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);
כדי להשבית את האילוץ הפעיל באותו סשן, משתמשים בשיטה disableConstraint:
chatSession.disableConstraint();