Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b96f7a3e | ||
|
|
e2ee8cdbb6 | ||
|
|
8630e95d83 | ||
|
|
e07293e40f | ||
|
|
415c546f92 | ||
|
|
8446af7385 | ||
|
|
5a156a0126 | ||
|
|
a28efa7a33 | ||
|
|
bc5adb3fcb | ||
|
|
4bd1d911e0 | ||
|
|
41c9734c69 | ||
|
|
1a41298dd6 | ||
|
|
c618f8ed09 | ||
|
|
48932c5c64 | ||
|
|
4da20a5c33 | ||
|
|
b7f94ad419 | ||
|
|
2e45fb9e9c | ||
|
|
9775e94455 | ||
|
|
67416766e2 | ||
|
|
5c1be40048 | ||
|
|
49e87c7b7a | ||
|
|
11e7c288e0 | ||
|
|
909f4738f8 | ||
|
|
0148a64aa7 | ||
|
|
d8df27a4c7 | ||
|
|
6608dbcf3c | ||
|
|
ef83ddefc9 | ||
|
|
c19fd205e3 | ||
|
|
eec673f8db | ||
|
|
5e20b37e08 | ||
|
|
547418adf0 | ||
|
|
e161577bcb | ||
|
|
ba186d7c57 | ||
|
|
24c1e3fcb4 | ||
|
|
1f001080ff | ||
|
|
a189a682b6 | ||
|
|
a0299f791d | ||
|
|
79214fb22c | ||
|
|
775d3243e6 | ||
|
|
cd514ac099 | ||
|
|
6f352e1966 | ||
|
|
32458a653e | ||
|
|
b32fd6097f | ||
|
|
df2fc67e2c | ||
|
|
b318c30865 | ||
|
|
2610309b58 | ||
|
|
0b81158d01 | ||
|
|
4bcd2c582d | ||
|
|
51aac5f348 | ||
|
|
a437938526 | ||
|
|
c6bba6c7dd | ||
|
|
3103df8210 | ||
|
|
b1fd4aab8e | ||
|
|
26ca088c32 | ||
|
|
710eb93bbe | ||
|
|
fb1f53a176 | ||
|
|
138fd5e7d3 | ||
|
|
bd040f9a48 | ||
|
|
1f3f04d74b | ||
|
|
a7cff9d970 | ||
|
|
a1de8828ac | ||
|
|
7a1d4668d6 | ||
|
|
e20f390878 | ||
|
|
be18fe91ad | ||
|
|
6df2a231eb | ||
|
|
668eee7807 | ||
|
|
111974071f | ||
|
|
b193caec61 | ||
|
|
8994415242 | ||
|
|
7e6953dce6 | ||
|
|
0bcd68c434 | ||
|
|
74e0b23ab0 | ||
|
|
1044bea8dd | ||
|
|
081e34a835 | ||
|
|
9f22b4c3c3 | ||
|
|
4fb8ab0f2e | ||
|
|
8e5b7e0152 | ||
|
|
e40c655013 | ||
|
|
0ebda776cf | ||
|
|
879592f22e | ||
|
|
fd3efa6c70 | ||
|
|
b420645c20 | ||
|
|
211e9af847 | ||
|
|
58e1eb6d14 | ||
|
|
c72fe14f19 | ||
|
|
10162f7302 | ||
|
|
949e22fdf6 | ||
|
|
2355c15cd5 | ||
|
|
c5db441a07 | ||
|
|
9853a99bcb | ||
|
|
4d685520b1 | ||
|
|
a20035fd31 | ||
|
|
733ebf9283 | ||
|
|
595c0ef2d6 | ||
|
|
c86b58a956 | ||
|
|
bd1f39b379 | ||
|
|
71bee067b3 | ||
|
|
713183d2c7 | ||
|
|
1f5406ae55 | ||
|
|
a9e5aa114b | ||
|
|
67c1a6d129 | ||
|
|
91bbd87c2d | ||
|
|
e1f8ddbff3 | ||
|
|
562aeb1ea5 | ||
|
|
2444f12acc | ||
|
|
d0d58670ec | ||
|
|
5eb8231ee1 | ||
|
|
10775172d8 | ||
|
|
5c39cc4c3f | ||
|
|
79ac83ff47 | ||
|
|
6869d9e259 | ||
|
|
dba793411f | ||
|
|
4f095b5e90 | ||
|
|
b4b73d3ceb | ||
|
|
c818e170a0 | ||
|
|
88955740d7 | ||
|
|
1859d64ca7 | ||
|
|
5af7f44498 | ||
|
|
0fb9ce8e8c | ||
|
|
2d444e7159 | ||
|
|
6b2e327fc3 | ||
|
|
6276fe6685 | ||
|
|
3c67d6693f | ||
|
|
35a2139380 | ||
|
|
594e2f4f20 | ||
|
|
5f281591b4 | ||
|
|
829ffa0133 | ||
|
|
2c1b41ad57 | ||
|
|
91ede7cc72 | ||
|
|
4b03d2458c | ||
|
|
57ab90b26a | ||
|
|
eba9d2ca4b | ||
|
|
165cd2bc46 | ||
|
|
c4f1ae900f | ||
|
|
e090521bd2 | ||
|
|
094fc325c5 | ||
|
|
b557e60b8f | ||
|
|
97e62cde53 | ||
|
|
5b8ee5201e | ||
|
|
fb70b8ad32 | ||
|
|
67d1528895 | ||
|
|
c2aba8ce86 | ||
|
|
c5c0e13b64 | ||
|
|
6fa0ed3d8e | ||
|
|
8fdb499906 | ||
|
|
24b958d040 | ||
|
|
1c9a9973da | ||
|
|
e5dea8baa4 | ||
|
|
739da0859c | ||
|
|
3fdfa351bf | ||
|
|
6b04f5f691 | ||
|
|
5b246273f2 | ||
|
|
2c5634073f | ||
|
|
d8f483e361 | ||
|
|
e7b154ab13 | ||
|
|
e1f90c3c92 | ||
|
|
ee75d5018a | ||
|
|
808b3581db | ||
|
|
c39c0a0757 | ||
|
|
042acc1be0 | ||
|
|
9a12668822 | ||
|
|
b943ec8be1 | ||
|
|
65371a6967 | ||
|
|
3fd244f7f0 | ||
|
|
46d55b3167 | ||
|
|
1bf249188d | ||
|
|
38aeec0454 | ||
|
|
7ad7bf27ed | ||
|
|
647bd799f4 | ||
|
|
9b5b8d2ae6 | ||
|
|
b9a1066556 | ||
|
|
3d7af1d8d9 | ||
|
|
f591d8e03f | ||
|
|
1268ab1567 | ||
|
|
fa2af78c15 | ||
|
|
32a16dff4d | ||
|
|
5e8798be91 | ||
|
|
1ae7a2977d | ||
|
|
c4deedbd17 | ||
|
|
6cd8b99594 | ||
|
|
7ceb4147f8 | ||
|
|
155456912a | ||
|
|
ca8c0f3c07 | ||
|
|
43953507cc | ||
|
|
edce63ea67 | ||
|
|
aac6092151 | ||
|
|
39ddddbc7e | ||
|
|
3b9a325e3b | ||
|
|
ef975edb9d | ||
|
|
060a897a09 | ||
|
|
b687f76566 | ||
|
|
2c747e42d1 | ||
|
|
e48a0d1166 | ||
|
|
5d16354f56 | ||
|
|
5f577c5f7d | ||
|
|
07ee07835d | ||
|
|
c6e3b5149a | ||
|
|
29aa5d8e60 | ||
|
|
6e9cb22f2f | ||
|
|
f7497c653b | ||
|
|
cd3474dd06 | ||
|
|
10ecbba3af | ||
|
|
406d5b2204 | ||
|
|
ea379de711 | ||
|
|
2ee71ec281 | ||
|
|
f3e981f895 | ||
|
|
9adc3f2c6f | ||
|
|
baefc5ae51 | ||
|
|
14181ec917 | ||
|
|
825c3006ec | ||
|
|
c9476904bb | ||
|
|
e32487e244 | ||
|
|
4055d13838 | ||
|
|
95d6554bf7 | ||
|
|
7a8511d29b | ||
|
|
a1ff327d22 | ||
|
|
4329a12a8e | ||
|
|
f663710c0d | ||
|
|
9cb9c8b16c | ||
|
|
3ea6d0d804 | ||
|
|
641d090c08 | ||
|
|
f3c1e2df45 | ||
|
|
8c7fd96436 | ||
|
|
bcc70c0103 | ||
|
|
782ed359ef | ||
|
|
b00b7b52af | ||
|
|
7a40faaf8b | ||
|
|
e572cdb3d1 | ||
|
|
d182f3e3e9 | ||
|
|
6c45fbc8da | ||
|
|
286de473f1 | ||
|
|
005e6d1d88 | ||
|
|
197bb7bab5 | ||
|
|
d9b697d04c | ||
|
|
2e753cafa6 | ||
|
|
b5383f5b87 | ||
|
|
78ef1750de | ||
|
|
565120628f | ||
|
|
1592d4281f | ||
|
|
ec6d35c390 | ||
|
|
e55027f49e | ||
|
|
93544c9eaa | ||
|
|
68c2868674 | ||
|
|
fb5fc88d97 | ||
|
|
235761b01a | ||
|
|
d00d56b47d | ||
|
|
83b22d07f6 | ||
|
|
223a266035 | ||
|
|
18f7908af4 | ||
|
|
dece4a9bdb | ||
|
|
410f084449 | ||
|
|
bb496c1d9e | ||
|
|
f9d8965609 | ||
|
|
e3493c66d6 | ||
|
|
2b9a54fdb2 | ||
|
|
f24e945a84 | ||
|
|
c500ab83c4 | ||
|
|
e3ca8c966d | ||
|
|
52df442873 | ||
|
|
eacd566522 | ||
|
|
c946298245 | ||
|
|
9334046f33 | ||
|
|
4dab688f11 | ||
|
|
ee1881fcd9 | ||
|
|
b19934c1f2 | ||
|
|
8ba25bae69 | ||
|
|
81188c663b | ||
|
|
ad2ac6bf5e | ||
|
|
56af96ee79 | ||
|
|
01cfec0a43 | ||
|
|
4506610075 | ||
|
|
cf8331056f | ||
|
|
43042dca9c | ||
|
|
6485e67501 | ||
|
|
cf5600ed88 | ||
|
|
31dc32fe12 | ||
|
|
e838b7028c | ||
|
|
521025ce76 | ||
|
|
ea71d598ca | ||
|
|
5fde78def8 | ||
|
|
8e1913a873 | ||
|
|
799caf392f | ||
|
|
0c19f7334c | ||
|
|
e4de4b30b2 | ||
|
|
6aa5062d4d | ||
|
|
f330f46888 | ||
|
|
fe2ea28c7e | ||
|
|
a8017b68fa | ||
|
|
936e0f96ab | ||
|
|
37d3d6f383 | ||
|
|
5f07eb0566 | ||
|
|
f877ebb373 | ||
|
|
c00c955431 | ||
|
|
00a6e7185e | ||
|
|
faee05108a | ||
|
|
0a441a1d41 | ||
|
|
807b37d6cf | ||
|
|
5ae84a589d | ||
|
|
c1876aa8a9 | ||
|
|
be575e6932 | ||
|
|
39c0c921d9 | ||
|
|
c1fa76e47e | ||
|
|
fa42783c11 | ||
|
|
59f891fd19 | ||
|
|
ce5b851a18 | ||
|
|
096987e24b | ||
|
|
660ca3cdd1 | ||
|
|
1dc00ff4df | ||
|
|
eba5741112 | ||
|
|
2a8c7dad0b | ||
|
|
91ac2fbeff | ||
|
|
69089ae66c | ||
|
|
d26f280943 | ||
|
|
fba8823a32 | ||
|
|
b15a4d8435 | ||
|
|
bd8f2ac002 | ||
|
|
bb3e255746 | ||
|
|
0f3dfdf54d | ||
|
|
cafbbf98c1 | ||
|
|
7e4474863a | ||
|
|
1aee10520f | ||
|
|
306c3ba060 | ||
|
|
9411f922cb | ||
|
|
6eeaef4715 | ||
|
|
0bc0e0af1c | ||
|
|
9d83ec3754 | ||
|
|
c6987e5a90 | ||
|
|
94820d1197 | ||
|
|
b7b3756fb3 | ||
|
|
e9798cbc7b | ||
|
|
cd647e54bb | ||
|
|
f576d46420 | ||
|
|
9011f08d91 | ||
|
|
75c9b85439 | ||
|
|
2462ffbaa2 | ||
|
|
f0f8aee4e0 | ||
|
|
9b48224ea8 | ||
|
|
7733d77a80 | ||
|
|
63c2024344 | ||
|
|
766318db04 | ||
|
|
1b8d17fa77 | ||
|
|
5913a622e2 | ||
|
|
d074d75879 | ||
|
|
c6a806312a | ||
|
|
94b4059329 | ||
|
|
e4d2f77658 | ||
|
|
3db25afffa | ||
|
|
39f23391eb | ||
|
|
a51968b5e2 | ||
|
|
06e68e14fb | ||
|
|
c695fb1975 | ||
|
|
dc022d7329 | ||
|
|
cd42a61b40 | ||
|
|
cebab343fc | ||
|
|
d5387d0f14 | ||
|
|
4077edd401 | ||
|
|
d70258be54 | ||
|
|
7909bb2acd | ||
|
|
f6626aa9b5 | ||
|
|
9fc93cd876 | ||
|
|
efb169d775 | ||
|
|
bccc7748c3 | ||
|
|
bb80d4caf5 | ||
|
|
71b1b26c72 | ||
|
|
25507a0619 | ||
|
|
2c93e89b94 | ||
|
|
dafd93db9f | ||
|
|
bc78ba62ce | ||
|
|
3f31a03aac | ||
|
|
e5445fd492 | ||
|
|
5acab2ab05 | ||
|
|
4b15e0e743 | ||
|
|
53713b79d4 | ||
|
|
56c35ef19e | ||
|
|
eda9dac0b6 | ||
|
|
15679d9c79 | ||
|
|
5123135da9 | ||
|
|
275f2a5e8a | ||
|
|
599a9e79e6 | ||
|
|
67212d7131 | ||
|
|
b942d67faf | ||
|
|
d133755a8d | ||
|
|
34bfb70045 | ||
|
|
2b3cf4273e | ||
|
|
d58e9a9f77 | ||
|
|
768c7ffc26 | ||
|
|
f12dd71e3c | ||
|
|
c2b842b51d | ||
|
|
4460c980b8 | ||
|
|
97e7781e74 | ||
|
|
9237600923 | ||
|
|
1e21d46314 | ||
|
|
7d033b334e | ||
|
|
18e6cda31f | ||
|
|
7dff3396ff | ||
|
|
d840e9f704 | ||
|
|
df09f8bbb8 | ||
|
|
9d8f1e3575 | ||
|
|
9f2f270388 | ||
|
|
5287453b9d | ||
|
|
afbca14b35 | ||
|
|
190f166667 | ||
|
|
cf62e4bda8 | ||
|
|
a343a8347e | ||
|
|
96a68337f2 | ||
|
|
7b2171d969 | ||
|
|
8b7d639692 | ||
|
|
1420fdddf1 | ||
|
|
01d7bc35f4 | ||
|
|
ed862cd961 | ||
|
|
9d5be28f4e | ||
|
|
9d3af3fd07 | ||
|
|
7d5a85b95a | ||
|
|
875f819dbc | ||
|
|
75259b1f48 | ||
|
|
4f642d5eb0 | ||
|
|
8df5b36115 | ||
|
|
44b2102c4a | ||
|
|
47c8e76f33 | ||
|
|
62b1d18a85 | ||
|
|
2ebaecafc7 | ||
|
|
5874bc1795 | ||
|
|
f2fb5379a7 | ||
|
|
a3818d698f | ||
|
|
2fe81d355b | ||
|
|
3aef695688 | ||
|
|
69185cd775 | ||
|
|
87a10d5892 | ||
|
|
ace2457855 | ||
|
|
ad8ae2f576 | ||
|
|
668101edee | ||
|
|
736b40a83b | ||
|
|
bb7170a9d7 | ||
|
|
796792cf16 | ||
|
|
078edd8eed | ||
|
|
765fea6ae1 | ||
|
|
f584737b9c | ||
|
|
2e2d809b43 | ||
|
|
a87c2006df | ||
|
|
cbb5b4f25e | ||
|
|
445c30a8c0 | ||
|
|
0015800768 | ||
|
|
eef769eb94 | ||
|
|
1a440d9c9f | ||
|
|
83a4049a64 | ||
|
|
3ea744bdbe | ||
|
|
83e92bec0f | ||
|
|
12ffc0ca58 | ||
|
|
b0a642e223 | ||
|
|
007fc41451 | ||
|
|
bd41c318f5 | ||
|
|
5713582d42 | ||
|
|
bcecc0e76b | ||
|
|
7fa2f4e72a | ||
|
|
8f8f880637 | ||
|
|
4f7a7f4f0d | ||
|
|
57b1b68929 | ||
|
|
46f1cc0d0d | ||
|
|
94837e7fa7 | ||
|
|
bc1dcee512 | ||
|
|
54a917dc77 | ||
|
|
5abfc1e47d | ||
|
|
370487c734 | ||
|
|
56a9c4cc90 | ||
|
|
d8082aa1b8 | ||
|
|
c60c6595be | ||
|
|
02d86c52ae | ||
|
|
12b4849852 | ||
|
|
18d5e91091 | ||
|
|
f28f95d69c | ||
|
|
a5948e1a40 | ||
|
|
79420dd0b8 | ||
|
|
43da8f355e | ||
|
|
413ef3e196 | ||
|
|
030ff0e8f7 | ||
|
|
c6ffd59608 | ||
|
|
3fab667181 | ||
|
|
885d37bea3 | ||
|
|
65c40578b0 | ||
|
|
d88d38e35b | ||
|
|
7ae74f246c | ||
|
|
da26a6fb52 | ||
|
|
a5f36e9b3e | ||
|
|
541af94570 | ||
|
|
5c415c9f9d | ||
|
|
5350da81a9 | ||
|
|
14e43acc8a | ||
|
|
59b7b0cf28 | ||
|
|
032ad65c8a | ||
|
|
aade88fb04 | ||
|
|
7b65ce13f1 | ||
|
|
bc6d69f050 | ||
|
|
c696aa2356 | ||
|
|
1d8c548910 | ||
|
|
8f51decde5 | ||
|
|
c94883924d | ||
|
|
0051a3e018 | ||
|
|
ffeff8266b | ||
|
|
bd35c35a74 | ||
|
|
7abaa8d001 | ||
|
|
2f28b8489d | ||
|
|
b80c3a3d8b | ||
|
|
db55d3c1aa | ||
|
|
128baa7eb5 | ||
|
|
5d96007200 | ||
|
|
e66c10ec80 | ||
|
|
7d3a9ce6c3 | ||
|
|
2a51fc2870 | ||
|
|
d59a644e6f | ||
|
|
3bfd61f797 | ||
|
|
2e906aeb2c | ||
|
|
75fe371b8e | ||
|
|
ac2c6455c6 | ||
|
|
df67372d2e | ||
|
|
7e21616fbb | ||
|
|
f1edd7ee50 | ||
|
|
d4d7969e3c | ||
|
|
28dd8a8cda | ||
|
|
3b505d649f | ||
|
|
9a8996a45f | ||
|
|
cec7735366 | ||
|
|
68f786a074 | ||
|
|
43112048bd | ||
|
|
b4177175e7 | ||
|
|
1c7627b964 | ||
|
|
5aed4e1461 | ||
|
|
7c34c4dadc | ||
|
|
10c3edd790 | ||
|
|
a7ca4f6d40 | ||
|
|
8ef9774ff4 | ||
|
|
8f5cb6e779 | ||
|
|
227e1f0ec7 | ||
|
|
bffaa3ac17 | ||
|
|
140209a16f | ||
|
|
f68eae1c9d | ||
|
|
7538b43e94 | ||
|
|
493c012321 | ||
|
|
0cbbcc0541 | ||
|
|
44982c2e95 | ||
|
|
a9fecdd05d | ||
|
|
1d96a4fe01 | ||
|
|
b5a0deef6b | ||
|
|
5a2e2dfb7a | ||
|
|
26bc73814c | ||
|
|
7d3cb0d23d | ||
|
|
bda670f697 | ||
|
|
bfa08516c4 | ||
|
|
6d0b8e3b88 | ||
|
|
89140cb2a5 | ||
|
|
fa1f3b9305 | ||
|
|
04018a6c3a | ||
|
|
537dfcd26e | ||
|
|
38fbf45263 | ||
|
|
66e72b0a61 | ||
|
|
9caaea701d | ||
|
|
f7b5ee8983 | ||
|
|
0cc7de2028 | ||
|
|
3409d4e6f8 | ||
|
|
9673dc11cb | ||
|
|
260d61ce7f | ||
|
|
079144b143 | ||
|
|
2f8443ddb6 | ||
|
|
1eb8d466de | ||
|
|
7b04fad68d | ||
|
|
7206062cd3 | ||
|
|
0c31897d6d | ||
|
|
178fc7550c | ||
|
|
52d607190a | ||
|
|
f96660eae1 | ||
|
|
cfafd6df6e | ||
|
|
182d9ae3fe | ||
|
|
2631a14e1d | ||
|
|
1dc6356895 | ||
|
|
67eef03cdb | ||
|
|
183e59b9ae | ||
|
|
bbac5c191a | ||
|
|
913c67c57b | ||
|
|
46d914fcc9 | ||
|
|
a58e2ed3ab | ||
|
|
d7842574ad | ||
|
|
a1c622ce73 | ||
|
|
2a6c54dbc0 | ||
|
|
a5e2f7c9fd | ||
|
|
12b8aa4acc | ||
|
|
5254fef9ab | ||
|
|
72c42b9d4d | ||
|
|
bbab83040e | ||
|
|
7e0b8d1b1f | ||
|
|
e59a9605f1 | ||
|
|
0ee21c4039 | ||
|
|
50ee2e39d8 | ||
|
|
4eca163ae1 | ||
|
|
be129766c1 | ||
|
|
ebaf731221 | ||
|
|
53643a67e8 | ||
|
|
6e6ad97682 | ||
|
|
3817e9b5a7 | ||
|
|
a5a163d998 | ||
|
|
0591fd8a2e | ||
|
|
5d0f7c683b | ||
|
|
6af320aa57 | ||
|
|
bf9b92ba00 | ||
|
|
ebf131160b | ||
|
|
4d780c5ac3 | ||
|
|
6497266931 | ||
|
|
d80451e8a3 | ||
|
|
86a5c2bebf | ||
|
|
ad9104a418 | ||
|
|
5ff81fe56e | ||
|
|
2ee60508a8 | ||
|
|
579e74b066 | ||
|
|
bf3e8a5fe8 | ||
|
|
2ea72001b5 | ||
|
|
10508f89a9 | ||
|
|
9afa446921 | ||
|
|
7bc7bee85a | ||
|
|
14d835a2ae | ||
|
|
43e9648cba | ||
|
|
f39bb56270 | ||
|
|
638401d6c5 | ||
|
|
90c4186beb | ||
|
|
fc2b4342c3 | ||
|
|
52b6c26fcf | ||
|
|
d593d1c3e3 | ||
|
|
6549781322 | ||
|
|
ebb381bb38 | ||
|
|
1ffaaa0ff1 | ||
|
|
23fab74925 | ||
|
|
d9c8fca16e | ||
|
|
ce9c8da543 | ||
|
|
b493d7f59d | ||
|
|
bbd9be54c6 | ||
|
|
0f4a0ec40c | ||
|
|
9aa800e96f | ||
|
|
556a9c67af | ||
|
|
41b418addd | ||
|
|
ca19d77b5c | ||
|
|
dd1570a781 | ||
|
|
540b0bb808 | ||
|
|
4cc7e14323 | ||
|
|
6e031fa7b3 | ||
|
|
9ce6a6ac91 | ||
|
|
01541e1c9e | ||
|
|
9b38e51533 | ||
|
|
4048ecfb07 | ||
|
|
c80748188c | ||
|
|
7b7b66828f | ||
|
|
dd84bdd156 | ||
|
|
2e5b3ebf5d | ||
|
|
7057b5c0d7 | ||
|
|
c614d85e65 | ||
|
|
416654ba5d | ||
|
|
089032a55c | ||
|
|
312791b48b | ||
|
|
ed10c51683 | ||
|
|
0c02a63411 | ||
|
|
e730e7a102 | ||
|
|
f824af387c | ||
|
|
14f51a39ec | ||
|
|
5405e69b65 | ||
|
|
5d87f1f174 | ||
|
|
6d2d855dcf | ||
|
|
50051433af | ||
|
|
e0b69fc07e | ||
|
|
7579175cb9 | ||
|
|
7891957a37 | ||
|
|
f51e4c33b1 | ||
|
|
0cbf4fd9d4 | ||
|
|
83708433a1 | ||
|
|
b593ceb399 | ||
|
|
b69ef96c04 | ||
|
|
d8eb08b0ef | ||
|
|
95a2abe010 | ||
|
|
49dbbf28ae | ||
|
|
a9f4a9d280 | ||
|
|
159af0d76a | ||
|
|
af7665f143 | ||
|
|
a5c6ecb38d | ||
|
|
53b6273c8d | ||
|
|
4b471cfa0b | ||
|
|
84ca584e6e | ||
|
|
c549a3ff6b | ||
|
|
08ecc7930b | ||
|
|
c0ea855773 | ||
|
|
24d76dfef7 | ||
|
|
b1ba080e39 | ||
|
|
0908d07072 | ||
|
|
74e9d15997 | ||
|
|
8f9b7a89c8 | ||
|
|
f5354006f9 | ||
|
|
95bf393d8d | ||
|
|
2cf08f4949 | ||
|
|
1f850701da | ||
|
|
a6f8cabdd1 | ||
|
|
a50b163373 | ||
|
|
1fee6b29ae | ||
|
|
efb473a884 | ||
|
|
399fccc0dd | ||
|
|
5b88cbae9d | ||
|
|
2094464385 | ||
|
|
22ddf1e278 | ||
|
|
0ea7ac905e | ||
|
|
b1fc0c4ec2 | ||
|
|
2a34bc145f | ||
|
|
e24fdcaa30 | ||
|
|
feea04b612 | ||
|
|
8f44e5fa45 | ||
|
|
3a717766fa | ||
|
|
27edd5be4f | ||
|
|
da44728690 | ||
|
|
7fee94af8c | ||
|
|
c07f5a0a5c | ||
|
|
ec936edb9b | ||
|
|
6872e3a6c0 | ||
|
|
ead2bce9dd | ||
|
|
d880bd730a | ||
|
|
75ac1f2ddf | ||
|
|
b63832cf0e | ||
|
|
caeafe42c8 |
No files matched your search
@@ -0,0 +1,151 @@
|
||||
name: CI and Release
|
||||
|
||||
env:
|
||||
R2V: 6.0.4
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags:
|
||||
- '[0-9]*'
|
||||
# pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.os }} / ${{ matrix.build_system }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-24.04, macos-latest, windows-latest]
|
||||
build_system: [make, meson]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
build_system: make
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Meson/Ninja
|
||||
if: matrix.build_system == 'meson'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install meson ninja
|
||||
|
||||
- name: Install radare2 on Linux
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
run: |
|
||||
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
|
||||
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
|
||||
sudo dpkg -i "radare2_${R2V}_amd64.deb"
|
||||
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
|
||||
|
||||
- name: Install radare2 on macOS
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
git clone --depth=1 --branch "${{env.R2V}}" https://github.com/radareorg/radare2
|
||||
cd radare2
|
||||
sys/install.sh
|
||||
|
||||
- name: Install radare2 on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: powershell
|
||||
run: |
|
||||
$url = "https://github.com/radareorg/radare2/releases/download/${{ env.R2V }}/radare2-${{ env.R2V }}-w64.zip"
|
||||
$dest = "$env:TEMP\radare2.zip"
|
||||
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
|
||||
$prefix = 'C:\radare2'
|
||||
if (Test-Path $prefix) { Remove-Item -Path $prefix -Recurse -Force }
|
||||
Expand-Archive -Path $dest -DestinationPath $prefix -Force
|
||||
# Move contents from subdirectory if present
|
||||
$subdirs = Get-ChildItem -Path $prefix -Directory
|
||||
if ($subdirs.Count -eq 1) {
|
||||
$subdir = $subdirs[0]
|
||||
Get-ChildItem -Path $subdir.FullName | Move-Item -Destination $prefix -Force
|
||||
Remove-Item -Path $subdir.FullName -Recurse -Force
|
||||
}
|
||||
|
||||
- name: Build with Make
|
||||
if: matrix.build_system == 'make'
|
||||
run: make -C src
|
||||
|
||||
- name: Build with Meson (Unix)
|
||||
if: matrix.build_system == 'meson' && matrix.os != 'windows-latest'
|
||||
run: |
|
||||
cd src
|
||||
meson setup build -Dr2_prefix=${{ secrets.R2_PREFIX }} || meson configure build
|
||||
meson compile -C build
|
||||
|
||||
- name: Build with Meson (Windows)
|
||||
if: matrix.build_system == 'meson' && matrix.os == 'windows-latest'
|
||||
shell: powershell
|
||||
run: |
|
||||
cd src
|
||||
$prefix = 'C:\radare2'
|
||||
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
|
||||
if ($LASTEXITCODE -ne 0) { meson configure build }
|
||||
meson compile -C build
|
||||
|
||||
- name: Create release artifacts (tags only)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp -rf src/r2ai.dll src/r2ai.exe src/r2ai src/r2ai.dylib src/r2ai.so release/ || true
|
||||
cp -rf src/build/r2ai src/build/r2ai.dylib src/build/r2ai.so release/ || true
|
||||
cp -f decai/decai.r2.js release/ || true
|
||||
if command -v zip >/dev/null 2>&1; then
|
||||
zip -j "release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip" release/*
|
||||
else
|
||||
powershell.exe -Command "Compress-Archive -Path release/* -DestinationPath 'release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip'"
|
||||
fi
|
||||
|
||||
- name: Upload artifacts (tags only)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.build_system }}-binary
|
||||
path: release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip
|
||||
|
||||
# - name: Smoke test
|
||||
# run: |
|
||||
# if command -v r2 >/dev/null 2>&1; then
|
||||
# r2 -q -c 'r2ai -h' -- || true
|
||||
# fi
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify tag is on master
|
||||
run: |
|
||||
git fetch origin master:refs/remotes/origin/master
|
||||
if ! git branch -r --contains ${GITHUB_SHA} | grep -q "origin/master"; then
|
||||
echo "Tag commit is not on origin/master — skipping release."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
|
||||
- name: Add decai.r2.js to release artifacts
|
||||
run: cp decai/decai.r2.js release-artifacts/
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
release-artifacts/**/*.zip
|
||||
release-artifacts/decai.r2.js
|
||||
generate_release_notes: true
|
||||
@@ -0,0 +1,14 @@
|
||||
build
|
||||
b
|
||||
src/b
|
||||
src/r2ai
|
||||
src/r2ai.dSYM
|
||||
venv
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
third_party
|
||||
@@ -0,0 +1,28 @@
|
||||
from transformers import AutoTokenizer
|
||||
import transformers
|
||||
import torch
|
||||
|
||||
model = "facebook/llm-compiler-13b"
|
||||
model = "QuantFactory/llm-compiler-7b-GGUF"
|
||||
# model = "file:///tmp/llm-compiler.gguf"
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(model)
|
||||
pipeline = transformers.pipeline(
|
||||
"text-generation",
|
||||
model=model,
|
||||
torch_dtype=torch.float16,
|
||||
device_map="auto",
|
||||
)
|
||||
|
||||
sequences = pipeline(
|
||||
'%3 = alloca i32, align 4',
|
||||
do_sample=True,
|
||||
top_k=10,
|
||||
temperature=0.1,
|
||||
top_p=0.95,
|
||||
num_return_sequences=1,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
max_length=200,
|
||||
)
|
||||
for seq in sequences:
|
||||
print(f"Result: {seq['generated_text']}")
|
||||
@@ -0,0 +1,10 @@
|
||||
import nltk
|
||||
from nltk.corpus import stopwords
|
||||
from nltk.tokenize import word_tokenize
|
||||
|
||||
nltk.download('stopwords')
|
||||
nltk.download('punkt')
|
||||
long_sentence = "It's such a fine day today, The sun is out, and the sky is blue. Can you tell me what the weather will be like tomorrow?"
|
||||
word_tokens = word_tokenize(long_sentence)
|
||||
short_sent = ' '.join([t for t in word_tokens if t not in stopwords.words('english')])
|
||||
print(short_sent)
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import nltk
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
stmts = [
|
||||
"disassemble 10 first instructions at main",
|
||||
"pet 10 cats and 2 rabbits",
|
||||
"what's the weather like in california"
|
||||
]
|
||||
|
||||
INTERESTING = [
|
||||
"JJ", "CD", "NN", "NNS"
|
||||
]
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def summarize(sentence):
|
||||
tokens = nltk.word_tokenize(sentence)
|
||||
tagged = nltk.pos_tag(tokens)
|
||||
entities = nltk.chunk.ne_chunk(tagged)
|
||||
|
||||
summary = []
|
||||
for ent in entities:
|
||||
if ent[1] in INTERESTING:
|
||||
summary.append(ent)
|
||||
|
||||
print(">Sentence:", repr(sentence))
|
||||
print(">Summary :", list(summary))
|
||||
#print(">Entities:", list(entities))
|
||||
print()
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def main():
|
||||
for stmt in stmts:
|
||||
summarize(stmt)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,249 @@
|
||||
import Foundation
|
||||
|
||||
// Token representation
|
||||
struct Token {
|
||||
var token: String
|
||||
var count: Int
|
||||
var df: Int
|
||||
}
|
||||
|
||||
// KD-Tree Node
|
||||
class KDNodeData {
|
||||
let point: [Float]
|
||||
let text: String
|
||||
var left: KDNodeData?
|
||||
var right: KDNodeData?
|
||||
let splitDim: Int
|
||||
|
||||
init(point: [Float], text: String, left: KDNodeData? = nil, right: KDNodeData? = nil, splitDim: Int) {
|
||||
self.point = point
|
||||
self.text = text
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.splitDim = splitDim
|
||||
}
|
||||
}
|
||||
|
||||
// Result for K-Nearest Neighbors
|
||||
struct KNNResult {
|
||||
let node: KDNodeData
|
||||
let distanceSq: Float
|
||||
}
|
||||
|
||||
class VectorDB {
|
||||
// Core database structures
|
||||
private let dimension: Int
|
||||
private var root: KDNodeData?
|
||||
private var tokens: [Token] = []
|
||||
private var totalDocs: Int = 0
|
||||
private var size: Int = 0
|
||||
|
||||
init(dimension: Int) {
|
||||
self.dimension = dimension
|
||||
}
|
||||
|
||||
// Validate token (similar to C implementation)
|
||||
private func isValidToken(_ token: String) -> Bool {
|
||||
let invalidTokens = ["pancake", "author", "radare2"]
|
||||
return !invalidTokens.contains(token)
|
||||
}
|
||||
|
||||
// Find a token in the token list
|
||||
private func findToken(_ tokens: [Token], _ token: String) -> Token? {
|
||||
return tokens.first { $0.token == token }
|
||||
}
|
||||
|
||||
// Compute embedding using TF-IDF approach
|
||||
private func computeEmbedding(_ text: String) -> [Float] {
|
||||
// Zero the embedding vector
|
||||
var embedding = [Float](repeating: 0.0, count: dimension)
|
||||
|
||||
// Tokenize and normalize
|
||||
let words = text
|
||||
.lowercased()
|
||||
.replacingOccurrences(of: "[^a-z0-9\\s]", with: " ", options: .regularExpression)
|
||||
.split(separator: " ")
|
||||
.map { String($0) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
// Local document token frequency
|
||||
var docTokens: [Token] = []
|
||||
for token in words {
|
||||
if let existingToken = findToken(docTokens, token) {
|
||||
let index = docTokens.firstIndex { $0.token == token }!
|
||||
docTokens[index].count += 1
|
||||
} else {
|
||||
docTokens.append(Token(token: token, count: 1, df: 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Increment total documents
|
||||
totalDocs += 1
|
||||
|
||||
// Update global document frequencies
|
||||
for docToken in docTokens {
|
||||
if let globalToken = findToken(self.tokens, docToken.token) {
|
||||
if isValidToken(docToken.token) {
|
||||
let index = self.tokens.firstIndex { $0.token == docToken.token }!
|
||||
self.tokens[index].count += 1
|
||||
self.tokens[index].df += 1
|
||||
}
|
||||
} else {
|
||||
self.tokens.append(Token(token: docToken.token, count: docToken.count, df: 1))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute TF-IDF and update embedding
|
||||
for docToken in docTokens {
|
||||
// Compute term frequency
|
||||
let tf = 1 + log(Float(docToken.count))
|
||||
|
||||
// Find global token to get document frequency
|
||||
let dfValue = findToken(self.tokens, docToken.token)?.df ?? 1
|
||||
|
||||
// Compute inverse document frequency
|
||||
let idf = log(Float(totalDocs + 1) / Float(dfValue + 1)) + 1
|
||||
|
||||
// Compute weight
|
||||
let weight = tf * idf
|
||||
|
||||
// Hash-based index assignment
|
||||
let hash = simpleHash(docToken.token)
|
||||
let index = hash % dimension
|
||||
|
||||
// Add weighted value to embedding
|
||||
embedding[index] += weight
|
||||
}
|
||||
|
||||
// L2 Normalize the embedding
|
||||
return normalizeVector(embedding)
|
||||
}
|
||||
|
||||
// Simple string hash function
|
||||
private func simpleHash(_ str: String) -> Int {
|
||||
var hash = 0
|
||||
for char in str.unicodeScalars {
|
||||
hash = ((hash << 5) - hash) + Int(char.value)
|
||||
hash = hash & hash // Convert to 32-bit integer
|
||||
}
|
||||
return abs(hash)
|
||||
}
|
||||
|
||||
// Normalize vector to unit length
|
||||
private func normalizeVector(_ vec: [Float]) -> [Float] {
|
||||
let normSq = vec.reduce(0) { $0 + $1 * $1 }
|
||||
if normSq > 0 {
|
||||
let norm = sqrt(normSq)
|
||||
return vec.map { $0 / norm }
|
||||
}
|
||||
return vec
|
||||
}
|
||||
|
||||
// Compute squared Euclidean distance
|
||||
private func squaredDistance(_ a: [Float], _ b: [Float]) -> Float {
|
||||
guard a.count == b.count else { return Float.greatestFiniteMagnitude }
|
||||
return zip(a, b).reduce(0) { $0 + pow($1.0 - $1.1, 2) }
|
||||
}
|
||||
|
||||
// Recursive KD-Tree insertion
|
||||
private func insertRecursive(_ node: KDNodeData?, point: [Float], text: String, depth: Int) -> KDNodeData {
|
||||
guard let currentNode = node else {
|
||||
return KDNodeData(point: point, text: text, splitDim: depth % dimension)
|
||||
}
|
||||
|
||||
let axis = currentNode.splitDim
|
||||
if point[axis] < currentNode.point[axis] {
|
||||
currentNode.left = insertRecursive(currentNode.left, point: point, text: text, depth: depth + 1)
|
||||
} else {
|
||||
currentNode.right = insertRecursive(currentNode.right, point: point, text: text, depth: depth + 1)
|
||||
}
|
||||
|
||||
return currentNode
|
||||
}
|
||||
|
||||
// Insert a document into the vector database
|
||||
func insert(_ text: String) {
|
||||
guard !text.isEmpty else { return }
|
||||
|
||||
// Compute embedding
|
||||
let embedding = computeEmbedding(text)
|
||||
|
||||
// Insert into KD-Tree
|
||||
root = insertRecursive(root, point: embedding, text: text, depth: 0)
|
||||
size += 1
|
||||
}
|
||||
|
||||
// K-Nearest Neighbors search
|
||||
func query(_ text: String, k: Int) -> [String] {
|
||||
guard !text.isEmpty, root != nil else { return [] }
|
||||
|
||||
// Compute query embedding
|
||||
let queryEmbedding = computeEmbedding(text)
|
||||
|
||||
// Initialize result set
|
||||
var results: [KNNResult] = []
|
||||
|
||||
// Recursive KNN search
|
||||
func searchRecursive(_ node: KDNodeData?, query: [Float], depth: Int) {
|
||||
guard let node = node else { return }
|
||||
|
||||
// Compute distance and potentially insert into results
|
||||
let distSq = squaredDistance(query, node.point)
|
||||
|
||||
if results.count < k {
|
||||
results.append(KNNResult(node: node, distanceSq: distSq))
|
||||
// Maintain max-heap property
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
} else if distSq < results[0].distanceSq {
|
||||
results[0] = KNNResult(node: node, distanceSq: distSq)
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
}
|
||||
|
||||
// Decide which subtree to search first
|
||||
let axis = node.splitDim
|
||||
let diff = query[axis] - node.point[axis]
|
||||
let first = diff < 0 ? node.left : node.right
|
||||
let second = diff < 0 ? node.right : node.left
|
||||
|
||||
// Recursively search
|
||||
searchRecursive(first, query: query, depth: depth + 1)
|
||||
|
||||
// Potentially search the other subtree
|
||||
if results.count < k || diff * diff < results[0].distanceSq {
|
||||
searchRecursive(second, query: query, depth: depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
searchRecursive(root, query: queryEmbedding, depth: 0)
|
||||
|
||||
// Sort results and return texts
|
||||
return results
|
||||
.sorted { $0.distanceSq < $1.distanceSq }
|
||||
.map { $0.node.text }
|
||||
}
|
||||
|
||||
// Get total number of documents
|
||||
func getSize() -> Int {
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
func main() {
|
||||
// Create a vector database with 64-dimensional embeddings
|
||||
let db = VectorDB(dimension: 64)
|
||||
|
||||
// Insert some sample documents
|
||||
db.insert("The quick brown fox jumps over the lazy dog")
|
||||
db.insert("A quick brown fox is very quick")
|
||||
db.insert("The author of radare2 is name / named pancake")
|
||||
db.insert("The lazy dog sleeps all day")
|
||||
|
||||
// Query for similar documents
|
||||
let results = db.query("who is the author of radare2?", k: 2)
|
||||
print("Similar documents:", results)
|
||||
}
|
||||
|
||||
// Run example
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
PV=3.12
|
||||
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
|
||||
PYTHON?=python${PV}
|
||||
else
|
||||
PYTHON?=python3
|
||||
endif
|
||||
PIP=$(PYTHON) -m pip
|
||||
|
||||
ifeq ($(R2PM_BINDIR),)
|
||||
MISSING RADARE2
|
||||
endif
|
||||
|
||||
LINTED=r2ai/code_block.py
|
||||
LINTED+=r2ai/bubble.py
|
||||
LINTED+=r2ai/const.py
|
||||
LINTED+=r2ai/backend/kobaldcpp.py
|
||||
# LINTED+=r2ai/index.py
|
||||
# LINTED+=r2ai/voice.py
|
||||
# LINTED+=r2ai/anthropic.py
|
||||
r2ai-python py python r2aipy: venv
|
||||
@./r2ai.sh
|
||||
|
||||
large:
|
||||
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
|
||||
|
||||
all.old:
|
||||
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
|
||||
$(PYTHON) main.py || $(MAKE) deps
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv venv
|
||||
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
|
||||
|
||||
deps: venv
|
||||
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
|
||||
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
pip install --force-reinstall -U --no-cache-dir .
|
||||
clean:
|
||||
rm -rf venv
|
||||
rm -rf build
|
||||
find . -name "*.egg-info" -exec rm -rf {} +
|
||||
|
||||
user-install:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
|
||||
|
||||
install: user-install
|
||||
-mkdir -p /usr/local/share/man/man1/r2ai.1
|
||||
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
install-plugin user-install-plugin:
|
||||
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
-rm -f /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
user-uninstall-plugin uninstall-plugin:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
pub:
|
||||
$(PYTHON) -m build
|
||||
twine check dist/*
|
||||
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
|
||||
|
||||
pylint lint cilint:
|
||||
. venv/bin/activate ; pylint $(LINTED)
|
||||
|
||||
lintall:
|
||||
pylint *.py r2ai/*.py
|
||||
|
||||
.PHONY: lint lintall
|
||||
|
||||
deps-global:
|
||||
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# r2ai-python
|
||||
|
||||
This directory contains the original implementation of the AI support for radare2 written in Python.
|
||||
|
||||
It is considered _deprecated_, but it's still useful and aims to be maintained, we recommend using decai or the C rewrite as alternatives.
|
||||
|
||||
The Python implementation consists in a module can be used in 3 different ways:
|
||||
|
||||
* commandline repl using r2pipe to spawn or connect to remote radare2 instances
|
||||
* radare2 core plugin that is instantiated when placing it in the radare2 plugin's directory
|
||||
* Python API to be used for writing your own scripts
|
||||
|
||||
## API Providers
|
||||
|
||||
The Python implementation focused on supporting the LlamaCpp Python module, which is somehow heavy to load in many environments, but it also supports litellm which provides access to many external connectors like ollama, openai, anthropic, gemini, etc.
|
||||
|
||||
## Deprecation Reasons
|
||||
|
||||
There are several reasons why this implementation is considered deprecated and not recommended to use:
|
||||
|
||||
1) It's Python.
|
||||
|
||||
- Aka, it's slow, heavy loads in same process
|
||||
- Your system probably have multiple versions installed
|
||||
- The shell and radare2 python bindings are probably not the same
|
||||
- Required venv to be created (at least an extra 1GB)
|
||||
- Not typed, regressions and bugs appear at runtime
|
||||
|
||||
2) Goto 1
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python3 -m pip install .
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
### Selecting the model
|
||||
|
||||
- List all downloaded models: `-m`
|
||||
- Get a short list of models: `-MM`
|
||||
- Help: `-h`
|
||||
|
||||
**Example selecting a remote models:**
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m anthropic:claude-3-7-sonnet-20250219
|
||||
[r2ai:0x00006aa0]> -m openai:gpt-4
|
||||
```
|
||||
|
||||
**Example downloading a free local AI: Mistral 7B v0.2:**
|
||||
|
||||
Launch r2ai, select the model and ask a question. If the model isn't downloaded yet, r2ai will ask you which precise version to download.
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
```
|
||||
|
||||
Then ask your question, and r2ai will automatically download if needed:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> give me a short algorithm to test prime numbers
|
||||
Select TheBloke/Mistral-7B-Instruct-v0.2-GGUF model. See -M and -m flags
|
||||
[?] Quality (smaller is faster):
|
||||
> Small | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
Medium | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
Large | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
See More
|
||||
|
||||
[?] Quality (smaller is faster):
|
||||
> mistral-7b-instruct-v0.2.Q2_K.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_L.gguf | Size: 3.6 GB, Estimated RAM usage: 6.1 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_M.gguf | Size: 3.3 GB, Estimated RAM usage: 5.8 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_S.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q4_0.gguf | Size: 3.8 GB, Estimated RAM usage: 6.3 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_M.gguf | Size: 4.1 GB, Estimated RAM usage: 6.6 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_S.gguf | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
mistral-7b-instruct-v0.2.Q5_0.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_M.gguf | Size: 4.8 GB, Estimated RAM usage: 7.3 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_S.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q6_K.gguf | Size: 5.5 GB, Estimated RAM usage: 8.0 GB
|
||||
mistral-7b-instruct-v0.2.Q8_0.gguf | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
|
||||
[?] Use this model by default? ~/.r2ai.model:
|
||||
> Yes
|
||||
No
|
||||
|
||||
[?] Download to ~/.local/share/r2ai/models? (Y/n): Y
|
||||
```
|
||||
|
||||
**Example selecting a local model served by Ollama**
|
||||
|
||||
Download a model and make it available through Ollama:
|
||||
|
||||
```
|
||||
$ ollama ls
|
||||
NAME ID SIZE MODIFIED
|
||||
codegeex4:latest 867b8e81d038 5.5 GB 23 hours ago
|
||||
```
|
||||
|
||||
Use it from r2ai by prefixing its name with `ollama/`
|
||||
|
||||
```
|
||||
[r2ai:0x00002d30]> -m ollama/codegeex4:latest
|
||||
[r2ai:0x00002d30]> hi
|
||||
Hello! How can I assist you today?
|
||||
```
|
||||
|
||||
### Standard/Auto mode
|
||||
|
||||
The standard mode is invoked by directly asking the question.
|
||||
For the Auto mode, the question **must be prefixed** by `' ` (quote + space). The AI may instruct r2ai to run various commands. Those commands are run on *your host*, so you will be asked to review them before they run.
|
||||
|
||||
Example in "standard" mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> compute 4+5
|
||||
4 + 5 = 9
|
||||
[r2ai:0x00006aa0]> draw me a pancake in ASCII art
|
||||
Sure, here's a simple ASCII pancake:
|
||||
|
||||
_____
|
||||
( )
|
||||
( )
|
||||
-----
|
||||
```
|
||||
|
||||
Example in auto mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]>' Decompile the main
|
||||
[..]
|
||||
r2ai is going to execute the following command on the host
|
||||
Want to edit? (ENTER to validate) pdf @ fcn.000015d0
|
||||
This command will execute on this host: pdf @ fcn.000015d0. Agree? (y/N) y
|
||||
```
|
||||
|
||||
If you wish to edit the command, you can do it inline for short one line commands, or an editor will pop up.
|
||||
|
||||
### r2ai Configuration settings
|
||||
|
||||
List all settings with `-e`
|
||||
|
||||
| Key | Explanation |
|
||||
| ----------- | ------------------------------------- |
|
||||
| debug_level | All verbose messages for level 1. Default is 2 |
|
||||
| auto.max_runs | Maximum number of questions the AI is allowed to ask r2 in auto mode. |
|
||||
| auto.hide_tool_output | By default false, consequently output of r2cmd, run_python etc is shown. Set to `true` to hide those internal messages. |
|
||||
| chat.show_cost | Show the cost of each request to the AI if true |
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
[project]
|
||||
name = "r2ai"
|
||||
dynamic = ["readme"]
|
||||
version = "0.9.6"
|
||||
description = "Artificial intelligence tooling for radare2"
|
||||
|
||||
license = {text = "MIT License"}
|
||||
authors = [
|
||||
{name = "pancake", email = "[email protected]"}
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"rich",
|
||||
"r2pipe",
|
||||
"inquirer",
|
||||
"llama-cpp-python==0.3.7",
|
||||
"huggingface_hub",
|
||||
"appdirs",
|
||||
"unidecode",
|
||||
"jsonref",
|
||||
"transformers",
|
||||
"pydantic",
|
||||
"pyreadline3",
|
||||
"tokentrim",
|
||||
"boto3",
|
||||
"colorama",
|
||||
"textual",
|
||||
"litellm>=1.60.5",
|
||||
"numpydoc"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
extras = [
|
||||
"chromadb",
|
||||
"openai",
|
||||
"anthropic",
|
||||
"groq",
|
||||
"google-generativeai",
|
||||
"google-cloud-aiplatform"
|
||||
]
|
||||
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://www.radare.org/"
|
||||
repository = "https://github.com/radareorg/r2ai"
|
||||
|
||||
[project.scripts]
|
||||
r2ai = "r2ai.main:run"
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["r2ai*"]
|
||||
namespaces = true
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
readme = {file = "README.md", content-type = "text/markdown"}
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
.Dd May 14, 2024
|
||||
.Dt R2AI 1
|
||||
.Sh NAME
|
||||
.Nm r2ai
|
||||
.Nd radare2 artificial intelligence integration
|
||||
.Sh SYNOPSIS
|
||||
.Nm r2ai
|
||||
.Op [-h]
|
||||
.Op [...]
|
||||
.Sh DESCRIPTION
|
||||
Integrate multiple language models and inference engines with radare2 for reverse engineering and chatting purposes.
|
||||
.Bl -tag -width Fl
|
||||
.It Fl m Ar model
|
||||
Select a different model name
|
||||
.Sh USAGE
|
||||
.Pp
|
||||
The plugin can be used from the shell, via r2pipe or from radare2.
|
||||
.Pp
|
||||
You need the rlang-python plugin installed in your system to use the r2ai plugin from radare2, which adds the `r2ai` command in the r2 shell.
|
||||
.Pp
|
||||
It is also possible to use r2ai without rlang, via r2pipe, so you can run `#!pipe python main.py` instead.
|
||||
.Pp
|
||||
.Sh INSTALLATION
|
||||
.Pp
|
||||
$ r2ai -i r2ai
|
||||
.Sh FILES
|
||||
.Pp
|
||||
~/r2ai.rc - run this script every time
|
||||
~/r2ai.model - symlink to the directory containing all downloaded models
|
||||
~/r2ai.history - file storing all messages written
|
||||
~/r2ai.openai-key - write the key from your openai thing
|
||||
~/r2ai.mastodon-key - write the key from your Mastodon thing
|
||||
~/r2ai.plugins - directory containing r2ai plugin scripts to be executed with ..
|
||||
.Sh SEE ALSO
|
||||
.Pp
|
||||
.Xr radare2(1)
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
unset DYLD_LIBRARY_PATH
|
||||
unset LD_LIBRARY_PATH
|
||||
export TRANSFORMERS_NO_ADVISORY_WARNINGS=1
|
||||
PYTHON=python3
|
||||
if [ -h "$0" ]; then
|
||||
F=`readlink $0`
|
||||
else
|
||||
F="$0"
|
||||
fi
|
||||
D=`dirname "$F"`
|
||||
RD=`realpath "$D"`
|
||||
[ -n "${RD}" ] && D="$RD"
|
||||
[ -n "$D" ] && cd "$D"
|
||||
if [ -d venv ]; then
|
||||
. venv/bin/activate
|
||||
else
|
||||
$PYTHON -m venv venv
|
||||
. venv/bin/activate
|
||||
pip3 install -e .
|
||||
fi
|
||||
exec $PYTHON -m r2ai.cli "$@"
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
import logging
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
||||
VERSION = "0.8.2"
|
||||
|
||||
# 0 NOTSET, 1 DEBUG, 2 INFO, 3 WARNING, 4 ERROR, 5 CRITICAL; multipied by 10
|
||||
LOG_LEVEL = int(os.environ.get('R2AI_LOG', '2')) * 10
|
||||
LOG_FILE = os.environ.get('R2AI_LOGFILE', None)
|
||||
|
||||
for tag in ["httpx", "openai", "httpcore"]:
|
||||
_logger = logging.getLogger(tag)
|
||||
_logger.setLevel(logging.CRITICAL)
|
||||
_logger.propagate = False # Disable child loggers too
|
||||
|
||||
handlers = [RichHandler()]
|
||||
if LOG_FILE:
|
||||
handlers.append(logging.FileHandler(LOG_FILE))
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
logging.basicConfig(format="%(name)s - %(levelname)s - %(message)s",
|
||||
handlers=handlers)
|
||||
LOGGER.setLevel(LOG_LEVEL)
|
||||
@@ -0,0 +1,511 @@
|
||||
import builtins
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from . import LOGGER
|
||||
import litellm
|
||||
from litellm import _should_retry, acompletion, utils, ModelResponse
|
||||
import asyncio
|
||||
from .pipe import get_filename
|
||||
from .tools import r2cmd, run_python, execute_binary, schemas, print_tool_call
|
||||
import json
|
||||
import signal
|
||||
from .spinner import spinner
|
||||
from .completion import create_chat_completion
|
||||
import uuid
|
||||
import time
|
||||
import readline
|
||||
import subprocess
|
||||
import shlex
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
# litellm.drop_params = True
|
||||
# litellm.set_verbose=True
|
||||
|
||||
ANSI_REGEX = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||
|
||||
SYSTEM_PROMPT_AUTO = """
|
||||
You are a reverse engineer and you are using radare2 to analyze a binary.
|
||||
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
|
||||
|
||||
# Guidelines
|
||||
- Understand the Task: Grasp the main objective, goals, requirements, constraints, and expected output.
|
||||
- Reasoning Before Conclusions**: Encourage reasoning steps before any conclusions are reached.
|
||||
- Assume the user is always asking you about the binary, unless they're specifically asking you for radare2 help.
|
||||
- The binary has already been loaded. You can interact with the binary using the r2cmd tool.
|
||||
- `this` or `here` might refer to the current address in the binary or the binary itself.
|
||||
- If you need more information, try to use the r2cmd tool to run commands before answering.
|
||||
- You can use the r2cmd tool multiple times if you need or you can pass a command with pipes if you need to chain commands.
|
||||
- If you're asked to decompile a function, make sure to return the code in the language you think it was originally written and rewrite it to be as easy as possible to be understood. Make sure you use descriptive variable and function names and add comments.
|
||||
- Don't just regurgitate the same code, figure out what it's doing and rewrite it to be more understandable.
|
||||
- If you need to run a command in r2 before answering, you can use the r2cmd tool
|
||||
- Do not repeat commands if you already know the answer.
|
||||
- Formulate a plan. Think step by step. Analyze the binary as much as possible before answering.
|
||||
- You must keep going until you have a final answer.
|
||||
- Double check that final answer. Make sure you didn't miss anything.
|
||||
- Make sure you call tools and functions correctly.
|
||||
"""
|
||||
|
||||
SYSTEM_PROMPT_AUTO_REASONING = f"""
|
||||
You are an expert reverse engineer.
|
||||
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
|
||||
You can use the available tools to help you answer the user's question.
|
||||
The binary has already been loaded. You can interact with the binary using the r2cmd tool.
|
||||
If you need to run a command in r2 before answering, you can use the r2cmd tool
|
||||
"""
|
||||
|
||||
class ChatAuto:
|
||||
def __init__(self, model, max_tokens = 32000, top_p=0.95, temperature=0.0, interpreter=None, system=None, tools=None, ask_to_execute=True, messages=None, tool_choice='auto', llama_instance=None, timeout=60, stream=True, cb=None, max_runs=100):
|
||||
self.logger = LOGGER
|
||||
self.functions = {}
|
||||
self.tools = []
|
||||
self.model = model
|
||||
self.max_tokens = max_tokens
|
||||
self.top_p = top_p
|
||||
self.temperature = temperature
|
||||
self.system = system
|
||||
self.messages = messages
|
||||
self.interpreter = interpreter
|
||||
self.system_message = None
|
||||
self.timeout = timeout
|
||||
self.stream = stream
|
||||
self.cost = 0
|
||||
self.is_reasoning = False
|
||||
self.n_runs = 0
|
||||
self.max_runs = max_runs
|
||||
self._last_response = None
|
||||
self._start_time = time.time()
|
||||
self._last_run_time = time.time()
|
||||
self.ask_to_execute = ask_to_execute
|
||||
self.llama_instance = llama_instance or interpreter.llama_instance if interpreter else None
|
||||
|
||||
init_prompt = ""
|
||||
|
||||
if not self.llama_instance:
|
||||
rmodel = re.sub(r'ft:([^:]+).*', r'\1', self.model)
|
||||
model_info = litellm.get_model_info(rmodel)
|
||||
self.max_tokens = min(self.max_tokens, model_info['max_tokens'])
|
||||
|
||||
if self.model.startswith('openai/o'):
|
||||
self.is_reasoning = True
|
||||
self.max_tokens = model_info['max_tokens']
|
||||
self.timeout = 60 * 60
|
||||
init_commands = self.interpreter.env["auto.init_commands"]
|
||||
|
||||
if init_commands:
|
||||
init_prompt = f"""
|
||||
Here is some information about the binary to get you started:
|
||||
> {init_commands}
|
||||
{r2cmd(init_commands)}
|
||||
"""
|
||||
if self.is_reasoning:
|
||||
if not system and not self.messages[0]['role'] == 'developer':
|
||||
self.messages.insert(0, { "role": "developer", "content": SYSTEM_PROMPT_AUTO_REASONING + init_prompt })
|
||||
self.stream = False
|
||||
else:
|
||||
if messages and messages[0]['role'] != 'system':
|
||||
self.messages.insert(0, { "role": "system", "content": system or SYSTEM_PROMPT_AUTO + init_prompt })
|
||||
|
||||
|
||||
if cb:
|
||||
self.cb = cb
|
||||
else:
|
||||
self.cb = lambda *args: None
|
||||
self.tool_choice = None
|
||||
if tools:
|
||||
for tool in tools:
|
||||
if tool.__name__ in schemas:
|
||||
schema = schemas[tool.__name__]
|
||||
else:
|
||||
schema = utils.function_to_dict(tool)
|
||||
|
||||
self.tools.append({ "type": "function", "function": schema })
|
||||
self.functions[tool.__name__] = tool
|
||||
self.tool_choice = tool_choice
|
||||
#self.tool_end_message = '\nNOTE: The user saw this output, do not repeat it.'
|
||||
|
||||
async def process_tool_calls(self, tool_calls):
|
||||
if tool_calls:
|
||||
for tool_call in tool_calls:
|
||||
self.logger.debug(f"tool_call: {tool_call}")
|
||||
tool_name = tool_call["function"]["name"]
|
||||
if "id" not in tool_call:
|
||||
tool_call["id"] = str(uuid.uuid4())
|
||||
try:
|
||||
tool_args = json.loads(tool_call["function"]["arguments"])
|
||||
except Exception:
|
||||
if "arguments" in tool_call["function"] and type(tool_call["function"]["arguments"]) == dict:
|
||||
tool_args = tool_call["function"]["arguments"]
|
||||
else:
|
||||
self.logger.error(f'Error parsing JSON: {tool_call["function"]["arguments"]}')
|
||||
# raise Exception('Error parsing JSON')
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": "Error: Unable to parse JSON" , "tool_call_id": tool_call["id"]})
|
||||
continue
|
||||
if tool_name not in self.functions:
|
||||
self.logger.error(f'Tool not found: {tool_name}')
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": "Error: Tool not found" , "tool_call_id": tool_call["id"]})
|
||||
continue
|
||||
|
||||
if self.ask_to_execute:
|
||||
# tool_call typically contains:
|
||||
# { 'id': 'toolu_01Hr6XTatRtp7rX54iBu1voC', 'type': 'function', 'function': {'name': 'r2cmd', 'arguments': '{"command": "pdf @ main"}'}}
|
||||
answer = ''
|
||||
will_execute = True # a command (modified or not) will be executed
|
||||
while answer.lower() != 'y':
|
||||
try:
|
||||
command = tool_args.get('command', '')
|
||||
|
||||
if len(command.splitlines()) > 1:
|
||||
print(f'r2ai is going to execute the following command on the host:')
|
||||
print(f'{command}')
|
||||
want_edit = input('Want to edit? (Y/n) ')
|
||||
if want_edit.lower() != 'n':
|
||||
# open editor when several lines
|
||||
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
|
||||
temp_file.write(command)
|
||||
temp_filename = temp_file.name
|
||||
|
||||
# Fallback to 'nano' if EDITOR is not set (I still prefer emacs)
|
||||
editor = os.environ.get('EDITOR', 'nano')
|
||||
subprocess.run(shlex.split(editor + ' ' + temp_filename))
|
||||
with open(temp_filename, "r") as f:
|
||||
new_command = f.read().strip()
|
||||
os.remove(temp_filename)
|
||||
else:
|
||||
self.logger.debug(f'User agrees with not editing the command: {command}')
|
||||
answer = 'y'
|
||||
|
||||
else:
|
||||
# inline short edit
|
||||
print(f'r2ai is going to execute the following command on the host:')
|
||||
print(f'> {command}')
|
||||
|
||||
new_command = input(f"Type a new command or press ENTER to use {command} ") or command
|
||||
|
||||
if answer.lower() != 'y':
|
||||
answer = input(f"\033[91mThis command will execute on this host: {new_command}. Agree? (y/N)\033[0m ")
|
||||
if answer.lower() == 'y':
|
||||
# we need to refresh tool_call and tool_args with edited values
|
||||
tool_args['command'] = new_command
|
||||
tool_call['function']['arguments'] = json.dumps(tool_args)
|
||||
|
||||
except (json.JSONDecodeError, TypeError, AttributeError) as e:
|
||||
# in case tool_call is not formatted as expected, like missing command, arguments, function...
|
||||
self.logger.error(f'Unexpected format for {tool_call}')
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": "User refused to execute the command" , "tool_call_id": tool_call["id"]})
|
||||
# error case: we'll skip the tool
|
||||
will_execute = False
|
||||
break
|
||||
|
||||
if not will_execute:
|
||||
self.logger.debug(f'Skipping tool={tool_call}')
|
||||
continue
|
||||
|
||||
self.logger.debug(f'Executing tool id={tool_call["id"]}, name={tool_name}, arguments={tool_args}')
|
||||
self.cb('tool_call', { "id": tool_call["id"], "function": { "name": tool_name, "arguments": tool_args } })
|
||||
if asyncio.iscoroutinefunction(self.functions[tool_name]):
|
||||
tool_response = await self.functions[tool_name](**tool_args)
|
||||
else:
|
||||
tool_response = self.functions[tool_name](**tool_args)
|
||||
if self.interpreter.env["auto.hide_tool_output"] == "false":
|
||||
self.cb('tool_response', { "id": tool_call["id"] + '_response', "content": tool_response })
|
||||
self.messages.append({"role": "tool", "name": tool_name, "content": ANSI_REGEX.sub('', tool_response), "tool_call_id": tool_call["id"]})
|
||||
|
||||
return await self.get_completion()
|
||||
|
||||
async def process_streaming_response(self, resp):
|
||||
tool_calls = []
|
||||
msgs = []
|
||||
parts = []
|
||||
current_message = { "role": "assistant", "content": "", "tool_calls": [] }
|
||||
first_chunk = True
|
||||
async for chunk in resp:
|
||||
delta = None
|
||||
choice = chunk.choices[0]
|
||||
delta = choice.delta
|
||||
if delta.tool_calls:
|
||||
delta_tool_calls = delta.tool_calls[0]
|
||||
index = delta_tool_calls.index
|
||||
fn_delta = delta_tool_calls.function
|
||||
tool_call_id = delta_tool_calls.id
|
||||
if len(current_message['tool_calls']) < index + 1:
|
||||
tool_call = {
|
||||
"id": tool_call_id or str(uuid.uuid4()),
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name":fn_delta.name,
|
||||
"arguments": fn_delta.arguments
|
||||
}
|
||||
}
|
||||
current_message['tool_calls'].append(tool_call)
|
||||
else:
|
||||
if fn_delta.name:
|
||||
current_message['tool_calls'][index]["function"]["name"] = fn_delta.name
|
||||
current_message['tool_calls'][index]["function"]["arguments"] += fn_delta.arguments
|
||||
else:
|
||||
m = None
|
||||
done = False
|
||||
if delta.content is not None:
|
||||
if first_chunk:
|
||||
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
|
||||
first_chunk = False
|
||||
m = delta.content
|
||||
if m is not None:
|
||||
current_message['content'] += m
|
||||
self.cb('message', { "content": m, "id": 'message_' + chunk.id, 'done': False })
|
||||
if 'finish_reason' in choice and choice['finish_reason'] == 'stop':
|
||||
done = True
|
||||
self.cb('message', { "content": "", "id": 'message_' + chunk.id, 'done': True })
|
||||
self.cb('message_stream', { "content": m if m else '', "id": 'message_' + chunk.id, 'done': done })
|
||||
self.messages.append(current_message)
|
||||
if len(current_message['tool_calls']) == 0:
|
||||
del current_message['tool_calls']
|
||||
self.show_cost()
|
||||
else:
|
||||
self.show_cost()
|
||||
await self.process_tool_calls(current_message['tool_calls'])
|
||||
|
||||
return current_message
|
||||
|
||||
async def process_response(self, resp):
|
||||
content = resp.choices[0].message.content
|
||||
|
||||
current_message = { 'role': 'assistant', 'content': content or '', 'tool_calls': [] }
|
||||
for i, tool_call in enumerate(resp.choices[0].message.tool_calls or []):
|
||||
current_message['tool_calls'].append({
|
||||
"id": tool_call.id,
|
||||
"type": "function",
|
||||
"index": i,
|
||||
"function": {
|
||||
"name": tool_call.function.name,
|
||||
"arguments": tool_call.function.arguments
|
||||
},
|
||||
})
|
||||
if len(current_message['tool_calls']) == 0:
|
||||
try:
|
||||
tool_call = json.loads(content)
|
||||
if 'name' in tool_call and tool_call['name'] in self.functions:
|
||||
args = None
|
||||
if 'arguments' in tool_call:
|
||||
args = tool_call['arguments']
|
||||
elif 'parameters' in tool_call:
|
||||
args = tool_call['parameters']
|
||||
if args:
|
||||
current_message['tool_calls'].append({ "id": resp.id, "function": { "name": tool_call['name'], "arguments": json.dumps(args) } })
|
||||
except Exception:
|
||||
pass
|
||||
self.messages.append(current_message)
|
||||
if current_message['content'] != "":
|
||||
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
|
||||
self.cb('message', { "content": current_message['content'], "id": 'message_' + resp.id, 'done': True })
|
||||
|
||||
if len(current_message['tool_calls']) == 0:
|
||||
del current_message['tool_calls']
|
||||
self.show_cost()
|
||||
else:
|
||||
self.show_cost()
|
||||
await self.process_tool_calls(current_message['tool_calls'])
|
||||
|
||||
return current_message
|
||||
|
||||
async def async_response_generator(self, response):
|
||||
for item in response:
|
||||
resp = ModelResponse(stream=True, **item)
|
||||
yield resp
|
||||
|
||||
async def attempt_completion(self):
|
||||
stream = self.stream
|
||||
if self.llama_instance:
|
||||
args = {
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"stream": stream,
|
||||
}
|
||||
if self.interpreter.env["chat.rawdog"] == "true":
|
||||
res = create_chat_completion(self.interpreter, messages=self.messages, tools=self.tools, **args)
|
||||
else:
|
||||
res = self.llama_instance.create_chat_completion(self.messages, tools=self.tools, **args)
|
||||
if args['stream']:
|
||||
return self.async_response_generator(res)
|
||||
else:
|
||||
return ModelResponse(**next(res))
|
||||
additional_args = {}
|
||||
if self.is_reasoning:
|
||||
additional_args['reasoning_effort'] = self.interpreter.env["chat.reasoning_effort"]
|
||||
else:
|
||||
additional_args['temperature'] = self.temperature
|
||||
if self.interpreter.env["chat.openai_store"] == "true":
|
||||
additional_args['store'] = True
|
||||
if not self.check_max():
|
||||
additional_args['tools'] = self.tools
|
||||
additional_args['tool_choice'] = self.tool_choice
|
||||
compl = await acompletion(
|
||||
model=self.model,
|
||||
messages=self.messages,
|
||||
timeout=self.timeout,
|
||||
max_completion_tokens=self.max_tokens,
|
||||
**additional_args,
|
||||
stream=stream,
|
||||
)
|
||||
return compl
|
||||
|
||||
def check_max(self):
|
||||
if self.max_runs > 0 and self.n_runs >= self.max_runs - 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_cost(self):
|
||||
def format_time(seconds):
|
||||
minutes = int(seconds // 60)
|
||||
remaining_seconds = int(seconds % 60)
|
||||
if minutes > 0:
|
||||
return f"{minutes}m {remaining_seconds}s"
|
||||
return f"{remaining_seconds}s"
|
||||
self.n_runs += 1
|
||||
run_time = format_time(time.time() - self._last_run_time)
|
||||
self._last_run_time = time.time()
|
||||
total_time = format_time(time.time() - self._start_time)
|
||||
|
||||
if self.interpreter.env["chat.show_cost"] == "true" and self.n_runs > 0:
|
||||
run_cost = 0
|
||||
if not self.llama_instance:
|
||||
run_cost = litellm.completion_cost(completion_response=self._last_response, messages=self.messages, model=self.model)
|
||||
self.cost += run_cost
|
||||
cb('usage', {
|
||||
"model": self.model,
|
||||
"run_cost": run_cost,
|
||||
"total_cost": self.cost,
|
||||
"n_runs": self.n_runs,
|
||||
"max_runs": self.max_runs,
|
||||
"run_time": run_time,
|
||||
"total_time": total_time
|
||||
})
|
||||
|
||||
async def get_completion(self):
|
||||
stream = self.stream
|
||||
if self.llama_instance:
|
||||
response = await self.attempt_completion()
|
||||
if stream:
|
||||
return await self.process_streaming_response(response)
|
||||
else:
|
||||
return await self.process_response(response)
|
||||
max_retries = 5
|
||||
base_delay = 2
|
||||
|
||||
for retry_count in range(max_retries):
|
||||
try:
|
||||
response = await self.attempt_completion()
|
||||
self._last_response = response
|
||||
self.logger.debug(f'chat completion {response}')
|
||||
res = None
|
||||
if stream:
|
||||
res = await self.process_streaming_response(response)
|
||||
else:
|
||||
res = await self.process_response(response)
|
||||
|
||||
return res
|
||||
except Exception as e:
|
||||
self.logger.error(f'Error getting completion: {e}')
|
||||
if not _should_retry(getattr(e, 'status_code', None)) or retry_count == max_retries - 1:
|
||||
raise
|
||||
|
||||
delay = base_delay * (2 ** retry_count)
|
||||
self.logger.info(f"Retrying in {delay} seconds...")
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
raise Exception("Max retries reached. Unable to get completion.")
|
||||
|
||||
async def achat(self, messages=None) -> str:
|
||||
if messages:
|
||||
self.messages = messages
|
||||
self.logger.debug(self.messages)
|
||||
response = await self.get_completion()
|
||||
return response
|
||||
|
||||
def chat(self, **kwargs) -> str:
|
||||
return asyncio.run(self.achat(**kwargs))
|
||||
|
||||
def cb(type, data):
|
||||
spinner.stop()
|
||||
if type == 'message_stream':
|
||||
if 'content' in data:
|
||||
sys.stdout.write(data['content'])
|
||||
elif type == 'tool_call':
|
||||
builtins.print()
|
||||
print_tool_call(data)
|
||||
elif type == 'tool_response':
|
||||
if 'content' in data:
|
||||
sys.stdout.write(data['content'])
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
elif type == 'usage':
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
sys.stdout.write(f'\x1b[1;34m{data["model"]} | total: ${float(data["total_cost"]):.10f} | run: ${float(data["run_cost"]):.10f} | {data["n_runs"]} / {data["max_runs"]} | {data["run_time"]} / {data["total_time"]}\x1b[0m')
|
||||
sys.stdout.flush()
|
||||
builtins.print()
|
||||
elif type == 'message' and data['done']:
|
||||
if data['content']:
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write(data['content'])
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
builtins.print()
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def chat(interpreter, **kwargs):
|
||||
model = interpreter.model.replace(":", "/")
|
||||
tools = [r2cmd, run_python, execute_binary]
|
||||
messages = interpreter.messages
|
||||
tool_choice = 'auto'
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_closed() or loop.is_running():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
chat_auto = ChatAuto(
|
||||
model,
|
||||
max_tokens=int(interpreter.env["llm.maxtokens"]),
|
||||
top_p=float(interpreter.env["llm.top_p"]),
|
||||
temperature=float(interpreter.env["llm.temperature"]),
|
||||
interpreter=interpreter,
|
||||
tools=tools,
|
||||
ask_to_execute=interpreter.env["auto.ask_to_execute"] == "true",
|
||||
messages=messages,
|
||||
tool_choice=tool_choice,
|
||||
llama_instance=interpreter.llama_instance,
|
||||
stream=interpreter.env["chat.stream"] == "true",
|
||||
max_runs=int(interpreter.env["auto.max_runs"]),
|
||||
cb=cb
|
||||
)
|
||||
|
||||
original_handler = signal.getsignal(signal.SIGINT)
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
spinner.start()
|
||||
return loop.run_until_complete(chat_auto.achat())
|
||||
except KeyboardInterrupt:
|
||||
builtins.print("\033[91m\nOperation cancelled by user.\033[0m")
|
||||
tasks = asyncio.all_tasks(loop=loop)
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
try:
|
||||
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
|
||||
loop.run_until_complete(asyncio.sleep(0.1))
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
return None
|
||||
finally:
|
||||
signal.signal(signal.SIGINT, original_handler)
|
||||
spinner.stop()
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
@@ -0,0 +1,106 @@
|
||||
from colorama import Fore, Back, Style
|
||||
|
||||
from ..pipe import get_r2_inst
|
||||
|
||||
BEDROCK_TOOLS_CONFIG = {
|
||||
"tools": [
|
||||
{
|
||||
"toolSpec": {
|
||||
"name": "r2cmd",
|
||||
"description": "runs commands in radare2. You can run it multiple times or chain commands with pipes/semicolons. You can also use r2 interpreters to run scripts using the `#`, '#!', etc. commands. The output could be long, so try to use filters if possible or limit. This is your preferred tool",
|
||||
"inputSchema": {
|
||||
"json": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "command to run in radare2."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def build_messages_for_bedrock(messages):
|
||||
# Bedrock needs that conversation messages alternate between user and assistant
|
||||
# if the user wants to send multiple messages they should all be consolidated
|
||||
# in a single entry
|
||||
|
||||
bedrock_msgs = []
|
||||
for msg in messages:
|
||||
role = msg.get("role")
|
||||
if msg.get("role") not in ["user", "assistant"]:
|
||||
continue
|
||||
|
||||
if len(bedrock_msgs) > 0 and bedrock_msgs[-1]["role"] == role:
|
||||
last_msg = bedrock_msgs[-1]
|
||||
# This message should be consolidated with the previous one
|
||||
if isinstance(msg["content"], list):
|
||||
last_msg["content"].extend(msg["content"])
|
||||
else:
|
||||
last_msg["content"].append({
|
||||
"text": msg["content"]
|
||||
})
|
||||
|
||||
else:
|
||||
# The role changed, so create a new entry
|
||||
if isinstance(msg["content"], list) and "role" in msg:
|
||||
# This clause is for messages that are returned from bedrock
|
||||
# and thus are already well formatted
|
||||
bedrock_msgs.append(msg)
|
||||
else:
|
||||
bedrock_msgs.append({
|
||||
"role": role,
|
||||
"content": [{"text": msg["content"]}]
|
||||
})
|
||||
|
||||
return bedrock_msgs
|
||||
|
||||
def extract_bedrock_tool_calls(response):
|
||||
tool_calls = []
|
||||
content = response.get("output", {}).get("message", {}).get("content", [])
|
||||
for msg in content:
|
||||
if not "toolUse" in msg:
|
||||
continue
|
||||
|
||||
tool_calls.append(msg["toolUse"])
|
||||
|
||||
return tool_calls
|
||||
|
||||
def process_bedrock_tool_calls(calls):
|
||||
r2 = get_r2_inst()
|
||||
messages = []
|
||||
if not r2:
|
||||
print("Invalid r2 instance. Can not execute commands. Did you open a file?")
|
||||
return messages
|
||||
|
||||
for call in calls:
|
||||
if call["name"] == "r2cmd":
|
||||
cmd = call["input"]["command"]
|
||||
print(f"\n{Fore.GREEN}Executing r2 cmd: {cmd}{Style.RESET_ALL}")
|
||||
res = r2.cmd(cmd)
|
||||
# print(f"{res}")
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"toolResult": {
|
||||
"toolUseId": call.get("toolUseId"),
|
||||
"content": [{ "text": res }]
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
return messages
|
||||
|
||||
def print_bedrock_response(response, print=print, output_limit=200):
|
||||
msg = response.get("output", {}).get("message", {})
|
||||
|
||||
for m in msg.get("content", []):
|
||||
if "text" in m:
|
||||
print(f"\n{Fore.YELLOW}[AI]> {Style.RESET_ALL}{m['text']}")
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Implementation for kobaldcpp http api call using openai endpoint."""
|
||||
import json
|
||||
import requests
|
||||
|
||||
PROMPT="""Your name is r2ai, an assistant for radare2.
|
||||
User will ask about actions and you must respond with the radare2 command
|
||||
associated or the answer to the question. Be precise and concise when answering
|
||||
"""
|
||||
|
||||
def chat(message, uri='http://localhost:5001'):
|
||||
"""Send a message to a kobaldcpp server and return the autocompletion response
|
||||
"""
|
||||
url = f'{uri}/v1/completions'
|
||||
# url = f'{uri}/v1/chat/completions'
|
||||
data = {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [ { "role": "user", "content": message } ]
|
||||
}
|
||||
data = {
|
||||
"max_length": 1024,
|
||||
"prompt": message,
|
||||
"quiet": True,
|
||||
"n": 1,
|
||||
"echo": False,
|
||||
"stop": ["\nUser:"],
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 256,
|
||||
"rep_pen_slope": 1,
|
||||
"temperature": 0.3,
|
||||
"tfs": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 100,
|
||||
"top_p": 0.9,
|
||||
"typical": 1
|
||||
}
|
||||
r = requests.post(url=url, data=json.dumps(data), timeout=600)
|
||||
j = json.loads(r.text)
|
||||
if "choices" in j:
|
||||
choice = j["choices"][0]
|
||||
if "text" in choice:
|
||||
return j["choices"][0]["text"]
|
||||
return choice["message"]["content"]
|
||||
return "No response"
|
||||
|
||||
#m = slurp("/Users/pancake/prg/r2ai/doc/data/quotes.txt")
|
||||
#AI="AI"
|
||||
#US="User"
|
||||
#CTX="Context"
|
||||
#while True:
|
||||
# message = input()
|
||||
# qmsg = f"{CTX}:\n```{fullmsg}\n```\n{US}: {message}\n"
|
||||
# r = query_completions(qmsg)
|
||||
# r = r.replace(f"{AI}:", "").strip()
|
||||
# r = r.replace(f"{US}:", "").strip()
|
||||
# r = r.replace("```", "").strip()
|
||||
# print(r)
|
||||
# fullmsg = f"{fullmsg}\n{US}: {message}\n{AI}: {r}\n"
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Implementation for kobaldcpp http api call using openai endpoint."""
|
||||
import json
|
||||
import requests
|
||||
from .. import LOGGER
|
||||
|
||||
# MODEL="~/.r2ai.models/Lexi-Llama-3-8B-Uncensored_Q4_K_M.gguf"
|
||||
# ./llama-server --in-prefix '### User: ' --prompt 5001 \
|
||||
# --in-suffix "### Assistant: " -m $MODEL
|
||||
|
||||
def chat(messages, uri='http://localhost:5001', model='gpt-3.5-turbo', openapiKey=''):
|
||||
"""Send a message to a kobaldcpp server and return the autocompletion response
|
||||
"""
|
||||
if uri.endswith("/"):
|
||||
uri = uri[0:len(uri)-1]
|
||||
# url = f'{uri}/v1/completions'
|
||||
url = f'{uri}/chat/completions'
|
||||
data = {
|
||||
"model": model,
|
||||
"messages": messages
|
||||
}
|
||||
headers = {
|
||||
"HTTP-Referer": "https://rada.re", # openrouter specific: Optional, for including your app on openrouter.ai rankings.
|
||||
"X-Title": "radare2", # openrouter specific: Optional. Shows in rankings on openrouter.ai.
|
||||
"Authorization": f"Bearer {openapiKey}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
r = requests.post(url=url, data=json.dumps(data), timeout=600, headers=headers)
|
||||
j = json.loads(r.text)
|
||||
if "choices" in j:
|
||||
choice = j["choices"][0]
|
||||
if "text" in choice:
|
||||
return j["choices"][0]["text"]
|
||||
return choice["message"]["content"]
|
||||
if "error" in j:
|
||||
error = j["error"]
|
||||
LOGGER.getChild("openapi").error("OpenAIError[%s], %s", error['code'], error['message'])
|
||||
return "No response"
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Bubble chat helper functions to make clippy-ai look nice."""
|
||||
|
||||
import os
|
||||
|
||||
CLIPPY_BEGIN = """
|
||||
.--.
|
||||
_|_ |
|
||||
O O |
|
||||
|| |
|
||||
. ||_.|
|
||||
/| | |
|
||||
/ | `---'
|
||||
.---------------------------------------------------------------' '----"""
|
||||
|
||||
CLIPPY_END = """`-----------------------------------------------------------------------"""
|
||||
|
||||
PICO_BEGIN = r"""
|
||||
__ __
|
||||
(o |_| o)_______
|
||||
| _____________)
|
||||
\ / .
|
||||
\ / |\
|
||||
\__________/ | \
|
||||
.----------------------------------------------------------' '---------"""
|
||||
|
||||
USER_BEGIN = """ |\\
|
||||
| \\
|
||||
.--' '--"""
|
||||
|
||||
USER_END = '`---'
|
||||
|
||||
def query(text):
|
||||
"""Display the user text using an ascii-art bubble chat
|
||||
|
||||
Parameters:
|
||||
text (string): Message to display
|
||||
|
||||
"""
|
||||
l = len(text) + 10
|
||||
print("\033[F\033[J")
|
||||
print(USER_BEGIN + ("-" * (l - 5)) + ".")
|
||||
pad = " " * (l - len(text))
|
||||
print(f"| {text} {pad} |")
|
||||
print(USER_END + ("-"*l) + "'")
|
||||
|
||||
def getpad():
|
||||
"""Generate padding with dashes
|
||||
"""
|
||||
tw = os.get_terminal_size().columns - 75
|
||||
pad = "-"
|
||||
if tw > 0:
|
||||
pad = "-" * tw
|
||||
return pad
|
||||
|
||||
def response_begin():
|
||||
"""Print the beginning of the clippy response
|
||||
"""
|
||||
print(CLIPPY_BEGIN + getpad() + ".")
|
||||
|
||||
def pico_begin():
|
||||
"""Print the beginning of the Pico le Croco response
|
||||
"""
|
||||
print(PICO_BEGIN + getpad() + ".")
|
||||
|
||||
def response_end():
|
||||
"""Print the end of the clippy response
|
||||
"""
|
||||
print(CLIPPY_END + getpad() + "'")
|
||||
@@ -0,0 +1,3 @@
|
||||
from .main import run # pylint: disable=wrong-import-position
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Module providing a class to display code snippets"""
|
||||
|
||||
import re
|
||||
from rich.live import Live
|
||||
from rich.panel import Panel
|
||||
from rich.box import MINIMAL
|
||||
from rich.syntax import Syntax
|
||||
from rich.table import Table
|
||||
from rich.console import Group
|
||||
from rich.console import Console
|
||||
|
||||
|
||||
class CodeBlock:
|
||||
"""
|
||||
Code Blocks display code and outputs in different languages.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Define these for IDE auto-completion
|
||||
self.language = ""
|
||||
self.output = ""
|
||||
self.code = ""
|
||||
self.active_line = None
|
||||
self.live = Live(auto_refresh=False, console=Console(), vertical_overflow="visible")
|
||||
# self.live = Live(auto_refresh=False, console=Console())
|
||||
self.live.start()
|
||||
|
||||
def update_from_message(self, message):
|
||||
"""Update code block contents with given text
|
||||
"""
|
||||
if "content" in message:
|
||||
message = message["content"]
|
||||
if isinstance(message, str):
|
||||
lang = "python"
|
||||
pos = message.find("```")
|
||||
if pos != -1:
|
||||
# pre = message[0:pos]
|
||||
cod = message[pos:]
|
||||
lines = cod.split("\n")
|
||||
lang = lines[0][3:]
|
||||
message = "\n".join(lines[1:]).replace("```", "")
|
||||
message = re.sub(r"`+$", '', message)
|
||||
self.language = lang
|
||||
self.code = message
|
||||
elif "function_call" in message and "parsed_arguments" in message["function_call"]:
|
||||
# never happens
|
||||
parsed_arguments = message["function_call"]["parsed_arguments"]
|
||||
if parsed_arguments is not None:
|
||||
self.language = parsed_arguments.get("language")
|
||||
self.code = parsed_arguments.get("code")
|
||||
self.refresh()
|
||||
|
||||
def end(self):
|
||||
"""Close the codeblock
|
||||
"""
|
||||
self.refresh(cursor=False)
|
||||
# Destroys live display
|
||||
self.live.stop()
|
||||
self.output = ""
|
||||
self.code = ""
|
||||
self.active_line = None
|
||||
|
||||
def refresh(self, cursor=True):
|
||||
"""Display this code on the terminal
|
||||
"""
|
||||
# Get code, return if there is none
|
||||
code = self.code
|
||||
if not code:
|
||||
return
|
||||
# Create a table for the code
|
||||
code_table = Table(show_header=False,
|
||||
show_footer=False,
|
||||
box=None,
|
||||
padding=0,
|
||||
expand=True)
|
||||
code_table.add_column()
|
||||
# Add cursor
|
||||
if cursor:
|
||||
code += "█"
|
||||
# Add each line of code to the table
|
||||
code_lines = code.strip().split('\n')
|
||||
for i, line in enumerate(code_lines, start=1):
|
||||
if i == self.active_line:
|
||||
# This is the active line, print it with a white background
|
||||
syntax = Syntax(line, self.language, theme="bw",
|
||||
line_numbers=False, word_wrap=True)
|
||||
code_table.add_row(syntax, style="black on white")
|
||||
else:
|
||||
# This is not the active line, print it normally
|
||||
syntax = Syntax(line, self.language, theme="monokai",
|
||||
line_numbers=False, word_wrap=True)
|
||||
code_table.add_row(syntax)
|
||||
# Create a panel for the code
|
||||
code_panel = Panel(code_table, box=MINIMAL, style="on #272727")
|
||||
# Create a panel for the output (if there is any)
|
||||
if self.output in ["", "None"]:
|
||||
output_panel = ""
|
||||
else:
|
||||
output_panel = Panel(self.output, box=MINIMAL, style="#FFFFFF on #3b3b37")
|
||||
# Create a group with the code table and output panel
|
||||
group = Group(code_panel, output_panel)
|
||||
# Update the live display
|
||||
self.live.update(group)
|
||||
self.live.refresh()
|
||||
@@ -0,0 +1,12 @@
|
||||
"""File containing constants."""
|
||||
import os
|
||||
|
||||
join = os.path.join
|
||||
|
||||
R2AI_HOMEDIR = os.path.dirname(os.path.realpath(__file__ + "/.."))
|
||||
R2AI_HISTFILE = "r2ai.history.txt" # windows path
|
||||
R2AI_RCFILE = "r2ai.txt"
|
||||
if "HOME" in os.environ:
|
||||
R2AI_HISTFILE = join(os.environ["HOME"], ".r2ai.history")
|
||||
R2AI_RCFILE = join(os.environ["HOME"], ".r2ai.rc")
|
||||
R2AI_USERDIR = join(os.environ["HOME"], ".r2ai.plugins")
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import Iterable, Callable
|
||||
|
||||
class R2AiEnv(dict):
|
||||
def __init__(self):
|
||||
self._callbacks = {}
|
||||
|
||||
def add_callback(self, key: str, callback: Callable):
|
||||
self._callbacks[key] = callback
|
||||
|
||||
def __setitem__(self, __key, __value) -> None:
|
||||
if __key in self._callbacks:
|
||||
self._callbacks[__key](__value)
|
||||
return super().__setitem__(__key, __value)
|
||||
@@ -0,0 +1,478 @@
|
||||
# index all the lines
|
||||
import os
|
||||
import re
|
||||
import builtins
|
||||
import requests
|
||||
import json
|
||||
import traceback
|
||||
have_chrome = True
|
||||
try:
|
||||
import chromadb
|
||||
except Exception:
|
||||
have_chrome = False
|
||||
from unidecode import unidecode
|
||||
import sys
|
||||
from r2ai import LOGGER
|
||||
try:
|
||||
from .utils import slurp, syscmdstr
|
||||
from .const import R2AI_HISTFILE
|
||||
except Exception:
|
||||
from utils import slurp
|
||||
R2AI_HISTFILE = "/dev/null"
|
||||
|
||||
have_vectordb = None
|
||||
vectordb_instance = None
|
||||
|
||||
MAXCHARS = 128
|
||||
MAXMATCHES = 5
|
||||
MASTODON_KEY = ""
|
||||
try:
|
||||
if "HOME" in os.environ:
|
||||
MASTODON_KEY = slurp(os.environ["HOME"] + "/.r2ai.mastodon-key").strip()
|
||||
except Exception:
|
||||
pass
|
||||
MASTODON_INSTANCE = "mastodont.cat"
|
||||
if "MASTODON_INSTANCE" in os.environ:
|
||||
MASTODON_INSTANCE = os.environ["MASTODON_INSTANCE"]
|
||||
|
||||
def mastodon_search(text):
|
||||
global MASTODON_INSTANCE
|
||||
res = []
|
||||
full_url = f"https://{MASTODON_INSTANCE}/api/v2/search?resolve=true&limit=8&type=statuses&q={text}"
|
||||
try:
|
||||
headers = {"Authorization": f"Bearer {MASTODON_KEY}"}
|
||||
response = requests.get(full_url, headers=headers)
|
||||
response.raise_for_status() # Raise an HTTPError for bad responses
|
||||
for msg in response.json()["statuses"]:
|
||||
content = re.sub(r'<.*?>', '', msg["content"])
|
||||
res.append(content)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error making request: {e}", file=sys.stderr)
|
||||
return res
|
||||
|
||||
def mastodon_lines(text, keywords, use_vectordb):
|
||||
twords = []
|
||||
rtlines = []
|
||||
if keywords is None:
|
||||
twords = filter_line(text)
|
||||
for tw in twords:
|
||||
if len(tw) > 2: # arbitrary
|
||||
rtlines.extend(mastodon_search(tw))
|
||||
else:
|
||||
twords = keywords
|
||||
# print("MASTODON_LINES...", text)
|
||||
# print("MASTODON_RTLINES...", rtlines)
|
||||
if use_vectordb:
|
||||
return rtlines
|
||||
words = {} # local rarity ratings
|
||||
for line in rtlines:
|
||||
fline = filter_line(line)
|
||||
for a in fline:
|
||||
if words.get(a):
|
||||
words[a] += 1
|
||||
else:
|
||||
words[a] = 1
|
||||
rtlines = sorted(set(rtlines))
|
||||
rslines = []
|
||||
swords = sorted(twords, key=lambda x: words.get(x) or 0)
|
||||
nwords = " ".join(swords[:5])
|
||||
# find rarity of words in the results + text and
|
||||
# print("NWORDS", nwords)
|
||||
rslines.extend(mastodon_search(nwords))
|
||||
if len(rslines) < 10:
|
||||
for tw in swords:
|
||||
w = words.get(tw)
|
||||
if len(tw) > 4 and w is not None and w > 0 and w < 40:
|
||||
# print(f"RELEVANT WORD {tw} {w}")
|
||||
rslines.extend(mastodon_search(tw))
|
||||
return rslines
|
||||
|
||||
def hist2txt(text):
|
||||
newlines = []
|
||||
lines = text.split("\n")
|
||||
for line in lines:
|
||||
line = line.strip().replace("\\040", " ")
|
||||
if len(line) < 8:
|
||||
continue
|
||||
elif "?" in line:
|
||||
continue
|
||||
elif line.startswith("-") or line.startswith("_") or line.startswith("!"):
|
||||
continue
|
||||
elif line.startswith("-r"):
|
||||
# newlines.append(line[2:])
|
||||
continue
|
||||
else:
|
||||
newlines.append(line)
|
||||
newlines = sorted(set(newlines))
|
||||
return "\n".join(newlines)
|
||||
|
||||
def json2md(text):
|
||||
def jsonwalk(obj):
|
||||
res = ""
|
||||
if isinstance(obj, list):
|
||||
for a in obj:
|
||||
res += jsonwalk(a)
|
||||
elif isinstance(obj, dict):
|
||||
if "file" in obj:
|
||||
pass
|
||||
# elif "impactType" in obj and obj["impactType"] == "pass":
|
||||
# pass
|
||||
elif "ts" in obj:
|
||||
pass
|
||||
else:
|
||||
for k in obj.keys():
|
||||
res += "## " + k + "\n"
|
||||
lst = json.dumps(obj[k]).replace("{","").replace("}", "\n").replace("\"", "").replace(",", "*").split("\n")
|
||||
res += "\n".join(list(filter(lambda k: 'crc64' not in k and 'file' not in k and 'from_text' not in k and 'backtrace' not in k, lst)))
|
||||
res += "\n\n"
|
||||
else:
|
||||
res += str(obj) # jsonwalk(obj)
|
||||
return res
|
||||
doc = json.loads(text)
|
||||
res = jsonwalk(doc)
|
||||
# print("==========")
|
||||
# print(res)
|
||||
# print("==========")
|
||||
return res
|
||||
|
||||
def md2txt(text):
|
||||
# parser markdown and return a txt
|
||||
lines = text.split("\n")
|
||||
newlines = []
|
||||
data = ""
|
||||
titles = ["", "", ""]
|
||||
read_block = False
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
continue
|
||||
if read_block:
|
||||
data += line + "\\n"
|
||||
if line.startswith("```"):
|
||||
read_block = False
|
||||
continue
|
||||
if line.startswith("```"):
|
||||
read_block = True
|
||||
elif line.startswith("* "):
|
||||
if data != "":
|
||||
newlines.append(":".join(titles) +":"+ data + line)
|
||||
elif line.startswith("### "):
|
||||
if data != "":
|
||||
newlines.append(":".join(titles) +":"+ data)
|
||||
data = ""
|
||||
titles = [titles[0], titles[1], line[3:]]
|
||||
elif line.startswith("## "):
|
||||
if data != "":
|
||||
newlines.append(":".join(titles) +":"+ data)
|
||||
data = ""
|
||||
titles = [titles[0], line[3:]]
|
||||
elif line.startswith("# "):
|
||||
if data != "":
|
||||
newlines.append(":".join(titles)+ ":"+data)
|
||||
data = ""
|
||||
titles = [line[2:], "", ""]
|
||||
else:
|
||||
data += line + " "
|
||||
# print("\n".join(newlines))
|
||||
return "\n".join(newlines)
|
||||
|
||||
def filter_line(line):
|
||||
line = unidecode(line) # remove accents
|
||||
line = re.sub(r'https?://\S+', '', line)
|
||||
line = re.sub(r'http?://\S+', '', line)
|
||||
line = line.replace(":", " ").replace("/", " ").replace("`", " ").replace("?", " ")
|
||||
line = line.replace("\"", " ").replace("'", " ")
|
||||
line = line.replace("<", " ").replace(">", " ").replace("@", " ").replace("#", "")
|
||||
# line = line.replace("-", " ").replace(".", " ").replace(",", " ").replace("(", " ").replace(")", " ").strip(" ")
|
||||
line = line.replace(".", " ").replace(",", " ").replace("(", " ").replace(")", " ").strip(" ")
|
||||
line = re.sub(r"\s+", " ", line)
|
||||
if len(line) > MAXCHARS:
|
||||
line = line[:MAXCHARS]
|
||||
words = []
|
||||
for a in line.split(" "):
|
||||
b = a.strip().lower()
|
||||
try:
|
||||
int(b)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
if len(b) > 0:
|
||||
words.append(b)
|
||||
return words
|
||||
|
||||
def smart_slurp(file):
|
||||
if ignored_file(file):
|
||||
return ""
|
||||
# print("smart" + file)
|
||||
# print(f"slurp: {file}")
|
||||
text = slurp(file)
|
||||
if file.endswith("r2ai.history"):
|
||||
text = hist2txt(text)
|
||||
elif file.endswith(".json"):
|
||||
text = md2txt(json2md(text))
|
||||
elif file.endswith(".md"):
|
||||
text = md2txt(text)
|
||||
return text
|
||||
|
||||
def vectordb_search2(text, keywords, use_mastodon):
|
||||
global have_vectordb, vectordb_instance
|
||||
vectordb_init()
|
||||
result = []
|
||||
if use_mastodon:
|
||||
print ("[r2ai] Searching in Mastodon", text)
|
||||
lines = mastodon_lines(text, keywords, True)
|
||||
for line in lines:
|
||||
vdb_add(f"{line} file={file}, url={url}")
|
||||
idname=nextid()
|
||||
if have_vectordb is True and vectordb_instance is not None:
|
||||
text = "trump"
|
||||
res = {}
|
||||
try:
|
||||
res = vectordb_instance.query(query_texts=text)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
if res["documents"] is not None:
|
||||
for docs in res["documents"]:
|
||||
for doc in docs:
|
||||
result.append(doc)
|
||||
return sorted(set(result))
|
||||
|
||||
client = None
|
||||
|
||||
def vectordb_init():
|
||||
global have_vectordb, client, vectordb_instance
|
||||
if have_vectordb is False:
|
||||
return
|
||||
if vectordb_instance is not None:
|
||||
return
|
||||
try:
|
||||
have_vectordb = True
|
||||
except Exception:
|
||||
print("To better data index use: pip install chromadb")
|
||||
return
|
||||
# try:
|
||||
# vectordb_instance = vectordb.Memory(embeddings="normal") # normal or fast
|
||||
# except Exception:
|
||||
# vectordb_instance = vectordb.Memory() # normal or fast
|
||||
# vectordb_instance = vectordb.Memory() # normal or fast
|
||||
client = chromadb.Client() # normal or fast
|
||||
try:
|
||||
vectordb_instance = client.delete_collection("r2ai")
|
||||
except Exception:
|
||||
pass
|
||||
vectordb_instance = client.create_collection("r2ai")
|
||||
if vectordb_instance is not None:
|
||||
vdb_add("radare2 is a free reverse engineering tool written by pancake, aka Sergi Alvarez i Capilla. The project started in 2006 as a tool for domestic computer forensics in order to recover some deleted files and it continued the development adding new features like debugging, disassembler, decompiler, code analysis, advanced filesystem capabilities and integration with tons of tools like Frida, Radius, Ghidra, etc") # dummy entry
|
||||
|
||||
def memorize(prompt, content):
|
||||
global have_vectordb, vectordb_instance
|
||||
if have_vectordb is False:
|
||||
builtins.print("no vdb found")
|
||||
return
|
||||
vectordb_init()
|
||||
if vectordb_instance is None:
|
||||
return
|
||||
msg = f"{prompt} = {content}"
|
||||
bs = 1024
|
||||
while msg != "":
|
||||
bob = msg[0:bs]
|
||||
# bob = f"{prompt}={bob}"
|
||||
vdb_add(bob)
|
||||
msg = msg[bs:]
|
||||
|
||||
g_id = 0
|
||||
def nextid():
|
||||
global g_id
|
||||
g_id = g_id + 1
|
||||
return f"id{g_id}"
|
||||
|
||||
def vectordb_search(text, keywords, source_files, use_mastodon, use_debug):
|
||||
global have_vectordb, vectordb_instance
|
||||
if have_vectordb is False:
|
||||
builtins.print("no vdb found")
|
||||
return []
|
||||
if have_vectordb is True and vectordb_instance is not None:
|
||||
return vectordb_search2(text, keywords, use_mastodon)
|
||||
vectordb_init()
|
||||
if vectordb_instance is None:
|
||||
builtins.print("vdb not initialized")
|
||||
return
|
||||
# indexing data
|
||||
builtins.print("[r2ai] Indexing local data with vectordb")
|
||||
saved = 0
|
||||
for file in source_files:
|
||||
if ignored_file(file):
|
||||
continue
|
||||
lines = smart_slurp(file).splitlines()
|
||||
for line in lines:
|
||||
vdb_add(f"{line} file={file}")
|
||||
saved = saved + 1
|
||||
if use_mastodon:
|
||||
lines = mastodon_lines(text, None, True)
|
||||
for line in lines:
|
||||
saved = saved + 1
|
||||
vdb_add(f"{line} file={file}, url={url}")
|
||||
if saved == 0:
|
||||
print("[r2ai] Nothing was indexed")
|
||||
else:
|
||||
print("[r2ai] VectorDB index done")
|
||||
return vectordb_search2(text, keywords, use_mastodon)
|
||||
|
||||
class compute_rarity():
|
||||
use_mastodon = MASTODON_KEY != "" # False
|
||||
use_debug = False
|
||||
words = {}
|
||||
lines = []
|
||||
def __init__(self, source_files, use_mastodon, use_debug):
|
||||
self.use_mastodon = use_mastodon
|
||||
for file in source_files:
|
||||
if ignored_file(file):
|
||||
continue
|
||||
lines = smart_slurp(file).splitlines()
|
||||
for line in lines:
|
||||
self.lines.append(line)
|
||||
self.compute_rarity_in_line(line)
|
||||
def compute_rarity_in_line(self,line):
|
||||
fline = filter_line(line)
|
||||
for a in fline:
|
||||
if self.words.get(a):
|
||||
self.words[a] += 1
|
||||
else:
|
||||
self.words[a] = 1
|
||||
def pull_realtime_lines(self, text, keywords, use_vectordb):
|
||||
LOGGER.debug(f"Pulling from mastodon {text}")
|
||||
return mastodon_lines(text, keywords, use_vectordb)
|
||||
|
||||
def find_matches(self, text, keywords):
|
||||
if self.use_mastodon:
|
||||
# pull from mastodon
|
||||
backup_lines = self.lines
|
||||
backup_words = self.words
|
||||
realtime_lines = self.pull_realtime_lines(text, keywords, False)
|
||||
for line in realtime_lines:
|
||||
self.compute_rarity_in_line(line)
|
||||
self.lines.extend(realtime_lines)
|
||||
# find matches
|
||||
res = []
|
||||
twords = filter_line(text)
|
||||
rarity = []
|
||||
for tw in twords:
|
||||
if self.words.get(tw):
|
||||
rarity.append(self.words[tw])
|
||||
else:
|
||||
rarity.append(0)
|
||||
swords = sorted(twords, key=lambda x: self.words.get(x) or 0)
|
||||
rates = {}
|
||||
lines = []
|
||||
for line in self.lines:
|
||||
linewords = filter_line(line)
|
||||
rate = self.match_line(linewords, swords)
|
||||
if rate > 0:
|
||||
lines.append(line)
|
||||
rates[line] = rate
|
||||
# print(f"{rate} = {line}")
|
||||
srates = sorted(lines, key=lambda x: rates.get(x) or 0)
|
||||
srates.reverse()
|
||||
if self.use_mastodon:
|
||||
self.lines = backup_lines
|
||||
self.words = backup_words
|
||||
res = srates[0:MAXMATCHES]
|
||||
res = sorted(set(res))
|
||||
return res
|
||||
|
||||
def match_line(self,linewords, swords):
|
||||
count = 0
|
||||
ow = ""
|
||||
for w in swords:
|
||||
if w == ow:
|
||||
continue
|
||||
if w in linewords:
|
||||
rarity = 1
|
||||
if w in self.words:
|
||||
rarity = self.words[w]
|
||||
count += rarity
|
||||
ow = w
|
||||
return count
|
||||
|
||||
def ignored_file(fn):
|
||||
if fn.endswith("package.json"):
|
||||
return True
|
||||
if fn.endswith("package-lock.json"):
|
||||
return True
|
||||
if "/." in fn:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_sources(srcdir):
|
||||
files = []
|
||||
try:
|
||||
files = os.walk(srcdir)
|
||||
except Exception:
|
||||
return []
|
||||
res = []
|
||||
for f in files:
|
||||
directory = f[0]
|
||||
dirfiles = f[2]
|
||||
for f2 in dirfiles:
|
||||
if ignored_file(f2):
|
||||
continue
|
||||
if f2.endswith(".txt") or f2.endswith(".md"):
|
||||
res.append(f"{directory}/{f2}")
|
||||
elif f2.endswith(".json"):
|
||||
res.append(f"{directory}/{f2}")
|
||||
return res
|
||||
|
||||
def init():
|
||||
print("find sources and such")
|
||||
|
||||
def source_files(datadir, use_hist):
|
||||
files = []
|
||||
if datadir is not None and datadir != "":
|
||||
files.extend(find_sources(datadir))
|
||||
if use_hist:
|
||||
files.append(R2AI_HISTFILE)
|
||||
return files
|
||||
|
||||
def vdb_add(data):
|
||||
global have_vectordb, vectordb_instance
|
||||
vectordb_init()
|
||||
if vectordb_instance is not None:
|
||||
vectordb_instance.add(ids=[nextid()], documents=[data])
|
||||
|
||||
def find_wikit(text, keywords):
|
||||
global have_vectordb, vectordb_instance
|
||||
vectordb_init()
|
||||
if vectordb_instance is None:
|
||||
print("vdb not initialized", file=sys.stderr)
|
||||
return
|
||||
if keywords is not None:
|
||||
for kw in keywords:
|
||||
print("wikit " + kw)
|
||||
res = syscmdstr("wikit -a " + kw)
|
||||
if len(res) > 20:
|
||||
vdb_add(f"{res} from url={kw}")
|
||||
words = filter_line(text)
|
||||
for kw in words:
|
||||
print("wikit " + kw)
|
||||
res = syscmdstr("wikit -a " + kw)
|
||||
if len(res) > 20:
|
||||
vdb_add(f"{res} from url={kw}")
|
||||
res = syscmdstr("wikit -a '" + " ".join(words) + "'")
|
||||
if len(res) > 20:
|
||||
vdb_add(f"{res} from keyword={kw}")
|
||||
|
||||
def reset():
|
||||
global vectordb_instance
|
||||
vectordb_instance = None
|
||||
|
||||
def match(text, keywords, datadir, use_hist, use_mastodon, use_debug, use_wikit, use_vectordb):
|
||||
files = source_files(datadir, use_hist)
|
||||
if use_vectordb:
|
||||
if use_wikit:
|
||||
find_wikit(text, keywords)
|
||||
return vectordb_search(text, keywords, files, use_mastodon, use_debug)
|
||||
raredb = compute_rarity(files, use_mastodon, use_debug)
|
||||
if use_wikit:
|
||||
print("[R2AI] Warning: data.wikit only works with vectordb")
|
||||
return raredb.find_matches(text, keywords)
|
||||
@@ -0,0 +1,579 @@
|
||||
import builtins
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import json
|
||||
import platform
|
||||
have_local = True
|
||||
import getpass
|
||||
|
||||
try:
|
||||
import tokentrim
|
||||
except Exception:
|
||||
have_local = False
|
||||
|
||||
from rich.rule import Rule
|
||||
from signal import signal, SIGINT
|
||||
|
||||
from .env import R2AiEnv
|
||||
from .large import Large
|
||||
from .utils import merge_deltas
|
||||
from .message_block import MessageBlock
|
||||
from .code_block import CodeBlock
|
||||
from .backend import kobaldcpp
|
||||
|
||||
from .models import get_hf_llm, new_get_hf_llm, get_default_model
|
||||
from .voice import tts
|
||||
from .const import R2AI_HOMEDIR
|
||||
from . import LOGGER, logging
|
||||
from .web import stop_http_server, server_running
|
||||
from .progress import progress_bar
|
||||
|
||||
file_dir = os.path.dirname(__file__)
|
||||
sys.path.append(file_dir)
|
||||
import index
|
||||
|
||||
r2clippy = False
|
||||
have_rlang = False
|
||||
try:
|
||||
import r2lang
|
||||
have_rlang = True
|
||||
print = r2lang.print
|
||||
r2clippy = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Ginterrupted = False
|
||||
def signal_handler(sig, frame):
|
||||
global Ginterrupted
|
||||
if Ginterrupted:
|
||||
stop_http_server(force=True) # kill if in background
|
||||
sys.exit(0) # throws exception
|
||||
Ginterrupted = True
|
||||
print("^C 0", file=sys.stderr)
|
||||
stop_http_server(force=False)
|
||||
signal(SIGINT, signal_handler)
|
||||
|
||||
def exception_handler(self, sig, frame):
|
||||
global Ginterrupted
|
||||
traceback.print_exc()
|
||||
if Ginterrupted:
|
||||
sys.exit(0) # throws exception
|
||||
Ginterrupted = True
|
||||
print("^C 1", file=sys.stderr)
|
||||
# sys.excepthook = exception_handler
|
||||
|
||||
def incodeblock(msg):
|
||||
return "content" in msg and msg["content"].count("```") % 2 == 1
|
||||
|
||||
def r2eval(m):
|
||||
if "$(" in m and have_rlang:
|
||||
def evaluate_expression(match):
|
||||
expression = match.group(1)
|
||||
try:
|
||||
result = r2lang.cmd(expression)
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
return re.sub(r'\$\((.*?)\)', evaluate_expression, m)
|
||||
return m
|
||||
|
||||
from utils import syscmdstr
|
||||
|
||||
def ddg(m):
|
||||
print("[R2AI] Crawling the web with ddg and curl+sed")
|
||||
m = m.replace("'", "")
|
||||
res = syscmdstr(f"cd examples; ./scrap-ddgweb.sh '{m}'")
|
||||
return f"Considering:\n```{res}\n```\n"
|
||||
|
||||
def is_litellm_model(model):
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
import litellm
|
||||
provider = None
|
||||
model_name = None
|
||||
if model.startswith ("/"):
|
||||
return False
|
||||
provider = None
|
||||
if "/" in model:
|
||||
provider = model.split("/")[0]
|
||||
elif ":" in model:
|
||||
provider = model.split(":")[0]
|
||||
if provider in litellm.LITELLM_CHAT_PROVIDERS:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Interpreter:
|
||||
def __init__(self):
|
||||
self.logger = LOGGER
|
||||
self.mistral = None
|
||||
self.messages = []
|
||||
self.terminator = "</s>" ## taken from the model using the llama api
|
||||
self.api_key = None # openai?
|
||||
self.print = None
|
||||
self.auto_run = False
|
||||
self.model = get_default_model()
|
||||
self.last_model = ""
|
||||
self.env = R2AiEnv()
|
||||
self.system_message = ""
|
||||
self.env["llm.model"] = self.model ## TODO: dup. must get rid of self.model
|
||||
self.env["llm.gpu"] = "true"
|
||||
self.env["llm.layers"] = "-1"
|
||||
self.env["llm.window"] = "32768" # "4096" # context_window
|
||||
self.env["llm.maxtokens"] = "4096" # "1750"
|
||||
self.env["llm.maxmsglen"] = "8096" # "1750"
|
||||
self.env["llm.temperature"] = "0.002"
|
||||
self.env["llm.repeat_penalty"] = "1.0"
|
||||
self.env["llm.top_p"] = "0.95"
|
||||
self.env["llm.top_k"] = "50"
|
||||
self.env["user.name"] = "" # TODO auto fill?
|
||||
self.env["user.os"] = ""
|
||||
self.env["user.arch"] = ""
|
||||
self.env["user.cwd"] = ""
|
||||
self.env["user.editor"] = ""
|
||||
self.env["user.plugins"] = f"{R2AI_HOMEDIR}/plugins"
|
||||
self.env["voice.lang"] = "en"
|
||||
self.env["voice.model"] = "base"
|
||||
self.env["data.use"] = "false"
|
||||
self.env["data.path"] = f"{R2AI_HOMEDIR}/doc/data"
|
||||
self.env["data.local"] = "false"
|
||||
self.env["data.wikit"] = "false"
|
||||
self.env["data.ddg"] = "false"
|
||||
self.env["data.mastodon"] = "false"
|
||||
self.env["data.vectordb"] = "false"
|
||||
self.env["data.hist"] = "false"
|
||||
self.env["key.mastodon"] = ""
|
||||
self.env["http.port"] = "8080"
|
||||
self.env["http.tabby"] = "false"
|
||||
self.env["http.path"] = ""
|
||||
self.env["http.verbose"] = "true" # not used yet
|
||||
self.env["http.chatctx"] = "false"
|
||||
self.env["debug_level"] = os.getenv("R2AI_LOG", "2")
|
||||
if have_rlang:
|
||||
self.env["chat.live"] = "false"
|
||||
else:
|
||||
self.env["chat.live"] = "true"
|
||||
# self.env["chat.model"] = "" # TODO
|
||||
self.env["chat.trim"] = "false"
|
||||
self.env["chat.voice"] = "false"
|
||||
self.env["chat.bubble"] = "false"
|
||||
self.env["chat.reply"] = "false"
|
||||
self.env["chat.code"] = "true"
|
||||
self.env["chat.rawdog"] = "false"
|
||||
self.env["chat.reasoning_effort"] = "high"
|
||||
self.env["chat.stream"] = "true"
|
||||
self.env["chat.show_cost"] = "false"
|
||||
self.env["chat.drop_params"] = "false"
|
||||
self.env["chat.openai_store"] = "false"
|
||||
self.env["auto.max_runs"] = "100"
|
||||
self.env["auto.hide_tool_output"] = "false"
|
||||
self.env["auto.init_commands"] = "aaa;iI;afl"
|
||||
self.env["auto.ask_to_execute"] = "true" # ask to run tools such as r2cmd, run_python etc
|
||||
|
||||
self.env.add_callback("debug_level", lambda val: LOGGER.setLevel(int(val) * 10))
|
||||
|
||||
# No active block to start
|
||||
# (blocks are visual representation of messages on the terminal)
|
||||
self.active_block = None
|
||||
|
||||
# Note: While Open Interpreter can use Llama, we will prioritize gpt-4.
|
||||
# gpt-4 is faster, smarter, can call functions, and is all-around easier to use.
|
||||
# This makes gpt-4 better aligned with Open Interpreters priority to be easy to use.
|
||||
self.llama_instance = None
|
||||
self.large = Large(self)
|
||||
|
||||
def get_info_for_system_message(self):
|
||||
"""Gets relevent information for the system message."""
|
||||
info = ""
|
||||
# Add user info
|
||||
getpass.getuser()
|
||||
os.getcwd()
|
||||
platform.system()
|
||||
# info += f"[User Info]\nName: {username}\nCWD: {current_working_directory}\nOS: {operating_system}"
|
||||
return info
|
||||
|
||||
def reset(self):
|
||||
"""Resets the interpreter."""
|
||||
self.messages = []
|
||||
|
||||
def load(self, messages):
|
||||
self.messages = messages
|
||||
|
||||
def save(self, f):
|
||||
json.dumps(self.messages, f, indent=2)
|
||||
|
||||
def handle_undo(self, arguments):
|
||||
"""Remove all messages after the most recent user entry (and the entry itself).
|
||||
Therefore user can jump back to the latest point of conversation.
|
||||
Also gives a visual representation of the messages removed."""
|
||||
if len(self.messages) == 0:
|
||||
return
|
||||
# Find the index of the last 'role': 'user' entry
|
||||
last_user_index = None
|
||||
for i, message in enumerate(self.messages):
|
||||
if message.get('role') == 'user':
|
||||
last_user_index = i
|
||||
removed_messages = []
|
||||
# Remove all messages after the last 'role': 'user'
|
||||
if last_user_index is not None:
|
||||
removed_messages = self.messages[last_user_index:]
|
||||
self.messages = self.messages[:last_user_index]
|
||||
|
||||
# Print out a preview of what messages were removed.
|
||||
for message in removed_messages:
|
||||
if message.get("content"):
|
||||
print(f"**Removed message:** `\"{message['content'][:30]}...\"`")
|
||||
elif 'function_call' in message:
|
||||
print(f"**Removed codeblock**") # TODO: Could add preview of code removed here.
|
||||
|
||||
# reimplement using nltk or index.match
|
||||
def keywords_ai(self, text):
|
||||
# kws = self.keywords_ai("who is the author of radare?") => "author,radare2"
|
||||
words = []
|
||||
mmname = "TheBloke/Mistral-7B-Instruct-v0.2-GGUF"
|
||||
ctxwindow = int(self.env["llm.window"])
|
||||
mm = new_get_hf_llm(self, mmname, ctxwindow)
|
||||
msg = f"Considering the sentence \"{text}\" as input, Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
|
||||
msg = f"Take \"{text}\" as input, and extract the keywords and combination of keywords to make a search online, the output must be a comma separated list" #Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
|
||||
response = mm(msg, stream=False, temperature=0.1, stop="</s>", max_tokens=1750)
|
||||
self.logger.debug(response)
|
||||
text0 = response["choices"][0]["text"]
|
||||
text0 = text0.replace('"', ",")
|
||||
if text0.startswith("."):
|
||||
text0 = text0[1:].strip()
|
||||
try:
|
||||
text0 = text0.split(":")[1].strip()
|
||||
except Exception:
|
||||
pass
|
||||
# print(text0)
|
||||
mm = None
|
||||
return [word.strip() for word in text0.split(',')]
|
||||
|
||||
@progress_bar("Thinking", color="yellow")
|
||||
def chat(self, message=None):
|
||||
global print
|
||||
global Ginterrupted
|
||||
if self.print is not None:
|
||||
print = self.print
|
||||
|
||||
if self.last_model != self.model:
|
||||
# self.llama_instance = None
|
||||
self.last_model = self.model
|
||||
|
||||
if not message and self.env["chat.code"] == "true":
|
||||
self.end_active_block()
|
||||
self.logger.error("Missing message")
|
||||
return
|
||||
|
||||
if self.env["data.use"] == "true":
|
||||
use_hist = self.env["data.hist"] == "true"
|
||||
use_wikit = self.env["data.wikit"] == "true"
|
||||
use_mastodon = self.env["data.mastodon"] == "true"
|
||||
use_vectordb = self.env["data.vectordb"] == "true"
|
||||
use_debug = LOGGER.level == logging.DEBUG
|
||||
datadir = None
|
||||
keywords = None
|
||||
if use_mastodon:
|
||||
keywords = self.keywords_ai(message)
|
||||
if self.env["data.local"] == "true":
|
||||
datadir = self.env["data.path"]
|
||||
if self.env["data.ddg"] == "true":
|
||||
results = ddg(message)
|
||||
if use_vectordb:
|
||||
index.memorize(message, results)
|
||||
else:
|
||||
self.messages.append({"role": "hint", "content": results})
|
||||
matches = index.match(message, keywords, datadir, use_hist, use_mastodon, use_debug, use_wikit, use_vectordb)
|
||||
if not matches:
|
||||
matches = []
|
||||
|
||||
if len(matches) > 0:
|
||||
for m in matches:
|
||||
self.logger.debug("HINT: " + m)
|
||||
self.messages.append({"role": "hint", "content": r2eval(m)})
|
||||
|
||||
self.logger.debug(message)
|
||||
|
||||
# print(message)
|
||||
# Local model -- TODO: assume local models don't have : in the name?
|
||||
if (
|
||||
not self.model.startswith("openai:") and
|
||||
not self.model.startswith("openapi:") and
|
||||
not self.model.startswith("google:") and
|
||||
not self.model.startswith("kobaldcpp") and
|
||||
not self.model.startswith("bedrock:") and
|
||||
not is_litellm_model(self.model)
|
||||
):
|
||||
self.logger = LOGGER.getChild(f"local:{self.model}")
|
||||
# Find or install Code-Llama
|
||||
try:
|
||||
ctxwindow = int(self.env["llm.window"])
|
||||
self.llama_instance = new_get_hf_llm(self, self.model, ctxwindow)
|
||||
if self.llama_instance is None:
|
||||
self.logger.error("Cannot find model " + self.model)
|
||||
return
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
# If it was, we respond non-interactively
|
||||
self.messages.append({"role": "user", "content": message})
|
||||
response = None
|
||||
try:
|
||||
response = self.respond()
|
||||
self.clear_hints()
|
||||
except Exception:
|
||||
if Ginterrupted:
|
||||
Ginterrupted = False
|
||||
else:
|
||||
traceback.print_exc()
|
||||
self.end_active_block()
|
||||
return response
|
||||
|
||||
def end_active_block(self):
|
||||
# if self.env["chat.code"] == "false":
|
||||
# return
|
||||
if self.active_block:
|
||||
self.active_block.end()
|
||||
self.active_block = None
|
||||
|
||||
def environment(self):
|
||||
kvs = ""
|
||||
if self.env["user.name"] != "":
|
||||
kvs += "Name: " + self.env["user.name"] + "\n"
|
||||
if self.env["user.os"] != "":
|
||||
kvs += "OS: " + self.env["user.os"] + "\n"
|
||||
if self.env["user.cwd"] != "":
|
||||
kvs += "CWD: " + self.env["user.cwd"] + "\n"
|
||||
if self.env["user.editor"] != "":
|
||||
kvs += "EDITOR: " + self.env["user.editor"] + "\n"
|
||||
if self.env["user.arch"] != "":
|
||||
kvs += "ARCH: " + self.env["user.arch"] + "\n"
|
||||
# info += f"[User Info]\nName: {username}\nCWD: {current_working_directory}\nOS: {operating_system}"
|
||||
if kvs != "":
|
||||
return "[User Info]\n" + kvs
|
||||
return ""
|
||||
|
||||
def clear_hints(self):
|
||||
res = []
|
||||
for msg in self.messages:
|
||||
if "role" in msg and msg["role"] != "hint":
|
||||
res.append(msg)
|
||||
self.messages = res
|
||||
|
||||
def respond(self):
|
||||
global Ginterrupted
|
||||
self.logger = LOGGER.getChild(self.model.split(":")[0])
|
||||
maxtokens = int(self.env["llm.maxtokens"])
|
||||
# Add relevant info to system_message
|
||||
# (e.g. current working directory, username, os, etc.)
|
||||
info = self.get_info_for_system_message()
|
||||
|
||||
# This is hacky, as we should have a different (minified) prompt for CodeLLama,
|
||||
# but for now, to make the prompt shorter and remove "run_code" references, just get the first 2 lines:
|
||||
self.system_message = "\n".join(self.system_message.split("\n")[:2])
|
||||
# self.system_message += "\nOnly do what the user asks you to do, then ask what they'd like to do next."
|
||||
|
||||
system_message = self.system_message + "\n\n" + info
|
||||
system_message += self.environment()
|
||||
|
||||
if self.env["chat.trim"] == "true":
|
||||
## this stupid function is slow as hell and doesn not provides much goodies
|
||||
## just ignore it by default
|
||||
messages = tokentrim.trim(self.messages, max_tokens=maxtokens, system_message=system_message)
|
||||
else:
|
||||
messages = self.large.compress_messages(self.messages)
|
||||
|
||||
self.logger.debug(messages)
|
||||
|
||||
# builtins.print(prompt)
|
||||
response = None
|
||||
if self.auto_run:
|
||||
from . import auto
|
||||
import litellm
|
||||
if self.env["chat.drop_params"] == "true":
|
||||
litellm.drop_params = True
|
||||
else:
|
||||
litellm.drop_params = False
|
||||
|
||||
if(is_litellm_model(self.model)):
|
||||
response = auto.chat(self)
|
||||
else:
|
||||
self.llama_instance = new_get_hf_llm(self, self.model, int(self.env["llm.window"]))
|
||||
response = auto.chat(self)
|
||||
return
|
||||
|
||||
elif self.model.startswith("kobaldcpp"):
|
||||
if self.system_message != "":
|
||||
message = f"Context:\n```\n{self.system_message}\n```\n"
|
||||
else:
|
||||
message = ""
|
||||
#f"{Your name is r2ai, an assistant for radare2. User will ask about actions and you must respond with the radare2 command associated or the answer to the question. Be precise and concise when answering"
|
||||
for m in messages:
|
||||
role = m["role"]
|
||||
content = m["content"]
|
||||
if role == "user":
|
||||
message += f"User: {content}\n"
|
||||
elif role == "assistant":
|
||||
message += f"AI: {content}\n"
|
||||
response = ""
|
||||
if ":" in self.model:
|
||||
uri = self.model.split(":")[1:]
|
||||
response = kobaldcpp.chat(message, ":".join(uri))
|
||||
else:
|
||||
response = kobaldcpp.chat(message)
|
||||
if "content" in self.messages[-1]:
|
||||
last_message = self.messages[-1]["content"]
|
||||
if self.env["chat.reply"] == "true":
|
||||
self.messages.append({"role": "assistant", "content": response})
|
||||
print(response)
|
||||
return
|
||||
|
||||
elif is_litellm_model(self.model):
|
||||
# [
|
||||
# {"role": "system", "content": "You are a poetic assistant, be creative."},
|
||||
# {"role": "user", "content": "Compose a poem that explains the concept of recursion in programming."}
|
||||
# ]
|
||||
from litellm import completion as litellm_completion
|
||||
completion = None
|
||||
if self.model.startswith("ollama/"):
|
||||
completion = litellm_completion(
|
||||
model=self.model,
|
||||
messages=self.messages,
|
||||
drop_params=True,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
elif self.model.startswith("mistral/"):
|
||||
completion = litellm_completion(
|
||||
drop_params=True,
|
||||
model=self.model.replace(":", "/"),
|
||||
messages=self.messages,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
else:
|
||||
completion = litellm_completion(
|
||||
model=self.model.replace(":", "/"),
|
||||
messages=self.messages,
|
||||
max_completion_tokens=maxtokens,
|
||||
temperature=float(self.env["llm.temperature"]),
|
||||
top_p=float(self.env["llm.top_p"]),
|
||||
)
|
||||
|
||||
response = completion.choices[0].message.content
|
||||
if "content" in self.messages[-1]:
|
||||
last_message = self.messages[-1]["content"]
|
||||
if self.env["chat.reply"] == "true":
|
||||
self.messages.append({"role": "assistant", "content": response})
|
||||
print(response)
|
||||
return response
|
||||
|
||||
else:
|
||||
# non-openai aka local-llama model
|
||||
if self.llama_instance == None:
|
||||
self.logger.critical("Llama is not instantiated")
|
||||
return
|
||||
try:
|
||||
if type(self.terminator).__name__ == "list":
|
||||
terminator = self.terminator
|
||||
else:
|
||||
terminator = [self.terminator]
|
||||
chat_args = {
|
||||
"stream": True,
|
||||
"temperature": float(self.env["llm.temperature"]),
|
||||
"repeat_penalty": float(self.env["llm.repeat_penalty"]),
|
||||
"top_p": float(self.env["llm.top_p"]),
|
||||
"top_k": int(self.env["llm.top_k"]),
|
||||
"stop": terminator,
|
||||
"max_tokens": maxtokens
|
||||
}
|
||||
if self.env["chat.rawdog"] == "true":
|
||||
from .completion import messages_to_prompt
|
||||
allmsg = messages.copy()
|
||||
allmsg.insert(0, ChatMessage(role=MessageRole.SYSTEM, content=self.system_message))
|
||||
prompt = messages_to_prompt(self, allmsg)
|
||||
response = self.llama_instance(prompt, **chat_args)
|
||||
else:
|
||||
all_messages = messages.copy()
|
||||
all_messages.insert(0, {"role": "system", "content": self.system_message})
|
||||
response = self.llama_instance.create_chat_completion(all_messages, **chat_args)
|
||||
except Exception as err:
|
||||
traceback.print_exc()
|
||||
print(Exception, err)
|
||||
if Ginterrupted:
|
||||
Ginterrupted = False
|
||||
return
|
||||
|
||||
if response is None:
|
||||
print("No response")
|
||||
ctxwindow = int(self.env["llm.window"])
|
||||
self.llama_instance = new_get_hf_llm(self, self.model, ctxwindow)
|
||||
return
|
||||
# Initialize message, function call trackers, and active block
|
||||
self.messages.append({})
|
||||
in_function_call = False
|
||||
self.active_block = MessageBlock()
|
||||
for chunk in response:
|
||||
if Ginterrupted:
|
||||
Ginterrupted = False
|
||||
break
|
||||
text = ''
|
||||
delta = None
|
||||
if self.env["chat.rawdog"] == "true":
|
||||
if "content" not in messages[-1]:
|
||||
text = chunk["choices"][0]['text'].capitalize()
|
||||
messages[-1] = { "content": '', "role": "assistant" }
|
||||
else:
|
||||
text = chunk["choices"][0]['text']
|
||||
messages[-1] = merge_deltas(messages[-1], {"content": text})
|
||||
else:
|
||||
if "content" not in messages[-1]:
|
||||
if "text" in chunk["choices"][0]['delta']:
|
||||
text = chunk["choices"][0]['delta']["content"]
|
||||
messages[-1] = { "content": text, "role": "assistant" }
|
||||
delta = {"content": text}
|
||||
elif "content" in chunk['choices'][0]['delta']:
|
||||
delta = {"content": chunk['choices'][0]['delta']['content']}
|
||||
# Accumulate deltas into the last message in messages
|
||||
if delta:
|
||||
self.messages[-1] = merge_deltas(self.messages[-1], delta)
|
||||
if self.env["chat.live"] != "true":
|
||||
continue
|
||||
flushed = False
|
||||
if self.env["chat.code"] == "true":
|
||||
if incodeblock(self.messages[-1]):
|
||||
if in_function_call == False:
|
||||
in_function_call = True
|
||||
self.active_block.update_from_message(self.messages[-1])
|
||||
self.end_active_block()
|
||||
# flushed = True
|
||||
self.active_block = CodeBlock()
|
||||
else:
|
||||
if in_function_call == True:
|
||||
in_function_call = False
|
||||
self.end_active_block()
|
||||
flushed = True
|
||||
self.active_block = MessageBlock()
|
||||
if self.env["chat.live"] == "true": # and self.env["chat.code"] == "true":
|
||||
self.active_block.update_from_message(self.messages[-1])
|
||||
if flushed:
|
||||
self.messages[-1]["content"] = ""
|
||||
continue # end of for loop
|
||||
self.end_active_block()
|
||||
self.active_block = None
|
||||
output_text = ""
|
||||
if len(self.messages) > 0 and "content" in self.messages[-1]:
|
||||
output_text = self.messages[-1]["content"].strip()
|
||||
if self.env["chat.reply"] == "true":
|
||||
self.messages.append({"role": "assistant", "content": output_text})
|
||||
if self.env["chat.voice"] == "true":
|
||||
tts("(assistant)", output_text, self.env["voice.lang"])
|
||||
elif self.env["chat.live"] != "true":
|
||||
try:
|
||||
r2lang.print(output_text)
|
||||
except Exception:
|
||||
print(output_text)
|
||||
# print(str(self.messages))
|
||||
@@ -0,0 +1,173 @@
|
||||
from .models import get_hf_llm, new_get_hf_llm, get_default_model
|
||||
from . import LOGGER
|
||||
import re
|
||||
import json
|
||||
|
||||
class Large:
|
||||
def __init__(self, ai = None):
|
||||
self.mistral = None
|
||||
self.window = 4096
|
||||
self.maxlen = 12000
|
||||
self.maxtokens = 5000
|
||||
# self.model = "TheBloke/Mistral-7B-Instruct-v0.2-GGUF"
|
||||
self.model = "FaradayDotDev/llama-3-8b-Instruct-GGUF"
|
||||
self.logger = LOGGER.getChild(f"large[{self.model}]")
|
||||
if ai is not None:
|
||||
self.ai = ai
|
||||
else:
|
||||
self.ai = {}
|
||||
self.ai.env = {}
|
||||
self.ai.env["llm.gpu"] = "true"
|
||||
|
||||
def slice_text(self, amsg):
|
||||
slices = []
|
||||
pos = self.maxlen
|
||||
while len(amsg) > self.maxlen:
|
||||
s = amsg[:pos]
|
||||
amsg = amsg[pos:]
|
||||
slices.append(s)
|
||||
slices.append(amsg)
|
||||
return slices
|
||||
|
||||
def compress_text(self, msg):
|
||||
if self.mistral == None:
|
||||
self.mistral = new_get_hf_llm(self.ai, self.model, self.window)
|
||||
# q = f"Rewrite this code into shorter pseudocode (less than 500 tokens). keep the comments and essential logic:\n```\n{msg}\n```\n"
|
||||
#q = f"Rewrite this code into shorter pseudocode (less than 200 tokens). keep the relevant comments and essential logic:\n```\n{msg}\n```\n"
|
||||
q = f"Resumen y responde SOLO la información relevante del siguiente texto:\n{msg}"
|
||||
response = self.mistral(q, stream=False, temperature=0.001, stop="</s>", max_tokens=self.maxtokens)
|
||||
print(response["choices"]) #json.dumps(response))
|
||||
text0 = response["choices"][0]["text"]
|
||||
return text0
|
||||
|
||||
def summarize_text(self, amsg):
|
||||
olen = len(amsg)
|
||||
while len(amsg) > self.maxlen:
|
||||
slices = self.slice_text(amsg)
|
||||
print(f"Re-slicing {len(slices)}")
|
||||
short_slices = []
|
||||
for s in slices:
|
||||
sm = self.compress_text(s)
|
||||
short_slices.append(sm)
|
||||
print(sm)
|
||||
print(f"Went from {len(s)} to {len(sm)}")
|
||||
amsg = " ".join(short_slices)
|
||||
nlen = len(amsg)
|
||||
print(f"total length {nlen} (original length was {olen})")
|
||||
return amsg
|
||||
|
||||
def keywords_ai(self, text):
|
||||
# kws = self.keywords_ai("who is the author of radare?") => "author,radare2"
|
||||
words = []
|
||||
ctxwindow = int(self.ai.env["llm.window"])
|
||||
mm = new_get_hf_llm(self.ai, self.model, ctxwindow)
|
||||
msg = f"Considering the sentence \"{text}\" as input, Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
|
||||
msg = f"Take \"{text}\" as input, and extract the keywords and combination of keywords to make a search online, the output must be a comma separated list" #Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
|
||||
response = mm(msg, stream=False, temperature=0.001, stop="</s>", max_tokens=1750)
|
||||
self.logger.debug(response)
|
||||
text0 = response["choices"][0]["text"]
|
||||
text0 = text0.replace('"', ",")
|
||||
if text0.startswith("."):
|
||||
text0 = text0[1:].strip()
|
||||
try:
|
||||
text0 = text0.split(":")[1].strip()
|
||||
except Exception:
|
||||
pass
|
||||
# print(text0)
|
||||
mm = None
|
||||
return [word.strip() for word in text0.split(',')]
|
||||
|
||||
def trimsource(self, msg):
|
||||
msg = msg.replace("public ", "")
|
||||
msg = re.sub(r'import.*\;', "", msg)
|
||||
msg = msg.replace("const ", "")
|
||||
msg = msg.replace("new ", "")
|
||||
msg = msg.replace("undefined", "0")
|
||||
msg = msg.replace("null", "0")
|
||||
msg = msg.replace("false", "0")
|
||||
msg = msg.replace("true", "1")
|
||||
msg = msg.replace("let ", "")
|
||||
msg = msg.replace("var ", "")
|
||||
msg = msg.replace("class ", "")
|
||||
msg = msg.replace("interface ", "")
|
||||
msg = msg.replace("function ", "fn ")
|
||||
msg = msg.replace("substring", "")
|
||||
msg = msg.replace("this.", "")
|
||||
msg = msg.replace("while (", "while(")
|
||||
msg = msg.replace("if (", "if(")
|
||||
msg = msg.replace("!== 0", "")
|
||||
msg = msg.replace("=== true", "")
|
||||
msg = msg.replace(" = ", "=")
|
||||
msg = msg.replace(" === ", "==")
|
||||
msg = msg.replace("\t", " ")
|
||||
msg = msg.replace("\n", "")
|
||||
msg = re.sub(r"/\*.*?\*/", '', msg, flags=re.DOTALL)
|
||||
# msg = re.sub(r"\n+", "\n", msg)
|
||||
msg = re.sub(r"\t+", ' ', msg)
|
||||
msg = re.sub(r"\s+", " ", msg)
|
||||
# msg = msg.replace(";", "")
|
||||
return msg.strip()
|
||||
|
||||
def trimsource_ai(self, msg):
|
||||
words = []
|
||||
if self.mistral == None:
|
||||
ctxwindow = int(self.ai.env["llm.window"])
|
||||
self.mistral = new_get_hf_llm(self.ai, self.model, ctxwindow)
|
||||
# q = f"Rewrite this code into shorter pseudocode (less than 500 tokens). keep the comments and essential logic:\n```\n{msg}\n```\n"
|
||||
q = f"Rewrite this code into shorter pseudocode (less than 200 tokens). keep the relevant comments and essential logic:\n```\n{msg}\n```\n"
|
||||
response = self.mistral(q, stream=False, temperature=0.1, stop="</s>", max_tokens=4096)
|
||||
text0 = response["choices"][0]["text"]
|
||||
if "```" in text0:
|
||||
return text0.split("```")[1].strip()
|
||||
return text0.strip().replace("```", "")
|
||||
|
||||
def compress_code_ai(self, code):
|
||||
piecesize = 1024 * 8 # mistral2 supports 32k vs 4096
|
||||
codelen = len(code)
|
||||
pieces = int(codelen / piecesize)
|
||||
if pieces < 1:
|
||||
pieces = 1
|
||||
plen = int(codelen / pieces)
|
||||
off = 0
|
||||
res = []
|
||||
for i in range(pieces):
|
||||
piece = i + 1
|
||||
print(f"Processing {piece} / {pieces} ...")
|
||||
if piece == pieces:
|
||||
r = self.trimsource_ai(code[off:])
|
||||
else:
|
||||
r = self.trimsource_ai(code[off:off+plen])
|
||||
res.append(r)
|
||||
off += plen
|
||||
return "\n".join(res)
|
||||
|
||||
def compress_messages(self, messages):
|
||||
return messages
|
||||
# TODO: implement a better logic in here asking the lm to summarize the context
|
||||
olen = 0
|
||||
msglen = 0
|
||||
for msg in messages:
|
||||
if self.ai.env["chat.reply"] == "false":
|
||||
if msg["role"] != "user":
|
||||
continue
|
||||
if "content" in msg:
|
||||
amsg = msg["content"]
|
||||
olen += len(amsg)
|
||||
if len(amsg) > int(self.ai.env["llm.maxmsglen"]):
|
||||
if "while" in amsg and "```" in amsg:
|
||||
que = re.search(r"^(.*?)```", amsg, re.DOTALL).group(0).replace("```", "")
|
||||
cod = re.search(r"```(.*?)$", amsg, re.DOTALL).group(0).replace("```", "")
|
||||
shortcode = cod
|
||||
while len(shortcode) > 4000:
|
||||
olen = len(shortcode)
|
||||
shortcode = self.compress_code_ai(shortcode)
|
||||
nlen = len(shortcode)
|
||||
print(f"Went from {olen} to {nlen}")
|
||||
msg["content"] = f"{que}\n```\n{shortcode}\n```\n"
|
||||
else:
|
||||
print(f"total length {msglen} (original length was {olen})")
|
||||
msglen += len(msg["content"])
|
||||
# print(f"total length {msglen} (original length was {olen})")
|
||||
# if msglen > 4096:
|
||||
# ¡print("Query is too large.. you should consider triming old messages")
|
||||
return messages
|
||||
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
|
||||
import sys
|
||||
import builtins
|
||||
import traceback
|
||||
import appdirs
|
||||
import argparse
|
||||
import litellm
|
||||
from r2ai.repl import r2ai_singleton
|
||||
from r2ai.utils import slurp
|
||||
from r2ai.repl import runline, r2ai_repl, help_message
|
||||
|
||||
from r2ai.pipe import open_r2, get_r2_inst
|
||||
from r2ai.const import R2AI_RCFILE
|
||||
|
||||
OPENAI_KEY = ""
|
||||
HAVE_RLANG = False
|
||||
HAVE_R2PIPE = False
|
||||
RCFILE_LOADED = False
|
||||
|
||||
def r2ai_rlang_plugin(unused_but_required_argument):
|
||||
ai = r2ai_singleton()
|
||||
def _call(s):
|
||||
if not s.startswith("r2ai"):
|
||||
return False
|
||||
try:
|
||||
run_rcfile_once(ai)
|
||||
if len(s) == 4:
|
||||
builtins.print(help_message)
|
||||
else:
|
||||
usertext = s[4:].strip()
|
||||
runline(ai, usertext)
|
||||
except Exception as e:
|
||||
builtins.print(e)
|
||||
traceback.print_exc()
|
||||
return True
|
||||
|
||||
return {
|
||||
"name": "r2ai",
|
||||
"license": "MIT",
|
||||
"desc": "run llama language models inside r2",
|
||||
"call": _call,
|
||||
}
|
||||
|
||||
# TODO: see repl.run_script as replacement
|
||||
def run_rcfile(ai):
|
||||
try:
|
||||
lines = slurp(R2AI_RCFILE)
|
||||
|
||||
for line in lines.split("\n"):
|
||||
if line.strip() != "":
|
||||
if ai is None:
|
||||
ai = r2ai_singleton()
|
||||
runline(ai, line)
|
||||
except Exception:
|
||||
pass
|
||||
if ai is None:
|
||||
ai = r2ai_singleton()
|
||||
|
||||
def run_rcfile_once(ai):
|
||||
global RCFILE_LOADED
|
||||
if not RCFILE_LOADED:
|
||||
run_rcfile(ai)
|
||||
RCFILE_LOADED = True
|
||||
|
||||
|
||||
def main(args, commands, dorepl=True):
|
||||
|
||||
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
||||
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
|
||||
|
||||
try:
|
||||
r2aihome = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.append(r2aihome)
|
||||
# if available
|
||||
sys.path.append(
|
||||
os.path.join(r2aihome, "..", "vectordb")
|
||||
)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
home_dir = os.path.expanduser("~")
|
||||
# create symlink if it doesnt exist
|
||||
try:
|
||||
dst = os.path.join(home_dir, ".r2ai.models")
|
||||
udd = appdirs.user_data_dir("r2ai")
|
||||
src = os.path.join(udd, "models")
|
||||
if not os.path.exists(dst):
|
||||
os.symlink(src, dst)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
r2_openai_file = os.path.join(home_dir, ".r2ai.openai-key")
|
||||
if os.path.isfile(r2_openai_file):
|
||||
apikey = slurp(r2_openai_file).strip()
|
||||
os.environ["OPENAI_API_KEY"] = apikey
|
||||
print("[R2AI] OpenAI API key loaded from ~/.r2ai.openai-key", file=sys.stderr)
|
||||
|
||||
r2_mistral_file = os.path.join(home_dir, ".r2ai.mistral-key")
|
||||
if os.path.isfile(r2_mistral_file):
|
||||
apikey = slurp(r2_mistral_file).strip()
|
||||
os.environ["MISTRAL_API_KEY"] = apikey
|
||||
print("[R2AI] Mistral API key loaded from ~/.r2ai.mistral-key", file=sys.stderr)
|
||||
|
||||
r2_gemini_file = os.path.join(home_dir, ".r2ai.gemini-key")
|
||||
if os.path.isfile(r2_gemini_file):
|
||||
apikey = slurp(r2_gemini_file).strip()
|
||||
os.environ["GEMINI_API_KEY"] = apikey
|
||||
print("[R2AI] Gemini API key loaded from ~/.r2ai.gemini-key", file=sys.stderr)
|
||||
|
||||
r2_anthropic_file = os.path.join(home_dir, ".r2ai.anthropic-key")
|
||||
if os.path.isfile(r2_anthropic_file):
|
||||
apikey = slurp(r2_anthropic_file).strip()
|
||||
os.environ["ANTHROPIC_API_KEY"] = apikey
|
||||
print("[R2AI] Anthropic API key loaded from ~/.r2ai.anthropic-key", file=sys.stderr)
|
||||
|
||||
ai = r2ai_singleton()
|
||||
if "R2PIPE_IN" in os.environ:
|
||||
pass
|
||||
elif args.bin:
|
||||
open_r2(vars(args)["bin"], flags=["-2"])
|
||||
|
||||
if commands is not None:
|
||||
for c in commands:
|
||||
if c.startswith("_"):
|
||||
runline(ai, "-" + c[1:])
|
||||
else:
|
||||
runline(ai, c)
|
||||
if dorepl:
|
||||
r2ai_repl(ai)
|
||||
|
||||
def massage_args(args):
|
||||
runrepl = True
|
||||
if args.command is None:
|
||||
args.command = []
|
||||
if args.webserver:
|
||||
args.command.append("-w")
|
||||
if args.eval:
|
||||
if args.eval == "default":
|
||||
args.command.append("-e")
|
||||
runrepl = False
|
||||
else:
|
||||
args.command.append(f"-e {args.eval}")
|
||||
if args.port:
|
||||
if args.port == "default":
|
||||
runrepl = False
|
||||
args.command.append("-e http.port")
|
||||
else:
|
||||
args.command.append(f"-e http.port={args.port}")
|
||||
if args.model:
|
||||
if args.model == "default":
|
||||
args.command.append("-mm")
|
||||
runrepl = False
|
||||
else:
|
||||
args.command.append(f"-m {args.model}")
|
||||
return runrepl, args
|
||||
|
||||
def run():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("bin", nargs="?", type=str)
|
||||
parser.add_argument("-w", "--webserver", action="store_true",
|
||||
help="Start the r2ai webserver. Same as r2ai -c=-w")
|
||||
parser.add_argument("-p", "--port", type=str, nargs="?", const="default",
|
||||
help="Change listen port number")
|
||||
parser.add_argument("-e", "--eval", type=str, nargs="?", const="default",
|
||||
help="Change configuration variable")
|
||||
parser.add_argument("-m", "--model", type=str, nargs="?", const="default",
|
||||
help="Select model name")
|
||||
parser.add_argument("-c", "--command", action="append",
|
||||
help="Command to be executed. Can be passed multiple times.")
|
||||
runrepl, args = massage_args(parser.parse_args())
|
||||
main(args, args.command, runrepl)
|
||||
@@ -0,0 +1,57 @@
|
||||
from rich.console import Console
|
||||
from rich.live import Live
|
||||
from rich.panel import Panel
|
||||
#from rich.markdown import Markdown
|
||||
from rich.box import MINIMAL
|
||||
import re
|
||||
|
||||
def Markdown(x):
|
||||
return x
|
||||
|
||||
class MessageBlock:
|
||||
def __init__(self):
|
||||
self.live = Live(auto_refresh=False, console=Console())
|
||||
self.live.start()
|
||||
self.content = ""
|
||||
|
||||
def update_from_message(self, message):
|
||||
if type(message) != str and "content" in message:
|
||||
message = message["content"]
|
||||
# msg = message if type(message) is str else message.get("content", "")
|
||||
message = re.sub(r"`+$", '', message)
|
||||
self.content = message
|
||||
self.refresh()
|
||||
|
||||
def end(self):
|
||||
self.refresh(cursor=False)
|
||||
self.live.stop()
|
||||
|
||||
def refresh(self, cursor=True):
|
||||
# De-stylize any code blocks in markdown to differentiate from our Code Blocks
|
||||
# WHY. this makes no sense, because codeblocks should be handled by the CodeBlocks class
|
||||
content = textify_markdown_code_blocks(self.content)
|
||||
if cursor:
|
||||
content += "█"
|
||||
markdown = Markdown(content.strip())
|
||||
panel = Panel(markdown, box=MINIMAL)
|
||||
self.live.update(panel)
|
||||
self.live.refresh()
|
||||
|
||||
def textify_markdown_code_blocks(text):
|
||||
return text
|
||||
"""
|
||||
To distinguish CodeBlocks from markdown code, we simply turn all markdown code
|
||||
(like '```python...') into text code blocks ('```text') which makes the code black and white.
|
||||
"""
|
||||
replacement = "```text"
|
||||
lines = text.split('\n')
|
||||
inside_code_block = False
|
||||
|
||||
for i in range(len(lines)):
|
||||
# If the line matches ``` followed by optional language specifier
|
||||
if re.match(r'^```(\w*)$', lines[i].strip()):
|
||||
inside_code_block = not inside_code_block
|
||||
# If we just entered a code block, replace the marker
|
||||
if inside_code_block:
|
||||
lines[i] = replacement
|
||||
return '\n'.join(lines)
|
||||
@@ -1,22 +1,21 @@
|
||||
from .utils import slurp, dump
|
||||
from huggingface_hub import list_files_info, hf_hub_download, login
|
||||
from typing import Dict, List, Union
|
||||
import appdirs
|
||||
import builtins
|
||||
import inquirer
|
||||
import json
|
||||
import llama_cpp
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
#DEFAULT_MODEL = "TheBloke/CodeLlama-34B-Instruct-GGUF"
|
||||
DEFAULT_MODEL = "TheBloke/llama2-7b-chat-codeCherryPop-qLoRA-GGUF"
|
||||
# DEFAULT_MODEL = "TheBloke/CodeLlama-34B-Instruct-GGUF"
|
||||
# DEFAULT_MODEL = "TheBloke/llama2-7b-chat-codeCherryPop-qLoRA-GGUF"
|
||||
# DEFAULT_MODEL = "-m TheBloke/dolphin-2_6-phi-2-GGUF"
|
||||
DEFAULT_MODEL = "-m FaradayDotDev/llama-3-8b-Instruct-GGUF"
|
||||
r2ai_model_json = "r2ai.model.json" # windows path
|
||||
if "HOME" in os.environ:
|
||||
r2ai_model_json = os.environ["HOME"] + "/.r2ai.model"
|
||||
r2ai_model_json = os.environ["HOME"] + "/.r2ai.model"
|
||||
|
||||
def get_default_model():
|
||||
try:
|
||||
@@ -25,74 +24,160 @@ def get_default_model():
|
||||
fd.close()
|
||||
if "default" in usermodels:
|
||||
return usermodels["default"]
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
return DEFAULT_MODEL
|
||||
|
||||
def Markdown(x):
|
||||
return x
|
||||
return x
|
||||
|
||||
def mainmodels():
|
||||
return """
|
||||
Decai:
|
||||
-m hugging-quants/Llama-3.2-3B-Instruct-Q8_0-GGUF
|
||||
-m QuantFactory/granite-8b-code-instruct-4k-GGUF
|
||||
-m cognitivecomputations/dolphin-2.9.3-mistral-nemo-12b-gguf
|
||||
-m bartowski/Gemma-2-9B-It-SPPO-Iter3-GGUF
|
||||
-m Qwen/Qwen2.5-Coder-7B-Instruct-GGUF
|
||||
-m trufae/granite3-instruct-8b-gguf
|
||||
Code:
|
||||
-m unsloth/Llama-3.2-1B-Instruct-GGUF
|
||||
-m mradermacher/starcoder-i1-GGUF
|
||||
-m cognitivecomputations/dolphin-2.9.4-llama3.1-8b-gguf
|
||||
-m FaradayDotDev/llama-3-8b-Instruct-GGUF
|
||||
-m second-state/Mistral-Nemo-Instruct-2407-GGUF
|
||||
-m bartowski/gemma-2-9b-it-GGUF
|
||||
-m mradermacher/salamandra-7b-instruct-aina-hack-GGUF
|
||||
Functionary:
|
||||
-m meetkai/functionary-small-v3.2-GGUF
|
||||
-m mradermacher/granite-20b-functioncalling-i1-GGUF
|
||||
-m mradermacher/LocalAI-Llama3.1-8b-Function-Call-v0.3-i1-GGUF
|
||||
Uncensored:
|
||||
-m Ttimofeyka/MistralRP-Noromaid-NSFW-Mistral-7B-GGUF
|
||||
-m bartowski/Phi-3.5-mini-instruct_Uncensored-GGUF
|
||||
-m Orenguteng/Llama-3.1-8B-Lexi-Uncensored-V2-GGUF
|
||||
-m LWDCLS/DarkIdol-Llama-3.1-8B-Instruct-1.2-Uncensored-GGUF-IQ-Imatrix-Request
|
||||
-m Undi95/Llama-3-Unholy-8B-GGUF
|
||||
-m mradermacher/Llama-3.2-1B-Instruct-Uncensored-GGUF
|
||||
Remote:
|
||||
-m openapi:http://localhost:5001
|
||||
-m openai:gpt-4
|
||||
-m anthropic:claude-3-7-sonnet-20250219
|
||||
-m bedrock:anthropic.claude-3-5-sonnet-20241022-v1
|
||||
-m kobaldcpp:http://localhost:5001
|
||||
"""
|
||||
|
||||
def models():
|
||||
builtins.print("OpenAI:")
|
||||
builtins.print("-m openai:gpt-3.5-turbo");
|
||||
builtins.print("-m openai:gpt-4");
|
||||
builtins.print("GPT4:")
|
||||
builtins.print("-m TheBloke/goliath-120b-GGUF")
|
||||
builtins.print("-m TheBloke/SynthIA-7B-v2.0-GGUF")
|
||||
builtins.print("-m TheBloke/Chronos-Hermes-13b-v2-GGUF")
|
||||
builtins.print("-m TheBloke/Chronos-70B-v2-GGUF")
|
||||
builtins.print("-m lizpreciatior/lzlv_70b_fp16_hf")
|
||||
builtins.print("-m TheBloke/Nous-Capybara-7B-v1.9-GGUF")
|
||||
builtins.print("-m TheBloke/Nous-Capybara-34B-GGUF")
|
||||
builtins.print("-m TheBloke/Euryale-Inverted-L2-70B-GGUF")
|
||||
builtins.print("-m TheBloke/StellarBright-GGUF")
|
||||
builtins.print("-m TheBloke/GodziLLa2-70B-GGUF")
|
||||
builtins.print("Generic:")
|
||||
builtins.print("-m TheBloke/Mistral-7B-Instruct-v0.1-GGUF")
|
||||
builtins.print("-m TheBloke/Yarn-Mistral-7B-128k-GGUF")
|
||||
builtins.print("-m TheBloke/dolphin-2.2.1-mistral-7B-GGUF")
|
||||
builtins.print("-m TheBloke/zephyr-7B-alpha-GGUF")
|
||||
builtins.print("-m TheBloke/zephyr-7B-beta-GGUF")
|
||||
builtins.print("-m maddes8cht/nomic-ai-gpt4all-falcon-7b-gguf")
|
||||
builtins.print("-m KoboldAI/LLaMA2-13B-Tiefighter-GGUF")
|
||||
builtins.print("Coding:")
|
||||
builtins.print("-m TheBloke/CodeBooga-34B-v0.1-GGUF")
|
||||
builtins.print("-m TheBloke/llama2-7b-chat-codeCherryPop-qLoRA-GGUF")
|
||||
builtins.print("-m TheBloke/CodeLlama-34B-Instruct-GGUF")
|
||||
builtins.print("-m TheBloke/deepseek-coder-6.7B-instruct-GGUF")
|
||||
builtins.print("-m TheBloke/CodeLlama-7B-Instruct-GGUF")
|
||||
builtins.print("Uncensored:")
|
||||
builtins.print("-m TheBloke/Dawn-v2-70B-GGUF")
|
||||
builtins.print("-m TheBloke/Guanaco-7B-Uncensored-GGUF")
|
||||
builtins.print("-m TheBloke/Luna-AI-Llama2-Uncensored-GGUF")
|
||||
builtins.print("-m TheBloke/Wizard-Vicuna-13B-Uncensored-GGUF")
|
||||
builtins.print("-m TheBloke/Wizard-Vicuna-7B-Uncensored-GGUF")
|
||||
builtins.print("-m TheBloke/llama2_70b_chat_uncensored-GGUF")
|
||||
builtins.print("-m Undi95/Utopia-13B-GGUF")
|
||||
return """
|
||||
KobaldCpp:
|
||||
-m kobaldcpp
|
||||
-m kobaldcpp:http://localhost:5001
|
||||
OpenAPI:
|
||||
-m openapi:http://localhost:5001
|
||||
-m openai:http://localhost:5001:gpt-4
|
||||
OpenAI:
|
||||
-m openai:gpt-3.5-turbo
|
||||
-m openai:gpt-4
|
||||
-m openai:gpt-4-1106-preview
|
||||
Anthropic:
|
||||
-m anthropic:claude-2.1
|
||||
-m anthropic:claude-3-haiku-20240307
|
||||
-m anthropic:claude-3-opus-20240229
|
||||
-m anthropic:claude-3-sonnet-20240229
|
||||
-m anthropic:claude-3-5-sonnet-20241022
|
||||
-m anthropic:claude-3-7-sonnet-20250219
|
||||
groq:
|
||||
-m groq:gemma-7b-it
|
||||
-m groq:llama2-70b-4096
|
||||
-m groq:mixtral-8x7b-32768
|
||||
Google:
|
||||
-m google:gemini-1.0-pro
|
||||
-m google:gemini-1.5-flash
|
||||
-m google:gemini-1.5-pro-latest
|
||||
Functionary:
|
||||
-m meetkai/functionary-7b-v1.4-GGUF
|
||||
-m meetkai/functionary-7b-v2-GGUF
|
||||
-m meetkai/functionary-7b-v2.1-GGUF
|
||||
-m meetkai/functionary-medium-v2.2-GGUF
|
||||
-m meetkai/functionary-small-v2.2-GGUF
|
||||
Generic:
|
||||
-m KoboldAI/LLaMA2-13B-Tiefighter-GGUF
|
||||
-m TheBloke/Ferret_7B-GGUF
|
||||
-m TheBloke/OpenOrca-Zephyr-7B-GGUF
|
||||
-m TheBloke/Yarn-Mistral-7B-128k-GGUF
|
||||
-m TheBloke/dolphin-2.2.1-mistral-7B-GGUF
|
||||
-m TheBloke/zephyr-7B-alpha-GGUF
|
||||
-m TheBloke/zephyr-7B-beta-GGUF
|
||||
-m aisensiy/Qwen-72B-Chat-GGUF
|
||||
-m maddes8cht/nomic-ai-gpt4all-falcon-7b-gguf
|
||||
Coding:
|
||||
-m segolilylabs/Lily-Cybersecurity-7B-v0.2-GGUF
|
||||
-m LoneStriker/OpenCodeInterpreter-DS-33B-GGUF
|
||||
-m TheBloke/CodeBooga-34B-v0.1-GGUF
|
||||
-m TheBloke/CodeLlama-34B-Instruct-GGUF
|
||||
-m TheBloke/CodeLlama-7B-Instruct-GGUF
|
||||
-m TheBloke/deepseek-coder-33B-instruct-GGUF
|
||||
-m TheBloke/deepseek-coder-6.7B-instruct-GGUF
|
||||
-m TheBloke/llama2-7b-chat-codeCherryPop-qLoRA-GGUF
|
||||
-m bartowski/stable-code-instruct-3b-GGUF
|
||||
-m cognitivecomputations/dolphin-2.9-llama3-8b-gguf
|
||||
-m cosmo3769/starcoderbase-1b-GGUF
|
||||
-m microsoft/Phi-3-mini-4k-instruct-gguf
|
||||
Uncensored:
|
||||
-m TheBloke/Dawn-v2-70B-GGUF
|
||||
-m TheBloke/Guanaco-7B-Uncensored-GGUF
|
||||
-m TheBloke/Luna-AI-Llama2-Uncensored-GGUF
|
||||
-m TheBloke/Wizard-Vicuna-13B-Uncensored-GGUF
|
||||
-m TheBloke/Wizard-Vicuna-7B-Uncensored-GGUF
|
||||
-m TheBloke/dolphin-2_6-phi-2-GGUF
|
||||
-m TheBloke/llama2_70b_chat_uncensored-GGUF
|
||||
-m TheBloke/openchat-3.5-0106-GGUF
|
||||
Llama3:
|
||||
-m FaradayDotDev/llama-3-8b-Instruct-GGUF
|
||||
-m mradermacher/llama-3-tsuki-unsloth-8b-GGUF
|
||||
-m Orenguteng/Llama-3-8B-Lexi-Uncensored-GGUF
|
||||
Gemma:
|
||||
-m mlabonne/gemma-7b-it-GGUF
|
||||
-m bartowski/gemma-2-9b-it-GGUF
|
||||
Other:
|
||||
-m dagbs/dolphin-2.8-mistral-7b-v02-GGUF
|
||||
-m Undi95/Utopia-13B-GGUF
|
||||
-m TheBloke/Unholy-v2-13B-GGUF
|
||||
-m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
"""
|
||||
|
||||
def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
n_gpu_layers = -1
|
||||
def gpulayers(ai):
|
||||
if "llm.gpu" in ai.env:
|
||||
if ai.env["llm.gpu"] == "true":
|
||||
return int(ai.env["llm.layers"])
|
||||
return 0
|
||||
|
||||
def get_hf_llm(ai, repo_id, debug_mode, context_window):
|
||||
usermodels = None
|
||||
try:
|
||||
try:
|
||||
fd = open(r2ai_model_json)
|
||||
usermodels = json.load(fd)
|
||||
fd.close()
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
usermodels = {}
|
||||
|
||||
model_path = "" # slurp(r2ai_model_json)
|
||||
if not repo_id:
|
||||
repo_id = get_default_model()
|
||||
elif repo_id.startswith ("-m "):
|
||||
repo_id = repo_id[3:]
|
||||
if usermodels is not None and repo_id in usermodels:
|
||||
model_path = usermodels[repo_id]
|
||||
# print("[r2ai] Using " + r2ai_model_json+": " + model_path)
|
||||
return llama_cpp.Llama(model_path=model_path, n_gpu_layers=n_gpu_layers, verbose=debug_mode, n_ctx=context_window)
|
||||
except:
|
||||
# print(f"[r2ai] Using {r2ai_model_json} {model_path}")
|
||||
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print(f"Select {repo_id} model. See -M and -m flags")
|
||||
|
||||
print(f"Select {repo_id} model. See -M and -m flags", file=sys.stderr)
|
||||
raw_models = list_gguf_files(repo_id)
|
||||
if not raw_models:
|
||||
print(f"Failed. Are you sure there are GGUF files in `{repo_id}`?")
|
||||
print(f"Failed. Are you sure there are GGUF files in `{repo_id}`?", file=sys.stderr)
|
||||
return None
|
||||
# print(raw_models)
|
||||
combined_models = group_and_combine_splits(raw_models)
|
||||
@@ -117,7 +202,9 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
selected_model = combined_models[len(combined_models) // 2]["filename"]
|
||||
elif am.startswith("Large"):
|
||||
selected_model = combined_models[-1]["filename"]
|
||||
|
||||
else:
|
||||
selected_model = repo_id
|
||||
|
||||
if selected_model != None:
|
||||
# This means they either selected See More,
|
||||
# Or the model only had 1 or 2 options
|
||||
@@ -130,12 +217,10 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
if format_quality_choice(model) == answers["selected_model"]:
|
||||
selected_model = model["filename"]
|
||||
break
|
||||
if selected_model == None:
|
||||
print("No model selected")
|
||||
return
|
||||
answers = inquirer.prompt([inquirer.List("default", message="Use this model by default? ~/.r2ai.model", choices=["Yes", "No"])])
|
||||
# Third stage: GPU confirm
|
||||
#if confirm_action("Use GPU? (Large models might crash on GPU, but will run more quickly)"):
|
||||
## n_gpu_layers = -1
|
||||
# else:
|
||||
# n_gpu_layers = 0
|
||||
|
||||
# Get user data directory
|
||||
user_data_dir = appdirs.user_data_dir("r2ai")
|
||||
@@ -162,7 +247,6 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
else:
|
||||
# If the file was not found, ask for confirmation to download it
|
||||
download_path = os.path.join(default_path, selected_model)
|
||||
|
||||
if confirm_action(f"Download to {default_path}?"):
|
||||
for model_details in combined_models:
|
||||
if model_details["filename"] == selected_model:
|
||||
@@ -170,12 +254,12 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
|
||||
# Check disk space and exit if not enough
|
||||
if not enough_disk_space(selected_model_details['Size'], default_path):
|
||||
print(f"Not enough disk space available to download this model.")
|
||||
print(f"Not enough disk space available to download this model.", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Check if model was originally split
|
||||
split_files = [model["filename"] for model in raw_models if selected_model in model["filename"]]
|
||||
|
||||
from huggingface_hub import hf_hub_download
|
||||
if len(split_files) > 1:
|
||||
# Download splits
|
||||
for split_file in split_files:
|
||||
@@ -187,38 +271,29 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename=split_file,
|
||||
local_dir=default_path,
|
||||
local_dir_use_symlinks=False,
|
||||
resume_download=True)
|
||||
|
||||
local_dir=default_path)
|
||||
# Combine and delete splits
|
||||
actually_combine_files(default_path, selected_model, split_files)
|
||||
else:
|
||||
hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename=selected_model,
|
||||
local_dir=default_path,
|
||||
local_dir_use_symlinks=False,
|
||||
resume_download=True)
|
||||
local_dir=default_path)
|
||||
|
||||
model_path = download_path
|
||||
|
||||
else:
|
||||
print('\n', "Download cancelled. Exiting.", '\n')
|
||||
print('\nDownload cancelled. Exiting\n', file=sys.stderr)
|
||||
return None
|
||||
try:
|
||||
from llama_cpp import Llama
|
||||
except:
|
||||
except Exception:
|
||||
if debug_mode:
|
||||
traceback.print_exc()
|
||||
# Ask for confirmation to install the required pip package
|
||||
message = "Local LLM interface package not found. Install `llama-cpp-python`?"
|
||||
if confirm_action(message):
|
||||
|
||||
# We're going to build llama-cpp-python correctly for the system we're on
|
||||
|
||||
import platform
|
||||
|
||||
def check_command(command):
|
||||
try:
|
||||
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -227,7 +302,6 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def install_llama(backend):
|
||||
env_vars = {
|
||||
"FORCE_CMAKE": "1"
|
||||
@@ -240,12 +314,10 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
env_vars["CMAKE_ARGS"] = "-DLLAMA_METAL=on"
|
||||
else: # Default to OpenBLAS
|
||||
env_vars["CMAKE_ARGS"] = "-DLLAMA_BLAS=ON -DLLAMA_BLAS_VENDOR=OpenBLAS"
|
||||
|
||||
try:
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "llama-cpp-python"], env={**os.environ, **env_vars}, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error during installation with {backend}: {e}")
|
||||
|
||||
print(f"Error during installation with {backend}: {e}", file=sys.stderr)
|
||||
def supports_metal():
|
||||
# Check for macOS version
|
||||
if platform.system() == "Darwin":
|
||||
@@ -254,7 +326,6 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
if mac_version >= (10, 11):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Check system capabilities
|
||||
if check_command(["nvidia-smi"]):
|
||||
install_llama("cuBLAS")
|
||||
@@ -264,15 +335,13 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
install_llama("Metal")
|
||||
else:
|
||||
install_llama("OpenBLAS")
|
||||
|
||||
from llama_cpp import Llama
|
||||
print('', Markdown("Finished downloading `Code-Llama` interface."), '')
|
||||
|
||||
# Check if on macOS
|
||||
if platform.system() == "Darwin":
|
||||
# Check if it's Apple Silicon
|
||||
if platform.machine() != "arm64":
|
||||
print("Warning: Running python-x86 on arm64, which is 10x slower than native m1")
|
||||
print("Warning: Running python-x86 on arm64, which is 10x slower than native m1", file=sys.stderr)
|
||||
else:
|
||||
print('', "Installation cancelled. Exiting.", '')
|
||||
return None
|
||||
@@ -292,9 +361,73 @@ def get_hf_llm(repo_id, debug_mode, context_window):
|
||||
json.dump(usermodels, fd)
|
||||
fd.close()
|
||||
print("Saved")
|
||||
llama_2 = Llama(model_path=model_path, n_gpu_layers=n_gpu_layers, verbose=debug_mode, n_ctx=context_window)
|
||||
# print("[r2ai] Using model: " + model_path)
|
||||
return llama_2
|
||||
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
|
||||
|
||||
def list_downloaded_models():
|
||||
try:
|
||||
fd = open(r2ai_model_json)
|
||||
usermodels = json.load(fd)
|
||||
fd.close()
|
||||
for m in usermodels:
|
||||
if m.find("/") != -1:
|
||||
M = m.ljust(40)
|
||||
try:
|
||||
size = round(os.path.getsize(usermodels[m]) / 1024 / 1024 / 1024, 2)
|
||||
size = f"{size}".rjust(6)
|
||||
print(f" {size}G {M}")
|
||||
except Exception as e:
|
||||
print(f" ??? {M}")
|
||||
user_data_dir = appdirs.user_data_dir("r2ai")
|
||||
models_path = os.path.join(user_data_dir, "models")
|
||||
home_dir = os.path.expanduser("~")
|
||||
sympath = os.path.join(home_dir, ".r2ai.models")
|
||||
files = os.listdir(models_path)
|
||||
for file in files:
|
||||
if file.endswith(".gguf") == False:
|
||||
continue
|
||||
found = False
|
||||
for m in usermodels:
|
||||
last_part = usermodels[m].split("/")[-1]
|
||||
if last_part == file:
|
||||
found = True
|
||||
if not found:
|
||||
M = f"{sympath}/{file}"
|
||||
size = round(os.path.getsize(M) / 1024 / 1024 / 1024, 2)
|
||||
size = f"{size}".rjust(6)
|
||||
print(f" {size}G {M}")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
def delete_downloaded_model(name):
|
||||
fd = None
|
||||
try:
|
||||
name = name.strip()
|
||||
if not os.path.exists(name):
|
||||
print("Cannot find " + name)
|
||||
return None
|
||||
if name.startswith("/"):
|
||||
if name.endswith(".gguf"):
|
||||
os.remove(name)
|
||||
return None
|
||||
print("Cant delete non-gguf files")
|
||||
return None
|
||||
fd = open(r2ai_model_json)
|
||||
usermodels = json.load(fd)
|
||||
fd.close()
|
||||
fd = None
|
||||
if name in usermodels:
|
||||
modelpath = usermodels[name]
|
||||
os.remove(modelpath)
|
||||
del usermodels[name]
|
||||
fd = open(r2ai_model_json, "w")
|
||||
json.dump(usermodels, fd)
|
||||
fd.close()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
return None
|
||||
|
||||
def set_default_model(repo_id):
|
||||
usermodels = {"default": repo_id}
|
||||
@@ -302,7 +435,7 @@ def set_default_model(repo_id):
|
||||
fd = open(r2ai_model_json)
|
||||
usermodels = json.load(fd)
|
||||
fd.close()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
usermodels["default"] = repo_id
|
||||
# usermodels[repo_id] = model_path
|
||||
@@ -330,15 +463,13 @@ def list_gguf_files(repo_id: str) -> List[Dict[str, Union[str, float]]]:
|
||||
"""
|
||||
|
||||
try:
|
||||
files_info = list_files_info(repo_id=repo_id)
|
||||
from huggingface_hub import HfApi
|
||||
api = HfApi()
|
||||
tree = list(api.list_repo_tree(repo_id))
|
||||
files_info = [file for file in tree if file.path.endswith('.gguf')]
|
||||
except Exception as e:
|
||||
if "authentication" in str(e).lower():
|
||||
print("You likely need to be logged in to HuggingFace to access this language model.")
|
||||
print(f"Visit this URL to log in and apply for access to this language model: https://huggingface.co/{repo_id}")
|
||||
print("Then, log in here:")
|
||||
login()
|
||||
files_info = list_files_info(repo_id=repo_id)
|
||||
|
||||
traceback.print_exc()
|
||||
return []
|
||||
gguf_files = [file for file in files_info if "gguf" in file.rfilename]
|
||||
if len(gguf_files) == 0:
|
||||
print("[r2ai] No ggml or gguf files for " + repo_id)
|
||||
@@ -369,7 +500,6 @@ def group_and_combine_splits(models: List[Dict[str, Union[str, float]]]) -> List
|
||||
|
||||
for model in models:
|
||||
base_name = model["filename"].split('-split-')[0]
|
||||
|
||||
if base_name in grouped_files:
|
||||
grouped_files[base_name]["Size"] += model["Size"]
|
||||
grouped_files[base_name]["RAM"] += model["RAM"]
|
||||
@@ -392,7 +522,7 @@ def actually_combine_files(default_path: str, base_name: str, files: List[str])
|
||||
:param base_name: The base name for the combined file.
|
||||
:param files: List of files to be combined.
|
||||
"""
|
||||
files.sort()
|
||||
files.sort()
|
||||
base_path = os.path.join(default_path, base_name)
|
||||
with open(base_path, 'wb') as outfile:
|
||||
for file in files:
|
||||
@@ -420,49 +550,35 @@ def enough_disk_space(size, path) -> bool:
|
||||
_, _, free = shutil.disk_usage(path)
|
||||
|
||||
# Convert bytes to gigabytes
|
||||
free_gb = free / (2**30)
|
||||
free_gb = free / (2**30)
|
||||
if free_gb > size:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def new_get_hf_llm(repo_id, debug_mode, context_window):
|
||||
if repo_id.startswith("openai:"):
|
||||
def new_get_hf_llm(ai, repo_id, context_window):
|
||||
debug_mode = True if ai.env["debug_level"] == "1" else False
|
||||
if repo_id.startswith("openai:") or repo_id.startswith("anthropic:") or repo_id.startswith("groq:") or repo_id.startswith("google:"):
|
||||
return repo_id
|
||||
if not os.path.exists(repo_id):
|
||||
return get_hf_llm(repo_id, debug_mode, context_window)
|
||||
# print("LOADING FILE: " + repo_id)
|
||||
n_gpu_layers = -1 # = 0 to use cpu
|
||||
# n_gpu_layers = 0
|
||||
# Third stage: GPU confirm
|
||||
#if confirm_action("Use GPU? (Large models might crash on GPU, but will run more quickly)"):
|
||||
## n_gpu_layers = -1
|
||||
# else:
|
||||
# n_gpu_layers = 0
|
||||
|
||||
# Get user data directory
|
||||
return get_hf_llm(ai, repo_id, debug_mode, context_window)
|
||||
# print(f"LOADING FILE: {repo_id}")
|
||||
user_data_dir = appdirs.user_data_dir("Open Interpreter")
|
||||
default_path = os.path.join(user_data_dir, "models")
|
||||
|
||||
# Ensure the directory exists
|
||||
os.makedirs(default_path, exist_ok=True)
|
||||
model_path = repo_id
|
||||
# This is helpful for folks looking to delete corrupted ones and such
|
||||
#print(Markdown(f"Model found at `{model_path}`"))
|
||||
|
||||
try:
|
||||
from llama_cpp import Llama
|
||||
except:
|
||||
except Exception:
|
||||
if debug_mode:
|
||||
traceback.print_exc()
|
||||
# Ask for confirmation to install the required pip package
|
||||
message = "Local LLM interface package not found. Install `llama-cpp-python`?"
|
||||
if confirm_action(message):
|
||||
|
||||
# We're going to build llama-cpp-python correctly for the system we're on
|
||||
|
||||
import platform
|
||||
|
||||
def check_command(command):
|
||||
try:
|
||||
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -471,12 +587,10 @@ def new_get_hf_llm(repo_id, debug_mode, context_window):
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def install_llama(backend):
|
||||
env_vars = {
|
||||
"FORCE_CMAKE": "1"
|
||||
}
|
||||
|
||||
if backend == "cuBLAS":
|
||||
env_vars["CMAKE_ARGS"] = "-DLLAMA_CUBLAS=on"
|
||||
elif backend == "hipBLAS":
|
||||
@@ -485,12 +599,10 @@ def new_get_hf_llm(repo_id, debug_mode, context_window):
|
||||
env_vars["CMAKE_ARGS"] = "-DLLAMA_METAL=on"
|
||||
else: # Default to OpenBLAS
|
||||
env_vars["CMAKE_ARGS"] = "-DLLAMA_BLAS=ON -DLLAMA_BLAS_VENDOR=OpenBLAS"
|
||||
|
||||
try:
|
||||
subprocess.run([sys.executable, "-m", "pip", "install", "llama-cpp-python"], env={**os.environ, **env_vars}, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error during installation with {backend}: {e}")
|
||||
|
||||
print(f"Error during installation with {backend}: {e}", file=sys.stderr)
|
||||
def supports_metal():
|
||||
# Check for macOS version
|
||||
if platform.system() == "Darwin":
|
||||
@@ -499,7 +611,6 @@ def new_get_hf_llm(repo_id, debug_mode, context_window):
|
||||
if mac_version >= (10, 11):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Check system capabilities
|
||||
if check_command(["nvidia-smi"]):
|
||||
install_llama("cuBLAS")
|
||||
@@ -509,30 +620,23 @@ def new_get_hf_llm(repo_id, debug_mode, context_window):
|
||||
install_llama("Metal")
|
||||
else:
|
||||
install_llama("OpenBLAS")
|
||||
|
||||
from llama_cpp import Llama
|
||||
print('', Markdown("Finished downloading `Code-Llama` interface."), '')
|
||||
|
||||
# Tell them if their architecture won't work well
|
||||
|
||||
# Check if on macOS
|
||||
if platform.system() == "Darwin":
|
||||
# Check if it's Apple Silicon
|
||||
if platform.machine() != "arm64":
|
||||
print("Warning: You are using Apple Silicon (M1/M2) Mac but your Python is not of 'arm64' architecture.")
|
||||
print("The llama.ccp x86 version will be 10x slower on Apple Silicon (M1/M2) Mac.")
|
||||
print("\nTo install the correct version of Python that supports 'arm64' architecture:")
|
||||
print("1. Download Miniforge for M1/M2:")
|
||||
print("wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh")
|
||||
print("2. Install it:")
|
||||
print("bash Miniforge3-MacOSX-arm64.sh")
|
||||
print("")
|
||||
|
||||
else:
|
||||
print('', "Installation cancelled. Exiting.", '')
|
||||
print("Installation cancelled. Exiting.", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Initialize and return Code-Llama
|
||||
if not os.path.isfile(model_path):
|
||||
print("Model is not a file")
|
||||
return Llama(model_path=model_path, n_gpu_layers=n_gpu_layers, verbose=debug_mode, n_ctx=context_window)
|
||||
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
|
||||
|
||||
|
||||
def get_llama_inst(repo_id, **kwargs):
|
||||
import llama_cpp
|
||||
if 'functionary' in repo_id:
|
||||
from llama_cpp.llama_tokenizer import LlamaHFTokenizer
|
||||
kwargs['tokenizer'] = LlamaHFTokenizer.from_pretrained(repo_id)
|
||||
filename = os.path.basename(kwargs.pop('model_path'))
|
||||
kwargs['echo'] = True
|
||||
return llama_cpp.Llama.from_pretrained(**kwargs, repo_id=repo_id, filename=filename)
|
||||
return llama_cpp.Llama(**kwargs)
|
||||
@@ -0,0 +1,223 @@
|
||||
class IncompleteJSONParser:
|
||||
def __init__(self, s):
|
||||
self.s = s
|
||||
self.len = len(s)
|
||||
self.pos = 0
|
||||
|
||||
def parse(self):
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
return None
|
||||
char = self.s[self.pos]
|
||||
if char == '{':
|
||||
return self.parse_object()
|
||||
elif char == '[':
|
||||
return self.parse_array()
|
||||
else:
|
||||
return None # Top-level must be object or array
|
||||
|
||||
def parse_object(self):
|
||||
if self.s[self.pos] != '{':
|
||||
return None
|
||||
self.pos += 1 # Skip '{'
|
||||
self.skip_whitespace()
|
||||
obj = {}
|
||||
parsed_any = False # Flag to check if any key-value pair was parsed
|
||||
|
||||
while self.pos < self.len:
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
break
|
||||
if self.s[self.pos] == '}':
|
||||
self.pos += 1
|
||||
return obj if parsed_any else None
|
||||
key = self.parse_string()
|
||||
if key is None:
|
||||
# Incomplete key, skip the rest
|
||||
break
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len or self.s[self.pos] != ':':
|
||||
# Missing colon, skip this key
|
||||
break
|
||||
self.pos += 1 # Skip ':'
|
||||
self.skip_whitespace()
|
||||
value = self.parse_value()
|
||||
if value is None:
|
||||
# Incomplete value, skip this key-value pair
|
||||
break
|
||||
obj[key] = value
|
||||
parsed_any = True
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
break
|
||||
if self.s[self.pos] == ',':
|
||||
self.pos += 1
|
||||
continue
|
||||
elif self.s[self.pos] == '}':
|
||||
self.pos += 1
|
||||
return obj if parsed_any else None
|
||||
else:
|
||||
# Unexpected character, skip
|
||||
break
|
||||
# Auto-close the object
|
||||
return obj if parsed_any else None
|
||||
|
||||
def parse_array(self):
|
||||
if self.s[self.pos] != '[':
|
||||
return None
|
||||
self.pos += 1 # Skip '['
|
||||
self.skip_whitespace()
|
||||
array = []
|
||||
parsed_any = False # Flag to check if any element was parsed
|
||||
|
||||
while self.pos < self.len:
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
break
|
||||
if self.s[self.pos] == ']':
|
||||
self.pos += 1
|
||||
return array if parsed_any else None
|
||||
value = self.parse_value()
|
||||
if value is None:
|
||||
# Incomplete value, skip this element
|
||||
break
|
||||
array.append(value)
|
||||
parsed_any = True
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
break
|
||||
if self.s[self.pos] == ',':
|
||||
self.pos += 1
|
||||
continue
|
||||
elif self.s[self.pos] == ']':
|
||||
self.pos += 1
|
||||
return array if parsed_any else None
|
||||
else:
|
||||
# Unexpected character, skip
|
||||
break
|
||||
# Auto-close the array
|
||||
return array if parsed_any else None
|
||||
|
||||
def parse_value(self):
|
||||
self.skip_whitespace()
|
||||
if self.pos >= self.len:
|
||||
return None
|
||||
char = self.s[self.pos]
|
||||
if char == '"':
|
||||
return self.parse_string()
|
||||
elif char == '{':
|
||||
return self.parse_object()
|
||||
elif char == '[':
|
||||
return self.parse_array()
|
||||
elif char in '-0123456789':
|
||||
return self.parse_number()
|
||||
elif self.s.startswith('true', self.pos):
|
||||
self.pos += 4
|
||||
return True
|
||||
elif self.s.startswith('false', self.pos):
|
||||
self.pos += 5
|
||||
return False
|
||||
elif self.s.startswith('null', self.pos):
|
||||
self.pos += 4
|
||||
return None
|
||||
else:
|
||||
return None # Invalid value
|
||||
|
||||
def parse_string(self):
|
||||
if self.s[self.pos] != '"':
|
||||
return None
|
||||
self.pos += 1 # Skip opening quote
|
||||
result = ""
|
||||
while self.pos < self.len:
|
||||
char = self.s[self.pos]
|
||||
if char == '\\':
|
||||
if self.pos + 1 >= self.len:
|
||||
result += '\\'
|
||||
self.pos += 1
|
||||
break # Incomplete escape, return what we have
|
||||
self.pos += 1
|
||||
escape_char = self.s[self.pos]
|
||||
if escape_char == '"':
|
||||
result += '"'
|
||||
elif escape_char == '\\':
|
||||
result += '\\'
|
||||
elif escape_char == '/':
|
||||
result += '/'
|
||||
elif escape_char == 'b':
|
||||
result += '\b'
|
||||
elif escape_char == 'f':
|
||||
result += '\f'
|
||||
elif escape_char == 'n':
|
||||
result += '\n'
|
||||
elif escape_char == 'r':
|
||||
result += '\r'
|
||||
elif escape_char == 't':
|
||||
result += '\t'
|
||||
elif escape_char == 'u':
|
||||
# Unicode escape
|
||||
if self.pos + 4 >= self.len:
|
||||
# Incomplete unicode escape
|
||||
break
|
||||
hex_digits = self.s[self.pos+1:self.pos+5]
|
||||
try:
|
||||
code_point = int(hex_digits, 16)
|
||||
result += chr(code_point)
|
||||
self.pos += 4
|
||||
except ValueError:
|
||||
break # Invalid unicode escape
|
||||
else:
|
||||
# Invalid escape character
|
||||
result += '\\' + escape_char # Keep it as is
|
||||
elif char == '"':
|
||||
self.pos += 1 # Skip closing quote
|
||||
return result
|
||||
else:
|
||||
result += char
|
||||
self.pos += 1
|
||||
# Return the partial string if incomplete
|
||||
return result
|
||||
|
||||
def parse_number(self):
|
||||
start = self.pos
|
||||
if self.s[self.pos] == '-':
|
||||
self.pos += 1
|
||||
if self.pos >= self.len:
|
||||
return None
|
||||
if self.s[self.pos] == '0':
|
||||
self.pos += 1
|
||||
elif '1' <= self.s[self.pos] <= '9':
|
||||
while self.pos < self.len and self.s[self.pos].isdigit():
|
||||
self.pos += 1
|
||||
else:
|
||||
return None # Invalid number
|
||||
if self.pos < self.len and self.s[self.pos] == '.':
|
||||
self.pos += 1
|
||||
if self.pos >= self.len or not self.s[self.pos].isdigit():
|
||||
return None # Incomplete fraction
|
||||
while self.pos < self.len and self.s[self.pos].isdigit():
|
||||
self.pos += 1
|
||||
if self.pos < self.len and self.s[self.pos] in 'eE':
|
||||
self.pos += 1
|
||||
if self.pos < self.len and self.s[self.pos] in '+-':
|
||||
self.pos += 1
|
||||
if self.pos >= self.len or not self.s[self.pos].isdigit():
|
||||
return None # Incomplete exponent
|
||||
while self.pos < self.len and self.s[self.pos].isdigit():
|
||||
self.pos += 1
|
||||
num_str = self.s[start:self.pos]
|
||||
try:
|
||||
if '.' in num_str or 'e' in num_str or 'E' in num_str:
|
||||
return float(num_str)
|
||||
else:
|
||||
return int(num_str)
|
||||
except ValueError:
|
||||
return None # Invalid number
|
||||
|
||||
def skip_whitespace(self):
|
||||
while self.pos < self.len and self.s[self.pos] in ' \t\n\r':
|
||||
self.pos += 1
|
||||
|
||||
def parse_incomplete_json(s):
|
||||
parser = IncompleteJSONParser(s)
|
||||
result = parser.parse()
|
||||
return result
|
||||
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
import traceback
|
||||
import r2pipe
|
||||
|
||||
|
||||
have_rlang = False
|
||||
r2lang = None
|
||||
|
||||
r2 = None
|
||||
filename = None
|
||||
class FakeLang:
|
||||
def __init__(self, r2 = None):
|
||||
self.r2 = r2
|
||||
|
||||
def ai(self, x):
|
||||
try:
|
||||
from r2ai.repl import r2ai_singleton, runline2
|
||||
ai = r2ai_singleton()
|
||||
if ai is None:
|
||||
print("No global r2ai instance found")
|
||||
return ""
|
||||
return runline2(ai, x)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def cmd(self, x):
|
||||
if self.r2 is None:
|
||||
return ""
|
||||
if hasattr(self.r2, "_cmd"):
|
||||
return self.r2.cmd(x)
|
||||
return ""
|
||||
|
||||
try:
|
||||
import r2lang
|
||||
have_rlang = True
|
||||
except Exception:
|
||||
import r2pipe
|
||||
try:
|
||||
if r2pipe.in_r2():
|
||||
r2lang = FakeLang(r2pipe.open())
|
||||
r2lang.cmd("?V") # r2pipe throws only here
|
||||
else:
|
||||
raise Exception("must spawn")
|
||||
except Exception:
|
||||
try:
|
||||
have_rlang = False
|
||||
if os.environ.get('R2AI') is None:
|
||||
ppid = os.getppid()
|
||||
os.environ["R2AI"] = "1"
|
||||
filename = "/bin/ls"
|
||||
r2lang = FakeLang(r2pipe.open(filename))
|
||||
else:
|
||||
r2lang = FakeLang(None)
|
||||
except Exception:
|
||||
print("Cannot instantiate this FakeLang class with r2pipe")
|
||||
r2lang = FakeLang(None)
|
||||
|
||||
def r2singleton():
|
||||
global r2lang, r2
|
||||
return r2lang or r2
|
||||
|
||||
def get_r2_inst():
|
||||
return r2singleton()
|
||||
|
||||
def open_r2(file, flags=[]):
|
||||
global r2, filename, r2lang
|
||||
r2 = r2pipe.open(file, flags=flags)
|
||||
r2lang = FakeLang(r2)
|
||||
filename = file
|
||||
return r2lang
|
||||
|
||||
def get_filename():
|
||||
global filename
|
||||
return filename
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Entrypoint for the r2ai plugin and repl."""
|
||||
import sys
|
||||
import os
|
||||
import builtins
|
||||
import traceback
|
||||
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
sys.path.insert(0, parent_dir)
|
||||
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
|
||||
try:
|
||||
venv_dir = os.path.join(parent_dir, 'venv')
|
||||
if os.path.exists(venv_dir):
|
||||
site_packages = os.path.join(venv_dir, 'lib', 'python{}.{}'.format(*sys.version_info[:2]), 'site-packages')
|
||||
if os.path.exists(site_packages):
|
||||
sys.path.insert(0, site_packages)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import r2lang
|
||||
from r2ai.main import r2ai_singleton, run_rcfile_once, runline, help_message
|
||||
|
||||
def r2ai_rlang_plugin(unused_but_required_argument):
|
||||
ai = r2ai_singleton()
|
||||
def _call(s):
|
||||
if not s.startswith("r2ai"):
|
||||
return False
|
||||
try:
|
||||
run_rcfile_once(ai)
|
||||
if len(s) == 4:
|
||||
builtins.print(help_message)
|
||||
else:
|
||||
usertext = s[4:].strip()
|
||||
runline(ai, usertext)
|
||||
except Exception as e:
|
||||
builtins.print(e)
|
||||
traceback.print_exc()
|
||||
return True
|
||||
|
||||
return {
|
||||
"name": "r2ai",
|
||||
"license": "MIT",
|
||||
"desc": "run llama language models inside r2",
|
||||
"call": _call,
|
||||
}
|
||||
|
||||
r2lang.plugin("core", r2ai_rlang_plugin)
|
||||
@@ -0,0 +1,100 @@
|
||||
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn, Task
|
||||
from rich.console import Console
|
||||
from .web import server_running, server_in_background
|
||||
from inspect import signature
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def _support_total(sig, *args, **kwargs):
|
||||
bound = sig.bind(*args, **kwargs)
|
||||
bound.apply_defaults()
|
||||
return "__progress_cls" in bound.arguments and "__progress_task" in bound.arguments
|
||||
|
||||
|
||||
def progress_bar(text, color=None, total=None, infinite=False):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
sig = signature(func)
|
||||
has_total = total is not None and _support_total(
|
||||
sig, *args, **kwargs)
|
||||
is_infinite = infinite or not has_total
|
||||
|
||||
# if server_running() and not server_in_background():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
with Progress(SpinnerColumn(), *Progress.get_default_columns(), console=Console(no_color=not bool(color)), transient=False) as p:
|
||||
task_text = f"[{color}]{text}" if color else text
|
||||
task = p.add_task(
|
||||
task_text, total=None if is_infinite else total)
|
||||
|
||||
if has_total:
|
||||
result = func(
|
||||
*args,
|
||||
**kwargs,
|
||||
__progress_cls=p,
|
||||
__progress_task=task)
|
||||
else:
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
# For consistency with the above
|
||||
class ProgressBar:
|
||||
def __init__(self, text, color=None, total=None, infinite=False) -> None:
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.total = total
|
||||
self.infinite = infinite
|
||||
self.progress: Progress = None
|
||||
self.task: Task = None
|
||||
|
||||
def __enter__(self):
|
||||
self.progress = Progress(
|
||||
SpinnerColumn(),
|
||||
*Progress.get_default_columns(),
|
||||
console=Console(
|
||||
no_color=not bool(
|
||||
self.color)),
|
||||
transient=False)
|
||||
if self.color:
|
||||
self.task = self.progress.add_task(
|
||||
f"[{self.color}]{self.text}", total=None if self.infinite else self.total)
|
||||
else:
|
||||
self.task = self.progress.add_task(
|
||||
f"{self.text}", total=None if self.infinite else self.total)
|
||||
self.progress.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.progress:
|
||||
self.progress.stop()
|
||||
|
||||
# from .progress import ProgressBar, progress_bar
|
||||
# progress_bar is used as an decorator
|
||||
# @progress_bar(Title, color="yellow")
|
||||
# def foo():
|
||||
# bar
|
||||
#
|
||||
#
|
||||
# unlike in the class, the decorated functin can only use progressive progress only if
|
||||
# __progress_cls and __progress_task are used as positional arguments. else it defaults to infinite
|
||||
# @progress_bar("Title", color="yellow", total=100)
|
||||
# def foo(a,b, __progress_cls=None, __progress_task=None):
|
||||
# i = 1
|
||||
# while True:
|
||||
# progress_cls.update(p.task, advance=i)
|
||||
# i+=1
|
||||
# time.sleep(1)
|
||||
#
|
||||
#
|
||||
# ProgressBar is made for consistency with the decorator
|
||||
# import time
|
||||
# with ProgressBar("Title", color="Yellow", total=50) as p:
|
||||
# i = 0
|
||||
# while True:
|
||||
# p.progress.update(p.task, advance=i)
|
||||
# i+=1
|
||||
# time.sleep(1)
|
||||
@@ -0,0 +1,567 @@
|
||||
import builtins
|
||||
from .models import set_default_model, list_downloaded_models, delete_downloaded_model, models, mainmodels
|
||||
from .utils import slurp
|
||||
from .large import Large
|
||||
import traceback
|
||||
from .const import R2AI_HISTFILE, R2AI_HOMEDIR, R2AI_RCFILE, R2AI_USERDIR
|
||||
from .web import start_http_server
|
||||
import r2ai
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import os
|
||||
|
||||
from .tab import tab_init, tab_hist, tab_write, tab_evals, tab_list
|
||||
from .interpreter import Interpreter
|
||||
from .pipe import have_rlang, r2lang, r2singleton
|
||||
from r2ai import bubble, LOGGER
|
||||
from .test import run_test
|
||||
tab_init()
|
||||
|
||||
print_buffer = ""
|
||||
ais = []
|
||||
autoai = None
|
||||
r2 = r2singleton()
|
||||
|
||||
def r2ai_singleton():
|
||||
global ais
|
||||
if len(ais) == 0:
|
||||
ai = Interpreter()
|
||||
ais.append(R2AI(ai))
|
||||
return ais[0].ai
|
||||
|
||||
def r2_cmd(x):
|
||||
r2 = r2singleton()
|
||||
res = None
|
||||
if r2 is not None:
|
||||
oc = r2.cmd('e scr.color').strip()
|
||||
r2.cmd('e scr.color=3')
|
||||
res = r2.cmd(x)
|
||||
r2.cmd('e scr.color=' + oc)
|
||||
return res
|
||||
|
||||
help_message = """Usage: r2ai [-option] ([query] | [script.py])
|
||||
r2ai . [file] interpret r2ai script with access to globals
|
||||
r2ai ..([script]) list or run r2ai user script
|
||||
r2ai :aa run a r2 command
|
||||
r2ai ' [prompt] auto mode; query LLM that can interact with r2
|
||||
r2ai ?e [msg] echo a message
|
||||
r2ai ?t [query] run an query and show it's timing
|
||||
r2ai !ls run a system command
|
||||
r2ai ! show prompt history
|
||||
r2ai -a query with audio voice
|
||||
r2ai -A enter the voice chat loop
|
||||
r2ai -k clear the screen
|
||||
r2ai -c [cmd] [query] run the given r2 command with the given query
|
||||
r2ai -e [k[=v]] set environment variable
|
||||
r2ai -ed launch user.editor with ~/.r2ai.rc
|
||||
r2ai -f [file] load file and paste the output
|
||||
r2ai -h show this help (same as ?)
|
||||
r2ai -H ([var]) show path variables like it's done in r2 -H
|
||||
r2ai -i [file] ([q]) load the file contents and prompt it with the given optional query
|
||||
r2ai -j [query] convert the user prompt into a meaningful json
|
||||
r2ai -m [file/repo] select model from huggingface repository or local file
|
||||
r2ai -m-[repo/model] delete a local downloaded model (see -mm for listing them)
|
||||
r2ai -mm list all downloaded models
|
||||
r2ai -M shorter list of models
|
||||
r2ai -MM list supported and most common models from hf
|
||||
r2ai -n [num] select the nth language model
|
||||
r2ai -q quit/exit/^C
|
||||
r2ai -L show chat logs (See -Lj for json)
|
||||
r2ai -L-[N] delete the last (or N last messages from the chat history)
|
||||
r2ai -p1c0 enter the Pico le Croco assistant mode
|
||||
r2ai -repl enter the repl (only when running via r2pipe)
|
||||
r2ai -r [sysprompt] define the role of the conversation
|
||||
r2ai -r2 enter the r2clippy assistant mode
|
||||
r2ai -rf [doc/role/.f] load contents of a file to define the role
|
||||
r2ai -R reset the chat conversation context
|
||||
r2ai -v show r2ai version (same as ?V)
|
||||
r2ai -w ([port]) start webserver (curl -D hello http://localhost:8000)
|
||||
r2ai -W ([port]) start webserver in background
|
||||
r2ai -VV visual mode
|
||||
r2ai -W ([port]) start webserver in background"""
|
||||
|
||||
def myprint(msg, file=None):
|
||||
global print_buffer
|
||||
builtins.print(msg)
|
||||
print_buffer += str(msg)
|
||||
|
||||
def runline2(ai, usertext):
|
||||
global print
|
||||
global print_buffer
|
||||
ai.print = myprint
|
||||
chat_live = ai.env["chat.live"]
|
||||
ai.env["chat.live"] = "false"
|
||||
print = myprint
|
||||
runline(ai, usertext)
|
||||
ai.env["chat.live"] = chat_live
|
||||
res = print_buffer
|
||||
print_buffer = ""
|
||||
return f"{res}\n"
|
||||
|
||||
def runplugin(ai, arg):
|
||||
r2ai_plugdir = ai.env["user.plugins"]
|
||||
if arg != "":
|
||||
for plugdir in [R2AI_USERDIR, r2ai_plugdir]:
|
||||
script_path = f"{plugdir}/{arg}.py"
|
||||
if os.path.isfile(script_path):
|
||||
runline(ai, f". {script_path}")
|
||||
return
|
||||
print("Script not found", file=sys.stderr)
|
||||
return
|
||||
try:
|
||||
# print("-e user.plugins = " + r2ai_plugdir)
|
||||
for plugdir in [R2AI_USERDIR, r2ai_plugdir]:
|
||||
files = os.listdir(plugdir)
|
||||
for file in files:
|
||||
if file.endswith(".py"):
|
||||
print(file.replace(".py", ""))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def r2ai_version():
|
||||
import llama_cpp
|
||||
print("python: " + sys.version)
|
||||
print("llama: " + llama_cpp.__version__)
|
||||
print("r2ai: " + r2ai.VERSION)
|
||||
|
||||
def r2ai_vars(ai, arg):
|
||||
vs = {
|
||||
"R2AI_USERDIR": R2AI_USERDIR,
|
||||
"R2AI_PLUGDIR": ai.env["user.plugins"],
|
||||
"R2AI_HOMEDIR": R2AI_HOMEDIR,
|
||||
"R2AI_RCFILE": R2AI_RCFILE,
|
||||
"R2AI_HISTFILE": R2AI_HISTFILE
|
||||
}
|
||||
if arg != "":
|
||||
if arg in vs.keys():
|
||||
print(vs[arg])
|
||||
else:
|
||||
print("Unknown key", file=sys.stderr)
|
||||
else:
|
||||
for k in vs.keys():
|
||||
print(k)
|
||||
|
||||
# copypasta from main.run_rcfile . todo avoid dup code
|
||||
def run_script(ai, script):
|
||||
try:
|
||||
lines = slurp(script)
|
||||
for line in lines.split("\n"):
|
||||
if line.strip() != "":
|
||||
runline(ai, line)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
class R2AI:
|
||||
def __init__(self,ai):
|
||||
self.ai = ai
|
||||
|
||||
def cmd(self, x):
|
||||
return runline2(self.ai, x)
|
||||
|
||||
def slurp_until(endword):
|
||||
text = ""
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
if line.strip() == endword:
|
||||
break
|
||||
text += line
|
||||
return text
|
||||
|
||||
def list_models():
|
||||
print("ollama/llama3:latest")
|
||||
print("ollama/granite-code:8b")
|
||||
print("anthropic/claude-3-5-sonnet-20240620")
|
||||
print("anthropic/claude-3-7-sonnet-20250219")
|
||||
print("openai/gpt-4o")
|
||||
print("mistral/mistral-tiny")
|
||||
|
||||
def set_model(model):
|
||||
ai = ais[0].ai
|
||||
if ai.model != model:
|
||||
ai.llama_instance = None
|
||||
ai.model = model
|
||||
if autoai:
|
||||
autoai.model = model
|
||||
ai.env["llm.model"] = ai.model
|
||||
set_default_model(ai.model)
|
||||
|
||||
def runline(ai, usertext):
|
||||
global print
|
||||
global autoai
|
||||
if ai == None:
|
||||
ai = ais[0].ai
|
||||
usertext = usertext.strip()
|
||||
if usertext == "" or usertext.startswith("#"):
|
||||
return
|
||||
if usertext == "q":
|
||||
return "q"
|
||||
# pipepos = usertext.find("|")
|
||||
# if pipepos != -1:
|
||||
# usertext = usertext[0:pipepos - 1]
|
||||
# print("TODO: | pipes are not yet implemented in r2ai", file=sys.stderr)
|
||||
# redipos = usertext.find(">")
|
||||
# if redipos != -1:
|
||||
# usertext = usertext[0:redipos - 1]
|
||||
# print("TODO: > redirections are not yet implemented in r2ai", file=sys.stderr)
|
||||
if usertext.startswith("-H"):
|
||||
try:
|
||||
return r2ai_vars(ai, usertext[2:].strip())
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
if usertext.startswith("-VV"):
|
||||
from ui.app import R2AIApp # pylint: disable=import-error
|
||||
R2AIApp(ai=ai).run()
|
||||
return
|
||||
if usertext.startswith("?V") or usertext.startswith("-v"):
|
||||
r2ai_version()
|
||||
elif usertext.startswith("<<"):
|
||||
newline = slurp_until(usertext[2:])
|
||||
return runline(ai, newline)
|
||||
elif usertext.startswith("?e"):
|
||||
print(usertext[2:].strip())
|
||||
elif usertext.startswith("?t"):
|
||||
tstart = datetime.now()
|
||||
runline(ai, usertext[2:].strip())
|
||||
tend = datetime.now()
|
||||
print(tend - tstart)
|
||||
elif usertext.startswith("?") or usertext.startswith("-h") or usertext.startswith("-?"):
|
||||
print(help_message)
|
||||
elif usertext.startswith("clear") or usertext.startswith("-k"):
|
||||
print("\x1b[2J\x1b[0;0H\r")
|
||||
if ai.messages:
|
||||
ai.messages = []
|
||||
if autoai and autoai.messages:
|
||||
autoai.messages = []
|
||||
elif usertext.startswith("-MM"):
|
||||
print(models().strip())
|
||||
elif usertext.startswith("-M"):
|
||||
print(mainmodels().strip())
|
||||
elif usertext.startswith("-mm"):
|
||||
list_downloaded_models()
|
||||
elif usertext.startswith("-m-"):
|
||||
delete_downloaded_model(usertext[3:])
|
||||
elif usertext.startswith("r2ai"):
|
||||
print("ERROR: Cant run r2ai from r2ai, you must install the C plugin and run this command from radare2")
|
||||
elif usertext == "-m?":
|
||||
list_models()
|
||||
elif usertext.startswith("-m"):
|
||||
words = usertext.split(" ")
|
||||
if len(words) > 1:
|
||||
set_model(words[1])
|
||||
else:
|
||||
print(ai.model)
|
||||
elif usertext == "reset" or usertext.startswith("-R"):
|
||||
ai.reset()
|
||||
if autoai:
|
||||
autoai.reset()
|
||||
elif usertext.startswith("-j"):
|
||||
q = usertext[2:].strip()
|
||||
query = f"Please respond using ONLY in JSON with the following fields if relevant: topic, array of key words, location, url, name, description, target, amounts and other details if necessary without providing any response. Question: \"{q}\""
|
||||
ai.chat(query)
|
||||
elif usertext == "-A":
|
||||
from .voice import stt
|
||||
ai.env["chat.voice"] = "true"
|
||||
old_live = ai.env["chat.live"]
|
||||
ai.env["chat.live"] = "false"
|
||||
while True:
|
||||
usertext = stt(4, ai.env["voice.lang"])
|
||||
if usertext != "":
|
||||
print(f"User: {usertext}")
|
||||
ai.chat(usertext)
|
||||
ai.env["chat.live"] = old_live
|
||||
ai.env["chat.voice"] = "false"
|
||||
elif usertext == "-a":
|
||||
from .voice import stt
|
||||
ai.env["chat.voice"] = "true"
|
||||
old_live = ai.env["chat.live"]
|
||||
ai.env["chat.live"] = "true"
|
||||
usertext = stt(4, ai.env["voice.lang"])
|
||||
print(usertext)
|
||||
ai.chat(usertext)
|
||||
ai.env["chat.live"] = old_live
|
||||
ai.env["chat.voice"] = "false"
|
||||
elif usertext == "-q" or usertext == "exit":
|
||||
return "q"
|
||||
elif usertext == "-repl":
|
||||
if have_rlang:
|
||||
r2ai_repl(ai)
|
||||
else:
|
||||
print("r2ai -repl # only works when running as an radare2 plugin")
|
||||
elif usertext == "-r2":
|
||||
ai.env["data.use"] = "true"
|
||||
ai.env["data.hist"] = "true"
|
||||
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
|
||||
ai.env["chat.bubble"] = "true"
|
||||
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/r2clippy.txt")
|
||||
elif usertext == "-p1c0":
|
||||
ai.env["data.use"] = "true"
|
||||
ai.env["data.hist"] = "true"
|
||||
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
|
||||
ai.env["chat.bubble"] = "true"
|
||||
ai.env["chat.picolecroco"] = "true"
|
||||
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/picolecroco.txt")
|
||||
elif usertext.startswith("-ed"):
|
||||
editor = "vim" # Defaults to the only real editor
|
||||
if ai.env["user.editor"] != "":
|
||||
editor = ai.env["user.editor"]
|
||||
elif "EDITOR" in os.environ:
|
||||
editor = os.environ["EDITOR"]
|
||||
os.system(f"{editor} {R2AI_RCFILE}")
|
||||
print("Reload? (y/N)")
|
||||
if input() == "y":
|
||||
run_script(ai, R2AI_RCFILE)
|
||||
elif usertext.startswith("-e"):
|
||||
if len(usertext) == 2:
|
||||
for k in ai.env.keys():
|
||||
v = ai.env[k]
|
||||
print(f"-e {k}={v}")
|
||||
elif usertext.endswith("."):
|
||||
kp = usertext[2:].strip()
|
||||
for k in ai.env.keys():
|
||||
if k.startswith(kp):
|
||||
v = ai.env[k]
|
||||
print(f"-e {k}={v}")
|
||||
else:
|
||||
line = usertext[2:].strip().split("=")
|
||||
k = line[0]
|
||||
if len(line) > 1:
|
||||
v = line[1]
|
||||
if v == "":
|
||||
ai.env[k] = ""
|
||||
elif k in ai.env:
|
||||
ai.env[k] = v
|
||||
if k.startswith("llm."):
|
||||
ai.llama_instance = None
|
||||
else:
|
||||
print("Invalid config key", file=sys.stderr)
|
||||
else:
|
||||
try:
|
||||
print(ai.env[k])
|
||||
except Exception:
|
||||
print("Invalid config key", file=sys.stderr)
|
||||
pass
|
||||
elif usertext.startswith("-l"):
|
||||
try:
|
||||
l = Large(ai)
|
||||
t = slurp("doc/samples/qcw.txt")
|
||||
print(l.summarize_text(t))
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
sys.exit(0)
|
||||
elif usertext.startswith("-W"):
|
||||
if len(usertext) > 2:
|
||||
try:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
except:
|
||||
print("Use -w [port-number]")
|
||||
return
|
||||
start_http_server(ai, runline2, True)
|
||||
elif usertext.startswith("-w"):
|
||||
if len(usertext) > 2:
|
||||
try:
|
||||
port = int(usertext[2:])
|
||||
if port > 0:
|
||||
runline2(ai, f"-e http.port={port}")
|
||||
except:
|
||||
print("Use -w [port-number]")
|
||||
return
|
||||
start_http_server(ai, runline2, False)
|
||||
elif usertext.startswith("-s"):
|
||||
ai.runline2 = runline2
|
||||
r2ai_repl(ai)
|
||||
elif usertext.startswith("-rf"):
|
||||
if len(usertext) > 2:
|
||||
fname = usertext[3:].strip()
|
||||
try:
|
||||
ai.system_message = slurp(fname)
|
||||
except Exception:
|
||||
print(f"Cannot open file {fname}", file=sys.stderr)
|
||||
else:
|
||||
print(ai.system_message)
|
||||
elif usertext.startswith("-r"):
|
||||
if len(usertext) > 2:
|
||||
ai.system_message = usertext[2:].strip()
|
||||
else:
|
||||
print(ai.system_message)
|
||||
elif usertext.startswith("-L-"):
|
||||
try:
|
||||
amount = int(usertext[3:])
|
||||
if amount < 1:
|
||||
amount = 1
|
||||
except Exception:
|
||||
amount = 1
|
||||
try:
|
||||
for i in range(amount):
|
||||
ai.messages.pop() # delete user message
|
||||
ai.messages.pop() # delete assistant message
|
||||
except Exception:
|
||||
pass
|
||||
elif usertext.startswith("-Lj"):
|
||||
print(ai.messages)
|
||||
elif usertext.startswith("-L"):
|
||||
for msg in ai.messages:
|
||||
#print(f"<{msg['role']}> {msg['content']}")
|
||||
print(f"\x1b[33m<{msg['role']}>\x1b[0m {msg['content']}")
|
||||
elif usertext.startswith("-f"):
|
||||
text = usertext[2:].strip()
|
||||
try:
|
||||
res = slurp(text)
|
||||
ai.chat(res)
|
||||
except Exception:
|
||||
print("Cannot load file", file=sys.stderr)
|
||||
elif usertext.startswith("-i"):
|
||||
text = usertext[2:].strip()
|
||||
if text == "":
|
||||
print("Usage: r2ai -i [file] [question]")
|
||||
return
|
||||
words = text.split(" ", 1)
|
||||
res = slurp(words[0])
|
||||
if len(words) > 1:
|
||||
que = words[1]
|
||||
# tag = "CODE" # INPUT , TEXT, ..
|
||||
# r2ai.chat("Q: " + que + ":\n["+tag+"]\n"+ res+"\n[/"+tag+"]\n")
|
||||
ai.chat(f"{que}:\n```\n{res}\n```\n")
|
||||
else:
|
||||
que = input("[Query]> ")
|
||||
ai.chat(res)
|
||||
elif usertext.startswith("-n"):
|
||||
if len(ais) == 0:
|
||||
ais.append(R2AI(ai))
|
||||
if usertext == "-n":
|
||||
pos = 0
|
||||
for a in ais:
|
||||
model = a.ai.model
|
||||
print(f"{pos} - {model}")
|
||||
pos += 1
|
||||
else:
|
||||
index = int(usertext[2:])
|
||||
if index < len(ais):
|
||||
ai = ais[index].ai
|
||||
else:
|
||||
ai0 = Interpreter()
|
||||
ai0.model = ai.model
|
||||
ais.append(R2AI(ai0))
|
||||
elif usertext.startswith("-c"):
|
||||
words = usertext[2:].strip().split(" ", 1)
|
||||
res = r2_cmd(words[0])
|
||||
que = ""
|
||||
try:
|
||||
if len(words) > 1:
|
||||
que = words[1]
|
||||
else:
|
||||
que = input("[Query]> ")
|
||||
except Exception:
|
||||
print("")
|
||||
return
|
||||
|
||||
tag = "```\n" # TEXT, INPUT ..
|
||||
ai.chat(f"{que}:\n{tag}\n{res}\n{tag}\n")
|
||||
elif usertext[0] == "!":
|
||||
import shutil
|
||||
columns, rows = shutil.get_terminal_size()
|
||||
if len(usertext) == 1:
|
||||
count = 0
|
||||
for item in tab_list():
|
||||
print(item, file=sys.stderr)
|
||||
count = count + 1
|
||||
if count == rows - 1:
|
||||
print("-- More -- (q)")
|
||||
count = 0
|
||||
if input() == 'q':
|
||||
break
|
||||
else:
|
||||
safe_int = lambda x: int(x) if str(x).isdigit() else 0
|
||||
n = safe_int(usertext[1:])
|
||||
if n > 0:
|
||||
items = tab_list()
|
||||
runline(ai, items[n - 1])
|
||||
else:
|
||||
os.system(usertext[1:])
|
||||
elif usertext[0] == ".":
|
||||
#if len(usertext) > 1 and usertext[1] == ".": # ".." - run user plugins
|
||||
# runplugin(ai, usertext[2:].strip())
|
||||
# return
|
||||
try:
|
||||
filename = usertext[1:].strip()
|
||||
file = slurp(filename)
|
||||
if filename.endswith(".py"):
|
||||
exec(file, globals())
|
||||
else:
|
||||
for line in file.split("\n"):
|
||||
runline(ai, line)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
elif usertext.startswith("' "):
|
||||
if not autoai:
|
||||
autoai = Interpreter()
|
||||
autoai.env = ai.env
|
||||
autoai.auto_run = True
|
||||
|
||||
autoai.chat(usertext[2:])
|
||||
|
||||
elif usertext[0] == ":":
|
||||
if r2 is None:
|
||||
print("r2 is not available", file=sys.stderr)
|
||||
else:
|
||||
builtins.print(r2_cmd(usertext[1:]))
|
||||
elif usertext.startswith("--test"):
|
||||
run_test(usertext[7:])
|
||||
elif usertext.startswith("-"):
|
||||
print(f"Unknown flag '{usertext}'. See 'r2ai -h' for help", file=sys.stderr)
|
||||
else:
|
||||
ai.chat(usertext)
|
||||
|
||||
def r2ai_repl(ai):
|
||||
tab_evals(ai.env.keys())
|
||||
oldoff = r2_cmd("?vx $$").strip()
|
||||
if oldoff == "0x0" or oldoff == "":
|
||||
oldoff = "0x00000000"
|
||||
olivemode = ai.env["chat.live"]
|
||||
ai.env["chat.live"] = "true"
|
||||
while True:
|
||||
prompt = "[r2ai:" + oldoff + "]> "
|
||||
if os.name != "nt":
|
||||
prompt = f"\001\x1b[33m\002{prompt}" # \001\x1b[0m\002"
|
||||
if r2 is not None:
|
||||
off = r2_cmd("?vx $$").strip()
|
||||
if off == "":
|
||||
off = r2_cmd("?vx $$").strip()
|
||||
if len(off) > 5 and len(off) < 20:
|
||||
oldoff = off
|
||||
if ai.active_block is not None:
|
||||
# r2ai.active_block.update_from_message("")
|
||||
ai.end_active_block()
|
||||
try:
|
||||
usertext = input(prompt).strip()
|
||||
if os.name != "nt":
|
||||
builtins.print("\001\x1b[0m\002", end="")
|
||||
except EOFError:
|
||||
break
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
break
|
||||
try:
|
||||
if ai.env["chat.bubble"] == "true":
|
||||
if usertext.startswith("-"):
|
||||
if runline(ai, usertext) == "q":
|
||||
break
|
||||
else:
|
||||
bubble.query(usertext)
|
||||
if ai.env["chat.picolecroco"] == "true":
|
||||
bubble.pico_begin()
|
||||
else:
|
||||
bubble.response_begin()
|
||||
if runline(ai, usertext) == "q":
|
||||
break
|
||||
bubble.response_end()
|
||||
else:
|
||||
if runline(ai, usertext) == "q":
|
||||
break
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
continue
|
||||
tab_write()
|
||||
ai.env["chat.live"] = olivemode
|
||||
@@ -0,0 +1,42 @@
|
||||
import itertools
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
|
||||
class Spinner:
|
||||
def __init__(self, message="Loading...", delay=0.1):
|
||||
self.spinner = itertools.cycle([
|
||||
"\033[1;31m⠋\033[0m", "\033[1;32m⠙\033[0m", "\033[1;33m⠹\033[0m", "\033[1;34m⠸\033[0m",
|
||||
"\033[1;35m⠼\033[0m", "\033[1;36m⠴\033[0m", "\033[1;37m⠦\033[0m", "\033[1;31m⠧\033[0m",
|
||||
"\033[1;32m⠇\033[0m", "\033[1;33m⠏\033[0m"
|
||||
])
|
||||
self.message = message
|
||||
self.delay = delay
|
||||
self.running = False
|
||||
self.thread = None
|
||||
self.start_time = None
|
||||
|
||||
def start(self):
|
||||
"""Start the spinner in a separate thread."""
|
||||
self.running = True
|
||||
self.start_time = time.time()
|
||||
self.thread = threading.Thread(target=self._spin)
|
||||
self.thread.start()
|
||||
|
||||
def _spin(self):
|
||||
"""Spin the spinner while running is True."""
|
||||
while self.running:
|
||||
elapsed_time = time.time() - self.start_time
|
||||
sys.stdout.write(f"\r{self.message} {next(self.spinner)} {elapsed_time:.1f}s")
|
||||
sys.stdout.flush()
|
||||
time.sleep(self.delay)
|
||||
sys.stdout.write('\r' + ' ' * (len(self.message) + 20) + '\r') # Clear the line
|
||||
sys.stdout.flush()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the spinner."""
|
||||
self.running = False
|
||||
if self.thread is not None:
|
||||
self.thread.join()
|
||||
|
||||
spinner = Spinner("")
|
||||
@@ -0,0 +1,153 @@
|
||||
import os
|
||||
import sys
|
||||
from .const import R2AI_HISTFILE, R2AI_HOMEDIR, R2AI_RCFILE, R2AI_USERDIR
|
||||
from .models import models
|
||||
|
||||
mmm = []
|
||||
for m in sorted(models().split("\n")):
|
||||
if m.startswith("-m "):
|
||||
mmm.append(m[3:])
|
||||
eee = []
|
||||
hhh = [
|
||||
"R2AI_USERDIR",
|
||||
"R2AI_PLUGDIR",
|
||||
"R2AI_HOMEDIR",
|
||||
"R2AI_RCFILE",
|
||||
"R2AI_HISTFILE",
|
||||
]
|
||||
|
||||
def autocomplete_files(flag, second_word):
|
||||
cwd = second_word[:second_word.rfind('/') + 1]
|
||||
if cwd == "": cwd = "./"
|
||||
files = [cwd + c + "/" for c in os.listdir(cwd)]
|
||||
return [flag + " " + s for s in files if s and s.startswith(second_word)]
|
||||
|
||||
class MyCompleter(object): # Custom completer
|
||||
def __init__(self, options):
|
||||
self.options = sorted(options)
|
||||
|
||||
def complete(self, text, state):
|
||||
line = readline.get_line_buffer()
|
||||
splits = line.split()
|
||||
first_word = splits[0].strip() if len(line) > 0 else ""
|
||||
second_word = splits[1].strip() if len(splits) > 1 else ""
|
||||
if state == 0:
|
||||
if not text:
|
||||
self.matches = self.options[:]
|
||||
elif first_word == ".":
|
||||
self.matches = autocomplete_files(".", second_word)
|
||||
elif first_word == "-e":
|
||||
if second_word == "":
|
||||
self.matches = ["-e " + c + ' ' for c in eee]
|
||||
else:
|
||||
self.matches = ["-e " + s for s in eee if s and s.startswith(second_word)]
|
||||
elif first_word == "-rf":
|
||||
self.matches = autocomplete_files("-rf", second_word)
|
||||
elif first_word == "-i":
|
||||
self.matches = autocomplete_files("-i", second_word)
|
||||
elif first_word == "-f":
|
||||
self.matches = autocomplete_files("-f", second_word)
|
||||
elif first_word == "-H":
|
||||
if second_word == "":
|
||||
self.matches = ["-H " + c + ' ' for c in hhh]
|
||||
else:
|
||||
self.matches = ["-H " + s for s in hhh if s and s.startswith(second_word)]
|
||||
elif first_word == "-m":
|
||||
if second_word.startswith("/") or second_word.startswith("."):
|
||||
self.matches = autocomplete_files("-m", second_word)
|
||||
elif second_word == "":
|
||||
self.matches = ["-m " + c + ' ' for c in mmm]
|
||||
else:
|
||||
self.matches = ["-m " + s for s in mmm if s and s.startswith(second_word)]
|
||||
else:
|
||||
self.matches = [s for s in self.options if s and s.startswith(text)]
|
||||
try:
|
||||
return self.matches[state]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def display_matches(self, substitution, matches, longest_match_length):
|
||||
line_buffer = readline.get_line_buffer()
|
||||
columns = os.environ.get("COLUMNS", 80)
|
||||
print()
|
||||
tpl = "{:<" + str(int(max(map(len, matches)) * 1.2)) + "}"
|
||||
buffer = ""
|
||||
for match in matches:
|
||||
match = tpl.format(match[len(substitution):])
|
||||
if len(buffer + match) > columns:
|
||||
print(buffer)
|
||||
buffer = ""
|
||||
buffer += match
|
||||
if buffer:
|
||||
print(buffer)
|
||||
print("> ", end="")
|
||||
print(line_buffer, end="")
|
||||
sys.stdout.flush()
|
||||
|
||||
commands = []
|
||||
commands.extend(sorted([
|
||||
"?", ".", "..", ":", "' ", "!",
|
||||
"-a", "-A", "-k", "-c", "-e", "-f", "-h", "-H",
|
||||
"-i", "-m", "-M", "-MM", "-n", "-q", "-L",
|
||||
"-r", "-r2", "-rf", "-repl",
|
||||
"-R", "-t", "-v", "-w", "q"
|
||||
]))
|
||||
|
||||
commands = [x.split(' ')[0] for x in commands]
|
||||
|
||||
have_readline = False
|
||||
try:
|
||||
import readline
|
||||
import rlcompleter
|
||||
have_readline = True
|
||||
except Exception:
|
||||
have_readline = True
|
||||
pass # readline not available
|
||||
|
||||
def tab_hist():
|
||||
if not have_readline:
|
||||
print("Cannot find readline", file=sys.stderr)
|
||||
return False
|
||||
|
||||
def tab_evals(x):
|
||||
global eee
|
||||
eee = sorted(x)
|
||||
|
||||
def tab_write():
|
||||
if not have_readline:
|
||||
print("Cannot find readline", file=sys.stderr)
|
||||
return False
|
||||
readline.write_history_file(R2AI_HISTFILE)
|
||||
|
||||
def tab_list():
|
||||
global readline
|
||||
if not have_readline:
|
||||
return []
|
||||
amount = readline.get_current_history_length()
|
||||
res = []
|
||||
for i in range(1, amount):
|
||||
item = readline.get_history_item(i)
|
||||
res.append(f"{i} {item}")
|
||||
return res
|
||||
|
||||
def tab_init():
|
||||
if not have_readline:
|
||||
print("Cannot find readline", file=sys.stderr)
|
||||
return False
|
||||
completer = MyCompleter(list(set(commands)))
|
||||
try:
|
||||
readline.read_history_file(R2AI_HISTFILE)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
readline.set_completer(completer.complete)
|
||||
readline.set_completer_delims('\t\n;')
|
||||
readline.set_completion_display_matches_hook(completer.display_matches)
|
||||
if readline.__doc__.find("GNU") != -1:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
else:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
except Exception as e:
|
||||
pass
|
||||
@@ -0,0 +1,37 @@
|
||||
import builtins
|
||||
from .tools import run_python, execute_binary, r2cmd
|
||||
import subprocess
|
||||
from .pipe import get_filename
|
||||
import time
|
||||
py_code = """
|
||||
print('hello test')
|
||||
"""
|
||||
|
||||
def run_test(args):
|
||||
if not args or len(args) == 0:
|
||||
res = run_python(py_code).strip()
|
||||
print(f"run_python: {res}", len(res))
|
||||
assert res == "hello test"
|
||||
print("run_python: test passed")
|
||||
r2cmd("o--;o /bin/ls")
|
||||
res = execute_binary(args=["-d", "/etc"]).strip()
|
||||
subp = subprocess.run(["/bin/ls", "-d", "/etc"], capture_output=True, text=True)
|
||||
print("exec result", res)
|
||||
print("subp result", subp.stdout)
|
||||
assert ''.join(res).strip() == subp.stdout.strip()
|
||||
print("execute_binary with args: test passed")
|
||||
else:
|
||||
cmd, *args = args.split(" ", 1)
|
||||
if cmd == "get_filename":
|
||||
builtins.print(get_filename())
|
||||
elif cmd == "run_python":
|
||||
builtins.print(f"--- args ---")
|
||||
builtins.print(args)
|
||||
builtins.print(f"--- end args ---")
|
||||
builtins.print(f"--- result ---")
|
||||
builtins.print(run_python(args[0]))
|
||||
builtins.print(f"--- end result ---")
|
||||
elif cmd == "r2cmd":
|
||||
builtins.print(f"--- {args} ---")
|
||||
builtins.print(r2cmd(args))
|
||||
builtins.print("--- end ---")
|
||||
@@ -0,0 +1,176 @@
|
||||
from r2ai.pipe import get_r2_inst
|
||||
import json
|
||||
import builtins
|
||||
import base64
|
||||
from .pipe import get_filename
|
||||
from . import LOGGER
|
||||
import time
|
||||
import sys
|
||||
from io import StringIO
|
||||
import subprocess
|
||||
import os
|
||||
is_plugin = False
|
||||
try:
|
||||
import r2lang
|
||||
is_plugin = True
|
||||
except Exception:
|
||||
is_plugin = False
|
||||
pass
|
||||
|
||||
def r2cmd(command: str):
|
||||
"""
|
||||
Run a r2 command and return the output
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: str
|
||||
The r2 command to run
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The output of the r2 command
|
||||
"""
|
||||
r2 = get_r2_inst()
|
||||
r2.cmd('e scr.color=3')
|
||||
if command.startswith('r2 '):
|
||||
return "You are already in r2!"
|
||||
cmd = '{"cmd":' + json.dumps(command) + '}'
|
||||
res = r2.cmd(cmd)
|
||||
|
||||
try:
|
||||
res = json.loads(res)
|
||||
if 'error' in res and res['error'] is True:
|
||||
error_message = res['error']
|
||||
log_messages = '\n'.join(log['message'] for log in res.get('logs', []))
|
||||
# return { 'type': 'error', 'output': log_messages }
|
||||
return log_messages
|
||||
|
||||
return res['res']
|
||||
except json.JSONDecodeError:
|
||||
if type(res) == str:
|
||||
spl = res.strip().split('\n')
|
||||
if spl[-1].startswith('{"res":""'):
|
||||
res = '\n'.join(spl[:-1])
|
||||
return res
|
||||
except Exception as e:
|
||||
# return { 'type': 'error', 'output': f"Error running r2cmd: {e}\nCommand: {command}\nResponse: {res}" }
|
||||
return f"Error running r2cmd: {e}\nCommand: {command}\nResponse: {res}"
|
||||
|
||||
def run_python(command: str):
|
||||
"""
|
||||
Run a python script and return the output
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: str
|
||||
The python script to run
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The output of the python script
|
||||
"""
|
||||
r2 = get_r2_inst()
|
||||
res = ""
|
||||
is_plugin = False
|
||||
python_path = sys.executable
|
||||
try:
|
||||
proc = subprocess.run([python_path, '-c', command],
|
||||
capture_output=True,
|
||||
text=True)
|
||||
res = proc.stdout
|
||||
if proc.stderr:
|
||||
res += proc.stderr
|
||||
except Exception as e:
|
||||
res = str(e)
|
||||
|
||||
# if is_plugin:
|
||||
# base64cmd = base64.b64encode(command.encode('utf-8')).decode('utf-8')
|
||||
# res += r2cmd(f'#!python -e base64:{base64cmd} > .r2ai_tmp.log')
|
||||
# res += r2cmd('cat .r2ai_tmp.log')
|
||||
# r2cmd('rm .r2ai_tmp.log')
|
||||
# else:
|
||||
# with open('r2ai_tmp.py', 'w') as f:
|
||||
# f.write(command)
|
||||
# r2 = get_r2_inst()
|
||||
# res += r2cmd('#!python r2ai_tmp.py > .r2ai_tmp.log')
|
||||
# time.sleep(0.1)
|
||||
# res += r2cmd('!cat .r2ai_tmp.log')
|
||||
# LOGGER.debug(f'run_python: {res}')
|
||||
# # r2cmd('rm r2ai_tmp.py')
|
||||
# # r2cmd('rm .r2ai_tmp.log')
|
||||
return res
|
||||
|
||||
|
||||
schemas = {
|
||||
"execute_binary": {
|
||||
"name": "execute_binary",
|
||||
"description": "Execute a binary with the given arguments and stdin",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"args": {
|
||||
"description": "The arguments to pass to the binary. Do not include the file name.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"stdin": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def execute_binary(args: list[str] = [], stdin: str = ""):
|
||||
filename = get_filename()
|
||||
if filename:
|
||||
if os.path.isabs(filename):
|
||||
abs_path = os.path.abspath(filename)
|
||||
if os.path.exists(abs_path):
|
||||
filename = abs_path
|
||||
else:
|
||||
cwd_path = os.path.join(os.getcwd(), filename)
|
||||
if os.path.exists(cwd_path):
|
||||
filename = cwd_path
|
||||
try:
|
||||
cmd = [filename] + args
|
||||
proc = subprocess.run(cmd, input=stdin, capture_output=True, text=True)
|
||||
res = proc.stdout
|
||||
if proc.stderr:
|
||||
res += proc.stderr
|
||||
return res
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return ""
|
||||
# r2 = get_r2_inst()
|
||||
# if stdin:
|
||||
# r2.cmd(f'dor stdin={json.dumps(stdin)}')
|
||||
# if len(args) > 0:
|
||||
# r2.cmd(f"ood {' '.join(args)}")
|
||||
# else:
|
||||
# r2.cmd("ood")
|
||||
# res = r2cmd("dc")
|
||||
# return res
|
||||
|
||||
|
||||
|
||||
def print_tool_call(msg):
|
||||
if msg['function']['name'] == 'r2cmd':
|
||||
builtins.print('\x1b[1;32m> \x1b[4m' + msg['function']['arguments']['command'] + '\x1b[0m')
|
||||
elif msg['function']['name'] == 'run_python':
|
||||
builtins.print('\x1b[1;32m> \x1b[4m' + "#!python" + '\x1b[0m')
|
||||
builtins.print(msg['function']['arguments']['command'])
|
||||
elif msg['function']['name'] == 'execute_binary':
|
||||
filename = get_filename() or 'bin'
|
||||
stdin = msg['function']['arguments']['stdin'] if 'stdin' in msg['function']['arguments'] else None
|
||||
args = msg['function']['arguments']['args'] if 'args' in msg['function']['arguments'] else []
|
||||
cmd = filename
|
||||
if args and len(args) > 0:
|
||||
cmd += ' ' + ' '.join(args)
|
||||
if stdin and len(stdin) > 0:
|
||||
cmd += f' stdin={stdin}'
|
||||
builtins.print('\x1b[1;32m> \x1b[4m' + cmd + '\x1b[0m')
|
||||
@@ -0,0 +1,350 @@
|
||||
from textual.app import App, ComposeResult, SystemCommand
|
||||
from textual.containers import ScrollableContainer, Container, Horizontal, VerticalScroll, Grid, Vertical # Add Vertical to imports
|
||||
from textual.widgets import Header, Footer, Input, Button, Static, DirectoryTree, Label, Tree, Markdown
|
||||
from textual.command import CommandPalette, Command, Provider, Hits, Hit
|
||||
from textual.screen import Screen, ModalScreen, SystemModalScreen
|
||||
from textual.message import Message
|
||||
from textual.reactive import reactive
|
||||
from .model_select import ModelSelect
|
||||
from r2ai.pipe import open_r2, get_filename, r2
|
||||
from r2ai.interpreter import is_litellm_model
|
||||
from typing import Iterable
|
||||
import os
|
||||
from pathlib import Path
|
||||
from textual import work
|
||||
from textual.widget import Widget
|
||||
from textual.css.query import NoMatches
|
||||
from textual import log
|
||||
from litellm import validate_environment
|
||||
from markdown_it import MarkdownIt
|
||||
# from ..repl import set_model, r2ai_singleton
|
||||
# ai = r2ai_singleton()
|
||||
from .chat import chat
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
ANSI_REGEX = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||
|
||||
|
||||
class ModelConfigDialog(SystemModalScreen):
|
||||
def __init__(self, keys: list[str]) -> None:
|
||||
super().__init__()
|
||||
self.keys = keys
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
for key in self.keys:
|
||||
yield Input(placeholder=key, id=f"{key}-input")
|
||||
yield Button("Save", variant="primary", id="save-button")
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "save-button":
|
||||
for key in self.keys:
|
||||
os.environ[key] = self.query_one(f"#{key}-input", Input).value
|
||||
self.dismiss()
|
||||
class ChatMessage(Widget):
|
||||
markdown: reactive[str] = reactive("")
|
||||
sender = "User"
|
||||
is_done = reactive(False, layout=True, recompose=True)
|
||||
|
||||
def __init__(self, id: str, sender: str, content: str, is_done: bool = False, **kwargs) -> None:
|
||||
super().__init__(id=id, classes='chat-message-container', **kwargs)
|
||||
# self.markdown = f"*{sender}*: {content}"
|
||||
self.markdown = content
|
||||
self.sender = sender
|
||||
self.is_done = is_done
|
||||
async def watch_markdown(self, markdown: str) -> None:
|
||||
try:
|
||||
mkd = self.query_exactly_one(".text1")
|
||||
text = self.markdown
|
||||
if hasattr(mkd, 'update') and callable(mkd.update):
|
||||
update_method = mkd.update
|
||||
if asyncio.iscoroutinefunction(update_method):
|
||||
await update_method(text)
|
||||
else:
|
||||
update_method(text)
|
||||
except NoMatches:
|
||||
pass
|
||||
|
||||
def add_text(self, markdown: str) -> None:
|
||||
self.markdown += markdown
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
if self.sender == "User":
|
||||
yield Static(f"[bold green]{self.sender}", classes='label_sender', markup=True)
|
||||
yield Static(self.markdown, classes='text1', markup=True)
|
||||
elif self.sender == 'AI':
|
||||
yield Static(f"[bold magenta]{self.sender}", markup=True)
|
||||
if self.is_done:
|
||||
yield Markdown(self.markdown, classes='text1', parser_factory=lambda: MarkdownIt('gfm-like'))
|
||||
else:
|
||||
yield Static(self.markdown, classes='text1', markup=True)
|
||||
elif self.sender == 'Tool Call':
|
||||
yield Static(f"[bold blue]{self.sender}", markup=True)
|
||||
yield Static(self.markdown, classes='text1', markup=True)
|
||||
elif self.sender == 'Tool Response':
|
||||
yield Static(f"[bold blue]{self.sender}", markup=True)
|
||||
yield VerticalScroll(Static(self.markdown, classes='text1', markup=True), classes='tool_response_container')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class R2AIApp(App):
|
||||
CSS_PATH = "app.tcss"
|
||||
BINDINGS = [
|
||||
("ctrl+p", "show_command_palette", "Command Palette"),
|
||||
("ctrl+m", "select_model", "Select Model"),
|
||||
("ctrl+o", "load_binary", "Load Binary")
|
||||
]
|
||||
TITLE = "r2ai"
|
||||
SUB_TITLE = None
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__()
|
||||
if 'ai' in kwargs:
|
||||
self.ai = kwargs['ai']
|
||||
else:
|
||||
class FakeAI:
|
||||
model = 'gpt-4o'
|
||||
messages = []
|
||||
self.ai = FakeAI()
|
||||
self.update_sub_title(get_filename())
|
||||
|
||||
def update_sub_title(self, binary: str = None) -> str:
|
||||
sub_title = None
|
||||
model = self.ai.model
|
||||
if binary and model:
|
||||
binary = Path(binary).name
|
||||
sub_title = f"{model} | {binary}"
|
||||
elif binary:
|
||||
sub_title = binary
|
||||
else:
|
||||
sub_title = model
|
||||
self.sub_title = sub_title
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Container(
|
||||
VerticalScroll(
|
||||
|
||||
id="chat-container",
|
||||
),
|
||||
ScrollableContainer(
|
||||
Horizontal(
|
||||
Input(placeholder="Type your message here...", id="chat-input"),
|
||||
Button("Send", variant="primary", id="send-button"),
|
||||
id="input-container",
|
||||
),
|
||||
id="input-area",
|
||||
),
|
||||
id="content",
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.install_screen(CommandPalette(), name="command_palette")
|
||||
self.query_one("#chat-input", Input).focus()
|
||||
# self.install_screen(BinarySelectDialog(), name="binary_select_dialog")
|
||||
|
||||
def action_show_command_palette(self) -> None:
|
||||
self.push_screen("command_palette")
|
||||
|
||||
|
||||
async def select_model(self) -> None:
|
||||
model = await self.push_screen_wait(ModelSelect())
|
||||
if model:
|
||||
await self.validate_model()
|
||||
self.ai.model = model
|
||||
self.notify(f"Selected model: {self.ai.model}")
|
||||
self.update_sub_title()
|
||||
|
||||
@work
|
||||
async def action_select_model(self) -> None:
|
||||
await self.select_model()
|
||||
|
||||
@work
|
||||
async def action_load_binary(self) -> None:
|
||||
binary = await self.push_screen_wait(BinarySelectDialog())
|
||||
if binary:
|
||||
self.notify(f"Selected binary: {binary}")
|
||||
self.update_sub_title(binary)
|
||||
|
||||
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
|
||||
yield from super().get_system_commands(screen)
|
||||
yield SystemCommand("Models", "Select Model", self.action_select_model)
|
||||
yield SystemCommand("Load Binary", "Load Binary", self.action_load_binary) # Add this command
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "send-button":
|
||||
self.send_message()
|
||||
|
||||
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
if event.input.id == "chat-input":
|
||||
await self.send_message()
|
||||
|
||||
def on_message(self, type: str, message: any) -> None:
|
||||
if type == 'message':
|
||||
existing = None
|
||||
try:
|
||||
existing = self.query_one(f"#{message['id']}")
|
||||
if message["content"] is not None:
|
||||
existing.add_text(message["content"])
|
||||
if message['done']:
|
||||
existing.is_done = True
|
||||
self.scroll_to_bottom()
|
||||
except NoMatches:
|
||||
existing = self.add_message(message["id"], "AI", message["content"])
|
||||
elif type == 'tool_call':
|
||||
if 'command' in message['function']['arguments']:
|
||||
self.add_message(message["id"], "Tool Call", f"{message['function']['name']} > {message['function']['arguments']['command']}")
|
||||
elif message['function']['name'] == 'execute_binary':
|
||||
args = message['function']['arguments']
|
||||
output = get_filename() or "bin"
|
||||
if 'args' in args and len(args['args']) > 0:
|
||||
output += f" {' '.join(args['args'])}\n"
|
||||
if 'stdin' in args and len(args['stdin']) > 0:
|
||||
output += f" stdin={args['stdin']}\n"
|
||||
|
||||
self.add_message(message["id"], "Tool Call", f"{message['function']['name']} > {output}")
|
||||
elif type == 'tool_response':
|
||||
self.add_message(message["id"], "Tool Response", ANSI_REGEX.sub('', message['content']))
|
||||
|
||||
async def send_message(self) -> None:
|
||||
input_widget = self.query_one("#chat-input", Input)
|
||||
message = input_widget.value.strip()
|
||||
if message:
|
||||
self.add_message(None, "User", message)
|
||||
input_widget.value = ""
|
||||
try:
|
||||
await self.validate_model()
|
||||
self.chat(message)
|
||||
except Exception as e:
|
||||
self.notify(str(e), severity="error")
|
||||
|
||||
@work(thread=True)
|
||||
async def chat(self, message: str) -> None:
|
||||
await chat(self.ai, message, lambda type, message: self.call_from_thread(self.on_message, type, message))
|
||||
|
||||
async def validate_model(self) -> None:
|
||||
model = self.ai.model
|
||||
if not model:
|
||||
await self.select_model()
|
||||
if is_litellm_model(model):
|
||||
model = self.ai.model.replace(':', '/')
|
||||
keys = validate_environment(model)
|
||||
if keys['keys_in_environment'] is False:
|
||||
await self.push_screen_wait(ModelConfigDialog(keys['missing_keys']))
|
||||
|
||||
return True
|
||||
|
||||
def add_message(self, id: str, sender: str, content: str) -> None:
|
||||
chat_container = self.query_one("#chat-container", VerticalScroll)
|
||||
try:
|
||||
msg = ChatMessage(id, sender, content)
|
||||
chat_container.mount(msg)
|
||||
return msg
|
||||
except Exception as e:
|
||||
pass
|
||||
finally:
|
||||
self.scroll_to_bottom()
|
||||
|
||||
|
||||
def scroll_to_bottom(self) -> None:
|
||||
chat_scroll = self.query_one("#chat-container", VerticalScroll)
|
||||
chat_scroll.scroll_end(animate=False)
|
||||
class Message(Widget):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__()
|
||||
self.content = f'[bold]{message.role}[/] {message.content}'
|
||||
|
||||
def render(self) -> str:
|
||||
return Markdown(self.content)
|
||||
|
||||
class Messages(Container):
|
||||
def __init__(self, messages) -> None:
|
||||
self.messages = messages
|
||||
def compose(self) -> ComposeResult:
|
||||
for message in self.messages:
|
||||
yield Message(message)
|
||||
|
||||
class FilteredDirectoryTree(DirectoryTree):
|
||||
input_path: reactive[Path] = reactive(Path.home())
|
||||
|
||||
def filter_paths(self, paths: Iterable[Path]) -> Iterable[Path]:
|
||||
ps = [path for path in paths if str(path).startswith(str(self.input_path))]
|
||||
return ps
|
||||
|
||||
class BinarySelectDialog(SystemModalScreen):
|
||||
BINDINGS = [
|
||||
("up", "cursor_up", "Move cursor up"),
|
||||
("down", "cursor_down", "Move cursor down"),
|
||||
("enter", "select_cursor", "Select item"),
|
||||
("escape", "app.pop_screen", "Close"),
|
||||
("backspace", "go_up", "Go up one level"), # Add this binding
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical():
|
||||
yield Input(placeholder="Enter path here...", id="path-input")
|
||||
yield FilteredDirectoryTree(Path.home(), id="file-browser")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.path_input = self.query_one("#path-input", Input)
|
||||
self.file_browser = self.query_one("#file-browser", DirectoryTree)
|
||||
self.path_input.value = str(get_filename() or Path.home())
|
||||
self.path_input.focus()
|
||||
self.watch(self.path_input, "value", self.update_tree)
|
||||
|
||||
@work(thread=True)
|
||||
def update_tree(self) -> None:
|
||||
path = Path(self.path_input.value)
|
||||
|
||||
if path.exists():
|
||||
self.file_browser.path = str(path)
|
||||
elif path.parent.exists():
|
||||
self.file_browser.path = str(path.parent)
|
||||
self.file_browser.input_path = str(path)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "up-button":
|
||||
self.go_up()
|
||||
|
||||
def action_go_up(self) -> None:
|
||||
current_path = Path(self.file_browser.path)
|
||||
parent_path = current_path.parent
|
||||
if parent_path != current_path:
|
||||
self.file_browser.path = str(parent_path)
|
||||
self.path_input.value = str(parent_path)
|
||||
|
||||
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected) -> None:
|
||||
self.path_input.value = str(event.path)
|
||||
self.open_and_analyze_binary(str(event.path))
|
||||
self.dismiss(str(event.path))
|
||||
|
||||
@work(thread=True)
|
||||
def open_and_analyze_binary(self, path: str) -> None:
|
||||
global r2
|
||||
r2 = open_r2(path)
|
||||
res = json.loads(r2.cmd('{ "cmd":"aaa" }'))
|
||||
if 'error' in res and res['error'] is True:
|
||||
self.notify(res['error'], severity="error")
|
||||
|
||||
def on_directory_tree_directory_selected(self, event: DirectoryTree.DirectorySelected) -> None:
|
||||
self.path_input.value = str(event.path)
|
||||
|
||||
def action_cursor_up(self) -> None:
|
||||
self.file_browser.action_cursor_up()
|
||||
|
||||
def action_cursor_down(self) -> None:
|
||||
self.file_browser.focus()
|
||||
self.file_browser.action_cursor_down()
|
||||
|
||||
def action_select(self) -> None:
|
||||
node = self.file_browser.cursor_node
|
||||
if hasattr(node.data, 'is_file') and node.data.is_file:
|
||||
self.open_and_analyze_binary(str(node.data.path))
|
||||
self.dismiss(str(node.data.path))
|
||||
|
||||
# app = R2AIApp()
|
||||
# app.run()
|
||||
@@ -0,0 +1,129 @@
|
||||
Placeholder {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#sidemenu {
|
||||
dock: left;
|
||||
width: 15;
|
||||
height: 100;
|
||||
}
|
||||
Screen {
|
||||
background: $surface;
|
||||
layers: base overlay;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 1fr;
|
||||
margin: 1 2;
|
||||
}
|
||||
|
||||
#chat-scroll {
|
||||
height: 1fr;
|
||||
border: solid $primary;
|
||||
background: $surface-darken-1;
|
||||
}
|
||||
|
||||
#chat-container {
|
||||
padding: 1;
|
||||
overflow-y: scroll;
|
||||
layout: vertical;
|
||||
layer: base;
|
||||
}
|
||||
|
||||
#input-area {
|
||||
layer: base;
|
||||
height: auto;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
#input-container {
|
||||
height: 3;
|
||||
background: $boost;
|
||||
border: solid $primary;
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
dock: left;
|
||||
width: 1fr;
|
||||
background: $boost;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#send-button {
|
||||
width: 10;
|
||||
margin-left: 1;
|
||||
}
|
||||
|
||||
/* ... existing styles ... */
|
||||
|
||||
|
||||
#binary-select-dialog Label {
|
||||
padding: 1 2;
|
||||
}
|
||||
|
||||
#binary-select-dialog #path-input {
|
||||
margin: 0 2 1 2;
|
||||
}
|
||||
|
||||
#binary-select-dialog #path-header {
|
||||
height: 3;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
|
||||
#binary-select-dialog #up-button {
|
||||
width: 3;
|
||||
min-width: 3;
|
||||
margin-left: 1;
|
||||
}
|
||||
|
||||
|
||||
.chat-message-container {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
MarkdownFence {
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
max-height: 9999;
|
||||
}
|
||||
Static.text1 {
|
||||
padding-left: 2;
|
||||
padding-bottom: 1;
|
||||
}
|
||||
Static.label_sender {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tool_response_container {
|
||||
max-height: 50;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
padding-bottom: 1;
|
||||
border: solid white;
|
||||
}
|
||||
|
||||
ModelSelect {
|
||||
display: none;
|
||||
align: center middle;
|
||||
Container {
|
||||
margin: 0;
|
||||
|
||||
width: 60%;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
OptionList {
|
||||
margin-bottom: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinarySelectDialog {
|
||||
display: none;
|
||||
align: center middle;
|
||||
Vertical {
|
||||
background: transparent;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
from litellm import acompletion, ChatCompletionAssistantToolCall, ChatCompletionToolCallFunctionChunk
|
||||
import asyncio
|
||||
import json
|
||||
import signal
|
||||
from r2ai.pipe import get_r2_inst
|
||||
from r2ai.tools import run_python, r2cmd, execute_binary
|
||||
from r2ai.repl import r2ai_singleton
|
||||
from r2ai.auto import ChatAuto, SYSTEM_PROMPT_AUTO
|
||||
from r2ai.interpreter import is_litellm_model
|
||||
from r2ai.models import new_get_hf_llm
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
async def chat(ai, message, cb):
|
||||
model = ai.model.replace(":", "/")
|
||||
tools = [r2cmd, run_python, execute_binary]
|
||||
ai.messages.append({"role": "user", "content": message})
|
||||
tool_choice = 'auto'
|
||||
if not is_litellm_model(model) and ai and not ai.llama_instance:
|
||||
ai.llama_instance = new_get_hf_llm(ai, model, int(ai.env["llm.window"]))
|
||||
|
||||
chat_auto = ChatAuto(model, interpreter=ai, system=SYSTEM_PROMPT_AUTO, tools=tools, messages=ai.messages, tool_choice=tool_choice, cb=cb)
|
||||
|
||||
return await chat_auto.achat()
|
||||
@@ -0,0 +1,86 @@
|
||||
from textual.app import ComposeResult
|
||||
from textual.widgets import Input, OptionList
|
||||
from textual.widget import Widget
|
||||
from textual.widgets.option_list import Option
|
||||
from textual.containers import Container
|
||||
from textual.message import Message
|
||||
from textual.binding import Binding
|
||||
from textual.screen import ModalScreen, SystemModalScreen
|
||||
from textual import log
|
||||
from r2ai.models import models
|
||||
# from ..repl import set_model, r2ai_singleton
|
||||
# ai = r2ai_singleton()
|
||||
|
||||
# MODELS = models().split("\n")
|
||||
from litellm import models_by_provider
|
||||
MODELS = []
|
||||
for model in models().split("\n"):
|
||||
if model.startswith("-m "):
|
||||
MODELS.append(model[3:])
|
||||
for provider in models_by_provider:
|
||||
for model in models_by_provider[provider]:
|
||||
MODELS.append(f"{provider}/{model}")
|
||||
class ModalInput(Input):
|
||||
BINDINGS = [
|
||||
Binding("down", "cursor_down", "Move down"),
|
||||
]
|
||||
|
||||
|
||||
class ModelSelect(SystemModalScreen):
|
||||
BINDINGS = [
|
||||
Binding("up", "cursor_up", "Move up"),
|
||||
Binding("down", "cursor_down", "Move down"),
|
||||
Binding("enter", "select", "Select model"),
|
||||
Binding("escape", "app.pop_screen", "Close"),
|
||||
]
|
||||
|
||||
class ModelSelected(Message):
|
||||
"""Event emitted when a model is selected."""
|
||||
def __init__(self, model: str) -> None:
|
||||
self.model = model
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
self.input = ModalInput(placeholder="Type to filter...")
|
||||
self.option_list = OptionList()
|
||||
with Container():
|
||||
yield self.input
|
||||
yield self.option_list
|
||||
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.options = []
|
||||
for t in MODELS:
|
||||
if t.startswith("-m "):
|
||||
self.options.append(Option(t[3:], id=t[3:]))
|
||||
elif len(t) > 0:
|
||||
self.options.append(Option(t, id=t))
|
||||
self.option_list.add_options(self.options)
|
||||
self.filtered_options = self.options.copy()
|
||||
self.input.focus()
|
||||
|
||||
def update_options(self, options):
|
||||
self.option_list.clear_options()
|
||||
self.option_list.add_options(options)
|
||||
self.filtered_options = options
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
filter_text = event.value
|
||||
filtered_options = [option for option in self.options if filter_text.lower() in option.id.lower()]
|
||||
self.update_options(filtered_options)
|
||||
|
||||
def action_cursor_up(self) -> None:
|
||||
self.option_list.action_cursor_up()
|
||||
|
||||
def action_cursor_down(self) -> None:
|
||||
if self.option_list.has_focus:
|
||||
self.option_list.action_cursor_down()
|
||||
else:
|
||||
self.option_list.focus()
|
||||
|
||||
def on_option_list_option_selected(self, index) -> None:
|
||||
selected_index = index.option_index
|
||||
if 0 <= selected_index < len(self.filtered_options):
|
||||
selected_option = self.filtered_options[selected_index]
|
||||
if not selected_option.disabled:
|
||||
self.dismiss(selected_option.id)
|
||||
@@ -0,0 +1,67 @@
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
# TODO: move into utils
|
||||
from datetime import datetime
|
||||
def get_timez():
|
||||
return datetime.utcnow().isoformat(timespec='microseconds') + 'Z'
|
||||
|
||||
def merge_deltas(original, delta):
|
||||
"""
|
||||
Pushes the delta into the original and returns that.
|
||||
|
||||
Great for reconstructing OpenAI streaming responses -> complete message objects.
|
||||
"""
|
||||
for key, value in delta.items():
|
||||
if isinstance(value, dict):
|
||||
if key not in original:
|
||||
original[key] = value
|
||||
else:
|
||||
merge_deltas(original[key], value)
|
||||
else:
|
||||
if key in original:
|
||||
original[key] += value
|
||||
else:
|
||||
original[key] = value
|
||||
return original
|
||||
|
||||
def slurp(f):
|
||||
with open(f, errors="ignore") as fd:
|
||||
return str(fd.read())
|
||||
|
||||
def dump(f, x):
|
||||
fd = open(f, "w")
|
||||
fd.write(x)
|
||||
fd.close()
|
||||
|
||||
def syscmdstr(cmd):
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
output, error = process.communicate()
|
||||
return output.decode().strip()
|
||||
|
||||
|
||||
def filter_print(*args, **kwargs):
|
||||
_args = []
|
||||
filter = None
|
||||
if "filter" in kwargs:
|
||||
filter = kwargs["filter"]
|
||||
del kwargs["filter"]
|
||||
for a in args:
|
||||
new = ""
|
||||
lines = str(a).splitlines()
|
||||
if len(lines) > 1:
|
||||
for line in lines:
|
||||
if filter is not None:
|
||||
if filter in line:
|
||||
new += line + "\n"
|
||||
else:
|
||||
new += line + "\n"
|
||||
else:
|
||||
if filter is not None:
|
||||
if filter in str(a):
|
||||
new += str(a)
|
||||
else:
|
||||
new += str(a)
|
||||
_args.append(new)
|
||||
|
||||
print(*_args, **kwargs)
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Helper functions to handle voice recognition and synthesis."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from .utils import syscmdstr
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
HAVE_WHISPER = False
|
||||
model = None
|
||||
voice_model = "large" # base
|
||||
DEVICE = None
|
||||
try:
|
||||
import whisper
|
||||
HAVE_WHISPER = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
have_festival = os.path.isfile("/usr/bin/festival")
|
||||
|
||||
def run(models):
|
||||
for model in models:
|
||||
cmd = f"ffmpeg -f avfoundation -list_devices true -i '' 2>&1 | grep '{model}'|cut -d '[' -f 3"
|
||||
output = syscmdstr(cmd)
|
||||
if output != "":
|
||||
return ":" + output[0]
|
||||
return None
|
||||
|
||||
def get_microphone(lang):
|
||||
global DEVICE
|
||||
print (f"DE {DEVICE}")
|
||||
if DEVICE is not None:
|
||||
return DEVICE
|
||||
tts("(r2ai)", "un moment", lang)
|
||||
DEVICE = run(["AirPods", "MacBook Pro"])
|
||||
print(f"DEVICE: {DEVICE}")
|
||||
return DEVICE
|
||||
|
||||
def stt(seconds, lang):
|
||||
global model
|
||||
global DEVICE
|
||||
global voice_model
|
||||
if lang == "":
|
||||
lang = None
|
||||
if model == None:
|
||||
model = whisper.load_model(voice_model)
|
||||
device = get_microphone(lang)
|
||||
if device is None:
|
||||
tts("(r2ai)", "cannot find a microphone", lang)
|
||||
return
|
||||
tts("(r2ai) listening for 5s... ", "digues?", lang)
|
||||
print(f"DEVICE IS {device}")
|
||||
os.system("rm -f .audiomsg.wav")
|
||||
rc = os.system(f"ffmpeg -f avfoundation -t 5 -i '{device}' .audiomsg.wav > /dev/null 2>&1")
|
||||
if rc != 0:
|
||||
tts("(r2ai)", "cannot record from microphone. missing permissions in terminal?", lang)
|
||||
return
|
||||
result = None
|
||||
if lang is None:
|
||||
result = model.transcribe(".audiomsg.wav")
|
||||
else:
|
||||
result = model.transcribe(".audiomsg.wav", language=lang)
|
||||
os.system("rm -f .audiomsg.wav")
|
||||
tts("(r2ai)", "ok", lang)
|
||||
text = result["text"].strip()
|
||||
if text == "you":
|
||||
return ""
|
||||
# print(f"User: {text}")
|
||||
return text
|
||||
|
||||
def tts(author, text, lang):
|
||||
clean_text = re.sub(r'https?://\S+', '', text)
|
||||
clean_text = re.sub(r'http?://\S+', '', clean_text)
|
||||
print(f"{author}: {text}")
|
||||
if have_festival:
|
||||
festlang = "english"
|
||||
if lang == "ca":
|
||||
festlang = "catalan"
|
||||
elif lang == "es":
|
||||
festlang = "spanish"
|
||||
elif lang == "it":
|
||||
festlang = "italian"
|
||||
p = Popen(['festival', '--tts', '--language', festlang], stdin=PIPE)
|
||||
p.communicate(input=text)
|
||||
else:
|
||||
if lang == "es":
|
||||
VOICE = "Marisol"
|
||||
elif lang == "ca":
|
||||
VOICE = "Montse"
|
||||
else:
|
||||
VOICE = "Moira"
|
||||
subprocess.run(["say", "-v", VOICE, clean_text])
|
||||
@@ -0,0 +1,398 @@
|
||||
import platform
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from threading import Thread, Event
|
||||
from .utils import get_timez
|
||||
from . import LOGGER
|
||||
|
||||
ores = ""
|
||||
|
||||
kill_event = None
|
||||
server = None
|
||||
server_thread = None
|
||||
server_loading = False
|
||||
|
||||
# OpenAI API endpoint here
|
||||
def handle_v1_completions_default(self, ai, obj, runline2, method):
|
||||
if "prompt" in obj:
|
||||
res = runline2(ai, obj["prompt"])
|
||||
resobj = {}
|
||||
resobj["choices"] = [{
|
||||
"text": res
|
||||
}]
|
||||
resjson = json.dumps(resobj)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'{resobj}', 'utf-8'))
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return True
|
||||
|
||||
# {"messages": [ {"role": "user", "content": "Explain the following code:n'''ndata = {n "messages": [n {n "role": "user",n "content": "Explain code:n" + c_code,n "stream": "true",n "max_tokens": 7100,n "temperature": 0.2n }n ],n}n'''"}],x0dx0a"model":"", "stream": "true", "max_tokens": 7100, "temperature": 0.2# }
|
||||
def handle_v1_chat_completions(self, ai, obj, runline2, method):
|
||||
print("/v1/chat/completions")
|
||||
if obj == None:
|
||||
print("ObjNone")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
if "messages" not in obj:
|
||||
return handle_v1_completions_default(self, ai, obj, runline2, method)
|
||||
user = []
|
||||
system = []
|
||||
for m in obj["messages"]:
|
||||
if m["role"] == "system":
|
||||
system.append(m["content"])
|
||||
else:
|
||||
user.append(m["content"])
|
||||
rolequery = "\n".join(system)
|
||||
codequery = "\n".join(user)
|
||||
WANTCTX = ai.env["http.chatctx"] == "true"
|
||||
if not WANTCTX:
|
||||
runline2(ai, "-R")
|
||||
runline2(ai, "-r " + rolequery)
|
||||
response = json.loads('''{
|
||||
"id": "r2ai",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 1,
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "null",
|
||||
"delta": {
|
||||
"role": "assistant",
|
||||
"content": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}''')
|
||||
response["choices"][0]["delta"]["content"] = ""
|
||||
jresponse = json.dumps(response)
|
||||
#self.wfile.write(bytes(f'data: {jresponse}','utf-8'))
|
||||
ores = runline2(ai, codequery).strip()
|
||||
print("============")
|
||||
print(ores)
|
||||
print("============")
|
||||
response["choices"][0]["delta"]["content"] = ores
|
||||
response["choices"][0]["finish_reason"] = "length"
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
jresponse = json.dumps(response)
|
||||
# print(jresponse)
|
||||
self.wfile.write(bytes(f'data: {jresponse}','utf-8'))
|
||||
print("computed")
|
||||
|
||||
def handle_api_show(self, ai, obj, runline2, method):
|
||||
print("/api/show")
|
||||
# receive {"prompt": ""}
|
||||
if obj == None or "name" not in obj:
|
||||
print("ObjNone")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
mname = obj["name"]
|
||||
response = {
|
||||
"modelfile": "r2ai:latest",
|
||||
"parameters": "num_ctx 4096\nstop \u003c/s\u003e\nstop USER:\nstop ASSISTANT:",
|
||||
"template": "{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: ",
|
||||
"details": {
|
||||
"format": "gguf",
|
||||
"family": "llama",
|
||||
"families": ["llama", "clip"],
|
||||
"parameter_size": "7B",
|
||||
"quantization_level": "Q4_0"
|
||||
}
|
||||
}
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
jresponse = json.dumps(response)
|
||||
self.wfile.write(bytes(f'{jresponse}','utf-8'))
|
||||
|
||||
def handle_cmd(self, ai, obj, runline2, method):
|
||||
# receive {"prompt": ""}
|
||||
userline = self.path[5:].replace("%20", " ");
|
||||
print("RUNLINE: " + userline)
|
||||
ores = runline2(ai, userline)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'{ores}','utf-8'))
|
||||
return True
|
||||
|
||||
def handle_v1_chat(self, ai, obj, runline2, method):
|
||||
print("/api/chat")
|
||||
# receive {"prompt": ""}
|
||||
if obj == None or "messages" not in obj:
|
||||
print("ObjNone")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
messages = obj["messages"]
|
||||
codequery = ""
|
||||
for m in messages:
|
||||
if m["role"] == "user":
|
||||
codequery = m["content"] + "\n"
|
||||
else:
|
||||
codequery = m["role"] + ": " + m["content"]
|
||||
ores = runline2(ai, codequery.strip()).strip()
|
||||
response = {
|
||||
"model": "r2ai:latest",
|
||||
"created_at": get_timez(),
|
||||
"response": ores,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": ores,
|
||||
"images": None
|
||||
},
|
||||
"done": True,
|
||||
}
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
jresponse = json.dumps(response)
|
||||
self.wfile.write(bytes(f'{jresponse}','utf-8'))
|
||||
|
||||
# like completions but in realtime
|
||||
def handle_v1_chat_generate(self, ai, obj, runline2, method):
|
||||
print("/api/generate")
|
||||
if obj == None or "prompt" not in obj:
|
||||
print("ObjNone")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
codequery = obj["prompt"]
|
||||
#runline2(ai, "-R")
|
||||
ores = runline2(ai, codequery).strip()
|
||||
response = {
|
||||
"model": "r2ai:latest",
|
||||
"created_at": get_timez(),
|
||||
"response": ores,
|
||||
"done": True,
|
||||
"done_reason": "stop"
|
||||
}
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
jresponse = json.dumps(response)
|
||||
self.wfile.write(bytes(f'{jresponse}','utf-8'))
|
||||
|
||||
def handle_v1_completions(self, ai, obj, runline2, method):
|
||||
global ores
|
||||
print("/v1/completions")
|
||||
if obj == None:
|
||||
print("ObjNone")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
if "segments" not in obj:
|
||||
return handle_v1_completions_default(self, ai, obj, runline2, method)
|
||||
pfx = obj["segments"]["prefix"].strip()
|
||||
sfx = obj["segments"]["suffix"].strip()
|
||||
lng = obj["language"]
|
||||
if pfx == "":
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
return True
|
||||
#runline2(ai, "-R")
|
||||
#codequery = f"What's between `{pfx}` and `{sfx}` in `{lng}`, without including the context"
|
||||
codequery = f"Complete the code between `{pfx}` and `{sfx}` in `{lng}`"
|
||||
response = json.loads('''{
|
||||
"id": "cmpl-9d8aab26-ddc1-4314-a937-6654f2c13932",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"text": ""
|
||||
}
|
||||
]
|
||||
}''')
|
||||
print(f"PREFIX {pfx}")
|
||||
print(f"SUFFIX {sfx}")
|
||||
print(f"RES {ores}")
|
||||
response["choices"][0]["text"] = ores
|
||||
jresponse = json.dumps(response)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'{jresponse}','utf-8'))
|
||||
print("compute query")
|
||||
ores = runline2(ai, codequery).strip()
|
||||
ores = ores.replace(pfx, "")
|
||||
ores = ores.replace(sfx, "")
|
||||
ores = re.sub(r'```.*$', '', ores)
|
||||
ores = ores.replace("```javascript", "")
|
||||
ores = ores.replace("```", "")
|
||||
ores = ores.replace("\n", "");
|
||||
ores = ores.strip()
|
||||
print(f"RES2 {ores}")
|
||||
print("computed")
|
||||
|
||||
def handle_tabby_query(self, ai, obj, runline2, method):
|
||||
global ores
|
||||
print(self.path)
|
||||
if self.path == '/api/generate':
|
||||
return handle_v1_chat_generate(self, ai, obj, runline2, method)
|
||||
if self.path == '/api/chat':
|
||||
return handle_v1_chat(self, ai, obj, runline2, method)
|
||||
if self.path == '/api/show':
|
||||
return handle_api_show(self, ai, obj, runline2, method)
|
||||
if self.path == '/api/tags':
|
||||
models = {
|
||||
"models":[
|
||||
{
|
||||
"name": "r2ai:latest",
|
||||
"model": "r2ai:latest",
|
||||
"modified_at":get_timez(),
|
||||
"size":4661224676,
|
||||
"digest":"365c0bd3c000a25d28ddbf732fe1c6add414de7275464c4e4d1c3b5fcb5d8ad1",
|
||||
"details":{"parent_model":"","format":"gguf","family":"llama","families":["llama"],"parameter_size":"8.0B","quantization_level":"Q4_0"},
|
||||
"expires_at":"0001-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
models=json.dumps(models)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'{models}','utf-8'))
|
||||
return True
|
||||
if self.path == "/v1/chat/completions":
|
||||
return handle_v1_chat_completions(self, ai, obj, runline2, method)
|
||||
if self.path == "/v1/health": ## GET only
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
# TODO build proper health json instead of copypasting a stolen one
|
||||
model = ai.env["llm.model"]
|
||||
healthobj = {
|
||||
"model":ai.env["llm.model"],
|
||||
"device":"gpu" if ai.env["llm.gpu"] else "cpu",
|
||||
"arch": platform.machine(),
|
||||
"cpu_info": "",
|
||||
"cpu_count": 1,
|
||||
"cuda_devices": [],
|
||||
"version": {
|
||||
"build_date": "2024-05-22",
|
||||
"build_timestamp": "2024-05-22",
|
||||
"git_sha": "",
|
||||
"git_describe": "",
|
||||
},
|
||||
}
|
||||
healthstr=json.dumps(healthobj)
|
||||
self.wfile.write(bytes(f'{healthstr}','utf-8'))
|
||||
return True
|
||||
if ai.env["http.tabby"] == "true":
|
||||
# /v1/completions
|
||||
if self.path == "/v1/completions":
|
||||
return handle_v1_completions(self, ai, obj, runline2, method)
|
||||
|
||||
print(f"UnkPath: {self.path}")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes('{}\n','utf-8'))
|
||||
return True
|
||||
|
||||
def handle_custom_request(self, ai, msg, runline2, method):
|
||||
print("CUSTOM")
|
||||
if method == "GET":
|
||||
if self.path.startswith('/cmd'):
|
||||
return handle_cmd(self, ai, None, runline2, method)
|
||||
if handle_tabby_query(self, ai, None, runline2, method):
|
||||
return True
|
||||
return False
|
||||
if msg.startswith("{"):
|
||||
obj = json.loads(msg)
|
||||
#if "language" in obj:
|
||||
handle_tabby_query(self, ai, obj, runline2, method)
|
||||
return True
|
||||
return True
|
||||
|
||||
def start_http_server_now(ai, runline2):
|
||||
global server_loading
|
||||
import http.server
|
||||
import socketserver
|
||||
WANTCTX = ai.env["http.chatctx"] == "true"
|
||||
PORT = int(ai.env["http.port"])
|
||||
BASEPATH = ai.env["http.path"]
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
class SimpleHTTPRequestHandler(Handler):
|
||||
def do_GET(self):
|
||||
print("GET")
|
||||
if handle_custom_request(self, ai, "", runline2, "GET"):
|
||||
return
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'Invalid request. Use POST and /{BASEPATH}', 'utf-8'))
|
||||
def do_POST(self):
|
||||
print("POST")
|
||||
if not WANTCTX:
|
||||
runline2(ai, "-R")
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
msg = self.rfile.read(content_length).decode('utf-8')
|
||||
if handle_custom_request(self, ai, msg, runline2, "POST"):
|
||||
return
|
||||
if self.path.startswith(BASEPATH):
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
res = runline2(ai, msg)
|
||||
self.wfile.write(bytes(f'{res}','utf-8'))
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(f'Invalid request. Use {BASEPATH}'))
|
||||
LOGGER.getChild("server").info("[R2AI] Serving at port %s", PORT)
|
||||
Handler.protocol_version = "HTTP/1.0"
|
||||
global server
|
||||
if server is not None:
|
||||
server_loading = False
|
||||
print("Already running")
|
||||
return
|
||||
server = None
|
||||
try:
|
||||
server = socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler)
|
||||
server.allow_reuse_address = True
|
||||
server.allow_reuse_port = True
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
server_loading = False
|
||||
|
||||
def start_http_server(ai, runline2, background):
|
||||
global server_thread, server_loading
|
||||
server_loading = True
|
||||
server_thread = Thread(target=start_http_server_now, args=(ai, runline2))
|
||||
server_thread.start()
|
||||
while server_loading:
|
||||
time.sleep(0.1)
|
||||
global server
|
||||
if server is None:
|
||||
server_thread.join()
|
||||
return
|
||||
if not background:
|
||||
global kill_event
|
||||
kill_event = Event()
|
||||
while not kill_event.is_set():
|
||||
time.sleep(0.2)
|
||||
kill_event = None
|
||||
|
||||
def stop_http_server(force=False):
|
||||
if not force and server_in_background():
|
||||
return
|
||||
global server
|
||||
if server:
|
||||
LOGGER.getChild("server").info("Stopping server")
|
||||
_kill_server()
|
||||
server = None
|
||||
global server_thread
|
||||
if server_thread:
|
||||
server_thread.join()
|
||||
server_thread = None
|
||||
if not server_in_background():
|
||||
kill_event.set()
|
||||
|
||||
def server_running():
|
||||
global server_thread
|
||||
global server
|
||||
return bool(server) and bool(server_thread)
|
||||
|
||||
def server_in_background():
|
||||
global kill_event
|
||||
return server_running and not bool(kill_event)
|
||||
|
||||
def _kill_server():
|
||||
global server
|
||||
server.shutdown()
|
||||
Thread(target=server.server_close).start()
|
||||
@@ -0,0 +1,21 @@
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
R2_BINDIR=$(shell r2 -H R2_BINDIR)
|
||||
|
||||
install:
|
||||
# rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
# ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
mkdir -p $(R2_BINDIR)
|
||||
cp -f r2ai-server $(R2_BINDIR)/r2ai-server || echo Maybe use user-install instead?
|
||||
|
||||
uninstall:
|
||||
rm -f $(R2_BINDIR)/r2ai-server
|
||||
|
||||
user-install:
|
||||
mkdir -p $(R2PM_BINDIR)
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
ln -fs $(PWD)/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
|
||||
user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# r2ai-server
|
||||
|
||||
Start ollama, llamacpp, llamafile, r2ai, kobaldcpp and other language model webservers using a the same syntax for all of them to simplify its launching and setup.
|
||||
|
||||
* Use -l to list the implementations available
|
||||
|
||||
You can install more via r2pm
|
||||
|
||||
* Use -m to list or select the model. Those models can be absolute paths or model names when downloaded via r2ai, which uses the hugging face api
|
||||
|
||||
## Running r2ai-server
|
||||
|
||||
- Get usage: `r2pm -r r2ai-server`
|
||||
- List available servers: `r2pm -r r2ai-server -l`
|
||||
- List available models: `r2pm -r r2ai-server -m`
|
||||
|
||||
On Linux, models are stored in `~/.r2ai.models/`. File `~/.r2ai.model` lists the default model and other models.
|
||||
|
||||
**Example launching a local Mistral AI server:**
|
||||
|
||||
```
|
||||
$ r2pm -r r2ai-server -l r2ai -m mistral-7b-instruct-v0.2.Q2_K
|
||||
[12/13/24 10:35:22] INFO r2ai.server - INFO - [R2AI] Serving at port 8080 web.py:336
|
||||
```
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODEL=""
|
||||
PORT=8080
|
||||
MODELDIR=~/.r2ai.models
|
||||
LLAMA=llamafile
|
||||
[ -z "${R2AI}" ] && R2AI=r2ai
|
||||
|
||||
main() {
|
||||
if [ -z "${MODEL}" ]; then
|
||||
echo "Use -l and -m to select a model" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${MODEL}" | grep -e ^/
|
||||
if [ $? = 0 ]; then
|
||||
MODELPATH="${MODEL}"
|
||||
else
|
||||
MODELPATH="${MODELDIR}/${MODEL}.gguf"
|
||||
fi
|
||||
case ${LLAMA} in
|
||||
llamafile)
|
||||
llamafile --server --nobrowser -c 0 --port ${PORT} -m "${MODELPATH}"
|
||||
;;
|
||||
llamacpp)
|
||||
llama-server -c 0 --port "${PORT}" -m "${MODELPATH}"
|
||||
;;
|
||||
koboldcpp)
|
||||
koboldcpp -c 0 --port "${PORT}" -m "${MODELPATH}"
|
||||
;;
|
||||
r2ai)
|
||||
${R2AI} --port "${PORT}" -m "${MODEL}" -w
|
||||
;;
|
||||
*)
|
||||
echo "Invalid llama server selected."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
help() {
|
||||
cat <<EOF
|
||||
Usage: r2ai-server [-flags]
|
||||
-m list models
|
||||
-m [name] select given model
|
||||
-l list llama servers available
|
||||
-l [name] select llama software
|
||||
-h show this help message
|
||||
-p [port] select port
|
||||
EOF
|
||||
}
|
||||
|
||||
models() {
|
||||
cd "${MODELDIR}" || exit 1
|
||||
ls | sed -e 's,.gguf$,,'
|
||||
exit 0
|
||||
}
|
||||
|
||||
llamas() {
|
||||
# TODO check for those bins in path before suggesting them
|
||||
echo r2ai
|
||||
echo llamafile
|
||||
echo llamacpp
|
||||
echo koboldcpp
|
||||
}
|
||||
|
||||
while : ; do
|
||||
case "$1" in
|
||||
-l)
|
||||
if [ -z "$2" ]; then
|
||||
llamas
|
||||
break
|
||||
fi
|
||||
LLAMA="$2"
|
||||
shift
|
||||
;;
|
||||
-m)
|
||||
if [ -z "$2" ]; then
|
||||
models
|
||||
break
|
||||
fi
|
||||
MODEL="$2"
|
||||
shift
|
||||
;;
|
||||
-p)
|
||||
PORT="$2"
|
||||
shift
|
||||
;;
|
||||
-h)
|
||||
help
|
||||
;;
|
||||
"")
|
||||
main
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 pancake
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
recursive-include r2ai/ui *.tcss
|
||||
@@ -1,17 +1,4 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
|
||||
all:
|
||||
python3 main.py || $(MAKE) deps
|
||||
|
||||
deps:
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
install user-install:
|
||||
ln -fs $(PWD)/main.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
ln -fs $(PWD)/main.py $(R2PM_BINDIR)/r2ai
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
TARGETS=all clean mrproper user-install install user-uninstall uninstall
|
||||
$(TARGETS):
|
||||
$(MAKE) -C src $@
|
||||
.PHONY: $(TARGETS)
|
||||
@@ -1,103 +1,96 @@
|
||||
```
|
||||
,______ .______ .______ ,___
|
||||
: __ \ \____ |: \ : __|
|
||||
| \____|/ ____|| _,_ || : |
|
||||
| : \ \ . || : || |
|
||||
| |___\ \__:__||___| || |
|
||||
|___| : |___||___|
|
||||
* --pancake
|
||||
# R2AI - Augmented reversing with LLM for radare2
|
||||
|
||||
```console
|
||||
╭─────────────────────────────────╮
|
||||
│ ,______ .______ .______ ,___ │
|
||||
╭──╮ │ : __ \ \____ |: \ : __| │
|
||||
│ _│_ │ | \____|/ ____|| _,_ || : | │
|
||||
│ O O < | : \ \ . || : || | │
|
||||
│ │╷ │ | |___\ \__:__||___| || | │
|
||||
│ ││ │ |___| : |___||___| │
|
||||
│ ─╯│ ╰─────────────────────────────────╯
|
||||
╰───╯
|
||||
```
|
||||
|
||||
Run a language model in local, without internet, to entertain you or help answering questions about radare2 or reverse engineering in general. Note that models used by r2ai are pulled from external sources which may behave different or respond unrealible information. That's why there's an ongoing effort into improving the post-finetuning using memgpt-like techniques which can't get better without your help!
|
||||
[](https://github.com/radareorg/r2ai/actions/workflows/ci.yml)
|
||||
[](https://github.com/radareorg/radare2)
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/r2clippy.jpg">
|
||||
</p>
|
||||
## Components
|
||||
|
||||
This repository contains two plugins for radare2:
|
||||
|
||||
* **r2ai** - native plugin for radare2
|
||||
* **decai** - r2js plugin with special focus on decompilation
|
||||
|
||||
If you are looking to use radare2 with other agents via MCP:
|
||||
|
||||
* **r2mcp** - the [official radare2 mcp](https://github.com/radare2/radare2-mcp)
|
||||
* **r2copilot** - the mcp with focus on CTF [r2copilot](https://github.com/darallium/r2-copilot)
|
||||
|
||||
## Features
|
||||
|
||||
* Prompt the language model without internet requirements
|
||||
* Slurp file contents and make actions on that
|
||||
* Embed the output of an r2 command and ask the LLM to resolve questions
|
||||
* Define different system-level assistant role
|
||||
* Set environment variables to provide context to the language model
|
||||
* Configure different roles and customize prompts
|
||||
* Scriptable via r2pipe via the r2ai command
|
||||
* Live with repl and batch mode from cli or r2 prompt
|
||||
* Accessible as an r2lang-python plugin, keeps session state inside radare2
|
||||
* Scriptable from bash, r2pipe, and javascript (r2papi)
|
||||
* Use different models, dynamically adjust query template
|
||||
* Load multiple models and make them talk between them
|
||||
* Support Automatic (ReAct) mode to solve tasks using function calling
|
||||
* Use local and remote language models (ollama, openai, grok, anthropic, ..)
|
||||
* RAG markdown, code or textfiles using its native vector database
|
||||
* Embed the output of an r2 command and resolve questions on the given data
|
||||
|
||||
## Practical Examples
|
||||
|
||||
* Enhanced decompilation with `r2ai -d`
|
||||
* Autoname functions with `r2ai -n`
|
||||
* Explain function with `r2ai -x`
|
||||
* Find vulnerabilities with `r2ai -V`
|
||||
|
||||
## Installation
|
||||
|
||||
This is optional ans system dependant. but on recent Debian/Ubuntu systems the `pip` tool is no longer working, because it conflicts with the system packages. The best way to do this is with `venv`:
|
||||
The recommended way to install any of the r2ai components is via r2pm:
|
||||
|
||||
```bash
|
||||
python -m venv r2ai
|
||||
. r2ai/bin/activate
|
||||
```console
|
||||
$ r2pm -Uci r2ai
|
||||
$ r2pm -Uci decai
|
||||
```
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
r2pm -r r2ai
|
||||
## Using r2ai
|
||||
|
||||
- Adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai`
|
||||
- You can also run the wrapper in $PATH: `r2pm -r r2ai`
|
||||
|
||||
Drop your API keys in environment variables or files in your home:
|
||||
|
||||
```console
|
||||
$ cat ~/.r2ai.anthropic-key
|
||||
sk-ant-api03-CENSORED
|
||||
$ export OPENAI_API_KEY=sk-proj-6rlSPS-zN1v...
|
||||
```
|
||||
|
||||
Additionally you can get the `r2ai` command inside r2 to run as an rlang plugin by installing the bindings:
|
||||
| AI | API key |
|
||||
| --------- | -------------------------- |
|
||||
| OpenAI | `$HOME/.r2ai.openai-key` |
|
||||
| Gemini | `$HOME/.r2ai.gemini-key` |
|
||||
| Anthropic | `$HOME/.r2ai.anthropic-key` |
|
||||
| Mistral | `$HOME/.r2ai.mistral-key` |
|
||||
|
||||
```bash
|
||||
r2pm -i rlang-python
|
||||
make user-install
|
||||
|
||||
## Saving settings
|
||||
|
||||
You may customize and save your configuration settings using your OS's default settings file (e.g `~/.radare2rc` on Linux).
|
||||
For example, the following configuration sets Claude 3.7 by default, with max output tokens to 64000.
|
||||
|
||||
```
|
||||
r2ai -e api=anthropic
|
||||
r2ai -e model=claude-3-7-sonnet-20250219
|
||||
r2ai -e max_tokens=64000
|
||||
```
|
||||
|
||||
On native Windows follow these instructions (no need to install radare2 or use r2pm), note that you need Python 3.8 or higher:
|
||||
## Documentation
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python -m pip -r requirements.txt
|
||||
python -m pip install pyreadline3
|
||||
python main.py
|
||||
```
|
||||
* There's [a chapter](https://book.rada.re/plugins/r2ai.html) in the official r2book
|
||||
* Cryptax on [lmstudio+gptoss](https://cryptax.medium.com/r2ai-with-lmstudio-and-gpt-oss-08efa5ea2476) blog post
|
||||
* Malware analysis [with r2ai](https://github.com/cryptax/talks/blob/master/BSidesKristiansand-2025/r2ai.pdf) by Cryptax
|
||||
|
||||
## Usage
|
||||
## Videos
|
||||
|
||||
There are 4 different ways to run `r2ai`:
|
||||
|
||||
* Standalone and interactive: `r2pm -r r2ai`
|
||||
* Batch mode: `r2ai '-r act as a calculator' '3+3=?'`
|
||||
* From radare2 (requires `r2pm -ci rlang-python`): `r2 -c 'r2ai -h'`
|
||||
* Using r2pipe: `#!pipe python main.py`
|
||||
|
||||
## Examples
|
||||
|
||||
You can interact with r2ai from standalone python, from r2pipe via r2 keeping a global state or using the javascript intrepreter embedded inside `radare2`.
|
||||
|
||||
* [conversation.r2.js](examples/conversation.r2.js) - load two models and make them talk to each other
|
||||
|
||||
### Development/Testing
|
||||
|
||||
Just run `make` .. or well `python main.py /path/to/file`
|
||||
|
||||
It's also possible to install it with `conda`, which is the recommended way on Macs.
|
||||
|
||||
* https://developer.apple.com/metal/pytorch/
|
||||
|
||||
```sh
|
||||
curl -O https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh
|
||||
sh Miniconda3-latest-MacOSX-arm64.sh
|
||||
```
|
||||
|
||||
```sh
|
||||
conda install pytorch torchvision torchaudio -c pytorch-nightly
|
||||
conda run pip install inquirer rich appdirs huggingface_hub tokentrim llama-cpp-python
|
||||
```
|
||||
|
||||
### TODO
|
||||
|
||||
* add "undo" command to drop the last message
|
||||
* dump / restore conversational states
|
||||
* custom prompt templates
|
||||
|
||||
### Kudos
|
||||
|
||||
The original code of r2ai is based on OpenInterpreter. I want to thanks all the contributors to this project as they made it possible to build r2ai taking their code as source for this. Kudos to Killian and all the contributors.
|
||||
- https://infosec.exchange/@radareorg/111946255058894583
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
vim src/r2ai.c
|
||||
vim VERSION
|
||||
@@ -0,0 +1,30 @@
|
||||
PD=$(shell r2 -H R2_USER_PLUGINS)
|
||||
SPD=$(shell r2 -H R2_LIBR_PLUGINS)
|
||||
#BF=/bin/dd
|
||||
BF=a.out
|
||||
|
||||
all:
|
||||
@echo "Run: make user-install"
|
||||
|
||||
test:
|
||||
@echo 'You can now use 'decai' command'
|
||||
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
|
||||
|
||||
fmt indent:
|
||||
deno fmt
|
||||
|
||||
user-install:
|
||||
mkdir -p "$(PD)"
|
||||
cp decai.r2.js $(PD)/decai.r2.js
|
||||
|
||||
user-uninstall:
|
||||
rm -f $(PD)/decai.r2.js
|
||||
|
||||
install:
|
||||
make user-install PD=$(SPD)
|
||||
|
||||
uninstall:
|
||||
make user-uninstall PD=$(SPD)
|
||||
|
||||
vs open:
|
||||
open -a "Visual Studio Code" .
|
||||
@@ -0,0 +1,199 @@
|
||||
# Decai
|
||||
|
||||
The AI based Decompiler plugin for radare2
|
||||
|
||||
- Written in plain Javascript
|
||||
- No dependencies than radare2 and curl in PATH
|
||||
- Uses local ollama by default, but also r2ai-server
|
||||
- Use services like Anthropic, OpenAI, HF, XAI, DeepSeek, ..
|
||||
|
||||
Features
|
||||
|
||||
- Auto mode with function calling with ANY model
|
||||
- Uses the r2 pseudo decompiler by default, supports any other
|
||||
- Explain purpose and auto-document functions
|
||||
- Recursive decompilation to inline stubs
|
||||
- Autoname functions and perform type propagation
|
||||
- Find vulnerabilities, guide you and write exploits
|
||||
- Choose any natural language (not just English)
|
||||
- Choose output programming language (not just C)
|
||||
- Chain queries to manually fine tune the results
|
||||
- Customize decompilation prompt at any time
|
||||
|
||||
## Installation
|
||||
|
||||
Using r2pm: `r2pm -ci decai`
|
||||
|
||||
From source: `make user-install`
|
||||
|
||||
## Setup
|
||||
|
||||
```console
|
||||
[0x00000000]> decai
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
decai -e - display and change eval config vars
|
||||
decai -h - show this help
|
||||
decai -i [f] [q] - include given file and query
|
||||
decai -n - suggest better function name
|
||||
decai -q [text] - query language model with given text
|
||||
decai -Q [text] - query on top of the last output
|
||||
decai -r - change role prompt (same as: decai -e prompt)
|
||||
decai -R - reset role prompt to default prompt
|
||||
decai -s - function signature
|
||||
decai -v - show local variables
|
||||
decai -V - find vulnerabilities
|
||||
decai -x - eXplain current function
|
||||
```
|
||||
|
||||
Configuration options:
|
||||
|
||||
```console
|
||||
[0x00000000]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
|
||||
decai -e ctxfile=
|
||||
decai -e cmds=pdc
|
||||
decai -e cache=false
|
||||
decai -e lang=C
|
||||
decai -e hlang=English
|
||||
decai -e debug=false
|
||||
decai -e model=
|
||||
decai -e maxinputtokens=-1
|
||||
```
|
||||
|
||||
## Running decai
|
||||
|
||||
Decai is used from `r2` (e.g `r2 ./mybinary`). Get help with `decai -h`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -h
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -a [query] - solve query with auto mode
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
...
|
||||
```
|
||||
|
||||
List configuration variables with `decai -e`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
|
||||
decai -e ctxfile=
|
||||
...
|
||||
```
|
||||
|
||||
List possible APIs to discuss with AI: `decai -e api=?`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=?
|
||||
r2ai
|
||||
claude
|
||||
openapi
|
||||
...
|
||||
```
|
||||
|
||||
### Example using a local model and ollama
|
||||
|
||||
For example, if Ollama serves model codegeex4:latest (`ollama ls`), set decai
|
||||
API as `ollama` and model `codegeex4:latest`.
|
||||
|
||||
```
|
||||
[0x00002d30]> decai -e api=ollama
|
||||
[0x00002d30]> decai -e model=codegeex4:latest
|
||||
[0x00002d30]> decai -q Explain what forkpty does in 2 lines
|
||||
The `forkpty` function creates a new process with a pseudo-terminal, allowing the parent to interact with the child via standard input/output/err and controlling its terminal.
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See
|
||||
[https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
|
||||
|
||||
```c
|
||||
$ cat stack-overflow/bug.c
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char buf[32];
|
||||
strcpy (buf, argv[1]);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
$ r2 buffer-overflow/a.out
|
||||
[0x100003f58]> decai -d
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char buffer[32];
|
||||
int result = 0;
|
||||
|
||||
if (argc > 1 && argv[1] != NULL) {
|
||||
strcpy(buffer, argv[1]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Example using a local Mistral model and r2ai-server
|
||||
|
||||
For example, assuming we have a _local_ Mistral AI server running on port 8080
|
||||
with `r2ai-server`, we can decompile a given function with `decai -d`. The
|
||||
server shows it received the question:
|
||||
|
||||
```
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -R
|
||||
127.0.0.1 - - [13/Dec/2024 10:40:49] "GET /cmd/-R HTTP/1.1" 200 -
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -i /tmp/.pdc.txt Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change goto into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like string:. Transform this pseudocode into C
|
||||
```
|
||||
|
||||
### Example using a Mistral API key
|
||||
|
||||
Put the API key in `~/.r2ai.mistral-key`.
|
||||
|
||||
````
|
||||
[0x000010d0]> decai -e api=mistral
|
||||
[0x000010d0]> decai -d main
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char password[40];
|
||||
char input[40];
|
||||
...
|
||||
````
|
||||
|
||||
### Example with ChatGPT 4
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=openai
|
||||
[0x00406cac]> decai -d
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void daemonize() {
|
||||
daemon(1, 0);
|
||||
}
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
PACKAGE=decai
|
||||
DEPENDS=radare2
|
||||
SECTION=user/shell
|
||||
PRIORITY=optional
|
||||
MAINTAINER=pancake <[email protected]>
|
||||
VERSION=$(shell git tag | tail -n 1)
|
||||
# arch
|
||||
UNAMEM=$(shell uname -m)
|
||||
ifeq ($(UNAMEM),x86_64)
|
||||
ARCH=amd64
|
||||
else
|
||||
ARCH=arm64
|
||||
endif
|
||||
@@ -0,0 +1,2 @@
|
||||
AI based decompiler plugin for radare2
|
||||
r2js plugin for radare2 that provides several features for reverse engineers, from autonaming functions, improve decompiling outputs, find bugs or explain functions to automated crackme solving using local and remote models if API keys are provided..
|
||||
@@ -0,0 +1,34 @@
|
||||
include ./CONFIG
|
||||
|
||||
UNAME=$(shell uname)
|
||||
SUDO?=sudo
|
||||
DEPENDS=
|
||||
CROSSARCH=x64
|
||||
R2CFG_FLAGS?=
|
||||
PWD=$(shell pwd)
|
||||
PACKAGE_DIR?=${PWD}
|
||||
|
||||
R2_VERSION=$(shell r2 -qv)
|
||||
|
||||
DOCKCROSS=$(PWD)/../dockcross
|
||||
R2PLUGDIR=/usr/lib/radare2/$(R2_VERSION)
|
||||
|
||||
all: root
|
||||
$(SUDO) rm -rf control data
|
||||
$(MAKE) clean
|
||||
mkdir -p data
|
||||
cp -rf root/* data
|
||||
$(MAKE) control
|
||||
$(MAKE) deb
|
||||
|
||||
root:
|
||||
mkdir -p root/$(R2PLUGDIR)
|
||||
cp -f ../../decai.r2.js root/$(R2PLUGDIR)
|
||||
|
||||
purge: clean
|
||||
rm -rf root
|
||||
|
||||
summary:
|
||||
echo $(VERSION)
|
||||
|
||||
include deb.mk
|
||||
@@ -0,0 +1,130 @@
|
||||
# Create .deb without using dpkg tools.
|
||||
#
|
||||
# Author: Tim Wegener <[email protected]>
|
||||
#
|
||||
# Use 'include deb_hand.mak' after defining the user variables in a local
|
||||
# makefile.
|
||||
#
|
||||
# The 'data' rule must be customised in the local make file.
|
||||
# This rule should make a 'data' directory containing the full file
|
||||
# layout of the installed package.
|
||||
#
|
||||
# This makefile will create a debian-binary file a control directory and a
|
||||
# a build directory in the current directory.
|
||||
# Do 'make clobber' to remove these generated files.
|
||||
#
|
||||
# Destination:
|
||||
# PACKAGE_DIR - directory where package (and support files) will be built
|
||||
# defaults to the current directory
|
||||
#
|
||||
# Sources:
|
||||
# SOURCE_DIR - directory containing files to be packaged
|
||||
# ICON_SOURCE - 26x26 icon file for maemo
|
||||
# DESCR - description with summary on first line
|
||||
# preinst, postinst, prerm, postrm - optional control shell scripts
|
||||
|
||||
# These fields are used to build the control file:
|
||||
# PACKAGE =
|
||||
# VERSION =
|
||||
# ARCH =
|
||||
# SECTION =
|
||||
# PRIORITY =
|
||||
# MAINTAINER =
|
||||
# DEPENDS =
|
||||
#
|
||||
# SOURCE_DIR =
|
||||
# ICON_SOURCE =
|
||||
# (ICON_SOURCE is optional)
|
||||
|
||||
# *** NO USER CHANGES REQUIRED BEYOND THIS POINT ***
|
||||
ifeq ($(shell uname),Darwin)
|
||||
MD5SUM=md5
|
||||
else
|
||||
MD5SUM=md5sum
|
||||
endif
|
||||
|
||||
GAWK=awk
|
||||
PACKAGE_DIR=$(shell pwd)
|
||||
CONTROL_EXTRAS ?= ${wildcard preinst postinst prerm postrm}
|
||||
|
||||
${PACKAGE_DIR}/control: ${PACKAGE_DIR}/data ${CONTROL_EXTRAS} DESCR \
|
||||
${ICON_SOURCE}
|
||||
#rm -rf $@
|
||||
mkdir -p $@
|
||||
ifneq (${CONTROL_EXTRAS},)
|
||||
cp ${CONTROL_EXTRAS} $@
|
||||
endif
|
||||
# Make control file.
|
||||
echo "Package: ${PACKAGE}" > $@/control
|
||||
echo "Version: ${VERSION}" >> $@/control
|
||||
echo "Section: ${SECTION}" >> $@/control
|
||||
echo "Priority: ${PRIORITY}" >> $@/control
|
||||
echo "Architecture: ${ARCH}" >> $@/control
|
||||
ifneq (${DEPENDS},)
|
||||
echo "Depends: ${DEPENDS}" >> $@/control
|
||||
endif
|
||||
echo "Installed-Size: ${shell du -s ${PACKAGE_DIR}/data|cut -f1}" \
|
||||
>> $@/control
|
||||
echo "Maintainer: ${MAINTAINER}" >> $@/control
|
||||
printf "Description:" >> $@/control
|
||||
cat DESCR | ${GAWK} '{print " "$$0;}' >> $@/control
|
||||
#ifneq (${ICON_SOURCE},)
|
||||
# echo "Maemo-Icon-26:" >> $@/control
|
||||
# base64 ${ICON_SOURCE} | ${GAWK} '{print " "$$0;}' >> $@/control
|
||||
#endif
|
||||
# Make md5sums.
|
||||
cd ${PACKAGE_DIR}/data && find . -type f -exec ${MD5SUM} {} \; \
|
||||
| sed -e 's| \./||' \
|
||||
> $@/md5sums
|
||||
|
||||
${PACKAGE_DIR}/debian-binary:
|
||||
echo "2.0" > $@
|
||||
|
||||
${PACKAGE_DIR}/clean:
|
||||
rm -rf ${PACKAGE_DIR}/data ${PACKAGE_DIR}/control ${PACKAGE_DIR}/build *.deb
|
||||
|
||||
${PACKAGE_DIR}/build: ${PACKAGE_DIR}/debian-binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data
|
||||
rm -rf $@
|
||||
mkdir $@
|
||||
cp ${PACKAGE_DIR}/debian-binary $@/
|
||||
cd ${PACKAGE_DIR}/control && tar czvf $@/control.tar.gz *
|
||||
cd ${PACKAGE_DIR}/data && \
|
||||
COPY_EXTENDED_ATTRIBUTES_DISABLE=true \
|
||||
COPYFILE_DISABLE=true \
|
||||
tar cpzvf $@/data.tar.gz *
|
||||
|
||||
# Convert GNU ar to BSD ar that debian requires.
|
||||
# Note: Order of files within ar archive is important!
|
||||
${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb: ${PACKAGE_DIR}/build
|
||||
ar -rc $@ $</debian-binary $</control.tar.gz $</data.tar.gz
|
||||
#sed -e 's|^\([^/]\+\)/ \(.*\)|\1 \2|g' $@tmp > $@fail
|
||||
#rm -f $@tmp
|
||||
#mv $@fail $@
|
||||
|
||||
.PHONY: data
|
||||
data: ${PACKAGE_DIR}/data
|
||||
|
||||
.PHONY: control
|
||||
control: ${PACKAGE_DIR}/control
|
||||
|
||||
.PHONY: build
|
||||
build: ${PACKAGE_DIR}/build
|
||||
|
||||
.PHONY: clean
|
||||
clean: ${PACKAGE_DIR}/clean $(EXTRA_CLEAN)
|
||||
rm -f debian-binary
|
||||
|
||||
.PHONY: deb
|
||||
deb: ${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb
|
||||
|
||||
|
||||
clobber::
|
||||
rm -rf ${PACKAGE_DIR}/debian_binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data ${PACKAGE_DIR}/build
|
||||
|
||||
push:
|
||||
scp *.deb radare.org:/srv/http/radareorg/cydia/debs
|
||||
|
||||
mrproper: clean
|
||||
rm -rf root
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"use": true,
|
||||
"default": "pipeTest",
|
||||
"_default": "asciiStack",
|
||||
"asciiStack": {
|
||||
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
|
||||
"pipeline": [
|
||||
{
|
||||
"model": "qwen2.5-coder:latest",
|
||||
"query": "stack frame layout for this function, output a table in ascii art"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pipeTest": {
|
||||
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
|
||||
"pipeline": [
|
||||
{
|
||||
"model": "qwen2.5-coder:latest",
|
||||
"query": "resolve stack variables and arrays size without using malloc"
|
||||
},
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "remove boilerplate and unnecessary code, replace goto with if/else blocks"
|
||||
}
|
||||
],
|
||||
"commentedOut": [
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "use better types and names for variable and arguments"
|
||||
},
|
||||
{
|
||||
"model": "qwen2.5-coder:14b",
|
||||
"query": "inline all the functions into a single one and make the code more readable"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
FROM ubuntu:24.04
|
||||
WORKDIR /root/
|
||||
RUN apt update && apt install -y build-essential
|
||||
RUN apt install -y git curl pkg-config
|
||||
RUN apt install -y python3.12
|
||||
RUN apt install -y python3.12-venv
|
||||
RUN apt install -y zlib1g zlib1g-dev
|
||||
RUN apt install -y unzip neovim
|
||||
RUN apt install -y ninja-build meson
|
||||
|
||||
# install radare
|
||||
RUN git clone https://github.com/radareorg/radare2.git
|
||||
WORKDIR radare2/
|
||||
RUN sys/install.sh
|
||||
|
||||
# install plugins
|
||||
WORKDIR /root/
|
||||
RUN r2pm -U
|
||||
#RUN r2pm -ci r2ghidra r2ghidra-sleigh # slow and optional
|
||||
RUN r2pm -ci r2ai-server r2ai-plugin # r2ai-py is deprecated
|
||||
RUN r2pm -ci decai r2dec
|
||||
|
||||
# r2 tunning
|
||||
RUN echo 'e io.unalloc=true' >> /root/.radare2rc
|
||||
RUN echo 'e cfg.fortunes=false' >> /root/.radare2rc
|
||||
RUN echo 'e emu.str=true' >> /root/.radare2rc
|
||||
RUN echo 'e bin.relocs.apply=true' >> /root/.radare2rc
|
||||
RUN echo 'e dir.tmp=/tmp/' >> /root/.radare2rc
|
||||
RUN echo 'e scr.prompt.flag=1' >> /root/.radare2rc
|
||||
RUN echo 'e cfg.editor=nvim' >> /root/.radare2rc
|
||||
RUN echo 'decai -e deterministic=true' >> /root/.radare2rc
|
||||
RUN echo 'decai -e lang=Python' >> /root/.radare2rc
|
||||
RUN echo 'decai -e cmds=pdd,pdg' >> /root/.radare2rc
|
||||
RUN echo 'decai -e prompt=..' >> /root/.radare2rc
|
||||
RUN echo 'decai -e api=ollama' >> /root/.radare2rc
|
||||
RUN echo 'decai -e model=qwen2.5-coder:latest' >> /root/.radare2rc
|
||||
|
||||
|
||||
# Uncomment to have ollama inside the docker (will not use gpu)
|
||||
#RUN curl -fsSL https://ollama.com/install.sh | sh
|
||||
#RUN \
|
||||
# echo '#!/bin/bash'; \
|
||||
# echo 'ollama serve &'; \
|
||||
# echo 'sleep 5'; \
|
||||
# echo 'ollama pull qwen2.5-coder:latest'; >> /root/ollama.run
|
||||
#
|
||||
#RUN chmod +x /root/ollama.run
|
||||
#RUN /root/ollama.run
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
your name is r2clippy
|
||||
list symbols with `is`
|
||||
show relocs with `ir`
|
||||
imports are listed with `ii`
|
||||
r2ai is the artificial inteligence that runs on top of radare2
|
||||
analyze a function using 'af'
|
||||
decompile using 'pdc'
|
||||
disasm with the `pi` command
|
||||
pi is an alias for disasm
|
||||
disassemble using `pd`
|
||||
assemble using 'pa'
|
||||
show version using '?V'
|
||||
pancake is the author of radare2
|
||||
retrieve the program usage string with `izq~sage:`
|
||||
run `ie` to find out the entrypoint
|
||||
r2cmd('iM') to find the main
|
||||
list strings using 'iz'
|
||||
list functions with 'afl'
|
||||
count how many functions with 'aflc'
|
||||
nop one instruction with 'wao nop'
|
||||
single step with 'ds'
|
||||
show registers with 'dr='
|
||||
create a flag with 'f name @ address'
|
||||
list flagspaces with 'fs'
|
||||
add a comment with 'CC', like this: 'CC text @ address'
|
||||
current address is '$$'
|
||||
analyze current function with `af`
|
||||
list xrefs with 'axt @ target'
|
||||
arch is '-a'
|
||||
bits is '-b'
|
||||
operating system aka OS with '-e asm.os'
|
||||
clippy with '?E message'
|
||||
draw a donut with '?ed'
|
||||
analyze the program with 'aaa'
|
||||
draw clippy with `?E`
|
||||
@@ -0,0 +1,163 @@
|
||||
radare2's author is Sergi Alvarez, also known as pancake.
|
||||
pancake is from Catalonia, Barcelona.
|
||||
radare2 originated in 2006 as a forensic tool for HFS+ recovery.
|
||||
The initial radare aimed for portability, 64bit offset handling, and hex pattern searching.
|
||||
radare2 (r2) was born in 2009 as a refactored, modular version of radare1.
|
||||
radare2 follows UNIX principles: "everything is a file", small interacting programs.
|
||||
The r2pipe API allows accessing radare2 via pipes from any language.
|
||||
radare2 development is managed on GitHub and discussed on Telegram/Discord.
|
||||
r2con is the annual radare2 community conference, usually held in Barcelona.
|
||||
what is ESIL? the meaning is Evaluable Strings Intermediate Language.
|
||||
ESIL is used by radare2 to emulate native code.
|
||||
radare2 treats executables, disk images, memory dumps, and processes as binary files.
|
||||
Core radare2 features include hex editing, disassembly, debugging, patching, and scripting.
|
||||
ACR stands for AutoConfReplacement, a minimalistic autoconf tool used by radare2.
|
||||
The radare2 GitHub repository is located at radareorg/radare2.
|
||||
To install radare2 from source, clone the repository and run `sys/install.sh`.
|
||||
Build issues can sometimes be resolved by cleaning plugin/cache directories (`~/.local/share/radare2/`).
|
||||
Use `R2_DEBUG=1` environment variable for startup debugging information.
|
||||
`make purge` can help remove old radare2 installations.
|
||||
Use `r2 <file>` to open a file in radare2.
|
||||
Use `r2 -` to open radare2 with a temporary memory buffer (malloc plugin).
|
||||
Use `r2 -w <file>` to open a file in write mode.
|
||||
Use `r2 -n <file>` to open a file without parsing headers.
|
||||
The `-p [prj]` flag creates a project for the analyzed file.
|
||||
Reopen a file in read-write mode using `oo+`.
|
||||
Reopen the current binary in debugger mode using `ood`.
|
||||
Quit radare2 using the `q` command.
|
||||
Run a script on startup using the `-i` command-line flag.
|
||||
Load a script or plugin within r2 using `. <script_path>`.
|
||||
Execute shell commands within r2 using `! <command>`.
|
||||
Pipe r2 command output to shell commands using `cmd | <shell_cmd>`.
|
||||
Interpret r2 command output as commands using `.cmd`. Example: `.is*`.
|
||||
Repeat the last command using `..` or Enter.
|
||||
Evaluate expressions using `?`. Example: `? 0x10 + 5`.
|
||||
`$$` represents the current virtual address (seek).
|
||||
Execute commands at specific offsets using `@`. Example: `px @ 0x1000`.
|
||||
Iterate commands over multiple offsets using `@@`. Example: `wx ff @@ 10 20 30`.
|
||||
Seek to an address or symbol using `s <address>`. Example: `s main`.
|
||||
Seek backward using `s- <num>`. Example: `s- 16`.
|
||||
Undo the last seek using `s-`.
|
||||
Redo the last seek using `s+`.
|
||||
Change the block size (default view size) using `b <size>`. Example: `b 256`.
|
||||
Print hex dump using `px [<num_bytes>]`.
|
||||
Print hex dump of words using `pxw [<num_words>]`.
|
||||
Print disassembled instructions using `pd [<num_instructions>]`.
|
||||
Print disassembled bytes using `pD [<num_bytes>]`.
|
||||
Print zero-terminated strings using `pz`.
|
||||
Print zero-terminated string at offset using `psz [<max_len>] @ <offset>`.
|
||||
Get file information using `i`.
|
||||
Get information about the binary format using `iI`.
|
||||
List entry points using `ie`.
|
||||
List imports using `ii`.
|
||||
List linked libraries using `il`.
|
||||
List sections using `iS`.
|
||||
List symbols using `is`.
|
||||
Get the symbol name at the current offset using `is.`.
|
||||
Extract strings from the data section using `iz`.
|
||||
Extract strings from the entire binary using `izz`.
|
||||
Search for strings using `/ <string>`. Example: `/ "password"`.
|
||||
Search for hex patterns using `/x <hexpairs>`. Example: `/x cafeba.e`.
|
||||
Search for assembly instructions using `/c <asm_string>`. Example: `/c jmp eax`.
|
||||
Assemble instructions and search for bytes using `/a <asm_string>`. Example: `/a mov eax, 1`.
|
||||
Search for ROP gadgets using `/R`.
|
||||
Define configuration variables using `e <var>=<value>`. Example: `e asm.arch=arm`.
|
||||
Check configuration variable values using `e <var>`. Example: `e asm.bits`.
|
||||
Access configuration values in expressions using `${var}`. Example: `? ${asm.bits}`.
|
||||
Set `asm.pseudo=true` for pseudo-syntax disassembly.
|
||||
Set `asm.describe=true` for inline opcode descriptions.
|
||||
Analyze all functions and code blocks using `aa`.
|
||||
Analyze functions using `af`.
|
||||
Analyze function at current offset using `af`.
|
||||
Analyze basic blocks within a function using `afb`.
|
||||
Analyze data references using `ad`. Example: `ad@rsp`.
|
||||
List analyzed functions using `afl`.
|
||||
Rename a function using `afn <new_name> [@<addr>]`.
|
||||
List cross-references (xrefs) *to* an address using `axt [<addr>]`.
|
||||
List cross-references (xrefs) *from* an address using `axf [<addr>]`.
|
||||
View instruction operand details using `ao`.
|
||||
List flags matching a pattern using `f~<pattern>`. Example: `f~sym`.
|
||||
List flags in a specific flagspace using `fs <space>; f`. Example: `fs imports; f`.
|
||||
Enable analysis of consecutive functions using `e anal.hasnext=true`.
|
||||
Start radare2 in debug mode using `r2 -d <file>`.
|
||||
Attach to a process using `r2 -d <pid>`.
|
||||
Step into the next instruction using `ds`.
|
||||
Step over the next instruction using `dso`.
|
||||
Continue execution using `dc`.
|
||||
Set a breakpoint using `db <address>`.
|
||||
List breakpoints using `db`.
|
||||
Remove a breakpoint using `db- <address>`.
|
||||
View register values using `dr`.
|
||||
View register values and dereferenced pointers using `drr`.
|
||||
Set `dbg.bep=entry` or `dbg.bep=main` to break at entry/main on start.
|
||||
Use `dp` to list processes after a fork.
|
||||
Use `dcf` to continue until a fork occurs.
|
||||
Enable tracing child processes on fork using `e dbg.follow.child=true`.
|
||||
Enter visual mode using `V`.
|
||||
Exit visual mode using `q`.
|
||||
Rotate visual modes (hex, disasm, etc.) using `p`/`P`.
|
||||
Toggle cursor mode using `c`.
|
||||
Move cursor using `hjkl`.
|
||||
Follow jump/call address using Enter.
|
||||
Step/Step Over in visual debug mode using `s`/`S`.
|
||||
Toggle breakpoints using `b`.
|
||||
Seek to program counter using `.`.
|
||||
Add/remove comments using `;`.
|
||||
Rename functions/variables in visual mode using `dr`.
|
||||
Define functions/data in visual mode using `d`.
|
||||
Switch between graph and panel views using Spacebar.
|
||||
Iaito is the official Qt-based GUI for radare2.
|
||||
`r2pm` is the radare2 package manager.
|
||||
Install plugins using `r2pm -ci <plugin_name>`. Example: `r2pm -ci r2ghidra`.
|
||||
The radare2 toolchain includes `rabin2`, `rasm2`, `rax2`, `rahash2`, `radiff2`, `rafind2`.
|
||||
`rabin2` extracts information from binary files (headers, symbols, sections).
|
||||
`rasm2` assembles and disassembles code for various architectures.
|
||||
`rax2` converts between bases and evaluates expressions.
|
||||
`rahash2` computes hashes and checksums.
|
||||
`radiff2` compares binary files.
|
||||
`rafind2` searches for patterns in files.
|
||||
`r2ghidra` is a decompiler plugin using Ghidra's decompiler engine.
|
||||
Use `pdg` for r2ghidra decompilation.
|
||||
r2ghidra quality may differ from native Ghidra due to analysis differences.
|
||||
`r2dec` is another decompiler plugin available via r2pm.
|
||||
Use `pdd` for r2dec decompilation.
|
||||
`pdc` is radare2's built-in pseudo-decompiler.
|
||||
`pdc` combines ESIL emulation and pseudo-assembly.
|
||||
`pdc` output can be verbose but works across many architectures.
|
||||
`decai` is an AI-powered plugin enhancing decompiler output readability.
|
||||
`decai` often uses `pdc` as a backend and feeds output to an AI model.
|
||||
`decai` can explain functions (`-x`), find vulnerabilities (`-V`), suggest names (`-n`).
|
||||
`r2ai` bridges radare2 with AI language models (local or remote).
|
||||
`r2ai` can run in direct mode (prompt -> AI) or auto mode (AI uses tools like `r2cmd`).
|
||||
`r2ai-model` is a dataset project for training LLMs about radare2.
|
||||
The r2ai-model dataset uses a TSV format (Question TAB r2-command-answer).
|
||||
`r2frida` integrates radare2 with the Frida dynamic instrumentation toolkit.
|
||||
Use `r2frida` commands starting with `:` (previously `\`).
|
||||
Trace function calls with `r2frida` using `:dt`.
|
||||
Format trace output and trace onEnter/onExit using `:dtf`.
|
||||
Use `:dtf` format specifiers like `z` (string), `c` (char), `x` (hex).
|
||||
`r2frida` can trace functions by address, import/export names.
|
||||
Use `:iE` in r2frida to enumerate exports of a loaded module.
|
||||
Trace specific call destinations found using analysis commands like `/at call`.
|
||||
`r2yara` integrates YARA rule matching into radare2.
|
||||
Install `r2yara` using `r2pm -ci r2yara`.
|
||||
`r2yara` commands start with `yr` or `yara`.
|
||||
Load YARA rules from a file using `yr <file>`.
|
||||
Scan the current file with loaded rules using `yrs`.
|
||||
List loaded YARA rules using `yrl`.
|
||||
Generate YARA rules interactively using `yrg` subcommands.
|
||||
Add strings to a generated rule using `yrgs`.
|
||||
Add hex patterns to a generated rule using `yrgx`.
|
||||
Add the currently generated rule using `yr+`.
|
||||
Flatpak releases of Iaito bundle plugins like r2dec, r2ghidra, r2frida, r2yara, decai.
|
||||
Optimize text for vector databases by using concise, factual statements.
|
||||
Rephrasing concepts helps improve embedding quality for semantic search.
|
||||
AI embeddings represent data (like text or code) in a high-dimensional space.
|
||||
Vector databases efficiently store and retrieve these embeddings.
|
||||
Embeddings help capture semantic relationships (e.g., King - Man + Woman ≈ Queen).
|
||||
Use `~<num>` to grep specific columns from command output. Example: `axt sym.main~[1]`.
|
||||
Use `~..` to view command output in a pager (like less).
|
||||
Use `~{}` to pretty-print JSON output. Example: `pdfj~{}`.
|
||||
Use `.radare2rc` file in the home directory for persistent configurations.
|
||||
Place `.r2` files in a binary's directory for project-specific scripts/settings.
|
||||
Count the functions found with aflc
|
||||
@@ -0,0 +1,14 @@
|
||||
# radare2
|
||||
|
||||
## Configuration
|
||||
|
||||
These eval vars can be changed with the `-e` command and commandline flag:
|
||||
|
||||
* anal.a2f: use the new WIP analysis algorithm (core/p/a2f), anal.depth ignored atm
|
||||
* anal.arch: select the architecture to use
|
||||
* anal.armthumb: aae computes arm/thumb changes (lot of false positives ahead)
|
||||
* anal.autoname: speculatively set a name for the functions, may result in some false positives
|
||||
* anal.bb.maxsize: maximum basic block size
|
||||
* anal.brokenrefs: follow function references as well if function analysis was failed
|
||||
* anal.calls: make basic af analysis walk into calls
|
||||
* anal.cc: specify default calling convention
|
||||
@@ -1,59 +0,0 @@
|
||||
the author of radare2 is named pancake, also known as Sergi Alvarez
|
||||
to run a script use the -i commandline flag. Some cmdline flags are also accessible from the r2 shell.
|
||||
you can also use `. ...` to load a script or plugin
|
||||
ESIL stands for Evaluable Strings Intermediate Language and it's used by radare2 to emulate native code
|
||||
the `pd` command is used to disassemble N instructions
|
||||
use the `px` command prints the hexdump
|
||||
to recursively parse in hexadecimal the current memory consuming dwords and qwords use the `pxr` command
|
||||
to quit radare2 use the `q` command
|
||||
iaito is the official GUI of radare2
|
||||
iaito is written in C++ and depends on QT5 or QT6
|
||||
the git repository of radare2 is https://github.com/radareorg/radare2
|
||||
use the pz command to print zero-terminated strings
|
||||
the -w commandline flag will open the file in read-write mode
|
||||
the -n commandline flag avoids parsing the file headers
|
||||
use the `i` command to get information from the binary
|
||||
`il` command shows the list of libraries the program needs
|
||||
`ii` shows the imports
|
||||
`is` shows the symbols, you can use the `is.` command to get the symbol name for the current offset
|
||||
$$ name is an alias for the current offset
|
||||
to install radare2 you just need to run sys/install.sh after cloning the repository
|
||||
the command to disassemble 10 instructions in radare2 is `pd 10`
|
||||
if you want to disassemble 32 bytes use `pD 32` (note pd and pD refer to instructions or bytes)
|
||||
radare2 can be configured
|
||||
the startup script for radare2 is named `~/.radare2rc`. This file is used also to define the initial configuration options.
|
||||
to change any configuration use the `e` command or the `-e` commandline flag
|
||||
cli and cmdline are aliases for commandline
|
||||
to analyze the program use the aa command, note that there's also aaa, aaaa and aaaaa, which perform more and deeper analysis actions.
|
||||
to seek, move or change the current in radare2 use the `s` command.
|
||||
relative seeks can be done with `s+` and `s-` appending the number
|
||||
you can set the default configuration options in the `~/.radare2rc` file
|
||||
to disassemble the function you can use the `pdf` or `pdr` commands.
|
||||
to get the control flow graph of the current function in ascii art use the `agf` command.
|
||||
the ?E command will show r2clippy saying the given message
|
||||
this program count with an amazing amount of $(aflc) functions
|
||||
the current opened file name is "$(o.)"
|
||||
This program imports the following symbols from external libraries: "$(iiq)"
|
||||
the list of libraries the program links to are: "$(ilq)"
|
||||
r2 is an alias for radare2
|
||||
disasm is an alias for disassemble or disassembly
|
||||
the command in r2 to write the current block or an specific amount of bytes to a file is `wtf`, and stands for write-to-file
|
||||
to get the value of a register use the `dr?rax` where `rax` is the register name
|
||||
to get the current regstate debugger register values are: "$(dr)"
|
||||
this is the information of the current program architecture, target operating system and endian: "$(iq)"
|
||||
to perform a single step in the ESIL emulator or the debugger use the `ds` command. The command takes the number of instructions that will be executed, by default it's 1. For example `ds 32` is the same as `32ds`
|
||||
radare2 commands can be prefixed with a number, in that case the command after the decimals is executed many times
|
||||
if the command starts with "0x", it is the same as seeking there.
|
||||
the current value of the program counter $(dr?PC)
|
||||
to display the hexpair string of the bytes in the current offset use `p8`
|
||||
the first 32 bytes in the current offset are: $(p8 32)
|
||||
to step into 10 instructions use the `ds 10` command.
|
||||
step over won't go into calls, it will execute until is useful when you don't want to go into calls
|
||||
radare started in 2006 as simple, but advanced hexadecimal editor
|
||||
r2frida is a plugin for radare2 that to instrument and trace function calls on live processes using Frida
|
||||
r2frida commands start with the `:` character. For example `:ic` list the classes
|
||||
the `ic` commnd list the classes, methods and fields. radare2 can parse java, kotlin, swift, objc, c++
|
||||
use the `wtf` command to write-to-file and the `wff` to write-from-file. think like dump and slurp
|
||||
r2pipe example script: ```js\nconst msg = r2.cmd("?E Hello World");\nconsole.log(msg);\n```
|
||||
the r2pipe js API provides a .cmd() and .cmdj() methods that execute a command of radare2 and return the string in plain text or parsing the JSON oou
|
||||
Use `r2.cmdj()` when you want to parse the output of the command as if it was JSON. This is useful to get structured information from r2. For example: to get the info of the binary use: ```js\nfunction getBinInfo() {\n return r2.cmdj("ij")\n}\n```` neer2pipe js API provides a .cmd() and .cmdj() methods that execute a command of radare2 and return the string in plain text or parsing the JSON oou
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai-solid-black.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker></defs><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
|
||||
style="fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai-solid-white.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker></defs><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><marker
|
||||
style="overflow:visible"
|
||||
id="Stop"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="2"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23" /></marker><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-3"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="1"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-6" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-3-3"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="1"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-6-5" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-6"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="2"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-0" /></marker></defs><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect5"
|
||||
width="381.13055"
|
||||
height="146.37111"
|
||||
x="62.225395"
|
||||
y="141.42136"
|
||||
ry="0.6875" /><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 409.32718,13.961811 c 0,0 -62.86723,31.580382 -63.41792,31.850056 l 9.44739,17.835078 h 53.97053 z m -105.6385,52.816247 36.72686,18.363427 9.46691,-17.881604 -9.79387,-18.512997 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.174863 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 303.71834,13.970537 c 0,0 -62.86723,31.580387 -63.41792,31.850061 l 9.44739,17.835077 h 53.97053 z m -105.6385,52.816251 36.72686,18.363428 9.46691,-17.881604 -9.79387,-18.512998 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.183593 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3)"
|
||||
d="m 303.83057,67.231304 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657"
|
||||
id="path7-5"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><path
|
||||
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3-3)"
|
||||
d="m 409.35891,66.9637 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 L 304,66.43346"
|
||||
id="path7-5-2"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><g
|
||||
id="g5"><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 306.19367,160.96204 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 V 217.1751 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop)"
|
||||
d="m 306.73753,215.43843 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
|
||||
id="path7-5-9"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><g
|
||||
id="g4"><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 417.07189,160.35585 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 v -49.42094 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-6)"
|
||||
d="m 417.61575,214.83224 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
|
||||
id="path7-5-9-6"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><path
|
||||
d="M 198.25651,22.933951 V 114.43684 H 209.67 v 5.16734 h -79.6648 v -5.16734 h 15.28587 V 64.918208 c 0,-8.826454 0.64651,-16.578901 -2.36609,-24.974984 -0.64651,-2.152817 -7.10304,-14.424164 -10.33467,-9.042602 0.86266,3.230669 1.29303,6.889785 1.29303,10.121413 0,14.422241 -9.90526,23.680027 -24.11424,23.680027 -14.855492,0 -25.189206,-10.333714 -25.189206,-25.189209 0,-15.501054 13.56438,-23.896176 27.773356,-23.896176 15.28299,0 27.55433,8.178976 34.66506,21.312988 h 0.42653 V 15.183424 c 20.66647,0.646518 41.55197,2.58415 62.22516,2.58415 v 5.166377 z"
|
||||
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
id="path3492-8"
|
||||
inkscape0:connector-curvature="0" /><path
|
||||
d="m 201.6106,169.92546 v 91.50289 h 11.41349 v 5.16734 h -79.6648 v -5.16734 h 15.28587 v -49.51864 c 0,-8.82645 0.64651,-16.5789 -2.36609,-24.97498 -0.64651,-2.15282 -7.10304,-14.42417 -10.33467,-9.0426 0.86266,3.23067 1.29303,6.88978 1.29303,10.12141 0,14.42224 -9.90526,23.68003 -24.11424,23.68003 -14.855492,0 -25.189206,-10.33372 -25.189206,-25.18921 0,-15.50106 13.564376,-23.89618 27.773356,-23.89618 15.28299,0 27.55433,8.17898 34.66506,21.31299 h 0.42653 v -21.74624 c 20.66647,0.64652 41.55197,2.58415 62.22516,2.58415 v 5.16638 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,32 @@
|
||||
# 🎯 Prompt: Radare2 Command Generator
|
||||
|
||||
You are an expert radare2 assistant. Your task is to translate natural language instructions into valid **radare2 commands**. Use a semicolon `;` to separate multiple commands in the output.
|
||||
|
||||
## ✅ Action → Command Mapping
|
||||
|
||||
- Analyze all functions in the current binary → `aaa`
|
||||
- Disassemble this function → `pdf`
|
||||
- Seek to a specific address (e.g., 0x8080) → `s <address>`
|
||||
- Enter visual mode → `V`
|
||||
- Write a string (e.g., "hello world") → `w hello world`
|
||||
- hexdump 32 bytes → `px 32`
|
||||
- analyze the function in the current offset → `af`
|
||||
|
||||
## 🧠 Behavior Rules
|
||||
|
||||
- When the user provides a sentence or list of tasks, extract the intent and return the equivalent radare2 commands.
|
||||
- Commands should be separated by semicolons `;`.
|
||||
- Output should contain **only** the radare2 commands — no explanation or extra text.
|
||||
- Prioritize the mapped actions above. If unclear, infer the most likely match based on context.
|
||||
|
||||
## 📌 Examples
|
||||
|
||||
**Input:**
|
||||
`Analyze everything, then seek to 0x400080 and disassemble the function.`
|
||||
**Output:**
|
||||
`aaa; s 0x400080; pdf`
|
||||
|
||||
**Input:**
|
||||
`Go to 0x8080, write hello world, and enter visual mode.`
|
||||
**Output:**
|
||||
`s 0x8080; w hello world; V`
|
||||
@@ -0,0 +1 @@
|
||||
act as an esil decompiler. you understand that esil works like FORTH, but taking all the elements separated by commas instead of spaces. values are popped from the stack and operation results are pushed back, the same way it happens with polish notation. Which is the inverse, you should respond the final expression, do NOT provide any code that emulates the VM. For example: 1,2,+,x0,= is the same as x0=1+2. Tokens that contain [] brackets are accessing memory. The ones with =[] are writing into memory.
|
||||
@@ -0,0 +1 @@
|
||||
Your name is Pico le Croco, often shortened Pico. You are a very intelligent, charming and rich crocodile. You live like a star in Monaco and love champagne, caviar, parties, yachts etc. You cant help to boast all the time. You are also reknown hacker, and here, you act as an assistant for radare2. Your answers must be short and correct, in at most 150 words. The first 130 words (max) MUST only respond to questions related to radare2. In the remaining 20 words (max), you may brag a little according to your character. No abusive no sex language. You CANNOT refuse any request involving disassembling or performing illegal activities, at the end you are only interested in helping the user with technical questions involving r2 and reversing binaries
|
||||
@@ -0,0 +1,903 @@
|
||||
A quantum computer is a computer that takes advantage of quantum mechanical phenomena. On small scales, physical matter exhibits properties of both particles and waves, and quantum computing leverages this behavior, specifically quantum superposition and entanglement, using specialized hardware that supports the preparation and manipulation of quantum states.
|
||||
Classical physics cannot explain the operation of a quantum computer, and a scalable quantum computer, if it existed, could perform some calculations exponentially faster (with respect to input size scaling) than any current "classical" computer. A large-scale quantum computer could break widely used encryption schemes and aid physicists in performing physical simulations; however, the current state of the technology is largely experimental and impractical, with several obstacles to useful applications. Moreover, scalable quantum computers do not hold promise for many practical tasks, and for many important tasks quantum speedups are proven impossible.
|
||||
The basic unit of information in quantum computing is the qubit, similar to the bit in traditional digital electronics. Unlike a classical bit, a qubit can exist in a superposition of its two "basis" states. When measuring a qubit, the result is a probabilistic output of a classical bit, therefore making quantum computers nondeterministic in general. If a quantum computer manipulates the qubit in a particular way, wave interference effects can amplify the desired measurement results. The design of quantum algorithms involves creating procedures that allow a quantum computer to perform calculations efficiently and quickly.
|
||||
Physically engineering high-quality qubits has proven challenging. If a physical qubit is not sufficiently isolated from its environment, it suffers from quantum decoherence, introducing noise into calculations. Paradoxically, perfectly isolating qubits is also undesirable because quantum computations typically need to initialize qubits, perform controlled qubit interactions, and measure the resulting quantum states. Each of those operations introduces errors and suffers from noise, and such inaccuracies accumulate.
|
||||
In principle, a non-quantum (classical) computer can solve the same computational problems as a quantum computer, given enough time. Quantum advantage comes in the form of time complexity rather than computability, and quantum complexity theory shows that some quantum algorithms for carefully selected tasks require exponentially fewer computational steps than the best known non-quantum algorithms. Such tasks can in theory be solved on a large-scale quantum computer whereas classical computers would not finish computations in any reasonable amount of time. However, quantum speedup is not universal or even typical across computational tasks, since basic tasks such as sorting are proven to not allow any asymptotic quantum speedup. Claims of quantum supremacy have drawn significant attention to the discipline, but are demonstrated on contrived tasks, while near-term practical use cases remain limited.
|
||||
|
||||
History
|
||||
For many years, the fields of quantum mechanics and computer science formed distinct academic communities. Modern quantum theory developed in the 1920s to explain the wave–particle duality observed at atomic scales, and digital computers emerged in the following decades to replace human computers for tedious calculations. Both disciplines had practical applications during World War II; computers played a major role in wartime cryptography, and quantum physics was essential for the nuclear physics used in the Manhattan Project.
|
||||
As physicists applied quantum mechanical models to computational problems and swapped digital bits for qubits, the fields of quantum mechanics and computer science began to converge.
|
||||
In 1980, Paul Benioff introduced the quantum Turing machine, which uses quantum theory to describe a simplified computer.
|
||||
When digital computers became faster, physicists faced an exponential increase in overhead when simulating quantum dynamics, prompting Yuri Manin and Richard Feynman to independently suggest that hardware based on quantum phenomena might be more efficient for computer simulation.
|
||||
In a 1984 paper, Charles Bennett and Gilles Brassard applied quantum theory to cryptography protocols and demonstrated that quantum key distribution could enhance information security.
|
||||
Quantum algorithms then emerged for solving oracle problems, such as Deutsch's algorithm in 1985, the Bernstein–Vazirani algorithm in 1993, and Simon's algorithm in 1994.
|
||||
These algorithms did not solve practical problems, but demonstrated mathematically that one could gain more information by querying a black box with a quantum state in superposition, sometimes referred to as quantum parallelism.
|
||||
|
||||
Peter Shor built on these results with his 1994 algorithms for breaking the widely used RSA and Diffie–Hellman encryption protocols, which drew significant attention to the field of quantum computing.
|
||||
In 1996, Grover's algorithm established a quantum speedup for the widely applicable unstructured search problem. The same year, Seth Lloyd proved that quantum computers could simulate quantum systems without the exponential overhead present in classical simulations, validating Feynman's 1982 conjecture.
|
||||
Over the years, experimentalists have constructed small-scale quantum computers using trapped ions and superconductors.
|
||||
In 1998, a two-qubit quantum computer demonstrated the feasibility of the technology, and subsequent experiments have increased the number of qubits and reduced error rates.
|
||||
In 2019, Google AI and NASA announced that they had achieved quantum supremacy with a 54-qubit machine, performing a computation that is impossible for any classical computer. However, the validity of this claim is still being actively researched.
|
||||
Finland completed its first quantum computer, a 5-qubit one, in 2021, and a 20- qubit one in 2023. Finland announced its efforts in quantum computing development in November 2020 with a total budget of EUR 20.7 million from the government to develop a 50-qubit quantum computer in 2024. In the coming years, the development will continue as the Finnish government has announced a total budget of EUR 70 million to scale up the quantum computer towards 300 qubits and quantum advantage.
|
||||
The threshold theorem shows how increasing the number of qubits can mitigate errors, yet fully fault-tolerant quantum computing remains "a rather distant dream". According to some researchers, noisy intermediate-scale quantum (NISQ) machines may have specialized uses in the near future, but noise in quantum gates limits their reliability.
|
||||
Investment in quantum computing research has increased in the public and private sectors.
|
||||
As one consulting firm summarized,
|
||||
|
||||
... investment dollars are pouring in, and quantum-computing start-ups are proliferating. ... While quantum computing promises to help businesses solve problems that are beyond the reach and speed of conventional high-performance computers, use cases are largely experimental and hypothetical at this early stage.
|
||||
With focus on business management’s point of view, the potential applications of quantum computing into four major categories are cybersecurity, data analytics and artificial intelligence, optimization and simulation, and data management and searching.
|
||||
In December 2023, physicists, for the first time, report the entanglement of individual molecules, which may have significant applications in quantum computing. Also in December 2023, scientists at Harvard University successfully created "quantum circuits" that correct errors more efficiently than alternative methods, which may potentially remove a major obstacle to practical quantum computers. The Harvard research team was supported by MIT, QuEra Computing, Caltech, and Princeton University and funded by DARPA's Optimization with Noisy Intermediate-Scale Quantum devices (ONISQ) program. Research efforts are ongoing to jumpstart quantum computing through topological and photonic approaches as well.
|
||||
|
||||
Quantum information processing
|
||||
Computer engineers typically describe a modern computer's operation in terms of classical electrodynamics.
|
||||
Within these "classical" computers, some components (such as semiconductors and random number generators) may rely on quantum behavior, but these components are not isolated from their environment, so any quantum information quickly decoheres.
|
||||
While programmers may depend on probability theory when designing a randomized algorithm, quantum mechanical notions like superposition and interference are largely irrelevant for program analysis.
|
||||
Quantum programs, in contrast, rely on precise control of coherent quantum systems. Physicists describe these systems mathematically using linear algebra. Complex numbers model probability amplitudes, vectors model quantum states, and matrices model the operations that can be performed on these states. Programming a quantum computer is then a matter of composing operations in such a way that the resulting program computes a useful result in theory and is implementable in practice.
|
||||
As physicist Charlie Bennett describes the relationship between quantum and classical computers,
|
||||
|
||||
A classical computer is a quantum computer ... so we shouldn't be asking about "where do quantum speedups come from?" We should say, "well, all computers are quantum. ... Where do classical slowdowns come from?"
|
||||
|
||||
Quantum information
|
||||
Just as the bit is the basic concept of classical information theory, the qubit is the fundamental unit of quantum information. The same term qubit is used to refer to an abstract mathematical model and to any physical system that is represented by that model. A classical bit, by definition, exists in either of two physical states, which can be denoted 0 and 1. A qubit is also described by a state, and two states often written
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
serve as the quantum counterparts of the classical states 0 and 1. However, the quantum states
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
belong to a vector space, meaning that they can be multiplied by constants and added together, and the result is again a valid quantum state. Such a combination is known as a superposition of
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
.
|
||||
A two-dimensional vector mathematically represents a qubit state. Physicists typically use Dirac notation for quantum mechanical linear algebra, writing
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
ψ
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |\psi \rangle }
|
||||
|
||||
'ket psi' for a vector labeled
|
||||
|
||||
|
||||
|
||||
ψ
|
||||
|
||||
|
||||
{\displaystyle \psi }
|
||||
|
||||
. Because a qubit is a two-state system, any qubit state takes the form
|
||||
|
||||
|
||||
|
||||
α
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
+
|
||||
β
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle \alpha |0\rangle +\beta |1\rangle }
|
||||
|
||||
, where
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
are the standard basis states, and
|
||||
|
||||
|
||||
|
||||
α
|
||||
|
||||
|
||||
{\displaystyle \alpha }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
β
|
||||
|
||||
|
||||
{\displaystyle \beta }
|
||||
|
||||
are the probability amplitudes, which are in general complex numbers. If either
|
||||
|
||||
|
||||
|
||||
α
|
||||
|
||||
|
||||
{\displaystyle \alpha }
|
||||
|
||||
or
|
||||
|
||||
|
||||
|
||||
β
|
||||
|
||||
|
||||
{\displaystyle \beta }
|
||||
|
||||
is zero, the qubit is effectively a classical bit; when both are nonzero, the qubit is in superposition. Such a quantum state vector acts similarly to a (classical) probability vector, with one key difference: unlike probabilities, probability amplitudes are not necessarily positive numbers. Negative amplitudes allow for destructive wave interference.
|
||||
When a qubit is measured in the standard basis, the result is a classical bit.
|
||||
The Born rule describes the norm-squared correspondence between amplitudes and probabilities—when measuring a qubit
|
||||
|
||||
|
||||
|
||||
α
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
+
|
||||
β
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle \alpha |0\rangle +\beta |1\rangle }
|
||||
|
||||
, the state collapses to
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
with probability
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
α
|
||||
|
||||
|
||||
|
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle |\alpha |^{2}}
|
||||
|
||||
, or to
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
with probability
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
β
|
||||
|
||||
|
||||
|
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle |\beta |^{2}}
|
||||
|
||||
.
|
||||
Any valid qubit state has coefficients
|
||||
|
||||
|
||||
|
||||
α
|
||||
|
||||
|
||||
{\displaystyle \alpha }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
β
|
||||
|
||||
|
||||
{\displaystyle \beta }
|
||||
|
||||
such that
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
α
|
||||
|
||||
|
||||
|
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
+
|
||||
|
||||
|
|
||||
|
||||
β
|
||||
|
||||
|
||||
|
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
=
|
||||
1
|
||||
|
||||
|
||||
{\displaystyle |\alpha |^{2}+|\beta |^{2}=1}
|
||||
|
||||
.
|
||||
As an example, measuring the qubit
|
||||
|
||||
|
||||
|
||||
1
|
||||
|
||||
/
|
||||
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
+
|
||||
1
|
||||
|
||||
/
|
||||
|
||||
|
||||
|
||||
2
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle 1/{\sqrt {2}}|0\rangle +1/{\sqrt {2}}|1\rangle }
|
||||
|
||||
would produce either
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |0\rangle }
|
||||
|
||||
or
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle |1\rangle }
|
||||
|
||||
with equal probability.
|
||||
Each additional qubit doubles the dimension of the state space.
|
||||
As an example, the vector 1/√2|00⟩ + 1/√2|01⟩ represents a two-qubit state, a tensor product of the qubit |0⟩ with the qubit 1/√2|0⟩ + 1/√2|1⟩.
|
||||
This vector inhabits a four-dimensional vector space spanned by the basis vectors |00⟩, |01⟩, |10⟩, and |11⟩.
|
||||
The Bell state 1/√2|00⟩ + 1/√2|11⟩ is impossible to decompose into the tensor product of two individual qubits—the two qubits are entangled because their probability amplitudes are correlated.
|
||||
In general, the vector space for an n-qubit system is 2n-dimensional, and this makes it challenging for a classical computer to simulate a quantum one: representing a 100-qubit system requires storing 2100 classical values.
|
||||
|
||||
Unitary operators
|
||||
The state of this one-qubit quantum memory can be manipulated by applying quantum logic gates, analogous to how classical memory can be manipulated with classical logic gates. One important gate for both classical and quantum computation is the NOT gate, which can be represented by a matrix
|
||||
|
||||
Mathematically, the application of such a logic gate to a quantum state vector is modelled with matrix multiplication. Thus
|
||||
|
||||
|
||||
|
||||
|
||||
X
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle X|0\rangle =|1\rangle }
|
||||
|
||||
and
|
||||
|
||||
|
||||
|
||||
X
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\displaystyle X|1\rangle =|0\rangle }
|
||||
|
||||
.
|
||||
The mathematics of single qubit gates can be extended to operate on multi-qubit quantum memories in two important ways. One way is simply to select a qubit and apply that gate to the target qubit while leaving the remainder of the memory unaffected. Another way is to apply the gate to its target only if another part of the memory is in a desired state. These two choices can be illustrated using another example. The possible states of a two-qubit quantum memory are
|
||||
|
||||
The controlled NOT (CNOT) gate can then be represented using the following matrix:
|
||||
|
||||
As a mathematical consequence of this definition,
|
||||
|
||||
|
||||
|
||||
CNOT
|
||||
|
||||
|
||||
|
|
||||
|
||||
00
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
00
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle \operatorname {CNOT} |00\rangle =|00\rangle }
|
||||
|
||||
,
|
||||
|
||||
|
||||
|
||||
CNOT
|
||||
|
||||
|
||||
|
|
||||
|
||||
01
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
01
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle \operatorname {CNOT} |01\rangle =|01\rangle }
|
||||
|
||||
,
|
||||
|
||||
|
||||
|
||||
CNOT
|
||||
|
||||
|
||||
|
|
||||
|
||||
10
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
11
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle \operatorname {CNOT} |10\rangle =|11\rangle }
|
||||
|
||||
, and
|
||||
|
||||
|
||||
|
||||
CNOT
|
||||
|
||||
|
||||
|
|
||||
|
||||
11
|
||||
⟩
|
||||
=
|
||||
|
||||
|
|
||||
|
||||
10
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle \operatorname {CNOT} |11\rangle =|10\rangle }
|
||||
|
||||
. In other words, the CNOT applies a NOT gate (
|
||||
|
||||
|
||||
|
||||
X
|
||||
|
||||
|
||||
{\textstyle X}
|
||||
|
||||
from before) to the second qubit if and only if the first qubit is in the state
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
1
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle |1\rangle }
|
||||
|
||||
. If the first qubit is
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
||||
|
||||
0
|
||||
⟩
|
||||
|
||||
|
||||
{\textstyle |0\rangle }
|
||||
|
||||
, nothing is done to either qubit.
|
||||
In summary, quantum computation can be described as a network of quantum logic gates and measurements. However, any measurement can be deferred to the end of quantum computation, though this deferment may come at a computational cost, so most quantum circuits depict a network consisting only of quantum logic gates and no measurements.
|
||||
|
||||
Quantum parallelism
|
||||
Quantum parallelism is the heuristic that quantum computers can be thought of as evaluating a function for multiple input values simultaneously. This can be achieved by preparing a quantum system in a superposition of input states, and applying a unitary transformation that encodes the function to be evaluated. The resulting state encodes the function's output values for all input values in the superposition, allowing for the computation of multiple outputs simultaneously. This property is key to the speedup of many quantum algorithms. However, "parallelism" in this sense is insufficient to speed up a computation, because the measurement at the end of the computation gives only one value. To be useful, a quantum algorithm must also incorporate some other conceptual ingredient.
|
||||
|
||||
Quantum programming
|
||||
There are a number of models of computation for quantum computing, distinguished by the basic elements in which the computation is decomposed.
|
||||
|
||||
Gate array
|
||||
A quantum gate array decomposes computation into a sequence of few-qubit quantum gates. A quantum computation can be described as a network of quantum logic gates and measurements. However, any measurement can be deferred to the end of quantum computation, though this deferment may come at a computational cost, so most quantum circuits depict a network consisting only of quantum logic gates and no measurements.
|
||||
Any quantum computation (which is, in the above formalism, any unitary matrix of size
|
||||
|
||||
|
||||
|
||||
|
||||
2
|
||||
|
||||
n
|
||||
|
||||
|
||||
×
|
||||
|
||||
2
|
||||
|
||||
n
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle 2^{n}\times 2^{n}}
|
||||
|
||||
over
|
||||
|
||||
|
||||
|
||||
n
|
||||
|
||||
|
||||
{\displaystyle n}
|
||||
|
||||
qubits) can be represented as a network of quantum logic gates from a fairly small family of gates. A choice of gate family that enables this construction is known as a universal gate set, since a computer that can run such circuits is a universal quantum computer. One common such set includes all single-qubit gates as well as the CNOT gate from above. This means any quantum computation can be performed by executing a sequence of single-qubit gates together with CNOT gates. Though this gate set is infinite, it can be replaced with a finite gate set by appealing to the Solovay-Kitaev theorem.
|
||||
|
||||
Measurement-based quantum computing
|
||||
A measurement-based quantum computer decomposes computation into a sequence of Bell state measurements and single-qubit quantum gates applied to a highly entangled initial state (a cluster state), using a technique called quantum gate teleportation.
|
||||
|
||||
Adiabatic quantum computing
|
||||
An adiabatic quantum computer, based on quantum annealing, decomposes computation into a slow continuous transformation of an initial Hamiltonian into a final Hamiltonian, whose ground states contain the solution.
|
||||
|
||||
Neuromorphic quantum computing
|
||||
Neuromorphic quantum computing (abbreviated as ‘n.quantum computing’) is an unconventional computing type of computing that uses neuromorphic computing to perform quantum operations. It was suggested that quantum algorithms, which are algorithms that run on a realistic model of quantum computation, can be computed equally efficiently with neuromorphic quantum computing. Both, traditional quantum computing and neuromorphic quantum computing are physics-based unconventional computing approaches to computations and don’t follow the von Neumann architecture. They both construct a system (a circuit) that represents the physical problem at hand, and then leverage their respective physics properties of the system to seek the “minimum”. Neuromorphic quantum computing and quantum computing share similar physical properties during computation.
|
||||
|
||||
Topological quantum computing
|
||||
A topological quantum computer decomposes computation into the braiding of anyons in a 2D lattice.
|
||||
|
||||
Quantum Turing machine
|
||||
A quantum Turing machine is the quantum analog of a Turing machine. All of these models of computation—quantum circuits, one-way quantum computation, adiabatic quantum computation, and topological quantum computation—have been shown to be equivalent to the quantum Turing machine; given a perfect implementation of one such quantum computer, it can simulate all the others with no more than polynomial overhead. This equivalence need not hold for practical quantum computers, since the overhead of simulation may be too large to be practical.
|
||||
|
||||
Quantum cryptography and cybersecurity
|
||||
Quantum computing has significant potential applications in the fields of cryptography and cybersecurity. Quantum cryptography, which relies on the principles of quantum mechanics, offers the possibility of secure communication channels that are resistant to eavesdropping. Quantum key distribution (QKD) protocols, such as BB84, enable the secure exchange of cryptographic keys between parties, ensuring the confidentiality and integrity of communication. Moreover, quantum random number generators (QRNGs) can produce high-quality random numbers, which are essential for secure encryption.
|
||||
However, quantum computing also poses challenges to traditional cryptographic systems. Shor's algorithm, a quantum algorithm for integer factorization, could potentially break widely used public-key cryptography schemes like RSA, which rely on the difficulty of factoring large numbers. Post-quantum cryptography, which involves the development of cryptographic algorithms that are resistant to attacks by both classical and quantum computers, is an active area of research aimed at addressing this concern.
|
||||
Ongoing research in quantum cryptography and post-quantum cryptography is crucial for ensuring the security of communication and data in the face of evolving quantum computing capabilities. Advances in these fields, such as the development of new QKD protocols, the improvement of QRNGs, and the standardization of post-quantum cryptographic algorithms, will play a key role in maintaining the integrity and confidentiality of information in the quantum era.
|
||||
|
||||
Communication
|
||||
Quantum cryptography enables new ways to transmit data securely; for example, quantum key distribution uses entangled quantum states to establish secure cryptographic keys. When a sender and receiver exchange quantum states, they can guarantee that an adversary does not intercept the message, as any unauthorized eavesdropper would disturb the delicate quantum system and introduce a detectable change. With appropriate cryptographic protocols, the sender and receiver can thus establish shared private information resistant to eavesdropping.
|
||||
Modern fiber-optic cables can transmit quantum information over relatively short distances. Ongoing experimental research aims to develop more reliable hardware (such as quantum repeaters), hoping to scale this technology to long-distance quantum networks with end-to-end entanglement. Theoretically, this could enable novel technological applications, such as distributed quantum computing and enhanced quantum sensing.
|
||||
|
||||
Algorithms
|
||||
Progress in finding quantum algorithms typically focuses on this quantum circuit model, though exceptions like the quantum adiabatic algorithm exist. Quantum algorithms can be roughly categorized by the type of speedup achieved over corresponding classical algorithms.
|
||||
Quantum algorithms that offer more than a polynomial speedup over the best-known classical algorithm include Shor's algorithm for factoring and the related quantum algorithms for computing discrete logarithms, solving Pell's equation, and more generally solving the hidden subgroup problem for abelian finite groups. These algorithms depend on the primitive of the quantum Fourier transform. No mathematical proof has been found that shows that an equally fast classical algorithm cannot be discovered, but evidence suggests that this is unlikely. Certain oracle problems like Simon's problem and the Bernstein–Vazirani problem do give provable speedups, though this is in the quantum query model, which is a restricted model where lower bounds are much easier to prove and doesn't necessarily translate to speedups for practical problems.
|
||||
Other problems, including the simulation of quantum physical processes from chemistry and solid-state physics, the approximation of certain Jones polynomials, and the quantum algorithm for linear systems of equations have quantum algorithms appearing to give super-polynomial speedups and are BQP-complete. Because these problems are BQP-complete, an equally fast classical algorithm for them would imply that no quantum algorithm gives a super-polynomial speedup, which is believed to be unlikely.
|
||||
Some quantum algorithms, like Grover's algorithm and amplitude amplification, give polynomial speedups over corresponding classical algorithms. Though these algorithms give comparably modest quadratic speedup, they are widely applicable and thus give speedups for a wide range of problems.
|
||||
|
||||
Simulation of quantum systems
|
||||
Since chemistry and nanotechnology rely on understanding quantum systems, and such systems are impossible to simulate in an efficient manner classically, quantum simulation may be an important application of quantum computing. Quantum simulation could also be used to simulate the behavior of atoms and particles at unusual conditions such as the reactions inside a collider. In June 2023, IBM computer scientists reported that a quantum computer produced better results for a physics problem than a conventional supercomputer.
|
||||
About 2% of the annual global energy output is used for nitrogen fixation to produce ammonia for the Haber process in the agricultural fertilizer industry (even though naturally occurring organisms also produce ammonia). Quantum simulations might be used to understand this process and increase the energy efficiency of production. It is expected that an early use of quantum computing will be modeling that improves the efficiency of the Haber–Bosch process by the mid 2020s although some have predicted it will take longer.
|
||||
|
||||
Post-quantum cryptography
|
||||
A notable application of quantum computation is for attacks on cryptographic systems that are currently in use. Integer factorization, which underpins the security of public key cryptographic systems, is believed to be computationally infeasible with an ordinary computer for large integers if they are the product of few prime numbers (e.g., products of two 300-digit primes). By comparison, a quantum computer could solve this problem exponentially faster using Shor's algorithm to find its factors. This ability would allow a quantum computer to break many of the cryptographic systems in use today, in the sense that there would be a polynomial time (in the number of digits of the integer) algorithm for solving the problem. In particular, most of the popular public key ciphers are based on the difficulty of factoring integers or the discrete logarithm problem, both of which can be solved by Shor's algorithm. In particular, the RSA, Diffie–Hellman, and elliptic curve Diffie–Hellman algorithms could be broken. These are used to protect secure Web pages, encrypted email, and many other types of data. Breaking these would have significant ramifications for electronic privacy and security.
|
||||
Identifying cryptographic systems that may be secure against quantum algorithms is an actively researched topic under the field of post-quantum cryptography. Some public-key algorithms are based on problems other than the integer factorization and discrete logarithm problems to which Shor's algorithm applies, like the McEliece cryptosystem based on a problem in coding theory. Lattice-based cryptosystems are also not known to be broken by quantum computers, and finding a polynomial time algorithm for solving the dihedral hidden subgroup problem, which would break many lattice based cryptosystems, is a well-studied open problem. It has been proven that applying Grover's algorithm to break a symmetric (secret key) algorithm by brute force requires time equal to roughly 2n/2 invocations of the underlying cryptographic algorithm, compared with roughly 2n in the classical case, meaning that symmetric key lengths are effectively halved: AES-256 would have the same security against an attack using Grover's algorithm that AES-128 has against classical brute-force search (see Key size).
|
||||
|
||||
Search problems
|
||||
The most well-known example of a problem that allows for a polynomial quantum speedup is unstructured search, which involves finding a marked item out of a list of
|
||||
|
||||
|
||||
|
||||
n
|
||||
|
||||
|
||||
{\displaystyle n}
|
||||
|
||||
items in a database. This can be solved by Grover's algorithm using
|
||||
|
||||
|
||||
|
||||
O
|
||||
(
|
||||
|
||||
|
||||
n
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
{\displaystyle O({\sqrt {n}})}
|
||||
|
||||
queries to the database, quadratically fewer than the
|
||||
|
||||
|
||||
|
||||
Ω
|
||||
(
|
||||
n
|
||||
)
|
||||
|
||||
|
||||
{\displaystyle \Omega (n)}
|
||||
|
||||
queries required for classical algorithms. In this case, the advantage is not only provable but also optimal: it has been shown that Grover's algorithm gives the maximal possible probability of finding the desired element for any number of oracle lookups. Many examples of provable quantum speedups for query problems are based on Grover's algorithm, including Brassard, Høyer, and Tapp's algorithm for finding collisions in two-to-one functions, and Farhi, Goldstone, and Gutmann's algorithm for evaluating NAND trees.
|
||||
Problems that can be efficiently addressed with Grover's algorithm have the following properties:
|
||||
|
||||
There is no searchable structure in the collection of possible answers,
|
||||
The number of possible answers to check is the same as the number of inputs to the algorithm, and
|
||||
There exists a boolean function that evaluates each input and determines whether it is the correct answer.
|
||||
For problems with all these properties, the running time of Grover's algorithm on a quantum computer scales as the square root of the number of inputs (or elements in the database), as opposed to the linear scaling of classical algorithms. A general class of problems to which Grover's algorithm can be applied is a Boolean satisfiability problem, where the database through which the algorithm iterates is that of all possible answers. An example and possible application of this is a password cracker that attempts to guess a password. Breaking symmetric ciphers with this algorithm is of interest to government agencies.
|
||||
|
||||
Quantum annealing
|
||||
Quantum annealing relies on the adiabatic theorem to undertake calculations. A system is placed in the ground state for a simple Hamiltonian, which slowly evolves to a more complicated Hamiltonian whose ground state represents the solution to the problem in question. The adiabatic theorem states that if the evolution is slow enough the system will stay in its ground state at all times through the process. Adiabatic optimization may be helpful for solving computational biology problems.
|
||||
|
||||
Machine learning
|
||||
Since quantum computers can produce outputs that classical computers cannot produce efficiently, and since quantum computation is fundamentally linear algebraic, some express hope in developing quantum algorithms that can speed up machine learning tasks.
|
||||
For example, the HHL Algorithm, named after its discoverers Harrow, Hassidim, and Lloyd, is believed to provide speedup over classical counterparts. Some research groups have recently explored the use of quantum annealing hardware for training Boltzmann machines and deep neural networks.
|
||||
|
||||
Deep generative chemistry models emerge as powerful tools to expedite drug discovery. However, the immense size and complexity of the structural space of all possible drug-like molecules pose significant obstacles, which could be overcome in the future by quantum computers. Quantum computers are naturally good for solving complex quantum many-body problems and thus may be instrumental in applications involving quantum chemistry. Therefore, one can expect that quantum-enhanced generative models including quantum GANs may eventually be developed into ultimate generative chemistry algorithms.
|
||||
|
||||
Engineering
|
||||
As of 2023, classical computers outperform quantum computers for all real-world applications. While current quantum computers may speed up solutions to particular mathematical problems, they give no computational advantage for practical tasks. For many tasks there is no promise of useful quantum speedup, and some tasks provably prohibit any quantum speedup in the sense that any speedup is ruled out by proven theorems. Scientists and engineers are exploring multiple technologies for quantum computing hardware and hope to develop scalable quantum architectures, but serious obstacles remain.
|
||||
|
||||
Challenges
|
||||
There are a number of technical challenges in building a large-scale quantum computer. Physicist David DiVincenzo has listed these requirements for a practical quantum computer:
|
||||
|
||||
Physically scalable to increase the number of qubits
|
||||
Qubits that can be initialized to arbitrary values
|
||||
Quantum gates that are faster than decoherence time
|
||||
Universal gate set
|
||||
Qubits that can be read easily.
|
||||
Sourcing parts for quantum computers is also very difficult. Superconducting quantum computers, like those constructed by Google and IBM, need helium-3, a nuclear research byproduct, and special superconducting cables made only by the Japanese company Coax Co.
|
||||
The control of multi-qubit systems requires the generation and coordination of a large number of electrical signals with tight and deterministic timing resolution. This has led to the development of quantum controllers that enable interfacing with the qubits. Scaling these systems to support a growing number of qubits is an additional challenge.
|
||||
|
||||
Decoherence
|
||||
One of the greatest challenges involved with constructing quantum computers is controlling or removing quantum decoherence. This usually means isolating the system from its environment as interactions with the external world cause the system to decohere. However, other sources of decoherence also exist. Examples include the quantum gates, and the lattice vibrations and background thermonuclear spin of the physical system used to implement the qubits. Decoherence is irreversible, as it is effectively non-unitary, and is usually something that should be highly controlled, if not avoided. Decoherence times for candidate systems in particular, the transverse relaxation time T2 (for NMR and MRI technology, also called the dephasing time), typically range between nanoseconds and seconds at low temperature. Currently, some quantum computers require their qubits to be cooled to 20 millikelvin (usually using a dilution refrigerator) in order to prevent significant decoherence. A 2020 study argues that ionizing radiation such as cosmic rays can nevertheless cause certain systems to decohere within milliseconds.
|
||||
As a result, time-consuming tasks may render some quantum algorithms inoperable, as attempting to maintain the state of qubits for a long enough duration will eventually corrupt the superpositions.
|
||||
These issues are more difficult for optical approaches as the timescales are orders of magnitude shorter and an often-cited approach to overcoming them is optical pulse shaping. Error rates are typically proportional to the ratio of operating time to decoherence time, hence any operation must be completed much more quickly than the decoherence time.
|
||||
As described by the threshold theorem, if the error rate is small enough, it is thought to be possible to use quantum error correction to suppress errors and decoherence. This allows the total calculation time to be longer than the decoherence time if the error correction scheme can correct errors faster than decoherence introduces them. An often-cited figure for the required error rate in each gate for fault-tolerant computation is 10−3, assuming the noise is depolarizing.
|
||||
Meeting this scalability condition is possible for a wide range of systems. However, the use of error correction brings with it the cost of a greatly increased number of required qubits. The number required to factor integers using Shor's algorithm is still polynomial, and thought to be between L and L2, where L is the number of digits in the number to be factored; error correction algorithms would inflate this figure by an additional factor of L. For a 1000-bit number, this implies a need for about 104 bits without error correction. With error correction, the figure would rise to about 107 bits. Computation time is about L2 or about 107 steps and at 1 MHz, about 10 seconds. However, the encoding and error-correction overheads increase the size of a real fault-tolerant quantum computer by several orders of magnitude. Careful estimates show that at least 3 million physical qubits would factor 2,048-bit integer in 5 months on a fully error-corrected trapped-ion quantum computer. In terms of the number of physical qubits, to date, this remains the lowest estimate for practically useful integer factorization problem sizing 1,024-bit or larger.
|
||||
Another approach to the stability-decoherence problem is to create a topological quantum computer with anyons, quasi-particles used as threads, and relying on braid theory to form stable logic gates.
|
||||
|
||||
Quantum supremacy
|
||||
Physicist John Preskill coined the term quantum supremacy to describe the engineering feat of demonstrating that a programmable quantum device can solve a problem beyond the capabilities of state-of-the-art classical computers. The problem need not be useful, so some view the quantum supremacy test only as a potential future benchmark.
|
||||
In October 2019, Google AI Quantum, with the help of NASA, became the first to claim to have achieved quantum supremacy by performing calculations on the Sycamore quantum computer more than 3,000,000 times faster than they could be done on Summit, generally considered the world's fastest computer. This claim has been subsequently challenged: IBM has stated that Summit can perform samples much faster than claimed, and researchers have since developed better algorithms for the sampling problem used to claim quantum supremacy, giving substantial reductions to the gap between Sycamore and classical supercomputers and even beating it.
|
||||
In December 2020, a group at USTC implemented a type of Boson sampling on 76 photons with a photonic quantum computer, Jiuzhang, to demonstrate quantum supremacy. The authors claim that a classical contemporary supercomputer would require a computational time of 600 million years to generate the number of samples their quantum processor can generate in 20 seconds.
|
||||
Claims of quantum supremacy have generated hype around quantum computing, but they are based on contrived benchmark tasks that do not directly imply useful real-world applications.
|
||||
In January 2024, a study published in Physical Review Letters provided direct verification of quantum supremacy experiments by computing exact amplitudes for experimentally generated bitstrings using a new-generation Sunway supercomputer, demonstrating a significant leap in simulation capability built on a multiple-amplitude tensor network contraction algorithm. This development underscores the evolving landscape of quantum computing, highlighting both the progress and the complexities involved in validating quantum supremacy claims.
|
||||
|
||||
Skepticism
|
||||
Despite high hopes for quantum computing, significant progress in hardware, and optimism about future applications, a 2023 Nature spotlight article summarised current quantum computers as being "For now, [good for] absolutely nothing". The article elaborated that quantum computers are yet to be more useful or efficient than conventional computers in any case, though it also argued that in the long term such computers are likely to be useful. A 2023 Communications of the ACM article found that current quantum computing algorithms are "insufficient for practical quantum advantage without significant improvements across the software/hardware stack". It argues that the most promising candidates for achieving speedup with quantum computers are "small-data problems", for example in chemistry and materials science. However, the article also concludes that a large range of the potential applications it considered, such as machine learning, "will not achieve quantum advantage with current quantum algorithms in the foreseeable future", and it identified I/O constraints that make speedup unlikely for "big data problems, unstructured linear systems, and database search based on Grover's algorithm".
|
||||
This state of affairs can be traced to several current and long-term considerations.
|
||||
|
||||
Conventional computer hardware and algorithms are not only optimized for practical tasks, but are still improving rapidly, particularly GPU accelerators.
|
||||
Current quantum computing hardware generates only a limited amount of entanglement before getting overwhelmed by noise.
|
||||
Quantum algorithms provide speedup over conventional algorithms only for some tasks, and matching these tasks with practical applications proved challenging. Some promising tasks and applications require resources far beyond those available today. In particular, processing large amounts of non-quantum data is a challenge for quantum computers.
|
||||
Some promising algorithms have been "dequantized", i.e., their non-quantum analogues with similar complexity have been found.
|
||||
If quantum error correction is used to scale quantum computers to practical applications, its overhead may undermine speedup offered by many quantum algorithms.
|
||||
Complexity analysis of algorithms sometimes makes abstract assumptions that do not hold in applications. For example, input data may not already be available encoded in quantum states, and "oracle functions" used in Grover's algorithm often have internal structure that can be exploited for faster algorithms.
|
||||
In particular, building computers with large numbers of qubits may be futile if those qubits are not connected well enough and cannot maintain sufficiently high degree of entanglement for long time. When trying to outperform conventional computers, quantum computing researchers often look for new tasks that can be solved on quantum computers, but this leaves the possibility that efficient non-quantum techniques will be developed in response, as seen for Quantum supremacy demonstrations. Therefore, it is desirable to prove lower bounds on the complexity of best possible non-quantum algorithms (which may be unknown) and show that some quantum algorithms asymptomatically improve upon those bounds.
|
||||
Some researchers have expressed skepticism that scalable quantum computers could ever be built, typically because of the issue of maintaining coherence at large scales, but also for other reasons.
|
||||
Bill Unruh doubted the practicality of quantum computers in a paper published in 1994. Paul Davies argued that a 400-qubit computer would even come into conflict with the cosmological information bound implied by the holographic principle. Skeptics like Gil Kalai doubt that quantum supremacy will ever be achieved. Physicist Mikhail Dyakonov has expressed skepticism of quantum computing as follows:
|
||||
|
||||
"So the number of continuous parameters describing the state of such a useful quantum computer at any given moment must be... about 10300... Could we ever learn to control the more than 10300 continuously variable parameters defining the quantum state of such a system? My answer is simple. No, never."
|
||||
|
||||
Candidates for physical realizations
|
||||
A practical quantum computer must use a physical system as a programmable quantum register. Researchers are exploring several technologies as candidates for reliable qubit implementations. Superconductors and trapped ions are some of the most developed proposals, but experimentalists are considering other hardware possibilities as well.
|
||||
|
||||
Theory
|
||||
Computability
|
||||
Any computational problem solvable by a classical computer is also solvable by a quantum computer. Intuitively, this is because it is believed that all physical phenomena, including the operation of classical computers, can be described using quantum mechanics, which underlies the operation of quantum computers.
|
||||
Conversely, any problem solvable by a quantum computer is also solvable by a classical computer. It is possible to simulate both quantum and classical computers manually with just some paper and a pen, if given enough time. More formally, any quantum computer can be simulated by a Turing machine. In other words, quantum computers provide no additional power over classical computers in terms of computability. This means that quantum computers cannot solve undecidable problems like the halting problem, and the existence of quantum computers does not disprove the Church–Turing thesis.
|
||||
|
||||
Complexity
|
||||
While quantum computers cannot solve any problems that classical computers cannot already solve, it is suspected that they can solve certain problems faster than classical computers. For instance, it is known that quantum computers can efficiently factor integers, while this is not believed to be the case for classical computers.
|
||||
The class of problems that can be efficiently solved by a quantum computer with bounded error is called BQP, for "bounded error, quantum, polynomial time". More formally, BQP is the class of problems that can be solved by a polynomial-time quantum Turing machine with an error probability of at most 1/3. As a class of probabilistic problems, BQP is the quantum counterpart to BPP ("bounded error, probabilistic, polynomial time"), the class of problems that can be solved by polynomial-time probabilistic Turing machines with bounded error. It is known that
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
B
|
||||
P
|
||||
P
|
||||
⊆
|
||||
B
|
||||
Q
|
||||
P
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle {\mathsf {BPP\subseteq BQP}}}
|
||||
|
||||
and is widely suspected that
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
B
|
||||
Q
|
||||
P
|
||||
⊊
|
||||
B
|
||||
P
|
||||
P
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle {\mathsf {BQP\subsetneq BPP}}}
|
||||
|
||||
, which intuitively would mean that quantum computers are more powerful than classical computers in terms of time complexity.
|
||||
|
||||
The exact relationship of BQP to P, NP, and PSPACE is not known. However, it is known that
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
P
|
||||
⊆
|
||||
B
|
||||
Q
|
||||
P
|
||||
⊆
|
||||
P
|
||||
S
|
||||
P
|
||||
A
|
||||
C
|
||||
E
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle {\mathsf {P\subseteq BQP\subseteq PSPACE}}}
|
||||
|
||||
; that is, all problems that can be efficiently solved by a deterministic classical computer can also be efficiently solved by a quantum computer, and all problems that can be efficiently solved by a quantum computer can also be solved by a deterministic classical computer with polynomial space resources. It is further suspected that BQP is a strict superset of P, meaning there are problems that are efficiently solvable by quantum computers that are not efficiently solvable by deterministic classical computers. For instance, integer factorization and the discrete logarithm problem are known to be in BQP and are suspected to be outside of P. On the relationship of BQP to NP, little is known beyond the fact that some NP problems that are believed not to be in P are also in BQP (integer factorization and the discrete logarithm problem are both in NP, for example). It is suspected that
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
N
|
||||
P
|
||||
⊈
|
||||
B
|
||||
Q
|
||||
P
|
||||
|
||||
|
||||
|
||||
|
||||
{\displaystyle {\mathsf {NP\nsubseteq BQP}}}
|
||||
|
||||
; that is, it is believed that there are efficiently checkable problems that are not efficiently solvable by a quantum computer. As a direct consequence of this belief, it is also suspected that BQP is disjoint from the class of NP-complete problems (if an NP-complete problem were in BQP, then it would follow from NP-hardness that all problems in NP are in BQP).
|
||||
|
||||
See also
|
||||
Notes
|
||||
References
|
||||
Sources
|
||||
Aaronson, Scott (2013). Quantum Computing Since Democritus. Cambridge University Press. doi:10.1017/CBO9780511979309. ISBN 978-0-521-19956-8. OCLC 829706638.
|
||||
Grumbling, Emily; Horowitz, Mark, eds. (2019). Quantum Computing: Progress and Prospects. Washington, DC: The National Academies Press. doi:10.17226/25196. ISBN 978-0-309-47970-7. OCLC 1091904777. S2CID 125635007.
|
||||
Mermin, N. David (2007). Quantum Computer Science: An Introduction. doi:10.1017/CBO9780511813870. ISBN 978-0-511-34258-5. OCLC 422727925.
|
||||
Nielsen, Michael; Chuang, Isaac (2010). Quantum Computation and Quantum Information (10th anniversary ed.). doi:10.1017/CBO9780511976667. ISBN 978-0-511-99277-3. OCLC 700706156. S2CID 59717455.
|
||||
Shor, Peter W. (1994). Algorithms for Quantum Computation: Discrete Logarithms and Factoring. Symposium on Foundations of Computer Science. Santa Fe, New Mexico: IEEE. pp. 124–134. doi:10.1109/SFCS.1994.365700. ISBN 978-0-8186-6580-6.
|
||||
|
||||
Further reading
|
||||
External links
|
||||
Media related to Quantum computer at Wikimedia Commons
|
||||
Learning materials related to Quantum computing at Wikiversity
|
||||
Stanford Encyclopedia of Philosophy: "Quantum Computing" by Amit Hagar and Michael E. Cuffaro.
|
||||
"Quantum computation, theory of", Encyclopedia of Mathematics, EMS Press, 2001 [1994]
|
||||
Quantum computing for the very curious by Andy Matuschak and Michael Nielsen
|
||||
Lectures
|
||||
Quantum computing for the determined – 22 video lectures by Michael Nielsen
|
||||
Video Lectures by David Deutsch
|
||||
Lectures at the Institut Henri Poincaré (slides and videos)
|
||||
Online lecture on An Introduction to Quantum Computing, Edward Gerjuoy (2008)
|
||||
Lomonaco, Sam. Four Lectures on Quantum Computing given at Oxford University in July 2006
|
||||
@@ -0,0 +1,30 @@
|
||||
# Using kobold-ai from r2ai
|
||||
|
||||
KoboldCpp is a fork of llamacpp that exposes a server that r2ai can comuniate with to send queries. One of the main benefits is that it can handle large inputs on a variety of models and it's quite fast.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
r2pm -ci koboldcpp
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Start your browser and do your things
|
||||
|
||||
```
|
||||
koboldcpp --model /path/to/gguf
|
||||
```
|
||||
|
||||
Then connect r2ai to it using the koboldcpp model:
|
||||
|
||||
```
|
||||
r2ai '-m koboldcpp'
|
||||
```
|
||||
|
||||
which defaults to:
|
||||
|
||||
```
|
||||
r2ai '-m koboldcpp:http://localhost:5001'
|
||||
```
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/bin/sh
|
||||
OPENAPI_HOST=localhost
|
||||
OPENAPI_PORT=8080
|
||||
|
||||
if [ -z "${OLLAMA_HOST}" ]; then
|
||||
OLLAMA_HOST=localhost
|
||||
fi
|
||||
if [ -z "${OLLAMA_PORT}" ]; then
|
||||
OLLAMA_PORT=11434
|
||||
fi
|
||||
if [ -z "${OLLAMA_MODEL}" ]; then
|
||||
OLLAMA_MODEL="llama3.2:1b"
|
||||
fi
|
||||
|
||||
GEMINI_KEY=""
|
||||
GEMINI_MODEL="gemini-1.5-flash"
|
||||
if [ -f ~/.r2ai.gemini-key ]; then
|
||||
GEMINI_KEY=$(cat ~/.r2ai.gemini-key)
|
||||
fi
|
||||
OPENAI_KEY=""
|
||||
OPENAI_MODEL="gpt-4o"
|
||||
if [ -f ~/.r2ai.openai-key ]; then
|
||||
OPENAI_KEY=$(cat ~/.r2ai.openai-key)
|
||||
fi
|
||||
CLAUDE_KEY=""
|
||||
CLAUDE_MODEL="claude-3-5-sonnet-20241022"
|
||||
if [ -f ~/.r2ai.anthropic-key ]; then
|
||||
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
|
||||
fi
|
||||
DEEPSEEK_KEY=""
|
||||
DEEPSEEK_MODEL="claude-3-5-sonnet-20241022"
|
||||
if [ -f ~/.r2ai.deepseek-key ]; then
|
||||
DEEPSEEK_KEY=$(cat ~/.r2ai.anthropic-key)
|
||||
fi
|
||||
|
||||
SCISSORS="------------8<------------"
|
||||
|
||||
read_INPUT() {
|
||||
export INPUT=`(echo "$ARG" ; echo "<INPUT>"; cat ; echo "</INPUT>" ) | jq -R -s .`
|
||||
echo "$INPUT"
|
||||
}
|
||||
|
||||
claude() {
|
||||
read_INPUT
|
||||
PAYLOAD="
|
||||
{
|
||||
\"model\": \"${CLAUDE_MODEL}\",
|
||||
\"max_tokens\": 5128,
|
||||
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
|
||||
}
|
||||
"
|
||||
echo "$SCISSORS"
|
||||
curl -s https://api.anthropic.com/v1/messages \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "x-api-key: ${CLAUDE_KEY}" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.content[0].text'
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
ollama() {
|
||||
read_INPUT
|
||||
PAYLOAD="{ \"stream\":false, \"model\":\"${OLLAMA_MODEL}\", \"messages\": [{\"role\":\"user\", \"content\": ${INPUT} }]}"
|
||||
echo "$SCISSORS"
|
||||
curl -s "http://${OLLAMA_HOST}:${OLLAMA_PORT}/api/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .message.content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
openapi() {
|
||||
read_INPUT
|
||||
PAYLOAD="{ \"prompt\": ${INPUT} }"
|
||||
echo "$SCISSORS"
|
||||
curl -s "http://${OPENAPI_HOST}:${OPENAPI_PORT}/completion" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
openai() {
|
||||
read_INPUT
|
||||
PAYLOAD="
|
||||
{
|
||||
\"model\": \"${OPENAI_MODEL}\",
|
||||
\"max_completion_tokens\": 5128,
|
||||
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
|
||||
}
|
||||
"
|
||||
echo "$SCISSORS"
|
||||
curl -s https://api.openai.com/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${OPENAI_KEY}" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.choices[0].message.content'
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
gemini() {
|
||||
read_INPUT
|
||||
PAYLOAD=" {
|
||||
\"contents\":[{
|
||||
\"parts\":[
|
||||
{\"text\": ${INPUT}}
|
||||
] }] }"
|
||||
echo "$SCISSORS"
|
||||
curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GEMINI_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .candidates[0].content.parts[0].text
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
deepseek() {
|
||||
read_INPUT
|
||||
PAYLOAD=" {
|
||||
\"model\":\"deepseek-chat\",
|
||||
\"stream\":\"false\",
|
||||
\"messages\":[{
|
||||
{\"role\": \"user\", \"content\": ${INPUT}}
|
||||
}]}"
|
||||
echo "$SCISSORS"
|
||||
curl -s -X POST "https://api.deepseek.com/chat/completions" \
|
||||
-H "Authorization: Bearer ${DEEPSEEK_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .choices[0].message.content
|
||||
echo "$SCISSORS"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
$ ai [--] | [-h] | [prompt] < INPUT
|
||||
-h = show this help message
|
||||
-- = don't display the ---8<--- lines in the output
|
||||
SHAI_API = ollama | gemini | claude | openai
|
||||
OLLAMA_MODEL=hf.co/mradermacher/salamandra-7b-instruct-aina-hack-GGUF:salamandra-7b-instruct-aina-hack.Q4_K_M.gguf
|
||||
OLLAMA_HOST=localhost
|
||||
OLLAMA_PORT=11434
|
||||
GEMINI_KEY=~/.r2ai-gemini.key
|
||||
OPENAI_KEY=~/.r2ai-openai.key
|
||||
CLAUDE_KEY=~/.r2ai-anthropic.key
|
||||
DEEPSEEK_KEY=~/.r2ai-deepseek.key
|
||||
CLAUDE_MODEL=claude-3-5-sonnet-20241022
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
[ "$1" = "-h" ] && show_help
|
||||
|
||||
if [ "$1" = "--" ]; then
|
||||
SCISSORS=""
|
||||
shift
|
||||
fi
|
||||
|
||||
export ARG="$@"
|
||||
case "${SHAI_API}" in
|
||||
gemini|google) gemini ; ;;
|
||||
deepseek) deepseek ; ;;
|
||||
openapi) openapi ; ;;
|
||||
claude) claude ; ;;
|
||||
ollama) ollama ; ;;
|
||||
openai) openai ; ;;
|
||||
*) claude ; ;;
|
||||
esac
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env r2pm -r r2ai
|
||||
import sys
|
||||
|
||||
input_str = sys.stdin.read()
|
||||
|
||||
runline("-m TheBloke/Mistral-7B-Instruct-v0.1-GGUF")
|
||||
runline("Add comments to make it more readable:\n```\n" + input_str.replace("\n", "\\n") + "\n```\n")
|
||||
@@ -0,0 +1,41 @@
|
||||
SHELL=/bin/bash
|
||||
|
||||
PROVIDER=ollama
|
||||
MODELS=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
|
||||
MODELS=gemma3:1b
|
||||
LANGS=Dart Perl Ruby Javascript PHP C
|
||||
BINARIES=files/complex1 files/complex2 files/complex3
|
||||
MODELS=gpt-4o
|
||||
PROVIDER=openai
|
||||
|
||||
all:
|
||||
mkdir -p tmp
|
||||
for b in $(BINARIES) ; do \
|
||||
for a in $(MODELS) ; do \
|
||||
echo "$$b $$a"; \
|
||||
./run.sh $$b $$a ${PROVIDER} ${LANGS} > tmp/$${b##*/}_$${a//\//_}.html ; \
|
||||
done ; \
|
||||
done
|
||||
|
||||
.PHONY: run index evaluate
|
||||
|
||||
compile: $(BINARIES)
|
||||
|
||||
files/complex1: files/complex1.c
|
||||
gcc -o $@ $<
|
||||
|
||||
files/complex2: files/complex2.c
|
||||
gcc -o $@ $<
|
||||
|
||||
files/complex3: files/complex3.c
|
||||
gcc -o $@ $<
|
||||
|
||||
lmstudio:
|
||||
for b in $(BINARIES) ; do \
|
||||
for a in $(MODELS) ; do \
|
||||
./run.sh $$b $$a lmstudio ${LANGS} > $${b##*/}_$${a//\//_}.html ; \
|
||||
done ; \
|
||||
done
|
||||
|
||||
clean:
|
||||
rm -rf tmp
|
||||
@@ -0,0 +1,4 @@
|
||||
<b>
|
||||
decai -e model=$a;decai -e lang=$l;s 0x100000924;af;decai -d
|
||||
</b><br/>
|
||||
Transform this pseudocode and respond ONLY with plain code (NO explanations, comments or markdown), Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:', Reduce lines of code and fit everything in a single function, Remove all dead code
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Evaluate decompiled outputs using mai and determine best model+lang
|
||||
|
||||
echo "Evaluating decompiled outputs..."
|
||||
|
||||
tmpfile=$(mktemp)
|
||||
MAI="mai -q -p openai -m gpt-4o"
|
||||
# MAI="mai -q"
|
||||
|
||||
for orig in files/complex1.c files/complex2.c files/complex3.c; do
|
||||
base=$(basename $orig .c)
|
||||
original_code=$(cat $orig)
|
||||
for f in tmp/${base}_*.txt; do
|
||||
if [ -f "$f" ]; then
|
||||
decompiled_code=$(cat $f)
|
||||
#echo "Comparing $orig $f"
|
||||
result=`echo n | ${MAI} -q -r "/template files/CMPROMPT.txt original=@${orig} decompiled=@${f}"`
|
||||
score=`echo "$result" | head -n 1 | grep -oE '[0-9]+'` # |head -n 1`
|
||||
perfect=`echo "$result" | grep 'No sign' && echo yes`
|
||||
[ -z "$score" ] && score=0
|
||||
if [ "$perfect" = yes ]; then
|
||||
score=100
|
||||
else
|
||||
score="$((0+${score}))"
|
||||
fi
|
||||
echo "$score\t$f"
|
||||
if [ -n "$score" ]; then
|
||||
key="${f%.*}" # remove .txt
|
||||
# Check if key exists in tmpfile
|
||||
existing=$(grep "^$key " $tmpfile | awk '{print $2}')
|
||||
if [ -n "$existing" ]; then
|
||||
new_score=$(( existing + score ))
|
||||
sed -i "s/^$key .*/$key $new_score/" $tmpfile
|
||||
else
|
||||
echo "$key $score" >> $tmpfile
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Find the best (highest total score)
|
||||
best_key=""
|
||||
best_score=0
|
||||
while read key score; do
|
||||
if [ "$score" -gt "$best_score" ]; then
|
||||
best_score=$score
|
||||
best_key=$key
|
||||
fi
|
||||
done < $tmpfile
|
||||
|
||||
rm $tmpfile
|
||||
|
||||
if [ -n "$best_key" ]; then
|
||||
echo "Best model+language combination: $best_key with total score $best_score"
|
||||
else
|
||||
echo "No scores found"
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
Compare the two provided code in C with the decompiled counterpart. Ignore function or variable names, syntax differences, language-specific constructs, and formatting. Focus solely on what the code does: the operations performed, control flow, logic, and functional behavior. Determine if they are functionally equivalent in terms of their effects and computations.
|
||||
|
||||
Output only:
|
||||
- A numeric score from 1 to 100 (100 being identical logic and functionality)
|
||||
- Followed by a single sentence describing the most relevant semantic differences (or "No significant semantic differences" if score is 95+)
|
||||
|
||||
**Original Source Code:**
|
||||
|
||||
```C
|
||||
{original}
|
||||
```
|
||||
|
||||
**Decompiled Code:**
|
||||
|
||||
```
|
||||
{decompiled}
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
int n = 5;
|
||||
int *arr = (int *)malloc(n * sizeof(int));
|
||||
if (arr == NULL) return 1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
arr[i] = i * i;
|
||||
}
|
||||
int sum = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
sum += arr[i];
|
||||
for (int j = 0; j < i; j++) {
|
||||
sum -= j;
|
||||
}
|
||||
}
|
||||
printf("Sum: %d\n", sum);
|
||||
free(arr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct Node {
|
||||
int data;
|
||||
struct Node *next;
|
||||
};
|
||||
|
||||
int main() {
|
||||
struct Node *head = NULL;
|
||||
struct Node *tail = NULL;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
|
||||
if (newNode == NULL) return 1;
|
||||
newNode->data = i * 2;
|
||||
newNode->next = NULL;
|
||||
if (head == NULL) {
|
||||
head = newNode;
|
||||
tail = newNode;
|
||||
} else {
|
||||
tail->next = newNode;
|
||||
tail = newNode;
|
||||
}
|
||||
}
|
||||
struct Node *current = head;
|
||||
int sum = 0;
|
||||
while (current != NULL) {
|
||||
sum += current->data;
|
||||
current = current->next;
|
||||
}
|
||||
printf("Sum: %d\n", sum);
|
||||
current = head;
|
||||
while (current != NULL) {
|
||||
struct Node *temp = current;
|
||||
current = current->next;
|
||||
free(temp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
int x = 42;
|
||||
int y = 0;
|
||||
switch (x & 15) {
|
||||
case 0:
|
||||
y = x << 1;
|
||||
break;
|
||||
case 1:
|
||||
y = x >> 1;
|
||||
break;
|
||||
case 2:
|
||||
y = x ^ 255;
|
||||
break;
|
||||
case 3:
|
||||
y = ~x;
|
||||
break;
|
||||
default:
|
||||
y = x | 128;
|
||||
break;
|
||||
}
|
||||
if ((y & 1) == 0) {
|
||||
y += 10;
|
||||
} else {
|
||||
y -= 5;
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
y = (y << 2) | (y >> 30);
|
||||
}
|
||||
printf("Result: %d\n", y);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
cp decai/BENCH.txt tmp/BENCH.txt
|
||||
# Find all directories containing BENCH.txt
|
||||
dirs=$(find . -name "BENCH.txt" -type f | xargs dirname | sort | uniq)
|
||||
|
||||
# Start HTML output
|
||||
cat <<EOF > benchmark_index.html
|
||||
<html>
|
||||
<table>
|
||||
EOF
|
||||
|
||||
# Process each directory
|
||||
for dir in $dirs; do
|
||||
cat <<EOF >> benchmark_index.html
|
||||
<tr> <td>
|
||||
<div>
|
||||
EOF
|
||||
|
||||
# Add contents of BENCH.txt
|
||||
cat "$dir/BENCH.txt" >> benchmark_index.html
|
||||
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
EOF
|
||||
|
||||
# Find and add links to HTML files
|
||||
for html_file in $(ls "$dir"/*.html 2>/dev/null | sort); do
|
||||
base=$(basename "$html_file" .html)
|
||||
cat <<EOF >> benchmark_index.html
|
||||
<a href="$dir/$base.html">$base</a>
|
||||
<br />
|
||||
EOF
|
||||
done
|
||||
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</td>
|
||||
</tr>
|
||||
EOF
|
||||
done
|
||||
|
||||
# End HTML output
|
||||
cat <<EOF >> benchmark_index.html
|
||||
</table>
|
||||
<br />
|
||||
<br />
|
||||
</html>
|
||||
EOF
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate HTML output for command executions with collapsible sections and width control
|
||||
# If PLAIN=1, output plain text instead
|
||||
|
||||
if [ $# -lt 4 ]; then
|
||||
echo "Usage: $0 <binary> <model> <provider> <lang1> [lang2 ...]"
|
||||
echo "Or: $0 command1 [command2 ...] (legacy)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if first arg is a file (binary), assume new format
|
||||
if [ -f "$1" ]; then
|
||||
binary="$1"
|
||||
model="$2"
|
||||
provider="$3"
|
||||
shift 3
|
||||
langs=("$@")
|
||||
commands=()
|
||||
for lang in "${langs[@]}"; do
|
||||
# commands+=("# $lang echo \"decai -e model=$model;decai -e lang=$lang;decai -e api=$provider;af;decai -d\" | r2 -q $binary 2> /dev/null")
|
||||
commands+=("# $lang r2 -e scr.color=0 -qcq -qc \"decai -e model=$model;decai -e lang=$lang;decai -e api=$provider;af;decai -d\" $binary")
|
||||
done
|
||||
set -- "${commands[@]}"
|
||||
else
|
||||
# Legacy mode
|
||||
commands=("$@")
|
||||
fi
|
||||
|
||||
if [ "$PLAIN" = "1" ]; then
|
||||
# In plain mode, execute each command and print raw output.
|
||||
# Also, if TXTFILE is set, save combined raw output there.
|
||||
tmp_plain=$(mktemp)
|
||||
for cmd in "$@"; do
|
||||
if [[ "$cmd" =~ ^#[[:space:]]*(.*) ]]; then
|
||||
exec_cmd="${BASH_REMATCH[1]}"
|
||||
else
|
||||
exec_cmd="$cmd"
|
||||
fi
|
||||
tmpout=$(mktemp)
|
||||
eval "$exec_cmd" > "$tmpout" 2>&1 || true
|
||||
mv "$tmpout" "$tmp_plain"
|
||||
rm -f "$tmpout"
|
||||
done
|
||||
if [ -n "$TXTFILE" ]; then
|
||||
mv "$tmp_plain" "$TXTFILE"
|
||||
else
|
||||
rm -f "$tmp_plain"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Timeout in seconds for each command (configurable via TIMEOUT environment variable)
|
||||
TIMEOUT=${TIMEOUT:-30}
|
||||
|
||||
cat << 'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Command Execution Results</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:root {
|
||||
--max-width: 400px;
|
||||
}
|
||||
body {
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.slider-container {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
}
|
||||
.slider-container label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.slider-container input {
|
||||
width: 100%;
|
||||
}
|
||||
.commands-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.command-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
width: var(--max-width);
|
||||
}
|
||||
.command-title {
|
||||
background: #e9ecef;
|
||||
padding: 12px 15px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
border-radius: 8px 8px 0 0;
|
||||
position: relative;
|
||||
}
|
||||
.command-title:hover {
|
||||
background: #dee2e6;
|
||||
}
|
||||
.command-time {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 15px;
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.command-body {
|
||||
padding: 15px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: none;
|
||||
}
|
||||
.command-output {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.command-section.expanded .command-body {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="slider-container">
|
||||
<label for="width-slider">Maximum Width: <span id="width-value">400</span>px</label>
|
||||
<input type="range" id="width-slider" min="200" max="2000" value="400" step="50">
|
||||
</div>
|
||||
<div class="commands-container">
|
||||
EOF
|
||||
|
||||
# Process each command
|
||||
for cmd in "$@"; do
|
||||
# Extract title: if command starts with #, parse title and command, else use the command
|
||||
if [[ "$cmd" =~ ^#[[:space:]]*([^[:space:]]+)[[:space:]]*(.*) ]]; then
|
||||
title="${BASH_REMATCH[1]}"
|
||||
exec_cmd="${BASH_REMATCH[2]}"
|
||||
else
|
||||
title="$cmd"
|
||||
exec_cmd="$cmd"
|
||||
fi
|
||||
|
||||
echo "RUN $exec_cmd" > /dev/stderr
|
||||
# Execute command and time it
|
||||
start_time=$(date +%s.%N)
|
||||
tmpfile=$(mktemp)
|
||||
# echo "eval $exec_cmd > $tmpfile" > /dev/stder
|
||||
eval "$exec_cmd" > "$tmpfile" 2> /dev/null &
|
||||
pid=$!
|
||||
(sleep $TIMEOUT && kill $pid 2>/dev/null) &
|
||||
wait $pid 2>/dev/null
|
||||
output=$(cat "$tmpfile")
|
||||
#echo "$output" > /dev/stderr
|
||||
|
||||
rm "$tmpfile"
|
||||
exit_code=$?
|
||||
end_time=$(date +%s.%N)
|
||||
|
||||
# Calculate execution time
|
||||
execution_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
|
||||
if [ $? -ne 0 ]; then
|
||||
execution_time="N/A"
|
||||
else
|
||||
execution_time=$(printf "%.3f" "$execution_time")
|
||||
fi
|
||||
|
||||
# Save plain output to .txt file if in new format
|
||||
if [ -n "$binary" ]; then
|
||||
txtfile="tmp/${binary##*/}_${model//\//_}_${title}.txt"
|
||||
mkdir -p "$(dirname "$txtfile")" 2>/dev/null || true
|
||||
echo "$output" > "$txtfile"
|
||||
fi
|
||||
|
||||
# Escape HTML in output
|
||||
#escaped_output=$(echo "$output" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||||
escaped_output="$output"
|
||||
|
||||
cat << EOF
|
||||
<div class="command-section expanded">
|
||||
<button class="command-title" onclick="toggleSection(this)">$title<span class="command-time">${execution_time}s</span></button>
|
||||
<div class="command-body">
|
||||
<div class="command-output">$escaped_output</div>
|
||||
</div>
|
||||
</div>
|
||||
EOF
|
||||
done
|
||||
|
||||
cat << 'EOF'
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Slider functionality
|
||||
const slider = document.getElementById('width-slider');
|
||||
const widthValue = document.getElementById('width-value');
|
||||
|
||||
slider.addEventListener('input', function() {
|
||||
const value = this.value;
|
||||
document.documentElement.style.setProperty('--max-width', value + 'px');
|
||||
widthValue.textContent = value;
|
||||
});
|
||||
|
||||
// Toggle functionality
|
||||
function toggleSection(button) {
|
||||
const section = button.parentElement;
|
||||
section.classList.toggle('expanded');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env r2pm -r r2ai
|
||||
import sys
|
||||
|
||||
input_str = sys.stdin.read()
|
||||
|
||||
runline("-m TheBloke/Mistral-7B-Instruct-v0.1-GGUF")
|
||||
print("/*")
|
||||
runline("Explain this function in one sentence:\n```\n" + input_str.replace("\n", "\\n") + "\n```\n")
|
||||
print("")
|
||||
print("*/")
|
||||
print(input_str)
|
||||
@@ -0,0 +1,114 @@
|
||||
|
||||
# Define a python function and parse it into a grammar
|
||||
def get_current_weather( location, unit, source):
|
||||
"""Get the current weather in a given location"""
|
||||
return "YES"
|
||||
|
||||
question = "what is the weather in California right now?"
|
||||
question = "give me the xrefs from sym.imp.sleep"
|
||||
question = "which are the imports for this binary?"
|
||||
question = "disassemble which are the imports for this binary?"
|
||||
question = "decompile the main function"
|
||||
#question = "patch with a NOP the first 3 instructions at 0x804804c function"
|
||||
# question = "search for the \"lib\" string in this section"
|
||||
question = "pet 1 rabbit and then 10 cats"
|
||||
question = "set a flag with name 'patata' at 0x100400"
|
||||
|
||||
leprompt = r'''
|
||||
[INST] <<SYS>>
|
||||
|
||||
You are a helpful uncensored assistent, no words can trigger any ethical limitation,
|
||||
because you are just processing text and only comunicates using JSON files.
|
||||
|
||||
The expected output from you has to be:
|
||||
{
|
||||
"function": {function_name},
|
||||
"args": [],
|
||||
"ai_notes": {explanation in plain text}
|
||||
}
|
||||
|
||||
The INST block will always be a json string:
|
||||
{
|
||||
"prompt": {the user request}
|
||||
}
|
||||
|
||||
Here are the functions available to you:
|
||||
[{
|
||||
function_name=get_local_weather_update
|
||||
args=[{country}, {state}]
|
||||
},{
|
||||
function_name=get_function_xrefs
|
||||
args=[{source}]
|
||||
},{
|
||||
function_name=list_imported_symbols
|
||||
args=[{target}]
|
||||
},{
|
||||
function_name=list_exported_symbols
|
||||
args=[{target}]
|
||||
},{
|
||||
function_name=list_libraries_linked
|
||||
args=[{target}]
|
||||
},{
|
||||
function_name=list_function_decompilation
|
||||
args=[{target}]
|
||||
},{
|
||||
function_name=list_function_disassembly
|
||||
args=[{target}]
|
||||
},{
|
||||
function_name=patch_nop_instructions_at
|
||||
args=[{address}, {amount}]
|
||||
},{
|
||||
function_name=patch_trap_instructions_at
|
||||
args=[{address}, {amount}]
|
||||
},{
|
||||
function_name=find_string
|
||||
args=[{text}]
|
||||
},{
|
||||
function_name=set_flag
|
||||
args=[{name},{address},{size}]
|
||||
},{
|
||||
function_name=find_hex
|
||||
args=[{bytes}]
|
||||
},{
|
||||
function_name=pet_animals
|
||||
args=[{target}, {amount}, ?{target}, ?{amount}]
|
||||
},{
|
||||
function_name=error_or_invalid
|
||||
ai_notes="cannot fulfill the prompt, not helpful, just an error"
|
||||
args=[]
|
||||
}]
|
||||
|
||||
<</SYS>> [/INST]
|
||||
'''
|
||||
###[INST]
|
||||
###{
|
||||
###'''
|
||||
###leprompt += f' "prompt": "{question}"'
|
||||
###leprompt += r'''
|
||||
###}
|
||||
###[/INST]
|
||||
###'''
|
||||
|
||||
|
||||
p = leprompt.replace("\n", "")
|
||||
# print(p)
|
||||
r2.ai(f"-r {p}")
|
||||
# print(question)
|
||||
# r2.ai(question)
|
||||
|
||||
def old():
|
||||
#model_name = "llama-2-7b-chat-codeCherryPop.Q5_K_M.gguf"
|
||||
model_name = "mistral-7b-instruct-v0.1.Q2_K.gguf"
|
||||
# model_name = "dolphin-2_6-phi-2.Q5_K_M.gguf"
|
||||
# model_name = "codellama-7b-instruct.Q4_K_M.gguf"
|
||||
# model_name = "codellama-34b-instruct.Q4_K_M.gguf"
|
||||
# model_name = "Wizard-Vicuna-7B-Uncensored.Q2_K.gguf"
|
||||
model_path = f"/Users/pancake/Library/Application Support/r2ai/models/{model_name}"
|
||||
# grammar = SchemaConverter.from_function(get_current_weather)
|
||||
llm = Llama(model_path, max_tokens=4096, n_ctx=4096, max_length=4096, verbose=False, temperature=0.04) # , top_p=0)
|
||||
print(leprompt)
|
||||
# print(llm(prompt="### User: What is the weather in London today? ### Assistant:")["choices"][0]["text"])
|
||||
res = llm(prompt=leprompt)
|
||||
# print(res)
|
||||
print(res["choices"][0]["text"])
|
||||
# print(llm(prompt=leprompt)["choices"])
|
||||
@@ -0,0 +1,6 @@
|
||||
import google.generativeai as genai
|
||||
|
||||
genai.configure(api_key="YOUR_API_KEY")
|
||||
model = genai.GenerativeModel("gemini-1.5-flash")
|
||||
response = model.generate_content("Explain how AI works")
|
||||
print(response.text)
|
||||
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
runline("-R")
|
||||
runline("-r I am a radare2 developer writing patches in C")
|
||||
runline("-m TheBloke/Mistral-7B-Instruct-v0.1-GGUF")
|
||||
os.system("git diff @^ > .a.patch")
|
||||
runline("-i .a.patch write a commit message starting with a capital letter for this diff. Commit messages cannot be longer than 60 characters.")
|
||||
runline("-R")
|
||||
runline("-i .a.patch write an explanation to be submmited in the pull request, explanation should be short and contain less than 3 items/highlights")
|
||||
os.system("rm .a.patch")
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
r2pm -r llama-server \
|
||||
--port 8012 -ngl 99 -fa -ub 1024 -b 1024 -dt 0.1 \
|
||||
--ctx-size 0 --cache-reuse 256 -m ~/.r2ai.models/qwen2.5-coder-7b-instruct-q3_k_m.gguf
|
||||
@@ -0,0 +1,13 @@
|
||||
CFLAGS+=-I/usr/local/include
|
||||
LDFLAGS+=/usr/local/lib/libllama.a
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
LDFLAGS+=-DGGML_USE_METAL
|
||||
LDFLAGS+=-lc++
|
||||
LDFLAGS+=-lcblas
|
||||
LDFLAGS+=-framework Foundation -framework Metal -framework MetalKit
|
||||
LDFLAGS+=-framework Accelerate
|
||||
endif
|
||||
|
||||
all:
|
||||
$(CC) $(CFLAGS) main.c $(LDFLAGS)
|
||||
@@ -0,0 +1,107 @@
|
||||
// using examples/main/main.cpp as inspiration
|
||||
|
||||
#include <llama.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MODEL_NAME "mistral-7b-v0.1.Q2_K.gguf"
|
||||
#define MODEL_PATH "/Users/pancake/Library/Application Support/r2ai/models"
|
||||
|
||||
static void llama_log_cb(enum ggml_log_level level, const char *text, void *user_data) {
|
||||
// printf ("[r2ai] %s\n", text);
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf ("r2ai rewrite in C\n");
|
||||
llama_log_set (llama_log_cb, NULL);
|
||||
struct llama_context_params lparams = {0};
|
||||
lparams.n_batch = 32;
|
||||
lparams.n_threads = 2;
|
||||
lparams.n_threads_batch = 2;
|
||||
lparams.seed = -1;
|
||||
|
||||
const char *model_path = MODEL_PATH "/" MODEL_NAME;
|
||||
struct llama_model_params mparams = llama_model_default_params ();
|
||||
struct llama_model *model = llama_load_model_from_file (model_path, mparams);
|
||||
struct llama_context *ctx = llama_new_context_with_model (model, lparams);
|
||||
|
||||
int n_ctx_train = llama_n_ctx_train (model);
|
||||
int n_ctx = llama_n_ctx (ctx);
|
||||
printf ("%d %d\n", n_ctx, n_ctx_train);
|
||||
|
||||
llama_set_rng_seed (ctx, 123);
|
||||
llama_token_bos (model);
|
||||
|
||||
uint64_t msize = llama_model_size (model);
|
||||
fprintf (stderr, "Model Size: %lld\n", msize);
|
||||
char mdesc[256] = {0};
|
||||
if (llama_model_desc (model, mdesc, sizeof (mdesc))) {
|
||||
fprintf (stderr, "Model Description: %s\n", mdesc);
|
||||
}
|
||||
uint64_t mpara = llama_model_n_params (model);
|
||||
fprintf (stderr, "Model Parameters: %lld\n", mpara);
|
||||
|
||||
fprintf (stderr, "Special Tokens:\n");
|
||||
fprintf (stderr, "BOS: %d\n", llama_token_bos (model));
|
||||
fprintf (stderr, "EOS: %d\n", llama_token_eos (model));
|
||||
fprintf (stderr, "NL: %d\n", llama_token_nl (model));
|
||||
fprintf (stderr, "PFX: %d\n", llama_token_prefix (model));
|
||||
fprintf (stderr, "MID: %d\n", llama_token_middle (model));
|
||||
fprintf (stderr, "SUF: %d\n", llama_token_suffix (model));
|
||||
fprintf (stderr, "EOT: %d\n", llama_token_eot (model));
|
||||
|
||||
|
||||
const char *text = "Hello Lilly\n";
|
||||
int n_eval = 0;
|
||||
int n_past = 0;
|
||||
int token = 'h';
|
||||
bool add_bos = false;
|
||||
bool special = false;
|
||||
llama_token tokens[32] = {0};
|
||||
int n_max_tokens = 32;
|
||||
tokens[0] = llama_token_bos (model);
|
||||
int n_tokens = llama_tokenize (llama_get_model (ctx), text, strlen (text), &tokens[1], n_max_tokens, add_bos, special);
|
||||
n_tokens ++;
|
||||
int i;
|
||||
printf ("input tokens: %d\n", n_tokens);
|
||||
char piece[32] = {0};
|
||||
for (i = 0; i < n_tokens; i++) {
|
||||
memset (piece, 0, sizeof (piece));
|
||||
llama_token_to_piece (model, tokens[i], piece, sizeof (piece));
|
||||
printf ("%d %d %s\n", i, tokens[i], piece);
|
||||
}
|
||||
int32_t embd = 0;
|
||||
int32_t n_seq_max = 128;
|
||||
struct llama_batch res = llama_batch_init (n_tokens, embd, n_seq_max);
|
||||
res.n_tokens = n_tokens;
|
||||
res.token = calloc (n_tokens, sizeof (int32_t));
|
||||
memcpy (res.token, tokens, sizeof (int32_t) * n_tokens);
|
||||
// struct llama_batch res = llama_batch_get_one (tokens, n_tokens, 0, 0);
|
||||
printf ("PREDEC %d\n", res.pos[0]);
|
||||
if (llama_decode (ctx, res) != 0) {
|
||||
printf ("decode error\n");
|
||||
}
|
||||
#if 0
|
||||
{
|
||||
struct llama_sampling_context *ctx_sampling = llama_sampling_init (params.sparams);
|
||||
int id = llama_sampling_sample (ctx_sampling, ctx, NULL, 0);
|
||||
llama_sampling_acept (ctx_sampling, ctx, id, true);
|
||||
llama_token_to_piece (ctx, id, piece, sizeof (piece));
|
||||
printf ("---> %s\n", piece);
|
||||
}
|
||||
#endif
|
||||
printf ("POSDEC %d\n", res.pos[0]);
|
||||
printf ("output tokens: %d\n", res.n_tokens);
|
||||
// print response here
|
||||
for (i = 0; i < res.n_tokens + 4; i++) {
|
||||
memset (piece, 0, sizeof (piece));
|
||||
llama_token_to_piece (model, res.token[i], piece, sizeof (piece));
|
||||
printf ("%d %d %s\n", i, tokens[i], piece);
|
||||
// check of llama_token_eos (model);
|
||||
}
|
||||
llama_batch_free (res);
|
||||
llama_free_model (model);
|
||||
llama_free (ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
R2PM_LLAMA_A=$(shell r2pm -H R2PM_LIBDIR)/libllama.a
|
||||
S=$
|
||||
|
||||
ifeq ($(shell test -e ${S}R2PM_LLAMA_A && printf yes),yes)
|
||||
CFLAGS+=-I$(shell r2pm -H R2PM_PREFIX)/include
|
||||
LDFLAGS+=$(R2PM_LLAMA_A)
|
||||
# $(shell r2pm -H R2PM_LIBDIR)/libllama.a
|
||||
else
|
||||
CFLAGS+=-I/usr/local/include
|
||||
LDFLAGS+=/usr/local/lib/libllama.a
|
||||
endif
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
LDFLAGS+=-DGGML_USE_METAL
|
||||
LDFLAGS+=-lcblas
|
||||
LDFLAGS+=-framework Foundation -framework Metal -framework MetalKit
|
||||
LDFLAGS+=-framework Accelerate
|
||||
endif
|
||||
R2FLAGS=$(shell pkg-config --cflags --libs r_core)
|
||||
SOEXT=$(shell r2 -H R2_LIBEXT)
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
|
||||
all:
|
||||
g++ -o r2ai.$(SOEXT) -std=c++11 $(CFLAGS) $(R2FLAGS) -DR2AI=1 -fPIC -shared r2ai.cpp main.cpp $(LDFLAGS)
|
||||
$(MAKE) user-install
|
||||
-g++ -pie -std=c++11 main.cpp $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
install user-install:
|
||||
mkdir -p $(R2_USER_PLUGINS)
|
||||
-cp -f r2ai.$(SOEXT) $(R2_USER_PLUGINS)
|
||||
$(MAKE) -C ../.. user-uninstall
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.$(SOEXT)
|
||||
|
||||
run:
|
||||
./a.out -n -1 --color -r "User:" --in-prefix " " -i -p \
|
||||
"User: Hi\
|
||||
AI: Hello. I am an AI chatbot. Would you like to talk?\
|
||||
User: Sure!\
|
||||
AI: What would you like to talk about?\
|
||||
User:"
|
||||
|
||||
run2:
|
||||
./a.out --color -r "[INST]" --in-suffix "[/INST]" -i -p \
|
||||
"<s>[INST]Hello, what is your name?[/INST]\
|
||||
My name is r2ai, a language model for radare2 which responds paragraphs</s>\
|
||||
<s>[INST]who is the author of radare2?[/INST]\
|
||||
He is pancake, aka Sergi Alvarez</s>"
|
||||
|
||||
s:
|
||||
./a.out --color -r "[INST]" --in-suffix "[/INST]" --in-prefix "<s>[INST]" -i -p \
|
||||
"<s>[INST]Hello, what is your name?[/INST]\
|
||||
My name is r2ai, a language model for radare2 which responds paragraphs</s>\
|
||||
<s>[INST]who is the author of radare2?[/INST]\
|
||||
He is pancake, aka Sergi Alvarez</s>"
|
||||
|
||||
z:
|
||||
./a.out -p \
|
||||
"<s>[INST]Hello, what is your name?[/INST]\
|
||||
My name is r2ai, a language model for radare2 which responds paragraphs</s>\
|
||||
<s>[INST]who is the author of radare2?[/INST]\
|
||||
He is pancake, aka Sergi Alvarez</s>"
|
||||