langfun 0.1.2.dev202410100804__py3-none-any.whl → 0.1.2.dev202410120803__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.
- langfun/core/__init__.py +1 -0
- langfun/core/eval/base_test.py +1 -0
- langfun/core/langfunc_test.py +2 -2
- langfun/core/language_model.py +140 -24
- langfun/core/language_model_test.py +166 -36
- langfun/core/llms/__init__.py +8 -1
- langfun/core/llms/anthropic.py +72 -7
- langfun/core/llms/cache/in_memory_test.py +3 -2
- langfun/core/llms/fake_test.py +7 -0
- langfun/core/llms/groq.py +154 -6
- langfun/core/llms/openai.py +300 -42
- langfun/core/llms/openai_test.py +35 -8
- langfun/core/llms/vertexai.py +121 -16
- langfun/core/logging.py +150 -43
- langfun/core/logging_test.py +33 -0
- langfun/core/message.py +249 -70
- langfun/core/message_test.py +70 -45
- langfun/core/modalities/audio.py +1 -1
- langfun/core/modalities/audio_test.py +1 -1
- langfun/core/modalities/image.py +1 -1
- langfun/core/modalities/image_test.py +9 -3
- langfun/core/modalities/mime.py +39 -3
- langfun/core/modalities/mime_test.py +39 -0
- langfun/core/modalities/ms_office.py +2 -5
- langfun/core/modalities/ms_office_test.py +1 -1
- langfun/core/modalities/pdf_test.py +1 -1
- langfun/core/modalities/video.py +1 -1
- langfun/core/modalities/video_test.py +2 -2
- langfun/core/structured/completion_test.py +1 -0
- langfun/core/structured/mapping.py +38 -0
- langfun/core/structured/mapping_test.py +55 -0
- langfun/core/structured/parsing_test.py +2 -1
- langfun/core/structured/prompting_test.py +1 -0
- langfun/core/structured/schema.py +34 -0
- langfun/core/template.py +110 -1
- langfun/core/template_test.py +37 -0
- langfun/core/templates/selfplay_test.py +4 -2
- {langfun-0.1.2.dev202410100804.dist-info → langfun-0.1.2.dev202410120803.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202410100804.dist-info → langfun-0.1.2.dev202410120803.dist-info}/RECORD +42 -42
- {langfun-0.1.2.dev202410100804.dist-info → langfun-0.1.2.dev202410120803.dist-info}/LICENSE +0 -0
- {langfun-0.1.2.dev202410100804.dist-info → langfun-0.1.2.dev202410120803.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202410100804.dist-info → langfun-0.1.2.dev202410120803.dist-info}/top_level.txt +0 -0
langfun/core/modalities/mime.py
CHANGED
@@ -178,14 +178,50 @@ class Mime(lf.Modality):
|
|
178
178
|
assert content is not None
|
179
179
|
return content
|
180
180
|
|
181
|
-
def
|
181
|
+
def _html_tree_view_content(
|
182
|
+
self,
|
183
|
+
**kwargs) -> str:
|
184
|
+
return self._raw_html()
|
185
|
+
|
186
|
+
def _html_tree_view_render(
|
187
|
+
self,
|
188
|
+
view: pg.views.HtmlTreeView,
|
189
|
+
raw_mime_content: bool = pg.View.PresetArgValue(False), # pytype: disable=annotation-type-mismatch
|
190
|
+
display_modality_when_hover: bool = pg.View.PresetArgValue(False), # pytype: disable=annotation-type-mismatch
|
191
|
+
**kwargs
|
192
|
+
):
|
193
|
+
if raw_mime_content:
|
194
|
+
return pg.Html(self._raw_html())
|
195
|
+
else:
|
196
|
+
if display_modality_when_hover:
|
197
|
+
kwargs.update(
|
198
|
+
display_modality_when_hover=True,
|
199
|
+
enable_summary_tooltip=True,
|
200
|
+
)
|
201
|
+
return super()._html_tree_view_render(view=view, **kwargs)
|
202
|
+
|
203
|
+
def _html_tree_view_tooltip(
|
204
|
+
self,
|
205
|
+
*,
|
206
|
+
view: pg.views.HtmlTreeView,
|
207
|
+
content: pg.Html | str | None = None,
|
208
|
+
display_modality_when_hover: bool = pg.View.PresetArgValue(False), # pytype: disable=annotation-type-mismatch
|
209
|
+
**kwargs
|
210
|
+
):
|
211
|
+
if content is None and display_modality_when_hover:
|
212
|
+
content = self._raw_html()
|
213
|
+
return super()._html_tree_view_tooltip(
|
214
|
+
view=view, content=content, **kwargs
|
215
|
+
)
|
216
|
+
|
217
|
+
def _raw_html(self) -> str:
|
182
218
|
if self.uri and self.uri.lower().startswith(('http:', 'https:', 'ftp:')):
|
183
219
|
uri = self.uri
|
184
220
|
else:
|
185
221
|
uri = self.content_uri
|
186
|
-
return self.
|
222
|
+
return self._mime_control_for(uri)
|
187
223
|
|
188
|
-
def
|
224
|
+
def _mime_control_for(self, uri) -> str:
|
189
225
|
return f'<embed type="{self.mime_type}" src="{uri}"/>'
|
190
226
|
|
191
227
|
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
"""MIME tests."""
|
15
|
+
import inspect
|
15
16
|
import unittest
|
16
17
|
from unittest import mock
|
17
18
|
|
@@ -77,6 +78,44 @@ class CustomMimeTest(unittest.TestCase):
|
|
77
78
|
self.assertEqual(content.to_bytes(), 'bar')
|
78
79
|
self.assertEqual(content.mime_type, 'text/plain')
|
79
80
|
|
81
|
+
def assert_html_content(self, html, expected):
|
82
|
+
expected = inspect.cleandoc(expected).strip()
|
83
|
+
actual = html.content.strip()
|
84
|
+
if actual != expected:
|
85
|
+
print(actual)
|
86
|
+
self.assertEqual(actual, expected)
|
87
|
+
|
88
|
+
def test_html(self):
|
89
|
+
self.assert_html_content(
|
90
|
+
mime.Custom('text/plain', b'foo').to_html(
|
91
|
+
enable_summary_tooltip=False,
|
92
|
+
enable_key_tooltip=False,
|
93
|
+
),
|
94
|
+
"""
|
95
|
+
<details open class="pyglove custom"><summary><div class="summary_title">Custom(...)</div></summary><embed type="text/plain" src="data:text/plain;base64,Zm9v"/></details>
|
96
|
+
"""
|
97
|
+
)
|
98
|
+
self.assert_html_content(
|
99
|
+
mime.Custom('text/plain', b'foo').to_html(
|
100
|
+
enable_summary_tooltip=False,
|
101
|
+
enable_key_tooltip=False,
|
102
|
+
raw_mime_content=True,
|
103
|
+
),
|
104
|
+
"""
|
105
|
+
<embed type="text/plain" src="data:text/plain;base64,Zm9v"/>
|
106
|
+
"""
|
107
|
+
)
|
108
|
+
self.assert_html_content(
|
109
|
+
mime.Custom('text/plain', b'foo').to_html(
|
110
|
+
enable_summary_tooltip=False,
|
111
|
+
enable_key_tooltip=False,
|
112
|
+
display_modality_when_hover=True,
|
113
|
+
),
|
114
|
+
"""
|
115
|
+
<details open class="pyglove custom"><summary><div class="summary_title">Custom(...)</div><span class="tooltip custom"><embed type="text/plain" src="data:text/plain;base64,Zm9v"/></span></summary><embed type="text/plain" src="data:text/plain;base64,Zm9v"/></details>
|
116
|
+
"""
|
117
|
+
)
|
118
|
+
|
80
119
|
|
81
120
|
if __name__ == '__main__':
|
82
121
|
unittest.main()
|
@@ -29,7 +29,7 @@ class Xlsx(mime.Mime):
|
|
29
29
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
30
30
|
)
|
31
31
|
|
32
|
-
def
|
32
|
+
def _raw_html(self) -> str:
|
33
33
|
try:
|
34
34
|
import pandas as pd # pylint: disable=g-import-not-at-top
|
35
35
|
import openpyxl # pylint: disable=g-import-not-at-top, unused-import
|
@@ -40,9 +40,6 @@ class Xlsx(mime.Mime):
|
|
40
40
|
'Please install "langfun[mime-xlsx]" to enable XLSX support.'
|
41
41
|
) from e
|
42
42
|
|
43
|
-
def _repr_html_(self) -> str:
|
44
|
-
return self.to_html()
|
45
|
-
|
46
43
|
def _is_compatible(self, mime_types: Iterable[str]) -> bool:
|
47
44
|
return bool(set(mime_types).intersection([
|
48
45
|
'text/html',
|
@@ -52,7 +49,7 @@ class Xlsx(mime.Mime):
|
|
52
49
|
def _make_compatible(self, mime_types: Iterable[str]) -> mime.Mime:
|
53
50
|
"""Returns the MimeType of the converted file."""
|
54
51
|
del mime_types
|
55
|
-
return mime.Mime(uri=self.uri, content=self.
|
52
|
+
return mime.Mime(uri=self.uri, content=self._raw_html())
|
56
53
|
|
57
54
|
|
58
55
|
class Docx(mime.Mime):
|
@@ -347,7 +347,7 @@ class XlsxTest(unittest.TestCase):
|
|
347
347
|
),
|
348
348
|
)
|
349
349
|
self.assertEqual(content.to_bytes(), xlsx_bytes)
|
350
|
-
self.assertEqual(content.
|
350
|
+
self.assertEqual(content._raw_html(), expected_xlsx_html)
|
351
351
|
|
352
352
|
|
353
353
|
class PptxTest(unittest.TestCase):
|
langfun/core/modalities/video.py
CHANGED
@@ -26,5 +26,5 @@ class Video(mime.Mime):
|
|
26
26
|
def video_format(self) -> str:
|
27
27
|
return self.mime_type.removeprefix(self.MIME_PREFIX + '/')
|
28
28
|
|
29
|
-
def
|
29
|
+
def _mime_control_for(self, uri: str) -> str:
|
30
30
|
return f'<video controls> <source src="{uri}"> </video>'
|
@@ -38,7 +38,7 @@ class VideoContentTest(unittest.TestCase):
|
|
38
38
|
video = video_lib.Video.from_bytes(mp4_bytes)
|
39
39
|
self.assertEqual(video.mime_type, 'video/mp4')
|
40
40
|
self.assertEqual(video.video_format, 'mp4')
|
41
|
-
self.assertIn('data:video/mp4;base64,', video.
|
41
|
+
self.assertIn('data:video/mp4;base64,', video._raw_html())
|
42
42
|
self.assertEqual(video.to_bytes(), mp4_bytes)
|
43
43
|
|
44
44
|
def test_bad_video(self):
|
@@ -56,7 +56,7 @@ class VideoFileTest(unittest.TestCase):
|
|
56
56
|
self.assertEqual(video.video_format, 'mp4')
|
57
57
|
self.assertEqual(video.mime_type, 'video/mp4')
|
58
58
|
self.assertEqual(
|
59
|
-
video.
|
59
|
+
video._raw_html(),
|
60
60
|
'<video controls> <source src="http://mock/web/a.mp4"> </video>',
|
61
61
|
)
|
62
62
|
self.assertEqual(video.to_bytes(), mp4_bytes)
|
@@ -581,6 +581,7 @@ class CompleteStructureTest(unittest.TestCase):
|
|
581
581
|
text='Activity(description="foo")',
|
582
582
|
result=Activity(description='foo'),
|
583
583
|
score=1.0,
|
584
|
+
is_cached=False,
|
584
585
|
logprobs=None,
|
585
586
|
usage=lf.LMSamplingUsage(553, 27, 580),
|
586
587
|
tags=['lm-response', 'lm-output', 'transformed']
|
@@ -183,6 +183,44 @@ class MappingExample(lf.NaturalLanguageFormattable, lf.Component):
|
|
183
183
|
result.write(lf.colored(str(self.metadata), color='cyan'))
|
184
184
|
return result.getvalue().strip()
|
185
185
|
|
186
|
+
def _html_tree_view_content(
|
187
|
+
self,
|
188
|
+
*,
|
189
|
+
parent: Any,
|
190
|
+
view: pg.views.HtmlTreeView,
|
191
|
+
root_path: pg.KeyPath,
|
192
|
+
**kwargs,
|
193
|
+
):
|
194
|
+
def render_value(value, **kwargs):
|
195
|
+
if isinstance(value, lf.Template):
|
196
|
+
# Make a shallow copy to make sure modalities are rooted by
|
197
|
+
# the input.
|
198
|
+
value = value.clone().render()
|
199
|
+
return view.render(value, **kwargs)
|
200
|
+
|
201
|
+
exclude_keys = []
|
202
|
+
if not self.context:
|
203
|
+
exclude_keys.append('context')
|
204
|
+
if not self.schema:
|
205
|
+
exclude_keys.append('schema')
|
206
|
+
if not self.metadata:
|
207
|
+
exclude_keys.append('metadata')
|
208
|
+
|
209
|
+
kwargs.pop('special_keys', None)
|
210
|
+
kwargs.pop('exclude_keys', None)
|
211
|
+
return view.complex_value(
|
212
|
+
self.sym_init_args,
|
213
|
+
parent=self,
|
214
|
+
root_path=root_path,
|
215
|
+
render_value_fn=render_value,
|
216
|
+
special_keys=['input', 'output', 'context', 'schema', 'metadata'],
|
217
|
+
exclude_keys=exclude_keys,
|
218
|
+
**kwargs
|
219
|
+
)
|
220
|
+
|
221
|
+
def _html_tree_view_uncollapse_level(self) -> int:
|
222
|
+
return 2
|
223
|
+
|
186
224
|
|
187
225
|
class Mapping(lf.LangFunc):
|
188
226
|
"""Base class for mapping.
|
@@ -14,6 +14,7 @@
|
|
14
14
|
"""Tests for structured mapping example."""
|
15
15
|
|
16
16
|
import inspect
|
17
|
+
from typing import Any
|
17
18
|
import unittest
|
18
19
|
|
19
20
|
import langfun.core as lf
|
@@ -164,6 +165,60 @@ class MappingExampleTest(unittest.TestCase):
|
|
164
165
|
pg.eq(pg.from_json_str(example.to_json_str()), example)
|
165
166
|
)
|
166
167
|
|
168
|
+
def assert_html_content(self, html, expected):
|
169
|
+
expected = inspect.cleandoc(expected).strip()
|
170
|
+
actual = html.content.strip()
|
171
|
+
if actual != expected:
|
172
|
+
print(actual)
|
173
|
+
self.assertEqual(actual, expected)
|
174
|
+
|
175
|
+
def test_html(self):
|
176
|
+
|
177
|
+
class Answer(pg.Object):
|
178
|
+
answer: int
|
179
|
+
|
180
|
+
class Addition(lf.Template):
|
181
|
+
"""Template Addition.
|
182
|
+
|
183
|
+
{{x}} + {{y}} = ?
|
184
|
+
"""
|
185
|
+
x: Any
|
186
|
+
y: Any
|
187
|
+
|
188
|
+
example = mapping.MappingExample(
|
189
|
+
input=Addition(x=1, y=2),
|
190
|
+
schema=Answer,
|
191
|
+
context='compute 1 + 1',
|
192
|
+
output=Answer(answer=3),
|
193
|
+
metadata={'foo': 'bar'},
|
194
|
+
)
|
195
|
+
self.assert_html_content(
|
196
|
+
example.to_html(
|
197
|
+
enable_summary_tooltip=False, include_message_metadata=False
|
198
|
+
),
|
199
|
+
"""
|
200
|
+
<details open class="pyglove mapping-example"><summary><div class="summary_title">MappingExample(...)</div></summary><div class="complex_value mapping-example"><div class="special_value"><details open class="pyglove user-message lf-message"><summary><div class="summary_name">input</div><div class="summary_title">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>rendered</span></div><div class="message-text">1 + 2 = ?</div></div></details></div><div class="special_value"><details open class="pyglove answer"><summary><div class="summary_name">output</div><div class="summary_title">Answer(...)</div></summary><div class="complex_value answer"><table><tr><td><span class="object_key str">answer</span><span class="tooltip key-path">output.answer</span></td><td><div><span class="simple_value int">3</span></div></td></tr></table></div></details></div><div class="special_value"><details open class="pyglove str"><summary><div class="summary_name">context</div><div class="summary_title">'compute 1 + 1'</div></summary><span class="simple_value str">'compute 1 + 1'</span></details></div><div class="special_value"><details open class="pyglove schema"><summary><div class="summary_name">schema</div><div class="summary_title">Schema(...)</div></summary><div class="lf-schema-definition">Answer
|
201
|
+
|
202
|
+
```python
|
203
|
+
class Answer:
|
204
|
+
answer: int
|
205
|
+
```</div></details></div><div class="special_value"><details open class="pyglove dict"><summary><div class="summary_name">metadata</div><div class="summary_title">Dict(...)</div></summary><div class="complex_value dict"><table><tr><td><span class="object_key str">foo</span><span class="tooltip key-path">metadata.foo</span></td><td><div><span class="simple_value str">'bar'</span></div></td></tr></table></div></details></div></div></details>
|
206
|
+
"""
|
207
|
+
)
|
208
|
+
|
209
|
+
example = mapping.MappingExample(
|
210
|
+
input=Addition(x=1, y=2),
|
211
|
+
output=Answer(answer=3),
|
212
|
+
)
|
213
|
+
self.assert_html_content(
|
214
|
+
example.to_html(
|
215
|
+
enable_summary_tooltip=False, include_message_metadata=False
|
216
|
+
),
|
217
|
+
"""
|
218
|
+
<details open class="pyglove mapping-example"><summary><div class="summary_title">MappingExample(...)</div></summary><div class="complex_value mapping-example"><div class="special_value"><details open class="pyglove user-message lf-message"><summary><div class="summary_name">input</div><div class="summary_title">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>rendered</span></div><div class="message-text">1 + 2 = ?</div></div></details></div><div class="special_value"><details open class="pyglove answer"><summary><div class="summary_name">output</div><div class="summary_title">Answer(...)</div></summary><div class="complex_value answer"><table><tr><td><span class="object_key str">answer</span><span class="tooltip key-path">output.answer</span></td><td><div><span class="simple_value int">3</span></div></td></tr></table></div></details></div></div></details>
|
219
|
+
"""
|
220
|
+
)
|
221
|
+
|
167
222
|
|
168
223
|
if __name__ == '__main__':
|
169
224
|
unittest.main()
|
@@ -285,7 +285,7 @@ class ParseStructurePythonTest(unittest.TestCase):
|
|
285
285
|
self.assertEqual(
|
286
286
|
r,
|
287
287
|
lf.AIMessage(
|
288
|
-
'1', score=1.0, result=1, logprobs=None,
|
288
|
+
'1', score=1.0, result=1, logprobs=None, is_cached=False,
|
289
289
|
usage=lf.LMSamplingUsage(652, 1, 653),
|
290
290
|
tags=['lm-response', 'lm-output', 'transformed']
|
291
291
|
),
|
@@ -645,6 +645,7 @@ class CallTest(unittest.TestCase):
|
|
645
645
|
result=3,
|
646
646
|
score=1.0,
|
647
647
|
logprobs=None,
|
648
|
+
is_cached=False,
|
648
649
|
usage=lf.LMSamplingUsage(315, 1, 316),
|
649
650
|
tags=['lm-response', 'lm-output', 'transformed']
|
650
651
|
),
|
@@ -189,6 +189,40 @@ class Schema(lf.NaturalLanguageFormattable, pg.Object):
|
|
189
189
|
return value
|
190
190
|
return cls(parse_value_spec(value))
|
191
191
|
|
192
|
+
def _html_tree_view_content(
|
193
|
+
self,
|
194
|
+
*,
|
195
|
+
view: pg.views.HtmlTreeView,
|
196
|
+
root_path: pg.KeyPath,
|
197
|
+
**kwargs,
|
198
|
+
):
|
199
|
+
return pg.Html.element(
|
200
|
+
'div',
|
201
|
+
[self.schema_str(protocol='python')],
|
202
|
+
css_class=['lf-schema-definition']
|
203
|
+
).add_style(
|
204
|
+
"""
|
205
|
+
.lf-schema-definition {
|
206
|
+
color: blue;
|
207
|
+
margin: 5px;
|
208
|
+
white-space: pre-wrap;
|
209
|
+
}
|
210
|
+
"""
|
211
|
+
)
|
212
|
+
|
213
|
+
def _html_tree_view_tooltip(
|
214
|
+
self,
|
215
|
+
*,
|
216
|
+
view: pg.views.HtmlTreeView,
|
217
|
+
content: pg.Html | str | None = None,
|
218
|
+
**kwargs,
|
219
|
+
):
|
220
|
+
return view.tooltip(
|
221
|
+
self,
|
222
|
+
content=content or pg.Html.escape(self.schema_str(protocol='python')),
|
223
|
+
**kwargs
|
224
|
+
)
|
225
|
+
|
192
226
|
|
193
227
|
def _top_level_object_specs_from_value(value: pg.Symbolic) -> list[Type[Any]]:
|
194
228
|
"""Returns a list of top level value specs from a symbolic value."""
|
langfun/core/template.py
CHANGED
@@ -17,7 +17,7 @@ import contextlib
|
|
17
17
|
import dataclasses
|
18
18
|
import functools
|
19
19
|
import inspect
|
20
|
-
from typing import Annotated, Any, Callable, Iterator, Set, Tuple, Type, Union
|
20
|
+
from typing import Annotated, Any, Callable, Iterator, Sequence, Set, Tuple, Type, Union
|
21
21
|
|
22
22
|
import jinja2
|
23
23
|
from jinja2 import meta as jinja2_meta
|
@@ -526,6 +526,115 @@ class Template(
|
|
526
526
|
return lfun
|
527
527
|
return cls(template_str='{{input}}', input=value, **kwargs)
|
528
528
|
|
529
|
+
def _html_tree_view_content(
|
530
|
+
self,
|
531
|
+
*,
|
532
|
+
view: pg.views.HtmlTreeView,
|
533
|
+
root_path: pg.KeyPath,
|
534
|
+
collapse_template_vars_level: int | None = pg.View.PresetArgValue(1),
|
535
|
+
collapse_level: int | None = pg.View.PresetArgValue(1), # pytype: disable=annotation-type-mismatch
|
536
|
+
**kwargs,
|
537
|
+
):
|
538
|
+
def render_template_str():
|
539
|
+
return pg.Html.element(
|
540
|
+
'div',
|
541
|
+
[
|
542
|
+
pg.Html.element('span', [self.template_str])
|
543
|
+
],
|
544
|
+
css_class=['template-str'],
|
545
|
+
)
|
546
|
+
|
547
|
+
def render_fields():
|
548
|
+
def render_value_fn(value, *, root_path, **kwargs):
|
549
|
+
if isinstance(value, component.ContextualAttribute):
|
550
|
+
inferred = self.sym_inferred(root_path.key, pg.MISSING_VALUE)
|
551
|
+
if inferred != pg.MISSING_VALUE:
|
552
|
+
return pg.Html.element(
|
553
|
+
'div',
|
554
|
+
[
|
555
|
+
view.render(inferred, root_path=root_path, **kwargs)
|
556
|
+
],
|
557
|
+
css_class=['inferred-value'],
|
558
|
+
)
|
559
|
+
else:
|
560
|
+
return pg.Html.element(
|
561
|
+
'span',
|
562
|
+
['(external)'],
|
563
|
+
css_class=['contextual-variable'],
|
564
|
+
)
|
565
|
+
return view.render(
|
566
|
+
value, root_path=root_path, **kwargs
|
567
|
+
)
|
568
|
+
return pg.Html.element(
|
569
|
+
'fieldset',
|
570
|
+
[
|
571
|
+
pg.Html.element('legend', ['Template Variables']),
|
572
|
+
view.complex_value(
|
573
|
+
self.sym_init_args,
|
574
|
+
name='fields',
|
575
|
+
root_path=root_path,
|
576
|
+
render_value_fn=render_value_fn,
|
577
|
+
exclude_keys=['template_str', 'clean'],
|
578
|
+
parent=self,
|
579
|
+
collapse_level=view.max_collapse_level(
|
580
|
+
collapse_level,
|
581
|
+
collapse_template_vars_level,
|
582
|
+
root_path
|
583
|
+
),
|
584
|
+
),
|
585
|
+
],
|
586
|
+
css_class=['template-fields'],
|
587
|
+
)
|
588
|
+
|
589
|
+
return pg.Html.element(
|
590
|
+
'div',
|
591
|
+
[
|
592
|
+
render_template_str(),
|
593
|
+
render_fields(),
|
594
|
+
],
|
595
|
+
css_class=['complex_value'],
|
596
|
+
)
|
597
|
+
|
598
|
+
def _html_style(self) -> list[str]:
|
599
|
+
return super()._html_style() + [
|
600
|
+
"""
|
601
|
+
/* Langfun Template styles. */
|
602
|
+
.template-str {
|
603
|
+
padding: 10px;
|
604
|
+
margin: 10px 5px 10px 5px;
|
605
|
+
font-style: italic;
|
606
|
+
font-size: 1.1em;
|
607
|
+
white-space: pre-wrap;
|
608
|
+
border: 1px solid #EEE;
|
609
|
+
border-radius: 5px;
|
610
|
+
background-color: #EEE;
|
611
|
+
color: #cc2986;
|
612
|
+
}
|
613
|
+
.template-fields {
|
614
|
+
margin: 0px 0px 5px 0px;
|
615
|
+
border: 1px solid #EEE;
|
616
|
+
padding: 5px;
|
617
|
+
}
|
618
|
+
.template-fields > legend {
|
619
|
+
font-size: 0.8em;
|
620
|
+
margin: 5px 0px 5px 0px;
|
621
|
+
}
|
622
|
+
.inferred-value::after {
|
623
|
+
content: ' (inferred)';
|
624
|
+
color: gray;
|
625
|
+
font-style: italic;
|
626
|
+
}
|
627
|
+
.contextual-variable {
|
628
|
+
margin: 0px 0px 0px 5px;
|
629
|
+
font-style: italic;
|
630
|
+
color: gray;
|
631
|
+
}
|
632
|
+
"""
|
633
|
+
]
|
634
|
+
|
635
|
+
# Additional CSS class to add to the root <details> element.
|
636
|
+
def _html_element_class(self) -> Sequence[str] | None:
|
637
|
+
return super()._html_element_class() + ['lf-template']
|
529
638
|
|
530
639
|
# Register converter from str to LangFunc, therefore we can always
|
531
640
|
# pass strs to attributes that accept LangFunc.
|
langfun/core/template_test.py
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
"""Template test."""
|
15
15
|
import inspect
|
16
|
+
from typing import Any
|
16
17
|
import unittest
|
17
18
|
|
18
19
|
from langfun.core import component
|
@@ -552,5 +553,41 @@ class TemplateRenderEventTest(unittest.TestCase):
|
|
552
553
|
self.assertEqual(render_stacks, [[l]])
|
553
554
|
|
554
555
|
|
556
|
+
class HtmlTest(unittest.TestCase):
|
557
|
+
|
558
|
+
def assert_html_content(self, html, expected):
|
559
|
+
expected = inspect.cleandoc(expected).strip()
|
560
|
+
actual = html.content.strip()
|
561
|
+
if actual != expected:
|
562
|
+
print(actual)
|
563
|
+
self.assertEqual(actual, expected)
|
564
|
+
|
565
|
+
def test_html(self):
|
566
|
+
|
567
|
+
class Foo(Template):
|
568
|
+
"""Template Foo.
|
569
|
+
|
570
|
+
{{x}} + {{y}} = ?
|
571
|
+
"""
|
572
|
+
x: Any
|
573
|
+
y: Any
|
574
|
+
|
575
|
+
class Bar(Template):
|
576
|
+
"""Template Bar.
|
577
|
+
|
578
|
+
{{y}} + {{z}}
|
579
|
+
"""
|
580
|
+
y: Any
|
581
|
+
|
582
|
+
self.assert_html_content(
|
583
|
+
Foo(x=Bar('{{y}} + {{z}}'), y=1).to_html(
|
584
|
+
enable_summary_tooltip=False,
|
585
|
+
),
|
586
|
+
"""
|
587
|
+
<details open class="pyglove foo lf-template"><summary><div class="summary_title">Foo(...)</div></summary><div class="complex_value"><div class="template-str"><span>{{x}} + {{y}} = ?</span></div><fieldset class="template-fields"><legend>Template Variables</legend><div class="complex_value foo lf-template"><table><tr><td><span class="object_key str">x</span><span class="tooltip key-path">x</span></td><td><div><details class="pyglove bar lf-template"><summary><div class="summary_title">Bar(...)</div></summary><div class="complex_value"><div class="template-str"><span>{{y}} + {{z}}</span></div><fieldset class="template-fields"><legend>Template Variables</legend><div class="complex_value bar lf-template"><table><tr><td><span class="object_key str">y</span><span class="tooltip key-path">x.y</span></td><td><div><div class="inferred-value"><span class="simple_value int">1</span></div></div></td></tr><tr><td><span class="object_key str">z</span><span class="tooltip key-path">x.z</span></td><td><div><span class="contextual-variable">(external)</span></div></td></tr></table></div></fieldset></div></details></div></td></tr><tr><td><span class="object_key str">y</span><span class="tooltip key-path">y</span></td><td><div><span class="simple_value int">1</span></div></td></tr></table></div></fieldset></div></details>
|
588
|
+
"""
|
589
|
+
)
|
590
|
+
|
591
|
+
|
555
592
|
if __name__ == '__main__':
|
556
593
|
unittest.main()
|
@@ -59,7 +59,8 @@ class SelfPlayTest(unittest.TestCase):
|
|
59
59
|
self.assertEqual(
|
60
60
|
g(),
|
61
61
|
lf.AIMessage(
|
62
|
-
'10', score=0.0, logprobs=None,
|
62
|
+
'10', score=0.0, logprobs=None, is_cached=False,
|
63
|
+
usage=lf.UsageNotAvailable()
|
63
64
|
)
|
64
65
|
)
|
65
66
|
|
@@ -72,7 +73,8 @@ class SelfPlayTest(unittest.TestCase):
|
|
72
73
|
self.assertEqual(
|
73
74
|
g(),
|
74
75
|
lf.AIMessage(
|
75
|
-
'2', score=0.0, logprobs=None,
|
76
|
+
'2', score=0.0, logprobs=None, is_cached=False,
|
77
|
+
usage=lf.UsageNotAvailable()
|
76
78
|
)
|
77
79
|
)
|
78
80
|
|