- PVSM.RU - https://www.pvsm.ru -

В большинстве случаев, когда кто-то утверждает, что «GPT написал X», человек выступает для LLM в роли своеобразного REPL (Read-Eval-Print Loop, цикл чтение-оценка-вывод), внимательно подводя модель к функциональному результату. Я нисколько не хочу принизить ценность этого процесса – очень здорово, что он работает. Но можем ли мы шагнуть дальше? Можем ли использовать LLM для генерации ВСЕГО кода сложной программы за раз без человеческого вмешательства?
Чтобы проверить способность GPT-4 генерировать сложные программы, я попросил этот инструмент создать расширение VSCode, позволяющее пользователю корректировать уровень заголовка выбранного текста Markdown. Эта задача требует:
В этом эксперименте для всех задач генерации я использовал GPT-4. Эту модель я нахожу наиболее эффективной среди всех современных аналогов.
Вдобавок к этому — я использовал для генерации кода фреймворк smol-ai [1].
Описание smol-ai из его README:
Это прототип агента «джуниор-разработчика» (он же
smol dev), который создаёт структуру всей базы кода за вас на основании предоставленной спецификации продукта, но не захватывает мир и не обещает фантастических возможностей AGI (Artificial General Intelligence, общий искусственный интеллект). Вместо создания специализированных, косных, одноразовых стартовых систем в стиле создать приложение react или создать приложение nextjs, этот инструмент, по сути, позволяет создать любое приложение, указания для скаффолдинга которого вы прописываете в тесном взаимодействии с вашимsmol dev.
Я ценю smol-ai за его простоту. Вся логика генерации кода находится в одном файле Python, состоящем из трёх основных функций:
Имейте в виду, что smol-ai также имеет и другие возможности вроде отладки ошибок и генерации инструкций на основе текущей базы кода. В этой статье мы их затрагивать не будем.
Я начал с простой инструкции, описывающей мою задачу:
python main_no_modal.py "a vscode extension that lets the user adjust the heading level of the selected text. it should have three commands: increase heading level, decrease heading level, and set heading level"
Перевод:
python main_no_modal.py «расширение vscode, позволяющее пользователю корректировать уровень заголовка выбранного текста. Оно должно поддерживать три команды: повышение уровня заголовка, понижение уровня заголовка и установка уровня заголовка».
Обратите внимание, что я задал короткую и обобщённую инструкцию. Мне хотелось посмотреть, насколько хорошо GPT справится без получения дополнительных деталей или предметной информации, отсутствующих в его обучающем наборе.
Спустя несколько минут генерации кода, smol-ai создал следующие файлы:
.
├── extension
│ ├── package.json
│ ├── src
│ │ ├── commands
│ │ │ ├── decreaseHeadingLevel.ts
│ │ │ ├── increaseHeadingLevel.ts
│ │ │ └── setHeadingLevel.ts
│ │ └── extension.ts
└── shared_dependencies.md
Заметьте, что shared_dependencies.md создан в виде догадки о том, какие общие зависимости будут присутствовать. В нашем случае ИИ создал файл с таким содержимым:
the app is: a vscode extension that lets the user adjust the heading level of the selected text. it should have three commands: increase heading level, decrease heading level, and set heading level
the files we have decided to generate are: tsconfig.json, package.json, utils.ts, extension.ts
Shared dependencies:
1. Exported variables:
- adjustHeadingLevel (from utils.ts)
2. Data schemas:
- None
3. ID names of DOM elements:
- None
4. Message names:
- None
5. Function names:
- increaseHeadingLevel
- decreaseHeadingLevel
- setHeadingLevel
6. Dependencies:
- None
Файл extension.ts является точкой входа расширения VSCode. В нём мы регистрируем три команды, которые указали в инструкции.
import * as vscode from 'vscode';
import { increaseHeadingLevel } from './commands/increaseHeadingLevel';
import { decreaseHeadingLevel } from './commands/decreaseHeadingLevel';
import { setHeadingLevel } from './commands/setHeadingLevel';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('extension.increaseHeadingLevel', increaseHeadingLevel),
vscode.commands.registerCommand('extension.decreaseHeadingLevel', decreaseHeadingLevel),
vscode.commands.registerCommand('extension.setHeadingLevel', setHeadingLevel)
);
}
export function deactivate() {}
Ниже я приведу логику, содержащуюся в decreaseHeadingLevel.ts:
import * as vscode from 'vscode';
export async function decreaseHeadingLevel() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const selection = editor.selection;
const selectedText = document.getText(selection);
const headingRegex = /^(#+)s(.*)$/;
const match = selectedText.match(headingRegex);
if (!match) {
vscode.window.showErrorMessage('Invalid selection. Please select a heading to decrease its level.');
return;
}
const currentLevel = match[1].length;
if (currentLevel === 1) {
vscode.window.showErrorMessage('Cannot decrease heading level. Already at the lowest level.');
return;
}
const newLevel = currentLevel - 1;
const newHeading = '#'.repeat(newLevel) + ' ' + match[2];
await editor.edit((editBuilder) => {
editBuilder.replace(selection, newHeading);
});
}
Код проверяет наличие активной программы-редактора, получает выбранный текст и ищет заголовок Markdown, используя регулярное выражение. Если заголовок найден, и его текущий уровень не является наименьшим, он этот уровень понижает.
На первый взгляд, никаких проблем в этой логике нет. Код выполняет команду и проверяет возможные пограничные случаи. Он даже предоставляет полезные сообщения об ошибках, чем уже опережает большинство программ, создаваемых людьми…
Для проверки расширения нам нужно успешно проделать следующие шаги:
При попытке установить зависимости мы сталкиваемся с первой проблемой:
$ yarn
Couldn't find any versions for "vscode-test" that matches "^1.6.2"
? Please choose a version of "vscode-test" from this list: (Use arrow keys)
❯ 1.6.1
Инспектирование package.json выдаёт следующее:
{
"name": "adjust-heading-level",
...
"engines": {
"vscode": "^1.62.0"
},
"devDependencies": {
"@types/node": "^14.17.0",
"@types/vscode": "^1.62.0",
"typescript": "^4.4.2",
"vscode": "^1.1.37",
"vscode-test": "^1.6.2"
},
}
Движок VSCode определяет минимальную версию редактора. На сегодня (23.05.2023) это версия 1.78. Версия 1.62.0 была выпущена 21 октября 2021 года [2].
Это согласуется с крайним сроком имеющихся у GPT-4 знаний [3]:
GPT-4, как правило, ничего не знает о событиях, произошедших после сентября 2021, так как этой датой ограничивается актуальность большей части известной ему информации.
Версия vscode-test 1.6.2 выглядит подозрительно похожей на 1.62, и это говорит о том, что GPT, скорее всего, эти числа выдумал.
Как бы то ни было, это легко исправить, указав верный номер версии и повторив установку:
- "vscode-test": "^1.6.2"
+ "vscode-test": "^1.6.1"
Повторная установка завершилась успешно.
$ yarn
...
[3/5] 🚚 Fetching packages...
[4/5] 🔗 Linking dependencies...
[5/5] 🔨 Building fresh packages...
✨ Done in 4.31s.
Поскольку TypeScript является компилируемым языком, нам потребуется пройти через этап сборки для компиляции кода в JS. Файл package.json содержит следующие скрипты:
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
Сборку кода можно выполнить скриптом compile. И здесь мы сталкиваемся со второй проблемой:
$ yarn compile
warning package.json: No license field
warning adjust-heading-level@0.1.0: The engine "vscode" appears to be invalid.
$ tsc -p ./
error TS5057: Cannot find a tsconfig.json file at the specified directory: './'.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
TypeScript для компиляции в JS необходим файл конфигурации tsconfig.json. Если заглянуть в нашу изначальную структуру файлов, то среди них его нет.
.
├── extension
│ ├── package.json
│ ├── src
│ │ ├── commands
│ │ │ ├── decreaseHeadingLevel.ts
│ │ │ ├── increaseHeadingLevel.ts
│ │ │ └── setHeadingLevel.ts
│ │ └── extension.ts
└── shared_dependencies.md
Это можно исправить, добавив необходимую конфигурацию и повторив сборку. Но теперь у нас возникают другие проблемы:
$ tsc --init
$ yarn compile
src/commands/decreaseHeadingLevel.ts:1:25 - error TS2307: Cannot find module 'vscode' or its corresponding type declarations.
1 import * as vscode from 'vscode';
~~~~~~~~
src/commands/decreaseHeadingLevel.ts:30:24 - error TS7006: Parameter 'editBuilder' implicitly has an 'any' type.
30 await editor.edit((editBuilder) => {
~~~~~~~~~~~
src/commands/increaseHeadingLevel.ts:1:25 - error TS2307: Cannot find module 'vscode' or its corresponding type declarations.
...
Found 7 errors
Typescript не может найти модуль vscode из-за синтаксиса, который мы используем для инструкций импорта:
// не сработает
import * as vscode from 'vscode';
// сработает
import vscode from 'vscode';
Разница в синтаксисе обуславливается отличиями [4] между модулями CommonJs и ES, а также между тем, как они экспортируют зависимости и тем, как эти зависимости транспилирует TS. О подобных раздражающих причудах совместимости модулей можно написать целую отдельную статью. Что же касается нашей проблемы, то её можно исправить отключением esModuleInterop в tsconfig.json:
@@ -71,7 +71,7 @@
- "esModuleInterop": true, /* Генерирует дополнительный JS-кода для лучшей поддержки импорта модулей CommonJS. Это активирует 'allowSyntheticDefaultImports' для совместимости типов. */
+ "esModuleInterop": false, /* Генерирует дополнительный JS-код для лучшей поддержки импорта модулей CommonJS. Это активирует 'allowSyntheticDefaultImports' для совместимости типов. */
Заметьте, что esModuleInterop начиная с версии TypeScript 4.4, стал по умолчанию true. Эта версия вышла 15 марта 2022 года – то есть о ней GPT не знает.
Попробуем повторить сборку. На этот раз нас ждёт всего одна новая ошибка:
$ yarn compile
src/commands/setHeadingLevel.ts:2:10 - error TS2305: Module '"../extension"' has no exported member 'adjustHeadingLevel'.
2 import { adjustHeadingLevel } from '../extension';
Found 1 error.
Эта ошибка возникает в результате попытки импортировать несуществующую функцию. В частности, она вызвана следующей логикой в setHeadingLevel.ts:
import * as vscode from 'vscode';
import { adjustHeadingLevel } from '../extension';
export async function setHeadingLevel() {
...
}
При объявлении зависимостей GPT склонен ко лжи излишнему оптимизму. Иногда он вызывает или импортирует функции, которых не существует. Перед нами один из таких случаев.
Исправить это можно, удалив лишнюю зависимость и добавив вручную логику в setHeadingLevel:
@@ -1,5 +1,4 @@
import * as vscode from 'vscode';
-import { adjustHeadingLevel } from '../extension';
export async function setHeadingLevel() {
const editor = vscode.window.activeTextEditor;
@@ -14,6 +13,12 @@ export async function setHeadingLevel() {
vscode.window.showErrorMessage('invalidSelection');
return;
}
+ const headingRegex = /^(#+)s(.*)$/;
+ const match = selectedText.match(headingRegex);
+ if (!match) {
+ vscode.window.showErrorMessage('Invalid selection.');
+ return;
+ }
const inputOptions: vscode.InputBoxOptions = {
prompt: 'setHeadingLevelPrompt',
@@ -31,6 +36,16 @@ export async function setHeadingLevel() {
if (headingLevel) {
const newHeadingLevel = parseInt(headingLevel);
- adjustHeadingLevel(editor, selection, selectedText, newHeadingLevel);
+
+ const newHeading = '#'.repeat(newHeadingLevel) + ' ' + match[2];
+
+ await editor.edit((editBuilder) => {
+ editBuilder.replace(selection, newHeading);
+ });
}
}
Заметьте, что бо́льшая часть кода была взята из decreaseHeadingLevel.ts.
Снова сборка. На этот раз успешная.
$ tsc -p ./
✨ Done in 0.80s.
Но запустится ли итоговая программа?
Обратите внимание, что GPT не предоставляет инструкций ни о запуске расширения, ни о его установке или сборке. Это довольно легко сделать, если вы уже собирали расширения VSCode ранее, но у новичков тут могут возникнуть сложности.
Для запуска расширения необходимо перейти в панель Run and Debug и выполнить задачу vscode extension при открытом файле extension.ts в редакторе.

Так, вы вы запустите новое окно VSCode с установленным расширением. Здесь также возникла ошибка, как только я попробовал выполнить команду.
Command 'Increase Heading Level' resulted in an error command 'adjust-heading-level. 'increaseHeadingLevel' was not found
VSCode знает о командах, которые объявлены внутри package.json.
В нашем package.json указаны следующие:
"activationEvents": [
"onCommand:adjust-heading-level.increaseHeadingLevel",
"onCommand:adjust-heading-level.decreaseHeadingLevel",
"onCommand:adjust-heading-level.setHeadingLevel"
],
...
"contributes": {
"commands": [
{
"command": "adjust-heading-level.increaseHeadingLevel",
"title": "Increase Heading Level"
},
{
"command": "adjust-heading-level.decreaseHeadingLevel",
"title": "Decrease Heading Level"
},
{
"command": "adjust-heading-level.setHeadingLevel",
"title": "Set Heading Level"
}
]
}
После объявления в package.json эти команды также необходимо зарегистрировать в расширении.
Вот наш extension.ts:
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('extension.increaseHeadingLevel', increaseHeadingLevel),
vscode.commands.registerCommand('extension.decreaseHeadingLevel', decreaseHeadingLevel),
vscode.commands.registerCommand('extension.setHeadingLevel', setHeadingLevel)
);
}
Видите, в чём проблема?
В этом файле TS команды объявлены как extension.{COMMAND}, а в package.json – как adjust-heading-level.{COMMAND}.
Исправить это можно, скорректировав package.json в соответствии с кодом. И хотя само это исправление довольно простое, для правильного определения проблемы необходимо знать, где конкретно смотреть.
@@ -1,5 +1,5 @@
{
"displayName": "Adjust Heading Level",
"description": "A VSCode extension that lets the user adjust the heading level of the selected text.",
"version": "0.1.0",
@@ -10,23 +10,20 @@
"Other"
],
"activationEvents": [
- "onCommand:adjust-heading-level.increaseHeadingLevel",
- "onCommand:adjust-heading-level.decreaseHeadingLevel",
- "onCommand:adjust-heading-level.setHeadingLevel"
],
"main": "./src/extension.js",
"contributes": {
"commands": [
{
- "command": "adjust-heading-level.increaseHeadingLevel",
+ "command": "extension.increaseHeadingLevel",
"title": "Increase Heading Level"
},
{
- "command": "adjust-heading-level.decreaseHeadingLevel",
+ "command": "extension.decreaseHeadingLevel",
"title": "Decrease Heading Level"
},
{
- "command": "adjust-heading-level.setHeadingLevel",
+ "command": "extension.setHeadingLevel",
"title": "Set Heading Level"
}
]
Примечание: я также воспользовался возникшей возможностью и удалил
activationEvents– эти события определяют, когда активируется расширение VSCode. В случае активации командами VSCode теперь сможет обнаруживать соответствующие события автоматически, а значит, объявлять их вручную больше не нужно.
Давайте попробуем повторить запуск и повысить уровень заголовка.

Хмм, неожиданный результат.
Вместо повышения — уровень заголовка у нас понижается.
Давайте заглянем в increaseHeadingLevel.ts:
import * as vscode from 'vscode';
export async function increaseHeadingLevel() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const selection = editor.selection;
const selectedText = document.getText(selection);
const headingRegex = /^(#+)s(.*)$/;
const match = selectedText.match(headingRegex);
if (!match) {
vscode.window.showErrorMessage('Invalid selection. Please select a valid heading.');
return;
}
const currentLevel = match[1].length;
const newLevel = Math.max(1, currentLevel - 1);
const newText = '#'.repeat(newLevel) + ' ' + match[2];
await editor.edit((editBuilder) => {
editBuilder.replace(selection, newText);
});
}
Видите проблему?
Это баг, вызванный отличием в один символ.
@@ -19,7 +19,7 @@ export async function increaseHeadingLevel() {
}
const currentLevel = match[1].length;
- const newLevel = Math.max(1, currentLevel - 1);
+ const newLevel = Math.max(1, currentLevel + 1);
const newText = '#'.repeat(newLevel) + ' ' + match[2];
Скомпилируем расширение и заново его запустим.

Работает.
И как мы в итоге справились? У нас получилось рабочее расширение, и мы добились от него поставленной изначально цели.
Путь к этому результату не был «автоматическим», и на нём мы столкнулись со множеством проблем. Не обладая достаточным уровнем знаний TS, Node.js и VSCode, мы бы решали их намного дольше.
И хотя в итоге мы смогли сгенерировать рабочий код, в нём есть ряд недоработок:
.gitignore для артефактов typescript/javascript/vscode;launch.json, конфигурирующий запуск разрабатываемого расширения;GPT генерирует 9 файлов, включающих ~100 строк TS, ~180 строк json и 17 строк Markdown.
$ cloc --exclude-dir=node_modules,out --not-match-f=package-lock.json --not-match-f=prompt.md --include-ext=ts,json,md .
15 text files.
13 unique files.
7 files ignored.
github.com/AlDanial/cloc v 1.92 T=0.01 s (986.5 files/s, 36610.4 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
JSON 4 8 0 181
TypeScript 4 22 0 98
Markdown 1 8 0 17
-------------------------------------------------------------------------------
SUM: 9 38 0 296
-------------------------------------------------------------------------------
Итоговая структура программы:
$ tree --gitfile extension/.gitignore
.
├── extension
│ ├── package.json
│ ├── src
│ │ ├── commands
│ │ │ ├── decreaseHeadingLevel.ts
│ │ │ ├── increaseHeadingLevel.ts
│ │ │ └── setHeadingLevel.ts
│ │ └── extension.ts
│ ├── tsconfig.json
│ └── yarn.lock
├── prompt.md
└── shared_dependencies.md
Из ~300 сгенерированных строк нам, чтобы всё заработало, пришлось изменить/добавить ~18.
GPT смог сгенерировать бо́льшую часть кода, ориентируясь на простую инструкцию без какого-либо контекста предметной области.
Что хочется отметить:

Черепахи…
В статье я описал первый эксперимент с простой обобщённой инструкцией без дополнительного контекста, так что здесь можно многое улучшить. Вот некоторые дальнейшие шаги:
Примечание: я уже проделал некоторые из этих шагов и смог свести число ошибок до нуля при первой же генерации. Нужно ещё посмотреть, будет ли мой подход обобщаться на другие примеры.
Автор: Дмитрий Брайт
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/385329
Ссылки в тексте:
[1] smol-ai: https://github.com/smol-ai/developer
[2] была выпущена 21 октября 2021 года: https://code.visualstudio.com/updates/v1_62
[3] крайним сроком имеющихся у GPT-4 знаний: https://openai.com/research/gpt-4
[4] отличиями: https://blog.logrocket.com/commonjs-vs-es-modules-node-js/
[5] Источник: https://habr.com/ru/companies/ruvds/articles/740716/?utm_source=habrahabr&utm_medium=rss&utm_campaign=740716
Нажмите здесь для печати.