大语言模型(Large Language Model,LLM)有丰富的知识,但它默认没有“手脚”。它可以解释天气预报是什么,却不能自己联网查询北京明天的天气;它知道 GitHub Issue 的含义,却不能天然帮你去仓库里查问题;它理解机器学习训练流程,也不会自动连接数据库、拉取数据、训练模型、保存结果。
OpenClaw Skill 解决的是这个“会说但不会做”的问题。Skill 可以理解成写给 AI 的操作手册:什么时候触发、需要什么工具、要执行哪些命令、结果怎么解释,都写在一个 SKILL.md 文件里。OpenClaw 负责加载这些手册,并在用户请求命中时让模型按流程执行。
当 Skill 遇到 RDS DuckDB 分析型实例,事情会变得更有意思。电商平台的行为数据通常非常大,浏览、加购、下单、收藏等事件会按天产生海量记录。如果直接在事务型 MySQL 上做复杂聚合,查询可能拖慢业务库,也可能跑很久。DuckDB 分析型实例负责把“翻账本”的动作变快,OpenClaw Skill 负责把“分析—训练—验证—记录”的动作自动化。
组合起来,就可以做出一个能够持续迭代的电商销售预测系统。
flowchart LR
U[用户提出预测需求] --> O[OpenClaw]
O --> S[ecommerce-predictor Skill]
S --> D[(RDS DuckDB 分析型实例)]
D --> F[生成时间序列特征]
F --> M[训练多个预测模型]
M --> V[用下月真实数据验证]
V --> H[保存历史版本与指标]
H --> S
这套系统的重点不是“让 AI 凭感觉预测”,而是把预测工作拆成确定的工程流程:查询数据、构造特征、训练模型、验证误差、记录版本、下次用更多数据重新训练。
OpenClaw Skill:把 AI 能力变成可执行流程
Skill 的本质是一个 Markdown 文件,通常叫 SKILL.md。文件顶部写结构化元信息,正文写操作步骤。
一个最简单的天气查询 Skill 可能只有几行逻辑:
---
name: weather
description: 查询指定城市的天气。Use when: 用户询问天气、气温、降雨情况。
requires:
bins:
- curl
---
当用户询问天气时,使用 wttr.in 查询城市天气。
示例:
curl wttr.in/Beijing
拿到结果后,用自然语言总结温度、天气状况和降雨概率。
这不是普通提示词,而是一份可被 OpenClaw 管理、筛选、注入和运行的能力描述。OpenClaw 可以内置多种 Skill,也允许用户自己写新的 Skill。只要把流程讲清楚,AI 就能从“知道这件事”变成“会按步骤做这件事”。
Skill 从文件到运行时的生命周期
一份 SKILL.md 并不会被 OpenClaw 原封不动全部塞进模型上下文。那样做很容易浪费 token,也会让系统提示词变得又长又乱。OpenClaw 采用的是渐进式披露:先只把 Skill 摘要暴露给模型,真正命中后再读取完整正文。
生命周期可以拆成 6 个环节。
flowchart TD
A[Discovery<br/>扫描 Skill 文件] --> B[Parse<br/>解析 Front Matter]
B --> C[Filter<br/>检查禁用状态、依赖和系统兼容性]
C --> D[Inject<br/>把 Skill 摘要注入系统提示词]
D --> E[Runtime<br/>用户请求命中后读取完整 SKILL.md]
W[Watch<br/>监听文件变化] --> A
E --> R[执行命令、查询数据或调用工具]
| 阶段 | 作用 | 典型产物 |
|---|---|---|
| Discovery | 扫描多个 Skill 目录,找到所有 SKILL.md,并按优先级合并 | Skill[] |
| Parse | 读取 YAML 元信息,提取名称、描述、依赖、适用场景 | SkillEntry |
| Filter | 检查用户是否禁用、依赖命令是否存在、系统是否兼容 | 可用的 SkillEntry[] |
| Inject | 只把名称和摘要压缩后注入系统提示词 | SkillSnapshot |
| Runtime | 用户请求命中 Skill 后,再读取完整正文并执行 | 命令、脚本、分析结果 |
| Watch | 监听文件变更,保存后自动让快照失效 | 新版本 SkillSnapshot |
这条链路里有三个关键对象:
SKILL.md
↓ Discovery
Skill[]
↓ Parse / Filter
SkillEntry[]
↓ Inject
SkillSnapshot
↓ Runtime
完整 Skill 正文
这种设计的好处很直接:系统里可以有几十个 Skill,但模型平时只看到每个 Skill 的简短说明;当某个请求确实需要某个 Skill 时,再把完整操作手册拿出来执行。
为什么电商预测需要 DuckDB 分析型实例
电商业务库一般适合在线事务处理(Online Transaction Processing,OLTP),例如写入订单、查询用户最近购买记录、修改库存状态。这类场景强调低延迟、高并发和事务一致性。
销售预测更接近在线分析处理(Online Analytical Processing,OLAP)。它关心的是过去一段时间的整体趋势,比如:
- 最近 90 天每天有多少 PV(页面浏览量);
- 每个品类的购买量和转化率怎么变化;
- 黑色星期五、圣诞节、周末等时间点是否影响购买;
- 新增数据后,模型误差是否下降。
这类问题经常需要扫描大量历史记录,并按时间、品类、品牌、事件类型做聚合。事务库不是不能查,而是不适合长期承担这种查询压力。
| 对比项 | 事务型 MySQL | DuckDB 分析型实例 |
|---|---|---|
| 主要目标 | 写订单、改状态、查单条记录 | 扫描历史数据、聚合统计、分析趋势 |
| 数据组织方式 | 更适合按行读写 | 更适合列式扫描与聚合 |
| 典型查询 | 查询某个用户订单 | 统计 7 个月全站 PV、UV、购买量 |
| 对业务影响 | 大查询可能占用线上资源 | 分析负载与交易负载隔离 |
| 适合任务 | 交易系统 | 报表、特征工程、模型训练数据准备 |
在电商预测系统中,DuckDB 的位置不是替代业务库,而是承担分析查询。OpenClaw Skill 需要频繁执行聚合 SQL,把原始事件变成模型可用的时间序列特征。如果每次查询都要等几个小时,模型迭代会非常慢;如果分析查询能在秒级返回,训练、验证、调参就可以形成闭环。
数据集与整体任务
示例任务使用公开数据集 eCommerce behavior data from multi category store。数据集中包含多个月的用户行为事件,常见字段包括:
| 字段 | 含义 |
|---|---|
event_time | 事件发生时间 |
event_type | 行为类型,例如 view、cart、purchase |
product_id | 商品 ID |
category_id | 品类 ID |
category_code | 品类路径 |
brand | 品牌 |
price | 价格 |
user_id | 用户 ID |
user_session | 会话 ID |
预测目标可以按天聚合成时间序列:
| 指标 | 含义 |
|---|---|
| PV | 页面浏览量,通常对应 event_type = 'view' |
| UV | 独立访客数,通常按 user_id 去重 |
| Purchase | 购买事件数量 |
| Cart | 加购事件数量 |
原始事件表可以先导入 DuckDB 分析型实例,再通过 SQL 生成每日特征。
CREATE TABLE user_events (
event_time DATETIME,
event_type VARCHAR(32),
product_id BIGINT,
category_id BIGINT,
category_code VARCHAR(255),
brand VARCHAR(255),
price DOUBLE,
user_id BIGINT,
user_session VARCHAR(255)
);
导入 CSV 时,可以使用 MySQL 生态工具的 LOAD DATA 能力。实际路径和连接参数需要替换成自己的环境。
LOAD DATA LOCAL INFILE '/data/2019-Oct.csv'
INTO TABLE user_events
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES
(event_time, event_type, product_id, category_id, category_code, brand, price, user_id, user_session);
生成每日 PV、UV、购买量和加购量的 SQL 可以这样写:
SELECT
DATE(event_time) AS dt,
SUM(CASE WHEN event_type = 'view' THEN 1 ELSE 0 END) AS pv,
COUNT(DISTINCT user_id) AS uv,
SUM(CASE WHEN event_type = 'purchase' THEN 1 ELSE 0 END) AS purchase_cnt,
SUM(CASE WHEN event_type = 'cart' THEN 1 ELSE 0 END) AS cart_cnt
FROM user_events
GROUP BY DATE(event_time)
ORDER BY dt;
这一步是后续建模的基础。模型不直接吃原始明细事件,而是吃按天聚合后的时间序列特征。
ecommerce-predictor Skill 应该怎么写
电商预测 Skill 至少需要描述 5 件事:
- 如何连接 DuckDB 分析型实例;
- 如何从事件表生成训练数据;
- 如何构造时间序列特征;
- 如何训练多个候选模型并选择效果较好的模型;
- 如何保存验证结果,供后续版本对比。
SKILL.md 的元信息可以这样组织:
---
name: ecommerce-predictor
description: "电商用户行为时间序列预测。用于预测 PV、UV、购买量、加购量,并对模型版本进行验证和追踪。Use when: 预测、时间序列、销量预测、电商趋势分析。NOT for: 实时风控、非时序分类问题。"
metadata:
OpenClaw:
emoji: "📈"
requires:
bins:
- python3
---
正文部分要把执行步骤写清楚,例如:
# ecommerce-predictor
## 目标
连接 RDS DuckDB 分析型实例,读取电商行为数据,按天聚合 PV、UV、Purchase、Cart,
基于历史数据训练时间序列预测模型,并用下一个月的真实数据验证误差。
## 执行流程
1. 检查 Python 环境和依赖。
2. 连接 DuckDB 分析型实例。
3. 查询每日聚合指标。
4. 构造日期、星期、月份、节假日、滞后项、滚动均值等特征。
5. 使用多个模型训练。
6. 使用下月真实数据验证。
7. 记录 MAPE、R²、最佳模型、训练时间范围。
8. 更新 HISTORY.md 和 validation_history.json。
用户调用时,可以用 OpenClaw 的 Skill 命令触发:
/skill ecommerce-predictor 预测下个月的 PV、UV、Purchase、Cart
Skill 的职责不是在一条提示词里“猜答案”,而是让 OpenClaw 进入固定流程:查数据、跑脚本、训练模型、验证结果、写入记录。
“自动进化”不是魔法,而是一个训练闭环
电商预测模型的自动迭代可以拆成四个机制:滚动式训练、自动验证、历史追踪、自动模型选择。
flowchart TD
A[导入新月份数据] --> B[扩大训练区间]
B --> C[生成特征]
C --> D[训练多个候选模型]
D --> E[用下月真实数据验证]
E --> F[计算 MAPE / R²]
F --> G[选择当前版本最佳模型]
G --> H[写入 validation_history.json]
H --> A
滚动式训练:训练集随时间向前扩展
滚动式训练的核心做法是:训练起点固定,训练终点逐月向后移动。
# 第 1 轮
train_start = "2019-10-01"
train_end = "2019-11-01"
val_start = "2019-11-01"
val_end = "2019-12-01"
# 第 2 轮
train_start = "2019-10-01"
train_end = "2019-12-01"
val_start = "2019-12-01"
val_end = "2020-01-01"
这样做有几个实际作用:
| 作用 | 解释 |
|---|---|
| 保留历史规律 | 周末、月初月末、促销日等模式会留在训练集中 |
| 吸收新数据 | 每导入一个月,新月份数据会进入下一轮训练 |
| 便于版本对比 | 每个版本都能明确对应训练区间和验证区间 |
| 不依赖人工重跑 | Skill 可以按固定流程自动执行 |
但滚动式训练也有代价。历史数据越来越多时,每轮训练都会变慢;如果业务模式发生明显变化,过早的数据还可能拖累模型。数据规模继续扩大后,可以考虑两种改法:
| 改法 | 适合场景 |
|---|---|
| 滑动窗口 | 只保留最近 N 个月数据,适合业务变化快的场景 |
| 时间衰减权重 | 保留全部历史,但让近期数据权重更高 |
自动验证:用未来一个月真实数据检查模型
模型训练完成后,不能只看训练集表现。更合理的做法是把下一个月作为验证集,用真实数据计算预测误差。
常用指标包括 MAPE(Mean Absolute Percentage Error,平均绝对百分比误差)和 R²(决定系数)。
import numpy as np
from sklearn.metrics import r2_score
def mape(y_true, y_pred, eps=1e-8):
y_true = np.asarray(y_true)
y_pred = np.asarray(y_pred)
return np.mean(np.abs((y_true - y_pred) / np.maximum(np.abs(y_true), eps))) * 100
pv_mape = mape(y_true_pv, y_pred_pv)
purchase_mape = mape(y_true_purchase, y_pred_purchase)
pv_r2 = r2_score(y_true_pv, y_pred_pv)
MAPE 越低,代表平均百分比误差越小;R² 越接近 1,代表模型对波动的解释能力越强。
需要注意,MAPE 在真实值接近 0 时会被放大,所以购买量很小的 SKU 或冷门品类不能只看 MAPE,最好同时看 MAE(Mean Absolute Error,平均绝对误差)或 RMSE(Root Mean Squared Error,均方根误差)。
历史追踪:给每次训练留下实验记录
如果每次训练只输出一张预测表,后续很难回答这些问题:
- 哪个版本的 PV 预测最好?
- 训练数据变多后,购买量误差有没有下降?
- 哪次加入了节假日特征?
- 哪次遇到了异常外部事件?
- 当前模型是进步了,还是过拟合了?
所以 Skill 需要把每轮结果写进历史文件,例如 validation_history.json。
{
"versions": [
{
"version": "v20260309_134100",
"train_start": "2019-10-01",
"train_end": "2019-11-01",
"validation_start": "2019-11-01",
"validation_end": "2019-12-01",
"train_days": 31,
"best_model": "ExponentialSmoothing",
"pv_mape": 26.92,
"purchase_mape": 11.39,
"notes": "baseline, black_friday_error_high"
},
{
"version": "v20260309_152600",
"train_start": "2019-10-01",
"train_end": "2019-12-01",
"validation_start": "2019-12-01",
"validation_end": "2020-01-01",
"train_days": 61,
"best_model": "RandomForest",
"pv_mape": 8.15,
"purchase_mape": 23.17,
"notes": "training_data_doubled"
}
]
}
这相当于一个轻量级 MLOps 记录系统。MLOps 指机器学习工程化运维,关注模型训练、评估、部署、监控和版本管理。这里不需要完整引入 MLflow 之类的平台,也能先把关键实验信息记录下来。
自动模型选择:不同阶段选择不同模型
电商行为数据在不同阶段可能适合不同模型。训练样本少时,线性模型或指数平滑模型可能更稳;数据变多后,树模型可以捕捉更复杂的非线性关系。
可以准备一组候选模型,每次训练后按验证集指标选择。
from sklearn.linear_model import Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
models = {
"Ridge": Ridge(alpha=1.0),
"Lasso": Lasso(alpha=0.01),
"RandomForest": RandomForestRegressor(
n_estimators=200,
max_depth=8,
random_state=42
),
"GradientBoosting": GradientBoostingRegressor(
n_estimators=200,
learning_rate=0.05,
max_depth=3,
random_state=42
)
}
val_results = {}
for name, model in models.items():
model.fit(X_train, y_train_pv)
pred = model.predict(X_val)
val_results[name] = {
"pv_mape": mape(y_val_pv, pred)
}
best_model_name = min(
val_results.keys(),
key=lambda name: val_results[name]["pv_mape"]
)
如果只按 PV MAPE 选模型,可能会牺牲购买量预测效果。更稳妥的做法是设计综合评分:
score = 0.5 * pv_mape + 0.3 * purchase_mape + 0.2 * cart_mape
权重应该跟业务目标一致。广告投放更关心流量时,PV 权重可以高一些;库存和供应链更关心购买量时,Purchase 权重应该更高。
特征工程:黑五误差为什么会很高
第一次训练只用 2019 年 10 月数据,拿 2019 年 11 月验证,可能会遇到一个典型问题:黑色星期五误差很高。
原因很简单。模型只看到了基础日期特征,没有被告知 11 月 29 日是特殊促销日。对模型来说,那天只是普通星期五。真实世界里,促销活动会显著改变浏览和购买行为,模型却没有对应输入,自然很难预测。
基础特征可能只有这些:
| 特征 | 含义 |
|---|---|
day_of_week | 星期几 |
day_of_month | 每月第几天 |
month | 月份 |
is_weekend | 是否周末 |
days_since_start | 距离训练起点的天数 |
后续版本可以加入更多特征:
| 特征类型 | 示例 | 作用 |
|---|---|---|
| 节假日特征 | is_black_friday、is_thanksgiving、is_christmas | 捕捉促销和节日冲击 |
| 滞后特征 | pv_lag_1、purchase_lag_7 | 利用昨天、上周同日的数据 |
| 滚动统计 | pv_rolling_7_mean、cart_rolling_14_mean | 平滑短期波动 |
| 转化特征 | purchase_cnt / pv、cart_cnt / pv | 表达用户行为链路变化 |
| 价格特征 | 平均价格、中位数价格 | 捕捉商品价格结构变化 |
特征工程往往比模型选择更重要。模型不知道的外部事件,很难靠算法自己猜出来。
从 v1 到 v6:模型迭代结果
在前两个月数据中,第一次预测使用 2019 年 10 月训练,用 2019 年 11 月验证。结果大致如下:
| 指标 | 数值 | 说明 |
|---|---|---|
| PV MAPE | 26.92% | 页面浏览量平均绝对百分比误差 |
| 购买量 MAPE | 11.39% | 购买量预测误差 |
| 黑五误差 | 45% | 未显式建模节假日导致误差偏高 |
| 训练天数 | 31 天 | 2019-10-01 到 2019-11-01 |
| 验证天数 | 30 天 | 2019-11-01 到 2019-12-01 |
| 特征数 | 5 个 | 基础时间特征 |
| 模型 | 指数平滑 | 基线模型 |
基线模型的意义不是一次就达到最优,而是建立可比较的起点。后续每导入一个月数据,Skill 会重新聚合数据、训练模型、验证误差并更新历史记录。
| 版本 | 训练天数 | 最佳模型 | PV 误差 | 购买量误差 | 主要变化 |
|---|---|---|---|---|---|
| v1.0 | 31 天 | 指数平滑 | 26.92% | 11.39% | 基线模型,黑五误差高 |
| v2.0 | 61 天 | RandomForest | 8.15% | 23.17% | 训练数据翻倍 |
| v3.0 | 92 天 | GradientBoosting | 2.73% | 42.28% | PV 预测达到阶段性最佳 |
| v4.0 | 123 天 | GradientBoosting | 2.73% | 42.28% | 版本稳定,结果延续 |
| v5.0 | 152 天 | Ridge | 29.99% | 36.99% | 外部环境变化导致误差上升 |
| v6.0 | 183 天 | GradientBoosting | 10.03% | 12.65% | 购买量预测明显恢复 |
这组结果说明两件事。
一方面,随着训练数据从 31 天增加到 183 天,PV 预测从 26.92% 降到 10.03%,误差下降约 62.7%。节假日特征、滞后特征和滚动统计加入后,黑五这类特殊日期不再像基线版本那样难以处理。
另一方面,模型并不是每一轮、每个指标都单调变好。v3.0 的 PV 误差很低,但购买量误差很高;v5.0 遇到外部环境变化后,PV 和购买量都出现明显波动。这很符合真实业务:流量和购买不是同一个目标,突发事件也不一定能从历史数据中推断出来。
如果后续要把它用于库存、补货或营销预算,不能只看一个指标。至少要同时跟踪 PV、UV、Cart、Purchase,并为不同业务目标设置不同模型选择策略。
DuckDB 在闭环里的价值
电商预测 Skill 每轮都需要做大量查询:
- 扫描多个月原始行为明细;
- 按天聚合 PV、UV、Cart、Purchase;
- 计算品类、品牌、价格等统计特征;
- 生成训练集和验证集;
- 对比不同模型版本的结果。
这些操作都属于分析查询。示例场景中,连接 RDS DuckDB 分析型实例后,数据洞察、迭代验证、策略调整阶段的查询比普通 MySQL 实例快约数百倍。具体倍数会受数据规模、SQL 写法、实例规格和缓存状态影响,但方向很明确:分析负载不应该长期压在交易库上。
flowchart LR
A[(业务 MySQL<br/>订单与行为写入)] -->|同步/导入| B[(RDS DuckDB<br/>分析型实例)]
B --> C[聚合 SQL]
C --> D[特征表]
D --> E[模型训练]
E --> F[验证指标]
F --> G[模型历史记录]
这种架构把交易和分析分开:
| 模块 | 负责内容 |
|---|---|
| 业务 MySQL | 写入订单、用户行为、交易状态 |
| DuckDB 分析型实例 | 扫描历史数据、聚合统计、生成特征 |
| OpenClaw Skill | 编排分析流程、执行脚本、保存结果 |
| Python 模型脚本 | 训练模型、计算指标、选择版本 |
| 历史记录文件 | 保存每轮训练的参数和效果 |
上手流程
一个可运行的最小版本可以按这个顺序搭建。
1. 准备 OpenClaw 环境
需要一台可以运行 OpenClaw 的机器,并确保能访问 RDS DuckDB 分析型实例。若 OpenClaw 需要调用云上资源,还需要配置阿里云命令行工具(Command Line Interface,CLI)或对应凭证。
2. 创建 DuckDB 分析型实例
创建实例时,尽量让它和运行 OpenClaw 的云服务器(Elastic Compute Service,ECS)处于同一可用区,并使用同一个专有网络(Virtual Private Cloud,VPC)。这样可以减少网络访问问题和跨区延迟。
创建完成后,需要做三件事:
1. 创建高权限账号或具备导入、查询权限的账号
2. 创建数据库和事件表
3. 在 OpenClaw 所在机器上验证连接
3. 下载并导入电商行为数据
公开数据可以从天池或 Kaggle 获取。解压后按月份导入,例如:
2019-Oct.csv
2019-Nov.csv
2019-Dec.csv
2020-Jan.csv
2020-Feb.csv
2020-Mar.csv
2020-Apr.csv
建议先导入两个月跑通全流程,再逐月追加数据观察模型变化。
4. 安装 Python 依赖
预测脚本通常需要数据库连接库、数据处理库和机器学习库。
pip install pandas numpy scikit-learn pymysql sqlalchemy
如果要使用 XGBoost,还需要额外安装:
pip install xgboost
5. 放置 Skill 文件
把 ecommerce-predictor/SKILL.md 放到 OpenClaw 可扫描的 Skill 目录里。保存文件后,OpenClaw 的 Watch 机制会监听变更,并在下次对话时重建 Skill 快照。
6. 触发预测
/skill ecommerce-predictor 预测下个月的 PV、UV、Purchase、Cart
一次完整执行后,目录里应该能看到类似文件:
ecommerce-predictor/
├── SKILL.md
├── scripts/
│ ├── build_features.py
│ ├── train.py
│ └── validate.py
├── outputs/
│ ├── forecast_next_month.csv
│ └── validation_report.json
├── HISTORY.md
└── validation_history.json
容易踩的坑
| 问题 | 表现 | 处理方式 |
|---|---|---|
| 把分析查询直接打到业务库 | SQL 慢、影响线上交易 | 使用分析型实例承接聚合查询 |
| 只保存最终预测结果 | 无法比较版本变化 | 保存训练区间、模型、指标、特征数量 |
| 只按 PV 选择模型 | 流量准但购买量不准 | 使用综合指标或按目标分别建模 |
| 不处理节假日 | 促销日误差异常高 | 加入节假日、活动日、周末等特征 |
| 历史数据无限增长 | 每轮训练越来越慢 | 使用滑动窗口或时间衰减 |
| MAPE 被小真实值放大 | 冷门商品误差看起来很夸张 | 同时观察 MAE、RMSE、样本量 |
| 突发事件无法预测 | 外部冲击期间误差升高 | 加入外部变量,或单独标记异常区间 |
| Skill 描述过长 | 系统提示词臃肿 | 摘要只写触发条件和能力边界,细节放正文 |
OpenClaw Skill 负责把流程标准化,DuckDB 分析型实例负责让历史数据查询足够快。二者结合后,电商预测就不再是一次性的 Notebook 实验,而是一个可以持续运行的闭环:新数据进入,Skill 触发聚合和训练,验证集衡量误差,历史记录沉淀经验,下一轮再基于更多数据重新选择模型。
相关资源:
- ecommerce-predictor Skill:https://github.com/huanjizhou/ecommerce-predictor
- 天池数据集:https://tianchi.aliyun.com/dataset/220316
- Kaggle 数据集:https://www.kaggle.com/datasets/mkechinov/ecommerce-behavior-data-from-multi-category-store
- RDS DuckDB 分析型实例文档:https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/duckdb-analysis-instance/