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