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.
Files changed (45) hide show
  1. edsl/__version__.py +1 -1
  2. edsl/agents/Agent.py +86 -35
  3. edsl/agents/AgentList.py +5 -0
  4. edsl/agents/InvigilatorBase.py +2 -23
  5. edsl/agents/PromptConstructor.py +147 -106
  6. edsl/agents/descriptors.py +17 -4
  7. edsl/config.py +1 -1
  8. edsl/conjure/AgentConstructionMixin.py +11 -3
  9. edsl/conversation/Conversation.py +66 -14
  10. edsl/conversation/chips.py +95 -0
  11. edsl/coop/coop.py +134 -3
  12. edsl/data/Cache.py +1 -1
  13. edsl/exceptions/BaseException.py +21 -0
  14. edsl/exceptions/__init__.py +7 -3
  15. edsl/exceptions/agents.py +17 -19
  16. edsl/exceptions/results.py +11 -8
  17. edsl/exceptions/scenarios.py +22 -0
  18. edsl/exceptions/surveys.py +13 -10
  19. edsl/inference_services/InferenceServicesCollection.py +32 -9
  20. edsl/jobs/Jobs.py +265 -53
  21. edsl/jobs/interviews/InterviewExceptionEntry.py +5 -1
  22. edsl/jobs/tasks/TaskHistory.py +1 -0
  23. edsl/language_models/KeyLookup.py +30 -0
  24. edsl/language_models/LanguageModel.py +47 -59
  25. edsl/language_models/__init__.py +1 -0
  26. edsl/prompts/Prompt.py +8 -4
  27. edsl/questions/QuestionBase.py +53 -13
  28. edsl/questions/QuestionBasePromptsMixin.py +1 -33
  29. edsl/questions/QuestionFunctional.py +2 -2
  30. edsl/questions/descriptors.py +23 -28
  31. edsl/results/DatasetExportMixin.py +25 -1
  32. edsl/results/Result.py +16 -1
  33. edsl/results/Results.py +31 -120
  34. edsl/results/ResultsDBMixin.py +1 -1
  35. edsl/results/Selector.py +18 -1
  36. edsl/scenarios/Scenario.py +48 -12
  37. edsl/scenarios/ScenarioHtmlMixin.py +7 -2
  38. edsl/scenarios/ScenarioList.py +12 -1
  39. edsl/surveys/Rule.py +10 -4
  40. edsl/surveys/Survey.py +100 -77
  41. edsl/utilities/utilities.py +18 -0
  42. {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/METADATA +1 -1
  43. {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/RECORD +45 -41
  44. {edsl-0.1.37.dev4.dist-info → edsl-0.1.37.dev6.dist-info}/LICENSE +0 -0
  45. {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="You are {{ agent_name }}. This is the converstaion so far: {{ conversation }}. What do you say next?",
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
- results = await q.run_async(
165
- index=index,
166
- conversation=conversation,
167
- conversation_index=self.conversation_index,
168
- agent_name=speaker.name,
169
- agent=speaker,
170
- just_answer=False,
171
- cache=self.cache,
172
- model=speaker.model,
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(self, response: requests.Response) -> None:
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 "Authorization" in message:
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)
@@ -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
- class AgentErrors(Exception):
2
- pass
1
+ from edsl.exceptions.BaseException import BaseException
3
2
 
4
3
 
5
- class AgentDynamicTraitsFunctionError(AgentErrors):
6
- pass
4
+ class AgentErrors(BaseException):
5
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html"
7
6
 
8
7
 
9
- class AgentDirectAnswerFunctionError(AgentErrors):
10
- pass
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 AgentAttributeLookupCallbackError(AgentErrors):
14
- pass
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
- pass
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
- pass
26
+ relevant_doc = "https://docs.expectedparrot.com/en/latest/agents.html#agent-names"
31
27
 
32
28
 
33
29
  class AgentTraitKeyError(AgentErrors):
34
- pass
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):
@@ -1,26 +1,29 @@
1
- class ResultsErrors(Exception):
2
- pass
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(ResultsErrors):
8
+ class ResultsDeserializationError(ResultsError):
6
9
  pass
7
10
 
8
11
 
9
- class ResultsBadMutationstringError(ResultsErrors):
12
+ class ResultsBadMutationstringError(ResultsError):
10
13
  pass
11
14
 
12
15
 
13
- class ResultsColumnNotFoundError(ResultsErrors):
16
+ class ResultsColumnNotFoundError(ResultsError):
14
17
  pass
15
18
 
16
19
 
17
- class ResultsInvalidNameError(ResultsErrors):
20
+ class ResultsInvalidNameError(ResultsError):
18
21
  pass
19
22
 
20
23
 
21
- class ResultsMutateError(ResultsErrors):
24
+ class ResultsMutateError(ResultsError):
22
25
  pass
23
26
 
24
27
 
25
- class ResultsFilterError(ResultsErrors):
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
@@ -1,34 +1,37 @@
1
- class SurveyErrors(Exception):
2
- pass
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(SurveyErrors):
8
+ class SurveyCreationError(SurveyError):
6
9
  pass
7
10
 
8
11
 
9
- class SurveyHasNoRulesError(SurveyErrors):
12
+ class SurveyHasNoRulesError(SurveyError):
10
13
  pass
11
14
 
12
15
 
13
- class SurveyRuleSendsYouBackwardsError(SurveyErrors):
16
+ class SurveyRuleSendsYouBackwardsError(SurveyError):
14
17
  pass
15
18
 
16
19
 
17
- class SurveyRuleSkipLogicSyntaxError(SurveyErrors):
20
+ class SurveyRuleSkipLogicSyntaxError(SurveyError):
18
21
  pass
19
22
 
20
23
 
21
- class SurveyRuleReferenceInRuleToUnknownQuestionError(SurveyErrors):
24
+ class SurveyRuleReferenceInRuleToUnknownQuestionError(SurveyError):
22
25
  pass
23
26
 
24
27
 
25
- class SurveyRuleRefersToFutureStateError(SurveyErrors):
28
+ class SurveyRuleRefersToFutureStateError(SurveyError):
26
29
  pass
27
30
 
28
31
 
29
- class SurveyRuleCollectionHasNoRulesAtNodeError(SurveyErrors):
32
+ class SurveyRuleCollectionHasNoRulesAtNodeError(SurveyError):
30
33
  pass
31
34
 
32
35
 
33
- class SurveyRuleCannotEvaluateError(SurveyErrors):
36
+ class SurveyRuleCannotEvaluateError(SurveyError):
34
37
  pass