- 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.
241 lines
10 KiB
Java
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();
|
|
}
|
|
}
|