یادگیری ایجاد یک بسته R با افزونگی عمدی ?? یادداشتی برای خودم

?? یک بسته R به طرز خنده‌داری زائد برای فراخوانی‌های ساده OpenAI ایجاد کردم، اما برد واقعی یادگیری نحوه ساخت یک بسته R بود! ??? آیا کارآمد است؟ مطلقا نه! آیا ارزش وقت و تجربه را داشت؟ بله! آیا دوباره این کار را خواهم کرد؟ بله! آیا خراب می‌شود؟ بله! ??

انگیزه‌ها

ما اخیراً به خصوص از طریق reticulate زیاد از LLM و به ویژه OpenAI GPT4omini استفاده کرده‌ایم. هر بار که می‌خواهیم از آن برای یک پروژه جدید یا یک اسکریپت جدید استفاده کنیم، مدام بارگیری reticulate -> وارد کردن OpenAI -> مقداردهی اولیه آن -> وارد کردن API_key و غیره، تکراری، خسته‌کننده و غیره است.

اخیراً، با انگیزه از Alec Wong که لطفاً به من توصیه کرد پس از پرسیدن یک سوال مبتدیانه "چگونه کسی یک بسته R موجود را آزمایش و تغییر می‌دهد و درخواست کشش می‌کند؟" ?? R Package را بخوانم. کتاب عالی است! من واقعاً از قسمت اول لذت می‌برم، در واقع به شما می‌آموزد که چگونه یک بسته R را از همان ابتدا ایجاد کنید! قبل از اینکه به جزئیات بپردازد. من این را بسیار طراوت‌بخش یافتم، زیرا می‌توانید به راحتی با پیروی از یک گردش کار، یک MVP را مستقیماً برای استفاده شخصی ایجاد کنید! ??

با در نظر داشتن LLM و تمایل به یادگیری نحوه ایجاد یک بسته R (به طور صحیح، بهترین روش ??)، چرا سعی نکنیم بسته خودمان را به نام myopenai بسازیم!؟ بسیار خب، در حال حاضر یک بسته درخشان به نام ellmer وجود دارد و پروژه فعلی من احتمالاً زائد است (از این رو عنوان)، اما چه راهی بهتر از این برای شروع یادگیری وجود دارد؟ و ما قصد داریم این کار را به روش ناکارآمد از طریق reticulate انجام دهیم (این زمانی است که می‌بینید متخصصان R از روی ناباوری سر خود را تکان می‌دهند ؟؟) به جای API http! ?? در یادگیری نحوه ایجاد یک بسته R با من همراه شوید!

جدی می‌گویم، این روش کارآمدی برای استفاده از openAI API نیست… فقط برای اهداف نمایشی است.

کد اصلی

 reticulate::use_virtualenv("openai") library(reticulate)  openai <- import("openai") OpenAI <- openai$OpenAI  client = OpenAI(api_key = 'YOUR API KEY') ## change this to yours    response <- client$chat$completions$create(   model = "gpt-4o-mini",   messages = list(dict(     role = "system",     content = "You are a summarizer. Please summarize the following abstract.      Include statistics.Use emoji to shorten characters. The summary must not      exceed 200 characters—if you reach 200 characters, stop immediately.      Do not add any extra commentary or exceed the 200-character limit."   ), dict(     role = "user",     content = df_new[10,"item_description"] |> pull()   )   ),   temperature = 0 )  (summary <- response$choices[[1]]$message$content) 

کد بالا کد قبلی ما بود. اکنون ما از کد بالا استفاده خواهیم کرد، آن را به یک تابع تبدیل می‌کنیم و امیدواریم در پایان پروژه یک کتابخانه به نام myopenai داشته باشیم و بتوانیم یک تابع chat() را فراخوانی کنیم و فقط نیاز به وارد کردن اعلان یا انتقال اعلان به تابع داشته باشیم.

اهداف:

گردش کار

یادداشتی سریع برای خودم:

  1. بارگیری devtool، create_package()
  2. use_git()، احتمالاً نیاز به راه اندازی مجدد دارد
  3. بارگیری مجدد devtools، نوشتن اولین تابع
  4. use_r(“function”)، سپس تابع را در آنجا وارد کنید
  5. load_all()
  6. check()
  7. ویرایش DESCRIPTION
  8. use_mit_license()
  9. وارد کردن اسکلت roxygen2 در تابع، ویرایش
  10. document()
  11. از آنجا که به بسته‌های دیگر نیاز داریم، use_package()
  12. check()

usethis::create_package

 usethis::create_package("myopenai") 

اگر از قبل یک .git در دایرکتوری کاری خود دارید، ممکن است چیزی شبیه به این ببینید، سعی کنید یک بسته را در یک دایرکتوری جدید ایجاد کنید که دایرکتوری والد با .git در آن نداشته باشد و به یک جلسه پروژه جدید با اساسی باز می‌شود، چیزی شبیه به usethiss::create_package("/Users/you/Document/myopenai"). متوجه خواهید شد که فایل‌های اساسی مانند.

برای اینکه دایرکتوری‌ها یا فایل‌ها چه چیزی را نشان می‌دهند، اینجا را بخوانید.

use_git

 library(devtools)  use_git() 

2 سوال دریافت خواهید کرد، اساساً اگر می‌خواهید از git برای کنترل نسخه استفاده کنید و همچنین اگر می‌خواهید commit کنید. به هر دو بله بگویید. سپس جلسه پروژه شما مجدداً راه اندازی می‌شود. توجه داشته باشید که هر بار که مجدداً راه اندازی می‌شود، باید devtools را دوباره بارگیری کنید.

نوشتن یک تابع

هنوز متوجه نشده‌ام که بهترین راه برای انجام این کار چیست، آیا یک کد کاربردی حداقل را در جای دیگری می‌نویسیم و سپس آن را در پروژه کپی می‌کنیم یا فقط یک اسکریپت R بدون عنوان ایجاد می‌کنیم و قبل از تابع use_r() یک کد کاربردی حداقل می‌نویسیم؟ به هر حال، فعلاً فقط یک اسکریپت بدون عنوان ایجاد کنید و شروع به نوشتن کنید قبل از اینکه آن را در یک فایل R سازمان‌یافته‌تر paste کنیم.

 chat <- function(prompt="how are you?", system="", temp=0, max_tokens = 500L) {    ## If virtualenv does not exist, create and install openai   if (reticulate::virtualenv_exists(envname = "openai")==FALSE) {     reticulate::virtualenv_create(envname = "openai", packages = c("openai"))     }    ## Start Env   reticulate::use_virtualenv("openai")    ## Initialize OpenAI   OpenAI = reticulate::import("openai")$OpenAI   client = OpenAI(api_key = "YOUR API KEY")   ## Prompt   response = client$chat$completions$create(     model = "gpt-4o-mini",     messages = list(reticulate::dict(       role = "system",       content = system     ),     reticulate::dict(       "role" = "user",       "content" = prompt     )),     temperature = temp,     max_tokens = max_tokens   )    ## Response   message = response$choices[[1]]$message$content    return(message) } 

بسیار خب، کد بالا کاملاً شبیه به کد اصلی ما است، اما حداقل اکنون یک تابع است و ما برخی از پارامترهای پیش‌فرض را وارد کرده‌ایم، بنابراین وقتی chat() را بدون هیچ پارامتری در آن فراخوانی می‌کنیم، حداقل چیزی را برمی‌گرداند. توجه داشته باشید که کد بالا اصلاً کارآمد نیست، بهترین راه برای برقراری ارتباط با openAI API هنوز از طریق httr2 است، وابستگی‌های بسیار کمتری خواهید داشت. همچنین، توجه داشته باشید که کد بالا دوباره بهترین تمرین کدنویسی دفاعی نیست، اما می‌توانیم از آنجا بهبود بخشیم، از جمله تنظیم کلید API خود در .Rprofile.

usethis::use_r()

 usethis::use_r("chat") 

این یک فایل R خالی به نام chat.R در پوشه R ایجاد می‌کند. سپس کد تابع حداقل خود را در این اسکریپت کپی می‌کنیم. هنوز هیچ کاری را اجرا نکنید! این مرحله بعدی است. ما لزوماً نباید برای هر تابع یک اسکریپت R ایجاد کنیم. حداقل باید آن را به گونه‌ای نام‌گذاری کنیم که برای ماشین و انسان قابل خواندن باشد، در صورت استفاده از تاریخ، مرتب‌سازی آسان باشد. در اینجا بیشتر در مورد راهنمای سبک Tidy بخوانید.

در مورد سازماندهی؟ سخت است، مانند اختصار هوشمندانه

توصیف دقیق اینکه چگونه باید کد خود را در چندین فایل سازماندهی کنید، سخت است. من فکر می‌کنم بهترین قاعده این است که اگر می‌توانید به یک فایل یک نام مختصر بدهید که همچنان محتوای آن را تداعی کند، به یک سازماندهی خوب رسیده‌اید. اما رسیدن به این نقطه سخت است. - راهنمای سبک Tidyverse

devtools::load_all()

در کنسول، موارد زیر را وارد کنید:

 devtools::load_all() 

این باعث می‌شود که کل بسته در دسترس / تعاملی باشد. و این زمانی است که می‌توانیم تابع خود را آزمایش کنیم و در صورت وجود خطا، اشکال‌زدایی کنیم. همانطور که در تصویر بالا می‌بینید، وقتی chat() را فراخوانی می‌کنیم، یک پاسخ برمی‌گرداند. این کتاب تفاوت بین source و running as a library را توضیح می‌دهد، اینجا

devtools::check()

 devtools::check() 

تابع check() ضروری است زیرا تست‌های جامعی را برای اطمینان از اینکه تمام اجزای یک بسته R به درستی کار می‌کنند، اجرا می‌کند و مشکلات را در مراحل اولیه که رفع آنها آسان‌تر است، catch می‌کند. این تأیید می‌کند که بسته شما با استانداردهای CRAN مطابقت دارد، کیفیت کد را حفظ می‌کند و از مشکلات downstream برای کاربران جلوگیری می‌کند. بررسی منظم عادات توسعه خوبی را ایجاد می‌کند، به خصوص قبل از به اشتراک گذاشتن بسته خود با دیگران یا ارسال آن به مخازن.

تصویر بالا نشان می‌دهد که 2 هشدار داریم، بیایید آن را رفع کنیم!

ویرایش DESCRIPTION

قبل از اینکه شروع به رفع 2 مورد بالا کنیم، بیایید DESCRIPTION خود را ویرایش کنیم.

در صورت تمایل عنوان، توضیحات، اطلاعات نویسنده را ویرایش کنید

usethis::use_mit_license()

 # if you already loaded `devtools` you can just use_mit_license() 

یک صفحه مجوز ایجاد می‌کند که می‌گوید بسته شما برای همه رایگان است! اگر نمی‌خواهید این کار را انجام دهید، می‌توانید از یک صفحه Creative Commons استفاده کنید یا از یک روش مجوز دیگر استفاده کنید.

وارد کردن اسکلت roxygen2 در تابع

roxygen2 سیستم مستندسازی درون‌کد R است. اسکلت‌ها را به تابع خود اضافه می‌کند تا به شما در مستندسازی بسته خود کمک کند و به این ترتیب check() خطاهایی را که قبلاً داشتیم نشان ندهد.

 #' Chat Function #' @param prompt user prompt #' @param system system prompt which helps the chatbot to get into the role #' @param temp temperature, the higher it is, the more creative it gets.  From 0 to 1 #' @param max_tokens maximum tokens the chatbot will respond #' @keywords chat #' @export #' @examples #' chat() chat <- function(prompt="how are you?", system="", temp=0, max_tokens = 500L) {    ## If virtualenv does not exist, create and install openai   if (reticulate::virtualenv_exists(envname = "openai")==FALSE) {     reticulate::virtualenv_create(envname = "openai", packages = c("openai"))     }    ## Start Env   reticulate::use_virtualenv("openai")    ## Initialize OpenAI   OpenAI = reticulate::import("openai")$OpenAI   client = OpenAI(api_key = "YOUR API KEY")   ## Prompt   response = client$chat$completions$create(     model = "gpt-4o-mini",     messages = list(reticulate::dict(       role = "system",       content = system     ),     reticulate::dict(       "role" = "user",       "content" = prompt     )),     temperature = temp,     max_tokens = max_tokens   )    ## Response   message = response$choices[[1]]$message$content    return(message) } 

موارد بالا را قبل از کدنویسی تابع خود کپی paste کنید. اگر قبلاً نام بسته‌ها را در توضیحات وارد کرده‌اید، دیگر نیازی به وارد کردن @import ندارید (در اینجا در حال وارد کردن reticulate هستیم). توضیحات را در هر پارامتر به درستی ویرایش کنید.

devtools::document()

 devtools::document() 

بررسی یک بار دیگر.

usethis::use_package()

وقتی متوجه می‌شویم که برای بسته خود به بسته‌های دیگری نیاز داریم، این مرحله مهم است. در مورد ما reticulate است.

 usethis::use_package("reticulate") 

devtools::check() و install() برای آخرین بار

حالا دوباره check() را اجرا کنید! و باید بدون هیچ اخطاری اجرا شود! اگر تمام مراحل بالا را به درستی دنبال کرده‌اید.

و بعد از آن، می‌توانیم بسته‌ای را که ایجاد کردیم install کنیم!

 devtools::install() 

تبریک می‌گویم! شما با موفقیت یک بسته R را ایجاد کرده‌اید!

بهبود بیشتر

اکنون که یک بسته اساسی داریم که حداقل به درستی کار می‌کند، بیایید راه‌های بیشتری برای بهبود آن بیابیم.

تقسیم فایل‌ها به Chat.R و utils.R

در حال حاضر، فقط یک فایل chat.R داریم، اما اگر عملکردهای بیشتری داشته باشیم، عاقلانه است که کد خود را ساختار و سازماندهی کنیم. ایده پشت این است که توابع مرتبط را در یک فایل دسته‌بندی کنیم و این کمک می‌کند فایل‌های کوچک را manageable نگه داریم و مرور را آسان‌تر می‌کند. ما یک فایل utils.R را برای نگهداری کدهایی ایجاد خواهیم کرد که توابع خاص را پشتیبانی می‌کنند و آن توابع برای همه در دسترس نیستند (داخلی).

از آنجا که ما فقط یک تابع در فایل chat.R داریم، ما هیچ کاری را به فایل utils.R نمی‌بریم.

افزودن تست واحد

بهترین راه برای اطمینان از درست کار کردن بسته ما، افزودن تست واحد است! اگر این کار را انجام دهید، حتی اگر بسته با حجم کد بزرگی بسازید، اگر تست‌های واحد را انجام دهید، اصلاح کد آسان‌تر خواهد بود. تست واحد به ما کمک می‌کند کد را قابل اعتمادتر و نگهداری‌پذیرتر کنیم.

 usethis::use_test("chat") 

این یک دایرکتوری test و یک test-chat.R ایجاد می‌کند. این تست به طور خودکار انجام نمی‌شود، ما باید تست‌هایی را اضافه کنیم که تعیین می‌کند تابع کار می‌کند یا نه و چگونه به داده‌ها پاسخ می‌دهد. برای اینکه تستی برای تابع chat به test-chat.R اضافه کنیم:

 test_that("Chat function returns a string", {   expect_type(chat(), "character") }) 

ما از تابع expect_type() از بسته testthat برای بررسی اینکه آیا تابع chat() یک رشته برمی‌گرداند استفاده می‌کنیم. اکنون، بیایید تست را با استفاده از:

 devtools::test() 

هر تستی در دایرکتوری test در کنسول تست می‌شود. اگر تست‌ها در کنسول موفقیت‌آمیز بودند، یک چراغ سبز "Pass" را در کنسول مشاهده خواهیم کرد.

تشکر و قدردانی

با تشکر از الک وونگ! کتاب بسته R برای این آموزش! من برای ساختن این بسته R بدون تکیه زیاد به دستورات بسته HTTR2 (برای تعامل با API)

درس‌های آموخته شده

حتی اگر این فقط یک پروژه 1 روزه است، من درس‌های زیادی را آموختم. نکته اصلی اکنون این است که نحوه انجام سریع تست و نصب را تمرین کنم.