edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev5__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/Base.py +413 -332
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +57 -49
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +1071 -867
- edsl/agents/AgentList.py +551 -413
- edsl/agents/Invigilator.py +284 -233
- edsl/agents/InvigilatorBase.py +257 -270
- edsl/agents/PromptConstructor.py +272 -354
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +2 -3
- edsl/agents/descriptors.py +99 -99
- edsl/agents/prompt_helpers.py +129 -129
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -117
- edsl/auto/StageBase.py +243 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +74 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +218 -224
- edsl/base/Base.py +279 -279
- edsl/config.py +177 -157
- edsl/conversation/Conversation.py +290 -290
- edsl/conversation/car_buying.py +59 -58
- edsl/conversation/chips.py +95 -95
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +1106 -1028
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +573 -555
- edsl/data/CacheEntry.py +230 -233
- edsl/data/CacheHandler.py +168 -149
- edsl/data/RemoteCacheSync.py +186 -78
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +5 -4
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +74 -73
- edsl/enums.py +202 -175
- edsl/exceptions/BaseException.py +21 -21
- edsl/exceptions/__init__.py +54 -54
- edsl/exceptions/agents.py +54 -42
- edsl/exceptions/cache.py +5 -5
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +109 -91
- edsl/exceptions/results.py +29 -29
- edsl/exceptions/scenarios.py +29 -22
- edsl/exceptions/surveys.py +37 -37
- edsl/inference_services/AnthropicService.py +106 -87
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -120
- edsl/inference_services/AzureAI.py +215 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +143 -148
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +80 -147
- edsl/inference_services/InferenceServicesCollection.py +138 -97
- edsl/inference_services/MistralAIService.py +120 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +236 -224
- edsl/inference_services/PerplexityService.py +160 -163
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -89
- edsl/inference_services/TogetherAIService.py +172 -170
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +41 -41
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +43 -56
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +823 -898
- edsl/jobs/JobsChecks.py +172 -147
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +283 -251
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +396 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
- edsl/jobs/runners/JobsRunnerStatus.py +297 -330
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +470 -450
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +161 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +626 -668
- edsl/language_models/ModelList.py +164 -155
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +2 -3
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +156 -156
- edsl/language_models/utilities.py +65 -64
- edsl/notebooks/Notebook.py +263 -258
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +352 -362
- edsl/prompts/__init__.py +2 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -664
- edsl/questions/QuestionBasePromptsMixin.py +221 -217
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +180 -182
- edsl/questions/QuestionFreeText.py +113 -114
- edsl/questions/QuestionFunctional.py +166 -166
- edsl/questions/QuestionList.py +223 -231
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +330 -286
- edsl/questions/QuestionNumerical.py +151 -153
- edsl/questions/QuestionRank.py +314 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/SimpleAskMixin.py +74 -73
- edsl/questions/__init__.py +27 -26
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +90 -87
- edsl/questions/derived/QuestionTopK.py +93 -93
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +427 -413
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
- edsl/questions/question_registry.py +177 -177
- edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/CSSParameterizer.py +108 -108
- edsl/results/Dataset.py +587 -424
- edsl/results/DatasetExportMixin.py +594 -731
- edsl/results/DatasetTree.py +295 -275
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +557 -465
- edsl/results/Results.py +1183 -1165
- edsl/results/ResultsExportMixin.py +45 -43
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/TableDisplay.py +125 -198
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +2 -2
- edsl/results/file_exports.py +252 -0
- edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
- edsl/results/{Selector.py → results_selector.py} +145 -135
- edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +77 -77
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +511 -632
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +498 -601
- edsl/scenarios/ScenarioHtmlMixin.py +65 -64
- edsl/scenarios/ScenarioList.py +1458 -1287
- edsl/scenarios/ScenarioListExportMixin.py +45 -52
- edsl/scenarios/ScenarioListPdfMixin.py +239 -261
- edsl/scenarios/__init__.py +3 -4
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +521 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +327 -326
- edsl/surveys/RuleCollection.py +385 -387
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1280 -1801
- edsl/surveys/SurveyCSS.py +273 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +5 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +60 -56
- edsl/surveys/instructions/ChangeInstruction.py +48 -49
- edsl/surveys/instructions/Instruction.py +56 -65
- edsl/surveys/instructions/InstructionCollection.py +82 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +19 -19
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/interface.py +627 -627
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -263
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +436 -424
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev5.dist-info/RECORD +358 -0
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/WHEEL +1 -1
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev3.dist-info/RECORD +0 -277
edsl/jobs/buckets/TokenBucket.py
CHANGED
@@ -1,251 +1,283 @@
|
|
1
|
-
from typing import Union, List, Any, Optional
|
2
|
-
import asyncio
|
3
|
-
import time
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
self.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
self.
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
return (
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
>>> bucket
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
>>>
|
146
|
-
>>> bucket.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
self.
|
170
|
-
|
171
|
-
|
172
|
-
return
|
173
|
-
|
174
|
-
def
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
return
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
1
|
+
from typing import Union, List, Any, Optional
|
2
|
+
import asyncio
|
3
|
+
import time
|
4
|
+
from threading import RLock
|
5
|
+
from edsl.jobs.decorators import synchronized_class
|
6
|
+
|
7
|
+
from typing import Union, List, Any, Optional
|
8
|
+
import asyncio
|
9
|
+
import time
|
10
|
+
from threading import RLock
|
11
|
+
from edsl.jobs.decorators import synchronized_class
|
12
|
+
|
13
|
+
|
14
|
+
@synchronized_class
|
15
|
+
class TokenBucket:
|
16
|
+
"""This is a token bucket used to respect rate limits to services.
|
17
|
+
It can operate either locally or remotely via a REST API based on initialization parameters.
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __new__(
|
21
|
+
cls,
|
22
|
+
*,
|
23
|
+
bucket_name: str,
|
24
|
+
bucket_type: str,
|
25
|
+
capacity: Union[int, float],
|
26
|
+
refill_rate: Union[int, float],
|
27
|
+
remote_url: Optional[str] = None,
|
28
|
+
):
|
29
|
+
"""Factory method to create either a local or remote token bucket.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
bucket_name: Name of the bucket
|
33
|
+
bucket_type: Type of the bucket
|
34
|
+
capacity: Maximum number of tokens
|
35
|
+
refill_rate: Rate at which tokens are refilled
|
36
|
+
remote_url: If provided, creates a remote token bucket client
|
37
|
+
"""
|
38
|
+
if remote_url is not None:
|
39
|
+
# Import here to avoid circular imports
|
40
|
+
from edsl.jobs.buckets.TokenBucketClient import TokenBucketClient
|
41
|
+
|
42
|
+
return TokenBucketClient(
|
43
|
+
bucket_name=bucket_name,
|
44
|
+
bucket_type=bucket_type,
|
45
|
+
capacity=capacity,
|
46
|
+
refill_rate=refill_rate,
|
47
|
+
api_base_url=remote_url,
|
48
|
+
)
|
49
|
+
|
50
|
+
# Create a local token bucket
|
51
|
+
instance = super(TokenBucket, cls).__new__(cls)
|
52
|
+
return instance
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
*,
|
57
|
+
bucket_name: str,
|
58
|
+
bucket_type: str,
|
59
|
+
capacity: Union[int, float],
|
60
|
+
refill_rate: Union[int, float],
|
61
|
+
remote_url: Optional[str] = None,
|
62
|
+
):
|
63
|
+
# Skip initialization if this is a remote bucket
|
64
|
+
if remote_url is not None:
|
65
|
+
return
|
66
|
+
|
67
|
+
self.bucket_name = bucket_name
|
68
|
+
self.bucket_type = bucket_type
|
69
|
+
self.capacity = capacity
|
70
|
+
self.added_tokens = 0
|
71
|
+
self._lock = RLock()
|
72
|
+
|
73
|
+
self.target_rate = (
|
74
|
+
capacity * 60
|
75
|
+
) # set this here because it can change with turbo mode
|
76
|
+
|
77
|
+
self._old_capacity = capacity
|
78
|
+
self.tokens = capacity # Current number of available tokens
|
79
|
+
self.refill_rate = refill_rate # Rate at which tokens are refilled
|
80
|
+
self._old_refill_rate = refill_rate
|
81
|
+
self.last_refill = time.monotonic() # Last refill time
|
82
|
+
self.log: List[Any] = []
|
83
|
+
self.turbo_mode = False
|
84
|
+
|
85
|
+
self.creation_time = time.monotonic()
|
86
|
+
|
87
|
+
self.num_requests = 0
|
88
|
+
self.num_released = 0
|
89
|
+
self.tokens_returned = 0
|
90
|
+
|
91
|
+
def turbo_mode_on(self):
|
92
|
+
"""Set the refill rate to infinity."""
|
93
|
+
if self.turbo_mode:
|
94
|
+
pass
|
95
|
+
else:
|
96
|
+
# pass
|
97
|
+
self.turbo_mode = True
|
98
|
+
self.capacity = float("inf")
|
99
|
+
self.refill_rate = float("inf")
|
100
|
+
|
101
|
+
def turbo_mode_off(self):
|
102
|
+
"""Restore the refill rate to its original value."""
|
103
|
+
self.turbo_mode = False
|
104
|
+
self.capacity = self._old_capacity
|
105
|
+
self.refill_rate = self._old_refill_rate
|
106
|
+
|
107
|
+
def __add__(self, other) -> "TokenBucket":
|
108
|
+
"""Combine two token buckets.
|
109
|
+
|
110
|
+
The resulting bucket has the minimum capacity and refill rate of the two buckets.
|
111
|
+
This is useful, for example, if we have two calls to the same model on the same service but have different temperatures.
|
112
|
+
"""
|
113
|
+
return TokenBucket(
|
114
|
+
bucket_name=self.bucket_name,
|
115
|
+
bucket_type=self.bucket_type,
|
116
|
+
capacity=min(self.capacity, other.capacity),
|
117
|
+
refill_rate=min(self.refill_rate, other.refill_rate),
|
118
|
+
)
|
119
|
+
|
120
|
+
def __repr__(self):
|
121
|
+
return f"TokenBucket(bucket_name={self.bucket_name}, bucket_type='{self.bucket_type}', capacity={self.capacity}, refill_rate={self.refill_rate})"
|
122
|
+
|
123
|
+
def add_tokens(self, tokens: Union[int, float]) -> None:
|
124
|
+
"""Add tokens to the bucket, up to the maximum capacity.
|
125
|
+
|
126
|
+
:param tokens: The number of tokens to add to the bucket.
|
127
|
+
|
128
|
+
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=10, refill_rate=1)
|
129
|
+
>>> bucket.tokens
|
130
|
+
10
|
131
|
+
>>> bucket.add_tokens(5)
|
132
|
+
>>> bucket.tokens
|
133
|
+
10
|
134
|
+
"""
|
135
|
+
self.tokens_returned += tokens
|
136
|
+
self.tokens = min(self.capacity, self.tokens + tokens)
|
137
|
+
self.log.append((time.monotonic(), self.tokens))
|
138
|
+
|
139
|
+
def refill(self) -> None:
|
140
|
+
"""Refill the bucket with new tokens based on elapsed time.
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=10, refill_rate=1)
|
145
|
+
>>> bucket.tokens = 0
|
146
|
+
>>> bucket.refill()
|
147
|
+
>>> bucket.tokens > 0
|
148
|
+
True
|
149
|
+
"""
|
150
|
+
"""Refill the bucket with new tokens based on elapsed time."""
|
151
|
+
now = time.monotonic()
|
152
|
+
# print(f"Time is now: {now}; Last refill time: {self.last_refill}")
|
153
|
+
elapsed = now - self.last_refill
|
154
|
+
# print("Elapsed time: ", elapsed)
|
155
|
+
refill_amount = elapsed * self.refill_rate
|
156
|
+
self.tokens = min(self.capacity, self.tokens + refill_amount)
|
157
|
+
self.last_refill = now
|
158
|
+
|
159
|
+
if self.tokens < self.capacity:
|
160
|
+
pass
|
161
|
+
# print(f"Refilled. Current tokens: {self.tokens:.4f}")
|
162
|
+
# print(f"Elapsed time: {elapsed:.4f} seconds")
|
163
|
+
# print(f"Refill amount: {refill_amount:.4f}")
|
164
|
+
|
165
|
+
self.log.append((now, self.tokens))
|
166
|
+
|
167
|
+
def wait_time(self, requested_tokens: Union[float, int]) -> float:
|
168
|
+
"""Calculate the time to wait for the requested number of tokens."""
|
169
|
+
# self.refill() # Update the current token count
|
170
|
+
if self.tokens >= requested_tokens:
|
171
|
+
return 0
|
172
|
+
return (requested_tokens - self.tokens) / self.refill_rate
|
173
|
+
|
174
|
+
async def get_tokens(
|
175
|
+
self, amount: Union[int, float] = 1, cheat_bucket_capacity=True
|
176
|
+
) -> None:
|
177
|
+
"""Wait for the specified number of tokens to become available.
|
178
|
+
|
179
|
+
|
180
|
+
:param amount: The number of tokens
|
181
|
+
:param warn: If True, warn if the requested amount exceeds the bucket capacity.
|
182
|
+
|
183
|
+
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=10, refill_rate=1)
|
184
|
+
>>> asyncio.run(bucket.get_tokens(5))
|
185
|
+
>>> bucket.tokens
|
186
|
+
5
|
187
|
+
>>> asyncio.run(bucket.get_tokens(9))
|
188
|
+
>>> bucket.tokens < 1
|
189
|
+
True
|
190
|
+
|
191
|
+
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=10, refill_rate=1)
|
192
|
+
>>> asyncio.run(bucket.get_tokens(11, cheat_bucket_capacity=False))
|
193
|
+
Traceback (most recent call last):
|
194
|
+
...
|
195
|
+
ValueError: Requested amount exceeds bucket capacity. Bucket capacity: 10, requested amount: 11. As the bucket never overflows, the requested amount will never be available.
|
196
|
+
>>> asyncio.run(bucket.get_tokens(11, cheat_bucket_capacity=True))
|
197
|
+
>>> bucket.capacity
|
198
|
+
12.100000000000001
|
199
|
+
"""
|
200
|
+
self.num_requests += amount
|
201
|
+
if amount >= self.capacity:
|
202
|
+
if not cheat_bucket_capacity:
|
203
|
+
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."
|
204
|
+
raise ValueError(msg)
|
205
|
+
else:
|
206
|
+
self.capacity = amount * 1.10
|
207
|
+
self._old_capacity = self.capacity
|
208
|
+
|
209
|
+
start_time = time.monotonic()
|
210
|
+
while True:
|
211
|
+
self.refill() # Refill based on elapsed time
|
212
|
+
if self.tokens >= amount:
|
213
|
+
self.tokens -= amount
|
214
|
+
break
|
215
|
+
|
216
|
+
wait_time = self.wait_time(amount)
|
217
|
+
if wait_time > 0:
|
218
|
+
await asyncio.sleep(wait_time)
|
219
|
+
|
220
|
+
self.num_released += amount
|
221
|
+
now = time.monotonic()
|
222
|
+
self.log.append((now, self.tokens))
|
223
|
+
return None
|
224
|
+
|
225
|
+
def get_log(self) -> list[tuple]:
|
226
|
+
return self.log
|
227
|
+
|
228
|
+
def visualize(self):
|
229
|
+
"""Visualize the token bucket over time."""
|
230
|
+
times, tokens = zip(*self.get_log())
|
231
|
+
start_time = times[0]
|
232
|
+
times = [t - start_time for t in times] # Normalize time to start from 0
|
233
|
+
from matplotlib import pyplot as plt
|
234
|
+
|
235
|
+
plt.figure(figsize=(10, 6))
|
236
|
+
plt.plot(times, tokens, label="Tokens Available")
|
237
|
+
plt.xlabel("Time (seconds)", fontsize=12)
|
238
|
+
plt.ylabel("Number of Tokens", fontsize=12)
|
239
|
+
details = f"{self.bucket_name} ({self.bucket_type}) Bucket Usage Over Time\nCapacity: {self.capacity:.1f}, Refill Rate: {self.refill_rate:.1f}/second"
|
240
|
+
plt.title(details, fontsize=14)
|
241
|
+
|
242
|
+
plt.legend()
|
243
|
+
plt.grid(True)
|
244
|
+
plt.tight_layout()
|
245
|
+
plt.show()
|
246
|
+
|
247
|
+
def get_throughput(self, time_window: Optional[float] = None) -> float:
|
248
|
+
"""
|
249
|
+
Calculate the empirical bucket throughput in tokens per minute for the specified time window.
|
250
|
+
|
251
|
+
:param time_window: The time window in seconds to calculate the throughput for.
|
252
|
+
:return: The throughput in tokens per minute.
|
253
|
+
|
254
|
+
>>> bucket = TokenBucket(bucket_name="test", bucket_type="test", capacity=100, refill_rate=10)
|
255
|
+
>>> asyncio.run(bucket.get_tokens(50))
|
256
|
+
>>> time.sleep(1) # Wait for 1 second
|
257
|
+
>>> asyncio.run(bucket.get_tokens(30))
|
258
|
+
>>> throughput = bucket.get_throughput(1)
|
259
|
+
>>> 4750 < throughput < 4850
|
260
|
+
True
|
261
|
+
"""
|
262
|
+
now = time.monotonic()
|
263
|
+
|
264
|
+
if time_window is None:
|
265
|
+
start_time = self.creation_time
|
266
|
+
else:
|
267
|
+
start_time = now - time_window
|
268
|
+
|
269
|
+
if start_time < self.creation_time:
|
270
|
+
start_time = self.creation_time
|
271
|
+
|
272
|
+
elapsed_time = now - start_time
|
273
|
+
|
274
|
+
if elapsed_time == 0:
|
275
|
+
return self.num_released / 0.001
|
276
|
+
|
277
|
+
return (self.num_released / elapsed_time) * 60
|
278
|
+
|
279
|
+
|
280
|
+
if __name__ == "__main__":
|
281
|
+
import doctest
|
282
|
+
|
283
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|