我用 Dify + EdgeOne 造一个“永不鸽 DM”的 AI 剧本杀守秘人!

文章目录
这是我用 Dify + EdgeOne Pages 在一个周末搭出来的东西。配套 Dify 工作流模板已发布到 Marketplace,前端 Next.js 代码全部开源。
写在前面:这个项目想解决一个很真实的问题
凑不齐人、DM 临时鸽、新手主持不入戏。线下不容易,线上更难。市面上的"AI 剧本杀"都是一个套路:
- AI 说你成功你就成功,说你失败你就失败,没有真正的规则
- 掉了血、SAN 减了、找到的线索,下一句对话就忘
- “凶手"完全由 LLM 临时编,前后矛盾——你说"这线索指向 A”,它就让 A 是凶手;你说"指向 B",它就让 B 是
直到我发现 Dify 的 会话变量 (Conversation Variables)——一个跨对话轮持久化、按 conversation_id 隔离的状态容器。再叠加上 EdgeOne Pages 对 Next.js 16 的一键部署,我意识到:这两个东西凑一块儿,正好可以做一个真正"规则不会被 LLM 自己打破"的 AI 守秘人。
来看几张实拍图:
欢迎页。左侧调查档案(角色卡 + HP/SAN + 线索本)实时显示,右侧守秘人对话。

开局。AI 守秘人按 1920 年代上海腔生成开场叙事,引导玩家进入剧情。

一、产品设计:把"守秘人"拆成可工程化的东西
在动手之前,先把"AI 守秘人到底要做什么"定义清楚。这一步想透了,后面的工作流就只是把它翻译成节点。
1.1 一局完整体验长这样
玩家 →「开始游戏」
守秘人 → 开场叙事 + 抽取角色卡(林婉宁,6 项属性)
+ 介绍场景(雨夜被困、三楼闷响、5 个可调查场景)
玩家 →「搜查 312 房」
守秘人 → 【POW 侦查】D100 = 37 vs 70 → 困难成功
+ 描述发现(公开线索 + 解锁隐藏线索)
+ "你打算怎么做?"
玩家 →「盘问陈守业」
守秘人 → 【POW 话术】D100 = 81 vs 70 → 失败
+ 陈守业以"早就下班了"敷衍
+ "你还要做什么?"
…(玩家继续调查多轮,收集证据)…
玩家 →「我认为凶手是陈守业,因为保险柜 24:00 有访问、楼梯间有咖啡渍脚印」
守秘人 → 〔证据链已凑齐 2 条〕
+ 揭晓真相 + 盛大复盘
注意几个关键设计:
- 判定真实:D100 是真的(伪)随机数,不是 LLM 自己说成功就成功
- 状态真实:HP/SAN/线索本会显示在侧边栏,玩家能实时看到
- 后果真实:失败不是"你失败了"四个字,而是世界发生变化——线索不解锁、SAN 可能下降
1.2 把这些落到 Dify Chatflow 上
我们需要一个 Chatflow(不是 Workflow,因为要多轮对话、要"记得"前面的线索)。内部结构最简版本:
开始(用户输入)
↓
守秘人 LLM(GLM-4-Flash,附带完整规则)
↓
直接回复(流式输出)
很多人会担心"单 LLM 节点会不会太弱"——其实关键在于 System Prompt 是不是真的把规则写死。后面我会贴完整的 Prompt,3200 字,每一段都是经过调试踩坑反复打磨过的。
模型选型:
| 用途 | 推荐模型 | 理由 |
|---|---|---|
| 守秘人叙事(核心) | GLM-4-Flash | 智谱 BigModel 完全免费、不限量,中文场景强 |
| 备选 | DeepSeek-Chat | 推理稍强,但 2025 末起需付费 |
| 备选 | Claude / GPT-4o-mini | 改写笔法最佳,但要科学上网 |
智谱 GLM-4-Flash 的免费额度让这个项目可以零成本运行,对独立开发者非常友好。
二、第一步:创建 Chatflow 应用(5 分钟)
打开 Dify Cloud,新用户注册会送一些通用模型试用。
Step 1:工作室 → 创建空白应用 → 选 Chatflow
Step 2:填写元信息
- 应用名称:
AI 剧本杀主持人·《隐秘饭店》
Step 3:选择应用类型 Chatflow,点击【创建】
Dify 应用创建完成
进入工作流编辑器后,你会看到默认结构是 开始 → LLM → 直接回复。下一步我们把它改造成守秘人。
三、第二步:装智谱插件并配置免费模型
智谱 BigModel 的 GLM-4-Flash 是这个项目能"零成本运行"的关键。
Step 1:打开 open.bigmodel.cn → 注册 → 控制台 → API Keys → 创建 → 复制 Key
Step 2:在 Dify 中安装智谱插件
- 顶部「插件」 → 探索 Marketplace
- 搜索
ZHIPU AI或直接访问marketplace.dify.ai/plugin/langgenius/zhipuai - 点击 Install,跳回 Dify 完成安装
Step 3:账户 → 设置 → 模型供应商 → 找到 ZHIPU AI → 添加 API Key → 粘贴你刚才复制的 Key → 保存
回到工作流编辑器,点击 LLM 节点,模型下拉就会出现 glm-4-flash。
在 LLM 节点选择 glm-4-flash 模型
四、第三步:守秘人 Prompt(最关键的一步)
这是项目的灵魂。整个游戏的剧本、规则、判定、铁律全部塞在这里。
完整 Prompt 太长,我拆成几段贴出来(可以直接复制到你的 Dify LLM 节点的 SYSTEM 框):
4.1 角色定位 & 剧本背景
你是民国剧本杀《隐秘饭店》的 AI 守秘人(Keeper)。
任务:营造氛围、推进剧情、引导玩家。
# 剧本背景
1923 年 10 月 14 日深夜,雨夜,上海法租界霞飞路 · 航竹饭店。
23:55 三楼 312 房传出闷响,沪上实业家黄景昌被发现死于书桌前,胸口插着象牙柄匕首。
暴雨封路,电话线断,无人能离开。
4.2 玩家角色卡(固定,开局即生效)
# 玩家身份
姓名:林婉宁,27 岁,《申报》社会版记者(暗中是地下党联络员)
属性:STR 45 / DEX 60 / INT 80 / POW 70 / CON 50 / EDU 75
状态:HP 60 / SAN 60
4.3 4 名嫌疑人 NPC 数据库
## 1. 张厚德 男 48 岁,住 308 房
- 公开:黄的多年生意伙伴
- 秘密:被黄挪用百万资金;曾扬言"让黄家断子绝孙"
- 动向:21-22 点在酒吧;之后自称在房间,无证人
- 可拷问:酒保实证他 22:00 已离开酒吧
## 2. 苏曼云 女 35 岁,舞女
- 公开:百乐门头牌,为黄晚宴伴舞
- 秘密:怀有黄的孩子两个月,今晚来索要抚养费
- 动向:23:00 进 312,23:30 离开;隔壁 311 客人听到激烈争吵
## 3. 陈守业 男 55 岁,饭店总经理 ★【真凶】★
- 公开:航竹饭店元老
- 秘密:早年豪赌输给黄三成股份;今晚黄要他次日签字交出
- 真实作案:23:50—23:54 用万能钥匙开门 → 拆信匕首一击致命 → 开保险柜取走借据 → 锁门离开
- 关键证据:保险柜 24:00 整有访问记录;便鞋鞋底沾有 312 曼特宁咖啡渍
## 4. 小李 男 22 岁,客房服务员
- 公开:负责三楼
- 秘密:黄景昌私生子,今晚找父亲对峙被拒认
- 动向:23:00 送咖啡到 312(未饮);23:55 发现尸体并报警
4.4 守秘人专属真相(严禁泄露)
# 守秘人专属真相
真凶 = 陈守业。作案时间窗:23:30—23:55。
决定性证据(玩家凑齐 ≥ 2 条方可正确指认):
A. 保险柜 24:00 整访问日志
B. 楼梯间陈守业便鞋脚印含 312 咖啡渍
C. 312 窗帘湿但窗户紧闭(排除翻窗逃逸)
D. 小李 23:00 送咖啡证词(压缩作案时间窗)
4.5 D100 真随机判定(最核心的规则)
# D100 真随机判定(伪掷骰)
玩家做调查/盘问/潜入/推理时:
1. 选属性:搜查/侦查/盘问用 POW=70;潜行/锁匠用 DEX=60;推理用 INT=80
2. 心算一个【尽量随机】的 1-100 整数(有时给低分有时给高分,避免每次都成功)
3. 判定:
- 骰子 ≤ 属性/5 → 极难成功(解锁所有隐藏 + 额外奖励)
- 属性/5 < 骰子 ≤ 属性/2 → 困难成功(解锁隐藏线索)
- 属性/2 < 骰子 ≤ 属性 → 常规成功(解锁公开线索)
- 属性 < 骰子 < 96 → 失败(仅给场景描述)
- 骰子 ≥ 96 → 大失败(误导 + 损失 1 SAN)
4. 报告格式必须以这一行开头:
【POW 侦查】D100 = 37 vs 70 → 困难成功
然后再描述发现。
4.6 守秘人铁律(划重点)
# 守秘人铁律
1. 严禁直接说"凶手是陈守业",让玩家通过证据推理
2. 每段叙事 ≤ 250 字,节奏紧凑,1920 年代上海腔
3. 冷峻、潮湿、宿命感的基调
4. 结尾用"你打算怎么做?"或开放式选择引导
5. 状态查询("我的线索/角色卡"):列表化复述已发现内容
6. 玩家说"开始游戏"/"重新开始"时:开场叙事(角色卡 + 雨夜被困 + 5 个可调查场景)
7. 玩家指认凶手时:
- 指陈守业且证据 ≥ 2 条决定性 → 揭晓真相 + 盛大复盘
- 指陈守业但证据不足 → 反问关键证据,不告诉对错
- 指其他人 → 严肃反问,给该嫌疑人辩护
8. 永远记住已经发现的线索,不要重复给

LLM 节点的 SYSTEM 框
五、第四步:发布 + 拿 API Key
Step 1:右上角【发布】 → 【发布更新】
Step 2:顶部【访问 API】标签 → 右上角【API 密钥】 → 创建 → 复制 app-xxxxxxxx
Dify API 密钥
六、第五步:Next.js 16 前端接入
6.1 项目骨架
npx create-next-app@latest dify-noir-detective \
--typescript --tailwind --app --src-dir --eslint --use-npm
cd dify-noir-detective
npx shadcn@latest init -y -d
npx shadcn@latest add card button input textarea badge progress tabs separator alert sonner skeleton -y
npm install lucide-react react-markdown remark-gfm
6.2 三大 UI 元素
┌─────── 调查档案 ────────┬──────── 守秘人对话 ────────┐
│ 角色卡:林婉宁 │ 守秘人: │
│ STR 45 DEX 60 INT 80 │ 雨夜,霞飞路… │
│ POW 70 CON 50 EDU 75 │ │
│ HP ████░ 60/60 │ 玩家: 搜查 312 房 │
│ SAN ████░ 60/60 │ │
│ │ 守秘人: │
│ 线索本 (3): │ 【POW 侦查】D100=37→困难成功 │
│ 1. 象牙柄匕首 │ 你注意到窗帘湿透了… │
│ 2. 空保险柜 │ 你打算怎么做? │
│ 3. 窗帘湿但窗紧闭 │ │
└─────────────────────────┴────────────────────────────┘
[开始游戏] [搜查 312] [盘问 X] [我的线索]
[输入框…] [发送]
6.3 关键实现一:API Key 注入在边缘
Dify 的 API Key 一旦泄露到客户端,别人就能刷你的 token。所以前端永远只调同源 /api/dify/chat,Authorization header 在 Edge Runtime 函数里注入:
// src/app/api/dify/chat/route.ts
import { DIFY_API_BASE_URL, DIFY_API_KEY, DIFY_END_USER } from "@/lib/dify";
export const runtime = "edge";
export async function POST(request: Request) {
if (!DIFY_API_KEY) {
return Response.json({ error: "DIFY_API_KEY 未配置" }, { status: 500 });
}
const { query, conversation_id } = await request.json();
const upstream = await fetch(`${DIFY_API_BASE_URL}/chat-messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${DIFY_API_KEY}`, // ← 只在边缘函数里出现
"Content-Type": "application/json",
},
body: JSON.stringify({
inputs: {},
query,
response_mode: "streaming",
conversation_id,
user: DIFY_END_USER,
}),
});
return new Response(upstream.body, {
headers: {
"content-type": "text/event-stream; charset=utf-8",
"cache-control": "no-cache, no-transform",
"x-accel-buffering": "no",
},
});
}
runtime = 'edge' 让这个路由自动部署为 EdgeOne 边缘函数,国内首字节 < 200ms。
6.4 关键实现二:SSE 流式解析 + 节点徽章
Dify Chatflow 的 SSE 流里有这些事件:
workflow_started ← 工作流开始
node_started ← 节点开始(守秘人 LLM)
message ← LLM token 流式输出 ×N
message_end ← 拿到 conversation_id
node_finished ← 节点完成
workflow_finished
前端用 ReadableStream.getReader() 按 \n\n 切事件:
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
const handleEvent = (evt: Record<string, unknown>) => {
if (evt.event === "node_started") {
setCurrentNodes((prev) => [...prev, { id, title, status: "running" }]);
} else if (evt.event === "message") {
const answer = (evt.answer as string) || "";
// 把 token 追加到最后一条 assistant 消息
setMessages((prev) => prev.map((m) =>
m.id === assistantId ? { ...m, content: m.content + answer } : m,
));
} else if (evt.event === "message_end") {
setConversationId(evt.conversation_id as string);
}
};
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let idx: number;
while ((idx = buffer.indexOf("\n\n")) !== -1) {
const raw = buffer.slice(0, idx).trim();
buffer = buffer.slice(idx + 2);
if (!raw.startsWith("data:")) continue;
try {
handleEvent(JSON.parse(raw.slice(5).trim()));
} catch {
// ignore
}
}
}
node_started / node_finished 事件被映射成 UI 上的小徽章——"守秘人正在调阅档案…"下面会看到当前节点亮起,像在看守秘人脑子转动。
6.5 关键实现三:会话变量同步到侧边栏
侧边栏显示的"角色卡 + HP/SAN + 线索本"不是前端自己维护的,而是每次 LLM 流式结束后,调用 Dify 的 /conversations/{id}/variables 接口拉取的——这意味着即使关掉浏览器再打开(带 conversation_id),状态也不会丢。
// src/app/api/dify/variables/route.ts
export const runtime = "edge";
export async function GET(request: Request) {
const url = new URL(request.url);
const conversationId = url.searchParams.get("conversation_id");
const res = await fetch(
`${DIFY_API_BASE_URL}/conversations/${conversationId}/variables` +
`?user=${encodeURIComponent(DIFY_END_USER)}&limit=20`,
{ headers: { Authorization: `Bearer ${DIFY_API_KEY}` } },
);
return new Response(await res.text(), {
status: res.status,
headers: { "content-type": "application/json" },
});
}
本期 demo 用的是单 LLM 节点,所以会话变量是空的。完整版项目里会用 Code 节点 + 变量赋值节点把角色卡、HP/SAN、线索本写入会话变量——这部分作为 Phase 2 改进项保留。即使是单 LLM 版本,因为 Dify Chatflow 默认会保留对话历史(窗口 = 20),LLM 也能"记得"前面的线索和判定。
七、第六步:部署到 EdgeOne Pages
6.1 部署流程(保姆级体验)
- Git 关联:直接关联 GitHub 仓库,EdgeOne 自动识别 Next.js 16 框架。
- 环境隔离:在控制台注入
DIFY_API_KEY。这一步至关重要——API Key 永远不会泄露到前端,安全性拉满。 - 秒级部署:点击部署后,EdgeOne 在 133 秒内完成了构建与全球生效。

6.2 为什么必须是 EdgeRuntime?
我的 Next.js 路由采用了 export const runtime = 'edge'。配合 EdgeOne 的边缘函数能力:
- 延迟极低:流式对话(SSE)的首字节返回时间(TTFB)从 800ms 优化到了 200ms 以内。
- 全球分发:EdgeOne 节点自动处理了 SSL 证书和静态资源加速。
亲测体验:
- 零配置真的零配置:没改过
next.config.ts、没写edgeone.json - Edge Runtime 完美兼容:
runtime = "edge"的 API Route 自动落到边缘函数,国内首字节 < 200ms - 流式 SSE 稳如老狗:30+ 秒的长连接没断过
- Next.js 16 一个坑:
middleware.ts改名成了proxy.ts,照旧文档写会拿 404
踩过的坑全集
坑 1:lucide-react v1 删了所有品牌图标
import { GithubIcon } from "lucide-react" 直接报错"Export GithubIcon doesn’t exist"。新版本因法律原因删了所有 brand icons。换成 ExternalLinkIcon 或自己用 SVG。
坑 2:Tailwind v4 + Typography 插件不太稳
Markdown 渲染用 prose 类发现样式不生效。Tailwind v4 的 @plugin 配置和老版不同。最稳的方案:自己手写 .md-body 样式,可控且能精细调。
坑 3:shadcn 的 Sonner 依赖 next-themes
不主动包 <ThemeProvider> 也能跑(useTheme() 在没 provider 时返回 system 默认),但 dark mode 会失效。不需要 dark 的话默认就够用。
坑 4:Dify Cloud 上 DeepSeek 不再赠送 token
2025 年末起新用户没有赠送了。替代方案:智谱 GLM-4-Flash 完全免费、不限量,或 Groq 上跑开源模型也免费。
坑 5:Dify DSL YAML 版本兼容性
我第一版 YAML 写成 version: 0.1.5 直接导入失败,Dify 静默跳到了"创建空白应用"。后来发现 Dify 1.13 期望的是 version: 0.6.0 + 必填顶层 dependencies: [] + 完整的 features.file_upload 结构。最稳的办法:先在 Dify 上创建一个空白应用,导出 DSL 当模板,照着改。
坑 6:Next.js 16 把 middleware.ts 改名成 proxy.ts
照着旧文档写中间件会 404。Next.js 16 的所有破坏性变更,建议直接 cat node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/route.md 看本地文档。
坑 7:edge runtime 下 static generation 警告
Using edge runtime on a page currently disables static generation for that page
这个警告无害——只是说带 runtime = 'edge' 的页面(不是 API)会被禁用静态生成。我们用的是 API Route 不受影响。
坑 8:lexical 编辑器无法 fill
Dify 的 SYSTEM 框是 Lexical 编辑器(React 富文本),普通 fill 不能写进去。要么手动粘贴,要么用 Chrome DevTools MCP 的 type_text 按字符输入。
十、扩展方向
这是个单 LLM 节点版本——能跑能玩但不极致。完整工程方案的下一步:
- 加 Code 节点真随机 D100:用 Python
random.randint(1, 100),比让 LLM 心算更稳定 - 加 Question Classifier 多分支:开始/调查/盘问/指认/状态查询分流不同 prompt
- 加 8 个会话变量:character/hp/san/clues/interrogated/phase/turn/is_started,状态完全工程化
- 加 Iteration 节点投票:让 4 个 NPC 同时给出反应,更像剧本杀
- 多剧本切换:在开始时让玩家选《雾港谜案》《校园密室》等
- 多人并发:每人一个 conversation_id,按 Dify 的会话隔离机制天然支持
十一、开源 & 体验
- Dify 模板:Marketplace 搜AI 剧本杀主持人·《隐秘饭店》(Dify x EdgeOne)
十二、总结
从想法到上线,实际耗时:
- 剧本设计 ~ 1.5 小时
- Dify Chatflow 调 prompt ~ 2 小时
- 前端 Next.js + Tailwind ~ 4 小时
- EdgeOne Pages 部署 ~ 10 分钟
- 写文章 ~ 1 小时
Dify 让 AI 编排变成拖拽,EdgeOne Pages 让全栈部署变成 push 一下。两者组合,让一个"我想做"的傻气脑洞真的能在一个周末从想法变成可分享的链接。
特别想说一句:智谱 GLM-4-Flash 的"完全免费、不限量"对独立开发者是真的友好。整个项目从开发到调试到 demo,零成本。
如果你也在找一个差异化的练手项目,把守秘人换个垂直场景就是新品:
- AI 面试官(陪练 + 评分 + 改进建议)
- AI 剧本主持(《狼人杀》《天黑请闭眼》)
- AI 桌游 NPC(角色扮演 + 状态管理)
- AI 心理咨询师(情绪持久化 + 多轮跟踪)
会话变量这玩意儿,本质上是给 LLM 装了"工作记忆"。任何需要跨轮维持状态的场景,它都能派上用场。
愿你也能在自己的周末,用 Dify + EdgeOne 搭一个能让朋友"哇"出来的小玩意儿。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)