edsl 0.1.15__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 +45 -10
- edsl/__version__.py +1 -1
- 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 +115 -113
- 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 -206
- 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.15.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 -435
- edsl/jobs/JobsRunner.py +0 -63
- edsl/jobs/JobsRunnerStatusMixin.py +0 -115
- edsl/jobs/base.py +0 -47
- edsl/jobs/buckets.py +0 -178
- edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
- edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
- edsl/jobs/task_management.py +0 -215
- 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.15.dist-info/METADATA +0 -69
- edsl-0.1.15.dist-info/RECORD +0 -142
- /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.15.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
from typing import Optional, TYPE_CHECKING, Protocol
|
2
|
+
import sys
|
3
|
+
from edsl.scenarios.FileStore import HTMLFileStore
|
4
|
+
from edsl.config import CONFIG
|
5
|
+
from edsl.coop.coop import Coop
|
6
|
+
|
7
|
+
|
8
|
+
class ResultsProtocol(Protocol):
|
9
|
+
"""Protocol defining the required interface for Results objects."""
|
10
|
+
|
11
|
+
@property
|
12
|
+
def has_unfixed_exceptions(self) -> bool: ...
|
13
|
+
|
14
|
+
@property
|
15
|
+
def task_history(self) -> "TaskHistoryProtocol": ...
|
16
|
+
|
17
|
+
|
18
|
+
class TaskHistoryProtocol(Protocol):
|
19
|
+
"""Protocol defining the required interface for TaskHistory objects."""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def indices(self) -> list: ...
|
23
|
+
|
24
|
+
def html(self, cta: str, open_in_browser: bool, return_link: bool) -> str: ...
|
25
|
+
|
26
|
+
|
27
|
+
class RunParametersProtocol(Protocol):
|
28
|
+
"""Protocol defining the required interface for RunParameters objects."""
|
29
|
+
|
30
|
+
@property
|
31
|
+
def print_exceptions(self) -> bool: ...
|
32
|
+
|
33
|
+
|
34
|
+
class ResultsExceptionsHandler:
|
35
|
+
"""Handles exception reporting and display functionality."""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self, results: ResultsProtocol, parameters: RunParametersProtocol
|
39
|
+
) -> None:
|
40
|
+
self.results = results
|
41
|
+
self.parameters = parameters
|
42
|
+
|
43
|
+
self.open_in_browser = self._get_browser_setting()
|
44
|
+
self.remote_logging = self._get_remote_logging_setting()
|
45
|
+
|
46
|
+
def _get_browser_setting(self) -> bool:
|
47
|
+
"""Determine if exceptions should be opened in browser based on config."""
|
48
|
+
setting = CONFIG.get("EDSL_OPEN_EXCEPTION_REPORT_URL")
|
49
|
+
if setting == "True":
|
50
|
+
return True
|
51
|
+
elif setting == "False":
|
52
|
+
return False
|
53
|
+
else:
|
54
|
+
raise Exception(
|
55
|
+
"EDSL_OPEN_EXCEPTION_REPORT_URL must be either True or False"
|
56
|
+
)
|
57
|
+
|
58
|
+
def _get_remote_logging_setting(self) -> bool:
|
59
|
+
"""Get remote logging setting from coop."""
|
60
|
+
try:
|
61
|
+
coop = Coop()
|
62
|
+
return coop.edsl_settings["remote_logging"]
|
63
|
+
except Exception as e:
|
64
|
+
# print(e)
|
65
|
+
return False
|
66
|
+
|
67
|
+
def _generate_error_message(self, indices) -> str:
|
68
|
+
"""Generate appropriate error message based on number of exceptions."""
|
69
|
+
msg = f"Exceptions were raised in {len(indices)} interviews.\n"
|
70
|
+
if len(indices) > 5:
|
71
|
+
msg += f"Exceptions were raised in the following interviews: {indices}.\n"
|
72
|
+
return msg
|
73
|
+
|
74
|
+
def handle_exceptions(self) -> None:
|
75
|
+
"""Handle exceptions by printing messages and generating reports as needed."""
|
76
|
+
if not (
|
77
|
+
self.results.has_unfixed_exceptions and self.parameters.print_exceptions
|
78
|
+
):
|
79
|
+
return
|
80
|
+
|
81
|
+
# Print error message
|
82
|
+
error_msg = self._generate_error_message(self.results.task_history.indices)
|
83
|
+
print(error_msg, file=sys.stderr)
|
84
|
+
|
85
|
+
# Generate HTML report
|
86
|
+
filepath = self.results.task_history.html(
|
87
|
+
cta="Open report to see details.",
|
88
|
+
open_in_browser=self.open_in_browser,
|
89
|
+
return_link=True,
|
90
|
+
)
|
91
|
+
|
92
|
+
# Handle remote logging if enabled
|
93
|
+
if self.remote_logging:
|
94
|
+
filestore = HTMLFileStore(filepath)
|
95
|
+
coop_details = filestore.push(description="Error report")
|
96
|
+
print(coop_details)
|
97
|
+
|
98
|
+
print("Also see: https://docs.expectedparrot.com/en/latest/exceptions.html")
|
@@ -1,122 +1,163 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
import time
|
2
3
|
import asyncio
|
3
|
-
|
4
|
+
import threading
|
5
|
+
import warnings
|
6
|
+
from typing import TYPE_CHECKING
|
4
7
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
|
8
|
-
from edsl.results import Results, Result
|
9
|
-
from edsl.jobs.JobsRunner import JobsRunner
|
10
|
-
from edsl.jobs.Interview import Interview
|
8
|
+
from edsl.results.Results import Results
|
9
|
+
from edsl.jobs.runners.JobsRunnerStatus import JobsRunnerStatus
|
10
|
+
from edsl.jobs.tasks.TaskHistory import TaskHistory
|
11
11
|
from edsl.utilities.decorators import jupyter_nb_handler
|
12
|
+
from edsl.jobs.async_interview_runner import AsyncInterviewRunner
|
13
|
+
from edsl.jobs.data_structures import RunEnvironment, RunParameters, RunConfig
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
class JobsRunnerAsyncio(JobsRunner, JobsRunnerStatusMixin):
|
17
|
-
runner_name = "asyncio"
|
18
|
-
|
19
|
-
async def run_async(
|
20
|
-
self, n=1, verbose=False, sleep=0, debug=False, progress_bar=False
|
21
|
-
) -> AsyncGenerator[Result, None]:
|
22
|
-
"""Creates the tasks, runs them asynchronously, and returns the results as a Results object.
|
23
|
-
Completed tasks are yielded as they are completed.
|
24
|
-
"""
|
25
|
-
tasks = self._create_all_interview_tasks(self.interviews, debug)
|
26
|
-
for task in asyncio.as_completed(tasks):
|
27
|
-
result = await task
|
28
|
-
yield result
|
29
|
-
|
30
|
-
def _create_all_interview_tasks(self, interviews, debug) -> List[asyncio.Task]:
|
31
|
-
"""Creates an awaitable task for each interview."""
|
32
|
-
tasks = []
|
33
|
-
for i, interview in enumerate(interviews):
|
34
|
-
interviewing_task = self._interview_task(interview, i, debug)
|
35
|
-
tasks.append(asyncio.create_task(interviewing_task))
|
36
|
-
return tasks
|
37
|
-
|
38
|
-
async def _interview_task(
|
39
|
-
self, interview: Interview, i: int, debug: bool
|
40
|
-
) -> Result:
|
41
|
-
"""Conducts an interview and returns the result."""
|
42
|
-
# the model buckets are used to track usage rates
|
43
|
-
model_buckets = self.bucket_collection[interview.model]
|
44
|
-
|
45
|
-
# get the results of the interview
|
46
|
-
answer, valid_results = await interview.async_conduct_interview(
|
47
|
-
debug=debug, model_buckets=model_buckets
|
48
|
-
)
|
49
|
-
# breakpoint()
|
50
|
-
|
51
|
-
# we should have a valid result for each question
|
52
|
-
answer_key_names = {k for k in set(answer.keys()) if not k.endswith("_comment")}
|
53
|
-
assert len(valid_results) == len(answer_key_names)
|
54
|
-
|
55
|
-
question_name_to_prompts = dict({})
|
56
|
-
for result in valid_results:
|
57
|
-
question_name = result["question_name"]
|
58
|
-
question_name_to_prompts[question_name] = {
|
59
|
-
"user_prompt": result["prompts"]["user_prompt"],
|
60
|
-
"system_prompt": result["prompts"]["system_prompt"],
|
61
|
-
}
|
62
|
-
|
63
|
-
prompt_dictionary = {}
|
64
|
-
for answer_key_name in answer_key_names:
|
65
|
-
prompt_dictionary[
|
66
|
-
answer_key_name + "_user_prompt"
|
67
|
-
] = question_name_to_prompts[answer_key_name]["user_prompt"]
|
68
|
-
prompt_dictionary[
|
69
|
-
answer_key_name + "_system_prompt"
|
70
|
-
] = question_name_to_prompts[answer_key_name]["system_prompt"]
|
71
|
-
|
72
|
-
raw_model_results_dictionary = {}
|
73
|
-
for result in valid_results:
|
74
|
-
question_name = result["question_name"]
|
75
|
-
raw_model_results_dictionary[
|
76
|
-
question_name + "_raw_model_response"
|
77
|
-
] = result["raw_model_response"]
|
78
|
-
|
79
|
-
result = Result(
|
80
|
-
agent=interview.agent,
|
81
|
-
scenario=interview.scenario,
|
82
|
-
model=interview.model,
|
83
|
-
iteration=i,
|
84
|
-
answer=answer,
|
85
|
-
prompt=prompt_dictionary,
|
86
|
-
raw_model_response=raw_model_results_dictionary,
|
87
|
-
)
|
88
|
-
return result
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from edsl.jobs.Jobs import Jobs
|
89
17
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
18
|
+
|
19
|
+
class JobsRunnerAsyncio:
|
20
|
+
"""A class for running a collection of interviews asynchronously.
|
21
|
+
|
22
|
+
It gets instaniated from a Jobs object.
|
23
|
+
The Jobs object is a collection of interviews that are to be run.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, jobs: "Jobs", environment: RunEnvironment):
|
27
|
+
self.jobs = jobs
|
28
|
+
self.environment = environment
|
29
|
+
|
30
|
+
def __len__(self):
|
31
|
+
return len(self.jobs)
|
32
|
+
|
33
|
+
async def run_async(self, parameters: RunParameters) -> Results:
|
34
|
+
"""Used for some other modules that have a non-standard way of running interviews."""
|
35
|
+
|
36
|
+
self.environment.jobs_runner_status = JobsRunnerStatus(self, n=parameters.n)
|
97
37
|
data = []
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
console=console,
|
105
|
-
refresh_per_second=10,
|
106
|
-
)
|
107
|
-
live.__enter__() # Manually enter the Live context
|
108
|
-
|
109
|
-
async for result in self.run_async(n, verbose, sleep, debug, progress_bar):
|
110
|
-
end_time = time.monotonic()
|
111
|
-
elapsed_time = end_time - start_time
|
38
|
+
task_history = TaskHistory(include_traceback=False)
|
39
|
+
|
40
|
+
run_config = RunConfig(parameters=parameters, environment=self.environment)
|
41
|
+
result_generator = AsyncInterviewRunner(self.jobs, run_config)
|
42
|
+
|
43
|
+
async for result, interview in result_generator.run():
|
112
44
|
data.append(result)
|
45
|
+
task_history.add_interview(interview)
|
113
46
|
|
114
|
-
|
115
|
-
live.update(self._generate_status_table(data, elapsed_time))
|
47
|
+
results = Results(survey=self.jobs.survey, task_history=task_history, data=data)
|
116
48
|
|
117
|
-
|
118
|
-
live.update(self._generate_status_table(data, elapsed_time))
|
119
|
-
await asyncio.sleep(0.5) # short delay to show the final status
|
120
|
-
live.__exit__(None, None, None) # Manually exit the Live context
|
49
|
+
relevant_cache = results.relevant_cache(self.environment.cache)
|
121
50
|
|
51
|
+
return Results(
|
52
|
+
survey=self.jobs.survey,
|
53
|
+
task_history=task_history,
|
54
|
+
data=data,
|
55
|
+
cache=relevant_cache,
|
56
|
+
)
|
57
|
+
|
58
|
+
def simple_run(self):
|
59
|
+
data = asyncio.run(self.run_async())
|
122
60
|
return Results(survey=self.jobs.survey, data=data)
|
61
|
+
|
62
|
+
@jupyter_nb_handler
|
63
|
+
async def run(self, parameters: RunParameters) -> Results:
|
64
|
+
"""Runs a collection of interviews, handling both async and sync contexts."""
|
65
|
+
|
66
|
+
run_config = RunConfig(parameters=parameters, environment=self.environment)
|
67
|
+
|
68
|
+
self.start_time = time.monotonic()
|
69
|
+
self.completed = False
|
70
|
+
|
71
|
+
from edsl.coop import Coop
|
72
|
+
|
73
|
+
coop = Coop()
|
74
|
+
endpoint_url = coop.get_progress_bar_url()
|
75
|
+
|
76
|
+
def set_up_jobs_runner_status(jobs_runner_status):
|
77
|
+
if jobs_runner_status is not None:
|
78
|
+
return jobs_runner_status(
|
79
|
+
self,
|
80
|
+
n=parameters.n,
|
81
|
+
endpoint_url=endpoint_url,
|
82
|
+
job_uuid=parameters.job_uuid,
|
83
|
+
)
|
84
|
+
else:
|
85
|
+
return JobsRunnerStatus(
|
86
|
+
self,
|
87
|
+
n=parameters.n,
|
88
|
+
endpoint_url=endpoint_url,
|
89
|
+
job_uuid=parameters.job_uuid,
|
90
|
+
)
|
91
|
+
|
92
|
+
run_config.environment.jobs_runner_status = set_up_jobs_runner_status(
|
93
|
+
self.environment.jobs_runner_status
|
94
|
+
)
|
95
|
+
|
96
|
+
async def get_results(results) -> None:
|
97
|
+
"""Conducted the interviews and append to the results list."""
|
98
|
+
result_generator = AsyncInterviewRunner(self.jobs, run_config)
|
99
|
+
async for result, interview in result_generator.run():
|
100
|
+
results.append(result)
|
101
|
+
results.task_history.add_interview(interview)
|
102
|
+
|
103
|
+
self.completed = True
|
104
|
+
|
105
|
+
def run_progress_bar(stop_event, jobs_runner_status) -> None:
|
106
|
+
"""Runs the progress bar in a separate thread."""
|
107
|
+
jobs_runner_status.update_progress(stop_event)
|
108
|
+
|
109
|
+
def set_up_progress_bar(progress_bar: bool, jobs_runner_status):
|
110
|
+
progress_thread = None
|
111
|
+
if progress_bar and jobs_runner_status.has_ep_api_key():
|
112
|
+
jobs_runner_status.setup()
|
113
|
+
progress_thread = threading.Thread(
|
114
|
+
target=run_progress_bar, args=(stop_event, jobs_runner_status)
|
115
|
+
)
|
116
|
+
progress_thread.start()
|
117
|
+
elif progress_bar:
|
118
|
+
warnings.warn(
|
119
|
+
"You need an Expected Parrot API key to view job progress bars."
|
120
|
+
)
|
121
|
+
return progress_thread
|
122
|
+
|
123
|
+
results = Results(
|
124
|
+
survey=self.jobs.survey,
|
125
|
+
data=[],
|
126
|
+
task_history=TaskHistory(),
|
127
|
+
# cache=self.environment.cache.new_entries_cache(),
|
128
|
+
)
|
129
|
+
|
130
|
+
stop_event = threading.Event()
|
131
|
+
progress_thread = set_up_progress_bar(
|
132
|
+
parameters.progress_bar, run_config.environment.jobs_runner_status
|
133
|
+
)
|
134
|
+
|
135
|
+
exception_to_raise = None
|
136
|
+
try:
|
137
|
+
await get_results(results)
|
138
|
+
except KeyboardInterrupt:
|
139
|
+
print("Keyboard interrupt received. Stopping gracefully...")
|
140
|
+
stop_event.set()
|
141
|
+
except Exception as e:
|
142
|
+
if parameters.stop_on_exception:
|
143
|
+
exception_to_raise = e
|
144
|
+
stop_event.set()
|
145
|
+
finally:
|
146
|
+
stop_event.set()
|
147
|
+
if progress_thread is not None:
|
148
|
+
progress_thread.join()
|
149
|
+
|
150
|
+
if exception_to_raise:
|
151
|
+
raise exception_to_raise
|
152
|
+
|
153
|
+
relevant_cache = results.relevant_cache(self.environment.cache)
|
154
|
+
results.cache = relevant_cache
|
155
|
+
# breakpoint()
|
156
|
+
results.bucket_collection = self.environment.bucket_collection
|
157
|
+
|
158
|
+
from edsl.jobs.results_exceptions_handler import ResultsExceptionsHandler
|
159
|
+
|
160
|
+
results_exceptions_handler = ResultsExceptionsHandler(results, parameters)
|
161
|
+
|
162
|
+
results_exceptions_handler.handle_exceptions()
|
163
|
+
return results
|
@@ -0,0 +1,298 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
import requests
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from collections import defaultdict
|
9
|
+
from typing import Any, Dict, Optional
|
10
|
+
from uuid import UUID
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class ModelInfo:
|
15
|
+
model_name: str
|
16
|
+
TPM_limit_k: float
|
17
|
+
RPM_limit_k: float
|
18
|
+
num_tasks_waiting: int
|
19
|
+
token_usage_info: dict
|
20
|
+
|
21
|
+
|
22
|
+
class StatisticsTracker:
|
23
|
+
def __init__(self, total_interviews: int, distinct_models: list[str]):
|
24
|
+
self.start_time = time.time()
|
25
|
+
self.total_interviews = total_interviews
|
26
|
+
self.completed_count = 0
|
27
|
+
self.completed_by_model = defaultdict(int)
|
28
|
+
self.distinct_models = distinct_models
|
29
|
+
self.total_exceptions = 0
|
30
|
+
self.unfixed_exceptions = 0
|
31
|
+
|
32
|
+
def add_completed_interview(
|
33
|
+
self, model: str, num_exceptions: int = 0, num_unfixed: int = 0
|
34
|
+
):
|
35
|
+
self.completed_count += 1
|
36
|
+
self.completed_by_model[model] += 1
|
37
|
+
self.total_exceptions += num_exceptions
|
38
|
+
self.unfixed_exceptions += num_unfixed
|
39
|
+
|
40
|
+
def get_elapsed_time(self) -> float:
|
41
|
+
return time.time() - self.start_time
|
42
|
+
|
43
|
+
def get_average_time_per_interview(self) -> float:
|
44
|
+
return (
|
45
|
+
self.get_elapsed_time() / self.completed_count
|
46
|
+
if self.completed_count > 0
|
47
|
+
else 0
|
48
|
+
)
|
49
|
+
|
50
|
+
def get_throughput(self) -> float:
|
51
|
+
elapsed = self.get_elapsed_time()
|
52
|
+
return self.completed_count / elapsed if elapsed > 0 else 0
|
53
|
+
|
54
|
+
def get_estimated_time_remaining(self) -> float:
|
55
|
+
if self.completed_count == 0:
|
56
|
+
return 0
|
57
|
+
avg_time = self.get_average_time_per_interview()
|
58
|
+
remaining = self.total_interviews - self.completed_count
|
59
|
+
return avg_time * remaining
|
60
|
+
|
61
|
+
|
62
|
+
class JobsRunnerStatusBase(ABC):
|
63
|
+
def __init__(
|
64
|
+
self,
|
65
|
+
jobs_runner: "JobsRunnerAsyncio",
|
66
|
+
n: int,
|
67
|
+
refresh_rate: float = 1,
|
68
|
+
endpoint_url: Optional[str] = "http://localhost:8000",
|
69
|
+
job_uuid: Optional[UUID] = None,
|
70
|
+
api_key: str = None,
|
71
|
+
):
|
72
|
+
self.jobs_runner = jobs_runner
|
73
|
+
self.job_uuid = job_uuid
|
74
|
+
self.base_url = f"{endpoint_url}"
|
75
|
+
self.refresh_rate = refresh_rate
|
76
|
+
self.statistics = [
|
77
|
+
"elapsed_time",
|
78
|
+
"total_interviews_requested",
|
79
|
+
"completed_interviews",
|
80
|
+
"average_time_per_interview",
|
81
|
+
"estimated_time_remaining",
|
82
|
+
"exceptions",
|
83
|
+
"unfixed_exceptions",
|
84
|
+
"throughput",
|
85
|
+
]
|
86
|
+
self.num_total_interviews = n * len(self.jobs_runner)
|
87
|
+
|
88
|
+
self.distinct_models = list(
|
89
|
+
set(model.model for model in self.jobs_runner.jobs.models)
|
90
|
+
)
|
91
|
+
|
92
|
+
self.stats_tracker = StatisticsTracker(
|
93
|
+
total_interviews=self.num_total_interviews,
|
94
|
+
distinct_models=self.distinct_models,
|
95
|
+
)
|
96
|
+
|
97
|
+
self.api_key = api_key or os.getenv("EXPECTED_PARROT_API_KEY")
|
98
|
+
|
99
|
+
@abstractmethod
|
100
|
+
def has_ep_api_key(self):
|
101
|
+
"""Checks if the user has an Expected Parrot API key."""
|
102
|
+
pass
|
103
|
+
|
104
|
+
def get_status_dict(self) -> Dict[str, Any]:
|
105
|
+
"""Converts current status into a JSON-serializable dictionary."""
|
106
|
+
# Get all statistics
|
107
|
+
stats = {}
|
108
|
+
for stat_name in self.statistics:
|
109
|
+
stat = self._compute_statistic(stat_name)
|
110
|
+
name, value = list(stat.items())[0]
|
111
|
+
stats[name] = value
|
112
|
+
|
113
|
+
# Get model-specific progress
|
114
|
+
model_progress = {}
|
115
|
+
target_per_model = int(self.num_total_interviews / len(self.distinct_models))
|
116
|
+
|
117
|
+
for model in self.distinct_models:
|
118
|
+
completed = self.stats_tracker.completed_by_model[model]
|
119
|
+
model_progress[model] = {
|
120
|
+
"completed": completed,
|
121
|
+
"total": target_per_model,
|
122
|
+
"percent": (
|
123
|
+
(completed / target_per_model * 100) if target_per_model > 0 else 0
|
124
|
+
),
|
125
|
+
}
|
126
|
+
|
127
|
+
status_dict = {
|
128
|
+
"overall_progress": {
|
129
|
+
"completed": self.stats_tracker.completed_count,
|
130
|
+
"total": self.num_total_interviews,
|
131
|
+
"percent": (
|
132
|
+
(
|
133
|
+
self.stats_tracker.completed_count
|
134
|
+
/ self.num_total_interviews
|
135
|
+
* 100
|
136
|
+
)
|
137
|
+
if self.num_total_interviews > 0
|
138
|
+
else 0
|
139
|
+
),
|
140
|
+
},
|
141
|
+
"language_model_progress": model_progress,
|
142
|
+
"statistics": stats,
|
143
|
+
"status": (
|
144
|
+
"completed"
|
145
|
+
if self.stats_tracker.completed_count >= self.num_total_interviews
|
146
|
+
else "running"
|
147
|
+
),
|
148
|
+
}
|
149
|
+
|
150
|
+
model_queues = {}
|
151
|
+
# for model, bucket in self.jobs_runner.bucket_collection.items():
|
152
|
+
for model, bucket in self.jobs_runner.environment.bucket_collection.items():
|
153
|
+
model_name = model.model
|
154
|
+
model_queues[model_name] = {
|
155
|
+
"language_model_name": model_name,
|
156
|
+
"requests_bucket": {
|
157
|
+
"completed": bucket.requests_bucket.num_released,
|
158
|
+
"requested": bucket.requests_bucket.num_requests,
|
159
|
+
"tokens_returned": bucket.requests_bucket.tokens_returned,
|
160
|
+
"target_rate": round(bucket.requests_bucket.target_rate, 1),
|
161
|
+
"current_rate": round(bucket.requests_bucket.get_throughput(), 1),
|
162
|
+
},
|
163
|
+
"tokens_bucket": {
|
164
|
+
"completed": bucket.tokens_bucket.num_released,
|
165
|
+
"requested": bucket.tokens_bucket.num_requests,
|
166
|
+
"tokens_returned": bucket.tokens_bucket.tokens_returned,
|
167
|
+
"target_rate": round(bucket.tokens_bucket.target_rate, 1),
|
168
|
+
"current_rate": round(bucket.tokens_bucket.get_throughput(), 1),
|
169
|
+
},
|
170
|
+
}
|
171
|
+
status_dict["language_model_queues"] = model_queues
|
172
|
+
return status_dict
|
173
|
+
|
174
|
+
def add_completed_interview(self, result):
|
175
|
+
"""Records a completed interview without storing the full interview data."""
|
176
|
+
self.stats_tracker.add_completed_interview(
|
177
|
+
model=result.model.model,
|
178
|
+
num_exceptions=(
|
179
|
+
len(result.exceptions) if hasattr(result, "exceptions") else 0
|
180
|
+
),
|
181
|
+
num_unfixed=(
|
182
|
+
result.exceptions.num_unfixed() if hasattr(result, "exceptions") else 0
|
183
|
+
),
|
184
|
+
)
|
185
|
+
|
186
|
+
def _compute_statistic(self, stat_name: str):
|
187
|
+
"""Computes individual statistics based on the stats tracker."""
|
188
|
+
if stat_name == "elapsed_time":
|
189
|
+
value = self.stats_tracker.get_elapsed_time()
|
190
|
+
return {"elapsed_time": (value, 1, "sec.")}
|
191
|
+
|
192
|
+
elif stat_name == "total_interviews_requested":
|
193
|
+
return {"total_interviews_requested": (self.num_total_interviews, None, "")}
|
194
|
+
|
195
|
+
elif stat_name == "completed_interviews":
|
196
|
+
return {
|
197
|
+
"completed_interviews": (self.stats_tracker.completed_count, None, "")
|
198
|
+
}
|
199
|
+
|
200
|
+
elif stat_name == "average_time_per_interview":
|
201
|
+
value = self.stats_tracker.get_average_time_per_interview()
|
202
|
+
return {"average_time_per_interview": (value, 2, "sec.")}
|
203
|
+
|
204
|
+
elif stat_name == "estimated_time_remaining":
|
205
|
+
value = self.stats_tracker.get_estimated_time_remaining()
|
206
|
+
return {"estimated_time_remaining": (value, 1, "sec.")}
|
207
|
+
|
208
|
+
elif stat_name == "exceptions":
|
209
|
+
return {"exceptions": (self.stats_tracker.total_exceptions, None, "")}
|
210
|
+
|
211
|
+
elif stat_name == "unfixed_exceptions":
|
212
|
+
return {
|
213
|
+
"unfixed_exceptions": (self.stats_tracker.unfixed_exceptions, None, "")
|
214
|
+
}
|
215
|
+
|
216
|
+
elif stat_name == "throughput":
|
217
|
+
value = self.stats_tracker.get_throughput()
|
218
|
+
return {"throughput": (value, 2, "interviews/sec.")}
|
219
|
+
|
220
|
+
def update_progress(self, stop_event):
|
221
|
+
while not stop_event.is_set():
|
222
|
+
self.send_status_update()
|
223
|
+
time.sleep(self.refresh_rate)
|
224
|
+
self.send_status_update()
|
225
|
+
|
226
|
+
@abstractmethod
|
227
|
+
def setup(self):
|
228
|
+
"""Conducts any setup needed prior to sending status updates."""
|
229
|
+
pass
|
230
|
+
|
231
|
+
@abstractmethod
|
232
|
+
def send_status_update(self):
|
233
|
+
"""Updates the current status of the job."""
|
234
|
+
pass
|
235
|
+
|
236
|
+
|
237
|
+
class JobsRunnerStatus(JobsRunnerStatusBase):
|
238
|
+
@property
|
239
|
+
def create_url(self) -> str:
|
240
|
+
return f"{self.base_url}/api/v0/local-job"
|
241
|
+
|
242
|
+
@property
|
243
|
+
def viewing_url(self) -> str:
|
244
|
+
return f"{self.base_url}/home/local-job-progress/{str(self.job_uuid)}"
|
245
|
+
|
246
|
+
@property
|
247
|
+
def update_url(self) -> str:
|
248
|
+
return f"{self.base_url}/api/v0/local-job/{str(self.job_uuid)}"
|
249
|
+
|
250
|
+
def setup(self) -> None:
|
251
|
+
"""Creates a local job on Coop if one does not already exist."""
|
252
|
+
headers = {
|
253
|
+
"Content-Type": "application/json",
|
254
|
+
"Authorization": f"Bearer {self.api_key or 'None'}",
|
255
|
+
}
|
256
|
+
|
257
|
+
if self.job_uuid is None:
|
258
|
+
response = requests.post(
|
259
|
+
self.create_url,
|
260
|
+
headers=headers,
|
261
|
+
timeout=1,
|
262
|
+
)
|
263
|
+
response.raise_for_status()
|
264
|
+
data = response.json()
|
265
|
+
self.job_uuid = data.get("job_uuid")
|
266
|
+
|
267
|
+
print(f"Running with progress bar. View progress at {self.viewing_url}")
|
268
|
+
|
269
|
+
def send_status_update(self) -> None:
|
270
|
+
"""Sends current status to the web endpoint using the instance's job_uuid."""
|
271
|
+
try:
|
272
|
+
status_dict = self.get_status_dict()
|
273
|
+
status_dict["job_id"] = str(self.job_uuid)
|
274
|
+
|
275
|
+
headers = {
|
276
|
+
"Content-Type": "application/json",
|
277
|
+
"Authorization": f"Bearer {self.api_key or 'None'}",
|
278
|
+
}
|
279
|
+
|
280
|
+
response = requests.patch(
|
281
|
+
self.update_url,
|
282
|
+
json=status_dict,
|
283
|
+
headers=headers,
|
284
|
+
timeout=1,
|
285
|
+
)
|
286
|
+
response.raise_for_status()
|
287
|
+
except requests.exceptions.RequestException as e:
|
288
|
+
print(f"Failed to send status update for job {self.job_uuid}: {e}")
|
289
|
+
|
290
|
+
def has_ep_api_key(self) -> bool:
|
291
|
+
"""Returns True if the user has an Expected Parrot API key."""
|
292
|
+
return self.api_key is not None
|
293
|
+
|
294
|
+
|
295
|
+
if __name__ == "__main__":
|
296
|
+
import doctest
|
297
|
+
|
298
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|