edsl 0.1.39__py3-none-any.whl → 0.1.39.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.
Files changed (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -1,138 +1,97 @@
1
- from functools import lru_cache
2
- from collections import defaultdict
3
- from typing import Optional, Protocol, Dict, List, Tuple, TYPE_CHECKING, Literal
4
-
5
1
  from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
6
- from edsl.inference_services.AvailableModelFetcher import AvailableModelFetcher
7
- from edsl.exceptions.inference_services import InferenceServiceError
8
-
9
- if TYPE_CHECKING:
10
- from edsl.language_models.LanguageModel import LanguageModel
11
- from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
12
-
13
-
14
- class ModelCreator(Protocol):
15
- def create_model(self, model_name: str) -> "LanguageModel":
16
- ...
17
-
18
-
19
- from edsl.enums import InferenceServiceLiteral
20
-
21
-
22
- class ModelResolver:
23
- def __init__(
24
- self,
25
- services: List[InferenceServiceLiteral],
26
- models_to_services: Dict[InferenceServiceLiteral, InferenceServiceABC],
27
- availability_fetcher: "AvailableModelFetcher",
28
- ):
29
- """
30
- Class for determining which service to use for a given model.
31
- """
32
- self.services = services
33
- self._models_to_services = models_to_services
34
- self.availability_fetcher = availability_fetcher
35
- self._service_names_to_classes = {
36
- service._inference_service_: service for service in services
37
- }
38
-
39
- def resolve_model(
40
- self, model_name: str, service_name: Optional[InferenceServiceLiteral] = None
41
- ) -> InferenceServiceABC:
42
- """Returns an InferenceServiceABC object for the given model name.
43
-
44
- :param model_name: The name of the model to resolve. E.g., 'gpt-4o'
45
- :param service_name: The name of the service to use. E.g., 'openai'
46
- :return: An InferenceServiceABC object
47
-
48
- """
49
- if model_name == "test":
50
- from edsl.inference_services.TestService import TestService
51
-
52
- return TestService()
53
-
54
- if service_name is not None:
55
- service: InferenceServiceABC = self._service_names_to_classes.get(
56
- service_name
57
- )
58
- if not service:
59
- raise InferenceServiceError(f"Service {service_name} not found")
60
- return service
61
-
62
- if model_name in self._models_to_services: # maybe we've seen it before!
63
- return self._models_to_services[model_name]
64
-
65
- for service in self.services:
66
- (
67
- available_models,
68
- service_name,
69
- ) = self.availability_fetcher.get_available_models_by_service(service)
70
- if model_name in available_models:
71
- self._models_to_services[model_name] = service
72
- return service
73
-
74
- raise InferenceServiceError(
75
- f"""Model {model_name} not found in any services.
76
- If you know the service that has this model, use the service_name parameter directly.
77
- E.g., Model("gpt-4o", service_name="openai")
78
- """
79
- )
2
+ import warnings
80
3
 
81
4
 
82
5
  class InferenceServicesCollection:
83
- added_models = defaultdict(list) # Moved back to class level
6
+ added_models = {}
84
7
 
85
- def __init__(self, services: Optional[List[InferenceServiceABC]] = None):
8
+ def __init__(self, services: list[InferenceServiceABC] = None):
86
9
  self.services = services or []
87
- self._models_to_services: Dict[str, InferenceServiceABC] = {}
88
-
89
- self.availability_fetcher = AvailableModelFetcher(
90
- self.services, self.added_models
91
- )
92
- self.resolver = ModelResolver(
93
- self.services, self._models_to_services, self.availability_fetcher
94
- )
95
10
 
96
11
  @classmethod
97
- def add_model(cls, service_name: str, model_name: str) -> None:
12
+ def add_model(cls, service_name, model_name):
98
13
  if service_name not in cls.added_models:
99
- cls.added_models[service_name].append(model_name)
100
-
101
- def service_names_to_classes(self) -> Dict[str, InferenceServiceABC]:
102
- return {service._inference_service_: service for service in self.services}
103
-
104
- def available(
105
- self,
106
- service: Optional[str] = None,
107
- ) -> List[Tuple[str, str, int]]:
108
- return self.availability_fetcher.available(service)
14
+ cls.added_models[service_name] = []
15
+ cls.added_models[service_name].append(model_name)
16
+
17
+ @staticmethod
18
+ def _get_service_available(service, warn: bool = False) -> list[str]:
19
+ try:
20
+ service_models = service.available()
21
+ except Exception:
22
+ if warn:
23
+ warnings.warn(
24
+ f"""Error getting models for {service._inference_service_}.
25
+ Check that you have properly stored your Expected Parrot API key and activated remote inference, or stored your own API keys for the language models that you want to use.
26
+ See https://docs.expectedparrot.com/en/latest/api_keys.html for instructions on storing API keys.
27
+ Relying on Coop.""",
28
+ UserWarning,
29
+ )
30
+
31
+ # Use the list of models on Coop as a fallback
32
+ try:
33
+ from edsl import Coop
34
+
35
+ c = Coop()
36
+ models_from_coop = c.fetch_models()
37
+ service_models = models_from_coop.get(service._inference_service_, [])
38
+
39
+ # cache results
40
+ service._models_list_cache = service_models
41
+
42
+ # Finally, use the available models cache from the Python file
43
+ except Exception:
44
+ if warn:
45
+ warnings.warn(
46
+ f"""Error getting models for {service._inference_service_}.
47
+ Relying on EDSL cache.""",
48
+ UserWarning,
49
+ )
50
+
51
+ from edsl.inference_services.models_available_cache import (
52
+ models_available,
53
+ )
54
+
55
+ service_models = models_available.get(service._inference_service_, [])
56
+
57
+ # cache results
58
+ service._models_list_cache = service_models
59
+
60
+ return service_models
61
+
62
+ def available(self):
63
+ total_models = []
64
+ for service in self.services:
65
+ service_models = self._get_service_available(service)
66
+ for model in service_models:
67
+ total_models.append([model, service._inference_service_, -1])
109
68
 
110
- def reset_cache(self) -> None:
111
- self.availability_fetcher.reset_cache()
69
+ for model in self.added_models.get(service._inference_service_, []):
70
+ total_models.append([model, service._inference_service_, -1])
112
71
 
113
- @property
114
- def num_cache_entries(self) -> int:
115
- return self.availability_fetcher.num_cache_entries
72
+ sorted_models = sorted(total_models)
73
+ for i, model in enumerate(sorted_models):
74
+ model[2] = i
75
+ model = tuple(model)
76
+ return sorted_models
116
77
 
117
- def register(self, service: InferenceServiceABC) -> None:
78
+ def register(self, service):
118
79
  self.services.append(service)
119
80
 
120
- def create_model_factory(
121
- self, model_name: str, service_name: Optional[InferenceServiceLiteral] = None
122
- ) -> "LanguageModel":
123
-
124
- if service_name is None: # we try to find the right service
125
- service = self.resolver.resolve_model(model_name, service_name)
126
- else: # if they passed a service, we'll use that
127
- service = self.service_names_to_classes().get(service_name)
81
+ def create_model_factory(self, model_name: str, service_name=None, index=None):
82
+ from edsl.inference_services.TestService import TestService
128
83
 
129
- if not service: # but if we can't find it, we'll raise an error
130
- raise InferenceServiceError(f"Service {service_name} not found")
131
-
132
- return service.create_model(model_name)
84
+ if model_name == "test":
85
+ return TestService.create_model(model_name)
133
86
 
87
+ if service_name:
88
+ for service in self.services:
89
+ if service_name == service._inference_service_:
90
+ return service.create_model(model_name)
134
91
 
135
- if __name__ == "__main__":
136
- import doctest
92
+ for service in self.services:
93
+ if model_name in self._get_service_available(service):
94
+ if service_name is None or service_name == service._inference_service_:
95
+ return service.create_model(model_name)
137
96
 
138
- doctest.testmod()
97
+ raise Exception(f"Model {model_name} not found in any of the services")
@@ -85,6 +85,9 @@ class MistralAIService(InferenceServiceABC):
85
85
  "top_p": 0.9,
86
86
  }
87
87
 
88
+ _tpm = cls.get_tpm(cls)
89
+ _rpm = cls.get_rpm(cls)
90
+
88
91
  def sync_client(self):
89
92
  return cls.sync_client()
90
93
 
@@ -1,19 +1,16 @@
1
1
  from __future__ import annotations
2
- from typing import Any, List, Optional, Dict, NewType
2
+ from typing import Any, List, Optional
3
3
  import os
4
4
 
5
-
6
5
  import openai
7
6
 
8
7
  from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
9
- from edsl.language_models.LanguageModel import LanguageModel
8
+ from edsl.language_models import LanguageModel
10
9
  from edsl.inference_services.rate_limits_cache import rate_limits
11
10
  from edsl.utilities.utilities import fix_partial_correct_response
12
11
 
13
12
  from edsl.config import CONFIG
14
13
 
15
- APIToken = NewType("APIToken", str)
16
-
17
14
 
18
15
  class OpenAIService(InferenceServiceABC):
19
16
  """OpenAI service class."""
@@ -25,43 +22,35 @@ class OpenAIService(InferenceServiceABC):
25
22
  _sync_client_ = openai.OpenAI
26
23
  _async_client_ = openai.AsyncOpenAI
27
24
 
28
- _sync_client_instances: Dict[APIToken, openai.OpenAI] = {}
29
- _async_client_instances: Dict[APIToken, openai.AsyncOpenAI] = {}
25
+ _sync_client_instance = None
26
+ _async_client_instance = None
30
27
 
31
28
  key_sequence = ["choices", 0, "message", "content"]
32
29
  usage_sequence = ["usage"]
33
30
  input_token_name = "prompt_tokens"
34
31
  output_token_name = "completion_tokens"
35
32
 
36
- available_models_url = "https://platform.openai.com/docs/models/gp"
37
-
38
33
  def __init_subclass__(cls, **kwargs):
39
34
  super().__init_subclass__(**kwargs)
40
- # so subclasses that use the OpenAI api key have to create their own instances of the clients
41
- cls._sync_client_instances = {}
42
- cls._async_client_instances = {}
35
+ # so subclasses have to create their own instances of the clients
36
+ cls._sync_client_instance = None
37
+ cls._async_client_instance = None
43
38
 
44
39
  @classmethod
45
- def sync_client(cls, api_key):
46
- if api_key not in cls._sync_client_instances:
47
- client = cls._sync_client_(
48
- api_key=api_key,
49
- base_url=cls._base_url_,
40
+ def sync_client(cls):
41
+ if cls._sync_client_instance is None:
42
+ cls._sync_client_instance = cls._sync_client_(
43
+ api_key=os.getenv(cls._env_key_name_), base_url=cls._base_url_
50
44
  )
51
- cls._sync_client_instances[api_key] = client
52
- client = cls._sync_client_instances[api_key]
53
- return client
45
+ return cls._sync_client_instance
54
46
 
55
47
  @classmethod
56
- def async_client(cls, api_key):
57
- if api_key not in cls._async_client_instances:
58
- client = cls._async_client_(
59
- api_key=api_key,
60
- base_url=cls._base_url_,
48
+ def async_client(cls):
49
+ if cls._async_client_instance is None:
50
+ cls._async_client_instance = cls._async_client_(
51
+ api_key=os.getenv(cls._env_key_name_), base_url=cls._base_url_
61
52
  )
62
- cls._async_client_instances[api_key] = client
63
- client = cls._async_client_instances[api_key]
64
- return client
53
+ return cls._async_client_instance
65
54
 
66
55
  model_exclude_list = [
67
56
  "whisper-1",
@@ -83,24 +72,20 @@ class OpenAIService(InferenceServiceABC):
83
72
  _models_list_cache: List[str] = []
84
73
 
85
74
  @classmethod
86
- def get_model_list(cls, api_key=None):
87
- if api_key is None:
88
- api_key = os.getenv(cls._env_key_name_)
89
- raw_list = cls.sync_client(api_key).models.list()
75
+ def get_model_list(cls):
76
+ raw_list = cls.sync_client().models.list()
90
77
  if hasattr(raw_list, "data"):
91
78
  return raw_list.data
92
79
  else:
93
80
  return raw_list
94
81
 
95
82
  @classmethod
96
- def available(cls, api_token=None) -> List[str]:
97
- if api_token is None:
98
- api_token = os.getenv(cls._env_key_name_)
83
+ def available(cls) -> List[str]:
99
84
  if not cls._models_list_cache:
100
85
  try:
101
86
  cls._models_list_cache = [
102
87
  m.id
103
- for m in cls.get_model_list(api_key=api_token)
88
+ for m in cls.get_model_list()
104
89
  if m.id not in cls.model_exclude_list
105
90
  ]
106
91
  except Exception as e:
@@ -122,6 +107,9 @@ class OpenAIService(InferenceServiceABC):
122
107
  input_token_name = cls.input_token_name
123
108
  output_token_name = cls.output_token_name
124
109
 
110
+ _rpm = cls.get_rpm(cls)
111
+ _tpm = cls.get_tpm(cls)
112
+
125
113
  _inference_service_ = cls._inference_service_
126
114
  _model_ = model_name
127
115
  _parameters_ = {
@@ -135,10 +123,10 @@ class OpenAIService(InferenceServiceABC):
135
123
  }
136
124
 
137
125
  def sync_client(self):
138
- return cls.sync_client(api_key=self.api_token)
126
+ return cls.sync_client()
139
127
 
140
128
  def async_client(self):
141
- return cls.async_client(api_key=self.api_token)
129
+ return cls.async_client()
142
130
 
143
131
  @classmethod
144
132
  def available(cls) -> list[str]:
@@ -187,16 +175,16 @@ class OpenAIService(InferenceServiceABC):
187
175
  ) -> dict[str, Any]:
188
176
  """Calls the OpenAI API and returns the API response."""
189
177
  if files_list:
178
+ encoded_image = files_list[0].base64_string
190
179
  content = [{"type": "text", "text": user_prompt}]
191
- for file_entry in files_list:
192
- content.append(
193
- {
194
- "type": "image_url",
195
- "image_url": {
196
- "url": f"data:{file_entry.mime_type};base64,{file_entry.base64_string}"
197
- },
198
- }
199
- )
180
+ content.append(
181
+ {
182
+ "type": "image_url",
183
+ "image_url": {
184
+ "url": f"data:image/jpeg;base64,{encoded_image}"
185
+ },
186
+ }
187
+ )
200
188
  else:
201
189
  content = user_prompt
202
190
  client = self.async_client()
@@ -51,6 +51,9 @@ class PerplexityService(OpenAIService):
51
51
  input_token_name = cls.input_token_name
52
52
  output_token_name = cls.output_token_name
53
53
 
54
+ _rpm = cls.get_rpm(cls)
55
+ _tpm = cls.get_tpm(cls)
56
+
54
57
  _inference_service_ = cls._inference_service_
55
58
  _model_ = model_name
56
59
 
@@ -2,7 +2,7 @@ from typing import Any, List, Optional
2
2
  import os
3
3
  import asyncio
4
4
  from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
5
- from edsl.language_models.LanguageModel import LanguageModel
5
+ from edsl.language_models import LanguageModel
6
6
  from edsl.inference_services.rate_limits_cache import rate_limits
7
7
  from edsl.utilities.utilities import fix_partial_correct_response
8
8
 
@@ -51,7 +51,6 @@ class TestService(InferenceServiceABC):
51
51
  @property
52
52
  def _canned_response(self):
53
53
  if hasattr(self, "canned_response"):
54
-
55
54
  return self.canned_response
56
55
  else:
57
56
  return "Hello, world"
@@ -64,15 +63,7 @@ class TestService(InferenceServiceABC):
64
63
  files_list: Optional[List["File"]] = None,
65
64
  ) -> dict[str, Any]:
66
65
  await asyncio.sleep(0.1)
67
-
68
- if hasattr(self, "throw_exception") and self.throw_exception:
69
- if hasattr(self, "exception_probability"):
70
- p = self.exception_probability
71
- else:
72
- p = 1
73
-
74
- if random.random() < p:
75
- raise Exception("This is a test error")
66
+ # return {"message": """{"answer": "Hello, world"}"""}
76
67
 
77
68
  if hasattr(self, "func"):
78
69
  return {
@@ -82,6 +73,14 @@ class TestService(InferenceServiceABC):
82
73
  "usage": {"prompt_tokens": 1, "completion_tokens": 1},
83
74
  }
84
75
 
76
+ if hasattr(self, "throw_exception") and self.throw_exception:
77
+ if hasattr(self, "exception_probability"):
78
+ p = self.exception_probability
79
+ else:
80
+ p = 1
81
+
82
+ if random.random() < p:
83
+ raise Exception("This is a test error")
85
84
  return {
86
85
  "message": [{"text": f"{self._canned_response}"}],
87
86
  "usage": {"prompt_tokens": 1, "completion_tokens": 1},
@@ -143,17 +143,15 @@ class TogetherAIService(OpenAIService):
143
143
  _async_client_ = openai.AsyncOpenAI
144
144
 
145
145
  @classmethod
146
- def get_model_list(cls, api_token=None):
146
+ def get_model_list(cls):
147
147
  # Togheter.ai has a different response in model list then openai
148
148
  # and the OpenAI class returns an error when calling .models.list()
149
149
  import requests
150
150
  import os
151
151
 
152
152
  url = "https://api.together.xyz/v1/models?filter=serverless"
153
- if api_token is None:
154
- api_token = os.getenv(cls._env_key_name_)
155
-
156
- headers = {"accept": "application/json", "authorization": f"Bearer {api_token}"}
153
+ token = os.getenv(cls._env_key_name_)
154
+ headers = {"accept": "application/json", "authorization": f"Bearer {token}"}
157
155
 
158
156
  response = requests.get(url, headers=headers)
159
157
  return response.json()
edsl/jobs/Answers.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """This module contains the Answers class, which is a helper class to hold the answers to a survey."""
2
2
 
3
3
  from collections import UserDict
4
+ from rich.table import Table
4
5
  from edsl.data_transfer_models import EDSLResultObjectInput
5
6
 
6
7
 
@@ -21,7 +22,7 @@ class Answers(UserDict):
21
22
  if comment:
22
23
  self[question.question_name + "_comment"] = comment
23
24
 
24
- def replace_missing_answers_with_none(self, survey: "Survey") -> None:
25
+ def replace_missing_answers_with_none(self, survey) -> None:
25
26
  """Replace missing answers with None. Answers can be missing if the agent skips a question."""
26
27
  for question_name in survey.question_names:
27
28
  if question_name not in self:
@@ -36,6 +37,18 @@ class Answers(UserDict):
36
37
  """Return an Answers object from a dictionary."""
37
38
  return cls(d)
38
39
 
40
+ def rich_print(self):
41
+ """Display an object as a table."""
42
+ table = Table(title="Answers")
43
+ table.add_column("Attribute", style="bold")
44
+ table.add_column("Value")
45
+
46
+ to_display = self
47
+ for attr_name, attr_value in to_display.items():
48
+ table.add_row(attr_name, repr(attr_value))
49
+
50
+ return table
51
+
39
52
 
40
53
  if __name__ == "__main__":
41
54
  import doctest