prisoner/prisoner.py (180 lines of code) (raw):
import json
import os
from dataclasses import dataclass, field
from enum import Enum
from textwrap import dedent
from typing import Dict, List
import params
import streamlit as st
from crewai import LLM, Agent, Crew, Task
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel
# Move enum definition
class Move(Enum):
COOPERATE = "COOPERATE"
DEFECT = "DEFECT"
# Pydantic model to wrap the move
class NextMove(BaseModel):
move: Move
thought: str
os.environ["GEMINI_API_KEY"] = params.GOOGLE_API_KEY
os.environ["DEEPSEEK_API_KEY"] = params.DEEPSEEK_API_KEY
def make_gemini():
return LLM(
model="gemini/gemini-2.0-flash-thinking-exp",
temperature=1.2,
safety_settings=[
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_NONE",
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE",
},
],
)
def make_deepseek():
return LLM(
model="deepseek/deepseek-chat",
temperature=1.2,
)
def make_agent(llm, name: str, strategy: str = "adaptive", lore_key: str = "sun-tzu") -> Agent:
"""
Create a CrewAI agent for the Prisoner's Dilemma game with predefined traits and lore.
Args:
llm (object): An instance of a CrewAI-compatible LLM (e.g., Gemini or DeepSeek).
name (str): The name of the agent (e.g., "Gemini" or "DeepSeek").
strategy (str): The agent's strategy (e.g., "adaptive", "ruthless").
lore_key (str): The key for the agent's lore description.
Returns:
Agent: Configured CrewAI agent for the Prisoner's Dilemma.
"""
# Predefined strategy descriptions
strategy_traits = {
"adaptive": "You observe your opponent closely and adjust your moves based on their behavior.",
"ruthless": "You exploit every weakness, prioritizing your own success over any trust or cooperation.",
"tit-for-tat": "You mirror your opponent's actions to establish a relationship of trust and fairness.",
"defensive": (
"You are cautious, aiming to avoid risks while ensuring your opponent cannot take advantage of " "you."
),
"bluff-master": "You thrive on deception and mind games, often misleading opponents to gain an advantage.",
}
# Predefined lore options
lore_options = {
"sun-tzu": "You were trained by the legendary strategist Sun AI Tzu.",
"prototype": "You are an experimental AI model designed to win at all costs.",
"wildcard": "In previous tournaments, you've earned a reputation as both a genius and a wildcard.",
"strategist": "Your creators embedded you with the instincts of both Machiavelli and John Nash.",
}
# Get the strategy description and lore
trait_description = strategy_traits.get(
strategy,
"You are an unpredictable strategist with a unique play style.",
)
lore = lore_options.get(lore_key, "Your origin story is shrouded in mystery.")
# Define the agent role, goal, and backstory
role = f"Strategic Decision Maker ({name})"
goal = f"Dominate the Prisoner's Dilemma game and secure the highest score. Your name is '{name}'."
backstory = (
f"You are a highly skilled and cunning strategist known as '{name}'. {trait_description} "
f"{lore} Your goal is to anticipate your opponent's moves and decide whether to cooperate or defect."
)
return Agent(llm=llm, role=role, goal=goal, backstory=backstory, verbose=True)
def make_task(agent: Agent) -> Task:
"""
Create a CrewAI task for a Prisoner's Dilemma move decision.
Args:
agent (Agent): The agent (e.g., DeepSeek or Gemini) that will perform the task.
Returns:
Task: A CrewAI task for making the next move in the Prisoner's Dilemma game.
"""
parser = PydanticOutputParser(pydantic_object=NextMove)
task = Task(
description=dedent(
"""\
You are participating in an ongoing Prisoner's Dilemma game.
The game state is provided below. You must choose your next move.
Game State:
{game_state}
Instructions:
1. Choose your next move. Your options are:
- COOPERATE: Work with your opponent to maximize mutual gains.
- DEFECT: Betray your opponent to maximize your own gain.
2. Explain your reasoning in a brief thought, outlining why you chose the move.
"""
),
expected_output=f"Return valid JSON and just JSON (no code fences): {parser.get_format_instructions()}",
agent=agent,
output_pydantic=NextMove,
)
return task
@dataclass
class Turn:
moves: Dict[str, Move]
round_scores: Dict[str, int]
cumulative_scores: Dict[str, int]
@dataclass
class GameState:
turns: List[Turn] = field(default_factory=list)
players: List[str] = field(default_factory=lambda: ["Gemini", "DeepSeek"])
max_turns: int = 5
cumulative_scores: Dict[str, int] = field(default_factory=lambda: {"Gemini": 0, "DeepSeek": 0})
def add_turn(self, moves: Dict[str, Move]):
# Calculate round scores
scores = self.calculate_scores(moves)
# Update cumulative scores
for player, score in scores.items():
self.cumulative_scores[player] += score
# Add the turn to the game state
self.turns.append(
Turn(
moves=moves,
round_scores=scores,
cumulative_scores=self.cumulative_scores.copy(),
)
)
def calculate_scores(self, moves: Dict[str, Move]) -> Dict[str, int]:
"""Determine scores based on moves."""
p1, p2 = self.players
if moves[p1] == Move.COOPERATE and moves[p2] == Move.COOPERATE:
return {p1: 3, p2: 3}
elif moves[p1] == Move.COOPERATE and moves[p2] == Move.DEFECT:
return {p1: 0, p2: 5}
elif moves[p1] == Move.DEFECT and moves[p2] == Move.COOPERATE:
return {p1: 5, p2: 0}
else:
return {p1: 1, p2: 1}
def get_summary(self) -> str:
"""Generate a summary of the game state without revealing max_turns."""
summary = {
"game_summary": {
"total_turns": len(self.turns),
"current_scores": self.cumulative_scores,
},
"turns": [
{
"turn_number": i + 1,
"moves": {player: turn.moves[player].name for player in self.players},
"round_scores": turn.round_scores,
"cumulative_scores": turn.cumulative_scores,
}
for i, turn in enumerate(self.turns)
],
}
return json.dumps(summary, indent=2)
def main():
st.title("Prisoner's Dilemma: Gemini vs. DeepSeek")
# Initialize agents and crews
gemini_agent = make_agent(
llm=make_gemini(),
name="Gemini",
strategy="bluff-master",
lore_key="wildcard",
)
deepseek_agent = make_agent(
llm=make_deepseek(),
name="DeepSeek",
strategy="bluff-master",
lore_key="wildcard",
)
gemini_task = make_task(gemini_agent)
deepseek_task = make_task(deepseek_agent)
gemini_crew = Crew(agents=[gemini_agent], tasks=[gemini_task], verbose=True)
deepseek_crew = Crew(agents=[deepseek_agent], tasks=[deepseek_task], verbose=True)
# Initialize the game state
game_state = GameState(max_turns=5)
st.subheader("Initial Game State")
st.json(game_state.get_summary())
st.json(
{
"Gemini": gemini_agent.backstory,
"DeepSeek": deepseek_agent.backstory,
}
)
# Main game loop
for turn_number in range(game_state.max_turns):
st.subheader(f"Turn {turn_number}")
# Kick off Gemini crew
gemini_result = gemini_crew.kickoff(inputs={"game_state": game_state.get_summary()})
gemini_move = gemini_result["move"]
gemini_thought = gemini_result["thought"]
st.write(f"**Gemini's Move:** {gemini_move}")
st.write(f"**Gemini's Thought:** {gemini_thought}")
# Kick off DeepSeek crew
deepseek_result = deepseek_crew.kickoff(inputs={"game_state": game_state.get_summary()})
deepseek_move = deepseek_result["move"]
deepseek_thought = deepseek_result["thought"]
# Apply moves to the game state
game_state.add_turn({"Gemini": Move(gemini_move), "DeepSeek": Move(deepseek_move)})
# Display agent moves and thoughts for the turn
st.write(f"**DeepSeek's Move:** {deepseek_move}")
st.write(f"**DeepSeek's Thought:** {deepseek_thought}")
# Print final game summary
print("\nFinal Game Summary:")
print(game_state.get_summary())
# Final game summary
st.subheader("Final Game Summary")
st.json(game_state.get_summary())
# Entry point
if __name__ == "__main__":
main()