edsl 0.1.47__py3-none-any.whl → 0.1.48__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 +303 -67
- 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.48.dist-info}/METADATA +1 -1
- edsl-0.1.48.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.48.dist-info}/LICENSE +0 -0
- {edsl-0.1.47.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py}
RENAMED
@@ -1,15 +1,12 @@
|
|
1
1
|
from typing import Optional, List
|
2
|
-
from collections import UserDict
|
3
2
|
import os
|
4
3
|
from functools import lru_cache
|
5
|
-
from dataclasses import dataclass, asdict
|
6
4
|
|
7
|
-
from
|
8
|
-
from
|
5
|
+
from ..enums import service_to_api_keyname
|
6
|
+
from ..exceptions.general import MissingAPIKeyError
|
9
7
|
|
10
|
-
from
|
11
|
-
|
12
|
-
from edsl.language_models.key_management.models import (
|
8
|
+
from .key_lookup import KeyLookup
|
9
|
+
from .models import (
|
13
10
|
APIKeyEntry,
|
14
11
|
LimitEntry,
|
15
12
|
APIIDEntry,
|
@@ -32,31 +29,61 @@ api_id_to_service = {"AWS_ACCESS_KEY_ID": "bedrock"}
|
|
32
29
|
|
33
30
|
|
34
31
|
class KeyLookupBuilder:
|
35
|
-
"""
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
32
|
+
"""Factory class for building KeyLookup objects by gathering credentials from multiple sources.
|
33
|
+
|
34
|
+
KeyLookupBuilder is responsible for discovering, organizing, and consolidating API keys
|
35
|
+
and rate limits from various sources. It can pull credentials from:
|
36
|
+
|
37
|
+
- Environment variables (env)
|
38
|
+
- Configuration files (config)
|
39
|
+
- Remote services (coop)
|
40
|
+
|
41
|
+
The builder handles the complexities of:
|
42
|
+
- Finding API keys with different naming conventions
|
43
|
+
- Merging rate limits from different sources
|
44
|
+
- Processing additional credentials like API IDs
|
45
|
+
- Prioritizing sources based on a configurable order
|
46
|
+
|
47
|
+
Basic usage:
|
48
|
+
>>> builder = KeyLookupBuilder()
|
49
|
+
>>> keys = builder.build()
|
50
|
+
>>> # Now use keys to access service credentials
|
51
|
+
>>> keys['test'].api_token
|
52
|
+
'test'
|
53
|
+
|
54
|
+
Customizing priorities:
|
55
|
+
>>> builder = KeyLookupBuilder(fetch_order=("config", "env"))
|
56
|
+
>>> builder.fetch_order
|
57
|
+
('config', 'env')
|
58
|
+
>>> # 'env' has higher priority than 'config'
|
59
|
+
|
60
|
+
Configuration parameters:
|
61
|
+
>>> builder = KeyLookupBuilder()
|
62
|
+
>>> builder.DEFAULT_RPM # Default API calls per minute
|
63
|
+
100
|
64
|
+
>>> builder.DEFAULT_TPM # Default tokens per minute
|
65
|
+
2000000
|
66
|
+
|
67
|
+
Validation examples:
|
68
|
+
>>> try:
|
69
|
+
... KeyLookupBuilder(fetch_order=["config", "env"]) # Should be tuple
|
70
|
+
... except ValueError as e:
|
71
|
+
... str(e)
|
72
|
+
'fetch_order must be a tuple'
|
73
|
+
|
74
|
+
>>> builder = KeyLookupBuilder()
|
75
|
+
>>> builder.extract_service("EDSL_SERVICE_RPM_OPENAI")
|
76
|
+
('openai', 'rpm')
|
77
|
+
|
78
|
+
Technical Notes:
|
79
|
+
- The fetch_order parameter controls priority (later sources override earlier ones)
|
80
|
+
- Default rate limits are applied when not explicitly provided
|
81
|
+
- Maintains tracking of where each value came from for debugging
|
55
82
|
"""
|
56
83
|
|
57
84
|
# DEFAULT_RPM = 10
|
58
85
|
# DEFAULT_TPM = 2000000
|
59
|
-
from
|
86
|
+
from ..config import CONFIG
|
60
87
|
|
61
88
|
DEFAULT_RPM = int(CONFIG.get("EDSL_SERVICE_RPM_BASELINE"))
|
62
89
|
DEFAULT_TPM = int(CONFIG.get("EDSL_SERVICE_TPM_BASELINE"))
|
@@ -66,7 +93,7 @@ class KeyLookupBuilder:
|
|
66
93
|
fetch_order: Optional[tuple[str]] = None,
|
67
94
|
coop: Optional["Coop"] = None,
|
68
95
|
):
|
69
|
-
from
|
96
|
+
from ..coop import Coop
|
70
97
|
|
71
98
|
# Fetch order goes from lowest priority to highest priority
|
72
99
|
if fetch_order is None:
|
@@ -99,34 +126,72 @@ class KeyLookupBuilder:
|
|
99
126
|
|
100
127
|
@lru_cache
|
101
128
|
def build(self) -> "KeyLookup":
|
102
|
-
"""Build a KeyLookup instance.
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
129
|
+
"""Build a KeyLookup instance with all discovered credentials.
|
130
|
+
|
131
|
+
Processes all discovered API keys and rate limits from the configured sources
|
132
|
+
and builds a KeyLookup instance containing LanguageModelInput objects for
|
133
|
+
each valid service. This method is cached, so subsequent calls will return
|
134
|
+
the same instance unless the builder state changes.
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
KeyLookup: A populated KeyLookup instance with service credentials
|
138
|
+
|
139
|
+
Examples:
|
140
|
+
>>> builder = KeyLookupBuilder()
|
141
|
+
>>> lookup = builder.build()
|
142
|
+
>>> isinstance(lookup, KeyLookup)
|
143
|
+
True
|
144
|
+
>>> lookup['test'].api_token # Test service should always exist
|
145
|
+
'test'
|
146
|
+
|
147
|
+
Technical Notes:
|
148
|
+
- Skips services with missing API keys
|
149
|
+
- Always includes a 'test' service for internal testing
|
150
|
+
- Uses lru_cache to avoid rebuilding unless necessary
|
151
|
+
- Each valid service gets a complete LanguageModelInput with
|
152
|
+
API token, rate limits, and optional API ID
|
110
153
|
"""
|
111
154
|
d = {}
|
155
|
+
# Create entries for all discovered services
|
112
156
|
for service in self.known_services:
|
113
157
|
try:
|
114
158
|
d[service] = self.get_language_model_input(service)
|
115
159
|
except MissingAPIKeyError:
|
116
|
-
pass
|
160
|
+
pass # Skip services with missing API keys
|
117
161
|
|
162
|
+
# Always include a test service
|
118
163
|
d.update({"test": LanguageModelInput(api_token="test", rpm=10, tpm=2000000)})
|
119
164
|
return KeyLookup(d)
|
120
165
|
|
121
166
|
def get_language_model_input(self, service: str) -> LanguageModelInput:
|
122
|
-
"""
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
167
|
+
"""Construct a LanguageModelInput object for the specified service.
|
168
|
+
|
169
|
+
Creates a complete LanguageModelInput object for the requested service by
|
170
|
+
combining the API key, rate limits, and optional API ID from the various
|
171
|
+
data sources. This method assembles the disparate pieces of information
|
172
|
+
into a single configuration object.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
service: Name of the service to retrieve configuration for (e.g., 'openai')
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
LanguageModelInput: A configuration object with the service's credentials
|
179
|
+
|
180
|
+
Raises:
|
181
|
+
MissingAPIKeyError: If the required API key for the service is not found
|
182
|
+
|
183
|
+
Examples:
|
184
|
+
>>> builder = KeyLookupBuilder()
|
185
|
+
>>> try:
|
186
|
+
... builder.get_language_model_input("nonexistent_service")
|
187
|
+
... except MissingAPIKeyError as e:
|
188
|
+
... str(e)
|
189
|
+
"No key found for service 'nonexistent_service'"
|
190
|
+
|
191
|
+
Technical Notes:
|
192
|
+
- Uses default rate limits if none are specified
|
193
|
+
- Preserves information about where each value came from
|
194
|
+
- Supports services that require both API key and API ID
|
130
195
|
"""
|
131
196
|
if (key_entries := self.key_data.get(service)) is None:
|
132
197
|
raise MissingAPIKeyError(f"No key found for service '{service}'")
|
@@ -173,7 +238,7 @@ class KeyLookupBuilder:
|
|
173
238
|
return dict(list(self.coop.fetch_rate_limit_config_vars().items()))
|
174
239
|
|
175
240
|
def _config_key_value_pairs(self):
|
176
|
-
from
|
241
|
+
from ..config import CONFIG
|
177
242
|
|
178
243
|
return dict(list(CONFIG.items()))
|
179
244
|
|
@@ -302,3 +367,9 @@ class KeyLookupBuilder:
|
|
302
367
|
def process_key_value_pairs(self) -> None:
|
303
368
|
"""Process all key-value pairs from the configured sources."""
|
304
369
|
self.update_from_dict(self.get_key_value_pairs())
|
370
|
+
|
371
|
+
|
372
|
+
if __name__ == "__main__":
|
373
|
+
import doctest
|
374
|
+
|
375
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from collections import UserDict
|
2
|
+
|
3
|
+
from .key_lookup_builder import KeyLookupBuilder
|
4
|
+
from .key_lookup import KeyLookup
|
5
|
+
|
6
|
+
class KeyLookupCollection(UserDict):
|
7
|
+
"""Singleton collection for caching and reusing KeyLookup objects.
|
8
|
+
|
9
|
+
KeyLookupCollection implements the singleton pattern to provide a global registry
|
10
|
+
of KeyLookup objects. It avoids rebuilding KeyLookup objects with the same
|
11
|
+
fetch_order by storing and reusing previously created instances.
|
12
|
+
|
13
|
+
This collection serves several purposes:
|
14
|
+
- Reduces overhead by avoiding redundant API key discovery
|
15
|
+
- Ensures consistency by reusing the same credentials throughout an application
|
16
|
+
- Provides a central access point for credential configuration
|
17
|
+
|
18
|
+
The collection uses fetch_order tuples as keys and KeyLookup objects as values,
|
19
|
+
creating a new KeyLookup only when a new fetch_order is requested.
|
20
|
+
|
21
|
+
Singleton behavior:
|
22
|
+
>>> collection = KeyLookupCollection()
|
23
|
+
>>> collection2 = KeyLookupCollection()
|
24
|
+
>>> collection is collection2 # Same instance
|
25
|
+
True
|
26
|
+
|
27
|
+
Basic usage:
|
28
|
+
>>> collection = KeyLookupCollection()
|
29
|
+
>>> collection.add_key_lookup(("config", "env"))
|
30
|
+
>>> lookup = collection[("config", "env")] # Get the stored KeyLookup
|
31
|
+
>>> isinstance(lookup, KeyLookup)
|
32
|
+
True
|
33
|
+
>>> ("config", "env") in collection
|
34
|
+
True
|
35
|
+
|
36
|
+
Technical Notes:
|
37
|
+
- Uses __new__ to implement the singleton pattern
|
38
|
+
- Lazily creates KeyLookup objects only when requested
|
39
|
+
- Default fetch_order is ("config", "env") if not specified
|
40
|
+
"""
|
41
|
+
|
42
|
+
_instance = None
|
43
|
+
|
44
|
+
def __new__(cls, *args, **kwargs):
|
45
|
+
if cls._instance is None:
|
46
|
+
cls._instance = super().__new__(cls)
|
47
|
+
return cls._instance
|
48
|
+
|
49
|
+
def __init__(self, *args, **kwargs):
|
50
|
+
if not hasattr(self, "_initialized"):
|
51
|
+
self.data = {}
|
52
|
+
self._initialized = True
|
53
|
+
super().__init__(*args, **kwargs)
|
54
|
+
|
55
|
+
def add_key_lookup(self, fetch_order=None):
|
56
|
+
"""Add a KeyLookup to the collection with the specified fetch order.
|
57
|
+
|
58
|
+
Creates a new KeyLookup using the KeyLookupBuilder with the given fetch_order,
|
59
|
+
or uses a default fetch_order of ("config", "env") if none is provided.
|
60
|
+
|
61
|
+
The created KeyLookup is stored in the collection using the fetch_order as the key.
|
62
|
+
If a KeyLookup with the same fetch_order already exists, this method does nothing.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
fetch_order: Tuple specifying the order of sources to fetch credentials from.
|
66
|
+
Later sources override earlier ones. If None, uses ("config", "env").
|
67
|
+
|
68
|
+
Examples:
|
69
|
+
>>> collection = KeyLookupCollection()
|
70
|
+
>>> collection.add_key_lookup(("config", "env", "coop"))
|
71
|
+
>>> ("config", "env", "coop") in collection
|
72
|
+
True
|
73
|
+
|
74
|
+
Technical Notes:
|
75
|
+
- KeyLookup objects are created lazily only when needed
|
76
|
+
- Sources can include: "config", "env", "coop"
|
77
|
+
- The fetch_order determines credential priority
|
78
|
+
"""
|
79
|
+
if fetch_order is None:
|
80
|
+
fetch_order = ("config", "env")
|
81
|
+
if fetch_order not in self.data:
|
82
|
+
self.data[fetch_order] = KeyLookupBuilder(fetch_order=fetch_order).build()
|
@@ -0,0 +1,218 @@
|
|
1
|
+
from dataclasses import dataclass, asdict
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
|
5
|
+
@dataclass
|
6
|
+
class APIKeyEntry:
|
7
|
+
"""Data structure for storing API key information with metadata.
|
8
|
+
|
9
|
+
APIKeyEntry encapsulates an API key along with metadata about its service,
|
10
|
+
environment variable name, and the source it was retrieved from. This structure
|
11
|
+
allows for tracking and debugging the origin of API keys.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
service: The service identifier (e.g., 'openai', 'anthropic')
|
15
|
+
name: The environment variable name (e.g., 'OPENAI_API_KEY')
|
16
|
+
value: The actual API key value
|
17
|
+
source: Where the key was obtained from (e.g., 'env', 'config')
|
18
|
+
|
19
|
+
Examples:
|
20
|
+
>>> entry = APIKeyEntry.example()
|
21
|
+
>>> entry.service
|
22
|
+
'openai'
|
23
|
+
>>> entry.name
|
24
|
+
'OPENAI_API_KEY'
|
25
|
+
>>> entry.value
|
26
|
+
'sk-abcd1234'
|
27
|
+
>>> entry.source
|
28
|
+
'env'
|
29
|
+
|
30
|
+
Technical Notes:
|
31
|
+
- Source values typically include: 'env', 'config', 'coop'
|
32
|
+
- Names follow the convention of the service's API documentation
|
33
|
+
"""
|
34
|
+
|
35
|
+
service: str
|
36
|
+
name: str
|
37
|
+
value: str
|
38
|
+
source: Optional[str] = None
|
39
|
+
|
40
|
+
@classmethod
|
41
|
+
def example(cls):
|
42
|
+
return APIKeyEntry(
|
43
|
+
service="openai", name="OPENAI_API_KEY", value="sk-abcd1234", source="env"
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
@dataclass
|
48
|
+
class LimitEntry:
|
49
|
+
"""Data structure for storing service rate limits with metadata.
|
50
|
+
|
51
|
+
LimitEntry encapsulates rate limit information for a service, including
|
52
|
+
requests per minute (rpm) and tokens per minute (tpm) limits. It also
|
53
|
+
tracks the source of each limit value to aid in debugging and understanding
|
54
|
+
configuration priority.
|
55
|
+
|
56
|
+
Attributes:
|
57
|
+
service: The service identifier (e.g., 'openai', 'anthropic')
|
58
|
+
rpm: Requests per minute limit
|
59
|
+
tpm: Tokens per minute limit
|
60
|
+
rpm_source: Where the rpm value was obtained from
|
61
|
+
tpm_source: Where the tpm value was obtained from
|
62
|
+
|
63
|
+
Examples:
|
64
|
+
>>> limit = LimitEntry.example()
|
65
|
+
>>> limit.service
|
66
|
+
'openai'
|
67
|
+
>>> limit.rpm # Requests per minute
|
68
|
+
60
|
69
|
+
>>> limit.tpm # Tokens per minute
|
70
|
+
100000
|
71
|
+
>>> limit.rpm_source # Source of the RPM value
|
72
|
+
'config'
|
73
|
+
>>> limit.tpm_source # Source of the TPM value
|
74
|
+
'env'
|
75
|
+
|
76
|
+
Technical Notes:
|
77
|
+
- Source values typically include: 'env', 'config', 'coop', 'default'
|
78
|
+
- rpm and tpm can come from different sources
|
79
|
+
- Default values are applied when specific limits aren't found
|
80
|
+
"""
|
81
|
+
|
82
|
+
service: str
|
83
|
+
rpm: int
|
84
|
+
tpm: int
|
85
|
+
rpm_source: Optional[str] = None
|
86
|
+
tpm_source: Optional[str] = None
|
87
|
+
|
88
|
+
@classmethod
|
89
|
+
def example(cls):
|
90
|
+
return LimitEntry(
|
91
|
+
service="openai", rpm=60, tpm=100000, rpm_source="config", tpm_source="env"
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
@dataclass
|
96
|
+
class APIIDEntry:
|
97
|
+
"""Data structure for storing API ID information with metadata.
|
98
|
+
|
99
|
+
APIIDEntry encapsulates an API ID (like AWS Access Key ID) along with
|
100
|
+
metadata about its service, environment variable name, and source. Some
|
101
|
+
services like AWS Bedrock require both an API key and an API ID.
|
102
|
+
|
103
|
+
Attributes:
|
104
|
+
service: The service identifier (e.g., 'bedrock' for AWS)
|
105
|
+
name: The environment variable name (e.g., 'AWS_ACCESS_KEY_ID')
|
106
|
+
value: The actual API ID value
|
107
|
+
source: Where the ID was obtained from (e.g., 'env', 'config')
|
108
|
+
|
109
|
+
Examples:
|
110
|
+
>>> id_entry = APIIDEntry.example()
|
111
|
+
>>> id_entry.service
|
112
|
+
'bedrock'
|
113
|
+
>>> id_entry.name
|
114
|
+
'AWS_ACCESS_KEY_ID'
|
115
|
+
>>> id_entry.value
|
116
|
+
'AKIA1234'
|
117
|
+
>>> id_entry.source
|
118
|
+
'env'
|
119
|
+
|
120
|
+
Technical Notes:
|
121
|
+
- Currently primarily used for AWS Bedrock integration
|
122
|
+
- Follows the same pattern as APIKeyEntry for consistency
|
123
|
+
- Source tracking helps with debugging configuration issues
|
124
|
+
"""
|
125
|
+
|
126
|
+
service: str
|
127
|
+
name: str
|
128
|
+
value: str
|
129
|
+
source: Optional[str] = None
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def example(cls):
|
133
|
+
return APIIDEntry(
|
134
|
+
service="bedrock", name="AWS_ACCESS_KEY_ID", value="AKIA1234", source="env"
|
135
|
+
)
|
136
|
+
|
137
|
+
|
138
|
+
@dataclass
|
139
|
+
class LanguageModelInput:
|
140
|
+
"""Comprehensive configuration for a language model service.
|
141
|
+
|
142
|
+
LanguageModelInput brings together all the configuration needed to interact with
|
143
|
+
a language model service, including authentication credentials and rate limits.
|
144
|
+
This is the primary data structure used by the language_models module to access
|
145
|
+
service credentials in a unified way.
|
146
|
+
|
147
|
+
The class combines:
|
148
|
+
- API token for authentication
|
149
|
+
- Rate limits (rpm, tpm)
|
150
|
+
- Optional API ID for services that require it
|
151
|
+
- Source tracking for all values
|
152
|
+
|
153
|
+
Attributes:
|
154
|
+
api_token: The API key/token for authentication
|
155
|
+
rpm: Requests per minute limit
|
156
|
+
tpm: Tokens per minute limit
|
157
|
+
api_id: Optional secondary ID (e.g., AWS Access Key ID)
|
158
|
+
token_source: Where the API token was obtained from
|
159
|
+
rpm_source: Where the rpm value was obtained from
|
160
|
+
tpm_source: Where the tpm value was obtained from
|
161
|
+
id_source: Where the api_id was obtained from
|
162
|
+
|
163
|
+
Basic usage:
|
164
|
+
>>> lm_input = LanguageModelInput(api_token='sk-key123', rpm=60, tpm=100000)
|
165
|
+
>>> lm_input.api_token
|
166
|
+
'sk-key123'
|
167
|
+
|
168
|
+
Example instance:
|
169
|
+
>>> lm_input = LanguageModelInput.example()
|
170
|
+
>>> lm_input.api_token
|
171
|
+
'sk-abcd123'
|
172
|
+
>>> lm_input.rpm
|
173
|
+
60
|
174
|
+
>>> lm_input.tpm
|
175
|
+
100000
|
176
|
+
>>> lm_input.api_id # None for most services
|
177
|
+
|
178
|
+
Serialization:
|
179
|
+
>>> d = lm_input.to_dict()
|
180
|
+
>>> isinstance(d, dict)
|
181
|
+
True
|
182
|
+
>>> LanguageModelInput.from_dict(d).api_token == lm_input.api_token
|
183
|
+
True
|
184
|
+
|
185
|
+
Technical Notes:
|
186
|
+
- Used as values in the KeyLookup dictionary
|
187
|
+
- Centralizes all service configuration in one object
|
188
|
+
- Supports serialization for storage and transmission
|
189
|
+
- Preserves metadata about configuration sources
|
190
|
+
"""
|
191
|
+
|
192
|
+
api_token: str
|
193
|
+
rpm: int
|
194
|
+
tpm: int
|
195
|
+
api_id: Optional[str] = None
|
196
|
+
token_source: Optional[str] = None
|
197
|
+
rpm_source: Optional[str] = None
|
198
|
+
tpm_source: Optional[str] = None
|
199
|
+
id_source: Optional[str] = None
|
200
|
+
|
201
|
+
def to_dict(self):
|
202
|
+
return asdict(self)
|
203
|
+
|
204
|
+
@classmethod
|
205
|
+
def from_dict(cls, d):
|
206
|
+
return cls(**d)
|
207
|
+
|
208
|
+
@classmethod
|
209
|
+
def example(cls):
|
210
|
+
return LanguageModelInput(
|
211
|
+
api_token="sk-abcd123", tpm=100000, rpm=60, api_id=None
|
212
|
+
)
|
213
|
+
|
214
|
+
|
215
|
+
if __name__ == "__main__":
|
216
|
+
import doctest
|
217
|
+
|
218
|
+
doctest.testmod()
|
edsl/language_models/__init__.py
CHANGED
@@ -1,2 +1,7 @@
|
|
1
|
-
from
|
2
|
-
from
|
1
|
+
from .language_model import LanguageModel
|
2
|
+
from .model import Model
|
3
|
+
from .model_list import ModelList
|
4
|
+
|
5
|
+
from .exceptions import LanguageModelBadResponseError
|
6
|
+
|
7
|
+
__all__ = ["Model", "ModelList", "LanguageModelBadResponseError"]
|
@@ -1,7 +1,16 @@
|
|
1
|
-
from typing import Any, Union
|
1
|
+
from typing import Any, Union, TYPE_CHECKING
|
2
2
|
|
3
|
+
if TYPE_CHECKING:
|
4
|
+
from .language_model import LanguageModel
|
3
5
|
|
4
6
|
class ComputeCost:
|
7
|
+
"""Computes the dollar cost of a raw response.
|
8
|
+
|
9
|
+
# TODO: Add doctests
|
10
|
+
>>> True
|
11
|
+
True
|
12
|
+
|
13
|
+
"""
|
5
14
|
def __init__(self, language_model: "LanguageModel"):
|
6
15
|
self.language_model = language_model
|
7
16
|
self._price_lookup = None
|
@@ -9,7 +18,7 @@ class ComputeCost:
|
|
9
18
|
@property
|
10
19
|
def price_lookup(self):
|
11
20
|
if self._price_lookup is None:
|
12
|
-
from
|
21
|
+
from ..coop import Coop
|
13
22
|
|
14
23
|
c = Coop()
|
15
24
|
self._price_lookup = c.fetch_prices()
|
@@ -19,7 +28,7 @@ class ComputeCost:
|
|
19
28
|
"""Return the dollar cost of a raw response."""
|
20
29
|
|
21
30
|
usage = self.get_usage_dict(raw_response)
|
22
|
-
from
|
31
|
+
from ..coop import Coop
|
23
32
|
|
24
33
|
c = Coop()
|
25
34
|
price_lookup = c.fetch_prices()
|
@@ -61,3 +70,9 @@ class ComputeCost:
|
|
61
70
|
return f"Could not compute output price - {e}"
|
62
71
|
|
63
72
|
return input_cost + output_cost
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
if __name__ == "__main__":
|
77
|
+
import doctest
|
78
|
+
doctest.testmod()
|
@@ -1,8 +1,9 @@
|
|
1
1
|
from textwrap import dedent
|
2
2
|
from typing import Optional
|
3
3
|
|
4
|
+
from ..base import BaseException
|
4
5
|
|
5
|
-
class LanguageModelExceptions(
|
6
|
+
class LanguageModelExceptions(BaseException):
|
6
7
|
explanation = (
|
7
8
|
"This is the base class for all exceptions in the LanguageModel class."
|
8
9
|
)
|