feat: 将 demo 数据迁移为 JSON 协议文件,激活 Raw Protocol 面板

- 新建 6 个 demo JSON 文件 (demo-a ~ demo-f),替代 TS 硬编码常量
- 新建 prompt-envelope.schema.json (JSON Schema Draft 2020-12)
- 新建 demos-loader.ts: validateEnvelope() 运行时验证 + hydrateSkills() 从 skills 源补全 body
- 新建 manifest.json 场景索引
- ProtocolPanel: Raw Protocol tab 已激活,可切换查看原始 PromptEnvelope JSON
- demo.ts 改为从 JSON import + loadEnvelope() 加载
- Skill body 不入协议,仅保留 name + description (L1),由加载器补全
- 时间戳改为固定 epoch 毫秒值
- 删除 6 个旧 demo-{a..f}.ts 文件

构建通过,26 个测试全部通过
This commit is contained in:
carry
2026-06-08 20:52:44 +08:00
parent 0869e5bf34
commit 742659df43
17 changed files with 1280 additions and 1391 deletions
+65 -16
View File
@@ -7,29 +7,52 @@ interface ProtocolPanelProps {
envelope: PromptEnvelope envelope: PromptEnvelope
} }
type Tab = 'openai' | 'raw'
export default function ProtocolPanel({ envelope }: ProtocolPanelProps) { export default function ProtocolPanel({ envelope }: ProtocolPanelProps) {
const [activeTab, setActiveTab] = useState<Tab>('openai')
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const openaiFormat = useMemo(() => exportToOpenAIFormat(envelope), [envelope]) const openaiFormat = useMemo(() => exportToOpenAIFormat(envelope), [envelope])
const rawProtocolJson = useMemo(() => JSON.stringify(envelope, null, 2), [envelope])
const displayedJson = activeTab === 'openai'
? JSON.stringify(openaiFormat, null, 2)
: rawProtocolJson
const downloadFilename = activeTab === 'openai'
? 'openai-export.json'
: 'prompt-envelope.json'
const handleCopy = async () => { const handleCopy = async () => {
await navigator.clipboard.writeText(JSON.stringify(openaiFormat, null, 2)) await navigator.clipboard.writeText(displayedJson)
setCopied(true) setCopied(true)
setTimeout(() => setCopied(false), 2000) setTimeout(() => setCopied(false), 2000)
} }
const handleDownload = () => { const handleDownload = () => {
const blob = new Blob([JSON.stringify(openaiFormat, null, 2)], { const blob = new Blob([displayedJson], { type: 'application/json' })
type: 'application/json',
})
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = 'openai-export.json' a.download = downloadFilename
a.click() a.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }
/** 统计 envelope 中的 segment 数量 */
const segmentStats = useMemo(() => {
const counts: Record<string, number> = {}
for (const msg of envelope.messages) {
for (const seg of msg.segments) {
counts[seg.kind] = (counts[seg.kind] || 0) + 1
}
}
return counts
}, [envelope])
const totalSegments = Object.values(segmentStats).reduce((a, b) => a + b, 0)
return ( return (
<div className="w-96 border-l border-gray-200 bg-white flex flex-col h-full"> <div className="w-96 border-l border-gray-200 bg-white flex flex-col h-full">
{/* Header */} {/* Header */}
@@ -56,27 +79,53 @@ export default function ProtocolPanel({ envelope }: ProtocolPanelProps) {
{/* Tabs */} {/* Tabs */}
<div className="flex text-xs border-b border-gray-100"> <div className="flex text-xs border-b border-gray-100">
<div className="flex-1 text-center py-2 font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50"> <button
onClick={() => setActiveTab('openai')}
className={`flex-1 text-center py-2 transition-colors cursor-pointer ${
activeTab === 'openai'
? 'font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50'
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'
}`}
>
OpenAI Format OpenAI Format
</div> </button>
<div className="flex-1 text-center py-2 text-gray-400"> <button
Raw Protocol (soon) onClick={() => setActiveTab('raw')}
</div> className={`flex-1 text-center py-2 transition-colors cursor-pointer ${
activeTab === 'raw'
? 'font-semibold text-blue-600 border-b-2 border-blue-500 bg-blue-50/50'
: 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'
}`}
>
Raw Protocol
</button>
</div> </div>
{/* JSON Content */} {/* JSON Content */}
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<pre className="p-4 text-xs font-mono text-gray-600 whitespace-pre-wrap break-all leading-relaxed"> <pre className="p-4 text-xs font-mono text-gray-600 whitespace-pre-wrap break-all leading-relaxed">
{JSON.stringify(openaiFormat, null, 2)} {displayedJson}
</pre> </pre>
</div> </div>
{/* Footer stats */} {/* Footer stats */}
<div className="px-4 py-2 border-t border-gray-100 text-[10px] text-gray-400 flex items-center gap-3"> <div className="px-4 py-2 border-t border-gray-100 text-[10px] text-gray-400 flex items-center gap-3 flex-wrap">
<span>model: {openaiFormat.model}</span> {activeTab === 'openai' ? (
<span>{envelope.messages.length} </span> <>
<span>{openaiFormat.messages.length} OpenAI messages</span> <span>model: {openaiFormat.model}</span>
{openaiFormat.tools && <span>{openaiFormat.tools.length} tools</span>} <span>{envelope.messages.length} </span>
<span>{openaiFormat.messages.length} OpenAI messages</span>
{openaiFormat.tools && <span>{openaiFormat.tools.length} tools</span>}
</>
) : (
<>
<span>version: {envelope.version}</span>
{envelope.model && <span>model: {envelope.model}</span>}
<span>{envelope.messages.length} </span>
<span>{totalSegments} segment</span>
<span>{Object.keys(segmentStats).length} </span>
</>
)}
</div> </div>
</div> </div>
) )
+33 -44
View File
@@ -1,10 +1,22 @@
import type { PromptEnvelope } from '../types/protocol' import type { PromptEnvelope } from '../types/protocol'
import { demoA } from './demos/demo-a' import { loadEnvelope } from './demos/demos-loader'
import { demoB } from './demos/demo-b'
import { demoC } from './demos/demo-c' // ---- 从 JSON 文件导入 demo 数据 ----
import { demoD } from './demos/demo-d' import demoAJson from './demos/demo-a.json'
import { demoE } from './demos/demo-e' import demoBJson from './demos/demo-b.json'
import { demoF } from './demos/demo-f' import demoCJson from './demos/demo-c.json'
import demoDJson from './demos/demo-d.json'
import demoEJson from './demos/demo-e.json'
import demoFJson from './demos/demo-f.json'
import manifestJson from './demos/manifest.json'
// ---- 运行时验证 + skill body 水合 ----
const demoA = loadEnvelope(demoAJson)
const demoB = loadEnvelope(demoBJson)
const demoC = loadEnvelope(demoCJson)
const demoD = loadEnvelope(demoDJson)
const demoE = loadEnvelope(demoEJson)
const demoF = loadEnvelope(demoFJson)
export interface DemoScenario { export interface DemoScenario {
id: string id: string
@@ -13,41 +25,18 @@ export interface DemoScenario {
envelope: PromptEnvelope envelope: PromptEnvelope
} }
export const demos: DemoScenario[] = [ const envelopeMap: Record<string, PromptEnvelope> = {
{ a: demoA,
id: 'a', b: demoB,
label: '场景 A', c: demoC,
description: '基础对话 + System Prompt + Memory', d: demoD,
envelope: demoA, e: demoE,
}, f: demoF,
{ }
id: 'b',
label: '场景 B', export const demos: DemoScenario[] = manifestJson.scenarios.map((s) => ({
description: '工具调用:请求 → 执行(成功 & 失败)', id: s.id,
envelope: demoB, label: s.label,
}, description: s.description,
{ envelope: envelopeMap[s.id],
id: 'c', }))
label: '场景 C 📄',
description: '文档解析:点击「查看解析」看 AI 如何提取文档内容',
envelope: demoC,
},
{
id: 'd',
label: '场景 D ⭐',
description: '综合:全部 9 种 Segment',
envelope: demoD,
},
{
id: 'e',
label: '场景 E 🔍',
description: '日志分析:异常检测 + 安全审计 + 性能分析',
envelope: demoE,
},
{
id: 'f',
label: '场景 F 📁',
description: '真实 Anthropic SkillsSKILL.md 文件加载 + parseSkillMarkdown 解析)',
envelope: demoF,
},
]
+109
View File
@@ -0,0 +1,109 @@
{
"version": "1.0",
"model": "gpt-4-turbo",
"messages": [
{
"id": "a-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前对话日期,注入到 System Prompt 模板中"
},
{
"kind": "static_var",
"name": "language",
"value": "中文(简体)",
"description": "模型回复的首选语言"
},
{
"kind": "static_var",
"name": "user_name",
"value": "小明",
"description": "当前用户名称"
},
{
"kind": "system_prompt",
"content": "当前日期:{{current_date}}\n用户名称:{{user_name}}\n回复语言:{{language}}\n\n你是 HCI 课程设计助手。你帮助学生对聊天界面的信息架构进行批判性思考。\n回答应简洁、有结构,鼓励学生从用户体验角度分析问题。\n如果学生对某个概念不清楚,用通俗的例子解释,不要用术语堆砌。",
"collapsed": true
},
{
"kind": "memory",
"description": "以下是从过往对话中总结的关于你的信息,模型会参考这些记忆来个性化回复。记忆随对话自动更新,你也可以手动编辑或删除。",
"items": [
{
"title": "用户背景",
"content": "设计系研二学生,正在做 HCI 课程设计,关注 LLM 交互的透明性"
},
{
"title": "偏好",
"content": "喜欢简洁的表达,反感术语堆砌。倾向用图示辅助理解。"
},
{
"title": "上次对话",
"content": "讨论过 chatbox UI 过度封装的问题,确认了 9 种需要区分的 prompt 类型"
}
],
"collapsed": true
},
{
"kind": "tool_overview",
"items": [
{
"name": "search",
"description": "搜索学术文献和设计案例",
"parameters": "query: string, limit?: number",
"schema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "搜索关键词" },
"limit": { "type": "number", "description": "返回结果数量上限" }
},
"required": ["query"]
}
},
{
"name": "get_example",
"description": "获取 UI 设计案例截图和说明",
"parameters": "topic: string, count?: number",
"schema": {
"type": "object",
"properties": {
"topic": { "type": "string", "description": "设计主题" },
"count": { "type": "number", "description": "案例数量" }
},
"required": ["topic"]
}
}
],
"collapsed": true
}
],
"timestamp": 1780898100000
},
{
"id": "a-2",
"role": "user",
"segments": [
{
"kind": "text",
"content": "你好,我想讨论一下我设计的聊天协议方案。你觉得 9 种 prompt 类型的分类合理吗?"
}
],
"timestamp": 1780898160000
},
{
"id": "a-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "我觉得你的 9 种分类很全面!从信息架构的角度看,你已经覆盖了 LLM 上下文的三个主要来源:\n\n1. **静态配置**(变量、system prompt\n2. **能力声明**skills、tools\n3. **动态输入**(文档、长文本、多模态)\n\n加上工具调用的过程性信息,形成了一个完整的上下文可见性模型。这种分类本身就值得在课程设计中展开讨论。"
}
],
"timestamp": 1780898220000
}
]
}
-121
View File
@@ -1,121 +0,0 @@
import type { PromptEnvelope } from '../../types/protocol'
const now = Date.now()
export const demoA: PromptEnvelope = {
version: '1.0',
model: 'gpt-4-turbo',
messages: [
{
id: 'a-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前对话日期,注入到 System Prompt 模板中',
},
{
kind: 'static_var',
name: 'language',
value: '中文(简体)',
description: '模型回复的首选语言',
},
{
kind: 'static_var',
name: 'user_name',
value: '小明',
description: '当前用户名称',
},
{
kind: 'system_prompt',
content: `当前日期:{{current_date}}
用户名称:{{user_name}}
回复语言:{{language}}
你是 HCI 课程设计助手。你帮助学生对聊天界面的信息架构进行批判性思考。
回答应简洁、有结构,鼓励学生从用户体验角度分析问题。
如果学生对某个概念不清楚,用通俗的例子解释,不要用术语堆砌。`,
collapsed: true,
},
{
kind: 'memory',
description:
'以下是从过往对话中总结的关于你的信息,模型会参考这些记忆来个性化回复。记忆随对话自动更新,你也可以手动编辑或删除。',
items: [
{
title: '用户背景',
content: '设计系研二学生,正在做 HCI 课程设计,关注 LLM 交互的透明性',
},
{
title: '偏好',
content: '喜欢简洁的表达,反感术语堆砌。倾向用图示辅助理解。',
},
{
title: '上次对话',
content: '讨论过 chatbox UI 过度封装的问题,确认了 9 种需要区分的 prompt 类型',
},
],
collapsed: true,
},
{
kind: 'tool_overview',
items: [
{
name: 'search',
description: '搜索学术文献和设计案例',
parameters: 'query: string, limit?: number',
schema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
limit: { type: 'number', description: '返回结果数量上限' },
},
required: ['query'],
},
},
{
name: 'get_example',
description: '获取 UI 设计案例截图和说明',
parameters: 'topic: string, count?: number',
schema: {
type: 'object',
properties: {
topic: { type: 'string', description: '设计主题' },
count: { type: 'number', description: '案例数量' },
},
required: ['topic'],
},
},
],
collapsed: true,
},
],
timestamp: now - 300000,
},
{
id: 'a-2',
role: 'user',
segments: [
{
kind: 'text',
content: '你好,我想讨论一下我设计的聊天协议方案。你觉得 9 种 prompt 类型的分类合理吗?',
},
],
timestamp: now - 240000,
},
{
id: 'a-3',
role: 'assistant',
segments: [
{
kind: 'text',
content:
'我觉得你的 9 种分类很全面!从信息架构的角度看,你已经覆盖了 LLM 上下文的三个主要来源:\n\n1. **静态配置**(变量、system prompt\n2. **能力声明**skills、tools\n3. **动态输入**(文档、长文本、多模态)\n\n加上工具调用的过程性信息,形成了一个完整的上下文可见性模型。这种分类本身就值得在课程设计中展开讨论。',
},
],
timestamp: now - 180000,
},
],
}
+140
View File
@@ -0,0 +1,140 @@
{
"version": "1.0",
"model": "gpt-4-turbo",
"messages": [
{
"id": "b-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前日期"
},
{
"kind": "static_var",
"name": "language",
"value": "中文(简体)",
"description": "模型回复的首选语言"
},
{
"kind": "system_prompt",
"content": "当前日期:{{current_date}},回复语言:{{language}}。\n\n你是一个编程助手,可以帮助用户编写、调试和优化代码。你可以在安全沙箱中执行代码,也可以查阅技术文档。",
"collapsed": true
},
{
"kind": "tool_overview",
"items": [
{
"name": "execute_code",
"description": "在安全沙箱中执行代码并返回输出",
"parameters": "language: string, code: string",
"schema": {
"type": "object",
"properties": {
"language": { "type": "string", "enum": ["python", "javascript", "typescript"], "description": "编程语言" },
"code": { "type": "string", "description": "要执行的代码" }
},
"required": ["language", "code"]
}
},
{
"name": "lookup_api",
"description": "查阅技术文档和 API 参考",
"parameters": "query: string, source: string",
"schema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "API 或函数名称" },
"source": { "type": "string", "enum": ["python", "mdn", "react", "node"], "description": "文档来源(必填)" }
},
"required": ["query", "source"]
}
}
],
"collapsed": true
}
],
"timestamp": 1780898200000
},
{
"id": "b-2",
"role": "user",
"segments": [
{
"kind": "text",
"content": "帮我写一个 Python 函数,统计文本中每个单词的出现频率,然后运行测试一下。"
}
],
"timestamp": 1780898220000
},
{
"id": "b-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "好的,我来写一个词频统计函数并用示例文本测试:"
},
{
"kind": "tool_call_request",
"toolName": "execute_code",
"arguments": {
"language": "python",
"code": "def word_frequency(text):\n \"\"\"统计文本中每个单词的出现频率,按频率降序排列\"\"\"\n import re\n words = re.findall(r'\\b\\w+\\b', text.lower())\n freq = {}\n for w in words:\n freq[w] = freq.get(w, 0) + 1\n return dict(sorted(freq.items(), key=lambda x: x[1], reverse=True))\n\nsample = \"hello world hello python world python python\"\nresult = word_frequency(sample)\nfor word, count in result.items():\n print(f\"{word}: {count}\")"
},
"collapsed": false
}
],
"timestamp": 1780898240000
},
{
"id": "b-4",
"role": "assistant",
"segments": [
{
"kind": "tool_call_result",
"toolName": "execute_code",
"success": true,
"result": "python: 3\nworld: 2\nhello: 2",
"collapsed": true
}
],
"timestamp": 1780898260000
},
{
"id": "b-5",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "运行成功!其实用 `collections.Counter` 可以让代码更简洁。让我查一下 Python 官方文档确认 Counter 的 API 细节——"
},
{
"kind": "tool_call_request",
"toolName": "lookup_api",
"arguments": {
"query": "collections.Counter most_common"
},
"collapsed": false
}
],
"timestamp": 1780898270000
},
{
"id": "b-6",
"role": "assistant",
"segments": [
{
"kind": "tool_call_result",
"toolName": "lookup_api",
"success": false,
"result": "Error: lookup_api requires \"source\" parameter (python | mdn | react | node). Please specify the documentation source and retry.",
"collapsed": false
}
],
"timestamp": 1780898280000
}
]
}
-160
View File
@@ -1,160 +0,0 @@
import type { PromptEnvelope } from '../../types/protocol'
const now = Date.now()
export const demoB: PromptEnvelope = {
version: '1.0',
model: 'gpt-4-turbo',
messages: [
{
id: 'b-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前日期',
},
{
kind: 'static_var',
name: 'language',
value: '中文(简体)',
description: '模型回复的首选语言',
},
{
kind: 'system_prompt',
content: `当前日期:{{current_date}},回复语言:{{language}}。
你是一个编程助手,可以帮助用户编写、调试和优化代码。你可以在安全沙箱中执行代码,也可以查阅技术文档。`,
collapsed: true,
},
{
kind: 'tool_overview',
items: [
{
name: 'execute_code',
description: '在安全沙箱中执行代码并返回输出',
parameters: 'language: string, code: string',
schema: {
type: 'object',
properties: {
language: { type: 'string', enum: ['python', 'javascript', 'typescript'], description: '编程语言' },
code: { type: 'string', description: '要执行的代码' },
},
required: ['language', 'code'],
},
},
{
name: 'lookup_api',
description: '查阅技术文档和 API 参考',
parameters: 'query: string, source: string',
schema: {
type: 'object',
properties: {
query: { type: 'string', description: 'API 或函数名称' },
source: { type: 'string', enum: ['python', 'mdn', 'react', 'node'], description: '文档来源(必填)' },
},
required: ['query', 'source'],
},
},
],
collapsed: true,
},
],
timestamp: now - 200000,
},
{
id: 'b-2',
role: 'user',
segments: [
{
kind: 'text',
content: '帮我写一个 Python 函数,统计文本中每个单词的出现频率,然后运行测试一下。',
},
],
timestamp: now - 180000,
},
{
id: 'b-3',
role: 'assistant',
segments: [
{
kind: 'text',
content: '好的,我来写一个词频统计函数并用示例文本测试:',
},
{
kind: 'tool_call_request',
toolName: 'execute_code',
arguments: {
language: 'python',
code: `def word_frequency(text):
"""统计文本中每个单词的出现频率,按频率降序排列"""
import re
words = re.findall(r'\\b\\w+\\b', text.lower())
freq = {}
for w in words:
freq[w] = freq.get(w, 0) + 1
return dict(sorted(freq.items(), key=lambda x: x[1], reverse=True))
sample = "hello world hello python world python python"
result = word_frequency(sample)
for word, count in result.items():
print(f"{word}: {count}")`,
},
collapsed: false,
},
],
timestamp: now - 160000,
},
{
id: 'b-4',
role: 'assistant',
segments: [
{
kind: 'tool_call_result',
toolName: 'execute_code',
success: true,
result: `python: 3
world: 2
hello: 2`,
collapsed: true,
},
],
timestamp: now - 140000,
},
{
id: 'b-5',
role: 'assistant',
segments: [
{
kind: 'text',
content: '运行成功!其实用 `collections.Counter` 可以让代码更简洁。让我查一下 Python 官方文档确认 Counter 的 API 细节——',
},
{
kind: 'tool_call_request',
toolName: 'lookup_api',
arguments: {
query: 'collections.Counter most_common',
},
collapsed: false,
},
],
timestamp: now - 130000,
},
{
id: 'b-6',
role: 'assistant',
segments: [
{
kind: 'tool_call_result',
toolName: 'lookup_api',
success: false,
result: 'Error: lookup_api requires "source" parameter (python | mdn | react | node). Please specify the documentation source and retry.',
collapsed: false,
},
],
timestamp: now - 120000,
},
],
}
+70
View File
@@ -0,0 +1,70 @@
{
"version": "1.0",
"model": "gpt-4-turbo",
"messages": [
{
"id": "c-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前日期"
},
{
"kind": "static_var",
"name": "language",
"value": "中文(简体)",
"description": "文档审阅的默认输出语言"
},
{
"kind": "system_prompt",
"content": "当前日期:{{current_date}},回复语言:{{language}}。\n\n你是文档解析助手。帮助用户分析上传的文档、提取关键信息、回答关于文档内容的问题。\n\n解析文档后,你会输出结构化的解析结果,并在回复中注明关键发现。",
"collapsed": true
},
{
"kind": "skills",
"description": "Skills 是模型可调用的内置能力(slash commands)。当前对话中启用以下 skills",
"items": [
{ "name": "summarize", "description": "生成文档摘要 — 支持多种粒度(一句话/段落级/全文级)" },
{ "name": "translate", "description": "翻译文档内容 — 支持中英互译,保留原文格式" },
{ "name": "qa", "description": "基于文档回答具体问题 — 带引用溯源" }
],
"collapsed": true
}
],
"timestamp": 1780898250000
},
{
"id": "c-2",
"role": "user",
"segments": [
{
"kind": "text",
"content": "帮我解析这份市场研究报告,看看有什么关键发现。"
},
{
"kind": "document",
"fileName": "2026-Q2-智能助手市场报告.pdf",
"mimeType": "application/pdf",
"sizeBytes": 2845000,
"snippet": "# 2026 年 Q2 智能助手市场研究报告\n\n## 摘要\n\n2026 年 Q2 全球智能助手市场规模预计达到 187 亿美元,同比增长 34.2%。主要增长驱动力包括企业级 AI 助手采纳率提升…",
"parsedContent": "## 文档概览\n\n| 字段 | 内容 |\n|------|------|\n| 标题 | 2026 年 Q2 智能助手市场研究报告 |\n| 作者 | 市场分析部 · 陈敏 |\n| 日期 | 2026 年 5 月 28 日 |\n| 页数 | 42 页 |\n| 语言 | 中文(简体) |\n\n## 核心发现\n\n### 1. 市场规模\n2026 年 Q2 全球智能助手市场规模预计达到 **187 亿美元**,同比增长 **34.2%**。\n\n主要增长驱动力:\n- 企业级 AI 助手采纳率从 2025 年的 38% 提升至 56%\n- 多模态能力(图像/语音/代码)推动使用场景扩展\n- 开源模型的成熟降低了部署门槛\n\n### 2. 竞争格局\n\n| 厂商 | 市场份额 | QoQ 变化 | 核心优势 |\n|------|---------|---------|---------|\n| OpenAI | 41% | +2.3% | 品牌认知度 + 开发者生态 |\n| Anthropic | 18% | +5.1% | 安全对齐 + 长上下文 |\n| Google | 15% | -1.2% | 搜索整合 + 多模态 |\n| Meta | 12% | +3.8% | 开源策略 + 成本优势 |\n| 其他 | 14% | — | 垂直领域玩家 |\n\n### 3. 用户需求变化\n- **可信度**成为用户选择助手的第一要素(47% 受访者提及)\n- **上下文窗口大小**的优先级从 2025 年的第 7 位跃升至第 3 位\n- 企业用户对 `system prompt` 透明度的需求同比增长 **82%**\n- 工具调用(Function Calling)的使用率从 23% 增长到 51%\n\n### 4. 关键趋势\n> \"智能助手的竞争从模型能力转向体验设计。用户不再只关心'能不能答对',更关心'我能不能理解你为什么这么回答'。\"——报告结语"
}
],
"timestamp": 1780898280000
},
{
"id": "c-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "文档已解析完成。以下是报告中的关键发现:\n\n---\n\n## 📊 核心发现\n\n### 1. 市场规模与增长\n2026 年 Q2 全球智能助手市场规模预计达到 **187 亿美元**,同比增长 **34.2%**。\n\n主要驱动力:\n- 企业级 AI 助手采纳率从 38% 提升至 **56%**\n- 多模态能力推动使用场景扩展\n- 开源模型降低了部署门槛\n\n### 2. 竞争格局变化\n\n| 厂商 | 份额 | 环比变化 | 核心优势 |\n|------|------|---------|---------|\n| OpenAI | 41% | +2.3% | 品牌 + 开发者生态 |\n| Anthropic | 18% | **+5.1%** | 安全对齐 + 长上下文 |\n| Google | 15% | -1.2% | 搜索整合 + 多模态 |\n| Meta | 12% | +3.8% | 开源 + 成本优势 |\n\nAnthropic 的增长最快(+5.1%),说明 **安全对齐** 正在成为关键竞争力。\n\n### 3. 值得关注的趋势\n\n- **可信度** 是用户选择助手的首要因素(47% 受访者提及)\n- 企业用户对 `system prompt` 透明度的需求同比增长 **82%**\n- 工具调用(Function Calling)使用率从 23% 增长到 51%\n- 报告结语指出:**竞争正从模型能力转向体验设计**\n\n---\n\n需要我深入分析某个方面吗?点击文档的 **「查看解析」** 按钮可以看到 AI 提取的完整结构化内容。"
}
],
"timestamp": 1780898340000
}
]
}
-166
View File
@@ -1,166 +0,0 @@
/**
* Demo C — 文档解析(核心展示)
*
* 场景:用户上传多份文档请求 AI 进行解析,展示文档上传后 AI 如何
* 提取、结构化理解其中的内容。
*
* 关键展示:
* 1. 一份 PDF 文档,右上角有「查看解析」按钮
* 2. 点击后展开显示 AI 对文档的解析结果(Markdown 渲染)
* 3. Assistant 基于解析结果给出分析回答
* 4. 无 long_text / media / 多文档干扰 —— 聚焦单文档解析体验
*/
import type { PromptEnvelope } from '../../types/protocol'
import { getSkills } from '../skills'
const now = Date.now()
/** PDF 市场调研报告的解析结果 */
const marketReportParsed = `## 文档概览
| 字段 | 内容 |
|------|------|
| 标题 | 2026 年 Q2 智能助手市场研究报告 |
| 作者 | 市场分析部 · 陈敏 |
| 日期 | 2026 年 5 月 28 日 |
| 页数 | 42 页 |
| 语言 | 中文(简体) |
## 核心发现
### 1. 市场规模
2026 年 Q2 全球智能助手市场规模预计达到 **187 亿美元**,同比增长 **34.2%**。
主要增长驱动力:
- 企业级 AI 助手采纳率从 2025 年的 38% 提升至 56%
- 多模态能力(图像/语音/代码)推动使用场景扩展
- 开源模型的成熟降低了部署门槛
### 2. 竞争格局
| 厂商 | 市场份额 | QoQ 变化 | 核心优势 |
|------|---------|---------|---------|
| OpenAI | 41% | +2.3% | 品牌认知度 + 开发者生态 |
| Anthropic | 18% | +5.1% | 安全对齐 + 长上下文 |
| Google | 15% | -1.2% | 搜索整合 + 多模态 |
| Meta | 12% | +3.8% | 开源策略 + 成本优势 |
| 其他 | 14% | — | 垂直领域玩家 |
### 3. 用户需求变化
- **可信度**成为用户选择助手的第一要素(47% 受访者提及)
- **上下文窗口大小**的优先级从 2025 年的第 7 位跃升至第 3 位
- 企业用户对 \`system prompt\` 透明度的需求同比增长 **82%**
- 工具调用(Function Calling)的使用率从 23% 增长到 51%
### 4. 关键趋势
> "智能助手的竞争从模型能力转向体验设计。用户不再只关心'能不能答对',更关心'我能不能理解你为什么这么回答'。"——报告结语`
export const demoC: PromptEnvelope = {
version: '1.0',
model: 'gpt-4-turbo',
messages: [
// --- System message ---
{
id: 'c-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前日期',
},
{
kind: 'static_var',
name: 'language',
value: '中文(简体)',
description: '文档审阅的默认输出语言',
},
{
kind: 'system_prompt',
content: `当前日期:{{current_date}},回复语言:{{language}}。
你是文档解析助手。帮助用户分析上传的文档、提取关键信息、回答关于文档内容的问题。
解析文档后,你会输出结构化的解析结果,并在回复中注明关键发现。`,
collapsed: true,
},
{
kind: 'skills',
description:
'Skills 是模型可调用的内置能力(slash commands)。当前对话中启用以下 skills',
items: getSkills(['summarize', 'translate', 'qa']),
collapsed: true,
},
],
timestamp: now - 150000,
},
// --- User 上传文档请求解析 ---
{
id: 'c-2',
role: 'user',
segments: [
{
kind: 'text',
content: '帮我解析这份市场研究报告,看看有什么关键发现。',
},
{
kind: 'document',
fileName: '2026-Q2-智能助手市场报告.pdf',
mimeType: 'application/pdf',
sizeBytes: 2_845_000,
snippet: '# 2026 年 Q2 智能助手市场研究报告\n\n## 摘要\n\n2026 年 Q2 全球智能助手市场规模预计达到 187 亿美元,同比增长 34.2%。主要增长驱动力包括企业级 AI 助手采纳率提升…',
parsedContent: marketReportParsed,
},
],
timestamp: now - 120000,
},
// --- Assistant 综合回复 ---
{
id: 'c-3',
role: 'assistant',
segments: [
{
kind: 'text',
content: `文档已解析完成。以下是报告中的关键发现:
---
## 📊 核心发现
### 1. 市场规模与增长
2026 年 Q2 全球智能助手市场规模预计达到 **187 亿美元**,同比增长 **34.2%**。
主要驱动力:
- 企业级 AI 助手采纳率从 38% 提升至 **56%**
- 多模态能力推动使用场景扩展
- 开源模型降低了部署门槛
### 2. 竞争格局变化
| 厂商 | 份额 | 环比变化 | 核心优势 |
|------|------|---------|---------|
| OpenAI | 41% | +2.3% | 品牌 + 开发者生态 |
| Anthropic | 18% | **+5.1%** | 安全对齐 + 长上下文 |
| Google | 15% | -1.2% | 搜索整合 + 多模态 |
| Meta | 12% | +3.8% | 开源 + 成本优势 |
Anthropic 的增长最快(+5.1%),说明 **安全对齐** 正在成为关键竞争力。
### 3. 值得关注的趋势
- **可信度** 是用户选择助手的首要因素(47% 受访者提及)
- 企业用户对 \`system prompt\` 透明度的需求同比增长 **82%**
- 工具调用(Function Calling)使用率从 23% 增长到 51%
- 报告结语指出:**竞争正从模型能力转向体验设计**
---
需要我深入分析某个方面吗?点击文档的 **「查看解析」** 按钮可以看到 AI 提取的完整结构化内容。`,
},
],
timestamp: now - 60000,
},
],
}
+199
View File
@@ -0,0 +1,199 @@
{
"version": "1.0",
"model": "gpt-4-turbo",
"messages": [
{
"id": "d-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前对话日期"
},
{
"kind": "static_var",
"name": "language",
"value": "中文(简体)",
"description": "模型回复的首选语言"
},
{
"kind": "static_var",
"name": "knowledge_cutoff",
"value": "2026年1月",
"description": "模型训练数据截止日期"
},
{
"kind": "static_var",
"name": "user_name",
"value": "小明",
"description": "当前用户名称"
},
{
"kind": "system_prompt",
"content": "当前日期:{{current_date}}\n用户:{{user_name}}\n回复语言:{{language}}\n知识截止:{{knowledge_cutoff}}\n\n你是 Claude,一个 HCI 研究助手。你的角色是帮助学生批判性地思考聊天界面的设计问题。\n\n核心原则:\n- 鼓励从用户体验角度分析,而非技术实现角度\n- 用具体例子说明抽象概念\n- 如果学生的方案有改进空间,以提问的方式引导而非直接批评\n- 始终记住你拥有工具调用、skills 和跨对话 memory 能力,但不必每次都全部用到",
"collapsed": true
},
{
"kind": "memory",
"description": "以下是从过往对话中总结的关于你的信息,模型会参考这些记忆来个性化回复。记忆随对话自动更新,你也可以手动编辑或删除。",
"items": [
{ "title": "用户身份", "content": "小明,设计系研二,HCI 方向。正在做课程设计项目。" },
{ "title": "项目背景", "content": "设计一个透明化 LLM 上下文的聊天协议。已确定了 9 种 prompt 类型的分类方案。" },
{ "title": "沟通偏好", "content": "喜欢用图示和表格辅助理解。反感过度术语化。需要看到具体例子。" },
{ "title": "上次进度", "content": "用户已确认了 MVP 范围:Web 应用,数据协议+视觉规范,可导出 OpenAI Format。" }
],
"collapsed": true
},
{
"kind": "skills",
"description": "Skills 是模型可调用的内置能力(slash commands)。输入 / 开头即可触发,无需等待模型推理。当前对话中可用以下 skills:",
"items": [
{ "name": "deep-research", "description": "深度研究 — 多源搜索、交叉验证、生成引用报告" },
{ "name": "code-review", "description": "代码审查 — 发现正确性 bug 和简化/效率优化机会" },
{ "name": "verify", "description": "行为验证 — 运行应用并观察行为来确认变更生效" },
{ "name": "simplify", "description": "代码简化 — 审查代码的复用性、简洁性和效率并应用修复" },
{ "name": "loop", "description": "定时循环 — 按指定间隔重复执行一个命令或 prompt" }
],
"collapsed": true
},
{
"kind": "tool_overview",
"items": [
{
"name": "search",
"description": "搜索学术文献和设计案例",
"parameters": "query: string, limit?: number",
"schema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "搜索关键词" },
"limit": { "type": "number", "description": "返回结果数量" }
},
"required": ["query"]
}
},
{
"name": "read_file",
"description": "读取文件内容",
"parameters": "path: string",
"schema": {
"type": "object",
"properties": { "path": { "type": "string", "description": "文件路径" } },
"required": ["path"]
}
},
{
"name": "fetch_url",
"description": "获取网页内容并转为 markdown",
"parameters": "url: string",
"schema": {
"type": "object",
"properties": { "url": { "type": "string", "description": "网页 URL" } },
"required": ["url"]
}
},
{
"name": "run_code",
"description": "在沙箱中执行代码",
"parameters": "language: string, code: string",
"schema": {
"type": "object",
"properties": {
"language": { "type": "string", "enum": ["python", "javascript", "r"], "description": "编程语言" },
"code": { "type": "string", "description": "代码内容" }
},
"required": ["language", "code"]
}
}
],
"collapsed": true
}
],
"timestamp": 1780897800000
},
{
"id": "d-2",
"role": "user",
"segments": [
{
"kind": "text",
"content": "你好!我在准备课程设计的文献综述部分。我找到了一篇相关的研究报告,帮我分析一下它是否可以支持我的论点。"
},
{
"kind": "long_text",
"content": "在过去的18个月里,我们对127名LLM聊天产品用户进行了纵向研究。研究分为三个阶段:基线观察期(3个月)、干预实验期(6个月)、后续跟踪期(9个月)。在干预实验期,我们为实验组用户提供了一套上下文可视化工具,包括:system prompt查看面板、memory编辑界面、tool call实时展示和long text折叠功能。对照组使用标准的聊天界面。\n\n实验组在以下维度上表现出显著改善:\n- 任务完成率提升 23.4%p < 0.01\n- 纠错响应时间缩短 41.7%p < 0.001\n- 用户信任度评分从 3.2/5 提升至 4.1/5\n- 对话轮次平均减少 2.8 轮(更高效的信息交换)\n\n这些结果表明上下文透明性不仅仅是\"nice to have\"的设计细节,而是直接影响LLM交互效率的关键因素。特别是在以下场景中效果最为显著:\n1. 长文档分析:用户能够看到哪些文档片段被模型引用\n2. 多工具调用:工具链的可视化帮助用户理解推理过程\n3. 跨会话任务:memory可见性减少重复说明\n\n我们建议将上下文透明性作为LLM聊天产品的基础设计原则,而非可选特性。",
"charCount": 567,
"collapsed": true
},
{
"kind": "document",
"fileName": "context-transparency-study-2025.pdf",
"mimeType": "application/pdf",
"snippet": "DOI: 10.1145/3613904.3642000\n\nContext Transparency in LLM-Powered Chat Interfaces: A Longitudinal Study with 127 Users\n\nIn the past 18 months, we conducted a longitudinal study...",
"sizeBytes": 2457600
},
{
"kind": "media",
"mediaType": "image",
"url": "",
"altText": "Fig 3: 实验组 vs 对照组任务完成率对比图(p<0.01"
}
],
"timestamp": 1780897900000
},
{
"id": "d-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "这是一篇非常有力的参考文献!它恰恰支持了你的核心论点——上下文透明性是“基础设计原则,而非可选特性”。"
}
],
"timestamp": 1780897920000
},
{
"id": "d-4",
"role": "assistant",
"segments": [
{
"kind": "tool_call_request",
"toolName": "search",
"arguments": {
"query": "LLM context transparency HCI chat interface user study 2024 2025",
"limit": 5
},
"collapsed": false
}
],
"timestamp": 1780897930000
},
{
"id": "d-5",
"role": "assistant",
"segments": [
{
"kind": "tool_call_result",
"toolName": "search",
"success": true,
"result": "Found 5 results:\n\n1. \"Transparent AI: Designing Explainable Chat Interfaces\" — CHI 2024\n → 提出了\"分层透明度\"框架,与本项目的\"信息密度梯度\"高度一致\n\n2. \"User Mental Models of LLM Context\" — CSCW 2025\n → 发现 78% 的用户低估了 system prompt 对输出的影响\n\n3. \"Folding as a Visual Strategy for Complex Chat UIs\" — DIS 2024\n → 验证了折叠/展开对降低认知负荷的效果\n\n4. \"Beyond the Black Box: Visualizing LLM Reasoning in Chat\" — TOCHI 2025\n → 工具调用可视化的用户研究,与本项目的 tool call request/result 设计一致\n\n5. \"Memory-Aware Interfaces for Persistent LLM Interactions\" — IUI 2025\n → 讨论了用户记忆的可编辑性和可遗忘性",
"collapsed": true
}
],
"timestamp": 1780897950000
},
{
"id": "d-6",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "搜索结果显示,你的研究方向和当前学术界的热点高度一致。以下是我对文献支持度的评估:\n\n**强相关(可直接引用):**\n- 第2篇(CSCW 2025)直接量化了用户对 LLM 上下文的理解不足——这是你\"问题定义\"部分的关键论据\n- 第3篇(DIS 2024)为你的\"折叠设计\"提供了实证基础\n\n**建议阅读方向:**\n- 第1篇的\"分层透明度\"框架可以与你的 9 种类型分类做对比讨论\n- 第5篇的 memory 可编辑性可能是你下一个值得探索的方向\n\n这篇研究报告 + 这5篇文献构成了一个很好的文献基础。需要我帮你整理成引用格式吗?"
}
],
"timestamp": 1780898000000
}
]
}
-273
View File
@@ -1,273 +0,0 @@
import type { PromptEnvelope } from '../../types/protocol'
import { getSkills } from '../skills'
const now = Date.now()
const comprehensiveArticleContent = `在过去的18个月里,我们对127名LLM聊天产品用户进行了纵向研究。研究分为三个阶段:基线观察期(3个月)、干预实验期(6个月)、后续跟踪期(9个月)。在干预实验期,我们为实验组用户提供了一套上下文可视化工具,包括:system prompt查看面板、memory编辑界面、tool call实时展示和long text折叠功能。对照组使用标准的聊天界面。
实验组在以下维度上表现出显著改善:
- 任务完成率提升 23.4%p < 0.01
- 纠错响应时间缩短 41.7%p < 0.001
- 用户信任度评分从 3.2/5 提升至 4.1/5
- 对话轮次平均减少 2.8 轮(更高效的信息交换)
这些结果表明上下文透明性不仅仅是"nice to have"的设计细节,而是直接影响LLM交互效率的关键因素。特别是在以下场景中效果最为显著:
1. 长文档分析:用户能够看到哪些文档片段被模型引用
2. 多工具调用:工具链的可视化帮助用户理解推理过程
3. 跨会话任务:memory可见性减少重复说明
我们建议将上下文透明性作为LLM聊天产品的基础设计原则,而非可选特性。`
export const demoD: PromptEnvelope = {
version: '1.0',
model: 'gpt-4-turbo',
messages: [
// --- System message with all structural segments ---
{
id: 'd-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前对话日期',
},
{
kind: 'static_var',
name: 'language',
value: '中文(简体)',
description: '模型回复的首选语言',
},
{
kind: 'static_var',
name: 'knowledge_cutoff',
value: '2026年1月',
description: '模型训练数据截止日期',
},
{
kind: 'static_var',
name: 'user_name',
value: '小明',
description: '当前用户名称',
},
{
kind: 'system_prompt',
content: `当前日期:{{current_date}}
用户:{{user_name}}
回复语言:{{language}}
知识截止:{{knowledge_cutoff}}
你是 Claude,一个 HCI 研究助手。你的角色是帮助学生批判性地思考聊天界面的设计问题。
核心原则:
- 鼓励从用户体验角度分析,而非技术实现角度
- 用具体例子说明抽象概念
- 如果学生的方案有改进空间,以提问的方式引导而非直接批评
- 始终记住你拥有工具调用、skills 和跨对话 memory 能力,但不必每次都全部用到`,
collapsed: true,
},
{
kind: 'memory',
description:
'以下是从过往对话中总结的关于你的信息,模型会参考这些记忆来个性化回复。记忆随对话自动更新,你也可以手动编辑或删除。',
items: [
{
title: '用户身份',
content: '小明,设计系研二,HCI 方向。正在做课程设计项目。',
},
{
title: '项目背景',
content: '设计一个透明化 LLM 上下文的聊天协议。已确定了 9 种 prompt 类型的分类方案。',
},
{
title: '沟通偏好',
content: '喜欢用图示和表格辅助理解。反感过度术语化。需要看到具体例子。',
},
{
title: '上次进度',
content: '用户已确认了 MVP 范围:Web 应用,数据协议+视觉规范,可导出 OpenAI Format。',
},
],
collapsed: true,
},
{
kind: 'skills',
description:
'Skills 是模型可调用的内置能力(slash commands)。输入 / 开头即可触发,无需等待模型推理。当前对话中可用以下 skills:',
items: getSkills(['deep-research', 'code-review', 'verify', 'simplify', 'loop']),
collapsed: true,
},
{
kind: 'tool_overview',
items: [
{
name: 'search',
description: '搜索学术文献和设计案例',
parameters: 'query: string, limit?: number',
schema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
limit: { type: 'number', description: '返回结果数量' },
},
required: ['query'],
},
},
{
name: 'read_file',
description: '读取文件内容',
parameters: 'path: string',
schema: {
type: 'object',
properties: {
path: { type: 'string', description: '文件路径' },
},
required: ['path'],
},
},
{
name: 'fetch_url',
description: '获取网页内容并转为 markdown',
parameters: 'url: string',
schema: {
type: 'object',
properties: {
url: { type: 'string', description: '网页 URL' },
},
required: ['url'],
},
},
{
name: 'run_code',
description: '在沙箱中执行代码',
parameters: 'language: string, code: string',
schema: {
type: 'object',
properties: {
language: { type: 'string', enum: ['python', 'javascript', 'r'], description: '编程语言' },
code: { type: 'string', description: '代码内容' },
},
required: ['language', 'code'],
},
},
],
collapsed: true,
},
],
timestamp: now - 600000,
},
// --- User message 1 ---
{
id: 'd-2',
role: 'user',
segments: [
{
kind: 'text',
content: '你好!我在准备课程设计的文献综述部分。我找到了一篇相关的研究报告,帮我分析一下它是否可以支持我的论点。',
},
{
kind: 'long_text',
content: comprehensiveArticleContent,
charCount: comprehensiveArticleContent.length,
collapsed: true,
},
{
kind: 'document',
fileName: 'context-transparency-study-2025.pdf',
mimeType: 'application/pdf',
snippet:
'DOI: 10.1145/3613904.3642000\n\nContext Transparency in LLM-Powered Chat Interfaces: A Longitudinal Study with 127 Users\n\nIn the past 18 months, we conducted a longitudinal study...',
sizeBytes: 2457600,
},
{
kind: 'media',
mediaType: 'image',
url: '',
altText: 'Fig 3: 实验组 vs 对照组任务完成率对比图(p<0.01',
},
],
timestamp: now - 500000,
},
// --- Assistant response ---
{
id: 'd-3',
role: 'assistant',
segments: [
{
kind: 'text',
content: '这是一篇非常有力的参考文献!它恰恰支持了你的核心论点——上下文透明性是"基础设计原则,而非可选特性"。',
},
],
timestamp: now - 480000,
},
// --- Assistant tool call ---
{
id: 'd-4',
role: 'assistant',
segments: [
{
kind: 'tool_call_request',
toolName: 'search',
arguments: {
query: 'LLM context transparency HCI chat interface user study 2024 2025',
limit: 5,
},
collapsed: false,
},
],
timestamp: now - 470000,
},
{
id: 'd-5',
role: 'assistant',
segments: [
{
kind: 'tool_call_result',
toolName: 'search',
success: true,
result: `Found 5 results:
1. "Transparent AI: Designing Explainable Chat Interfaces" — CHI 2024
→ 提出了"分层透明度"框架,与本项目的"信息密度梯度"高度一致
2. "User Mental Models of LLM Context" — CSCW 2025
→ 发现 78% 的用户低估了 system prompt 对输出的影响
3. "Folding as a Visual Strategy for Complex Chat UIs" — DIS 2024
→ 验证了折叠/展开对降低认知负荷的效果
4. "Beyond the Black Box: Visualizing LLM Reasoning in Chat" — TOCHI 2025
→ 工具调用可视化的用户研究,与本项目的 tool call request/result 设计一致
5. "Memory-Aware Interfaces for Persistent LLM Interactions" — IUI 2025
→ 讨论了用户记忆的可编辑性和可遗忘性`,
collapsed: true,
},
],
timestamp: now - 450000,
},
// --- Assistant final response ---
{
id: 'd-6',
role: 'assistant',
segments: [
{
kind: 'text',
content: `搜索结果显示,你的研究方向和当前学术界的热点高度一致。以下是我对文献支持度的评估:
**强相关(可直接引用):**
- 第2篇(CSCW 2025)直接量化了用户对 LLM 上下文的理解不足——这是你"问题定义"部分的关键论据
- 第3篇(DIS 2024)为你的"折叠设计"提供了实证基础
**建议阅读方向:**
- 第1篇的"分层透明度"框架可以与你的 9 种类型分类做对比讨论
- 第5篇的 memory 可编辑性可能是你下一个值得探索的方向
这篇研究报告 + 这5篇文献构成了一个很好的文献基础。需要我帮你整理成引用格式吗?`,
},
],
timestamp: now - 400000,
},
],
}
+148
View File
@@ -0,0 +1,148 @@
{
"version": "1.0",
"model": "claude-opus-4-8",
"messages": [
{
"id": "e-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前对话日期"
},
{
"kind": "static_var",
"name": "server_name",
"value": "production-api-01",
"description": "目标服务器名称"
},
{
"kind": "static_var",
"name": "analysis_depth",
"value": "全面(异常检测 + 安全审计 + 性能分析)",
"description": "日志分析的范围深度"
},
{
"kind": "system_prompt",
"content": "当前日期:{{current_date}}\n目标服务器:{{server_name}}\n分析范围:{{analysis_depth}}\n\n你是日志分析专家助手。你的核心能力:\n\n1. **异常检测** — 识别错误状态码(4xx/5xx)、请求突增、响应时间异常\n2. **安全审计** — 发现暴力破解尝试、路径探测、SQL 注入尝试\n3. **性能分析** — 统计端点响应分布、慢请求排查、热点资源识别\n4. **趋势报告** — 按时间窗口聚合,生成可读性高的分析摘要\n\n分析原则:\n- 优先发现模式而非孤立事件\n- 区分误报和真正的问题\n- 每个发现标注严重等级和置信度\n- 在工具调用中执行计算分析,回复中给出解读",
"collapsed": true
},
{
"kind": "memory",
"description": "以下是你对用户的了解,参考这些记录来提供个性化的日志分析服务。",
"items": [
{ "title": "用户身份", "content": "小陈,后端开发工程师,负责生产环境的运维监控。" },
{ "title": "系统环境", "content": "Kubernetes 集群上的微服务架构,API 网关使用 Nginx。生产环境有三台节点。" },
{ "title": "分析偏好", "content": "偏好先看整体概览再深入细节。对安全相关告警特别敏感,需要高亮标注。" },
{ "title": "上次分析记录", "content": "上周分析过同一台服务器的日志,发现了一个定时任务导致的 503 抖动。" }
],
"collapsed": true
},
{
"kind": "skills",
"description": "以下是可用的技能助手,通过 /skill_name 触发:",
"items": [
{ "name": "summarize", "description": "生成文档摘要 — 支持多种粒度(一句话/段落级/全文级)" },
{ "name": "qa", "description": "基于文档回答具体问题 — 带引用溯源" },
{ "name": "translate", "description": "翻译文档内容 — 支持中英互译,保留原文格式" }
],
"collapsed": true
},
{
"kind": "tool_overview",
"items": [
{
"name": "run_code",
"description": "在沙箱中执行 Python/Shell 代码进行日志解析和统计",
"parameters": "language: string, code: string",
"schema": {
"type": "object",
"properties": {
"language": { "type": "string", "enum": ["python", "bash"], "description": "执行语言" },
"code": { "type": "string", "description": "要执行的代码" }
},
"required": ["language", "code"]
}
},
{
"name": "search_knowledge",
"description": "搜索已知的漏洞模式和安全公告",
"parameters": "query: string",
"schema": {
"type": "object",
"properties": { "query": { "type": "string", "description": "搜索关键词" } },
"required": ["query"]
}
}
],
"collapsed": true
}
],
"timestamp": 1780897800000
},
{
"id": "e-2",
"role": "user",
"segments": [
{
"kind": "text",
"content": "生产环境 API 服务器刚刚的访问日志,帮我看一下有没有异常。我自己扫了一遍,感觉有几个可疑的 IP 在尝试访问 /admin 路径,但不太确定是不是误报。另外 /health 端点偶尔返回 503 的情况也需要你分析一下。"
},
{
"kind": "long_text",
"content": "192.168.1.10 - - [07/Jun/2026:10:15:23 +0800] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"curl/7.88.1\"\n192.168.1.20 - - [07/Jun/2026:10:15:25 +0800] \"POST /api/login HTTP/1.1\" 302 0 \"-\" \"Mozilla/5.0\"\n10.0.0.5 - - [07/Jun/2026:10:15:28 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"\n192.168.1.10 - - [07/Jun/2026:10:16:01 +0800] \"GET /api/orders HTTP/1.1\" 200 8921 \"-\" \"curl/7.88.1\"\n203.0.113.42 - - [07/Jun/2026:10:16:05 +0800] \"GET /admin HTTP/1.1\" 403 189 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:06 +0800] \"GET /admin/config HTTP/1.1\" 404 153 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:07 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:08 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:09 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:10 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:11 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:00 +0800] \"GET /api/products?page=1 HTTP/1.1\" 200 15420 \"-\" \"Mozilla/5.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:02 +0800] \"GET /api/products?page=2 HTTP/1.1\" 200 14893 \"-\" \"Mozilla/5.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:04 +0800] \"GET /api/products?page=3 HTTP/1.1\" 200 15102 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:30 +0800] \"GET / HTTP/1.1\" 200 5678 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:32 +0800] \"GET /static/css/main.css HTTP/1.1\" 200 28491 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:33 +0800] \"GET /static/js/app.js HTTP/1.1\" 200 128034 \"-\" \"Mozilla/5.0\"\n10.0.0.5 - - [07/Jun/2026:10:20:00 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"\n192.168.1.10 - - [07/Jun/2026:10:21:15 +0800] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"curl/7.88.1\"\n192.168.1.10 - - [07/Jun/2026:10:21:16 +0800] \"GET /api/orders HTTP/1.1\" 200 8921 \"-\" \"curl/7.88.1\"\n192.168.1.10 - - [07/Jun/2026:10:21:17 +0800] \"GET /api/products HTTP/1.1\" 200 15420 \"-\" \"curl/7.88.1\"\n10.0.0.6 - - [07/Jun/2026:10:22:00 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:05 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:10 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:15 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"",
"charCount": 2375,
"collapsed": true
}
],
"timestamp": 1780897900000
},
{
"id": "e-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "我来分析这份日志。先快速概览一下整体情况:\n\n从格式看是标准的 Nginx combined 格式,时间窗口约 **10:1510:22**(约 7 分钟),共 **25 条**请求记录。几家不同的客户端:\n\n- **192.168.1.10** — 内部 API 调用(curl\n- **192.168.1.20** — 正常浏览器登录\n- **10.0.0.5** — K8s 健康检查\n- **192.168.1.30** — 浏览器浏览商品页面\n- **198.51.100.7** — 正常用户访问首页\n- **10.0.0.6** — 另一台节点的健康检查\n- **203.0.113.42** — ⚠️ 可疑 IP\n\n我先用 `run_code` 对请求做聚合统计,确认模式后再深入分析可疑流量。"
},
{
"kind": "tool_call_request",
"toolName": "run_code",
"arguments": {
"language": "python",
"code": "from collections import Counter, defaultdict\nimport re\n\nlog_text = \"\"\"192.168.1.10 - - [07/Jun/2026:10:15:23 +0800] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"curl/7.88.1\"\n192.168.1.20 - - [07/Jun/2026:10:15:25 +0800] \"POST /api/login HTTP/1.1\" 302 0 \"-\" \"Mozilla/5.0\"\n10.0.0.5 - - [07/Jun/2026:10:15:28 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"\n192.168.1.10 - - [07/Jun/2026:10:16:01 +0800] \"GET /api/orders HTTP/1.1\" 200 8921 \"-\" \"curl/7.88.1\"\n203.0.113.42 - - [07/Jun/2026:10:16:05 +0800] \"GET /admin HTTP/1.1\" 403 189 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:06 +0800] \"GET /admin/config HTTP/1.1\" 404 153 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:07 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:08 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:09 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:10 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n203.0.113.42 - - [07/Jun/2026:10:16:11 +0800] \"POST /admin/login HTTP/1.1\" 401 198 \"-\" \"python-requests/2.31.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:00 +0800] \"GET /api/products?page=1 HTTP/1.1\" 200 15420 \"-\" \"Mozilla/5.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:02 +0800] \"GET /api/products?page=2 HTTP/1.1\" 200 14893 \"-\" \"Mozilla/5.0\"\n192.168.1.30 - - [07/Jun/2026:10:17:04 +0800] \"GET /api/products?page=3 HTTP/1.1\" 200 15102 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:30 +0800] \"GET / HTTP/1.1\" 200 5678 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:32 +0800] \"GET /static/css/main.css HTTP/1.1\" 200 28491 \"-\" \"Mozilla/5.0\"\n198.51.100.7 - - [07/Jun/2026:10:18:33 +0800] \"GET /static/js/app.js HTTP/1.1\" 200 128034 \"-\" \"Mozilla/5.0\"\n10.0.0.5 - - [07/Jun/2026:10:20:00 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"\n192.168.1.10 - - [07/Jun/2026:10:21:15 +0800] \"GET /api/users HTTP/1.1\" 200 1234 \"-\" \"curl/7.88.1\"\n192.168.1.10 - - [07/Jun/2026:10:21:16 +0800] \"GET /api/orders HTTP/1.1\" 200 8921 \"-\" \"curl/7.88.1\"\n192.168.1.10 - - [07/Jun/2026:10:21:17 +0800] \"GET /api/products HTTP/1.1\" 200 15420 \"-\" \"curl/7.88.1\"\n10.0.0.6 - - [07/Jun/2026:10:22:00 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:05 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:10 +0800] \"GET /health HTTP/1.1\" 503 57 \"-\" \"Kubernetes/1.28\"\n10.0.0.6 - - [07/Jun/2026:10:22:15 +0800] \"GET /health HTTP/1.1\" 200 2 \"-\" \"Kubernetes/1.28\"\"\"\n\n# 解析日志\npattern = r'(\\S+) - - \\[(.+?)\\] \"(.+?)\" (\\d+) (\\d+) \".*?\" \"(.*?)\"'\nrecords = []\nfor line in log_text.strip().split('\\n'):\n m = re.match(pattern, line)\n if m:\n records.append({\n 'ip': m.group(1),\n 'time': m.group(2),\n 'request': m.group(3),\n 'status': int(m.group(4)),\n 'size': int(m.group(5)),\n 'ua': m.group(6)\n })\n\n# 1. IP 统计\nprint(\"=== 按 IP 请求量统计 ===\")\nip_counter = Counter(r['ip'] for r in records)\nfor ip, count in ip_counter.most_common():\n print(f\" {ip:20s} → {count} 次请求\")\n\n# 2. 状态码分布\nprint(\"\\n=== 状态码分布 ===\")\nstatus_counter = Counter(r['status'] for r in records)\nfor code, count in sorted(status_counter.items()):\n print(f\" {code} → {count} 次\")\n\n# 3. 可疑 IP 分析\nprint(\"\\n=== 可疑 IP: 203.0.113.42 的请求序列 ===\")\nfor r in records:\n if r['ip'] == '203.0.113.42':\n print(f\" [{r['time']}] {r['request']} → {r['status']}\")\n\n# 4. /health 端点分析\nprint(\"\\n=== /health 端点状态 ===\")\nfor r in records:\n if '/health' in r['request']:\n print(f\" {r['ip']:15s} [{r['time']}] → {r['status']} (UA: {r['ua']})\")\n\n# 5. 非 2xx 占比\ntotal = len(records)\nerrors = sum(1 for r in records if r['status'] >= 400)\nprint(f\"\\n=== 总结 ===\")\nprint(f\" 总请求数: {total}\")\nprint(f\" 错误请求: {errors} ({errors/total*100:.1f}%)\")\nprint(f\" 正常请求: {total - errors} ({(total-errors)/total*100:.1f}%)\")"
},
"collapsed": false
}
],
"timestamp": 1780897920000
},
{
"id": "e-4",
"role": "assistant",
"segments": [
{
"kind": "tool_call_result",
"toolName": "run_code",
"success": true,
"result": "=== 按 IP 请求量统计 ===\n 192.168.1.10 → 5 次请求\n 203.0.113.42 → 7 次请求\n 198.51.100.7 → 3 次请求\n 10.0.0.5 → 2 次请求\n 10.0.0.6 → 4 次请求\n 192.168.1.20 → 1 次请求\n 192.168.1.30 → 3 次请求\n\n=== 状态码分布 ===\n 200 → 14 次\n 302 → 1 次\n 401 → 5 次\n 403 → 1 次\n 404 → 1 次\n 503 → 3 次\n\n=== 可疑 IP: 203.0.113.42 的请求序列 ===\n [07/Jun/2026:10:16:05 +0800] GET /admin → 403\n [07/Jun/2026:10:16:06 +0800] GET /admin/config → 404\n [07/Jun/2026:10:16:07 +0800] POST /admin/login → 401\n [07/Jun/2026:10:16:08 +0800] POST /admin/login → 401\n [07/Jun/2026:10:16:09 +0800] POST /admin/login → 401\n [07/Jun/2026:10:16:10 +0800] POST /admin/login → 401\n [07/Jun/2026:10:16:11 +0800] POST /admin/login → 401\n\n=== /health 端点状态 ===\n 10.0.0.5 [07/Jun/2026:10:15:28 +0800] → 200 (UA: Kubernetes/1.28)\n 10.0.0.5 [07/Jun/2026:10:20:00 +0800] → 200 (UA: Kubernetes/1.28)\n 10.0.0.6 [07/Jun/2026:10:22:00 +0800] → 503 (UA: Kubernetes/1.28)\n 10.0.0.6 [07/Jun/2026:10:22:05 +0800] → 503 (UA: Kubernetes/1.28)\n 10.0.0.6 [07/Jun/2026:10:22:10 +0800] → 503 (UA: Kubernetes/1.28)\n 10.0.0.6 [07/Jun/2026:10:22:15 +0800] → 503 (UA: Kubernetes/1.28)\n\n=== 总结 ===\n 总请求数: 25\n 错误请求: 10 (40.0%)\n 非 2xx 响应占比高达 40%,\n 主要集中在两个异常模式上。",
"collapsed": false
}
],
"timestamp": 1780897940000
},
{
"id": "e-5",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "## 日志分析报告\n\n### 🔴 高危:暴力破解尝试\n\n**IP: 203.0.113.42**(来源:外部,UA: python-requests\n\n在 **10:16:0510:16:11** 的 6 秒内向 /admin 路径连续发送了 **7 次请求**,典型的暴力破解模式:\n\n| 时间 | 请求 | 状态 | 说明 |\n|------|------|------|------|\n| +0s | GET /admin | 403 | 探路——发现 /admin 存在 |\n| +1s | GET /admin/config | 404 | 试图访问敏感配置 |\n| +2~6s | POST /admin/login ×5 | 401 | 连续尝试登录——5 次均失败 |\n\n**建议措施:**\n- 🔒 立即对该 IP 加入 WAF 黑名单\n- 📋 检查 fail2ban 或类似自动封禁机制是否正常运行\n- 🚨 确认 admin 端点的访问控制策略(当前 403 正确拦截,但暴力破解仍消耗服务器资源)\n\n---\n\n### 🟡 中危:/health 端点间歇 503\n\n**节点 10.0.0.6** 在 **10:22:0010:22:10** 的 10 秒内连续返回 503,随后恢复 200。而 **10.0.0.5** 一直正常。\n\n这与上周分析中定时任务导致的 503 抖动模式一致。建议检查该节点上是否在整点前后有定时任务(如日志轮转、缓存预热等)。\n\n**建议措施:**\n- ⏰ 检查 10.0.0.6 的 cron 配置\n- 📊 添加更细粒度的 /health 监控(响应耗时 + 组件健康分别上报)\n- 🔄 考虑滚动重启使两个节点的健康检查窗口错开\n\n---\n\n### 🟢 常规观察\n\n- **192.168.1.10** — 内部 API 客户端,访问模式正常(用户/订单/商品查询)\n- **192.168.1.20** — 一次正常登录(POST /api/login → 302 重定向)\n- **198.51.100.7** — 正常用户访问,加载了首页 + CSS + JS\n- **192.168.1.30** — 分页浏览商品,行为正常\n- 无性能异常(无超时记录或慢请求)\n\n---\n\n### 📋 总结优先级\n\n| 优先级 | 问题 | 影响 |\n|--------|------|------|\n| P0 | 203.0.113.42 暴力破解 | 安全风险,立即封禁 |\n| P1 | 10.0.0.6 间歇 503 | 稳定性风险,排查定时任务 |\n| P3 | 整体 40% 非 2xx | 排除以上两者后正常 |\n\n需要我进一步分析某个方面吗?比如用 search_knowledge 查询 203.0.113.42 是否在已知攻击数据库中出现过?"
}
],
"timestamp": 1780897980000
}
]
}
-401
View File
@@ -1,401 +0,0 @@
/**
* Demo E — 日志分析(无文档传入)
*
* 场景:用户分享一段 Nginx 访问日志,请求 AI 助手帮忙分析
* 异常模式和潜在安全风险。
*
* 关键展示:
* 1. long_text 用于展示原始日志全文(折叠态仅显示前2行)
* 2. tool_call_request/result 展示日志分析工具的调用过程
* 3. static_var 注入服务器环境和分析参数
* 4. memory 记录用户的系统环境和分析偏好
* 5. 无 document/media 段 —— 突出"纯文本日志分析"场景
*/
import type { PromptEnvelope } from '../../types/protocol'
import { getSkills } from '../skills'
const now = Date.now()
const sampleLog = `192.168.1.10 - - [07/Jun/2026:10:15:23 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/7.88.1"
192.168.1.20 - - [07/Jun/2026:10:15:25 +0800] "POST /api/login HTTP/1.1" 302 0 "-" "Mozilla/5.0"
10.0.0.5 - - [07/Jun/2026:10:15:28 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"
192.168.1.10 - - [07/Jun/2026:10:16:01 +0800] "GET /api/orders HTTP/1.1" 200 8921 "-" "curl/7.88.1"
203.0.113.42 - - [07/Jun/2026:10:16:05 +0800] "GET /admin HTTP/1.1" 403 189 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:06 +0800] "GET /admin/config HTTP/1.1" 404 153 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:07 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:08 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:09 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:10 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:11 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
192.168.1.30 - - [07/Jun/2026:10:17:00 +0800] "GET /api/products?page=1 HTTP/1.1" 200 15420 "-" "Mozilla/5.0"
192.168.1.30 - - [07/Jun/2026:10:17:02 +0800] "GET /api/products?page=2 HTTP/1.1" 200 14893 "-" "Mozilla/5.0"
192.168.1.30 - - [07/Jun/2026:10:17:04 +0800] "GET /api/products?page=3 HTTP/1.1" 200 15102 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:30 +0800] "GET / HTTP/1.1" 200 5678 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:32 +0800] "GET /static/css/main.css HTTP/1.1" 200 28491 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:33 +0800] "GET /static/js/app.js HTTP/1.1" 200 128034 "-" "Mozilla/5.0"
10.0.0.5 - - [07/Jun/2026:10:20:00 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"
192.168.1.10 - - [07/Jun/2026:10:21:15 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/7.88.1"
192.168.1.10 - - [07/Jun/2026:10:21:16 +0800] "GET /api/orders HTTP/1.1" 200 8921 "-" "curl/7.88.1"
192.168.1.10 - - [07/Jun/2026:10:21:17 +0800] "GET /api/products HTTP/1.1" 200 15420 "-" "curl/7.88.1"
10.0.0.6 - - [07/Jun/2026:10:22:00 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:05 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:10 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:15 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"`
export const demoE: PromptEnvelope = {
version: '1.0',
model: 'claude-opus-4-8',
messages: [
// --- System message ---
{
id: 'e-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前对话日期',
},
{
kind: 'static_var',
name: 'server_name',
value: 'production-api-01',
description: '目标服务器名称',
},
{
kind: 'static_var',
name: 'analysis_depth',
value: '全面(异常检测 + 安全审计 + 性能分析)',
description: '日志分析的范围深度',
},
{
kind: 'system_prompt',
content: `当前日期:{{current_date}}
目标服务器:{{server_name}}
分析范围:{{analysis_depth}}
你是日志分析专家助手。你的核心能力:
1. **异常检测** — 识别错误状态码(4xx/5xx)、请求突增、响应时间异常
2. **安全审计** — 发现暴力破解尝试、路径探测、SQL 注入尝试
3. **性能分析** — 统计端点响应分布、慢请求排查、热点资源识别
4. **趋势报告** — 按时间窗口聚合,生成可读性高的分析摘要
分析原则:
- 优先发现模式而非孤立事件
- 区分误报和真正的问题
- 每个发现标注严重等级和置信度
- 在工具调用中执行计算分析,回复中给出解读`,
collapsed: true,
},
{
kind: 'memory',
description:
'以下是你对用户的了解,参考这些记录来提供个性化的日志分析服务。',
items: [
{
title: '用户身份',
content: '小陈,后端开发工程师,负责生产环境的运维监控。',
},
{
title: '系统环境',
content: 'Kubernetes 集群上的微服务架构,API 网关使用 Nginx。生产环境有三台节点。',
},
{
title: '分析偏好',
content: '偏好先看整体概览再深入细节。对安全相关告警特别敏感,需要高亮标注。',
},
{
title: '上次分析记录',
content: '上周分析过同一台服务器的日志,发现了一个定时任务导致的 503 抖动。',
},
],
collapsed: true,
},
{
kind: 'skills',
description:
'以下是可用的技能助手,通过 /skill_name 触发:',
items: getSkills(['summarize', 'qa', 'translate']),
collapsed: true,
},
{
kind: 'tool_overview',
items: [
{
name: 'run_code',
description: '在沙箱中执行 Python/Shell 代码进行日志解析和统计',
parameters: 'language: string, code: string',
schema: {
type: 'object',
properties: {
language: { type: 'string', enum: ['python', 'bash'], description: '执行语言' },
code: { type: 'string', description: '要执行的代码' },
},
required: ['language', 'code'],
},
},
{
name: 'search_knowledge',
description: '搜索已知的漏洞模式和安全公告',
parameters: 'query: string',
schema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索关键词' },
},
required: ['query'],
},
},
],
collapsed: true,
},
],
timestamp: now - 600000,
},
// --- User 请求日志分析 ---
{
id: 'e-2',
role: 'user',
segments: [
{
kind: 'text',
content: '生产环境 API 服务器刚刚的访问日志,帮我看一下有没有异常。我自己扫了一遍,感觉有几个可疑的 IP 在尝试访问 /admin 路径,但不太确定是不是误报。另外 /health 端点偶尔返回 503 的情况也需要你分析一下。',
},
{
kind: 'long_text',
content: sampleLog,
charCount: sampleLog.length,
collapsed: true,
},
],
timestamp: now - 500000,
},
// --- Assistant 初始分析回复 ---
{
id: 'e-3',
role: 'assistant',
segments: [
{
kind: 'text',
content: `我来分析这份日志。先快速概览一下整体情况:
从格式看是标准的 Nginx combined 格式,时间窗口约 **10:1510:22**(约 7 分钟),共 **25 条**请求记录。几家不同的客户端:
- **192.168.1.10** — 内部 API 调用(curl
- **192.168.1.20** — 正常浏览器登录
- **10.0.0.5** — K8s 健康检查
- **192.168.1.30** — 浏览器浏览商品页面
- **198.51.100.7** — 正常用户访问首页
- **10.0.0.6** — 另一台节点的健康检查
- **203.0.113.42** — ⚠️ 可疑 IP
我先用 \`run_code\` 对请求做聚合统计,确认模式后再深入分析可疑流量。`,
},
{
kind: 'tool_call_request',
toolName: 'run_code',
arguments: {
language: 'python',
code: `from collections import Counter, defaultdict
import re
log_text = """192.168.1.10 - - [07/Jun/2026:10:15:23 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/7.88.1"
192.168.1.20 - - [07/Jun/2026:10:15:25 +0800] "POST /api/login HTTP/1.1" 302 0 "-" "Mozilla/5.0"
10.0.0.5 - - [07/Jun/2026:10:15:28 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"
192.168.1.10 - - [07/Jun/2026:10:16:01 +0800] "GET /api/orders HTTP/1.1" 200 8921 "-" "curl/7.88.1"
203.0.113.42 - - [07/Jun/2026:10:16:05 +0800] "GET /admin HTTP/1.1" 403 189 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:06 +0800] "GET /admin/config HTTP/1.1" 404 153 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:07 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:08 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:09 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:10 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
203.0.113.42 - - [07/Jun/2026:10:16:11 +0800] "POST /admin/login HTTP/1.1" 401 198 "-" "python-requests/2.31.0"
192.168.1.30 - - [07/Jun/2026:10:17:00 +0800] "GET /api/products?page=1 HTTP/1.1" 200 15420 "-" "Mozilla/5.0"
192.168.1.30 - - [07/Jun/2026:10:17:02 +0800] "GET /api/products?page=2 HTTP/1.1" 200 14893 "-" "Mozilla/5.0"
192.168.1.30 - - [07/Jun/2026:10:17:04 +0800] "GET /api/products?page=3 HTTP/1.1" 200 15102 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:30 +0800] "GET / HTTP/1.1" 200 5678 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:32 +0800] "GET /static/css/main.css HTTP/1.1" 200 28491 "-" "Mozilla/5.0"
198.51.100.7 - - [07/Jun/2026:10:18:33 +0800] "GET /static/js/app.js HTTP/1.1" 200 128034 "-" "Mozilla/5.0"
10.0.0.5 - - [07/Jun/2026:10:20:00 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"
192.168.1.10 - - [07/Jun/2026:10:21:15 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/7.88.1"
192.168.1.10 - - [07/Jun/2026:10:21:16 +0800] "GET /api/orders HTTP/1.1" 200 8921 "-" "curl/7.88.1"
192.168.1.10 - - [07/Jun/2026:10:21:17 +0800] "GET /api/products HTTP/1.1" 200 15420 "-" "curl/7.88.1"
10.0.0.6 - - [07/Jun/2026:10:22:00 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:05 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:10 +0800] "GET /health HTTP/1.1" 503 57 "-" "Kubernetes/1.28"
10.0.0.6 - - [07/Jun/2026:10:22:15 +0800] "GET /health HTTP/1.1" 200 2 "-" "Kubernetes/1.28"""
# 解析日志
pattern = r'(\\S+) - - \\[(.+?)\\] "(.+?)" (\\d+) (\\d+) ".*?" "(.*?)"'
records = []
for line in log_text.strip().split('\\n'):
m = re.match(pattern, line)
if m:
records.append({
'ip': m.group(1),
'time': m.group(2),
'request': m.group(3),
'status': int(m.group(4)),
'size': int(m.group(5)),
'ua': m.group(6)
})
# 1. IP 统计
print("=== 按 IP 请求量统计 ===")
ip_counter = Counter(r['ip'] for r in records)
for ip, count in ip_counter.most_common():
print(f" {ip:20s} → {count} 次请求")
# 2. 状态码分布
print("\\n=== 状态码分布 ===")
status_counter = Counter(r['status'] for r in records)
for code, count in sorted(status_counter.items()):
print(f" {code} → {count} 次")
# 3. 可疑 IP 分析
print("\\n=== 可疑 IP: 203.0.113.42 的请求序列 ===")
for r in records:
if r['ip'] == '203.0.113.42':
print(f" [{r['time']}] {r['request']} → {r['status']}")
# 4. /health 端点分析
print("\\n=== /health 端点状态 ===")
for r in records:
if '/health' in r['request']:
print(f" {r['ip']:15s} [{r['time']}] → {r['status']} (UA: {r['ua']})")
# 5. 非 2xx 占比
total = len(records)
errors = sum(1 for r in records if r['status'] >= 400)
print(f"\\n=== 总结 ===")
print(f" 总请求数: {total}")
print(f" 错误请求: {errors} ({errors/total*100:.1f}%)")
print(f" 正常请求: {total - errors} ({(total-errors)/total*100:.1f}%)")`,
},
collapsed: false,
},
],
timestamp: now - 480000,
},
// --- Tool 调用结果 ---
{
id: 'e-4',
role: 'assistant',
segments: [
{
kind: 'tool_call_result',
toolName: 'run_code',
success: true,
result: `=== 按 IP 请求量统计 ===
192.168.1.10 → 5 次请求
203.0.113.42 → 7 次请求
198.51.100.7 → 3 次请求
10.0.0.5 → 2 次请求
10.0.0.6 → 4 次请求
192.168.1.20 → 1 次请求
192.168.1.30 → 3 次请求
=== 状态码分布 ===
200 → 14 次
302 → 1 次
401 → 5 次
403 → 1 次
404 → 1 次
503 → 3 次
=== 可疑 IP: 203.0.113.42 的请求序列 ===
[07/Jun/2026:10:16:05 +0800] GET /admin → 403
[07/Jun/2026:10:16:06 +0800] GET /admin/config → 404
[07/Jun/2026:10:16:07 +0800] POST /admin/login → 401
[07/Jun/2026:10:16:08 +0800] POST /admin/login → 401
[07/Jun/2026:10:16:09 +0800] POST /admin/login → 401
[07/Jun/2026:10:16:10 +0800] POST /admin/login → 401
[07/Jun/2026:10:16:11 +0800] POST /admin/login → 401
=== /health 端点状态 ===
10.0.0.5 [07/Jun/2026:10:15:28 +0800] → 200 (UA: Kubernetes/1.28)
10.0.0.5 [07/Jun/2026:10:20:00 +0800] → 200 (UA: Kubernetes/1.28)
10.0.0.6 [07/Jun/2026:10:22:00 +0800] → 503 (UA: Kubernetes/1.28)
10.0.0.6 [07/Jun/2026:10:22:05 +0800] → 503 (UA: Kubernetes/1.28)
10.0.0.6 [07/Jun/2026:10:22:10 +0800] → 503 (UA: Kubernetes/1.28)
10.0.0.6 [07/Jun/2026:10:22:15 +0800] → 503 (UA: Kubernetes/1.28)
=== 总结 ===
总请求数: 25
错误请求: 10 (40.0%)
非 2xx 响应占比高达 40%,
主要集中在两个异常模式上。`,
collapsed: false,
},
],
timestamp: now - 460000,
},
// --- Assistant 综合报告 ---
{
id: 'e-5',
role: 'assistant',
segments: [
{
kind: 'text',
content: `## 日志分析报告
### 🔴 高危:暴力破解尝试
**IP: 203.0.113.42**(来源:外部,UA: python-requests
在 **10:16:0510:16:11** 的 6 秒内向 /admin 路径连续发送了 **7 次请求**,典型的暴力破解模式:
| 时间 | 请求 | 状态 | 说明 |
|------|------|------|------|
| +0s | GET /admin | 403 | 探路——发现 /admin 存在 |
| +1s | GET /admin/config | 404 | 试图访问敏感配置 |
| +2~6s | POST /admin/login ×5 | 401 | 连续尝试登录——5 次均失败 |
**建议措施:**
- 🔒 立即对该 IP 加入 WAF 黑名单
- 📋 检查 fail2ban 或类似自动封禁机制是否正常运行
- 🚨 确认 admin 端点的访问控制策略(当前 403 正确拦截,但暴力破解仍消耗服务器资源)
---
### 🟡 中危:/health 端点间歇 503
**节点 10.0.0.6** 在 **10:22:0010:22:10** 的 10 秒内连续返回 503,随后恢复 200。而 **10.0.0.5** 一直正常。
这与上周分析中定时任务导致的 503 抖动模式一致。建议检查该节点上是否在整点前后有定时任务(如日志轮转、缓存预热等)。
**建议措施:**
- ⏰ 检查 10.0.0.6 的 cron 配置
- 📊 添加更细粒度的 /health 监控(响应耗时 + 组件健康分别上报)
- 🔄 考虑滚动重启使两个节点的健康检查窗口错开
---
### 🟢 常规观察
- **192.168.1.10** — 内部 API 客户端,访问模式正常(用户/订单/商品查询)
- **192.168.1.20** — 一次正常登录(POST /api/login → 302 重定向)
- **198.51.100.7** — 正常用户访问,加载了首页 + CSS + JS
- **192.168.1.30** — 分页浏览商品,行为正常
- 无性能异常(无超时记录或慢请求)
---
### 📋 总结优先级
| 优先级 | 问题 | 影响 |
|--------|------|------|
| P0 | 203.0.113.42 暴力破解 | 安全风险,立即封禁 |
| P1 | 10.0.0.6 间歇 503 | 稳定性风险,排查定时任务 |
| P3 | 整体 40% 非 2xx | 排除以上两者后正常 |
需要我进一步分析某个方面吗?比如用 search_knowledge 查询 203.0.113.42 是否在已知攻击数据库中出现过?`,
},
],
timestamp: now - 420000,
},
],
}
+116
View File
@@ -0,0 +1,116 @@
{
"version": "1.0",
"model": "claude-opus-4-8",
"messages": [
{
"id": "f-1",
"role": "system",
"segments": [
{
"kind": "static_var",
"name": "current_date",
"value": "2026年6月7日",
"description": "当前对话日期"
},
{
"kind": "system_prompt",
"content": "你是 Claude,具备 Anthropic Skills 机制的 HCI 研究助手。\n\n你加载了以下从官方 skills 仓库的 SKILL.md 文件解析得到的 skills。\n每个 skill 的 YAML frontmattername + description)始终在你的上下文中可用;\n当 skill 被触发时,SKILL.md 的完整 body 会被加载。",
"collapsed": true
},
{
"kind": "skills",
"description": "以下 skills 来自 Anthropic 官方 skills 仓库,通过 parseSkillMarkdown() 从 SKILL.md 文件解析。name 和 description 取自 YAML frontmatterL1),body 作为完整指令(L2/L3)。",
"items": [
{ "name": "webapp-testing", "description": "Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs." },
{ "name": "pdf", "description": "Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill." },
{ "name": "doc-coauthoring", "description": "Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks." },
{ "name": "mcp-builder", "description": "Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK)." }
],
"collapsed": false
},
{
"kind": "tool_overview",
"items": [
{
"name": "run_skill",
"description": "执行一个 Skill",
"parameters": "skill_name: string, input: string",
"schema": {
"type": "object",
"properties": {
"skill_name": { "type": "string", "description": "Skill 名称" },
"input": { "type": "string", "description": "传递给 skill 的输入" }
},
"required": ["skill_name", "input"]
}
}
],
"collapsed": true
}
],
"timestamp": 1780898040000
},
{
"id": "f-2",
"role": "user",
"segments": [
{
"kind": "static_var",
"name": "user_name",
"value": "小明",
"description": "当前用户名"
},
{
"kind": "text",
"content": "我在做一个 HCI 课程项目——一个 React 聊天界面。本地 dev server 在 http://localhost:5173 运行。\n\n我想验证几个功能:\n1. 页面是否正常加载\n2. 场景切换按钮是否响应点击\n3. 右侧的 Protocol Panel 是否正确显示 JSON\n\n能帮我自动化测试一下吗?"
}
],
"timestamp": 1780898100000
},
{
"id": "f-3",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "你的测试需求非常适合用 **/webapp-testing** skill 来处理——它正是为本地 web 应用的 Playwright 自动化测试设计的。\n\n让我触发这个 skill 来生成测试脚本。它来自 Anthropic 官方 skills 仓库,通过 `parseSkillMarkdown()` 解析 SKILL.md 的 YAML frontmatter 和 Markdown body\n\n> **解析结构:**\n> - **L1** — `name: webapp-testing` + `description`(从 frontmatter 提取)\n> - **L2** — body 摘要(前 10 行 + 统计信息)\n> - **L3** — 完整 SKILL.md 原始内容(点击「查看原始 SKILL.md」展开)"
},
{
"kind": "tool_call_request",
"toolName": "run_skill",
"arguments": {
"skill_name": "webapp-testing",
"input": "Test a React chat app at http://localhost:5173. Verify: (1) page loads correctly, (2) demo scenario buttons respond to clicks, (3) the right-side Protocol Panel displays JSON correctly. Use Playwright with the reconnaissance-then-action pattern."
},
"collapsed": false
}
],
"timestamp": 1780898120000
},
{
"id": "f-4",
"role": "assistant",
"segments": [
{
"kind": "tool_call_result",
"toolName": "run_skill",
"success": true,
"result": "[/webapp-testing] SKILL.md 已加载\n以下完整 body 已注入上下文,agent 将基于这些指令执行:\n\n# Web Application Testing\n\nTo test local web applications, write native Python Playwright scripts.\n\n**Helper Scripts Available**:\n- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)\n\n**Always run scripts with `--help` first** to see usage.\n\n## Decision Tree: Choosing Your Approach\n\n```\nUser task → Is it static HTML?\n ├─ Yes → Read HTML file directly to identify selectors\n │ ├─ Success → Write Playwright script using selectors\n │ └─ Fails/Incomplete → Treat as dynamic (below)\n │\n └─ No (dynamic webapp) → Is the server already running?\n ├─ No → Run: python scripts/with_server.py --help\n │ Then use the helper + write simplified Playwright script\n │\n └─ Yes → Reconnaissance-then-action:\n 1. Navigate and wait for networkidle\n 2. Take screenshot or inspect DOM\n 3. Identify selectors from rendered state\n 4. Execute actions with discovered selectors\n```\n\n## Reconnaissance-Then-Action Pattern\n\n1. **Inspect rendered DOM** — take screenshot + page.content() + locate elements\n2. **Identify selectors** from inspection results\n3. **Execute actions** using discovered selectors\n\n(完整 body 共 96 行,详见上方 skills 面板 L3",
"collapsed": false
}
],
"timestamp": 1780898130000
},
{
"id": "f-5",
"role": "assistant",
"segments": [
{
"kind": "text",
"content": "## 测试结果:全部通过 ✅\n\n`/webapp-testing` skill 使用了 **reconnaissance-then-action** 模式:\n\n1. 先截图 + 检查 DOM 获取真实渲染状态\n2. 从渲染结果中识别选择器(而非猜测)\n3. 用发现的选择器执行操作\n\n### 关于 Skills 的 HCI 应用\n\n这个 demo 展示了 **Anthropic Skills 格式的关键优势**\n\n| 层级 | 内容 | HCI 意义 |\n|------|------|---------|\n| L1 | name + description | **识别**recognition over recall |\n| L2 | body 摘要 + 触发条件 | **评估**(用户判断 skill 是否适用) |\n| L3 | 完整 SKILL.md body | **理解**(透明化——用户可审阅完整指令) |\n\n这种设计直接对应 Shneiderman 的 \"Overview first, zoom and filter, then details-on-demand\" 信息可视化 mantra。"
}
],
"timestamp": 1780898160000
}
]
}
-210
View File
@@ -1,210 +0,0 @@
/**
* Demo F — 真实 Anthropic Skills(从 SKILL.md 解析)
*
* 场景:用户请求帮助完成网页应用的 Playwright 测试,
* Agent 检测到合适 skill 并建议触发 /webapp-testing。
*
* 关键展示:
* 1. Skills 使用了真实的 Anthropic SKILL.md 格式
* 2. parseSkillMarkdown() 解析 YAML frontmatter → SkillItem
* 3. SkillsView 渐进式披露 L1→L2→L3L3 显示原始 SKILL.md body
* 4. Agent 以工具调用方式触发 skill
* 5. 触发后 SKILL.md body 以 tool_call_result 返回并注入上下文(f-4
* 6. 对话中展示 static_var 展开(日期、用户身份)
*/
import type { PromptEnvelope } from '../../types/protocol'
import { getRealSkills } from '../skills-loader'
const now = Date.now()
export const demoF: PromptEnvelope = {
version: '1.0',
model: 'claude-opus-4-8',
messages: [
// --- System message: 加载真实 Anthropic skills ---
{
id: 'f-1',
role: 'system',
segments: [
{
kind: 'static_var',
name: 'current_date',
value: '2026年6月7日',
description: '当前对话日期',
},
{
kind: 'system_prompt',
content: `你是 Claude,具备 Anthropic Skills 机制的 HCI 研究助手。
你加载了以下从官方 skills 仓库的 SKILL.md 文件解析得到的 skills。
每个 skill 的 YAML frontmattername + description)始终在你的上下文中可用;
当 skill 被触发时,SKILL.md 的完整 body 会被加载。`,
collapsed: true,
},
{
kind: 'skills',
description:
'以下 skills 来自 Anthropic 官方 skills 仓库,通过 parseSkillMarkdown() 从 SKILL.md 文件解析。name 和 description 取自 YAML frontmatterL1),body 作为完整指令(L2/L3)。',
items: getRealSkills(['webapp-testing', 'pdf', 'doc-coauthoring', 'mcp-builder']),
collapsed: false, // 默认展开展示解析效果
},
{
kind: 'tool_overview',
items: [
{
name: 'run_skill',
description: '执行一个 Skill',
parameters: 'skill_name: string, input: string',
schema: {
type: 'object',
properties: {
skill_name: { type: 'string', description: 'Skill 名称' },
input: { type: 'string', description: '传递给 skill 的输入' },
},
required: ['skill_name', 'input'],
},
},
],
collapsed: true,
},
],
timestamp: now - 360000,
},
// --- User 提出测试需求 ---
{
id: 'f-2',
role: 'user',
segments: [
{
kind: 'static_var',
name: 'user_name',
value: '小明',
description: '当前用户名',
},
{
kind: 'text',
content: `我在做一个 HCI 课程项目——一个 React 聊天界面。本地 dev server 在 http://localhost:5173 运行。
我想验证几个功能:
1. 页面是否正常加载
2. 场景切换按钮是否响应点击
3. 右侧的 Protocol Panel 是否正确显示 JSON
能帮我自动化测试一下吗?`,
},
],
timestamp: now - 300000,
},
// --- Assistant 识别需求,触发 webapp-testing skill ---
{
id: 'f-3',
role: 'assistant',
segments: [
{
kind: 'text',
content: `你的测试需求非常适合用 **/webapp-testing** skill 来处理——它正是为本地 web 应用的 Playwright 自动化测试设计的。
让我触发这个 skill 来生成测试脚本。它来自 Anthropic 官方 skills 仓库,通过 \`parseSkillMarkdown()\` 解析 SKILL.md 的 YAML frontmatter 和 Markdown body
> **解析结构:**
> - **L1** — \`name: webapp-testing\` + \`description\`(从 frontmatter 提取)
> - **L2** — body 摘要(前 10 行 + 统计信息)
> - **L3** — 完整 SKILL.md 原始内容(点击「查看原始 SKILL.md」展开)`,
},
{
kind: 'tool_call_request',
toolName: 'run_skill',
arguments: {
skill_name: 'webapp-testing',
input: 'Test a React chat app at http://localhost:5173. Verify: (1) page loads correctly, (2) demo scenario buttons respond to clicks, (3) the right-side Protocol Panel displays JSON correctly. Use Playwright with the reconnaissance-then-action pattern.',
},
collapsed: false,
},
],
timestamp: now - 280000,
},
// --- Skill body 加载:SKILL.md 作为 tool 结果返回 ---
{
id: 'f-4',
role: 'assistant',
segments: [
{
kind: 'tool_call_result',
toolName: 'run_skill',
success: true,
result: `[/webapp-testing] SKILL.md 已加载
以下完整 body 已注入上下文,agent 将基于这些指令执行:
# Web Application Testing
To test local web applications, write native Python Playwright scripts.
**Helper Scripts Available**:
- \`scripts/with_server.py\` - Manages server lifecycle (supports multiple servers)
**Always run scripts with \`--help\` first** to see usage.
## Decision Tree: Choosing Your Approach
\`\`\`
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│ ├─ Success → Write Playwright script using selectors
│ └─ Fails/Incomplete → Treat as dynamic (below)
└─ No (dynamic webapp) → Is the server already running?
├─ No → Run: python scripts/with_server.py --help
│ Then use the helper + write simplified Playwright script
└─ Yes → Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors
\`\`\`
## Reconnaissance-Then-Action Pattern
1. **Inspect rendered DOM** — take screenshot + page.content() + locate elements
2. **Identify selectors** from inspection results
3. **Execute actions** using discovered selectors
(完整 body 共 96 行,详见上方 skills 面板 L3)`,
collapsed: false,
},
],
timestamp: now - 270000,
},
// --- Assistant 总结 ---
{
id: 'f-5',
role: 'assistant',
segments: [
{
kind: 'text',
content: `## 测试结果:全部通过 ✅
\`/webapp-testing\` skill 使用了 **reconnaissance-then-action** 模式:
1. 先截图 + 检查 DOM 获取真实渲染状态
2. 从渲染结果中识别选择器(而非猜测)
3. 用发现的选择器执行操作
### 关于 Skills 的 HCI 应用
这个 demo 展示了 **Anthropic Skills 格式的关键优势**
| 层级 | 内容 | HCI 意义 |
|------|------|---------|
| L1 | name + description | **识别**recognition over recall |
| L2 | body 摘要 + 触发条件 | **评估**(用户判断 skill 是否适用) |
| L3 | 完整 SKILL.md body | **理解**(透明化——用户可审阅完整指令) |
这种设计直接对应 Shneiderman 的 "Overview first, zoom and filter, then details-on-demand" 信息可视化 mantra。`,
},
],
timestamp: now - 240000,
},
],
}
+152
View File
@@ -0,0 +1,152 @@
/**
* Demo JSON 加载器 —— 将 JSON 文件还原为类型安全的 PromptEnvelope。
*
* 职责:
* 1. validateEnvelope() — 运行时类型守卫,断言 JSON 结构符合协议
* 2. hydrateSkills() — 根据 skill name 从 skills.ts / skills-loader.ts 补全 body
*
* 加载流程:
* JSON import (unknown) → validateEnvelope() → PromptEnvelope → hydrateSkills() → 完整 PromptEnvelope
*/
import type { PromptEnvelope, Message, Segment, SkillItem } from '../../types/protocol'
import { ALL_SKILLS, getSkills } from '../skills'
import { PARSED_SKILLS, toSkillItem } from '../skills-loader'
// ============================================================
// 类型守卫
// ============================================================
const VALID_SEGMENT_KINDS = new Set([
'text',
'static_var',
'system_prompt',
'memory',
'skills',
'tool_overview',
'tool_call_request',
'tool_call_result',
'document',
'long_text',
'media',
])
const VALID_ROLES = new Set(['system', 'user', 'assistant', 'tool'])
function assert(cond: boolean, msg: string): asserts cond {
if (!cond) throw new Error(`[demos-loader] 验证失败: ${msg}`)
}
function isObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v)
}
function validateSegment(seg: unknown, msgIdx: number): Segment {
assert(isObject(seg), `message[${msgIdx}] segment 不是对象`)
const s = seg as Record<string, unknown>
assert(
typeof s.kind === 'string' && VALID_SEGMENT_KINDS.has(s.kind),
`message[${msgIdx}] segment.kind "${String(s.kind)}" 无效(合法值: ${[...VALID_SEGMENT_KINDS].join(', ')}`
)
// 对于所有 segment 类型,基础的类型检查已经足够——详细的字段级验证由 JSON Schema 在开发时完成
// 这里只保证 kind 字段存在且有效,其余字段按原样信任(因为数据来自 JSON 文件,已在 VSCode 中被 Schema 校验)
return s as unknown as Segment
}
function validateMessage(msg: unknown, idx: number): Message {
assert(isObject(msg), `messages[${idx}] 不是对象`)
const m = msg as Record<string, unknown>
assert(typeof m.id === 'string', `messages[${idx}].id 必须是字符串`)
assert(
typeof m.role === 'string' && VALID_ROLES.has(m.role),
`messages[${idx}].role "${String(m.role)}" 无效`
)
assert(Array.isArray(m.segments), `messages[${idx}].segments 必须是数组`)
assert(m.segments.length > 0, `messages[${idx}].segments 不能为空`)
assert(typeof m.timestamp === 'number', `messages[${idx}].timestamp 必须是数字`)
return {
id: m.id as string,
role: m.role as Message['role'],
segments: m.segments.map((s, si) => validateSegment(s, idx)),
timestamp: m.timestamp as number,
}
}
/**
* 验证并类型断言一个 JSON 对象是否符合 PromptEnvelope 协议。
* 抛出带有描述性消息的 Error 如果格式不符。
*/
export function validateEnvelope(json: unknown): PromptEnvelope {
assert(isObject(json), '顶层必须是对象')
const env = json as Record<string, unknown>
assert(env.version === '1.0', `version 必须是 "1.0",实际为 "${String(env.version)}"`)
assert(Array.isArray(env.messages), 'messages 必须是数组')
assert(env.messages.length > 0, 'messages 不能为空')
const messages = env.messages.map((m, i) => validateMessage(m, i))
return {
version: '1.0',
model: typeof env.model === 'string' ? env.model : undefined,
messages,
}
}
// ============================================================
// Skills 水合(补全 body
// ============================================================
/** 合并所有 skill 源(手写 + SKILL.md 解析)为一个查询表 */
function buildSkillLookup(): Map<string, SkillItem> {
const map = new Map<string, SkillItem>()
// 手写 skillsskills.ts
for (const skill of Object.values(ALL_SKILLS)) {
map.set(skill.name, skill)
}
// 真实 SKILL.md 解析(skills-loader.ts
for (const parsed of Object.values(PARSED_SKILLS)) {
if (!map.has(parsed.name)) {
map.set(parsed.name, toSkillItem(parsed))
}
}
return map
}
/**
* 根据 skill name 从 skill 注册表补全 body。
* 处理所有 messages 中所有 skills segment 的 items。
* 如果某个 name 在注册表中找不到,body 保留为空字符串(生产环境不应发生此情况)。
*/
export function hydrateSkills(envelope: PromptEnvelope): PromptEnvelope {
const lookup = buildSkillLookup()
for (const msg of envelope.messages) {
for (const seg of msg.segments) {
if (seg.kind === 'skills') {
seg.items = seg.items.map((item: SkillItem) => {
const found = lookup.get(item.name)
if (found) {
return { ...item, body: found.body }
}
console.warn(`[demos-loader] skill "${item.name}" 在注册表中未找到,body 将为空`)
return { ...item, body: '' }
})
}
}
}
return envelope
}
/**
* 从 JSON 对象加载完整的 PromptEnvelope(验证 + skill 水合)。
* 这是推荐的外部入口函数。
*/
export function loadEnvelope(json: unknown): PromptEnvelope {
const envelope = validateEnvelope(json)
return hydrateSkills(envelope)
}
+43
View File
@@ -0,0 +1,43 @@
{
"version": "1.0",
"description": "Prompt Envelope Demo 场景索引",
"scenarios": [
{
"id": "a",
"file": "demo-a.json",
"label": "场景 A",
"description": "基础对话 + System Prompt + Memory"
},
{
"id": "b",
"file": "demo-b.json",
"label": "场景 B",
"description": "工具调用:请求 → 执行(成功 & 失败)"
},
{
"id": "c",
"file": "demo-c.json",
"label": "场景 C 📄",
"description": "文档解析:点击「查看解析」看 AI 如何提取文档内容"
},
{
"id": "d",
"file": "demo-d.json",
"label": "场景 D ⭐",
"description": "综合:覆盖全部 11 种 Segment"
},
{
"id": "f",
"file": "demo-f.json",
"label": "场景 F 📁",
"description": "真实 Anthropic SkillsSKILL.md 文件加载 + parseSkillMarkdown 解析)",
"default": true
},
{
"id": "e",
"file": "demo-e.json",
"label": "场景 E 🔍",
"description": "日志分析:异常检测 + 安全审计 + 性能分析"
}
]
}
+205
View File
@@ -0,0 +1,205 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://hci-demo.local/prompt-envelope.schema.json",
"title": "Prompt Envelope Protocol",
"description": "Prompt Envelope 协议格式 — 消息由若干带类型的 Segment 组成,每种 Segment 有独立的视觉呈现。",
"type": "object",
"required": ["version", "messages"],
"properties": {
"version": {
"type": "string",
"const": "1.0",
"description": "协议版本号"
},
"model": {
"type": "string",
"description": "导出时使用的模型名称"
},
"messages": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/Message" }
}
},
"$defs": {
"Message": {
"type": "object",
"required": ["id", "role", "segments", "timestamp"],
"properties": {
"id": { "type": "string" },
"role": {
"type": "string",
"enum": ["system", "user", "assistant", "tool"]
},
"segments": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/Segment" }
},
"timestamp": {
"type": "number",
"description": "Unix epoch 毫秒"
}
}
},
"Segment": {
"oneOf": [
{ "$ref": "#/$defs/TextSegment" },
{ "$ref": "#/$defs/StaticVarSegment" },
{ "$ref": "#/$defs/SystemPromptSegment" },
{ "$ref": "#/$defs/MemorySegment" },
{ "$ref": "#/$defs/SkillsSegment" },
{ "$ref": "#/$defs/ToolOverviewSegment" },
{ "$ref": "#/$defs/ToolCallRequestSegment" },
{ "$ref": "#/$defs/ToolCallResultSegment" },
{ "$ref": "#/$defs/DocumentSegment" },
{ "$ref": "#/$defs/LongTextSegment" },
{ "$ref": "#/$defs/MediaSegment" }
]
},
"TextSegment": {
"type": "object",
"required": ["kind", "content"],
"properties": {
"kind": { "type": "string", "const": "text" },
"content": { "type": "string" }
}
},
"StaticVarSegment": {
"type": "object",
"required": ["kind", "name", "value"],
"properties": {
"kind": { "type": "string", "const": "static_var" },
"name": { "type": "string", "description": "模板变量名" },
"value": { "type": "string", "description": "展开后的值" },
"description": { "type": "string", "description": "简短说明" }
}
},
"SystemPromptSegment": {
"type": "object",
"required": ["kind", "content", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "system_prompt" },
"content": { "type": "string" },
"collapsed": { "type": "boolean" }
}
},
"MemorySegment": {
"type": "object",
"required": ["kind", "items", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "memory" },
"description": { "type": "string" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["title", "content"],
"properties": {
"title": { "type": "string" },
"content": { "type": "string" }
}
}
},
"collapsed": { "type": "boolean" }
}
},
"SkillsSegment": {
"type": "object",
"required": ["kind", "items", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "skills" },
"description": { "type": "string" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["name", "description"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" }
}
}
},
"collapsed": { "type": "boolean" }
}
},
"ToolOverviewSegment": {
"type": "object",
"required": ["kind", "items", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "tool_overview" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["name", "description", "parameters"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"parameters": { "type": "string" },
"schema": { "type": "object" }
}
}
},
"collapsed": { "type": "boolean" }
}
},
"ToolCallRequestSegment": {
"type": "object",
"required": ["kind", "toolName", "arguments", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "tool_call_request" },
"toolName": { "type": "string" },
"arguments": { "type": "object" },
"collapsed": { "type": "boolean" }
}
},
"ToolCallResultSegment": {
"type": "object",
"required": ["kind", "toolName", "result", "success", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "tool_call_result" },
"toolName": { "type": "string" },
"result": { "type": "string" },
"success": { "type": "boolean" },
"collapsed": { "type": "boolean" }
}
},
"DocumentSegment": {
"type": "object",
"required": ["kind", "fileName", "mimeType", "snippet", "sizeBytes"],
"properties": {
"kind": { "type": "string", "const": "document" },
"fileName": { "type": "string" },
"mimeType": { "type": "string" },
"snippet": { "type": "string" },
"sizeBytes": { "type": "number" },
"parsedContent": { "type": "string" }
}
},
"LongTextSegment": {
"type": "object",
"required": ["kind", "content", "charCount", "collapsed"],
"properties": {
"kind": { "type": "string", "const": "long_text" },
"content": { "type": "string" },
"charCount": { "type": "number" },
"collapsed": { "type": "boolean" }
}
},
"MediaSegment": {
"type": "object",
"required": ["kind", "mediaType", "url"],
"properties": {
"kind": { "type": "string", "const": "media" },
"mediaType": { "type": "string", "enum": ["image", "audio", "video"] },
"url": { "type": "string" },
"altText": { "type": "string" }
}
}
}
}