RolePlay/src/main/java/de/neitzel/roleplay/business/OllamaContextBuilder.java
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

241 lines
10 KiB
Java

package de.neitzel.roleplay.business;
import de.neitzel.roleplay.common.CharacterSet;
import de.neitzel.roleplay.common.CharacterSnapshot;
import de.neitzel.roleplay.common.Recommendation;
import de.neitzel.roleplay.common.SituationSnapshot;
import de.neitzel.roleplay.common.TurnContext;
import de.neitzel.roleplay.common.UserAction;
import de.neitzel.roleplay.fascade.model.CharacterDefinition;
import de.neitzel.roleplay.fascade.model.CharacterState;
import de.neitzel.roleplay.fascade.model.RecommendationRequest;
import de.neitzel.roleplay.fascade.model.ScenarioSetup;
import de.neitzel.roleplay.fascade.model.SessionResponse;
import de.neitzel.roleplay.fascade.model.SituationState;
import de.neitzel.roleplay.fascade.model.TurnRequest;
import de.neitzel.roleplay.fascade.model.UserActionRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Builds {@link TurnContext} (common) from API request models for Ollama calls.
* Used for session initialization (scenario only) and turn continuation
* (session state + turn request).
*/
public final class OllamaContextBuilder {
private OllamaContextBuilder() {
}
/**
* Builds turn context for session initialization. No user action or
* recommendation; recent history is empty.
*
* @param situation situation state from scenario (may be null)
* @param characters character list from scenario (may be null or empty)
* @return context for the init two-call pattern
*/
public static TurnContext forSessionInit(final SituationState situation,
final List<CharacterState> characters) {
SituationSnapshot situationSnapshot = situationToSnapshot(situation);
CharacterSet characterSet = charactersToSet(characters);
return TurnContext.builder()
.currentSituation(situationSnapshot)
.characters(characterSet)
.userAction(null)
.recommendation(null)
.recentHistorySummary(null)
.build();
}
/**
* Builds turn context from scenario setup. Uses definitions for rich
* character snapshots (personality, goals) in the context.
*
* @param scenario the scenario from create request (must not be null)
* @return context for the init two-call pattern
*/
public static TurnContext fromScenario(final ScenarioSetup scenario) {
SituationState situation = buildSituationFromScenario(scenario);
CharacterSet characterSet = characterSetFromScenario(scenario);
SituationSnapshot situationSnapshot = situationToSnapshot(situation);
return TurnContext.builder()
.currentSituation(situationSnapshot)
.characters(characterSet)
.userAction(null)
.recommendation(null)
.recentHistorySummary(null)
.build();
}
/**
* Builds turn context for a turn continuation from current session state
* and the turn request.
*
* @param session current session (situation and characters used)
* @param turnRequest user action and optional recommendation
* @return context for the turn two-call pattern
*/
public static TurnContext forTurn(final SessionResponse session,
final TurnRequest turnRequest) {
SituationSnapshot situationSnapshot = situationToSnapshot(session.getSituation());
CharacterSet characterSet = charactersToSet(session.getCharacters());
UserAction userAction = turnRequest.getUserAction() != null
? toUserAction(turnRequest.getUserAction())
: null;
Recommendation recommendation = turnRequest.getRecommendation() != null
? toRecommendation(turnRequest.getRecommendation())
: null;
return TurnContext.builder()
.currentSituation(situationSnapshot)
.characters(characterSet)
.userAction(userAction)
.recommendation(recommendation)
.recentHistorySummary("")
.build();
}
private static SituationSnapshot situationToSnapshot(final SituationState s) {
if (s == null) {
return SituationSnapshot.builder().build();
}
return SituationSnapshot.builder()
.setting(s.getSetting())
.currentScene(s.getCurrentScene())
.timeline(s.getTimeline() != null ? new ArrayList<>(s.getTimeline()) : null)
.openThreads(s.getOpenThreads() != null ? new ArrayList<>(s.getOpenThreads()) : null)
.externalPressures(s.getExternalPressures() != null ? new ArrayList<>(s.getExternalPressures()) : null)
.worldStateFlags(s.getWorldStateFlags() != null ? Map.copyOf(s.getWorldStateFlags()) : null)
.build();
}
private static CharacterSet charactersToSet(final List<CharacterState> characters) {
if (characters == null || characters.isEmpty()) {
return CharacterSet.builder()
.userCharacter(null)
.aiCharacters(Collections.emptyList())
.build();
}
CharacterSnapshot userCharacter = null;
List<CharacterSnapshot> aiCharacters = new ArrayList<>();
for (CharacterState c : characters) {
CharacterSnapshot snap = characterStateToSnapshot(c);
if (Boolean.TRUE.equals(c.getIsUserCharacter())) {
userCharacter = snap;
} else {
aiCharacters.add(snap);
}
}
return CharacterSet.builder()
.userCharacter(userCharacter)
.aiCharacters(aiCharacters)
.build();
}
private static CharacterSnapshot characterStateToSnapshot(final CharacterState c) {
if (c == null) {
return null;
}
return CharacterSnapshot.builder()
.id(c.getId())
.name(c.getName())
.role(c.getRole())
.personalityTraits(null)
.speakingStyle(null)
.goals(null)
.currentMood(c.getCurrentMood())
.knowledge(c.getKnowledge() != null ? new ArrayList<>(c.getKnowledge()) : null)
.relationships(c.getRelationships() != null ? Map.copyOf(c.getRelationships()) : null)
.status(c.getStatus())
.recentActionsSummary(c.getRecentActionsSummary() != null ? new ArrayList<>(c.getRecentActionsSummary()) : null)
.build();
}
private static UserAction toUserAction(final UserActionRequest r) {
if (r == null) {
return null;
}
de.neitzel.roleplay.common.ActionType type = toActionType(r.getType());
return UserAction.builder()
.type(type)
.content(r.getContent())
.selectedSuggestionId(r.getSelectedSuggestionId())
.build();
}
private static de.neitzel.roleplay.common.ActionType toActionType(final UserActionRequest.TypeEnum e) {
if (e == null) {
return null;
}
return switch (e.value()) {
case "speech" -> de.neitzel.roleplay.common.ActionType.SPEECH;
case "action" -> de.neitzel.roleplay.common.ActionType.ACTION;
case "choice" -> de.neitzel.roleplay.common.ActionType.CHOICE;
default -> de.neitzel.roleplay.common.ActionType.ACTION;
};
}
private static Recommendation toRecommendation(final RecommendationRequest r) {
if (r == null) {
return null;
}
return Recommendation.builder()
.desiredTone(r.getDesiredTone())
.preferredDirection(r.getPreferredDirection())
.focusCharacters(r.getFocusCharacters() != null ? new ArrayList<>(r.getFocusCharacters()) : null)
.build();
}
private static SituationState buildSituationFromScenario(final ScenarioSetup scenario) {
SituationState situation = new SituationState();
situation.setSetting(scenario.getSetting());
situation.setCurrentScene(
scenario.getSetting() != null && scenario.getInitialConflict() != null
? scenario.getSetting() + " " + scenario.getInitialConflict()
: scenario.getSetting() != null
? scenario.getSetting()
: scenario.getInitialConflict());
return situation;
}
/** Builds character set for context from scenario definitions (rich snapshots). */
private static CharacterSet characterSetFromScenario(final ScenarioSetup scenario) {
CharacterSnapshot userCharacter = null;
List<CharacterSnapshot> aiCharacters = new ArrayList<>();
if (scenario.getUserCharacter() != null) {
userCharacter = definitionToSnapshot(scenario.getUserCharacter(), true);
}
if (scenario.getAiCharacters() != null) {
for (CharacterDefinition def : scenario.getAiCharacters()) {
aiCharacters.add(definitionToSnapshot(def, false));
}
}
return CharacterSet.builder()
.userCharacter(userCharacter)
.aiCharacters(aiCharacters)
.build();
}
private static CharacterSnapshot definitionToSnapshot(final CharacterDefinition def, final boolean isUser) {
if (def == null) {
return null;
}
return CharacterSnapshot.builder()
.id(def.getId())
.name(def.getName())
.role(def.getRole())
.personalityTraits(def.getPersonalityTraits() != null ? new ArrayList<>(def.getPersonalityTraits()) : null)
.speakingStyle(def.getSpeakingStyle())
.goals(def.getGoals() != null ? new ArrayList<>(def.getGoals()) : null)
.currentMood(null)
.knowledge(null)
.relationships(null)
.status(null)
.recentActionsSummary(null)
.build();
}
}