From 6cb5f5c35c2e8fa042f208f8e1a880b2190bbca1 Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 13:58:28 -0700 Subject: [PATCH 1/7] Add chat template kwargs to C API Preserve the existing API while allowing typed JSON context values for model-specific chat templates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- include/ortx_tokenizer.h | 24 ++++++ shared/api/c_api_tokenizer.cc | 11 ++- shared/api/chat_template.cc | 36 +++++---- shared/api/tokenizer_impl.h | 3 +- test/pp_api_test/test_tokenizer_chat.cc | 97 +++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 16 deletions(-) diff --git a/include/ortx_tokenizer.h b/include/ortx_tokenizer.h index 015b90a33..53d3b47c1 100644 --- a/include/ortx_tokenizer.h +++ b/include/ortx_tokenizer.h @@ -276,6 +276,30 @@ extError_t ORTX_API_CALL OrtxApplyChatTemplate(const OrtxTokenizer* tokenizer, c const char* input, const char* tools, OrtxTensorResult** output, bool add_generation_prompt, bool tokenize); +/** + * @brief Applies a chat template with additional template context values. + * + * Behaves like OrtxApplyChatTemplate, while also adding the properties from + * template_kwargs to the chat template context. template_kwargs must be a + * null-terminated JSON object or null. Core context properties such as messages, + * tools, and add_generation_prompt cannot be overridden. + * + * @param tokenizer Pointer to an OrtxTokenizer used for template processing. + * @param template_str Null-terminated string representing the chat template; can be null if tokenizer.json has one. + * @param input Null-terminated string containing the input to be processed. + * @param tools Null-terminated string containing the function tools. + * @param template_kwargs Null-terminated JSON object containing additional template context values; can be null. + * @param output Pointer to an OrtxTensorResult that will be populated with the output strings, + * if tokenize is true, the ids will be in the output as indexed 1. + * @param add_generation_prompt Indicates whether to add a generation prompt to the output. + * @param tokenize Indicates whether to tokenize the templated text to IDs. + * @return extError_t Returns an error code indicating success or the type of failure. + */ +extError_t ORTX_API_CALL OrtxApplyChatTemplateWithOptions(const OrtxTokenizer* tokenizer, const char* template_str, + const char* input, const char* tools, + const char* template_kwargs, OrtxTensorResult** output, + bool add_generation_prompt, bool tokenize); + #ifdef __cplusplus } #endif diff --git a/shared/api/c_api_tokenizer.cc b/shared/api/c_api_tokenizer.cc index 7584d7052..a09a88c40 100644 --- a/shared/api/c_api_tokenizer.cc +++ b/shared/api/c_api_tokenizer.cc @@ -467,6 +467,14 @@ extError_t ORTX_API_CALL OrtxApplyChatTemplate(const OrtxTokenizer* tokenizer, c const char* input, const char* tools, OrtxTensorResult** output, bool add_generation_prompt, bool tokenize) { + return OrtxApplyChatTemplateWithOptions(tokenizer, template_str, input, tools, nullptr, output, + add_generation_prompt, tokenize); +} + +extError_t ORTX_API_CALL OrtxApplyChatTemplateWithOptions(const OrtxTokenizer* tokenizer, const char* template_str, + const char* input, const char* tools, + const char* template_kwargs, OrtxTensorResult** output, + bool add_generation_prompt, bool tokenize) { if (tokenizer == nullptr && template_str == nullptr) { ReturnableStatus::last_error_message_ = "both tokenizer and template_str are null, no template to apply"; return kOrtxErrorInvalidArgument; @@ -485,7 +493,8 @@ extError_t ORTX_API_CALL OrtxApplyChatTemplate(const OrtxTokenizer* tokenizer, c std::string text; std::vector ids_vec; - status = token_ptr->ApplyChatTemplate(template_str, input, tools, text, ids_vec, add_generation_prompt, tokenize); + status = token_ptr->ApplyChatTemplate(template_str, input, tools, template_kwargs, text, ids_vec, + add_generation_prompt, tokenize); if (status.IsOk()) { auto result = std::make_unique(); std::vector> tensors; diff --git a/shared/api/chat_template.cc b/shared/api/chat_template.cc index 1b454a60e..568cc2fef 100644 --- a/shared/api/chat_template.cc +++ b/shared/api/chat_template.cc @@ -376,8 +376,9 @@ std::string normalize_tool_quotes(const std::string& input) { } OrtxStatus TokenizerImpl::ApplyChatTemplate(const char* template_str, const char* message, const char* tools, - std::string& output, std::vector& ids_vec, - bool add_generation_prompt, bool tokenize) const { + const char* template_kwargs, std::string& output, + std::vector& ids_vec, bool add_generation_prompt, + bool tokenize) const { OrtxStatus status; std::string input_str = minja::normalize_newlines(message); @@ -408,7 +409,18 @@ OrtxStatus TokenizerImpl::ApplyChatTemplate(const char* template_str, const char throw std::runtime_error("Invalid or unsupported chat template."); } - std::shared_ptr context; + json context_values = json::object(); + if (template_kwargs && *template_kwargs) { + auto parsed_kwargs = json::parse(minja::normalize_newlines(template_kwargs), nullptr, + /*allow_exceptions=*/false); + if (parsed_kwargs.is_discarded()) { + throw std::runtime_error("Invalid template_kwargs JSON."); + } + if (!parsed_kwargs.is_object()) { + throw std::runtime_error("template_kwargs must be a JSON object."); + } + context_values = std::move(parsed_kwargs); + } // Check Phi-4-mini tool call case for quote normalization bool phi_4_mini = false; @@ -462,20 +474,16 @@ OrtxStatus TokenizerImpl::ApplyChatTemplate(const char* template_str, const char tools_json = NormalizeTools(tools_str.c_str()); } - // Add tools to the context - context = minja::Context::make(json({ - {"messages", actual_messages}, - {"tools", tools_json}, - {"add_generation_prompt", add_generation_prompt}, - })); + context_values["tools"] = std::move(tools_json); } else { - // No tools input, just use the messages - context = minja::Context::make(json({ - {"messages", actual_messages}, - {"add_generation_prompt", add_generation_prompt}, - })); + context_values.erase("tools"); } + // Core request values take precedence over additional template kwargs. + context_values["messages"] = std::move(actual_messages); + context_values["add_generation_prompt"] = add_generation_prompt; + auto context = minja::Context::make(std::move(context_values)); + // Set required context values context->set("strftime_now", minja::Value::callable(strftime_function)); context->set("bos_token", tok_config_->bos_token_); diff --git a/shared/api/tokenizer_impl.h b/shared/api/tokenizer_impl.h index c919c8bff..7dfd53952 100644 --- a/shared/api/tokenizer_impl.h +++ b/shared/api/tokenizer_impl.h @@ -89,7 +89,8 @@ class TokenizerImpl : public OrtxObjectImpl { OrtxStatus Id2Token(extTokenId_t id, std::string& token, TokenizerDecodingState** state, bool skip_special_tokens) const; OrtxStatus GetDecoderPromptIds(size_t batch_size, const char* lang, const char* task, int no_timestamps, std::vector>& t_ids) const; - OrtxStatus ApplyChatTemplate(const char* template_str, const char* message, const char* tools, std::string& output, + OrtxStatus ApplyChatTemplate(const char* template_str, const char* message, const char* tools, + const char* template_kwargs, std::string& output, std::vector& ids_vec, bool add_generation_prompt, bool tokenize) const; private: diff --git a/test/pp_api_test/test_tokenizer_chat.cc b/test/pp_api_test/test_tokenizer_chat.cc index 90e5f3f03..2c881c50a 100644 --- a/test/pp_api_test/test_tokenizer_chat.cc +++ b/test/pp_api_test/test_tokenizer_chat.cc @@ -2255,4 +2255,101 @@ TEST(OrtxTokenizerTest, ChatTemplateDivisionByZero) { messages_json.c_str(), nullptr, result.ToBeAssigned(), false, false); EXPECT_NE(err, kOrtxOK) << "Expected modulo by zero to return an error."; } +} + +TEST(OrtxTokenizerTest, ChatTemplateAcceptsTypedTemplateKwargs) { + OrtxObjectPtr tokenizer(OrtxCreateTokenizer, "data/phi-4-base"); + ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); + + const std::string template_str = + R"({% if enable_thinking is defined and enable_thinking is false %}NO_THINK{% else %}THINK{% endif %}|{{ reasoning_effort }}|{{ level }})"; + const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; + const std::string template_kwargs = + R"({"enable_thinking":false,"reasoning_effort":"low","level":2})"; + OrtxObjectPtr result; + + auto err = OrtxApplyChatTemplateWithOptions( + tokenizer.get(), template_str.c_str(), messages_json.c_str(), nullptr, + template_kwargs.c_str(), result.ToBeAssigned(), true, false); + ASSERT_EQ(err, kOrtxOK) << OrtxGetLastErrorMessage(); + + OrtxObjectPtr tensor; + ASSERT_EQ(OrtxTensorResultGetAt(result.get(), 0, tensor.ToBeAssigned()), kOrtxOK); + const char* text = nullptr; + ASSERT_EQ(OrtxGetTensorData(tensor.get(), reinterpret_cast(&text), nullptr, nullptr), kOrtxOK); + EXPECT_STREQ(text, "NO_THINK|low|2"); +} + +TEST(OrtxTokenizerTest, ChatTemplateKwargsCannotOverrideCoreContext) { + OrtxObjectPtr tokenizer(OrtxCreateTokenizer, "data/phi-4-base"); + ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); + + const std::string template_str = + R"({{ messages[0].content }}|{% if add_generation_prompt %}GEN{% else %}NO_GEN{% endif %}|{% if tools is defined %}TOOLS{% else %}NO_TOOLS{% endif %})"; + const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; + const std::string template_kwargs = + R"({"messages":[{"role":"user","content":"Override"}],"add_generation_prompt":false,"tools":[{"name":"override"}]})"; + OrtxObjectPtr result; + + auto err = OrtxApplyChatTemplateWithOptions( + tokenizer.get(), template_str.c_str(), messages_json.c_str(), nullptr, + template_kwargs.c_str(), result.ToBeAssigned(), true, false); + ASSERT_EQ(err, kOrtxOK) << OrtxGetLastErrorMessage(); + + OrtxObjectPtr tensor; + ASSERT_EQ(OrtxTensorResultGetAt(result.get(), 0, tensor.ToBeAssigned()), kOrtxOK); + const char* text = nullptr; + ASSERT_EQ(OrtxGetTensorData(tensor.get(), reinterpret_cast(&text), nullptr, nullptr), kOrtxOK); + EXPECT_STREQ(text, "Hello|GEN|NO_TOOLS"); +} + +TEST(OrtxTokenizerTest, ChatTemplateRejectsInvalidTemplateKwargs) { + OrtxObjectPtr tokenizer(OrtxCreateTokenizer, "data/phi-4-base"); + ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); + + const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; + OrtxObjectPtr result; + + auto invalid_json = OrtxApplyChatTemplateWithOptions( + tokenizer.get(), "{{ messages[0].content }}", messages_json.c_str(), nullptr, + "{", result.ToBeAssigned(), true, false); + EXPECT_EQ(invalid_json, kOrtxErrorInvalidArgument); + EXPECT_STREQ(OrtxGetLastErrorMessage(), "Invalid template_kwargs JSON."); + + auto non_object = OrtxApplyChatTemplateWithOptions( + tokenizer.get(), "{{ messages[0].content }}", messages_json.c_str(), nullptr, + "[]", result.ToBeAssigned(), true, false); + EXPECT_EQ(non_object, kOrtxErrorInvalidArgument); + EXPECT_STREQ(OrtxGetLastErrorMessage(), "template_kwargs must be a JSON object."); +} + +TEST(OrtxTokenizerTest, LegacyChatTemplateApiMatchesNullTemplateKwargs) { + OrtxObjectPtr tokenizer(OrtxCreateTokenizer, "data/phi-4-base"); + ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); + + const std::string template_str = R"({{ messages[0].content }}|{{ add_generation_prompt }})"; + const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; + OrtxObjectPtr legacy_result; + OrtxObjectPtr options_result; + + ASSERT_EQ(OrtxApplyChatTemplate( + tokenizer.get(), template_str.c_str(), messages_json.c_str(), nullptr, + legacy_result.ToBeAssigned(), true, false), + kOrtxOK); + ASSERT_EQ(OrtxApplyChatTemplateWithOptions( + tokenizer.get(), template_str.c_str(), messages_json.c_str(), nullptr, nullptr, + options_result.ToBeAssigned(), true, false), + kOrtxOK); + + OrtxObjectPtr legacy_tensor; + OrtxObjectPtr options_tensor; + ASSERT_EQ(OrtxTensorResultGetAt(legacy_result.get(), 0, legacy_tensor.ToBeAssigned()), kOrtxOK); + ASSERT_EQ(OrtxTensorResultGetAt(options_result.get(), 0, options_tensor.ToBeAssigned()), kOrtxOK); + const char* legacy_text = nullptr; + const char* options_text = nullptr; + ASSERT_EQ(OrtxGetTensorData(legacy_tensor.get(), reinterpret_cast(&legacy_text), nullptr, nullptr), + kOrtxOK); + ASSERT_EQ(OrtxGetTensorData(options_tensor.get(), reinterpret_cast(&options_text), nullptr, nullptr), + kOrtxOK); + EXPECT_STREQ(legacy_text, options_text); } \ No newline at end of file From 60f0a2217c87eef4b664099e01004825bd26eeec Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:03:53 -0700 Subject: [PATCH 2/7] Add temporary branch validation workflow Run the repository C API build and C++ tests on Jenny's fork while upstream Azure Pipelines await maintainer authorization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- .github/workflows/branch-cpp-validation.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/branch-cpp-validation.yml diff --git a/.github/workflows/branch-cpp-validation.yml b/.github/workflows/branch-cpp-validation.yml new file mode 100644 index 000000000..38fffee18 --- /dev/null +++ b/.github/workflows/branch-cpp-validation.yml @@ -0,0 +1,16 @@ +name: Branch C++ validation + +on: + push: + branches: + - feature/chat-template-kwargs + +jobs: + c-api-tests: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Build C API and C++ tests + run: ./build.sh -DOCOS_ENABLE_C_API=ON + - name: Run C++ tests + run: ctest --test-dir out/Linux/RelWithDebInfo -C RelWithDebInfo --output-on-failure From 1e59db7c7028976079146bef22e866cc4623398a Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:10:36 -0700 Subject: [PATCH 3/7] Use supported Minja boolean syntax in test Exercise the same typed false value without relying on the unsupported is false predicate in an explicit template. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- test/pp_api_test/test_tokenizer_chat.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pp_api_test/test_tokenizer_chat.cc b/test/pp_api_test/test_tokenizer_chat.cc index 2c881c50a..4fa8fbb07 100644 --- a/test/pp_api_test/test_tokenizer_chat.cc +++ b/test/pp_api_test/test_tokenizer_chat.cc @@ -2262,7 +2262,7 @@ TEST(OrtxTokenizerTest, ChatTemplateAcceptsTypedTemplateKwargs) { ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); const std::string template_str = - R"({% if enable_thinking is defined and enable_thinking is false %}NO_THINK{% else %}THINK{% endif %}|{{ reasoning_effort }}|{{ level }})"; + R"({% if enable_thinking is defined and not enable_thinking %}NO_THINK{% else %}THINK{% endif %}|{{ reasoning_effort }}|{{ level }})"; const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; const std::string template_kwargs = R"({"enable_thinking":false,"reasoning_effort":"low","level":2})"; From c85f0e74c2f843a672d9123f3ac0b4798cf27eaf Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:16:11 -0700 Subject: [PATCH 4/7] Remove temporary fork validation workflow The repository C API build and full C++ test suite passed in fork run 32069739093. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- .github/workflows/branch-cpp-validation.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/workflows/branch-cpp-validation.yml diff --git a/.github/workflows/branch-cpp-validation.yml b/.github/workflows/branch-cpp-validation.yml deleted file mode 100644 index 38fffee18..000000000 --- a/.github/workflows/branch-cpp-validation.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Branch C++ validation - -on: - push: - branches: - - feature/chat-template-kwargs - -jobs: - c-api-tests: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - name: Build C API and C++ tests - run: ./build.sh -DOCOS_ENABLE_C_API=ON - - name: Run C++ tests - run: ctest --test-dir out/Linux/RelWithDebInfo -C RelWithDebInfo --output-on-failure From a888b1c916c0b97f72d95942d49d8d986acbe207 Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:17:26 -0700 Subject: [PATCH 5/7] Reject null tokenizer in chat template APIs Return an invalid-argument error instead of dereferencing a null tokenizer, with a focused regression test and temporary fork validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- .github/workflows/branch-cpp-validation.yml | 16 ++++++++++++++++ shared/api/c_api_tokenizer.cc | 4 ++-- test/pp_api_test/test_tokenizer_chat.cc | 11 +++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/branch-cpp-validation.yml diff --git a/.github/workflows/branch-cpp-validation.yml b/.github/workflows/branch-cpp-validation.yml new file mode 100644 index 000000000..38fffee18 --- /dev/null +++ b/.github/workflows/branch-cpp-validation.yml @@ -0,0 +1,16 @@ +name: Branch C++ validation + +on: + push: + branches: + - feature/chat-template-kwargs + +jobs: + c-api-tests: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Build C API and C++ tests + run: ./build.sh -DOCOS_ENABLE_C_API=ON + - name: Run C++ tests + run: ctest --test-dir out/Linux/RelWithDebInfo -C RelWithDebInfo --output-on-failure diff --git a/shared/api/c_api_tokenizer.cc b/shared/api/c_api_tokenizer.cc index a09a88c40..052f00b62 100644 --- a/shared/api/c_api_tokenizer.cc +++ b/shared/api/c_api_tokenizer.cc @@ -475,8 +475,8 @@ extError_t ORTX_API_CALL OrtxApplyChatTemplateWithOptions(const OrtxTokenizer* t const char* input, const char* tools, const char* template_kwargs, OrtxTensorResult** output, bool add_generation_prompt, bool tokenize) { - if (tokenizer == nullptr && template_str == nullptr) { - ReturnableStatus::last_error_message_ = "both tokenizer and template_str are null, no template to apply"; + if (tokenizer == nullptr) { + ReturnableStatus::last_error_message_ = "tokenizer is null"; return kOrtxErrorInvalidArgument; } diff --git a/test/pp_api_test/test_tokenizer_chat.cc b/test/pp_api_test/test_tokenizer_chat.cc index 4fa8fbb07..b7f561176 100644 --- a/test/pp_api_test/test_tokenizer_chat.cc +++ b/test/pp_api_test/test_tokenizer_chat.cc @@ -2323,6 +2323,17 @@ TEST(OrtxTokenizerTest, ChatTemplateRejectsInvalidTemplateKwargs) { EXPECT_STREQ(OrtxGetLastErrorMessage(), "template_kwargs must be a JSON object."); } +TEST(OrtxTokenizerTest, ChatTemplateRejectsNullTokenizerWithExplicitTemplate) { + const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; + OrtxObjectPtr result; + + auto err = OrtxApplyChatTemplateWithOptions( + nullptr, "{{ messages[0].content }}", messages_json.c_str(), nullptr, + nullptr, result.ToBeAssigned(), true, false); + EXPECT_EQ(err, kOrtxErrorInvalidArgument); + EXPECT_STREQ(OrtxGetLastErrorMessage(), "tokenizer is null"); +} + TEST(OrtxTokenizerTest, LegacyChatTemplateApiMatchesNullTemplateKwargs) { OrtxObjectPtr tokenizer(OrtxCreateTokenizer, "data/phi-4-base"); ASSERT_EQ(tokenizer.Code(), kOrtxOK) << OrtxGetLastErrorMessage(); From 35c1ca8cf63df1ccec21bb0111c4c2cd0032ce8b Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:23:30 -0700 Subject: [PATCH 6/7] Reject empty chat template kwargs Keep the public contract strict: callers must pass a JSON object or null, never an empty string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- shared/api/chat_template.cc | 5 ++++- test/pp_api_test/test_tokenizer_chat.cc | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/shared/api/chat_template.cc b/shared/api/chat_template.cc index 568cc2fef..23bd8aca9 100644 --- a/shared/api/chat_template.cc +++ b/shared/api/chat_template.cc @@ -410,7 +410,10 @@ OrtxStatus TokenizerImpl::ApplyChatTemplate(const char* template_str, const char } json context_values = json::object(); - if (template_kwargs && *template_kwargs) { + if (template_kwargs) { + if (*template_kwargs == '\0') { + throw std::runtime_error("template_kwargs must be a JSON object or null."); + } auto parsed_kwargs = json::parse(minja::normalize_newlines(template_kwargs), nullptr, /*allow_exceptions=*/false); if (parsed_kwargs.is_discarded()) { diff --git a/test/pp_api_test/test_tokenizer_chat.cc b/test/pp_api_test/test_tokenizer_chat.cc index b7f561176..211a55975 100644 --- a/test/pp_api_test/test_tokenizer_chat.cc +++ b/test/pp_api_test/test_tokenizer_chat.cc @@ -2310,6 +2310,12 @@ TEST(OrtxTokenizerTest, ChatTemplateRejectsInvalidTemplateKwargs) { const std::string messages_json = R"([{"role":"user","content":"Hello"}])"; OrtxObjectPtr result; + auto empty_string = OrtxApplyChatTemplateWithOptions( + tokenizer.get(), "{{ messages[0].content }}", messages_json.c_str(), nullptr, + "", result.ToBeAssigned(), true, false); + EXPECT_EQ(empty_string, kOrtxErrorInvalidArgument); + EXPECT_STREQ(OrtxGetLastErrorMessage(), "template_kwargs must be a JSON object or null."); + auto invalid_json = OrtxApplyChatTemplateWithOptions( tokenizer.get(), "{{ messages[0].content }}", messages_json.c_str(), nullptr, "{", result.ToBeAssigned(), true, false); From 338dc42ed2f30bcb417925010f8f721b0621a14c Mon Sep 17 00:00:00 2001 From: Jenny Ferries Date: Mon, 17 Aug 2026 14:28:53 -0700 Subject: [PATCH 7/7] Remove completed fork validation workflow The strict kwargs validation and null-tokenizer regression pass the full C API C++ suite in run 32070849855. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9f5bae1-1b88-4311-8fac-6224769e0400 --- .github/workflows/branch-cpp-validation.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/workflows/branch-cpp-validation.yml diff --git a/.github/workflows/branch-cpp-validation.yml b/.github/workflows/branch-cpp-validation.yml deleted file mode 100644 index 38fffee18..000000000 --- a/.github/workflows/branch-cpp-validation.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Branch C++ validation - -on: - push: - branches: - - feature/chat-template-kwargs - -jobs: - c-api-tests: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - name: Build C API and C++ tests - run: ./build.sh -DOCOS_ENABLE_C_API=ON - - name: Run C++ tests - run: ctest --test-dir out/Linux/RelWithDebInfo -C RelWithDebInfo --output-on-failure