llms-py 2.0.35__py3-none-any.whl → 3.0.0__py3-none-any.whl

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 (206) hide show
  1. llms/__pycache__/__init__.cpython-312.pyc +0 -0
  2. llms/__pycache__/__init__.cpython-313.pyc +0 -0
  3. llms/__pycache__/__init__.cpython-314.pyc +0 -0
  4. llms/__pycache__/__main__.cpython-312.pyc +0 -0
  5. llms/__pycache__/__main__.cpython-314.pyc +0 -0
  6. llms/__pycache__/llms.cpython-312.pyc +0 -0
  7. llms/__pycache__/main.cpython-312.pyc +0 -0
  8. llms/__pycache__/main.cpython-313.pyc +0 -0
  9. llms/__pycache__/main.cpython-314.pyc +0 -0
  10. llms/__pycache__/plugins.cpython-314.pyc +0 -0
  11. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +154 -238
  12. llms/extensions/app/README.md +20 -0
  13. llms/extensions/app/__init__.py +530 -0
  14. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  15. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  16. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  17. llms/extensions/app/db.py +644 -0
  18. llms/extensions/app/db_manager.py +195 -0
  19. llms/extensions/app/requests.json +9073 -0
  20. llms/extensions/app/threads.json +15290 -0
  21. llms/{ui → extensions/app/ui}/Recents.mjs +91 -65
  22. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +124 -58
  23. llms/extensions/app/ui/threadStore.mjs +411 -0
  24. llms/extensions/core_tools/CALCULATOR.md +32 -0
  25. llms/extensions/core_tools/__init__.py +598 -0
  26. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  27. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  28. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  29. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  30. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  31. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  32. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  33. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  34. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  35. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  36. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  37. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  38. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  39. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  40. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  41. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  42. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  43. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  44. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  45. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  46. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  47. llms/extensions/core_tools/ui/index.mjs +650 -0
  48. llms/extensions/gallery/README.md +61 -0
  49. llms/extensions/gallery/__init__.py +61 -0
  50. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  51. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  52. llms/extensions/gallery/db.py +298 -0
  53. llms/extensions/gallery/ui/index.mjs +482 -0
  54. llms/extensions/katex/README.md +39 -0
  55. llms/extensions/katex/__init__.py +6 -0
  56. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  57. llms/extensions/katex/ui/README.md +125 -0
  58. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  59. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  60. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  61. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  62. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  63. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  64. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  65. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  66. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  67. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  68. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  69. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  70. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  71. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  72. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  124. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  125. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  126. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  127. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  128. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  129. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  130. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  131. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  132. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  133. llms/extensions/katex/ui/index.mjs +92 -0
  134. llms/extensions/katex/ui/katex-swap.css +1230 -0
  135. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  136. llms/extensions/katex/ui/katex.css +1230 -0
  137. llms/extensions/katex/ui/katex.js +19080 -0
  138. llms/extensions/katex/ui/katex.min.css +1 -0
  139. llms/extensions/katex/ui/katex.min.js +1 -0
  140. llms/extensions/katex/ui/katex.min.mjs +1 -0
  141. llms/extensions/katex/ui/katex.mjs +18547 -0
  142. llms/extensions/providers/__init__.py +18 -0
  143. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  144. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  145. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  146. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  147. llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  148. llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  149. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  150. llms/extensions/providers/anthropic.py +229 -0
  151. llms/extensions/providers/chutes.py +155 -0
  152. llms/extensions/providers/google.py +378 -0
  153. llms/extensions/providers/nvidia.py +105 -0
  154. llms/extensions/providers/openai.py +156 -0
  155. llms/extensions/providers/openrouter.py +72 -0
  156. llms/extensions/system_prompts/README.md +22 -0
  157. llms/extensions/system_prompts/__init__.py +45 -0
  158. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  159. llms/extensions/system_prompts/ui/index.mjs +280 -0
  160. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  161. llms/extensions/tools/__init__.py +5 -0
  162. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  163. llms/extensions/tools/ui/index.mjs +204 -0
  164. llms/index.html +35 -77
  165. llms/llms.json +357 -1186
  166. llms/main.py +2349 -591
  167. llms/providers-extra.json +356 -0
  168. llms/providers.json +1 -0
  169. llms/ui/App.mjs +151 -60
  170. llms/ui/ai.mjs +132 -60
  171. llms/ui/app.css +2173 -161
  172. llms/ui/ctx.mjs +365 -0
  173. llms/ui/index.mjs +129 -0
  174. llms/ui/lib/charts.mjs +9 -13
  175. llms/ui/lib/servicestack-vue.mjs +3 -3
  176. llms/ui/lib/vue.min.mjs +10 -9
  177. llms/ui/lib/vue.mjs +1796 -1635
  178. llms/ui/markdown.mjs +18 -7
  179. llms/ui/modules/chat/ChatBody.mjs +691 -0
  180. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +9 -9
  181. llms/ui/modules/chat/index.mjs +828 -0
  182. llms/ui/modules/layout.mjs +243 -0
  183. llms/ui/modules/model-selector.mjs +851 -0
  184. llms/ui/tailwind.input.css +496 -80
  185. llms/ui/utils.mjs +161 -93
  186. {llms_py-2.0.35.dist-info → llms_py-3.0.0.dist-info}/METADATA +1 -1
  187. llms_py-3.0.0.dist-info/RECORD +202 -0
  188. llms/ui/Avatar.mjs +0 -85
  189. llms/ui/Brand.mjs +0 -52
  190. llms/ui/ChatPrompt.mjs +0 -590
  191. llms/ui/Main.mjs +0 -823
  192. llms/ui/ModelSelector.mjs +0 -78
  193. llms/ui/OAuthSignIn.mjs +0 -92
  194. llms/ui/ProviderIcon.mjs +0 -30
  195. llms/ui/ProviderStatus.mjs +0 -105
  196. llms/ui/SignIn.mjs +0 -64
  197. llms/ui/SystemPromptEditor.mjs +0 -31
  198. llms/ui/SystemPromptSelector.mjs +0 -56
  199. llms/ui/Welcome.mjs +0 -8
  200. llms/ui/threadStore.mjs +0 -563
  201. llms/ui.json +0 -1069
  202. llms_py-2.0.35.dist-info/RECORD +0 -48
  203. {llms_py-2.0.35.dist-info → llms_py-3.0.0.dist-info}/WHEEL +0 -0
  204. {llms_py-2.0.35.dist-info → llms_py-3.0.0.dist-info}/entry_points.txt +0 -0
  205. {llms_py-2.0.35.dist-info → llms_py-3.0.0.dist-info}/licenses/LICENSE +0 -0
  206. {llms_py-2.0.35.dist-info → llms_py-3.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,18 @@
1
+ from .anthropic import install_anthropic
2
+ from .chutes import install_chutes
3
+ from .google import install_google
4
+ from .nvidia import install_nvidia
5
+ from .openai import install_openai
6
+ from .openrouter import install_openrouter
7
+
8
+
9
+ def install(ctx):
10
+ install_anthropic(ctx)
11
+ install_chutes(ctx)
12
+ install_google(ctx)
13
+ install_openai(ctx)
14
+ install_openrouter(ctx)
15
+ install_nvidia(ctx)
16
+
17
+
18
+ __install__ = install
@@ -0,0 +1,229 @@
1
+ import json
2
+ import time
3
+
4
+ import aiohttp
5
+
6
+
7
+ def install_anthropic(ctx):
8
+ from llms.main import OpenAiCompatible
9
+
10
+ class AnthropicProvider(OpenAiCompatible):
11
+ sdk = "@ai-sdk/anthropic"
12
+
13
+ def __init__(self, **kwargs):
14
+ if "api" not in kwargs:
15
+ kwargs["api"] = "https://api.anthropic.com/v1"
16
+ super().__init__(**kwargs)
17
+
18
+ # Anthropic uses x-api-key header instead of Authorization
19
+ if self.api_key:
20
+ self.headers = self.headers.copy()
21
+ if "Authorization" in self.headers:
22
+ del self.headers["Authorization"]
23
+ self.headers["x-api-key"] = self.api_key
24
+
25
+ if "anthropic-version" not in self.headers:
26
+ self.headers = self.headers.copy()
27
+ self.headers["anthropic-version"] = "2023-06-01"
28
+ self.chat_url = f"{self.api}/messages"
29
+
30
+ async def chat(self, chat):
31
+ chat["model"] = self.provider_model(chat["model"]) or chat["model"]
32
+
33
+ chat = await self.process_chat(chat, provider_id=self.id)
34
+
35
+ # Transform OpenAI format to Anthropic format
36
+ anthropic_request = {
37
+ "model": chat["model"],
38
+ "messages": [],
39
+ }
40
+
41
+ # Extract system message (Anthropic uses top-level 'system' parameter)
42
+ system_messages = []
43
+ for message in chat.get("messages", []):
44
+ if message.get("role") == "system":
45
+ content = message.get("content", "")
46
+ if isinstance(content, str):
47
+ system_messages.append(content)
48
+ elif isinstance(content, list):
49
+ for item in content:
50
+ if item.get("type") == "text":
51
+ system_messages.append(item.get("text", ""))
52
+
53
+ if system_messages:
54
+ anthropic_request["system"] = "\n".join(system_messages)
55
+
56
+ # Transform messages (exclude system messages)
57
+ for message in chat.get("messages", []):
58
+ if message.get("role") == "system":
59
+ continue
60
+
61
+ if message.get("role") == "tool":
62
+ # Convert OpenAI tool response to Anthropic tool_result
63
+ tool_call_id = message.get("tool_call_id")
64
+ content = ctx.to_content(message.get("content", ""))
65
+ if not isinstance(content, (str, list)):
66
+ content = str(content)
67
+
68
+ tool_result = {"type": "tool_result", "tool_use_id": tool_call_id, "content": content}
69
+
70
+ # Anthropic requires tool results to be in a user message
71
+ # Check if the last message was a user message, if so append to it
72
+ if anthropic_request["messages"] and anthropic_request["messages"][-1]["role"] == "user":
73
+ anthropic_request["messages"][-1]["content"].append(tool_result)
74
+ else:
75
+ anthropic_request["messages"].append({"role": "user", "content": [tool_result]})
76
+ continue
77
+
78
+ anthropic_message = {"role": message.get("role"), "content": []}
79
+
80
+ content = message.get("content", "")
81
+ if isinstance(content, str):
82
+ anthropic_message["content"] = content
83
+ elif isinstance(content, list):
84
+ for item in content:
85
+ if item.get("type") == "text":
86
+ anthropic_message["content"].append({"type": "text", "text": item.get("text", "")})
87
+ elif item.get("type") == "image_url" and "image_url" in item:
88
+ # Transform OpenAI image_url format to Anthropic format
89
+ image_url = item["image_url"].get("url", "")
90
+ if image_url.startswith("data:"):
91
+ # Extract media type and base64 data
92
+ parts = image_url.split(";base64,", 1)
93
+ if len(parts) == 2:
94
+ media_type = parts[0].replace("data:", "")
95
+ base64_data = parts[1]
96
+ anthropic_message["content"].append(
97
+ {
98
+ "type": "image",
99
+ "source": {"type": "base64", "media_type": media_type, "data": base64_data},
100
+ }
101
+ )
102
+
103
+ anthropic_request["messages"].append(anthropic_message)
104
+
105
+ # Handle max_tokens (required by Anthropic, uses max_tokens not max_completion_tokens)
106
+ if "max_completion_tokens" in chat:
107
+ anthropic_request["max_tokens"] = chat["max_completion_tokens"]
108
+ elif "max_tokens" in chat:
109
+ anthropic_request["max_tokens"] = chat["max_tokens"]
110
+ else:
111
+ # Anthropic requires max_tokens, set a default
112
+ anthropic_request["max_tokens"] = 4096
113
+
114
+ # Copy other supported parameters
115
+ if "temperature" in chat:
116
+ anthropic_request["temperature"] = chat["temperature"]
117
+ if "top_p" in chat:
118
+ anthropic_request["top_p"] = chat["top_p"]
119
+ if "top_k" in chat:
120
+ anthropic_request["top_k"] = chat["top_k"]
121
+ if "stop" in chat:
122
+ anthropic_request["stop_sequences"] = chat["stop"] if isinstance(chat["stop"], list) else [chat["stop"]]
123
+ if "stream" in chat:
124
+ anthropic_request["stream"] = chat["stream"]
125
+ if "tools" in chat:
126
+ anthropic_tools = []
127
+ for tool in chat["tools"]:
128
+ if tool.get("type") == "function":
129
+ function = tool.get("function", {})
130
+ anthropic_tool = {
131
+ "name": function.get("name"),
132
+ "description": function.get("description"),
133
+ "input_schema": function.get("parameters"),
134
+ }
135
+ anthropic_tools.append(anthropic_tool)
136
+ if anthropic_tools:
137
+ anthropic_request["tools"] = anthropic_tools
138
+ if "tool_choice" in chat:
139
+ anthropic_request["tool_choice"] = chat["tool_choice"]
140
+
141
+ ctx.log(f"POST {self.chat_url}")
142
+ ctx.log(json.dumps(anthropic_request, indent=2))
143
+
144
+ async with aiohttp.ClientSession() as session:
145
+ started_at = time.time()
146
+ async with session.post(
147
+ self.chat_url,
148
+ headers=self.headers,
149
+ data=json.dumps(anthropic_request),
150
+ timeout=aiohttp.ClientTimeout(total=120),
151
+ ) as response:
152
+ return ctx.log_json(self.to_response(await self.response_json(response), chat, started_at))
153
+
154
+ def to_response(self, response, chat, started_at):
155
+ """Convert Anthropic response format to OpenAI-compatible format."""
156
+ # Transform Anthropic response to OpenAI format
157
+ ret = {
158
+ "id": response.get("id", ""),
159
+ "object": "chat.completion",
160
+ "created": int(started_at),
161
+ "model": response.get("model", ""),
162
+ "choices": [],
163
+ "usage": {},
164
+ }
165
+
166
+ # Transform content blocks to message content
167
+ content_parts = []
168
+ thinking_parts = []
169
+ tool_calls = []
170
+
171
+ for block in response.get("content", []):
172
+ if block.get("type") == "text":
173
+ content_parts.append(block.get("text", ""))
174
+ elif block.get("type") == "thinking":
175
+ # Store thinking blocks separately (some models include reasoning)
176
+ thinking_parts.append(block.get("thinking", ""))
177
+ elif block.get("type") == "tool_use":
178
+ tool_call = {
179
+ "id": block.get("id"),
180
+ "type": "function",
181
+ "function": {
182
+ "name": block.get("name"),
183
+ "arguments": json.dumps(block.get("input", {})),
184
+ },
185
+ }
186
+ tool_calls.append(tool_call)
187
+
188
+ # Combine all text content
189
+ message_content = "\n".join(content_parts) if content_parts else ""
190
+
191
+ # Create the choice object
192
+ choice = {
193
+ "index": 0,
194
+ "message": {"role": "assistant", "content": message_content},
195
+ "finish_reason": response.get("stop_reason", "stop"),
196
+ }
197
+
198
+ # Add thinking as metadata if present
199
+ if thinking_parts:
200
+ choice["message"]["thinking"] = "\n".join(thinking_parts)
201
+
202
+ # Add tool_calls if present
203
+ if tool_calls:
204
+ choice["message"]["tool_calls"] = tool_calls
205
+
206
+ ret["choices"].append(choice)
207
+
208
+ # Transform usage
209
+ if "usage" in response:
210
+ usage = response["usage"]
211
+ ret["usage"] = {
212
+ "prompt_tokens": usage.get("input_tokens", 0),
213
+ "completion_tokens": usage.get("output_tokens", 0),
214
+ "total_tokens": usage.get("input_tokens", 0) + usage.get("output_tokens", 0),
215
+ }
216
+
217
+ # Add metadata
218
+ if "metadata" not in ret:
219
+ ret["metadata"] = {}
220
+ ret["metadata"]["duration"] = int((time.time() - started_at) * 1000)
221
+
222
+ if chat is not None and "model" in chat:
223
+ cost = self.model_cost(chat["model"])
224
+ if cost and "input" in cost and "output" in cost:
225
+ ret["metadata"]["pricing"] = f"{cost['input']}/{cost['output']}"
226
+
227
+ return ret
228
+
229
+ ctx.add_provider(AnthropicProvider)
@@ -0,0 +1,155 @@
1
+ import json
2
+ import mimetypes
3
+ import time
4
+
5
+ import aiohttp
6
+
7
+
8
+ def install_chutes(ctx):
9
+ from llms.main import GeneratorBase
10
+
11
+ class ChutesImage(GeneratorBase):
12
+ sdk = "chutes/image"
13
+
14
+ def __init__(self, **kwargs):
15
+ super().__init__(**kwargs)
16
+ self.width = int(kwargs.get("width", 1024))
17
+ self.height = int(kwargs.get("height", 1024))
18
+ self.cfg_scale = float(kwargs.get("cfg_scale", 7.5))
19
+ self.steps = int(kwargs.get("steps", 50))
20
+ self.negative_prompt = kwargs.get("negative_prompt", "blur, distortion, low quality")
21
+ self.gen_url = kwargs.get("api", "https://image.chutes.ai/generate")
22
+ self.model_resolutions = {
23
+ "chutes-hidream": {
24
+ "1:1": "1024x1024",
25
+ "9:16": "768x1360",
26
+ "16:9": "1360x768",
27
+ "3:4": "880x1168",
28
+ "4:3": "1168x880",
29
+ "2:3": "832x1248",
30
+ "3:2": "1248x832",
31
+ }
32
+ }
33
+ self.model_sizes = ["chutes-hunyuan-image-3"]
34
+ self.model_negative_prompt = [
35
+ "chroma",
36
+ "qwen-image-edit-2509",
37
+ "JuggernautXL-Ragnarok",
38
+ "JuggernautXL",
39
+ "Animij",
40
+ "iLustMix",
41
+ ]
42
+
43
+ async def chat(self, chat, provider=None):
44
+ headers = {"Authorization": f"Bearer {self.api_key}"}
45
+ if provider is not None:
46
+ headers["Authorization"] = f"Bearer {provider.api_key}"
47
+ chat["model"] = provider.provider_model(chat["model"]) or chat["model"]
48
+
49
+ aspect_ratio = "1:1"
50
+ if "messages" in chat and len(chat["messages"]) > 0:
51
+ aspect_ratio = chat["messages"][0].get("aspect_ratio", "1:1")
52
+ cfg_scale = self.cfg_scale
53
+ steps = self.steps
54
+ width = self.width
55
+ height = self.height
56
+ if chat["model"] == "chutes-z-image-turbo":
57
+ cfg_scale = min(self.cfg_scale, 5)
58
+ payload = {
59
+ "model": chat["model"],
60
+ "prompt": ctx.last_user_prompt(chat),
61
+ "guidance_scale": cfg_scale,
62
+ "width": width,
63
+ "height": height,
64
+ "num_inference_steps": steps,
65
+ }
66
+ if chat["model"] in self.model_negative_prompt:
67
+ payload["negative_prompt"] = self.negative_prompt
68
+
69
+ image_config = chat.get("image_config", {})
70
+ aspect_ratio = image_config.get("aspect_ratio")
71
+ if aspect_ratio:
72
+ dimension = ctx.app.aspect_ratios.get(aspect_ratio)
73
+ if dimension:
74
+ w, h = dimension.split("×")
75
+ width, height = int(w), int(h)
76
+ payload["width"] = width
77
+ payload["height"] = height
78
+
79
+ if chat["model"] in self.model_resolutions:
80
+ # if models use resolution, remove width and height
81
+ del payload["width"]
82
+ del payload["height"]
83
+ resolution = self.model_resolutions[chat["model"]][aspect_ratio]
84
+ payload["resolution"] = resolution
85
+ elif chat["model"] in self.model_sizes:
86
+ del payload["width"]
87
+ del payload["height"]
88
+ payload["size"] = aspect_ratio
89
+
90
+ gen_url = self.gen_url
91
+ if chat["model"].startswith("chutes-"):
92
+ model = payload["model"]
93
+ gen_url = f"https://{model}.chutes.ai/generate"
94
+ del payload["model"]
95
+
96
+ ctx.log(f"POST {gen_url}")
97
+ ctx.log(json.dumps(payload, indent=2))
98
+ async with aiohttp.ClientSession() as session, session.post(
99
+ gen_url, headers=headers, json=payload
100
+ ) as response:
101
+ if response.status < 300:
102
+ image_data = await response.read()
103
+ content_type = response.headers.get("Content-Type")
104
+ if content_type:
105
+ ext = mimetypes.guess_extension(content_type)
106
+ if ext:
107
+ ext = ext.lstrip(".") # remove leading dot
108
+ if not ext:
109
+ ext = "png"
110
+
111
+ relative_url, info = ctx.save_image_to_cache(
112
+ image_data,
113
+ f"{chat['model']}.{ext}",
114
+ ctx.to_file_info(
115
+ chat,
116
+ {
117
+ "aspect_ratio": aspect_ratio,
118
+ "width": width,
119
+ "height": height,
120
+ "cfg_scale": cfg_scale,
121
+ "steps": steps,
122
+ },
123
+ ),
124
+ )
125
+ return {
126
+ "choices": [
127
+ {
128
+ "message": {
129
+ "role": "assistant",
130
+ "content": self.default_content,
131
+ "images": [
132
+ {
133
+ "type": "image_url",
134
+ "image_url": {
135
+ "url": relative_url,
136
+ },
137
+ }
138
+ ],
139
+ }
140
+ }
141
+ ],
142
+ "created": int(time.time()),
143
+ }
144
+ else:
145
+ text = await response.text()
146
+ try:
147
+ data = json.loads(text)
148
+ ctx.log(data)
149
+ if "detail" in data:
150
+ raise Exception(data["detail"])
151
+ except json.JSONDecodeError:
152
+ pass
153
+ raise Exception(f"Failed to generate image {response.status}")
154
+
155
+ ctx.add_provider(ChutesImage)