prompture 1.0.52.dev1__tar.gz → 1.0.53.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/dev.yml +10 -0
  2. prompture-1.0.53.dev1/.github/workflows/pricing-freshness.yml +25 -0
  3. prompture-1.0.53.dev1/AGENTS.md +93 -0
  4. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/BREAKING_CHANGES.md +1 -1
  5. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/PKG-INFO +76 -3
  6. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/README.md +75 -2
  7. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/_version.py +2 -2
  8. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_claude_driver.py +27 -164
  9. prompture-1.0.53.dev1/prompture/drivers/async_google_vertexai_driver.py +425 -0
  10. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_driver.py +31 -95
  11. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/claude_driver.py +174 -162
  12. prompture-1.0.53.dev1/prompture/drivers/google_vertexai_driver.py +709 -0
  13. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_driver.py +109 -100
  14. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/provider_descriptors.py +49 -0
  15. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/capabilities.py +16 -13
  16. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/anthropic.json +17 -4
  17. prompture-1.0.53.dev1/prompture/infra/rates/google.json +119 -0
  18. prompture-1.0.52.dev1/prompture/infra/rates/google.json → prompture-1.0.53.dev1/prompture/infra/rates/google_vertexai.json +22 -9
  19. prompture-1.0.53.dev1/prompture/infra/rates/groq.json +119 -0
  20. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/moonshot.json +30 -17
  21. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/openai.json +39 -0
  22. prompture-1.0.53.dev1/prompture/infra/rates/xai.json +119 -0
  23. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/settings.py +7 -0
  24. prompture-1.0.53.dev1/prompture/testing/__init__.py +25 -0
  25. prompture-1.0.53.dev1/prompture/testing/driver_contract.py +76 -0
  26. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/PKG-INFO +76 -3
  27. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/SOURCES.txt +8 -22
  28. prompture-1.0.53.dev1/scripts/check_pricing_freshness.py +167 -0
  29. prompture-1.0.52.dev1/docs/source/_generated/compatibility_matrix.rst +0 -124
  30. prompture-1.0.52.dev1/docs/source/_static/custom.css +0 -40
  31. prompture-1.0.52.dev1/docs/source/_templates/footer.html +0 -16
  32. prompture-1.0.52.dev1/docs/source/api/core.rst +0 -218
  33. prompture-1.0.52.dev1/docs/source/api/drivers.rst +0 -477
  34. prompture-1.0.52.dev1/docs/source/api/field_definitions.rst +0 -471
  35. prompture-1.0.52.dev1/docs/source/api/index.rst +0 -426
  36. prompture-1.0.52.dev1/docs/source/api/runner.rst +0 -330
  37. prompture-1.0.52.dev1/docs/source/api/tools.rst +0 -216
  38. prompture-1.0.52.dev1/docs/source/api/validator.rst +0 -423
  39. prompture-1.0.52.dev1/docs/source/conf.py +0 -134
  40. prompture-1.0.52.dev1/docs/source/contributing.rst +0 -439
  41. prompture-1.0.52.dev1/docs/source/examples.rst +0 -590
  42. prompture-1.0.52.dev1/docs/source/field_definitions_reference.rst +0 -1038
  43. prompture-1.0.52.dev1/docs/source/index.rst +0 -84
  44. prompture-1.0.52.dev1/docs/source/installation.rst +0 -204
  45. prompture-1.0.52.dev1/docs/source/migration.rst +0 -114
  46. prompture-1.0.52.dev1/docs/source/quickstart.rst +0 -345
  47. prompture-1.0.52.dev1/docs/source/stability.rst +0 -120
  48. prompture-1.0.52.dev1/docs/source/test_harness.rst +0 -139
  49. prompture-1.0.52.dev1/docs/source/toon_input_guide.rst +0 -398
  50. prompture-1.0.52.dev1/docs/source/why_prompture.rst +0 -145
  51. prompture-1.0.52.dev1/prompture/infra/rates/groq.json +0 -28
  52. prompture-1.0.52.dev1/prompture/infra/rates/xai.json +0 -54
  53. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
  54. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
  55. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-example/SKILL.md +0 -0
  56. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-field/SKILL.md +0 -0
  57. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-persona/SKILL.md +0 -0
  58. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-test/SKILL.md +0 -0
  59. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-tool/SKILL.md +0 -0
  60. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/create-pr/SKILL.md +0 -0
  61. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
  62. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  63. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/update-pricing/SKILL.md +0 -0
  64. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.env.copy +0 -0
  65. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/FUNDING.yml +0 -0
  66. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  67. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/pricing_cost_mismatch.yml +0 -0
  68. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/provider_driver_bug.yml +0 -0
  69. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/schema_extraction_failure.yml +0 -0
  70. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/scripts/update_docs_version.py +0 -0
  71. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/scripts/update_wrapper_version.py +0 -0
  72. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/documentation.yml +0 -0
  73. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/publish.yml +0 -0
  74. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/security.yml +0 -0
  75. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.safety-project.ini +0 -0
  76. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/CLAUDE.md +0 -0
  77. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/CONTRIBUTING.md +0 -0
  78. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/LICENSE +0 -0
  79. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/MANIFEST.in +0 -0
  80. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/ROADMAP.md +0 -0
  81. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/SECURITY.md +0 -0
  82. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/dev.ps1 +0 -0
  83. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/dev.sh +0 -0
  84. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/mypy_errors.txt +0 -0
  85. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/README.md +0 -0
  86. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/README.md +0 -0
  87. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  88. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/pyproject.toml +0 -0
  89. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/test.py +0 -0
  90. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/README.md +0 -0
  91. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  92. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
  93. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/test.py +0 -0
  94. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/__init__.py +0 -0
  95. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/__init__.py +0 -0
  96. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/agent.py +0 -0
  97. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/async_agent.py +0 -0
  98. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/async_conversation.py +0 -0
  99. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/conversation.py +0 -0
  100. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/persona.py +0 -0
  101. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/simulated_tools.py +0 -0
  102. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/skills.py +0 -0
  103. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/tools_schema.py +0 -0
  104. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/types.py +0 -0
  105. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/aio/__init__.py +0 -0
  106. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/bridges/__init__.py +0 -0
  107. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/bridges/tukuy_backend.py +0 -0
  108. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/__init__.py +0 -0
  109. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/cli.py +0 -0
  110. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/formatters.py +0 -0
  111. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/runner.py +0 -0
  112. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/server.py +0 -0
  113. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/__init__.py +0 -0
  114. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/airllm_driver.py +0 -0
  115. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
  116. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_azure_driver.py +0 -0
  117. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_base.py +0 -0
  118. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_cachibot_driver.py +0 -0
  119. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_elevenlabs_stt_driver.py +0 -0
  120. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_elevenlabs_tts_driver.py +0 -0
  121. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_embedding_base.py +0 -0
  122. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_google_driver.py +0 -0
  123. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_google_img_gen_driver.py +0 -0
  124. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_grok_driver.py +0 -0
  125. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_grok_img_gen_driver.py +0 -0
  126. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_groq_driver.py +0 -0
  127. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
  128. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_img_gen_base.py +0 -0
  129. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_lmstudio_driver.py +0 -0
  130. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
  131. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
  132. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_moonshot_driver.py +0 -0
  133. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
  134. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_ollama_embedding_driver.py +0 -0
  135. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_embedding_driver.py +0 -0
  136. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_img_gen_driver.py +0 -0
  137. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_stt_driver.py +0 -0
  138. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_tts_driver.py +0 -0
  139. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openrouter_driver.py +0 -0
  140. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_registry.py +0 -0
  141. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_stability_img_gen_driver.py +0 -0
  142. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_stt_base.py +0 -0
  143. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_tts_base.py +0 -0
  144. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_zai_driver.py +0 -0
  145. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/audio_registry.py +0 -0
  146. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/azure_config.py +0 -0
  147. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/azure_driver.py +0 -0
  148. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/base.py +0 -0
  149. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/cachibot_driver.py +0 -0
  150. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/driver_registry.py +0 -0
  151. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/elevenlabs_stt_driver.py +0 -0
  152. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/elevenlabs_tts_driver.py +0 -0
  153. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/embedding_base.py +0 -0
  154. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/embedding_registry.py +0 -0
  155. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/google_driver.py +0 -0
  156. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/google_img_gen_driver.py +0 -0
  157. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/grok_driver.py +0 -0
  158. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/grok_img_gen_driver.py +0 -0
  159. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/groq_driver.py +0 -0
  160. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/hugging_driver.py +0 -0
  161. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/img_gen_base.py +0 -0
  162. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/img_gen_registry.py +0 -0
  163. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/lmstudio_driver.py +0 -0
  164. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/local_http_driver.py +0 -0
  165. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/modelscope_driver.py +0 -0
  166. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/moonshot_driver.py +0 -0
  167. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/ollama_driver.py +0 -0
  168. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/ollama_embedding_driver.py +0 -0
  169. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_embedding_driver.py +0 -0
  170. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_img_gen_driver.py +0 -0
  171. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_stt_driver.py +0 -0
  172. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_tts_driver.py +0 -0
  173. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openrouter_driver.py +0 -0
  174. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/registry.py +0 -0
  175. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/stability_img_gen_driver.py +0 -0
  176. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/stt_base.py +0 -0
  177. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/tts_base.py +0 -0
  178. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/vision_helpers.py +0 -0
  179. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/zai_driver.py +0 -0
  180. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/exceptions.py +0 -0
  181. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/__init__.py +0 -0
  182. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/async_core.py +0 -0
  183. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/core.py +0 -0
  184. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/fields.py +0 -0
  185. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/reasoning.py +0 -0
  186. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/strategy.py +0 -0
  187. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/tools.py +0 -0
  188. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/tukuy_bridge.py +0 -0
  189. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/validator.py +0 -0
  190. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/__init__.py +0 -0
  191. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/async_groups.py +0 -0
  192. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/consensus.py +0 -0
  193. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/debate.py +0 -0
  194. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/groups.py +0 -0
  195. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/types.py +0 -0
  196. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/__init__.py +0 -0
  197. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/budget.py +0 -0
  198. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/cache.py +0 -0
  199. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/callbacks.py +0 -0
  200. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/cost_mixin.py +0 -0
  201. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/discovery.py +0 -0
  202. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/ledger.py +0 -0
  203. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/logging.py +0 -0
  204. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/model_rates.py +0 -0
  205. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/provider_env.py +0 -0
  206. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/azure.json +0 -0
  207. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/openrouter.json +0 -0
  208. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/session.py +0 -0
  209. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/tracker.py +0 -0
  210. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/tukuy_backend.py +0 -0
  211. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/__init__.py +0 -0
  212. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/chunking.py +0 -0
  213. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/detect.py +0 -0
  214. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/document.py +0 -0
  215. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/__init__.py +0 -0
  216. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/base.py +0 -0
  217. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/csv_parser.py +0 -0
  218. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/docx.py +0 -0
  219. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/html.py +0 -0
  220. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/markdown.py +0 -0
  221. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/pdf.py +0 -0
  222. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/xlsx.py +0 -0
  223. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/integrations/__init__.py +0 -0
  224. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/integrations/tukuy_bridge.py +0 -0
  225. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/__init__.py +0 -0
  226. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/audio.py +0 -0
  227. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/image.py +0 -0
  228. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/01-why-prompture.md +0 -0
  229. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/02-installation.md +0 -0
  230. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/03-quickstart.md +0 -0
  231. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/04-toon-input.md +0 -0
  232. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/05-test-harness.md +0 -0
  233. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/06-stability.md +0 -0
  234. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/07-migration.md +0 -0
  235. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/__init__.py +0 -0
  236. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/history.py +0 -0
  237. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/serialization.py +0 -0
  238. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/store.py +0 -0
  239. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/__init__.py +0 -0
  240. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/pipeline.py +0 -0
  241. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/resolver.py +0 -0
  242. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/routing.py +0 -0
  243. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/py.typed +0 -0
  244. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/__init__.py +0 -0
  245. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/generator.py +0 -0
  246. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  247. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
  248. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
  249. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
  250. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
  251. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
  252. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  253. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/dependency_links.txt +0 -0
  254. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/entry_points.txt +0 -0
  255. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/requires.txt +0 -0
  256. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/top_level.txt +0 -0
  257. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture_cost_tracking.md +0 -0
  258. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/pyproject.toml +0 -0
  259. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/requirements.txt +0 -0
  260. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/setup.cfg +0 -0
  261. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/basic_extraction.json +0 -0
  262. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/schema_validation.json +0 -0
  263. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/strategy_comparison.json +0 -0
  264. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/test.py +0 -0
  265. {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/test_version_diagnosis.py +0 -0
@@ -23,6 +23,16 @@ jobs:
23
23
  run: |
24
24
  python -m pip install --upgrade pip
25
25
  pip install -e ".[test,all]"
26
+ pip install ruff mypy
27
+
28
+ - name: Lint with ruff
29
+ run: ruff check .
30
+
31
+ - name: Type check with mypy
32
+ # Advisory: 500+ pre-existing errors. Fix incrementally before
33
+ # promoting to a required check.
34
+ run: mypy prompture
35
+ continue-on-error: true
26
36
 
27
37
  - name: Run tests
28
38
  run: pytest
@@ -0,0 +1,25 @@
1
+ name: Pricing Freshness
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 6 * * *"
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Setup Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.11"
18
+
19
+ - name: Install dependencies
20
+ run: |
21
+ python -m pip install --upgrade pip
22
+ pip install -e ".[test,all]"
23
+
24
+ - name: Check pricing freshness
25
+ run: python scripts/check_pricing_freshness.py
@@ -0,0 +1,93 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ Prompture is a Python library for extracting structured JSON output from LLMs, with schema enforcement, Pydantic model integration, TOON (Token-Oriented Object Notation) input/output conversion, and multi-provider driver support. Published on PyPI as `prompture`.
8
+
9
+ ## Build & Development Commands
10
+
11
+ ```bash
12
+ # Install in development mode
13
+ pip install -e ".[test,dev]"
14
+
15
+ # Run all tests (uses DEFAULT_MODEL from tests/conftest.py)
16
+ python test.py
17
+
18
+ # Run tests with pytest directly
19
+ pytest
20
+
21
+ # Run integration tests (require live LLM access)
22
+ pytest --run-integration
23
+ # or: RUN_INTEGRATION_TESTS=1 pytest
24
+
25
+ # Run a single test file
26
+ pytest tests/test_core.py
27
+
28
+ # Run a single test
29
+ pytest tests/test_core.py::TestCleanJsonText::test_basic_json
30
+
31
+ # Skip integration tests when credentials are missing
32
+ TEST_SKIP_NO_CREDENTIALS=true python test.py
33
+
34
+ # Build distribution
35
+ python -m build
36
+
37
+ # CLI entry point
38
+ prompture run <spec-file>
39
+ ```
40
+
41
+ ### Linting & Formatting (ruff)
42
+
43
+ ```bash
44
+ # Format all files
45
+ ruff format .
46
+
47
+ # Check for lint violations
48
+ ruff check .
49
+
50
+ # Auto-fix lint violations
51
+ ruff check --fix .
52
+
53
+ # Auto-fix including unsafe fixes (e.g., typing modernization)
54
+ ruff check --fix --unsafe-fixes .
55
+ ```
56
+
57
+ Configuration is in `pyproject.toml` under `[tool.ruff]`.
58
+
59
+ ## Architecture
60
+
61
+ ### Module Layout
62
+
63
+ - **`prompture/core.py`** — Primary business logic. All extraction functions live here: `ask_for_json()` (low-level schema enforcement), `extract_and_jsonify()` / `manual_extract_and_jsonify()` (text-to-JSON), `extract_with_model()` / `stepwise_extract_with_model()` (Pydantic-based), `extract_from_data()` / `extract_from_pandas()` (TOON input), `render_output()` (raw text formatting).
64
+ - **`prompture/drivers/`** — One module per LLM provider (openai, Codex, google, groq, grok, azure, ollama, lmstudio, openrouter, local_http, huggingface, airllm). Each driver implements `generate(prompt, options)` returning a standardized response with token/cost metadata.
65
+ - **`prompture/drivers/__init__.py`** — Central `DRIVER_REGISTRY` dict mapping provider name to factory lambda. `get_driver_for_model("provider/model")` parses the string and instantiates the right driver. `get_driver("provider")` is the legacy interface.
66
+ - **`prompture/tools.py`** — Utilities: JSON/TOON text cleanup, type conversion (shorthand numbers, multilingual booleans, datetimes), field schema generation.
67
+ - **`prompture/logging.py`** — Library logging configuration: `JSONFormatter` for structured JSON-lines output, `configure_logging()` for enabling library-level logging via Python stdlib `logging`.
68
+ - **`prompture/callbacks.py`** — `DriverCallbacks` dataclass with `on_request`, `on_response`, `on_error` hooks for driver-level observability.
69
+ - **`prompture/session.py`** — `UsageSession` dataclass for tracking token counts, costs, and errors across multiple driver calls.
70
+ - **`prompture/field_definitions.py`** — Thread-safe global field registry with 50+ predefined fields, template variable substitution (`{{current_year}}`, `{{current_date}}`), and Pydantic Field generation via `field_from_registry()`.
71
+ - **`prompture/settings.py`** — Pydantic-settings `Settings` class loading provider API keys/endpoints from `.env`.
72
+ - **`prompture/discovery.py`** — `get_available_models()` auto-detects models from configured providers (static pricing tables + dynamic Ollama endpoint query).
73
+ - **`prompture/runner.py`** — Spec-driven test suite runner for cross-model comparison.
74
+ - **`prompture/validator.py`** — JSON schema validation via jsonschema with fallback.
75
+
76
+ ### Key Patterns
77
+
78
+ - **Model strings** use `"provider/model"` format (e.g., `"ollama/llama3.1:8b"`, `"openai/gpt-4"`). The provider prefix routes to the correct driver.
79
+ - **Driver responses** always include metadata: `prompt_tokens`, `completion_tokens`, `total_tokens`, `cost`, `raw_response`.
80
+ - **Output formats**: JSON (default) and TOON (experimental, for compact output). Controlled via `output_format` parameter.
81
+ - **TOON input conversion** uses `python-toon` and `tukuy` packages to reduce token usage by 45-60% when sending structured data to LLMs.
82
+
83
+ ### Testing
84
+
85
+ Tests live in `tests/`. Integration tests that call live LLMs are marked with `@pytest.mark.integration` and skipped by default. The default test model is set in `tests/conftest.py` as `DEFAULT_MODEL`. Shared fixtures and assertion helpers (`assert_valid_usage_metadata`, `assert_jsonify_response_structure`) are in `conftest.py`.
86
+
87
+ ### Configuration
88
+
89
+ Provider API keys and endpoints are configured via environment variables loaded from `.env` (see `.env.copy` for the template). The `Settings` class in `settings.py` manages all provider config.
90
+
91
+ ### Versioning
92
+
93
+ Uses `setuptools_scm` for automatic version from git tags. The `VERSION` file contains the current dev version.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This log records every backward-incompatible change to Prompture's
4
4
  **stable** APIs. Experimental surfaces may change without an entry here
5
- (see [API Stability](docs/source/stability.rst) for tier definitions).
5
+ (see [API Stability](prompture/pages/06-stability.md) for tier definitions).
6
6
 
7
7
  Changes are listed newest-first.
8
8
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 1.0.52.dev1
3
+ Version: 1.0.53.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
@@ -95,7 +95,10 @@ print(person.name) # Maria
95
95
  ## Key Features
96
96
 
97
97
  - **Structured output** — JSON schema enforcement and direct Pydantic model population
98
- - **12 providers** — OpenAI, Claude, Google, Groq, Grok, Azure, Ollama, LM Studio, OpenRouter, HuggingFace, AirLLM, and generic HTTP
98
+ - **17+ providers** — OpenAI, Claude, Google, Groq, Grok, Azure, Ollama, LM Studio, OpenRouter, HuggingFace, Moonshot, ModelScope, Z.ai, Vertex AI, AirLLM, CachiBot, and generic HTTP
99
+ - **Multi-modal** — Drivers for embeddings, image generation (DALL-E, Imagen, Grok, Stability), text-to-speech, and speech-to-text (Whisper, ElevenLabs)
100
+ - **Multi-model fallback** — Try a list of models in sequence with per-attempt cost, token, and capability accounting
101
+ - **Strategy cascade** — Auto-selects between provider-native JSON mode, tool-call extraction, and prompted repair so extraction works on any model
99
102
  - **TOON input conversion** — 45-60% token savings when sending structured data via [Token-Oriented Object Notation](https://github.com/jhd3197/python-toon)
100
103
  - **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
101
104
  - **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
@@ -172,14 +175,42 @@ Model strings use `"provider/model"` format. The provider prefix routes to the c
172
175
  | `openai` | `openai/gpt-4` | Automatic |
173
176
  | `claude` | `claude/claude-3` | Automatic |
174
177
  | `google` | `google/gemini-1.5-pro` | Automatic |
178
+ | `google_vertexai` | `google_vertexai/gemini-1.5-pro` | Automatic |
175
179
  | `groq` | `groq/llama2-70b-4096` | Automatic |
176
180
  | `grok` | `grok/grok-4-fast-reasoning` | Automatic |
177
181
  | `azure` | `azure/deployed-name` | Automatic |
178
182
  | `openrouter` | `openrouter/anthropic/claude-2` | Automatic |
183
+ | `moonshot` | `moonshot/kimi-k2` | Automatic |
184
+ | `modelscope` | `modelscope/Qwen2.5-72B-Instruct` | Automatic |
185
+ | `zai` | `zai/glm-4` | Automatic |
186
+ | `cachibot` | `cachibot/openai/gpt-4o-mini` | Automatic |
179
187
  | `ollama` | `ollama/llama3.1:8b` | Free (local) |
180
188
  | `lmstudio` | `lmstudio/local-model` | Free (local) |
181
189
  | `huggingface` | `hf/model-name` | Free (local) |
182
- | `http` | `http/self-hosted` | Free |
190
+ | `airllm` | `airllm/Qwen2-7B` | Free (local) |
191
+ | `local_http` | `local_http/self-hosted` | Free |
192
+
193
+ Aliases (`anthropic`, `gemini`, `chatgpt`, `xai`, `lm_studio`, `zhipu`, `hf`, `dalle`) route to their canonical providers.
194
+
195
+ ## Multi-Modal
196
+
197
+ Beyond text LLMs, Prompture exposes drivers for adjacent modalities under the same `provider/model` routing:
198
+
199
+ - **Embeddings** — OpenAI (`text-embedding-3-*`) and Ollama (`nomic-embed-text`)
200
+ - **Image generation** — OpenAI DALL-E, Google Imagen, Grok, Stability AI
201
+ - **Text-to-speech** — OpenAI (`tts-1`) and ElevenLabs
202
+ - **Speech-to-text** — OpenAI Whisper and ElevenLabs
203
+
204
+ ```python
205
+ from prompture.drivers.img_gen_registry import get_img_gen_driver_for_model
206
+
207
+ driver = get_img_gen_driver_for_model("openai/dall-e-3")
208
+ result = driver.generate_image(
209
+ "a cat on a surfboard at sunset",
210
+ {"size": "1024x1024", "quality": "hd"},
211
+ )
212
+ print(result["meta"]["cost"], result["meta"]["image_count"])
213
+ ```
183
214
 
184
215
  ## Usage
185
216
 
@@ -256,6 +287,48 @@ print(result["json_object"]) # {"name": "John", "age": 28}
256
287
  print(result["usage"]) # token counts and cost
257
288
  ```
258
289
 
290
+ ### Strategy Cascade
291
+
292
+ Prompture picks how to obtain structured JSON based on each model's capabilities. The cascade is `provider_native` (built-in JSON mode / schema enforcement) → `tool_call` (encode the schema as a function definition and read it back from the tool call) → `prompted_repair` (prompt for JSON, repair malformed output via AI cleanup). Pass `strategy="auto"` (default) to let Prompture select per model, or pin a specific strategy via the `StructuredOutputStrategy` enum or its string value. The strategy used is recorded in the response so you can see which path each call took.
293
+
294
+ ### Multi-Model Fallback
295
+
296
+ Try a list of models in priority order, with full per-attempt accounting — every model tried (success, failure, or skipped) is recorded with its cost, tokens, duration, capabilities, and strategy. The first success wins; if all fail, an optional `fallback` Pydantic instance is returned instead of raising.
297
+
298
+ ```python
299
+ from prompture import extract_with_models
300
+
301
+ result = extract_with_models(
302
+ Person,
303
+ "Maria is 32, a software developer in NYC.",
304
+ models=[
305
+ "openai/gpt-4o-mini", # try first
306
+ "claude/claude-3-5-haiku", # fallback
307
+ "ollama/llama3.1:8b", # last resort, free
308
+ ],
309
+ fallback=Person(name="unknown", age=0, profession="unknown"),
310
+ )
311
+
312
+ print(result["selected_model"]) # winning model string
313
+ print(result["model"]) # validated Pydantic instance
314
+ print(result["total_cost"]) # cumulative cost across all attempts
315
+ print(result["total_attempts"]) # number of models actually called
316
+
317
+ for attempt in result["attempts"]:
318
+ print(
319
+ attempt["model"],
320
+ attempt["status"], # "success" | "failed" | "skipped"
321
+ attempt["strategy"], # "single" | "stepwise"
322
+ attempt["cost"],
323
+ attempt["prompt_tokens"],
324
+ attempt["completion_tokens"],
325
+ attempt["duration_ms"],
326
+ attempt["capabilities"], # {"json_mode": bool, "json_schema": bool}
327
+ )
328
+ ```
329
+
330
+ If every model fails and no `fallback` is provided, an `ExtractionError` is raised with the full `attempts` list, `total_cost`, and `total_tokens` attached as attributes.
331
+
259
332
  ### TOON Input — Token Savings
260
333
 
261
334
  Analyze structured data with automatic TOON conversion for 45-60% fewer tokens:
@@ -31,7 +31,10 @@ print(person.name) # Maria
31
31
  ## Key Features
32
32
 
33
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
34
+ - **17+ providers** — OpenAI, Claude, Google, Groq, Grok, Azure, Ollama, LM Studio, OpenRouter, HuggingFace, Moonshot, ModelScope, Z.ai, Vertex AI, AirLLM, CachiBot, and generic HTTP
35
+ - **Multi-modal** — Drivers for embeddings, image generation (DALL-E, Imagen, Grok, Stability), text-to-speech, and speech-to-text (Whisper, ElevenLabs)
36
+ - **Multi-model fallback** — Try a list of models in sequence with per-attempt cost, token, and capability accounting
37
+ - **Strategy cascade** — Auto-selects between provider-native JSON mode, tool-call extraction, and prompted repair so extraction works on any model
35
38
  - **TOON input conversion** — 45-60% token savings when sending structured data via [Token-Oriented Object Notation](https://github.com/jhd3197/python-toon)
36
39
  - **Stepwise extraction** — Per-field prompts with smart type coercion (shorthand numbers, multilingual booleans, dates)
37
40
  - **Field registry** — 50+ predefined extraction fields with template variables and Pydantic integration
@@ -108,14 +111,42 @@ Model strings use `"provider/model"` format. The provider prefix routes to the c
108
111
  | `openai` | `openai/gpt-4` | Automatic |
109
112
  | `claude` | `claude/claude-3` | Automatic |
110
113
  | `google` | `google/gemini-1.5-pro` | Automatic |
114
+ | `google_vertexai` | `google_vertexai/gemini-1.5-pro` | Automatic |
111
115
  | `groq` | `groq/llama2-70b-4096` | Automatic |
112
116
  | `grok` | `grok/grok-4-fast-reasoning` | Automatic |
113
117
  | `azure` | `azure/deployed-name` | Automatic |
114
118
  | `openrouter` | `openrouter/anthropic/claude-2` | Automatic |
119
+ | `moonshot` | `moonshot/kimi-k2` | Automatic |
120
+ | `modelscope` | `modelscope/Qwen2.5-72B-Instruct` | Automatic |
121
+ | `zai` | `zai/glm-4` | Automatic |
122
+ | `cachibot` | `cachibot/openai/gpt-4o-mini` | Automatic |
115
123
  | `ollama` | `ollama/llama3.1:8b` | Free (local) |
116
124
  | `lmstudio` | `lmstudio/local-model` | Free (local) |
117
125
  | `huggingface` | `hf/model-name` | Free (local) |
118
- | `http` | `http/self-hosted` | Free |
126
+ | `airllm` | `airllm/Qwen2-7B` | Free (local) |
127
+ | `local_http` | `local_http/self-hosted` | Free |
128
+
129
+ Aliases (`anthropic`, `gemini`, `chatgpt`, `xai`, `lm_studio`, `zhipu`, `hf`, `dalle`) route to their canonical providers.
130
+
131
+ ## Multi-Modal
132
+
133
+ Beyond text LLMs, Prompture exposes drivers for adjacent modalities under the same `provider/model` routing:
134
+
135
+ - **Embeddings** — OpenAI (`text-embedding-3-*`) and Ollama (`nomic-embed-text`)
136
+ - **Image generation** — OpenAI DALL-E, Google Imagen, Grok, Stability AI
137
+ - **Text-to-speech** — OpenAI (`tts-1`) and ElevenLabs
138
+ - **Speech-to-text** — OpenAI Whisper and ElevenLabs
139
+
140
+ ```python
141
+ from prompture.drivers.img_gen_registry import get_img_gen_driver_for_model
142
+
143
+ driver = get_img_gen_driver_for_model("openai/dall-e-3")
144
+ result = driver.generate_image(
145
+ "a cat on a surfboard at sunset",
146
+ {"size": "1024x1024", "quality": "hd"},
147
+ )
148
+ print(result["meta"]["cost"], result["meta"]["image_count"])
149
+ ```
119
150
 
120
151
  ## Usage
121
152
 
@@ -192,6 +223,48 @@ print(result["json_object"]) # {"name": "John", "age": 28}
192
223
  print(result["usage"]) # token counts and cost
193
224
  ```
194
225
 
226
+ ### Strategy Cascade
227
+
228
+ Prompture picks how to obtain structured JSON based on each model's capabilities. The cascade is `provider_native` (built-in JSON mode / schema enforcement) → `tool_call` (encode the schema as a function definition and read it back from the tool call) → `prompted_repair` (prompt for JSON, repair malformed output via AI cleanup). Pass `strategy="auto"` (default) to let Prompture select per model, or pin a specific strategy via the `StructuredOutputStrategy` enum or its string value. The strategy used is recorded in the response so you can see which path each call took.
229
+
230
+ ### Multi-Model Fallback
231
+
232
+ Try a list of models in priority order, with full per-attempt accounting — every model tried (success, failure, or skipped) is recorded with its cost, tokens, duration, capabilities, and strategy. The first success wins; if all fail, an optional `fallback` Pydantic instance is returned instead of raising.
233
+
234
+ ```python
235
+ from prompture import extract_with_models
236
+
237
+ result = extract_with_models(
238
+ Person,
239
+ "Maria is 32, a software developer in NYC.",
240
+ models=[
241
+ "openai/gpt-4o-mini", # try first
242
+ "claude/claude-3-5-haiku", # fallback
243
+ "ollama/llama3.1:8b", # last resort, free
244
+ ],
245
+ fallback=Person(name="unknown", age=0, profession="unknown"),
246
+ )
247
+
248
+ print(result["selected_model"]) # winning model string
249
+ print(result["model"]) # validated Pydantic instance
250
+ print(result["total_cost"]) # cumulative cost across all attempts
251
+ print(result["total_attempts"]) # number of models actually called
252
+
253
+ for attempt in result["attempts"]:
254
+ print(
255
+ attempt["model"],
256
+ attempt["status"], # "success" | "failed" | "skipped"
257
+ attempt["strategy"], # "single" | "stepwise"
258
+ attempt["cost"],
259
+ attempt["prompt_tokens"],
260
+ attempt["completion_tokens"],
261
+ attempt["duration_ms"],
262
+ attempt["capabilities"], # {"json_mode": bool, "json_schema": bool}
263
+ )
264
+ ```
265
+
266
+ If every model fails and no `fallback` is provided, an `ExtractionError` is raised with the full `attempts` list, `total_cost`, and `total_tokens` attached as attributes.
267
+
195
268
  ### TOON Input — Token Savings
196
269
 
197
270
  Analyze structured data with automatic TOON conversion for 45-60% fewer tokens:
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.0.52.dev1'
22
- __version_tuple__ = version_tuple = (1, 0, 52, 'dev1')
21
+ __version__ = version = '1.0.53.dev1'
22
+ __version_tuple__ = version_tuple = (1, 0, 53, 'dev1')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -14,7 +14,15 @@ except Exception:
14
14
 
15
15
  from ..infra.cost_mixin import CostMixin
16
16
  from .async_base import AsyncDriver
17
- from .claude_driver import ClaudeDriver
17
+ from .claude_driver import (
18
+ ClaudeDriver,
19
+ _build_anthropic_json_mode_tool_def,
20
+ _build_anthropic_meta,
21
+ _build_anthropic_stream_done,
22
+ _convert_tools_to_anthropic,
23
+ _extract_anthropic_system_and_messages,
24
+ _extract_anthropic_text_and_tool_calls,
25
+ )
18
26
 
19
27
 
20
28
  class AsyncClaudeDriver(CostMixin, AsyncDriver):
@@ -60,10 +68,8 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
60
68
 
61
69
  client = anthropic.AsyncAnthropic(api_key=self.api_key)
62
70
 
63
- # Anthropic requires system messages as a top-level parameter
64
- system_content, api_messages = self._extract_system_and_messages(messages)
71
+ system_content, api_messages = _extract_anthropic_system_and_messages(messages)
65
72
 
66
- # Build common kwargs
67
73
  common_kwargs: dict[str, Any] = {
68
74
  "model": model,
69
75
  "messages": api_messages,
@@ -77,14 +83,9 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
77
83
  if options.get("json_mode"):
78
84
  json_schema = options.get("json_schema")
79
85
  if json_schema:
80
- tool_def = {
81
- "name": "extract_json",
82
- "description": "Extract structured data matching the schema",
83
- "input_schema": json_schema,
84
- }
85
86
  resp = await client.messages.create( # type: ignore[call-overload]
86
87
  **common_kwargs,
87
- tools=[tool_def],
88
+ tools=[_build_anthropic_json_mode_tool_def(json_schema)],
88
89
  tool_choice={"type": "tool", "name": "extract_json"},
89
90
  )
90
91
  text = ""
@@ -99,27 +100,14 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
99
100
  resp = await client.messages.create(**common_kwargs)
100
101
  text = resp.content[0].text
101
102
 
102
- # Extract reasoning/thinking content from content blocks
103
103
  reasoning_content = ClaudeDriver._extract_thinking(resp.content)
104
-
105
- # Fallback: use reasoning as text if content is empty
106
104
  if not text and reasoning_content:
107
105
  text = reasoning_content
108
106
 
109
- prompt_tokens = resp.usage.input_tokens
110
- completion_tokens = resp.usage.output_tokens
111
- total_tokens = prompt_tokens + completion_tokens
112
-
113
- total_cost = self._calculate_cost("claude", model, prompt_tokens, completion_tokens)
114
-
115
- meta = {
116
- "prompt_tokens": prompt_tokens,
117
- "completion_tokens": completion_tokens,
118
- "total_tokens": total_tokens,
119
- "cost": round(total_cost, 6),
120
- "raw_response": dict(resp),
121
- "model_name": model,
122
- }
107
+ total_cost = self._calculate_cost(
108
+ "claude", model, resp.usage.input_tokens, resp.usage.output_tokens
109
+ )
110
+ meta = _build_anthropic_meta(resp, model, total_cost)
123
111
 
124
112
  result: dict[str, Any] = {"text": text, "meta": meta}
125
113
  if reasoning_content is not None:
@@ -131,78 +119,7 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
131
119
  # ------------------------------------------------------------------
132
120
 
133
121
  def _extract_system_and_messages(self, messages: list[dict[str, Any]]) -> tuple[str | None, list[dict[str, Any]]]:
134
- """Separate system message and translate tool messages for Anthropic API.
135
-
136
- The conversation layer stores tool use in OpenAI-compatible shape
137
- (assistant messages with a ``tool_calls`` list and separate
138
- ``role: "tool"`` result messages). Anthropic rejects role=tool and
139
- expects:
140
-
141
- - Assistant tool calls → ``content=[{"type": "text", ...}, {"type": "tool_use", "id": ..., "name": ..., "input": {...}}]``
142
- - Tool results → a user message whose content is ``[{"type": "tool_result", "tool_use_id": ..., "content": ...}]``
143
-
144
- Consecutive tool results are merged into a single user message so
145
- the API sees one user turn per assistant turn, as required.
146
- """
147
- system_content: str | None = None
148
- api_messages: list[dict[str, Any]] = []
149
- for msg in messages:
150
- role = msg.get("role")
151
- if role == "system":
152
- system_content = msg.get("content", "")
153
- continue
154
-
155
- if role == "assistant" and msg.get("tool_calls"):
156
- content = msg.get("content") or ""
157
- blocks: list[dict[str, Any]] = []
158
- if content:
159
- blocks.append({"type": "text", "text": content})
160
- for tc in msg["tool_calls"]:
161
- fn = tc.get("function", tc)
162
- raw_args = fn.get("arguments", tc.get("arguments", {}))
163
- if isinstance(raw_args, str):
164
- try:
165
- tool_input = json.loads(raw_args) if raw_args else {}
166
- except json.JSONDecodeError:
167
- tool_input = {"_raw": raw_args}
168
- else:
169
- tool_input = raw_args or {}
170
- blocks.append(
171
- {
172
- "type": "tool_use",
173
- "id": tc.get("id", ""),
174
- "name": fn.get("name", tc.get("name", "")),
175
- "input": tool_input,
176
- }
177
- )
178
- api_messages.append({"role": "assistant", "content": blocks})
179
- continue
180
-
181
- if role == "tool":
182
- result_block = {
183
- "type": "tool_result",
184
- "tool_use_id": msg.get("tool_call_id", ""),
185
- "content": msg.get("content", ""),
186
- }
187
- # Merge with a preceding user message whose content is a list
188
- # of tool_result blocks — Anthropic prefers one user turn per
189
- # assistant turn even with multiple tool calls.
190
- if (
191
- api_messages
192
- and api_messages[-1].get("role") == "user"
193
- and isinstance(api_messages[-1].get("content"), list)
194
- and all(
195
- isinstance(b, dict) and b.get("type") == "tool_result"
196
- for b in api_messages[-1]["content"]
197
- )
198
- ):
199
- api_messages[-1]["content"].append(result_block)
200
- else:
201
- api_messages.append({"role": "user", "content": [result_block]})
202
- continue
203
-
204
- api_messages.append(msg)
205
- return system_content, api_messages
122
+ return _extract_anthropic_system_and_messages(messages)
206
123
 
207
124
  # ------------------------------------------------------------------
208
125
  # Tool use
@@ -225,26 +142,8 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
225
142
 
226
143
  client = anthropic.AsyncAnthropic(api_key=self.api_key)
227
144
 
228
- system_content, api_messages = self._extract_system_and_messages(messages)
229
-
230
- # Convert tools from OpenAI format to Anthropic format if needed
231
- anthropic_tools = []
232
- for t in tools:
233
- if "type" in t and t["type"] == "function":
234
- # OpenAI format -> Anthropic format
235
- fn = t["function"]
236
- anthropic_tools.append(
237
- {
238
- "name": fn["name"],
239
- "description": fn.get("description", ""),
240
- "input_schema": fn.get("parameters", {"type": "object", "properties": {}}),
241
- }
242
- )
243
- elif "input_schema" in t:
244
- # Already Anthropic format
245
- anthropic_tools.append(t)
246
- else:
247
- anthropic_tools.append(t)
145
+ system_content, api_messages = _extract_anthropic_system_and_messages(messages)
146
+ anthropic_tools = _convert_tools_to_anthropic(tools)
248
147
 
249
148
  kwargs: dict[str, Any] = {
250
149
  "model": model,
@@ -258,34 +157,12 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
258
157
 
259
158
  resp = await client.messages.create(**kwargs)
260
159
 
261
- prompt_tokens = resp.usage.input_tokens
262
- completion_tokens = resp.usage.output_tokens
263
- total_tokens = prompt_tokens + completion_tokens
264
- total_cost = self._calculate_cost("claude", model, prompt_tokens, completion_tokens)
265
-
266
- meta = {
267
- "prompt_tokens": prompt_tokens,
268
- "completion_tokens": completion_tokens,
269
- "total_tokens": total_tokens,
270
- "cost": round(total_cost, 6),
271
- "raw_response": dict(resp),
272
- "model_name": model,
273
- }
274
-
275
- text = ""
276
- tool_calls_out: list[dict[str, Any]] = []
277
- for block in resp.content:
278
- if block.type == "text":
279
- text += block.text
280
- elif block.type == "tool_use":
281
- tool_calls_out.append(
282
- {
283
- "id": block.id,
284
- "name": block.name,
285
- "arguments": block.input,
286
- }
287
- )
160
+ total_cost = self._calculate_cost(
161
+ "claude", model, resp.usage.input_tokens, resp.usage.output_tokens
162
+ )
163
+ meta = _build_anthropic_meta(resp, model, total_cost)
288
164
 
165
+ text, tool_calls_out = _extract_anthropic_text_and_tool_calls(resp.content)
289
166
  reasoning_content = ClaudeDriver._extract_thinking(resp.content)
290
167
 
291
168
  result: dict[str, Any] = {
@@ -315,7 +192,7 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
315
192
  model = options.get("model", self.model)
316
193
  client = anthropic.AsyncAnthropic(api_key=self.api_key)
317
194
 
318
- system_content, api_messages = self._extract_system_and_messages(messages)
195
+ system_content, api_messages = _extract_anthropic_system_and_messages(messages)
319
196
 
320
197
  kwargs: dict[str, Any] = {
321
198
  "model": model,
@@ -353,21 +230,7 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
353
230
  if usage:
354
231
  prompt_tokens = getattr(usage, "input_tokens", 0)
355
232
 
356
- total_tokens = prompt_tokens + completion_tokens
357
233
  total_cost = self._calculate_cost("claude", model, prompt_tokens, completion_tokens)
358
-
359
- done_chunk: dict[str, Any] = {
360
- "type": "done",
361
- "text": full_text,
362
- "meta": {
363
- "prompt_tokens": prompt_tokens,
364
- "completion_tokens": completion_tokens,
365
- "total_tokens": total_tokens,
366
- "cost": round(total_cost, 6),
367
- "raw_response": {},
368
- "model_name": model,
369
- },
370
- }
371
- if full_reasoning:
372
- done_chunk["reasoning_content"] = full_reasoning
373
- yield done_chunk
234
+ yield _build_anthropic_stream_done(
235
+ model, full_text, full_reasoning, prompt_tokens, completion_tokens, total_cost
236
+ )