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.
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/dev.yml +10 -0
- prompture-1.0.53.dev1/.github/workflows/pricing-freshness.yml +25 -0
- prompture-1.0.53.dev1/AGENTS.md +93 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/BREAKING_CHANGES.md +1 -1
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/PKG-INFO +76 -3
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/README.md +75 -2
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/_version.py +2 -2
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_claude_driver.py +27 -164
- prompture-1.0.53.dev1/prompture/drivers/async_google_vertexai_driver.py +425 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_driver.py +31 -95
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/claude_driver.py +174 -162
- prompture-1.0.53.dev1/prompture/drivers/google_vertexai_driver.py +709 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_driver.py +109 -100
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/provider_descriptors.py +49 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/capabilities.py +16 -13
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/anthropic.json +17 -4
- prompture-1.0.53.dev1/prompture/infra/rates/google.json +119 -0
- prompture-1.0.52.dev1/prompture/infra/rates/google.json → prompture-1.0.53.dev1/prompture/infra/rates/google_vertexai.json +22 -9
- prompture-1.0.53.dev1/prompture/infra/rates/groq.json +119 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/moonshot.json +30 -17
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/openai.json +39 -0
- prompture-1.0.53.dev1/prompture/infra/rates/xai.json +119 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/settings.py +7 -0
- prompture-1.0.53.dev1/prompture/testing/__init__.py +25 -0
- prompture-1.0.53.dev1/prompture/testing/driver_contract.py +76 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/PKG-INFO +76 -3
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/SOURCES.txt +8 -22
- prompture-1.0.53.dev1/scripts/check_pricing_freshness.py +167 -0
- prompture-1.0.52.dev1/docs/source/_generated/compatibility_matrix.rst +0 -124
- prompture-1.0.52.dev1/docs/source/_static/custom.css +0 -40
- prompture-1.0.52.dev1/docs/source/_templates/footer.html +0 -16
- prompture-1.0.52.dev1/docs/source/api/core.rst +0 -218
- prompture-1.0.52.dev1/docs/source/api/drivers.rst +0 -477
- prompture-1.0.52.dev1/docs/source/api/field_definitions.rst +0 -471
- prompture-1.0.52.dev1/docs/source/api/index.rst +0 -426
- prompture-1.0.52.dev1/docs/source/api/runner.rst +0 -330
- prompture-1.0.52.dev1/docs/source/api/tools.rst +0 -216
- prompture-1.0.52.dev1/docs/source/api/validator.rst +0 -423
- prompture-1.0.52.dev1/docs/source/conf.py +0 -134
- prompture-1.0.52.dev1/docs/source/contributing.rst +0 -439
- prompture-1.0.52.dev1/docs/source/examples.rst +0 -590
- prompture-1.0.52.dev1/docs/source/field_definitions_reference.rst +0 -1038
- prompture-1.0.52.dev1/docs/source/index.rst +0 -84
- prompture-1.0.52.dev1/docs/source/installation.rst +0 -204
- prompture-1.0.52.dev1/docs/source/migration.rst +0 -114
- prompture-1.0.52.dev1/docs/source/quickstart.rst +0 -345
- prompture-1.0.52.dev1/docs/source/stability.rst +0 -120
- prompture-1.0.52.dev1/docs/source/test_harness.rst +0 -139
- prompture-1.0.52.dev1/docs/source/toon_input_guide.rst +0 -398
- prompture-1.0.52.dev1/docs/source/why_prompture.rst +0 -145
- prompture-1.0.52.dev1/prompture/infra/rates/groq.json +0 -28
- prompture-1.0.52.dev1/prompture/infra/rates/xai.json +0 -54
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-driver/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-driver/references/driver-template.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-example/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-field/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-persona/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-test/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/add-tool/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/create-pr/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/run-tests/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.claude/skills/update-pricing/SKILL.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.env.copy +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/FUNDING.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/pricing_cost_mismatch.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/provider_driver_bug.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/ISSUE_TEMPLATE/schema_extraction_failure.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/scripts/update_docs_version.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/scripts/update_wrapper_version.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/documentation.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/publish.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.github/workflows/security.yml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/.safety-project.ini +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/CLAUDE.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/CONTRIBUTING.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/LICENSE +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/MANIFEST.in +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/ROADMAP.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/SECURITY.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/dev.ps1 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/dev.sh +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/mypy_errors.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/README.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/README.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/pyproject.toml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_json/test.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/README.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/pyproject.toml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/packages/llm_to_toon/test.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/agent.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/async_agent.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/async_conversation.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/conversation.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/persona.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/simulated_tools.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/skills.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/tools_schema.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/agents/types.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/aio/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/bridges/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/bridges/tukuy_backend.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/cli.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/formatters.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/runner.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/cli/server.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/airllm_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_airllm_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_azure_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_cachibot_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_elevenlabs_stt_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_elevenlabs_tts_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_embedding_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_google_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_google_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_grok_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_grok_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_groq_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_hugging_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_img_gen_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_lmstudio_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_local_http_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_modelscope_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_moonshot_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_ollama_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_ollama_embedding_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_embedding_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_stt_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openai_tts_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_openrouter_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_stability_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_stt_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_tts_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/async_zai_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/audio_registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/azure_config.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/azure_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/cachibot_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/driver_registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/elevenlabs_stt_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/elevenlabs_tts_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/embedding_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/embedding_registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/google_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/google_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/grok_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/grok_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/groq_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/hugging_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/img_gen_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/img_gen_registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/lmstudio_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/local_http_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/modelscope_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/moonshot_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/ollama_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/ollama_embedding_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_embedding_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_stt_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openai_tts_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/openrouter_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/registry.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/stability_img_gen_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/stt_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/tts_base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/vision_helpers.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/drivers/zai_driver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/exceptions.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/async_core.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/core.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/fields.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/reasoning.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/strategy.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/tools.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/tukuy_bridge.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/extraction/validator.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/async_groups.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/consensus.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/debate.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/groups.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/groups/types.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/budget.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/cache.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/callbacks.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/cost_mixin.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/discovery.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/ledger.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/logging.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/model_rates.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/provider_env.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/azure.json +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/rates/openrouter.json +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/session.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/tracker.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/infra/tukuy_backend.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/chunking.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/detect.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/document.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/base.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/csv_parser.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/docx.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/html.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/markdown.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/pdf.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/ingestion/parsers/xlsx.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/integrations/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/integrations/tukuy_bridge.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/audio.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/media/image.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/01-why-prompture.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/02-installation.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/03-quickstart.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/04-toon-input.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/05-test-harness.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/06-stability.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pages/07-migration.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/history.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/serialization.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/persistence/store.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/pipeline.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/resolver.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/pipeline/routing.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/py.typed +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/__init__.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/generator.py +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/README.md.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/config.py.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/env.example.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/main.py.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/models.py.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/dependency_links.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/entry_points.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/requires.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture.egg-info/top_level.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/prompture_cost_tracking.md +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/pyproject.toml +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/requirements.txt +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/setup.cfg +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/basic_extraction.json +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/schema_validation.json +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/specs/strategy_comparison.json +0 -0
- {prompture-1.0.52.dev1 → prompture-1.0.53.dev1}/test.py +0 -0
- {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](
|
|
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.
|
|
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
|
-
- **
|
|
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
|
-
| `
|
|
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
|
-
- **
|
|
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
|
-
| `
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
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
|
|
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
|
-
|
|
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=[
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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 =
|
|
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
|
-
|
|
360
|
-
|
|
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
|
+
)
|