Skip to content

Add web search request pricing support#288

Open
Kludex wants to merge 5 commits intomainfrom
add-web-search-request-pricing
Open

Add web search request pricing support#288
Kludex wants to merge 5 commits intomainfrom
add-web-search-request-pricing

Conversation

@Kludex
Copy link
Copy Markdown
Member

@Kludex Kludex commented Feb 19, 2026

Summary

  • Add web_search_kcount price field and web_search_requests usage field across Python, JS, and YAML data
  • Anthropic responses with server_tool_use.web_search_requests are now extracted automatically via the default extractor
  • OpenAI prices included for manual count pass-through until extraction from Responses API output is added
  • Pricing: Anthropic $10/1k (all Claude 3+ models), OpenAI gpt-4o $25/1k, gpt-4.1/gpt-5+ $30/1k

Test plan

  • make build-prices rebuilds data files
  • make package-data regenerates package data
  • make format && make lint && make typecheck all pass
  • make test - 350 passed, 67 xfailed, dataset stable
  • JS tests - 613 passed
  • Verify OpenAI web search pricing against latest pricing page (used plan estimates, may need adjustment)

Add `web_search_kcount` price field and `web_search_requests` usage
field across the full stack (Python, JS, YAML data). Anthropic
responses with `server_tool_use.web_search_requests` are now extracted
automatically. OpenAI prices are included for manual count pass-through
until extraction from Responses API output is added.

Pricing: Anthropic $10/1k, OpenAI gpt-4o $25/1k, gpt-4.1/gpt-5+ $30/1k.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +666 to +669
if self.web_search_kcount is not None:
web_search_requests = getattr(usage, 'web_search_requests', None)
if web_search_requests:
total_price += self.web_search_kcount * web_search_requests / 1000
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 AbstractUsage protocol intentionally omits web_search_requests

The AbstractUsage protocol at packages/python/genai_prices/types.py:191-226 does not include a web_search_requests property, even though the Usage dataclass and calc_price method now support it. The calc_price method works around this at line 667 with getattr(usage, 'web_search_requests', None). This is a deliberate backward-compatibility choice: existing AbstractUsage implementations (e.g. from third-party integrations) won't break. However, it means that any custom usage class implementing AbstractUsage but missing web_search_requests will silently ignore web search costs even for models with web_search_kcount set. This is likely fine for now (the PR description notes OpenAI extraction isn't wired up yet), but should be revisited when web search extraction is added for more providers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Add `file_search_requests` usage field and `file_search_kcount` pricing
  field across Python, JS, and YAML type definitions
- Add `file_search_kcount: 2.5` to 11 OpenAI models supporting Responses
  API file search
- Map `web_search` pricing in OpenRouter source via new `kcount()` helper
- Add calc_price logic for file_search_kcount in Python and JS engines
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Python _extract_path raises unconditionally at line 483 for non-mapping non-None data — JS handles gracefully

The pre-existing _extract_path function at packages/python/genai_prices/types.py:483-484 raises ValueError unconditionally when the data after navigating through intermediate steps is not a Mapping and not None, regardless of whether required=False. The JS equivalent at packages/js/src/extractUsage.ts:104-109 correctly returns null for non-required paths in the same scenario. This PR introduces the first multi-step non-required path (['server_tool_use', 'web_search_requests']), making this code path newly reachable. In practice, the Anthropic API always returns server_tool_use as either None or a dict, so this divergence won't trigger. But if an unexpected type were received (e.g., an integer), Python would crash while JS would gracefully return null.

(Refers to lines 483-484)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

input_mtok: 1.25
cache_read_mtok: 0.125
output_mtok: 10
web_search_kcount: 30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this number come from? why is it 30 for some and 25 for others?

is this 'web search preview'? how does that differ from 'web search'?

Image

…_use` system

Replace hard-coded `web_search_kcount`/`file_search_kcount` fields and
`web_search_requests`/`file_search_requests` usage fields with a generic
dict-based system using `ToolUseUnit` Literal for type enforcement.

Adding a new tool type now requires adding one string to `ToolUseUnit`
instead of new fields across 6+ files.
Expand the set of supported non-token pricing units so the system is
ready for image generation, TTS, video processing, and other
count-based pricing as providers adopt them.
Providers can define arbitrary tool use unit names in YAML without
being constrained by a type-level enum. Routing in extraction logic
now checks if dest is a known UsageField (token field); anything else
goes to the tool_use dict.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Pre-existing date mismatch between JS dataset test and Python dataset generation

The JS dataset test at packages/js/src/__tests__/dataset.test.ts:89 uses new Date(2025, 11, 6, 12, 0, 0) which is December 6, 2025 in local time (JS months are 0-indexed). The Python dataset generation at tests/dataset/extract_usages.py:115 uses datetime.datetime(2025, 11, 6, 12, 0, 0, tzinfo=datetime.timezone.utc) which is November 6, 2025 in UTC.

This is a pre-existing discrepancy (not introduced by this PR). It doesn't currently cause test failures because all date-sensitive price constraints (like the o3 model's start_date of 2025-06-10) resolve to the same price tier for both dates. However, if a future price change has a start_date between November and December 2025, the JS and Python tests would compute different prices for the same dataset row.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +298 to +300
tool_use_kcount:
web_search: 30
file_search: 2.5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 OpenAI web search pricing not yet auto-extracted — manual pass-through only

The PR description notes that OpenAI prices are included for 'manual count pass-through until extraction from Responses API output is added.' OpenAI models have tool_use_kcount prices defined (e.g., web_search: 30 for gpt-4.1 at prices/providers/openai.yml:298), but the OpenAI extractors (chat and responses flavors) don't have any mapping for web search extraction. This means OpenAI web search costs will only be calculated if the caller manually provides tool_use: {web_search: N} in the Usage object. The Anthropic extractor, by contrast, auto-extracts from server_tool_use.web_search_requests. This asymmetry is documented in the PR but worth noting for follow-up.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants