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
4 changes: 3 additions & 1 deletion examples/spring-ai-engram-cloud-demo/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
OPENAI_API_KEY=sk-...

# Sign up at https://cloud.jamjet.dev, create a project, and copy the API key.
# Sent as `Authorization: Bearer ${JAMJET_API_KEY}` on every OTLP trace export.
JAMJET_API_KEY=jk_...

# Optional — defaults to the public hosted JamJet Cloud.
# Optional — defaults to the public hosted JamJet Cloud OTLP intake at
# https://api.jamjet.dev. Override for self-hosted deployments.
# JAMJET_API_URL=https://api.jamjet.dev
13 changes: 7 additions & 6 deletions examples/spring-ai-engram-cloud-demo/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# 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.
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) — using **stock Spring Boot OTLP tracing**, no custom configuration, no JamJet SDK.

## 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
- **Standard Spring Boot OTLP tracing** — Spring AI 1.0 emits Micrometer Observations for every chat call and tool call, `micrometer-tracing-bridge-otel` converts them to OTel spans, and `opentelemetry-exporter-otlp` ships them to JamJet's `/v1/otlp/v1/traces` intake. No JamJet `@Configuration`, no JamJet observation handler, just Spring Boot's standard tracing autoconfig.
- **Cross-platform run flow** — works on macOS, Linux, and Windows with the same `mvnw` + `docker compose` commands

## How it's wired
Expand All @@ -23,7 +23,7 @@ User → POST /chat?session=alice ──→ Spring AI ChatClient
└─→ 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.**
Spring AI's Micrometer Observations → OTel spans → OTLP HTTP exporter → `https://api.jamjet.dev/v1/otlp/v1/traces` with the project key as a bearer token. **All wiring is in `application.yml` — zero Java glue.**

## Prerequisites

Expand Down Expand Up @@ -92,7 +92,7 @@ The interesting code is ~120 LOC across 4 files:
| `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.
There's no `JamjetCloudConfiguration` or observation handler — observability is pure `application.yml`.

## Configuration

Expand All @@ -101,8 +101,9 @@ The pom has three starter dependencies. Zero custom plumbing.
| `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 |
| `management.otlp.tracing.endpoint` | `${JAMJET_API_URL}/v1/otlp/v1/traces` | OTLP intake URL — defaults to JamJet's hosted intake |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align README default endpoint expression with actual config default.

Line 104 omits the fallback (:https://api.jamjet.dev) that exists in application.yml, so readers may assume JAMJET_API_URL is mandatory.

Suggested doc fix
-| `management.otlp.tracing.endpoint` | `${JAMJET_API_URL}/v1/otlp/v1/traces` | OTLP intake URL — defaults to JamJet's hosted intake |
+| `management.otlp.tracing.endpoint` | `${JAMJET_API_URL:https://api.jamjet.dev}/v1/otlp/v1/traces` | OTLP intake URL — defaults to JamJet's hosted intake |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `management.otlp.tracing.endpoint` | `${JAMJET_API_URL}/v1/otlp/v1/traces` | OTLP intake URL — defaults to JamJet's hosted intake |
| `management.otlp.tracing.endpoint` | `${JAMJET_API_URL:https://api.jamjet.dev}/v1/otlp/v1/traces` | OTLP intake URL — defaults to JamJet's hosted intake |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/spring-ai-engram-cloud-demo/README.md` at line 104, Update the
README table entry for management.otlp.tracing.endpoint to match the
application.yml default by including the fallback value; change the displayed
expression to include the fallback :https://api.jamjet.dev so it mirrors the
config's JAMJET_API_URL fallback used in application.yml and avoids implying
JAMJET_API_URL is mandatory.

| `management.otlp.tracing.headers.Authorization` | `Bearer ${JAMJET_API_KEY}` | Per-project bearer token |
| `management.tracing.sampling.probability` | `1.0` | Sample every chat trace (lower for prod) |

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).

Expand Down
24 changes: 11 additions & 13 deletions examples/spring-ai-engram-cloud-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
<maven.compiler.release>21</maven.compiler.release>
<spring-boot.version>3.3.5</spring-boot.version>
<spring-ai.version>1.0.0</spring-ai.version>
<jamjet-cloud.version>0.2.0</jamjet-cloud.version>
<jamjet-engram.version>0.2.0</jamjet-engram.version>
<wiremock.version>3.9.1</wiremock.version>
<testcontainers.version>1.21.3</testcontainers.version>
Expand Down Expand Up @@ -55,8 +54,8 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Required so Micrometer's ObservationRegistry auto-registers JamjetObservationHandler.
Without Actuator, the handler bean exists but never receives Spring AI observations. -->
<!-- Required for Spring Boot's tracing autoconfig (management.tracing.* +
management.otlp.tracing.*). Without actuator the OTLP exporter beans never wire up. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
Expand All @@ -70,19 +69,18 @@
<artifactId>engram-spring-boot-starter</artifactId>
<version>${jamjet-engram.version}</version>
</dependency>
<!-- Cloud SDK directly. We deliberately skip jamjet-cloud-spring-boot-starter 0.2.0
because its autoconfig hard-references LangChain4j classes (would force langchain4j-core
onto the classpath of every Spring-AI-only app). The demo's JamjetCloudConfiguration
does the Spring AI half explicitly — also gives us a cleaner trace registration path. -->
<!-- JamJet Cloud observability via the standard OpenTelemetry path:
Spring AI 1.0 emits `gen_ai.client.operation` Micrometer Observations →
micrometer-tracing-bridge-otel converts them to OTel spans →
opentelemetry-exporter-otlp ships them via OTLP/HTTP-protobuf to JamJet's
/v1/otlp/v1/traces intake. No JamJet SDK, no @Configuration, no observation handler. -->
<dependency>
<groupId>dev.jamjet</groupId>
<artifactId>jamjet-cloud-sdk</artifactId>
<version>${jamjet-cloud.version}</version>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<!-- micrometer-observation is optional in jamjet-cloud-sdk; pull it in here so JamjetObservationHandler resolves. -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

<dependency>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@ spring:
engram:
base-url: ${ENGRAM_BASE_URL:http://127.0.0.1:9090}

jamjet:
cloud:
api-key: ${JAMJET_API_KEY}
api-url: ${JAMJET_API_URL:https://api.jamjet.dev}
# Spring Boot tracing → JamJet Cloud over OTLP/HTTP-protobuf.
# Spring AI 1.0 emits Micrometer Observations for every chat call + tool call;
# micrometer-tracing-bridge-otel turns them into OTel spans; the OTLP exporter
# ships them to /v1/otlp/v1/traces with the project key as a bearer token.
management:
tracing:
sampling:
probability: 1.0
otlp:
tracing:
endpoint: ${JAMJET_API_URL:https://api.jamjet.dev}/v1/otlp/v1/traces
headers:
Authorization: "Bearer ${JAMJET_API_KEY}"

app:
engram:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.ai.openai.api-key", () -> "sk-test");
registry.add("engram.base-url",
() -> "http://" + engram.getHost() + ":" + engram.getMappedPort(9090));
registry.add("jamjet.cloud.api-key", () -> "jk_test");
registry.add("jamjet.cloud.api-url", () -> "http://localhost:1");
// OTLP exporter: point at a black hole + supply a placeholder bearer
// token so Spring's property resolver doesn't trip on the unresolved
// ${JAMJET_API_KEY} reference in application.yml. The exporter retries
// in the background and won't fail the test if the endpoint is
// unreachable; this just prevents accidental traffic to prod.
registry.add("management.otlp.tracing.endpoint", () -> "http://127.0.0.1:1/v1/otlp/v1/traces");
registry.add("management.otlp.tracing.headers.Authorization", () -> "Bearer jk_test");
registry.add("management.tracing.sampling.probability", () -> "0.0");
registry.add("app.engram.health-url",
() -> "http://" + engram.getHost() + ":" + engram.getMappedPort(9090) + "/health");
}
Expand Down
Loading