RolePlay/docs/OPEN_TASKS_PLAN.md
Konrad Neitzel b79334ee67 Implement Ollama integration for session management and turn processing
- 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.
2026-02-21 12:45:20 +01:00

156 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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