CafeAI:面向 Java 的可组合生成式 AI 框架发布 0.6.0
akilisha/cafeai
CafeAI 发布 0.6.0,一个面向 Java 的可组合生成式 AI 框架,已上架 Maven Central(com.akilisha.oss),要求 Java 23+。
A foundational and composable framework for Gen AI in Java.
Try it in ten seconds — no clone, no build file, no project. With JBang:
jbang hello@akilisha/cafeai # a web server in one file jbang ask@akilisha/cafeai # an LLM behind POST /ask (Anthropic, OpenAI, or local Ollama) jbang init -t cafeai@akilisha/cafeai app.java # start your own
The scripts are jbang/hello.java and jbang/ask.java, each one file.
CafeAI is not an invention of anything new. It is a deliberate re-orientation of familiar, battle-tested patterns and paradigms — Java's robustness, Express's composability, Langchain's AI primitives — unified into a foundational and composable framework for the AI age. Built for Java developers who refuse to trade understanding for convenience.
Why CafeAI?
The Java ecosystem deserves a serious Gen AI story. Not one hidden behind Spring Boot abstractions, but one built on first principles — where every layer is explainable, every concern is composable, and every design decision has a reason you can articulate in a conference talk.
| Problem | CafeAI's Answer |
|---|---|
| Spring AI abstracts too aggressively | Helidon SE — you see the plumbing |
| Python has Langchain. Java has...? | Langchain4j, with full API parity |
| Node devs know Express. Java devs don't | CafeAI mirrors Express pound-for-pound |
| AI pipelines are magic black boxes | Everything is a middleware. Everything is explainable. |
| "Just use Redis" for everything | Tiered memory — FFM/SSD first, Redis only when you need it |
The Name
Cafe → instantly recognizable as a coffee shop — Java
AI → the technology we're introducing
CafeAI → sounds like "kaf-ai" — a natural coming together of Java and AI
The Three Lineages
CafeAI stands deliberately on the shoulders of three proven traditions:
| Lineage | Contribution | Why It Matters |
|---|---|---|
| Java / JVM | Robustness, FFM, Structured Concurrency, Virtual Threads | Enterprise systems are already here |
| Express.js / Node | Middleware composability, ergonomic API | Zero mental model ramp-up for Java devs |
| Python Langchain | AI primitives vocabulary, RAG, agents | Parity for AI practitioners across languages |
Installation
On Maven Central under com.akilisha.oss. CafeAI is modular — start with
cafeai-core and add capability modules as you climb the adoption ladder.
Gradle
repositories { mavenCentral() }
dependencies {
implementation 'com.akilisha.oss:cafeai-core:0.6.0'
// add only what you use:
implementation 'com.akilisha.oss:cafeai-config:0.6.0' // application.properties/.yaml + profiles
implementation 'com.akilisha.oss:cafeai-aiservices:0.6.0' // app.agent() — LangChain4j AiServices
implementation 'com.akilisha.oss:cafeai-agentic:0.6.0' // CafeAgentic — LangChain4j langchain4j-agentic workflows
implementation 'com.akilisha.oss:cafeai-memory:0.6.0' // tiered context memory
implementation 'com.akilisha.oss:cafeai-rag:0.6.0' // retrieval-augmented generation
implementation 'com.akilisha.oss:cafeai-guardrails:0.6.0' // PII, jailbreak, toxicity, regulatory
implementation 'com.akilisha.oss:cafeai-observability:0.6.0' // OpenTelemetry tracing
implementation 'com.akilisha.oss:cafeai-security:0.6.0' // audit events for blocked prompt injection
implementation 'com.akilisha.oss:cafeai-connect:0.6.0' // Redis, Ollama, pgvector
implementation 'com.akilisha.oss:cafeai-mustache:0.6.0' // Mustache view engine
implementation 'com.akilisha.oss:cafeai-session:0.6.0' // SQLite-backed HTTP session store
implementation 'com.akilisha.oss:cafeai-flight:0.6.0' // JVM visibility via Flight Recorder -> OTel
implementation 'com.akilisha.oss:cafeai-identity:0.6.0' // verified callers (OIDC/OAuth); model calls without API keys
implementation 'com.akilisha.oss:cafeai-mcp:0.6.0' // app.mcp(): routes as tools for AI agents
implementation 'com.akilisha.oss:cafeai-sentinel:0.6.0' // AI Kubernetes/OpenShift incident pipeline
testImplementation 'com.akilisha.oss:cafeai-test:0.6.0' // record/replay model calls, evals
}Maven
<dependency> <groupId>com.akilisha.oss</groupId> <artifactId>cafeai-core</artifactId> <version>0.6.0</version> </dependency>
Using several modules? Import the BOM once and leave the versions off, so the modules cannot drift apart (they are released together and share service-loader interfaces, so a mix of versions is not a supported combination):
dependencies {
implementation platform('com.akilisha.oss:cafeai-bom:0.6.0')
implementation 'com.akilisha.oss:cafeai-core'
implementation 'com.akilisha.oss:cafeai-guardrails'
implementation 'com.akilisha.oss:cafeai-aiservices'
}<dependencyManagement> <dependencies> <dependency> <groupId>com.akilisha.oss</groupId> <artifactId>cafeai-bom</artifactId> <version>0.6.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
The BOM lists only CafeAI's own modules; LangChain4j and Helidon keep their own BOMs, which the modules import.
Requires Java 23+ (CI builds and tests on Java 23). For a local
Jlama model, also add --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED
to your run args.
Quick Start
var app = CafeAI.create(); // Infrastructure app.ai(OpenAI.of("gpt-4o")); app.memory(MemoryStrategy.mapped()); // SSD-backed via Java FFM — no Redis needed app.observe(ObserveStrategy.otel()); // Safety app.guard(GuardRail.pii()); app.guard(GuardRail.jailbreak()); // Persona app.system("You are a helpful customer service agent for Acme Corp..."); // Routes app.use(Middleware.json()); app.use(Middleware.rateLimit(60)); app.get("/health", (req, res, next) -> res.json(Map.of("status", "ok"))); app.post("/chat", (req, res, next) -> { res.stream(app.prompt(req.body("message"))); }); app.listen(8080, () -> System.out.println("☕ CafeAI is brewing on :8080"));
Read that out loud. A Java developer who has never touched Gen AI understands every line. An Express developer who has never touched Java understands the structure. A Python LangChain developer recognizes the concepts. That's three audiences, zero confusion.
Talk to it with any HTTP client. /chat streams the answer back as Server-Sent Events:
curl -N -H 'Content-Type: application/json' -H 'Accept: text/event-stream' \ -d '{"message": "What is your return policy?"}' \ http://localhost:8080/chat # data: Our # data: return # data: policy # data: allows # ... # data: [DONE]
The Middleware Pipeline
Everything in CafeAI is middleware. HTTP concerns, AI concerns, security, observability, guardrails — all composable, all testable, all replaceable. This is the lesson Express taught us. CafeAI carries it to the AI age.
Incoming Request
↓
[ auth / JWT ] ← standard HTTP middleware
↓
[ rate limiter ] ← standard HTTP middleware
↓
[ PII scrubber ] ← security middleware
↓
[ jailbreak detector ] ← security middleware
↓
[ prompt injection guard ] ← security middleware
↓
[ guardrails PRE ] ← ethical / regulatory middleware
↓
[ token budget enforcer ] ← cost middleware
↓
[ semantic cache lookup ] ← memory middleware (clean, prompt-only answers only)
↓
[ RAG retrieval ] ← rag middleware
↓
[ LLM call / model router ] ← ai middleware
↓
[ guardrails POST ] ← ethical / regulatory middleware
↓
[ observability / OTel trace ] ← observe middleware
↓
[ memory write ] ← memory middleware
↓
[ streaming response ] ← streaming middleware (SSE / WebSocket)
Every hard problem in Gen AI is a middleware concern. You can teach any one of those layers in isolation, then snap it into the pipeline. The pipeline is the curriculum.
The API Vocabulary
CafeAI introduces a deliberate vocabulary for AI-native Java development. Every name is guessable before you look it up.
Express-Parity HTTP (pound-for-pound)
app.get(path, handler) // GET route app.post(path, handler) // POST route app.put(path, handler) // PUT route app.delete(path, handler) // DELETE route app.use(middleware) // global middleware app.use(path, middleware) // path-scoped middleware app.listen(port) // start the server
AI Infrastructure
app.ai(OpenAI.of("gpt-4o")) // register LLM provider app.ai(Anthropic.of("claude-sonnet-4-5")) // swap providers freely app.ai(Gemini.of("gemini-3.6-flash")) // Google Gemini — not a built-in type, added via ChatModelAccess app.ai(Nvidia.of("moonshotai/kimi-k3")) // NVIDIA API catalog (build.nvidia.com), key from $NVIDIA_API_KEY app.ai(Ollama.of("llama3.3")) // local model via Ollama, no data leaves your infra app.ai(Jlama.of("tjake/Qwen2.5-0.5B-Instruct-JQ4")) // pure-Java local model — in-process, no server // run with: --add-modules jdk.incubator.vector // --enable-native-access=ALL-UNNAMED app.ai(ModelRouter.smart() // smart routing — cheap vs expensive .simple(OpenAI.of("gpt-4o-mini")) .complex(OpenAI.of("gpt-4o"))) app.system("You are...") // system prompt — the AI's persona app.template("name", "{{variable}}") // named prompt templates
Memory
app.memory(MemoryStrategy.inMemory()) // Rung 1: JVM HashMap — prototype app.memory(MemoryStrategy.mapped()) // Rung 2: SSD-backed via Java FFM app.memory(MemoryStrategy.redis(config)) // Rung 3: Redis — the escape valve app.memory(MemoryStrategy.hybrid()) // Rung 4: warm SSD + cold Redis
Semantic cache
app.cache(SemanticCache.inMemory(EmbeddingProvider.local()).build());
Repeat questions are answered from a cache matched by meaning, skipping the model call. A cache
shared between users is also an attack surface — coax the model into a bad answer, get it stored,
and it is served to everyone whose question lands nearby — so the defences are built in, not
optional: only clean, prompt-only answers are stored (nothing a guardrail flagged; nothing with
a session or RAG); entries are isolated per model and system prompt; a hit needs high embedding
similarity and high word overlap and similar length (so a popular question with instructions
appended does not match); every hit is re-screened by the current guardrails; and entries expire.
.noCache() opts a call out. It is heuristic, not a guarantee — see
docs/adr/ADR-013-semantic-cache-and-poisoning-defences.md for the threat model and its limits.
RAG
app.vectordb(PgVector.connect(config)) // vector store app.embed(EmbeddingProvider.local()) // embedding model (ONNX via FFM) app.ingest(Source.pdf("handbook.pdf")) // ingest knowledge app.ingest(Source.url("https://...")) app.ingest(Source.directory("docs/")) app.rag(Retriever.semantic(5)) // attach retrieval pipeline app.rag(Retriever.hybrid(5)) // dense + sparse retrieval
Guardrails
app.guard(GuardRail.pii()) // PII detection — pre and post LLM app.guard(GuardRail.jailbreak()) // adversarial prompt detection app.guard(GuardRail.promptInjection()) // injected instructions — in input AND in retrieved RAG documents app.guard(GuardRail.secrets()) // API keys, tokens, private keys — in and out app.guard(GuardRail.promptLeak(system)) // the model repeating its own system prompt app.guard(GuardRail.toxicity()) // harmful content filtering app.guard(GuardRail.moderation( // a moderation MODEL (LangChain4j's), not a pattern list OpenAI.moderation("omni-moderation-latest"))) app.guard(GuardRail.regulatory() // GDPR, HIPAA, FCRA, CCPA .gdpr().hipaa()) app.guard(GuardRail.topicBoundary() // scope enforcement .allow("customer service", "orders") .deny("politics", "medical advice"))
Agents & Tools
// You define a typed interface; CafeAI binds it to a LangChain4j AiService — // which owns the reasoning loop, tool dispatch, and chat memory — and gives it // an HTTP identity: session threading, guardrail pre-screening, observability. app.agent("support", SupportAgent.class) .system("You are a support specialist for Acme.") .tool(new OrderLookupTool()) // @Tool-annotated Java methods .memory(MemoryStrategy.inMemory()) .guard(GuardRail.jailbreak()) // runs before the agent loop .configure(b -> b /* full AiServices.Builder escape hatch */);
CafeAI does not reimplement the ReAct loop, tool protocol, or MCP client — LangChain4j does all of that. CafeAI writes the HTTP binding. MCP servers attach through LangChain4j's MCP support; multi-agent workflows are a supervisor agent calling sub-agents as tools, or a middleware chain — not bespoke primitives.
Observability
app.observe(ObserveStrategy.otel()) // OpenTelemetry — production app.observe(ObserveStrategy.console()) // console — development
Configuration
// declared once, at the point of use — a self-documenting, self-registering key static final ConfigKey<Integer> CHUNK_SIZE = ConfigKey.of("cafeai.rag.chunk.size", Integer.class, 500, "Chunk size in characters"); int size = AppConfig.load().get(CHUNK_SIZE);
# application.yaml on the classpath — or application-{profile}.yaml, or an # external file pointed to by CAFEAI_CONFIG_FILE / cafeai.config.file cafeai: rag: chunk: size: 800
ConfigKey/AppConfig live in cafeai-core — every module can declare a tunable
value with no new dependency. Add cafeai-config to your application to turn
those keys into real, overridable settings — system property, environment
variable, profile file, or application.yaml — resolved by Helidon Config, no
CafeAI-invented naming scheme. Without it, every key just resolves to its own
coded default, exactly as before this existed. See docs/adr/ADR-012-application-config.md.
cafeai-sentinel — AI Cluster Incident Pipeline
SentinelConfig config = SentinelConfig.create().namespace("payments"); ClusterWatch watch = new ClusterWatch(config); app.agent("cluster-investigator", ClusterInvestigator.class) .model(Anthropic.of("claude-sonnet-4-5-20250929")) .tool(new KubeTools(watch.client(), "payments", Redactor.of(config.isRedact()))); IncidentTracker tracker = new IncidentTracker(config) .onIncident(IncidentSink.of(new LogSink(), new SsePublisher())) .investigator(incident -> /* run the agent above */ null) .start(); watch.onPodState(tracker::accept); watch.start();
Watches one Kubernetes/OpenShift namespace, triages pod failures with rules
(no model), coalesces correlated failures into one incident per broken
workload, and runs a read-only agentic investigation on confirmed incidents —
secrets and PII redacted before anything reaches the prompt. A pipeline, not a
product: it ends at "incident published," fanned out to a log, a webhook, an
SSE stream, or your own IncidentSink. See docs/roadmap/ROADMAP-18-sentinel.md
and the runnable capstones/cluster-sentinel companion.
cafeai-identity — Callers, Not API Keys
var issuer = Issuer.discover("https://issuer.example.com/realms/acme"); app.filter(Auth.bearer(issuer, "orders-api")); // or Auth.login(...) for browsers app.get("/reports", Auth.require(Auth.group("finance")), reports); app.ai(OpenAI.of("<model-id>").withBaseUrl("https://models.internal.example.com/v1") .withCredentials(OAuthCredentials.tokenExchange(issuer, "orders-api", secret, "model-gateway")));
Static API keys are banned in most companies: nothing ties a call to a person, and no one person's
access can be revoked. With cafeai-identity, every request has a verified caller from any
OpenID Connect issuer, and that caller goes everywhere:
- Sign-in: API tokens, browser sign-in (tokens kept server-side), or terminal sign-in.
- Model calls: made on the caller's behalf (RFC 8693 token exchange), with no API key.
- Conversations and records: conversations belong to whoever started them, and usage and audit records name the caller.
- RAG: the database decides what each caller may read (PostgreSQL row-level security).
- MCP and WebSockets: both are protected the same way.
Built on open standards only, and tested against Keycloak. See DEVELOPER_GUIDE §32 and
docs/design/IDENTITY.md.
Module Structure
cafeai/
├── cafeai-bom ← Bill of materials: one version for every published module
├── cafeai-core ← Express-style API, routing, middleware chain, all AI primitives
├── cafeai-config ← File-based config (application.properties/.yaml + profiles) for AppConfig
├── cafeai-aiservices ← Binds LangChain4j AiServices to the HTTP server — app.agent()
├── cafeai-agentic ← Binds LangChain4j langchain4j-agentic multi-agent workflows — CafeAgentic
├── cafeai-memory ← Tiered context memory (in-memory, FFM/SSD, Redis)
├── cafeai-rag ← Document ingestion, chunking, embedding, retrieval, vector DBs
├── cafeai-guardrails ← PII, jailbreak, toxicity, regulatory compliance
├── cafeai-observability ← OpenTelemetry tracing, console logging
├── cafeai-security ← Blocks prompt injection, raises audit events
├── cafeai-connect ← Out-of-process services: Redis, Ollama, pgvector
├── cafeai-mustache ← Optional Mustache view engine
├── cafeai-session ← HTTP session store for Middleware.session() (SQLite, single-instance)
├── cafeai-flight ← JVM-level visibility via Java Flight Recorder, surfaced as OTel metrics
├── cafeai-identity ← Verified callers (OpenID Connect, OAuth 2.0) through the AI layer; no API keys
├── cafeai-mcp ← MCP server: chosen routes and @Tool objects as tools for AI agents
├── cafeai-dev ← Development only: save a file, the app reloads in about half a second
├── cafeai-sentinel ← AI cluster incident pipeline for Kubernetes / OpenShift (ROADMAP-18)
├── cafeai-test ← Tests only: record/replay model calls (no API key, no cost) and evals
└── cafeai-examples ← Runnable reference implementations — the adoption ladder
Each module is an independent rung on the adoption ladder. Start with cafeai-core. Graduate when you're ready.
The Incremental Adoption Ladder
CafeAI is structured so that every team can start at the bottom and climb deliberately.
| Rung | Capability | What You Learn |
|---|---|---|
| 1 | Plain LLM call | Helidon SE + Langchain4j basics |
| 2 | Prompt templates | Structured prompt engineering |
| 3 | Context memory | Conversation state, FFM memory API |
| 4 | RAG | Ingestion, embeddings, vector retrieval |
| 5 | Tool use / MCP | Giving the AI actions to take |
| 6 | Guardrails | Safety, ethics, compliance as middleware |
| 7 | Agents | Typed agent interfaces, tool-call loops via LangChain4j |
| 8 | Observability | Production measurement, OpenTelemetry traces |
| 9 | Streaming | SSE, backpressure, real-time UX |
| 10 | Security | Prompt-injection blocking with audit events |
Modern Java Feature Map
CafeAI treats modern Java (21–23) features as load-bearing architecture — not demos.
| Feature | Where CafeAI Uses It | Why |
|---|---|---|
| FFM API | Native ML bindings (ONNX, llama.cpp) | JNI-free native access |
| FFM MemorySegment | SSD-backed session memory | Off-heap, OS page cache, crash-recovery |
| Scoped Values | Request context propagation | No ThreadLocal hacks |
| Vector API | Cosine similarity, dot products | SIMD hardware acceleration for RAG |
| Virtual Threads | Every request handler | I/O-bound LLM calls at zero cost |
Tiered Memory Architecture
Hot → JVM Heap (active conversation turn)
Warm → FFM MemorySegment (recent sessions — SSD-backed, no network)
Cold → Redis (distributed — the escape valve)
Frozen → Vector DB (semantic long-term memory, RAG corpus)
The key insight: most applications do not need Redis. The SSD-backed FFM tier handles production single-node deployments with zero network overhead, zero cloud tax, and crash-recovery for free. Redis is the escape valve — not the default.
Technology Stack
| Concern | Technology | Version |
|---|---|---|
| Runtime | Java | 23+ |
| HTTP Server | Helidon SE | 4.5.5 |
| AI Framework | LangChain4j | 1.20.0 |
| LLM Providers | OpenAI, Anthropic, Gemini, NVIDIA, Ollama, Jlama | — |
| Off-heap Memory | Java FFM | JDK 23 |
| Distributed Cache | Redis (Lettuce) | 6.3.2 |
| Vector DB | PgVector / Chroma | — |
| Observability | OpenTelemetry | 1.40.0 |
| PII Detection | Built-in regex patterns | — |
| Build | Gradle (Groovy DSL) | 9.7.1 |
Running the Examples
# Clone git clone https://github.com/akilisha/cafeai.git cd cafeai # Run the hello world example ./gradlew :cafeai-examples:run # Run a specific example ./gradlew :cafeai-examples:run -PmainClass=io.cafeai.examples.JlamaStreamingExample # Smoke-test a real provider with your own key (skips itself without one) ./gradlew :cafeai-core:liveTest
Live tests, the keys they read and the model variables are described in GETTING-STARTED.md.
Capstones
Full reference applications live in capstones/ —
support-desk, meridian-qualify, acme-claims, invoice-processor. They build
against the framework as project(':cafeai-*'), so they break the build if an API
changes. Not published.
./gradlew :capstones:invoice-processor:run -Pdry
Blog Series
A narrative arc through the framework, one capability at a time, each post anchored to working code — not one post per module.
- Brewing AI in Java — Introducing CafeAI
- The Middleware Pattern Meets Gen AI — From Express to CafeAI
- Your First LLM Call Without Spring Boot — Helidon SE + LangChain4j
- Prompt Engineering in Java — Templates, System Prompts, and the API Vocabulary
- Context Memory Without the Cloud Tax — Java FFM and the Tiered Memory Model
- Building a RAG Pipeline in Java — Ingestion, Embedding, and Retrieval
- Tool Use in Java — Giving the LLM Actions to Take
- Ethical Guardrails as Middleware — PII, Jailbreak, and Regulatory Compliance
- Vision and Audio in Java — Multimodal AI Without the Boilerplate
- Structured Output — Typed LLM Responses, No Parser Required
- Production-Grade AI — Token Budgets, Retries, Observability, and Incident Response
- The Capstone Series — Five Applications and What Each Found
License
Apache 2.0
CafeAI: Not an invention of anything new — a re-orientation of everything proven.
来源:GitHub · github.com