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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to models are documented here.

## [Unreleased]

## [0.2.3] - 2026-08-02

### Added

- Added `GroundedRagPrompt`, a canonical framework-neutral RAG prompt that
screens retrieved evidence and withholds rejected context from generation.

## [0.2.2] - 2026-08-02

### Changed
Expand Down
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ the qualification ledger for those exact details.
| Llama 3.2 1B | General |
| Llama 3.2 3B | General |
| Gemma 3 1B | General |
| Gemma 4 26B-A4B Instruct | General |
| H2O Danube2 1.8B | General |
| DeepSeek-R1-Distill-Qwen 1.5B | General |
| TinyLlama 1.1B Chat | General |
Expand All @@ -104,11 +105,10 @@ the qualification ledger for those exact details.
| Nexus Finance | Finance |
| Nexus Medical | Healthcare |

Gemma 4 26B-A4B Instruct Q4_K_M is integration-tested through plain Java,
LangChain4j, and Spring AI, but it is intentionally absent from the qualified
table. Its exact-artifact run passed every correctness gate and failed the
2-second usable TTFT ceiling. See the [qualification analysis](GEMMA4_QUALIFICATION.md)
and [retained evidence](benchmark-results/certified-20260802/rag/gemma4-26b-a4b-q4_k_m/README.md).
Gemma 4 26B-A4B Instruct Q4_K_M is qualified at the usable tier through plain
Java, LangChain4j, and Spring AI. See the
[qualification analysis](GEMMA4_QUALIFICATION.md) and
[retained evidence](benchmark-results/certified-20260802/rag/gemma4-26b-a4b-q4_k_m/README.md).

- [Model support and qualification](https://integrallis.github.io/models/docs/models/current/model-support.html)
- [Production RAG results](RAG_BENCHMARKS.md)
Expand All @@ -121,7 +121,7 @@ JAR for the selected model:

```kotlin
dependencies {
implementation("org.modeljars:modeljars:0.1.0")
implementation("org.modeljars:modeljars:0.1.2")
implementation(
"org.modeljars.huggingface:" +
"ggml-org.qwen3-0.6b-gguf.q4_0:" +
Expand All @@ -136,16 +136,16 @@ directly:

```kotlin
dependencies {
implementation("com.integrallis:models:0.2.2")
implementation("com.integrallis:backend-java:0.2.2") // or backend-native
implementation("com.integrallis:models:0.2.3")
implementation("com.integrallis:backend-java:0.2.3") // or backend-native
}
```

Use Apple's on-device system model on a supported Apple Silicon Mac:

```kotlin
dependencies {
implementation("com.integrallis:backend-apple:0.2.2")
implementation("com.integrallis:backend-apple:0.2.3")
}
```

Expand All @@ -159,31 +159,34 @@ catalog dependency.
```java
import static org.modeljars.catalog.Qwen3_0_6b_Q4_0.MODEL;

var prompt = ChatTemplate.CHATML_NO_THINK.render(List.of(
ChatMessage.system("Classify the user's intent in one phrase."),
ChatMessage.user("I want to cancel my order")));
var options = SamplingOptions.builder()
.temperature(0.0f)
.maxTokens(20)
.maxTokens(128)
.build();

try (var model = ModelJars.open(MODEL)) {
String result = model.generate(prompt, options);
try (var runtime = ModelJars.openRuntime(MODEL)) {
var prompt = runtime.chatTemplate().render(List.of(
ChatMessage.system("Classify the user's intent in one phrase."),
ChatMessage.user("I want to cancel my order")));
String result = runtime.model().generate(prompt, options);
System.out.println(result);
}
```

`ModelJars.open` resolves the pinned artifact, downloads and verifies it when needed, chooses its
qualified Models backend, and applies a matching performance profile. Applications that manage
`ModelJars.openRuntime` resolves the pinned artifact, downloads and verifies it when needed, chooses
its qualified Models backend and chat template, and applies a matching performance profile.
Applications that manage
their own GGUF files can use the lower-level `PureJavaBackend.load(Path)` and
`RustFfmBackend.load(Path)` APIs described in the
[Using Models guide](https://integrallis.github.io/models/docs/models/current/using-models.html).

Streaming uses the same loaded model:

```java
try (var model = ModelJars.open(MODEL)) {
model.generate(prompt, options, new TokenStream() {
try (var runtime = ModelJars.openRuntime(MODEL)) {
var prompt = runtime.chatTemplate().render(
List.of(ChatMessage.user("Explain local inference in one sentence.")));
runtime.model().generate(prompt, options, new TokenStream() {
@Override
public void onToken(String token) {
System.out.print(token);
Expand All @@ -205,8 +208,8 @@ try (var model = ModelJars.open(MODEL)) {
Backend diagnostics expose the exact plan selected for the loaded model:

```java
try (var model = ModelJars.open(MODEL)) {
model.diagnostics().optimizations().forEach(System.out::println);
try (var runtime = ModelJars.openRuntime(MODEL)) {
runtime.model().diagnostics().optimizations().forEach(System.out::println);
}
```

Expand Down
2 changes: 1 addition & 1 deletion backend-native/src/main/rust/model-kernels/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend-native/src/main/rust/model-kernels/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jmodels-kernels"
version = "0.2.2"
version = "0.2.3"
edition = "2024"
license = "Apache-2.0"
publish = false
Expand Down
15 changes: 9 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -1446,21 +1446,22 @@ tasks.register("verifyDocumentation") {
}
val usingModels = usingModelsFile.readText()
require(
"ChatTemplate.CHATML.render(" in usingModels &&
"ModelJars.openRuntime(MODEL)" in usingModels &&
"runtime.chatTemplate().render(" in usingModels &&
"ChatMessage.system(" in usingModels &&
"ChatMessage.user(" in usingModels &&
"new TokenStream()" in usingModels
) {
"Using Models must show role-aware prompting and token streaming"
"Using Models must show qualification-owned role-aware prompting and token streaming"
}
require("xref:using-models.adoc[Using Models]" in documentationNavigation) {
"Documentation navigation must expose the Using Models page"
}

require(
"xref:using-models.adoc[Using Models]" in docsIndex &&
"ModelJars.open(MODEL)" in docsIndex &&
"ChatTemplate.CHATML_NO_THINK.render(" in docsIndex
"ModelJars.openRuntime(MODEL)" in docsIndex &&
"runtime.chatTemplate().render(" in docsIndex
) {
"The Models opening page must link to Using Models and show a complete quick example"
}
Expand Down Expand Up @@ -1520,7 +1521,8 @@ tasks.register("verifyDocumentation") {
listOf(
"$modelJarsGroup:modeljars:{modeljars-version}",
"$modelJarsGroup.catalog.Qwen3_0_6b_Q4_0.MODEL",
"$modelJarsFacade.open(MODEL)",
"$modelJarsFacade.openRuntime(MODEL)",
"runtime.chatTemplate()",
"new ModelsChatModel(",
"new ModelsStreamingChatModel(",
"StreamingChatResponseHandler",
Expand All @@ -1538,7 +1540,8 @@ tasks.register("verifyDocumentation") {
listOf(
"$modelJarsGroup:modeljars:{modeljars-version}",
"$modelJarsGroup.catalog.Qwen3_0_6b_Q4_0.MODEL",
"$modelJarsFacade.open(MODEL)",
"$modelJarsFacade.openRuntime(MODEL)",
"runtime.chatTemplate()",
"new ModelsSpringAiChatModel(",
"model.stream(new Prompt(",
"ChatOptions.builder()",
Expand Down
6 changes: 3 additions & 3 deletions docs/content/antora.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: models
title: Models
version: 'current'
display_version: '0.2.2'
display_version: '0.2.3'
prerelease: false
start_page: ROOT:index.adoc
nav:
Expand All @@ -10,8 +10,8 @@ asciidoc:
attributes:
source-language: java
source-highlighter: highlight.js
models-version: '0.2.2'
modeljars-version: '0.1.0'
models-version: '0.2.3'
modeljars-version: '0.1.2'
vectors-version: '0.1.4'
url-models-github: https://github.com/integrallis/models
url-modeljars: https://modeljars.org
Expand Down
11 changes: 5 additions & 6 deletions docs/content/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,18 @@ import static org.modeljars.catalog.Qwen3_0_6b_Q4_0.MODEL;

import com.integrallis.models.api.SamplingOptions;
import com.integrallis.models.runtime.chat.ChatMessage;
import com.integrallis.models.runtime.chat.ChatTemplate;
import java.util.List;
import org.modeljars.ModelJars;

var prompt = ChatTemplate.CHATML_NO_THINK.render(
List.of(ChatMessage.user("Why is local inference useful?")));
var options = SamplingOptions.builder()
.temperature(0.0f)
.maxTokens(64)
.maxTokens(128)
.build();

try (var model = ModelJars.open(MODEL)) {
System.out.println(model.generate(prompt, options));
try (var runtime = ModelJars.openRuntime(MODEL)) {
var prompt = runtime.chatTemplate().render(
List.of(ChatMessage.user("Why is local inference useful?")));
System.out.println(runtime.model().generate(prompt, options));
}
----

Expand Down
17 changes: 8 additions & 9 deletions docs/content/modules/ROOT/pages/langchain4j.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import static org.modeljars.catalog.Qwen3_0_6b_Q4_0.MODEL;

import com.integrallis.models.api.SamplingOptions;
import com.integrallis.models.langchain4j.ModelsChatModel;
import com.integrallis.models.runtime.chat.ChatTemplate;
import java.util.List;
import org.modeljars.ModelJars;

Expand All @@ -48,10 +47,10 @@ var defaults = SamplingOptions.builder()
.stopSequences(List.of("</answer>"))
.build();

try (var runtime = ModelJars.open(MODEL)) {
try (var runtime = ModelJars.openRuntime(MODEL)) {
var model = new ModelsChatModel(
runtime,
ChatTemplate.CHATML_NO_THINK,
runtime.model(),
runtime.chatTemplate(),
defaults);

String answer = model.chat("Answer using only the supplied context.");
Expand Down Expand Up @@ -110,8 +109,8 @@ import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import java.util.List;

var streaming = new ModelsStreamingChatModel(
runtime,
ChatTemplate.CHATML_NO_THINK,
runtime.model(),
runtime.chatTemplate(),
defaults);
var request = ChatRequest.builder()
.messages(List.of(UserMessage.from("List the retrieved facts.")))
Expand Down Expand Up @@ -165,7 +164,7 @@ var collection = VectorCollection.builder()
.indexType(IndexType.HNSW)
.build();

try (var runtime = ModelJars.open(MODEL);
try (var runtime = ModelJars.openRuntime(MODEL);
var store = JavaVectorsEmbeddingStore.builder(collection)
.commitAfterAdd(true)
.build()) {
Expand All @@ -177,8 +176,8 @@ try (var runtime = ModelJars.open(MODEL);

var assistant = AiServices.builder(Assistant.class)
.chatModel(new ModelsChatModel(
runtime,
ChatTemplate.CHATML_NO_THINK,
runtime.model(),
runtime.chatTemplate(),
defaults))
.contentRetriever(retriever)
.build();
Expand Down
35 changes: 14 additions & 21 deletions docs/content/modules/ROOT/pages/rag.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ LangChain4j, Spring AI, or another caller of a Models runtime.

== Retrieval Validation

Retrieved documents are untrusted input. Call `assess` before placing them in a
prompt:
Retrieved documents are untrusted input. Prepare the canonical prompt before
generation; preparation calls `assess` and does not expose rejected context as
prompt text:

[source,java]
----
var policy = GroundedAnswerPolicy.productionDefault();
GroundingContextDecision context = policy.assess(question, evidence);
if (!context.generationAllowed()) {
GroundedRagPrompt prompt = GroundedRagPrompt.prepare(policy, question, evidence);
if (!prompt.generationAllowed()) {
return GroundedAnswerPolicy.ABSTENTION;
}
----
Expand Down Expand Up @@ -107,11 +108,9 @@ higher assurance requirement should add domain validation or human review.
import com.integrallis.models.api.SamplingOptions;
import com.integrallis.models.api.TextGenerationModel;
import com.integrallis.models.rag.GroundedAnswerPolicy;
import com.integrallis.models.rag.GroundedRagPrompt;
import com.integrallis.models.rag.GroundingDocument;
import com.integrallis.models.runtime.chat.ChatMessage;
import com.integrallis.models.runtime.chat.ChatTemplate;
import java.util.List;
import java.util.stream.Collectors;

final class RagAnswerService {
static String answer(
Expand All @@ -120,20 +119,12 @@ final class RagAnswerService {
List<GroundingDocument> evidence,
SamplingOptions options) {
var policy = GroundedAnswerPolicy.productionDefault();
if (!policy.assess(question, evidence).generationAllowed()) {
var prompt = GroundedRagPrompt.prepare(policy, question, evidence);
if (!prompt.generationAllowed()) {
return GroundedAnswerPolicy.ABSTENTION;
}

String context = evidence.stream()
.map(document ->
"[" + document.id() + "] " + document.title() + "\n" + document.text())
.collect(Collectors.joining("\n\n"));
var prompt = ChatTemplate.CHATML_NO_THINK.render(List.of(
ChatMessage.system(
"Answer only from the context and cite source IDs in brackets."),
ChatMessage.user("Context:\n" + context + "\n\nQuestion:\n" + question)));

String rawModelText = model.generate(prompt, options);
String rawModelText = model.generate(prompt.text(), options);
return policy.apply(question, evidence, rawModelText).text();
}
}
Expand Down Expand Up @@ -177,9 +168,11 @@ var policy = new GroundedAnswerPolicy(0.78f, limits);

== Framework Placement

Run retrieval through the framework integration, call `assess`, add only
accepted evidence to the prompt, generate through the local model, and call
`apply` before returning the answer. See xref:langchain4j.adoc[LangChain4j],
Run retrieval through the framework integration, call `GroundedRagPrompt.prepare`,
add only accepted evidence to the prompt, generate through the local model, and
call `apply` before returning the answer. Framework chat APIs can place
`prompt.instructions()` in a system message and `prompt.request()` in a user
message. See xref:langchain4j.adoc[LangChain4j],
xref:spring-ai.adoc[Spring AI], xref:spring-boot.adoc[Spring Boot], and
xref:vectors.adoc[Vectors] for adapter wiring.

Expand Down
7 changes: 3 additions & 4 deletions docs/content/modules/ROOT/pages/spring-ai.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ inject template control tokens.
import static org.modeljars.catalog.Qwen3_0_6b_Q4_0.MODEL;

import com.integrallis.models.api.SamplingOptions;
import com.integrallis.models.runtime.chat.ChatTemplate;
import com.integrallis.models.spring.ai.ModelsSpringAiChatModel;
import java.util.List;
import org.modeljars.ModelJars;
Expand All @@ -48,10 +47,10 @@ var defaults = SamplingOptions.builder()
.stopSequences(List.of("</answer>"))
.build();

try (var runtime = ModelJars.open(MODEL)) {
try (var runtime = ModelJars.openRuntime(MODEL)) {
var model = new ModelsSpringAiChatModel(
runtime,
ChatTemplate.CHATML_NO_THINK,
runtime.model(),
runtime.chatTemplate(),
defaults);

String answer = model.call("Answer using only the supplied context.");
Expand Down
Loading
Loading