직접 API를 사용하여 LLM 실행
| 필드 | 값 |
|---|---|
| 범주 | GenAI |
| 난이도 | 중급 |
| 예상 소요 시간 | 10 minutes |
| 레이블 | genai, llm, chat, history, streaming |
클래식 Model 튜토리얼은 .tar.gz MPK 아카이브를 사용합니다. GenAI 모델은 대신 LLiMa 모델 디렉터리와 neat::genai API를 사용합니다. 가장 작은 요청으로 시작합니다. 모델을 로드하고, request.prompt를 설정하고, 실행한 다음, 답변을 출력합니다. 작동하면 대화 상태가 필요할 때 request.messages로 전환합니다.
둘러보기
모델 디렉터리 로드
배포된 LLiMa 모델 디렉터리를 GenAIModel에 지정합니다. 이 튜토리얼에서는 GenAIModel을 사용하는데, 이는 디렉토리가 LLM, VLM 또는 ASR 모델인지 자동으로 감지하기 때문입니다.
모델 경로에서 simaai::neat::genai::GenAIModel을 생성합니다.
genai::GenAIModel model(args.model);
단일 프롬프트 보내기
prompt 및 토큰 예산과 함께 GenerationRequest를 빌드합니다. 이것은 일회성 질문, 테스트 및 스크립트에 대한 가장 간단한 방법입니다.
genai::GenerationRequest request;
request.prompt = "Give me three practical tips for designing a small REST API.";
request.max_new_tokens = 96;
const genai::GenerationResult first = model.run(request);
std::cout << "assistant: " << first.text << "\n\n";
시스템 프롬프트 정의
짧은 시스템 지침을 사용하여 모델의 동작을 제어합니다. system_prompt를 사용하여 간단한 프롬프트 요청에 연결할 수 있습니다. 대화 기록으로 전환하면 동일한 지침을 메시지 목록의 system 메시지로 전달합니다.
const std::string system_prompt = "You are concise and practical.";
genai::GenerationRequest concise_request;
concise_request.system_prompt = system_prompt;
concise_request.prompt = "Give me one rule of thumb for designing a small REST API.";
concise_request.max_new_tokens = 64;
const genai::GenerationResult concise = model.run(concise_request);
std::cout << "assistant: " << concise.text << "\n\n";
메시지로 전환
대화 스타일 요청의 경우 prompt 대신 messages를 사용합니다. 시스템 메시지와 사용자 메시기로 시작하고, 요청을 실행한 다음, 어시스턴트 응답을 저장합니다. 모델은 이전 run() 호출을 자체적으로 기억하지 않습니다. 애플리케이션이 메시지 기록을 소유합니다.
std::vector<genai::ChatMessage> messages;
messages.push_back(genai::ChatMessage{
.role = "system",
.content = system_prompt,
});
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Give me three practical tips for writing API documentation.",
});
genai::GenerationRequest chat_request;
chat_request.messages = messages;
chat_request.max_new_tokens = 96;
const genai::GenerationResult chat_result = model.run(chat_request);
std::cout << "assistant: " << chat_result.text << "\n\n";
messages.push_back(genai::ChatMessage{.role = "assistant", .content = chat_result.text});
기록과 함께 후속 질문
다른 사용자 메시지를 추가하고, 업데이트된 메시지 목록을 보내고, 답변을 읽습니다. 이제 모델은 애플리케이션이 유지한 전체 대화를 볼 수 있습니다.
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Which tip should I apply first for a prototype?",
});
genai::GenerationRequest follow_up;
follow_up.messages = messages;
follow_up.max_new_tokens = 96;
const genai::GenerationResult second = model.run(follow_up);
std::cout << "assistant: " << second.text << "\n\n";
messages.push_back(genai::ChatMessage{.role = "assistant", .content = second.text});
답변 스트리밍
UI 스타일 출력의 경우 stream()을 호출하고 반환된 GenerationStream을 반복합니다. 각 토큰 샘플에는 최신 텍스트 조각이 포함됩니다.
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Turn that advice into a short checklist.",
});
genai::GenerationRequest streaming_request;
streaming_request.messages = messages;
streaming_request.max_new_tokens = 96;
genai::GenerationStream stream_handle = model.stream(streaming_request);
std::cout << "assistant: ";
for (const genai::TokenSample& token : stream_handle) {
std::cout << token.text << std::flush;
}
std::cout << "\n";
실행
Modalix DevKit에서 Hugging Face의 LLiMa CLI를 사용하여 Qwen3 4B와 같은 LLM을 다운로드합니다.
llima pull Qwen3-4B-Instruct-2507-GPTQ-a16w4
Modalix에서 DevKit 로컬 모델 디렉터리를 사용하여 튜토리얼을 실행합니다.
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_019_run_an_llm \
--model /media/nvme/llima/models/Qwen3-4B-Instruct-2507-GPTQ-a16w4
C++ (build from source):
./build.sh --target tutorial_019_run_an_llm
./build/tutorials-standalone/tutorial_019_run_an_llm \
--model /media/nvme/llima/models/Qwen3-4B-Instruct-2507-GPTQ-a16w4
예상 출력은 간단한 프롬프트 응답, 시스템 프롬프트 응답, 상황에 맞는 후속 응답, 그리고 스트리밍 방식으로 제공되는 최종 응답입니다.
실전 활용
애플리케이션에 필요한 메시지 기록의 양만 유지합니다. 긴 기록은 컨텍스트 토큰을 소모하고 첫 번째 토큰을 생성하는 데 걸리는 시간을 늘립니다. 지속적인 채팅 애플리케이션의 경우, 대화 내용을 모델 객체 외부에 저장하고 각 단계마다 GenerationRequest.messages를 다시 구성합니다.
전체 소스
전체 소스 프로그램 표시
#include "neat/genai.h"
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace genai = simaai::neat::genai;
struct Args {
std::filesystem::path model;
};
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 {
throw std::runtime_error("usage: run_an_llm --model <llima_model_dir>");
}
}
if (args.model.empty()) {
throw std::runtime_error("missing required --model <llima_model_dir>");
}
return args;
}
int main(int argc, char** argv) {
try {
const Args args = parse_args(argc, argv);
genai::GenAIModel model(args.model);
genai::GenerationRequest request;
request.prompt = "Give me three practical tips for designing a small REST API.";
request.max_new_tokens = 96;
const genai::GenerationResult first = model.run(request);
std::cout << "assistant: " << first.text << "\n\n";
const std::string system_prompt = "You are concise and practical.";
genai::GenerationRequest concise_request;
concise_request.system_prompt = system_prompt;
concise_request.prompt = "Give me one rule of thumb for designing a small REST API.";
concise_request.max_new_tokens = 64;
const genai::GenerationResult concise = model.run(concise_request);
std::cout << "assistant: " << concise.text << "\n\n";
std::vector<genai::ChatMessage> messages;
messages.push_back(genai::ChatMessage{
.role = "system",
.content = system_prompt,
});
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Give me three practical tips for writing API documentation.",
});
genai::GenerationRequest chat_request;
chat_request.messages = messages;
chat_request.max_new_tokens = 96;
const genai::GenerationResult chat_result = model.run(chat_request);
std::cout << "assistant: " << chat_result.text << "\n\n";
messages.push_back(genai::ChatMessage{.role = "assistant", .content = chat_result.text});
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Which tip should I apply first for a prototype?",
});
genai::GenerationRequest follow_up;
follow_up.messages = messages;
follow_up.max_new_tokens = 96;
const genai::GenerationResult second = model.run(follow_up);
std::cout << "assistant: " << second.text << "\n\n";
messages.push_back(genai::ChatMessage{.role = "assistant", .content = second.text});
messages.push_back(genai::ChatMessage{
.role = "user",
.content = "Turn that advice into a short checklist.",
});
genai::GenerationRequest streaming_request;
streaming_request.messages = messages;
streaming_request.max_new_tokens = 96;
genai::GenerationStream stream_handle = model.stream(streaming_request);
std::cout << "assistant: ";
for (const genai::TokenSample& token : stream_handle) {
std::cout << token.text << std::flush;
}
std::cout << "\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << "\n";
return 1;
}
}