Entropic 2.11.1
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
84 const std::filesystem::path& grammar_dir)
85{
86 if (!std::filesystem::is_directory(grammar_dir)) {
87 logger->warn("Grammar directory not found: {}",
88 grammar_dir.string());
89 return 0;
90 }
91
92 size_t count = 0;
93 for (const auto& entry : std::filesystem::directory_iterator(grammar_dir)) {
94 if (entry.path().extension() != ".gbnf") {
95 continue;
96 }
97 auto ge = build_grammar_entry(entry.path());
98 if (!ge) {
99 continue;
100 }
101 std::string key = ge->key;
102 std::lock_guard<std::mutex> lock(registry_mutex_);
103 grammars_[key] = std::move(*ge);
104 ++count;
105 }
106
107 logger->info("Loaded {} bundled grammar(s)", count);
108 return count;
109}
110
123 const std::string& key,
124 const std::string& gbnf_content,
125 const std::string& source)
126{
127 std::lock_guard<std::mutex> lock(registry_mutex_);
128 if (grammars_.count(key) > 0) {
129 logger->warn("Grammar key '{}' already registered", key);
130 return false;
131 }
132
133 std::string error = validate(gbnf_content);
134 GrammarEntry ge;
135 ge.key = key;
136 ge.gbnf_content = gbnf_content;
137 ge.source = source;
138 ge.validated = error.empty();
139 ge.error = std::move(error);
140
141 grammars_[key] = std::move(ge);
142 logger->info("Registered grammar '{}' (source={}, valid={})",
143 key, source, grammars_[key].validated);
144 return true;
145}
146
157 const std::string& key,
158 const std::filesystem::path& path)
159{
160 std::string content = read_file_contents(path);
161 if (content.empty()) {
162 logger->error("Cannot read grammar file: {}", path.string());
163 return false;
164 }
165
166 std::string resolved_key = key.empty()
167 ? path.stem().string() : key;
168 return register_grammar(resolved_key, content, "file");
169}
170
178bool GrammarRegistry::deregister(const std::string& key) {
179 std::lock_guard<std::mutex> lock(registry_mutex_);
180 auto it = grammars_.find(key);
181 if (it == grammars_.end()) {
182 return false;
183 }
184 grammars_.erase(it);
185 logger->info("Deregistered grammar '{}'", key);
186 return true;
187}
188
196std::string GrammarRegistry::get(const std::string& key) const {
197 std::lock_guard<std::mutex> lock(registry_mutex_);
198 auto it = grammars_.find(key);
199 if (it == grammars_.end()) {
200 return "";
201 }
202 return it->second.gbnf_content;
203}
204
212bool GrammarRegistry::has(const std::string& key) const {
213 std::lock_guard<std::mutex> lock(registry_mutex_);
214 return grammars_.count(key) > 0;
215}
216
224GrammarEntry GrammarRegistry::entry(const std::string& key) const {
225 std::lock_guard<std::mutex> lock(registry_mutex_);
226 auto it = grammars_.find(key);
227 if (it == grammars_.end()) {
228 return {};
229 }
230 return it->second;
231}
232
239std::vector<GrammarEntry> GrammarRegistry::list() const {
240 std::lock_guard<std::mutex> lock(registry_mutex_);
241 std::vector<GrammarEntry> result;
242 result.reserve(grammars_.size());
243 for (const auto& [k, e] : grammars_) {
244 GrammarEntry meta;
245 meta.key = e.key;
246 meta.source = e.source;
247 meta.validated = e.validated;
248 meta.error = e.error;
249 result.push_back(std::move(meta));
250 }
251 return result;
252}
253
263std::string GrammarRegistry::validate(const std::string& gbnf_content) {
264 if (gbnf_content.empty()) {
265 return "empty grammar string";
266 }
267
268 // Use llama_sampler_init_grammar with nullptr vocab for validation.
269 // Grammar parsing is independent of vocabulary — the GBNF parser
270 // only needs the grammar string. Returns nullptr on parse failure.
271 llama_sampler* sampler = llama_sampler_init_grammar(
272 nullptr, gbnf_content.c_str(), "root");
273
274 if (sampler == nullptr) {
275 return "GBNF parse failed";
276 }
277
278 llama_sampler_free(sampler);
279 return "";
280}
281
288size_t GrammarRegistry::size() const {
289 std::lock_guard<std::mutex> lock(registry_mutex_);
290 return grammars_.size();
291}
292
299 std::lock_guard<std::mutex> lock(registry_mutex_);
300 grammars_.clear();
301 logger->info("Cleared all grammars");
302}
303
304} // 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.
@ count
Sentinel — MUST remain last.
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