edsl 0.1.44__py3-none-any.whl → 0.1.46__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. edsl/Base.py +7 -3
  2. edsl/__version__.py +1 -1
  3. edsl/agents/InvigilatorBase.py +3 -1
  4. edsl/agents/PromptConstructor.py +66 -91
  5. edsl/agents/QuestionInstructionPromptBuilder.py +160 -79
  6. edsl/agents/QuestionTemplateReplacementsBuilder.py +80 -17
  7. edsl/agents/question_option_processor.py +15 -6
  8. edsl/coop/CoopFunctionsMixin.py +3 -4
  9. edsl/coop/coop.py +171 -96
  10. edsl/data/RemoteCacheSync.py +10 -9
  11. edsl/enums.py +3 -3
  12. edsl/inference_services/AnthropicService.py +11 -9
  13. edsl/inference_services/AvailableModelFetcher.py +2 -0
  14. edsl/inference_services/AwsBedrock.py +1 -2
  15. edsl/inference_services/AzureAI.py +12 -9
  16. edsl/inference_services/GoogleService.py +9 -4
  17. edsl/inference_services/InferenceServicesCollection.py +2 -2
  18. edsl/inference_services/MistralAIService.py +1 -2
  19. edsl/inference_services/OpenAIService.py +9 -4
  20. edsl/inference_services/PerplexityService.py +2 -1
  21. edsl/inference_services/{GrokService.py → XAIService.py} +2 -2
  22. edsl/inference_services/registry.py +2 -2
  23. edsl/jobs/AnswerQuestionFunctionConstructor.py +12 -1
  24. edsl/jobs/Jobs.py +24 -17
  25. edsl/jobs/JobsChecks.py +10 -13
  26. edsl/jobs/JobsPrompts.py +49 -26
  27. edsl/jobs/JobsRemoteInferenceHandler.py +4 -5
  28. edsl/jobs/async_interview_runner.py +3 -1
  29. edsl/jobs/check_survey_scenario_compatibility.py +5 -5
  30. edsl/jobs/data_structures.py +3 -0
  31. edsl/jobs/interviews/Interview.py +6 -3
  32. edsl/jobs/interviews/InterviewExceptionEntry.py +12 -0
  33. edsl/jobs/tasks/TaskHistory.py +1 -1
  34. edsl/language_models/LanguageModel.py +6 -3
  35. edsl/language_models/PriceManager.py +45 -5
  36. edsl/language_models/model.py +47 -26
  37. edsl/questions/QuestionBase.py +21 -0
  38. edsl/questions/QuestionBasePromptsMixin.py +103 -0
  39. edsl/questions/QuestionFreeText.py +22 -5
  40. edsl/questions/descriptors.py +4 -0
  41. edsl/questions/question_base_gen_mixin.py +96 -29
  42. edsl/results/Dataset.py +65 -0
  43. edsl/results/DatasetExportMixin.py +320 -32
  44. edsl/results/Result.py +27 -0
  45. edsl/results/Results.py +22 -2
  46. edsl/results/ResultsGGMixin.py +7 -3
  47. edsl/scenarios/DocumentChunker.py +2 -0
  48. edsl/scenarios/FileStore.py +10 -0
  49. edsl/scenarios/PdfExtractor.py +21 -1
  50. edsl/scenarios/Scenario.py +25 -9
  51. edsl/scenarios/ScenarioList.py +226 -24
  52. edsl/scenarios/handlers/__init__.py +1 -0
  53. edsl/scenarios/handlers/docx.py +5 -1
  54. edsl/scenarios/handlers/jpeg.py +39 -0
  55. edsl/surveys/Survey.py +5 -4
  56. edsl/surveys/SurveyFlowVisualization.py +91 -43
  57. edsl/templates/error_reporting/exceptions_table.html +7 -8
  58. edsl/templates/error_reporting/interview_details.html +1 -1
  59. edsl/templates/error_reporting/interviews.html +0 -1
  60. edsl/templates/error_reporting/overview.html +2 -7
  61. edsl/templates/error_reporting/performance_plot.html +1 -1
  62. edsl/templates/error_reporting/report.css +1 -1
  63. edsl/utilities/PrettyList.py +14 -0
  64. edsl-0.1.46.dist-info/METADATA +246 -0
  65. {edsl-0.1.44.dist-info → edsl-0.1.46.dist-info}/RECORD +67 -66
  66. edsl-0.1.44.dist-info/METADATA +0 -110
  67. {edsl-0.1.44.dist-info → edsl-0.1.46.dist-info}/LICENSE +0 -0
  68. {edsl-0.1.44.dist-info → edsl-0.1.46.dist-info}/WHEEL +0 -0
@@ -8,51 +8,113 @@ import tempfile
8
8
  class SurveyFlowVisualization:
9
9
  """A mixin for visualizing the flow of a survey with parameter visualization."""
10
10
 
11
- def __init__(self, survey: "Survey"):
11
+ def __init__(self, survey: "Survey", scenario: Optional["Scenario"] = None, agent: Optional["Agent"] = None):
12
12
  self.survey = survey
13
+ self.scenario = scenario or {}
14
+ self.agent = agent
15
+ #from edsl import Scenario
16
+ #self.scenario = Scenario({'hello': 'world'})
13
17
 
14
18
  def show_flow(self, filename: Optional[str] = None):
15
19
  """Create an image showing the flow of users through the survey and question parameters."""
16
20
  # Create a graph object
17
21
  import pydot
18
22
 
19
- graph = pydot.Dot(graph_type="digraph")
23
+ FONT_SIZE = "10"
20
24
 
21
- # First collect all unique parameters and answer references
25
+ graph = pydot.Dot(graph_type="digraph", fontsize=FONT_SIZE)
26
+
27
+ # First collect all unique parameters and different types of references
22
28
  params_and_refs = set()
23
29
  param_to_questions = {} # Keep track of which questions use each parameter
24
- answer_refs = set() # Track answer references between questions
30
+ reference_types = {} # Dictionary to store different types of references
31
+ reference_colors = {
32
+ 'answer': 'purple',
33
+ 'question_text': 'red',
34
+ 'question_options': 'orange',
35
+ 'comment': 'blue',
36
+ 'default': "grey"
37
+ }
25
38
 
26
39
  # First pass: collect parameters and their question associations
27
40
  for index, question in enumerate(self.survey.questions):
28
- # Add the main question node
29
41
  question_node = pydot.Node(
30
- f"Q{index}", label=f"{question.question_name}", shape="ellipse"
42
+ f"Q{index}", label=f"{question.question_name}", shape="ellipse", fontsize=FONT_SIZE
31
43
  )
32
44
  graph.add_node(question_node)
33
45
 
34
- if hasattr(question, "parameters"):
35
- for param in question.parameters:
36
- # Check if this is an answer reference (contains '.answer')
37
- if ".answer" in param:
38
- answer_refs.add((param.split(".")[0], index))
46
+ if hasattr(question, "detailed_parameters"):
47
+ for param in question.detailed_parameters:
48
+ if "agent." in param:
49
+ # Handle agent trait references
50
+ trait_name = param.replace("agent.", "")
51
+ params_and_refs.add(param)
52
+ if param not in param_to_questions:
53
+ param_to_questions[param] = []
54
+ param_to_questions[param].append(index)
55
+ elif "." in param:
56
+ source_q, ref_type = param.split(".", 1)
57
+ if ref_type not in reference_types:
58
+ reference_types[ref_type] = set()
59
+ reference_types[ref_type].add((source_q, index))
39
60
  else:
40
61
  params_and_refs.add(param)
41
62
  if param not in param_to_questions:
42
63
  param_to_questions[param] = []
43
64
  param_to_questions[param].append(index)
44
65
 
66
+ # Add edges for all reference types
67
+ for ref_type, references in reference_types.items():
68
+ color = reference_colors.get(ref_type, reference_colors['default'])
69
+ for source_q_name, target_q_index in references:
70
+ # Find the source question index by name
71
+ try:
72
+ source_q_index = next(
73
+ i
74
+ for i, q in enumerate(self.survey.questions)
75
+ if q.question_name == source_q_name
76
+ )
77
+ except StopIteration:
78
+ print(f"Source question {source_q_name} not found in survey.")
79
+ continue
80
+
81
+ ref_edge = pydot.Edge(
82
+ f"Q{source_q_index}",
83
+ f"Q{target_q_index}",
84
+ style="dashed",
85
+ color=color,
86
+ label=f".{ref_type}",
87
+ fontcolor=color,
88
+ fontname="Courier",
89
+ fontsize=FONT_SIZE
90
+ )
91
+ graph.add_edge(ref_edge)
92
+
45
93
  # Create parameter nodes and connect them to questions
46
94
  for param in params_and_refs:
47
95
  param_node_name = f"param_{param}"
48
- param_node = pydot.Node(
49
- param_node_name,
50
- label=f"{{{{ {param} }}}}",
51
- shape="box",
52
- style="filled",
53
- fillcolor="lightgrey",
54
- fontsize="10",
55
- )
96
+ node_attrs = {
97
+ "label": f"{{{{ {param} }}}}",
98
+ "shape": "box",
99
+ "style": "filled",
100
+ "fillcolor": "lightgrey",
101
+ "fontsize": FONT_SIZE,
102
+ }
103
+
104
+ # Special handling for agent traits
105
+ if param.startswith("agent."):
106
+ node_attrs.update({
107
+ "fillcolor": "lightpink",
108
+ "label": f"Agent Trait\n{{{{ {param} }}}}"
109
+ })
110
+ # Check if parameter exists in scenario
111
+ elif self.scenario and param in self.scenario:
112
+ node_attrs.update({
113
+ "fillcolor": "lightgreen",
114
+ "label": f"Scenario\n{{{{ {param} }}}}"
115
+ })
116
+
117
+ param_node = pydot.Node(param_node_name, **node_attrs)
56
118
  graph.add_node(param_node)
57
119
 
58
120
  # Connect this parameter to all questions that use it
@@ -61,39 +123,22 @@ class SurveyFlowVisualization:
61
123
  param_node_name,
62
124
  f"Q{q_index}",
63
125
  style="dotted",
64
- color="grey",
65
126
  arrowsize="0.5",
127
+ fontsize=FONT_SIZE,
66
128
  )
67
129
  graph.add_edge(param_edge)
68
130
 
69
- # Add edges for answer references
70
- for source_q_name, target_q_index in answer_refs:
71
- # Find the source question index by name
72
- source_q_index = next(
73
- i
74
- for i, q in enumerate(self.survey.questions)
75
- if q.question_name == source_q_name
76
- )
77
- ref_edge = pydot.Edge(
78
- f"Q{source_q_index}",
79
- f"Q{target_q_index}",
80
- style="dashed",
81
- color="purple",
82
- label="answer reference",
83
- )
84
- graph.add_edge(ref_edge)
85
-
86
131
  # Add an "EndOfSurvey" node
87
132
  graph.add_node(
88
- pydot.Node("EndOfSurvey", label="End of Survey", shape="rectangle")
133
+ pydot.Node("EndOfSurvey", label="End of Survey", shape="rectangle", fontsize=FONT_SIZE, style="filled", fillcolor="lightgrey")
89
134
  )
90
135
 
91
136
  # Add edges for normal flow through the survey
92
137
  num_questions = len(self.survey.questions)
93
138
  for index in range(num_questions - 1):
94
- graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
139
+ graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}", fontsize=FONT_SIZE))
95
140
 
96
- graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey"))
141
+ graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey", fontsize=FONT_SIZE))
97
142
 
98
143
  relevant_rules = [
99
144
  rule
@@ -109,7 +154,7 @@ class SurveyFlowVisualization:
109
154
  "purple",
110
155
  "brown",
111
156
  "cyan",
112
- "green",
157
+ "darkgreen",
113
158
  ]
114
159
  rule_colors = {
115
160
  rule: colors[i % len(colors)] for i, rule in enumerate(relevant_rules)
@@ -133,6 +178,8 @@ class SurveyFlowVisualization:
133
178
  fontcolor=color,
134
179
  tailport="n",
135
180
  headport="n",
181
+ fontname="Courier",
182
+ fontsize=FONT_SIZE,
136
183
  )
137
184
  else:
138
185
  edge = pydot.Edge(
@@ -141,6 +188,8 @@ class SurveyFlowVisualization:
141
188
  label=edge_label,
142
189
  color=color,
143
190
  fontcolor=color,
191
+ fontname="Courier",
192
+ fontsize=FONT_SIZE,
144
193
  )
145
194
 
146
195
  graph.add_edge(edge)
@@ -156,9 +205,8 @@ class SurveyFlowVisualization:
156
205
  except FileNotFoundError:
157
206
  print(
158
207
  """File not found. Most likely it's because you don't have graphviz installed. Please install it and try again.
159
- It's
208
+ On Ubuntu, you can install it by running:
160
209
  $ sudo apt-get install graphviz
161
- on Ubuntu.
162
210
  """
163
211
  )
164
212
  from edsl.utilities.is_notebook import is_notebook
@@ -7,11 +7,11 @@
7
7
  <table border="1">
8
8
  <thead>
9
9
  <tr>
10
- <th>Exception Type</th>
11
- <th>Service</th>
12
- <th>Model</th>
13
- <th>Question Name</th>
14
- <th>Total</th>
10
+ <th style="text-align: left">Exception Type</th>
11
+ <th style="text-align: left">Service</th>
12
+ <th style="text-align: left">Model</th>
13
+ <th style="text-align: left">Question Name</th>
14
+ <th style="text-align: left">Total</th>
15
15
  </tr>
16
16
  </thead>
17
17
  <tbody>
@@ -27,9 +27,8 @@
27
27
  </tbody>
28
28
  </table>
29
29
  <p>
30
- <i>Note:</i> You may encounter repeated exceptions where retries were attempted.
31
- You can modify the maximum number of attempts for failed API calls in `edsl/config.py`.
30
+ Note: You may encounter repeated exceptions where retries were attempted.
32
31
  </p>
33
32
  <p>
34
- Click to expand the details below for information about each exception, including code for reproducing it.
33
+ See details about each exception, including code for reproducing it (click to expand).
35
34
  </p>
@@ -25,7 +25,7 @@
25
25
  }
26
26
  </style>
27
27
 
28
- <div class="question">question_name: {{ question }}</div>
28
+ <div class="question">Question name: {{ question }}</div>
29
29
 
30
30
  {% for exception_message in exceptions %}
31
31
  <div class="exception-detail">
@@ -1,4 +1,3 @@
1
- <h2>Exceptions Details</h2>
2
1
  {% for index, interview in interviews.items() %}
3
2
  {% for question, exceptions in interview.exceptions.items() %}
4
3
  {% include 'interview_details.html' %}
@@ -4,16 +4,11 @@
4
4
  }
5
5
  </style>
6
6
 
7
- <h1>Exceptions Report</h1>
7
+ <h3>Exceptions Report</h3>
8
8
  <p>
9
9
  This report summarizes exceptions encountered in the job that was run.
10
10
  </p>
11
- <p>
12
- For advice on dealing with exceptions, please see the EDSL <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">documentation</a> page. <br>
13
- You can also post a question at the Expected Parrot <a href="https://discord.com/invite/mxAYkjfy9m">Discord channel</a>, open an issue on <a href="https://github.com/expectedparrot/edsl">GitHub</a>, or send an email to <a href="mailto:info@expectedparrot.com">info@expectedparrot.com</a>.
14
- </p>
15
11
 
16
- <h2>Overview</h2>
17
12
  <table border="1">
18
13
  <tbody>
19
14
  <tr>
@@ -27,5 +22,5 @@
27
22
  </tbody>
28
23
  </table>
29
24
  <p>
30
- An "interview" is the result of one survey, taken by one agent, with one model and one scenario (if any).
25
+ An interview is the result of one survey, taken by one agent, with one model and one scenario (if any).
31
26
  </p>
@@ -1,2 +1,2 @@
1
- <h2>Performance Plot</h2>
1
+ <!-- <h2>Performance Plot</h2> -->
2
2
  {{ performance_plot_html }}
@@ -15,7 +15,7 @@ body {
15
15
  }
16
16
 
17
17
  .question {
18
- font-size: 1.2em;
18
+ font-size: 1.0em;
19
19
  margin-bottom: 10px;
20
20
  padding: 10px;
21
21
  background-color: #fff9c4;
@@ -1,12 +1,26 @@
1
1
  from collections import UserList
2
2
  from edsl.results.Dataset import Dataset
3
3
 
4
+ class Markkdown:
5
+
6
+ def __init__(self, text: str):
7
+ self.text = text
8
+
9
+ def __str__(self):
10
+ return self.text
11
+
12
+ def _repr_markdown_(self):
13
+ return self.text
4
14
 
5
15
  class PrettyList(UserList):
6
16
  def __init__(self, data=None, columns=None):
7
17
  super().__init__(data)
8
18
  self.columns = columns
9
19
 
20
+ def to_markdown(self):
21
+ text = "".join([str(row) for row in self])
22
+ return Markkdown(text)
23
+
10
24
  def _repr_html_(self):
11
25
  if isinstance(self[0], list) or isinstance(self[0], tuple):
12
26
  num_cols = len(self[0])
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.1
2
+ Name: edsl
3
+ Version: 0.1.46
4
+ Summary: Create and analyze LLM-based surveys
5
+ Home-page: https://www.expectedparrot.com/
6
+ License: MIT
7
+ Keywords: LLM,social science,surveys,user research
8
+ Author: John Horton
9
+ Author-email: info@expectedparrot.com
10
+ Requires-Python: >=3.9.1,<3.13
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Dist: aiohttp (>=3.9.1,<4.0.0)
20
+ Requires-Dist: anthropic (>=0.45.0,<0.46.0)
21
+ Requires-Dist: azure-ai-inference (>=1.0.0b3,<2.0.0)
22
+ Requires-Dist: black[jupyter] (>=24.4.2,<25.0.0)
23
+ Requires-Dist: boto3 (>=1.34.161,<2.0.0)
24
+ Requires-Dist: google-generativeai (>=0.8.2,<0.9.0)
25
+ Requires-Dist: groq (>=0.9.0,<0.10.0)
26
+ Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
27
+ Requires-Dist: json-repair (>=0.28.4,<0.29.0)
28
+ Requires-Dist: jupyter (>=1.0.0,<2.0.0)
29
+ Requires-Dist: markdown2 (>=2.4.11,<3.0.0)
30
+ Requires-Dist: matplotlib (>=3.8,<3.9)
31
+ Requires-Dist: mistralai (>=1.0.2,<2.0.0)
32
+ Requires-Dist: nest-asyncio (>=1.5.9,<2.0.0)
33
+ Requires-Dist: numpy (>=1.22,<2.0)
34
+ Requires-Dist: openai (>=1.4.0,<2.0.0)
35
+ Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
36
+ Requires-Dist: pandas (>=2.1.4,<3.0.0)
37
+ Requires-Dist: platformdirs (>=4.3.6,<5.0.0)
38
+ Requires-Dist: pydot (>=2.0.0,<3.0.0)
39
+ Requires-Dist: pygments (>=2.17.2,<3.0.0)
40
+ Requires-Dist: pypdf2 (>=3.0.1,<4.0.0)
41
+ Requires-Dist: pyreadstat (>=1.2.7,<2.0.0)
42
+ Requires-Dist: python-docx (>=1.1.0,<2.0.0)
43
+ Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
44
+ Requires-Dist: python-pptx (>=1.0.2,<2.0.0)
45
+ Requires-Dist: restrictedpython (>=7.1,<8.0)
46
+ Requires-Dist: rich (>=13.7.0,<14.0.0)
47
+ Requires-Dist: setuptools (<72.0)
48
+ Requires-Dist: simpleeval (>=0.9.13,<0.10.0)
49
+ Requires-Dist: sqlalchemy (>=2.0.23,<3.0.0)
50
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
51
+ Requires-Dist: tenacity (>=8.2.3,<9.0.0)
52
+ Requires-Dist: urllib3 (>=1.25.4,<1.27)
53
+ Project-URL: Documentation, https://docs.expectedparrot.com
54
+ Description-Content-Type: text/markdown
55
+
56
+ # Expected Parrot Domain-Specific Language (EDSL)
57
+
58
+ The Expected Parrot Domain-Specific Language (EDSL) package makes it easy to conduct computational social science and market research with AI. Use it to design surveys and experiments, collect responses from humans and large language models, and perform data labeling and many other research tasks. Results are formatted as specified datasets and come with built-in methods for analyzing, visualizing and sharing.
59
+
60
+ <p align="right">
61
+ <img src="https://github.com/expectedparrot/edsl/blob/main/static/logo.png?raw=true" alt="edsl.png" width="100"/>
62
+ </p>
63
+
64
+ ## Features
65
+
66
+ **Declarative design**:
67
+ Specified <a href="https://docs.expectedparrot.con/en/latest/questions.html" target="_blank" rel="noopener noreferrer">question types</a> ensure consistent results without requiring a JSON schema (<a href="https://www.expectedparrot.com/content/2a848a0e-f9de-46bc-98d0-a13b9a1caf11" target="_blank" rel="noopener noreferrer">view at Coop</a>):
68
+
69
+ ```python
70
+ from edsl import QuestionMultipleChoice
71
+
72
+ q = QuestionMultipleChoice(
73
+ question_name = "example",
74
+ question_text = "How do you feel today?",
75
+ question_options = ["Bad", "OK", "Good"]
76
+ )
77
+
78
+ results = q.run()
79
+
80
+ results.select("example")
81
+ ```
82
+
83
+ > | answer.example |
84
+ > |-----------------|
85
+ > | Good |
86
+
87
+ <br>
88
+
89
+ **Parameterized prompts**:
90
+ Easily parameterize and control prompts with "<a href="https://docs.expectedparrot.com/en/latest/scenarios.html" target="_blank" rel="noopener noreferrer">scenarios</a>" of data automatically imported from many sources (CSV, PDF, PNG, etc.)(<a href="https://www.expectedparrot.com/content/7bb9ec2e-827b-4867-ac02-33163df1a1d1" target="_blank" rel="noopener noreferrer">view at Coop</a>):
91
+
92
+ ```python
93
+ from edsl import ScenarioList, QuestionLinearScale
94
+
95
+ q = QuestionLinearScale(
96
+ question_name = "example",
97
+ question_text = "How much do you enjoy {{ activity }}?",
98
+ question_options = [1,2,3,4,5,],
99
+ option_labels = {1:"Not at all", 5:"Very much"}
100
+ )
101
+
102
+ sl = ScenarioList.from_list("activity", ["coding", "sleeping"])
103
+
104
+ results = q.by(sl).run()
105
+
106
+ results.select("activity", "example")
107
+ ```
108
+
109
+ > | scenario.activity | answer.example |
110
+ > |--------------------|-----------------|
111
+ > | Coding | 5 |
112
+ > | Sleeping | 5 |
113
+
114
+ <br>
115
+
116
+ **Design AI agent personas to answer questions**:
117
+ Construct agents with relevant traits to provide diverse responses to your surveys (<a href="https://www.expectedparrot.com/content/b639a2d7-4ae6-48fe-8b9e-58350fab93de" target="_blank" rel="noopener noreferrer">view at Coop</a>)
118
+
119
+ ```python
120
+ from edsl import Agent, AgentList, QuestionList
121
+
122
+ al = AgentList(Agent(traits = {"persona":p}) for p in ["botanist", "detective"])
123
+
124
+ q = QuestionList(
125
+ question_name = "example",
126
+ question_text = "What are your favorite colors?",
127
+ max_list_items = 3
128
+ )
129
+
130
+ results = q.by(al).run()
131
+
132
+ results.select("persona", "example")
133
+ ```
134
+
135
+ > | agent.persona | answer.example |
136
+ > |----------------|---------------------------------------------|
137
+ > | botanist | ['Green', 'Earthy Brown', 'Sunset Orange'] |
138
+ > | detective | ['Gray', 'Black', 'Navy Blye'] |
139
+
140
+ <br>
141
+
142
+ **Simplified access to LLMs**:
143
+ Choose whether to use your own keys for LLMs, or access all <a href="https://www.expectedparrot.com/getting-started/coop-pricing" target="_blank" rel="noopener noreferrer">available models</a> with an Expected Parrot API key. Run surveys with many models at once and compare responses at a convenient inferface (<a href="https://www.expectedparrot.com/content/044465f0-b87f-430d-a3b9-4fd3b8560299" target="_blank" rel="noopener noreferrer">view at Coop</a>)
144
+
145
+ ```python
146
+ from edsl import Model, ModelList, QuestionFreeText
147
+
148
+ ml = ModelList(Model(m) for m in ["gpt-4o", "gemini-1.5-flash"])
149
+
150
+ q = QuestionFreeText(
151
+ question_name = "example",
152
+ question_text = "What is your top tip for using LLMs to answer surveys?"
153
+ )
154
+
155
+ results = q.by(ml).run()
156
+
157
+ results.select("model", "example")
158
+ ```
159
+
160
+ > | model.model | answer.example |
161
+ > |--------------------|-------------------------------------------------------------------------------------------------|
162
+ > | gpt-4o | When using large language models (LLMs) to answer surveys, my top tip is to ensure that the ... |
163
+ > | gemini-1.5-flash | My top tip for using LLMs to answer surveys is to **treat the LLM as a sophisticated brainst... |
164
+
165
+ <br>
166
+
167
+ **Piping & skip-logic**:
168
+ Build rich data labeling flows with features for piping answers and adding survey logic such as skip and stop rules (<a href="https://www.expectedparrot.com/content/b8afe09d-49bf-4c05-b753-d7b0ae782eb3" target="_blank" rel="noopener noreferrer">view at Coop</a>):
169
+
170
+ ```python
171
+ from edsl import QuestionMultipleChoice, QuestionFreeText, Survey
172
+
173
+ q1 = QuestionMultipleChoice(
174
+ question_name = "color",
175
+ question_text = "What is your favorite primary color?",
176
+ question_options = ["red", "yellow", "blue"]
177
+ )
178
+
179
+ q2 = QuestionFreeText(
180
+ question_name = "flower",
181
+ question_text = "Name a flower that is {{ color.answer }}."
182
+ )
183
+
184
+ survey = Survey(questions = [q1, q2])
185
+
186
+ results = survey.run()
187
+
188
+ results.select("color", "flower")
189
+ ```
190
+
191
+ > | answer.color | answer.flower |
192
+ > |---------------|-----------------------------------------------------------------------------------|
193
+ > | blue | A commonly known blue flower is the bluebell. Another example is the cornflower. |
194
+
195
+ <br>
196
+
197
+ **Caching**:
198
+ API calls to LLMs are cached automatically, allowing you to retrieve responses to questions that have already been run and reproduce experiments at no cost. Learn more about how the <a href="https://docs.expectedparrot.com/en/latest/remote_caching.html" target="_blank" rel="noopener noreferrer">universal remote cache</a> works.
199
+
200
+ **Flexibility**:
201
+ Choose whether to run surveys on your own computer or at the Expected Parrot server.
202
+
203
+ **Tools for collaboration**:
204
+ Easily share workflows and projects privately or publicly at Coop: an integrated platform for AI-based research. Your account comes with free credits for running surveys, and lets you securely share keys, track expenses and usage for your team.
205
+
206
+ **Built-in tools for analyis**:
207
+ Analyze results as specified datasets from your account or workspace. Easily import data to use with your surveys and export results.
208
+
209
+ ## Getting started
210
+
211
+ 1. Run `pip install edsl` to install the package.
212
+
213
+ 2. <a href="https://www.expectedparrot.com/login" target="_blank" rel="noopener noreferrer">Create an account</a> to run surveys at the Expected Parrot server and access a <a href="https://docs.expectedparrot.com/en/latest/remote_caching.html" target="_blank" rel="noopener noreferrer">universal remote cache</a> of stored responses for reproducing results.
214
+
215
+ 3. Choose whether to use your own keys for language models or get an Expected Parrot key to access all available models at once. Securely <a href="https://docs.expectedparrot.com/en/latest/api_keys.html" target="_blank" rel="noopener noreferrer">manage keys</a>, share expenses and track usage for your team from your account.
216
+
217
+ 4. Run the <a href="https://docs.expectedparrot.com/en/latest/starter_tutorial.html" target="_blank" rel="noopener noreferrer">starter tutorial</a> and explore other demo notebooks.
218
+
219
+ 5. Share workflows and survey results at <a href="https://www.expectedparrot.com/content/explore" target="_blank" rel="noopener noreferrer">Coop</a>
220
+
221
+ 6. Join our <a href="https://discord.com/invite/mxAYkjfy9m" target="_blank" rel="noopener noreferrer">Discord</a> for updates and discussions! Request new features!
222
+
223
+ ## Code & Docs
224
+ - <a href="https://pypi.org/project/edsl/" target="_blank" rel="noopener noreferrer">PyPI</a>
225
+ - <a href="https://github.com/expectedparrot/edsl" target="_blank" rel="noopener noreferrer">GitHub</a>
226
+ - <a href="https://docs.expectedparrot.com" target="_blank" rel="noopener noreferrer">Documentation</a>
227
+
228
+ ## Requirements
229
+ - Python 3.9 - 3.12
230
+ - API keys for language models. You can use your own keys or an Expected Parrot key that provides access to all available models.
231
+ See instructions on <a href="https://docs.expectedparrot.com/en/latest/api_keys.html" target="_blank" rel="noopener noreferrer">managing keys</a> and <a href="https://www.expectedparrot.com/getting-started/coop-pricing" target="_blank" rel="noopener noreferrer">model pricing and performance</a> information.
232
+
233
+ ## Coop
234
+ An integrated platform for running experiments, sharing workflows and launching hybrid human/AI surveys.
235
+ - <a href="https://www.expectedparrot.com/login" target="_blank" rel="noopener noreferrer">Login / Signup</a>
236
+ - <a href="https://www.expectedparrot.com/content/explore" target="_blank" rel="noopener noreferrer">Explore</a>
237
+
238
+ ## Community
239
+ - <a href="https://discord.com/invite/mxAYkjfy9m" target="_blank" rel="noopener noreferrer">Discord</a>
240
+ - <a href="https://x.com/ExpectedParrot" target="_blank" rel="noopener noreferrer">Twitter</a>
241
+ - <a href="https://www.linkedin.com/company/expectedparrot/" target="_blank" rel="noopener noreferrer">LinkedIn</a>
242
+ - <a href="https://blog.expectedparrot.com" target="_blank" rel="noopener noreferrer">Blog</a>
243
+
244
+ ## Contact
245
+ - <a href="mailto:info@expectedparrot.com" target="_blank" rel="noopener noreferrer">Email</a>
246
+