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