langfun 0.1.2.dev202503240804__py3-none-any.whl → 0.1.2.dev202503250804__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/__init__.py CHANGED
@@ -48,6 +48,7 @@ query_output = structured.query_output
48
48
  source_form = structured.source_form
49
49
  function_gen = structured.function_gen
50
50
 
51
+ from langfun.core import data
51
52
  from langfun.core import eval # pylint: disable=redefined-builtin
52
53
  from langfun.core import templates
53
54
  from langfun.core import coding
langfun/core/__init__.py CHANGED
@@ -87,6 +87,8 @@ from langfun.core.message import AIMessage
87
87
  from langfun.core.message import SystemMessage
88
88
  from langfun.core.message import MemoryRecord
89
89
 
90
+ from langfun.core.message import MessageConverter
91
+
90
92
  # Interface for modality.
91
93
  from langfun.core.modality import Modality
92
94
  from langfun.core.modality import ModalityRef
@@ -0,0 +1,19 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """langfun data sub-module."""
15
+
16
+ # pylint: disable=g-importing-member
17
+ # pylint: disable=g-bad-import-order
18
+
19
+ from langfun.core.data import conversion
@@ -0,0 +1,21 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """langfun data conversions."""
15
+
16
+ # pylint: disable=g-importing-member
17
+ # pylint: disable=g-bad-import-order
18
+
19
+ from langfun.core.data.conversion import anthropic
20
+ from langfun.core.data.conversion import gemini
21
+ from langfun.core.data.conversion import openai
@@ -0,0 +1,131 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Anthropic API message conversion."""
15
+
16
+ import base64
17
+ from typing import Annotated, Any, Callable
18
+
19
+ import langfun.core as lf
20
+ from langfun.core import modalities as lf_modalities
21
+
22
+
23
+ class AnthropicMessageConverter(lf.MessageConverter):
24
+ """Converter to Anthropic public API."""
25
+
26
+ FORMAT_ID = 'anthropic'
27
+
28
+ chunk_preprocessor: Annotated[
29
+ Callable[[str | lf.Modality], Any] | None,
30
+ (
31
+ 'Chunk preprocessor for Langfun chunk to Anthropic chunk conversion. '
32
+ 'It will be applied before each Langfun chunk is converted. '
33
+ 'If returns None, the chunk will be skipped.'
34
+ )
35
+ ] = None
36
+
37
+ def to_value(self, message: lf.Message) -> dict[str, Any]:
38
+ """Converts a Langfun message to Gemini API."""
39
+ content = []
40
+ for chunk in message.chunk():
41
+ if self.chunk_preprocessor:
42
+ chunk = self.chunk_preprocessor(chunk)
43
+ if chunk is None:
44
+ continue
45
+
46
+ if isinstance(chunk, str):
47
+ content.append({'type': 'text', 'text': chunk})
48
+ elif isinstance(chunk, lf_modalities.Mime):
49
+ if isinstance(chunk, lf_modalities.Image):
50
+ content.append(
51
+ dict(
52
+ type='image',
53
+ source=dict(
54
+ type='base64',
55
+ media_type=chunk.mime_type,
56
+ data=base64.b64encode(chunk.to_bytes()).decode(),
57
+ ),
58
+ )
59
+ )
60
+ elif isinstance(chunk, lf_modalities.PDF):
61
+ content.append(
62
+ dict(
63
+ type='document',
64
+ source=dict(
65
+ type='base64',
66
+ media_type=chunk.mime_type,
67
+ data=base64.b64encode(chunk.to_bytes()).decode(),
68
+ ),
69
+ )
70
+ )
71
+ else:
72
+ raise NotImplementedError(
73
+ f'Modality conversion not implemented: {chunk!r}'
74
+ )
75
+ return dict(role=self.get_role(message), content=content)
76
+
77
+ def from_value(self, value: dict[str, Any]) -> lf.Message:
78
+ """Returns a Langfun message from Anthropic message."""
79
+ message_cls = self.get_message_cls(
80
+ self._safe_read(value, 'role', default='assistant')
81
+ )
82
+ content = self._safe_read(value, 'content', default=[])
83
+ assert isinstance(content, list)
84
+
85
+ chunks = []
86
+ thought_chunks = []
87
+ for part in content:
88
+ t = self._safe_read(part, 'type')
89
+ if t == 'text':
90
+ chunks.append(self._safe_read(part, 'text'))
91
+ elif t == 'thinking':
92
+ thought_chunks.append(self._safe_read(part, 'thinking'))
93
+ elif t in ('image', 'document'):
94
+ source = self._safe_read(part, 'source')
95
+ chunks.append(
96
+ lf_modalities.Mime.class_from_mime_type(
97
+ self._safe_read(source, 'media_type')
98
+ ).from_bytes(base64.b64decode(self._safe_read(source, 'data')))
99
+ )
100
+ else:
101
+ raise ValueError(f'Unsupported content part: {part!r}.')
102
+ message = message_cls.from_chunks(chunks)
103
+ if thought_chunks:
104
+ message.set('thought', message_cls.from_chunks(thought_chunks))
105
+ return message
106
+
107
+
108
+ def _as_anthropic_format(
109
+ self,
110
+ chunk_preprocessor: Callable[[str | lf.Modality], Any] | None = None,
111
+ **kwargs
112
+ ) -> dict[str, Any]:
113
+ """Returns the Anthropic format of the chunk."""
114
+ return AnthropicMessageConverter(
115
+ chunk_preprocessor=chunk_preprocessor, **kwargs
116
+ ).to_value(self)
117
+
118
+
119
+ @classmethod
120
+ def _from_anthropic_format(
121
+ cls,
122
+ anthropic_message: dict[str, Any],
123
+ **kwargs
124
+ ) -> dict[str, Any]:
125
+ """Returns the Anthropic format of the chunk."""
126
+ del cls
127
+ return AnthropicMessageConverter(**kwargs).from_value(anthropic_message)
128
+
129
+ # Set shortcut methods in lf.Message.
130
+ lf.Message.as_anthropic_format = _as_anthropic_format
131
+ lf.Message.from_anthropic_format = _from_anthropic_format
@@ -0,0 +1,267 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import base64
15
+ import unittest
16
+ import langfun.core as lf
17
+ from langfun.core import modalities as lf_modalities
18
+ from langfun.core.data.conversion import anthropic # pylint: disable=unused-import
19
+
20
+
21
+ image_content = (
22
+ b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x04'
23
+ b'\x03\x00\x00\x00\x12Y \xcb\x00\x00\x00\x18PLTE\x00\x00'
24
+ b'\x00fff_chaag_cg_ch^ci_ciC\xedb\x94\x00\x00\x00\x08tRNS'
25
+ b'\x00\n\x9f*\xd4\xff_\xf4\xe4\x8b\xf3a\x00\x00\x00>IDATx'
26
+ b'\x01c \x05\x08)"\xd8\xcc\xae!\x06pNz\x88k\x19\\Q\xa8"\x10'
27
+ b'\xc1\x14\x95\x01%\xc1\n\xa143Ta\xa8"D-\x84\x03QM\x98\xc3'
28
+ b'\x1a\x1a\x1a@5\x0e\x04\xa0q\x88\x05\x00\x07\xf8\x18\xf9'
29
+ b'\xdao\xd0|\x00\x00\x00\x00IEND\xaeB`\x82'
30
+ )
31
+
32
+ pdf_content = (
33
+ b'%PDF-1.1\n%\xc2\xa5\xc2\xb1\xc3\xab\n\n1 0 obj\n'
34
+ b'<< /Type /Catalog\n /Pages 2 0 R\n >>\nendobj\n\n2 0 obj\n '
35
+ b'<< /Type /Pages\n /Kids [3 0 R]\n '
36
+ b'/Count 1\n /MediaBox [0 0 300 144]\n '
37
+ b'>>\nendobj\n\n3 0 obj\n '
38
+ b'<< /Type /Page\n /Parent 2 0 R\n /Resources\n '
39
+ b'<< /Font\n'
40
+ b'<< /F1\n'
41
+ b'<< /Type /Font\n'
42
+ b'/Subtype /Type1\n'
43
+ b'/BaseFont /Times-Roman\n'
44
+ b'>>\n>>\n>>\n '
45
+ b'/Contents 4 0 R\n >>\nendobj\n\n4 0 obj\n '
46
+ b'<< /Length 55 >>\nstream\n BT\n /F1 18 Tf\n 0 0 Td\n '
47
+ b'(Hello World) Tj\n ET\nendstream\nendobj\n\nxref\n0 5\n0000000000 '
48
+ b'65535 f \n0000000018 00000 n \n0000000077 00000 n \n0000000178 00000 n '
49
+ b'\n0000000457 00000 n \ntrailer\n << /Root 1 0 R\n /Size 5\n '
50
+ b'>>\nstartxref\n565\n%%EOF\n'
51
+ )
52
+
53
+
54
+ class AnthropicConversionTest(unittest.TestCase):
55
+
56
+ def test_as_format_with_role(self):
57
+ self.assertEqual(
58
+ lf.UserMessage('hi').as_format('anthropic'),
59
+ {
60
+ 'role': 'user',
61
+ 'content': [{'type': 'text', 'text': 'hi'}],
62
+ },
63
+ )
64
+ self.assertEqual(
65
+ lf.AIMessage('hi').as_format('anthropic'),
66
+ {
67
+ 'role': 'assistant',
68
+ 'content': [{'type': 'text', 'text': 'hi'}],
69
+ },
70
+ )
71
+ self.assertEqual(
72
+ lf.SystemMessage('hi').as_format('anthropic'),
73
+ {
74
+ 'role': 'system',
75
+ 'content': [{'type': 'text', 'text': 'hi'}],
76
+ },
77
+ )
78
+
79
+ def test_as_format_with_image(self):
80
+ self.assertEqual(
81
+ lf.Template(
82
+ 'What are the common words from {{image}} and {{pdf}}?',
83
+ image=lf_modalities.Image.from_bytes(image_content),
84
+ pdf=lf_modalities.PDF.from_bytes(pdf_content),
85
+ ).render().as_anthropic_format(),
86
+ {
87
+ 'role': 'user',
88
+ 'content': [
89
+ {
90
+ 'type': 'text',
91
+ 'text': 'What are the common words from'
92
+ },
93
+ {
94
+ 'type': 'image',
95
+ 'source': {
96
+ 'type': 'base64',
97
+ 'media_type': 'image/png',
98
+ 'data': base64.b64encode(
99
+ image_content
100
+ ).decode('utf-8'),
101
+ }
102
+ },
103
+ {
104
+ 'type': 'text',
105
+ 'text': 'and'
106
+ },
107
+ {
108
+ 'type': 'document',
109
+ 'source': {
110
+ 'type': 'base64',
111
+ 'media_type': 'application/pdf',
112
+ 'data': base64.b64encode(
113
+ pdf_content
114
+ ).decode('utf-8'),
115
+ }
116
+ },
117
+ {
118
+ 'type': 'text',
119
+ 'text': '?'
120
+ },
121
+ ],
122
+ },
123
+ )
124
+
125
+ def test_as_format_with_chunk_preprocessor(self):
126
+ self.assertEqual(
127
+ lf.Template(
128
+ 'What is this {{image}}?',
129
+ image=lf_modalities.Image.from_bytes(image_content)
130
+ ).render().as_format(
131
+ 'anthropic',
132
+ chunk_preprocessor=lambda x: x if isinstance(x, str) else None
133
+ ),
134
+ {
135
+ 'role': 'user',
136
+ 'content': [
137
+ {
138
+ 'type': 'text', 'text': 'What is this'
139
+ },
140
+ {
141
+ 'type': 'text', 'text': '?'
142
+ }
143
+ ],
144
+ },
145
+ )
146
+
147
+ def test_from_value_with_simple_text(self):
148
+ self.assertEqual(
149
+ lf.Message.from_value(
150
+ {
151
+ 'content': [{'type': 'text', 'text': 'this is a text'}],
152
+ },
153
+ format='anthropic',
154
+ ),
155
+ lf.AIMessage('this is a text'),
156
+ )
157
+
158
+ def test_from_value_with_role(self):
159
+ self.assertEqual(
160
+ lf.Message.from_value(
161
+ {
162
+ 'role': 'user',
163
+ 'content': [{'type': 'text', 'text': 'this is a text'}],
164
+ },
165
+ format='anthropic',
166
+ ),
167
+ lf.UserMessage('this is a text'),
168
+ )
169
+ self.assertEqual(
170
+ lf.Message.from_value(
171
+ {
172
+ 'role': 'assistant',
173
+ 'content': [{'type': 'text', 'text': 'this is a text'}],
174
+ },
175
+ format='anthropic',
176
+ ),
177
+ lf.AIMessage('this is a text'),
178
+ )
179
+ self.assertEqual(
180
+ lf.Message.from_value(
181
+ {
182
+ 'role': 'system',
183
+ 'content': [{'type': 'text', 'text': 'this is a text'}],
184
+ },
185
+ format='anthropic',
186
+ ),
187
+ lf.SystemMessage('this is a text'),
188
+ )
189
+ with self.assertRaisesRegex(ValueError, 'Unsupported role: .*'):
190
+ lf.Message.from_value(
191
+ {
192
+ 'role': 'function',
193
+ 'content': [{'type': 'text', 'text': 'this is a text'}],
194
+ },
195
+ format='anthropic',
196
+ )
197
+
198
+ def test_from_value_with_thoughts(self):
199
+ message = lf.Message.from_anthropic_format(
200
+ {
201
+ 'role': 'user',
202
+ 'content': [
203
+ {
204
+ 'type': 'thinking',
205
+ 'thinking': 'this is a red round object',
206
+ },
207
+ {
208
+ 'type': 'text',
209
+ 'text': 'this is a apple',
210
+ },
211
+ ],
212
+ },
213
+ )
214
+ self.assertEqual(message.text, 'this is a apple')
215
+ self.assertEqual(message.thought, 'this is a red round object')
216
+
217
+ def test_from_value_with_modalities(self):
218
+ m = lf.Message.from_value(
219
+ {
220
+ 'role': 'user',
221
+ 'content': [
222
+ {
223
+ 'type': 'text',
224
+ 'text': 'What are the common words from'
225
+ },
226
+ {
227
+ 'type': 'image',
228
+ 'source': {
229
+ 'type': 'base64',
230
+ 'media_type': 'image/png',
231
+ 'data': base64.b64encode(image_content).decode('utf-8'),
232
+ }
233
+ },
234
+ {
235
+ 'type': 'text',
236
+ 'text': 'and'
237
+ },
238
+ {
239
+ 'type': 'document',
240
+ 'source': {
241
+ 'type': 'base64',
242
+ 'media_type': 'application/pdf',
243
+ 'data': base64.b64encode(pdf_content).decode('utf-8'),
244
+ }
245
+ },
246
+ {
247
+ 'type': 'text',
248
+ 'text': '?'
249
+ },
250
+ ],
251
+ },
252
+ format='anthropic',
253
+ )
254
+ self.assertEqual(
255
+ m.text,
256
+ 'What are the common words from <<[[obj0]]>> and <<[[obj1]]>> ?'
257
+ )
258
+ self.assertIsInstance(m.obj0, lf_modalities.Image)
259
+ self.assertEqual(m.obj0.mime_type, 'image/png')
260
+ self.assertEqual(m.obj0.to_bytes(), image_content)
261
+
262
+ self.assertIsInstance(m.obj1, lf_modalities.PDF)
263
+ self.assertEqual(m.obj1.to_bytes(), pdf_content)
264
+
265
+
266
+ if __name__ == '__main__':
267
+ unittest.main()
@@ -0,0 +1,168 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Gemini API message conversion."""
15
+
16
+ import base64
17
+ from typing import Annotated, Any, Callable
18
+
19
+ import langfun.core as lf
20
+ from langfun.core import modalities as lf_modalities
21
+
22
+
23
+ class GeminiMessageConverter(lf.MessageConverter):
24
+ """Converter to Gemini public API."""
25
+
26
+ FORMAT_ID = 'gemini'
27
+
28
+ chunk_preprocessor: Annotated[
29
+ Callable[[str | lf.Modality], Any] | None,
30
+ (
31
+ 'Chunk preprocessor for Langfun chunk to Gemini chunk conversion. '
32
+ 'It will be applied before each Langfun chunk is converted. '
33
+ 'If returns None, the chunk will be skipped.'
34
+ )
35
+ ] = None
36
+
37
+ def to_value(self, message: lf.Message) -> dict[str, Any]:
38
+ """Converts a Langfun message to Gemini API."""
39
+ parts = []
40
+ for chunk in message.chunk():
41
+ if self.chunk_preprocessor:
42
+ chunk = self.chunk_preprocessor(chunk)
43
+ if chunk is None:
44
+ continue
45
+
46
+ if isinstance(chunk, str):
47
+ parts.append(self._convert_chunk(chunk))
48
+ else:
49
+ if isinstance(chunk, lf_modalities.Mime):
50
+ modalities = [chunk]
51
+ # NOTE(daiyip): preprocessing may convert a single chunk into
52
+ # a list of chunks
53
+ elif isinstance(chunk, list):
54
+ modalities = chunk
55
+ else:
56
+ raise ValueError(f'Unsupported content type: {chunk!r}.')
57
+ parts.extend(self._convert_chunk(c) for c in modalities)
58
+ return dict(role=self.get_role(message), parts=parts)
59
+
60
+ def _convert_chunk(self, chunk: str | lf.Modality) -> Any:
61
+ """Converts a Langfun chunk to Gemini chunk."""
62
+ if isinstance(chunk, str):
63
+ return {'text': chunk}
64
+ if not isinstance(chunk, lf_modalities.Mime):
65
+ raise ValueError(f'Unsupported content chunk: {chunk!r}.')
66
+ # NOTE(daiyip): special handling for YouTube video.
67
+ if chunk.uri and chunk.uri.startswith('https://www.youtube.com/watch?v='):
68
+ return {
69
+ 'fileData': {
70
+ 'mimeType': 'video/*',
71
+ 'fileUri': chunk.uri
72
+ }
73
+ }
74
+ if chunk.is_text:
75
+ return {'text': chunk.to_text()}
76
+ if chunk.uri and chunk.uri.lower().startswith(
77
+ ('http:', 'https:', 'ftp:')
78
+ ):
79
+ return {
80
+ 'fileData': {
81
+ 'mimeType': chunk.mime_type,
82
+ 'fileUri': chunk.uri,
83
+ }
84
+ }
85
+ return {
86
+ 'inlineData': {
87
+ 'data': base64.b64encode(chunk.to_bytes()).decode(),
88
+ 'mimeType': chunk.mime_type,
89
+ }
90
+ }
91
+
92
+ def from_value(self, value: dict[str, Any]) -> lf.Message:
93
+ """Returns a Langfun message from Gemini message."""
94
+ message_cls = self.get_message_cls(
95
+ self._safe_read(value, 'role', default='model')
96
+ )
97
+ parts = self._safe_read(value, 'parts', default=[])
98
+ assert isinstance(parts, list)
99
+
100
+ chunks = []
101
+ thought_chunks = []
102
+ for part in parts:
103
+ if 'text' in part:
104
+ text = self._safe_read(part, 'text')
105
+ if 'thought' in part:
106
+ thought_chunks.append(text)
107
+ else:
108
+ chunks.append(text)
109
+ elif 'inlineData' in part:
110
+ data = self._safe_read(part, 'inlineData')
111
+ chunks.append(
112
+ lf_modalities.Mime.class_from_mime_type(
113
+ self._safe_read(data, 'mimeType')
114
+ ).from_bytes(base64.b64decode(self._safe_read(data, 'data')))
115
+ )
116
+ elif 'fileData' in part:
117
+ data = self._safe_read(part, 'fileData')
118
+ chunks.append(
119
+ lf_modalities.Mime.class_from_mime_type(
120
+ self._safe_read(data, 'mimeType')
121
+ ).from_uri(self._safe_read(data, 'fileUri'))
122
+ )
123
+ else:
124
+ raise ValueError(f'Unsupported content part: {part!r}.')
125
+ message = message_cls.from_chunks(chunks)
126
+ if thought_chunks:
127
+ message.set('thought', message_cls.from_chunks(thought_chunks))
128
+ return message
129
+
130
+ @classmethod
131
+ def get_role(cls, message: lf.Message) -> str:
132
+ """Returns the role of the message."""
133
+ if isinstance(message, lf.AIMessage):
134
+ return 'model'
135
+ return super().get_role(message)
136
+
137
+ @classmethod
138
+ def get_message_cls(cls, role: str) -> type[lf.Message]:
139
+ """Returns the message class of the message."""
140
+ if role == 'model':
141
+ return lf.AIMessage
142
+ return super().get_message_cls(role)
143
+
144
+
145
+ def _as_gemini_format(
146
+ self,
147
+ chunk_preprocessor: Callable[[str | lf.Modality], Any] | None = None,
148
+ **kwargs
149
+ ) -> dict[str, Any]:
150
+ """Returns the Gemini format of the chunk."""
151
+ return GeminiMessageConverter(
152
+ chunk_preprocessor=chunk_preprocessor, **kwargs
153
+ ).to_value(self)
154
+
155
+
156
+ @classmethod
157
+ def _from_gemini_format(
158
+ cls,
159
+ gemini_message: dict[str, Any],
160
+ **kwargs
161
+ ) -> dict[str, Any]:
162
+ """Returns the Gemini format of the chunk."""
163
+ del cls
164
+ return GeminiMessageConverter(**kwargs).from_value(gemini_message)
165
+
166
+ # Set shortcut methods in lf.Message.
167
+ lf.Message.as_gemini_format = _as_gemini_format
168
+ lf.Message.from_gemini_format = _from_gemini_format