prompture 1.0.53.dev2__tar.gz → 1.0.54.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 (243) hide show
  1. prompture-1.0.54.dev1/.claude/skills/update-pricing/SKILL.md +164 -0
  2. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/PKG-INFO +1 -1
  3. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/_version.py +2 -2
  4. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_base.py +2 -0
  5. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_claude_driver.py +31 -6
  6. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_google_driver.py +9 -1
  7. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_grok_driver.py +16 -2
  8. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_groq_driver.py +16 -2
  9. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_moonshot_driver.py +21 -4
  10. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openai_driver.py +23 -8
  11. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/base.py +2 -0
  12. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/claude_driver.py +63 -7
  13. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/google_driver.py +12 -1
  14. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/grok_driver.py +17 -3
  15. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/groq_driver.py +17 -3
  16. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/moonshot_driver.py +45 -3
  17. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openai_driver.py +41 -8
  18. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/cost_mixin.py +34 -6
  19. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/model_rates.py +8 -18
  20. prompture-1.0.54.dev1/prompture/infra/pricing.py +284 -0
  21. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/anthropic.json +28 -4
  22. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/google.json +42 -7
  23. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/groq.json +44 -8
  24. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/moonshot.json +30 -5
  25. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/openai.json +17 -3
  26. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/xai.json +36 -6
  27. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/settings.py +9 -0
  28. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/tracker.py +32 -2
  29. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/PKG-INFO +1 -1
  30. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/SOURCES.txt +1 -0
  31. prompture-1.0.53.dev2/.claude/skills/update-pricing/SKILL.md +0 -124
  32. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
  33. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
  34. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-example/SKILL.md +0 -0
  35. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-field/SKILL.md +0 -0
  36. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-persona/SKILL.md +0 -0
  37. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-test/SKILL.md +0 -0
  38. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/add-tool/SKILL.md +0 -0
  39. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/create-pr/SKILL.md +0 -0
  40. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
  41. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
  42. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.env.copy +0 -0
  43. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/FUNDING.yml +0 -0
  44. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  45. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/ISSUE_TEMPLATE/pricing_cost_mismatch.yml +0 -0
  46. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/ISSUE_TEMPLATE/provider_driver_bug.yml +0 -0
  47. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/ISSUE_TEMPLATE/schema_extraction_failure.yml +0 -0
  48. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/scripts/update_docs_version.py +0 -0
  49. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/scripts/update_wrapper_version.py +0 -0
  50. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/workflows/dev.yml +0 -0
  51. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/workflows/documentation.yml +0 -0
  52. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/workflows/pricing-freshness.yml +0 -0
  53. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/workflows/publish.yml +0 -0
  54. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.github/workflows/security.yml +0 -0
  55. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/.safety-project.ini +0 -0
  56. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/AGENTS.md +0 -0
  57. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/BREAKING_CHANGES.md +0 -0
  58. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/CLAUDE.md +0 -0
  59. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/CONTRIBUTING.md +0 -0
  60. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/LICENSE +0 -0
  61. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/MANIFEST.in +0 -0
  62. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/README.md +0 -0
  63. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/ROADMAP.md +0 -0
  64. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/SECURITY.md +0 -0
  65. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/dev.ps1 +0 -0
  66. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/dev.sh +0 -0
  67. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/mypy_errors.txt +0 -0
  68. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/README.md +0 -0
  69. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_json/README.md +0 -0
  70. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
  71. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_json/pyproject.toml +0 -0
  72. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_json/test.py +0 -0
  73. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_toon/README.md +0 -0
  74. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
  75. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
  76. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/packages/llm_to_toon/test.py +0 -0
  77. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/__init__.py +0 -0
  78. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/__init__.py +0 -0
  79. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/agent.py +0 -0
  80. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/async_agent.py +0 -0
  81. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/async_conversation.py +0 -0
  82. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/conversation.py +0 -0
  83. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/persona.py +0 -0
  84. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/simulated_tools.py +0 -0
  85. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/skills.py +0 -0
  86. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/tools_schema.py +0 -0
  87. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/agents/types.py +0 -0
  88. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/aio/__init__.py +0 -0
  89. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/bridges/__init__.py +0 -0
  90. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/bridges/tukuy_backend.py +0 -0
  91. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/cli/__init__.py +0 -0
  92. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/cli/cli.py +0 -0
  93. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/cli/formatters.py +0 -0
  94. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/cli/runner.py +0 -0
  95. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/cli/server.py +0 -0
  96. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/__init__.py +0 -0
  97. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/airllm_driver.py +0 -0
  98. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
  99. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_azure_driver.py +0 -0
  100. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_cachibot_driver.py +0 -0
  101. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_elevenlabs_stt_driver.py +0 -0
  102. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_elevenlabs_tts_driver.py +0 -0
  103. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_embedding_base.py +0 -0
  104. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_google_img_gen_driver.py +0 -0
  105. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_google_vertexai_driver.py +0 -0
  106. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_grok_img_gen_driver.py +0 -0
  107. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
  108. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_img_gen_base.py +0 -0
  109. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_lmstudio_driver.py +0 -0
  110. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
  111. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
  112. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
  113. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_ollama_embedding_driver.py +0 -0
  114. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openai_embedding_driver.py +0 -0
  115. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openai_img_gen_driver.py +0 -0
  116. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openai_stt_driver.py +0 -0
  117. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openai_tts_driver.py +0 -0
  118. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_openrouter_driver.py +0 -0
  119. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_registry.py +0 -0
  120. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_stability_img_gen_driver.py +0 -0
  121. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_stt_base.py +0 -0
  122. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_tts_base.py +0 -0
  123. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/async_zai_driver.py +0 -0
  124. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/audio_registry.py +0 -0
  125. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/azure_config.py +0 -0
  126. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/azure_driver.py +0 -0
  127. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/cachibot_driver.py +0 -0
  128. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/driver_registry.py +0 -0
  129. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/elevenlabs_stt_driver.py +0 -0
  130. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/elevenlabs_tts_driver.py +0 -0
  131. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/embedding_base.py +0 -0
  132. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/embedding_registry.py +0 -0
  133. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/google_img_gen_driver.py +0 -0
  134. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/google_vertexai_driver.py +0 -0
  135. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/grok_img_gen_driver.py +0 -0
  136. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/hugging_driver.py +0 -0
  137. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/img_gen_base.py +0 -0
  138. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/img_gen_registry.py +0 -0
  139. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/lmstudio_driver.py +0 -0
  140. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/local_http_driver.py +0 -0
  141. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/modelscope_driver.py +0 -0
  142. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/ollama_driver.py +0 -0
  143. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/ollama_embedding_driver.py +0 -0
  144. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openai_embedding_driver.py +0 -0
  145. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openai_img_gen_driver.py +0 -0
  146. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openai_stt_driver.py +0 -0
  147. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openai_tts_driver.py +0 -0
  148. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/openrouter_driver.py +0 -0
  149. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/provider_descriptors.py +0 -0
  150. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/registry.py +0 -0
  151. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/stability_img_gen_driver.py +0 -0
  152. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/stt_base.py +0 -0
  153. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/tts_base.py +0 -0
  154. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/vision_helpers.py +0 -0
  155. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/drivers/zai_driver.py +0 -0
  156. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/exceptions.py +0 -0
  157. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/__init__.py +0 -0
  158. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/async_core.py +0 -0
  159. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/core.py +0 -0
  160. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/fields.py +0 -0
  161. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/reasoning.py +0 -0
  162. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/strategy.py +0 -0
  163. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/tools.py +0 -0
  164. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/tukuy_bridge.py +0 -0
  165. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/extraction/validator.py +0 -0
  166. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/__init__.py +0 -0
  167. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/async_groups.py +0 -0
  168. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/consensus.py +0 -0
  169. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/debate.py +0 -0
  170. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/groups.py +0 -0
  171. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/groups/types.py +0 -0
  172. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/__init__.py +0 -0
  173. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/budget.py +0 -0
  174. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/cache.py +0 -0
  175. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/callbacks.py +0 -0
  176. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/capabilities.py +0 -0
  177. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/discovery.py +0 -0
  178. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/ledger.py +0 -0
  179. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/logging.py +0 -0
  180. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/provider_env.py +0 -0
  181. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/azure.json +0 -0
  182. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/google_vertexai.json +0 -0
  183. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/rates/openrouter.json +0 -0
  184. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/session.py +0 -0
  185. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/infra/tukuy_backend.py +0 -0
  186. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/__init__.py +0 -0
  187. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/chunking.py +0 -0
  188. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/detect.py +0 -0
  189. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/document.py +0 -0
  190. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/__init__.py +0 -0
  191. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/base.py +0 -0
  192. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/csv_parser.py +0 -0
  193. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/docx.py +0 -0
  194. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/html.py +0 -0
  195. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/markdown.py +0 -0
  196. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/pdf.py +0 -0
  197. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/ingestion/parsers/xlsx.py +0 -0
  198. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/integrations/__init__.py +0 -0
  199. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/integrations/tukuy_bridge.py +0 -0
  200. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/media/__init__.py +0 -0
  201. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/media/audio.py +0 -0
  202. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/media/image.py +0 -0
  203. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/01-why-prompture.md +0 -0
  204. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/02-installation.md +0 -0
  205. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/03-quickstart.md +0 -0
  206. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/04-toon-input.md +0 -0
  207. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/05-test-harness.md +0 -0
  208. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/06-stability.md +0 -0
  209. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pages/07-migration.md +0 -0
  210. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/persistence/__init__.py +0 -0
  211. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/persistence/history.py +0 -0
  212. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/persistence/serialization.py +0 -0
  213. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/persistence/store.py +0 -0
  214. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pipeline/__init__.py +0 -0
  215. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pipeline/pipeline.py +0 -0
  216. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pipeline/resolver.py +0 -0
  217. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/pipeline/routing.py +0 -0
  218. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/py.typed +0 -0
  219. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/__init__.py +0 -0
  220. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/generator.py +0 -0
  221. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
  222. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
  223. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
  224. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
  225. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
  226. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
  227. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
  228. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/testing/__init__.py +0 -0
  229. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture/testing/driver_contract.py +0 -0
  230. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/dependency_links.txt +0 -0
  231. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/entry_points.txt +0 -0
  232. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/requires.txt +0 -0
  233. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture.egg-info/top_level.txt +0 -0
  234. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/prompture_cost_tracking.md +0 -0
  235. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/pyproject.toml +0 -0
  236. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/requirements.txt +0 -0
  237. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/scripts/check_pricing_freshness.py +0 -0
  238. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/setup.cfg +0 -0
  239. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/specs/basic_extraction.json +0 -0
  240. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/specs/schema_validation.json +0 -0
  241. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/specs/strategy_comparison.json +0 -0
  242. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/test.py +0 -0
  243. {prompture-1.0.53.dev2 → prompture-1.0.54.dev1}/test_version_diagnosis.py +0 -0
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: update-pricing
3
+ description: Update LLM model pricing in Prompture. Pricing resolves through a pluggable source registry — by default local KB JSON files (primary, curated) then models.dev (fallback). Use when model prices change, new models launch, models.dev data is stale, or you need to add a custom pricing source.
4
+ metadata:
5
+ author: prompture
6
+ version: "4.0"
7
+ ---
8
+
9
+ # Update Model Pricing
10
+
11
+ ## How Pricing Resolves
12
+
13
+ Pricing flows through a **pluggable source registry** at `prompture/infra/pricing.py`. `get_model_rates(provider, model_id)` walks the registered sources in priority order (lowest first) and returns the first dict that includes both `input` and `output` per-1M rates.
14
+
15
+ Default registration (controlled by `Settings.pricing_source`):
16
+
17
+ | Setting value | Sources registered (priority order) |
18
+ |---------------|-------------------------------------|
19
+ | `local_first` (default) | `LocalKBPricingSource` (priority 0) → `ModelsDevPricingSource` (priority 100) |
20
+ | `local_only` | `LocalKBPricingSource` only |
21
+ | `models_dev_only` | `ModelsDevPricingSource` only |
22
+
23
+ **Use `local_first` (default)** when you want curated rates with automatic fallback. **`local_only`** for offline/locked-down envs or when you don't trust models.dev. **`models_dev_only`** as a temporary opt-out while local files are unmaintained.
24
+
25
+ `CostMixin._calculate_cost()` consumes the resolved dict — `input`, `output`, optional `cache_read`, `cache_write`, `reasoning` rates per 1M tokens. Cached prompt tokens are billed at `cache_read` (falls back to `input` rate when not published).
26
+
27
+ ## Local KB layout (the primary source)
28
+
29
+ Per-provider JSON files in `prompture/infra/rates/` carry both **capabilities** and **pricing** for each model. Adding a `cost` block makes that model use local pricing; omitting it falls through to models.dev.
30
+
31
+ ```json
32
+ {
33
+ "gpt-5.5": {
34
+ "supports_temperature": false,
35
+ "supports_tool_use": true,
36
+ "supports_structured_output": true,
37
+ "supports_vision": true,
38
+ "is_reasoning": true,
39
+ "context_window": 1050000,
40
+ "max_output_tokens": 128000,
41
+ "modalities_input": ["text", "image"],
42
+ "modalities_output": ["text"],
43
+ "api_type": "openai",
44
+ "tokens_param": "max_completion_tokens",
45
+ "cost": {
46
+ "input": 5.00,
47
+ "output": 30.00,
48
+ "cache_read": 0.50
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ **`cost` block keys** (USD per 1M tokens):
55
+ - `input` — required for cost to compute
56
+ - `output` — required
57
+ - `cache_read` — optional. Discount rate for cached prompt tokens (Anthropic / OpenAI / Kimi all support this).
58
+ - `cache_write` — optional. Anthropic only — premium rate for tokens written to prompt cache.
59
+ - `reasoning` — optional. For models that bill reasoning tokens separately.
60
+
61
+ ## Vendor pricing pages (verify before editing)
62
+
63
+ **Always cross-check rates against the vendor's official page**, not just models.dev — feeds get stale.
64
+
65
+ | Provider | Pricing page |
66
+ |----------|--------------|
67
+ | OpenAI | https://developers.openai.com/api/docs/pricing |
68
+ | Anthropic (Claude) | https://platform.claude.com/docs/en/about-claude/pricing |
69
+ | Moonshot (Kimi) | https://platform.kimi.ai/docs (search "Model Pricing") |
70
+ | xAI (Grok) | https://docs.x.ai/docs/models |
71
+ | Google (Gemini) | https://ai.google.dev/gemini-api/docs/pricing |
72
+ | Groq | https://groq.com/pricing |
73
+ | OpenRouter | https://openrouter.ai/models |
74
+ | Moonshot K2 | https://platform.moonshot.ai/docs/pricing/chat |
75
+ | Z.ai (GLM) | https://docs.z.ai/guides/llm/pricing |
76
+
77
+ When a vendor publishes "cache hit" / "cache read" pricing, encode it as `cache_read`. When they publish a "cache write" / "cache creation" rate (Anthropic only), encode it as `cache_write`.
78
+
79
+ ## When to update what
80
+
81
+ | Scenario | Action |
82
+ |----------|--------|
83
+ | Vendor changed prices for a model already in the KB | Update the `cost` block in `rates/{provider}.json` |
84
+ | New model from existing provider | Add entry with capabilities + `cost` block |
85
+ | New provider | Create `rates/{provider}.json`, add to `PROVIDER_MAP` in `model_rates.py`, wire descriptor in `provider_descriptors.py` |
86
+ | Model not in KB but in models.dev | Nothing — fallback already covers it |
87
+ | You want to override what models.dev says | Add the `cost` block to local KB; it wins |
88
+ | You want to lock pricing to local-only | Set `pricing_source=local_only` in env / `.env` |
89
+
90
+ ## Adding a custom pricing source
91
+
92
+ For internal price feeds, vendor APIs the registry doesn't cover, or per-environment overrides:
93
+
94
+ ```python
95
+ from prompture.infra.pricing import register_pricing_source
96
+
97
+ class MyInternalFeed:
98
+ name = "internal_feed"
99
+ priority = 50 # between local_kb (0) and models_dev (100)
100
+
101
+ def get_rates(self, provider: str, model_id: str) -> dict[str, float] | None:
102
+ # Return {"input": ..., "output": ..., "cache_read": ...} or None
103
+ return _query_my_internal_service(provider, model_id)
104
+
105
+ register_pricing_source(MyInternalFeed())
106
+ ```
107
+
108
+ Sources are ordered by `(priority, name)`. The first to return a dict containing both `input` and `output` wins. Exceptions inside `get_rates` are swallowed and the next source is tried.
109
+
110
+ ## Refreshing the models.dev cache
111
+
112
+ ```python
113
+ from prompture.infra.model_rates import refresh_rates_cache
114
+ refresh_rates_cache(force=True)
115
+ ```
116
+
117
+ Or delete the cache file:
118
+ ```bash
119
+ rm ~/.prompture/cache/models_dev.json
120
+ ```
121
+
122
+ ## Provider name → models.dev key mapping
123
+
124
+ Lives in `prompture/infra/model_rates.py` (`PROVIDER_MAP`):
125
+
126
+ ```python
127
+ {"openai": "openai", "claude": "anthropic", "google": "google", "groq": "groq",
128
+ "grok": "xai", "azure": "azure", "openrouter": "openrouter",
129
+ "moonshot": "moonshotai", "zai": "zai"}
130
+ ```
131
+
132
+ ## Free / local drivers (always $0)
133
+
134
+ `ollama_driver.py`, `lmstudio_driver.py`, `local_http_driver.py`, `airllm_driver.py`, `hugging_driver.py`.
135
+
136
+ ## Steps for adding / updating a model price
137
+
138
+ 1. **Open** the vendor's pricing page (table above) and copy the current rates.
139
+ 2. **Edit** `prompture/infra/rates/{provider}.json` — find the model entry (or add a new one with capabilities) and add/update the `cost` block.
140
+ 3. **Verify** with `python -c "from prompture.infra.model_rates import get_model_rates; print(get_model_rates('openai', 'gpt-5.5'))"`. The dict should include exactly the keys you wrote.
141
+ 4. **Run tests**: `pytest tests/test_pricing.py tests/test_cached_tokens.py -q`
142
+ 5. **For long-running processes** that imported the LocalKBPricingSource before the edit: the in-memory KB is loaded once at startup. Either restart, or call `LocalKBPricingSource().reload()` on the registered instance.
143
+
144
+ ## Verification commands
145
+
146
+ ```bash
147
+ # Check resolved rates for a model
148
+ python -c "from prompture.infra.model_rates import get_model_rates; print(get_model_rates('openai', 'gpt-5.5'))"
149
+
150
+ # Inspect which sources are registered
151
+ python -c "from prompture.infra.pricing import get_pricing_sources; print([(s.name, s.priority) for s in get_pricing_sources()])"
152
+
153
+ # Check that capabilities still load
154
+ python -c "from prompture.infra.model_rates import get_model_capabilities; print(get_model_capabilities('openai', 'gpt-5.5'))"
155
+
156
+ # Run pricing + cost tests
157
+ pytest tests/test_pricing.py tests/test_cached_tokens.py tests/test_model_rates.py -q
158
+ ```
159
+
160
+ ## Side effects of editing the KB
161
+
162
+ - `prompture/infra/discovery.py` reads KB model IDs via `get_kb_models_for_provider()` for static detection (no API key needed).
163
+ - `CostMixin._get_model_config()` still reads `tokens_param` / `supports_temperature` from the KB capabilities.
164
+ - A model with a local `cost` block now appears in cost calculations regardless of models.dev cache state — useful when models.dev lags vendor announcements.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prompture
3
- Version: 1.0.53.dev2
3
+ Version: 1.0.54.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
@@ -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.53.dev2'
22
- __version_tuple__ = version_tuple = (1, 0, 53, 'dev2')
21
+ __version__ = version = '1.0.54.dev1'
22
+ __version_tuple__ = version_tuple = (1, 0, 54, 'dev1')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -232,6 +232,8 @@ class AsyncDriver(ABC):
232
232
  prompt_tokens=meta.get("prompt_tokens", 0),
233
233
  completion_tokens=meta.get("completion_tokens", 0),
234
234
  total_tokens=meta.get("total_tokens", 0),
235
+ cached_prompt_tokens=meta.get("cached_prompt_tokens", 0),
236
+ cache_creation_tokens=meta.get("cache_creation_tokens", 0),
235
237
  cost=meta.get("cost", 0.0),
236
238
  elapsed_ms=elapsed_ms,
237
239
  status=status,
@@ -20,6 +20,7 @@ from .claude_driver import (
20
20
  _build_anthropic_meta,
21
21
  _build_anthropic_stream_done,
22
22
  _convert_tools_to_anthropic,
23
+ _extract_anthropic_cache_tokens,
23
24
  _extract_anthropic_system_and_messages,
24
25
  _extract_anthropic_text_and_tool_calls,
25
26
  )
@@ -104,8 +105,14 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
104
105
  if not text and reasoning_content:
105
106
  text = reasoning_content
106
107
 
108
+ cache_read, cache_create = _extract_anthropic_cache_tokens(resp.usage)
107
109
  total_cost = self._calculate_cost(
108
- "claude", model, resp.usage.input_tokens, resp.usage.output_tokens
110
+ "claude",
111
+ model,
112
+ resp.usage.input_tokens + cache_read + cache_create,
113
+ resp.usage.output_tokens,
114
+ cached_tokens=cache_read,
115
+ cache_creation_tokens=cache_create,
109
116
  )
110
117
  meta = _build_anthropic_meta(resp, model, total_cost)
111
118
 
@@ -157,8 +164,14 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
157
164
 
158
165
  resp = await client.messages.create(**kwargs)
159
166
 
167
+ cache_read, cache_create = _extract_anthropic_cache_tokens(resp.usage)
160
168
  total_cost = self._calculate_cost(
161
- "claude", model, resp.usage.input_tokens, resp.usage.output_tokens
169
+ "claude",
170
+ model,
171
+ resp.usage.input_tokens + cache_read + cache_create,
172
+ resp.usage.output_tokens,
173
+ cached_tokens=cache_read,
174
+ cache_creation_tokens=cache_create,
162
175
  )
163
176
  meta = _build_anthropic_meta(resp, model, total_cost)
164
177
 
@@ -205,8 +218,10 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
205
218
 
206
219
  full_text = ""
207
220
  full_reasoning = ""
208
- prompt_tokens = 0
221
+ base_input = 0
209
222
  completion_tokens = 0
223
+ cache_read = 0
224
+ cache_create = 0
210
225
 
211
226
  async with client.messages.stream(**kwargs) as stream:
212
227
  async for event in stream:
@@ -228,9 +243,19 @@ class AsyncClaudeDriver(CostMixin, AsyncDriver):
228
243
  elif event.type == "message_start" and hasattr(event, "message"):
229
244
  usage = getattr(event.message, "usage", None)
230
245
  if usage:
231
- prompt_tokens = getattr(usage, "input_tokens", 0)
246
+ base_input = getattr(usage, "input_tokens", 0)
247
+ cache_read, cache_create = _extract_anthropic_cache_tokens(usage)
232
248
 
233
- total_cost = self._calculate_cost("claude", model, prompt_tokens, completion_tokens)
249
+ prompt_tokens = base_input + cache_read + cache_create
250
+ total_cost = self._calculate_cost(
251
+ "claude",
252
+ model,
253
+ prompt_tokens,
254
+ completion_tokens,
255
+ cached_tokens=cache_read,
256
+ cache_creation_tokens=cache_create,
257
+ )
234
258
  yield _build_anthropic_stream_done(
235
- model, full_text, full_reasoning, prompt_tokens, completion_tokens, total_cost
259
+ model, full_text, full_reasoning, prompt_tokens, completion_tokens, total_cost,
260
+ cached_prompt_tokens=cache_read, cache_creation_tokens=cache_create,
236
261
  )
@@ -64,11 +64,18 @@ class AsyncGoogleDriver(CostMixin, AsyncDriver):
64
64
  def _extract_usage_metadata(self, response: Any, messages: list[dict[str, Any]]) -> dict[str, Any]:
65
65
  """Extract token counts from response, falling back to character estimation."""
66
66
  usage = getattr(response, "usage_metadata", None)
67
+ cached_prompt_tokens = 0
67
68
  if usage:
68
69
  prompt_tokens = getattr(usage, "prompt_token_count", 0) or 0
69
70
  completion_tokens = getattr(usage, "candidates_token_count", 0) or 0
70
71
  total_tokens = getattr(usage, "total_token_count", 0) or (prompt_tokens + completion_tokens)
71
- cost = self._calculate_cost("google", self.model, prompt_tokens, completion_tokens)
72
+ raw_cached = getattr(usage, "cached_content_token_count", 0) or 0
73
+ if isinstance(raw_cached, (int, float)):
74
+ cached_prompt_tokens = int(raw_cached)
75
+ cost = self._calculate_cost(
76
+ "google", self.model, prompt_tokens, completion_tokens,
77
+ cached_tokens=cached_prompt_tokens,
78
+ )
72
79
  else:
73
80
  # Fallback: estimate from character counts
74
81
  total_prompt_chars = 0
@@ -92,6 +99,7 @@ class AsyncGoogleDriver(CostMixin, AsyncDriver):
92
99
  "prompt_tokens": prompt_tokens,
93
100
  "completion_tokens": completion_tokens,
94
101
  "total_tokens": total_tokens,
102
+ "cached_prompt_tokens": cached_prompt_tokens,
95
103
  "cost": round(cost, 6),
96
104
  }
97
105
 
@@ -81,17 +81,24 @@ class AsyncGrokDriver(CostMixin, AsyncDriver):
81
81
  except Exception as e:
82
82
  raise RuntimeError(f"Grok API request failed: {e!s}") from e
83
83
 
84
+ from .moonshot_driver import _extract_moonshot_cached_tokens
85
+
84
86
  usage = resp.get("usage", {})
85
87
  prompt_tokens = usage.get("prompt_tokens", 0)
86
88
  completion_tokens = usage.get("completion_tokens", 0)
87
89
  total_tokens = usage.get("total_tokens", 0)
90
+ cached_prompt_tokens = _extract_moonshot_cached_tokens(usage)
88
91
 
89
- total_cost = self._calculate_cost("grok", model, prompt_tokens, completion_tokens)
92
+ total_cost = self._calculate_cost(
93
+ "grok", model, prompt_tokens, completion_tokens,
94
+ cached_tokens=cached_prompt_tokens,
95
+ )
90
96
 
91
97
  meta = {
92
98
  "prompt_tokens": prompt_tokens,
93
99
  "completion_tokens": completion_tokens,
94
100
  "total_tokens": total_tokens,
101
+ "cached_prompt_tokens": cached_prompt_tokens,
95
102
  "cost": round(total_cost, 6),
96
103
  "raw_response": resp,
97
104
  "model_name": model,
@@ -159,16 +166,23 @@ class AsyncGrokDriver(CostMixin, AsyncDriver):
159
166
  except Exception as e:
160
167
  raise RuntimeError(f"Grok API request failed: {e!s}") from e
161
168
 
169
+ from .moonshot_driver import _extract_moonshot_cached_tokens
170
+
162
171
  usage = resp.get("usage", {})
163
172
  prompt_tokens = usage.get("prompt_tokens", 0)
164
173
  completion_tokens = usage.get("completion_tokens", 0)
165
174
  total_tokens = usage.get("total_tokens", 0)
166
- total_cost = self._calculate_cost("grok", model, prompt_tokens, completion_tokens)
175
+ cached_prompt_tokens = _extract_moonshot_cached_tokens(usage)
176
+ total_cost = self._calculate_cost(
177
+ "grok", model, prompt_tokens, completion_tokens,
178
+ cached_tokens=cached_prompt_tokens,
179
+ )
167
180
 
168
181
  meta = {
169
182
  "prompt_tokens": prompt_tokens,
170
183
  "completion_tokens": completion_tokens,
171
184
  "total_tokens": total_tokens,
185
+ "cached_prompt_tokens": cached_prompt_tokens,
172
186
  "cost": round(total_cost, 6),
173
187
  "raw_response": resp,
174
188
  "model_name": model,
@@ -75,17 +75,24 @@ class AsyncGroqDriver(CostMixin, AsyncDriver):
75
75
 
76
76
  resp = await self.client.chat.completions.create(**kwargs)
77
77
 
78
+ from .openai_driver import _extract_openai_cached_tokens
79
+
78
80
  usage = getattr(resp, "usage", None)
79
81
  prompt_tokens = getattr(usage, "prompt_tokens", 0)
80
82
  completion_tokens = getattr(usage, "completion_tokens", 0)
81
83
  total_tokens = getattr(usage, "total_tokens", 0)
84
+ cached_prompt_tokens = _extract_openai_cached_tokens(usage)
82
85
 
83
- total_cost = self._calculate_cost("groq", model, prompt_tokens, completion_tokens)
86
+ total_cost = self._calculate_cost(
87
+ "groq", model, prompt_tokens, completion_tokens,
88
+ cached_tokens=cached_prompt_tokens,
89
+ )
84
90
 
85
91
  meta = {
86
92
  "prompt_tokens": prompt_tokens,
87
93
  "completion_tokens": completion_tokens,
88
94
  "total_tokens": total_tokens,
95
+ "cached_prompt_tokens": cached_prompt_tokens,
89
96
  "cost": round(total_cost, 6),
90
97
  "raw_response": resp.model_dump(),
91
98
  "model_name": model,
@@ -137,16 +144,23 @@ class AsyncGroqDriver(CostMixin, AsyncDriver):
137
144
 
138
145
  resp = await self.client.chat.completions.create(**kwargs)
139
146
 
147
+ from .openai_driver import _extract_openai_cached_tokens
148
+
140
149
  usage = getattr(resp, "usage", None)
141
150
  prompt_tokens = getattr(usage, "prompt_tokens", 0)
142
151
  completion_tokens = getattr(usage, "completion_tokens", 0)
143
152
  total_tokens = getattr(usage, "total_tokens", 0)
144
- total_cost = self._calculate_cost("groq", model, prompt_tokens, completion_tokens)
153
+ cached_prompt_tokens = _extract_openai_cached_tokens(usage)
154
+ total_cost = self._calculate_cost(
155
+ "groq", model, prompt_tokens, completion_tokens,
156
+ cached_tokens=cached_prompt_tokens,
157
+ )
145
158
 
146
159
  meta = {
147
160
  "prompt_tokens": prompt_tokens,
148
161
  "completion_tokens": completion_tokens,
149
162
  "total_tokens": total_tokens,
163
+ "cached_prompt_tokens": cached_prompt_tokens,
150
164
  "cost": round(total_cost, 6),
151
165
  "raw_response": resp.model_dump(),
152
166
  "model_name": model,
@@ -20,7 +20,7 @@ import httpx
20
20
  from ..infra.cost_mixin import CostMixin, prepare_strict_schema
21
21
  from .async_base import AsyncDriver
22
22
  from .base import _parse_tool_arguments
23
- from .moonshot_driver import MoonshotDriver
23
+ from .moonshot_driver import MoonshotDriver, _extract_moonshot_cached_tokens
24
24
 
25
25
  logger = logging.getLogger("prompture.drivers.moonshot")
26
26
 
@@ -142,6 +142,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
142
142
  prompt_tokens = usage.get("prompt_tokens", 0)
143
143
  completion_tokens = usage.get("completion_tokens", 0)
144
144
  total_tokens = usage.get("total_tokens", 0)
145
+ cached_prompt_tokens = _extract_moonshot_cached_tokens(usage)
145
146
 
146
147
  message = resp["choices"][0]["message"]
147
148
  text = message.get("content") or ""
@@ -189,6 +190,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
189
190
  prompt_tokens += fb_usage.get("prompt_tokens", 0)
190
191
  completion_tokens = fb_usage.get("completion_tokens", 0)
191
192
  total_tokens = prompt_tokens + completion_tokens
193
+ cached_prompt_tokens += _extract_moonshot_cached_tokens(fb_usage)
192
194
  resp = fb_resp
193
195
  fb_message = fb_resp["choices"][0]["message"]
194
196
  text = fb_message.get("content") or ""
@@ -196,12 +198,16 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
196
198
  if not text and reasoning_content:
197
199
  text = reasoning_content
198
200
 
199
- total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
201
+ total_cost = self._calculate_cost(
202
+ "moonshot", model, prompt_tokens, completion_tokens,
203
+ cached_tokens=cached_prompt_tokens,
204
+ )
200
205
 
201
206
  meta = {
202
207
  "prompt_tokens": prompt_tokens,
203
208
  "completion_tokens": completion_tokens,
204
209
  "total_tokens": total_tokens,
210
+ "cached_prompt_tokens": cached_prompt_tokens,
205
211
  "cost": round(total_cost, 6),
206
212
  "raw_response": resp,
207
213
  "model_name": model,
@@ -280,12 +286,17 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
280
286
  prompt_tokens = usage.get("prompt_tokens", 0)
281
287
  completion_tokens = usage.get("completion_tokens", 0)
282
288
  total_tokens = usage.get("total_tokens", 0)
283
- total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
289
+ cached_prompt_tokens = _extract_moonshot_cached_tokens(usage)
290
+ total_cost = self._calculate_cost(
291
+ "moonshot", model, prompt_tokens, completion_tokens,
292
+ cached_tokens=cached_prompt_tokens,
293
+ )
284
294
 
285
295
  meta = {
286
296
  "prompt_tokens": prompt_tokens,
287
297
  "completion_tokens": completion_tokens,
288
298
  "total_tokens": total_tokens,
299
+ "cached_prompt_tokens": cached_prompt_tokens,
289
300
  "cost": round(total_cost, 6),
290
301
  "raw_response": resp,
291
302
  "model_name": model,
@@ -365,6 +376,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
365
376
  full_reasoning = ""
366
377
  prompt_tokens = 0
367
378
  completion_tokens = 0
379
+ cached_prompt_tokens = 0
368
380
 
369
381
  async with (
370
382
  httpx.AsyncClient() as client,
@@ -392,6 +404,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
392
404
  if usage:
393
405
  prompt_tokens = usage.get("prompt_tokens", 0)
394
406
  completion_tokens = usage.get("completion_tokens", 0)
407
+ cached_prompt_tokens = _extract_moonshot_cached_tokens(usage)
395
408
 
396
409
  choices = chunk.get("choices", [])
397
410
  if choices:
@@ -407,7 +420,10 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
407
420
  yield {"type": "delta", "text": content}
408
421
 
409
422
  total_tokens = prompt_tokens + completion_tokens
410
- total_cost = self._calculate_cost("moonshot", model, prompt_tokens, completion_tokens)
423
+ total_cost = self._calculate_cost(
424
+ "moonshot", model, prompt_tokens, completion_tokens,
425
+ cached_tokens=cached_prompt_tokens,
426
+ )
411
427
 
412
428
  done_chunk: dict[str, Any] = {
413
429
  "type": "done",
@@ -416,6 +432,7 @@ class AsyncMoonshotDriver(CostMixin, AsyncDriver):
416
432
  "prompt_tokens": prompt_tokens,
417
433
  "completion_tokens": completion_tokens,
418
434
  "total_tokens": total_tokens,
435
+ "cached_prompt_tokens": cached_prompt_tokens,
419
436
  "cost": round(total_cost, 6),
420
437
  "raw_response": {},
421
438
  "model_name": model,
@@ -19,6 +19,7 @@ from .openai_driver import (
19
19
  _build_openai_base_kwargs,
20
20
  _build_openai_json_mode_response_format,
21
21
  _build_openai_stream_done,
22
+ _extract_openai_cached_tokens,
22
23
  _extract_openai_meta,
23
24
  _extract_openai_tool_calls,
24
25
  )
@@ -90,9 +91,13 @@ class AsyncOpenAIDriver(CostMixin, AsyncDriver):
90
91
 
91
92
  resp = await self.client.chat.completions.create(**kwargs)
92
93
 
93
- prompt_tokens = getattr(getattr(resp, "usage", None), "prompt_tokens", 0)
94
- completion_tokens = getattr(getattr(resp, "usage", None), "completion_tokens", 0)
95
- total_cost = self._calculate_cost("openai", model, prompt_tokens, completion_tokens)
94
+ usage = getattr(resp, "usage", None)
95
+ prompt_tokens = getattr(usage, "prompt_tokens", 0)
96
+ completion_tokens = getattr(usage, "completion_tokens", 0)
97
+ cached_prompt_tokens = _extract_openai_cached_tokens(usage)
98
+ total_cost = self._calculate_cost(
99
+ "openai", model, prompt_tokens, completion_tokens, cached_tokens=cached_prompt_tokens
100
+ )
96
101
  meta = _extract_openai_meta(resp, model, total_cost)
97
102
 
98
103
  text = resp.choices[0].message.content
@@ -126,9 +131,13 @@ class AsyncOpenAIDriver(CostMixin, AsyncDriver):
126
131
 
127
132
  resp = await self.client.chat.completions.create(**kwargs)
128
133
 
129
- prompt_tokens = getattr(getattr(resp, "usage", None), "prompt_tokens", 0)
130
- completion_tokens = getattr(getattr(resp, "usage", None), "completion_tokens", 0)
131
- total_cost = self._calculate_cost("openai", model, prompt_tokens, completion_tokens)
134
+ usage = getattr(resp, "usage", None)
135
+ prompt_tokens = getattr(usage, "prompt_tokens", 0)
136
+ completion_tokens = getattr(usage, "completion_tokens", 0)
137
+ cached_prompt_tokens = _extract_openai_cached_tokens(usage)
138
+ total_cost = self._calculate_cost(
139
+ "openai", model, prompt_tokens, completion_tokens, cached_tokens=cached_prompt_tokens
140
+ )
132
141
  meta = _extract_openai_meta(resp, model, total_cost)
133
142
 
134
143
  choice = resp.choices[0]
@@ -177,12 +186,14 @@ class AsyncOpenAIDriver(CostMixin, AsyncDriver):
177
186
  full_text = ""
178
187
  prompt_tokens = 0
179
188
  completion_tokens = 0
189
+ cached_prompt_tokens = 0
180
190
 
181
191
  async for chunk in stream:
182
192
  # Usage comes in the final chunk
183
193
  if getattr(chunk, "usage", None):
184
194
  prompt_tokens = chunk.usage.prompt_tokens or 0
185
195
  completion_tokens = chunk.usage.completion_tokens or 0
196
+ cached_prompt_tokens = _extract_openai_cached_tokens(chunk.usage)
186
197
 
187
198
  if chunk.choices:
188
199
  delta = chunk.choices[0].delta
@@ -191,5 +202,9 @@ class AsyncOpenAIDriver(CostMixin, AsyncDriver):
191
202
  full_text += content
192
203
  yield {"type": "delta", "text": content}
193
204
 
194
- total_cost = self._calculate_cost("openai", model, prompt_tokens, completion_tokens)
195
- yield _build_openai_stream_done(model, full_text, prompt_tokens, completion_tokens, total_cost)
205
+ total_cost = self._calculate_cost(
206
+ "openai", model, prompt_tokens, completion_tokens, cached_tokens=cached_prompt_tokens
207
+ )
208
+ yield _build_openai_stream_done(
209
+ model, full_text, prompt_tokens, completion_tokens, total_cost, cached_prompt_tokens
210
+ )
@@ -402,6 +402,8 @@ class Driver(ABC):
402
402
  prompt_tokens=meta.get("prompt_tokens", 0),
403
403
  completion_tokens=meta.get("completion_tokens", 0),
404
404
  total_tokens=meta.get("total_tokens", 0),
405
+ cached_prompt_tokens=meta.get("cached_prompt_tokens", 0),
406
+ cache_creation_tokens=meta.get("cache_creation_tokens", 0),
405
407
  cost=meta.get("cost", 0.0),
406
408
  elapsed_ms=elapsed_ms,
407
409
  status=status,