edsl 0.1.37.dev4__py3-none-any.whl → 0.1.37.dev6__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/__version__.py +1 -1
- edsl/agents/Agent.py +86 -35
- edsl/agents/AgentList.py +5 -0
- edsl/agents/InvigilatorBase.py +2 -23
- edsl/agents/PromptConstructor.py +147 -106
- edsl/agents/descriptors.py +17 -4
- edsl/config.py +1 -1
- edsl/conjure/AgentConstructionMixin.py +11 -3
- edsl/conversation/Conversation.py +66 -14
- edsl/conversation/chips.py +95 -0
- edsl/coop/coop.py +134 -3
- edsl/data/Cache.py +1 -1
- edsl/exceptions/BaseException.py +21 -0
- edsl/exceptions/__init__.py +7 -3
- edsl/exceptions/agents.py +17 -19
- edsl/exceptions/results.py +11 -8
- edsl/exceptions/scenarios.py +22 -0
- edsl/exceptions/surveys.py +13 -10
- edsl/inference_services/InferenceServicesCollection.py +32 -9
- edsl/jobs/Jobs.py +265 -53
- edsl/jobs/interviews/InterviewExceptionEntry.py +5 -1
- edsl/jobs/tasks/TaskHistory.py +1 -0
- edsl/language_models/KeyLookup.py +30 -0
- edsl/language_models/LanguageModel.py +47 -59
- edsl/language_models/__init__.py +1 -0
- edsl/prompts/Prompt.py +8 -4
- edsl/questions/QuestionBase.py +53 -13
- edsl/questions/QuestionBasePromptsMixin.py +1 -33
- edsl/questions/QuestionFunctional.py +2 -2
- edsl/questions/descriptors.py +23 -28
- edsl/results/DatasetExportMixin.py +25 -1
- edsl/results/Result.py +16 -1
- edsl/results/Results.py +31 -120
- edsl/results/ResultsDBMixin.py +1 -1
- edsl/results/Selector.py +18 -1
- edsl/scenarios/Scenario.py +48 -12
- edsl/scenarios/ScenarioHtmlMixin.py +7 -2
- edsl/scenarios/ScenarioList.py +12 -1
- edsl/surveys/Rule.py +10 -4
- edsl/surveys/Survey.py +100 -77
- edsl/utilities/utilities.py +18 -0
- {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/METADATA +1 -1
- {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/RECORD +45 -41
- {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/LICENSE +0 -0
- {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/WHEEL +0 -0
@@ -99,6 +99,8 @@ class AgentConstructionMixin:
|
|
99
99
|
sample_size: int = None,
|
100
100
|
seed: str = "edsl",
|
101
101
|
dryrun=False,
|
102
|
+
disable_remote_cache: bool = False,
|
103
|
+
disable_remote_inference: bool = False,
|
102
104
|
) -> Union[Results, None]:
|
103
105
|
"""Return the results of the survey.
|
104
106
|
|
@@ -109,7 +111,7 @@ class AgentConstructionMixin:
|
|
109
111
|
|
110
112
|
>>> from edsl.conjure.InputData import InputDataABC
|
111
113
|
>>> id = InputDataABC.example()
|
112
|
-
>>> r = id.to_results()
|
114
|
+
>>> r = id.to_results(disable_remote_cache = True, disable_remote_inference = True)
|
113
115
|
>>> len(r) == id.num_observations
|
114
116
|
True
|
115
117
|
"""
|
@@ -125,7 +127,10 @@ class AgentConstructionMixin:
|
|
125
127
|
import time
|
126
128
|
|
127
129
|
start = time.time()
|
128
|
-
_ = survey.by(agent_list.sample(DRYRUN_SAMPLE)).run(
|
130
|
+
_ = survey.by(agent_list.sample(DRYRUN_SAMPLE)).run(
|
131
|
+
disable_remote_cache=disable_remote_cache,
|
132
|
+
disable_remote_inference=disable_remote_inference,
|
133
|
+
)
|
129
134
|
end = time.time()
|
130
135
|
print(f"Time to run {DRYRUN_SAMPLE} agents (s): {round(end - start, 2)}")
|
131
136
|
time_per_agent = (end - start) / DRYRUN_SAMPLE
|
@@ -143,7 +148,10 @@ class AgentConstructionMixin:
|
|
143
148
|
f"Full sample will take about {round(full_sample_time / 3600, 2)} hours."
|
144
149
|
)
|
145
150
|
return None
|
146
|
-
return survey.by(agent_list).run(
|
151
|
+
return survey.by(agent_list).run(
|
152
|
+
disable_remote_cache=disable_remote_cache,
|
153
|
+
disable_remote_inference=disable_remote_inference,
|
154
|
+
)
|
147
155
|
|
148
156
|
|
149
157
|
if __name__ == "__main__":
|
@@ -5,7 +5,7 @@ from typing import Optional, Callable
|
|
5
5
|
from edsl import Agent, QuestionFreeText, Results, AgentList, ScenarioList, Scenario
|
6
6
|
from edsl.questions import QuestionBase
|
7
7
|
from edsl.results.Result import Result
|
8
|
-
|
8
|
+
from jinja2 import Template
|
9
9
|
from edsl.data import Cache
|
10
10
|
|
11
11
|
from edsl.conversation.next_speaker_utilities import (
|
@@ -54,6 +54,9 @@ class Conversation:
|
|
54
54
|
"""A conversation between a list of agents. The first agent in the list is the first speaker.
|
55
55
|
After that, order is determined by the next_speaker function.
|
56
56
|
The question asked to each agent is determined by the next_statement_question.
|
57
|
+
|
58
|
+
If the user has passed in a "per_round_message_template", this will be displayed at the beginning of each round.
|
59
|
+
{{ round_message }} must be in the question_text.
|
57
60
|
"""
|
58
61
|
|
59
62
|
def __init__(
|
@@ -64,28 +67,62 @@ class Conversation:
|
|
64
67
|
next_statement_question: Optional[QuestionBase] = None,
|
65
68
|
next_speaker_generator: Optional[Callable] = None,
|
66
69
|
verbose: bool = False,
|
70
|
+
per_round_message_template: Optional[str] = None,
|
67
71
|
conversation_index: Optional[int] = None,
|
68
72
|
cache=None,
|
73
|
+
disable_remote_inference=False,
|
74
|
+
default_model: Optional["LanguageModel"] = None,
|
69
75
|
):
|
76
|
+
self.disable_remote_inference = disable_remote_inference
|
77
|
+
self.per_round_message_template = per_round_message_template
|
78
|
+
|
70
79
|
if cache is None:
|
71
80
|
self.cache = Cache()
|
72
81
|
else:
|
73
82
|
self.cache = cache
|
74
83
|
|
75
84
|
self.agent_list = agent_list
|
85
|
+
|
86
|
+
from edsl import Model
|
87
|
+
|
88
|
+
for agent in self.agent_list:
|
89
|
+
if not hasattr(agent, "model"):
|
90
|
+
if default_model is not None:
|
91
|
+
agent.model = default_model
|
92
|
+
else:
|
93
|
+
agent.model = Model()
|
94
|
+
|
76
95
|
self.verbose = verbose
|
77
96
|
self.agent_statements = []
|
78
97
|
self._conversation_index = conversation_index
|
79
|
-
|
80
98
|
self.agent_statements = AgentStatements()
|
81
99
|
|
82
100
|
self.max_turns = max_turns
|
83
101
|
|
84
102
|
if next_statement_question is None:
|
103
|
+
import textwrap
|
104
|
+
|
105
|
+
base_question = textwrap.dedent(
|
106
|
+
"""\
|
107
|
+
You are {{ agent_name }}. This is the conversation so far: {{ conversation }}
|
108
|
+
{% if round_message is not none %}
|
109
|
+
{{ round_message }}
|
110
|
+
{% endif %}
|
111
|
+
What do you say next?"""
|
112
|
+
)
|
85
113
|
self.next_statement_question = QuestionFreeText(
|
86
|
-
question_text=
|
114
|
+
question_text=base_question,
|
87
115
|
question_name="dialogue",
|
88
116
|
)
|
117
|
+
else:
|
118
|
+
self.next_statement_question = next_statement_question
|
119
|
+
if (
|
120
|
+
per_round_message_template
|
121
|
+
and "{{ round_message }}" not in next_statement_question.question_text
|
122
|
+
):
|
123
|
+
raise ValueError(
|
124
|
+
"If you pass in a per_round_message_template, you must include {{ round_message }} in the question_text."
|
125
|
+
)
|
89
126
|
|
90
127
|
# Determine how the next speaker is chosen
|
91
128
|
if next_speaker_generator is None:
|
@@ -93,6 +130,7 @@ class Conversation:
|
|
93
130
|
else:
|
94
131
|
func = next_speaker_generator
|
95
132
|
|
133
|
+
# Choose the next speaker
|
96
134
|
self.next_speaker = speaker_closure(
|
97
135
|
agent_list=self.agent_list, generator_function=func
|
98
136
|
)
|
@@ -158,18 +196,32 @@ class Conversation:
|
|
158
196
|
}
|
159
197
|
return Scenario(d)
|
160
198
|
|
161
|
-
async def get_next_statement(self, *, index, speaker, conversation):
|
199
|
+
async def get_next_statement(self, *, index, speaker, conversation) -> "Result":
|
200
|
+
"""Get the next statement from the speaker."""
|
162
201
|
q = self.next_statement_question
|
163
|
-
assert q.parameters == {"agent_name", "conversation"}, q.parameters
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
202
|
+
# assert q.parameters == {"agent_name", "conversation"}, q.parameters
|
203
|
+
from edsl import Scenario
|
204
|
+
|
205
|
+
if self.per_round_message_template is None:
|
206
|
+
round_message = None
|
207
|
+
else:
|
208
|
+
round_message = Template(self.per_round_message_template).render(
|
209
|
+
{"max_turns": self.max_turns, "current_turn": index}
|
210
|
+
)
|
211
|
+
|
212
|
+
s = Scenario(
|
213
|
+
{
|
214
|
+
"agent_name": speaker.name,
|
215
|
+
"conversation": conversation,
|
216
|
+
"conversation_index": self.conversation_index,
|
217
|
+
"index": index,
|
218
|
+
"round_message": round_message,
|
219
|
+
}
|
220
|
+
)
|
221
|
+
jobs = q.by(s).by(speaker).by(speaker.model)
|
222
|
+
jobs.show_prompts()
|
223
|
+
results = await jobs.run_async(
|
224
|
+
cache=self.cache, disable_remote_inference=self.disable_remote_inference
|
173
225
|
)
|
174
226
|
return results[0]
|
175
227
|
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from edsl import Agent, AgentList, QuestionFreeText
|
4
|
+
from edsl import Cache
|
5
|
+
from edsl import QuestionList
|
6
|
+
from edsl import Model
|
7
|
+
|
8
|
+
from edsl.conversation.Conversation import Conversation, ConversationList
|
9
|
+
|
10
|
+
m = Model("gemini-1.5-flash")
|
11
|
+
|
12
|
+
|
13
|
+
class ChipLover(Agent):
|
14
|
+
def __init__(self, name, chip_values, initial_chips, model: Optional[Model] = None):
|
15
|
+
self.chip_values = chip_values
|
16
|
+
self.initial_chips = initial_chips
|
17
|
+
self.current_chips = initial_chips
|
18
|
+
self.model = model or Model()
|
19
|
+
super().__init__(
|
20
|
+
name=name,
|
21
|
+
traits={
|
22
|
+
"motivation": f"""
|
23
|
+
You are {name}. You are negotiating the trading of colored 'chips' with other players. You want to maximize your score.
|
24
|
+
When you want to accept a deal, say "DEAL!"
|
25
|
+
Note that different players can have different values for the chips.
|
26
|
+
""",
|
27
|
+
"chip_values": chip_values,
|
28
|
+
"initial_chips": initial_chips,
|
29
|
+
},
|
30
|
+
)
|
31
|
+
|
32
|
+
def trade(self, chips_given_dict, chips_received_dict):
|
33
|
+
for color, amount in chips_given_dict.items():
|
34
|
+
self.current_chips[color] -= amount
|
35
|
+
for color, amount in chips_received_dict.items():
|
36
|
+
self.current_chips[color] += amount
|
37
|
+
|
38
|
+
def get_score(self):
|
39
|
+
return sum(
|
40
|
+
self.chip_values[color] * self.current_chips[color]
|
41
|
+
for color in self.chip_values
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
a1 = ChipLover(
|
46
|
+
name="Alice",
|
47
|
+
chip_values={"Green": 7, "Blue": 1, "Red": 0},
|
48
|
+
model=Model("gemini-1.5-flash"),
|
49
|
+
initial_chips={"Green": 1, "Blue": 2, "Red": 3},
|
50
|
+
)
|
51
|
+
a2 = ChipLover(
|
52
|
+
name="Bob",
|
53
|
+
chip_values={"Green": 7, "Blue": 1, "Red": 0},
|
54
|
+
initial_chips={"Green": 1, "Blue": 2, "Red": 3},
|
55
|
+
)
|
56
|
+
|
57
|
+
c1 = Conversation(agent_list=AgentList([a1, a2]), max_turns=10, verbose=True)
|
58
|
+
c2 = Conversation(agent_list=AgentList([a1, a2]), max_turns=10, verbose=True)
|
59
|
+
|
60
|
+
with Cache() as c:
|
61
|
+
combo = ConversationList([c1, c2], cache=c)
|
62
|
+
combo.run()
|
63
|
+
results = combo.to_results()
|
64
|
+
results.select("conversation_index", "index", "agent_name", "dialogue").print(
|
65
|
+
format="rich"
|
66
|
+
)
|
67
|
+
|
68
|
+
q = QuestionFreeText(
|
69
|
+
question_text="""This was a conversation/negotiation: {{ transcript }}.
|
70
|
+
What trades occurred in the conversation?
|
71
|
+
""",
|
72
|
+
question_name="trades",
|
73
|
+
)
|
74
|
+
|
75
|
+
q_actors = QuestionList(
|
76
|
+
question_text="""Here is a transcript: {{ transcript }}.
|
77
|
+
Who were the actors in the conversation?
|
78
|
+
""",
|
79
|
+
question_name="actors",
|
80
|
+
)
|
81
|
+
|
82
|
+
from edsl import QuestionList
|
83
|
+
|
84
|
+
q_transfers = QuestionList(
|
85
|
+
question_text="""This was a conversation/negotiation: {{ transcript }}.
|
86
|
+
Extract all offers and their outcomes.
|
87
|
+
Use this format: {'proposing_agent':"Alice": 'receiving_agent': "Bob", 'gives':{"Green": 1, "Blue": 2}, 'receives':{"Green": 2, "Blue": 1}, 'accepted':True}
|
88
|
+
""",
|
89
|
+
question_name="transfers",
|
90
|
+
)
|
91
|
+
|
92
|
+
transcript_analysis = (
|
93
|
+
q.add_question(q_actors).add_question(q_transfers).by(combo.summarize()).run()
|
94
|
+
)
|
95
|
+
transcript_analysis.select("trades", "actors", "transfers").print(format="rich")
|
edsl/coop/coop.py
CHANGED
@@ -90,18 +90,83 @@ class Coop:
|
|
90
90
|
|
91
91
|
return response
|
92
92
|
|
93
|
-
def _resolve_server_response(
|
93
|
+
def _resolve_server_response(
|
94
|
+
self, response: requests.Response, check_api_key: bool = True
|
95
|
+
) -> None:
|
94
96
|
"""
|
95
97
|
Check the response from the server and raise errors as appropriate.
|
96
98
|
"""
|
97
99
|
if response.status_code >= 400:
|
98
100
|
message = response.json().get("detail")
|
99
101
|
# print(response.text)
|
100
|
-
if "
|
102
|
+
if "The API key you provided is invalid" in message and check_api_key:
|
103
|
+
import secrets
|
104
|
+
from edsl.utilities.utilities import write_api_key_to_env
|
105
|
+
|
106
|
+
edsl_auth_token = secrets.token_urlsafe(16)
|
107
|
+
|
108
|
+
print("Your Expected Parrot API key is invalid.")
|
109
|
+
print(
|
110
|
+
"\nUse the link below to log in to Expected Parrot so we can automatically update your API key."
|
111
|
+
)
|
112
|
+
self._display_login_url(edsl_auth_token=edsl_auth_token)
|
113
|
+
api_key = self._poll_for_api_key(edsl_auth_token)
|
114
|
+
|
115
|
+
if api_key is None:
|
116
|
+
print("\nTimed out waiting for login. Please try again.")
|
117
|
+
return
|
118
|
+
|
119
|
+
write_api_key_to_env(api_key)
|
120
|
+
print("\n✨ API key retrieved and written to .env file.")
|
121
|
+
print("Rerun your code to try again with a valid API key.")
|
122
|
+
return
|
123
|
+
|
124
|
+
elif "Authorization" in message:
|
101
125
|
print(message)
|
102
126
|
message = "Please provide an Expected Parrot API key."
|
127
|
+
|
103
128
|
raise CoopServerResponseError(message)
|
104
129
|
|
130
|
+
def _poll_for_api_key(
|
131
|
+
self, edsl_auth_token: str, timeout: int = 120
|
132
|
+
) -> Union[str, None]:
|
133
|
+
"""
|
134
|
+
Allows the user to retrieve their Expected Parrot API key by logging in with an EDSL auth token.
|
135
|
+
|
136
|
+
:param edsl_auth_token: The EDSL auth token to use for login
|
137
|
+
:param timeout: Maximum time to wait for login, in seconds (default: 120)
|
138
|
+
"""
|
139
|
+
import time
|
140
|
+
from datetime import datetime
|
141
|
+
|
142
|
+
start_poll_time = time.time()
|
143
|
+
waiting_for_login = True
|
144
|
+
while waiting_for_login:
|
145
|
+
elapsed_time = time.time() - start_poll_time
|
146
|
+
if elapsed_time > timeout:
|
147
|
+
# Timed out waiting for the user to log in
|
148
|
+
print("\r" + " " * 80 + "\r", end="")
|
149
|
+
return None
|
150
|
+
|
151
|
+
api_key = self._get_api_key(edsl_auth_token)
|
152
|
+
if api_key is not None:
|
153
|
+
print("\r" + " " * 80 + "\r", end="")
|
154
|
+
return api_key
|
155
|
+
else:
|
156
|
+
duration = 5
|
157
|
+
time_checked = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
|
158
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
159
|
+
start_time = time.time()
|
160
|
+
i = 0
|
161
|
+
while time.time() - start_time < duration:
|
162
|
+
print(
|
163
|
+
f"\r{frames[i % len(frames)]} Waiting for login. Last checked: {time_checked}",
|
164
|
+
end="",
|
165
|
+
flush=True,
|
166
|
+
)
|
167
|
+
time.sleep(0.1)
|
168
|
+
i += 1
|
169
|
+
|
105
170
|
def _json_handle_none(self, value: Any) -> Any:
|
106
171
|
"""
|
107
172
|
Handle None values during JSON serialization.
|
@@ -134,7 +199,7 @@ class Coop:
|
|
134
199
|
response = self._send_server_request(
|
135
200
|
uri="api/v0/edsl-settings", method="GET", timeout=5
|
136
201
|
)
|
137
|
-
self._resolve_server_response(response)
|
202
|
+
self._resolve_server_response(response, check_api_key=False)
|
138
203
|
return response.json()
|
139
204
|
except Timeout:
|
140
205
|
return {}
|
@@ -489,6 +554,7 @@ class Coop:
|
|
489
554
|
description: Optional[str] = None,
|
490
555
|
status: RemoteJobStatus = "queued",
|
491
556
|
visibility: Optional[VisibilityType] = "unlisted",
|
557
|
+
initial_results_visibility: Optional[VisibilityType] = "unlisted",
|
492
558
|
iterations: Optional[int] = 1,
|
493
559
|
) -> dict:
|
494
560
|
"""
|
@@ -517,6 +583,7 @@ class Coop:
|
|
517
583
|
"iterations": iterations,
|
518
584
|
"visibility": visibility,
|
519
585
|
"version": self._edsl_version,
|
586
|
+
"initial_results_visibility": initial_results_visibility,
|
520
587
|
},
|
521
588
|
)
|
522
589
|
self._resolve_server_response(response)
|
@@ -664,6 +731,17 @@ class Coop:
|
|
664
731
|
else:
|
665
732
|
return {}
|
666
733
|
|
734
|
+
def fetch_models(self) -> dict:
|
735
|
+
"""
|
736
|
+
Fetch a dict of available models from Coop.
|
737
|
+
|
738
|
+
Each key in the dict is an inference service, and each value is a list of models from that service.
|
739
|
+
"""
|
740
|
+
response = self._send_server_request(uri="api/v0/models", method="GET")
|
741
|
+
self._resolve_server_response(response)
|
742
|
+
data = response.json()
|
743
|
+
return data
|
744
|
+
|
667
745
|
def fetch_rate_limit_config_vars(self) -> dict:
|
668
746
|
"""
|
669
747
|
Fetch a dict of rate limit config vars from Coop.
|
@@ -678,6 +756,59 @@ class Coop:
|
|
678
756
|
data = response.json()
|
679
757
|
return data
|
680
758
|
|
759
|
+
def _display_login_url(self, edsl_auth_token: str):
|
760
|
+
"""
|
761
|
+
Uses rich.print to display a login URL.
|
762
|
+
|
763
|
+
- We need this function because URL detection with print() does not work alongside animations in VSCode.
|
764
|
+
"""
|
765
|
+
from rich import print as rich_print
|
766
|
+
|
767
|
+
url = f"{CONFIG.EXPECTED_PARROT_URL}/login?edsl_auth_token={edsl_auth_token}"
|
768
|
+
|
769
|
+
rich_print(f"[#38bdf8][link={url}]{url}[/link][/#38bdf8]")
|
770
|
+
|
771
|
+
def _get_api_key(self, edsl_auth_token: str):
|
772
|
+
"""
|
773
|
+
Given an EDSL auth token, find the corresponding user's API key.
|
774
|
+
"""
|
775
|
+
|
776
|
+
response = self._send_server_request(
|
777
|
+
uri="api/v0/get-api-key",
|
778
|
+
method="POST",
|
779
|
+
payload={
|
780
|
+
"edsl_auth_token": edsl_auth_token,
|
781
|
+
},
|
782
|
+
)
|
783
|
+
data = response.json()
|
784
|
+
api_key = data.get("api_key")
|
785
|
+
return api_key
|
786
|
+
|
787
|
+
def login(self):
|
788
|
+
"""
|
789
|
+
Starts the EDSL auth token login flow.
|
790
|
+
"""
|
791
|
+
import secrets
|
792
|
+
from dotenv import load_dotenv
|
793
|
+
from edsl.utilities.utilities import write_api_key_to_env
|
794
|
+
|
795
|
+
edsl_auth_token = secrets.token_urlsafe(16)
|
796
|
+
|
797
|
+
print(
|
798
|
+
"\nUse the link below to log in to Expected Parrot so we can automatically update your API key."
|
799
|
+
)
|
800
|
+
self._display_login_url(edsl_auth_token=edsl_auth_token)
|
801
|
+
api_key = self._poll_for_api_key(edsl_auth_token)
|
802
|
+
|
803
|
+
if api_key is None:
|
804
|
+
raise Exception("Timed out waiting for login. Please try again.")
|
805
|
+
|
806
|
+
write_api_key_to_env(api_key)
|
807
|
+
print("\n✨ API key retrieved and written to .env file.")
|
808
|
+
|
809
|
+
# Add API key to environment
|
810
|
+
load_dotenv()
|
811
|
+
|
681
812
|
|
682
813
|
if __name__ == "__main__":
|
683
814
|
sheet_data = fetch_sheet_data()
|
edsl/data/Cache.py
CHANGED
@@ -194,7 +194,7 @@ class Cache(Base):
|
|
194
194
|
>>> c = Cache()
|
195
195
|
>>> len(c)
|
196
196
|
0
|
197
|
-
>>> results = Question.example("free_text").by(m).run(cache = c)
|
197
|
+
>>> results = Question.example("free_text").by(m).run(cache = c, disable_remote_cache = True, disable_remote_inference = True)
|
198
198
|
>>> len(c)
|
199
199
|
1
|
200
200
|
"""
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class BaseException(Exception):
|
2
|
+
relevant_doc = "https://docs.expectedparrot.com/"
|
3
|
+
|
4
|
+
def __init__(self, message, *, show_docs=True):
|
5
|
+
# Format main error message
|
6
|
+
formatted_message = [message.strip()]
|
7
|
+
|
8
|
+
# Add documentation links if requested
|
9
|
+
if show_docs:
|
10
|
+
if hasattr(self, "relevant_doc"):
|
11
|
+
formatted_message.append(
|
12
|
+
f"\nFor more information, see:\n{self.relevant_doc}"
|
13
|
+
)
|
14
|
+
if hasattr(self, "relevant_notebook"):
|
15
|
+
formatted_message.append(
|
16
|
+
f"\nFor a usage example, see:\n{self.relevant_notebook}"
|
17
|
+
)
|
18
|
+
|
19
|
+
# Join with double newlines for clear separation
|
20
|
+
final_message = "\n\n".join(formatted_message)
|
21
|
+
super().__init__(final_message)
|
edsl/exceptions/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from .agents import (
|
2
|
-
AgentAttributeLookupCallbackError,
|
2
|
+
# AgentAttributeLookupCallbackError,
|
3
3
|
AgentCombinationError,
|
4
|
-
AgentLacksLLMError,
|
5
|
-
AgentRespondedWithBadJSONError,
|
4
|
+
# AgentLacksLLMError,
|
5
|
+
# AgentRespondedWithBadJSONError,
|
6
6
|
)
|
7
7
|
from .configuration import (
|
8
8
|
InvalidEnvironmentVariableError,
|
@@ -14,6 +14,10 @@ from .data import (
|
|
14
14
|
DatabaseIntegrityError,
|
15
15
|
)
|
16
16
|
|
17
|
+
from .scenarios import (
|
18
|
+
ScenarioError,
|
19
|
+
)
|
20
|
+
|
17
21
|
from .general import MissingAPIKeyError
|
18
22
|
|
19
23
|
from .jobs import JobsRunError, InterviewErrorPriorTaskCanceled, InterviewTimeoutError
|
edsl/exceptions/agents.py
CHANGED
@@ -1,37 +1,35 @@
|
|
1
|
-
|
2
|
-
pass
|
1
|
+
from edsl.exceptions.BaseException import BaseException
|
3
2
|
|
4
3
|
|
5
|
-
class
|
6
|
-
|
4
|
+
class AgentErrors(BaseException):
|
5
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html"
|
7
6
|
|
8
7
|
|
9
|
-
class
|
10
|
-
|
8
|
+
class AgentDynamicTraitsFunctionError(AgentErrors):
|
9
|
+
relevant_doc = (
|
10
|
+
"https://docs.expectedparrot.com/en/latest/agents.html#dynamic-traits-function"
|
11
|
+
)
|
12
|
+
relevant_notebook = "https://docs.expectedparrot.com/en/latest/notebooks/example_agent_dynamic_traits.html"
|
11
13
|
|
12
14
|
|
13
|
-
class
|
14
|
-
|
15
|
+
class AgentDirectAnswerFunctionError(AgentErrors):
|
16
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#agent-direct-answering-methods"
|
15
17
|
|
16
18
|
|
17
19
|
class AgentCombinationError(AgentErrors):
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
class AgentLacksLLMError(AgentErrors):
|
22
|
-
pass
|
23
|
-
|
24
|
-
|
25
|
-
class AgentRespondedWithBadJSONError(AgentErrors):
|
26
|
-
pass
|
20
|
+
relevant_doc = (
|
21
|
+
"https://docs.expectedparrot.com/en/latest/agents.html#combining-agents"
|
22
|
+
)
|
27
23
|
|
28
24
|
|
29
25
|
class AgentNameError(AgentErrors):
|
30
|
-
|
26
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#agent-names"
|
31
27
|
|
32
28
|
|
33
29
|
class AgentTraitKeyError(AgentErrors):
|
34
|
-
|
30
|
+
relevant_doc = (
|
31
|
+
"https://docs.expectedparrot.com/en/latest/agents.html#constructing-an-agent"
|
32
|
+
)
|
35
33
|
|
36
34
|
|
37
35
|
class FailedTaskException(Exception):
|
edsl/exceptions/results.py
CHANGED
@@ -1,26 +1,29 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
from edsl.exceptions.BaseException import BaseException
|
2
|
+
|
3
|
+
|
4
|
+
class ResultsError(BaseException):
|
5
|
+
relevant_docs = "https://docs.expectedparrot.com/en/latest/results.html"
|
3
6
|
|
4
7
|
|
5
|
-
class ResultsDeserializationError(
|
8
|
+
class ResultsDeserializationError(ResultsError):
|
6
9
|
pass
|
7
10
|
|
8
11
|
|
9
|
-
class ResultsBadMutationstringError(
|
12
|
+
class ResultsBadMutationstringError(ResultsError):
|
10
13
|
pass
|
11
14
|
|
12
15
|
|
13
|
-
class ResultsColumnNotFoundError(
|
16
|
+
class ResultsColumnNotFoundError(ResultsError):
|
14
17
|
pass
|
15
18
|
|
16
19
|
|
17
|
-
class ResultsInvalidNameError(
|
20
|
+
class ResultsInvalidNameError(ResultsError):
|
18
21
|
pass
|
19
22
|
|
20
23
|
|
21
|
-
class ResultsMutateError(
|
24
|
+
class ResultsMutateError(ResultsError):
|
22
25
|
pass
|
23
26
|
|
24
27
|
|
25
|
-
class ResultsFilterError(
|
28
|
+
class ResultsFilterError(ResultsError):
|
26
29
|
pass
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import re
|
2
|
+
import textwrap
|
3
|
+
|
4
|
+
|
5
|
+
class ScenarioError(Exception):
|
6
|
+
documentation = "https://docs.expectedparrot.com/en/latest/scenarios.html#module-edsl.scenarios.Scenario"
|
7
|
+
|
8
|
+
def __init__(self, message: str):
|
9
|
+
self.message = message + "\n" + "Documentation: " + self.documentation
|
10
|
+
super().__init__(self.message)
|
11
|
+
|
12
|
+
def __str__(self):
|
13
|
+
return self.make_urls_clickable(self.message)
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def make_urls_clickable(text):
|
17
|
+
url_pattern = r"https?://[^\s]+"
|
18
|
+
urls = re.findall(url_pattern, text)
|
19
|
+
for url in urls:
|
20
|
+
clickable_url = f"\033]8;;{url}\007{url}\033]8;;\007"
|
21
|
+
text = text.replace(url, clickable_url)
|
22
|
+
return text
|
edsl/exceptions/surveys.py
CHANGED
@@ -1,34 +1,37 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
from edsl.exceptions.BaseException import BaseException
|
2
|
+
|
3
|
+
|
4
|
+
class SurveyError(BaseException):
|
5
|
+
relevant_doc = "https://docs.expectedparrot.com/en/latest/surveys.html"
|
3
6
|
|
4
7
|
|
5
|
-
class SurveyCreationError(
|
8
|
+
class SurveyCreationError(SurveyError):
|
6
9
|
pass
|
7
10
|
|
8
11
|
|
9
|
-
class SurveyHasNoRulesError(
|
12
|
+
class SurveyHasNoRulesError(SurveyError):
|
10
13
|
pass
|
11
14
|
|
12
15
|
|
13
|
-
class SurveyRuleSendsYouBackwardsError(
|
16
|
+
class SurveyRuleSendsYouBackwardsError(SurveyError):
|
14
17
|
pass
|
15
18
|
|
16
19
|
|
17
|
-
class SurveyRuleSkipLogicSyntaxError(
|
20
|
+
class SurveyRuleSkipLogicSyntaxError(SurveyError):
|
18
21
|
pass
|
19
22
|
|
20
23
|
|
21
|
-
class SurveyRuleReferenceInRuleToUnknownQuestionError(
|
24
|
+
class SurveyRuleReferenceInRuleToUnknownQuestionError(SurveyError):
|
22
25
|
pass
|
23
26
|
|
24
27
|
|
25
|
-
class SurveyRuleRefersToFutureStateError(
|
28
|
+
class SurveyRuleRefersToFutureStateError(SurveyError):
|
26
29
|
pass
|
27
30
|
|
28
31
|
|
29
|
-
class SurveyRuleCollectionHasNoRulesAtNodeError(
|
32
|
+
class SurveyRuleCollectionHasNoRulesAtNodeError(SurveyError):
|
30
33
|
pass
|
31
34
|
|
32
35
|
|
33
|
-
class SurveyRuleCannotEvaluateError(
|
36
|
+
class SurveyRuleCannotEvaluateError(SurveyError):
|
34
37
|
pass
|