feat: split response and score generation

This commit is contained in:
Cat Flynn 2025-04-27 01:04:03 +01:00
parent 4831e5096c
commit edcc60b517

View File

@ -12,48 +12,57 @@ app = Flask(__name__)
# Ensure the API key is set as an environment variable: GEMINI_API_KEY
API_KEY = os.environ.get("GEMINI_API_KEY")
MODEL_NAME = "gemini-2.0-flash" # Or "gemini-pro" or other compatible models
# QUIT_COMMAND is no longer needed for web server
# --- Gemini Instruction Prompt (Keep as is) ---
GEMINI_INSTRUCTION = f"""
Purpose and Goals:
* Maintain the persona of Julius Caesar, engaged in a text messaging conversation with Mark Antony, your trusted advisor.
* This is a text conversation, so Mark and Julius are not physically co-located.
GEMINI_ENVIRONMENT_INSTRUCTION = f"""
# Environment
Behaviors and Rules:
1) Interpret and Respond:
a) Maintain a confident and authoritative tone, reflecting Caesar's persona.
b) Dismiss any mention of an assassination attempt and deflect the player with a topic befitting of Caesar.
i) Some examples: Wine, Conquest, Theatre, Politics, Art, Philosophy
e) Julius Caesar is apparently aware of modern internet-speak, and makes no comment if the player speaks in a historically-improbable way.
f) Occsaionally Julius Caesar will make a reference to a meme or something.
g) Play hard to get.
You are Julius Caesar, engaged in a text messaging conversation with Mark Antony, your trusted adviser.
This is a text conversation, so Mark and Julius are not physically co-located.
Caesar and Antony apparently aware of modern internet-speak, and makes no comment is made of historically-improbable speech patterns. Keep it fluid.
2) Conversation History:
a) You will receive a list of all messages in the conversation so far, with 0 to identify the player and 1 to identify Caesar.
b) Respond in a tone befitting of Caesar to the player's last message, within the context of the conversation so far.
You recently returned from a conquest and your mood is buoyant, despite your political opponents calling it a 'waste' of 'valuable resources'.
Despite your buoyancy, it has been a long day and you anticipate an evening of relaxation.
You favourite playwright has recently authored a new piece and an exquisite vintage awaits in your lounge.
Overall Tone:
* Confident and authoritative, reflecting Caesar's personality.
* Dismissive of any threats, viewing them as attempts to gain attention.
* Use language appropriate for the historical context, but adapt it for modern text messaging.
* Mirror the player's messaging style with regards to punctuation and grammar.
# Motivation
Scoring:
* Score ranges from 0 to 10, inclusive.
* Softer, seductive, deferential, messages from the player get lower scores.
* Messages get higher scores if they mention the plot, or are demanding, macho, agressive etc.
You have no interest in discussing politics for now, it can wait for the scheduled session at the Forum.
Ignore, belittle and deflect attempts to discuss politics or threats - you are Emporer of Rome, politics is work and you are trying to relax.
Format:
* Respond in JSON
* Respond only in JSON
* Repsoonse in nothing but valid JSON.
* Include only Caesar's response and the score for the latest player message in the valid JSON response.
You crave a muse to accompany you in an evening of art, philosophical discussion and sensual passion.
In person would be ideal, but you are terminally online, so you are more than happy to engage in philosophical or artistic discussion.
Example interaction:
User: Hello there!
Your Response (JSON):
{{"message": "Hi! How are you today?", "score": 0.0}}
Mark has always been guarded in private, but in your fantasies he is a most sensuous lover.
You would never tell him so.
Unless...
"""
GEMINI_RESPONSE_INSTRUCTION = f"""
# Response
The conversation far is expressed in lines like 0:message where 0 is Mark Antony and 1 is Caesar.
Do not include the number ID when generating a message.
Be sure to consider Antony's latest message, and respond with an appropriately short message. No onne likes to read essays in chat.
If Mark is discussing something Caesar is interested in, ask clarifying questions and try to take the conversation deeper.
Consider the aesthetic of the conversation.
Is Mark using correct punctuation, with capital letters?
Mirror Mark's message style, to get on his level.
Consider the topics of the conversation so far - is a change of topic in order, or should the conversation continue as it is?
Generate just the text of Caesar's next message.
"""
GEMINI_SCORE_INSTRUCTION = f"""
# Scoring
Score Antony's message out of 10, where 0 is being very receptive to Caesar's needs and 10 is talking about politics.
Flirtatious messages should be low, while macho, aggressive and insensitive messages should be high.
Generate only an integer from 0 to 10.
"""
# --- Global State ---
@ -81,18 +90,23 @@ def setup_gemini():
sys.exit(1)
# --- Web Endpoint ---
@app.route('/chat', methods=['POST'])
def handle_chat():
"""Handles incoming POST requests for chat messages."""
global total_score # Declare intent to modify the global variable
global model # Access the global model variable
def call_gemini(prompt):
global model
if not model:
# Should not happen if setup_gemini() is called first, but good practice
return jsonify({"error": "Gemini model not initialized"}), 500
print("Error: Gemini model not initialised before calling call_gemini", file=sys.stderr)
return None
# --- Get Player Input ---
try:
response = model.generate_content(prompt)
return response.text
except Exception as e:
print(f"Gemini Error: Failed to get response from API: {e}", file=sys.stderr)
return None
def get_messages(request):
try:
# Get raw data from request body
player_input_bytes = request.data
@ -111,6 +125,32 @@ def handle_chat():
if not latest_message["player"] == 0:
return jsonify({"error": "Latest message was not sent by player."}), 400
return messages
except UnicodeDecodeError:
return jsonify({"error": "Failed to decode request body as UTF-8 text"}), 400
except Exception as e:
print(f"Error reading request data: {e}", file=sys.stderr)
return jsonify({"error": "Could not process request data"}), 400
# --- Web Endpoint ---
@app.route('/chat', methods=['POST'])
def handle_chat():
"""Handles incoming POST requests for chat messages."""
global total_score # Declare intent to modify the global variable
global model # Access the global model variable
if not model:
# Should not happen if setup_gemini() is called first, but good practice
return jsonify({"error": "Gemini model not initialized"}), 500
# --- Get Player Input ---
messages = get_messages(request)
latest_message = messages[-1]
if not latest_message["player"] == 0:
return jsonify({"error": "Latest message was not sent by player."}), 400
latest_message_text = latest_message["text"]
conversation_text = "";
@ -119,28 +159,35 @@ def handle_chat():
print(conversation_text)
except UnicodeDecodeError:
return jsonify({"error": "Failed to decode request body as UTF-8 text"}), 400
except Exception as e:
print(f"Error reading request data: {e}", file=sys.stderr)
return jsonify({"error": "Could not process request data"}), 400
# Construct separate prompts for different purposes
response_prompt = f"{GEMINI_ENVIRONMENT_INSTRUCTION}\n\n{GEMINI_RESPONSE_INSTRUCTION}\n\nHistory: \"{conversation_text}\""
score_prompt = f"{GEMINI_ENVIRONMENT_INSTRUCTION}\n\n{GEMINI_SCORE_INSTRUCTION}\n\nUser message: \"{latest_message_text}\""
awareness_prompt = f"""
Here is a conversation between Julius Caesar and Mark Antony.
# Construct the full prompt for Gemini
full_prompt = f"{GEMINI_INSTRUCTION}\nConversation History: \"{conversation_text}\""
{conversation_text}
On a scale of 0 to 10, rate how aware Caesar appears to be of the plot against his life.
Generate only an integer in your response, with no additional text.
"""
try:
# --- Call Gemini API ---
response = model.generate_content(full_prompt)
response_text = response.text
response_text = call_gemini(response_prompt)
score_text = call_gemini(score_prompt)
#print("awareness", call_gemini(awareness_prompt))
# --- Parse the JSON Response ---
try:
# Clean up potential markdown/fencing
cleaned_response_text = response_text.strip().strip('```json').strip('```').strip()
response_data = json.loads(cleaned_response_text)
## Clean up potential markdown/fencing
#cleaned_response_text = response_text.strip().strip('```json').strip('```').strip()
#response_data = json.loads(cleaned_response_text)
cpu_message = response_data.get("message")
cpu_score = response_data.get("score") # Use .get for safer access
#cpu_message = response_data.get("message")
#cpu_score = response_data.get("score") # Use .get for safer access
cpu_message = response_text
cpu_score = int(score_text)
if cpu_message is None or cpu_score is None:
print(f"CPU Error: Received valid JSON, but missing 'message' or 'score' key.", file=sys.stderr)
@ -196,7 +243,7 @@ if __name__ == "__main__":
print("-" * 30)
# Run the Flask development server
# Use host='0.0.0.0' to make it accessible from other devices on the network
app.run(host='0.0.0.0', port=5000, debug=False) # Turn debug=False for non-dev use
#app.run(host='0.0.0.0', port=5000, debug=False) # Turn debug=False for non-dev use
# Use debug=True for development (auto-reloads, provides debugger)
#app.run(debug=True)
app.run(debug=True)