Читать книгу LangChain и OpenSCAD: Практическая реализация AI-агента - - Страница 2

Глава 2. Основы построения AI‑метапрограмм

Оглавление

Введение в создание AI‑агента в OpenSCAD через LangChain


OpenSCAD ― это скриптовый языкъ моделирования твердых тел, широко используемый в техническом проектировании и 3D‑печати. Нативное API OpenSCAD ориентировано на декларативное описание геометрии, но для взаимодействия с внешними данными, динамического выбора параметров и генерации сложных моделей часто требуется более гибкое управление. Именно здесь вступает в игру LangChain, позволяющая построить так называемый AI‑агент, который может автоматически генерировать код OpenSCAD, учитывая запросы пользователя, ограничения проектирования и результаты предыдущих шагов.


В этом разaking мы рассмотрим пошаговый процесс написания такого агента, опираясь на возможности библиотеки LangChain, а также покажем практико‑ориентированный формат кода, который можно сразу использовать в своих проектах.


1. Подготовка окружения


Для начала необходимо установить необходимые зависимости. На Python‑окружении рекомендуется создать виртуальное окружение и установить пакеты langchain‑core, langchain‑openai (или другой провайдер LLM‑модели), а также библиотеку python‑openSCAD, которая обеспечивает возможность генерировать и отправлять команды OpenSCAD из кода Python. Кроме того, потребуется доступ к LLM‑модели, например, GPT‑4 или аналог.


Команды установки выглядят так:

```

python -m venv venv

source venv/bin/activate # Linux/macOS

venv\Scripts\activate # Windows

pip install langchain-core langchain-openai python-openSCAD

```

После установки необходимо задать переменные окружения для доступа к LLM: OPENAI_API_KEY и другие параметры, связанные с выбранным провайдером.


2. Общая архитектура агента


AI‑агент в контексте LangChain представляет собой цепочку (pipeline) of LLM‑вызовов, правил и вспомогательных компонентов. Для OpenSCAD‑сценария типичная структура выглядит так:


‑ Входные данные – запрос пользователя, описывающий требуемую модель (например, «создайçõesлучайную спираль с радиусом от 5 до 15 мм и пяти estivesseтами»).

‑ Преобразователь запроса (prompt template) – переводит естественный язык в промпт, пригодный LLM.

‑ LLM‑вызов – запрашивает у модели генерацию кода OpenSCAD.

‑ Пост‑обработка – фильтрует полученный код: проверка синтаксиса, корректировка параметров, приведение к формату, совместимому с OpenSCAD.

‑ Вывод – передача готового кода в OpenSCAD или сохранение в файл.


LangChain предоставляет готовые классы для построения таких цепочек: PromptTemplate, LLMChain, SequentialChain и др. В нашем случае наиболее подходящим вариантом будет использование LLMChain с пользовательским обработчиком вывода (output parser), который преобразует строку кода в объект, который можно непосредственно выполнить.


3. Создание шаблона промпта


Промпт должен четко формулировать требования к коду: указать типы геометрических примитивов, их параметры, отношения между элементами, а также ограничения по размеру модели. Пример шаблона:


```

Ты – эксперт по OpenSCAD. Сгенерируй только корректный код OpenSCAD, соответствующий следующему описанию:

{user_request}

Обязательно включи модуль main() и заверши код оператором render()!\nКод:

```


Здесь {user_request} будет заменяться на реальный запрос пользователя. Подобный шаблон гарантирует, что модель будет выводить именно код, а не объяснительные тексты.


4. Настройка LLMChain


С помощью LLMChain мы свяжем промпт, модель и обработчик. Пример реализации:


```

from langchain import LLMChain

from langchain.prompts import PromptTemplate

from langchain_openai import ChatOpenAI


llm = ChatOpenAI(model="gpt-4", temperature=0.2, max_tokens=1500)


prompt = PromptTemplate(

input_variables=["user_request"],

template=(

"Ты – эксперт по OpenSCAD. Сгенерируй только корректный код OpenSCAD, "

"соответствующий описанию:\n{user_request}\n"

"Обязательно включи модуль main() и заверши код оператором render()!\n"

"Код:"

)

)


chain = LLMChain(

llm=llm,

prompt=prompt,

output_parser=OpenSCADParser()

)

```


В данном примере `OpenSCADParser` – пользовательский класс, реализующий парсинг полученного текста в валидный набор команд. Он будет подробно рассмотрен ниже.


5. Реализация парсера кода OpenSCAD


Получаемый от LLM текст может содержать лишние префиксы, пояснительные строки и даже ошибки синтаксиса. Чтобы гарантировать корректную работу, нужен парсер, который:


‑ Отрезает все строки до первого появления слова `module` или `//=== START CODE ===//`.

‑ Удаляет комментарии в конце строк.

‑ Проверяет наличие обязательных блоков `module main()` и `render()`; при их отсутствии добавляет.

‑ Возвращает чистый код в виде строки.


Пример реализации простого парсера:


```

import re


class OpenSCADParser:

def parse(self, text: str) -> str:

# Удаляем всё до первого признака начала кода

match = re.search(r"(?s)(module\s+\w+\|?\s*\(.*?\)|//=== START CODE ===//)", text)

if not match:

raise ValueError("Не найден маркер начала кода")

start_idx = match.start(1) if match.lastgroup == "module" else match.start("START CODE")

code = text[start_idx:].strip()

# Удаляем комментарии в конце строк

lines = code.splitlines()

processed = []

for line in lines:

if "#" in line:

line = line[: line.index("#")].rstrip()

processed.append(line)

code = "\n".join(processed)

# Убеждаемся, что есть модуль main и render

if "module main()" not in code:

code += "\nmodule main() {\n}"

if "render()" not in code:

code += "\nrender()\n}"

# Финальная очистка лишних пустых строк

code = re.sub(r"\n{3,}", "\n\n", code).strip()

return code

```


Этот парсер достаточно прост, но в реальных проектах можно расширить его, добавив проверку соответствия синтаксису OpenSCAD через подпроцесс `openscad -c` и откат к повторному запросу модели при ошибке.


6. Интеграция с OpenSCAD


После получения чистого кода его можно выполнить в OpenSCAD через командную строку или через Python‑интерфейс. Если требуется дальнейшая обработка (например, экспорт STL), удобно создать функцию `execute_openSCAD(code: str) -> str`, которая:


1. Записывает код в временный файл `temp.scad`.

2. Вызывает OpenSCAD в режиме командной строки: `openscad -c temp.scad -o temp.stl`.

3. Возвращает путь к полученному STL‑файлу или содержимое, если нужен только 2D‑проект.


Пример реализации:


```

import os

import tempfile

import subprocess


def execute_openSCAD(code: str) -> str:

# Создаём временный файл

with tempfile.NamedTemporaryFile(mode="w", suffix=".scad", delete=False) as f:

f.write(code)

temp_path = f.name

try:

# Путь к исполняемому файлу OpenSCAD

openscad_path = "openscad" # Предполагаем, что он доступен в PATH

# Формируем команду для генерации STL

cmd = [openscad_path, "-c", temp_path, "-o", temp_path.replace(".scad", ".stl")]

subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

# Возвращаем путь к STL

stl_path = temp_path.replace(".scad", ".stl")

return stl_path

finally:

# Очистка временных файлов

os.remove(temp_path)

```


Такой подход позволяет полностью автоматизировать процесс генерации, проверки и экспорта модели без ручного вмешательства.


7. Обработка ошибок и рекурсивное уточнение


Несмотря на тщательный парсинг, генерация кода может иногда приводить к синтаксическим ошибкам, особенно при работе с сложными параметрами. LangChain предоставляет механизмы для повторных запросов к LLM. Один из вариантов – использовать `Retryable` в цепочке, или вручную отлавливать исключения парсера и отправлять в модель запрос на «исправьте ошибку: {error_message}» с сохранением прежних ограничений.


Пример функции обратного вызова:


```

def handle_error(error_msg: str) -> str:

return f"Исправь ошибку: {error_msg}. Сгенерируй корректный код, соблюдая исходные параметры."


# Внутри LLMChain можно использовать:

chain_with_retry = LLMChain(

llm=llm,

prompt=prompt,

output_parser=OpenSCADParser(),

verbose=True,

callbacks=[handle_error] # упрощённый пример

)

```


Таким образом, система получает возможность «само‑практиковаться», улучшая качество генерируемого кода.


8. Пример практического сценария


Допустим, пользователь хочет создать модуль «шестерню с радиусом 30 мм, 12 зубцами, толщиной 5 мм». Запрос передаётся агенту, который формирует промпт и запрашивает генерацию кода. Ниже полный пример взаимодействия:


```

user_request = "Создай шестиугольный блок с длиной 50 мм, шириной 30 мм и высотой 20 мм, а также вставь в центр отверстие диаметром 10 мм"

result = chain.run(user_request)

parsed_code = OpenSCADParser().parse(result)

stl_file = execute_openSCAD(parsed_code)

print(f"Файл STL сохранён в {stl_file}")

```


В результате получаем готовый STL‑файл, который можно импортировать в любую программу 3D‑визуализации или отправить на 3D‑печать.


9. Расширение функциональности


После базовой реализации можно добавить несколько типовых расширений:


‑ Параметрические модули: Позволить пользователю задавать функции‑модули, которые потом могут быть переиспользованы.

LangChain и OpenSCAD: Практическая реализация AI-агента

Подняться наверх