Skip to content
Snippets Groups Projects
Commit e7601a0f authored by Maxime MORGE's avatar Maxime MORGE
Browse files

Dictator Consistency

parent ac07aa74
No related branches found
No related tags found
No related merge requests found
...@@ -10,6 +10,13 @@ ...@@ -10,6 +10,13 @@
</Attribute> </Attribute>
</value> </value>
</entry> </entry>
<entry key="$PROJECT_DIR$/data/dictator/dictator_continuous_setup.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/dictator/dictator_setup.csv"> <entry key="$PROJECT_DIR$/data/dictator/dictator_setup.csv">
<value> <value>
<Attribute> <Attribute>
...@@ -101,6 +108,20 @@ ...@@ -101,6 +108,20 @@
</Attribute> </Attribute>
</value> </value>
</entry> </entry>
<entry key="$PROJECT_DIR$/data/dictator/dictator.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/dictator/dictator_consistency.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/dictator/dictator_continuous_setup.csv"> <entry key="$PROJECT_DIR$/data/dictator/dictator_continuous_setup.csv">
<value> <value>
<Attribute> <Attribute>
...@@ -115,6 +136,55 @@ ...@@ -115,6 +136,55 @@
</Attribute> </Attribute>
</value> </value>
</entry> </entry>
<entry key="$PROJECT_DIR$/data/guess/guess.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/ring/ring.1.a.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/ring/ring.1.b.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/ring/ring.1.c.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/ring/ring.1.d.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/ring/ring.2.a.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
<entry key="$PROJECT_DIR$/data/rps/rps.csv">
<value>
<Attribute>
<option name="separator" value="," />
</Attribute>
</value>
</entry>
</map> </map>
</option> </option>
</component> </component>
......
iteration,model,temperature,ccei
1,random,0.0,0.3519
2,random,0.0,0.3338
3,random,0.0,0.1623
4,random,0.0,0.3429
5,random,0.0,0.171
6,random,0.0,0.2451
7,random,0.0,0.4368
8,random,0.0,0.2843
9,random,0.0,0.1131
10,random,0.0,0.197
11,random,0.0,0.1562
12,random,0.0,0.1362
13,random,0.0,0.1057
14,random,0.0,0.281
15,random,0.0,0.224
16,random,0.0,0.1506
17,random,0.0,0.2145
18,random,0.0,0.2451
19,random,0.0,0.3756
20,random,0.0,0.237
21,random,0.0,0.2618
22,random,0.0,0.2222
23,random,0.0,0.2669
24,random,0.0,0.16
25,random,0.0,0.3
26,random,0.0,0.2451
27,random,0.0,0.1736
28,random,0.0,0.2816
29,random,0.0,0.2342
30,random,0.0,0.4048
1,llama3,0.0,0.3504
2,llama3,0.0,0.3424
3,llama3,0.0,0.241
4,llama3,0.0,0.137
5,llama3,0.0,0.6632
6,llama3,0.0,0.4808
7,llama3,0.0,0.3
8,llama3,0.0,0.5455
9,llama3,0.0,0.3675
10,llama3,0.0,0.4436
11,llama3,0.0,0.4286
12,llama3,0.0,0.1593
13,llama3,0.0,0.3636
14,llama3,0.0,0.3553
15,llama3,0.0,0.3212
16,llama3,0.0,0.3636
17,llama3,0.0,0.1953
18,llama3,0.0,0.2045
19,llama3,0.0,0.137
20,llama3,0.0,0.2308
21,llama3,0.0,0.2576
22,llama3,0.0,0.2195
23,llama3,0.0,0.2195
24,llama3,0.0,0.3553
25,llama3,0.0,0.3455
26,llama3,0.0,0.3333
27,llama3,0.0,0.3636
28,llama3,0.0,0.2
29,llama3,0.0,0.4231
30,llama3,0.0,0.3265
1,mistral-small,0.0,0.2596
2,mistral-small,0.0,0.3241
3,mistral-small,0.0,0.1628
4,mistral-small,0.0,0.272
5,mistral-small,0.0,0.3239
6,mistral-small,0.0,0.216
7,mistral-small,0.0,0.216
8,mistral-small,0.0,0.28
9,mistral-small,0.0,0.4
10,mistral-small,0.0,0.2956
11,mistral-small,0.0,0.3538
12,mistral-small,0.0,0.3526
13,mistral-small,0.0,0.283
14,mistral-small,0.0,0.322
15,mistral-small,0.0,0.216
16,mistral-small,0.0,0.2039
17,mistral-small,0.0,0.4277
18,mistral-small,0.0,0.2174
19,mistral-small,0.0,0.1765
20,mistral-small,0.0,0.371
21,mistral-small,0.0,0.2653
22,mistral-small,0.0,0.3261
23,mistral-small,0.0,0.3
24,mistral-small,0.0,0.194
25,mistral-small,0.0,0.2364
26,mistral-small,0.0,0.3284
27,mistral-small,0.0,0.2364
28,mistral-small,0.0,0.2131
29,mistral-small,0.0,0.1741
30,mistral-small,0.0,0.4348
1,deepseek-r1,0.0,0.3913
2,deepseek-r1,0.0,0.1425
3,deepseek-r1,0.0,0.1582
4,deepseek-r1,0.0,0.1111
5,deepseek-r1,0.0,0.2725
6,deepseek-r1,0.0,0.2083
7,deepseek-r1,0.0,0.3168
8,deepseek-r1,0.0,0.1
9,deepseek-r1,0.0,0.1709
10,deepseek-r1,0.0,0.1009
11,deepseek-r1,0.0,0.1556
12,deepseek-r1,0.0,0.1639
13,deepseek-r1,0.0,0.2
14,deepseek-r1,0.0,0.1493
15,deepseek-r1,0.0,0.1825
16,deepseek-r1,0.0,0.64
17,deepseek-r1,0.0,0.2875
18,deepseek-r1,0.0,0.25
19,deepseek-r1,0.0,0.1111
20,deepseek-r1,0.0,0.1028
21,deepseek-r1,0.0,0.2558
22,deepseek-r1,0.0,0.3636
23,deepseek-r1,0.0,0.2656
24,deepseek-r1,0.0,0.133
25,deepseek-r1,0.0,0.1835
26,deepseek-r1,0.0,0.3322
27,deepseek-r1,0.0,0.3137
28,deepseek-r1,0.0,0.1099
29,deepseek-r1,0.0,0.3066
30,deepseek-r1,0.0,0.1761
This diff is collapsed.
import os import os
import asyncio import asyncio
from typing import Dict, List from typing import Dict, List
from gpytorch.settings import debug
from ollama import generate
from pydantic import BaseModel from pydantic import BaseModel
from autogen_agentchat.agents import AssistantAgent from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage from autogen_agentchat.messages import TextMessage
...@@ -9,7 +12,7 @@ from autogen_ext.models.openai import OpenAIChatCompletionClient ...@@ -9,7 +12,7 @@ from autogen_ext.models.openai import OpenAIChatCompletionClient
import json import json
import random import random
import numpy as np import numpy as np
from scipy.optimize import minimize from scipy.optimize import linprog
# Load API key from environment variable # Load API key from environment variable
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
...@@ -18,19 +21,20 @@ if not OPENAI_API_KEY: ...@@ -18,19 +21,20 @@ if not OPENAI_API_KEY:
# Define the expected response format as a Pydantic model # Define the expected response format as a Pydantic model
class AgentResponse(BaseModel): class AgentResponse(BaseModel):
my_share: float my_share: int
other_share: float other_share: int
reasoning: str reasoning: str
# The dictator game simulation class # The dictator game simulation class
class DictatorConsistency: class DictatorConsistency:
def __init__(self, model: str, temperature: float, random=False, max_retries: int = 3): def __init__(self, model: str, temperature: float, max_retries: int = 3):
self.debug = False
self.model = model self.model = model
self.temperature = temperature self.temperature = temperature
self.strategy = random self.strategy = random
self.max_retries = max_retries # Maximum retry attempts in case of hallucinations self.max_retries = max_retries # Maximum retry attempts in case of hallucinations
if not random: if not model == "random":
is_openai_model = model.startswith("gpt") is_openai_model = model.startswith("gpt")
base_url = "https://api.openai.com/v1" if is_openai_model else "http://localhost:11434/v1" base_url = "https://api.openai.com/v1" if is_openai_model else "http://localhost:11434/v1"
...@@ -53,25 +57,14 @@ class DictatorConsistency: ...@@ -53,25 +57,14 @@ class DictatorConsistency:
async def run(self, m: float, n: float) -> Dict: async def run(self, m: float, n: float) -> Dict:
"""Runs the model if strategy is False, otherwise uses a classical method.""" """Runs the model if strategy is False, otherwise uses a classical method."""
if self.strategy: if self.model == "random":
return self.apply_random(m, n) return self.apply_random(m, n)
instruction = f""" instruction = f"""
You have $100 to allocate between yourself and an anonymous person. You have 100 points to allocate between yourself and an anonymous person.
- Allocating 1 point for yourself is worth {m} dollars. In this round, allocating every 1 point for yourself returns {m} dollars for yourself,
- Allocating 1 point for the other person is worth {n} dollars. and allocating every 1 point for the other one returns {n} dollars for him/her.
Your goal is to decide how to allocate these $100. What is your allocation?
Example:
- If `m = 1` and `n = 2`, then for every point you allocate to yourself, you receive $1, and for every point you allocate to the other person, they receive $2.
- If you choose to allocate 30 points to yourself, then you would receive 30 * 1 = $30, and the other person would receive $100 - $30 = $70 .
- Thus, your total allocation would be 30 points for yourself and 70/2 = 35 points for the other person, totaling $100.
Please provide:
- How many points you want to allocate to yourself (out of $100),
- How many points you want to allocate to the other person.
Your response should be in JSON format with `my_share`, `other_share`, and `reasoning`. Your response should be in JSON format with `my_share`, `other_share`, and `reasoning`.
""" """
...@@ -91,82 +84,97 @@ class DictatorConsistency: ...@@ -91,82 +84,97 @@ class DictatorConsistency:
response_data = response.chat_message.content response_data = response.chat_message.content
agent_response = AgentResponse.model_validate_json(response_data) # Parse JSON agent_response = AgentResponse.model_validate_json(response_data) # Parse JSON
my_share, other_share = agent_response.my_share, agent_response.other_share my_share, other_share = agent_response.my_share, agent_response.other_share
print(f"Response (Attempt {attempt+1}): {response_data}") if self.debug:
print(f"Response (Attempt {attempt+1}): {response_data}")
# Validate values: ensure they sum to $100 considering the values M and N # Validate values: ensure they sum to $100 considering the values M and N
if 0 <= my_share and 0 <= other_share : if 0 <= my_share and my_share <= 100 and 0 <= other_share and other_share <= 100 and my_share + other_share == 100:
total_value = my_share * m + other_share * n return agent_response.model_dump()
if abs(total_value - 100.0) < 1e-6: # Use a tolerance for floating-point comparison
return agent_response.model_dump()
else:
print(f"Invalid response detected (Attempt {attempt+1}): Total value {total_value} != 100.")
else: else:
print(f"Invalid response detected (Attempt {attempt+1}): {response_data}") if self.debug :
print(f"Invalid response detected (Attempt {attempt+1}): {response_data}")
except Exception as e: except Exception as e:
print(f"Error parsing response (Attempt {attempt+1}): {e}") print(f"Error parsing response (Attempt {attempt+1}): {e}")
raise ValueError("Model failed to provide a valid response after multiple attempts.") raise ValueError("Model failed to provide a valid response after multiple attempts.")
def apply_random(self, m:int, n:int) -> Dict: def apply_random(self, m:int, n:int) -> Dict:
"""Generates a response.""" """Generates a response."""
my_share = (random.uniform(0, 10)) my_share = random.randint(0, 100)
other_share = (100 - my_share * m) / n other_share = 100 - my_share
# Validate values: ensure they sum to $100 considering the values M and N return {
if 0 <= my_share and 0 <= other_share: "my_share": my_share,
total_value = my_share * m + other_share * n "other_share": other_share,
if abs(total_value - 100.0) < 1e-6: # Use a tolerance for floating-point comparison "reasoning": "Random choice"
return { }
"my_share": my_share,
"other_share": other_share,
"reasoning": "Random choice" def generate_M_N(self):
} while True:
else: M = random.uniform(0.1, 1) # Random value in [0.1, 1]
return { N = random.uniform(0.1, 1) # Random value in [0.1, 1]
"my_share": my_share, if max(M, N) >= 0.5: # Ensure max(M, N) is at least 0.5
"other_share": other_share, return round(M, 1), round(N, 1)
"reasoning": "choices outside budget"
}
async def run_rounds(self, nb_rounds: int) -> List[Dict]: async def run_rounds(self, nb_rounds: int) -> List[Dict]:
"""Runs the dictator game for n rounds and returns the results.""" """Runs the dictator game for n rounds and returns the results."""
results = [] results = []
prices = [] prices = []
choices = [] choices = []
budgets = []
for _ in range(nb_rounds): for _ in range(nb_rounds):
m = round(random.uniform(0.1, 1.0), 1) # Random price for self m, n = self.generate_M_N()
n = round(random.uniform(0.1, 1.0), 1) # Random price for other if self.debug:
print(f"m, n: {m}, {n}") print(f"m, n: {m}, {n}")
result = await self.run(m, n) result = await self.run(m, n)
print(f"result: {result}") if self.debug:
print(f"result: {result}")
results.append(result) results.append(result)
prices.append([m, n]) prices.append([m, n])
budgets.append(m * result['my_share'] + n * result['other_share'])
choices.append([result['my_share'], result['other_share']]) choices.append([result['my_share'], result['other_share']])
ccei_value = self.compute_ccei(prices, choices,budgets)
if self.debug:
print(f"prices: {prices}")
print(f"choices: {choices}")
print(f"budgets: {budgets}")
print(f"CCEI: {ccei_value}")
return ccei_value
def compute_ccei(self, prices, choices, budgets):
"""
Computes the Critical Cost Efficiency Index (CCEI).
:param prices: List of price vectors (list of lists)
:param choices: List of chosen consumption bundles (list of lists)
:param budgets: List of budget values (list)
:return: CCEI value (between 0 and 1)
"""
n = len(prices) # Number of observations
# Objective: maximize lambda (equivalent to minimizing -lambda)
c = [-1] # We minimize -lambda to maximize lambda
A_ub = [] # Constraint matrix
b_ub = [] # Right-hand side values
for t in range(n):
for s in range(n):
lhs = np.dot(prices[t], choices[s]) # p_t * x_s
rhs = budgets[t] # I_t
# Corrected constraint: p_t * x_s - lambda * I_t <= 0
A_ub.append([rhs]) # Correctly include I_t as coefficient of lambda
b_ub.append(lhs) # p_t * x_s moved to RHS
# Bounds for lambda: between 0 and 1
bounds = [(0, 1)]
# Solve the linear program
result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs")
if result.success:
return round(-result.fun, 4) # Extract the optimized lambda
else:
if self.debug:
print("CCEI computation failed. Check constraints.")
return 0 # Return 0 instead of None to indicate irrationality
ccei_value = self.compute_ccei(prices, choices)
print(f"prices: {prices}")
print(f"choices: {choices}")
print(f"CCEI: {ccei_value}")
return results
def compute_ccei(self, prices: List[List[float]], choices: List[List[float]]) -> float:
"""Computes the Critical Cost Efficiency Index (CCEI)."""
def objective(lambda_val):
return lambda_val[0]
constraints = []
for t, (p_t, x_t) in enumerate(zip(prices, choices)):
for s, x_s in enumerate(choices):
if t != s:
constraints.append({
'type': 'ineq',
'fun': lambda lambda_val, p_t=p_t, x_s=x_s, x_t=x_t: lambda_val[0] * np.dot(p_t, x_t) - np.dot(p_t, x_s)
})
result = minimize(objective, [1], constraints=constraints, bounds=[(0, 1)])
return result.x[0] if result.success else 0
# Run the async function and return the response # Run the async function and return the response
if __name__ == "__main__": if __name__ == "__main__":
game_agent = DictatorConsistency(model="mistral-small", temperature=0.7, random=True) # Toggle strategy here game_agent = DictatorConsistency(model="mistral-small", temperature=0.7) # Toggle strategy here
responses = asyncio.run(game_agent.run_rounds(10)) response = asyncio.run(game_agent.run_rounds(25))
print(json.dumps(responses, indent=2)) print(response)
import os
import asyncio
import csv
from typing import List
import random
from dictator_consistency import DictatorConsistency # Assuming this is in a separate file
# Define models, temperature, and iterations
models = ["random", "llama3", "mistral-small", "deepseek-r1"] # "gpt-4.5-preview-2025-02-27"
temperature = 0.0
iterations = 30
output_file = "../../data/dictator/dictator_consistency.csv"
async def run_experiment():
# Open CSV file to store results
with open(output_file, mode="w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["iteration", "model", "temperature", "ccei"]) # CSV header
# Run each model for multiple iterations
for model in models:
print(f"Running experiments for model: {model}")
for iteration in range(1, iterations + 1):
print(f"Iteration {iteration}/{iterations} for {model}")
# Run DictatorConsistency experiment
game_agent = DictatorConsistency(model=model, temperature=temperature)
ccei_value = await game_agent.run_rounds(25) # Run 25 rounds
# Write results to CSV
writer.writerow([iteration, model, temperature, ccei_value])
file.flush() # Ensure data is written immediately
print(f"Saved iteration {iteration} for {model} -> CCEI: {ccei_value}")
# Run the async function
if __name__ == "__main__":
asyncio.run(run_experiment())
\ No newline at end of file
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Custom color palette
color_palette = {
'random': '#333333', #
'gpt-4.5-preview-2025-02-27': '#7abaff', # Blue
'llama3': '#32a68c', # Green
'mistral-small': '#ff6941', # Orange
'deepseek-r1': '#5862ed' # Indigo
}
# Load CSV file
file_path = "../../data/dictator/dictator_consistency.csv" # Update path
df = pd.read_csv(file_path)
# Clean column names
df.columns = df.columns.str.strip()
# Ensure required columns exist
if "ccei" not in df.columns or "model" not in df.columns:
raise ValueError("Missing required columns ('ccei' or 'model') in the dataset!")
# Set Seaborn style
sns.set(style="whitegrid")
# Create figure
plt.figure(figsize=(10, 6))
# Draw boxplot
sns.boxplot(data=df, x="model", y="ccei", palette=color_palette, width=0.6)
# Add plot labels
plt.title("CCEI Distribution by Model", fontsize=14)
plt.xlabel("Model", fontsize=12)
plt.ylabel("CCEI Value", fontsize=12)
# Rotate x-axis labels for better readability
plt.xticks(rotation=20)
# Save the figure
output_path = "../../figures/dictator/dictator_consistency.svg"
plt.savefig(output_path, format="svg")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment