| #pragma once |
| #include <onnxruntime_cxx_api.h> |
| #include <tokenizers_cpp.h> |
|
|
| #include <string> |
| #include <vector> |
| #include <memory> |
| #include <fstream> |
| #include <sstream> |
| #include <algorithm> |
| #include <cctype> |
| #include <unicode/unistr.h> |
| #include <unicode/normalizer2.h> |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| class EOUDetector { |
| public: |
| |
| static constexpr float THRESHOLD = 0.0766f; |
| static constexpr int MAX_TOKENS = 512; |
|
|
| struct Turn { |
| enum class Role { User, Assistant } role; |
| std::string text; |
| }; |
|
|
| |
| |
| |
| EOUDetector(const std::string& modelPath, |
| const std::string& tokenizerJson, |
| bool useGpu = false) |
| : env_(ORT_LOGGING_LEVEL_WARNING, "eou") |
| , memoryInfo_("Cpu", OrtDeviceAllocator, 0, OrtMemTypeDefault) |
| { |
| |
| Ort::SessionOptions opts; |
| opts.SetIntraOpNumThreads(1); |
| opts.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); |
| if (useGpu) { |
| OrtCUDAProviderOptions cuda{}; |
| cuda.device_id = 0; |
| opts.AppendExecutionProvider_CUDA(cuda); |
| } |
|
|
| std::string onnxPath = modelPath + "/model.onnx"; |
| session_ = std::make_unique<Ort::Session>(env_, onnxPath.c_str(), opts); |
|
|
| |
| inputName_ = session_->GetInputNameAllocated(0, allocator_).get(); |
| outputName_ = session_->GetOutputNameAllocated(0, allocator_).get(); |
|
|
| |
| std::ifstream f(tokenizerJson); |
| std::string json((std::istreambuf_iterator<char>(f)), |
| std::istreambuf_iterator<char>()); |
| tokenizer_ = tokenizers::Tokenizer::FromBlobJSON(json); |
| } |
|
|
| |
| |
| |
| |
| float score(const std::vector<Turn>& history, |
| const std::string& currentText) |
| { |
| std::vector<int64_t> ids = buildInputIds(history, currentText); |
|
|
| |
| int64_t seqLen = static_cast<int64_t>(ids.size()); |
| std::vector<int64_t> inputDims {1, seqLen}; |
| std::vector<int64_t> outputDims {1}; |
|
|
| |
| std::vector<const char*> inputNames {inputName_.c_str()}; |
| std::vector<const char*> outputNames {outputName_.c_str()}; |
|
|
| |
| std::vector<Ort::Value> inputTensors; |
| inputTensors.push_back( |
| Ort::Value::CreateTensor<int64_t>( |
| memoryInfo_, |
| ids.data(), ids.size(), |
| inputDims.data(), inputDims.size())); |
|
|
| |
| std::vector<float> outputValues(1); |
| std::vector<Ort::Value> outputTensors; |
| outputTensors.push_back( |
| Ort::Value::CreateTensor<float>( |
| memoryInfo_, |
| outputValues.data(), outputValues.size(), |
| outputDims.data(), outputDims.size())); |
|
|
| |
| session_->Run( |
| Ort::RunOptions{nullptr}, |
| inputNames.data(), inputTensors.data(), inputTensors.size(), |
| outputNames.data(), outputTensors.data(), outputTensors.size()); |
|
|
| return outputValues[0]; |
| } |
|
|
| bool isEndOfUtterance(const std::vector<Turn>& history, |
| const std::string& currentText) |
| { |
| return score(history, currentText) >= THRESHOLD; |
| } |
|
|
| private: |
| |
| |
| |
| |
| |
| static const std::vector<int64_t> SYSTEM_BLOCK; |
| static const std::vector<int64_t> USER_OPEN; |
| static const std::vector<int64_t> USER_CLOSE; |
| static const std::vector<int64_t> ASST_OPEN; |
| static const std::vector<int64_t> ASST_CLOSE; |
|
|
| |
| Ort::Env env_; |
| Ort::AllocatorWithDefaultOptions allocator_; |
| Ort::MemoryInfo memoryInfo_; |
| std::unique_ptr<Ort::Session> session_; |
| std::unique_ptr<tokenizers::Tokenizer> tokenizer_; |
| std::string inputName_; |
| std::string outputName_; |
|
|
| |
| |
| |
| std::string preprocess(const std::string& raw) |
| { |
| |
| icu::UnicodeString u = icu::UnicodeString::fromUTF8(raw); |
| UErrorCode err = U_ZERO_ERROR; |
| const icu::Normalizer2* nfkc = |
| icu::Normalizer2::getNFKCInstance(err); |
| u = nfkc->normalize(u, err); |
| u.foldCase(U_FOLD_CASE_DEFAULT); |
|
|
| std::string s; |
| u.toUTF8String(s); |
|
|
| |
| std::string out; |
| out.reserve(s.size()); |
| bool lastWasSpace = true; |
| for (unsigned char c : s) { |
| if (c == '\'' || c == '-') { |
| out += c; lastWasSpace = false; |
| } else if (std::ispunct(c)) { |
| |
| } else if (std::isspace(c)) { |
| if (!lastWasSpace) { out += ' '; lastWasSpace = true; } |
| } else { |
| out += c; lastWasSpace = false; |
| } |
| } |
| while (!out.empty() && out.back() == ' ') out.pop_back(); |
| return out; |
| } |
|
|
| |
| std::vector<int64_t> encode(const std::string& text) |
| { |
| auto ids32 = tokenizer_->Encode(text, false); |
| std::vector<int64_t> ids64(ids32.begin(), ids32.end()); |
| return ids64; |
| } |
|
|
| |
| static void append(std::vector<int64_t>& dst, const std::vector<int64_t>& src) |
| { |
| dst.insert(dst.end(), src.begin(), src.end()); |
| } |
|
|
| |
| std::vector<int64_t> buildInputIds(const std::vector<Turn>& history, |
| const std::string& currentText) |
| { |
| std::vector<int64_t> ids; |
| ids.reserve(MAX_TOKENS); |
|
|
| |
| append(ids, SYSTEM_BLOCK); |
|
|
| |
| std::vector<Turn> merged; |
| for (const auto& t : history) { |
| std::string clean = preprocess(t.text); |
| if (!merged.empty() && merged.back().role == t.role) |
| merged.back().text += " " + clean; |
| else |
| merged.push_back({t.role, clean}); |
| } |
|
|
| for (const auto& t : merged) { |
| if (t.role == Turn::Role::User) { |
| append(ids, USER_OPEN); |
| append(ids, encode(t.text)); |
| append(ids, USER_CLOSE); |
| } else { |
| append(ids, ASST_OPEN); |
| append(ids, encode(t.text)); |
| append(ids, ASST_CLOSE); |
| } |
| } |
|
|
| |
| append(ids, USER_OPEN); |
| append(ids, encode(preprocess(currentText))); |
|
|
| |
| if ((int)ids.size() > MAX_TOKENS) |
| ids = std::vector<int64_t>(ids.end() - MAX_TOKENS, ids.end()); |
|
|
| return ids; |
| } |
| }; |
|
|
| |
|
|
| const std::vector<int64_t> EOUDetector::SYSTEM_BLOCK = { |
| 151644, 8948, 198, |
| 2610, 525, 1207, 16948, 11, 3465, 553, 54364, 14817, |
| 13, 1446, 525, 264, 10950, 17847, 13, |
| 151645, 198 |
| }; |
| const std::vector<int64_t> EOUDetector::USER_OPEN = {151644, 872, 198}; |
| const std::vector<int64_t> EOUDetector::USER_CLOSE = {151645, 198}; |
| const std::vector<int64_t> EOUDetector::ASST_OPEN = {151644, 77091, 198}; |
| const std::vector<int64_t> EOUDetector::ASST_CLOSE = {151645, 198}; |
|
|