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. Nó 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ì ngữ 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 câu lệnh Jinja với định dạng thông báo dựa trên vai trò.
Quy trình API Conversation
Vòng đời điển hình để sử dụng Conversation API là:
- Tạo một
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 có trọng lượng lớn, lưu giữ trọng số của 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, nhờ đó 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 dùng phương thức này trong hầu hết các trường hợp sử dụng. Nền tảng này phản ánh Gemini Chat API.
SendMessage: Một lệnh gọi chặn nhận dữ liệu đầ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 mã thông báo phản hồi của mô hình theo từng mã thông báo thông qua các lệnh gọi lại.
Sau đâ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 tính năng Trò chuyện với các công cụ
Vui lòng tham khảo phần Cách 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 cuộc trò chuyện
Conversation có thể được coi là một đại biểu cho 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.
Các loại I/O
Định dạng đầu vào và đầu ra chính cho Conversation API là Message. Hiện tại, điều 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 nhau linh hoạt.
API Conversation hoạt động dựa trên cơ chế gửi tin nhắn và nhận tin nhắn, 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 các trường tuỳ ý vào khi cần theo các mẫu câu lệnh hoặc mô hình LLM cụ thể, cho phép LiteRT-LM hỗ trợ nhiều loại mô hình.
Mặc dù không có một tiêu chuẩn duy nhất và cố định, nhưng hầu hết các mẫu và mô hình câu lệnh đều mong đợi Message tuân theo các quy ước tương tự như quy ước được dùng trong Nội dung của Gemini API hoặc Cấu trúc thông báo của OpenAI.
Message phải chứa role, cho biết 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à danh sách part. Một lần nữa, part không phải là một cấu trúc dữ liệu được xác định trước mà là một kiểu dữ liệu cặp khoá-giá trị có thứ tự. Các trường cụ thể phụ thuộc vào những gì mà mẫu câu lệnh 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 part đa phương thức, 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",
}
Mẫu câu lệnh
Để 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 một 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 ra các câu lệnh được định dạng.
Công cụ mẫu Jinja là một định dạng được sử dụng rộng rãi cho các mẫu câu lệnh 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 tinh chỉnh theo hướng dẫn dự kiến. Thông thường, các bản phát hành mô hình sẽ 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ẽ do siêu dữ liệu tệp mô hình cung cấp.
[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ể về 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 đặc điểm giả tạo trong thiết kế câu lệnh hoặc: Cách tôi học được cách bắt đầu lo lắng về định dạng câu lệnh
Lời nói đầu
Preface đặt bối cảnh ban đầu cho cuộc trò chuyện. Nội dung này có thể bao gồm các thông báo ban đầu, định nghĩa công cụ và mọi thông tin cơ bản khác mà LLM cần để bắt đầu tương tác. Điều này giúp đạt được chức năng tương tự như Gemini API system instruction và Gemini API Tools
Lời nói đầu chứa các trường sau
messagesCác thông báo trong phần lời nói đầu. Các tin nhắn này cung cấp bối cảnh ban đầu cho cuộc trò chuyện. Ví dụ: các tin nhắn có thể là nhật ký trò chuyện, hướng dẫn hệ thống kỹ thuật tạo câu lệnh, ví dụ minh hoạ vài lần, 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ụ này cũng 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 bắt buộc nhằm bắt đầu một cuộc trò chuyện. Ví dụ:enable_thinkingcho các mô hình có chế độ tư duy, ví dụ: Qwen3 hoặc SmolLM3-3B.
Ví dụ về lời nói đầu để cung cấp hướng dẫn ban đầu cho hệ thống, các công cụ và tắt chế độ tư duy.
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 lượt trao đổi Message trong phiên. Nhật ký này rất quan trọng đối với việc hiển thị mẫu câu lệnh, vì mẫu câu lệnh jinja thường yêu cầu toàn bộ nhật ký trò chuyện để tạo câu lệnh 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ý các đầu vào theo gia số. Để khắc phục khoảng trống này, Conversation sẽ tạo câu lệnh gia tăng cần thiết bằng cách hiển thị mẫu câu lệnh 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 cả thông báo hiện tại. Bằng cách so sánh hai câu lệnh được kết xuất 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 thực thể Conversation. Bạn có thể tạo cấu hình này theo một số 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ể: Lựa chọn này giúp bạn kiểm soát chế độ cài đặt phiên một cách chi tiết hơn.
Ngoài 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 đè
PromptTemplatemặc định. - Ghi đè
DataProcessorConfigmặc định.
Các thao tác ghi đè này đặc biệt hữu ích đối với 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 câu lệnh khác với mô hình cơ sở mà chúng được lấy từ đó.
MessageCallback
MessageCallback là hàm gọi lại 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ý của lệnh gọi lại là absl::AnyInvocable<void(absl::StatusOr<Message>)>.
Hàm này được kích hoạt trong các trường hợp 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ý thông báo của LiteRT-LM.
- Sau khi quá trình suy luận của LLM hoàn tất, lệnh gọi lại sẽ được kích hoạt bằng một
Messagetrống (ví dụ:JsonMessage()) để báo hiệu phần cuối của phản hồi.
Hãy tham khảo Lệnh gọi không đồng bộ ở Bước 6 để biết ví dụ về cách triển khai.
[!IMPORTANT]
Messagemà lệnh gọi lại nhận được chỉ chứa đoạn đầu ra mới nhất của 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ừ một lệnh gọi SendMessage chặn 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 khối này nếu cần phản hồi đầy đủ 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 cuối cùng trong History sau khi lệnh gọi không đồng bộ hoàn tất.
Cách sử dụng nâng cao
Giải mã bị hạn chế
LiteRT-LM hỗ trợ hoạt động giải mã có 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ư giản đồ JSON, mẫu Regex 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 một ConstraintProviderConfig (ví dụ: LlGuidanceConfig để hỗ trợ biểu thức chính quy/JSON/ngữ pháp). Sau đó, hãy truyền các quy tắc ràng buộc thông qua OptionalArgs trong SendMessage.
Ví dụ: Regex Constraint
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 đầy đủ, bao gồm cả hỗ trợ Cú pháp Lark và Sơ đồ JSON, hãy xem tài liệu về Giải mã bị hạn chế.
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 tên cho các công cụ đó. Khi mô hình xuất mộ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 của mình và trả về kết quả cho mô hình.
Luồng cấp cao:
1. Khai báo công cụ: Xác định công cụ (tên, nội dung mô tả, tham số) trong JSON Preface.
2. Phát hiện cuộc gọi: Kiểm tra model_message["tool_calls"] trong câu trả lời.
3. Thực thi: Chạy logic ứng dụng cho công cụ được yêu cầu.
4. Trả lời: Gửi một thông báo có role: "tool" chứa đầu ra của công cụ trở lại mô hình.
Để biết thông tin đầy đủ và ví dụ hoàn chỉnh về vòng lặp trò chuyện, hãy xem tài liệu về việc sử dụng công cụ.