-
Notifications
You must be signed in to change notification settings - Fork 217
Support for multipart requests, new ways of routing the request to MP graph #3250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
src/BUILD
Outdated
@@ -810,6 +816,7 @@ cc_library( | |||
deps = [ | |||
"@com_github_tencent_rapidjson//:rapidjson", | |||
"libovmsclient_connection", | |||
"libovmsmulti_part_parser", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why split multi and part? How about naming it libovmsmultipart_parser
I can see such split is made in other places as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, also removing ovms
from name per @atobiszei request
@@ -0,0 +1,49 @@ | |||
//***************************************************************************** | |||
// Copyright 2024 Intel Corporation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Copyright 2024 Intel Corporation | |
// Copyright 2025 Intel Corporation |
return this->parser->getParameter<std::string>(name); | ||
} | ||
|
||
std::string_view DrogonMultiPartParser::getFileContentByName(const std::string& name) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work based on extension? I wonder how much can we trust the name in establishing the content type.
Yet I'm not sure if we should dive into this here since we use third party parser.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not file name, but field name. it is not clear from method name, will change that to getFileContentByFieldName
src/http_rest_api_handler.cpp
Outdated
if (isMultiPart) { | ||
OVMS_PROFILE_SCOPE("multipart parse"); | ||
if (!multiPartParser->parse()) { | ||
return StatusCode::REST_INVALID_URL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add logger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is no specific logger for http component, however, I will add specific status codes
src/http_rest_api_handler.cpp
Outdated
SPDLOG_DEBUG("Model name from deduced from MultiPart field: {}", model_name); | ||
} | ||
// Set json parser in invalid state in order to get HasParseError to respond with true | ||
doc->Parse("error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to rename doc
to jsonParser
? What do you think about such idea?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is already named parsedJson
in HttpPayload class, will name it similarly here
src/http_rest_api_handler.cpp
Outdated
if (uri.size() <= 4) { // nothing after "/v3/..." | ||
return StatusCode::REST_INVALID_URL; | ||
} | ||
model_name = std::string(uri.substr(4)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we assume model name comes right after /v3/
.
Don't we want to nest it a bit like /v3/models/<model_name>
or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think its good idea, with this we wont be able to support OpenAI image variation API:
https://platform.openai.com/docs/api-reference/images/createVariation
src/http_rest_api_handler.cpp
Outdated
@@ -838,11 +879,11 @@ Status HttpRestApiHandler::processRequest( | |||
|
|||
headers->clear(); | |||
response->clear(); | |||
headers->push_back({"Content-Type", "application/json"}); | |||
(*headers)["content-type"] = "application/json"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this line still be here since we can now support other content types and here we do hard assignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very good catch. all our tests use dispatchToProcessor directly, therefore we never noticed this overwrite! will remove it
ovms::StatusCode::OK); | ||
|
||
std::string expectedResponse = R"(/v3/v1/completions/ | ||
|
||
content-typeapplication/json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, the calculator concatenates all headers keys and values so we can test the final output
src/http_rest_api_handler.cpp
Outdated
if (!doc->IsObject()) { | ||
return Status(StatusCode::JSON_INVALID, "JSON body must be an object"); | ||
} | ||
std::string model_name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::string model_name; | |
std::string modelName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
}); | ||
|
||
ASSERT_EQ( | ||
handler->dispatchToProcessor("/v3/v1/completions/", requestBody, &response, comp, responseComponents, writer, multiPartParser), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using /v3/v1/completions
here is a bit confusing to me. It's pretty much OpenAI dedicated URI and here it's used for completely different thing. If I understand it correctly you could use any other v3
uri like /v3/test/multipart
and it would work since model name is in the content right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only a test and does not matter what URI we pass. i can change it to any but multipart. this test ensures that we route not based on URI, but rather multipart model field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will change to any URI and comment
src/BUILD
Outdated
local_defines = COMMON_LOCAL_DEFINES, | ||
copts = COPTS_ADJUSTED, | ||
linkopts = LINKOPTS_ADJUSTED, | ||
alwayslink = 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it wasnt needed, removed that
bool parse() override; | ||
|
||
bool hasParseError() const override; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment that it returns empty in case of error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
@@ -49,6 +49,7 @@ cc_library( | |||
"streaming_test_calculator.cpp", | |||
"two_input_calculator.cpp", | |||
"openai_chat_completions_mock_calculator.cpp", | |||
"multipart_accepting_calculator.cpp", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This calculator will be rebuild with every change in ovms_lib and its dependencies. We could depend only onhttp payload. Please check if other calculators are easily splittable as well.
request(request), | ||
parser(std::make_shared<drogon::MultiPartParser>()) {} | ||
|
||
bool parse() override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need both parse & hasParseError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse is called once, by ovms core in processV3 during route logic, hasParseError is API for calculator users, to check if request was valid multipart content
src/http_rest_api_handler.cpp
Outdated
model_name = std::string(uri.substr(4)); | ||
SPDLOG_DEBUG("Model name from deduced from URI: {}", model_name); | ||
// Set json parser in invalid state in order to get HasParseError to respond with true | ||
doc->Parse("error"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it more explicit what happens here and why is it required
ensureDocInErrorState?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -163,7 +163,7 @@ void HttpRestApiHandler::registerHandler(RequestType type, HandlerCallbackFn f) | |||
} | |||
|
|||
void HttpRestApiHandler::registerAll() { | |||
registerHandler(Predict, [this](const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, HttpResponseComponents& response_components, std::shared_ptr<HttpAsyncWriter> serverReaderWriter) -> Status { | |||
registerHandler(Predict, [this](const std::string_view uri, const HttpRequestComponents& request_components, std::string& response, const std::string& request_body, HttpResponseComponents& response_components, std::shared_ptr<HttpAsyncWriter> serverReaderWriter, std::shared_ptr<MultiPartParser> multiPartParser) -> Status { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this need to be shared_ptr?
ovms::StatusCode::OK); | ||
|
||
std::string expectedResponse = R"(/v3/v1/completions/ | ||
|
||
content-typeapplication/json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
content-typeapplication/json | |
Key: content-type Value: application/json |
???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix it to be more readable
4bfc031
to
4b4336e
Compare
🛠 Summary
CVS-163721
If request content-type is application-json - route based on
model
JSON fieldIf request content-type s multipart - route based on
mode
multipart fieldIn other cases try to deduce graph name from URI:
/v3/GRAPH-NAME
🧪 Checklist