Kod incelemeleri, bir kodlama projesinde yüksek standartları korumak ve en iyi uygulamaları güçlendirmek için her zaman önemli olmuştur. Bu, geliştiricilerin kodu nasıl incelemesi gerektiğiyle ilgili bir gönderi değil, daha çok bir kısmını yapay zekaya devretmekle ilgilidir.
Michael Lynch'in "İnsan Gibi Kod İncelemeleri Nasıl Yapılır" başlıklı yazısında belirttiği gibi, kod incelemesinin sıkıcı kısımlarıyla bilgisayarların ilgilenmesine izin vermeliyiz . Michael bir biçimlendirme aracını vurgularken, ben bunu bir adım öteye taşıyıp yapay zekanın çözmesine izin vermek istiyorum. Yani, sektördeki yapay zeka patlamasından neden faydalanmayalım ki?
Şimdi AI'nın biçimlendirme araçları ve lint araçları yerine kullanılması gerektiğini söylemiyorum. Bunun yerine, bir insanın kaçırabileceği önemsiz şeyleri yakalamak için bunun üstüne kullanılmalıdır. Bu yüzden, bir çekme isteği farkını inceleyen ve AI kullanarak öneriler üreten bir github eylemi oluşturmaya karar verdim. Size yol göstereyim.
🚨 NOT:
- Bu GitHub eylemi artık GitHub pazar yerinde mevcuttur.
- Bu bir javascript eylemidir - javascript github eylemleri oluşturma hakkında daha fazla bilgi edinin.
Github API'siyle etkileşime girebilmek için, Github API'siyle deyimsel bir şekilde etkileşime girmek için bir tür SDK veya istemci kütüphanesi olan octokit
kullandım.
Çekme isteğinin farkını alabilmeniz için Accept
başlığına application/vnd.github.diff
değerini ve gerekli parametreleri geçirmeniz gerekiyor.
async function getPullRequestDetails(octokit, { mode }) { let AcceptFormat = "application/vnd.github.raw+json"; if (mode === "diff") AcceptFormat = "application/vnd.github.diff"; if (mode === "json") AcceptFormat = "application/vnd.github.raw+json"; return await octokit.rest.pulls.get({ owner: github.context.repo.owner, repo: github.context.repo.repo, pull_number: github.context.payload.pull_request.number, headers: { accept: AcceptFormat, }, }); }
Eğer github action'larına hiç aşina değilseniz, Victoria Lo'nun github actions 101 serisi iyi bir başlangıç olabilir.
Diff'i aldıktan sonra onu ayrıştırıp istenmeyen değişiklikleri kaldırıyorum ve ardından aşağıda gösterilen şemaya döndürüyorum:
/** using zod */ schema = z.object({ path: z.string(), position: z.number(), line: z.number(), change: z.object({ type: z.string(), add: z.boolean(), ln: z.number(), content: z.string(), relativePosition: z.number(), }), previously: z.string().optional(), suggestions: z.string().optional(), })
Dosyaları yok saymak oldukça basittir. Kullanıcı girdi listesi, noktalı virgülle ayrılmış bir glob desenleri dizisi gerektirir. Daha sonra ayrıştırılır, yok sayılan dosyaların varsayılan listesiyle birleştirilir ve çoğaltılmaz.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
Yoksayılan dosyalar listesi daha sonra, bu yoksayılan dosyalara atıfta bulunan diff değişikliklerini kaldırmak için kullanılır. Bu size yalnızca istediğiniz değişiklikleri içeren ham bir yük verir.
Diff'i ayrıştırdıktan sonra ham veriyi aldığımda, bunu platform API'sine iletirim. İşte OpenAI API'sinin bir uygulaması.
async function useOpenAI({ rawComments, openAI, rules, modelName, pullRequestContext }) { const result = await openAI.beta.chat.completions.parse({ model: getModelName(modelName, "openai"), messages: [ { role: "system", content: COMMON_SYSTEM_PROMPT, }, { role: "user", content: getUserPrompt(rules, rawComments, pullRequestContext), }, ], response_format: zodResponseFormat(diffPayloadSchema, "json_diff_response"), }); const { message } = result.choices[0]; if (message.refusal) { throw new Error(`the model refused to generate suggestions - ${message.refusal}`); } return message.parsed; }
API uygulamasında yanıt biçiminin kullanıldığını fark edebilirsiniz. Bu, birçok LLM platformu tarafından sağlanan ve modele yanıtı belirli bir şema/biçimde oluşturmasını söylemenize olanak tanıyan bir özelliktir. Bu durumda özellikle yararlıdır çünkü modelin halüsinasyon görmesini ve çekme isteğindeki yanlış dosyalar veya konumlar için öneriler oluşturmasını veya yanıt yüküne yeni özellikler eklemesini istemiyorum.
Sistem istemi, modele kod incelemesini nasıl yapması gerektiği ve akılda tutulması gereken bazı şeyler hakkında daha fazla bağlam sağlamak için vardır. Sistem istemini buradan görüntüleyebilirsiniz github.com/murtuzaalisurti/better . Kullanıcı istemi gerçek diff'i, kuralları ve çekme isteğinin bağlamını içerir. Kod incelemesini başlatan şeydir.
Bu github eylemi hem OpenAI hem de Anthropic modellerini destekler. Anthropic API'yi şu şekilde uygular:
async function useAnthropic({ rawComments, anthropic, rules, modelName, pullRequestContext }) { const { definitions } = zodToJsonSchema(diffPayloadSchema, "diffPayloadSchema"); const result = await anthropic.messages.create({ max_tokens: 8192, model: getModelName(modelName, "anthropic"), system: COMMON_SYSTEM_PROMPT, tools: [ { name: "structuredOutput", description: "Structured Output", input_schema: definitions["diffPayloadSchema"], }, ], tool_choice: { type: "tool", name: "structuredOutput", }, messages: [ { role: "user", content: getUserPrompt(rules, rawComments, pullRequestContext), }, ], }); let parsed = null; for (const block of result.content) { if (block.type === "tool_use") { parsed = block.input; break; } } return parsed; }
Son olarak önerileri aldıktan sonra onları temizleyip yorumların incelemenin bir parçası olarak eklenmesi için GitHub API'sine iletiyorum.
Aşağıdaki yorum ekleme yolunu seçtim çünkü yeni bir inceleme oluşturarak tek seferde tek yorum eklemek yerine tüm yorumları tek seferde ekleyebilirsiniz. Yorumları tek tek eklemek ayrıca oran sınırlamasını da tetikleyebilir çünkü yorum eklemek bildirimleri tetikler ve kullanıcıları bildirimlerle spamlamak istemezsiniz.
function filterPositionsNotPresentInRawPayload(rawComments, comments) { return comments.filter(comment => rawComments.some(rawComment => rawComment.path === comment.path && rawComment.line === comment.line) ); } async function addReviewComments(suggestions, octokit, rawComments, modelName) { const { info } = log({ withTimestamp: true }); // eslint-disable-line no-use-before-define const comments = filterPositionsNotPresentInRawPayload(rawComments, extractComments().comments(suggestions)); try { await octokit.rest.pulls.createReview({ owner: github.context.repo.owner, repo: github.context.repo.repo, pull_number: github.context.payload.pull_request.number, body: `Code Review by ${modelName}`, event: "COMMENT", comments, }); } catch (error) { info(`Failed to add review comments: ${JSON.stringify(comments, null, 2)}`); throw error; } }
GitHub eylemini açık uçlu ve entegrasyonlara açık tutmak istedim ve bu yüzden dilediğiniz modeli kullanabilirsiniz ( desteklenen modeller listesine bakın) veya desteklenen temel modeller üzerine kendi özel modelinizi ince ayar yapıp oluşturabilir ve bu GitHub eylemiyle birlikte kullanabilirsiniz.
Herhangi bir token sorunu veya oran sınırlamasıyla karşılaşırsanız, ilgili platformun belgelerine başvurarak model sınırlarınızı yükseltmek isteyebilirsiniz.
Peki, daha ne bekliyorsunuz? Github'da deponuz varsa, eylemi şimdi deneyin - Github Action Marketplace'te .