edsl 0.1.39.dev2__py3-none-any.whl → 0.1.39.dev3__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 (334) hide show
  1. edsl/Base.py +332 -385
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +49 -57
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +867 -1079
  7. edsl/agents/AgentList.py +413 -551
  8. edsl/agents/Invigilator.py +233 -285
  9. edsl/agents/InvigilatorBase.py +270 -254
  10. edsl/agents/PromptConstructor.py +354 -252
  11. edsl/agents/__init__.py +3 -2
  12. edsl/agents/descriptors.py +99 -99
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +279 -279
  26. edsl/config.py +157 -177
  27. edsl/conversation/Conversation.py +290 -290
  28. edsl/conversation/car_buying.py +58 -59
  29. edsl/conversation/chips.py +95 -95
  30. edsl/conversation/mug_negotiation.py +81 -81
  31. edsl/conversation/next_speaker_utilities.py +93 -93
  32. edsl/coop/PriceFetcher.py +54 -54
  33. edsl/coop/__init__.py +2 -2
  34. edsl/coop/coop.py +1028 -1090
  35. edsl/coop/utils.py +131 -131
  36. edsl/data/Cache.py +555 -562
  37. edsl/data/CacheEntry.py +233 -230
  38. edsl/data/CacheHandler.py +149 -170
  39. edsl/data/RemoteCacheSync.py +78 -78
  40. edsl/data/SQLiteDict.py +292 -292
  41. edsl/data/__init__.py +4 -5
  42. edsl/data/orm.py +10 -10
  43. edsl/data_transfer_models.py +73 -74
  44. edsl/enums.py +175 -195
  45. edsl/exceptions/BaseException.py +21 -21
  46. edsl/exceptions/__init__.py +54 -54
  47. edsl/exceptions/agents.py +42 -54
  48. edsl/exceptions/cache.py +5 -5
  49. edsl/exceptions/configuration.py +16 -16
  50. edsl/exceptions/coop.py +10 -10
  51. edsl/exceptions/data.py +14 -14
  52. edsl/exceptions/general.py +34 -34
  53. edsl/exceptions/jobs.py +33 -33
  54. edsl/exceptions/language_models.py +63 -63
  55. edsl/exceptions/prompts.py +15 -15
  56. edsl/exceptions/questions.py +91 -109
  57. edsl/exceptions/results.py +29 -29
  58. edsl/exceptions/scenarios.py +22 -29
  59. edsl/exceptions/surveys.py +37 -37
  60. edsl/inference_services/AnthropicService.py +87 -84
  61. edsl/inference_services/AwsBedrock.py +120 -118
  62. edsl/inference_services/AzureAI.py +217 -215
  63. edsl/inference_services/DeepInfraService.py +18 -18
  64. edsl/inference_services/GoogleService.py +148 -139
  65. edsl/inference_services/GroqService.py +20 -20
  66. edsl/inference_services/InferenceServiceABC.py +147 -80
  67. edsl/inference_services/InferenceServicesCollection.py +97 -122
  68. edsl/inference_services/MistralAIService.py +123 -120
  69. edsl/inference_services/OllamaService.py +18 -18
  70. edsl/inference_services/OpenAIService.py +224 -221
  71. edsl/inference_services/PerplexityService.py +163 -160
  72. edsl/inference_services/TestService.py +89 -92
  73. edsl/inference_services/TogetherAIService.py +170 -170
  74. edsl/inference_services/models_available_cache.py +118 -118
  75. edsl/inference_services/rate_limits_cache.py +25 -25
  76. edsl/inference_services/registry.py +41 -41
  77. edsl/inference_services/write_available.py +10 -10
  78. edsl/jobs/Answers.py +56 -43
  79. edsl/jobs/Jobs.py +898 -757
  80. edsl/jobs/JobsChecks.py +147 -172
  81. edsl/jobs/JobsPrompts.py +268 -270
  82. edsl/jobs/JobsRemoteInferenceHandler.py +239 -287
  83. edsl/jobs/__init__.py +1 -1
  84. edsl/jobs/buckets/BucketCollection.py +63 -104
  85. edsl/jobs/buckets/ModelBuckets.py +65 -65
  86. edsl/jobs/buckets/TokenBucket.py +251 -283
  87. edsl/jobs/interviews/Interview.py +661 -358
  88. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  89. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  90. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  91. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  92. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  93. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  94. edsl/jobs/interviews/ReportErrors.py +66 -66
  95. edsl/jobs/interviews/interview_status_enum.py +9 -9
  96. edsl/jobs/runners/JobsRunnerAsyncio.py +466 -421
  97. edsl/jobs/runners/JobsRunnerStatus.py +330 -330
  98. edsl/jobs/tasks/QuestionTaskCreator.py +242 -244
  99. edsl/jobs/tasks/TaskCreators.py +64 -64
  100. edsl/jobs/tasks/TaskHistory.py +450 -449
  101. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  102. edsl/jobs/tasks/task_status_enum.py +163 -161
  103. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  104. edsl/jobs/tokens/TokenUsage.py +34 -34
  105. edsl/language_models/KeyLookup.py +30 -0
  106. edsl/language_models/LanguageModel.py +668 -571
  107. edsl/language_models/ModelList.py +155 -153
  108. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  109. edsl/language_models/__init__.py +3 -2
  110. edsl/language_models/fake_openai_call.py +15 -15
  111. edsl/language_models/fake_openai_service.py +61 -61
  112. edsl/language_models/registry.py +190 -180
  113. edsl/language_models/repair.py +156 -156
  114. edsl/language_models/unused/ReplicateBase.py +83 -0
  115. edsl/language_models/utilities.py +64 -65
  116. edsl/notebooks/Notebook.py +258 -263
  117. edsl/notebooks/__init__.py +1 -1
  118. edsl/prompts/Prompt.py +362 -352
  119. edsl/prompts/__init__.py +2 -2
  120. edsl/questions/AnswerValidatorMixin.py +289 -334
  121. edsl/questions/QuestionBase.py +664 -509
  122. edsl/questions/QuestionBaseGenMixin.py +161 -165
  123. edsl/questions/QuestionBasePromptsMixin.py +217 -221
  124. edsl/questions/QuestionBudget.py +227 -227
  125. edsl/questions/QuestionCheckBox.py +359 -359
  126. edsl/questions/QuestionExtract.py +182 -182
  127. edsl/questions/QuestionFreeText.py +114 -113
  128. edsl/questions/QuestionFunctional.py +166 -166
  129. edsl/questions/QuestionList.py +231 -229
  130. edsl/questions/QuestionMultipleChoice.py +286 -330
  131. edsl/questions/QuestionNumerical.py +153 -151
  132. edsl/questions/QuestionRank.py +324 -314
  133. edsl/questions/Quick.py +41 -41
  134. edsl/questions/RegisterQuestionsMeta.py +71 -71
  135. edsl/questions/ResponseValidatorABC.py +174 -200
  136. edsl/questions/SimpleAskMixin.py +73 -74
  137. edsl/questions/__init__.py +26 -27
  138. edsl/questions/compose_questions.py +98 -98
  139. edsl/questions/decorators.py +21 -21
  140. edsl/questions/derived/QuestionLikertFive.py +76 -76
  141. edsl/questions/derived/QuestionLinearScale.py +87 -90
  142. edsl/questions/derived/QuestionTopK.py +93 -93
  143. edsl/questions/derived/QuestionYesNo.py +82 -82
  144. edsl/questions/descriptors.py +413 -427
  145. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  146. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  147. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  148. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  149. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  150. edsl/questions/prompt_templates/question_list.jinja +17 -17
  151. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  152. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  153. edsl/questions/question_registry.py +177 -177
  154. edsl/questions/settings.py +12 -12
  155. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  156. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  157. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  158. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  159. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  160. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  161. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  162. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  163. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  164. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  165. edsl/questions/templates/list/question_presentation.jinja +5 -5
  166. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  167. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  168. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  169. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  170. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  171. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  172. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  173. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  174. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  176. edsl/results/CSSParameterizer.py +108 -108
  177. edsl/results/Dataset.py +424 -587
  178. edsl/results/DatasetExportMixin.py +731 -653
  179. edsl/results/DatasetTree.py +275 -295
  180. edsl/results/Result.py +465 -451
  181. edsl/results/Results.py +1165 -1172
  182. edsl/results/ResultsDBMixin.py +238 -0
  183. edsl/results/ResultsExportMixin.py +43 -45
  184. edsl/results/ResultsFetchMixin.py +33 -33
  185. edsl/results/ResultsGGMixin.py +121 -121
  186. edsl/results/ResultsToolsMixin.py +98 -98
  187. edsl/results/Selector.py +135 -145
  188. edsl/results/TableDisplay.py +198 -125
  189. edsl/results/__init__.py +2 -2
  190. edsl/results/table_display.css +77 -77
  191. edsl/results/tree_explore.py +115 -115
  192. edsl/scenarios/FileStore.py +632 -511
  193. edsl/scenarios/Scenario.py +601 -498
  194. edsl/scenarios/ScenarioHtmlMixin.py +64 -65
  195. edsl/scenarios/ScenarioJoin.py +127 -131
  196. edsl/scenarios/ScenarioList.py +1287 -1430
  197. edsl/scenarios/ScenarioListExportMixin.py +52 -45
  198. edsl/scenarios/ScenarioListPdfMixin.py +261 -239
  199. edsl/scenarios/__init__.py +4 -3
  200. edsl/shared.py +1 -1
  201. edsl/study/ObjectEntry.py +173 -173
  202. edsl/study/ProofOfWork.py +113 -113
  203. edsl/study/SnapShot.py +80 -80
  204. edsl/study/Study.py +528 -521
  205. edsl/study/__init__.py +4 -4
  206. edsl/surveys/DAG.py +148 -148
  207. edsl/surveys/Memory.py +31 -31
  208. edsl/surveys/MemoryPlan.py +244 -244
  209. edsl/surveys/Rule.py +326 -327
  210. edsl/surveys/RuleCollection.py +387 -385
  211. edsl/surveys/Survey.py +1801 -1229
  212. edsl/surveys/SurveyCSS.py +261 -273
  213. edsl/surveys/SurveyExportMixin.py +259 -259
  214. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +179 -181
  215. edsl/surveys/SurveyQualtricsImport.py +284 -284
  216. edsl/surveys/__init__.py +3 -5
  217. edsl/surveys/base.py +53 -53
  218. edsl/surveys/descriptors.py +56 -60
  219. edsl/surveys/instructions/ChangeInstruction.py +49 -48
  220. edsl/surveys/instructions/Instruction.py +65 -56
  221. edsl/surveys/instructions/InstructionCollection.py +77 -82
  222. edsl/templates/error_reporting/base.html +23 -23
  223. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  224. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  225. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  226. edsl/templates/error_reporting/interview_details.html +115 -115
  227. edsl/templates/error_reporting/interviews.html +19 -19
  228. edsl/templates/error_reporting/overview.html +4 -4
  229. edsl/templates/error_reporting/performance_plot.html +1 -1
  230. edsl/templates/error_reporting/report.css +73 -73
  231. edsl/templates/error_reporting/report.html +117 -117
  232. edsl/templates/error_reporting/report.js +25 -25
  233. edsl/tools/__init__.py +1 -1
  234. edsl/tools/clusters.py +192 -192
  235. edsl/tools/embeddings.py +27 -27
  236. edsl/tools/embeddings_plotting.py +118 -118
  237. edsl/tools/plotting.py +112 -112
  238. edsl/tools/summarize.py +18 -18
  239. edsl/utilities/SystemInfo.py +28 -28
  240. edsl/utilities/__init__.py +22 -22
  241. edsl/utilities/ast_utilities.py +25 -25
  242. edsl/utilities/data/Registry.py +6 -6
  243. edsl/utilities/data/__init__.py +1 -1
  244. edsl/utilities/data/scooter_results.json +1 -1
  245. edsl/utilities/decorators.py +77 -77
  246. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  247. edsl/utilities/interface.py +627 -627
  248. edsl/utilities/naming_utilities.py +263 -263
  249. edsl/utilities/repair_functions.py +28 -28
  250. edsl/utilities/restricted_python.py +70 -70
  251. edsl/utilities/utilities.py +424 -436
  252. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/LICENSE +21 -21
  253. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/METADATA +10 -12
  254. edsl-0.1.39.dev3.dist-info/RECORD +277 -0
  255. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  256. edsl/agents/QuestionOptionProcessor.py +0 -172
  257. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  258. edsl/coop/CoopFunctionsMixin.py +0 -15
  259. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  260. edsl/exceptions/inference_services.py +0 -5
  261. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  262. edsl/inference_services/AvailableModelFetcher.py +0 -209
  263. edsl/inference_services/ServiceAvailability.py +0 -135
  264. edsl/inference_services/data_structures.py +0 -62
  265. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -188
  266. edsl/jobs/FetchInvigilator.py +0 -40
  267. edsl/jobs/InterviewTaskManager.py +0 -98
  268. edsl/jobs/InterviewsConstructor.py +0 -48
  269. edsl/jobs/JobsComponentConstructor.py +0 -189
  270. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  271. edsl/jobs/RequestTokenEstimator.py +0 -30
  272. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  273. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  274. edsl/jobs/decorators.py +0 -35
  275. edsl/jobs/jobs_status_enums.py +0 -9
  276. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  277. edsl/language_models/ComputeCost.py +0 -63
  278. edsl/language_models/PriceManager.py +0 -127
  279. edsl/language_models/RawResponseHandler.py +0 -106
  280. edsl/language_models/ServiceDataSources.py +0 -0
  281. edsl/language_models/key_management/KeyLookup.py +0 -63
  282. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  283. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  284. edsl/language_models/key_management/__init__.py +0 -0
  285. edsl/language_models/key_management/models.py +0 -131
  286. edsl/notebooks/NotebookToLaTeX.py +0 -142
  287. edsl/questions/ExceptionExplainer.py +0 -77
  288. edsl/questions/HTMLQuestion.py +0 -103
  289. edsl/questions/LoopProcessor.py +0 -149
  290. edsl/questions/QuestionMatrix.py +0 -265
  291. edsl/questions/ResponseValidatorFactory.py +0 -28
  292. edsl/questions/templates/matrix/__init__.py +0 -1
  293. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  294. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  295. edsl/results/MarkdownToDocx.py +0 -122
  296. edsl/results/MarkdownToPDF.py +0 -111
  297. edsl/results/TextEditor.py +0 -50
  298. edsl/results/smart_objects.py +0 -96
  299. edsl/results/table_data_class.py +0 -12
  300. edsl/results/table_renderers.py +0 -118
  301. edsl/scenarios/ConstructDownloadLink.py +0 -109
  302. edsl/scenarios/DirectoryScanner.py +0 -96
  303. edsl/scenarios/DocumentChunker.py +0 -102
  304. edsl/scenarios/DocxScenario.py +0 -16
  305. edsl/scenarios/PdfExtractor.py +0 -40
  306. edsl/scenarios/ScenarioSelector.py +0 -156
  307. edsl/scenarios/file_methods.py +0 -85
  308. edsl/scenarios/handlers/__init__.py +0 -13
  309. edsl/scenarios/handlers/csv.py +0 -38
  310. edsl/scenarios/handlers/docx.py +0 -76
  311. edsl/scenarios/handlers/html.py +0 -37
  312. edsl/scenarios/handlers/json.py +0 -111
  313. edsl/scenarios/handlers/latex.py +0 -5
  314. edsl/scenarios/handlers/md.py +0 -51
  315. edsl/scenarios/handlers/pdf.py +0 -68
  316. edsl/scenarios/handlers/png.py +0 -39
  317. edsl/scenarios/handlers/pptx.py +0 -105
  318. edsl/scenarios/handlers/py.py +0 -294
  319. edsl/scenarios/handlers/sql.py +0 -313
  320. edsl/scenarios/handlers/sqlite.py +0 -149
  321. edsl/scenarios/handlers/txt.py +0 -33
  322. edsl/surveys/ConstructDAG.py +0 -92
  323. edsl/surveys/EditSurvey.py +0 -221
  324. edsl/surveys/InstructionHandler.py +0 -100
  325. edsl/surveys/MemoryManagement.py +0 -72
  326. edsl/surveys/RuleManager.py +0 -172
  327. edsl/surveys/Simulator.py +0 -75
  328. edsl/surveys/SurveyToApp.py +0 -141
  329. edsl/utilities/PrettyList.py +0 -56
  330. edsl/utilities/is_notebook.py +0 -18
  331. edsl/utilities/is_valid_variable_name.py +0 -11
  332. edsl/utilities/remove_edsl_version.py +0 -24
  333. edsl-0.1.39.dev2.dist-info/RECORD +0 -352
  334. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/WHEEL +0 -0
@@ -1,211 +0,0 @@
1
- from fastapi import FastAPI, HTTPException
2
- from pydantic import BaseModel
3
- from typing import Union, Dict
4
- from typing import Union, List, Any, Optional
5
- from threading import RLock
6
- from edsl.jobs.buckets.TokenBucket import TokenBucket # Original implementation
7
-
8
-
9
- def safe_float_for_json(value: float) -> Union[float, str]:
10
- """Convert float('inf') to 'infinity' for JSON serialization.
11
-
12
- Args:
13
- value: The float value to convert
14
-
15
- Returns:
16
- Either the original float or the string 'infinity' if the value is infinite
17
- """
18
- if value == float("inf"):
19
- return "infinity"
20
- return value
21
-
22
-
23
- app = FastAPI()
24
-
25
- # In-memory storage for TokenBucket instances
26
- buckets: Dict[str, TokenBucket] = {}
27
-
28
-
29
- class TokenBucketCreate(BaseModel):
30
- bucket_name: str
31
- bucket_type: str
32
- capacity: Union[int, float]
33
- refill_rate: Union[int, float]
34
-
35
-
36
- @app.get("/buckets")
37
- async def list_buckets(
38
- bucket_type: Optional[str] = None,
39
- bucket_name: Optional[str] = None,
40
- include_logs: bool = False,
41
- ):
42
- """List all buckets and their current status.
43
-
44
- Args:
45
- bucket_type: Optional filter by bucket type
46
- bucket_name: Optional filter by bucket name
47
- include_logs: Whether to include the full logs in the response
48
- """
49
- result = {}
50
-
51
- for bucket_id, bucket in buckets.items():
52
- # Apply filters if specified
53
- if bucket_type and bucket.bucket_type != bucket_type:
54
- continue
55
- if bucket_name and bucket.bucket_name != bucket_name:
56
- continue
57
-
58
- # Get basic bucket info
59
- bucket_info = {
60
- "bucket_name": bucket.bucket_name,
61
- "bucket_type": bucket.bucket_type,
62
- "tokens": bucket.tokens,
63
- "capacity": bucket.capacity,
64
- "refill_rate": bucket.refill_rate,
65
- "turbo_mode": bucket.turbo_mode,
66
- "num_requests": bucket.num_requests,
67
- "num_released": bucket.num_released,
68
- "tokens_returned": bucket.tokens_returned,
69
- }
70
- for k, v in bucket_info.items():
71
- if isinstance(v, float):
72
- bucket_info[k] = safe_float_for_json(v)
73
-
74
- # Only include logs if requested
75
- if include_logs:
76
- bucket_info["log"] = bucket.log
77
-
78
- result[bucket_id] = bucket_info
79
-
80
- return result
81
-
82
-
83
- @app.post("/bucket/{bucket_id}/add_tokens")
84
- async def add_tokens(bucket_id: str, amount: float):
85
- """Add tokens to an existing bucket."""
86
- if bucket_id not in buckets:
87
- raise HTTPException(status_code=404, detail="Bucket not found")
88
-
89
- if not isinstance(amount, (int, float)) or amount != amount: # Check for NaN
90
- raise HTTPException(status_code=400, detail="Invalid amount specified")
91
-
92
- if amount == float("inf") or amount == float("-inf"):
93
- raise HTTPException(status_code=400, detail="Amount cannot be infinite")
94
-
95
- bucket = buckets[bucket_id]
96
- bucket.add_tokens(amount)
97
-
98
- # Ensure we return a JSON-serializable float
99
- current_tokens = float(bucket.tokens)
100
- if not -1e308 <= current_tokens <= 1e308: # Check if within JSON float bounds
101
- current_tokens = 0.0 # or some other reasonable default
102
-
103
- return {"status": "success", "current_tokens": safe_float_for_json(current_tokens)}
104
-
105
-
106
- # @app.post("/bucket")
107
- # async def create_bucket(bucket: TokenBucketCreate):
108
- # bucket_id = f"{bucket.bucket_name}_{bucket.bucket_type}"
109
- # if bucket_id in buckets:
110
- # raise HTTPException(status_code=400, detail="Bucket already exists")
111
-
112
- # # Create an actual TokenBucket instance
113
- # buckets[bucket_id] = TokenBucket(
114
- # bucket_name=bucket.bucket_name,
115
- # bucket_type=bucket.bucket_type,
116
- # capacity=bucket.capacity,
117
- # refill_rate=bucket.refill_rate,
118
- # )
119
- # return {"status": "created"}
120
-
121
-
122
- @app.post("/bucket")
123
- async def create_bucket(bucket: TokenBucketCreate):
124
- if (
125
- not isinstance(bucket.capacity, (int, float))
126
- or bucket.capacity != bucket.capacity
127
- ): # Check for NaN
128
- raise HTTPException(status_code=400, detail="Invalid capacity value")
129
- if (
130
- not isinstance(bucket.refill_rate, (int, float))
131
- or bucket.refill_rate != bucket.refill_rate
132
- ): # Check for NaN
133
- raise HTTPException(status_code=400, detail="Invalid refill rate value")
134
- if bucket.capacity == float("inf") or bucket.refill_rate == float("inf"):
135
- raise HTTPException(status_code=400, detail="Values cannot be infinite")
136
- bucket_id = f"{bucket.bucket_name}_{bucket.bucket_type}"
137
- if bucket_id in buckets:
138
- # Instead of error, return success with "existing" status
139
- return {
140
- "status": "existing",
141
- "bucket": {
142
- "capacity": safe_float_for_json(buckets[bucket_id].capacity),
143
- "refill_rate": safe_float_for_json(buckets[bucket_id].refill_rate),
144
- },
145
- }
146
-
147
- # Create a new bucket
148
- buckets[bucket_id] = TokenBucket(
149
- bucket_name=bucket.bucket_name,
150
- bucket_type=bucket.bucket_type,
151
- capacity=bucket.capacity,
152
- refill_rate=bucket.refill_rate,
153
- )
154
- return {"status": "created"}
155
-
156
-
157
- @app.post("/bucket/{bucket_id}/get_tokens")
158
- async def get_tokens(bucket_id: str, amount: float, cheat_bucket_capacity: bool = True):
159
- if bucket_id not in buckets:
160
- raise HTTPException(status_code=404, detail="Bucket not found")
161
-
162
- bucket = buckets[bucket_id]
163
- await bucket.get_tokens(amount, cheat_bucket_capacity)
164
- return {"status": "success"}
165
-
166
-
167
- @app.post("/bucket/{bucket_id}/turbo_mode/{state}")
168
- async def set_turbo_mode(bucket_id: str, state: bool):
169
- if bucket_id not in buckets:
170
- raise HTTPException(status_code=404, detail="Bucket not found")
171
-
172
- bucket = buckets[bucket_id]
173
- if state:
174
- bucket.turbo_mode_on()
175
- else:
176
- bucket.turbo_mode_off()
177
- return {"status": "success"}
178
-
179
-
180
- @app.get("/bucket/{bucket_id}/status")
181
- async def get_bucket_status(bucket_id: str):
182
- if bucket_id not in buckets:
183
- raise HTTPException(status_code=404, detail="Bucket not found")
184
-
185
- bucket = buckets[bucket_id]
186
- status = {
187
- "tokens": bucket.tokens,
188
- "capacity": bucket.capacity,
189
- "refill_rate": bucket.refill_rate,
190
- "turbo_mode": bucket.turbo_mode,
191
- "num_requests": bucket.num_requests,
192
- "num_released": bucket.num_released,
193
- "tokens_returned": bucket.tokens_returned,
194
- "log": bucket.log,
195
- }
196
- for k, v in status.items():
197
- if isinstance(v, float):
198
- status[k] = safe_float_for_json(v)
199
-
200
- for index, entry in enumerate(status["log"]):
201
- ts, value = entry
202
- status["log"][index] = (ts, safe_float_for_json(value))
203
-
204
- # print(status)
205
- return status
206
-
207
-
208
- if __name__ == "__main__":
209
- import uvicorn
210
-
211
- uvicorn.run(app, host="0.0.0.0", port=8001)
@@ -1,191 +0,0 @@
1
- from typing import Union, Optional
2
- import asyncio
3
- import time
4
- import aiohttp
5
-
6
-
7
- class TokenBucketClient:
8
- """REST API client version of TokenBucket that maintains the same interface
9
- by delegating to a server running the original TokenBucket implementation."""
10
-
11
- def __init__(
12
- self,
13
- *,
14
- bucket_name: str,
15
- bucket_type: str,
16
- capacity: Union[int, float],
17
- refill_rate: Union[int, float],
18
- api_base_url: str = "http://localhost:8000",
19
- ):
20
- self.bucket_name = bucket_name
21
- self.bucket_type = bucket_type
22
- self.capacity = capacity
23
- self.refill_rate = refill_rate
24
- self.api_base_url = api_base_url
25
- self.bucket_id = f"{bucket_name}_{bucket_type}"
26
-
27
- # Initialize the bucket on the server
28
- asyncio.run(self._create_bucket())
29
-
30
- # Cache some values locally
31
- self.creation_time = time.monotonic()
32
- self.turbo_mode = False
33
-
34
- async def _create_bucket(self):
35
- async with aiohttp.ClientSession() as session:
36
- payload = {
37
- "bucket_name": self.bucket_name,
38
- "bucket_type": self.bucket_type,
39
- "capacity": self.capacity,
40
- "refill_rate": self.refill_rate,
41
- }
42
- async with session.post(
43
- f"{self.api_base_url}/bucket",
44
- json=payload,
45
- ) as response:
46
- if response.status != 200:
47
- raise ValueError(f"Unexpected error: {await response.text()}")
48
-
49
- result = await response.json()
50
- if result["status"] == "existing":
51
- # Update our local values to match the existing bucket
52
- self.capacity = float(result["bucket"]["capacity"])
53
- self.refill_rate = float(result["bucket"]["refill_rate"])
54
-
55
- def turbo_mode_on(self):
56
- """Set the refill rate to infinity."""
57
- asyncio.run(self._set_turbo_mode(True))
58
- self.turbo_mode = True
59
-
60
- def turbo_mode_off(self):
61
- """Restore the refill rate to its original value."""
62
- asyncio.run(self._set_turbo_mode(False))
63
- self.turbo_mode = False
64
-
65
- async def add_tokens(self, amount: Union[int, float]):
66
- """Add tokens to the bucket."""
67
- async with aiohttp.ClientSession() as session:
68
- async with session.post(
69
- f"{self.api_base_url}/bucket/{self.bucket_id}/add_tokens",
70
- params={"amount": amount},
71
- ) as response:
72
- if response.status != 200:
73
- raise ValueError(f"Failed to add tokens: {await response.text()}")
74
-
75
- async def _set_turbo_mode(self, state: bool):
76
- async with aiohttp.ClientSession() as session:
77
- async with session.post(
78
- f"{self.api_base_url}/bucket/{self.bucket_id}/turbo_mode/{str(state).lower()}"
79
- ) as response:
80
- if response.status != 200:
81
- raise ValueError(
82
- f"Failed to set turbo mode: {await response.text()}"
83
- )
84
-
85
- async def get_tokens(
86
- self, amount: Union[int, float] = 1, cheat_bucket_capacity=True
87
- ) -> None:
88
- async with aiohttp.ClientSession() as session:
89
- async with session.post(
90
- f"{self.api_base_url}/bucket/{self.bucket_id}/get_tokens",
91
- params={
92
- "amount": amount,
93
- "cheat_bucket_capacity": int(cheat_bucket_capacity),
94
- },
95
- ) as response:
96
- if response.status != 200:
97
- raise ValueError(f"Failed to get tokens: {await response.text()}")
98
-
99
- def get_throughput(self, time_window: Optional[float] = None) -> float:
100
- status = asyncio.run(self._get_status())
101
- now = time.monotonic()
102
-
103
- if time_window is None:
104
- start_time = self.creation_time
105
- else:
106
- start_time = now - time_window
107
-
108
- if start_time < self.creation_time:
109
- start_time = self.creation_time
110
-
111
- elapsed_time = now - start_time
112
-
113
- if elapsed_time == 0:
114
- return status["num_released"] / 0.001
115
-
116
- return (status["num_released"] / elapsed_time) * 60
117
-
118
- async def _get_status(self) -> dict:
119
- async with aiohttp.ClientSession() as session:
120
- async with session.get(
121
- f"{self.api_base_url}/bucket/{self.bucket_id}/status"
122
- ) as response:
123
- if response.status != 200:
124
- raise ValueError(
125
- f"Failed to get bucket status: {await response.text()}"
126
- )
127
- return await response.json()
128
-
129
- def __add__(self, other) -> "TokenBucketClient":
130
- """Combine two token buckets."""
131
- return TokenBucketClient(
132
- bucket_name=self.bucket_name,
133
- bucket_type=self.bucket_type,
134
- capacity=min(self.capacity, other.capacity),
135
- refill_rate=min(self.refill_rate, other.refill_rate),
136
- api_base_url=self.api_base_url,
137
- )
138
-
139
- @property
140
- def tokens(self) -> float:
141
- """Get the number of tokens remaining in the bucket."""
142
- status = asyncio.run(self._get_status())
143
- return float(status["tokens"])
144
-
145
- def wait_time(self, requested_tokens: Union[float, int]) -> float:
146
- """Calculate the time to wait for the requested number of tokens."""
147
- # self.refill() # Update the current token count
148
- if self.tokens >= float(requested_tokens):
149
- return 0.0
150
- try:
151
- return (requested_tokens - self.tokens) / self.refill_rate
152
- except Exception as e:
153
- raise ValueError(f"Error calculating wait time: {e}")
154
-
155
- # def wait_time(self, num_tokens: Union[int, float]) -> float:
156
- # return 0 # TODO - Need to implement this on the server side
157
-
158
- def visualize(self):
159
- """Visualize the token bucket over time."""
160
- status = asyncio.run(self._get_status())
161
- times, tokens = zip(*status["log"])
162
- start_time = times[0]
163
- times = [t - start_time for t in times]
164
-
165
- from matplotlib import pyplot as plt
166
-
167
- plt.figure(figsize=(10, 6))
168
- plt.plot(times, tokens, label="Tokens Available")
169
- plt.xlabel("Time (seconds)", fontsize=12)
170
- plt.ylabel("Number of Tokens", fontsize=12)
171
- details = f"{self.bucket_name} ({self.bucket_type}) Bucket Usage Over Time\nCapacity: {self.capacity:.1f}, Refill Rate: {self.refill_rate:.1f}/second"
172
- plt.title(details, fontsize=14)
173
- plt.legend()
174
- plt.grid(True)
175
- plt.tight_layout()
176
- plt.show()
177
-
178
-
179
- if __name__ == "__main__":
180
- import doctest
181
-
182
- doctest.testmod()
183
- # bucket = TokenBucketClient(
184
- # bucket_name="test", bucket_type="test", capacity=100, refill_rate=10
185
- # )
186
- # asyncio.run(bucket.get_tokens(50))
187
- # time.sleep(1) # Wait for 1 second
188
- # asyncio.run(bucket.get_tokens(30))
189
- # throughput = bucket.get_throughput(1)
190
- # print(throughput)
191
- # bucket.visualize()
edsl/jobs/decorators.py DELETED
@@ -1,35 +0,0 @@
1
- from functools import wraps
2
- from threading import RLock
3
- import inspect
4
-
5
-
6
- def synchronized_class(wrapped_class):
7
- """Class decorator that makes all methods thread-safe."""
8
-
9
- # Add a lock to the class
10
- setattr(wrapped_class, "_lock", RLock())
11
-
12
- # Get all methods from the class
13
- for name, method in inspect.getmembers(wrapped_class, inspect.isfunction):
14
- # Skip magic methods except __getitem__, __setitem__, __delitem__
15
- if name.startswith("__") and name not in [
16
- "__getitem__",
17
- "__setitem__",
18
- "__delitem__",
19
- ]:
20
- continue
21
-
22
- # Create synchronized version of the method
23
- def create_synchronized_method(method):
24
- @wraps(method)
25
- def synchronized_method(*args, **kwargs):
26
- instance = args[0] # first arg is self
27
- with instance._lock:
28
- return method(*args, **kwargs)
29
-
30
- return synchronized_method
31
-
32
- # Replace the original method with synchronized version
33
- setattr(wrapped_class, name, create_synchronized_method(method))
34
-
35
- return wrapped_class
@@ -1,9 +0,0 @@
1
- from enum import Enum
2
-
3
-
4
- class JobsStatus(Enum):
5
- QUEUED = "queued"
6
- RUNNING = "running"
7
- COMPLETED = "completed"
8
- FAILED = "failed"
9
- CANCELLED = "cancelled"