edsl 0.1.33.dev2__py3-none-any.whl → 0.1.34__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 (78) hide show
  1. edsl/Base.py +24 -14
  2. edsl/__init__.py +1 -0
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +6 -6
  5. edsl/agents/Invigilator.py +28 -6
  6. edsl/agents/InvigilatorBase.py +8 -27
  7. edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +150 -182
  8. edsl/agents/prompt_helpers.py +129 -0
  9. edsl/config.py +26 -34
  10. edsl/coop/coop.py +14 -4
  11. edsl/data_transfer_models.py +26 -73
  12. edsl/enums.py +2 -0
  13. edsl/inference_services/AnthropicService.py +5 -2
  14. edsl/inference_services/AwsBedrock.py +5 -2
  15. edsl/inference_services/AzureAI.py +5 -2
  16. edsl/inference_services/GoogleService.py +108 -33
  17. edsl/inference_services/InferenceServiceABC.py +44 -13
  18. edsl/inference_services/MistralAIService.py +5 -2
  19. edsl/inference_services/OpenAIService.py +10 -6
  20. edsl/inference_services/TestService.py +34 -16
  21. edsl/inference_services/TogetherAIService.py +170 -0
  22. edsl/inference_services/registry.py +2 -0
  23. edsl/jobs/Jobs.py +109 -18
  24. edsl/jobs/buckets/BucketCollection.py +24 -15
  25. edsl/jobs/buckets/TokenBucket.py +64 -10
  26. edsl/jobs/interviews/Interview.py +130 -49
  27. edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +16 -0
  28. edsl/jobs/interviews/InterviewExceptionEntry.py +2 -0
  29. edsl/jobs/runners/JobsRunnerAsyncio.py +119 -173
  30. edsl/jobs/runners/JobsRunnerStatus.py +332 -0
  31. edsl/jobs/tasks/QuestionTaskCreator.py +1 -13
  32. edsl/jobs/tasks/TaskHistory.py +17 -0
  33. edsl/language_models/LanguageModel.py +36 -38
  34. edsl/language_models/registry.py +13 -9
  35. edsl/language_models/utilities.py +5 -2
  36. edsl/questions/QuestionBase.py +74 -16
  37. edsl/questions/QuestionBaseGenMixin.py +28 -0
  38. edsl/questions/QuestionBudget.py +93 -41
  39. edsl/questions/QuestionCheckBox.py +1 -1
  40. edsl/questions/QuestionFreeText.py +6 -0
  41. edsl/questions/QuestionMultipleChoice.py +13 -24
  42. edsl/questions/QuestionNumerical.py +5 -4
  43. edsl/questions/Quick.py +41 -0
  44. edsl/questions/ResponseValidatorABC.py +11 -6
  45. edsl/questions/derived/QuestionLinearScale.py +4 -1
  46. edsl/questions/derived/QuestionTopK.py +4 -1
  47. edsl/questions/derived/QuestionYesNo.py +8 -2
  48. edsl/questions/descriptors.py +12 -11
  49. edsl/questions/templates/budget/__init__.py +0 -0
  50. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  51. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  52. edsl/questions/templates/extract/__init__.py +0 -0
  53. edsl/questions/templates/numerical/answering_instructions.jinja +0 -1
  54. edsl/questions/templates/rank/__init__.py +0 -0
  55. edsl/questions/templates/yes_no/answering_instructions.jinja +2 -2
  56. edsl/results/DatasetExportMixin.py +5 -1
  57. edsl/results/Result.py +1 -1
  58. edsl/results/Results.py +4 -1
  59. edsl/scenarios/FileStore.py +178 -34
  60. edsl/scenarios/Scenario.py +76 -37
  61. edsl/scenarios/ScenarioList.py +19 -2
  62. edsl/scenarios/ScenarioListPdfMixin.py +150 -4
  63. edsl/study/Study.py +32 -0
  64. edsl/surveys/DAG.py +62 -0
  65. edsl/surveys/MemoryPlan.py +26 -0
  66. edsl/surveys/Rule.py +34 -1
  67. edsl/surveys/RuleCollection.py +55 -5
  68. edsl/surveys/Survey.py +189 -10
  69. edsl/surveys/base.py +4 -0
  70. edsl/templates/error_reporting/interview_details.html +6 -1
  71. edsl/utilities/utilities.py +9 -1
  72. {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/METADATA +3 -1
  73. {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/RECORD +75 -69
  74. edsl/jobs/interviews/retry_management.py +0 -39
  75. edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
  76. edsl/scenarios/ScenarioImageMixin.py +0 -100
  77. {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/LICENSE +0 -0
  78. {edsl-0.1.33.dev2.dist-info → edsl-0.1.34.dist-info}/WHEEL +0 -0
@@ -44,6 +44,13 @@ class QuestionBase(
44
44
  _answering_instructions = None
45
45
  _question_presentation = None
46
46
 
47
+ @property
48
+ def response_model(self) -> type["BaseModel"]:
49
+ if self._response_model is not None:
50
+ return self._response_model
51
+ else:
52
+ return self.create_response_model()
53
+
47
54
  # region: Validation and simulation methods
48
55
  @property
49
56
  def response_validator(self) -> "ResponseValidatorBase":
@@ -99,20 +106,17 @@ class QuestionBase(
99
106
  comment: Optional[str]
100
107
  generated_tokens: Optional[str]
101
108
 
102
- def _validate_answer(self, answer: dict) -> ValidatedAnswer:
109
+ def _validate_answer(
110
+ self, answer: dict, replacement_dict: dict = None
111
+ ) -> ValidatedAnswer:
103
112
  """Validate the answer.
104
113
  >>> from edsl.exceptions import QuestionAnswerValidationError
105
114
  >>> from edsl import QuestionFreeText as Q
106
- >>> Q.example()._validate_answer({'answer': 'Hello'})
107
- {'answer': 'Hello', 'generated_tokens': None}
108
- >>> Q.example()._validate_answer({'shmanswer': 1})
109
- Traceback (most recent call last):
110
- ...
111
- edsl.exceptions.questions.QuestionAnswerValidationError:...
112
- ...
115
+ >>> Q.example()._validate_answer({'answer': 'Hello', 'generated_tokens': 'Hello'})
116
+ {'answer': 'Hello', 'generated_tokens': 'Hello'}
113
117
  """
114
118
 
115
- return self.response_validator.validate(answer)
119
+ return self.response_validator.validate(answer, replacement_dict)
116
120
 
117
121
  # endregion
118
122
 
@@ -471,6 +475,7 @@ class QuestionBase(
471
475
  self,
472
476
  scenario: Optional[dict] = None,
473
477
  agent: Optional[dict] = {},
478
+ answers: Optional[dict] = None,
474
479
  include_question_name: bool = False,
475
480
  height: Optional[int] = None,
476
481
  width: Optional[int] = None,
@@ -482,6 +487,17 @@ class QuestionBase(
482
487
  if scenario is None:
483
488
  scenario = {}
484
489
 
490
+ prior_answers_dict = {}
491
+
492
+ if isinstance(answers, dict):
493
+ for key, value in answers.items():
494
+ if not key.endswith("_comment") and not key.endswith(
495
+ "_generated_tokens"
496
+ ):
497
+ prior_answers_dict[key] = {"answer": value}
498
+
499
+ # breakpoint()
500
+
485
501
  base_template = """
486
502
  <div id="{{ question_name }}" class="survey_question" data-type="{{ question_type }}">
487
503
  {% if include_question_name %}
@@ -501,13 +517,40 @@ class QuestionBase(
501
517
 
502
518
  base_template = Template(base_template)
503
519
 
504
- params = {
505
- "question_name": self.question_name,
506
- "question_text": Template(self.question_text).render(scenario, agent=agent),
507
- "question_type": self.question_type,
508
- "question_content": Template(question_content).render(scenario),
509
- "include_question_name": include_question_name,
510
- }
520
+ context = {
521
+ "scenario": scenario,
522
+ "agent": agent,
523
+ } | prior_answers_dict
524
+
525
+ # Render the question text
526
+ try:
527
+ question_text = Template(self.question_text).render(context)
528
+ except Exception as e:
529
+ print(
530
+ f"Error rendering question: question_text = {self.question_text}, error = {e}"
531
+ )
532
+ question_text = self.question_text
533
+
534
+ try:
535
+ question_content = Template(question_content).render(context)
536
+ except Exception as e:
537
+ print(
538
+ f"Error rendering question: question_content = {question_content}, error = {e}"
539
+ )
540
+ question_content = question_content
541
+
542
+ try:
543
+ params = {
544
+ "question_name": self.question_name,
545
+ "question_text": question_text,
546
+ "question_type": self.question_type,
547
+ "question_content": question_content,
548
+ "include_question_name": include_question_name,
549
+ }
550
+ except Exception as e:
551
+ raise ValueError(
552
+ f"Error rendering question: params = {params}, error = {e}"
553
+ )
511
554
  rendered_html = base_template.render(**params)
512
555
 
513
556
  if iframe:
@@ -526,6 +569,21 @@ class QuestionBase(
526
569
 
527
570
  return rendered_html
528
571
 
572
+ @classmethod
573
+ def example_model(cls):
574
+ from edsl import Model
575
+
576
+ q = cls.example()
577
+ m = Model("test", canned_response=cls._simulate_answer(q)["answer"])
578
+
579
+ return m
580
+
581
+ @classmethod
582
+ def example_results(cls):
583
+ m = cls.example_model()
584
+ q = cls.example()
585
+ return q.by(m).run(cache=False)
586
+
529
587
  def rich_print(self):
530
588
  """Print the question in a rich format."""
531
589
  from rich.table import Table
@@ -95,6 +95,34 @@ class QuestionBaseGenMixin:
95
95
  questions.append(QuestionBase.from_dict(new_data))
96
96
  return questions
97
97
 
98
+ def render(self, replacement_dict: dict) -> "QuestionBase":
99
+ """Render the question components as jinja2 templates with the replacement dictionary."""
100
+ from jinja2 import Environment
101
+ from edsl import Scenario
102
+
103
+ strings_only_replacement_dict = {
104
+ k: v for k, v in replacement_dict.items() if not isinstance(v, Scenario)
105
+ }
106
+
107
+ def render_string(value: str) -> str:
108
+ if value is None or not isinstance(value, str):
109
+ return value
110
+ else:
111
+ try:
112
+ return (
113
+ Environment()
114
+ .from_string(value)
115
+ .render(strings_only_replacement_dict)
116
+ )
117
+ except Exception as e:
118
+ import warnings
119
+
120
+ warnings.warn("Failed to render string: " + value)
121
+ # breakpoint()
122
+ return value
123
+
124
+ return self.apply_function(render_string)
125
+
98
126
  def apply_function(self, func: Callable, exclude_components=None) -> QuestionBase:
99
127
  """Apply a function to the question parts
100
128
 
@@ -1,8 +1,60 @@
1
1
  from __future__ import annotations
2
- import random
3
- from typing import Any, Optional, Union
2
+ from typing import Any, Optional, Union, List
3
+
4
+ from pydantic import Field, BaseModel, validator
5
+
4
6
  from edsl.questions.QuestionBase import QuestionBase
5
7
  from edsl.questions.descriptors import IntegerDescriptor, QuestionOptionsDescriptor
8
+ from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
9
+
10
+
11
+ class BudgewResponseValidator(ResponseValidatorABC):
12
+ valid_examples = []
13
+
14
+ invalid_examples = []
15
+
16
+ def fix(self, response, verbose=False):
17
+ if verbose:
18
+ print(f"Fixing list response: {response}")
19
+ answer = str(response.get("answer") or response.get("generated_tokens", ""))
20
+ if len(answer.split(",")) > 0:
21
+ return (
22
+ {"answer": answer.split(",")} | {"comment": response.get("comment")}
23
+ if "comment" in response
24
+ else {}
25
+ )
26
+
27
+
28
+ def create_budget_model(
29
+ budget_sum: float, permissive: bool, question_options: List[str]
30
+ ):
31
+ class BudgetResponse(BaseModel):
32
+ answer: List[float] = Field(
33
+ ...,
34
+ description="List of non-negative numbers representing budget allocation",
35
+ min_items=len(question_options),
36
+ max_items=len(question_options),
37
+ )
38
+ comment: Optional[str] = None
39
+ generated_tokens: Optional[str] = None
40
+
41
+ @validator("answer")
42
+ def validate_answer(cls, v):
43
+ if len(v) != len(question_options):
44
+ raise ValueError(f"Must provide {len(question_options)} values")
45
+ if any(x < 0 for x in v):
46
+ raise ValueError("All values must be non-negative")
47
+ total = sum(v)
48
+ if not permissive and total != budget_sum:
49
+ raise ValueError(f"Sum of numbers must equal {budget_sum}")
50
+ elif permissive and total > budget_sum:
51
+ raise ValueError(f"Sum of numbers cannot exceed {budget_sum}")
52
+ return v
53
+
54
+ class Config:
55
+ extra = "forbid"
56
+
57
+ return BudgetResponse
6
58
 
7
59
 
8
60
  class QuestionBudget(QuestionBase):
@@ -12,7 +64,7 @@ class QuestionBudget(QuestionBase):
12
64
  budget_sum: int = IntegerDescriptor(none_allowed=False)
13
65
  question_options: list[str] = QuestionOptionsDescriptor(q_budget=True)
14
66
  _response_model = None
15
- response_validator_class = None
67
+ response_validator_class = BudgewResponseValidator
16
68
 
17
69
  def __init__(
18
70
  self,
@@ -20,8 +72,10 @@ class QuestionBudget(QuestionBase):
20
72
  question_text: str,
21
73
  question_options: list[str],
22
74
  budget_sum: int,
75
+ include_comment: bool = True,
23
76
  question_presentation: Optional[str] = None,
24
77
  answering_instructions: Optional[str] = None,
78
+ permissive: bool = False,
25
79
  ):
26
80
  """Instantiate a new QuestionBudget.
27
81
 
@@ -36,20 +90,17 @@ class QuestionBudget(QuestionBase):
36
90
  self.budget_sum = budget_sum
37
91
  self.question_presentation = question_presentation
38
92
  self.answering_instructions = answering_instructions
93
+ self.permissive = permissive
94
+ self.include_comment = include_comment
39
95
 
40
- ################
41
- # Answer methods
42
- ################
43
- def _validate_answer(self, answer: dict[str, Any]) -> dict[str, Union[int, str]]:
44
- """Validate the answer."""
45
- self._validate_answer_template_basic(answer)
46
- self._validate_answer_key_value(answer, "answer", dict)
47
- self._validate_answer_budget(answer)
48
- return answer
96
+ def create_response_model(self):
97
+ return create_budget_model(
98
+ self.budget_sum, self.permissive, self.question_options
99
+ )
49
100
 
50
101
  def _translate_answer_code_to_answer(
51
- self, answer_codes: dict[str, int], scenario: "Scenario" = None
52
- ):
102
+ self, answer_code, combined_dict
103
+ ) -> list[dict]:
53
104
  """
54
105
  Translate the answer codes to the actual answers.
55
106
 
@@ -58,35 +109,35 @@ class QuestionBudget(QuestionBase):
58
109
  This code will translate that to "a".
59
110
  """
60
111
  translated_codes = []
61
- for answer_code, response in answer_codes.items():
62
- translated_codes.append({self.question_options[int(answer_code)]: response})
112
+ for answer_code, question_option in zip(answer_code, self.question_options):
113
+ translated_codes.append({question_option: answer_code})
63
114
 
64
115
  return translated_codes
65
116
 
66
- def _simulate_answer(self, human_readable=True):
67
- """Simulate a valid answer for debugging purposes (what the validator expects)."""
68
- from edsl.utilities.utilities import random_string
69
-
70
- if human_readable:
71
- keys = self.question_options
72
- else:
73
- keys = range(len(self.question_options))
74
- remaining_budget = self.budget_sum
75
- values = []
76
- for _ in range(len(self.question_options)):
77
- if _ == len(self.question_options) - 1:
78
- # Assign remaining budget to the last value
79
- values.append(remaining_budget)
80
- else:
81
- # Generate a random value between 0 and remaining budget
82
- value = random.randint(0, remaining_budget)
83
- values.append(value)
84
- remaining_budget -= value
85
- answer = dict(zip(keys, values))
86
- return {
87
- "answer": answer,
88
- "comment": random_string(),
89
- }
117
+ # def _simulate_answer(self, human_readable=True):
118
+ # """Simulate a valid answer for debugging purposes (what the validator expects)."""
119
+ # from edsl.utilities.utilities import random_string
120
+
121
+ # if human_readable:
122
+ # keys = self.question_options
123
+ # else:
124
+ # keys = range(len(self.question_options))
125
+ # remaining_budget = self.budget_sum
126
+ # values = []
127
+ # for _ in range(len(self.question_options)):
128
+ # if _ == len(self.question_options) - 1:
129
+ # # Assign remaining budget to the last value
130
+ # values.append(remaining_budget)
131
+ # else:
132
+ # # Generate a random value between 0 and remaining budget
133
+ # value = random.randint(0, remaining_budget)
134
+ # values.append(value)
135
+ # remaining_budget -= value
136
+ # answer = dict(zip(keys, values))
137
+ # return {
138
+ # "answer": answer,
139
+ # "comment": random_string(),
140
+ # }
90
141
 
91
142
  @property
92
143
  def question_html_content(self) -> str:
@@ -133,13 +184,14 @@ class QuestionBudget(QuestionBase):
133
184
  # Helpful methods
134
185
  ################
135
186
  @classmethod
136
- def example(cls) -> QuestionBudget:
187
+ def example(cls, include_comment: bool = True) -> QuestionBudget:
137
188
  """Return an example of a budget question."""
138
189
  return cls(
139
190
  question_name="food_budget",
140
191
  question_text="How would you allocate $100?",
141
192
  question_options=["Pizza", "Ice Cream", "Burgers", "Salad"],
142
193
  budget_sum=100,
194
+ include_comment=include_comment,
143
195
  )
144
196
 
145
197
 
@@ -245,7 +245,7 @@ class QuestionCheckBox(QuestionBase):
245
245
 
246
246
  scenario = scenario or Scenario()
247
247
  translated_options = [
248
- Template(option).render(scenario) for option in self.question_options
248
+ Template(str(option)).render(scenario) for option in self.question_options
249
249
  ]
250
250
  translated_codes = []
251
251
  for answer_code in answer_codes:
@@ -36,6 +36,12 @@ class FreeTextResponseValidator(ResponseValidatorABC):
36
36
  ),
37
37
  ]
38
38
 
39
+ def fix(self, response, verbose=False):
40
+ return {
41
+ "answer": str(response.get("generated_tokens")),
42
+ "generated_tokens": str(response.get("generated_tokens")),
43
+ }
44
+
39
45
 
40
46
  class QuestionFreeText(QuestionBase):
41
47
  """This question prompts the agent to respond with free text."""
@@ -1,22 +1,14 @@
1
1
  from __future__ import annotations
2
- from typing import Union, Literal, Optional
3
- from jinja2 import Template
2
+ from typing import Union, Literal, Optional, List, Any
4
3
 
4
+ from jinja2 import Template
5
5
  from pydantic import BaseModel, Field
6
- from typing import Optional, Literal
7
6
 
7
+ from edsl.scenarios.Scenario import Scenario
8
8
  from edsl.questions.QuestionBase import QuestionBase
9
9
  from edsl.questions.descriptors import QuestionOptionsDescriptor
10
10
  from edsl.questions.decorators import inject_exception
11
-
12
11
  from edsl.questions.ResponseValidatorABC import ResponseValidatorABC
13
- from edsl.questions.ResponseValidatorABC import BaseResponse
14
-
15
- from edsl.exceptions import QuestionAnswerValidationError
16
-
17
- from pydantic import BaseModel, Field, create_model
18
-
19
- from typing import List, Any, Literal
20
12
 
21
13
 
22
14
  def create_response_model(choices: List[str], permissive: bool = False):
@@ -27,7 +19,6 @@ def create_response_model(choices: List[str], permissive: bool = False):
27
19
  :param permissive: If True, any value will be accepted as an answer.
28
20
  :return: A new Pydantic model class.
29
21
  """
30
- # Convert the choices list to a tuple for use with Literal
31
22
  choice_tuple = tuple(choices)
32
23
 
33
24
  if not permissive:
@@ -66,16 +57,6 @@ def create_response_model(choices: List[str], permissive: bool = False):
66
57
  return ChoiceResponse
67
58
 
68
59
 
69
- def fix_multiple_choice(response, question_options, use_code, verbose=False):
70
- """Fix the response to a multiple choice question.
71
- Respnse is a dictionary with keys:
72
- - answer: the answer code
73
- - generated_tokens: the generated tokens
74
- - comment: the comment
75
- """
76
- pass
77
-
78
-
79
60
  class MultipleChoiceResponseValidator(ResponseValidatorABC):
80
61
  required_params = ["question_options", "use_code"]
81
62
 
@@ -161,6 +142,11 @@ class QuestionMultipleChoice(QuestionBase):
161
142
  :param question_name: The name of the question.
162
143
  :param question_text: The text of the question.
163
144
  :param question_options: The options the agent should select from.
145
+ :param include_comment: Whether to include a comment field.
146
+ :param use_code: Whether to use code for the options.
147
+ :param answering_instructions: Instructions for the question.
148
+ :param question_presentation: The presentation of the question.
149
+ :param permissive: Whether to force the answer to be one of the options.
164
150
 
165
151
  """
166
152
  self.question_name = question_name
@@ -177,7 +163,11 @@ class QuestionMultipleChoice(QuestionBase):
177
163
  # Answer methods
178
164
  ################
179
165
 
180
- def create_response_model(self):
166
+ def create_response_model(self, replacement_dict: dict = None):
167
+ if replacement_dict is None:
168
+ replacement_dict = {}
169
+ # The replacement dict that could be from scenario, current answers, etc. to populate the response model
170
+
181
171
  if self.use_code:
182
172
  return create_response_model(
183
173
  list(range(len(self.question_options))), self.permissive
@@ -202,7 +192,6 @@ class QuestionMultipleChoice(QuestionBase):
202
192
  'Happy'
203
193
 
204
194
  """
205
- from edsl.scenarios.Scenario import Scenario
206
195
 
207
196
  scenario = scenario or Scenario()
208
197
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
- from decimal import Decimal
2
+
3
+ # from decimal import Decimal
3
4
  from random import uniform
4
5
  from typing import Any, Optional, Union, Literal
5
6
 
@@ -14,8 +15,8 @@ from edsl.exceptions.questions import QuestionAnswerValidationError
14
15
 
15
16
 
16
17
  def create_numeric_response(
17
- min_value: Optional[Decimal] = None,
18
- max_value: Optional[Decimal] = None,
18
+ min_value: Optional[float] = None,
19
+ max_value: Optional[float] = None,
19
20
  permissive=False,
20
21
  ):
21
22
  field_kwargs = {}
@@ -27,7 +28,7 @@ def create_numeric_response(
27
28
  field_kwargs["le"] = max_value
28
29
 
29
30
  class ConstrainedNumericResponse(BaseModel):
30
- answer: Union[Decimal] = Field(**field_kwargs)
31
+ answer: Union[int, float] = Field(**field_kwargs)
31
32
  comment: Optional[str] = Field(None)
32
33
  generated_tokens: Optional[Any] = Field(None)
33
34
 
@@ -0,0 +1,41 @@
1
+ from edsl import (
2
+ QuestionFreeText,
3
+ QuestionMultipleChoice,
4
+ Survey,
5
+ QuestionList,
6
+ Question,
7
+ )
8
+
9
+
10
+ def Quick(question_text):
11
+ q_type = QuestionMultipleChoice(
12
+ question_text=f"A researcher is asking a language model this: {question_text}. What is the most appropriate type of question to ask?",
13
+ question_name="potential_question_type",
14
+ question_options=["multiple_choice", "list", "free_text"],
15
+ )
16
+
17
+ q_name = QuestionFreeText(
18
+ question_text=f"A researcher is asking a language model this: {question_text}. What is a good name for this question that's a valid python identifier? Just return the proposed identifer",
19
+ question_name="potential_question_name",
20
+ )
21
+
22
+ q_options = QuestionList(
23
+ question_text=f"A research is asking this question: { question_text }. What are the possible options for this question?",
24
+ question_name="potential_question_options",
25
+ )
26
+
27
+ survey = Survey([q_type, q_name, q_options]).add_skip_rule(
28
+ q_options, "{{ potential_question_type }} != 'multiple_choice'"
29
+ )
30
+ return survey
31
+ # results = survey.run()
32
+ # question_type = results.select("potential_question_type").first()
33
+ # question_options = results.select("potential_question_options").first()
34
+ # question_name = results.select("potential_question_name").first()
35
+ # print("Question Type: ", question_type)
36
+ # print("Question Name: ", question_name)
37
+ # print("Question Options: ", question_options)
38
+ # if question_options == None:
39
+ # return Question(question_type, question_name = question_name)
40
+ # else:
41
+ # return Question(question_type, question_name = question_name, question_options = question_options)
@@ -1,6 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from pydantic import BaseModel, Field, field_validator
3
- from decimal import Decimal
3
+
4
+ # from decimal import Decimal
4
5
  from typing import Optional, Any, List, TypedDict
5
6
 
6
7
  from edsl.exceptions import QuestionAnswerValidationError
@@ -64,7 +65,7 @@ class ResponseValidatorABC(ABC):
64
65
  >>> rv = ResponseValidatorABC.example()
65
66
  >>> rv.override_answer = {"answer": 42}
66
67
  >>> rv.validate({"answer": 23})
67
- {'answer': Decimal('42'), 'comment': None, 'generated_tokens': None}
68
+ {'answer': 42, 'comment': None, 'generated_tokens': None}
68
69
  """
69
70
  if self.exception_to_throw:
70
71
  raise self.exception_to_throw
@@ -75,7 +76,7 @@ class ResponseValidatorABC(ABC):
75
76
 
76
77
  >>> rv = ResponseValidatorABC.example("numerical")
77
78
  >>> rv._base_validate({"answer": 42})
78
- ConstrainedNumericResponse(answer=Decimal('42'), comment=None, generated_tokens=None)
79
+ ConstrainedNumericResponse(answer=42, comment=None, generated_tokens=None)
79
80
  """
80
81
  try:
81
82
  return self.response_model(**data)
@@ -91,13 +92,17 @@ class ResponseValidatorABC(ABC):
91
92
  generated_tokens: Optional[str]
92
93
 
93
94
  def validate(
94
- self, raw_edsl_answer_dict: RawEdslAnswerDict, fix=False, verbose=False
95
+ self,
96
+ raw_edsl_answer_dict: RawEdslAnswerDict,
97
+ fix=False,
98
+ verbose=False,
99
+ replacement_dict: dict = None,
95
100
  ) -> EdslAnswerDict:
96
101
  """This is the main validation function.
97
102
 
98
103
  >>> rv = ResponseValidatorABC.example("numerical")
99
104
  >>> rv.validate({"answer": 42})
100
- {'answer': Decimal('42'), 'comment': None, 'generated_tokens': None}
105
+ {'answer': 42, 'comment': None, 'generated_tokens': None}
101
106
  >>> rv.max_value
102
107
  86.7
103
108
  >>> rv.validate({"answer": "120"})
@@ -109,7 +114,7 @@ class ResponseValidatorABC(ABC):
109
114
  >>> q.permissive = True
110
115
  >>> rv = q.response_validator
111
116
  >>> rv.validate({"answer": "120"})
112
- {'answer': Decimal('120'), 'comment': None, 'generated_tokens': None}
117
+ {'answer': 120, 'comment': None, 'generated_tokens': None}
113
118
  >>> rv.validate({"answer": "poo"})
114
119
  Traceback (most recent call last):
115
120
  ...
@@ -22,6 +22,7 @@ class QuestionLinearScale(QuestionMultipleChoice):
22
22
  option_labels: Optional[dict[int, str]] = None,
23
23
  answering_instructions: Optional[str] = None,
24
24
  question_presentation: Optional[str] = None,
25
+ include_comment: Optional[bool] = True,
25
26
  ):
26
27
  """Instantiate a new QuestionLinearScale.
27
28
 
@@ -36,6 +37,7 @@ class QuestionLinearScale(QuestionMultipleChoice):
36
37
  question_text=question_text,
37
38
  question_options=question_options,
38
39
  use_code=False, # question linear scale will have it's own code
40
+ include_comment=include_comment,
39
41
  )
40
42
  self.question_options = question_options
41
43
  self.option_labels = (
@@ -49,13 +51,14 @@ class QuestionLinearScale(QuestionMultipleChoice):
49
51
  ################
50
52
  @classmethod
51
53
  @inject_exception
52
- def example(cls) -> QuestionLinearScale:
54
+ def example(cls, include_comment: bool = True) -> QuestionLinearScale:
53
55
  """Return an example of a linear scale question."""
54
56
  return cls(
55
57
  question_text="How much do you like ice cream?",
56
58
  question_options=[1, 2, 3, 4, 5],
57
59
  question_name="ice_cream",
58
60
  option_labels={1: "I hate it", 5: "I love it"},
61
+ include_comment=include_comment,
59
62
  )
60
63
 
61
64
 
@@ -20,6 +20,7 @@ class QuestionTopK(QuestionCheckBox):
20
20
  max_selections: int,
21
21
  question_presentation: Optional[str] = None,
22
22
  answering_instructions: Optional[str] = None,
23
+ include_comment: Optional[bool] = True,
23
24
  ):
24
25
  """Initialize the question.
25
26
 
@@ -37,6 +38,7 @@ class QuestionTopK(QuestionCheckBox):
37
38
  max_selections=max_selections,
38
39
  question_presentation=question_presentation,
39
40
  answering_instructions=answering_instructions,
41
+ include_comment=include_comment,
40
42
  )
41
43
  if min_selections != max_selections:
42
44
  raise QuestionCreationValidationError(
@@ -52,7 +54,7 @@ class QuestionTopK(QuestionCheckBox):
52
54
  ################
53
55
  @classmethod
54
56
  @inject_exception
55
- def example(cls) -> QuestionTopK:
57
+ def example(cls, include_comment: bool = True) -> QuestionTopK:
56
58
  """Return an example question."""
57
59
  return cls(
58
60
  question_name="two_fruits",
@@ -60,6 +62,7 @@ class QuestionTopK(QuestionCheckBox):
60
62
  question_options=["apple", "banana", "carrot", "durian"],
61
63
  min_selections=2,
62
64
  max_selections=2,
65
+ include_comment=include_comment,
63
66
  )
64
67
 
65
68