edsl 0.1.14__py3-none-any.whl → 0.1.40__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/Base.py +348 -38
- edsl/BaseDiff.py +260 -0
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +46 -10
- edsl/__version__.py +1 -0
- edsl/agents/Agent.py +842 -144
- edsl/agents/AgentList.py +521 -25
- edsl/agents/Invigilator.py +250 -374
- edsl/agents/InvigilatorBase.py +257 -0
- edsl/agents/PromptConstructor.py +272 -0
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/descriptors.py +43 -13
- edsl/agents/prompt_helpers.py +129 -0
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -0
- edsl/auto/StageBase.py +243 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +74 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +218 -0
- edsl/base/Base.py +279 -0
- edsl/config.py +121 -104
- edsl/conversation/Conversation.py +290 -0
- edsl/conversation/car_buying.py +59 -0
- edsl/conversation/chips.py +95 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -0
- edsl/coop/__init__.py +1 -0
- edsl/coop/coop.py +1029 -134
- edsl/coop/utils.py +131 -0
- edsl/data/Cache.py +560 -89
- edsl/data/CacheEntry.py +230 -0
- edsl/data/CacheHandler.py +168 -0
- edsl/data/RemoteCacheSync.py +186 -0
- edsl/data/SQLiteDict.py +292 -0
- edsl/data/__init__.py +5 -3
- edsl/data/orm.py +6 -33
- edsl/data_transfer_models.py +74 -27
- edsl/enums.py +165 -8
- edsl/exceptions/BaseException.py +21 -0
- edsl/exceptions/__init__.py +52 -46
- edsl/exceptions/agents.py +33 -15
- edsl/exceptions/cache.py +5 -0
- edsl/exceptions/coop.py +8 -0
- edsl/exceptions/general.py +34 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +15 -0
- edsl/exceptions/language_models.py +46 -1
- edsl/exceptions/questions.py +80 -5
- edsl/exceptions/results.py +16 -5
- edsl/exceptions/scenarios.py +29 -0
- edsl/exceptions/surveys.py +13 -10
- edsl/inference_services/AnthropicService.py +106 -0
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -0
- edsl/inference_services/AzureAI.py +215 -0
- edsl/inference_services/DeepInfraService.py +18 -0
- edsl/inference_services/GoogleService.py +143 -0
- edsl/inference_services/GroqService.py +20 -0
- edsl/inference_services/InferenceServiceABC.py +80 -0
- edsl/inference_services/InferenceServicesCollection.py +138 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OllamaService.py +18 -0
- edsl/inference_services/OpenAIService.py +236 -0
- edsl/inference_services/PerplexityService.py +160 -0
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -0
- edsl/inference_services/TogetherAIService.py +172 -0
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/registry.py +41 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +21 -20
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +684 -204
- edsl/jobs/JobsChecks.py +172 -0
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -0
- edsl/jobs/buckets/ModelBuckets.py +65 -0
- edsl/jobs/buckets/TokenBucket.py +283 -0
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +392 -0
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
- edsl/jobs/interviews/InterviewStatistic.py +63 -0
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
- edsl/jobs/interviews/InterviewStatusLog.py +92 -0
- edsl/jobs/interviews/ReportErrors.py +66 -0
- edsl/jobs/interviews/interview_status_enum.py +9 -0
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
- edsl/jobs/runners/JobsRunnerStatus.py +298 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
- edsl/jobs/tasks/TaskCreators.py +64 -0
- edsl/jobs/tasks/TaskHistory.py +470 -0
- edsl/jobs/tasks/TaskStatusLog.py +23 -0
- edsl/jobs/tasks/task_status_enum.py +161 -0
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
- edsl/jobs/tokens/TokenUsage.py +34 -0
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +507 -386
- edsl/language_models/ModelList.py +164 -0
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
- edsl/language_models/__init__.py +1 -8
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +109 -41
- edsl/language_models/utilities.py +65 -0
- edsl/notebooks/Notebook.py +263 -0
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -0
- edsl/prompts/Prompt.py +222 -93
- edsl/prompts/__init__.py +1 -1
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -0
- edsl/questions/QuestionBasePromptsMixin.py +221 -0
- edsl/questions/QuestionBudget.py +164 -67
- edsl/questions/QuestionCheckBox.py +281 -62
- edsl/questions/QuestionDict.py +343 -0
- edsl/questions/QuestionExtract.py +136 -50
- edsl/questions/QuestionFreeText.py +79 -55
- edsl/questions/QuestionFunctional.py +138 -41
- edsl/questions/QuestionList.py +184 -57
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +293 -69
- edsl/questions/QuestionNumerical.py +109 -56
- edsl/questions/QuestionRank.py +244 -49
- edsl/questions/Quick.py +41 -0
- edsl/questions/SimpleAskMixin.py +74 -0
- edsl/questions/__init__.py +9 -6
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
- edsl/questions/compose_questions.py +13 -7
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +28 -26
- edsl/questions/derived/QuestionLinearScale.py +41 -28
- edsl/questions/derived/QuestionTopK.py +34 -26
- edsl/questions/derived/QuestionYesNo.py +40 -27
- edsl/questions/descriptors.py +228 -74
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_base_gen_mixin.py +168 -0
- edsl/questions/question_registry.py +130 -46
- edsl/questions/register_questions_meta.py +71 -0
- edsl/questions/response_validator_abc.py +188 -0
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +5 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/dict/__init__.py +0 -0
- edsl/questions/templates/dict/answering_instructions.jinja +21 -0
- edsl/questions/templates/dict/question_presentation.jinja +1 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +550 -19
- edsl/results/DatasetExportMixin.py +594 -0
- edsl/results/DatasetTree.py +295 -0
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +477 -173
- edsl/results/Results.py +987 -269
- edsl/results/ResultsExportMixin.py +28 -125
- edsl/results/ResultsGGMixin.py +83 -15
- edsl/results/TableDisplay.py +125 -0
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/results_fetch_mixin.py +33 -0
- edsl/results/results_selector.py +145 -0
- edsl/results/results_tools_mixin.py +98 -0
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +78 -0
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +543 -0
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +431 -62
- edsl/scenarios/ScenarioHtmlMixin.py +65 -0
- edsl/scenarios/ScenarioList.py +1415 -45
- edsl/scenarios/ScenarioListExportMixin.py +45 -0
- edsl/scenarios/ScenarioListPdfMixin.py +239 -0
- edsl/scenarios/__init__.py +2 -0
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/scenario_join.py +131 -0
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +80 -0
- edsl/study/Study.py +521 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +92 -11
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +9 -4
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +156 -35
- edsl/surveys/Rule.py +221 -74
- edsl/surveys/RuleCollection.py +241 -61
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1079 -339
- edsl/surveys/SurveyCSS.py +273 -0
- edsl/surveys/SurveyExportMixin.py +235 -40
- edsl/surveys/SurveyFlowVisualization.py +181 -0
- edsl/surveys/SurveyQualtricsImport.py +284 -0
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/base.py +19 -3
- edsl/surveys/descriptors.py +17 -6
- edsl/surveys/instructions/ChangeInstruction.py +48 -0
- edsl/surveys/instructions/Instruction.py +56 -0
- edsl/surveys/instructions/InstructionCollection.py +82 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +116 -0
- edsl/templates/error_reporting/interviews.html +19 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- edsl/tools/__init__.py +1 -0
- edsl/tools/clusters.py +192 -0
- edsl/tools/embeddings.py +27 -0
- edsl/tools/embeddings_plotting.py +118 -0
- edsl/tools/plotting.py +112 -0
- edsl/tools/summarize.py +18 -0
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +5 -0
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/ast_utilities.py +3 -0
- edsl/utilities/data/Registry.py +2 -0
- edsl/utilities/decorators.py +41 -0
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/interface.py +310 -60
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/restricted_python.py +70 -0
- edsl/utilities/utilities.py +203 -13
- edsl-0.1.40.dist-info/METADATA +111 -0
- edsl-0.1.40.dist-info/RECORD +362 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
- edsl/agents/AgentListExportMixin.py +0 -24
- edsl/coop/old.py +0 -31
- edsl/data/Database.py +0 -141
- edsl/data/crud.py +0 -121
- edsl/jobs/Interview.py +0 -417
- edsl/jobs/JobsRunner.py +0 -63
- edsl/jobs/JobsRunnerStatusMixin.py +0 -115
- edsl/jobs/base.py +0 -47
- edsl/jobs/buckets.py +0 -166
- edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
- edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
- edsl/jobs/task_management.py +0 -218
- edsl/jobs/token_tracking.py +0 -78
- edsl/language_models/DeepInfra.py +0 -69
- edsl/language_models/OpenAI.py +0 -98
- edsl/language_models/model_interfaces/GeminiPro.py +0 -66
- edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
- edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
- edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
- edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
- edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
- edsl/language_models/registry.py +0 -81
- edsl/language_models/schemas.py +0 -15
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/prompts/QuestionInstructionsBase.py +0 -6
- edsl/prompts/library/agent_instructions.py +0 -29
- edsl/prompts/library/agent_persona.py +0 -17
- edsl/prompts/library/question_budget.py +0 -26
- edsl/prompts/library/question_checkbox.py +0 -32
- edsl/prompts/library/question_extract.py +0 -19
- edsl/prompts/library/question_freetext.py +0 -14
- edsl/prompts/library/question_linear_scale.py +0 -20
- edsl/prompts/library/question_list.py +0 -22
- edsl/prompts/library/question_multiple_choice.py +0 -44
- edsl/prompts/library/question_numerical.py +0 -31
- edsl/prompts/library/question_rank.py +0 -21
- edsl/prompts/prompt_config.py +0 -33
- edsl/prompts/registry.py +0 -185
- edsl/questions/Question.py +0 -240
- edsl/report/InputOutputDataTypes.py +0 -134
- edsl/report/RegressionMixin.py +0 -28
- edsl/report/ReportOutputs.py +0 -1228
- edsl/report/ResultsFetchMixin.py +0 -106
- edsl/report/ResultsOutputMixin.py +0 -14
- edsl/report/demo.ipynb +0 -645
- edsl/results/ResultsDBMixin.py +0 -184
- edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
- edsl/trackers/Tracker.py +0 -91
- edsl/trackers/TrackerAPI.py +0 -196
- edsl/trackers/TrackerTasks.py +0 -70
- edsl/utilities/pastebin.py +0 -141
- edsl-0.1.14.dist-info/METADATA +0 -69
- edsl-0.1.14.dist-info/RECORD +0 -141
- /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
- /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
- /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/jobs/Jobs.py
CHANGED
@@ -1,52 +1,240 @@
|
|
1
|
+
# """The Jobs class is a collection of agents, scenarios and models and one survey."""
|
1
2
|
from __future__ import annotations
|
2
|
-
|
3
|
-
from
|
4
|
-
from
|
5
|
-
|
3
|
+
import asyncio
|
4
|
+
from inspect import signature
|
5
|
+
from typing import (
|
6
|
+
Literal,
|
7
|
+
Optional,
|
8
|
+
Union,
|
9
|
+
Sequence,
|
10
|
+
Generator,
|
11
|
+
TYPE_CHECKING,
|
12
|
+
Callable,
|
13
|
+
Tuple,
|
14
|
+
)
|
6
15
|
|
7
|
-
from edsl import CONFIG
|
8
|
-
from edsl.agents import Agent
|
9
16
|
from edsl.Base import Base
|
10
|
-
from edsl.data import Database, database
|
11
|
-
from edsl.language_models import LanguageModel, LanguageModelOpenAIThreeFiveTurbo
|
12
|
-
from edsl.results import Results
|
13
|
-
from edsl.scenarios import Scenario
|
14
|
-
from edsl.surveys import Survey
|
15
|
-
from edsl.jobs.base import JobsRunnersRegistry, JobsRunnerDescriptor
|
16
|
-
from edsl.jobs.Interview import Interview
|
17
|
-
from edsl.coop.old import JobRunnerAPI, ResultsAPI
|
18
17
|
|
19
|
-
|
18
|
+
from edsl.jobs.buckets.BucketCollection import BucketCollection
|
19
|
+
from edsl.jobs.JobsPrompts import JobsPrompts
|
20
|
+
from edsl.jobs.interviews.Interview import Interview
|
21
|
+
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
22
|
+
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
23
|
+
from edsl.data.RemoteCacheSync import RemoteCacheSync
|
24
|
+
from edsl.exceptions.coop import CoopServerResponseError
|
25
|
+
|
26
|
+
from edsl.jobs.JobsChecks import JobsChecks
|
27
|
+
from edsl.jobs.data_structures import RunEnvironment, RunParameters, RunConfig
|
28
|
+
|
29
|
+
if TYPE_CHECKING:
|
30
|
+
from edsl.agents.Agent import Agent
|
31
|
+
from edsl.agents.AgentList import AgentList
|
32
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
33
|
+
from edsl.scenarios.Scenario import Scenario
|
34
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
35
|
+
from edsl.surveys.Survey import Survey
|
36
|
+
from edsl.results.Results import Results
|
37
|
+
from edsl.results.Dataset import Dataset
|
38
|
+
from edsl.language_models.ModelList import ModelList
|
39
|
+
from edsl.data.Cache import Cache
|
40
|
+
from edsl.language_models.key_management.KeyLookup import KeyLookup
|
41
|
+
|
42
|
+
VisibilityType = Literal["private", "public", "unlisted"]
|
43
|
+
|
44
|
+
from dataclasses import dataclass
|
45
|
+
from typing import Optional, Union, TypeVar, Callable, cast
|
46
|
+
from functools import wraps
|
47
|
+
|
48
|
+
try:
|
49
|
+
from typing import ParamSpec
|
50
|
+
except ImportError:
|
51
|
+
from typing_extensions import ParamSpec
|
52
|
+
|
53
|
+
|
54
|
+
P = ParamSpec("P")
|
55
|
+
T = TypeVar("T")
|
56
|
+
|
57
|
+
|
58
|
+
from edsl.jobs.check_survey_scenario_compatibility import (
|
59
|
+
CheckSurveyScenarioCompatibility,
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
def with_config(f: Callable[P, T]) -> Callable[P, T]:
|
64
|
+
"This decorator make it so that the run function parameters match the RunConfig dataclass."
|
65
|
+
parameter_fields = {
|
66
|
+
name: field.default
|
67
|
+
for name, field in RunParameters.__dataclass_fields__.items()
|
68
|
+
}
|
69
|
+
environment_fields = {
|
70
|
+
name: field.default
|
71
|
+
for name, field in RunEnvironment.__dataclass_fields__.items()
|
72
|
+
}
|
73
|
+
combined = {**parameter_fields, **environment_fields}
|
74
|
+
|
75
|
+
@wraps(f)
|
76
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
77
|
+
environment = RunEnvironment(
|
78
|
+
**{k: v for k, v in kwargs.items() if k in environment_fields}
|
79
|
+
)
|
80
|
+
parameters = RunParameters(
|
81
|
+
**{k: v for k, v in kwargs.items() if k in parameter_fields}
|
82
|
+
)
|
83
|
+
config = RunConfig(environment=environment, parameters=parameters)
|
84
|
+
return f(*args, config=config)
|
85
|
+
|
86
|
+
# Update the wrapper's signature to include all RunConfig parameters
|
87
|
+
# old_sig = signature(f)
|
88
|
+
# wrapper.__signature__ = old_sig.replace(
|
89
|
+
# parameters=list(old_sig.parameters.values())[:-1]
|
90
|
+
# + [
|
91
|
+
# old_sig.parameters["config"].replace(
|
92
|
+
# default=parameter_fields[name], name=name
|
93
|
+
# )
|
94
|
+
# for name in combined
|
95
|
+
# ]
|
96
|
+
# )
|
20
97
|
|
21
|
-
|
98
|
+
return cast(Callable[P, T], wrapper)
|
22
99
|
|
23
100
|
|
24
101
|
class Jobs(Base):
|
25
102
|
"""
|
26
|
-
|
27
|
-
|
28
|
-
Methods:
|
29
|
-
- `by()`: adds agents, scenarios or models to the job. Its a tricksy little method, be careful.
|
30
|
-
- `interviews()`: creates a collection of interviews
|
31
|
-
- `run()`: runs a collection of interviews
|
32
|
-
|
103
|
+
A collection of agents, scenarios and models and one survey that creates 'interviews'
|
33
104
|
"""
|
34
105
|
|
35
|
-
|
106
|
+
__documentation__ = "https://docs.expectedparrot.com/en/latest/jobs.html"
|
36
107
|
|
37
108
|
def __init__(
|
38
109
|
self,
|
39
|
-
survey: Survey,
|
40
|
-
agents: list[Agent] = None,
|
41
|
-
models: list[LanguageModel] = None,
|
42
|
-
scenarios: list[Scenario] = None,
|
110
|
+
survey: "Survey",
|
111
|
+
agents: Optional[Union[list[Agent], AgentList]] = None,
|
112
|
+
models: Optional[Union[ModelList, list[LanguageModel]]] = None,
|
113
|
+
scenarios: Optional[Union[ScenarioList, list[Scenario]]] = None,
|
43
114
|
):
|
115
|
+
"""Initialize a Jobs instance.
|
116
|
+
|
117
|
+
:param survey: the survey to be used in the job
|
118
|
+
:param agents: a list of agents
|
119
|
+
:param models: a list of models
|
120
|
+
:param scenarios: a list of scenarios
|
121
|
+
"""
|
122
|
+
self.run_config = RunConfig(
|
123
|
+
environment=RunEnvironment(), parameters=RunParameters()
|
124
|
+
)
|
125
|
+
|
44
126
|
self.survey = survey
|
45
|
-
self.agents = agents
|
46
|
-
self.
|
47
|
-
self.
|
127
|
+
self.agents: AgentList = agents
|
128
|
+
self.scenarios: ScenarioList = scenarios
|
129
|
+
self.models: ModelList = models
|
130
|
+
|
131
|
+
def add_running_env(self, running_env: RunEnvironment):
|
132
|
+
self.run_config.add_environment(running_env)
|
133
|
+
return self
|
134
|
+
|
135
|
+
def using_cache(self, cache: "Cache") -> Jobs:
|
136
|
+
"""
|
137
|
+
Add a Cache to the job.
|
138
|
+
|
139
|
+
:param cache: the cache to add
|
140
|
+
"""
|
141
|
+
self.run_config.add_cache(cache)
|
142
|
+
return self
|
143
|
+
|
144
|
+
def using_bucket_collection(self, bucket_collection: BucketCollection) -> Jobs:
|
145
|
+
"""
|
146
|
+
Add a BucketCollection to the job.
|
147
|
+
|
148
|
+
:param bucket_collection: the bucket collection to add
|
149
|
+
"""
|
150
|
+
self.run_config.add_bucket_collection(bucket_collection)
|
151
|
+
return self
|
152
|
+
|
153
|
+
def using_key_lookup(self, key_lookup: KeyLookup) -> Jobs:
|
154
|
+
"""
|
155
|
+
Add a KeyLookup to the job.
|
156
|
+
|
157
|
+
:param key_lookup: the key lookup to add
|
158
|
+
"""
|
159
|
+
self.run_config.add_key_lookup(key_lookup)
|
160
|
+
return self
|
161
|
+
|
162
|
+
def using(self, obj: Union[Cache, BucketCollection, KeyLookup]) -> Jobs:
|
163
|
+
"""
|
164
|
+
Add a Cache, BucketCollection, or KeyLookup to the job.
|
165
|
+
|
166
|
+
:param obj: the object to add
|
167
|
+
"""
|
168
|
+
from edsl.data.Cache import Cache
|
169
|
+
from edsl.language_models.key_management.KeyLookup import KeyLookup
|
170
|
+
|
171
|
+
if isinstance(obj, Cache):
|
172
|
+
self.using_cache(obj)
|
173
|
+
elif isinstance(obj, BucketCollection):
|
174
|
+
self.using_bucket_collection(obj)
|
175
|
+
elif isinstance(obj, KeyLookup):
|
176
|
+
self.using_key_lookup(obj)
|
177
|
+
return self
|
178
|
+
|
179
|
+
@property
|
180
|
+
def models(self):
|
181
|
+
return self._models
|
182
|
+
|
183
|
+
@models.setter
|
184
|
+
def models(self, value):
|
185
|
+
from edsl.language_models.ModelList import ModelList
|
186
|
+
|
187
|
+
if value:
|
188
|
+
if not isinstance(value, ModelList):
|
189
|
+
self._models = ModelList(value)
|
190
|
+
else:
|
191
|
+
self._models = value
|
192
|
+
else:
|
193
|
+
self._models = ModelList([])
|
194
|
+
|
195
|
+
# update the bucket collection if it exists
|
196
|
+
if self.run_config.environment.bucket_collection is None:
|
197
|
+
self.run_config.environment.bucket_collection = (
|
198
|
+
self.create_bucket_collection()
|
199
|
+
)
|
48
200
|
|
49
|
-
|
201
|
+
@property
|
202
|
+
def agents(self):
|
203
|
+
return self._agents
|
204
|
+
|
205
|
+
@agents.setter
|
206
|
+
def agents(self, value):
|
207
|
+
from edsl.agents.AgentList import AgentList
|
208
|
+
|
209
|
+
if value:
|
210
|
+
if not isinstance(value, AgentList):
|
211
|
+
self._agents = AgentList(value)
|
212
|
+
else:
|
213
|
+
self._agents = value
|
214
|
+
else:
|
215
|
+
self._agents = AgentList([])
|
216
|
+
|
217
|
+
@property
|
218
|
+
def scenarios(self):
|
219
|
+
return self._scenarios
|
220
|
+
|
221
|
+
@scenarios.setter
|
222
|
+
def scenarios(self, value):
|
223
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
224
|
+
from edsl.results.Dataset import Dataset
|
225
|
+
|
226
|
+
if value:
|
227
|
+
if isinstance(
|
228
|
+
value, Dataset
|
229
|
+
): # if the user passes in a Dataset, convert it to a ScenarioList
|
230
|
+
value = value.to_scenario_list()
|
231
|
+
|
232
|
+
if not isinstance(value, ScenarioList):
|
233
|
+
self._scenarios = ScenarioList(value)
|
234
|
+
else:
|
235
|
+
self._scenarios = value
|
236
|
+
else:
|
237
|
+
self._scenarios = ScenarioList([])
|
50
238
|
|
51
239
|
def by(
|
52
240
|
self,
|
@@ -54,14 +242,29 @@ class Jobs(Base):
|
|
54
242
|
Agent,
|
55
243
|
Scenario,
|
56
244
|
LanguageModel,
|
57
|
-
Sequence[Union[Agent, Scenario, LanguageModel]],
|
245
|
+
Sequence[Union["Agent", "Scenario", "LanguageModel"]],
|
58
246
|
],
|
59
247
|
) -> Jobs:
|
60
248
|
"""
|
61
|
-
|
249
|
+
Add Agents, Scenarios and LanguageModels to a job.
|
250
|
+
|
251
|
+
:param args: objects or a sequence (list, tuple, ...) of objects of the same type
|
252
|
+
|
253
|
+
If no objects of this type exist in the Jobs instance, it stores the new objects as a list in the corresponding attribute.
|
254
|
+
Otherwise, it combines the new objects with existing objects using the object's `__add__` method.
|
255
|
+
|
256
|
+
This 'by' is intended to create a fluent interface.
|
257
|
+
|
258
|
+
>>> from edsl.surveys.Survey import Survey
|
259
|
+
>>> from edsl.questions.QuestionFreeText import QuestionFreeText
|
260
|
+
>>> q = QuestionFreeText(question_name="name", question_text="What is your name?")
|
261
|
+
>>> j = Jobs(survey = Survey(questions=[q]))
|
262
|
+
>>> j
|
263
|
+
Jobs(survey=Survey(...), agents=AgentList([]), models=ModelList([]), scenarios=ScenarioList([]))
|
264
|
+
>>> from edsl.agents.Agent import Agent; a = Agent(traits = {"status": "Sad"})
|
265
|
+
>>> j.by(a).agents
|
266
|
+
AgentList([Agent(traits = {'status': 'Sad'})])
|
62
267
|
|
63
|
-
Arguments:
|
64
|
-
- objects or a sequence (list, tuple, ...) of objects of the same type
|
65
268
|
|
66
269
|
Notes:
|
67
270
|
- all objects must implement the 'get_value', 'set_value', and `__add__` methods
|
@@ -69,187 +272,432 @@ class Jobs(Base):
|
|
69
272
|
- scenarios: traits of new scenarios are combined with traits of old existing. New scenarios will overwrite overlapping traits, and do not increase the number of scenarios in the instance
|
70
273
|
- models: new models overwrite old models.
|
71
274
|
"""
|
72
|
-
|
275
|
+
from edsl.jobs.JobsComponentConstructor import JobsComponentConstructor
|
73
276
|
|
74
|
-
|
277
|
+
return JobsComponentConstructor(self).by(*args)
|
75
278
|
|
76
|
-
|
77
|
-
|
78
|
-
)
|
279
|
+
def prompts(self) -> "Dataset":
|
280
|
+
"""Return a Dataset of prompts that will be used.
|
79
281
|
|
80
|
-
if not current_objects:
|
81
|
-
new_objects = passed_objects
|
82
|
-
else:
|
83
|
-
new_objects = self._merge_objects(passed_objects, current_objects)
|
84
282
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def did_user_pass_a_sequence(args):
|
91
|
-
"""
|
92
|
-
>>> did_user_pass_a_sequence([1,2,3])
|
93
|
-
True
|
94
|
-
>>> did_user_pass_a_sequence(1)
|
95
|
-
False
|
96
|
-
"""
|
97
|
-
return len(args) == 1 and isinstance(args[0], Sequence)
|
98
|
-
|
99
|
-
if did_user_pass_a_sequence(args):
|
100
|
-
return list(args[0])
|
101
|
-
else:
|
102
|
-
return list(args)
|
283
|
+
>>> from edsl.jobs import Jobs
|
284
|
+
>>> Jobs.example().prompts()
|
285
|
+
Dataset(...)
|
286
|
+
"""
|
287
|
+
return JobsPrompts(self).prompts()
|
103
288
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
LanguageModel: "models",
|
109
|
-
}
|
110
|
-
for class_type in class_to_key:
|
111
|
-
if isinstance(object, class_type) or issubclass(
|
112
|
-
object.__class__, class_type
|
113
|
-
):
|
114
|
-
key = class_to_key[class_type]
|
115
|
-
break
|
289
|
+
def show_prompts(self, all: bool = False) -> None:
|
290
|
+
"""Print the prompts."""
|
291
|
+
if all:
|
292
|
+
return self.prompts().to_scenario_list().table()
|
116
293
|
else:
|
117
|
-
|
118
|
-
|
294
|
+
return (
|
295
|
+
self.prompts().to_scenario_list().table("user_prompt", "system_prompt")
|
119
296
|
)
|
120
|
-
current_objects = getattr(self, key, None)
|
121
|
-
return current_objects, key
|
122
297
|
|
123
298
|
@staticmethod
|
124
|
-
def
|
299
|
+
def estimate_prompt_cost(
|
300
|
+
system_prompt: str,
|
301
|
+
user_prompt: str,
|
302
|
+
price_lookup: dict,
|
303
|
+
inference_service: str,
|
304
|
+
model: str,
|
305
|
+
) -> dict:
|
306
|
+
"""
|
307
|
+
Estimate the cost of running the prompts.
|
308
|
+
:param iterations: the number of iterations to run
|
309
|
+
:param system_prompt: the system prompt
|
310
|
+
:param user_prompt: the user prompt
|
311
|
+
:param price_lookup: the price lookup
|
312
|
+
:param inference_service: the inference service
|
313
|
+
:param model: the model name
|
314
|
+
"""
|
315
|
+
return JobsPrompts.estimate_prompt_cost(
|
316
|
+
system_prompt, user_prompt, price_lookup, inference_service, model
|
317
|
+
)
|
318
|
+
|
319
|
+
def estimate_job_cost(self, iterations: int = 1) -> dict:
|
125
320
|
"""
|
126
|
-
|
127
|
-
For example, if the user passes in 3 agents,
|
128
|
-
and there are 2 existing agents, this will create 6 new agents
|
321
|
+
Estimate the cost of running the job.
|
129
322
|
|
130
|
-
|
131
|
-
[5, 6, 7, 6, 7, 8, 7, 8, 9]
|
323
|
+
:param iterations: the number of iterations to run
|
132
324
|
"""
|
133
|
-
|
134
|
-
for current_object in current_objects:
|
135
|
-
for new_object in passed_objects:
|
136
|
-
new_objects.append(current_object + new_object)
|
137
|
-
return new_objects
|
325
|
+
return JobsPrompts(self).estimate_job_cost(iterations)
|
138
326
|
|
139
|
-
def
|
327
|
+
def estimate_job_cost_from_external_prices(
|
328
|
+
self, price_lookup: dict, iterations: int = 1
|
329
|
+
) -> dict:
|
330
|
+
return JobsPrompts(self).estimate_job_cost_from_external_prices(
|
331
|
+
price_lookup, iterations
|
332
|
+
)
|
333
|
+
|
334
|
+
@staticmethod
|
335
|
+
def compute_job_cost(job_results: Results) -> float:
|
140
336
|
"""
|
141
|
-
|
142
|
-
- Returns one Interview for each combination of Agent, Scenario, and LanguageModel.
|
143
|
-
- If any of Agents, Scenarios, or LanguageModels are missing, fills in with defaults. Note that this will change the corresponding class attributes.
|
337
|
+
Computes the cost of a completed job in USD.
|
144
338
|
"""
|
339
|
+
return job_results.compute_job_cost()
|
340
|
+
|
341
|
+
def replace_missing_objects(self) -> None:
|
342
|
+
from edsl.agents.Agent import Agent
|
343
|
+
from edsl.language_models.model import Model
|
344
|
+
from edsl.scenarios.Scenario import Scenario
|
345
|
+
|
145
346
|
self.agents = self.agents or [Agent()]
|
146
|
-
self.models = self.models or [
|
347
|
+
self.models = self.models or [Model()]
|
147
348
|
self.scenarios = self.scenarios or [Scenario()]
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
349
|
+
|
350
|
+
def generate_interviews(self) -> Generator[Interview, None, None]:
|
351
|
+
"""
|
352
|
+
Generate interviews.
|
353
|
+
|
354
|
+
Note that this sets the agents, model and scenarios if they have not been set. This is a side effect of the method.
|
355
|
+
This is useful because a user can create a job without setting the agents, models, or scenarios, and the job will still run,
|
356
|
+
with us filling in defaults.
|
357
|
+
|
358
|
+
"""
|
359
|
+
from edsl.jobs.InterviewsConstructor import InterviewsConstructor
|
360
|
+
|
361
|
+
self.replace_missing_objects()
|
362
|
+
yield from InterviewsConstructor(
|
363
|
+
self, cache=self.run_config.environment.cache
|
364
|
+
).create_interviews()
|
365
|
+
|
366
|
+
def interviews(self) -> list[Interview]:
|
367
|
+
"""
|
368
|
+
Return a list of :class:`edsl.jobs.interviews.Interview` objects.
|
369
|
+
|
370
|
+
It returns one Interview for each combination of Agent, Scenario, and LanguageModel.
|
371
|
+
If any of Agents, Scenarios, or LanguageModels are missing, it fills in with defaults.
|
372
|
+
|
373
|
+
>>> from edsl.jobs import Jobs
|
374
|
+
>>> j = Jobs.example()
|
375
|
+
>>> len(j.interviews())
|
376
|
+
4
|
377
|
+
>>> j.interviews()[0]
|
378
|
+
Interview(agent = Agent(traits = {'status': 'Joyful'}), survey = Survey(...), scenario = Scenario({'period': 'morning'}), model = Model(...))
|
379
|
+
"""
|
380
|
+
return list(self.generate_interviews())
|
381
|
+
|
382
|
+
@classmethod
|
383
|
+
def from_interviews(cls, interview_list) -> "Jobs":
|
384
|
+
"""Return a Jobs instance from a list of interviews.
|
385
|
+
|
386
|
+
This is useful when you have, say, a list of failed interviews and you want to create
|
387
|
+
a new job with only those interviews.
|
388
|
+
"""
|
389
|
+
survey = interview_list[0].survey
|
390
|
+
# get all the models
|
391
|
+
models = list(set([interview.model for interview in interview_list]))
|
392
|
+
jobs = cls(survey)
|
393
|
+
jobs.models = models
|
394
|
+
jobs._interviews = interview_list
|
395
|
+
return jobs
|
396
|
+
|
397
|
+
def create_bucket_collection(self) -> BucketCollection:
|
398
|
+
"""
|
399
|
+
Create a collection of buckets for each model.
|
400
|
+
|
401
|
+
These buckets are used to track API calls and token usage.
|
402
|
+
|
403
|
+
>>> from edsl.jobs import Jobs
|
404
|
+
>>> from edsl import Model
|
405
|
+
>>> j = Jobs.example().by(Model(temperature = 1), Model(temperature = 0.5))
|
406
|
+
>>> bc = j.create_bucket_collection()
|
407
|
+
>>> bc
|
408
|
+
BucketCollection(...)
|
409
|
+
"""
|
410
|
+
return BucketCollection.from_models(self.models)
|
411
|
+
|
412
|
+
def html(self):
|
413
|
+
"""Return the HTML representations for each scenario"""
|
414
|
+
links = []
|
415
|
+
for index, scenario in enumerate(self.scenarios):
|
416
|
+
links.append(
|
417
|
+
self.survey.html(
|
418
|
+
scenario=scenario, return_link=True, cta=f"Scenario {index}"
|
419
|
+
)
|
152
420
|
)
|
153
|
-
|
154
|
-
|
421
|
+
return links
|
422
|
+
|
423
|
+
def __hash__(self):
|
424
|
+
"""Allow the model to be used as a key in a dictionary.
|
425
|
+
|
426
|
+
>>> from edsl.jobs import Jobs
|
427
|
+
>>> hash(Jobs.example())
|
428
|
+
846655441787442972
|
155
429
|
|
156
|
-
def create_bucket_collection(self):
|
157
430
|
"""
|
158
|
-
|
431
|
+
from edsl.utilities.utilities import dict_hash
|
432
|
+
|
433
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
434
|
+
|
435
|
+
def _output(self, message) -> None:
|
436
|
+
"""Check if a Job is verbose. If so, print the message."""
|
437
|
+
if self.run_config.parameters.verbose:
|
438
|
+
print(message)
|
439
|
+
# if hasattr(self, "verbose") and self.verbose:
|
440
|
+
# print(message)
|
441
|
+
|
442
|
+
def all_question_parameters(self) -> set:
|
443
|
+
"""Return all the fields in the questions in the survey.
|
444
|
+
>>> from edsl.jobs import Jobs
|
445
|
+
>>> Jobs.example().all_question_parameters()
|
446
|
+
{'period'}
|
159
447
|
"""
|
160
|
-
|
161
|
-
for model in self.models:
|
162
|
-
bucket_collection.add_model(model)
|
163
|
-
return bucket_collection
|
448
|
+
return set.union(*[question.parameters for question in self.survey.questions])
|
164
449
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
450
|
+
def use_remote_cache(self) -> bool:
|
451
|
+
import requests
|
452
|
+
|
453
|
+
if self.run_config.parameters.disable_remote_cache:
|
454
|
+
return False
|
455
|
+
if not self.run_config.parameters.disable_remote_cache:
|
456
|
+
try:
|
457
|
+
from edsl.coop.coop import Coop
|
458
|
+
|
459
|
+
user_edsl_settings = Coop().edsl_settings
|
460
|
+
return user_edsl_settings.get("remote_caching", False)
|
461
|
+
except requests.ConnectionError:
|
462
|
+
pass
|
463
|
+
except CoopServerResponseError as e:
|
464
|
+
pass
|
170
465
|
|
171
|
-
|
466
|
+
return False
|
467
|
+
|
468
|
+
def _remote_results(
|
172
469
|
self,
|
173
|
-
|
174
|
-
|
175
|
-
verbose: bool = False,
|
176
|
-
progress_bar: bool = False,
|
177
|
-
dry_run: bool = False,
|
178
|
-
streaming: bool = False,
|
179
|
-
db: Database = database,
|
180
|
-
) -> Union[Results, ResultsAPI, None]:
|
181
|
-
"""
|
182
|
-
Runs the Job: conducts Interviews and returns their results.
|
183
|
-
- `method`: "serial" or "threaded", defaults to "serial"
|
184
|
-
- `n`: how many times to run each interview
|
185
|
-
- `debug`: prints debug messages
|
186
|
-
- `verbose`: prints messages
|
187
|
-
- `progress_bar`: shows a progress bar
|
188
|
-
"""
|
189
|
-
# self.job_runner_name = method
|
190
|
-
if dry_run:
|
191
|
-
self.job_runner_name = "dry_run"
|
192
|
-
elif streaming:
|
193
|
-
self.job_runner_name = "streaming"
|
194
|
-
else:
|
195
|
-
self.job_runner_name = "asyncio"
|
470
|
+
) -> Union["Results", None]:
|
471
|
+
from edsl.jobs.JobsRemoteInferenceHandler import JobsRemoteInferenceHandler
|
196
472
|
|
197
|
-
|
198
|
-
|
199
|
-
|
473
|
+
jh = JobsRemoteInferenceHandler(
|
474
|
+
self, verbose=self.run_config.parameters.verbose
|
475
|
+
)
|
476
|
+
if jh.use_remote_inference(self.run_config.parameters.disable_remote_inference):
|
477
|
+
job_info = jh.create_remote_inference_job(
|
478
|
+
iterations=self.run_config.parameters.n,
|
479
|
+
remote_inference_description=self.run_config.parameters.remote_inference_description,
|
480
|
+
remote_inference_results_visibility=self.run_config.parameters.remote_inference_results_visibility,
|
200
481
|
)
|
482
|
+
results = jh.poll_remote_inference_job(job_info)
|
483
|
+
return results
|
484
|
+
else:
|
485
|
+
return None
|
486
|
+
|
487
|
+
def _prepare_to_run(self) -> None:
|
488
|
+
"This makes sure that the job is ready to run and that keys are in place for a remote job."
|
489
|
+
CheckSurveyScenarioCompatibility(self.survey, self.scenarios).check()
|
490
|
+
|
491
|
+
def _check_if_remote_keys_ok(self):
|
492
|
+
jc = JobsChecks(self)
|
493
|
+
if jc.needs_key_process():
|
494
|
+
jc.key_process()
|
495
|
+
|
496
|
+
def _check_if_local_keys_ok(self):
|
497
|
+
jc = JobsChecks(self)
|
498
|
+
if self.run_config.parameters.check_api_keys:
|
499
|
+
jc.check_api_keys()
|
500
|
+
|
501
|
+
async def _execute_with_remote_cache(self, run_job_async: bool) -> Results:
|
502
|
+
use_remote_cache = self.use_remote_cache()
|
503
|
+
|
504
|
+
from edsl.coop.coop import Coop
|
505
|
+
from edsl.jobs.runners.JobsRunnerAsyncio import JobsRunnerAsyncio
|
506
|
+
from edsl.data.Cache import Cache
|
507
|
+
|
508
|
+
assert isinstance(self.run_config.environment.cache, Cache)
|
509
|
+
|
510
|
+
# with RemoteCacheSync(
|
511
|
+
# coop=Coop(),
|
512
|
+
# cache=self.run_config.environment.cache,
|
513
|
+
# output_func=self._output,
|
514
|
+
# remote_cache=use_remote_cache,
|
515
|
+
# remote_cache_description=self.run_config.parameters.remote_cache_description,
|
516
|
+
# ):
|
517
|
+
runner = JobsRunnerAsyncio(self, environment=self.run_config.environment)
|
518
|
+
if run_job_async:
|
519
|
+
results = await runner.run_async(self.run_config.parameters)
|
520
|
+
else:
|
521
|
+
results = runner.run(self.run_config.parameters)
|
522
|
+
return results
|
523
|
+
|
524
|
+
def _setup_and_check(self) -> Tuple[RunConfig, Optional[Results]]:
|
525
|
+
self._prepare_to_run()
|
526
|
+
self._check_if_remote_keys_ok()
|
527
|
+
|
528
|
+
# first try to run the job remotely
|
529
|
+
if results := self._remote_results():
|
530
|
+
return results
|
531
|
+
|
532
|
+
self._check_if_local_keys_ok()
|
533
|
+
return None
|
534
|
+
|
535
|
+
@property
|
536
|
+
def num_interviews(self):
|
537
|
+
if self.run_config.parameters.n is None:
|
538
|
+
return len(self)
|
201
539
|
else:
|
202
|
-
|
203
|
-
|
540
|
+
return len(self) * self.run_config.parameters.n
|
541
|
+
|
542
|
+
def _run(self, config: RunConfig):
|
543
|
+
"Shared code for run and run_async"
|
544
|
+
if config.environment.cache is not None:
|
545
|
+
self.run_config.environment.cache = config.environment.cache
|
546
|
+
if config.environment.jobs_runner_status is not None:
|
547
|
+
self.run_config.environment.jobs_runner_status = (
|
548
|
+
config.environment.jobs_runner_status
|
204
549
|
)
|
205
550
|
|
206
|
-
|
551
|
+
if config.environment.bucket_collection is not None:
|
552
|
+
self.run_config.environment.bucket_collection = (
|
553
|
+
config.environment.bucket_collection
|
554
|
+
)
|
207
555
|
|
208
|
-
|
209
|
-
|
210
|
-
db._health_check_pre_run()
|
211
|
-
JobRunner = JobsRunnersRegistry[self.job_runner_name](jobs=self)
|
212
|
-
results = JobRunner.run(*args, **kwargs)
|
213
|
-
db._health_check_post_run()
|
214
|
-
return results
|
556
|
+
if config.environment.key_lookup is not None:
|
557
|
+
self.run_config.environment.key_lookup = config.environment.key_lookup
|
215
558
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
559
|
+
# replace the parameters with the ones from the config
|
560
|
+
self.run_config.parameters = config.parameters
|
561
|
+
|
562
|
+
self.replace_missing_objects()
|
563
|
+
|
564
|
+
# try to run remotely first
|
565
|
+
self._prepare_to_run()
|
566
|
+
self._check_if_remote_keys_ok()
|
567
|
+
|
568
|
+
if (
|
569
|
+
self.run_config.environment.cache is None
|
570
|
+
or self.run_config.environment.cache is True
|
571
|
+
):
|
572
|
+
from edsl.data.CacheHandler import CacheHandler
|
573
|
+
|
574
|
+
self.run_config.environment.cache = CacheHandler().get_cache()
|
575
|
+
|
576
|
+
if self.run_config.environment.cache is False:
|
577
|
+
from edsl.data.Cache import Cache
|
578
|
+
|
579
|
+
self.run_config.environment.cache = Cache(immediate_write=False)
|
580
|
+
|
581
|
+
# first try to run the job remotely
|
582
|
+
if results := self._remote_results():
|
583
|
+
return results
|
584
|
+
|
585
|
+
self._check_if_local_keys_ok()
|
586
|
+
|
587
|
+
if config.environment.bucket_collection is None:
|
588
|
+
self.run_config.environment.bucket_collection = (
|
589
|
+
self.create_bucket_collection()
|
590
|
+
)
|
591
|
+
|
592
|
+
@with_config
|
593
|
+
def run(self, *, config: RunConfig) -> "Results":
|
594
|
+
"""
|
595
|
+
Runs the Job: conducts Interviews and returns their results.
|
596
|
+
|
597
|
+
:param n: How many times to run each interview
|
598
|
+
:param progress_bar: Whether to show a progress bar
|
599
|
+
:param stop_on_exception: Stops the job if an exception is raised
|
600
|
+
:param check_api_keys: Raises an error if API keys are invalid
|
601
|
+
:param verbose: Prints extra messages
|
602
|
+
:param remote_cache_description: Specifies a description for this group of entries in the remote cache
|
603
|
+
:param remote_inference_description: Specifies a description for the remote inference job
|
604
|
+
:param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
|
605
|
+
:param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
|
606
|
+
:param disable_remote_inference: If True, the job will not use remote inference
|
607
|
+
:param cache: A Cache object to store results
|
608
|
+
:param bucket_collection: A BucketCollection object to track API calls
|
609
|
+
:param key_lookup: A KeyLookup object to manage API keys
|
610
|
+
"""
|
611
|
+
self._run(config)
|
612
|
+
|
613
|
+
return asyncio.run(self._execute_with_remote_cache(run_job_async=False))
|
614
|
+
|
615
|
+
@with_config
|
616
|
+
async def run_async(self, *, config: RunConfig) -> "Results":
|
617
|
+
"""
|
618
|
+
Runs the Job: conducts Interviews and returns their results.
|
619
|
+
|
620
|
+
:param n: How many times to run each interview
|
621
|
+
:param progress_bar: Whether to show a progress bar
|
622
|
+
:param stop_on_exception: Stops the job if an exception is raised
|
623
|
+
:param check_api_keys: Raises an error if API keys are invalid
|
624
|
+
:param verbose: Prints extra messages
|
625
|
+
:param remote_cache_description: Specifies a description for this group of entries in the remote cache
|
626
|
+
:param remote_inference_description: Specifies a description for the remote inference job
|
627
|
+
:param remote_inference_results_visibility: The initial visibility of the Results object on Coop. This will only be used for remote jobs!
|
628
|
+
:param disable_remote_cache: If True, the job will not use remote cache. This only works for local jobs!
|
629
|
+
:param disable_remote_inference: If True, the job will not use remote inference
|
630
|
+
:param cache: A Cache object to store results
|
631
|
+
:param bucket_collection: A BucketCollection object to track API calls
|
632
|
+
:param key_lookup: A KeyLookup object to manage API keys
|
633
|
+
"""
|
634
|
+
self._run(config)
|
635
|
+
|
636
|
+
return await self._execute_with_remote_cache(run_job_async=True)
|
220
637
|
|
221
|
-
#######################
|
222
|
-
# Dunder methods
|
223
|
-
#######################
|
224
638
|
def __repr__(self) -> str:
|
225
|
-
"""
|
639
|
+
"""Return an eval-able string representation of the Jobs instance."""
|
226
640
|
return f"Jobs(survey={repr(self.survey)}, agents={repr(self.agents)}, models={repr(self.models)}, scenarios={repr(self.scenarios)})"
|
227
641
|
|
642
|
+
def _summary(self):
|
643
|
+
return {
|
644
|
+
"questions": len(self.survey),
|
645
|
+
"agents": len(self.agents or [1]),
|
646
|
+
"models": len(self.models or [1]),
|
647
|
+
"scenarios": len(self.scenarios or [1]),
|
648
|
+
}
|
649
|
+
|
228
650
|
def __len__(self) -> int:
|
229
|
-
"""
|
230
|
-
|
651
|
+
"""Return the number of interviews that will be conducted for one iteration of this job.
|
652
|
+
An interview is the result of one survey, taken by one agent, with one model, with one scenario.
|
653
|
+
|
654
|
+
>>> from edsl.jobs import Jobs
|
655
|
+
>>> len(Jobs.example())
|
656
|
+
4
|
657
|
+
"""
|
658
|
+
number_of_interviews = (
|
231
659
|
len(self.agents or [1])
|
232
660
|
* len(self.scenarios or [1])
|
233
661
|
* len(self.models or [1])
|
234
|
-
* len(self.survey)
|
235
662
|
)
|
236
|
-
return
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
"
|
246
|
-
|
247
|
-
|
663
|
+
return number_of_interviews
|
664
|
+
|
665
|
+
def to_dict(self, add_edsl_version=True):
|
666
|
+
d = {
|
667
|
+
"survey": self.survey.to_dict(add_edsl_version=add_edsl_version),
|
668
|
+
"agents": [
|
669
|
+
agent.to_dict(add_edsl_version=add_edsl_version)
|
670
|
+
for agent in self.agents
|
671
|
+
],
|
672
|
+
"models": [
|
673
|
+
model.to_dict(add_edsl_version=add_edsl_version)
|
674
|
+
for model in self.models
|
675
|
+
],
|
676
|
+
"scenarios": [
|
677
|
+
scenario.to_dict(add_edsl_version=add_edsl_version)
|
678
|
+
for scenario in self.scenarios
|
679
|
+
],
|
248
680
|
}
|
681
|
+
if add_edsl_version:
|
682
|
+
from edsl import __version__
|
683
|
+
|
684
|
+
d["edsl_version"] = __version__
|
685
|
+
d["edsl_class_name"] = "Jobs"
|
686
|
+
|
687
|
+
return d
|
688
|
+
|
689
|
+
def table(self):
|
690
|
+
return self.prompts().to_scenario_list().table()
|
249
691
|
|
250
692
|
@classmethod
|
693
|
+
@remove_edsl_version
|
251
694
|
def from_dict(cls, data: dict) -> Jobs:
|
252
|
-
"""Creates a Jobs instance from a
|
695
|
+
"""Creates a Jobs instance from a dictionary."""
|
696
|
+
from edsl.surveys.Survey import Survey
|
697
|
+
from edsl.agents.Agent import Agent
|
698
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
699
|
+
from edsl.scenarios.Scenario import Scenario
|
700
|
+
|
253
701
|
return cls(
|
254
702
|
survey=Survey.from_dict(data["survey"]),
|
255
703
|
agents=[Agent.from_dict(agent) for agent in data["agents"]],
|
@@ -257,14 +705,45 @@ class Jobs(Base):
|
|
257
705
|
scenarios=[Scenario.from_dict(scenario) for scenario in data["scenarios"]],
|
258
706
|
)
|
259
707
|
|
260
|
-
|
261
|
-
|
262
|
-
|
708
|
+
def __eq__(self, other: Jobs) -> bool:
|
709
|
+
"""Return True if the Jobs instance is equal to another Jobs instance.
|
710
|
+
|
711
|
+
>>> from edsl.jobs import Jobs
|
712
|
+
>>> Jobs.example() == Jobs.example()
|
713
|
+
True
|
714
|
+
|
715
|
+
"""
|
716
|
+
return hash(self) == hash(other)
|
717
|
+
|
263
718
|
@classmethod
|
264
|
-
def example(
|
265
|
-
|
719
|
+
def example(
|
720
|
+
cls,
|
721
|
+
throw_exception_probability: float = 0.0,
|
722
|
+
randomize: bool = False,
|
723
|
+
test_model=False,
|
724
|
+
) -> Jobs:
|
725
|
+
"""Return an example Jobs instance.
|
726
|
+
|
727
|
+
:param throw_exception_probability: the probability that an exception will be thrown when answering a question. This is useful for testing error handling.
|
728
|
+
:param randomize: whether to randomize the job by adding a random string to the period
|
729
|
+
:param test_model: whether to use a test model
|
730
|
+
|
731
|
+
>>> Jobs.example()
|
732
|
+
Jobs(...)
|
266
733
|
|
267
|
-
|
734
|
+
"""
|
735
|
+
import random
|
736
|
+
from uuid import uuid4
|
737
|
+
from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
|
738
|
+
from edsl.agents.Agent import Agent
|
739
|
+
from edsl.scenarios.Scenario import Scenario
|
740
|
+
|
741
|
+
addition = "" if not randomize else str(uuid4())
|
742
|
+
|
743
|
+
if test_model:
|
744
|
+
from edsl.language_models.LanguageModel import LanguageModel
|
745
|
+
|
746
|
+
m = LanguageModel.example(test_model=True)
|
268
747
|
|
269
748
|
# (status, question, period)
|
270
749
|
agent_answers = {
|
@@ -279,6 +758,10 @@ class Jobs(Base):
|
|
279
758
|
}
|
280
759
|
|
281
760
|
def answer_question_directly(self, question, scenario):
|
761
|
+
"""Return the answer to a question. This is a method that can be added to an agent."""
|
762
|
+
|
763
|
+
if random.random() < throw_exception_probability:
|
764
|
+
raise Exception("Error!")
|
282
765
|
return agent_answers[
|
283
766
|
(self.traits["status"], question.question_name, scenario["period"])
|
284
767
|
]
|
@@ -299,46 +782,43 @@ class Jobs(Base):
|
|
299
782
|
question_options=["Good", "Great", "OK", "Terrible"],
|
300
783
|
question_name="how_feeling_yesterday",
|
301
784
|
)
|
785
|
+
from edsl.surveys.Survey import Survey
|
786
|
+
from edsl.scenarios.ScenarioList import ScenarioList
|
787
|
+
|
302
788
|
base_survey = Survey(questions=[q1, q2])
|
303
789
|
|
304
|
-
|
305
|
-
|
306
|
-
|
790
|
+
scenario_list = ScenarioList(
|
791
|
+
[
|
792
|
+
Scenario({"period": f"morning{addition}"}),
|
793
|
+
Scenario({"period": "afternoon"}),
|
794
|
+
]
|
795
|
+
)
|
796
|
+
if test_model:
|
797
|
+
job = base_survey.by(m).by(scenario_list).by(joy_agent, sad_agent)
|
798
|
+
else:
|
799
|
+
job = base_survey.by(scenario_list).by(joy_agent, sad_agent)
|
307
800
|
|
308
801
|
return job
|
309
802
|
|
310
|
-
def rich_print(self):
|
311
|
-
"""Prints a rich representation of the Jobs instance."""
|
312
|
-
from rich.table import Table
|
313
|
-
|
314
|
-
table = Table(title="Jobs")
|
315
|
-
table.add_column("Jobs")
|
316
|
-
table.add_row(self.survey.rich_print())
|
317
|
-
return table
|
318
|
-
|
319
803
|
def code(self):
|
804
|
+
"""Return the code to create this instance."""
|
320
805
|
raise NotImplementedError
|
321
806
|
|
322
807
|
|
323
808
|
def main():
|
324
|
-
|
809
|
+
"""Run the module's doctests."""
|
810
|
+
from edsl.jobs.Jobs import Jobs
|
811
|
+
from edsl.data.Cache import Cache
|
325
812
|
|
326
813
|
job = Jobs.example()
|
327
|
-
len(job) ==
|
328
|
-
results = job.run(
|
329
|
-
len(results) ==
|
814
|
+
len(job) == 4
|
815
|
+
results = job.run(cache=Cache())
|
816
|
+
len(results) == 4
|
330
817
|
results
|
331
818
|
|
332
819
|
|
333
820
|
if __name__ == "__main__":
|
821
|
+
"""Run the module's doctests."""
|
334
822
|
import doctest
|
335
823
|
|
336
|
-
doctest.testmod()
|
337
|
-
|
338
|
-
from edsl.jobs import Jobs
|
339
|
-
|
340
|
-
job = Jobs.example()
|
341
|
-
len(job) == 8
|
342
|
-
results, info = job.run(debug=True)
|
343
|
-
len(results) == 8
|
344
|
-
results
|
824
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|