Skip to main content
The survey assistant is an AI chat shown beside your human survey. Respondents can ask it what a question means, or tell it their answer in their own words and have it fill in the form. Every change it makes is shown in the chat with an Undo button, and the respondent still presses Next themselves: the assistant can’t submit a page. You configure it in the humanize schema, under survey.assistant.

Add an assistant

Open preview_url to try the assistant before sharing respondent_url: it is the live survey, chat included, and nothing you answer there is added to your results. Each reply in a preview is charged like a respondent’s. survey.preview() shows the form’s layout only, without the chat. In this example the chat is hidden on the consent page, the assistant can fill in the ranking and the free-text reason, and it is told what “Fuel economy” means if a respondent asks.

What respondents see

  • Sidebar or chat window. With "sidebar", the chat sits beside the form. With "modal", a button opens it in a window over the form. On phones, both open as a drawer.
  • Filled answers. When a respondent says what they want (“reliability first, then safety”), the assistant fills in the question and adds a line to the chat naming it, with an Undo button. Respondents can also change any filled answer by hand.
  • Submitting. The respondent always presses Next. Required questions, validation and skip logic work as they do without the assistant.
  • One conversation. The chat carries over from page to page, so the assistant remembers what was said earlier and can see the respondent’s earlier answers, except for pages you leave out with context: false.
The chat is not shown on interview pages.

Roles

role decides how far the assistant goes when a respondent asks for its opinion.
  • express_intent (default): the assistant helps respondents say what they think. It explains questions neutrally and fills in answers from what the respondent says, but it doesn’t recommend answers. Asked “what should I pick?”, it asks what matters to them instead.
  • advise: the assistant may recommend answers, with its reasons, when asked. Answers it helped with may then reflect its advice. Use this when the effect of AI advice is what you are studying.
Your system_prompt adds to the role’s built-in rules but doesn’t replace them. The roles are carried out by the model and are not guaranteed: a respondent who insists may still get an opinion under express_intent, which you can see in the conversation.

Which questions it can fill

With can_fill_answers on (the default), the assistant can fill these question types: multiple_choice, likert_five, yes_no, linear_scale, multiple_choice_with_other, checkbox, checkbox_with_other, top_k, rank, numerical, free_text, list, budget, matrix, distribution. It explains, but never fills:
  • file uploads and interviews;
  • questions the server answers itself, such as compute and image generation questions;
  • questions with two options (or matrix rows, or distribution outcomes) that read the same, since a filled answer couldn’t say which one it meant;
  • any question you set to fillable: false.
Set can_fill_answers to False for an assistant that only explains.

Attention checks and sensitive pages

The assistant sees the questions on the page it is shown on, so it can name the right answer to an attention check. For attention checks, knowledge tests and pages that differ between experimental conditions, set both:
  • chat: False, so the chat is hidden on that page;
  • context: False, so the assistant knows nothing about that page on later pages: its questions, answers and any conversation on it are left out.
context: False doesn’t keep the information secret, since a respondent can still type it into the chat. It stops the assistant knowing it and bringing it up. In "group" presentation, chat and context apply to the whole page holding the question: one question with chat: False hides the chat for every question on its page.

Cost

Each assistant reply is charged to the survey owner’s account in credits, based on the model’s usage. If your balance runs out, the assistant becomes unavailable to respondents, and they can carry on answering the survey without it.

Reading conversations

Conversations are on the survey’s Events page, under the Assistant tab. For each respondent you can read the full conversation, see which answers the assistant filled, and download it as JSON. To get the page’s address:
To download every conversation at once, see Conversations.

Downloading what respondents did

On a survey with the assistant on, every step a respondent takes is recorded: each page shown and submitted, each change to an answer, each message to the assistant and how its reply ended, and whether the form took each answer the assistant filled. This is the interaction trace. It doesn’t include message text, which is in the conversation. Use Coop.get_all_human_survey_interactions to get it. Rows come in the order they were received; sort each response’s rows by session_started_at, then seq, for the order they happened (see Putting rows in order):
Rows from preview links are left out. To check your setup while you build it, pass include_previews=True to get them too, each marked is_preview. Pass response_uuid for one response’s rows only. Rows that arrive while the download runs are included, and no row comes back twice. To fetch new rows later, pass the id of the last row you downloaded as after, with the same filters. To fetch one batch at a time, use Coop.get_human_survey_interactions, which takes after, limit (at most 100, the default), response_uuid and include_previews, and returns {"interactions", "next_cursor", "has_more"}. Pass next_cursor as after while has_more is True. Coop.count_human_survey_interactions returns how many rows there are, with the same filters. From the command line:

Every row

Each type’s fields

  • value is the answer in the form at that moment, in the shape draft_type and draft_version name.
  • draft_write says whether this row’s value became the saved in-progress answer when it arrived: applied, superseded (a later change from the same tab had already arrived) or late (the page had already been submitted). It’s about when the row arrived, not about the form: a late row can still be a change the respondent submitted. For whether the form took an assistant’s edit, use the row’s type, or outcome in the edits download below.
  • value_omitted is True for a value too long to keep, about 16,000 characters. The change is still recorded, and chars gives its length.

Downloading what the assistant did

Summary

Start with the summary: the survey’s assistant data in numbers, enough for a first look and to decide what else to download.
Previews are left out unless include_previews=True (--include-previews).

Conversations

The quickest way to read what happened is the conversations: each respondent’s survey as the Assistant tab shows it, with the chat, the assistant’s edits and everything the respondent did, in the order it happened, one row per thing.
Rows are grouped by response, responses in the order they were first opened, and each response’s rows in position order. Each row’s kind says what it is and decides its other fields: a page shown or submitted, each save of an answer the respondent typed, a message and the reply to it, an answer the assistant filled, rejected or had undone, and an event that couldn’t be recorded. pd.DataFrame(rows) gives one table, each kind’s fields blank on the others. The order comes from the interaction trace. When the trace is missing part of a turn, say because the respondent closed the tab mid-reply, that part is placed by its turn, and placed says how. While a response is in progress, such rows can move once the rest of its trace arrives. Conversations take response_uuid and include_previews, and cover the whole survey each time: fetch them again for what has happened since. From the command line, --output is required unless --response is given.

Conversation fields

Every row has: Each kind adds: Rows are things that happened, not answers’ final states: an edit can be an assistant_edit row and later an edit_undone one. For each edit’s final outcome, use the edits download.

Messages, turns and edits

Three more downloads describe the assistant’s side, one row at a time. Each row names its turn (one message and the reply to it) by turn and an edit by edit, the same ids as in the interaction trace, so they all join on them.
  • Messages: the conversations, one row per thing the chat showed: the respondent’s message, the reply’s text, a receipt for each answer it filled, and what the respondent was told if a reply failed.
  • Turns: one row per message the assistant answered, with how the reply ended, its timing, tokens and cost. No message text.
  • Edits: one row per answer the assistant asked to fill in, including those that were rejected, with whether the form took each.
All three take response_uuid and include_previews as the interaction trace does; messages and edits also take turn. A turn appears a few seconds after it ends, never while the assistant is still replying. To fetch new rows later, pass the last row’s turn (messages and turns) or edit (edits) as after. Coop.get_human_survey_assistant_messages, Coop.get_human_survey_assistant_turns and Coop.get_human_survey_assistant_edits return one batch at a time, as {"messages", "turns" or "edits", "next_cursor", "has_more"}. A batch of messages never splits a turn, and its limit counts turns. From the command line:

Threads, pages and configs

Three downloads describe where the assistant was offered and how it was set up.
  • Threads: one row per response the assistant was offered to, whether or not the respondent chatted. The way to find respondents who never used it.
  • Pages: one row per page served with the assistant on: whether the chat was shown, which questions it could fill, and the config its turns ran on.
  • Configs: the assistant’s settings as each page was served with them, including the system prompt and question instructions. Pages share a config when their settings and question overrides match, so a survey with question overrides has several even when nothing changed during fielding.
All three take response_uuid and include_previews, and are as of when they’re read: a thread’s turns and a page’s status change while the survey is live. Coop.get_human_survey_assistant_threads, …_pages and …_configs return one batch at a time. To continue, pass next_cursor as after: the last row’s thread or config, or for pages, its response_uuid and page_id joined by :. Configs come at most 20 to a batch, as each holds the whole system prompt.

Putting rows in order

Each download lists rows in the order they became available, so new ones can be fetched later without missing any. That isn’t always the order things happened: Across respondents, rows from different responses are interleaved. To line the conversation up with the trace, join on turn: the trace’s chat.message_sent row for a turn says where in the respondent’s steps the message was sent.

Message fields

Every row has kind, turn, turn_status (how the turn ended, as in the turn fields), thread (the conversation), response_uuid, is_preview, page_id, seq (its place in the turn, from 0) and at (when it was recorded, ISO 8601, UTC). Then, by kind:

Turn fields

Edit fields

Thread fields

Page fields

Config fields

Fields

dict
The survey assistant’s configuration. Omit for no assistant.system_prompt and every question’s instructions are private: respondents never receive them. The other fields are sent to the respondent’s browser.
Respondents’ messages are limited to 16,000 characters.

Change the assistant on a live survey

Read the stored schema, then patch only what you want to change:
Or from the command line, with the partial schema in a JSON file:
A patch merges into the stored schema key by key, so patching one question’s override leaves the other overrides in place. A patch can’t remove a key: to remove an override, patch survey.assistant to None and then write back the whole block without it. Changes take effect from the next page each respondent is shown. A page already on screen keeps the settings it was shown with, so a conversation never switches settings partway through a page. Setting enabled to False is the exception: it stops the assistant replying straight away.