Codeüberprüfungen waren schon immer von entscheidender Bedeutung, um hohe Standards aufrechtzuerhalten und bewährte Methoden in einem Codierungsprojekt durchzusetzen. In diesem Beitrag geht es nicht darum, wie Entwickler den Code überprüfen sollten, sondern eher darum, einen Teil davon an die KI zu delegieren.
Wie Michael Lynch in seinem Beitrag „So führen Sie Code-Reviews wie ein Mensch durch“ erwähnt, sollten wir die langweiligen Teile der Code-Überprüfung Computern überlassen . Während Michael ein Formatierungstool hervorhebt, würde ich gerne noch einen Schritt weitergehen und die künstliche Intelligenz die Arbeit erledigen lassen. Ich meine, warum nicht den KI-Boom in der Branche ausnutzen?
Ich sage jetzt nicht, dass KI anstelle von Formatierungstools und Linters verwendet werden sollte. Vielmehr sollte sie darüber hinaus eingesetzt werden, um triviale Dinge zu erkennen, die einem Menschen möglicherweise entgehen. Deshalb habe ich beschlossen, eine GitHub-Aktion zu erstellen, die einen Pull Request-Diff mithilfe von KI überprüft und Vorschläge generiert. Ich werde Sie durch die Aktion führen.
🚨 HINWEIS:
- Diese GitHub-Aktion ist jetzt im GitHub-Marktplatz verfügbar.
- Es handelt sich um eine JavaScript-Aktion. Erfahren Sie mehr über das Erstellen von JavaScript-GitHub-Aktionen .
Zur Interaktion mit der GitHub-API habe ich octokit
verwendet, eine Art SDK oder Client-Bibliothek für die idiomatische Interaktion mit der GitHub-API .
Damit Sie den Diff des erstellten Pull Requests erhalten, müssen Sie den Accept
-Header mit dem Wert application/vnd.github.diff
zusammen mit den erforderlichen Parametern übergeben.
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, }, }); }
Wenn Sie mit GitHub-Aktionen überhaupt nicht vertraut sind, finden Sie hier eine Reihe „GitHub-Aktionen 101“ von Victoria Lo . Sie ist ein guter Einstieg.
Sobald ich den Unterschied habe, analysiere ich ihn, entferne unerwünschte Änderungen und gebe ihn dann in dem unten gezeigten Schema zurück:
/** 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(), })
Das Ignorieren von Dateien ist ganz einfach. Die Benutzereingabeliste erfordert eine durch Semikolon getrennte Zeichenfolge mit Glob-Mustern. Diese wird dann analysiert, mit der Standardliste ignorierter Dateien verknüpft und Duplikate entfernt.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
Die Liste der ignorierten Dateien wird dann verwendet, um die Diff-Änderungen zu entfernen, die sich auf diese ignorierten Dateien beziehen. Dadurch erhalten Sie eine Rohnutzlast, die nur die gewünschten Änderungen enthält.
Sobald ich nach dem Parsen des Diffs die Rohnutzlast erhalte, übergebe ich sie an die Plattform-API. Hier ist eine Implementierung der OpenAI-API.
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; }
Möglicherweise bemerken Sie die Verwendung des Antwortformats in der API-Implementierung. Dies ist eine Funktion, die von vielen LLM-Plattformen bereitgestellt wird und mit der Sie dem Modell mitteilen können, dass die Antwort in einem bestimmten Schema/Format generiert werden soll. Dies ist in diesem Fall besonders hilfreich, da ich nicht möchte, dass das Modell halluziniert und Vorschläge für falsche Dateien oder Positionen in der Pull-Anfrage generiert oder der Antwortnutzlast neue Eigenschaften hinzufügt.
Die Systemeingabeaufforderung gibt dem Modell mehr Kontext, wie es die Codeüberprüfung durchführen soll und was zu beachten ist. Sie können die Systemeingabeaufforderung hier anzeigen: github.com/murtuzaalisurti/better . Die Benutzereingabeaufforderung enthält den eigentlichen Unterschied, die Regeln und den Kontext der Pull-Anforderung. Sie ist der Startschuss für die Codeüberprüfung.
Diese GitHub-Aktion unterstützt sowohl OpenAI- als auch Anthropic-Modelle. So implementiert sie die Anthropic-API:
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; }
Abschließend bereinige ich die Vorschläge, nachdem ich sie abgerufen habe, und übergebe sie an die GitHub-API, um im Rahmen der Überprüfung Kommentare hinzuzufügen.
Ich habe die folgende Methode zum Hinzufügen von Kommentaren gewählt, da Sie durch das Erstellen einer neuen Rezension alle Kommentare auf einmal hinzufügen können, anstatt jeweils einen einzelnen Kommentar hinzuzufügen. Das Hinzufügen einzelner Kommentare kann auch eine Ratenbegrenzung auslösen, da das Hinzufügen von Kommentaren Benachrichtigungen auslöst und Sie Benutzer nicht mit Benachrichtigungen zuspammen möchten.
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; } }
Ich wollte die GitHub-Aktion offen und für Integrationen halten, und deshalb können Sie jedes Modell Ihrer Wahl verwenden (siehe die Liste der unterstützten Modelle ) , oder Sie können die unterstützten Basismodelle optimieren und Ihr eigenes benutzerdefiniertes Modell erstellen und es mit dieser GitHub-Aktion verwenden.
Wenn Sie auf Token-Probleme oder Ratenbegrenzungen stoßen, sollten Sie Ihre Modellbegrenzungen anhand der Dokumentation der jeweiligen Plattform aktualisieren.
Also, worauf warten Sie noch? Wenn Sie ein Repository auf GitHub haben, probieren Sie die Aktion jetzt aus – sie befindet sich auf dem GitHub Action Marketplace .