feat(models): add optional Pricing field to ModelInfo - #92
Draft
manojp99 wants to merge 4 commits into
Draft
Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔄 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)as_of: date | Nonefield fromPricing— no consumer needed it, and dropping it eliminated thejson.dumps(model_dump())serialization hazard the triage flagged (Medium docs: update TOOL_CONTRACT.md with current ToolResult schema #1) and the "as_of always None" gap (Medium fix: improve MountPlanValidator test coverage and remove dead code #2) as byproducts.^[A-Z]{3}$) — addresses triage Medium feat: add debug/raw LLM events and document orchestrator:complete requirement #6.Pricingusesfloatbecause it's display-only, and per-turn accounting usesUsage.cost_usd(Decimal) — addresses triage Medium Add MODULES.md community module catalog #4.Pricing.model_dump()+json.dumpsand currency validation.feat(models): propagate Pricing through Rust bridges + guest crate(cb28489)Pricingstruct andpricing: Option<Pricing>field on RustModelInfo(crates/amplifier-core/src/models.rs).pricing_jsonstring field on the protoModelInfoat tag 7 (mirrors the existingdefaults_jsonpattern at tag 6). Regeneratedsrc/generated/amplifier.module.rsviabuild.rs.src/generated/conversions.rsto round-trip pricing between native and proto structs in both directions. Parse failures returnNone(no synthetic defaultPricing).parse_pricing_jsonhelper insrc/bridges/grpc_provider.rsand threaded it throughlist_models()— addresses the HIGH item in the triage (gRPC bridge silently dropping pricing).Pricing+ field incrates/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.rsrequires no changes — it deserializes directly into nativeModelInfoand picks up the new field via#[serde(default)].parse_pricing_json, updatedequivalence_tests.rs, and newtest_model_info_pricing_fieldinbindings/python/tests/test_schema_sync.py.Verification results
cargo build --workspaceregenerates proto cleanly;cargo test -p amplifier-core --lib454/454 pass;cargo test -p amplifier-guest --lib94/94 pass; clippy clean onamplifier-coreandamplifier-core-py. Full-workspacecargo testhangs on unrelatedwasm_*andsha256_*tests — these do not touch pricing code paths.pytest bindings/python/tests/370/370 pass including the new pricing test.Design decisions locked
pricing_json) mirroringdefaults_json, not a nestedmessage 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
Pricingfield toModelInfoso providers can surfaceper-model pricing (input/output/cache rates per million tokens, currency,
and an optional
as_ofdate) through/v1/models.Why
Today HTTP-bridge applications (e.g.
amplifier-app-opencode) hand-maintaintheir 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
_RATESdict used for per-turn costaccounting) through the public
ModelInfocontract. No new source oftruth — we're promoting an existing one.
What's in this PR
Pricingpydantic model inpython/amplifier_core/models.pyinput_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)pricing: Pricing | None = Nonefield onModelInfo(default None — backwards compatible)Pricingexported viapython/amplifier_core/__init__.pytests/test_model_info_pricing.pycovering round-trip serialization and thepricing=NonecaseCompatibility
Backwards-compatible. Existing providers that don't populate
pricingcontinue to work; the field is
Noneby default. Existing consumers thatdon't read
pricingare unaffected.Companion PR
Partner change in
amplifier-module-provider-anthropicpopulates the newfield from the existing
_RATEStable: microsoft/amplifier-module-provider-anthropic#63Validation
Validated end-to-end in a Digital Twin Universe environment with both
patches active:
amplifier-corepatch installed,Pricingimportable, field present onModelInfoAnthropicProvider.list_models()returns models with populated pricingModelInfo.model_dump(mode="json")emits the expected wire shapeScope
This is the minimum viable slice —
amplifier-coreschema change here,companion
amplifier-module-provider-anthropicchange in the partner PR.Other providers (
openai,azure-openai,gemini,vllm,ollama) arefollow-up work and will land in separate PRs.