edsl 0.1.46__py3-none-any.whl → 0.1.48__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/__init__.py +44 -39
- edsl/__version__.py +1 -1
- edsl/agents/__init__.py +4 -2
- edsl/agents/{Agent.py → agent.py} +442 -152
- edsl/agents/{AgentList.py → agent_list.py} +220 -162
- edsl/agents/descriptors.py +46 -7
- edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
- edsl/base/__init__.py +75 -0
- edsl/base/base_class.py +1303 -0
- edsl/base/data_transfer_models.py +114 -0
- edsl/base/enums.py +215 -0
- edsl/base.py +8 -0
- edsl/buckets/__init__.py +25 -0
- edsl/buckets/bucket_collection.py +324 -0
- edsl/buckets/model_buckets.py +206 -0
- edsl/buckets/token_bucket.py +502 -0
- edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
- edsl/buckets/token_bucket_client.py +509 -0
- edsl/caching/__init__.py +20 -0
- edsl/caching/cache.py +814 -0
- edsl/caching/cache_entry.py +427 -0
- edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
- edsl/caching/exceptions.py +24 -0
- edsl/caching/orm.py +30 -0
- edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
- edsl/caching/sql_dict.py +441 -0
- edsl/config/__init__.py +8 -0
- edsl/config/config_class.py +177 -0
- edsl/config.py +4 -176
- edsl/conversation/Conversation.py +7 -7
- edsl/conversation/car_buying.py +4 -4
- edsl/conversation/chips.py +6 -6
- edsl/coop/__init__.py +25 -2
- edsl/coop/coop.py +430 -113
- edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
- edsl/coop/exceptions.py +62 -0
- edsl/coop/price_fetcher.py +126 -0
- edsl/coop/utils.py +89 -24
- edsl/data_transfer_models.py +5 -72
- edsl/dataset/__init__.py +10 -0
- edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
- edsl/dataset/dataset_operations_mixin.py +1492 -0
- edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
- edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
- edsl/{results → dataset/display}/table_renderers.py +58 -2
- edsl/{results → dataset}/file_exports.py +4 -5
- edsl/{results → dataset}/smart_objects.py +2 -2
- edsl/enums.py +5 -205
- edsl/inference_services/__init__.py +5 -0
- edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
- edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
- edsl/inference_services/data_structures.py +3 -2
- edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
- edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
- edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
- edsl/inference_services/registry.py +4 -41
- edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
- edsl/inference_services/services/__init__.py +31 -0
- edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
- edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
- edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
- edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
- edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
- edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
- edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
- edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
- edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
- edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
- edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +12 -12
- edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
- edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
- edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
- edsl/inference_services/write_available.py +1 -2
- edsl/instructions/__init__.py +6 -0
- edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
- edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
- edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
- edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
- edsl/interviews/__init__.py +4 -0
- edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
- edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
- edsl/interviews/interview.py +638 -0
- edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
- edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
- edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
- edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
- edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
- edsl/invigilators/__init__.py +38 -0
- edsl/invigilators/invigilator_base.py +477 -0
- edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
- edsl/invigilators/prompt_constructor.py +476 -0
- edsl/{agents → invigilators}/prompt_helpers.py +2 -1
- edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
- edsl/{agents → invigilators}/question_option_processor.py +96 -21
- edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
- edsl/jobs/__init__.py +7 -1
- edsl/jobs/async_interview_runner.py +99 -35
- edsl/jobs/check_survey_scenario_compatibility.py +7 -5
- edsl/jobs/data_structures.py +153 -22
- edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
- edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
- edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
- edsl/jobs/{Jobs.py → jobs.py} +321 -155
- edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
- edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +20 -17
- edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
- edsl/jobs/jobs_pricing_estimation.py +347 -0
- edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
- edsl/jobs/jobs_runner_asyncio.py +282 -0
- edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
- edsl/jobs/results_exceptions_handler.py +2 -2
- edsl/key_management/__init__.py +28 -0
- edsl/key_management/key_lookup.py +161 -0
- edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
- edsl/key_management/key_lookup_collection.py +82 -0
- edsl/key_management/models.py +218 -0
- edsl/language_models/__init__.py +7 -2
- edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
- edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
- edsl/language_models/language_model.py +1080 -0
- edsl/language_models/model.py +10 -25
- edsl/language_models/{ModelList.py → model_list.py} +9 -14
- edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
- edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
- edsl/language_models/repair.py +4 -4
- edsl/language_models/utilities.py +4 -4
- edsl/notebooks/__init__.py +3 -1
- edsl/notebooks/{Notebook.py → notebook.py} +7 -8
- edsl/prompts/__init__.py +1 -1
- edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
- edsl/prompts/{Prompt.py → prompt.py} +101 -95
- edsl/questions/HTMLQuestion.py +1 -1
- edsl/questions/__init__.py +154 -25
- edsl/questions/answer_validator_mixin.py +1 -1
- edsl/questions/compose_questions.py +4 -3
- edsl/questions/derived/question_likert_five.py +166 -0
- edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
- edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
- edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
- edsl/questions/descriptors.py +24 -30
- edsl/questions/loop_processor.py +65 -19
- edsl/questions/question_base.py +881 -0
- edsl/questions/question_base_gen_mixin.py +15 -16
- edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
- edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
- edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
- edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
- edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
- edsl/questions/question_free_text.py +282 -0
- edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
- edsl/questions/{QuestionList.py → question_list.py} +6 -7
- edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
- edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
- edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
- edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
- edsl/questions/question_registry.py +10 -16
- edsl/questions/register_questions_meta.py +8 -4
- edsl/questions/response_validator_abc.py +17 -16
- edsl/results/__init__.py +4 -1
- edsl/{exceptions/results.py → results/exceptions.py} +1 -1
- edsl/results/report.py +197 -0
- edsl/results/{Result.py → result.py} +131 -45
- edsl/results/{Results.py → results.py} +420 -216
- edsl/results/results_selector.py +344 -25
- edsl/scenarios/__init__.py +30 -3
- edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
- edsl/scenarios/directory_scanner.py +156 -13
- edsl/scenarios/document_chunker.py +186 -0
- edsl/scenarios/exceptions.py +101 -0
- edsl/scenarios/file_methods.py +2 -3
- edsl/scenarios/file_store.py +755 -0
- edsl/scenarios/handlers/__init__.py +14 -14
- edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
- edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
- edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
- edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
- edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
- edsl/scenarios/handlers/latex_file_store.py +5 -0
- edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
- edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
- edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
- edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
- edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
- edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
- edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
- edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
- edsl/scenarios/scenario.py +928 -0
- edsl/scenarios/scenario_join.py +18 -5
- edsl/scenarios/{ScenarioList.py → scenario_list.py} +424 -106
- edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
- edsl/scenarios/scenario_selector.py +5 -1
- edsl/study/ObjectEntry.py +2 -2
- edsl/study/SnapShot.py +5 -5
- edsl/study/Study.py +20 -21
- edsl/study/__init__.py +6 -4
- edsl/surveys/__init__.py +7 -4
- edsl/surveys/dag/__init__.py +2 -0
- edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
- edsl/surveys/{DAG.py → dag/dag.py} +13 -10
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
- edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
- edsl/surveys/memory/__init__.py +3 -0
- edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
- edsl/surveys/rules/__init__.py +3 -0
- edsl/surveys/{Rule.py → rules/rule.py} +103 -43
- edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
- edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
- edsl/surveys/survey.py +1743 -0
- edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
- edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
- edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
- edsl/tasks/__init__.py +32 -0
- edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
- edsl/tasks/task_creators.py +135 -0
- edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
- edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
- edsl/tasks/task_status_log.py +85 -0
- edsl/tokens/__init__.py +2 -0
- edsl/tokens/interview_token_usage.py +53 -0
- edsl/utilities/PrettyList.py +1 -1
- edsl/utilities/SystemInfo.py +25 -22
- edsl/utilities/__init__.py +29 -21
- edsl/utilities/gcp_bucket/__init__.py +2 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
- edsl/utilities/interface.py +44 -536
- edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
- edsl/utilities/repair_functions.py +1 -1
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/METADATA +3 -2
- edsl-0.1.48.dist-info/RECORD +347 -0
- edsl/Base.py +0 -426
- edsl/BaseDiff.py +0 -260
- edsl/agents/InvigilatorBase.py +0 -260
- edsl/agents/PromptConstructor.py +0 -318
- edsl/auto/AutoStudy.py +0 -130
- edsl/auto/StageBase.py +0 -243
- edsl/auto/StageGenerateSurvey.py +0 -178
- edsl/auto/StageLabelQuestions.py +0 -125
- edsl/auto/StagePersona.py +0 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
- edsl/auto/StagePersonaDimensionValues.py +0 -74
- edsl/auto/StagePersonaDimensions.py +0 -69
- edsl/auto/StageQuestions.py +0 -74
- edsl/auto/SurveyCreatorPipeline.py +0 -21
- edsl/auto/utilities.py +0 -218
- edsl/base/Base.py +0 -279
- edsl/coop/PriceFetcher.py +0 -54
- edsl/data/Cache.py +0 -580
- edsl/data/CacheEntry.py +0 -230
- edsl/data/SQLiteDict.py +0 -292
- edsl/data/__init__.py +0 -5
- edsl/data/orm.py +0 -10
- edsl/exceptions/cache.py +0 -5
- edsl/exceptions/coop.py +0 -14
- edsl/exceptions/data.py +0 -14
- edsl/exceptions/scenarios.py +0 -29
- edsl/jobs/Answers.py +0 -43
- edsl/jobs/JobsPrompts.py +0 -354
- edsl/jobs/buckets/BucketCollection.py +0 -134
- edsl/jobs/buckets/ModelBuckets.py +0 -65
- edsl/jobs/buckets/TokenBucket.py +0 -283
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/interviews/Interview.py +0 -395
- edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
- edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
- edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
- edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
- edsl/jobs/tasks/TaskCreators.py +0 -64
- edsl/jobs/tasks/TaskStatusLog.py +0 -23
- edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
- edsl/language_models/LanguageModel.py +0 -635
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/models.py +0 -137
- edsl/questions/QuestionBase.py +0 -539
- edsl/questions/QuestionFreeText.py +0 -130
- edsl/questions/derived/QuestionLikertFive.py +0 -76
- edsl/results/DatasetExportMixin.py +0 -911
- edsl/results/ResultsExportMixin.py +0 -45
- edsl/results/TextEditor.py +0 -50
- edsl/results/results_fetch_mixin.py +0 -33
- edsl/results/results_tools_mixin.py +0 -98
- edsl/scenarios/DocumentChunker.py +0 -104
- edsl/scenarios/FileStore.py +0 -564
- edsl/scenarios/Scenario.py +0 -548
- edsl/scenarios/ScenarioHtmlMixin.py +0 -65
- edsl/scenarios/ScenarioListExportMixin.py +0 -45
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/shared.py +0 -1
- edsl/surveys/Survey.py +0 -1306
- edsl/surveys/SurveyQualtricsImport.py +0 -284
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/tools/__init__.py +0 -1
- edsl/tools/clusters.py +0 -192
- edsl/tools/embeddings.py +0 -27
- edsl/tools/embeddings_plotting.py +0 -118
- edsl/tools/plotting.py +0 -112
- edsl/tools/summarize.py +0 -18
- edsl/utilities/data/Registry.py +0 -6
- edsl/utilities/data/__init__.py +0 -1
- edsl/utilities/data/scooter_results.json +0 -1
- edsl-0.1.46.dist-info/RECORD +0 -366
- /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
- /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
- /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
- /edsl/{results → dataset/display}/table_data_class.py +0 -0
- /edsl/{results → dataset/display}/table_display.css +0 -0
- /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
- /edsl/{results → dataset}/tree_explore.py +0 -0
- /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
- /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
- /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
- /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
- /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
- /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
- /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
- /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
- /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
- /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
- /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
- /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
- /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
- /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
- /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
edsl/coop/coop.py
CHANGED
@@ -2,22 +2,24 @@ import aiohttp
|
|
2
2
|
import json
|
3
3
|
import requests
|
4
4
|
|
5
|
-
from typing import Any, Optional, Union, Literal, TypedDict
|
5
|
+
from typing import Any, Optional, Union, Literal, TypedDict, TYPE_CHECKING
|
6
6
|
from uuid import UUID
|
7
7
|
|
8
|
-
import
|
8
|
+
from .. import __version__
|
9
9
|
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from edsl.jobs.Jobs import Jobs
|
13
|
-
from edsl.surveys.Survey import Survey
|
10
|
+
from ..config import CONFIG
|
11
|
+
from ..caching import CacheEntry
|
14
12
|
|
15
|
-
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from ..jobs import Jobs
|
15
|
+
from ..surveys import Survey
|
16
|
+
|
17
|
+
from .exceptions import (
|
16
18
|
CoopInvalidURLError,
|
17
19
|
CoopNoUUIDError,
|
18
20
|
CoopServerResponseError,
|
19
21
|
)
|
20
|
-
from
|
22
|
+
from .utils import (
|
21
23
|
EDSLObject,
|
22
24
|
ObjectRegistry,
|
23
25
|
ObjectType,
|
@@ -25,10 +27,10 @@ from edsl.coop.utils import (
|
|
25
27
|
VisibilityType,
|
26
28
|
)
|
27
29
|
|
28
|
-
from
|
29
|
-
from
|
30
|
+
from .coop_functions import CoopFunctionsMixin
|
31
|
+
from .ep_key_handling import ExpectedParrotKeyHandler
|
30
32
|
|
31
|
-
from
|
33
|
+
from ..inference_services.data_structures import ServiceToModelsMapping
|
32
34
|
|
33
35
|
|
34
36
|
class RemoteInferenceResponse(TypedDict):
|
@@ -54,16 +56,58 @@ class RemoteInferenceCreationInfo(TypedDict):
|
|
54
56
|
|
55
57
|
class Coop(CoopFunctionsMixin):
|
56
58
|
"""
|
57
|
-
Client for the Expected Parrot API.
|
59
|
+
Client for the Expected Parrot API that provides cloud-based functionality for EDSL.
|
60
|
+
|
61
|
+
The Coop class is the main interface for interacting with Expected Parrot's cloud services.
|
62
|
+
It enables:
|
63
|
+
|
64
|
+
1. Storing and retrieving EDSL objects (surveys, agents, models, results, etc.)
|
65
|
+
2. Running inference jobs remotely for better performance and scalability
|
66
|
+
3. Retrieving and caching interview results
|
67
|
+
4. Managing API keys and authentication
|
68
|
+
5. Accessing model availability and pricing information
|
69
|
+
|
70
|
+
The client handles authentication, serialization/deserialization of EDSL objects,
|
71
|
+
and communication with the Expected Parrot API endpoints. It also provides
|
72
|
+
methods for tracking job status and managing results.
|
73
|
+
|
74
|
+
When initialized without parameters, Coop will attempt to use an API key from:
|
75
|
+
1. The EXPECTED_PARROT_API_KEY environment variable
|
76
|
+
2. A stored key in the user's config directory
|
77
|
+
3. Interactive login if needed
|
78
|
+
|
79
|
+
Attributes:
|
80
|
+
api_key (str): The API key used for authentication
|
81
|
+
url (str): The base URL for the Expected Parrot API
|
82
|
+
api_url (str): The URL for API endpoints (derived from base URL)
|
58
83
|
"""
|
59
84
|
|
60
85
|
def __init__(
|
61
86
|
self, api_key: Optional[str] = None, url: Optional[str] = None
|
62
87
|
) -> None:
|
63
88
|
"""
|
64
|
-
Initialize the client.
|
65
|
-
|
66
|
-
|
89
|
+
Initialize the Expected Parrot API client.
|
90
|
+
|
91
|
+
This constructor sets up the connection to Expected Parrot's cloud services.
|
92
|
+
If not provided explicitly, it will attempt to obtain an API key from
|
93
|
+
environment variables or from a stored location in the user's config directory.
|
94
|
+
|
95
|
+
Parameters:
|
96
|
+
api_key (str, optional): API key for authentication with Expected Parrot.
|
97
|
+
If not provided, will attempt to obtain from environment or stored location.
|
98
|
+
url (str, optional): Base URL for the Expected Parrot service.
|
99
|
+
If not provided, uses the default from configuration.
|
100
|
+
|
101
|
+
Notes:
|
102
|
+
- The API key is stored in the EXPECTED_PARROT_API_KEY environment variable
|
103
|
+
or in a platform-specific config directory
|
104
|
+
- The URL is determined based on whether it's a production, staging,
|
105
|
+
or development environment
|
106
|
+
- The api_url for actual API endpoints is derived from the base URL
|
107
|
+
|
108
|
+
Example:
|
109
|
+
>>> coop = Coop() # Uses API key from environment or stored location
|
110
|
+
>>> coop = Coop(api_key="your-api-key") # Explicitly provide API key
|
67
111
|
"""
|
68
112
|
self.ep_key_handler = ExpectedParrotKeyHandler()
|
69
113
|
self.api_key = api_key or self.ep_key_handler.get_ep_api_key()
|
@@ -79,7 +123,7 @@ class Coop(CoopFunctionsMixin):
|
|
79
123
|
self.api_url = "http://localhost:8000"
|
80
124
|
else:
|
81
125
|
self.api_url = self.url
|
82
|
-
self._edsl_version =
|
126
|
+
self._edsl_version = __version__
|
83
127
|
|
84
128
|
def get_progress_bar_url(self):
|
85
129
|
return f"{CONFIG.EXPECTED_PARROT_URL}"
|
@@ -205,7 +249,7 @@ class Coop(CoopFunctionsMixin):
|
|
205
249
|
# print(response.text)
|
206
250
|
if "The API key you provided is invalid" in message and check_api_key:
|
207
251
|
import secrets
|
208
|
-
from
|
252
|
+
from ..utilities.utilities import write_api_key_to_env
|
209
253
|
|
210
254
|
edsl_auth_token = secrets.token_urlsafe(16)
|
211
255
|
|
@@ -239,6 +283,30 @@ class Coop(CoopFunctionsMixin):
|
|
239
283
|
|
240
284
|
raise CoopServerResponseError(message)
|
241
285
|
|
286
|
+
def _resolve_gcs_response(self, response: requests.Response) -> None:
|
287
|
+
"""
|
288
|
+
Check the response from uploading or downloading a file from Google Cloud Storage.
|
289
|
+
Raise errors as appropriate.
|
290
|
+
"""
|
291
|
+
if response.status_code >= 400:
|
292
|
+
try:
|
293
|
+
import xml.etree.ElementTree as ET
|
294
|
+
|
295
|
+
# Extract elements from XML string
|
296
|
+
root = ET.fromstring(response.text)
|
297
|
+
|
298
|
+
code = root.find("Code").text
|
299
|
+
message = root.find("Message").text
|
300
|
+
details = root.find("Details").text
|
301
|
+
except Exception:
|
302
|
+
raise Exception(
|
303
|
+
f"Server returned status code {response.status_code}",
|
304
|
+
"XML response could not be decoded.",
|
305
|
+
"The server response was: " + response.text,
|
306
|
+
)
|
307
|
+
|
308
|
+
raise Exception(f"An error occurred: {code} - {message} - {details}")
|
309
|
+
|
242
310
|
def _poll_for_api_key(
|
243
311
|
self, edsl_auth_token: str, timeout: int = 120
|
244
312
|
) -> Union[str, None]:
|
@@ -287,19 +355,25 @@ class Coop(CoopFunctionsMixin):
|
|
287
355
|
if value is None:
|
288
356
|
return "null"
|
289
357
|
|
358
|
+
@staticmethod
|
359
|
+
def _is_url(url_or_uuid: Union[str, UUID]) -> bool:
|
360
|
+
return "http://" in str(url_or_uuid) or "https://" in str(url_or_uuid)
|
361
|
+
|
290
362
|
def _resolve_uuid_or_alias(
|
291
|
-
self,
|
363
|
+
self, url_or_uuid: Union[str, UUID]
|
292
364
|
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
293
365
|
"""
|
294
366
|
Resolve the uuid or alias information from a uuid or a url.
|
295
367
|
Returns a tuple of (uuid, owner_username, alias)
|
296
|
-
- For content
|
297
|
-
- For content
|
368
|
+
- For content/uuid URLs: returns (uuid, None, None)
|
369
|
+
- For content/username/alias URLs: returns (None, username, alias)
|
298
370
|
"""
|
299
|
-
if not
|
371
|
+
if not url_or_uuid:
|
300
372
|
raise CoopNoUUIDError("No uuid or url provided for the object.")
|
301
373
|
|
302
|
-
if
|
374
|
+
if self._is_url(url_or_uuid):
|
375
|
+
url = str(url_or_uuid)
|
376
|
+
|
303
377
|
parts = (
|
304
378
|
url.replace("http://", "")
|
305
379
|
.replace("https://", "")
|
@@ -326,7 +400,8 @@ class Coop(CoopFunctionsMixin):
|
|
326
400
|
f"Invalid URL format. The URL must end with /content/<uuid> or /content/<username>/<alias>: {url}"
|
327
401
|
)
|
328
402
|
|
329
|
-
|
403
|
+
uuid = str(url_or_uuid)
|
404
|
+
return uuid, None, None
|
330
405
|
|
331
406
|
@property
|
332
407
|
def edsl_settings(self) -> dict:
|
@@ -348,6 +423,15 @@ class Coop(CoopFunctionsMixin):
|
|
348
423
|
################
|
349
424
|
# Objects
|
350
425
|
################
|
426
|
+
def _get_alias_url(self, owner_username: str, alias: str) -> Union[str, None]:
|
427
|
+
"""
|
428
|
+
Get the URL of an object by its owner username and alias.
|
429
|
+
"""
|
430
|
+
if owner_username and alias:
|
431
|
+
return f"{self.url}/content/{owner_username}/{alias}"
|
432
|
+
else:
|
433
|
+
return None
|
434
|
+
|
351
435
|
def create(
|
352
436
|
self,
|
353
437
|
object: EDSLObject,
|
@@ -356,7 +440,35 @@ class Coop(CoopFunctionsMixin):
|
|
356
440
|
visibility: Optional[VisibilityType] = "unlisted",
|
357
441
|
) -> dict:
|
358
442
|
"""
|
359
|
-
|
443
|
+
Store an EDSL object in the Expected Parrot cloud service.
|
444
|
+
|
445
|
+
This method uploads an EDSL object (like a Survey, Agent, or Results) to the
|
446
|
+
Expected Parrot cloud service for storage, sharing, or further processing.
|
447
|
+
|
448
|
+
Parameters:
|
449
|
+
object (EDSLObject): The EDSL object to store (Survey, Agent, Results, etc.)
|
450
|
+
description (str, optional): A human-readable description of the object
|
451
|
+
alias (str, optional): A custom alias for easier reference later
|
452
|
+
visibility (VisibilityType, optional): Access level for the object. One of:
|
453
|
+
- "private": Only accessible by the owner
|
454
|
+
- "public": Accessible by anyone
|
455
|
+
- "unlisted": Accessible with the link, but not listed publicly
|
456
|
+
|
457
|
+
Returns:
|
458
|
+
dict: Information about the created object including:
|
459
|
+
- url: The URL to access the object
|
460
|
+
- alias_url: The URL with the custom alias (if provided)
|
461
|
+
- uuid: The unique identifier for the object
|
462
|
+
- visibility: The visibility setting
|
463
|
+
- version: The EDSL version used to create the object
|
464
|
+
|
465
|
+
Raises:
|
466
|
+
CoopServerResponseError: If there's an error communicating with the server
|
467
|
+
|
468
|
+
Example:
|
469
|
+
>>> survey = Survey(questions=[QuestionFreeText(question_name="name")])
|
470
|
+
>>> result = coop.create(survey, description="Basic survey", visibility="public")
|
471
|
+
>>> print(result["url"]) # URL to access the survey
|
360
472
|
"""
|
361
473
|
object_type = ObjectRegistry.get_object_type_by_edsl_class(object)
|
362
474
|
response = self._send_server_request(
|
@@ -365,9 +477,13 @@ class Coop(CoopFunctionsMixin):
|
|
365
477
|
payload={
|
366
478
|
"description": description,
|
367
479
|
"alias": alias,
|
368
|
-
"json_string":
|
369
|
-
|
370
|
-
|
480
|
+
"json_string": (
|
481
|
+
json.dumps(
|
482
|
+
object.to_dict(),
|
483
|
+
default=self._json_handle_none,
|
484
|
+
)
|
485
|
+
if object_type != "scenario"
|
486
|
+
else ""
|
371
487
|
),
|
372
488
|
"object_type": object_type,
|
373
489
|
"visibility": visibility,
|
@@ -376,33 +492,77 @@ class Coop(CoopFunctionsMixin):
|
|
376
492
|
)
|
377
493
|
self._resolve_server_response(response)
|
378
494
|
response_json = response.json()
|
495
|
+
|
496
|
+
if object_type == "scenario":
|
497
|
+
json_data = json.dumps(
|
498
|
+
object.to_dict(),
|
499
|
+
default=self._json_handle_none,
|
500
|
+
)
|
501
|
+
headers = {"Content-Type": "application/json"}
|
502
|
+
if response_json.get("upload_signed_url"):
|
503
|
+
signed_url = response_json.get("upload_signed_url")
|
504
|
+
else:
|
505
|
+
raise Exception("No signed url provided received")
|
506
|
+
|
507
|
+
response = requests.put(
|
508
|
+
signed_url, data=json_data.encode(), headers=headers
|
509
|
+
)
|
510
|
+
self._resolve_gcs_response(response)
|
511
|
+
owner_username = response_json.get("owner_username")
|
512
|
+
object_alias = response_json.get("alias")
|
513
|
+
|
379
514
|
return {
|
380
515
|
"description": response_json.get("description"),
|
381
516
|
"object_type": object_type,
|
382
517
|
"url": f"{self.url}/content/{response_json.get('uuid')}",
|
518
|
+
"alias_url": self._get_alias_url(owner_username, object_alias),
|
383
519
|
"uuid": response_json.get("uuid"),
|
384
520
|
"version": self._edsl_version,
|
385
521
|
"visibility": response_json.get("visibility"),
|
522
|
+
"upload_signed_url": response_json.get("upload_signed_url", None),
|
386
523
|
}
|
387
524
|
|
388
525
|
def get(
|
389
526
|
self,
|
390
|
-
|
391
|
-
url: str = None,
|
527
|
+
url_or_uuid: Union[str, UUID],
|
392
528
|
expected_object_type: Optional[ObjectType] = None,
|
393
529
|
) -> EDSLObject:
|
394
530
|
"""
|
395
|
-
Retrieve an EDSL object
|
396
|
-
|
397
|
-
|
531
|
+
Retrieve an EDSL object from the Expected Parrot cloud service.
|
532
|
+
|
533
|
+
This method downloads and deserializes an EDSL object from the cloud service
|
534
|
+
using either its UUID, URL, or username/alias combination.
|
398
535
|
|
399
|
-
:
|
400
|
-
|
401
|
-
|
536
|
+
Parameters:
|
537
|
+
url_or_uuid (Union[str, UUID]): Identifier for the object to retrieve.
|
538
|
+
Can be one of:
|
539
|
+
- UUID string (e.g., "123e4567-e89b-12d3-a456-426614174000")
|
540
|
+
- Full URL (e.g., "https://expectedparrot.com/content/123e4567...")
|
541
|
+
- Alias URL (e.g., "https://expectedparrot.com/content/username/my-survey")
|
542
|
+
expected_object_type (ObjectType, optional): If provided, validates that the
|
543
|
+
retrieved object is of the expected type (e.g., "survey", "agent")
|
544
|
+
|
545
|
+
Returns:
|
546
|
+
EDSLObject: The retrieved object as its original EDSL class instance
|
547
|
+
(e.g., Survey, Agent, Results)
|
402
548
|
|
403
|
-
:
|
549
|
+
Raises:
|
550
|
+
CoopNoUUIDError: If no UUID or URL is provided
|
551
|
+
CoopInvalidURLError: If the URL format is invalid
|
552
|
+
CoopServerResponseError: If the server returns an error (e.g., not found,
|
553
|
+
unauthorized access)
|
554
|
+
Exception: If the retrieved object doesn't match the expected type
|
555
|
+
|
556
|
+
Notes:
|
557
|
+
- If the object's visibility is set to "private", you must be the owner to access it
|
558
|
+
- For objects stored with an alias, you can use either the UUID or the alias URL
|
559
|
+
|
560
|
+
Example:
|
561
|
+
>>> survey = coop.get("123e4567-e89b-12d3-a456-426614174000")
|
562
|
+
>>> survey = coop.get("https://expectedparrot.com/content/username/my-survey")
|
563
|
+
>>> survey = coop.get(url, expected_object_type="survey") # Validates the type
|
404
564
|
"""
|
405
|
-
obj_uuid, owner_username, alias = self._resolve_uuid_or_alias(
|
565
|
+
obj_uuid, owner_username, alias = self._resolve_uuid_or_alias(url_or_uuid)
|
406
566
|
|
407
567
|
if obj_uuid:
|
408
568
|
response = self._send_server_request(
|
@@ -419,6 +579,11 @@ class Coop(CoopFunctionsMixin):
|
|
419
579
|
|
420
580
|
self._resolve_server_response(response)
|
421
581
|
json_string = response.json().get("json_string")
|
582
|
+
if "load_from:" in json_string[0:12]:
|
583
|
+
load_link = json_string.split("load_from:")[1]
|
584
|
+
object_data = requests.get(load_link)
|
585
|
+
self._resolve_gcs_response(object_data)
|
586
|
+
json_string = object_data.text
|
422
587
|
object_type = response.json().get("object_type")
|
423
588
|
if expected_object_type and object_type != expected_object_type:
|
424
589
|
raise Exception(f"Expected {expected_object_type=} but got {object_type=}")
|
@@ -437,28 +602,52 @@ class Coop(CoopFunctionsMixin):
|
|
437
602
|
params={"type": object_type},
|
438
603
|
)
|
439
604
|
self._resolve_server_response(response)
|
440
|
-
objects = [
|
441
|
-
|
442
|
-
|
605
|
+
objects = []
|
606
|
+
for o in response.json():
|
607
|
+
json_string = o.get("json_string")
|
608
|
+
## check if load from bucket needed.
|
609
|
+
if "load_from:" in json_string[0:12]:
|
610
|
+
load_link = json_string.split("load_from:")[1]
|
611
|
+
object_data = requests.get(load_link)
|
612
|
+
self._resolve_gcs_response(object_data)
|
613
|
+
json_string = object_data.text
|
614
|
+
|
615
|
+
json_string = json.loads(json_string)
|
616
|
+
object = {
|
617
|
+
"object": edsl_class.from_dict(json_string),
|
443
618
|
"uuid": o.get("uuid"),
|
444
619
|
"version": o.get("version"),
|
445
620
|
"description": o.get("description"),
|
446
621
|
"visibility": o.get("visibility"),
|
447
622
|
"url": f"{self.url}/content/{o.get('uuid')}",
|
623
|
+
"alias_url": self._get_alias_url(
|
624
|
+
o.get("owner_username"), o.get("alias")
|
625
|
+
),
|
448
626
|
}
|
449
|
-
|
450
|
-
|
627
|
+
objects.append(object)
|
628
|
+
|
451
629
|
return objects
|
452
630
|
|
453
|
-
def delete(self,
|
631
|
+
def delete(self, url_or_uuid: Union[str, UUID]) -> dict:
|
454
632
|
"""
|
455
633
|
Delete an object from the server.
|
634
|
+
|
635
|
+
:param url_or_uuid: The UUID or URL of the object.
|
636
|
+
URLs can be in the form content/uuid or content/username/alias.
|
456
637
|
"""
|
457
|
-
obj_uuid,
|
638
|
+
obj_uuid, owner_username, alias = self._resolve_uuid_or_alias(url_or_uuid)
|
639
|
+
|
640
|
+
if obj_uuid:
|
641
|
+
uri = "api/v0/object"
|
642
|
+
params = {"uuid": obj_uuid}
|
643
|
+
else:
|
644
|
+
uri = "api/v0/object/alias"
|
645
|
+
params = {"owner_username": owner_username, "alias": alias}
|
646
|
+
|
458
647
|
response = self._send_server_request(
|
459
|
-
uri=
|
648
|
+
uri=uri,
|
460
649
|
method="DELETE",
|
461
|
-
params=
|
650
|
+
params=params,
|
462
651
|
)
|
463
652
|
|
464
653
|
self._resolve_server_response(response)
|
@@ -466,8 +655,7 @@ class Coop(CoopFunctionsMixin):
|
|
466
655
|
|
467
656
|
def patch(
|
468
657
|
self,
|
469
|
-
|
470
|
-
url: str = None,
|
658
|
+
url_or_uuid: Union[str, UUID],
|
471
659
|
description: Optional[str] = None,
|
472
660
|
alias: Optional[str] = None,
|
473
661
|
value: Optional[EDSLObject] = None,
|
@@ -475,15 +663,35 @@ class Coop(CoopFunctionsMixin):
|
|
475
663
|
) -> dict:
|
476
664
|
"""
|
477
665
|
Change the attributes of an uploaded object
|
478
|
-
|
479
|
-
|
480
|
-
|
666
|
+
|
667
|
+
:param url_or_uuid: The UUID or URL of the object.
|
668
|
+
URLs can be in the form content/uuid or content/username/alias.
|
669
|
+
:param description: Optional new description
|
670
|
+
:param alias: Optional new alias
|
671
|
+
:param value: Optional new object value
|
672
|
+
:param visibility: Optional new visibility setting
|
673
|
+
"""
|
674
|
+
if (
|
675
|
+
description is None
|
676
|
+
and visibility is None
|
677
|
+
and value is None
|
678
|
+
and alias is None
|
679
|
+
):
|
481
680
|
raise Exception("Nothing to patch.")
|
482
|
-
|
681
|
+
|
682
|
+
obj_uuid, owner_username, obj_alias = self._resolve_uuid_or_alias(url_or_uuid)
|
683
|
+
|
684
|
+
if obj_uuid:
|
685
|
+
uri = "api/v0/object"
|
686
|
+
params = {"uuid": obj_uuid}
|
687
|
+
else:
|
688
|
+
uri = "api/v0/object/alias"
|
689
|
+
params = {"owner_username": owner_username, "alias": obj_alias}
|
690
|
+
|
483
691
|
response = self._send_server_request(
|
484
|
-
uri=
|
692
|
+
uri=uri,
|
485
693
|
method="PATCH",
|
486
|
-
params=
|
694
|
+
params=params,
|
487
695
|
payload={
|
488
696
|
"description": description,
|
489
697
|
"alias": alias,
|
@@ -765,7 +973,7 @@ class Coop(CoopFunctionsMixin):
|
|
765
973
|
|
766
974
|
def remote_inference_create(
|
767
975
|
self,
|
768
|
-
job: Jobs,
|
976
|
+
job: "Jobs",
|
769
977
|
description: Optional[str] = None,
|
770
978
|
status: RemoteJobStatus = "queued",
|
771
979
|
visibility: Optional[VisibilityType] = "unlisted",
|
@@ -774,17 +982,48 @@ class Coop(CoopFunctionsMixin):
|
|
774
982
|
fresh: Optional[bool] = False,
|
775
983
|
) -> RemoteInferenceCreationInfo:
|
776
984
|
"""
|
777
|
-
|
985
|
+
Create a remote inference job for execution in the Expected Parrot cloud.
|
986
|
+
|
987
|
+
This method sends a job to be executed in the cloud, which can be more efficient
|
988
|
+
for large jobs or when you want to run jobs in the background. The job execution
|
989
|
+
is handled by Expected Parrot's infrastructure, and you can check the status
|
990
|
+
and retrieve results later.
|
991
|
+
|
992
|
+
Parameters:
|
993
|
+
job (Jobs): The EDSL job to run in the cloud
|
994
|
+
description (str, optional): A human-readable description of the job
|
995
|
+
status (RemoteJobStatus): Initial status, should be "queued" for normal use
|
996
|
+
Possible values: "queued", "running", "completed", "failed"
|
997
|
+
visibility (VisibilityType): Access level for the job information. One of:
|
998
|
+
- "private": Only accessible by the owner
|
999
|
+
- "public": Accessible by anyone
|
1000
|
+
- "unlisted": Accessible with the link, but not listed publicly
|
1001
|
+
initial_results_visibility (VisibilityType): Access level for the job results
|
1002
|
+
iterations (int): Number of times to run each interview (default: 1)
|
1003
|
+
fresh (bool): If True, ignore existing cache entries and generate new results
|
778
1004
|
|
779
|
-
:
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
1005
|
+
Returns:
|
1006
|
+
RemoteInferenceCreationInfo: Information about the created job including:
|
1007
|
+
- uuid: The unique identifier for the job
|
1008
|
+
- description: The job description
|
1009
|
+
- status: Current status of the job
|
1010
|
+
- iterations: Number of iterations for each interview
|
1011
|
+
- visibility: Access level for the job
|
1012
|
+
- version: EDSL version used to create the job
|
1013
|
+
|
1014
|
+
Raises:
|
1015
|
+
CoopServerResponseError: If there's an error communicating with the server
|
1016
|
+
|
1017
|
+
Notes:
|
1018
|
+
- Remote jobs run asynchronously and may take time to complete
|
1019
|
+
- Use remote_inference_get() with the returned UUID to check status
|
1020
|
+
- Credits are consumed based on the complexity of the job
|
1021
|
+
|
1022
|
+
Example:
|
1023
|
+
>>> from edsl.jobs import Jobs
|
1024
|
+
>>> job = Jobs.example()
|
1025
|
+
>>> job_info = coop.remote_inference_create(job=job, description="My job")
|
1026
|
+
>>> print(f"Job created with UUID: {job_info['uuid']}")
|
788
1027
|
"""
|
789
1028
|
response = self._send_server_request(
|
790
1029
|
uri="api/v0/remote-inference",
|
@@ -821,15 +1060,43 @@ class Coop(CoopFunctionsMixin):
|
|
821
1060
|
self, job_uuid: Optional[str] = None, results_uuid: Optional[str] = None
|
822
1061
|
) -> RemoteInferenceResponse:
|
823
1062
|
"""
|
824
|
-
Get the details of a remote inference job.
|
825
|
-
|
826
|
-
|
1063
|
+
Get the status and details of a remote inference job.
|
1064
|
+
|
1065
|
+
This method retrieves the current status and information about a remote job,
|
1066
|
+
including links to results if the job has completed successfully.
|
827
1067
|
|
828
|
-
:
|
829
|
-
|
1068
|
+
Parameters:
|
1069
|
+
job_uuid (str, optional): The UUID of the remote job to check
|
1070
|
+
results_uuid (str, optional): The UUID of the results associated with the job
|
1071
|
+
(can be used if you only have the results UUID)
|
830
1072
|
|
831
|
-
|
832
|
-
|
1073
|
+
Returns:
|
1074
|
+
RemoteInferenceResponse: Information about the job including:
|
1075
|
+
- job_uuid: The unique identifier for the job
|
1076
|
+
- results_uuid: The UUID of the results (if job is completed)
|
1077
|
+
- results_url: URL to access the results (if available)
|
1078
|
+
- latest_error_report_uuid: UUID of error report (if job failed)
|
1079
|
+
- latest_error_report_url: URL to access error details (if available)
|
1080
|
+
- status: Current status ("queued", "running", "completed", "failed")
|
1081
|
+
- reason: Reason for failure (if applicable)
|
1082
|
+
- credits_consumed: Credits used for the job execution
|
1083
|
+
- version: EDSL version used for the job
|
1084
|
+
|
1085
|
+
Raises:
|
1086
|
+
ValueError: If neither job_uuid nor results_uuid is provided
|
1087
|
+
CoopServerResponseError: If there's an error communicating with the server
|
1088
|
+
|
1089
|
+
Notes:
|
1090
|
+
- Either job_uuid or results_uuid must be provided
|
1091
|
+
- If both are provided, job_uuid takes precedence
|
1092
|
+
- For completed jobs, you can use the results_url to view or download results
|
1093
|
+
- For failed jobs, check the latest_error_report_url for debugging information
|
1094
|
+
|
1095
|
+
Example:
|
1096
|
+
>>> job_status = coop.remote_inference_get("9f8484ee-b407-40e4-9652-4133a7236c9c")
|
1097
|
+
>>> print(f"Job status: {job_status['status']}")
|
1098
|
+
>>> if job_status['status'] == 'completed':
|
1099
|
+
... print(f"Results available at: {job_status['results_url']}")
|
833
1100
|
"""
|
834
1101
|
if job_uuid is None and results_uuid is None:
|
835
1102
|
raise ValueError("Either job_uuid or results_uuid must be provided.")
|
@@ -887,7 +1154,7 @@ class Coop(CoopFunctionsMixin):
|
|
887
1154
|
return response.json().get("running_jobs", [])
|
888
1155
|
|
889
1156
|
def remote_inference_cost(
|
890
|
-
self, input: Union[Jobs, Survey], iterations: int = 1
|
1157
|
+
self, input: Union["Jobs", "Survey"], iterations: int = 1
|
891
1158
|
) -> int:
|
892
1159
|
"""
|
893
1160
|
Get the cost of a remote inference job.
|
@@ -898,6 +1165,9 @@ class Coop(CoopFunctionsMixin):
|
|
898
1165
|
>>> coop.remote_inference_cost(input=job)
|
899
1166
|
{'credits': 0.77, 'usd': 0.0076950000000000005}
|
900
1167
|
"""
|
1168
|
+
from ..jobs import Jobs
|
1169
|
+
from ..surveys import Survey
|
1170
|
+
|
901
1171
|
if isinstance(input, Jobs):
|
902
1172
|
job = input
|
903
1173
|
elif isinstance(input, Survey):
|
@@ -928,7 +1198,7 @@ class Coop(CoopFunctionsMixin):
|
|
928
1198
|
################
|
929
1199
|
def create_project(
|
930
1200
|
self,
|
931
|
-
survey: Survey,
|
1201
|
+
survey: "Survey",
|
932
1202
|
project_name: str = "Project",
|
933
1203
|
survey_description: Optional[str] = None,
|
934
1204
|
survey_alias: Optional[str] = None,
|
@@ -958,16 +1228,10 @@ class Coop(CoopFunctionsMixin):
|
|
958
1228
|
"respondent_url": f"{self.url}/respond/{response_json.get('uuid')}",
|
959
1229
|
}
|
960
1230
|
|
961
|
-
################
|
962
|
-
# DUNDER METHODS
|
963
|
-
################
|
964
1231
|
def __repr__(self):
|
965
1232
|
"""Return a string representation of the client."""
|
966
1233
|
return f"Client(api_key='{self.api_key}', url='{self.url}')"
|
967
1234
|
|
968
|
-
################
|
969
|
-
# EXPERIMENTAL
|
970
|
-
################
|
971
1235
|
async def remote_async_execute_model_call(
|
972
1236
|
self, model_dict: dict, user_prompt: str, system_prompt: str
|
973
1237
|
) -> dict:
|
@@ -1004,12 +1268,39 @@ class Coop(CoopFunctionsMixin):
|
|
1004
1268
|
|
1005
1269
|
def fetch_prices(self) -> dict:
|
1006
1270
|
"""
|
1007
|
-
Fetch
|
1008
|
-
"""
|
1271
|
+
Fetch the current pricing information for language models.
|
1009
1272
|
|
1010
|
-
|
1273
|
+
This method retrieves the latest pricing information for all supported language models
|
1274
|
+
from the Expected Parrot API. The pricing data is used to estimate costs for jobs
|
1275
|
+
and to optimize model selection based on budget constraints.
|
1011
1276
|
|
1012
|
-
|
1277
|
+
Returns:
|
1278
|
+
dict: A dictionary mapping (service, model) tuples to pricing information.
|
1279
|
+
Each entry contains token pricing for input and output tokens.
|
1280
|
+
Example structure:
|
1281
|
+
{
|
1282
|
+
('openai', 'gpt-4'): {
|
1283
|
+
'input': {'usd_per_1M_tokens': 30.0, ...},
|
1284
|
+
'output': {'usd_per_1M_tokens': 60.0, ...}
|
1285
|
+
}
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
Raises:
|
1289
|
+
ValueError: If the EDSL_FETCH_TOKEN_PRICES configuration setting is invalid
|
1290
|
+
|
1291
|
+
Notes:
|
1292
|
+
- Returns an empty dict if EDSL_FETCH_TOKEN_PRICES is set to "False"
|
1293
|
+
- The pricing data is cached to minimize API calls
|
1294
|
+
- Pricing may vary based on the model, provider, and token type (input/output)
|
1295
|
+
- All prices are in USD per million tokens
|
1296
|
+
|
1297
|
+
Example:
|
1298
|
+
>>> prices = coop.fetch_prices()
|
1299
|
+
>>> gpt4_price = prices.get(('openai', 'gpt-4'), {})
|
1300
|
+
>>> print(f"GPT-4 input price: ${gpt4_price.get('input', {}).get('usd_per_1M_tokens')}")
|
1301
|
+
"""
|
1302
|
+
from .price_fetcher import PriceFetcher
|
1303
|
+
from ..config import CONFIG
|
1013
1304
|
|
1014
1305
|
if CONFIG.get("EDSL_FETCH_TOKEN_PRICES") == "True":
|
1015
1306
|
price_fetcher = PriceFetcher()
|
@@ -1023,9 +1314,37 @@ class Coop(CoopFunctionsMixin):
|
|
1023
1314
|
|
1024
1315
|
def fetch_models(self) -> ServiceToModelsMapping:
|
1025
1316
|
"""
|
1026
|
-
Fetch
|
1317
|
+
Fetch information about available language models from Expected Parrot.
|
1027
1318
|
|
1028
|
-
|
1319
|
+
This method retrieves the current list of available language models grouped
|
1320
|
+
by service provider (e.g., OpenAI, Anthropic, etc.). This information is
|
1321
|
+
useful for programmatically selecting models based on availability and
|
1322
|
+
for ensuring that jobs only use supported models.
|
1323
|
+
|
1324
|
+
Returns:
|
1325
|
+
ServiceToModelsMapping: A mapping of service providers to their available models.
|
1326
|
+
Example structure:
|
1327
|
+
{
|
1328
|
+
"openai": ["gpt-4", "gpt-3.5-turbo", ...],
|
1329
|
+
"anthropic": ["claude-3-opus", "claude-3-sonnet", ...],
|
1330
|
+
...
|
1331
|
+
}
|
1332
|
+
|
1333
|
+
Raises:
|
1334
|
+
CoopServerResponseError: If there's an error communicating with the server
|
1335
|
+
|
1336
|
+
Notes:
|
1337
|
+
- The availability of models may change over time
|
1338
|
+
- Not all models may be accessible with your current API keys
|
1339
|
+
- Use this method to check for model availability before creating jobs
|
1340
|
+
- Models may have different capabilities (text-only, multimodal, etc.)
|
1341
|
+
|
1342
|
+
Example:
|
1343
|
+
>>> models = coop.fetch_models()
|
1344
|
+
>>> if "gpt-4" in models.get("openai", []):
|
1345
|
+
... print("GPT-4 is available")
|
1346
|
+
>>> available_services = list(models.keys())
|
1347
|
+
>>> print(f"Available services: {available_services}")
|
1029
1348
|
"""
|
1030
1349
|
response = self._send_server_request(uri="api/v0/models", method="GET")
|
1031
1350
|
self._resolve_server_response(response)
|
@@ -1134,7 +1453,7 @@ class Coop(CoopFunctionsMixin):
|
|
1134
1453
|
"""
|
1135
1454
|
import secrets
|
1136
1455
|
from dotenv import load_dotenv
|
1137
|
-
from
|
1456
|
+
from ..utilities.utilities import write_api_key_to_env
|
1138
1457
|
|
1139
1458
|
edsl_auth_token = secrets.token_urlsafe(16)
|
1140
1459
|
|
@@ -1172,9 +1491,9 @@ def main():
|
|
1172
1491
|
ScenarioList,
|
1173
1492
|
Survey,
|
1174
1493
|
)
|
1175
|
-
from
|
1176
|
-
from
|
1177
|
-
from
|
1494
|
+
from ..coop import Coop
|
1495
|
+
from ..caching import CacheEntry
|
1496
|
+
from ..jobs import Jobs
|
1178
1497
|
|
1179
1498
|
# init & basics
|
1180
1499
|
API_KEY = "b"
|
@@ -1187,27 +1506,25 @@ def main():
|
|
1187
1506
|
##############
|
1188
1507
|
# .. create and manipulate an object through the Coop client
|
1189
1508
|
response = coop.create(QuestionMultipleChoice.example())
|
1190
|
-
coop.get(
|
1191
|
-
coop.get(
|
1192
|
-
coop.get(
|
1509
|
+
coop.get(response.get("uuid"))
|
1510
|
+
coop.get(response.get("uuid"), expected_object_type="question")
|
1511
|
+
coop.get(response.get("url"))
|
1193
1512
|
coop.create(QuestionMultipleChoice.example())
|
1194
1513
|
coop.get_all("question")
|
1195
|
-
coop.patch(
|
1196
|
-
coop.patch(
|
1197
|
-
coop.patch(
|
1198
|
-
# coop.patch(
|
1199
|
-
coop.get(
|
1200
|
-
coop.delete(
|
1514
|
+
coop.patch(response.get("uuid"), visibility="private")
|
1515
|
+
coop.patch(response.get("uuid"), description="hey")
|
1516
|
+
coop.patch(response.get("uuid"), value=QuestionFreeText.example())
|
1517
|
+
# coop.patch(response.get("uuid"), value=Survey.example()) - should throw error
|
1518
|
+
coop.get(response.get("uuid"))
|
1519
|
+
coop.delete(response.get("uuid"))
|
1201
1520
|
|
1202
1521
|
# .. create and manipulate an object through the class
|
1203
1522
|
response = QuestionMultipleChoice.example().push()
|
1204
|
-
QuestionMultipleChoice.pull(
|
1205
|
-
QuestionMultipleChoice.pull(
|
1206
|
-
QuestionMultipleChoice.patch(
|
1207
|
-
QuestionMultipleChoice.patch(
|
1208
|
-
QuestionMultipleChoice.patch(
|
1209
|
-
uuid=response.get("uuid"), value=QuestionFreeText.example()
|
1210
|
-
)
|
1523
|
+
QuestionMultipleChoice.pull(response.get("uuid"))
|
1524
|
+
QuestionMultipleChoice.pull(response.get("url"))
|
1525
|
+
QuestionMultipleChoice.patch(response.get("uuid"), visibility="private")
|
1526
|
+
QuestionMultipleChoice.patch(response.get("uuid"), description="hey")
|
1527
|
+
QuestionMultipleChoice.patch(response.get("uuid"), value=QuestionFreeText.example())
|
1211
1528
|
QuestionMultipleChoice.pull(response.get("uuid"))
|
1212
1529
|
QuestionMultipleChoice.delete(response.get("uuid"))
|
1213
1530
|
|
@@ -1230,7 +1547,7 @@ def main():
|
|
1230
1547
|
# 1. Delete existing objects
|
1231
1548
|
existing_objects = coop.get_all(object_type)
|
1232
1549
|
for item in existing_objects:
|
1233
|
-
coop.delete(
|
1550
|
+
coop.delete(item.get("uuid"))
|
1234
1551
|
# 2. Create new objects
|
1235
1552
|
example = cls.example()
|
1236
1553
|
response_1 = coop.create(example)
|
@@ -1244,21 +1561,21 @@ def main():
|
|
1244
1561
|
assert len(objects) == 4
|
1245
1562
|
# 4. Try to retrieve an item that does not exist
|
1246
1563
|
try:
|
1247
|
-
coop.get(
|
1564
|
+
coop.get(uuid4())
|
1248
1565
|
except Exception as e:
|
1249
1566
|
print(e)
|
1250
1567
|
# 5. Try to retrieve all test objects by their uuids
|
1251
1568
|
for response in [response_1, response_2, response_3, response_4]:
|
1252
|
-
coop.get(
|
1569
|
+
coop.get(response.get("uuid"))
|
1253
1570
|
# 6. Change visibility of all objects
|
1254
1571
|
for item in objects:
|
1255
|
-
coop.patch(
|
1572
|
+
coop.patch(item.get("uuid"), visibility="private")
|
1256
1573
|
# 6. Change description of all objects
|
1257
1574
|
for item in objects:
|
1258
|
-
coop.patch(
|
1575
|
+
coop.patch(item.get("uuid"), description="hey")
|
1259
1576
|
# 7. Delete all objects
|
1260
1577
|
for item in objects:
|
1261
|
-
coop.delete(
|
1578
|
+
coop.delete(item.get("uuid"))
|
1262
1579
|
assert len(coop.get_all(object_type)) == 0
|
1263
1580
|
|
1264
1581
|
##############
|
@@ -1291,4 +1608,4 @@ def main():
|
|
1291
1608
|
coop.remote_inference_cost(job)
|
1292
1609
|
job_coop_object = coop.remote_inference_create(job)
|
1293
1610
|
job_coop_results = coop.remote_inference_get(job_coop_object.get("uuid"))
|
1294
|
-
coop.get(
|
1611
|
+
coop.get(job_coop_results.get("results_uuid"))
|