GenAI를 그래프로 구성
| 필드 | 값 |
|---|---|
| 범주 | GenAI |
| 난이도 | 고급 |
| 예상 소요 시간 | 20-25 minutes |
| 레이블 | genai, graph, composition, streaming, advanced |
대부분의 생성형 AI 애플리케이션은 직접 모델 API로 시작해야 합니다. 생성형 AI가 다른 모델과 함께 사용되어야 할 때 그래프 구성을 활용하면 유용합니다. 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.
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";
그래프 입력 생성 및 푸시
그래프를 기반으로 Run이미지 샘플을 푸시하여 image 입력한 후 텍스트 샘플을 다음으로 전송합니다. prompt 입력을 제공하고 GenAI 단계에서 토큰을 생성합니다.
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 샘플은 생성된 토큰 수 및 완료 사유와 같은 필드를 포함하는 묶음입니다.
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 DevKitLFM2-VL 1.6B VLM을 다운로드하세요. Hugging Face 다음 텍스트를 사용하여 LLiMa CLI:
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 호출을 유지합니다.
전체 소 스
전체 소스 프로그램 표시
#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;
}
}