Skip to content

feat(search): add TinyFish as search provider#30634

Open
simantak-dabhade wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
simantak-dabhade:litellm_tinyfish_search_provider
Open

feat(search): add TinyFish as search provider#30634
simantak-dabhade wants to merge 3 commits into
BerriAI:litellm_internal_stagingfrom
simantak-dabhade:litellm_tinyfish_search_provider

Conversation

@simantak-dabhade

@simantak-dabhade simantak-dabhade commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Adds TinyFish as the 16th search provider in LiteLLM, following the same GET-based pattern as Brave and Serper. Implements TinyfishSearchConfig(BaseSearchConfig) with full request/response transformation, mapping LiteLLM's unified params (country -> location, search_domain_filter -> site: operators, max_results -> client-side truncation clamped 1-20). Auth via X-API-Key header from api_key param or TINYFISH_API_KEY env var

This is a backend-only search provider addition with no UI changes

Files changed (9)

File Change
litellm/llms/tinyfish/search/__init__.py New module export
litellm/llms/tinyfish/search/transformation.py Core TinyfishSearchConfig; fully typed with Pydantic validation at httpx boundaries
litellm/types/utils.py Add TINYFISH to SearchProviders enum
litellm/utils.py Register config in get_provider_search_config()
model_prices_and_context_window.json Add tinyfish/search pricing entry ($5/1k searches)
provider_endpoints_support.json Add tinyfish endpoint metadata
tests/code_coverage_tests/enforce_llms_folder_style.py Add tinyfish to SEARCH_PROVIDERS
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py 31 unit tests covering every method and branch
tests/search_tests/test_tinyfish_search.py 7 integration-style tests via litellm.asearch()

Relevant issues

Follow-up to #30158 (approved by Sameerlite, closed due to merge conflicts during branch rotation), #30492 (closed for Greptile comments + CI), #30592 (closed, staging branch rotated)

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Proof of testing

To test with a live proxy:

  1. Add TINYFISH_API_KEY to your .env
  2. Start the proxy: python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log
  3. Run a search:
curl -s http://localhost:4000/v1/search \
  -H "Authorization: Bearer sk-1234" \
  -H "Content-Type: application/json" \
  -d '{"query": "web automation tools", "custom_llm_provider": "tinyfish"}' | python -m json.tool

38 unit tests (31 in tests/test_litellm/, 7 in tests/search_tests/) cover every method and branch. All CI checks pass including any-discipline (0 budget, 3 annotated httpx/base-class boundaries) and codecov (100% patch coverage)

Test plan

  • All 38 unit tests pass
  • any-discipline gate passes (0 budget file, genuine boundaries annotated)
  • codecov patch coverage at 100%
  • Greptile confidence score 4/5

Type

New Feature

Adds TinyFish web search (GET https://api.search.tinyfish.ai) as the
16th search provider in LiteLLM. Follows the BaseSearchConfig pattern
used by other GET-based providers like Brave.

Includes unit tests in tests/test_litellm/ for full patch coverage.
…uff UP006/UP045

Replace typing.Dict/List/Optional/Union with modern syntax (dict, list,
X | None) and use concrete type parameters (dict[str, str] for headers,
dict[str, object] for params) to eliminate LIT009 Any-discipline
violations. Move _append_domain_filters to module level to avoid leaking
Any through self.
@simantak-dabhade

Copy link
Copy Markdown
Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds TinyFish as the 16th search provider in LiteLLM, following the established GET-based pattern used by Brave and Serper. The implementation includes full request/response transformation, API key resolution, countrylocation mapping, search_domain_filtersite: operator injection, and max_results clamping with 38 unit and integration tests.

  • transformation.py: Core config inherits BaseSearchConfig and uses a _tinyfish_params pass-through dict to carry query parameters from transform_search_request to get_complete_url for URL encoding — the same design used by BraveSearchConfig.
  • model_prices_and_context_window.json / provider_endpoints_support.json: Correct pricing ($0.005/query = $5/1k) and endpoint metadata added.
  • Tests: Both the unit test suite (tests/test_litellm/llms/tinyfish/) and the integration suite (tests/search_tests/) properly mock AsyncHTTPHandler.get and make no real network calls.

Confidence Score: 4/5

The change is well-scoped and isolated, but get_complete_url has an unhandled ValidationError that can surface as an opaque internal error when any non-primitive optional param reaches the URL-encoding step.

The _UrlEncodableParams.validate_python() call in get_complete_url is not wrapped in a try/except. Any value in optional_params that is not str | int | bool — including None for an unset parameter — will cause pydantic to raise ValidationError with no handler, crashing the entire search request with an unhelpful traceback rather than a clean error.

litellm/llms/tinyfish/search/transformation.py — specifically the get_complete_url method around the _UrlEncodableParams.validate_python() call.

Important Files Changed

Filename Overview
litellm/llms/tinyfish/search/init.py New module init — re-exports TinyfishSearchConfig cleanly.
litellm/llms/tinyfish/search/transformation.py Core provider config — GET-based search following the Brave pattern. An unhandled ValidationError in get_complete_url can crash the request path when any passthrough optional param holds a non-primitive value.
litellm/types/utils.py Adds TINYFISH to SearchProviders enum — correct and isolated.
litellm/utils.py Registers TinyfishSearchConfig in get_provider_search_config — follows the established pattern.
model_prices_and_context_window.json Adds tinyfish/search pricing entry at $5/1k searches ($0.005/query).
tests/search_tests/test_tinyfish_search.py Integration-style tests that correctly mock AsyncHTTPHandler.get; all 7 tests use mocks and make no real network calls.
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py 31 unit tests covering every method and branch.

Reviews (3): Last reviewed commit: "fix(search/tinyfish): eliminate Any-type..." | Re-trigger Greptile

Comment thread litellm/llms/tinyfish/search/transformation.py Outdated
@pytest.mark.asyncio
async def test_basic_search(self):
os.environ["TINYFISH_API_KEY"] = "sk-tinyfish-test"

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.

P2 os.environ mutation without cleanup

Each async def test_* method assigns os.environ["TINYFISH_API_KEY"] = "sk-tinyfish-test" directly and never removes it. When pytest runs these methods in a single process, the key leaks into every test that runs after them — including tests for other providers that might inadvertently pick up a stale TINYFISH_API_KEY. Use monkeypatch.setenv (for class-based tests, declare monkeypatch as a fixture parameter) or at minimum add a teardown_method that calls os.environ.pop("TINYFISH_API_KEY", None). The same pattern applies to the matching unit test file at tests/test_litellm/llms/tinyfish/test_tinyfish_search.py.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds TinyFish as the 16th search provider, following the same GET-based wrapper-dict pattern already established by Brave. All integration points (enum, config map, pricing, endpoint support, folder-style enforcement) are wired up correctly.

  • transformation.py implements TinyfishSearchConfig with validate_environment, get_complete_url, transform_search_request, and transform_search_response; the _tinyfish_params wrapper-dict mirrors how _brave_params works in the Brave provider.
  • 38 tests across two suites cover every method and branch; all are mock-based with no live network calls.

Confidence Score: 4/5

Self-contained new provider addition; all changes to shared files are purely additive and do not touch existing code paths.

The implementation is consistent with the Brave reference provider and the existing search infrastructure. The one noteworthy point is the raw_max: object = {} sentinel in transform_search_response — the logic is correct, but the empty-dict initializer is unintuitive and deviates from the cleaner pattern Brave uses.

litellm/llms/tinyfish/search/transformation.py — specifically the max_results parsing block in transform_search_response.

Important Files Changed

Filename Overview
litellm/llms/tinyfish/search/transformation.py Core provider implementation: GET-based search, wrapper-dict URL pattern matching Brave. One confusing raw_max: object = {} sentinel in transform_search_response; logic is correct but misleading.
litellm/llms/tinyfish/search/init.py Minimal module re-export for TinyfishSearchConfig; no issues.
litellm/types/utils.py Adds TINYFISH to SearchProviders enum; straightforward addition consistent with existing entries.
litellm/utils.py Registers TinyfishSearchConfig in ProviderConfigManager's search provider map; minimal change matching existing pattern.
model_prices_and_context_window.json Adds tinyfish/search pricing entry at $0.005/query ($5/1k); format matches existing search entries.
provider_endpoints_support.json Adds tinyfish endpoint support entry with search: true; consistent with other provider entries.
tests/code_coverage_tests/enforce_llms_folder_style.py Adds tinyfish to SEARCH_PROVIDERS list for folder-style enforcement; no issues.
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py 31 unit tests covering all methods and branches using patched get_secret_str; all mock-based with no real network calls.
tests/search_tests/test_tinyfish_search.py 7 integration-style tests via litellm.asearch() with AsyncHTTPHandler patched; all mock-based, no live network calls.

Reviews (2): Last reviewed commit: "fix(search/tinyfish): use concrete types..." | Re-trigger Greptile

Comment thread litellm/llms/tinyfish/search/transformation.py Outdated
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Use Pydantic BaseModel and TypeAdapter at httpx/base-class boundaries
to validate untyped inputs (json(), params.get(), bare set). Three
genuine external boundaries annotated with any-ok.
@simantak-dabhade

Copy link
Copy Markdown
Author

@greptileai review

Comment on lines +87 to +91
if isinstance(data, dict) and _TINYFISH_PARAMS_KEY in data:
validated_params = _UrlEncodableParams.validate_python(
data[_TINYFISH_PARAMS_KEY]
)
return f"{resolved_base}?{urlencode(validated_params, doseq=True)}"

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.

P1 Unhandled ValidationError crashes the request path

_UrlEncodableParams.validate_python() expects dict[str, str | int | bool], but result_data is typed dict[str, object] and can contain any value that slipped through the passthrough loop (e.g. None, a list, or any non-primitive). If pydantic can't coerce a value to str | int | bool, it raises ValidationError which is not caught here — the exception propagates out of get_complete_url and surfaces as an opaque internal error instead of a clean LiteLLM exception. None values are a particularly easy trigger: if LiteLLM forwards an optional param with value None, validation will fail immediately. The fix is to catch ValidationError and either filter out non-coercible entries or raise a user-friendly ValueError.

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