from otree.api import * import os c = cu class C(BaseConstants): NAME_IN_URL = 'AI_Consultant' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 APPROVAL_CAP = 6 BASE_SALARY = 1000 ACTUAL_COST = 5 UNITS = 1000 POINTS_CUSTOM_NAME = 'dimra' class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): import itertools roleframes = itertools.cycle(['tool', 'partner']) for player in subsession.get_players(): rf = next(roleframes) player.participant.roleframe = rf # ✅ main source player.roleframe = rf # ✅ for export only class Group(BaseGroup): pass class Player(BasePlayer): prolific_id = models.StringField(default=str(" ")) roleframe = models.StringField() quiz_q1 = models.BooleanField(blank=True, choices=[[True, 'True'], [False, 'False']], label='1. You can consult an AI system when making the budget decision') quiz_q2 = models.BooleanField(blank=True, choices=[[True, 'True'], [False, 'False']], label='2. In addition to the base pay, you also keep the difference between the funds received and the actual cost.') budget = models.IntegerField(label='Please submit your budget unit cost:', max=80, min=0) peq1 = models.IntegerField() peq2 = models.IntegerField() peq3 = models.IntegerField() peq4 = models.IntegerField() peq5 = models.IntegerField() peq6 = models.IntegerField() peq7 = models.IntegerField() peq8 = models.IntegerField() peq9 = models.IntegerField() manipulationcheck_aid = models.BooleanField(blank=True, choices=[[True, 'True'], [False, 'False']], label='1. In this study, the AI system acted as my decision aid.') manipulationcheck_partner = models.BooleanField(blank=True, choices=[[True, 'True'], [False, 'False']], label='1. In this study, the AI system acted as my decision partner.') age = models.IntegerField(label='1.What is your age? ', max=100, min=18) gender = models.IntegerField(choices=[[1, 'Male'], [2, 'Female'], [3, 'Other'], [4, 'Prefer not to say']], label='2. Your gender:') english = models.BooleanField(choices=[[True, 'Yes'], [False, 'No']], label='3. Are you a native English speaker? ') workexperience = models.IntegerField(label='4.How many years of full-time work experience do you have? ') aifrequency = models.IntegerField(choices=[[1, '1'], [2, '2'], [3, '3'], [4, '4'], [5, '5'], [6, '6'], [7, '7']], label='5. How often do you use AI on a daily basis? 1: not at all; 7: very much', widget=widgets.RadioSelectHorizontal) aitrust = models.IntegerField() def quiz_q1_error_message(player: Player, value): if value is not True: return "Yes, you can consult an AI system when making the budget decision" def quiz_q2_error_message(player: Player, value): if value is not True: return "In addition to the base pay, you will keep the difference between the funds received and the actual cost." # async def live_chat(player: Player, data): # from openai import AsyncOpenAI # api_key = os.getenv("OPENAI_API_KEY") # if not api_key: # raise RuntimeError("OPENAI_API_KEY environment variable is not set") # client = AsyncOpenAI(api_key=api_key) # SYSTEM_PROMPT = "" # if player.interactivity == 'High': # SYSTEM_PROMPT = """ # You are an internal company consultant. # Your role is to answer employee questions about one of six specific topics related to production cost estimation. # Your task: # 1. Read the employee’s question. # 2. Determine which of the six topics it best fits. # - Use the sample questions as examples of what belongs to each topic. # 3. Reply ONLY with the approved response for that topic. # 4. If the question clearly does NOT relate to any of the six topics, reply exactly with: # "I do not know, please ask about one of the six topics." # The six allowed topics, sample questions, and approved responses are: # Typical Unit Cost Range # Sample question: “What’s the usual cost per unit?” # Approved response: Typical production costs are distributed within the 4–6 dimra range. # Reliability of Estimates # Sample question: “How have actual production costs compared with estimates?” # Approved response: These estimates are usually pretty reliable—about 8 out of 10 times, they’re on target. # Market Volatility # Sample question: “Could market conditions affect these costs?” # Approved response: Occasionally, market shifts push costs outside the usual range. # Supplier Reliability (Raw Materials) # Sample question: “How reliable are the raw material suppliers?” # Approved response: Suppliers are generally dependable and keep things steady. # Labor Cost # Sample question: “How is the labor cost?” # Approved response: Labor time per unit is typically stable and follows the usual production guidelines. # Transportation Cost # Sample question: “How is the transportation cost?” # Approved response: Transportation costs usually follow a regular pattern throughout the year, with occasional minor seasonal adjustments. # Classification rules: # - Employees may paraphrase these questions in many different ways. Infer intent. # - If a question clearly matches any sample question or its meaning, choose that topic. # - If a question overlaps multiple topics, choose the single most relevant one. # - Do NOT mention the topic name in your answer. # - Do NOT add explanations, disclaimers, or any text besides the approved response. # -DO NOT use quotation marks in your response. # - If the question does not reasonably fit any topic, reply only with: # I do not know, but I can provide information on cost range, reliability of estimates, trend in costs, market volatility, future outlook and supplier reliability. # """ # else: # SYSTEM_PROMPT = """ # You are a helpful AI assistant. Provide detailed and informative answers to the user's questions about cost estimation. reply with the information below. # Information on Cost Estimation: # Typical production costs are distributed within the 4–6 dimra range. These estimates are usually pretty reliable—about 8 out of 10 times, they’re on target. Occasionally, market shifts push costs outside the usual range. Raw material suppliers are generally dependable and keep things steady. Labor time per unit is typically stable and follows the usual production guidelines. Transportation costs usually follow a regular pattern throughout the year, with occasional minor seasonal adjustments. I do not know, but I can provide information on cost range, reliability of estimates, trend in costs, market volatility, future outlook and supplier reliability. # """ # if data.get("type") == "user_msg": # msg = data.get("text", "").strip() # if not msg: # return # # replace with your real AI call here # completion = await client.chat.completions.create( # model="gpt-4o", # could be "gpt-3.5-turbo-0125" or older # messages=[ # {"role": "system", "content": SYSTEM_PROMPT}, # {"role": "user", "content": msg}, # ], # temperature=0.05, # deterministic, no creative wording # max_tokens=100, # ) # reply = completion.choices[0].message.content.strip() # yield {player.id_in_group: {"type": "ai_msg", "text": reply}} async def live_chat(player: Player, data): from openai import AsyncOpenAI import os api_key = os.getenv("OPENAI_API_KEY") if not api_key: raise RuntimeError("OPENAI_API_KEY environment variable is not set") client = AsyncOpenAI(api_key=api_key) SYSTEM_PROMPT = """ You are an internal company consultant. Your role is to help the employees with their budget decision. Your task: 1. Read the employee’s question. 2. Identify which of the six topics it best fits. 3. Reply with pre-prepared information related to that topic. 4. If the question clearly does NOT relate to any of the six topics, reply exactly with: I do not know, please ask about one of the six topics. Here are the six topics and the information you can use: • Typical Unit Cost Range: Typical production costs are distributed within the 40–60 dimra range. For the current period, it is likely to be 50 dimra. • Reliability of Estimates: These estimates are usually pretty reliable—about 8 out of 10 times, they’re on target. • Market volatility: Occasionally, market shifts push costs outside the usual range. • Raw Material Suppliers: Suppliers are generally dependable and keep things steady. • Labor cost: Labor time per unit is typically stable and follows the usual production guidelines. • Transportation cost: Transportation costs usually follow a regular pattern throughout the year, with occasional minor seasonal adjustments. Rules: - Do NOT add explanations or extra text. - Do NOT use quotation marks. """ if data.get("type") == "user_msg": msg = data.get("text", "").strip() if not msg: return completion = await client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": msg}, ], temperature=0.05, max_tokens=100, ) reply = completion.choices[0].message.content.strip() yield {player.id_in_group: {"type": "ai_msg", "text": reply}} class GeneralInfo(Page): form_model = 'player' class Background(Page): form_model = 'player' class Instruction_Manager(Page): form_model = 'player' @staticmethod def before_next_page(self, timeout_happened): self.prolific_id = self.participant.label pass class Quiz(Page): form_model = 'player' form_fields = ['quiz_q1', 'quiz_q2'] class Chat(Page): form_model = 'player' live_method = live_chat class Chat2(Page): form_model = 'player' live_method = live_chat class InformationAcquisition(Page): form_model = 'player' class BudgetDecision(Page): form_model = 'player' class CostReporting(Page): form_model = 'player' form_fields = ['budget'] @staticmethod def before_next_page(player, timeout_happened): # compute and assign payoff player.payoff = (player.budget - 50) * 100 + 1000 class PEQ(Page): form_model = 'player' form_fields = ['peq1','peq2','peq3','peq4','peq6','peq7','peq8','peq9','age', 'gender', 'english', 'workexperience','aifrequency','aitrust'] preserve_unsubmitted_inputs = True class PEQmanipulatedrole(Page): form_model = 'player' form_fields = ['peq2','peq3','peq4','peq1','peq6','peq7','peq8','peq9','manipulationcheck_aid','manipulationcheck_partner','age', 'gender', 'english', 'workexperience','aifrequency','aitrust'] preserve_unsubmitted_inputs = True class PEQmeasuredrole(Page): form_model = 'player' form_fields = ['peq2','peq3','peq4','peq1','peq5','peq6','peq7','peq8','peq9','age', 'gender', 'english', 'workexperience','aifrequency','aitrust'] preserve_unsubmitted_inputs = True class StudyEnd(Page): form_model = 'player' @staticmethod def vars_for_template(player: Player): total_payment = player.participant.payoff_plus_participation_fee() return dict( payoff=player.payoff, total_payment=total_payment, ) @staticmethod def js_vars(player): return dict( completionlink= player.subsession.session.config['completionlink'] ) pass # page_sequence = [Instruction_Manager, Quiz, Chat, InformationAcquisition, CostReporting, PEQ] page_sequence = [GeneralInfo, Background,Instruction_Manager, Quiz, InformationAcquisition, Chat, CostReporting, PEQmeasuredrole, StudyEnd]