llms-py 3.0.11__py3-none-any.whl → 3.0.13__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.
@@ -63,6 +63,50 @@ def install_google(ctx):
63
63
  to[k] = v
64
64
  return to
65
65
 
66
+ def sanitize_parameters(params):
67
+ """Sanitize tool parameters for Google provider."""
68
+
69
+ if not isinstance(params, dict):
70
+ return params
71
+
72
+ # Create a copy to avoid modifying original tool definition
73
+ p = params.copy()
74
+
75
+ # Remove forbidden fields
76
+ for forbidden in ["$schema", "additionalProperties"]:
77
+ if forbidden in p:
78
+ del p[forbidden]
79
+
80
+ # Recursively sanitize known nesting fields
81
+ # 1. Properties (dict of schemas)
82
+ if "properties" in p:
83
+ for k, v in p["properties"].items():
84
+ p["properties"][k] = sanitize_parameters(v)
85
+
86
+ # 2. Items (schema or list of schemas)
87
+ if "items" in p:
88
+ if isinstance(p["items"], list):
89
+ p["items"] = [sanitize_parameters(i) for i in p["items"]]
90
+ else:
91
+ p["items"] = sanitize_parameters(p["items"])
92
+
93
+ # 3. Combinators (list of schemas)
94
+ for combinator in ["allOf", "anyOf", "oneOf"]:
95
+ if combinator in p:
96
+ p[combinator] = [sanitize_parameters(i) for i in p[combinator]]
97
+
98
+ # 4. Not (schema)
99
+ if "not" in p:
100
+ p["not"] = sanitize_parameters(p["not"])
101
+
102
+ # 5. Definitions (dict of schemas)
103
+ for def_key in ["definitions", "$defs"]:
104
+ if def_key in p:
105
+ for k, v in p[def_key].items():
106
+ p[def_key][k] = sanitize_parameters(v)
107
+
108
+ return p
109
+
66
110
  class GoogleProvider(OpenAiCompatible):
67
111
  sdk = "@ai-sdk/google"
68
112
 
@@ -112,11 +156,12 @@ def install_google(ctx):
112
156
  for tool in chat["tools"]:
113
157
  if tool["type"] == "function":
114
158
  f = tool["function"]
159
+
115
160
  function_declarations.append(
116
161
  {
117
162
  "name": f["name"],
118
163
  "description": f.get("description"),
119
- "parameters": f.get("parameters"),
164
+ "parameters": sanitize_parameters(f.get("parameters")),
120
165
  }
121
166
  )
122
167
  elif tool["type"] == "file_search":
@@ -183,13 +228,19 @@ def install_google(ctx):
183
228
  if name:
184
229
  # content is the string response
185
230
  # Some implementations pass the content directly.
186
- # Google docs say: response: { "name": "...", "content": { ... } }
187
- # Actually "response" field in functionResponse is a Struct/Map.
231
+ # Google docs say: response: { "key": "value" }
232
+ try:
233
+ response_data = json.loads(message["content"])
234
+ if not isinstance(response_data, dict):
235
+ response_data = {"content": message["content"]}
236
+ except Exception:
237
+ response_data = {"content": message["content"]}
238
+
188
239
  parts.append(
189
240
  {
190
241
  "functionResponse": {
191
242
  "name": name,
192
- "response": {"name": name, "content": message["content"]},
243
+ "response": response_data,
193
244
  }
194
245
  }
195
246
  )
@@ -18,7 +18,7 @@ const ToolResult = {
18
18
  preview
19
19
  </span>
20
20
  </div>
21
- <div class="not-prose px-3 py-2">
21
+ <div class="not-prose py-2">
22
22
  <pre v-if="ext.prefs.toolFormat !== 'preview'" class="tool-output">{{ origResult }}</pre>
23
23
  <div v-else>
24
24
  <ViewTypes v-if="Array.isArray(result)" :results="result" />
@@ -191,7 +191,7 @@ const Tools = {
191
191
  </div>
192
192
 
193
193
  <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
194
- <div v-for="tool in filteredTools" :key="tool.function.name"
194
+ <div v-for="tool in filteredTools" :key="tool.function.name" :id="'tool-' + tool.function.name"
195
195
  class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
196
196
 
197
197
  <div class="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
@@ -262,7 +262,6 @@ const Tools = {
262
262
  const ctx = inject('ctx')
263
263
 
264
264
  // Execution State
265
- const executingTool = ref(null)
266
265
  const execForm = ref({})
267
266
  const execResult = ref(null)
268
267
  const execError = ref(null)
@@ -270,6 +269,12 @@ const Tools = {
270
269
  const refForm = ref()
271
270
  const refTop = ref()
272
271
 
272
+ const executingTool = computed(() => {
273
+ const tool = ext.prefs.selectedTool
274
+ if (!tool) return null
275
+ return ctx.state.tool.definitions.find(x => x.function.name === tool)
276
+ })
277
+
273
278
  // UI State
274
279
  const expandedDescriptions = ref({})
275
280
 
@@ -284,7 +289,7 @@ const Tools = {
284
289
  })
285
290
 
286
291
  function startExec(tool) {
287
- executingTool.value = tool
292
+ ext.setPrefs({ selectedTool: tool.function.name })
288
293
  execForm.value = {}
289
294
  execResult.value = null
290
295
  execError.value = null
@@ -309,7 +314,7 @@ const Tools = {
309
314
  }
310
315
 
311
316
  function closeExec() {
312
- executingTool.value = null
317
+ ext.setPrefs({ selectedTool: null })
313
318
  execForm.value = {}
314
319
  execResult.value = null
315
320
  execError.value = null
@@ -588,9 +593,16 @@ function useTools(ctx) {
588
593
  Object.assign(toolPageHeaders, components)
589
594
  }
590
595
 
596
+ function selectTool({ group, tool }) {
597
+ ext.setPrefs({ selectedGroup: group, selectedTool: tool })
598
+ }
599
+
591
600
  return {
592
601
  toolPageHeaders,
593
602
  setToolPageHeaders,
603
+ selectTool,
604
+ get selectedGroup() { return ext.prefs.selectedGroup },
605
+ get selectedTool() { return ext.prefs.selectedTool },
594
606
  }
595
607
  }
596
608
 
llms/main.py CHANGED
@@ -43,7 +43,7 @@ try:
43
43
  except ImportError:
44
44
  HAS_PIL = False
45
45
 
46
- VERSION = "3.0.11"
46
+ VERSION = "3.0.13"
47
47
  _ROOT = None
48
48
  DEBUG = os.getenv("DEBUG") == "1"
49
49
  MOCK = os.getenv("MOCK") == "1"
@@ -1513,6 +1513,7 @@ def g_tool_result(result, function_name: Optional[str] = None, function_args: Op
1513
1513
  content = []
1514
1514
  resources = []
1515
1515
  args = function_args or {}
1516
+ _dbg(f"{function_name} tool result type: {type(result)}")
1516
1517
  if isinstance(result, dict):
1517
1518
  text, res = tool_result_part(result, function_name, args)
1518
1519
  if text:
@@ -1555,7 +1556,7 @@ def group_resources(resources: list):
1555
1556
  {"images": [{"type": "image_url", "image_url": {"url": "/image.jpg"}}] }
1556
1557
  """
1557
1558
  grouped = {}
1558
- for res in resources:
1559
+ for res in resources or []:
1559
1560
  type = res.get("type")
1560
1561
  if not type:
1561
1562
  continue
@@ -1628,10 +1629,15 @@ async def g_chat_completion(chat, context=None):
1628
1629
  tool_history = []
1629
1630
  final_response = None
1630
1631
 
1631
- for _ in range(max_iterations):
1632
+ for request_count in range(max_iterations):
1632
1633
  if should_cancel_thread(context):
1633
1634
  return
1634
1635
 
1636
+ if DEBUG:
1637
+ messages = current_chat.get("messages", [])
1638
+ last_message = messages[-1] if messages else None
1639
+ _dbg(f"Provider {provider_name}, request {request_count}:\n{json.dumps(last_message, indent=2)}")
1640
+
1635
1641
  response = await provider.chat(current_chat, context=context)
1636
1642
 
1637
1643
  if should_cancel_thread(context):
llms/ui/ai.mjs CHANGED
@@ -6,7 +6,7 @@ const headers = { 'Accept': 'application/json' }
6
6
  const prefsKey = 'llms.prefs'
7
7
 
8
8
  export const o = {
9
- version: '3.0.11',
9
+ version: '3.0.13',
10
10
  base,
11
11
  prefsKey,
12
12
  welcome: 'Welcome to llms.py',
@@ -56,7 +56,7 @@ function embedHtml(html) {
56
56
  export const TypeText = {
57
57
  template: `
58
58
  <div v-if="text.type === 'text'">
59
- <div v-html="html"></div>
59
+ <div v-html="html?.trim()" class="whitespace-pre-wrap"></div>
60
60
  </div>
61
61
  `,
62
62
  props: {
@@ -72,7 +72,7 @@ export const TypeText = {
72
72
  return ctx.fmt.markdown(props.text.text)
73
73
  } catch (e) {
74
74
  console.error('TypeText: markdown', e)
75
- return `<div class="whitespace-pre-wrap">${props.text.text}</div>`
75
+ return `<div>${props.text.text}</div>`
76
76
  }
77
77
  })
78
78
  return { html }
@@ -950,7 +950,7 @@ export default {
950
950
  ctx.setLeftIcons({
951
951
  chat: {
952
952
  component: {
953
- template: `<svg @click="$ctx.togglePath('/')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 2.19c3.13 0 5.68 2.25 5.68 5s-2.55 5-5.68 5a5.7 5.7 0 0 1-1.89-.29l-.75-.26l-.56.56a14 14 0 0 1-2 1.55a.13.13 0 0 1-.07 0v-.06a6.58 6.58 0 0 0 .15-4.29a5.25 5.25 0 0 1-.55-2.16c0-2.77 2.55-5 5.68-5M8 .94c-3.83 0-6.93 2.81-6.93 6.27a6.4 6.4 0 0 0 .64 2.64a5.53 5.53 0 0 1-.18 3.48a1.32 1.32 0 0 0 2 1.5a15 15 0 0 0 2.16-1.71a6.8 6.8 0 0 0 2.31.36c3.83 0 6.93-2.81 6.93-6.27S11.83.94 8 .94"/><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/></svg>`,
953
+ template: `<svg @click="$ctx.togglePath($ctx.layout.path?.startsWith('/c/') ? $ctx.layout.path : '/')" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 2.19c3.13 0 5.68 2.25 5.68 5s-2.55 5-5.68 5a5.7 5.7 0 0 1-1.89-.29l-.75-.26l-.56.56a14 14 0 0 1-2 1.55a.13.13 0 0 1-.07 0v-.06a6.58 6.58 0 0 0 .15-4.29a5.25 5.25 0 0 1-.55-2.16c0-2.77 2.55-5 5.68-5M8 .94c-3.83 0-6.93 2.81-6.93 6.27a6.4 6.4 0 0 0 .64 2.64a5.53 5.53 0 0 1-.18 3.48a1.32 1.32 0 0 0 2 1.5a15 15 0 0 0 2.16-1.71a6.8 6.8 0 0 0 2.31.36c3.83 0 6.93-2.81 6.93-6.27S11.83.94 8 .94"/><ellipse cx="5.2" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/><ellipse cx="10.8" cy="7.7" fill="currentColor" rx=".8" ry=".75"/></svg>`,
954
954
  },
955
955
  isActive({ path }) {
956
956
  return path === '/' || path.startsWith('/c/')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.11
3
+ Version: 3.0.13
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
@@ -44,6 +44,6 @@ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing mu
44
44
 
45
45
  [llmspy.org](https://llmspy.org)
46
46
 
47
- [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
47
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home-v3.webp?raw=true)](https://llmspy.org)
48
48
 
49
49
  GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -3,7 +3,7 @@ llms/__main__.py,sha256=hrBulHIt3lmPm1BCyAEVtB6DQ0Hvc3gnIddhHCmJasg,151
3
3
  llms/db.py,sha256=oozp5I5lECVO8oZEFwcZl3ES5mARqWeR1BkoqG5kSqM,11687
4
4
  llms/index.html,sha256=nGk1Djtn9p7l6LuKp4Kg0JIB9fCzxtTWXFfmDb4ggpc,1658
5
5
  llms/llms.json,sha256=NEr9kJRkUGZ2YZHbWC-haGPlVVL2Qtnx4kKZENGH1wk,11494
6
- llms/main.py,sha256=IoKt4rGaAKAcc-Y2M-cTbxyIqOy9tmeiWxnrHjzHHv0,173872
6
+ llms/main.py,sha256=Bnn7qUbPskaY1A_mGwB3tfWXn3H-aqTijIIb_M2-Bks,174231
7
7
  llms/providers-extra.json,sha256=_6DmGBiQY9LM6_Y0zOiObYn7ba4g3akSNQfmHcYlENc,11101
8
8
  llms/providers.json,sha256=yjhDurlwo70xqfV0HNLiZaCpw3WvtIgkjoLahQIKX2w,282530
9
9
  llms/extensions/analytics/ui/index.mjs,sha256=m1XwaqYCLwK267JAUCAltkN_nOXep0GxfpvGNS5i4_w,69547
@@ -131,7 +131,7 @@ llms/extensions/providers/__init__.py,sha256=C5zOBQEOB2L96rAZdjV42fPVk_dZxSh2Dv3
131
131
  llms/extensions/providers/anthropic.py,sha256=V9mechnhyoX-5Z5AkwyQ-UzLax6cqG7j7GLvGTZF9no,10941
132
132
  llms/extensions/providers/cerebras.py,sha256=HaeFW0GwbD6V6Zrrwqyv78kQb0VXg9oHmykvJfIOOYE,1417
133
133
  llms/extensions/providers/chutes.py,sha256=5ZrfbqoOhgzKLQy_qULcp4jlvW5WXPR0jP9kN2Jzb9g,6229
134
- llms/extensions/providers/google.py,sha256=9P90bEefRA18tpjfEuAz1T5YpwzdOngrTFhw-LI3eXg,24434
134
+ llms/extensions/providers/google.py,sha256=oCCTE2KAw-WWE2v14XpKzgAMdFIWbjTBoa6GWuqT4dw,26215
135
135
  llms/extensions/providers/nvidia.py,sha256=C6cwqn3EufYDfRIgbc8MDkQNyD6w3c7hbjfYaHJSDik,4279
136
136
  llms/extensions/providers/openai.py,sha256=hkE-LVsw6M92_qEbpayuPo17Z1OWKHe7lm2wduLMng8,6138
137
137
  llms/extensions/providers/openrouter.py,sha256=5SfCJKo1aGKoDGez6HXYQe9elMMo9sSEDFqqdxamAgA,3330
@@ -141,9 +141,9 @@ llms/extensions/system_prompts/__init__.py,sha256=TZy1CS2dPkBNBA_Ovf9BlVetZqTt2N
141
141
  llms/extensions/system_prompts/ui/index.mjs,sha256=_pVCreAebSzE9dzcHF2kiYODwP-fDHCqtUQB-X5Io9Q,12107
142
142
  llms/extensions/system_prompts/ui/prompts.json,sha256=t5DD3bird-87wFa4OlW-bC2wdoYDrVzfyc8TO5OaotI,128489
143
143
  llms/extensions/tools/__init__.py,sha256=u76604Cn_sRFQRqeA_pkVEty27V688Mt9Z7Kh63yDr8,4825
144
- llms/extensions/tools/ui/index.mjs,sha256=IbGB2FQJ5VL4a8arwoR9C79vUCNrz8VIyQnHZ4vxU9o,34486
144
+ llms/extensions/tools/ui/index.mjs,sha256=-Rby2y1Rx83JFcqYqn86uYZjVd_WlF4wSajZ1M31LWg,34997
145
145
  llms/ui/App.mjs,sha256=CoUzO9mV__-jV19NKHYIbwHsjWMnO11jyNSbnJhe1gQ,7486
146
- llms/ui/ai.mjs,sha256=GVyxu3Thsuck0b1Akvqk2c5w-maSzONU06QN-TUWoJk,6541
146
+ llms/ui/ai.mjs,sha256=wBkkhbg60HS6TGc2wkSg6pmJoxUwvykLZYtzVNh47eM,6541
147
147
  llms/ui/app.css,sha256=vfXErYVdVlE3pL8oZ-2G_OC-_reJzmaL0p91EVv48uo,186490
148
148
  llms/ui/ctx.mjs,sha256=X4scgXEQ9bMUfQl36sM4A3o2Ufad3LRwItxfmSu1xwc,12838
149
149
  llms/ui/fav.svg,sha256=_R6MFeXl6wBFT0lqcUxYQIDWgm246YH_3hSTW0oO8qw,734
@@ -166,12 +166,12 @@ llms/ui/lib/vue.mjs,sha256=75FuLhUTPk19sncwNIrm0BGEL0_Qw298-_v01fPWYoI,542872
166
166
  llms/ui/modules/icons.mjs,sha256=LGcH0ys0QLS2ZKCO42qHpwPYbBV_EssoWLezU4XZEzU,27751
167
167
  llms/ui/modules/layout.mjs,sha256=8pAxs8bedQI3b3eRA9nrfpLZznLmrpp4BZvigYAQjpQ,12572
168
168
  llms/ui/modules/model-selector.mjs,sha256=6U4rAZ7vmQELFRQGWk4YEtq02v3lyHdMq6yUOp-ArXg,43184
169
- llms/ui/modules/chat/ChatBody.mjs,sha256=5yWjo6tWmcKidDpRvKFeHqx3lXO3DB-3rTyXY72gB4U,49122
169
+ llms/ui/modules/chat/ChatBody.mjs,sha256=L2925VM-LmrvWM1MAIBN9dGTPfyFJUIN4QF54TBtROU,49130
170
170
  llms/ui/modules/chat/SettingsDialog.mjs,sha256=HMBJTwrapKrRIAstIIqp0QlJL5O-ho4hzgvfagPfsX8,19930
171
- llms/ui/modules/chat/index.mjs,sha256=lfSbERMaM3bLsKhdJJPWwL4-FGr8U_ftlvqW5vC3T1s,39762
172
- llms_py-3.0.11.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
- llms_py-3.0.11.dist-info/METADATA,sha256=90LpikNrckf1EaRrQHKGLOGobshThyJsRcX75csIs1Y,2192
174
- llms_py-3.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
- llms_py-3.0.11.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
- llms_py-3.0.11.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
- llms_py-3.0.11.dist-info/RECORD,,
171
+ llms/ui/modules/chat/index.mjs,sha256=FZ0fJ53JXQnTDiSOFhyGp3J5OMuEDQ2YZJGKC5VwS0E,39819
172
+ llms_py-3.0.13.dist-info/licenses/LICENSE,sha256=bus9cuAOWeYqBk2OuhSABVV1P4z7hgrEFISpyda_H5w,1532
173
+ llms_py-3.0.13.dist-info/METADATA,sha256=dIAc0K49R183BqM2sAS5SC3DFCEU6BciWnTPOv1sKdI,2195
174
+ llms_py-3.0.13.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
175
+ llms_py-3.0.13.dist-info/entry_points.txt,sha256=WswyE7PfnkZMIxboC-MS6flBD6wm-CYU7JSUnMhqMfM,40
176
+ llms_py-3.0.13.dist-info/top_level.txt,sha256=gC7hk9BKSeog8gyg-EM_g2gxm1mKHwFRfK-10BxOsa4,5
177
+ llms_py-3.0.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5