Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
grammar_registry.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
11
12#include <llama.h>
13
14#include <fstream>
15#include <sstream>
16
17namespace entropic {
18
19namespace {
20auto logger = entropic::log::get("inference.grammar_registry");
21
29std::string read_file_contents(const std::filesystem::path& path) {
30 std::ifstream file(path);
31 if (!file.is_open()) {
32 return "";
33 }
34 std::ostringstream ss;
35 ss << file.rdbuf();
36 return ss.str();
37}
38
39} // anonymous namespace
40
55static std::optional<GrammarEntry> build_grammar_entry(
56 const std::filesystem::path& path) {
57 std::string content = read_file_contents(path);
58 if (content.empty()) {
59 logger->warn("Empty or unreadable grammar file: {}", path.string());
60 return std::nullopt;
61 }
62 std::string error = GrammarRegistry::validate(content);
63 GrammarEntry ge;
64 ge.key = path.stem().string();
65 ge.gbnf_content = std::move(content);
66 ge.source = "bundled";
67 ge.validated = error.empty();
68 ge.error = std::move(error);
69 if (!ge.validated) {
70 logger->warn("Bundled grammar '{}' has validation errors: {}",
71 ge.key, ge.error);
72 }
73 return ge;
74}
75
82 const std::filesystem::path& grammar_dir)
83{
84 if (!std::filesystem::is_directory(grammar_dir)) {
85 logger->warn("Grammar directory not found: {}",
86 grammar_dir.string());
87 return 0;
88 }
89
90 size_t count = 0;
91 for (const auto& entry : std::filesystem::directory_iterator(grammar_dir)) {
92 if (entry.path().extension() != ".gbnf") {
93 continue;
94 }
95 auto ge = build_grammar_entry(entry.path());
96 if (!ge) {
97 continue;
98 }
99 std::string key = ge->key;
100 std::lock_guard<std::mutex> lock(registry_mutex_);
101 grammars_[key] = std::move(*ge);
102 ++count;
103 }
104
105 logger->info("Loaded {} bundled grammar(s)", count);
106 return count;
107}
108
119 const std::string& key,
120 const std::string& gbnf_content,
121 const std::string& source)
122{
123 std::lock_guard<std::mutex> lock(registry_mutex_);
124 if (grammars_.count(key) > 0) {
125 logger->warn("Grammar key '{}' already registered", key);
126 return false;
127 }
128
129 std::string error = validate(gbnf_content);
130 GrammarEntry ge;
131 ge.key = key;
132 ge.gbnf_content = gbnf_content;
133 ge.source = source;
134 ge.validated = error.empty();
135 ge.error = std::move(error);
136
137 grammars_[key] = std::move(ge);
138 logger->info("Registered grammar '{}' (source={}, valid={})",
139 key, source, grammars_[key].validated);
140 return true;
141}
142
152 const std::string& key,
153 const std::filesystem::path& path)
154{
155 std::string content = read_file_contents(path);
156 if (content.empty()) {
157 logger->error("Cannot read grammar file: {}", path.string());
158 return false;
159 }
160
161 std::string resolved_key = key.empty()
162 ? path.stem().string() : key;
163 return register_grammar(resolved_key, content, "file");
164}
165
173bool GrammarRegistry::deregister(const std::string& key) {
174 std::lock_guard<std::mutex> lock(registry_mutex_);
175 auto it = grammars_.find(key);
176 if (it == grammars_.end()) {
177 return false;
178 }
179 grammars_.erase(it);
180 logger->info("Deregistered grammar '{}'", key);
181 return true;
182}
183
191std::string GrammarRegistry::get(const std::string& key) const {
192 std::lock_guard<std::mutex> lock(registry_mutex_);
193 auto it = grammars_.find(key);
194 if (it == grammars_.end()) {
195 return "";
196 }
197 return it->second.gbnf_content;
198}
199
207bool GrammarRegistry::has(const std::string& key) const {
208 std::lock_guard<std::mutex> lock(registry_mutex_);
209 return grammars_.count(key) > 0;
210}
211
219GrammarEntry GrammarRegistry::entry(const std::string& key) const {
220 std::lock_guard<std::mutex> lock(registry_mutex_);
221 auto it = grammars_.find(key);
222 if (it == grammars_.end()) {
223 return {};
224 }
225 return it->second;
226}
227
234std::vector<GrammarEntry> GrammarRegistry::list() const {
235 std::lock_guard<std::mutex> lock(registry_mutex_);
236 std::vector<GrammarEntry> result;
237 result.reserve(grammars_.size());
238 for (const auto& [k, e] : grammars_) {
239 GrammarEntry meta;
240 meta.key = e.key;
241 meta.source = e.source;
242 meta.validated = e.validated;
243 meta.error = e.error;
244 result.push_back(std::move(meta));
245 }
246 return result;
247}
248
256std::string GrammarRegistry::validate(const std::string& gbnf_content) {
257 if (gbnf_content.empty()) {
258 return "empty grammar string";
259 }
260
261 // Use llama_sampler_init_grammar with nullptr vocab for validation.
262 // Grammar parsing is independent of vocabulary — the GBNF parser
263 // only needs the grammar string. Returns nullptr on parse failure.
264 llama_sampler* sampler = llama_sampler_init_grammar(
265 nullptr, gbnf_content.c_str(), "root");
266
267 if (sampler == nullptr) {
268 return "GBNF parse failed";
269 }
270
271 llama_sampler_free(sampler);
272 return "";
273}
274
281size_t GrammarRegistry::size() const {
282 std::lock_guard<std::mutex> lock(registry_mutex_);
283 return grammars_.size();
284}
285
292 std::lock_guard<std::mutex> lock(registry_mutex_);
293 grammars_.clear();
294 logger->info("Cleared all grammars");
295}
296
297} // namespace entropic
size_t load_bundled(const std::filesystem::path &grammar_dir)
Load all bundled grammars from a directory.
size_t size() const
Number of registered grammars.
static std::string validate(const std::string &gbnf_content)
Validate a GBNF grammar string.
GrammarEntry entry(const std::string &key) const
Get full entry metadata for a grammar key.
bool deregister(const std::string &key)
Remove a grammar from the registry.
bool has(const std::string &key) const
Check if a grammar key exists in the registry.
std::vector< GrammarEntry > list() const
List all registered grammar keys.
void clear()
Remove all registered grammars.
bool register_from_file(const std::string &key, const std::filesystem::path &path)
Register a grammar from a file path.
std::string get(const std::string &key) const
Get GBNF content string for a grammar key.
bool register_grammar(const std::string &key, const std::string &gbnf_content, const std::string &source="runtime")
Register a grammar by key with GBNF content string.
GrammarRegistry — named grammar management and validation.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Definition logging.cpp:211
Activate model on GPU (WARM → ACTIVE).
@ error
Tool server returned an error payload.
static std::optional< GrammarEntry > build_grammar_entry(const std::filesystem::path &path)
Load all bundled grammars from a directory.
Metadata for a registered grammar.
Definition config.h:80
std::string source
Origin: "bundled", "file", "runtime", "dynamic".
Definition config.h:83
std::string key
Unique registry key (e.g., "compactor", "chess_executor")
Definition config.h:81
bool validated
true if grammar has passed validation
Definition config.h:84
std::string error
Non-empty if validation failed.
Definition config.h:85
std::string gbnf_content
Raw GBNF grammar string.
Definition config.h:82