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.
- langroid/agent/md_tool_message_grammar.py +455 -0
- langroid/agent/tools/code_file_tool_parse.py +150 -0
- langroid/agent/tools/code_file_tool_pyparsing.py +194 -0
- langroid/agent/tools/code_file_tool_pyparsing2.py +199 -0
- langroid/agent/tools/formatted_model_custom.py +150 -0
- langroid/agent/tools/formatted_model_custom2.py +168 -0
- langroid/agent/tools/formatted_model_custom3.py +279 -0
- langroid/agent/tools/formatted_model_custom4.py +395 -0
- langroid/agent/tools/formatted_model_jinja.py +133 -0
- langroid/agent/tools/formatted_model_jinja.py-e +122 -0
- langroid/agent/tools/formatted_model_jinja2.py +145 -0
- langroid/agent/tools/formatted_model_jinja2.py-e +135 -0
- langroid/agent/tools/formatted_model_lark.py +0 -0
- langroid/agent/tools/formatted_model_lark2.py +168 -0
- langroid/agent/tools/formatted_model_parse.py +105 -0
- langroid/agent/tools/formatted_model_parse.py-e +98 -0
- langroid/agent/tools/formatted_model_parse2.py +113 -0
- langroid/agent/tools/formatted_model_parse2.py-e +109 -0
- langroid/agent/tools/formatted_model_parse3.py +114 -0
- langroid/agent/tools/formatted_model_parse3.py-e +110 -0
- langroid/agent/tools/formatted_model_parsimon.py +194 -0
- langroid/agent/tools/formatted_model_parsimon.py-e +186 -0
- langroid/agent/tools/formatted_model_pyparsing.py +169 -0
- langroid/agent/tools/formatted_model_pyparsing.py-e +149 -0
- langroid/agent/tools/formatted_model_pyparsing2.py +159 -0
- langroid/agent/tools/formatted_model_pyparsing2.py-e +143 -0
- langroid/agent/tools/formatted_model_pyparsing3.py +133 -0
- langroid/agent/tools/formatted_model_pyparsing3.py-e +121 -0
- langroid/agent/tools/formatted_model_pyparsing4.py +213 -0
- langroid/agent/tools/formatted_model_pyparsing4.py-e +176 -0
- langroid/agent/tools/formatted_model_pyparsing5.py +173 -0
- langroid/agent/tools/formatted_model_pyparsing5.py-e +142 -0
- langroid/agent/tools/formatted_model_regex.py +246 -0
- langroid/agent/tools/formatted_model_regex.py-e +248 -0
- langroid/agent/tools/formatted_model_regex2.py +250 -0
- langroid/agent/tools/formatted_model_regex2.py-e +253 -0
- langroid/agent/tools/formatted_model_tatsu.py +172 -0
- langroid/agent/tools/formatted_model_tatsu.py-e +160 -0
- langroid/agent/tools/formatted_model_template.py +217 -0
- langroid/agent/tools/formatted_model_template.py-e +200 -0
- langroid/agent/tools/formatted_model_xml.py +178 -0
- langroid/agent/tools/formatted_model_xml2.py +178 -0
- langroid/agent/tools/formatted_model_xml3.py +132 -0
- langroid/agent/tools/formatted_model_xml4.py +130 -0
- langroid/agent/tools/formatted_model_xml5.py +130 -0
- langroid/agent/tools/formatted_model_xml6.py +113 -0
- langroid/agent/tools/formatted_model_xml7.py +117 -0
- langroid/agent/tools/formatted_model_xml8.py +164 -0
- langroid/agent/tools/generic_tool.py +165 -0
- langroid/agent/tools/generic_tool_tatsu.py +275 -0
- langroid/agent/tools/grammar_based_model.py +132 -0
- langroid/agent/tools/grammar_based_model.py-e +128 -0
- langroid/agent/tools/grammar_based_model_lark.py +156 -0
- langroid/agent/tools/grammar_based_model_lark.py-e +153 -0
- langroid/agent/tools/grammar_based_model_parse.py +86 -0
- langroid/agent/tools/grammar_based_model_parse.py-e +80 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py +129 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py-e +120 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py +105 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py-e +103 -0
- langroid/agent/tools/grammar_based_model_regex.py +139 -0
- langroid/agent/tools/grammar_based_model_regex.py-e +130 -0
- langroid/agent/tools/grammar_based_model_regex2.py +124 -0
- langroid/agent/tools/grammar_based_model_regex2.py-e +116 -0
- langroid/agent/tools/grammar_based_model_tatsu.py +80 -0
- langroid/agent/tools/grammar_based_model_tatsu.py-e +77 -0
- langroid/agent/tools/lark_earley_example.py +135 -0
- langroid/agent/tools/lark_earley_example.py-e +117 -0
- langroid/agent/tools/lark_example.py +72 -0
- langroid/agent/tools/parse_example.py +76 -0
- langroid/agent/tools/parse_example2.py +87 -0
- langroid/agent/tools/parse_example3.py +42 -0
- langroid/agent/tools/parse_test.py +791 -0
- langroid/agent/xml_tool_message.py +106 -0
- langroid/language_models/openai_gpt.py +6 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/METADATA +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/RECORD +80 -6
- pyproject.toml +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/LICENSE +0 -0
- {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!")
|