LangChain и OpenSCAD: Практическая реализация AI-агента
Глава 1. Введение в LangChain и OpenSCAD
Введение в LangChain и OpenSCAD и практический сценарий создания AI‑агента, способного генерировать модели параметрических деталей с помощью комбинации мощного фреймворка LangChain и детализированного язык‑программирования OpenSCAD может стать отправной точкой для построения целых экосистем автоматического проектирования; в этом разделе мы подробно разберём архитектуру LangChain, принципы работы его компонентов‑агентов, способы интеграции с OpenSCAD через скриптовые вызовы и демонстрируем, как собрать полностью рабочий прототип агента, способного принимать естественную речь пользователя, интерпретировать запросы о форме, размерах и функциональных характеристиках, а затем преобразовывать их в готовый код OpenSCAD и отправлять готовый STL‑файл на виртуальный или реальный 3‑D‑принтер; сначала необходимо понять, что LangChain представляет собой набор модулей и цепочек (chains), позволяющих связывать между собой вызовы LLM, базы данных, внешних API и пользовательских функций так, чтобы построить сложные сценарии обработки информации, при этом каждый компонент (prompt, model, memory, tool, output parser) играет определённую роль в минимизации ложных интерпретаций и повышении воспроизводимости результатов; в контексте OpenSCAD агент будет оперировать не только текстовыми данными, но и структурированными процедурными скриптами, поэтому важно обеспечить методы передачи кода в обе стороны: от LLM к OpenSCAD (выполнение внешних процессов) и от OpenSCAD к LLM (получение обратной связи о структуре, визуализации модели и возможности её доработки); один из самых простых и одновременно гибких подходов подразумевает создание собственного `Tool`‑класса, наследующего базовый интерфейс LangChain, который будет отвечать за запуск внешних команд `openscad` из подпроцесса, чтение результата их выполнения (например, выдаваемый G‑code или файл `.scad`) и возврат его содержимого в виде строки, которую последующий `output parser` сможет интерпретировать как готовый Beschreibung или как набор параметров, подлежащих повторному запросу у модели; благодаря такой интеграции агент получает возможность выполнять не только генерацию чисто текстовых ответов, но и реального производства 3‑D‑моделей, что открывает путь к автоматизированной конвейерной обработке запросов от конечных пользователей, работающих в CAD‑системах, инженерных отделах или сервисах быстрого прототипирования; следующий шаг состоит в построении цепочки (chain) LangChain, в которой запрос пользователя сначала обрабатывается prompts‑модулем, который формирует ввод‑текст для LLM, затем запрос передаётся через `LLMChain`, где модель генерирует промпт для получения спецификации модели (размер, шаги, материалы, особенности), далее этот промпт отправляется в наш кастомный `OpenSCADExecutorTool`, который создает временный файл `.scad` со всеми вычисленными параметрами и вызывает OpenSCAD через `subprocess` для компиляции модели; после генерации файл передаётся в `STLExportStep` – встроенную функцию, которая использует OpenSCAD в режиме headless (`openscad -o output.stl model.scad`) и в результате получает готовый STL‑файл, который затем может быть сохранён, отправлен в очередь печати или возвращён пользователю в виде ссылки на файл; каждый из этих шагов можно снабдить механизмами памяти (`memory`), так что агент будет помнить предыдущие запросы, поддерживать контекст и корректировать последующие генерации, тем самым обеспечивая более персонализированный опыт и сокращая количество повторных уточнений; для реализации такой цепочки в коде на Python потребуется установить несколько библиотек: `langchain`, `langchain_community` (для новых модулей), `openai` или любой другой провайдер LLM‑API, а также `subprocess`, `os`, `tempfile` и `re` для работы с внешними процессами и регулярными выражениями; пример инициализации агента выглядит следующим образом:
`from langchain.agents import Tool, AgentType`
`from langchain.llms import OpenAI`
`from langchain.chains import LLMChain`
`from langchain.prompts import PromptTemplate`
`import subprocess, tempfile, os, re`
`llm = OpenAI(model="gpt-4-turbo", temperature=0.2)`
`scad_prompt = PromptTemplate(input_variables=["spec"], template="Generate a complete OpenSCAD script based on the following specification: {spec}")`
`spec_chain = LLMChain(llm=llm, prompt=scad_prompt)`
`def run_scad(script):`
` with tempfile.NamedTemporaryFile(mode='w', suffix='.scad', delete=False) as f:`
` f.write(script)`
` script_path = f.name`
` stl_path = script_path.replace('.scad', '.stl')`
` subprocess.run(['openscad', '-o', stl_path, script_path], check=True)`
` return stl_path`
`scad_tool = Tool(name="OpenSCADExecutor", func=run_scad, description="Executes an OpenSCAD script and returns the path to the generated STL file.")`
`agent = AgentType.create(agent_name="OpenSCADAgent", tools=[scad_tool])`
Тут `AgentType.create` – гипотетический метод, позволяющий собрать агента из набора инструментов; в реальной реализации обычно используют `initialize_agent` или `AgentExecutor` из более новых версий библиотеки; важно отметить, что `AgentExecutor` автоматически управляет prompt‑ами, memory и выводом, поэтому он может заменить ручное построение цепочки; при этом каждый вызов `execute` будет проверять, требуется ли запрос к инструменту `OpenSCADExecutor`, и автоматически преобразовывать полученный путь к STL‑файлу в ответ пользователю или в сообщение об ошибке, если скрипт не скомпилировался; следующий пример демонстрирует полный рабочий цикл:
`def ask_user(request_text):`
` # 1. Сформировать промпт для LLM`
` spec = spec_chain.run(request_text)`
` # 2. Передать скрипт в OpenSCADExecutorTool`
` stl_path = scad_tool.run(spec)`
` # 3. Вернуть путь к файлу`
` return stl_path`
В этом сценарии пользователь отправляет запрос через любой канал – чат‑бота, веб‑интерфейса или телефонную линию – а агент, используя `AgentExecutor`, последовательно обрабатывает запрос, генерирует спецификацию, компилирует её в OpenSCAD и возвращает готовый STL‑файл; при желании можно добавить дополнительный слой – `output parser`, который будет трансформировать путь к STL в URL, который пользователь сможет открыть непосредственно в браузере; также можно внедрить механизм `memory` (например, `ConversationTokenMemory`), чтобы агент помнил предыдущие обсуждения о ранге материалов, размерах печати или требованиях к поверхности, тем самым уменьшает количество корректировок и делает генерацию более предсказуемой; в процессе тестирования пользователь может вводить уточнения вроде `"увеличьте радиус в 1.5 раза"` или `"добавьте отверстие диаметром 5 мм в центре"`, которые агент интерпретирует заново, либо сохраняет в контексте для последующего предиката, тем самым создавая гибридный диалог, в котором естественный язык и технические требования плавно переходят друг в друга; при масштабировании такой системы на множество типов моделей (например, сложные механизмы, артефактные геометрии или многочастичные сборки) можно построить набор специализированных инструментов: `ExtrudeTool`, `RotateTool`, `LoopTool`, каждый из которых реализует специфичную операцию над уже сгенерированным скриптом и может быть вызван последовательно в цепочке; при этом `Agent` будет автоматически определять, какой инструмент нужен на основе анализа промпта, и выполнять соответствующую функцию, позволяя пользователю описывать модель почти свободным языком без необходимости писать реальный код OpenSCAD; такой подход открывает значительные возможности для дальнейшего развития: от интеграции с облачными сервисами хранения STL‑файлов, через автоматический импорт готовых библиотек библиотек OpenSCAD (`<mcad>`, `<cga>` и др.) до построения полноценных CI/CD пайплайнов, где изменения в спецификации автоматически порождают новые версии моделей, проходят тесты на валидность стенок и отправляются в очередь печати без человеческого вмешательства; в конечном итоге практическая реализация агента на базе LangChain и OpenSCAD демонстрирует, как современные LLM‑модели могут стать «мостом» между свободной коммуникацией и детализированным механическим проектированием, позволяя как новичкам без глубоких знаний CAD‑техники быстро создавать прототипы, так и инженерам ускорять итеративный процесс разработки за счёт автоматизированного рендеринга и экспорта; для дальнейшего развития рекомендуется протестировать разные модели LLM – от GPT‑3.5‑Turbo до специализированных моделей, оптимизированных под генерацию технических описаний, исследовать варианты «chain‑of‑thought» для более точного парсинга пользовательских запросов, а также добавить слои валидации, такие как проверка единиц измерения, диапазонов размеров и корректности синтаксиса OpenSCAD, чтобы гарантировать, что каждая сгенерированная модель будет пригодна к печати без необходимости ручной правки; таким образом, глава «Введение в LangChain и OpenSCAD» дает полное представление о том, как построить многофункционального AI‑агента, способного принимать естественную речь, преобразовывать её в детализированную параметрическую модель в OpenSCAD, вычислять её, экспортировать STL и доставлять готовый объект на 3‑D‑принтер, при этом используя гибкую архитектуру LangChain, промпты, память и кастомные инструменты, что делает систему масштабируемой, пользовательско‑ориентированной и готовой к интеграции в реальные промышленные и исследовательские проекты.
Глава 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. Расширение функциональности
После базовой реализации можно добавить несколько типовых расширений:
‑ Параметрические модули: Позволить пользователю задавать функции‑модули, которые потом могут быть переиспользованы.
