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,160 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from pydantic import BaseModel
|
3
|
+
import tatsu
|
4
|
+
from tatsu.model import ModelBuilderSemantics
|
5
|
+
|
6
|
+
class FormattingModel(BaseModel, ABC):
|
7
|
+
@classmethod
|
8
|
+
@abstractmethod
|
9
|
+
def format_spec(cls):
|
10
|
+
pass
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
@abstractmethod
|
14
|
+
def parse_spec(cls):
|
15
|
+
pass
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
@abstractmethod
|
19
|
+
def start_token(cls) -> str:
|
20
|
+
pass
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
@abstractmethod
|
24
|
+
def end_token(cls) -> str:
|
25
|
+
pass
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def format(cls, instance: 'FormattingModel') -> str:
|
29
|
+
spec = cls.format_spec()
|
30
|
+
formatted = spec.format(**instance.dict())
|
31
|
+
return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
def parse(cls, formatted_string: str) -> 'FormattingModel':
|
35
|
+
lines = formatted_string.strip().split('\n')
|
36
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
37
|
+
raise ValueError("Invalid start or end token")
|
38
|
+
content = '\n'.join(lines[1:-1])
|
39
|
+
|
40
|
+
parser = tatsu.compile(cls.parse_spec())
|
41
|
+
ast = parser.parse(content)
|
42
|
+
return cls(**ast)
|
43
|
+
|
44
|
+
class CodeFileModel(FormattingModel):
|
45
|
+
language: str
|
46
|
+
file_path: str
|
47
|
+
code: str
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def format_spec(cls):
|
51
|
+
return "code_file_model\n{file_path}\n```{language}\n{code}\n```"
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def parse_spec(cls):
|
55
|
+
return '''
|
56
|
+
@@grammar::CodeFileModel
|
57
|
+
|
58
|
+
start = header file_path language code $ ;
|
59
|
+
|
60
|
+
header = "code_file_model" ~;
|
61
|
+
file_path = /[^\n]+/ ~;
|
62
|
+
language = "```" /[a-zA-Z]+/ ~;
|
63
|
+
code = /(?s).*?(?=```)/ "```" ~;
|
64
|
+
|
65
|
+
@@whitespace :: /\s*/
|
66
|
+
'''
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def start_token(cls):
|
70
|
+
return '<format>'
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def end_token(cls):
|
74
|
+
return '</format>'
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def parse(cls, formatted_string: str) -> 'CodeFileModel':
|
78
|
+
lines = formatted_string.strip().split('\n')
|
79
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
80
|
+
raise ValueError("Invalid start or end token")
|
81
|
+
content = '\n'.join(lines[1:-1])
|
82
|
+
|
83
|
+
class CodeFileModelSemantics(ModelBuilderSemantics):
|
84
|
+
def file_path(self, ast):
|
85
|
+
return ast.strip()
|
86
|
+
|
87
|
+
def language(self, ast):
|
88
|
+
return ast[1].strip()
|
89
|
+
|
90
|
+
def code(self, ast):
|
91
|
+
return ast[0].strip()
|
92
|
+
|
93
|
+
parser = tatsu.compile(cls.parse_spec(), semantics=CodeFileModelSemantics())
|
94
|
+
ast = parser.parse(content)
|
95
|
+
return cls(**ast)
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
# Test cases
|
100
|
+
if __name__ == "__main__":
|
101
|
+
# Test formatting
|
102
|
+
code_file = CodeFileModel(
|
103
|
+
language="Python",
|
104
|
+
file_path="src/main.py",
|
105
|
+
code="def hello():\n print('Hello, World!')"
|
106
|
+
)
|
107
|
+
formatted = CodeFileModel.format(code_file)
|
108
|
+
expected_format = """<format>
|
109
|
+
code_file_model
|
110
|
+
src/main.py
|
111
|
+
```Python
|
112
|
+
def hello():
|
113
|
+
print('Hello, World!')
|
114
|
+
```
|
115
|
+
</format>"""
|
116
|
+
assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
117
|
+
print("Formatting test passed.")
|
118
|
+
|
119
|
+
# Test parsing
|
120
|
+
parsed = CodeFileModel.parse(formatted)
|
121
|
+
assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
122
|
+
print("Parsing test passed.")
|
123
|
+
|
124
|
+
# Test round-trip
|
125
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
126
|
+
assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
127
|
+
print("Round-trip test passed.")
|
128
|
+
|
129
|
+
# Test with different values
|
130
|
+
code_file2 = CodeFileModel(
|
131
|
+
language="JavaScript",
|
132
|
+
file_path="src/app.js",
|
133
|
+
code="function greet() {\n console.log('Hello, World!');\n}"
|
134
|
+
)
|
135
|
+
formatted2 = CodeFileModel.format(code_file2)
|
136
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
137
|
+
assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
138
|
+
print("Different values test passed.")
|
139
|
+
|
140
|
+
# Test tolerant parsing
|
141
|
+
tolerant_input = """<format>
|
142
|
+
code_file_model
|
143
|
+
src/main.py
|
144
|
+
|
145
|
+
``` Python
|
146
|
+
def hello():
|
147
|
+
print('Hello, World!')
|
148
|
+
```
|
149
|
+
</format>"""
|
150
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
151
|
+
expected_tolerant = CodeFileModel(
|
152
|
+
language="Python",
|
153
|
+
file_path="src/main.py",
|
154
|
+
code="def hello():\n print('Hello, World!')"
|
155
|
+
)
|
156
|
+
assert parsed_tolerant == expected_tolerant, f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
157
|
+
print("Tolerant parsing test passed.")
|
158
|
+
|
159
|
+
print("All tests passed successfully!")
|
160
|
+
|
@@ -0,0 +1,217 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from string import Template
|
3
|
+
from typing import Any, Dict
|
4
|
+
|
5
|
+
from langroid.pydantic_v1 import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class FormattingModel(BaseModel, ABC):
|
9
|
+
@classmethod
|
10
|
+
@abstractmethod
|
11
|
+
def format_spec(cls) -> Dict[str, Any]:
|
12
|
+
pass
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
@abstractmethod
|
16
|
+
def parse_spec(cls) -> Dict[str, Any]:
|
17
|
+
pass
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
@abstractmethod
|
21
|
+
def start_token(cls) -> str:
|
22
|
+
pass
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
@abstractmethod
|
26
|
+
def end_token(cls) -> str:
|
27
|
+
pass
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def format(cls, instance: "FormattingModel") -> str:
|
31
|
+
spec = cls.format_spec()
|
32
|
+
template = Template(spec["template"])
|
33
|
+
formatted = template.substitute(instance.dict())
|
34
|
+
return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
38
|
+
spec = cls.parse_spec()
|
39
|
+
lines = formatted_string.strip().split("\n")
|
40
|
+
|
41
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
42
|
+
raise ValueError("Invalid start or end token")
|
43
|
+
|
44
|
+
content = "\n".join(lines[1:-1])
|
45
|
+
|
46
|
+
parsed_data = {}
|
47
|
+
for field, token in spec["tokens"].items():
|
48
|
+
if token:
|
49
|
+
parts = content.split(token, 1)
|
50
|
+
if len(parts) > 1:
|
51
|
+
value = parts[1].split("\n")[0].strip()
|
52
|
+
else:
|
53
|
+
raise ValueError(
|
54
|
+
f"Could not find token '{token}' for field '{field}'"
|
55
|
+
)
|
56
|
+
else:
|
57
|
+
# For fields without a specific token (like age in our example)
|
58
|
+
value = (
|
59
|
+
content.split("\n")[spec["field_order"].index(field)]
|
60
|
+
.split(" ")[0]
|
61
|
+
.strip()
|
62
|
+
)
|
63
|
+
|
64
|
+
# Convert to appropriate type based on the model's field types
|
65
|
+
field_type = cls.__annotations__[field]
|
66
|
+
if field_type == int:
|
67
|
+
parsed_data[field] = int(value)
|
68
|
+
elif field_type == float:
|
69
|
+
parsed_data[field] = float(value)
|
70
|
+
else:
|
71
|
+
parsed_data[field] = value
|
72
|
+
|
73
|
+
return cls(**parsed_data)
|
74
|
+
|
75
|
+
|
76
|
+
class PersonModel(FormattingModel):
|
77
|
+
name: str
|
78
|
+
age: int
|
79
|
+
city: str
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def format_spec(cls):
|
83
|
+
return {"template": "name: $name\n$age is the age\nlives in $city"}
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
def parse_spec(cls):
|
87
|
+
return {
|
88
|
+
"template": "name: $name\n$age is the age\nlives in $city",
|
89
|
+
"tokens": {"name": "name: ", "age": "", "city": "lives in "},
|
90
|
+
"field_order": ["name", "age", "city"],
|
91
|
+
}
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def start_token(cls):
|
95
|
+
return "<format>"
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def end_token(cls):
|
99
|
+
return "</format>"
|
100
|
+
|
101
|
+
|
102
|
+
# Test cases
|
103
|
+
if __name__ == "__main__":
|
104
|
+
# Test formatting
|
105
|
+
person = PersonModel(name="John", age=30, city="Tokyo")
|
106
|
+
formatted = PersonModel.format(person)
|
107
|
+
expected_format = """<format>
|
108
|
+
name: John
|
109
|
+
30 is the age
|
110
|
+
lives in Tokyo
|
111
|
+
</format>"""
|
112
|
+
assert (
|
113
|
+
formatted == expected_format
|
114
|
+
), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
115
|
+
print("Formatting test passed.")
|
116
|
+
|
117
|
+
# Test parsing
|
118
|
+
parsed = PersonModel.parse(formatted)
|
119
|
+
assert parsed == person, f"Parsing failed. Expected:\n{person}\nGot:\n{parsed}"
|
120
|
+
print("Parsing test passed.")
|
121
|
+
|
122
|
+
# Test round-trip
|
123
|
+
round_trip = PersonModel.parse(PersonModel.format(person))
|
124
|
+
assert (
|
125
|
+
round_trip == person
|
126
|
+
), f"Round-trip failed. Expected:\n{person}\nGot:\n{round_trip}"
|
127
|
+
print("Round-trip test passed.")
|
128
|
+
|
129
|
+
# Test with different values
|
130
|
+
person2 = PersonModel(name="Alice", age=25, city="New York")
|
131
|
+
formatted2 = PersonModel.format(person2)
|
132
|
+
parsed2 = PersonModel.parse(formatted2)
|
133
|
+
assert (
|
134
|
+
parsed2 == person2
|
135
|
+
), f"Parsing failed for different values. Expected:\n{person2}\nGot:\n{parsed2}"
|
136
|
+
print("Different values test passed.")
|
137
|
+
|
138
|
+
print("All tests passed successfully!")
|
139
|
+
|
140
|
+
class CodeFileModel(FormattingModel):
|
141
|
+
language: str
|
142
|
+
file_path: str
|
143
|
+
code: str
|
144
|
+
|
145
|
+
@classmethod
|
146
|
+
def format_spec(cls):
|
147
|
+
return {"template": "code_file_model\n$file_path\n```$language\n$code\n```"}
|
148
|
+
|
149
|
+
@classmethod
|
150
|
+
def parse_spec(cls):
|
151
|
+
return {
|
152
|
+
"template": "code_file_model\n$file_path\n```$language\n$code\n```",
|
153
|
+
"tokens": {
|
154
|
+
"language": "```",
|
155
|
+
"file_path": "code_file_model\n",
|
156
|
+
"code": "\n",
|
157
|
+
},
|
158
|
+
"field_order": ["file_path", "language", "code"],
|
159
|
+
}
|
160
|
+
|
161
|
+
@classmethod
|
162
|
+
def start_token(cls):
|
163
|
+
return "<format>"
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def end_token(cls):
|
167
|
+
return "</format>"
|
168
|
+
|
169
|
+
# Test formatting
|
170
|
+
|
171
|
+
code_file = CodeFileModel(
|
172
|
+
language="Python",
|
173
|
+
file_path="src/main.py",
|
174
|
+
code="def hello():\n print('Hello, World!')",
|
175
|
+
)
|
176
|
+
formatted = CodeFileModel.format(code_file)
|
177
|
+
expected_format = """<format>
|
178
|
+
code_file_model
|
179
|
+
src/main.py
|
180
|
+
```Python
|
181
|
+
def hello():
|
182
|
+
print('Hello, World!')
|
183
|
+
```
|
184
|
+
</format>"""
|
185
|
+
assert (
|
186
|
+
formatted == expected_format
|
187
|
+
), f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
188
|
+
print("Formatting test passed.")
|
189
|
+
|
190
|
+
# Test parsing
|
191
|
+
parsed = CodeFileModel.parse(formatted)
|
192
|
+
assert (
|
193
|
+
parsed == code_file
|
194
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
195
|
+
print("Parsing test passed.")
|
196
|
+
|
197
|
+
# Test round-trip
|
198
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
199
|
+
assert (
|
200
|
+
round_trip == code_file
|
201
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
202
|
+
print("Round-trip test passed.")
|
203
|
+
|
204
|
+
# Test with different values
|
205
|
+
code_file2 = CodeFileModel(
|
206
|
+
language="JavaScript",
|
207
|
+
file_path="src/app.js",
|
208
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
209
|
+
)
|
210
|
+
formatted2 = CodeFileModel.format(code_file2)
|
211
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
212
|
+
assert (
|
213
|
+
parsed2 == code_file2
|
214
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
215
|
+
print("Different values test passed.")
|
216
|
+
|
217
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,200 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from pydantic import BaseModel
|
3
|
+
from string import Template
|
4
|
+
from typing import Dict, Any
|
5
|
+
|
6
|
+
class FormattingModel(BaseModel, ABC):
|
7
|
+
@classmethod
|
8
|
+
@abstractmethod
|
9
|
+
def format_spec(cls) -> Dict[str, Any]:
|
10
|
+
pass
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
@abstractmethod
|
14
|
+
def parse_spec(cls) -> Dict[str, Any]:
|
15
|
+
pass
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
@abstractmethod
|
19
|
+
def start_token(cls) -> str:
|
20
|
+
pass
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
@abstractmethod
|
24
|
+
def end_token(cls) -> str:
|
25
|
+
pass
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def format(cls, instance: 'FormattingModel') -> str:
|
29
|
+
spec = cls.format_spec()
|
30
|
+
template = Template(spec['template'])
|
31
|
+
formatted = template.substitute(instance.dict())
|
32
|
+
return f"{cls.start_token()}\n{formatted}\n{cls.end_token()}"
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def parse(cls, formatted_string: str) -> 'FormattingModel':
|
36
|
+
spec = cls.parse_spec()
|
37
|
+
lines = formatted_string.strip().split('\n')
|
38
|
+
|
39
|
+
if lines[0] != cls.start_token() or lines[-1] != cls.end_token():
|
40
|
+
raise ValueError("Invalid start or end token")
|
41
|
+
|
42
|
+
content = '\n'.join(lines[1:-1])
|
43
|
+
|
44
|
+
parsed_data = {}
|
45
|
+
for field, token in spec['tokens'].items():
|
46
|
+
if token:
|
47
|
+
parts = content.split(token, 1)
|
48
|
+
if len(parts) > 1:
|
49
|
+
value = parts[1].split('\n')[0].strip()
|
50
|
+
else:
|
51
|
+
raise ValueError(f"Could not find token '{token}' for field '{field}'")
|
52
|
+
else:
|
53
|
+
# For fields without a specific token (like age in our example)
|
54
|
+
value = content.split('\n')[spec['field_order'].index(field)].split(' ')[0].strip()
|
55
|
+
|
56
|
+
# Convert to appropriate type based on the model's field types
|
57
|
+
field_type = cls.__annotations__[field]
|
58
|
+
if field_type == int:
|
59
|
+
parsed_data[field] = int(value)
|
60
|
+
elif field_type == float:
|
61
|
+
parsed_data[field] = float(value)
|
62
|
+
else:
|
63
|
+
parsed_data[field] = value
|
64
|
+
|
65
|
+
return cls(**parsed_data)
|
66
|
+
|
67
|
+
class PersonModel(FormattingModel):
|
68
|
+
name: str
|
69
|
+
age: int
|
70
|
+
city: str
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def format_spec(cls):
|
74
|
+
return {
|
75
|
+
'template': 'name: $name\n$age is the age\nlives in $city'
|
76
|
+
}
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def parse_spec(cls):
|
80
|
+
return {
|
81
|
+
'template': 'name: $name\n$age is the age\nlives in $city',
|
82
|
+
'tokens': {
|
83
|
+
'name': 'name: ',
|
84
|
+
'age': '',
|
85
|
+
'city': 'lives in '
|
86
|
+
},
|
87
|
+
'field_order': ['name', 'age', 'city']
|
88
|
+
}
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def start_token(cls):
|
92
|
+
return '<format>'
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def end_token(cls):
|
96
|
+
return '</format>'
|
97
|
+
|
98
|
+
# Test cases
|
99
|
+
if __name__ == "__main__":
|
100
|
+
# Test formatting
|
101
|
+
person = PersonModel(name="John", age=30, city="Tokyo")
|
102
|
+
formatted = PersonModel.format(person)
|
103
|
+
expected_format = """<format>
|
104
|
+
name: John
|
105
|
+
30 is the age
|
106
|
+
lives in Tokyo
|
107
|
+
</format>"""
|
108
|
+
assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
109
|
+
print("Formatting test passed.")
|
110
|
+
|
111
|
+
# Test parsing
|
112
|
+
parsed = PersonModel.parse(formatted)
|
113
|
+
assert parsed == person, f"Parsing failed. Expected:\n{person}\nGot:\n{parsed}"
|
114
|
+
print("Parsing test passed.")
|
115
|
+
|
116
|
+
# Test round-trip
|
117
|
+
round_trip = PersonModel.parse(PersonModel.format(person))
|
118
|
+
assert round_trip == person, f"Round-trip failed. Expected:\n{person}\nGot:\n{round_trip}"
|
119
|
+
print("Round-trip test passed.")
|
120
|
+
|
121
|
+
# Test with different values
|
122
|
+
person2 = PersonModel(name="Alice", age=25, city="New York")
|
123
|
+
formatted2 = PersonModel.format(person2)
|
124
|
+
parsed2 = PersonModel.parse(formatted2)
|
125
|
+
assert parsed2 == person2, f"Parsing failed for different values. Expected:\n{person2}\nGot:\n{parsed2}"
|
126
|
+
print("Different values test passed.")
|
127
|
+
|
128
|
+
print("All tests passed successfully!")
|
129
|
+
|
130
|
+
class CodeFileModel(FormattingModel):
|
131
|
+
language: str
|
132
|
+
file_path: str
|
133
|
+
code: str
|
134
|
+
|
135
|
+
@classmethod
|
136
|
+
def format_spec(cls):
|
137
|
+
return {
|
138
|
+
'template': 'code_file_model\n$file_path\n```$language\n$code\n```'
|
139
|
+
}
|
140
|
+
|
141
|
+
@classmethod
|
142
|
+
def parse_spec(cls):
|
143
|
+
return {
|
144
|
+
'template': 'code_file_model\n$file_path\n```$language\n$code\n```',
|
145
|
+
'tokens': {
|
146
|
+
'language': '```',
|
147
|
+
'file_path': 'code_file_model\n',
|
148
|
+
'code': '\n'
|
149
|
+
},
|
150
|
+
'field_order': ['file_path', 'language', 'code']
|
151
|
+
}
|
152
|
+
|
153
|
+
@classmethod
|
154
|
+
def start_token(cls):
|
155
|
+
return '<format>'
|
156
|
+
|
157
|
+
@classmethod
|
158
|
+
def end_token(cls):
|
159
|
+
return '</format>'
|
160
|
+
|
161
|
+
# Test formatting
|
162
|
+
code_file = CodeFileModel(
|
163
|
+
language="Python",
|
164
|
+
file_path="src/main.py",
|
165
|
+
code="def hello():\n print('Hello, World!')"
|
166
|
+
)
|
167
|
+
formatted = CodeFileModel.format(code_file)
|
168
|
+
expected_format = """<format>
|
169
|
+
code_file_model
|
170
|
+
src/main.py
|
171
|
+
```Python
|
172
|
+
def hello():
|
173
|
+
print('Hello, World!')
|
174
|
+
```
|
175
|
+
</format>"""
|
176
|
+
assert formatted == expected_format, f"Formatting failed. Expected:\n{expected_format}\nGot:\n{formatted}"
|
177
|
+
print("Formatting test passed.")
|
178
|
+
|
179
|
+
# Test parsing
|
180
|
+
parsed = CodeFileModel.parse(formatted)
|
181
|
+
assert parsed == code_file, f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
182
|
+
print("Parsing test passed.")
|
183
|
+
|
184
|
+
# Test round-trip
|
185
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
186
|
+
assert round_trip == code_file, f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
187
|
+
print("Round-trip test passed.")
|
188
|
+
|
189
|
+
# Test with different values
|
190
|
+
code_file2 = CodeFileModel(
|
191
|
+
language="JavaScript",
|
192
|
+
file_path="src/app.js",
|
193
|
+
code="function greet() {\n console.log('Hello, World!');\n}"
|
194
|
+
)
|
195
|
+
formatted2 = CodeFileModel.format(code_file2)
|
196
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
197
|
+
assert parsed2 == code_file2, f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
198
|
+
print("Different values test passed.")
|
199
|
+
|
200
|
+
print("All tests passed successfully!")
|