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
@@ -0,0 +1,477 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
import asyncio
|
3
|
+
from typing import Coroutine, Dict, Any, Optional, TYPE_CHECKING
|
4
|
+
|
5
|
+
from ..utilities.decorators import jupyter_nb_handler
|
6
|
+
from ..data_transfer_models import AgentResponseDict
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from ..prompts import Prompt
|
10
|
+
from ..caching import Cache
|
11
|
+
from ..questions import QuestionBase
|
12
|
+
from ..scenarios import Scenario
|
13
|
+
from ..surveys.memory import MemoryPlan
|
14
|
+
from ..language_models import LanguageModel
|
15
|
+
from ..surveys import Survey
|
16
|
+
from ..agents import Agent
|
17
|
+
from ..key_management import KeyLookup
|
18
|
+
|
19
|
+
from ..data_transfer_models import EDSLResultObjectInput
|
20
|
+
from .prompt_constructor import PromptConstructor
|
21
|
+
from .prompt_helpers import PromptPlan
|
22
|
+
|
23
|
+
|
24
|
+
class InvigilatorBase(ABC):
|
25
|
+
"""
|
26
|
+
Abstract base class for invigilators that administer questions to agents.
|
27
|
+
|
28
|
+
An invigilator is responsible for the entire process of administering a question
|
29
|
+
to an agent, including:
|
30
|
+
1. Constructing appropriate prompts based on the question, scenario, and agent
|
31
|
+
2. Handling agent-specific presentation of the question
|
32
|
+
3. Processing model responses and validating them
|
33
|
+
4. Managing memory and state across sequential questions
|
34
|
+
5. Handling errors and validation failures
|
35
|
+
|
36
|
+
This abstract base class defines the interface that all invigilators must implement
|
37
|
+
and provides common functionality. Concrete subclasses implement the abstract
|
38
|
+
async_answer_question method to define the specific question-answering behavior.
|
39
|
+
|
40
|
+
Technical architecture:
|
41
|
+
- Uses async/await pattern for efficient concurrent processing
|
42
|
+
- Maintains references to agent, question, scenario, model and other components
|
43
|
+
- Delegates prompt construction to the PromptConstructor class
|
44
|
+
- Manages caching and memory for efficient execution
|
45
|
+
|
46
|
+
Examples:
|
47
|
+
>>> # Example usage, returning a test response
|
48
|
+
>>> InvigilatorBase.example().answer_question()
|
49
|
+
{'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
|
50
|
+
|
51
|
+
>>> # Example of error handling
|
52
|
+
>>> InvigilatorBase.example().get_failed_task_result(failure_reason="Failed to get response").comment
|
53
|
+
'Failed to get response'
|
54
|
+
|
55
|
+
Implementation note:
|
56
|
+
Concrete invigilator classes must implement the async_answer_question method,
|
57
|
+
which performs the actual question administration and returns the result.
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __init__(
|
61
|
+
self,
|
62
|
+
agent: "Agent",
|
63
|
+
question: "QuestionBase",
|
64
|
+
scenario: "Scenario",
|
65
|
+
model: "LanguageModel",
|
66
|
+
memory_plan: "MemoryPlan",
|
67
|
+
current_answers: dict,
|
68
|
+
survey: Optional["Survey"],
|
69
|
+
cache: Optional["Cache"] = None,
|
70
|
+
iteration: Optional[int] = 1,
|
71
|
+
additional_prompt_data: Optional[dict] = None,
|
72
|
+
raise_validation_errors: Optional[bool] = True,
|
73
|
+
prompt_plan: Optional["PromptPlan"] = None,
|
74
|
+
key_lookup: Optional["KeyLookup"] = None,
|
75
|
+
):
|
76
|
+
"""
|
77
|
+
Initialize a new Invigilator.
|
78
|
+
|
79
|
+
This constructor sets up an invigilator with all the components required to
|
80
|
+
administer a question to an agent. It establishes references to the agent,
|
81
|
+
question, scenario, language model, and other components that participate in
|
82
|
+
the question-asking process.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
agent: The agent to which the question will be administered.
|
86
|
+
question: The question to be asked.
|
87
|
+
scenario: The scenario providing context for the question.
|
88
|
+
model: The language model to use for generating responses (for AI agents).
|
89
|
+
memory_plan: Plan for managing memory across questions in a survey.
|
90
|
+
current_answers: Dictionary of answers to previous questions.
|
91
|
+
survey: Optional reference to the parent survey.
|
92
|
+
cache: Optional cache for storing and retrieving responses.
|
93
|
+
iteration: Counter for tracking question repetitions (for retry logic).
|
94
|
+
additional_prompt_data: Additional data to include in prompts.
|
95
|
+
raise_validation_errors: Whether to raise exceptions on validation errors.
|
96
|
+
prompt_plan: Custom prompt plan to use instead of the default.
|
97
|
+
key_lookup: Optional key lookup for API key management.
|
98
|
+
|
99
|
+
Technical Notes:
|
100
|
+
- The current_answers dict allows for referencing previous answers in prompts
|
101
|
+
- The memory_plan controls what information is carried forward between questions
|
102
|
+
- The prompt_plan configures the structure of system and user prompts
|
103
|
+
- The raw_model_response field stores the unprocessed response from the model
|
104
|
+
"""
|
105
|
+
self.agent = agent
|
106
|
+
self.question = question
|
107
|
+
self.scenario = scenario
|
108
|
+
self.model = model
|
109
|
+
self.memory_plan = memory_plan
|
110
|
+
self.current_answers = current_answers or {}
|
111
|
+
self.iteration = iteration
|
112
|
+
self.additional_prompt_data = additional_prompt_data
|
113
|
+
self.cache = cache
|
114
|
+
self.survey = survey
|
115
|
+
self.raise_validation_errors = raise_validation_errors
|
116
|
+
self.key_lookup = key_lookup
|
117
|
+
|
118
|
+
# Initialize prompt plan (default or custom)
|
119
|
+
if prompt_plan is None:
|
120
|
+
self.prompt_plan = PromptPlan()
|
121
|
+
else:
|
122
|
+
self.prompt_plan = prompt_plan
|
123
|
+
|
124
|
+
# Storage for the raw model response
|
125
|
+
self.raw_model_response = None
|
126
|
+
|
127
|
+
@property
|
128
|
+
def prompt_constructor(self) -> PromptConstructor:
|
129
|
+
"""
|
130
|
+
Get the prompt constructor for this invigilator.
|
131
|
+
|
132
|
+
The prompt constructor is responsible for generating the prompts that will be
|
133
|
+
sent to the language model based on the question, scenario, agent, and other
|
134
|
+
context. This property lazily creates and returns a PromptConstructor instance
|
135
|
+
configured for this invigilator.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
PromptConstructor: A prompt constructor instance configured for this invigilator.
|
139
|
+
|
140
|
+
Technical Notes:
|
141
|
+
- This uses the factory method from_invigilator on PromptConstructor
|
142
|
+
- The constructor has access to all the context of this invigilator
|
143
|
+
- The prompt_plan controls the structure and components of the prompts
|
144
|
+
- This is a key part of the separation of concerns in the invigilator architecture
|
145
|
+
"""
|
146
|
+
return PromptConstructor.from_invigilator(self, prompt_plan=self.prompt_plan)
|
147
|
+
|
148
|
+
def to_dict(self, include_cache: bool = False) -> Dict[str, Any]:
|
149
|
+
"""
|
150
|
+
Serialize the invigilator to a dictionary.
|
151
|
+
|
152
|
+
This method serializes the invigilator and all its attributes to a dictionary
|
153
|
+
representation that can be stored, transmitted, or used to recreate an equivalent
|
154
|
+
invigilator. It handles complex nested objects by recursively serializing them
|
155
|
+
if they have a to_dict method.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
include_cache: Whether to include the cache in the serialized output.
|
159
|
+
Defaults to False as the cache can be very large.
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
A dictionary representation of the invigilator.
|
163
|
+
|
164
|
+
Technical Notes:
|
165
|
+
- Objects with a to_dict method are serialized recursively
|
166
|
+
- Primitive types (int, float, str, bool, dict, list) are included directly
|
167
|
+
- Other objects are converted to strings
|
168
|
+
- The cache is optionally included based on the include_cache parameter
|
169
|
+
"""
|
170
|
+
attributes = [
|
171
|
+
"agent",
|
172
|
+
"question",
|
173
|
+
"scenario",
|
174
|
+
"model",
|
175
|
+
"memory_plan",
|
176
|
+
"current_answers",
|
177
|
+
"iteration",
|
178
|
+
"additional_prompt_data",
|
179
|
+
"survey",
|
180
|
+
"raw_model_response",
|
181
|
+
]
|
182
|
+
if include_cache:
|
183
|
+
attributes.append("cache")
|
184
|
+
|
185
|
+
def serialize_attribute(attr):
|
186
|
+
"""Helper function to serialize a single attribute."""
|
187
|
+
value = getattr(self, attr)
|
188
|
+
if value is None:
|
189
|
+
return None
|
190
|
+
if hasattr(value, "to_dict"):
|
191
|
+
return value.to_dict()
|
192
|
+
if isinstance(value, (int, float, str, bool, dict, list)):
|
193
|
+
return value
|
194
|
+
return str(value)
|
195
|
+
|
196
|
+
return {attr: serialize_attribute(attr) for attr in attributes}
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def from_dict(cls, data: Dict[str, Any]) -> "InvigilatorBase":
|
200
|
+
"""
|
201
|
+
Create an invigilator from a dictionary representation.
|
202
|
+
|
203
|
+
This class method deserializes an invigilator from a dictionary, typically
|
204
|
+
one created by the to_dict method. It recursively deserializes nested objects
|
205
|
+
using their from_dict methods.
|
206
|
+
|
207
|
+
Args:
|
208
|
+
data: Dictionary representation of an invigilator.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
An invigilator instance reconstructed from the dictionary.
|
212
|
+
|
213
|
+
Technical Notes:
|
214
|
+
- This method implements the inverse of to_dict
|
215
|
+
- Complex objects are reconstructed using their respective from_dict methods
|
216
|
+
- The mapping between attribute names and classes is defined in attributes_to_classes
|
217
|
+
- Special handling is provided for raw_model_response which is set after construction
|
218
|
+
- This method supports the persistence and restoration of invigilator state
|
219
|
+
"""
|
220
|
+
from ..agents import Agent
|
221
|
+
from ..questions import QuestionBase
|
222
|
+
from ..scenarios import Scenario
|
223
|
+
from ..surveys.memory import MemoryPlan
|
224
|
+
from ..language_models import LanguageModel
|
225
|
+
from ..surveys import Survey
|
226
|
+
from ..data import Cache
|
227
|
+
|
228
|
+
# Map attribute names to their corresponding classes
|
229
|
+
attributes_to_classes = {
|
230
|
+
"agent": Agent,
|
231
|
+
"question": QuestionBase,
|
232
|
+
"scenario": Scenario,
|
233
|
+
"model": LanguageModel,
|
234
|
+
"memory_plan": MemoryPlan,
|
235
|
+
"survey": Survey,
|
236
|
+
"cache": Cache,
|
237
|
+
}
|
238
|
+
|
239
|
+
# Reconstruct complex objects
|
240
|
+
d = {}
|
241
|
+
for attr, cls_ in attributes_to_classes.items():
|
242
|
+
if attr in data and data[attr] is not None:
|
243
|
+
if attr not in data:
|
244
|
+
d[attr] = {}
|
245
|
+
else:
|
246
|
+
d[attr] = cls_.from_dict(data[attr])
|
247
|
+
|
248
|
+
# Copy primitive data directly
|
249
|
+
d["current_answers"] = data["current_answers"]
|
250
|
+
d["iteration"] = data["iteration"]
|
251
|
+
d["additional_prompt_data"] = data["additional_prompt_data"]
|
252
|
+
|
253
|
+
# Create the invigilator instance
|
254
|
+
invigilator = cls(**d)
|
255
|
+
|
256
|
+
# Set raw_model_response after construction
|
257
|
+
invigilator.raw_model_response = data.get("raw_model_response")
|
258
|
+
return invigilator
|
259
|
+
|
260
|
+
def __repr__(self) -> str:
|
261
|
+
"""
|
262
|
+
Get a string representation of the Invigilator.
|
263
|
+
|
264
|
+
This method creates a detailed string representation of the invigilator
|
265
|
+
including all its major components. This is useful for debugging and logging.
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
A string representation of the invigilator.
|
269
|
+
|
270
|
+
Examples:
|
271
|
+
>>> InvigilatorBase.example().__repr__()
|
272
|
+
'InvigilatorExample(...)'
|
273
|
+
"""
|
274
|
+
return f"{self.__class__.__name__}(agent={repr(self.agent)}, question={repr(self.question)}, scenario={repr(self.scenario)}, model={repr(self.model)}, memory_plan={repr(self.memory_plan)}, current_answers={repr(self.current_answers)}, iteration={repr(self.iteration)}, additional_prompt_data={repr(self.additional_prompt_data)}, cache={repr(self.cache)})"
|
275
|
+
|
276
|
+
def get_failed_task_result(self, failure_reason: str) -> EDSLResultObjectInput:
|
277
|
+
"""
|
278
|
+
Create a result object for a failed question-answering task.
|
279
|
+
|
280
|
+
This method constructs a standardized result object that represents a failed
|
281
|
+
attempt to answer a question. The result includes the failure reason and
|
282
|
+
context information, allowing for consistent handling of failures throughout
|
283
|
+
the system.
|
284
|
+
|
285
|
+
Args:
|
286
|
+
failure_reason: Description of why the question-answering task failed.
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
An EDSLResultObjectInput representing the failed task.
|
290
|
+
|
291
|
+
Technical Notes:
|
292
|
+
- Used for both expected failures (e.g., skip logic) and unexpected errors
|
293
|
+
- Maintains a consistent structure for all results, regardless of success or failure
|
294
|
+
- Includes the question name and prompts for context and debugging
|
295
|
+
- Sets all response-related fields to None to indicate no response was obtained
|
296
|
+
"""
|
297
|
+
data = {
|
298
|
+
"answer": None,
|
299
|
+
"generated_tokens": None,
|
300
|
+
"comment": failure_reason,
|
301
|
+
"question_name": self.question.question_name,
|
302
|
+
"prompts": self.get_prompts(),
|
303
|
+
"cached_response": None,
|
304
|
+
"raw_model_response": None,
|
305
|
+
"cache_used": None,
|
306
|
+
"cache_key": None,
|
307
|
+
}
|
308
|
+
return EDSLResultObjectInput(**data)
|
309
|
+
|
310
|
+
def get_prompts(self) -> Dict[str, "Prompt"]:
|
311
|
+
"""
|
312
|
+
Get the prompts used by this invigilator.
|
313
|
+
|
314
|
+
This base implementation returns placeholder prompts. Subclasses should
|
315
|
+
override this method to provide the actual prompts used in question answering.
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
A dictionary mapping prompt types to Prompt objects.
|
319
|
+
|
320
|
+
Technical Notes:
|
321
|
+
- This is a fallback implementation that returns "NA" prompts
|
322
|
+
- Concrete invigilator implementations generate real prompts using the prompt_constructor
|
323
|
+
- The returned dictionary uses standardized keys like "user_prompt" and "system_prompt"
|
324
|
+
"""
|
325
|
+
from ..prompts import Prompt
|
326
|
+
|
327
|
+
return {
|
328
|
+
"user_prompt": Prompt("NA"),
|
329
|
+
"system_prompt": Prompt("NA"),
|
330
|
+
}
|
331
|
+
|
332
|
+
@abstractmethod
|
333
|
+
async def async_answer_question(self) -> Any:
|
334
|
+
"""
|
335
|
+
Asynchronously administer a question to an agent and get the response.
|
336
|
+
|
337
|
+
This abstract method must be implemented by concrete invigilator subclasses.
|
338
|
+
It should handle the entire process of administering a question to an agent
|
339
|
+
and processing the response.
|
340
|
+
|
341
|
+
Returns:
|
342
|
+
The processed response from the agent.
|
343
|
+
|
344
|
+
Technical Notes:
|
345
|
+
- This is an async method to support efficient concurrent execution
|
346
|
+
- Different invigilator types implement this differently:
|
347
|
+
- InvigilatorAI: Sends prompts to a language model
|
348
|
+
- InvigilatorHuman: Displays questions to a human user
|
349
|
+
- InvigilatorFunctional: Executes a function to generate responses
|
350
|
+
"""
|
351
|
+
pass
|
352
|
+
|
353
|
+
@jupyter_nb_handler
|
354
|
+
def answer_question(self) -> Coroutine:
|
355
|
+
"""
|
356
|
+
Get the answer to the question from the agent.
|
357
|
+
|
358
|
+
This method creates and returns a coroutine that, when awaited, will
|
359
|
+
administer the question to the agent and return the response. It
|
360
|
+
handles the async execution details and provides a convenient interface
|
361
|
+
for both async and sync contexts.
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
A coroutine that, when awaited, returns the agent's response.
|
365
|
+
|
366
|
+
Technical Notes:
|
367
|
+
- The @jupyter_nb_handler decorator enables this to work properly in Jupyter notebooks
|
368
|
+
- This method uses asyncio.gather to await the async_answer_question method
|
369
|
+
- It acts as a bridge between the async and sync parts of the system
|
370
|
+
- In synchronous contexts, this method can be called directly to get the response
|
371
|
+
"""
|
372
|
+
async def main():
|
373
|
+
"""Execute the question-answering process and return the result."""
|
374
|
+
results = await asyncio.gather(self.async_answer_question())
|
375
|
+
return results[0] # Since there's only one task, return its result
|
376
|
+
|
377
|
+
return main()
|
378
|
+
|
379
|
+
@classmethod
|
380
|
+
def example(
|
381
|
+
cls,
|
382
|
+
throw_an_exception: bool = False,
|
383
|
+
question: Optional["QuestionBase"] = None,
|
384
|
+
scenario: Optional["Scenario"] = None,
|
385
|
+
survey: Optional["Survey"] = None
|
386
|
+
) -> "InvigilatorBase":
|
387
|
+
"""
|
388
|
+
Create an example invigilator for testing and documentation.
|
389
|
+
|
390
|
+
This factory method creates a concrete implementation of the InvigilatorBase
|
391
|
+
with predefined components suitable for testing, examples, and documentation.
|
392
|
+
It supports customization through parameters and can be configured to simulate
|
393
|
+
error conditions.
|
394
|
+
|
395
|
+
Args:
|
396
|
+
throw_an_exception: If True, the model will raise an exception when called.
|
397
|
+
question: Custom question to use instead of the default.
|
398
|
+
scenario: Custom scenario to use instead of the default.
|
399
|
+
survey: Custom survey to use instead of the default.
|
400
|
+
|
401
|
+
Returns:
|
402
|
+
A concrete InvigilatorBase instance ready for testing.
|
403
|
+
|
404
|
+
Examples:
|
405
|
+
>>> # Basic example with default components
|
406
|
+
>>> InvigilatorBase.example()
|
407
|
+
InvigilatorExample(...)
|
408
|
+
|
409
|
+
>>> # Example answering a question with a canned response
|
410
|
+
>>> InvigilatorBase.example().answer_question()
|
411
|
+
{'message': [{'text': 'SPAM!'}], 'usage': {'prompt_tokens': 1, 'completion_tokens': 1}}
|
412
|
+
|
413
|
+
>>> # Example that simulates an error condition
|
414
|
+
>>> InvigilatorBase.example(throw_an_exception=True).answer_question()
|
415
|
+
Traceback (most recent call last):
|
416
|
+
...
|
417
|
+
Exception: This is a test error
|
418
|
+
|
419
|
+
Technical Notes:
|
420
|
+
- Creates an anonymous subclass (InvigilatorExample) with a simplified implementation
|
421
|
+
- Uses example() factory methods on other classes to create compatible components
|
422
|
+
- The canned_response in the model provides predictable output for testing
|
423
|
+
- The throw_exception parameter can be used to test error handling
|
424
|
+
"""
|
425
|
+
from ..agents import Agent
|
426
|
+
from ..scenarios import Scenario
|
427
|
+
from ..surveys.memory import MemoryPlan
|
428
|
+
from ..language_models import Model
|
429
|
+
from ..surveys import Survey
|
430
|
+
|
431
|
+
# Create a test model with predictable output
|
432
|
+
model = Model("test", canned_response="SPAM!")
|
433
|
+
|
434
|
+
# Configure the model to throw an exception if requested
|
435
|
+
if throw_an_exception:
|
436
|
+
model.throw_exception = True
|
437
|
+
|
438
|
+
# Create or use the provided components
|
439
|
+
agent = Agent.example()
|
440
|
+
survey = survey or Survey.example()
|
441
|
+
|
442
|
+
# Ensure the question is in the survey
|
443
|
+
if question is not None and question not in survey.questions:
|
444
|
+
survey.add_question(question)
|
445
|
+
|
446
|
+
# Get or create the remaining required components
|
447
|
+
question = question or survey.questions[0]
|
448
|
+
scenario = scenario or Scenario.example()
|
449
|
+
memory_plan = MemoryPlan(survey=survey)
|
450
|
+
current_answers = None
|
451
|
+
|
452
|
+
# Define a concrete implementation of the abstract class
|
453
|
+
class InvigilatorExample(cls):
|
454
|
+
"""An example invigilator implementation for testing."""
|
455
|
+
|
456
|
+
async def async_answer_question(self):
|
457
|
+
"""Implement the abstract method with simplified behavior."""
|
458
|
+
return await self.model.async_execute_model_call(
|
459
|
+
user_prompt="Hello", system_prompt="Hi"
|
460
|
+
)
|
461
|
+
|
462
|
+
# Create and return the example invigilator
|
463
|
+
return InvigilatorExample(
|
464
|
+
agent=agent,
|
465
|
+
question=question,
|
466
|
+
scenario=scenario,
|
467
|
+
survey=survey,
|
468
|
+
model=model,
|
469
|
+
memory_plan=memory_plan,
|
470
|
+
current_answers=current_answers,
|
471
|
+
)
|
472
|
+
|
473
|
+
|
474
|
+
if __name__ == "__main__":
|
475
|
+
import doctest
|
476
|
+
|
477
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|