Files

319 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
本项目的答案是两层设计:
- **协议层** —— 一种结构化的数据格式,用带类型的 Segment 描述 LLM 上下文
- **UI 层** —— 一个参考实现,展示协议如何被渲染为可读的聊天界面
### 协议:每条消息 = Segments 的有序列表
协议定义的不是"聊天文本",而是**上下文的结构化描述**。一条消息不再是字符串,而是若干带明确 `kind` 的 Segment
```
一条消息不是 "你好,帮我审阅这篇论文"
而是:
├─ text segment "你好,帮我审阅这篇论文"
├─ long_text segment [论文全文,1423 字]
├─ document segment paper-draft.pdf (2.3MB)
└─ media segment image: 图3 实验组vs对照组
```
每个 Segment 的 `kind` 字段声明了"这是一段什么",其余字段携带数据本身。协议只关心 types 和 structure,**不关心颜色、折叠、布局**——那是 UI 层的决策。
### UI:每种类型有独立视觉呈现
在本项目的参考实现中,每种 Segment 被渲染为不同的视觉组件。例如 system prompt 是折叠面板,工具请求是代码块,变量在会话横栏中展示。**但这只是一种可能的 UI,协议本身与视觉无关。**
### 设计原则
**协议原则:**
| 原则 | 说明 |
|------|------|
| **类型明确** | 每种上下文有独立的 Segment kind,无一物混在文本中 |
| **可导出** | 协议可无损导出为标准 OpenAI Chat Format,不被本项目 UI 绑定 |
| **自描述** | 协议数据不依赖外部渲染逻辑——kind 字段即语义标签 |
**UI 原则(本项目的参考实现):**
| 原则 | 说明 |
|------|------|
| **信息密度梯度** | 核心对话文本优先可见;元信息默认折叠 |
| **颜色编码** | 不同类型用不同颜色辅助区分,形成可学习的视觉语言 |
| **折叠默认值在数据中** | segment 的 `collapsed` 字段指定默认状态,保证数据自足 |
---
## 协议:11 种 Segment 类型
通过对主流 LLM 聊天产品的逆向分析,我们识别出 LLM 上下文中 **11 种需要区分类型的信息**。每种在协议中对应一个 Segment kind。
### text
用户和 AI 的直接对话文本。协议中最基本的 Segment。
```typescript
{ kind: 'text', content: string }
```
### static_var
会话级模板变量,在对话开始时被替换为具体值。协议中携带变量名和展开后的值,使替换关系可见。
```typescript
{ kind: 'static_var', name: string, value: string, description?: string }
```
### system_prompt
模型的行为准则和角色设定。协议中为完整文本内容,可标记折叠默认状态。
```typescript
{ kind: 'system_prompt', content: string, collapsed: boolean }
```
### memory
跨对话持久化的用户信息,以标题-内容条目组织。协议中为记忆项列表。
```typescript
{ kind: 'memory', items: MemoryItem[], collapsed: boolean, description?: string }
```
### skills
模型可调用的技能,遵循 Anthropic SKILL.md 规范。协议中每项包含 name、descriptionL1 始终在上下文)和 bodyL2 触发时加载的 Markdown 正文)。
```typescript
{ kind: 'skills', items: SkillItem[], collapsed: boolean, description?: string }
```
### tool_overview
模型可用的工具清单。每项包含名称、描述、参数摘要和可选的 JSON Schema 定义。
```typescript
{ kind: 'tool_overview', items: ToolItem[], collapsed: boolean }
```
### tool_call_request
模型发起的工具调用。协议中携带函数名和参数键值对。
```typescript
{ kind: 'tool_call_request', toolName: string, arguments: Record<string, unknown>, collapsed: boolean }
```
### tool_call_result
工具调用的执行结果。协议中标记成功/失败状态和结果文本。
```typescript
{ kind: 'tool_call_result', toolName: string, result: string, success: boolean, collapsed: boolean }
```
### document
用户上传的文件。协议中存储文件名、MIME 类型、大小和文本预览,可附带解析后的完整内容。
```typescript
{ kind: 'document', fileName: string, mimeType: string, snippet: string, sizeBytes: number, parsedContent?: string }
```
### long_text
用户粘贴的长篇文本素材。协议中携带完整内容和字符数,可标记折叠默认状态。
```typescript
{ kind: 'long_text', content: string, charCount: number, collapsed: boolean }
```
### media
图片、音频、视频等多模态内容。协议中记录媒体类型、URL 和替代文本。
```typescript
{ kind: 'media', mediaType: 'image'|'audio'|'video', url: string, altText?: string }
```
---
## UI 参考实现:差异化视觉呈现
以上是协议层——定义了什么信息存在、以什么结构存在。以下是本项目为协议做的参考 UI。**颜色、折叠行为、布局都是 UI 决策,不是协议规范。**
| Segment | UI 处理 | 交互 |
|---------|--------|------|
| `text` | 正文渲染,支持 Markdown | 无交互 |
| `static_var` | 提取到对话区顶部的会话变量横栏,不在气泡内 | 纯展示 |
| `system_prompt` | 折叠面板,灰色标记,标注行数 | 点击展开 |
| `memory` | 折叠面板,紫色标记,展开为标题-内容列表 | 点击展开 |
| `skills` | 折叠面板,绿色标记,每项支持渐进式披露(L1 名称+描述 → L2 body) | 逐层点击展开 |
| `tool_overview` | 折叠面板,橙色标记,每项可独立展开 JSON Schema | 点击展开 |
| `tool_call_request` | 深色终端风格代码块,参数以标签化键值行展示 | 默认展开;长值折叠 |
| `tool_call_result` | 绿色(成功)/ 红色(失败)状态条 | 成功默认折叠,失败默认展开 |
| `document` | 文件卡片:图标 + 文件名 + 大小 + 文本预览 | 可展开 AI 解析内容 |
| `long_text` | 折叠态前 2 行预览 + 字数标签 | 点击展开全文 |
| `media` | 图片有 URL 时可渲染缩略图;其他类型图标占位 | 加载失败回退为图标 |
完整类型定义见 [`src/types/protocol.ts`](src/types/protocol.ts)。
---
## 协议格式
### Prompt Envelope(顶层文档)
```typescript
interface PromptEnvelope {
version: '1.0'
model?: string // 导出时使用的模型名
messages: Message[]
}
```
### Message(单条消息)
```typescript
interface Message {
id: string
role: 'system' | 'user' | 'assistant' | 'tool'
segments: Segment[] // 有序的片段列表
timestamp: number
}
```
### Segment(片段联合类型)
每种 Segment 有独有的 `kind` 字段,标识其语义类型:
```
text | static_var | system_prompt | memory | skills |
tool_overview | tool_call_request | tool_call_result |
document | long_text | media
```
完整类型定义见 [`src/types/protocol.ts`](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 场景(AF),覆盖全部 Segment 类型
- ✅ 双栏布局:对话视图 + 协议面板(实时 JSON 预览)
- ✅ 双 Tab 协议面板:OpenAI Format / Raw Protocol,支持复制和下载
- ✅ 4 个真实 Anthropic SKILL.md 文件加载 + `parseSkillMarkdown()` 自研解析器
- ✅ 26 个单元测试覆盖导出逻辑和 SKILL.md 解析
- ❌ 不接入真实 LLM API
- ❌ 不做后端/数据库/用户认证
### 快速开始
```bash
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 说了什么"的设计。**