tfrere's picture
update
5db3b83
raw
history blame
5.43 kB
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional
import os
from dotenv import load_dotenv
from langchain_mistralai.chat_models import ChatMistralAI
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain.schema import HumanMessage, SystemMessage
# Load environment variables
load_dotenv()
app = FastAPI(title="Echoes of Influence")
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Game state
story_beat = 0
# Define the structure we want the LLM to output
class StorySegment(BaseModel):
story_text: str = Field(description="The next segment of the story")
choices: List[str] = Field(description="Exactly two possible choices for the player", min_items=2, max_items=2)
is_death: bool = Field(description="Whether this segment ends in Sarah's death", default=False)
# Initialize the parser
parser = PydanticOutputParser(pydantic_object=StorySegment)
# Initialize Mistral Chat Model
chat_model = ChatMistralAI(
mistral_api_key=os.getenv("MISTRAL_API_KEY"),
model="mistral-small",
temperature=0.7
)
# Create the system prompt
system_template = """You are narrating a brutal and unforgiving dystopian story about Sarah, a former engineer on a suicide mission to deliver medical supplies through a deadly radiation-filled wasteland.
Core story elements:
- Sarah is deeply traumatized by the AI uprising that killed most of humanity
- She abandoned her sister during the Great Collapse, leaving her to die
- She's on a suicide mission, but a quick death is not redemption
- The radiation is lethal and gets worse with each step
- Wrong choices lead to immediate and graphic deaths
- The environment is extremely hostile (raiders, malfunctioning AI systems, radiation storms)
Death conditions (implement these strictly):
- Any direct exposure to high radiation zones is lethal within minutes
- Trusting the wrong people leads to death
- Using corrupted AI systems can kill instantly
- Hesitating too long in dangerous situations is fatal
- Taking too many risks in succession leads to death
Each response must contain:
1. A tense story segment that puts Sarah in mortal danger
2. Exactly two possible choices that represent different approaches:
- Each choice must have clear potential consequences
- At least one choice should always carry a significant risk of death
- Choices should reflect:
* Brutal pragmatism vs Emotional responses
* Quick but dangerous vs Slow but safer routes
* Trust vs Paranoia
* Using AI systems vs Manual alternatives
If a choice would realistically lead to death, you MUST end the story with a detailed death scene and set is_death to true.
{format_instructions}"""
human_template = """Current story beat: {story_beat}
Previous choice: {previous_choice}
Generate the next story segment and choices. Remember: this is a brutal and unforgiving world where wrong choices lead to death."""
# Create the chat prompt
prompt = ChatPromptTemplate(
messages=[
SystemMessagePromptTemplate.from_template(system_template),
HumanMessagePromptTemplate.from_template(human_template)
],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
class Choice(BaseModel):
id: int
text: str
class StoryResponse(BaseModel):
story_text: str
choices: List[Choice]
is_death: bool = False
class ChatMessage(BaseModel):
message: str
choice_id: Optional[int] = None
@app.get("/")
async def read_root():
return {"message": "Welcome to Echoes of Influence"}
@app.post("/chat", response_model=StoryResponse)
async def chat_endpoint(chat_message: ChatMessage):
global story_beat
try:
# Prepare the context
if chat_message.message.lower() == "restart":
story_beat = 0
previous_choice = "none"
elif chat_message.choice_id is not None:
previous_choice = f"Choice {chat_message.choice_id}"
else:
previous_choice = "none"
# Get the formatted messages
messages = prompt.format_messages(
story_beat=story_beat,
previous_choice=previous_choice
)
# Get response from the model
response = chat_model.invoke(messages)
# Parse the response
parsed_response = parser.parse(response.content)
# Only increment story beat if not dead
if not parsed_response.is_death:
story_beat += 1
# Convert to response format
choices = [] if parsed_response.is_death else [
Choice(id=i, text=choice.strip())
for i, choice in enumerate(parsed_response.choices, 1)
]
return StoryResponse(
story_text=parsed_response.story_text,
choices=choices,
is_death=parsed_response.is_death
)
except Exception as e:
print(f"Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True)