edsl 0.1.40.dev2__py3-none-any.whl → 0.1.42__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 (59) hide show
  1. edsl/__init__.py +1 -0
  2. edsl/__version__.py +1 -1
  3. edsl/agents/Agent.py +1 -1
  4. edsl/agents/Invigilator.py +6 -4
  5. edsl/agents/InvigilatorBase.py +2 -1
  6. edsl/agents/QuestionTemplateReplacementsBuilder.py +7 -2
  7. edsl/coop/coop.py +37 -2
  8. edsl/data/Cache.py +7 -0
  9. edsl/data/RemoteCacheSync.py +16 -16
  10. edsl/enums.py +3 -0
  11. edsl/exceptions/jobs.py +1 -9
  12. edsl/exceptions/language_models.py +8 -4
  13. edsl/exceptions/questions.py +8 -11
  14. edsl/inference_services/DeepSeekService.py +18 -0
  15. edsl/inference_services/registry.py +2 -0
  16. edsl/jobs/AnswerQuestionFunctionConstructor.py +1 -1
  17. edsl/jobs/Jobs.py +42 -34
  18. edsl/jobs/JobsPrompts.py +11 -1
  19. edsl/jobs/JobsRemoteInferenceHandler.py +1 -0
  20. edsl/jobs/JobsRemoteInferenceLogger.py +1 -1
  21. edsl/jobs/interviews/Interview.py +2 -6
  22. edsl/jobs/interviews/InterviewExceptionEntry.py +14 -4
  23. edsl/jobs/loggers/HTMLTableJobLogger.py +6 -1
  24. edsl/jobs/results_exceptions_handler.py +2 -7
  25. edsl/jobs/runners/JobsRunnerAsyncio.py +18 -6
  26. edsl/jobs/runners/JobsRunnerStatus.py +2 -1
  27. edsl/jobs/tasks/TaskHistory.py +49 -17
  28. edsl/language_models/LanguageModel.py +7 -4
  29. edsl/language_models/ModelList.py +1 -1
  30. edsl/language_models/key_management/KeyLookupBuilder.py +7 -3
  31. edsl/language_models/model.py +49 -0
  32. edsl/questions/QuestionBudget.py +2 -2
  33. edsl/questions/QuestionDict.py +343 -0
  34. edsl/questions/QuestionExtract.py +1 -1
  35. edsl/questions/__init__.py +1 -0
  36. edsl/questions/answer_validator_mixin.py +29 -0
  37. edsl/questions/derived/QuestionLinearScale.py +1 -1
  38. edsl/questions/descriptors.py +49 -5
  39. edsl/questions/question_registry.py +1 -1
  40. edsl/questions/templates/dict/__init__.py +0 -0
  41. edsl/questions/templates/dict/answering_instructions.jinja +21 -0
  42. edsl/questions/templates/dict/question_presentation.jinja +1 -0
  43. edsl/results/Result.py +25 -3
  44. edsl/results/Results.py +17 -5
  45. edsl/scenarios/FileStore.py +32 -0
  46. edsl/scenarios/PdfExtractor.py +3 -6
  47. edsl/scenarios/Scenario.py +2 -1
  48. edsl/scenarios/handlers/csv.py +11 -0
  49. edsl/surveys/Survey.py +5 -1
  50. edsl/templates/error_reporting/base.html +2 -4
  51. edsl/templates/error_reporting/exceptions_table.html +35 -0
  52. edsl/templates/error_reporting/interview_details.html +67 -53
  53. edsl/templates/error_reporting/interviews.html +4 -17
  54. edsl/templates/error_reporting/overview.html +31 -5
  55. edsl/templates/error_reporting/performance_plot.html +1 -1
  56. {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/METADATA +1 -1
  57. {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/RECORD +59 -53
  58. {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/LICENSE +0 -0
  59. {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/WHEEL +0 -0
edsl/results/Results.py CHANGED
@@ -90,6 +90,7 @@ class Results(UserList, Mixins, Base):
90
90
  "comment",
91
91
  "generated_tokens",
92
92
  "cache_used",
93
+ "cache_keys",
93
94
  ]
94
95
 
95
96
  def __init__(
@@ -109,6 +110,7 @@ class Results(UserList, Mixins, Base):
109
110
  :param created_columns: A list of strings that are created columns.
110
111
  :param job_uuid: A string representing the job UUID.
111
112
  :param total_results: An integer representing the total number of results.
113
+ :cache: A Cache object.
112
114
  """
113
115
  super().__init__(data)
114
116
  from edsl.data.Cache import Cache
@@ -138,6 +140,16 @@ class Results(UserList, Mixins, Base):
138
140
  }
139
141
  return d
140
142
 
143
+ def _cache_keys(self):
144
+ cache_keys = []
145
+ for result in self:
146
+ cache_keys.extend(list(result["cache_keys"].values()))
147
+ return cache_keys
148
+
149
+ def relevant_cache(self, cache: Cache) -> Cache:
150
+ cache_keys = self._cache_keys()
151
+ return cache.subset(cache_keys)
152
+
141
153
  def insert(self, item):
142
154
  item_order = getattr(item, "order", None)
143
155
  if item_order is not None:
@@ -170,12 +182,12 @@ class Results(UserList, Mixins, Base):
170
182
  """
171
183
  total_cost = 0
172
184
  for result in self:
173
- for key in result.raw_model_response:
185
+ for key in result["raw_model_response"]:
174
186
  if key.endswith("_cost"):
175
- result_cost = result.raw_model_response[key]
187
+ result_cost = result["raw_model_response"][key]
176
188
 
177
189
  question_name = key.removesuffix("_cost")
178
- cache_used = result.cache_used_dict[question_name]
190
+ cache_used = result["cache_used_dict"][question_name]
179
191
 
180
192
  if isinstance(result_cost, (int, float)):
181
193
  if include_cached_responses_in_cost:
@@ -349,7 +361,7 @@ class Results(UserList, Mixins, Base):
349
361
  self,
350
362
  sort: bool = False,
351
363
  add_edsl_version: bool = False,
352
- include_cache: bool = False,
364
+ include_cache: bool = True,
353
365
  include_task_history: bool = False,
354
366
  include_cache_info: bool = True,
355
367
  ) -> dict[str, Any]:
@@ -635,7 +647,7 @@ class Results(UserList, Mixins, Base):
635
647
 
636
648
  >>> r = Results.example()
637
649
  >>> r.model_keys
638
- ['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
650
+ ['frequency_penalty', 'inference_service', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
639
651
  """
640
652
  return sorted(self._data_type_to_keys["model"])
641
653
 
@@ -327,6 +327,38 @@ class FileStore(Scenario):
327
327
 
328
328
  return ConstructDownloadLink(self).create_link(custom_filename, style)
329
329
 
330
+ def to_pandas(self):
331
+ """
332
+ Convert the file content to a pandas DataFrame if supported by the file handler.
333
+
334
+ Returns:
335
+ pandas.DataFrame: The data from the file as a DataFrame
336
+
337
+ Raises:
338
+ AttributeError: If the file type's handler doesn't support pandas conversion
339
+ """
340
+ handler = FileMethods.get_handler(self.suffix)
341
+ if handler and hasattr(handler, "to_pandas"):
342
+ return handler(self.path).to_pandas()
343
+ raise AttributeError(
344
+ f"Converting {self.suffix} files to pandas DataFrame is not supported"
345
+ )
346
+
347
+ def __getattr__(self, name):
348
+ """
349
+ Delegate pandas DataFrame methods to the underlying DataFrame if this is a CSV file
350
+ """
351
+ if self.suffix == "csv":
352
+ # Get the pandas DataFrame
353
+ df = self.to_pandas()
354
+ # Check if the requested attribute exists in the DataFrame
355
+ if hasattr(df, name):
356
+ return getattr(df, name)
357
+ # If not a CSV or attribute doesn't exist in DataFrame, raise AttributeError
358
+ raise AttributeError(
359
+ f"'{self.__class__.__name__}' object has no attribute '{name}'"
360
+ )
361
+
330
362
 
331
363
  class CSVFileStore(FileStore):
332
364
  @classmethod
@@ -2,14 +2,11 @@ import os
2
2
 
3
3
 
4
4
  class PdfExtractor:
5
- def __init__(self, pdf_path: str, parent_object: object):
5
+ def __init__(self, pdf_path: str):
6
6
  self.pdf_path = pdf_path
7
- self.constructor = parent_object.__class__
7
+ #self.constructor = parent_object.__class__
8
8
 
9
- def get_object(self) -> object:
10
- return self.constructor(self._get_pdf_dict())
11
-
12
- def _get_pdf_dict(self) -> dict:
9
+ def get_pdf_dict(self) -> dict:
13
10
  # Ensure the file exists
14
11
  import fitz
15
12
 
@@ -358,7 +358,8 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
358
358
  def from_pdf(cls, pdf_path: str):
359
359
  from edsl.scenarios.PdfExtractor import PdfExtractor
360
360
 
361
- return PdfExtractor(pdf_path, cls).get_object()
361
+ extractor = PdfExtractor(pdf_path)
362
+ return Scenario(extractor.get_pdf_dict())
362
363
 
363
364
  @classmethod
364
365
  def from_docx(cls, docx_path: str) -> "Scenario":
@@ -36,3 +36,14 @@ class CsvMethods(FileMethods):
36
36
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as f:
37
37
  df.to_csv(f.name, index=False)
38
38
  return f.name
39
+
40
+ def to_pandas(self):
41
+ """
42
+ Convert the CSV file to a pandas DataFrame.
43
+
44
+ Returns:
45
+ pandas.DataFrame: The data from the CSV as a DataFrame
46
+ """
47
+ import pandas as pd
48
+
49
+ return pd.read_csv(self.path)
edsl/surveys/Survey.py CHANGED
@@ -391,6 +391,10 @@ class Survey(SurveyExportMixin, Base):
391
391
 
392
392
  if (class_name := pass_dict.get("edsl_class_name")) == "QuestionBase":
393
393
  return QuestionBase
394
+ elif pass_dict.get("edsl_class_name") == "QuestionDict":
395
+ from edsl.questions.QuestionDict import QuestionDict
396
+
397
+ return QuestionDict
394
398
  elif class_name == "Instruction":
395
399
  from edsl.surveys.instructions.Instruction import Instruction
396
400
 
@@ -1277,4 +1281,4 @@ if __name__ == "__main__":
1277
1281
  import doctest
1278
1282
 
1279
1283
  # doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.SKIP)
1280
- doctest.testmod(optionflags=doctest.ELLIPSIS)
1284
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Exception Details</title>
6
+ <title>Exceptions Report</title>
7
7
  <style>
8
8
  {{ css }}
9
9
  </style>
@@ -15,9 +15,7 @@
15
15
  </head>
16
16
  <body>
17
17
  {% include 'overview.html' %}
18
- {% include 'exceptions_by_type.html' %}
19
- {% include 'exceptions_by_model.html' %}
20
- {% include 'exceptions_by_question_name.html' %}
18
+ {% include 'exceptions_table.html' %}
21
19
  {% include 'interviews.html' %}
22
20
  {% include 'performance_plot.html' %}
23
21
  </body>
@@ -0,0 +1,35 @@
1
+ <style>
2
+ th, td {
3
+ padding: 0 10px; /* This applies the padding uniformly to all td elements */
4
+ }
5
+ </style>
6
+
7
+ <table border="1">
8
+ <thead>
9
+ <tr>
10
+ <th>Exception Type</th>
11
+ <th>Service</th>
12
+ <th>Model</th>
13
+ <th>Question Name</th>
14
+ <th>Total</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ {% for (exception_type, service, model, question_name), count in exceptions_table.items() %}
19
+ <tr>
20
+ <td>{{ exception_type }}</td>
21
+ <td>{{ service }}</td>
22
+ <td>{{ model }}</td>
23
+ <td>{{ question_name }}</td>
24
+ <td>{{ count }}</td>
25
+ </tr>
26
+ {% endfor %}
27
+ </tbody>
28
+ </table>
29
+ <p>
30
+ <i>Note:</i> You may encounter repeated exceptions where retries were attempted.
31
+ You can modify the maximum number of attempts for failed API calls in `edsl/config.py`.
32
+ </p>
33
+ <p>
34
+ Click to expand the details below for information about each exception, including code for reproducing it.
35
+ </p>
@@ -1,43 +1,67 @@
1
- <div class="question">question_name: {{ question }}</div>
2
-
1
+ <style>
2
+ td {
3
+ padding: 0 10px; /* This applies the padding uniformly to all td elements */
4
+ }
5
+ .toggle-btn {
6
+ background-color: #4CAF50;
7
+ color: white;
8
+ border: none;
9
+ padding: 10px 20px;
10
+ text-align: center;
11
+ text-decoration: none;
12
+ display: inline-block;
13
+ font-size: 16px;
14
+ margin: 4px 2px;
15
+ cursor: pointer;
16
+ border-radius: 8px;
17
+ white-space: nowrap;
18
+ }
19
+ .toggle-btn span.collapse {
20
+ display: none;
21
+ }
22
+ .exception-content {
23
+ max-width: 100%; /* Adjust this value based on your layout */
24
+ overflow-x: auto; /* Enables horizontal scrolling */
25
+ }
26
+ </style>
3
27
 
4
- <h2>Exception details</h2>
28
+ <div class="question">question_name: {{ question }}</div>
5
29
 
6
30
  {% for exception_message in exceptions %}
7
31
  <div class="exception-detail">
8
- <div class="exception-header">
32
+ <div class="exception-header">
9
33
  <span class="exception-exception">Exception: {{ exception_message.name }}</span>
10
- <button class="toggle-btn">▼</button>
11
- </div>
12
- <div class="exception-content">
34
+ <button id="toggleBtn" class="toggle-btn" onclick="toggleButton(this)" aria-expanded="false">
35
+ <span class="expand"> ▼ </span>
36
+ </button>
37
+ </div>
38
+ <div class="exception-content">
13
39
  <table border="1">
14
- <tr>
15
- <th>Key</th>
16
- <th>Value</th>
17
- </tr>
18
40
  <tr>
19
41
  <td>Interview ID (index in results)</td>
20
42
  <td>{{ index }}</td>
21
43
  </tr>
22
44
  <tr>
23
- <td>Question name (question_name)</td>
45
+ <td>Question name</td>
24
46
  <td>{{ question }}</td>
25
47
  </tr>
26
-
27
48
  <tr>
28
- <td>Question type (question_type)</td>
49
+ <td>Question type</td>
29
50
  <td>{{ exception_message.question_type }}</td>
30
51
  </tr>
31
-
32
52
  <tr>
33
53
  <td>Human-readable question</td>
34
54
  <td>{{ interview.survey._get_question_by_name(question).html(
35
55
  scenario = interview.scenario,
36
56
  agent = interview.agent,
37
- answers = exception_message.answers)
38
-
57
+ answers = exception_message.answers
58
+ )
39
59
  }}</td>
40
60
  </tr>
61
+ <tr>
62
+ <td>User Prompt</td>
63
+ <td><pre>{{ exception_message.rendered_prompts['user_prompt'] }}</pre></td>
64
+ </tr>
41
65
  <tr>
42
66
  <td>Scenario</td>
43
67
  <td>{{ interview.scenario.__repr__() }}</td>
@@ -47,24 +71,20 @@
47
71
  <td>{{ interview.agent.__repr__() }}</td>
48
72
  </tr>
49
73
  <tr>
50
- <td>Model name</td>
51
- <td>{{ interview.model.model }}</td>
74
+ <td>System Prompt</td>
75
+ <td><pre>{{ exception_message.rendered_prompts['system_prompt'] }}</pre></td>
52
76
  </tr>
53
77
  <tr>
54
78
  <td>Inference service</td>
55
79
  <td>{{ interview.model._inference_service_ }}</td>
56
80
  </tr>
57
81
  <tr>
58
- <td>Model parameters</td>
59
- <td>{{ interview.model.__repr__() }}</td>
60
- </tr>
61
- <tr>
62
- <td>User Prompt</td>
63
- <td><pre>{{ exception_message.rendered_prompts['user_prompt'] }}</pre></td>
82
+ <td>Model name</td>
83
+ <td>{{ interview.model.model }}</td>
64
84
  </tr>
65
85
  <tr>
66
- <td>System Prompt</td>
67
- <td><pre>{{ exception_message.rendered_prompts['system_prompt'] }}</pre></td>
86
+ <td>Model parameters</td>
87
+ <td>{{ interview.model.__repr__() }}</td>
68
88
  </tr>
69
89
  <tr>
70
90
  <td>Raw model response</td>
@@ -77,7 +97,7 @@
77
97
  </td>
78
98
  </tr>
79
99
  <tr>
80
- <td>Code to (likely) reproduce the error</td>
100
+ <td>Code likely to reproduce the error</td>
81
101
  <td>
82
102
  <textarea id="codeToCopy" rows="10" cols="90">{{ exception_message.code_to_reproduce }}</textarea>
83
103
  <button onclick="copyCode()">Copy</button>
@@ -85,32 +105,26 @@
85
105
  </tr>
86
106
 
87
107
  </table>
88
-
89
-
90
- {% if exception_message.exception.__class__.__name__ == 'QuestionAnswerValidationError' %}
91
- <h3>Answer validation details</h3>
92
- <table border="1">
93
- <tr>
94
- <th>Field</th>
95
- <th>Value</th>
96
- </tr>
97
- {% for field, (explanation, open_tag, close_tag, value) in exception_message.exception.to_html_dict().items() %}
98
-
99
- <tr>
100
- <td>{{ field }}: ({{ explanation }})</td>
101
- <td><{{open_tag}}> {{ value | escape }} <{{close_tag}}></td>
102
- </tr>
103
- {% endfor %}
104
- </table>
105
- {% endif %}
106
-
107
- <div class="exception-time">Time: {{ exception_message.time }}</div>
108
- <div class="exception-traceback">Traceback:
109
- <text>
110
- <pre>{{ exception_message.traceback }}</pre>
111
- </text>
112
- </div>
108
+
109
+ {% if exception_message.exception.__class__.__name__ == 'QuestionAnswerValidationError' %}
110
+ <h3>Answer validation details</h3>
111
+ <table border="1">
112
+ {% for field, (open_tag, close_tag, value) in exception_message.exception.to_html_dict().items() %}
113
+ <tr>
114
+ <td>{{ field }}</td>
115
+ <td><{{ open_tag }}> {{ value | escape }} <{{ close_tag }}></td>
116
+ </tr>
117
+ {% endfor %}
118
+ </table>
119
+ {% endif %}
120
+ <br><br>
121
+ <div class="exception-time">Time: {{ exception_message.time }}</div>
122
+ <div class="exception-traceback">Traceback:
123
+ <text>
124
+ <pre>{{ exception_message.traceback }}</pre>
125
+ </text>
113
126
  </div>
114
127
  </div>
128
+ </div>
115
129
 
116
130
  {% endfor %}
@@ -1,19 +1,6 @@
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
-
1
+ <h2>Exceptions Details</h2>
8
2
  {% 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 %}
3
+ {% for question, exceptions in interview.exceptions.items() %}
4
+ {% include 'interview_details.html' %}
5
+ {% endfor %}
19
6
  {% endfor %}
@@ -1,5 +1,31 @@
1
- <h1>Overview</h1>
2
- <p>There were {{ interviews|length }} total interview(s). An 'interview' is the result of one survey, taken by one agent, with one model, with one scenario.</p>
3
- The number of interviews with any exceptions was {{ num_exceptions }}.</p>
4
- <p>For advice on dealing with exceptions on Expected Parrot,
5
- see <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">here</a>.</p>
1
+ <style>
2
+ td {
3
+ padding: 0 10px; /* This applies the padding uniformly to all td elements */
4
+ }
5
+ </style>
6
+
7
+ <h1>Exceptions Report</h1>
8
+ <p>
9
+ This report summarizes exceptions encountered in the job that was run.
10
+ </p>
11
+ <p>
12
+ For advice on dealing with exceptions, please see the EDSL <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">documentation</a> page. <br>
13
+ You can also post a question at the Expected Parrot <a href="https://discord.com/invite/mxAYkjfy9m">Discord channel</a>, open an issue on <a href="https://github.com/expectedparrot/edsl">GitHub</a>, or send an email to <a href="mailto:info@expectedparrot.com">info@expectedparrot.com</a>.
14
+ </p>
15
+
16
+ <h2>Overview</h2>
17
+ <table border="1">
18
+ <tbody>
19
+ <tr>
20
+ <td>Total interviews</td>
21
+ <td>{{ interviews|length }}</td>
22
+ </tr>
23
+ <tr>
24
+ <td>Interviews with exceptions</td>
25
+ <td>{{ num_exceptions }}</td>
26
+ </tr>
27
+ </tbody>
28
+ </table>
29
+ <p>
30
+ An "interview" is the result of one survey, taken by one agent, with one model and one scenario (if any).
31
+ </p>
@@ -1,2 +1,2 @@
1
- <h1>Performance Plot</h1>
1
+ <h2>Performance Plot</h2>
2
2
  {{ performance_plot_html }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edsl
3
- Version: 0.1.40.dev2
3
+ Version: 0.1.42
4
4
  Summary: Create and analyze LLM-based surveys
5
5
  Home-page: https://www.expectedparrot.com/
6
6
  License: MIT