edsl 0.1.48__py3-none-any.whl → 0.1.50__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 +124 -53
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +21 -21
- edsl/agents/agent_list.py +2 -5
- edsl/agents/exceptions.py +119 -5
- edsl/base/__init__.py +10 -35
- edsl/base/base_class.py +71 -36
- edsl/base/base_exception.py +204 -0
- edsl/base/data_transfer_models.py +1 -1
- edsl/base/exceptions.py +94 -0
- edsl/buckets/__init__.py +15 -1
- edsl/buckets/bucket_collection.py +3 -4
- edsl/buckets/exceptions.py +75 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +1 -2
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +7 -2
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +1 -2
- edsl/caching/sql_dict.py +17 -12
- edsl/cli.py +43 -0
- edsl/config/config_class.py +30 -6
- edsl/conversation/Conversation.py +3 -2
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +0 -2
- edsl/coop/__init__.py +20 -1
- edsl/coop/coop.py +129 -38
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +3 -6
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +53 -43
- edsl/dataset/dataset_operations_mixin.py +86 -72
- edsl/dataset/dataset_tree.py +9 -5
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +0 -1
- edsl/dataset/exceptions.py +125 -0
- edsl/dataset/file_exports.py +18 -11
- edsl/dataset/r/ggplot.py +13 -6
- edsl/display/__init__.py +27 -0
- edsl/display/core.py +147 -0
- edsl/display/plugin.py +189 -0
- edsl/display/utils.py +52 -0
- edsl/inference_services/__init__.py +9 -1
- edsl/inference_services/available_model_cache_handler.py +1 -1
- edsl/inference_services/available_model_fetcher.py +4 -5
- edsl/inference_services/data_structures.py +9 -6
- edsl/inference_services/exceptions.py +132 -1
- edsl/inference_services/inference_service_abc.py +2 -2
- edsl/inference_services/inference_services_collection.py +2 -6
- edsl/inference_services/registry.py +4 -3
- edsl/inference_services/service_availability.py +2 -1
- edsl/inference_services/services/anthropic_service.py +4 -1
- edsl/inference_services/services/aws_bedrock.py +13 -12
- edsl/inference_services/services/azure_ai.py +12 -10
- edsl/inference_services/services/deep_infra_service.py +1 -4
- edsl/inference_services/services/deep_seek_service.py +1 -5
- edsl/inference_services/services/google_service.py +6 -2
- edsl/inference_services/services/groq_service.py +1 -1
- edsl/inference_services/services/mistral_ai_service.py +4 -2
- edsl/inference_services/services/ollama_service.py +1 -1
- edsl/inference_services/services/open_ai_service.py +7 -5
- edsl/inference_services/services/perplexity_service.py +6 -2
- edsl/inference_services/services/test_service.py +8 -7
- edsl/inference_services/services/together_ai_service.py +2 -3
- edsl/inference_services/services/xai_service.py +1 -1
- edsl/instructions/__init__.py +1 -1
- edsl/instructions/change_instruction.py +3 -2
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +5 -2
- edsl/instructions/instruction_collection.py +2 -1
- edsl/instructions/instruction_handler.py +4 -9
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +14 -7
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +32 -29
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +3 -3
- edsl/interviews/request_token_estimator.py +3 -1
- edsl/interviews/statistics.py +2 -3
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +8 -12
- edsl/invigilators/prompt_constructor.py +1 -5
- edsl/invigilators/prompt_helpers.py +8 -4
- edsl/invigilators/question_instructions_prompt_builder.py +1 -1
- edsl/invigilators/question_option_processor.py +9 -5
- edsl/invigilators/question_template_replacements_builder.py +3 -2
- edsl/jobs/__init__.py +3 -3
- edsl/jobs/async_interview_runner.py +24 -22
- edsl/jobs/check_survey_scenario_compatibility.py +7 -6
- edsl/jobs/data_structures.py +7 -4
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +72 -67
- edsl/jobs/jobs_checks.py +2 -3
- edsl/jobs/jobs_component_constructor.py +2 -2
- edsl/jobs/jobs_pricing_estimation.py +3 -2
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +1 -2
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +26 -23
- edsl/jobs/results_exceptions_handler.py +8 -5
- edsl/key_management/__init__.py +3 -1
- edsl/key_management/exceptions.py +62 -0
- edsl/key_management/key_lookup.py +1 -1
- edsl/key_management/key_lookup_builder.py +37 -14
- edsl/key_management/key_lookup_collection.py +2 -0
- edsl/language_models/__init__.py +1 -1
- edsl/language_models/exceptions.py +302 -14
- edsl/language_models/language_model.py +4 -7
- edsl/language_models/model.py +4 -4
- edsl/language_models/model_list.py +1 -1
- edsl/language_models/price_manager.py +1 -1
- edsl/language_models/raw_response_handler.py +14 -9
- edsl/language_models/registry.py +17 -21
- edsl/language_models/repair.py +0 -6
- edsl/language_models/unused/fake_openai_service.py +0 -1
- edsl/load_plugins.py +69 -0
- edsl/logger.py +146 -0
- edsl/notebooks/notebook.py +1 -1
- edsl/notebooks/notebook_to_latex.py +0 -1
- edsl/plugins/__init__.py +63 -0
- edsl/plugins/built_in/export_example.py +50 -0
- edsl/plugins/built_in/pig_latin.py +67 -0
- edsl/plugins/cli.py +372 -0
- edsl/plugins/cli_typer.py +283 -0
- edsl/plugins/exceptions.py +31 -0
- edsl/plugins/hookspec.py +51 -0
- edsl/plugins/plugin_host.py +128 -0
- edsl/plugins/plugin_manager.py +633 -0
- edsl/plugins/plugins_registry.py +168 -0
- edsl/prompts/__init__.py +2 -0
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +14 -6
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +2 -0
- edsl/questions/answer_validator_mixin.py +318 -318
- edsl/questions/compose_questions.py +2 -2
- edsl/questions/descriptors.py +10 -49
- edsl/questions/exceptions.py +278 -22
- edsl/questions/loop_processor.py +7 -5
- edsl/questions/prompt_templates/question_list.jinja +3 -0
- edsl/questions/question_base.py +14 -16
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +9 -3
- edsl/questions/question_budget.py +9 -5
- edsl/questions/question_check_box.py +3 -5
- edsl/questions/question_dict.py +171 -194
- edsl/questions/question_extract.py +1 -1
- edsl/questions/question_free_text.py +4 -6
- edsl/questions/question_functional.py +4 -3
- edsl/questions/question_list.py +36 -9
- edsl/questions/question_matrix.py +95 -61
- edsl/questions/question_multiple_choice.py +6 -4
- edsl/questions/question_numerical.py +2 -4
- edsl/questions/question_registry.py +4 -2
- edsl/questions/register_questions_meta.py +0 -1
- edsl/questions/response_validator_abc.py +7 -13
- edsl/questions/templates/dict/answering_instructions.jinja +1 -0
- edsl/questions/templates/rank/question_presentation.jinja +1 -1
- edsl/results/__init__.py +1 -1
- edsl/results/exceptions.py +141 -7
- edsl/results/report.py +0 -1
- edsl/results/result.py +4 -5
- edsl/results/results.py +10 -51
- edsl/results/results_selector.py +8 -4
- edsl/scenarios/PdfExtractor.py +2 -2
- edsl/scenarios/construct_download_link.py +69 -35
- edsl/scenarios/directory_scanner.py +33 -14
- edsl/scenarios/document_chunker.py +1 -1
- edsl/scenarios/exceptions.py +238 -14
- edsl/scenarios/file_methods.py +1 -1
- edsl/scenarios/file_store.py +7 -3
- edsl/scenarios/handlers/__init__.py +17 -0
- edsl/scenarios/handlers/docx_file_store.py +0 -5
- edsl/scenarios/handlers/pdf_file_store.py +0 -1
- edsl/scenarios/handlers/pptx_file_store.py +0 -5
- edsl/scenarios/handlers/py_file_store.py +0 -1
- edsl/scenarios/handlers/sql_file_store.py +1 -4
- edsl/scenarios/handlers/sqlite_file_store.py +0 -1
- edsl/scenarios/handlers/txt_file_store.py +1 -1
- edsl/scenarios/scenario.py +0 -1
- edsl/scenarios/scenario_list.py +152 -18
- edsl/scenarios/scenario_list_pdf_tools.py +1 -0
- edsl/scenarios/scenario_selector.py +0 -1
- edsl/surveys/__init__.py +3 -4
- edsl/surveys/dag/__init__.py +4 -2
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/edit_survey.py +1 -0
- edsl/surveys/exceptions.py +165 -9
- edsl/surveys/memory/__init__.py +5 -3
- edsl/surveys/memory/memory_management.py +1 -0
- edsl/surveys/memory/memory_plan.py +6 -15
- edsl/surveys/rules/__init__.py +5 -3
- edsl/surveys/rules/rule.py +1 -2
- edsl/surveys/rules/rule_collection.py +1 -1
- edsl/surveys/survey.py +12 -24
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/tasks/__init__.py +2 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +5 -7
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +3 -1
- edsl/tokens/token_usage.py +1 -1
- edsl/utilities/__init__.py +21 -1
- edsl/utilities/decorators.py +1 -2
- edsl/utilities/markdown_to_docx.py +2 -2
- edsl/utilities/markdown_to_pdf.py +1 -1
- edsl/utilities/repair_functions.py +0 -1
- edsl/utilities/restricted_python.py +0 -1
- edsl/utilities/template_loader.py +2 -3
- edsl/utilities/utilities.py +8 -29
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/METADATA +32 -2
- edsl-0.1.50.dist-info/RECORD +363 -0
- edsl-0.1.50.dist-info/entry_points.txt +3 -0
- edsl/dataset/smart_objects.py +0 -96
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/__init__.py +0 -54
- edsl/exceptions/configuration.py +0 -16
- edsl/exceptions/general.py +0 -34
- edsl/study/ObjectEntry.py +0 -173
- edsl/study/ProofOfWork.py +0 -113
- edsl/study/SnapShot.py +0 -80
- edsl/study/Study.py +0 -520
- edsl/study/__init__.py +0 -6
- edsl/utilities/interface.py +0 -135
- edsl-0.1.48.dist-info/RECORD +0 -347
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
- {edsl-0.1.48.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
edsl/jobs/remote_inference.py
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
from typing import Optional, Union, Literal, TYPE_CHECKING, NewType, Callable, Any
|
2
|
-
|
3
2
|
from dataclasses import dataclass
|
3
|
+
from ..coop import CoopServerResponseError
|
4
|
+
from ..coop.utils import VisibilityType
|
5
|
+
from ..coop.coop import RemoteInferenceResponse, RemoteInferenceCreationInfo
|
6
|
+
from .jobs_status_enums import JobsStatus
|
7
|
+
from .jobs_remote_inference_logger import JobLogger
|
8
|
+
from .exceptions import RemoteInferenceError
|
4
9
|
|
5
10
|
|
6
11
|
Seconds = NewType("Seconds", float)
|
@@ -9,14 +14,7 @@ JobUUID = NewType("JobUUID", str)
|
|
9
14
|
if TYPE_CHECKING:
|
10
15
|
from ..results import Results
|
11
16
|
from .jobs import Jobs
|
12
|
-
from .jobs_remote_inference_logger import JobLogger
|
13
|
-
|
14
|
-
from ..coop import CoopServerResponseError
|
15
|
-
from ..coop.utils import VisibilityType
|
16
|
-
from ..coop.coop import RemoteInferenceResponse, RemoteInferenceCreationInfo
|
17
17
|
|
18
|
-
from .jobs_status_enums import JobsStatus
|
19
|
-
from .jobs_remote_inference_logger import JobLogger
|
20
18
|
|
21
19
|
class RemoteJobConstants:
|
22
20
|
"""Constants for remote job handling."""
|
@@ -53,7 +51,6 @@ class JobsRemoteInferenceHandler:
|
|
53
51
|
def _create_logger(self) -> JobLogger:
|
54
52
|
from ..utilities import is_notebook
|
55
53
|
from .jobs_remote_inference_logger import (
|
56
|
-
JupyterJobLogger,
|
57
54
|
StdOutJobLogger,
|
58
55
|
)
|
59
56
|
from .html_table_job_logger import HTMLTableJobLogger
|
@@ -75,7 +72,7 @@ class JobsRemoteInferenceHandler:
|
|
75
72
|
return user_edsl_settings.get("remote_inference", False)
|
76
73
|
except requests.ConnectionError:
|
77
74
|
pass
|
78
|
-
except CoopServerResponseError
|
75
|
+
except CoopServerResponseError:
|
79
76
|
pass
|
80
77
|
|
81
78
|
return False
|
@@ -84,10 +81,9 @@ class JobsRemoteInferenceHandler:
|
|
84
81
|
self,
|
85
82
|
iterations: int = 1,
|
86
83
|
remote_inference_description: Optional[str] = None,
|
87
|
-
remote_inference_results_visibility: Optional[
|
84
|
+
remote_inference_results_visibility: Optional["VisibilityType"] = "unlisted",
|
88
85
|
fresh: Optional[bool] = False,
|
89
86
|
) -> RemoteJobInfo:
|
90
|
-
from ..config import CONFIG
|
91
87
|
from ..coop import Coop
|
92
88
|
|
93
89
|
logger = self._create_logger()
|
@@ -137,7 +133,7 @@ class JobsRemoteInferenceHandler:
|
|
137
133
|
@staticmethod
|
138
134
|
def check_status(
|
139
135
|
job_uuid: JobUUID,
|
140
|
-
) ->
|
136
|
+
) -> "RemoteInferenceResponse":
|
141
137
|
from ..coop import Coop
|
142
138
|
|
143
139
|
coop = Coop()
|
@@ -182,6 +178,13 @@ class JobsRemoteInferenceHandler:
|
|
182
178
|
) -> None:
|
183
179
|
"Handles a failed job by logging the error and updating the job status."
|
184
180
|
latest_error_report_url = remote_job_data.get("latest_error_report_url")
|
181
|
+
|
182
|
+
reason = remote_job_data.get("reason")
|
183
|
+
|
184
|
+
if reason == "insufficient funds":
|
185
|
+
latest_error_report_url = "Error: Insufficient balance to start the job"
|
186
|
+
print("❌ Error: Insufficient balance to start the job")
|
187
|
+
|
185
188
|
if latest_error_report_url:
|
186
189
|
job_info.logger.add_info("error_report_url", latest_error_report_url)
|
187
190
|
|
@@ -235,13 +238,13 @@ class JobsRemoteInferenceHandler:
|
|
235
238
|
"""Makes one attempt to fetch and process a remote job's status and results."""
|
236
239
|
remote_job_data = remote_job_data_fetcher(job_info.job_uuid)
|
237
240
|
status = remote_job_data.get("status")
|
238
|
-
|
241
|
+
reason = remote_job_data.get("reason")
|
239
242
|
if status == "cancelled":
|
240
243
|
self._handle_cancelled_job(job_info)
|
241
|
-
return None
|
244
|
+
return None, reason
|
242
245
|
|
243
|
-
elif status == "failed" or status == "completed":
|
244
|
-
if status == "failed":
|
246
|
+
elif status == "failed" or status == "completed" or status == "partial_failed":
|
247
|
+
if status == "failed" or status == "partial_failed":
|
245
248
|
self._handle_failed_job(job_info, remote_job_data)
|
246
249
|
|
247
250
|
results_uuid = remote_job_data.get("results_uuid")
|
@@ -252,13 +255,13 @@ class JobsRemoteInferenceHandler:
|
|
252
255
|
remote_job_data=remote_job_data,
|
253
256
|
object_fetcher=object_fetcher,
|
254
257
|
)
|
255
|
-
return results
|
258
|
+
return results, reason
|
256
259
|
else:
|
257
|
-
return None
|
260
|
+
return None, reason
|
258
261
|
|
259
262
|
else:
|
260
263
|
self._sleep_for_a_bit(job_info, status)
|
261
|
-
return "continue"
|
264
|
+
return "continue", reason
|
262
265
|
|
263
266
|
def poll_remote_inference_job(
|
264
267
|
self,
|
@@ -274,11 +277,11 @@ class JobsRemoteInferenceHandler:
|
|
274
277
|
|
275
278
|
job_in_queue = True
|
276
279
|
while job_in_queue:
|
277
|
-
result = self._attempt_fetch_job(
|
280
|
+
result, reason = self._attempt_fetch_job(
|
278
281
|
job_info, remote_job_data_fetcher, object_fetcher
|
279
282
|
)
|
280
283
|
if result != "continue":
|
281
|
-
return result
|
284
|
+
return result, reason
|
282
285
|
|
283
286
|
async def create_and_poll_remote_job(
|
284
287
|
self,
|
@@ -310,7 +313,7 @@ class JobsRemoteInferenceHandler:
|
|
310
313
|
),
|
311
314
|
)
|
312
315
|
if job_info is None:
|
313
|
-
raise
|
316
|
+
raise RemoteInferenceError("Remote job creation failed.")
|
314
317
|
|
315
318
|
return await loop.run_in_executor(
|
316
319
|
None,
|
@@ -1,8 +1,10 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Protocol
|
2
2
|
import sys
|
3
3
|
#from edsl.scenarios.FileStore import HTMLFileStore
|
4
4
|
from edsl.config import CONFIG
|
5
5
|
from edsl.coop.coop import Coop
|
6
|
+
from ..scenarios import FileStore
|
7
|
+
from .exceptions import JobsErrors
|
6
8
|
|
7
9
|
|
8
10
|
class ResultsProtocol(Protocol):
|
@@ -41,7 +43,8 @@ class ResultsExceptionsHandler:
|
|
41
43
|
self.parameters = parameters
|
42
44
|
|
43
45
|
self.open_in_browser = self._get_browser_setting()
|
44
|
-
self.remote_logging = self._get_remote_logging_setting()
|
46
|
+
#self.remote_logging = self._get_remote_logging_setting()
|
47
|
+
self.remote_logging = False
|
45
48
|
|
46
49
|
def _get_browser_setting(self) -> bool:
|
47
50
|
"""Determine if exceptions should be opened in browser based on config."""
|
@@ -51,7 +54,7 @@ class ResultsExceptionsHandler:
|
|
51
54
|
elif setting == "False":
|
52
55
|
return False
|
53
56
|
else:
|
54
|
-
raise
|
57
|
+
raise JobsErrors(
|
55
58
|
"EDSL_OPEN_EXCEPTION_REPORT_URL must be either True or False"
|
56
59
|
)
|
57
60
|
|
@@ -60,13 +63,13 @@ class ResultsExceptionsHandler:
|
|
60
63
|
try:
|
61
64
|
coop = Coop()
|
62
65
|
return coop.edsl_settings["remote_logging"]
|
63
|
-
except Exception
|
66
|
+
except Exception:
|
64
67
|
# print(e)
|
65
68
|
return False
|
66
69
|
|
67
70
|
def _generate_error_message(self, indices) -> str:
|
68
71
|
"""Generate appropriate error message based on number of exceptions."""
|
69
|
-
msg =
|
72
|
+
msg = "Exceptions were raised.\n"
|
70
73
|
return msg
|
71
74
|
|
72
75
|
def handle_exceptions(self) -> None:
|
edsl/key_management/__init__.py
CHANGED
@@ -25,4 +25,6 @@ openai_key = keys['openai'].api_token
|
|
25
25
|
|
26
26
|
from .key_lookup import KeyLookup
|
27
27
|
from .key_lookup_collection import KeyLookupCollection
|
28
|
-
from .key_lookup_builder import KeyLookupBuilder
|
28
|
+
from .key_lookup_builder import KeyLookupBuilder
|
29
|
+
|
30
|
+
__all__ = ["KeyLookup", "KeyLookupCollection", "KeyLookupBuilder"]
|
@@ -0,0 +1,62 @@
|
|
1
|
+
"""
|
2
|
+
Exceptions specific to the key_management module.
|
3
|
+
|
4
|
+
This module defines custom exception classes for all key management-related errors
|
5
|
+
in the EDSL framework, ensuring consistent error handling and user feedback.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from ..base import BaseException
|
9
|
+
|
10
|
+
|
11
|
+
class KeyManagementError(BaseException):
|
12
|
+
"""
|
13
|
+
Base exception class for all key management-related errors.
|
14
|
+
|
15
|
+
This is the parent class for all exceptions related to API key management,
|
16
|
+
including key lookup, validation, and configuration.
|
17
|
+
|
18
|
+
Examples:
|
19
|
+
```python
|
20
|
+
# Usually not raised directly, but through subclasses
|
21
|
+
# For example, when attempting to use an invalid key
|
22
|
+
key_lookup.add_key(service="invalid_service", key="api_key") # Would raise KeyManagementServiceError
|
23
|
+
```
|
24
|
+
"""
|
25
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/api_keys.html"
|
26
|
+
|
27
|
+
|
28
|
+
class KeyManagementValueError(KeyManagementError):
|
29
|
+
"""
|
30
|
+
Exception raised when invalid values are provided to key management operations.
|
31
|
+
|
32
|
+
This exception occurs when:
|
33
|
+
- Invalid fetch orders or configuration parameters are provided
|
34
|
+
- API key format is invalid
|
35
|
+
- Service names don't match expected values
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
```python
|
39
|
+
# Invalid fetch order
|
40
|
+
key_lookup_builder.set_fetch_order("invalid_order") # Raises KeyManagementValueError
|
41
|
+
```
|
42
|
+
"""
|
43
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/api_keys.html"
|
44
|
+
|
45
|
+
|
46
|
+
class KeyManagementDuplicateError(KeyManagementError):
|
47
|
+
"""
|
48
|
+
Exception raised when duplicate keys or services are encountered.
|
49
|
+
|
50
|
+
This exception occurs when:
|
51
|
+
- Attempting to add a duplicate service ID
|
52
|
+
- Adding a key that already exists for a service
|
53
|
+
- Registering multiple handlers for the same service
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
```python
|
57
|
+
# Adding a duplicate service
|
58
|
+
builder.register_env_var(service_id="openai", env_var="OPENAI_API_KEY")
|
59
|
+
builder.register_env_var(service_id="openai", env_var="ANOTHER_KEY") # Raises KeyManagementDuplicateError
|
60
|
+
```
|
61
|
+
"""
|
62
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/api_keys.html"
|
@@ -19,7 +19,7 @@ class KeyLookup(UserDict):
|
|
19
19
|
- Creating example instances for testing
|
20
20
|
|
21
21
|
Typical usage:
|
22
|
-
>>> from
|
22
|
+
>>> from .models import LanguageModelInput # Import for doctest
|
23
23
|
>>> lookup = KeyLookup()
|
24
24
|
>>> lookup['openai'] = LanguageModelInput(api_token='sk-key123', rpm=60, tpm=100000)
|
25
25
|
>>> openai_config = lookup['openai']
|
@@ -1,9 +1,25 @@
|
|
1
|
-
from typing import Optional,
|
1
|
+
from typing import Optional, TYPE_CHECKING
|
2
2
|
import os
|
3
3
|
from functools import lru_cache
|
4
|
+
import textwrap
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from ..coop import Coop
|
4
8
|
|
5
9
|
from ..enums import service_to_api_keyname
|
6
|
-
from ..
|
10
|
+
from ..base import BaseException
|
11
|
+
|
12
|
+
class MissingAPIKeyError(BaseException):
|
13
|
+
def __init__(self, full_message=None, model_name=None, inference_service=None, silent=False):
|
14
|
+
if model_name and inference_service:
|
15
|
+
full_message = textwrap.dedent(
|
16
|
+
f"""
|
17
|
+
An API Key for model `{model_name}` is missing from the .env file.
|
18
|
+
This key is associated with the inference service `{inference_service}`.
|
19
|
+
Please see https://docs.expectedparrot.com/en/latest/api_keys.html for more information.
|
20
|
+
"""
|
21
|
+
)
|
22
|
+
super().__init__(full_message, show_docs=False, silent=silent)
|
7
23
|
|
8
24
|
from .key_lookup import KeyLookup
|
9
25
|
from .models import (
|
@@ -31,6 +47,8 @@ api_id_to_service = {"AWS_ACCESS_KEY_ID": "bedrock"}
|
|
31
47
|
class KeyLookupBuilder:
|
32
48
|
"""Factory class for building KeyLookup objects by gathering credentials from multiple sources.
|
33
49
|
|
50
|
+
>>> from edsl.key_management.exceptions import KeyManagementValueError
|
51
|
+
|
34
52
|
KeyLookupBuilder is responsible for discovering, organizing, and consolidating API keys
|
35
53
|
and rate limits from various sources. It can pull credentials from:
|
36
54
|
|
@@ -67,9 +85,9 @@ class KeyLookupBuilder:
|
|
67
85
|
Validation examples:
|
68
86
|
>>> try:
|
69
87
|
... KeyLookupBuilder(fetch_order=["config", "env"]) # Should be tuple
|
70
|
-
... except
|
71
|
-
... str(e)
|
72
|
-
|
88
|
+
... except KeyManagementValueError as e:
|
89
|
+
... "fetch_order must be a tuple" in str(e)
|
90
|
+
True
|
73
91
|
|
74
92
|
>>> builder = KeyLookupBuilder()
|
75
93
|
>>> builder.extract_service("EDSL_SERVICE_RPM_OPENAI")
|
@@ -93,7 +111,8 @@ class KeyLookupBuilder:
|
|
93
111
|
fetch_order: Optional[tuple[str]] = None,
|
94
112
|
coop: Optional["Coop"] = None,
|
95
113
|
):
|
96
|
-
|
114
|
+
# Import here to avoid circular import issues
|
115
|
+
from ..coop import Coop # Import Coop type for type hinting
|
97
116
|
|
98
117
|
# Fetch order goes from lowest priority to highest priority
|
99
118
|
if fetch_order is None:
|
@@ -102,7 +121,8 @@ class KeyLookupBuilder:
|
|
102
121
|
self.fetch_order = fetch_order
|
103
122
|
|
104
123
|
if not isinstance(self.fetch_order, tuple):
|
105
|
-
|
124
|
+
from edsl.key_management.exceptions import KeyManagementValueError
|
125
|
+
raise KeyManagementValueError("fetch_order must be a tuple")
|
106
126
|
|
107
127
|
if coop is None:
|
108
128
|
self.coop = Coop()
|
@@ -141,8 +161,8 @@ class KeyLookupBuilder:
|
|
141
161
|
>>> lookup = builder.build()
|
142
162
|
>>> isinstance(lookup, KeyLookup)
|
143
163
|
True
|
144
|
-
>>> lookup['test'].api_token # Test service should always exist
|
145
|
-
|
164
|
+
>>> lookup['test'].api_token == 'test' # Test service should always exist
|
165
|
+
True
|
146
166
|
|
147
167
|
Technical Notes:
|
148
168
|
- Skips services with missing API keys
|
@@ -194,7 +214,7 @@ class KeyLookupBuilder:
|
|
194
214
|
- Supports services that require both API key and API ID
|
195
215
|
"""
|
196
216
|
if (key_entries := self.key_data.get(service)) is None:
|
197
|
-
raise MissingAPIKeyError(f"No key found for service '{service}'")
|
217
|
+
raise MissingAPIKeyError(f"No key found for service '{service}'", silent=True)
|
198
218
|
|
199
219
|
if len(key_entries) == 1:
|
200
220
|
api_key_entry = key_entries[0]
|
@@ -286,6 +306,8 @@ class KeyLookupBuilder:
|
|
286
306
|
|
287
307
|
def _add_id(self, key: str, value: str, source: str) -> None:
|
288
308
|
"""Add an API ID to the id_data dictionary.
|
309
|
+
|
310
|
+
>>> from edsl.key_management.exceptions import KeyManagementDuplicateError
|
289
311
|
|
290
312
|
>>> builder = KeyLookupBuilder()
|
291
313
|
>>> builder._add_id("AWS_ACCESS_KEY_ID", "AKIA1234", "env")
|
@@ -293,9 +315,9 @@ class KeyLookupBuilder:
|
|
293
315
|
'AKIA1234'
|
294
316
|
>>> try:
|
295
317
|
... builder._add_id("AWS_ACCESS_KEY_ID", "AKIA5678", "env")
|
296
|
-
... except
|
297
|
-
... str(e)
|
298
|
-
|
318
|
+
... except KeyManagementDuplicateError as e:
|
319
|
+
... "Duplicate ID for service bedrock" in str(e)
|
320
|
+
True
|
299
321
|
"""
|
300
322
|
service = api_id_to_service[key]
|
301
323
|
if service not in self.id_data:
|
@@ -303,7 +325,8 @@ class KeyLookupBuilder:
|
|
303
325
|
service=service, name=key, value=value, source=source
|
304
326
|
)
|
305
327
|
else:
|
306
|
-
|
328
|
+
from edsl.key_management.exceptions import KeyManagementDuplicateError
|
329
|
+
raise KeyManagementDuplicateError(f"Duplicate ID for service {service}")
|
307
330
|
|
308
331
|
def _add_limit(self, key: str, value: str, source: str) -> None:
|
309
332
|
"""Add a rate limit entry to the limit_data dictionary.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from collections import UserDict
|
2
2
|
|
3
|
+
# Import for doctest and type hints
|
3
4
|
from .key_lookup_builder import KeyLookupBuilder
|
4
5
|
from .key_lookup import KeyLookup
|
5
6
|
|
@@ -25,6 +26,7 @@ class KeyLookupCollection(UserDict):
|
|
25
26
|
True
|
26
27
|
|
27
28
|
Basic usage:
|
29
|
+
>>> from edsl.key_management import KeyLookup
|
28
30
|
>>> collection = KeyLookupCollection()
|
29
31
|
>>> collection.add_key_lookup(("config", "env"))
|
30
32
|
>>> lookup = collection[("config", "env")] # Get the stored KeyLookup
|
edsl/language_models/__init__.py
CHANGED