diff --git a/.gitignore b/.gitignore index 43675e7..2bd584f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.vs build +out TestApp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..68209d6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.13) + +project(liboai) + +option(BUILD_EXAMPLES "Build example applications" OFF) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +add_subdirectory(liboai) + +if(BUILD_EXAMPLES) + add_subdirectory(documentation) +endif() + +set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT oai) \ No newline at end of file diff --git a/documentation/CMakeLists.txt b/documentation/CMakeLists.txt new file mode 100644 index 0000000..04f9392 --- /dev/null +++ b/documentation/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.13) + +project(documentation) + +macro(add_example target_name source_name) + add_executable(${target_name} "${source_name}") + target_link_libraries(${target_name} oai) + set_target_properties(${target_name} PROPERTIES FOLDER "examples/${PROJECT_NAME}") +endmacro() + +macro(add_basic_example source_base_name) + add_example(${source_base_name} "${source_base_name}.cpp") +endmacro() + +add_subdirectory(audio/examples) +add_subdirectory(authorization/examples) +add_subdirectory(azure/examples) +add_subdirectory(chat/examples) +add_subdirectory(chat/conversation/examples) +add_subdirectory(completions/examples) +add_subdirectory(edits/examples) +add_subdirectory(embeddings/examples) +add_subdirectory(files/examples) +add_subdirectory(fine-tunes/examples) +add_subdirectory(images/examples) +add_subdirectory(models/examples) +add_subdirectory(moderations/examples) diff --git a/documentation/audio/examples/CMakeLists.txt b/documentation/audio/examples/CMakeLists.txt new file mode 100644 index 0000000..c476f4b --- /dev/null +++ b/documentation/audio/examples/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.13) + +project(audio) + +add_basic_example(create_speech) +add_basic_example(create_speech_async) +add_basic_example(create_transcription) +add_basic_example(create_transcription_async) +add_basic_example(create_translation) +add_basic_example(create_translation_async) diff --git a/documentation/authorization/examples/CMakeLists.txt b/documentation/authorization/examples/CMakeLists.txt new file mode 100644 index 0000000..1fa3fd4 --- /dev/null +++ b/documentation/authorization/examples/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.13) + +project(authorization) + +add_basic_example(set_azure_key) +add_basic_example(set_azure_key_env) +add_basic_example(set_azure_key_file) +add_basic_example(set_key) +add_basic_example(set_key_env_var) +add_basic_example(set_key_file) +add_basic_example(set_organization) +add_basic_example(set_organization_env_var) +add_basic_example(set_organization_file) +add_basic_example(set_proxies) +add_basic_example(set_proxy_auth) diff --git a/documentation/authorization/examples/set_azure_key.cpp b/documentation/authorization/examples/set_azure_key.cpp index 5d777ff..a4f065c 100644 --- a/documentation/authorization/examples/set_azure_key.cpp +++ b/documentation/authorization/examples/set_azure_key.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetAzureKey("hard-coded-key")) { // NOT recommended - ... + // ... } } diff --git a/documentation/authorization/examples/set_azure_key_env.cpp b/documentation/authorization/examples/set_azure_key_env.cpp index 42ae506..a5797bf 100644 --- a/documentation/authorization/examples/set_azure_key_env.cpp +++ b/documentation/authorization/examples/set_azure_key_env.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetAzureKeyEnv("AZURE_API_KEY")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_azure_key_file.cpp b/documentation/authorization/examples/set_azure_key_file.cpp index b84f490..dee52ca 100644 --- a/documentation/authorization/examples/set_azure_key_file.cpp +++ b/documentation/authorization/examples/set_azure_key_file.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetAzureKeyFile("C:/some/folder/key.dat")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_key.cpp b/documentation/authorization/examples/set_key.cpp index f7b035a..599e3b4 100644 --- a/documentation/authorization/examples/set_key.cpp +++ b/documentation/authorization/examples/set_key.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKey("hard-coded-key")) { // NOT recommended - ... + // ... } } diff --git a/documentation/authorization/examples/set_key_env_var.cpp b/documentation/authorization/examples/set_key_env_var.cpp index 9997434..58c3d61 100644 --- a/documentation/authorization/examples/set_key_env_var.cpp +++ b/documentation/authorization/examples/set_key_env_var.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKeyEnv("OPENAI_API_KEY")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_key_file.cpp b/documentation/authorization/examples/set_key_file.cpp index 588e058..e76415b 100644 --- a/documentation/authorization/examples/set_key_file.cpp +++ b/documentation/authorization/examples/set_key_file.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKeyFile("C:/some/folder/key.dat")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_organization.cpp b/documentation/authorization/examples/set_organization.cpp index 3d6ad11..9686880 100644 --- a/documentation/authorization/examples/set_organization.cpp +++ b/documentation/authorization/examples/set_organization.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKeyEnv("OPENAI_API_KEY") && oai.auth.SetOrganization("org-123")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_organization_env_var.cpp b/documentation/authorization/examples/set_organization_env_var.cpp index e57a0ff..0e3926a 100644 --- a/documentation/authorization/examples/set_organization_env_var.cpp +++ b/documentation/authorization/examples/set_organization_env_var.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKeyEnv("OPENAI_API_KEY") && oai.auth.SetOrganizationEnv("OPENAI_ORG_ID")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_organization_file.cpp b/documentation/authorization/examples/set_organization_file.cpp index cd73621..55b1ce0 100644 --- a/documentation/authorization/examples/set_organization_file.cpp +++ b/documentation/authorization/examples/set_organization_file.cpp @@ -5,6 +5,6 @@ using namespace liboai; int main() { OpenAI oai; if (oai.auth.SetKeyEnv("OPENAI_API_KEY") && oai.auth.SetOrganizationFile("C:/some/folder/org.dat")) { - ... + // ... } } diff --git a/documentation/authorization/examples/set_proxies.cpp b/documentation/authorization/examples/set_proxies.cpp index 76e541f..d11aad3 100644 --- a/documentation/authorization/examples/set_proxies.cpp +++ b/documentation/authorization/examples/set_proxies.cpp @@ -16,6 +16,6 @@ int main() { }); if (oai.auth.SetKeyEnv("OPENAI_API_KEY")) { - ... + // ... } } \ No newline at end of file diff --git a/documentation/authorization/examples/set_proxy_auth.cpp b/documentation/authorization/examples/set_proxy_auth.cpp index 456e7d0..4ef28ba 100644 --- a/documentation/authorization/examples/set_proxy_auth.cpp +++ b/documentation/authorization/examples/set_proxy_auth.cpp @@ -26,6 +26,6 @@ int main() { }); if (oai.auth.SetKeyEnv("OPENAI_API_KEY")) { - ... + // ... } } diff --git a/documentation/azure/examples/CMakeLists.txt b/documentation/azure/examples/CMakeLists.txt new file mode 100644 index 0000000..22760b4 --- /dev/null +++ b/documentation/azure/examples/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.13) + +project(azure) + +add_example(create_chat_completion_azure "create_chat_completion.cpp") +add_example(create_chat_completion_async_azure "create_chat_completion_async.cpp") +add_basic_example(create_completion) +add_basic_example(create_completion_async) +add_example(create_embedding_azure "create_embedding.cpp") +add_example(create_embedding_async_azure "create_embedding_async.cpp") +add_basic_example(delete_generated_image) +add_basic_example(delete_generated_image_async) +add_basic_example(get_generated_image) +add_basic_example(get_generated_image_async) +add_basic_example(request_image_generation) +add_basic_example(request_image_generation_async) diff --git a/documentation/chat/conversation/examples/CMakeLists.txt b/documentation/chat/conversation/examples/CMakeLists.txt new file mode 100644 index 0000000..8e31474 --- /dev/null +++ b/documentation/chat/conversation/examples/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.13) + +project(conversation) + +add_basic_example(adduserdata) +add_basic_example(getjsonobject) +add_basic_example(getlastresponse) +add_basic_example(getrawconversation) +add_basic_example(poplastresponse) +add_basic_example(popsystemdata) +add_basic_example(popuserdata) +add_basic_example(setsystemdata) +add_basic_example(update) diff --git a/documentation/chat/conversation/examples/adduserdata.cpp b/documentation/chat/conversation/examples/adduserdata.cpp index 2771f9d..7d68cbb 100644 --- a/documentation/chat/conversation/examples/adduserdata.cpp +++ b/documentation/chat/conversation/examples/adduserdata.cpp @@ -11,5 +11,5 @@ int main() { // add user data - such as a question convo.AddUserData("What is the meaning of life?"); - ... + // ... } diff --git a/documentation/chat/conversation/examples/popsystemdata.cpp b/documentation/chat/conversation/examples/popsystemdata.cpp index f443cd9..09175d6 100644 --- a/documentation/chat/conversation/examples/popsystemdata.cpp +++ b/documentation/chat/conversation/examples/popsystemdata.cpp @@ -17,5 +17,5 @@ int main() { // add a different system message convo.SetSystemData("You are a helpful bot that enjoys business."); - ... + // ... } diff --git a/documentation/chat/conversation/examples/popuserdata.cpp b/documentation/chat/conversation/examples/popuserdata.cpp index fc66ff2..956ea77 100644 --- a/documentation/chat/conversation/examples/popuserdata.cpp +++ b/documentation/chat/conversation/examples/popuserdata.cpp @@ -17,5 +17,5 @@ int main() { // add different user data convo.AddUserData("What is the size of the universe?"); - ... + // ... } diff --git a/documentation/chat/conversation/examples/setsystemdata.cpp b/documentation/chat/conversation/examples/setsystemdata.cpp index bc7e202..ee373ef 100644 --- a/documentation/chat/conversation/examples/setsystemdata.cpp +++ b/documentation/chat/conversation/examples/setsystemdata.cpp @@ -11,5 +11,5 @@ int main() { // set system message to guide the chat model convo.SetSystemData("You are helpful bot."); - ... + // ... } diff --git a/documentation/chat/examples/CMakeLists.txt b/documentation/chat/examples/CMakeLists.txt new file mode 100644 index 0000000..20c5572 --- /dev/null +++ b/documentation/chat/examples/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.13) + +project(chat) + +add_basic_example(create_chat_completion) +add_basic_example(create_chat_completion_async) +add_basic_example(ongoing_user_convo) diff --git a/documentation/completions/examples/CMakeLists.txt b/documentation/completions/examples/CMakeLists.txt new file mode 100644 index 0000000..fdb88b2 --- /dev/null +++ b/documentation/completions/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13) + +project(completions) + +add_basic_example(generate_completion) +add_basic_example(generate_completion_async) diff --git a/documentation/edits/examples/CMakeLists.txt b/documentation/edits/examples/CMakeLists.txt new file mode 100644 index 0000000..8c9a6c9 --- /dev/null +++ b/documentation/edits/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13) + +project(edits) + +add_basic_example(create_edit) +add_basic_example(create_edit_async) diff --git a/documentation/embeddings/examples/CMakeLists.txt b/documentation/embeddings/examples/CMakeLists.txt new file mode 100644 index 0000000..1cac248 --- /dev/null +++ b/documentation/embeddings/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13) + +project(embeddings) + +add_basic_example(create_embedding) +add_basic_example(create_embedding_async) diff --git a/documentation/files/examples/CMakeLists.txt b/documentation/files/examples/CMakeLists.txt new file mode 100644 index 0000000..527dc0f --- /dev/null +++ b/documentation/files/examples/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.13) + +project(files) + +add_basic_example(delete_file) +add_basic_example(delete_file_async) +add_basic_example(download_uploaded_file) +add_basic_example(download_uploaded_file_async) +add_basic_example(list_files) +add_basic_example(list_files_async) +add_basic_example(retrieve_file) +add_basic_example(retrieve_file_async) +add_basic_example(upload_file) +add_basic_example(upload_file_async) diff --git a/documentation/fine-tunes/examples/CMakeLists.txt b/documentation/fine-tunes/examples/CMakeLists.txt new file mode 100644 index 0000000..9604402 --- /dev/null +++ b/documentation/fine-tunes/examples/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.13) + +project(fine-tunes) + +add_basic_example(cancel_fine_tune) +add_basic_example(cancel_fine_tune_async) +add_basic_example(create_fine_tune) +add_basic_example(create_fine_tune_async) +add_basic_example(delete_fine_tune_model) +add_basic_example(delete_fine_tune_model_async) +add_basic_example(list_fine_tune_events) +add_basic_example(list_fine_tune_events_async) +add_basic_example(list_fine_tunes) +add_basic_example(list_fine_tunes_async) +add_basic_example(retrieve_fine_tune) +add_basic_example(retrieve_fine_tune_async) diff --git a/documentation/images/examples/CMakeLists.txt b/documentation/images/examples/CMakeLists.txt new file mode 100644 index 0000000..384f2bd --- /dev/null +++ b/documentation/images/examples/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.13) + +project(images) + +# compilation error +#add_basic_example(download_generated_image) +add_basic_example(generate_edit) +add_basic_example(generate_edit_async) +add_basic_example(generate_image) +add_basic_example(generate_image_async) +add_basic_example(generate_variation) +add_basic_example(generate_variation_async) diff --git a/documentation/models/examples/CMakeLists.txt b/documentation/models/examples/CMakeLists.txt new file mode 100644 index 0000000..7cbb7ef --- /dev/null +++ b/documentation/models/examples/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.13) + +project(models) + +add_basic_example(list_models) +add_basic_example(list_models_async) +add_basic_example(retrieve_model) +add_basic_example(retrieve_model_async) diff --git a/documentation/moderations/examples/CMakeLists.txt b/documentation/moderations/examples/CMakeLists.txt new file mode 100644 index 0000000..e89e256 --- /dev/null +++ b/documentation/moderations/examples/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13) + +project(moderations) + +add_basic_example(create_moderation) +add_basic_example(create_moderation_async) diff --git a/liboai/CMakeLists.txt b/liboai/CMakeLists.txt index 2712076..4101267 100644 --- a/liboai/CMakeLists.txt +++ b/liboai/CMakeLists.txt @@ -1,54 +1,136 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.13) -project(oai) +include(CMakePackageConfigHelpers) + +project(oai VERSION 4.0.1) + +if(MSVC) + set(CMAKE_DEBUG_POSTFIX "d") +endif() find_package(nlohmann_json CONFIG REQUIRED) find_package(CURL REQUIRED) -add_library(oai -components/audio.cpp -components/azure.cpp -components/chat.cpp -components/completions.cpp -components/edits.cpp -components/embeddings.cpp -components/files.cpp -components/fine_tunes.cpp -components/images.cpp -components/models.cpp -components/moderations.cpp -core/authorization.cpp -core/netimpl.cpp -core/response.cpp -) - -target_compile_features(oai PRIVATE cxx_std_17) - -target_link_libraries(oai PRIVATE nlohmann_json::nlohmann_json) -target_link_libraries(oai PRIVATE CURL::libcurl) - -install(TARGETS oai DESTINATION lib) -#not needed anymore -#install(FILES liboai.h DESTINATION include) -install(FILES -include/components/audio.h -include/components/azure.h -include/components/chat.h -include/components/completions.h -include/components/edits.h -include/components/embeddings.h -include/components/files.h -include/components/fine_tunes.h -include/components/images.h -include/components/models.h -include/components/moderations.h -DESTINATION include/components) +add_library(${PROJECT_NAME}) + +function(make_absolute_paths result_var) + set(absolute_paths) + foreach(file IN LISTS ARGN) + list(APPEND absolute_paths "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + endforeach() + set(${result_var} "${absolute_paths}" PARENT_SCOPE) +endfunction() + +set(HEADERS_RELATIVE + "include/liboai.h" +) + +make_absolute_paths(HEADERS ${HEADERS_RELATIVE}) +source_group("include" FILES ${HEADERS}) + +set(COMPONENT_HEADERS_RELATIVE + "include/components/audio.h" + "include/components/azure.h" + "include/components/chat.h" + "include/components/completions.h" + "include/components/edits.h" + "include/components/embeddings.h" + "include/components/files.h" + "include/components/fine_tunes.h" + "include/components/images.h" + "include/components/models.h" + "include/components/moderations.h" +) + +make_absolute_paths(COMPONENT_HEADERS ${COMPONENT_HEADERS_RELATIVE}) +source_group("include/components" FILES ${COMPONENT_HEADERS}) + +set(COMPONENT_SOURCES_RELATIVE + "components/audio.cpp" + "components/azure.cpp" + "components/chat.cpp" + "components/completions.cpp" + "components/edits.cpp" + "components/embeddings.cpp" + "components/files.cpp" + "components/fine_tunes.cpp" + "components/images.cpp" + "components/models.cpp" + "components/moderations.cpp" +) + +make_absolute_paths(COMPONENT_SOURCES ${COMPONENT_SOURCES_RELATIVE}) +source_group("source/components" FILES ${COMPONENT_SOURCES}) + +set(CORE_HEADERS_RELATIVE + "include/core/authorization.h" + "include/core/exception.h" + "include/core/netimpl.h" + "include/core/network.h" + "include/core/response.h" +) + +make_absolute_paths(CORE_HEADERS ${CORE_HEADERS_RELATIVE}) +source_group("include/core" FILES ${CORE_HEADERS}) + +set(CORE_SOURCES_RELATIVE + "core/authorization.cpp" + "core/netimpl.cpp" + "core/response.cpp" +) + +make_absolute_paths(CORE_SOURCES ${CORE_SOURCES_RELATIVE}) +source_group("source/core" FILES ${CORE_SOURCES}) + +target_sources(${PROJECT_NAME} + PRIVATE + ${COMPONENT_SOURCES} + ${CORE_SOURCES} + PUBLIC + "$" + "$" + "$" + "$" + "$" + "$" +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} + PUBLIC + nlohmann_json::nlohmann_json + CURL::libcurl +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + "$" + "$" +) + +install(TARGETS ${PROJECT_NAME} DESTINATION lib EXPORT ${PROJECT_NAME}Targets) +install(FILES ${HEADERS} DESTINATION "include") +install(FILES ${COMPONENT_HEADERS} DESTINATION "include/components") +install(FILES ${CORE_HEADERS} DESTINATION "include/core") + +configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION "lib/cmake/${PROJECT_NAME}" +) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + COMPATIBILITY AnyNewerVersion +) + +install(EXPORT ${PROJECT_NAME}Targets + FILE ${PROJECT_NAME}Targets.cmake + NAMESPACE oai:: + DESTINATION "lib/cmake/${PROJECT_NAME}" +) install(FILES -include/core/authorization.h -include/core/exception.h -include/core/netimpl.h -include/core/network.h -include/core/response.h -DESTINATION include/core) - -set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT oai) + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION "lib/cmake/${PROJECT_NAME}" +) diff --git a/liboai/Config.cmake.in b/liboai/Config.cmake.in new file mode 100644 index 0000000..202da49 --- /dev/null +++ b/liboai/Config.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/oaiTargets.cmake") + +find_package(nlohmann_json CONFIG REQUIRED) +find_package(CURL REQUIRED) diff --git a/liboai/components/chat.cpp b/liboai/components/chat.cpp index 05b3189..4963d93 100644 --- a/liboai/components/chat.cpp +++ b/liboai/components/chat.cpp @@ -225,12 +225,13 @@ bool liboai::Conversation::Update(std::string_view response) & noexcept(false) { // conversation is not updated as there is no assistant // response to be added. However, we do add the function // information - + + this->_conversation["function_call"] = nlohmann::json::object(); if (choice.value()["message"]["function_call"].contains("name")) { - this->_conversation["function_call"] = { { "name", choice.value()["message"]["function_call"]["name"] } }; + this->_conversation["function_call"]["name"] = choice.value()["message"]["function_call"]["name"]; } if (choice.value()["message"]["function_call"].contains("arguments")) { - this->_conversation["function_call"] = { { "arguments", choice.value()["message"]["function_call"]["arguments"] } }; + this->_conversation["function_call"]["arguments"] = choice.value()["message"]["function_call"]["arguments"]; } this->_last_resp_is_fc = true; @@ -271,12 +272,13 @@ bool liboai::Conversation::Update(std::string_view response) & noexcept(false) { // conversation is not updated as there is no assistant // response to be added. However, we do add the function // information - + + this->_conversation["function_call"] = nlohmann::json::object(); if (j["message"]["function_call"].contains("name")) { - this->_conversation["function_call"] = { { "name", j["message"]["function_call"]["name"] } }; + this->_conversation["function_call"]["name"] = j["message"]["function_call"]["name"]; } if (j["message"]["function_call"].contains("arguments")) { - this->_conversation["function_call"] = { { "arguments", j["message"]["function_call"]["arguments"] } }; + this->_conversation["function_call"]["arguments"] = j["message"]["function_call"]["arguments"]; } this->_last_resp_is_fc = true; @@ -311,12 +313,12 @@ bool liboai::Conversation::Update(std::string_view response) & noexcept(false) { // conversation is not updated as there is no assistant // response to be added. However, we do add the function // information - + this->_conversation["function_call"] = nlohmann::json::object(); if (j["message"]["function_call"].contains("name")) { - this->_conversation["function_call"] = { { "name", j["message"]["function_call"]["name"] } }; + this->_conversation["function_call"]["name"] = j["message"]["function_call"]["name"]; } if (j["message"]["function_call"].contains("arguments")) { - this->_conversation["function_call"] = { { "arguments", j["message"]["function_call"]["arguments"] } }; + this->_conversation["function_call"]["arguments"] = j["message"]["function_call"]["arguments"]; } this->_last_resp_is_fc = true; @@ -374,101 +376,23 @@ bool liboai::Conversation::Import(std::string_view json) & noexcept(false) { bool liboai::Conversation::AppendStreamData(std::string data) & noexcept(false) { if (!data.empty()) { - if (data.find("data: [DONE]") == std::string::npos) { - /* - j should have content in the form of: - {"id":"chatcmpl-7SKOck29emvbBbDS6cHg5xwnRrsLO","object":"chat.completion.chunk","created":1686985942,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}]} - where "delta" may be empty - */ - std::vector objects = this->SplitStreamedData(data); - - if (!objects.empty()) { - // create an empty message at the end of the conversation, - // marked as "pending" to indicate that the response is - // still being processed. This flag will be removed once - // the response is processed. If the marking already - // exists, keep appending to the same message. - if (this->_conversation["messages"].empty() || !this->_conversation["messages"].back().contains("pending")) { - this->_conversation["messages"].push_back( - { - { "role", "" }, - { "content", "" }, - { "pending", true } - } - ); - } - - for (auto& json_object : objects) { - nlohmann::json j = nlohmann::json::parse(json_object); - - if (j.contains("choices")) { - if (j["choices"][0].contains("delta")) { - if (!j["choices"][0]["delta"].empty() && !j["choices"][0]["delta"].is_null()) { - if (j["choices"][0]["delta"].contains("role")) { - this->_conversation["messages"].back()["role"] = j["choices"][0]["delta"]["role"]; - } - - if (j["choices"][0]["delta"].contains("content")) { - if (!j["choices"][0]["delta"]["content"].empty() && !j["choices"][0]["delta"]["content"].is_null()) { - this->_conversation["messages"].back()["content"] = (this->_conversation["messages"].back()["content"].get() + j["choices"][0]["delta"]["content"].get()); - } - - // function calls do not have a content field, - // set _last_resp_is_fc to false and remove any - // previously set function_call field in the - // conversation - if (this->_last_resp_is_fc) { - if (this->_conversation.contains("function_call")) { - this->_conversation.erase("function_call"); - } - this->_last_resp_is_fc = false; - } - } - - if (j["choices"][0]["delta"].contains("function_call")) { - if (!j["choices"][0]["delta"]["function_call"].empty() && !j["choices"][0]["delta"]["function_call"].is_null()) { - if (j["choices"][0]["delta"]["function_call"].contains("name")) { - if (!j["choices"][0]["delta"]["function_call"]["name"].empty() && !j["choices"][0]["delta"]["function_call"]["name"].is_null()) { - if (!this->_conversation["messages"].back().contains("function_call")) { - this->_conversation["function_call"] = { { "name", j["choices"][0]["delta"]["function_call"]["name"] } }; - this->_last_resp_is_fc = true; - } - } - } - else if (j["choices"][0]["delta"]["function_call"].contains("arguments")) { - if (!j["choices"][0]["delta"]["function_call"]["arguments"].empty() && !j["choices"][0]["delta"]["function_call"]["arguments"].is_null()) { - if (!this->_conversation["function_call"].contains("arguments")) { - this->_conversation["function_call"].push_back({ "arguments", j["choices"][0]["delta"]["function_call"]["arguments"] }); - } - else { - this->_conversation["function_call"]["arguments"] = this->_conversation["function_call"]["arguments"].get() + j["choices"][0]["delta"]["function_call"]["arguments"].get(); - } - } - } - } - } - } - } - } - else { - return false; // no "choices" found - invalid - } - } - - return true; // response processed successfully - } - } - else { - // the response is complete, erase the "pending" flag - this->_conversation["messages"].back().erase("pending"); - - return true; // last message received - } + std::string delta; + bool completed = false; + return this->ParseStreamData(data, delta, completed); } return false; // data is empty } +bool liboai::Conversation::AppendStreamData(std::string data, std::string& delta, bool& completed) & noexcept(false){ + if (!data.empty()) { + return this->ParseStreamData(data, delta, completed); + } + + return false; +} + + bool liboai::Conversation::SetFunctions(Functions functions) & noexcept(false) { nlohmann::json j = functions.GetJSON(); @@ -543,6 +467,128 @@ void liboai::Conversation::RemoveStrings(std::string& s, std::string_view p) con } } +std::vector liboai::Conversation::SplitFullStreamedData(std::string data) const noexcept(false) { + if (data.empty()) { + return {}; + } + + std::vector split_data; + std::string temp; + std::istringstream iss(data); + while (std::getline(iss, temp)) { + if (temp.empty()) { + split_data.push_back(temp); + } + else { + split_data.push_back(temp); + } + } + + // remove empty strings from the vector + split_data.erase(std::remove_if(split_data.begin(), split_data.end(), [](const std::string& s) { return s.empty(); }), split_data.end()); + + return split_data; +} + +bool liboai::Conversation::ParseStreamData(std::string data, std::string& delta_content, bool& completed){ + std::vector data_lines = SplitFullStreamedData(data); + + if (data_lines.empty()){ + return false; + } + + // create an empty message at the end of the conversation, + // marked as "pending" to indicate that the response is + // still being processed. This flag will be removed once + // the response is processed. If the marking already + // exists, keep appending to the same message. + if (this->_conversation["messages"].empty() || !this->_conversation["messages"].back().contains("pending")) { + this->_conversation["messages"].push_back( + { + { "role", "" }, + { "content", "" }, + { "pending", true } + } + ); + } + + for (auto& line : data_lines){ + if (line.find("data: [DONE]") == std::string::npos) { + /* + j should have content in the form of: + {"id":"chatcmpl-7SKOck29emvbBbDS6cHg5xwnRrsLO","object":"chat.completion.chunk","created":1686985942,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"."},"finish_reason":null}]} + where "delta" may be empty + */ + this->RemoveStrings(line, "data: "); + + // for (auto& json_object : objects) { + nlohmann::json j = nlohmann::json::parse(line); + + if (j.contains("choices")) { + if (j["choices"][0].contains("delta")) { + if (!j["choices"][0]["delta"].empty() && !j["choices"][0]["delta"].is_null()) { + if (j["choices"][0]["delta"].contains("role")) { + this->_conversation["messages"].back()["role"] = j["choices"][0]["delta"]["role"]; + } + + if (j["choices"][0]["delta"].contains("content")) { + if (!j["choices"][0]["delta"]["content"].empty() && !j["choices"][0]["delta"]["content"].is_null()) { + std::string stream_content = j["choices"][0]["delta"]["content"].get(); + this->_conversation["messages"].back()["content"] = this->_conversation["messages"].back()["content"].get() + stream_content; + delta_content += stream_content; + } + + // function calls do not have a content field, + // set _last_resp_is_fc to false and remove any + // previously set function_call field in the + // conversation + if (this->_last_resp_is_fc) { + if (this->_conversation.contains("function_call")) { + this->_conversation.erase("function_call"); + } + this->_last_resp_is_fc = false; + } + } + + if (j["choices"][0]["delta"].contains("function_call")) { + if (!j["choices"][0]["delta"]["function_call"].empty() && !j["choices"][0]["delta"]["function_call"].is_null()) { + if (j["choices"][0]["delta"]["function_call"].contains("name")) { + if (!j["choices"][0]["delta"]["function_call"]["name"].empty() && !j["choices"][0]["delta"]["function_call"]["name"].is_null()) { + if (!this->_conversation["messages"].back().contains("function_call")) { + this->_conversation["function_call"] = { { "name", j["choices"][0]["delta"]["function_call"]["name"] } }; + this->_last_resp_is_fc = true; + } + } + } + else if (j["choices"][0]["delta"]["function_call"].contains("arguments")) { + if (!j["choices"][0]["delta"]["function_call"]["arguments"].empty() && !j["choices"][0]["delta"]["function_call"]["arguments"].is_null()) { + if (!this->_conversation["function_call"].contains("arguments")) { + this->_conversation["function_call"].push_back({ "arguments", j["choices"][0]["delta"]["function_call"]["arguments"] }); + } + else { + this->_conversation["function_call"]["arguments"] = this->_conversation["function_call"]["arguments"].get() + j["choices"][0]["delta"]["function_call"]["arguments"].get(); + } + } + } + } + } + } + } + } else { + return false; // no "choices" found - invalid + } + } else { + // the response is complete, erase the "pending" flag + this->_conversation["messages"].back().erase("pending"); + completed = true; + } + } + + return true; // last message received +} + + + liboai::Response liboai::ChatCompletion::create(const std::string& model, Conversation& conversation, std::optional function_call, std::optional temperature, std::optional top_p, std::optional n, std::optional stream, std::optional> stop, std::optional max_tokens, std::optional presence_penalty, std::optional frequency_penalty, std::optional> logit_bias, std::optional user) const& noexcept(false) { liboai::JsonConstructor jcon; jcon.push_back("model", model); diff --git a/liboai/core/netimpl.cpp b/liboai/core/netimpl.cpp index bf36de5..15d4634 100644 --- a/liboai/core/netimpl.cpp +++ b/liboai/core/netimpl.cpp @@ -118,7 +118,7 @@ liboai::netimpl::Session::~Session() { void liboai::netimpl::Session::Prepare() { // holds error codes - all init to OK to prevent errors // when checking unset values - CURLcode e[10]; memset(e, CURLcode::CURLE_OK, sizeof(e)); + CURLcode e[11]; memset(e, CURLcode::CURLE_OK, sizeof(e)); // add parameters to base url if (!this->parameter_string_.empty()) { @@ -136,36 +136,47 @@ void liboai::netimpl::Session::Prepare() { e[0] = curl_easy_setopt(this->curl_, CURLOPT_URL, this->url_.c_str()); - // set proxy if available - const std::string protocol = url_.substr(0, url_.find(':')); - if (proxies_.has(protocol)) { - e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol].c_str()); - - #if defined(LIBOAI_DEBUG) - _liboai_dbg( - "[dbg] [@%s] Set CURLOPT_PROXY for Session (0x%p) to %s.\n", - __func__, this, proxies_[protocol].c_str() - ); - #endif - - if (proxyAuth_.has(protocol)) { - e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol)); - e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol)); - + const std::string protocol_socket5_hostname = "socket5_hostname"; + if (proxies_.has(protocol_socket5_hostname)) { + e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol_socket5_hostname].c_str()); + e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); + + if (proxyAuth_.has(protocol_socket5_hostname)) { + e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol_socket5_hostname)); + e[4] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol_socket5_hostname)); + } + } else { + // set proxy if available + const std::string protocol = url_.substr(0, url_.find(':')); + if (proxies_.has(protocol)) { + e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol].c_str()); + #if defined(LIBOAI_DEBUG) _liboai_dbg( - "[dbg] [@%s] Set CURLOPT_PROXYUSERNAME and CURLOPT_PROXYPASSWORD for Session (0x%p) to %s and %s.\n", - __func__, this, proxyAuth_.GetUsername(protocol), proxyAuth_.GetPassword(protocol) + "[dbg] [@%s] Set CURLOPT_PROXY for Session (0x%p) to %s.\n", + __func__, this, proxies_[protocol].c_str() ); #endif + + if (proxyAuth_.has(protocol)) { + e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol)); + e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol)); + + #if defined(LIBOAI_DEBUG) + _liboai_dbg( + "[dbg] [@%s] Set CURLOPT_PROXYUSERNAME and CURLOPT_PROXYPASSWORD for Session (0x%p) to %s and %s.\n", + __func__, this, proxyAuth_.GetUsername(protocol), proxyAuth_.GetPassword(protocol) + ); + #endif + } } } // accept all encoding types - e[4] = curl_easy_setopt(this->curl_, CURLOPT_ACCEPT_ENCODING, ""); + e[5] = curl_easy_setopt(this->curl_, CURLOPT_ACCEPT_ENCODING, ""); #if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 71) - e[5] = curl_easy_setopt(this->curl_, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + e[6] = curl_easy_setopt(this->curl_, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #if defined(LIBOAI_DEBUG) _liboai_dbg( @@ -177,8 +188,8 @@ void liboai::netimpl::Session::Prepare() { // set string the response will be sent to if (!this->write_.callback) { - e[6] = curl_easy_setopt(this->curl_, CURLOPT_WRITEFUNCTION, liboai::netimpl::components::writeFunction); - e[7] = curl_easy_setopt(this->curl_, CURLOPT_WRITEDATA, &this->response_string_); + e[7] = curl_easy_setopt(this->curl_, CURLOPT_WRITEFUNCTION, liboai::netimpl::components::writeFunction); + e[8] = curl_easy_setopt(this->curl_, CURLOPT_WRITEDATA, &this->response_string_); #if defined(LIBOAI_DEBUG) _liboai_dbg( @@ -189,8 +200,8 @@ void liboai::netimpl::Session::Prepare() { } // set string the raw headers will be sent to - e[8] = curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, liboai::netimpl::components::writeFunction); - e[9] = curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &this->header_string_); + e[9] = curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, liboai::netimpl::components::writeFunction); + e[10] = curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &this->header_string_); #if defined(LIBOAI_DEBUG) _liboai_dbg( @@ -199,13 +210,13 @@ void liboai::netimpl::Session::Prepare() { ); #endif - ErrorCheck(e, 10, "liboai::netimpl::Session::Prepare()"); + ErrorCheck(e, 11, "liboai::netimpl::Session::Prepare()"); } void liboai::netimpl::Session::PrepareDownloadInternal() { // holds error codes - all init to OK to prevent errors // when checking unset values - CURLcode e[6]; memset(e, CURLcode::CURLE_OK, sizeof(e)); + CURLcode e[7]; memset(e, CURLcode::CURLE_OK, sizeof(e)); if (!this->parameter_string_.empty()) { this->url_ += "?"; @@ -222,32 +233,42 @@ void liboai::netimpl::Session::PrepareDownloadInternal() { e[0] = curl_easy_setopt(this->curl_, CURLOPT_URL, this->url_.c_str()); - const std::string protocol = url_.substr(0, url_.find(':')); - if (proxies_.has(protocol)) { - e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol].c_str()); - - #if defined(LIBOAI_DEBUG) - _liboai_dbg( - "[dbg] [@%s] Set CURLOPT_PROXY for Session (0x%p) to %s.\n", - __func__, this, proxies_[protocol].c_str() - ); - #endif - - if (proxyAuth_.has(protocol)) { - e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol)); - e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol)); + const std::string protocol_socket5_hostname = "socket5_hostname"; + if (proxies_.has(protocol_socket5_hostname)) { + e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol_socket5_hostname].c_str()); + e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); + if (proxyAuth_.has(protocol_socket5_hostname)) { + e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol_socket5_hostname)); + e[4] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol_socket5_hostname)); + } + } else { + const std::string protocol = url_.substr(0, url_.find(':')); + if (proxies_.has(protocol)) { + e[1] = curl_easy_setopt(this->curl_, CURLOPT_PROXY, proxies_[protocol].c_str()); #if defined(LIBOAI_DEBUG) _liboai_dbg( - "[dbg] [@%s] Set CURLOPT_PROXYUSERNAME and CURLOPT_PROXYPASSWORD for Session (0x%p) to %s and %s.\n", - __func__, this, proxyAuth_.GetUsername(protocol), proxyAuth_.GetPassword(protocol) + "[dbg] [@%s] Set CURLOPT_PROXY for Session (0x%p) to %s.\n", + __func__, this, proxies_[protocol].c_str() ); #endif + + if (proxyAuth_.has(protocol)) { + e[2] = curl_easy_setopt(this->curl_, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol)); + e[3] = curl_easy_setopt(this->curl_, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol)); + + #if defined(LIBOAI_DEBUG) + _liboai_dbg( + "[dbg] [@%s] Set CURLOPT_PROXYUSERNAME and CURLOPT_PROXYPASSWORD for Session (0x%p) to %s and %s.\n", + __func__, this, proxyAuth_.GetUsername(protocol), proxyAuth_.GetPassword(protocol) + ); + #endif + } } } - e[4] = curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, liboai::netimpl::components::writeFunction); - e[5] = curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &this->header_string_); + e[5] = curl_easy_setopt(this->curl_, CURLOPT_HEADERFUNCTION, liboai::netimpl::components::writeFunction); + e[6] = curl_easy_setopt(this->curl_, CURLOPT_HEADERDATA, &this->header_string_); #if defined(LIBOAI_DEBUG) _liboai_dbg( @@ -256,7 +277,7 @@ void liboai::netimpl::Session::PrepareDownloadInternal() { ); #endif - ErrorCheck(e, 6, "liboai::netimpl::Session::PrepareDownloadInternal()"); + ErrorCheck(e, 7, "liboai::netimpl::Session::PrepareDownloadInternal()"); } CURLcode liboai::netimpl::Session::Perform() { @@ -631,6 +652,57 @@ liboai::Response liboai::netimpl::Session::Download(std::ofstream& file) { return CompleteDownload(); } +void liboai::netimpl::Session::ClearContext() { + if (curl_) { + curl_easy_reset(curl_); + } + status_code = 0; + elapsed = 0.0; + status_line.clear(); + content.clear(); + url_str.clear(); + reason.clear(); + + if (this->headers) { + curl_slist_free_all(this->headers); + this->headers = nullptr; + +#if defined(LIBOAI_DEBUG) + _liboai_dbg("[dbg] [@%s] curl_slist_free_all() called.\n", __func__); +#endif + } + +#if LIBCURL_VERSION_MAJOR < 7 || \ + (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 56) + if (this->form) { + curl_formfree(this->form); + this->form = nullptr; + +#if defined(LIBOAI_DEBUG) + _liboai_dbg("[dbg] [@%s] curl_formfree() called.\n", __func__); +#endif + } +#endif + +#if LIBCURL_VERSION_MAJOR > 7 || \ + (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 56) + if (this->mime) { + curl_mime_free(this->mime); + this->mime = nullptr; + +#if defined(LIBOAI_DEBUG) + _liboai_dbg("[dbg] [@%s] curl_mime_free() called.\n", __func__); +#endif + } +#endif + + hasBody = false; + parameter_string_.clear(); + url_.clear(); + response_string_.clear(); + header_string_.clear(); +} + void liboai::netimpl::Session::ParseResponseHeader(const std::string& headers, std::string* status_line, std::string* reason) { std::vector lines; std::istringstream stream(headers); @@ -835,7 +907,8 @@ void liboai::netimpl::Session::SetMultipart(const components::Multipart& multipa else if (part.is_buffer) { mimedata.push_back(curl_mime_addpart(this->mime)); e[1] = curl_mime_name(mimedata.back(), part.name.c_str()); - e[2] = curl_mime_data(mimedata.back(), reinterpret_cast(part.data), part.datalen); + e[2] = curl_mime_filename(mimedata.back(), part.value.c_str()); + e[3] = curl_mime_data(mimedata.back(), reinterpret_cast(part.data), part.datalen); } else { mimedata.push_back(curl_mime_addpart(this->mime)); @@ -952,7 +1025,8 @@ void liboai::netimpl::Session::SetMultipart(components::Multipart&& multipart) { else if (part.is_buffer) { mimedata.push_back(curl_mime_addpart(this->mime)); e[1] = curl_mime_name(mimedata.back(), part.name.c_str()); - e[2] = curl_mime_data(mimedata.back(), reinterpret_cast(part.data), part.datalen); + e[2] = curl_mime_filename(mimedata.back(), part.value.c_str()); + e[3] = curl_mime_data(mimedata.back(), reinterpret_cast(part.data), part.datalen); } else { mimedata.push_back(curl_mime_addpart(this->mime)); diff --git a/liboai/include/components/chat.h b/liboai/include/components/chat.h index 65f707a..278b761 100644 --- a/liboai/include/components/chat.h +++ b/liboai/include/components/chat.h @@ -727,6 +727,23 @@ namespace liboai { [[nodiscard]] LIBOAI_EXPORT bool AppendStreamData(std::string data) & noexcept(false); + + /* + @brief Appends stream data (SSEs) from streamed methods. + This method updates the conversation given a token from a + streamed method. This method should be used when using + streamed methods such as ChatCompletion::create or + create_async with a callback supplied. This function should + be called from within the stream's callback function + receiving the SSEs. + + @param *token Streamed token (data) to update the conversation with. + @param *delta output parameter. The delta to append to the conversation. + @param *completed output parameter. Whether the stream is completed. + */ + [[nodiscard]] + LIBOAI_EXPORT bool AppendStreamData(std::string data, std::string& delta, bool& completed) & noexcept(false); + /* @brief Sets the functions to be used for the conversation. This method sets the functions to be used for the conversation. @@ -767,11 +784,21 @@ namespace liboai { */ LIBOAI_EXPORT const nlohmann::json& GetFunctionsJSON() const & noexcept; + /* + @brief Returns whether the conversation has functions or not. this function call from ChatComplete + */ + [[nodiscard]] constexpr bool HasFunctions() const & noexcept { return this->_functions ? true : false; } + private: friend class ChatCompletion; friend class Azure; - [[nodiscard]] constexpr bool HasFunctions() const & noexcept { return this->_functions ? true : false; } [[nodiscard]] std::vector SplitStreamedData(std::string data) const noexcept(false); void RemoveStrings(std::string& s, std::string_view p) const noexcept(false); + /* + @brief split full stream data that read from remote server. + @returns vector of string that contains the split data that will contains the last termination string(data: "DONE"). + */ + [[nodiscard]] std::vector SplitFullStreamedData(std::string data) const noexcept(false); + bool ParseStreamData(std::string data, std::string& delta, bool& completed); nlohmann::json _conversation; std::optional _functions = std::nullopt; diff --git a/liboai/include/core/netimpl.h b/liboai/include/core/netimpl.h index 762c668..c5f82f6 100644 --- a/liboai/include/core/netimpl.h +++ b/liboai/include/core/netimpl.h @@ -592,7 +592,7 @@ namespace liboai { size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write); size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data); size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file); - } + }; /* Class for sessions; each session is a single request. @@ -614,6 +614,8 @@ namespace liboai { liboai::Response Post(); liboai::Response Delete(); liboai::Response Download(std::ofstream& file); + void ClearContext(); + private: template @@ -700,13 +702,27 @@ namespace liboai { return session.Get(); } + template + liboai::Response GetWithSession(Session& session, _Options&&... options) { + session.ClearContext(); + set_options(session, std::forward<_Options>(options)...); + return session.Get(); + } + template liboai::Response Post(_Options&&... options) { Session session; set_options(session, std::forward<_Options>(options)...); return session.Post(); } - + + template + liboai::Response PostWithSession(Session& session, _Options&&... options) { + session.ClearContext(); + set_options(session, std::forward<_Options>(options)...); + return session.Post(); + } + template liboai::Response Delete(_Options&&... options) { Session session; @@ -714,6 +730,13 @@ namespace liboai { return session.Delete(); } + template + liboai::Response DeleteWithSession(Session& session, _Options&&... options) { + session.ClearContext(); + set_options(session, std::forward<_Options>(options)...); + return session.Delete(); + } + template liboai::Response Download(std::ofstream& file, _Options&&... options) { Session session; @@ -721,6 +744,13 @@ namespace liboai { return session.Download(file); } + template + liboai::Response DownloadWithSession(Session& session, std::ofstream& file, _Options&&... options) { + session.ClearContext(); + set_options(session, std::forward<_Options>(options)...); + return session.Download(file); + } + template void set_options(Session& session, _Options&&... opts) { (session.SetOption(std::forward<_Options>(opts)), ...); diff --git a/liboai/include/core/network.h b/liboai/include/core/network.h index 7f04a21..577e239 100644 --- a/liboai/include/core/network.h +++ b/liboai/include/core/network.h @@ -57,6 +57,25 @@ namespace liboai { return res.status_code == 200; } + [[nodiscard]] + static inline bool DownloadWithSession( + const std::string& to, + const std::string& from, + netimpl::components::Header authorization, + netimpl::Session& session + ) noexcept(false) { + std::ofstream file(to, std::ios::binary); + Response res; + res = netimpl::DownloadWithSession( + session, + file, + netimpl::components::Url{ from }, + std::move(authorization) + ); + + return res.status_code == 200; + } + /* @brief Function to asynchronously download a file at 'from' to file path 'to.' Useful @@ -93,6 +112,29 @@ namespace liboai { } ); } + + [[nodiscard]] + static inline std::future DownloadAsyncWithSession( + const std::string& to, + const std::string& from, + netimpl::components::Header authorization, + netimpl::Session& session + ) noexcept(false) { + return std::async( + std::launch::async, [&]() -> bool { + std::ofstream file(to, std::ios::binary); + Response res; + res = netimpl::DownloadWithSession( + session, + file, + netimpl::components::Url{ from }, + std::move(authorization) + ); + + return res.status_code == 200; + } + ); + } protected: enum class Method : uint8_t { @@ -138,6 +180,47 @@ namespace liboai { return res; } + + template >...>, int> = 0> + inline Response RequestWithSession( + const Method& http_method, + const std::string& root, + const std::string& endpoint, + const std::string& content_type, + netimpl::Session& session, + std::optional headers = std::nullopt, + _Params&&... parameters + ) const { + netimpl::components::Header _headers = { { "Content-Type", content_type } }; + if (headers) { + if (headers.value().size() != 0) { + for (auto& i : headers.value()) { + _headers.insert(std::move(i)); + } + } + } + + Response res; + if constexpr (sizeof...(parameters) > 0) { + res = Network::MethodSchemaWithSession::_method[static_cast(http_method)]( + session, + netimpl::components::Url { root + endpoint }, + std::move(_headers), + std::forward<_Params>(parameters)... + ); + } + else { + res = Network::MethodSchemaWithSession::_method[static_cast(http_method)]( + session, + netimpl::components::Url { root + endpoint }, + std::move(_headers) + ); + } + + return res; + } + /* @brief Function to validate the existence and validity of a file located at a provided file path. This is used @@ -163,5 +246,13 @@ namespace liboai { netimpl::Delete }; }; + + template struct MethodSchemaWithSession { + inline static std::function _method[3] = { + netimpl::GetWithSession , + netimpl::PostWithSession , + netimpl::DeleteWithSession + }; + }; }; } \ No newline at end of file diff --git a/liboai/include/liboai.h b/liboai/include/liboai.h index 8fb628d..b0f9a46 100644 --- a/liboai/include/liboai.h +++ b/liboai/include/liboai.h @@ -41,7 +41,7 @@ #include "components/moderations.h" namespace liboai { - class OpenAI final { + class OpenAI { public: OpenAI() = default; OpenAI(OpenAI const&) = delete;