Обзоры кода всегда были важны для поддержания высоких стандартов и закрепления лучших практик в проекте кодирования. Это не пост о том, как разработчикам следует просматривать код, а скорее о делегировании его части ИИ. Как упоминает Майкл Линч в своем посте , мы должны обзора кода. В то время как Майкл подчеркивает инструмент форматирования, я хотел бы пойти дальше и позволить искусственному интеллекту разобраться с этим. Я имею в виду, почему бы не воспользоваться бумом ИИ в отрасли? «Как делать обзоры кода, как человек» позволить компьютерам заботиться о скучных частях Я не говорю, что ИИ следует использовать вместо инструментов форматирования и линтеров. Вместо этого его следует использовать поверх всего этого, чтобы отлавливать тривиальные вещи, которые может пропустить человек. Вот почему я решил создать , которое просматривает код diff pull request и генерирует предложения с помощью ИИ. Позвольте мне провести вас через это. действие github 🚨 ПРИМЕЧАНИЕ: Это действие GitHub теперь доступно на . торговой площадке GitHub Это действие JavaScript — узнайте больше о . создании действий JavaScript на GitHub Получение разницы Для взаимодействия с API github я использовал , который представляет собой своего рода SDK или идиоматическим способом. octokit клиентскую библиотеку для взаимодействия с 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