llms-py 3.0.1__tar.gz → 3.0.3__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 (193) hide show
  1. {llms_py-3.0.1 → llms_py-3.0.3}/PKG-INFO +1 -1
  2. llms_py-3.0.1/llms/extensions/app/db_manager.py → llms_py-3.0.3/llms/db.py +180 -16
  3. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/__init__.py +96 -29
  4. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/db.py +16 -124
  5. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/ui/threadStore.mjs +23 -2
  6. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/__init__.py +37 -0
  7. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/gallery/__init__.py +15 -13
  8. llms_py-3.0.3/llms/extensions/gallery/db.py +243 -0
  9. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/gallery/ui/index.mjs +1 -1
  10. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/__init__.py +3 -1
  11. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/anthropic.py +7 -3
  12. llms_py-3.0.3/llms/extensions/providers/cerebras.py +37 -0
  13. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/chutes.py +1 -1
  14. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/google.py +131 -28
  15. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/nvidia.py +2 -2
  16. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/openai.py +2 -2
  17. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/providers/openrouter.py +4 -2
  18. {llms_py-3.0.1 → llms_py-3.0.3}/llms/llms.json +3 -0
  19. {llms_py-3.0.1 → llms_py-3.0.3}/llms/main.py +83 -34
  20. {llms_py-3.0.1 → llms_py-3.0.3}/llms/providers.json +1 -1
  21. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/ai.mjs +1 -1
  22. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/app.css +106 -3
  23. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/ctx.mjs +34 -0
  24. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/index.mjs +2 -0
  25. llms_py-3.0.3/llms/ui/modules/chat/ChatBody.mjs +688 -0
  26. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/modules/chat/index.mjs +93 -2
  27. llms_py-3.0.3/llms/ui/modules/icons.mjs +46 -0
  28. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/modules/layout.mjs +28 -0
  29. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/modules/model-selector.mjs +0 -40
  30. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/tailwind.input.css +1 -1
  31. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/utils.mjs +9 -1
  32. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/PKG-INFO +1 -1
  33. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/SOURCES.txt +4 -1
  34. {llms_py-3.0.1 → llms_py-3.0.3}/pyproject.toml +1 -1
  35. {llms_py-3.0.1 → llms_py-3.0.3}/setup.py +1 -1
  36. llms_py-3.0.3/tests/test_gemini_upload.py +71 -0
  37. llms_py-3.0.1/llms/extensions/gallery/db.py +0 -298
  38. llms_py-3.0.1/llms/ui/modules/chat/ChatBody.mjs +0 -691
  39. {llms_py-3.0.1 → llms_py-3.0.3}/LICENSE +0 -0
  40. {llms_py-3.0.1 → llms_py-3.0.3}/MANIFEST.in +0 -0
  41. {llms_py-3.0.1 → llms_py-3.0.3}/README.md +0 -0
  42. {llms_py-3.0.1 → llms_py-3.0.3}/llms/__init__.py +0 -0
  43. {llms_py-3.0.1 → llms_py-3.0.3}/llms/__main__.py +0 -0
  44. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/analytics/ui/index.mjs +0 -0
  45. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/README.md +0 -0
  46. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/ui/Recents.mjs +0 -0
  47. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/app/ui/index.mjs +0 -0
  48. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/CALCULATOR.md +0 -0
  49. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -0
  50. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -0
  51. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -0
  52. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -0
  53. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -0
  54. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -0
  55. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -0
  56. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -0
  57. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -0
  58. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -0
  59. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  60. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -0
  61. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -0
  62. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -0
  63. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -0
  64. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -0
  65. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -0
  66. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -0
  67. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/core_tools/ui/index.mjs +0 -0
  68. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/gallery/README.md +0 -0
  69. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/README.md +0 -0
  70. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/__init__.py +0 -0
  71. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/README.md +0 -0
  72. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/auto-render.js +0 -0
  73. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/auto-render.min.js +0 -0
  74. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/auto-render.mjs +0 -0
  75. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/copy-tex.js +0 -0
  76. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -0
  77. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -0
  78. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -0
  79. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -0
  80. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -0
  81. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mhchem.js +0 -0
  82. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mhchem.min.js +0 -0
  83. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/mhchem.mjs +0 -0
  84. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -0
  85. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -0
  86. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -0
  87. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  88. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  89. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  90. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  91. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  92. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  93. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  94. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  95. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  96. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  97. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  98. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  99. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  100. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  101. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  102. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  103. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  104. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  105. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  106. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  107. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  108. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  109. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  110. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  111. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  112. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  113. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  114. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  115. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  116. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  117. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  118. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  119. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  120. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  121. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  122. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  123. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  124. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  125. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  126. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  127. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  128. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  129. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  130. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  131. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  132. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  133. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  134. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  135. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  136. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  137. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  138. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  139. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  140. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  141. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  142. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  143. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  144. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  145. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  146. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  147. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/index.mjs +0 -0
  148. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex-swap.css +0 -0
  149. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex-swap.min.css +0 -0
  150. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.css +0 -0
  151. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.js +0 -0
  152. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.min.css +0 -0
  153. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.min.js +0 -0
  154. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.min.mjs +0 -0
  155. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/katex/ui/katex.mjs +0 -0
  156. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/system_prompts/README.md +0 -0
  157. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/system_prompts/__init__.py +0 -0
  158. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/system_prompts/ui/index.mjs +0 -0
  159. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/system_prompts/ui/prompts.json +0 -0
  160. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/tools/__init__.py +0 -0
  161. {llms_py-3.0.1 → llms_py-3.0.3}/llms/extensions/tools/ui/index.mjs +0 -0
  162. {llms_py-3.0.1 → llms_py-3.0.3}/llms/index.html +0 -0
  163. {llms_py-3.0.1 → llms_py-3.0.3}/llms/providers-extra.json +0 -0
  164. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/App.mjs +0 -0
  165. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/fav.svg +0 -0
  166. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/chart.js +0 -0
  167. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/charts.mjs +0 -0
  168. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/color.js +0 -0
  169. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/highlight.min.mjs +0 -0
  170. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/idb.min.mjs +0 -0
  171. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/marked.min.mjs +0 -0
  172. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/servicestack-client.mjs +0 -0
  173. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/servicestack-vue.mjs +0 -0
  174. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/vue-router.min.mjs +0 -0
  175. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/vue.min.mjs +0 -0
  176. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/lib/vue.mjs +0 -0
  177. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/markdown.mjs +0 -0
  178. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/modules/chat/SettingsDialog.mjs +0 -0
  179. {llms_py-3.0.1 → llms_py-3.0.3}/llms/ui/typography.css +0 -0
  180. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/dependency_links.txt +0 -0
  181. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/entry_points.txt +0 -0
  182. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/not-zip-safe +0 -0
  183. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/requires.txt +0 -0
  184. {llms_py-3.0.1 → llms_py-3.0.3}/llms_py.egg-info/top_level.txt +0 -0
  185. {llms_py-3.0.1 → llms_py-3.0.3}/requirements.txt +0 -0
  186. {llms_py-3.0.1 → llms_py-3.0.3}/setup.cfg +0 -0
  187. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_async.py +0 -0
  188. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_config.py +0 -0
  189. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_extensions.py +0 -0
  190. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_integration.py +0 -0
  191. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_provider_checks.py +0 -0
  192. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_provider_config.py +0 -0
  193. {llms_py-3.0.1 → llms_py-3.0.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.1
3
+ Version: 3.0.3
4
4
  Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
5
  Home-page: https://github.com/ServiceStack/llms
6
6
  Author: ServiceStack
@@ -1,11 +1,18 @@
1
1
  import json
2
+ import os
2
3
  import sqlite3
4
+ import threading
3
5
  from queue import Empty, Queue
4
6
  from threading import Event, Thread
5
7
 
8
+ POOL = os.getenv("LLMS_POOL", "1") == "1"
9
+
6
10
 
7
11
  def create_reader_connection(db_path):
8
- conn = sqlite3.connect(db_path, timeout=1.0) # Lower - reads should be fast
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
9
16
  conn.execute("PRAGMA query_only=1") # Read-only optimization
10
17
  return conn
11
18
 
@@ -33,7 +40,7 @@ def writer_thread(ctx, db_path, task_queue, stop_event):
33
40
  sql, args, callback = task # Optional callback for results
34
41
 
35
42
  try:
36
- ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql)
43
+ ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if args else " ") + str(args))
37
44
  cursor = conn.execute(sql, args)
38
45
  conn.commit()
39
46
  ctx.dbg(f"lastrowid {cursor.lastrowid}, rowcount {cursor.rowcount}")
@@ -53,17 +60,79 @@ def writer_thread(ctx, db_path, task_queue, stop_event):
53
60
  conn.close()
54
61
 
55
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
+
56
119
  class DbManager:
57
- def __init__(self, ctx, db_path):
120
+ def __init__(self, ctx, db_path, clone=None):
58
121
  if db_path is None:
59
122
  raise ValueError("db_path is required")
60
123
  self.ctx = ctx
61
124
  self.db_path = db_path
62
- self.task_queue = Queue()
63
- self.stop_event = Event()
64
- self.writer_thread = Thread(target=writer_thread, args=(ctx, db_path, self.task_queue, self.stop_event))
65
- self.writer_thread.start()
66
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
67
136
 
68
137
  def create_reader_connection(self):
69
138
  return create_reader_connection(self.db_path)
@@ -72,11 +141,21 @@ class DbManager:
72
141
  return create_writer_connection(self.db_path)
73
142
 
74
143
  def resolve_connection(self):
75
- try:
76
- return self.read_only_pool.get_nowait()
77
- except Empty:
144
+ if POOL:
145
+ try:
146
+ return self.read_only_pool.get_nowait()
147
+ except Empty:
148
+ return self.create_reader_connection()
149
+ else:
78
150
  return self.create_reader_connection()
79
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
+
80
159
  def write(self, query, args=None, callback=None):
81
160
  """
82
161
  Execute a write operation asynchronously.
@@ -94,13 +173,18 @@ class DbManager:
94
173
 
95
174
  def log_sql(self, sql, parameters=None):
96
175
  if self.ctx.debug:
97
- self.ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if parameters else "") + str(parameters))
176
+ self.ctx.dbg(
177
+ "SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if parameters else " ") + str(parameters)
178
+ )
98
179
 
99
180
  def exec(self, connection, sql, parameters=None):
100
181
  self.log_sql(sql, parameters)
101
182
  return connection.execute(sql, parameters or ())
102
183
 
103
184
  def all(self, sql, parameters=None, connection=None):
185
+ """
186
+ Execute a query and return all rows as a list of dictionaries.
187
+ """
104
188
  conn = self.resolve_connection() if connection is None else connection
105
189
 
106
190
  try:
@@ -112,9 +196,12 @@ class DbManager:
112
196
  finally:
113
197
  if connection is None:
114
198
  conn.row_factory = None
115
- self.read_only_pool.put(conn)
199
+ self.release_connection(conn)
116
200
 
117
201
  def one(self, sql, parameters=None, connection=None):
202
+ """
203
+ Execute a query and return the first row as a dictionary.
204
+ """
118
205
  conn = self.resolve_connection() if connection is None else connection
119
206
 
120
207
  try:
@@ -126,9 +213,12 @@ class DbManager:
126
213
  finally:
127
214
  if connection is None:
128
215
  conn.row_factory = None
129
- self.read_only_pool.put(conn)
216
+ self.release_connection(conn)
130
217
 
131
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
+ """
132
222
  conn = self.resolve_connection() if connection is None else connection
133
223
 
134
224
  try:
@@ -140,7 +230,7 @@ class DbManager:
140
230
  finally:
141
231
  if connection is None:
142
232
  conn.row_factory = None
143
- self.read_only_pool.put(conn)
233
+ self.release_connection(conn)
144
234
 
145
235
  def column(self, sql, parameters=None, connection=None):
146
236
  """
@@ -154,7 +244,7 @@ class DbManager:
154
244
  return [row[0] for row in cursor.fetchall()]
155
245
  finally:
156
246
  if connection is None:
157
- self.read_only_pool.put(conn)
247
+ self.release_connection(conn)
158
248
 
159
249
  def dict(self, sql, parameters=None, connection=None):
160
250
  """
@@ -171,7 +261,7 @@ class DbManager:
171
261
  finally:
172
262
  if connection is None:
173
263
  conn.row_factory = None
174
- self.read_only_pool.put(conn)
264
+ self.release_connection(conn)
175
265
 
176
266
  # Helper to safely dump JSON if value exists
177
267
  def value(self, val):
@@ -181,6 +271,80 @@ class DbManager:
181
271
  return json.dumps(val)
182
272
  return val
183
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
+
184
348
  def close(self):
185
349
  self.ctx.dbg("Closing database")
186
350
  self.stop_event.set()
@@ -7,13 +7,9 @@ from typing import Any
7
7
 
8
8
  from aiohttp import web
9
9
 
10
- g_db = None
10
+ from .db import AppDB
11
11
 
12
- try:
13
- from llms.extensions.app.db import AppDB
14
- except ImportError as e:
15
- print(f"Failed to import AppDB: {e}")
16
- AppDB = None
12
+ g_db = None
17
13
 
18
14
 
19
15
  def install(ctx):
@@ -32,28 +28,50 @@ def install(ctx):
32
28
  if not get_db():
33
29
  return
34
30
 
35
- def to_dto(row, json_columns):
36
- # as=column -> [0,1,2]
37
- if not isinstance(row, dict):
38
- return row
39
-
40
- to = {}
41
- for k, v in row.items():
42
- if k in json_columns and v is not None and isinstance(v, str):
43
- try:
44
- to[k] = json.loads(v)
45
- except Exception as e:
46
- ctx.err(f"Failed to parse JSON for {k}: {v} ({type(v)})", e)
47
- to[k] = v
48
- else:
49
- to[k] = v
50
- return to
31
+ thread_fields = [
32
+ "id",
33
+ "threadId",
34
+ "createdAt",
35
+ "updatedAt",
36
+ "title",
37
+ "model",
38
+ "modelInfo",
39
+ "modalities",
40
+ "messages",
41
+ "tools",
42
+ "args",
43
+ "cost",
44
+ "inputTokens",
45
+ "outputTokens",
46
+ "stats",
47
+ "provider",
48
+ "providerModel",
49
+ "publishedAt",
50
+ "startedAt",
51
+ "completedAt",
52
+ "metadata",
53
+ "error",
54
+ "ref",
55
+ ]
51
56
 
52
57
  def thread_dto(row):
53
- return row and to_dto(row, ["messages", "modalities", "args", "modelInfo", "stats", "metadata"])
58
+ return row and g_db.to_dto(
59
+ row,
60
+ [
61
+ "messages",
62
+ "tools",
63
+ "toolHistory",
64
+ "modalities",
65
+ "args",
66
+ "modelInfo",
67
+ "stats",
68
+ "metadata",
69
+ "providerResponse",
70
+ ],
71
+ )
54
72
 
55
73
  def request_dto(row):
56
- return row and to_dto(row, ["usage"])
74
+ return row and g_db.to_dto(row, ["usage"])
57
75
 
58
76
  def prompt_to_title(prompt):
59
77
  return prompt[:100] + ("..." if len(prompt) > 100 else "") if prompt else None
@@ -67,7 +85,10 @@ def install(ctx):
67
85
  return messages
68
86
 
69
87
  async def query_threads(request):
70
- rows = g_db.query_threads(request.query, user=ctx.get_username(request))
88
+ query = request.query.copy()
89
+ if "fields" not in query:
90
+ query["fields"] = thread_fields
91
+ rows = g_db.query_threads(query, user=ctx.get_username(request))
71
92
  dtos = [thread_dto(row) for row in rows]
72
93
  return web.json_response(dtos)
73
94
 
@@ -81,6 +102,13 @@ def install(ctx):
81
102
 
82
103
  ctx.add_post("threads", create_thread)
83
104
 
105
+ async def get_thread(request):
106
+ id = request.match_info["id"]
107
+ row = g_db.get_thread(id, user=ctx.get_username(request))
108
+ return web.json_response(thread_dto(row) if row else "")
109
+
110
+ ctx.add_get("threads/{id}", get_thread)
111
+
84
112
  async def update_thread(request):
85
113
  thread = await request.json()
86
114
  id = request.match_info["id"]
@@ -119,8 +147,10 @@ def install(ctx):
119
147
  if not thread:
120
148
  raise Exception("Thread not found")
121
149
 
150
+ tools = chat.get("tools", thread.get("tools", []))
122
151
  update_thread = {
123
152
  "messages": messages,
153
+ "tools": tools,
124
154
  "startedAt": datetime.now(),
125
155
  "completedAt": None,
126
156
  "error": None,
@@ -164,24 +194,28 @@ def install(ctx):
164
194
  if not thread:
165
195
  raise Exception("Thread not found")
166
196
 
167
- metadata = thread.get("metadata", {})
197
+ metadata = thread.get("metadata") or {}
168
198
  chat = {
169
199
  "model": thread.get("model"),
170
200
  "messages": thread.get("messages"),
171
201
  "modalities": thread.get("modalities"),
172
202
  "systemPrompt": thread.get("systemPrompt"),
203
+ "tools": thread.get("tools"), # tools request
173
204
  "metadata": metadata,
174
205
  }
175
- for k, v in thread.get("args", {}).items():
206
+ args = thread.get("args") or {}
207
+ for k, v in args.items():
176
208
  if k in ctx.request_args:
177
209
  chat[k] = v
178
210
 
211
+ ctx.dbg("CHAT\n" + json.dumps(chat, indent=2))
212
+
179
213
  context = {
180
214
  "chat": chat,
181
215
  "user": user,
182
216
  "threadId": id,
183
217
  "metadata": metadata,
184
- "tools": metadata.get("tools", "all"),
218
+ "tools": metadata.get("tools", "all"), # only tools: all|none|<tool1>,<tool2>,...
185
219
  }
186
220
 
187
221
  # execute chat in background thread
@@ -295,6 +329,7 @@ def install(ctx):
295
329
  metadata = chat.get("metadata", {})
296
330
  model = chat.get("model", None)
297
331
  messages = timestamp_messages(chat.get("messages", []))
332
+ tools = chat.get("tools", [])
298
333
  title = context.get("title") or prompt_to_title(ctx.last_user_prompt(chat) if chat else None)
299
334
  started_at = context.get("startedAt")
300
335
  if not started_at:
@@ -307,6 +342,7 @@ def install(ctx):
307
342
  "modelInfo": model_info,
308
343
  "title": title,
309
344
  "messages": messages,
345
+ "tools": tools,
310
346
  "systemPrompt": ctx.chat_to_system_prompt(chat),
311
347
  "modalities": chat.get("modalities", ["text"]),
312
348
  "startedAt": started_at,
@@ -321,6 +357,7 @@ def install(ctx):
321
357
  "modelInfo": model_info,
322
358
  "startedAt": started_at,
323
359
  "messages": messages,
360
+ "tools": tools,
324
361
  "completedAt": None,
325
362
  "error": None,
326
363
  "metadata": metadata,
@@ -355,6 +392,29 @@ def install(ctx):
355
392
 
356
393
  ctx.register_chat_tool_filter(tool_request)
357
394
 
395
+ def truncate_long_strings(obj, max_length=10000):
396
+ """
397
+ Recursively traverse a dictionary/list structure and replace
398
+ string values longer than max_length with their length indicator.
399
+
400
+ Args:
401
+ obj: The object to process (dict, list, or other value)
402
+ max_length: Maximum string length before truncation (default 10000)
403
+
404
+ Returns:
405
+ A new object with long strings replaced by "({length})"
406
+ """
407
+ if isinstance(obj, dict):
408
+ return {key: truncate_long_strings(value, max_length) for key, value in obj.items()}
409
+ elif isinstance(obj, list):
410
+ return [truncate_long_strings(item, max_length) for item in obj]
411
+ elif isinstance(obj, str):
412
+ if len(obj) > max_length:
413
+ return f"({len(obj)})"
414
+ return obj
415
+ else:
416
+ return obj
417
+
358
418
  async def chat_response(openai_response, context):
359
419
  ctx.dbg("create_response")
360
420
  o = openai_response
@@ -406,7 +466,6 @@ def install(ctx):
406
466
  "totalTokens": total_tokens,
407
467
  "usage": usage,
408
468
  "completedAt": completed_at,
409
- "toolHistory": o.get("tool_history", None),
410
469
  "ref": o.get("id", None),
411
470
  }
412
471
  tasks.append(g_db.create_request_async(request, user=user))
@@ -436,15 +495,23 @@ def install(ctx):
436
495
  }
437
496
  messages.append(assistant_message)
438
497
 
498
+ tools = chat.get("tools", [])
439
499
  update_thread = {
440
500
  "model": model,
441
501
  "providerModel": o.get("model"),
442
502
  "modelInfo": model_info,
443
503
  "messages": messages,
504
+ "tools": tools,
444
505
  "completedAt": completed_at,
445
506
  }
507
+ tool_history = o.get("tool_history", None)
508
+ if tool_history:
509
+ update_thread["toolHistory"] = tool_history
446
510
  if "error" in metadata:
447
511
  update_thread["error"] = metadata["error"]
512
+ provider_response = context.get("providerResponse", None)
513
+ if provider_response:
514
+ update_thread["providerResponse"] = truncate_long_strings(provider_response)
448
515
  tasks.append(g_db.update_thread_async(thread_id, update_thread, user=user))
449
516
  else:
450
517
  ctx.dbg("Missing thread_id")