Revisões de código sempre foram cruciais para manter altos padrões e reforçar as melhores práticas em um projeto de codificação. Este não é um post sobre como os desenvolvedores devem revisar o código, é mais sobre delegar uma parte dele para a IA.
Como Michael Lynch menciona em seu post - "Como fazer revisões de código como um humano" - devemos deixar os computadores cuidarem das partes chatas da revisão de código. Enquanto Michael enfatiza uma ferramenta de formatação, eu gostaria de dar um passo adiante e deixar a inteligência artificial descobrir. Quer dizer, por que não aproveitar o boom da IA na indústria?
Agora, não estou dizendo que a IA deve ser usada no lugar de ferramentas de formatação e linters. Em vez disso, ela deve ser usada em cima disso, para capturar coisas triviais que podem passar despercebidas por um humano. É por isso que decidi criar uma ação do github que revisa o código de um diff de pull request e gera sugestões usando IA. Deixe-me explicar isso.
🚨 NOTA:
- Esta ação do GitHub agora está disponível no mercado do GitHub .
- É uma ação javascript - saiba mais sobre como criar ações javascript no github .
Para interagir com a API do GitHub, usei octokit
, que é uma espécie de SDK ou biblioteca cliente para interagir com a API do GitHub de forma idiomática.
Para que você obtenha o diff da solicitação de pull gerada, você precisa passar o cabeçalho Accept
com o valor application/vnd.github.diff
junto com os parâmetros necessários.
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, }, }); }
Se você não está familiarizado com as ações do GitHub, aqui está uma série de ações do GitHub 101 de Victoria Lo , que é um bom começo.
Depois de obter o diff, eu o analiso e removo as alterações indesejadas e, em seguida, o retorno em um esquema mostrado abaixo:
/** 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(), })
Ignorar arquivos é bem direto. A lista de entrada do usuário requer uma sequência separada por ponto e vírgula de padrões glob. Ela é então analisada, concatenada com a lista padrão de arquivos ignorados e desduplicada.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
A lista de arquivos ignorados é então usada para remover as alterações diff que se referem a esses arquivos ignorados. Isso lhe dá uma carga bruta contendo apenas as alterações que você deseja.
Assim que obtenho o payload bruto após analisar o diff, passo-o para a API da plataforma. Aqui está uma implementação da API OpenAI.
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; }
Você pode notar o uso do formato de resposta na implementação da API. Esse é um recurso fornecido por muitas plataformas LLM, que permite que você diga ao modelo para gerar a resposta em um esquema/formato específico. É especialmente útil neste caso, pois não quero que o modelo tenha alucinações e gere sugestões para arquivos ou posições incorretas na solicitação de pull, ou adicione novas propriedades à carga útil da resposta.
O prompt do sistema está lá para dar ao modelo mais contexto sobre como ele deve fazer a revisão de código e quais são algumas coisas para manter em mente. Você pode visualizar o prompt do sistema aqui github.com/murtuzaalisurti/better . O prompt do usuário contém o diff real, as regras e o contexto da solicitação de pull. É o que inicia a revisão de código.
Esta ação do github suporta os modelos OpenAI e Anthropic. Veja como ela implementa a API Anthropic:
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; }
Por fim, depois de recuperar as sugestões, eu as higienizo e as passo para a API do GitHub para adicionar comentários como parte da revisão.
Escolhi a maneira abaixo para adicionar comentários porque, ao criar uma nova avaliação, você pode adicionar todos os comentários de uma vez em vez de adicionar um único comentário por vez. Adicionar comentários um por um também pode acionar a limitação de taxa porque adicionar comentários aciona notificações e você não quer enviar spam para os usuários com notificações.
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; } }
Eu queria manter a ação do GitHub aberta e aberta a integrações, e é por isso que você pode usar qualquer modelo de sua escolha (veja a lista de modelos suportados ) , ou você pode ajustar e criar seu próprio modelo personalizado com base nos modelos base suportados e usá-lo com esta ação do GitHub.
Se você encontrar algum problema de token ou limitação de taxa , talvez seja necessário atualizar os limites do seu modelo consultando a documentação da respectiva plataforma.
Então, o que você está esperando? Se você tem um repositório no github, experimente a ação agora - ela está no mercado de ações do github .