Обзоры кода всегда были важны для поддержания высоких стандартов и закрепления лучших практик в проекте кодирования. Это не пост о том, как разработчикам следует просматривать код, а скорее о делегировании его части ИИ.
Как упоминает Майкл Линч в своем посте «Как делать обзоры кода, как человек» , мы должны позволить компьютерам заботиться о скучных частях обзора кода. В то время как Майкл подчеркивает инструмент форматирования, я хотел бы пойти дальше и позволить искусственному интеллекту разобраться с этим. Я имею в виду, почему бы не воспользоваться бумом ИИ в отрасли?
Я не говорю, что ИИ следует использовать вместо инструментов форматирования и линтеров. Вместо этого его следует использовать поверх всего этого, чтобы отлавливать тривиальные вещи, которые может пропустить человек. Вот почему я решил создать действие github , которое просматривает код diff pull request и генерирует предложения с помощью ИИ. Позвольте мне провести вас через это.
🚨 ПРИМЕЧАНИЕ:
- Это действие GitHub теперь доступно на торговой площадке GitHub .
- Это действие JavaScript — узнайте больше о создании действий JavaScript на GitHub .
Для взаимодействия с API github я использовал octokit
, который представляет собой своего рода SDK или клиентскую библиотеку для взаимодействия с API github идиоматическим способом.
Чтобы получить разницу в запросе на извлечение, вам необходимо передать заголовок Accept
со значением application/vnd.github.diff
вместе с требуемыми параметрами.
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, }, }); }
Если вы вообще не знакомы с действиями GitHub, вот серия «101 действие GitHub» от Виктории Ло , и это хорошее начало.
Получив разницу, я анализирую ее и удаляю нежелательные изменения, а затем возвращаю ее в виде схемы, показанной ниже:
/** 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(), })
Игнорирование файлов довольно простое. Список пользовательского ввода требует строку шаблонов glob, разделенную точкой с запятой. Затем он анализируется, объединяется со списком игнорируемых файлов по умолчанию и дедуплицируется.
**/*.md; **/*.env; **/*.lock; const filesToIgnoreList = [ ...new Set( filesToIgnore .split(";") .map(file => file.trim()) .filter(file => file !== "") .concat(FILES_IGNORED_BY_DEFAULT) ), ];
Список игнорируемых файлов затем используется для удаления изменений diff, которые ссылаются на эти игнорируемые файлы. Это дает вам сырую полезную нагрузку, содержащую только нужные вам изменения.
Как только я получаю сырую полезную нагрузку после разбора diff, я передаю ее в API платформы. Вот реализация 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; }
Вы могли заметить использование формата ответа в реализации API. Это функция, предоставляемая многими платформами LLM, которая позволяет вам указать модели сгенерировать ответ в определенной схеме/формате. Это особенно полезно в данном случае, поскольку я не хочу, чтобы модель галлюцинировала и генерировала предложения для неправильных файлов или позиций в запросе на извлечение или добавляла новые свойства в полезную нагрузку ответа.
Системное приглашение нужно, чтобы дать модели больше контекста о том, как она должна выполнять проверку кода и что следует иметь в виду. Вы можете просмотреть системное приглашение здесь github.com/murtuzaalisurti/better . Пользовательское приглашение содержит фактическое различие, правила и контекст запроса на извлечение. Это то, что запускает проверку кода.
Это действие github поддерживает как OpenAI, так и Anthropic модели. Вот как оно реализует 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; }
Наконец, после получения предложений я очищаю их и передаю в API GitHub для добавления комментариев как части обзора.
Я выбрал следующий способ добавления комментариев, потому что, создавая новый отзыв, вы можете добавить все комментарии за один раз, а не добавлять по одному комментарию за раз. Добавление комментариев по одному может также вызвать ограничение скорости, потому что добавление комментариев вызывает уведомления, а вы не хотите спамить пользователей уведомлениями.
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 открытым для интеграции, и именно поэтому вы можете использовать любую модель по своему выбору (см. список поддерживаемых моделей ) или же вы можете настроить и создать свою собственную пользовательскую модель поверх поддерживаемых базовых моделей и использовать ее с этим действием GitHub.
Если у вас возникли проблемы с токенами или ограничения по скорости , возможно, вам следует обновить лимиты вашей модели, обратившись к документации соответствующей платформы.
Так чего же вы ждете? Если у вас есть репозиторий на github, попробуйте действие прямо сейчас — оно есть на github action marketplace .