title: "2. llama.cppを組み込んでREST APIを作る" order: 2
1章の雛形では/translateが"TODO"を返すだけでした。この章ではllama.cppの推論を組み込んで、実際に翻訳結果を返すAPIに仕上げます。
llama.cppのAPIを直接扱うとコードが長くなるので、薄いラッパーライブラリcpp-llamalibを使います。モデルのロードから推論まで数行で書けるので、cpp-httplibの使い方に集中できます。
llamalib::Llamaにモデルファイルのパスを渡すだけで、モデルのロード・コンテキスト作成・サンプラー設定がすべて済みます。1章で別のモデルをダウンロードした場合は、パスをそのモデルに合わせてください。
#include <cpp-llamalib.h>
int main() {
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf"};
// LLM推論は時間がかかるのでタイムアウトを長めに設定(デフォルトは5秒)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
// ... HTTPサーバーの構築・起動 ...
}
GPU層数やコンテキスト長などを変えたい場合はllamalib::Optionsで指定できます。
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf", {
.n_gpu_layers = 0, // CPUのみ
.n_ctx = 4096,
}};
/translateハンドラ1章ではダミーのJSONを返していたハンドラを、実際の推論に差し替えます。
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
// JSONパース(第3引数`false`: 失敗時に例外を投げず`is_discarded()`で判定)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
// 必須フィールドの検証
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja"); // デフォルトは日本語
// プロンプトを組み立てて推論
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
llm.chat()は推論中に例外を投げることがあります(コンテキスト長の超過など)。try/catchで捕捉してエラーをJSONで返すことで、サーバーがクラッシュするのを防ぎます。
ここまでの変更をまとめた完成形です。
ビルドし直してサーバーを起動し、今度は実際の翻訳結果が返ってくるか確かめましょう。
cmake --build build -j
./build/translate-server
curl -X POST http://localhost:8080/translate \
-H "Content-Type: application/json" \
-d '{"text": "I had a great time visiting Tokyo last spring. The cherry blossoms were beautiful.", "target_lang": "ja"}'
# => {"translation":"去年の春に東京を訪れた。桜が綺麗だった。"}
1章では"TODO"が返ってきていましたが、今度は実際の翻訳結果が返ってきます。
この章で作ったREST APIは、翻訳が完了するまで全文を待つので、長いテキストだとユーザーは進捗がわからないまま待つことになります。
次の章ではSSE(Server-Sent Events)を使って、トークンが生成されるたびにリアルタイムで返す仕組みにします。
Next: SSEでトークンストリーミングを追加する