langfun 0.0.2.dev20240415__py3-none-any.whl → 0.0.2.dev20240419__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
@@ -34,6 +34,7 @@ score = structured.score
34
34
  generate_class = structured.generate_class
35
35
 
36
36
  source_form = structured.source_form
37
+ function_gen = structured.function_gen
37
38
 
38
39
  from langfun.core import eval # pylint: disable=redefined-builtin
39
40
  from langfun.core import templates
@@ -440,7 +440,7 @@ class LanguageModel(component.Component):
440
440
  response.metadata.usage = result.usage
441
441
 
442
442
  elapse = time.time() - request_start
443
- self._debug(prompt, response, call_counter, elapse)
443
+ self._debug(prompt, response, call_counter, result.usage, elapse)
444
444
  return response
445
445
 
446
446
  def _debug(
@@ -448,35 +448,51 @@ class LanguageModel(component.Component):
448
448
  prompt: message_lib.Message,
449
449
  response: message_lib.Message,
450
450
  call_counter: int,
451
+ usage: LMSamplingUsage | None,
451
452
  elapse: float,
452
- ):
453
+ ) -> None:
453
454
  """Outputs debugging information."""
454
455
  debug = self.debug
455
456
  if isinstance(debug, bool):
456
457
  debug = LMDebugMode.ALL if debug else LMDebugMode.NONE
457
458
 
458
459
  if debug & LMDebugMode.INFO:
459
- self._debug_model_info(call_counter)
460
+ self._debug_model_info(call_counter, usage)
460
461
 
461
462
  if debug & LMDebugMode.PROMPT:
462
- self._debug_prompt(prompt, call_counter)
463
+ self._debug_prompt(prompt, call_counter, usage)
463
464
 
464
465
  if debug & LMDebugMode.RESPONSE:
465
- self._debug_response(response, call_counter, elapse)
466
+ self._debug_response(response, call_counter, usage, elapse)
466
467
 
467
- def _debug_model_info(self, call_counter: int):
468
+ def _debug_model_info(
469
+ self, call_counter: int, usage: LMSamplingUsage | None) -> None:
468
470
  """Outputs debugging information about the model."""
471
+ title_suffix = ''
472
+ if usage and usage.total_tokens != 0:
473
+ title_suffix = console.colored(
474
+ f' (total {usage.total_tokens} tokens)', 'red')
475
+
469
476
  console.write(
470
477
  self.format(compact=True, use_inferred=True),
471
- title=f'[{call_counter}] LM INFO:',
478
+ title=f'[{call_counter}] LM INFO{title_suffix}:',
472
479
  color='magenta',
473
480
  )
474
481
 
475
- def _debug_prompt(self, prompt: message_lib.Message, call_counter: int):
482
+ def _debug_prompt(
483
+ self,
484
+ prompt: message_lib.Message,
485
+ call_counter: int,
486
+ usage: LMSamplingUsage | None,
487
+ ) -> None:
476
488
  """Outputs debugging information about the prompt."""
489
+ title_suffix = ''
490
+ if usage and usage.prompt_tokens != 0:
491
+ title_suffix = console.colored(f' ({usage.prompt_tokens} tokens)', 'red')
492
+
477
493
  console.write(
478
494
  prompt,
479
- title=f'\n[{call_counter}] PROMPT SENT TO LM:',
495
+ title=f'\n[{call_counter}] PROMPT SENT TO LM{title_suffix}:',
480
496
  color='green',
481
497
  )
482
498
  referred_modalities = prompt.referred_modalities()
@@ -490,12 +506,22 @@ class LanguageModel(component.Component):
490
506
  )
491
507
 
492
508
  def _debug_response(
493
- self, response: message_lib.Message, call_counter: int, elapse: float
494
- ):
509
+ self,
510
+ response: message_lib.Message,
511
+ call_counter: int,
512
+ usage: LMSamplingUsage | None,
513
+ elapse: float
514
+ ) -> None:
495
515
  """Outputs debugging information about the response."""
516
+ title_suffix = ' ('
517
+ if usage and usage.completion_tokens != 0:
518
+ title_suffix += f'{usage.completion_tokens} tokens '
519
+ title_suffix += f'in {elapse:.2f} seconds)'
520
+ title_suffix = console.colored(title_suffix, 'red')
521
+
496
522
  console.write(
497
523
  str(response) + '\n',
498
- title=f'\n[{call_counter}] LM RESPONSE (in {elapse:.2f} seconds):',
524
+ title=f'\n[{call_counter}] LM RESPONSE{title_suffix}:',
499
525
  color='blue',
500
526
  )
501
527
 
@@ -542,7 +568,7 @@ class LanguageModel(component.Component):
542
568
  debug = LMDebugMode.ALL if debug else LMDebugMode.NONE
543
569
 
544
570
  if debug & LMDebugMode.INFO:
545
- self._debug_model_info(call_counter)
571
+ self._debug_model_info(call_counter, None)
546
572
 
547
573
  if debug & LMDebugMode.PROMPT:
548
574
  console.write(
@@ -57,6 +57,12 @@ from langfun.core.llms.openai import Gpt3Curie
57
57
  from langfun.core.llms.openai import Gpt3Babbage
58
58
  from langfun.core.llms.openai import Gpt3Ada
59
59
 
60
+ from langfun.core.llms.anthropic import Anthropic
61
+ from langfun.core.llms.anthropic import Claude3Opus
62
+ from langfun.core.llms.anthropic import Claude3Sonnet
63
+ from langfun.core.llms.anthropic import Claude3Haiku
64
+
65
+
60
66
  # LLaMA C++ models.
61
67
  from langfun.core.llms.llama_cpp import LlamaCppRemote
62
68
 
@@ -0,0 +1,230 @@
1
+ # Copyright 2023 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
+ """Language models from Anthropic."""
15
+
16
+ import base64
17
+ import functools
18
+ import os
19
+ from typing import Annotated, Any
20
+
21
+ import langfun.core as lf
22
+ from langfun.core import modalities as lf_modalities
23
+ import pyglove as pg
24
+ import requests
25
+
26
+
27
+ SUPPORTED_MODELS_AND_SETTINGS = {
28
+ # See https://docs.anthropic.com/claude/docs/models-overview
29
+ 'claude-3-opus-20240229': pg.Dict(max_tokens=4096, max_concurrency=1),
30
+ 'claude-3-sonnet-20240229': pg.Dict(max_tokens=4096, max_concurrency=4),
31
+ 'claude-3-haiku-20240307': pg.Dict(max_tokens=4096, max_concurrency=16),
32
+ }
33
+
34
+
35
+ class AnthropicError(Exception): # pylint: disable=g-bad-exception-name
36
+ """Base class for Anthropic errors."""
37
+
38
+
39
+ class RateLimitError(AnthropicError):
40
+ """Error for rate limit reached."""
41
+
42
+
43
+ class OverloadedError(AnthropicError):
44
+ """Anthropic's server is temporarily overloaded."""
45
+
46
+
47
+ _ANTHROPIC_MESSAGE_API_ENDPOINT = 'https://api.anthropic.com/v1/messages'
48
+ _ANTHROPIC_API_VERSION = '2023-06-01'
49
+
50
+
51
+ @lf.use_init_args(['model'])
52
+ class Anthropic(lf.LanguageModel):
53
+ """Anthropic LLMs (Claude) through REST APIs.
54
+
55
+ See https://docs.anthropic.com/claude/reference/messages_post
56
+ """
57
+
58
+ model: Annotated[ # pytype: disable=invalid-annotation
59
+ pg.typing.Enum(
60
+ pg.MISSING_VALUE, list(SUPPORTED_MODELS_AND_SETTINGS.keys())
61
+ ),
62
+ 'The name of the model to use.',
63
+ ]
64
+
65
+ multimodal: Annotated[bool, 'Whether this model has multimodal support.'] = (
66
+ True
67
+ )
68
+
69
+ api_key: Annotated[
70
+ str | None,
71
+ (
72
+ 'API key. If None, the key will be read from environment variable '
73
+ "'ANTHROPIC_API_KEY'."
74
+ ),
75
+ ] = None
76
+
77
+ def _on_bound(self):
78
+ super()._on_bound()
79
+ self._api_key = None
80
+ self.__dict__.pop('_api_initialized', None)
81
+
82
+ @functools.cached_property
83
+ def _api_initialized(self):
84
+ api_key = self.api_key or os.environ.get('ANTHROPIC_API_KEY', None)
85
+ if not api_key:
86
+ raise ValueError(
87
+ 'Please specify `api_key` during `__init__` or set environment '
88
+ 'variable `ANTHROPIC_API_KEY` with your Anthropic API key.'
89
+ )
90
+ self._api_key = api_key
91
+ return True
92
+
93
+ @property
94
+ def model_id(self) -> str:
95
+ """Returns a string to identify the model."""
96
+ return self.model
97
+
98
+ @property
99
+ def max_concurrency(self) -> int:
100
+ return SUPPORTED_MODELS_AND_SETTINGS[self.model].max_concurrency
101
+
102
+ def _sample(self, prompts: list[lf.Message]) -> list[lf.LMSamplingResult]:
103
+ assert self._api_initialized
104
+ return self._parallel_execute_with_currency_control(
105
+ self._sample_single, prompts, retry_on_errors=(RateLimitError)
106
+ )
107
+
108
+ def _get_request_args(self, options: lf.LMSamplingOptions) -> dict[str, Any]:
109
+ """Returns a dict as request arguments."""
110
+ # Authropic requires `max_tokens` to be specified.
111
+ max_tokens = (
112
+ options.max_tokens
113
+ or SUPPORTED_MODELS_AND_SETTINGS[self.model].max_tokens
114
+ )
115
+ args = dict(
116
+ model=self.model,
117
+ max_tokens=max_tokens,
118
+ stream=False,
119
+ )
120
+ if options.stop:
121
+ args['stop_sequences'] = options.stop
122
+ if options.temperature is not None:
123
+ args['temperature'] = options.temperature
124
+ if options.top_k is not None:
125
+ args['top_k'] = options.top_k
126
+ if options.top_p is not None:
127
+ args['top_p'] = options.top_p
128
+ return args
129
+
130
+ def _content_from_message(self, prompt: lf.Message) -> list[dict[str, Any]]:
131
+ """Converts an message to Anthropic's content protocol (list of dicts)."""
132
+ # Refer: https://docs.anthropic.com/claude/reference/messages-examples
133
+ if self.multimodal:
134
+ content = []
135
+ for chunk in prompt.chunk():
136
+ if isinstance(chunk, str):
137
+ item = dict(type='text', text=chunk)
138
+ elif isinstance(chunk, lf_modalities.Image):
139
+ # NOTE(daiyip): Anthropic only support image content instead of URL.
140
+ item = dict(
141
+ type='image',
142
+ source=dict(
143
+ type='base64',
144
+ media_type=chunk.mime_type,
145
+ data=base64.b64encode(chunk.to_bytes()).decode(),
146
+ ),
147
+ )
148
+ else:
149
+ raise ValueError(f'Unsupported modality object: {chunk!r}.')
150
+ content.append(item)
151
+ return content
152
+ else:
153
+ return [dict(type='text', text=prompt.text)]
154
+
155
+ def _message_from_content(self, content: list[dict[str, Any]]) -> lf.Message:
156
+ """Converts Anthropic's content protocol to message."""
157
+ # Refer: https://docs.anthropic.com/claude/reference/messages-examples
158
+ return lf.AIMessage.from_chunks(
159
+ [x['text'] for x in content if x['type'] == 'text']
160
+ )
161
+
162
+ def _parse_response(self, response: requests.Response) -> lf.LMSamplingResult:
163
+ """Parses Anthropic's response."""
164
+ # NOTE(daiyip): Refer https://docs.anthropic.com/claude/reference/errors
165
+ output = response.json()
166
+ if response.status_code == 200:
167
+ message = self._message_from_content(output['content'])
168
+ input_tokens = output['usage']['input_tokens']
169
+ output_tokens = output['usage']['output_tokens']
170
+ return lf.LMSamplingResult(
171
+ [lf.LMSample(message)],
172
+ usage=lf.LMSamplingUsage(
173
+ prompt_tokens=input_tokens,
174
+ completion_tokens=output_tokens,
175
+ total_tokens=input_tokens + output_tokens,
176
+ ),
177
+ )
178
+ else:
179
+ if response.status_code == 429:
180
+ error_cls = RateLimitError
181
+ elif response.status_code == 529:
182
+ error_cls = OverloadedError
183
+ else:
184
+ error_cls = AnthropicError
185
+ error = output['error']
186
+ raise error_cls(f'{error["type"]}: {error["message"]}')
187
+
188
+ def _sample_single(self, prompt: lf.Message) -> lf.LMSamplingResult:
189
+ request = dict()
190
+ request.update(self._get_request_args(self.sampling_options))
191
+ request.update(
192
+ dict(
193
+ messages=[
194
+ dict(role='user', content=self._content_from_message(prompt))
195
+ ]
196
+ )
197
+ )
198
+ response = requests.post(
199
+ _ANTHROPIC_MESSAGE_API_ENDPOINT,
200
+ json=request,
201
+ headers={
202
+ 'x-api-key': self._api_key,
203
+ 'anthropic-version': _ANTHROPIC_API_VERSION,
204
+ 'content-type': 'application/json',
205
+ },
206
+ timeout=self.timeout,
207
+ )
208
+ return self._parse_response(response)
209
+
210
+
211
+ class Claude3(Anthropic):
212
+ """Base class for Claude 3 models. 200K input tokens and 4K output tokens."""
213
+
214
+
215
+ class Claude3Opus(Claude3):
216
+ """Anthropic's most powerful model."""
217
+
218
+ model = 'claude-3-opus-20240229'
219
+
220
+
221
+ class Claude3Sonnet(Claude3):
222
+ """A balance between between Opus and Haiku."""
223
+
224
+ model = 'claude-3-sonnet-20240229'
225
+
226
+
227
+ class Claude3Haiku(Claude3):
228
+ """Anthropic's most compact model."""
229
+
230
+ model = 'claude-3-haiku-20240307'
@@ -0,0 +1,167 @@
1
+ # Copyright 2023 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
+ """Tests for Anthropic models."""
15
+
16
+ import base64
17
+ import os
18
+ from typing import Any
19
+ import unittest
20
+ from unittest import mock
21
+ from langfun.core import modalities as lf_modalities
22
+ from langfun.core.llms import anthropic
23
+ import pyglove as pg
24
+ import requests
25
+
26
+
27
+ def mock_requests_post(url: str, json: dict[str, Any], **kwargs):
28
+ del url, kwargs
29
+
30
+ response = requests.Response()
31
+ response.status_code = 200
32
+ response._content = pg.to_json_str({
33
+ 'content': [{
34
+ 'type': 'text',
35
+ 'text': (
36
+ f'hello with temperature={json.get("temperature")}, '
37
+ f'top_k={json.get("top_k")}, '
38
+ f'top_p={json.get("top_p")}, '
39
+ f'max_tokens={json.get("max_tokens")}, '
40
+ f'stop={json.get("stop_sequences")}.'
41
+ ),
42
+ }],
43
+ 'usage': {
44
+ 'input_tokens': 2,
45
+ 'output_tokens': 1,
46
+ },
47
+ }).encode()
48
+ return response
49
+
50
+
51
+ image_content = (
52
+ b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x04'
53
+ b'\x03\x00\x00\x00\x12Y \xcb\x00\x00\x00\x18PLTE\x00\x00'
54
+ b'\x00fff_chaag_cg_ch^ci_ciC\xedb\x94\x00\x00\x00\x08tRNS'
55
+ b'\x00\n\x9f*\xd4\xff_\xf4\xe4\x8b\xf3a\x00\x00\x00>IDATx'
56
+ b'\x01c \x05\x08)"\xd8\xcc\xae!\x06pNz\x88k\x19\\Q\xa8"\x10'
57
+ b'\xc1\x14\x95\x01%\xc1\n\xa143Ta\xa8"D-\x84\x03QM\x98\xc3'
58
+ b'\x1a\x1a\x1a@5\x0e\x04\xa0q\x88\x05\x00\x07\xf8\x18\xf9'
59
+ b'\xdao\xd0|\x00\x00\x00\x00IEND\xaeB`\x82'
60
+ )
61
+
62
+
63
+ def mock_mm_requests_post(url: str, json: dict[str, Any], **kwargs):
64
+ del url, kwargs
65
+ v = json['messages'][0]['content'][0]
66
+ image = lf_modalities.Image.from_bytes(base64.b64decode(v['source']['data']))
67
+
68
+ response = requests.Response()
69
+ response.status_code = 200
70
+ response._content = pg.to_json_str({
71
+ 'content': [{
72
+ 'type': 'text',
73
+ 'text': f'{v["type"]}: {image.mime_type}',
74
+ }],
75
+ 'usage': {
76
+ 'input_tokens': 2,
77
+ 'output_tokens': 1,
78
+ },
79
+ }).encode()
80
+ return response
81
+
82
+
83
+ def mock_requests_post_error(status_code, error_type, error_message):
84
+ def _mock_requests(url: str, json: dict[str, Any], **kwargs):
85
+ del url, json, kwargs
86
+ response = requests.Response()
87
+ response.status_code = status_code
88
+ response._content = pg.to_json_str(
89
+ {
90
+ 'error': {
91
+ 'type': error_type,
92
+ 'message': error_message,
93
+ }
94
+ }
95
+ ).encode()
96
+ return response
97
+
98
+ return _mock_requests
99
+
100
+
101
+ class AuthropicTest(unittest.TestCase):
102
+
103
+ def test_basics(self):
104
+ self.assertEqual(
105
+ anthropic.Claude3Haiku().model_id, 'claude-3-haiku-20240307'
106
+ )
107
+ self.assertEqual(anthropic.Claude3Haiku().max_concurrency, 16)
108
+
109
+ def test_api_key(self):
110
+ lm = anthropic.Claude3Haiku()
111
+ with self.assertRaisesRegex(ValueError, 'Please specify `api_key`'):
112
+ lm('hi')
113
+
114
+ with mock.patch('requests.post') as mock_request:
115
+ mock_request.side_effect = mock_requests_post
116
+
117
+ lm = anthropic.Claude3Haiku(api_key='fake key')
118
+ self.assertRegex(lm('hi').text, 'hello.*')
119
+
120
+ os.environ['ANTHROPIC_API_KEY'] = 'abc'
121
+ lm = anthropic.Claude3Haiku()
122
+ self.assertRegex(lm('hi').text, 'hello.*')
123
+ del os.environ['ANTHROPIC_API_KEY']
124
+
125
+ def test_call(self):
126
+ with mock.patch('requests.post') as mock_request:
127
+ mock_request.side_effect = mock_requests_post
128
+ lm = anthropic.Claude3Haiku(api_key='fake_key')
129
+ response = lm('hello', temperature=0.0, top_k=0.1, top_p=0.2, stop=['\n'])
130
+ self.assertEqual(
131
+ response.text,
132
+ (
133
+ 'hello with temperature=0.0, top_k=0.1, top_p=0.2, '
134
+ "max_tokens=4096, stop=['\\n']."
135
+ ),
136
+ )
137
+ self.assertIsNotNone(response.usage)
138
+ self.assertIsNotNone(response.usage.prompt_tokens, 2)
139
+ self.assertIsNotNone(response.usage.completion_tokens, 1)
140
+ self.assertIsNotNone(response.usage.total_tokens, 3)
141
+
142
+ def test_mm_call(self):
143
+ with mock.patch('requests.post') as mock_mm_request:
144
+ mock_mm_request.side_effect = mock_mm_requests_post
145
+ lm = anthropic.Claude3Haiku(api_key='fake_key')
146
+ response = lm(lf_modalities.Image.from_bytes(image_content), lm=lm)
147
+ self.assertEqual(response.text, 'image: image/png')
148
+
149
+ def test_call_errors(self):
150
+ for status_code, error_type, error_message in [
151
+ (429, 'rate_limit', 'Rate limit exceeded.'),
152
+ (529, 'service_unavailable', 'Service unavailable.'),
153
+ (500, 'bad_request', 'Bad request.'),
154
+ ]:
155
+ with mock.patch('requests.post') as mock_mm_request:
156
+ mock_mm_request.side_effect = mock_requests_post_error(
157
+ status_code, error_type, error_message
158
+ )
159
+ lm = anthropic.Claude3Haiku(api_key='fake_key')
160
+ with self.assertRaisesRegex(
161
+ Exception, f'{error_type}: {error_message}'
162
+ ):
163
+ lm('hello', lm=lm, max_attempts=1)
164
+
165
+
166
+ if __name__ == '__main__':
167
+ unittest.main()
@@ -39,8 +39,8 @@ class EchoTest(unittest.TestCase):
39
39
  with contextlib.redirect_stdout(string_io):
40
40
  self.assertEqual(lm('hi'), 'hi')
41
41
  debug_info = string_io.getvalue()
42
- self.assertIn('[0] LM INFO:', debug_info)
43
- self.assertIn('[0] PROMPT SENT TO LM:', debug_info)
42
+ self.assertIn('[0] LM INFO', debug_info)
43
+ self.assertIn('[0] PROMPT SENT TO LM', debug_info)
44
44
  self.assertIn('[0] LM RESPONSE', debug_info)
45
45
 
46
46
  def test_score(self):
@@ -84,8 +84,8 @@ class StaticResponseTest(unittest.TestCase):
84
84
  self.assertEqual(lm('hi'), canned_response)
85
85
 
86
86
  debug_info = string_io.getvalue()
87
- self.assertIn('[0] LM INFO:', debug_info)
88
- self.assertIn('[0] PROMPT SENT TO LM:', debug_info)
87
+ self.assertIn('[0] LM INFO', debug_info)
88
+ self.assertIn('[0] PROMPT SENT TO LM', debug_info)
89
89
  self.assertIn('[0] LM RESPONSE', debug_info)
90
90
 
91
91
 
@@ -48,6 +48,8 @@ from langfun.core.structured.schema_generation import generate_class
48
48
  from langfun.core.structured.schema_generation import classgen_example
49
49
  from langfun.core.structured.schema_generation import default_classgen_examples
50
50
 
51
+ from langfun.core.structured.function_generation import function_gen
52
+
51
53
  from langfun.core.structured.mapping import Mapping
52
54
  from langfun.core.structured.mapping import MappingExample
53
55
 
@@ -0,0 +1,245 @@
1
+ # Copyright 2023 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
+ """LLM-based function generation."""
15
+
16
+ import functools
17
+ import inspect
18
+ import re
19
+ from typing import Any, Callable, Optional, Tuple
20
+
21
+ from langfun.core import language_model
22
+ from langfun.core import template
23
+ from langfun.core.coding import python
24
+ from langfun.core.structured import prompting
25
+ import pyglove as pg
26
+
27
+
28
+ def unittest_gen(signature, lm, num_retries=10):
29
+ """Generates unit tests for a python function signature."""
30
+
31
+ class UnitTest(pg.Object):
32
+ """A valid unit test for a python function."""
33
+
34
+ input: dict[str, Any]
35
+ expected_output: Any
36
+
37
+ class PythonFunctionSignature(pg.Object):
38
+ signature: str
39
+
40
+ unittest_examples = None
41
+ for _ in range(num_retries):
42
+ r = prompting.query(
43
+ PythonFunctionSignature(signature=signature),
44
+ list[UnitTest],
45
+ lm=lm,
46
+ default=None,
47
+ )
48
+ if isinstance(r, list) and r:
49
+ unittest_examples = []
50
+ for unit_test in r:
51
+ unittest_examples.append((unit_test.input, unit_test.expected_output))
52
+ break
53
+
54
+ return unittest_examples
55
+
56
+
57
+ def unittest_with_test_cases(f, unittests):
58
+ """Applies unit tests to a python function to be tested."""
59
+ if not unittests:
60
+ raise ValueError(f"No unit tests provided: {unittests}")
61
+
62
+ for unit_test in unittests:
63
+ inputs = unit_test[0]
64
+ if isinstance(inputs, dict):
65
+ actual = f(**inputs)
66
+ elif isinstance(inputs, tuple):
67
+ actual = f(*inputs)
68
+ else:
69
+ actual = f(inputs)
70
+
71
+ expected = unit_test[1]
72
+ assert (
73
+ actual == expected
74
+ ), f"Test FAILED: Inputs: {inputs}, Expected: {expected}, Actual: {actual}"
75
+
76
+
77
+ def _function_gen(
78
+ func: Callable[..., Any],
79
+ signature: str,
80
+ lm: language_model.LanguageModel,
81
+ num_retries: int = 10,
82
+ unittest: Optional[
83
+ Callable[[Callable[..., Any]], None] | list[Tuple[Any, Any]]
84
+ ] = None,
85
+ ):
86
+ """Generates a python function with LLM and verify its quality with unit testing."""
87
+
88
+ class PythonFunctionPrompt(template.Template):
89
+ r"""A template for a python function generation.
90
+
91
+ Please reply to the last PYTHON_FUNCTION_SIGNATURE with a self-sufficient,
92
+ error-free, and efficiently coded PYTHON_FUNCTION, crafted to the standards
93
+ of a world-class programmer.
94
+
95
+ PYTHON_FUNCTION_SIGNATURE:
96
+ ```python
97
+ def calculate_area_circle(radius: float) -> float:
98
+ \"\"\"Calculates the area of a circle given its radius.
99
+
100
+ Args:
101
+ radius: The radius of the circle.
102
+
103
+ Returns:
104
+ The area of the circle.
105
+ \"\"\"
106
+ ```
107
+
108
+ PYTHON_FUNCTION:
109
+ ```python
110
+ def calculate_area_circle(radius: float) -> float:
111
+ \"\"\"Calculates the area of a circle given its radius.
112
+
113
+ Args:
114
+ radius: The radius of the circle.
115
+
116
+ Returns:
117
+ The area of the circle.
118
+ \"\"\"
119
+ import math
120
+
121
+ area = math.pi * radius**2
122
+ return area
123
+ ```
124
+
125
+ PYTHON_FUNCTION_SIGNATURE:
126
+ ```python
127
+ {{signature}}
128
+ ```
129
+
130
+ PYTHON_FUNCTION:
131
+ """
132
+
133
+ unittest_examples = None
134
+ if unittest is None:
135
+ unittest_examples = unittest_gen(signature, lm=lm)
136
+ elif not callable(unittest):
137
+ unittest_examples = unittest
138
+
139
+ for _ in range(num_retries):
140
+ try:
141
+ source_code = prompting.query(
142
+ PythonFunctionPrompt(signature=signature), lm=lm
143
+ )
144
+ f = python.evaluate(source_code)
145
+
146
+ # Check whether the sigantures are the same.
147
+ if inspect.signature(f) != inspect.signature(func):
148
+ continue
149
+
150
+ if callable(unittest):
151
+ unittest(f)
152
+ else:
153
+ unittest_with_test_cases(f, unittest_examples)
154
+
155
+ return f, source_code
156
+ except Exception: # pylint: disable=broad-exception-caught
157
+ pass
158
+
159
+ return None, None
160
+
161
+
162
+ def _process_signature(signature):
163
+ # Remove the decorator.
164
+ pattern = r"^\@.*function_gen.*$"
165
+ signature = re.sub(pattern, "", signature, flags=re.MULTILINE)
166
+ # Remove the possible 'pass' in an empty function.
167
+ pattern = r"^\s*pass\s*$"
168
+ signature = re.sub(pattern, "", signature, flags=re.MULTILINE)
169
+ return signature.strip()
170
+
171
+
172
+ def function_gen(
173
+ lm: language_model.LanguageModel,
174
+ cache_filename: str | None = None,
175
+ num_retries: int = 10,
176
+ unittest: Optional[
177
+ Callable[[Callable[..., Any]], None] | list[Tuple[Any, Any]]
178
+ ] = None,
179
+ ):
180
+ """A decorator for automating function generation using a language model.
181
+
182
+ This decorator should be applied to functions that are not yet implemented. It
183
+ facilitates the implementation via the specified LLM, ensuring
184
+ quality through unit tests.
185
+
186
+ Args:
187
+ lm (lf.LanguageModel): The language model used for generating function
188
+ implementations.
189
+ cache_filename (str | None): Optional. The path of the file where
190
+ generated function implementations are loaded from or saved to.
191
+ num_retries (int): Maximum number of attempts the language model should
192
+ make to generate a suitable function implementation.
193
+ unittest: This optional parameter enables the definition of custom unit
194
+ tests. You can either provide a list of test cases as tuples of inputs
195
+ and outputs, or a function that throws an error if a test fails. If left
196
+ as None (the default setting), the LLM will automatically create the
197
+ unit test cases.
198
+
199
+ Returns:
200
+ The implemented function object.
201
+ """
202
+
203
+ def _decorate(func):
204
+ setattr(func, "__function__", None)
205
+ setattr(func, "__source_code__", None)
206
+
207
+ @functools.wraps(func)
208
+ def lm_generated_func(*args, **kwargs):
209
+ if func.__function__ is not None:
210
+ return func.__function__(*args, **kwargs)
211
+
212
+ signature = _process_signature(inspect.getsource(func))
213
+ cache = pg.Dict()
214
+ if cache_filename is not None:
215
+ try:
216
+ cache = pg.load(cache_filename)
217
+ except FileNotFoundError:
218
+ pg.logging.warning(
219
+ "Creating a new cache as cache file '%s' does not exist.",
220
+ cache_filename,
221
+ )
222
+
223
+ if signature in cache:
224
+ func.__source_code__ = cache[signature]
225
+ func.__function__ = python.evaluate(func.__source_code__)
226
+ return func.__function__(*args, **kwargs)
227
+
228
+ func.__function__, func.__source_code__ = _function_gen(
229
+ func, signature, lm, num_retries=num_retries, unittest=unittest
230
+ )
231
+ if func.__function__ is None:
232
+ raise ValueError(f"Function generation failed. Signature:\n{signature}")
233
+
234
+ if cache_filename is not None:
235
+ cache[signature] = func.__source_code__
236
+ cache.save(cache_filename)
237
+ return func.__function__(*args, **kwargs)
238
+
239
+ lm_generated_func.__name__ = func.__name__
240
+ lm_generated_func.__qualname__ = func.__qualname__
241
+ lm_generated_func.__module__ = func.__module__
242
+ lm_generated_func.source = lambda: func.__source_code__
243
+ return lm_generated_func
244
+
245
+ return _decorate
@@ -0,0 +1,329 @@
1
+ # Copyright 2024 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 inspect
15
+ import os
16
+ import tempfile
17
+ import unittest
18
+ from langfun.core.llms import fake
19
+ from langfun.core.structured import function_generation
20
+ import pyglove as pg
21
+
22
+
23
+ class FunctionGenerationTest(unittest.TestCase):
24
+
25
+ def test_generate_function(self):
26
+ function_gen_lm_response = inspect.cleandoc("""
27
+ def linear_search(items, target):
28
+ \"\"\"
29
+ Performs a linear search on a list to find a target value.
30
+
31
+ Args:
32
+ items (list): The list to search within.
33
+ target: The value to search for.
34
+
35
+ Returns:
36
+ int: The index of the target value if found, otherwise -1.
37
+ \"\"\"
38
+ for i, item in enumerate(items):
39
+ if item == target:
40
+ return i
41
+ return -1
42
+ """)
43
+ unittest_lm_response = inspect.cleandoc("""
44
+ ```python
45
+ [
46
+ UnitTest(
47
+ input={
48
+ 'items': [1, 2, 3, 4, 5],
49
+ 'target': 3
50
+ },
51
+ expected_output=2
52
+ ),
53
+ UnitTest(
54
+ input={
55
+ 'items': [1, 2, 3, 4, 5],
56
+ 'target': 6
57
+ },
58
+ expected_output=-1
59
+ )
60
+ ]
61
+ ```
62
+ """)
63
+
64
+ lm = fake.StaticSequence([unittest_lm_response, function_gen_lm_response])
65
+
66
+ @function_generation.function_gen(lm=lm)
67
+ def linear_search(items, target): # pylint: disable=unused-argument
68
+ """Performs a linear search on a list to find a target value.
69
+
70
+ Args:
71
+ items (list): The list to search within.
72
+ target: The value to search for.
73
+
74
+ Returns:
75
+ int: The index of the target value if found, otherwise -1.
76
+ """
77
+
78
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
79
+ self.assertEqual(linear_search.source(), function_gen_lm_response)
80
+
81
+ def test_custom_unittest_examples(self):
82
+ function_gen_lm_response = inspect.cleandoc("""
83
+ ```python
84
+ def linear_search(items, target):
85
+ \"\"\"
86
+ Performs a linear search on a list to find a target value.
87
+
88
+ Args:
89
+ items (list): The list to search within.
90
+ target: The value to search for.
91
+
92
+ Returns:
93
+ int: The index of the target value if found, otherwise -1.
94
+ \"\"\"
95
+ for i, item in enumerate(items):
96
+ if item == target:
97
+ return i
98
+ return -1
99
+ ```
100
+ """)
101
+
102
+ lm = fake.StaticSequence([function_gen_lm_response])
103
+
104
+ custom_unittest = [(([1, 2, 3, 4, 5], 3), 2)]
105
+
106
+ @function_generation.function_gen(lm=lm, unittest=custom_unittest)
107
+ def linear_search(items, target): # pylint: disable=unused-argument
108
+ """Performs a linear search on a list to find a target value.
109
+
110
+ Args:
111
+ items (list): The list to search within.
112
+ target: The value to search for.
113
+
114
+ Returns:
115
+ int: The index of the target value if found, otherwise -1.
116
+ """
117
+
118
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
119
+
120
+ def test_custom_unittest_fn(self):
121
+ function_gen_lm_response = inspect.cleandoc("""
122
+ ```python
123
+ def linear_search(items, target):
124
+ \"\"\"
125
+ Performs a linear search on a list to find a target value.
126
+
127
+ Args:
128
+ items (list): The list to search within.
129
+ target: The value to search for.
130
+
131
+ Returns:
132
+ int: The index of the target value if found, otherwise -1.
133
+ \"\"\"
134
+ for i, item in enumerate(items):
135
+ if item == target:
136
+ return i
137
+ return -1
138
+ ```
139
+ """)
140
+
141
+ lm = fake.StaticSequence([function_gen_lm_response])
142
+
143
+ def _unittest_fn(func):
144
+ assert func([1, 2, 3, 4, 5], 3) == 2
145
+ assert func([1, 2, 3, 4, 5], 6) == -1
146
+
147
+ custom_unittest = _unittest_fn
148
+
149
+ @function_generation.function_gen(lm=lm, unittest=custom_unittest)
150
+ def linear_search(items, target): # pylint: disable=unused-argument
151
+ """Performs a linear search on a list to find a target value.
152
+
153
+ Args:
154
+ items (list): The list to search within.
155
+ target: The value to search for.
156
+
157
+ Returns:
158
+ int: The index of the target value if found, otherwise -1.
159
+ """
160
+
161
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
162
+
163
+ def test_load_function_from_cache_file(self):
164
+ lm = fake.StaticSequence([])
165
+
166
+ def _unittest_fn(func):
167
+ assert func([1, 2, 3, 4, 5], 3) == 2
168
+ assert func([1, 2, 3, 4, 5], 6) == -1
169
+
170
+ cache_file_dir = tempfile.gettempdir()
171
+ cache_file = os.path.join(cache_file_dir, 'cache_file.json')
172
+
173
+ cache_key = """@function_generation.function_gen(
174
+ lm=lm,
175
+ unittest=_unittest_fn,
176
+ cache_filename=cache_file,
177
+ )
178
+ def linear_search(items, target): # pylint: disable=unused-argument
179
+ \"\"\"Performs a linear search on a list to find a target value.
180
+
181
+ Args:
182
+ items (list): The list to search within.
183
+ target: The value to search for.
184
+
185
+ Returns:
186
+ int: The index of the target value if found, otherwise -1.
187
+ \"\"\""""
188
+ cache_value = """
189
+ ```python
190
+ def linear_search(items, target):
191
+ \"\"\"
192
+ Performs a linear search on a list to find a target value.
193
+
194
+ Args:
195
+ items (list): The list to search within.
196
+ target: The value to search for.
197
+
198
+ Returns:
199
+ int: The index of the target value if found, otherwise -1.
200
+ \"\"\"
201
+ for i, item in enumerate(items):
202
+ if item == target:
203
+ return i
204
+ return -1
205
+ ```
206
+ """
207
+ cache = pg.Dict()
208
+ cache[cache_key] = cache_value
209
+ cache.save(cache_file)
210
+
211
+ @function_generation.function_gen(
212
+ lm=lm,
213
+ unittest=_unittest_fn,
214
+ cache_filename=cache_file,
215
+ )
216
+ def linear_search(items, target): # pylint: disable=unused-argument
217
+ """Performs a linear search on a list to find a target value.
218
+
219
+ Args:
220
+ items (list): The list to search within.
221
+ target: The value to search for.
222
+
223
+ Returns:
224
+ int: The index of the target value if found, otherwise -1.
225
+ """
226
+
227
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
228
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'd'), -1)
229
+
230
+ def test_empty_cache_file(self):
231
+ function_gen_lm_response = inspect.cleandoc("""
232
+ ```python
233
+ def linear_search(items, target):
234
+ \"\"\"
235
+ Performs a linear search on a list to find a target value.
236
+
237
+ Args:
238
+ items (list): The list to search within.
239
+ target: The value to search for.
240
+
241
+ Returns:
242
+ int: The index of the target value if found, otherwise -1.
243
+ \"\"\"
244
+ for i, item in enumerate(items):
245
+ if item == target:
246
+ return i
247
+ return -1
248
+ ```
249
+ """)
250
+
251
+ lm = fake.StaticSequence([function_gen_lm_response])
252
+
253
+ def _unittest_fn(func):
254
+ assert func([1, 2, 3, 4, 5], 3) == 2
255
+ assert func([1, 2, 3, 4, 5], 6) == -1
256
+
257
+ cache_file_dir = tempfile.gettempdir()
258
+ cache_file = os.path.join(cache_file_dir, 'cache_file.json')
259
+
260
+ @function_generation.function_gen(
261
+ lm=lm, unittest=_unittest_fn, cache_filename=cache_file
262
+ )
263
+ def linear_search(items, target): # pylint: disable=unused-argument
264
+ """Performs a linear search on a list to find a target value.
265
+
266
+ Args:
267
+ items (list): The list to search within.
268
+ target: The value to search for.
269
+
270
+ Returns:
271
+ int: The index of the target value if found, otherwise -1.
272
+ """
273
+
274
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
275
+
276
+ def test_siganture_check(self):
277
+ incorrect_signature_lm_response = inspect.cleandoc("""
278
+ ```python
279
+ def dummy():
280
+ pass
281
+ ```
282
+ """)
283
+ function_gen_lm_response = inspect.cleandoc("""
284
+ ```python
285
+ def linear_search(items, target):
286
+ \"\"\"
287
+ Performs a linear search on a list to find a target value.
288
+
289
+ Args:
290
+ items (list): The list to search within.
291
+ target: The value to search for.
292
+
293
+ Returns:
294
+ int: The index of the target value if found, otherwise -1.
295
+ \"\"\"
296
+ for i, item in enumerate(items):
297
+ if item == target:
298
+ return i
299
+ return -1
300
+ ```
301
+ """)
302
+
303
+ lm = fake.StaticSequence(
304
+ [incorrect_signature_lm_response, function_gen_lm_response]
305
+ )
306
+
307
+ def _unittest_fn(func):
308
+ assert func([1, 2, 3, 4, 5], 3) == 2
309
+ assert func([1, 2, 3, 4, 5], 6) == -1
310
+
311
+ custom_unittest = _unittest_fn
312
+
313
+ @function_generation.function_gen(lm=lm, unittest=custom_unittest)
314
+ def linear_search(items, target): # pylint: disable=unused-argument
315
+ """Performs a linear search on a list to find a target value.
316
+
317
+ Args:
318
+ items (list): The list to search within.
319
+ target: The value to search for.
320
+
321
+ Returns:
322
+ int: The index of the target value if found, otherwise -1.
323
+ """
324
+
325
+ self.assertEqual(linear_search(['a', 'b', 'c'], 'c'), 2)
326
+
327
+
328
+ if __name__ == '__main__':
329
+ unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langfun
3
- Version: 0.0.2.dev20240415
3
+ Version: 0.0.2.dev20240419
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -1,4 +1,4 @@
1
- langfun/__init__.py,sha256=PqX3u18BC0szYIMu00j-RKxvwkNPwXtAFZ-96oxrQ0M,1841
1
+ langfun/__init__.py,sha256=3iCC7F8XoRZ7Gvus11NT50e4KDOQJxIPn9a7TlLzuVI,1880
2
2
  langfun/core/__init__.py,sha256=6QEuXOZ9BXxm6TjpaMXuLwUBTYO3pkFDqn9QVBXyyPQ,4248
3
3
  langfun/core/component.py,sha256=VRPfDB_2jEnxcB3-HoiVjG4ID-SMenNPIsytb0uXMPg,9674
4
4
  langfun/core/component_test.py,sha256=VAPd6V_-odAe8rBvesW3ogYDd6OSqRq4FaPhfgOM4Zg,7949
@@ -8,7 +8,7 @@ langfun/core/console.py,sha256=bk5rNPNm9rMGW5YT2HixxU04p2umnoabn5SDz6Dqe88,2317
8
8
  langfun/core/console_test.py,sha256=5SYJdxpJGLgdSSQqqMPoA1X6jpsLD8rgcyk-EgI65oE,1077
9
9
  langfun/core/langfunc.py,sha256=WXdTc3QsmGD_n80KD9dFRr5MHpGZ9E_y_Rhtk4t9-3w,11852
10
10
  langfun/core/langfunc_test.py,sha256=sQaKuZpGGmG80GRifhbxkj7nfzQLJKj4Vuw5y1s1K3U,8378
11
- langfun/core/language_model.py,sha256=Tzswu0hyXOQOZ3fZ_Mz_Cc0ei7tVj8rTay9jJEgM6mI,17510
11
+ langfun/core/language_model.py,sha256=1_GO6oEm0wXnE7aRRLOdT-A4j_6YvRanS5oMgfobcIs,18331
12
12
  langfun/core/language_model_test.py,sha256=KvXXOr64TsSs3WkEALCLLZSlz09i7hBiHDOZ_8Eq8_o,13047
13
13
  langfun/core/memory.py,sha256=f-asN1F7Vehgdn_fK84v73GrEUOxRtaW934keutTKjk,2416
14
14
  langfun/core/message.py,sha256=QhvV9t5qaryPcruyxxcXi3gm9QDInkSldwTtK6sVJ3c,15734
@@ -46,9 +46,11 @@ langfun/core/eval/matching.py,sha256=aqNlYrlav7YmsB7rUlsdfoi1RLA5CYqn2RGPxRlPc78
46
46
  langfun/core/eval/matching_test.py,sha256=FFHYD7IDuKe5RMjkx74ksukiwUhO5a_SS340JaIPMws,4898
47
47
  langfun/core/eval/scoring.py,sha256=aKeanBJf1yO3Q9JEtgPWoiZk_3M_GiqwXVXX7x_g22w,6172
48
48
  langfun/core/eval/scoring_test.py,sha256=YH1cIxBWtfdKcAV9Fh10vLkV5J-gxk8b6nxW4Z2u5pk,4024
49
- langfun/core/llms/__init__.py,sha256=gROJ8AjMq_ebXFcEfsyzYGCS6NsGfzf9d43nLu_TIdw,2504
49
+ langfun/core/llms/__init__.py,sha256=qIvFxup-A7PYL_2Fn7CvTzVNQ05413RKZwZpO5jrTiQ,2715
50
+ langfun/core/llms/anthropic.py,sha256=Z3nER4Zc5dbREZa40RwPHY-JifTSQ_ZPSup31FFY8Lg,7305
51
+ langfun/core/llms/anthropic_test.py,sha256=OuLDxeiPRdqsfKILS0R6jJLTRs3-1KCIotPPr7IbIDU,5502
50
52
  langfun/core/llms/fake.py,sha256=b-Xk5IPTbUt-elsyzd_i3n1tqzc_kgETXrEvgJruSMk,2824
51
- langfun/core/llms/fake_test.py,sha256=AThvNyhZbkpsn-YO798uLgqB6TSw5XP2SKpKvcXEytw,4188
53
+ langfun/core/llms/fake_test.py,sha256=ZlDQgL41EX3eYTfBQNp2nB2LciqCmtoHgCsGvW4XhwI,4184
52
54
  langfun/core/llms/google_genai.py,sha256=n8zyJwh9UCTgb6-8LyvmjVNFGZQ4-zfzZ0ulkhHAnR8,8624
53
55
  langfun/core/llms/google_genai_test.py,sha256=_UcGTfl16-aDUlEWFC2W2F8y9jPUs53RBYA6MOCpGXw,7525
54
56
  langfun/core/llms/llama_cpp.py,sha256=Y_KkMUf3Xfac49koMUtUslKl3h-HWp3-ntq7Jaa3bdo,2385
@@ -69,11 +71,13 @@ langfun/core/modalities/mime.py,sha256=wVfaYflhGz1W4v3m972rAplW3OGOFtjFpHDYIaUD5
69
71
  langfun/core/modalities/mime_test.py,sha256=cVHxRvJ1QXC1SVhBmWkJdWGpL9Xl0UNfTQq6j0OGGL4,1881
70
72
  langfun/core/modalities/video.py,sha256=25M4XsNG5XEWRy57LYT_a6_aMURMPAgC41B3weEXFsY,1747
71
73
  langfun/core/modalities/video_test.py,sha256=jYuI2m8S8zDCAVBPEUbbpP205dXAht90A2_PHWo4-r8,2039
72
- langfun/core/structured/__init__.py,sha256=SpObW-HKpyKvkLlX8FV5ixz7CRm098j2aGfOguM3AUI,3462
74
+ langfun/core/structured/__init__.py,sha256=YJfifg66fFF5I6a-riI7T4PV4jYRmv89Ti617zsFNzo,3532
73
75
  langfun/core/structured/completion.py,sha256=skBxt6V_fv2TBUKnzFgnPMbVY8HSYn8sY04MLok2yvs,7299
74
76
  langfun/core/structured/completion_test.py,sha256=0FJreSmz0Umsj47dIlOyCjBXUa7janIplXhg1CbLT4U,19301
75
77
  langfun/core/structured/description.py,sha256=SXW4MJvshFjbR-0gw6rE21o6WXq12UlRXawvDBXMZFA,5211
76
78
  langfun/core/structured/description_test.py,sha256=UtZGjSFUaQ6130t1E5tcL7ODu0xIefkapb53TbnqsK8,7362
79
+ langfun/core/structured/function_generation.py,sha256=pFgS3vcRAWiuFBol2x5Eeip3XqoudONsOpeJpWyjT3s,7479
80
+ langfun/core/structured/function_generation_test.py,sha256=ZJI-aaGgWWszn92u7h5IZ9Pl70N2DgAGGJrIxPzsvwg,10065
77
81
  langfun/core/structured/mapping.py,sha256=7JInwZLmQdu7asHhC0vFLJNOCBnY-hrD6v5RQgf-xKk,11020
78
82
  langfun/core/structured/mapping_test.py,sha256=07DDCGbwytQHSMm7fCi5-Ly-JNgdV4ubHZq0wthX4A4,3338
79
83
  langfun/core/structured/parsing.py,sha256=keoVqEfzAbdULh6GawWFsTQzU91MzJXYFZjXGXLaD8g,11492
@@ -95,8 +99,8 @@ langfun/core/templates/demonstration.py,sha256=vCrgYubdZM5Umqcgp8NUVGXgr4P_c-fik
95
99
  langfun/core/templates/demonstration_test.py,sha256=SafcDQ0WgI7pw05EmPI2S4v1t3ABKzup8jReCljHeK4,2162
96
100
  langfun/core/templates/selfplay.py,sha256=yhgrJbiYwq47TgzThmHrDQTF4nDrTI09CWGhuQPNv-s,2273
97
101
  langfun/core/templates/selfplay_test.py,sha256=DYVrkk7uNKCqJGEHH31HssU2BPuMItU1vJLzfcXIlYg,2156
98
- langfun-0.0.2.dev20240415.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
99
- langfun-0.0.2.dev20240415.dist-info/METADATA,sha256=V_zKk0hFMrBR6jMyr0C0v71Y4RJ9GL9b0uAkBerHIIw,3405
100
- langfun-0.0.2.dev20240415.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
101
- langfun-0.0.2.dev20240415.dist-info/top_level.txt,sha256=RhlEkHxs1qtzmmtWSwYoLVJAc1YrbPtxQ52uh8Z9VvY,8
102
- langfun-0.0.2.dev20240415.dist-info/RECORD,,
102
+ langfun-0.0.2.dev20240419.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
103
+ langfun-0.0.2.dev20240419.dist-info/METADATA,sha256=sDej42bUZ0DRsCLxy8bdXp-qTDeIGKoY5ZndIG7dKMM,3405
104
+ langfun-0.0.2.dev20240419.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
105
+ langfun-0.0.2.dev20240419.dist-info/top_level.txt,sha256=RhlEkHxs1qtzmmtWSwYoLVJAc1YrbPtxQ52uh8Z9VvY,8
106
+ langfun-0.0.2.dev20240419.dist-info/RECORD,,