Skip to content

Commit 07fb6bc

Browse files
committed
feat: Add support for Bedrock system tools.
1 parent 78e547c commit 07fb6bc

File tree

5 files changed

+276
-27
lines changed

5 files changed

+276
-27
lines changed

docs/api_openai_chat_completions.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This OpenAI-compatible endpoint provides access to AWS Bedrock foundation models
4646
| Parallel tool calls | :material-cog:{ .model-dep } | Multiple tools in one turn |
4747
| Disable Parallel tool calls | :material-close-circle:{ .unsupported } | Parallel tool calls are always on |
4848
| Non-function tool types | :material-close-circle:{ .unsupported } | Only function tools supported |
49+
| System tools (`systemTool_*`) | :material-plus-circle:{ .extra-feature } | AWS Bedrock system tools (e.g., web grounding with citations) |
4950
| **Generation Control** | | |
5051
| `max_tokens` / `max_completion_tokens` | :material-check-circle:{ .success } | Output length limits |
5152
| `temperature` | :material-cog:{ .model-dep } | Mapped to Bedrock inference params |
@@ -74,6 +75,7 @@ This OpenAI-compatible endpoint provides access to AWS Bedrock foundation models
7475
| Audio | :material-check-circle:{ .success } | Synthesis from text output |
7576
| `response_format` (JSON mode) | :material-cog:{ .model-dep } | Model-specific JSON support |
7677
| `reasoning_content` (From Deepseek API) | :material-cog:{ .model-dep } | Text reasoning messages |
78+
| `annotations` (URL citations) | :material-check-circle:{ .success } | URL citations from system tools (non-streaming only) |
7779
| **Usage tracking** | | |
7880
| Input text tokens | :material-check-circle:{ .success } | Billing unit |
7981
| Output tokens | :material-check-circle:{ .success } | Billing unit |
@@ -265,6 +267,95 @@ curl -X POST "$BASE/v1/chat/completions" \
265267
!!! note "Unsupported Parameter"
266268
The `tagSuffix` parameter is not supported in this implementation.
267269

270+
### AWS Bedrock System Tools
271+
272+
AWS Bedrock system tools are built-in capabilities that foundation models can use directly without requiring you to implement backend integrations. Access any AWS Bedrock system tool by adding the `systemTool_` prefix to its name—this works for current tools and any future system tools AWS releases.
273+
274+
**How to Use:**
275+
276+
Add system tools to your `tools` array using the `systemTool_` prefix followed by the tool name. System tools don't require parameter definitions—just specify the tool name and the model will handle the rest.
277+
278+
As AWS releases new system tools, simply use the same `systemTool_` prefix pattern to access them.
279+
280+
#### ![Amazon Nova](styles/logo_amazon_nova.svg){ style="height: 1.2em; vertical-align: text-bottom;" } Amazon Nova Web Grounding
281+
282+
Amazon Nova Web Grounding enables models to search the web for current information, helping answer questions requiring real-time data like news, weather, product availability, or recent events. The model automatically determines when to use web grounding based on the user's query.
283+
284+
!!! info "Learn More"
285+
- [Amazon Nova Web Grounding - User Guide](https://docs.aws.amazon.com/nova/latest/userguide/grounding.html)
286+
- [Build More Accurate AI Applications with Amazon Nova Web Grounding - Blog Post](https://aws.amazon.com/fr/blogs/aws/build-more-accurate-ai-applications-with-amazon-nova-web-grounding/)
287+
288+
**Usage:**
289+
290+
```bash
291+
curl -X POST "$BASE/v1/chat/completions" \
292+
-H "Authorization: Bearer $OPENAI_API_KEY" \
293+
-H "Content-Type: application/json" \
294+
-d '{
295+
"model": "amazon.nova-premier-v1:0",
296+
"messages": [
297+
{
298+
"role": "user",
299+
"content": "What are the current AWS Regions and their locations?"
300+
}
301+
],
302+
"tools": [
303+
{
304+
"type": "function",
305+
"function": {
306+
"name": "systemTool_nova_grounding"
307+
}
308+
}
309+
]
310+
}'
311+
```
312+
313+
**Response Format:**
314+
315+
When using web grounding, the API response includes `annotations` with URL citations in non-streaming mode:
316+
317+
```json
318+
{
319+
"choices": [{
320+
"message": {
321+
"role": "assistant",
322+
"content": "The AWS Regions include...",
323+
"annotations": [
324+
{
325+
"type": "url_citation",
326+
"url_citation": {
327+
"url": "https://aws.amazon.com/about-aws/global-infrastructure/",
328+
"title": "AWS Global Infrastructure"
329+
}
330+
}
331+
]
332+
}
333+
}]
334+
}
335+
```
336+
337+
!!! note "Streaming Mode"
338+
Citations are only available in non-streaming responses. The OpenAI API does not support annotations in streaming mode.
339+
340+
**Use Cases:**
341+
342+
- **Current Events**: Get up-to-date information about news, weather, stock prices, or sports scores
343+
- **Dynamic Data**: Query information that changes frequently like AWS service availability or product prices
344+
- **Verification**: Cross-reference facts with current web sources for improved accuracy
345+
- **Knowledge Extension**: Supplement model training data with real-time information
346+
347+
**Benefits:**
348+
349+
- **Zero Integration**: No need to implement or maintain web search APIs
350+
- **Automatic Invocation**: Models intelligently decide when to use web grounding
351+
- **Enhanced Accuracy**: Reduce hallucinations with real-time information retrieval
352+
- **OpenAI-Compatible**: Works seamlessly with standard tool calling patterns
353+
354+
!!! warning "Model and Region Compatibility"
355+
**Model**: Only Amazon Nova Premier (`amazon.nova-premier-v1:0`) supports the `systemTool_nova_grounding` tool.
356+
357+
**Region**: Web Grounding is only available in US regions.
358+
268359
### Provider-Specific Parameters
269360

270361
Unlock advanced model capabilities by passing provider-specific parameters directly in your requests. These parameters are forwarded to AWS Bedrock and allow you to access features unique to each foundation model provider.

docs/roadmap.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,17 @@ The following features may be implemented in future releases based on community
126126

127127
## ✨ Release History
128128

129-
### v1.1.0 – Embeddings Enhancement, Prompt Caching & Advanced Routing
129+
### v1.1.0 – Embeddings Enhancement, Prompt Caching, Advanced Routing & System Tools
130130

131-
Expands multimodal embedding capabilities, adds prompt caching support, and introduces advanced routing with application inference profiles and prompt routers.
131+
Expands multimodal embedding capabilities, adds prompt caching support, introduces advanced routing with application inference profiles and prompt routers, and enables AWS Bedrock system tools (like web grounding) with automatic URL citation extraction.
132132

133133
### 💬 Chat Completions
134134

135-
| Provider | Endpoint/Feature | AWS Backend |
136-
|--------------------------------------------------------------------------------|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
137-
| ![OpenAI](styles/logo_openai.svg){: style="height:20px;width:20px"} **OpenAI** | Prompt caching `/v1/chat/completions` `prompt_cache_key` | ![Amazon Bedrock](styles/logo_amazon_bedrock.svg){: style="height:20px;width:20px"} Amazon Bedrock - prompt caching |
138-
| ![OpenAI](styles/logo_openai.svg){: style="height:20px;width:20px"} **OpenAI** | `/v1/chat/completions` GPT5.1 API update (`reasoning_effort=none`) | |
135+
| Provider | Endpoint/Feature | AWS Backend |
136+
|------------------------------------------------------------------------------------------|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
137+
| ![OpenAI](styles/logo_openai.svg){: style="height:20px;width:20px"} **OpenAI** | Prompt caching `/v1/chat/completions` `prompt_cache_key` | ![Amazon Bedrock](styles/logo_amazon_bedrock.svg){: style="height:20px;width:20px"} Amazon Bedrock - prompt caching |
138+
| ![OpenAI](styles/logo_openai.svg){: style="height:20px;width:20px"} **OpenAI** | `/v1/chat/completions` GPT5.1 API update (`reasoning_effort=none`) | |
139+
| ![AWS Bedrock](styles/logo_amazon_bedrock.svg){: style="height:20px;width:20px"} **AWS** | System tools support with URL citations (`systemTool_*`) | ![Amazon Bedrock](styles/logo_amazon_bedrock.svg){: style="height:20px;width:20px"} Amazon Bedrock - system tools |
139140

140141
### 🧠 Embeddings
141142

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ requires-python = ">=3.13"
77
dependencies = [
88
"fastapi",
99
"aioboto3>=15.1.0",
10-
"aiobotocore>=2.24.0",
10+
"botocore>=1.40.62",
1111
"pydantic>=2",
1212
"pydantic-settings>=2",
1313
"sse-starlette",

stdapi/routes/openai_chat_completions.py

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,13 @@
5757
log_request_stream_event,
5858
log_response_params,
5959
)
60+
from stdapi.openai_exceptions import OpenaiError
6061
from stdapi.routes.openai_audio_speech import generate_audio
6162
from stdapi.tokenizer import estimate_token_count
6263
from stdapi.types.openai import FunctionDefinition
6364
from stdapi.types.openai_chat_completions import (
65+
Annotation,
66+
AnnotationURLCitation,
6467
ChatCompletion,
6568
ChatCompletionAssistantMessageParam,
6669
ChatCompletionAudio,
@@ -107,6 +110,7 @@
107110
VideoFormatType,
108111
)
109112
from types_aiobotocore_bedrock_runtime.type_defs import (
113+
CitationOutputTypeDef,
110114
ContentBlockDeltaEventTypeDef,
111115
ContentBlockOutputTypeDef,
112116
ContentBlockStartEventTypeDef,
@@ -119,10 +123,12 @@
119123
PerformanceConfigurationTypeDef,
120124
ReasoningContentBlockUnionTypeDef,
121125
SystemContentBlockTypeDef,
126+
SystemToolTypeDef,
122127
ToolChoiceTypeDef,
123128
ToolConfigurationTypeDef,
124129
ToolResultContentBlockUnionTypeDef,
125130
ToolSpecificationTypeDef,
131+
ToolTypeDef,
126132
VideoBlockTypeDef,
127133
)
128134

@@ -155,6 +161,9 @@
155161
#: Minimal tool JSON input schema for Bedrock
156162
_EMPTY_TOOL = {"type": "object"}
157163

164+
#: Bedrock system tools prefix
165+
_SYSTEM_TOOL_PREFIX = "systemTool_"
166+
158167

159168
def _req_extract_system_content_blocks(
160169
content: str | Iterable[ChatCompletionContentPartTextParam],
@@ -728,39 +737,55 @@ def _req_map_tool_or_function(
728737

729738

730739
def _req_map_tool_spec(
731-
tool: ChatCompletionToolUnionParam,
732-
) -> "ToolSpecificationTypeDef":
733-
"""Convert an OpenAI tool dict to a Bedrock ToolSpecification, if possible."""
740+
tool: ChatCompletionToolUnionParam, tools: "list[ToolTypeDef]"
741+
) -> None:
742+
"""Maps a tool's specification to the provided tools list based on its type.
743+
744+
Args:
745+
tool: The tool to be processed and mapped.
746+
tools: The list where processed tool specifications will be appended.
747+
"""
734748
tool_type = tool.type
735749
if tool_type == "function":
736750
function_tool: ChatCompletionFunctionToolParam = tool # type: ignore[assignment]
737751
function_spec = function_tool.function
738-
return {
739-
"name": function_spec.name,
740-
"description": function_spec.description or tool_type,
741-
"inputSchema": {"json": function_spec.parameters or _EMPTY_TOOL},
742-
}
743-
raise HTTPException( # pragma: no cover
744-
status_code=400,
745-
detail=f"Unsupported tool type '{tool_type}': {to_json(tool).decode()}",
746-
)
752+
name = function_spec.name
753+
if name.startswith(_SYSTEM_TOOL_PREFIX) and not function_spec.parameters:
754+
system_tool: SystemToolTypeDef = {
755+
"name": name.removeprefix(_SYSTEM_TOOL_PREFIX)
756+
}
757+
tools.append({"systemTool": system_tool})
758+
else:
759+
tool_spec: ToolSpecificationTypeDef = {
760+
"name": function_spec.name,
761+
"description": function_spec.description or tool_type,
762+
"inputSchema": {"json": function_spec.parameters or _EMPTY_TOOL},
763+
}
764+
tools.append({"toolSpec": tool_spec})
765+
else: # pragma: no cover
766+
msg = f"Unsupported tool type '{tool_type}': {to_json(tool).decode()}"
767+
raise OpenaiError(msg)
747768

748769

749770
def _req_build_tool_config(
750771
request: "CompletionCreateParams",
751772
) -> "ToolConfigurationTypeDef | None":
752-
"""Build Bedrock tool configuration from OpenAI tools/function fields.
773+
"""Builds a configuration for tools based on the provided request.
774+
775+
Args:
776+
request: The request object containing the data
777+
to map and configure tools.
753778
754-
Returns None when no usable function tools are provided.
779+
Returns:
780+
The mapped tool configuration object if tools are present, otherwise None.
755781
"""
756-
tools_specs: list[ToolSpecificationTypeDef] = []
757-
tools_specs.extend(_req_map_tool_spec(tool) for tool in _req_map_tools(request))
758-
if not tools_specs:
782+
tools: list[ToolTypeDef] = []
783+
for tool in _req_map_tools(request):
784+
_req_map_tool_spec(tool, tools)
785+
if not tools:
759786
return None
760787

761-
tool_config: ToolConfigurationTypeDef = {
762-
"tools": [{"toolSpec": spec} for spec in tools_specs]
763-
}
788+
tool_config: ToolConfigurationTypeDef = {"tools": tools}
764789
tool_choice_bedrock = _req_map_tool_or_function(request)
765790
if tool_choice_bedrock:
766791
tool_config["toolChoice"] = tool_choice_bedrock
@@ -946,6 +971,59 @@ def _resp_extract_output_text_from_converse(
946971
) if reasoning_text else None
947972

948973

974+
def _resp_extract_citation_from_bedrock(
975+
citation: "CitationOutputTypeDef",
976+
) -> "Annotation | None":
977+
"""Extract a single URL citation from Bedrock citation data.
978+
979+
Args:
980+
citation: A Bedrock CitationOutputTypeDef from non-streaming response.
981+
982+
Returns:
983+
An Annotation object with URL citation, or None if no web URL found.
984+
"""
985+
try:
986+
web_location = citation["location"]["web"]
987+
url = web_location["url"]
988+
except KeyError:
989+
return None
990+
return Annotation(
991+
type="url_citation",
992+
url_citation=AnnotationURLCitation(
993+
url=url,
994+
title=citation.get("title") or web_location.get("domain", ""),
995+
start_index=0,
996+
end_index=0,
997+
),
998+
)
999+
1000+
1001+
def _resp_extract_citations_from_output_blocks(
1002+
contents: "list[ContentBlockOutputTypeDef]",
1003+
) -> "list[Annotation] | None":
1004+
"""Extract URL citations from Bedrock non-streaming content blocks.
1005+
1006+
Args:
1007+
contents: content blocks from Bedrock Converse response (non-streaming).
1008+
1009+
Returns:
1010+
List of Annotation objects with URL citations, or None if no citations found.
1011+
"""
1012+
annotations: list[Annotation] = []
1013+
for block in contents:
1014+
try:
1015+
citations = block["citationsContent"]["citations"]
1016+
except KeyError:
1017+
continue
1018+
1019+
for citation in citations:
1020+
annotation = _resp_extract_citation_from_bedrock(citation)
1021+
if annotation:
1022+
annotations.append(annotation)
1023+
1024+
return annotations if annotations else None
1025+
1026+
9491027
def _resp_extract_tool_calls_from_converse(
9501028
contents: "list[ContentBlockOutputTypeDef]",
9511029
) -> tuple[list[ChatCompletionMessageToolCallUnion] | None, FunctionCall | None]:
@@ -1115,6 +1193,7 @@ async def _non_streaming_completion(
11151193
message = response["output"]["message"]["content"]
11161194
tool_calls, function_call = _resp_extract_tool_calls_from_converse(message)
11171195
content, reasoning_content = _resp_extract_output_text_from_converse(message)
1196+
annotations = _resp_extract_citations_from_output_blocks(message)
11181197
if reasoning_content:
11191198
reasoning_contents.append(reasoning_content)
11201199
if audio_params and content:
@@ -1133,6 +1212,7 @@ async def _non_streaming_completion(
11331212
reasoning_content=reasoning_content,
11341213
tool_calls=tool_calls,
11351214
function_call=function_call,
1215+
annotations=annotations,
11361216
),
11371217
)
11381218
)

0 commit comments

Comments
 (0)