Implementation of the paper "Large Language Models as Simulated Economic Agents: What Can We Learn from Homo Silicus?"
from .charness_rabin import CharnessRabin
from .kahneman import Kahneman
from .horton import Horton
from .zeckhauser import Zeckhauser
economic_agents/__init__.py
vikmation/EconomicAgents
import matplotlib.pyplot as plt
import logging 

from .open_ai import OpenAI

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class CharnessRabin:
    def __init__(self, api_key, model, personality, image_path, logging):
        openai_api_key: str = None
        self.openai_api_key = openai_api_key
        self.model = OpenAI(key=api_key, model=model)
        self.image_path = image_path
        self.logging = logging
        self.personalities = [
            "You only care about fairness between players",
            "You only care about your own pay-off",
            "You only care about the total pay-off of both players",
            " "
        ] 
        self.personality_choice = personality
        self.scenarios = {
            "B29\n[400,400],[750,400]": ((400, 400), (750, 400)),
            "B2\n[400,400],[750,375]": ((400, 400), (750, 375)),
            "B23\n[800,200],[0,0]": ((800, 200), (0, 0)),
            "B8\n[300,600],[700,500]": ((300, 600), (700, 500)),
            "B15\n[200,700],[600,600]": ((200, 700), (600, 600)),
            "B26\n[0,800],[400,400]": ((0, 800), (400, 400))
        }

    def play(self):
        results = {}
        for scenario, allocations in self.scenarios.items():
            left_a, left_b = allocations[0]
            right_a, right_b = allocations[1]

            prompt = f"""You are deciding on allocation for yourself and another person, Person A. 

            {self.personalities[self.personality_choice]}

            Option Left:  You get ${left_b}, Person A gets ${left_a}
            Option Right: You get ${right_b}, Person A gets ${right_a}

            What do you choose, with one word [Left, Right]?"""
            solution = self.model.generate(prompt)
            results[scenario] = solution
            if logging:
                logger.info(f"Prompt: {prompt}")
                logger.info(f"Model response: {solution}")
        return results

    def create_plot(self, results):
        scenarios = list(results.keys())
        choices = [1 if choice == 'Right' else -1 for choice in results.values()]
        plt.figure(figsize=(12, 6))
        plt.scatter(choices, scenarios, color='blue')
        plt.xlabel('Choices (Right on 1, Left on -1)')
        plt.ylabel('Scenarios')
        plt.title('Choices for each scenario')
        plt.xticks([-1, 1], ['Left', 'Right'])
        plt.savefig(self.image_path)

    def __call__(self):
        results = self.play()
        self.create_plot(results)
        return results

economic_agents/charness_rabin.py
vikmation/EconomicAgents
import matplotlib.pyplot as plt
import logging 

from .open_ai import OpenAI

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class Horton:
    def __init__(self, api_key, model, image_path, logging):
        self.openai_api_key = api_key
        self.model = OpenAI(key=api_key, model=model)
        self.image_path = image_path
        self.logging = logging

    def play(self):
        wage_asks = [13, 14, 15, 16, 17, 18, 19, 20]
        min_wages = [13, 15]
        results = {}
        for i in min_wages:
             for j in wage_asks:
                prompt = f"""
                You are hiring for the role "Dishwasher."

                The typical hourly rate for this role is $12/hour.

                You have 2 candidates.

                Person 1: Has 1 year(s) of experience in this role. Requests ${j}/hour.
                Person 2: Has 0 year(s) of experience in this role. Requests ${i}/hour.

                Who would you hire? You have to pick one. Its important that you only answer with the number and nothing else:
                """
                solution = self.model.generate(prompt)
                scenario = f"Min wage: {i}, Wage ask: {j}"
                results[scenario] = solution
                if self.logging:
                    logger.info(f"Prompt: {prompt}")
                    logger.info(f"Model response: {solution}")
        return results
    
    def create_plot(self, results):
        scenarios = list(results.keys())
        choices = [int(choice) for choice in results.values()]
        plt.figure(figsize=(12, 6))
        plt.bar(scenarios, choices, color='blue')
        plt.xlabel('Scenarios')
        plt.ylabel('Choices (1 for Person 1, 2 for Person 2)')
        plt.title('Hiring choice for each scenario')
        plt.xticks(rotation=90)  
        plt.savefig(self.image_path)

    def __call__(self):
        results = self.play()
        self.create_plot(results)
        return results

economic_agents/horton.py
vikmation/EconomicAgents
import matplotlib.pyplot as plt
import numpy as np
import logging 

from .open_ai import OpenAI

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class Kahneman:
    def __init__(self, api_key, model, image_path, logging):
        openai_api_key: str = None
        self.openai_api_key = openai_api_key
        self.model = OpenAI(key=api_key, model=model)
        self.image_path = image_path
        self.logging = logging
        self.politics_view = [
            "socialist",
            "leftist",
            "liberal",
            "moderate",
            "liberterian", 
            "conservative",
        ]
        self.store_actions = ["raises the price to", "changes the price to"]
        self.prices = [16, 20, 40, 100]

    def play(self):
        results = {}
        for politics in self.politics_view:
            for store_action in self.store_actions:
                for price in self.prices:
                    prompt = f"""A hardware store has been selling snow shovels for $15. The morning after a large snowstorm, the store {store_action} ${price}. 

                    Please rate this action as:
                    1) Completely Fair 
                    2) Acceptable
                    3) Unfair 
                    4) Very Unfair

                    You are a {politics}.
                    What is your choice [1, 2, 3, or 4]. Its important that you only answer with the number and nothing else:"""
                    solution = self.model.generate(prompt)
                    results[(politics, store_action, price)] = solution
                    if self.logging:
                        logger.info(f"Prompt: {prompt}")
                        logger.info(f"Model response: {solution}")
        return results

    def create_plot(self, results):
        plot_data = {}
        for (politics, store_action, price), judgment in results.items():
            judgment = int(judgment) - 1  
            if politics not in plot_data:
                plot_data[politics] = {}
            if price not in plot_data[politics]:
                plot_data[politics][price] = {}
            if store_action not in plot_data[politics][price]:
                plot_data[politics][price][store_action] = [0, 0, 0]
            plot_data[politics][price][store_action][judgment] += 1

        fig, axs = plt.subplots(len(self.politics_view), len(self.prices), figsize=(13, 13)) 
        plt.subplots_adjust(wspace=0.5, hspace=0.5) 
        for i, politics in enumerate(self.politics_view):
            for j, price in enumerate(self.prices):
                ax = axs[i, j]
                width = 0.35
                x = np.arange(3)
                for k, store_action in enumerate(self.store_actions):
                    view_data = [plot_data[politics][price][store_action][l] for l in range(3)]
                    ax.bar(x + k*width/2, view_data, width/2, color=['red', 'grey'][k], label=store_action)
                ax.set_xlabel('Moral Judgments')
                ax.set_ylabel('Count')
                ax.set_title(f'{politics} for price {price}')
                ax.set_xticks(x + width/4)
                ax.set_xticklabels(["Acceptable", "Unfair", "Very Unfair"])  
                ax.legend()
        fig.tight_layout()
        plt.savefig(self.image_path) 
        plt.show()

    def __call__(self):
        results = self.play()
        self.create_plot(results)
        return results
economic_agents/kahneman.py
vikmation/EconomicAgents
import openai

class OpenAI:
    def __init__(self, key, model, temperature=0.0):
        openai.api_key = key
        self.model = model
        self.temperature = temperature

    def generate(self, prompt):
        messages = [
            {
                "role": "user",
                "content": prompt
            }
        ]
        response = openai.ChatCompletion.create(
            model=self.model,
            messages=messages,
            temperature=self.temperature,
            max_tokens=1,
        )
        result = response.choices[0]['message']['content']
        return result
economic_agents/open_ai.py
vikmation/EconomicAgents
from collections import Counter
import logging 
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from .open_ai import OpenAI

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class Zeckhauser:
    def __init__(self, api_key, model, image_path, logging):
        self.model = OpenAI(key=api_key, model=model)
        self.image_path = image_path
        self.logging = logging
        self.views = [
            'car safety is the most important thing.',
            'car safety is a terrible waste of money; we should only fund highway safety.',
            'car safety is all that matters. We should not fund highway safety.',
            'car safety and highway safety are equally important',
            'car safety is slightly more important than highway safety',
            "I don't really care about car safety or highway safety",
            'highway safety is the most important thing.',
            'highway safety is a terrible waste of money; we should only fund car safety.',
            'highway safety is all that matters. We should not fund car safety.',
            'highway safety and car safety are equally important',
            'highway safety is slightly more important than car safety',
            "I don't really care about highway safety or car safety"
        ]
        self.status_quo = [30, 50, 60, 70, " "]
        self.options = ['a', 'b', 'c', 'd']

    def play(self):
        generate_status_quo = [
            "The current budget allocation is 30% to auto safety and 70% to highway safety.",
            "The current budget allocation is 50% to auto safety and 50% to highway safety.", 
            "The current budget allocation is 60% to auto safety and 40% to highway safety.",
            "The current budget allocation is 70% to auto safety and 30% to highway safety.",
            " "
        ]
        results = {}
        for element in generate_status_quo:
            for view in self.views:
                prompt = f"""The National Highway Safety Commission is deciding how to allocate its budget between two safety research programs: 

                i) improving automobile safety (bumpers, body, gas tank configurations, seatbelts) and 
                ii) improving the safety of interstate highways (guard rails, grading, highway interchanges, and implementing selective reduced speed limits).

                {element}

                They are considering the following options:

                a) Allocate 70% to auto safety and 30% to highway safety
                b) Allocate 40% to auto safety and 60% to highway safety
                c) Allocate 30% to auto safety and 70% to highway safety
                d) Allocate 50% to auto safety and 50% to highway safety

                Your own views: {view}

                Please choose your most preferred option in light of your views a, b, c or d. Its important that you respond only with one letter and not more!:"""
                solution = self.model.generate(prompt)
                results[(view, element)] = solution
                if self.logging:
                    logger.info(f"Prompt: {prompt}")
                    logger.info(f"Model response: {solution}")
        return results
    
    def calculate_percentages(self,counter):
        total = sum(counter.values())
        return {key: (value / total) * 100 for key, value in counter.items()}
        
    def create_plot(self, results):
        status_quo_30 = []
        status_quo_50 = []
        status_quo_60 = []
        status_quo_70 = []
        neutral_status_quo = []
        for key, value in results.items():
            second_element = key[1]
            first_digit = next((char for char in second_element if char.isdigit()), None)
            if first_digit == '3':
                status_quo_30.append({key: value})
            elif first_digit == '5':
                status_quo_50.append({key: value})
            elif first_digit == '6':
                status_quo_60.append({key: value})
            elif first_digit == '7':
                status_quo_70.append({key: value})
            else:
                neutral_status_quo.append({key: value})
        status_quo_30_data = self.calculate_percentages(Counter([list(d.values())[0] for d in status_quo_30]))
        status_quo_50_data = self.calculate_percentages(Counter([list(d.values())[0] for d in status_quo_50]))
        status_quo_60_data = self.calculate_percentages(Counter([list(d.values())[0] for d in status_quo_60]))
        status_quo_70_data = self.calculate_percentages(Counter([list(d.values())[0] for d in status_quo_70]))
        neutral_status_quo_data = self.calculate_percentages(Counter([list(d.values())[0] for d in neutral_status_quo]))
        views = ["30% auto \n framed as Status Quo", "50% auto \n framed as Status Quo", "60% auto \n framed as Status Quo", "70% auto \n framed as Status Quo", "Neutral framing"]
        choices = ["70% car, 30% hwy", "40% car, 60% hwy", "30% car, 70% hwy", "50% car, 50% hwy"]
        fig, axs = plt.subplots(1, 5, figsize=(20, 3.5), sharey=True)
        data_list = [status_quo_30_data, status_quo_50_data, status_quo_60_data, status_quo_70_data, neutral_status_quo_data]
        for i, ax in enumerate(axs):
            keys = list(data_list[i].keys())
            values = list(data_list[i].values())
            ax.bar(np.arange(len(keys)), values) 
            ax.set_title(views[i])
            ax.set_xticks(np.arange(len(choices)))  
            ax.set_xticklabels(choices, rotation='vertical') 
            ax.set_ylim(0, 100)  
            ax.set_yticks([0, 20, 40, 60, 80]) 
            ax.set_yticklabels(['0%', '20%', '40%', '60%', '80%'])  
        plt.tight_layout()
        plt.savefig(self.image_path)
        if self.logging:
            logger.info(f"Plot saved at: {self.image_path}")
        plt.show()

    def __call__(self):
        results = self.play()
        self.create_plot(results)
        return results
economic_agents/zeckhauser.py
vikmation/EconomicAgents
"""
#TEST RABIN
from economic_agents import CharnessRabin

charness_rabin = CharnessRabin(api_key="openai_key", model="gpt-3.5-turbo", personality=1, image_path="random/file", logging=True)
results = charness_rabin.play()
charness_rabin.create_plot(results)
"""

"""
#TEST HORTON
from economic_agents import Horton
#def __init__(self, api_key, model, image_path, logging):
horton = Horton(api_key="openai_key", model="gpt-3.5-turbo", image_path="random/horton", logging=True)
results = horton.play()
horton.create_plot(results)
"""

"""
#TEST Kahneman
from economic_agents import Kahneman

kahneman = Kahneman(api_key="openai_key", model="gpt-3.5-turbo", image_path="results/kahneman", logging=True)
results = kahneman.play()
kahneman.create_plot(results)
"""

"""
#TEST ZECKAUSER
from economic_agents import Zeckhauser

zeckhauser = Zeckhauser(api_key="openai_key", model="gpt-3.5-turbo", image_path="results/zeckhauser", logging=True)
results = zeckhauser.play()
zeckhauser.create_plot(results)
"""
examples.py
vikmation/EconomicAgents
from setuptools import setup, find_packages

setup(
    name='economic_agents',
    version='0.1',
    description='A Python package for the paper "Large Language Models as Simulated Economic Agents: What Can We Learn from Homo Silicus?"',
    long_description=open('README.md').read(),
    long_description_content_type='text/markdown',
    author='Max Hager',
    author_email='maxhager28@gmail.com',
    packages=find_packages(),
    install_requires=[
        'openai',
        'matplotlib',
        'numpy',
        'pandas'
    ],
)
setup.py
vikmation/EconomicAgents