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.
- {prompture-0.0.47 → prompture-0.0.47.dev1}/PKG-INFO +2 -35
- {prompture-0.0.47 → prompture-0.0.47.dev1}/README.md +1 -34
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/_version.py +2 -2
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_conversation.py +2 -87
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/conversation.py +2 -87
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_grok_driver.py +9 -23
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_groq_driver.py +9 -23
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_lmstudio_driver.py +2 -10
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_moonshot_driver.py +12 -32
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_openrouter_driver.py +17 -43
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/grok_driver.py +9 -23
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/groq_driver.py +9 -23
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/lmstudio_driver.py +2 -11
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/moonshot_driver.py +12 -32
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/openrouter_driver.py +10 -34
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/tools_schema.py +0 -22
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/PKG-INFO +2 -35
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/SOURCES.txt +0 -2
- prompture-0.0.47/VERSION +0 -1
- prompture-0.0.47/prompture/simulated_tools.py +0 -115
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-example/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-field/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-persona/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-test/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/add-tool/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.claude/skills/update-pricing/SKILL.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.env.copy +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/FUNDING.yml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/scripts/update_docs_version.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/scripts/update_wrapper_version.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/dev.yml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/documentation.yml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/.github/workflows/publish.yml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/CLAUDE.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/LICENSE +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/MANIFEST.in +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/ROADMAP.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/_static/custom.css +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/_templates/footer.html +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/core.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/drivers.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/field_definitions.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/index.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/runner.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/tools.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/api/validator.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/conf.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/contributing.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/examples.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/field_definitions_reference.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/index.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/installation.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/quickstart.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/docs/source/toon_input_guide.rst +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/README.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/README.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/pyproject.toml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_json/test.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/README.md +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/packages/llm_to_toon/test.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/agent.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/agent_types.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/aio/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_agent.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_core.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/async_groups.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cache.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/callbacks.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cli.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/core.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/cost_mixin.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/discovery.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/airllm_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_azure_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_claude_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_google_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_openai_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_registry.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/async_zai_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/azure_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/claude_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/google_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/hugging_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/local_http_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/modelscope_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/ollama_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/openai_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/registry.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/vision_helpers.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/drivers/zai_driver.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/field_definitions.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/group_types.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/groups.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/image.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/ledger.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/logging.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/model_rates.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/persistence.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/persona.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/runner.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/__init__.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/generator.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/serialization.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/server.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/session.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/settings.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/tools.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture/validator.py +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/dependency_links.txt +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/entry_points.txt +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/requires.txt +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/prompture.egg-info/top_level.txt +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/pyproject.toml +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/requirements.txt +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/setup.cfg +0 -0
- {prompture-0.0.47 → prompture-0.0.47.dev1}/test.py +0 -0
- {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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|