Перейти до основного вмісту

Інтегруйте GenAI у граф

ПолеЗначення
КатегоріяGenAI
СкладністьПросунутий
Орієнтовний час читання20-25 minutes
Міткиgenai, graph, composition, streaming, advanced

Більшість застосунків генеративного штучного інтелекту (GenAI) повинні починатися з використання прямих API моделей. Компонування графа стає корисним, коли GenAI потрібно інтегрувати з іншими етапами Neat, такими як іменовані вхідні дані, іменовані вихідні дані, маршрутизація або оркестрація на рівні застосунку.

Покроковий огляд

Створення фрагмента графа GenAI

Створіть обробник моделі, специфічний для завдання, налаштуйте параметри фрагмента графа та створіть загальнодоступний Graph фрагмент.

Фрагмент, що поєднує обробку зображень і тексту, надає вхідні дані prompt, image і use_cached_image, а також вихідні дані tokens, done, encoded і error. Фрагмент транскриптора мовлення надає вхідні дані audio і audio_path, а також вихідні дані tokens, done і error.

SpeechTranscriberOptions за замовчуванням використовує автоматичне визначення мови та транскрипцію. Встановіть task на ASRTask::Translate в C++ або ASRTask.Translate в Python, щоб перекласти мовлення на англійську мову. Його пакет done містить інформацію про виявлену вихідну мову, а також, за наявності, no_speech_prob і avg_logprob.

tutorials/022_compose_genai_into_graph/compose_genai_into_graph.cpp
auto model = std::make_shared<genai::VisionLanguageModel>(args.model);

genai::VisionLanguageOptions options;
options.system_prompt = "You are concise.";
options.max_new_tokens = 96;
options.streaming = true;
options.encode_images_on_input = false;

simaai::neat::Graph genai_fragment =
genai::graphs::VisionLanguage(model, options, "genai_stage");

Додавання фрагмента до графа застосунку

Додайте фрагмент до більшого графа застосунку. Фрагмент зберігає назви своїх загальнодоступних кінцевих точок, тому код застосунку може передавати та отримувати дані за назвою.

tutorials/022_compose_genai_into_graph/compose_genai_into_graph.cpp
simaai::neat::Graph app("genai_app");
app.add(genai_fragment);
std::cout << app.describe() << "\n";

Створення та передавання вхідних даних графа

Створіть граф у вигляді Run, передайте зразок зображення у вхід image, потім передайте текстовий зразок у вхід prompt і дозвольте етапу GenAI генерувати токени.

tutorials/022_compose_genai_into_graph/compose_genai_into_graph.cpp
simaai::neat::Run run = app.build();
if (!run.push("image", make_image_sample(args.image))) {
throw std::runtime_error("push(image) failed: " + run.last_error());
}
if (!run.push("prompt", make_text_sample("prompt", "Describe this image in one sentence."))) {
throw std::runtime_error("push(prompt) failed: " + run.last_error());
}

Отримання токенів і метаданих завершення

Отримуйте дані з tokens, поки не надійде зразок done. Зразок done містить інформацію, таку як кількість згенерованих токенів і причина завершення.

tutorials/022_compose_genai_into_graph/compose_genai_into_graph.cpp
std::cout << "assistant: ";
for (int i = 0; i < 256; ++i) {
if (auto token = run.pull("tokens", 250)) {
std::cout << sample_text(*token) << std::flush;
continue;
}
if (auto done = run.pull("done", 10)) {
(void)done;
break;
}
if (auto error = run.pull("error", 10)) {
throw std::runtime_error(sample_text(*error));
}
}
std::cout << "\n";
run.close();

Запуск

У Modalix DevKit завантажте VLM LFM2-VL 1.6B з Hugging Face, використовуючи CLI LLiMa:

llima pull LFM2-VL-1.6B-a16w4

Запустіть навчальний посібник у Modalix з локальним каталогом моделі DevKit і локальним зображенням:

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_022_compose_genai_into_graph \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image share/sima-neat/tutorials/assets/fronalpstock_1330.jpg

C++ (build from source):

./build.sh --target tutorial_022_compose_genai_into_graph
./build/tutorials-standalone/tutorial_022_compose_genai_into_graph \
--model /media/nvme/llima/models/LFM2-VL-1.6B-a16w4 \
--image share/sima-neat/tutorials/assets/fronalpstock_1330.jpg

Очікуваний вивід містить опис графа та поточну відповідь, отриману з вихідних даних tokens.

На практиці

Використовуйте цей шаблон, коли GenAI є частиною більшого графа застосунку. Забезпечте прямий GenAIModel, VisionLanguageModelі ASRModel вимагає простого коду застосунку для обробки запитів і відповідей.

Повний початковий код

Показати повні програми
tutorials/022_compose_genai_into_graph/compose_genai_into_graph.cpp
#include "neat.h"

#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include <filesystem>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>

namespace genai = simaai::neat::genai;

struct Args {
std::filesystem::path model;
std::filesystem::path image;
};

Args parse_args(int argc, char** argv) {
Args args;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--model" && i + 1 < argc) {
args.model = argv[++i];
} else if (arg == "--image" && i + 1 < argc) {
args.image = argv[++i];
} else {
throw std::runtime_error(
"usage: compose_genai_into_graph --model <vlm_model_dir> --image <image>");
}
}
if (args.model.empty() || args.image.empty()) {
throw std::runtime_error("missing required --model <vlm_model_dir> or --image <image>");
}
return args;
}

simaai::neat::Sample make_text_sample(const std::string& port, const std::string& text) {
return simaai::neat::make_tensor_sample(port, simaai::neat::Tensor::from_text(text));
}

simaai::neat::Sample make_image_sample(const std::filesystem::path& image_path) {
cv::Mat bgr = cv::imread(image_path.string(), cv::IMREAD_COLOR);
if (bgr.empty()) {
throw std::runtime_error("failed to read image: " + image_path.string());
}

cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
return simaai::neat::make_tensor_sample(
"image", simaai::neat::Tensor::from_cv_mat(rgb, simaai::neat::ImageSpec::PixelFormat::RGB,
simaai::neat::TensorMemory::CPU));
}

std::string sample_text(const simaai::neat::Sample& sample) {
if (sample.kind == simaai::neat::SampleKind::Tensor && sample.tensor.has_value()) {
return sample.tensor->to_text();
}
if (sample.kind == simaai::neat::SampleKind::TensorSet && sample.tensors.size() == 1U) {
return sample.tensors.front().to_text();
}
return {};
}

int main(int argc, char** argv) {
try {
const Args args = parse_args(argc, argv);

auto model = std::make_shared<genai::VisionLanguageModel>(args.model);

genai::VisionLanguageOptions options;
options.system_prompt = "You are concise.";
options.max_new_tokens = 96;
options.streaming = true;
options.encode_images_on_input = false;

simaai::neat::Graph genai_fragment =
genai::graphs::VisionLanguage(model, options, "genai_stage");

simaai::neat::Graph app("genai_app");
app.add(genai_fragment);
std::cout << app.describe() << "\n";

simaai::neat::Run run = app.build();
if (!run.push("image", make_image_sample(args.image))) {
throw std::runtime_error("push(image) failed: " + run.last_error());
}
if (!run.push("prompt", make_text_sample("prompt", "Describe this image in one sentence."))) {
throw std::runtime_error("push(prompt) failed: " + run.last_error());
}

std::cout << "assistant: ";
for (int i = 0; i < 256; ++i) {
if (auto token = run.pull("tokens", 250)) {
std::cout << sample_text(*token) << std::flush;
continue;
}
if (auto done = run.pull("done", 10)) {
(void)done;
break;
}
if (auto error = run.pull("error", 10)) {
throw std::runtime_error(sample_text(*error));
}
}
std::cout << "\n";
run.close();

return 0;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << "\n";
return 1;
}
}

Джерело