A real-time AI chat application that streams LLM responses text-by-text to the browser using Atmosphere's built-in OpenAiCompatibleClient. Works with Gemini, OpenAI, Ollama, and any OpenAI-compatible endpoint.
@Agent— drop@Agenton a class with a@Promptmethod and aSKILL.mdpersona and that class is a running, streaming agent.MultiModalAgentis exactly that: one@Agentclass, askill:multimodal-assistantskill file, and a multi-modal chat endpoint at/atmosphere/agent/multimodal@AiEndpoint— the lower-level building block@Agentdesugars to: a declarative AI endpoint with system prompt, capability validation, and conversation memory (used byAiChat)- Capability requirements —
requires = {TEXT_STREAMING, SYSTEM_PROMPT}fails fast if the backend can't deliver - Conversation memory — multi-turn context preserved automatically per client
- Structured events —
AiEventwire protocol for tool calls, agent steps, and structured output - Demo mode — works out-of-the-box without an API key (simulated streaming)
- Prompt cache demo —
PromptCacheDemoChatat/atmosphere/ai-chat-with-cacheshows how@AiEndpoint(promptCache = CONSERVATIVE)threads aCacheHintinto every request; the sample routes prompts through a realAiPipeline+InMemoryResponseCacheso the framework emitsai.cache.hit=falseon the first request andai.cache.hit=trueon repeated identical prompts (canonical framework-level wire signal, not a sample shim) - Retry policy demo —
RetryDemoChatat/atmosphere/ai-chat-with-retryechoes the declared@AiEndpoint(retry = @Retry(...))attributes and exposes a deterministicfail-once:<id>fault-injection path that recovers on a second request - Multi-modal
@Agentdemo —MultiModalAgent(an@Agentclass whose persona lives inprompts/multimodal-assistant-skill.md) at/atmosphere/agent/multimodalaccepts both vision and audio input:- Vision —
image:<base64>prompts are wrapped in aContent.Imageand streamed back as a binary content frame next to a text acknowledgement. - Audio input —
audio:<base64>prompts are wrapped in aContent.Audioand forwarded to the resolved AI runtime as a multi-modal input part viasession.stream(prompt, parts). The runtime encodes it onto the provider wire request (the built-in OpenAI-compatible client emits aninput_audiocontent block), so an audio-capable model such asgpt-4o-audio-previewreceives the clip. With no API key the demo runtime returns a canned reply, but the audio still reaches the runtime context. Override the media type withaudio:audio/<subtype>:<base64>(defaultaudio/wav). - The delivery test
MultiModalAudioInputDeliveryTestproves the audio reaches the runtime by asserting the capturedAgentExecutionContext.parts()contains theContent.Audiowith the right media type. - A minimal picker page is served at
/multimodal.html.
- Vision —
An @AiEndpoint at /atmosphere/ai-chat:
- Client connects via WebSocket and sends a prompt
- The
@Prompthandler callssession.stream(message)which routes through theAgentRuntimeSPI - The framework handles conversation memory, interceptors, guardrails, and streaming automatically
- Each streaming text is pushed to the client as a JSON frame
@AiEndpoint(path = "/atmosphere/ai-chat",
systemPromptResource = "skill:ai-assistant",
requires = {AiCapability.TEXT_STREAMING, AiCapability.SYSTEM_PROMPT},
conversationMemory = true)
public class AiChat {
@Prompt
public void onPrompt(String message, StreamingSession session) {
session.stream(message);
}
}Uses the useChat hook from atmosphere.js/react:
- Connects to
/atmosphere/ai-chatover WebSocket - Parses streaming JSON messages and
AiEventframes - Keeps optimistic user and assistant message state in one hook
- Renders streaming texts as they arrive with markdown support
- Shows model name, cost, and latency badges
Set environment variables before running:
# Gemini (default)
export LLM_API_KEY=AIza...
# OpenAI
export LLM_MODEL=gpt-4o-mini
export LLM_BASE_URL=https://api.openai.com/v1
export LLM_API_KEY=sk-...
# Ollama (local)
export LLM_MODE=local
export LLM_MODEL=llama3.2# From the repository root
./mvnw spring-boot:run -pl samples/spring-boot-ai-chat
# Or via the CLI
atmosphere run spring-boot-ai-chatOpen http://localhost:8080 in your browser. The AI Console UI is bundled at
/atmosphere/console/ (the root path redirects there).
Token-based authentication is disabled by default in this sample
(atmosphere.auth.enabled=false in application.properties) so the bundled
AI Console connects out-of-the-box. The framework default is fail-closed
per Correctness Invariant #6 — the sample-level override is explicit.
To demo the bundled AuthConfig token flow, run with auth enabled:
./mvnw spring-boot:run -pl samples/spring-boot-ai-chat \
-Dspring-boot.run.arguments="--atmosphere.auth.enabled=true"Then mint a token and use it on the handshake:
# 1. Mint a demo token
curl -s -X POST http://localhost:8080/api/auth/login \
-H 'Content-Type: application/json' -d '{"user":"demo"}'
# -> {"token":"demo-token"}
# 2. Use it as a header
curl -i -H 'X-Atmosphere-Auth: demo-token' http://localhost:8080/atmosphere/ai-chat
# Or as a query parameter (works for WebSocket too)
curl -i 'http://localhost:8080/atmosphere/ai-chat?X-Atmosphere-Auth=demo-token'Without X-Atmosphere-Auth (and with auth enabled), the handshake returns
HTTP 401 X-Atmosphere-error: No authentication token provided.
spring-boot-ai-chat/
├── pom.xml
├── frontend/ # React + Vite frontend
│ └── src/
│ ├── App.tsx # Chat UI with useChat hook
│ └── main.tsx # AtmosphereProvider wrapper
└── src/main/
├── java/.../aichat/
│ ├── AiChatApplication.java # Spring Boot entry point
│ ├── AiChat.java # @AiEndpoint with capability validation
│ ├── MultiModalAgent.java # @Agent — vision + audio input, skill-file persona
│ ├── AuthConfig.java # Token-based authentication
│ ├── DemoResponseProducer.java # Simulated streaming for demo mode
│ └── LlmConfig.java # Spring properties → AiConfig bridge
└── resources/
├── application.yml # LLM config (model, mode, API key)
├── prompts/ # @Agent skill files (multimodal-assistant-skill.md)
└── static/ # Built frontend assets
- AI Tools sample — framework-agnostic tool calling with real-time tool events
- Dentist agent — full
@Agentwith commands, tools, and multi-channel - Multi-agent startup team — 5 agents collaborating via A2A