Conversation là một API cấp cao, đại diện cho một cuộc trò chuyện duy nhất, có trạng thái với LLM và là điểm truy cập được đề xuất cho hầu hết người dùng. API này quản lý nội bộ một Session và xử lý
các tác vụ xử lý dữ liệu phức tạp. Các tác vụ này bao gồm duy trì bối cảnh ban đầu, quản lý định nghĩa công cụ, tiền xử lý dữ liệu đa phương thức và áp dụng các mẫu lời nhắc Jinja với định dạng thông báo dựa trên vai trò.
Quy trình làm việc của Conversation API
Vòng đời điển hình để sử dụng Conversation API là:
- Tạo
Engine: Khởi chạy mộtEngineduy nhất bằng đường dẫn và cấu hình mô hình. Đây là một đối tượng nặng chứa trọng số mô hình. - Tạo
Conversation: Sử dụngEngineđể tạo một hoặc nhiều đối tượngConversationđơn giản. - Gửi tin nhắn: Sử dụng các phương thức của đối tượng
Conversationđể gửi tin nhắn đến LLM và nhận phản hồi, cho phép tương tác giống như trò chuyện một cách hiệu quả.
Dưới đây là cách đơn giản nhất để gửi tin nhắn và nhận phản hồi của mô hình. Bạn nên sử dụng cách này cho hầu hết các trường hợp sử dụng. Cách này phản ánh Gemini Chat API.
SendMessage: Một lệnh gọi chặn nhận hoạt động đầu vào của người dùng và trả về phản hồi hoàn chỉnh của mô hình.SendMessageAsync: Một lệnh gọi không chặn truyền trực tuyến phản hồi của mô hình theo từng mã thông báo thông qua lệnh gọi lại.
Dưới đây là ví dụ về đoạn mã:
Nội dung chỉ có văn bản
#include "runtime/engine/engine.h"
// ...
// 1. Define model assets and engine settings.
auto model_assets = ModelAssets::Create(model_path);
CHECK_OK(model_assets);
auto engine_settings = EngineSettings::CreateDefault(
model_assets,
/*backend=*/litert::lm::Backend::CPU);
// 2. Create the main Engine object.
absl::StatusOr<std::unique_ptr<Engine>> engine = Engine::CreateEngine(engine_settings);
CHECK_OK(engine);
// 3. Create a Conversation
auto conversation_config = ConversationConfig::CreateDefault(**engine);
CHECK_OK(conversation_config)
absl::StatusOr<std::unique_ptr<Conversation>> conversation = Conversation::Create(**engine, *conversation_config);
CHECK_OK(conversation);
// 4. Send message to the LLM with blocking call.
absl::StatusOr<Message> model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", "What is the tallest building in the world?"}
});
CHECK_OK(model_message);
// 5. Print the model message.
std::cout << *model_message << std::endl;
// 6. Send message to the LLM with asynchronous call
// where CreatePrintMessageCallback is a users implemented callback that would
// process the message once a chunk of message output is received.
std::stringstream captured_output;
(*conversation)->SendMessageAsync(
JsonMessage{
{"role", "user"},
{"content", "What is the tallest building in the world?"}
},
CreatePrintMessageCallback(std::stringstream& captured_output)
);
// Wait until asynchronous finish or timeout.
*engine->WaitUntilDone(absl::Seconds(10));
Ví dụ: CreatePrintMessageCallback
CreatePrintMessageCallbackabsl::AnyInvocable<void(absl::StatusOr<Message>)> CreatePrintMessageCallback(
std::stringstream& captured_output) {
return [&captured_output](absl::StatusOr<Message> message) {
if (!message.ok()) {
std::cout << message.status().message() << std::endl;
return;
}
if (auto json_message = std::get_if<JsonMessage>(&(*message))) {
if (json_message->is_null()) {
std::cout << std::endl << std::flush;
return;
}
ABSL_CHECK_OK(PrintJsonMessage(*json_message, captured_output,
/*streaming=*/true));
}
};
}
absl::Status PrintJsonMessage(const JsonMessage& message,
std::stringstream& captured_output,
bool streaming = false) {
if (message["content"].is_array()) {
for (const auto& content : message["content"]) {
if (content["type"] == "text") {
captured_output << content["text"].get<std::string>();
std::cout << content["text"].get<std::string>();
}
}
if (!streaming) {
captured_output << std::endl << std::flush;
std::cout << std::endl << std::flush;
} else {
captured_output << std::flush;
std::cout << std::flush;
}
} else if (message["content"]["text"].is_string()) {
if (!streaming) {
captured_output << message["content"]["text"].get<std::string>()
<< std::endl
<< std::flush;
std::cout << message["content"]["text"].get<std::string>() << std::endl
<< std::flush;
} else {
captured_output << message["content"]["text"].get<std::string>()
<< std::flush;
std::cout << message["content"]["text"].get<std::string>() << std::flush;
}
} else {
return absl::InvalidArgumentError("Invalid message: " + message.dump());
}
return absl::OkStatus();
}
Nội dung dữ liệu đa phương thức
// To use multimodality, the engine must be created with vision and audio
// backend depending on the multimodality to be used
auto engine_settings = EngineSettings::CreateDefault(
model_assets,
/*backend=*/litert::lm::Backend::CPU,
/*vision_backend*/litert::lm::Backend::GPU,
/*audio_backend*/litert::lm::Backend::CPU,
);
// The same steps to create Engine and Conversation as above...
// Send message to the LLM with image data.
absl::StatusOr<Message> model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "Describe the following image: "}
},
{
{"type", "image"}, {"path", "/file/path/to/image.jpg"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
// Send message to the LLM with audio data.
model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "Transcribe the audio: "}
},
{
{"type", "audio"}, {"path", "/file/path/to/audio.wav"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
// The content can include multiple image or audio data.
model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "First briefly describe the two images "}
},
{
{"type", "image"}, {"path", "/file/path/to/image1.jpg"}
},
{
{"type", "text"}, {"text", "and "}
},
{
{"type", "image"}, {"path", "/file/path/to/image2.jpg"}
},
{
{"type", "text"}, {"text", " then transcribe the content in the audio"}
},
{
{"type", "audio"}, {"path", "/file/path/to/audio.wav"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
Sử dụng Conversation với Công cụ
Tham khảo phần Sử dụng nâng cao để biết thông tin chi tiết về cách sử dụng công cụ với Conversation API
Các thành phần trong Conversation
Conversation có thể được coi là một đại diện để người dùng duy trì Session và xử lý dữ liệu phức tạp trước khi gửi dữ liệu đến Phiên.
Loại I/O
Định dạng đầu vào và đầu ra cốt lõi cho Conversation API là
Message. Hiện tại, định dạng này được triển khai dưới dạng
JsonMessage, là một bí danh loại cho
ordered_json, một cấu trúc dữ liệu khoá-giá trị lồng ghép linh hoạt.
API Conversation hoạt động dựa trên cơ sở tin nhắn vào-tin nhắn ra, mô phỏng trải nghiệm trò chuyện thông thường. Tính linh hoạt của
Message cho phép người dùng đưa vào các trường tuỳ ý theo yêu cầu của
các mẫu lời nhắc hoặc mô hình LLM cụ thể, cho phép LiteRT-LM hỗ trợ nhiều
mô hình.
Mặc dù không có một tiêu chuẩn cứng nhắc duy nhất, nhưng hầu hết các mẫu và mô hình lời nhắc
mong đợi Message tuân theo các quy ước tương tự như các quy ước được sử dụng trong
Nội dung Gemini API hoặc cấu trúc
Tin nhắn OpenAI.
Message phải chứa role, đại diện cho người gửi tin nhắn. content có thể đơn giản như một chuỗi văn bản.
{
"role": "model", // Represent who the message is sent from.
"content": "Hello World!" // Naive text only content.
}
Đối với dữ liệu đầu vào đa phương thức, content là một danh sách part. Một lần nữa part không phải là cấu trúc dữ liệu được xác định trước mà là một loại dữ liệu cặp khoá-giá trị được sắp xếp. Các trường cụ thể phụ thuộc vào những gì mẫu lời nhắc và mô hình mong đợi.
{
"role": "user",
"content": [ // Multimodal content.
// Now the content is composed of parts
{
"type": "text",
"text": "Describe the image in details: "
},
{
"type": "image",
"path": "/path/to/image.jpg"
}
]
}
Đối với đa phương thức part, chúng tôi hỗ trợ định dạng sau do
data_utils.h xử lý
{
"type": "text",
"text": "this is a text"
}
{
"type": "image",
"path": "/path/to/image.jpg"
}
{
"type": "image",
"blob": "base64 encoded image bytes as string",
}
{
"type": "audio",
"path": "/path/to/audio.wav"
}
{
"type": "audio",
"blob": "base64 encoded audio bytes as string",
}
Prompt Template
Để duy trì tính linh hoạt cho các mô hình biến thể, PromptTemplate được triển khai dưới dạng trình bao bọc mỏng xung quanh Minja. Minja là một cách triển khai C++ của công cụ mẫu Jinja, xử lý dữ liệu đầu vào JSON để tạo lời nhắc được định dạng.
Công cụ mẫu Jinja là một định dạng được áp dụng rộng rãi cho các mẫu lời nhắc LLM. Dưới đây là một số ví dụ:
Định dạng công cụ mẫu Jinja phải hoàn toàn khớp với cấu trúc mà mô hình được điều chỉnh theo hướng dẫn mong đợi. Thông thường, các bản phát hành mô hình bao gồm mẫu Jinja tiêu chuẩn để đảm bảo sử dụng mô hình đúng cách.
Mẫu Jinja mà mô hình sử dụng sẽ được cung cấp bởi siêu dữ liệu tệp mô hình.
Lưu ý: Một thay đổi nhỏ trong câu lệnh do định dạng không chính xác có thể dẫn đến sự suy giảm đáng kể của mô hình. Như đã báo cáo trong Định lượng độ nhạy của Mô hình ngôn ngữ đối với các tính năng giả mạo trong Thiết kế lời nhắc hoặc: Cách tôi học được cách bắt đầu lo lắng về định dạng lời nhắc
Lời nói đầu
Preface đặt bối cảnh ban đầu cho cuộc trò chuyện. Bối cảnh này có thể bao gồm các tin nhắn ban đầu, định nghĩa công cụ và mọi thông tin nền khác mà LLM cần để bắt đầu tương tác. Điều này đạt được chức năng tương tự như
the
Gemini API system instruction
and Gemini API Tools
Lời nói đầu chứa các trường sau
messagesCác tin nhắn trong lời nói đầu. Các tin nhắn cung cấp thông tin nền ban đầu cho cuộc trò chuyện. Ví dụ: các tin nhắn có thể là nhật ký cuộc trò chuyện, hướng dẫn hệ thống thiết kế câu lệnh, ví dụ về few-shot, v.v.toolsCác công cụ mà mô hình có thể sử dụng trong cuộc trò chuyện. Định dạng của các công cụ không cố định, nhưng hầu hết đều tuân theoGemini API FunctionDeclaration.extra_contextBối cảnh bổ sung giúp duy trì khả năng mở rộng cho các mô hình để tuỳ chỉnh thông tin bối cảnh cần thiết nhằm bắt đầu cuộc trò chuyện. Ví dụ:enable_thinkingcho các mô hình có chế độ suy nghĩ, chẳng hạn như Qwen3 hoặc SmolLM3-3B.
Ví dụ về lời nói đầu để cung cấp hướng dẫn hệ thống ban đầu, các công cụ và tắt chế độ suy nghĩ.
Preface preface = JsonPreface({
.messages = {
{"role", "system"},
{"content", {"You are a model that can do function calling."}}
},
.tools = {
{
{"name", "get_weather"},
{"description", "Returns the weather for a given location."},
{"parameters", {
{"type", "object"},
{"properties", {
{"location", {
{"type", "string"},
{"description", "The location to get the weather for."}
}}
}},
{"required", {"location"}}
}}
},
{
{"name", "get_stock_price"},
{"description", "Returns the stock price for a given stock symbol."},
{"parameters", {
{"type", "object"},
{"properties", {
{"stock_symbol", {
{"type", "string"},
{"description", "The stock symbol to get the price for."}
}}
}},
{"required", {"stock_symbol"}}
}}
}
},
.extra_context = {
{"enable_thinking": false}
}
});
Cập nhật trước đây
Conversation duy trì danh sách tất cả các trao đổi Tin nhắn trong phiên. Nhật ký này rất quan trọng đối với việc hiển thị mẫu lời nhắc, vì mẫu lời nhắc jinja thường yêu cầu toàn bộ nhật ký cuộc trò chuyện để tạo lời nhắc chính xác cho LLM.
Tuy nhiên, Phiên LiteRT-LM có trạng thái, nghĩa là phiên này xử lý dữ liệu đầu vào theo từng bước. Để thu hẹp khoảng cách này, Conversation tạo lời nhắc gia tăng cần thiết bằng cách hiển thị mẫu lời nhắc hai lần: một lần với nhật ký cho đến lượt trước và một lần bao gồm tin nhắn hiện tại. Bằng cách so sánh hai lời nhắc được hiển thị này, hệ thống sẽ trích xuất phần mới để gửi đến Phiên.
ConversationConfig
ConversationConfig được dùng để khởi chạy một
Conversation thực thể. Bạn có thể tạo cấu hình này theo một vài cách:
- Từ
Engine: Phương thức này sử dụngSessionConfigmặc định được liên kết với công cụ. - Từ một
SessionConfigcụ thể: Điều này cho phép kiểm soát chi tiết hơn đối với các chế độ cài đặt phiên.
Ngoài các chế độ cài đặt phiên, bạn có thể tuỳ chỉnh thêm hành vi
Conversation trong
ConversationConfig. Nội dung như vậy bao gồm:
- Cung cấp
Preface. - Ghi đè mặc định
PromptTemplate. - Ghi đè
DataProcessorConfigmặc định.
Các ghi đè này đặc biệt hữu ích cho các mô hình được tinh chỉnh, có thể yêu cầu các cấu hình hoặc mẫu lời nhắc khác với mô hình cơ sở mà chúng được lấy từ đó.
MessageCallback
MessageCallback là hàm callback mà người dùng nên triển khai khi sử dụng phương thức SendMessageAsync không đồng bộ.
Chữ ký gọi lại là absl::AnyInvocable<void(absl::StatusOr<Message>)>.
Hàm này được kích hoạt trong các điều kiện sau:
- Khi một đoạn
Messagemới được nhận từ Mô hình. - Nếu xảy ra lỗi trong quá trình xử lý tin nhắn của LiteRT-LM.
- Sau khi hoàn tất quá trình suy luận của LLM, lệnh gọi lại sẽ được kích hoạt bằng một
trống
Message(ví dụ:JsonMessage()) để báo hiệu kết thúc phản hồi.
Tham khảo lệnh gọi không đồng bộ Bước 6 để biết ví dụ về cách triển khai.
Lưu ý: Message mà lệnh gọi lại nhận được chỉ chứa đoạn mới nhất của đầu ra mô hình, chứ không phải toàn bộ nhật ký tin nhắn.
Ví dụ: nếu phản hồi hoàn chỉnh của mô hình dự kiến từ lệnh gọi chặn
SendMessage sẽ là:
{
"role": "model",
"content": [
"type": "text",
"text": "Hello World!"
]
}
Lệnh gọi lại trong SendMessageAsync có thể được gọi nhiều
lần, mỗi lần với một đoạn văn bản tiếp theo:
// 1st Message
{
"role": "model",
"content": [
"type": "text",
"text": "He"
]
}
// 2nd Message
{
"role": "model",
"content": [
"type": "text",
"text": "llo"
]
}
// 3rd Message
{
"role": "model",
"content": [
"type": "text",
"text": " Wo"
]
}
// 4th Message
{
"role": "model",
"content": [
"type": "text",
"text": "rl"
]
}
// 5th Message
{
"role": "model",
"content": [
"type": "text",
"text": "d!"
]
}
Người triển khai chịu trách nhiệm tích luỹ các đoạn này nếu cần phản hồi hoàn chỉnh trong luồng không đồng bộ. Ngoài ra, phản hồi đầy đủ
sẽ có sẵn dưới dạng mục nhập cuối cùng trong History sau khi
hoàn tất lệnh gọi không đồng bộ.
Sử dụng nâng cao {#advanced-usage}
Giải mã bị ràng buộc
LiteRT-LM hỗ trợ giải mã bị ràng buộc, cho phép bạn thực thi các cấu trúc cụ thể trên đầu ra của mô hình, chẳng hạn như lược đồ JSON, mẫu biểu thức chính quy hoặc quy tắc ngữ pháp.
Để bật tính năng này, hãy đặt EnableConstrainedDecoding(true) trong ConversationConfig và cung cấp ConstraintProviderConfig (ví dụ: LlGuidanceConfig để hỗ trợ biểu thức chính quy/JSON/ngữ pháp). Sau đó, hãy truyền các ràng buộc thông qua OptionalArgs trong SendMessage.
Ví dụ: Ràng buộc biểu thức chính quy
LlGuidanceConstraintArg constraint_arg;
constraint_arg.constraint_type = LlgConstraintType::kRegex;
constraint_arg.constraint_string = "a+b+"; // Force output to match this regex
auto response = conversation->SendMessage(
user_message,
{.decoding_constraint = constraint_arg}
);
Để biết thông tin chi tiết đầy đủ, bao gồm cả tính năng hỗ trợ Lược đồ JSON và Ngữ pháp Lark, hãy xem tài liệu Giải mã bị ràng buộc.
Sử dụng công cụ
Tính năng gọi công cụ cho phép LLM yêu cầu thực thi các hàm phía máy khách. Bạn xác định các công cụ trong Preface của cuộc trò chuyện, đặt khoá cho các công cụ đó theo tên. Khi mô hình xuất lệnh gọi công cụ, bạn sẽ nắm bắt lệnh gọi đó, thực thi hàm tương ứng trong ứng dụng và trả về kết quả cho mô hình.
Luồng cấp cao:
- Khai báo công cụ: Xác định các công cụ (tên, nội dung mô tả, tham số) trong JSON
Preface. - Phát hiện lệnh gọi: Kiểm tra
model_message["tool_calls"]trong phản hồi. - Thực thi: Chạy logic ứng dụng cho công cụ được yêu cầu.
- Phản hồi: Gửi một tin nhắn có
role: "tool"chứa đầu ra của công cụ trở lại mô hình.
Để biết thông tin chi tiết đầy đủ và ví dụ hoàn chỉnh về vòng lặp trò chuyện, hãy xem tài liệu Sử dụng công cụ.