11 KiB
Prompt Envelope Protocol
让 LLM 上下文在聊天界面中可见、可读、可理解。
问题:Prompt 被过度封装,用户不知道真正的输入是什么
以 ChatGPT、Claude 为代表的 AI 聊天产品已拥有数亿用户。这些产品的交互界面模拟的是「人对人」的对话——一个输入框,一段回复。用户看到自己打了一句话,AI 回了一句话。
但模型真正收到的,远不止这句话。
在用户输入的文本被发送给 LLM 之前,系统在后台拼接了大量的上下文:
- System Prompt —— 决定了回复的风格、边界、角色设定
- User Memory —— 跨对话持久化的用户画像,在每次请求时被注入
- Tools & Skills 的声明 —— 函数签名、JSON Schema、触发条件的完整描述
- 模板变量的值 ——
{{user_name}}被替换为"小明",但用户看到的是变量名还是值?
对于普通用户,这可能无所谓。但对于需要精确控制模型行为的进阶用户(开发者、研究者、prompt engineer),这种不透明是致命的:
- 你精心设计了一段 prompt,但不知道 system prompt 是否和它冲突
- 你修改了一条 memory,但不确定新的值是否真的被注入了每一次请求
- 你添加了一个 tool,但看不到它的 schema 在上下文中以什么形式出现
- 你粘贴了一篇长论文,但不知道全文都被包含还是被截断
- 你看到 AI 回复了错误结果,但无法排查是哪个上下文片段导致了问题
界面上看起来是「一句话的对话」,实际发送给模型的是一份由十几个组件拼接而成的结构化文档。 聊天 UI 对这一切做了完全的封装——把复杂的 prompt 构造过程包装成了一个简单的 textarea。
这个问题不是"看不到 AI 的推理过程",而是"看不到自己的输入到底是什么"。
设计问题
当前的聊天界面将 prompt 构造的复杂性过度封装在单一输入框中。用户以为自己发送的是文本,实际发送的是一个经过层层拼接的复合体。界面模拟的是"人说了一句话"的隐喻,但模型侧的真实情况是"一份多来源、多类型、多优先级的结构化上下文文档"。
HCI 告诉我们:界面的信息架构应该反映系统的心智模型。当用户不知道模型真正收到了什么,他们就无法调试、无法精确控制、无法建立信任。
方案:Prompt Envelope Protocol
本项目的答案是:一种将 LLM 完整上下文结构化的协议和对应的可视界面。
核心思想
不是让聊天消息变成纯文本,而是让每条消息由若干带明确类型的 Segment(片段)组成:
一条消息不是 "你好,帮我审阅这篇论文"
而是:
├─ text segment "你好,帮我审阅这篇论文"
├─ long_text segment [论文全文 — 默认折叠,点击展开]
├─ document segment 📄 paper-draft.pdf (2.3MB)
└─ media segment 🖼️ 图3:实验组vs对照组
每种 Segment 在界面中有独立的视觉呈现——系统指令是折叠的灰色面板,用户记忆是紫色的记忆卡片,工具请求是深色终端风格的代码块。
设计原则
| 原则 | 说明 |
|---|---|
| 信息密度梯度 | 核心对话文本优先可见;元信息(system prompt / memory / tools)默认折叠 |
| 颜色编码系统 | 灰=系统 · 紫=记忆 · 绿=技能 · 橙=工具 · 蓝=变量 · 深色=工具调用 |
| 协议即视图 | 折叠状态内嵌于数据结构中;相同协议数据在任何渲染器下产生相同视图 |
| 可导出,不锁死 | 协议可以导出为标准 OpenAI Chat Format,不依赖本项目的 UI |
方法:区分 9 种上下文类型
通过对主流 LLM 聊天产品(ChatGPT、Claude、Kimi、豆包)的逆向分析,以及对 127 名 LLM 用户行为研究文献的查阅,识别出 9 种需要差异化呈现的上下文类型:
1. 文本(text)
用户和 AI 的直接对话内容。始终可见,不做折叠。用 Markdown 渲染保留结构化表达。
2. 静态变量(static_var)
如 {{user_name}}、{{current_date}}——在对话开始前被替换为具体值。提取为对话区顶部的会话变量横栏,不在消息气泡中占用空间。
会话变量 {{user_name}} → 小明 {{current_date}} → 2026年6月8日
3. System Prompt(system_prompt)
模型的行为准则和角色设定。默认折叠为灰色面板,标注行数。支持展开查看——当用户想知道"为什么 AI 这样说话"。
4. 用户记忆(memory)
跨对话持久化的用户信息。默认折叠为紫色面板,展开后以标题列表展示每条记忆。用户可以质疑不准确的记忆。
5. Skills
模型可调用的技能,遵循 Anthropic SKILL.md 规范。默认折叠为绿色面板。展开后实现渐进式披露:
- L1:名称 + 描述(始终可见)
- L2:完整指令 body(点击展开——提示"触发时会加载到 LLM 上下文")
6. 工具总览(tool_overview)
模型可使用的工具清单。默认折叠为橙色面板。每项工具可独立展开其 JSON Schema 定义。
7. 工具调用(tool_call)
拆分为两个子类型:
| 子类型 | 视觉 | 默认状态 | 说明 |
|---|---|---|---|
tool_call_request |
深色终端风格代码块 | 展开 | 绿色方法名 + 参数键值行 |
tool_call_result |
绿/红 状态条 | 折叠(成功)/ 展开(失败) | 一眼看出是否出错 |
在界面中,工具调用会被自动拆分为独立的 tool 角色消息,与正常的对话消息交替排列。
8. 传文档(document)
用户上传的文件。以文件卡片展示:图标 + 文件名 + 大小 + 前 200 字预览。点击「查看解析」可展开 AI 提取的文档内容。
9. 长文本素材(long_text)
用户粘贴的长篇文本(>500 字)。默认折叠,展示前 2 行预览 + 字数统计。点击展开显示全文(Markdown 渲染)。
附加:多模态(media)
图片、音频、视频。图片有有效 URL 时直接渲染缩略图;加载失败或无 URL 时回退为图标占位 + alt 文本。音频/视频展示类型标签。
协议格式
Prompt Envelope(顶层文档)
interface PromptEnvelope {
version: '1.0'
model?: string // 导出时使用的模型名
messages: Message[]
}
Message(单条消息)
interface Message {
id: string
role: 'system' | 'user' | 'assistant' | 'tool'
segments: Segment[] // 有序的片段列表
timestamp: number
}
Segment(片段联合类型)
每种 Segment 有独有的 kind 字段,UI 据此决定渲染方式:
text | static_var | system_prompt | memory | skills |
tool_overview | tool_call_request | tool_call_result |
document | long_text | media
完整类型定义见 src/types/protocol.ts。
导出兼容
协议可无损导出为标准 OpenAI Chat Completions 格式({ model, messages, tools }),确认此设计不是"另一种 agent 运行时",而是一种对标准协议的格式化表达层。
技术实现
技术栈
| 层 | 选型 |
|---|---|
| 框架 | React 18 + TypeScript |
| 构建 | Vite |
| 样式 | Tailwind CSS |
| 图标 | Lucide React |
| Markdown | react-markdown + remark-gfm |
| 测试 | Vitest |
MVP 范围
- ✅ 纯前端 React 应用,无后端、无真实 LLM 调用
- ✅ 6 个 Demo 场景(A–F),覆盖全部 Segment 类型
- ✅ 双栏布局:对话视图 + 协议面板(实时 JSON 预览)
- ✅ 双 Tab 协议面板:OpenAI Format / Raw Protocol,支持复制和下载
- ✅ 4 个真实 Anthropic SKILL.md 文件加载 +
parseSkillMarkdown()自研解析器 - ✅ 26 个单元测试覆盖导出逻辑和 SKILL.md 解析
- ❌ 不接入真实 LLM API
- ❌ 不做后端/数据库/用户认证
快速开始
git clone <this-repo>
cd hci
npm install
npm run dev # 启动开发服务器 → http://localhost:5173
npm test # 运行全部 26 个单元测试
npm run build # 生产构建
文件概览
src/
├── types/protocol.ts # 协议类型定义(11 种 Segment)
├── utils/
│ ├── export.ts # 导出 → OpenAI Chat Format
│ └── parseSkill.ts # SKILL.md YAML 解析器(自研)
├── data/
│ ├── demos/ # 6 个 Demo JSON 文件 + manifest
│ │ └── demos-loader.ts # 运行时验证 + skill body 水合
│ ├── skills.ts # 8 个手写自定义 skill
│ ├── skills-loader.ts # 4 个真实 Anthropic SKILL.md
│ └── skills/ # SKILL.md 源文件
├── components/
│ ├── SegmentRenderer.tsx # 按 kind 路由分发
│ ├── CollapsiblePanel.tsx # 通用折叠容器
│ ├── MarkdownRenderer.tsx # 共享 Markdown 渲染(所有文本组件)
│ ├── MessageList.tsx # 消息预处理 + 列表
│ ├── MessageBubble.tsx # 消息气泡容器
│ ├── SessionBar.tsx # 会话变量横栏
│ ├── ProtocolPanel.tsx # 右侧协议面板(双 Tab)
│ └── segments/ # 11 个 Segment View 组件
├── context/ChatContext.tsx # 全局状态
├── __tests__/
│ ├── export.test.ts # 导出逻辑(~20 个用例)
│ └── parseSkill.test.ts # SKILL.md 解析(7 个用例)
└── App.tsx # 顶层布局
设计反思
这个项目本质上在探索一个更根本的问题:当一条"聊天消息"背后实际是十几个上下文组件拼接而成的结构化文档,聊天界面该怎样诚实地呈现它?
传统的聊天界面诞生于"人对人"的隐喻——一个输入框,一个输出框。但当这个输入框背后连接的是一个拥有 system prompt、记忆、工具声明和变量注入的复杂 prompt 构造系统时,把一切封装在简单的 chat bubble 里就是在对用户撒谎。用户以为自己发送的是纯文本,实际被发送的是一份经过大量预处理的复合文档。
Prompt Envelope Protocol 尝试将"人机对话"的界面从隐藏复杂性的 textarea 升级为诚实可见的信息架构。让对话保持流畅,同时让 prompt 的真实组成变得可见。这不是一个关于"AI 在做什么"的设计——这是关于"你到底对 AI 说了什么"的设计。