prompture 0.0.29.dev8__py3-none-any.whl → 0.0.35__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prompture/__init__.py +146 -23
- prompture/_version.py +34 -0
- prompture/aio/__init__.py +74 -0
- prompture/async_conversation.py +607 -0
- prompture/async_core.py +803 -0
- prompture/async_driver.py +169 -0
- prompture/cache.py +469 -0
- prompture/callbacks.py +55 -0
- prompture/cli.py +63 -4
- prompture/conversation.py +631 -0
- prompture/core.py +876 -263
- prompture/cost_mixin.py +51 -0
- prompture/discovery.py +164 -0
- prompture/driver.py +168 -5
- prompture/drivers/__init__.py +173 -69
- prompture/drivers/airllm_driver.py +109 -0
- prompture/drivers/async_airllm_driver.py +26 -0
- prompture/drivers/async_azure_driver.py +117 -0
- prompture/drivers/async_claude_driver.py +107 -0
- prompture/drivers/async_google_driver.py +132 -0
- prompture/drivers/async_grok_driver.py +91 -0
- prompture/drivers/async_groq_driver.py +84 -0
- prompture/drivers/async_hugging_driver.py +61 -0
- prompture/drivers/async_lmstudio_driver.py +79 -0
- prompture/drivers/async_local_http_driver.py +44 -0
- prompture/drivers/async_ollama_driver.py +125 -0
- prompture/drivers/async_openai_driver.py +96 -0
- prompture/drivers/async_openrouter_driver.py +96 -0
- prompture/drivers/async_registry.py +129 -0
- prompture/drivers/azure_driver.py +36 -9
- prompture/drivers/claude_driver.py +251 -34
- prompture/drivers/google_driver.py +107 -38
- prompture/drivers/grok_driver.py +29 -32
- prompture/drivers/groq_driver.py +27 -26
- prompture/drivers/hugging_driver.py +6 -6
- prompture/drivers/lmstudio_driver.py +26 -13
- prompture/drivers/local_http_driver.py +6 -6
- prompture/drivers/ollama_driver.py +157 -23
- prompture/drivers/openai_driver.py +178 -9
- prompture/drivers/openrouter_driver.py +31 -25
- prompture/drivers/registry.py +306 -0
- prompture/field_definitions.py +106 -96
- prompture/logging.py +80 -0
- prompture/model_rates.py +217 -0
- prompture/runner.py +49 -47
- prompture/scaffold/__init__.py +1 -0
- prompture/scaffold/generator.py +84 -0
- prompture/scaffold/templates/Dockerfile.j2 +12 -0
- prompture/scaffold/templates/README.md.j2 +41 -0
- prompture/scaffold/templates/config.py.j2 +21 -0
- prompture/scaffold/templates/env.example.j2 +8 -0
- prompture/scaffold/templates/main.py.j2 +86 -0
- prompture/scaffold/templates/models.py.j2 +40 -0
- prompture/scaffold/templates/requirements.txt.j2 +5 -0
- prompture/server.py +183 -0
- prompture/session.py +117 -0
- prompture/settings.py +18 -1
- prompture/tools.py +219 -267
- prompture/tools_schema.py +254 -0
- prompture/validator.py +3 -3
- {prompture-0.0.29.dev8.dist-info → prompture-0.0.35.dist-info}/METADATA +117 -21
- prompture-0.0.35.dist-info/RECORD +66 -0
- {prompture-0.0.29.dev8.dist-info → prompture-0.0.35.dist-info}/WHEEL +1 -1
- prompture-0.0.29.dev8.dist-info/RECORD +0 -27
- {prompture-0.0.29.dev8.dist-info → prompture-0.0.35.dist-info}/entry_points.txt +0 -0
- {prompture-0.0.29.dev8.dist-info → prompture-0.0.35.dist-info}/licenses/LICENSE +0 -0
- {prompture-0.0.29.dev8.dist-info → prompture-0.0.35.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Function calling / tool use support for Prompture.
|
|
2
|
+
|
|
3
|
+
Provides :class:`ToolDefinition` for describing callable tools,
|
|
4
|
+
:class:`ToolRegistry` for managing a collection of tools, and
|
|
5
|
+
:func:`tool_from_function` to auto-generate tool schemas from type hints.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from prompture import ToolRegistry
|
|
10
|
+
|
|
11
|
+
registry = ToolRegistry()
|
|
12
|
+
|
|
13
|
+
@registry.tool
|
|
14
|
+
def get_weather(city: str, units: str = "celsius") -> str:
|
|
15
|
+
\"\"\"Get the current weather for a city.\"\"\"
|
|
16
|
+
return f"Weather in {city}: 22 {units}"
|
|
17
|
+
|
|
18
|
+
# Or register explicitly
|
|
19
|
+
registry.register(get_weather)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import inspect
|
|
25
|
+
import logging
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import Any, Callable, get_type_hints
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("prompture.tools_schema")
|
|
30
|
+
|
|
31
|
+
# Mapping from Python types to JSON Schema types
|
|
32
|
+
_TYPE_MAP: dict[type, str] = {
|
|
33
|
+
str: "string",
|
|
34
|
+
int: "integer",
|
|
35
|
+
float: "number",
|
|
36
|
+
bool: "boolean",
|
|
37
|
+
list: "array",
|
|
38
|
+
dict: "object",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _python_type_to_json_schema(annotation: Any) -> dict[str, Any]:
|
|
43
|
+
"""Convert a Python type annotation to a JSON Schema snippet."""
|
|
44
|
+
if annotation is inspect.Parameter.empty or annotation is None:
|
|
45
|
+
return {"type": "string"}
|
|
46
|
+
|
|
47
|
+
# Handle Optional[X] (Union[X, None])
|
|
48
|
+
origin = getattr(annotation, "__origin__", None)
|
|
49
|
+
args = getattr(annotation, "__args__", ())
|
|
50
|
+
|
|
51
|
+
if origin is type(None):
|
|
52
|
+
return {"type": "string"}
|
|
53
|
+
|
|
54
|
+
# Union types (Optional)
|
|
55
|
+
if origin is not None and hasattr(origin, "__name__") and origin.__name__ == "Union":
|
|
56
|
+
non_none = [a for a in args if a is not type(None)]
|
|
57
|
+
if len(non_none) == 1:
|
|
58
|
+
return _python_type_to_json_schema(non_none[0])
|
|
59
|
+
|
|
60
|
+
# list[X]
|
|
61
|
+
if origin is list and args:
|
|
62
|
+
return {"type": "array", "items": _python_type_to_json_schema(args[0])}
|
|
63
|
+
|
|
64
|
+
# dict[str, X]
|
|
65
|
+
if origin is dict:
|
|
66
|
+
return {"type": "object"}
|
|
67
|
+
|
|
68
|
+
# Simple types
|
|
69
|
+
json_type = _TYPE_MAP.get(annotation, "string")
|
|
70
|
+
return {"type": json_type}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ToolDefinition:
|
|
75
|
+
"""Describes a single callable tool the LLM can invoke.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
name: Unique tool identifier.
|
|
79
|
+
description: Human-readable description shown to the LLM.
|
|
80
|
+
parameters: JSON Schema describing the function parameters.
|
|
81
|
+
function: The Python callable to execute.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name: str
|
|
85
|
+
description: str
|
|
86
|
+
parameters: dict[str, Any]
|
|
87
|
+
function: Callable[..., Any]
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
# Serialisation helpers
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def to_openai_format(self) -> dict[str, Any]:
|
|
94
|
+
"""Serialise to OpenAI ``tools`` array element format."""
|
|
95
|
+
return {
|
|
96
|
+
"type": "function",
|
|
97
|
+
"function": {
|
|
98
|
+
"name": self.name,
|
|
99
|
+
"description": self.description,
|
|
100
|
+
"parameters": self.parameters,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def to_anthropic_format(self) -> dict[str, Any]:
|
|
105
|
+
"""Serialise to Anthropic ``tools`` array element format."""
|
|
106
|
+
return {
|
|
107
|
+
"name": self.name,
|
|
108
|
+
"description": self.description,
|
|
109
|
+
"input_schema": self.parameters,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def tool_from_function(fn: Callable[..., Any], *, name: str | None = None, description: str | None = None) -> ToolDefinition:
|
|
114
|
+
"""Build a :class:`ToolDefinition` by inspecting *fn*'s signature and docstring.
|
|
115
|
+
|
|
116
|
+
Parameters:
|
|
117
|
+
fn: The callable to wrap.
|
|
118
|
+
name: Override the tool name (defaults to ``fn.__name__``).
|
|
119
|
+
description: Override the description (defaults to the first line of the docstring).
|
|
120
|
+
"""
|
|
121
|
+
tool_name = name or fn.__name__
|
|
122
|
+
tool_desc = description or (inspect.getdoc(fn) or "").split("\n")[0] or f"Call {tool_name}"
|
|
123
|
+
|
|
124
|
+
sig = inspect.signature(fn)
|
|
125
|
+
try:
|
|
126
|
+
hints = get_type_hints(fn)
|
|
127
|
+
except Exception:
|
|
128
|
+
hints = {}
|
|
129
|
+
|
|
130
|
+
properties: dict[str, Any] = {}
|
|
131
|
+
required: list[str] = []
|
|
132
|
+
|
|
133
|
+
for param_name, param in sig.parameters.items():
|
|
134
|
+
if param_name == "self":
|
|
135
|
+
continue
|
|
136
|
+
annotation = hints.get(param_name, param.annotation)
|
|
137
|
+
prop = _python_type_to_json_schema(annotation)
|
|
138
|
+
|
|
139
|
+
# Use parameter name as description fallback
|
|
140
|
+
prop.setdefault("description", f"Parameter: {param_name}")
|
|
141
|
+
|
|
142
|
+
properties[param_name] = prop
|
|
143
|
+
|
|
144
|
+
if param.default is inspect.Parameter.empty:
|
|
145
|
+
required.append(param_name)
|
|
146
|
+
|
|
147
|
+
parameters: dict[str, Any] = {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": properties,
|
|
150
|
+
}
|
|
151
|
+
if required:
|
|
152
|
+
parameters["required"] = required
|
|
153
|
+
|
|
154
|
+
return ToolDefinition(
|
|
155
|
+
name=tool_name,
|
|
156
|
+
description=tool_desc,
|
|
157
|
+
parameters=parameters,
|
|
158
|
+
function=fn,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class ToolRegistry:
|
|
164
|
+
"""A collection of :class:`ToolDefinition` instances.
|
|
165
|
+
|
|
166
|
+
Supports decorator-based and explicit registration::
|
|
167
|
+
|
|
168
|
+
registry = ToolRegistry()
|
|
169
|
+
|
|
170
|
+
@registry.tool
|
|
171
|
+
def my_func(x: int) -> str:
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
registry.register(another_func)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
_tools: dict[str, ToolDefinition] = field(default_factory=dict)
|
|
178
|
+
|
|
179
|
+
# ------------------------------------------------------------------
|
|
180
|
+
# Registration
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
def register(
|
|
184
|
+
self,
|
|
185
|
+
fn: Callable[..., Any],
|
|
186
|
+
*,
|
|
187
|
+
name: str | None = None,
|
|
188
|
+
description: str | None = None,
|
|
189
|
+
) -> ToolDefinition:
|
|
190
|
+
"""Register *fn* as a tool and return the :class:`ToolDefinition`."""
|
|
191
|
+
td = tool_from_function(fn, name=name, description=description)
|
|
192
|
+
self._tools[td.name] = td
|
|
193
|
+
return td
|
|
194
|
+
|
|
195
|
+
def tool(self, fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
196
|
+
"""Decorator to register a function as a tool.
|
|
197
|
+
|
|
198
|
+
Returns the original function unchanged so it remains callable.
|
|
199
|
+
"""
|
|
200
|
+
self.register(fn)
|
|
201
|
+
return fn
|
|
202
|
+
|
|
203
|
+
def add(self, tool_def: ToolDefinition) -> None:
|
|
204
|
+
"""Add a pre-built :class:`ToolDefinition`."""
|
|
205
|
+
self._tools[tool_def.name] = tool_def
|
|
206
|
+
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
# Lookup
|
|
209
|
+
# ------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
def get(self, name: str) -> ToolDefinition | None:
|
|
212
|
+
return self._tools.get(name)
|
|
213
|
+
|
|
214
|
+
def __contains__(self, name: str) -> bool:
|
|
215
|
+
return name in self._tools
|
|
216
|
+
|
|
217
|
+
def __len__(self) -> int:
|
|
218
|
+
return len(self._tools)
|
|
219
|
+
|
|
220
|
+
def __bool__(self) -> bool:
|
|
221
|
+
return bool(self._tools)
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def names(self) -> list[str]:
|
|
225
|
+
return list(self._tools.keys())
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def definitions(self) -> list[ToolDefinition]:
|
|
229
|
+
return list(self._tools.values())
|
|
230
|
+
|
|
231
|
+
# ------------------------------------------------------------------
|
|
232
|
+
# Serialisation
|
|
233
|
+
# ------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
def to_openai_format(self) -> list[dict[str, Any]]:
|
|
236
|
+
return [td.to_openai_format() for td in self._tools.values()]
|
|
237
|
+
|
|
238
|
+
def to_anthropic_format(self) -> list[dict[str, Any]]:
|
|
239
|
+
return [td.to_anthropic_format() for td in self._tools.values()]
|
|
240
|
+
|
|
241
|
+
# ------------------------------------------------------------------
|
|
242
|
+
# Execution
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def execute(self, name: str, arguments: dict[str, Any]) -> Any:
|
|
246
|
+
"""Execute a registered tool by name with the given arguments.
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
KeyError: If no tool with *name* is registered.
|
|
250
|
+
"""
|
|
251
|
+
td = self._tools.get(name)
|
|
252
|
+
if td is None:
|
|
253
|
+
raise KeyError(f"Tool not registered: {name!r}")
|
|
254
|
+
return td.function(**arguments)
|
prompture/validator.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
try:
|
|
5
5
|
import jsonschema
|
|
@@ -7,7 +7,7 @@ except Exception:
|
|
|
7
7
|
jsonschema = None
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def validate_against_schema(instance_json: str, schema:
|
|
10
|
+
def validate_against_schema(instance_json: str, schema: dict[str, Any]) -> dict[str, Any]:
|
|
11
11
|
"""Valida el JSON (string) contra un JSON Schema.
|
|
12
12
|
Devuelve dict con ok: bool y detalles.
|
|
13
13
|
"""
|
|
@@ -28,4 +28,4 @@ def validate_against_schema(instance_json: str, schema: Dict[str,Any]) -> Dict[s
|
|
|
28
28
|
jsonschema.validate(instance=data, schema=schema)
|
|
29
29
|
return {"ok": True, "data": data}
|
|
30
30
|
except jsonschema.ValidationError as e:
|
|
31
|
-
return {"ok": False, "error": str(e), "data": data}
|
|
31
|
+
return {"ok": False, "error": str(e), "data": data}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prompture
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.35
|
|
4
4
|
Summary: Ask LLMs to return structured JSON and run cross-model tests. API-first.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Author-email: Juan Denis <juan@vene.co>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jhd3197/prompture
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
9
|
Classifier: Operating System :: OS Independent
|
|
11
10
|
Requires-Python: >=3.9
|
|
12
11
|
Description-Content-Type: text/markdown
|
|
@@ -18,26 +17,33 @@ Requires-Dist: groq>=0.4.0
|
|
|
18
17
|
Requires-Dist: httpx>=0.25.0
|
|
19
18
|
Requires-Dist: jsonschema>=4.0
|
|
20
19
|
Requires-Dist: openai>=1.0.0
|
|
20
|
+
Requires-Dist: pandas>=1.3.0
|
|
21
21
|
Requires-Dist: pydantic>=1.10
|
|
22
22
|
Requires-Dist: pydantic-settings>=2.0
|
|
23
23
|
Requires-Dist: python-dotenv>=0.19.0
|
|
24
|
+
Requires-Dist: python-toon>=0.1.0
|
|
24
25
|
Requires-Dist: requests>=2.28
|
|
25
26
|
Requires-Dist: python-dateutil>=2.9.0
|
|
26
27
|
Requires-Dist: tukuy>=0.0.6
|
|
27
28
|
Requires-Dist: pyyaml>=6.0
|
|
28
29
|
Provides-Extra: test
|
|
29
30
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.8.0; extra == "dev"
|
|
36
|
+
Provides-Extra: airllm
|
|
37
|
+
Requires-Dist: airllm>=2.8.0; extra == "airllm"
|
|
38
|
+
Provides-Extra: redis
|
|
39
|
+
Requires-Dist: redis>=4.0; extra == "redis"
|
|
40
|
+
Provides-Extra: serve
|
|
41
|
+
Requires-Dist: fastapi>=0.100; extra == "serve"
|
|
42
|
+
Requires-Dist: uvicorn[standard]>=0.20; extra == "serve"
|
|
43
|
+
Requires-Dist: sse-starlette>=1.6; extra == "serve"
|
|
44
|
+
Provides-Extra: scaffold
|
|
45
|
+
Requires-Dist: jinja2>=3.0; extra == "scaffold"
|
|
36
46
|
Dynamic: license-file
|
|
37
|
-
Dynamic: provides-extra
|
|
38
|
-
Dynamic: requires-dist
|
|
39
|
-
Dynamic: requires-python
|
|
40
|
-
Dynamic: summary
|
|
41
47
|
|
|
42
48
|
# Prompture
|
|
43
49
|
|
|
@@ -53,12 +59,13 @@ Dynamic: summary
|
|
|
53
59
|
## ✨ Features
|
|
54
60
|
|
|
55
61
|
- ✅ **Structured output** → JSON schema enforcement, or direct **Pydantic** instances
|
|
62
|
+
- ✅ **TOON input conversion** → 45-60% token savings for structured data analysis with `extract_from_data()` and `extract_from_pandas()`
|
|
56
63
|
- ✅ **Stepwise extraction** → Per-field prompts, with smart type conversion (incl. shorthand numbers)
|
|
57
64
|
- ✅ **Multi-driver** → OpenAI, Azure, Claude, Ollama, LM Studio, Google, Groq, OpenRouter, Grok, HTTP, Mock, HuggingFace (via `get_driver()`)
|
|
58
65
|
- ✅ **Usage & cost** → Token + $ tracking on every call (`usage` from driver meta)
|
|
59
66
|
- ✅ **AI cleanup** → Optional LLM pass to fix malformed JSON
|
|
60
67
|
- ✅ **Batch testing** → Define suites and compare models (spec-driven)
|
|
61
|
-
|
|
68
|
+
- 🧪 **Experimental TOON output** → Request Token-Oriented Object Notation when you need ultra-compact text
|
|
62
69
|
<br>
|
|
63
70
|
|
|
64
71
|
> [!TIP]
|
|
@@ -118,6 +125,25 @@ export LMSTUDIO_ENDPOINT=...
|
|
|
118
125
|
|
|
119
126
|
---
|
|
120
127
|
|
|
128
|
+
## 🔍 Model Discovery
|
|
129
|
+
|
|
130
|
+
Prompture can auto-detect available models from your configured environment. This is especially useful for local setups (like Ollama) or when you want to see which models are available to your application.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from prompture import get_available_models
|
|
134
|
+
|
|
135
|
+
# Returns a list of strings like ["openai/gpt-4o", "ollama/llama3:latest", ...]
|
|
136
|
+
models = get_available_models()
|
|
137
|
+
|
|
138
|
+
for model in models:
|
|
139
|
+
print(f"Found: {model}")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **Static Drivers** (OpenAI, Claude, Azure, etc.): Returns models listed in the driver's `MODEL_PRICING` configuration if the driver is configured (API key present).
|
|
143
|
+
- **Dynamic Drivers** (Ollama): Queries the local endpoint (e.g., `http://localhost:11434/api/tags`) to fetch currently installed models.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
121
147
|
## Quickstart: Pydantic in one line (auto driver)
|
|
122
148
|
|
|
123
149
|
Use `extract_with_model` for a single LLM call that fills your Pydantic model.
|
|
@@ -144,6 +170,55 @@ print(person.dict())
|
|
|
144
170
|
|
|
145
171
|
**Why start here?** It's fast (one call), cost-efficient, and returns a validated Pydantic instance.
|
|
146
172
|
|
|
173
|
+
|
|
174
|
+
## 🚀 TOON Input Conversion: 45-60% Token Savings
|
|
175
|
+
|
|
176
|
+
Analyze structured data with automatic TOON (Token-Oriented Object Notation) conversion for massive token savings.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from prompture import extract_from_data, extract_from_pandas
|
|
180
|
+
|
|
181
|
+
# Your product data
|
|
182
|
+
products = [
|
|
183
|
+
{"id": 1, "name": "Laptop", "price": 999.99, "rating": 4.5},
|
|
184
|
+
{"id": 2, "name": "Book", "price": 19.99, "rating": 4.2},
|
|
185
|
+
{"id": 3, "name": "Headphones", "price": 149.99, "rating": 4.7}
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
# Ask questions about your data - automatically uses TOON format for 60%+ token savings
|
|
189
|
+
result = extract_from_data(
|
|
190
|
+
data=products,
|
|
191
|
+
question="What is the average price and highest rated product?",
|
|
192
|
+
json_schema={
|
|
193
|
+
"type": "object",
|
|
194
|
+
"properties": {
|
|
195
|
+
"average_price": {"type": "number"},
|
|
196
|
+
"highest_rated": {"type": "string"}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
model_name="openai/gpt-4"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
print(result["json_object"])
|
|
203
|
+
# {"average_price": 389.96, "highest_rated": "Headphones"}
|
|
204
|
+
|
|
205
|
+
print(f"Token savings: {result['token_savings']['percentage_saved']}%")
|
|
206
|
+
# Token savings: 62.3%
|
|
207
|
+
|
|
208
|
+
# Works with Pandas DataFrames too!
|
|
209
|
+
import pandas as pd
|
|
210
|
+
df = pd.DataFrame(products)
|
|
211
|
+
result = extract_from_pandas(df=df, question="...", json_schema=schema, model_name="openai/gpt-4")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Preview token savings without LLM calls:**
|
|
215
|
+
```bash
|
|
216
|
+
python examples/token_comparison_utility.py
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
> **Note:** Both `python-toon` and `pandas` are now included by default when you install Prompture!
|
|
220
|
+
|
|
221
|
+
---
|
|
147
222
|
---
|
|
148
223
|
|
|
149
224
|
## 📋 Field Definitions
|
|
@@ -238,6 +313,25 @@ resp2 = extract_and_jsonify(
|
|
|
238
313
|
print(resp2["json_object"], resp2["usage"])
|
|
239
314
|
```
|
|
240
315
|
|
|
316
|
+
### Experimental TOON output
|
|
317
|
+
|
|
318
|
+
Prompture can ask for TOON (Token-Oriented Object Notation) instead of JSON by setting `output_format="toon"` on `ask_for_json`, `extract_and_jsonify`, `manual_extract_and_jsonify`, or `extract_with_model`. The LLM is still instructed to return JSON (for reliability); Prompture parses it and emits a TOON string via `python-toon`.
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
result = extract_and_jsonify(
|
|
322
|
+
text="Alice Johnson is a 30-year-old data scientist...",
|
|
323
|
+
json_schema=schema,
|
|
324
|
+
model_name="lmstudio/deepseek/deepseek-r1-0528-qwen3-8b",
|
|
325
|
+
output_format="toon",
|
|
326
|
+
)
|
|
327
|
+
print(result["toon_string"]) # TOON text generated locally
|
|
328
|
+
print(result["json_object"]) # regular dict parsed from the JSON response
|
|
329
|
+
# result["json_string"] still contains the original JSON text
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
> [!IMPORTANT]
|
|
333
|
+
> TOON output is **experimental**. General-purpose models often emit more verbose completions when asked for TOON, so total token usage can increase (see `toon_token_analysis.md`). Treat it as an opt-in mode until TOON-aware fine-tunes or adapters are available.
|
|
334
|
+
|
|
241
335
|
### Return shape (JSON helpers)
|
|
242
336
|
|
|
243
337
|
```python
|
|
@@ -292,12 +386,15 @@ print(res["usage"]) # includes per-field usage and totals
|
|
|
292
386
|
|
|
293
387
|
## Manual control with logging
|
|
294
388
|
|
|
295
|
-
`manual_extract_and_jsonify` is like `extract_and_jsonify` but
|
|
389
|
+
`manual_extract_and_jsonify` is like `extract_and_jsonify` but lets you provide your own driver.
|
|
390
|
+
Enable library logging via Python's standard `logging` module:
|
|
296
391
|
|
|
297
392
|
```python
|
|
298
|
-
|
|
393
|
+
import logging
|
|
394
|
+
from prompture import manual_extract_and_jsonify, configure_logging
|
|
299
395
|
from prompture.drivers import get_driver
|
|
300
|
-
|
|
396
|
+
|
|
397
|
+
configure_logging(logging.DEBUG) # see internal debug output
|
|
301
398
|
|
|
302
399
|
driver = get_driver("ollama")
|
|
303
400
|
res = manual_extract_and_jsonify(
|
|
@@ -310,7 +407,6 @@ res = manual_extract_and_jsonify(
|
|
|
310
407
|
},
|
|
311
408
|
model_name="llama3.1:8b",
|
|
312
409
|
options={"temperature": 0.2},
|
|
313
|
-
verbose_level=LogLevel.DEBUG # TRACE for full prompts/results
|
|
314
410
|
)
|
|
315
411
|
print(res["json_object"])
|
|
316
412
|
```
|
|
@@ -359,7 +455,7 @@ This example script compares multiple Ollama models on a complex task of extract
|
|
|
359
455
|
* Add `description` to schema fields (or Pydantic field metadata) for better extractions.
|
|
360
456
|
* Start with **one-shot Pydantic**; switch specific fields to **stepwise** if they’re noisy.
|
|
361
457
|
* Track usage/cost before scaling; tweak `temperature` in `options` if consistency wobbles.
|
|
362
|
-
* Use `
|
|
458
|
+
* Use `configure_logging(logging.DEBUG)` in dev to see internal debug output and tighten your specs.
|
|
363
459
|
|
|
364
460
|
---
|
|
365
461
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
prompture/__init__.py,sha256=IzJ-QVN9MYX0VMjPEqsulDDDBvHKQpR9GtBYptSmRBg,4690
|
|
2
|
+
prompture/_version.py,sha256=w77E3DIE0tp22UaCyYn9461JfWMpEPhsv91t-4n4Bjw,706
|
|
3
|
+
prompture/async_conversation.py,sha256=i-cbV4WfPxU6m-SgeC7X-xl_Hg20B6zK-U4CJjeLFIA,23529
|
|
4
|
+
prompture/async_core.py,sha256=s8G0nGUGR1Bf_BQG9_FcQRpveSnJKkEwcWNfbAJaSkg,29208
|
|
5
|
+
prompture/async_driver.py,sha256=tyk8qc7VjMCyfP9MBAG15-EXa2kBM3tVhpJABGWAgFU,6075
|
|
6
|
+
prompture/cache.py,sha256=4dfQDMsEZ9JMQDXLOkiugPmmMJQIfKVE8rTAKDH4oL8,14401
|
|
7
|
+
prompture/callbacks.py,sha256=JPDqWGzPIzv44l54ocmezlYVBnbKPDEEXRrLdluWGAo,1731
|
|
8
|
+
prompture/cli.py,sha256=tNiIddRmgC1BomjY5O1VVVAwvqHVzF8IHmQrM-cG2wQ,2902
|
|
9
|
+
prompture/conversation.py,sha256=Kwz0iCQX8-bpxXVEX6SwNqbjFKj-NMGxxx6w7AfuqV8,24475
|
|
10
|
+
prompture/core.py,sha256=V__4YAt42WKf7CNiFRiET1oT2GFFIElOLFFF7mSFx-o,55735
|
|
11
|
+
prompture/cost_mixin.py,sha256=_spz84i8Qsplh6V3GkWyXXSUE4EwGy2IsbcsU2LEBxs,1918
|
|
12
|
+
prompture/discovery.py,sha256=_mSeeIQfRl3pHDPm84Mc2LDcOSwfkUanITXWeRPfv5E,7116
|
|
13
|
+
prompture/driver.py,sha256=3rLu-QZJBaOE2JwfRDt5xWDHuD5NdMBLfiYq4lMYyWs,7157
|
|
14
|
+
prompture/field_definitions.py,sha256=PLvxq2ot-ngJ8JbWkkZ-XLtM1wvjUQ3TL01vSEo-a6E,21368
|
|
15
|
+
prompture/logging.py,sha256=SkFO26_56Zai05vW8kTq3jvJudfLG2ipI5qNHaXKH3g,2574
|
|
16
|
+
prompture/model_rates.py,sha256=qtZUjsCVskA9LyG73JklG_kjKJHABA6ldBmBX0UzlSQ,6415
|
|
17
|
+
prompture/runner.py,sha256=lHe2L2jqY1pDXoKNPJALN9lAm-Q8QOY8C8gw-vM9VrM,4213
|
|
18
|
+
prompture/server.py,sha256=W6Kn6Et8nG5twXjD2wKn_N9yplGjz5Z-2naeI_UPd1Y,6198
|
|
19
|
+
prompture/session.py,sha256=FldK3cKq_jO0-beukVOhIiwsYWb6U_lLBlAERx95aaM,3821
|
|
20
|
+
prompture/settings.py,sha256=o-zsYpxRvSg-ICGWqqVNEoJG23GCMBLlkC7RPXpouSw,1976
|
|
21
|
+
prompture/tools.py,sha256=PmFbGHTWYWahpJOG6BLlM0Y-EG6S37IFW57C-8GdsXo,36449
|
|
22
|
+
prompture/tools_schema.py,sha256=JVc0dxC4aIHIUlgE8yFCcn1gPzJ3unTMVmZ8Ec04aD0,7764
|
|
23
|
+
prompture/validator.py,sha256=FY_VjIVEbjG2nwzh-r6l23Kt3UzaLyCis8_pZMNGHBA,993
|
|
24
|
+
prompture/aio/__init__.py,sha256=bKqTu4Jxld16aP_7SP9wU5au45UBIb041ORo4E4HzVo,1810
|
|
25
|
+
prompture/drivers/__init__.py,sha256=yj-z7_RMbfxP8zaKjQFZFcxGdf8Q2_KNQ6RTo7SPEpQ,6984
|
|
26
|
+
prompture/drivers/airllm_driver.py,sha256=SaTh7e7Plvuct_TfRqQvsJsKHvvM_3iVqhBtlciM-Kw,3858
|
|
27
|
+
prompture/drivers/async_airllm_driver.py,sha256=1hIWLXfyyIg9tXaOE22tLJvFyNwHnOi1M5BIKnV8ysk,908
|
|
28
|
+
prompture/drivers/async_azure_driver.py,sha256=vRp1PlOB87OLUbEZJEp7En3tvadG956Q6AV2o9UmyLA,4196
|
|
29
|
+
prompture/drivers/async_claude_driver.py,sha256=w9HRE7njh1N9BzX4QtsrUnMakKBb-nykBkvTwLmoFnw,3825
|
|
30
|
+
prompture/drivers/async_google_driver.py,sha256=qKMl6R60xBPQpsjCd9acXiXphVE5QxKbPSjxal7Sq2U,5656
|
|
31
|
+
prompture/drivers/async_grok_driver.py,sha256=yQgdBHiyyweueWDKChm9HnWzvbtWz60SNATsRKQY-RQ,3279
|
|
32
|
+
prompture/drivers/async_groq_driver.py,sha256=iShPE_YbR3oObHD3joFJDSmFeyCmaisLl7nwILbVux4,2808
|
|
33
|
+
prompture/drivers/async_hugging_driver.py,sha256=IblxqU6TpNUiigZ0BCgNkAgzpUr2FtPHJOZnOZMnHF0,2152
|
|
34
|
+
prompture/drivers/async_lmstudio_driver.py,sha256=7iQ-Fqezlpj4O13QC9oPjylGCINU5KvSRkwL52_2Cf0,2810
|
|
35
|
+
prompture/drivers/async_local_http_driver.py,sha256=qoigIf-w3_c2dbVdM6m1e2RMAWP4Gk4VzVs5hM3lPvQ,1609
|
|
36
|
+
prompture/drivers/async_ollama_driver.py,sha256=vRd2VIl412d6WVSo8vmZg0GBYUo7gBj-S2_55PpUWbk,4511
|
|
37
|
+
prompture/drivers/async_openai_driver.py,sha256=jHtSA_MeeIwGeE9o9F1ZsKTNgGGA7xF3WbGZgD8ACEU,3305
|
|
38
|
+
prompture/drivers/async_openrouter_driver.py,sha256=OKL4MfRAopXaMevf6A6WcAytyvWr0tWO_BmshdI0fSY,3516
|
|
39
|
+
prompture/drivers/async_registry.py,sha256=gJDr60688MZx9rbP-I8Um1gLeDwfRynv5SHsYjHjEGk,4263
|
|
40
|
+
prompture/drivers/azure_driver.py,sha256=4IAzdKqcORgVEDUj6itkVmJUg1ayo4HXSfqLKzIGnlM,5460
|
|
41
|
+
prompture/drivers/claude_driver.py,sha256=mZE3flcZImNCpMl803hLDd5KrkbYZt4VFQvF1ezs9qU,11269
|
|
42
|
+
prompture/drivers/google_driver.py,sha256=Ysa1ZZEAPEKHCJFCBiJtB4K-sGvsYti4hGBv_85nowY,8454
|
|
43
|
+
prompture/drivers/grok_driver.py,sha256=_ZQfmE4NfzHTj-nTEtBkWFbypjCF_4aWFODAaxxEjog,5027
|
|
44
|
+
prompture/drivers/groq_driver.py,sha256=QfMhzKDhTQyU7yG0oayCojunaKPdxrxzYo-u87_uN18,3928
|
|
45
|
+
prompture/drivers/hugging_driver.py,sha256=gZir3XnM77VfYIdnu3S1pRftlZJM6G3L8bgGn5esg-Q,2346
|
|
46
|
+
prompture/drivers/lmstudio_driver.py,sha256=AuhmKS4DBvxEPSfjL-uN7wnAyFWoB7Wjk1o0Oscan4I,4010
|
|
47
|
+
prompture/drivers/local_http_driver.py,sha256=QJgEf9kAmy8YZ5fb8FHnWuhoDoZYNd8at4jegzNVJH0,1658
|
|
48
|
+
prompture/drivers/ollama_driver.py,sha256=o44HCKbljLRN6TCMylj0X4RjvtI_VaZdoDZXsLQBkH4,9577
|
|
49
|
+
prompture/drivers/openai_driver.py,sha256=Dg0YiduDSyTiLMG4F9YuWKB0vbzVuOYOog5CpWgbxxo,9897
|
|
50
|
+
prompture/drivers/openrouter_driver.py,sha256=WH48KEkafuxFX6b55FzwT57tUlmbwYlHSeNsIxWvM4o,5141
|
|
51
|
+
prompture/drivers/registry.py,sha256=Dg_5w9alnIPKhOnsR9Xspuf5T7roBGu0r_L2Cf-UhXs,9926
|
|
52
|
+
prompture/scaffold/__init__.py,sha256=aitUxBV0MpjC7Od3iG8WUzcC7tGPXSt3oMzUBX8UDwQ,60
|
|
53
|
+
prompture/scaffold/generator.py,sha256=5QTHdWEXB7ADqOttfU7NgoxuaofNQnObzzI7NIPWFgo,2387
|
|
54
|
+
prompture/scaffold/templates/Dockerfile.j2,sha256=ukox6eVzQMVw-hAaFmNRL5HTrXw2Z0RB6g-vvbMVeu8,207
|
|
55
|
+
prompture/scaffold/templates/README.md.j2,sha256=xFgKnEP_JmLiiwD1QahNWdyKC85smyhOiIBCmN2U3y0,935
|
|
56
|
+
prompture/scaffold/templates/config.py.j2,sha256=q1LOnLlGzgJHPQz5geZ2AvrB-DskkLzay_CSj26hthE,529
|
|
57
|
+
prompture/scaffold/templates/env.example.j2,sha256=eESKr1KWgyrczO6d-nwAhQwSpf_G-T6P9gmHMhR9Sqc,246
|
|
58
|
+
prompture/scaffold/templates/main.py.j2,sha256=TEgc5OvsZOEX0JthkSW1NI_yLwgoeVN_x97Ibg-vyWY,2632
|
|
59
|
+
prompture/scaffold/templates/models.py.j2,sha256=JrZ99GCVK6TKWapskVRSwCssGrTu5cGZ_r46fOhY2GE,858
|
|
60
|
+
prompture/scaffold/templates/requirements.txt.j2,sha256=m3S5fi1hq9KG9l_9j317rjwWww0a43WMKd8VnUWv2A4,102
|
|
61
|
+
prompture-0.0.35.dist-info/licenses/LICENSE,sha256=0HgDepH7aaHNFhHF-iXuW6_GqDfYPnVkjtiCAZ4yS8I,1060
|
|
62
|
+
prompture-0.0.35.dist-info/METADATA,sha256=UZZi4UWdcyxEwwdZDeM_8A6bTRobx3zd8R0T0-mjQ1Y,18478
|
|
63
|
+
prompture-0.0.35.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
64
|
+
prompture-0.0.35.dist-info/entry_points.txt,sha256=AFPG3lJR86g4IJMoWQUW5Ph7G6MLNWG3A2u2Tp9zkp8,48
|
|
65
|
+
prompture-0.0.35.dist-info/top_level.txt,sha256=to86zq_kjfdoLeAxQNr420UWqT0WzkKoZ509J7Qr2t4,10
|
|
66
|
+
prompture-0.0.35.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
prompture/__init__.py,sha256=bZb1qaYkFU8nDXwNsUTwXyawK55lUhbw2EteimolZuk,1870
|
|
2
|
-
prompture/cli.py,sha256=vA86GNjtKSHz8eRMl5YDaT9HHIWuhkeJtfx8jqTaqtM,809
|
|
3
|
-
prompture/core.py,sha256=aqhYIRlt39awsiDP16gg3cCS_Fua3JhW3eFqWOnWoVQ,35962
|
|
4
|
-
prompture/driver.py,sha256=w8pdXHujImIGF3ee8rkG8f6-UD0h2jLHhucSPInRrYI,989
|
|
5
|
-
prompture/field_definitions.py,sha256=6kDMYNedccTK5l2L_I8_NI3_av-iYHqGPwkKDy8214c,21731
|
|
6
|
-
prompture/runner.py,sha256=5xwal3iBQQj4_q7l3Rjr0e3RrUMJPaPDLiEchO0mmHo,4192
|
|
7
|
-
prompture/settings.py,sha256=vHRkBAZNP6yRsI2Sm4FMa_FCw0Zxy2VX97ooiVYWvks,1500
|
|
8
|
-
prompture/tools.py,sha256=yULrVyhyE-rQF7Mvwe4zNFbB-hSDGGSOr7mCVxM0I60,38965
|
|
9
|
-
prompture/validator.py,sha256=oLzVsNveHuF-N_uOd11_uDa9Q5rFyo0wrk_l1N4zqDk,996
|
|
10
|
-
prompture/drivers/__init__.py,sha256=IQ7DsWC_FP45h2CprWRhQ7lKi3-9ZO6CgweNX6IxTUA,3896
|
|
11
|
-
prompture/drivers/azure_driver.py,sha256=GROhK3hqMfMurnEgpAawa1DPS-FhOU0YQcgy9SNGTzM,4622
|
|
12
|
-
prompture/drivers/claude_driver.py,sha256=ZEHQNqNThLZ0p-WmGVuKiNyiudGYGP07xIzbgZhLY1g,3293
|
|
13
|
-
prompture/drivers/google_driver.py,sha256=SOo02nJuNJiOE_pcGCo-LAmz7DA9pOazSOqI29K99zo,4771
|
|
14
|
-
prompture/drivers/grok_driver.py,sha256=Xp6L75oL3dN8St8_m46C_5bM8FcaIdNKUASAt9kZ39w,5003
|
|
15
|
-
prompture/drivers/groq_driver.py,sha256=91WGXP8G5dO0beuFO8FehZszlDC_X9hv_yPzQRGmcqw,3920
|
|
16
|
-
prompture/drivers/hugging_driver.py,sha256=rngz7hIR7l-9M_xe4EjWPaBqdyPFHdQsqnDDy9gm5So,2357
|
|
17
|
-
prompture/drivers/lmstudio_driver.py,sha256=Umy1kT211TAxxSPyQrtZnIGIZgqFeSV87FLTiPFF0CY,3455
|
|
18
|
-
prompture/drivers/local_http_driver.py,sha256=S2diikvtQOQHF7fB07zU2X0QWkej4Of__rJgaU2C6FI,1669
|
|
19
|
-
prompture/drivers/ollama_driver.py,sha256=fq_eFgwmCT3SK1D-ICHjxLjcm_An0suwkFIWC38xsS0,4681
|
|
20
|
-
prompture/drivers/openai_driver.py,sha256=9q9OjQslquRFvIl1Hd9JVmFFFVh6OBIWrFulw1mkYWg,3976
|
|
21
|
-
prompture/drivers/openrouter_driver.py,sha256=GKvLOFDhsyopH-k3iaD3VWllm7xbGuopRSA02MfCKoM,5031
|
|
22
|
-
prompture-0.0.29.dev8.dist-info/licenses/LICENSE,sha256=0HgDepH7aaHNFhHF-iXuW6_GqDfYPnVkjtiCAZ4yS8I,1060
|
|
23
|
-
prompture-0.0.29.dev8.dist-info/METADATA,sha256=zb5atZWplRLdhVAKLHHv3Mt0BLPFRZn--q7qFv_TnMs,14432
|
|
24
|
-
prompture-0.0.29.dev8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
prompture-0.0.29.dev8.dist-info/entry_points.txt,sha256=AFPG3lJR86g4IJMoWQUW5Ph7G6MLNWG3A2u2Tp9zkp8,48
|
|
26
|
-
prompture-0.0.29.dev8.dist-info/top_level.txt,sha256=to86zq_kjfdoLeAxQNr420UWqT0WzkKoZ509J7Qr2t4,10
|
|
27
|
-
prompture-0.0.29.dev8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|