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,130 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from lxml import etree
|
4
|
+
|
5
|
+
from langroid.pydantic_v1 import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class FormattingModel(BaseModel, ABC):
|
9
|
+
@classmethod
|
10
|
+
@abstractmethod
|
11
|
+
def root_element(cls) -> str:
|
12
|
+
pass
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def format(cls, instance: "FormattingModel") -> str:
|
16
|
+
root = etree.Element(cls.root_element())
|
17
|
+
for field_name, field in cls.__fields__.items():
|
18
|
+
value = getattr(instance, field_name)
|
19
|
+
elem = etree.SubElement(root, field_name)
|
20
|
+
xml_type = field.field_info.extra.get("xml", {}).get("type", "text")
|
21
|
+
if xml_type == "cdata":
|
22
|
+
elem.text = etree.CDATA(str(value))
|
23
|
+
else:
|
24
|
+
elem.text = str(value)
|
25
|
+
return etree.tostring(root, encoding="unicode", pretty_print=True)
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
29
|
+
root = etree.fromstring(formatted_string.encode("utf-8"))
|
30
|
+
if root.tag != cls.root_element():
|
31
|
+
raise ValueError(
|
32
|
+
f"Invalid root element: expected {cls.root_element()}, got {root.tag}"
|
33
|
+
)
|
34
|
+
|
35
|
+
data = {}
|
36
|
+
for field_name, field in cls.__fields__.items():
|
37
|
+
elem = root.find(field_name)
|
38
|
+
if elem is None:
|
39
|
+
raise ValueError(f"Missing field: {field_name}")
|
40
|
+
xml_type = field.field_info.extra.get("xml", {}).get("type", "text")
|
41
|
+
if xml_type == "cdata":
|
42
|
+
data[field_name] = elem.text
|
43
|
+
elif xml_type == "text":
|
44
|
+
data[field_name] = elem.text.strip() if elem.text else ""
|
45
|
+
else:
|
46
|
+
# Handle other field types as needed
|
47
|
+
data[field_name] = elem.text
|
48
|
+
|
49
|
+
return cls(**data)
|
50
|
+
|
51
|
+
|
52
|
+
from langroid.pydantic_v1 import Field
|
53
|
+
|
54
|
+
|
55
|
+
def XmlField(*args, xml_type: str = "text", **kwargs):
|
56
|
+
return Field(*args, xml={"type": xml_type}, **kwargs)
|
57
|
+
|
58
|
+
|
59
|
+
class CodeFileModel(FormattingModel):
|
60
|
+
language: str = XmlField(xml_type="text")
|
61
|
+
file_path: str = XmlField(xml_type="text")
|
62
|
+
code: str = XmlField(xml_type="cdata")
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def root_element(cls):
|
66
|
+
return "code_file_model"
|
67
|
+
|
68
|
+
|
69
|
+
# Test cases
|
70
|
+
if __name__ == "__main__":
|
71
|
+
# Test formatting
|
72
|
+
code_file = CodeFileModel(
|
73
|
+
language="Python",
|
74
|
+
file_path="src/main.py",
|
75
|
+
code="def hello():\n print('Hello, World!')",
|
76
|
+
)
|
77
|
+
formatted = CodeFileModel.format(code_file)
|
78
|
+
print("Formatted XML:")
|
79
|
+
print(formatted)
|
80
|
+
|
81
|
+
# Test parsing
|
82
|
+
parsed = CodeFileModel.parse(formatted)
|
83
|
+
assert (
|
84
|
+
parsed == code_file
|
85
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
86
|
+
print("Parsing test passed.")
|
87
|
+
|
88
|
+
# Test round-trip
|
89
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
90
|
+
assert (
|
91
|
+
round_trip == code_file
|
92
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
93
|
+
print("Round-trip test passed.")
|
94
|
+
|
95
|
+
# Test with different values
|
96
|
+
code_file2 = CodeFileModel(
|
97
|
+
language="JavaScript",
|
98
|
+
file_path="src/app.js",
|
99
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
100
|
+
)
|
101
|
+
formatted2 = CodeFileModel.format(code_file2)
|
102
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
103
|
+
assert (
|
104
|
+
parsed2 == code_file2
|
105
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
106
|
+
print("Different values test passed.")
|
107
|
+
|
108
|
+
# Test tolerant parsing
|
109
|
+
tolerant_input = """
|
110
|
+
<code_file_model>
|
111
|
+
<language> Python </language>
|
112
|
+
<file_path> src/main.py </file_path>
|
113
|
+
<code><![CDATA[
|
114
|
+
def hello():
|
115
|
+
print('Hello, World!')
|
116
|
+
]]></code>
|
117
|
+
</code_file_model>
|
118
|
+
"""
|
119
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
120
|
+
expected_tolerant = CodeFileModel(
|
121
|
+
language="Python",
|
122
|
+
file_path="src/main.py",
|
123
|
+
code="\ndef hello():\n print('Hello, World!')\n ",
|
124
|
+
)
|
125
|
+
assert (
|
126
|
+
parsed_tolerant == expected_tolerant
|
127
|
+
), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
128
|
+
print("Tolerant parsing test passed.")
|
129
|
+
|
130
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,113 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from lxml import etree
|
4
|
+
|
5
|
+
from langroid.pydantic_v1 import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class FormattingModel(BaseModel, ABC):
|
9
|
+
@classmethod
|
10
|
+
@abstractmethod
|
11
|
+
def root_element(cls) -> str:
|
12
|
+
pass
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def format(cls, instance: "FormattingModel") -> str:
|
16
|
+
root = etree.Element(cls.root_element())
|
17
|
+
for name, value in instance.dict().items():
|
18
|
+
elem = etree.SubElement(root, name)
|
19
|
+
elem.text = str(value)
|
20
|
+
return etree.tostring(root, encoding="unicode", pretty_print=True)
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
24
|
+
root = etree.fromstring(formatted_string.encode("utf-8"))
|
25
|
+
if root.tag != cls.root_element():
|
26
|
+
raise ValueError(
|
27
|
+
f"Invalid root element: expected {cls.root_element()}, got {root.tag}"
|
28
|
+
)
|
29
|
+
|
30
|
+
data = {}
|
31
|
+
for elem in root:
|
32
|
+
if elem.tag == "code":
|
33
|
+
# Preserve whitespace for 'code' field
|
34
|
+
data[elem.tag] = elem.text if elem.text else ""
|
35
|
+
else:
|
36
|
+
# Strip whitespace for other fields
|
37
|
+
data[elem.tag] = elem.text.strip() if elem.text else ""
|
38
|
+
|
39
|
+
return cls(**data)
|
40
|
+
|
41
|
+
|
42
|
+
class CodeFileModel(FormattingModel):
|
43
|
+
language: str
|
44
|
+
file_path: str
|
45
|
+
code: str
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def root_element(cls):
|
49
|
+
return "code_file_model"
|
50
|
+
|
51
|
+
|
52
|
+
# Test cases
|
53
|
+
if __name__ == "__main__":
|
54
|
+
# Test formatting
|
55
|
+
code_file = CodeFileModel(
|
56
|
+
language="Python",
|
57
|
+
file_path="src/main.py",
|
58
|
+
code="def hello():\n print('Hello, World!')",
|
59
|
+
)
|
60
|
+
formatted = CodeFileModel.format(code_file)
|
61
|
+
print("Formatted XML:")
|
62
|
+
print(formatted)
|
63
|
+
|
64
|
+
# Test parsing
|
65
|
+
parsed = CodeFileModel.parse(formatted)
|
66
|
+
assert (
|
67
|
+
parsed == code_file
|
68
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
69
|
+
print("Parsing test passed.")
|
70
|
+
|
71
|
+
# Test round-trip
|
72
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
73
|
+
assert (
|
74
|
+
round_trip == code_file
|
75
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
76
|
+
print("Round-trip test passed.")
|
77
|
+
|
78
|
+
# Test with different values
|
79
|
+
code_file2 = CodeFileModel(
|
80
|
+
language="JavaScript",
|
81
|
+
file_path="src/app.js",
|
82
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
83
|
+
)
|
84
|
+
formatted2 = CodeFileModel.format(code_file2)
|
85
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
86
|
+
assert (
|
87
|
+
parsed2 == code_file2
|
88
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
89
|
+
print("Different values test passed.")
|
90
|
+
|
91
|
+
# Test tolerant parsing
|
92
|
+
tolerant_input = """
|
93
|
+
<code_file_model>
|
94
|
+
<language> Python </language>
|
95
|
+
<file_path> src/main.py </file_path>
|
96
|
+
<code>
|
97
|
+
def hello():
|
98
|
+
print('Hello, World!')
|
99
|
+
</code>
|
100
|
+
</code_file_model>
|
101
|
+
"""
|
102
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
103
|
+
expected_tolerant = CodeFileModel(
|
104
|
+
language="Python",
|
105
|
+
file_path="src/main.py",
|
106
|
+
code="\ndef hello():\n print('Hello, World!')\n ",
|
107
|
+
)
|
108
|
+
assert (
|
109
|
+
parsed_tolerant == expected_tolerant
|
110
|
+
), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
111
|
+
print("Tolerant parsing test passed.")
|
112
|
+
|
113
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from lxml import etree
|
4
|
+
|
5
|
+
from langroid.pydantic_v1 import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class FormattingModel(BaseModel, ABC):
|
9
|
+
@classmethod
|
10
|
+
@abstractmethod
|
11
|
+
def root_element(cls) -> str:
|
12
|
+
pass
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
16
|
+
parser = etree.XMLParser(strip_cdata=False)
|
17
|
+
root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)
|
18
|
+
if root.tag != cls.root_element():
|
19
|
+
raise ValueError(
|
20
|
+
f"Invalid root element: expected {cls.root_element()}, got {root.tag}"
|
21
|
+
)
|
22
|
+
|
23
|
+
data = {}
|
24
|
+
for elem in root:
|
25
|
+
if elem.tag == "code":
|
26
|
+
# Extract CDATA content
|
27
|
+
data[elem.tag] = elem.text if elem.text else ""
|
28
|
+
else:
|
29
|
+
# Strip whitespace for other fields
|
30
|
+
data[elem.tag] = elem.text.strip() if elem.text else ""
|
31
|
+
|
32
|
+
return cls(**data)
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def format(cls, instance: "FormattingModel") -> str:
|
36
|
+
root = etree.Element(cls.root_element())
|
37
|
+
for name, value in instance.dict().items():
|
38
|
+
elem = etree.SubElement(root, name)
|
39
|
+
if name == "code":
|
40
|
+
elem.text = etree.CDATA(value)
|
41
|
+
else:
|
42
|
+
elem.text = str(value)
|
43
|
+
return etree.tostring(root, encoding="unicode", pretty_print=True)
|
44
|
+
|
45
|
+
|
46
|
+
class CodeFileModel(FormattingModel):
|
47
|
+
language: str
|
48
|
+
file_path: str
|
49
|
+
code: str
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def root_element(cls):
|
53
|
+
return "code_file_model"
|
54
|
+
|
55
|
+
|
56
|
+
# Test cases
|
57
|
+
if __name__ == "__main__":
|
58
|
+
# Test formatting
|
59
|
+
code_file = CodeFileModel(
|
60
|
+
language="Python",
|
61
|
+
file_path="src/main.py",
|
62
|
+
code="def hello():\n print('Hello, World!')",
|
63
|
+
)
|
64
|
+
formatted = CodeFileModel.format(code_file)
|
65
|
+
print("Formatted XML:")
|
66
|
+
print(formatted)
|
67
|
+
|
68
|
+
# Test parsing
|
69
|
+
parsed = CodeFileModel.parse(formatted)
|
70
|
+
assert (
|
71
|
+
parsed == code_file
|
72
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
73
|
+
print("Parsing test passed.")
|
74
|
+
|
75
|
+
# Test round-trip
|
76
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
77
|
+
assert (
|
78
|
+
round_trip == code_file
|
79
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
80
|
+
print("Round-trip test passed.")
|
81
|
+
|
82
|
+
# Test with different values
|
83
|
+
code_file2 = CodeFileModel(
|
84
|
+
language="JavaScript",
|
85
|
+
file_path="src/app.js",
|
86
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
87
|
+
)
|
88
|
+
formatted2 = CodeFileModel.format(code_file2)
|
89
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
90
|
+
assert (
|
91
|
+
parsed2 == code_file2
|
92
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
93
|
+
print("Different values test passed.")
|
94
|
+
|
95
|
+
# Test tolerant parsing
|
96
|
+
tolerant_input = """
|
97
|
+
<code_file_model>
|
98
|
+
<language> Python </language>
|
99
|
+
<file_path> src/main.py </file_path>
|
100
|
+
<code>
|
101
|
+
def hello():
|
102
|
+
print('Hello, World!')
|
103
|
+
</code>
|
104
|
+
</code_file_model>
|
105
|
+
"""
|
106
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
107
|
+
expected_tolerant = CodeFileModel(
|
108
|
+
language="Python",
|
109
|
+
file_path="src/main.py",
|
110
|
+
code="\ndef hello():\n print('Hello, World!')\n ",
|
111
|
+
)
|
112
|
+
assert (
|
113
|
+
parsed_tolerant == expected_tolerant
|
114
|
+
), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
115
|
+
print("Tolerant parsing test passed.")
|
116
|
+
|
117
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,164 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import get_type_hints
|
3
|
+
|
4
|
+
from lxml import etree
|
5
|
+
|
6
|
+
from langroid.pydantic_v1 import BaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class FormattingModel(BaseModel, ABC):
|
10
|
+
@classmethod
|
11
|
+
@abstractmethod
|
12
|
+
def root_element(cls) -> str:
|
13
|
+
pass
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def parse(cls, formatted_string: str) -> "FormattingModel":
|
17
|
+
parser = etree.XMLParser(strip_cdata=False)
|
18
|
+
root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)
|
19
|
+
if root.tag != cls.root_element():
|
20
|
+
raise ValueError(
|
21
|
+
f"Invalid root element: expected {cls.root_element()}, got {root.tag}"
|
22
|
+
)
|
23
|
+
|
24
|
+
data = {}
|
25
|
+
type_hints = get_type_hints(cls)
|
26
|
+
for elem in root:
|
27
|
+
field_type = type_hints.get(elem.tag, str)
|
28
|
+
if elem.tag == "code":
|
29
|
+
data[elem.tag] = elem.text if elem.text else ""
|
30
|
+
else:
|
31
|
+
# Parse according to the field type
|
32
|
+
value = elem.text.strip() if elem.text else ""
|
33
|
+
if field_type == int:
|
34
|
+
data[elem.tag] = int(value)
|
35
|
+
elif field_type == float:
|
36
|
+
data[elem.tag] = float(value)
|
37
|
+
elif field_type == bool:
|
38
|
+
data[elem.tag] = value.lower() in ("true", "1", "yes")
|
39
|
+
else:
|
40
|
+
data[elem.tag] = value
|
41
|
+
|
42
|
+
return cls(**data)
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def instructions(cls) -> str:
|
46
|
+
# Get only the fields defined in the model
|
47
|
+
fields = list(cls.__fields__.keys())
|
48
|
+
|
49
|
+
# Preamble with placeholder variables
|
50
|
+
preamble = "Placeholders:\n"
|
51
|
+
for field in fields:
|
52
|
+
preamble += f"{field.upper()} = [value for {field}]\n"
|
53
|
+
|
54
|
+
# Formatting example
|
55
|
+
example = f"Formatting example:\n\n<{cls.root_element()}>\n"
|
56
|
+
for field in fields:
|
57
|
+
if field == "code":
|
58
|
+
example += f" <{field}><![CDATA[{{{field.upper()}}}]]></{field}>\n"
|
59
|
+
else:
|
60
|
+
example += f" <{field}>{{{field.upper()}}}</{field}>\n"
|
61
|
+
example += f"</{cls.root_element()}>"
|
62
|
+
|
63
|
+
return f"{preamble}\n{example}"
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def format(cls, instance: "FormattingModel") -> str:
|
67
|
+
root = etree.Element(cls.root_element())
|
68
|
+
for name, value in instance.dict().items():
|
69
|
+
elem = etree.SubElement(root, name)
|
70
|
+
if name == "code":
|
71
|
+
elem.text = etree.CDATA(str(value))
|
72
|
+
else:
|
73
|
+
elem.text = str(value)
|
74
|
+
return etree.tostring(root, encoding="unicode", pretty_print=True)
|
75
|
+
|
76
|
+
|
77
|
+
class CodeFileModel(FormattingModel):
|
78
|
+
language: str
|
79
|
+
file_path: str
|
80
|
+
code: str
|
81
|
+
line_count: int
|
82
|
+
average_line_length: float
|
83
|
+
is_executable: bool
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
def root_element(cls):
|
87
|
+
return "code_file_model"
|
88
|
+
|
89
|
+
|
90
|
+
if __name__ == "__main__":
|
91
|
+
# Test formatting
|
92
|
+
code_file = CodeFileModel(
|
93
|
+
language="Python",
|
94
|
+
file_path="src/main.py",
|
95
|
+
code="def hello():\n print('Hello, World!')",
|
96
|
+
line_count=2,
|
97
|
+
average_line_length=20.5,
|
98
|
+
is_executable=True,
|
99
|
+
)
|
100
|
+
formatted = CodeFileModel.format(code_file)
|
101
|
+
print("Formatted XML:")
|
102
|
+
print(formatted)
|
103
|
+
|
104
|
+
# Test parsing
|
105
|
+
parsed = CodeFileModel.parse(formatted)
|
106
|
+
assert (
|
107
|
+
parsed == code_file
|
108
|
+
), f"Parsing failed. Expected:\n{code_file}\nGot:\n{parsed}"
|
109
|
+
print("Parsing test passed.")
|
110
|
+
|
111
|
+
# Test round-trip
|
112
|
+
round_trip = CodeFileModel.parse(CodeFileModel.format(code_file))
|
113
|
+
assert (
|
114
|
+
round_trip == code_file
|
115
|
+
), f"Round-trip failed. Expected:\n{code_file}\nGot:\n{round_trip}"
|
116
|
+
print("Round-trip test passed.")
|
117
|
+
|
118
|
+
# Test with different values
|
119
|
+
code_file2 = CodeFileModel(
|
120
|
+
language="JavaScript",
|
121
|
+
file_path="src/app.js",
|
122
|
+
code="function greet() {\n console.log('Hello, World!');\n}",
|
123
|
+
line_count=3,
|
124
|
+
average_line_length=15.33,
|
125
|
+
is_executable=False,
|
126
|
+
)
|
127
|
+
formatted2 = CodeFileModel.format(code_file2)
|
128
|
+
parsed2 = CodeFileModel.parse(formatted2)
|
129
|
+
assert (
|
130
|
+
parsed2 == code_file2
|
131
|
+
), f"Parsing failed for different values. Expected:\n{code_file2}\nGot:\n{parsed2}"
|
132
|
+
print("Different values test passed.")
|
133
|
+
|
134
|
+
# Test tolerant parsing
|
135
|
+
tolerant_input = """
|
136
|
+
<code_file_model>
|
137
|
+
<language> Python </language>
|
138
|
+
<file_path> src/main.py </file_path>
|
139
|
+
<code><![CDATA[
|
140
|
+
def hello():
|
141
|
+
print('Hello, World!')
|
142
|
+
]]></code>
|
143
|
+
<line_count> 2 </line_count>
|
144
|
+
<average_line_length> 20.5 </average_line_length>
|
145
|
+
<is_executable> True </is_executable>
|
146
|
+
</code_file_model>
|
147
|
+
"""
|
148
|
+
parsed_tolerant = CodeFileModel.parse(tolerant_input)
|
149
|
+
expected_tolerant = CodeFileModel(
|
150
|
+
language="Python",
|
151
|
+
file_path="src/main.py",
|
152
|
+
code="\ndef hello():\n print('Hello, World!')\n ",
|
153
|
+
line_count=2,
|
154
|
+
average_line_length=20.5,
|
155
|
+
is_executable=True,
|
156
|
+
)
|
157
|
+
assert (
|
158
|
+
parsed_tolerant == expected_tolerant
|
159
|
+
), f"Tolerant parsing failed. Expected:\n{expected_tolerant}\nGot:\n{parsed_tolerant}"
|
160
|
+
print("Tolerant parsing test passed.")
|
161
|
+
|
162
|
+
print(CodeFileModel.instructions())
|
163
|
+
|
164
|
+
print("All tests passed successfully!")
|
@@ -0,0 +1,165 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
from typing import Any, Dict, List
|
3
|
+
|
4
|
+
from pyparsing import (
|
5
|
+
LineEnd,
|
6
|
+
Literal,
|
7
|
+
ParserElement,
|
8
|
+
)
|
9
|
+
|
10
|
+
from langroid.agent.tool_message import ToolMessage
|
11
|
+
|
12
|
+
|
13
|
+
class GenericTool(ToolMessage):
|
14
|
+
"""
|
15
|
+
Abstract class for a tool whose format is defined by a grammar,
|
16
|
+
and not necessarily JSON-based.
|
17
|
+
Especially useful for tools where we need an LLM to return code.
|
18
|
+
Most LLMs, especially weaker ones, have significant issues
|
19
|
+
(related to unescaped newlines, quotes, etc) when returning code within JSON.
|
20
|
+
"""
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
@abstractmethod
|
24
|
+
def define_grammar(cls):
|
25
|
+
"""Define the grammar for the specific tool."""
|
26
|
+
pass
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def create_parser(cls):
|
30
|
+
"""Create a parser based on the defined grammar."""
|
31
|
+
grammar = cls.define_grammar()
|
32
|
+
# Use the grammar to create and return a parser
|
33
|
+
return grammar
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def parse(cls, string) -> Dict[str, Any]:
|
37
|
+
parser = cls.create_parser()
|
38
|
+
try:
|
39
|
+
result = parser.parseString(string, parseAll=True)
|
40
|
+
return {
|
41
|
+
name: result[name]
|
42
|
+
for name in result.keys()
|
43
|
+
if name and not name.startswith("_")
|
44
|
+
}
|
45
|
+
except Exception as e:
|
46
|
+
print(f"Parsing failed: {e}")
|
47
|
+
return {}
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def instructions(cls) -> str:
|
51
|
+
preamble = "Preamble:\n"
|
52
|
+
for field, field_info in cls.__dict__["__fields__"].items():
|
53
|
+
preamble += (
|
54
|
+
f"<{field}> denotes the value of the `{field}` field "
|
55
|
+
f"(type: {field_info.type_})\n"
|
56
|
+
)
|
57
|
+
|
58
|
+
parser = cls.create_parser()
|
59
|
+
|
60
|
+
def format_element(element):
|
61
|
+
if isinstance(element, Literal):
|
62
|
+
return element.match
|
63
|
+
elif hasattr(element, "resultsName") and element.resultsName:
|
64
|
+
return f"<{element.resultsName}>"
|
65
|
+
elif isinstance(element, LineEnd):
|
66
|
+
return "\n"
|
67
|
+
return ""
|
68
|
+
|
69
|
+
def traverse_parser(parser_element):
|
70
|
+
if isinstance(parser_element, ParserElement):
|
71
|
+
if hasattr(parser_element, "exprs"):
|
72
|
+
return "".join(
|
73
|
+
traverse_parser(expr) for expr in parser_element.exprs
|
74
|
+
)
|
75
|
+
else:
|
76
|
+
return format_element(parser_element)
|
77
|
+
return str(parser_element)
|
78
|
+
|
79
|
+
template = traverse_parser(parser)
|
80
|
+
|
81
|
+
return f"{preamble}\nFormatted Example:\n{template.strip()}"
|
82
|
+
|
83
|
+
@classmethod
|
84
|
+
def parse(cls, string) -> Dict[str, Any]:
|
85
|
+
parser = cls.create_parser()
|
86
|
+
try:
|
87
|
+
result = parser.parseString(string, parseAll=True)
|
88
|
+
return {
|
89
|
+
name: result[name]
|
90
|
+
for name in result.keys()
|
91
|
+
if name and not name.startswith("_")
|
92
|
+
}
|
93
|
+
except Exception as e:
|
94
|
+
print(f"Parsing failed: {e}")
|
95
|
+
return {}
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def format(cls, instance) -> str:
|
99
|
+
parser = cls.create_parser()
|
100
|
+
|
101
|
+
def format_element(element):
|
102
|
+
if isinstance(element, Literal):
|
103
|
+
return element.match
|
104
|
+
elif hasattr(element, "resultsName") and element.resultsName:
|
105
|
+
return getattr(instance, element.resultsName, "")
|
106
|
+
elif isinstance(element, LineEnd):
|
107
|
+
return "\n"
|
108
|
+
return ""
|
109
|
+
|
110
|
+
def traverse_parser(parser_element):
|
111
|
+
if isinstance(parser_element, ParserElement):
|
112
|
+
if hasattr(parser_element, "exprs"):
|
113
|
+
return "".join(
|
114
|
+
traverse_parser(expr) for expr in parser_element.exprs
|
115
|
+
)
|
116
|
+
else:
|
117
|
+
return format_element(parser_element)
|
118
|
+
return str(parser_element)
|
119
|
+
|
120
|
+
formatted_string = traverse_parser(parser)
|
121
|
+
|
122
|
+
return formatted_string.strip()
|
123
|
+
|
124
|
+
@classmethod
|
125
|
+
def from_string(cls, input_string: str) -> "CodeFileTool":
|
126
|
+
"""Parse a string into a CodeFileTool object, using the TEMPLATE."""
|
127
|
+
parsed_data = cls.parse(input_string)
|
128
|
+
if parsed_data:
|
129
|
+
return cls(**parsed_data)
|
130
|
+
raise ValueError("Invalid input string format")
|
131
|
+
|
132
|
+
@classmethod
|
133
|
+
def to_string(cls, instance) -> str:
|
134
|
+
"""Convert a CodeFileTool object to a string, using the TEMPLATE."""
|
135
|
+
return cls.format(instance)
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def find_candidates(cls, input_str: str) -> List[str]:
|
139
|
+
"""
|
140
|
+
Find all possible (top-level) candidates for
|
141
|
+
CodeFileTool in the input string.
|
142
|
+
"""
|
143
|
+
parser = cls.create_parser()
|
144
|
+
candidates = []
|
145
|
+
|
146
|
+
for tokens, start, end in parser.scanString(input_str):
|
147
|
+
candidates.append(input_str[start:end])
|
148
|
+
|
149
|
+
return candidates
|
150
|
+
|
151
|
+
def __str__(self):
|
152
|
+
return self.to_string()
|
153
|
+
|
154
|
+
# def __repr__(self) -> str:
|
155
|
+
# class_name = self.__class__.__name__
|
156
|
+
# attributes = []
|
157
|
+
# for key, value in self.__dict__.items():
|
158
|
+
# if not key.startswith('_'): # Skip private attributes
|
159
|
+
# if isinstance(value, str):
|
160
|
+
# # Escape quotes and newlines in string values
|
161
|
+
# value_repr = f"'{value.replace('\\', '\\\\').replace(\"'\", \"\\'\").replace('\\n', '\\n')}'"
|
162
|
+
# else:
|
163
|
+
# value_repr = repr(value)
|
164
|
+
# attributes.append(f"{key}={value_repr}")
|
165
|
+
# return f"{class_name}({', '.join(attributes)})"
|