Amazon Q in ConnectでServiceNowをナレッジベースとし、タグで検索対象を絞り込んでみた
はじめに
Amazon Q in Connect は、コンタクトセンター業務を AI で支援するマネージドサービスです。
本サービスには、データソースにタグを付与し、問い合わせに関連するドキュメントだけを絞り込める「コンテンツセグメンテーション」という機能があります。これにより次のメリットが得られます。
- 回答精度の向上 : 関連性の高い情報だけを検索対象にするため、より正確な回答が可能
- 応答時間の短縮 : 検索対象が減ることで、必要な情報へ迅速にアクセス
- 顧客満足度の向上 : 的確でスピーディーな回答により、顧客の課題を早期に解決
以前、ナレッジベースのソースをS3バケットで試しました。
今回は、ナレッジベースのソースをServiceNowでコンテンツセグメンテーションを試してみます。
前提条件
- ServiceNowのナレッジ記事作成済み
- Amazon Q in Connect有効化済み
ServiceNowのナレッジ記事の確認
ServiceNowのトップページから、ナレッジに遷移します。

ナレッジをクリックします。

クラスメソッドのオフィス住所として、東京と札幌の住所情報の2つのナレッジ記事を作成しています。

それぞれの記事は、公開済みの記事です。

札幌オフィス情報のナレッジ記事番号は、KB0010006です。後で利用します。
コンテンツのタグ付け手順
まず、Connectインスタンスと紐づいている対象のナレッジベースのknowledgeBaseIdを取得します。
AWS CloudShellで以下のコマンドを実行します。
$ aws qconnect list-knowledge-bases
{
"knowledgeBaseSummaries": [
{
"knowledgeBaseId": "bbe5faac-a562-41ee-b50b-b53716fbc54c",
"knowledgeBaseArn": "arn:aws:wisdom:ap-northeast-1:アカウントID:knowledge-base/bbe5faac-a562-41ee-b50b-b53716fbc54c",
"name": "integration-servicenow-hirai-2-2",
"knowledgeBaseType": "EXTERNAL",
"status": "ACTIVE",
"sourceConfiguration": {
"appIntegrations": {
"appIntegrationArn": "arn:aws:app-integrations:ap-northeast-1:アカウントID:data-integration/9a19f3eb-979d-4652-97fd-ec73b01de3fa",
"objectFields": [
"number",
"workflow_state",
"active",
"short_description",
"sys_mod_count",
"sys_id",
"wiki",
"sys_updated_on",
"text"
]
}
対象のナレッジベース名前は、マネジメントコンソール上の[統合]から確認できます。

統合名
次に、ListContents APIにて、対象ナレッジベースのコンテンツリストを取得します。ここで言うコンテンツとは、ServiceNowに保存されている各ナレッジ記事のことです。
各コンテンツのcontentArnをコピーしておきます。ナレッジ記事の数が多いため、今回は、札幌オフィス情報に絞るため、ナレッジ記事番号KB0010006のみを出力させます。
$ aws qconnect list-contents --knowledge-base bbe5faac-a562-41ee-b50b-b53716fbc54c --query "contentSummaries[?metadata.number=='KB0010006']"
[
{
"contentArn": "arn:aws:wisdom:ap-northeast-1:アカウントID:content/bbe5faac-a562-41ee-b50b-b53716fbc54c/95786efd-0a48-4956-b8ff-377bc67d8e0a",
"contentId": "95786efd-0a48-4956-b8ff-377bc67d8e0a",
"knowledgeBaseArn": "arn:aws:wisdom:ap-northeast-1:アカウントID:knowledge-base/bbe5faac-a562-41ee-b50b-b53716fbc54c",
"knowledgeBaseId": "bbe5faac-a562-41ee-b50b-b53716fbc54c",
"name": "KB0010006",
"revisionId": "OGU0MjAyNWQ2OWUyMzIzNjY1OGYyZGY1MGNlNTVlNjc0NmM1OWQxYTkxMjY3OTMwZjZmYjkyNDk2ODFjMWQzYg==",
"title": "札幌オフィス",
"contentType": "application/x.wisdom-json;source=servicenow",
"status": "ACTIVE",
"metadata": {
"active": "true",
"aws:wisdom:externalVersion": "3",
"number": "KB0010006",
"short_description": "札幌オフィス",
"sys_mod_count": "3",
"workflow_state": "Published"
},
"tags": {}
}
]
TagResource APIにて、先程取得したコンテンツそれぞれにタグ付けを実行します。
なお、TagResource APIのリクエストパラメータresourceArnは、先ほど取得した各コンテンツのcontentArnです。
ここでは category と content_type の 2 種類のタグを付けます。
$ aws qconnect tag-resource \
--resource-arn "arn:aws:wisdom:ap-northeast-1:アカウントID:content/bbe5faac-a562-41ee-b50b-b53716fbc54c/95786efd-0a48-4956-b8ff-377bc67d8e0a" \
--tags '{"category":"product_info","content_type":"address_sapporo"}'
コンテンツがタグ付けされているかも確認できます。
$ aws qconnect list-tags-for-resource \
--resource-arn "arn:aws:wisdom:ap-northeast-1:アカウントID:content/bbe5faac-a562-41ee-b50b-b53716fbc54c/95786efd-0a48-4956-b8ff-377bc67d8e0a"
{
"tags": {
"content_type": "address_sapporo",
"category": "product_info"
}
}
Lambdaを作成する
コンタクトフローから呼び出し、Amazon Q in Connect の「セッション」にタグフィルターを設定する Lambda 関数を作成します。
- 名前 :
cm-hirai-q-in-connect-session-tag-filter-updater - ランタイム : Python 3.13
- 付与するロール :
AWSLambdaBasicExecutionRole+ 下記インラインポリシー
{
"Version": "2012-10-17",
"Statement":[
{
"Effect": "Allow",
"Action": [
"connect:DescribeContact",
"wisdom:UpdateSession"
],
"Resource": "*"
}
]
}
import json
import boto3
from botocore.exceptions import ClientError
connect_client = boto3.client('connect')
qconnect_client = boto3.client('qconnect')
def lambda_handler(event, context):
print('Event Received: ' + json.dumps(event, ensure_ascii=False))
# Connectコンタクトデータとパラメータの取得
contact_data = event.get('Details', {}).get('ContactData', {})
tag_filter = event.get('Details', {}).get('Parameters', {}).get('tagFilter')
print("Tag Filter: ", json.dumps(tag_filter))
# Amazon Connect Contactの情報を取得
try:
contact_response = connect_client.describe_contact(
InstanceId=contact_data.get('InstanceARN'),
ContactId=contact_data.get('ContactId')
).get("Contact", {})
print(f"Describe ContactId: {contact_data.get('ContactId')}: {json.dumps(contact_response, default=str)}")
except ClientError as e:
error_message = f"ClientError - DescribeContact for ContactId: {contact_data.get('ContactId')} - {e}"
print(error_message)
return {"success": False}
# Amazon Q in Connect セッション情報の抽出
wisdom_info = contact_response.get("WisdomInfo", {})
session_arn = wisdom_info.get("SessionArn")
if not session_arn:
error_message = f"No Amazon Q in Connect session found for ContactId: {contact_data.get('ContactId')}"
print(error_message)
return {"success": False}
# セッションARNからアシスタントIDとセッションIDを抽出
try:
arn_parts = session_arn.split('/')
assistant_id = arn_parts[1]
session_id = arn_parts[2]
except (IndexError, AttributeError) as e:
error_message = f"Error parsing session ARN: {session_arn} - {e}"
print(error_message)
return {"success": False}
# タグフィルターの処理とセッションの更新
try:
# タグフィルターが文字列の場合、JSONオブジェクトに変換
if isinstance(tag_filter, str):
try:
tag_filter_json = json.loads(tag_filter)
except json.JSONDecodeError:
error_message = f"Invalid JSON in tagFilter parameter: {tag_filter}"
print(error_message)
return {
"statusCode": 400,
"body": error_message
}
else:
tag_filter_json = tag_filter
# セッションにタグフィルターを適用
update_session_response = qconnect_client.update_session(
assistantId=assistant_id,
sessionId=session_id,
tagFilter=tag_filter_json
).get("session", {})
print("Update Session Response: ", json.dumps(update_session_response))
return {"success": True}
except Exception as e:
print(f"Error: {str(e)}")
return {"success": False}
この関数は以下の処理を行います
- コンタクトフローから渡された Contact情報と
tagFilterパラメータを受け取る DescribeContactで Wisdom セッション ARN を取得- ARN から assistantId と sessionId を分解
UpdateSessionAPI にtagFilterを渡してセッションを更新
→ 以降の検索対象が、指定タグを持つコンテンツだけに絞られる
Connectフロー
検証用のフローは下図のとおりです。

(クリックで展開)
{
"Version": "2019-10-30",
"StartAction": "62416ee0-7b7d-406f-8df5-4888d812fa9a",
"Metadata": {
"entryPointPosition": {
"x": 290.4,
"y": 53.6
},
"ActionMetadata": {
"fba2c05b-c6a2-4795-acaf-200e9f6c39da": {
"position": {
"x": 631.2,
"y": 52.8
}
},
"62416ee0-7b7d-406f-8df5-4888d812fa9a": {
"position": {
"x": 404.8,
"y": 52
}
},
"10d508f7-76b0-4ff6-b084-59ed78c9e1a0": {
"position": {
"x": 176.8,
"y": 238.4
},
"children": [
"b3ab6dc2-902c-411a-b211-7c3128e8abfa"
],
"parameters": {
"WisdomAssistantArn": {
"displayName": "arn:aws:wisdom:ap-northeast-1:111111111111:assistant/a1793008-f4de-481b-a9ed-3697ef373ff2"
}
},
"fragments": {
"SetContactData": "b3ab6dc2-902c-411a-b211-7c3128e8abfa"
}
},
"b3ab6dc2-902c-411a-b211-7c3128e8abfa": {
"position": {
"x": 176.8,
"y": 238.4
},
"dynamicParams": []
},
"90537ffa-b186-47ec-938f-cca6dc4286db": {
"position": {
"x": 404,
"y": 236.8
},
"parameters": {
"LambdaFunctionARN": {
"displayName": "cm-hirai-q-in-connect-session-tag-filter-updater"
},
"LambdaInvocationAttributes": {
"tagFilter": {
"useJson": true
}
}
},
"dynamicMetadata": {
"tagFilter": false
}
},
"433d0be0-48cb-44da-a24f-6aa745e3f242": {
"position": {
"x": 853.6,
"y": 224.8
}
},
"d7f03407-1f71-4852-9ca0-1a8e00d17409": {
"position": {
"x": 630.4,
"y": 234.4
},
"parameters": {
"QueueId": {
"displayName": "cm-hirai"
}
},
"queue": {
"text": "cm-hirai"
}
},
"8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc": {
"position": {
"x": 863.2,
"y": 454.4
}
}
},
"Annotations": [],
"name": "cm-hirai-q-in-connect-session-tag-filter-updater",
"description": "",
"type": "contactFlow",
"status": "published",
"hash": {}
},
"Actions": [
{
"Parameters": {
"RecordingBehavior": {
"RecordedParticipants": [
"Agent",
"Customer"
],
"ScreenRecordedParticipants": [
"Agent"
],
"IVRRecordingBehavior": "Disabled"
},
"AnalyticsBehavior": {
"Enabled": "True",
"AnalyticsLanguage": "ja-JP",
"AnalyticsRedactionBehavior": "Disabled",
"AnalyticsRedactionResults": "None",
"ChannelConfiguration": {
"Chat": {
"AnalyticsModes": []
},
"Voice": {
"AnalyticsModes": [
"PostContact"
]
}
}
}
},
"Identifier": "fba2c05b-c6a2-4795-acaf-200e9f6c39da",
"Type": "UpdateContactRecordingBehavior",
"Transitions": {
"NextAction": "10d508f7-76b0-4ff6-b084-59ed78c9e1a0"
}
},
{
"Parameters": {
"FlowLoggingBehavior": "Enabled"
},
"Identifier": "62416ee0-7b7d-406f-8df5-4888d812fa9a",
"Type": "UpdateFlowLoggingBehavior",
"Transitions": {
"NextAction": "fba2c05b-c6a2-4795-acaf-200e9f6c39da"
}
},
{
"Parameters": {
"WisdomAssistantArn": "arn:aws:wisdom:ap-northeast-1:111111111111:assistant/a1793008-f4de-481b-a9ed-3697ef373ff2"
},
"Identifier": "10d508f7-76b0-4ff6-b084-59ed78c9e1a0",
"Type": "CreateWisdomSession",
"Transitions": {
"NextAction": "b3ab6dc2-902c-411a-b211-7c3128e8abfa",
"Errors": [
{
"NextAction": "90537ffa-b186-47ec-938f-cca6dc4286db",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"WisdomSessionArn": "$.Wisdom.SessionArn"
},
"Identifier": "b3ab6dc2-902c-411a-b211-7c3128e8abfa",
"Type": "UpdateContactData",
"Transitions": {
"NextAction": "90537ffa-b186-47ec-938f-cca6dc4286db",
"Errors": [
{
"NextAction": "90537ffa-b186-47ec-938f-cca6dc4286db",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"LambdaFunctionARN": "arn:aws:lambda:ap-northeast-1:111111111111:function:cm-hirai-q-in-connect-session-tag-filter-updater",
"InvocationTimeLimitSeconds": "8",
"LambdaInvocationAttributes": {
"tagFilter": {
"tagCondition": {
"key": "content_type",
"value": "address_sapporo"
}
}
},
"ResponseValidation": {
"ResponseType": "STRING_MAP"
}
},
"Identifier": "90537ffa-b186-47ec-938f-cca6dc4286db",
"Type": "InvokeLambdaFunction",
"Transitions": {
"NextAction": "d7f03407-1f71-4852-9ca0-1a8e00d17409",
"Errors": [
{
"NextAction": "d7f03407-1f71-4852-9ca0-1a8e00d17409",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {},
"Identifier": "433d0be0-48cb-44da-a24f-6aa745e3f242",
"Type": "TransferContactToQueue",
"Transitions": {
"NextAction": "8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc",
"Errors": [
{
"NextAction": "8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc",
"ErrorType": "QueueAtCapacity"
},
{
"NextAction": "8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"QueueId": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/queue/ba8d05d9-27b3-406e-b089-f5707174697e"
},
"Identifier": "d7f03407-1f71-4852-9ca0-1a8e00d17409",
"Type": "UpdateContactTargetQueue",
"Transitions": {
"NextAction": "433d0be0-48cb-44da-a24f-6aa745e3f242",
"Errors": [
{
"NextAction": "8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {},
"Identifier": "8e1110a3-e055-4bf4-9c5a-2b5c2b48f6bc",
"Type": "DisconnectParticipant",
"Transitions": {}
}
]
}
Lambda ブロックの設定
[AWS Lambda 関数を呼び出す] ブロックに、前項で作成した Lambda を指定し、パラメータ tagFilter に次の JSON を渡します。
この設定により、content_type=address_sapporo のタグを持つコンテンツだけが検索対象になります。
(実際に利用する場合は、前段でIVRなどでお客様のお問い合わせ内容に応じて動的にフィルタリングしますが、今回は検証なので、シンプルなフローで検証します。)
{
"tagCondition": {
"key": "content_type",
"value": "address_sapporo"
}
}

UpdateSession API の TagFilter 仕様
UpdateSession の tagFilter は「Tagged Union」形式で、トップレベルには次の 3 つのいずれか 1 つしか指定できません。
andConditionsorConditionstagCondition(単一条件)
ドキュメントには明確に記載されています。
This is a Tagged Union structure. Only one of the following top level keys can be set: andConditions, orConditions, tagCondition.
つまり、これらの条件を同時に使用することはできません。1つのリクエストで指定できるのは、これらのうちの1つだけです。
ただし、それぞれの条件内で複雑な条件を構築することは可能です。
andConditionsでは複数のタグ条件をAND条件で結合できますorConditionsでは複数の条件をOR条件で結合でき、その中にandConditionsやtagConditionを含めることができますtagConditionは単一のタグキーと値のペアを指定します
これにより、複雑なタグフィルタリング条件を構築できますが、トップレベルでは3つのうち1つだけを選択する必要があります。
以下のように複数条件フィルタリングすることが可能です。
{
"andConditions": [
{
"key": "content_type",
"value": "address_sapporo"
},
{
"key": "category",
"value": "product_info"
}
]
}
{
"orConditions": [
{
"tagCondition": {
"key": "content_type",
"value": "address_sapporo"
}
},
{
"tagCondition": {
"key": "content_type",
"value": "address_tokyo"
}
}
]
}
{
"orConditions": [
{
"andConditions": [
{
"key": "content_type",
"value": "address"
},
{
"key": "region",
"value": "hokkaido"
}
]
},
{
"tagCondition": {
"key": "priority",
"value": "high"
}
}
]
}
動作検証
Amazon Q in Connect コンテンツ検索して、タグフィルタリングの効果を検証します。
まずは、タグフィルタリングのない、通話していないとき、電話対応前で試します。
「オフィス情報を教えて」と質問すると、以下の通り、東京と札幌の2つのオフィス情報が確認できました。
日比谷本社オフィス:
〒105-0003
東京都港区西新橋1-1-1 日比谷フォートタワー26階
札幌オフィス:
〒060-0003
北海道札幌市中央区北3条西1-1-1 札幌ブリックキューブ10階
アクセス:
・JR札幌駅 南口東改札口から徒歩7分
・地下鉄東豊線/南北線 さっぽろ駅 21番出口から徒歩1分
・地下鉄東西線 大通駅から徒歩7分

対して、チャットで先程のConnectフローでチャットを行い、チャット中に「オフィス情報を教えて」と質問すると、以下の通り、札幌のみのオフィス情報が確認できました。
札幌オフィスは札幌ブリックキューブ10階にあります。住所は〒060-0003 北海道札幌市中央区北3条西1-1-1です。
アクセス方法:
・JR札幌駅 南口東改札口から徒歩7分
・地下鉄東豊線/南北線 さっぽろ駅21番出口から徒歩1分
・地下鉄東西線 大通駅から徒歩7分
コンテンツセグメンテーションが機能していることがわかります。

最後に
本記事では、Amazon Q in Connectにおける「コンテンツセグメンテーション」機能を使い、ServiceNowをナレッジベースとして、特定タグを持つドキュメントだけをナレッジ検索の対象にする方法を紹介しました。
コンテンツセグメンテーションを活用することで、顧客の問い合わせ内容に応じて関連性の高い情報のみを検索対象とし、回答精度の向上と応答時間の短縮が実現できます。
課題として、現在はナレッジ記事単位でのタグ付けが必要なため、大量のコンテンツを扱う場合は自動化スクリプトの導入が推奨されます。






