edsl 0.1.33__py3-none-any.whl → 0.1.33.dev2__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 (63) hide show
  1. edsl/Base.py +3 -9
  2. edsl/__init__.py +0 -1
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +6 -6
  5. edsl/agents/Invigilator.py +3 -6
  6. edsl/agents/InvigilatorBase.py +27 -8
  7. edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +29 -101
  8. edsl/config.py +34 -26
  9. edsl/coop/coop.py +2 -11
  10. edsl/data_transfer_models.py +73 -26
  11. edsl/enums.py +0 -2
  12. edsl/inference_services/GoogleService.py +1 -1
  13. edsl/inference_services/InferenceServiceABC.py +13 -44
  14. edsl/inference_services/OpenAIService.py +4 -7
  15. edsl/inference_services/TestService.py +15 -24
  16. edsl/inference_services/registry.py +0 -2
  17. edsl/jobs/Jobs.py +8 -18
  18. edsl/jobs/buckets/BucketCollection.py +15 -24
  19. edsl/jobs/buckets/TokenBucket.py +10 -64
  20. edsl/jobs/interviews/Interview.py +47 -115
  21. edsl/jobs/interviews/InterviewExceptionEntry.py +0 -2
  22. edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +0 -16
  23. edsl/jobs/interviews/retry_management.py +39 -0
  24. edsl/jobs/runners/JobsRunnerAsyncio.py +170 -95
  25. edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
  26. edsl/jobs/tasks/TaskHistory.py +0 -17
  27. edsl/language_models/LanguageModel.py +31 -26
  28. edsl/language_models/registry.py +9 -13
  29. edsl/questions/QuestionBase.py +14 -63
  30. edsl/questions/QuestionBudget.py +41 -93
  31. edsl/questions/QuestionFreeText.py +0 -6
  32. edsl/questions/QuestionMultipleChoice.py +23 -8
  33. edsl/questions/QuestionNumerical.py +4 -5
  34. edsl/questions/ResponseValidatorABC.py +5 -6
  35. edsl/questions/derived/QuestionLinearScale.py +1 -4
  36. edsl/questions/derived/QuestionTopK.py +1 -4
  37. edsl/questions/derived/QuestionYesNo.py +2 -8
  38. edsl/results/DatasetExportMixin.py +1 -5
  39. edsl/results/Result.py +1 -1
  40. edsl/results/Results.py +1 -4
  41. edsl/scenarios/FileStore.py +10 -71
  42. edsl/scenarios/Scenario.py +21 -86
  43. edsl/scenarios/ScenarioImageMixin.py +2 -2
  44. edsl/scenarios/ScenarioList.py +0 -13
  45. edsl/scenarios/ScenarioListPdfMixin.py +4 -150
  46. edsl/study/Study.py +0 -32
  47. edsl/surveys/Rule.py +1 -10
  48. edsl/surveys/RuleCollection.py +3 -19
  49. edsl/surveys/Survey.py +0 -7
  50. edsl/templates/error_reporting/interview_details.html +1 -6
  51. edsl/utilities/utilities.py +1 -9
  52. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +1 -2
  53. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/RECORD +55 -61
  54. edsl/inference_services/TogetherAIService.py +0 -170
  55. edsl/jobs/runners/JobsRunnerStatus.py +0 -331
  56. edsl/questions/Quick.py +0 -41
  57. edsl/questions/templates/budget/__init__.py +0 -0
  58. edsl/questions/templates/budget/answering_instructions.jinja +0 -7
  59. edsl/questions/templates/budget/question_presentation.jinja +0 -7
  60. edsl/questions/templates/extract/__init__.py +0 -0
  61. edsl/questions/templates/rank/__init__.py +0 -0
  62. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
  63. {edsl-0.1.33.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -64,7 +64,7 @@ class GoogleService(InferenceServiceABC):
64
64
  "stopSequences": self.stopSequences,
65
65
  },
66
66
  }
67
- # print(combined_prompt)
67
+ print(combined_prompt)
68
68
  async with aiohttp.ClientSession() as session:
69
69
  async with session.post(
70
70
  url, headers=headers, data=json.dumps(data)
@@ -1,27 +1,14 @@
1
1
  from abc import abstractmethod, ABC
2
- import os
2
+ from typing import Any
3
3
  import re
4
4
  from edsl.config import CONFIG
5
5
 
6
6
 
7
7
  class InferenceServiceABC(ABC):
8
- """
9
- Abstract class for inference services.
10
- Anthropic: https://docs.anthropic.com/en/api/rate-limits
11
- """
12
-
13
- default_levels = {
14
- "google": {"tpm": 2_000_000, "rpm": 15},
15
- "openai": {"tpm": 2_000_000, "rpm": 10_000},
16
- "anthropic": {"tpm": 2_000_000, "rpm": 500},
17
- }
8
+ """Abstract class for inference services."""
18
9
 
10
+ # check if child class has cls attribute "key_sequence"
19
11
  def __init_subclass__(cls):
20
- """
21
- Check that the subclass has the required attributes.
22
- - `key_sequence` attribute determines...
23
- - `model_exclude_list` attribute determines...
24
- """
25
12
  if not hasattr(cls, "key_sequence"):
26
13
  raise NotImplementedError(
27
14
  f"Class {cls.__name__} must have a 'key_sequence' attribute."
@@ -31,47 +18,29 @@ class InferenceServiceABC(ABC):
31
18
  f"Class {cls.__name__} must have a 'model_exclude_list' attribute."
32
19
  )
33
20
 
34
- @classmethod
35
- def _get_limt(cls, limit_type: str) -> int:
36
- key = f"EDSL_SERVICE_{limit_type.upper()}_{cls._inference_service_.upper()}"
37
- if key in os.environ:
38
- return int(os.getenv(key))
39
-
40
- if cls._inference_service_ in cls.default_levels:
41
- return int(cls.default_levels[cls._inference_service_][limit_type])
42
-
43
- return int(CONFIG.get(f"EDSL_SERVICE_{limit_type.upper()}_BASELINE"))
44
-
45
- def get_tpm(cls) -> int:
46
- """
47
- Returns the TPM for the service. If the service is not defined in the environment variables, it will return the baseline TPM.
48
- """
49
- return cls._get_limt(limit_type="tpm")
21
+ def get_tpm(cls):
22
+ key = f"EDSL_SERVICE_TPM_{cls._inference_service_.upper()}"
23
+ if key not in CONFIG:
24
+ key = "EDSL_SERVICE_TPM_BASELINE"
25
+ return int(CONFIG.get(key))
50
26
 
51
27
  def get_rpm(cls):
52
- """
53
- Returns the RPM for the service. If the service is not defined in the environment variables, it will return the baseline RPM.
54
- """
55
- return cls._get_limt(limit_type="rpm")
28
+ key = f"EDSL_SERVICE_RPM_{cls._inference_service_.upper()}"
29
+ if key not in CONFIG:
30
+ key = "EDSL_SERVICE_RPM_BASELINE"
31
+ return int(CONFIG.get(key))
56
32
 
57
33
  @abstractmethod
58
34
  def available() -> list[str]:
59
- """
60
- Returns a list of available models for the service.
61
- """
62
35
  pass
63
36
 
64
37
  @abstractmethod
65
38
  def create_model():
66
- """
67
- Returns a LanguageModel object.
68
- """
69
39
  pass
70
40
 
71
41
  @staticmethod
72
42
  def to_class_name(s):
73
- """
74
- Converts a string to a valid class name.
43
+ """Convert a string to a valid class name.
75
44
 
76
45
  >>> InferenceServiceABC.to_class_name("hello world")
77
46
  'HelloWorld'
@@ -187,15 +187,12 @@ class OpenAIService(InferenceServiceABC):
187
187
  else:
188
188
  content = user_prompt
189
189
  client = self.async_client()
190
- messages = [
191
- {"role": "system", "content": system_prompt},
192
- {"role": "user", "content": content},
193
- ]
194
- if system_prompt == "" and self.omit_system_prompt_if_empty:
195
- messages = messages[1:]
196
190
  params = {
197
191
  "model": self.model,
198
- "messages": messages,
192
+ "messages": [
193
+ {"role": "system", "content": system_prompt},
194
+ {"role": "user", "content": content},
195
+ ],
199
196
  "temperature": self.temperature,
200
197
  "max_tokens": self.max_tokens,
201
198
  "top_p": self.top_p,
@@ -7,25 +7,14 @@ from edsl.inference_services.rate_limits_cache import rate_limits
7
7
  from edsl.utilities.utilities import fix_partial_correct_response
8
8
 
9
9
  from edsl.enums import InferenceServiceType
10
- import random
11
10
 
12
11
 
13
12
  class TestService(InferenceServiceABC):
14
13
  """OpenAI service class."""
15
14
 
16
- _inference_service_ = "test"
17
- _env_key_name_ = None
18
- _base_url_ = None
19
-
20
- _sync_client_ = None
21
- _async_client_ = None
22
-
23
- _sync_client_instance = None
24
- _async_client_instance = None
25
-
26
15
  key_sequence = None
27
- usage_sequence = None
28
16
  model_exclude_list = []
17
+ _inference_service_ = "test"
29
18
  input_token_name = "prompt_tokens"
30
19
  output_token_name = "completion_tokens"
31
20
 
@@ -56,25 +45,27 @@ class TestService(InferenceServiceABC):
56
45
  return "Hello, world"
57
46
 
58
47
  async def async_execute_model_call(
59
- self,
60
- user_prompt: str,
61
- system_prompt: str,
62
- encoded_image=None,
48
+ self, user_prompt: str, system_prompt: str
63
49
  ) -> dict[str, Any]:
64
50
  await asyncio.sleep(0.1)
65
51
  # return {"message": """{"answer": "Hello, world"}"""}
66
-
67
52
  if hasattr(self, "throw_exception") and self.throw_exception:
68
- if hasattr(self, "exception_probability"):
69
- p = self.exception_probability
70
- else:
71
- p = 1
72
-
73
- if random.random() < p:
74
- raise Exception("This is a test error")
53
+ raise Exception("This is a test error")
75
54
  return {
76
55
  "message": [{"text": f"{self._canned_response}"}],
77
56
  "usage": {"prompt_tokens": 1, "completion_tokens": 1},
78
57
  }
79
58
 
80
59
  return TestServiceLanguageModel
60
+
61
+ # _inference_service_ = "openai"
62
+ # _env_key_name_ = "OPENAI_API_KEY"
63
+ # _base_url_ = None
64
+
65
+ # _sync_client_ = openai.OpenAI
66
+ # _async_client_ = openai.AsyncOpenAI
67
+
68
+ # _sync_client_instance = None
69
+ # _async_client_instance = None
70
+
71
+ # key_sequence = ["choices", 0, "message", "content"]
@@ -12,7 +12,6 @@ from edsl.inference_services.AzureAI import AzureAIService
12
12
  from edsl.inference_services.OllamaService import OllamaService
13
13
  from edsl.inference_services.TestService import TestService
14
14
  from edsl.inference_services.MistralAIService import MistralAIService
15
- from edsl.inference_services.TogetherAIService import TogetherAIService
16
15
 
17
16
  default = InferenceServicesCollection(
18
17
  [
@@ -26,6 +25,5 @@ default = InferenceServicesCollection(
26
25
  OllamaService,
27
26
  TestService,
28
27
  MistralAIService,
29
- TogetherAIService,
30
28
  ]
31
29
  )
edsl/jobs/Jobs.py CHANGED
@@ -460,12 +460,6 @@ class Jobs(Base):
460
460
  if warn:
461
461
  warnings.warn(message)
462
462
 
463
- if self.scenarios.has_jinja_braces:
464
- warnings.warn(
465
- "The scenarios have Jinja braces ({{ and }}). Converting to '<<' and '>>'. If you want a different conversion, use the convert_jinja_braces method first to modify the scenario."
466
- )
467
- self.scenarios = self.scenarios.convert_jinja_braces()
468
-
469
463
  @property
470
464
  def skip_retry(self):
471
465
  if not hasattr(self, "_skip_retry"):
@@ -492,7 +486,6 @@ class Jobs(Base):
492
486
  remote_inference_description: Optional[str] = None,
493
487
  skip_retry: bool = False,
494
488
  raise_validation_errors: bool = False,
495
- disable_remote_inference: bool = False,
496
489
  ) -> Results:
497
490
  """
498
491
  Runs the Job: conducts Interviews and returns their results.
@@ -515,17 +508,14 @@ class Jobs(Base):
515
508
 
516
509
  self.verbose = verbose
517
510
 
518
- remote_cache = False
519
- remote_inference = False
520
-
521
- if not disable_remote_inference:
522
- try:
523
- coop = Coop()
524
- user_edsl_settings = Coop().edsl_settings
525
- remote_cache = user_edsl_settings.get("remote_caching", False)
526
- remote_inference = user_edsl_settings.get("remote_inference", False)
527
- except Exception:
528
- pass
511
+ try:
512
+ coop = Coop()
513
+ user_edsl_settings = coop.edsl_settings
514
+ remote_cache = user_edsl_settings["remote_caching"]
515
+ remote_inference = user_edsl_settings["remote_inference"]
516
+ except Exception:
517
+ remote_cache = False
518
+ remote_inference = False
529
519
 
530
520
  if remote_inference:
531
521
  import time
@@ -13,8 +13,6 @@ class BucketCollection(UserDict):
13
13
  def __init__(self, infinity_buckets=False):
14
14
  super().__init__()
15
15
  self.infinity_buckets = infinity_buckets
16
- self.models_to_services = {}
17
- self.services_to_buckets = {}
18
16
 
19
17
  def __repr__(self):
20
18
  return f"BucketCollection({self.data})"
@@ -23,7 +21,6 @@ class BucketCollection(UserDict):
23
21
  """Adds a model to the bucket collection.
24
22
 
25
23
  This will create the token and request buckets for the model."""
26
-
27
24
  # compute the TPS and RPS from the model
28
25
  if not self.infinity_buckets:
29
26
  TPS = model.TPM / 60.0
@@ -32,28 +29,22 @@ class BucketCollection(UserDict):
32
29
  TPS = float("inf")
33
30
  RPS = float("inf")
34
31
 
35
- if model.model not in self.models_to_services:
36
- service = model._inference_service_
37
- if service not in self.services_to_buckets:
38
- requests_bucket = TokenBucket(
39
- bucket_name=service,
40
- bucket_type="requests",
41
- capacity=RPS,
42
- refill_rate=RPS,
43
- )
44
- tokens_bucket = TokenBucket(
45
- bucket_name=service,
46
- bucket_type="tokens",
47
- capacity=TPS,
48
- refill_rate=TPS,
49
- )
50
- self.services_to_buckets[service] = ModelBuckets(
51
- requests_bucket, tokens_bucket
52
- )
53
- self.models_to_services[model.model] = service
54
- self[model] = self.services_to_buckets[service]
32
+ # create the buckets
33
+ requests_bucket = TokenBucket(
34
+ bucket_name=model.model,
35
+ bucket_type="requests",
36
+ capacity=RPS,
37
+ refill_rate=RPS,
38
+ )
39
+ tokens_bucket = TokenBucket(
40
+ bucket_name=model.model, bucket_type="tokens", capacity=TPS, refill_rate=TPS
41
+ )
42
+ model_buckets = ModelBuckets(requests_bucket, tokens_bucket)
43
+ if model in self:
44
+ # it if already exists, combine the buckets
45
+ self[model] += model_buckets
55
46
  else:
56
- self[model] = self.services_to_buckets[self.models_to_services[model.model]]
47
+ self[model] = model_buckets
57
48
 
58
49
  def visualize(self) -> dict:
59
50
  """Visualize the token and request buckets for each model."""
@@ -1,4 +1,4 @@
1
- from typing import Union, List, Any, Optional
1
+ from typing import Union, List, Any
2
2
  import asyncio
3
3
  import time
4
4
 
@@ -17,12 +17,6 @@ class TokenBucket:
17
17
  self.bucket_name = bucket_name
18
18
  self.bucket_type = bucket_type
19
19
  self.capacity = capacity # Maximum number of tokens
20
- self.added_tokens = 0
21
-
22
- self.target_rate = (
23
- capacity * 60
24
- ) # set this here because it can change with turbo mode
25
-
26
20
  self._old_capacity = capacity
27
21
  self.tokens = capacity # Current number of available tokens
28
22
  self.refill_rate = refill_rate # Rate at which tokens are refilled
@@ -31,12 +25,6 @@ class TokenBucket:
31
25
  self.log: List[Any] = []
32
26
  self.turbo_mode = False
33
27
 
34
- self.creation_time = time.monotonic()
35
-
36
- self.num_requests = 0
37
- self.num_released = 0
38
- self.tokens_returned = 0
39
-
40
28
  def turbo_mode_on(self):
41
29
  """Set the refill rate to infinity."""
42
30
  if self.turbo_mode:
@@ -81,7 +69,6 @@ class TokenBucket:
81
69
  >>> bucket.tokens
82
70
  10
83
71
  """
84
- self.tokens_returned += tokens
85
72
  self.tokens = min(self.capacity, self.tokens + tokens)
86
73
  self.log.append((time.monotonic(), self.tokens))
87
74
 
@@ -146,12 +133,15 @@ class TokenBucket:
146
133
  >>> bucket.capacity
147
134
  12.100000000000001
148
135
  """
149
- self.num_requests += amount
150
136
  if amount >= self.capacity:
151
137
  if not cheat_bucket_capacity:
152
138
  msg = f"Requested amount exceeds bucket capacity. Bucket capacity: {self.capacity}, requested amount: {amount}. As the bucket never overflows, the requested amount will never be available."
153
139
  raise ValueError(msg)
154
140
  else:
141
+ # self.tokens = 0 # clear the bucket but let it go through
142
+ # print(
143
+ # f"""The requested amount, {amount}, exceeds the current bucket capacity of {self.capacity}.Increasing bucket capacity to {amount} * 1.10 accommodate the requested amount."""
144
+ # )
155
145
  self.capacity = amount * 1.10
156
146
  self._old_capacity = self.capacity
157
147
 
@@ -163,10 +153,14 @@ class TokenBucket:
163
153
  break
164
154
 
165
155
  wait_time = self.wait_time(amount)
156
+ # print(f"Waiting for {wait_time:.4f} seconds")
166
157
  if wait_time > 0:
158
+ # print(f"Waiting for {wait_time:.4f} seconds")
167
159
  await asyncio.sleep(wait_time)
168
160
 
169
- self.num_released += amount
161
+ # total_elapsed = time.monotonic() - start_time
162
+ # print(f"Total time to acquire tokens: {total_elapsed:.4f} seconds")
163
+
170
164
  now = time.monotonic()
171
165
  self.log.append((now, self.tokens))
172
166
  return None
@@ -193,54 +187,6 @@ class TokenBucket:
193
187
  plt.tight_layout()
194
188
  plt.show()
195
189
 
196
- def get_throughput(self, time_window: Optional[float] = None) -> float:
197
- """
198
- Calculate the empirical bucket throughput in tokens per minute for the specified time window.
199
-
200
- :param time_window: The time window in seconds to calculate the throughput for.
201
- :return: The throughput in tokens per minute.
202
-
203
- >>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=100, refill_rate=10)
204
- >>> asyncio.run(bucket.get_tokens(50))
205
- >>> time.sleep(1) # Wait for 1 second
206
- >>> asyncio.run(bucket.get_tokens(30))
207
- >>> throughput = bucket.get_throughput(1)
208
- >>> 4750 < throughput < 4850
209
- True
210
- """
211
- now = time.monotonic()
212
-
213
- if time_window is None:
214
- start_time = self.creation_time
215
- else:
216
- start_time = now - time_window
217
-
218
- if start_time < self.creation_time:
219
- start_time = self.creation_time
220
-
221
- elapsed_time = now - start_time
222
-
223
- return (self.num_released / elapsed_time) * 60
224
-
225
- # # Filter log entries within the time window
226
- # relevant_log = [(t, tokens) for t, tokens in self.log if t >= start_time]
227
-
228
- # if len(relevant_log) < 2:
229
- # return 0 # Not enough data points to calculate throughput
230
-
231
- # # Calculate total tokens used
232
- # initial_tokens = relevant_log[0][1]
233
- # final_tokens = relevant_log[-1][1]
234
- # tokens_used = self.num_released - (final_tokens - initial_tokens)
235
-
236
- # # Calculate actual time elapsed
237
- # actual_time_elapsed = relevant_log[-1][0] - relevant_log[0][0]
238
-
239
- # # Calculate throughput in tokens per minute
240
- # throughput = (tokens_used / actual_time_elapsed) * 60
241
-
242
- # return throughput
243
-
244
190
 
245
191
  if __name__ == "__main__":
246
192
  import doctest