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 CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.47'
32
- __version_tuple__ = version_tuple = (0, 0, 47)
31
+ __version__ = version = '0.0.47.dev1'
32
+ __version_tuple__ = version_tuple = (0, 0, 47, 'dev1')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -55,7 +55,6 @@ class AsyncConversation:
55
55
  callbacks: DriverCallbacks | None = None,
56
56
  tools: ToolRegistry | None = None,
57
57
  max_tool_rounds: int = 10,
58
- simulated_tools: bool | Literal["auto"] = "auto",
59
58
  conversation_id: str | None = None,
60
59
  auto_save: str | Path | None = None,
61
60
  tags: list[str] | None = None,
@@ -107,10 +106,6 @@ class AsyncConversation:
107
106
  }
108
107
  self._tools = tools or ToolRegistry()
109
108
  self._max_tool_rounds = max_tool_rounds
110
- self._simulated_tools = simulated_tools
111
-
112
- # Reasoning content from last response
113
- self._last_reasoning: str | None = None
114
109
 
115
110
  # Persistence
116
111
  self._conversation_id = conversation_id or str(uuid.uuid4())
@@ -124,11 +119,6 @@ class AsyncConversation:
124
119
  # Public helpers
125
120
  # ------------------------------------------------------------------
126
121
 
127
- @property
128
- def last_reasoning(self) -> str | None:
129
- """The reasoning/thinking content from the last LLM response, if any."""
130
- return self._last_reasoning
131
-
132
122
  @property
133
123
  def messages(self) -> list[dict[str, Any]]:
134
124
  """Read-only view of the conversation history."""
@@ -334,15 +324,8 @@ class AsyncConversation:
334
324
  If tools are registered and the driver supports tool use,
335
325
  dispatches to the async tool execution loop.
336
326
  """
337
- self._last_reasoning = None
338
-
339
- # Route to appropriate tool handling
340
- if self._tools:
341
- use_native = getattr(self._driver, "supports_tool_use", False)
342
- if self._simulated_tools is True or (self._simulated_tools == "auto" and not use_native):
343
- return await self._ask_with_simulated_tools(content, options, images=images)
344
- elif use_native and self._simulated_tools is not True:
345
- return await self._ask_with_tools(content, options, images=images)
327
+ if self._tools and getattr(self._driver, "supports_tool_use", False):
328
+ return await self._ask_with_tools(content, options, images=images)
346
329
 
347
330
  merged = {**self._options, **(options or {})}
348
331
  messages = self._build_messages(content, images=images)
@@ -350,7 +333,6 @@ class AsyncConversation:
350
333
 
351
334
  text = resp.get("text", "")
352
335
  meta = resp.get("meta", {})
353
- self._last_reasoning = resp.get("reasoning_content")
354
336
 
355
337
  user_content = self._build_content_with_images(content, images)
356
338
  self._messages.append({"role": "user", "content": user_content})
@@ -383,7 +365,6 @@ class AsyncConversation:
383
365
  text = resp.get("text", "")
384
366
 
385
367
  if not tool_calls:
386
- self._last_reasoning = resp.get("reasoning_content")
387
368
  self._messages.append({"role": "assistant", "content": text})
388
369
  return text
389
370
 
@@ -396,11 +377,6 @@ class AsyncConversation:
396
377
  }
397
378
  for tc in tool_calls
398
379
  ]
399
- # Preserve reasoning_content for providers that require it
400
- # on subsequent requests (e.g. Moonshot reasoning models).
401
- if resp.get("reasoning_content") is not None:
402
- assistant_msg["reasoning_content"] = resp["reasoning_content"]
403
-
404
380
  self._messages.append(assistant_msg)
405
381
  msgs.append(assistant_msg)
406
382
 
@@ -421,63 +397,6 @@ class AsyncConversation:
421
397
 
422
398
  raise RuntimeError(f"Tool execution loop exceeded {self._max_tool_rounds} rounds")
423
399
 
424
- async def _ask_with_simulated_tools(
425
- self,
426
- content: str,
427
- options: dict[str, Any] | None = None,
428
- images: list[ImageInput] | None = None,
429
- ) -> str:
430
- """Async prompt-based tool calling for drivers without native tool use."""
431
- from .simulated_tools import build_tool_prompt, format_tool_result, parse_simulated_response
432
-
433
- merged = {**self._options, **(options or {})}
434
- tool_prompt = build_tool_prompt(self._tools)
435
-
436
- # Augment system prompt with tool descriptions
437
- augmented_system = tool_prompt
438
- if self._system_prompt:
439
- augmented_system = f"{self._system_prompt}\n\n{tool_prompt}"
440
-
441
- # Record user message in history
442
- user_content = self._build_content_with_images(content, images)
443
- self._messages.append({"role": "user", "content": user_content})
444
-
445
- for _round in range(self._max_tool_rounds):
446
- # Build messages with the augmented system prompt
447
- msgs: list[dict[str, Any]] = []
448
- msgs.append({"role": "system", "content": augmented_system})
449
- msgs.extend(self._messages)
450
-
451
- resp = await self._driver.generate_messages_with_hooks(msgs, merged)
452
- text = resp.get("text", "")
453
- meta = resp.get("meta", {})
454
- self._accumulate_usage(meta)
455
-
456
- parsed = parse_simulated_response(text, self._tools)
457
-
458
- if parsed["type"] == "final_answer":
459
- answer = parsed["content"]
460
- self._messages.append({"role": "assistant", "content": answer})
461
- return answer
462
-
463
- # Tool call
464
- tool_name = parsed["name"]
465
- tool_args = parsed["arguments"]
466
-
467
- # Record assistant's tool call as an assistant message
468
- self._messages.append({"role": "assistant", "content": text})
469
-
470
- try:
471
- result = self._tools.execute(tool_name, tool_args)
472
- result_msg = format_tool_result(tool_name, result)
473
- except Exception as exc:
474
- result_msg = format_tool_result(tool_name, f"Error: {exc}")
475
-
476
- # Record tool result as a user message
477
- self._messages.append({"role": "user", "content": result_msg})
478
-
479
- raise RuntimeError(f"Simulated tool execution loop exceeded {self._max_tool_rounds} rounds")
480
-
481
400
  def _build_messages_raw(self) -> list[dict[str, Any]]:
482
401
  """Build messages array from system prompt + full history (including tool messages)."""
483
402
  msgs: list[dict[str, Any]] = []
@@ -538,8 +457,6 @@ class AsyncConversation:
538
457
  images: list[ImageInput] | None = None,
539
458
  ) -> dict[str, Any]:
540
459
  """Send a message with schema enforcement and get structured JSON back (async)."""
541
- self._last_reasoning = None
542
-
543
460
  merged = {**self._options, **(options or {})}
544
461
 
545
462
  schema_string = json.dumps(json_schema, indent=2)
@@ -577,7 +494,6 @@ class AsyncConversation:
577
494
 
578
495
  text = resp.get("text", "")
579
496
  meta = resp.get("meta", {})
580
- self._last_reasoning = resp.get("reasoning_content")
581
497
 
582
498
  user_content = self._build_content_with_images(content, images)
583
499
  self._messages.append({"role": "user", "content": user_content})
@@ -612,7 +528,6 @@ class AsyncConversation:
612
528
  "json_object": json_obj,
613
529
  "usage": usage,
614
530
  "output_format": output_format,
615
- "reasoning": self._last_reasoning,
616
531
  }
617
532
 
618
533
  if output_format == "toon":
prompture/conversation.py CHANGED
@@ -56,7 +56,6 @@ class Conversation:
56
56
  callbacks: DriverCallbacks | None = None,
57
57
  tools: ToolRegistry | None = None,
58
58
  max_tool_rounds: int = 10,
59
- simulated_tools: bool | Literal["auto"] = "auto",
60
59
  conversation_id: str | None = None,
61
60
  auto_save: str | Path | None = None,
62
61
  tags: list[str] | None = None,
@@ -110,10 +109,6 @@ class Conversation:
110
109
  }
111
110
  self._tools = tools or ToolRegistry()
112
111
  self._max_tool_rounds = max_tool_rounds
113
- self._simulated_tools = simulated_tools
114
-
115
- # Reasoning content from last response
116
- self._last_reasoning: str | None = None
117
112
 
118
113
  # Persistence
119
114
  self._conversation_id = conversation_id or str(uuid.uuid4())
@@ -127,11 +122,6 @@ class Conversation:
127
122
  # Public helpers
128
123
  # ------------------------------------------------------------------
129
124
 
130
- @property
131
- def last_reasoning(self) -> str | None:
132
- """The reasoning/thinking content from the last LLM response, if any."""
133
- return self._last_reasoning
134
-
135
125
  @property
136
126
  def messages(self) -> list[dict[str, Any]]:
137
127
  """Read-only view of the conversation history."""
@@ -348,15 +338,8 @@ class Conversation:
348
338
  images: Optional list of images to include (bytes, path, URL,
349
339
  base64 string, or :class:`ImageContent`).
350
340
  """
351
- self._last_reasoning = None
352
-
353
- # Route to appropriate tool handling
354
- if self._tools:
355
- use_native = getattr(self._driver, "supports_tool_use", False)
356
- if self._simulated_tools is True or (self._simulated_tools == "auto" and not use_native):
357
- return self._ask_with_simulated_tools(content, options, images=images)
358
- elif use_native and self._simulated_tools is not True:
359
- return self._ask_with_tools(content, options, images=images)
341
+ if self._tools and getattr(self._driver, "supports_tool_use", False):
342
+ return self._ask_with_tools(content, options, images=images)
360
343
 
361
344
  merged = {**self._options, **(options or {})}
362
345
  messages = self._build_messages(content, images=images)
@@ -364,7 +347,6 @@ class Conversation:
364
347
 
365
348
  text = resp.get("text", "")
366
349
  meta = resp.get("meta", {})
367
- self._last_reasoning = resp.get("reasoning_content")
368
350
 
369
351
  # Record in history — store content with images for context
370
352
  user_content = self._build_content_with_images(content, images)
@@ -400,7 +382,6 @@ class Conversation:
400
382
 
401
383
  if not tool_calls:
402
384
  # No tool calls -> final response
403
- self._last_reasoning = resp.get("reasoning_content")
404
385
  self._messages.append({"role": "assistant", "content": text})
405
386
  return text
406
387
 
@@ -414,11 +395,6 @@ class Conversation:
414
395
  }
415
396
  for tc in tool_calls
416
397
  ]
417
- # Preserve reasoning_content for providers that require it
418
- # on subsequent requests (e.g. Moonshot reasoning models).
419
- if resp.get("reasoning_content") is not None:
420
- assistant_msg["reasoning_content"] = resp["reasoning_content"]
421
-
422
398
  self._messages.append(assistant_msg)
423
399
  msgs.append(assistant_msg)
424
400
 
@@ -440,63 +416,6 @@ class Conversation:
440
416
 
441
417
  raise RuntimeError(f"Tool execution loop exceeded {self._max_tool_rounds} rounds")
442
418
 
443
- def _ask_with_simulated_tools(
444
- self,
445
- content: str,
446
- options: dict[str, Any] | None = None,
447
- images: list[ImageInput] | None = None,
448
- ) -> str:
449
- """Prompt-based tool calling for drivers without native tool use."""
450
- from .simulated_tools import build_tool_prompt, format_tool_result, parse_simulated_response
451
-
452
- merged = {**self._options, **(options or {})}
453
- tool_prompt = build_tool_prompt(self._tools)
454
-
455
- # Augment system prompt with tool descriptions
456
- augmented_system = tool_prompt
457
- if self._system_prompt:
458
- augmented_system = f"{self._system_prompt}\n\n{tool_prompt}"
459
-
460
- # Record user message in history
461
- user_content = self._build_content_with_images(content, images)
462
- self._messages.append({"role": "user", "content": user_content})
463
-
464
- for _round in range(self._max_tool_rounds):
465
- # Build messages with the augmented system prompt
466
- msgs: list[dict[str, Any]] = []
467
- msgs.append({"role": "system", "content": augmented_system})
468
- msgs.extend(self._messages)
469
-
470
- resp = self._driver.generate_messages_with_hooks(msgs, merged)
471
- text = resp.get("text", "")
472
- meta = resp.get("meta", {})
473
- self._accumulate_usage(meta)
474
-
475
- parsed = parse_simulated_response(text, self._tools)
476
-
477
- if parsed["type"] == "final_answer":
478
- answer = parsed["content"]
479
- self._messages.append({"role": "assistant", "content": answer})
480
- return answer
481
-
482
- # Tool call
483
- tool_name = parsed["name"]
484
- tool_args = parsed["arguments"]
485
-
486
- # Record assistant's tool call as an assistant message
487
- self._messages.append({"role": "assistant", "content": text})
488
-
489
- try:
490
- result = self._tools.execute(tool_name, tool_args)
491
- result_msg = format_tool_result(tool_name, result)
492
- except Exception as exc:
493
- result_msg = format_tool_result(tool_name, f"Error: {exc}")
494
-
495
- # Record tool result as a user message (all drivers understand user/assistant)
496
- self._messages.append({"role": "user", "content": result_msg})
497
-
498
- raise RuntimeError(f"Simulated tool execution loop exceeded {self._max_tool_rounds} rounds")
499
-
500
419
  def _build_messages_raw(self) -> list[dict[str, Any]]:
501
420
  """Build messages array from system prompt + full history (including tool messages)."""
502
421
  msgs: list[dict[str, Any]] = []
@@ -565,8 +484,6 @@ class Conversation:
565
484
  context clean for subsequent turns.
566
485
  """
567
486
 
568
- self._last_reasoning = None
569
-
570
487
  merged = {**self._options, **(options or {})}
571
488
 
572
489
  # Build the full prompt with schema instructions inline (handled by ask_for_json)
@@ -608,7 +525,6 @@ class Conversation:
608
525
 
609
526
  text = resp.get("text", "")
610
527
  meta = resp.get("meta", {})
611
- self._last_reasoning = resp.get("reasoning_content")
612
528
 
613
529
  # Store original content (without schema boilerplate) for cleaner context
614
530
  # Include images in history so subsequent turns can reference them
@@ -647,7 +563,6 @@ class Conversation:
647
563
  "json_object": json_obj,
648
564
  "usage": usage,
649
565
  "output_format": output_format,
650
- "reasoning": self._last_reasoning,
651
566
  }
652
567
 
653
568
  if output_format == "toon":
@@ -95,17 +95,8 @@ class AsyncGrokDriver(CostMixin, AsyncDriver):
95
95
  "model_name": model,
96
96
  }
97
97
 
98
- message = resp["choices"][0]["message"]
99
- text = message.get("content") or ""
100
- reasoning_content = message.get("reasoning_content")
101
-
102
- if not text and reasoning_content:
103
- text = reasoning_content
104
-
105
- result: dict[str, Any] = {"text": text, "meta": meta}
106
- if reasoning_content is not None:
107
- result["reasoning_content"] = reasoning_content
108
- return result
98
+ text = resp["choices"][0]["message"]["content"]
99
+ return {"text": text, "meta": meta}
109
100
 
110
101
  # ------------------------------------------------------------------
111
102
  # Tool use
@@ -182,20 +173,15 @@ class AsyncGrokDriver(CostMixin, AsyncDriver):
182
173
  args = json.loads(tc["function"]["arguments"])
183
174
  except (json.JSONDecodeError, TypeError):
184
175
  args = {}
185
- tool_calls_out.append(
186
- {
187
- "id": tc["id"],
188
- "name": tc["function"]["name"],
189
- "arguments": args,
190
- }
191
- )
192
-
193
- result: dict[str, Any] = {
176
+ tool_calls_out.append({
177
+ "id": tc["id"],
178
+ "name": tc["function"]["name"],
179
+ "arguments": args,
180
+ })
181
+
182
+ return {
194
183
  "text": text,
195
184
  "meta": meta,
196
185
  "tool_calls": tool_calls_out,
197
186
  "stop_reason": stop_reason,
198
187
  }
199
- if choice["message"].get("reasoning_content") is not None:
200
- result["reasoning_content"] = choice["message"]["reasoning_content"]
201
- return result
@@ -88,16 +88,8 @@ class AsyncGroqDriver(CostMixin, AsyncDriver):
88
88
  "model_name": model,
89
89
  }
90
90
 
91
- text = resp.choices[0].message.content or ""
92
- reasoning_content = getattr(resp.choices[0].message, "reasoning_content", None)
93
-
94
- if not text and reasoning_content:
95
- text = reasoning_content
96
-
97
- result: dict[str, Any] = {"text": text, "meta": meta}
98
- if reasoning_content is not None:
99
- result["reasoning_content"] = reasoning_content
100
- return result
91
+ text = resp.choices[0].message.content
92
+ return {"text": text, "meta": meta}
101
93
 
102
94
  # ------------------------------------------------------------------
103
95
  # Tool use
@@ -160,21 +152,15 @@ class AsyncGroqDriver(CostMixin, AsyncDriver):
160
152
  args = json.loads(tc.function.arguments)
161
153
  except (json.JSONDecodeError, TypeError):
162
154
  args = {}
163
- tool_calls_out.append(
164
- {
165
- "id": tc.id,
166
- "name": tc.function.name,
167
- "arguments": args,
168
- }
169
- )
170
-
171
- result: dict[str, Any] = {
155
+ tool_calls_out.append({
156
+ "id": tc.id,
157
+ "name": tc.function.name,
158
+ "arguments": args,
159
+ })
160
+
161
+ return {
172
162
  "text": text,
173
163
  "meta": meta,
174
164
  "tool_calls": tool_calls_out,
175
165
  "stop_reason": stop_reason,
176
166
  }
177
- reasoning_content = getattr(choice.message, "reasoning_content", None)
178
- if reasoning_content is not None:
179
- result["reasoning_content"] = reasoning_content
180
- return result
@@ -98,12 +98,7 @@ class AsyncLMStudioDriver(AsyncDriver):
98
98
  if "choices" not in response_data or not response_data["choices"]:
99
99
  raise ValueError(f"Unexpected response format: {response_data}")
100
100
 
101
- message = response_data["choices"][0]["message"]
102
- text = message.get("content") or ""
103
- reasoning_content = message.get("reasoning_content")
104
-
105
- if not text and reasoning_content:
106
- text = reasoning_content
101
+ text = response_data["choices"][0]["message"]["content"]
107
102
 
108
103
  usage = response_data.get("usage", {})
109
104
  prompt_tokens = usage.get("prompt_tokens", 0)
@@ -119,10 +114,7 @@ class AsyncLMStudioDriver(AsyncDriver):
119
114
  "model_name": merged_options.get("model", self.model),
120
115
  }
121
116
 
122
- result: dict[str, Any] = {"text": text, "meta": meta}
123
- if reasoning_content is not None:
124
- result["reasoning_content"] = reasoning_content
125
- return result
117
+ return {"text": text, "meta": meta}
126
118
 
127
119
  # -- Model management (LM Studio 0.4.0+) ----------------------------------
128
120
 
@@ -138,11 +138,10 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
138
138
 
139
139
  message = resp["choices"][0]["message"]
140
140
  text = message.get("content") or ""
141
- reasoning_content = message.get("reasoning_content")
142
141
 
143
142
  # Reasoning models may return content in reasoning_content when content is empty
144
- if not text and reasoning_content:
145
- text = reasoning_content
143
+ if not text and message.get("reasoning_content"):
144
+ text = message["reasoning_content"]
146
145
 
147
146
  # Structured output fallback: if we used json_schema mode and got an
148
147
  # empty response, retry with json_object mode and schema in the prompt.
@@ -185,9 +184,8 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
185
184
  resp = fb_resp
186
185
  fb_message = fb_resp["choices"][0]["message"]
187
186
  text = fb_message.get("content") or ""
188
- reasoning_content = fb_message.get("reasoning_content")
189
- if not text and reasoning_content:
190
- text = reasoning_content
187
+ if not text and fb_message.get("reasoning_content"):
188
+ text = fb_message["reasoning_content"]
191
189
 
192
190
  total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
193
191
 
@@ -200,10 +198,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
200
198
  "model_name": model,
201
199
  }
202
200
 
203
- result: dict[str, Any] = {"text": text, "meta": meta}
204
- if reasoning_content is not None:
205
- result["reasoning_content"] = reasoning_content
206
- return result
201
+ return {"text": text, "meta": meta}
207
202
 
208
203
  # ------------------------------------------------------------------
209
204
  # Tool use
@@ -276,12 +271,11 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
276
271
  }
277
272
 
278
273
  choice = resp["choices"][0]
279
- message = choice["message"]
280
- text = message.get("content") or ""
274
+ text = choice["message"].get("content") or ""
281
275
  stop_reason = choice.get("finish_reason")
282
276
 
283
277
  tool_calls_out: list[dict[str, Any]] = []
284
- for tc in message.get("tool_calls", []):
278
+ for tc in choice["message"].get("tool_calls", []):
285
279
  try:
286
280
  args = json.loads(tc["function"]["arguments"])
287
281
  except (json.JSONDecodeError, TypeError):
@@ -294,21 +288,13 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
294
288
  }
295
289
  )
296
290
 
297
- result: dict[str, Any] = {
291
+ return {
298
292
  "text": text,
299
293
  "meta": meta,
300
294
  "tool_calls": tool_calls_out,
301
295
  "stop_reason": stop_reason,
302
296
  }
303
297
 
304
- # Preserve reasoning_content for reasoning models so the
305
- # conversation loop can include it when sending the assistant
306
- # message back (Moonshot requires it on subsequent requests).
307
- if message.get("reasoning_content") is not None:
308
- result["reasoning_content"] = message["reasoning_content"]
309
-
310
- return result
311
-
312
298
  # ------------------------------------------------------------------
313
299
  # Streaming
314
300
  # ------------------------------------------------------------------
@@ -339,7 +325,6 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
339
325
  data["temperature"] = opts["temperature"]
340
326
 
341
327
  full_text = ""
342
- full_reasoning = ""
343
328
  prompt_tokens = 0
344
329
  completion_tokens = 0
345
330
 
@@ -374,11 +359,9 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
374
359
  if choices:
375
360
  delta = choices[0].get("delta", {})
376
361
  content = delta.get("content") or ""
377
- reasoning_chunk = delta.get("reasoning_content") or ""
378
- if reasoning_chunk:
379
- full_reasoning += reasoning_chunk
380
- if not content and reasoning_chunk:
381
- content = reasoning_chunk
362
+ # Reasoning models stream thinking via reasoning_content
363
+ if not content:
364
+ content = delta.get("reasoning_content") or ""
382
365
  if content:
383
366
  full_text += content
384
367
  yield {"type": "delta", "text": content}
@@ -386,7 +369,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
386
369
  total_tokens = prompt_tokens + completion_tokens
387
370
  total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
388
371
 
389
- done_chunk: dict[str, Any] = {
372
+ yield {
390
373
  "type": "done",
391
374
  "text": full_text,
392
375
  "meta": {
@@ -398,6 +381,3 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
398
381
  "model_name": model,
399
382
  },
400
383
  }
401
- if full_reasoning:
402
- done_chunk["reasoning_content"] = full_reasoning
403
- yield done_chunk