Skip to content

feat(models): add optional Pricing field to ModelInfo - #92

Draft
manojp99 wants to merge 4 commits into
mainfrom
feature/model-info-pricing
Draft

feat(models): add optional Pricing field to ModelInfo#92
manojp99 wants to merge 4 commits into
mainfrom
feature/model-info-pricing

Conversation

@manojp99

@manojp99 manojp99 commented Jun 30, 2026

Copy link
Copy Markdown

🔄 Updated per triage feedback (2026-06-30)

Two commits added addressing the "Updated Triage — PR Branch Review" from issue microsoft-amplifier/amplifier-support#295:

refactor(models): drop Pricing.as_of and tighten schema (1a94515)

feat(models): propagate Pricing through Rust bridges + guest crate (cb28489)

  • Added native Rust Pricing struct and pricing: Option<Pricing> field on Rust ModelInfo (crates/amplifier-core/src/models.rs).
  • Added pricing_json string field on the proto ModelInfo at tag 7 (mirrors the existing defaults_json pattern at tag 6). Regenerated src/generated/amplifier.module.rs via build.rs.
  • Extended src/generated/conversions.rs to round-trip pricing between native and proto structs in both directions. Parse failures return None (no synthetic default Pricing).
  • Added parse_pricing_json helper in src/bridges/grpc_provider.rs and threaded it through list_models() — addresses the HIGH item in the triage (gRPC bridge silently dropping pricing).
  • Added duplicated Pricing + field in crates/amplifier-guest/src/types.rs — closes the WASM guest-side gap the triage did not originally identify, but which is required for WASM providers to populate pricing at all.
  • src/bridges/wasm_provider.rs requires no changes — it deserializes directly into native ModelInfo and picks up the new field via #[serde(default)].
  • Rust unit tests for parse_pricing_json, updated equivalence_tests.rs, and new test_model_info_pricing_field in bindings/python/tests/test_schema_sync.py.

Verification results

  • Python: 6/6 pricing/model_info tests pass; ruff/pyright clean on touched files (pre-existing issues elsewhere are documented, not fixed).
  • Rust: cargo build --workspace regenerates proto cleanly; cargo test -p amplifier-core --lib 454/454 pass; cargo test -p amplifier-guest --lib 94/94 pass; clippy clean on amplifier-core and amplifier-core-py. Full-workspace cargo test hangs on unrelated wasm_* and sha256_* tests — these do not touch pricing code paths.
  • Cross-language: pytest bindings/python/tests/ 370/370 pass including the new pricing test.

Design decisions locked

  • Proto shape: string blob (pricing_json) mirroring defaults_json, not a nested message Pricing. Discussed in the triage response; the string-blob pattern is consistent with existing precedent and can be migrated to a nested type later without a breaking change.

What

Adds an optional Pricing field to ModelInfo so providers can surface
per-model pricing (input/output/cache rates per million tokens, currency,
and an optional as_of date) through /v1/models.

Why

Today HTTP-bridge applications (e.g. amplifier-app-opencode) hand-maintain
their own pricing tables because pricing isn't part of model info on the
wire. This forces drift whenever provider pricing changes and leaves
end-users seeing stale cost estimates. Filed as
microsoft-amplifier/amplifier-support#295.

The fix is to expose the pricing data the provider modules already
maintain internally
(each has a _RATES dict used for per-turn cost
accounting) through the public ModelInfo contract. No new source of
truth — we're promoting an existing one.

What's in this PR

  • New Pricing pydantic model in python/amplifier_core/models.py
    • input_per_million, output_per_million (required floats)
    • cache_read_per_million, cache_write_per_million (optional)
    • currency (defaults to "USD")
    • as_of (optional date for staleness tracking)
  • Optional pricing: Pricing | None = None field on ModelInfo (default None — backwards compatible)
  • Pricing exported via python/amplifier_core/__init__.py
  • Smoke tests in tests/test_model_info_pricing.py covering round-trip serialization and the pricing=None case

Compatibility

Backwards-compatible. Existing providers that don't populate pricing
continue to work; the field is None by default. Existing consumers that
don't read pricing are unaffected.

Companion PR

Partner change in amplifier-module-provider-anthropic populates the new
field from the existing _RATES table: microsoft/amplifier-module-provider-anthropic#63

Validation

Validated end-to-end in a Digital Twin Universe environment with both
patches active:

  • amplifier-core patch installed, Pricing importable, field present on ModelInfo
  • AnthropicProvider.list_models() returns models with populated pricing
  • ModelInfo.model_dump(mode="json") emits the expected wire shape
  • Opus values (5.0 / 25.0 USD per million in/out) match the expected rate sheet

Scope

This is the minimum viable slice — amplifier-core schema change here,
companion amplifier-module-provider-anthropic change in the partner PR.
Other providers (openai, azure-openai, gemini, vllm, ollama) are
follow-up work and will land in separate PRs.

Add a Pricing model (input/output per-million rates, optional
cache-read/cache-write rates, currency, as_of) and an optional
`pricing` field on ModelInfo. Providers can now surface their existing
internal rate tables (e.g., the Anthropic provider's _RATES dict)
through /v1/models so HTTP-bridge applications such as
amplifier-app-opencode can display cost estimates without maintaining
their own hardcoded pricing tables.

Backwards-compatible: pricing defaults to None, so providers that
don't populate it (local providers like ollama, self-hosted backends
like vllm) are unaffected, and existing ModelInfo construction call
sites continue to work unchanged.

Fixes: microsoft-amplifier/amplifier-support#295

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Manoj Prabhakar Paidiparthy and others added 3 commits June 30, 2026 17:51
Applies triage feedback to the Pricing model added in 8977778:

- Removed the `as_of` date field entirely. All Pricing fields are now
  float/str only -- no dates. The unused `date` import is removed too.
- Added ISO 4217 currency validation via a field_validator: currency
  must match `^[A-Z]{3}$`.
- Added a docstring note clarifying that Pricing rate fields use float
  (not Decimal) because they are display-only estimates for /v1/models,
  distinct from Usage.cost_usd which is Decimal and rejects float.

Tests: removed as_of assertions, added a JSON round-trip test
(model_dump / model_dump(mode="json") / model_dump_json all succeed
now that there are no date fields) and a currency validation test
covering valid and invalid ISO 4217 codes.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Threads the Pricing model added in 8977778 through the full Rust
ecosystem: native crate, WASM guest crate, proto wire format, gRPC
bridge, and equivalence tests.

- crates/amplifier-core/src/models.rs: adds a native `Pricing` struct
  (input_per_million, output_per_million, optional cache rates,
  currency defaulting to "USD") and `ModelInfo.pricing: Option<Pricing>`.
- crates/amplifier-guest/src/types.rs: duplicates the `Pricing` struct
  (this crate has no amplifier-core dependency) and adds the same
  `pricing` field to its `ModelInfo`. Updates existing tests to cover
  the new field.
- proto/amplifier_module.proto: adds `string pricing_json = 7` to
  `ModelInfo`, mirroring the existing `defaults_json` string-blob
  pattern rather than a nested message type. Regenerates
  src/generated/amplifier.module.rs via `cargo build` (protoc
  installed locally).
- crates/amplifier-core/src/generated/conversions.rs: extends the
  native <-> proto ModelInfo conversions for pricing/pricing_json.
  Proto -> native intentionally returns None (not a synthetic
  default) on parse failure, since there is no meaningful default
  price. Adds roundtrip and failure-mode tests.
- crates/amplifier-core/src/bridges/grpc_provider.rs: adds
  `parse_pricing_json`, threaded through `list_models()`, with unit
  tests for the valid/empty/invalid cases.
- crates/amplifier-core/src/bridges/wasm_provider.rs: no changes
  needed -- it deserializes directly into the native `ModelInfo` via
  `serde_json::from_slice`, so `pricing` is picked up automatically
  via `#[serde(default)]`.
- crates/amplifier-core/src/generated/equivalence_tests.rs: extends
  the ModelInfo proto equivalence test to cover `pricing_json`.
- tests/fixtures/wasm/src/echo-provider/src/lib.rs and
  crates/amplifier-guest/src/lib.rs: update existing ModelInfo
  literals for the new required field.
- bindings/python/tests/test_schema_sync.py: adds a Python <-> JSON
  round-trip test for ModelInfo.pricing.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
CI's rustfmt (stable) wraps the pricing_json line differently than
the local rustfmt run that produced the prior commit. Applying the
CI-preferred formatting to unblock the fmt check on PR #92.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant