Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b61484e
feat: add Amazon Bedrock AgentCore Web Search as a native search prov…
CrypticDriver Jul 21, 2026
ebdad6e
fix: address bot review findings (auth hardening, SSE parsing, defaults)
CrypticDriver Jul 22, 2026
2f342dc
fix: honor AWS shared-config region for custom gateway hostnames
CrypticDriver Jul 22, 2026
b434418
test: mirror AgentCore search tests into tests/test_litellm for coverage
CrypticDriver Jul 23, 2026
e1629b7
fix(interactions): sync queued status enum from #34318 to unblock CI …
CrypticDriver Jul 26, 2026
07f1461
feat(search): add Amazon Bedrock AgentCore web search provider
devin-ai-integration[bot] Aug 9, 2026
b1d77bb
style: ruff format agentcore search transformation
devin-ai-integration[bot] Aug 9, 2026
15a6664
fix(search): keep signed auth headers out of logging callbacks
devin-ai-integration[bot] Aug 9, 2026
25144fc
chore: retrigger ci after docs merge
devin-ai-integration[bot] Aug 9, 2026
cf2e500
Merge branch 'litellm_internal_staging' into devin_ai_agentcore_search
yassin-berriai Aug 18, 2026
17b72d5
fix(search): send MCP-Protocol-Version on AgentCore gateway calls
yassin-berriai Aug 18, 2026
ae18f05
fix(search): harden AgentCore gateway trust, error and SSE handling
yassin-berriai Aug 19, 2026
49dca49
fix(search): point the AgentCore region error at AWS_DEFAULT_REGION
yassin-berriai Aug 19, 2026
b3c3e6e
fix(search): default the AgentCore MCP protocol version to the gatewa…
mateo-berri Aug 19, 2026
ed84e82
docs(search): use the latest Bedrock sonnet in the agentcore example …
mateo-berri Aug 19, 2026
8ef522a
fix(search): read AgentCore structuredContent results
yassin-berriai Aug 19, 2026
70a4f9a
fix(search): refuse AgentCore credentials over plaintext HTTP
mateo-berri Aug 19, 2026
7744b91
docs(search): stop advertising yaml litellm_params knobs the search r…
mateo-berri Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions litellm/llms/base_llm/search/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,29 @@ def validate_environment(
"""
return headers

def sign_request(
self,
headers: dict[str, str], # mutable-ok: matches the request header dict every other hook on this base takes
optional_params: dict[str, object], # mutable-ok: matches every other hook on this base
request_data: dict[str, object] | list[dict[str, object]], # mutable-ok: transform_search_request's body
api_base: str,
api_key: str | None = None,
) -> tuple[dict[str, str], bytes | None]: # mutable-ok: the handler passes these headers straight to httpx
"""
OPTIONAL

Sign the request. Providers like Bedrock AgentCore need to SigV4-sign
the request before sending it to the API.

For all other providers, this is a no-op and we just return the headers.

Returns:
Tuple of (headers, signed_json_body). When signed_json_body is not
None, the handler MUST send it verbatim as the request body —
re-serializing the payload would invalidate the signature.
"""
return headers, None

def get_complete_url(
self,
api_base: str | None,
Expand Down
Empty file.
455 changes: 455 additions & 0 deletions litellm/llms/bedrock/search/transformation.py

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions litellm/llms/custom_httpx/llm_http_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,14 @@ def search(
api_key=api_key,
)

signed_headers, signed_json_body = provider_config.sign_request(
headers=headers,
optional_params=optional_params,
request_data=data,
api_base=complete_url,
api_key=api_key,
)

## LOGGING
logging_obj.pre_call(
input=query if isinstance(query, str) else str(query),
Expand All @@ -1786,14 +1794,15 @@ def search(
# Note: timeout is set on the client itself, not per-request for GET
response = client.get(
url=complete_url,
headers=headers,
headers=signed_headers,
)
else:
# Make POST request with JSON data
# A signed body must be sent verbatim, re-serializing it would break the signature
response = client.post(
url=complete_url,
headers=headers,
json=data,
headers=signed_headers,
data=signed_json_body,
json=data if signed_json_body is None else None,
timeout=timeout,
)
except Exception as e:
Expand Down Expand Up @@ -1847,6 +1856,14 @@ async def async_search(
api_key=api_key,
)

signed_headers, signed_json_body = provider_config.sign_request(
headers=headers,
optional_params=optional_params,
request_data=data,
api_base=complete_url,
api_key=api_key,
)

## LOGGING
logging_obj.pre_call(
input=query if isinstance(query, str) else str(query),
Expand Down Expand Up @@ -1875,14 +1892,15 @@ async def async_search(
# Note: timeout is set on the client itself, not per-request for GET
response = await async_httpx_client.get(
url=complete_url,
headers=headers,
headers=signed_headers,
)
else:
# Make async POST request with JSON data
# A signed body must be sent verbatim, re-serializing it would break the signature
response = await async_httpx_client.post(
url=complete_url,
headers=headers,
json=data,
headers=signed_headers,
data=signed_json_body,
json=data if signed_json_body is None else None,
timeout=timeout,
)
except Exception as e:
Expand Down
8 changes: 8 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -16435,6 +16435,14 @@
"notes": "APISerpent deep search (/api/search), multi-engine (Google, Bing, Yahoo, DuckDuckGo). Pricing: $0.60/1k searches."
}
},
"agentcore/search": {
"input_cost_per_query": 0.0,
"litellm_provider": "agentcore",
"mode": "search",
"metadata": {
"notes": "Web Search on Amazon Bedrock AgentCore, billed by AWS on the gateway"
}
},
"tinyfish/search": {
"input_cost_per_query": 0.0,
"litellm_provider": "tinyfish",
Expand Down
40 changes: 40 additions & 0 deletions litellm/proxy/example_config_yaml/agentcore_websearch_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Claude Code / Anthropic-native web search on Bedrock, backed by
# Amazon Bedrock AgentCore Web Search (AWS-managed web index, no third-party
# search API). See litellm/llms/bedrock/search/transformation.py for details.

model_list:
- model_name: claude-sonnet
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-5
aws_region_name: us-east-1

search_tools:
- search_tool_name: agentcore-search
litellm_params:
search_provider: agentcore
# Your AgentCore Gateway MCP endpoint (gateway must have a `web-search`
# connector target). Alternatively set the AGENTCORE_GATEWAY_URL env var.
api_base: https://<gateway-id>.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp

# The gateway exposes the connector as "<target-name>___WebSearch".
# Default is "web-search-tool___WebSearch", matching the target name used
# in the AWS docs' boto3/CLI setup examples. If your target was created
# with a different name (misconfiguration surfaces as an MCP "tool not
# found" error), set the AGENTCORE_SEARCH_TOOL_NAME env var or pass
# tool_name in the request body. The search router forwards only
# search_provider / api_key / api_base from this litellm_params block,
# so a tool_name set here would be silently ignored.

# AWS_IAM gateway (default): SigV4-signed using the standard AWS
# credential chain (env / profile / IRSA / instance role). Explicit
# aws_access_key_id / aws_secret_access_key set here would be silently
# ignored for the same reason; pass them per request instead.

# CUSTOM_JWT gateway alternative — OAuth2 bearer token instead of SigV4:
# api_key: os.environ/AGENTCORE_GATEWAY_TOKEN

litellm_settings:
callbacks: ["websearch_interception"]
websearch_interception_params:
enabled_providers: ["bedrock"]
search_tool_name: agentcore-search
1 change: 1 addition & 0 deletions litellm/types/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3791,6 +3791,7 @@ class SearchProviders(str, Enum):
YOU_COM = "you_com"
APISERPENT = "apiserpent"
TINYFISH = "tinyfish"
AGENTCORE = "agentcore"
NIMBLE = "nimble"


Expand Down
2 changes: 2 additions & 0 deletions litellm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9062,6 +9062,7 @@ def get_provider_search_config(
from litellm.llms.apiserpent.search.transformation import (
APISerpentSearchConfig,
)
from litellm.llms.bedrock.search.transformation import AgentCoreSearchConfig
from litellm.llms.brave.search.transformation import BraveSearchConfig
from litellm.llms.dataforseo.search.transformation import DataForSEOSearchConfig
from litellm.llms.duckduckgo.search.transformation import DuckDuckGoSearchConfig
Expand Down Expand Up @@ -9100,6 +9101,7 @@ def get_provider_search_config(
SearchProviders.YOU_COM: YouComSearchConfig,
SearchProviders.APISERPENT: APISerpentSearchConfig,
SearchProviders.TINYFISH: TinyfishSearchConfig,
SearchProviders.AGENTCORE: AgentCoreSearchConfig,
SearchProviders.NIMBLE: NimbleSearchConfig,
}
config_class: Final = PROVIDER_TO_CONFIG_MAP.get(provider, None)
Expand Down
8 changes: 8 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -16435,6 +16435,14 @@
"notes": "APISerpent deep search (/api/search), multi-engine (Google, Bing, Yahoo, DuckDuckGo). Pricing: $0.60/1k searches."
}
},
"agentcore/search": {
"input_cost_per_query": 0.0,
"litellm_provider": "agentcore",
"mode": "search",
"metadata": {
"notes": "Web Search on Amazon Bedrock AgentCore, billed by AWS on the gateway"
}
},
"tinyfish/search": {
"input_cost_per_query": 0.0,
"litellm_provider": "tinyfish",
Expand Down
Loading
Loading