-
Notifications
You must be signed in to change notification settings - Fork 0
feat(examples): spring-ai-engram-cloud-demo — Spring AI + Engram + JamJet Cloud #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
202a88f
feat(demo): scaffold spring-ai-engram-cloud-demo module
sunilp 84b7c51
feat(demo): add Maven wrapper to spring-ai-engram-cloud-demo
sunilp 1c21ef8
feat(demo): add Engram docker-compose + .env.example
sunilp a40cedd
Revert "feat(demo): add Engram docker-compose + .env.example"
sunilp 86d42b9
feat(demo): use engram-spring-boot-starter instead of Spring AI MCP c…
sunilp 8a01f0e
feat(demo): Engram (Rust 0.5.0) docker-compose + .env.example
sunilp 83c8f9b
feat(demo): switch Engram to openai-compatible LLM provider so fact e…
sunilp f0da883
feat(demo): PreflightCheck validates required env vars at startup
sunilp 8070725
feat(demo): PreflightCheck polls Engram /health with timeout
sunilp 8142ed2
feat(demo): DemoApplication + application.yml
sunilp e3f7188
feat(demo): MemoryTools + MemoryAgent + ChatController
sunilp 402dc1f
test(demo): integration test with WireMock + Testcontainers Engram
sunilp 76399b8
docs(demo): README for spring-ai-engram-cloud-demo
sunilp 6815fc1
fix(demo): boot wiring — pull in langchain4j-core for cloud starter; …
sunilp 5641181
fix(demo): add spring-boot-starter-actuator so JamjetObservationHandl…
sunilp 242e328
fix(demo): cloud observability — drop buggy starter, wire JamjetObser…
sunilp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Sign up at https://platform.openai.com/api-keys to get an OpenAI key. | ||
| OPENAI_API_KEY=sk-... | ||
|
|
||
| # Sign up at https://cloud.jamjet.dev, create a project, and copy the API key. | ||
| JAMJET_API_KEY=jk_... | ||
|
|
||
| # Optional — defaults to the public hosted JamJet Cloud. | ||
| # JAMJET_API_URL=https://api.jamjet.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| target/ | ||
| .env | ||
| .idea/ | ||
| *.iml | ||
| .vscode/ |
3 changes: 3 additions & 0 deletions
3
examples/spring-ai-engram-cloud-demo/.mvn/wrapper/maven-wrapper.properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| wrapperVersion=3.3.4 | ||
| distributionType=only-script | ||
| distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| # Spring AI + Engram + JamJet Cloud Demo | ||
|
|
||
| A multi-turn chat agent that **remembers facts across calls** via [Engram](https://github.com/jamjet-labs/jamjet/tree/main/runtime/engram-server) and is **observed end-to-end** by [JamJet Cloud](https://cloud.jamjet.dev) — drop in three Spring Boot starters, get durable memory + cloud observability for free. | ||
|
|
||
| ## What this demo shows | ||
|
|
||
| - **Spring AI 1.0** chat agent using OpenAI for inference | ||
| - **`dev.jamjet:engram-spring-boot-starter`** autoconfigures `EngramClient` so the agent's `@Tool` methods can record + recall facts against a real Engram server | ||
| - **`dev.jamjet:jamjet-cloud-spring-boot-starter`** auto-instruments every chat call + tool span — no code changes | ||
| - **Cross-platform run flow** — works on macOS, Linux, and Windows with the same `mvnw` + `docker compose` commands | ||
|
|
||
| ## How it's wired | ||
|
|
||
| ``` | ||
| User → POST /chat?session=alice ──→ Spring AI ChatClient | ||
| │ | ||
| ├─→ OpenAI chat completion | ||
| │ | ||
| └─→ @Tool methods (rememberFact / recallFacts) | ||
| │ | ||
| └─→ EngramClient (autoconfigured) | ||
| │ | ||
| └─→ Engram REST API (Docker) | ||
| ``` | ||
|
|
||
| JamJet Cloud's starter watches the whole flow via Spring AI's Micrometer Observation hooks and ships traces + cost rollups to the dashboard. **Zero observability code in your demo.** | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - **Java 21+** (`--release 21`; Java 23 also works) | ||
| - **Docker Desktop** (or any Docker engine) — for the Engram sidecar | ||
| - **An OpenAI API key** — sign up at [platform.openai.com](https://platform.openai.com/api-keys) | ||
| - **A JamJet Cloud project** — sign up at [cloud.jamjet.dev](https://cloud.jamjet.dev), create a project, copy the API key | ||
|
|
||
| > **Cost note:** The OpenAI key is used twice per chat turn — once by Spring AI for the chat completion, and once by Engram for fact extraction (the LLM that turns "I prefer espresso" into a structured fact). Both calls use `gpt-4o-mini` by default. | ||
|
|
||
| ## Run it | ||
|
|
||
| ```bash | ||
| git clone https://github.com/jamjet-labs/jamjet-runtime-java | ||
| cd jamjet-runtime-java/examples/spring-ai-engram-cloud-demo | ||
|
|
||
| cp .env.example .env # Windows: copy .env.example .env | ||
| # Edit .env — paste your OPENAI_API_KEY and JAMJET_API_KEY | ||
|
|
||
| docker compose up -d # boots Engram on 127.0.0.1:9090 | ||
| ./mvnw spring-boot:run # Windows: mvnw.cmd spring-boot:run | ||
| ``` | ||
|
|
||
| The app starts on `127.0.0.1:8080`. `PreflightCheck` validates both env vars and waits for Engram's `/health` endpoint before the server accepts requests — if either is missing or Engram is unreachable, the app exits with a clear error. | ||
|
|
||
| In another terminal: | ||
|
|
||
| ```bash | ||
| # Tell the agent a fact — it stores it in Engram | ||
| curl -s -X POST "localhost:8080/chat?session=alice" \ | ||
| -H "Content-Type: text/plain" \ | ||
| -d "I work at Acme as a Java engineer" | ||
| # → {"session":"alice","reply":"Got it, I've stored that you work at Acme as a Java engineer."} | ||
|
|
||
| # Ask about it — agent recalls from Engram | ||
| curl -s -X POST "localhost:8080/chat?session=alice" \ | ||
| -H "Content-Type: text/plain" \ | ||
| -d "Where do I work?" | ||
| # → {"session":"alice","reply":"You work at Acme."} | ||
|
|
||
| # The first message also contained "Java engineer" — Engram extracted that too | ||
| curl -s -X POST "localhost:8080/chat?session=alice" \ | ||
| -H "Content-Type: text/plain" \ | ||
| -d "What languages do I use?" | ||
| # → {"session":"alice","reply":"Based on what you've shared, you're a Java engineer."} | ||
| ``` | ||
|
|
||
| Each response is a JSON object `{"session": "...", "reply": "..."}`. | ||
|
|
||
| ## See the trace in JamJet Cloud | ||
|
|
||
| Open [cloud.jamjet.dev/dashboard/graph](https://cloud.jamjet.dev/dashboard/graph) — each `/chat` call appears as a trace with: | ||
|
|
||
| - 1 LLM span (OpenAI chat completion) | ||
| - 1 or more Engram tool spans (`rememberFact` or `recallFacts`) | ||
| - Cost rollup (per-token, per-call) | ||
|
|
||
| ## Anatomy | ||
|
|
||
| The interesting code is ~120 LOC across 4 files: | ||
|
|
||
| | File | What it does | | ||
| |---|---| | ||
| | `MemoryTools.java` | `@Tool` methods (`rememberFact`, `recallFacts`) backed by autoconfigured `EngramClient` | | ||
| | `MemoryAgent.java` | Spring AI `ChatClient` wired with the tools + system prompt | | ||
| | `ChatController.java` | `POST /chat?session=X` — accepts `text/plain`, returns `{"session","reply"}` | | ||
| | `startup/PreflightCheck.java` | Validates env vars + polls Engram `/health` before the app accepts traffic | | ||
|
|
||
| The pom has three starter dependencies. Zero custom plumbing. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Property | Default | Purpose | | ||
| |---|---|---| | ||
| | `engram.base-url` | `http://127.0.0.1:9090` | Where the autoconfigured `EngramClient` connects | | ||
| | `spring.ai.openai.api-key` | `${OPENAI_API_KEY}` | Spring AI OpenAI key | | ||
| | `spring.ai.openai.chat.options.model` | `gpt-4o-mini` | OpenAI model for chat | | ||
| | `jamjet.cloud.api-key` | `${JAMJET_API_KEY}` | JamJet Cloud project key | | ||
| | `jamjet.cloud.api-url` | `https://api.jamjet.dev` | JamJet Cloud ingest endpoint | | ||
|
|
||
| To swap the chat model (e.g. to `gpt-4o`), edit `application.yml`. To use a different LLM provider for Engram's fact extraction, change `ENGRAM_LLM_PROVIDER` in `docker-compose.yml` — see [Engram's provider docs](https://github.com/jamjet-labs/jamjet/tree/main/runtime/engram-server#llm-providers). | ||
|
|
||
| ## Windows notes | ||
|
|
||
| - Use PowerShell or cmd; `mvnw.cmd` is the entry point instead of `./mvnw`. | ||
| - WSL2 users: run from the WSL side for cleanest networking with Docker Desktop. | ||
| - The `ghcr.io/jamjet-labs/engram-server:0.5.0` image is multi-arch; Docker Desktop on Windows ARM should work with Linux containers enabled. | ||
|
|
||
| ## Cleaning up | ||
|
|
||
| ```bash | ||
| docker compose down # stops Engram, removes container + volume | ||
| # Press Ctrl-C on the Spring app | ||
| ``` | ||
|
|
||
| When you're done, rotate or delete your JamJet API key and OpenAI key in their respective dashboards — they are tied to your account quotas. | ||
|
|
||
| ## Security | ||
|
|
||
| - `.env` is in `.gitignore` — never commit your keys. | ||
| - Both Engram and the Spring app bind to `127.0.0.1`. Do not expose this demo on a public network. | ||
| - For real apps with PII in prompts, enable [JamJet's redaction settings](https://docs.jamjet.dev/redaction) (Team tier and up). The demo runs on the free tier without redaction — do not pipe production traffic through it. | ||
|
|
||
| ## What's next | ||
|
|
||
| - **The new Python Engram rewrite** (`jamjet-engram` 0.1.0) is the next-generation server — more featureful, better benchmarks, but currently has wire-protocol gaps with the Java starter. Once those gaps close, the docker-compose image is a one-line swap. | ||
| - **A separate MCP demo** (`examples/mcp-engram-demo/`, coming soon) targets MCP-protocol-native clients (Cursor, Claude Desktop) instead of Spring Boot ergonomics. | ||
| - Read the [JamJet Spring AI integration guide](../../docs/spring-ai-integration.md). | ||
|
|
||
| ## License | ||
|
|
||
| Apache 2.0. See [LICENSE](../../LICENSE). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| services: | ||
| engram: | ||
| image: ghcr.io/jamjet-labs/engram-server:0.5.0 | ||
| container_name: engram-demo | ||
| # No explicit command — all configuration is via environment variables. | ||
| # "serve" is the default entrypoint; mode=rest and port=9090 are set below. | ||
| ports: | ||
| - "127.0.0.1:9090:9090" # loopback-only — do not expose Engram on the network | ||
| environment: | ||
| - ENGRAM_MODE=rest # default is "mcp" (stdio); must be "rest" for HTTP | ||
| - ENGRAM_LLM_PROVIDER=openai-compatible # real fact extraction so the demo's memory actually works | ||
| - ENGRAM_OPENAI_BASE_URL=https://api.openai.com/v1 | ||
| - OPENAI_API_KEY=${OPENAI_API_KEY:?OPENAI_API_KEY required for Engram fact extraction; copy .env.example to .env and paste your key} | ||
| - ENGRAM_EMBEDDING_PROVIDER=mock # mock embeddings (768d, deterministic) — fine for a 3-fact demo without an Ollama prereq | ||
| volumes: | ||
| - engram-data:/data # ENGRAM_DB_PATH defaults to /data/engram.db inside the image | ||
| healthcheck: | ||
| test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/health"] | ||
| interval: 2s | ||
| timeout: 1s | ||
| retries: 15 | ||
| start_period: 5s | ||
| restart: unless-stopped | ||
|
|
||
| volumes: | ||
| engram-data: | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify
wgetis available in theengram-servercontainer image.The Engram server is a Rust binary distributed as
ghcr.io/jamjet-labs/engram-server:0.5.0. Minimal Rust containers frequently ship withoutwgetorcurl. If the binary is absent, Docker marks the container perpetually unhealthy even when the port is responding correctly — which degrades DX (docker psalways shows unhealthy) and would break any futuredepends_on: condition: service_healthyusage.A safer fallback is using
/bin/sh -cwith a POSIX-compliant/dev/tcpredirect ornc, both of which are more likely to be present:🛡️ Alternative healthcheck (if wget is absent)
Or, if neither is available, use a side-car healthcheck script bundled in the image. Verify with:
docker run --rm --entrypoint sh ghcr.io/jamjet-labs/engram-server:0.5.0 -c "which wget || which curl || echo NONE"📝 Committable suggestion
🤖 Prompt for AI Agents