Software Engineer, Building syntackle.com
This story contains AI-generated text. The author has used AI either for research, to generate outlines, or write the text itself.
This post provides insights into new product.
Обзоры кода всегда были важны для поддержания высоких стандартов и закрепления лучших практик в проекте кодирования. Это не пост о том, как разработчикам следует просматривать код, а скорее о делегировании его части ИИ.
Как упоминает Майкл Линч в своем посте «Как делать обзоры кода, как человек» , мы должны позволить компьютерам заботиться о скучных частях обзора кода. В то время как Майкл подчеркивает инструмент форматирования, я хотел бы пойти дальше и позволить искусственному интеллекту разобраться с этим. Я имею в виду, почему бы не воспользоваться бумом ИИ в отрасли?
Я не говорю, что ИИ следует использовать вместо инструментов форматирования и линтеров. Вместо этого его следует использовать поверх всего этого, чтобы отлавливать тривиальные вещи, которые может пропустить человек. Вот почему я решил создать действие 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 .