Intro to EDSL

This notebook provides example code for base components of EDSL, an open-source library for simulating surveys, experiments and other research with AI agents and large language models. Details on the code below are provided in accompanying slides: How to use EDSL.

Technical setup

Before running the code below, please ensure that you have installed the EDSL library and either activated remote inference from your Coop account or stored API keys for the language models that you want to use with EDSL.

Documentation

Please also see our documentation page for tips, tutorials and more demo notebooks on using EDSL.

Simple example

We start by selecting a question type and constructing a question in the relevant template:

[1]:
from edsl import QuestionMultipleChoice

q = QuestionMultipleChoice(
    question_name = "marvel_movies",
    question_text = "Do you enjoy Marvel movies?",
    question_options = ["Yes", "No", "I do not know"]
)

We administer a question by calling the run() method. This generates a dataset of Results including the model’s response to the question:

[2]:
results = q.run()

results.select("marvel_movies").print(format="rich")
┏━━━━━━━━━━━━━━━━┓
┃ answer         ┃
┃ .marvel_movies ┃
┡━━━━━━━━━━━━━━━━┩
│ I do not know  │
└────────────────┘

Designing AI agents

We can create personas for agents to answer the question:

[3]:
from edsl import AgentList, Agent

personas = ["comic book collector", "movie critic"]

a = AgentList(
    Agent(traits = {"persona": p}) for p in personas
)

Selecting language models

We can select language models to generate the responses (in the example above we did not specify a model, so GPT 4 preview was used by default):

[4]:
from edsl import ModelList, Model

models = ["gpt-4o", "claude-3-5-sonnet-20240620"]

m = ModelList(
    Model(m) for m in ["gpt-4o", "claude-3-5-sonnet-20240620"]
)

Generating results

We add agents and models to a question when running it:

[5]:
results = q.by(a).by(m).run()

results.select("model", "persona", "marvel_movies").print(format="rich")
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ model                       agent                 answer         ┃
┃ .model                      .persona              .marvel_movies ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ gpt-4o                      comic book collector  Yes            │
├────────────────────────────┼──────────────────────┼────────────────┤
│ claude-3-5-sonnet-20240620  comic book collector  Yes            │
├────────────────────────────┼──────────────────────┼────────────────┤
│ gpt-4o                      movie critic          Yes            │
├────────────────────────────┼──────────────────────┼────────────────┤
│ claude-3-5-sonnet-20240620  movie critic          Yes            │
└────────────────────────────┴──────────────────────┴────────────────┘

Parameterizing questions

We can use Scenario objects to add data or content to questions:

[6]:
q1 = QuestionMultipleChoice(
    question_name = "politically_motivated",
    question_text = """
    Read the following movie review and determine whether it is politically motivated.
    Movie: {{ title }}
    Review: {{ review }}
    """,
    question_options = ["Yes", "No", "I do not know"]
)

EDSL comes with methods for generating scenarios from many data sources, including PDFs, CSVs, docs, images, tables, lists, dicts:

[7]:
from edsl import Scenario

example_review = {
    "year": 2014,
    "title": "Captain America: The Winter Soldier",
    "review": """
    Part superhero flick, part 70s political thriller.
    It's a bold mix that pays off, delivering a scathing
    critique of surveillance states wrapped in spandex
    and shield-throwing action.
    """
}

s = Scenario.from_dict(example_review)
[8]:
results = q1.by(s).by(a).by(m).run()

(
    results.filter("persona == 'movie critic'")
    .sort_by("model")
    .select("model", "year", "title", "politically_motivated")
    .print(format="rich")
)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ model                       scenario  scenario                             answer                 ┃
┃ .model                      .year     .title                               .politically_motivated ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ claude-3-5-sonnet-20240620  2014      Captain America: The Winter Soldier  No                     │
├────────────────────────────┼──────────┼─────────────────────────────────────┼────────────────────────┤
│ gpt-4o                      2014      Captain America: The Winter Soldier  Yes                    │
└────────────────────────────┴──────────┴─────────────────────────────────────┴────────────────────────┘

Comments

Questions automatically include a “comment” field. This can be useful for understanding the context of a response, or debugging a non-response.

[9]:
(
    results.filter("persona == 'movie critic'")
    .sort_by("model")
    .select("model", "politically_motivated", "politically_motivated_comment")
    .print(format="rich")
)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ model                       answer                  comment                                                   ┃
┃ .model                      .politically_motivated  .politically_motivated_comment                            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ claude-3-5-sonnet-20240620  No                      This review appears to be a straightforward critique of   │
│                                                     the film's genre-blending and themes, without any overt   │
│                                                     political agenda or bias influencing the assessment.      │
├────────────────────────────┼────────────────────────┼───────────────────────────────────────────────────────────┤
│ gpt-4o                      Yes                     The review mentions a "scathing critique of surveillance  │
│                                                     states," which indicates that the film's themes and the   │
│                                                     review itself have political undertones.                  │
└────────────────────────────┴────────────────────────┴───────────────────────────────────────────────────────────┘

Combining questions in a survey

We can combine questions in a ``Survey` <https://docs.expectedparrot.com/en/latest/surveys.html>`__ to administer them together. Here we create some variations on the above question to compare responses:

[10]:
from edsl import QuestionYesNo

q2 = QuestionYesNo(
    question_name = "yn",
    question_text = """
    Read the following movie review and determine whether it is politically motivated.
    Movie: {{ title }}
    Review: {{ review }}
    """
)
[11]:
from edsl import QuestionLinearScale

q3 = QuestionLinearScale(
    question_name = "ls",
    question_text = """
    Read the following movie review and indicate whether it is politically motivated.
    Movie: {{ title }}
    Review: {{ review }}
    """,
    question_options = [0,1,2,3,4,5],
    option_labels = {0:"Not at all", 5:"Very much"}
)
[12]:
from edsl import QuestionList

q4 = QuestionList(
    question_name = "favorites",
    question_text = "List your favorite Marvel movies.",
    max_list_items = 3
)

Survey rules & logic

We can add skip/stop and other rules, and “memory” of other questions in a survey:

[13]:
from edsl import Survey

survey = Survey(questions = [q2, q3, q4])

survey = survey.add_stop_rule(q3, "ls < 3")
[14]:
results = survey.by(s).by(a).by(m).run()
[15]:
(
    results.filter("persona == 'comic book collector'")
    .select("model", "persona", "yn", "ls", "favorites")
    .print(pretty_labels = {
        "answer.yn": "Yes/No version",
        "answer.ls": "Linear scale version",
        "answer.favorites": "Favorites"
    }, format="rich")
)
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ model.model             agent.persona         Yes/No version  Linear scale version  Favorites               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ claude-3-5-sonnet-202…  comic book collector  No              2                     None                    │
├────────────────────────┼──────────────────────┼────────────────┼──────────────────────┼─────────────────────────┤
│ gpt-4o                  comic book collector  Yes             3                     ['The Avengers',        │
│                                                                                     'Guardians of the       │
│                                                                                     Galaxy', 'Spider-Man:   │
│                                                                                     Into the Spider-Verse'] │
└────────────────────────┴──────────────────────┴────────────────┴──────────────────────┴─────────────────────────┘

Working with results as datasets

EDSL provides built-in methods for analyzing results, e.g., as SQL tables, dataframes:

[16]:
results.sql("select model, persona, yn, ls, favorites from self", shape="wide")
[16]:
model persona yn ls favorites
0 claude-3-5-sonnet-20240620 comic book collector No 2 None
1 claude-3-5-sonnet-20240620 movie critic No 2 None
2 gpt-4o comic book collector Yes 3 ['The Avengers', 'Guardians of the Galaxy', 'S...
3 gpt-4o movie critic Yes 3 ['Iron Man', 'Black Panther', 'Avengers: Endga...
[17]:
results.to_pandas()
[17]:
answer.ls answer.yn answer.favorites scenario.year scenario.review scenario.title agent.persona agent.agent_instruction agent.agent_name model.temperature ... question_options.favorites_question_options question_type.favorites_question_type question_type.ls_question_type question_type.yn_question_type comment.ls_comment comment.yn_comment comment.favorites_comment generated_tokens.yn_generated_tokens generated_tokens.favorites_generated_tokens generated_tokens.ls_generated_tokens
0 2 No NaN 2014 \n Part superhero flick, part 70s political... Captain America: The Winter Soldier comic book collector You are answering questions as if you were a h... Agent_1 0.5 ... NaN list linear_scale yes_no As a comic book collector, I don't see this re... Comment: As a comic book collector, I don't se... Task was cancelled. No\n\nComment: As a comic book collector, I do... NaN 2\n\nAs a comic book collector, I don't see th...
1 2 No NaN 2014 \n Part superhero flick, part 70s political... Captain America: The Winter Soldier movie critic You are answering questions as if you were a h... Agent_2 0.5 ... NaN list linear_scale yes_no While the review mentions political themes lik... Comment: This review does not appear to be pol... Task was cancelled. No\n\nComment: This review does not appear to ... NaN 2\n\nWhile the review mentions political theme...
2 3 Yes ['The Avengers', 'Guardians of the Galaxy', 'S... 2014 \n Part superhero flick, part 70s political... Captain America: The Winter Soldier comic book collector You are answering questions as if you were a h... Agent_1 0.5 ... NaN list linear_scale yes_no The review mentions the movie's critique of su... The review mentions a "scathing critique of su... These movies capture the essence of Marvel's s... Yes\n\nThe review mentions a "scathing critiqu... ["The Avengers", "Guardians of the Galaxy", "S... 3\n\nThe review mentions the movie's critique ...
3 3 Yes ['Iron Man', 'Black Panther', 'Avengers: Endga... 2014 \n Part superhero flick, part 70s political... Captain America: The Winter Soldier movie critic You are answering questions as if you were a h... Agent_2 0.5 ... NaN list linear_scale yes_no The review highlights a "scathing critique of ... The review mentions that the movie delivers "a... These films stand out for their groundbreaking... Yes\n\nThe review mentions that the movie deli... ["Iron Man", "Black Panther", "Avengers: Endga... 3\n\nThe review highlights a "scathing critiqu...

4 rows × 48 columns

[18]:
results.to_csv("marvel_movies_survey.csv")

Posting to the Coop

[19]:
from edsl import Notebook
[20]:
n = Notebook(path = "edsl_intro.ipynb")
[21]:
n.push(description = "Example survey: Using EDSL to analyze content", visibility = "public")
[21]:
{'description': 'Example survey: Using EDSL to analyze content',
 'object_type': 'notebook',
 'url': 'https://www.expectedparrot.com/content/b3c45b82-5d3a-4d79-9e00-0ce1f8b1ff65',
 'uuid': 'b3c45b82-5d3a-4d79-9e00-0ce1f8b1ff65',
 'version': '0.1.33.dev1',
 'visibility': 'public'}

To update an object at the Coop:

[23]:
n = Notebook(path = "edsl_intro.ipynb") # resave
[24]:
n.patch(uuid = "b3c45b82-5d3a-4d79-9e00-0ce1f8b1ff65", value = n)
[24]:
{'status': 'success'}