edsl 0.1.30.dev5__py3-none-any.whl → 0.1.31.dev1__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.
edsl/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.30.dev5"
1
+ __version__ = "0.1.31.dev1"
edsl/coop/utils.py CHANGED
@@ -2,6 +2,7 @@ from edsl import (
2
2
  Agent,
3
3
  AgentList,
4
4
  Cache,
5
+ ModelList,
5
6
  Notebook,
6
7
  Results,
7
8
  Scenario,
@@ -9,6 +10,7 @@ from edsl import (
9
10
  Survey,
10
11
  Study,
11
12
  )
13
+ from edsl.language_models import LanguageModel
12
14
  from edsl.questions import QuestionBase
13
15
  from typing import Literal, Optional, Type, Union
14
16
 
@@ -16,6 +18,8 @@ EDSLObject = Union[
16
18
  Agent,
17
19
  AgentList,
18
20
  Cache,
21
+ LanguageModel,
22
+ ModelList,
19
23
  Notebook,
20
24
  Type[QuestionBase],
21
25
  Results,
@@ -29,6 +33,8 @@ ObjectType = Literal[
29
33
  "agent",
30
34
  "agent_list",
31
35
  "cache",
36
+ "model",
37
+ "model_list",
32
38
  "notebook",
33
39
  "question",
34
40
  "results",
@@ -62,8 +68,10 @@ class ObjectRegistry:
62
68
  {"object_type": "agent", "edsl_class": Agent},
63
69
  {"object_type": "agent_list", "edsl_class": AgentList},
64
70
  {"object_type": "cache", "edsl_class": Cache},
65
- {"object_type": "question", "edsl_class": QuestionBase},
71
+ {"object_type": "model", "edsl_class": LanguageModel},
72
+ {"object_type": "model_list", "edsl_class": ModelList},
66
73
  {"object_type": "notebook", "edsl_class": Notebook},
74
+ {"object_type": "question", "edsl_class": QuestionBase},
67
75
  {"object_type": "results", "edsl_class": Results},
68
76
  {"object_type": "scenario", "edsl_class": Scenario},
69
77
  {"object_type": "scenario_list", "edsl_class": ScenarioList},
@@ -30,7 +30,7 @@ class TokenBucket:
30
30
  if self.turbo_mode:
31
31
  pass
32
32
  else:
33
- #pass
33
+ # pass
34
34
  self.turbo_mode = True
35
35
  self.capacity = float("inf")
36
36
  self.refill_rate = float("inf")
@@ -74,7 +74,7 @@ class TokenBucket:
74
74
 
75
75
  def refill(self) -> None:
76
76
  """Refill the bucket with new tokens based on elapsed time.
77
-
77
+
78
78
 
79
79
 
80
80
  >>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=10, refill_rate=1)
@@ -82,7 +82,7 @@ class TokenBucket:
82
82
  >>> bucket.refill()
83
83
  >>> bucket.tokens > 0
84
84
  True
85
-
85
+
86
86
  """
87
87
  now = time.monotonic()
88
88
  elapsed = now - self.last_refill
@@ -22,9 +22,11 @@ from edsl.jobs.interviews.InterviewStatusMixin import InterviewStatusMixin
22
22
 
23
23
  import asyncio
24
24
 
25
+
25
26
  def run_async(coro):
26
27
  return asyncio.run(coro)
27
28
 
29
+
28
30
  class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
29
31
  """
30
32
  An 'interview' is one agent answering one survey, with one language model, for a given scenario.
@@ -41,7 +43,7 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
41
43
  debug: Optional[bool] = False,
42
44
  iteration: int = 0,
43
45
  cache: Optional["Cache"] = None,
44
- sidecar_model: Optional['LanguageModel'] = None,
46
+ sidecar_model: Optional["LanguageModel"] = None,
45
47
  ):
46
48
  """Initialize the Interview instance.
47
49
 
@@ -98,7 +100,7 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
98
100
  model_buckets: ModelBuckets = None,
99
101
  debug: bool = False,
100
102
  stop_on_exception: bool = False,
101
- sidecar_model: Optional['LanguageModel'] = None,
103
+ sidecar_model: Optional["LanguageModel"] = None,
102
104
  ) -> tuple["Answers", List[dict[str, Any]]]:
103
105
  """
104
106
  Conduct an Interview asynchronously.
@@ -146,13 +148,11 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
146
148
  if model_buckets is None or hasattr(self.agent, "answer_question_directly"):
147
149
  model_buckets = ModelBuckets.infinity_bucket()
148
150
 
149
-
150
151
  ## build the tasks using the InterviewTaskBuildingMixin
151
152
  ## This is the key part---it creates a task for each question,
152
153
  ## with dependencies on the questions that must be answered before this one can be answered.
153
154
  self.tasks = self._build_question_tasks(
154
- debug=debug,
155
- model_buckets=model_buckets
155
+ debug=debug, model_buckets=model_buckets
156
156
  )
157
157
 
158
158
  ## 'Invigilators' are used to administer the survey
@@ -195,7 +195,7 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
195
195
 
196
196
  def _record_exception(self, task, exception: Exception) -> None:
197
197
  """Record an exception in the Interview instance.
198
-
198
+
199
199
  It records the exception in the Interview instance, with the task name and the exception entry.
200
200
 
201
201
  >>> i = Interview.example()
@@ -235,14 +235,14 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
235
235
  """Return a string representation of the Interview instance."""
236
236
  return f"Interview(agent = {repr(self.agent)}, survey = {repr(self.survey)}, scenario = {repr(self.scenario)}, model = {repr(self.model)})"
237
237
 
238
- def duplicate(self, iteration: int, cache: 'Cache') -> Interview:
238
+ def duplicate(self, iteration: int, cache: "Cache") -> Interview:
239
239
  """Duplicate the interview, but with a new iteration number and cache.
240
-
240
+
241
241
  >>> i = Interview.example()
242
242
  >>> i2 = i.duplicate(1, None)
243
243
  >>> i.iteration + 1 == i2.iteration
244
244
  True
245
-
245
+
246
246
  """
247
247
  return Interview(
248
248
  agent=self.agent,
@@ -270,7 +270,7 @@ class Interview(InterviewStatusMixin, InterviewTaskBuildingMixin):
270
270
  scenario = Scenario.example()
271
271
  model = LanguageModel.example()
272
272
  if throw_exception:
273
- model = LanguageModel.example(test_model = True, throw_exception=True)
273
+ model = LanguageModel.example(test_model=True, throw_exception=True)
274
274
  agent = Agent.example()
275
275
  return Interview(agent=agent, survey=survey, scenario=scenario, model=model)
276
276
  return Interview(agent=agent, survey=survey, scenario=scenario, model=model)
@@ -25,7 +25,7 @@ TIMEOUT = float(CONFIG.get("EDSL_API_TIMEOUT"))
25
25
  class InterviewTaskBuildingMixin:
26
26
  def _build_invigilators(
27
27
  self, debug: bool
28
- ) -> Generator['InvigilatorBase', None, None]:
28
+ ) -> Generator["InvigilatorBase", None, None]:
29
29
  """Create an invigilator for each question.
30
30
 
31
31
  :param debug: whether to use debug mode, in which case `InvigilatorDebug` is used.
@@ -35,7 +35,7 @@ class InterviewTaskBuildingMixin:
35
35
  for question in self.survey.questions:
36
36
  yield self._get_invigilator(question=question, debug=debug)
37
37
 
38
- def _get_invigilator(self, question: 'QuestionBase', debug: bool) -> "Invigilator":
38
+ def _get_invigilator(self, question: "QuestionBase", debug: bool) -> "Invigilator":
39
39
  """Return an invigilator for the given question.
40
40
 
41
41
  :param question: the question to be answered
@@ -84,7 +84,7 @@ class InterviewTaskBuildingMixin:
84
84
  return tuple(tasks) # , invigilators
85
85
 
86
86
  def _get_tasks_that_must_be_completed_before(
87
- self, *, tasks: list[asyncio.Task], question: 'QuestionBase'
87
+ self, *, tasks: list[asyncio.Task], question: "QuestionBase"
88
88
  ) -> Generator[asyncio.Task, None, None]:
89
89
  """Return the tasks that must be completed before the given question can be answered.
90
90
 
@@ -100,7 +100,7 @@ class InterviewTaskBuildingMixin:
100
100
  def _create_question_task(
101
101
  self,
102
102
  *,
103
- question: 'QuestionBase',
103
+ question: "QuestionBase",
104
104
  tasks_that_must_be_completed_before: list[asyncio.Task],
105
105
  model_buckets: ModelBuckets,
106
106
  debug: bool,
@@ -179,8 +179,10 @@ class InterviewTaskBuildingMixin:
179
179
  return AgentResponseDict(**response)
180
180
  except Exception as e:
181
181
  raise e
182
-
183
- def _add_answer(self, response: 'AgentResponseDict', question: 'QuestionBase') -> None:
182
+
183
+ def _add_answer(
184
+ self, response: "AgentResponseDict", question: "QuestionBase"
185
+ ) -> None:
184
186
  """Add the answer to the answers dictionary.
185
187
 
186
188
  :param response: the response to the question.
@@ -188,7 +190,7 @@ class InterviewTaskBuildingMixin:
188
190
  """
189
191
  self.answers.add_answer(response=response, question=question)
190
192
 
191
- def _skip_this_question(self, current_question: 'QuestionBase') -> bool:
193
+ def _skip_this_question(self, current_question: "QuestionBase") -> bool:
192
194
  """Determine if the current question should be skipped.
193
195
 
194
196
  :param current_question: the question to be answered.
@@ -88,8 +88,7 @@ class QuestionTaskCreator(UserList):
88
88
  self.append(task)
89
89
 
90
90
  def generate_task(self, debug: bool) -> asyncio.Task:
91
- """Create a task that depends on the passed-in dependencies.
92
- """
91
+ """Create a task that depends on the passed-in dependencies."""
93
92
  task = asyncio.create_task(
94
93
  self._run_task_async(debug), name=self.question.question_name
95
94
  )
@@ -145,7 +144,7 @@ class QuestionTaskCreator(UserList):
145
144
  self.task_status = TaskStatus.FAILED
146
145
  raise e
147
146
 
148
- if results.get('cache_used', False):
147
+ if results.get("cache_used", False):
149
148
  self.tokens_bucket.add_tokens(requested_tokens)
150
149
  self.requests_bucket.add_tokens(1)
151
150
  self.from_cache = True
@@ -494,7 +494,12 @@ class LanguageModel(
494
494
  return table
495
495
 
496
496
  @classmethod
497
- def example(cls, test_model: bool = False, canned_response: str = "Hello world", throw_exception: bool = False):
497
+ def example(
498
+ cls,
499
+ test_model: bool = False,
500
+ canned_response: str = "Hello world",
501
+ throw_exception: bool = False,
502
+ ):
498
503
  """Return a default instance of the class.
499
504
 
500
505
  >>> from edsl.language_models import LanguageModel
@@ -86,8 +86,14 @@ class ModelList(Base, UserList):
86
86
  pass
87
87
 
88
88
  @classmethod
89
- def example(cl):
90
- return ModelList([LanguageModel.example() for _ in range(3)])
89
+ def example(cls, randomize: bool = False) -> "ModelList":
90
+ """
91
+ Returns an example ModelList instance.
92
+
93
+ :param randomize: If True, uses Model's randomize method.
94
+ """
95
+
96
+ return cls([Model.example(randomize) for _ in range(3)])
91
97
 
92
98
 
93
99
  if __name__ == "__main__":
@@ -1,4 +1,5 @@
1
1
  import textwrap
2
+ from random import random
2
3
 
3
4
 
4
5
  def get_model_class(model_name, registry=None):
@@ -92,6 +93,17 @@ class Model(metaclass=Meta):
92
93
  print("OK!")
93
94
  print("\n")
94
95
 
96
+ @classmethod
97
+ def example(cls, randomize: bool = False) -> "Model":
98
+ """
99
+ Returns an example Model instance.
100
+
101
+ :param randomize: If True, the temperature is set to a random decimal between 0 and 1.
102
+ """
103
+ temperature = 0.5 if not randomize else round(random(), 2)
104
+ model_name = cls.default_model
105
+ return cls(model_name, temperature=temperature)
106
+
95
107
 
96
108
  if __name__ == "__main__":
97
109
  import doctest
@@ -6,11 +6,12 @@ from edsl.questions.QuestionBase import QuestionBase
6
6
  from edsl.utilities.restricted_python import create_restricted_function
7
7
  from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
8
8
 
9
+
9
10
  class QuestionFunctional(QuestionBase):
10
11
  """A special type of question that is *not* answered by an LLM.
11
-
12
+
12
13
  >>> from edsl import Scenario, Agent
13
-
14
+
14
15
  # Create an instance of QuestionFunctional with the new function
15
16
  >>> question = QuestionFunctional.example()
16
17
 
@@ -21,7 +22,7 @@ class QuestionFunctional(QuestionBase):
21
22
  >>> results = question.by(scenario).by(agent).run()
22
23
  >>> results.select("answer.*").to_list()[0] == 150
23
24
  True
24
-
25
+
25
26
  # Serialize the question to a dictionary
26
27
 
27
28
  >>> from edsl.questions.QuestionBase import QuestionBase
@@ -105,8 +106,6 @@ class QuestionFunctional(QuestionBase):
105
106
  "requires_loop": self.requires_loop,
106
107
  "function_name": self.function_name,
107
108
  }
108
-
109
-
110
109
 
111
110
  @classmethod
112
111
  def example(cls):
@@ -141,7 +140,9 @@ def main():
141
140
  results = question.by(scenario).by(agent).run()
142
141
  assert results.select("answer.*").to_list()[0] == 150
143
142
 
143
+
144
144
  if __name__ == "__main__":
145
- #main()
145
+ # main()
146
146
  import doctest
147
- doctest.testmod(optionflags=doctest.ELLIPSIS)
147
+
148
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -11,7 +11,7 @@ from edsl.questions.descriptors import QuestionOptionsDescriptor
11
11
 
12
12
  class QuestionMultipleChoice(QuestionBase):
13
13
  """This question prompts the agent to select one option from a list of options.
14
-
14
+
15
15
  https://docs.expectedparrot.com/en/latest/questions.html#questionmultiplechoice-class
16
16
 
17
17
  """
@@ -51,7 +51,7 @@ class QuestionMultipleChoice(QuestionBase):
51
51
  self, answer: dict[str, Union[str, int]]
52
52
  ) -> dict[str, Union[str, int]]:
53
53
  """Validate the answer.
54
-
54
+
55
55
  >>> q = QuestionMultipleChoice.example()
56
56
  >>> q._validate_answer({"answer": 0, "comment": "I like custard"})
57
57
  {'answer': 0, 'comment': 'I like custard'}
@@ -67,19 +67,17 @@ class QuestionMultipleChoice(QuestionBase):
67
67
  return answer
68
68
 
69
69
  def _translate_answer_code_to_answer(
70
- self,
71
- answer_code: int,
72
- scenario: Optional["Scenario"] = None
70
+ self, answer_code: int, scenario: Optional["Scenario"] = None
73
71
  ):
74
72
  """Translate the answer code to the actual answer.
75
73
 
76
- It is used to translate the answer code to the actual answer.
74
+ It is used to translate the answer code to the actual answer.
77
75
  The question options might be templates, so they need to be rendered with the scenario.
78
-
76
+
79
77
  >>> q = QuestionMultipleChoice.example()
80
78
  >>> q._translate_answer_code_to_answer(0, {})
81
79
  'Good'
82
-
80
+
83
81
  >>> q = QuestionMultipleChoice(question_name="how_feeling", question_text="How are you?", question_options=["{{emotion[0]}}", "emotion[1]"])
84
82
  >>> q._translate_answer_code_to_answer(0, {"emotion": ["Happy", "Sad"]})
85
83
  'Happy'
@@ -92,16 +90,20 @@ class QuestionMultipleChoice(QuestionBase):
92
90
  if isinstance(self.question_options, str):
93
91
  # If dynamic options are provided like {{ options }}, render them with the scenario
94
92
  from jinja2 import Environment, meta
93
+
95
94
  env = Environment()
96
95
  parsed_content = env.parse(self.question_options)
97
- question_option_key = list(meta.find_undeclared_variables(parsed_content))[0]
96
+ question_option_key = list(meta.find_undeclared_variables(parsed_content))[
97
+ 0
98
+ ]
98
99
  translated_options = scenario.get(question_option_key)
99
100
  else:
100
101
  translated_options = [
101
- Template(str(option)).render(scenario) for option in self.question_options
102
+ Template(str(option)).render(scenario)
103
+ for option in self.question_options
102
104
  ]
103
- #print("Translated options:", translated_options)
104
- #breakpoint()
105
+ # print("Translated options:", translated_options)
106
+ # breakpoint()
105
107
  return translated_options[int(answer_code)]
106
108
 
107
109
  def _simulate_answer(
@@ -249,6 +249,7 @@ class QuestionOptionsDescriptor(BaseDescriptor):
249
249
 
250
250
  def __init__(self, question_options: List[str]):
251
251
  self.question_options = question_options
252
+
252
253
  return TestQuestion
253
254
 
254
255
  def __init__(
@@ -264,16 +265,16 @@ class QuestionOptionsDescriptor(BaseDescriptor):
264
265
 
265
266
  def validate(self, value: Any, instance) -> None:
266
267
  """Validate the question options.
267
-
268
+
268
269
  >>> q_class = QuestionOptionsDescriptor.example()
269
270
  >>> _ = q_class(["a", "b", "c"])
270
271
  >>> _ = q_class(["a", "b", "c", "d", "d"])
271
272
  Traceback (most recent call last):
272
273
  ...
273
274
  edsl.exceptions.questions.QuestionCreationValidationError: Question options must be unique (got ['a', 'b', 'c', 'd', 'd']).
274
-
275
+
275
276
  We allow dynamic question options, which are strings of the form '{{ question_options }}'.
276
-
277
+
277
278
  >>> _ = q_class("{{dynamic_options}}")
278
279
  >>> _ = q_class("dynamic_options")
279
280
  Traceback (most recent call last):
@@ -373,7 +374,8 @@ class QuestionTextDescriptor(BaseDescriptor):
373
374
  UserWarning,
374
375
  )
375
376
 
377
+
376
378
  if __name__ == "__main__":
377
379
  import doctest
378
380
 
379
- doctest.testmod(optionflags=doctest.ELLIPSIS)
381
+ doctest.testmod(optionflags=doctest.ELLIPSIS)