Real PostgreSQL 16 and Redis Stack 7.4; 24 tests write output/01-24. Covers the 20-message default window with no property, the 36-character conversation_id, tool messages dropped by JdbcChatMemoryRepository, concurrent add() on one conversation, a 1.x table under the 2.0 repository, the Redis repository silently backing off for a custom ChatMemory, and the removal of PromptChatMemoryAdvisor. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ja4jkzrbQ4LQZBNrb5mkZE
68 lines
4.6 KiB
Markdown
68 lines
4.6 KiB
Markdown
# chat-memory
|
|
|
|
Companion code for [Chat Memory in Spring AI 2.0: JDBC, Redis and Windowed Conversations](https://ankurm.com/spring-ai-2-0-chat-memory-jdbc-redis-windowed-conversations/), part of the [Spring AI series](../README.md) on ankurm.com.
|
|
|
|
`MessageChatMemoryAdvisor` + `MessageWindowChatMemory` + a `ChatMemoryRepository` (in-memory, PostgreSQL through JDBC, or Redis Stack), with per-user conversation IDs, a token-budget memory of our own, and every trap the article describes reproduced by a test that writes its own transcript.
|
|
|
|
There is no live model anywhere: [`ScriptedChatModel`](src/test/java/com/ankurm/chatmemory/support/ScriptedChatModel.java) records the prompts it is sent, which is what a memory test needs to prove -- *which messages reached the model*. The databases are real.
|
|
|
|
## Versions
|
|
|
|
| Component | Version |
|
|
|---|---|
|
|
| Spring Boot | 4.1.1 |
|
|
| Spring AI | 2.0.1 (GA 2026-06-12 for 2.0.0; 2.0.1 on Maven Central 2026-08-20) |
|
|
| PostgreSQL | 16.13 |
|
|
| Redis Stack | 7.4.7 (RediSearch 2.10.20, RedisJSON) |
|
|
| Jedis (pulled in by the Redis starter) | 7.4.1 |
|
|
| Java | 25 (LTS) |
|
|
|
|
## Quickstart
|
|
|
|
```bash
|
|
scripts/services-up.sh # PostgreSQL 16 (apt), Redis Stack (docker), plain Redis (apt) -- optional
|
|
scripts/run-all.sh # runs all 24 tests and regenerates output/01 .. 24
|
|
```
|
|
|
|
Tests that need a service which is not listening are **skipped with a message**, not failed, so the in-memory tests still run with nothing installed. Run the app itself with `mvn spring-boot:run` (in-memory), `-Dspring-boot.run.profiles=jdbc` or `=redis`; it needs `OPENAI_API_KEY` for real answers.
|
|
|
|
## What's here
|
|
|
|
| File | What it shows |
|
|
|---|---|
|
|
| [`config/MemoryConfig.java`](src/main/java/com/ankurm/chatmemory/config/MemoryConfig.java) | The `ChatMemory` bean (window size is not configurable by property) and the `ChatClient` with `MessageChatMemoryAdvisor` |
|
|
| [`config/RedisMemoryConfig.java`](src/main/java/com/ankurm/chatmemory/config/RedisMemoryConfig.java) | Why the Redis repository is built by hand: the autoconfigured one silently steps aside for a custom `ChatMemory` |
|
|
| [`memory/ConversationRegistry.java`](src/main/java/com/ankurm/chatmemory/memory/ConversationRegistry.java) | Who owns which conversation; `app.ownership.enforce=false` reproduces the leak |
|
|
| [`memory/ConversationService.java`](src/main/java/com/ankurm/chatmemory/memory/ConversationService.java) | Passing `ChatMemory.CONVERSATION_ID` per request |
|
|
| [`memory/TokenBudgetChatMemory.java`](src/main/java/com/ankurm/chatmemory/memory/TokenBudgetChatMemory.java) | A `ChatMemory` that prunes by tokens instead of by message count |
|
|
| [`web/ChatController.java`](src/main/java/com/ankurm/chatmemory/web/ChatController.java) | Four endpoints; the user comes from an `X-User` header only so the tests need no login |
|
|
| [`application*.yml`](src/main/resources) | Profiles `jdbc` and `redis`; the default excludes both repository autoconfigurations |
|
|
| [`src/broken/PromptAdvisorFrom1x.java`](src/broken/PromptAdvisorFrom1x.java) | Not compiled by the build; `scripts/capture-1x-compile.sh` compiles it against 2.0.1 and 1.1.8 |
|
|
|
|
## Output files
|
|
|
|
Every file is written by the test named in the right column (or by the script), and every console block in the article is quoted from one of them.
|
|
|
|
| File | Written by |
|
|
|---|---|
|
|
| `01-window-semantics.txt` | `WindowSemanticsTest` |
|
|
| `02-advisor-conversations.txt`, `03-missing-conversation-id.txt` | `AdvisorConversationTest` |
|
|
| `04-autoconfigured-defaults.txt`, `05-no-window-property.txt` | `DefaultWindowTest` |
|
|
| `06-token-growth.txt` | `TokenGrowthTest` |
|
|
| `07-token-budget-memory.txt` | `TokenBudgetChatMemoryTest` |
|
|
| `08` .. `14` (JDBC round trip, 36-character limit, dropped tool messages, lost update, overlapping saves, 1.x table upgrade, locked fix) | `JdbcPostgresTest` |
|
|
| `15-redis-round-trip.txt`, `16-redis-lost-update.txt` | `RedisStackTest` |
|
|
| `17-redis-silent-fallback.txt` | `RedisSilentFallbackTest` |
|
|
| `18-redis-ttl-and-cap.txt` | `RedisCapsAndTtlTest` |
|
|
| `19-redis-plain-fails.txt` | `RedisPlainFailureTest` |
|
|
| `20-ownership-enforced.txt`, `21-ownership-unenforced.txt` | `OwnershipTest`, `OwnershipLeakTest` |
|
|
| `22-config-keys.txt` | `ConfigKeysTest` |
|
|
| `23-prompt-advisor-removed.txt` | `scripts/capture-1x-compile.sh` |
|
|
| `24-memory-with-tool-calling.txt` | `ToolCallingMemoryTest` |
|
|
|
|
Two consecutive `mvn test` runs produce byte-identical files. `12-jdbc-overlapping-saves.txt` deliberately prints no exact count: two `saveAll` calls that overlap produce 3 or 6 messages depending on timing, and the file says so instead of pinning one run.
|
|
|
|
## Requirements
|
|
|
|
JDK 25, Maven. PostgreSQL 16 and Redis Stack are optional (see above); Docker is only used to run Redis Stack.
|