edsl 0.1.49__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 +120 -29
- 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.49.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.49.dist-info/RECORD +0 -347
- {edsl-0.1.49.dist-info → edsl-0.1.50.dist-info}/LICENSE +0 -0
- {edsl-0.1.49.dist-info → edsl-0.1.50.dist-info}/WHEEL +0 -0
edsl/buckets/token_bucket.py
CHANGED
@@ -4,6 +4,7 @@ import time
|
|
4
4
|
from threading import RLock
|
5
5
|
|
6
6
|
from ..jobs.decorators import synchronized_class
|
7
|
+
from .exceptions import TokenLimitError
|
7
8
|
|
8
9
|
|
9
10
|
@synchronized_class
|
@@ -350,6 +351,7 @@ class TokenBucket:
|
|
350
351
|
- Usage statistics and token levels are logged for tracking purposes
|
351
352
|
|
352
353
|
Example:
|
354
|
+
>>> from edsl.buckets.token_bucket import TokenBucket
|
353
355
|
>>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=100, refill_rate=10)
|
354
356
|
>>> bucket.tokens = 100
|
355
357
|
>>> import asyncio
|
@@ -358,29 +360,32 @@ class TokenBucket:
|
|
358
360
|
70
|
359
361
|
|
360
362
|
>>> # Example with capacity cheating
|
363
|
+
>>> from edsl.buckets.token_bucket import TokenBucket
|
361
364
|
>>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=10, refill_rate=1)
|
362
365
|
>>> asyncio.run(bucket.get_tokens(15, cheat_bucket_capacity=True))
|
363
366
|
>>> bucket.capacity > 15 # Capacity should have been increased
|
364
367
|
True
|
365
368
|
|
366
|
-
>>> # Example raising
|
369
|
+
>>> # Example raising TokenLimitError
|
370
|
+
>>> from edsl.buckets.token_bucket import TokenBucket
|
371
|
+
>>> from edsl.buckets.exceptions import TokenLimitError
|
367
372
|
>>> bucket = TokenBucket(bucket_name="api", bucket_type="test", capacity=10, refill_rate=1)
|
368
373
|
>>> try:
|
369
374
|
... asyncio.run(bucket.get_tokens(15, cheat_bucket_capacity=False))
|
370
|
-
... except
|
371
|
-
... print("
|
372
|
-
|
375
|
+
... except TokenLimitError as e:
|
376
|
+
... print("TokenLimitError raised")
|
377
|
+
TokenLimitError raised
|
373
378
|
"""
|
374
379
|
self.num_requests += amount
|
375
380
|
if amount >= self.capacity:
|
376
381
|
if not cheat_bucket_capacity:
|
377
382
|
msg = f"Requested amount exceeds bucket capacity. Bucket capacity: {self.capacity}, requested amount: {amount}. As the bucket never overflows, the requested amount will never be available."
|
378
|
-
raise
|
383
|
+
raise TokenLimitError(msg)
|
379
384
|
else:
|
380
385
|
self.capacity = amount * 1.10
|
381
386
|
self._old_capacity = self.capacity
|
382
387
|
|
383
|
-
|
388
|
+
# Loop until we have enough tokens
|
384
389
|
while True:
|
385
390
|
self.refill() # Refill based on elapsed time
|
386
391
|
if self.tokens >= amount:
|
edsl/buckets/token_bucket_api.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
from fastapi import FastAPI, HTTPException
|
2
2
|
from pydantic import BaseModel
|
3
3
|
from typing import Union, Dict
|
4
|
-
from typing import
|
5
|
-
from threading import RLock
|
4
|
+
from typing import Optional
|
6
5
|
|
7
6
|
from .token_bucket import TokenBucket # Original implementation
|
8
7
|
|
@@ -7,13 +7,15 @@ operations to a remote server, enabling distributed rate limiting across
|
|
7
7
|
multiple processes or machines.
|
8
8
|
"""
|
9
9
|
|
10
|
-
from typing import Union, Optional, Dict,
|
10
|
+
from typing import Union, Optional, Dict, Any
|
11
11
|
import asyncio
|
12
12
|
import time
|
13
13
|
import aiohttp
|
14
14
|
from matplotlib import pyplot as plt
|
15
15
|
from matplotlib.figure import Figure
|
16
16
|
|
17
|
+
from .exceptions import BucketError, TokenBucketClientError
|
18
|
+
|
17
19
|
|
18
20
|
class TokenBucketClient:
|
19
21
|
"""
|
@@ -125,7 +127,7 @@ class TokenBucketClient:
|
|
125
127
|
json=payload,
|
126
128
|
) as response:
|
127
129
|
if response.status != 200:
|
128
|
-
raise
|
130
|
+
raise TokenBucketClientError(f"Unexpected error: {await response.text()}")
|
129
131
|
|
130
132
|
# Process server response
|
131
133
|
result = await response.json()
|
@@ -200,7 +202,7 @@ class TokenBucketClient:
|
|
200
202
|
params={"amount": amount},
|
201
203
|
) as response:
|
202
204
|
if response.status != 200:
|
203
|
-
raise
|
205
|
+
raise TokenBucketClientError(f"Failed to add tokens: {await response.text()}")
|
204
206
|
|
205
207
|
async def _set_turbo_mode(self, state: bool) -> None:
|
206
208
|
"""
|
@@ -220,7 +222,7 @@ class TokenBucketClient:
|
|
220
222
|
f"{self.api_base_url}/bucket/{self.bucket_id}/turbo_mode/{str(state).lower()}"
|
221
223
|
) as response:
|
222
224
|
if response.status != 200:
|
223
|
-
raise
|
225
|
+
raise TokenBucketClientError(
|
224
226
|
f"Failed to set turbo mode: {await response.text()}"
|
225
227
|
)
|
226
228
|
|
@@ -259,7 +261,7 @@ class TokenBucketClient:
|
|
259
261
|
},
|
260
262
|
) as response:
|
261
263
|
if response.status != 200:
|
262
|
-
raise
|
264
|
+
raise TokenBucketClientError(f"Failed to get tokens: {await response.text()}")
|
263
265
|
|
264
266
|
def get_throughput(self, time_window: Optional[float] = None) -> float:
|
265
267
|
"""
|
@@ -324,7 +326,7 @@ class TokenBucketClient:
|
|
324
326
|
f"{self.api_base_url}/bucket/{self.bucket_id}/status"
|
325
327
|
) as response:
|
326
328
|
if response.status != 200:
|
327
|
-
raise
|
329
|
+
raise TokenBucketClientError(
|
328
330
|
f"Failed to get bucket status: {await response.text()}"
|
329
331
|
)
|
330
332
|
return await response.json()
|
@@ -408,7 +410,7 @@ class TokenBucketClient:
|
|
408
410
|
# Calculate time needed to accumulate the required tokens
|
409
411
|
return (requested_tokens - self.tokens) / self.refill_rate
|
410
412
|
except Exception as e:
|
411
|
-
raise
|
413
|
+
raise BucketError(f"Error calculating wait time: {e}")
|
412
414
|
|
413
415
|
# Note: The commented out method below is a reminder for future implementation
|
414
416
|
# def wait_time(self, num_tokens: Union[int, float]) -> float:
|
edsl/caching/cache.py
CHANGED
@@ -32,10 +32,14 @@ import json
|
|
32
32
|
import os
|
33
33
|
import warnings
|
34
34
|
from typing import Optional, Union, TYPE_CHECKING
|
35
|
-
from ..base import Base
|
36
35
|
|
36
|
+
from ..base import Base
|
37
37
|
from ..utilities import remove_edsl_version, dict_hash
|
38
38
|
from .exceptions import CacheError
|
39
|
+
from .sql_dict import SQLiteDict
|
40
|
+
|
41
|
+
if TYPE_CHECKING:
|
42
|
+
from .cache_entry import CacheEntry
|
39
43
|
|
40
44
|
class Cache(Base):
|
41
45
|
"""Cache for storing and retrieving language model responses.
|
@@ -418,9 +422,10 @@ class Cache(Base):
|
|
418
422
|
"""
|
419
423
|
# if a file doesn't exist at jsonfile, throw an error
|
420
424
|
from .sql_dict import SQLiteDict
|
425
|
+
from .exceptions import CacheFileNotFoundError
|
421
426
|
|
422
427
|
if not os.path.exists(jsonlfile):
|
423
|
-
raise
|
428
|
+
raise CacheFileNotFoundError(f"File {jsonlfile} not found")
|
424
429
|
|
425
430
|
if db_path is None:
|
426
431
|
data = {}
|
edsl/caching/cache_entry.py
CHANGED
@@ -11,10 +11,11 @@ from __future__ import annotations
|
|
11
11
|
import json
|
12
12
|
import datetime
|
13
13
|
import hashlib
|
14
|
-
from typing import Optional, Dict, List, Any
|
14
|
+
from typing import Optional, Dict, List, Any
|
15
15
|
from uuid import uuid4
|
16
16
|
|
17
17
|
from ..base import RepresentationMixin
|
18
|
+
from .exceptions import CacheError
|
18
19
|
|
19
20
|
|
20
21
|
class CacheEntry(RepresentationMixin):
|
@@ -81,22 +82,22 @@ class CacheEntry(RepresentationMixin):
|
|
81
82
|
TypeError: If any attribute has an incorrect type
|
82
83
|
"""
|
83
84
|
if not isinstance(self.model, str):
|
84
|
-
raise
|
85
|
+
raise CacheError("`model` should be a string.")
|
85
86
|
if not isinstance(self.parameters, dict):
|
86
|
-
raise
|
87
|
+
raise CacheError("`parameters` should be a dictionary.")
|
87
88
|
if not isinstance(self.system_prompt, str):
|
88
|
-
raise
|
89
|
+
raise CacheError("`system_prompt` should be a string.")
|
89
90
|
if not isinstance(self.user_prompt, str):
|
90
|
-
raise
|
91
|
+
raise CacheError("`user_prompt` should be a string")
|
91
92
|
if not isinstance(self.output, str):
|
92
|
-
raise
|
93
|
+
raise CacheError("`output` should be a string")
|
93
94
|
if not isinstance(self.iteration, int):
|
94
|
-
raise
|
95
|
+
raise CacheError("`iteration` should be an integer")
|
95
96
|
# Note: timestamp is stored as int for compatibility, but could be float in future
|
96
97
|
if not isinstance(self.timestamp, int):
|
97
|
-
raise
|
98
|
+
raise CacheError("`timestamp` should be an integer")
|
98
99
|
if self.service is not None and not isinstance(self.service, str):
|
99
|
-
raise
|
100
|
+
raise CacheError("`service` should be either a string or None")
|
100
101
|
|
101
102
|
@classmethod
|
102
103
|
def gen_key(
|
edsl/caching/exceptions.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
"""
|
2
|
-
Custom exceptions for the
|
2
|
+
Custom exceptions for the caching module in the EDSL framework.
|
3
3
|
|
4
4
|
This module defines exceptions that are raised during cache operations,
|
5
5
|
such as when entries cannot be stored, retrieved, or synchronized.
|
@@ -13,12 +13,118 @@ class CacheError(BaseException):
|
|
13
13
|
Exception raised for errors related to cache operations.
|
14
14
|
|
15
15
|
This exception is raised when cache operations fail, such as when:
|
16
|
-
- A cache key cannot be generated
|
17
|
-
- A cache entry cannot be stored or retrieved
|
18
|
-
- A cache synchronization operation fails
|
19
|
-
- Cache migration encounters an error
|
16
|
+
- A cache key cannot be generated due to invalid or non-serializable inputs
|
17
|
+
- A cache entry cannot be stored or retrieved due to database access issues
|
18
|
+
- A cache synchronization operation fails when syncing with remote cache
|
19
|
+
- Cache migration encounters an error when upgrading between versions
|
20
|
+
|
21
|
+
To resolve this error:
|
22
|
+
1. Check that your inputs to cached functions are serializable
|
23
|
+
2. Ensure you have proper file system permissions for the SQLite cache file
|
24
|
+
3. For remote cache issues, verify your network connection and API credentials
|
25
|
+
4. For migration issues, you may need to clear the cache by calling cache.clear()
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
```python
|
29
|
+
# Cache key generation failure
|
30
|
+
@cache
|
31
|
+
def my_function(non_serializable_object): # Raises CacheError
|
32
|
+
return result
|
33
|
+
|
34
|
+
# Storage failure
|
35
|
+
cache.set("key", "value") # May raise CacheError if DB is locked
|
36
|
+
```
|
20
37
|
|
21
38
|
Attributes:
|
22
|
-
message (str):
|
39
|
+
message (str): Detailed explanation of the error
|
40
|
+
"""
|
41
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
|
42
|
+
|
43
|
+
|
44
|
+
class CacheFileNotFoundError(CacheError):
|
45
|
+
"""
|
46
|
+
Exception raised when a cache file cannot be found.
|
47
|
+
|
48
|
+
This exception is raised when attempting to load or access a cache file
|
49
|
+
that does not exist on the filesystem.
|
50
|
+
|
51
|
+
To resolve this error:
|
52
|
+
1. Check that the file path is correct
|
53
|
+
2. Verify that the file exists and has proper permissions
|
54
|
+
3. If you're loading a serialized cache, make sure it was previously saved
|
55
|
+
|
56
|
+
Examples:
|
57
|
+
```python
|
58
|
+
# Attempting to load a non-existent cache file
|
59
|
+
cache.load_from_file("non_existent_file.json") # Raises CacheFileNotFoundError
|
60
|
+
```
|
61
|
+
"""
|
62
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
|
63
|
+
|
64
|
+
|
65
|
+
class CacheValueError(CacheError):
|
66
|
+
"""
|
67
|
+
Exception raised when an invalid value is provided to a cache operation.
|
68
|
+
|
69
|
+
This exception is raised when:
|
70
|
+
- An incorrect type is provided for a cache entry
|
71
|
+
- A value cannot be serialized for storage
|
72
|
+
- A validation check fails during cache operations
|
73
|
+
|
74
|
+
To resolve this error:
|
75
|
+
1. Ensure the value has the expected type and structure
|
76
|
+
2. Verify that values being stored are serializable
|
77
|
+
3. Check for any special requirements in the cache implementation
|
78
|
+
|
79
|
+
Examples:
|
80
|
+
```python
|
81
|
+
# Attempting to store an invalid value type
|
82
|
+
cache_dict["key"] = non_cache_entry_object # Raises CacheValueError
|
83
|
+
```
|
84
|
+
"""
|
85
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
|
86
|
+
|
87
|
+
|
88
|
+
class CacheKeyError(CacheError):
|
89
|
+
"""
|
90
|
+
Exception raised when a key cannot be found in the cache.
|
91
|
+
|
92
|
+
This exception is raised when attempting to access a cache entry that
|
93
|
+
does not exist in the cache.
|
94
|
+
|
95
|
+
To resolve this error:
|
96
|
+
1. Check that the key exists before accessing it
|
97
|
+
2. Use cache.get() with a default value to handle missing keys gracefully
|
98
|
+
3. Consider initializing the key-value pair before accessing
|
99
|
+
|
100
|
+
Examples:
|
101
|
+
```python
|
102
|
+
# Attempting to access a non-existent key
|
103
|
+
value = cache["non_existent_key"] # Raises CacheKeyError
|
104
|
+
|
105
|
+
# Safer alternative
|
106
|
+
value = cache.get("non_existent_key", default=None) # Returns None instead of raising
|
107
|
+
```
|
108
|
+
"""
|
109
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
|
110
|
+
|
111
|
+
|
112
|
+
class CacheNotImplementedError(CacheError):
|
113
|
+
"""
|
114
|
+
Exception raised when a cache method is not implemented.
|
115
|
+
|
116
|
+
This exception is raised when attempting to use a method that is
|
117
|
+
defined in the interface but not implemented in the concrete class.
|
118
|
+
|
119
|
+
To resolve this error:
|
120
|
+
1. Check if the operation is supported by the specific cache implementation
|
121
|
+
2. Consider using an alternative approach that is supported
|
122
|
+
3. If you're implementing a custom cache, implement the required method
|
123
|
+
|
124
|
+
Examples:
|
125
|
+
```python
|
126
|
+
# Attempting to use an unimplemented method
|
127
|
+
cache.some_unimplemented_method() # Raises CacheNotImplementedError
|
128
|
+
```
|
23
129
|
"""
|
24
|
-
relevant_doc = "https://docs.expectedparrot.com/en/latest/
|
130
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/remote_caching.html"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import List,
|
1
|
+
from typing import List, TYPE_CHECKING, Callable
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from contextlib import AbstractContextManager
|
4
4
|
from collections import UserList
|
@@ -8,7 +8,6 @@ if TYPE_CHECKING:
|
|
8
8
|
from edsl.coop.coop import Coop
|
9
9
|
from .cache_entry import CacheEntry
|
10
10
|
|
11
|
-
from logging import Logger
|
12
11
|
|
13
12
|
|
14
13
|
class CacheKeyList(UserList):
|
edsl/caching/sql_dict.py
CHANGED
@@ -9,11 +9,11 @@ making it a drop-in replacement for regular dictionaries but with database persi
|
|
9
9
|
|
10
10
|
from __future__ import annotations
|
11
11
|
import json
|
12
|
-
from typing import Any, Generator, Optional, Union, Dict,
|
12
|
+
from typing import Any, Generator, Optional, Union, Dict, TypeVar
|
13
13
|
|
14
14
|
from ..config import CONFIG
|
15
15
|
from .cache_entry import CacheEntry
|
16
|
-
from .orm import
|
16
|
+
from .orm import Data
|
17
17
|
|
18
18
|
T = TypeVar('T')
|
19
19
|
|
@@ -76,13 +76,14 @@ class SQLiteDict:
|
|
76
76
|
if not self.db_path.startswith("sqlite:///"):
|
77
77
|
self.db_path = f"sqlite:///{self.db_path}"
|
78
78
|
try:
|
79
|
-
from edsl.caching.orm import Base
|
79
|
+
from edsl.caching.orm import Base
|
80
80
|
|
81
81
|
self.engine = create_engine(self.db_path, echo=False, future=True)
|
82
82
|
Base.metadata.create_all(self.engine)
|
83
83
|
self.Session = sessionmaker(bind=self.engine)
|
84
84
|
except SQLAlchemyError as e:
|
85
|
-
|
85
|
+
from edsl.caching.exceptions import CacheError
|
86
|
+
raise CacheError(
|
86
87
|
f"""Database initialization error: {e}. The attempted DB path was {db_path}"""
|
87
88
|
) from e
|
88
89
|
|
@@ -99,7 +100,6 @@ class SQLiteDict:
|
|
99
100
|
Path to a temporary file location
|
100
101
|
"""
|
101
102
|
import tempfile
|
102
|
-
import os
|
103
103
|
|
104
104
|
_, temp_db_path = tempfile.mkstemp(suffix=".db")
|
105
105
|
return temp_db_path
|
@@ -123,9 +123,10 @@ class SQLiteDict:
|
|
123
123
|
>>> d["foo"] = CacheEntry.example()
|
124
124
|
"""
|
125
125
|
if not isinstance(value, CacheEntry):
|
126
|
-
|
126
|
+
from edsl.caching.exceptions import CacheValueError
|
127
|
+
raise CacheValueError(f"Value must be a CacheEntry object (got {type(value)}).")
|
127
128
|
with self.Session() as db:
|
128
|
-
from edsl.caching.orm import
|
129
|
+
from edsl.caching.orm import Data
|
129
130
|
|
130
131
|
db.merge(Data(key=key, value=json.dumps(value.to_dict())))
|
131
132
|
db.commit()
|
@@ -154,11 +155,12 @@ class SQLiteDict:
|
|
154
155
|
True
|
155
156
|
"""
|
156
157
|
with self.Session() as db:
|
157
|
-
from edsl.caching.orm import
|
158
|
+
from edsl.caching.orm import Data
|
158
159
|
|
159
160
|
value = db.query(Data).filter_by(key=key).first()
|
160
161
|
if not value:
|
161
|
-
|
162
|
+
from edsl.caching.exceptions import CacheKeyError
|
163
|
+
raise CacheKeyError(f"Key '{key}' not found.")
|
162
164
|
return CacheEntry.from_dict(json.loads(value.value))
|
163
165
|
|
164
166
|
def get(self, key: str, default: Optional[Any] = None) -> Union[CacheEntry, Any]:
|
@@ -181,9 +183,10 @@ class SQLiteDict:
|
|
181
183
|
>>> d.get("foo", "bar")
|
182
184
|
'bar'
|
183
185
|
"""
|
186
|
+
from edsl.caching.exceptions import CacheKeyError
|
184
187
|
try:
|
185
188
|
return self[key]
|
186
|
-
except KeyError:
|
189
|
+
except (KeyError, CacheKeyError):
|
187
190
|
return default
|
188
191
|
|
189
192
|
def __bool__(self) -> bool:
|
@@ -233,7 +236,8 @@ class SQLiteDict:
|
|
233
236
|
the database from being locked for too long.
|
234
237
|
"""
|
235
238
|
if not (isinstance(new_d, dict) or isinstance(new_d, SQLiteDict)):
|
236
|
-
|
239
|
+
from edsl.caching.exceptions import CacheValueError
|
240
|
+
raise CacheValueError(
|
237
241
|
f"new_d must be a dict or SQLiteDict object (got {type(new_d)})"
|
238
242
|
)
|
239
243
|
current_batch = 0
|
@@ -301,7 +305,8 @@ class SQLiteDict:
|
|
301
305
|
db.delete(instance)
|
302
306
|
db.commit()
|
303
307
|
else:
|
304
|
-
|
308
|
+
from edsl.caching.exceptions import CacheKeyError
|
309
|
+
raise CacheKeyError(f"Key '{key}' not found.")
|
305
310
|
|
306
311
|
def __contains__(self, key: str) -> bool:
|
307
312
|
"""
|
edsl/cli.py
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
EDSL Command Line Interface.
|
3
|
+
|
4
|
+
This module provides the main entry point for the EDSL command-line tool.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import sys
|
8
|
+
import typer
|
9
|
+
from typing import Optional
|
10
|
+
from rich.console import Console
|
11
|
+
from importlib import metadata
|
12
|
+
|
13
|
+
# Create the main Typer app
|
14
|
+
app = typer.Typer(help="Expected Parrot EDSL Command Line Interface")
|
15
|
+
console = Console()
|
16
|
+
|
17
|
+
# Import the plugins app
|
18
|
+
from .plugins.cli_typer import app as plugins_app
|
19
|
+
|
20
|
+
# Add the plugins subcommand
|
21
|
+
app.add_typer(plugins_app, name="plugins")
|
22
|
+
|
23
|
+
@app.callback()
|
24
|
+
def callback():
|
25
|
+
"""
|
26
|
+
Expected Parrot EDSL Command Line Interface.
|
27
|
+
|
28
|
+
A toolkit for creating, managing, and running surveys with language models.
|
29
|
+
"""
|
30
|
+
pass
|
31
|
+
|
32
|
+
@app.command()
|
33
|
+
def version():
|
34
|
+
"""Show the EDSL version."""
|
35
|
+
try:
|
36
|
+
version = metadata.version("edsl")
|
37
|
+
console.print(f"[bold cyan]EDSL version:[/bold cyan] {version}")
|
38
|
+
except metadata.PackageNotFoundError:
|
39
|
+
console.print("[yellow]EDSL package not installed or version not available.[/yellow]")
|
40
|
+
|
41
|
+
def main():
|
42
|
+
"""Main entry point for the EDSL CLI."""
|
43
|
+
app()
|
edsl/config/config_class.py
CHANGED
@@ -3,10 +3,16 @@
|
|
3
3
|
import os
|
4
4
|
import platformdirs
|
5
5
|
from dotenv import load_dotenv, find_dotenv
|
6
|
-
from edsl.
|
7
|
-
|
8
|
-
|
9
|
-
)
|
6
|
+
from edsl.base import BaseException
|
7
|
+
from edsl import logger
|
8
|
+
|
9
|
+
class InvalidEnvironmentVariableError(BaseException):
|
10
|
+
"""Raised when an environment variable is invalid."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
class MissingEnvironmentVariableError(BaseException):
|
14
|
+
"""Raised when an expected environment variable is missing."""
|
15
|
+
pass
|
10
16
|
|
11
17
|
cache_dir = platformdirs.user_cache_dir("edsl")
|
12
18
|
os.makedirs(cache_dir, exist_ok=True)
|
@@ -50,6 +56,10 @@ CONFIG_MAP = {
|
|
50
56
|
"default": "True",
|
51
57
|
"info": "This config var determines whether to fetch prices for tokens used in remote inference",
|
52
58
|
},
|
59
|
+
"EDSL_LOG_LEVEL": {
|
60
|
+
"default": "ERROR",
|
61
|
+
"info": "This config var determines the logging level for the EDSL package (DEBUG, INFO, WARNING, ERROR, CRITICAL).",
|
62
|
+
},
|
53
63
|
"EDSL_MAX_ATTEMPTS": {
|
54
64
|
"default": "5",
|
55
65
|
"info": "This config var determines the maximum number of times to retry a failed API call.",
|
@@ -86,9 +96,11 @@ class Config:
|
|
86
96
|
|
87
97
|
def __init__(self):
|
88
98
|
"""Initialize the Config class."""
|
99
|
+
logger.debug("Initializing Config class")
|
89
100
|
self._set_run_mode()
|
90
101
|
self._load_dotenv()
|
91
102
|
self._set_env_vars()
|
103
|
+
logger.info(f"Config initialized with run mode: {self.EDSL_RUN_MODE}")
|
92
104
|
|
93
105
|
def show_path_to_dot_env(self):
|
94
106
|
print(find_dotenv(usecwd=True))
|
@@ -101,7 +113,12 @@ class Config:
|
|
101
113
|
default = CONFIG_MAP.get("EDSL_RUN_MODE").get("default")
|
102
114
|
if run_mode is None:
|
103
115
|
run_mode = default
|
116
|
+
logger.debug(f"EDSL_RUN_MODE not set, using default: {default}")
|
117
|
+
else:
|
118
|
+
logger.debug(f"EDSL_RUN_MODE set to: {run_mode}")
|
119
|
+
|
104
120
|
if run_mode not in EDSL_RUN_MODES:
|
121
|
+
logger.error(f"Invalid EDSL_RUN_MODE: {run_mode}")
|
105
122
|
raise InvalidEnvironmentVariableError(
|
106
123
|
f"Value `{run_mode}` is not allowed for EDSL_RUN_MODE."
|
107
124
|
)
|
@@ -149,12 +166,19 @@ class Config:
|
|
149
166
|
"""
|
150
167
|
Returns the value of an environment variable.
|
151
168
|
"""
|
169
|
+
logger.debug(f"Getting config value for: {env_var}")
|
170
|
+
|
152
171
|
if env_var not in CONFIG_MAP:
|
172
|
+
logger.error(f"Invalid environment variable requested: {env_var}")
|
153
173
|
raise InvalidEnvironmentVariableError(f"{env_var} is not a valid env var. ")
|
154
174
|
elif env_var not in self.__dict__:
|
155
175
|
info = CONFIG_MAP[env_var].get("info")
|
176
|
+
logger.error(f"Missing environment variable: {env_var}")
|
156
177
|
raise MissingEnvironmentVariableError(f"{env_var} is not set. {info}")
|
157
|
-
|
178
|
+
|
179
|
+
value = self.__dict__.get(env_var)
|
180
|
+
logger.debug(f"Config value for {env_var}: {value}")
|
181
|
+
return value
|
158
182
|
|
159
183
|
def __iter__(self):
|
160
184
|
"""Iterate over the environment variables."""
|
@@ -174,4 +198,4 @@ class Config:
|
|
174
198
|
|
175
199
|
# Note: Python modules are singletons. As such, once this module is imported
|
176
200
|
# the same instance of it is reused across the application.
|
177
|
-
CONFIG = Config()
|
201
|
+
CONFIG = Config()
|
@@ -2,7 +2,7 @@ from collections import UserList
|
|
2
2
|
import asyncio
|
3
3
|
import inspect
|
4
4
|
from typing import Optional, Callable
|
5
|
-
from .. import
|
5
|
+
from .. import QuestionFreeText, Results, AgentList, ScenarioList, Scenario
|
6
6
|
from ..questions import QuestionBase
|
7
7
|
from ..results.Result import Result
|
8
8
|
from jinja2 import Template
|
@@ -120,7 +120,8 @@ What do you say next?"""
|
|
120
120
|
per_round_message_template
|
121
121
|
and "{{ round_message }}" not in next_statement_question.question_text
|
122
122
|
):
|
123
|
-
|
123
|
+
from edsl.conversation.exceptions import ConversationValueError
|
124
|
+
raise ConversationValueError(
|
124
125
|
"If you pass in a per_round_message_template, you must include {{ round_message }} in the question_text."
|
125
126
|
)
|
126
127
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
Exceptions for the conversation module.
|
3
|
+
|
4
|
+
This module defines custom exceptions for the conversation module,
|
5
|
+
including errors for invalid participant configurations, agent interaction
|
6
|
+
failures, and conversation state errors.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from ..base import BaseException
|
10
|
+
|
11
|
+
|
12
|
+
class ConversationError(BaseException):
|
13
|
+
"""
|
14
|
+
Base exception class for all conversation-related errors.
|
15
|
+
|
16
|
+
This is the parent class for all exceptions related to conversation
|
17
|
+
operations, including agent communication, turn management, and
|
18
|
+
participant configuration.
|
19
|
+
"""
|
20
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
21
|
+
|
22
|
+
|
23
|
+
class ConversationValueError(ConversationError):
|
24
|
+
"""
|
25
|
+
Exception raised when an invalid value is provided to a conversation.
|
26
|
+
|
27
|
+
This exception occurs when attempting to create or modify a conversation
|
28
|
+
with invalid values, such as:
|
29
|
+
- Invalid participant configurations
|
30
|
+
- Inappropriate agent parameters
|
31
|
+
- Incompatible conversation settings
|
32
|
+
|
33
|
+
Examples:
|
34
|
+
```python
|
35
|
+
# Attempting to add an invalid participant to a conversation
|
36
|
+
conversation.add_participant(None) # Raises ConversationValueError
|
37
|
+
```
|
38
|
+
"""
|
39
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
40
|
+
|
41
|
+
|
42
|
+
class ConversationStateError(ConversationError):
|
43
|
+
"""
|
44
|
+
Exception raised when the conversation is in an invalid state.
|
45
|
+
|
46
|
+
This exception occurs when attempting to perform an operation that
|
47
|
+
is incompatible with the current state of the conversation, such as:
|
48
|
+
- Ending a conversation that hasn't started
|
49
|
+
- Starting a conversation that's already in progress
|
50
|
+
- Accessing a participant that doesn't exist
|
51
|
+
|
52
|
+
Examples:
|
53
|
+
```python
|
54
|
+
# Attempting to get the next speaker when the conversation is empty
|
55
|
+
empty_conversation.next_speaker() # Raises ConversationStateError
|
56
|
+
```
|
57
|
+
"""
|
58
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|