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,215 +0,0 @@
1
- from typing import Any, List, Tuple, Optional, Dict, TYPE_CHECKING, Union, Generator
2
- from concurrent.futures import ThreadPoolExecutor, as_completed
3
- from collections import UserList
4
-
5
- from edsl.inference_services.ServiceAvailability import ServiceAvailability
6
- from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
7
- from edsl.inference_services.data_structures import ModelNamesList
8
- from edsl.enums import InferenceServiceLiteral
9
-
10
- from edsl.inference_services.data_structures import LanguageModelInfo
11
- from edsl.inference_services.AvailableModelCacheHandler import (
12
- AvailableModelCacheHandler,
13
- )
14
-
15
-
16
- from edsl.inference_services.data_structures import AvailableModels
17
-
18
-
19
- class AvailableModelFetcher:
20
- """Fetches available models from the various services with JSON caching."""
21
-
22
- service_availability = ServiceAvailability()
23
- CACHE_VALIDITY_HOURS = 48 # Cache validity period in hours
24
-
25
- def __init__(
26
- self,
27
- services: List["InferenceServiceABC"],
28
- added_models: Dict[str, List[str]],
29
- verbose: bool = False,
30
- use_cache: bool = True,
31
- ):
32
- self.services = services
33
- self.added_models = added_models
34
- self._service_map = {
35
- service._inference_service_: service for service in services
36
- }
37
- self.verbose = verbose
38
- if use_cache:
39
- self.cache_handler = AvailableModelCacheHandler()
40
- else:
41
- self.cache_handler = None
42
-
43
- @property
44
- def num_cache_entries(self):
45
- return self.cache_handler.num_cache_entries
46
-
47
- @property
48
- def path_to_db(self):
49
- return self.cache_handler.path_to_db
50
-
51
- def reset_cache(self):
52
- if self.cache_handler:
53
- self.cache_handler.reset_cache()
54
-
55
- def available(
56
- self,
57
- service: Optional[InferenceServiceABC] = None,
58
- force_refresh: bool = False,
59
- ) -> List[LanguageModelInfo]:
60
- """
61
- Get available models from all services, using cached data when available.
62
-
63
- :param service: Optional[InferenceServiceABC] - If specified, only fetch models for this service.
64
-
65
- >>> from edsl.inference_services.OpenAIService import OpenAIService
66
- >>> af = AvailableModelFetcher([OpenAIService()], {})
67
- >>> af.available(service="openai")
68
- [LanguageModelInfo(model_name='...', service_name='openai'), ...]
69
-
70
- Returns a list of [model, service_name, index] entries.
71
- """
72
-
73
- if service: # they passed a specific service
74
- matching_models, _ = self.get_available_models_by_service(
75
- service=service, force_refresh=force_refresh
76
- )
77
- return matching_models
78
-
79
- # Nope, we need to fetch them all
80
- all_models = self._get_all_models()
81
-
82
- # if self.cache_handler:
83
- # self.cache_handler.add_models_to_cache(all_models)
84
-
85
- return all_models
86
-
87
- def get_available_models_by_service(
88
- self,
89
- service: Union["InferenceServiceABC", InferenceServiceLiteral],
90
- force_refresh: bool = False,
91
- ) -> Tuple[AvailableModels, InferenceServiceLiteral]:
92
- """Get models for a single service.
93
-
94
- :param service: InferenceServiceABC - e.g., OpenAIService or "openai"
95
- :return: Tuple[List[LanguageModelInfo], InferenceServiceLiteral]
96
- """
97
- if isinstance(service, str):
98
- service = self._fetch_service_by_service_name(service)
99
-
100
- if not force_refresh:
101
- models_from_cache = self.cache_handler.models(
102
- service=service._inference_service_
103
- )
104
- if self.verbose:
105
- print(
106
- "Searching cache for models with service name:",
107
- service._inference_service_,
108
- )
109
- print("Got models from cache:", models_from_cache)
110
- else:
111
- models_from_cache = None
112
-
113
- if models_from_cache:
114
- # print(f"Models from cache for {service}: {models_from_cache}")
115
- # print(hasattr(models_from_cache[0], "service_name"))
116
- return models_from_cache, service._inference_service_
117
- else:
118
- return self.get_available_models_by_service_fresh(service)
119
-
120
- def get_available_models_by_service_fresh(
121
- self, service: Union["InferenceServiceABC", InferenceServiceLiteral]
122
- ) -> Tuple[AvailableModels, InferenceServiceLiteral]:
123
- """Get models for a single service. This method always fetches fresh data.
124
-
125
- :param service: InferenceServiceABC - e.g., OpenAIService or "openai"
126
- :return: Tuple[List[LanguageModelInfo], InferenceServiceLiteral]
127
- """
128
- if isinstance(service, str):
129
- service = self._fetch_service_by_service_name(service)
130
-
131
- service_models: ModelNamesList = (
132
- self.service_availability.get_service_available(service, warn=False)
133
- )
134
- service_name = service._inference_service_
135
-
136
- if not service_models:
137
- import warnings
138
-
139
- warnings.warn(f"No models found for service {service_name}")
140
- return [], service_name
141
-
142
- models_list = AvailableModels(
143
- [
144
- LanguageModelInfo(
145
- model_name=model_name,
146
- service_name=service_name,
147
- )
148
- for model_name in service_models
149
- ]
150
- )
151
- self.cache_handler.add_models_to_cache(models_list) # update the cache
152
- return models_list, service_name
153
-
154
- def _fetch_service_by_service_name(
155
- self, service_name: InferenceServiceLiteral
156
- ) -> "InferenceServiceABC":
157
- """The service name is the _inference_service_ attribute of the service."""
158
- if service_name in self._service_map:
159
- return self._service_map[service_name]
160
- raise ValueError(f"Service {service_name} not found")
161
-
162
- def _get_all_models(self, force_refresh=False) -> List[LanguageModelInfo]:
163
- all_models = []
164
- with ThreadPoolExecutor(max_workers=min(len(self.services), 10)) as executor:
165
- future_to_service = {
166
- executor.submit(
167
- self.get_available_models_by_service, service, force_refresh
168
- ): service
169
- for service in self.services
170
- }
171
-
172
- for future in as_completed(future_to_service):
173
- try:
174
- models, service_name = future.result()
175
- all_models.extend(models)
176
-
177
- # Add any additional models for this service
178
- for model in self.added_models.get(service_name, []):
179
- all_models.append(
180
- LanguageModelInfo(
181
- model_name=model, service_name=service_name
182
- )
183
- )
184
-
185
- except Exception as exc:
186
- print(f"Service query failed for service {service_name}: {exc}")
187
- continue
188
-
189
- return AvailableModels(all_models)
190
-
191
-
192
- def main():
193
- from edsl.inference_services.OpenAIService import OpenAIService
194
-
195
- af = AvailableModelFetcher([OpenAIService()], {}, verbose=True)
196
- # print(af.available(service="openai"))
197
- all_models = AvailableModelFetcher([OpenAIService()], {})._get_all_models(
198
- force_refresh=True
199
- )
200
- print(all_models)
201
-
202
-
203
- if __name__ == "__main__":
204
- import doctest
205
-
206
- doctest.testmod(optionflags=doctest.ELLIPSIS)
207
- # main()
208
-
209
- # from edsl.inference_services.OpenAIService import OpenAIService
210
-
211
- # af = AvailableModelFetcher([OpenAIService()], {}, verbose=True)
212
- # # print(af.available(service="openai"))
213
-
214
- # all_models = AvailableModelFetcher([OpenAIService()], {})._get_all_models()
215
- # print(all_models)
@@ -1,135 +0,0 @@
1
- from enum import Enum
2
- from typing import List, Optional, TYPE_CHECKING
3
- from functools import partial
4
- import warnings
5
-
6
- from edsl.inference_services.data_structures import AvailableModels, ModelNamesList
7
-
8
- if TYPE_CHECKING:
9
- from edsl.inference_services.InferenceServiceABC import InferenceServiceABC
10
-
11
-
12
- class ModelSource(Enum):
13
- LOCAL = "local"
14
- COOP = "coop"
15
- CACHE = "cache"
16
-
17
-
18
- class ServiceAvailability:
19
- """This class is responsible for fetching the available models from different sources."""
20
-
21
- _coop_model_list = None
22
-
23
- def __init__(self, source_order: Optional[List[ModelSource]] = None):
24
- """
25
- Initialize with custom source order.
26
- Default order is LOCAL -> COOP -> CACHE
27
- """
28
- self.source_order = source_order or [
29
- ModelSource.LOCAL,
30
- ModelSource.COOP,
31
- ModelSource.CACHE,
32
- ]
33
-
34
- # Map sources to their fetch functions
35
- self._source_fetchers = {
36
- ModelSource.LOCAL: self._fetch_from_local_service,
37
- ModelSource.COOP: self._fetch_from_coop,
38
- ModelSource.CACHE: self._fetch_from_cache,
39
- }
40
-
41
- @classmethod
42
- def models_from_coop(cls) -> AvailableModels:
43
- if not cls._coop_model_list:
44
- from edsl.coop.coop import Coop
45
-
46
- c = Coop()
47
- coop_model_list = c.fetch_models()
48
- cls._coop_model_list = coop_model_list
49
- return cls._coop_model_list
50
-
51
- def get_service_available(
52
- self, service: "InferenceServiceABC", warn: bool = False
53
- ) -> ModelNamesList:
54
- """
55
- Try to fetch available models from sources in specified order.
56
- Returns first successful result.
57
- """
58
- last_error = None
59
-
60
- for source in self.source_order:
61
- try:
62
- fetch_func = partial(self._source_fetchers[source], service)
63
- result = fetch_func()
64
-
65
- # Cache successful result
66
- service._models_list_cache = result
67
- return result
68
-
69
- except Exception as e:
70
- last_error = e
71
- if warn:
72
- self._warn_source_failed(service, source)
73
- continue
74
-
75
- # If we get here, all sources failed
76
- raise RuntimeError(
77
- f"All sources failed to fetch models. Last error: {last_error}"
78
- )
79
-
80
- @staticmethod
81
- def _fetch_from_local_service(service: "InferenceServiceABC") -> ModelNamesList:
82
- """Attempt to fetch models directly from the service."""
83
- return service.available()
84
-
85
- @classmethod
86
- def _fetch_from_coop(cls, service: "InferenceServiceABC") -> ModelNamesList:
87
- """Fetch models from Coop."""
88
- models_from_coop = cls.models_from_coop()
89
- return models_from_coop.get(service._inference_service_, [])
90
-
91
- @staticmethod
92
- def _fetch_from_cache(service: "InferenceServiceABC") -> ModelNamesList:
93
- """Fetch models from local cache."""
94
- from edsl.inference_services.models_available_cache import models_available
95
-
96
- return models_available.get(service._inference_service_, [])
97
-
98
- def _warn_source_failed(self, service: "InferenceServiceABC", source: ModelSource):
99
- """Display appropriate warning message based on failed source."""
100
- messages = {
101
- ModelSource.LOCAL: f"""Error getting models for {service._inference_service_}.
102
- Check that you have properly stored your Expected Parrot API key and activated remote inference,
103
- or stored your own API keys for the language models that you want to use.
104
- See https://docs.expectedparrot.com/en/latest/api_keys.html for instructions on storing API keys.
105
- Trying next source.""",
106
- ModelSource.COOP: f"Error getting models from Coop for {service._inference_service_}. Trying next source.",
107
- ModelSource.CACHE: f"Error getting models from cache for {service._inference_service_}.",
108
- }
109
- warnings.warn(messages[source], UserWarning)
110
-
111
-
112
- if __name__ == "__main__":
113
- # sa = ServiceAvailability()
114
- # models_from_coop = sa.models_from_coop()
115
- # print(models_from_coop)
116
- from edsl.inference_services.OpenAIService import OpenAIService
117
-
118
- openai_models = ServiceAvailability._fetch_from_local_service(OpenAIService())
119
- print(openai_models)
120
-
121
- # Example usage:
122
- """
123
- # Default order (LOCAL -> COOP -> CACHE)
124
- availability = ServiceAvailability()
125
-
126
- # Custom order (COOP -> LOCAL -> CACHE)
127
- availability_coop_first = ServiceAvailability([
128
- ModelSource.COOP,
129
- ModelSource.LOCAL,
130
- ModelSource.CACHE
131
- ])
132
-
133
- # Get available models using custom order
134
- models = availability_coop_first.get_service_available(service, warn=True)
135
- """
@@ -1,134 +0,0 @@
1
- from collections import UserDict, defaultdict, UserList
2
- from typing import Union, Optional, List
3
- from edsl.enums import InferenceServiceLiteral
4
- from dataclasses import dataclass
5
-
6
-
7
- @dataclass
8
- class LanguageModelInfo:
9
- """A dataclass for storing information about a language model.
10
-
11
-
12
- >>> LanguageModelInfo("gpt-4-1106-preview", "openai")
13
- LanguageModelInfo(model_name='gpt-4-1106-preview', service_name='openai')
14
-
15
- >>> model_name, service = LanguageModelInfo.example()
16
- >>> model_name
17
- 'gpt-4-1106-preview'
18
-
19
- >>> LanguageModelInfo.example().service_name
20
- 'openai'
21
-
22
- """
23
-
24
- model_name: str
25
- service_name: str
26
-
27
- def __iter__(self):
28
- yield self.model_name
29
- yield self.service_name
30
-
31
- def __getitem__(self, key: int) -> str:
32
- import warnings
33
-
34
- warnings.warn(
35
- "Accessing LanguageModelInfo via index is deprecated. "
36
- "Please use .model_name, .service_name, or .index attributes instead.",
37
- DeprecationWarning,
38
- stacklevel=2,
39
- )
40
-
41
- if key == 0:
42
- return self.model_name
43
- elif key == 1:
44
- return self.service_name
45
- else:
46
- raise IndexError("Index out of range")
47
-
48
- @classmethod
49
- def example(cls) -> "LanguageModelInfo":
50
- return cls("gpt-4-1106-preview", "openai")
51
-
52
-
53
- class ModelNamesList(UserList):
54
- pass
55
-
56
-
57
- class AvailableModels(UserList):
58
-
59
- def __init__(self, data: List[LanguageModelInfo]) -> None:
60
- super().__init__(data)
61
-
62
- def __contains__(self, model_name: str) -> bool:
63
- for model_entry in self:
64
- if model_entry.model_name == model_name:
65
- return True
66
- return False
67
-
68
- def print(self):
69
- return self.to_dataset().print()
70
-
71
- def to_dataset(self):
72
- from edsl.scenarios.ScenarioList import ScenarioList
73
-
74
- models, services = zip(
75
- *[(model.model_name, model.service_name) for model in self]
76
- )
77
- return (
78
- ScenarioList.from_list("model", models)
79
- .add_list("service", services)
80
- .to_dataset()
81
- )
82
-
83
- def to_model_list(self):
84
- from edsl.language_models.ModelList import ModelList
85
-
86
- return ModelList.from_available_models(self)
87
-
88
- def search(
89
- self, pattern: str, service_name: Optional[str] = None, regex: bool = False
90
- ) -> "AvailableModels":
91
- import re
92
-
93
- if not regex:
94
- # Escape special regex characters except *
95
- pattern = re.escape(pattern).replace(r"\*", ".*")
96
-
97
- try:
98
- regex = re.compile(pattern)
99
- avm = AvailableModels(
100
- [
101
- entry
102
- for entry in self
103
- if regex.search(entry.model_name)
104
- and (service_name is None or entry.service_name == service_name)
105
- ]
106
- )
107
- if len(avm) == 0:
108
- raise ValueError(
109
- "No models found matching the search pattern: " + pattern
110
- )
111
- else:
112
- return avm
113
- except re.error as e:
114
- raise ValueError(f"Invalid regular expression pattern: {e}")
115
-
116
-
117
- class ServiceToModelsMapping(UserDict):
118
- def __init__(self, data: dict) -> None:
119
- super().__init__(data)
120
-
121
- @property
122
- def service_names(self) -> list[str]:
123
- return list(self.data.keys())
124
-
125
- def _validate_service_names(self):
126
- for service in self.service_names:
127
- if service not in InferenceServiceLiteral:
128
- raise ValueError(f"Invalid service name: {service}")
129
-
130
- def model_to_services(self) -> dict:
131
- self._model_to_service = defaultdict(list)
132
- for service, models in self.data.items():
133
- for model in models:
134
- self._model_to_service[model].append(service)
@@ -1,223 +0,0 @@
1
- import copy
2
- import asyncio
3
-
4
- from typing import Union, Type, Callable, TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- from edsl.questions.QuestionBase import QuestionBase
8
- from edsl.jobs.interviews.Interview import Interview
9
- from edsl.language_models.key_management.KeyLookup import KeyLookup
10
-
11
- from edsl.surveys.base import EndOfSurvey
12
- from edsl.jobs.tasks.task_status_enum import TaskStatus
13
-
14
- from edsl.jobs.FetchInvigilator import FetchInvigilator
15
- from edsl.exceptions.language_models import LanguageModelNoResponseError
16
- from edsl.exceptions.questions import QuestionAnswerValidationError
17
- from edsl.data_transfer_models import AgentResponseDict, EDSLResultObjectInput
18
-
19
- from edsl.jobs.Answers import Answers
20
-
21
-
22
- class RetryConfig:
23
- from edsl.config import CONFIG
24
-
25
- EDSL_BACKOFF_START_SEC = float(CONFIG.get("EDSL_BACKOFF_START_SEC"))
26
- EDSL_BACKOFF_MAX_SEC = float(CONFIG.get("EDSL_BACKOFF_MAX_SEC"))
27
- EDSL_MAX_ATTEMPTS = int(CONFIG.get("EDSL_MAX_ATTEMPTS"))
28
-
29
-
30
- class SkipHandler:
31
-
32
- def __init__(self, interview: "Interview"):
33
- self.interview = interview
34
- self.question_index = self.interview.to_index
35
-
36
- self.skip_function: Callable = (
37
- self.interview.survey.rule_collection.skip_question_before_running
38
- )
39
-
40
- def should_skip(self, current_question: "QuestionBase") -> bool:
41
- """Determine if the current question should be skipped."""
42
- current_question_index = self.question_index[current_question.question_name]
43
- combined_answers = (
44
- self.interview.answers
45
- | self.interview.scenario
46
- | self.interview.agent["traits"]
47
- )
48
- return self.skip_function(current_question_index, combined_answers)
49
-
50
- def cancel_skipped_questions(self, current_question: "QuestionBase") -> None:
51
- """Cancel the tasks for questions that should be skipped."""
52
- current_question_index: int = self.interview.to_index[
53
- current_question.question_name
54
- ]
55
- answers = (
56
- self.interview.answers
57
- | self.interview.scenario
58
- | self.interview.agent["traits"]
59
- )
60
-
61
- # Get the index of the next question, which could also be the end of the survey
62
- next_question: Union[int, EndOfSurvey] = (
63
- self.interview.survey.rule_collection.next_question(
64
- q_now=current_question_index,
65
- answers=answers,
66
- )
67
- )
68
-
69
- def cancel_between(start, end):
70
- """Cancel the tasks for questions between the start and end indices."""
71
- for i in range(start, end):
72
- self.interview.tasks[i].cancel()
73
-
74
- if (next_question_index := next_question.next_q) == EndOfSurvey:
75
- cancel_between(
76
- current_question_index + 1, len(self.interview.survey.questions)
77
- )
78
- return
79
-
80
- if next_question_index > (current_question_index + 1):
81
- cancel_between(current_question_index + 1, next_question_index)
82
-
83
-
84
- class AnswerQuestionFunctionConstructor:
85
- """Constructs a function that answers a question and records the answer."""
86
-
87
- def __init__(self, interview: "Interview", key_lookup: "KeyLookup"):
88
- self.interview = interview
89
- self.key_lookup = key_lookup
90
-
91
- self.had_language_model_no_response_error: bool = False
92
- self.question_index = self.interview.to_index
93
-
94
- self.skip_function: Callable = (
95
- self.interview.survey.rule_collection.skip_question_before_running
96
- )
97
-
98
- self.invigilator_fetcher = FetchInvigilator(
99
- self.interview, key_lookup=self.key_lookup
100
- )
101
- self.skip_handler = SkipHandler(self.interview)
102
-
103
- def _handle_exception(
104
- self, e: Exception, invigilator: "InvigilatorBase", task=None
105
- ):
106
- """Handle an exception that occurred while answering a question."""
107
-
108
- from edsl.jobs.interviews.InterviewExceptionEntry import InterviewExceptionEntry
109
-
110
- answers = copy.copy(
111
- self.interview.answers
112
- ) # copy to freeze the answers here for logging
113
- exception_entry = InterviewExceptionEntry(
114
- exception=e,
115
- invigilator=invigilator,
116
- answers=answers,
117
- )
118
- if task:
119
- task.task_status = TaskStatus.FAILED
120
-
121
- self.interview.exceptions.add(
122
- invigilator.question.question_name, exception_entry
123
- )
124
-
125
- if self.interview.raise_validation_errors and isinstance(
126
- e, QuestionAnswerValidationError
127
- ):
128
- raise e
129
-
130
- stop_on_exception = getattr(self.interview, "stop_on_exception", False)
131
- if stop_on_exception:
132
- raise e
133
-
134
- def __call__(self):
135
- return self.answer_question_and_record_task
136
-
137
- async def answer_question_and_record_task(
138
- self,
139
- *,
140
- question: "QuestionBase",
141
- task=None,
142
- ) -> "AgentResponseDict":
143
-
144
- from tenacity import (
145
- retry,
146
- stop_after_attempt,
147
- wait_exponential,
148
- retry_if_exception_type,
149
- RetryError,
150
- )
151
-
152
- @retry(
153
- stop=stop_after_attempt(RetryConfig.EDSL_MAX_ATTEMPTS),
154
- wait=wait_exponential(
155
- multiplier=RetryConfig.EDSL_BACKOFF_START_SEC,
156
- max=RetryConfig.EDSL_BACKOFF_MAX_SEC,
157
- ),
158
- retry=retry_if_exception_type(LanguageModelNoResponseError),
159
- reraise=True,
160
- )
161
- async def attempt_answer():
162
- invigilator = self.invigilator_fetcher(question)
163
-
164
- if self.skip_handler.should_skip(question):
165
- return invigilator.get_failed_task_result(
166
- failure_reason="Question skipped."
167
- )
168
-
169
- try:
170
- response: EDSLResultObjectInput = (
171
- await invigilator.async_answer_question()
172
- )
173
- if response.validated:
174
- self.interview.answers.add_answer(
175
- response=response, question=question
176
- )
177
-
178
- self.skip_handler.cancel_skipped_questions(question)
179
- else:
180
- if (
181
- hasattr(response, "exception_occurred")
182
- and response.exception_occurred
183
- ):
184
- raise response.exception_occurred
185
-
186
- except QuestionAnswerValidationError as e:
187
- self._handle_exception(e, invigilator, task)
188
- return invigilator.get_failed_task_result(
189
- failure_reason="Question answer validation failed."
190
- )
191
-
192
- except asyncio.TimeoutError as e:
193
- self._handle_exception(e, invigilator, task)
194
- had_language_model_no_response_error = True
195
- raise LanguageModelNoResponseError(
196
- f"Language model timed out for question '{question.question_name}.'"
197
- )
198
-
199
- except Exception as e:
200
- self._handle_exception(e, invigilator, task)
201
-
202
- if "response" not in locals():
203
- had_language_model_no_response_error = True
204
- raise LanguageModelNoResponseError(
205
- f"Language model did not return a response for question '{question.question_name}.'"
206
- )
207
-
208
- if (
209
- question.question_name in self.interview.exceptions
210
- and had_language_model_no_response_error
211
- ):
212
- self.interview.exceptions.record_fixed_question(question.question_name)
213
-
214
- return response
215
-
216
- try:
217
- return await attempt_answer()
218
- except RetryError as retry_error:
219
- original_error = retry_error.last_attempt.exception()
220
- self._handle_exception(
221
- original_error, self.invigilator_fetcher(question), task
222
- )
223
- raise original_error