- Enhance InMemorySessionService to utilize the two-call Ollama pattern for session creation and turn submissions, generating narratives and state updates based on provided scenarios. - Introduce OllamaContextBuilder to construct turn contexts for both session initialization and turn continuation. - Add OllamaPrompts class to define system prompts for narrative generation and state extraction. - Implement StateUpdateMapper to handle merging state updates into session responses. - Create unit tests for InMemorySessionService to validate Ollama interactions and ensure correct session state management.
156 lines
12 KiB
Markdown
156 lines
12 KiB
Markdown
# Plan: Open Tasks (Ollama Integration in InMemorySessionService)
|
||
|
||
This document plans the two remaining backend open tasks: wiring the two-call Ollama pattern into **createSession** and **submitTurn** in [InMemorySessionService](src/main/java/de/neitzel/roleplay/business/InMemorySessionService.java).
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
| Task | Method | Goal |
|
||
|------|--------|------|
|
||
| 1 | `createSession` | Produce opening narrative and initial state via Ollama (narrative + state extraction). |
|
||
| 2 | `submitTurn` | Produce turn narrative and state update via Ollama (narrative + state update), then merge into session. |
|
||
|
||
Both follow the same pattern: build **TurnContext** (JSON) → **Call 1** (narrative) → **Call 2** (state update with narrative in user message) → map **StateUpdateResponse** and narrative into API responses and session state.
|
||
|
||
---
|
||
|
||
## Prerequisites and Dependencies
|
||
|
||
- **[OllamaClient](src/main/java/de/neitzel/roleplay/fascade/OllamaClient.java)** – already provides `generateNarrative(model, systemPrompt, userContent)` and `generateStateUpdate(model, systemPrompt, userContent)`.
|
||
- **Common types** ([common](src/main/java/de/neitzel/roleplay/common/)) – `TurnContext`, `StateUpdateResponse`, `SituationSnapshot`, `CharacterSet`, `CharacterSnapshot`, `UserAction`, `Recommendation`, `SituationUpdate`, `CharacterUpdate`, `OpenThreadsChanges`, `CharacterResponse`, `Suggestion` (with `SuggestionType`, `RiskLevel`).
|
||
- **API models** (`de.neitzel.roleplay.fascade.model`) – generated from OpenAPI: `SessionResponse`, `TurnResponse`, `SituationState`, `CharacterState`, `Suggestion`, `CharacterResponseItem`, etc.
|
||
- **Prompts** – defined in [ROLEPLAY_CONCEPT.md](docs/ROLEPLAY_CONCEPT.md) (§4.3 for init, §4.4 for turn). Should be extracted to constants or a small prompt provider (e.g. in `business` or `common`) so they stay consistent and maintainable.
|
||
|
||
---
|
||
|
||
## Task 1: createSession – Opening scene and initial state
|
||
|
||
### Flow
|
||
|
||
1. Create `SessionResponse` with sessionId, model, language, safetyLevel, turnNumber (0) – **unchanged**.
|
||
2. Build initial **situation** and **characters** from `request.getScenario()` (reuse existing `buildSituationFromScenario` / `buildCharactersFromScenario`).
|
||
3. Build **TurnContext** for session init:
|
||
- **currentSituation**: Map from `SituationState` (API) to `SituationSnapshot` (common). Initial values: setting, currentScene from scenario; timeline/openThreads/externalPressures/worldStateFlags can be empty or minimal.
|
||
- **characters**: Map from `CharacterState` list to `CharacterSet` (userCharacter + aiCharacters). Map each to `CharacterSnapshot` (id, name, role, personalityTraits, speakingStyle, goals from scenario; currentMood/status/knowledge/relationships/recentActionsSummary can be null or default).
|
||
- **userAction**: `null` (no user action at init).
|
||
- **recommendation**: `null`.
|
||
- **recentHistorySummary**: `null` or `""`.
|
||
4. Serialise `TurnContext` to JSON (snake_case) via Jackson `ObjectMapper`.
|
||
5. **Call 1** – `ollamaClient.generateNarrative(model, INIT_SYSTEM_PROMPT, contextJson)`.
|
||
6. **Call 2** – `ollamaClient.generateStateUpdate(model, STATE_EXTRACT_SYSTEM_PROMPT, contextJson + "\n\nNarrative that was just generated:\n" + narrative)`.
|
||
7. Set **session** from Call 1 + Call 2:
|
||
- `session.setNarrative(narrative)` (from Call 1).
|
||
- Apply **StateUpdateResponse** to session:
|
||
- **updated_situation** → merge into `session.getSituation()` (or build new `SituationState`): currentScene, append newTimelineEntries to timeline, apply openThreadsChanges (add/remove from openThreads), set worldStateFlags.
|
||
- **updated_characters** → merge into `session.getCharacters()` by characterId: update currentMood, append knowledgeGained to knowledge, apply relationshipChanges to relationships; keep id/name/role/isUserCharacter from existing list.
|
||
- **suggestions** → map common `Suggestion` list to API `Suggestion` list and `session.setSuggestions(...)`.
|
||
8. Store session and return.
|
||
|
||
### Design choices
|
||
|
||
- **Context builder**: Introduce a small helper (or service) to build `TurnContext` from scenario + optional session state, and to serialise it. Keeps `InMemorySessionService` focused on orchestration.
|
||
- **Situation/character mapping**: Reuse or introduce mappers between API models (`SituationState`, `CharacterState`) and common types (`SituationSnapshot`, `CharacterSnapshot`) so both createSession and submitTurn can share the same logic.
|
||
- **Error handling**: If Ollama fails (network, parse), decide: throw and fail session creation, or fall back to placeholder narrative and log. Prefer throwing and letting the resource layer return 5xx, with a clear message.
|
||
|
||
---
|
||
|
||
## Task 2: submitTurn – Turn narrative and state update
|
||
|
||
### Flow
|
||
|
||
1. Load session; if missing return `Optional.empty()` – **unchanged**.
|
||
2. Increment turn number – **unchanged**.
|
||
3. Build **TurnContext** for this turn:
|
||
- **currentSituation**: From `session.getSituation()` → `SituationSnapshot` (setting, currentScene, timeline, openThreads, externalPressures, worldStateFlags).
|
||
- **characters**: From `session.getCharacters()` → `CharacterSet` (userCharacter + aiCharacters as `CharacterSnapshot` list).
|
||
- **userAction**: From `turnRequest.getUserAction()` → map API `UserActionRequest` to common `UserAction` (type, content, selectedSuggestionId).
|
||
- **recommendation**: From `turnRequest.getRecommendation()` → map to common `Recommendation` (desiredTone, preferredDirection, focusCharacters), or null.
|
||
- **recentHistorySummary**: For now empty string or a short placeholder; later can be derived from stored turn history if added.
|
||
4. Serialise `TurnContext` to JSON.
|
||
5. **Call 1** – `ollamaClient.generateNarrative(model, TURN_NARRATIVE_SYSTEM_PROMPT, contextJson)`.
|
||
6. **Call 2** – `ollamaClient.generateStateUpdate(model, STATE_EXTRACT_SYSTEM_PROMPT, contextJson + "\n\nNarrative that was just generated:\n" + narrative)`.
|
||
7. Build **TurnResponse**:
|
||
- turnNumber, narrative (from Call 1).
|
||
- characterResponses, updatedSituation, updatedCharacters, suggestions from `StateUpdateResponse` (mapped to API model types).
|
||
8. **Merge** state update into session (same as createSession): apply updated_situation to `session.getSituation()`, updated_characters to `session.getCharacters()`, replace `session.setSuggestions(...)`, set `session.setNarrative(narrative)`, `session.setTurnNumber(nextTurn)`.
|
||
9. Save session and return `Optional.of(turnResponse)`.
|
||
|
||
### Design choices
|
||
|
||
- **Shared merge logic**: Extract “apply StateUpdateResponse to SessionResponse” into a private method (or small helper) used by both createSession and submitTurn.
|
||
- **TurnResponse mapping**: Map `StateUpdateResponse` (common) to `TurnResponse` (API): responses → characterResponses, updatedSituation, updatedCharacters, suggestions. API uses same structure; ensure enum/value names match (e.g. speech/action/reaction, risk_level).
|
||
|
||
---
|
||
|
||
## Shared implementation elements
|
||
|
||
### 1. Prompts
|
||
|
||
- **INIT_SYSTEM_PROMPT** (opening narrative) – from concept §4.3 Call 1.
|
||
- **STATE_EXTRACT_SYSTEM_PROMPT** (JSON extraction) – from concept §4.3 Call 2 (same for init and turn).
|
||
- **TURN_NARRATIVE_SYSTEM_PROMPT** (turn continuation) – from concept §4.4 Call 1.
|
||
|
||
Store as constants in a single class (e.g. `OllamaPrompts` in `business`) or in a configurable provider.
|
||
|
||
### 2. Context building
|
||
|
||
- **Session init**: `TurnContext` from scenario-derived situation + characters; no userAction/recommendation.
|
||
- **Turn**: `TurnContext` from current session situation + characters + userAction + recommendation.
|
||
|
||
Both need **API → common** mapping for situation and characters when building the context. A dedicated mapper or builder class keeps the service clean.
|
||
|
||
### 3. State merge (StateUpdateResponse → SessionResponse)
|
||
|
||
- **Situation**: Apply `SituationUpdate`: set currentScene if present; append newTimelineEntries to timeline; apply openThreadsChanges (add/remove from openThreads); replace or merge worldStateFlags.
|
||
- **Characters**: For each `CharacterUpdate` find character by characterId; update currentMood; append knowledgeGained to knowledge; merge relationshipChanges into relationships.
|
||
- **Suggestions**: Replace session suggestions with the new list (mapped from common to API).
|
||
|
||
### 4. API ↔ common mapping
|
||
|
||
- **Common** uses snake_case (Jackson `@JsonNaming`); **API** models are OpenAPI-generated (camelCase). When mapping StateUpdateResponse (common) into SessionResponse / TurnResponse (API), copy fields and convert enums where needed (e.g. ResponseType, SuggestionType, RiskLevel).
|
||
- **TurnContext** is always built from common types and serialised to JSON as-is for Ollama.
|
||
|
||
---
|
||
|
||
## Suggested order of implementation
|
||
|
||
1. **Prompts** – Add `OllamaPrompts` (or similar) with the three system prompt strings from the concept doc.
|
||
2. **Mappers** – Add mapping from API `SituationState`/`CharacterState` (and scenario) to common `SituationSnapshot`/`CharacterSet`/`CharacterSnapshot`; and from common `StateUpdateResponse`/`Suggestion`/etc. to API types for SessionResponse/TurnResponse. Optionally a dedicated “context builder” that takes scenario or session + turnRequest and returns `TurnContext`.
|
||
3. **State merge** – Implement “apply StateUpdateResponse to SessionResponse” (situation, characters, suggestions) in one place.
|
||
4. **createSession** – Inject `OllamaClient` and `ObjectMapper`; build context from scenario; call two-step Ollama; merge state; set narrative/situation/characters/suggestions on session.
|
||
5. **submitTurn** – Build context from session + turnRequest; call two-step Ollama; build TurnResponse; merge state into session; return TurnResponse.
|
||
6. **Tests** – Update or add unit tests: mock `OllamaClient` in `InMemorySessionServiceTest` to verify two-call pattern and state merge; optionally integration test with a real or stubbed Ollama.
|
||
|
||
---
|
||
|
||
## Risks and mitigations
|
||
|
||
| Risk | Mitigation |
|
||
|------|------------|
|
||
| Ollama slow/unavailable | Timeouts already set in rest-client config; fail fast and return 502/503 from resource layer. |
|
||
| Model returns invalid JSON (Call 2) | Already throws `OllamaParseException`; map to 502 or 503 in a mapper if not already. |
|
||
| Large context / token limit | Keep recentHistorySummary short; later add condensing of older turns. |
|
||
| Enum mismatches (API vs common) | Use same enum names in OpenAPI as in common (e.g. speech, action, reaction); document mapping in one place. |
|
||
|
||
---
|
||
|
||
## Files to touch (summary)
|
||
|
||
| File / area | Change |
|
||
|------------|--------|
|
||
| New: e.g. `business/OllamaPrompts.java` | System prompt constants for init narrative, turn narrative, state extraction. |
|
||
| New or existing: mappers / context builder | Build `TurnContext` from scenario or session + turnRequest; map StateUpdateResponse → SessionResponse / TurnResponse. |
|
||
| [InMemorySessionService](src/main/java/de/neitzel/roleplay/business/InMemorySessionService.java) | Inject `OllamaClient`, `ObjectMapper`; implement createSession and submitTurn with two-call pattern and state merge; remove TODOs and placeholder narratives. |
|
||
| [InMemorySessionServiceTest](src/test/java/de/neitzel/roleplay/business/InMemorySessionServiceTest.java) | Add or adjust tests with mocked OllamaClient to verify createSession and submitTurn call Ollama and merge state. |
|
||
| Optional: exception mapper | Map `OllamaParseException` (and network errors) to a suitable HTTP response if not already. |
|
||
|
||
---
|
||
|
||
## Done criteria
|
||
|
||
- **createSession**: Opening narrative and initial situation/characters/suggestions come from Ollama (Call 1 + Call 2); no placeholder text.
|
||
- **submitTurn**: Turn narrative and updated situation/characters/suggestions come from Ollama; session state is updated; TurnResponse contains narrative and structured state (characterResponses, updatedSituation, updatedCharacters, suggestions).
|
||
- Unit tests cover the two-call flow with mocked OllamaClient and assert on state merge.
|
||
- No remaining TODOs in InMemorySessionService for Ollama integration.
|