Skip to content

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

Closed
simantak-dabhade wants to merge 1 commit into
BerriAI:litellm_oss_staging160626from
simantak-dabhade:feat/tinyfish-search-v5
Closed

feat(search): add TinyFish as search provider#30592
simantak-dabhade wants to merge 1 commit into
BerriAI:litellm_oss_staging160626from
simantak-dabhade:feat/tinyfish-search-v5

Conversation

@simantak-dabhade

Copy link
Copy Markdown

Relevant issues

Follow-up to #30492, #30158, #28385 (all closed). Addresses Greptile review feedback from the prior PR

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

Screenshots / Proof of Fix

Backend-only change. Unit tests exercise every method of TinyfishSearchConfig directly (31 tests in tests/test_litellm/llms/tinyfish/, 7 integration-style tests in tests/search_tests/)

$ uv run pytest tests/test_litellm/llms/tinyfish/ tests/search_tests/test_tinyfish_search.py -v
38 passed in 0.22s

Type

New Feature

Changes

Adds TinyFish (https://docs.tinyfish.ai/search-api) as the 16th search provider. TinyFish is a GET-based search API; the implementation follows the same wrapper-dict pattern used by Brave and other GET-based providers

Files changed:

  • litellm/llms/tinyfish/search/transformation.py -- core config (TinyfishSearchConfig) with request/response transforms, API key validation, URL construction
  • litellm/llms/tinyfish/search/__init__.py -- module export
  • litellm/types/utils.py -- added TINYFISH to SearchProviders enum
  • litellm/utils.py -- added TinyfishSearchConfig to provider config map
  • model_prices_and_context_window.json -- pricing entry ($5/1k searches)
  • provider_endpoints_support.json -- endpoint support entry
  • tests/code_coverage_tests/enforce_llms_folder_style.py -- added "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()

Changes from prior PR (#30492) based on Greptile feedback:

  • _append_domain_filters now uses explicit AND keyword between query and domain clauses
  • Removed redundant max(1, ...) floor in transform_search_response since transform_search_request already clamps to min 1
  • Added comprehensive unit tests in tests/test_litellm/ to fix codecov patch coverage (prior PR only had tests in tests/search_tests/ which CI doesn't count for coverage)

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.
@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 same GET-based wrapper-dict pattern used by Brave and other GET providers. The implementation is self-contained within litellm/llms/tinyfish/ and registers cleanly through the existing SearchProviders enum and ProviderConfigManager map.

  • TinyfishSearchConfig handles request transformation (query join, country→location mapping, max_results clamping 1–20, domain filter injection via boolean AND/site: syntax), URL construction via urlencode, and response transformation with a client-side result cap.
  • 38 tests across two directories; all properly mocked with no real network calls, satisfying the tests/search_tests/ and tests/test_litellm/ rules respectively.
  • Pricing ($5/1k) and endpoint-support entries follow the existing JSON schemas exactly.

Confidence Score: 4/5

Safe to merge; the new provider is fully isolated, no existing behaviour is changed, and all tests are mocked.

The implementation closely mirrors existing GET-based search providers and is well-tested. The only notable gap is that transform_search_response lost its max(1, ...) floor when the PR removed it as redundant, meaning a manually constructed URL with max_results=0 would silently return zero results — but this path is unreachable through the normal litellm pipeline.

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

Important Files Changed

Filename Overview
litellm/llms/tinyfish/search/transformation.py Core TinyFish provider implementation; overall clean but response-side max_results handling has a minor edge-case gap
litellm/llms/tinyfish/search/init.py Module export boilerplate; correct and minimal
litellm/types/utils.py Adds TINYFISH to SearchProviders enum; follows the same pattern as all other providers
litellm/utils.py Registers TinyfishSearchConfig in PROVIDER_TO_CONFIG_MAP; correct and minimal
model_prices_and_context_window.json Adds tinyfish/search pricing entry at $0.005/query ($5/1k); consistent with other search providers
provider_endpoints_support.json Adds tinyfish endpoint support entry; follows existing pattern
tests/code_coverage_tests/enforce_llms_folder_style.py Adds tinyfish to SEARCH_PROVIDERS enforcement list; correct
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py 31 unit tests covering every method; all mocked, no real network calls
tests/search_tests/test_tinyfish_search.py 7 integration-style tests via litellm.asearch(); all properly mocked, follows repo rules for this folder

Reviews (1): Last reviewed commit: "feat(search): add TinyFish as search pro..." | Re-trigger Greptile

response_json = raw_response.json()

query_params = raw_response.request.url.params if raw_response.request else {}
max_results = min(int(query_params.get("max_results", 20)), 20)

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 The response-side max_results cap has no lower-bound guard. transform_search_request clamps to max(1, ...), but if a request URL reaches transform_search_response with max_results=0 (e.g., a manually constructed URL or a future caller that skips transform_search_request), the condition len(results) >= 0 is true on the very first iteration and the method silently returns zero results regardless of what the API returned. Adding max(1, ...) here makes the response transformation independently safe.

Suggested change
max_results = min(int(query_params.get("max_results", 20)), 20)
max_results = max(1, min(int(query_params.get("max_results", 20)), 20))

Comment on lines +68 to +70
api_base = (
api_base or get_secret_str("TINYFISH_API_BASE") or self.TINYFISH_API_BASE
)

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 get_complete_url calls get_secret_str("TINYFISH_API_BASE") on every request. The or-chain short-circuits correctly so this is functionally fine, but the TINYFISH_API_BASE env var override is entirely undocumented; consider adding a comment so future maintainers know it exists.

Suggested change
api_base = (
api_base or get_secret_str("TINYFISH_API_BASE") or self.TINYFISH_API_BASE
)
# Supports overriding via TINYFISH_API_BASE env var (for self-hosted / proxy setups)
api_base = (
api_base or get_secret_str("TINYFISH_API_BASE") or self.TINYFISH_API_BASE
)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@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!

@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 used by Brave. The implementation is clean, self-contained in litellm/llms/tinyfish/, and backed by 38 tests (31 unit + 7 integration-style) that all use mocks.

  • TinyfishSearchConfig correctly implements BaseSearchConfig: validate_environment enforces the API key, transform_search_request clamps max_results to [1, 20], maps countrylocation, and embeds search_domain_filter as boolean site: clauses in the query string.
  • transform_search_response re-reads max_results from the request URL params for defensive truncation; the design works but is slightly fragile when the request object is absent (falls back silently to 20).
  • A minor test-isolation issue exists in tests/search_tests/test_tinyfish_search.py: test_missing_api_key mutates os.environ without cleanup, unlike the equivalent test in the unit suite that uses patch.

Confidence Score: 4/5

Safe to merge; the change is additive and isolated to the new tinyfish provider.

The core implementation is correct and well-tested. The only concerns are a test-isolation issue in the search_tests suite (os.environ mutation without cleanup) and the design choice of re-reading max_results from URL params in transform_search_response, which silently falls back to 20 if the request object is absent. Neither affects the production path under normal operation.

tests/search_tests/test_tinyfish_search.py — env-var mutation without cleanup; litellm/llms/tinyfish/search/transformation.py — max_results URL round-trip.

Important Files Changed

Filename Overview
litellm/llms/tinyfish/search/transformation.py Core TinyFish search provider implementation; GET-based approach using _tinyfish_params wrapper pattern consistent with existing providers; one minor robustness concern with max_results round-trip through URL params in transform_search_response.
litellm/llms/tinyfish/search/init.py Module export for TinyfishSearchConfig; minimal and correct.
litellm/types/utils.py Adds TINYFISH to SearchProviders enum; straightforward addition.
litellm/utils.py Registers TinyfishSearchConfig in the provider config map; follows the established pattern.
model_prices_and_context_window.json Adds tinyfish/search pricing entry at $5/1k searches ($0.005/query); consistent with other search provider 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 allowlist; correct addition.
tests/test_litellm/llms/tinyfish/test_tinyfish_search.py 31 unit tests covering every method and branch; all properly mocked, no real network calls.
tests/search_tests/test_tinyfish_search.py 7 integration-style tests via litellm.asearch(); all async tests mock AsyncHTTPHandler.get; test_missing_api_key uses os.environ.pop without cleanup which could affect subsequent tests in the same process.

Reviews (2): Last reviewed commit: "feat(search): add TinyFish as search pro..." | Re-trigger Greptile

Comment on lines +218 to +225
def test_missing_api_key(self):
os.environ.pop("TINYFISH_API_KEY", None)

from litellm.llms.tinyfish.search.transformation import TinyfishSearchConfig

config = TinyfishSearchConfig()
with pytest.raises(ValueError, match="TINYFISH_API_KEY"):
config.validate_environment(headers={})

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 The test pops TINYFISH_API_KEY from the environment without restoring it. If pytest runs tests in the same process in an order where this executes before an async test that forgets to set the env var, the async test will hit a missing-key error. The unit test counterpart in tests/test_litellm/ correctly uses patch to avoid this. This test should do the same.

Suggested change
def test_missing_api_key(self):
os.environ.pop("TINYFISH_API_KEY", None)
from litellm.llms.tinyfish.search.transformation import TinyfishSearchConfig
config = TinyfishSearchConfig()
with pytest.raises(ValueError, match="TINYFISH_API_KEY"):
config.validate_environment(headers={})
def test_missing_api_key(self):
with patch(
"litellm.llms.tinyfish.search.transformation.get_secret_str",
return_value=None,
):
from litellm.llms.tinyfish.search.transformation import TinyfishSearchConfig
config = TinyfishSearchConfig()
with pytest.raises(ValueError, match="TINYFISH_API_KEY"):
config.validate_environment(headers={})

Comment on lines +126 to +127
query_params = raw_response.request.url.params if raw_response.request else {}
max_results = min(int(query_params.get("max_results", 20)), 20)

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 max_results round-trip through URL params is fragile: if raw_response.request is unexpectedly None (e.g. a cached or reconstructed response), the limit silently falls back to the hardcoded 20, potentially returning more items than the caller requested. Consider threading the value through kwargs or a shared context object so transform_search_response doesn't need to re-parse the URL to recover a parameter it already received during request construction.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@mateo-berri mateo-berri deleted the branch BerriAI:litellm_oss_staging160626 June 17, 2026 01:23
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