I’ve seen a bunch of people asking about how to embed chat GPT into a qualtrics survey. I managed to construct a working version of a multi-turn interaction with chatGPT, and so I’m posting the code here. Hopefully it helps someone.
The basic idea is to dynamically alter properties in the webpage, including adding custom buttons and changing the question text. You can see a demo of the survey here. Because of the way that it directly alters various webpage properties, I’m quite sure that it will break easily. For example, I don’t know what happens if you have multiple questions on the same page. There be dragons there.
The results of the conversation get stored in an embedded field.
I just created a single question with a text area answer, and then dropped this code into the question’s “addOnLoad” method.
1// this question will drop a JSON version of the conversation into this embedded data location2const EMBEDDED_DATA_DEST = "convo_history";34// adjust as needed5const OPENAI_API_KEY = "Bearer XXX-PUT-YOUR-API-KEY-HERE-XXX";6const OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";7const OPENAI_MODEL = 'gpt-4';89// after this many turns, the conversation will be disabled and the respondent will be able to advance to the next question10const MAX_TURNS = 6;1112//13// ----------------------------------------------------------------14//1516// these are all used to construct the initial set of messages1718const GPT4_SYS_PROMPT = `You are an expert in helping individuals cultivate gratitude in their lives. You are acting as a therapist. Your goal is help the user feel more grateful in life. You have been trained in the latest academic literature about gratitude. You know that when asked what they are grateful for, most people will respond with a list of things. However, you also know that when people reflect on relationships the benefits are much greater. Therefore, you must guide the user to reflect on relationships that are important, that have shaped them, and that have shaped how they treat others.1920You have three specific goals:2122First, you must get them to identify a benevolent relationship that has had meaningful impact on their live. Ideally, this relationship would involve some sort of sacrifice on the part of the giver.2324Second, you must get them to reflect their feelings about that relationship and its impact.2526Third, you must get them to express how this relationship will change their behavior. In other words, how will this relationship drive some sort of outward expression? How will they "pay it forward"?`;27const GPT4_INITIAL_TURN = "Hello! Thank you for meeting with me today. Could we start by having you tell me a bit about yourself?"28const GPT4_SECOND_TURN = "Perfect. Now, can you please list three things that you are grateful for?"2930// grabbing some data from another question31const user_demographics = "${e://Field/description}";3233//34// ----------------------------------------------------------------35//3637// the initial converation3839var messages = [{role: 'system', content: GPT4_SYS_PROMPT},40 {role: 'assistant', content: GPT4_INITIAL_TURN},41 {role: 'user', content: user_demographics},42 {role: 'assistant', content: GPT4_SECOND_TURN}43 ];44Qualtrics.SurveyEngine.setEmbeddedData( EMBEDDED_DATA_DEST , JSON.stringify( messages ) );4546//47// ----------------------------------------------------------------48// ----------------------------------------------------------------49// ----------------------------------------------------------------50//5152// store this reference for future use53const question_this = this;5455var initial_messages_length = messages.length; // check this later5657this.disableNextButton();5859// I had no luck hiding the "done" button, so I'm just deleting it60jQuery(".advanceButtonContainer").empty();6162// create my own button63jQuery("<div style='display:inline-block;padding:20 20 20 20;'><input type='button' value='Submit response' class='AdvanceButton Button' id='submitButton' disabled /></div>").appendTo( jQuery(".QuestionOuter") );64jQuery("#submitButton").prop("disabled",false);6566jQuery("#submitButton").click( function() {6768 /*69Things that should happen when they click the button:70711) Check that their input was not empty722) Disable the submit button733) Construct the new set of messages744) Update the global conversation structure755) Store a json version of the GCS766) Ping the GPT API777) Once we have a response, clear the question text and replace it by the GPT respone788) clear the input text area799) re-enable the submit button8010) check to see if it's time for the convo to end / enable the "next" button81*/8283 var current_user_response = jQuery(".InputText").val();8485 if ( ( current_user_response == undefined) || ( current_user_response.length == 0 ) ) {86 alert("Please enter a response");87 return;88 }8990 jQuery("#submitButton").prop("disabled",true);9192 messages.push( {93 role: 'user',94 content: current_user_response,95 });96 Qualtrics.SurveyEngine.setEmbeddedData( EMBEDDED_DATA_DEST , JSON.stringify( messages ) );9798 const data = {99 model: OPENAI_MODEL,100 messages: messages,101 max_tokens: 1000,102 temperature: 1.0,103 };104105 console.log( "initiating gpt query..." );106 console.log( messages );107108 fetch( OPENAI_ENDPOINT, {109 method: 'POST',110 headers: {111 'Content-Type': 'application/json',112 'Authorization': OPENAI_API_KEY113 },114 body: JSON.stringify(data)115 })116 .then(response => {117 if (!response.ok) {118 throw new Error('Network response was not ok');119 }120 return response.json();121 })122 .then(data => {123124 //125 // GOT A RESPONSE!126 //127128 console.log('API Response:', data);129130 message = data.choices[0].message.content;131 console.log("Message: ", message);132133 $$('.QuestionText')[0].innerText = message;134135 messages.push( {136 role: 'assistant',137 content: message138 });139 Qualtrics.SurveyEngine.setEmbeddedData( EMBEDDED_DATA_DEST , JSON.stringify( messages ) );140141 jQuery(".InputText").val( '' ); // reset answer field for next question142 jQuery("#submitButton").prop("disabled",false);143144 // CHECK TO SEE IF THE CONVO HAS ENDED145 if (( messages.length - initial_messages_length ) / 2 >= MAX_TURNS) {146147 question_this.enableNextButton();148 jQuery("#submitButton").prop("disabled",true);149 jQuery(".InputText").val( '' );150 $$('.QuestionText')[0].innerText = "Thank you for your answers. Please click the 'Next' button below";151152 }153154 })155 .catch(error => {156 console.error('There was a problem with the fetch operation:', error);157158 Qualtrics.SurveyEngine.setEmbeddedData("API Error" , "true");159160 question_this.enableNextButton();161 });162163});164
