关于 virtual-agent
virtual-agent 是用于设计 ServiceNow 对话式自助主题和流程的技能,涵盖 NLU 配置、对话块,以及管理和测试 Virtual Agent 对话的 MCP 工具。
主要功能
- 01创建主题和对话块
- 02配置 NLU 意图和实体
- 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 虚拟 Agent
虚拟 Agent(VA)通过聊天界面提供自助服务的对话式 AI 功能。
虚拟 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 | 转接 Agent | 连接到人工 Agent |
文本块
// 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()
提示块
// 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()
脚本块(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 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()
转接块
// 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 | 转接给 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",
})
最佳实践
- 明确意图 - 每个主题只对应一个目的
- 丰富训练 - 使用多种不同的话语
- 优雅回退 - 处理未知输入
- 快捷回复 - 减少输入
- 确认 - 执行操作前先验证
- 转接路径 - 始终允许转接给 Agent
- 充分测试 - 测试多种对话路径
- 角色风格 - 保持友好语气的一致性
评论
支持 Markdown