Когда-то, использовав ныне устаревший Codex API от OpenAI, я написал плагин VSCode. Им неплохо выполнялись простые инструкции в выбранном коде в VSCode, например добавление к выделенной функции строки документации.
Я плотно занимаюсь локальными большими языковыми моделями, поэтому естественным продолжением было создание чего-то подобного независимо от OpenAI.
С этой целью я запустил проект Oasis. Идея очень простая: генерировать строки документации для кода, напрямую вызывая API text-generation-webui
.
Вскоре получил результаты, аналогичные достигнутым старым плагином — теперь с WizardLM вместо OpenAI. Но лицензия здесь ограничена: модель предназначена только для исследований.
Мне же она нужна для коммерческих целей или повседневной работы, и без проблем с лицензией.
Когда-то я написал о небольшом экспериментальнном проекте для генерации кода, созданном с применением модели Salesforce Codegen на процессоре. Тогда я не подключил ее к VSCode главным образом потому, что это модель автозаполнения, и она не для следования инструкциям.
Сейчас же, когда больше пишу подсказки и познакомился с библиотекой Guidance от Microsoft, я решил создать жизнеспособный продукт, объединив:
- Salesforce Codegen для генерации кода.
- Библиотеку Guidance для генерирования моделью того, что действительно нужно, а не случайного ввода.
За несколько дней я сделал из них новую версию плагина и вполне доволен первыми результатами. Надо признать: решение получилось намного сложнее ожидаемого, но вполне рабочее. А главное — доступна даже версия модели с 350 млн параметров, которой при этом корректно генерируются строки документации.
Конечно, она не всегда применима, и модели покрупнее обычно все-таки намного лучше в работе, зато у нее отмечается огромный рост производительности вывода и доступности — версия модели с 350 млн параметров развертывается практически у всех.
Текущая версия этого плагина доступна на GitHub.
Рассмотрим, как она создавалась:
- Написание расширения VSCode.
- Настройка HTTP-сервера с помощью библиотеки Guidance.
- Настройка сервера подсказок промежуточного ПО для синтаксического анализа кода и динамического выбора подсказок.
- Подробно о том, как сделать эффективной работу команды
add_docstring
с менее мощными моделями, написать подсказку-руководство и убедиться, что во вводе/выводе нет неожиданностей. Этот пункт подразделяется дальше на:
a) синтаксический анализ входных данных;
б) подсказку-руководство Add Docstring по добавлению строки документации;
в) создание выходных данных.
Примечание: формат строки документации довольно прост, никаких препятствий для доработки сгенерированного формата нет.
Вот высокоуровневая схема того, что мы создаем:
Написание расширения VSCode. Основы
Сначала берем из официальной документации hello world образец расширения extension.ts
, добавляем основную логику: определяем, как инициализировать расширение; после инициализации регистрируем имеющиеся команды. Вот код:
export function activate(context: vscode.ExtensionContext) {
const commands = [
["addDocstring", "add_docstring"],
["addTypeHints", "add_type_hints"],
["addUnitTest", "add_unit_test"],
["fixSyntaxError", "fix_syntax_error"],
["customPrompt", "custom_prompt"],
];
commands.forEach(tuple_ => {
const [commandName, oasisCommand] = tuple_;
const command = vscode.commands.registerCommand(`oasis.${commandName}`, () => {
useoasis(oasisCommand);
});
context.subscriptions.push(command);
});
}
Здесь каждая ожидаемая команда привязывается к вызову функции основной логики useoasis
. Перейдем к этой функции позже.
Эти команды должны соответствовать тому, что определено в package.json
в проекте. Определим блоки для объявления команд из расширения, начнем с contributes.commands
:
[
{
"command": "oasis.addDocstring",
"title": "Add Docstring to Selection"
},
{
"command": "oasis.addTypeHints",
"title": "Add type hints to selection"
},
{
"command": "oasis.fixSyntaxError",
"title": "Fix syntax error"
},
{
"command": "oasis.customPrompt",
"title": "Custom Prompt"
}
]
Чтобы пользователь выбирал блок кода мышью, находил эту команду правой кнопкой, применяем конфигурацию contributes.menus.editor/context
:
"editor/context": [
{
"command": "oasis.addDocstring",
"when": "editorHasSelection",
"group": "7_modification"
},
{
"command": "oasis.addTypeHints",
"when": "editorHasSelection",
"group": "7_modification"
},
{
"command": "oasis.fixSyntaxError",
"when": "editorHasSelection",
"group": "7_modification"
},
{
"command": "oasis.customPrompt",
"when": "editorHasSelection",
"group": "7_modification"
}
]
Для работы с командами привязки этого достаточно. Вернемся к основной функции расширения:
async function useoasis(command: string) {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
return;
}
console.log("Reading config")
const oasisUrl = vscode.workspace.getConfiguration('oasis').get("prompt_server_url")
console.log("Oasis URL:", oasisUrl);
const document = activeEditor.document;
const selection = activeEditor.selection;
const text = document.getText(selection);
const requestBody = JSON.stringify({
data: text
});
const url = `${oasisUrl}/${command}`;
console.log("Calling API", url, "with body: ", requestBody);
let response: oasisResponse | undefined = undefined;
try {
response = await got(url, {
method: "POST",
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"Content-Type": "application/json",
// eslint-disable-next-line @typescript-eslint/naming-convention
},
body: requestBody,
timeout: {
request: 300000 // макс. пять минут
}
}).json();
} catch (e: any) {
vscode.window.showErrorMessage("Oasis Plugin: error calling the API")
try {
const apiStatusCode = `Error calling API: ${e.response.statusCode}`;
vscode.window.showErrorMessage(apiStatusCode);
} catch (error) {
console.error("Error parsing error response code", error);
}
}
if (response) {
console.log("From got", response.text);
const editedText = response.text;
activeEditor.edit(editBuilder => {
console.log("Edit builder", editBuilder);
editBuilder.replace(selection, editedText);
});
}
};
Чуть длиннее, но тоже просто. Сначала, чтобы избежать ссылок на строки со значением null
, проверяем наличие активного редактора. Вот простой, слегка переформатированный прием:
const oasisUrl = vscode.workspace
.getConfiguration('oasis')
.get("prompt_server_url")
Здесь для определения, где обслуживается сервер запросов, считывается конфигурация рабочей области. Поэтому добавляем в конфигурацию расширения package.json
следующее:
"contributes": {
"configuration": {
"title": "Oasis",
"properties": {
"oasis.prompt_server_url": {
"type": "string",
"default": "http://0.0.0.0:9000",
"description": "The URL where the extension can find the prompt server. Defaults to: 'http://0.0.0.0:9000'"
}
}
},
Этим не только определяется значение по умолчанию, но и предоставляется конфигурация для переопределения его пользователем через пользовательский интерфейс или редактор JSON. Вот пример скриншота пользовательского интерфейса:
Вернемся к коду расширения:
const text = document.getText(selection);
const requestBody = JSON.stringify({
data: text
});
const url = `${oasisUrl}/${command}`;
console.log("Calling API", url, "with body: ", requestBody);
Довольно просто: считываем выделенный текст в редакторе, затем готовим запрос. command
— это параметр встроенного в функцию activate
замыкания.
Используя библиотеку got
, делаем запрос:
let response: oasisResponse | undefined = undefined;
try {
response = await got(url, {
method: "POST",
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"Content-Type": "application/json",
// eslint-disable-next-line @typescript-eslint/naming-convention
},
body: requestBody,
timeout: {
request: 300000 // макс. пять минут
}
}).json();
} catch (e: any) {
vscode.window.showErrorMessage("Oasis Plugin: error calling the API")
try {
const apiStatusCode = `Error calling API: ${e.response.statusCode}`;
vscode.window.showErrorMessage(apiStatusCode);
} catch (error) {
console.error("Error parsing error response code", error);
}
}
Если запрос не выполняется, отображаем в пользовательском интерфейсе ошибку:
Не очень информативно, но пользователь хотя бы знает: больше ждать не стоит. Затем, если все хорошо, выполняем синтаксический анализ и с помощью активного редактора заменяем выделенное на ответ из API:
if (response) {
console.log("From got", response.text);
const editedText = response.text;
activeEditor.edit(editBuilder => {
console.log("Edit builder", editBuilder);
editBuilder.replace(selection, editedText);
});
}
Библиотека Guidance для настройки HTTP-сервера
Guidance — очень интересная библиотека от Microsoft для работы с большими языковыми моделями. Ознакомьтесь с ее Readme.
Главное преимущество Guidance — возможность выполнения конкретных задач с моделями, гораздо менее эффективными в следовании инструкциям.
Недостаток — значительное увеличение затрат на разработку, ведь в модели на стандартном языке программирования применяется много логических этапов.
При разработке с Guidance перезагрузка сервера затруднительна, особенно если модель тяжелая. Поэтому загрузку модели и библиотеку перенесем на сервер, который не нужно часто менять.
Исходный код этого сервера прост, умещается в одном файле:
class Request(BaseModel):
input_vars: Dict[str, str]
output_vars: List[str]
guidance_kwargs: Dict[str, str]
prompt_template: str
app = FastAPI()
print("Loading model, this may take a while...")
# model = "TheBloke/wizardLM-7B-HF"
model = "Salesforce/codegen-350m-mono"
# model = "Salesforce/codegen-2b-mono"
# model = "Salesforce/codegen-6b-mono"
# model = "Salesforce/codegen-16B-mono"
llama = guidance.llms.Transformers(model, quantization_config=nf4_config, revision="main")
print("Server loaded!")
@app.post("/")
def call_llama(request: Request):
input_vars = request.input_vars
kwargs = request.guidance_kwargs
output_vars = request.output_vars
guidance_program: Program = guidance(request.prompt_template)
program_result = guidance_program(
**kwargs,
stream=False,
async_mode=False,
caching=False,
**input_vars,
llm=llama,
)
output = {}
for output_var in output_vars:
output[output_var] = program_result[output_var]
return output
Вот и все. Принимается шаблон подсказки с входными и ожидаемыми выходными переменными из HTTP-запроса и направляется через библиотеку Guidance, затем извлекаются ожидаемые выходные переменные и возвращаются в HTTP-ответе.
Что касается quantization_config
, здесь применяется новейший 4-битный метод квантования Hugging Face.
Для задействования этого сервера также понадобится клиент:
import requests
guidance_url = "http://0.0.0.0:9090"
def call_guidance(prompt_template, output_vars, input_vars=None, guidance_kwargs=None):
if input_vars is None:
input_vars = {}
if guidance_kwargs is None:
guidance_kwargs = {}
data = {
"prompt_template": prompt_template,
"output_vars": output_vars,
"guidance_kwargs": guidance_kwargs,
"input_vars": input_vars,
}
response = requests.post(
guidance_url,
json=data
)
response.raise_for_status()
return response.json()
Здесь ничего интересного, сервер не взаимодействует напрямую с расширением VSCode.
Настройка сервера подсказок промежуточного ПО для синтаксического анализа кода и динамического выбора подсказок
Переходим к промежуточному ПО, которым получается запрос от VSCode, основному серверному коду:
from fastapi import FastAPI, HTTPException
import logging
import codegen_guidance_prompts
import wizard_lm_guidance_prompts
from pydantic import BaseModel
from guidance_client import call_guidance
from commands.commands import build_command_mapping
logger = logging.getLogger("uvicorn")
logger.setLevel(logging.DEBUG)
class Request(BaseModel):
data: str
app = FastAPI()
prompts_module = codegen_guidance_prompts
commands_mapping = build_command_mapping(prompts_module)
@app.post("/{command}/")
def read_root(command, request: Request):
logger.info("Received command: '%s'", command)
logger.debug("Received data: '%s'", request)
received_code = request.data
try:
command_to_apply = commands_mapping[command]
logger.info("Loaded command: '%s'", command_to_apply)
except KeyError:
raise HTTPException(status_code=404, detail=f"Command not supported: {command}")
prompt_key, prompt_to_apply, extracted_input = command_to_apply.prompt_picker(received_code)
logger.info("Extracted input: %s", extracted_input)
keys_difference = set(prompt_to_apply.input_vars) - set(extracted_input.keys())
if keys_difference:
error_msg = f"Missing input keys for the prompt: {keys_difference}"
logger.error(error_msg)
raise HTTPException(status_code=500, detail=error_msg)
logger.info("Loaded command: '%s'", command_to_apply.__class__.__name__)
logger.info("Parsed input :")
for key, item in extracted_input.items():
logger.info("(%s): '%s'", key, item)
logger.info("Calling LLM...")
result = call_guidance(
prompt_template=prompt_to_apply.prompt_template,
input_vars=extracted_input,
output_vars=prompt_to_apply.output_vars,
guidance_kwargs={}
)
logger.info("LLM output: '%s'", result)
result = command_to_apply.output_extractor(prompt_key, extracted_input, result)
logger.info("parsed output: '%s'", result)
return {"text": result}
Несмотря на внешнюю простоту, здесь скрывается много неприятных деталей. Но общая идея контроллера действительно проста:
- Принимается пара входных параметров:
command
, входной код. - Находится подходящая подсказка.
- Выполняется синтаксический анализ входных данных в конкретный формат.
- Запускается через сервер Guidance.
- Выполняется синтаксический анализ выходных данных в правильный формат.
- Отправляется ответ.
Подробнее о команде «add_docstring»
Синтаксический анализ входных данных
С простыми частями разобрались, теперь посложнее: расширим каждый этап.
Сначала выясняем, применение какой команды запрашивается:
logger.info("Received command: '%s'", command)
logger.debug("Received data: '%s'", request)
received_code = request.data
try:
command_to_apply = commands_mapping[command]
logger.info("Loaded command: '%s'", command_to_apply)
Это словарь, созданный с помощью такого вызова:
commands_mapping = build_command_mapping(prompts_module)
Здесь мы определяем доступные команды. Доступна пока только одна:
def build_command_mapping(prompt_module: PromptModuleInterface):
add_docstring_command = DocStringCommand(
prompt={
"generic_prompt": prompt_module.doc_string_guidance_prompt,
"function_prompt": prompt_module.function_doc_string_guidance_prompt
}
)
commands_mapping = {
"add_docstring": add_docstring_command,
# "add_type_hints": ADD_TYPE_HINTS,
# "fix_syntax_error": FIX_SYNTAX_ERROR,
# "custom_prompt": CUSTOM_PROMPT
}
return commands_mapping
Классом DocStringCommand
реализуется абстрактный класс Command
:
@dataclass
class Command:
prompt: Dict[str, GuidancePrompt]
@abc.abstractclassmethod
def prompt_picker(self, input_: str) -> Tuple[str, GuidancePrompt, Dict[str, str]]:
raise NotImplementedError()
@abc.abstractclassmethod
def output_extractor(self, prompt_key: str, extracted_input: Dict[str, str], result: Dict[str, str]) -> str:
raise NotImplementedError()
class DocStringCommand(Command):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
В этом классе содержится словарь подсказок-руководств и реализовывается две функции: для выбора корректной подсказки и извлечения выходных данных. GuidancePrompt
похож на сервер Guidance:
@dataclass
class GuidancePrompt:
prompt_template: str
input_vars: Dict[str, str]
output_vars: Dict[str, str]
guidance_kwargs: Dict[str, str]
Вот как выбирается подсказка:
def prompt_picker(self, input_: str) -> Tuple[str, GuidancePrompt, Dict[str, str]]:
prompt_key = "None"
try:
function_header, function_body, leading_indentation, indentation_type = function_parser(input_)
prompt_key = "function_prompt"
return_value = prompt_key, self.prompt[prompt_key], {
"function_header": function_header,
"function_body": function_body,
"leading_indentation": leading_indentation,
"indentation_type": indentation_type
}
except (FailedToParseFunctionException):
logger.warn("Failed to identify specific type of code block, falling back to generic prompt")
prompt_key = "generic_prompt"
return_value = prompt_key, self.prompt[prompt_key], {"input": input_}
logger.info("Chosen prompt: %s", prompt_key)
return return_value
Здесь имеется две подсказки: generic_prompt
и function_prompt
. Если идентифицировать функцию, применяется вторая, специализированная, в противном случае — обобщенная.
Возвращаемое значение — кортеж в формате (prompt_key, prompt, input_dict)
. Применим эти значения позже при вызове с этой подсказкой большой языковой модели и синтаксическом анализе ее выходных данных.
Возвращаясь чуть назад, при идентификации функции вызываем function_parser
. Обследуем эту кроличью нору:
def function_parser(input_code_str: str) -> Tuple[str, str, str]:
leading_indentation = _get_leading_indentation(input_code_str)
simple_indented_code = _remove_extra_indentation(input_code_str, leading_indentation)
indentation_type = _get_indentation_type(input_code_str)
parsed = ast.parse(simple_indented_code, filename="<string>")
parsed
try:
first_node = parsed.body[0]
except IndexError:
raise FailedToParseFunctionException from IndexError
if not isinstance(first_node, ast.FunctionDef):
raise FailedToParseFunctionException(f"Parsed type is not a function: '{type(first_node)}'")
function_body = _extract_function_body(first_node, leading_indentation, indentation_type)
function_header = _extract_function_header(first_node)
return function_header, function_body, leading_indentation, indentation_type
Основная идея здесь — используя документацию ast.parse
, найти на абстрактном синтаксическом дереве тело/заголовок функции. Но зачем эти странности с отступом?
Если передать в ast.parse
следующую строку:
"""
def hello():
print('hello!')
"""
Возникнет IndentationError
. Похоже, здесь предполагается, что этим синтаксическим анализатором всегда считывается файл. То есть эта строка интерпретируется как исходный код на уровне модуля, поэтому ее отступ неправильный.
Найденные мною решения — в полном исходном коде.
Вернемся чуть назад и, получив от синтаксического анализатора заголовок и тело функции, перейдем к приготовленной подсказке-руководству.
Подсказка-руководство по добавлению строки документации
Сначала продемонстрируем, как выполняется это генерирование, приняв во входных данных одну из функций Oasis:
def _extract_function_header(fun_code: ast.FunctionDef) -> str:
full = ast.unparse(fun_code)
body = ast.unparse(fun_code.body)
return full.split(body[0:USED_BODY_CHARS_TO_SPLIT])[0].strip()
Вот строка документации, сгенерированная Oasis:
def _extract_function_header(fun_code: ast.FunctionDef) -> str:
"""This function extracts the function header from the given function definition
Parameters:
fun_code (ast.FunctionDef): The function definition to extract the function header from.
Returns:
str: The function header.
"""
full = ast.unparse(fun_code)
body = ast.unparse(fun_code.body)
return full.split(body[0:USED_BODY_CHARS_TO_SPLIT])[0].strip()
Как же она генерируется? Подсказка длинновата, разберем ее по блокам. Возьмем примеры «подсказок с несколькими выстрелами»:
function_doc_string_guidance_prompt = GuidancePrompt(
prompt_template="""
def print_hello_world():
print("hello_world")
def print_hello_world():
# Строка документации ниже
\"\"\"This functions print the string 'hello_world' to the standard output.\"\"\"
def sum_2(x, y):
return x + y
def sum_2(x, y):
# Строка документации ниже
\"\"\"This functions receives two parameters and returns the sum.
Parameters:
int: x - first number to sum
int: y - second number to sum
Returns:
int: sum of the two given integers
\"\"\"
return x + y
Отличие от WizardLM: здесь инструкция не подается в модель явно, просто предоставляются шаблоны для автозаполнения. Несколькими примерами определяем, как выполнять генерацию:
"""
(few shot examples above...)
{{leading_indentation}}{{function_header}}
{{function_body}}
{{leading_indentation}}{{function_header}}
# Строка документации ниже
{{leading_indentation}}\"\"\"{{gen 'description' temperature=0.1 max_tokens=128 stop='.'}}
Parameters: {{gen 'parameters' temperature=0.1 max_tokens=128 stop='Returns:'}}
Returns: {{gen 'returns' temperature=0.1 max_tokens=128 stop='\"\"\"'}}
""",
guidance_kwargs={},
input_vars=["function_header", "function_body", "leading_indentation"],
output_vars=["description", "parameters", "returns"],
)
В самом конце определяем ожидаемые входные и выходные переменные. Это, строго говоря, не часть шаблона — она полезна при подключении к этой подсказке из высокоуровневого вызова в кодовой базе.
В конце подсказки, в первом блоке добавляется входной исходный код, как в примерах «подсказок с несколькими выстрелами»:
"""
{{leading_indentation}}{{function_header}}
{{function_body}}
"""
Синтаксис, как у шаблонизатора Jinja2. Это переменные, заменяемые перед отправкой входных данных в большую языковую модель.
Чтобы запустить ожидаемое от модели автозаполнение, вставляем сюда только заголовок функции и комментарий # Строка документации ниже
, как в примерах «подсказок с несколькими выстрелами»:
{{leading_indentation}}{{function_header}}
# Строка документации ниже
Начинаем первую генерацию с этой команды, отступ пока игнорируем:
\"\"\"{{gen 'description' temperature=0.1 max_tokens=128 stop='.'}}
Здесь запускается строка документации, генерация выполняется моделью до символа «.
». Вот пример генерации без тройных кавычек во выходных данных:
This function extracts the function header from the given function definition
Вот следующая сгенерированная переменная:
Parameters: {{gen 'parameters' temperature=0.1 max_tokens=128 stop='Returns:'}}
которая разворачивается в:
fun_code (ast.FunctionDef): The function definition to extract the function header from.
и, наконец:
{{gen 'returns' temperature=0.1 max_tokens=128 stop='\"\"\"'}}
превращается в:
str: The function header.
Но это лишь разрозненные фрагменты, реальной строки документации пока нет. Создадим ее из них.
Создание выходных данных
Переходим в конец серверного контроллера промежуточного ПО:
result = call_guidance(
prompt_template=prompt_to_apply.prompt_template,
input_vars=extracted_input,
output_vars=prompt_to_apply.output_vars,
guidance_kwargs={}
)
logger.info("LLM output: '%s'", result)
result = command_to_apply.output_extractor(prompt_key, extracted_input, result)
Чтобы сгенерировать словарь с предыдущими генерациями, используем подсказку выше с клиентом guidance. Формат такой:
{
"description": "Func description",
"parameters": "a (int): a number to use in the sum",
"returns": "(int): returns the sum"
}
Передаем его в средство извлечения выходных данных DocStringCommand
. Это длинная функция, рассмотрим только ее части.
Вначале определяем, как анализировать выходные данные в зависимости от применяемой подсказки:
def output_extractor(self, prompt_key, extracted_input, result: Dict[str, str]) -> str:
if prompt_key == "generic_prompt":
return result["output"]
elif prompt_key == "function_prompt":
ind = extracted_input["leading_indentation"]
Затем при выполнении синтаксического анализа выходных данных function_prompt
имеется логика преобразования сгенерированных параметров в часть строки документации:
# (скучное решение проблемы отступа выше...)
try:
parameters = result["parameters"].strip().split("\n")
except (KeyError, ValueError):
parameters = []
# (то же для части с «returns»...)
parameters_string = ""
if parameters:
parameters_string = body_indentation + "Parameters: \n"
for param in parameters:
parameters_string += f"{body_indentation}{indentation_type}{param.lstrip().strip()}\n"
parameters_string += "\n"
Дальше используем заголовок функции, добавляем созданные ранее описание и строку параметров в той же функции:
(...)
code_with_docstring = (header_indentation + extracted_input["function_header"] + "\n")
code_with_docstring += (body_indentation + '"""')
code_with_docstring += (result["description"].lstrip().strip() + "\n\n")
logger.info("Header with description: %s", code_with_docstring)
if parameters_string:
code_with_docstring += (parameters_string)
if returns_string:
code_with_docstring += (returns_string)
code_with_docstring += (body_indentation + '"""\n\n')
code_with_docstring += extracted_input["function_body"]
logger.info("Generated code with docstring: %s", code_with_docstring)
return code_with_docstring
Так мы «вручную» воссоздаем строку документации с генерациями из модели. Это не единственный способ: прямо из руководства можно взять строку целиком, избежав части всех этих телодвижений, хотя идентифицировать начало строки документации и чуть поменять подсказку все же придется.
Заключение
Далековато мы забрались в программную инженерию, использовав библиотеку Guidance: чтобы создавать с ней решения, простой подсказки для большой языковой модели мало, нужно много дополнительных усилий.
Однако в некоторых случаях они окупаются: когда требуется точность, повышение производительности от применения подсказок будет совсем не лишним.
Настолько сложным оказалось применение ast.parse
, что возникли две проблемы:
- Из-за
IndentationError
пришлось написать еще много кода. - Удаление дублей новых строк из проанализированного кода.
Вторая проблема сейчас — это источник багов в Oasis. Возможно, придется полностью удалить ast.parse
и написать собственный синтаксический анализатор.
В целом, это хорошая база для написания направляемых подсказок, полезных при генерации кода.
Читайте также:
- CodeGPT: расширение VSCode с функциями ChatGPT
- Как настроить внешний вид Visual Studio Code
- Расширения для Visual Studio Code, которые поднимут процесс разработки на новый уровень
Читайте нас в Telegram, VK и Дзен
Перевод статьи Paolo Rechia: Building Oasis: a Local Code Generator Tool Using Open Source Models and Microsoft’s Guidance Library