edsl 0.1.33__py3-none-any.whl → 0.1.33.dev1__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 +3 -9
- edsl/__init__.py +3 -8
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +8 -40
- edsl/agents/AgentList.py +0 -43
- edsl/agents/Invigilator.py +219 -135
- edsl/agents/InvigilatorBase.py +59 -148
- edsl/agents/{PromptConstructor.py → PromptConstructionMixin.py} +89 -138
- edsl/agents/__init__.py +0 -1
- edsl/config.py +56 -47
- edsl/coop/coop.py +7 -50
- edsl/data/Cache.py +1 -35
- edsl/data_transfer_models.py +38 -73
- edsl/enums.py +0 -4
- edsl/exceptions/language_models.py +1 -25
- edsl/exceptions/questions.py +5 -62
- edsl/exceptions/results.py +0 -4
- edsl/inference_services/AnthropicService.py +11 -13
- edsl/inference_services/AwsBedrock.py +17 -19
- edsl/inference_services/AzureAI.py +20 -37
- edsl/inference_services/GoogleService.py +12 -16
- edsl/inference_services/GroqService.py +0 -2
- edsl/inference_services/InferenceServiceABC.py +3 -58
- edsl/inference_services/OpenAIService.py +54 -48
- edsl/inference_services/models_available_cache.py +6 -0
- edsl/inference_services/registry.py +0 -6
- edsl/jobs/Answers.py +12 -10
- edsl/jobs/Jobs.py +21 -36
- edsl/jobs/buckets/BucketCollection.py +15 -24
- edsl/jobs/buckets/TokenBucket.py +14 -93
- edsl/jobs/interviews/Interview.py +78 -366
- edsl/jobs/interviews/InterviewExceptionEntry.py +19 -85
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +286 -0
- edsl/jobs/interviews/{InterviewExceptionCollection.py → interview_exception_tracking.py} +68 -14
- edsl/jobs/interviews/retry_management.py +37 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +175 -146
- edsl/jobs/runners/JobsRunnerStatusMixin.py +333 -0
- edsl/jobs/tasks/QuestionTaskCreator.py +23 -30
- edsl/jobs/tasks/TaskHistory.py +213 -148
- edsl/language_models/LanguageModel.py +156 -261
- edsl/language_models/ModelList.py +2 -2
- edsl/language_models/RegisterLanguageModelsMeta.py +29 -14
- edsl/language_models/registry.py +6 -23
- edsl/language_models/repair.py +19 -0
- edsl/prompts/Prompt.py +2 -52
- edsl/questions/AnswerValidatorMixin.py +26 -23
- edsl/questions/QuestionBase.py +249 -329
- edsl/questions/QuestionBudget.py +41 -99
- edsl/questions/QuestionCheckBox.py +35 -227
- edsl/questions/QuestionExtract.py +27 -98
- edsl/questions/QuestionFreeText.py +29 -52
- edsl/questions/QuestionFunctional.py +0 -7
- edsl/questions/QuestionList.py +22 -141
- edsl/questions/QuestionMultipleChoice.py +65 -159
- edsl/questions/QuestionNumerical.py +46 -88
- edsl/questions/QuestionRank.py +24 -182
- edsl/questions/RegisterQuestionsMeta.py +12 -31
- edsl/questions/__init__.py +4 -3
- edsl/questions/derived/QuestionLikertFive.py +5 -10
- edsl/questions/derived/QuestionLinearScale.py +2 -15
- edsl/questions/derived/QuestionTopK.py +1 -10
- edsl/questions/derived/QuestionYesNo.py +3 -24
- edsl/questions/descriptors.py +7 -43
- edsl/questions/question_registry.py +2 -6
- edsl/results/Dataset.py +0 -20
- edsl/results/DatasetExportMixin.py +48 -46
- edsl/results/Result.py +5 -32
- edsl/results/Results.py +46 -135
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/scenarios/FileStore.py +10 -71
- edsl/scenarios/Scenario.py +25 -96
- edsl/scenarios/ScenarioImageMixin.py +2 -2
- edsl/scenarios/ScenarioList.py +39 -361
- edsl/scenarios/ScenarioListExportMixin.py +0 -9
- edsl/scenarios/ScenarioListPdfMixin.py +4 -150
- edsl/study/SnapShot.py +1 -8
- edsl/study/Study.py +0 -32
- edsl/surveys/Rule.py +1 -10
- edsl/surveys/RuleCollection.py +5 -21
- edsl/surveys/Survey.py +310 -636
- edsl/surveys/SurveyExportMixin.py +9 -71
- edsl/surveys/SurveyFlowVisualizationMixin.py +1 -2
- edsl/surveys/SurveyQualtricsImport.py +4 -75
- edsl/utilities/gcp_bucket/simple_example.py +9 -0
- edsl/utilities/utilities.py +1 -9
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/METADATA +2 -5
- edsl-0.1.33.dev1.dist-info/RECORD +209 -0
- edsl/TemplateLoader.py +0 -24
- edsl/auto/AutoStudy.py +0 -117
- edsl/auto/StageBase.py +0 -230
- edsl/auto/StageGenerateSurvey.py +0 -178
- edsl/auto/StageLabelQuestions.py +0 -125
- edsl/auto/StagePersona.py +0 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
- edsl/auto/StagePersonaDimensionValues.py +0 -74
- edsl/auto/StagePersonaDimensions.py +0 -69
- edsl/auto/StageQuestions.py +0 -73
- edsl/auto/SurveyCreatorPipeline.py +0 -21
- edsl/auto/utilities.py +0 -224
- edsl/coop/PriceFetcher.py +0 -58
- edsl/inference_services/MistralAIService.py +0 -120
- edsl/inference_services/TestService.py +0 -80
- edsl/inference_services/TogetherAIService.py +0 -170
- edsl/jobs/FailedQuestion.py +0 -78
- edsl/jobs/runners/JobsRunnerStatus.py +0 -331
- edsl/language_models/fake_openai_call.py +0 -15
- edsl/language_models/fake_openai_service.py +0 -61
- edsl/language_models/utilities.py +0 -61
- edsl/questions/QuestionBaseGenMixin.py +0 -133
- edsl/questions/QuestionBasePromptsMixin.py +0 -266
- edsl/questions/Quick.py +0 -41
- edsl/questions/ResponseValidatorABC.py +0 -170
- edsl/questions/decorators.py +0 -21
- edsl/questions/prompt_templates/question_budget.jinja +0 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +0 -32
- edsl/questions/prompt_templates/question_extract.jinja +0 -11
- edsl/questions/prompt_templates/question_free_text.jinja +0 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +0 -11
- edsl/questions/prompt_templates/question_list.jinja +0 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +0 -33
- edsl/questions/prompt_templates/question_numerical.jinja +0 -37
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/budget/__init__.py +0 -0
- edsl/questions/templates/budget/answering_instructions.jinja +0 -7
- edsl/questions/templates/budget/question_presentation.jinja +0 -7
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +0 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +0 -22
- edsl/questions/templates/extract/__init__.py +0 -0
- edsl/questions/templates/extract/answering_instructions.jinja +0 -7
- edsl/questions/templates/extract/question_presentation.jinja +0 -1
- 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 +0 -1
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +0 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +0 -12
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +0 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +0 -5
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +0 -4
- edsl/questions/templates/list/question_presentation.jinja +0 -5
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +0 -9
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +0 -12
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +0 -8
- edsl/questions/templates/numerical/question_presentation.jinja +0 -7
- edsl/questions/templates/rank/__init__.py +0 -0
- edsl/questions/templates/rank/answering_instructions.jinja +0 -11
- edsl/questions/templates/rank/question_presentation.jinja +0 -15
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +0 -8
- edsl/questions/templates/top_k/question_presentation.jinja +0 -22
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +0 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +0 -12
- edsl/results/DatasetTree.py +0 -145
- edsl/results/Selector.py +0 -118
- edsl/results/tree_explore.py +0 -115
- edsl/surveys/instructions/ChangeInstruction.py +0 -47
- edsl/surveys/instructions/Instruction.py +0 -34
- edsl/surveys/instructions/InstructionCollection.py +0 -77
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +0 -24
- edsl/templates/error_reporting/exceptions_by_model.html +0 -35
- edsl/templates/error_reporting/exceptions_by_question_name.html +0 -17
- edsl/templates/error_reporting/exceptions_by_type.html +0 -17
- edsl/templates/error_reporting/interview_details.html +0 -116
- edsl/templates/error_reporting/interviews.html +0 -10
- edsl/templates/error_reporting/overview.html +0 -5
- edsl/templates/error_reporting/performance_plot.html +0 -2
- edsl/templates/error_reporting/report.css +0 -74
- edsl/templates/error_reporting/report.html +0 -118
- edsl/templates/error_reporting/report.js +0 -25
- edsl-0.1.33.dist-info/RECORD +0 -295
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dist-info → edsl-0.1.33.dev1.dist-info}/WHEEL +0 -0
edsl/agents/InvigilatorBase.py
CHANGED
@@ -8,27 +8,34 @@ from edsl.data_transfer_models import AgentResponseDict
|
|
8
8
|
|
9
9
|
from edsl.data.Cache import Cache
|
10
10
|
|
11
|
+
# from edsl.agents.Agent import Agent
|
11
12
|
from edsl.questions.QuestionBase import QuestionBase
|
12
13
|
from edsl.scenarios.Scenario import Scenario
|
13
14
|
from edsl.surveys.MemoryPlan import MemoryPlan
|
14
15
|
from edsl.language_models.LanguageModel import LanguageModel
|
15
16
|
|
16
|
-
from edsl.data_transfer_models import EDSLResultObjectInput
|
17
|
-
from edsl.agents.PromptConstructor import PromptConstructor
|
18
|
-
|
19
17
|
|
20
18
|
class InvigilatorBase(ABC):
|
21
19
|
"""An invigiator (someone who administers an exam) is a class that is responsible for administering a question to an agent.
|
22
20
|
|
23
21
|
>>> InvigilatorBase.example().answer_question()
|
24
|
-
{'message':
|
22
|
+
{'message': '{"answer": "SPAM!"}'}
|
25
23
|
|
26
|
-
>>> InvigilatorBase.example().get_failed_task_result(
|
27
|
-
'Failed to get response'
|
24
|
+
>>> InvigilatorBase.example().get_failed_task_result()
|
25
|
+
{'answer': None, 'comment': 'Failed to get response', ...
|
28
26
|
|
29
27
|
This returns an empty prompt because there is no memory the agent needs to have at q0.
|
30
28
|
|
29
|
+
>>> InvigilatorBase.example().create_memory_prompt("q0")
|
30
|
+
Prompt(text=\"""\""")
|
31
31
|
|
32
|
+
>>> i = InvigilatorBase.example()
|
33
|
+
>>> i.current_answers = {"q0": "Prior answer"}
|
34
|
+
>>> i.memory_plan.add_single_memory("q1", "q0")
|
35
|
+
>>> i.create_memory_prompt("q1")
|
36
|
+
Prompt(text=\"""
|
37
|
+
Before the question you are now answering, you already answered the following question(s):
|
38
|
+
...
|
32
39
|
"""
|
33
40
|
|
34
41
|
def __init__(
|
@@ -44,7 +51,6 @@ class InvigilatorBase(ABC):
|
|
44
51
|
iteration: Optional[int] = 1,
|
45
52
|
additional_prompt_data: Optional[dict] = None,
|
46
53
|
sidecar_model: Optional[LanguageModel] = None,
|
47
|
-
raise_validation_errors: Optional[bool] = True,
|
48
54
|
):
|
49
55
|
"""Initialize a new Invigilator."""
|
50
56
|
self.agent = agent
|
@@ -58,78 +64,6 @@ class InvigilatorBase(ABC):
|
|
58
64
|
self.cache = cache
|
59
65
|
self.sidecar_model = sidecar_model
|
60
66
|
self.survey = survey
|
61
|
-
self.raise_validation_errors = raise_validation_errors
|
62
|
-
|
63
|
-
self.raw_model_response = (
|
64
|
-
None # placeholder for the raw response from the model
|
65
|
-
)
|
66
|
-
|
67
|
-
@property
|
68
|
-
def prompt_constructor(self) -> PromptConstructor:
|
69
|
-
"""Return the prompt constructor."""
|
70
|
-
return PromptConstructor(self)
|
71
|
-
|
72
|
-
def to_dict(self):
|
73
|
-
attributes = [
|
74
|
-
"agent",
|
75
|
-
"question",
|
76
|
-
"scenario",
|
77
|
-
"model",
|
78
|
-
"memory_plan",
|
79
|
-
"current_answers",
|
80
|
-
"iteration",
|
81
|
-
"additional_prompt_data",
|
82
|
-
"cache",
|
83
|
-
"sidecar_model",
|
84
|
-
"survey",
|
85
|
-
]
|
86
|
-
|
87
|
-
def serialize_attribute(attr):
|
88
|
-
value = getattr(self, attr)
|
89
|
-
if value is None:
|
90
|
-
return None
|
91
|
-
if hasattr(value, "to_dict"):
|
92
|
-
return value.to_dict()
|
93
|
-
if isinstance(value, (int, float, str, bool, dict, list)):
|
94
|
-
return value
|
95
|
-
return str(value)
|
96
|
-
|
97
|
-
return {attr: serialize_attribute(attr) for attr in attributes}
|
98
|
-
|
99
|
-
@classmethod
|
100
|
-
def from_dict(cls, data):
|
101
|
-
from edsl.agents.Agent import Agent
|
102
|
-
from edsl.questions import QuestionBase
|
103
|
-
from edsl.scenarios.Scenario import Scenario
|
104
|
-
from edsl.surveys.MemoryPlan import MemoryPlan
|
105
|
-
from edsl.language_models.LanguageModel import LanguageModel
|
106
|
-
from edsl.surveys.Survey import Survey
|
107
|
-
|
108
|
-
agent = Agent.from_dict(data["agent"])
|
109
|
-
question = QuestionBase.from_dict(data["question"])
|
110
|
-
scenario = Scenario.from_dict(data["scenario"])
|
111
|
-
model = LanguageModel.from_dict(data["model"])
|
112
|
-
memory_plan = MemoryPlan.from_dict(data["memory_plan"])
|
113
|
-
survey = Survey.from_dict(data["survey"])
|
114
|
-
current_answers = data["current_answers"]
|
115
|
-
iteration = data["iteration"]
|
116
|
-
additional_prompt_data = data["additional_prompt_data"]
|
117
|
-
cache = Cache.from_dict(data["cache"])
|
118
|
-
sidecar_model = LanguageModel.from_dict(data["sidecar_model"])
|
119
|
-
|
120
|
-
return cls(
|
121
|
-
agent=agent,
|
122
|
-
question=question,
|
123
|
-
scenario=scenario,
|
124
|
-
model=model,
|
125
|
-
memory_plan=memory_plan,
|
126
|
-
current_answers=current_answers,
|
127
|
-
survey=survey,
|
128
|
-
iteration=iteration,
|
129
|
-
additional_prompt_data=additional_prompt_data,
|
130
|
-
cache=cache,
|
131
|
-
sidecar_model=sidecar_model,
|
132
|
-
)
|
133
67
|
|
134
68
|
def __repr__(self) -> str:
|
135
69
|
"""Return a string representation of the Invigilator.
|
@@ -140,45 +74,18 @@ class InvigilatorBase(ABC):
|
|
140
74
|
"""
|
141
75
|
return f"{self.__class__.__name__}(agent={repr(self.agent)}, question={repr(self.question)}, scneario={repr(self.scenario)}, model={repr(self.model)}, memory_plan={repr(self.memory_plan)}, current_answers={repr(self.current_answers)}, iteration{repr(self.iteration)}, additional_prompt_data={repr(self.additional_prompt_data)}, cache={repr(self.cache)}, sidecarmodel={repr(self.sidecar_model)})"
|
142
76
|
|
143
|
-
def get_failed_task_result(self
|
77
|
+
def get_failed_task_result(self) -> AgentResponseDict:
|
144
78
|
"""Return an AgentResponseDict used in case the question-asking fails.
|
145
79
|
|
146
|
-
|
147
|
-
|
148
|
-
- Failed to get response from the model
|
149
|
-
|
80
|
+
>>> InvigilatorBase.example().get_failed_task_result()
|
81
|
+
{'answer': None, 'comment': 'Failed to get response', ...}
|
150
82
|
"""
|
151
|
-
|
152
|
-
|
153
|
-
"
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
"cached_response": None,
|
158
|
-
"raw_model_response": None,
|
159
|
-
"cache_used": None,
|
160
|
-
"cache_key": None,
|
161
|
-
}
|
162
|
-
return EDSLResultObjectInput(**data)
|
163
|
-
|
164
|
-
# breakpoint()
|
165
|
-
# if hasattr(self, "augmented_model_response"):
|
166
|
-
# import json
|
167
|
-
|
168
|
-
# generated_tokens = json.loads(self.augmented_model_response)["answer"][
|
169
|
-
# "generated_tokens"
|
170
|
-
# ]
|
171
|
-
# else:
|
172
|
-
# generated_tokens = "Filled in by InvigilatorBase.get_failed_task_result"
|
173
|
-
# agent_response_dict = AgentResponseDict(
|
174
|
-
# answer=None,
|
175
|
-
# comment="Failed to get usable response",
|
176
|
-
# generated_tokens=generated_tokens,
|
177
|
-
# question_name=self.question.question_name,
|
178
|
-
# prompts=self.get_prompts(),
|
179
|
-
# )
|
180
|
-
# # breakpoint()
|
181
|
-
# return agent_response_dict
|
83
|
+
return AgentResponseDict(
|
84
|
+
answer=None,
|
85
|
+
comment="Failed to get response",
|
86
|
+
question_name=self.question.question_name,
|
87
|
+
prompts=self.get_prompts(),
|
88
|
+
)
|
182
89
|
|
183
90
|
def get_prompts(self) -> Dict[str, Prompt]:
|
184
91
|
"""Return the prompt used."""
|
@@ -204,10 +111,24 @@ class InvigilatorBase(ABC):
|
|
204
111
|
|
205
112
|
return main()
|
206
113
|
|
114
|
+
def create_memory_prompt(self, question_name: str) -> Prompt:
|
115
|
+
"""Create a memory for the agent.
|
116
|
+
|
117
|
+
The returns a memory prompt for the agent.
|
118
|
+
|
119
|
+
>>> i = InvigilatorBase.example()
|
120
|
+
>>> i.current_answers = {"q0": "Prior answer"}
|
121
|
+
>>> i.memory_plan.add_single_memory("q1", "q0")
|
122
|
+
>>> p = i.create_memory_prompt("q1")
|
123
|
+
>>> p.text.strip().replace("\\n", " ").replace("\\t", " ")
|
124
|
+
'Before the question you are now answering, you already answered the following question(s): Question: Do you like school? Answer: Prior answer'
|
125
|
+
"""
|
126
|
+
return self.memory_plan.get_memory_prompt_fragment(
|
127
|
+
question_name, self.current_answers
|
128
|
+
)
|
129
|
+
|
207
130
|
@classmethod
|
208
|
-
def example(
|
209
|
-
cls, throw_an_exception=False, question=None, scenario=None, survey=None
|
210
|
-
) -> "InvigilatorBase":
|
131
|
+
def example(cls, throw_an_exception=False, question=None, scenario=None):
|
211
132
|
"""Return an example invigilator.
|
212
133
|
|
213
134
|
>>> InvigilatorBase.example()
|
@@ -222,53 +143,43 @@ class InvigilatorBase(ABC):
|
|
222
143
|
|
223
144
|
from edsl.enums import InferenceServiceType
|
224
145
|
|
225
|
-
|
226
|
-
|
227
|
-
model = Model("test", canned_response="SPAM!")
|
228
|
-
# class TestLanguageModelGood(LanguageModel):
|
229
|
-
# """A test language model."""
|
146
|
+
class TestLanguageModelGood(LanguageModel):
|
147
|
+
"""A test language model."""
|
230
148
|
|
231
|
-
|
232
|
-
|
233
|
-
|
149
|
+
_model_ = "test"
|
150
|
+
_parameters_ = {"temperature": 0.5}
|
151
|
+
_inference_service_ = InferenceServiceType.TEST.value
|
234
152
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
153
|
+
async def async_execute_model_call(
|
154
|
+
self, user_prompt: str, system_prompt: str
|
155
|
+
) -> dict[str, Any]:
|
156
|
+
await asyncio.sleep(0.1)
|
157
|
+
if hasattr(self, "throw_an_exception"):
|
158
|
+
raise Exception("Error!")
|
159
|
+
return {"message": """{"answer": "SPAM!"}"""}
|
242
160
|
|
243
|
-
|
244
|
-
|
245
|
-
|
161
|
+
def parse_response(self, raw_response: dict[str, Any]) -> str:
|
162
|
+
"""Parse the response from the model."""
|
163
|
+
return raw_response["message"]
|
246
164
|
|
165
|
+
model = TestLanguageModelGood()
|
247
166
|
if throw_an_exception:
|
248
167
|
model.throw_an_exception = True
|
249
168
|
agent = Agent.example()
|
250
169
|
# question = QuestionMultipleChoice.example()
|
251
170
|
from edsl.surveys import Survey
|
252
171
|
|
253
|
-
|
254
|
-
survey = Survey.example()
|
255
|
-
# if question:
|
256
|
-
# need to have the focal question name in the list of names
|
257
|
-
# survey._questions[0].question_name = question.question_name
|
258
|
-
# survey.add_question(question)
|
259
|
-
if question:
|
260
|
-
survey.add_question(question)
|
261
|
-
|
172
|
+
survey = Survey.example()
|
262
173
|
question = question or survey.questions[0]
|
263
174
|
scenario = scenario or Scenario.example()
|
264
175
|
# memory_plan = None #memory_plan = MemoryPlan()
|
265
176
|
from edsl import Survey
|
266
177
|
|
267
|
-
memory_plan = MemoryPlan(survey=
|
178
|
+
memory_plan = MemoryPlan(survey=Survey.example())
|
268
179
|
current_answers = None
|
269
|
-
from edsl.agents.
|
180
|
+
from edsl.agents.PromptConstructionMixin import PromptConstructorMixin
|
270
181
|
|
271
|
-
class InvigilatorExample(InvigilatorBase):
|
182
|
+
class InvigilatorExample(PromptConstructorMixin, InvigilatorBase):
|
272
183
|
"""An example invigilator."""
|
273
184
|
|
274
185
|
async def async_answer_question(self):
|
@@ -1,15 +1,15 @@
|
|
1
|
-
from
|
2
|
-
from typing import Dict, Any, Optional, Set
|
1
|
+
from typing import Dict, Any, Optional
|
3
2
|
from collections import UserList
|
4
|
-
import enum
|
5
|
-
|
6
|
-
from jinja2 import Environment, meta
|
7
3
|
|
4
|
+
# from functools import reduce
|
8
5
|
from edsl.prompts.Prompt import Prompt
|
9
|
-
|
6
|
+
|
7
|
+
# from edsl.utilities.decorators import sync_wrapper, jupyter_nb_handler
|
10
8
|
from edsl.prompts.registry import get_classes as prompt_lookup
|
11
9
|
from edsl.exceptions import QuestionScenarioRenderError
|
12
10
|
|
11
|
+
import enum
|
12
|
+
|
13
13
|
|
14
14
|
class PromptComponent(enum.Enum):
|
15
15
|
AGENT_INSTRUCTIONS = "agent_instructions"
|
@@ -18,21 +18,6 @@ class PromptComponent(enum.Enum):
|
|
18
18
|
PRIOR_QUESTION_MEMORY = "prior_question_memory"
|
19
19
|
|
20
20
|
|
21
|
-
def get_jinja2_variables(template_str: str) -> Set[str]:
|
22
|
-
"""
|
23
|
-
Extracts all variable names from a Jinja2 template using Jinja2's built-in parsing.
|
24
|
-
|
25
|
-
Args:
|
26
|
-
template_str (str): The Jinja2 template string
|
27
|
-
|
28
|
-
Returns:
|
29
|
-
Set[str]: A set of variable names found in the template
|
30
|
-
"""
|
31
|
-
env = Environment()
|
32
|
-
ast = env.parse(template_str)
|
33
|
-
return meta.find_undeclared_variables(ast)
|
34
|
-
|
35
|
-
|
36
21
|
class PromptList(UserList):
|
37
22
|
separator = Prompt(" ")
|
38
23
|
|
@@ -151,7 +136,7 @@ class PromptPlan:
|
|
151
136
|
}
|
152
137
|
|
153
138
|
|
154
|
-
class
|
139
|
+
class PromptConstructorMixin:
|
155
140
|
"""Mixin for constructing prompts for the LLM call.
|
156
141
|
|
157
142
|
The pieces of a prompt are:
|
@@ -163,40 +148,16 @@ class PromptConstructor:
|
|
163
148
|
This is mixed into the Invigilator class.
|
164
149
|
"""
|
165
150
|
|
166
|
-
|
167
|
-
self.invigilator = invigilator
|
168
|
-
self.agent = invigilator.agent
|
169
|
-
self.question = invigilator.question
|
170
|
-
self.scenario = invigilator.scenario
|
171
|
-
self.survey = invigilator.survey
|
172
|
-
self.model = invigilator.model
|
173
|
-
self.current_answers = invigilator.current_answers
|
174
|
-
self.memory_plan = invigilator.memory_plan
|
175
|
-
self.prompt_plan = PromptPlan() # Assuming PromptPlan is defined elsewhere
|
176
|
-
|
177
|
-
# prompt_plan = PromptPlan()
|
178
|
-
|
179
|
-
@property
|
180
|
-
def scenario_image_keys(self):
|
181
|
-
image_entries = []
|
182
|
-
|
183
|
-
for key, value in self.scenario.items():
|
184
|
-
if isinstance(value, ImageInfo):
|
185
|
-
image_entries.append(key)
|
186
|
-
return image_entries
|
151
|
+
prompt_plan = PromptPlan()
|
187
152
|
|
188
153
|
@property
|
189
154
|
def agent_instructions_prompt(self) -> Prompt:
|
190
155
|
"""
|
191
156
|
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
192
157
|
>>> i = InvigilatorBase.example()
|
193
|
-
>>> i.
|
158
|
+
>>> i.agent_instructions_prompt
|
194
159
|
Prompt(text=\"""You are answering questions as if you were a human. Do not break character.\""")
|
195
160
|
"""
|
196
|
-
from edsl import Agent
|
197
|
-
|
198
|
-
if self.agent == Agent(): # if agent is empty, then return an empty prompt
|
199
|
-
return Prompt(text="")
|
200
161
|
if not hasattr(self, "_agent_instructions_prompt"):
|
201
162
|
applicable_prompts = prompt_lookup(
|
202
163
|
component_type="agent_instructions",
|
@@ -214,17 +175,12 @@ class PromptConstructor:
|
|
214
175
|
"""
|
215
176
|
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
216
177
|
>>> i = InvigilatorBase.example()
|
217
|
-
>>> i.
|
178
|
+
>>> i.agent_persona_prompt
|
218
179
|
Prompt(text=\"""You are an agent with the following persona:
|
219
180
|
{'age': 22, 'hair': 'brown', 'height': 5.5}\""")
|
220
181
|
|
221
182
|
"""
|
222
183
|
if not hasattr(self, "_agent_persona_prompt"):
|
223
|
-
from edsl import Agent
|
224
|
-
|
225
|
-
if self.agent == Agent(): # if agent is empty, then return an empty prompt
|
226
|
-
return Prompt(text="")
|
227
|
-
|
228
184
|
if not hasattr(self.agent, "agent_persona"):
|
229
185
|
applicable_prompts = prompt_lookup(
|
230
186
|
component_type="agent_persona",
|
@@ -269,69 +225,92 @@ class PromptConstructor:
|
|
269
225
|
d[new_question].comment = answer
|
270
226
|
return d
|
271
227
|
|
272
|
-
@property
|
273
|
-
def question_image_keys(self):
|
274
|
-
raw_question_text = self.question.question_text
|
275
|
-
variables = get_jinja2_variables(raw_question_text)
|
276
|
-
question_image_keys = []
|
277
|
-
for var in variables:
|
278
|
-
if var in self.scenario_image_keys:
|
279
|
-
question_image_keys.append(var)
|
280
|
-
return question_image_keys
|
281
|
-
|
282
228
|
@property
|
283
229
|
def question_instructions_prompt(self) -> Prompt:
|
284
230
|
"""
|
285
231
|
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
286
232
|
>>> i = InvigilatorBase.example()
|
287
|
-
>>> i.
|
288
|
-
Prompt(text=\"""
|
233
|
+
>>> i.question_instructions_prompt
|
234
|
+
Prompt(text=\"""You are being asked the following question: Do you like school?
|
235
|
+
The options are
|
236
|
+
<BLANKLINE>
|
237
|
+
0: yes
|
238
|
+
<BLANKLINE>
|
239
|
+
1: no
|
240
|
+
<BLANKLINE>
|
241
|
+
Return a valid JSON formatted like this, selecting only the number of the option:
|
242
|
+
{"answer": <put answer code here>, "comment": "<put explanation here>"}
|
243
|
+
Only 1 option may be selected.\""")
|
244
|
+
|
245
|
+
>>> from edsl import QuestionFreeText
|
246
|
+
>>> q = QuestionFreeText(question_text = "Consider {{ X }}. What is your favorite color?", question_name = "q_color")
|
247
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
248
|
+
>>> i = InvigilatorBase.example(question = q)
|
249
|
+
>>> i.question_instructions_prompt
|
250
|
+
Traceback (most recent call last):
|
289
251
|
...
|
252
|
+
edsl.exceptions.questions.QuestionScenarioRenderError: Question instructions still has variables: ['X'].
|
253
|
+
|
254
|
+
|
255
|
+
>>> from edsl import QuestionFreeText
|
256
|
+
>>> q = QuestionFreeText(question_text = "You were asked the question '{{ q0.question_text }}'. What is your favorite color?", question_name = "q_color")
|
257
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
258
|
+
>>> i = InvigilatorBase.example(question = q)
|
259
|
+
>>> i.question_instructions_prompt
|
260
|
+
Prompt(text=\"""You are being asked the following question: You were asked the question 'Do you like school?'. What is your favorite color?
|
261
|
+
Return a valid JSON formatted like this:
|
262
|
+
{"answer": "<put free text answer here>"}\""")
|
263
|
+
|
264
|
+
>>> from edsl import QuestionFreeText
|
265
|
+
>>> q = QuestionFreeText(question_text = "You stated '{{ q0.answer }}'. What is your favorite color?", question_name = "q_color")
|
266
|
+
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
267
|
+
>>> i = InvigilatorBase.example(question = q)
|
268
|
+
>>> i.current_answers = {"q0": "I like school"}
|
269
|
+
>>> i.question_instructions_prompt
|
270
|
+
Prompt(text=\"""You are being asked the following question: You stated 'I like school'. What is your favorite color?
|
271
|
+
Return a valid JSON formatted like this:
|
272
|
+
{"answer": "<put free text answer here>"}\""")
|
273
|
+
|
274
|
+
|
290
275
|
"""
|
291
276
|
if not hasattr(self, "_question_instructions_prompt"):
|
292
277
|
question_prompt = self.question.get_instructions(model=self.model.model)
|
293
278
|
|
294
|
-
#
|
279
|
+
# TODO: Try to populate the answers in the question object if they are available
|
280
|
+
# d = self.survey.question_names_to_questions()
|
281
|
+
# for question, answer in self.current_answers.items():
|
282
|
+
# if question in d:
|
283
|
+
# d[question].answer = answer
|
284
|
+
# else:
|
285
|
+
# # adds a comment to the question
|
286
|
+
# if (new_question := question.split("_comment")[0]) in d:
|
287
|
+
# d[new_question].comment = answer
|
295
288
|
|
296
289
|
question_data = self.question.data.copy()
|
297
290
|
|
298
291
|
# check to see if the question_options is actually a string
|
299
292
|
# This is used when the user is using the question_options as a variable from a sceario
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
question_options
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
|
317
|
-
| {
|
318
|
-
k: v
|
319
|
-
for k, v in self.scenario.items()
|
320
|
-
if k not in self.scenario_image_keys
|
321
|
-
} # don't include images in the replacement dict
|
293
|
+
if "question_options" in question_data:
|
294
|
+
if isinstance(self.question.data["question_options"], str):
|
295
|
+
from jinja2 import Environment, meta
|
296
|
+
|
297
|
+
env = Environment()
|
298
|
+
parsed_content = env.parse(self.question.data["question_options"])
|
299
|
+
question_option_key = list(
|
300
|
+
meta.find_undeclared_variables(parsed_content)
|
301
|
+
)[0]
|
302
|
+
question_data["question_options"] = self.scenario.get(
|
303
|
+
question_option_key
|
304
|
+
)
|
305
|
+
|
306
|
+
# breakpoint()
|
307
|
+
rendered_instructions = question_prompt.render(
|
308
|
+
question_data
|
309
|
+
| self.scenario
|
322
310
|
| self.prior_answers_dict()
|
323
311
|
| {"agent": self.agent}
|
324
|
-
| {
|
325
|
-
"use_code": getattr(self.question, "_use_code", True),
|
326
|
-
"include_comment": getattr(
|
327
|
-
self.question, "_include_comment", False
|
328
|
-
),
|
329
|
-
}
|
330
312
|
)
|
331
313
|
|
332
|
-
rendered_instructions = question_prompt.render(replacement_dict)
|
333
|
-
|
334
|
-
# is there anything left to render?
|
335
314
|
undefined_template_variables = (
|
336
315
|
rendered_instructions.undefined_template_variables({})
|
337
316
|
)
|
@@ -345,25 +324,11 @@ class PromptConstructor:
|
|
345
324
|
)
|
346
325
|
|
347
326
|
if undefined_template_variables:
|
327
|
+
print(undefined_template_variables)
|
348
328
|
raise QuestionScenarioRenderError(
|
349
329
|
f"Question instructions still has variables: {undefined_template_variables}."
|
350
330
|
)
|
351
331
|
|
352
|
-
####################################
|
353
|
-
# Check if question has instructions - these are instructions in a Survey that can apply to multiple follow-on questions
|
354
|
-
####################################
|
355
|
-
relevant_instructions = self.survey.relevant_instructions(
|
356
|
-
self.question.question_name
|
357
|
-
)
|
358
|
-
|
359
|
-
if relevant_instructions != []:
|
360
|
-
preamble_text = Prompt(
|
361
|
-
text="Before answer this question, you were given the following instructions: "
|
362
|
-
)
|
363
|
-
for instruction in relevant_instructions:
|
364
|
-
preamble_text += instruction.text
|
365
|
-
rendered_instructions = preamble_text + rendered_instructions
|
366
|
-
|
367
332
|
self._question_instructions_prompt = rendered_instructions
|
368
333
|
return self._question_instructions_prompt
|
369
334
|
|
@@ -380,23 +345,6 @@ class PromptConstructor:
|
|
380
345
|
self._prior_question_memory_prompt = memory_prompt
|
381
346
|
return self._prior_question_memory_prompt
|
382
347
|
|
383
|
-
def create_memory_prompt(self, question_name: str) -> Prompt:
|
384
|
-
"""Create a memory for the agent.
|
385
|
-
|
386
|
-
The returns a memory prompt for the agent.
|
387
|
-
|
388
|
-
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
389
|
-
>>> i = InvigilatorBase.example()
|
390
|
-
>>> i.current_answers = {"q0": "Prior answer"}
|
391
|
-
>>> i.memory_plan.add_single_memory("q1", "q0")
|
392
|
-
>>> p = i.prompt_constructor.create_memory_prompt("q1")
|
393
|
-
>>> p.text.strip().replace("\\n", " ").replace("\\t", " ")
|
394
|
-
'Before the question you are now answering, you already answered the following question(s): Question: Do you like school? Answer: Prior answer'
|
395
|
-
"""
|
396
|
-
return self.memory_plan.get_memory_prompt_fragment(
|
397
|
-
question_name, self.current_answers
|
398
|
-
)
|
399
|
-
|
400
348
|
def construct_system_prompt(self) -> Prompt:
|
401
349
|
"""Construct the system prompt for the LLM call."""
|
402
350
|
import warnings
|
@@ -420,10 +368,17 @@ class PromptConstructor:
|
|
420
368
|
|
421
369
|
>>> from edsl import QuestionFreeText
|
422
370
|
>>> from edsl.agents.InvigilatorBase import InvigilatorBase
|
423
|
-
>>> q = QuestionFreeText(question_text="How are you today?", question_name="
|
371
|
+
>>> q = QuestionFreeText(question_text="How are you today?", question_name="q0")
|
424
372
|
>>> i = InvigilatorBase.example(question = q)
|
425
373
|
>>> i.get_prompts()
|
426
374
|
{'user_prompt': ..., 'system_prompt': ...}
|
375
|
+
>>> scenario = i._get_scenario_with_image()
|
376
|
+
>>> scenario.has_image
|
377
|
+
True
|
378
|
+
>>> q = QuestionFreeText(question_text="How are you today?", question_name="q0")
|
379
|
+
>>> i = InvigilatorBase.example(question = q, scenario = scenario)
|
380
|
+
>>> i.get_prompts()
|
381
|
+
{'user_prompt': ..., 'system_prompt': ..., 'encoded_image': ...'}
|
427
382
|
"""
|
428
383
|
prompts = self.prompt_plan.get_prompts(
|
429
384
|
agent_instructions=self.agent_instructions_prompt,
|
@@ -431,16 +386,12 @@ class PromptConstructor:
|
|
431
386
|
question_instructions=self.question_instructions_prompt,
|
432
387
|
prior_question_memory=self.prior_question_memory_prompt,
|
433
388
|
)
|
434
|
-
if len(self.question_image_keys) > 1:
|
435
|
-
raise ValueError("We can only handle one image per question.")
|
436
|
-
elif len(self.question_image_keys) == 1:
|
437
|
-
prompts["encoded_image"] = self.scenario[
|
438
|
-
self.question_image_keys[0]
|
439
|
-
].encoded_image
|
440
389
|
|
390
|
+
if hasattr(self.scenario, "has_image") and self.scenario.has_image:
|
391
|
+
prompts["encoded_image"] = self.scenario["encoded_image"]
|
441
392
|
return prompts
|
442
393
|
|
443
|
-
def _get_scenario_with_image(self) ->
|
394
|
+
def _get_scenario_with_image(self) -> Dict[str, Any]:
|
444
395
|
"""This is a helper function to get a scenario with an image, for testing purposes."""
|
445
396
|
from edsl import Scenario
|
446
397
|
|
edsl/agents/__init__.py
CHANGED