Las revisiones de código siempre han sido cruciales para mantener altos estándares y reforzar las mejores prácticas en un proyecto de codificación. Esta no es una publicación sobre cómo los desarrolladores deberían revisar el código, sino más bien sobre delegar una parte de este a la IA.
Como menciona Michael Lynch en su artículo "Cómo hacer revisiones de código como un ser humano" , deberíamos dejar que las computadoras se encarguen de las partes aburridas de la revisión de código. Si bien Michael hace hincapié en una herramienta de formato, me gustaría dar un paso más y dejar que la inteligencia artificial se encargue de ello. Es decir, ¿por qué no aprovechar el auge de la IA en la industria?
Ahora bien, no estoy diciendo que la IA deba utilizarse en lugar de herramientas de formato y depuradores. En cambio, debe utilizarse además de eso, para detectar cosas triviales que un humano podría pasar por alto. Por eso, decidí crear una acción de GitHub cuyo código revisa una diferencia de una solicitud de extracción y genera sugerencias utilizando IA. Déjame explicarte cómo hacerlo.
🚨 NOTA:
- Esta acción de GitHub ahora está disponible en el mercado de GitHub .
- Es una acción de javascript: obtenga más información sobre cómo crear acciones de javascript en Github .
Para interactuar con la API de Github, he utilizado octokit
, que es una especie de SDK o una biblioteca cliente para interactuar con la API de Github de forma idiomática.
Para que pueda obtener la diferencia de la solicitud de extracción generada, debe pasar el encabezado Accept
con el valor application/vnd.github.diff
junto con los parámetros requeridos.
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, }, }); }
Si no está familiarizado en absoluto con las acciones de Github, aquí hay una serie de acciones de Github 101 de Victoria Lo y es un buen comienzo.
Una vez que obtengo la diferencia, la analizo y elimino los cambios no deseados y luego la devuelvo en un esquema que se muestra a continuación:
/** 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 archivos es bastante sencillo. La lista de entrada del usuario requiere una cadena de patrones glob separados por punto y coma. Luego se analiza, se concatena con la lista predeterminada de archivos ignorados y se eliminan los duplicados.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
La lista de archivos ignorados se utiliza luego para eliminar los cambios de diferencias que hacen referencia a esos archivos ignorados. Esto le proporciona una carga útil sin procesar que contiene solo los cambios que desea.
Una vez que obtengo la carga útil sin procesar después de analizar la diferencia, la paso a la API de la plataforma. Aquí se muestra una implementación de la API de 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; }
Es posible que notes el uso del formato de respuesta en la implementación de la API. Esta es una característica que ofrecen muchas plataformas LLM y que te permite indicarle al modelo que genere la respuesta en un esquema o formato específico. Es especialmente útil en este caso, ya que no quiero que el modelo alucine y genere sugerencias de archivos o posiciones incorrectos en la solicitud de extracción, o que agregue nuevas propiedades a la carga útil de la respuesta.
El mensaje del sistema está ahí para darle al modelo más contexto sobre cómo debe hacer la revisión del código y qué cosas hay que tener en cuenta. Puedes ver el mensaje del sistema aquí github.com/murtuzaalisurti/better . El mensaje del usuario contiene la diferencia real, las reglas y el contexto de la solicitud de incorporación de cambios. Es lo que da inicio a la revisión del código.
Esta acción de GitHub es compatible con los modelos OpenAI y Anthropic. Así es como implementa la API de 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; }
Finalmente, después de recuperar las sugerencias, las limpio y las paso a la API de GitHub para agregar comentarios como parte de la revisión.
Elegí la siguiente forma de agregar comentarios porque al crear una nueva reseña, puedes agregar todos los comentarios de una sola vez en lugar de agregar un solo comentario a la vez. Agregar comentarios uno por uno también puede activar la limitación de velocidad porque agregar comentarios activa las notificaciones y no quieres enviarles spam a los usuarios con notificaciones.
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; } }
Quería mantener la acción de GitHub abierta y abierta a integraciones y es por eso que puedes usar cualquier modelo de tu elección (consulta la lista de modelos compatibles ) , o puedes ajustar y crear tu propio modelo personalizado sobre los modelos base compatibles y usarlo con esta acción de GitHub.
Si encuentra algún problema con el token o limitación de velocidad , es posible que desee actualizar los límites de su modelo consultando la documentación de la plataforma respectiva.
Entonces, ¿qué estás esperando? Si tienes un repositorio en GitHub, prueba la acción ahora: está en el mercado de acciones de GitHub .