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
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
|
prompture/async_conversation.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
92
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
-
if
|
|
379
|
-
|
|
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
|
-
|
|
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
|