prompture 0.0.35.dev1__tar.gz → 0.0.36__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 (125) hide show
  1. prompture-0.0.36/PKG-INFO +369 -0
  2. prompture-0.0.36/README.md +322 -0
  3. prompture-0.0.36/VERSION +1 -0
  4. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/_version.py +2 -2
  5. prompture-0.0.36/prompture.egg-info/PKG-INFO +369 -0
  6. prompture-0.0.35.dev1/PKG-INFO +0 -464
  7. prompture-0.0.35.dev1/README.md +0 -417
  8. prompture-0.0.35.dev1/VERSION +0 -1
  9. prompture-0.0.35.dev1/prompture.egg-info/PKG-INFO +0 -464
  10. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/add-driver/SKILL.md +0 -0
  11. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/add-driver/references/driver-template.md +0 -0
  12. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/add-example/SKILL.md +0 -0
  13. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/add-field/SKILL.md +0 -0
  14. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/add-test/SKILL.md +0 -0
  15. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/run-tests/SKILL.md +0 -0
  16. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  17. {prompture-0.0.35.dev1 → prompture-0.0.36}/.claude/skills/update-pricing/SKILL.md +0 -0
  18. {prompture-0.0.35.dev1 → prompture-0.0.36}/.env.copy +0 -0
  19. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/FUNDING.yml +0 -0
  20. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/scripts/update_docs_version.py +0 -0
  21. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/scripts/update_wrapper_version.py +0 -0
  22. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/workflows/dev.yml +0 -0
  23. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/workflows/documentation.yml +0 -0
  24. {prompture-0.0.35.dev1 → prompture-0.0.36}/.github/workflows/publish.yml +0 -0
  25. {prompture-0.0.35.dev1 → prompture-0.0.36}/.mcp.json +0 -0
  26. {prompture-0.0.35.dev1 → prompture-0.0.36}/0.23.0 +0 -0
  27. {prompture-0.0.35.dev1 → prompture-0.0.36}/8 +0 -0
  28. {prompture-0.0.35.dev1 → prompture-0.0.36}/CLAUDE.md +0 -0
  29. {prompture-0.0.35.dev1 → prompture-0.0.36}/LICENSE +0 -0
  30. {prompture-0.0.35.dev1 → prompture-0.0.36}/MANIFEST.in +0 -0
  31. {prompture-0.0.35.dev1 → prompture-0.0.36}/ROADMAP.md +0 -0
  32. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/_static/custom.css +0 -0
  33. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/core.rst +0 -0
  34. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/drivers.rst +0 -0
  35. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/field_definitions.rst +0 -0
  36. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/index.rst +0 -0
  37. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/runner.rst +0 -0
  38. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/tools.rst +0 -0
  39. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/api/validator.rst +0 -0
  40. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/conf.py +0 -0
  41. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/contributing.rst +0 -0
  42. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/examples.rst +0 -0
  43. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/field_definitions_reference.rst +0 -0
  44. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/index.rst +0 -0
  45. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/installation.rst +0 -0
  46. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/quickstart.rst +0 -0
  47. {prompture-0.0.35.dev1 → prompture-0.0.36}/docs/source/toon_input_guide.rst +0 -0
  48. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/README.md +0 -0
  49. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_json/README.md +0 -0
  50. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  51. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_json/pyproject.toml +0 -0
  52. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_json/test.py +0 -0
  53. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_toon/README.md +0 -0
  54. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  55. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_toon/pyproject.toml +0 -0
  56. {prompture-0.0.35.dev1 → prompture-0.0.36}/packages/llm_to_toon/test.py +0 -0
  57. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/__init__.py +0 -0
  58. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/aio/__init__.py +0 -0
  59. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/async_conversation.py +0 -0
  60. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/async_core.py +0 -0
  61. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/async_driver.py +0 -0
  62. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/cache.py +0 -0
  63. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/callbacks.py +0 -0
  64. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/cli.py +0 -0
  65. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/conversation.py +0 -0
  66. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/core.py +0 -0
  67. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/cost_mixin.py +0 -0
  68. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/discovery.py +0 -0
  69. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/driver.py +0 -0
  70. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/__init__.py +0 -0
  71. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/airllm_driver.py +0 -0
  72. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_airllm_driver.py +0 -0
  73. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_azure_driver.py +0 -0
  74. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_claude_driver.py +0 -0
  75. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_google_driver.py +0 -0
  76. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_grok_driver.py +0 -0
  77. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_groq_driver.py +0 -0
  78. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_hugging_driver.py +0 -0
  79. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_lmstudio_driver.py +0 -0
  80. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_local_http_driver.py +0 -0
  81. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_ollama_driver.py +0 -0
  82. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_openai_driver.py +0 -0
  83. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_openrouter_driver.py +0 -0
  84. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/async_registry.py +0 -0
  85. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/azure_driver.py +0 -0
  86. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/claude_driver.py +0 -0
  87. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/google_driver.py +0 -0
  88. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/grok_driver.py +0 -0
  89. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/groq_driver.py +0 -0
  90. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/hugging_driver.py +0 -0
  91. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/lmstudio_driver.py +0 -0
  92. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/local_http_driver.py +0 -0
  93. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/ollama_driver.py +0 -0
  94. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/openai_driver.py +0 -0
  95. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/openrouter_driver.py +0 -0
  96. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/drivers/registry.py +0 -0
  97. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/field_definitions.py +0 -0
  98. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/logging.py +0 -0
  99. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/model_rates.py +0 -0
  100. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/runner.py +0 -0
  101. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/__init__.py +0 -0
  102. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/generator.py +0 -0
  103. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  104. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/README.md.j2 +0 -0
  105. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/config.py.j2 +0 -0
  106. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/env.example.j2 +0 -0
  107. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/main.py.j2 +0 -0
  108. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/models.py.j2 +0 -0
  109. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  110. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/server.py +0 -0
  111. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/session.py +0 -0
  112. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/settings.py +0 -0
  113. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/tools.py +0 -0
  114. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/tools_schema.py +0 -0
  115. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture/validator.py +0 -0
  116. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture.egg-info/SOURCES.txt +0 -0
  117. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture.egg-info/dependency_links.txt +0 -0
  118. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture.egg-info/entry_points.txt +0 -0
  119. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture.egg-info/requires.txt +0 -0
  120. {prompture-0.0.35.dev1 → prompture-0.0.36}/prompture.egg-info/top_level.txt +0 -0
  121. {prompture-0.0.35.dev1 → prompture-0.0.36}/pyproject.toml +0 -0
  122. {prompture-0.0.35.dev1 → prompture-0.0.36}/requirements.txt +0 -0
  123. {prompture-0.0.35.dev1 → prompture-0.0.36}/setup.cfg +0 -0
  124. {prompture-0.0.35.dev1 → prompture-0.0.36}/test.py +0 -0
  125. {prompture-0.0.35.dev1 → prompture-0.0.36}/test_version_diagnosis.py +0 -0
@@ -0,0 +1,369 @@
1
+ Metadata-Version: 2.4
2
+ Name: prompture
3
+ Version: 0.0.36
4
+ Summary: Ask LLMs to return structured JSON and run cross-model tests. API-first.
5
+ Author-email: Juan Denis <juan@vene.co>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jhd3197/prompture
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: anthropic>=0.8.0
14
+ Requires-Dist: click>=8.0
15
+ Requires-Dist: google-generativeai>=0.3.0
16
+ Requires-Dist: groq>=0.4.0
17
+ Requires-Dist: httpx>=0.25.0
18
+ Requires-Dist: jsonschema>=4.0
19
+ Requires-Dist: openai>=1.0.0
20
+ Requires-Dist: pandas>=1.3.0
21
+ Requires-Dist: pydantic>=1.10
22
+ Requires-Dist: pydantic-settings>=2.0
23
+ Requires-Dist: python-dotenv>=0.19.0
24
+ Requires-Dist: python-toon>=0.1.0
25
+ Requires-Dist: requests>=2.28
26
+ Requires-Dist: python-dateutil>=2.9.0
27
+ Requires-Dist: tukuy>=0.0.6
28
+ Requires-Dist: pyyaml>=6.0
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=7.0; extra == "test"
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"
46
+ Dynamic: license-file
47
+
48
+ <p align="center">
49
+ <h1 align="center">Prompture</h1>
50
+ <p align="center">Structured JSON extraction from any LLM. Schema-enforced, Pydantic-native, multi-provider.</p>
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="https://pypi.org/project/prompture/"><img src="https://badge.fury.io/py/prompture.svg" alt="PyPI version"></a>
55
+ <a href="https://pypi.org/project/prompture/"><img src="https://img.shields.io/pypi/pyversions/prompture.svg" alt="Python versions"></a>
56
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
57
+ <a href="https://pepy.tech/project/prompture"><img src="https://static.pepy.tech/badge/prompture" alt="Downloads"></a>
58
+ <a href="https://github.com/jhd3197/prompture"><img src="https://img.shields.io/github/stars/jhd3197/prompture?style=social" alt="GitHub stars"></a>
59
+ </p>
60
+
61
+ ---
62
+
63
+ **Prompture** is a Python library that turns LLM responses into validated, structured data. Define a schema or Pydantic model, point it at any provider, and get typed output back — with token tracking, cost calculation, and automatic JSON repair built in.
64
+
65
+ ```python
66
+ from pydantic import BaseModel
67
+ from prompture import extract_with_model
68
+
69
+ class Person(BaseModel):
70
+ name: str
71
+ age: int
72
+ profession: str
73
+
74
+ person = extract_with_model(Person, "Maria is 32, a developer in NYC.", model_name="openai/gpt-4")
75
+ print(person.name) # Maria
76
+ ```
77
+
78
+ ## Key Features
79
+
80
+ - **Structured output** — JSON schema enforcement and direct Pydantic model population
81
+ - **12 providers** — OpenAI, Claude, Google, Groq, Grok, Azure, Ollama, LM Studio, OpenRouter, HuggingFace, AirLLM, and generic HTTP
82
+ - **TOON input conversion** — 45-60% token savings when sending structured data via [Token-Oriented Object Notation](https://github.com/jhd3197/python-toon)
83
+ - **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
84
+ - **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
85
+ - **Conversations** — Stateful multi-turn sessions with sync and async support
86
+ - **Tool use** — Function calling and streaming across supported providers
87
+ - **Caching** — Built-in response cache with memory, SQLite, and Redis backends
88
+ - **Plugin system** — Register custom drivers via entry points
89
+ - **Usage tracking** — Token counts and cost calculation on every call
90
+ - **Auto-repair** — Optional second LLM pass to fix malformed JSON
91
+ - **Batch testing** — Spec-driven suites to compare models side by side
92
+
93
+ ## Installation
94
+
95
+ ```bash
96
+ pip install prompture
97
+ ```
98
+
99
+ Optional extras:
100
+
101
+ ```bash
102
+ pip install prompture[redis] # Redis cache backend
103
+ pip install prompture[serve] # FastAPI server mode
104
+ pip install prompture[airllm] # AirLLM local inference
105
+ ```
106
+
107
+ ## Configuration
108
+
109
+ Set API keys for the providers you use. Prompture reads from environment variables or a `.env` file:
110
+
111
+ ```bash
112
+ OPENAI_API_KEY=sk-...
113
+ ANTHROPIC_API_KEY=sk-ant-...
114
+ GOOGLE_API_KEY=...
115
+ GROQ_API_KEY=...
116
+ GROK_API_KEY=...
117
+ OPENROUTER_API_KEY=...
118
+ AZURE_OPENAI_ENDPOINT=...
119
+ AZURE_OPENAI_API_KEY=...
120
+ ```
121
+
122
+ Local providers (Ollama, LM Studio) work out of the box with no keys required.
123
+
124
+ ## Providers
125
+
126
+ Model strings use `"provider/model"` format. The provider prefix routes to the correct driver automatically.
127
+
128
+ | Provider | Example Model | Cost |
129
+ |---|---|---|
130
+ | `openai` | `openai/gpt-4` | Automatic |
131
+ | `claude` | `claude/claude-3` | Automatic |
132
+ | `google` | `google/gemini-1.5-pro` | Automatic |
133
+ | `groq` | `groq/llama2-70b-4096` | Automatic |
134
+ | `grok` | `grok/grok-4-fast-reasoning` | Automatic |
135
+ | `azure` | `azure/deployed-name` | Automatic |
136
+ | `openrouter` | `openrouter/anthropic/claude-2` | Automatic |
137
+ | `ollama` | `ollama/llama3.1:8b` | Free (local) |
138
+ | `lmstudio` | `lmstudio/local-model` | Free (local) |
139
+ | `huggingface` | `hf/model-name` | Free (local) |
140
+ | `http` | `http/self-hosted` | Free |
141
+
142
+ ## Usage
143
+
144
+ ### One-Shot Pydantic Extraction
145
+
146
+ Single LLM call, returns a validated Pydantic instance:
147
+
148
+ ```python
149
+ from typing import List, Optional
150
+ from pydantic import BaseModel
151
+ from prompture import extract_with_model
152
+
153
+ class Person(BaseModel):
154
+ name: str
155
+ age: int
156
+ profession: str
157
+ city: str
158
+ hobbies: List[str]
159
+ education: Optional[str] = None
160
+
161
+ person = extract_with_model(
162
+ Person,
163
+ "Maria is 32, a software developer in New York. She loves hiking and photography.",
164
+ model_name="openai/gpt-4"
165
+ )
166
+ print(person.model_dump())
167
+ ```
168
+
169
+ ### Stepwise Extraction
170
+
171
+ One LLM call per field. Higher accuracy, per-field error recovery:
172
+
173
+ ```python
174
+ from prompture import stepwise_extract_with_model
175
+
176
+ result = stepwise_extract_with_model(
177
+ Person,
178
+ "Maria is 32, a software developer in New York. She loves hiking and photography.",
179
+ model_name="openai/gpt-4"
180
+ )
181
+ print(result["model"].model_dump())
182
+ print(result["usage"]) # per-field and total token usage
183
+ ```
184
+
185
+ | Aspect | `extract_with_model` | `stepwise_extract_with_model` |
186
+ |---|---|---|
187
+ | LLM calls | 1 | N (one per field) |
188
+ | Speed / cost | Faster, cheaper | Slower, higher |
189
+ | Accuracy | Good global coherence | Higher per-field accuracy |
190
+ | Error handling | All-or-nothing | Per-field recovery |
191
+
192
+ ### JSON Schema Extraction
193
+
194
+ For raw JSON output with full control:
195
+
196
+ ```python
197
+ from prompture import ask_for_json
198
+
199
+ schema = {
200
+ "type": "object",
201
+ "required": ["name", "age"],
202
+ "properties": {
203
+ "name": {"type": "string"},
204
+ "age": {"type": "integer"}
205
+ }
206
+ }
207
+
208
+ result = ask_for_json(
209
+ content_prompt="Extract the person's info from: John is 28 and lives in Miami.",
210
+ json_schema=schema,
211
+ model_name="openai/gpt-4"
212
+ )
213
+ print(result["json_object"]) # {"name": "John", "age": 28}
214
+ print(result["usage"]) # token counts and cost
215
+ ```
216
+
217
+ ### TOON Input — Token Savings
218
+
219
+ Analyze structured data with automatic TOON conversion for 45-60% fewer tokens:
220
+
221
+ ```python
222
+ from prompture import extract_from_data
223
+
224
+ products = [
225
+ {"id": 1, "name": "Laptop", "price": 999.99, "rating": 4.5},
226
+ {"id": 2, "name": "Book", "price": 19.99, "rating": 4.2},
227
+ {"id": 3, "name": "Headphones", "price": 149.99, "rating": 4.7},
228
+ ]
229
+
230
+ result = extract_from_data(
231
+ data=products,
232
+ question="What is the average price and highest rated product?",
233
+ json_schema={
234
+ "type": "object",
235
+ "properties": {
236
+ "average_price": {"type": "number"},
237
+ "highest_rated": {"type": "string"}
238
+ }
239
+ },
240
+ model_name="openai/gpt-4"
241
+ )
242
+
243
+ print(result["json_object"])
244
+ # {"average_price": 389.99, "highest_rated": "Headphones"}
245
+
246
+ print(f"Token savings: {result['token_savings']['percentage_saved']}%")
247
+ ```
248
+
249
+ Works with Pandas DataFrames via `extract_from_pandas()`.
250
+
251
+ ### Field Definitions
252
+
253
+ Use the built-in field registry for consistent extraction across models:
254
+
255
+ ```python
256
+ from pydantic import BaseModel
257
+ from prompture import field_from_registry, stepwise_extract_with_model
258
+
259
+ class Person(BaseModel):
260
+ name: str = field_from_registry("name")
261
+ age: int = field_from_registry("age")
262
+ email: str = field_from_registry("email")
263
+ occupation: str = field_from_registry("occupation")
264
+
265
+ result = stepwise_extract_with_model(
266
+ Person,
267
+ "John Smith, 25, software engineer at TechCorp, john@example.com",
268
+ model_name="openai/gpt-4"
269
+ )
270
+ ```
271
+
272
+ Register custom fields with template variables:
273
+
274
+ ```python
275
+ from prompture import register_field
276
+
277
+ register_field("document_date", {
278
+ "type": "str",
279
+ "description": "Document creation date",
280
+ "instructions": "Use {{current_date}} if not specified",
281
+ "default": "{{current_date}}",
282
+ "nullable": False
283
+ })
284
+ ```
285
+
286
+ ### Conversations
287
+
288
+ Stateful multi-turn sessions:
289
+
290
+ ```python
291
+ from prompture import Conversation
292
+
293
+ conv = Conversation(model_name="openai/gpt-4")
294
+ conv.add_message("system", "You are a helpful assistant.")
295
+ response = conv.send("What is the capital of France?")
296
+ follow_up = conv.send("What about Germany?") # retains context
297
+ ```
298
+
299
+ ### Model Discovery
300
+
301
+ Auto-detect available models from configured providers:
302
+
303
+ ```python
304
+ from prompture import get_available_models
305
+
306
+ models = get_available_models()
307
+ for model in models:
308
+ print(model) # "openai/gpt-4", "ollama/llama3:latest", ...
309
+ ```
310
+
311
+ ### Logging and Debugging
312
+
313
+ ```python
314
+ import logging
315
+ from prompture import configure_logging
316
+
317
+ configure_logging(logging.DEBUG)
318
+ ```
319
+
320
+ ### Response Shape
321
+
322
+ All extraction functions return a consistent structure:
323
+
324
+ ```python
325
+ {
326
+ "json_string": str, # raw JSON text
327
+ "json_object": dict, # parsed result
328
+ "usage": {
329
+ "prompt_tokens": int,
330
+ "completion_tokens": int,
331
+ "total_tokens": int,
332
+ "cost": float,
333
+ "model_name": str
334
+ }
335
+ }
336
+ ```
337
+
338
+ ## CLI
339
+
340
+ ```bash
341
+ prompture run <spec-file>
342
+ ```
343
+
344
+ Run spec-driven extraction suites for cross-model comparison.
345
+
346
+ ## Development
347
+
348
+ ```bash
349
+ # Install with dev dependencies
350
+ pip install -e ".[test,dev]"
351
+
352
+ # Run tests
353
+ pytest
354
+
355
+ # Run integration tests (requires live LLM access)
356
+ pytest --run-integration
357
+
358
+ # Lint and format
359
+ ruff check .
360
+ ruff format .
361
+ ```
362
+
363
+ ## Contributing
364
+
365
+ PRs welcome. Please add tests for new functionality and examples under `examples/` for new drivers or patterns.
366
+
367
+ ## License
368
+
369
+ [MIT](https://opensource.org/licenses/MIT)
@@ -0,0 +1,322 @@
1
+ <p align="center">
2
+ <h1 align="center">Prompture</h1>
3
+ <p align="center">Structured JSON extraction from any LLM. Schema-enforced, Pydantic-native, multi-provider.</p>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://pypi.org/project/prompture/"><img src="https://badge.fury.io/py/prompture.svg" alt="PyPI version"></a>
8
+ <a href="https://pypi.org/project/prompture/"><img src="https://img.shields.io/pypi/pyversions/prompture.svg" alt="Python versions"></a>
9
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
10
+ <a href="https://pepy.tech/project/prompture"><img src="https://static.pepy.tech/badge/prompture" alt="Downloads"></a>
11
+ <a href="https://github.com/jhd3197/prompture"><img src="https://img.shields.io/github/stars/jhd3197/prompture?style=social" alt="GitHub stars"></a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ **Prompture** is a Python library that turns LLM responses into validated, structured data. Define a schema or Pydantic model, point it at any provider, and get typed output back — with token tracking, cost calculation, and automatic JSON repair built in.
17
+
18
+ ```python
19
+ from pydantic import BaseModel
20
+ from prompture import extract_with_model
21
+
22
+ class Person(BaseModel):
23
+ name: str
24
+ age: int
25
+ profession: str
26
+
27
+ person = extract_with_model(Person, "Maria is 32, a developer in NYC.", model_name="openai/gpt-4")
28
+ print(person.name) # Maria
29
+ ```
30
+
31
+ ## Key Features
32
+
33
+ - **Structured output** — JSON schema enforcement and direct Pydantic model population
34
+ - **12 providers** — OpenAI, Claude, Google, Groq, Grok, Azure, Ollama, LM Studio, OpenRouter, HuggingFace, AirLLM, and generic HTTP
35
+ - **TOON input conversion** — 45-60% token savings when sending structured data via [Token-Oriented Object Notation](https://github.com/jhd3197/python-toon)
36
+ - **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
37
+ - **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
38
+ - **Conversations** — Stateful multi-turn sessions with sync and async support
39
+ - **Tool use** — Function calling and streaming across supported providers
40
+ - **Caching** — Built-in response cache with memory, SQLite, and Redis backends
41
+ - **Plugin system** — Register custom drivers via entry points
42
+ - **Usage tracking** — Token counts and cost calculation on every call
43
+ - **Auto-repair** — Optional second LLM pass to fix malformed JSON
44
+ - **Batch testing** — Spec-driven suites to compare models side by side
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install prompture
50
+ ```
51
+
52
+ Optional extras:
53
+
54
+ ```bash
55
+ pip install prompture[redis] # Redis cache backend
56
+ pip install prompture[serve] # FastAPI server mode
57
+ pip install prompture[airllm] # AirLLM local inference
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ Set API keys for the providers you use. Prompture reads from environment variables or a `.env` file:
63
+
64
+ ```bash
65
+ OPENAI_API_KEY=sk-...
66
+ ANTHROPIC_API_KEY=sk-ant-...
67
+ GOOGLE_API_KEY=...
68
+ GROQ_API_KEY=...
69
+ GROK_API_KEY=...
70
+ OPENROUTER_API_KEY=...
71
+ AZURE_OPENAI_ENDPOINT=...
72
+ AZURE_OPENAI_API_KEY=...
73
+ ```
74
+
75
+ Local providers (Ollama, LM Studio) work out of the box with no keys required.
76
+
77
+ ## Providers
78
+
79
+ Model strings use `"provider/model"` format. The provider prefix routes to the correct driver automatically.
80
+
81
+ | Provider | Example Model | Cost |
82
+ |---|---|---|
83
+ | `openai` | `openai/gpt-4` | Automatic |
84
+ | `claude` | `claude/claude-3` | Automatic |
85
+ | `google` | `google/gemini-1.5-pro` | Automatic |
86
+ | `groq` | `groq/llama2-70b-4096` | Automatic |
87
+ | `grok` | `grok/grok-4-fast-reasoning` | Automatic |
88
+ | `azure` | `azure/deployed-name` | Automatic |
89
+ | `openrouter` | `openrouter/anthropic/claude-2` | Automatic |
90
+ | `ollama` | `ollama/llama3.1:8b` | Free (local) |
91
+ | `lmstudio` | `lmstudio/local-model` | Free (local) |
92
+ | `huggingface` | `hf/model-name` | Free (local) |
93
+ | `http` | `http/self-hosted` | Free |
94
+
95
+ ## Usage
96
+
97
+ ### One-Shot Pydantic Extraction
98
+
99
+ Single LLM call, returns a validated Pydantic instance:
100
+
101
+ ```python
102
+ from typing import List, Optional
103
+ from pydantic import BaseModel
104
+ from prompture import extract_with_model
105
+
106
+ class Person(BaseModel):
107
+ name: str
108
+ age: int
109
+ profession: str
110
+ city: str
111
+ hobbies: List[str]
112
+ education: Optional[str] = None
113
+
114
+ person = extract_with_model(
115
+ Person,
116
+ "Maria is 32, a software developer in New York. She loves hiking and photography.",
117
+ model_name="openai/gpt-4"
118
+ )
119
+ print(person.model_dump())
120
+ ```
121
+
122
+ ### Stepwise Extraction
123
+
124
+ One LLM call per field. Higher accuracy, per-field error recovery:
125
+
126
+ ```python
127
+ from prompture import stepwise_extract_with_model
128
+
129
+ result = stepwise_extract_with_model(
130
+ Person,
131
+ "Maria is 32, a software developer in New York. She loves hiking and photography.",
132
+ model_name="openai/gpt-4"
133
+ )
134
+ print(result["model"].model_dump())
135
+ print(result["usage"]) # per-field and total token usage
136
+ ```
137
+
138
+ | Aspect | `extract_with_model` | `stepwise_extract_with_model` |
139
+ |---|---|---|
140
+ | LLM calls | 1 | N (one per field) |
141
+ | Speed / cost | Faster, cheaper | Slower, higher |
142
+ | Accuracy | Good global coherence | Higher per-field accuracy |
143
+ | Error handling | All-or-nothing | Per-field recovery |
144
+
145
+ ### JSON Schema Extraction
146
+
147
+ For raw JSON output with full control:
148
+
149
+ ```python
150
+ from prompture import ask_for_json
151
+
152
+ schema = {
153
+ "type": "object",
154
+ "required": ["name", "age"],
155
+ "properties": {
156
+ "name": {"type": "string"},
157
+ "age": {"type": "integer"}
158
+ }
159
+ }
160
+
161
+ result = ask_for_json(
162
+ content_prompt="Extract the person's info from: John is 28 and lives in Miami.",
163
+ json_schema=schema,
164
+ model_name="openai/gpt-4"
165
+ )
166
+ print(result["json_object"]) # {"name": "John", "age": 28}
167
+ print(result["usage"]) # token counts and cost
168
+ ```
169
+
170
+ ### TOON Input — Token Savings
171
+
172
+ Analyze structured data with automatic TOON conversion for 45-60% fewer tokens:
173
+
174
+ ```python
175
+ from prompture import extract_from_data
176
+
177
+ products = [
178
+ {"id": 1, "name": "Laptop", "price": 999.99, "rating": 4.5},
179
+ {"id": 2, "name": "Book", "price": 19.99, "rating": 4.2},
180
+ {"id": 3, "name": "Headphones", "price": 149.99, "rating": 4.7},
181
+ ]
182
+
183
+ result = extract_from_data(
184
+ data=products,
185
+ question="What is the average price and highest rated product?",
186
+ json_schema={
187
+ "type": "object",
188
+ "properties": {
189
+ "average_price": {"type": "number"},
190
+ "highest_rated": {"type": "string"}
191
+ }
192
+ },
193
+ model_name="openai/gpt-4"
194
+ )
195
+
196
+ print(result["json_object"])
197
+ # {"average_price": 389.99, "highest_rated": "Headphones"}
198
+
199
+ print(f"Token savings: {result['token_savings']['percentage_saved']}%")
200
+ ```
201
+
202
+ Works with Pandas DataFrames via `extract_from_pandas()`.
203
+
204
+ ### Field Definitions
205
+
206
+ Use the built-in field registry for consistent extraction across models:
207
+
208
+ ```python
209
+ from pydantic import BaseModel
210
+ from prompture import field_from_registry, stepwise_extract_with_model
211
+
212
+ class Person(BaseModel):
213
+ name: str = field_from_registry("name")
214
+ age: int = field_from_registry("age")
215
+ email: str = field_from_registry("email")
216
+ occupation: str = field_from_registry("occupation")
217
+
218
+ result = stepwise_extract_with_model(
219
+ Person,
220
+ "John Smith, 25, software engineer at TechCorp, john@example.com",
221
+ model_name="openai/gpt-4"
222
+ )
223
+ ```
224
+
225
+ Register custom fields with template variables:
226
+
227
+ ```python
228
+ from prompture import register_field
229
+
230
+ register_field("document_date", {
231
+ "type": "str",
232
+ "description": "Document creation date",
233
+ "instructions": "Use {{current_date}} if not specified",
234
+ "default": "{{current_date}}",
235
+ "nullable": False
236
+ })
237
+ ```
238
+
239
+ ### Conversations
240
+
241
+ Stateful multi-turn sessions:
242
+
243
+ ```python
244
+ from prompture import Conversation
245
+
246
+ conv = Conversation(model_name="openai/gpt-4")
247
+ conv.add_message("system", "You are a helpful assistant.")
248
+ response = conv.send("What is the capital of France?")
249
+ follow_up = conv.send("What about Germany?") # retains context
250
+ ```
251
+
252
+ ### Model Discovery
253
+
254
+ Auto-detect available models from configured providers:
255
+
256
+ ```python
257
+ from prompture import get_available_models
258
+
259
+ models = get_available_models()
260
+ for model in models:
261
+ print(model) # "openai/gpt-4", "ollama/llama3:latest", ...
262
+ ```
263
+
264
+ ### Logging and Debugging
265
+
266
+ ```python
267
+ import logging
268
+ from prompture import configure_logging
269
+
270
+ configure_logging(logging.DEBUG)
271
+ ```
272
+
273
+ ### Response Shape
274
+
275
+ All extraction functions return a consistent structure:
276
+
277
+ ```python
278
+ {
279
+ "json_string": str, # raw JSON text
280
+ "json_object": dict, # parsed result
281
+ "usage": {
282
+ "prompt_tokens": int,
283
+ "completion_tokens": int,
284
+ "total_tokens": int,
285
+ "cost": float,
286
+ "model_name": str
287
+ }
288
+ }
289
+ ```
290
+
291
+ ## CLI
292
+
293
+ ```bash
294
+ prompture run <spec-file>
295
+ ```
296
+
297
+ Run spec-driven extraction suites for cross-model comparison.
298
+
299
+ ## Development
300
+
301
+ ```bash
302
+ # Install with dev dependencies
303
+ pip install -e ".[test,dev]"
304
+
305
+ # Run tests
306
+ pytest
307
+
308
+ # Run integration tests (requires live LLM access)
309
+ pytest --run-integration
310
+
311
+ # Lint and format
312
+ ruff check .
313
+ ruff format .
314
+ ```
315
+
316
+ ## Contributing
317
+
318
+ PRs welcome. Please add tests for new functionality and examples under `examples/` for new drivers or patterns.
319
+
320
+ ## License
321
+
322
+ [MIT](https://opensource.org/licenses/MIT)
@@ -0,0 +1 @@
1
+ 0.0.36
@@ -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.35.dev1'
32
- __version_tuple__ = version_tuple = (0, 0, 35, 'dev1')
31
+ __version__ = version = '0.0.36'
32
+ __version_tuple__ = version_tuple = (0, 0, 36)
33
33
 
34
34
  __commit_id__ = commit_id = None