edsl 0.1.38__py3-none-any.whl → 0.1.38.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.
Files changed (53) hide show
  1. edsl/Base.py +31 -60
  2. edsl/__version__.py +1 -1
  3. edsl/agents/Agent.py +9 -18
  4. edsl/agents/AgentList.py +8 -59
  5. edsl/agents/Invigilator.py +7 -18
  6. edsl/agents/InvigilatorBase.py +19 -0
  7. edsl/agents/PromptConstructor.py +4 -5
  8. edsl/config.py +0 -8
  9. edsl/coop/coop.py +7 -74
  10. edsl/data/Cache.py +2 -27
  11. edsl/data/CacheEntry.py +3 -8
  12. edsl/data/RemoteCacheSync.py +19 -0
  13. edsl/enums.py +0 -2
  14. edsl/inference_services/GoogleService.py +15 -7
  15. edsl/inference_services/registry.py +0 -2
  16. edsl/jobs/Jobs.py +548 -88
  17. edsl/jobs/interviews/Interview.py +11 -11
  18. edsl/jobs/runners/JobsRunnerAsyncio.py +35 -140
  19. edsl/jobs/runners/JobsRunnerStatus.py +2 -0
  20. edsl/jobs/tasks/TaskHistory.py +16 -15
  21. edsl/language_models/LanguageModel.py +84 -44
  22. edsl/language_models/ModelList.py +1 -47
  23. edsl/language_models/registry.py +4 -57
  24. edsl/prompts/Prompt.py +3 -8
  25. edsl/questions/QuestionBase.py +16 -20
  26. edsl/questions/QuestionExtract.py +4 -3
  27. edsl/questions/question_registry.py +6 -36
  28. edsl/results/Dataset.py +15 -146
  29. edsl/results/DatasetExportMixin.py +217 -231
  30. edsl/results/DatasetTree.py +4 -134
  31. edsl/results/Result.py +9 -18
  32. edsl/results/Results.py +51 -145
  33. edsl/scenarios/FileStore.py +13 -187
  34. edsl/scenarios/Scenario.py +4 -61
  35. edsl/scenarios/ScenarioList.py +62 -237
  36. edsl/surveys/Survey.py +2 -16
  37. edsl/surveys/SurveyFlowVisualizationMixin.py +9 -67
  38. edsl/surveys/instructions/Instruction.py +0 -12
  39. edsl/templates/error_reporting/interview_details.html +3 -3
  40. edsl/templates/error_reporting/interviews.html +9 -18
  41. edsl/utilities/utilities.py +0 -15
  42. {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/METADATA +1 -2
  43. {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/RECORD +45 -53
  44. edsl/inference_services/PerplexityService.py +0 -163
  45. edsl/jobs/JobsChecks.py +0 -147
  46. edsl/jobs/JobsPrompts.py +0 -268
  47. edsl/jobs/JobsRemoteInferenceHandler.py +0 -239
  48. edsl/results/CSSParameterizer.py +0 -108
  49. edsl/results/TableDisplay.py +0 -198
  50. edsl/results/table_display.css +0 -78
  51. edsl/scenarios/ScenarioJoin.py +0 -127
  52. {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/LICENSE +0 -0
  53. {edsl-0.1.38.dist-info → edsl-0.1.38.dev2.dist-info}/WHEEL +0 -0
@@ -31,10 +31,6 @@ class ScenarioListMixin(ScenarioListPdfMixin, ScenarioListExportMixin):
31
31
  class ScenarioList(Base, UserList, ScenarioListMixin):
32
32
  """Class for creating a list of scenarios to be used in a survey."""
33
33
 
34
- __documentation__ = (
35
- "https://docs.expectedparrot.com/en/latest/scenarios.html#scenariolist"
36
- )
37
-
38
34
  def __init__(self, data: Optional[list] = None, codebook: Optional[dict] = None):
39
35
  """Initialize the ScenarioList class."""
40
36
  if data is not None:
@@ -245,9 +241,6 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
245
241
 
246
242
  return dict_hash(self.to_dict(sort=True, add_edsl_version=False))
247
243
 
248
- def __eq__(self, other: Any) -> bool:
249
- return hash(self) == hash(other)
250
-
251
244
  def __repr__(self):
252
245
  return f"ScenarioList({self.data})"
253
246
 
@@ -289,49 +282,41 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
289
282
  random.shuffle(self.data)
290
283
  return self
291
284
 
292
- def _repr_html_(self):
293
- """Return an HTML representation of the AgentList."""
294
- # return (
295
- # str(self.summary(format="html")) + "<br>" + str(self.table(tablefmt="html"))
296
- # )
297
- footer = f"<a href={self.__documentation__}>(docs)</a>"
298
- return str(self.summary(format="html")) + footer
299
-
300
- # def _repr_html_(self) -> str:
301
- # from edsl.utilities.utilities import data_to_html
302
-
303
- # data = self.to_dict()
304
- # _ = data.pop("edsl_version")
305
- # _ = data.pop("edsl_class_name")
306
- # for s in data["scenarios"]:
307
- # _ = s.pop("edsl_version")
308
- # _ = s.pop("edsl_class_name")
309
- # for scenario in data["scenarios"]:
310
- # for key, value in scenario.items():
311
- # if hasattr(value, "to_dict"):
312
- # data[key] = value.to_dict()
313
- # return data_to_html(data)
314
-
315
- # def tally(self, field) -> dict:
316
- # """Return a tally of the values in the field.
317
-
318
- # Example:
319
-
320
- # >>> s = ScenarioList([Scenario({'a': 1, 'b': 1}), Scenario({'a': 1, 'b': 2})])
321
- # >>> s.tally('b')
322
- # {1: 1, 2: 1}
323
- # """
324
- # return dict(Counter([scenario[field] for scenario in self]))
325
-
326
- def sample(self, n: int, seed: Optional[str] = None) -> ScenarioList:
285
+ def _repr_html_(self) -> str:
286
+ from edsl.utilities.utilities import data_to_html
287
+
288
+ data = self.to_dict()
289
+ _ = data.pop("edsl_version")
290
+ _ = data.pop("edsl_class_name")
291
+ for s in data["scenarios"]:
292
+ _ = s.pop("edsl_version")
293
+ _ = s.pop("edsl_class_name")
294
+ for scenario in data["scenarios"]:
295
+ for key, value in scenario.items():
296
+ if hasattr(value, "to_dict"):
297
+ data[key] = value.to_dict()
298
+ return data_to_html(data)
299
+
300
+ def tally(self, field) -> dict:
301
+ """Return a tally of the values in the field.
302
+
303
+ Example:
304
+
305
+ >>> s = ScenarioList([Scenario({'a': 1, 'b': 1}), Scenario({'a': 1, 'b': 2})])
306
+ >>> s.tally('b')
307
+ {1: 1, 2: 1}
308
+ """
309
+ return dict(Counter([scenario[field] for scenario in self]))
310
+
311
+ def sample(self, n: int, seed="edsl") -> ScenarioList:
327
312
  """Return a random sample from the ScenarioList
328
313
 
329
314
  >>> s = ScenarioList.from_list("a", [1,2,3,4,5,6])
330
- >>> s.sample(3, seed = "edsl")
315
+ >>> s.sample(3)
331
316
  ScenarioList([Scenario({'a': 2}), Scenario({'a': 1}), Scenario({'a': 3})])
332
317
  """
333
- if seed:
334
- random.seed(seed)
318
+
319
+ random.seed(seed)
335
320
 
336
321
  return ScenarioList(random.sample(self.data, n))
337
322
 
@@ -579,47 +564,6 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
579
564
  func = lambda x: x
580
565
  return cls([Scenario({name: func(value)}) for value in values])
581
566
 
582
- def table(self, *fields, tablefmt=None, pretty_labels=None) -> str:
583
- """Return the ScenarioList as a table."""
584
-
585
- from tabulate import tabulate_formats
586
-
587
- if tablefmt is not None and tablefmt not in tabulate_formats:
588
- raise ValueError(
589
- f"Invalid table format: {tablefmt}",
590
- f"Valid formats are: {tabulate_formats}",
591
- )
592
- return self.to_dataset().table(
593
- *fields, tablefmt=tablefmt, pretty_labels=pretty_labels
594
- )
595
-
596
- def tree(self, node_list: Optional[List[str]] = None) -> str:
597
- """Return the ScenarioList as a tree."""
598
- return self.to_dataset().tree(node_list)
599
-
600
- def _summary(self):
601
- d = {
602
- "EDSL Class name": "ScenarioList",
603
- "# Scenarios": len(self),
604
- "Scenario Keys": list(self.parameters),
605
- }
606
- return d
607
-
608
- def reorder_keys(self, new_order):
609
- """Reorder the keys in the scenarios.
610
-
611
- Example:
612
-
613
- >>> s = ScenarioList([Scenario({'a': 1, 'b': 2}), Scenario({'a': 3, 'b': 4})])
614
- >>> s.reorder_keys(['b', 'a'])
615
- ScenarioList([Scenario({'b': 2, 'a': 1}), Scenario({'b': 4, 'a': 3})])
616
- """
617
- new_scenarios = []
618
- for scenario in self:
619
- new_scenario = Scenario({key: scenario[key] for key in new_order})
620
- new_scenarios.append(new_scenario)
621
- return ScenarioList(new_scenarios)
622
-
623
567
  def to_dataset(self) -> "Dataset":
624
568
  """
625
569
  >>> s = ScenarioList.from_list("a", [1,2,3])
@@ -635,32 +579,16 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
635
579
  data = [{key: [scenario[key] for scenario in self.data]} for key in keys]
636
580
  return Dataset(data)
637
581
 
638
- def unpack(
639
- self, field: str, new_names: Optional[List[str]] = None, keep_original=True
582
+ def split(
583
+ self, field: str, split_on: str, index: int, new_name: Optional[str] = None
640
584
  ) -> ScenarioList:
641
- """Unpack a field into multiple fields.
642
-
643
- Example:
644
-
645
- >>> s = ScenarioList([Scenario({'a': 1, 'b': [2, True]}), Scenario({'a': 3, 'b': [3, False]})])
646
- >>> s.unpack('b')
647
- ScenarioList([Scenario({'a': 1, 'b': [2, True], 'b_0': 2, 'b_1': True}), Scenario({'a': 3, 'b': [3, False], 'b_0': 3, 'b_1': False})])
648
- >>> s.unpack('b', new_names=['c', 'd'], keep_original=False)
649
- ScenarioList([Scenario({'a': 1, 'c': 2, 'd': True}), Scenario({'a': 3, 'c': 3, 'd': False})])
650
-
651
- """
652
- new_names = new_names or [f"{field}_{i}" for i in range(len(self[0][field]))]
585
+ """Split a scenario fiel in multiple fields."""
586
+ if new_name is None:
587
+ new_name = field + "_split_" + str(index)
653
588
  new_scenarios = []
654
589
  for scenario in self:
655
590
  new_scenario = scenario.copy()
656
- if len(new_names) == 1:
657
- new_scenario[new_names[0]] = scenario[field]
658
- else:
659
- for i, new_name in enumerate(new_names):
660
- new_scenario[new_name] = scenario[field][i]
661
-
662
- if not keep_original:
663
- del new_scenario[field]
591
+ new_scenario[new_name] = scenario[field].split(split_on)[index]
664
592
  new_scenarios.append(new_scenario)
665
593
  return ScenarioList(new_scenarios)
666
594
 
@@ -973,32 +901,33 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
973
901
  return cls.from_excel(temp_filename, sheet_name=sheet_name)
974
902
 
975
903
  @classmethod
976
- def from_delimited_file(
977
- cls, source: Union[str, urllib.parse.ParseResult], delimiter: str = ","
978
- ) -> ScenarioList:
979
- """Create a ScenarioList from a delimited file (CSV/TSV) or URL.
904
+ def from_csv(cls, source: Union[str, urllib.parse.ParseResult]) -> ScenarioList:
905
+ """Create a ScenarioList from a CSV file or URL.
980
906
 
981
907
  Args:
982
- source: A string representing either a local file path or a URL to a delimited file,
908
+ source: A string representing either a local file path or a URL to a CSV file,
983
909
  or a urllib.parse.ParseResult object for a URL.
984
- delimiter: The delimiter used in the file. Defaults to ',' for CSV files.
985
- Use '\t' for TSV files.
986
910
 
987
911
  Returns:
988
- ScenarioList: A ScenarioList object containing the data from the file.
912
+ ScenarioList: A ScenarioList object containing the data from the CSV.
989
913
 
990
914
  Example:
991
- # For CSV files
992
915
 
993
- >>> with open('data.csv', 'w') as f:
994
- ... _ = f.write('name,age\\nAlice,30\\nBob,25\\n')
995
- >>> scenario_list = ScenarioList.from_delimited_file('data.csv')
996
-
997
- # For TSV files
998
- >>> with open('data.tsv', 'w') as f:
999
- ... _ = f.write('name\\tage\\nAlice\t30\\nBob\t25\\n')
1000
- >>> scenario_list = ScenarioList.from_delimited_file('data.tsv', delimiter='\\t')
916
+ >>> import tempfile
917
+ >>> import os
918
+ >>> with tempfile.NamedTemporaryFile(delete=False, mode='w', suffix='.csv') as f:
919
+ ... _ = f.write("name,age,location\\nAlice,30,New York\\nBob,25,Los Angeles\\n")
920
+ ... temp_filename = f.name
921
+ >>> scenario_list = ScenarioList.from_csv(temp_filename)
922
+ >>> len(scenario_list)
923
+ 2
924
+ >>> scenario_list[0]['name']
925
+ 'Alice'
926
+ >>> scenario_list[1]['age']
927
+ '25'
1001
928
 
929
+ >>> url = "https://example.com/data.csv"
930
+ >>> ## scenario_list_from_url = ScenarioList.from_csv(url)
1002
931
  """
1003
932
  from edsl.scenarios.Scenario import Scenario
1004
933
 
@@ -1011,111 +940,24 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
1011
940
 
1012
941
  if isinstance(source, str) and is_url(source):
1013
942
  with urllib.request.urlopen(source) as response:
1014
- file_content = response.read().decode("utf-8")
1015
- file_obj = StringIO(file_content)
943
+ csv_content = response.read().decode("utf-8")
944
+ csv_file = StringIO(csv_content)
1016
945
  elif isinstance(source, urllib.parse.ParseResult):
1017
946
  with urllib.request.urlopen(source.geturl()) as response:
1018
- file_content = response.read().decode("utf-8")
1019
- file_obj = StringIO(file_content)
947
+ csv_content = response.read().decode("utf-8")
948
+ csv_file = StringIO(csv_content)
1020
949
  else:
1021
- file_obj = open(source, "r")
950
+ csv_file = open(source, "r")
1022
951
 
1023
952
  try:
1024
- reader = csv.reader(file_obj, delimiter=delimiter)
953
+ reader = csv.reader(csv_file)
1025
954
  header = next(reader)
1026
955
  observations = [Scenario(dict(zip(header, row))) for row in reader]
1027
956
  finally:
1028
- file_obj.close()
957
+ csv_file.close()
1029
958
 
1030
959
  return cls(observations)
1031
960
 
1032
- # Convenience methods for specific file types
1033
- @classmethod
1034
- def from_csv(cls, source: Union[str, urllib.parse.ParseResult]) -> ScenarioList:
1035
- """Create a ScenarioList from a CSV file or URL."""
1036
- return cls.from_delimited_file(source, delimiter=",")
1037
-
1038
- def left_join(self, other: ScenarioList, by: Union[str, list[str]]) -> ScenarioList:
1039
- """Perform a left join with another ScenarioList, following SQL join semantics.
1040
-
1041
- Args:
1042
- other: The ScenarioList to join with
1043
- by: String or list of strings representing the key(s) to join on. Cannot be empty.
1044
-
1045
- >>> s1 = ScenarioList([Scenario({'name': 'Alice', 'age': 30}), Scenario({'name': 'Bob', 'age': 25})])
1046
- >>> s2 = ScenarioList([Scenario({'name': 'Alice', 'location': 'New York'}), Scenario({'name': 'Charlie', 'location': 'Los Angeles'})])
1047
- >>> s3 = s1.left_join(s2, 'name')
1048
- >>> s3 == ScenarioList([Scenario({'age': 30, 'location': 'New York', 'name': 'Alice'}), Scenario({'age': 25, 'location': None, 'name': 'Bob'})])
1049
- True
1050
- """
1051
- from edsl.scenarios.ScenarioJoin import ScenarioJoin
1052
-
1053
- sj = ScenarioJoin(self, other)
1054
- return sj.left_join(by)
1055
- # # Validate join keys
1056
- # if not by:
1057
- # raise ValueError(
1058
- # "Join keys cannot be empty. Please specify at least one key to join on."
1059
- # )
1060
-
1061
- # # Convert single string to list for consistent handling
1062
- # by_keys = [by] if isinstance(by, str) else by
1063
-
1064
- # # Verify all join keys exist in both ScenarioLists
1065
- # left_keys = set(next(iter(self)).keys()) if self else set()
1066
- # right_keys = set(next(iter(other)).keys()) if other else set()
1067
-
1068
- # missing_left = set(by_keys) - left_keys
1069
- # missing_right = set(by_keys) - right_keys
1070
- # if missing_left or missing_right:
1071
- # missing = missing_left | missing_right
1072
- # raise ValueError(f"Join key(s) {missing} not found in both ScenarioLists")
1073
-
1074
- # # Create lookup dictionary from the other ScenarioList
1075
- # def get_key_tuple(scenario: Scenario, keys: list[str]) -> tuple:
1076
- # return tuple(scenario[k] for k in keys)
1077
-
1078
- # other_dict = {get_key_tuple(scenario, by_keys): scenario for scenario in other}
1079
-
1080
- # # Collect all possible keys (like SQL combining all columns)
1081
- # all_keys = set()
1082
- # for scenario in self:
1083
- # all_keys.update(scenario.keys())
1084
- # for scenario in other:
1085
- # all_keys.update(scenario.keys())
1086
-
1087
- # new_scenarios = []
1088
- # for scenario in self:
1089
- # new_scenario = {
1090
- # key: None for key in all_keys
1091
- # } # Start with nulls (like SQL)
1092
- # new_scenario.update(scenario) # Add all left values
1093
-
1094
- # key_tuple = get_key_tuple(scenario, by_keys)
1095
- # if matching_scenario := other_dict.get(key_tuple):
1096
- # # Check for overlapping keys with different values
1097
- # overlapping_keys = set(scenario.keys()) & set(matching_scenario.keys())
1098
- # for key in overlapping_keys:
1099
- # if key not in by_keys and scenario[key] != matching_scenario[key]:
1100
- # join_conditions = [f"{k}='{scenario[k]}'" for k in by_keys]
1101
- # print(
1102
- # f"Warning: Conflicting values for key '{key}' where {' AND '.join(join_conditions)}. "
1103
- # f"Keeping left value: {scenario[key]} (discarding: {matching_scenario[key]})"
1104
- # )
1105
-
1106
- # # Only update with non-overlapping keys from matching scenario
1107
- # new_keys = set(matching_scenario.keys()) - set(scenario.keys())
1108
- # new_scenario.update({k: matching_scenario[k] for k in new_keys})
1109
-
1110
- # new_scenarios.append(Scenario(new_scenario))
1111
-
1112
- # return ScenarioList(new_scenarios)
1113
-
1114
- @classmethod
1115
- def from_tsv(cls, source: Union[str, urllib.parse.ParseResult]) -> ScenarioList:
1116
- """Create a ScenarioList from a TSV file or URL."""
1117
- return cls.from_delimited_file(source, delimiter="\t")
1118
-
1119
961
  def to_dict(self, sort=False, add_edsl_version=True) -> dict:
1120
962
  """
1121
963
  >>> s = ScenarioList([Scenario({'food': 'wood chips'}), Scenario({'food': 'wood-fired pizza'})])
@@ -1232,25 +1074,8 @@ class ScenarioList(Base, UserList, ScenarioListMixin):
1232
1074
  """
1233
1075
  from edsl.agents.AgentList import AgentList
1234
1076
  from edsl.agents.Agent import Agent
1235
- import warnings
1236
-
1237
- agents = []
1238
- for scenario in self:
1239
- new_scenario = scenario.copy().data
1240
- if "name" in new_scenario:
1241
- name = new_scenario.pop("name")
1242
- proposed_agent_name = "agent_name"
1243
- while proposed_agent_name not in new_scenario:
1244
- proposed_agent_name += "_"
1245
- warnings.warn(
1246
- f"The 'name' field is reserved for the agent's name---putting this value in {proposed_agent_name}"
1247
- )
1248
- new_scenario[proposed_agent_name] = name
1249
- agents.append(Agent(traits=new_scenario, name=name))
1250
- else:
1251
- agents.append(Agent(traits=new_scenario))
1252
1077
 
1253
- return AgentList(agents)
1078
+ return AgentList([Agent(traits=s.data) for s in self])
1254
1079
 
1255
1080
  def chunk(
1256
1081
  self,
edsl/surveys/Survey.py CHANGED
@@ -41,8 +41,6 @@ class ValidatedString(str):
41
41
  class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
42
42
  """A collection of questions that supports skip logic."""
43
43
 
44
- __documentation__ = """https://docs.expectedparrot.com/en/latest/surveys.html"""
45
-
46
44
  questions = QuestionsDescriptor()
47
45
  """
48
46
  A collection of questions that supports skip logic.
@@ -1589,22 +1587,10 @@ class Survey(SurveyExportMixin, SurveyFlowVisualizationMixin, Base):
1589
1587
  # question_names_string = ", ".join([repr(name) for name in self.question_names])
1590
1588
  return f"Survey(questions=[{questions_string}], memory_plan={self.memory_plan}, rule_collection={self.rule_collection}, question_groups={self.question_groups})"
1591
1589
 
1592
- def _summary(self) -> dict:
1593
- return {
1594
- "EDSL Class": "Survey",
1595
- "Number of Questions": len(self),
1596
- "Question Names": self.question_names,
1597
- }
1598
-
1599
1590
  def _repr_html_(self) -> str:
1600
- footer = f"<a href={self.__documentation__}>(docs)</a>"
1601
- return str(self.summary(format="html")) + footer
1602
-
1603
- def tree(self, node_list: Optional[List[str]] = None):
1604
- return self.to_scenario_list().tree(node_list=node_list)
1591
+ from edsl.utilities.utilities import data_to_html
1605
1592
 
1606
- def table(self, *fields, tablefmt=None) -> Table:
1607
- return self.to_scenario_list().to_dataset().table(*fields, tablefmt=tablefmt)
1593
+ return data_to_html(self.to_dict())
1608
1594
 
1609
1595
  def rich_print(self) -> Table:
1610
1596
  """Print the survey in a rich format.
@@ -1,85 +1,27 @@
1
- """A mixin for visualizing the flow of a survey with parameter nodes."""
1
+ """A mixin for visualizing the flow of a survey."""
2
2
 
3
3
  from typing import Optional
4
4
  from edsl.surveys.base import RulePriority, EndOfSurvey
5
5
  import tempfile
6
- import os
7
6
 
8
7
 
9
8
  class SurveyFlowVisualizationMixin:
10
- """A mixin for visualizing the flow of a survey with parameter visualization."""
9
+ """A mixin for visualizing the flow of a survey."""
11
10
 
12
11
  def show_flow(self, filename: Optional[str] = None):
13
- """Create an image showing the flow of users through the survey and question parameters."""
12
+ """Create an image showing the flow of users through the survey."""
14
13
  # Create a graph object
15
14
  import pydot
16
15
 
17
16
  graph = pydot.Dot(graph_type="digraph")
18
17
 
19
- # First collect all unique parameters and answer references
20
- params_and_refs = set()
21
- param_to_questions = {} # Keep track of which questions use each parameter
22
- answer_refs = set() # Track answer references between questions
23
-
24
- # First pass: collect parameters and their question associations
18
+ # Add nodes for each question
25
19
  for index, question in enumerate(self.questions):
26
- # Add the main question node
27
- question_node = pydot.Node(
28
- f"Q{index}", label=f"{question.question_name}", shape="ellipse"
29
- )
30
- graph.add_node(question_node)
31
-
32
- if hasattr(question, "parameters"):
33
- for param in question.parameters:
34
- # Check if this is an answer reference (contains '.answer')
35
- if ".answer" in param:
36
- answer_refs.add((param.split(".")[0], index))
37
- else:
38
- params_and_refs.add(param)
39
- if param not in param_to_questions:
40
- param_to_questions[param] = []
41
- param_to_questions[param].append(index)
42
-
43
- # Create parameter nodes and connect them to questions
44
- for param in params_and_refs:
45
- param_node_name = f"param_{param}"
46
- param_node = pydot.Node(
47
- param_node_name,
48
- label=f"{{{{ {param} }}}}",
49
- shape="box",
50
- style="filled",
51
- fillcolor="lightgrey",
52
- fontsize="10",
53
- )
54
- graph.add_node(param_node)
55
-
56
- # Connect this parameter to all questions that use it
57
- for q_index in param_to_questions[param]:
58
- param_edge = pydot.Edge(
59
- param_node_name,
60
- f"Q{q_index}",
61
- style="dotted",
62
- color="grey",
63
- arrowsize="0.5",
20
+ graph.add_node(
21
+ pydot.Node(
22
+ f"Q{index}", label=f"{question.question_name}", shape="ellipse"
64
23
  )
65
- graph.add_edge(param_edge)
66
-
67
- # Add edges for answer references
68
- for source_q_name, target_q_index in answer_refs:
69
- # Find the source question index by name
70
- source_q_index = next(
71
- i
72
- for i, q in enumerate(self.questions)
73
- if q.question_name == source_q_name
74
- )
75
- ref_edge = pydot.Edge(
76
- f"Q{source_q_index}",
77
- f"Q{target_q_index}",
78
- style="dashed",
79
- color="purple",
80
- label="answer reference",
81
24
  )
82
- graph.add_edge(ref_edge)
83
25
 
84
26
  # Add an "EndOfSurvey" node
85
27
  graph.add_node(
@@ -88,7 +30,7 @@ class SurveyFlowVisualizationMixin:
88
30
 
89
31
  # Add edges for normal flow through the survey
90
32
  num_questions = len(self.questions)
91
- for index in range(num_questions - 1):
33
+ for index in range(num_questions - 1): # From Q1 to Q3
92
34
  graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
93
35
 
94
36
  graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey"))
@@ -122,7 +64,7 @@ class SurveyFlowVisualizationMixin:
122
64
  if rule.next_q != EndOfSurvey and rule.next_q < num_questions
123
65
  else "EndOfSurvey"
124
66
  )
125
- if rule.before_rule:
67
+ if rule.before_rule: # Assume skip rules have an attribute `is_skip`
126
68
  edge = pydot.Edge(
127
69
  source_node,
128
70
  target_node,
@@ -18,18 +18,6 @@ class Instruction:
18
18
  def __repr__(self):
19
19
  return """Instruction(name="{}", text="{}")""".format(self.name, self.text)
20
20
 
21
- def _repr_html_(self):
22
- d = self.to_dict(add_edsl_version=False)
23
- data = [[k, v] for k, v in d.items()]
24
- from tabulate import tabulate
25
-
26
- table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
27
- return f"<pre>{table}</pre>"
28
-
29
- @classmethod
30
- def example(cls) -> "Instruction":
31
- return cls(name="example", text="This is an example instruction.")
32
-
33
21
  def to_dict(self, add_edsl_version=True):
34
22
  d = {
35
23
  "name": self.name,
@@ -40,11 +40,11 @@
40
40
  </tr>
41
41
  <tr>
42
42
  <td>Scenario</td>
43
- <td>{{ interview.scenario.__repr__() }}</td>
43
+ <td>{{ interview.scenario._repr_html_() }}</td>
44
44
  </tr>
45
45
  <tr>
46
46
  <td>Agent</td>
47
- <td>{{ interview.agent.__repr__() }}</td>
47
+ <td>{{ interview.agent._repr_html_() }}</td>
48
48
  </tr>
49
49
  <tr>
50
50
  <td>Model name</td>
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr>
58
58
  <td>Model parameters</td>
59
- <td>{{ interview.model.__repr__() }}</td>
59
+ <td>{{ interview.model._repr_html_() }}</td>
60
60
  </tr>
61
61
  <tr>
62
62
  <td>User Prompt</td>
@@ -1,19 +1,10 @@
1
-
2
- {% if interviews|length > max_interviews %}
3
- <h1>Only showing the first {{ max_interviews }} interviews with errors</h1>
4
- {% else %}
5
- <h1>Showing all interviews</h1>
6
- {% endif %}
7
-
8
1
  {% for index, interview in interviews.items() %}
9
- {% if index < max_interviews %}
10
- {% if interview.exceptions != {} %}
11
- <div class="interview">Interview: {{ index }} </div>
12
- Model: {{ interview.model.model }}
13
- <h1>Failing questions</h1>
14
- {% endif %}
15
- {% for question, exceptions in interview.exceptions.items() %}
16
- {% include 'interview_details.html' %}
17
- {% endfor %}
18
- {% endif %}
19
- {% endfor %}
2
+ {% if interview.exceptions != {} %}
3
+ <div class="interview">Interview: {{ index }} </div>
4
+ Model: {{ interview.model.model }}
5
+ <h1>Failing questions</h1>
6
+ {% endif %}
7
+ {% for question, exceptions in interview.exceptions.items() %}
8
+ {% include 'interview_details.html' %}
9
+ {% endfor %}
10
+ {% endfor %}
@@ -207,21 +207,6 @@ def is_notebook() -> bool:
207
207
  return False # Probably standard Python interpreter
208
208
 
209
209
 
210
- def file_notice(file_name):
211
- """Print a notice about the file being created."""
212
- if is_notebook():
213
- from IPython.display import HTML, display
214
-
215
- link_text = "Download file"
216
- display(
217
- HTML(
218
- f'<p>File created: {file_name}</p>.<a href="{file_name}" download>{link_text}</a>'
219
- )
220
- )
221
- else:
222
- print(f"File created: {file_name}")
223
-
224
-
225
210
  class HTMLSnippet(str):
226
211
  """Create an object with html content (`value`).
227
212
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edsl
3
- Version: 0.1.38
3
+ Version: 0.1.38.dev2
4
4
  Summary: Create and analyze LLM-based surveys
5
5
  Home-page: https://www.expectedparrot.com/
6
6
  License: MIT
@@ -45,7 +45,6 @@ Requires-Dist: rich (>=13.7.0,<14.0.0)
45
45
  Requires-Dist: setuptools (<72.0)
46
46
  Requires-Dist: simpleeval (>=0.9.13,<0.10.0)
47
47
  Requires-Dist: sqlalchemy (>=2.0.23,<3.0.0)
48
- Requires-Dist: tabulate (>=0.9.0,<0.10.0)
49
48
  Requires-Dist: tenacity (>=8.2.3,<9.0.0)
50
49
  Requires-Dist: urllib3 (>=1.25.4,<1.27)
51
50
  Project-URL: Documentation, https://docs.expectedparrot.com