prompture 0.0.47__tar.gz → 0.0.47.dev1__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.dev1}/PKG-INFO +2 -35
  2. {prompture-0.0.47 → prompture-0.0.47.dev1}/README.md +1 -34
  3. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/_version.py +2 -2
  4. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_conversation.py +2 -87
  5. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/conversation.py +2 -87
  6. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_grok_driver.py +9 -23
  7. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_groq_driver.py +9 -23
  8. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_lmstudio_driver.py +2 -10
  9. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_moonshot_driver.py +12 -32
  10. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_openrouter_driver.py +17 -43
  11. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/grok_driver.py +9 -23
  12. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/groq_driver.py +9 -23
  13. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/lmstudio_driver.py +2 -11
  14. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/moonshot_driver.py +12 -32
  15. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/openrouter_driver.py +10 -34
  16. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/tools_schema.py +0 -22
  17. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/PKG-INFO +2 -35
  18. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/SOURCES.txt +0 -2
  19. prompture-0.0.47/VERSION +0 -1
  20. prompture-0.0.47/prompture/simulated_tools.py +0 -115
  21. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
  22. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
  23. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-example/SKILL.md +0 -0
  24. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-field/SKILL.md +0 -0
  25. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-persona/SKILL.md +0 -0
  26. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-test/SKILL.md +0 -0
  27. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-tool/SKILL.md +0 -0
  28. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
  29. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  30. {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/update-pricing/SKILL.md +0 -0
  31. {prompture-0.0.47 → prompture-0.0.47.dev1}/.env.copy +0 -0
  32. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/FUNDING.yml +0 -0
  33. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/scripts/update_docs_version.py +0 -0
  34. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/scripts/update_wrapper_version.py +0 -0
  35. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/dev.yml +0 -0
  36. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/documentation.yml +0 -0
  37. {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/publish.yml +0 -0
  38. {prompture-0.0.47 → prompture-0.0.47.dev1}/CLAUDE.md +0 -0
  39. {prompture-0.0.47 → prompture-0.0.47.dev1}/LICENSE +0 -0
  40. {prompture-0.0.47 → prompture-0.0.47.dev1}/MANIFEST.in +0 -0
  41. {prompture-0.0.47 → prompture-0.0.47.dev1}/ROADMAP.md +0 -0
  42. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/_static/custom.css +0 -0
  43. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/_templates/footer.html +0 -0
  44. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/core.rst +0 -0
  45. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/drivers.rst +0 -0
  46. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/field_definitions.rst +0 -0
  47. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/index.rst +0 -0
  48. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/runner.rst +0 -0
  49. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/tools.rst +0 -0
  50. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/validator.rst +0 -0
  51. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/conf.py +0 -0
  52. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/contributing.rst +0 -0
  53. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/examples.rst +0 -0
  54. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/field_definitions_reference.rst +0 -0
  55. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/index.rst +0 -0
  56. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/installation.rst +0 -0
  57. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/quickstart.rst +0 -0
  58. {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/toon_input_guide.rst +0 -0
  59. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/README.md +0 -0
  60. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/README.md +0 -0
  61. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  62. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/pyproject.toml +0 -0
  63. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/test.py +0 -0
  64. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/README.md +0 -0
  65. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  66. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
  67. {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/test.py +0 -0
  68. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/__init__.py +0 -0
  69. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/agent.py +0 -0
  70. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/agent_types.py +0 -0
  71. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/aio/__init__.py +0 -0
  72. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_agent.py +0 -0
  73. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_core.py +0 -0
  74. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_driver.py +0 -0
  75. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_groups.py +0 -0
  76. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cache.py +0 -0
  77. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/callbacks.py +0 -0
  78. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cli.py +0 -0
  79. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/core.py +0 -0
  80. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cost_mixin.py +0 -0
  81. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/discovery.py +0 -0
  82. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/driver.py +0 -0
  83. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/__init__.py +0 -0
  84. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/airllm_driver.py +0 -0
  85. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
  86. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_azure_driver.py +0 -0
  87. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_claude_driver.py +0 -0
  88. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_google_driver.py +0 -0
  89. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
  90. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
  91. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
  92. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
  93. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_openai_driver.py +0 -0
  94. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_registry.py +0 -0
  95. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_zai_driver.py +0 -0
  96. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/azure_driver.py +0 -0
  97. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/claude_driver.py +0 -0
  98. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/google_driver.py +0 -0
  99. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/hugging_driver.py +0 -0
  100. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/local_http_driver.py +0 -0
  101. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/modelscope_driver.py +0 -0
  102. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/ollama_driver.py +0 -0
  103. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/openai_driver.py +0 -0
  104. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/registry.py +0 -0
  105. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/vision_helpers.py +0 -0
  106. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/zai_driver.py +0 -0
  107. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/field_definitions.py +0 -0
  108. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/group_types.py +0 -0
  109. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/groups.py +0 -0
  110. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/image.py +0 -0
  111. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/ledger.py +0 -0
  112. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/logging.py +0 -0
  113. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/model_rates.py +0 -0
  114. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/persistence.py +0 -0
  115. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/persona.py +0 -0
  116. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/runner.py +0 -0
  117. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/__init__.py +0 -0
  118. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/generator.py +0 -0
  119. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  120. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
  121. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
  122. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
  123. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
  124. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
  125. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  126. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/serialization.py +0 -0
  127. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/server.py +0 -0
  128. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/session.py +0 -0
  129. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/settings.py +0 -0
  130. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/tools.py +0 -0
  131. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/validator.py +0 -0
  132. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/dependency_links.txt +0 -0
  133. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/entry_points.txt +0 -0
  134. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/requires.txt +0 -0
  135. {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/top_level.txt +0 -0
  136. {prompture-0.0.47 → prompture-0.0.47.dev1}/pyproject.toml +0 -0
  137. {prompture-0.0.47 → prompture-0.0.47.dev1}/requirements.txt +0 -0
  138. {prompture-0.0.47 → prompture-0.0.47.dev1}/setup.cfg +0 -0
  139. {prompture-0.0.47 → prompture-0.0.47.dev1}/test.py +0 -0
  140. {prompture-0.0.47 → prompture-0.0.47.dev1}/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.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, with automatic prompt-based simulation for models without native tool support
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:
@@ -36,7 +36,7 @@ print(person.name) # Maria
36
36
  - **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
37
37
  - **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
38
38
  - **Conversations** — Stateful multi-turn sessions with sync and async support
39
- - **Tool use** — Function calling and streaming across supported providers, with automatic prompt-based simulation for models without native tool support
39
+ - **Tool use** — Function calling and streaming across supported providers
40
40
  - **Caching** — Built-in response cache with memory, SQLite, and Redis backends
41
41
  - **Plugin system** — Register custom drivers via entry points
42
42
  - **Usage tracking** — Token counts and cost calculation on every call
@@ -249,39 +249,6 @@ response = conv.send("What is the capital of France?")
249
249
  follow_up = conv.send("What about Germany?") # retains context
250
250
  ```
251
251
 
252
- ### Tool Use
253
-
254
- Register Python functions as tools the LLM can call during a conversation:
255
-
256
- ```python
257
- from prompture import Conversation, ToolRegistry
258
-
259
- registry = ToolRegistry()
260
-
261
- @registry.tool
262
- def get_weather(city: str, units: str = "celsius") -> str:
263
- """Get the current weather for a city."""
264
- return f"Weather in {city}: 22 {units}"
265
-
266
- conv = Conversation("openai/gpt-4", tools=registry)
267
- result = conv.ask("What's the weather in London?")
268
- ```
269
-
270
- 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:
271
-
272
- ```python
273
- # Auto-detect: uses native tool calling if available, simulation otherwise
274
- conv = Conversation("ollama/llama3.1:8b", tools=registry, simulated_tools="auto")
275
-
276
- # Force simulation even on capable models
277
- conv = Conversation("openai/gpt-4", tools=registry, simulated_tools=True)
278
-
279
- # Disable tool use entirely
280
- conv = Conversation("openai/gpt-4", tools=registry, simulated_tools=False)
281
- ```
282
-
283
- 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.
284
-
285
252
  ### Model Discovery
286
253
 
287
254
  Auto-detect available models from configured providers:
@@ -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":
@@ -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