edsl 0.1.14__py3-none-any.whl → 0.1.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/Base.py +348 -38
- edsl/BaseDiff.py +260 -0
- edsl/TemplateLoader.py +24 -0
- edsl/__init__.py +46 -10
- edsl/__version__.py +1 -0
- edsl/agents/Agent.py +842 -144
- edsl/agents/AgentList.py +521 -25
- edsl/agents/Invigilator.py +250 -374
- edsl/agents/InvigilatorBase.py +257 -0
- edsl/agents/PromptConstructor.py +272 -0
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/descriptors.py +43 -13
- edsl/agents/prompt_helpers.py +129 -0
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -0
- edsl/auto/StageBase.py +243 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +74 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +218 -0
- edsl/base/Base.py +279 -0
- edsl/config.py +121 -104
- edsl/conversation/Conversation.py +290 -0
- edsl/conversation/car_buying.py +59 -0
- edsl/conversation/chips.py +95 -0
- edsl/conversation/mug_negotiation.py +81 -0
- edsl/conversation/next_speaker_utilities.py +93 -0
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -0
- edsl/coop/__init__.py +1 -0
- edsl/coop/coop.py +1029 -134
- edsl/coop/utils.py +131 -0
- edsl/data/Cache.py +560 -89
- edsl/data/CacheEntry.py +230 -0
- edsl/data/CacheHandler.py +168 -0
- edsl/data/RemoteCacheSync.py +186 -0
- edsl/data/SQLiteDict.py +292 -0
- edsl/data/__init__.py +5 -3
- edsl/data/orm.py +6 -33
- edsl/data_transfer_models.py +74 -27
- edsl/enums.py +165 -8
- edsl/exceptions/BaseException.py +21 -0
- edsl/exceptions/__init__.py +52 -46
- edsl/exceptions/agents.py +33 -15
- edsl/exceptions/cache.py +5 -0
- edsl/exceptions/coop.py +8 -0
- edsl/exceptions/general.py +34 -0
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +15 -0
- edsl/exceptions/language_models.py +46 -1
- edsl/exceptions/questions.py +80 -5
- edsl/exceptions/results.py +16 -5
- edsl/exceptions/scenarios.py +29 -0
- edsl/exceptions/surveys.py +13 -10
- edsl/inference_services/AnthropicService.py +106 -0
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -0
- edsl/inference_services/AzureAI.py +215 -0
- edsl/inference_services/DeepInfraService.py +18 -0
- edsl/inference_services/GoogleService.py +143 -0
- edsl/inference_services/GroqService.py +20 -0
- edsl/inference_services/InferenceServiceABC.py +80 -0
- edsl/inference_services/InferenceServicesCollection.py +138 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OllamaService.py +18 -0
- edsl/inference_services/OpenAIService.py +236 -0
- edsl/inference_services/PerplexityService.py +160 -0
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -0
- edsl/inference_services/TogetherAIService.py +172 -0
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -0
- edsl/inference_services/rate_limits_cache.py +25 -0
- edsl/inference_services/registry.py +41 -0
- edsl/inference_services/write_available.py +10 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +21 -20
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +684 -204
- edsl/jobs/JobsChecks.py +172 -0
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -0
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -0
- edsl/jobs/buckets/ModelBuckets.py +65 -0
- edsl/jobs/buckets/TokenBucket.py +283 -0
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +392 -0
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
- edsl/jobs/interviews/InterviewStatistic.py +63 -0
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
- edsl/jobs/interviews/InterviewStatusLog.py +92 -0
- edsl/jobs/interviews/ReportErrors.py +66 -0
- edsl/jobs/interviews/interview_status_enum.py +9 -0
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
- edsl/jobs/runners/JobsRunnerStatus.py +298 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
- edsl/jobs/tasks/TaskCreators.py +64 -0
- edsl/jobs/tasks/TaskHistory.py +470 -0
- edsl/jobs/tasks/TaskStatusLog.py +23 -0
- edsl/jobs/tasks/task_status_enum.py +161 -0
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
- edsl/jobs/tokens/TokenUsage.py +34 -0
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +507 -386
- edsl/language_models/ModelList.py +164 -0
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
- edsl/language_models/__init__.py +1 -8
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +109 -41
- edsl/language_models/utilities.py +65 -0
- edsl/notebooks/Notebook.py +263 -0
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -0
- edsl/prompts/Prompt.py +222 -93
- edsl/prompts/__init__.py +1 -1
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -0
- edsl/questions/QuestionBasePromptsMixin.py +221 -0
- edsl/questions/QuestionBudget.py +164 -67
- edsl/questions/QuestionCheckBox.py +281 -62
- edsl/questions/QuestionDict.py +343 -0
- edsl/questions/QuestionExtract.py +136 -50
- edsl/questions/QuestionFreeText.py +79 -55
- edsl/questions/QuestionFunctional.py +138 -41
- edsl/questions/QuestionList.py +184 -57
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +293 -69
- edsl/questions/QuestionNumerical.py +109 -56
- edsl/questions/QuestionRank.py +244 -49
- edsl/questions/Quick.py +41 -0
- edsl/questions/SimpleAskMixin.py +74 -0
- edsl/questions/__init__.py +9 -6
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
- edsl/questions/compose_questions.py +13 -7
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +28 -26
- edsl/questions/derived/QuestionLinearScale.py +41 -28
- edsl/questions/derived/QuestionTopK.py +34 -26
- edsl/questions/derived/QuestionYesNo.py +40 -27
- edsl/questions/descriptors.py +228 -74
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_base_gen_mixin.py +168 -0
- edsl/questions/question_registry.py +130 -46
- edsl/questions/register_questions_meta.py +71 -0
- edsl/questions/response_validator_abc.py +188 -0
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +5 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +7 -0
- edsl/questions/templates/budget/question_presentation.jinja +7 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/dict/__init__.py +0 -0
- edsl/questions/templates/dict/answering_instructions.jinja +21 -0
- edsl/questions/templates/dict/question_presentation.jinja +1 -0
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/CSSParameterizer.py +108 -0
- edsl/results/Dataset.py +550 -19
- edsl/results/DatasetExportMixin.py +594 -0
- edsl/results/DatasetTree.py +295 -0
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +477 -173
- edsl/results/Results.py +987 -269
- edsl/results/ResultsExportMixin.py +28 -125
- edsl/results/ResultsGGMixin.py +83 -15
- edsl/results/TableDisplay.py +125 -0
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +1 -1
- edsl/results/file_exports.py +252 -0
- edsl/results/results_fetch_mixin.py +33 -0
- edsl/results/results_selector.py +145 -0
- edsl/results/results_tools_mixin.py +98 -0
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +78 -0
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +543 -0
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +431 -62
- edsl/scenarios/ScenarioHtmlMixin.py +65 -0
- edsl/scenarios/ScenarioList.py +1415 -45
- edsl/scenarios/ScenarioListExportMixin.py +45 -0
- edsl/scenarios/ScenarioListPdfMixin.py +239 -0
- edsl/scenarios/__init__.py +2 -0
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +49 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/scenario_join.py +131 -0
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -0
- edsl/study/ObjectEntry.py +173 -0
- edsl/study/ProofOfWork.py +113 -0
- edsl/study/SnapShot.py +80 -0
- edsl/study/Study.py +521 -0
- edsl/study/__init__.py +4 -0
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +92 -11
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +9 -4
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +156 -35
- edsl/surveys/Rule.py +221 -74
- edsl/surveys/RuleCollection.py +241 -61
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1079 -339
- edsl/surveys/SurveyCSS.py +273 -0
- edsl/surveys/SurveyExportMixin.py +235 -40
- edsl/surveys/SurveyFlowVisualization.py +181 -0
- edsl/surveys/SurveyQualtricsImport.py +284 -0
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +4 -2
- edsl/surveys/base.py +19 -3
- edsl/surveys/descriptors.py +17 -6
- edsl/surveys/instructions/ChangeInstruction.py +48 -0
- edsl/surveys/instructions/Instruction.py +56 -0
- edsl/surveys/instructions/InstructionCollection.py +82 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +116 -0
- edsl/templates/error_reporting/interviews.html +19 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- edsl/tools/__init__.py +1 -0
- edsl/tools/clusters.py +192 -0
- edsl/tools/embeddings.py +27 -0
- edsl/tools/embeddings_plotting.py +118 -0
- edsl/tools/plotting.py +112 -0
- edsl/tools/summarize.py +18 -0
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +5 -0
- edsl/utilities/__init__.py +21 -20
- edsl/utilities/ast_utilities.py +3 -0
- edsl/utilities/data/Registry.py +2 -0
- edsl/utilities/decorators.py +41 -0
- edsl/utilities/gcp_bucket/__init__.py +0 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
- edsl/utilities/interface.py +310 -60
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -0
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -0
- edsl/utilities/restricted_python.py +70 -0
- edsl/utilities/utilities.py +203 -13
- edsl-0.1.40.dist-info/METADATA +111 -0
- edsl-0.1.40.dist-info/RECORD +362 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
- edsl/agents/AgentListExportMixin.py +0 -24
- edsl/coop/old.py +0 -31
- edsl/data/Database.py +0 -141
- edsl/data/crud.py +0 -121
- edsl/jobs/Interview.py +0 -417
- edsl/jobs/JobsRunner.py +0 -63
- edsl/jobs/JobsRunnerStatusMixin.py +0 -115
- edsl/jobs/base.py +0 -47
- edsl/jobs/buckets.py +0 -166
- edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
- edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
- edsl/jobs/task_management.py +0 -218
- edsl/jobs/token_tracking.py +0 -78
- edsl/language_models/DeepInfra.py +0 -69
- edsl/language_models/OpenAI.py +0 -98
- edsl/language_models/model_interfaces/GeminiPro.py +0 -66
- edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
- edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
- edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
- edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
- edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
- edsl/language_models/registry.py +0 -81
- edsl/language_models/schemas.py +0 -15
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/prompts/QuestionInstructionsBase.py +0 -6
- edsl/prompts/library/agent_instructions.py +0 -29
- edsl/prompts/library/agent_persona.py +0 -17
- edsl/prompts/library/question_budget.py +0 -26
- edsl/prompts/library/question_checkbox.py +0 -32
- edsl/prompts/library/question_extract.py +0 -19
- edsl/prompts/library/question_freetext.py +0 -14
- edsl/prompts/library/question_linear_scale.py +0 -20
- edsl/prompts/library/question_list.py +0 -22
- edsl/prompts/library/question_multiple_choice.py +0 -44
- edsl/prompts/library/question_numerical.py +0 -31
- edsl/prompts/library/question_rank.py +0 -21
- edsl/prompts/prompt_config.py +0 -33
- edsl/prompts/registry.py +0 -185
- edsl/questions/Question.py +0 -240
- edsl/report/InputOutputDataTypes.py +0 -134
- edsl/report/RegressionMixin.py +0 -28
- edsl/report/ReportOutputs.py +0 -1228
- edsl/report/ResultsFetchMixin.py +0 -106
- edsl/report/ResultsOutputMixin.py +0 -14
- edsl/report/demo.ipynb +0 -645
- edsl/results/ResultsDBMixin.py +0 -184
- edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
- edsl/trackers/Tracker.py +0 -91
- edsl/trackers/TrackerAPI.py +0 -196
- edsl/trackers/TrackerTasks.py +0 -70
- edsl/utilities/pastebin.py +0 -141
- edsl-0.1.14.dist-info/METADATA +0 -69
- edsl-0.1.14.dist-info/RECORD +0 -141
- /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
- /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
- /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
- {edsl-0.1.14.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
edsl/agents/Agent.py
CHANGED
@@ -1,67 +1,84 @@
|
|
1
|
+
"""An Agent is an AI agent that can reference a set of traits in answering questions."""
|
2
|
+
|
1
3
|
from __future__ import annotations
|
2
4
|
import copy
|
3
5
|
import inspect
|
4
6
|
import types
|
5
|
-
import
|
6
|
-
|
7
|
+
from typing import (
|
8
|
+
Callable,
|
9
|
+
Optional,
|
10
|
+
Union,
|
11
|
+
Any,
|
12
|
+
TYPE_CHECKING,
|
13
|
+
Protocol,
|
14
|
+
runtime_checkable,
|
15
|
+
TypeVar,
|
16
|
+
)
|
17
|
+
from contextlib import contextmanager
|
18
|
+
from dataclasses import dataclass
|
19
|
+
|
20
|
+
# Type variable for the Agent class
|
21
|
+
A = TypeVar("A", bound="Agent")
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from edsl.data.Cache import Cache
|
25
|
+
from edsl.surveys.Survey import Survey
|
26
|
+
from edsl.scenarios.Scenario import Scenario
|
27
|
+
from edsl.language_models import LanguageModel
|
28
|
+
from edsl.surveys.MemoryPlan import MemoryPlan
|
29
|
+
from edsl.questions import QuestionBase
|
30
|
+
from edsl.agents.Invigilator import InvigilatorBase
|
31
|
+
from edsl.prompts import Prompt
|
32
|
+
from edsl.questions.QuestionBase import QuestionBase
|
33
|
+
from edsl.scenarios.Scenario import Scenario
|
34
|
+
|
7
35
|
|
8
|
-
|
36
|
+
@runtime_checkable
|
37
|
+
class DirectAnswerMethod(Protocol):
|
38
|
+
"""Protocol defining the required signature for direct answer methods."""
|
9
39
|
|
10
|
-
|
11
|
-
|
40
|
+
def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
|
41
|
+
|
42
|
+
|
43
|
+
from uuid import uuid4
|
12
44
|
|
13
45
|
from edsl.Base import Base
|
46
|
+
from edsl.exceptions.questions import QuestionScenarioRenderError
|
14
47
|
|
15
48
|
from edsl.exceptions.agents import (
|
49
|
+
AgentErrors,
|
16
50
|
AgentCombinationError,
|
17
51
|
AgentDirectAnswerFunctionError,
|
18
52
|
AgentDynamicTraitsFunctionError,
|
19
53
|
)
|
20
54
|
|
21
|
-
from edsl.agents.Invigilator import (
|
22
|
-
InvigilatorDebug,
|
23
|
-
InvigilatorHuman,
|
24
|
-
InvigilatorFunctional,
|
25
|
-
InvigilatorAI,
|
26
|
-
)
|
27
|
-
|
28
|
-
from edsl.language_models.registry import Model
|
29
|
-
from edsl.scenarios import Scenario
|
30
|
-
from edsl.enums import LanguageModelType
|
31
|
-
|
32
|
-
# from edsl.utilities import (
|
33
|
-
# dict_to_html,
|
34
|
-
# print_dict_as_html_table,
|
35
|
-
# print_dict_with_rich,
|
36
|
-
# )
|
37
|
-
|
38
55
|
from edsl.agents.descriptors import (
|
39
56
|
TraitsDescriptor,
|
40
57
|
CodebookDescriptor,
|
41
58
|
InstructionDescriptor,
|
42
59
|
NameDescriptor,
|
43
60
|
)
|
44
|
-
|
45
|
-
|
46
|
-
|
61
|
+
from edsl.utilities.decorators import (
|
62
|
+
sync_wrapper,
|
63
|
+
)
|
64
|
+
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
47
65
|
from edsl.data_transfer_models import AgentResponseDict
|
66
|
+
from edsl.utilities.restricted_python import create_restricted_function
|
48
67
|
|
49
|
-
from edsl.
|
68
|
+
from edsl.scenarios.Scenario import Scenario
|
50
69
|
|
51
70
|
|
52
|
-
class
|
53
|
-
"""
|
71
|
+
class AgentTraits(Scenario):
|
72
|
+
"""A class representing the traits of an agent."""
|
54
73
|
|
55
|
-
|
56
|
-
|
57
|
-
traits : dict, optional - A dictionary of traits that the agent has. The keys need to be
|
58
|
-
valid python variable names. The values can be any python object that has a valid __str__ method.
|
59
|
-
codebook : dict, optional - A codebook mapping trait keys to trait descriptions.
|
60
|
-
instruction : str, optional - Instructions for the agent.
|
74
|
+
def __repr__(self):
|
75
|
+
return f"{self.data}"
|
61
76
|
|
62
|
-
dynamic_traits_function : Callable, optional - A function that returns a dictionary of traits.
|
63
77
|
|
64
|
-
|
78
|
+
class Agent(Base):
|
79
|
+
"""An class representing an agent that can answer questions."""
|
80
|
+
|
81
|
+
__documentation__ = "https://docs.expectedparrot.com/en/latest/agents.html"
|
65
82
|
|
66
83
|
default_instruction = """You are answering questions as if you were a human. Do not break character."""
|
67
84
|
|
@@ -69,36 +86,250 @@ class Agent(Base):
|
|
69
86
|
codebook = CodebookDescriptor()
|
70
87
|
instruction = InstructionDescriptor()
|
71
88
|
name = NameDescriptor()
|
89
|
+
dynamic_traits_function_name = ""
|
90
|
+
answer_question_directly_function_name = ""
|
91
|
+
has_dynamic_traits_function = False
|
72
92
|
|
73
93
|
def __init__(
|
74
94
|
self,
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
95
|
+
traits: Optional[dict] = None,
|
96
|
+
name: Optional[str] = None,
|
97
|
+
codebook: Optional[dict] = None,
|
98
|
+
instruction: Optional[str] = None,
|
99
|
+
traits_presentation_template: Optional[str] = None,
|
100
|
+
dynamic_traits_function: Optional[Callable] = None,
|
101
|
+
dynamic_traits_function_source_code: Optional[str] = None,
|
102
|
+
dynamic_traits_function_name: Optional[str] = None,
|
103
|
+
answer_question_directly_source_code: Optional[str] = None,
|
104
|
+
answer_question_directly_function_name: Optional[str] = None,
|
82
105
|
):
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
106
|
+
"""Initialize a new instance of Agent.
|
107
|
+
|
108
|
+
:param traits: A dictionary of traits that the agent has. The keys need to be valid identifiers.
|
109
|
+
:param name: A name for the agent
|
110
|
+
:param codebook: A codebook mapping trait keys to trait descriptions.
|
111
|
+
:param instruction: Instructions for the agent in how to answer questions.
|
112
|
+
:param trait_presentation_template: A template for how to present the agent's traits.
|
113
|
+
:param dynamic_traits_function: A function that returns a dictionary of traits.
|
114
|
+
:param dynamic_traits_function_source_code: The source code for the dynamic traits function.
|
115
|
+
:param dynamic_traits_function_name: The name of the dynamic traits function.
|
116
|
+
|
117
|
+
The `traits` parameter is a dictionary of traits that the agent has.
|
118
|
+
These traits are used to construct a prompt that is presented to the LLM.
|
119
|
+
In the absence of a `traits_presentation_template`, the default is used.
|
120
|
+
This is a template that is used to present the agent's traits to the LLM.
|
121
|
+
See :py:class:`edsl.prompts.library.agent_persona.AgentPersona` for more information.
|
122
|
+
|
123
|
+
Example usage:
|
124
|
+
|
125
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
126
|
+
>>> a.traits
|
127
|
+
{'age': 10, 'hair': 'brown', 'height': 5.5}
|
128
|
+
|
129
|
+
These traits are used to construct a prompt that is presented to the LLM.
|
130
|
+
|
131
|
+
In the absence of a `traits_presentation_template`, the default is used.
|
132
|
+
|
133
|
+
>>> a = Agent(traits = {"age": 10}, traits_presentation_template = "I am a {{age}} year old.")
|
134
|
+
>>> repr(a.agent_persona)
|
135
|
+
'Prompt(text=\"""I am a {{age}} year old.\""")'
|
136
|
+
|
137
|
+
When this is rendered for presentation to the LLM, it will replace the `{{age}}` with the actual age.
|
138
|
+
it is also possible to use the `codebook` to provide a more human-readable description of the trait.
|
139
|
+
Here is an example where we give a prefix to the age trait (namely the age):
|
140
|
+
|
141
|
+
>>> traits = {"age": 10, "hair": "brown", "height": 5.5}
|
142
|
+
>>> codebook = {'age': 'Their age is'}
|
143
|
+
>>> a = Agent(traits = traits, codebook = codebook, traits_presentation_template = "This agent is Dave. {{codebook['age']}} {{age}}")
|
144
|
+
>>> d = a.traits | {'codebook': a.codebook}
|
145
|
+
>>> a.agent_persona.render(d)
|
146
|
+
Prompt(text=\"""This agent is Dave. Their age is 10\""")
|
147
|
+
|
148
|
+
Instructions
|
149
|
+
------------
|
150
|
+
The agent can also have instructions. These are instructions that are given to the agent when answering questions.
|
151
|
+
|
152
|
+
>>> Agent.default_instruction
|
153
|
+
'You are answering questions as if you were a human. Do not break character.'
|
154
|
+
|
155
|
+
See see how these are used to actually construct the prompt that is presented to the LLM, see :py:class:`edsl.agents.Invigilator.InvigilatorBase`.
|
156
|
+
|
157
|
+
"""
|
158
|
+
self._initialize_basic_attributes(traits, name, codebook)
|
159
|
+
self._initialize_instruction(instruction)
|
160
|
+
self._initialize_dynamic_traits_function(
|
161
|
+
dynamic_traits_function,
|
162
|
+
dynamic_traits_function_source_code,
|
163
|
+
dynamic_traits_function_name,
|
164
|
+
)
|
165
|
+
self._initialize_answer_question_directly(
|
166
|
+
answer_question_directly_source_code, answer_question_directly_function_name
|
167
|
+
)
|
88
168
|
self._check_dynamic_traits_function()
|
169
|
+
self._initialize_traits_presentation_template(traits_presentation_template)
|
89
170
|
self.current_question = None
|
90
171
|
|
91
|
-
|
92
|
-
|
93
|
-
|
172
|
+
def _initialize_basic_attributes(self, traits, name, codebook) -> None:
|
173
|
+
"""Initialize the basic attributes of the agent."""
|
174
|
+
self.name = name
|
175
|
+
self._traits = AgentTraits(traits or dict())
|
176
|
+
self.codebook = codebook or dict()
|
177
|
+
|
178
|
+
def _initialize_instruction(self, instruction) -> None:
|
179
|
+
"""Initialize the instruction for the agent i.e., how the agent should answer questions."""
|
180
|
+
if instruction is None:
|
181
|
+
self.instruction = self.default_instruction
|
182
|
+
self._instruction = self.default_instruction
|
183
|
+
self.set_instructions = False
|
184
|
+
else:
|
185
|
+
self.instruction = instruction
|
186
|
+
self._instruction = instruction
|
187
|
+
self.set_instructions = True
|
188
|
+
|
189
|
+
def _initialize_traits_presentation_template(
|
190
|
+
self, traits_presentation_template
|
191
|
+
) -> None:
|
192
|
+
"""Initialize the traits presentation template. How the agent's traits are presented to the LLM."""
|
193
|
+
if traits_presentation_template is not None:
|
194
|
+
self._traits_presentation_template = traits_presentation_template
|
195
|
+
self.traits_presentation_template = traits_presentation_template
|
196
|
+
self.set_traits_presentation_template = True
|
197
|
+
else:
|
198
|
+
self.traits_presentation_template = "Your traits: {{traits}}"
|
199
|
+
self.set_traits_presentation_template = False
|
200
|
+
|
201
|
+
def _initialize_dynamic_traits_function(
|
202
|
+
self,
|
203
|
+
dynamic_traits_function,
|
204
|
+
dynamic_traits_function_source_code,
|
205
|
+
dynamic_traits_function_name,
|
206
|
+
) -> None:
|
207
|
+
"""Initialize the dynamic traits function i.e., a function that returns a dictionary of traits based on the question."""
|
208
|
+
# Deal with dynamic traits function
|
209
|
+
self.dynamic_traits_function = dynamic_traits_function
|
94
210
|
|
95
|
-
def _check_dynamic_traits_function(self):
|
96
211
|
if self.dynamic_traits_function:
|
212
|
+
self.dynamic_traits_function_name = self.dynamic_traits_function.__name__
|
213
|
+
self.has_dynamic_traits_function = True
|
214
|
+
else:
|
215
|
+
self.has_dynamic_traits_function = False
|
216
|
+
|
217
|
+
if dynamic_traits_function_source_code:
|
218
|
+
self.dynamic_traits_function_name = dynamic_traits_function_name
|
219
|
+
self.dynamic_traits_function = create_restricted_function(
|
220
|
+
dynamic_traits_function_name, dynamic_traits_function
|
221
|
+
)
|
222
|
+
|
223
|
+
def _initialize_answer_question_directly(
|
224
|
+
self,
|
225
|
+
answer_question_directly_source_code,
|
226
|
+
answer_question_directly_function_name,
|
227
|
+
) -> None:
|
228
|
+
if answer_question_directly_source_code:
|
229
|
+
self.answer_question_directly_function_name = (
|
230
|
+
answer_question_directly_function_name
|
231
|
+
)
|
232
|
+
protected_method = create_restricted_function(
|
233
|
+
answer_question_directly_function_name,
|
234
|
+
answer_question_directly_source_code,
|
235
|
+
)
|
236
|
+
bound_method = types.MethodType(protected_method, self)
|
237
|
+
setattr(self, "answer_question_directly", bound_method)
|
238
|
+
|
239
|
+
def _initialize_traits_presentation_template(
|
240
|
+
self, traits_presentation_template
|
241
|
+
) -> None:
|
242
|
+
if traits_presentation_template is not None:
|
243
|
+
self._traits_presentation_template = traits_presentation_template
|
244
|
+
self.traits_presentation_template = traits_presentation_template
|
245
|
+
self.set_traits_presentation_template = True
|
246
|
+
else:
|
247
|
+
self.traits_presentation_template = "Your traits: {{traits}}"
|
248
|
+
self.set_traits_presentation_template = False
|
249
|
+
|
250
|
+
def duplicate(self) -> Agent:
|
251
|
+
"""Return a duplicate of the agent.
|
252
|
+
|
253
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
|
254
|
+
>>> a2 = a.duplicate()
|
255
|
+
>>> a2 == a
|
256
|
+
True
|
257
|
+
>>> id(a) == id(a2)
|
258
|
+
False
|
259
|
+
>>> def f(self, question, scenario): return "I am a direct answer."
|
260
|
+
>>> a.add_direct_question_answering_method(f)
|
261
|
+
>>> hasattr(a, "answer_question_directly")
|
262
|
+
True
|
263
|
+
>>> a2 = a.duplicate()
|
264
|
+
>>> a2.answer_question_directly(None, None)
|
265
|
+
'I am a direct answer.'
|
266
|
+
|
267
|
+
>>> a = Agent(traits = {'age': 10}, instruction = "Have fun!")
|
268
|
+
>>> a2 = a.duplicate()
|
269
|
+
>>> a2.instruction
|
270
|
+
'Have fun!'
|
271
|
+
"""
|
272
|
+
new_agent = Agent.from_dict(self.to_dict())
|
273
|
+
if hasattr(self, "answer_question_directly"):
|
274
|
+
answer_question_directly = self.answer_question_directly
|
275
|
+
newf = lambda self, question, scenario: answer_question_directly(
|
276
|
+
question, scenario
|
277
|
+
)
|
278
|
+
new_agent.add_direct_question_answering_method(newf)
|
279
|
+
if hasattr(self, "dynamic_traits_function"):
|
280
|
+
dynamic_traits_function = self.dynamic_traits_function
|
281
|
+
new_agent.dynamic_traits_function = dynamic_traits_function
|
282
|
+
return new_agent
|
283
|
+
|
284
|
+
@property
|
285
|
+
def agent_persona(self) -> Prompt:
|
286
|
+
"""Return the agent persona template."""
|
287
|
+
from edsl.prompts.Prompt import Prompt
|
288
|
+
|
289
|
+
return Prompt(text=self.traits_presentation_template)
|
290
|
+
|
291
|
+
def prompt(self) -> str:
|
292
|
+
"""Return the prompt for the agent.
|
293
|
+
|
294
|
+
Example usage:
|
295
|
+
|
296
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
297
|
+
>>> a.prompt()
|
298
|
+
Prompt(text=\"""Your traits: {'age': 10, 'hair': 'brown', 'height': 5.5}\""")
|
299
|
+
"""
|
300
|
+
replacement_dict = (
|
301
|
+
self.traits | {"traits": self.traits} | {"codebook": self.codebook}
|
302
|
+
)
|
303
|
+
if undefined := self.agent_persona.undefined_template_variables(
|
304
|
+
replacement_dict
|
305
|
+
):
|
306
|
+
raise QuestionScenarioRenderError(
|
307
|
+
f"Agent persona still has variables that were not rendered: {undefined}"
|
308
|
+
)
|
309
|
+
else:
|
310
|
+
return self.agent_persona.render(replacement_dict)
|
311
|
+
|
312
|
+
def _check_dynamic_traits_function(self) -> None:
|
313
|
+
"""Check whether dynamic trait function is valid.
|
314
|
+
|
315
|
+
This checks whether the dynamic traits function is valid.
|
316
|
+
|
317
|
+
>>> def f(question): return {"age": 10, "hair": "brown", "height": 5.5}
|
318
|
+
>>> a = Agent(dynamic_traits_function = f)
|
319
|
+
>>> a._check_dynamic_traits_function()
|
320
|
+
|
321
|
+
>>> def g(question, poo): return {"age": 10, "hair": "brown", "height": 5.5}
|
322
|
+
>>> a = Agent(dynamic_traits_function = g)
|
323
|
+
Traceback (most recent call last):
|
324
|
+
...
|
325
|
+
edsl.exceptions.agents.AgentDynamicTraitsFunctionError: ...
|
326
|
+
"""
|
327
|
+
if self.has_dynamic_traits_function:
|
97
328
|
sig = inspect.signature(self.dynamic_traits_function)
|
98
329
|
if "question" in sig.parameters:
|
99
330
|
if len(sig.parameters) > 1:
|
100
331
|
raise AgentDynamicTraitsFunctionError(
|
101
|
-
f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
|
332
|
+
message=f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
|
102
333
|
)
|
103
334
|
else:
|
104
335
|
if len(sig.parameters) > 0:
|
@@ -109,35 +340,196 @@ class Agent(Base):
|
|
109
340
|
|
110
341
|
@property
|
111
342
|
def traits(self) -> dict[str, str]:
|
112
|
-
"""
|
343
|
+
"""An agent's traits, which is a dictionary.
|
113
344
|
|
114
|
-
|
115
|
-
|
116
|
-
|
345
|
+
The agent could have a a dynamic traits function (`dynamic_traits_function`) that returns a dictionary of traits
|
346
|
+
when called. This function can also take a `question` as an argument.
|
347
|
+
If so, the dynamic traits function is called and the result is returned.
|
348
|
+
Otherwise, the traits are returned.
|
117
349
|
|
118
|
-
|
119
|
-
|
120
|
-
|
350
|
+
Example:
|
351
|
+
|
352
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
353
|
+
>>> a.traits
|
121
354
|
{'age': 10, 'hair': 'brown', 'height': 5.5}
|
122
355
|
|
123
|
-
The agent could have a a dynamic traits function (dynamic_traits_function) that returns a dictionary of traits
|
124
|
-
when called. This function can also take a question as an argument.
|
125
|
-
If so, the dynamic traits function is called and the result is returned.
|
126
|
-
Otherwise, the traits are returned.
|
127
356
|
"""
|
128
|
-
if self.
|
357
|
+
if self.has_dynamic_traits_function:
|
129
358
|
sig = inspect.signature(self.dynamic_traits_function)
|
130
359
|
if "question" in sig.parameters:
|
131
360
|
return self.dynamic_traits_function(question=self.current_question)
|
132
361
|
else:
|
133
362
|
return self.dynamic_traits_function()
|
134
363
|
else:
|
135
|
-
return self._traits
|
364
|
+
return dict(self._traits)
|
365
|
+
|
366
|
+
@contextmanager
|
367
|
+
def modify_traits_context(self):
|
368
|
+
self._check_before_modifying_traits()
|
369
|
+
try:
|
370
|
+
yield
|
371
|
+
finally:
|
372
|
+
self._traits = AgentTraits(self._traits)
|
373
|
+
|
374
|
+
def _check_before_modifying_traits(self):
|
375
|
+
"""Check before modifying traits."""
|
376
|
+
if self.has_dynamic_traits_function:
|
377
|
+
raise AgentErrors(
|
378
|
+
"You cannot modify the traits of an agent that has a dynamic traits function.",
|
379
|
+
"If you want to modify the traits, you should remove the dynamic traits function.",
|
380
|
+
)
|
381
|
+
|
382
|
+
@traits.setter
|
383
|
+
def traits(self, traits: dict[str, str]):
|
384
|
+
with self.modify_traits_context():
|
385
|
+
self._traits = traits
|
386
|
+
# self._check_before_modifying_traits()
|
387
|
+
# self._traits = AgentTraits(traits)
|
388
|
+
|
389
|
+
def rename(
|
390
|
+
self,
|
391
|
+
old_name_or_dict: Union[str, dict[str, str]],
|
392
|
+
new_name: Optional[str] = None,
|
393
|
+
) -> Agent:
|
394
|
+
"""Rename a trait.
|
395
|
+
|
396
|
+
:param old_name_or_dict: The old name of the trait or a dictionary of old names and new names.
|
397
|
+
:param new_name: The new name of the trait.
|
398
|
+
|
399
|
+
Example usage:
|
400
|
+
|
401
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
402
|
+
>>> newa = a.rename("age", "years")
|
403
|
+
>>> newa == Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
|
404
|
+
True
|
405
|
+
|
406
|
+
>>> newa.rename({'years': 'smage'}) == Agent(traits = {'smage': 10, 'hair': 'brown', 'height': 5.5})
|
407
|
+
True
|
408
|
+
|
409
|
+
"""
|
410
|
+
self._check_before_modifying_traits()
|
411
|
+
if isinstance(old_name_or_dict, dict) and new_name:
|
412
|
+
raise AgentErrors(
|
413
|
+
f"You passed a dict: {old_name_or_dict} and a new name: {new_name}. You should pass only a dict."
|
414
|
+
)
|
415
|
+
|
416
|
+
if isinstance(old_name_or_dict, dict) and new_name is None:
|
417
|
+
return self._rename_dict(old_name_or_dict)
|
418
|
+
|
419
|
+
if isinstance(old_name_or_dict, str):
|
420
|
+
return self._rename(old_name_or_dict, new_name)
|
421
|
+
|
422
|
+
raise AgentErrors("Something is not right with Agent renaming")
|
423
|
+
|
424
|
+
def _rename_dict(self, renaming_dict: dict[str, str]) -> Agent:
|
425
|
+
"""
|
426
|
+
Internal method to rename traits using a dictionary.
|
427
|
+
The keys should all be old names and the values should all be new names.
|
428
|
+
|
429
|
+
Example usage:
|
430
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
431
|
+
>>> a._rename_dict({"age": "years", "height": "feet"})
|
432
|
+
Agent(traits = {'years': 10, 'hair': 'brown', 'feet': 5.5})
|
433
|
+
|
434
|
+
"""
|
435
|
+
try:
|
436
|
+
assert all(k in self.traits for k in renaming_dict.keys())
|
437
|
+
except AssertionError:
|
438
|
+
raise AgentErrors(
|
439
|
+
f"The trait(s) {set(renaming_dict.keys()) - set(self.traits.keys())} do not exist in the agent's traits, which are {self.traits}."
|
440
|
+
)
|
441
|
+
new_agent = self.duplicate()
|
442
|
+
new_agent.traits = {renaming_dict.get(k, k): v for k, v in self.traits.items()}
|
443
|
+
return new_agent
|
444
|
+
|
445
|
+
def _rename(self, old_name: str, new_name: str) -> Agent:
|
446
|
+
"""Rename a trait.
|
447
|
+
|
448
|
+
Example usage:
|
449
|
+
|
450
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
451
|
+
>>> a._rename(old_name="age", new_name="years")
|
452
|
+
Agent(traits = {'years': 10, 'hair': 'brown', 'height': 5.5})
|
453
|
+
|
454
|
+
"""
|
455
|
+
try:
|
456
|
+
assert old_name in self.traits
|
457
|
+
except AssertionError:
|
458
|
+
raise AgentErrors(
|
459
|
+
f"The trait '{old_name}' does not exist in the agent's traits, which are {self.traits}."
|
460
|
+
)
|
461
|
+
newagent = self.duplicate()
|
462
|
+
newagent.traits = {
|
463
|
+
new_name if k == old_name else k: v for k, v in self.traits.items()
|
464
|
+
}
|
465
|
+
newagent.codebook = {
|
466
|
+
new_name if k == old_name else k: v for k, v in self.codebook.items()
|
467
|
+
}
|
468
|
+
return newagent
|
469
|
+
|
470
|
+
def __getitem__(self, key):
|
471
|
+
"""Allow for accessing traits using the bracket notation.
|
472
|
+
|
473
|
+
Example:
|
474
|
+
|
475
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
476
|
+
>>> a['traits']['age']
|
477
|
+
10
|
478
|
+
|
479
|
+
"""
|
480
|
+
return getattr(self, key)
|
481
|
+
|
482
|
+
def remove_direct_question_answering_method(self) -> None:
|
483
|
+
"""Remove the direct question answering method.
|
136
484
|
|
137
|
-
|
138
|
-
|
485
|
+
Example usage:
|
486
|
+
|
487
|
+
>>> a = Agent()
|
488
|
+
>>> def f(self, question, scenario): return "I am a direct answer."
|
489
|
+
>>> a.add_direct_question_answering_method(f)
|
490
|
+
>>> a.remove_direct_question_answering_method()
|
491
|
+
>>> hasattr(a, "answer_question_directly")
|
492
|
+
False
|
493
|
+
"""
|
494
|
+
if hasattr(self, "answer_question_directly"):
|
495
|
+
delattr(self, "answer_question_directly")
|
496
|
+
|
497
|
+
def add_direct_question_answering_method(
|
498
|
+
self,
|
499
|
+
method: DirectAnswerMethod,
|
500
|
+
validate_response: bool = False,
|
501
|
+
translate_response: bool = False,
|
502
|
+
) -> None:
|
503
|
+
"""Add a method to the agent that can answer a particular question type.
|
504
|
+
https://docs.expectedparrot.com/en/latest/agents.html#agent-direct-answering-methods
|
505
|
+
|
506
|
+
:param method: A method that can answer a question directly.
|
507
|
+
:param validate_response: Whether to validate the response.
|
508
|
+
:param translate_response: Whether to translate the response.
|
509
|
+
|
510
|
+
Example usage:
|
511
|
+
|
512
|
+
>>> a = Agent()
|
513
|
+
>>> def f(self, question, scenario): return "I am a direct answer."
|
514
|
+
>>> a.add_direct_question_answering_method(f)
|
515
|
+
>>> a.answer_question_directly(question = None, scenario = None)
|
516
|
+
'I am a direct answer.'
|
517
|
+
"""
|
139
518
|
if hasattr(self, "answer_question_directly"):
|
140
|
-
|
519
|
+
import warnings
|
520
|
+
|
521
|
+
warnings.warn(
|
522
|
+
"Warning: overwriting existing answer_question_directly method"
|
523
|
+
)
|
524
|
+
|
525
|
+
self.validate_response = validate_response
|
526
|
+
self.translate_response = translate_response
|
527
|
+
|
528
|
+
# if not isinstance(method, DirectAnswerMethod):
|
529
|
+
# raise AgentDirectAnswerFunctionError(
|
530
|
+
# f"Method {method} does not match required signature. "
|
531
|
+
# "Must take (self, question, scenario) parameters."
|
532
|
+
# )
|
141
533
|
|
142
534
|
signature = inspect.signature(method)
|
143
535
|
for argument in ["question", "scenario", "self"]:
|
@@ -147,93 +539,215 @@ class Agent(Base):
|
|
147
539
|
)
|
148
540
|
bound_method = types.MethodType(method, self)
|
149
541
|
setattr(self, "answer_question_directly", bound_method)
|
542
|
+
self.answer_question_directly_function_name = bound_method.__name__
|
150
543
|
|
151
544
|
def create_invigilator(
|
152
545
|
self,
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
546
|
+
*,
|
547
|
+
question: "QuestionBase",
|
548
|
+
cache: "Cache",
|
549
|
+
survey: Optional["Survey"] = None,
|
550
|
+
scenario: Optional["Scenario"] = None,
|
551
|
+
model: Optional["LanguageModel"] = None,
|
552
|
+
memory_plan: Optional["MemoryPlan"] = None,
|
158
553
|
current_answers: Optional[dict] = None,
|
159
|
-
|
160
|
-
|
161
|
-
|
554
|
+
iteration: int = 1,
|
555
|
+
raise_validation_errors: bool = True,
|
556
|
+
key_lookup: Optional["KeyLookup"] = None,
|
557
|
+
) -> "InvigilatorBase":
|
558
|
+
"""Create an Invigilator.
|
559
|
+
|
560
|
+
An invigilator is an object that is responsible for administering a question to an agent.
|
561
|
+
There are several different types of invigilators, depending on the type of question and the agent.
|
562
|
+
For example, there are invigilators for functional questions (i.e., question is of type :class:`edsl.questions.QuestionFunctional`:), for direct questions, and for LLM questions.
|
563
|
+
|
564
|
+
>>> a = Agent(traits = {})
|
565
|
+
>>> a.create_invigilator(question = None, cache = False)
|
566
|
+
InvigilatorAI(...)
|
567
|
+
|
568
|
+
An invigator is an object that is responsible for administering a question to an agent and
|
162
569
|
recording the responses.
|
163
570
|
"""
|
571
|
+
from edsl.language_models.model import Model
|
572
|
+
|
573
|
+
from edsl.scenarios.Scenario import Scenario
|
574
|
+
|
575
|
+
cache = cache
|
164
576
|
self.current_question = question
|
165
|
-
model = model or Model(
|
577
|
+
model = model or Model()
|
166
578
|
scenario = scenario or Scenario()
|
167
579
|
invigilator = self._create_invigilator(
|
168
|
-
question,
|
580
|
+
question=question,
|
581
|
+
scenario=scenario,
|
582
|
+
survey=survey,
|
583
|
+
model=model,
|
584
|
+
memory_plan=memory_plan,
|
585
|
+
current_answers=current_answers,
|
586
|
+
iteration=iteration,
|
587
|
+
cache=cache,
|
588
|
+
raise_validation_errors=raise_validation_errors,
|
589
|
+
key_lookup=key_lookup,
|
169
590
|
)
|
591
|
+
if hasattr(self, "validate_response"):
|
592
|
+
invigilator.validate_response = self.validate_response
|
593
|
+
if hasattr(self, "translate_response"):
|
594
|
+
invigilator.translate_response = self.translate_response
|
170
595
|
return invigilator
|
171
596
|
|
172
597
|
async def async_answer_question(
|
173
598
|
self,
|
174
|
-
|
599
|
+
*,
|
600
|
+
question: QuestionBase,
|
601
|
+
cache: Cache,
|
175
602
|
scenario: Optional[Scenario] = None,
|
603
|
+
survey: Optional[Survey] = None,
|
176
604
|
model: Optional[LanguageModel] = None,
|
177
605
|
debug: bool = False,
|
178
606
|
memory_plan: Optional[MemoryPlan] = None,
|
179
607
|
current_answers: Optional[dict] = None,
|
608
|
+
iteration: int = 0,
|
609
|
+
key_lookup: Optional["KeyLookup"] = None,
|
180
610
|
) -> AgentResponseDict:
|
181
611
|
"""
|
612
|
+
Answer a posed question.
|
613
|
+
|
614
|
+
:param question: The question to answer.
|
615
|
+
:param scenario: The scenario in which the question is asked.
|
616
|
+
:param model: The language model to use.
|
617
|
+
:param debug: Whether to run in debug mode.
|
618
|
+
:param memory_plan: The memory plan to use.
|
619
|
+
:param current_answers: The current answers.
|
620
|
+
:param iteration: The iteration number.
|
621
|
+
|
622
|
+
>>> a = Agent(traits = {})
|
623
|
+
>>> a.add_direct_question_answering_method(lambda self, question, scenario: "I am a direct answer.")
|
624
|
+
>>> from edsl.questions.QuestionFreeText import QuestionFreeText
|
625
|
+
>>> q = QuestionFreeText.example()
|
626
|
+
>>> a.answer_question(question = q, cache = False).answer
|
627
|
+
'I am a direct answer.'
|
628
|
+
|
182
629
|
This is a function where an agent returns an answer to a particular question.
|
183
630
|
However, there are several different ways an agent can answer a question, so the
|
184
|
-
actual functionality is delegated to an
|
631
|
+
actual functionality is delegated to an :class:`edsl.agents.InvigilatorBase`: object.
|
185
632
|
"""
|
186
633
|
invigilator = self.create_invigilator(
|
187
634
|
question=question,
|
635
|
+
cache=cache,
|
188
636
|
scenario=scenario,
|
637
|
+
survey=survey,
|
189
638
|
model=model,
|
190
|
-
debug=debug,
|
191
639
|
memory_plan=memory_plan,
|
192
640
|
current_answers=current_answers,
|
641
|
+
iteration=iteration,
|
642
|
+
key_lookup=key_lookup,
|
193
643
|
)
|
194
644
|
response: AgentResponseDict = await invigilator.async_answer_question()
|
195
645
|
return response
|
196
646
|
|
197
647
|
answer_question = sync_wrapper(async_answer_question)
|
198
648
|
|
649
|
+
def _get_invigilator_class(self, question: QuestionBase) -> Type[InvigilatorBase]:
|
650
|
+
"""Get the invigilator class for a question.
|
651
|
+
|
652
|
+
This method returns the invigilator class that should be used to answer a question.
|
653
|
+
The invigilator class is determined by the type of question and the type of agent.
|
654
|
+
"""
|
655
|
+
from edsl.agents.Invigilator import (
|
656
|
+
InvigilatorHuman,
|
657
|
+
InvigilatorFunctional,
|
658
|
+
InvigilatorAI,
|
659
|
+
)
|
660
|
+
|
661
|
+
if hasattr(question, "answer_question_directly"):
|
662
|
+
return InvigilatorFunctional
|
663
|
+
elif hasattr(self, "answer_question_directly"):
|
664
|
+
return InvigilatorHuman
|
665
|
+
else:
|
666
|
+
return InvigilatorAI
|
667
|
+
|
199
668
|
def _create_invigilator(
|
200
669
|
self,
|
201
|
-
question:
|
670
|
+
question: QuestionBase,
|
671
|
+
cache: Optional[Cache] = None,
|
202
672
|
scenario: Optional[Scenario] = None,
|
203
673
|
model: Optional[LanguageModel] = None,
|
204
|
-
|
674
|
+
survey: Optional[Survey] = None,
|
205
675
|
memory_plan: Optional[MemoryPlan] = None,
|
206
676
|
current_answers: Optional[dict] = None,
|
207
|
-
|
208
|
-
|
677
|
+
iteration: int = 0,
|
678
|
+
raise_validation_errors: bool = True,
|
679
|
+
key_lookup: Optional["KeyLookup"] = None,
|
680
|
+
) -> "InvigilatorBase":
|
681
|
+
"""Create an Invigilator."""
|
682
|
+
from edsl.language_models.model import Model
|
683
|
+
from edsl.scenarios.Scenario import Scenario
|
684
|
+
|
685
|
+
model = model or Model()
|
209
686
|
scenario = scenario or Scenario()
|
210
687
|
|
211
|
-
if
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
elif hasattr(self, "answer_question_directly"):
|
218
|
-
# this of the case where the agent has a method that can answer the question directly
|
219
|
-
# this occurrs when 'answer_question_directly' has been monkey-patched onto the agent
|
220
|
-
# which happens when the agent is created from an existing survey
|
221
|
-
invigilator_class = InvigilatorHuman
|
222
|
-
else:
|
223
|
-
# this means an LLM agent will be used. This is the standard case.
|
224
|
-
invigilator_class = InvigilatorAI
|
688
|
+
if cache is None:
|
689
|
+
from edsl.data.Cache import Cache
|
690
|
+
|
691
|
+
cache = Cache()
|
692
|
+
|
693
|
+
invigilator_class = self._get_invigilator_class(question)
|
225
694
|
|
226
695
|
invigilator = invigilator_class(
|
227
|
-
self,
|
696
|
+
self,
|
697
|
+
question=question,
|
698
|
+
scenario=scenario,
|
699
|
+
survey=survey,
|
700
|
+
model=model,
|
701
|
+
memory_plan=memory_plan,
|
702
|
+
current_answers=current_answers,
|
703
|
+
iteration=iteration,
|
704
|
+
cache=cache,
|
705
|
+
raise_validation_errors=raise_validation_errors,
|
706
|
+
key_lookup=key_lookup,
|
228
707
|
)
|
229
708
|
return invigilator
|
230
709
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
710
|
+
def select(self, *traits: str) -> Agent:
|
711
|
+
"""Selects agents with only the references traits
|
712
|
+
|
713
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, codebook = {'age': 'Their age is'})
|
714
|
+
>>> a
|
715
|
+
Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5}, codebook = {'age': 'Their age is'})
|
716
|
+
|
717
|
+
|
718
|
+
>>> a.select("age", "height")
|
719
|
+
Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is'})
|
720
|
+
|
721
|
+
>>> a.select("height")
|
722
|
+
Agent(traits = {'height': 5.5})
|
723
|
+
|
724
|
+
"""
|
725
|
+
|
726
|
+
if len(traits) == 1:
|
727
|
+
traits_to_select = [list(traits)[0]]
|
728
|
+
else:
|
729
|
+
traits_to_select = list(traits)
|
730
|
+
|
731
|
+
def _remove_none(d):
|
732
|
+
return {k: v for k, v in d.items() if v is not None}
|
733
|
+
|
734
|
+
newagent = self.duplicate()
|
735
|
+
newagent.traits = {
|
736
|
+
trait: self.traits.get(trait, None) for trait in traits_to_select
|
737
|
+
}
|
738
|
+
newagent.codebook = _remove_none(
|
739
|
+
{trait: self.codebook.get(trait, None) for trait in traits_to_select}
|
740
|
+
)
|
741
|
+
return newagent
|
742
|
+
|
743
|
+
def __add__(self, other_agent: Optional[Agent] = None) -> Agent:
|
235
744
|
"""
|
236
|
-
|
745
|
+
Combine two agents by joining their traits.
|
746
|
+
|
747
|
+
The agents must not have overlapping traits.
|
748
|
+
|
749
|
+
Example usage:
|
750
|
+
|
237
751
|
>>> a1 = Agent(traits = {"age": 10})
|
238
752
|
>>> a2 = Agent(traits = {"height": 5.5})
|
239
753
|
>>> a1 + a2
|
@@ -242,6 +756,11 @@ class Agent(Base):
|
|
242
756
|
Traceback (most recent call last):
|
243
757
|
...
|
244
758
|
edsl.exceptions.agents.AgentCombinationError: The agents have overlapping traits: {'age'}.
|
759
|
+
...
|
760
|
+
>>> a1 = Agent(traits = {"age": 10}, codebook = {"age": "Their age is"})
|
761
|
+
>>> a2 = Agent(traits = {"height": 5.5}, codebook = {"height": "Their height is"})
|
762
|
+
>>> a1 + a2
|
763
|
+
Agent(traits = {'age': 10, 'height': 5.5}, codebook = {'age': 'Their age is', 'height': 'Their height is'})
|
245
764
|
"""
|
246
765
|
if other_agent is None:
|
247
766
|
return self
|
@@ -250,12 +769,19 @@ class Agent(Base):
|
|
250
769
|
f"The agents have overlapping traits: {common_traits}."
|
251
770
|
)
|
252
771
|
else:
|
253
|
-
|
254
|
-
|
255
|
-
|
772
|
+
new_codebook = copy.deepcopy(self.codebook) | copy.deepcopy(
|
773
|
+
other_agent.codebook
|
774
|
+
)
|
775
|
+
d = self.traits | other_agent.traits
|
776
|
+
newagent = self.duplicate()
|
777
|
+
newagent.traits = d
|
778
|
+
newagent.codebook = new_codebook
|
779
|
+
return newagent
|
256
780
|
|
257
781
|
def __eq__(self, other: Agent) -> bool:
|
258
|
-
"""
|
782
|
+
"""Check if two agents are equal.
|
783
|
+
|
784
|
+
This only checks the traits.
|
259
785
|
>>> a1 = Agent(traits = {"age": 10})
|
260
786
|
>>> a2 = Agent(traits = {"age": 10})
|
261
787
|
>>> a1 == a2
|
@@ -266,25 +792,57 @@ class Agent(Base):
|
|
266
792
|
"""
|
267
793
|
return self.data == other.data
|
268
794
|
|
269
|
-
def
|
795
|
+
def __getattr__(self, name):
|
796
|
+
"""
|
797
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
798
|
+
>>> a.age
|
799
|
+
10
|
800
|
+
"""
|
801
|
+
if name == "has_dynamic_traits_function":
|
802
|
+
return self.has_dynamic_traits_function
|
803
|
+
|
804
|
+
if name in self._traits:
|
805
|
+
return self._traits[name]
|
806
|
+
|
807
|
+
raise AttributeError(
|
808
|
+
f"'{type(self).__name__}' object has no attribute '{name}'"
|
809
|
+
)
|
810
|
+
|
811
|
+
def __getstate__(self):
|
812
|
+
state = self.__dict__.copy()
|
813
|
+
# Include any additional state that needs to be serialized
|
814
|
+
return state
|
815
|
+
|
816
|
+
def __setstate__(self, state):
|
817
|
+
self.__dict__.update(state)
|
818
|
+
# Ensure _traits is initialized if it's missing
|
819
|
+
if "_traits" not in self.__dict__:
|
820
|
+
self._traits = {}
|
821
|
+
|
822
|
+
def __repr__(self) -> str:
|
823
|
+
"""Return representation of Agent."""
|
270
824
|
class_name = self.__class__.__name__
|
271
825
|
items = [
|
272
|
-
f
|
826
|
+
f'{k} = """{v}"""' if isinstance(v, str) else f"{k} = {v}"
|
273
827
|
for k, v in self.data.items()
|
274
828
|
if k != "question_type"
|
275
829
|
]
|
276
830
|
return f"{class_name}({', '.join(items)})"
|
277
831
|
|
278
|
-
################
|
279
|
-
# SERIALIZATION METHODS
|
280
|
-
################
|
281
832
|
@property
|
282
|
-
def data(self):
|
833
|
+
def data(self) -> dict:
|
834
|
+
"""Format the data for serialization.
|
835
|
+
|
836
|
+
TODO: Warn if has dynamic traits function or direct answer function that cannot be serialized.
|
837
|
+
TODO: Add ability to have coop-hosted functions that are serializable.
|
838
|
+
"""
|
839
|
+
|
283
840
|
raw_data = {
|
284
841
|
k.replace("_", "", 1): v
|
285
842
|
for k, v in self.__dict__.items()
|
286
843
|
if k.startswith("_")
|
287
844
|
}
|
845
|
+
|
288
846
|
if hasattr(self, "set_instructions"):
|
289
847
|
if not self.set_instructions:
|
290
848
|
raw_data.pop("instruction")
|
@@ -292,20 +850,102 @@ class Agent(Base):
|
|
292
850
|
raw_data.pop("codebook")
|
293
851
|
if self.name == None:
|
294
852
|
raw_data.pop("name")
|
853
|
+
|
854
|
+
if hasattr(self, "dynamic_traits_function"):
|
855
|
+
raw_data.pop(
|
856
|
+
"dynamic_traits_function", None
|
857
|
+
) # in case dynamic_traits_function will appear with _ in self.__dict__
|
858
|
+
dynamic_traits_func = self.dynamic_traits_function
|
859
|
+
if dynamic_traits_func:
|
860
|
+
func = inspect.getsource(dynamic_traits_func)
|
861
|
+
raw_data["dynamic_traits_function_source_code"] = func
|
862
|
+
raw_data["dynamic_traits_function_name"] = (
|
863
|
+
self.dynamic_traits_function_name
|
864
|
+
)
|
865
|
+
if hasattr(self, "answer_question_directly"):
|
866
|
+
raw_data.pop(
|
867
|
+
"answer_question_directly", None
|
868
|
+
) # in case answer_question_directly will appear with _ in self.__dict__
|
869
|
+
answer_question_directly_func = self.answer_question_directly
|
870
|
+
|
871
|
+
if (
|
872
|
+
answer_question_directly_func
|
873
|
+
and raw_data.get("answer_question_directly_source_code", None) != None
|
874
|
+
):
|
875
|
+
raw_data["answer_question_directly_source_code"] = inspect.getsource(
|
876
|
+
answer_question_directly_func
|
877
|
+
)
|
878
|
+
raw_data["answer_question_directly_function_name"] = (
|
879
|
+
self.answer_question_directly_function_name
|
880
|
+
)
|
881
|
+
raw_data["traits"] = dict(raw_data["traits"])
|
882
|
+
|
295
883
|
return raw_data
|
296
884
|
|
297
|
-
def
|
298
|
-
"""
|
299
|
-
|
885
|
+
def __hash__(self) -> int:
|
886
|
+
"""Return a hash of the agent.
|
887
|
+
|
888
|
+
>>> hash(Agent.example())
|
889
|
+
2067581884874391607
|
890
|
+
"""
|
891
|
+
from edsl.utilities.utilities import dict_hash
|
892
|
+
|
893
|
+
return dict_hash(self.to_dict(add_edsl_version=False))
|
894
|
+
|
895
|
+
def to_dict(self, add_edsl_version=True) -> dict[str, Union[dict, bool]]:
|
896
|
+
"""Serialize to a dictionary with EDSL info.
|
897
|
+
|
898
|
+
Example usage:
|
899
|
+
|
900
|
+
>>> a = Agent(name = "Steve", traits = {"age": 10, "hair": "brown", "height": 5.5})
|
901
|
+
>>> a.to_dict()
|
902
|
+
{'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'name': 'Steve', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
|
903
|
+
|
904
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5}, instruction = "Have fun.")
|
905
|
+
>>> a.to_dict()
|
906
|
+
{'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}, 'instruction': 'Have fun.', 'edsl_version': '...', 'edsl_class_name': 'Agent'}
|
907
|
+
"""
|
908
|
+
d = {}
|
909
|
+
d["traits"] = copy.deepcopy(dict(self._traits))
|
910
|
+
if self.name:
|
911
|
+
d["name"] = self.name
|
912
|
+
if self.set_instructions:
|
913
|
+
d["instruction"] = self.instruction
|
914
|
+
if self.set_traits_presentation_template:
|
915
|
+
d["traits_presentation_template"] = self.traits_presentation_template
|
916
|
+
if self.codebook:
|
917
|
+
d["codebook"] = self.codebook
|
918
|
+
if add_edsl_version:
|
919
|
+
from edsl import __version__
|
920
|
+
|
921
|
+
d["edsl_version"] = __version__
|
922
|
+
d["edsl_class_name"] = self.__class__.__name__
|
923
|
+
|
924
|
+
return d
|
300
925
|
|
301
926
|
@classmethod
|
927
|
+
@remove_edsl_version
|
302
928
|
def from_dict(cls, agent_dict: dict[str, Union[dict, bool]]) -> Agent:
|
303
|
-
"""
|
304
|
-
|
929
|
+
"""Deserialize from a dictionary.
|
930
|
+
|
931
|
+
Example usage:
|
305
932
|
|
306
|
-
|
307
|
-
|
308
|
-
|
933
|
+
>>> Agent.from_dict({'name': "Steve", 'traits': {'age': 10, 'hair': 'brown', 'height': 5.5}})
|
934
|
+
Agent(name = \"""Steve\""", traits = {'age': 10, 'hair': 'brown', 'height': 5.5})
|
935
|
+
|
936
|
+
"""
|
937
|
+
if "traits" in agent_dict:
|
938
|
+
return cls(
|
939
|
+
traits=agent_dict["traits"],
|
940
|
+
name=agent_dict.get("name", None),
|
941
|
+
instruction=agent_dict.get("instruction", None),
|
942
|
+
traits_presentation_template=agent_dict.get(
|
943
|
+
"traits_presentation_template", None
|
944
|
+
),
|
945
|
+
codebook=agent_dict.get("codebook", None),
|
946
|
+
)
|
947
|
+
else: # old-style agent - we used to only store the traits
|
948
|
+
return cls(**agent_dict)
|
309
949
|
|
310
950
|
def _table(self) -> tuple[dict, list]:
|
311
951
|
"""Prepare generic table data."""
|
@@ -315,35 +955,95 @@ class Agent(Base):
|
|
315
955
|
column_names = ["Attribute", "Value"]
|
316
956
|
return table_data, column_names
|
317
957
|
|
318
|
-
def
|
319
|
-
"""
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
958
|
+
def add_trait(self, trait_name_or_dict: str, value: Optional[Any] = None) -> Agent:
|
959
|
+
"""Adds a trait to an agent and returns that agent
|
960
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
961
|
+
>>> a.add_trait("weight", 150)
|
962
|
+
Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5, 'weight': 150})
|
963
|
+
"""
|
964
|
+
if isinstance(trait_name_or_dict, dict) and value is None:
|
965
|
+
newagent = self.duplicate()
|
966
|
+
newagent.traits = {**self.traits, **trait_name_or_dict}
|
967
|
+
return newagent
|
968
|
+
|
969
|
+
if isinstance(trait_name_or_dict, dict) and value:
|
970
|
+
raise AgentErrors(
|
971
|
+
f"You passed a dict: {trait_name_or_dict} and a value: {value}. You should pass only a dict."
|
972
|
+
)
|
973
|
+
|
974
|
+
if isinstance(trait_name_or_dict, str):
|
975
|
+
newagent = self.duplicate()
|
976
|
+
newagent.traits = {**self.traits, **{trait_name_or_dict: value}}
|
977
|
+
return newagent
|
978
|
+
|
979
|
+
raise AgentErrors("Something is not right with adding a trait to an Agent")
|
324
980
|
|
325
|
-
|
326
|
-
|
327
|
-
table.add_row(*row_data)
|
981
|
+
def remove_trait(self, trait: str) -> Agent:
|
982
|
+
"""Remove a trait from the agent.
|
328
983
|
|
329
|
-
|
984
|
+
Example usage:
|
985
|
+
|
986
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
987
|
+
>>> a.remove_trait("age")
|
988
|
+
Agent(traits = {'hair': 'brown', 'height': 5.5})
|
989
|
+
"""
|
990
|
+
newagent = self.duplicate()
|
991
|
+
newagent.traits = {k: v for k, v in self.traits.items() if k != trait}
|
992
|
+
return newagent
|
993
|
+
|
994
|
+
def translate_traits(self, values_codebook: dict) -> Agent:
|
995
|
+
"""Translate traits to a new codebook.
|
996
|
+
|
997
|
+
>>> a = Agent(traits = {"age": 10, "hair": 1, "height": 5.5})
|
998
|
+
>>> a.translate_traits({"hair": {1:"brown"}})
|
999
|
+
Agent(traits = {'age': 10, 'hair': 'brown', 'height': 5.5})
|
1000
|
+
|
1001
|
+
:param values_codebook: The new codebook.
|
1002
|
+
"""
|
1003
|
+
new_traits = {}
|
1004
|
+
for key, value in self.traits.items():
|
1005
|
+
if key in values_codebook:
|
1006
|
+
new_traits[key] = values_codebook[key].get(value, value)
|
1007
|
+
else:
|
1008
|
+
new_traits[key] = value
|
1009
|
+
newagent = self.duplicate()
|
1010
|
+
newagent.traits = new_traits
|
1011
|
+
return newagent
|
330
1012
|
|
331
1013
|
@classmethod
|
332
|
-
def example(cls) -> Agent:
|
333
|
-
"""
|
1014
|
+
def example(cls, randomize: bool = False) -> Agent:
|
1015
|
+
"""
|
1016
|
+
Returns an example Agent instance.
|
1017
|
+
|
1018
|
+
:param randomize: If True, adds a random string to the value of an example key.
|
334
1019
|
|
335
1020
|
>>> Agent.example()
|
336
1021
|
Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})
|
337
1022
|
"""
|
338
|
-
|
1023
|
+
addition = "" if not randomize else str(uuid4())
|
1024
|
+
return cls(traits={"age": 22, "hair": f"brown{addition}", "height": 5.5})
|
339
1025
|
|
340
1026
|
def code(self) -> str:
|
341
|
-
"""
|
342
|
-
|
1027
|
+
"""Return the code for the agent.
|
1028
|
+
|
1029
|
+
Example usage:
|
1030
|
+
|
1031
|
+
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
1032
|
+
>>> print(a.code())
|
1033
|
+
from edsl.agents.Agent import Agent
|
1034
|
+
agent = Agent(traits={'age': 10, 'hair': 'brown', 'height': 5.5})
|
1035
|
+
"""
|
1036
|
+
return (
|
1037
|
+
f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
|
1038
|
+
)
|
343
1039
|
|
344
1040
|
|
345
1041
|
def main():
|
346
|
-
"""
|
1042
|
+
"""
|
1043
|
+
Give an example of usage.
|
1044
|
+
|
1045
|
+
WARNING: Consume API credits
|
1046
|
+
"""
|
347
1047
|
from edsl.agents import Agent
|
348
1048
|
from edsl.questions import QuestionMultipleChoice
|
349
1049
|
|
@@ -361,13 +1061,11 @@ def main():
|
|
361
1061
|
question_options=["Yes", "No"],
|
362
1062
|
question_name="food_preference",
|
363
1063
|
)
|
364
|
-
job =
|
365
|
-
# run the job
|
1064
|
+
job = question.by(agent)
|
366
1065
|
results = job.run()
|
367
|
-
# results
|
368
1066
|
|
369
1067
|
|
370
1068
|
if __name__ == "__main__":
|
371
1069
|
import doctest
|
372
1070
|
|
373
|
-
doctest.testmod()
|
1071
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|