edsl 0.1.33.dev1__py3-none-any.whl → 0.1.33.dev2__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/TemplateLoader.py +24 -0
- edsl/__init__.py +8 -4
- edsl/agents/Agent.py +46 -14
- edsl/agents/AgentList.py +43 -0
- edsl/agents/Invigilator.py +125 -212
- edsl/agents/InvigilatorBase.py +140 -32
- edsl/agents/PromptConstructionMixin.py +43 -66
- edsl/agents/__init__.py +1 -0
- edsl/auto/AutoStudy.py +117 -0
- edsl/auto/StageBase.py +230 -0
- edsl/auto/StageGenerateSurvey.py +178 -0
- edsl/auto/StageLabelQuestions.py +125 -0
- edsl/auto/StagePersona.py +61 -0
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
- edsl/auto/StagePersonaDimensionValues.py +74 -0
- edsl/auto/StagePersonaDimensions.py +69 -0
- edsl/auto/StageQuestions.py +73 -0
- edsl/auto/SurveyCreatorPipeline.py +21 -0
- edsl/auto/utilities.py +224 -0
- edsl/config.py +38 -39
- edsl/coop/PriceFetcher.py +58 -0
- edsl/coop/coop.py +39 -5
- edsl/data/Cache.py +35 -1
- edsl/data_transfer_models.py +120 -38
- edsl/enums.py +2 -0
- edsl/exceptions/language_models.py +25 -1
- edsl/exceptions/questions.py +62 -5
- edsl/exceptions/results.py +4 -0
- edsl/inference_services/AnthropicService.py +13 -11
- edsl/inference_services/AwsBedrock.py +19 -17
- edsl/inference_services/AzureAI.py +37 -20
- edsl/inference_services/GoogleService.py +16 -12
- edsl/inference_services/GroqService.py +2 -0
- edsl/inference_services/InferenceServiceABC.py +24 -0
- edsl/inference_services/MistralAIService.py +120 -0
- edsl/inference_services/OpenAIService.py +41 -50
- edsl/inference_services/TestService.py +71 -0
- edsl/inference_services/models_available_cache.py +0 -6
- edsl/inference_services/registry.py +4 -0
- edsl/jobs/Answers.py +10 -12
- edsl/jobs/FailedQuestion.py +78 -0
- edsl/jobs/Jobs.py +18 -13
- edsl/jobs/buckets/TokenBucket.py +39 -14
- edsl/jobs/interviews/Interview.py +297 -77
- edsl/jobs/interviews/InterviewExceptionEntry.py +83 -19
- edsl/jobs/interviews/interview_exception_tracking.py +0 -70
- edsl/jobs/interviews/retry_management.py +3 -1
- edsl/jobs/runners/JobsRunnerAsyncio.py +116 -70
- edsl/jobs/runners/JobsRunnerStatusMixin.py +1 -1
- edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
- edsl/jobs/tasks/TaskHistory.py +131 -213
- edsl/language_models/LanguageModel.py +239 -129
- edsl/language_models/ModelList.py +2 -2
- edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
- edsl/language_models/fake_openai_call.py +15 -0
- edsl/language_models/fake_openai_service.py +61 -0
- edsl/language_models/registry.py +15 -2
- edsl/language_models/repair.py +0 -19
- edsl/language_models/utilities.py +61 -0
- edsl/prompts/Prompt.py +52 -2
- edsl/questions/AnswerValidatorMixin.py +23 -26
- edsl/questions/QuestionBase.py +273 -242
- edsl/questions/QuestionBaseGenMixin.py +133 -0
- edsl/questions/QuestionBasePromptsMixin.py +266 -0
- edsl/questions/QuestionBudget.py +6 -0
- edsl/questions/QuestionCheckBox.py +227 -35
- edsl/questions/QuestionExtract.py +98 -27
- edsl/questions/QuestionFreeText.py +46 -29
- edsl/questions/QuestionFunctional.py +7 -0
- edsl/questions/QuestionList.py +141 -22
- edsl/questions/QuestionMultipleChoice.py +173 -64
- edsl/questions/QuestionNumerical.py +87 -46
- edsl/questions/QuestionRank.py +182 -24
- edsl/questions/RegisterQuestionsMeta.py +31 -12
- edsl/questions/ResponseValidatorABC.py +169 -0
- edsl/questions/__init__.py +3 -4
- edsl/questions/decorators.py +21 -0
- edsl/questions/derived/QuestionLikertFive.py +10 -5
- edsl/questions/derived/QuestionLinearScale.py +11 -1
- edsl/questions/derived/QuestionTopK.py +6 -0
- edsl/questions/derived/QuestionYesNo.py +16 -1
- edsl/questions/descriptors.py +43 -7
- edsl/questions/prompt_templates/question_budget.jinja +13 -0
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
- edsl/questions/prompt_templates/question_extract.jinja +11 -0
- edsl/questions/prompt_templates/question_free_text.jinja +3 -0
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
- edsl/questions/prompt_templates/question_list.jinja +17 -0
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
- edsl/questions/prompt_templates/question_numerical.jinja +37 -0
- edsl/questions/question_registry.py +6 -2
- edsl/questions/templates/__init__.py +0 -0
- edsl/questions/templates/checkbox/__init__.py +0 -0
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
- edsl/questions/templates/extract/answering_instructions.jinja +7 -0
- edsl/questions/templates/extract/question_presentation.jinja +1 -0
- edsl/questions/templates/free_text/__init__.py +0 -0
- edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
- edsl/questions/templates/free_text/question_presentation.jinja +1 -0
- edsl/questions/templates/likert_five/__init__.py +0 -0
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
- edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
- edsl/questions/templates/linear_scale/__init__.py +0 -0
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
- edsl/questions/templates/list/__init__.py +0 -0
- edsl/questions/templates/list/answering_instructions.jinja +4 -0
- edsl/questions/templates/list/question_presentation.jinja +5 -0
- edsl/questions/templates/multiple_choice/__init__.py +0 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
- edsl/questions/templates/multiple_choice/html.jinja +0 -0
- edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
- edsl/questions/templates/numerical/__init__.py +0 -0
- edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
- edsl/questions/templates/numerical/question_presentation.jinja +7 -0
- edsl/questions/templates/rank/answering_instructions.jinja +11 -0
- edsl/questions/templates/rank/question_presentation.jinja +15 -0
- edsl/questions/templates/top_k/__init__.py +0 -0
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
- edsl/questions/templates/top_k/question_presentation.jinja +22 -0
- edsl/questions/templates/yes_no/__init__.py +0 -0
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
- edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
- edsl/results/Dataset.py +20 -0
- edsl/results/DatasetExportMixin.py +41 -47
- edsl/results/DatasetTree.py +145 -0
- edsl/results/Result.py +32 -5
- edsl/results/Results.py +131 -45
- edsl/results/ResultsDBMixin.py +3 -3
- edsl/results/Selector.py +118 -0
- edsl/results/tree_explore.py +115 -0
- edsl/scenarios/Scenario.py +10 -4
- edsl/scenarios/ScenarioList.py +348 -39
- edsl/scenarios/ScenarioListExportMixin.py +9 -0
- edsl/study/SnapShot.py +8 -1
- edsl/surveys/RuleCollection.py +2 -2
- edsl/surveys/Survey.py +634 -315
- edsl/surveys/SurveyExportMixin.py +71 -9
- edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
- edsl/surveys/SurveyQualtricsImport.py +75 -4
- edsl/surveys/instructions/ChangeInstruction.py +47 -0
- edsl/surveys/instructions/Instruction.py +34 -0
- edsl/surveys/instructions/InstructionCollection.py +77 -0
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/templates/error_reporting/base.html +24 -0
- edsl/templates/error_reporting/exceptions_by_model.html +35 -0
- edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
- edsl/templates/error_reporting/exceptions_by_type.html +17 -0
- edsl/templates/error_reporting/interview_details.html +111 -0
- edsl/templates/error_reporting/interviews.html +10 -0
- edsl/templates/error_reporting/overview.html +5 -0
- edsl/templates/error_reporting/performance_plot.html +2 -0
- edsl/templates/error_reporting/report.css +74 -0
- edsl/templates/error_reporting/report.html +118 -0
- edsl/templates/error_reporting/report.js +25 -0
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/METADATA +4 -2
- edsl-0.1.33.dev2.dist-info/RECORD +289 -0
- edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
- edsl/utilities/gcp_bucket/simple_example.py +0 -9
- edsl-0.1.33.dev1.dist-info/RECORD +0 -209
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/LICENSE +0 -0
- {edsl-0.1.33.dev1.dist-info → edsl-0.1.33.dev2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
from textwrap import dedent
|
2
|
+
from dataclasses import dataclass
|
3
|
+
|
4
|
+
from typing import List, Dict
|
5
|
+
|
6
|
+
from edsl.auto.StageBase import StageBase
|
7
|
+
from edsl.auto.StageBase import FlowDataBase
|
8
|
+
|
9
|
+
from edsl.auto.StagePersonaDimensions import StagePersonaDimensions
|
10
|
+
from edsl import Model
|
11
|
+
from edsl.questions import QuestionList, QuestionExtract
|
12
|
+
from edsl.scenarios import Scenario
|
13
|
+
|
14
|
+
from edsl.auto.utilities import gen_pipeline
|
15
|
+
|
16
|
+
|
17
|
+
class StagePersonaDimensionValues(StageBase):
|
18
|
+
input = StagePersonaDimensions.output
|
19
|
+
|
20
|
+
@dataclass
|
21
|
+
class Output(FlowDataBase):
|
22
|
+
attribute_results: List[str]
|
23
|
+
dimension_values: Dict[str, str]
|
24
|
+
persona: str
|
25
|
+
|
26
|
+
output = Output
|
27
|
+
|
28
|
+
def handle_data(self, data):
|
29
|
+
attribute_results = data.attribute_results
|
30
|
+
persona = data.persona
|
31
|
+
m = Model()
|
32
|
+
q = QuestionExtract(
|
33
|
+
question_text=dedent(
|
34
|
+
"""\
|
35
|
+
This is a persona: "{{ persona }}"
|
36
|
+
They vary on the following dimensions: "{{ attribute_results }}"
|
37
|
+
For each dimenion, what are some values that this persona might have for that dimension?
|
38
|
+
Please keep answers very short, ideally one word.
|
39
|
+
"""
|
40
|
+
),
|
41
|
+
answer_template={k: None for k in attribute_results},
|
42
|
+
question_name="dimension_values",
|
43
|
+
)
|
44
|
+
results = (
|
45
|
+
q.by(Scenario({"attribute_results": attribute_results, "persona": persona}))
|
46
|
+
.by(m)
|
47
|
+
.run()
|
48
|
+
)
|
49
|
+
results.select("attribute_results", "dimension_values").print()
|
50
|
+
return self.output(
|
51
|
+
dimension_values=results.select("dimension_values").first(),
|
52
|
+
attribute_results=attribute_results,
|
53
|
+
persona=persona,
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
if __name__ == "__main__":
|
58
|
+
from edsl.auto.StageQuestions import StageQuestions
|
59
|
+
from edsl.auto.StagePersona import StagePersona
|
60
|
+
from edsl.auto.StagePersonaDimensions import StagePersonaDimensions
|
61
|
+
|
62
|
+
pipeline = gen_pipeline(
|
63
|
+
[
|
64
|
+
StageQuestions,
|
65
|
+
StagePersona,
|
66
|
+
StagePersonaDimensions,
|
67
|
+
StagePersonaDimensionValues,
|
68
|
+
]
|
69
|
+
)
|
70
|
+
pipeline.process(
|
71
|
+
pipeline.input(
|
72
|
+
overall_question="What are some factors that could determine whether someone likes ice cream?"
|
73
|
+
)
|
74
|
+
)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from textwrap import dedent
|
2
|
+
from dataclasses import dataclass
|
3
|
+
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
from edsl.auto.StageBase import StageBase
|
7
|
+
from edsl.auto.StageBase import FlowDataBase
|
8
|
+
|
9
|
+
from edsl.auto.StagePersona import StagePersona
|
10
|
+
|
11
|
+
from edsl.questions import QuestionList
|
12
|
+
from edsl.scenarios import Scenario
|
13
|
+
from edsl import Model
|
14
|
+
|
15
|
+
from edsl.auto.utilities import gen_pipeline
|
16
|
+
|
17
|
+
|
18
|
+
class StagePersonaDimensions(StageBase):
|
19
|
+
input = StagePersona.output
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class Output(FlowDataBase):
|
23
|
+
attribute_results: List[str]
|
24
|
+
persona: str
|
25
|
+
|
26
|
+
output = Output
|
27
|
+
|
28
|
+
def handle_data(self, data):
|
29
|
+
q_attributes = QuestionList(
|
30
|
+
question_text=dedent(
|
31
|
+
"""\
|
32
|
+
Here is a persona: "{{ persona }}"
|
33
|
+
It was construced to be someone who could answer these questions: "{{ questions }}"
|
34
|
+
|
35
|
+
We want to identify the general dimensions that make up this persona.
|
36
|
+
E.g., if the person is desribed as 'happy' then a dimenion would be 'mood'
|
37
|
+
"""
|
38
|
+
),
|
39
|
+
question_name="find_attributes",
|
40
|
+
)
|
41
|
+
m = Model()
|
42
|
+
results = (
|
43
|
+
q_attributes.by(
|
44
|
+
Scenario({"persona": data.persona, "questions": data.questions})
|
45
|
+
)
|
46
|
+
.by(m)
|
47
|
+
.run()
|
48
|
+
)
|
49
|
+
(
|
50
|
+
results.select("find_attributes").print(
|
51
|
+
pretty_labels={
|
52
|
+
"answer.find_attributes": f'Persona dimensions for: "{data.persona}"'
|
53
|
+
},
|
54
|
+
split_at_dot=False,
|
55
|
+
)
|
56
|
+
)
|
57
|
+
attribute_results = results.select("find_attributes").first()
|
58
|
+
return self.output(attribute_results=attribute_results, persona=data.persona)
|
59
|
+
|
60
|
+
|
61
|
+
if __name__ == "__main__":
|
62
|
+
from edsl.auto.StageQuestions import StageQuestions
|
63
|
+
|
64
|
+
pipeline = gen_pipeline([StageQuestions, StagePersona, StagePersonaDimensions])
|
65
|
+
pipeline.process(
|
66
|
+
pipeline.input(
|
67
|
+
overall_question="What are some factors that could determine whether someone likes ice cream?"
|
68
|
+
)
|
69
|
+
)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import List
|
3
|
+
from textwrap import dedent
|
4
|
+
|
5
|
+
|
6
|
+
from edsl import Scenario
|
7
|
+
from edsl import Model
|
8
|
+
from edsl.questions.QuestionList import QuestionList
|
9
|
+
|
10
|
+
from edsl.auto.StageBase import StageBase
|
11
|
+
from edsl.auto.StageBase import FlowDataBase
|
12
|
+
|
13
|
+
from edsl.auto.utilities import gen_pipeline
|
14
|
+
|
15
|
+
|
16
|
+
class StageQuestions(StageBase):
|
17
|
+
"This stages takes as input an overall question and returns a list of questions"
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class Input(FlowDataBase):
|
21
|
+
overall_question: str
|
22
|
+
population: str
|
23
|
+
|
24
|
+
@dataclass
|
25
|
+
class Output(FlowDataBase):
|
26
|
+
questions: List[str]
|
27
|
+
population: str
|
28
|
+
|
29
|
+
input = Input
|
30
|
+
output = Output
|
31
|
+
|
32
|
+
def handle_data(self, data):
|
33
|
+
m = Model()
|
34
|
+
overall_question = data.overall_question
|
35
|
+
population = data.population
|
36
|
+
s = Scenario({"overall_question": overall_question, "population": population})
|
37
|
+
q = QuestionList(
|
38
|
+
question_text=dedent(
|
39
|
+
"""\
|
40
|
+
Suppose I am interested in the question:
|
41
|
+
"{{ overall_question }}"
|
42
|
+
What would be some survey questions I could ask to {{ population }} that might shed light on this question?
|
43
|
+
"""
|
44
|
+
),
|
45
|
+
question_name="questions",
|
46
|
+
)
|
47
|
+
results = q.by(s).by(m).run()
|
48
|
+
(
|
49
|
+
results.select("questions").print(
|
50
|
+
pretty_labels={
|
51
|
+
"answer.questions": f'Questions for overall question: "{overall_question }"'
|
52
|
+
},
|
53
|
+
split_at_dot=False,
|
54
|
+
)
|
55
|
+
)
|
56
|
+
|
57
|
+
raw_questions = results.select("questions").first()
|
58
|
+
questions = [q.replace("'", "").replace(":", "") for q in raw_questions]
|
59
|
+
return self.Output(questions=questions, population=population)
|
60
|
+
|
61
|
+
|
62
|
+
if __name__ == "__main__":
|
63
|
+
pipeline = gen_pipeline([StageQuestions])
|
64
|
+
|
65
|
+
pipeline.process(
|
66
|
+
pipeline.input(
|
67
|
+
overall_question="What are some factors that could determine whether someone likes ice cream?",
|
68
|
+
population="Consumers",
|
69
|
+
)
|
70
|
+
)
|
71
|
+
StageQuestions.func(
|
72
|
+
overall_question="Why aren't my students studying more?", population="Tech"
|
73
|
+
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import random
|
2
|
+
from typing import Dict, List, Any, TypeVar, Generator, Optional
|
3
|
+
|
4
|
+
from textwrap import dedent
|
5
|
+
|
6
|
+
# from edsl.language_models.model_interfaces.LanguageModelOpenAIFour import LanguageModelOpenAIFour
|
7
|
+
from edsl import Model
|
8
|
+
from edsl.agents.AgentList import AgentList
|
9
|
+
from edsl.results.Results import Results
|
10
|
+
from edsl import Agent
|
11
|
+
|
12
|
+
from edsl import Scenario
|
13
|
+
from edsl.surveys.Survey import Survey
|
14
|
+
|
15
|
+
from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
|
16
|
+
from edsl.questions.QuestionFreeText import QuestionFreeText
|
17
|
+
from edsl.auto.utilities import gen_pipeline
|
18
|
+
from edsl.conjure.naming_utilities import sanitize_string
|
19
|
+
|
20
|
+
|
21
|
+
m = Model()
|
edsl/auto/utilities.py
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
from textwrap import dedent
|
2
|
+
import random
|
3
|
+
from typing import List, TypeVar, Generator, Optional
|
4
|
+
from edsl.auto.StageBase import StageBase
|
5
|
+
from edsl.conjure.naming_utilities import sanitize_string
|
6
|
+
from edsl import Agent, Survey, Model, Cache, AgentList
|
7
|
+
from edsl import QuestionFreeText, Scenario
|
8
|
+
from edsl import QuestionMultipleChoice, Scenario, Agent, ScenarioList
|
9
|
+
|
10
|
+
StageClassType = TypeVar("StageClassType", bound=StageBase)
|
11
|
+
|
12
|
+
|
13
|
+
def gen_pipeline(stages_list: List[StageClassType]) -> StageBase:
|
14
|
+
"""Takes as input a list of Stage classes & returns a pipeline of instantiated stages.
|
15
|
+
A pipeline is a linked list of stages where each stage has a next_stage attribute.
|
16
|
+
|
17
|
+
"""
|
18
|
+
pipeline = stages_list[0]()
|
19
|
+
last_stage = pipeline
|
20
|
+
for stage in stages_list[1:]:
|
21
|
+
while last_stage.next_stage is not None: # find the end of the pipeline
|
22
|
+
last_stage = last_stage.next_stage
|
23
|
+
stage_to_add = stage()
|
24
|
+
last_stage.next_stage = stage_to_add
|
25
|
+
return pipeline
|
26
|
+
|
27
|
+
|
28
|
+
q_eligibility = QuestionMultipleChoice(
|
29
|
+
question_text=dedent(
|
30
|
+
"""\
|
31
|
+
Consider this set of question: '{{ questions }}'.
|
32
|
+
Consider this persona: '{{ persona }}'.
|
33
|
+
Would this persona be able to answer all of these questions?
|
34
|
+
"""
|
35
|
+
),
|
36
|
+
question_options=["No", "Yes"],
|
37
|
+
question_name="eligibility",
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
def agent_list_eligibility(
|
42
|
+
agent_list: AgentList,
|
43
|
+
survey: Optional[Survey] = None,
|
44
|
+
model: Optional[Model] = None,
|
45
|
+
cache: Optional[Cache] = None,
|
46
|
+
) -> List[bool]:
|
47
|
+
"""
|
48
|
+
Returns whether each agent in a list is elgible for a survey i.e., can answer every question.
|
49
|
+
|
50
|
+
>>> from edsl.language_models import LanguageModel
|
51
|
+
>>> m = LanguageModel.example(canned_response = "1", test_model = True)
|
52
|
+
>>> agent_list_eligibility(AgentList.example())
|
53
|
+
[True, True]
|
54
|
+
>>> agent_list_eligibility(AgentList.example().add_trait('persona', 2*["Cool dude"]), survey = Survey.example(), model = m)
|
55
|
+
[True, True]
|
56
|
+
"""
|
57
|
+
if survey is None:
|
58
|
+
return [True] * len(agent_list)
|
59
|
+
if "persona" not in agent_list.all_traits:
|
60
|
+
raise ValueError(
|
61
|
+
f"Each agent needs to have a persona attribute; traits are {agent_list.all_traits}"
|
62
|
+
)
|
63
|
+
sl = agent_list.select("persona").to_scenario_list()
|
64
|
+
sl.add_value("questions", [q.question_text for q in survey._questions])
|
65
|
+
results = q_eligibility.by(sl).by(model).run(cache=cache)
|
66
|
+
return [r == "Yes" for r in results.select("eligibility").to_list()]
|
67
|
+
|
68
|
+
|
69
|
+
def agent_eligibility(
|
70
|
+
agent: Agent,
|
71
|
+
survey: Survey,
|
72
|
+
model: Optional[Model] = None,
|
73
|
+
cache: Optional[Cache] = None,
|
74
|
+
) -> bool:
|
75
|
+
"""NB: This could be parallelized.
|
76
|
+
|
77
|
+
>>> from edsl.language_models import LanguageModel
|
78
|
+
>>> m = LanguageModel.example(canned_response = "1", test_model = True)
|
79
|
+
>>> agent_eligibility(agent = Agent.example().add_trait({'persona': "Persona"}), survey = Survey.example(), model = m)
|
80
|
+
True
|
81
|
+
|
82
|
+
"""
|
83
|
+
model = model or Model()
|
84
|
+
|
85
|
+
questions = [q.question_text for q in survey._questions]
|
86
|
+
persona = agent.traits["persona"]
|
87
|
+
return (
|
88
|
+
q_eligibility(model=model, questions=questions, persona=persona, cache=cache)
|
89
|
+
== "Yes"
|
90
|
+
)
|
91
|
+
# results = (
|
92
|
+
# q.by(model)
|
93
|
+
# .by(Scenario({"questions": questions, "persona": persona}))
|
94
|
+
# .run(cache=cache)
|
95
|
+
# )
|
96
|
+
# return results.select("eligibility").first() == "Yes"
|
97
|
+
|
98
|
+
|
99
|
+
def gen_agent_traits(dimension_dict: dict, seed_value: Optional[str] = None):
|
100
|
+
"""
|
101
|
+
>>> dimension_dict = {'attitude':['positive', 'negative']}
|
102
|
+
>>> ag = gen_agent_traits(dimension_dict)
|
103
|
+
>>> a = next(ag)
|
104
|
+
>>> a == {'attitude': 'positive'} or a == {'attitude': 'negative'}
|
105
|
+
True
|
106
|
+
>>> len([next(ag) for _ in range(100)])
|
107
|
+
100
|
108
|
+
"""
|
109
|
+
if seed_value is None:
|
110
|
+
seed_value = "edsl"
|
111
|
+
|
112
|
+
random.seed(seed_value)
|
113
|
+
|
114
|
+
while True:
|
115
|
+
new_agent_traits = {}
|
116
|
+
for key, list_of_values in dimension_dict.items():
|
117
|
+
new_agent_traits[key] = random.choice(list_of_values)
|
118
|
+
yield new_agent_traits
|
119
|
+
|
120
|
+
|
121
|
+
def agent_generator(
|
122
|
+
persona: str,
|
123
|
+
dimension_dict: dict,
|
124
|
+
model: Optional[Model] = None,
|
125
|
+
cache: Optional["Cache"] = None,
|
126
|
+
) -> Generator["Results", None, None]:
|
127
|
+
"""
|
128
|
+
>>> from edsl.language_models import LanguageModel
|
129
|
+
>>> m = LanguageModel.example(canned_response = "This is a cool dude.", test_model = True)
|
130
|
+
>>> ag = agent_generator(persona = "Base person", dimension_dict = {'attitude':['Positive', 'Negative']}, model = m)
|
131
|
+
>>> next(ag).select('new_agent_persona').first()
|
132
|
+
'This is a cool dude.'
|
133
|
+
>>> next(ag).select('new_agent_persona').first()
|
134
|
+
'This is a cool dude.'
|
135
|
+
"""
|
136
|
+
|
137
|
+
if model is None:
|
138
|
+
model = Model()
|
139
|
+
|
140
|
+
q = QuestionFreeText(
|
141
|
+
question_text=dedent(
|
142
|
+
"""\
|
143
|
+
Consider this persona: '{{ persona }}'.
|
144
|
+
Now imagine writing a new persona with these traits:
|
145
|
+
'{{ new_agent_traits }}'
|
146
|
+
Please write this persona as a narrative.
|
147
|
+
"""
|
148
|
+
),
|
149
|
+
question_name="new_agent_persona",
|
150
|
+
)
|
151
|
+
agent_trait_generator = gen_agent_traits(dimension_dict)
|
152
|
+
codebook = {sanitize_string(k): k for k in dimension_dict.keys()}
|
153
|
+
while True:
|
154
|
+
new_agent_traits = next(agent_trait_generator)
|
155
|
+
yield q(
|
156
|
+
persona=persona,
|
157
|
+
new_agent_traits=new_agent_traits,
|
158
|
+
codebook=codebook,
|
159
|
+
just_answer=False,
|
160
|
+
cache=cache,
|
161
|
+
model=model,
|
162
|
+
)
|
163
|
+
|
164
|
+
|
165
|
+
def create_agents(
|
166
|
+
agent_generator: Generator["Results", None, None],
|
167
|
+
survey: Optional[Survey] = None,
|
168
|
+
num_agents=11,
|
169
|
+
) -> AgentList:
|
170
|
+
"""
|
171
|
+
>>> from edsl.language_models import LanguageModel
|
172
|
+
>>> m = LanguageModel.example(canned_response = "This is a cool dude.", test_model = True)
|
173
|
+
>>> ag = agent_generator(persona = "Base person", dimension_dict = {'attitude':['Positive', 'Negative']}, model = m)
|
174
|
+
>>> new_agent_list = create_agents(agent_generator = ag)
|
175
|
+
>>> new_agent_list
|
176
|
+
|
177
|
+
"""
|
178
|
+
agent_list = AgentList([])
|
179
|
+
|
180
|
+
MAX_ITERATIONS_MULTIPLIER = 2
|
181
|
+
iterations = 0
|
182
|
+
|
183
|
+
while len(agent_list) < num_agents:
|
184
|
+
iterations += 1
|
185
|
+
candidate_agent = next(agent_generator)
|
186
|
+
codebook = candidate_agent.select("codebook").to_list()[0]
|
187
|
+
|
188
|
+
koobedoc = {v: k for k, v in codebook.items()}
|
189
|
+
persona = candidate_agent.select("new_agent_persona").to_list()[0]
|
190
|
+
traits = candidate_agent.select("new_agent_traits").to_list()[0]
|
191
|
+
new_traits = {koobedoc[key]: value for key, value in traits.items()} | {
|
192
|
+
"persona": persona
|
193
|
+
}
|
194
|
+
agent = Agent(traits=new_traits, codebook=codebook)
|
195
|
+
if survey is not None:
|
196
|
+
if agent_eligibility(agent, survey):
|
197
|
+
agent_list.append(agent)
|
198
|
+
else:
|
199
|
+
print("Agent not eligible")
|
200
|
+
else:
|
201
|
+
agent_list.append(agent)
|
202
|
+
|
203
|
+
if iterations > MAX_ITERATIONS_MULTIPLIER * num_agents:
|
204
|
+
raise Exception("Too many failures")
|
205
|
+
|
206
|
+
return agent_list
|
207
|
+
|
208
|
+
|
209
|
+
if __name__ == "__main__":
|
210
|
+
import doctest
|
211
|
+
|
212
|
+
doctest.testmod()
|
213
|
+
# from edsl.language_models import LanguageModel
|
214
|
+
|
215
|
+
# m = LanguageModel.example(canned_response="This is a cool dude.", test_model=True)
|
216
|
+
# ag = agent_generator(
|
217
|
+
# persona="Base person",
|
218
|
+
# dimension_dict={"attitude": ["Positive", "Negative"]},
|
219
|
+
# model=m,
|
220
|
+
# )
|
221
|
+
# example = [next(ag).select("new_agent_persona").first() for _ in range(10)]
|
222
|
+
# dimension_dict = {"attitude": ["positive", "negative"]}
|
223
|
+
# ag = gen_agent_traits(dimension_dict)
|
224
|
+
# example = [next(ag) for _ in range(100)]
|
edsl/config.py
CHANGED
@@ -41,43 +41,34 @@ CONFIG_MAP = {
|
|
41
41
|
"default": "5",
|
42
42
|
"info": "This env var determines the maximum number of times to retry a failed API call.",
|
43
43
|
},
|
44
|
+
"EDSL_DEFAULT_MODEL": {
|
45
|
+
"default": "gpt-4o",
|
46
|
+
"info": "This env var holds the default model name.",
|
47
|
+
},
|
48
|
+
"EDSL_SERVICE_TPM_BASELINE": {
|
49
|
+
"default": "2000000",
|
50
|
+
"info": "This env var holds the maximum number of tokens per minute for all models. Model-specific values such as EDSL_SERVICE_TPM_OPENAI will override this.",
|
51
|
+
},
|
52
|
+
"EDSL_SERVICE_RPM_BASELINE": {
|
53
|
+
"default": "100",
|
54
|
+
"info": "This env var holds the maximum number of requests per minute for OpenAI. Model-specific values such as EDSL_SERVICE_RPM_OPENAI will override this.",
|
55
|
+
},
|
56
|
+
"EDSL_SERVICE_TPM_OPENAI": {
|
57
|
+
"default": "2000000",
|
58
|
+
"info": "This env var holds the maximum number of tokens per minute for OpenAI.",
|
59
|
+
},
|
60
|
+
"EDSL_SERVICE_RPM_OPENAI": {
|
61
|
+
"default": "100",
|
62
|
+
"info": "This env var holds the maximum number of requests per minute for OpenAI.",
|
63
|
+
},
|
64
|
+
"EDSL_FETCH_TOKEN_PRICES": {
|
65
|
+
"default": "True",
|
66
|
+
"info": "Whether to fetch the prices for tokens",
|
67
|
+
},
|
44
68
|
"EXPECTED_PARROT_URL": {
|
45
69
|
"default": "https://www.expectedparrot.com",
|
46
70
|
"info": "This env var holds the URL of the Expected Parrot API.",
|
47
71
|
},
|
48
|
-
# "EXPECTED_PARROT_API_KEY": {
|
49
|
-
# "default": None,
|
50
|
-
# "info": "This env var holds your Expected Parrot API key (https://www.expectedparrot.com/).",
|
51
|
-
# },
|
52
|
-
# "OPENAI_API_KEY": {
|
53
|
-
# "default": None,
|
54
|
-
# "info": "This env var holds your OpenAI API key (https://platform.openai.com/api-keys).",
|
55
|
-
# },
|
56
|
-
# "DEEP_INFRA_API_KEY": {
|
57
|
-
# "default": None,
|
58
|
-
# "info": "This env var holds your DeepInfra API key (https://deepinfra.com/).",
|
59
|
-
# },
|
60
|
-
# "GOOGLE_API_KEY": {
|
61
|
-
# "default": None,
|
62
|
-
# "info": "This env var holds your Google API key (https://console.cloud.google.com/apis/credentials).",
|
63
|
-
# },
|
64
|
-
# "ANTHROPIC_API_KEY": {
|
65
|
-
# "default": None,
|
66
|
-
# "info": "This env var holds your Anthropic API key (https://www.anthropic.com/).",
|
67
|
-
# },
|
68
|
-
# "GROQ_API_KEY": {
|
69
|
-
# "default": None,
|
70
|
-
# "info": "This env var holds your GROQ API key (https://console.groq.com/login).",
|
71
|
-
# },
|
72
|
-
# "AWS_ACCESS_KEY_ID" :
|
73
|
-
# "default": None,
|
74
|
-
# "info": "This env var holds your AWS access key ID.",
|
75
|
-
# "AWS_SECRET_ACCESS_KEY:
|
76
|
-
# "default": None,
|
77
|
-
# "info": "This env var holds your AWS secret access key.",
|
78
|
-
# "AZURE_ENDPOINT_URL_AND_KEY":
|
79
|
-
# "default": None,
|
80
|
-
# "info": "This env var holds your Azure endpoint URL and key (URL:key). You can have several comma-separated URL-key pairs (URL1:key1,URL2:key2).",
|
81
72
|
}
|
82
73
|
|
83
74
|
|
@@ -92,7 +83,7 @@ class Config:
|
|
92
83
|
|
93
84
|
def _set_run_mode(self) -> None:
|
94
85
|
"""
|
95
|
-
|
86
|
+
Sets EDSL_RUN_MODE as a class attribute.
|
96
87
|
"""
|
97
88
|
run_mode = os.getenv("EDSL_RUN_MODE")
|
98
89
|
default = CONFIG_MAP.get("EDSL_RUN_MODE").get("default")
|
@@ -107,27 +98,35 @@ class Config:
|
|
107
98
|
def _load_dotenv(self) -> None:
|
108
99
|
"""
|
109
100
|
Loads the .env
|
110
|
-
-
|
101
|
+
- The .env will override existing env vars **unless** EDSL_RUN_MODE=="development-testrun"
|
111
102
|
"""
|
112
103
|
|
113
|
-
override = True
|
114
104
|
if self.EDSL_RUN_MODE == "development-testrun":
|
115
105
|
override = False
|
106
|
+
else:
|
107
|
+
override = True
|
116
108
|
_ = load_dotenv(dotenv_path=find_dotenv(usecwd=True), override=override)
|
117
109
|
|
110
|
+
def __contains__(self, env_var: str) -> bool:
|
111
|
+
"""
|
112
|
+
Checks if an env var is set as a class attribute.
|
113
|
+
"""
|
114
|
+
return env_var in self.__dict__
|
115
|
+
|
118
116
|
def _set_env_vars(self) -> None:
|
119
117
|
"""
|
120
|
-
Sets env vars as
|
118
|
+
Sets env vars as class attributes.
|
119
|
+
- EDSL_RUN_MODE is not set my this method, but by _set_run_mode
|
121
120
|
- If an env var is not set and has a default value in the CONFIG_MAP, sets it to the default value.
|
122
121
|
"""
|
123
122
|
# for each env var in the CONFIG_MAP
|
124
123
|
for env_var, config in CONFIG_MAP.items():
|
125
|
-
#
|
124
|
+
# EDSL_RUN_MODE is already set by _set_run_mode
|
126
125
|
if env_var == "EDSL_RUN_MODE":
|
127
126
|
continue
|
128
127
|
value = os.getenv(env_var)
|
129
128
|
default_value = config.get("default")
|
130
|
-
# if
|
129
|
+
# if an env var exists, set it as a class attribute
|
131
130
|
if value:
|
132
131
|
setattr(self, env_var, value)
|
133
132
|
# otherwise, if EDSL_RUN_MODE == "production" set it to its default value
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import requests
|
2
|
+
import csv
|
3
|
+
from io import StringIO
|
4
|
+
|
5
|
+
|
6
|
+
class PriceFetcher:
|
7
|
+
_instance = None
|
8
|
+
|
9
|
+
def __new__(cls):
|
10
|
+
if cls._instance is None:
|
11
|
+
cls._instance = super(PriceFetcher, cls).__new__(cls)
|
12
|
+
cls._instance._cached_prices = None
|
13
|
+
return cls._instance
|
14
|
+
|
15
|
+
def fetch_prices(self):
|
16
|
+
if self._cached_prices is not None:
|
17
|
+
return self._cached_prices
|
18
|
+
|
19
|
+
import requests
|
20
|
+
import csv
|
21
|
+
from io import StringIO
|
22
|
+
|
23
|
+
sheet_id = "1SAO3Bhntefl0XQHJv27rMxpvu6uzKDWNXFHRa7jrUDs"
|
24
|
+
|
25
|
+
# Construct the URL to fetch the CSV
|
26
|
+
url = f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv"
|
27
|
+
|
28
|
+
try:
|
29
|
+
# Fetch the CSV data
|
30
|
+
response = requests.get(url)
|
31
|
+
response.raise_for_status() # Raise an exception for bad responses
|
32
|
+
|
33
|
+
# Parse the CSV data
|
34
|
+
csv_data = StringIO(response.text)
|
35
|
+
reader = csv.reader(csv_data)
|
36
|
+
|
37
|
+
# Convert to list of dictionaries
|
38
|
+
headers = next(reader)
|
39
|
+
data = [dict(zip(headers, row)) for row in reader]
|
40
|
+
|
41
|
+
# self._cached_prices = data
|
42
|
+
# return data
|
43
|
+
price_lookup = {}
|
44
|
+
for entry in data:
|
45
|
+
service = entry.get("service", None)
|
46
|
+
model = entry.get("model", None)
|
47
|
+
if service and model:
|
48
|
+
token_type = entry.get("token_type", None)
|
49
|
+
if (service, model) in price_lookup:
|
50
|
+
price_lookup[(service, model)].update({token_type: entry})
|
51
|
+
else:
|
52
|
+
price_lookup[(service, model)] = {token_type: entry}
|
53
|
+
self._cached_prices = price_lookup
|
54
|
+
return self._cached_prices
|
55
|
+
|
56
|
+
except requests.RequestException as e:
|
57
|
+
# print(f"An error occurred: {e}")
|
58
|
+
return {}
|