langroid 0.16.7__py3-none-any.whl → 0.17.1__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 (91) hide show
  1. langroid/agent/base.py +45 -21
  2. langroid/agent/chat_agent.py +22 -14
  3. langroid/agent/chat_document.py +22 -13
  4. langroid/agent/tool_message.py +11 -11
  5. langroid/agent/tools/file_tools.py +234 -0
  6. langroid/agent/xml_tool_message.py +179 -45
  7. langroid/utils/constants.py +2 -0
  8. langroid/utils/git_utils.py +251 -0
  9. langroid/utils/system.py +78 -0
  10. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/METADATA +6 -3
  11. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/RECORD +14 -89
  12. pyproject.toml +3 -2
  13. langroid/agent/md_tool_message_grammar.py +0 -455
  14. langroid/agent/tools/code_file_tool_parse.py +0 -150
  15. langroid/agent/tools/code_file_tool_pyparsing.py +0 -194
  16. langroid/agent/tools/code_file_tool_pyparsing2.py +0 -199
  17. langroid/agent/tools/extract_tool.py +0 -96
  18. langroid/agent/tools/formatted_model_custom.py +0 -150
  19. langroid/agent/tools/formatted_model_custom2.py +0 -168
  20. langroid/agent/tools/formatted_model_custom3.py +0 -279
  21. langroid/agent/tools/formatted_model_custom4.py +0 -395
  22. langroid/agent/tools/formatted_model_jinja.py +0 -133
  23. langroid/agent/tools/formatted_model_jinja.py-e +0 -122
  24. langroid/agent/tools/formatted_model_jinja2.py +0 -145
  25. langroid/agent/tools/formatted_model_jinja2.py-e +0 -135
  26. langroid/agent/tools/formatted_model_lark.py +0 -0
  27. langroid/agent/tools/formatted_model_lark2.py +0 -168
  28. langroid/agent/tools/formatted_model_parse.py +0 -105
  29. langroid/agent/tools/formatted_model_parse.py-e +0 -98
  30. langroid/agent/tools/formatted_model_parse2.py +0 -113
  31. langroid/agent/tools/formatted_model_parse2.py-e +0 -109
  32. langroid/agent/tools/formatted_model_parse3.py +0 -114
  33. langroid/agent/tools/formatted_model_parse3.py-e +0 -110
  34. langroid/agent/tools/formatted_model_parsimon.py +0 -194
  35. langroid/agent/tools/formatted_model_parsimon.py-e +0 -186
  36. langroid/agent/tools/formatted_model_pyparsing.py +0 -169
  37. langroid/agent/tools/formatted_model_pyparsing.py-e +0 -149
  38. langroid/agent/tools/formatted_model_pyparsing2.py +0 -159
  39. langroid/agent/tools/formatted_model_pyparsing2.py-e +0 -143
  40. langroid/agent/tools/formatted_model_pyparsing3.py +0 -133
  41. langroid/agent/tools/formatted_model_pyparsing3.py-e +0 -121
  42. langroid/agent/tools/formatted_model_pyparsing4.py +0 -213
  43. langroid/agent/tools/formatted_model_pyparsing4.py-e +0 -176
  44. langroid/agent/tools/formatted_model_pyparsing5.py +0 -173
  45. langroid/agent/tools/formatted_model_pyparsing5.py-e +0 -142
  46. langroid/agent/tools/formatted_model_regex.py +0 -246
  47. langroid/agent/tools/formatted_model_regex.py-e +0 -248
  48. langroid/agent/tools/formatted_model_regex2.py +0 -250
  49. langroid/agent/tools/formatted_model_regex2.py-e +0 -253
  50. langroid/agent/tools/formatted_model_tatsu.py +0 -172
  51. langroid/agent/tools/formatted_model_tatsu.py-e +0 -160
  52. langroid/agent/tools/formatted_model_template.py +0 -217
  53. langroid/agent/tools/formatted_model_template.py-e +0 -200
  54. langroid/agent/tools/formatted_model_xml.py +0 -178
  55. langroid/agent/tools/formatted_model_xml2.py +0 -178
  56. langroid/agent/tools/formatted_model_xml3.py +0 -132
  57. langroid/agent/tools/formatted_model_xml4.py +0 -130
  58. langroid/agent/tools/formatted_model_xml5.py +0 -130
  59. langroid/agent/tools/formatted_model_xml6.py +0 -113
  60. langroid/agent/tools/formatted_model_xml7.py +0 -117
  61. langroid/agent/tools/formatted_model_xml8.py +0 -164
  62. langroid/agent/tools/generator_tool.py +0 -20
  63. langroid/agent/tools/generic_tool.py +0 -165
  64. langroid/agent/tools/generic_tool_tatsu.py +0 -275
  65. langroid/agent/tools/grammar_based_model.py +0 -132
  66. langroid/agent/tools/grammar_based_model.py-e +0 -128
  67. langroid/agent/tools/grammar_based_model_lark.py +0 -156
  68. langroid/agent/tools/grammar_based_model_lark.py-e +0 -153
  69. langroid/agent/tools/grammar_based_model_parse.py +0 -86
  70. langroid/agent/tools/grammar_based_model_parse.py-e +0 -80
  71. langroid/agent/tools/grammar_based_model_parsimonious.py +0 -129
  72. langroid/agent/tools/grammar_based_model_parsimonious.py-e +0 -120
  73. langroid/agent/tools/grammar_based_model_pyparsing.py +0 -105
  74. langroid/agent/tools/grammar_based_model_pyparsing.py-e +0 -103
  75. langroid/agent/tools/grammar_based_model_regex.py +0 -139
  76. langroid/agent/tools/grammar_based_model_regex.py-e +0 -130
  77. langroid/agent/tools/grammar_based_model_regex2.py +0 -124
  78. langroid/agent/tools/grammar_based_model_regex2.py-e +0 -116
  79. langroid/agent/tools/grammar_based_model_tatsu.py +0 -80
  80. langroid/agent/tools/grammar_based_model_tatsu.py-e +0 -77
  81. langroid/agent/tools/lark_earley_example.py +0 -135
  82. langroid/agent/tools/lark_earley_example.py-e +0 -117
  83. langroid/agent/tools/lark_example.py +0 -72
  84. langroid/agent/tools/note_tool.py +0 -0
  85. langroid/agent/tools/parse_example.py +0 -76
  86. langroid/agent/tools/parse_example2.py +0 -87
  87. langroid/agent/tools/parse_example3.py +0 -42
  88. langroid/agent/tools/parse_test.py +0 -791
  89. langroid/agent/tools/run_python_code.py +0 -60
  90. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/LICENSE +0 -0
  91. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/WHEEL +0 -0
@@ -1,395 +0,0 @@
1
- from abc import ABC
2
- from typing import Dict, List
3
-
4
- from langroid.pydantic_v1 import BaseModel, Field
5
-
6
-
7
- class FormatMetadata(BaseModel):
8
- prefix: str = ""
9
- suffix: str = ""
10
- multiline: bool = False
11
- order: int = 0 # New field for ordering
12
-
13
-
14
- class FormattingModel(BaseModel, ABC):
15
- @classmethod
16
- def format_spec(cls) -> str:
17
- fields = sorted(
18
- cls.__fields__.items(),
19
- key=lambda x: x[1]
20
- .field_info.extra.get("format_metadata", FormatMetadata())
21
- .order,
22
- )
23
- lines = []
24
- for name, field in fields:
25
- metadata: FormatMetadata = field.field_info.extra.get(
26
- "format_metadata", FormatMetadata()
27
- )
28
- if metadata.multiline:
29
- lines.append(f"{metadata.prefix}{{{name}}}{metadata.suffix}")
30
- else:
31
- lines.append(f"{metadata.prefix}{{{name}}}{metadata.suffix}")
32
- return "\n".join(lines)
33
-
34
- @classmethod
35
- def parse_spec(cls) -> Dict[str, FormatMetadata]:
36
- fields = sorted(
37
- cls.__fields__.items(),
38
- key=lambda x: x[1]
39
- .field_info.extra.get("format_metadata", FormatMetadata())
40
- .order,
41
- )
42
- return {
43
- name: field.field_info.extra.get("format_metadata", FormatMetadata())
44
- for name, field in fields
45
- }
46
-
47
- @classmethod
48
- def start_token(cls) -> str:
49
- return getattr(cls.Config, "start_token", "<format>")
50
-
51
- @classmethod
52
- def end_token(cls) -> str:
53
- return getattr(cls.Config, "end_token", "</format>")
54
-
55
- @classmethod
56
- def format(cls, instance: "FormattingModel") -> str:
57
- spec = cls.format_spec()
58
- formatted = spec.format(**instance.dict())
59
- return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
60
-
61
- @classmethod
62
- def parse(cls, formatted_string: str) -> "FormattingModel":
63
- lines = formatted_string.strip().split("\n")
64
- if lines[0] != cls.start_token():
65
- raise ValueError("Invalid start token")
66
-
67
- content = "\n".join(lines[1:])
68
- if content.endswith(cls.end_token()):
69
- content = content[: -len(cls.end_token())]
70
-
71
- parsed_data = {}
72
- parse_spec = cls.parse_spec()
73
- field_names = list(parse_spec.keys())
74
-
75
- for i, (field, metadata) in enumerate(parse_spec.items()):
76
- is_last_field = i == len(field_names) - 1
77
- if metadata.multiline:
78
- start = f"{metadata.prefix}"
79
- end = f"{metadata.suffix}"
80
- start_index = content.find(start)
81
- if start_index == -1:
82
- raise ValueError(f"Could not find start of {field}")
83
- start_index += len(start)
84
- if is_last_field:
85
- end_index = content.rfind(
86
- end
87
- ) # Use rfind to find the last occurrence
88
- if end_index == -1:
89
- end_index = len(content)
90
- value = content[start_index:end_index] # Don't strip here
91
- else:
92
- end_index = content.find(end, start_index)
93
- if end_index == -1:
94
- raise ValueError(f"Could not find end of {field}")
95
- value = content[start_index:end_index] # Don't strip here
96
- else:
97
- line_start = f"{metadata.prefix}"
98
- line_end = metadata.suffix or "\n"
99
- start_index = content.find(line_start)
100
- if start_index == -1:
101
- raise ValueError(f"Could not find {field}")
102
- start_index += len(line_start)
103
- if is_last_field:
104
- end_index = content.rfind(
105
- line_end
106
- ) # Use rfind to find the last occurrence
107
- if end_index == -1:
108
- end_index = len(content)
109
- value = content[
110
- start_index:end_index
111
- ].strip() # Strip for non-multiline fields
112
- else:
113
- end_index = content.find(line_end, start_index)
114
- if end_index == -1:
115
- raise ValueError(f"Could not find end of {field}")
116
- value = content[
117
- start_index:end_index
118
- ].strip() # Strip for non-multiline fields
119
-
120
- parsed_data[field] = value
121
- content = content[
122
- end_index + len(end if metadata.multiline else line_end) :
123
- ]
124
-
125
- return cls(**parsed_data)
126
-
127
- @staticmethod
128
- def find_all_candidates(string: str, begin_token: str, end_token: str) -> List[str]:
129
- candidates = []
130
- start = 0
131
- while True:
132
- start_index = string.find(begin_token, start)
133
- if start_index == -1:
134
- break
135
-
136
- end_index = string.find(end_token, start_index + len(begin_token))
137
- if end_index == -1:
138
- # If no end token is found, assume it extends to the end of the string
139
- candidates.append(string[start_index:])
140
- break
141
-
142
- # Check if there's a nested begin token before the end token
143
- next_start = string.find(
144
- begin_token, start_index + len(begin_token), end_index
145
- )
146
- if next_start != -1:
147
- # If there's a nested begin token, continue searching from there
148
- start = next_start
149
- continue
150
-
151
- candidates.append(string[start_index : end_index + len(end_token)])
152
- start = end_index + len(end_token)
153
-
154
- return candidates
155
-
156
-
157
- class CodeFileModel(FormattingModel):
158
- file_path: str = Field(
159
- ..., format_metadata=FormatMetadata(prefix="file_path: ", order=1)
160
- )
161
- language: str = Field(
162
- ..., format_metadata=FormatMetadata(prefix="language: ", order=2)
163
- )
164
- code: str = Field(
165
- ...,
166
- format_metadata=FormatMetadata(
167
- prefix="```\n", suffix="\n```", multiline=True, order=3
168
- ),
169
- )
170
-
171
- class Config:
172
- start_token = "<code_file>"
173
- end_token = "</code_file>"
174
-
175
-
176
- if __name__ == "__main__":
177
- # Test formatting
178
- code_file = CodeFileModel(
179
- file_path="src/main.py",
180
- language="python",
181
- code="def main():\n print('Hello, World!')",
182
- )
183
- formatted = CodeFileModel.format(code_file)
184
- expected_format = """<code_file>
185
- file_path: src/main.py
186
- language: python
187
- ```
188
- def main():
189
- print('Hello, World!')
190
- ```
191
- </code_file>"""
192
- assert (
193
- formatted == expected_format
194
- ), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
195
- print("Formatting test passed.")
196
-
197
- # Test parsing
198
- parsed = CodeFileModel.parse(formatted)
199
- assert (
200
- parsed == code_file
201
- ), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
202
- print("Parsing test passed.")
203
-
204
- # Test round-trip
205
- round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
206
- assert (
207
- round_trip == code_file
208
- ), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
209
- print("Round-trip test passed.")
210
-
211
- # Test with different values
212
- code_file2 = CodeFileModel(
213
- file_path="src/app.js",
214
- language="javascript",
215
- code="function greet() {\n console.log('Hello, World!');\n}",
216
- )
217
- formatted2 = CodeFileModel.format(code_file2)
218
- parsed2 = CodeFileModel.parse(formatted2)
219
- assert (
220
- parsed2 == code_file2
221
- ), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
222
- print("Different values test passed.")
223
-
224
- # Test tolerant parsing
225
- tolerant_input = """<code_file>
226
- file_path: src/main.py
227
- language: python
228
- ```
229
- def main():
230
- print('Hello, World!')
231
- ```
232
- </code_file>"""
233
- parsed_tolerant = CodeFileModel.parse(tolerant_input)
234
- expected_tolerant = CodeFileModel(
235
- file_path="src/main.py",
236
- language="python",
237
- code="def main():\n print('Hello, World!')",
238
- )
239
- assert (
240
- parsed_tolerant == expected_tolerant
241
- ), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
242
- print("Tolerant parsing test passed.")
243
-
244
- # Test tolerant parsing without end token and last field suffix
245
- tolerant_input_no_end = """<code_file>
246
- file_path: src/main.py
247
- language: python
248
- ```
249
- def main():
250
- print('Hello, World!')"""
251
- parsed_tolerant_no_end = CodeFileModel.parse(tolerant_input_no_end)
252
- expected_tolerant_no_end = CodeFileModel(
253
- file_path="src/main.py",
254
- language="python",
255
- code="def main():\n print('Hello, World!')",
256
- )
257
- assert (
258
- parsed_tolerant_no_end == expected_tolerant_no_end
259
- ), f"Tolerant parsing without end token failed. Expected:\n{expected_tolerant_no_end}\nGot:\n{parsed_tolerant_no_end}"
260
- print("Tolerant parsing without end token test passed.")
261
-
262
- # Test find_all_candidates method
263
- test_string = """
264
- Some text before
265
- <code_file>
266
- file_path: src/main.py
267
- language: python
268
- ```
269
- def main():
270
- print('Hello, World!')
271
- ```
272
- </code_file>
273
- Some text in between
274
- <code_file>
275
- file_path: src/helper.py
276
- language: python
277
- ```
278
- def helper():
279
- return 'Helper function'
280
- ```
281
- </code_file>
282
- <code_file>
283
- file_path: src/incomplete.py
284
- language: python
285
- ```
286
- def incomplete():
287
- print('No end token')
288
- Some text after
289
- """
290
-
291
- candidates = FormattingModel.find_all_candidates(
292
- test_string, "<code_file>", "</code_file>"
293
- )
294
- assert len(candidates) == 3, f"Expected 3 candidates, got {len(candidates)}"
295
- assert candidates[0].startswith("<code_file>") and candidates[0].endswith(
296
- "</code_file>"
297
- ), "First candidate is incorrect"
298
- assert candidates[1].startswith("<code_file>") and candidates[1].endswith(
299
- "</code_file>"
300
- ), "Second candidate is incorrect"
301
- assert candidates[2].startswith("<code_file>") and not candidates[2].endswith(
302
- "</code_file>"
303
- ), "Third candidate is incorrect"
304
- print("find_all_candidates test passed.")
305
-
306
- print("All tests passed successfully!")
307
-
308
- # Test field order
309
- code_file = CodeFileModel(
310
- file_path="src/main.py",
311
- language="python",
312
- code="def main():\n print('Hello, World!')",
313
- )
314
- formatted = CodeFileModel.format(code_file)
315
- expected_format = """<code_file>
316
- file_path: src/main.py
317
- language: python
318
- ```
319
- def main():
320
- print('Hello, World!')
321
- ```
322
- </code_file>"""
323
- assert (
324
- formatted == expected_format
325
- ), f"Formatting with field order failed. Expected:\n{expected_format}\nGot:\n{formatted}"
326
- print("Field order test passed.")
327
-
328
- # Test parsing with different field order
329
- class DifferentOrderCodeFileModel(FormattingModel):
330
- language: str = Field(
331
- ..., format_metadata=FormatMetadata(prefix="language: ", order=1)
332
- )
333
- file_path: str = Field(
334
- ..., format_metadata=FormatMetadata(prefix="file_path: ", order=2)
335
- )
336
- code: str = Field(
337
- ...,
338
- format_metadata=FormatMetadata(
339
- prefix="```\n", suffix="\n```", multiline=True, order=3
340
- ),
341
- )
342
-
343
- class Config:
344
- start_token = "<code_file>"
345
- end_token = "</code_file>"
346
-
347
- different_order_input = """<code_file>
348
- language: python
349
- file_path: src/main.py
350
- ```
351
- def main():
352
- print('Hello, World!')
353
- ```
354
- </code_file>"""
355
- parsed_different_order = DifferentOrderCodeFileModel.parse(different_order_input)
356
- expected_different_order = DifferentOrderCodeFileModel(
357
- language="python",
358
- file_path="src/main.py",
359
- code="def main():\n print('Hello, World!')",
360
- )
361
- assert (
362
- parsed_different_order == expected_different_order
363
- ), f"Parsing with different field order failed. Expected:\n{expected_different_order}\nGot:\n{parsed_different_order}"
364
- print("Different field order parsing test passed.")
365
-
366
- # Test with code containing special characters
367
- complex_code = CodeFileModel(
368
- file_path="src/complex.py",
369
- language="python",
370
- code='''
371
- def complex_function():
372
- # This is a comment with "quotes" and 'apostrophes'
373
- special_chars = "!@#$%^&*()_+{}[]|\\:;<>?,./"
374
- multiline_string = """
375
- This is a multiline string.
376
- It can contain anything:
377
- 1. Numbers: 12345
378
- 2. Symbols: !@#$%^&*()
379
- 3. Quotes: "Hello" 'World'
380
- 4. Backticks: `code`
381
- 5. Even triple backticks: ```python
382
- """
383
- print(f"Special chars: {special_chars}")
384
- print(multiline_string)
385
- ''',
386
- )
387
-
388
- formatted_complex = CodeFileModel.format(complex_code)
389
- parsed_complex = CodeFileModel.parse(formatted_complex)
390
- assert (
391
- parsed_complex == complex_code
392
- ), f"Complex code parsing failed. Expected:\n{complex_code}\nGot:\n{parsed_complex}"
393
- print("Complex code test passed.")
394
-
395
- print("All tests passed successfully!")
@@ -1,133 +0,0 @@
1
- import re
2
- from abc import ABC, abstractmethod
3
- from typing import Type, TypeVar
4
-
5
- from jinja2 import BaseLoader, Environment
6
-
7
- from langroid.pydantic_v1 import BaseModel
8
-
9
- T = TypeVar("T", bound="FormattingModel")
10
-
11
-
12
- class FormattingModel(BaseModel, ABC):
13
- @classmethod
14
- @abstractmethod
15
- def format_spec(cls) -> str:
16
- pass
17
-
18
- @classmethod
19
- @abstractmethod
20
- def start_token(cls) -> str:
21
- pass
22
-
23
- @classmethod
24
- @abstractmethod
25
- def end_token(cls) -> str:
26
- pass
27
-
28
- @classmethod
29
- def parse(cls: Type[T], text: str) -> T:
30
- content = text.strip()[len(cls.start_token()) : -len(cls.end_token())].strip()
31
- pattern = cls.format_spec()
32
- for field in cls.__fields__:
33
- pattern = pattern.replace(f"{{{{{field}}}}}", f"(?P<{field}>.*?)")
34
- pattern = pattern.replace("\n", "\\n")
35
-
36
- match = re.match(pattern, content, re.DOTALL)
37
- if not match:
38
- raise ValueError("Failed to parse the input string")
39
-
40
- parsed_data = {k: v.strip() for k, v in match.groupdict().items()}
41
- return cls(**parsed_data)
42
-
43
- def generate(self) -> str:
44
- env = Environment(loader=BaseLoader())
45
- template = env.from_string(self.format_spec())
46
- content = template.render(**self.dict())
47
- return f"{self.start_token()}\n{content}\n{self.end_token()}"
48
-
49
-
50
- class CodeFileModel(FormattingModel):
51
- language: str
52
- file_path: str
53
- code: str
54
-
55
- @classmethod
56
- def format_spec(cls) -> str:
57
- return (
58
- "code_file_model\nfile_path: {{file_path}}\n```{{language}}\n{{code}}\n```"
59
- )
60
-
61
- @classmethod
62
- def start_token(cls) -> str:
63
- return "<format>"
64
-
65
- @classmethod
66
- def end_token(cls) -> str:
67
- return "</format>"
68
-
69
-
70
- if __name__ == "__main__":
71
- # Test CodeFileModel
72
- code_model = CodeFileModel(
73
- language="python",
74
- file_path="src/main.py",
75
- code='def hello():\n print("Hello, World!")',
76
- )
77
-
78
- print("Original CodeFileModel:")
79
- print(code_model)
80
- print()
81
-
82
- generated = code_model.generate()
83
- print("Generated string:")
84
- print(generated)
85
- print()
86
-
87
- parsed = CodeFileModel.parse(generated)
88
- print("Parsed CodeFileModel:")
89
- print(parsed)
90
- print()
91
-
92
- print("Round-trip test:")
93
- assert (
94
- code_model == parsed
95
- ), "Round-trip test failed: original and parsed models are not equal"
96
- print("Passed!")
97
-
98
- # Test with different values
99
- another_model = CodeFileModel(
100
- language="javascript",
101
- file_path="src/app.js",
102
- code="function greet(name) {\n console.log(`Hello, ${name}!`);\n}",
103
- )
104
-
105
- print("\nAnother CodeFileModel:")
106
- print(another_model)
107
- print()
108
-
109
- another_generated = another_model.generate()
110
- print("Another generated string:")
111
- print(another_generated)
112
- print()
113
-
114
- another_parsed = CodeFileModel.parse(another_generated)
115
- print("Another parsed CodeFileModel:")
116
- print(another_parsed)
117
- print()
118
-
119
- print("Another round-trip test:")
120
- assert (
121
- another_model == another_parsed
122
- ), "Another round-trip test failed: original and parsed models are not equal"
123
- print("Passed!")
124
-
125
- # Test error handling
126
- print("\nTesting error handling:")
127
- try:
128
- CodeFileModel.parse("Invalid format string")
129
- assert False, "Should have raised a ValueError"
130
- except ValueError as e:
131
- print(f"Correctly raised ValueError: {e}")
132
-
133
- print("\nAll tests passed successfully!")
@@ -1,122 +0,0 @@
1
- from pydantic import BaseModel
2
- from abc import ABC, abstractmethod
3
- from typing import Dict, Type, TypeVar
4
- from jinja2 import Environment, BaseLoader
5
- import re
6
-
7
- T = TypeVar('T', bound='FormattingModel')
8
-
9
- class FormattingModel(BaseModel, ABC):
10
- @classmethod
11
- @abstractmethod
12
- def format_spec(cls) -> str:
13
- pass
14
-
15
- @classmethod
16
- @abstractmethod
17
- def start_token(cls) -> str:
18
- pass
19
-
20
- @classmethod
21
- @abstractmethod
22
- def end_token(cls) -> str:
23
- pass
24
-
25
- @classmethod
26
- def parse(cls: Type[T], text: str) -> T:
27
- content = text.strip()[len(cls.start_token()):-len(cls.end_token())].strip()
28
- pattern = cls.format_spec()
29
- for field in cls.__fields__:
30
- pattern = pattern.replace(f"{{{{{field}}}}}", f"(?P<{field}>.*?)")
31
- pattern = pattern.replace("\n", "\\n")
32
-
33
- match = re.match(pattern, content, re.DOTALL)
34
- if not match:
35
- raise ValueError("Failed to parse the input string")
36
-
37
- parsed_data = {k: v.strip() for k, v in match.groupdict().items()}
38
- return cls(**parsed_data)
39
-
40
- def generate(self) -> str:
41
- env = Environment(loader=BaseLoader())
42
- template = env.from_string(self.format_spec())
43
- content = template.render(**self.dict())
44
- return f"{self.start_token()}\n{content}\n{self.end_token()}"
45
-
46
- class CodeFileModel(FormattingModel):
47
- language: str
48
- file_path: str
49
- code: str
50
-
51
- @classmethod
52
- def format_spec(cls) -> str:
53
- return "code_file_model\nfile_path: {{file_path}}\n```{{language}}\n{{code}}\n```"
54
-
55
- @classmethod
56
- def start_token(cls) -> str:
57
- return "<format>"
58
-
59
- @classmethod
60
- def end_token(cls) -> str:
61
- return "</format>"
62
-
63
- if __name__ == "__main__":
64
- # Test CodeFileModel
65
- code_model = CodeFileModel(
66
- language="python",
67
- file_path="src/main.py",
68
- code="def hello():\n print(\"Hello, World!\")"
69
- )
70
-
71
- print("Original CodeFileModel:")
72
- print(code_model)
73
- print()
74
-
75
- generated = code_model.generate()
76
- print("Generated string:")
77
- print(generated)
78
- print()
79
-
80
- parsed = CodeFileModel.parse(generated)
81
- print("Parsed CodeFileModel:")
82
- print(parsed)
83
- print()
84
-
85
- print("Round-trip test:")
86
- assert code_model == parsed, "Round-trip test failed: original and parsed models are not equal"
87
- print("Passed!")
88
-
89
- # Test with different values
90
- another_model = CodeFileModel(
91
- language="javascript",
92
- file_path="src/app.js",
93
- code="function greet(name) {\n console.log(`Hello, ${name}!`);\n}"
94
- )
95
-
96
- print("\nAnother CodeFileModel:")
97
- print(another_model)
98
- print()
99
-
100
- another_generated = another_model.generate()
101
- print("Another generated string:")
102
- print(another_generated)
103
- print()
104
-
105
- another_parsed = CodeFileModel.parse(another_generated)
106
- print("Another parsed CodeFileModel:")
107
- print(another_parsed)
108
- print()
109
-
110
- print("Another round-trip test:")
111
- assert another_model == another_parsed, "Another round-trip test failed: original and parsed models are not equal"
112
- print("Passed!")
113
-
114
- # Test error handling
115
- print("\nTesting error handling:")
116
- try:
117
- CodeFileModel.parse("Invalid format string")
118
- assert False, "Should have raised a ValueError"
119
- except ValueError as e:
120
- print(f"Correctly raised ValueError: {e}")
121
-
122
- print("\nAll tests passed successfully!")