Skip to content

Commit ef16dec

Browse files
committed
Expose chat template, special tokens, and full metadata via ModelMeta
Enrich the existing getModelMetaJson native method and the ModelMeta wrapper with three read-only introspection fields, so callers can inspect a loaded model without a second round-trip: - chat_template - the model's resolved Jinja template (getChatTemplate) - special_tokens - bos/eos/eot (plus sep/nl/pad in the JSON) via getBosTokenId/getEosTokenId/getEotTokenId - metadata - the full GGUF key/value map via getMetadata(key), capped at 2 KB per value so large array metadata (tokenizer tokens/merges) cannot bloat the JSON No new native method or JNI signature change: the fields ride the existing getModelMetaJson payload, and absent fields default cleanly on the Java side. Verified: libjllama builds and links against b9842, loads cleanly (NativeLibraryLoadSmokeTest), and the ModelMeta unit tests pass (11 total).
1 parent b5ee309 commit ef16dec

3 files changed

Lines changed: 105 additions & 0 deletions

File tree

src/main/cpp/jllama.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,12 +767,22 @@ JNIEXPORT void JNICALL Java_net_ladenthin_llama_LlamaModel_loadModelWithProgress
767767
load_model_impl(env, obj, jparams, callback);
768768
}
769769

770+
// Build the special-token id map (a token is -1 / LLAMA_TOKEN_NULL when the model defines none).
771+
static json special_tokens_json(const llama_vocab *vocab) {
772+
return {
773+
{"bos", llama_vocab_bos(vocab)}, {"eos", llama_vocab_eos(vocab)},
774+
{"eot", llama_vocab_eot(vocab)}, {"sep", llama_vocab_sep(vocab)},
775+
{"nl", llama_vocab_nl(vocab)}, {"pad", llama_vocab_pad(vocab)},
776+
};
777+
}
778+
770779
JNIEXPORT jstring JNICALL Java_net_ladenthin_llama_LlamaModel_getModelMetaJson(JNIEnv *env, jobject obj) {
771780
REQUIRE_SERVER_CONTEXT(nullptr);
772781
if (jctx->vocab_only) {
773782
json meta = {
774783
{"vocab_type", llama_vocab_type(jctx->vocab)},
775784
{"n_vocab", llama_vocab_n_tokens(jctx->vocab)},
785+
{"special_tokens", special_tokens_json(jctx->vocab)},
776786
};
777787
return json_to_jstring_impl(env, meta);
778788
}
@@ -794,6 +804,26 @@ JNIEXPORT jstring JNICALL Java_net_ladenthin_llama_LlamaModel_getModelMetaJson(J
794804
{"name", m.model_name},
795805
{"architecture", std::string(arch_buf)},
796806
};
807+
// Resolved default chat template (Jinja); empty when the model ships none.
808+
const char *chat_tmpl = mdl != nullptr ? llama_model_chat_template(mdl, /*name*/ nullptr) : nullptr;
809+
j["chat_template"] = chat_tmpl != nullptr ? std::string(chat_tmpl) : std::string();
810+
j["special_tokens"] = special_tokens_json(jctx->vocab);
811+
// Full GGUF metadata key/value map.
812+
if (mdl != nullptr) {
813+
json meta_map = json::object();
814+
const int meta_count = llama_model_meta_count(mdl);
815+
for (int i = 0; i < meta_count; i++) {
816+
char key_buf[256] = {};
817+
// ponytail: 2 KB/value cap — scalar metadata fits; huge array values
818+
// (tokenizer tokens/merges) truncate rather than bloating the JSON.
819+
char val_buf[2048] = {};
820+
if (llama_model_meta_key_by_index(mdl, i, key_buf, sizeof(key_buf)) >= 0 &&
821+
llama_model_meta_val_str_by_index(mdl, i, val_buf, sizeof(val_buf)) >= 0) {
822+
meta_map[std::string(key_buf)] = std::string(val_buf);
823+
}
824+
}
825+
j["metadata"] = std::move(meta_map);
826+
}
797827
return json_to_jstring_impl(env, j);
798828
}
799829

src/main/java/net/ladenthin/llama/value/ModelMeta.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,55 @@ public String getModelName() {
129129
return node.path("name").asText("");
130130
}
131131

132+
/**
133+
* The model's resolved default chat template (Jinja), from GGUF
134+
* {@code tokenizer.chat_template} metadata.
135+
*
136+
* @return the chat template string, or {@code ""} if the model ships none
137+
*/
138+
public String getChatTemplate() {
139+
return node.path("chat_template").asText("");
140+
}
141+
142+
/**
143+
* Beginning-of-sentence token id.
144+
*
145+
* @return the BOS token id, or {@code -1} if the model defines none
146+
*/
147+
public int getBosTokenId() {
148+
return node.at("/special_tokens/bos").asInt(-1);
149+
}
150+
151+
/**
152+
* End-of-sentence token id.
153+
*
154+
* @return the EOS token id, or {@code -1} if the model defines none
155+
*/
156+
public int getEosTokenId() {
157+
return node.at("/special_tokens/eos").asInt(-1);
158+
}
159+
160+
/**
161+
* End-of-turn token id (used by chat- and FIM-aware models).
162+
*
163+
* @return the EOT token id, or {@code -1} if the model defines none
164+
*/
165+
public int getEotTokenId() {
166+
return node.at("/special_tokens/eot").asInt(-1);
167+
}
168+
169+
/**
170+
* Look up a raw GGUF metadata value by key (e.g. {@code "general.architecture"},
171+
* {@code "general.quantization_version"}). Large array metadata (tokenizer tokens/merges)
172+
* is truncated by the native layer, not returned in full.
173+
*
174+
* @param key the GGUF metadata key
175+
* @return the metadata value as a string, or {@code ""} if the key is absent
176+
*/
177+
public String getMetadata(String key) {
178+
return node.path("metadata").path(key).asText("");
179+
}
180+
132181
/**
133182
* Returns the underlying {@link JsonNode} for direct access to any field,
134183
* including fields added in future llama.cpp versions.

src/test/java/net/ladenthin/llama/value/ModelMetaTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,30 @@ public void testToStringContainsNewFields() throws Exception {
133133
assertThat(json, containsString("\"llama\""));
134134
assertThat(json, containsString("\"CodeLlama-7B\""));
135135
}
136+
137+
@Test
138+
public void testChatTemplateSpecialTokensAndMetadata() throws Exception {
139+
ModelMeta meta = parse("{\"n_vocab\":32000,"
140+
+ "\"chat_template\":\"{% for m in messages %}{{ m.content }}{% endfor %}\","
141+
+ "\"special_tokens\":{\"bos\":1,\"eos\":2,\"eot\":32000,\"sep\":-1,\"nl\":13,\"pad\":-1},"
142+
+ "\"metadata\":{\"general.architecture\":\"llama\",\"general.quantization_version\":\"2\"}}");
143+
144+
assertThat(meta.getChatTemplate(), containsString("for m in messages"));
145+
assertThat(meta.getBosTokenId(), is(1));
146+
assertThat(meta.getEosTokenId(), is(2));
147+
assertThat(meta.getEotTokenId(), is(32000));
148+
assertThat(meta.getMetadata("general.architecture"), is("llama"));
149+
assertThat(meta.getMetadata("general.quantization_version"), is("2"));
150+
}
151+
152+
@Test
153+
public void testNewGettersDefaultWhenAbsent() throws Exception {
154+
ModelMeta meta = parse("{\"n_vocab\":100}");
155+
156+
assertThat(meta.getChatTemplate(), is(""));
157+
assertThat(meta.getBosTokenId(), is(-1));
158+
assertThat(meta.getEosTokenId(), is(-1));
159+
assertThat(meta.getEotTokenId(), is(-1));
160+
assertThat(meta.getMetadata("general.architecture"), is(""));
161+
}
136162
}

0 commit comments

Comments
 (0)