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")
Job UUID | c62b796c-f4e0-4028-b256-9b64e6688898 |
Progress Bar URL | https://www.expectedparrot.com/home/remote-job-progress/c62b796c-f4e0-4028-b256-9b64e6688898 |
Exceptions Report URL | None |
Results UUID | a45fb248-29b1-4339-a2d3-7aee1aaa1da3 |
Results URL | https://www.expectedparrot.com/content/a45fb248-29b1-4339-a2d3-7aee1aaa1da3 |
[2]:
answer.marvel_movies | |
---|---|
0 | 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", "gemini-1.5-flash"]
m = ModelList(Model(model) for model in models)
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")
Job UUID | 4977ba4e-8572-4ce0-9281-56475199e4d4 |
Progress Bar URL | https://www.expectedparrot.com/home/remote-job-progress/4977ba4e-8572-4ce0-9281-56475199e4d4 |
Exceptions Report URL | None |
Results UUID | 872be459-3e97-4bfc-a796-e198c085e8b6 |
Results URL | https://www.expectedparrot.com/content/872be459-3e97-4bfc-a796-e198c085e8b6 |
[5]:
model.model | agent.persona | answer.marvel_movies | |
---|---|---|---|
0 | gpt-4o | comic book collector | Yes |
1 | gemini-1.5-flash | comic book collector | Yes |
2 | gpt-4o | movie critic | Yes |
3 | gemini-1.5-flash | 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()
Job UUID | 58a63ef0-269b-4709-8c16-148e59bb01a8 |
Progress Bar URL | https://www.expectedparrot.com/home/remote-job-progress/58a63ef0-269b-4709-8c16-148e59bb01a8 |
Exceptions Report URL | None |
Results UUID | a1816206-731e-402f-be7f-2e144395adba |
Results URL | https://www.expectedparrot.com/content/a1816206-731e-402f-be7f-2e144395adba |
[9]:
(
results.filter("persona == 'movie critic'")
.sort_by("model")
.select("model", "year", "title", "politically_motivated")
)
[9]:
model.model | scenario.year | scenario.title | answer.politically_motivated | |
---|---|---|---|---|
0 | gemini-1.5-flash | 2014 | Captain America: The Winter Soldier | No |
1 | 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.
[10]:
(
results.filter("persona == 'movie critic'")
.sort_by("model")
.select("model", "politically_motivated", "politically_motivated_comment")
)
[10]:
model.model | answer.politically_motivated | comment.politically_motivated_comment | |
---|---|---|---|
0 | gemini-1.5-flash | No | The review focuses on the film's genre blending and its thematic exploration of surveillance states. While the subject matter has political implications, the review itself doesn't explicitly endorse or condemn any specific political ideology or party. It's a critique of a concept, not a political stance. |
1 | gpt-4o | Yes | The review highlights the film's critique of surveillance states, which is a political theme, suggesting that the review is politically motivated. |
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:
[11]:
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 }}
"""
)
[12]:
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"}
)
[13]:
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:
[14]:
from edsl import Survey
survey = Survey(questions = [q2, q3, q4])
survey = survey.add_stop_rule(q3, "ls < 3")
[15]:
results = survey.by(s).by(a).by(m).run()
Job UUID | 5aca03bf-d1bd-4fb0-984e-c8f53d8a5a08 |
Progress Bar URL | https://www.expectedparrot.com/home/remote-job-progress/5aca03bf-d1bd-4fb0-984e-c8f53d8a5a08 |
Exceptions Report URL | None |
Results UUID | 084c5a89-3bc3-4f7f-b037-34445bfb27a9 |
Results URL | https://www.expectedparrot.com/content/084c5a89-3bc3-4f7f-b037-34445bfb27a9 |
[16]:
(
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"
})
)
[16]:
model.model | agent.persona | Yes/No version | Linear scale version | Favorites | |
---|---|---|---|---|---|
0 | gpt-4o | comic book collector | Yes | 4 | ['Avengers: Endgame', 'Spider-Man: Into the Spider-Verse', 'Guardians of the Galaxy'] |
1 | gemini-1.5-flash | comic book collector | No | 1 | nan |
Working with results as datasets
EDSL provides built-in methods for analyzing results, e.g., as SQL tables, dataframes:
[17]:
results.sql("select model, persona, yn, ls, favorites from self")
[17]:
model | persona | yn | ls | favorites | |
---|---|---|---|---|---|
0 | gpt-4o | comic book collector | Yes | 4 | ['Avengers: Endgame', 'Spider-Man: Into the Spider-Verse', 'Guardians of the Galaxy'] |
1 | gemini-1.5-flash | comic book collector | No | 1 | nan |
2 | gpt-4o | movie critic | Yes | 4 | ['Avengers: Endgame', 'Black Panther', 'Guardians of the Galaxy'] |
3 | gemini-1.5-flash | movie critic | Yes | 1 | nan |
[18]:
results.to_pandas()
[18]:
answer.ls | answer.yn | answer.favorites | scenario.title | scenario.year | scenario.review | scenario.scenario_index | agent.agent_index | agent.agent_name | agent.agent_instruction | ... | comment.favorites_comment | generated_tokens.yn_generated_tokens | generated_tokens.ls_generated_tokens | generated_tokens.favorites_generated_tokens | cache_used.ls_cache_used | cache_used.yn_cache_used | cache_used.favorites_cache_used | cache_keys.yn_cache_key | cache_keys.ls_cache_key | cache_keys.favorites_cache_key | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 4 | Yes | ['Avengers: Endgame', 'Spider-Man: Into the Sp... | Captain America: The Winter Soldier | 2014 | \n Part superhero flick, part 70s political... | 0 | 0 | Agent_9 | You are answering questions as if you were a h... | ... | Avengers: Endgame is an epic conclusion to the... | Yes\n\nThe review mentions a "scathing critiqu... | 4 \nThe review highlights the movie's critiqu... | ["Avengers: Endgame", "Spider-Man: Into the Sp... | True | True | True | 28be7af4ca7266aeaaf6dfb1939f2830 | f34701a54317094ac2145039c54d2e82 | 4a65448cf57b2e2529af3a3fe11ad8b0 |
1 | 1 | No | NaN | Captain America: The Winter Soldier | 2014 | \n Part superhero flick, part 70s political... | 0 | 0 | Agent_10 | You are answering questions as if you were a h... | ... | Task was cancelled. | No\n\nLook, I'm not saying the movie *doesn't*... | NaN | NaN | NaN | False | NaN | 455b0c2040a35ed5ee2b5653a939e8ad | NaN | NaN |
2 | 4 | Yes | ['Avengers: Endgame', 'Black Panther', 'Guardi... | Captain America: The Winter Soldier | 2014 | \n Part superhero flick, part 70s political... | 0 | 1 | Agent_11 | You are answering questions as if you were a h... | ... | These films stand out for their compelling sto... | Yes\n\nThe review describes the movie as deliv... | 4 \nThe review highlights a critique of surve... | ["Avengers: Endgame", "Black Panther", "Guardi... | True | True | True | 502a250f2082fec0421839e48967b5d6 | b24392669b17dbf2ddcaf21067eafae9 | 595a2e86d433057894b2609f4e058586 |
3 | 1 | Yes | NaN | Captain America: The Winter Soldier | 2014 | \n Part superhero flick, part 70s political... | 0 | 1 | Agent_12 | You are answering questions as if you were a h... | ... | Task was cancelled. | Yes\n\nThe review explicitly mentions a "scath... | NaN | NaN | NaN | False | NaN | 6a0c7ae0d01760ce2648dae44cbcfa82 | NaN | NaN |
4 rows × 62 columns
[19]:
results.to_csv("marvel_movies_survey.csv")
File written to marvel_movies_survey.csv
Posting to the Coop
[20]:
from edsl import Notebook
[21]:
n = Notebook(path = "edsl_intro.ipynb")
[22]:
info = n.push(description = "Example survey: Using EDSL to analyze content", visibility = "public")
info
[22]:
{'description': 'Example survey: Using EDSL to analyze content',
'object_type': 'notebook',
'url': 'https://www.expectedparrot.com/content/0d416fec-133f-4a93-a87d-5ae398fb9356',
'uuid': '0d416fec-133f-4a93-a87d-5ae398fb9356',
'version': '0.1.43.dev1',
'visibility': 'public'}
To update an object at the Coop:
[23]:
n = Notebook(path = "edsl_intro.ipynb") # resave
[24]:
n.patch(uuid = info["uuid"], value = n)
[24]:
{'status': 'success'}