قابلیت فراخوانی تابع در مدلهای GPT به برنامه شما اجازه می دهد توابع داخلی برنامه را بر اساس ورودی های کاربر فراخوانی کند. این به این معنی است که برنامه می تواند عملیات مختلفی از جمله، جستجو در وب، ارسال ایمیل، یا رزرو بلیط از طرف کاربران را انجام دهد، که این امر برنامه شما را قدرتمندتر از یک چت بات معمولی می کند.
در این پست، شما برنامهای می سازید که از آخرین نسخه از OpenAI SDK Node.js استفاده می کند. این برنامه در مرورگر اجرا می شود،
اگر Node.js بر روی کامپیوتر شما نصب نیست٬ میتوانید از طریق Scrimba کد برنامه را نوشته و اجرا کنید.
نحوه عملکرد برنامه #
این برنامه یک عامل یا Agent ساده است که به شما کمک می کند تا فعالیت های مورد علاقهتان را در اطراف خود پیدا کنید.
این برنامه دسترسی به دو تابع، getLocation()
و getCurrentWeather()
دارد، که به این معنی است که می تواند متوجه شود که شما کجا قرار دارید و هوا در حال حاضر چگونه است.
لازم به ذکر است که GPT هیچ کدی را برای شما اجرا نمی کند. فقط به برنامه شما می گوید که کدام توابع را باید در یک سناریو مشخص استفاده کند، و فراخوانی آنها را به برنامه واگذار می کند.
هنگامی که Agent مکان شما و هوا را می داند، از دانش داخلی GPT برای پیشنهاد فعالیت های مناسب محلی برای شما استفاده می کند.
برای اجرای کدهای زیر ابتدا باید یک کلید API را از طریق پنل کاربری گیلاس تولید کنید. برای این کار ابتدا یک حساب کاربری جدید بسازید یا اگر صاحب حساب کاربری هستید وارد پنل کاربری خود شوید. سپس، به صفحه کلید API بروید و با کلیک روی دکمه “ساخت کلید API” یک کلید جدید برای دسترسی به Gilas API بسازید.
نوشتن کد برنامه #
ابتدا یک کلاینت از OpenAI بسازید.
1import OpenAI from "openai";
2
3const openai = new OpenAI({
4 apiKey: process.env.GILAS_API_KEY, // <کلید API خود را اینجا بسازید https://dashboard.gilas.io/apiKey>
5 baseUrl: "https://api.gilas.io/v1/",
6 dangerouslyAllowBrowser: true,
7});
اگر کد خود را در محیط مرورگر در Scrimba اجرا می کنید٬ مقدار متغیر dangerouslyAllowBrowser: true
را تنظیم کنید.
ساخت دو تابع برای برنامه #
ابتدا یک تابع به نام getLocation
میسازیم که یا استفاده از سرویس IP API محل یوزر را تشخیص میدهد.
1async function getLocation() {
2 const response = await fetch("https://ipapi.co/json/");
3 const locationData = await response.json();
4 return locationData;
5}
سرویس IP API مجموعه ای از داده ها در مورد مکان شما را برمی گرداند، از جمله عرض و طول جغرافیایی که ما آنها را به عنوان آرگومان ها در تابع دوم getCurrentWeather
استفاده می کنیم. ما از Open Meteo API برای دریافت داده های هوای فعلی استفاده می کند.
1async function getCurrentWeather(latitude, longitude) {
2 const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=apparent_temperature`;
3 const response = await fetch(url);
4 const weatherData = await response.json();
5 return weatherData;
6}
معرفی توابع به مدل GPT #
برای اینکه مدل GPT هدف این توابع را بفهمد، ما باید آنها را برای مدل توصیف کنیم. برای این کار٬ یک آرایه به نام tools
ایجاد می کنیم که شامل یک شیء برای هر تابع است. هر شیء دو کلید خواهد داشت: type
, function
و کلید function
سه زیرکلید دارد: name
, description
و parameters
.
در زیر نحوه توصیف هر یک از توابع که شامل اسم تابع٬ آرگومانهای ورودی و توضیحی در مورد عملکرد تابع است را مشاهده میکنید.
1const tools = [
2 {
3 type: "function",
4 function: {
5 name: "getCurrentWeather",
6 description: "Get the current weather in a given location",
7 parameters: {
8 type: "object",
9 properties: {
10 latitude: {
11 type: "string",
12 },
13 longitude: {
14 type: "string",
15 },
16 },
17 required: ["longitude", "latitude"],
18 },
19 }
20 },
21 {
22 type: "function",
23 function: {
24 name: "getLocation",
25 description: "Get the user's location based on their IP address",
26 parameters: {
27 type: "object",
28 properties: {},
29 },
30 }
31 },
32];
ساخت آرایهای از پیامها #
ما همچنین باید یک آرایه messages
تعریف کنیم. این آرایه همه پیام های رفت و برگشت بین برنامه ما و مدل را پیگیری می کند. اولین شیء در آرایه باید همیشه یک پیغام سیستمی باشد تا به مدل بگوید چگونه رفتار کند.
1const messages = [
2 {
3 role: "system",
4 content:
5 "You are a helpful assistant. Only use the functions you have been provided with.",
6 },
7];
ساخت تابع agent #
اکنون آماده ایم تا منطق برنامه خود را که در تابع agent
قرار دارد ، بسازیم. این تابع یک آرگومان به نام userInput
را می گیرد.
کار را با افزودن userInput
به آرایه پیام ها شروع می کنیم. این بار، role
این پیام را برابر با user
قرار میدهیم تا مدل بداند که این ورودی از کاربر است.
1async function agent(userInput) {
2 messages.push({
3 role: "user",
4 content: userInput,
5 });
6 const response = await openai.chat.completions.create({
7 model: "gpt-4-turbo",
8 messages: messages,
9 tools: tools,
10 });
11 console.log(response);
12}
سپس، یک درخواست به اندپوینت Chat completions از طریق متود chat.completions.create()
در SDK Node ارسال می کنیم.
این متود یک شیء با مقادیر زیر را به عنوان آرگومان می گیرد.
model
: تصمیم می گیرد که از کدام مدل GPT استفاده کند (در مورد اینجاgpt-4-turbo
).messages
: تاریخچه کامل پیام ها بین کاربر و مدل تا این لحظه.tools
: لیستی از ابزارهایی که مدل ممکن است فراخوانی کند. در حال حاضر، فقط توابع به عنوان یک ابزار پشتیبانی می شوند.
اجرای برنامه #
حال میخواهیم سعی کنیم تا agent را با یک ورودی که نیاز به یک فراخوانی تابع برای دادن پاسخ مناسب به سوال ما را دارد، اجرا کنیم.
1agent("محل جغرافیایی من کجاست؟");
با اجرای کد بالا پاسخ زیر در کنسول چاپ میشود.
1{
2 id: "chatcmpl-84ojoEJtyGnR6jRHK2Dl4zTtwsa7O",
3 object: "chat.completion",
4 created: 1696159040,
5 model: "gpt-4-turbo",
6 choices: [{
7 index: 0,
8 message: {
9 role: "assistant",
10 content: null,
11 tool_calls: [
12 id: "call_CBwbo9qoXUn1kTR5pPuv6vR1",
13 type: "function",
14 function: {
15 name: "getLocation",
16 arguments: "{}"
17 }
18 ]
19 },
20 logprobs: null,
21 finish_reason: "tool_calls" // Model wants us to call a function
22 }],
23 usage: {
24 prompt_tokens: 134,
25 completion_tokens: 6,
26 total_tokens: 140
27 }
28 system_fingerprint: null
29}
این پاسخ به ما می گوید که برنامه باید یکی از توابع خود را فراخوانی کند، زیرا مقدار: finish_reason
برابر با tool_calls
است. نام تابع در کلید response.choices[0].message.tool_calls[0].function.name
قرار دارد که برابر با getLocation
٬تابعی از برنامه که ما به مدل معرفی کردهایم٬ است.
فراخوانی تابع در داخل برنامه #
حال که مدل تابع متناسب با ورودی کاربر که باید فراخوانی شود را به برگرداند٬ باید آن را در داخل برنامه فراخوانی کنیم.
برای این کار نام توابعی که به مدل معرفی کرده ایم را در یک ثابت به نام availableTools
ذخیره میکنیم.
و حال با استفاده از نام تابع برگردانده شده از سوی مدل میتوانیم تابع اصلی را پیدا کرده و آن را با پارامترهای مورد نیاز که آنها هم توسط مدل تولید شده اند فراخوانی کنیم.
البته در این جا هنوز نیازی به فراخوانی تابع با مقادیر آرگومانها نیست.
1const { finish_reason, message } = response.choices[0];
2
3if (finish_reason === "tool_calls" && message.tool_calls) {
4 const functionName = message.tool_calls[0].function.name;
5 const functionToCall = availableTools[functionName];
6 const functionArgs = JSON.parse(message.tool_calls[0].function.arguments);
7 const functionArgsArr = Object.values(functionArgs);
8 const functionResponse = await functionToCall.apply(null, functionArgsArr);
9 console.log(functionResponse);
10}
خروجی برای یوزر ما:
{ip: "193.212.60.170", network: "193.212.60.0/23", version: "IPv4", city: "Oslo", region: "Oslo County", region_code: "03", country: "NO", country_name: "Norway", country_code: "NO", country_code_iso3: "NOR", country_capital: "Oslo", country_tld: ".no", continent_code: "EU", in_eu: false, postal: "0026", latitude: 59.955, longitude: 10.859, timezone: "Europe/Oslo", utc_offset: "+0200", country_calling_code: "+47", currency: "NOK", currency_name: "Krone", languages: "no,nb,nn,se,fi", country_area: 324220, country_population: 5314336, asn: "AS2119", org: "Telenor Norge AS"}
حال این داده ها را به یک پیغام جدید در آرایه messages
اضافه می کنیم، جایی که نام تابعی را که فراخوانی کردیم را نیز مشخص می کنیم
1messages.push({
2 role: "function",
3 name: functionName,
4 content: `The result of the last function was this: ${JSON.stringify(
5 functionResponse
6 )}
7 `,
8});
توجه داشته باشید که role
به function
تغییر کرده است. این به مدل می گوید که پارامتر content
نتیجه فراخوانی تابع را دارد و نه ورودی کاربر را.
حال، ما باید یک درخواست جدید به مدل با این آرایه messages
به روز شده ارسال کنیم تا مدل با استفاده داده های جدید مجددا سعی کند که به سوال کاربر پاسخ دهد. برای این کار بهتر است یک حلقه ایجاد کنیم تا این رفت و برگشت بین agent و مدل را برای چند دفعه انجام دهد. انتظار ما این است که مدل نهایتا بتواند سوال کاربر را به درستی جواب دهد.
گد پایین شامل یک حلقه است که به برنامه اجازه میده تا کل روند را تا پنج بار اجرا کند. اگر ما finish_reason: "tool_calls"
را از مدل دریافت کنیم، فقط نتیجه فراخوانی تابع را به آرایه messages
اضافه می کنیم و به تکرار بعدی حلقه می رویم. در صورتی که finish_reason: "stop"
را دریافت کنیم، در این صورت معنی اش این است که مدل پاسخ مناسب را پیدا کرده است، بنابراین می توانیماز حلقه خارج شویم
.
1for (let i = 0; i < 5; i++) {
2 const response = await openai.chat.completions.create({
3 model: "gpt-4",
4 messages: messages,
5 tools: tools,
6 });
7 const { finish_reason, message } = response.choices[0];
8
9 if (finish_reason === "tool_calls" && message.tool_calls) {
10 const functionName = message.tool_calls[0].function.name;
11 const functionToCall = availableTools[functionName];
12 const functionArgs = JSON.parse(message.tool_calls[0].function.arguments);
13 const functionArgsArr = Object.values(functionArgs);
14 const functionResponse = await functionToCall.apply(null, functionArgsArr);
15
16 messages.push({
17 role: "function",
18 name: functionName,
19 content: `
20 The result of the last function was this: ${JSON.stringify(
21 functionResponse
22 )}
23 `,
24 });
25 } else if (finish_reason === "stop") {
26 messages.push(message);
27 return message.content;
28 }
29}
30return "The maximum number of iterations has been met without a suitable answer. Please try again with a more specific input.";
اجرای برنامهی نهایی #
حالا آماده امتحان کردن برنامهی نهایی هستیم. من از agent خواهم خواست که بر اساس مکان من و آب و هوای فعلی، چند فعالیت مناسب را پیشنهاد کند.
1const response = await agent(
2 "لطفا بر اساس موقعیت جغرافیایی من و آب و هوای فعلی جند فعالیت مناسب را پیشنهاد بده."
3);
4console.log(response);
و این جوابی هست که از برنامه میگیرم.
Based on your current location in Oslo, Norway and the weather (15°C and snowy),
here are some activity suggestions:
1. A visit to the Oslo Winter Park for skiing or snowboarding.
2. Enjoy a cosy day at a local café or restaurant.
3. Visit one of Oslo's many museums. The Fram Museum or Viking Ship Museum offer interesting insights into Norway’s seafaring history.
4. Take a stroll in the snowy streets and enjoy the beautiful winter landscape.
5. Enjoy a nice book by the fireplace in a local library.
6. Take a fjord sightseeing cruise to enjoy the snowy landscapes.
Always remember to bundle up and stay warm. Enjoy your day!
اگر نگاهی به لیست پیامهای رد و بدل شده بین agent و مدل بیاندازیم - response.choices[0].message
- می بینیم که مدل به برنامه دستور اجرای هر دو تابع را داده است. ابتدا، دستور اجرای تابع getLocation
و سپس تابع getCurrentWeather
را با “longitude”: “10.859”, “latitude”: “59.955” به عنوان آرگومان ها.
این داده ای است که از اولین فراخوانی تابعی که انجام دادیم، برگردانده شده است.
{"role":"assistant","content":null,"tool_calls":[{"id":"call_Cn1KH8mtHQ2AMbyNwNJTweEP","type":"function","function":{"name":"getLocation","arguments":"{}"}}]}
{"role":"assistant","content":null,"tool_calls":[{"id":"call_uc1oozJfGTvYEfIzzcsfXfOl","type":"function","function":{"name":"getCurrentWeather","arguments":"{\n\"latitude\": \"10.859\",\n\"longitude\": \"59.955\"\n}"}}]}
تبریک! شما توانستید یک عامل AI را که قادر به انجام کارهایی که کاربر از و میخواهد٬ است را ساختید.
اگر به دنبال چالش بیشتری هستید، میتوانید دامنه کارهایی را که agent شما از پس انجام آنها بر میاد را با تعریف توابع جدید بهبود دهید.