prompture 0.0.47__py3-none-any.whl → 0.0.47.dev1__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.
- prompture/_version.py +2 -2
- prompture/async_conversation.py +2 -87
- prompture/conversation.py +2 -87
- prompture/drivers/async_grok_driver.py +9 -23
- prompture/drivers/async_groq_driver.py +9 -23
- prompture/drivers/async_lmstudio_driver.py +2 -10
- prompture/drivers/async_moonshot_driver.py +12 -32
- prompture/drivers/async_openrouter_driver.py +17 -43
- prompture/drivers/grok_driver.py +9 -23
- prompture/drivers/groq_driver.py +9 -23
- prompture/drivers/lmstudio_driver.py +2 -11
- prompture/drivers/moonshot_driver.py +12 -32
- prompture/drivers/openrouter_driver.py +10 -34
- prompture/tools_schema.py +0 -22
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/METADATA +2 -35
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/RECORD +20 -21
- prompture/simulated_tools.py +0 -115
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/WHEEL +0 -0
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/entry_points.txt +0 -0
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/licenses/LICENSE +0 -0
- {prompture-0.0.47.dist-info → prompture-0.0.47.dev1.dist-info}/top_level.txt +0 -0
|
@@ -122,17 +122,8 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
122
122
|
"model_name": model,
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
reasoning_content = message.get("reasoning_content")
|
|
128
|
-
|
|
129
|
-
if not text and reasoning_content:
|
|
130
|
-
text = reasoning_content
|
|
131
|
-
|
|
132
|
-
result: dict[str, Any] = {"text": text, "meta": meta}
|
|
133
|
-
if reasoning_content is not None:
|
|
134
|
-
result["reasoning_content"] = reasoning_content
|
|
135
|
-
return result
|
|
125
|
+
text = resp["choices"][0]["message"]["content"]
|
|
126
|
+
return {"text": text, "meta": meta}
|
|
136
127
|
|
|
137
128
|
# ------------------------------------------------------------------
|
|
138
129
|
# Tool use
|
|
@@ -205,23 +196,18 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
205
196
|
args = json.loads(tc["function"]["arguments"])
|
|
206
197
|
except (json.JSONDecodeError, TypeError):
|
|
207
198
|
args = {}
|
|
208
|
-
tool_calls_out.append(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
)
|
|
199
|
+
tool_calls_out.append({
|
|
200
|
+
"id": tc["id"],
|
|
201
|
+
"name": tc["function"]["name"],
|
|
202
|
+
"arguments": args,
|
|
203
|
+
})
|
|
215
204
|
|
|
216
|
-
|
|
205
|
+
return {
|
|
217
206
|
"text": text,
|
|
218
207
|
"meta": meta,
|
|
219
208
|
"tool_calls": tool_calls_out,
|
|
220
209
|
"stop_reason": stop_reason,
|
|
221
210
|
}
|
|
222
|
-
if choice["message"].get("reasoning_content") is not None:
|
|
223
|
-
result["reasoning_content"] = choice["message"]["reasoning_content"]
|
|
224
|
-
return result
|
|
225
211
|
|
|
226
212
|
# ------------------------------------------------------------------
|
|
227
213
|
# Streaming
|
|
@@ -252,25 +238,21 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
252
238
|
data["temperature"] = opts["temperature"]
|
|
253
239
|
|
|
254
240
|
full_text = ""
|
|
255
|
-
full_reasoning = ""
|
|
256
241
|
prompt_tokens = 0
|
|
257
242
|
completion_tokens = 0
|
|
258
243
|
|
|
259
|
-
async with (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
timeout=120,
|
|
267
|
-
) as response,
|
|
268
|
-
):
|
|
244
|
+
async with httpx.AsyncClient() as client, client.stream(
|
|
245
|
+
"POST",
|
|
246
|
+
f"{self.base_url}/chat/completions",
|
|
247
|
+
headers=self.headers,
|
|
248
|
+
json=data,
|
|
249
|
+
timeout=120,
|
|
250
|
+
) as response:
|
|
269
251
|
response.raise_for_status()
|
|
270
252
|
async for line in response.aiter_lines():
|
|
271
253
|
if not line or not line.startswith("data: "):
|
|
272
254
|
continue
|
|
273
|
-
payload = line[len("data: ")
|
|
255
|
+
payload = line[len("data: "):]
|
|
274
256
|
if payload.strip() == "[DONE]":
|
|
275
257
|
break
|
|
276
258
|
try:
|
|
@@ -288,11 +270,6 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
288
270
|
if choices:
|
|
289
271
|
delta = choices[0].get("delta", {})
|
|
290
272
|
content = delta.get("content", "")
|
|
291
|
-
reasoning_chunk = delta.get("reasoning_content") or ""
|
|
292
|
-
if reasoning_chunk:
|
|
293
|
-
full_reasoning += reasoning_chunk
|
|
294
|
-
if not content and reasoning_chunk:
|
|
295
|
-
content = reasoning_chunk
|
|
296
273
|
if content:
|
|
297
274
|
full_text += content
|
|
298
275
|
yield {"type": "delta", "text": content}
|
|
@@ -300,7 +277,7 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
300
277
|
total_tokens = prompt_tokens + completion_tokens
|
|
301
278
|
total_cost = self._calculate_cost("openrouter", model, prompt_tokens, completion_tokens)
|
|
302
279
|
|
|
303
|
-
|
|
280
|
+
yield {
|
|
304
281
|
"type": "done",
|
|
305
282
|
"text": full_text,
|
|
306
283
|
"meta": {
|
|
@@ -312,6 +289,3 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
|
|
|
312
289
|
"model_name": model,
|
|
313
290
|
},
|
|
314
291
|
}
|
|
315
|
-
if full_reasoning:
|
|
316
|
-
done_chunk["reasoning_content"] = full_reasoning
|
|
317
|
-
yield done_chunk
|
prompture/drivers/grok_driver.py
CHANGED
|
@@ -154,17 +154,8 @@ class GrokDriver(CostMixin, Driver):
|
|
|
154
154
|
"model_name": model,
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
reasoning_content = message.get("reasoning_content")
|
|
160
|
-
|
|
161
|
-
if not text and reasoning_content:
|
|
162
|
-
text = reasoning_content
|
|
163
|
-
|
|
164
|
-
result: dict[str, Any] = {"text": text, "meta": meta}
|
|
165
|
-
if reasoning_content is not None:
|
|
166
|
-
result["reasoning_content"] = reasoning_content
|
|
167
|
-
return result
|
|
157
|
+
text = resp["choices"][0]["message"]["content"]
|
|
158
|
+
return {"text": text, "meta": meta}
|
|
168
159
|
|
|
169
160
|
# ------------------------------------------------------------------
|
|
170
161
|
# Tool use
|
|
@@ -236,20 +227,15 @@ class GrokDriver(CostMixin, Driver):
|
|
|
236
227
|
args = json.loads(tc["function"]["arguments"])
|
|
237
228
|
except (json.JSONDecodeError, TypeError):
|
|
238
229
|
args = {}
|
|
239
|
-
tool_calls_out.append(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
result: dict[str, Any] = {
|
|
230
|
+
tool_calls_out.append({
|
|
231
|
+
"id": tc["id"],
|
|
232
|
+
"name": tc["function"]["name"],
|
|
233
|
+
"arguments": args,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
return {
|
|
248
237
|
"text": text,
|
|
249
238
|
"meta": meta,
|
|
250
239
|
"tool_calls": tool_calls_out,
|
|
251
240
|
"stop_reason": stop_reason,
|
|
252
241
|
}
|
|
253
|
-
if choice["message"].get("reasoning_content") is not None:
|
|
254
|
-
result["reasoning_content"] = choice["message"]["reasoning_content"]
|
|
255
|
-
return result
|
prompture/drivers/groq_driver.py
CHANGED
|
@@ -122,16 +122,8 @@ class GroqDriver(CostMixin, Driver):
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
# Extract generated text
|
|
125
|
-
text = resp.choices[0].message.content
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if not text and reasoning_content:
|
|
129
|
-
text = reasoning_content
|
|
130
|
-
|
|
131
|
-
result: dict[str, Any] = {"text": text, "meta": meta}
|
|
132
|
-
if reasoning_content is not None:
|
|
133
|
-
result["reasoning_content"] = reasoning_content
|
|
134
|
-
return result
|
|
125
|
+
text = resp.choices[0].message.content
|
|
126
|
+
return {"text": text, "meta": meta}
|
|
135
127
|
|
|
136
128
|
# ------------------------------------------------------------------
|
|
137
129
|
# Tool use
|
|
@@ -194,21 +186,15 @@ class GroqDriver(CostMixin, Driver):
|
|
|
194
186
|
args = json.loads(tc.function.arguments)
|
|
195
187
|
except (json.JSONDecodeError, TypeError):
|
|
196
188
|
args = {}
|
|
197
|
-
tool_calls_out.append(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
result: dict[str, Any] = {
|
|
189
|
+
tool_calls_out.append({
|
|
190
|
+
"id": tc.id,
|
|
191
|
+
"name": tc.function.name,
|
|
192
|
+
"arguments": args,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return {
|
|
206
196
|
"text": text,
|
|
207
197
|
"meta": meta,
|
|
208
198
|
"tool_calls": tool_calls_out,
|
|
209
199
|
"stop_reason": stop_reason,
|
|
210
200
|
}
|
|
211
|
-
reasoning_content = getattr(choice.message, "reasoning_content", None)
|
|
212
|
-
if reasoning_content is not None:
|
|
213
|
-
result["reasoning_content"] = reasoning_content
|
|
214
|
-
return result
|
|
@@ -123,13 +123,7 @@ class LMStudioDriver(Driver):
|
|
|
123
123
|
raise RuntimeError(f"LM Studio request failed: {e}") from e
|
|
124
124
|
|
|
125
125
|
# Extract text
|
|
126
|
-
|
|
127
|
-
text = message.get("content") or ""
|
|
128
|
-
reasoning_content = message.get("reasoning_content")
|
|
129
|
-
|
|
130
|
-
# Reasoning models (e.g. DeepSeek R1) may return content in reasoning_content
|
|
131
|
-
if not text and reasoning_content:
|
|
132
|
-
text = reasoning_content
|
|
126
|
+
text = response_data["choices"][0]["message"]["content"]
|
|
133
127
|
|
|
134
128
|
# Meta info
|
|
135
129
|
usage = response_data.get("usage", {})
|
|
@@ -146,10 +140,7 @@ class LMStudioDriver(Driver):
|
|
|
146
140
|
"model_name": merged_options.get("model", self.model),
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
if reasoning_content is not None:
|
|
151
|
-
result["reasoning_content"] = reasoning_content
|
|
152
|
-
return result
|
|
143
|
+
return {"text": text, "meta": meta}
|
|
153
144
|
|
|
154
145
|
# -- Model management (LM Studio 0.4.0+) ----------------------------------
|
|
155
146
|
|
|
@@ -228,11 +228,10 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
228
228
|
|
|
229
229
|
message = resp["choices"][0]["message"]
|
|
230
230
|
text = message.get("content") or ""
|
|
231
|
-
reasoning_content = message.get("reasoning_content")
|
|
232
231
|
|
|
233
232
|
# Reasoning models may return content in reasoning_content when content is empty
|
|
234
|
-
if not text and reasoning_content:
|
|
235
|
-
text = reasoning_content
|
|
233
|
+
if not text and message.get("reasoning_content"):
|
|
234
|
+
text = message["reasoning_content"]
|
|
236
235
|
|
|
237
236
|
# Structured output fallback: if we used json_schema mode and got an
|
|
238
237
|
# empty response, retry with json_object mode and schema in the prompt.
|
|
@@ -276,9 +275,8 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
276
275
|
resp = fb_resp
|
|
277
276
|
fb_message = fb_resp["choices"][0]["message"]
|
|
278
277
|
text = fb_message.get("content") or ""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
text = reasoning_content
|
|
278
|
+
if not text and fb_message.get("reasoning_content"):
|
|
279
|
+
text = fb_message["reasoning_content"]
|
|
282
280
|
|
|
283
281
|
total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
|
|
284
282
|
|
|
@@ -291,10 +289,7 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
291
289
|
"model_name": model,
|
|
292
290
|
}
|
|
293
291
|
|
|
294
|
-
|
|
295
|
-
if reasoning_content is not None:
|
|
296
|
-
result["reasoning_content"] = reasoning_content
|
|
297
|
-
return result
|
|
292
|
+
return {"text": text, "meta": meta}
|
|
298
293
|
|
|
299
294
|
# ------------------------------------------------------------------
|
|
300
295
|
# Tool use
|
|
@@ -369,12 +364,11 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
369
364
|
}
|
|
370
365
|
|
|
371
366
|
choice = resp["choices"][0]
|
|
372
|
-
|
|
373
|
-
text = message.get("content") or ""
|
|
367
|
+
text = choice["message"].get("content") or ""
|
|
374
368
|
stop_reason = choice.get("finish_reason")
|
|
375
369
|
|
|
376
370
|
tool_calls_out: list[dict[str, Any]] = []
|
|
377
|
-
for tc in message.get("tool_calls", []):
|
|
371
|
+
for tc in choice["message"].get("tool_calls", []):
|
|
378
372
|
try:
|
|
379
373
|
args = json.loads(tc["function"]["arguments"])
|
|
380
374
|
except (json.JSONDecodeError, TypeError):
|
|
@@ -387,21 +381,13 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
387
381
|
}
|
|
388
382
|
)
|
|
389
383
|
|
|
390
|
-
|
|
384
|
+
return {
|
|
391
385
|
"text": text,
|
|
392
386
|
"meta": meta,
|
|
393
387
|
"tool_calls": tool_calls_out,
|
|
394
388
|
"stop_reason": stop_reason,
|
|
395
389
|
}
|
|
396
390
|
|
|
397
|
-
# Preserve reasoning_content for reasoning models so the
|
|
398
|
-
# conversation loop can include it when sending the assistant
|
|
399
|
-
# message back (Moonshot requires it on subsequent requests).
|
|
400
|
-
if message.get("reasoning_content") is not None:
|
|
401
|
-
result["reasoning_content"] = message["reasoning_content"]
|
|
402
|
-
|
|
403
|
-
return result
|
|
404
|
-
|
|
405
391
|
# ------------------------------------------------------------------
|
|
406
392
|
# Streaming
|
|
407
393
|
# ------------------------------------------------------------------
|
|
@@ -444,7 +430,6 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
444
430
|
response.raise_for_status()
|
|
445
431
|
|
|
446
432
|
full_text = ""
|
|
447
|
-
full_reasoning = ""
|
|
448
433
|
prompt_tokens = 0
|
|
449
434
|
completion_tokens = 0
|
|
450
435
|
|
|
@@ -468,11 +453,9 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
468
453
|
if choices:
|
|
469
454
|
delta = choices[0].get("delta", {})
|
|
470
455
|
content = delta.get("content") or ""
|
|
471
|
-
|
|
472
|
-
if
|
|
473
|
-
|
|
474
|
-
if not content and reasoning_chunk:
|
|
475
|
-
content = reasoning_chunk
|
|
456
|
+
# Reasoning models stream thinking via reasoning_content
|
|
457
|
+
if not content:
|
|
458
|
+
content = delta.get("reasoning_content") or ""
|
|
476
459
|
if content:
|
|
477
460
|
full_text += content
|
|
478
461
|
yield {"type": "delta", "text": content}
|
|
@@ -480,7 +463,7 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
480
463
|
total_tokens = prompt_tokens + completion_tokens
|
|
481
464
|
total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
|
|
482
465
|
|
|
483
|
-
|
|
466
|
+
yield {
|
|
484
467
|
"type": "done",
|
|
485
468
|
"text": full_text,
|
|
486
469
|
"meta": {
|
|
@@ -492,6 +475,3 @@ class MoonshotDriver(CostMixin, Driver):
|
|
|
492
475
|
"model_name": model,
|
|
493
476
|
},
|
|
494
477
|
}
|
|
495
|
-
if full_reasoning:
|
|
496
|
-
done_chunk["reasoning_content"] = full_reasoning
|
|
497
|
-
yield done_chunk
|
|
@@ -181,18 +181,8 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
181
181
|
"model_name": model,
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
reasoning_content = message.get("reasoning_content")
|
|
187
|
-
|
|
188
|
-
# Reasoning models may return content in reasoning_content when content is empty
|
|
189
|
-
if not text and reasoning_content:
|
|
190
|
-
text = reasoning_content
|
|
191
|
-
|
|
192
|
-
result: dict[str, Any] = {"text": text, "meta": meta}
|
|
193
|
-
if reasoning_content is not None:
|
|
194
|
-
result["reasoning_content"] = reasoning_content
|
|
195
|
-
return result
|
|
184
|
+
text = resp["choices"][0]["message"]["content"]
|
|
185
|
+
return {"text": text, "meta": meta}
|
|
196
186
|
|
|
197
187
|
# ------------------------------------------------------------------
|
|
198
188
|
# Tool use
|
|
@@ -267,23 +257,18 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
267
257
|
args = json.loads(tc["function"]["arguments"])
|
|
268
258
|
except (json.JSONDecodeError, TypeError):
|
|
269
259
|
args = {}
|
|
270
|
-
tool_calls_out.append(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
)
|
|
260
|
+
tool_calls_out.append({
|
|
261
|
+
"id": tc["id"],
|
|
262
|
+
"name": tc["function"]["name"],
|
|
263
|
+
"arguments": args,
|
|
264
|
+
})
|
|
277
265
|
|
|
278
|
-
|
|
266
|
+
return {
|
|
279
267
|
"text": text,
|
|
280
268
|
"meta": meta,
|
|
281
269
|
"tool_calls": tool_calls_out,
|
|
282
270
|
"stop_reason": stop_reason,
|
|
283
271
|
}
|
|
284
|
-
if choice["message"].get("reasoning_content") is not None:
|
|
285
|
-
result["reasoning_content"] = choice["message"]["reasoning_content"]
|
|
286
|
-
return result
|
|
287
272
|
|
|
288
273
|
# ------------------------------------------------------------------
|
|
289
274
|
# Streaming
|
|
@@ -326,14 +311,13 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
326
311
|
response.raise_for_status()
|
|
327
312
|
|
|
328
313
|
full_text = ""
|
|
329
|
-
full_reasoning = ""
|
|
330
314
|
prompt_tokens = 0
|
|
331
315
|
completion_tokens = 0
|
|
332
316
|
|
|
333
317
|
for line in response.iter_lines(decode_unicode=True):
|
|
334
318
|
if not line or not line.startswith("data: "):
|
|
335
319
|
continue
|
|
336
|
-
payload = line[len("data: ")
|
|
320
|
+
payload = line[len("data: "):]
|
|
337
321
|
if payload.strip() == "[DONE]":
|
|
338
322
|
break
|
|
339
323
|
try:
|
|
@@ -351,11 +335,6 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
351
335
|
if choices:
|
|
352
336
|
delta = choices[0].get("delta", {})
|
|
353
337
|
content = delta.get("content", "")
|
|
354
|
-
reasoning_chunk = delta.get("reasoning_content") or ""
|
|
355
|
-
if reasoning_chunk:
|
|
356
|
-
full_reasoning += reasoning_chunk
|
|
357
|
-
if not content and reasoning_chunk:
|
|
358
|
-
content = reasoning_chunk
|
|
359
338
|
if content:
|
|
360
339
|
full_text += content
|
|
361
340
|
yield {"type": "delta", "text": content}
|
|
@@ -363,7 +342,7 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
363
342
|
total_tokens = prompt_tokens + completion_tokens
|
|
364
343
|
total_cost = self._calculate_cost("openrouter", model, prompt_tokens, completion_tokens)
|
|
365
344
|
|
|
366
|
-
|
|
345
|
+
yield {
|
|
367
346
|
"type": "done",
|
|
368
347
|
"text": full_text,
|
|
369
348
|
"meta": {
|
|
@@ -375,6 +354,3 @@ class OpenRouterDriver(CostMixin, Driver):
|
|
|
375
354
|
"model_name": model,
|
|
376
355
|
},
|
|
377
356
|
}
|
|
378
|
-
if full_reasoning:
|
|
379
|
-
done_chunk["reasoning_content"] = full_reasoning
|
|
380
|
-
yield done_chunk
|
prompture/tools_schema.py
CHANGED
|
@@ -109,24 +109,6 @@ class ToolDefinition:
|
|
|
109
109
|
"input_schema": self.parameters,
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
def to_prompt_format(self) -> str:
|
|
113
|
-
"""Plain-text description suitable for prompt-based tool calling."""
|
|
114
|
-
lines = [f"Tool: {self.name}", f" Description: {self.description}", " Parameters:"]
|
|
115
|
-
props = self.parameters.get("properties", {})
|
|
116
|
-
required = set(self.parameters.get("required", []))
|
|
117
|
-
if not props:
|
|
118
|
-
lines.append(" (none)")
|
|
119
|
-
else:
|
|
120
|
-
for pname, pschema in props.items():
|
|
121
|
-
ptype = pschema.get("type", "string")
|
|
122
|
-
req_label = "required" if pname in required else "optional"
|
|
123
|
-
desc = pschema.get("description", "")
|
|
124
|
-
line = f" - {pname} ({ptype}, {req_label})"
|
|
125
|
-
if desc:
|
|
126
|
-
line += f": {desc}"
|
|
127
|
-
lines.append(line)
|
|
128
|
-
return "\n".join(lines)
|
|
129
|
-
|
|
130
112
|
|
|
131
113
|
def tool_from_function(
|
|
132
114
|
fn: Callable[..., Any], *, name: str | None = None, description: str | None = None
|
|
@@ -262,10 +244,6 @@ class ToolRegistry:
|
|
|
262
244
|
def to_anthropic_format(self) -> list[dict[str, Any]]:
|
|
263
245
|
return [td.to_anthropic_format() for td in self._tools.values()]
|
|
264
246
|
|
|
265
|
-
def to_prompt_format(self) -> str:
|
|
266
|
-
"""Join all tool descriptions into a single plain-text block."""
|
|
267
|
-
return "\n\n".join(td.to_prompt_format() for td in self._tools.values())
|
|
268
|
-
|
|
269
247
|
# ------------------------------------------------------------------
|
|
270
248
|
# Execution
|
|
271
249
|
# ------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prompture
|
|
3
|
-
Version: 0.0.47
|
|
3
|
+
Version: 0.0.47.dev1
|
|
4
4
|
Summary: Ask LLMs to return structured JSON and run cross-model tests. API-first.
|
|
5
5
|
Author-email: Juan Denis <juan@vene.co>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -83,7 +83,7 @@ print(person.name) # Maria
|
|
|
83
83
|
- **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
|
|
84
84
|
- **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
|
|
85
85
|
- **Conversations** — Stateful multi-turn sessions with sync and async support
|
|
86
|
-
- **Tool use** — Function calling and streaming across supported providers
|
|
86
|
+
- **Tool use** — Function calling and streaming across supported providers
|
|
87
87
|
- **Caching** — Built-in response cache with memory, SQLite, and Redis backends
|
|
88
88
|
- **Plugin system** — Register custom drivers via entry points
|
|
89
89
|
- **Usage tracking** — Token counts and cost calculation on every call
|
|
@@ -296,39 +296,6 @@ response = conv.send("What is the capital of France?")
|
|
|
296
296
|
follow_up = conv.send("What about Germany?") # retains context
|
|
297
297
|
```
|
|
298
298
|
|
|
299
|
-
### Tool Use
|
|
300
|
-
|
|
301
|
-
Register Python functions as tools the LLM can call during a conversation:
|
|
302
|
-
|
|
303
|
-
```python
|
|
304
|
-
from prompture import Conversation, ToolRegistry
|
|
305
|
-
|
|
306
|
-
registry = ToolRegistry()
|
|
307
|
-
|
|
308
|
-
@registry.tool
|
|
309
|
-
def get_weather(city: str, units: str = "celsius") -> str:
|
|
310
|
-
"""Get the current weather for a city."""
|
|
311
|
-
return f"Weather in {city}: 22 {units}"
|
|
312
|
-
|
|
313
|
-
conv = Conversation("openai/gpt-4", tools=registry)
|
|
314
|
-
result = conv.ask("What's the weather in London?")
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
For models without native function calling (Ollama, LM Studio, etc.), Prompture automatically simulates tool use by describing tools in the prompt and parsing structured JSON responses:
|
|
318
|
-
|
|
319
|
-
```python
|
|
320
|
-
# Auto-detect: uses native tool calling if available, simulation otherwise
|
|
321
|
-
conv = Conversation("ollama/llama3.1:8b", tools=registry, simulated_tools="auto")
|
|
322
|
-
|
|
323
|
-
# Force simulation even on capable models
|
|
324
|
-
conv = Conversation("openai/gpt-4", tools=registry, simulated_tools=True)
|
|
325
|
-
|
|
326
|
-
# Disable tool use entirely
|
|
327
|
-
conv = Conversation("openai/gpt-4", tools=registry, simulated_tools=False)
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
The simulation loop describes tools in the system prompt, asks the model to respond with JSON (`tool_call` or `final_answer`), executes tools, and feeds results back — all transparent to the caller.
|
|
331
|
-
|
|
332
299
|
### Model Discovery
|
|
333
300
|
|
|
334
301
|
Auto-detect available models from configured providers:
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
prompture/__init__.py,sha256=cJnkefDpiyFbU77juw4tXPdKJQWoJ-c6XBFt2v-e5Q4,7455
|
|
2
|
-
prompture/_version.py,sha256=
|
|
2
|
+
prompture/_version.py,sha256=m4L2kLiZktyjsO5dlv6VYgYlU0JGlYNdugMyoHzVbXk,719
|
|
3
3
|
prompture/agent.py,sha256=-8qdo_Lz20GGssCe5B_QPxb5Kct71YtKHh5vZgrSYik,34748
|
|
4
4
|
prompture/agent_types.py,sha256=Icl16PQI-ThGLMFCU43adtQA6cqETbsPn4KssKBI4xc,4664
|
|
5
5
|
prompture/async_agent.py,sha256=_6_IRb-LGzZxGxfPVy43SIWByUoQfN-5XnUWahVP6r8,33110
|
|
6
|
-
prompture/async_conversation.py,sha256=
|
|
6
|
+
prompture/async_conversation.py,sha256=m9sdKBu1wxo5veGwO6g6Zvf1sBzpuxP-mSIEeNKlBjQ,31155
|
|
7
7
|
prompture/async_core.py,sha256=hbRXLvsBJv3JAnUwGZbazsL6x022FrsJU6swmZolgxY,29745
|
|
8
8
|
prompture/async_driver.py,sha256=4VQ9Q_tI6Ufw6W1CYJ5j8hVtgVdqFGuk6e2tLaSceWE,8581
|
|
9
9
|
prompture/async_groups.py,sha256=pceKrt0UayQjMLFs1dFGoxOHpgD948aEjIY61r608C4,22459
|
|
10
10
|
prompture/cache.py,sha256=4dfQDMsEZ9JMQDXLOkiugPmmMJQIfKVE8rTAKDH4oL8,14401
|
|
11
11
|
prompture/callbacks.py,sha256=JPDqWGzPIzv44l54ocmezlYVBnbKPDEEXRrLdluWGAo,1731
|
|
12
12
|
prompture/cli.py,sha256=tNiIddRmgC1BomjY5O1VVVAwvqHVzF8IHmQrM-cG2wQ,2902
|
|
13
|
-
prompture/conversation.py,sha256=
|
|
13
|
+
prompture/conversation.py,sha256=kBflwh7Qmw1I_jcUGyV36oskdVz4SYDSw_dCjemRRRc,32756
|
|
14
14
|
prompture/core.py,sha256=5FHwX7fNPwFHMbFCMvV-RH7LpPpTToLAmcyDnKbrN0E,57202
|
|
15
15
|
prompture/cost_mixin.py,sha256=Qx7gPgPsWgTHiaFeI7q_p9cfe95ccjgN8Mi56d_AVX0,4563
|
|
16
16
|
prompture/discovery.py,sha256=K-svbO-qJraHinCbFVS64vEo5McWX5pURv26ZMmuL6U,10295
|
|
@@ -29,9 +29,8 @@ prompture/serialization.py,sha256=m4cdAQJspitMcfwRgecElkY2SBt3BjEwubbhS3W-0s0,74
|
|
|
29
29
|
prompture/server.py,sha256=W6Kn6Et8nG5twXjD2wKn_N9yplGjz5Z-2naeI_UPd1Y,6198
|
|
30
30
|
prompture/session.py,sha256=FldK3cKq_jO0-beukVOhIiwsYWb6U_lLBlAERx95aaM,3821
|
|
31
31
|
prompture/settings.py,sha256=2cTuko8PLhq0SbBMtqmjBgzl9jv6SgoXeaUEhmm4G4Y,2562
|
|
32
|
-
prompture/simulated_tools.py,sha256=oL6W6hAEKXZHBfb8b-UDPfm3V4nSqXu7eG8IpvwtqKg,3901
|
|
33
32
|
prompture/tools.py,sha256=PmFbGHTWYWahpJOG6BLlM0Y-EG6S37IFW57C-8GdsXo,36449
|
|
34
|
-
prompture/tools_schema.py,sha256=
|
|
33
|
+
prompture/tools_schema.py,sha256=c1ag6kyIGgZxWbZRsaHl72cAelb34J_JomyW1h5Atw0,7964
|
|
35
34
|
prompture/validator.py,sha256=FY_VjIVEbjG2nwzh-r6l23Kt3UzaLyCis8_pZMNGHBA,993
|
|
36
35
|
prompture/aio/__init__.py,sha256=bKqTu4Jxld16aP_7SP9wU5au45UBIb041ORo4E4HzVo,1810
|
|
37
36
|
prompture/drivers/__init__.py,sha256=r8wBYGKD7C7v4CqcyRNoaITzGVyxasoiAU6jBYsPZio,8178
|
|
@@ -40,31 +39,31 @@ prompture/drivers/async_airllm_driver.py,sha256=1hIWLXfyyIg9tXaOE22tLJvFyNwHnOi1
|
|
|
40
39
|
prompture/drivers/async_azure_driver.py,sha256=s__y_EGQkK7UZjxiyF08uql8F09cnbJ0q7aFuxzreIw,7328
|
|
41
40
|
prompture/drivers/async_claude_driver.py,sha256=oawbFVVMtRlikQOmu3jRjbdpoeu95JqTF1YHLKO3ybE,10576
|
|
42
41
|
prompture/drivers/async_google_driver.py,sha256=LTUgCXJjzuTDGzsCsmY2-xH2KdTLJD7htwO49ZNFOdE,13711
|
|
43
|
-
prompture/drivers/async_grok_driver.py,sha256=
|
|
44
|
-
prompture/drivers/async_groq_driver.py,sha256=
|
|
42
|
+
prompture/drivers/async_grok_driver.py,sha256=4oOGT4SzsheulU_QK0ZSqj4-THrFAOCeZwIqIslnW14,6858
|
|
43
|
+
prompture/drivers/async_groq_driver.py,sha256=iORpf0wcqPfS4zKCg4BTWpQCoHV2klkQVTQ1W-jhjUE,5755
|
|
45
44
|
prompture/drivers/async_hugging_driver.py,sha256=IblxqU6TpNUiigZ0BCgNkAgzpUr2FtPHJOZnOZMnHF0,2152
|
|
46
|
-
prompture/drivers/async_lmstudio_driver.py,sha256=
|
|
45
|
+
prompture/drivers/async_lmstudio_driver.py,sha256=rPn2qVPm6UE2APzAn7ZHYTELUwr0dQMi8XHv6gAhyH8,5782
|
|
47
46
|
prompture/drivers/async_local_http_driver.py,sha256=qoigIf-w3_c2dbVdM6m1e2RMAWP4Gk4VzVs5hM3lPvQ,1609
|
|
48
47
|
prompture/drivers/async_modelscope_driver.py,sha256=wzHYGLf9qE9KXRFZYtN1hZS10Bw1m1Wy6HcmyUD67HM,10170
|
|
49
|
-
prompture/drivers/async_moonshot_driver.py,sha256=
|
|
48
|
+
prompture/drivers/async_moonshot_driver.py,sha256=Jl6rGlW3SsneFfmBiDo0RBZQN5c3-08kwax369me01E,14798
|
|
50
49
|
prompture/drivers/async_ollama_driver.py,sha256=pFtCvh5bHe_qwGy-jIJbyG_zmnPbNbagJCGxCTJMdPU,8244
|
|
51
50
|
prompture/drivers/async_openai_driver.py,sha256=COa_JE-AgKowKJpmRnfDJp4RSQKZel_7WswxOzvLksM,9044
|
|
52
|
-
prompture/drivers/async_openrouter_driver.py,sha256=
|
|
51
|
+
prompture/drivers/async_openrouter_driver.py,sha256=GnOMY67CCV3HV83lCC-CxcngwrUnuc7G-AX7fb1DYpg,10698
|
|
53
52
|
prompture/drivers/async_registry.py,sha256=JFEnXNPm-8AAUCiNLoKuYBSCYEK-4BmAen5t55QrMvg,5223
|
|
54
53
|
prompture/drivers/async_zai_driver.py,sha256=zXHxske1CtK8dDTGY-D_kiyZZ_NfceNTJlyTpKn0R4c,10727
|
|
55
54
|
prompture/drivers/azure_driver.py,sha256=gQFffA29gOr-GZ25fNXTokV8-mEmffeV9CT_UBZ3yXc,8565
|
|
56
55
|
prompture/drivers/claude_driver.py,sha256=C8Av3DXP2x3f35jEv8BRwEM_4vh0cfmLsy3t5dsR6aM,11837
|
|
57
56
|
prompture/drivers/google_driver.py,sha256=Zck5VUsW37kDgohXz3cUWRmZ88OfhmTpVD-qzAVMp-8,16318
|
|
58
|
-
prompture/drivers/grok_driver.py,sha256=
|
|
59
|
-
prompture/drivers/groq_driver.py,sha256=
|
|
57
|
+
prompture/drivers/grok_driver.py,sha256=mNfPgOsJR53_5Ep6aYnfKGy7lnZMqN8bxrqKep4CiF0,8408
|
|
58
|
+
prompture/drivers/groq_driver.py,sha256=olr1t7V71ET8Z-7VyRwb75_iYEiZg8-n5qs1edZ2erw,6897
|
|
60
59
|
prompture/drivers/hugging_driver.py,sha256=gZir3XnM77VfYIdnu3S1pRftlZJM6G3L8bgGn5esg-Q,2346
|
|
61
|
-
prompture/drivers/lmstudio_driver.py,sha256=
|
|
60
|
+
prompture/drivers/lmstudio_driver.py,sha256=9ZnJ1l5LuWAjkH2WKfFjZprNMVIXoSC7qXDNDTxm-tA,6748
|
|
62
61
|
prompture/drivers/local_http_driver.py,sha256=QJgEf9kAmy8YZ5fb8FHnWuhoDoZYNd8at4jegzNVJH0,1658
|
|
63
62
|
prompture/drivers/modelscope_driver.py,sha256=yTxTG7j5f7zz4CjbrV8J0VKeoBmxv69F40bfp8nq6AE,10651
|
|
64
|
-
prompture/drivers/moonshot_driver.py,sha256=
|
|
63
|
+
prompture/drivers/moonshot_driver.py,sha256=MtlvtUUwE4WtzCKo_pJJ5wATB-h2GU4zY9jbGo3a_-g,18264
|
|
65
64
|
prompture/drivers/ollama_driver.py,sha256=SJtMRtAr8geUB4y5GIZxPr-RJ0C3q7yqigYei2b4luM,13710
|
|
66
65
|
prompture/drivers/openai_driver.py,sha256=DqdMhxF8M2HdOY5vfsFrz0h23lqBoQlbxV3xUdHvZho,10548
|
|
67
|
-
prompture/drivers/openrouter_driver.py,sha256=
|
|
66
|
+
prompture/drivers/openrouter_driver.py,sha256=DaG1H99s8GaOgJXZK4TP28HM7U4wiLu9wHXzWZleW_U,12589
|
|
68
67
|
prompture/drivers/registry.py,sha256=Dg_5w9alnIPKhOnsR9Xspuf5T7roBGu0r_L2Cf-UhXs,9926
|
|
69
68
|
prompture/drivers/vision_helpers.py,sha256=l5iYXHJLR_vLFvqDPPPK1QqK7YPKh5GwocpbSyt0R04,5403
|
|
70
69
|
prompture/drivers/zai_driver.py,sha256=Wkur0HfwKJt8ugYErpvz1Gy6e9an8vt4R7U3i6HWV_s,11038
|
|
@@ -77,9 +76,9 @@ prompture/scaffold/templates/env.example.j2,sha256=eESKr1KWgyrczO6d-nwAhQwSpf_G-
|
|
|
77
76
|
prompture/scaffold/templates/main.py.j2,sha256=TEgc5OvsZOEX0JthkSW1NI_yLwgoeVN_x97Ibg-vyWY,2632
|
|
78
77
|
prompture/scaffold/templates/models.py.j2,sha256=JrZ99GCVK6TKWapskVRSwCssGrTu5cGZ_r46fOhY2GE,858
|
|
79
78
|
prompture/scaffold/templates/requirements.txt.j2,sha256=m3S5fi1hq9KG9l_9j317rjwWww0a43WMKd8VnUWv2A4,102
|
|
80
|
-
prompture-0.0.47.dist-info/licenses/LICENSE,sha256=0HgDepH7aaHNFhHF-iXuW6_GqDfYPnVkjtiCAZ4yS8I,1060
|
|
81
|
-
prompture-0.0.47.dist-info/METADATA,sha256=
|
|
82
|
-
prompture-0.0.47.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
83
|
-
prompture-0.0.47.dist-info/entry_points.txt,sha256=AFPG3lJR86g4IJMoWQUW5Ph7G6MLNWG3A2u2Tp9zkp8,48
|
|
84
|
-
prompture-0.0.47.dist-info/top_level.txt,sha256=to86zq_kjfdoLeAxQNr420UWqT0WzkKoZ509J7Qr2t4,10
|
|
85
|
-
prompture-0.0.47.dist-info/RECORD,,
|
|
79
|
+
prompture-0.0.47.dev1.dist-info/licenses/LICENSE,sha256=0HgDepH7aaHNFhHF-iXuW6_GqDfYPnVkjtiCAZ4yS8I,1060
|
|
80
|
+
prompture-0.0.47.dev1.dist-info/METADATA,sha256=gxnbPKPzC1F715GdpLjy6LchTZ3mlQTQHrjnoGUibDQ,10842
|
|
81
|
+
prompture-0.0.47.dev1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
82
|
+
prompture-0.0.47.dev1.dist-info/entry_points.txt,sha256=AFPG3lJR86g4IJMoWQUW5Ph7G6MLNWG3A2u2Tp9zkp8,48
|
|
83
|
+
prompture-0.0.47.dev1.dist-info/top_level.txt,sha256=to86zq_kjfdoLeAxQNr420UWqT0WzkKoZ509J7Qr2t4,10
|
|
84
|
+
prompture-0.0.47.dev1.dist-info/RECORD,,
|