Создание инструмента генерации кода с помощью Rust и локальных LLM от Ollama

от автора

Это реакция на выпуск ChatGPT o1-preview. Попытка добавить логику в LLM с открытым исходным кодом, которые можно запустить дома на скромном GPU или даже на CPU

Сейчас я работаю над инструментом на основе Rust, который автоматизирует генерацию, компиляцию и тестирование кода с использованием больших языковых моделей (LLM). Идея заключается в том, чтобы взаимодействовать с LLM для генерации кода на основе предоставленных пользователем объяснений, компилировать код, разрешать зависимости и запускать тесты, чтобы убедиться, что все работает так, как ожидается.

Я хотел бы оптимизировать процесс кодирования функций на основе описаний на естественном языке. Я хотел создать систему, в которой я мог бы ввести объяснение того, что должна делать функция, и чтобы инструмент обрабатывал все остальное от генерации кода до тестирования.

Работа инструмента начинается с того, что запрашивает у пользователя объяснение функции, которую он хочет создать. Затем он взаимодействует с LLM для генерации кода функции, компилирует его и проверяет на наличие ошибок компиляции. Если ошибки обнаружены, инструмент пытается их устранить, возможно, путем добавления зависимостей или переписывания кода. После успешной компиляции кода он генерирует тесты для функции, запускает их и снова обрабатывает любые ошибки, итеративно улучшая код или тесты.

Первый шаг — получить объяснение от пользователя:

println!("Explain what the function should do:"); let mut explanation = String::new(); std::io::stdin().read_line(&mut explanation).unwrap(

Используя это объяснение, инструмент создает запрос для отправки LLM:

let generate_code_prompt = construct_prompt(     generate_code_prompt_template,     vec![&explanation], );

Вот `generate_code_prompt_template`:

let generate_code_prompt_template = r#" {{{0}}}  Write on Rust language code of this function (without example of usage like main function): ```rust fn solution( "#;

Этот prompt сообщает LLM о необходимости сгенерировать код Rust для функции на основе объяснения пользователя.

После генерации кода инструмент пытается его скомпилировать:

create_rust_project(&code, "", ""); let (mut exit_code, mut output) = cargo("build", &mut cache);

Если компиляция завершается неудачей, проверяется, связана ли проблема с отсутствием зависимостей

let build_dependencies_req_prompt = construct_prompt(     build_dependencies_req_prompt_template,     vec![&explanation, &code, &output], );

В зависимости от ответа LLM он может добавить необходимые зависимости в файл `Cargo.toml`:

let build_dependencies_prompt = construct_prompt(     build_dependencies_prompt_template,     vec![&explanation, &code], ); let build_dependencies_result = llm_request(&build_dependencies_prompt, &mut cache); dependencies = extract_code(&build_dependencies_result);

После успешной компиляции кода инструмент генерирует тесты:

let generate_test_prompt = construct_prompt(     generate_test_prompt_template,     vec![&explanation, &code], ); let generation_test_result = llm_request(&generate_test_prompt, &mut cache); code_test = extract_code(&generation_test_result);

Затем он запускает тесты:

let (exit_code_immut, output_immut) = cargo("test", &mut cache);

Если тесты не пройдены, инструмент решает, следует ли переписать код или тесты, в зависимости от того, где находится ошибка:

let rewrite_code_req_prompt_template_prompt = construct_prompt(     rewrite_code_req_prompt_template,     vec![&explanation, &code, &code_test, &output], ); let rewrite_code_req_result = llm_request(&rewrite_code_req_prompt_template_prompt, &mut cache); if extract_number(&rewrite_code_req_result) == 1 {     // Rewrite code } else {     // Rewrite tests }

Для повышения эффективности в инструменте реализована система кэширования:

let result_str_opt = cache.get(&key); let result_str = match result_str_opt {     None => {         // Run command and cache result     }     Some(result) => {         result.to_string()     } };

Это позволяет избежать избыточных вычислений за счет сохранения предыдущих результатов и их извлечения при повторном вводе тех же данных.

Вот более подробная схема логики работы:

Перед запуском инструмента выполните следующие шаги:

  1. Убедитесь, что у вас установлен Rust. Вы можете установить его [здесь]

  2. Требуется для взаимодействия с LLM. Установите с [официального сайта Ollama]

  3. Загрузите модель:

ollama run gemma2:27b

После загрузки модели вы можете сказать «привет» модели, чтобы проверить, правильно ли она работает. После этого вы можете нажать «Ctrl+D», чтобы выйти из модели.

cargo run

Вам будет предложено объяснить, что должна делать функция:

Explain what the function should do:

Предоставьте подробное объяснение, и инструмент сделает все остальное.

Допустим, я ввожу:

parse json string and return struct User (age, name)

Инструмент сгенерирует соответствующую функцию Rust, обработает все зависимости (например, добавит `serde` и `serde_json`), сгенерирует тесты и запустит их. Окончательный вывод будет отображен, а результат будет сохранен в папке `sandbox`.

Сгенерированный результат:

[dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0"  use serde::{Deserialize, Serialize};  #[derive(Deserialize, Serialize, Debug)] struct User {     name: String,     age: u32, }  fn solution(json_string: &str) -> Result<User, serde_json::Error> {     let user: User = serde_json::from_str(json_string)?;     Ok(user) }  #[cfg(test)] mod tests {     use super::*;      #[test]     fn test_solution() {         let json_string = r#"{"name": "John Doe", "age": 30}"#;         let user = solution(json_string).unwrap();         assert_eq!(user.name, "John Doe");         assert_eq!(user.age, 30);     }      #[test]     fn test_solution_invalid_json() {         let json_string = r#"{"name": "John Doe", "age": }"#;         assert!(solution(json_string).is_err());     } }

Исходный код доступен на [GitHub]. Контрибутинг приветствуются!


ссылка на оригинал статьи https://habr.com/ru/articles/844426/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *