edsl 0.1.38.dev4__py3-none-any.whl → 0.1.39__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 (212) hide show
  1. edsl/Base.py +197 -116
  2. edsl/__init__.py +15 -7
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +351 -147
  5. edsl/agents/AgentList.py +211 -73
  6. edsl/agents/Invigilator.py +101 -50
  7. edsl/agents/InvigilatorBase.py +62 -70
  8. edsl/agents/PromptConstructor.py +143 -225
  9. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  10. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  11. edsl/agents/__init__.py +0 -1
  12. edsl/agents/prompt_helpers.py +3 -3
  13. edsl/agents/question_option_processor.py +172 -0
  14. edsl/auto/AutoStudy.py +18 -5
  15. edsl/auto/StageBase.py +53 -40
  16. edsl/auto/StageQuestions.py +2 -1
  17. edsl/auto/utilities.py +0 -6
  18. edsl/config.py +22 -2
  19. edsl/conversation/car_buying.py +2 -1
  20. edsl/coop/CoopFunctionsMixin.py +15 -0
  21. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  22. edsl/coop/PriceFetcher.py +1 -1
  23. edsl/coop/coop.py +125 -47
  24. edsl/coop/utils.py +14 -14
  25. edsl/data/Cache.py +45 -27
  26. edsl/data/CacheEntry.py +12 -15
  27. edsl/data/CacheHandler.py +31 -12
  28. edsl/data/RemoteCacheSync.py +154 -46
  29. edsl/data/__init__.py +4 -3
  30. edsl/data_transfer_models.py +2 -1
  31. edsl/enums.py +27 -0
  32. edsl/exceptions/__init__.py +50 -50
  33. edsl/exceptions/agents.py +12 -0
  34. edsl/exceptions/inference_services.py +5 -0
  35. edsl/exceptions/questions.py +24 -6
  36. edsl/exceptions/scenarios.py +7 -0
  37. edsl/inference_services/AnthropicService.py +38 -19
  38. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  39. edsl/inference_services/AvailableModelFetcher.py +215 -0
  40. edsl/inference_services/AwsBedrock.py +0 -2
  41. edsl/inference_services/AzureAI.py +0 -2
  42. edsl/inference_services/GoogleService.py +7 -12
  43. edsl/inference_services/InferenceServiceABC.py +18 -85
  44. edsl/inference_services/InferenceServicesCollection.py +120 -79
  45. edsl/inference_services/MistralAIService.py +0 -3
  46. edsl/inference_services/OpenAIService.py +47 -35
  47. edsl/inference_services/PerplexityService.py +0 -3
  48. edsl/inference_services/ServiceAvailability.py +135 -0
  49. edsl/inference_services/TestService.py +11 -10
  50. edsl/inference_services/TogetherAIService.py +5 -3
  51. edsl/inference_services/data_structures.py +134 -0
  52. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  53. edsl/jobs/Answers.py +1 -14
  54. edsl/jobs/FetchInvigilator.py +47 -0
  55. edsl/jobs/InterviewTaskManager.py +98 -0
  56. edsl/jobs/InterviewsConstructor.py +50 -0
  57. edsl/jobs/Jobs.py +356 -431
  58. edsl/jobs/JobsChecks.py +35 -10
  59. edsl/jobs/JobsComponentConstructor.py +189 -0
  60. edsl/jobs/JobsPrompts.py +6 -4
  61. edsl/jobs/JobsRemoteInferenceHandler.py +205 -133
  62. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  63. edsl/jobs/RequestTokenEstimator.py +30 -0
  64. edsl/jobs/async_interview_runner.py +138 -0
  65. edsl/jobs/buckets/BucketCollection.py +44 -3
  66. edsl/jobs/buckets/TokenBucket.py +53 -21
  67. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  68. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  69. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  70. edsl/jobs/data_structures.py +120 -0
  71. edsl/jobs/decorators.py +35 -0
  72. edsl/jobs/interviews/Interview.py +143 -408
  73. edsl/jobs/jobs_status_enums.py +9 -0
  74. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  75. edsl/jobs/results_exceptions_handler.py +98 -0
  76. edsl/jobs/runners/JobsRunnerAsyncio.py +88 -403
  77. edsl/jobs/runners/JobsRunnerStatus.py +133 -165
  78. edsl/jobs/tasks/QuestionTaskCreator.py +21 -19
  79. edsl/jobs/tasks/TaskHistory.py +38 -18
  80. edsl/jobs/tasks/task_status_enum.py +0 -2
  81. edsl/language_models/ComputeCost.py +63 -0
  82. edsl/language_models/LanguageModel.py +194 -236
  83. edsl/language_models/ModelList.py +28 -19
  84. edsl/language_models/PriceManager.py +127 -0
  85. edsl/language_models/RawResponseHandler.py +106 -0
  86. edsl/language_models/ServiceDataSources.py +0 -0
  87. edsl/language_models/__init__.py +1 -2
  88. edsl/language_models/key_management/KeyLookup.py +63 -0
  89. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  90. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  91. edsl/language_models/key_management/__init__.py +0 -0
  92. edsl/language_models/key_management/models.py +131 -0
  93. edsl/language_models/model.py +256 -0
  94. edsl/language_models/repair.py +2 -2
  95. edsl/language_models/utilities.py +5 -4
  96. edsl/notebooks/Notebook.py +19 -14
  97. edsl/notebooks/NotebookToLaTeX.py +142 -0
  98. edsl/prompts/Prompt.py +29 -39
  99. edsl/questions/ExceptionExplainer.py +77 -0
  100. edsl/questions/HTMLQuestion.py +103 -0
  101. edsl/questions/QuestionBase.py +68 -214
  102. edsl/questions/QuestionBasePromptsMixin.py +7 -3
  103. edsl/questions/QuestionBudget.py +1 -1
  104. edsl/questions/QuestionCheckBox.py +3 -3
  105. edsl/questions/QuestionExtract.py +5 -7
  106. edsl/questions/QuestionFreeText.py +2 -3
  107. edsl/questions/QuestionList.py +10 -18
  108. edsl/questions/QuestionMatrix.py +265 -0
  109. edsl/questions/QuestionMultipleChoice.py +67 -23
  110. edsl/questions/QuestionNumerical.py +2 -4
  111. edsl/questions/QuestionRank.py +7 -17
  112. edsl/questions/SimpleAskMixin.py +4 -3
  113. edsl/questions/__init__.py +2 -1
  114. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +47 -2
  115. edsl/questions/data_structures.py +20 -0
  116. edsl/questions/derived/QuestionLinearScale.py +6 -3
  117. edsl/questions/derived/QuestionTopK.py +1 -1
  118. edsl/questions/descriptors.py +17 -3
  119. edsl/questions/loop_processor.py +149 -0
  120. edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +57 -50
  121. edsl/questions/question_registry.py +1 -1
  122. edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +40 -26
  123. edsl/questions/response_validator_factory.py +34 -0
  124. edsl/questions/templates/matrix/__init__.py +1 -0
  125. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  126. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  127. edsl/results/CSSParameterizer.py +1 -1
  128. edsl/results/Dataset.py +170 -7
  129. edsl/results/DatasetExportMixin.py +168 -305
  130. edsl/results/DatasetTree.py +28 -8
  131. edsl/results/MarkdownToDocx.py +122 -0
  132. edsl/results/MarkdownToPDF.py +111 -0
  133. edsl/results/Result.py +298 -206
  134. edsl/results/Results.py +149 -131
  135. edsl/results/ResultsExportMixin.py +2 -0
  136. edsl/results/TableDisplay.py +98 -171
  137. edsl/results/TextEditor.py +50 -0
  138. edsl/results/__init__.py +1 -1
  139. edsl/results/file_exports.py +252 -0
  140. edsl/results/{Selector.py → results_selector.py} +23 -13
  141. edsl/results/smart_objects.py +96 -0
  142. edsl/results/table_data_class.py +12 -0
  143. edsl/results/table_renderers.py +118 -0
  144. edsl/scenarios/ConstructDownloadLink.py +109 -0
  145. edsl/scenarios/DocumentChunker.py +102 -0
  146. edsl/scenarios/DocxScenario.py +16 -0
  147. edsl/scenarios/FileStore.py +150 -239
  148. edsl/scenarios/PdfExtractor.py +40 -0
  149. edsl/scenarios/Scenario.py +90 -193
  150. edsl/scenarios/ScenarioHtmlMixin.py +4 -3
  151. edsl/scenarios/ScenarioList.py +415 -244
  152. edsl/scenarios/ScenarioListExportMixin.py +0 -7
  153. edsl/scenarios/ScenarioListPdfMixin.py +15 -37
  154. edsl/scenarios/__init__.py +1 -2
  155. edsl/scenarios/directory_scanner.py +96 -0
  156. edsl/scenarios/file_methods.py +85 -0
  157. edsl/scenarios/handlers/__init__.py +13 -0
  158. edsl/scenarios/handlers/csv.py +49 -0
  159. edsl/scenarios/handlers/docx.py +76 -0
  160. edsl/scenarios/handlers/html.py +37 -0
  161. edsl/scenarios/handlers/json.py +111 -0
  162. edsl/scenarios/handlers/latex.py +5 -0
  163. edsl/scenarios/handlers/md.py +51 -0
  164. edsl/scenarios/handlers/pdf.py +68 -0
  165. edsl/scenarios/handlers/png.py +39 -0
  166. edsl/scenarios/handlers/pptx.py +105 -0
  167. edsl/scenarios/handlers/py.py +294 -0
  168. edsl/scenarios/handlers/sql.py +313 -0
  169. edsl/scenarios/handlers/sqlite.py +149 -0
  170. edsl/scenarios/handlers/txt.py +33 -0
  171. edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +10 -6
  172. edsl/scenarios/scenario_selector.py +156 -0
  173. edsl/study/ObjectEntry.py +1 -1
  174. edsl/study/SnapShot.py +1 -1
  175. edsl/study/Study.py +5 -12
  176. edsl/surveys/ConstructDAG.py +92 -0
  177. edsl/surveys/EditSurvey.py +221 -0
  178. edsl/surveys/InstructionHandler.py +100 -0
  179. edsl/surveys/MemoryManagement.py +72 -0
  180. edsl/surveys/Rule.py +5 -4
  181. edsl/surveys/RuleCollection.py +25 -27
  182. edsl/surveys/RuleManager.py +172 -0
  183. edsl/surveys/Simulator.py +75 -0
  184. edsl/surveys/Survey.py +270 -791
  185. edsl/surveys/SurveyCSS.py +20 -8
  186. edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +11 -9
  187. edsl/surveys/SurveyToApp.py +141 -0
  188. edsl/surveys/__init__.py +4 -2
  189. edsl/surveys/descriptors.py +6 -2
  190. edsl/surveys/instructions/ChangeInstruction.py +1 -2
  191. edsl/surveys/instructions/Instruction.py +4 -13
  192. edsl/surveys/instructions/InstructionCollection.py +11 -6
  193. edsl/templates/error_reporting/interview_details.html +1 -1
  194. edsl/templates/error_reporting/report.html +1 -1
  195. edsl/tools/plotting.py +1 -1
  196. edsl/utilities/PrettyList.py +56 -0
  197. edsl/utilities/is_notebook.py +18 -0
  198. edsl/utilities/is_valid_variable_name.py +11 -0
  199. edsl/utilities/remove_edsl_version.py +24 -0
  200. edsl/utilities/utilities.py +35 -23
  201. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/METADATA +12 -10
  202. edsl-0.1.39.dist-info/RECORD +358 -0
  203. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/WHEEL +1 -1
  204. edsl/language_models/KeyLookup.py +0 -30
  205. edsl/language_models/registry.py +0 -190
  206. edsl/language_models/unused/ReplicateBase.py +0 -83
  207. edsl/results/ResultsDBMixin.py +0 -238
  208. edsl-0.1.38.dev4.dist-info/RECORD +0 -277
  209. /edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +0 -0
  210. /edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +0 -0
  211. /edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +0 -0
  212. {edsl-0.1.38.dev4.dist-info → edsl-0.1.39.dist-info}/LICENSE +0 -0
edsl/prompts/Prompt.py CHANGED
@@ -1,43 +1,21 @@
1
1
  from __future__ import annotations
2
- from typing import Optional
3
- from abc import ABC
4
- from typing import Any, List
5
-
6
- from jinja2 import Environment, FileSystemLoader
7
- from typing import Union, Dict
2
+ from typing import Any, List, Union, Dict, Optional
8
3
  from pathlib import Path
9
4
 
10
- from rich.table import Table
11
- from jinja2 import Template, Environment, meta, TemplateSyntaxError, Undefined
12
-
13
-
14
- class PreserveUndefined(Undefined):
15
- def __str__(self):
16
- return "{{ " + str(self._undefined_name) + " }}"
5
+ # from jinja2 import Undefined
17
6
 
18
7
 
19
8
  from edsl.exceptions.prompts import TemplateRenderError
20
- from edsl.Base import PersistenceMixin, RichPrintingMixin
9
+ from edsl.Base import PersistenceMixin, RepresentationMixin
21
10
 
22
11
  MAX_NESTING = 100
23
12
 
24
13
 
25
- class Prompt(PersistenceMixin, RichPrintingMixin):
14
+ class Prompt(PersistenceMixin, RepresentationMixin):
26
15
  """Class for creating a prompt to be used in a survey."""
27
16
 
28
17
  default_instructions: Optional[str] = "Do good things, friendly LLM!"
29
18
 
30
- def _repr_html_(self):
31
- """Return an HTML representation of the Prompt."""
32
- # from edsl.utilities.utilities import data_to_html
33
- # return data_to_html(self.to_dict())
34
- d = self.to_dict()
35
- data = [[k, v] for k, v in d.items()]
36
- from tabulate import tabulate
37
-
38
- table = str(tabulate(data, headers=["keys", "values"], tablefmt="html"))
39
- return f"<pre>{table}</pre>"
40
-
41
19
  def __len__(self):
42
20
  """Return the length of the prompt text."""
43
21
  return len(self.text)
@@ -185,6 +163,12 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
185
163
  :param template: The template to find the variables in.
186
164
 
187
165
  """
166
+ from jinja2 import Environment, meta, Undefined
167
+
168
+ class PreserveUndefined(Undefined):
169
+ def __str__(self):
170
+ return "{{ " + str(self._undefined_name) + " }}"
171
+
188
172
  env = Environment(undefined=PreserveUndefined)
189
173
  ast = env.parse(template)
190
174
  return list(meta.find_undeclared_variables(ast))
@@ -273,6 +257,12 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
273
257
  >>> p.render({"name": "John", "age": 44}, codebook=codebook)
274
258
  Prompt(text=\"""You are an agent named John. Age: 44\""")
275
259
  """
260
+ from jinja2 import Environment, meta, TemplateSyntaxError, Undefined
261
+
262
+ class PreserveUndefined(Undefined):
263
+ def __str__(self):
264
+ return "{{ " + str(self._undefined_name) + " }}"
265
+
276
266
  env = Environment(undefined=PreserveUndefined)
277
267
  try:
278
268
  previous_text = None
@@ -296,7 +286,7 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
296
286
  f"Template syntax error: {e}. Bad template: {text}"
297
287
  )
298
288
 
299
- def to_dict(self) -> dict[str, Any]:
289
+ def to_dict(self, add_edsl_version=False) -> dict[str, Any]:
300
290
  """Return the `Prompt` as a dictionary.
301
291
 
302
292
  Example:
@@ -323,18 +313,18 @@ class Prompt(PersistenceMixin, RichPrintingMixin):
323
313
  # class_name = data["class_name"]
324
314
  return Prompt(text=data["text"])
325
315
 
326
- def rich_print(self):
327
- """Display an object as a table."""
328
- table = Table(title="Prompt")
329
- table.add_column("Attribute", style="bold")
330
- table.add_column("Value")
331
-
332
- to_display = self.__dict__.copy()
333
- for attr_name, attr_value in to_display.items():
334
- table.add_row(attr_name, repr(attr_value))
335
- table.add_row("Component type", str(self.component_type))
336
- table.add_row("Model", str(getattr(self, "model", "Not specified")))
337
- return table
316
+ # def rich_print(self):
317
+ # """Display an object as a table."""
318
+ # table = Table(title="Prompt")
319
+ # table.add_column("Attribute", style="bold")
320
+ # table.add_column("Value")
321
+
322
+ # to_display = self.__dict__.copy()
323
+ # for attr_name, attr_value in to_display.items():
324
+ # table.add_row(attr_name, repr(attr_value))
325
+ # table.add_row("Component type", str(self.component_type))
326
+ # table.add_row("Model", str(getattr(self, "model", "Not specified")))
327
+ # return table
338
328
 
339
329
  @classmethod
340
330
  def example(cls):
@@ -0,0 +1,77 @@
1
+ from pydantic import ValidationError
2
+ from typing import Union
3
+
4
+
5
+ class ExceptionExplainer:
6
+ """
7
+ A class that converts validation errors into human-readable explanations,
8
+ specifically for Language Model responses.
9
+ """
10
+
11
+ def __init__(self, error: Union[ValidationError, Exception], model_response: str):
12
+ """
13
+ Initialize the explainer with the error and model response.
14
+
15
+ Args:
16
+ error: The validation error that occurred
17
+ model_response: The raw response from the Language Model
18
+ """
19
+ self.error = error
20
+ self.model_response = model_response
21
+
22
+ def explain(self) -> str:
23
+ """
24
+ Generate a human-readable explanation of why the model's response failed validation.
25
+
26
+ Returns:
27
+ A user-friendly explanation of why the model's response was invalid
28
+ """
29
+ self.error = self.error.pydantic_error
30
+ return self._explain_validation_error()
31
+
32
+ # Fallback for unknown errors
33
+ return self._create_generic_explanation()
34
+
35
+ def _explain_validation_error(self) -> str:
36
+ """Handle Pydantic ValidationError specifically."""
37
+ error_dict = self.error.errors()
38
+ explanations = []
39
+
40
+ context = f'The AI model returned "{self.model_response}", but this was invalid for the question you asked and the constraints you provided.\n'
41
+ explanations.append(context)
42
+ explanations.append("Reason(s) invalidated:")
43
+ for e in error_dict:
44
+ msg = e.get("msg", "Unknown error")
45
+ explanations.append(f"- {msg}")
46
+
47
+ main_message = "\n".join(explanations)
48
+ return f"{main_message}\n\n{self._get_suggestion()}"
49
+
50
+ def _create_generic_explanation(self) -> str:
51
+ """Create a generic explanation for non-ValidationError exceptions."""
52
+ return (
53
+ f'The AI model returned "{self.model_response}", but this response was invalid. '
54
+ f"Error: {str(self.error)}"
55
+ )
56
+
57
+ def _get_suggestion(self) -> str:
58
+ """Get a suggestion for handling the error."""
59
+ return (
60
+ "EDSL Advice:\n"
61
+ "- Look at the Model comments - often the model will provide a hint about what went wrong.\n"
62
+ "- If the model's response doesn't make sense, try rephrasing your question.\n"
63
+ "- Try using 'use_code' parameter of a MultipleChoice.\n"
64
+ "- A QuestionFreeText will almost always validate.\n"
65
+ "- Try setting the 'permissive' = True parameter in the Question constructor."
66
+ )
67
+
68
+
69
+ # Example usage:
70
+ if __name__ == "__main__":
71
+ try:
72
+ # Your validation code here
73
+ raise ValidationError.parse_obj({"answer": "120"})
74
+ except ValidationError as e:
75
+ explainer = ExceptionExplainer(e, "120")
76
+ explanation = explainer.explain()
77
+ print(explanation)
@@ -0,0 +1,103 @@
1
+ from typing import Optional
2
+ from edsl.prompts.Prompt import Prompt
3
+
4
+
5
+ class HTMLQuestion:
6
+ def __init__(self, question):
7
+ self.question = question
8
+
9
+ def html(
10
+ self,
11
+ scenario: Optional[dict] = None,
12
+ agent: Optional[dict] = {},
13
+ answers: Optional[dict] = None,
14
+ include_question_name: bool = False,
15
+ height: Optional[int] = None,
16
+ width: Optional[int] = None,
17
+ iframe=False,
18
+ ):
19
+ """Return the question in HTML format."""
20
+ from jinja2 import Template
21
+
22
+ if scenario is None:
23
+ scenario = {}
24
+
25
+ prior_answers_dict = {}
26
+
27
+ if isinstance(answers, dict):
28
+ for key, value in answers.items():
29
+ if not key.endswith("_comment") and not key.endswith(
30
+ "_generated_tokens"
31
+ ):
32
+ prior_answers_dict[key] = {"answer": value}
33
+
34
+ base_template = """
35
+ <div id="{{ question_name }}" class="survey_question" data-type="{{ question_type }}">
36
+ {% if include_question_name %}
37
+ <p>question_name: {{ question_name }}</p>
38
+ {% endif %}
39
+ <p class="question_text">{{ question_text }}</p>
40
+ {{ question_content }}
41
+ </div>
42
+ """
43
+ if not hasattr(self.question, "question_type"):
44
+ self.question.question_type = "unknown"
45
+
46
+ if hasattr(self.question, "question_html_content"):
47
+ question_content = self.question.question_html_content
48
+ else:
49
+ question_content = Template("")
50
+
51
+ base_template = Template(base_template)
52
+
53
+ context = {
54
+ "scenario": scenario,
55
+ "agent": agent,
56
+ } | prior_answers_dict
57
+
58
+ # Render the question text
59
+ try:
60
+ question_text = Template(self.question.question_text).render(context)
61
+ except Exception as e:
62
+ print(
63
+ f"Error rendering question: question_text = {self.question.question_text}, error = {e}"
64
+ )
65
+ question_text = self.question.question_text
66
+
67
+ try:
68
+ question_content = Template(question_content).render(context)
69
+ except Exception as e:
70
+ print(
71
+ f"Error rendering question: question_content = {question_content}, error = {e}"
72
+ )
73
+ question_content = question_content
74
+
75
+ try:
76
+ params = {
77
+ "question_name": self.question.question_name,
78
+ "question_text": question_text,
79
+ "question_type": self.question.question_type,
80
+ "question_content": question_content,
81
+ "include_question_name": include_question_name,
82
+ }
83
+ except Exception as e:
84
+ raise ValueError(
85
+ f"Error rendering question: params = {params}, error = {e}"
86
+ )
87
+ rendered_html = base_template.render(**params)
88
+
89
+ if iframe:
90
+ import html
91
+ from IPython.display import display, HTML
92
+
93
+ height = height or 200
94
+ width = width or 600
95
+ escaped_output = html.escape(rendered_html)
96
+ # escaped_output = rendered_html
97
+ iframe = f""""
98
+ <iframe srcdoc="{ escaped_output }" style="width: {width}px; height: {height}px;"></iframe>
99
+ """
100
+ display(HTML(iframe))
101
+ return None
102
+
103
+ return rendered_html