edsl 0.1.47__py3-none-any.whl → 0.1.49__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/__init__.py +44 -39
- edsl/__version__.py +1 -1
- edsl/agents/__init__.py +4 -2
- edsl/agents/{Agent.py → agent.py} +442 -152
- edsl/agents/{AgentList.py → agent_list.py} +220 -162
- edsl/agents/descriptors.py +46 -7
- edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
- edsl/base/__init__.py +75 -0
- edsl/base/base_class.py +1303 -0
- edsl/base/data_transfer_models.py +114 -0
- edsl/base/enums.py +215 -0
- edsl/base.py +8 -0
- edsl/buckets/__init__.py +25 -0
- edsl/buckets/bucket_collection.py +324 -0
- edsl/buckets/model_buckets.py +206 -0
- edsl/buckets/token_bucket.py +502 -0
- edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
- edsl/buckets/token_bucket_client.py +509 -0
- edsl/caching/__init__.py +20 -0
- edsl/caching/cache.py +814 -0
- edsl/caching/cache_entry.py +427 -0
- edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
- edsl/caching/exceptions.py +24 -0
- edsl/caching/orm.py +30 -0
- edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
- edsl/caching/sql_dict.py +441 -0
- edsl/config/__init__.py +8 -0
- edsl/config/config_class.py +177 -0
- edsl/config.py +4 -176
- edsl/conversation/Conversation.py +7 -7
- edsl/conversation/car_buying.py +4 -4
- edsl/conversation/chips.py +6 -6
- edsl/coop/__init__.py +25 -2
- edsl/coop/coop.py +311 -75
- edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
- edsl/coop/exceptions.py +62 -0
- edsl/coop/price_fetcher.py +126 -0
- edsl/coop/utils.py +89 -24
- edsl/data_transfer_models.py +5 -72
- edsl/dataset/__init__.py +10 -0
- edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
- edsl/{results/DatasetExportMixin.py → dataset/dataset_operations_mixin.py} +606 -122
- edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
- edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
- edsl/{results → dataset/display}/table_renderers.py +58 -2
- edsl/{results → dataset}/file_exports.py +4 -5
- edsl/{results → dataset}/smart_objects.py +2 -2
- edsl/enums.py +5 -205
- edsl/inference_services/__init__.py +5 -0
- edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
- edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
- edsl/inference_services/data_structures.py +3 -2
- edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
- edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
- edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
- edsl/inference_services/registry.py +4 -41
- edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
- edsl/inference_services/services/__init__.py +31 -0
- edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
- edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
- edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
- edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
- edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
- edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
- edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
- edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
- edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
- edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
- edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +3 -7
- edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
- edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
- edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
- edsl/inference_services/write_available.py +1 -2
- edsl/instructions/__init__.py +6 -0
- edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
- edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
- edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
- edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
- edsl/interviews/__init__.py +4 -0
- edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
- edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
- edsl/interviews/interview.py +638 -0
- edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
- edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
- edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
- edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
- edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
- edsl/invigilators/__init__.py +38 -0
- edsl/invigilators/invigilator_base.py +477 -0
- edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
- edsl/invigilators/prompt_constructor.py +476 -0
- edsl/{agents → invigilators}/prompt_helpers.py +2 -1
- edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
- edsl/{agents → invigilators}/question_option_processor.py +96 -21
- edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
- edsl/jobs/__init__.py +7 -1
- edsl/jobs/async_interview_runner.py +99 -35
- edsl/jobs/check_survey_scenario_compatibility.py +7 -5
- edsl/jobs/data_structures.py +153 -22
- edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
- edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
- edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
- edsl/jobs/{Jobs.py → jobs.py} +313 -167
- edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
- edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +19 -17
- edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
- edsl/jobs/jobs_pricing_estimation.py +347 -0
- edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
- edsl/jobs/jobs_runner_asyncio.py +282 -0
- edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
- edsl/jobs/results_exceptions_handler.py +2 -2
- edsl/key_management/__init__.py +28 -0
- edsl/key_management/key_lookup.py +161 -0
- edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
- edsl/key_management/key_lookup_collection.py +82 -0
- edsl/key_management/models.py +218 -0
- edsl/language_models/__init__.py +7 -2
- edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
- edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
- edsl/language_models/language_model.py +1080 -0
- edsl/language_models/model.py +10 -25
- edsl/language_models/{ModelList.py → model_list.py} +9 -14
- edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
- edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
- edsl/language_models/repair.py +4 -4
- edsl/language_models/utilities.py +4 -4
- edsl/notebooks/__init__.py +3 -1
- edsl/notebooks/{Notebook.py → notebook.py} +7 -8
- edsl/prompts/__init__.py +1 -1
- edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
- edsl/prompts/{Prompt.py → prompt.py} +101 -95
- edsl/questions/HTMLQuestion.py +1 -1
- edsl/questions/__init__.py +154 -25
- edsl/questions/answer_validator_mixin.py +1 -1
- edsl/questions/compose_questions.py +4 -3
- edsl/questions/derived/question_likert_five.py +166 -0
- edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
- edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
- edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
- edsl/questions/descriptors.py +24 -30
- edsl/questions/loop_processor.py +65 -19
- edsl/questions/question_base.py +881 -0
- edsl/questions/question_base_gen_mixin.py +15 -16
- edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
- edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
- edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
- edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
- edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
- edsl/questions/question_free_text.py +282 -0
- edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
- edsl/questions/{QuestionList.py → question_list.py} +6 -7
- edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
- edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
- edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
- edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
- edsl/questions/question_registry.py +4 -9
- edsl/questions/register_questions_meta.py +8 -4
- edsl/questions/response_validator_abc.py +17 -16
- edsl/results/__init__.py +4 -1
- edsl/{exceptions/results.py → results/exceptions.py} +1 -1
- edsl/results/report.py +197 -0
- edsl/results/{Result.py → result.py} +131 -45
- edsl/results/{Results.py → results.py} +365 -220
- edsl/results/results_selector.py +344 -25
- edsl/scenarios/__init__.py +30 -3
- edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
- edsl/scenarios/directory_scanner.py +156 -13
- edsl/scenarios/document_chunker.py +186 -0
- edsl/scenarios/exceptions.py +101 -0
- edsl/scenarios/file_methods.py +2 -3
- edsl/scenarios/{FileStore.py → file_store.py} +275 -189
- edsl/scenarios/handlers/__init__.py +14 -14
- edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
- edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
- edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
- edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
- edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
- edsl/scenarios/handlers/latex_file_store.py +5 -0
- edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
- edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
- edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
- edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
- edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
- edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
- edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
- edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
- edsl/scenarios/scenario.py +928 -0
- edsl/scenarios/scenario_join.py +18 -5
- edsl/scenarios/{ScenarioList.py → scenario_list.py} +294 -106
- edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
- edsl/scenarios/scenario_selector.py +5 -1
- edsl/study/ObjectEntry.py +2 -2
- edsl/study/SnapShot.py +5 -5
- edsl/study/Study.py +18 -19
- edsl/study/__init__.py +6 -4
- edsl/surveys/__init__.py +7 -4
- edsl/surveys/dag/__init__.py +2 -0
- edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
- edsl/surveys/{DAG.py → dag/dag.py} +13 -10
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
- edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
- edsl/surveys/memory/__init__.py +3 -0
- edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
- edsl/surveys/rules/__init__.py +3 -0
- edsl/surveys/{Rule.py → rules/rule.py} +103 -43
- edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
- edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
- edsl/surveys/survey.py +1743 -0
- edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
- edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
- edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
- edsl/tasks/__init__.py +32 -0
- edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
- edsl/tasks/task_creators.py +135 -0
- edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
- edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
- edsl/tasks/task_status_log.py +85 -0
- edsl/tokens/__init__.py +2 -0
- edsl/tokens/interview_token_usage.py +53 -0
- edsl/utilities/PrettyList.py +1 -1
- edsl/utilities/SystemInfo.py +25 -22
- edsl/utilities/__init__.py +29 -21
- edsl/utilities/gcp_bucket/__init__.py +2 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
- edsl/utilities/interface.py +44 -536
- edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
- edsl/utilities/repair_functions.py +1 -1
- {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/METADATA +1 -1
- edsl-0.1.49.dist-info/RECORD +347 -0
- edsl/Base.py +0 -493
- edsl/BaseDiff.py +0 -260
- edsl/agents/InvigilatorBase.py +0 -260
- edsl/agents/PromptConstructor.py +0 -318
- edsl/coop/PriceFetcher.py +0 -54
- edsl/data/Cache.py +0 -582
- edsl/data/CacheEntry.py +0 -238
- edsl/data/SQLiteDict.py +0 -292
- edsl/data/__init__.py +0 -5
- edsl/data/orm.py +0 -10
- edsl/exceptions/cache.py +0 -5
- edsl/exceptions/coop.py +0 -14
- edsl/exceptions/data.py +0 -14
- edsl/exceptions/scenarios.py +0 -29
- edsl/jobs/Answers.py +0 -43
- edsl/jobs/JobsPrompts.py +0 -354
- edsl/jobs/buckets/BucketCollection.py +0 -134
- edsl/jobs/buckets/ModelBuckets.py +0 -65
- edsl/jobs/buckets/TokenBucket.py +0 -283
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/interviews/Interview.py +0 -395
- edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
- edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
- edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
- edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
- edsl/jobs/tasks/TaskCreators.py +0 -64
- edsl/jobs/tasks/TaskStatusLog.py +0 -23
- edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
- edsl/language_models/LanguageModel.py +0 -635
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/models.py +0 -137
- edsl/questions/QuestionBase.py +0 -544
- edsl/questions/QuestionFreeText.py +0 -130
- edsl/questions/derived/QuestionLikertFive.py +0 -76
- edsl/results/ResultsExportMixin.py +0 -45
- edsl/results/TextEditor.py +0 -50
- edsl/results/results_fetch_mixin.py +0 -33
- edsl/results/results_tools_mixin.py +0 -98
- edsl/scenarios/DocumentChunker.py +0 -104
- edsl/scenarios/Scenario.py +0 -548
- edsl/scenarios/ScenarioHtmlMixin.py +0 -65
- edsl/scenarios/ScenarioListExportMixin.py +0 -45
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/shared.py +0 -1
- edsl/surveys/Survey.py +0 -1301
- edsl/surveys/SurveyQualtricsImport.py +0 -284
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/tools/__init__.py +0 -1
- edsl/tools/clusters.py +0 -192
- edsl/tools/embeddings.py +0 -27
- edsl/tools/embeddings_plotting.py +0 -118
- edsl/tools/plotting.py +0 -112
- edsl/tools/summarize.py +0 -18
- edsl/utilities/data/Registry.py +0 -6
- edsl/utilities/data/__init__.py +0 -1
- edsl/utilities/data/scooter_results.json +0 -1
- edsl-0.1.47.dist-info/RECORD +0 -354
- /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
- /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
- /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
- /edsl/{results → dataset/display}/table_data_class.py +0 -0
- /edsl/{results → dataset/display}/table_display.css +0 -0
- /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
- /edsl/{results → dataset}/tree_explore.py +0 -0
- /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
- /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
- /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
- /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
- /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
- /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
- /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
- /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
- /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
- /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
- /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
- /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
- /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
- /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
- /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
- {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/LICENSE +0 -0
- {edsl-0.1.47.dist-info → edsl-0.1.49.dist-info}/WHEEL +0 -0
@@ -0,0 +1,509 @@
|
|
1
|
+
"""
|
2
|
+
Token Bucket Client for distributed rate limiting in EDSL.
|
3
|
+
|
4
|
+
This module provides a client implementation for interacting with a remote token
|
5
|
+
bucket server. It implements the same interface as TokenBucket, but delegates
|
6
|
+
operations to a remote server, enabling distributed rate limiting across
|
7
|
+
multiple processes or machines.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from typing import Union, Optional, Dict, List, Any, Tuple
|
11
|
+
import asyncio
|
12
|
+
import time
|
13
|
+
import aiohttp
|
14
|
+
from matplotlib import pyplot as plt
|
15
|
+
from matplotlib.figure import Figure
|
16
|
+
|
17
|
+
|
18
|
+
class TokenBucketClient:
|
19
|
+
"""
|
20
|
+
Client implementation for interacting with a remote token bucket server.
|
21
|
+
|
22
|
+
TokenBucketClient implements the same interface as TokenBucket, but
|
23
|
+
delegates operations to a remote server via REST API calls. This enables
|
24
|
+
distributed rate limiting across multiple processes or machines, ensuring
|
25
|
+
that rate limits are properly enforced in distributed environments.
|
26
|
+
|
27
|
+
The client maintains minimal local state and fetches most information from
|
28
|
+
the server when needed. It creates the bucket on the server during
|
29
|
+
initialization if it doesn't already exist.
|
30
|
+
|
31
|
+
Attributes:
|
32
|
+
bucket_name (str): Name identifier for the bucket (usually service name)
|
33
|
+
bucket_type (str): Type of bucket ("requests" or "tokens")
|
34
|
+
capacity (float): Maximum tokens the bucket can hold
|
35
|
+
refill_rate (float): Rate at which tokens are refilled (tokens per second)
|
36
|
+
api_base_url (str): Base URL for the token bucket server API
|
37
|
+
bucket_id (str): Unique identifier for this bucket on the server
|
38
|
+
creation_time (float): Local timestamp when this client was created
|
39
|
+
turbo_mode (bool): Flag indicating if turbo mode is active
|
40
|
+
|
41
|
+
Example:
|
42
|
+
>>> # Create a client connected to a running token bucket server
|
43
|
+
>>> client = TokenBucketClient(
|
44
|
+
... bucket_name="openai",
|
45
|
+
... bucket_type="requests",
|
46
|
+
... capacity=100,
|
47
|
+
... refill_rate=10,
|
48
|
+
... api_base_url="http://localhost:8000"
|
49
|
+
... )
|
50
|
+
>>> # Now use this client just like a regular TokenBucket
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(
|
54
|
+
self,
|
55
|
+
*,
|
56
|
+
bucket_name: str,
|
57
|
+
bucket_type: str,
|
58
|
+
capacity: Union[int, float],
|
59
|
+
refill_rate: Union[int, float],
|
60
|
+
api_base_url: str = "http://localhost:8000",
|
61
|
+
):
|
62
|
+
"""
|
63
|
+
Initialize a new TokenBucketClient connected to a remote token bucket server.
|
64
|
+
|
65
|
+
Creates a new TokenBucketClient instance that connects to a remote token
|
66
|
+
bucket server. During initialization, it attempts to create the bucket
|
67
|
+
on the server if it doesn't already exist.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
bucket_name: Name identifier for the bucket (usually service name)
|
71
|
+
bucket_type: Type of bucket, either "requests" or "tokens"
|
72
|
+
capacity: Maximum tokens the bucket can hold
|
73
|
+
refill_rate: Rate at which tokens are added (tokens per second)
|
74
|
+
api_base_url: Base URL for the token bucket server API
|
75
|
+
(default: "http://localhost:8000")
|
76
|
+
|
77
|
+
Raises:
|
78
|
+
ValueError: If bucket creation on the server fails
|
79
|
+
|
80
|
+
Example:
|
81
|
+
>>> client = TokenBucketClient(
|
82
|
+
... bucket_name="openai",
|
83
|
+
... bucket_type="requests",
|
84
|
+
... capacity=100,
|
85
|
+
... refill_rate=10
|
86
|
+
... )
|
87
|
+
"""
|
88
|
+
self.bucket_name = bucket_name
|
89
|
+
self.bucket_type = bucket_type
|
90
|
+
self.capacity = capacity
|
91
|
+
self.refill_rate = refill_rate
|
92
|
+
self.api_base_url = api_base_url
|
93
|
+
self.bucket_id = f"{bucket_name}_{bucket_type}"
|
94
|
+
|
95
|
+
# Initialize the bucket on the server
|
96
|
+
asyncio.run(self._create_bucket())
|
97
|
+
|
98
|
+
# Cache some values locally
|
99
|
+
self.creation_time = time.monotonic()
|
100
|
+
self.turbo_mode = False
|
101
|
+
|
102
|
+
async def _create_bucket(self) -> None:
|
103
|
+
"""
|
104
|
+
Create or retrieve the bucket on the remote server.
|
105
|
+
|
106
|
+
This private async method sends a request to the server to create a new
|
107
|
+
bucket with the specified parameters. If the bucket already exists on
|
108
|
+
the server, it updates the local parameters to match the server's values.
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
ValueError: If the server returns an error
|
112
|
+
"""
|
113
|
+
async with aiohttp.ClientSession() as session:
|
114
|
+
# Prepare payload with bucket parameters
|
115
|
+
payload = {
|
116
|
+
"bucket_name": self.bucket_name,
|
117
|
+
"bucket_type": self.bucket_type,
|
118
|
+
"capacity": self.capacity,
|
119
|
+
"refill_rate": self.refill_rate,
|
120
|
+
}
|
121
|
+
|
122
|
+
# Send request to create/retrieve bucket
|
123
|
+
async with session.post(
|
124
|
+
f"{self.api_base_url}/bucket",
|
125
|
+
json=payload,
|
126
|
+
) as response:
|
127
|
+
if response.status != 200:
|
128
|
+
raise ValueError(f"Unexpected error: {await response.text()}")
|
129
|
+
|
130
|
+
# Process server response
|
131
|
+
result = await response.json()
|
132
|
+
if result["status"] == "existing":
|
133
|
+
# Update our local values to match the existing bucket
|
134
|
+
self.capacity = float(result["bucket"]["capacity"])
|
135
|
+
self.refill_rate = float(result["bucket"]["refill_rate"])
|
136
|
+
|
137
|
+
def turbo_mode_on(self) -> None:
|
138
|
+
"""
|
139
|
+
Enable turbo mode to bypass rate limits.
|
140
|
+
|
141
|
+
Turbo mode sets the refill rate to infinity on the server,
|
142
|
+
effectively bypassing rate limits. This is useful for testing
|
143
|
+
or when rate limits are not needed.
|
144
|
+
|
145
|
+
Raises:
|
146
|
+
ValueError: If the server returns an error
|
147
|
+
|
148
|
+
Example:
|
149
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
150
|
+
... capacity=100, refill_rate=10)
|
151
|
+
>>> client.turbo_mode_on() # Now rate limits are bypassed
|
152
|
+
"""
|
153
|
+
asyncio.run(self._set_turbo_mode(True))
|
154
|
+
self.turbo_mode = True
|
155
|
+
|
156
|
+
def turbo_mode_off(self) -> None:
|
157
|
+
"""
|
158
|
+
Disable turbo mode and restore original rate limits.
|
159
|
+
|
160
|
+
This method restores the original refill rates on the server,
|
161
|
+
re-enabling rate limiting after it was bypassed with turbo_mode_on().
|
162
|
+
|
163
|
+
Raises:
|
164
|
+
ValueError: If the server returns an error
|
165
|
+
|
166
|
+
Example:
|
167
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
168
|
+
... capacity=100, refill_rate=10)
|
169
|
+
>>> client.turbo_mode_on() # Bypass rate limits
|
170
|
+
>>> # Do some work without rate limiting
|
171
|
+
>>> client.turbo_mode_off() # Restore rate limits
|
172
|
+
"""
|
173
|
+
asyncio.run(self._set_turbo_mode(False))
|
174
|
+
self.turbo_mode = False
|
175
|
+
|
176
|
+
async def add_tokens(self, amount: Union[int, float]) -> None:
|
177
|
+
"""
|
178
|
+
Add tokens to the bucket on the server.
|
179
|
+
|
180
|
+
This async method adds tokens to the bucket on the server.
|
181
|
+
It's useful for manually restoring tokens or increasing the
|
182
|
+
available tokens beyond the normal refill rate.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
amount: Number of tokens to add to the bucket
|
186
|
+
|
187
|
+
Raises:
|
188
|
+
ValueError: If the server returns an error
|
189
|
+
|
190
|
+
Example:
|
191
|
+
>>> import asyncio
|
192
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
193
|
+
... capacity=100, refill_rate=10)
|
194
|
+
>>> # Add 50 tokens to the bucket
|
195
|
+
>>> asyncio.run(client.add_tokens(50))
|
196
|
+
"""
|
197
|
+
async with aiohttp.ClientSession() as session:
|
198
|
+
async with session.post(
|
199
|
+
f"{self.api_base_url}/bucket/{self.bucket_id}/add_tokens",
|
200
|
+
params={"amount": amount},
|
201
|
+
) as response:
|
202
|
+
if response.status != 200:
|
203
|
+
raise ValueError(f"Failed to add tokens: {await response.text()}")
|
204
|
+
|
205
|
+
async def _set_turbo_mode(self, state: bool) -> None:
|
206
|
+
"""
|
207
|
+
Set the turbo mode state on the server.
|
208
|
+
|
209
|
+
This private async method sends a request to the server to
|
210
|
+
enable or disable turbo mode.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
state: True to enable turbo mode, False to disable
|
214
|
+
|
215
|
+
Raises:
|
216
|
+
ValueError: If the server returns an error
|
217
|
+
"""
|
218
|
+
async with aiohttp.ClientSession() as session:
|
219
|
+
async with session.post(
|
220
|
+
f"{self.api_base_url}/bucket/{self.bucket_id}/turbo_mode/{str(state).lower()}"
|
221
|
+
) as response:
|
222
|
+
if response.status != 200:
|
223
|
+
raise ValueError(
|
224
|
+
f"Failed to set turbo mode: {await response.text()}"
|
225
|
+
)
|
226
|
+
|
227
|
+
async def get_tokens(
|
228
|
+
self, amount: Union[int, float] = 1, cheat_bucket_capacity: bool = True
|
229
|
+
) -> None:
|
230
|
+
"""
|
231
|
+
Request tokens from the token bucket on the server.
|
232
|
+
|
233
|
+
This async method requests tokens from the token bucket on the server.
|
234
|
+
It will either return immediately if tokens are available or raise an
|
235
|
+
exception if tokens are not available.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
amount: Number of tokens to request (default: 1)
|
239
|
+
cheat_bucket_capacity: If True, allow exceeding capacity temporarily
|
240
|
+
(default: True)
|
241
|
+
|
242
|
+
Raises:
|
243
|
+
ValueError: If the server returns an error, which may indicate
|
244
|
+
insufficient tokens are available
|
245
|
+
|
246
|
+
Example:
|
247
|
+
>>> import asyncio
|
248
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
249
|
+
... capacity=100, refill_rate=10)
|
250
|
+
>>> # Request 20 tokens
|
251
|
+
>>> asyncio.run(client.get_tokens(20))
|
252
|
+
"""
|
253
|
+
async with aiohttp.ClientSession() as session:
|
254
|
+
async with session.post(
|
255
|
+
f"{self.api_base_url}/bucket/{self.bucket_id}/get_tokens",
|
256
|
+
params={
|
257
|
+
"amount": amount,
|
258
|
+
"cheat_bucket_capacity": int(cheat_bucket_capacity),
|
259
|
+
},
|
260
|
+
) as response:
|
261
|
+
if response.status != 200:
|
262
|
+
raise ValueError(f"Failed to get tokens: {await response.text()}")
|
263
|
+
|
264
|
+
def get_throughput(self, time_window: Optional[float] = None) -> float:
|
265
|
+
"""
|
266
|
+
Calculate the token throughput over a specified time window.
|
267
|
+
|
268
|
+
This method calculates the average token throughput (tokens per minute)
|
269
|
+
over the specified time window by requesting the bucket status from
|
270
|
+
the server and analyzing token usage.
|
271
|
+
|
272
|
+
Args:
|
273
|
+
time_window: Time window in seconds to calculate throughput over
|
274
|
+
(default: entire bucket lifetime)
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
Average throughput in tokens per minute
|
278
|
+
|
279
|
+
Example:
|
280
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
281
|
+
... capacity=100, refill_rate=10)
|
282
|
+
>>> # Calculate throughput over the last 60 seconds
|
283
|
+
>>> throughput = client.get_throughput(60)
|
284
|
+
>>> print(f"Average throughput: {throughput:.1f} tokens/minute")
|
285
|
+
"""
|
286
|
+
# Get current bucket status from server
|
287
|
+
status = asyncio.run(self._get_status())
|
288
|
+
now = time.monotonic()
|
289
|
+
|
290
|
+
# Determine start time based on time_window parameter
|
291
|
+
if time_window is None:
|
292
|
+
start_time = self.creation_time
|
293
|
+
else:
|
294
|
+
start_time = now - time_window
|
295
|
+
|
296
|
+
# Ensure start_time isn't before bucket creation
|
297
|
+
if start_time < self.creation_time:
|
298
|
+
start_time = self.creation_time
|
299
|
+
|
300
|
+
# Calculate elapsed time and avoid division by zero
|
301
|
+
elapsed_time = now - start_time
|
302
|
+
if elapsed_time == 0:
|
303
|
+
return status["num_released"] / 0.001 # Avoid division by zero
|
304
|
+
|
305
|
+
# Convert to tokens per minute
|
306
|
+
return (status["num_released"] / elapsed_time) * 60
|
307
|
+
|
308
|
+
async def _get_status(self) -> Dict[str, Any]:
|
309
|
+
"""
|
310
|
+
Get the current status of the bucket from the server.
|
311
|
+
|
312
|
+
This private async method retrieves the current status of the bucket
|
313
|
+
from the server, including the current token count, history log,
|
314
|
+
and various statistics.
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
Dictionary containing the bucket status information
|
318
|
+
|
319
|
+
Raises:
|
320
|
+
ValueError: If the server returns an error
|
321
|
+
"""
|
322
|
+
async with aiohttp.ClientSession() as session:
|
323
|
+
async with session.get(
|
324
|
+
f"{self.api_base_url}/bucket/{self.bucket_id}/status"
|
325
|
+
) as response:
|
326
|
+
if response.status != 200:
|
327
|
+
raise ValueError(
|
328
|
+
f"Failed to get bucket status: {await response.text()}"
|
329
|
+
)
|
330
|
+
return await response.json()
|
331
|
+
|
332
|
+
def __add__(self, other: "TokenBucketClient") -> "TokenBucketClient":
|
333
|
+
"""
|
334
|
+
Combine two token bucket clients to create a new one with merged limits.
|
335
|
+
|
336
|
+
This method combines two token bucket clients by creating a new client
|
337
|
+
with the minimum capacity and refill rate of both inputs. This is useful
|
338
|
+
for creating a client that respects both sets of rate limits.
|
339
|
+
|
340
|
+
Args:
|
341
|
+
other: Another TokenBucketClient to combine with this one
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
A new TokenBucketClient with the combined (minimum) limits
|
345
|
+
|
346
|
+
Example:
|
347
|
+
>>> client1 = TokenBucketClient(bucket_name="service1", bucket_type="requests",
|
348
|
+
... capacity=100, refill_rate=10)
|
349
|
+
>>> client2 = TokenBucketClient(bucket_name="service2", bucket_type="requests",
|
350
|
+
... capacity=50, refill_rate=5)
|
351
|
+
>>> combined = client1 + client2 # Takes the minimum of both limits
|
352
|
+
"""
|
353
|
+
return TokenBucketClient(
|
354
|
+
bucket_name=self.bucket_name,
|
355
|
+
bucket_type=self.bucket_type,
|
356
|
+
capacity=min(self.capacity, other.capacity),
|
357
|
+
refill_rate=min(self.refill_rate, other.refill_rate),
|
358
|
+
api_base_url=self.api_base_url,
|
359
|
+
)
|
360
|
+
|
361
|
+
@property
|
362
|
+
def tokens(self) -> float:
|
363
|
+
"""
|
364
|
+
Get the current number of tokens available in the bucket.
|
365
|
+
|
366
|
+
This property retrieves the current token count from the server.
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
Current number of tokens available in the bucket
|
370
|
+
|
371
|
+
Example:
|
372
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
373
|
+
... capacity=100, refill_rate=10)
|
374
|
+
>>> available = client.tokens
|
375
|
+
>>> print(f"Available tokens: {available}")
|
376
|
+
"""
|
377
|
+
status = asyncio.run(self._get_status())
|
378
|
+
return float(status["tokens"])
|
379
|
+
|
380
|
+
def wait_time(self, requested_tokens: Union[float, int]) -> float:
|
381
|
+
"""
|
382
|
+
Calculate the time to wait for the requested number of tokens.
|
383
|
+
|
384
|
+
This method calculates how long to wait (in seconds) for the requested
|
385
|
+
number of tokens to become available, based on the current token count
|
386
|
+
and refill rate.
|
387
|
+
|
388
|
+
Args:
|
389
|
+
requested_tokens: Number of tokens needed
|
390
|
+
|
391
|
+
Returns:
|
392
|
+
Time to wait in seconds (0.0 if tokens are already available)
|
393
|
+
|
394
|
+
Raises:
|
395
|
+
ValueError: If an error occurs while calculating wait time
|
396
|
+
|
397
|
+
Example:
|
398
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
399
|
+
... capacity=100, refill_rate=10)
|
400
|
+
>>> wait_seconds = client.wait_time(50)
|
401
|
+
>>> print(f"Need to wait {wait_seconds:.2f} seconds")
|
402
|
+
"""
|
403
|
+
# If we have enough tokens, no need to wait
|
404
|
+
if self.tokens >= float(requested_tokens):
|
405
|
+
return 0.0
|
406
|
+
|
407
|
+
try:
|
408
|
+
# Calculate time needed to accumulate the required tokens
|
409
|
+
return (requested_tokens - self.tokens) / self.refill_rate
|
410
|
+
except Exception as e:
|
411
|
+
raise ValueError(f"Error calculating wait time: {e}")
|
412
|
+
|
413
|
+
# Note: The commented out method below is a reminder for future implementation
|
414
|
+
# def wait_time(self, num_tokens: Union[int, float]) -> float:
|
415
|
+
# """Server-side wait time calculation (future implementation)"""
|
416
|
+
# return 0 # TODO - Need to implement this on the server side
|
417
|
+
|
418
|
+
def visualize(self) -> Figure:
|
419
|
+
"""
|
420
|
+
Visualize the token bucket usage over time as a matplotlib figure.
|
421
|
+
|
422
|
+
This method generates a plot showing the available tokens over time,
|
423
|
+
which can be useful for monitoring and debugging rate limit issues.
|
424
|
+
|
425
|
+
Returns:
|
426
|
+
A matplotlib Figure object that can be displayed or saved
|
427
|
+
|
428
|
+
Example:
|
429
|
+
>>> client = TokenBucketClient(bucket_name="test", bucket_type="test",
|
430
|
+
... capacity=100, refill_rate=10)
|
431
|
+
>>> # Do some operations with the bucket
|
432
|
+
>>> plot = client.visualize()
|
433
|
+
>>> # Now you can display or save the plot
|
434
|
+
"""
|
435
|
+
# Get the bucket history from the server
|
436
|
+
status = asyncio.run(self._get_status())
|
437
|
+
times, tokens = zip(*status["log"])
|
438
|
+
|
439
|
+
# Normalize times to start at 0
|
440
|
+
start_time = times[0]
|
441
|
+
times = [t - start_time for t in times]
|
442
|
+
|
443
|
+
# Create the plot
|
444
|
+
fig = plt.figure(figsize=(10, 6))
|
445
|
+
plt.plot(times, tokens, label="Tokens Available")
|
446
|
+
plt.xlabel("Time (seconds)", fontsize=12)
|
447
|
+
plt.ylabel("Number of Tokens", fontsize=12)
|
448
|
+
details = f"{self.bucket_name} ({self.bucket_type}) Bucket Usage Over Time\nCapacity: {self.capacity:.1f}, Refill Rate: {self.refill_rate:.1f}/second"
|
449
|
+
plt.title(details, fontsize=14)
|
450
|
+
plt.legend()
|
451
|
+
plt.grid(True)
|
452
|
+
plt.tight_layout()
|
453
|
+
|
454
|
+
return fig
|
455
|
+
|
456
|
+
|
457
|
+
# Examples and doctests
|
458
|
+
if __name__ == "__main__":
|
459
|
+
#import doctest
|
460
|
+
|
461
|
+
# Example showing how to use TokenBucketClient
|
462
|
+
def example_usage():
|
463
|
+
"""
|
464
|
+
Example demonstrating how to use TokenBucketClient:
|
465
|
+
|
466
|
+
```python
|
467
|
+
import asyncio
|
468
|
+
import time
|
469
|
+
from edsl.buckets.token_bucket_client import TokenBucketClient
|
470
|
+
|
471
|
+
# Create a client connected to a running token bucket server
|
472
|
+
bucket = TokenBucketClient(
|
473
|
+
bucket_name="openai",
|
474
|
+
bucket_type="requests",
|
475
|
+
capacity=100,
|
476
|
+
refill_rate=10,
|
477
|
+
api_base_url="http://localhost:8000"
|
478
|
+
)
|
479
|
+
|
480
|
+
# Get tokens from the bucket
|
481
|
+
asyncio.run(bucket.get_tokens(50))
|
482
|
+
|
483
|
+
# Wait for a second
|
484
|
+
time.sleep(1)
|
485
|
+
|
486
|
+
# Get more tokens
|
487
|
+
asyncio.run(bucket.get_tokens(30))
|
488
|
+
|
489
|
+
# Check throughput
|
490
|
+
throughput = bucket.get_throughput(1)
|
491
|
+
print(f"Throughput: {throughput:.1f} tokens/minute")
|
492
|
+
|
493
|
+
# Enable turbo mode to bypass rate limits
|
494
|
+
bucket.turbo_mode_on()
|
495
|
+
|
496
|
+
# Do some operations without rate limiting
|
497
|
+
asyncio.run(bucket.get_tokens(1000)) # Would normally exceed limits
|
498
|
+
|
499
|
+
# Disable turbo mode
|
500
|
+
bucket.turbo_mode_off()
|
501
|
+
|
502
|
+
# Visualize bucket usage
|
503
|
+
plot = bucket.visualize()
|
504
|
+
```
|
505
|
+
"""
|
506
|
+
pass
|
507
|
+
|
508
|
+
# Run doctests
|
509
|
+
#doctest.testmod()
|
edsl/caching/__init__.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
"""
|
2
|
+
Caching module for EDSL framework.
|
3
|
+
|
4
|
+
This module provides caching functionality for language model responses,
|
5
|
+
with support for both in-memory and persistent storage through SQLite.
|
6
|
+
It includes components for managing cache entries, handling cache initialization
|
7
|
+
and migration, and synchronizing with remote caches.
|
8
|
+
|
9
|
+
Key components:
|
10
|
+
- Cache: Central class for storing and retrieving language model responses
|
11
|
+
- CacheEntry: Represents individual cached responses with metadata
|
12
|
+
- CacheHandler: Manages cache initialization and migration
|
13
|
+
- SQLiteDict: Dictionary-like interface to SQLite database
|
14
|
+
"""
|
15
|
+
|
16
|
+
from .cache import Cache
|
17
|
+
from .cache_entry import CacheEntry
|
18
|
+
from .cache_handler import CacheHandler
|
19
|
+
|
20
|
+
__all__ = ["Cache", "CacheEntry", "CacheHandler"]
|