llms-py 2.0.21__tar.gz → 3.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. {llms_py-2.0.21 → llms_py-3.0.6}/LICENSE +1 -2
  2. llms_py-3.0.6/PKG-INFO +49 -0
  3. llms_py-3.0.6/README.md +9 -0
  4. llms_py-3.0.6/llms/__init__.py +4 -0
  5. llms_py-3.0.6/llms/db.py +359 -0
  6. llms_py-2.0.21/llms/ui/Analytics.mjs → llms_py-3.0.6/llms/extensions/analytics/ui/index.mjs +254 -327
  7. llms_py-3.0.6/llms/extensions/app/README.md +20 -0
  8. llms_py-3.0.6/llms/extensions/app/__init__.py +587 -0
  9. llms_py-3.0.6/llms/extensions/app/db.py +536 -0
  10. {llms_py-2.0.21/llms → llms_py-3.0.6/llms/extensions/app}/ui/Recents.mjs +98 -72
  11. llms_py-2.0.21/llms/ui/Sidebar.mjs → llms_py-3.0.6/llms/extensions/app/ui/index.mjs +139 -68
  12. llms_py-3.0.6/llms/extensions/app/ui/threadStore.mjs +432 -0
  13. llms_py-3.0.6/llms/extensions/core_tools/CALCULATOR.md +32 -0
  14. llms_py-3.0.6/llms/extensions/core_tools/__init__.py +635 -0
  15. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  16. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  17. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  18. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  19. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  20. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  21. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  22. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  23. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  24. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  25. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  26. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  27. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  28. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  29. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  30. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  31. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  32. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  33. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  34. llms_py-3.0.6/llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  35. llms_py-3.0.6/llms/extensions/core_tools/ui/index.mjs +650 -0
  36. llms_py-3.0.6/llms/extensions/gallery/README.md +61 -0
  37. llms_py-3.0.6/llms/extensions/gallery/__init__.py +63 -0
  38. llms_py-3.0.6/llms/extensions/gallery/db.py +243 -0
  39. llms_py-3.0.6/llms/extensions/gallery/ui/index.mjs +482 -0
  40. llms_py-3.0.6/llms/extensions/katex/README.md +39 -0
  41. llms_py-3.0.6/llms/extensions/katex/__init__.py +6 -0
  42. llms_py-3.0.6/llms/extensions/katex/ui/README.md +125 -0
  43. llms_py-3.0.6/llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  44. llms_py-3.0.6/llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  45. llms_py-3.0.6/llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  46. llms_py-3.0.6/llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  47. llms_py-3.0.6/llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  48. llms_py-3.0.6/llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  49. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  50. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  51. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  52. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  53. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  54. llms_py-3.0.6/llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  55. llms_py-3.0.6/llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  56. llms_py-3.0.6/llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  57. llms_py-3.0.6/llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  58. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  59. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  60. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  61. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  62. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  63. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  64. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  65. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  66. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  67. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  68. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  69. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  70. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  71. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  72. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  73. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  74. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  75. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  76. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  77. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  78. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  79. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  80. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  81. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  82. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  83. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  84. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  85. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  86. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  87. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  88. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  89. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  90. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  91. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  92. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  93. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  94. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  95. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  96. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  97. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  98. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  99. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  100. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  101. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  102. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  103. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  104. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  105. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  106. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  107. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  108. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  109. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  110. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  111. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  112. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  113. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  114. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  115. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  116. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  117. llms_py-3.0.6/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  118. llms_py-3.0.6/llms/extensions/katex/ui/index.mjs +92 -0
  119. llms_py-3.0.6/llms/extensions/katex/ui/katex-swap.css +1230 -0
  120. llms_py-3.0.6/llms/extensions/katex/ui/katex-swap.min.css +1 -0
  121. llms_py-3.0.6/llms/extensions/katex/ui/katex.css +1230 -0
  122. llms_py-3.0.6/llms/extensions/katex/ui/katex.js +19080 -0
  123. llms_py-3.0.6/llms/extensions/katex/ui/katex.min.css +1 -0
  124. llms_py-3.0.6/llms/extensions/katex/ui/katex.min.js +1 -0
  125. llms_py-3.0.6/llms/extensions/katex/ui/katex.min.mjs +1 -0
  126. llms_py-3.0.6/llms/extensions/katex/ui/katex.mjs +18547 -0
  127. llms_py-3.0.6/llms/extensions/providers/__init__.py +20 -0
  128. llms_py-3.0.6/llms/extensions/providers/anthropic.py +233 -0
  129. llms_py-3.0.6/llms/extensions/providers/cerebras.py +37 -0
  130. llms_py-3.0.6/llms/extensions/providers/chutes.py +155 -0
  131. llms_py-3.0.6/llms/extensions/providers/google.py +481 -0
  132. llms_py-3.0.6/llms/extensions/providers/nvidia.py +105 -0
  133. llms_py-3.0.6/llms/extensions/providers/openai.py +156 -0
  134. llms_py-3.0.6/llms/extensions/providers/openrouter.py +74 -0
  135. llms_py-3.0.6/llms/extensions/system_prompts/README.md +22 -0
  136. llms_py-3.0.6/llms/extensions/system_prompts/__init__.py +45 -0
  137. llms_py-3.0.6/llms/extensions/system_prompts/ui/index.mjs +280 -0
  138. llms_py-3.0.6/llms/extensions/system_prompts/ui/prompts.json +1067 -0
  139. llms_py-3.0.6/llms/extensions/tools/__init__.py +5 -0
  140. llms_py-3.0.6/llms/extensions/tools/ui/index.mjs +204 -0
  141. llms_py-3.0.6/llms/index.html +58 -0
  142. llms_py-3.0.6/llms/llms.json +388 -0
  143. llms_py-3.0.6/llms/main.py +4045 -0
  144. llms_py-3.0.6/llms/providers-extra.json +356 -0
  145. llms_py-3.0.6/llms/providers.json +1 -0
  146. llms_py-3.0.6/llms/ui/App.mjs +188 -0
  147. llms_py-3.0.6/llms/ui/ai.mjs +217 -0
  148. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/app.css +2881 -245
  149. llms_py-3.0.6/llms/ui/ctx.mjs +399 -0
  150. llms_py-3.0.6/llms/ui/index.mjs +131 -0
  151. llms_py-3.0.6/llms/ui/lib/charts.mjs +16 -0
  152. llms_py-3.0.6/llms/ui/lib/servicestack-vue.mjs +37 -0
  153. llms_py-3.0.6/llms/ui/lib/vue.min.mjs +13 -0
  154. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/vue.mjs +1796 -1635
  155. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/markdown.mjs +25 -14
  156. llms_py-3.0.6/llms/ui/modules/chat/ChatBody.mjs +688 -0
  157. {llms_py-2.0.21/llms/ui → llms_py-3.0.6/llms/ui/modules/chat}/SettingsDialog.mjs +74 -74
  158. llms_py-3.0.6/llms/ui/modules/chat/index.mjs +977 -0
  159. llms_py-3.0.6/llms/ui/modules/icons.mjs +46 -0
  160. llms_py-3.0.6/llms/ui/modules/layout.mjs +271 -0
  161. llms_py-3.0.6/llms/ui/modules/model-selector.mjs +811 -0
  162. llms_py-3.0.6/llms/ui/tailwind.input.css +688 -0
  163. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/typography.css +54 -36
  164. llms_py-3.0.6/llms/ui/utils.mjs +232 -0
  165. llms_py-3.0.6/llms_py.egg-info/PKG-INFO +49 -0
  166. llms_py-3.0.6/llms_py.egg-info/SOURCES.txt +191 -0
  167. {llms_py-2.0.21 → llms_py-3.0.6}/pyproject.toml +58 -3
  168. {llms_py-2.0.21 → llms_py-3.0.6}/requirements.txt +1 -0
  169. {llms_py-2.0.21 → llms_py-3.0.6}/setup.py +7 -6
  170. llms_py-3.0.6/tests/test_async.py +116 -0
  171. llms_py-3.0.6/tests/test_config.py +89 -0
  172. llms_py-3.0.6/tests/test_extensions.py +48 -0
  173. llms_py-3.0.6/tests/test_gemini_upload.py +71 -0
  174. llms_py-3.0.6/tests/test_integration.py +116 -0
  175. llms_py-3.0.6/tests/test_provider_checks.py +150 -0
  176. llms_py-3.0.6/tests/test_provider_config.py +232 -0
  177. llms_py-3.0.6/tests/test_utils.py +254 -0
  178. llms_py-2.0.21/PKG-INFO +0 -931
  179. llms_py-2.0.21/README.md +0 -891
  180. llms_py-2.0.21/llms/__init__.py +0 -2
  181. llms_py-2.0.21/llms/index.html +0 -84
  182. llms_py-2.0.21/llms/llms.json +0 -1099
  183. llms_py-2.0.21/llms/main.py +0 -1666
  184. llms_py-2.0.21/llms/ui/App.mjs +0 -20
  185. llms_py-2.0.21/llms/ui/Avatar.mjs +0 -28
  186. llms_py-2.0.21/llms/ui/Brand.mjs +0 -34
  187. llms_py-2.0.21/llms/ui/ChatPrompt.mjs +0 -443
  188. llms_py-2.0.21/llms/ui/Main.mjs +0 -740
  189. llms_py-2.0.21/llms/ui/ModelSelector.mjs +0 -60
  190. llms_py-2.0.21/llms/ui/ProviderIcon.mjs +0 -29
  191. llms_py-2.0.21/llms/ui/ProviderStatus.mjs +0 -105
  192. llms_py-2.0.21/llms/ui/SignIn.mjs +0 -64
  193. llms_py-2.0.21/llms/ui/SystemPromptEditor.mjs +0 -31
  194. llms_py-2.0.21/llms/ui/SystemPromptSelector.mjs +0 -36
  195. llms_py-2.0.21/llms/ui/Welcome.mjs +0 -8
  196. llms_py-2.0.21/llms/ui/ai.mjs +0 -81
  197. llms_py-2.0.21/llms/ui/lib/charts.mjs +0 -20
  198. llms_py-2.0.21/llms/ui/lib/servicestack-vue.mjs +0 -37
  199. llms_py-2.0.21/llms/ui/lib/vue.min.mjs +0 -12
  200. llms_py-2.0.21/llms/ui/tailwind.input.css +0 -270
  201. llms_py-2.0.21/llms/ui/threadStore.mjs +0 -524
  202. llms_py-2.0.21/llms/ui/utils.mjs +0 -156
  203. llms_py-2.0.21/llms/ui.json +0 -1069
  204. llms_py-2.0.21/llms_py.egg-info/PKG-INFO +0 -931
  205. llms_py-2.0.21/llms_py.egg-info/SOURCES.txt +0 -54
  206. {llms_py-2.0.21 → llms_py-3.0.6}/MANIFEST.in +0 -0
  207. {llms_py-2.0.21 → llms_py-3.0.6}/llms/__main__.py +0 -0
  208. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/fav.svg +0 -0
  209. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/chart.js +0 -0
  210. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/color.js +0 -0
  211. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/highlight.min.mjs +0 -0
  212. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/idb.min.mjs +0 -0
  213. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/marked.min.mjs +0 -0
  214. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/servicestack-client.mjs +0 -0
  215. {llms_py-2.0.21 → llms_py-3.0.6}/llms/ui/lib/vue-router.min.mjs +0 -0
  216. {llms_py-2.0.21 → llms_py-3.0.6}/llms_py.egg-info/dependency_links.txt +0 -0
  217. {llms_py-2.0.21 → llms_py-3.0.6}/llms_py.egg-info/entry_points.txt +0 -0
  218. {llms_py-2.0.21 → llms_py-3.0.6}/llms_py.egg-info/not-zip-safe +0 -0
  219. {llms_py-2.0.21 → llms_py-3.0.6}/llms_py.egg-info/requires.txt +0 -0
  220. {llms_py-2.0.21 → llms_py-3.0.6}/llms_py.egg-info/top_level.txt +0 -0
  221. {llms_py-2.0.21 → llms_py-3.0.6}/setup.cfg +0 -0
@@ -1,6 +1,5 @@
1
1
  Copyright (c) 2007-present, Demis Bellot, ServiceStack, Inc.
2
2
  https://servicestack.net
3
- All rights reserved.
4
3
 
5
4
  Redistribution and use in source and binary forms, with or without
6
5
  modification, are permitted provided that the following conditions are met:
@@ -9,7 +8,7 @@ modification, are permitted provided that the following conditions are met:
9
8
  * Redistributions in binary form must reproduce the above copyright
10
9
  notice, this list of conditions and the following disclaimer in the
11
10
  documentation and/or other materials provided with the distribution.
12
- * Neither the name of the ServiceStack nor the
11
+ * Neither the name of the copyright holder nor the
13
12
  names of its contributors may be used to endorse or promote products
14
13
  derived from this software without specific prior written permission.
15
14
 
llms_py-3.0.6/PKG-INFO ADDED
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: llms-py
3
+ Version: 3.0.6
4
+ Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
+ Home-page: https://github.com/ServiceStack/llms
6
+ Author: ServiceStack
7
+ Author-email: ServiceStack <team@servicestack.net>
8
+ Maintainer-email: ServiceStack <team@servicestack.net>
9
+ License-Expression: BSD-3-Clause
10
+ Project-URL: Homepage, https://github.com/ServiceStack/llms
11
+ Project-URL: Documentation, https://github.com/ServiceStack/llms#readme
12
+ Project-URL: Repository, https://github.com/ServiceStack/llms
13
+ Project-URL: Bug Reports, https://github.com/ServiceStack/llms/issues
14
+ Keywords: llm,ai,openai,anthropic,google,gemini,groq,mistral,ollama,cli,server,chat,completion
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.7
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
28
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
29
+ Classifier: Topic :: System :: Systems Administration
30
+ Classifier: Topic :: Utilities
31
+ Classifier: Environment :: Console
32
+ Requires-Python: >=3.7
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: aiohttp
36
+ Dynamic: author
37
+ Dynamic: home-page
38
+ Dynamic: license-file
39
+ Dynamic: requires-python
40
+
41
+ # llms.py
42
+
43
+ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing multiple LLMs, entirely offline, with all data kept private in browser storage.
44
+
45
+ [llmspy.org](https://llmspy.org)
46
+
47
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
48
+
49
+ GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -0,0 +1,9 @@
1
+ # llms.py
2
+
3
+ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing multiple LLMs, entirely offline, with all data kept private in browser storage.
4
+
5
+ [llmspy.org](https://llmspy.org)
6
+
7
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
8
+
9
+ GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -0,0 +1,4 @@
1
+ # Import the main module content
2
+ from .main import main as main
3
+
4
+ __all__ = ["main"]
@@ -0,0 +1,359 @@
1
+ import json
2
+ import os
3
+ import sqlite3
4
+ import threading
5
+ from queue import Empty, Queue
6
+ from threading import Event, Thread
7
+
8
+ POOL = os.getenv("LLMS_POOL", "0") == "1"
9
+
10
+
11
+ def create_reader_connection(db_path):
12
+ # isolation_level=None leaves the connection in autocommit mode
13
+ conn = sqlite3.connect(
14
+ db_path, timeout=1.0, check_same_thread=False, isolation_level=None
15
+ ) # Lower - reads should be fast
16
+ conn.execute("PRAGMA query_only=1") # Read-only optimization
17
+ return conn
18
+
19
+
20
+ def create_writer_connection(db_path):
21
+ conn = sqlite3.connect(db_path)
22
+ conn.execute("PRAGMA busy_timeout=5000") # Reasonable timeout for busy connections
23
+ conn.execute("PRAGMA journal_mode=WAL") # Enable WAL mode for better concurrency
24
+ conn.execute("PRAGMA cache_size=-128000") # Increase cache size for better performance
25
+ conn.execute("PRAGMA synchronous=NORMAL") # Reasonable durability/performance balance
26
+ return conn
27
+
28
+
29
+ def writer_thread(ctx, db_path, task_queue, stop_event):
30
+ conn = create_writer_connection(db_path)
31
+ try:
32
+ while not stop_event.is_set():
33
+ try:
34
+ # Use timeout to check stop_event periodically
35
+ task = task_queue.get(timeout=0.1)
36
+
37
+ if task is None: # Poison pill for clean shutdown
38
+ break
39
+
40
+ sql, args, callback = task # Optional callback for results
41
+
42
+ try:
43
+ ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if args else " ") + str(args))
44
+ cursor = conn.execute(sql, args)
45
+ conn.commit()
46
+ ctx.dbg(f"lastrowid {cursor.lastrowid}, rowcount {cursor.rowcount}")
47
+ if callback:
48
+ callback(cursor.lastrowid, cursor.rowcount)
49
+ except sqlite3.Error as e:
50
+ ctx.err("writer_thread", e)
51
+ if callback:
52
+ callback(None, None, error=e)
53
+ finally:
54
+ task_queue.task_done()
55
+
56
+ except Empty:
57
+ continue
58
+
59
+ finally:
60
+ conn.close()
61
+
62
+
63
+ def to_dto(ctx, row, json_columns):
64
+ # as=column -> [0,1,2]
65
+ if not isinstance(row, dict):
66
+ return row
67
+
68
+ to = {}
69
+ for k, v in row.items():
70
+ if k in json_columns and v is not None and isinstance(v, str):
71
+ try:
72
+ to[k] = json.loads(v)
73
+ except Exception as e:
74
+ print(f"Failed to parse JSON for {k}: {v} ({type(v)})", e)
75
+ to[k] = v
76
+ else:
77
+ to[k] = v
78
+ return to
79
+
80
+
81
+ def valid_columns(all_columns, fields):
82
+ if fields:
83
+ if not isinstance(fields, list):
84
+ fields = fields.split(",")
85
+ cols = []
86
+ for k in fields:
87
+ k = k.strip()
88
+ if k in all_columns:
89
+ cols.append(k)
90
+ return cols
91
+ return []
92
+
93
+
94
+ def table_columns(all_columns, fields):
95
+ cols = valid_columns(all_columns, fields)
96
+ return ", ".join(cols) if len(cols) > 0 else ", ".join(all_columns)
97
+
98
+
99
+ def select_columns(all_columns, fields, select=None):
100
+ columns = table_columns(all_columns, fields)
101
+ if select == "distinct":
102
+ return f"SELECT DISTINCT {columns}"
103
+ return f"SELECT {columns}"
104
+
105
+
106
+ def order_by(all_columns, sort):
107
+ cols = []
108
+ for k in sort.split(","):
109
+ k = k.strip()
110
+ by = ""
111
+ if k[0] == "-":
112
+ by = " DESC"
113
+ k = k[1:]
114
+ if k in all_columns:
115
+ cols.append(f"{k}{by}")
116
+ return f"ORDER BY {', '.join(cols)} " if len(cols) > 0 else ""
117
+
118
+
119
+ class DbManager:
120
+ def __init__(self, ctx, db_path, clone=None):
121
+ if db_path is None:
122
+ raise ValueError("db_path is required")
123
+ self.ctx = ctx
124
+ self.db_path = db_path
125
+ self.read_only_pool = Queue()
126
+ if not clone:
127
+ self.task_queue = Queue()
128
+ self.stop_event = Event()
129
+ self.writer_thread = Thread(target=writer_thread, args=(ctx, db_path, self.task_queue, self.stop_event))
130
+ self.writer_thread.start()
131
+ else:
132
+ # share singleton writer thread in clones
133
+ self.task_queue = clone.task_queue
134
+ self.stop_event = clone.stop_event
135
+ self.writer_thread = clone.writer_thread
136
+
137
+ def create_reader_connection(self):
138
+ return create_reader_connection(self.db_path)
139
+
140
+ def create_writer_connection(self):
141
+ return create_writer_connection(self.db_path)
142
+
143
+ def resolve_connection(self):
144
+ if POOL:
145
+ try:
146
+ return self.read_only_pool.get_nowait()
147
+ except Empty:
148
+ return self.create_reader_connection()
149
+ else:
150
+ return self.create_reader_connection()
151
+
152
+ def release_connection(self, conn):
153
+ if POOL:
154
+ conn.rollback()
155
+ self.read_only_pool.put(conn)
156
+ else:
157
+ conn.close()
158
+
159
+ def write(self, query, args=None, callback=None):
160
+ """
161
+ Execute a write operation asynchronously.
162
+
163
+ Args:
164
+ query (str): The SQL query to execute.
165
+ args (tuple, optional): Arguments for the query.
166
+ callback (callable, optional): A function called after execution with signature:
167
+ callback(lastrowid, rowcount, error=None)
168
+ - lastrowid (int): output of cursor.lastrowid
169
+ - rowcount (int): output of cursor.rowcount
170
+ - error (Exception): exception if operation failed, else None
171
+ """
172
+ self.task_queue.put((query, args, callback))
173
+
174
+ def log_sql(self, sql, parameters=None):
175
+ if self.ctx.debug:
176
+ self.ctx.dbg(
177
+ "SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if parameters else " ") + str(parameters)
178
+ )
179
+
180
+ def exec(self, connection, sql, parameters=None):
181
+ self.log_sql(sql, parameters)
182
+ return connection.execute(sql, parameters or ())
183
+
184
+ def all(self, sql, parameters=None, connection=None):
185
+ """
186
+ Execute a query and return all rows as a list of dictionaries.
187
+ """
188
+ conn = self.resolve_connection() if connection is None else connection
189
+
190
+ try:
191
+ self.log_sql(sql, parameters)
192
+ conn.row_factory = sqlite3.Row
193
+ cursor = conn.execute(sql, parameters or ())
194
+ rows = [dict(row) for row in cursor.fetchall()]
195
+ return rows
196
+ finally:
197
+ if connection is None:
198
+ conn.row_factory = None
199
+ self.release_connection(conn)
200
+
201
+ def one(self, sql, parameters=None, connection=None):
202
+ """
203
+ Execute a query and return the first row as a dictionary.
204
+ """
205
+ conn = self.resolve_connection() if connection is None else connection
206
+
207
+ try:
208
+ self.log_sql(sql, parameters)
209
+ conn.row_factory = sqlite3.Row
210
+ cursor = conn.execute(sql, parameters or ())
211
+ row = cursor.fetchone()
212
+ return dict(row) if row else None
213
+ finally:
214
+ if connection is None:
215
+ conn.row_factory = None
216
+ self.release_connection(conn)
217
+
218
+ def scalar(self, sql, parameters=None, connection=None):
219
+ """
220
+ Execute a scalar query and return the first column of the first row.
221
+ """
222
+ conn = self.resolve_connection() if connection is None else connection
223
+
224
+ try:
225
+ self.log_sql(sql, parameters)
226
+ conn.row_factory = sqlite3.Row
227
+ cursor = conn.execute(sql, parameters or ())
228
+ row = cursor.fetchone()
229
+ return row[0] if row else None
230
+ finally:
231
+ if connection is None:
232
+ conn.row_factory = None
233
+ self.release_connection(conn)
234
+
235
+ def column(self, sql, parameters=None, connection=None):
236
+ """
237
+ Execute a 1 column query and return the values as a list.
238
+ """
239
+ conn = self.resolve_connection() if connection is None else connection
240
+
241
+ try:
242
+ self.log_sql(sql, parameters)
243
+ cursor = conn.execute(sql, parameters or ())
244
+ return [row[0] for row in cursor.fetchall()]
245
+ finally:
246
+ if connection is None:
247
+ self.release_connection(conn)
248
+
249
+ def dict(self, sql, parameters=None, connection=None):
250
+ """
251
+ Execute a 2 column query and return the keys as the first column and the values as the second column.
252
+ """
253
+ conn = self.resolve_connection() if connection is None else connection
254
+
255
+ try:
256
+ self.log_sql(sql, parameters)
257
+ conn.row_factory = sqlite3.Row
258
+ cursor = conn.execute(sql, parameters or ())
259
+ rows = cursor.fetchall()
260
+ return {row[0]: row[1] for row in rows}
261
+ finally:
262
+ if connection is None:
263
+ conn.row_factory = None
264
+ self.release_connection(conn)
265
+
266
+ # Helper to safely dump JSON if value exists
267
+ def value(self, val):
268
+ if val is None or val == "":
269
+ return None
270
+ if isinstance(val, (dict, list)):
271
+ return json.dumps(val)
272
+ return val
273
+
274
+ def insert(self, table, columns, info, callback=None):
275
+ if not info:
276
+ raise Exception("info is required")
277
+
278
+ args = {}
279
+ known_columns = columns.keys()
280
+ for k, val in info.items():
281
+ if k in known_columns and k != "id":
282
+ args[k] = self.value(val)
283
+
284
+ insert_keys = list(args.keys())
285
+ insert_body = ", ".join(insert_keys)
286
+ insert_values = ", ".join(["?" for _ in insert_keys])
287
+
288
+ sql = f"INSERT INTO {table} ({insert_body}) VALUES ({insert_values})"
289
+
290
+ self.write(sql, tuple(args[k] for k in insert_keys), callback)
291
+
292
+ async def insert_async(self, table, columns, info):
293
+ event = threading.Event()
294
+
295
+ ret = [None, None]
296
+
297
+ def cb(lastrowid, rowcount, error=None):
298
+ nonlocal ret
299
+ if error:
300
+ ret[1] = error
301
+ else:
302
+ ret[0] = lastrowid
303
+ event.set()
304
+
305
+ self.insert(table, columns, info, cb)
306
+ event.wait()
307
+ if ret[1]:
308
+ raise ret[1]
309
+ return ret[0]
310
+
311
+ def update(self, table, columns, info, callback=None):
312
+ if not info:
313
+ raise Exception("info is required")
314
+
315
+ args = {}
316
+ known_columns = columns.keys()
317
+ for k, val in info.items():
318
+ if k in known_columns and k != "id":
319
+ args[k] = self.value(val)
320
+
321
+ update_keys = list(args.keys())
322
+ update_body = ", ".join([f"{k} = :{k}" for k in update_keys])
323
+
324
+ args["id"] = info["id"]
325
+ sql = f"UPDATE {table} SET {update_body} WHERE id = :id"
326
+
327
+ self.write(sql, args, callback)
328
+
329
+ async def update_async(self, table, columns, info):
330
+ event = threading.Event()
331
+
332
+ ret = [None, None]
333
+
334
+ def cb(lastrowid, rowcount, error=None):
335
+ nonlocal ret
336
+ if error:
337
+ ret[1] = error
338
+ else:
339
+ ret[0] = rowcount
340
+ event.set()
341
+
342
+ self.update(table, columns, info, cb)
343
+ event.wait()
344
+ if ret[1]:
345
+ raise ret[1]
346
+ return ret[0]
347
+
348
+ def close(self):
349
+ self.ctx.dbg("Closing database")
350
+ self.stop_event.set()
351
+ self.task_queue.put(None) # Poison pill to signal shutdown
352
+ self.writer_thread.join()
353
+
354
+ while not self.read_only_pool.empty():
355
+ try:
356
+ conn = self.read_only_pool.get_nowait()
357
+ conn.close()
358
+ except Empty:
359
+ break