mirror of
https://github.com/radareorg/r2ai.git
synced 2026-11-09 17:46:36 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9077de9162 | ||
|
|
287fad0ecd | ||
|
|
08afde5117 | ||
|
|
a086a12f1d | ||
|
|
4a86d0fc46 | ||
|
|
0509bf6755 | ||
|
|
1d55b7f72a | ||
|
|
222f0b7410 | ||
|
|
065288e29f | ||
|
|
5824aa4482 | ||
|
|
3c7ed851d8 | ||
|
|
1586d27e93 | ||
|
|
898855d7ce | ||
|
|
20a3751624 | ||
|
|
d81957d856 | ||
|
|
b6d7c9424c | ||
|
|
c3efc53c27 | ||
|
|
b6d234c154 | ||
|
|
ef7f49a68e | ||
|
|
d1fcea6859 | ||
|
|
96dcc0dd4f | ||
|
|
659e14a4cd | ||
|
|
926ef545d0 | ||
|
|
1b11c648de | ||
|
|
394e55cf87 | ||
|
|
8db446cf31 | ||
|
|
65d7d87539 | ||
|
|
372b38380f | ||
|
|
72b9ff2f49 | ||
|
|
742d3f2a51 | ||
|
|
1f3f93fa35 | ||
|
|
3f1b91fbce | ||
|
|
acd0513ebb | ||
|
|
c44e838238 | ||
|
|
f4fa4ba432 | ||
|
|
561db876ac | ||
|
|
bcdc182d03 | ||
|
|
19cba835e4 | ||
|
|
87a1e89112 | ||
|
|
012118c25c | ||
|
|
e2030b31a1 | ||
|
|
53f437d55a | ||
|
|
66f202254e | ||
|
|
8e9555d144 | ||
|
|
a8caa51e76 | ||
|
|
021aa205f0 | ||
|
|
92c86f94e6 | ||
|
|
50c1d63a47 | ||
|
|
79f01945b7 | ||
|
|
9043638e01 | ||
|
|
6bd0227382 | ||
|
|
9f9a3b87ae | ||
|
|
b68ddaeaed | ||
|
|
d2663f75dc | ||
|
|
e24dbab677 | ||
|
|
c765269b02 | ||
|
|
55b856e70b | ||
|
|
5e148bc882 | ||
|
|
9f93754483 | ||
|
|
d262fabb2b | ||
|
|
d0a1f292f5 | ||
|
|
dad02ac243 | ||
|
|
02c561c415 | ||
|
|
9414cdf72b | ||
|
|
7cc61c4cc5 | ||
|
|
e4875eeb37 | ||
|
|
888251a2ea | ||
|
|
af6153ad64 | ||
|
|
87e4e7b232 | ||
|
|
cc3a003a64 | ||
|
|
f2be5cc3c2 | ||
|
|
e360148931 | ||
|
|
d06a9ee361 | ||
|
|
2e7b97b065 | ||
|
|
cd5d9a1640 | ||
|
|
e606392aaf | ||
|
|
8bf1169844 | ||
|
|
90e3bbafc5 | ||
|
|
e5a0639b88 | ||
|
|
80f27cd4d9 | ||
|
|
455da7bdba | ||
|
|
623e98ed3d | ||
|
|
17bf56b5e3 | ||
|
|
f50a7836eb | ||
|
|
2d9b69c624 | ||
|
|
de9c7c904c | ||
|
|
6b3ea982cd | ||
|
|
209d9257ee | ||
|
|
b840d002ee | ||
|
|
9d900b2a6b | ||
|
|
227b15e793 | ||
|
|
4b9dbf7686 | ||
|
|
8ca32e258f | ||
|
|
449c41eae6 | ||
|
|
5210fe6892 | ||
|
|
6222791075 | ||
|
|
05ce908e3a | ||
|
|
b8457c0bfa | ||
|
|
6a256b9210 | ||
|
|
8cc748ab4c | ||
|
|
17df397a3d | ||
|
|
fcd464f1cc | ||
|
|
1f41b6d2d7 | ||
|
|
adf8189541 | ||
|
|
cb8f3548ff | ||
|
|
b99034e5d8 | ||
|
|
fa1f6c1d3c | ||
|
|
d9fb7b18df | ||
|
|
e0b9ff235c | ||
|
|
e1bf58d4e4 | ||
|
|
51a63ac7ef | ||
|
|
c3a3eff046 | ||
|
|
37b5c2b6d0 | ||
|
|
0e6a8cc2e2 | ||
|
|
8dcc6f0418 | ||
|
|
b68cdd1171 | ||
|
|
3f935a46be | ||
|
|
fd46390d41 | ||
|
|
63106508dc | ||
|
|
46f42d3dd9 | ||
|
|
836fae3d91 | ||
|
|
66dd3dced7 | ||
|
|
c016fc72ed | ||
|
|
779b787f2d | ||
|
|
ca210f149b | ||
|
|
99e99b1797 | ||
|
|
fb5427238b | ||
|
|
d7c2dacd7a | ||
|
|
d6c32f4b72 | ||
|
|
0a083e7231 | ||
|
|
1a067fbbfc | ||
|
|
8a724d3682 | ||
|
|
8f7d152aa3 | ||
|
|
7120c325da | ||
|
|
14c991cd10 | ||
|
|
d1ef1e065e | ||
|
|
6f8ec21a39 | ||
|
|
3db0744c91 | ||
|
|
3306e412e2 | ||
|
|
20f5e16164 | ||
|
|
660249a31f | ||
|
|
312921c2a1 | ||
|
|
2356dbef9e | ||
|
|
7612a80a9c | ||
|
|
12d8bc9e22 | ||
|
|
d4be5f8569 | ||
|
|
a9333c8c38 | ||
|
|
29bdb2fcea | ||
|
|
078376c5d2 | ||
|
|
aa24702335 | ||
|
|
9aa40216a2 | ||
|
|
96e454e0c6 | ||
|
|
219b034261 | ||
|
|
797c967fb0 | ||
|
|
e61b7db8a5 | ||
|
|
3a39f9cd9d | ||
|
|
2b5e50d8f0 | ||
|
|
bb737f5be6 | ||
|
|
3e5f91e631 | ||
|
|
a3154bdcaf | ||
|
|
a6be6e6555 | ||
|
|
ad0bdbb902 | ||
|
|
4651d3b1b3 | ||
|
|
572e02c4ec | ||
|
|
75c787ef41 | ||
|
|
731f1933a9 | ||
|
|
573755ccef | ||
|
|
ecca7825fe | ||
|
|
95117608fb | ||
|
|
b3958b5f7d | ||
|
|
d0eaf37764 | ||
|
|
65692edd54 | ||
|
|
50f55bb5d2 | ||
|
|
fe1768fcc3 | ||
|
|
f241fba17d | ||
|
|
e5252960e0 | ||
|
|
8a45dadbde | ||
|
|
53d32efbbe | ||
|
|
f06faba9d6 | ||
|
|
8b52c2f6e2 | ||
|
|
b37436fb8d | ||
|
|
2524e66825 | ||
|
|
519017a75e | ||
|
|
6f30d6ca37 | ||
|
|
08ea4d11b2 | ||
|
|
0281308434 | ||
|
|
17a59d2057 | ||
|
|
eac0b8c6ef | ||
|
|
24aee426b4 | ||
|
|
b31bc5c116 | ||
|
|
01738210f5 | ||
|
|
7b19c4ff99 | ||
|
|
1986dd88bd | ||
|
|
1ed40401dd | ||
|
|
6ac0ff6352 | ||
|
|
4158e5f157 | ||
|
|
225ff58228 | ||
|
|
a51be44c68 | ||
|
|
911e455f72 | ||
|
|
70eb975b7f | ||
|
|
a0d36cc909 | ||
|
|
030551816b | ||
|
|
88c44c871b | ||
|
|
9218e3ea27 | ||
|
|
6f7ffcae1a | ||
|
|
fe12d2f8ee | ||
|
|
ae55b2f204 | ||
|
|
7d4e949240 | ||
|
|
9b97d39c7a | ||
|
|
33f72a997e | ||
|
|
36a8f13131 | ||
|
|
5c2ded0171 | ||
|
|
d5a39816a8 | ||
|
|
5aab38ab76 | ||
|
|
9f15ec6c73 | ||
|
|
68baac9911 | ||
|
|
54a280952e | ||
|
|
efc2368af1 | ||
|
|
28ddcbf638 | ||
|
|
85e9ed5c65 | ||
|
|
8886365925 | ||
|
|
01d8e29420 | ||
|
|
e8b556ee5d | ||
|
|
6c336f48f7 | ||
|
|
53d61831b2 | ||
|
|
cbcff6b9dd | ||
|
|
f04bf8162e | ||
|
|
0206ceb390 | ||
|
|
ff1f788c48 | ||
|
|
df135b2c70 | ||
|
|
c152777bbe | ||
|
|
3e084ce797 | ||
|
|
7fce8788b5 | ||
|
|
585e30470e | ||
|
|
5594e64ef3 | ||
|
|
904a356290 | ||
|
|
d1e2bcf911 | ||
|
|
21528f7ea8 | ||
|
|
2883d134aa | ||
|
|
ec96ba3329 | ||
|
|
f92ea17b01 | ||
|
|
2dc92031e7 | ||
|
|
55d54b4953 | ||
|
|
35460e639c | ||
|
|
a5382c17c4 | ||
|
|
f4bdb847db | ||
|
|
a7c3d3767c | ||
|
|
97c47ab28a | ||
|
|
c36bc1500c | ||
|
|
179cb6ca58 | ||
|
|
402c31cc96 | ||
|
|
be14729424 | ||
|
|
aa9b8e0fba | ||
|
|
96225cd555 | ||
|
|
321ca1bb41 | ||
|
|
d94ed5a088 | ||
|
|
3537d006de | ||
|
|
bdcee9e736 | ||
|
|
074b7a6c9f | ||
|
|
6697df8437 | ||
|
|
ff0fb4c802 | ||
|
|
ca7d02e03c | ||
|
|
bd105b2b0e | ||
|
|
5e18525403 | ||
|
|
c9d0756e74 | ||
|
|
d3fc79f9d1 | ||
|
|
8f4c972beb | ||
|
|
f95cda42f7 | ||
|
|
b241f30432 | ||
|
|
8eb50e466b | ||
|
|
14b5dcbfb9 | ||
|
|
e264f8f0c8 | ||
|
|
46c2ce6c62 | ||
|
|
693ad9270c | ||
|
|
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,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- buildsystem
|
||||
@@ -0,0 +1,170 @@
|
||||
name: CI and Release
|
||||
|
||||
env:
|
||||
R2V: 6.2.6
|
||||
|
||||
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@v7
|
||||
|
||||
- name: Setup Meson/Ninja
|
||||
if: matrix.build_system == 'meson'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install meson ninja
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- 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 || 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 = "${{ steps.r2.outputs.prefix }}"
|
||||
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
|
||||
if ($LASTEXITCODE -ne 0) { meson configure build }
|
||||
meson compile -C build
|
||||
|
||||
bindist:
|
||||
name: Build r2pm binary (${{ matrix.os }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install radare2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Package r2pm binary
|
||||
run: make -C src bindist
|
||||
|
||||
- name: Upload r2pm binary artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: bindist-${{ matrix.os }}
|
||||
path: r2ai-*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
windows-bindist:
|
||||
name: Build r2pm binary (Windows)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Meson/Ninja
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install meson ninja
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v3
|
||||
with:
|
||||
version: ${{ env.R2V }}
|
||||
|
||||
- name: Build with Meson
|
||||
shell: powershell
|
||||
run: |
|
||||
cd src
|
||||
$prefix = "${{ steps.r2.outputs.prefix }}"
|
||||
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
|
||||
if ($LASTEXITCODE -ne 0) { meson configure build }
|
||||
meson compile -C build
|
||||
|
||||
- name: Package r2pm binary
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path bindist\plugins,bindist\bin,bindist\prompts | Out-Null
|
||||
Get-ChildItem src\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
|
||||
Get-ChildItem src\..\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
|
||||
Copy-Item src\build\r2ai.exe bindist\bin
|
||||
Copy-Item prompts\*.r2ai.md bindist\prompts
|
||||
$rv = (& radare2 -qv).Trim()
|
||||
Compress-Archive -Path bindist\* -DestinationPath "r2ai-$rv-windows-x86-64.zip" -Force
|
||||
|
||||
- name: Upload r2pm binary artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: windows-bindist
|
||||
path: r2ai-*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [bindist, windows-bindist]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- 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 r2pm binary artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: '*bindist*'
|
||||
path: release-artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- 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@v3
|
||||
with:
|
||||
files: |
|
||||
release-artifacts/*.zip
|
||||
release-artifacts/decai.r2.js
|
||||
generate_release_notes: true
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
build
|
||||
b
|
||||
src/b
|
||||
src/r2ai
|
||||
src/r2ai.dSYM
|
||||
venv
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.o
|
||||
*.so
|
||||
*.dylib
|
||||
*.a
|
||||
third_party
|
||||
|
||||
# r2pm binary packages
|
||||
r2ai-*.zip
|
||||
Executable
+162
@@ -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-sonnet-5-5"
|
||||
if [ -f ~/.r2ai.anthropic-key ]; then
|
||||
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
|
||||
fi
|
||||
DEEPSEEK_KEY=""
|
||||
DEEPSEEK_MODEL="claude-sonnet-5-5"
|
||||
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-sonnet-5-5
|
||||
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
|
||||
Executable
+7
@@ -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,14 @@
|
||||
SHELL=/bin/bash
|
||||
|
||||
MODELS=gpt-oss:20b gemma3:4b gemma3:12b
|
||||
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=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
|
||||
LANGS=Dart Perl Ruby Javascript PHP C
|
||||
|
||||
all:
|
||||
for a in ${MODELS} ; do \
|
||||
declare -a ARGS; \
|
||||
for l in ${LANGS} ; do \
|
||||
ARGS+=("echo \"decai -e model=$$a;decai -e lang=$$l;s 0x100000924;af;decai -d\" | r2 -q /bin/ls"); \
|
||||
done ; \
|
||||
./run.sh "$${ARGS[@]}" > ${a//\//_}.html ; done
|
||||
@@ -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,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 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
|
||||
Executable
+177
@@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate HTML output for command executions with collapsible sections and width control
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 command1 [command2 ...]"
|
||||
exit 1
|
||||
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 #, use the rest as title, else use the command
|
||||
if [[ "$cmd" =~ ^#[[:space:]]*(.*) ]]; then
|
||||
title="${BASH_REMATCH[1]}"
|
||||
exec_cmd="${cmd#*#}"
|
||||
exec_cmd="${exec_cmd#"${exec_cmd%%[![:space:]]*}"}" # trim leading spaces
|
||||
else
|
||||
title="$cmd"
|
||||
exec_cmd="$cmd"
|
||||
fi
|
||||
|
||||
# Execute command and time it
|
||||
start_time=$(date +%s.%N)
|
||||
tmpfile=$(mktemp)
|
||||
eval "$exec_cmd" > "$tmpfile" 2>&1 &
|
||||
pid=$!
|
||||
(sleep $TIMEOUT && kill $pid 2>/dev/null) &
|
||||
wait $pid 2>/dev/null
|
||||
output=$(cat "$tmpfile")
|
||||
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
|
||||
|
||||
# Escape HTML in output
|
||||
escaped_output=$(echo "$output" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||||
|
||||
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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Executable
+11
@@ -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)
|
||||
Executable
+114
@@ -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)
|
||||
Executable
+9
@@ -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")
|
||||
Executable
+4
@@ -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>"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,242 @@
|
||||
// Various helper functions and utilities
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "llama.h"
|
||||
|
||||
#include "sampling.h"
|
||||
|
||||
#define LOG_NO_FILE_LINE_FUNCTION
|
||||
#include "log.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIRECTORY_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIRECTORY_SEPARATOR '/'
|
||||
#endif // _WIN32
|
||||
|
||||
#define die(msg) do { fputs("error: " msg "\n", stderr); exit(1); } while (0)
|
||||
#define die_fmt(fmt, ...) do { fprintf(stderr, "error: " fmt "\n", __VA_ARGS__); exit(1); } while (0)
|
||||
|
||||
#define print_build_info() do { \
|
||||
fprintf(stderr, "%s: build = %d (%s)\n", __func__, LLAMA_BUILD_NUMBER, LLAMA_COMMIT); \
|
||||
fprintf(stderr, "%s: built with %s for %s\n", __func__, LLAMA_COMPILER, LLAMA_BUILD_TARGET); \
|
||||
} while(0)
|
||||
|
||||
// build info
|
||||
extern int LLAMA_BUILD_NUMBER;
|
||||
extern char const *LLAMA_COMMIT;
|
||||
extern char const *LLAMA_COMPILER;
|
||||
extern char const *LLAMA_BUILD_TARGET;
|
||||
|
||||
//
|
||||
// CLI argument parsing
|
||||
//
|
||||
int32_t get_num_physical_cores();
|
||||
|
||||
struct gpt_params {
|
||||
uint32_t seed = -1; // RNG seed
|
||||
|
||||
int32_t n_threads = get_num_physical_cores();
|
||||
int32_t n_threads_batch = -1; // number of threads to use for batch processing (-1 = use n_threads)
|
||||
int32_t n_predict = -1; // new tokens to predict
|
||||
int32_t n_ctx = 512; // context size
|
||||
int32_t n_batch = 512; // batch size for prompt processing (must be >=32 to use BLAS)
|
||||
int32_t n_keep = 0; // number of tokens to keep from initial prompt
|
||||
int32_t n_draft = 16; // number of tokens to draft during speculative decoding
|
||||
int32_t n_chunks = -1; // max number of chunks to process (-1 = unlimited)
|
||||
int32_t n_parallel = 1; // number of parallel sequences to decode
|
||||
int32_t n_sequences = 1; // number of sequences to decode
|
||||
float p_accept = 0.5f; // speculative decoding accept probability
|
||||
float p_split = 0.1f; // speculative decoding split probability
|
||||
int32_t n_gpu_layers = -1; // number of layers to store in VRAM (-1 - use default)
|
||||
int32_t n_gpu_layers_draft = -1; // number of layers to store in VRAM for the draft model (-1 - use default)
|
||||
int32_t main_gpu = 0; // the GPU that is used for scratch and small tensors
|
||||
float tensor_split[LLAMA_MAX_DEVICES] = {0}; // how split tensors should be distributed across GPUs
|
||||
int32_t n_beams = 0; // if non-zero then use beam search of given width.
|
||||
float rope_freq_base = 0.0f; // RoPE base frequency
|
||||
float rope_freq_scale = 0.0f; // RoPE frequency scaling factor
|
||||
float yarn_ext_factor = -1.0f; // YaRN extrapolation mix factor
|
||||
float yarn_attn_factor = 1.0f; // YaRN magnitude scaling factor
|
||||
float yarn_beta_fast = 32.0f; // YaRN low correction dim
|
||||
float yarn_beta_slow = 1.0f; // YaRN high correction dim
|
||||
int32_t yarn_orig_ctx = 0; // YaRN original context length
|
||||
int8_t rope_scaling_type = LLAMA_ROPE_SCALING_UNSPECIFIED; // TODO: better to be int32_t for alignment
|
||||
// pinging @cebtenzzre
|
||||
|
||||
// // sampling parameters
|
||||
struct llama_sampling_params sparams;
|
||||
|
||||
std::string model = "models/7B/ggml-model-f16.gguf"; // model path
|
||||
std::string model_draft = ""; // draft model for speculative decoding
|
||||
std::string model_alias = "unknown"; // model alias
|
||||
std::string prompt = "";
|
||||
std::string prompt_file = ""; // store the external prompt file name
|
||||
std::string path_prompt_cache = ""; // path to file for saving/loading prompt eval state
|
||||
std::string input_prefix = ""; // string to prefix user inputs with
|
||||
std::string input_suffix = ""; // string to suffix user inputs with
|
||||
std::vector<std::string> antiprompt; // string upon seeing which more user input is prompted
|
||||
std::string logdir = ""; // directory in which to save YAML log files
|
||||
|
||||
std::vector<llama_model_kv_override> kv_overrides;
|
||||
|
||||
// TODO: avoid tuple, use struct
|
||||
std::vector<std::tuple<std::string, float>> lora_adapter; // lora adapter path with user defined scale
|
||||
std::string lora_base = ""; // base model path for the lora adapter
|
||||
|
||||
int ppl_stride = 0; // stride for perplexity calculations. If left at 0, the pre-existing approach will be used.
|
||||
int ppl_output_type = 0; // = 0 -> ppl output is as usual, = 1 -> ppl output is num_tokens, ppl, one per line
|
||||
// (which is more convenient to use for plotting)
|
||||
//
|
||||
bool hellaswag = false; // compute HellaSwag score over random tasks from datafile supplied in prompt
|
||||
size_t hellaswag_tasks = 400; // number of tasks to use when computing the HellaSwag score
|
||||
|
||||
bool mul_mat_q = true; // if true, use mul_mat_q kernels instead of cuBLAS
|
||||
bool random_prompt = false; // do not randomize prompt if none provided
|
||||
bool use_color = false; // use color to distinguish generations and inputs
|
||||
bool interactive = false; // interactive mode
|
||||
bool chatml = false; // chatml mode (used for models trained on chatml syntax)
|
||||
bool prompt_cache_all = false; // save user input and generations to prompt cache
|
||||
bool prompt_cache_ro = false; // open the prompt cache read-only and do not update it
|
||||
|
||||
bool embedding = false; // get only sentence embedding
|
||||
bool escape = false; // escape "\n", "\r", "\t", "\'", "\"", and "\\"
|
||||
bool interactive_first = false; // wait for user input immediately
|
||||
bool multiline_input = false; // reverse the usage of `\`
|
||||
bool simple_io = false; // improves compatibility with subprocesses and limited consoles
|
||||
bool cont_batching = false; // insert new sequences for decoding on-the-fly
|
||||
|
||||
bool input_prefix_bos = false; // prefix BOS to user inputs, preceding input_prefix
|
||||
bool ignore_eos = false; // ignore generated EOS tokens
|
||||
bool instruct = false; // instruction mode (used for Alpaca models)
|
||||
bool logits_all = false; // return logits for all tokens in the batch
|
||||
bool use_mmap = true; // use mmap for faster loads
|
||||
bool use_mlock = false; // use mlock to keep model in memory
|
||||
bool numa = false; // attempt optimizations that help on some NUMA systems
|
||||
bool verbose_prompt = false; // print prompt tokens before generation
|
||||
bool infill = false; // use infill mode
|
||||
bool dump_kv_cache = false; // dump the KV cache contents for debugging purposes
|
||||
bool no_kv_offload = false; // disable KV offloading
|
||||
|
||||
std::string cache_type_k = "f16"; // KV cache data type for the K
|
||||
std::string cache_type_v = "f16"; // KV cache data type for the V
|
||||
|
||||
// multimodal models (see examples/llava)
|
||||
std::string mmproj = ""; // path to multimodal projector
|
||||
std::string image = ""; // path to an image file
|
||||
};
|
||||
|
||||
bool gpt_params_parse_ex(int argc, char ** argv, gpt_params & params);
|
||||
|
||||
bool gpt_params_parse(int argc, char ** argv, gpt_params & params);
|
||||
|
||||
void gpt_print_usage(int argc, char ** argv, const gpt_params & params);
|
||||
|
||||
std::string get_system_info(const gpt_params & params);
|
||||
|
||||
std::string gpt_random_prompt(std::mt19937 & rng);
|
||||
|
||||
void process_escapes(std::string& input);
|
||||
|
||||
//
|
||||
// String parsing
|
||||
//
|
||||
|
||||
std::string parse_samplers_input(std::string input);
|
||||
|
||||
//
|
||||
// Model utils
|
||||
//
|
||||
|
||||
// TODO: avoid tuplue, use struct
|
||||
std::tuple<struct llama_model *, struct llama_context *> llama_init_from_gpt_params(gpt_params & params);
|
||||
|
||||
struct llama_model_params llama_model_params_from_gpt_params (const gpt_params & params);
|
||||
struct llama_context_params llama_context_params_from_gpt_params(const gpt_params & params);
|
||||
|
||||
// Batch utils
|
||||
|
||||
void llama_batch_clear(struct llama_batch & batch);
|
||||
|
||||
void llama_batch_add(
|
||||
struct llama_batch & batch,
|
||||
llama_token id,
|
||||
llama_pos pos,
|
||||
const std::vector<llama_seq_id> & seq_ids,
|
||||
bool logits);
|
||||
|
||||
//
|
||||
// Vocab utils
|
||||
//
|
||||
|
||||
// tokenizes a string into a vector of tokens
|
||||
// should work similar to Python's `tokenizer.encode`
|
||||
std::vector<llama_token> llama_tokenize(
|
||||
const struct llama_context * ctx,
|
||||
const std::string & text,
|
||||
bool add_bos,
|
||||
bool special = false);
|
||||
|
||||
std::vector<llama_token> llama_tokenize(
|
||||
const struct llama_model * model,
|
||||
const std::string & text,
|
||||
bool add_bos,
|
||||
bool special = false);
|
||||
|
||||
// tokenizes a token into a piece
|
||||
// should work similar to Python's `tokenizer.id_to_piece`
|
||||
std::string llama_token_to_piece(
|
||||
const struct llama_context * ctx,
|
||||
llama_token token);
|
||||
|
||||
// TODO: these should be moved in llama.h C-style API under single `llama_detokenize` function
|
||||
// that takes into account the tokenizer type and decides how to handle the leading space
|
||||
//
|
||||
// detokenizes a vector of tokens into a string
|
||||
// should work similar to Python's `tokenizer.decode`
|
||||
// removes the leading space from the first non-BOS token
|
||||
std::string llama_detokenize_spm(
|
||||
llama_context * ctx,
|
||||
const std::vector<llama_token> & tokens);
|
||||
|
||||
// detokenizes a vector of tokens into a string
|
||||
// should work similar to Python's `tokenizer.decode`
|
||||
std::string llama_detokenize_bpe(
|
||||
llama_context * ctx,
|
||||
const std::vector<llama_token> & tokens);
|
||||
|
||||
// Uses the value from the model metadata if possible, otherwise
|
||||
// defaults to true when model type is SPM, otherwise false.
|
||||
bool llama_should_add_bos_token(const llama_model * model);
|
||||
|
||||
//
|
||||
// YAML utils
|
||||
//
|
||||
|
||||
bool create_directory_with_parents(const std::string & path);
|
||||
void dump_vector_float_yaml(FILE * stream, const char * prop_name, const std::vector<float> & data);
|
||||
void dump_vector_int_yaml(FILE * stream, const char * prop_name, const std::vector<int> & data);
|
||||
void dump_string_yaml_multiline(FILE * stream, const char * prop_name, const char * data);
|
||||
std::string get_sortable_timestamp();
|
||||
|
||||
void dump_non_result_info_yaml(
|
||||
FILE * stream, const gpt_params & params, const llama_context * lctx,
|
||||
const std::string & timestamp, const std::vector<int> & prompt_tokens, const char * model_desc);
|
||||
|
||||
//
|
||||
// KV cache utils
|
||||
//
|
||||
|
||||
// Dump the KV cache view with the number of sequences per cell.
|
||||
void dump_kv_cache_view(const llama_kv_cache_view & view, int row_size = 80);
|
||||
|
||||
// Dump the KV cache view showing individual sequences in each cell (long output).
|
||||
void dump_kv_cache_view_seqs(const llama_kv_cache_view & view, int row_size = 40);
|
||||
@@ -0,0 +1,723 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
|
||||
// --------------------------------
|
||||
//
|
||||
// Basic usage:
|
||||
//
|
||||
// --------
|
||||
//
|
||||
// The LOG() and LOG_TEE() macros are ready to go by default
|
||||
// they do not require any initialization.
|
||||
//
|
||||
// LOGLN() and LOG_TEELN() are variants which automatically
|
||||
// include \n character at the end of the log string.
|
||||
//
|
||||
// LOG() behaves exactly like printf, by default writing to a logfile.
|
||||
// LOG_TEE() additionally, prints to the screen too ( mimics Unix tee command ).
|
||||
//
|
||||
// Default logfile is named
|
||||
// "llama.<threadID>.log"
|
||||
// Default LOG_TEE() secondary output target is
|
||||
// stderr
|
||||
//
|
||||
// Logs can be dynamically disabled or enabled using functions:
|
||||
// log_disable()
|
||||
// and
|
||||
// log_enable()
|
||||
//
|
||||
// A log target can be changed with:
|
||||
// log_set_target( string )
|
||||
// creating and opening, or re-opening a file by string filename
|
||||
// or
|
||||
// log_set_target( FILE* )
|
||||
// allowing to point at stderr, stdout, or any valid FILE* file handler.
|
||||
//
|
||||
// --------
|
||||
//
|
||||
// End of Basic usage.
|
||||
//
|
||||
// --------------------------------
|
||||
|
||||
// Specifies a log target.
|
||||
// default uses log_handler() with "llama.log" log file
|
||||
// this can be changed, by defining LOG_TARGET
|
||||
// like so:
|
||||
//
|
||||
// #define LOG_TARGET (a valid FILE*)
|
||||
// #include "log.h"
|
||||
//
|
||||
// or it can be simply redirected to stdout or stderr
|
||||
// like so:
|
||||
//
|
||||
// #define LOG_TARGET stderr
|
||||
// #include "log.h"
|
||||
//
|
||||
// The log target can also be redirected to a different function
|
||||
// like so:
|
||||
//
|
||||
// #define LOG_TARGET log_handler_different()
|
||||
// #include "log.h"
|
||||
//
|
||||
// FILE* log_handler_different()
|
||||
// {
|
||||
// return stderr;
|
||||
// }
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// #define LOG_TARGET log_handler_another_one("somelog.log")
|
||||
// #include "log.h"
|
||||
//
|
||||
// FILE* log_handler_another_one(char*filename)
|
||||
// {
|
||||
// static FILE* logfile = nullptr;
|
||||
// (...)
|
||||
// if( !logfile )
|
||||
// {
|
||||
// fopen(...)
|
||||
// }
|
||||
// (...)
|
||||
// return logfile
|
||||
// }
|
||||
//
|
||||
#ifndef LOG_TARGET
|
||||
#define LOG_TARGET log_handler()
|
||||
#endif
|
||||
|
||||
#ifndef LOG_TEE_TARGET
|
||||
#define LOG_TEE_TARGET stderr
|
||||
#endif
|
||||
|
||||
// Utility for synchronizing log configuration state
|
||||
// since std::optional was introduced only in c++17
|
||||
enum LogTriState
|
||||
{
|
||||
LogTriStateSame,
|
||||
LogTriStateFalse,
|
||||
LogTriStateTrue
|
||||
};
|
||||
|
||||
// Utility to obtain "pid" like unique process id and use it when creating log files.
|
||||
inline std::string log_get_pid()
|
||||
{
|
||||
static std::string pid;
|
||||
if (pid.empty())
|
||||
{
|
||||
// std::this_thread::get_id() is the most portable way of obtaining a "process id"
|
||||
// it's not the same as "pid" but is unique enough to solve multiple instances
|
||||
// trying to write to the same log.
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
pid = ss.str();
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
// Utility function for generating log file names with unique id based on thread id.
|
||||
// invocation with log_filename_generator( "llama", "log" ) creates a string "llama.<number>.log"
|
||||
// where the number is a runtime id of the current thread.
|
||||
|
||||
#define log_filename_generator(log_file_basename, log_file_extension) log_filename_generator_impl(LogTriStateSame, log_file_basename, log_file_extension)
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline std::string log_filename_generator_impl(LogTriState multilog, const std::string & log_file_basename, const std::string & log_file_extension)
|
||||
{
|
||||
static bool _multilog = false;
|
||||
|
||||
if (multilog != LogTriStateSame)
|
||||
{
|
||||
_multilog = multilog == LogTriStateTrue;
|
||||
}
|
||||
|
||||
std::stringstream buf;
|
||||
|
||||
buf << log_file_basename;
|
||||
if (_multilog)
|
||||
{
|
||||
buf << ".";
|
||||
buf << log_get_pid();
|
||||
}
|
||||
buf << ".";
|
||||
buf << log_file_extension;
|
||||
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
#ifndef LOG_DEFAULT_FILE_NAME
|
||||
#define LOG_DEFAULT_FILE_NAME log_filename_generator("llama", "log")
|
||||
#endif
|
||||
|
||||
// Utility for turning #define values into string literals
|
||||
// so we can have a define for stderr and
|
||||
// we can print "stderr" instead of literal stderr, etc.
|
||||
#define LOG_STRINGIZE1(s) #s
|
||||
#define LOG_STRINGIZE(s) LOG_STRINGIZE1(s)
|
||||
|
||||
#define LOG_TEE_TARGET_STRING LOG_STRINGIZE(LOG_TEE_TARGET)
|
||||
|
||||
// Allows disabling timestamps.
|
||||
// in order to disable, define LOG_NO_TIMESTAMPS
|
||||
// like so:
|
||||
//
|
||||
// #define LOG_NO_TIMESTAMPS
|
||||
// #include "log.h"
|
||||
//
|
||||
#ifndef LOG_NO_TIMESTAMPS
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_TIMESTAMP_FMT "[%" PRIu64 "] "
|
||||
#define LOG_TIMESTAMP_VAL , (std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(std::chrono::system_clock::now().time_since_epoch())).count()
|
||||
#else
|
||||
#define LOG_TIMESTAMP_FMT "[%" PRIu64 "] "
|
||||
#define LOG_TIMESTAMP_VAL , (std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(std::chrono::system_clock::now().time_since_epoch())).count()
|
||||
#endif
|
||||
#else
|
||||
#define LOG_TIMESTAMP_FMT "%s"
|
||||
#define LOG_TIMESTAMP_VAL ,""
|
||||
#endif
|
||||
|
||||
#ifdef LOG_TEE_TIMESTAMPS
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_TEE_TIMESTAMP_FMT "[%" PRIu64 "] "
|
||||
#define LOG_TEE_TIMESTAMP_VAL , (std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(std::chrono::system_clock::now().time_since_epoch())).count()
|
||||
#else
|
||||
#define LOG_TEE_TIMESTAMP_FMT "[%" PRIu64 "] "
|
||||
#define LOG_TEE_TIMESTAMP_VAL , (std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(std::chrono::system_clock::now().time_since_epoch())).count()
|
||||
#endif
|
||||
#else
|
||||
#define LOG_TEE_TIMESTAMP_FMT "%s"
|
||||
#define LOG_TEE_TIMESTAMP_VAL ,""
|
||||
#endif
|
||||
|
||||
// Allows disabling file/line/function prefix
|
||||
// in order to disable, define LOG_NO_FILE_LINE_FUNCTION
|
||||
// like so:
|
||||
//
|
||||
// #define LOG_NO_FILE_LINE_FUNCTION
|
||||
// #include "log.h"
|
||||
//
|
||||
#ifndef LOG_NO_FILE_LINE_FUNCTION
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_FLF_FMT "[%24s:%5d][%24s] "
|
||||
#define LOG_FLF_VAL , __FILE__, __LINE__, __FUNCTION__
|
||||
#else
|
||||
#define LOG_FLF_FMT "[%24s:%5ld][%24s] "
|
||||
#define LOG_FLF_VAL , __FILE__, __LINE__, __FUNCTION__
|
||||
#endif
|
||||
#else
|
||||
#define LOG_FLF_FMT "%s"
|
||||
#define LOG_FLF_VAL ,""
|
||||
#endif
|
||||
|
||||
#ifdef LOG_TEE_FILE_LINE_FUNCTION
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_TEE_FLF_FMT "[%24s:%5d][%24s] "
|
||||
#define LOG_TEE_FLF_VAL , __FILE__, __LINE__, __FUNCTION__
|
||||
#else
|
||||
#define LOG_TEE_FLF_FMT "[%24s:%5ld][%24s] "
|
||||
#define LOG_TEE_FLF_VAL , __FILE__, __LINE__, __FUNCTION__
|
||||
#endif
|
||||
#else
|
||||
#define LOG_TEE_FLF_FMT "%s"
|
||||
#define LOG_TEE_FLF_VAL ,""
|
||||
#endif
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
// USE LOG() INSTEAD
|
||||
//
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_IMPL(str, ...) \
|
||||
do { \
|
||||
if (LOG_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TARGET, LOG_TIMESTAMP_FMT LOG_FLF_FMT str "%s" LOG_TIMESTAMP_VAL LOG_FLF_VAL, __VA_ARGS__); \
|
||||
fflush(LOG_TARGET); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_IMPL(str, ...) \
|
||||
do { \
|
||||
if (LOG_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TARGET, LOG_TIMESTAMP_FMT LOG_FLF_FMT str "%s" LOG_TIMESTAMP_VAL LOG_FLF_VAL "", ##__VA_ARGS__); \
|
||||
fflush(LOG_TARGET); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
// USE LOG_TEE() INSTEAD
|
||||
//
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_TEE_IMPL(str, ...) \
|
||||
do { \
|
||||
if (LOG_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TARGET, LOG_TIMESTAMP_FMT LOG_FLF_FMT str "%s" LOG_TIMESTAMP_VAL LOG_FLF_VAL, __VA_ARGS__); \
|
||||
fflush(LOG_TARGET); \
|
||||
} \
|
||||
if (LOG_TARGET != nullptr && LOG_TARGET != stdout && LOG_TARGET != stderr && LOG_TEE_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TEE_TARGET, LOG_TEE_TIMESTAMP_FMT LOG_TEE_FLF_FMT str "%s" LOG_TEE_TIMESTAMP_VAL LOG_TEE_FLF_VAL, __VA_ARGS__); \
|
||||
fflush(LOG_TEE_TARGET); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_TEE_IMPL(str, ...) \
|
||||
do { \
|
||||
if (LOG_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TARGET, LOG_TIMESTAMP_FMT LOG_FLF_FMT str "%s" LOG_TIMESTAMP_VAL LOG_FLF_VAL "", ##__VA_ARGS__); \
|
||||
fflush(LOG_TARGET); \
|
||||
} \
|
||||
if (LOG_TARGET != nullptr && LOG_TARGET != stdout && LOG_TARGET != stderr && LOG_TEE_TARGET != nullptr) \
|
||||
{ \
|
||||
fprintf(LOG_TEE_TARGET, LOG_TEE_TIMESTAMP_FMT LOG_TEE_FLF_FMT str "%s" LOG_TEE_TIMESTAMP_VAL LOG_TEE_FLF_VAL "", ##__VA_ARGS__); \
|
||||
fflush(LOG_TEE_TARGET); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// The '\0' as a last argument, is a trick to bypass the silly
|
||||
// "warning: ISO C++11 requires at least one argument for the "..." in a variadic macro"
|
||||
// so we can have a single macro which can be called just like printf.
|
||||
|
||||
// Main LOG macro.
|
||||
// behaves like printf, and supports arguments the exact same way.
|
||||
//
|
||||
#ifndef _MSC_VER
|
||||
#define LOG(...) LOG_IMPL(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LOG(str, ...) LOG_IMPL("%s" str, "", __VA_ARGS__, "")
|
||||
#endif
|
||||
|
||||
// Main TEE macro.
|
||||
// does the same as LOG
|
||||
// and
|
||||
// simultaneously writes stderr.
|
||||
//
|
||||
// Secondary target can be changed just like LOG_TARGET
|
||||
// by defining LOG_TEE_TARGET
|
||||
//
|
||||
#ifndef _MSC_VER
|
||||
#define LOG_TEE(...) LOG_TEE_IMPL(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LOG_TEE(str, ...) LOG_TEE_IMPL("%s" str, "", __VA_ARGS__, "")
|
||||
#endif
|
||||
|
||||
// LOG macro variants with auto endline.
|
||||
#ifndef _MSC_VER
|
||||
#define LOGLN(...) LOG_IMPL(__VA_ARGS__, "\n")
|
||||
#define LOG_TEELN(...) LOG_TEE_IMPL(__VA_ARGS__, "\n")
|
||||
#else
|
||||
#define LOGLN(str, ...) LOG_IMPL("%s" str, "", __VA_ARGS__, "\n")
|
||||
#define LOG_TEELN(str, ...) LOG_TEE_IMPL("%s" str, "", __VA_ARGS__, "\n")
|
||||
#endif
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_handler1_impl(bool change = false, LogTriState append = LogTriStateSame, LogTriState disable = LogTriStateSame, const std::string & filename = LOG_DEFAULT_FILE_NAME, FILE *target = nullptr)
|
||||
{
|
||||
static bool _initialized = false;
|
||||
static bool _append = false;
|
||||
static bool _disabled = filename.empty() && target == nullptr;
|
||||
static std::string log_current_filename{filename};
|
||||
static FILE *log_current_target{target};
|
||||
static FILE *logfile = nullptr;
|
||||
|
||||
if (change)
|
||||
{
|
||||
if (append != LogTriStateSame)
|
||||
{
|
||||
_append = append == LogTriStateTrue;
|
||||
return logfile;
|
||||
}
|
||||
|
||||
if (disable == LogTriStateTrue)
|
||||
{
|
||||
// Disable primary target
|
||||
_disabled = true;
|
||||
}
|
||||
// If previously disabled, only enable, and keep previous target
|
||||
else if (disable == LogTriStateFalse)
|
||||
{
|
||||
_disabled = false;
|
||||
}
|
||||
// Otherwise, process the arguments
|
||||
else if (log_current_filename != filename || log_current_target != target)
|
||||
{
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_disabled)
|
||||
{
|
||||
// Log is disabled
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (_initialized)
|
||||
{
|
||||
// with fallback in case something went wrong
|
||||
return logfile ? logfile : stderr;
|
||||
}
|
||||
|
||||
// do the (re)initialization
|
||||
if (target != nullptr)
|
||||
{
|
||||
if (logfile != nullptr && logfile != stdout && logfile != stderr)
|
||||
{
|
||||
fclose(logfile);
|
||||
}
|
||||
|
||||
log_current_filename = LOG_DEFAULT_FILE_NAME;
|
||||
log_current_target = target;
|
||||
|
||||
logfile = target;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (log_current_filename != filename)
|
||||
{
|
||||
if (logfile != nullptr && logfile != stdout && logfile != stderr)
|
||||
{
|
||||
fclose(logfile);
|
||||
}
|
||||
}
|
||||
|
||||
logfile = fopen(filename.c_str(), _append ? "a" : "w");
|
||||
}
|
||||
|
||||
if (!logfile)
|
||||
{
|
||||
// Verify whether the file was opened, otherwise fallback to stderr
|
||||
logfile = stderr;
|
||||
|
||||
fprintf(stderr, "Failed to open logfile '%s' with error '%s'\n", filename.c_str(), std::strerror(errno));
|
||||
fflush(stderr);
|
||||
|
||||
// At this point we let the init flag be to true below, and let the target fallback to stderr
|
||||
// otherwise we would repeatedly fopen() which was already unsuccessful
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
|
||||
return logfile ? logfile : stderr;
|
||||
}
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_handler2_impl(bool change = false, LogTriState append = LogTriStateSame, LogTriState disable = LogTriStateSame, FILE *target = nullptr, const std::string & filename = LOG_DEFAULT_FILE_NAME)
|
||||
{
|
||||
return log_handler1_impl(change, append, disable, filename, target);
|
||||
}
|
||||
|
||||
// Disables logs entirely at runtime.
|
||||
// Makes LOG() and LOG_TEE() produce no output,
|
||||
// until enabled back.
|
||||
#define log_disable() log_disable_impl()
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_disable_impl()
|
||||
{
|
||||
return log_handler1_impl(true, LogTriStateSame, LogTriStateTrue);
|
||||
}
|
||||
|
||||
// Enables logs at runtime.
|
||||
#define log_enable() log_enable_impl()
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_enable_impl()
|
||||
{
|
||||
return log_handler1_impl(true, LogTriStateSame, LogTriStateFalse);
|
||||
}
|
||||
|
||||
// Sets target fir logs, either by a file name or FILE* pointer (stdout, stderr, or any valid FILE*)
|
||||
#define log_set_target(target) log_set_target_impl(target)
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_set_target_impl(const std::string & filename) { return log_handler1_impl(true, LogTriStateSame, LogTriStateSame, filename); }
|
||||
inline FILE *log_set_target_impl(FILE *target) { return log_handler2_impl(true, LogTriStateSame, LogTriStateSame, target); }
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_handler() { return log_handler1_impl(); }
|
||||
|
||||
// Enable or disable creating separate log files for each run.
|
||||
// can ONLY be invoked BEFORE first log use.
|
||||
#define log_multilog(enable) log_filename_generator_impl((enable) ? LogTriStateTrue : LogTriStateFalse, "", "")
|
||||
// Enable or disable append mode for log file.
|
||||
// can ONLY be invoked BEFORE first log use.
|
||||
#define log_append(enable) log_append_impl(enable)
|
||||
// INTERNAL, DO NOT USE
|
||||
inline FILE *log_append_impl(bool enable)
|
||||
{
|
||||
return log_handler1_impl(true, enable ? LogTriStateTrue : LogTriStateFalse, LogTriStateSame);
|
||||
}
|
||||
|
||||
inline void log_test()
|
||||
{
|
||||
log_disable();
|
||||
LOG("01 Hello World to nobody, because logs are disabled!\n");
|
||||
log_enable();
|
||||
LOG("02 Hello World to default output, which is \"%s\" ( Yaaay, arguments! )!\n", LOG_STRINGIZE(LOG_TARGET));
|
||||
LOG_TEE("03 Hello World to **both** default output and " LOG_TEE_TARGET_STRING "!\n");
|
||||
log_set_target(stderr);
|
||||
LOG("04 Hello World to stderr!\n");
|
||||
LOG_TEE("05 Hello World TEE with double printing to stderr prevented!\n");
|
||||
log_set_target(LOG_DEFAULT_FILE_NAME);
|
||||
LOG("06 Hello World to default log file!\n");
|
||||
log_set_target(stdout);
|
||||
LOG("07 Hello World to stdout!\n");
|
||||
log_set_target(LOG_DEFAULT_FILE_NAME);
|
||||
LOG("08 Hello World to default log file again!\n");
|
||||
log_disable();
|
||||
LOG("09 Hello World _1_ into the void!\n");
|
||||
log_enable();
|
||||
LOG("10 Hello World back from the void ( you should not see _1_ in the log or the output )!\n");
|
||||
log_disable();
|
||||
log_set_target("llama.anotherlog.log");
|
||||
LOG("11 Hello World _2_ to nobody, new target was selected but logs are still disabled!\n");
|
||||
log_enable();
|
||||
LOG("12 Hello World this time in a new file ( you should not see _2_ in the log or the output )?\n");
|
||||
log_set_target("llama.yetanotherlog.log");
|
||||
LOG("13 Hello World this time in yet new file?\n");
|
||||
log_set_target(log_filename_generator("llama_autonamed", "log"));
|
||||
LOG("14 Hello World in log with generated filename!\n");
|
||||
#ifdef _MSC_VER
|
||||
LOG_TEE("15 Hello msvc TEE without arguments\n");
|
||||
LOG_TEE("16 Hello msvc TEE with (%d)(%s) arguments\n", 1, "test");
|
||||
LOG_TEELN("17 Hello msvc TEELN without arguments\n");
|
||||
LOG_TEELN("18 Hello msvc TEELN with (%d)(%s) arguments\n", 1, "test");
|
||||
LOG("19 Hello msvc LOG without arguments\n");
|
||||
LOG("20 Hello msvc LOG with (%d)(%s) arguments\n", 1, "test");
|
||||
LOGLN("21 Hello msvc LOGLN without arguments\n");
|
||||
LOGLN("22 Hello msvc LOGLN with (%d)(%s) arguments\n", 1, "test");
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool log_param_single_parse(const std::string & param)
|
||||
{
|
||||
if ( param == "--log-test")
|
||||
{
|
||||
log_test();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( param == "--log-disable")
|
||||
{
|
||||
log_disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( param == "--log-enable")
|
||||
{
|
||||
log_enable();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (param == "--log-new")
|
||||
{
|
||||
log_multilog(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (param == "--log-append")
|
||||
{
|
||||
log_append(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool log_param_pair_parse(bool check_but_dont_parse, const std::string & param, const std::string & next = std::string())
|
||||
{
|
||||
if ( param == "--log-file")
|
||||
{
|
||||
if (!check_but_dont_parse)
|
||||
{
|
||||
log_set_target(log_filename_generator(next.empty() ? "unnamed" : next, "log"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void log_print_usage()
|
||||
{
|
||||
printf("log options:\n");
|
||||
/* format
|
||||
printf(" -h, --help show this help message and exit\n");*/
|
||||
/* spacing
|
||||
printf("__-param----------------Description\n");*/
|
||||
printf(" --log-test Run simple logging test\n");
|
||||
printf(" --log-disable Disable trace logs\n");
|
||||
printf(" --log-enable Enable trace logs\n");
|
||||
printf(" --log-file Specify a log filename (without extension)\n");
|
||||
printf(" --log-new Create a separate new log file on start. "
|
||||
"Each log file will have unique name: \"<name>.<ID>.log\"\n");
|
||||
printf(" --log-append Don't truncate the old log file.\n");
|
||||
}
|
||||
|
||||
#define log_dump_cmdline(argc, argv) log_dump_cmdline_impl(argc, argv)
|
||||
|
||||
// INTERNAL, DO NOT USE
|
||||
inline void log_dump_cmdline_impl(int argc, char **argv)
|
||||
{
|
||||
std::stringstream buf;
|
||||
for (int i = 0; i < argc; ++i)
|
||||
{
|
||||
if (std::string(argv[i]).find(' ') != std::string::npos)
|
||||
{
|
||||
buf << " \"" << argv[i] <<"\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
buf << " " << argv[i];
|
||||
}
|
||||
}
|
||||
LOGLN("Cmd:%s", buf.str().c_str());
|
||||
}
|
||||
|
||||
#define log_tostr(var) log_var_to_string_impl(var).c_str()
|
||||
|
||||
inline std::string log_var_to_string_impl(bool var)
|
||||
{
|
||||
return var ? "true" : "false";
|
||||
}
|
||||
|
||||
inline std::string log_var_to_string_impl(std::string var)
|
||||
{
|
||||
return var;
|
||||
}
|
||||
|
||||
inline std::string log_var_to_string_impl(const std::vector<int> & var)
|
||||
{
|
||||
std::stringstream buf;
|
||||
buf << "[ ";
|
||||
bool first = true;
|
||||
for (auto e : var)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf << ", ";
|
||||
}
|
||||
buf << std::to_string(e);
|
||||
}
|
||||
buf << " ]";
|
||||
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
inline std::string LOG_TOKENS_TOSTR_PRETTY(const C & ctx, const T & tokens)
|
||||
{
|
||||
std::stringstream buf;
|
||||
buf << "[ ";
|
||||
|
||||
bool first = true;
|
||||
for (const auto &token : tokens)
|
||||
{
|
||||
if (!first) {
|
||||
buf << ", ";
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
auto detokenized = llama_token_to_piece(ctx, token);
|
||||
|
||||
detokenized.erase(
|
||||
std::remove_if(
|
||||
detokenized.begin(),
|
||||
detokenized.end(),
|
||||
[](const unsigned char c) { return !std::isprint(c); }),
|
||||
detokenized.end());
|
||||
|
||||
buf
|
||||
<< "'" << detokenized << "'"
|
||||
<< ":" << std::to_string(token);
|
||||
}
|
||||
buf << " ]";
|
||||
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
template <typename C, typename B>
|
||||
inline std::string LOG_BATCH_TOSTR_PRETTY(const C & ctx, const B & batch)
|
||||
{
|
||||
std::stringstream buf;
|
||||
buf << "[ ";
|
||||
|
||||
bool first = true;
|
||||
for (int i = 0; i < batch.n_tokens; ++i)
|
||||
{
|
||||
if (!first) {
|
||||
buf << ", ";
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
auto detokenized = llama_token_to_piece(ctx, batch.token[i]);
|
||||
|
||||
detokenized.erase(
|
||||
std::remove_if(
|
||||
detokenized.begin(),
|
||||
detokenized.end(),
|
||||
[](const unsigned char c) { return !std::isprint(c); }),
|
||||
detokenized.end());
|
||||
|
||||
buf
|
||||
<< "\n" << std::to_string(i)
|
||||
<< ":token '" << detokenized << "'"
|
||||
<< ":pos " << std::to_string(batch.pos[i])
|
||||
<< ":n_seq_id " << std::to_string(batch.n_seq_id[i])
|
||||
<< ":seq_id " << std::to_string(batch.seq_id[i][0])
|
||||
<< ":logits " << std::to_string(batch.logits[i]);
|
||||
}
|
||||
buf << " ]";
|
||||
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
#ifdef LOG_DISABLE_LOGS
|
||||
|
||||
#undef LOG
|
||||
#define LOG(...) // dummy stub
|
||||
#undef LOGLN
|
||||
#define LOGLN(...) // dummy stub
|
||||
|
||||
#undef LOG_TEE
|
||||
#define LOG_TEE(...) fprintf(stderr, __VA_ARGS__) // convert to normal fprintf
|
||||
|
||||
#undef LOG_TEELN
|
||||
#define LOG_TEELN(...) fprintf(stderr, __VA_ARGS__) // convert to normal fprintf
|
||||
|
||||
#undef LOG_DISABLE
|
||||
#define LOG_DISABLE() // dummy stub
|
||||
|
||||
#undef LOG_ENABLE
|
||||
#define LOG_ENABLE() // dummy stub
|
||||
|
||||
#undef LOG_ENABLE
|
||||
#define LOG_ENABLE() // dummy stub
|
||||
|
||||
#undef LOG_SET_TARGET
|
||||
#define LOG_SET_TARGET(...) // dummy stub
|
||||
|
||||
#undef LOG_DUMP_CMDLINE
|
||||
#define LOG_DUMP_CMDLINE(...) // dummy stub
|
||||
|
||||
#endif // LOG_DISABLE_LOGS
|
||||
@@ -0,0 +1,840 @@
|
||||
#include "common.h"
|
||||
|
||||
#include "llama.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common.cpp"
|
||||
#include "sampling.cpp"
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#elif defined (_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(disable: 4244 4267) // possible loss of data
|
||||
#endif
|
||||
|
||||
#define R2AI_MODEL_PATH "/tmp/mistral-7b-v0.1.Q2_K.gguf"
|
||||
// #define R2AI_MODEL_PATH "/tmp/Utopia-13B.q5_k_m.gguf"
|
||||
// Abuse globals to make things easier to refactor this shitty code
|
||||
static gpt_params params; // SHOULDNT BE A GLOBAL
|
||||
static std::string path_session;
|
||||
static llama_sampling_params *sparams;
|
||||
static std::vector<llama_token> session_tokens;
|
||||
static std::vector<llama_token> guidance_inp;
|
||||
static int guidance_offset = 0;
|
||||
static int original_prompt_len = 0;
|
||||
static std::vector<llama_token> embd_inp;
|
||||
static std::vector<llama_token> inp_pfx;
|
||||
static std::vector<llama_token> inp_sfx;
|
||||
static std::vector<llama_token> cml_pfx;
|
||||
static std::vector<llama_token> cml_sfx;
|
||||
static llama_context ** g_ctx;
|
||||
static llama_context *ctx_guidance = NULL;
|
||||
static llama_model * model = NULL;
|
||||
static llama_model ** g_model = NULL;
|
||||
static gpt_params * g_params;
|
||||
static std::vector<llama_token> * g_input_tokens;
|
||||
static std::ostringstream * g_output_ss;
|
||||
size_t n_matching_session_tokens = 0;
|
||||
static std::vector<llama_token> *g_output_tokens;
|
||||
static llama_context * ctx;
|
||||
static bool is_interacting = false;
|
||||
static int n_ctx_train;
|
||||
static int n_ctx;
|
||||
static struct llama_sampling_context *ctx_sampling;
|
||||
static bool stdin_borken = false;
|
||||
|
||||
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) || defined (_WIN32)
|
||||
static void sigint_handler(int signo) {
|
||||
if (signo == SIGINT) {
|
||||
if (!is_interacting) {
|
||||
is_interacting = true;
|
||||
} else {
|
||||
// console::cleanup();
|
||||
printf ("\n");
|
||||
// llama_print_timings (*g_ctx);
|
||||
_exit(130);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void null_log(ggml_log_level level, const char * text, void * user_data) {
|
||||
(void) level;
|
||||
(void) user_data;
|
||||
// LOG_TEE ("%s", text);
|
||||
}
|
||||
|
||||
static bool cxxreadline(std::string & line, bool multiline_input) {
|
||||
#if defined(_WIN32)
|
||||
std::wstring wline;
|
||||
if (!std::getline (std::wcin, wline)) {
|
||||
// Input stream is bad or EOF received
|
||||
line.clear ();
|
||||
GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0);
|
||||
return false;
|
||||
}
|
||||
int size_needed = WideCharToMultiByte (CP_UTF8, 0, &wline[0], (int)wline.size(), NULL, 0, NULL, NULL);
|
||||
line.resize (size_needed);
|
||||
WideCharToMultiByte (CP_UTF8, 0, &wline[0], (int)wline.size(), &line[0], size_needed, NULL, NULL);
|
||||
#else
|
||||
if (!std::getline (std::cin, line)) {
|
||||
// Input stream is bad or EOF received
|
||||
stdin_borken = true;
|
||||
line.clear ();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (!line.empty()) {
|
||||
char last = line.back();
|
||||
if (last == '/') { // Always return control on '/' symbol
|
||||
line.pop_back();
|
||||
return false;
|
||||
}
|
||||
if (last == '\\') { // '\\' changes the default action
|
||||
line.pop_back();
|
||||
multiline_input = !multiline_input;
|
||||
}
|
||||
}
|
||||
line += '\n';
|
||||
|
||||
// By default, continue input if multiline_input is set
|
||||
return multiline_input;
|
||||
}
|
||||
|
||||
bool main_r2ai_init(const char *model_path) {
|
||||
params.input_prefix = "<s>[INST]";
|
||||
params.input_suffix = "[/INST]";
|
||||
|
||||
params.antiprompt.push_back("[INST]");
|
||||
params.antiprompt.push_back("</s>");
|
||||
params.interactive = true;
|
||||
params.interactive_first = true;
|
||||
params.model = model_path; //
|
||||
// params.prompt = "<s>"; // Act as a helpful assistant for radare2, your name is r2ai.";
|
||||
/// params.prompt = "<s>[INST][/INST]"; // <s>[INST]Act as a helpful assistant for radare2, your name is r2ai[/INST]Sure!";
|
||||
// params.model = "/tmp/phi-2_Q4_K_M.gguf";
|
||||
params.n_predict = -1;
|
||||
if (params.n_ctx != 0 && params.n_ctx < 8) {
|
||||
LOG_TEE("%s: warning: minimum context size is 8, using minimum size.\n", __func__);
|
||||
params.n_ctx = 8;
|
||||
}
|
||||
#if R2
|
||||
params.seed = r_config_get_i (core->config, "r2ai.seed");
|
||||
if (params.seed == 0) {
|
||||
params.seed = LLAMA_DEFAULT_SEED;
|
||||
}
|
||||
#else
|
||||
if (params.seed == LLAMA_DEFAULT_SEED) {
|
||||
params.seed = time(NULL);
|
||||
}
|
||||
#endif
|
||||
g_params = ¶ms;
|
||||
llama_log_set (null_log, nullptr);
|
||||
// log_set_target(log_filename_generator("main", "log"));
|
||||
// log_dump_cmdline (argc, argv);
|
||||
sparams = &g_params->sparams;
|
||||
// TODO: Dump params ?
|
||||
//printf ("Params perplexity: %s\n", LOG_TOSTR(params.perplexity));
|
||||
|
||||
// save choice to use color for later
|
||||
// (note for later: this is a slightly awkward choice)
|
||||
#if 0
|
||||
console::init(params.simple_io, params.use_color);
|
||||
atexit([]() { console::cleanup(); });
|
||||
if (params.logits_all) {
|
||||
printf("\n************\n");
|
||||
printf("%s: please use the 'perplexity' tool for perplexity calculations\n", __func__);
|
||||
printf("************\n\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (params.embedding) {
|
||||
printf("\n************\n");
|
||||
printf("%s: please use the 'embedding' tool for embedding calculations\n", __func__);
|
||||
printf("************\n\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if (params.rope_freq_base != 0.0) {
|
||||
LOG_TEE("%s: warning: changing RoPE frequency base to %g.\n", __func__, params.rope_freq_base);
|
||||
}
|
||||
|
||||
if (params.rope_freq_scale != 0.0) {
|
||||
LOG_TEE("%s: warning: scaling RoPE frequency by %g.\n", __func__, params.rope_freq_scale);
|
||||
}
|
||||
|
||||
LOG_TEE ("%s: build = %d (%s)\n", __func__, LLAMA_BUILD_NUMBER, LLAMA_COMMIT);
|
||||
LOG_TEE ("%s: built with %s for %s\n", __func__, LLAMA_COMPILER, LLAMA_BUILD_TARGET);
|
||||
LOG_TEE ("%s: seed = %u\n", __func__, g_params.seed);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
std::mt19937 rng(params.seed);
|
||||
if (params.random_prompt) {
|
||||
params.prompt = gpt_random_prompt(rng);
|
||||
}
|
||||
printf ("%s: llama backend init\n", __func__);
|
||||
#endif
|
||||
llama_backend_init (g_params->numa);
|
||||
|
||||
g_model = &model;
|
||||
g_ctx = &ctx;
|
||||
|
||||
// load the model and apply lora adapter, if any
|
||||
LOG ("%s: load the model and apply lora adapter, if any\n", __func__);
|
||||
std::tie(model, ctx) = llama_init_from_gpt_params (*g_params);
|
||||
|
||||
if (model == NULL) {
|
||||
LOG_TEE("%s: error: unable to load model\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
if (sparams->cfg_scale > 1.f) {
|
||||
struct llama_context_params lparams = llama_context_params_from_gpt_params (*g_params);
|
||||
ctx_guidance = llama_new_context_with_model (model, lparams);
|
||||
}
|
||||
|
||||
n_ctx_train = llama_n_ctx_train (model);
|
||||
n_ctx = llama_n_ctx (ctx);
|
||||
LOG ("n_ctx: %d\n", n_ctx);
|
||||
|
||||
if (n_ctx > n_ctx_train) {
|
||||
LOG_TEE("%s: warning: model was trained on only %d context tokens (%d specified)\n",
|
||||
__func__, n_ctx_train, n_ctx);
|
||||
}
|
||||
|
||||
path_session = g_params->path_prompt_cache;
|
||||
|
||||
const bool add_bos = llama_should_add_bos_token (model);
|
||||
//printf ("add_bos: %d\n", add_bos);
|
||||
|
||||
if (g_params->interactive_first || g_params->instruct || g_params->chatml || !g_params->prompt.empty() || session_tokens.empty()) {
|
||||
//printf ("tokenize the prompt\n");
|
||||
if (g_params->chatml) {
|
||||
g_params->prompt = "<|im_start|>system\n" + g_params->prompt + "<|im_end|>";
|
||||
}
|
||||
embd_inp = ::llama_tokenize (ctx, g_params->prompt, add_bos, true);
|
||||
} else {
|
||||
//printf ("use session tokens\n");
|
||||
embd_inp = session_tokens;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf ("--> (%s)\n", params.prompt.c_str());
|
||||
printf ("prompt: \"%s\"\n", log_tostr(params.prompt));
|
||||
printf ("tokens: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd_inp).c_str());
|
||||
#endif
|
||||
|
||||
// Should not run without any tokens
|
||||
if (embd_inp.empty()) {
|
||||
embd_inp.push_back (llama_token_bos(model));
|
||||
printf ("embd_inp was considered empty and bos was added: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd_inp).c_str());
|
||||
}
|
||||
|
||||
// Tokenize negative prompt
|
||||
if (ctx_guidance) {
|
||||
//printf ("cfg_negative_prompt: \"%s\"\n", log_tostr (sparams->cfg_negative_prompt));
|
||||
guidance_inp = ::llama_tokenize(ctx_guidance, sparams->cfg_negative_prompt, add_bos, true);
|
||||
//printf ("guidance_inp tokenized: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx_guidance, guidance_inp).c_str());
|
||||
std::vector<llama_token> original_inp = ::llama_tokenize(ctx, g_params->prompt, add_bos, true);
|
||||
// printf ("original_inp tokenized: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, original_inp).c_str());
|
||||
|
||||
original_prompt_len = original_inp.size();
|
||||
guidance_offset = (int)guidance_inp.size() - original_prompt_len;
|
||||
// printf ("original_prompt_len: %s\n", log_tostr(original_prompt_len));
|
||||
// printf ("guidance_offset: %s\n", log_tostr(guidance_offset));
|
||||
}
|
||||
|
||||
if ((int) embd_inp.size() > n_ctx - 4) {
|
||||
LOG_TEE("%s: error: prompt is too long (%d tokens, max %d)\n", __func__, (int) embd_inp.size(), n_ctx - 4);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// debug message about similarity of saved session, if applicable
|
||||
if (!session_tokens.empty()) {
|
||||
for (llama_token id : session_tokens) {
|
||||
if (n_matching_session_tokens >= embd_inp.size() || id != embd_inp[n_matching_session_tokens]) {
|
||||
break;
|
||||
}
|
||||
n_matching_session_tokens++;
|
||||
}
|
||||
#if 0
|
||||
if (params.prompt.empty() && n_matching_session_tokens == embd_inp.size()) {
|
||||
LOG_TEE("%s: using full prompt from session file\n", __func__);
|
||||
} else if (n_matching_session_tokens >= embd_inp.size()) {
|
||||
LOG_TEE("%s: session file has exact match for prompt!\n", __func__);
|
||||
} else if (n_matching_session_tokens < (embd_inp.size() / 2)) {
|
||||
LOG_TEE("%s: warning: session file has low similarity to prompt (%zu / %zu tokens); will mostly be reevaluated\n",
|
||||
__func__, n_matching_session_tokens, embd_inp.size());
|
||||
} else {
|
||||
LOG_TEE("%s: session file matches %zu / %zu tokens of prompt\n",
|
||||
__func__, n_matching_session_tokens, embd_inp.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
// remove any "future" tokens that we might have inherited from the previous session
|
||||
llama_kv_cache_seq_rm (ctx, -1, n_matching_session_tokens, -1);
|
||||
}
|
||||
#if 0
|
||||
LOGLN(
|
||||
"recalculate the cached logits (check): embd_inp.empty() %s, n_matching_session_tokens %zu, embd_inp.size() %zu, session_tokens.size() %zu, embd_inp.size() %zu",
|
||||
log_tostr(embd_inp.empty()), n_matching_session_tokens, embd_inp.size(), session_tokens.size(), embd_inp.size());
|
||||
|
||||
#endif
|
||||
// if we will use the cache for the full prompt without reaching the end of the cache, force
|
||||
// reevaluation of the last token token to recalculate the cached logits
|
||||
if (!embd_inp.empty() && n_matching_session_tokens == embd_inp.size() && session_tokens.size() > embd_inp.size()) {
|
||||
#if 0
|
||||
LOGLN("recalculate the cached logits (do): session_tokens.resize( %zu )", embd_inp.size() - 1);
|
||||
#endif
|
||||
|
||||
session_tokens.resize(embd_inp.size() - 1);
|
||||
}
|
||||
|
||||
// number of tokens to keep when resetting context
|
||||
if (g_params->n_keep < 0 || g_params->n_keep > (int) embd_inp.size() || g_params->instruct || g_params->chatml) {
|
||||
g_params->n_keep = (int)embd_inp.size();
|
||||
}
|
||||
|
||||
// prefix & suffix for instruct mode
|
||||
inp_pfx = ::llama_tokenize(ctx, "\n\n### Instruction:\n\n", add_bos, true);
|
||||
inp_sfx = ::llama_tokenize(ctx, "\n\n### Response:\n\n", false, true);
|
||||
|
||||
#if 0
|
||||
printf ("inp_pfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, inp_pfx).c_str());
|
||||
printf ("inp_sfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, inp_sfx).c_str());
|
||||
#endif
|
||||
|
||||
// chatml prefix & suffix
|
||||
cml_pfx = ::llama_tokenize(ctx, "\n<|im_start|>user\n", add_bos, true);
|
||||
cml_sfx = ::llama_tokenize(ctx, "<|im_end|>\n<|im_start|>assistant\n", false, true);
|
||||
#if 0
|
||||
printf ("cml_pfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, cml_pfx).c_str());
|
||||
printf ("cml_sfx: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, cml_sfx).c_str());
|
||||
#endif
|
||||
// in instruct mode, we inject a prefix and a suffix to each input by the user
|
||||
if (g_params->instruct) {
|
||||
g_params->interactive_first = true;
|
||||
g_params->antiprompt.push_back("### Instruction:\n\n");
|
||||
}
|
||||
// similar for chatml mode
|
||||
else if (g_params->chatml) {
|
||||
g_params->interactive_first = true;
|
||||
g_params->antiprompt.push_back("<|im_start|>user\n");
|
||||
}
|
||||
|
||||
// enable interactive mode if interactive start is specified
|
||||
if (g_params->interactive_first) {
|
||||
g_params->interactive = true;
|
||||
}
|
||||
|
||||
if (g_params->interactive) {
|
||||
#if 1
|
||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||
struct sigaction sigint_action;
|
||||
sigint_action.sa_handler = sigint_handler;
|
||||
sigemptyset (&sigint_action.sa_mask);
|
||||
sigint_action.sa_flags = 0;
|
||||
sigaction (SIGINT, &sigint_action, NULL);
|
||||
#elif defined (_WIN32)
|
||||
auto console_ctrl_handler = +[](DWORD ctrl_type) -> BOOL {
|
||||
return (ctrl_type == CTRL_C_EVENT) ? (sigint_handler (SIGINT), true) : false;
|
||||
};
|
||||
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
|
||||
#endif
|
||||
#endif
|
||||
#if 0
|
||||
if (!g_params->antiprompt.empty()) {
|
||||
for (const auto & antiprompt : g_params->antiprompt) {
|
||||
// LOG_TEE("Reverse prompt: '%s'\n", antiprompt.c_str());
|
||||
if (g_params->verbose_prompt) {
|
||||
auto tmp = ::llama_tokenize(ctx, antiprompt, false, true);
|
||||
for (int i = 0; i < (int) tmp.size(); i++) {
|
||||
LOG_TEE("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_params->input_prefix.empty()) {
|
||||
// LOG_TEE("Input prefix: '%s'\n", g_params->input_prefix.c_str());
|
||||
if (g_params->verbose_prompt) {
|
||||
auto tmp = ::llama_tokenize(ctx, g_params->input_prefix, true, true);
|
||||
for (int i = 0; i < (int) tmp.size(); i++) {
|
||||
LOG_TEE("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_params->input_suffix.empty()) {
|
||||
// LOG_TEE("Input suffix: '%s'\n", g_params->input_suffix.c_str());
|
||||
if (g_params->verbose_prompt) {
|
||||
auto tmp = ::llama_tokenize(ctx, g_params->input_suffix, false, true);
|
||||
for (int i = 0; i < (int) tmp.size(); i++) {
|
||||
LOG_TEE("%6d -> '%s'\n", tmp[i], llama_token_to_piece(ctx, tmp[i]).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if 0
|
||||
LOG_TEE("sampling: \n%s\n", llama_sampling_print(sparams).c_str());
|
||||
LOG_TEE("sampling order: \n%s\n", llama_sampling_order_print(sparams).c_str());
|
||||
LOG_TEE("generate: n_ctx = %d, n_batch = %d, n_predict = %d, n_keep = %d\n",
|
||||
n_ctx, g_params->n_batch, g_params->n_predict, g_params->n_keep);
|
||||
#endif
|
||||
|
||||
if (g_params->interactive) {
|
||||
is_interacting = g_params->interactive_first;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void main_r2ai_fini(void) {
|
||||
if (ctx_guidance) {
|
||||
llama_free (ctx_guidance);
|
||||
}
|
||||
llama_free (ctx);
|
||||
llama_free_model (model);
|
||||
|
||||
llama_sampling_free (ctx_sampling);
|
||||
llama_backend_free ();
|
||||
}
|
||||
|
||||
int main_r2ai_message(const char *message) {
|
||||
if (model == NULL) {
|
||||
printf ("Model not loaded\n");
|
||||
return 0;
|
||||
}
|
||||
bool is_antiprompt = false;
|
||||
bool input_echo = false;
|
||||
int n_past = 0;
|
||||
int n_remain = params.n_predict;
|
||||
int n_consumed = 0;
|
||||
int n_session_consumed = 0;
|
||||
int n_past_guidance = 0;
|
||||
|
||||
std::vector<int> input_tokens; g_input_tokens = &input_tokens;
|
||||
std::vector<int> output_tokens; g_output_tokens = &output_tokens;
|
||||
std::ostringstream output_ss; g_output_ss = &output_ss;
|
||||
|
||||
// the first thing we will do is to output the prompt, so set color accordingly
|
||||
// console::set_display(console::prompt);
|
||||
|
||||
std::vector<llama_token> embd;
|
||||
std::vector<llama_token> embd_guidance;
|
||||
|
||||
ctx_sampling = llama_sampling_init (*sparams);
|
||||
bool breakloop = false;
|
||||
bool eos_found = false;
|
||||
|
||||
while (( (n_remain != 0 && !is_antiprompt) || params.interactive)) {
|
||||
// predict
|
||||
if (!embd.empty ()) {
|
||||
// Note: n_ctx - 4 here is to match the logic for commandline prompt handling via
|
||||
// --prompt or --file which uses the same value.
|
||||
int max_embd_size = n_ctx - 4;
|
||||
|
||||
// Ensure the input doesn't exceed the context size by truncating embd if necessary.
|
||||
if ((int) embd.size() > max_embd_size) {
|
||||
const int skipped_tokens = (int) embd.size() - max_embd_size;
|
||||
embd.resize (max_embd_size);
|
||||
|
||||
// console::set_display(console::error);
|
||||
printf ("<<input too long: skipped %d token%s>>", skipped_tokens, skipped_tokens != 1 ? "s" : "");
|
||||
// console::set_display(console::reset);
|
||||
fflush (stdout);
|
||||
}
|
||||
|
||||
// infinite text generation via context swapping
|
||||
// if we run out of context:
|
||||
// - take the n_keep first tokens from the original prompt (via n_past)
|
||||
// - take half of the last (n_ctx - n_keep) tokens and recompute the logits in batches
|
||||
if (n_past + (int) embd.size() + std::max<int>(0, guidance_offset) > n_ctx) {
|
||||
if (params.n_predict == -2) {
|
||||
LOG_TEE ("\n\n%s: context full and n_predict == -%d => stopping\n", __func__, params.n_predict);
|
||||
break;
|
||||
}
|
||||
|
||||
const int n_left = n_past - params.n_keep - 1;
|
||||
const int n_discard = n_left / 2;
|
||||
|
||||
printf ("context full, swapping: n_past = %d, n_left = %d, n_ctx = %d, n_keep = %d, n_discard = %d\n",
|
||||
n_past, n_left, n_ctx, params.n_keep, n_discard);
|
||||
|
||||
llama_kv_cache_seq_rm (ctx, 0, params.n_keep + 1 , params.n_keep + n_discard + 1);
|
||||
llama_kv_cache_seq_shift (ctx, 0, params.n_keep + 1 + n_discard, n_past, -n_discard);
|
||||
|
||||
n_past -= n_discard;
|
||||
|
||||
if (ctx_guidance) {
|
||||
n_past_guidance -= n_discard;
|
||||
}
|
||||
#if 0
|
||||
printf ("after swap: n_past = %d, n_past_guidance = %d\n", n_past, n_past_guidance);
|
||||
printf ("embd: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd).c_str());
|
||||
printf ("clear session path\n");
|
||||
#endif
|
||||
path_session.clear ();
|
||||
}
|
||||
|
||||
// try to reuse a matching prefix from the loaded session instead of re-eval (via n_past)
|
||||
if (n_session_consumed < (int) session_tokens.size()) {
|
||||
size_t i = 0;
|
||||
for ( ; i < embd.size (); i++) {
|
||||
if (embd[i] != session_tokens[n_session_consumed]) {
|
||||
session_tokens.resize(n_session_consumed);
|
||||
break;
|
||||
}
|
||||
n_past++;
|
||||
n_session_consumed++;
|
||||
if (n_session_consumed >= (int) session_tokens.size()) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
embd.erase (embd.begin(), embd.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate tokens in batches
|
||||
// embd is typically prepared beforehand to fit within a batch, but not always
|
||||
if (ctx_guidance) {
|
||||
int input_size = 0;
|
||||
llama_token *input_buf = NULL;
|
||||
if (n_past_guidance < (int) guidance_inp.size()) {
|
||||
// Guidance context should have the same data with these modifications:
|
||||
//
|
||||
// * Replace the initial prompt
|
||||
// * Shift everything by guidance_offset
|
||||
embd_guidance = guidance_inp;
|
||||
if (embd.begin() + original_prompt_len < embd.end()) {
|
||||
embd_guidance.insert(
|
||||
embd_guidance.end(),
|
||||
embd.begin() + original_prompt_len,
|
||||
embd.end()
|
||||
);
|
||||
}
|
||||
|
||||
input_buf = embd_guidance.data();
|
||||
input_size = embd_guidance.size();
|
||||
|
||||
printf ("guidance context: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd_guidance).c_str());
|
||||
} else {
|
||||
input_buf = embd.data();
|
||||
input_size = embd.size();
|
||||
}
|
||||
|
||||
for (int i = 0; i < input_size; i += params.n_batch) {
|
||||
int n_eval = std::min(input_size - i, params.n_batch);
|
||||
if (llama_decode (ctx_guidance, llama_batch_get_one (input_buf + i, n_eval, n_past_guidance, 0))) {
|
||||
LOG_TEE ("%s : failed to eval\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
n_past_guidance += n_eval;
|
||||
}
|
||||
// printf ("DECODE0 %d neval=%d\n", 0, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int) embd.size(); i += params.n_batch) {
|
||||
int n_eval = (int) embd.size() - i;
|
||||
if (n_eval > params.n_batch) {
|
||||
n_eval = params.n_batch;
|
||||
}
|
||||
// printf ("eval: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, embd).c_str());
|
||||
// printf ("DECODE1 %d embd=%d\n", n_eval, embd[i]);
|
||||
if (llama_decode (ctx, llama_batch_get_one (&embd[i], n_eval, n_past, 0))) {
|
||||
LOG_TEE("%s : failed to eval\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
n_past += n_eval;
|
||||
// printf ("n_past = %d\n", n_past);
|
||||
}
|
||||
if (!embd.empty() && !path_session.empty()) {
|
||||
session_tokens.insert (session_tokens.end(), embd.begin(), embd.end());
|
||||
n_session_consumed = session_tokens.size();
|
||||
}
|
||||
}
|
||||
|
||||
embd.clear ();
|
||||
embd_guidance.clear ();
|
||||
|
||||
if ((int) embd_inp.size() <= n_consumed && !is_interacting) {
|
||||
const llama_token id = llama_sampling_sample (ctx_sampling, ctx, ctx_guidance);
|
||||
llama_sampling_accept (ctx_sampling, ctx, id, true);
|
||||
// printf ("last: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, ctx_sampling->prev).c_str());
|
||||
embd.push_back (id);
|
||||
// echo this to console
|
||||
input_echo = true;
|
||||
// decrement remaining sampling budget
|
||||
n_remain--;
|
||||
} else {
|
||||
// some user input remains from prompt or interaction, forward it to processing
|
||||
// printf ("embd_inp.size(): %d, n_consumed: %d\n", (int) embd_inp.size(), n_consumed);
|
||||
while ((int) embd_inp.size() > n_consumed) {
|
||||
// printf ("PUSH oBACK %d\n", embd_inp[n_consumed]);
|
||||
embd.push_back (embd_inp[n_consumed]);
|
||||
|
||||
// push the prompt in the sampling context in order to apply repetition penalties later
|
||||
// for the prompt, we don't apply grammar rules
|
||||
llama_sampling_accept (ctx_sampling, ctx, embd_inp[n_consumed], false);
|
||||
n_consumed++;
|
||||
if ((int) embd.size() >= params.n_batch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// display text
|
||||
if (input_echo) {
|
||||
for (auto id : embd) {
|
||||
const std::string token_str = llama_token_to_piece (ctx, id);
|
||||
if (token_str == "INST") {
|
||||
// HACK
|
||||
eos_found = true;
|
||||
break;
|
||||
}
|
||||
printf ("%s", token_str.c_str ());
|
||||
if (embd.size () > 1) {
|
||||
input_tokens.push_back (id);
|
||||
} else {
|
||||
output_tokens.push_back (id);
|
||||
output_ss << token_str;
|
||||
}
|
||||
}
|
||||
fflush (stdout);
|
||||
}
|
||||
#if 0
|
||||
// reset color to default if there is no pending user input
|
||||
if (input_echo && (int) embd_inp.size() == n_consumed) {
|
||||
console::set_display(console::reset);
|
||||
}
|
||||
#endif
|
||||
// if not currently processing queued inputs;
|
||||
if ((int) embd_inp.size() <= n_consumed) {
|
||||
// check for reverse prompt in the last n_prev tokens
|
||||
if (!params.antiprompt.empty ()) {
|
||||
const int n_prev = 32;
|
||||
const std::string last_output = llama_sampling_prev_str (ctx_sampling, ctx, n_prev);
|
||||
|
||||
is_antiprompt = false;
|
||||
// Check if each of the reverse prompts appears at the end of the output.
|
||||
// If we're not running interactively, the reverse prompt might be tokenized with some following characters
|
||||
// so we'll compensate for that by widening the search window a bit.
|
||||
for (std::string & antiprompt : params.antiprompt) {
|
||||
size_t extra_padding = params.interactive ? 0 : 2;
|
||||
size_t search_start_pos = last_output.length() > static_cast<size_t>(antiprompt.length() + extra_padding)
|
||||
? last_output.length () - static_cast<size_t>(antiprompt.length() + extra_padding)
|
||||
: 0;
|
||||
|
||||
if (last_output.find (antiprompt, search_start_pos) != std::string::npos) {
|
||||
if (params.interactive) {
|
||||
is_interacting = true;
|
||||
}
|
||||
is_antiprompt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deal with end of text token in interactive mode
|
||||
// printf ("%d %d\n", llama_sampling_last (ctx_sampling), llama_token_eos (model));
|
||||
if (eos_found || llama_sampling_last (ctx_sampling) == llama_token_eos (model)) {
|
||||
// printf ("found EOS token\n");
|
||||
|
||||
if (params.interactive) {
|
||||
if (!params.antiprompt.empty()) {
|
||||
// tokenize and inject first reverse prompt
|
||||
const auto first_antiprompt = ::llama_tokenize(ctx, params.antiprompt.front(), false, true);
|
||||
embd_inp.insert (embd_inp.end(), first_antiprompt.begin(), first_antiprompt.end());
|
||||
is_antiprompt = true;
|
||||
}
|
||||
|
||||
is_interacting = true;
|
||||
printf ("\n");
|
||||
} else if (params.instruct || params.chatml) {
|
||||
is_interacting = true;
|
||||
}
|
||||
if (breakloop) {
|
||||
// printf ("BreakLoop\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!breakloop)
|
||||
if (message != nullptr || (n_past > 0 && is_interacting)) {
|
||||
if (params.instruct || params.chatml) {
|
||||
printf ("\n> ");
|
||||
}
|
||||
|
||||
if (params.input_prefix_bos) {
|
||||
embd_inp.push_back (llama_token_bos(model));
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
if (!params.input_prefix.empty()) {
|
||||
LOG ("appending input prefix: '%s'\n", params.input_prefix.c_str());
|
||||
// printf("(%s)", params.input_prefix.c_str());
|
||||
}
|
||||
|
||||
// color user input only
|
||||
// console::set_display(console::user_input);
|
||||
|
||||
std::string line;
|
||||
if (breakloop) {
|
||||
// printf ("BreakLoop\n");
|
||||
// break;
|
||||
}
|
||||
bool another_line = true;
|
||||
if (message == nullptr) {
|
||||
// initial prompt
|
||||
do {
|
||||
another_line = cxxreadline (line, params.multiline_input);
|
||||
buffer += line;
|
||||
} while (another_line);
|
||||
if (stdin_borken) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
buffer += message;
|
||||
// message = nullptr;
|
||||
// message = nullptr;
|
||||
breakloop = true;
|
||||
}
|
||||
|
||||
// done taking input, reset color
|
||||
// console::set_display(console::reset);
|
||||
|
||||
// Add tokens to embd only if the input buffer is non-empty
|
||||
// Entering a empty line lets the user pass control back
|
||||
if (buffer.length() > 1) {
|
||||
// printf ("POP BACK %d\n", buffer.length());
|
||||
// append input suffix if any
|
||||
#if 0
|
||||
if (!params.input_suffix.empty()) {
|
||||
// printf ("appending input suffix: '%s'\n", params.input_suffix.c_str());
|
||||
// printf("%s", params.input_suffix.c_str());
|
||||
}
|
||||
// printf ("buffer: '%s'\n", buffer.c_str());
|
||||
#endif
|
||||
const size_t original_size = embd_inp.size();
|
||||
|
||||
// instruct mode: insert instruction prefix
|
||||
if (params.instruct && !is_antiprompt) {
|
||||
n_consumed = embd_inp.size();
|
||||
embd_inp.insert(embd_inp.end(), inp_pfx.begin(), inp_pfx.end());
|
||||
}
|
||||
// chatml mode: insert user chat prefix
|
||||
if (params.chatml && !is_antiprompt) {
|
||||
n_consumed = embd_inp.size();
|
||||
embd_inp.insert(embd_inp.end(), cml_pfx.begin(), cml_pfx.end());
|
||||
}
|
||||
if (params.escape) {
|
||||
process_escapes (buffer);
|
||||
}
|
||||
|
||||
const auto line_pfx = ::llama_tokenize(ctx, params.input_prefix, false, true);
|
||||
const auto line_inp = ::llama_tokenize(ctx, buffer, false, false);
|
||||
const auto line_sfx = ::llama_tokenize(ctx, params.input_suffix, false, true);
|
||||
// printf ("input tokens: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, line_inp).c_str());
|
||||
|
||||
embd_inp.insert(embd_inp.end(), line_pfx.begin(), line_pfx.end());
|
||||
embd_inp.insert(embd_inp.end(), line_inp.begin(), line_inp.end());
|
||||
embd_inp.insert(embd_inp.end(), line_sfx.begin(), line_sfx.end());
|
||||
|
||||
// instruct mode: insert response suffix
|
||||
if (params.instruct) {
|
||||
embd_inp.insert(embd_inp.end(), inp_sfx.begin(), inp_sfx.end());
|
||||
}
|
||||
// chatml mode: insert assistant chat suffix
|
||||
if (params.chatml) {
|
||||
embd_inp.insert(embd_inp.end(), cml_sfx.begin(), cml_sfx.end());
|
||||
}
|
||||
|
||||
for (size_t i = original_size; i < embd_inp.size(); ++i) {
|
||||
const llama_token token = embd_inp[i];
|
||||
output_tokens.push_back(token);
|
||||
output_ss << llama_token_to_piece(ctx, token);
|
||||
}
|
||||
n_remain -= line_inp.size();
|
||||
}
|
||||
|
||||
input_echo = false; // do not echo this again
|
||||
}
|
||||
|
||||
if (n_past > 0) {
|
||||
// printf ("npast > 0\n");
|
||||
if (is_interacting) {
|
||||
llama_sampling_reset (ctx_sampling);
|
||||
}
|
||||
is_interacting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// end of text token
|
||||
if (!embd.empty() && embd.back() == llama_token_eos(model) && !(params.instruct || params.interactive || params.chatml)) {
|
||||
printf (" [end of text]\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// In interactive mode, respect the maximum number of tokens and drop back to user input when reached.
|
||||
// We skip this logic when n_predict == -1 (infinite) or -2 (stop at context size).
|
||||
if (params.interactive && n_remain <= 0 && params.n_predict >= 0) {
|
||||
n_remain = params.n_predict;
|
||||
is_interacting = true;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern bool main_r2ai_preinit(int argc, char **argv) {
|
||||
const char *_argv[] = {"r2ai"};
|
||||
if (argc < 1 || argv == nullptr) {
|
||||
argc = 1;
|
||||
argv = (char **)_argv;
|
||||
}
|
||||
if (!gpt_params_parse (argc, argv, params)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !R2AI
|
||||
int main(int argc, char **argv) {
|
||||
if (!main_r2ai_preinit (argc, argv)) {
|
||||
return 1;
|
||||
}
|
||||
if (!gpt_params_parse (argc, (char **)argv, params)) {
|
||||
}
|
||||
main_r2ai_init (R2AI_MODEL_PATH);
|
||||
main_r2ai_message (NULL); // if null just get into the repl
|
||||
main_r2ai_fini ();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
/* radare - Copyright 2023 - pancake */
|
||||
|
||||
#define R_LOG_ORIGIN "r2ai"
|
||||
|
||||
#include <r_core.h>
|
||||
#include "common.h"
|
||||
|
||||
#define R2AI_HELP_MESSAGE \
|
||||
"Usage: r2ai [-option] ([query] | [script.py])\n"\
|
||||
" r2ai . [file] interpret r2ai script with access to globals\n"\
|
||||
" r2ai :aa run a r2 command\n"\
|
||||
" r2ai !ls run a system command\n"\
|
||||
" r2ai -a query with audio voice\n"\
|
||||
" r2ai -A enter the voice chat loop\n"\
|
||||
" r2ai -k clear the screen\n"\
|
||||
" r2ai -c [cmd] [query] run the given r2 command with the given query\n"\
|
||||
" r2ai -e [k[=v]] set environment variable\n"\
|
||||
" r2ai -f [file] load file and paste the output\n"\
|
||||
" r2ai -h show this help\n"\
|
||||
" r2ai -i [file] [query] load the file contents and prompt it with the given query\n"\
|
||||
" r2ai -m [file/repo] select model from huggingface repository or local file\n"\
|
||||
" r2ai -M list supported and most common models from hf\n"\
|
||||
" r2ai -n [num] select the nth language model\n"\
|
||||
" r2ai -q quit/exit/^C\n"\
|
||||
" r2ai -L show chat logs\n"\
|
||||
" r2ai -r [sysprompt] define the role of the conversation\n"\
|
||||
" r2ai -r2 enter the r2clippy assistant mode\n"\
|
||||
" r2ai -rf [doc/role/.f] load contents of a file to define the role\n"\
|
||||
" r2ai -R reset the chat conversation context\n"\
|
||||
" r2ai -t [temp] from 0.0001 to 10 your scale to randomness in my replies\n"\
|
||||
" r2ai -v show r2ai version\n"\
|
||||
" r2ai -w toggle including LLM responses into the query (False is faster)\n"
|
||||
|
||||
static void r2ai_parseflag(RCore *core, const char *input) {
|
||||
switch (*input) {
|
||||
case 'e':
|
||||
r_core_cmd0 (core, "-e r2ai.");
|
||||
break;
|
||||
case 'v':
|
||||
r_cons_printf ("r2ai-native-v0.1\n");
|
||||
#if 0
|
||||
r_cons_printf ("%s: build = %d (%s)\n", __func__, LLAMA_BUILD_NUMBER, LLAMA_COMMIT);
|
||||
r_cons_printf ("%s: built with %s for %s\n", __func__, LLAMA_COMPILER, LLAMA_BUILD_TARGET);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
R_LOG_ERROR ("Unknown flag");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern int main_r2ai_message(const char *message);
|
||||
extern bool main_r2ai_init(const char *model_path);
|
||||
extern bool main_r2ai_preinit(int argc, char **argv);
|
||||
|
||||
static void r2ai_message(RCore *core, const char *input) {
|
||||
const char *model_path = r_config_get (core->config, "r2ai.model");
|
||||
main_r2ai_init (model_path);
|
||||
const char *prompt = "Act as a radare2 assistant named r2ai";
|
||||
const char *prompt_reply = "Sure!";
|
||||
char *s = r_str_newf ("<s>[INST]%s[/INST]%s</s>%s", prompt, prompt_reply, input);
|
||||
main_r2ai_message (input);
|
||||
free (s);
|
||||
}
|
||||
|
||||
static int r_cmd_r2ai_init(void *user, const char *input) {
|
||||
RCmd *rcmd = (RCmd*)user;
|
||||
RCore *core = (RCore *) rcmd->data;
|
||||
if (core) {
|
||||
RConfig *cfg = core->config;
|
||||
r_config_lock (cfg, false);
|
||||
r_config_set (cfg, "r2ai.model", "/tmp/mistral-7b-v0.1.Q2_K.gguf");
|
||||
r_config_set (cfg, "r2ai.temp", "0.02");
|
||||
r_config_lock (cfg, true);
|
||||
} else {
|
||||
R_LOG_INFO ("Can't init");
|
||||
}
|
||||
main_r2ai_preinit (0, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int r_cmd_r2ai_fini(void *user, const char *input) {
|
||||
RCmd *rcmd = (RCmd*)user;
|
||||
RCore *core = (RCore *) rcmd->data;
|
||||
if (core) {
|
||||
RConfig *cfg = core->config;
|
||||
r_config_lock (cfg, false);
|
||||
r_config_rm (cfg, "r2ai.model");
|
||||
r_config_rm (cfg, "r2ai.temp");
|
||||
r_config_lock (cfg, true);
|
||||
} else {
|
||||
R_LOG_INFO ("Can't init");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int r_cmd_r2ai_native(void *user, const char *input) {
|
||||
RCore *core = (RCore *) user;
|
||||
if (r_str_startswith (input, "r2ai")) {
|
||||
if (input[4] == ' ') {
|
||||
const char *arg = r_str_trim_head_ro (input + 4);
|
||||
if (*arg == '-') {
|
||||
r2ai_parseflag (core, arg + 1);
|
||||
} else {
|
||||
r2ai_message (core, r_str_trim_head_ro (arg));
|
||||
}
|
||||
} else {
|
||||
r_cons_printf (R2AI_HELP_MESSAGE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// PLUGIN Definition Info
|
||||
RCorePlugin r_core_plugin_hello = {
|
||||
.meta = {
|
||||
.name = (char *)"r2ai-native",
|
||||
.desc = (char *)"native r2ai plugin",
|
||||
.author = (char *)"pancake",
|
||||
.license = (char *)"MIT",
|
||||
},
|
||||
.call = r_cmd_r2ai_native,
|
||||
.init = r_cmd_r2ai_init,
|
||||
.fini = r_cmd_r2ai_fini
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
R_API RLibStruct radare_plugin = {
|
||||
.type = R_LIB_TYPE_CORE,
|
||||
.data = &r_core_plugin_hello,
|
||||
.version = R2_VERSION
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,280 @@
|
||||
#include "sampling.h"
|
||||
|
||||
struct llama_sampling_context * llama_sampling_init(const struct llama_sampling_params & params) {
|
||||
struct llama_sampling_context * result = new llama_sampling_context();
|
||||
|
||||
result->params = params;
|
||||
#if 0
|
||||
result->grammar = nullptr;
|
||||
|
||||
// if there is a grammar, parse it
|
||||
if (!params.grammar.empty()) {
|
||||
result->parsed_grammar = grammar_parser::parse(params.grammar.c_str());
|
||||
|
||||
// will be empty (default) if there are parse errors
|
||||
if (result->parsed_grammar.rules.empty()) {
|
||||
fprintf(stderr, "%s: failed to parse grammar\n", __func__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const llama_grammar_element *> grammar_rules(result->parsed_grammar.c_rules());
|
||||
|
||||
result->grammar = llama_grammar_init(
|
||||
grammar_rules.data(),
|
||||
grammar_rules.size(), result->parsed_grammar.symbol_ids.at("root"));
|
||||
}
|
||||
#endif
|
||||
|
||||
result->prev.resize(params.n_prev);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void llama_sampling_free(struct llama_sampling_context * ctx) {
|
||||
#if 0
|
||||
if (ctx->grammar != NULL) {
|
||||
llama_grammar_free(ctx->grammar);
|
||||
}
|
||||
#endif
|
||||
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
void llama_sampling_reset(llama_sampling_context * ctx) {
|
||||
#if 0
|
||||
if (ctx->grammar != NULL) {
|
||||
llama_grammar_free(ctx->grammar);
|
||||
ctx->grammar = NULL;
|
||||
}
|
||||
|
||||
if (!ctx->parsed_grammar.rules.empty()) {
|
||||
std::vector<const llama_grammar_element *> grammar_rules(ctx->parsed_grammar.c_rules());
|
||||
|
||||
ctx->grammar = llama_grammar_init(
|
||||
grammar_rules.data(),
|
||||
grammar_rules.size(), ctx->parsed_grammar.symbol_ids.at("root"));
|
||||
}
|
||||
#endif
|
||||
|
||||
std::fill(ctx->prev.begin(), ctx->prev.end(), 0);
|
||||
ctx->cur.clear();
|
||||
}
|
||||
|
||||
void llama_sampling_cp(llama_sampling_context * src, llama_sampling_context * dst) {
|
||||
#if 0
|
||||
if (dst->grammar) {
|
||||
llama_grammar_free(dst->grammar);
|
||||
dst->grammar = nullptr;
|
||||
}
|
||||
|
||||
if (src->grammar) {
|
||||
dst->grammar = llama_grammar_copy(src->grammar);
|
||||
}
|
||||
#endif
|
||||
dst->prev = src->prev;
|
||||
}
|
||||
|
||||
llama_token llama_sampling_last(llama_sampling_context * ctx) {
|
||||
return ctx->prev.back();
|
||||
}
|
||||
|
||||
std::string llama_sampling_prev_str(llama_sampling_context * ctx_sampling, llama_context * ctx_main, int n) {
|
||||
const int size = ctx_sampling->prev.size();
|
||||
|
||||
n = std::min(n, size);
|
||||
|
||||
std::string result;
|
||||
|
||||
for (int i = size - n; i < size; i++) {
|
||||
result += llama_token_to_piece(ctx_main, ctx_sampling->prev[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string llama_sampling_print(const llama_sampling_params & params) {
|
||||
char result[1024];
|
||||
|
||||
snprintf(result, sizeof(result),
|
||||
"\trepeat_last_n = %d, repeat_penalty = %.3f, frequency_penalty = %.3f, presence_penalty = %.3f\n"
|
||||
"\ttop_k = %d, tfs_z = %.3f, top_p = %.3f, min_p = %.3f, typical_p = %.3f, temp = %.3f\n"
|
||||
"\tmirostat = %d, mirostat_lr = %.3f, mirostat_ent = %.3f",
|
||||
params.penalty_last_n, params.penalty_repeat, params.penalty_freq, params.penalty_present,
|
||||
params.top_k, params.tfs_z, params.top_p, params.min_p, params.typical_p, params.temp,
|
||||
params.mirostat, params.mirostat_eta, params.mirostat_tau);
|
||||
|
||||
return std::string(result);
|
||||
}
|
||||
|
||||
std::string llama_sampling_order_print(const llama_sampling_params & params) {
|
||||
std::string result = "CFG -> Penalties ";
|
||||
if (params.mirostat == 0) {
|
||||
for (auto s : params.samplers_sequence) {
|
||||
switch (s) {
|
||||
case 'k': result += "-> top_k "; break;
|
||||
case 'f': result += "-> tfs_z "; break;
|
||||
case 'y': result += "-> typical_p "; break;
|
||||
case 'p': result += "-> top_p "; break;
|
||||
case 'm': result += "-> min_p "; break;
|
||||
case 't': result += "-> temp "; break;
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += "-> mirostat ";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// no reasons to expose this function in header
|
||||
static void sampler_queue(
|
||||
struct llama_context * ctx_main,
|
||||
const llama_sampling_params & params,
|
||||
llama_token_data_array & cur_p,
|
||||
size_t & min_keep) {
|
||||
const int n_vocab = llama_n_vocab(llama_get_model(ctx_main));
|
||||
|
||||
const float temp = params.temp;
|
||||
const int32_t top_k = params.top_k <= 0 ? n_vocab : params.top_k;
|
||||
const float top_p = params.top_p;
|
||||
const float min_p = params.min_p;
|
||||
const float tfs_z = params.tfs_z;
|
||||
const float typical_p = params.typical_p;
|
||||
const std::string & samplers_sequence = params.samplers_sequence;
|
||||
|
||||
for (auto s : samplers_sequence) {
|
||||
switch (s){
|
||||
case 'k': llama_sample_top_k (ctx_main, &cur_p, top_k, min_keep); break;
|
||||
case 'f': llama_sample_tail_free(ctx_main, &cur_p, tfs_z, min_keep); break;
|
||||
case 'y': llama_sample_typical (ctx_main, &cur_p, typical_p, min_keep); break;
|
||||
case 'p': llama_sample_top_p (ctx_main, &cur_p, top_p, min_keep); break;
|
||||
case 'm': llama_sample_min_p (ctx_main, &cur_p, min_p, min_keep); break;
|
||||
case 't': llama_sample_temp (ctx_main, &cur_p, temp); break;
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
llama_token llama_sampling_sample(
|
||||
struct llama_sampling_context * ctx_sampling,
|
||||
struct llama_context * ctx_main,
|
||||
struct llama_context * ctx_cfg,
|
||||
const int idx) {
|
||||
const llama_sampling_params & params = ctx_sampling->params;
|
||||
|
||||
const int n_vocab = llama_n_vocab(llama_get_model(ctx_main));
|
||||
|
||||
const float temp = params.temp;
|
||||
const int32_t penalty_last_n = params.penalty_last_n < 0 ? params.n_prev : params.penalty_last_n;
|
||||
const float penalty_repeat = params.penalty_repeat;
|
||||
const float penalty_freq = params.penalty_freq;
|
||||
const float penalty_present = params.penalty_present;
|
||||
const int mirostat = params.mirostat;
|
||||
const float mirostat_tau = params.mirostat_tau;
|
||||
const float mirostat_eta = params.mirostat_eta;
|
||||
const bool penalize_nl = params.penalize_nl;
|
||||
|
||||
auto & prev = ctx_sampling->prev;
|
||||
auto & cur = ctx_sampling->cur;
|
||||
|
||||
llama_token id = 0;
|
||||
|
||||
float * logits = llama_get_logits_ith(ctx_main, idx);
|
||||
|
||||
// apply params.logit_bias map
|
||||
for (auto it = params.logit_bias.begin(); it != params.logit_bias.end(); it++) {
|
||||
logits[it->first] += it->second;
|
||||
}
|
||||
|
||||
cur.clear();
|
||||
|
||||
for (llama_token token_id = 0; token_id < n_vocab; token_id++) {
|
||||
cur.emplace_back(llama_token_data{token_id, logits[token_id], 0.0f});
|
||||
}
|
||||
|
||||
llama_token_data_array cur_p = { cur.data(), cur.size(), false };
|
||||
|
||||
if (ctx_cfg) {
|
||||
llama_sample_classifier_free_guidance(ctx_main, &cur_p, ctx_cfg, params.cfg_scale);
|
||||
}
|
||||
|
||||
// apply penalties
|
||||
if (!prev.empty()) {
|
||||
const float nl_logit = logits[llama_token_nl(llama_get_model(ctx_main))];
|
||||
|
||||
llama_sample_repetition_penalties(ctx_main, &cur_p,
|
||||
prev.data() + prev.size() - penalty_last_n,
|
||||
penalty_last_n, penalty_repeat, penalty_freq, penalty_present);
|
||||
|
||||
if (!penalize_nl) {
|
||||
for (size_t idx = 0; idx < cur_p.size; idx++) {
|
||||
if (cur_p.data[idx].id == llama_token_nl(llama_get_model(ctx_main))) {
|
||||
cur_p.data[idx].logit = nl_logit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (ctx_sampling->grammar != NULL) {
|
||||
llama_sample_grammar(ctx_main, &cur_p, ctx_sampling->grammar);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (temp < 0.0) {
|
||||
// greedy sampling, with probs
|
||||
llama_sample_softmax(ctx_main, &cur_p);
|
||||
id = cur_p.data[0].id;
|
||||
} else if (temp == 0.0) {
|
||||
// greedy sampling, no probs
|
||||
id = llama_sample_token_greedy(ctx_main, &cur_p);
|
||||
} else {
|
||||
if (mirostat == 1) {
|
||||
const int mirostat_m = 100;
|
||||
llama_sample_temp(ctx_main, &cur_p, temp);
|
||||
id = llama_sample_token_mirostat(ctx_main, &cur_p, mirostat_tau, mirostat_eta, mirostat_m, &ctx_sampling->mirostat_mu);
|
||||
} else if (mirostat == 2) {
|
||||
llama_sample_temp(ctx_main, &cur_p, temp);
|
||||
id = llama_sample_token_mirostat_v2(ctx_main, &cur_p, mirostat_tau, mirostat_eta, &ctx_sampling->mirostat_mu);
|
||||
} else {
|
||||
// temperature sampling
|
||||
size_t min_keep = std::max(1, params.n_probs);
|
||||
|
||||
sampler_queue(ctx_main, params, cur_p, min_keep);
|
||||
|
||||
id = llama_sample_token(ctx_main, &cur_p);
|
||||
|
||||
//{
|
||||
// const int n_top = 10;
|
||||
// LOG("top %d candidates:\n", n_top);
|
||||
|
||||
// for (int i = 0; i < n_top; i++) {
|
||||
// const llama_token id = cur_p.data[i].id;
|
||||
// (void)id; // To avoid a warning that id is unused when logging is disabled.
|
||||
// LOG(" - %5d: '%12s' (%.3f)\n", id, llama_token_to_piece(ctx_main, id).c_str(), cur_p.data[i].p);
|
||||
// }
|
||||
//}
|
||||
|
||||
LOG("sampled token: %5d: '%s'\n", id, llama_token_to_piece(ctx_main, id).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void llama_sampling_accept(
|
||||
struct llama_sampling_context * ctx_sampling,
|
||||
struct llama_context * ctx_main,
|
||||
llama_token id,
|
||||
bool apply_grammar) {
|
||||
ctx_sampling->prev.erase(ctx_sampling->prev.begin());
|
||||
ctx_sampling->prev.push_back(id);
|
||||
|
||||
#if 0
|
||||
if (ctx_sampling->grammar != NULL && apply_grammar) {
|
||||
llama_grammar_accept_token(ctx_main, ctx_sampling->grammar, id);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <llama.h>
|
||||
|
||||
// #include "grammar-parser.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
// sampling parameters
|
||||
typedef struct llama_sampling_params {
|
||||
int32_t n_prev = 64; // number of previous tokens to remember
|
||||
int32_t n_probs = 0; // if greater than 0, output the probabilities of top n_probs tokens.
|
||||
int32_t top_k = 40; // <= 0 to use vocab size
|
||||
float top_p = 0.95f; // 1.0 = disabled
|
||||
float min_p = 0.05f; // 0.0 = disabled
|
||||
float tfs_z = 1.00f; // 1.0 = disabled
|
||||
float typical_p = 1.00f; // 1.0 = disabled
|
||||
float temp = 0.80f; // 1.0 = disabled
|
||||
int32_t penalty_last_n = 64; // last n tokens to penalize (0 = disable penalty, -1 = context size)
|
||||
float penalty_repeat = 1.10f; // 1.0 = disabled
|
||||
float penalty_freq = 0.00f; // 0.0 = disabled
|
||||
float penalty_present = 0.00f; // 0.0 = disabled
|
||||
int32_t mirostat = 0; // 0 = disabled, 1 = mirostat, 2 = mirostat 2.0
|
||||
float mirostat_tau = 5.00f; // target entropy
|
||||
float mirostat_eta = 0.10f; // learning rate
|
||||
bool penalize_nl = true; // consider newlines as a repeatable token
|
||||
std::string samplers_sequence = "kfypmt"; // top_k, tail_free, typical_p, top_p, min_p, temp
|
||||
|
||||
std::string grammar; // optional BNF-like grammar to constrain sampling
|
||||
|
||||
// Classifier-Free Guidance
|
||||
// https://arxiv.org/abs/2306.17806
|
||||
std::string cfg_negative_prompt; // string to help guidance
|
||||
float cfg_scale = 1.f; // how strong is guidance
|
||||
|
||||
std::unordered_map<llama_token, float> logit_bias; // logit bias for specific tokens
|
||||
} llama_sampling_params;
|
||||
|
||||
// general sampler context
|
||||
// TODO: move to llama.h
|
||||
struct llama_sampling_context {
|
||||
// parameters that will be used for sampling
|
||||
llama_sampling_params params;
|
||||
|
||||
// mirostat sampler state
|
||||
float mirostat_mu;
|
||||
|
||||
llama_grammar * grammar;
|
||||
|
||||
// internal
|
||||
// grammar_parser::parse_state parsed_grammar;
|
||||
|
||||
// TODO: replace with ring-buffer
|
||||
std::vector<llama_token> prev;
|
||||
std::vector<llama_token_data> cur;
|
||||
};
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// Create a new sampling context instance.
|
||||
struct llama_sampling_context * llama_sampling_init(const struct llama_sampling_params & params);
|
||||
|
||||
void llama_sampling_free(struct llama_sampling_context * ctx);
|
||||
|
||||
// Reset the sampler context
|
||||
// - clear prev tokens
|
||||
// - reset grammar
|
||||
void llama_sampling_reset(llama_sampling_context * ctx);
|
||||
|
||||
// Copy the sampler context
|
||||
void llama_sampling_cp(llama_sampling_context * src, llama_sampling_context * dst);
|
||||
|
||||
// Get the last sampled token
|
||||
llama_token llama_sampling_last(llama_sampling_context * ctx);
|
||||
|
||||
// Get a string representation of the last sampled tokens
|
||||
std::string llama_sampling_prev_str(llama_sampling_context * ctx_sampling, llama_context * ctx_main, int n);
|
||||
|
||||
// Print sampling parameters into a string
|
||||
std::string llama_sampling_print(const llama_sampling_params & params);
|
||||
|
||||
// Print sampling order into a string
|
||||
std::string llama_sampling_order_print(const llama_sampling_params & params);
|
||||
|
||||
// this is a common sampling function used across the examples for convenience
|
||||
// it can serve as a starting point for implementing your own sampling function
|
||||
// Note: When using multiple sequences, it is the caller's responsibility to call
|
||||
// llama_sampling_reset when a sequence ends
|
||||
//
|
||||
// required:
|
||||
// - ctx_main: context to use for sampling
|
||||
// - ctx_sampling: sampling-specific context
|
||||
//
|
||||
// optional:
|
||||
// - ctx_cfg: context to use for classifier-free guidance
|
||||
// - idx: sample from llama_get_logits_ith(ctx, idx)
|
||||
//
|
||||
// returns:
|
||||
// - token: sampled token
|
||||
// - candidates: vector of candidate tokens
|
||||
//
|
||||
llama_token llama_sampling_sample(
|
||||
struct llama_sampling_context * ctx_sampling,
|
||||
struct llama_context * ctx_main,
|
||||
struct llama_context * ctx_cfg,
|
||||
int idx = 0);
|
||||
|
||||
void llama_sampling_accept(
|
||||
struct llama_sampling_context * ctx_sampling,
|
||||
struct llama_context * ctx_main,
|
||||
llama_token id,
|
||||
bool apply_grammar);
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Launch r2ai and use this prompt to generate the conversation from data.txt
|
||||
# Copypaste the output into podcast.txt
|
||||
#
|
||||
# -f /tmp/data.txt Create a script for a podcast between two technical people named Sam and Max, about the changelog on radare2 5.9.4, focus on important features and stuff that impacts positively to users. The length must be as long as possible. Do not show any "**" section, output must contain only the conversation, be emotional and make the two people ask questions to learn more about the details of each feature
|
||||
|
||||
if [ ! -f podcast.txt ]; then
|
||||
echo "Missing podcast.txt. Please read the script before running it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat podcast.txt | sed -e 's/Sam:/[[pbas 40]]/g' -e 's/Max:/[[pbas 60]]/g' > podcast.say.txt
|
||||
say -f podcast.say.txt -o podcast.aiff
|
||||
ffmpeg -i podcast.aiff podcast.mp3
|
||||
@@ -0,0 +1,6 @@
|
||||
import sys
|
||||
print(sys.argv)
|
||||
print("Explain this function")
|
||||
print(r2pipe)
|
||||
print(r2)
|
||||
print(r2.cmd("pd 20"))
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# pip install ddgr
|
||||
ddgr --json $@
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
URLS=`./scrap-ddg.sh -n2 -- $@ |jq '.[].url'`
|
||||
for URL in ${URLS}; do
|
||||
eval ./scrap-web.sh ${URL}
|
||||
done
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
if [ -z "$1" ]; then
|
||||
echo "Gimme an url to scrap"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# https://github.com/aaronsw/html2text =>
|
||||
# https://raw.githubusercontent.com/aaronsw/html2text/master/README.md
|
||||
|
||||
curl -s "$1" | html2text -width 1024 -utf8 -nobs | grep -E '.{80}' | grep -v '=====' | grep -v '\*\*\*\*' | grep -v -e '^\d' | sed -r 's/^[[:space:]]+|[[:space:]]+$//g' | grep -v ://
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
yt-dlp -vU --write-auto-sub "$1" > /dev/null 2>&1
|
||||
cat *.vtt | sed 's/<[^>]*>//g' | grep -v '^0' | sed -e 's/\t/ /g' | grep -v '^\s+$' | uniq | paste -s d ' ' /dev/stdin | sed 's/\t/ /g' | sed -E 's/ +/ /g'
|
||||
rm -f *.vtt
|
||||
@@ -0,0 +1,68 @@
|
||||
import llama_cpp
|
||||
|
||||
model_path="/Users/pancake/.r2ai.models/";
|
||||
# model_path+="unsloth.Q4_K_M.gguf"
|
||||
# model_path+= "llama-3-tsuki-unsloth-8b.Q5_K_M.gguf"
|
||||
model_path+= "llama-2-7b-chat-codeCherryPop.Q5_K_M.gguf"
|
||||
# model_path += "mistral-7b-instruct-v0.2.Q5_K_M.gguf"
|
||||
peers = [
|
||||
["@kelsy", "act as a twitter user responding in one short sentence your first though on my messages, be constructive and help me discuss ideas"],
|
||||
["@john", "act as a twitter user, be concise, respond in one short sentence be funny, help me reason my plans"],
|
||||
["@anna", "respond in one short sentence with philosophical reasoning on my message"],
|
||||
["@tony", "behave like a shy twitter user, respond with one or two short sentences, as a software developer, respond in short but wise sentence reasoning the best plans for implementing the topic"]
|
||||
]
|
||||
|
||||
logs = []
|
||||
|
||||
ai = llama_cpp.Llama(model_path=model_path, verbose=False, n_ctx=8096)
|
||||
|
||||
def context(msg):
|
||||
global logs
|
||||
ats = [word for word in msg.split() if word.startswith('@')]
|
||||
ctx = []
|
||||
for log in logs:
|
||||
if log == msg:
|
||||
continue
|
||||
if len(ats) > 0 and any(at in log for at in ats):
|
||||
ctx.append(log)
|
||||
if not log.startswith("@"):
|
||||
ctx.append(log)
|
||||
return ctx
|
||||
|
||||
def sortedpeers(msg):
|
||||
global peers
|
||||
ats = [word for word in msg.split() if word.startswith('@')]
|
||||
if len(ats) == 0:
|
||||
return peers
|
||||
ps = []
|
||||
for peer in peers:
|
||||
if any(at in peer[0] for at in ats):
|
||||
ps.insert(0, peer)
|
||||
else:
|
||||
ps.append(peer)
|
||||
return ps
|
||||
|
||||
def chat(msg):
|
||||
logs.append(msg)
|
||||
global ai
|
||||
for peer in sortedpeers(msg):
|
||||
ctx = ",".join(context(msg))
|
||||
m = f"[INST]{peer[1]}[/INST] {msg}"
|
||||
m = f"[INST]{peer[1]}[/INST] Consider this context: {ctx}. Respond to: {msg}```"
|
||||
m = f"[INST]{peer[1]}[/INST] <s>{ctx}</s>. Respond in one sentence to: {msg}```"
|
||||
mm = ai(m, max_tokens=-1)
|
||||
r = mm["choices"][0]["text"]
|
||||
r = "".join(r.split("\n"))
|
||||
reply = f"{peer[0]}: {r}"
|
||||
logs.append(reply)
|
||||
print(f"\x1b[31m{reply}\x1b[0m")
|
||||
|
||||
# res = ai("Hello")
|
||||
# print(res["choices"][0]["text"])
|
||||
|
||||
|
||||
while True:
|
||||
msg = input()
|
||||
if not msg:
|
||||
break
|
||||
chat(msg)
|
||||
File renamed without changes.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ALPHABET_SIZE 26
|
||||
|
||||
// Function to perform substitution (both encryption and decryption)
|
||||
void substitute(const char *input, const char *substitution, char *output, int reverse) {
|
||||
char mapping[ALPHABET_SIZE];
|
||||
|
||||
if (reverse) {
|
||||
// Create reverse substitution table for decryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[substitution[i] - 'A'] = 'A' + i;
|
||||
}
|
||||
} else {
|
||||
// Use the direct substitution table for encryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[i] = substitution[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; input[i] != '\0'; i++) {
|
||||
char c = input[i];
|
||||
|
||||
// Handle uppercase letters
|
||||
if (isupper(c)) {
|
||||
output[i] = mapping[c - 'A'];
|
||||
}
|
||||
// Handle lowercase letters
|
||||
else if (islower(c)) {
|
||||
output[i] = tolower(mapping[c - 'a']);
|
||||
}
|
||||
// Leave non-alphabet characters unchanged
|
||||
else {
|
||||
output[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
output[strlen(input)] = '\0'; // Null-terminate the output string
|
||||
}
|
||||
|
||||
int main() {
|
||||
char input[256];
|
||||
char output[256];
|
||||
|
||||
// Configurable substitution key (A-Z)
|
||||
const char *substitution = "QWERTYUIOPASDFGHJKLZXCVBNM"; // Example substitution key
|
||||
|
||||
// Get input from the user
|
||||
printf("Enter text to encrypt: ");
|
||||
fgets(input, sizeof(input), stdin);
|
||||
|
||||
// Remove newline character from input if it exists
|
||||
input[strcspn(input, "\n")] = 0;
|
||||
|
||||
// Perform encryption (forward substitution)
|
||||
substitute(input, substitution, output, 0);
|
||||
printf("Encrypted text: %s\n", output);
|
||||
|
||||
// Perform decryption (reverse substitution)
|
||||
substitute(output, substitution, input, 1);
|
||||
printf("Decrypted text: %s\n", input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
File renamed without changes.
@@ -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']}")
|
||||
+10
@@ -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-sonnet-5-5
|
||||
[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)
|
||||
Executable
+22
@@ -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()
|
||||
Whitespace-only changes.
@@ -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()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
Executable
+174
@@ -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,157 @@ 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-sonnet-5-5
|
||||
-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-opus-4-8
|
||||
-m anthropic:claude-sonnet-5-5
|
||||
-m anthropic:claude-haiku-4-5-20251001
|
||||
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 +199,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 +214,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 +244,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 +251,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 +268,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 +299,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 +311,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 +323,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 +332,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 +358,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 +432,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 +460,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 +497,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 +519,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 +547,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 +584,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 +596,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 +608,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 +617,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,566 @@
|
||||
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-sonnet-5-5")
|
||||
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')
|
||||
Whitespace-only changes.
@@ -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,103 @@
|
||||
"""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}")
|
||||
try:
|
||||
os.remove(".audiomsg.wav")
|
||||
except OSError:
|
||||
pass
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-f", "avfoundation", "-t", "5", "-i", device, ".audiomsg.wav"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
rc = result.returncode
|
||||
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)
|
||||
try:
|
||||
os.remove(".audiomsg.wav")
|
||||
except OSError:
|
||||
pass
|
||||
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
|
||||
```
|
||||
|
||||
Executable
+96
@@ -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.
|
||||
@@ -1,17 +1,9 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
TARGETS=all clean mrproper user-install install user-uninstall uninstall fmt indent
|
||||
|
||||
all:
|
||||
python3 main.py || $(MAKE) deps
|
||||
$(TARGETS):
|
||||
$(MAKE) -C src $@
|
||||
|
||||
deps:
|
||||
python -m pip install -r requirements.txt
|
||||
test: all
|
||||
$(MAKE) -C test
|
||||
|
||||
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
|
||||
.PHONY: $(TARGETS) test
|
||||
@@ -1,103 +1,115 @@
|
||||
```
|
||||
,______ .______ .______ ,___
|
||||
: __ \ \____ |: \ : __|
|
||||
| \____|/ ____|| _,_ || : |
|
||||
| : \ \ . || : || |
|
||||
| |___\ \__:__||___| || |
|
||||
|___| : |___||___|
|
||||
* --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 [AI plugin](./src/README.md) for radare2
|
||||
* **decai** - r2js plugin with special [focus on decompilation](./decai/README.md)
|
||||
|
||||
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)
|
||||
|
||||
If you are looking for a radare2 focused autonomous agent:
|
||||
* **r2agent** - automate radare2 workflows through autonomous agents. [r2agent](https://github.com/nitanmarcel/r2agent)
|
||||
|
||||
## 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
|
||||
|
||||
## User defined Prompts
|
||||
|
||||
```console
|
||||
[0x00000000]> r2ai -q
|
||||
explain: Explain the current function -
|
||||
devices: Find and explain devices used -
|
||||
libs: Group imports by Libraries -
|
||||
varnames: Better variable names -
|
||||
autoname: Automatically suggest a better name for this function -
|
||||
vulns: Find vulnerabilities or bugs in the current function -
|
||||
signature: Suggest an improved function signature -
|
||||
dlopen: List libraries loaded with dlopen - Some libraries are loaded
|
||||
decompile: Augmented decompilation based on LLM -
|
||||
[0x00000000]>
|
||||
```
|
||||
|
||||
## 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 use the configuration file:
|
||||
|
||||
```console
|
||||
$ export ANTHROPIC_API_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:
|
||||
Or edit the api keys file `~/.config/r2ai/apikeys.txt` run:
|
||||
|
||||
```bash
|
||||
r2pm -i rlang-python
|
||||
make user-install
|
||||
```console
|
||||
$ r2ai -K
|
||||
```
|
||||
|
||||
On native Windows follow these instructions (no need to install radare2 or use r2pm), note that you need Python 3.8 or higher:
|
||||
## Saving settings
|
||||
|
||||
```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
|
||||
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.
|
||||
|
||||
|
||||
```console
|
||||
$ r2ai -E
|
||||
```
|
||||
|
||||
## Usage
|
||||
then you can type the commands you want to run when the r2ai plugin is loaded:
|
||||
|
||||
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
|
||||
```console
|
||||
r2ai -e api=anthropic
|
||||
r2ai -e model=claude-sonnet-5-5
|
||||
r2ai -e max_tokens=64000
|
||||
```
|
||||
|
||||
```sh
|
||||
conda install pytorch torchvision torchaudio -c pytorch-nightly
|
||||
conda run pip install inquirer rich appdirs huggingface_hub tokentrim llama-cpp-python
|
||||
```
|
||||
## Further Reading
|
||||
|
||||
### TODO
|
||||
* 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://arxiv.org/pdf/2504.07574) by Cryptax and Daniel Nakov
|
||||
* Analysis of [Linux/Trigona ransomware](https://cryptax.medium.com/linux-trigona-analysis-with-r2ai-3e2bd1815e52), [Linux/Prometei botnet](https://cryptax.medium.com/reversing-a-prometei-botnet-binary-with-r2-and-ai-part-one-3cdb3dc6ffab) and [W32/SkyAI](https://cryptax.medium.com/w32-skyai-uses-ai-so-do-i-d33f04d63534with) with r2ai
|
||||
|
||||
* add "undo" command to drop the last message
|
||||
* dump / restore conversational states
|
||||
* custom prompt templates
|
||||
## Videos
|
||||
|
||||
### 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.
|
||||
- [Solving a crackme](https://infosec.exchange/@radareorg/111946255058894583)
|
||||
- [De-obfuscation of malware Linux/Ladvix](https://asciinema.org/a/724126)
|
||||
- [Analysis of the /fast option inside Linux/Trigona ransomware](https://asciinema.org/a/pBPEaJhp6cunWSKFpBUDTgPt4)
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
vim decai/src/constants.ts
|
||||
make -C decai/src
|
||||
vim src/r2ai.h
|
||||
vim src/meson.build
|
||||
vim VERSION
|
||||
@@ -0,0 +1,13 @@
|
||||
# Decompiler based AI
|
||||
|
||||
## Actions
|
||||
|
||||
* Build: `make -C src`
|
||||
* Install: `make user-install`
|
||||
* Test: `r2 -A -qc "decai -e api=ollamacloud;decai -e model=glm-5;decai -d @ 0x100000924" /bin/ls`
|
||||
|
||||
## Hints
|
||||
|
||||
* Never read the minified, autogenerated `decai.r2.js`
|
||||
* Focus and work only on the TypeScript source
|
||||
* Never run `tsc` or `npx`. Always use `make` targets to compile
|
||||
@@ -0,0 +1,29 @@
|
||||
PD=$(shell r2 -H R2_USER_PLUGINS)
|
||||
SPD=$(shell r2 -H R2_LIBR_PLUGINS)
|
||||
BF=/bin/dd
|
||||
|
||||
all:
|
||||
@echo "Run: 'make user-install' to install it in your home"
|
||||
@echo "Run: 'make -C src' to recompile the r2.js"
|
||||
|
||||
test:
|
||||
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
|
||||
|
||||
fmt indent:
|
||||
$(MAKE) -C src 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" src
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
# 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 -E - edit decai.txt
|
||||
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 apitype=chat
|
||||
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 headers=
|
||||
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=
|
||||
...
|
||||
```
|
||||
|
||||
Persist user defaults in `~/.config/r2ai/decai.txt` with one `key=value` entry
|
||||
per line, then edit it with `decai -E`. That file is loaded on the first
|
||||
`decai` command in a session and reloaded after `decai -E` exits.
|
||||
|
||||
Use `decai -e http=curl` to force system curl, `http=r2` to require `r2.http()`,
|
||||
or `http=auto` (the default) to use `r2.http()` when available and curl otherwise.
|
||||
Failed `r2.http()` calls return an error without falling back to curl. The
|
||||
`timeout` setting applies only to system curl. Save `http=curl` in
|
||||
`~/.config/r2ai/decai.txt` to persist the override.
|
||||
|
||||
List possible APIs to discuss with AI: `decai -e api=?`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=?
|
||||
r2ai
|
||||
claude
|
||||
openapi
|
||||
...
|
||||
```
|
||||
|
||||
Extra headers can be injected globally with `decai -e headers=...` or from the
|
||||
environment with `DECAI_HEADERS` / `R2AI_HEADERS`. Use one header per line, for
|
||||
example:
|
||||
|
||||
```console
|
||||
[0x00406cac]> decai -e headers=Authorization: Bearer sk-123\nUser-Agent: curl/8.7.1
|
||||
```
|
||||
|
||||
### 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);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
## WASM HTTP
|
||||
|
||||
With radare2's WASI HTTP build, decai defaults to `r2.http()` to reach the existing
|
||||
`r_socket_http_get/post` C APIs. Headers and JSON payloads go directly to the
|
||||
host HTTP adapter; no shell curl, temporary payload file or WebSocket proxy is
|
||||
needed. A failed/denied HTTP call is returned as an error, without a shell
|
||||
fallback. In `http=auto` mode, runtimes without `r2.http()` use system curl.
|
||||
|
||||
Enable HTTP in the embedding application (`enabled: true` in JavaScript or
|
||||
`network=True` in Python). The radare2 sandbox must permit both `socket` and
|
||||
`network`; process execution is not needed. Browser requests remain subject
|
||||
to CORS. The host adapter controls the HTTP deadline in WASM; decai's `timeout`
|
||||
setting still controls native curl only. Responses are buffered, not streamed.
|
||||
|
||||
This requires radareorg/radare2#27037 and the rebuilt decai script. For the
|
||||
C r2ai plugin compiled into WASM, the `auto` HTTP backend likewise selects
|
||||
radare2's C HTTP implementation; native backend selection is unchanged.
|
||||
@@ -0,0 +1,171 @@
|
||||
📦
|
||||
27680 /main.js
|
||||
✄
|
||||
var te="1.5.4",b="decai",w="~/.config/r2ai",N=w+"/apikeys.txt",U=w+"/decai.txt",T="Rewrite this pseudocode into concise and clean code. Output only the provided function. Do not add wrappers, helper examples, test code, or main-like functions. Replace goto with structured control flow, simplify as much as possible, infer types and use better names for variables and parameters, some strings may be appearing as comments, preserve only what is implied by the input, and remove dead code.",L={decai:`# Using Decai
|
||||
|
||||
Decai is the radare2 plugin for decompiling functions with the help of language models.
|
||||
|
||||
By default uses a local ollama server, but can you can pick any other service by using 'decai -e api=?'.
|
||||
|
||||
[0x00000000]> decai -e api=?
|
||||
r2ai deepseek anthropic claude gemini groq hf lmstudio mistral ollama ollamacloud openapi openai openrouter vllm xai zen
|
||||
|
||||
## Using Ollama
|
||||
|
||||
* Visit https://ollama.com to install it.
|
||||
* Download the model of choice: 'ollama run llama3.3'
|
||||
* Configure decai to use the given model with: 'decai -e model=?'
|
||||
|
||||
These are the most recommended models for decompiling in local:
|
||||
|
||||
* hhao/qwen2.5-coder-tools:latest (18GB of ram)
|
||||
* hhao/qwen2.5-coder-tools:32b (24GB of ram required)
|
||||
|
||||
## Common Options
|
||||
* 'decai -e baseurl=<url>' override default host and port for API endpoint (e.g., 'http://localhost:11434')
|
||||
* 'decai -e apitype=chat|generate' choose the Ollama API endpoint (default: chat)
|
||||
* 'decai -e headers=Authorization: Bearer ...\\nUser-Agent: curl/8.7.1' add or override HTTP headers
|
||||
* "export DECAI_HEADERS='Authorization: Bearer ...\\nUser-Agent: curl/8.7.1'" set extra headers from the environment
|
||||
* 'decai -e http=auto|r2|curl' select the HTTP transport (default: auto, prefers r2.http)
|
||||
* 'decai -e timeout=0' disable curl timeouts, or set a positive number of seconds
|
||||
|
||||
* 'decai -e deterministic=true' to remove randomness from decompilation responses
|
||||
* 'decai -e lang=Python' to output the decompilation in Python instead of C
|
||||
* 'decai -e hlang=Catalan' to add comments or explanations in that language (instead of English)
|
||||
* 'decai -e cmds=pdd,pdg' use r2dec and r2ghidra instead of r2's pdc as input for decompiling
|
||||
* 'decai -e prompt=..' default prompt must be fine for most models and binaries, feel free to tweak it
|
||||
|
||||
## API Keys
|
||||
|
||||
Remove services like OpenAI, Mistral, Anthropic, Grok, Gemini, .. require API keys to work.
|
||||
|
||||
See 'decai -k' to list the status of available APIkeys
|
||||
Use 'decai -K' to edit apikeys.txt and 'decai -E' to edit decai.txt.
|
||||
|
||||
Decai will pick them from the environment or the config files in your home:
|
||||
|
||||
* echo KEY > ~/.r2ai.openai-key
|
||||
* export OPENAI_API_KEY=...
|
||||
* put one 'key=value' pair per line in ~/.config/r2ai/decai.txt
|
||||
|
||||
## Using the R2AI Server:
|
||||
|
||||
Install r2ai or r2ai-server with r2pm:
|
||||
|
||||
[0x0000000]> decai -e api=r2ai
|
||||
[0x0000000]> r2pm -ci r2ai
|
||||
|
||||
Choose one of the recommended models (after r2pm -r r2ai):
|
||||
|
||||
* -m ibm-granite/granite-20b-code-instruct-8k-GGUF
|
||||
* -m QuantFactory/granite-8b-code-instruct-4k-GGUF
|
||||
* -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
|
||||
Start the webserver:
|
||||
|
||||
$ r2pm -r r2ai-server -l r2ai -m granite-8...
|
||||
`,auto:`# Radare2 Auto Mode
|
||||
|
||||
Use function calling to execute radare2 commands in order to resolve the user request defined in the "User Prompt" section, analyze the responses attached in the "Command Results" section.
|
||||
|
||||
## Function Calling
|
||||
|
||||
Respond ONLY using plain JSON. Process user query and decide which function calls are necessary to solve the task.
|
||||
|
||||
1. Analyze the user request to determine if we need to run commands to extend the knowledge and context of the problem.
|
||||
2. If function call is needed, construct the JSON like this:
|
||||
- Fill the "action" key with the "r2cmd" value.
|
||||
- Specify the "command" as a string.
|
||||
- Optionally, provide a "reason" and "description"
|
||||
3. If the answer can be provided and no more function calls are required:
|
||||
- Use the key "action": "reply".
|
||||
- Include "response" with the direct answer to the user query.
|
||||
|
||||
Return the result as a JSON object.
|
||||
|
||||
### Sample Function Calling Communication
|
||||
|
||||
Command Results: already performed actions with their responses
|
||||
User Prompt: "Count how many functions we have here."
|
||||
Response:
|
||||
{
|
||||
"action": "r2cmd",
|
||||
"command": "aflc",
|
||||
"description": "Count functions"
|
||||
"reason": "Evaluate if the program is analyzed before running aaa"
|
||||
}
|
||||
|
||||
## Rules
|
||||
|
||||
Use radare2 to resolve user requests.
|
||||
|
||||
* Explain each step in the "reason" field of the JSON.
|
||||
* Follow the initial analysis instructions.
|
||||
* Output only valid JSON as specified.
|
||||
* Decompile and inspect functions, starting from main.
|
||||
* Run only the needed commands to gather info.
|
||||
* Use "@ (address|symbol)" to seek temporarily.
|
||||
* Output should be a verbose markdown report.
|
||||
* Use "sym." or "fcn." prefixes if "pdc" is empty.
|
||||
* If a seek fails, use "f~name" to find the symbol's address.
|
||||
|
||||
### Initial Analysis
|
||||
|
||||
1. Run "aflc" to count the number of functions
|
||||
2. If the output of "aflc" is "0" run "aaa" once, then "aflc" again
|
||||
3. Run only one command at a time (do not use ";")
|
||||
|
||||
### Special cases
|
||||
|
||||
* On Swift binaries run...
|
||||
`};var E={decopipe:{use:!1},host:"http://localhost",port:"11434",baseurl:"",extraHeaders:[],api:"ollama",pipeline:"",commands:"pdc",yolo:!1,tts:!1,language:"C",humanLanguage:"English",deterministic:!0,debug:!1,timeout:180,http:"auto",think:"",apitype:"chat",useFiles:!1,contextFile:"",model:"",cache:!1,maxInputTokens:-1,prompt:T,lastOutput:""},r={...E,decopipe:{...E.decopipe},extraHeaders:[...E.extraHeaders]};function re(e){return(r2.cmd("-e dir.tmp").trim()??".")+"/"+e}function ne(e){if(!e.startsWith("~/"))return e;let t=r2.cmd("'%HOME").trim()||r2.cmd("'%USERPROFILE").trim();return t?t+e.slice(1):e}function H(e,t){r2.cmd("'mkdir -p "+ne(e)),r2.cmd("'touch "+ne(t))}function _(e){return(r2.cmd2("test -h").logs?.[0]?.message??"").includes("-fdx")?!0:r2.cmd("'test -vf "+e).startsWith("found")}function G(e,t){return e+" ".repeat(Math.max(0,t-e.length))}function h(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}function oe(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}function ie(e){let t=e.match(/```json\s*([\s\S]*?)```/);return t&&t[1]?t[1].trim():e}function ae(e){let t=e,n=t.indexOf("{");n!==-1&&(t=t.slice(n));let o=t.lastIndexOf("}");return o!==-1&&(t=t.slice(0,o+1)),t}function $(e){return btoa(e)}function se(e,t){let n=$(t);r2.cmd("p6ds "+n+" > "+e)}function k(e){let t=e;return t=t.replace(/<think>[\s\S]*?<\/think>/gi,""),t.split(`
|
||||
`).filter(n=>!n.trim().startsWith("```")).join(`
|
||||
`)}function J(e){r.debug&&console.log(e)}function le(e){let t={};for(let n of e.split(/\r?\n/)){let o=n.trim();if(!o||o.startsWith("#"))continue;let[i,...a]=o.split("=");if(!i||a.length===0)continue;let s=a.join("=").trim(),l=i.toLowerCase().replace(/_api_key$/i,"");t[l]=s}return t}var Ue=["DECAI_HEADERS","R2AI_HEADERS"];function _e(e){let t=r2.cmd("'%"+e).trim();return t!==""&&!t.includes("=")?t:""}function Me(e){return e.trim().toLowerCase()}function Ne(e){let t=e.indexOf(":"),n=e.indexOf("="),o=t!==-1&&(n===-1||t<n)?t:n;if(o===-1)return null;let i=e.slice(0,o).trim();return i===""?null:{name:i,value:e.slice(o+1).trim()}}function Te(e){return e.value===""?`${e.name}:`:`${e.name}: ${e.value}`}function ce(e,t){for(let n of e){let o=Ne(n.trim());o&&t.set(Me(o.name),Te(o))}}function W(e){let t=new Map,n=e.replace(/\\n/g,`
|
||||
`).split(/\r?\n/g).filter(o=>{let i=o.trim();return i&&!i.startsWith("#")});return ce(n,t),Array.from(t.values())}function ue(e){return e.join("\\n")}function A(...e){let t=new Map;for(let n of e)ce(n,t);return Array.from(t.values())}function He(){for(let e of Ue){let t=_e(e);if(t)return W(t)}return[]}function de(){return A(He(),r.extraHeaders)}function M(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function Ke(e){let{method:t,url:n,headers:o,payload:i}=e,a=A(["Content-Type: application/json"],o);if(r.http!=="curl"){if(r2.http)return r2.http(t,n,a.join(`
|
||||
`),i??"").body;if(r.http==="r2")throw new Error("r2.http is not available in this runtime")}let s=["curl","-s"];if(r.timeout>0&&s.push("--max-time",String(r.timeout)),a.forEach(l=>s.push("-H",M(l))),t==="POST"){if(!i)throw new Error("Payload required for POST requests");let l=r2.fdump(i);s.push("--data-binary","@"+M(l),M(n));let u=s.join(" ")+"; rm "+M(l);return J(u),r2.syscmds(u)}else{s.push(M(n));let l=s.join(" ");return J(l),r2.syscmds(l)}}function qe(e){let t=e.split(`
|
||||
`).filter(i=>i.trim()!=="");if(t.length<=1)return null;let n="",o=null;for(let i of t)try{let a=JSON.parse(i);if(a.error)return a;a.message?.content!==void 0&&(n+=a.message.content),o=a}catch{return null}return o?(o.message={role:"assistant",content:n},o):null}function pe(e){try{let t=Ke(e).trim();if(t==="")return{error:"empty response"};try{return JSON.parse(t)}catch{let n=qe(t);return n||{error:t,rawOutput:t}}}catch(t){return{error:t.message}}}function O(e,t){return pe({method:"GET",url:e,headers:t})}function fe(e,t,n){return pe({method:"POST",url:e,headers:t,payload:n})}function z(e){return e.split("_")[0].toLowerCase()}function me(e){if(!e.authKey)return;let t=Array.from(new Set([e.keyName,z(e.authKey)].filter(Boolean))),n;for(let o of t){let i=K(o,e.authKey);if(i[0])return i;!n&&i[2]!=="nope"&&(n=i)}return n||K(z(e.authKey),e.authKey)}function S(e){if(e)return typeof e=="string"?e:e.message||JSON.stringify(e)}function he(e){return r.baseurl||e.defaultBaseUrl}function ge(e,t=de()){return A(e,t)}function De(e){return e?["Authorization: Bearer "+e]:[]}function je(e){let t=["anthropic-version: 2023-06-01"];return e?A(t,["x-api-key: "+e]):t}function ye(e,t){switch(e.authStyle||"none"){case"bearer":return De(t);case"anthropic":return je(t);case"none":default:return[]}}function Be(e,t,n){let o=O(e,t),i=S(o.error);return i?(console.error(i),"error invalid response"):o.data?.map(n).join(`
|
||||
`)||""}function Fe(e,t){let n=new Set;return e.filter(o=>{let i=t(o);return n.has(i)?!1:(n.add(i),!0)})}function V(e){let t=me(e)?.[0]||null;return{baseUrl:he(e),headers:ge(ye(e,t))}}function Le(e){let{baseUrl:t,headers:n}=V(e),o=h(t,"v1/models");return Be(o,n,i=>i.id)}function Ge(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"api/tags"),n),i=S(o.error);return i?(console.error(i),"error invalid response"):o.models?.map(a=>a.name).join(`
|
||||
`)||""}function $e(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"v1/models"),n);return o.data?Fe(o.data,i=>i.name||i.id).map(i=>[G(i.name||i.id,30),G(""+(i.max_context_length||""),10),i.description||""].join(" ")).join(`
|
||||
`):""}var Je={openai:{buildPayload:(e,t)=>({stream:!1,model:e,messages:[{role:"user",content:t}]}),parseResponse:e=>{if(e.error)throw new Error(S(e.error)||"Unknown error");if(e.choices&&e.choices[0]?.message?.content)return k(e.choices[0].message.content);throw new Error("Invalid response format")},buildUrl:e=>h(e,"v1/chat/completions")},anthropic:{buildPayload:(e,t)=>{let n={model:e,max_tokens:5128,messages:[{role:"user",content:t}]};return Y()&&(n.thinking={type:"enabled",budget_tokens:4096},n.max_tokens=16e3),r.deterministic&&!Y()&&Object.assign(n,{temperature:0,top_p:0,top_k:1}),n},parseResponse:e=>{if(e.content&&Array.isArray(e.content)){let t=[];for(let n of e.content)n.text&&t.push(n.text);if(t.length>0)return k(t.join(`
|
||||
`))}throw e.error?new Error(S(e.error)||"Unknown error"):new Error("Invalid response format")},buildUrl:e=>h(e,"v1/messages")},ollama:{buildPayload:(e,t)=>{let n={stream:!1,model:e};return r.apitype==="generate"?n.prompt=t:n.messages=[{role:"user",content:t}],Y()?r.think==="true"||r.think==="1"?n.think=!0:n.think=r.think:n.think=!1,r.deterministic&&(n.options={repeat_last_n:0,top_p:1,top_k:1,temperature:0,repeat_penalty:1,seed:123}),n},parseResponse:e=>{if(e.error)throw new Error(S(e.error));if(typeof e.response=="string")return k(e.response);if(e.message?.content)return k(e.message.content);throw new Error(JSON.stringify(e))},buildUrl:e=>h(e,"api/"+r.apitype)},gemini:{buildPayload:(e,t)=>{let n={contents:[{parts:[{text:t}]}]},o={};return r.deterministic&&Object.assign(o,{temperature:0,topP:1,topK:1}),r.think!==""&&(X()?o.thinkingConfig={thinkingBudget:0}:o.thinkingConfig={thinkingBudget:8192}),Object.keys(o).length>0&&(n.generationConfig=o),n},parseResponse:e=>{if(e.candidates&&e.candidates[0]?.content?.parts){let n=e.candidates[0].content.parts.filter(o=>!o.thought&&o.text).map(o=>o.text);if(n.length>0)return k(n.join(`
|
||||
`))}throw e.error?new Error(S(e.error)):(console.log(JSON.stringify(e)),new Error("Invalid response format"))},buildUrl:(e,t,n)=>h(e,`v1beta/models/${t}:generateContent?key=${n}`),requiresUrlApiKey:!0}},q={anthropic:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"anthropic",authStyle:"anthropic",apiStyle:"anthropic"},claude:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"claude",authStyle:"anthropic",apiStyle:"anthropic"},openai:{defaultModel:"gpt-4o-mini",defaultBaseUrl:"https://api.openai.com",authKey:"OPENAI_API_KEY",authStyle:"bearer",apiStyle:"openai"},ollama:{defaultModel:"qwen2.5-coder:latest",defaultBaseUrl:"http://localhost:11434",authStyle:"none",apiStyle:"ollama"},ollamacloud:{defaultModel:"gpt-oss:120b",defaultBaseUrl:"https://ollama.com",authKey:"OLLAMA_API_KEY",keyName:"ollamacloud",authStyle:"bearer",apiStyle:"ollama"},zen:{defaultModel:"big-pickle",defaultBaseUrl:"https://opencode.ai/zen",authKey:"OPENCODE_API_KEY",keyName:"zen",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["big-pickle","glm-5","kimi-k2.5"]},gemini:{defaultModel:"gemini-2.5-flash",defaultBaseUrl:"https://generativelanguage.googleapis.com",authKey:"GEMINI_API_KEY",authStyle:"none",apiStyle:"gemini",hardcodedModels:["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite"]},mistral:{defaultModel:"codestral-latest",defaultBaseUrl:"https://api.mistral.ai",authKey:"MISTRAL_API_KEY",authStyle:"bearer",apiStyle:"openai"},xai:{defaultModel:"grok-beta",defaultBaseUrl:"https://api.x.ai",authKey:"XAI_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["grok-2","grok-beta"]},lmstudio:{defaultModel:"local-model",defaultBaseUrl:"http://127.0.0.1:1234",authStyle:"none",apiStyle:"openai"},deepseek:{defaultModel:"deepseek-coder",defaultBaseUrl:"https://api.deepseek.com",authKey:"DEEPSEEK_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["deepseek-coder","deepseek-chat"]},openrouter:{defaultModel:"openai/gpt-4o-mini",defaultBaseUrl:"https://openrouter.ai/api",authKey:"OPENROUTER_API_KEY",authStyle:"bearer",apiStyle:"openai"},groq:{defaultModel:"llama-3.3-70b-versatile",defaultBaseUrl:"https://api.groq.com/openai",authKey:"GROQ_API_KEY",authStyle:"bearer",apiStyle:"openai"}};function D(e){return q[e]}function Q(){return Object.keys(q)}function be(e){let t=D(e);if(!t)throw new Error(`Unknown provider: ${e}`);if(e==="mistral")return $e(t);switch(t.apiStyle){case"openai":case"anthropic":return Le(t);case"ollama":return Ge(t);case"gemini":return"";default:return""}}function X(){return r.think==="false"||r.think==="0"}function Y(){return r.think!==""&&!X()}function We(e,t,n){let o=e;return r.think!==""&&n==="openai"&&(X()?(o+=' Answers directly and concisely, without showing any thinking steps or internal reasoning. Never include phrases like "Let me think".',o+=" /no_think"):o="Think step by step and explain the reasoning process, When answering, first output your reasoning inside <think> and </think> tags, then give the final answer."+o),t?o:r.prompt+Ye()+o}function Ye(){return`
|
||||
.Translate the code into `+r.language+` programming language
|
||||
`}function ze(e,t,n,o){let i=r.model||e.defaultModel,a=We(n,o,e.apiStyle),s=me(e);if(t.requiresUrlApiKey&&s?.[1]&&e.authKey)return`Cannot read ~/.r2ai.${z(e.authKey)}-key`;let l=t.buildPayload(i,a),u=ye(e,s?.[0]||null);e.apiStyle==="ollama"&&u.push("Accept: application/x-ndjson");let v=ge(u),c=t.buildUrl(he(e),i,s?.[0]||void 0);try{return t.parseResponse(fe(c,v,JSON.stringify(l)))}catch(d){return"ERROR: "+d.message}}function ke(e,t){let n=D(r.api);return n?ze(n,Je[n.apiStyle],e,t):`Unknown value for 'decai -e api'. Available: ${Q().join(", ")}`}function Ve(){return Object.fromEntries(Object.entries(q).filter(([,e])=>e.authKey).map(([e,t])=>[e,t.authKey]))}function K(e,t){let n=r2.cmd("'%"+t).trim();if(!n.includes("=")&&n!=="")return[n.trim(),null,"env"];let o=e.toLowerCase(),i=N;if(_(i)){let s=r2.cmd("'cat "+i),l=le(s);if(o in l)return[l[o],null,"txt"]}let a="~/.r2ai."+o+"-key";if(_(a)){let s=r2.cmd("'cat "+a);return s===""?[null,"Cannot read "+a,"no"]:[s.trim(),null,"file"]}return[null,"Not available","nope"]}function xe(){H(w,N),r2.cmd("'ed "+N)}function Z(){Object.entries(Ve()).forEach(([e,t])=>{let n=K(e,t)[2];console.log(n," ",e)})}function ve(e){let t=D(e);if(!t){console.error(`Unknown provider: ${e}`);return}try{let n=be(e);n&&console.log(n),t.hardcodedModels&&t.hardcodedModels.forEach(o=>console.log(o)),e==="mistral"&&console.log("ministral-8b-latest"),!n&&!t.hardcodedModels&&console.log(t.defaultModel)}catch(n){let o=n;console.error(`Error listing models for ${e}:`,o.message),console.log(t.defaultModel)}}var ee=!1;function Qe(e){return e==="true"||e==="1"}function P(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=Qe(n)}}}function R(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=n}}}function Xe(e){let t=e.trim();return t==="chat"?"chat":t==="generate"?"generate":null}var Ee={pipeline:{get:()=>r.pipeline,set:e=>{r.pipeline=e;try{r.decopipe=JSON.parse(r2.cmd("cat "+e))}catch(t){console.error(t)}}},model:{get:()=>r.model,set:e=>{e==="?"?ve(r.api):r.model=e.trim()}},deterministic:P("deterministic"),files:P("useFiles"),think:{get:()=>r.think||"false",set:e=>{r.think=e}},apitype:{get:()=>r.apitype,set:e=>{let t=Xe(e);t?r.apitype=t:console.error("Invalid apitype. Use chat or generate.")}},debug:P("debug"),http:{get:()=>r.http,set:e=>{let t=e.trim();t==="auto"||t==="r2"||t==="curl"?r.http=t:console.error("Invalid http backend. Use auto, r2 or curl.")}},timeout:{get:()=>r.timeout,set:e=>{r.timeout=Math.max(0,parseInt(e,10)||0)}},api:{get:()=>r.api,set:e=>{e==="?"?console.error(Q().join(`
|
||||
`)):r.api=e}},lang:R("language"),hlang:R("humanLanguage"),cache:P("cache"),cmds:R("commands"),tts:P("tts"),yolo:P("yolo"),prompt:R("prompt"),ctxfile:R("contextFile"),baseurl:R("baseurl"),headers:{get:()=>ue(r.extraHeaders),set:e=>{r.extraHeaders=W(e)}},maxtokens:{get:()=>r.maxInputTokens,set:e=>{r.maxInputTokens=parseInt(e,10)||-1}}};function Ze(){let e={host:r.host,port:r.port,lastOutput:r.lastOutput};Object.assign(r,E,e),r.decopipe={...E.decopipe},r.extraHeaders=[...E.extraHeaders]}function et(e){let t=e.trim();return!t||t.startsWith("#")?null:t.startsWith("decai -e ")?t.slice(9).trim():t}function x(e){let t=e.indexOf("="),n=t===-1?e:e.slice(0,t),o=t===-1?void 0:e.slice(t+1),i=Ee[n];if(!i){console.error("Unknown config key");return}typeof o<"u"?i.set(o):console.log(i.get())}function we(){Object.entries(Ee).forEach(([e,t])=>{let n=t.get();console.log("decai -e "+e+"="+n)})}function Ae(){if(Ze(),!_(U))return;let e=r2.call("cat "+U);for(let t of e.split(/\r?\n/)){let n=et(t);n&&n[0]!="#"&&x(n)}}function Oe(){ee||(Ae(),ee=!0)}function Se(){H(w,U),r2.cmd("'ed "+U),Ae(),ee=!0}function p(e,t,n=!1){let o=(t||"").replace(/`/g,""),i=e.replace(/'/g,"");if(r.api==="r2"||r.api==="r2ai"){let s=re(".pdc.txt");se(s,o);let l=i.startsWith("-")?i:["-i",s,i].join(" "),u=r.baseurl||r.host+":"+r.port,v=h(u,"cmd/"+encodeURIComponent(l)),c=O(v,[]);return c.error?`Error: ${c.error}`:c.result||JSON.stringify(c)||"Cannot curl, use r2ai-server or r2ai -w"}if(i.startsWith("-"))return"";let a=i+`:
|
||||
`+o;return r.maxInputTokens>0&&a.length>r.maxInputTokens&&(a=a.slice(0,r.maxInputTokens)),ke(a,n)}function Re(e){let t=r2.cmd("e scr.color");r2.cmd("e scr.color=0");try{return e()}finally{r2.cmd("e scr.color="+t)}}function Pe(){let e="",t=n=>e+=" "+b+" "+n+`
|
||||
`;e+="Usage: "+b+` (-h) ...
|
||||
`,e+="Version: "+te+`
|
||||
`,t("-a [query] - solve query with auto mode"),t("-b [url] - set base URL (alias for decai -e baseurl)"),t("-d [f1 ..] - decompile given functions"),t("-dd [..] - same as above, but ignoring cache"),t("-dD [query] - decompile current function with given extra query"),t("-dr - decompile function and its called ones (recursive)"),t("-e - display and change eval config vars"),t("-E - edit and run decai.txt"),t("-h - show this help"),t("-H - help setting up r2ai"),t("-i [f] [q] - include given file and query"),t("-k - list API key status"),t("-K - edit apikeys.txt"),t("-m [model] - use -m? or -e model=? to list the available models"),t("-n - suggest better function name"),t("-p [provider] - same as decai -e api (will be provider)"),t("-q [text] - query language model with given text"),t("-Q [text] - query on top of the last output"),t("-r [prompt] - change role prompt (same as: decai -e prompt)"),t("-R - reset role prompt to default prompt"),t("-s - function signature"),t("-v - show local variables"),t("-V - find vulnerabilities"),t("-x[*] - eXplain current function (-x* for r2 script)"),r2.log(e.trim())}function j(e,t,n,o){if(n){let s=r2.cmd("anos").trim();if(s.length>0)return s}let i="";if(o){let s=r2.cmd("s");i+=`## Context functions:
|
||||
`;let l=r2.cmdAt("axff~^C[2]~$$",s);for(let u of l.split(/\n/g))i+=r2.cmd("pdc@"+u);r2.cmd("s "+s)}let a=t?" "+e:"";try{let s=e.slice(2).trim(),l="";r.contextFile!==""&&r2.cmd2("test -f "+r.contextFile).value===0&&(l+=`## Context:
|
||||
[START]
|
||||
`+r2.cmd("cat "+r.contextFile)+`
|
||||
[END]
|
||||
`);let{body:u,count:v}=Re(()=>{let d=`## Before:
|
||||
`,y=0;for(let g of r.commands.split(",")){if(g.trim()==="")continue;let C=t||s.trim().length===0?g:g+"@@= "+s,I=r2.cmd(C);I.length>5&&(d+="Output of "+g+`:
|
||||
[START]
|
||||
`+I+`
|
||||
[END]
|
||||
`,y++)}return d+=`## After:
|
||||
`,{body:d,count:y}});if(v===0){console.error("Nothing to do.");return}let c="";if(r.decopipe.use){let d=r.model;try{let y=r.decopipe[r.decopipe.default],g=l+u;for(let C of y.pipeline){C.model&&(r.model=C.model);let I=C.query+". "+y.globalQuery;c=p(I,g,!0),r.debug&&console.log(`QUERY
|
||||
`,I,`
|
||||
INPUT
|
||||
`,g,`
|
||||
OUTPUT
|
||||
`,c),g=c}c=g}finally{r.model=d}}else{let d=a;l+=u+i,c=p(d,l,!1),r.lastOutput=c}return n&&c.length>1&&r2.call("ano=base64:"+$(c)),c.startsWith("```")&&(c=c.replace(/```.*\n/,"").replace(/```$/,"")),c.trim()}catch(s){let l=s;console.error(l,l.stack);return}}function tt(){let e=Re(()=>"[START]"+r.commands.split(",").map(o=>r2.cmd(o)).join(`
|
||||
`)+"[END]"),n=p("Analyze function calls, references, comments and strings, loops and ignore registers and memory accesses. Explain the purpose of this function in a single short sentence. /no_think Do not introduce or argue the response, translation of the explanation in "+r.humanLanguage,e,!0).trim().split(/\n/g);return n[n.length-1].trim()}function nt(){let e=r.language,t=r2.cmd("afv;pdc");r.language="C";let n="'afs "+p("analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the return. Do NOT print the function body, ONLY output the function signature, like if it was going to be used in a C header",t,!1),o=n.indexOf("{");return o!==-1&&(n=n.substring(0,o)),r.language=e,n}function rt(e,t){let n=[];for(;;){let o=L.auto;if(n.length>0){o+=`## Command Results
|
||||
|
||||
`;for(let a of n){let s=JSON.parse(a);o+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}o+=`
|
||||
|
||||
## User Prompt
|
||||
|
||||
`+e,r.debug&&console.log(`#### input
|
||||
`,o,`
|
||||
#### /input`),console.log("Thinking...");let i=p("",o,!0);r.debug&&console.log(`#### output
|
||||
`,i,`
|
||||
#### /output`);try{let a=JSON.parse(ae(ie(k(i))));if(a.action==="r2cmd"||a.action==="response"||a.action===a.command){let s=a.command||"";a.reason&&(console.log("[r2cmd] Reasoning: "+a.reason),r.tts&&(r2.syscmd("pkill say"),r2.syscmd("say -v Alex -r 250 '"+a.reason.replace(/'/g,"")+"' &"))),console.log("[r2cmd] Action: "+a.description),console.log("[r2cmd] Command: "+s);let l=s;r.yolo||(l=ot(s,t)),console.log("[r2cmd] Running: "+l);let u=r2.cmd2(l),v=u.logs?u.logs.map(y=>y.type+": "+y.message).join(`
|
||||
`):"",c=(u.res+v).trim();console.log(c);let d=oe(c);r.debug&&console.log(`<r2output>
|
||||
`,d,`
|
||||
<(r2output>`),n.push(JSON.stringify({action:"response",command:l,description:a.description,response:d}))}else if(a.action==="reply"){console.log(`Done
|
||||
`,a.response);break}else{console.log(`Unknown response
|
||||
`,JSON.stringify(i));break}}catch(a){let s=i.indexOf('response": "');if(s!==-1){let l=i.slice(s+12).replace(/\\n/g,`
|
||||
`).replace(/\\/g,"");console.log(l)}else console.log(i),console.error(a);break}}}function ot(e,t){for(;;){let n=r2.cmd("'?ie Tweak command? ('?' for help)").trim();if(n==="q!"){console.error("Break!");break}if(n==="?"){it();continue}else if(n.startsWith(":")){console.log(r2.cmd(n.slice(1)));continue}else if(n.startsWith("-e")){t(n);continue}else{if(n==="!")return"?e do NOT execute '"+e+"' again, continue without it";if(n.startsWith("!")){console.log(r2.syscmd(n.slice(1)));continue}else{if(n==="q")return"?e All data collected!. Do not call more commands, reply the solutions";if(n){let o=n.indexOf("#");return o!==-1?n.slice(0,o).trim():n}else return e}}}return e}function it(){console.log(" '!' do not run this command"),console.log(" '!c' run system command"),console.log(" 'q' to quit auto and try to solve"),console.log(" 'q!' quit auto without solving"),console.log(" 'c # C' use given command with comment"),console.log(" ':c' run r2 command without feeding auto"),console.log(" '-e' set decai configuration variables")}function at(e){return e.slice(2).trim()}function B(){return r2.cmd("afi.").trim().length>0}var F="Cannot find function at current offset";function Ce(e,t){if(e===""||!e.startsWith("-")){Pe();return}let n="",o=e[1],i=at(e);switch(o){case"H":console.log(L.decai);break;case"a":rt(i,t);break;case"m":{i==="="?x("model="):i?x("model="+i):x("model");break}case"n":case"f":{if(!B()){n=F;break}n=r2.cmd("axff~$[3]");let a=r2.cmd("fd.").trim().split(/\n/).filter(s=>!s.startsWith("secti")).join(",");n=p("give me a better name for this function. the output must be: 'afn NEWNAME'. do not include the function code, only the afn line. consider: "+a,n,!1).trim(),n+=" @ "+r2.cmd("?v $FB").trim();break}case"v":{if(!B()){n=F;break}n=r2.cmd("afv;pdc"),n=p("guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands",n,!1);break}case"i":{let a=i.split(/\s+/).filter(Boolean);if(a.length>=2){let s=a[0],l=a.slice(1).join(" "),u=r2.cmd("cat "+s);console.log(p(l,u,!0))}else console.log("Use: decai -i [file] [query ...]");break}case"p":{i?x("api="+i):Z();break}case"r":{i?r.prompt=i:console.log(r.prompt);break}case"R":r.prompt=T;break;case"s":if(!B()){n=F;break}n=nt();break;case"V":n=p("find vulnerabilities, dont show the code, only show the response, provide a sample exploit",r.lastOutput,!1);break;case"K":xe();break;case"E":Se();break;case"k":Z();break;case"b":{i?x("baseurl="+i):console.log(r.baseurl);break}case"e":{i?x(i):we();break}case"q":try{n=p(i,null,!0)}catch(a){let s=a;console.error(s,s.stack)}break;case"Q":n=p(i,r.lastOutput,!1);break;case"x":if(!B()){n=F;break}n=tt(),(e[2]==="*"||e[2]==="r")&&(n="'CC "+n);break;case"d":e[2]==="r"?n=j(e.slice(2),!0,r.cache,!0)||"":e[2]==="d"?n=j(e,!1,!1,!1)||"":e[2]==="D"?n=j(e,!0,!1,!1)||"":n=j(e,!1,r.cache,!1)||"";break;default:Pe();break}return n||void 0}function Ie(e){Oe();let t=Ce(e,Ie);return t&&r2.log(t),!0}function st(){r2.unload("core",b),r2.plugin("core",function(){function e(t){if(t.startsWith(b)){let n=t.slice(b.length).trim();return Ie(n)}return!1}return{name:b,license:"MIT",desc:"r2 decompiler based on r2ai",call:e}})}st();
|
||||
Vendored
+13
@@ -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
|
||||
Vendored
+2
@@ -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..
|
||||
Vendored
+34
@@ -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
|
||||
Vendored
+130
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loaded 100 of 225 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user