Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Waiting to run
Windows CI / Windows (push) Waiting to run
Build Docs / Deploy Docs (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

544 lines
19 KiB
C++

/*!
* Copyright (c) 2023-2025 by Contributors
* \file json_ffi/openai_api_protocol.cc
* \brief The implementation of OpenAI API Protocol in MLC LLM.
*/
#include "openai_api_protocol.h"
#include "../support/json_parser.h"
namespace mlc {
namespace llm {
namespace json_ffi {
Result<ChatFunction> ChatFunction::FromJSON(const tvm::ffi::json::Object& json_obj) {
using TResult = Result<ChatFunction>;
ChatFunction chat_func;
// description
Result<std::optional<std::string>> description_res =
json::LookupOptionalWithResultReturn<std::string>(json_obj, "description");
if (description_res.IsErr()) {
return TResult::Error(description_res.UnwrapErr());
}
chat_func.description = description_res.Unwrap();
// name
Result<std::string> name_res = json::LookupWithResultReturn<std::string>(json_obj, "name");
if (name_res.IsErr()) {
return TResult::Error(name_res.UnwrapErr());
}
chat_func.name = name_res.Unwrap();
// parameters
Result<tvm::ffi::json::Object> parameters_obj_res =
json::LookupWithResultReturn<tvm::ffi::json::Object>(json_obj, "parameters");
if (parameters_obj_res.IsErr()) {
return TResult::Error(parameters_obj_res.UnwrapErr());
}
tvm::ffi::json::Object parameters_obj = parameters_obj_res.Unwrap();
chat_func.parameters.reserve(parameters_obj.size());
for (const auto& [key, value] : parameters_obj) {
chat_func.parameters[key.cast<tvm::ffi::String>()] = tvm::ffi::json::Stringify(value);
}
return TResult::Ok(chat_func);
}
tvm::ffi::json::Object ChatFunction::AsJSON() const {
tvm::ffi::json::Object obj;
if (this->description.has_value()) {
obj.Set("description", this->description.value());
}
obj.Set("name", this->name);
tvm::ffi::json::Object parameters_obj;
for (const auto& pair : this->parameters) {
parameters_obj.Set(pair.first, pair.second);
}
obj.Set("parameters", parameters_obj);
return obj;
}
Result<ChatTool> ChatTool::FromJSON(const tvm::ffi::json::Object& json_obj) {
using TResult = Result<ChatTool>;
ChatTool chatTool;
// function
Result<tvm::ffi::json::Object> function_obj_res =
json::LookupWithResultReturn<tvm::ffi::json::Object>(json_obj, "function");
if (function_obj_res.IsErr()) {
return TResult::Error(function_obj_res.UnwrapErr());
}
Result<ChatFunction> function = ChatFunction::FromJSON(function_obj_res.Unwrap());
if (function.IsErr()) {
return TResult::Error(function.UnwrapErr());
}
chatTool.function = function.Unwrap();
return TResult::Ok(chatTool);
}
tvm::ffi::json::Object ChatTool::AsJSON() const {
tvm::ffi::json::Object obj;
obj.Set("type", "function");
obj.Set("function", this->function.AsJSON());
return obj;
}
Result<ChatFunctionCall> ChatFunctionCall::FromJSON(const tvm::ffi::json::Object& json_obj) {
using TResult = Result<ChatFunctionCall>;
ChatFunctionCall chat_func_call;
// name
Result<std::string> name_res = json::LookupWithResultReturn<std::string>(json_obj, "name");
if (name_res.IsErr()) {
return TResult::Error(name_res.UnwrapErr());
}
chat_func_call.name = name_res.Unwrap();
// arguments
Result<std::optional<tvm::ffi::json::Object>> arguments_obj_res =
json::LookupOptionalWithResultReturn<tvm::ffi::json::Object>(json_obj, "arguments");
if (arguments_obj_res.IsErr()) {
return TResult::Error(arguments_obj_res.UnwrapErr());
}
std::optional<tvm::ffi::json::Object> arguments_obj = arguments_obj_res.Unwrap();
if (arguments_obj.has_value()) {
std::unordered_map<std::string, std::string> arguments;
arguments.reserve(arguments_obj.value().size());
for (const auto& [key, value] : arguments_obj.value()) {
arguments[key.cast<tvm::ffi::String>()] = tvm::ffi::json::Stringify(value);
}
chat_func_call.arguments = std::move(arguments);
}
return TResult::Ok(chat_func_call);
}
tvm::ffi::json::Object ChatFunctionCall::AsJSON() const {
tvm::ffi::json::Object obj;
tvm::ffi::json::Object arguments_obj;
if (this->arguments.has_value()) {
for (const auto& pair : this->arguments.value()) {
arguments_obj.Set(pair.first, pair.second);
}
obj.Set("arguments", arguments_obj);
}
obj.Set("name", this->name);
return obj;
}
Result<ChatToolCall> ChatToolCall::FromJSON(const tvm::ffi::json::Object& json_obj) {
using TResult = Result<ChatToolCall>;
ChatToolCall chat_tool_call;
// function
Result<tvm::ffi::json::Object> function_obj_res =
json::LookupWithResultReturn<tvm::ffi::json::Object>(json_obj, "function");
if (function_obj_res.IsErr()) {
return TResult::Error(function_obj_res.UnwrapErr());
}
Result<ChatFunctionCall> function_res = ChatFunctionCall::FromJSON(function_obj_res.Unwrap());
if (function_res.IsErr()) {
return TResult::Error(function_res.UnwrapErr());
}
chat_tool_call.function = function_res.Unwrap();
// overwrite default id
Result<std::optional<std::string>> id_res =
json::LookupOptionalWithResultReturn<std::string>(json_obj, "id");
if (id_res.IsErr()) {
return TResult::Error(id_res.UnwrapErr());
}
std::optional<std::string> id = id_res.UnwrapErr();
if (id.has_value()) {
chat_tool_call.id = id.value();
}
return TResult::Ok(chat_tool_call);
}
tvm::ffi::json::Object ChatToolCall::AsJSON() const {
tvm::ffi::json::Object obj;
obj.Set("id", this->id);
obj.Set("function", this->function.AsJSON());
obj.Set("type", "function");
return obj;
}
Result<ChatCompletionMessage> ChatCompletionMessage::FromJSON(
const tvm::ffi::json::Object& json_obj) {
using TResult = Result<ChatCompletionMessage>;
ChatCompletionMessage message;
ChatCompletionMessageContent content;
// content
if (json_obj.count("content") == 0) {
return TResult::Error("ValueError: key \"content\" not found in the chat completion.");
}
tvm::ffi::json::Value content_val = json_obj.at("content");
if (content_val.try_cast<std::string>().has_value()) {
content = content_val.cast<std::string>();
} else if (content_val == nullptr) {
// skip
} else {
// most complicated case
std::vector<std::unordered_map<std::string, std::string>> parts;
Result<tvm::ffi::json::Array> content_arr_res =
json::LookupWithResultReturn<tvm::ffi::json::Array>(json_obj, "content");
if (content_arr_res.IsErr()) {
return TResult::Error(content_arr_res.UnwrapErr());
}
tvm::ffi::json::Array content_arr = content_arr_res.Unwrap();
for (const auto& item : content_arr) {
if (!item.try_cast<tvm::ffi::json::Object>().has_value()) {
return TResult::Error("The content of chat completion message is not an object");
}
tvm::ffi::json::Object item_obj = item.cast<tvm::ffi::json::Object>();
std::unordered_map<std::string, std::string> item_map;
for (const auto& [key, value] : item_obj) {
item_map[key.cast<tvm::ffi::String>()] = tvm::ffi::json::Stringify(value);
}
parts.push_back(std::move(item_map));
}
content = parts;
}
message.content = content;
// role
Result<std::string> role_str_res = json::LookupWithResultReturn<std::string>(json_obj, "role");
if (role_str_res.IsErr()) {
return TResult::Error(role_str_res.UnwrapErr());
}
std::string role_str = role_str_res.Unwrap();
if (role_str == "system" || role_str == "user" || role_str == "assistant" || role_str == "tool") {
message.role = role_str;
} else {
return TResult::Error("Invalid role in chat completion message: " + role_str);
}
// name
Result<std::optional<std::string>> name_res =
json::LookupOptionalWithResultReturn<std::string>(json_obj, "name");
if (name_res.IsErr()) {
return TResult::Error(name_res.UnwrapErr());
}
message.name = name_res.Unwrap();
// tool calls
Result<std::optional<tvm::ffi::json::Array>> tool_calls_arr_res =
json::LookupOptionalWithResultReturn<tvm::ffi::json::Array>(json_obj, "tool_calls");
if (tool_calls_arr_res.IsErr()) {
return TResult::Error(tool_calls_arr_res.UnwrapErr());
}
std::optional<tvm::ffi::json::Array> tool_calls_arr = tool_calls_arr_res.Unwrap();
if (tool_calls_arr.has_value()) {
std::vector<ChatToolCall> tool_calls;
tool_calls.reserve(tool_calls_arr.value().size());
for (const auto& item : tool_calls_arr.value()) {
if (!item.try_cast<tvm::ffi::json::Object>().has_value()) {
return TResult::Error("A tool call item in the chat completion message is not an object");
}
Result<ChatToolCall> tool_call = ChatToolCall::FromJSON(item.cast<tvm::ffi::json::Object>());
if (tool_call.IsErr()) {
return TResult::Error(tool_call.UnwrapErr());
}
tool_calls.push_back(tool_call.Unwrap());
}
message.tool_calls = tool_calls;
}
// tool call id
Result<std::optional<std::string>> tool_call_id_res =
json::LookupOptionalWithResultReturn<std::string>(json_obj, "tool_call_id");
if (tool_call_id_res.IsErr()) {
return TResult::Error(tool_call_id_res.UnwrapErr());
}
message.tool_call_id = tool_call_id_res.Unwrap();
return TResult::Ok(message);
}
Result<ChatCompletionRequest> ChatCompletionRequest::FromJSON(const std::string& json_str) {
using TResult = Result<ChatCompletionRequest>;
Result<tvm::ffi::json::Object> json_obj_res = json::ParseToJSONObjectWithResultReturn(json_str);
if (json_obj_res.IsErr()) {
return TResult::Error(json_obj_res.UnwrapErr());
}
tvm::ffi::json::Object json_obj = json_obj_res.Unwrap();
ChatCompletionRequest request;
// messages
Result<tvm::ffi::json::Array> messages_arr_res =
json::LookupWithResultReturn<tvm::ffi::json::Array>(json_obj, "messages");
if (messages_arr_res.IsErr()) {
return TResult::Error(messages_arr_res.UnwrapErr());
}
std::vector<ChatCompletionMessage> messages;
tvm::ffi::json::Array messages_arr = messages_arr_res.Unwrap();
for (const auto& item : messages_arr) {
if (!item.try_cast<tvm::ffi::json::Object>().has_value()) {
return TResult::Error("A message in chat completion request is not object");
}
tvm::ffi::json::Object item_obj = item.cast<tvm::ffi::json::Object>();
Result<ChatCompletionMessage> message = ChatCompletionMessage::FromJSON(item_obj);
if (message.IsErr()) {
return TResult::Error(message.UnwrapErr());
}
messages.push_back(message.Unwrap());
}
request.messages = messages;
// model
Result<std::optional<std::string>> model_res =
json::LookupOptionalWithResultReturn<std::string>(json_obj, "model");
if (model_res.IsErr()) {
return TResult::Error(model_res.UnwrapErr());
}
request.model = model_res.Unwrap();
// temperature
Result<std::optional<double>> temperature_res =
json::LookupOptionalWithResultReturn<double>(json_obj, "temperature");
if (temperature_res.IsErr()) {
return TResult::Error(temperature_res.UnwrapErr());
}
request.temperature = temperature_res.Unwrap();
// top_p
Result<std::optional<double>> top_p_res =
json::LookupOptionalWithResultReturn<double>(json_obj, "top_p");
if (top_p_res.IsErr()) {
return TResult::Error(top_p_res.UnwrapErr());
}
request.top_p = top_p_res.Unwrap();
// max_tokens
Result<std::optional<int64_t>> max_tokens_res =
json::LookupOptionalWithResultReturn<int64_t>(json_obj, "max_tokens");
if (max_tokens_res.IsErr()) {
return TResult::Error(max_tokens_res.UnwrapErr());
}
request.max_tokens = max_tokens_res.Unwrap();
// n
Result<int64_t> n_res = json::LookupOrDefaultWithResultReturn<int64_t>(json_obj, "n", 1);
if (n_res.IsErr()) {
return TResult::Error(n_res.UnwrapErr());
}
request.n = n_res.Unwrap();
// frequency_penalty
Result<std::optional<double>> frequency_penalty_res =
json::LookupOptionalWithResultReturn<double>(json_obj, "frequency_penalty");
if (frequency_penalty_res.IsErr()) {
return TResult::Error(frequency_penalty_res.UnwrapErr());
}
request.frequency_penalty = frequency_penalty_res.Unwrap();
// presence_penalty
Result<std::optional<double>> presence_penalty_res =
json::LookupOptionalWithResultReturn<double>(json_obj, "presence_penalty");
if (presence_penalty_res.IsErr()) {
return TResult::Error(presence_penalty_res.UnwrapErr());
}
request.presence_penalty = presence_penalty_res.Unwrap();
// seed
Result<std::optional<int64_t>> seed_res =
json::LookupOptionalWithResultReturn<int64_t>(json_obj, "seed");
if (seed_res.IsErr()) {
return TResult::Error(seed_res.UnwrapErr());
}
request.seed = seed_res.Unwrap();
// stop strings
Result<std::optional<tvm::ffi::json::Array>> stop_strs_res =
json::LookupOptionalWithResultReturn<tvm::ffi::json::Array>(json_obj, "stop");
if (stop_strs_res.IsErr()) {
return TResult::Error(stop_strs_res.UnwrapErr());
}
std::optional<tvm::ffi::json::Array> stop_strs = stop_strs_res.Unwrap();
if (stop_strs.has_value()) {
std::vector<std::string> stop;
for (const auto& stop_str_value : stop_strs.value()) {
if (!stop_str_value.try_cast<std::string>().has_value()) {
return TResult::Error("One given value in field \"stop\" is not a string.");
}
stop.push_back(stop_str_value.cast<std::string>());
}
request.stop = std::move(stop);
}
// tool_choice
Result<std::string> tool_choice_res =
json::LookupOrDefaultWithResultReturn<std::string>(json_obj, "tool_choice", "auto");
if (tool_choice_res.IsErr()) {
return TResult::Error(tool_choice_res.UnwrapErr());
}
request.tool_choice = tool_choice_res.Unwrap();
// tools
Result<std::optional<tvm::ffi::json::Array>> tools_arr_res =
json::LookupOptionalWithResultReturn<tvm::ffi::json::Array>(json_obj, "tools");
if (tool_choice_res.IsErr()) {
return TResult::Error(tool_choice_res.UnwrapErr());
}
std::optional<tvm::ffi::json::Array> tools_arr = tools_arr_res.Unwrap();
if (tools_arr.has_value()) {
std::vector<ChatTool> tools;
tools.reserve(tools_arr.value().size());
for (const auto& item : tools_arr.value()) {
if (!item.try_cast<tvm::ffi::json::Object>().has_value()) {
return TResult::Error("A tool of the chat completion request is not an object");
}
Result<ChatTool> tool = ChatTool::FromJSON(item.cast<tvm::ffi::json::Object>());
if (tool.IsErr()) {
return TResult::Error(tool.UnwrapErr());
}
tools.push_back(tool.Unwrap());
}
request.tools = tools;
}
// response format
std::optional<tvm::ffi::json::Object> response_format_obj =
json::LookupOptional<tvm::ffi::json::Object>(json_obj, "response_format");
if (response_format_obj.has_value()) {
Result<ResponseFormat> response_format_res =
ResponseFormat::FromJSON(response_format_obj.value());
if (response_format_res.IsErr()) {
return TResult::Error(response_format_res.UnwrapErr());
}
request.response_format = response_format_res.Unwrap();
}
// debug_config
Result<std::optional<tvm::ffi::json::Object>> debug_config_opt_res =
json::LookupOptionalWithResultReturn<tvm::ffi::json::Object>(json_obj, "debug_config");
if (debug_config_opt_res.IsErr()) {
return TResult::Error(debug_config_opt_res.UnwrapErr());
}
auto debug_config_opt = debug_config_opt_res.Unwrap();
if (debug_config_opt.has_value()) {
Result<DebugConfig> debug_config_res = DebugConfig::FromJSON(debug_config_opt.value());
if (debug_config_res.IsErr()) {
return TResult::Error(debug_config_res.UnwrapErr());
}
request.debug_config = debug_config_res.Unwrap();
}
// TODO: Other parameters
return TResult::Ok(request);
}
tvm::ffi::json::Object ChatCompletionMessage::AsJSON() const {
tvm::ffi::json::Object obj;
if (this->content.IsText()) {
obj.Set("content", this->content.Text());
} else if (this->content.IsParts()) {
tvm::ffi::json::Array content_arr;
for (const auto& item : this->content.Parts()) {
tvm::ffi::json::Object item_obj;
for (const auto& pair : item) {
item_obj.Set(pair.first, pair.second);
}
content_arr.push_back(item_obj);
}
obj.Set("content", content_arr);
}
obj.Set("role", this->role);
if (this->name.has_value()) {
obj.Set("name", this->name.value());
}
if (this->tool_call_id.has_value()) {
obj.Set("tool_call_id", this->tool_call_id.value());
}
if (this->tool_calls.has_value()) {
tvm::ffi::json::Array tool_calls_arr;
for (const auto& tool_call : this->tool_calls.value()) {
tool_calls_arr.push_back(tool_call.AsJSON());
}
obj.Set("tool_calls", tool_calls_arr);
}
return obj;
}
tvm::ffi::json::Object ChatCompletionResponseChoice::AsJSON() const {
tvm::ffi::json::Object obj;
if (!this->finish_reason.has_value()) {
obj.Set("finish_reason", nullptr);
} else {
if (this->finish_reason == FinishReason::stop) {
obj.Set("finish_reason", "stop");
} else if (this->finish_reason == FinishReason::length) {
obj.Set("finish_reason", "length");
} else if (this->finish_reason == FinishReason::tool_calls) {
obj.Set("finish_reason", "tool_calls");
} else if (this->finish_reason == FinishReason::error) {
obj.Set("finish_reason", "error");
}
}
obj.Set("index", static_cast<int64_t>(this->index));
obj.Set("message", this->message.AsJSON());
return obj;
}
tvm::ffi::json::Object ChatCompletionStreamResponseChoice::AsJSON() const {
tvm::ffi::json::Object obj;
if (!this->finish_reason.has_value()) {
obj.Set("finish_reason", nullptr);
} else {
if (this->finish_reason.value() == FinishReason::stop) {
obj.Set("finish_reason", "stop");
} else if (this->finish_reason.value() == FinishReason::length) {
obj.Set("finish_reason", "length");
} else if (this->finish_reason.value() == FinishReason::tool_calls) {
obj.Set("finish_reason", "tool_calls");
} else if (this->finish_reason.value() == FinishReason::error) {
obj.Set("finish_reason", "error");
}
}
obj.Set("index", static_cast<int64_t>(this->index));
obj.Set("delta", this->delta.AsJSON());
return obj;
}
tvm::ffi::json::Object ChatCompletionResponse::AsJSON() const {
tvm::ffi::json::Object obj;
obj.Set("id", this->id);
tvm::ffi::json::Array choices_arr;
for (const auto& choice : this->choices) {
choices_arr.push_back(choice.AsJSON());
}
obj.Set("choices", choices_arr);
obj.Set("created", static_cast<int64_t>(this->created));
obj.Set("model", this->model);
obj.Set("system_fingerprint", this->system_fingerprint);
obj.Set("object", this->object);
return obj;
}
tvm::ffi::json::Object ChatCompletionStreamResponse::AsJSON() const {
tvm::ffi::json::Object obj;
obj.Set("id", this->id);
tvm::ffi::json::Array choices_arr;
for (const auto& choice : this->choices) {
choices_arr.push_back(choice.AsJSON());
}
obj.Set("choices", choices_arr);
obj.Set("created", static_cast<int64_t>(this->created));
obj.Set("model", this->model);
obj.Set("system_fingerprint", this->system_fingerprint);
obj.Set("object", this->object);
if (usage.has_value()) {
obj.Set("usage", usage.value());
}
return obj;
}
} // namespace json_ffi
} // namespace llm
} // namespace mlc