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,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)})"