![[Update] Amazon Bedrock Managed Knowledge Bases Now Lets You Make Metadata Fields Searchable, So I Tried It Out](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/f_auto,q_auto/v1790870640/eyecatch/fqat5yzhltnknpjit83r.png)
[Update] Amazon Bedrock Managed Knowledge Bases Now Lets You Make Metadata Fields Searchable, So I Tried It Out
This page has been translated by machine translation. View original
Introduction
Hello, I'm Kanno from the Consulting Department, and I love ramen.
With the API update on November 6, 2026, the metadata fields of Amazon Bedrock Managed Knowledge Base can now be made searchable!
Metadata filtering itself was already possible before, wasn't it? So what exactly got updated?
What's Good About It
Until now, metadata was used to narrow down documents with a filter before searching.
Even if you stored information such as the author or project name in metadata, if the name did not appear in the body text, you could not use that name as a clue to raise a document's ranking, and to search by name the app had to build the filter conditions.

Starting with this update, settings such as whether each metadata field is searchable have been added!
Fields made searchable are matched against the search query and also used in the ranking calculation, so if you search for something like "monthly report written by Hanako Yamada", even documents without her name in the body text can be found. That's nice!
Unlike a filter, it doesn't discard documents that don't match the conditions; instead it is a mechanism that raises rankings. The official documentation linked at the beginning also advises making fields that users are likely to use in searches, such as author, category, and status, searchable, and leaving fields used only for narrowing down turned off.
Here is an image of how it works.

The setting is made with metadataConfigurations in the UpdateKnowledgeBase API.
To make the author and projects metadata searchable, write it as follows.
metadataConfigurations=[
{"name": "author", "type": "STRING", "searchability": "ENABLED"},
{"name": "projects", "type": "STRING_LIST", "searchability": "ENABLED"},
]
For each field, you specify the name, type, and searchability.
The available types are two: string (STRING) and string list (STRING_LIST), and you can configure up to 30 fields per knowledge base.
At the moment, this can only be set with the UpdateKnowledgeBase API, and it cannot be specified in the CreateKnowledgeBase API at creation time. The only change in CreateKnowledgeBase is that the response now includes the configuration, so the flow is to create the knowledge base first and then configure it with an update. It would be nice to be able to set it at the same time as creation... I hope they support it soon!
Trying It Out
I prepared four fictional monthly reports. The body text of each is similar, and the author and project names are stored in metadata.
| Document ID | author | projects |
|---|---|---|
| report-01 | Hanako Yamada | Kohaku, Ruri |
| report-02 | Ken Sato | Hisui |
| report-03 | Ichiro Suzuki | Menou |
| report-04 | Misaki Takahashi | Sango |
With these four ingested, I'll search the following two queries and compare the rankings before and after making the fields searchable.
- Monthly report written by Hanako Yamada
- Progress of Kohaku
Verification Environment
- Region is us-east-1
- Python 3.15.2
- boto3 1.43.126 or later is required
mkdir kb-metadata-search && cd kb-metadata-search
uv init --bare
uv add "boto3>=1.43.126"
Next, save the following code as demo.py.
Using subcommands, you can run everything from creating the knowledge base to ingesting documents, searching, changing settings, and cleaning up.
Full demo.py
import json
import sys
import time
from pathlib import Path
import boto3
REGION = "us-east-1"
STATE = Path("state.json")
agent = boto3.client("bedrock-agent", region_name=REGION)
runtime = boto3.client("bedrock-agent-runtime", region_name=REGION)
iam = boto3.client("iam", region_name=REGION)
# 本文には作成者もプロジェクト名も書かず、メタデータにだけ持たせる
REPORTS = [
{
"id": "report-01",
"author": "山田花子",
"projects": ["コハク", "ルリ"],
"text": "10月の月次報告です。問い合わせ対応の件数は前月より12%減りました。FAQページを改修した影響と見ています。来月はチャットボットの回答精度の改善に取り組みます。",
},
{
"id": "report-02",
"author": "佐藤健",
"projects": ["ヒスイ"],
"text": "10月の月次報告です。問い合わせ対応の件数は前月より5%増えました。新機能のリリース直後に質問が集中したためです。来月はリリースノートの書き方を見直します。",
},
{
"id": "report-03",
"author": "鈴木一郎",
"projects": ["メノウ"],
"text": "10月の月次報告です。問い合わせ対応の件数は前月とほぼ同じでした。夜間の一次回答をチャットボットに任せる準備を進めています。来月は回答テンプレートを整理します。",
},
{
"id": "report-04",
"author": "高橋美咲",
"projects": ["サンゴ"],
"text": "10月の月次報告です。問い合わせ対応の件数は前月より8%減りました。よくある質問を社内ポータルに掲載したためです。来月は担当者向けの研修資料を作ります。",
},
]
QUERIES = ["山田花子さんが書いた月次報告", "コハクの進捗"]
def save(name, data):
Path("evidence").mkdir(exist_ok=True)
Path("evidence", name + ".json").write_text(
json.dumps(data, ensure_ascii=False, indent=2, default=str)
)
def state():
return json.loads(STATE.read_text())
def wait_kb(kb_id):
for _ in range(120):
kb = agent.get_knowledge_base(knowledgeBaseId=kb_id)["knowledgeBase"]
print("kb status", kb["status"], flush=True)
if kb["status"] == "ACTIVE":
return kb
if kb["status"] in ("FAILED", "UPDATE_UNSUCCESSFUL"):
raise RuntimeError(kb.get("failureReasons"))
time.sleep(10)
raise RuntimeError("timed out")
def setup():
if STATE.exists():
raise RuntimeError("state.json already exists")
account = boto3.client("sts").get_caller_identity()["Account"]
name = "kb-meta-search-" + str(int(time.time()))
trust = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "bedrock.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"aws:SourceAccount": account},
"ArnLike": {
"aws:SourceArn": f"arn:aws:bedrock:{REGION}:{account}:knowledge-base/*"
},
},
}
],
}
role = iam.create_role(RoleName=name, AssumeRolePolicyDocument=json.dumps(trust))
s = {"name": name, "roleArn": role["Role"]["Arn"]}
STATE.write_text(json.dumps(s))
time.sleep(15)
kb = agent.create_knowledge_base(
name=name,
roleArn=s["roleArn"],
knowledgeBaseConfiguration={
"type": "MANAGED",
"managedKnowledgeBaseConfiguration": {"embeddingModelType": "MANAGED"},
},
)["knowledgeBase"]
s["knowledgeBaseId"] = kb["knowledgeBaseId"]
STATE.write_text(json.dumps(s))
save("created-kb", kb)
wait_kb(s["knowledgeBaseId"])
ds = agent.create_data_source(
knowledgeBaseId=s["knowledgeBaseId"],
name="custom",
dataSourceConfiguration={
"type": "MANAGED_KNOWLEDGE_BASE_CONNECTOR",
"managedKnowledgeBaseConnectorConfiguration": {
"connectorParameters": {"type": "CUSTOM", "version": "1"}
},
},
)["dataSource"]
s["dataSourceId"] = ds["dataSourceId"]
STATE.write_text(json.dumps(s))
print(json.dumps(s))
def ingest():
s = state()
documents = [
{
"content": {
"dataSourceType": "CUSTOM",
"custom": {
"customDocumentIdentifier": {"id": r["id"]},
"sourceType": "IN_LINE",
"inlineContent": {
"type": "TEXT",
"textContent": {"data": r["text"]},
},
},
},
"metadata": {
"type": "IN_LINE_ATTRIBUTE",
"inlineAttributes": [
{
"key": "author",
"value": {"type": "STRING", "stringValue": r["author"]},
},
{
"key": "projects",
"value": {
"type": "STRING_LIST",
"stringListValue": r["projects"],
},
},
],
},
}
for r in REPORTS
]
params = {k: s[k] for k in ["knowledgeBaseId", "dataSourceId"]}
agent.ingest_knowledge_base_documents(**params, documents=documents)
ids = [{"dataSourceType": "CUSTOM", "custom": {"id": r["id"]}} for r in REPORTS]
for _ in range(60):
details = agent.get_knowledge_base_documents(
**params, documentIdentifiers=ids
)["documentDetails"]
statuses = [d["status"] for d in details]
print("documents", statuses, flush=True)
if all(x == "INDEXED" for x in statuses):
return
time.sleep(10)
raise RuntimeError("timed out")
def search(label="search"):
s = state()
out = {}
for q in QUERIES:
resp = runtime.retrieve(
knowledgeBaseId=s["knowledgeBaseId"],
retrievalQuery={"text": q},
retrievalConfiguration={
"managedSearchConfiguration": {"numberOfResults": 4}
},
)
out[q] = resp["retrievalResults"]
print("Q:", q)
for i, r in enumerate(resp["retrievalResults"], 1):
m = r.get("metadata", {})
print(
f" [{i}] score={r.get('score', 0):.4f} author={m.get('author')} projects={m.get('projects')}"
)
save(label, out)
def enable():
s = state()
kb = agent.get_knowledge_base(knowledgeBaseId=s["knowledgeBaseId"])[
"knowledgeBase"
]
resp = agent.update_knowledge_base(
knowledgeBaseId=s["knowledgeBaseId"],
name=kb["name"],
roleArn=kb["roleArn"],
knowledgeBaseConfiguration=kb["knowledgeBaseConfiguration"],
metadataConfigurations=[
{"name": "author", "type": "STRING", "searchability": "ENABLED"},
{"name": "projects", "type": "STRING_LIST", "searchability": "ENABLED"},
],
)
save("update", resp)
kb = wait_kb(s["knowledgeBaseId"])
save("kb-after-update", kb)
print(json.dumps(kb.get("metadataConfigurations"), ensure_ascii=False))
def cleanup():
s = state()
agent.delete_knowledge_base(knowledgeBaseId=s["knowledgeBaseId"])
for _ in range(180):
try:
agent.get_knowledge_base(knowledgeBaseId=s["knowledgeBaseId"])
except agent.exceptions.ResourceNotFoundException:
break
time.sleep(5)
iam.delete_role(RoleName=s["name"])
STATE.unlink()
print("Knowledge Base and IAM role deleted")
if __name__ == "__main__":
cmd = sys.argv[1]
if cmd == "search":
search(sys.argv[2] if len(sys.argv) > 2 else "search")
else:
{"setup": setup, "ingest": ingest, "enable": enable, "cleanup": cleanup}[cmd]()
Metadata is attached to each document at ingestion time, and lists of strings are passed with the STRING_LIST type.
"metadata": {
"type": "IN_LINE_ATTRIBUTE",
"inlineAttributes": [
{"key": "author", "value": {"type": "STRING", "stringValue": "山田花子"}},
{
"key": "projects",
"value": {"type": "STRING_LIST", "stringListValue": ["コハク", "ルリ"]},
},
],
},
Create the knowledge base and ingest the four documents.
uv run demo.py setup
uv run demo.py ingest
Before Making Fields Searchable
First, let's search in the state where nothing has been configured.
uv run demo.py search before
Q: 山田花子さんが書いた月次報告
[1] score=0.3517 author=鈴木一郎 projects=['メノウ']
[2] score=0.2934 author=高橋美咲 projects=['サンゴ']
[3] score=0.2141 author=山田花子 projects=['コハク', 'ルリ']
Q: コハクの進捗
[1] score=0.3342 author=佐藤健 projects=['ヒスイ']
[2] score=0.2883 author=高橋美咲 projects=['サンゴ']
[3] score=0.1614 author=鈴木一郎 projects=['メノウ']
Hanako Yamada's report stayed in 3rd place, and other reports with similar body text ranked higher.
In the search for "Progress of Kohaku", Hanako Yamada's report doesn't hit at all.
Making Metadata Fields Searchable
Set the searchability of author and projects to ENABLED with the UpdateKnowledgeBase API.
kb = agent.get_knowledge_base(knowledgeBaseId=s["knowledgeBaseId"])["knowledgeBase"]
agent.update_knowledge_base(
knowledgeBaseId=s["knowledgeBaseId"],
name=kb["name"],
roleArn=kb["roleArn"],
knowledgeBaseConfiguration=kb["knowledgeBaseConfiguration"],
metadataConfigurations=[
{"name": "author", "type": "STRING", "searchability": "ENABLED"},
{"name": "projects", "type": "STRING_LIST", "searchability": "ENABLED"},
],
)
UpdateKnowledgeBase also requires the name, role, and configuration, so I pass the current values obtained with GetKnowledgeBase as-is. When adding fields later, specify the existing fields together as well. metadataConfigurations is replaced entirely by the list you pass.
uv run demo.py enable
kb status UPDATING
kb status ACTIVE
[{"name": "author", "type": "STRING", "searchability": "ENABLED"}, {"name": "projects", "type": "STRING_LIST", "searchability": "ENABLED"}]
It returned to ACTIVE about 12 seconds after running the command. Without re-ingesting the documents, let's search as-is.
After Making Fields Searchable
uv run demo.py search after
Q: 山田花子さんが書いた月次報告
[1] score=0.4371 author=山田花子 projects=['コハク', 'ルリ']
[2] score=0.1890 author=鈴木一郎 projects=['メノウ']
[3] score=0.1535 author=高橋美咲 projects=['サンゴ']
Q: コハクの進捗
[1] score=0.4840 author=山田花子 projects=['コハク', 'ルリ']
[2] score=0.1538 author=佐藤健 projects=['ヒスイ']
[3] score=0.1536 author=鈴木一郎 projects=['メノウ']
In both searches, Hanako Yamada's report ranked 1st!

Hanako Yamada's report score rose from 0.21 to 0.44, and for Kohaku it went from outside the results to 0.48, creating a gap from 2nd place and below.
projects is a list of strings, but even when only "Kohaku" out of the two values is included in the query, it is matched correctly. It's also nice that the settings change was immediately reflected for already ingested documents.
Is It Searchable Even with Paraphrasing?
Here I became curious whether metadata is also searched by semantic closeness.
The searches so far had characters matching the metadata values, so I also tried paraphrases that don't match.
After deleting the knowledge base from the first half, I recreated a knowledge base using a demo.py with a department field added, and gave the four documents the departments HR, Accounting, Legal, and Information Systems. I searched in the state with nothing configured, then made the three fields author, projects, and department searchable all at once and searched again.
Changes from demo.py and execution results
I added the department to each report in REPORTS, and added it to the metadata at ingestion and to the fields made searchable. The department is also shown in the search results display.
# REPORTS の各報告に部署を追加(report-01から順に人事部・経理部・法務部・情報システム部)
"department": "経理部",
# ingest() の inlineAttributes に追加
{"key": "department", "value": {"type": "STRING", "stringValue": r["department"]}},
# 検索するクエリ
QUERIES = [
"経理部の月次報告",
"お金の出入りを管理する部署の月次報告",
"契約書をチェックする部署の報告",
"琥珀の進捗",
"Hanako Yamada report",
]
# enable() の metadataConfigurations に追加
{"name": "department", "type": "STRING", "searchability": "ENABLED"},
# search() の表示に部署を追加
print(
f" [{i}] score={r.get('score', 0):.4f} author={m.get('author')} dept={m.get('department')} projects={m.get('projects')}"
)
The execution order is the same as in the first half.
uv run demo.py setup
uv run demo.py ingest
uv run demo.py search before
uv run demo.py enable
uv run demo.py search after
uv run demo.py cleanup
Q: 経理部の月次報告
[3] score=0.1592 author=佐藤健 dept=経理部 projects=['ヒスイ']
Q: お金の出入りを管理する部署の月次報告
[3] score=0.1820 author=佐藤健 dept=経理部 projects=['ヒスイ']
Q: 契約書をチェックする部署の報告
[3] score=0.1780 author=鈴木一郎 dept=法務部 projects=['メノウ']
Q: 経理部の月次報告
[1] score=0.4642 author=佐藤健 dept=経理部 projects=['ヒスイ']
[2] score=0.2342 author=高橋美咲 dept=情報システム部 projects=['サンゴ']
Q: お金の出入りを管理する部署の月次報告
[1] score=0.3474 author=佐藤健 dept=経理部 projects=['ヒスイ']
[2] score=0.3278 author=高橋美咲 dept=情報システム部 projects=['サンゴ']
Q: 契約書をチェックする部署の報告
[3] score=0.1749 author=鈴木一郎 dept=法務部 projects=['メノウ']
Q: 琥珀の進捗
[1] score=0.3938 author=高橋美咲 dept=情報システム部 projects=['サンゴ']
[2] score=0.2020 author=佐藤健 dept=経理部 projects=['ヒスイ']
[3] score=0.1685 author=鈴木一郎 dept=法務部 projects=['メノウ']
Q: Hanako Yamada report
[1] score=0.3558 author=高橋美咲 dept=情報システム部 projects=['サンゴ']
[2] score=0.2760 author=鈴木一郎 dept=法務部 projects=['メノウ']
[3] score=0.1510 author=佐藤健 dept=経理部 projects=['ヒスイ']

"Accounting Department", whose characters match, rose from 3rd to 1st, but for the Legal department, amber (琥珀), and the romanized paraphrases, the ranking did not change. For "money flow", the Accounting report came in 1st, but the gap to 2nd place was 0.02.
In this verification, rankings rose for queries whose characters matched, but for paraphrases such as "琥珀" and "Hanako Yamada", the targeted report did not appear in the search results...
For fields where notation may vary, it seems best to put in the values the notations likely to be used in searches.
These are only impressions from the range I tried this time, so please judge for yourself whether it is useful when actually designing and searching metadata!
Cleanup
Finally, delete the knowledge base and the IAM role.
uv run demo.py cleanup
Conclusion
You can now find documents by author or project name without building filter conditions!
For cases where you want to use them as search conditions, I think it's worth trying once!
I hope this article was helpful! Thank you for reading to the end!!
