langroid 0.16.5__py3-none-any.whl → 0.16.7__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 (80) hide show
  1. langroid/agent/md_tool_message_grammar.py +455 -0
  2. langroid/agent/tools/code_file_tool_parse.py +150 -0
  3. langroid/agent/tools/code_file_tool_pyparsing.py +194 -0
  4. langroid/agent/tools/code_file_tool_pyparsing2.py +199 -0
  5. langroid/agent/tools/formatted_model_custom.py +150 -0
  6. langroid/agent/tools/formatted_model_custom2.py +168 -0
  7. langroid/agent/tools/formatted_model_custom3.py +279 -0
  8. langroid/agent/tools/formatted_model_custom4.py +395 -0
  9. langroid/agent/tools/formatted_model_jinja.py +133 -0
  10. langroid/agent/tools/formatted_model_jinja.py-e +122 -0
  11. langroid/agent/tools/formatted_model_jinja2.py +145 -0
  12. langroid/agent/tools/formatted_model_jinja2.py-e +135 -0
  13. langroid/agent/tools/formatted_model_lark.py +0 -0
  14. langroid/agent/tools/formatted_model_lark2.py +168 -0
  15. langroid/agent/tools/formatted_model_parse.py +105 -0
  16. langroid/agent/tools/formatted_model_parse.py-e +98 -0
  17. langroid/agent/tools/formatted_model_parse2.py +113 -0
  18. langroid/agent/tools/formatted_model_parse2.py-e +109 -0
  19. langroid/agent/tools/formatted_model_parse3.py +114 -0
  20. langroid/agent/tools/formatted_model_parse3.py-e +110 -0
  21. langroid/agent/tools/formatted_model_parsimon.py +194 -0
  22. langroid/agent/tools/formatted_model_parsimon.py-e +186 -0
  23. langroid/agent/tools/formatted_model_pyparsing.py +169 -0
  24. langroid/agent/tools/formatted_model_pyparsing.py-e +149 -0
  25. langroid/agent/tools/formatted_model_pyparsing2.py +159 -0
  26. langroid/agent/tools/formatted_model_pyparsing2.py-e +143 -0
  27. langroid/agent/tools/formatted_model_pyparsing3.py +133 -0
  28. langroid/agent/tools/formatted_model_pyparsing3.py-e +121 -0
  29. langroid/agent/tools/formatted_model_pyparsing4.py +213 -0
  30. langroid/agent/tools/formatted_model_pyparsing4.py-e +176 -0
  31. langroid/agent/tools/formatted_model_pyparsing5.py +173 -0
  32. langroid/agent/tools/formatted_model_pyparsing5.py-e +142 -0
  33. langroid/agent/tools/formatted_model_regex.py +246 -0
  34. langroid/agent/tools/formatted_model_regex.py-e +248 -0
  35. langroid/agent/tools/formatted_model_regex2.py +250 -0
  36. langroid/agent/tools/formatted_model_regex2.py-e +253 -0
  37. langroid/agent/tools/formatted_model_tatsu.py +172 -0
  38. langroid/agent/tools/formatted_model_tatsu.py-e +160 -0
  39. langroid/agent/tools/formatted_model_template.py +217 -0
  40. langroid/agent/tools/formatted_model_template.py-e +200 -0
  41. langroid/agent/tools/formatted_model_xml.py +178 -0
  42. langroid/agent/tools/formatted_model_xml2.py +178 -0
  43. langroid/agent/tools/formatted_model_xml3.py +132 -0
  44. langroid/agent/tools/formatted_model_xml4.py +130 -0
  45. langroid/agent/tools/formatted_model_xml5.py +130 -0
  46. langroid/agent/tools/formatted_model_xml6.py +113 -0
  47. langroid/agent/tools/formatted_model_xml7.py +117 -0
  48. langroid/agent/tools/formatted_model_xml8.py +164 -0
  49. langroid/agent/tools/generic_tool.py +165 -0
  50. langroid/agent/tools/generic_tool_tatsu.py +275 -0
  51. langroid/agent/tools/grammar_based_model.py +132 -0
  52. langroid/agent/tools/grammar_based_model.py-e +128 -0
  53. langroid/agent/tools/grammar_based_model_lark.py +156 -0
  54. langroid/agent/tools/grammar_based_model_lark.py-e +153 -0
  55. langroid/agent/tools/grammar_based_model_parse.py +86 -0
  56. langroid/agent/tools/grammar_based_model_parse.py-e +80 -0
  57. langroid/agent/tools/grammar_based_model_parsimonious.py +129 -0
  58. langroid/agent/tools/grammar_based_model_parsimonious.py-e +120 -0
  59. langroid/agent/tools/grammar_based_model_pyparsing.py +105 -0
  60. langroid/agent/tools/grammar_based_model_pyparsing.py-e +103 -0
  61. langroid/agent/tools/grammar_based_model_regex.py +139 -0
  62. langroid/agent/tools/grammar_based_model_regex.py-e +130 -0
  63. langroid/agent/tools/grammar_based_model_regex2.py +124 -0
  64. langroid/agent/tools/grammar_based_model_regex2.py-e +116 -0
  65. langroid/agent/tools/grammar_based_model_tatsu.py +80 -0
  66. langroid/agent/tools/grammar_based_model_tatsu.py-e +77 -0
  67. langroid/agent/tools/lark_earley_example.py +135 -0
  68. langroid/agent/tools/lark_earley_example.py-e +117 -0
  69. langroid/agent/tools/lark_example.py +72 -0
  70. langroid/agent/tools/parse_example.py +76 -0
  71. langroid/agent/tools/parse_example2.py +87 -0
  72. langroid/agent/tools/parse_example3.py +42 -0
  73. langroid/agent/tools/parse_test.py +791 -0
  74. langroid/agent/xml_tool_message.py +106 -0
  75. langroid/language_models/openai_gpt.py +6 -1
  76. {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/METADATA +1 -1
  77. {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/RECORD +80 -6
  78. pyproject.toml +1 -1
  79. {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/LICENSE +0 -0
  80. {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,194 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from parsimonious import Grammar, NodeVisitor, exceptions
4
+
5
+ from langroid.pydantic_v1 import BaseModel
6
+
7
+
8
+ class FormattingModel(BaseModel, ABC):
9
+ @classmethod
10
+ @abstractmethod
11
+ def format_spec(cls):
12
+ pass
13
+
14
+ @classmethod
15
+ @abstractmethod
16
+ def parse_spec(cls):
17
+ pass
18
+
19
+ @classmethod
20
+ @abstractmethod
21
+ def parse_visitor(cls):
22
+ pass
23
+
24
+ @classmethod
25
+ @abstractmethod
26
+ def start_token(cls) -> str:
27
+ pass
28
+
29
+ @classmethod
30
+ @abstractmethod
31
+ def end_token(cls) -> str:
32
+ pass
33
+
34
+ @classmethod
35
+ def format(cls, instance: "FormattingModel") -> str:
36
+ spec = cls.format_spec()
37
+ formatted = spec.format(**instance.dict())
38
+ return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
39
+
40
+ @classmethod
41
+ def parse(cls, formatted_string: str) -> "FormattingModel":
42
+ lines = formatted_string.strip().split("\n")
43
+ if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
44
+ raise ValueError("Invalid start or end token")
45
+ content = "\n".join(lines[1:-1])
46
+
47
+ grammar = Grammar(cls.parse_spec())
48
+ try:
49
+ tree = grammar.parse(content)
50
+ except exceptions.ParseError as e:
51
+ raise ValueError(f"Failed to parse content: {e}\nContent:\n{content}")
52
+
53
+ visitor = cls.parse_visitor()()
54
+ try:
55
+ visitor_result = visitor.visit(tree)
56
+ except Exception as e:
57
+ raise ValueError(f"Error during tree visitation: {e}")
58
+
59
+ if not all(field in visitor_result for field in cls.__fields__):
60
+ missing_fields = [
61
+ field for field in cls.__fields__ if field not in visitor_result
62
+ ]
63
+ raise ValueError(
64
+ f"Missing fields after parsing: {', '.join(missing_fields)}"
65
+ )
66
+
67
+ return cls(**visitor_result)
68
+
69
+
70
+ class CodeFileModel(FormattingModel):
71
+ language: str
72
+ file_path: str
73
+ code: str
74
+
75
+ @classmethod
76
+ def format_spec(cls):
77
+ return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
78
+
79
+ @classmethod
80
+ def parse_spec(cls):
81
+ return """
82
+ model = "code_file_model" newline file_path newline code_block
83
+ file_path = line
84
+ code_block = "```" language newline code "```"
85
+ language = ~"[^\\n]+"
86
+ code = (!(~"```") (newline / any_char))*
87
+ line = ~"[^\\n]+"
88
+ newline = ~"\\s*\\n\\s*"
89
+ any_char = ~"."
90
+ """
91
+
92
+ @classmethod
93
+ def parse_visitor(cls):
94
+ class Visitor(NodeVisitor):
95
+ def __init__(self):
96
+ self.data = {}
97
+
98
+ def visit_model(self, node, visited_children):
99
+ return self.data
100
+
101
+ def visit_file_path(self, node, visited_children):
102
+ self.data["file_path"] = node.text.strip()
103
+
104
+ def visit_language(self, node, visited_children):
105
+ self.data["language"] = node.text.strip()
106
+
107
+ def visit_code(self, node, visited_children):
108
+ self.data["code"] = node.text.strip()
109
+
110
+ def generic_visit(self, node, visited_children):
111
+ return visited_children or node
112
+
113
+ return Visitor
114
+
115
+ @classmethod
116
+ def start_token(cls):
117
+ return "<format>"
118
+
119
+ @classmethod
120
+ def end_token(cls):
121
+ return "</format>"
122
+
123
+
124
+ # Test cases
125
+ if __name__ == "__main__":
126
+ # Test formatting
127
+ code_file = CodeFileModel(
128
+ language="Python",
129
+ file_path="src/main.py",
130
+ code="def hello():\n print('Hello, World!')",
131
+ )
132
+ formatted = CodeFileModel.format(code_file)
133
+ expected_format = """<format>
134
+ code_file_model
135
+ src/main.py
136
+ ```Python
137
+ def hello():
138
+ print('Hello, World!')
139
+ ```
140
+ </format>"""
141
+ assert (
142
+ formatted == expected_format
143
+ ), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
144
+ print("Formatting test passed.")
145
+
146
+ # Test parsing
147
+ parsed = CodeFileModel.parse(formatted)
148
+ assert (
149
+ parsed == code_file
150
+ ), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
151
+ print("Parsing test passed.")
152
+
153
+ # Test round-trip
154
+ round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
155
+ assert (
156
+ round_trip == code_file
157
+ ), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
158
+ print("Round-trip test passed.")
159
+
160
+ # Test with different values
161
+ code_file2 = CodeFileModel(
162
+ language="JavaScript",
163
+ file_path="src/app.js",
164
+ code="function greet() {\n console.log('Hello, World!');\n}",
165
+ )
166
+ formatted2 = CodeFileModel.format(code_file2)
167
+ parsed2 = CodeFileModel.parse(formatted2)
168
+ assert (
169
+ parsed2 == code_file2
170
+ ), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
171
+ print("Different values test passed.")
172
+
173
+ # Test tolerant parsing
174
+ tolerant_input = """<format>
175
+ code_file_model
176
+ src/main.py
177
+
178
+ ``` Python
179
+ def hello():
180
+ print('Hello, World!')
181
+ ```
182
+ </format>"""
183
+ parsed_tolerant = CodeFileModel.parse(tolerant_input)
184
+ expected_tolerant = CodeFileModel(
185
+ language="Python",
186
+ file_path="src/main.py",
187
+ code="def hello():\n print('Hello, World!')",
188
+ )
189
+ assert (
190
+ parsed_tolerant == expected_tolerant
191
+ ), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
192
+ print("Tolerant parsing test passed.")
193
+
194
+ print("All tests passed successfully!")
@@ -0,0 +1,186 @@
1
+ from abc import ABC, abstractmethod
2
+ from pydantic import BaseModel
3
+ from parsimonious import Grammar, NodeVisitor, exceptions
4
+
5
+ class FormattingModel(BaseModel, ABC):
6
+ @classmethod
7
+ @abstractmethod
8
+ def format_spec(cls):
9
+ pass
10
+
11
+ @classmethod
12
+ @abstractmethod
13
+ def parse_spec(cls):
14
+ pass
15
+
16
+ @classmethod
17
+ @abstractmethod
18
+ def parse_visitor(cls):
19
+ pass
20
+
21
+ @classmethod
22
+ @abstractmethod
23
+ def start_token(cls) -> str:
24
+ pass
25
+
26
+ @classmethod
27
+ @abstractmethod
28
+ def end_token(cls) -> str:
29
+ pass
30
+
31
+ @classmethod
32
+ def format(cls, instance: 'FormattingModel') -> str:
33
+ spec = cls.format_spec()
34
+ formatted = spec.format(**instance.dict())
35
+ return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
36
+
37
+ @classmethod
38
+ def parse(cls, formatted_string: str) -> 'FormattingModel':
39
+ lines = formatted_string.strip().split('\n')
40
+ if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
41
+ raise ValueError("Invalid start or end token")
42
+ content = '\n'.join(lines[1:-1])
43
+
44
+ grammar = Grammar(cls.parse_spec())
45
+ try:
46
+ tree = grammar.parse(content)
47
+ except exceptions.ParseError as e:
48
+ raise ValueError(f"Failed to parse content: {e}\nContent:\n{content}")
49
+
50
+ visitor = cls.parse_visitor()()
51
+ try:
52
+ visitor_result = visitor.visit(tree)
53
+ except Exception as e:
54
+ raise ValueError(f"Error during tree visitation: {e}")
55
+
56
+ if not all(field in visitor_result for field in cls.__fields__):
57
+ missing_fields = [field for field in cls.__fields__ if field not in visitor_result]
58
+ raise ValueError(f"Missing fields after parsing: {', '.join(missing_fields)}")
59
+
60
+ return cls(**visitor_result)
61
+
62
+
63
+ from parsimonious import Grammar, NodeVisitor
64
+
65
+ class CodeFileModel(FormattingModel):
66
+ language: str
67
+ file_path: str
68
+ code: str
69
+
70
+ @classmethod
71
+ def format_spec(cls):
72
+ return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
73
+
74
+ @classmethod
75
+ def parse_spec(cls):
76
+ return """
77
+ model = "code_file_model" newline file_path newline code_block
78
+ file_path = line
79
+ code_block = "```" language newline code "```"
80
+ language = ~"[^\\n]+"
81
+ code = (!(~"```") (newline / any_char))*
82
+ line = ~"[^\\n]+"
83
+ newline = ~"\\s*\\n\\s*"
84
+ any_char = ~"."
85
+ """
86
+
87
+ @classmethod
88
+ def parse_visitor(cls):
89
+ class Visitor(NodeVisitor):
90
+ def __init__(self):
91
+ self.data = {}
92
+
93
+ def visit_model(self, node, visited_children):
94
+ return self.data
95
+
96
+ def visit_file_path(self, node, visited_children):
97
+ self.data['file_path'] = node.text.strip()
98
+
99
+ def visit_language(self, node, visited_children):
100
+ self.data['language'] = node.text.strip()
101
+
102
+ def visit_code(self, node, visited_children):
103
+ self.data['code'] = node.text.strip()
104
+
105
+ def generic_visit(self, node, visited_children):
106
+ return visited_children or node
107
+
108
+ return Visitor
109
+
110
+ @classmethod
111
+ def start_token(cls):
112
+ return '<format>'
113
+
114
+ @classmethod
115
+ def end_token(cls):
116
+ return '</format>'
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+ # Test cases
126
+ if __name__ == "__main__":
127
+ # Test formatting
128
+ code_file = CodeFileModel(
129
+ language="Python",
130
+ file_path="src/main.py",
131
+ code="def hello():\n print('Hello, World!')"
132
+ )
133
+ formatted = CodeFileModel.format(code_file)
134
+ expected_format = """<format>
135
+ code_file_model
136
+ src/main.py
137
+ ```Python
138
+ def hello():
139
+ print('Hello, World!')
140
+ ```
141
+ </format>"""
142
+ assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
143
+ print("Formatting test passed.")
144
+
145
+ # Test parsing
146
+ parsed = CodeFileModel.parse(formatted)
147
+ assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
148
+ print("Parsing test passed.")
149
+
150
+ # Test round-trip
151
+ round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
152
+ assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
153
+ print("Round-trip test passed.")
154
+
155
+ # Test with different values
156
+ code_file2 = CodeFileModel(
157
+ language="JavaScript",
158
+ file_path="src/app.js",
159
+ code="function greet() {\n console.log('Hello, World!');\n}"
160
+ )
161
+ formatted2 = CodeFileModel.format(code_file2)
162
+ parsed2 = CodeFileModel.parse(formatted2)
163
+ assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
164
+ print("Different values test passed.")
165
+
166
+ # Test tolerant parsing
167
+ tolerant_input = """<format>
168
+ code_file_model
169
+ src/main.py
170
+
171
+ ``` Python
172
+ def hello():
173
+ print('Hello, World!')
174
+ ```
175
+ </format>"""
176
+ parsed_tolerant = CodeFileModel.parse(tolerant_input)
177
+ expected_tolerant = CodeFileModel(
178
+ language="Python",
179
+ file_path="src/main.py",
180
+ code="def hello():\n print('Hello, World!')"
181
+ )
182
+ assert parsed_tolerant == expected_tolerant, f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
183
+ print("Tolerant parsing test passed.")
184
+
185
+ print("All tests passed successfully!")
186
+
@@ -0,0 +1,169 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict, Type, TypeVar
3
+
4
+ from pyparsing import (
5
+ LineEnd,
6
+ ParseException,
7
+ ParserElement,
8
+ Regex,
9
+ SkipTo,
10
+ Suppress,
11
+ Word,
12
+ alphanums,
13
+ )
14
+
15
+ from langroid.pydantic_v1 import BaseModel
16
+
17
+ T = TypeVar("T", bound="FormattingModel")
18
+
19
+
20
+ class FormattingModel(BaseModel, ABC):
21
+ @classmethod
22
+ @abstractmethod
23
+ def format_spec(cls) -> tuple[str, ParserElement]:
24
+ pass
25
+
26
+ @classmethod
27
+ @abstractmethod
28
+ def start_token(cls) -> str:
29
+ pass
30
+
31
+ @classmethod
32
+ @abstractmethod
33
+ def end_token(cls) -> str:
34
+ pass
35
+
36
+ @classmethod
37
+ @abstractmethod
38
+ def field_mappings(cls) -> Dict[str, str]:
39
+ pass
40
+
41
+ @classmethod
42
+ def parse(cls: Type[T], text: str) -> T:
43
+ content = text.strip()[len(cls.start_token()) : -len(cls.end_token())].strip()
44
+ try:
45
+ _, parser = cls.format_spec()
46
+ parsed = parser.parseString(content)
47
+ data = {
48
+ field: parsed[token].strip()
49
+ for field, token in cls.field_mappings().items()
50
+ }
51
+ return cls(**data)
52
+ except ParseException as e:
53
+ print(f"Parsing error: {e}")
54
+ raise
55
+
56
+ def generate(self) -> str:
57
+ template, _ = self.format_spec()
58
+ for field, token in self.field_mappings().items():
59
+ value = getattr(self, field)
60
+ template = template.replace(f"{{{token}}}", str(value))
61
+ return f"{self.start_token()}\n{template}\n{self.end_token()}"
62
+
63
+
64
+ class MyFormattedModel(FormattingModel):
65
+ name: str
66
+ age: int
67
+ city: str
68
+
69
+ @classmethod
70
+ def format_spec(cls) -> tuple[str, ParserElement]:
71
+ template = "name: {NAME}\n{AGE} is the age\nlives in {CITY}"
72
+ name = Suppress("name:") + Word(alphanums + " ")("NAME") + LineEnd()
73
+ age = Word(alphanums)("AGE") + Suppress("is the age") + LineEnd()
74
+ city = Suppress("lives in") + SkipTo(LineEnd())("CITY")
75
+ parser = name + age + city
76
+ return template, parser
77
+
78
+ @classmethod
79
+ def start_token(cls) -> str:
80
+ return "<format>"
81
+
82
+ @classmethod
83
+ def end_token(cls) -> str:
84
+ return "</format>"
85
+
86
+ @classmethod
87
+ def field_mappings(cls) -> Dict[str, str]:
88
+ return {"name": "NAME", "age": "AGE", "city": "CITY"}
89
+
90
+
91
+ class CodeFileModel(FormattingModel):
92
+ language: str
93
+ file_path: str
94
+ code: str
95
+
96
+ @classmethod
97
+ def format_spec(cls) -> tuple[str, ParserElement]:
98
+ template = "code_file_model\nfile_path: {FILE_PATH}\n```{LANGUAGE}\n{CODE}\n```"
99
+ file_path = (
100
+ Suppress("code_file_model")
101
+ + LineEnd()
102
+ + Suppress("file_path:")
103
+ + SkipTo(LineEnd())("FILE_PATH")
104
+ + LineEnd()
105
+ )
106
+ code_block = (
107
+ Suppress("```")
108
+ + Word(alphanums)("LANGUAGE")
109
+ + LineEnd()
110
+ + Regex(r"(?s).*?(?=```)").set_parse_action(lambda s, l, t: t[0].strip())(
111
+ "CODE"
112
+ )
113
+ + Suppress("```")
114
+ )
115
+ parser = file_path + code_block
116
+ return template, parser
117
+
118
+ @classmethod
119
+ def start_token(cls) -> str:
120
+ return "<format>"
121
+
122
+ @classmethod
123
+ def end_token(cls) -> str:
124
+ return "</format>"
125
+
126
+ @classmethod
127
+ def field_mappings(cls) -> Dict[str, str]:
128
+ return {"file_path": "FILE_PATH", "language": "LANGUAGE", "code": "CODE"}
129
+
130
+
131
+ if __name__ == "__main__":
132
+ # Test MyFormattedModel
133
+ model = MyFormattedModel(name="John", age=30, city="Tokyo")
134
+ generated = model.generate()
135
+ print("Generated MyFormattedModel string:")
136
+ print(generated)
137
+ print()
138
+
139
+ parsed = MyFormattedModel.parse(generated)
140
+ print("Parsed MyFormattedModel object:")
141
+ print(parsed)
142
+ print()
143
+
144
+ print("MyFormattedModel Round-trip test:")
145
+ assert model == parsed, "MyFormattedModel: Original != Parsed"
146
+ print("Passed!")
147
+ print()
148
+
149
+ # Test CodeFileModel
150
+ code_model = CodeFileModel(
151
+ language="python",
152
+ file_path="src/main.py",
153
+ code='def hello():\n print("Hello, World!")',
154
+ )
155
+ code_generated = code_model.generate()
156
+ print("Generated CodeFileModel string:")
157
+ print(code_generated)
158
+ print()
159
+
160
+ code_parsed = CodeFileModel.parse(code_generated)
161
+ print("Parsed CodeFileModel object:")
162
+ print(code_parsed)
163
+ print()
164
+
165
+ print("CodeFileModel Round-trip test:")
166
+ assert code_model == code_parsed, "CodeFileModel: Original != Parsed"
167
+ print("Passed!")
168
+
169
+ print("\nAll tests passed successfully!")
@@ -0,0 +1,149 @@
1
+ from pydantic import BaseModel
2
+ from abc import ABC, abstractmethod
3
+ from typing import Dict, Type, TypeVar
4
+ from pyparsing import (
5
+ Word, alphanums, Suppress, SkipTo, LineEnd,
6
+ QuotedString, delimitedList, Group, OneOrMore, ParseException,
7
+ ParserElement, Regex
8
+ )
9
+
10
+ T = TypeVar('T', bound='FormattingModel')
11
+
12
+ class FormattingModel(BaseModel, ABC):
13
+ @classmethod
14
+ @abstractmethod
15
+ def format_spec(cls) -> tuple[str, ParserElement]:
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
+ @abstractmethod
30
+ def field_mappings(cls) -> Dict[str, str]:
31
+ pass
32
+
33
+ @classmethod
34
+ def parse(cls: Type[T], text: str) -> T:
35
+ content = text.strip()[len(cls.start_token()):-len(cls.end_token())].strip()
36
+ try:
37
+ _, parser = cls.format_spec()
38
+ parsed = parser.parseString(content)
39
+ data = {field: parsed[token].strip() for field, token in cls.field_mappings().items()}
40
+ return cls(**data)
41
+ except ParseException as e:
42
+ print(f"Parsing error: {e}")
43
+ raise
44
+
45
+ def generate(self) -> str:
46
+ template, _ = self.format_spec()
47
+ for field, token in self.field_mappings().items():
48
+ value = getattr(self, field)
49
+ template = template.replace(f"{{{token}}}", str(value))
50
+ return f"{self.start_token()}\n{template}\n{self.end_token()}"
51
+
52
+ class MyFormattedModel(FormattingModel):
53
+ name: str
54
+ age: int
55
+ city: str
56
+
57
+ @classmethod
58
+ def format_spec(cls) -> tuple[str, ParserElement]:
59
+ template = "name: {NAME}\n{AGE} is the age\nlives in {CITY}"
60
+ name = Suppress("name:") + Word(alphanums + " ")("NAME") + LineEnd()
61
+ age = Word(alphanums)("AGE") + Suppress("is the age") + LineEnd()
62
+ city = Suppress("lives in") + SkipTo(LineEnd())("CITY")
63
+ parser = name + age + city
64
+ return template, parser
65
+
66
+ @classmethod
67
+ def start_token(cls) -> str:
68
+ return "<format>"
69
+
70
+ @classmethod
71
+ def end_token(cls) -> str:
72
+ return "</format>"
73
+
74
+ @classmethod
75
+ def field_mappings(cls) -> Dict[str, str]:
76
+ return {
77
+ "name": "NAME",
78
+ "age": "AGE",
79
+ "city": "CITY"
80
+ }
81
+
82
+ class CodeFileModel(FormattingModel):
83
+ language: str
84
+ file_path: str
85
+ code: str
86
+
87
+ @classmethod
88
+ def format_spec(cls) -> tuple[str, ParserElement]:
89
+ template = "code_file_model\nfile_path: {FILE_PATH}\n```{LANGUAGE}\n{CODE}\n```"
90
+ file_path = Suppress("code_file_model") + LineEnd() + Suppress("file_path:") + SkipTo(LineEnd())("FILE_PATH") + LineEnd()
91
+ code_block = Suppress("```") + Word(alphanums)("LANGUAGE") + LineEnd() + Regex(r"(?s).*?(?=```)").set_parse_action(lambda s, l, t: t[0].strip())("CODE") + Suppress("```")
92
+ parser = file_path + code_block
93
+ return template, parser
94
+
95
+ @classmethod
96
+ def start_token(cls) -> str:
97
+ return "<format>"
98
+
99
+ @classmethod
100
+ def end_token(cls) -> str:
101
+ return "</format>"
102
+
103
+ @classmethod
104
+ def field_mappings(cls) -> Dict[str, str]:
105
+ return {
106
+ "file_path": "FILE_PATH",
107
+ "language": "LANGUAGE",
108
+ "code": "CODE"
109
+ }
110
+
111
+ if __name__ == "__main__":
112
+ # Test MyFormattedModel
113
+ model = MyFormattedModel(name="John", age=30, city="Tokyo")
114
+ generated = model.generate()
115
+ print("Generated MyFormattedModel string:")
116
+ print(generated)
117
+ print()
118
+
119
+ parsed = MyFormattedModel.parse(generated)
120
+ print("Parsed MyFormattedModel object:")
121
+ print(parsed)
122
+ print()
123
+
124
+ print("MyFormattedModel Round-trip test:")
125
+ assert model == parsed, "MyFormattedModel: Original != Parsed"
126
+ print("Passed!")
127
+ print()
128
+
129
+ # Test CodeFileModel
130
+ code_model = CodeFileModel(
131
+ language="python",
132
+ file_path="src/main.py",
133
+ code="def hello():\n print(\"Hello, World!\")"
134
+ )
135
+ code_generated = code_model.generate()
136
+ print("Generated CodeFileModel string:")
137
+ print(code_generated)
138
+ print()
139
+
140
+ code_parsed = CodeFileModel.parse(code_generated)
141
+ print("Parsed CodeFileModel object:")
142
+ print(code_parsed)
143
+ print()
144
+
145
+ print("CodeFileModel Round-trip test:")
146
+ assert code_model == code_parsed, "CodeFileModel: Original != Parsed"
147
+ print("Passed!")
148
+
149
+ print("\nAll tests passed successfully!")