edsl 0.1.49__py3-none-any.whl → 0.1.51__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 +107 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +27 -12
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +12 -4
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +6 -7
- edsl/caching/sql_dict.py +20 -14
- edsl/cli.py +43 -0
- edsl/config/__init__.py +1 -1
- edsl/config/config_class.py +32 -6
- edsl/conversation/Conversation.py +8 -4
- edsl/conversation/car_buying.py +1 -3
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +2 -8
- edsl/coop/__init__.py +28 -6
- edsl/coop/coop.py +120 -29
- edsl/coop/coop_functions.py +1 -1
- edsl/coop/ep_key_handling.py +1 -1
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +5 -8
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +177 -86
- edsl/dataset/dataset_operations_mixin.py +98 -76
- edsl/dataset/dataset_tree.py +11 -7
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +6 -4
- 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 +5 -6
- edsl/inference_services/data_structures.py +10 -7
- 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 +4 -3
- 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 +7 -3
- 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 +7 -5
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +6 -2
- edsl/instructions/instruction_collection.py +6 -4
- edsl/instructions/instruction_handler.py +12 -15
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +15 -8
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +33 -30
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +5 -5
- edsl/interviews/request_token_estimator.py +5 -2
- edsl/interviews/statistics.py +3 -4
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +9 -13
- 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 +42 -5
- edsl/jobs/async_interview_runner.py +25 -23
- edsl/jobs/check_survey_scenario_compatibility.py +11 -10
- edsl/jobs/data_structures.py +8 -5
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +74 -69
- edsl/jobs/jobs_checks.py +6 -7
- edsl/jobs/jobs_component_constructor.py +4 -4
- edsl/jobs/jobs_pricing_estimation.py +4 -3
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +3 -4
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +27 -24
- edsl/jobs/results_exceptions_handler.py +10 -7
- 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 +9 -8
- 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/__init__.py +24 -1
- edsl/notebooks/exceptions.py +82 -0
- edsl/notebooks/notebook.py +7 -3
- edsl/notebooks/notebook_to_latex.py +1 -2
- 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 +24 -1
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +15 -7
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +6 -4
- edsl/questions/answer_validator_mixin.py +318 -323
- edsl/questions/compose_questions.py +3 -3
- edsl/questions/descriptors.py +11 -50
- 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 +46 -19
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +13 -7
- edsl/questions/question_budget.py +503 -98
- edsl/questions/question_check_box.py +660 -160
- edsl/questions/question_dict.py +345 -194
- edsl/questions/question_extract.py +401 -61
- edsl/questions/question_free_text.py +80 -14
- edsl/questions/question_functional.py +119 -9
- edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
- edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
- edsl/questions/question_list.py +275 -28
- edsl/questions/question_matrix.py +643 -96
- edsl/questions/question_multiple_choice.py +219 -51
- edsl/questions/question_numerical.py +361 -32
- edsl/questions/question_rank.py +401 -124
- edsl/questions/question_registry.py +7 -5
- edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
- edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
- edsl/questions/register_questions_meta.py +2 -2
- edsl/questions/response_validator_abc.py +13 -15
- edsl/questions/response_validator_factory.py +10 -12
- 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 +1 -2
- edsl/results/result.py +11 -9
- edsl/results/results.py +480 -321
- 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 +1 -3
- edsl/scenarios/scenario_list.py +179 -27
- 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_css.py +3 -3
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/surveys/survey_simulator.py +2 -1
- edsl/tasks/__init__.py +23 -1
- edsl/tasks/exceptions.py +72 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +8 -10
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +29 -1
- edsl/tokens/exceptions.py +37 -0
- edsl/tokens/interview_token_usage.py +3 -2
- edsl/tokens/token_usage.py +4 -3
- 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.51.dist-info}/METADATA +32 -2
- edsl-0.1.51.dist-info/RECORD +365 -0
- edsl-0.1.51.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/questions/derived/__init__.py +0 -0
- 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.51.dist-info}/LICENSE +0 -0
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
edsl/jobs/exceptions.py
CHANGED
@@ -1,26 +1,195 @@
|
|
1
|
-
from textwrap import dedent
|
2
1
|
|
3
2
|
from ..base import BaseException
|
4
3
|
|
5
4
|
class JobsErrors(BaseException):
|
6
|
-
|
5
|
+
"""
|
6
|
+
Base exception class for all job-related errors.
|
7
|
+
|
8
|
+
This is the parent class for all exceptions related to job execution
|
9
|
+
in the EDSL framework. It provides a common type for catching any
|
10
|
+
job-specific error.
|
11
|
+
"""
|
12
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/jobs.html"
|
7
13
|
|
8
14
|
|
9
15
|
class JobsRunError(JobsErrors):
|
10
|
-
|
16
|
+
"""
|
17
|
+
Exception raised when a job fails to run correctly.
|
18
|
+
|
19
|
+
This exception indicates issues with job execution such as:
|
20
|
+
- Configuration problems before job execution
|
21
|
+
- Resource allocation failures
|
22
|
+
- Job termination due to internal errors
|
23
|
+
|
24
|
+
To fix this error:
|
25
|
+
1. Check the job configuration for any invalid parameters
|
26
|
+
2. Ensure all required resources (models, agents, etc.) are available
|
27
|
+
3. Verify that dependent services are accessible
|
28
|
+
|
29
|
+
Note: This exception is currently not used actively in the codebase,
|
30
|
+
but is kept for potential future use and test cases.
|
31
|
+
"""
|
32
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/jobs.html"
|
11
33
|
|
12
34
|
|
13
35
|
class MissingRemoteInferenceError(JobsErrors):
|
14
|
-
|
36
|
+
"""
|
37
|
+
Exception raised when remote inference is required but not configured.
|
38
|
+
|
39
|
+
This exception occurs when:
|
40
|
+
- A job requires remote inference capabilities but they're not available
|
41
|
+
- Credentials for remote inference are missing or invalid
|
42
|
+
|
43
|
+
To fix this error:
|
44
|
+
1. Set up remote inference configuration in your environment
|
45
|
+
2. Provide valid API keys for the required inference service
|
46
|
+
|
47
|
+
Note: This exception is defined but not currently used in the codebase.
|
48
|
+
It raises Exception("not used") to indicate this state.
|
49
|
+
"""
|
50
|
+
def __init__(self, message="Remote inference configuration is missing", **kwargs):
|
51
|
+
super().__init__(message, **kwargs)
|
15
52
|
|
16
53
|
|
17
|
-
class InterviewError(
|
18
|
-
|
54
|
+
class InterviewError(JobsErrors):
|
55
|
+
"""
|
56
|
+
Base exception class for all interview-related errors.
|
57
|
+
|
58
|
+
This exception serves as the parent class for specific interview errors
|
59
|
+
and handles cases where an interview process fails for reasons not covered
|
60
|
+
by more specific exceptions.
|
61
|
+
"""
|
62
|
+
|
63
|
+
def __init__(self, message="An error occurred during the interview process", **kwargs):
|
64
|
+
super().__init__(message, **kwargs)
|
65
|
+
self.message = message
|
19
66
|
|
20
67
|
|
21
68
|
class InterviewErrorPriorTaskCanceled(InterviewError):
|
22
|
-
|
69
|
+
"""
|
70
|
+
Exception raised when a task cannot run because a dependent task failed.
|
71
|
+
|
72
|
+
This exception is raised in task pipelines when a prerequisite task
|
73
|
+
fails or is canceled, preventing dependent tasks from executing.
|
74
|
+
|
75
|
+
When you encounter this error:
|
76
|
+
1. Check the error logs for information about the failed dependent task
|
77
|
+
2. Fix any issues with the prerequisite task first before retrying
|
78
|
+
3. If using custom task dependencies, verify that the dependency chain is correct
|
79
|
+
"""
|
80
|
+
|
81
|
+
def __init__(self, message="Cannot run this task because a required prior task was canceled or failed"):
|
82
|
+
super().__init__(message)
|
23
83
|
|
24
84
|
|
25
85
|
class InterviewTimeoutError(InterviewError):
|
26
|
-
|
86
|
+
"""
|
87
|
+
Exception raised when an interview operation times out.
|
88
|
+
|
89
|
+
This exception indicates that a model call or other operation
|
90
|
+
during an interview process took too long to complete.
|
91
|
+
|
92
|
+
To fix this error:
|
93
|
+
1. Check your network connection if using remote models
|
94
|
+
2. Consider increasing timeout settings if dealing with complex prompts
|
95
|
+
3. Try a different model provider if consistently experiencing timeouts
|
96
|
+
|
97
|
+
Note: While defined here, the codebase currently uses LanguageModelNoResponseError
|
98
|
+
to handle timeouts in actual operation.
|
99
|
+
"""
|
100
|
+
|
101
|
+
def __init__(self, message="The interview operation timed out", **kwargs):
|
102
|
+
super().__init__(message, **kwargs)
|
103
|
+
|
104
|
+
|
105
|
+
class JobsValueError(JobsErrors):
|
106
|
+
"""
|
107
|
+
Exception raised when there's an invalid value in job-related operations.
|
108
|
+
|
109
|
+
This exception indicates that a parameter or value used in job configuration
|
110
|
+
or execution is invalid, out of range, or otherwise inappropriate.
|
111
|
+
|
112
|
+
Common causes include:
|
113
|
+
- Invalid question names in job configuration
|
114
|
+
- Incompatible survey and scenario combinations
|
115
|
+
- Invalid parameter values for job construction
|
116
|
+
|
117
|
+
To fix this error:
|
118
|
+
1. Check the parameter values in your job configuration
|
119
|
+
2. Ensure that all question names exist in the survey
|
120
|
+
3. Verify that survey and scenario combinations are compatible
|
121
|
+
"""
|
122
|
+
|
123
|
+
def __init__(self, message="Invalid value in jobs module", **kwargs):
|
124
|
+
super().__init__(message, **kwargs)
|
125
|
+
|
126
|
+
|
127
|
+
class JobsCompatibilityError(JobsErrors):
|
128
|
+
"""
|
129
|
+
Exception raised when there are compatibility issues between components.
|
130
|
+
|
131
|
+
This exception indicates that the components being used together (like
|
132
|
+
surveys and scenarios) are not compatible with each other for the requested
|
133
|
+
operation.
|
134
|
+
|
135
|
+
To fix this error:
|
136
|
+
1. Check that your survey and scenario are compatible
|
137
|
+
2. Ensure all referenced questions exist in the survey
|
138
|
+
3. Verify that scenario fields match expected inputs for questions
|
139
|
+
"""
|
140
|
+
|
141
|
+
def __init__(self, message="Compatibility issue between job components", **kwargs):
|
142
|
+
super().__init__(message, **kwargs)
|
143
|
+
|
144
|
+
|
145
|
+
class JobsImplementationError(JobsErrors):
|
146
|
+
"""
|
147
|
+
Exception raised when a required method or feature is not implemented.
|
148
|
+
|
149
|
+
This exception indicates that a method or functionality expected to be
|
150
|
+
available is not implemented, typically in abstract classes or
|
151
|
+
interfaces that require concrete implementation.
|
152
|
+
|
153
|
+
To fix this error:
|
154
|
+
1. Implement the required method in your subclass
|
155
|
+
2. Use a different implementation that provides the required functionality
|
156
|
+
3. Check for updates to the library that might implement this feature
|
157
|
+
"""
|
158
|
+
|
159
|
+
def __init__(self, message="Required method or feature is not implemented", **kwargs):
|
160
|
+
super().__init__(message, **kwargs)
|
161
|
+
|
162
|
+
|
163
|
+
class RemoteInferenceError(JobsErrors):
|
164
|
+
"""
|
165
|
+
Exception raised when there are issues with remote inference.
|
166
|
+
|
167
|
+
This exception indicates problems with remote inference configuration,
|
168
|
+
connection, or execution. It can occur when a remote job fails to create
|
169
|
+
or execute properly.
|
170
|
+
|
171
|
+
To fix this error:
|
172
|
+
1. Check your remote inference configuration
|
173
|
+
2. Verify API keys and authentication are correct
|
174
|
+
3. Ensure the remote service is available and responsive
|
175
|
+
"""
|
176
|
+
|
177
|
+
def __init__(self, message="Remote inference operation failed", **kwargs):
|
178
|
+
super().__init__(message, **kwargs)
|
179
|
+
|
180
|
+
|
181
|
+
class JobsTypeError(JobsErrors):
|
182
|
+
"""
|
183
|
+
Exception raised when there's a type mismatch in job-related operations.
|
184
|
+
|
185
|
+
This exception indicates that a parameter or value is of the wrong type
|
186
|
+
for the operation being performed.
|
187
|
+
|
188
|
+
To fix this error:
|
189
|
+
1. Check the types of parameters you're passing to job functions
|
190
|
+
2. Ensure that you're using the correct types as defined in the API
|
191
|
+
3. Convert parameters to the expected types if necessary
|
192
|
+
"""
|
193
|
+
|
194
|
+
def __init__(self, message="Type mismatch in jobs module", **kwargs):
|
195
|
+
super().__init__(message, **kwargs)
|
edsl/jobs/fetch_invigilator.py
CHANGED
edsl/jobs/jobs.py
CHANGED
@@ -17,19 +17,14 @@ who need to run complex simulations with language models.
|
|
17
17
|
"""
|
18
18
|
from __future__ import annotations
|
19
19
|
import asyncio
|
20
|
-
from inspect import signature
|
21
20
|
from typing import Optional, Union, TypeVar, Callable, cast
|
22
21
|
from functools import wraps
|
23
22
|
|
24
23
|
from typing import (
|
25
24
|
Literal,
|
26
|
-
Optional,
|
27
|
-
Union,
|
28
25
|
Sequence,
|
29
26
|
Generator,
|
30
27
|
TYPE_CHECKING,
|
31
|
-
Callable,
|
32
|
-
Tuple,
|
33
28
|
)
|
34
29
|
|
35
30
|
from ..base import Base
|
@@ -40,6 +35,7 @@ from ..buckets import BucketCollection
|
|
40
35
|
from ..scenarios import Scenario, ScenarioList
|
41
36
|
from ..surveys import Survey
|
42
37
|
from ..interviews import Interview
|
38
|
+
from .exceptions import JobsValueError, JobsImplementationError
|
43
39
|
|
44
40
|
from .jobs_pricing_estimation import JobsPrompts
|
45
41
|
from .remote_inference import JobsRemoteInferenceHandler
|
@@ -73,27 +69,25 @@ P = ParamSpec("P")
|
|
73
69
|
T = TypeVar("T")
|
74
70
|
|
75
71
|
|
76
|
-
|
77
|
-
|
78
72
|
def with_config(f: Callable[P, T]) -> Callable[P, T]:
|
79
73
|
"""
|
80
74
|
Decorator that processes function parameters to match the RunConfig dataclass structure.
|
81
|
-
|
75
|
+
|
82
76
|
This decorator is used primarily with the run() and run_async() methods to provide
|
83
77
|
a consistent interface for job configuration while maintaining a clean API.
|
84
|
-
|
78
|
+
|
85
79
|
The decorator:
|
86
80
|
1. Extracts environment-related parameters into a RunEnvironment instance
|
87
81
|
2. Extracts execution-related parameters into a RunParameters instance
|
88
82
|
3. Combines both into a single RunConfig object
|
89
83
|
4. Passes this RunConfig to the decorated function as a keyword argument
|
90
|
-
|
84
|
+
|
91
85
|
Parameters:
|
92
86
|
f (Callable): The function to decorate, typically run() or run_async()
|
93
|
-
|
87
|
+
|
94
88
|
Returns:
|
95
89
|
Callable: A wrapped function that accepts all RunConfig parameters directly
|
96
|
-
|
90
|
+
|
97
91
|
Example:
|
98
92
|
@with_config
|
99
93
|
def run(self, *, config: RunConfig) -> Results:
|
@@ -107,7 +101,8 @@ def with_config(f: Callable[P, T]) -> Callable[P, T]:
|
|
107
101
|
name: field.default
|
108
102
|
for name, field in RunEnvironment.__dataclass_fields__.items()
|
109
103
|
}
|
110
|
-
|
104
|
+
# Combined fields dict used for reference during development
|
105
|
+
# combined = {**parameter_fields, **environment_fields}
|
111
106
|
|
112
107
|
@wraps(f)
|
113
108
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
@@ -126,25 +121,25 @@ def with_config(f: Callable[P, T]) -> Callable[P, T]:
|
|
126
121
|
class Jobs(Base):
|
127
122
|
"""
|
128
123
|
A collection of agents, scenarios, models, and a survey that orchestrates interviews.
|
129
|
-
|
124
|
+
|
130
125
|
The Jobs class is the central component for running large-scale experiments or simulations
|
131
126
|
in EDSL. It manages the execution of interviews where agents interact with surveys through
|
132
127
|
language models, possibly in different scenarios.
|
133
|
-
|
128
|
+
|
134
129
|
Key responsibilities:
|
135
130
|
1. Managing collections of agents, scenarios, and models
|
136
131
|
2. Configuring execution parameters (caching, API keys, etc.)
|
137
132
|
3. Managing parallel execution of interviews
|
138
133
|
4. Handling remote cache and inference capabilities
|
139
134
|
5. Collecting and organizing results
|
140
|
-
|
135
|
+
|
141
136
|
A typical workflow involves:
|
142
137
|
1. Creating a survey with questions
|
143
138
|
2. Creating a Jobs instance with that survey
|
144
139
|
3. Adding agents, scenarios, and models using the `by()` method
|
145
140
|
4. Running the job with `run()` or `run_async()`
|
146
141
|
5. Analyzing the results
|
147
|
-
|
142
|
+
|
148
143
|
Jobs implements a fluent interface pattern, where methods return self to allow
|
149
144
|
method chaining for concise, readable configuration.
|
150
145
|
"""
|
@@ -159,22 +154,22 @@ class Jobs(Base):
|
|
159
154
|
scenarios: Optional[Union["ScenarioList", list["Scenario"]]] = None,
|
160
155
|
):
|
161
156
|
"""Initialize a Jobs instance with a survey and optional components.
|
162
|
-
|
157
|
+
|
163
158
|
The Jobs constructor requires a survey and optionally accepts collections of
|
164
159
|
agents, models, and scenarios. If any of these optional components are not provided,
|
165
160
|
they can be added later using the `by()` method or will be automatically populated
|
166
161
|
with defaults when the job is run.
|
167
|
-
|
162
|
+
|
168
163
|
Parameters:
|
169
164
|
survey (Survey): The survey containing questions to be used in the job
|
170
165
|
agents (Union[list[Agent], AgentList], optional): The agents that will take the survey
|
171
166
|
models (Union[ModelList, list[LanguageModel]], optional): The language models to use
|
172
167
|
scenarios (Union[ScenarioList, list[Scenario]], optional): The scenarios to run
|
173
|
-
|
168
|
+
|
174
169
|
Raises:
|
175
170
|
ValueError: If the survey contains questions with invalid names
|
176
171
|
(e.g., names containing template variables)
|
177
|
-
|
172
|
+
|
178
173
|
Examples:
|
179
174
|
>>> from edsl.surveys import Survey
|
180
175
|
>>> from edsl.questions import QuestionFreeText
|
@@ -183,11 +178,7 @@ class Jobs(Base):
|
|
183
178
|
>>> j = Jobs(survey = s)
|
184
179
|
>>> q = QuestionFreeText(question_name="{{ bad_name }}", question_text="What is your name?")
|
185
180
|
>>> s = Survey(questions=[q])
|
186
|
-
|
187
|
-
Traceback (most recent call last):
|
188
|
-
...
|
189
|
-
ValueError: At least some question names are not valid: ['{{ bad_name }}']
|
190
|
-
|
181
|
+
|
191
182
|
Notes:
|
192
183
|
- The survey's questions must have valid names without templating variables
|
193
184
|
- If agents, models, or scenarios are not provided, defaults will be used when running
|
@@ -206,10 +197,15 @@ class Jobs(Base):
|
|
206
197
|
|
207
198
|
try:
|
208
199
|
assert self.survey.question_names_valid()
|
209
|
-
except Exception
|
210
|
-
invalid_question_names = [
|
211
|
-
|
212
|
-
|
200
|
+
except Exception:
|
201
|
+
invalid_question_names = [
|
202
|
+
q.question_name
|
203
|
+
for q in self.survey.questions
|
204
|
+
if not q.is_valid_question_name()
|
205
|
+
]
|
206
|
+
raise JobsValueError(
|
207
|
+
f"At least some question names are not valid: {invalid_question_names}"
|
208
|
+
)
|
213
209
|
|
214
210
|
def add_running_env(self, running_env: RunEnvironment):
|
215
211
|
self.run_config.add_environment(running_env)
|
@@ -224,7 +220,7 @@ class Jobs(Base):
|
|
224
220
|
self.run_config.add_cache(cache)
|
225
221
|
return self
|
226
222
|
|
227
|
-
def using_bucket_collection(self, bucket_collection:
|
223
|
+
def using_bucket_collection(self, bucket_collection: "BucketCollection") -> Jobs:
|
228
224
|
"""
|
229
225
|
Add a BucketCollection to the job.
|
230
226
|
|
@@ -233,7 +229,7 @@ class Jobs(Base):
|
|
233
229
|
self.run_config.add_bucket_collection(bucket_collection)
|
234
230
|
return self
|
235
231
|
|
236
|
-
def using_key_lookup(self, key_lookup:
|
232
|
+
def using_key_lookup(self, key_lookup: "KeyLookup") -> Jobs:
|
237
233
|
"""
|
238
234
|
Add a KeyLookup to the job.
|
239
235
|
|
@@ -339,18 +335,18 @@ class Jobs(Base):
|
|
339
335
|
) -> "Jobs":
|
340
336
|
"""
|
341
337
|
Add agents, scenarios, and language models to a job using a fluent interface.
|
342
|
-
|
338
|
+
|
343
339
|
This method is the primary way to configure a Jobs instance with components.
|
344
340
|
It intelligently handles different types of objects and collections, making
|
345
341
|
it easy to build complex job configurations with a concise syntax.
|
346
|
-
|
342
|
+
|
347
343
|
Parameters:
|
348
|
-
*args: Objects or sequences of objects to add to the job.
|
344
|
+
*args: Objects or sequences of objects to add to the job.
|
349
345
|
Supported types are Agent, Scenario, LanguageModel, and sequences of these.
|
350
|
-
|
346
|
+
|
351
347
|
Returns:
|
352
348
|
Jobs: The Jobs instance (self) for method chaining
|
353
|
-
|
349
|
+
|
354
350
|
Examples:
|
355
351
|
>>> from edsl.surveys import Survey
|
356
352
|
>>> from edsl.questions import QuestionFreeText
|
@@ -361,17 +357,17 @@ class Jobs(Base):
|
|
361
357
|
>>> from edsl.agents import Agent; a = Agent(traits = {"status": "Sad"})
|
362
358
|
>>> j.by(a).agents
|
363
359
|
AgentList([Agent(traits = {'status': 'Sad'})])
|
364
|
-
|
360
|
+
|
365
361
|
# Adding multiple components at once
|
366
362
|
>>> from edsl.language_models import Model
|
367
363
|
>>> from edsl.scenarios import Scenario
|
368
364
|
>>> j = Jobs.example()
|
369
365
|
>>> _ = j.by(Agent(traits={"mood": "happy"})).by(Model(temperature=0.7)).by(Scenario({"time": "morning"}))
|
370
|
-
|
366
|
+
|
371
367
|
# Adding a sequence of the same type
|
372
368
|
>>> agents = [Agent(traits={"age": i}) for i in range(5)]
|
373
369
|
>>> _ = j.by(agents)
|
374
|
-
|
370
|
+
|
375
371
|
Notes:
|
376
372
|
- All objects must implement 'get_value', 'set_value', and '__add__' methods
|
377
373
|
- Agent traits: When adding agents with traits to existing agents, the traits are
|
@@ -475,16 +471,19 @@ class Jobs(Base):
|
|
475
471
|
|
476
472
|
def show_flow(self, filename: Optional[str] = None) -> None:
|
477
473
|
"""Show the flow of the survey.
|
478
|
-
|
474
|
+
|
479
475
|
>>> from edsl.jobs import Jobs
|
480
476
|
>>> Jobs.example().show_flow()
|
481
477
|
"""
|
482
478
|
from ..surveys import SurveyFlowVisualization
|
483
|
-
|
479
|
+
|
480
|
+
if self.scenarios:
|
484
481
|
scenario = self.scenarios[0]
|
485
482
|
else:
|
486
483
|
scenario = None
|
487
|
-
SurveyFlowVisualization(self.survey, scenario=scenario, agent=None).show_flow(
|
484
|
+
SurveyFlowVisualization(self.survey, scenario=scenario, agent=None).show_flow(
|
485
|
+
filename=filename
|
486
|
+
)
|
488
487
|
|
489
488
|
def interviews(self) -> list[Interview]:
|
490
489
|
"""
|
@@ -585,7 +584,7 @@ class Jobs(Base):
|
|
585
584
|
return user_edsl_settings.get("remote_caching", False)
|
586
585
|
except requests.ConnectionError:
|
587
586
|
pass
|
588
|
-
except CoopServerResponseError
|
587
|
+
except CoopServerResponseError:
|
589
588
|
pass
|
590
589
|
|
591
590
|
return False
|
@@ -604,7 +603,7 @@ class Jobs(Base):
|
|
604
603
|
)
|
605
604
|
return job_info
|
606
605
|
|
607
|
-
def _create_remote_inference_handler(self) ->
|
606
|
+
def _create_remote_inference_handler(self) -> "JobsRemoteInferenceHandler":
|
608
607
|
return JobsRemoteInferenceHandler(
|
609
608
|
self, verbose=self.run_config.parameters.verbose
|
610
609
|
)
|
@@ -621,15 +620,15 @@ class Jobs(Base):
|
|
621
620
|
if jh.use_remote_inference(self.run_config.parameters.disable_remote_inference):
|
622
621
|
job_info: RemoteJobInfo = self._start_remote_inference_job(jh)
|
623
622
|
if background:
|
624
|
-
from
|
623
|
+
from ..results import Results
|
625
624
|
|
626
625
|
results = Results.from_job_info(job_info)
|
627
|
-
return results
|
626
|
+
return results, None
|
628
627
|
else:
|
629
|
-
results = jh.poll_remote_inference_job(job_info)
|
630
|
-
return results
|
628
|
+
results, reason = jh.poll_remote_inference_job(job_info)
|
629
|
+
return results, reason
|
631
630
|
else:
|
632
|
-
return None
|
631
|
+
return None, None
|
633
632
|
|
634
633
|
def _prepare_to_run(self) -> None:
|
635
634
|
"This makes sure that the job is ready to run and that keys are in place for a remote job."
|
@@ -646,9 +645,9 @@ class Jobs(Base):
|
|
646
645
|
jc.check_api_keys()
|
647
646
|
|
648
647
|
async def _execute_with_remote_cache(self, run_job_async: bool) -> Results:
|
649
|
-
|
648
|
+
# Remote cache usage determination happens inside this method
|
649
|
+
# use_remote_cache = self.use_remote_cache()
|
650
650
|
|
651
|
-
from ..coop import Coop
|
652
651
|
from .jobs_runner_asyncio import JobsRunnerAsyncio
|
653
652
|
from ..caching import Cache
|
654
653
|
|
@@ -707,8 +706,9 @@ class Jobs(Base):
|
|
707
706
|
self.run_config.environment.cache = Cache(immediate_write=False)
|
708
707
|
|
709
708
|
# first try to run the job remotely
|
710
|
-
|
711
|
-
|
709
|
+
results, reason = self._remote_results(config)
|
710
|
+
if results is not None:
|
711
|
+
return results, reason
|
712
712
|
|
713
713
|
self._check_if_local_keys_ok()
|
714
714
|
|
@@ -725,17 +725,17 @@ class Jobs(Base):
|
|
725
725
|
self.run_config.environment.key_lookup
|
726
726
|
)
|
727
727
|
|
728
|
-
return None
|
728
|
+
return None, reason
|
729
729
|
|
730
730
|
@with_config
|
731
731
|
def run(self, *, config: RunConfig) -> "Results":
|
732
732
|
"""
|
733
733
|
Runs the job by conducting interviews and returns their results.
|
734
|
-
|
734
|
+
|
735
735
|
This is the main entry point for executing a job. It processes all interviews
|
736
736
|
(combinations of agents, scenarios, and models) and returns a Results object
|
737
737
|
containing all responses and metadata.
|
738
|
-
|
738
|
+
|
739
739
|
Parameters:
|
740
740
|
n (int): Number of iterations to run each interview (default: 1)
|
741
741
|
progress_bar (bool): Whether to show a progress bar (default: False)
|
@@ -756,16 +756,16 @@ class Jobs(Base):
|
|
756
756
|
cache (Cache, optional): Cache object to store results
|
757
757
|
bucket_collection (BucketCollection, optional): Object to track API calls
|
758
758
|
key_lookup (KeyLookup, optional): Object to manage API keys
|
759
|
-
|
759
|
+
|
760
760
|
Returns:
|
761
761
|
Results: A Results object containing all responses and metadata
|
762
|
-
|
762
|
+
|
763
763
|
Notes:
|
764
764
|
- This method will first try to use remote inference if available
|
765
765
|
- If remote inference is not available, it will run locally
|
766
766
|
- For long-running jobs, consider using progress_bar=True
|
767
767
|
- For maximum performance, ensure appropriate caching is configured
|
768
|
-
|
768
|
+
|
769
769
|
Example:
|
770
770
|
>>> from edsl.jobs import Jobs
|
771
771
|
>>> from edsl.caching import Cache
|
@@ -775,22 +775,25 @@ class Jobs(Base):
|
|
775
775
|
>>> results = job.by(m).run(cache=Cache(), progress_bar=False, n=2, disable_remote_inference=True)
|
776
776
|
...
|
777
777
|
"""
|
778
|
-
potentially_completed_results = self._run(config)
|
778
|
+
potentially_completed_results, reason = self._run(config)
|
779
779
|
|
780
780
|
if potentially_completed_results is not None:
|
781
781
|
return potentially_completed_results
|
782
782
|
|
783
|
+
if reason == "insufficient funds":
|
784
|
+
return None
|
785
|
+
|
783
786
|
return asyncio.run(self._execute_with_remote_cache(run_job_async=False))
|
784
787
|
|
785
788
|
@with_config
|
786
789
|
async def run_async(self, *, config: RunConfig) -> "Results":
|
787
790
|
"""
|
788
791
|
Asynchronously runs the job by conducting interviews and returns their results.
|
789
|
-
|
792
|
+
|
790
793
|
This method is the asynchronous version of `run()`. It has the same functionality and
|
791
794
|
parameters but can be awaited in an async context for better integration with
|
792
795
|
asynchronous code.
|
793
|
-
|
796
|
+
|
794
797
|
Parameters:
|
795
798
|
n (int): Number of iterations to run each interview (default: 1)
|
796
799
|
progress_bar (bool): Whether to show a progress bar (default: False)
|
@@ -811,15 +814,15 @@ class Jobs(Base):
|
|
811
814
|
cache (Cache, optional): Cache object to store results
|
812
815
|
bucket_collection (BucketCollection, optional): Object to track API calls
|
813
816
|
key_lookup (KeyLookup, optional): Object to manage API keys
|
814
|
-
|
817
|
+
|
815
818
|
Returns:
|
816
819
|
Results: A Results object containing all responses and metadata
|
817
|
-
|
820
|
+
|
818
821
|
Notes:
|
819
822
|
- This method should be used in async contexts (e.g., with `await`)
|
820
823
|
- For non-async contexts, use the `run()` method instead
|
821
824
|
- This method is particularly useful in notebook environments or async applications
|
822
|
-
|
825
|
+
|
823
826
|
Example:
|
824
827
|
>>> import asyncio
|
825
828
|
>>> from edsl.jobs import Jobs
|
@@ -878,7 +881,7 @@ class Jobs(Base):
|
|
878
881
|
],
|
879
882
|
}
|
880
883
|
if add_edsl_version:
|
881
|
-
from
|
884
|
+
from .. import __version__
|
882
885
|
|
883
886
|
d["edsl_version"] = __version__
|
884
887
|
d["edsl_class_name"] = "Jobs"
|
@@ -960,7 +963,9 @@ class Jobs(Base):
|
|
960
963
|
"""Return the answer to a question. This is a method that can be added to an agent."""
|
961
964
|
|
962
965
|
if random.random() < throw_exception_probability:
|
963
|
-
|
966
|
+
from .exceptions import JobsErrors
|
967
|
+
|
968
|
+
raise JobsErrors("Simulated error during question answering")
|
964
969
|
return agent_answers[
|
965
970
|
(self.traits["status"], question.question_name, scenario["period"])
|
966
971
|
]
|
@@ -1001,7 +1006,7 @@ class Jobs(Base):
|
|
1001
1006
|
|
1002
1007
|
def code(self):
|
1003
1008
|
"""Return the code to create this instance."""
|
1004
|
-
raise
|
1009
|
+
raise JobsImplementationError("Code generation not implemented yet")
|
1005
1010
|
|
1006
1011
|
|
1007
1012
|
def main():
|
edsl/jobs/jobs_checks.py
CHANGED
@@ -3,7 +3,7 @@ Checks a Jobs object for missing API keys and other requirements.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import os
|
6
|
-
from
|
6
|
+
from ..key_management.key_lookup_builder import MissingAPIKeyError
|
7
7
|
|
8
8
|
|
9
9
|
class JobsChecks:
|
@@ -16,7 +16,7 @@ class JobsChecks:
|
|
16
16
|
self.jobs = jobs
|
17
17
|
|
18
18
|
def check_api_keys(self) -> None:
|
19
|
-
from
|
19
|
+
from ..language_models.model import Model
|
20
20
|
|
21
21
|
if len(self.jobs.models) == 0:
|
22
22
|
models = [Model()]
|
@@ -28,6 +28,7 @@ class JobsChecks:
|
|
28
28
|
raise MissingAPIKeyError(
|
29
29
|
model_name=str(model.model),
|
30
30
|
inference_service=model._inference_service_,
|
31
|
+
silent=False
|
31
32
|
)
|
32
33
|
|
33
34
|
def get_missing_api_keys(self) -> set:
|
@@ -36,8 +37,7 @@ class JobsChecks:
|
|
36
37
|
"""
|
37
38
|
missing_api_keys = set()
|
38
39
|
|
39
|
-
from
|
40
|
-
from edsl.enums import service_to_api_keyname
|
40
|
+
from ..enums import service_to_api_keyname
|
41
41
|
|
42
42
|
for model in self.jobs.models: # + [Model()]:
|
43
43
|
if not model.has_valid_api_key():
|
@@ -134,9 +134,8 @@ class JobsChecks:
|
|
134
134
|
def key_process(self):
|
135
135
|
import secrets
|
136
136
|
from dotenv import load_dotenv
|
137
|
-
from
|
138
|
-
from
|
139
|
-
from edsl.utilities.utilities import write_api_key_to_env
|
137
|
+
from ..coop.coop import Coop
|
138
|
+
from ..utilities.utilities import write_api_key_to_env
|
140
139
|
|
141
140
|
missing_api_keys = self.get_missing_api_keys()
|
142
141
|
|