Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions include/contracts/config_schema_validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "contracts/config_schema.h"
#include "contracts/error_codes.h"
#include "contracts/json_pointer.h"

namespace llm_edgeflow {

Expand Down Expand Up @@ -208,19 +209,6 @@ inline bool ValidateConfigFieldDefinitions(

namespace detail {

inline std::string ConfigPointerToken(const std::string& value) {
std::string escaped;
for (char c : value) {
if (c == '~')
escaped += "~0";
else if (c == '/')
escaped += "~1";
else
escaped += c;
}
return escaped;
}

inline bool ConfigError(std::vector<ConfigFieldValidationError>* errors,
const std::string& path, ConfigFieldErrorKind kind,
std::string message) {
Expand All @@ -242,14 +230,14 @@ inline bool NormalizeConfigObject(
std::find_if(fields.begin(), fields.end(),
[&](const auto& f) { return f.name == it.key(); });
if (found == fields.end()) {
ConfigError(errors, path + "/" + ConfigPointerToken(it.key()),
ConfigError(errors, path + "/" + EscapeJsonPointer(it.key()),
ConfigFieldErrorKind::kUnknownField,
"Unknown config field: " + it.key());
ok = false;
}
}
for (const auto& field : fields) {
const auto field_path = path + "/" + ConfigPointerToken(field.name);
const auto field_path = path + "/" + EscapeJsonPointer(field.name);
const auto entry = input.find(field.name);
if (entry == input.end() && field.required) {
ConfigError(errors, field_path, ConfigFieldErrorKind::kMissingField,
Expand Down Expand Up @@ -334,7 +322,7 @@ inline bool NormalizeConfigValue(
bool ok = true;
for (auto it = value.begin(); it != value.end(); ++it) {
const auto key = value.is_array() ? std::to_string(it - value.begin())
: ConfigPointerToken(it.key());
: EscapeJsonPointer(it.key());
nlohmann::json next;
if (!NormalizeConfigValue(*field.items, *it, &next, errors,
path + "/" + key)) {
Expand Down
4 changes: 2 additions & 2 deletions include/contracts/parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -876,8 +876,8 @@ class Parameters {
ParamsT params{};
for (const auto& binding : bindings_) {
if (!binding->Assign(normalized, &params, error)) {
SetDiagnosticNoexcept(
field_path, "/" + detail::ConfigPointerToken(binding->Name()));
SetDiagnosticNoexcept(field_path,
"/" + EscapeJsonPointer(binding->Name()));
return std::nullopt;
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/engine/backends/inferlet/inferlet_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,9 @@ class InferletSession : public Interface {
static_cast<int>(decoded.size()), &text_size, 0),
"Decode");
decoded.resize(static_cast<size_t>(text_size));
size_t stop_position = decoded.size();
for (const auto& word : options.stop_words) {
stop_position = std::min(stop_position, decoded.find(word));
}
decoded.resize(stop_position);
const auto stop_position =
text_generation::FindFirstStop(decoded, options.stop_words);
if (stop_position != std::string::npos) decoded.resize(stop_position);
*output = std::move(decoded);
}
utf8::StripIncompleteSuffix(output);
Expand Down
8 changes: 3 additions & 5 deletions src/engine/backends/kite_llm/kite_llm_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,9 @@ class KiteSession : public Interface {
static_cast<int>(decoded.size()), &text_size, 0),
"Decode");
decoded.resize(static_cast<size_t>(text_size));
size_t stop_position = decoded.size();
for (const auto& word : options.stop_words) {
stop_position = std::min(stop_position, decoded.find(word));
}
decoded.resize(stop_position);
const auto stop_position =
text_generation::FindFirstStop(decoded, options.stop_words);
if (stop_position != std::string::npos) decoded.resize(stop_position);
*output = std::move(decoded);
}
utf8::StripIncompleteSuffix(output);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ bool SampleToken(const std::vector<float>& source_logits,
return true;
}

} // namespace

size_t FindFirstStop(const std::string& text,
const std::vector<std::string>& stop_words) noexcept {
size_t first = std::string::npos;
Expand All @@ -155,8 +157,6 @@ size_t FindFirstStop(const std::string& text,
return first;
}

} // namespace

bool ValidateGenerateOptions(const GenerateOptions& options,
std::string* diagnostic) noexcept {
try {
Expand Down
4 changes: 4 additions & 0 deletions src/engine/text_generation/common_autoregressive_generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class IAutoregressiveDecoder {
bool ValidateGenerateOptions(const GenerateOptions& options,
std::string* diagnostic = nullptr) noexcept;

// Returns std::string::npos when no stop word occurs in text.
size_t FindFirstStop(const std::string& text,
const std::vector<std::string>& stop_words) noexcept;

class CommonAutoregressiveGenerator final {
public:
static int Generate(IAutoregressiveDecoder& decoder,
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/engine/test_llama_cpp_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ TEST(LlamaCppBackendTest,
}
}

TEST(LlamaCppBackendTest, StopWordLookupUsesEarliestMatchRegardlessOfOrder) {
EXPECT_EQ(text_generation::FindFirstStop("before<end>after<stop>",
{"<stop>", "missing", "<end>"}),
6u);
EXPECT_EQ(text_generation::FindFirstStop("<end>after", {"<end>"}), 0u);
EXPECT_EQ(text_generation::FindFirstStop("text", {"missing"}),
std::string::npos);
EXPECT_EQ(text_generation::FindFirstStop("text", {}), std::string::npos);
}

TEST(LlamaCppBackendTest, GeneratorStopsOnWordSpanningDecodedPieces) {
// The decoder emits "prefix" on every step; "fixpre" spans two steps.
FailingDecoder decoder(-1);
GenerateOptions options;
options.max_tokens = 3;
options.temperature = 0.0f;
options.stop_words = {"missing", "fixpre"};
std::string output, diagnostic;
ASSERT_EQ(text_generation::CommonAutoregressiveGenerator::Generate(
decoder, "prompt", false, options, 0, &output, &diagnostic),
error::kOk);
EXPECT_EQ(output, "pre");

options.stop_words.clear();
ASSERT_EQ(text_generation::CommonAutoregressiveGenerator::Generate(
decoder, "prompt", false, options, 0, &output, &diagnostic),
error::kOk);
EXPECT_EQ(output, "prefixprefixprefix");
}

#ifdef HAVE_LLAMACPP
class InvalidGguf {
public:
Expand Down
Loading