Compare commits
538 Commits
Author | SHA1 | Date |
---|---|---|
gregandev | 77800c1636 | 1 year ago |
ci-robbot [bot] | 5ee186b8e5 | 1 year ago |
Ettore Di Giacinto | 94817b557c | 1 year ago |
Ettore Di Giacinto | 26e1496075 | 1 year ago |
Ettore Di Giacinto | 92fca8ae74 | 1 year ago |
Stepan | 7fa5b8401d | 1 year ago |
Ettore Di Giacinto | 0eac0402e1 | 1 year ago |
Ettore Di Giacinto | c71c729bc2 | 1 year ago |
Ettore Di Giacinto | e459f114cd | 1 year ago |
Ettore Di Giacinto | 982a7e86a8 | 1 year ago |
Ettore Di Giacinto | 94916749c5 | 1 year ago |
Ettore Di Giacinto | 5ce5f87a26 | 1 year ago |
Ettore Di Giacinto | 1d2ae46ddc | 1 year ago |
ci-robbot [bot] | 71ac331f90 | 1 year ago |
Ettore Di Giacinto | 47cc95fc9f | 1 year ago |
Ettore Di Giacinto | 3feb632eb4 | 1 year ago |
Ettore Di Giacinto | 236497e331 | 1 year ago |
ci-robbot [bot] | a38dc497b2 | 1 year ago |
ci-robbot [bot] | 28ed52fa94 | 1 year ago |
Enzo Einhorn | e995b95c94 | 1 year ago |
Ettore Di Giacinto | 8379cce209 | 1 year ago |
ci-robbot [bot] | 3c6b798522 | 1 year ago |
ci-robbot [bot] | c18770a61a | 1 year ago |
Ettore Di Giacinto | 6352448b72 | 1 year ago |
renovate[bot] | fb6cce487f | 1 year ago |
renovate[bot] | 3079cc4167 | 1 year ago |
ci-robbot [bot] | 27ef8b1eb7 | 1 year ago |
ci-robbot [bot] | c00435d72b | 1 year ago |
Ettore Di Giacinto | d0e67cce75 | 1 year ago |
renovate[bot] | 6ec315e540 | 1 year ago |
renovate[bot] | cf4e6f909c | 1 year ago |
renovate[bot] | b3a99166fd | 1 year ago |
renovate[bot] | 107008331e | 1 year ago |
ci-robbot [bot] | accd9f9044 | 1 year ago |
Ettore Di Giacinto | 17294ae5e5 | 1 year ago |
renovate[bot] | 3c3a9b765a | 1 year ago |
renovate[bot] | 526c5bcdad | 1 year ago |
renovate[bot] | a1bbe75d43 | 1 year ago |
renovate[bot] | 572a311639 | 1 year ago |
Ettore Di Giacinto | cb5d6f6e3a | 1 year ago |
Ettore Di Giacinto | e3cabb555d | 1 year ago |
Ettore Di Giacinto | f193f56564 | 1 year ago |
Ettore Di Giacinto | c0a91ab548 | 1 year ago |
Ettore Di Giacinto | 26e510bf28 | 1 year ago |
Ettore Di Giacinto | 98e73ed67a | 1 year ago |
Ettore Di Giacinto | 7f3de3ca4a | 1 year ago |
Ettore Di Giacinto | 189cb3a7be | 1 year ago |
Ettore Di Giacinto | 1d0ed95a54 | 1 year ago |
Ettore Di Giacinto | 5dcfdbe51d | 1 year ago |
Ettore Di Giacinto | f2f1d7fe72 | 1 year ago |
Ettore Di Giacinto | ae533cadef | 1 year ago |
Ettore Di Giacinto | 58f6aab637 | 1 year ago |
Ettore Di Giacinto | b816009db0 | 1 year ago |
ci-robbot [bot] | a84dee1be1 | 1 year ago |
renovate[bot] | 30e4ddbf10 | 1 year ago |
Ettore Di Giacinto | 296a5b6707 | 1 year ago |
renovate[bot] | b0520dcb59 | 1 year ago |
renovate[bot] | f42967ed86 | 1 year ago |
renovate[bot] | 966675c8e3 | 1 year ago |
renovate[bot] | f68df1624b | 1 year ago |
renovate[bot] | 42cade808b | 1 year ago |
Ettore Di Giacinto | d59211982b | 1 year ago |
Ettore Di Giacinto | 7aaa10680d | 1 year ago |
mudler | dcf35dd25f | 1 year ago |
mudler | e70322676c | 1 year ago |
mudler | b3f43ab938 | 1 year ago |
mudler | bbc4468908 | 1 year ago |
mudler | 4de7f55f2f | 1 year ago |
mudler | def23e4ee2 | 1 year ago |
mudler | 55befe396a | 1 year ago |
mudler | 483fddccf9 | 1 year ago |
mudler | c4495ad8f2 | 1 year ago |
mudler | 05aed255db | 1 year ago |
mudler | 0f1326b2bd | 1 year ago |
mudler | 1668489b00 | 1 year ago |
mudler | 7dd292cbb3 | 1 year ago |
mudler | c0578031b5 | 1 year ago |
mudler | a5b64b6a41 | 1 year ago |
mudler | b722e7eb7e | 1 year ago |
mudler | 6d19a8bdb5 | 1 year ago |
mudler | f09ddd2983 | 1 year ago |
Luis López | a6839fd238 | 1 year ago |
Ettore Di Giacinto | f3063f98d3 | 1 year ago |
Ettore Di Giacinto | 70674d3c58 | 1 year ago |
ci-robbot [bot] | 3829aba869 | 1 year ago |
Ettore Di Giacinto | 92614b91d7 | 1 year ago |
Ettore Di Giacinto | bf5acf646e | 1 year ago |
renovate[bot] | 0780be022c | 1 year ago |
renovate[bot] | c756b5d054 | 1 year ago |
ci-robbot [bot] | e3db6496d7 | 1 year ago |
renovate[bot] | 1f1c95c618 | 1 year ago |
renovate[bot] | 5ea032cf81 | 1 year ago |
ci-robbot [bot] | 1e6542a5ca | 1 year ago |
ci-robbot [bot] | 218e7bc8df | 1 year ago |
Ettore Di Giacinto | a06e467a1a | 1 year ago |
mudler | 730645b3c6 | 1 year ago |
mudler | 3dd632fd5a | 1 year ago |
renovate[bot] | 365d4d3756 | 1 year ago |
renovate[bot] | d22053a5e6 | 1 year ago |
renovate[bot] | e3ac561d30 | 1 year ago |
ci-robbot [bot] | 69367a7948 | 1 year ago |
ci-robbot [bot] | 85a38a8122 | 1 year ago |
Ettore Di Giacinto | d2cf1954fc | 1 year ago |
renovate[bot] | 70712e3445 | 1 year ago |
ci-robbot [bot] | 85eea1189e | 1 year ago |
ci-robbot [bot] | ed2344ab9b | 1 year ago |
Samuel Maynard | 935bd51510 | 1 year ago |
Ettore Di Giacinto | 3593cb0c87 | 1 year ago |
Samuel Maynard | e130b208ab | 1 year ago |
Ettore Di Giacinto | 02136531a3 | 1 year ago |
Ettore Di Giacinto | d3a486a4f8 | 1 year ago |
Ettore Di Giacinto | 2b957df56c | 1 year ago |
Matthew Koski | c2dec387aa | 1 year ago |
ci-robbot [bot] | a1ed6fbd96 | 1 year ago |
renovate[bot] | ad81e37672 | 1 year ago |
Ettore Di Giacinto | 78f3c3da48 | 1 year ago |
mudler | d18f85df46 | 1 year ago |
Ettore Di Giacinto | 6213da330a | 1 year ago |
renovate[bot] | 53f8d73101 | 1 year ago |
renovate[bot] | 2cfc9a2706 | 1 year ago |
ci-robbot [bot] | 0ba94bf33f | 1 year ago |
renovate[bot] | 06570d1e41 | 1 year ago |
ci-robbot [bot] | be1667c387 | 1 year ago |
ci-robbot [bot] | eb39d908d0 | 1 year ago |
Ettore Di Giacinto | 60db5957d3 | 1 year ago |
Ettore Di Giacinto | 2a45a99737 | 1 year ago |
renovate[bot] | 91a67d5ee0 | 1 year ago |
ci-robbot [bot] | 55cf9d5792 | 1 year ago |
Ettore Di Giacinto | a7bb029d23 | 1 year ago |
ci-robbot [bot] | cc31c58235 | 1 year ago |
renovate[bot] | 4e831307a8 | 1 year ago |
ci-robbot [bot] | 445067f6ad | 1 year ago |
ci-robbot [bot] | 11bfd0de76 | 1 year ago |
mudler | dc7b8ad23b | 1 year ago |
Ettore Di Giacinto | 2f5feb4841 | 1 year ago |
renovate[bot] | 4e3c319e83 | 1 year ago |
ci-robbot [bot] | d0025a7483 | 1 year ago |
ci-robbot [bot] | db0b29be51 | 1 year ago |
Ettore Di Giacinto | 7da07e8af9 | 1 year ago |
renovate[bot] | 6da892758b | 1 year ago |
renovate[bot] | 5e88930475 | 1 year ago |
renovate[bot] | 97b02f9765 | 1 year ago |
renovate[bot] | 7ee1b10dfb | 1 year ago |
renovate[bot] | 3932c15823 | 1 year ago |
renovate[bot] | 618fd1d417 | 1 year ago |
renovate[bot] | 151a6cf4c2 | 1 year ago |
ci-robbot [bot] | 1766de814c | 1 year ago |
ci-robbot [bot] | 0b351d6da2 | 1 year ago |
renovate[bot] | 6623ce9942 | 1 year ago |
renovate[bot] | 1dbc190fa6 | 1 year ago |
renovate[bot] | 46b9445fa6 | 1 year ago |
Ettore Di Giacinto | d3d3187e51 | 1 year ago |
Ettore Di Giacinto | 6c94f3cd67 | 1 year ago |
Ettore Di Giacinto | 295f3030a9 | 1 year ago |
renovate[bot] | 1ba88258a9 | 1 year ago |
Ettore Di Giacinto | 10ddd72b58 | 1 year ago |
Ettore Di Giacinto | 1b7990d5d9 | 1 year ago |
renovate[bot] | 9f50b8024d | 1 year ago |
Samuel Maynard | 7b9dcb05d4 | 1 year ago |
Ettore Di Giacinto | e37361985c | 1 year ago |
ci-robbot [bot] | 467e88d305 | 1 year ago |
renovate[bot] | fe4a8fbc74 | 1 year ago |
renovate[bot] | 2328bbaea1 | 1 year ago |
renovate[bot] | 4cc834adcd | 1 year ago |
renovate[bot] | 5e49ff5072 | 1 year ago |
ci-robbot [bot] | f98680a18a | 1 year ago |
Ettore Di Giacinto | 2880221bb3 | 1 year ago |
Samuel Maynard | 27887c74d8 | 1 year ago |
ci-robbot [bot] | 6306885fe7 | 1 year ago |
Ettore Di Giacinto | 2a11f16c0f | 1 year ago |
Ettore Di Giacinto | 2297504fb3 | 1 year ago |
ci-robbot [bot] | 897ac6e4e5 | 1 year ago |
renovate[bot] | f20c12a1c0 | 1 year ago |
renovate[bot] | 5dea31385c | 1 year ago |
renovate[bot] | 58f0f63926 | 1 year ago |
renovate[bot] | ed2bf48a6d | 1 year ago |
ci-robbot [bot] | e6c8ebb65c | 1 year ago |
renovate[bot] | 119733892e | 1 year ago |
ci-robbot [bot] | 437f563128 | 1 year ago |
renovate[bot] | ecad2261c8 | 1 year ago |
renovate[bot] | 182323a7fb | 1 year ago |
renovate[bot] | 30d06f9b12 | 1 year ago |
ci-robbot [bot] | 6bb562272d | 1 year ago |
Ettore Di Giacinto | 3b3164b039 | 1 year ago |
renovate[bot] | 6f0bdbd01c | 1 year ago |
renovate[bot] | ce2a1799ab | 1 year ago |
renovate[bot] | d088bd3034 | 1 year ago |
ci-robbot [bot] | 806e4c3a63 | 1 year ago |
renovate[bot] | 8532ce2002 | 1 year ago |
Ettore Di Giacinto | 84946e9275 | 1 year ago |
Ettore Di Giacinto | c9bbba4872 | 1 year ago |
Ettore Di Giacinto | ea9a651573 | 1 year ago |
Ettore Di Giacinto | 5abbb134d9 | 1 year ago |
renovate[bot] | 694dd4ad9e | 1 year ago |
renovate[bot] | 4af48e548a | 1 year ago |
Ettore Di Giacinto | 079dc197c7 | 1 year ago |
renovate[bot] | 77613169da | 1 year ago |
ci-robbot [bot] | 2630e251ce | 1 year ago |
ci-robbot [bot] | 0909a0637e | 1 year ago |
Ettore Di Giacinto | d62aef2016 | 1 year ago |
ci-robbot [bot] | 25e9483add | 1 year ago |
renovate[bot] | c1be2bdeeb | 1 year ago |
renovate[bot] | 49a2b30350 | 1 year ago |
renovate[bot] | 472cd0fc2f | 1 year ago |
renovate[bot] | dc9c43b6dd | 1 year ago |
renovate[bot] | e1e23a6302 | 1 year ago |
ci-robbot [bot] | 2e916abe15 | 1 year ago |
renovate[bot] | 3ebdb9b67e | 1 year ago |
renovate[bot] | 01f5046caf | 1 year ago |
renovate[bot] | ac17d544e0 | 1 year ago |
Ettore Di Giacinto | b447a2a719 | 1 year ago |
Ettore Di Giacinto | ec4fd1d219 | 1 year ago |
Ettore Di Giacinto | b503725dc7 | 1 year ago |
ci-robbot [bot] | e873fc7b71 | 1 year ago |
renovate[bot] | 3070e9503a | 1 year ago |
Ettore Di Giacinto | d9130def39 | 1 year ago |
renovate[bot] | cdf0a6e766 | 1 year ago |
renovate[bot] | a0e0ac887f | 1 year ago |
Ettore Di Giacinto | 4ddc956462 | 1 year ago |
renovate[bot] | 203fd7b2e8 | 1 year ago |
Ettore Di Giacinto | 1bb85377e4 | 1 year ago |
renovate[bot] | 3892fafc2d | 1 year ago |
renovate[bot] | 8a34679a13 | 1 year ago |
ci-robbot [bot] | b64c1d8ac1 | 1 year ago |
Ettore Di Giacinto | 8fb86c13bc | 1 year ago |
ci-robbot [bot] | 05edf59c91 | 1 year ago |
ci-robbot [bot] | b9f1f85433 | 1 year ago |
renovate[bot] | f8e2e76698 | 1 year ago |
ci-robbot [bot] | 29856f7527 | 1 year ago |
Sébastien Prud'homme | aa6cdf16c8 | 1 year ago |
Samuel Maynard | 96794851b3 | 1 year ago |
renovate[bot] | 51a1a721b3 | 1 year ago |
renovate[bot] | 695f3e5758 | 1 year ago |
Ettore Di Giacinto | e875c1f64a | 1 year ago |
Ettore Di Giacinto | 19f92d7d55 | 1 year ago |
Ettore Di Giacinto | 5a8dd40918 | 1 year ago |
renovate[bot] | 1b766ab89c | 1 year ago |
ci-robbot [bot] | a63d6f6364 | 1 year ago |
ci-robbot [bot] | 4422ca2235 | 1 year ago |
Ettore Di Giacinto | 78ad4813df | 1 year ago |
renovate[bot] | 42d753846e | 1 year ago |
ci-robbot [bot] | 5c018c0437 | 1 year ago |
renovate[bot] | 07cee3f6ef | 1 year ago |
ci-robbot [bot] | c5cb2ff268 | 1 year ago |
Aisuko | c8a4a4f4e9 | 1 year ago |
Pavel Zloi | 3ba07a5928 | 1 year ago |
renovate[bot] | 7282668da1 | 1 year ago |
renovate[bot] | 451e803444 | 1 year ago |
Ettore Di Giacinto | d70c55231b | 1 year ago |
ci-robbot [bot] | 275c124701 | 1 year ago |
ci-robbot [bot] | 87a6bbd251 | 1 year ago |
renovate[bot] | 8fd4c7afcc | 1 year ago |
Sébastien Prud'homme | eee3f83d98 | 1 year ago |
renovate[bot] | 28ee180283 | 1 year ago |
renovate[bot] | 432b0223f1 | 1 year ago |
renovate[bot] | 16050a32c7 | 1 year ago |
renovate[bot] | 898ca62b55 | 1 year ago |
ci-robbot [bot] | 5623a7c331 | 1 year ago |
ci-robbot [bot] | 9e3ca6d1a3 | 1 year ago |
ci-robbot [bot] | fa58965bbc | 1 year ago |
renovate[bot] | b8ef9028f1 | 1 year ago |
ci-robbot [bot] | f711d35377 | 1 year ago |
ci-robbot [bot] | abd3c62194 | 1 year ago |
Ettore Di Giacinto | 2f3c3b1867 | 1 year ago |
Ettore Di Giacinto | 11af09faf3 | 1 year ago |
Ettore Di Giacinto | 577d36b596 | 1 year ago |
Ettore Di Giacinto | 6d71dd7d98 | 1 year ago |
Aisuko | 49ce24984c | 1 year ago |
Ettore Di Giacinto | f401181cb5 | 1 year ago |
renovate[bot] | ff8295a97c | 1 year ago |
Ettore Di Giacinto | aacb96df7a | 1 year ago |
renovate[bot] | ca9115d6d0 | 1 year ago |
Ettore Di Giacinto | 2c91837865 | 1 year ago |
Sébastien Prud'homme | 2272324fd6 | 1 year ago |
Sébastien Prud'homme | 171b50bb1c | 1 year ago |
ci-robbot [bot] | 04d6bd7922 | 1 year ago |
ci-robbot [bot] | 2abdac7003 | 1 year ago |
Ettore Di Giacinto | 190f01dbe3 | 1 year ago |
renovate[bot] | 18a701355c | 1 year ago |
renovate[bot] | 3911957d34 | 1 year ago |
Ettore Di Giacinto | f5146bde18 | 1 year ago |
renovate[bot] | b57ea10c94 | 1 year ago |
renovate[bot] | 821cfed6c0 | 1 year ago |
renovate[bot] | 728f297bb8 | 1 year ago |
renovate[bot] | 4c0013fd79 | 1 year ago |
Ettore Di Giacinto | 65d06285d8 | 1 year ago |
renovate[bot] | e0d1a8995d | 1 year ago |
ci-robbot [bot] | 425beea6c5 | 1 year ago |
ci-robbot [bot] | cdfb930a69 | 1 year ago |
renovate[bot] | 09641b9790 | 1 year ago |
renovate[bot] | aac9a57500 | 1 year ago |
Ettore Di Giacinto | 59f7953249 | 1 year ago |
Ettore Di Giacinto | 217dbb448e | 1 year ago |
Ettore Di Giacinto | 76c881043e | 1 year ago |
ci-robbot [bot] | 835a20610b | 2 years ago |
ci-robbot [bot] | 74e808b8c3 | 2 years ago |
Ettore Di Giacinto | 53c83f2fae | 2 years ago |
renovate[bot] | 62365fa31d | 2 years ago |
Ettore Di Giacinto | a44c8e9b4e | 2 years ago |
ci-robbot [bot] | 320e430c7f | 2 years ago |
renovate[bot] | 8615646827 | 2 years ago |
renovate[bot] | 925d7c3057 | 2 years ago |
renovate[bot] | e350924ac1 | 2 years ago |
ci-robbot [bot] | e891a46740 | 2 years ago |
renovate[bot] | cd9285bbe6 | 2 years ago |
Ettore Di Giacinto | 917ff13c86 | 2 years ago |
Ettore Di Giacinto | 2a40f44023 | 2 years ago |
renovate[bot] | c22d06c780 | 2 years ago |
ci-robbot [bot] | babbd23744 | 2 years ago |
ci-robbot [bot] | eee41cbe2b | 2 years ago |
Ettore Di Giacinto | bf54b78270 | 2 years ago |
renovate[bot] | 589dfae89f | 2 years ago |
Ettore Di Giacinto | c8cc197ddd | 2 years ago |
Robert Gracey | 76c561a908 | 2 years ago |
renovate[bot] | 04797a80e1 | 2 years ago |
renovate[bot] | 29583a5ea5 | 2 years ago |
renovate[bot] | d12c1f7a4a | 2 years ago |
Al | 505572dae8 | 2 years ago |
renovate[bot] | 3ddea794e1 | 2 years ago |
renovate[bot] | 10e03bde35 | 2 years ago |
ci-robbot [bot] | e969604d75 | 2 years ago |
ci-robbot [bot] | c822e18f0d | 2 years ago |
Ettore Di Giacinto | 891af1c524 | 2 years ago |
Ettore Di Giacinto | 5807d0b766 | 2 years ago |
Ettore Di Giacinto | 9decd0813c | 2 years ago |
Ettore Di Giacinto | 43d3fb3eba | 2 years ago |
Ettore Di Giacinto | f5f8c687be | 2 years ago |
ci-robbot [bot] | 9e5cd0f10b | 2 years ago |
renovate[bot] | 231a3e7c02 | 2 years ago |
renovate[bot] | 57172e2e30 | 2 years ago |
Ettore Di Giacinto | 043399dd07 | 2 years ago |
renovate[bot] | 6b19356740 | 2 years ago |
ci-robbot [bot] | 1cbe6a7067 | 2 years ago |
Ettore Di Giacinto | 2912f9870f | 2 years ago |
Ettore Di Giacinto | 9630be56e1 | 2 years ago |
Robert Hambrock | 4aa78843c0 | 2 years ago |
renovate[bot] | b36d9f3776 | 2 years ago |
Ettore Di Giacinto | 6f54cab3f0 | 2 years ago |
Ettore Di Giacinto | ed5df1e68e | 2 years ago |
mudler | 3c07e11e73 | 2 years ago |
mudler | 91bdad1d12 | 2 years ago |
ci-robbot [bot] | 482a83886e | 2 years ago |
renovate[bot] | b8f52d67e1 | 2 years ago |
renovate[bot] | 9ed82199c5 | 2 years ago |
ci-robbot [bot] | 864aaf8c4d | 2 years ago |
renovate[bot] | c7056756d5 | 2 years ago |
ci-robbot [bot] | 93cc8569c3 | 2 years ago |
Ettore Di Giacinto | 05a3d569b0 | 2 years ago |
renovate[bot] | 7bc08797f9 | 2 years ago |
renovate[bot] | 5b22704799 | 2 years ago |
ci-robbot [bot] | 9609e4392b | 2 years ago |
Aisuko | d0c033d09b | 2 years ago |
Ettore Di Giacinto | 4e381cbe92 | 2 years ago |
renovate[bot] | ffaf3b1d36 | 2 years ago |
ci-robbot [bot] | 465a3b755d | 2 years ago |
ci-robbot [bot] | 91fc52bfb7 | 2 years ago |
mudler | b425954b9e | 2 years ago |
Ettore Di Giacinto | 2e64ed6255 | 2 years ago |
Ettore Di Giacinto | bf3d936aea | 2 years ago |
Aisuko | 19deea986a | 2 years ago |
Ettore Di Giacinto | aa7a18f131 | 2 years ago |
ci-robbot [bot] | 837ce2cb31 | 2 years ago |
renovate[bot] | cadce540f9 | 2 years ago |
Ettore Di Giacinto | 1fade53a61 | 2 years ago |
Tyler Gillson | 207ce81e4a | 2 years ago |
renovate[bot] | fc59f74849 | 2 years ago |
renovate[bot] | 9d3c5ead93 | 2 years ago |
Tyler Gillson | 549a01b62e | 2 years ago |
renovate[bot] | 5a6d9d4e5b | 2 years ago |
Sébastien Prud'homme | 1a7587ee48 | 2 years ago |
Ettore Di Giacinto | cc9aa9eb3f | 2 years ago |
ci-robbot [bot] | 5617e50ebc | 2 years ago |
ci-robbot [bot] | b83e8b950d | 2 years ago |
Ettore Di Giacinto | d15fc5371a | 2 years ago |
Ettore Di Giacinto | 3f739575d8 | 2 years ago |
ci-robbot [bot] | 7e4616646f | 2 years ago |
renovate[bot] | 44ffaf86ad | 2 years ago |
renovate[bot] | d096644c67 | 2 years ago |
renovate[bot] | 1428600de4 | 2 years ago |
renovate[bot] | 17b18df600 | 2 years ago |
renovate[bot] | cd81dbae1c | 2 years ago |
ci-robbot [bot] | 76be06ed56 | 2 years ago |
renovate[bot] | c2026e01c0 | 2 years ago |
Ettore Di Giacinto | cdca286be1 | 2 years ago |
ci-robbot [bot] | 41de6efca9 | 2 years ago |
renovate[bot] | 63a4ccebdc | 2 years ago |
renovate[bot] | 9237c1e91d | 2 years ago |
Ettore Di Giacinto | 9d051c5d4f | 2 years ago |
Ettore Di Giacinto | acd03d15f2 | 2 years ago |
Ettore Di Giacinto | a035de2fdd | 2 years ago |
Ettore Di Giacinto | 76a1267799 | 2 years ago |
Ettore Di Giacinto | e533b008d4 | 2 years ago |
Ettore Di Giacinto | a4380228e3 | 2 years ago |
Ettore Di Giacinto | 2a9d7474ce | 2 years ago |
Ettore Di Giacinto | 850a690290 | 2 years ago |
Ettore Di Giacinto | 39edd9ff73 | 2 years ago |
ci-robbot [bot] | b82bbbfc6b | 2 years ago |
mudler | 023c065812 | 2 years ago |
mudler | a627a6c4e2 | 2 years ago |
ci-robbot [bot] | 6c9ddff8e9 | 2 years ago |
ci-robbot [bot] | c5318587b8 | 2 years ago |
mudler | c3622299ce | 2 years ago |
Ettore Di Giacinto | de36a48861 | 2 years ago |
mudler | 961ca93219 | 2 years ago |
Ettore Di Giacinto | 557ccc5ad8 | 2 years ago |
Ettore Di Giacinto | 2488c445b6 | 2 years ago |
Ettore Di Giacinto | b4241d0a0d | 2 years ago |
Ettore Di Giacinto | 8250391e49 | 2 years ago |
Ettore Di Giacinto | fd1df4e971 | 2 years ago |
ci-robbot [bot] | 5115b2faa3 | 2 years ago |
ci-robbot [bot] | 93e82a8bf4 | 2 years ago |
Ettore Di Giacinto | 4413defca5 | 2 years ago |
Ettore Di Giacinto | f359e1c6c4 | 2 years ago |
renovate[bot] | 1bc87d582d | 2 years ago |
renovate[bot] | a86a383357 | 2 years ago |
renovate[bot] | 16f02c7b30 | 2 years ago |
Ettore Di Giacinto | fe2706890c | 2 years ago |
Ettore Di Giacinto | 85f0f8227d | 2 years ago |
Ettore Di Giacinto | 59e3c02002 | 2 years ago |
Matthew Campbell | 032dee256f | 2 years ago |
Matthew Campbell | 6b5e2b2bf5 | 2 years ago |
renovate[bot] | 6fc303de87 | 2 years ago |
renovate[bot] | 6ad6e4873d | 2 years ago |
ci-robbot [bot] | d6d7391da8 | 2 years ago |
Ettore Di Giacinto | 11675932ac | 2 years ago |
Ettore Di Giacinto | f02202e1e1 | 2 years ago |
Ettore Di Giacinto | f8ee20991c | 2 years ago |
Cedrik Boudreau | e6db14e2f1 | 2 years ago |
Dave | d00886abea | 2 years ago |
renovate[bot] | 4873d2bfa1 | 2 years ago |
Ettore Di Giacinto | 9f426578cf | 2 years ago |
ci-robbot [bot] | 9d01b695a8 | 2 years ago |
Ettore Di Giacinto | 93829ab228 | 2 years ago |
renovate[bot] | dd234f86d5 | 2 years ago |
mudler | 3daff6f1aa | 2 years ago |
Ettore Di Giacinto | 89dfa0f5fc | 2 years ago |
renovate[bot] | bc03c492a0 | 2 years ago |
Fabian Hachenberg | f50a4c1454 | 2 years ago |
mudler | d13d4d95ce | 2 years ago |
renovate[bot] | 428790ec06 | 2 years ago |
mudler | 4f551ce414 | 2 years ago |
mudler | 6ed7b10273 | 2 years ago |
mudler | 02979566ee | 2 years ago |
ci-robbot [bot] | cbdcc839f3 | 2 years ago |
mudler | e1c8f087f4 | 2 years ago |
mudler | 3a90ea44a5 | 2 years ago |
renovate[bot] | e55492475d | 2 years ago |
Dave | 07ec2e441d | 2 years ago |
ci-robbot [bot] | 38d7e0b43c | 2 years ago |
Dave | 3411bfd00d | 2 years ago |
Ettore Di Giacinto | 7e5fe35ae4 | 2 years ago |
mudler | 8c8cf38d4d | 2 years ago |
mudler | 75b25297fd | 2 years ago |
mudler | 009ee47fe2 | 2 years ago |
mudler | ec2adc2c03 | 2 years ago |
mudler | ad301e6ed7 | 2 years ago |
mudler | d094381e5d | 2 years ago |
mudler | 3ff9bbd217 | 2 years ago |
mudler | e62ee2bc06 | 2 years ago |
mudler | b49721cdd1 | 2 years ago |
mudler | 64c0a7967f | 2 years ago |
mudler | e96eadab40 | 2 years ago |
mudler | e73283121b | 2 years ago |
mudler | 857d13e8d6 | 2 years ago |
ci-robbot [bot] | 91db3d4d5c | 2 years ago |
Ettore Di Giacinto | 961cf29217 | 2 years ago |
Ettore Di Giacinto | c839b334eb | 2 years ago |
Ettore Di Giacinto | 714bfcd45b | 2 years ago |
renovate[bot] | 77ce8b953e | 2 years ago |
renovate[bot] | 01ada95941 | 2 years ago |
ci-robbot [bot] | eabdc5042a | 2 years ago |
Dhruv Gera | 96267d9437 | 2 years ago |
Ettore Di Giacinto | 9497a24127 | 2 years ago |
Ettore Di Giacinto | fdf75c6d0e | 2 years ago |
mudler | 6352308882 | 2 years ago |
mudler | a8172a0f4e | 2 years ago |
mudler | ebcd10d66f | 2 years ago |
mudler | 885642915f | 2 years ago |
mudler | 2e424491c0 | 2 years ago |
mudler | aa6faef8f7 | 2 years ago |
mudler | b3254baf60 | 2 years ago |
mudler | 0a43d27f0e | 2 years ago |
Ettore Di Giacinto | 3fe11fe24d | 2 years ago |
renovate[bot] | af18fdc749 | 2 years ago |
renovate[bot] | 32b5eddd7d | 2 years ago |
Dave | 07c3aa1869 | 2 years ago |
renovate[bot] | e59bad89e7 | 2 years ago |
Jeremy Price | b971807980 | 2 years ago |
Ettore Di Giacinto | c974dad799 | 2 years ago |
Ettore Di Giacinto | 4eae570ef5 | 2 years ago |
Ettore Di Giacinto | 67992a7d99 | 2 years ago |
renovate[bot] | 0a4899f366 | 2 years ago |
renovate[bot] | 1eb02f6c91 | 2 years ago |
mudler | 575874e4fb | 2 years ago |
Ettore Di Giacinto | 751b7eca62 | 2 years ago |
Ettore Di Giacinto | 1ae7150810 | 2 years ago |
Ettore Di Giacinto | 70caf9bf8c | 2 years ago |
Dave | 0b226ac027 | 2 years ago |
Ettore Di Giacinto | 220d6fd59b | 2 years ago |
antongisli | 0a00a4b58e | 2 years ago |
Ettore Di Giacinto | 156e15a4fa | 2 years ago |
renovate[bot] | 271d3f6673 | 2 years ago |
Ettore Di Giacinto | fec4ab93c5 | 2 years ago |
renovate[bot] | 38a7a7a54d | 2 years ago |
Ettore Di Giacinto | 0db0704e2c | 2 years ago |
Dave | 88f472e5d2 | 2 years ago |
Ettore Di Giacinto | 92452d46da | 2 years ago |
Ettore Di Giacinto | ac70252d70 | 2 years ago |
renovate[bot] | f6451d2518 | 2 years ago |
Ettore Di Giacinto | 2473f9d19b | 2 years ago |
renovate[bot] | bc583385a9 | 2 years ago |
renovate[bot] | 8286bfbab7 | 2 years ago |
Ettore Di Giacinto | d129fabe3b | 2 years ago |
renovate[bot] | 2539867247 | 2 years ago |
renovate[bot] | 69fedb92d9 | 2 years ago |
Ettore Di Giacinto | 54b5eadcc4 | 2 years ago |
Ettore Di Giacinto | 16773e2a35 | 2 years ago |
renovate[bot] | 78503c62b7 | 2 years ago |
Ettore Di Giacinto | a330c9cee5 | 2 years ago |
Ettore Di Giacinto | ff0867996e | 2 years ago |
Ettore Di Giacinto | 1bf8f996d1 | 2 years ago |
Ettore Di Giacinto | 52f4d993c1 | 2 years ago |
renovate[bot] | d0ceebc5d7 | 2 years ago |
renovate[bot] | 9122af3ae1 | 2 years ago |
Ettore Di Giacinto | b8533428bc | 2 years ago |
Ettore Di Giacinto | 677905334c | 2 years ago |
Mauro Morales | d1d55d29a0 | 2 years ago |
Ettore Di Giacinto | e07dba7ad6 | 2 years ago |
Matthieu Talbot | 062f832510 | 2 years ago |
Ettore Di Giacinto | d0330bb64b | 2 years ago |
antongisli | 91a23ec6ec | 2 years ago |
Ron Evans | 0b000dd043 | 2 years ago |
Ettore Di Giacinto | c73ba91a66 | 2 years ago |
Ettore Di Giacinto | dfc00f8bc1 | 2 years ago |
Ettore Di Giacinto | a18ff9c9b3 | 2 years ago |
Ettore Di Giacinto | d0199279ad | 2 years ago |
Ettore Di Giacinto | 9ede1e12d8 | 2 years ago |
Ettore Di Giacinto | c806eae0de | 2 years ago |
@ -1,3 +0,0 @@ |
||||
ARG GO_VERSION=1.20 |
||||
FROM mcr.microsoft.com/devcontainers/go:0-$GO_VERSION-bullseye |
||||
RUN apt-get update && apt-get install -y cmake |
@ -1,46 +0,0 @@ |
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the |
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose |
||||
{ |
||||
"name": "Existing Docker Compose (Extend)", |
||||
|
||||
// Update the 'dockerComposeFile' list if you have more compose files or use different names. |
||||
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. |
||||
"dockerComposeFile": [ |
||||
"../docker-compose.yaml", |
||||
"docker-compose.yml" |
||||
], |
||||
|
||||
// The 'service' property is the name of the service for the container that VS Code should |
||||
// use. Update this value and .devcontainer/docker-compose.yml to the real service name. |
||||
"service": "api", |
||||
|
||||
// The optional 'workspaceFolder' property is the path VS Code should open by default when |
||||
// connected. This is typically a file mount in .devcontainer/docker-compose.yml |
||||
"workspaceFolder": "/workspace", |
||||
|
||||
"features": { |
||||
"ghcr.io/devcontainers/features/go:1": {}, |
||||
"ghcr.io/azutake/devcontainer-features/go-packages-install:0": {} |
||||
}, |
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features. |
||||
// "features": {}, |
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally. |
||||
// "forwardPorts": [], |
||||
|
||||
// Uncomment the next line if you want start specific services in your Docker Compose config. |
||||
// "runServices": [], |
||||
|
||||
// Uncomment the next line if you want to keep your containers running after VS Code shuts down. |
||||
// "shutdownAction": "none", |
||||
|
||||
// Uncomment the next line to run commands after the container is created. |
||||
"postCreateCommand": "make prepare" |
||||
|
||||
// Configure tool-specific properties. |
||||
// "customizations": {}, |
||||
|
||||
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. |
||||
// "remoteUser": "devcontainer" |
||||
} |
@ -1,26 +0,0 @@ |
||||
version: '3.6' |
||||
services: |
||||
# Update this to the name of the service you want to work with in your docker-compose.yml file |
||||
api: |
||||
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer |
||||
# folder. Note that the path of the Dockerfile and context is relative to the *primary* |
||||
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" |
||||
# array). The sample below assumes your primary file is in the root of your project. |
||||
# |
||||
build: |
||||
context: . |
||||
dockerfile: .devcontainer/Dockerfile |
||||
|
||||
volumes: |
||||
# Update this to wherever you want VS Code to mount the folder of your project |
||||
- .:/workspace:cached |
||||
|
||||
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. |
||||
# cap_add: |
||||
# - SYS_PTRACE |
||||
# security_opt: |
||||
# - seccomp:unconfined |
||||
|
||||
# Overrides default command so things don't shut down after the process ends. |
||||
command: /bin/sh -c "while sleep 1000; do :; done" |
||||
|
@ -1 +1,5 @@ |
||||
.idea |
||||
models |
||||
examples/chatbot-ui/models |
||||
examples/rwkv/models |
||||
examples/**/models |
||||
|
@ -1,5 +1,43 @@ |
||||
## Set number of threads. |
||||
## Note: prefer the number of physical cores. Overbooking the CPU degrades performance notably. |
||||
# THREADS=14 |
||||
|
||||
## Specify a different bind address (defaults to ":8080") |
||||
# ADDRESS=127.0.0.1:8080 |
||||
|
||||
## Default models context size |
||||
# CONTEXT_SIZE=512 |
||||
# |
||||
## Define galleries. |
||||
## models will to install will be visible in `/models/available` |
||||
# GALLERIES=[{"name":"model-gallery", "url":"github:go-skynet/model-gallery/index.yaml"}] |
||||
|
||||
## CORS settings |
||||
# CORS=true |
||||
# CORS_ALLOW_ORIGINS=* |
||||
|
||||
## Default path for models |
||||
# |
||||
MODELS_PATH=/models |
||||
|
||||
## Enable debug mode |
||||
# DEBUG=true |
||||
# BUILD_TYPE=generic |
||||
|
||||
## Specify a build type. Available: cublas, openblas, clblas. |
||||
# BUILD_TYPE=openblas |
||||
|
||||
## Uncomment and set to true to enable rebuilding from source |
||||
# REBUILD=true |
||||
|
||||
## Enable go tags, available: stablediffusion, tts |
||||
## stablediffusion: image generation with stablediffusion |
||||
## tts: enables text-to-speech with go-piper |
||||
## (requires REBUILD=true) |
||||
# |
||||
# GO_TAGS=stablediffusion |
||||
|
||||
## Path where to store generated images |
||||
# IMAGE_PATH=/tmp |
||||
|
||||
## Specify a default upload limit in MB (whisper) |
||||
# UPLOAD_LIMIT |
||||
|
@ -0,0 +1,5 @@ |
||||
# These are supported funding model platforms |
||||
|
||||
github: [mudler] |
||||
custom: |
||||
- https://www.buymeacoffee.com/mudler |
@ -0,0 +1,31 @@ |
||||
--- |
||||
name: Bug report |
||||
about: Create a report to help us improve |
||||
title: '' |
||||
labels: bug |
||||
assignees: mudler |
||||
|
||||
--- |
||||
|
||||
<!-- Thanks for helping us to improve LocalAI! We welcome all bug reports. Please fill out each area of the template so we can better help you. Comments like this will be hidden when you post but you can delete them if you wish. --> |
||||
|
||||
**LocalAI version:** |
||||
<!-- Container Image or LocalAI tag/commit --> |
||||
|
||||
**Environment, CPU architecture, OS, and Version:** |
||||
<!-- Provide the output from "uname -a", HW specs, if it's a VM --> |
||||
|
||||
**Describe the bug** |
||||
<!-- A clear and concise description of what the bug is. --> |
||||
|
||||
**To Reproduce** |
||||
<!-- Steps to reproduce the behavior, including the LocalAI command used, if any --> |
||||
|
||||
**Expected behavior** |
||||
<!-- A clear and concise description of what you expected to happen. --> |
||||
|
||||
**Logs** |
||||
<!-- If applicable, add logs while running LocalAI in debug mode (`--debug` or `DEBUG=true`) to help explain your problem. --> |
||||
|
||||
**Additional context** |
||||
<!-- Add any other context about the problem here. --> |
@ -0,0 +1,8 @@ |
||||
blank_issues_enabled: false |
||||
contact_links: |
||||
- name: Community Support |
||||
url: https://github.com/go-skynet/LocalAI/discussions |
||||
about: Please ask and answer questions here. |
||||
- name: Discord |
||||
url: https://discord.gg/uJAeKSAGDy |
||||
about: Join our community on Discord! |
@ -0,0 +1,22 @@ |
||||
--- |
||||
name: Feature request |
||||
about: Suggest an idea for this project |
||||
title: '' |
||||
labels: enhancement |
||||
assignees: mudler |
||||
|
||||
--- |
||||
|
||||
<!-- Thanks for helping us to improve LocalAI! We welcome all feature requests. Please fill out each area of the template so we can better help you. Comments like this will be hidden when you post but you can delete them if you wish. --> |
||||
|
||||
**Is your feature request related to a problem? Please describe.** |
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] --> |
||||
|
||||
**Describe the solution you'd like** |
||||
<!-- A clear and concise description of what you want to happen. --> |
||||
|
||||
**Describe alternatives you've considered** |
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. --> |
||||
|
||||
**Additional context** |
||||
<!-- Add any other context or screenshots about the feature request here. --> |
@ -0,0 +1,23 @@ |
||||
**Description** |
||||
|
||||
This PR fixes # |
||||
|
||||
**Notes for Reviewers** |
||||
|
||||
|
||||
**[Signed commits](../CONTRIBUTING.md#signing-off-on-commits-developer-certificate-of-origin)** |
||||
- [ ] Yes, I signed my commits. |
||||
|
||||
|
||||
<!-- |
||||
Thank you for contributing to LocalAI! |
||||
|
||||
Contributing Conventions: |
||||
|
||||
1. Include descriptive PR titles with [<component-name>] prepended. |
||||
2. Build and test your changes before submitting a PR. |
||||
3. Sign your commits |
||||
|
||||
By following the community's contribution conventions upfront, the review process will |
||||
be accelerated and your PR merged more quickly. |
||||
--> |
@ -0,0 +1,9 @@ |
||||
#!/bin/bash |
||||
set -xe |
||||
REPO=$1 |
||||
BRANCH=$2 |
||||
VAR=$3 |
||||
|
||||
LAST_COMMIT=$(curl -s -H "Accept: application/vnd.github.VERSION.sha" "https://api.github.com/repos/$REPO/commits/$BRANCH") |
||||
|
||||
sed -i Makefile -e "s/$VAR?=.*/$VAR?=$LAST_COMMIT/" |
@ -0,0 +1,24 @@ |
||||
# .github/release.yml |
||||
|
||||
changelog: |
||||
exclude: |
||||
labels: |
||||
- ignore-for-release |
||||
categories: |
||||
- title: Breaking Changes 🛠 |
||||
labels: |
||||
- Semver-Major |
||||
- breaking-change |
||||
- title: "Bug fixes :bug:" |
||||
labels: |
||||
- bug |
||||
- title: Exciting New Features 🎉 |
||||
labels: |
||||
- Semver-Minor |
||||
- enhancement |
||||
- title: 👒 Dependencies |
||||
labels: |
||||
- dependencies |
||||
- title: Other Changes |
||||
labels: |
||||
- "*" |
@ -0,0 +1,18 @@ |
||||
# Number of days of inactivity before an issue becomes stale |
||||
daysUntilStale: 45 |
||||
# Number of days of inactivity before a stale issue is closed |
||||
daysUntilClose: 10 |
||||
# Issues with these labels will never be considered stale |
||||
exemptLabels: |
||||
- issue/willfix |
||||
# Label to use when marking an issue as stale |
||||
staleLabel: issue/stale |
||||
# Comment to post when marking an issue as stale. Set to `false` to disable |
||||
markComment: > |
||||
This issue has been automatically marked as stale because it has not had |
||||
recent activity. It will be closed if no further activity occurs. Thank you |
||||
for your contributions. |
||||
# Comment to post when closing a stale issue. Set to `false` to disable |
||||
closeComment: > |
||||
This issue is being automatically closed due to inactivity. |
||||
However, you may choose to reopen this issue. |
@ -0,0 +1,63 @@ |
||||
name: Bump dependencies |
||||
on: |
||||
schedule: |
||||
- cron: 0 20 * * * |
||||
workflow_dispatch: |
||||
jobs: |
||||
bump: |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
include: |
||||
- repository: "go-skynet/go-llama.cpp" |
||||
variable: "GOLLAMA_VERSION" |
||||
branch: "master" |
||||
- repository: "go-skynet/go-llama.cpp" |
||||
variable: "GOLLAMA_GRAMMAR_VERSION" |
||||
branch: "master" |
||||
- repository: "go-skynet/go-ggml-transformers.cpp" |
||||
variable: "GOGGMLTRANSFORMERS_VERSION" |
||||
branch: "master" |
||||
- repository: "donomii/go-rwkv.cpp" |
||||
variable: "RWKV_VERSION" |
||||
branch: "main" |
||||
- repository: "ggerganov/whisper.cpp" |
||||
variable: "WHISPER_CPP_VERSION" |
||||
branch: "master" |
||||
- repository: "go-skynet/go-bert.cpp" |
||||
variable: "BERT_VERSION" |
||||
branch: "master" |
||||
- repository: "go-skynet/bloomz.cpp" |
||||
variable: "BLOOMZ_VERSION" |
||||
branch: "main" |
||||
- repository: "nomic-ai/gpt4all" |
||||
variable: "GPT4ALL_VERSION" |
||||
branch: "main" |
||||
- repository: "mudler/go-ggllm.cpp" |
||||
variable: "GOGGLLM_VERSION" |
||||
branch: "master" |
||||
- repository: "mudler/go-stable-diffusion" |
||||
variable: "STABLEDIFFUSION_VERSION" |
||||
branch: "master" |
||||
- repository: "mudler/go-piper" |
||||
variable: "PIPER_VERSION" |
||||
branch: "master" |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v3 |
||||
- name: Bump dependencies 🔧 |
||||
run: | |
||||
bash .github/bump_deps.sh ${{ matrix.repository }} ${{ matrix.branch }} ${{ matrix.variable }} |
||||
- name: Create Pull Request |
||||
uses: peter-evans/create-pull-request@v5 |
||||
with: |
||||
token: ${{ secrets.UPDATE_BOT_TOKEN }} |
||||
push-to-fork: ci-forks/LocalAI |
||||
commit-message: ':arrow_up: Update ${{ matrix.repository }}' |
||||
title: ':arrow_up: Update ${{ matrix.repository }}' |
||||
branch: "update/${{ matrix.variable }}" |
||||
body: Bump of ${{ matrix.repository }} version |
||||
signoff: true |
||||
|
||||
|
||||
|
@ -0,0 +1,79 @@ |
||||
name: Build and Release |
||||
|
||||
on: push |
||||
|
||||
permissions: |
||||
contents: write |
||||
|
||||
jobs: |
||||
build-linux: |
||||
strategy: |
||||
matrix: |
||||
include: |
||||
- build: 'avx2' |
||||
defines: '' |
||||
- build: 'avx' |
||||
defines: '-DLLAMA_AVX2=OFF' |
||||
- build: 'avx512' |
||||
defines: '-DLLAMA_AVX512=ON' |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: Clone |
||||
uses: actions/checkout@v3 |
||||
with: |
||||
submodules: true |
||||
- name: Dependencies |
||||
run: | |
||||
sudo apt-get update |
||||
sudo apt-get install build-essential ffmpeg |
||||
- name: Build |
||||
id: build |
||||
env: |
||||
CMAKE_ARGS: "${{ matrix.defines }}" |
||||
BUILD_ID: "${{ matrix.build }}" |
||||
run: | |
||||
STATIC=true make dist |
||||
- uses: actions/upload-artifact@v3 |
||||
with: |
||||
name: ${{ matrix.build }} |
||||
path: release/ |
||||
- name: Release |
||||
uses: softprops/action-gh-release@v1 |
||||
if: startsWith(github.ref, 'refs/tags/') |
||||
with: |
||||
files: | |
||||
release/* |
||||
|
||||
build-macOS: |
||||
strategy: |
||||
matrix: |
||||
include: |
||||
- build: 'avx2' |
||||
defines: '' |
||||
- build: 'avx' |
||||
defines: '-DLLAMA_AVX2=OFF' |
||||
- build: 'avx512' |
||||
defines: '-DLLAMA_AVX512=ON' |
||||
runs-on: macOS-latest |
||||
steps: |
||||
- name: Clone |
||||
uses: actions/checkout@v3 |
||||
with: |
||||
submodules: true |
||||
- name: Build |
||||
id: build |
||||
env: |
||||
CMAKE_ARGS: "${{ matrix.defines }}" |
||||
BUILD_ID: "${{ matrix.build }}" |
||||
run: | |
||||
make dist |
||||
- uses: actions/upload-artifact@v3 |
||||
with: |
||||
name: ${{ matrix.build }} |
||||
path: release/ |
||||
- name: Release |
||||
uses: softprops/action-gh-release@v1 |
||||
if: startsWith(github.ref, 'refs/tags/') |
||||
with: |
||||
files: | |
||||
release/* |
@ -1,26 +0,0 @@ |
||||
name: goreleaser |
||||
|
||||
on: |
||||
push: |
||||
tags: |
||||
- 'v*' |
||||
|
||||
jobs: |
||||
goreleaser: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- name: Checkout |
||||
uses: actions/checkout@v3 |
||||
with: |
||||
fetch-depth: 0 |
||||
- name: Set up Go |
||||
uses: actions/setup-go@v3 |
||||
with: |
||||
go-version: 1.18 |
||||
- name: Run GoReleaser |
||||
uses: goreleaser/goreleaser-action@v4 |
||||
with: |
||||
version: latest |
||||
args: release --clean |
||||
env: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
@ -1,15 +0,0 @@ |
||||
# Make sure to check the documentation at http://goreleaser.com |
||||
project_name: local-ai |
||||
builds: |
||||
- ldflags: |
||||
- -w -s |
||||
env: |
||||
- CGO_ENABLED=0 |
||||
goos: |
||||
- linux |
||||
- darwin |
||||
- windows |
||||
goarch: |
||||
- amd64 |
||||
- arm64 |
||||
binary: '{{ .ProjectName }}' |
@ -1,13 +1,125 @@ |
||||
ARG GO_VERSION=1.20 |
||||
ARG DEBIAN_VERSION=11 |
||||
ARG BUILD_TYPE= |
||||
ARG GO_VERSION=1.20-bullseye |
||||
|
||||
FROM golang:$GO_VERSION as requirements |
||||
|
||||
ARG BUILD_TYPE |
||||
ARG CUDA_MAJOR_VERSION=11 |
||||
ARG CUDA_MINOR_VERSION=7 |
||||
ARG SPDLOG_VERSION="1.11.0" |
||||
ARG PIPER_PHONEMIZE_VERSION='1.0.0' |
||||
ARG TARGETARCH |
||||
ARG TARGETVARIANT |
||||
|
||||
ENV BUILD_TYPE=${BUILD_TYPE} |
||||
ENV EXTERNAL_GRPC_BACKENDS="huggingface-embeddings:/build/extra/grpc/huggingface/huggingface.py" |
||||
ARG GO_TAGS="stablediffusion tts" |
||||
|
||||
RUN apt-get update && \ |
||||
apt-get install -y ca-certificates cmake curl patch pip |
||||
|
||||
# Extras requirements |
||||
COPY extra/requirements.txt /build/extra/requirements.txt |
||||
RUN pip install -r /build/extra/requirements.txt && rm -rf /build/extra/requirements.txt |
||||
|
||||
# CuBLAS requirements |
||||
RUN if [ "${BUILD_TYPE}" = "cublas" ]; then \ |
||||
apt-get install -y software-properties-common && \ |
||||
apt-add-repository contrib && \ |
||||
curl -O https://developer.download.nvidia.com/compute/cuda/repos/debian11/x86_64/cuda-keyring_1.0-1_all.deb && \ |
||||
dpkg -i cuda-keyring_1.0-1_all.deb && \ |
||||
rm -f cuda-keyring_1.0-1_all.deb && \ |
||||
apt-get update && \ |
||||
apt-get install -y cuda-nvcc-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} libcublas-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \ |
||||
; fi |
||||
ENV PATH /usr/local/cuda/bin:${PATH} |
||||
|
||||
FROM golang:$GO_VERSION as builder |
||||
WORKDIR /build |
||||
RUN apt-get update && apt-get install -y cmake |
||||
|
||||
# OpenBLAS requirements |
||||
RUN apt-get install -y libopenblas-dev |
||||
|
||||
# Stable Diffusion requirements |
||||
RUN apt-get install -y libopencv-dev && \ |
||||
ln -s /usr/include/opencv4/opencv2 /usr/include/opencv2 |
||||
|
||||
# Use the variables in subsequent instructions |
||||
RUN echo "Target Architecture: $TARGETARCH" |
||||
RUN echo "Target Variant: $TARGETVARIANT" |
||||
|
||||
# piper requirements |
||||
# Use pre-compiled Piper phonemization library (includes onnxruntime) |
||||
#RUN if echo "${GO_TAGS}" | grep -q "tts"; then \ |
||||
RUN test -n "$TARGETARCH" \ |
||||
|| (echo 'warn: missing $TARGETARCH, either set this `ARG` manually, or run using `docker buildkit`') |
||||
|
||||
RUN curl -L "https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz" | \ |
||||
tar -xzvf - && \ |
||||
mkdir -p "spdlog-${SPDLOG_VERSION}/build" && \ |
||||
cd "spdlog-${SPDLOG_VERSION}/build" && \ |
||||
cmake .. && \ |
||||
make -j8 && \ |
||||
cmake --install . --prefix /usr && mkdir -p "lib/Linux-$(uname -m)" && \ |
||||
cd /build && \ |
||||
mkdir -p "lib/Linux-$(uname -m)/piper_phonemize" && \ |
||||
curl -L "https://github.com/rhasspy/piper-phonemize/releases/download/v${PIPER_PHONEMIZE_VERSION}/libpiper_phonemize-${TARGETARCH:-$(go env GOARCH)}${TARGETVARIANT}.tar.gz" | \ |
||||
tar -C "lib/Linux-$(uname -m)/piper_phonemize" -xzvf - && ls -liah /build/lib/Linux-$(uname -m)/piper_phonemize/ && \ |
||||
cp -rfv /build/lib/Linux-$(uname -m)/piper_phonemize/lib/. /lib64/ && \ |
||||
cp -rfv /build/lib/Linux-$(uname -m)/piper_phonemize/lib/. /usr/lib/ && \ |
||||
cp -rfv /build/lib/Linux-$(uname -m)/piper_phonemize/include/. /usr/include/ |
||||
# \ |
||||
# ; fi |
||||
|
||||
################################### |
||||
################################### |
||||
|
||||
FROM requirements as builder |
||||
|
||||
ARG GO_TAGS="stablediffusion tts" |
||||
|
||||
ENV GO_TAGS=${GO_TAGS} |
||||
ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility |
||||
ENV NVIDIA_REQUIRE_CUDA="cuda>=${CUDA_MAJOR_VERSION}.0" |
||||
ENV NVIDIA_VISIBLE_DEVICES=all |
||||
|
||||
WORKDIR /build |
||||
|
||||
COPY Makefile . |
||||
RUN make get-sources |
||||
COPY go.mod . |
||||
RUN make prepare |
||||
COPY . . |
||||
COPY .git . |
||||
|
||||
RUN ESPEAK_DATA=/build/lib/Linux-$(uname -m)/piper_phonemize/lib/espeak-ng-data make build |
||||
|
||||
################################### |
||||
################################### |
||||
|
||||
FROM requirements |
||||
|
||||
ARG FFMPEG |
||||
|
||||
ENV REBUILD=false |
||||
ENV HEALTHCHECK_ENDPOINT=http://localhost:8080/readyz |
||||
|
||||
# Add FFmpeg |
||||
RUN if [ "${FFMPEG}" = "true" ]; then \ |
||||
apt-get install -y ffmpeg \ |
||||
; fi |
||||
|
||||
WORKDIR /build |
||||
|
||||
# we start fresh & re-copy all assets because `make build` does not clean up nicely after itself |
||||
# so when `entrypoint.sh` runs `make build` again (which it does by default), the build would fail |
||||
# see https://github.com/go-skynet/LocalAI/pull/658#discussion_r1241971626 and |
||||
# https://github.com/go-skynet/LocalAI/pull/434 |
||||
COPY . . |
||||
RUN make build |
||||
RUN make prepare-sources |
||||
COPY --from=builder /build/local-ai ./ |
||||
|
||||
# Define the health check command |
||||
HEALTHCHECK --interval=1m --timeout=10m --retries=10 \ |
||||
CMD curl -f $HEALTHCHECK_ENDPOINT || exit 1 |
||||
|
||||
FROM debian:$DEBIAN_VERSION |
||||
COPY --from=builder /build/local-ai /usr/bin/local-ai |
||||
ENTRYPOINT [ "/usr/bin/local-ai" ] |
||||
EXPOSE 8080 |
||||
ENTRYPOINT [ "/build/entrypoint.sh" ] |
||||
|
@ -1,437 +1,184 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
"sync" |
||||
|
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
gpt2 "github.com/go-skynet/go-gpt2.cpp" |
||||
gptj "github.com/go-skynet/go-gpt4all-j.cpp" |
||||
llama "github.com/go-skynet/go-llama.cpp" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/localai" |
||||
"github.com/go-skynet/LocalAI/api/openai" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/internal" |
||||
"github.com/go-skynet/LocalAI/pkg/assets" |
||||
|
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/gofiber/fiber/v2/middleware/cors" |
||||
"github.com/gofiber/fiber/v2/middleware/logger" |
||||
"github.com/gofiber/fiber/v2/middleware/recover" |
||||
"github.com/rs/zerolog" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
// APIError provides error information returned by the OpenAI API.
|
||||
type APIError struct { |
||||
Code any `json:"code,omitempty"` |
||||
Message string `json:"message"` |
||||
Param *string `json:"param,omitempty"` |
||||
Type string `json:"type"` |
||||
} |
||||
|
||||
type ErrorResponse struct { |
||||
Error *APIError `json:"error,omitempty"` |
||||
} |
||||
|
||||
type OpenAIResponse struct { |
||||
Created int `json:"created,omitempty"` |
||||
Object string `json:"chat.completion,omitempty"` |
||||
ID string `json:"id,omitempty"` |
||||
Model string `json:"model,omitempty"` |
||||
Choices []Choice `json:"choices,omitempty"` |
||||
} |
||||
|
||||
type Choice struct { |
||||
Index int `json:"index,omitempty"` |
||||
FinishReason string `json:"finish_reason,omitempty"` |
||||
Message *Message `json:"message,omitempty"` |
||||
Text string `json:"text,omitempty"` |
||||
} |
||||
|
||||
type Message struct { |
||||
Role string `json:"role,omitempty"` |
||||
Content string `json:"content,omitempty"` |
||||
} |
||||
|
||||
type OpenAIModel struct { |
||||
ID string `json:"id"` |
||||
Object string `json:"object"` |
||||
} |
||||
|
||||
type OpenAIRequest struct { |
||||
Model string `json:"model"` |
||||
|
||||
// Prompt is read only by completion API calls
|
||||
Prompt string `json:"prompt"` |
||||
|
||||
Stop string `json:"stop"` |
||||
|
||||
// Messages is read only by chat/completion API calls
|
||||
Messages []Message `json:"messages"` |
||||
|
||||
Echo bool `json:"echo"` |
||||
// Common options between all the API calls
|
||||
TopP float64 `json:"top_p"` |
||||
TopK int `json:"top_k"` |
||||
Temperature float64 `json:"temperature"` |
||||
Maxtokens int `json:"max_tokens"` |
||||
|
||||
N int `json:"n"` |
||||
|
||||
// Custom parameters - not present in the OpenAI API
|
||||
Batch int `json:"batch"` |
||||
F16 bool `json:"f16kv"` |
||||
IgnoreEOS bool `json:"ignore_eos"` |
||||
RepeatPenalty float64 `json:"repeat_penalty"` |
||||
Keep int `json:"n_keep"` |
||||
|
||||
Seed int `json:"seed"` |
||||
} |
||||
|
||||
// https://platform.openai.com/docs/api-reference/completions
|
||||
func openAIEndpoint(chat, debug bool, loader *model.ModelLoader, threads, ctx int, f16 bool, mutexMap *sync.Mutex, mutexes map[string]*sync.Mutex) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
var err error |
||||
var model *llama.LLama |
||||
var gptModel *gptj.GPTJ |
||||
var gpt2Model *gpt2.GPT2 |
||||
var stableLMModel *gpt2.StableLM |
||||
|
||||
input := new(OpenAIRequest) |
||||
// Get input data from the request body
|
||||
if err := c.BodyParser(input); err != nil { |
||||
return err |
||||
} |
||||
modelFile := input.Model |
||||
received, _ := json.Marshal(input) |
||||
|
||||
log.Debug().Msgf("Request received: %s", string(received)) |
||||
|
||||
// Set model from bearer token, if available
|
||||
bearer := strings.TrimLeft(c.Get("authorization"), "Bearer ") |
||||
bearerExists := bearer != "" && loader.ExistsInModelPath(bearer) |
||||
|
||||
// If no model was specified, take the first available
|
||||
if modelFile == "" { |
||||
models, _ := loader.ListModels() |
||||
if len(models) > 0 { |
||||
modelFile = models[0] |
||||
log.Debug().Msgf("No model specified, using: %s", modelFile) |
||||
} |
||||
} |
||||
|
||||
// If no model is found or specified, we bail out
|
||||
if modelFile == "" && !bearerExists { |
||||
return fmt.Errorf("no model specified") |
||||
} |
||||
|
||||
// If a model is found in bearer token takes precedence
|
||||
if bearerExists { |
||||
log.Debug().Msgf("Using model from bearer token: %s", bearer) |
||||
modelFile = bearer |
||||
} |
||||
|
||||
// Try to load the model
|
||||
var llamaerr, gpt2err, gptjerr, stableerr error |
||||
llamaOpts := []llama.ModelOption{} |
||||
if ctx != 0 { |
||||
llamaOpts = append(llamaOpts, llama.SetContext(ctx)) |
||||
} |
||||
if f16 { |
||||
llamaOpts = append(llamaOpts, llama.EnableF16Memory) |
||||
} |
||||
func App(opts ...options.AppOption) (*fiber.App, error) { |
||||
options := options.NewOptions(opts...) |
||||
|
||||
// TODO: this is ugly, better identifying the model somehow! however, it is a good stab for a first implementation..
|
||||
model, llamaerr = loader.LoadLLaMAModel(modelFile, llamaOpts...) |
||||
if llamaerr != nil { |
||||
gptModel, gptjerr = loader.LoadGPTJModel(modelFile) |
||||
if gptjerr != nil { |
||||
gpt2Model, gpt2err = loader.LoadGPT2Model(modelFile) |
||||
if gpt2err != nil { |
||||
stableLMModel, stableerr = loader.LoadStableLMModel(modelFile) |
||||
if stableerr != nil { |
||||
return fmt.Errorf("llama: %s gpt: %s gpt2: %s stableLM: %s", llamaerr.Error(), gptjerr.Error(), gpt2err.Error(), stableerr.Error()) // llama failed first, so we want to catch both errors
|
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mutexMap.Lock() |
||||
l, ok := mutexes[modelFile] |
||||
if !ok { |
||||
m := &sync.Mutex{} |
||||
mutexes[modelFile] = m |
||||
l = m |
||||
} |
||||
mutexMap.Unlock() |
||||
l.Lock() |
||||
defer l.Unlock() |
||||
|
||||
// Set the parameters for the language model prediction
|
||||
topP := input.TopP |
||||
if topP == 0 { |
||||
topP = 0.7 |
||||
} |
||||
topK := input.TopK |
||||
if topK == 0 { |
||||
topK = 80 |
||||
} |
||||
|
||||
temperature := input.Temperature |
||||
if temperature == 0 { |
||||
temperature = 0.9 |
||||
} |
||||
|
||||
tokens := input.Maxtokens |
||||
if tokens == 0 { |
||||
tokens = 512 |
||||
} |
||||
|
||||
predInput := input.Prompt |
||||
if chat { |
||||
mess := []string{} |
||||
// TODO: encode roles
|
||||
for _, i := range input.Messages { |
||||
mess = append(mess, i.Content) |
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel) |
||||
if options.Debug { |
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel) |
||||
} |
||||
|
||||
predInput = strings.Join(mess, "\n") |
||||
} |
||||
// Return errors as JSON responses
|
||||
app := fiber.New(fiber.Config{ |
||||
BodyLimit: options.UploadLimitMB * 1024 * 1024, // this is the default limit of 4MB
|
||||
DisableStartupMessage: options.DisableMessage, |
||||
// Override default error handler
|
||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error { |
||||
// Status code defaults to 500
|
||||
code := fiber.StatusInternalServerError |
||||
|
||||
// A model can have a "file.bin.tmpl" file associated with a prompt template prefix
|
||||
templatedInput, err := loader.TemplatePrefix(modelFile, struct { |
||||
Input string |
||||
}{Input: predInput}) |
||||
if err == nil { |
||||
predInput = templatedInput |
||||
log.Debug().Msgf("Template found, input modified to: %s", predInput) |
||||
// Retrieve the custom status code if it's a *fiber.Error
|
||||
var e *fiber.Error |
||||
if errors.As(err, &e) { |
||||
code = e.Code |
||||
} |
||||
|
||||
result := []Choice{} |
||||
|
||||
n := input.N |
||||
|
||||
if input.N == 0 { |
||||
n = 1 |
||||
} |
||||
// Send custom error page
|
||||
return ctx.Status(code).JSON( |
||||
openai.ErrorResponse{ |
||||
Error: &openai.APIError{Message: err.Error(), Code: code}, |
||||
}, |
||||
) |
||||
}, |
||||
}) |
||||
|
||||
var predFunc func() (string, error) |
||||
switch { |
||||
case stableLMModel != nil: |
||||
predFunc = func() (string, error) { |
||||
// Generate the prediction using the language model
|
||||
predictOptions := []gpt2.PredictOption{ |
||||
gpt2.SetTemperature(temperature), |
||||
gpt2.SetTopP(topP), |
||||
gpt2.SetTopK(topK), |
||||
gpt2.SetTokens(tokens), |
||||
gpt2.SetThreads(threads), |
||||
if options.Debug { |
||||
app.Use(logger.New(logger.Config{ |
||||
Format: "[${ip}]:${port} ${status} - ${method} ${path}\n", |
||||
})) |
||||
} |
||||
|
||||
if input.Batch != 0 { |
||||
predictOptions = append(predictOptions, gpt2.SetBatch(input.Batch)) |
||||
} |
||||
log.Info().Msgf("Starting LocalAI using %d threads, with models path: %s", options.Threads, options.Loader.ModelPath) |
||||
log.Info().Msgf("LocalAI version: %s", internal.PrintableVersion()) |
||||
|
||||
if input.Seed != 0 { |
||||
predictOptions = append(predictOptions, gpt2.SetSeed(input.Seed)) |
||||
cm := config.NewConfigLoader() |
||||
if err := cm.LoadConfigs(options.Loader.ModelPath); err != nil { |
||||
log.Error().Msgf("error loading config files: %s", err.Error()) |
||||
} |
||||
|
||||
return stableLMModel.Predict( |
||||
predInput, |
||||
predictOptions..., |
||||
) |
||||
if options.ConfigFile != "" { |
||||
if err := cm.LoadConfigFile(options.ConfigFile); err != nil { |
||||
log.Error().Msgf("error loading config file: %s", err.Error()) |
||||
} |
||||
case gpt2Model != nil: |
||||
predFunc = func() (string, error) { |
||||
// Generate the prediction using the language model
|
||||
predictOptions := []gpt2.PredictOption{ |
||||
gpt2.SetTemperature(temperature), |
||||
gpt2.SetTopP(topP), |
||||
gpt2.SetTopK(topK), |
||||
gpt2.SetTokens(tokens), |
||||
gpt2.SetThreads(threads), |
||||
} |
||||
|
||||
if input.Batch != 0 { |
||||
predictOptions = append(predictOptions, gpt2.SetBatch(input.Batch)) |
||||
if options.Debug { |
||||
for _, v := range cm.ListConfigs() { |
||||
cfg, _ := cm.GetConfig(v) |
||||
log.Debug().Msgf("Model: %s (config: %+v)", v, cfg) |
||||
} |
||||
|
||||
if input.Seed != 0 { |
||||
predictOptions = append(predictOptions, gpt2.SetSeed(input.Seed)) |
||||
} |
||||
|
||||
return gpt2Model.Predict( |
||||
predInput, |
||||
predictOptions..., |
||||
) |
||||
if options.AssetsDestination != "" { |
||||
// Extract files from the embedded FS
|
||||
err := assets.ExtractFiles(options.BackendAssets, options.AssetsDestination) |
||||
log.Debug().Msgf("Extracting backend assets files to %s", options.AssetsDestination) |
||||
if err != nil { |
||||
log.Warn().Msgf("Failed extracting backend assets files: %s (might be required for some backends to work properly, like gpt4all)", err) |
||||
} |
||||
case gptModel != nil: |
||||
predFunc = func() (string, error) { |
||||
// Generate the prediction using the language model
|
||||
predictOptions := []gptj.PredictOption{ |
||||
gptj.SetTemperature(temperature), |
||||
gptj.SetTopP(topP), |
||||
gptj.SetTopK(topK), |
||||
gptj.SetTokens(tokens), |
||||
gptj.SetThreads(threads), |
||||
} |
||||
|
||||
if input.Batch != 0 { |
||||
predictOptions = append(predictOptions, gptj.SetBatch(input.Batch)) |
||||
} |
||||
|
||||
if input.Seed != 0 { |
||||
predictOptions = append(predictOptions, gptj.SetSeed(input.Seed)) |
||||
} |
||||
// Default middleware config
|
||||
app.Use(recover.New()) |
||||
|
||||
return gptModel.Predict( |
||||
predInput, |
||||
predictOptions..., |
||||
) |
||||
if options.PreloadJSONModels != "" { |
||||
if err := localai.ApplyGalleryFromString(options.Loader.ModelPath, options.PreloadJSONModels, cm, options.Galleries); err != nil { |
||||
return nil, err |
||||
} |
||||
case model != nil: |
||||
predFunc = func() (string, error) { |
||||
// Generate the prediction using the language model
|
||||
predictOptions := []llama.PredictOption{ |
||||
llama.SetTemperature(temperature), |
||||
llama.SetTopP(topP), |
||||
llama.SetTopK(topK), |
||||
llama.SetTokens(tokens), |
||||
llama.SetThreads(threads), |
||||
} |
||||
|
||||
if debug { |
||||
predictOptions = append(predictOptions, llama.Debug) |
||||
if options.PreloadModelsFromPath != "" { |
||||
if err := localai.ApplyGalleryFromFile(options.Loader.ModelPath, options.PreloadModelsFromPath, cm, options.Galleries); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if input.Stop != "" { |
||||
predictOptions = append(predictOptions, llama.SetStopWords(input.Stop)) |
||||
} |
||||
|
||||
if input.RepeatPenalty != 0 { |
||||
predictOptions = append(predictOptions, llama.SetPenalty(input.RepeatPenalty)) |
||||
if options.CORS { |
||||
var c func(ctx *fiber.Ctx) error |
||||
if options.CORSAllowOrigins == "" { |
||||
c = cors.New() |
||||
} else { |
||||
c = cors.New(cors.Config{AllowOrigins: options.CORSAllowOrigins}) |
||||
} |
||||
|
||||
if input.Keep != 0 { |
||||
predictOptions = append(predictOptions, llama.SetNKeep(input.Keep)) |
||||
app.Use(c) |
||||
} |
||||
|
||||
if input.Batch != 0 { |
||||
predictOptions = append(predictOptions, llama.SetBatch(input.Batch)) |
||||
} |
||||
// LocalAI API endpoints
|
||||
galleryService := localai.NewGalleryService(options.Loader.ModelPath) |
||||
galleryService.Start(options.Context, cm) |
||||
|
||||
if input.F16 { |
||||
predictOptions = append(predictOptions, llama.EnableF16KV) |
||||
} |
||||
app.Get("/version", func(c *fiber.Ctx) error { |
||||
return c.JSON(struct { |
||||
Version string `json:"version"` |
||||
}{Version: internal.PrintableVersion()}) |
||||
}) |
||||
|
||||
if input.IgnoreEOS { |
||||
predictOptions = append(predictOptions, llama.IgnoreEOS) |
||||
} |
||||
app.Post("/models/apply", localai.ApplyModelGalleryEndpoint(options.Loader.ModelPath, cm, galleryService.C, options.Galleries)) |
||||
app.Get("/models/available", localai.ListModelFromGalleryEndpoint(options.Galleries, options.Loader.ModelPath)) |
||||
app.Get("/models/jobs/:uuid", localai.GetOpStatusEndpoint(galleryService)) |
||||
|
||||
if input.Seed != 0 { |
||||
predictOptions = append(predictOptions, llama.SetSeed(input.Seed)) |
||||
} |
||||
// openAI compatible API endpoint
|
||||
|
||||
return model.Predict( |
||||
predInput, |
||||
predictOptions..., |
||||
) |
||||
} |
||||
} |
||||
// chat
|
||||
app.Post("/v1/chat/completions", openai.ChatEndpoint(cm, options)) |
||||
app.Post("/chat/completions", openai.ChatEndpoint(cm, options)) |
||||
|
||||
for i := 0; i < n; i++ { |
||||
prediction, err := predFunc() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// edit
|
||||
app.Post("/v1/edits", openai.EditEndpoint(cm, options)) |
||||
app.Post("/edits", openai.EditEndpoint(cm, options)) |
||||
|
||||
if input.Echo { |
||||
prediction = predInput + prediction |
||||
} |
||||
// completion
|
||||
app.Post("/v1/completions", openai.CompletionEndpoint(cm, options)) |
||||
app.Post("/completions", openai.CompletionEndpoint(cm, options)) |
||||
app.Post("/v1/engines/:model/completions", openai.CompletionEndpoint(cm, options)) |
||||
|
||||
if chat { |
||||
result = append(result, Choice{Message: &Message{Role: "assistant", Content: prediction}}) |
||||
} else { |
||||
result = append(result, Choice{Text: prediction}) |
||||
} |
||||
} |
||||
// embeddings
|
||||
app.Post("/v1/embeddings", openai.EmbeddingsEndpoint(cm, options)) |
||||
app.Post("/embeddings", openai.EmbeddingsEndpoint(cm, options)) |
||||
app.Post("/v1/engines/:model/embeddings", openai.EmbeddingsEndpoint(cm, options)) |
||||
|
||||
jsonResult, _ := json.Marshal(result) |
||||
log.Debug().Msgf("Response: %s", jsonResult) |
||||
// audio
|
||||
app.Post("/v1/audio/transcriptions", openai.TranscriptEndpoint(cm, options)) |
||||
app.Post("/tts", localai.TTSEndpoint(cm, options)) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: result, |
||||
}) |
||||
} |
||||
} |
||||
// images
|
||||
app.Post("/v1/images/generations", openai.ImageEndpoint(cm, options)) |
||||
|
||||
func listModels(loader *model.ModelLoader) func(ctx *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
models, err := loader.ListModels() |
||||
if err != nil { |
||||
return err |
||||
if options.ImageDir != "" { |
||||
app.Static("/generated-images", options.ImageDir) |
||||
} |
||||
|
||||
dataModels := []OpenAIModel{} |
||||
for _, m := range models { |
||||
dataModels = append(dataModels, OpenAIModel{ID: m, Object: "model"}) |
||||
} |
||||
return c.JSON(struct { |
||||
Object string `json:"object"` |
||||
Data []OpenAIModel `json:"data"` |
||||
}{ |
||||
Object: "list", |
||||
Data: dataModels, |
||||
}) |
||||
if options.AudioDir != "" { |
||||
app.Static("/generated-audio", options.AudioDir) |
||||
} |
||||
} |
||||
|
||||
func App(loader *model.ModelLoader, threads, ctxSize int, f16 bool, debug, disableMessage bool) *fiber.App { |
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel) |
||||
if debug { |
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel) |
||||
ok := func(c *fiber.Ctx) error { |
||||
return c.SendStatus(200) |
||||
} |
||||
|
||||
// Return errors as JSON responses
|
||||
app := fiber.New(fiber.Config{ |
||||
DisableStartupMessage: disableMessage, |
||||
// Override default error handler
|
||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error { |
||||
// Status code defaults to 500
|
||||
code := fiber.StatusInternalServerError |
||||
|
||||
// Retrieve the custom status code if it's a *fiber.Error
|
||||
var e *fiber.Error |
||||
if errors.As(err, &e) { |
||||
code = e.Code |
||||
} |
||||
|
||||
// Send custom error page
|
||||
return ctx.Status(code).JSON( |
||||
ErrorResponse{ |
||||
Error: &APIError{Message: err.Error(), Code: code}, |
||||
}, |
||||
) |
||||
}, |
||||
}) |
||||
|
||||
// Default middleware config
|
||||
app.Use(recover.New()) |
||||
app.Use(cors.New()) |
||||
|
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mu := map[string]*sync.Mutex{} |
||||
var mumutex = &sync.Mutex{} |
||||
|
||||
// openAI compatible API endpoint
|
||||
app.Post("/v1/chat/completions", openAIEndpoint(true, debug, loader, threads, ctxSize, f16, mumutex, mu)) |
||||
app.Post("/chat/completions", openAIEndpoint(true, debug, loader, threads, ctxSize, f16, mumutex, mu)) |
||||
// Kubernetes health checks
|
||||
app.Get("/healthz", ok) |
||||
app.Get("/readyz", ok) |
||||
|
||||
app.Post("/v1/completions", openAIEndpoint(false, debug, loader, threads, ctxSize, f16, mumutex, mu)) |
||||
app.Post("/completions", openAIEndpoint(false, debug, loader, threads, ctxSize, f16, mumutex, mu)) |
||||
// models
|
||||
app.Get("/v1/models", openai.ListModelsEndpoint(options.Loader, cm)) |
||||
app.Get("/models", openai.ListModelsEndpoint(options.Loader, cm)) |
||||
|
||||
app.Get("/v1/models", listModels(loader)) |
||||
app.Get("/models", listModels(loader)) |
||||
// turn off any process that was started by GRPC if the context is canceled
|
||||
go func() { |
||||
<-options.Context.Done() |
||||
log.Debug().Msgf("Context canceled, shutting down") |
||||
options.Loader.StopGRPC() |
||||
}() |
||||
|
||||
return app |
||||
return app, nil |
||||
} |
||||
|
@ -0,0 +1,109 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
) |
||||
|
||||
func ModelEmbedding(s string, tokens []int, loader *model.ModelLoader, c config.Config, o *options.Option) (func() ([]float32, error), error) { |
||||
if !c.Embeddings { |
||||
return nil, fmt.Errorf("endpoint disabled for this model by API configuration") |
||||
} |
||||
|
||||
modelFile := c.Model |
||||
|
||||
grpcOpts := gRPCModelOpts(c) |
||||
|
||||
var inferenceModel interface{} |
||||
var err error |
||||
|
||||
opts := []model.Option{ |
||||
model.WithLoadGRPCLLMModelOpts(grpcOpts), |
||||
model.WithThreads(uint32(c.Threads)), |
||||
model.WithAssetDir(o.AssetsDestination), |
||||
model.WithModelFile(modelFile), |
||||
model.WithContext(o.Context), |
||||
} |
||||
|
||||
for k, v := range o.ExternalGRPCBackends { |
||||
opts = append(opts, model.WithExternalBackend(k, v)) |
||||
} |
||||
|
||||
if c.Backend == "" { |
||||
inferenceModel, err = loader.GreedyLoader(opts...) |
||||
} else { |
||||
opts = append(opts, model.WithBackendString(c.Backend)) |
||||
inferenceModel, err = loader.BackendLoader(opts...) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var fn func() ([]float32, error) |
||||
switch model := inferenceModel.(type) { |
||||
case *grpc.Client: |
||||
fn = func() ([]float32, error) { |
||||
predictOptions := gRPCPredictOpts(c, loader.ModelPath) |
||||
if len(tokens) > 0 { |
||||
embeds := []int32{} |
||||
|
||||
for _, t := range tokens { |
||||
embeds = append(embeds, int32(t)) |
||||
} |
||||
predictOptions.EmbeddingTokens = embeds |
||||
|
||||
res, err := model.Embeddings(o.Context, predictOptions) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return res.Embeddings, nil |
||||
} |
||||
predictOptions.Embeddings = s |
||||
|
||||
res, err := model.Embeddings(o.Context, predictOptions) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return res.Embeddings, nil |
||||
} |
||||
default: |
||||
fn = func() ([]float32, error) { |
||||
return nil, fmt.Errorf("embeddings not supported by the backend") |
||||
} |
||||
} |
||||
|
||||
return func() ([]float32, error) { |
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mutexMap.Lock() |
||||
l, ok := mutexes[modelFile] |
||||
if !ok { |
||||
m := &sync.Mutex{} |
||||
mutexes[modelFile] = m |
||||
l = m |
||||
} |
||||
mutexMap.Unlock() |
||||
l.Lock() |
||||
defer l.Unlock() |
||||
|
||||
embeds, err := fn() |
||||
if err != nil { |
||||
return embeds, err |
||||
} |
||||
// Remove trailing 0s
|
||||
for i := len(embeds) - 1; i >= 0; i-- { |
||||
if embeds[i] == 0.0 { |
||||
embeds = embeds[:i] |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
return embeds, nil |
||||
}, nil |
||||
} |
@ -0,0 +1,68 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc/proto" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
) |
||||
|
||||
func ImageGeneration(height, width, mode, step, seed int, positive_prompt, negative_prompt, dst string, loader *model.ModelLoader, c config.Config, o *options.Option) (func() error, error) { |
||||
if c.Backend != model.StableDiffusionBackend { |
||||
return nil, fmt.Errorf("endpoint only working with stablediffusion models") |
||||
} |
||||
|
||||
opts := []model.Option{ |
||||
model.WithBackendString(c.Backend), |
||||
model.WithAssetDir(o.AssetsDestination), |
||||
model.WithThreads(uint32(c.Threads)), |
||||
model.WithContext(o.Context), |
||||
model.WithModelFile(c.ImageGenerationAssets), |
||||
} |
||||
|
||||
for k, v := range o.ExternalGRPCBackends { |
||||
opts = append(opts, model.WithExternalBackend(k, v)) |
||||
} |
||||
|
||||
inferenceModel, err := loader.BackendLoader( |
||||
opts..., |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
fn := func() error { |
||||
_, err := inferenceModel.GenerateImage( |
||||
o.Context, |
||||
&proto.GenerateImageRequest{ |
||||
Height: int32(height), |
||||
Width: int32(width), |
||||
Mode: int32(mode), |
||||
Step: int32(step), |
||||
Seed: int32(seed), |
||||
PositivePrompt: positive_prompt, |
||||
NegativePrompt: negative_prompt, |
||||
Dst: dst, |
||||
}) |
||||
return err |
||||
} |
||||
|
||||
return func() error { |
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mutexMap.Lock() |
||||
l, ok := mutexes[c.Backend] |
||||
if !ok { |
||||
m := &sync.Mutex{} |
||||
mutexes[c.Backend] = m |
||||
l = m |
||||
} |
||||
mutexMap.Unlock() |
||||
l.Lock() |
||||
defer l.Unlock() |
||||
|
||||
return fn() |
||||
}, nil |
||||
} |
@ -0,0 +1,124 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"os" |
||||
"regexp" |
||||
"strings" |
||||
"sync" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/gallery" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/go-skynet/LocalAI/pkg/utils" |
||||
) |
||||
|
||||
func ModelInference(s string, loader *model.ModelLoader, c config.Config, o *options.Option, tokenCallback func(string) bool) (func() (string, error), error) { |
||||
modelFile := c.Model |
||||
|
||||
grpcOpts := gRPCModelOpts(c) |
||||
|
||||
var inferenceModel *grpc.Client |
||||
var err error |
||||
|
||||
opts := []model.Option{ |
||||
model.WithLoadGRPCLLMModelOpts(grpcOpts), |
||||
model.WithThreads(uint32(c.Threads)), // some models uses this to allocate threads during startup
|
||||
model.WithAssetDir(o.AssetsDestination), |
||||
model.WithModelFile(modelFile), |
||||
model.WithContext(o.Context), |
||||
} |
||||
|
||||
for k, v := range o.ExternalGRPCBackends { |
||||
opts = append(opts, model.WithExternalBackend(k, v)) |
||||
} |
||||
|
||||
if c.Backend != "" { |
||||
opts = append(opts, model.WithBackendString(c.Backend)) |
||||
} |
||||
|
||||
// Check if the modelFile exists, if it doesn't try to load it from the gallery
|
||||
if o.AutoloadGalleries { // experimental
|
||||
if _, err := os.Stat(modelFile); os.IsNotExist(err) { |
||||
utils.ResetDownloadTimers() |
||||
// if we failed to load the model, we try to download it
|
||||
err := gallery.InstallModelFromGalleryByName(o.Galleries, modelFile, loader.ModelPath, gallery.GalleryModel{}, utils.DisplayDownloadFunction) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
} |
||||
|
||||
if c.Backend == "" { |
||||
inferenceModel, err = loader.GreedyLoader(opts...) |
||||
} else { |
||||
inferenceModel, err = loader.BackendLoader(opts...) |
||||
} |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// in GRPC, the backend is supposed to answer to 1 single token if stream is not supported
|
||||
fn := func() (string, error) { |
||||
opts := gRPCPredictOpts(c, loader.ModelPath) |
||||
opts.Prompt = s |
||||
if tokenCallback != nil { |
||||
ss := "" |
||||
err := inferenceModel.PredictStream(o.Context, opts, func(s string) { |
||||
tokenCallback(s) |
||||
ss += s |
||||
}) |
||||
return ss, err |
||||
} else { |
||||
reply, err := inferenceModel.Predict(o.Context, opts) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return reply.Message, err |
||||
} |
||||
} |
||||
|
||||
return func() (string, error) { |
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mutexMap.Lock() |
||||
l, ok := mutexes[modelFile] |
||||
if !ok { |
||||
m := &sync.Mutex{} |
||||
mutexes[modelFile] = m |
||||
l = m |
||||
} |
||||
mutexMap.Unlock() |
||||
l.Lock() |
||||
defer l.Unlock() |
||||
|
||||
return fn() |
||||
}, nil |
||||
} |
||||
|
||||
var cutstrings map[string]*regexp.Regexp = make(map[string]*regexp.Regexp) |
||||
var mu sync.Mutex = sync.Mutex{} |
||||
|
||||
func Finetune(config config.Config, input, prediction string) string { |
||||
if config.Echo { |
||||
prediction = input + prediction |
||||
} |
||||
|
||||
for _, c := range config.Cutstrings { |
||||
mu.Lock() |
||||
reg, ok := cutstrings[c] |
||||
if !ok { |
||||
cutstrings[c] = regexp.MustCompile(c) |
||||
reg = cutstrings[c] |
||||
} |
||||
mu.Unlock() |
||||
prediction = reg.ReplaceAllString(prediction, "") |
||||
} |
||||
|
||||
for _, c := range config.TrimSpace { |
||||
prediction = strings.TrimSpace(strings.TrimPrefix(prediction, c)) |
||||
} |
||||
return prediction |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
package backend |
||||
|
||||
import "sync" |
||||
|
||||
// mutex still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
var mutexMap sync.Mutex |
||||
var mutexes map[string]*sync.Mutex = make(map[string]*sync.Mutex) |
||||
|
||||
func Lock(s string) *sync.Mutex { |
||||
// This is still needed, see: https://github.com/ggerganov/llama.cpp/discussions/784
|
||||
mutexMap.Lock() |
||||
l, ok := mutexes[s] |
||||
if !ok { |
||||
m := &sync.Mutex{} |
||||
mutexes[s] = m |
||||
l = m |
||||
} |
||||
mutexMap.Unlock() |
||||
l.Lock() |
||||
|
||||
return l |
||||
} |
@ -0,0 +1,72 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
pb "github.com/go-skynet/LocalAI/pkg/grpc/proto" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
) |
||||
|
||||
func gRPCModelOpts(c config.Config) *pb.ModelOptions { |
||||
b := 512 |
||||
if c.Batch != 0 { |
||||
b = c.Batch |
||||
} |
||||
return &pb.ModelOptions{ |
||||
ContextSize: int32(c.ContextSize), |
||||
Seed: int32(c.Seed), |
||||
NBatch: int32(b), |
||||
F16Memory: c.F16, |
||||
MLock: c.MMlock, |
||||
NUMA: c.NUMA, |
||||
Embeddings: c.Embeddings, |
||||
LowVRAM: c.LowVRAM, |
||||
NGPULayers: int32(c.NGPULayers), |
||||
MMap: c.MMap, |
||||
MainGPU: c.MainGPU, |
||||
Threads: int32(c.Threads), |
||||
TensorSplit: c.TensorSplit, |
||||
} |
||||
} |
||||
|
||||
func gRPCPredictOpts(c config.Config, modelPath string) *pb.PredictOptions { |
||||
promptCachePath := "" |
||||
if c.PromptCachePath != "" { |
||||
p := filepath.Join(modelPath, c.PromptCachePath) |
||||
os.MkdirAll(filepath.Dir(p), 0755) |
||||
promptCachePath = p |
||||
} |
||||
return &pb.PredictOptions{ |
||||
Temperature: float32(c.Temperature), |
||||
TopP: float32(c.TopP), |
||||
TopK: int32(c.TopK), |
||||
Tokens: int32(c.Maxtokens), |
||||
Threads: int32(c.Threads), |
||||
PromptCacheAll: c.PromptCacheAll, |
||||
PromptCacheRO: c.PromptCacheRO, |
||||
PromptCachePath: promptCachePath, |
||||
F16KV: c.F16, |
||||
DebugMode: c.Debug, |
||||
Grammar: c.Grammar, |
||||
|
||||
Mirostat: int32(c.Mirostat), |
||||
MirostatETA: float32(c.MirostatETA), |
||||
MirostatTAU: float32(c.MirostatTAU), |
||||
Debug: c.Debug, |
||||
StopPrompts: c.StopWords, |
||||
Repeat: int32(c.RepeatPenalty), |
||||
NKeep: int32(c.Keep), |
||||
Batch: int32(c.Batch), |
||||
IgnoreEOS: c.IgnoreEOS, |
||||
Seed: int32(c.Seed), |
||||
FrequencyPenalty: float32(c.FrequencyPenalty), |
||||
MLock: c.MMlock, |
||||
MMap: c.MMap, |
||||
MainGPU: c.MainGPU, |
||||
TensorSplit: c.TensorSplit, |
||||
TailFreeSamplingZ: float32(c.TFZ), |
||||
TypicalP: float32(c.TypicalP), |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc/proto" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc/whisper/api" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
) |
||||
|
||||
func ModelTranscription(audio, language string, loader *model.ModelLoader, c config.Config, o *options.Option) (*api.Result, error) { |
||||
opts := []model.Option{ |
||||
model.WithBackendString(model.WhisperBackend), |
||||
model.WithModelFile(c.Model), |
||||
model.WithContext(o.Context), |
||||
model.WithThreads(uint32(c.Threads)), |
||||
model.WithAssetDir(o.AssetsDestination), |
||||
} |
||||
|
||||
for k, v := range o.ExternalGRPCBackends { |
||||
opts = append(opts, model.WithExternalBackend(k, v)) |
||||
} |
||||
|
||||
whisperModel, err := o.Loader.BackendLoader(opts...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if whisperModel == nil { |
||||
return nil, fmt.Errorf("could not load whisper model") |
||||
} |
||||
|
||||
return whisperModel.AudioTranscription(context.Background(), &proto.TranscriptRequest{ |
||||
Dst: audio, |
||||
Language: language, |
||||
Threads: uint32(c.Threads), |
||||
}) |
||||
} |
@ -0,0 +1,72 @@ |
||||
package backend |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/grpc/proto" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/go-skynet/LocalAI/pkg/utils" |
||||
) |
||||
|
||||
func generateUniqueFileName(dir, baseName, ext string) string { |
||||
counter := 1 |
||||
fileName := baseName + ext |
||||
|
||||
for { |
||||
filePath := filepath.Join(dir, fileName) |
||||
_, err := os.Stat(filePath) |
||||
if os.IsNotExist(err) { |
||||
return fileName |
||||
} |
||||
|
||||
counter++ |
||||
fileName = fmt.Sprintf("%s_%d%s", baseName, counter, ext) |
||||
} |
||||
} |
||||
|
||||
func ModelTTS(text, modelFile string, loader *model.ModelLoader, o *options.Option) (string, *proto.Result, error) { |
||||
opts := []model.Option{ |
||||
model.WithBackendString(model.PiperBackend), |
||||
model.WithModelFile(modelFile), |
||||
model.WithContext(o.Context), |
||||
model.WithAssetDir(o.AssetsDestination), |
||||
} |
||||
|
||||
for k, v := range o.ExternalGRPCBackends { |
||||
opts = append(opts, model.WithExternalBackend(k, v)) |
||||
} |
||||
|
||||
piperModel, err := o.Loader.BackendLoader(opts...) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
|
||||
if piperModel == nil { |
||||
return "", nil, fmt.Errorf("could not load piper model") |
||||
} |
||||
|
||||
if err := os.MkdirAll(o.AudioDir, 0755); err != nil { |
||||
return "", nil, fmt.Errorf("failed creating audio directory: %s", err) |
||||
} |
||||
|
||||
fileName := generateUniqueFileName(o.AudioDir, "piper", ".wav") |
||||
filePath := filepath.Join(o.AudioDir, fileName) |
||||
|
||||
modelPath := filepath.Join(o.Loader.ModelPath, modelFile) |
||||
|
||||
if err := utils.VerifyPath(modelPath, o.Loader.ModelPath); err != nil { |
||||
return "", nil, err |
||||
} |
||||
|
||||
res, err := piperModel.TTS(context.Background(), &proto.TTSRequest{ |
||||
Text: text, |
||||
Model: modelPath, |
||||
Dst: filePath, |
||||
}) |
||||
|
||||
return filePath, res, err |
||||
} |
@ -0,0 +1,209 @@ |
||||
package api_config |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/fs" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"gopkg.in/yaml.v3" |
||||
) |
||||
|
||||
type Config struct { |
||||
PredictionOptions `yaml:"parameters"` |
||||
Name string `yaml:"name"` |
||||
StopWords []string `yaml:"stopwords"` |
||||
Cutstrings []string `yaml:"cutstrings"` |
||||
TrimSpace []string `yaml:"trimspace"` |
||||
ContextSize int `yaml:"context_size"` |
||||
F16 bool `yaml:"f16"` |
||||
NUMA bool `yaml:"numa"` |
||||
Threads int `yaml:"threads"` |
||||
Debug bool `yaml:"debug"` |
||||
Roles map[string]string `yaml:"roles"` |
||||
Embeddings bool `yaml:"embeddings"` |
||||
Backend string `yaml:"backend"` |
||||
TemplateConfig TemplateConfig `yaml:"template"` |
||||
MirostatETA float64 `yaml:"mirostat_eta"` |
||||
MirostatTAU float64 `yaml:"mirostat_tau"` |
||||
Mirostat int `yaml:"mirostat"` |
||||
NGPULayers int `yaml:"gpu_layers"` |
||||
MMap bool `yaml:"mmap"` |
||||
MMlock bool `yaml:"mmlock"` |
||||
LowVRAM bool `yaml:"low_vram"` |
||||
|
||||
TensorSplit string `yaml:"tensor_split"` |
||||
MainGPU string `yaml:"main_gpu"` |
||||
ImageGenerationAssets string `yaml:"asset_dir"` |
||||
|
||||
PromptCachePath string `yaml:"prompt_cache_path"` |
||||
PromptCacheAll bool `yaml:"prompt_cache_all"` |
||||
PromptCacheRO bool `yaml:"prompt_cache_ro"` |
||||
|
||||
Grammar string `yaml:"grammar"` |
||||
|
||||
PromptStrings, InputStrings []string |
||||
InputToken [][]int |
||||
functionCallString, functionCallNameString string |
||||
|
||||
FunctionsConfig Functions `yaml:"function"` |
||||
} |
||||
|
||||
type Functions struct { |
||||
DisableNoAction bool `yaml:"disable_no_action"` |
||||
NoActionFunctionName string `yaml:"no_action_function_name"` |
||||
NoActionDescriptionName string `yaml:"no_action_description_name"` |
||||
} |
||||
|
||||
type TemplateConfig struct { |
||||
Completion string `yaml:"completion"` |
||||
Functions string `yaml:"function"` |
||||
Chat string `yaml:"chat"` |
||||
Edit string `yaml:"edit"` |
||||
} |
||||
|
||||
type ConfigLoader struct { |
||||
configs map[string]Config |
||||
sync.Mutex |
||||
} |
||||
|
||||
func (c *Config) SetFunctionCallString(s string) { |
||||
c.functionCallString = s |
||||
} |
||||
|
||||
func (c *Config) SetFunctionCallNameString(s string) { |
||||
c.functionCallNameString = s |
||||
} |
||||
|
||||
func (c *Config) ShouldUseFunctions() bool { |
||||
return ((c.functionCallString != "none" || c.functionCallString == "") || c.ShouldCallSpecificFunction()) |
||||
} |
||||
|
||||
func (c *Config) ShouldCallSpecificFunction() bool { |
||||
return len(c.functionCallNameString) > 0 |
||||
} |
||||
|
||||
func (c *Config) FunctionToCall() string { |
||||
return c.functionCallNameString |
||||
} |
||||
|
||||
func defaultPredictOptions(modelFile string) PredictionOptions { |
||||
return PredictionOptions{ |
||||
TopP: 0.7, |
||||
TopK: 80, |
||||
Maxtokens: 512, |
||||
Temperature: 0.9, |
||||
Model: modelFile, |
||||
} |
||||
} |
||||
|
||||
func DefaultConfig(modelFile string) *Config { |
||||
return &Config{ |
||||
PredictionOptions: defaultPredictOptions(modelFile), |
||||
} |
||||
} |
||||
|
||||
func NewConfigLoader() *ConfigLoader { |
||||
return &ConfigLoader{ |
||||
configs: make(map[string]Config), |
||||
} |
||||
} |
||||
func ReadConfigFile(file string) ([]*Config, error) { |
||||
c := &[]*Config{} |
||||
f, err := os.ReadFile(file) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("cannot read config file: %w", err) |
||||
} |
||||
if err := yaml.Unmarshal(f, c); err != nil { |
||||
return nil, fmt.Errorf("cannot unmarshal config file: %w", err) |
||||
} |
||||
|
||||
return *c, nil |
||||
} |
||||
|
||||
func ReadConfig(file string) (*Config, error) { |
||||
c := &Config{} |
||||
f, err := os.ReadFile(file) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("cannot read config file: %w", err) |
||||
} |
||||
if err := yaml.Unmarshal(f, c); err != nil { |
||||
return nil, fmt.Errorf("cannot unmarshal config file: %w", err) |
||||
} |
||||
|
||||
return c, nil |
||||
} |
||||
|
||||
func (cm *ConfigLoader) LoadConfigFile(file string) error { |
||||
cm.Lock() |
||||
defer cm.Unlock() |
||||
c, err := ReadConfigFile(file) |
||||
if err != nil { |
||||
return fmt.Errorf("cannot load config file: %w", err) |
||||
} |
||||
|
||||
for _, cc := range c { |
||||
cm.configs[cc.Name] = *cc |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (cm *ConfigLoader) LoadConfig(file string) error { |
||||
cm.Lock() |
||||
defer cm.Unlock() |
||||
c, err := ReadConfig(file) |
||||
if err != nil { |
||||
return fmt.Errorf("cannot read config file: %w", err) |
||||
} |
||||
|
||||
cm.configs[c.Name] = *c |
||||
return nil |
||||
} |
||||
|
||||
func (cm *ConfigLoader) GetConfig(m string) (Config, bool) { |
||||
cm.Lock() |
||||
defer cm.Unlock() |
||||
v, exists := cm.configs[m] |
||||
return v, exists |
||||
} |
||||
|
||||
func (cm *ConfigLoader) ListConfigs() []string { |
||||
cm.Lock() |
||||
defer cm.Unlock() |
||||
var res []string |
||||
for k := range cm.configs { |
||||
res = append(res, k) |
||||
} |
||||
return res |
||||
} |
||||
|
||||
func (cm *ConfigLoader) LoadConfigs(path string) error { |
||||
cm.Lock() |
||||
defer cm.Unlock() |
||||
entries, err := os.ReadDir(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
files := make([]fs.FileInfo, 0, len(entries)) |
||||
for _, entry := range entries { |
||||
info, err := entry.Info() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
files = append(files, info) |
||||
} |
||||
for _, file := range files { |
||||
// Skip templates, YAML and .keep files
|
||||
if !strings.Contains(file.Name(), ".yaml") { |
||||
continue |
||||
} |
||||
c, err := ReadConfig(filepath.Join(path, file.Name())) |
||||
if err == nil { |
||||
cm.configs[c.Name] = *c |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,56 @@ |
||||
package api_config_test |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
. "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/model" |
||||
. "github.com/onsi/ginkgo/v2" |
||||
. "github.com/onsi/gomega" |
||||
) |
||||
|
||||
var _ = Describe("Test cases for config related functions", func() { |
||||
|
||||
var ( |
||||
configFile string |
||||
) |
||||
|
||||
Context("Test Read configuration functions", func() { |
||||
configFile = os.Getenv("CONFIG_FILE") |
||||
It("Test ReadConfigFile", func() { |
||||
config, err := ReadConfigFile(configFile) |
||||
Expect(err).To(BeNil()) |
||||
Expect(config).ToNot(BeNil()) |
||||
// two configs in config.yaml
|
||||
Expect(config[0].Name).To(Equal("list1")) |
||||
Expect(config[1].Name).To(Equal("list2")) |
||||
}) |
||||
|
||||
It("Test LoadConfigs", func() { |
||||
cm := NewConfigLoader() |
||||
opts := options.NewOptions() |
||||
modelLoader := model.NewModelLoader(os.Getenv("MODELS_PATH")) |
||||
options.WithModelLoader(modelLoader)(opts) |
||||
|
||||
err := cm.LoadConfigs(opts.Loader.ModelPath) |
||||
Expect(err).To(BeNil()) |
||||
Expect(cm.ListConfigs()).ToNot(BeNil()) |
||||
|
||||
// config should includes gpt4all models's api.config
|
||||
Expect(cm.ListConfigs()).To(ContainElements("gpt4all")) |
||||
|
||||
// config should includes gpt2 models's api.config
|
||||
Expect(cm.ListConfigs()).To(ContainElements("gpt4all-2")) |
||||
|
||||
// config should includes text-embedding-ada-002 models's api.config
|
||||
Expect(cm.ListConfigs()).To(ContainElements("text-embedding-ada-002")) |
||||
|
||||
// config should includes rwkv_test models's api.config
|
||||
Expect(cm.ListConfigs()).To(ContainElements("rwkv_test")) |
||||
|
||||
// config should includes whisper-1 models's api.config
|
||||
Expect(cm.ListConfigs()).To(ContainElements("whisper-1")) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,37 @@ |
||||
package api_config |
||||
|
||||
type PredictionOptions struct { |
||||
|
||||
// Also part of the OpenAI official spec
|
||||
Model string `json:"model" yaml:"model"` |
||||
|
||||
// Also part of the OpenAI official spec
|
||||
Language string `json:"language"` |
||||
|
||||
// Also part of the OpenAI official spec. use it for returning multiple results
|
||||
N int `json:"n"` |
||||
|
||||
// Common options between all the API calls, part of the OpenAI spec
|
||||
TopP float64 `json:"top_p" yaml:"top_p"` |
||||
TopK int `json:"top_k" yaml:"top_k"` |
||||
Temperature float64 `json:"temperature" yaml:"temperature"` |
||||
Maxtokens int `json:"max_tokens" yaml:"max_tokens"` |
||||
Echo bool `json:"echo"` |
||||
|
||||
// Custom parameters - not present in the OpenAI API
|
||||
Batch int `json:"batch" yaml:"batch"` |
||||
F16 bool `json:"f16" yaml:"f16"` |
||||
IgnoreEOS bool `json:"ignore_eos" yaml:"ignore_eos"` |
||||
RepeatPenalty float64 `json:"repeat_penalty" yaml:"repeat_penalty"` |
||||
Keep int `json:"n_keep" yaml:"n_keep"` |
||||
|
||||
MirostatETA float64 `json:"mirostat_eta" yaml:"mirostat_eta"` |
||||
MirostatTAU float64 `json:"mirostat_tau" yaml:"mirostat_tau"` |
||||
Mirostat int `json:"mirostat" yaml:"mirostat"` |
||||
|
||||
FrequencyPenalty float64 `json:"frequency_penalty" yaml:"frequency_penalty"` |
||||
TFZ float64 `json:"tfz" yaml:"tfz"` |
||||
|
||||
TypicalP float64 `json:"typical_p" yaml:"typical_p"` |
||||
Seed int `json:"seed" yaml:"seed"` |
||||
} |
@ -0,0 +1,224 @@ |
||||
package localai |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"os" |
||||
"strings" |
||||
"sync" |
||||
|
||||
json "github.com/json-iterator/go" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/pkg/gallery" |
||||
"github.com/go-skynet/LocalAI/pkg/utils" |
||||
|
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/google/uuid" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
type galleryOp struct { |
||||
req gallery.GalleryModel |
||||
id string |
||||
galleries []gallery.Gallery |
||||
galleryName string |
||||
} |
||||
|
||||
type galleryOpStatus struct { |
||||
Error error `json:"error"` |
||||
Processed bool `json:"processed"` |
||||
Message string `json:"message"` |
||||
Progress float64 `json:"progress"` |
||||
TotalFileSize string `json:"file_size"` |
||||
DownloadedFileSize string `json:"downloaded_size"` |
||||
} |
||||
|
||||
type galleryApplier struct { |
||||
modelPath string |
||||
sync.Mutex |
||||
C chan galleryOp |
||||
statuses map[string]*galleryOpStatus |
||||
} |
||||
|
||||
func NewGalleryService(modelPath string) *galleryApplier { |
||||
return &galleryApplier{ |
||||
modelPath: modelPath, |
||||
C: make(chan galleryOp), |
||||
statuses: make(map[string]*galleryOpStatus), |
||||
} |
||||
} |
||||
|
||||
// prepareModel applies a
|
||||
func prepareModel(modelPath string, req gallery.GalleryModel, cm *config.ConfigLoader, downloadStatus func(string, string, string, float64)) error { |
||||
|
||||
config, err := gallery.GetGalleryConfigFromURL(req.URL) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
config.Files = append(config.Files, req.AdditionalFiles...) |
||||
|
||||
return gallery.InstallModel(modelPath, req.Name, &config, req.Overrides, downloadStatus) |
||||
} |
||||
|
||||
func (g *galleryApplier) updateStatus(s string, op *galleryOpStatus) { |
||||
g.Lock() |
||||
defer g.Unlock() |
||||
g.statuses[s] = op |
||||
} |
||||
|
||||
func (g *galleryApplier) getStatus(s string) *galleryOpStatus { |
||||
g.Lock() |
||||
defer g.Unlock() |
||||
|
||||
return g.statuses[s] |
||||
} |
||||
|
||||
func (g *galleryApplier) Start(c context.Context, cm *config.ConfigLoader) { |
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-c.Done(): |
||||
return |
||||
case op := <-g.C: |
||||
utils.ResetDownloadTimers() |
||||
|
||||
g.updateStatus(op.id, &galleryOpStatus{Message: "processing", Progress: 0}) |
||||
|
||||
// updates the status with an error
|
||||
updateError := func(e error) { |
||||
g.updateStatus(op.id, &galleryOpStatus{Error: e, Processed: true, Message: "error: " + e.Error()}) |
||||
} |
||||
|
||||
// displayDownload displays the download progress
|
||||
progressCallback := func(fileName string, current string, total string, percentage float64) { |
||||
g.updateStatus(op.id, &galleryOpStatus{Message: "processing", Progress: percentage, TotalFileSize: total, DownloadedFileSize: current}) |
||||
utils.DisplayDownloadFunction(fileName, current, total, percentage) |
||||
} |
||||
|
||||
var err error |
||||
// if the request contains a gallery name, we apply the gallery from the gallery list
|
||||
if op.galleryName != "" { |
||||
if strings.Contains(op.galleryName, "@") { |
||||
err = gallery.InstallModelFromGallery(op.galleries, op.galleryName, g.modelPath, op.req, progressCallback) |
||||
} else { |
||||
err = gallery.InstallModelFromGalleryByName(op.galleries, op.galleryName, g.modelPath, op.req, progressCallback) |
||||
} |
||||
} else { |
||||
err = prepareModel(g.modelPath, op.req, cm, progressCallback) |
||||
} |
||||
|
||||
if err != nil { |
||||
updateError(err) |
||||
continue |
||||
} |
||||
|
||||
// Reload models
|
||||
err = cm.LoadConfigs(g.modelPath) |
||||
if err != nil { |
||||
updateError(err) |
||||
continue |
||||
} |
||||
|
||||
g.updateStatus(op.id, &galleryOpStatus{Processed: true, Message: "completed", Progress: 100}) |
||||
} |
||||
} |
||||
}() |
||||
} |
||||
|
||||
type galleryModel struct { |
||||
gallery.GalleryModel |
||||
ID string `json:"id"` |
||||
} |
||||
|
||||
func ApplyGalleryFromFile(modelPath, s string, cm *config.ConfigLoader, galleries []gallery.Gallery) error { |
||||
dat, err := os.ReadFile(s) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return ApplyGalleryFromString(modelPath, string(dat), cm, galleries) |
||||
} |
||||
|
||||
func ApplyGalleryFromString(modelPath, s string, cm *config.ConfigLoader, galleries []gallery.Gallery) error { |
||||
var requests []galleryModel |
||||
err := json.Unmarshal([]byte(s), &requests) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, r := range requests { |
||||
utils.ResetDownloadTimers() |
||||
if r.ID == "" { |
||||
err = prepareModel(modelPath, r.GalleryModel, cm, utils.DisplayDownloadFunction) |
||||
} else { |
||||
err = gallery.InstallModelFromGallery(galleries, r.ID, modelPath, r.GalleryModel, utils.DisplayDownloadFunction) |
||||
} |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
/// Endpoints
|
||||
|
||||
func GetOpStatusEndpoint(g *galleryApplier) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
|
||||
status := g.getStatus(c.Params("uuid")) |
||||
if status == nil { |
||||
return fmt.Errorf("could not find any status for ID") |
||||
} |
||||
|
||||
return c.JSON(status) |
||||
} |
||||
} |
||||
|
||||
type GalleryModel struct { |
||||
ID string `json:"id"` |
||||
gallery.GalleryModel |
||||
} |
||||
|
||||
func ApplyModelGalleryEndpoint(modelPath string, cm *config.ConfigLoader, g chan galleryOp, galleries []gallery.Gallery) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
input := new(GalleryModel) |
||||
// Get input data from the request body
|
||||
if err := c.BodyParser(input); err != nil { |
||||
return err |
||||
} |
||||
|
||||
uuid, err := uuid.NewUUID() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
g <- galleryOp{ |
||||
req: input.GalleryModel, |
||||
id: uuid.String(), |
||||
galleryName: input.ID, |
||||
galleries: galleries, |
||||
} |
||||
return c.JSON(struct { |
||||
ID string `json:"uuid"` |
||||
StatusURL string `json:"status"` |
||||
}{ID: uuid.String(), StatusURL: c.BaseURL() + "/models/jobs/" + uuid.String()}) |
||||
} |
||||
} |
||||
|
||||
func ListModelFromGalleryEndpoint(galleries []gallery.Gallery, basePath string) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
log.Debug().Msgf("Listing models from galleries: %+v", galleries) |
||||
|
||||
models, err := gallery.AvailableGalleryModels(galleries, basePath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.Debug().Msgf("Models found from galleries: %+v", models) |
||||
for _, m := range models { |
||||
log.Debug().Msgf("Model found from galleries: %+v", m) |
||||
} |
||||
dat, err := json.Marshal(models) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return c.Send(dat) |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
package localai |
||||
|
||||
import ( |
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/gofiber/fiber/v2" |
||||
) |
||||
|
||||
type TTSRequest struct { |
||||
Model string `json:"model" yaml:"model"` |
||||
Input string `json:"input" yaml:"input"` |
||||
} |
||||
|
||||
func TTSEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
|
||||
input := new(TTSRequest) |
||||
// Get input data from the request body
|
||||
if err := c.BodyParser(input); err != nil { |
||||
return err |
||||
} |
||||
|
||||
filePath, _, err := backend.ModelTTS(input.Input, input.Model, o.Loader, o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return c.Download(filePath) |
||||
} |
||||
} |
@ -0,0 +1,105 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
|
||||
"github.com/go-skynet/LocalAI/pkg/grammar" |
||||
) |
||||
|
||||
// APIError provides error information returned by the OpenAI API.
|
||||
type APIError struct { |
||||
Code any `json:"code,omitempty"` |
||||
Message string `json:"message"` |
||||
Param *string `json:"param,omitempty"` |
||||
Type string `json:"type"` |
||||
} |
||||
|
||||
type ErrorResponse struct { |
||||
Error *APIError `json:"error,omitempty"` |
||||
} |
||||
|
||||
type OpenAIUsage struct { |
||||
PromptTokens int `json:"prompt_tokens"` |
||||
CompletionTokens int `json:"completion_tokens"` |
||||
TotalTokens int `json:"total_tokens"` |
||||
} |
||||
|
||||
type Item struct { |
||||
Embedding []float32 `json:"embedding"` |
||||
Index int `json:"index"` |
||||
Object string `json:"object,omitempty"` |
||||
|
||||
// Images
|
||||
URL string `json:"url,omitempty"` |
||||
B64JSON string `json:"b64_json,omitempty"` |
||||
} |
||||
|
||||
type OpenAIResponse struct { |
||||
Created int `json:"created,omitempty"` |
||||
Object string `json:"object,omitempty"` |
||||
ID string `json:"id,omitempty"` |
||||
Model string `json:"model,omitempty"` |
||||
Choices []Choice `json:"choices,omitempty"` |
||||
Data []Item `json:"data,omitempty"` |
||||
|
||||
Usage OpenAIUsage `json:"usage"` |
||||
} |
||||
|
||||
type Choice struct { |
||||
Index int `json:"index"` |
||||
FinishReason string `json:"finish_reason,omitempty"` |
||||
Message *Message `json:"message,omitempty"` |
||||
Delta *Message `json:"delta,omitempty"` |
||||
Text string `json:"text,omitempty"` |
||||
} |
||||
|
||||
type Message struct { |
||||
// The message role
|
||||
Role string `json:"role,omitempty" yaml:"role"` |
||||
// The message content
|
||||
Content *string `json:"content" yaml:"content"` |
||||
// A result of a function call
|
||||
FunctionCall interface{} `json:"function_call,omitempty" yaml:"function_call,omitempty"` |
||||
} |
||||
|
||||
type OpenAIModel struct { |
||||
ID string `json:"id"` |
||||
Object string `json:"object"` |
||||
} |
||||
|
||||
type OpenAIRequest struct { |
||||
config.PredictionOptions |
||||
|
||||
// whisper
|
||||
File string `json:"file" validate:"required"` |
||||
//whisper/image
|
||||
ResponseFormat string `json:"response_format"` |
||||
// image
|
||||
Size string `json:"size"` |
||||
// Prompt is read only by completion/image API calls
|
||||
Prompt interface{} `json:"prompt" yaml:"prompt"` |
||||
|
||||
// Edit endpoint
|
||||
Instruction string `json:"instruction" yaml:"instruction"` |
||||
Input interface{} `json:"input" yaml:"input"` |
||||
|
||||
Stop interface{} `json:"stop" yaml:"stop"` |
||||
|
||||
// Messages is read only by chat/completion API calls
|
||||
Messages []Message `json:"messages" yaml:"messages"` |
||||
|
||||
// A list of available functions to call
|
||||
Functions []grammar.Function `json:"functions" yaml:"functions"` |
||||
FunctionCall interface{} `json:"function_call" yaml:"function_call"` // might be a string or an object
|
||||
|
||||
Stream bool `json:"stream"` |
||||
|
||||
// Image (not supported by OpenAI)
|
||||
Mode int `json:"mode"` |
||||
Step int `json:"step"` |
||||
|
||||
// A grammar to constrain the LLM output
|
||||
Grammar string `json:"grammar" yaml:"grammar"` |
||||
|
||||
JSONFunctionGrammarObject *grammar.JSONFunctionStructure `json:"grammar_json_functions" yaml:"grammar_json_functions"` |
||||
} |
@ -0,0 +1,322 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/go-skynet/LocalAI/pkg/grammar" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
"github.com/valyala/fasthttp" |
||||
) |
||||
|
||||
func ChatEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
emptyMessage := "" |
||||
|
||||
process := func(s string, req *OpenAIRequest, config *config.Config, loader *model.ModelLoader, responses chan OpenAIResponse) { |
||||
initialMessage := OpenAIResponse{ |
||||
Model: req.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: []Choice{{Delta: &Message{Role: "assistant", Content: &emptyMessage}}}, |
||||
Object: "chat.completion.chunk", |
||||
} |
||||
responses <- initialMessage |
||||
|
||||
ComputeChoices(s, req.N, config, o, loader, func(s string, c *[]Choice) {}, func(s string) bool { |
||||
resp := OpenAIResponse{ |
||||
Model: req.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: []Choice{{Delta: &Message{Content: &s}, Index: 0}}, |
||||
Object: "chat.completion.chunk", |
||||
} |
||||
|
||||
responses <- resp |
||||
return true |
||||
}) |
||||
close(responses) |
||||
} |
||||
return func(c *fiber.Ctx) error { |
||||
processFunctions := false |
||||
funcs := grammar.Functions{} |
||||
model, input, err := readInput(c, o.Loader, true) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
config, input, err := readConfig(model, input, cm, o.Loader, o.Debug, o.Threads, o.ContextSize, o.F16) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
log.Debug().Msgf("Configuration read: %+v", config) |
||||
|
||||
// Allow the user to set custom actions via config file
|
||||
// to be "embedded" in each model
|
||||
noActionName := "answer" |
||||
noActionDescription := "use this action to answer without performing any action" |
||||
|
||||
if config.FunctionsConfig.NoActionFunctionName != "" { |
||||
noActionName = config.FunctionsConfig.NoActionFunctionName |
||||
} |
||||
if config.FunctionsConfig.NoActionDescriptionName != "" { |
||||
noActionDescription = config.FunctionsConfig.NoActionDescriptionName |
||||
} |
||||
|
||||
// process functions if we have any defined or if we have a function call string
|
||||
if len(input.Functions) > 0 && config.ShouldUseFunctions() { |
||||
log.Debug().Msgf("Response needs to process functions") |
||||
|
||||
processFunctions = true |
||||
|
||||
noActionGrammar := grammar.Function{ |
||||
Name: noActionName, |
||||
Description: noActionDescription, |
||||
Parameters: map[string]interface{}{ |
||||
"properties": map[string]interface{}{ |
||||
"message": map[string]interface{}{ |
||||
"type": "string", |
||||
"description": "The message to reply the user with", |
||||
}}, |
||||
}, |
||||
} |
||||
|
||||
// Append the no action function
|
||||
funcs = append(funcs, input.Functions...) |
||||
if !config.FunctionsConfig.DisableNoAction { |
||||
funcs = append(funcs, noActionGrammar) |
||||
} |
||||
|
||||
// Force picking one of the functions by the request
|
||||
if config.FunctionToCall() != "" { |
||||
funcs = funcs.Select(config.FunctionToCall()) |
||||
} |
||||
|
||||
// Update input grammar
|
||||
jsStruct := funcs.ToJSONStructure() |
||||
config.Grammar = jsStruct.Grammar("") |
||||
} else if input.JSONFunctionGrammarObject != nil { |
||||
config.Grammar = input.JSONFunctionGrammarObject.Grammar("") |
||||
} |
||||
|
||||
// functions are not supported in stream mode (yet?)
|
||||
toStream := input.Stream && !processFunctions |
||||
|
||||
log.Debug().Msgf("Parameters: %+v", config) |
||||
|
||||
var predInput string |
||||
|
||||
mess := []string{} |
||||
for _, i := range input.Messages { |
||||
var content string |
||||
role := i.Role |
||||
// if function call, we might want to customize the role so we can display better that the "assistant called a json action"
|
||||
// if an "assistant_function_call" role is defined, we use it, otherwise we use the role that is passed by in the request
|
||||
if i.FunctionCall != nil && i.Role == "assistant" { |
||||
roleFn := "assistant_function_call" |
||||
r := config.Roles[roleFn] |
||||
if r != "" { |
||||
role = roleFn |
||||
} |
||||
} |
||||
r := config.Roles[role] |
||||
contentExists := i.Content != nil && *i.Content != "" |
||||
if r != "" { |
||||
if contentExists { |
||||
content = fmt.Sprint(r, " ", *i.Content) |
||||
} |
||||
if i.FunctionCall != nil { |
||||
j, err := json.Marshal(i.FunctionCall) |
||||
if err == nil { |
||||
if contentExists { |
||||
content += "\n" + fmt.Sprint(r, " ", string(j)) |
||||
} else { |
||||
content = fmt.Sprint(r, " ", string(j)) |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
if contentExists { |
||||
content = fmt.Sprint(*i.Content) |
||||
} |
||||
if i.FunctionCall != nil { |
||||
j, err := json.Marshal(i.FunctionCall) |
||||
if err == nil { |
||||
if contentExists { |
||||
content += "\n" + string(j) |
||||
} else { |
||||
content = string(j) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
mess = append(mess, content) |
||||
} |
||||
|
||||
predInput = strings.Join(mess, "\n") |
||||
log.Debug().Msgf("Prompt (before templating): %s", predInput) |
||||
|
||||
if toStream { |
||||
log.Debug().Msgf("Stream request received") |
||||
c.Context().SetContentType("text/event-stream") |
||||
//c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)
|
||||
// c.Set("Content-Type", "text/event-stream")
|
||||
c.Set("Cache-Control", "no-cache") |
||||
c.Set("Connection", "keep-alive") |
||||
c.Set("Transfer-Encoding", "chunked") |
||||
} |
||||
|
||||
templateFile := config.Model |
||||
|
||||
if config.TemplateConfig.Chat != "" && !processFunctions { |
||||
templateFile = config.TemplateConfig.Chat |
||||
} |
||||
|
||||
if config.TemplateConfig.Functions != "" && processFunctions { |
||||
templateFile = config.TemplateConfig.Functions |
||||
} |
||||
|
||||
// A model can have a "file.bin.tmpl" file associated with a prompt template prefix
|
||||
templatedInput, err := o.Loader.TemplatePrefix(templateFile, struct { |
||||
Input string |
||||
Functions []grammar.Function |
||||
}{ |
||||
Input: predInput, |
||||
Functions: funcs, |
||||
}) |
||||
if err == nil { |
||||
predInput = templatedInput |
||||
log.Debug().Msgf("Template found, input modified to: %s", predInput) |
||||
} else { |
||||
log.Debug().Msgf("Template failed loading: %s", err.Error()) |
||||
} |
||||
|
||||
log.Debug().Msgf("Prompt (after templating): %s", predInput) |
||||
if processFunctions { |
||||
log.Debug().Msgf("Grammar: %+v", config.Grammar) |
||||
} |
||||
|
||||
if toStream { |
||||
responses := make(chan OpenAIResponse) |
||||
|
||||
go process(predInput, input, config, o.Loader, responses) |
||||
|
||||
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { |
||||
|
||||
for ev := range responses { |
||||
var buf bytes.Buffer |
||||
enc := json.NewEncoder(&buf) |
||||
enc.Encode(ev) |
||||
|
||||
log.Debug().Msgf("Sending chunk: %s", buf.String()) |
||||
fmt.Fprintf(w, "data: %v\n", buf.String()) |
||||
w.Flush() |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: []Choice{ |
||||
{ |
||||
FinishReason: "stop", |
||||
Index: 0, |
||||
Delta: &Message{Content: &emptyMessage}, |
||||
}}, |
||||
Object: "chat.completion.chunk", |
||||
} |
||||
respData, _ := json.Marshal(resp) |
||||
|
||||
w.WriteString(fmt.Sprintf("data: %s\n\n", respData)) |
||||
w.WriteString("data: [DONE]\n\n") |
||||
w.Flush() |
||||
})) |
||||
return nil |
||||
} |
||||
|
||||
result, err := ComputeChoices(predInput, input.N, config, o, o.Loader, func(s string, c *[]Choice) { |
||||
if processFunctions { |
||||
// As we have to change the result before processing, we can't stream the answer (yet?)
|
||||
ss := map[string]interface{}{} |
||||
json.Unmarshal([]byte(s), &ss) |
||||
log.Debug().Msgf("Function return: %s %+v", s, ss) |
||||
|
||||
// The grammar defines the function name as "function", while OpenAI returns "name"
|
||||
func_name := ss["function"] |
||||
// Similarly, while here arguments is a map[string]interface{}, OpenAI actually want a stringified object
|
||||
args := ss["arguments"] // arguments needs to be a string, but we return an object from the grammar result (TODO: fix)
|
||||
d, _ := json.Marshal(args) |
||||
|
||||
ss["arguments"] = string(d) |
||||
ss["name"] = func_name |
||||
|
||||
// if do nothing, reply with a message
|
||||
if func_name == noActionName { |
||||
log.Debug().Msgf("nothing to do, computing a reply") |
||||
|
||||
// If there is a message that the LLM already sends as part of the JSON reply, use it
|
||||
arguments := map[string]interface{}{} |
||||
json.Unmarshal([]byte(d), &arguments) |
||||
m, exists := arguments["message"] |
||||
if exists { |
||||
switch message := m.(type) { |
||||
case string: |
||||
if message != "" { |
||||
log.Debug().Msgf("Reply received from LLM: %s", message) |
||||
message = backend.Finetune(*config, predInput, message) |
||||
log.Debug().Msgf("Reply received from LLM(finetuned): %s", message) |
||||
|
||||
*c = append(*c, Choice{Message: &Message{Role: "assistant", Content: &message}}) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
log.Debug().Msgf("No action received from LLM, without a message, computing a reply") |
||||
// Otherwise ask the LLM to understand the JSON output and the context, and return a message
|
||||
// Note: This costs (in term of CPU) another computation
|
||||
config.Grammar = "" |
||||
predFunc, err := backend.ModelInference(predInput, o.Loader, *config, o, nil) |
||||
if err != nil { |
||||
log.Error().Msgf("inference error: %s", err.Error()) |
||||
return |
||||
} |
||||
|
||||
prediction, err := predFunc() |
||||
if err != nil { |
||||
log.Error().Msgf("inference error: %s", err.Error()) |
||||
return |
||||
} |
||||
|
||||
prediction = backend.Finetune(*config, predInput, prediction) |
||||
*c = append(*c, Choice{Message: &Message{Role: "assistant", Content: &prediction}}) |
||||
} else { |
||||
// otherwise reply with the function call
|
||||
*c = append(*c, Choice{ |
||||
FinishReason: "function_call", |
||||
Message: &Message{Role: "assistant", FunctionCall: ss}, |
||||
}) |
||||
} |
||||
|
||||
return |
||||
} |
||||
*c = append(*c, Choice{FinishReason: "stop", Index: 0, Message: &Message{Role: "assistant", Content: &s}}) |
||||
}, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: result, |
||||
Object: "chat.completion", |
||||
} |
||||
respData, _ := json.Marshal(resp) |
||||
log.Debug().Msgf("Response: %s", respData) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(resp) |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
"github.com/valyala/fasthttp" |
||||
) |
||||
|
||||
// https://platform.openai.com/docs/api-reference/completions
|
||||
func CompletionEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
process := func(s string, req *OpenAIRequest, config *config.Config, loader *model.ModelLoader, responses chan OpenAIResponse) { |
||||
ComputeChoices(s, req.N, config, o, loader, func(s string, c *[]Choice) {}, func(s string) bool { |
||||
resp := OpenAIResponse{ |
||||
Model: req.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: []Choice{ |
||||
{ |
||||
Index: 0, |
||||
Text: s, |
||||
}, |
||||
}, |
||||
Object: "text_completion", |
||||
} |
||||
log.Debug().Msgf("Sending goroutine: %s", s) |
||||
|
||||
responses <- resp |
||||
return true |
||||
}) |
||||
close(responses) |
||||
} |
||||
|
||||
return func(c *fiber.Ctx) error { |
||||
model, input, err := readInput(c, o.Loader, true) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
log.Debug().Msgf("`input`: %+v", input) |
||||
|
||||
config, input, err := readConfig(model, input, cm, o.Loader, o.Debug, o.Threads, o.ContextSize, o.F16) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
log.Debug().Msgf("Parameter Config: %+v", config) |
||||
|
||||
if input.Stream { |
||||
log.Debug().Msgf("Stream request received") |
||||
c.Context().SetContentType("text/event-stream") |
||||
//c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)
|
||||
//c.Set("Content-Type", "text/event-stream")
|
||||
c.Set("Cache-Control", "no-cache") |
||||
c.Set("Connection", "keep-alive") |
||||
c.Set("Transfer-Encoding", "chunked") |
||||
} |
||||
|
||||
templateFile := config.Model |
||||
|
||||
if config.TemplateConfig.Completion != "" { |
||||
templateFile = config.TemplateConfig.Completion |
||||
} |
||||
|
||||
if input.Stream { |
||||
if len(config.PromptStrings) > 1 { |
||||
return errors.New("cannot handle more than 1 `PromptStrings` when Streaming") |
||||
} |
||||
|
||||
predInput := config.PromptStrings[0] |
||||
|
||||
// A model can have a "file.bin.tmpl" file associated with a prompt template prefix
|
||||
templatedInput, err := o.Loader.TemplatePrefix(templateFile, struct { |
||||
Input string |
||||
}{ |
||||
Input: predInput, |
||||
}) |
||||
if err == nil { |
||||
predInput = templatedInput |
||||
log.Debug().Msgf("Template found, input modified to: %s", predInput) |
||||
} |
||||
|
||||
responses := make(chan OpenAIResponse) |
||||
|
||||
go process(predInput, input, config, o.Loader, responses) |
||||
|
||||
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { |
||||
|
||||
for ev := range responses { |
||||
var buf bytes.Buffer |
||||
enc := json.NewEncoder(&buf) |
||||
enc.Encode(ev) |
||||
|
||||
log.Debug().Msgf("Sending chunk: %s", buf.String()) |
||||
fmt.Fprintf(w, "data: %v\n", buf.String()) |
||||
w.Flush() |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: []Choice{ |
||||
{ |
||||
Index: 0, |
||||
FinishReason: "stop", |
||||
}, |
||||
}, |
||||
Object: "text_completion", |
||||
} |
||||
respData, _ := json.Marshal(resp) |
||||
|
||||
w.WriteString(fmt.Sprintf("data: %s\n\n", respData)) |
||||
w.WriteString("data: [DONE]\n\n") |
||||
w.Flush() |
||||
})) |
||||
return nil |
||||
} |
||||
|
||||
var result []Choice |
||||
for k, i := range config.PromptStrings { |
||||
// A model can have a "file.bin.tmpl" file associated with a prompt template prefix
|
||||
templatedInput, err := o.Loader.TemplatePrefix(templateFile, struct { |
||||
Input string |
||||
}{ |
||||
Input: i, |
||||
}) |
||||
if err == nil { |
||||
i = templatedInput |
||||
log.Debug().Msgf("Template found, input modified to: %s", i) |
||||
} |
||||
|
||||
r, err := ComputeChoices(i, input.N, config, o, o.Loader, func(s string, c *[]Choice) { |
||||
*c = append(*c, Choice{Text: s, FinishReason: "stop", Index: k}) |
||||
}, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
result = append(result, r...) |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: result, |
||||
Object: "text_completion", |
||||
} |
||||
|
||||
jsonResult, _ := json.Marshal(resp) |
||||
log.Debug().Msgf("Response: %s", jsonResult) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(resp) |
||||
} |
||||
} |
@ -0,0 +1,67 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
func EditEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
model, input, err := readInput(c, o.Loader, true) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
config, input, err := readConfig(model, input, cm, o.Loader, o.Debug, o.Threads, o.ContextSize, o.F16) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
log.Debug().Msgf("Parameter Config: %+v", config) |
||||
|
||||
templateFile := config.Model |
||||
|
||||
if config.TemplateConfig.Edit != "" { |
||||
templateFile = config.TemplateConfig.Edit |
||||
} |
||||
|
||||
var result []Choice |
||||
for _, i := range config.InputStrings { |
||||
// A model can have a "file.bin.tmpl" file associated with a prompt template prefix
|
||||
templatedInput, err := o.Loader.TemplatePrefix(templateFile, struct { |
||||
Input string |
||||
Instruction string |
||||
}{Input: i}) |
||||
if err == nil { |
||||
i = templatedInput |
||||
log.Debug().Msgf("Template found, input modified to: %s", i) |
||||
} |
||||
|
||||
r, err := ComputeChoices(i, input.N, config, o, o.Loader, func(s string, c *[]Choice) { |
||||
*c = append(*c, Choice{Text: s}) |
||||
}, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
result = append(result, r...) |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Choices: result, |
||||
Object: "edit", |
||||
} |
||||
|
||||
jsonResult, _ := json.Marshal(resp) |
||||
log.Debug().Msgf("Response: %s", jsonResult) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(resp) |
||||
} |
||||
} |
@ -0,0 +1,70 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
// https://platform.openai.com/docs/api-reference/embeddings
|
||||
func EmbeddingsEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
model, input, err := readInput(c, o.Loader, true) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
config, input, err := readConfig(model, input, cm, o.Loader, o.Debug, o.Threads, o.ContextSize, o.F16) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
log.Debug().Msgf("Parameter Config: %+v", config) |
||||
items := []Item{} |
||||
|
||||
for i, s := range config.InputToken { |
||||
// get the model function to call for the result
|
||||
embedFn, err := backend.ModelEmbedding("", s, o.Loader, *config, o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
embeddings, err := embedFn() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
items = append(items, Item{Embedding: embeddings, Index: i, Object: "embedding"}) |
||||
} |
||||
|
||||
for i, s := range config.InputStrings { |
||||
// get the model function to call for the result
|
||||
embedFn, err := backend.ModelEmbedding(s, []int{}, o.Loader, *config, o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
embeddings, err := embedFn() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
items = append(items, Item{Embedding: embeddings, Index: i, Object: "embedding"}) |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Model: input.Model, // we have to return what the user sent here, due to OpenAI spec.
|
||||
Data: items, |
||||
Object: "list", |
||||
} |
||||
|
||||
jsonResult, _ := json.Marshal(resp) |
||||
log.Debug().Msgf("Response: %s", jsonResult) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(resp) |
||||
} |
||||
} |
@ -0,0 +1,158 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
// https://platform.openai.com/docs/api-reference/images/create
|
||||
|
||||
/* |
||||
* |
||||
|
||||
curl http://localhost:8080/v1/images/generations \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ |
||||
"prompt": "A cute baby sea otter", |
||||
"n": 1, |
||||
"size": "512x512" |
||||
}' |
||||
|
||||
* |
||||
*/ |
||||
func ImageEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
m, input, err := readInput(c, o.Loader, false) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
if m == "" { |
||||
m = model.StableDiffusionBackend |
||||
} |
||||
log.Debug().Msgf("Loading model: %+v", m) |
||||
|
||||
config, input, err := readConfig(m, input, cm, o.Loader, o.Debug, 0, 0, false) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
log.Debug().Msgf("Parameter Config: %+v", config) |
||||
|
||||
// XXX: Only stablediffusion is supported for now
|
||||
if config.Backend == "" { |
||||
config.Backend = model.StableDiffusionBackend |
||||
} |
||||
|
||||
sizeParts := strings.Split(input.Size, "x") |
||||
if len(sizeParts) != 2 { |
||||
return fmt.Errorf("Invalid value for 'size'") |
||||
} |
||||
width, err := strconv.Atoi(sizeParts[0]) |
||||
if err != nil { |
||||
return fmt.Errorf("Invalid value for 'size'") |
||||
} |
||||
height, err := strconv.Atoi(sizeParts[1]) |
||||
if err != nil { |
||||
return fmt.Errorf("Invalid value for 'size'") |
||||
} |
||||
|
||||
b64JSON := false |
||||
if input.ResponseFormat == "b64_json" { |
||||
b64JSON = true |
||||
} |
||||
|
||||
var result []Item |
||||
for _, i := range config.PromptStrings { |
||||
n := input.N |
||||
if input.N == 0 { |
||||
n = 1 |
||||
} |
||||
for j := 0; j < n; j++ { |
||||
prompts := strings.Split(i, "|") |
||||
positive_prompt := prompts[0] |
||||
negative_prompt := "" |
||||
if len(prompts) > 1 { |
||||
negative_prompt = prompts[1] |
||||
} |
||||
|
||||
mode := 0 |
||||
step := 15 |
||||
|
||||
if input.Mode != 0 { |
||||
mode = input.Mode |
||||
} |
||||
|
||||
if input.Step != 0 { |
||||
step = input.Step |
||||
} |
||||
|
||||
tempDir := "" |
||||
if !b64JSON { |
||||
tempDir = o.ImageDir |
||||
} |
||||
// Create a temporary file
|
||||
outputFile, err := ioutil.TempFile(tempDir, "b64") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
outputFile.Close() |
||||
output := outputFile.Name() + ".png" |
||||
// Rename the temporary file
|
||||
err = os.Rename(outputFile.Name(), output) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
baseURL := c.BaseURL() |
||||
|
||||
fn, err := backend.ImageGeneration(height, width, mode, step, input.Seed, positive_prompt, negative_prompt, output, o.Loader, *config, o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := fn(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
item := &Item{} |
||||
|
||||
if b64JSON { |
||||
defer os.RemoveAll(output) |
||||
data, err := os.ReadFile(output) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
item.B64JSON = base64.StdEncoding.EncodeToString(data) |
||||
} else { |
||||
base := filepath.Base(output) |
||||
item.URL = baseURL + "/generated-images/" + base |
||||
} |
||||
|
||||
result = append(result, *item) |
||||
} |
||||
} |
||||
|
||||
resp := &OpenAIResponse{ |
||||
Data: result, |
||||
} |
||||
|
||||
jsonResult, _ := json.Marshal(resp) |
||||
log.Debug().Msgf("Response: %s", jsonResult) |
||||
|
||||
// Return the prediction in the response body
|
||||
return c.JSON(resp) |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
) |
||||
|
||||
func ComputeChoices(predInput string, n int, config *config.Config, o *options.Option, loader *model.ModelLoader, cb func(string, *[]Choice), tokenCallback func(string) bool) ([]Choice, error) { |
||||
result := []Choice{} |
||||
|
||||
if n == 0 { |
||||
n = 1 |
||||
} |
||||
|
||||
// get the model function to call for the result
|
||||
predFunc, err := backend.ModelInference(predInput, loader, *config, o, tokenCallback) |
||||
if err != nil { |
||||
return result, err |
||||
} |
||||
|
||||
for i := 0; i < n; i++ { |
||||
prediction, err := predFunc() |
||||
if err != nil { |
||||
return result, err |
||||
} |
||||
|
||||
prediction = backend.Finetune(*config, predInput, prediction) |
||||
cb(prediction, &result) |
||||
|
||||
//result = append(result, Choice{Text: prediction})
|
||||
|
||||
} |
||||
return result, err |
||||
} |
@ -0,0 +1,37 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/gofiber/fiber/v2" |
||||
) |
||||
|
||||
func ListModelsEndpoint(loader *model.ModelLoader, cm *config.ConfigLoader) func(ctx *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
models, err := loader.ListModels() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var mm map[string]interface{} = map[string]interface{}{} |
||||
|
||||
dataModels := []OpenAIModel{} |
||||
for _, m := range models { |
||||
mm[m] = nil |
||||
dataModels = append(dataModels, OpenAIModel{ID: m, Object: "model"}) |
||||
} |
||||
|
||||
for _, k := range cm.ListConfigs() { |
||||
if _, exists := mm[k]; !exists { |
||||
dataModels = append(dataModels, OpenAIModel{ID: k, Object: "model"}) |
||||
} |
||||
} |
||||
|
||||
return c.JSON(struct { |
||||
Object string `json:"object"` |
||||
Data []OpenAIModel `json:"data"` |
||||
}{ |
||||
Object: "list", |
||||
Data: dataModels, |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,234 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
func readInput(c *fiber.Ctx, loader *model.ModelLoader, randomModel bool) (string, *OpenAIRequest, error) { |
||||
input := new(OpenAIRequest) |
||||
// Get input data from the request body
|
||||
if err := c.BodyParser(input); err != nil { |
||||
return "", nil, err |
||||
} |
||||
|
||||
modelFile := input.Model |
||||
|
||||
if c.Params("model") != "" { |
||||
modelFile = c.Params("model") |
||||
} |
||||
|
||||
received, _ := json.Marshal(input) |
||||
|
||||
log.Debug().Msgf("Request received: %s", string(received)) |
||||
|
||||
// Set model from bearer token, if available
|
||||
bearer := strings.TrimLeft(c.Get("authorization"), "Bearer ") |
||||
bearerExists := bearer != "" && loader.ExistsInModelPath(bearer) |
||||
|
||||
// If no model was specified, take the first available
|
||||
if modelFile == "" && !bearerExists && randomModel { |
||||
models, _ := loader.ListModels() |
||||
if len(models) > 0 { |
||||
modelFile = models[0] |
||||
log.Debug().Msgf("No model specified, using: %s", modelFile) |
||||
} else { |
||||
log.Debug().Msgf("No model specified, returning error") |
||||
return "", nil, fmt.Errorf("no model specified") |
||||
} |
||||
} |
||||
|
||||
// If a model is found in bearer token takes precedence
|
||||
if bearerExists { |
||||
log.Debug().Msgf("Using model from bearer token: %s", bearer) |
||||
modelFile = bearer |
||||
} |
||||
return modelFile, input, nil |
||||
} |
||||
|
||||
func updateConfig(config *config.Config, input *OpenAIRequest) { |
||||
if input.Echo { |
||||
config.Echo = input.Echo |
||||
} |
||||
if input.TopK != 0 { |
||||
config.TopK = input.TopK |
||||
} |
||||
if input.TopP != 0 { |
||||
config.TopP = input.TopP |
||||
} |
||||
|
||||
if input.Grammar != "" { |
||||
config.Grammar = input.Grammar |
||||
} |
||||
|
||||
if input.Temperature != 0 { |
||||
config.Temperature = input.Temperature |
||||
} |
||||
|
||||
if input.Maxtokens != 0 { |
||||
config.Maxtokens = input.Maxtokens |
||||
} |
||||
|
||||
switch stop := input.Stop.(type) { |
||||
case string: |
||||
if stop != "" { |
||||
config.StopWords = append(config.StopWords, stop) |
||||
} |
||||
case []interface{}: |
||||
for _, pp := range stop { |
||||
if s, ok := pp.(string); ok { |
||||
config.StopWords = append(config.StopWords, s) |
||||
} |
||||
} |
||||
} |
||||
|
||||
if input.RepeatPenalty != 0 { |
||||
config.RepeatPenalty = input.RepeatPenalty |
||||
} |
||||
|
||||
if input.Keep != 0 { |
||||
config.Keep = input.Keep |
||||
} |
||||
|
||||
if input.Batch != 0 { |
||||
config.Batch = input.Batch |
||||
} |
||||
|
||||
if input.F16 { |
||||
config.F16 = input.F16 |
||||
} |
||||
|
||||
if input.IgnoreEOS { |
||||
config.IgnoreEOS = input.IgnoreEOS |
||||
} |
||||
|
||||
if input.Seed != 0 { |
||||
config.Seed = input.Seed |
||||
} |
||||
|
||||
if input.Mirostat != 0 { |
||||
config.Mirostat = input.Mirostat |
||||
} |
||||
|
||||
if input.MirostatETA != 0 { |
||||
config.MirostatETA = input.MirostatETA |
||||
} |
||||
|
||||
if input.MirostatTAU != 0 { |
||||
config.MirostatTAU = input.MirostatTAU |
||||
} |
||||
|
||||
if input.TypicalP != 0 { |
||||
config.TypicalP = input.TypicalP |
||||
} |
||||
|
||||
switch inputs := input.Input.(type) { |
||||
case string: |
||||
if inputs != "" { |
||||
config.InputStrings = append(config.InputStrings, inputs) |
||||
} |
||||
case []interface{}: |
||||
for _, pp := range inputs { |
||||
switch i := pp.(type) { |
||||
case string: |
||||
config.InputStrings = append(config.InputStrings, i) |
||||
case []interface{}: |
||||
tokens := []int{} |
||||
for _, ii := range i { |
||||
tokens = append(tokens, int(ii.(float64))) |
||||
} |
||||
config.InputToken = append(config.InputToken, tokens) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Can be either a string or an object
|
||||
switch fnc := input.FunctionCall.(type) { |
||||
case string: |
||||
if fnc != "" { |
||||
config.SetFunctionCallString(fnc) |
||||
} |
||||
case map[string]interface{}: |
||||
var name string |
||||
n, exists := fnc["name"] |
||||
if exists { |
||||
nn, e := n.(string) |
||||
if !e { |
||||
name = nn |
||||
} |
||||
} |
||||
config.SetFunctionCallNameString(name) |
||||
} |
||||
|
||||
switch p := input.Prompt.(type) { |
||||
case string: |
||||
config.PromptStrings = append(config.PromptStrings, p) |
||||
case []interface{}: |
||||
for _, pp := range p { |
||||
if s, ok := pp.(string); ok { |
||||
config.PromptStrings = append(config.PromptStrings, s) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func readConfig(modelFile string, input *OpenAIRequest, cm *config.ConfigLoader, loader *model.ModelLoader, debug bool, threads, ctx int, f16 bool) (*config.Config, *OpenAIRequest, error) { |
||||
// Load a config file if present after the model name
|
||||
modelConfig := filepath.Join(loader.ModelPath, modelFile+".yaml") |
||||
|
||||
var cfg *config.Config |
||||
|
||||
defaults := func() { |
||||
cfg = config.DefaultConfig(modelFile) |
||||
cfg.ContextSize = ctx |
||||
cfg.Threads = threads |
||||
cfg.F16 = f16 |
||||
cfg.Debug = debug |
||||
} |
||||
|
||||
cfgExisting, exists := cm.GetConfig(modelFile) |
||||
if !exists { |
||||
if _, err := os.Stat(modelConfig); err == nil { |
||||
if err := cm.LoadConfig(modelConfig); err != nil { |
||||
return nil, nil, fmt.Errorf("failed loading model config (%s) %s", modelConfig, err.Error()) |
||||
} |
||||
cfgExisting, exists = cm.GetConfig(modelFile) |
||||
if exists { |
||||
cfg = &cfgExisting |
||||
} else { |
||||
defaults() |
||||
} |
||||
} else { |
||||
defaults() |
||||
} |
||||
} else { |
||||
cfg = &cfgExisting |
||||
} |
||||
|
||||
// Set the parameters for the language model prediction
|
||||
updateConfig(cfg, input) |
||||
|
||||
// Don't allow 0 as setting
|
||||
if cfg.Threads == 0 { |
||||
if threads != 0 { |
||||
cfg.Threads = threads |
||||
} else { |
||||
cfg.Threads = 4 |
||||
} |
||||
} |
||||
|
||||
// Enforce debug flag if passed from CLI
|
||||
if debug { |
||||
cfg.Debug = true |
||||
} |
||||
|
||||
return cfg, input, nil |
||||
} |
@ -0,0 +1,71 @@ |
||||
package openai |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
|
||||
"github.com/go-skynet/LocalAI/api/backend" |
||||
config "github.com/go-skynet/LocalAI/api/config" |
||||
"github.com/go-skynet/LocalAI/api/options" |
||||
|
||||
"github.com/gofiber/fiber/v2" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
// https://platform.openai.com/docs/api-reference/audio/create
|
||||
func TranscriptEndpoint(cm *config.ConfigLoader, o *options.Option) func(c *fiber.Ctx) error { |
||||
return func(c *fiber.Ctx) error { |
||||
m, input, err := readInput(c, o.Loader, false) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
|
||||
config, input, err := readConfig(m, input, cm, o.Loader, o.Debug, o.Threads, o.ContextSize, o.F16) |
||||
if err != nil { |
||||
return fmt.Errorf("failed reading parameters from request:%w", err) |
||||
} |
||||
// retrieve the file data from the request
|
||||
file, err := c.FormFile("file") |
||||
if err != nil { |
||||
return err |
||||
} |
||||
f, err := file.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer f.Close() |
||||
|
||||
dir, err := os.MkdirTemp("", "whisper") |
||||
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer os.RemoveAll(dir) |
||||
|
||||
dst := filepath.Join(dir, path.Base(file.Filename)) |
||||
dstFile, err := os.Create(dst) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err := io.Copy(dstFile, f); err != nil { |
||||
log.Debug().Msgf("Audio file copying error %+v - %+v - err %+v", file.Filename, dst, err) |
||||
return err |
||||
} |
||||
|
||||
log.Debug().Msgf("Audio file copied to: %+v", dst) |
||||
|
||||
tr, err := backend.ModelTranscription(dst, input.Language, o.Loader, *config, o) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
log.Debug().Msgf("Trascribed: %+v", tr) |
||||
// TODO: handle different outputs here
|
||||
return c.Status(http.StatusOK).JSON(tr) |
||||
} |
||||
} |
@ -0,0 +1,186 @@ |
||||
package options |
||||
|
||||
import ( |
||||
"context" |
||||
"embed" |
||||
"encoding/json" |
||||
|
||||
"github.com/go-skynet/LocalAI/pkg/gallery" |
||||
model "github.com/go-skynet/LocalAI/pkg/model" |
||||
"github.com/rs/zerolog/log" |
||||
) |
||||
|
||||
type Option struct { |
||||
Context context.Context |
||||
ConfigFile string |
||||
Loader *model.ModelLoader |
||||
UploadLimitMB, Threads, ContextSize int |
||||
F16 bool |
||||
Debug, DisableMessage bool |
||||
ImageDir string |
||||
AudioDir string |
||||
CORS bool |
||||
PreloadJSONModels string |
||||
PreloadModelsFromPath string |
||||
CORSAllowOrigins string |
||||
|
||||
Galleries []gallery.Gallery |
||||
|
||||
BackendAssets embed.FS |
||||
AssetsDestination string |
||||
|
||||
ExternalGRPCBackends map[string]string |
||||
|
||||
AutoloadGalleries bool |
||||
} |
||||
|
||||
type AppOption func(*Option) |
||||
|
||||
func NewOptions(o ...AppOption) *Option { |
||||
opt := &Option{ |
||||
Context: context.Background(), |
||||
UploadLimitMB: 15, |
||||
Threads: 1, |
||||
ContextSize: 512, |
||||
Debug: true, |
||||
DisableMessage: true, |
||||
} |
||||
for _, oo := range o { |
||||
oo(opt) |
||||
} |
||||
return opt |
||||
} |
||||
|
||||
func WithCors(b bool) AppOption { |
||||
return func(o *Option) { |
||||
o.CORS = b |
||||
} |
||||
} |
||||
|
||||
var EnableGalleriesAutoload = func(o *Option) { |
||||
o.AutoloadGalleries = true |
||||
} |
||||
|
||||
func WithExternalBackend(name string, uri string) AppOption { |
||||
return func(o *Option) { |
||||
if o.ExternalGRPCBackends == nil { |
||||
o.ExternalGRPCBackends = make(map[string]string) |
||||
} |
||||
o.ExternalGRPCBackends[name] = uri |
||||
} |
||||
} |
||||
|
||||
func WithCorsAllowOrigins(b string) AppOption { |
||||
return func(o *Option) { |
||||
o.CORSAllowOrigins = b |
||||
} |
||||
} |
||||
|
||||
func WithBackendAssetsOutput(out string) AppOption { |
||||
return func(o *Option) { |
||||
o.AssetsDestination = out |
||||
} |
||||
} |
||||
|
||||
func WithBackendAssets(f embed.FS) AppOption { |
||||
return func(o *Option) { |
||||
o.BackendAssets = f |
||||
} |
||||
} |
||||
|
||||
func WithStringGalleries(galls string) AppOption { |
||||
return func(o *Option) { |
||||
if galls == "" { |
||||
log.Debug().Msgf("no galleries to load") |
||||
return |
||||
} |
||||
var galleries []gallery.Gallery |
||||
if err := json.Unmarshal([]byte(galls), &galleries); err != nil { |
||||
log.Error().Msgf("failed loading galleries: %s", err.Error()) |
||||
} |
||||
o.Galleries = append(o.Galleries, galleries...) |
||||
} |
||||
} |
||||
|
||||
func WithGalleries(galleries []gallery.Gallery) AppOption { |
||||
return func(o *Option) { |
||||
o.Galleries = append(o.Galleries, galleries...) |
||||
} |
||||
} |
||||
|
||||
func WithContext(ctx context.Context) AppOption { |
||||
return func(o *Option) { |
||||
o.Context = ctx |
||||
} |
||||
} |
||||
|
||||
func WithYAMLConfigPreload(configFile string) AppOption { |
||||
return func(o *Option) { |
||||
o.PreloadModelsFromPath = configFile |
||||
} |
||||
} |
||||
|
||||
func WithJSONStringPreload(configFile string) AppOption { |
||||
return func(o *Option) { |
||||
o.PreloadJSONModels = configFile |
||||
} |
||||
} |
||||
func WithConfigFile(configFile string) AppOption { |
||||
return func(o *Option) { |
||||
o.ConfigFile = configFile |
||||
} |
||||
} |
||||
|
||||
func WithModelLoader(loader *model.ModelLoader) AppOption { |
||||
return func(o *Option) { |
||||
o.Loader = loader |
||||
} |
||||
} |
||||
|
||||
func WithUploadLimitMB(limit int) AppOption { |
||||
return func(o *Option) { |
||||
o.UploadLimitMB = limit |
||||
} |
||||
} |
||||
|
||||
func WithThreads(threads int) AppOption { |
||||
return func(o *Option) { |
||||
o.Threads = threads |
||||
} |
||||
} |
||||
|
||||
func WithContextSize(ctxSize int) AppOption { |
||||
return func(o *Option) { |
||||
o.ContextSize = ctxSize |
||||
} |
||||
} |
||||
|
||||
func WithF16(f16 bool) AppOption { |
||||
return func(o *Option) { |
||||
o.F16 = f16 |
||||
} |
||||
} |
||||
|
||||
func WithDebug(debug bool) AppOption { |
||||
return func(o *Option) { |
||||
o.Debug = debug |
||||
} |
||||
} |
||||
|
||||
func WithDisableMessage(disableMessage bool) AppOption { |
||||
return func(o *Option) { |
||||
o.DisableMessage = disableMessage |
||||
} |
||||
} |
||||
|
||||
func WithAudioDir(audioDir string) AppOption { |
||||
return func(o *Option) { |
||||
o.AudioDir = audioDir |
||||
} |
||||
} |
||||
|
||||
func WithImageDir(imageDir string) AppOption { |
||||
return func(o *Option) { |
||||
o.ImageDir = imageDir |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
package main |
||||
|
||||
import "embed" |
||||
|
||||
//go:embed backend-assets/*
|
||||
var backendAssets embed.FS |
@ -1,6 +0,0 @@ |
||||
apiVersion: v2 |
||||
appVersion: 0.1.0 |
||||
description: A Helm chart for LocalAI |
||||
name: local-ai |
||||
type: application |
||||
version: 1.0.0 |
@ -1,44 +0,0 @@ |
||||
{{/* |
||||
Expand the name of the chart. |
||||
*/}} |
||||
{{- define "local-ai.name" -}} |
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Create a default fully qualified app name. |
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). |
||||
If release name contains chart name it will be used as a full name. |
||||
*/}} |
||||
{{- define "local-ai.fullname" -}} |
||||
{{- if .Values.fullnameOverride }} |
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} |
||||
{{- else }} |
||||
{{- $name := default .Chart.Name .Values.nameOverride }} |
||||
{{- if contains $name .Release.Name }} |
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }} |
||||
{{- else }} |
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
{{- end }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Create chart name and version as used by the chart label. |
||||
*/}} |
||||
{{- define "local-ai.chart" -}} |
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Common labels |
||||
*/}} |
||||
{{- define "local-ai.labels" -}} |
||||
helm.sh/chart: {{ include "local-ai.chart" . }} |
||||
app.kubernetes.io/name: {{ include "local-ai.name" . }} |
||||
app.kubernetes.io/instance: "{{ .Release.Name }}" |
||||
app.kubernetes.io/managed-by: {{ .Release.Service }} |
||||
{{- if .Chart.AppVersion }} |
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} |
||||
{{- end }} |
||||
{{- end }} |
@ -1,39 +0,0 @@ |
||||
{{- if .Values.dataVolume.enabled }} |
||||
apiVersion: cdi.kubevirt.io/v1beta1 |
||||
kind: DataVolume |
||||
metadata: |
||||
name: {{ template "local-ai.fullname" . }} |
||||
namespace: {{ .Release.Namespace | quote }} |
||||
labels: |
||||
{{- include "local-ai.labels" . | nindent 4 }} |
||||
spec: |
||||
contentType: archive |
||||
source: |
||||
{{ .Values.dataVolume.source.type }}: |
||||
url: {{ .Values.dataVolume.source.url }} |
||||
secretRef: {{ template "local-ai.fullname" . }} |
||||
{{- if and (eq .Values.dataVolume.source.type "http") .Values.dataVolume.source.secretExtraHeaders }} |
||||
secretExtraHeaders: {{ .Values.dataVolume.source.secretExtraHeaders }} |
||||
{{- end }} |
||||
{{- if .Values.dataVolume.source.caCertConfigMap }} |
||||
caCertConfigMap: {{ .Values.dataVolume.source.caCertConfigMap }} |
||||
{{- end }} |
||||
pvc: |
||||
accessModes: {{ .Values.dataVolume.pvc.accessModes }} |
||||
resources: |
||||
requests: |
||||
storage: {{ .Values.dataVolume.pvc.size }} |
||||
--- |
||||
{{- if .Values.dataVolume.secret.enabled }} |
||||
apiVersion: v1 |
||||
kind: Secret |
||||
metadata: |
||||
name: {{ template "local-ai.fullname" . }} |
||||
namespace: {{ .Release.Namespace | quote }} |
||||
labels: |
||||
{{- include "local-ai.labels" . | nindent 4 }} |
||||
data: |
||||
accessKeyId: {{ .Values.dataVolume.secret.username }} |
||||
secretKey: {{ .Values.dataVolume.secret.password }} |
||||
{{- end }} |
||||
{{- end }} |
@ -1,39 +0,0 @@ |
||||
apiVersion: apps/v1 |
||||
kind: Deployment |
||||
metadata: |
||||
name: {{ template "local-ai.fullname" . }} |
||||
namespace: {{ .Release.Namespace | quote }} |
||||
labels: |
||||
{{- include "local-ai.labels" . | nindent 4 }} |
||||
spec: |
||||
selector: |
||||
matchLabels: |
||||
app.kubernetes.io/name: {{ include "local-ai.name" . }} |
||||
app.kubernetes.io/instance: {{ .Release.Name }} |
||||
replicas: 1 |
||||
template: |
||||
metadata: |
||||
name: {{ template "local-ai.fullname" . }} |
||||
labels: |
||||
app.kubernetes.io/name: {{ include "local-ai.name" . }} |
||||
app.kubernetes.io/instance: {{ .Release.Name }} |
||||
spec: |
||||
containers: |
||||
- name: {{ template "local-ai.fullname" . }} |
||||
image: {{ .Values.deployment.image }} |
||||
env: |
||||
- name: THREADS |
||||
value: {{ .Values.deployment.env.threads | quote }} |
||||
- name: CONTEXT_SIZE |
||||
value: {{ .Values.deployment.env.contextSize | quote }} |
||||
- name: MODELS_PATH |
||||
value: {{ .Values.deployment.env.modelsPath }} |
||||
{{- if .Values.deployment.volume.enabled }} |
||||
volumeMounts: |
||||
- mountPath: {{ .Values.deployment.env.modelsPath }} |
||||
name: models |
||||
volumes: |
||||
- name: models |
||||
persistentVolumeClaim: |
||||
claimName: {{ template "local-ai.fullname" . }} |
||||
{{- end }} |
@ -1,19 +0,0 @@ |
||||
apiVersion: v1 |
||||
kind: Service |
||||
metadata: |
||||
name: {{ template "local-ai.fullname" . }} |
||||
namespace: {{ .Release.Namespace | quote }} |
||||
labels: |
||||
{{- include "local-ai.labels" . | nindent 4 }} |
||||
{{- if .Values.service.annotations }} |
||||
annotations: |
||||
{{ toYaml .Values.service.annotations | indent 4 }} |
||||
{{- end }} |
||||
spec: |
||||
selector: |
||||
app.kubernetes.io/name: {{ include "local-ai.name" . }} |
||||
type: "{{ .Values.service.type }}" |
||||
ports: |
||||
- protocol: TCP |
||||
port: 8080 |
||||
targetPort: 8080 |
@ -1,38 +0,0 @@ |
||||
deployment: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
env: |
||||
threads: 14 |
||||
contextSize: 512 |
||||
modelsPath: "/models" |
||||
volume: |
||||
enabled: false |
||||
|
||||
service: |
||||
type: ClusterIP |
||||
annotations: {} |
||||
# If using an AWS load balancer, you'll need to override the default 60s load balancer idle timeout |
||||
# service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "1200" |
||||
|
||||
# Optionally create a PVC containing a model binary, sourced from an arbitrary HTTP server or S3 bucket |
||||
# (requires https://github.com/kubevirt/containerized-data-importer) |
||||
dataVolume: |
||||
enabled: false |
||||
source: |
||||
type: "http" # Source type. One of: [ http | s3 ] |
||||
url: "http://<model_server>/<model_archive>" # e.g. koala-7B-4bit-128g.GGML.tar |
||||
|
||||
# CertConfigMap is an optional ConfigMap reference, containing a Certificate Authority (CA) public key |
||||
# and a base64 encoded pem certificate |
||||
caCertConfigMap: "" |
||||
|
||||
# SecretExtraHeaders is an optional list of Secret references, each containing an extra HTTP header |
||||
# that may include sensitive information. Only applicable for the http source type. |
||||
secretExtraHeaders: [] |
||||
pvc: |
||||
accessModes: |
||||
- ReadWriteOnce |
||||
size: 5Gi |
||||
secret: |
||||
enabled: false |
||||
username: "" # base64 encoded |
||||
password: "" # base64 encoded |
@ -0,0 +1,22 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
bert "github.com/go-skynet/LocalAI/pkg/grpc/llm/bert" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &bert.Embeddings{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
bloomz "github.com/go-skynet/LocalAI/pkg/grpc/llm/bloomz" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &bloomz.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.Dolly{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.Falcon{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
package main |
||||
|
||||
// GRPC Falcon server
|
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
falcon "github.com/go-skynet/LocalAI/pkg/grpc/llm/falcon" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &falcon.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.GPT2{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
gpt4all "github.com/go-skynet/LocalAI/pkg/grpc/llm/gpt4all" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &gpt4all.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.GPTJ{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.GPTNeoX{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
langchain "github.com/go-skynet/LocalAI/pkg/grpc/llm/langchain" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &langchain.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
package main |
||||
|
||||
// GRPC Falcon server
|
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
llama "github.com/go-skynet/LocalAI/pkg/grpc/llm/llama-grammar" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &llama.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
package main |
||||
|
||||
// GRPC Falcon server
|
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
llama "github.com/go-skynet/LocalAI/pkg/grpc/llm/llama" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &llama.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.MPT{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
tts "github.com/go-skynet/LocalAI/pkg/grpc/tts" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &tts.Piper{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.Replit{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
rwkv "github.com/go-skynet/LocalAI/pkg/grpc/llm/rwkv" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &rwkv.LLM{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
image "github.com/go-skynet/LocalAI/pkg/grpc/image" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &image.StableDiffusion{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transformers "github.com/go-skynet/LocalAI/pkg/grpc/llm/transformers" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transformers.Starcoder{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package main |
||||
|
||||
// Note: this is started internally by LocalAI and a server is allocated for each model
|
||||
|
||||
import ( |
||||
"flag" |
||||
|
||||
transcribe "github.com/go-skynet/LocalAI/pkg/grpc/transcribe" |
||||
|
||||
grpc "github.com/go-skynet/LocalAI/pkg/grpc" |
||||
) |
||||
|
||||
var ( |
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to") |
||||
) |
||||
|
||||
func main() { |
||||
flag.Parse() |
||||
|
||||
if err := grpc.StartServer(*addr, &transcribe.Whisper{}); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
cd /build |
||||
|
||||
if [ "$REBUILD" != "false" ]; then |
||||
rm -rf ./local-ai |
||||
ESPEAK_DATA=/build/lib/Linux-$(uname -m)/piper_phonemize/lib/espeak-ng-data make build -j${BUILD_PARALLELISM:-1} |
||||
else |
||||
echo "@@@@@" |
||||
echo "Skipping rebuild" |
||||
echo "@@@@@" |
||||
echo "If you are experiencing issues with the pre-compiled builds, try setting REBUILD=true" |
||||
echo "If you are still experiencing issues with the build, try setting CMAKE_ARGS and disable the instructions set as needed:" |
||||
echo 'CMAKE_ARGS="-DLLAMA_F16C=OFF -DLLAMA_AVX512=OFF -DLLAMA_AVX2=OFF -DLLAMA_FMA=OFF"' |
||||
echo "see the documentation at: https://localai.io/basics/build/index.html" |
||||
echo "Note: See also https://github.com/go-skynet/LocalAI/issues/288" |
||||
echo "@@@@@" |
||||
fi |
||||
|
||||
./local-ai "$@" |
@ -0,0 +1,153 @@ |
||||
# Examples |
||||
|
||||
Here is a list of projects that can easily be integrated with the LocalAI backend. |
||||
|
||||
### Projects |
||||
|
||||
### AutoGPT |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
This example shows how to use AutoGPT with LocalAI. |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/autoGPT/) |
||||
|
||||
### Chatbot-UI |
||||
|
||||
_by [@mkellerman](https://github.com/mkellerman)_ |
||||
|
||||
![Screenshot from 2023-04-26 23-59-55](https://user-images.githubusercontent.com/2420543/234715439-98d12e03-d3ce-4f94-ab54-2b256808e05e.png) |
||||
|
||||
This integration shows how to use LocalAI with [mckaywrigley/chatbot-ui](https://github.com/mckaywrigley/chatbot-ui). |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/chatbot-ui/) |
||||
|
||||
There is also a separate example to show how to manually setup a model: [example](https://github.com/go-skynet/LocalAI/tree/master/examples/chatbot-ui-manual/) |
||||
|
||||
### K8sGPT |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
This example show how to use LocalAI inside Kubernetes with [k8sgpt](https://k8sgpt.ai). |
||||
|
||||
![Screenshot from 2023-06-19 23-58-47](https://github.com/go-skynet/go-ggml-transformers.cpp/assets/2420543/cab87409-ee68-44ae-8d53-41627fb49509) |
||||
|
||||
### Flowise |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
This example shows how to use [FlowiseAI/Flowise](https://github.com/FlowiseAI/Flowise) with LocalAI. |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/flowise/) |
||||
|
||||
### Discord bot |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
Run a discord bot which lets you talk directly with a model |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/discord-bot/), or for a live demo you can talk with our bot in #random-bot in our discord server. |
||||
|
||||
### Langchain |
||||
|
||||
_by [@dave-gray101](https://github.com/dave-gray101)_ |
||||
|
||||
A ready to use example to show e2e how to integrate LocalAI with langchain |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/langchain/) |
||||
|
||||
### Langchain Python |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
A ready to use example to show e2e how to integrate LocalAI with langchain |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/langchain-python/) |
||||
|
||||
### LocalAI functions |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
A ready to use example to show how to use OpenAI functions with LocalAI |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/functions/) |
||||
|
||||
### LocalAI WebUI |
||||
|
||||
_by [@dhruvgera](https://github.com/dhruvgera)_ |
||||
|
||||
![image](https://user-images.githubusercontent.com/42107491/235344183-44b5967d-ba22-4331-804c-8da7004a5d35.png) |
||||
|
||||
A light, community-maintained web interface for LocalAI |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/localai-webui/) |
||||
|
||||
### How to run rwkv models |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
A full example on how to run RWKV models with LocalAI |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/rwkv/) |
||||
|
||||
### PrivateGPT |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
A full example on how to run PrivateGPT with LocalAI |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/privateGPT/) |
||||
|
||||
### Slack bot |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
Run a slack bot which lets you talk directly with a model |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/slack-bot/) |
||||
|
||||
### Slack bot (Question answering) |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
Run a slack bot, ideally for teams, which lets you ask questions on a documentation website, or a github repository. |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/slack-qa-bot/) |
||||
|
||||
### Question answering on documents with llama-index |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
Shows how to integrate with [Llama-Index](https://gpt-index.readthedocs.io/en/stable/getting_started/installation.html) to enable question answering on a set of documents. |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/query_data/) |
||||
|
||||
### Question answering on documents with langchain and chroma |
||||
|
||||
_by [@mudler](https://github.com/mudler)_ |
||||
|
||||
Shows how to integrate with `Langchain` and `Chroma` to enable question answering on a set of documents. |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/langchain-chroma/) |
||||
|
||||
### Telegram bot |
||||
|
||||
_by [@mudler](https://github.com/mudler) |
||||
|
||||
![Screenshot from 2023-06-09 00-36-26](https://github.com/go-skynet/LocalAI/assets/2420543/e98b4305-fa2d-41cf-9d2f-1bb2d75ca902) |
||||
|
||||
Use LocalAI to power a Telegram bot assistant, with Image generation and audio support! |
||||
|
||||
[Check it out here](https://github.com/go-skynet/LocalAI/tree/master/examples/telegram-bot/) |
||||
|
||||
### Template for Runpod.io |
||||
|
||||
_by [@fHachenberg](https://github.com/fHachenberg)_ |
||||
|
||||
Allows to run any LocalAI-compatible model as a backend on the servers of https://runpod.io |
||||
|
||||
[Check it out here](https://runpod.io/gsc?template=uv9mtqnrd0&ref=984wlcra) |
||||
|
||||
## Want to contribute? |
||||
|
||||
Create an issue, and put `Example: <description>` in the title! We will post your examples here. |
@ -0,0 +1,5 @@ |
||||
OPENAI_API_KEY=sk---anystringhere |
||||
OPENAI_API_BASE=http://api:8080/v1 |
||||
# Models to preload at start |
||||
# Here we configure gpt4all as gpt-3.5-turbo and bert as embeddings |
||||
PRELOAD_MODELS=[{"url": "github:go-skynet/model-gallery/gpt4all-j.yaml", "name": "gpt-3.5-turbo"}, { "url": "github:go-skynet/model-gallery/bert-embeddings.yaml", "name": "text-embedding-ada-002"}] |
@ -0,0 +1,32 @@ |
||||
# AutoGPT |
||||
|
||||
Example of integration with [AutoGPT](https://github.com/Significant-Gravitas/Auto-GPT). |
||||
|
||||
## Run |
||||
|
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/autoGPT |
||||
|
||||
docker-compose run --rm auto-gpt |
||||
``` |
||||
|
||||
Note: The example automatically downloads the `gpt4all` model as it is under a permissive license. The GPT4All model does not seem to be enough to run AutoGPT. WizardLM-7b-uncensored seems to perform better (with `f16: true`). |
||||
|
||||
See the `.env` configuration file to set a different model with the [model-gallery](https://github.com/go-skynet/model-gallery) by editing `PRELOAD_MODELS`. |
||||
|
||||
## Without docker |
||||
|
||||
Run AutoGPT with `OPENAI_API_BASE` pointing to the LocalAI endpoint. If you run it locally for instance: |
||||
|
||||
``` |
||||
OPENAI_API_BASE=http://localhost:8080 python ... |
||||
``` |
||||
|
||||
Note: you need a model named `gpt-3.5-turbo` and `text-embedding-ada-002`. You can preload those in LocalAI at start by setting in the env: |
||||
|
||||
``` |
||||
PRELOAD_MODELS=[{"url": "github:go-skynet/model-gallery/gpt4all-j.yaml", "name": "gpt-3.5-turbo"}, { "url": "github:go-skynet/model-gallery/bert-embeddings.yaml", "name": "text-embedding-ada-002"}] |
||||
``` |
@ -0,0 +1,42 @@ |
||||
version: "3.9" |
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
ports: |
||||
- 8080:8080 |
||||
env_file: |
||||
- .env |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
auto-gpt: |
||||
image: significantgravitas/auto-gpt |
||||
depends_on: |
||||
api: |
||||
condition: service_healthy |
||||
redis: |
||||
condition: service_started |
||||
env_file: |
||||
- .env |
||||
environment: |
||||
MEMORY_BACKEND: ${MEMORY_BACKEND:-redis} |
||||
REDIS_HOST: ${REDIS_HOST:-redis} |
||||
profiles: ["exclude-from-up"] |
||||
volumes: |
||||
- ./auto_gpt_workspace:/app/autogpt/auto_gpt_workspace |
||||
- ./data:/app/data |
||||
## allow auto-gpt to write logs to disk |
||||
- ./logs:/app/logs |
||||
## uncomment following lines if you want to make use of these files |
||||
## you must have them existing in the same folder as this docker-compose.yml |
||||
#- type: bind |
||||
# source: ./azure.yaml |
||||
# target: /app/azure.yaml |
||||
#- type: bind |
||||
# source: ./ai_settings.yaml |
||||
# target: /app/ai_settings.yaml |
||||
redis: |
||||
image: "redis/redis-stack-server:latest" |
@ -0,0 +1,48 @@ |
||||
# chatbot-ui |
||||
|
||||
Example of integration with [mckaywrigley/chatbot-ui](https://github.com/mckaywrigley/chatbot-ui). |
||||
|
||||
![Screenshot from 2023-04-26 23-59-55](https://user-images.githubusercontent.com/2420543/234715439-98d12e03-d3ce-4f94-ab54-2b256808e05e.png) |
||||
|
||||
## Setup |
||||
|
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/chatbot-ui |
||||
|
||||
# (optional) Checkout a specific LocalAI tag |
||||
# git checkout -b build <TAG> |
||||
|
||||
# Download gpt4all-j to models/ |
||||
wget https://gpt4all.io/models/ggml-gpt4all-j.bin -O models/ggml-gpt4all-j |
||||
|
||||
# start with docker-compose |
||||
docker-compose up -d --pull always |
||||
# or you can build the images with: |
||||
# docker-compose up -d --build |
||||
``` |
||||
|
||||
## Pointing chatbot-ui to a separately managed LocalAI service |
||||
|
||||
If you want to use the [chatbot-ui example](https://github.com/go-skynet/LocalAI/tree/master/examples/chatbot-ui) with an externally managed LocalAI service, you can alter the `docker-compose` file so that it looks like the below. You will notice the file is smaller, because we have removed the section that would normally start the LocalAI service. Take care to update the IP address (or FQDN) that the chatbot-ui service tries to access (marked `<<LOCALAI_IP>>` below): |
||||
``` |
||||
version: '3.6' |
||||
|
||||
services: |
||||
chatgpt: |
||||
image: ghcr.io/mckaywrigley/chatbot-ui:main |
||||
ports: |
||||
- 3000:3000 |
||||
environment: |
||||
- 'OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXX' |
||||
- 'OPENAI_API_HOST=http://<<LOCALAI_IP>>:8080' |
||||
``` |
||||
|
||||
Once you've edited the Dockerfile, you can start it with `docker compose up`, then browse to `http://localhost:3000`. |
||||
|
||||
## Accessing chatbot-ui |
||||
|
||||
Open http://localhost:3000 for the Web UI. |
||||
|
@ -0,0 +1,24 @@ |
||||
version: '3.6' |
||||
|
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
build: |
||||
context: ../../ |
||||
dockerfile: Dockerfile |
||||
ports: |
||||
- 8080:8080 |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
|
||||
chatgpt: |
||||
image: ghcr.io/mckaywrigley/chatbot-ui:main |
||||
ports: |
||||
- 3000:3000 |
||||
environment: |
||||
- 'OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXX' |
||||
- 'OPENAI_API_HOST=http://api:8080' |
@ -0,0 +1 @@ |
||||
{{.Input}} |
@ -0,0 +1,16 @@ |
||||
name: gpt-3.5-turbo |
||||
parameters: |
||||
model: ggml-gpt4all-j |
||||
top_k: 80 |
||||
temperature: 0.2 |
||||
top_p: 0.7 |
||||
context_size: 1024 |
||||
stopwords: |
||||
- "HUMAN:" |
||||
- "GPT:" |
||||
roles: |
||||
user: " " |
||||
system: " " |
||||
template: |
||||
completion: completion |
||||
chat: gpt4all |
@ -0,0 +1,4 @@ |
||||
The prompt below is a question to answer, a task to complete, or a conversation to respond to; decide which and write an appropriate response. |
||||
### Prompt: |
||||
{{.Input}} |
||||
### Response: |
@ -0,0 +1,44 @@ |
||||
# chatbot-ui |
||||
|
||||
Example of integration with [mckaywrigley/chatbot-ui](https://github.com/mckaywrigley/chatbot-ui). |
||||
|
||||
![Screenshot from 2023-04-26 23-59-55](https://user-images.githubusercontent.com/2420543/234715439-98d12e03-d3ce-4f94-ab54-2b256808e05e.png) |
||||
|
||||
## Run |
||||
|
||||
In this example LocalAI will download the gpt4all model and set it up as "gpt-3.5-turbo". See the `docker-compose.yaml` |
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/chatbot-ui |
||||
|
||||
# start with docker-compose |
||||
docker-compose up --pull always |
||||
|
||||
# or you can build the images with: |
||||
# docker-compose up -d --build |
||||
``` |
||||
|
||||
## Pointing chatbot-ui to a separately managed LocalAI service |
||||
|
||||
If you want to use the [chatbot-ui example](https://github.com/go-skynet/LocalAI/tree/master/examples/chatbot-ui) with an externally managed LocalAI service, you can alter the `docker-compose` file so that it looks like the below. You will notice the file is smaller, because we have removed the section that would normally start the LocalAI service. Take care to update the IP address (or FQDN) that the chatbot-ui service tries to access (marked `<<LOCALAI_IP>>` below): |
||||
``` |
||||
version: '3.6' |
||||
|
||||
services: |
||||
chatgpt: |
||||
image: ghcr.io/mckaywrigley/chatbot-ui:main |
||||
ports: |
||||
- 3000:3000 |
||||
environment: |
||||
- 'OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXX' |
||||
- 'OPENAI_API_HOST=http://<<LOCALAI_IP>>:8080' |
||||
``` |
||||
|
||||
Once you've edited the Dockerfile, you can start it with `docker compose up`, then browse to `http://localhost:3000`. |
||||
|
||||
## Accessing chatbot-ui |
||||
|
||||
Open http://localhost:3000 for the Web UI. |
||||
|
@ -0,0 +1,37 @@ |
||||
version: '3.6' |
||||
|
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
# As initially LocalAI will download the models defined in PRELOAD_MODELS |
||||
# you might need to tweak the healthcheck values here according to your network connection. |
||||
# Here we give a timespan of 20m to download all the required files. |
||||
healthcheck: |
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"] |
||||
interval: 1m |
||||
timeout: 20m |
||||
retries: 20 |
||||
build: |
||||
context: ../../ |
||||
dockerfile: Dockerfile |
||||
ports: |
||||
- 8080:8080 |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
# You can preload different models here as well. |
||||
# See: https://github.com/go-skynet/model-gallery |
||||
- 'PRELOAD_MODELS=[{"url": "github:go-skynet/model-gallery/gpt4all-j.yaml", "name": "gpt-3.5-turbo"}]' |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
chatgpt: |
||||
depends_on: |
||||
api: |
||||
condition: service_healthy |
||||
image: ghcr.io/mckaywrigley/chatbot-ui:main |
||||
ports: |
||||
- 3000:3000 |
||||
environment: |
||||
- 'OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXX' |
||||
- 'OPENAI_API_HOST=http://api:8080' |
@ -0,0 +1,6 @@ |
||||
OPENAI_API_KEY=x |
||||
DISCORD_BOT_TOKEN=x |
||||
DISCORD_CLIENT_ID=x |
||||
OPENAI_API_BASE=http://api:8080 |
||||
ALLOWED_SERVER_IDS=x |
||||
SERVER_TO_MODERATION_CHANNEL=1:1 |
@ -0,0 +1,76 @@ |
||||
# discord-bot |
||||
|
||||
![Screenshot from 2023-05-01 07-58-19](https://user-images.githubusercontent.com/2420543/235413924-0cb2e75b-f2d6-4119-8610-44386e44afb8.png) |
||||
|
||||
## Setup |
||||
|
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/discord-bot |
||||
|
||||
# (optional) Checkout a specific LocalAI tag |
||||
# git checkout -b build <TAG> |
||||
|
||||
# Download gpt4all-j to models/ |
||||
wget https://gpt4all.io/models/ggml-gpt4all-j.bin -O models/ggml-gpt4all-j |
||||
|
||||
# Set the discord bot options (see: https://github.com/go-skynet/gpt-discord-bot#setup) |
||||
cp -rfv .env.example .env |
||||
vim .env |
||||
|
||||
# start with docker-compose |
||||
docker-compose up -d --build |
||||
``` |
||||
|
||||
Note: see setup options here: https://github.com/go-skynet/gpt-discord-bot#setup |
||||
|
||||
Open up the URL in the console and give permission to the bot in your server. Start a thread with `/chat ..` |
||||
|
||||
## Kubernetes |
||||
|
||||
- install the local-ai chart first |
||||
- change OPENAI_API_BASE to point to the API address and apply the discord-bot manifest: |
||||
|
||||
```yaml |
||||
apiVersion: v1 |
||||
kind: Namespace |
||||
metadata: |
||||
name: discord-bot |
||||
--- |
||||
apiVersion: apps/v1 |
||||
kind: Deployment |
||||
metadata: |
||||
name: localai |
||||
namespace: discord-bot |
||||
labels: |
||||
app: localai |
||||
spec: |
||||
selector: |
||||
matchLabels: |
||||
app: localai |
||||
replicas: 1 |
||||
template: |
||||
metadata: |
||||
labels: |
||||
app: localai |
||||
name: localai |
||||
spec: |
||||
containers: |
||||
- name: localai-discord |
||||
env: |
||||
- name: OPENAI_API_KEY |
||||
value: "x" |
||||
- name: DISCORD_BOT_TOKEN |
||||
value: "" |
||||
- name: DISCORD_CLIENT_ID |
||||
value: "" |
||||
- name: OPENAI_API_BASE |
||||
value: "http://local-ai.default.svc.cluster.local:8080" |
||||
- name: ALLOWED_SERVER_IDS |
||||
value: "xx" |
||||
- name: SERVER_TO_MODERATION_CHANNEL |
||||
value: "1:1" |
||||
image: quay.io/go-skynet/gpt-discord-bot:main |
||||
``` |
@ -0,0 +1,21 @@ |
||||
version: '3.6' |
||||
|
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
build: |
||||
context: ../../ |
||||
dockerfile: Dockerfile |
||||
ports: |
||||
- 8080:8080 |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
|
||||
bot: |
||||
image: quay.io/go-skynet/gpt-discord-bot:main |
||||
env_file: |
||||
- .env |
@ -0,0 +1 @@ |
||||
../chatbot-ui/models/ |
@ -0,0 +1,30 @@ |
||||
# flowise |
||||
|
||||
Example of integration with [FlowiseAI/Flowise](https://github.com/FlowiseAI/Flowise). |
||||
|
||||
![Screenshot from 2023-05-30 18-01-03](https://github.com/go-skynet/LocalAI/assets/2420543/02458782-0549-4131-971c-95ee56ec1af8) |
||||
|
||||
You can check a demo video in the Flowise PR: https://github.com/FlowiseAI/Flowise/pull/123 |
||||
|
||||
## Run |
||||
|
||||
In this example LocalAI will download the gpt4all model and set it up as "gpt-3.5-turbo". See the `docker-compose.yaml` |
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/flowise |
||||
|
||||
# start with docker-compose |
||||
docker-compose up --pull always |
||||
|
||||
``` |
||||
|
||||
## Accessing flowise |
||||
|
||||
Open http://localhost:3000. |
||||
|
||||
## Using LocalAI |
||||
|
||||
Search for LocalAI in the integration, and use the `http://api:8080/` as URL. |
||||
|
@ -0,0 +1,37 @@ |
||||
version: '3.6' |
||||
|
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:latest |
||||
# As initially LocalAI will download the models defined in PRELOAD_MODELS |
||||
# you might need to tweak the healthcheck values here according to your network connection. |
||||
# Here we give a timespan of 20m to download all the required files. |
||||
healthcheck: |
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/readyz"] |
||||
interval: 1m |
||||
timeout: 20m |
||||
retries: 20 |
||||
build: |
||||
context: ../../ |
||||
dockerfile: Dockerfile |
||||
ports: |
||||
- 8080:8080 |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
# You can preload different models here as well. |
||||
# See: https://github.com/go-skynet/model-gallery |
||||
- 'PRELOAD_MODELS=[{"url": "github:go-skynet/model-gallery/gpt4all-j.yaml", "name": "gpt-3.5-turbo"}]' |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
flowise: |
||||
depends_on: |
||||
api: |
||||
condition: service_healthy |
||||
image: flowiseai/flowise |
||||
ports: |
||||
- 3000:3000 |
||||
volumes: |
||||
- ~/.flowise:/root/.flowise |
||||
command: /bin/sh -c "sleep 3; flowise start" |
@ -0,0 +1,9 @@ |
||||
OPENAI_API_KEY=sk---anystringhere |
||||
OPENAI_API_BASE=http://api:8080/v1 |
||||
# Models to preload at start |
||||
# Here we configure gpt4all as gpt-3.5-turbo and bert as embeddings |
||||
PRELOAD_MODELS=[{"url": "github:go-skynet/model-gallery/openllama-7b-open-instruct.yaml", "name": "gpt-3.5-turbo"}] |
||||
|
||||
## Change the default number of threads |
||||
#THREADS=14 |
||||
|
@ -0,0 +1,5 @@ |
||||
FROM python:3.10-bullseye |
||||
COPY . /app |
||||
WORKDIR /app |
||||
RUN pip install --no-cache-dir -r requirements.txt |
||||
ENTRYPOINT [ "python", "./functions-openai.py" ]; |
@ -0,0 +1,18 @@ |
||||
# LocalAI functions |
||||
|
||||
Example of using LocalAI functions, see the [OpenAI](https://openai.com/blog/function-calling-and-other-api-updates) blog post. |
||||
|
||||
## Run |
||||
|
||||
```bash |
||||
# Clone LocalAI |
||||
git clone https://github.com/go-skynet/LocalAI |
||||
|
||||
cd LocalAI/examples/functions |
||||
|
||||
docker-compose run --rm functions |
||||
``` |
||||
|
||||
Note: The example automatically downloads the `openllama` model as it is under a permissive license. |
||||
|
||||
See the `.env` configuration file to set a different model with the [model-gallery](https://github.com/go-skynet/model-gallery) by editing `PRELOAD_MODELS`. |
@ -0,0 +1,23 @@ |
||||
version: "3.9" |
||||
services: |
||||
api: |
||||
image: quay.io/go-skynet/local-ai:master |
||||
ports: |
||||
- 8080:8080 |
||||
env_file: |
||||
- .env |
||||
environment: |
||||
- DEBUG=true |
||||
- MODELS_PATH=/models |
||||
volumes: |
||||
- ./models:/models:cached |
||||
command: ["/usr/bin/local-ai" ] |
||||
functions: |
||||
build: |
||||
context: . |
||||
dockerfile: Dockerfile |
||||
depends_on: |
||||
api: |
||||
condition: service_healthy |
||||
env_file: |
||||
- .env |
@ -0,0 +1,76 @@ |
||||
import openai |
||||
import json |
||||
|
||||
# Example dummy function hard coded to return the same weather |
||||
# In production, this could be your backend API or an external API |
||||
def get_current_weather(location, unit="fahrenheit"): |
||||
"""Get the current weather in a given location""" |
||||
weather_info = { |
||||
"location": location, |
||||
"temperature": "72", |
||||
"unit": unit, |
||||
"forecast": ["sunny", "windy"], |
||||
} |
||||
return json.dumps(weather_info) |
||||
|
||||
|
||||
def run_conversation(): |
||||
# Step 1: send the conversation and available functions to GPT |
||||
messages = [{"role": "user", "content": "What's the weather like in Boston?"}] |
||||
functions = [ |
||||
{ |
||||
"name": "get_current_weather", |
||||
"description": "Get the current weather in a given location", |
||||
"parameters": { |
||||
"type": "object", |
||||
"properties": { |
||||
"location": { |
||||
"type": "string", |
||||
"description": "The city and state, e.g. San Francisco, CA", |
||||
}, |
||||
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, |
||||
}, |
||||
"required": ["location"], |
||||
}, |
||||
} |
||||
] |
||||
response = openai.ChatCompletion.create( |
||||
model="gpt-3.5-turbo", |
||||
messages=messages, |
||||
functions=functions, |
||||
function_call="auto", # auto is default, but we'll be explicit |
||||
) |
||||
response_message = response["choices"][0]["message"] |
||||
|
||||
# Step 2: check if GPT wanted to call a function |
||||
if response_message.get("function_call"): |
||||
# Step 3: call the function |
||||
# Note: the JSON response may not always be valid; be sure to handle errors |
||||
available_functions = { |
||||
"get_current_weather": get_current_weather, |
||||
} # only one function in this example, but you can have multiple |
||||
function_name = response_message["function_call"]["name"] |
||||
fuction_to_call = available_functions[function_name] |
||||
function_args = json.loads(response_message["function_call"]["arguments"]) |
||||
function_response = fuction_to_call( |
||||
location=function_args.get("location"), |
||||
unit=function_args.get("unit"), |
||||
) |
||||
|
||||
# Step 4: send the info on the function call and function response to GPT |
||||
messages.append(response_message) # extend conversation with assistant's reply |
||||
messages.append( |
||||
{ |
||||
"role": "function", |
||||
"name": function_name, |
||||
"content": function_response, |
||||
} |
||||
) # extend conversation with function response |
||||
second_response = openai.ChatCompletion.create( |
||||
model="gpt-3.5-turbo", |
||||
messages=messages, |
||||
) # get a new response from GPT where it can see the function response |
||||
return second_response |
||||
|
||||
|
||||
print(run_conversation()) |
@ -0,0 +1,2 @@ |
||||
langchain==0.0.234 |
||||
openai==0.27.8 |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue