feat(token_count): add Bedrock InvokeModel header-based extraction#240
feat(token_count): add Bedrock InvokeModel header-based extraction#240mkoushni wants to merge 1 commit into
Conversation
praxis-bot
left a comment
There was a problem hiding this comment.
token_count filter review
The filter design and test coverage are solid, but there are two critical issues that prevent the code from compiling and running.
| Severity | Count | Summary |
|---|---|---|
| Critical | 2 | Wrong import path (won't compile); filter not registered (won't load at runtime) |
| Large | 2 | Integration tests use wrong registry; missing doc comment on TokenCountConfig |
| Medium | 2 | Unused import in integration tests; example config in wrong README table section |
Non-inline findings
[Large] All integration tests in token_counting.rs call start_proxy(&config) which uses FilterRegistry::with_builtins(). The token_count filter is not a builtin, so every test will fail at pipeline construction with "unknown filter type: 'token_count'". Tests must use start_proxy_with_registry with a registry that includes token_count, similar to how other AI filter integration tests work.
309f0c8 to
4d7b50e
Compare
|
PR too large: 808 lines added (limit: 750, excludes Cargo files, tests, docs, examples, and benchmarks). Please split into smaller PRs. Add |
0f3e064 to
4c95a0a
Compare
4c95a0a to
3efc75b
Compare
|
Missing Signed-off-by: fb44e8c. All commits require sign-off (via |
3f0c5c6 to
acee3ef
Compare
|
Unsigned commits: acee3ef. Please sign your commits. |
…raxis-proxy#210) PR praxis-proxy#262 merged an equivalent token_count filter to main while this branch was in flight. Rebase onto it and extend it to close the one gap against the praxis-proxy#210 proposal: Bedrock InvokeModel returns token counts as response headers (x-amzn-bedrock-input/output-token-count) rather than in the body, so it needs a distinct extraction path. Introduces a local ProviderKind config enum (OpenAi, Anthropic, Google, Bedrock, BedrockInvokeModel, Azure) that maps to the shared TokenUsageProvider for body-based providers and short-circuits to header-only extraction for BedrockInvokeModel, with BodyAccess::None so no body is ever buffered for that provider. Also fixes a backwards ordering note in the token-usage-headers example: response hooks run in reverse declared order, so a token-counting filter must be declared after token_usage_headers, not before. Signed-off-by: mkoushni <mkoushni@redhat.com>
acee3ef to
a107239
Compare
aslakknutsen
left a comment
There was a problem hiding this comment.
Tests cover absent and partial headers but not non-numeric values (e.g. x-amzn-bedrock-input-token-count: abc). Behavior is likely correct (parse failure -> no-op) but untested; low risk given symmetric parse logic.
| /// other providers, detects the content-type to select the body | ||
| /// extraction strategy used by `on_response_body`. | ||
| async fn on_response(&self, ctx: &mut HttpFilterContext<'_>) -> Result<FilterAction, FilterError> { | ||
| if self.provider == ProviderKind::BedrockInvokeModel { |
There was a problem hiding this comment.
We call extract_bedrock_headers unconditionally before any status check on the response. Every one else is guarded by the is_success. Probably not the end of the world, but the API is relyable with token headers on non 200 responses ?
| assert!(matches!(action, FilterAction::Continue)); | ||
| assert!(ctx.get_metadata("token.input").is_none()); | ||
| } | ||
|
|
There was a problem hiding this comment.
on_response_skips_non_success_status covers OpenAi but there is no analogue for BedrockInvokeModel with error status plus valid token headers. A regression test would lock the intended parity with body providers and guard the bug in bug-bedrock-no-success-check.
Summary
Implements the
provider: bedrock_invoke_modelextraction path required by proposal #210 for thetoken_countfilter.Context
While this PR was in flight, #262 merged an equivalent
token_countfilter tomain(filters/src/token_count/) covering the 5 body-based providers (OpenAI, Anthropic, Google, Bedrock Converse, Azure). This PR is now rebased on top of that merged filter and extends it with the one requirement from proposal #210 that #262 didn't cover: BedrockInvokeModel, which returns token counts as HTTP response headers rather than in the response body.What changed
filters/src/token_count/mod.rsProviderKindconfig enum (openai,anthropic,google,bedrock,bedrock_invoke_model,azure) and the header-only extraction path forbedrock_invoke_modelfilters/src/token_count/tests.rstests/integration/tests/suite/examples/token_count.rstoken_count+token_usage_headersto observe the extracted counts as response headersdocs/filters/token_count.mdcargo xtask generate-filter-docsexamples/configs/token-usage-headers.yamlHow it works
ProviderKindis distinct from the sharedTokenUsageProvider(used for JSON/SSE body parsing) because BedrockInvokeModelhas no body format to parse:response_body_accessreturnsBodyAccess::Noneforbedrock_invoke_model— no body is ever buffered for this provider.on_responseshort-circuits forbedrock_invoke_model: readsx-amzn-bedrock-input-token-count/x-amzn-bedrock-output-token-countdirectly from the upstream response headers and callsset_token_usageimmediately, skipping content-type detection entirely.on_response_bodyconvertsProviderKindtoOption<TokenUsageProvider>viato_library_provider(); it's a no-op whenNone(i.e. forbedrock_invoke_model, whose body is never delivered anyway).BodyMode::Stream, no full-body buffering).Fixed: incorrect filter-ordering guidance
While writing the integration test, I found that
examples/configs/token-usage-headers.yaml's note on filter ordering was backwards. Response hooks in the Praxis pipeline run in reverse declared order (confirmed againstfilter/src/pipeline/http.rsin praxis core:self.filters.iter().enumerate().rev()), so a token-counting filter must be declared aftertoken_usage_headers, not before, foron_responseto populatefilter_metadatabeforetoken_usage_headersreads it. Fixed the comment accordingly.Configuration
Testing
modemetadata,response_body_accessisNoneforbedrock_invoke_model/ReadOnlyfor all other providers,on_response_bodyno-op forbedrock_invoke_model, andbedrock_invoke_modelaccepted byfrom_config.token_count_bedrock_invoke_model_extracts_header_countsstarts a real proxy chainingtoken_count(bedrock_invoke_model) withtoken_usage_headers, has the backend return the Bedrock headers, and asserts the proxy response carriespraxis-token-input: 25,praxis-token-output: 50,praxis-token-total: 75.cargo test --workspace) passes: 2464 tests, 0 failed.cargo clippy --workspace --all-targets -- -D warnings: clean.cargo +nightly fmt --all --check: clean.cargo xtask lint-separators/cargo xtask lint-filter-docs: clean.Out of scope