Skip to content

feat(tinyfish): surface response headers + top-level response extras - #32301

Closed
ChenluJi wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
ChenluJi:feat/tinyfish-search-top-level-extras
Closed

feat(tinyfish): surface response headers + top-level response extras#32301
ChenluJi wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
ChenluJi:feat/tinyfish-search-top-level-extras

Conversation

@ChenluJi

@ChenluJi ChenluJi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #31411 (superseded and merged as #31997). Two related fixes so LiteLLM callers see what TinyFish actually returns, plus small correctness cleanups.

1. Response headers surfaced on _hidden_params

TinyFish sets useful response headers — x-request-id on every response, retry-after and x-ratelimit-limit on 429s. Previously these were only accessible via BaseLLMException.headers on error paths; on the success path they were dropped entirely.

Fix: stash headers on both LiteLLM-conventional channels, matching the pattern used by Gemini / Volcengine / Manus / ChatGPT / OpenAI-responses providers.

  • _hidden_params["headers"] — raw dict from httpx, all keys lowercased (httpx normalizes).
  • _hidden_params["additional_headers"] — passed through process_response_headers, which prefixes any x-litellm-* header a provider might set with llm_provider- so downstream LiteLLM code that trusts bare x-litellm-* markers can't be spoofed. Provider values survive under the prefixed key for observability.

Verified live: resp._hidden_params["headers"]["x-request-id"] now populated. Covered by three new tests including a spoofing regression (test_response_headers_strips_x_litellm_spoof).

2. Top-level response extras (query, total_results, page, future fields)

transform_search_response was building a fresh SearchResponse from just results, silently dropping every top-level field TinyFish's response carries beyond results / object. Reproduced twice against production: resp.query, resp.total_results, resp.page all absent, model_extra empty.

Fix: mutate parsed.results to its truncated slice and return the same SearchResponse instance rather than reconstructing. Every field pydantic populated during model_validate — declared attributes AND __pydantic_extra__ bucket — survives to the caller. Access via resp.query etc., or via resp.model_extra for enumeration.

Future-proof: if LiteLLM upstream ever promotes a field from extras to declared (e.g. SearchResponse gets a first-class total_results attribute), this code needs no change — pydantic will store the value wherever it stores it, and truncate-in-place preserves both storage buckets.

Verified live against production: query, total_results, page now surface. Covered by test_top_level_extras_flow_through (real fields) and test_top_level_future_extras_flow_through (proves the general "any envelope extra rides through" contract, which covers parameter_warnings and any future addition).

3. Code cleanup

Small correctness fixes and hygiene, no behavior change for the common path:

  • List-valued custom params now JSON-encoded on the wire (matching the existing dict-handling), so callers can pass a natural Python list for JSON-array wire params.
  • URL-encodable-params adapter accepts float in addition to str | int | bool. Server-side rejection of a wrong-typed float now surfaces cleanly with TinyFish Search: attribution + docs link, rather than a raw pydantic union failure.
  • Assorted comment / docstring / test-fixture hygiene (no logic touched).

Test plan

  • 60 unit tests pass locally (tests/test_litellm/llms/tinyfish/)
  • Integration tests pass locally (tests/search_tests/test_tinyfish_search.py)
  • Live regression against production TinyFish covering:
    • Response headers (x-request-id etc.) surface via both _hidden_params["headers"] and _hidden_params["additional_headers"]
    • Top-level extras (query, total_results, page) present on every response
    • Per-result extras still flow through
    • max_results boundaries (0, 1, 999) — client-side clamping intact
    • Error paths still produce attributed BaseLLMException with TinyFish Search: prefix + docs link
    • Domain-filter query rewrite still works
    • countrylocation mapping still works
    • Float param on server-typed field produces the expected attributed error
  • Ruff format at 120 col, C901 + I001 within budget

No behavior change for callers using per-result extras (already correct). Error paths unchanged. Request-side behavior unchanged for str/int/bool/dict params.

@ChenluJi

ChenluJi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two data-loss gaps in the TinyFish Search integration: top-level response envelope fields (query, total_results, page) were silently dropped when rebuilding SearchResponse, and provider response headers were never exposed on the success path. The fix uses Pydantic's model_extra spread and the standard _hidden_params pattern already used by Gemini, Volcengine, and other providers.

  • Envelope extras pass-throughSearchResponse is now constructed with **(parsed.model_extra or {}), so any top-level field TinyFish returns (current or future) flows to the caller with no further code change required.
  • Response headers on _hidden_params — raw headers land at _hidden_params["headers"] and process_response_headers-sanitized headers (strips x-litellm-* spoofing) land at _hidden_params["additional_headers"], matching the established convention.
  • Cleanup — internal tracker references (ML-2084, ML-2085, ux-labs, tf-fetch) removed from comments and test fixtures; tests reformatted to 120-column line length.

Confidence Score: 5/5

Safe to merge — both changes follow well-established LiteLLM provider patterns and the new tests cover the exact behaviors being fixed.

The model_extra spread is guarded by or {} so a missing extras dict can't panic. The headers path uses the same process_response_headers flow other providers already rely on. No request-side logic changes, no schema changes, no backwards-incompatible behaviour. Existing tests were reformatted rather than gutted, and the new unit tests directly assert the two fixed behaviors.

No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/tinyfish/search/transformation.py Adds top-level response extras pass-through via model_extra spreading and stashes response headers on _hidden_params, matching the existing pattern used by other providers. Logic is sound and well-commented.
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py Adds 5 new unit tests for header stashing and extras pass-through; reformats existing tests (line length); simplifies a few fixtures without weakening assertions. All tests use mocks only.
tests/search_tests/test_tinyfish_search.py Single comment change only — removes internal tracker reference from a test docstring. No logic changes.

Reviews (5): Last reviewed commit: "feat(tinyfish): surface top-level respon..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ChenluJi

ChenluJi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ChenluJi ChenluJi changed the title feat(tinyfish): surface top-level response extras (query, total_results, page) feat(tinyfish): surface top-level response extras + response headers Jul 7, 2026
@ChenluJi
ChenluJi marked this pull request as ready for review July 7, 2026 04:35
@ChenluJi
ChenluJi force-pushed the feat/tinyfish-search-top-level-extras branch from 533d2a1 to 6c5be5c Compare July 7, 2026 05:31
@ChenluJi

ChenluJi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ChenluJi
ChenluJi force-pushed the feat/tinyfish-search-top-level-extras branch from 6c5be5c to f7658d9 Compare July 7, 2026 05:42
@ChenluJi

ChenluJi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ChenluJi
ChenluJi force-pushed the feat/tinyfish-search-top-level-extras branch from f7658d9 to 3d62ae9 Compare July 7, 2026 21:44
@ChenluJi
ChenluJi marked this pull request as draft July 7, 2026 21:45
@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing ChenluJi:feat/tinyfish-search-top-level-extras (bf65ef9) with litellm_internal_staging (d6cbf6e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (bcd5275) during the generation of this report, so d6cbf6e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Follow-up to BerriAI#31411 (superseded and merged as BerriAI#31997). Two related fixes
so LiteLLM callers see what TinyFish actually returns.

## Top-level response extras (query, total_results, page, future fields)

transform_search_response was constructing a fresh SearchResponse from
just `results`, silently dropping every top-level extra that
SearchResponse.model_validate(raw_json) had captured via extra='allow'.
Reproduced twice against production — resp.query, resp.total_results,
resp.page all absent, model_extra empty.

Fix: spread parsed.model_extra at construction time.

    return SearchResponse(
        results=list(parsed.results[:max_results]),
        **(parsed.model_extra or {}),
    )

Future-proof: any new top-level field TinyFish adds rides through with
no LiteLLM code change (SearchResponse's extra='allow' captures every
non-declared field into parsed.model_extra automatically).

## Response headers surfaced on _hidden_params

TinyFish sets useful response headers (X-Request-ID on every response;
Retry-After and X-RateLimit-Limit on 429s). These were only accessible
via BaseLLMException.headers on error paths; on the success path they
were dropped entirely.

Fix: stash headers on both LiteLLM-conventional channels, matching the
convention used by Gemini/Volcengine/Manus/ChatGPT/OpenAI responses
providers.

    response._hidden_params['headers'] = dict(raw_response.headers)
    response._hidden_params['additional_headers'] = (
        process_response_headers(dict(raw_response.headers))
    )

- 'headers' — raw dict for debugging.
- 'additional_headers' — passed through process_response_headers so
  downstream LiteLLM consumers get sanitized keys (strips any
  x-litellm-* a misbehaving provider might set).

Future-proof: no allowlist, no filtering. Any future response header
flows through automatically.

## Tests

Adds 5 new mock-only unit tests:
- test_top_level_extras_flow_through
- test_top_level_future_extras_flow_through
- test_response_headers_stashed_on_hidden_params
- test_response_headers_future_headers_flow_through
- test_response_headers_strips_x_litellm_spoof

66 unit + integration tests pass locally. Live-tested against production
TinyFish (23/23 regression checks). No request-side changes. No behavior
change for per-result extras (already correct). Error paths unchanged.
@ChenluJi
ChenluJi force-pushed the feat/tinyfish-search-top-level-extras branch from 3d62ae9 to bf65ef9 Compare July 8, 2026 06:52
@ChenluJi ChenluJi changed the title feat(tinyfish): surface top-level response extras + response headers feat(tinyfish): surface response headers + top-level response extras Jul 8, 2026
@ChenluJi

ChenluJi commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #32448 — same code, cleaner history and shorter diff.

@ChenluJi ChenluJi closed this Jul 8, 2026
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