Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
bundled_models.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
9#include <entropic/entropic_config.h>
11#include "yaml_util.h"
12
13#include <cstdlib>
14#include <vector>
15#include <dlfcn.h>
16
17static auto s_log = entropic::log::get("config");
18
19namespace entropic::config {
20
21namespace {
22
40std::filesystem::path share_dir_from_library()
41{
42 Dl_info info = {};
43 if (dladdr(reinterpret_cast<void*>(&share_dir_from_library), &info) == 0
44 || info.dli_fname == nullptr) {
45 return {};
46 }
47 std::error_code ec;
48 auto abs_lib = std::filesystem::absolute(
49 std::filesystem::path(info.dli_fname), ec);
50 if (ec) {
51 return {};
52 }
53 return abs_lib.parent_path().parent_path() / "share" / "entropic";
54}
55
56} // namespace
57
66static std::string parse_bundled_entry(ryml::ConstNodeRef child,
67 BundledModelEntry& entry)
68{
69 entry.key = to_string(child.key());
70 extract(child, "name", entry.name);
71 extract(child, "url", entry.url);
72 extract(child, "size_gb", entry.size_gb);
73 extract(child, "adapter", entry.adapter);
74 extract(child, "description", entry.description);
75 extract(child, "mmproj_key", entry.mmproj_key);
76 // gh#62 (v2.3.0): optional structured selectors. Entries that
77 // don't declare them stay queryable by flat key only.
78 extract(child, "provider", entry.provider);
79 extract(child, "family", entry.family);
80 extract(child, "size_label", entry.size_label);
81 extract(child, "quant", entry.quant);
82 if (entry.name.empty()) {
83 return "bundled model '" + entry.key + "' missing 'name'";
84 }
85 return "";
86}
87
95std::string BundledModels::load(const std::filesystem::path& path)
96{
97 auto content = read_file(path);
98 if (content.empty()) {
99 return "cannot read " + path.string();
100 }
101
102 ryml::Tree tree = ryml::parse_in_arena(
103 ryml::to_csubstr(path.string()),
104 ryml::to_csubstr(content));
105 ryml::ConstNodeRef root = tree.rootref();
106 if (!root.is_map()) {
107 return "bundled_models.yaml root is not a mapping";
108 }
109
110 std::string err;
111 for (auto child : root) {
112 BundledModelEntry entry;
113 err = parse_bundled_entry(child, entry);
114 if (!err.empty()) { break; }
115 s_log->info("Registered bundled model: {} -> {} ({:.1f} GB)",
116 entry.key, entry.name, entry.size_gb);
117 entries_[entry.key] = std::move(entry);
118 }
119 return err;
120}
121
129bool BundledModels::contains(const std::string& key) const
130{
131 return entries_.count(key) > 0;
132}
133
141const BundledModelEntry* BundledModels::get(const std::string& key) const
142{
143 auto it = entries_.find(key);
144 if (it == entries_.end()) {
145 return nullptr;
146 }
147 return &it->second;
148}
149
162 const std::string& family,
163 const std::string& size_label,
164 const std::string& quant) const
165{
166 for (const auto& [key, entry] : entries_) {
167 if (entry.family == family
168 && entry.size_label == size_label
169 && entry.quant == quant) {
170 return key;
171 }
172 }
173 return "";
174}
175
199std::filesystem::path BundledModels::resolve(const std::string& value) const
200{
201 auto it = entries_.find(value);
202 if (it == entries_.end()) {
203 return expand_home(std::filesystem::path(value));
204 }
205
206 const auto filename = it->second.name + ".gguf";
207 std::filesystem::path result;
208 const char* reason = nullptr;
209
210 // Priority 1: explicit operator override via env var always wins.
211 // Deliberately does NOT check existence — the user may be pointing
212 // at a download destination that hasn't been populated yet.
213 if (const char* env = std::getenv("ENTROPIC_MODEL_DIR"); env && *env) {
214 result = std::filesystem::path(env) / filename;
215 reason = "ENTROPIC_MODEL_DIR";
216 } else {
217 // Priority 2-3: user home, then system. First existing file wins;
218 // if neither exists, home_path is returned so error messages
219 // point at the most-likely-intended location.
220 const auto home_path =
221 expand_home("~") / ".entropic" / "models" / filename;
222 const auto sys_path =
223 std::filesystem::path("/opt/entropic/models") / filename;
224 if (std::filesystem::is_regular_file(sys_path)
225 && !std::filesystem::is_regular_file(home_path)) {
226 result = sys_path;
227 reason = "/opt/entropic/models";
228 } else {
229 result = home_path;
230 reason = std::filesystem::is_regular_file(home_path)
231 ? "~/.entropic/models"
232 : "fallback (file not found)";
233 }
234 }
235
236 s_log->info("Model '{}' resolved to {} ({})",
237 value, result.string(), reason);
238 return result;
239}
240
247const std::unordered_map<std::string, BundledModelEntry>&
249{
250 return entries_;
251}
252
268 // Discovery order mirrors data_dir.cpp::resolve_data_dir so the
269 // registry is found the same way as prompts/grammars/schemas:
270 // 1. ENTROPIC_DATA_DIR env (explicit operator override)
271 // 2. binary-relative via dladdr (portable across install prefixes)
272 // 3-5. compile-time install path, source tree, CWD (dev/build-host)
273 // Pre-v2.3.6 this list was only 3-5 — purely compile-time/CWD — so
274 // a relocated install (any machine that isn't the build host, incl.
275 // container-staged release binaries) silently loaded zero models.
276 std::vector<std::filesystem::path> candidates;
277 if (const char* env = std::getenv("ENTROPIC_DATA_DIR"); env && *env) {
278 candidates.emplace_back(std::filesystem::path(env) / "bundled_models.yaml");
279 }
280 if (auto from_lib = share_dir_from_library(); !from_lib.empty()) {
281 candidates.emplace_back(from_lib / "bundled_models.yaml");
282 }
283 candidates.emplace_back(
284 std::filesystem::path(CONFIG_ENTROPIC_DATA_DIR) / "bundled_models.yaml");
285 candidates.emplace_back(
286 std::filesystem::path(CONFIG_ENTROPIC_SOURCE_DATA_DIR) / "bundled_models.yaml");
287 candidates.emplace_back("data/bundled_models.yaml");
288
289 for (const auto& path : candidates) {
290 if (std::filesystem::is_regular_file(path)) {
291 auto err = load(path);
292 if (err.empty()) {
293 s_log->info("pre-loaded {} bundled model(s) from {}",
294 entries_.size(), path.string());
295 return "";
296 }
297 s_log->warn("bundled models load failed: {} — {}", path.string(), err);
298 }
299 }
300 return "bundled_models.yaml not found in default paths";
301}
302
303} // namespace entropic::config
static std::string parse_bundled_entry(ryml::ConstNodeRef child, BundledModelEntry &entry)
Parse one bundled-model YAML entry into entry.
Bundled model registry — resolves keys to filesystem paths.
bool contains(const std::string &key) const
Check if a key exists in the registry.
const std::unordered_map< std::string, BundledModelEntry > & entries() const
Get all entries.
std::string auto_discover_and_load()
Auto-discover and load bundled_models.yaml.
std::filesystem::path resolve(const std::string &value) const
Resolve a model reference to a filesystem path.
std::string find_by(const std::string &family, const std::string &size_label, const std::string &quant) const
Look up a registry key by (family, size, quant) (gh#62).
const BundledModelEntry * get(const std::string &key) const
Get entry by key.
std::string load(const std::filesystem::path &path)
Load registry from YAML file.
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
Entry in the bundled model registry.
std::string size_label
e.g. "0_8b", "2b", "4b", "9b", "e2b", "nano_4b"
std::string key
Registry key (e.g., "primary")
std::string description
Human-readable description.
double size_gb
Model size in GB.
std::string provider
e.g. "unsloth"
std::string mmproj_key
Paired mmproj registry key, or empty (gh#42, v2.1.8).
std::string family
e.g. "qwen3_5", "gemma4", "nemotron3"
std::string quant
e.g. "Q8_0", "Q4_K_M", "UD-IQ4_XS"
std::string name
Model filename stem (e.g., "Qwen3.5-35B-A3B-UD-IQ3_XXS")
std::string adapter
Adapter name (e.g., "qwen35")
std::filesystem::path expand_home(const std::filesystem::path &p)
Expand ~ to home directory in a path.
Definition yaml_util.cpp:61
std::string read_file(const std::filesystem::path &path)
Read a file into a string.
Definition yaml_util.cpp:39
std::string to_string(c4::csubstr s)
Convert ryml csubstr to std::string.
Definition yaml_util.cpp:27
bool extract(ryml::ConstNodeRef node, c4::csubstr key, std::string &out)
Extract a string value from a YAML node.
Definition yaml_util.cpp:85
ryml extraction helpers for config parsing.