prompture 0.0.47__tar.gz → 0.0.47.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. {prompture-0.0.47 → prompture-0.0.47.dev2}/PKG-INFO +1 -1
  2. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/_version.py +2 -2
  3. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/async_conversation.py +0 -16
  4. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/conversation.py +0 -16
  5. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_grok_driver.py +9 -23
  6. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_groq_driver.py +9 -23
  7. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_lmstudio_driver.py +2 -10
  8. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_moonshot_driver.py +9 -20
  9. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_openrouter_driver.py +17 -43
  10. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/grok_driver.py +9 -23
  11. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/groq_driver.py +9 -23
  12. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/lmstudio_driver.py +2 -11
  13. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/moonshot_driver.py +9 -20
  14. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/openrouter_driver.py +10 -34
  15. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/PKG-INFO +1 -1
  16. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/SOURCES.txt +0 -1
  17. prompture-0.0.47/VERSION +0 -1
  18. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-driver/SKILL.md +0 -0
  19. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-driver/references/driver-template.md +0 -0
  20. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-example/SKILL.md +0 -0
  21. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-field/SKILL.md +0 -0
  22. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-persona/SKILL.md +0 -0
  23. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-test/SKILL.md +0 -0
  24. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/add-tool/SKILL.md +0 -0
  25. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/run-tests/SKILL.md +0 -0
  26. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  27. {prompture-0.0.47 → prompture-0.0.47.dev2}/.claude/skills/update-pricing/SKILL.md +0 -0
  28. {prompture-0.0.47 → prompture-0.0.47.dev2}/.env.copy +0 -0
  29. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/FUNDING.yml +0 -0
  30. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/scripts/update_docs_version.py +0 -0
  31. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/scripts/update_wrapper_version.py +0 -0
  32. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/workflows/dev.yml +0 -0
  33. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/workflows/documentation.yml +0 -0
  34. {prompture-0.0.47 → prompture-0.0.47.dev2}/.github/workflows/publish.yml +0 -0
  35. {prompture-0.0.47 → prompture-0.0.47.dev2}/CLAUDE.md +0 -0
  36. {prompture-0.0.47 → prompture-0.0.47.dev2}/LICENSE +0 -0
  37. {prompture-0.0.47 → prompture-0.0.47.dev2}/MANIFEST.in +0 -0
  38. {prompture-0.0.47 → prompture-0.0.47.dev2}/README.md +0 -0
  39. {prompture-0.0.47 → prompture-0.0.47.dev2}/ROADMAP.md +0 -0
  40. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/_static/custom.css +0 -0
  41. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/_templates/footer.html +0 -0
  42. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/core.rst +0 -0
  43. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/drivers.rst +0 -0
  44. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/field_definitions.rst +0 -0
  45. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/index.rst +0 -0
  46. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/runner.rst +0 -0
  47. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/tools.rst +0 -0
  48. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/api/validator.rst +0 -0
  49. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/conf.py +0 -0
  50. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/contributing.rst +0 -0
  51. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/examples.rst +0 -0
  52. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/field_definitions_reference.rst +0 -0
  53. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/index.rst +0 -0
  54. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/installation.rst +0 -0
  55. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/quickstart.rst +0 -0
  56. {prompture-0.0.47 → prompture-0.0.47.dev2}/docs/source/toon_input_guide.rst +0 -0
  57. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/README.md +0 -0
  58. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_json/README.md +0 -0
  59. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  60. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_json/pyproject.toml +0 -0
  61. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_json/test.py +0 -0
  62. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_toon/README.md +0 -0
  63. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  64. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_toon/pyproject.toml +0 -0
  65. {prompture-0.0.47 → prompture-0.0.47.dev2}/packages/llm_to_toon/test.py +0 -0
  66. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/__init__.py +0 -0
  67. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/agent.py +0 -0
  68. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/agent_types.py +0 -0
  69. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/aio/__init__.py +0 -0
  70. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/async_agent.py +0 -0
  71. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/async_core.py +0 -0
  72. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/async_driver.py +0 -0
  73. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/async_groups.py +0 -0
  74. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/cache.py +0 -0
  75. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/callbacks.py +0 -0
  76. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/cli.py +0 -0
  77. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/core.py +0 -0
  78. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/cost_mixin.py +0 -0
  79. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/discovery.py +0 -0
  80. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/driver.py +0 -0
  81. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/__init__.py +0 -0
  82. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/airllm_driver.py +0 -0
  83. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_airllm_driver.py +0 -0
  84. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_azure_driver.py +0 -0
  85. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_claude_driver.py +0 -0
  86. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_google_driver.py +0 -0
  87. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_hugging_driver.py +0 -0
  88. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_local_http_driver.py +0 -0
  89. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_modelscope_driver.py +0 -0
  90. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_ollama_driver.py +0 -0
  91. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_openai_driver.py +0 -0
  92. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_registry.py +0 -0
  93. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/async_zai_driver.py +0 -0
  94. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/azure_driver.py +0 -0
  95. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/claude_driver.py +0 -0
  96. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/google_driver.py +0 -0
  97. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/hugging_driver.py +0 -0
  98. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/local_http_driver.py +0 -0
  99. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/modelscope_driver.py +0 -0
  100. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/ollama_driver.py +0 -0
  101. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/openai_driver.py +0 -0
  102. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/registry.py +0 -0
  103. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/vision_helpers.py +0 -0
  104. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/drivers/zai_driver.py +0 -0
  105. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/field_definitions.py +0 -0
  106. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/group_types.py +0 -0
  107. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/groups.py +0 -0
  108. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/image.py +0 -0
  109. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/ledger.py +0 -0
  110. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/logging.py +0 -0
  111. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/model_rates.py +0 -0
  112. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/persistence.py +0 -0
  113. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/persona.py +0 -0
  114. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/runner.py +0 -0
  115. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/__init__.py +0 -0
  116. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/generator.py +0 -0
  117. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  118. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/README.md.j2 +0 -0
  119. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/config.py.j2 +0 -0
  120. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/env.example.j2 +0 -0
  121. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/main.py.j2 +0 -0
  122. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/models.py.j2 +0 -0
  123. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  124. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/serialization.py +0 -0
  125. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/server.py +0 -0
  126. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/session.py +0 -0
  127. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/settings.py +0 -0
  128. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/simulated_tools.py +0 -0
  129. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/tools.py +0 -0
  130. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/tools_schema.py +0 -0
  131. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture/validator.py +0 -0
  132. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/dependency_links.txt +0 -0
  133. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/entry_points.txt +0 -0
  134. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/requires.txt +0 -0
  135. {prompture-0.0.47 → prompture-0.0.47.dev2}/prompture.egg-info/top_level.txt +0 -0
  136. {prompture-0.0.47 → prompture-0.0.47.dev2}/pyproject.toml +0 -0
  137. {prompture-0.0.47 → prompture-0.0.47.dev2}/requirements.txt +0 -0
  138. {prompture-0.0.47 → prompture-0.0.47.dev2}/setup.cfg +0 -0
  139. {prompture-0.0.47 → prompture-0.0.47.dev2}/test.py +0 -0
  140. {prompture-0.0.47 → prompture-0.0.47.dev2}/test_version_diagnosis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 0.0.47
3
+ Version: 0.0.47.dev2
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
@@ -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.dev2'
32
+ __version_tuple__ = version_tuple = (0, 0, 47, 'dev2')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -109,9 +109,6 @@ class AsyncConversation:
109
109
  self._max_tool_rounds = max_tool_rounds
110
110
  self._simulated_tools = simulated_tools
111
111
 
112
- # Reasoning content from last response
113
- self._last_reasoning: str | None = None
114
-
115
112
  # Persistence
116
113
  self._conversation_id = conversation_id or str(uuid.uuid4())
117
114
  self._auto_save = Path(auto_save) if auto_save else None
@@ -124,11 +121,6 @@ class AsyncConversation:
124
121
  # Public helpers
125
122
  # ------------------------------------------------------------------
126
123
 
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
124
  @property
133
125
  def messages(self) -> list[dict[str, Any]]:
134
126
  """Read-only view of the conversation history."""
@@ -334,8 +326,6 @@ class AsyncConversation:
334
326
  If tools are registered and the driver supports tool use,
335
327
  dispatches to the async tool execution loop.
336
328
  """
337
- self._last_reasoning = None
338
-
339
329
  # Route to appropriate tool handling
340
330
  if self._tools:
341
331
  use_native = getattr(self._driver, "supports_tool_use", False)
@@ -350,7 +340,6 @@ class AsyncConversation:
350
340
 
351
341
  text = resp.get("text", "")
352
342
  meta = resp.get("meta", {})
353
- self._last_reasoning = resp.get("reasoning_content")
354
343
 
355
344
  user_content = self._build_content_with_images(content, images)
356
345
  self._messages.append({"role": "user", "content": user_content})
@@ -383,7 +372,6 @@ class AsyncConversation:
383
372
  text = resp.get("text", "")
384
373
 
385
374
  if not tool_calls:
386
- self._last_reasoning = resp.get("reasoning_content")
387
375
  self._messages.append({"role": "assistant", "content": text})
388
376
  return text
389
377
 
@@ -538,8 +526,6 @@ class AsyncConversation:
538
526
  images: list[ImageInput] | None = None,
539
527
  ) -> dict[str, Any]:
540
528
  """Send a message with schema enforcement and get structured JSON back (async)."""
541
- self._last_reasoning = None
542
-
543
529
  merged = {**self._options, **(options or {})}
544
530
 
545
531
  schema_string = json.dumps(json_schema, indent=2)
@@ -577,7 +563,6 @@ class AsyncConversation:
577
563
 
578
564
  text = resp.get("text", "")
579
565
  meta = resp.get("meta", {})
580
- self._last_reasoning = resp.get("reasoning_content")
581
566
 
582
567
  user_content = self._build_content_with_images(content, images)
583
568
  self._messages.append({"role": "user", "content": user_content})
@@ -612,7 +597,6 @@ class AsyncConversation:
612
597
  "json_object": json_obj,
613
598
  "usage": usage,
614
599
  "output_format": output_format,
615
- "reasoning": self._last_reasoning,
616
600
  }
617
601
 
618
602
  if output_format == "toon":
@@ -112,9 +112,6 @@ class Conversation:
112
112
  self._max_tool_rounds = max_tool_rounds
113
113
  self._simulated_tools = simulated_tools
114
114
 
115
- # Reasoning content from last response
116
- self._last_reasoning: str | None = None
117
-
118
115
  # Persistence
119
116
  self._conversation_id = conversation_id or str(uuid.uuid4())
120
117
  self._auto_save = Path(auto_save) if auto_save else None
@@ -127,11 +124,6 @@ class Conversation:
127
124
  # Public helpers
128
125
  # ------------------------------------------------------------------
129
126
 
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
127
  @property
136
128
  def messages(self) -> list[dict[str, Any]]:
137
129
  """Read-only view of the conversation history."""
@@ -348,8 +340,6 @@ class Conversation:
348
340
  images: Optional list of images to include (bytes, path, URL,
349
341
  base64 string, or :class:`ImageContent`).
350
342
  """
351
- self._last_reasoning = None
352
-
353
343
  # Route to appropriate tool handling
354
344
  if self._tools:
355
345
  use_native = getattr(self._driver, "supports_tool_use", False)
@@ -364,7 +354,6 @@ class Conversation:
364
354
 
365
355
  text = resp.get("text", "")
366
356
  meta = resp.get("meta", {})
367
- self._last_reasoning = resp.get("reasoning_content")
368
357
 
369
358
  # Record in history — store content with images for context
370
359
  user_content = self._build_content_with_images(content, images)
@@ -400,7 +389,6 @@ class Conversation:
400
389
 
401
390
  if not tool_calls:
402
391
  # No tool calls -> final response
403
- self._last_reasoning = resp.get("reasoning_content")
404
392
  self._messages.append({"role": "assistant", "content": text})
405
393
  return text
406
394
 
@@ -565,8 +553,6 @@ class Conversation:
565
553
  context clean for subsequent turns.
566
554
  """
567
555
 
568
- self._last_reasoning = None
569
-
570
556
  merged = {**self._options, **(options or {})}
571
557
 
572
558
  # Build the full prompt with schema instructions inline (handled by ask_for_json)
@@ -608,7 +594,6 @@ class Conversation:
608
594
 
609
595
  text = resp.get("text", "")
610
596
  meta = resp.get("meta", {})
611
- self._last_reasoning = resp.get("reasoning_content")
612
597
 
613
598
  # Store original content (without schema boilerplate) for cleaner context
614
599
  # Include images in history so subsequent turns can reference them
@@ -647,7 +632,6 @@ class Conversation:
647
632
  "json_object": json_obj,
648
633
  "usage": usage,
649
634
  "output_format": output_format,
650
- "reasoning": self._last_reasoning,
651
635
  }
652
636
 
653
637
  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
@@ -339,7 +334,6 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
339
334
  data["temperature"] = opts["temperature"]
340
335
 
341
336
  full_text = ""
342
- full_reasoning = ""
343
337
  prompt_tokens = 0
344
338
  completion_tokens = 0
345
339
 
@@ -374,11 +368,9 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
374
368
  if choices:
375
369
  delta = choices[0].get("delta", {})
376
370
  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
371
+ # Reasoning models stream thinking via reasoning_content
372
+ if not content:
373
+ content = delta.get("reasoning_content") or ""
382
374
  if content:
383
375
  full_text += content
384
376
  yield {"type": "delta", "text": content}
@@ -386,7 +378,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
386
378
  total_tokens = prompt_tokens + completion_tokens
387
379
  total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
388
380
 
389
- done_chunk: dict[str, Any] = {
381
+ yield {
390
382
  "type": "done",
391
383
  "text": full_text,
392
384
  "meta": {
@@ -398,6 +390,3 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
398
390
  "model_name": model,
399
391
  },
400
392
  }
401
- if full_reasoning:
402
- done_chunk["reasoning_content"] = full_reasoning
403
- yield done_chunk
@@ -122,17 +122,8 @@ class AsyncOpenRouterDriver(CostMixin, AsyncDriver):
122
122
  "model_name": model,
123
123
  }
124
124
 
125
- message = resp["choices"][0]["message"]
126
- text = message.get("content") or ""
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
- "id": tc["id"],
211
- "name": tc["function"]["name"],
212
- "arguments": args,
213
- }
214
- )
199
+ tool_calls_out.append({
200
+ "id": tc["id"],
201
+ "name": tc["function"]["name"],
202
+ "arguments": args,
203
+ })
215
204
 
216
- result: dict[str, Any] = {
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
- httpx.AsyncClient() as client,
261
- client.stream(
262
- "POST",
263
- f"{self.base_url}/chat/completions",
264
- headers=self.headers,
265
- json=data,
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
- done_chunk: dict[str, Any] = {
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
@@ -154,17 +154,8 @@ class GrokDriver(CostMixin, Driver):
154
154
  "model_name": model,
155
155
  }
156
156
 
157
- message = resp["choices"][0]["message"]
158
- text = message.get("content") or ""
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
- "id": tc["id"],
242
- "name": tc["function"]["name"],
243
- "arguments": args,
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
@@ -122,16 +122,8 @@ class GroqDriver(CostMixin, Driver):
122
122
  }
123
123
 
124
124
  # Extract generated text
125
- text = resp.choices[0].message.content or ""
126
- reasoning_content = getattr(resp.choices[0].message, "reasoning_content", None)
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
- "id": tc.id,
200
- "name": tc.function.name,
201
- "arguments": args,
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
- message = response_data["choices"][0]["message"]
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
- result: dict[str, Any] = {"text": text, "meta": meta}
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