virtual-agent について
virtual-agentは、ServiceNowの会話型セルフサービス向けトピックやフローを設計するスキルです。NLU設定や会話ブロックに加え、Virtual Agentの会話を管理・テストするMCPツールを扱います。
主な機能
- 01トピックと会話ブロックの作成
- 02NLUのインテントとエンティティ設定
- 03クイック返信とトピック変数の追加
- 04インシデント作成とナレッジ記事検索
- 05会話のテストと有人担当者への転送
活用シーン
- 01パスワードリセットのセルフサービスフロー構築
- 02リクエストの有人担当者への転送
- 03チャットからのインシデント作成
- 04ユーザーの質問に合うナレッジ記事の検索
MCP 対応
Virtual Agentのトピック作成・検索、ブロック追加、メッセージ送信、会話履歴の取得、有人担当者への引き継ぎに使うMCPツールを説明します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill virtual-agentインストール後、Claude Code で/virtual-agentを入力して読み込まれたか確認します。
詳細
ServiceNow の Virtual Agent
Virtual Agent(VA)は、チャットインターフェースを通じたセルフサービス向けに、会話型 AI 機能を提供します。
Virtual Agent のアーキテクチャ
トピック構造
Topic: Password Reset
├── NLU Model
│ ├── Utterances: "reset my password", "forgot password"
│ └── Entities: application_name
├── Topic Blocks (Flow)
│ ├── Greeting
│ ├── Ask for Application
│ ├── Verify User
│ ├── Process Reset
│ └── Confirmation
└── Variables
├── user_email
└── application
主要なテーブル
| テーブル | 用途 |
|---|---|
sys_cs_topic | トピックの定義 |
sys_cs_topic_block | 会話フローブロック |
sys_cs_intent | NLU インテント |
sys_cs_utterance | トレーニングフレーズ |
sys_cs_entity | カスタムエンティティ |
トピック
トピックの作成(ES5)
// Create Password Reset topic
var topic = new GlideRecord("sys_cs_topic")
topic.initialize()
topic.setValue("name", "Password Reset")
topic.setValue("description", "Help users reset their passwords")
topic.setValue("active", true)
// NLU configuration
topic.setValue("goal", "I want to reset my password")
topic.setValue("nlu_enabled", true)
// Category
topic.setValue("category", "IT Support")
var topicSysId = topic.insert()
トピック変数
// Add variable to topic
var variable = new GlideRecord("sys_cs_topic_variable")
variable.initialize()
variable.setValue("topic", topicSysId)
variable.setValue("name", "user_email")
variable.setValue("label", "Email Address")
variable.setValue("type", "string")
variable.setValue("mandatory", true)
variable.insert()
トピックブロック
ブロックの種類
| 種類 | 用途 | 例 |
|---|---|---|
| Text | メッセージを表示 | "それについてお手伝いできます!" |
| Prompt | 質問する | "メールアドレスを教えてください。" |
| Script | サーバーコードを実行 | ユーザーの検索、チケットの作成 |
| Decision | 分岐ロジック | VIP の場合は…… |
| Link | 外部リンク | ポータルページを開く |
| Handoff | エージェントへの転送 | ライブエージェントに接続する |
Text ブロック
// Greeting block
var block = new GlideRecord("sys_cs_topic_block")
block.initialize()
block.setValue("topic", topicSysId)
block.setValue("name", "greeting")
block.setValue("type", "text")
block.setValue("order", 100)
block.setValue("message", "Hello! I can help you reset your password. Let me gather some information.")
block.insert()
Prompt ブロック
// Ask for email
var promptBlock = new GlideRecord("sys_cs_topic_block")
promptBlock.initialize()
promptBlock.setValue("topic", topicSysId)
promptBlock.setValue("name", "ask_email")
promptBlock.setValue("type", "prompt")
promptBlock.setValue("order", 200)
promptBlock.setValue("message", "What is your email address?")
promptBlock.setValue("variable_name", "user_email")
promptBlock.setValue("validation_type", "email")
promptBlock.insert()
Script ブロック(ES5)
// Script block to verify user
var scriptBlock = new GlideRecord("sys_cs_topic_block")
scriptBlock.initialize()
scriptBlock.setValue("topic", topicSysId)
scriptBlock.setValue("name", "verify_user")
scriptBlock.setValue("type", "script")
scriptBlock.setValue("order", 300)
// Server-side script (ES5 only!)
scriptBlock.setValue(
"script",
"(function execute(inputs, outputs) {\n" +
" var email = inputs.user_email;\n" +
' var user = new GlideRecord("sys_user");\n' +
' user.addQuery("email", email);\n' +
" user.query();\n" +
" \n" +
" if (user.next()) {\n" +
" outputs.user_found = true;\n" +
' outputs.user_name = user.getValue("name");\n' +
" outputs.user_sys_id = user.getUniqueValue();\n" +
" } else {\n" +
" outputs.user_found = false;\n" +
" }\n" +
"})(inputs, outputs);",
)
scriptBlock.insert()
Decision ブロック
// Decision based on user found
var decisionBlock = new GlideRecord("sys_cs_topic_block")
decisionBlock.initialize()
decisionBlock.setValue("topic", topicSysId)
decisionBlock.setValue("name", "check_user")
decisionBlock.setValue("type", "decision")
decisionBlock.setValue("order", 400)
// Decision branches configured separately
decisionBlock.insert()
// Add decision branches
var branch1 = new GlideRecord("sys_cs_topic_block_branch")
branch1.initialize()
branch1.setValue("block", decisionBlock.getUniqueValue())
branch1.setValue("condition", "user_found == true")
branch1.setValue("next_block", processResetBlockSysId)
branch1.insert()
var branch2 = new GlideRecord("sys_cs_topic_block_branch")
branch2.initialize()
branch2.setValue("block", decisionBlock.getUniqueValue())
branch2.setValue("condition", "user_found == false")
branch2.setValue("next_block", userNotFoundBlockSysId)
branch2.insert()
Handoff ブロック
// Transfer to live agent
var handoffBlock = new GlideRecord("sys_cs_topic_block")
handoffBlock.initialize()
handoffBlock.setValue("topic", topicSysId)
handoffBlock.setValue("name", "transfer_agent")
handoffBlock.setValue("type", "handoff")
handoffBlock.setValue("order", 900)
handoffBlock.setValue("message", "Let me connect you with a live agent who can help further.")
handoffBlock.setValue("queue", liveSupportQueueSysId)
handoffBlock.insert()
NLU トレーニング
発話の追加
// Add training utterances for intent
function addUtterance(topicId, text) {
var utt = new GlideRecord("sys_cs_utterance")
utt.initialize()
utt.setValue("topic", topicId)
utt.setValue("utterance", text)
utt.insert()
}
// Training phrases for password reset
addUtterance(topicSysId, "reset my password")
addUtterance(topicSysId, "I forgot my password")
addUtterance(topicSysId, "change my password")
addUtterance(topicSysId, "password not working")
addUtterance(topicSysId, "can't log in")
addUtterance(topicSysId, "locked out of my account")
addUtterance(topicSysId, "need to reset password for @application")
カスタムエンティティ
// Create custom entity for applications
var entity = new GlideRecord("sys_cs_entity")
entity.initialize()
entity.setValue("name", "application_name")
entity.setValue("description", "Enterprise application names")
entity.setValue("type", "list")
entity.insert()
// Add entity values
var values = ["SAP", "Salesforce", "Workday", "ServiceNow", "Email"]
for (var i = 0; i < values.length; i++) {
var val = new GlideRecord("sys_cs_entity_value")
val.initialize()
val.setValue("entity", entity.getUniqueValue())
val.setValue("value", values[i])
val.setValue("synonyms", "") // comma-separated synonyms
val.insert()
}
クイック返信
クイック返信の設定
// Add quick reply options to prompt
var promptWithReplies = new GlideRecord("sys_cs_topic_block")
promptWithReplies.initialize()
promptWithReplies.setValue("topic", topicSysId)
promptWithReplies.setValue("name", "select_application")
promptWithReplies.setValue("type", "prompt")
promptWithReplies.setValue("order", 150)
promptWithReplies.setValue("message", "Which application do you need to reset?")
promptWithReplies.setValue("variable_name", "application")
promptWithReplies.setValue(
"quick_replies",
JSON.stringify([
{ label: "Email", value: "email" },
{ label: "SAP", value: "sap" },
{ label: "ServiceNow", value: "servicenow" },
{ label: "Other", value: "other" },
]),
)
promptWithReplies.insert()
統合アクション
VA からインシデントを作成する(ES5)
// Script block to create incident
var createIncidentScript =
"(function execute(inputs, outputs) {\n" +
' var inc = new GlideRecord("incident");\n' +
" inc.initialize();\n" +
' inc.setValue("caller_id", inputs.user_sys_id);\n' +
' inc.setValue("short_description", "Password Reset Request: " + inputs.application);\n' +
' inc.setValue("description", "User requested password reset via Virtual Agent.");\n' +
' inc.setValue("category", "software");\n' +
' inc.setValue("subcategory", "password reset");\n' +
' inc.setValue("priority", 3);\n' +
" \n" +
" var sysId = inc.insert();\n" +
" \n" +
' outputs.incident_number = inc.getValue("number");\n' +
" outputs.incident_sys_id = sysId;\n" +
"})(inputs, outputs);"
レコードの検索(ES5)
// Script to lookup knowledge articles
var lookupKBScript =
"(function execute(inputs, outputs) {\n" +
" var query = inputs.user_question;\n" +
" var articles = [];\n" +
" \n" +
' var kb = new GlideRecord("kb_knowledge");\n' +
' kb.addQuery("workflow_state", "published");\n' +
' kb.addQuery("short_description", "CONTAINS", query);\n' +
" kb.setLimit(3);\n" +
" kb.query();\n" +
" \n" +
" while (kb.next()) {\n" +
" articles.push({\n" +
' number: kb.getValue("number"),\n' +
' title: kb.getValue("short_description"),\n' +
" sys_id: kb.getUniqueValue()\n" +
" });\n" +
" }\n" +
" \n" +
" outputs.articles = JSON.stringify(articles);\n" +
" outputs.article_count = articles.length;\n" +
"})(inputs, outputs);"
MCP ツールの統合
利用可能な VA ツール
| ツール | 用途 |
|---|---|
snow_create_va_topic | トピックを作成する |
snow_create_va_topic_block | 会話ブロックを追加する |
snow_discover_va_topics | トピックを検索する |
snow_send_va_message | 会話をテストする |
snow_get_va_conversation | 会話履歴を取得する |
snow_handoff_to_agent | エージェントに転送する |
ワークフローの例
// 1. Create topic
var topicId = await snow_create_va_topic({
name: "IT Equipment Request",
description: "Help users request new equipment",
category: "IT Support",
})
// 2. Add greeting block
await snow_create_va_topic_block({
topic: topicId,
name: "greeting",
type: "text",
order: 100,
message: "I can help you request new IT equipment!",
})
// 3. Add prompt block
await snow_create_va_topic_block({
topic: topicId,
name: "equipment_type",
type: "prompt",
order: 200,
message: "What type of equipment do you need?",
quick_replies: ["Laptop", "Monitor", "Keyboard", "Mouse"],
})
// 4. Test conversation
await snow_send_va_message({
topic: topicId,
message: "I need a new laptop",
})
ベストプラクティス
- 明確なインテント - トピックごとに目的を 1 つにします
- 充実したトレーニング - さまざまな発話を多数用意します
- 適切なフォールバック - 不明な入力に対応します
- クイック返信 - 入力の手間を減らします
- 確認 - アクションの前に確認します
- エージェントへの転送経路 - 常にエージェントへの転送を可能にします
- 十分なテスト - さまざまな会話経路をテストします
- ペルソナ - 一貫して親しみやすい口調を保ちます
コメント
Markdown 対応