Skip to content

第 18 章 · 上下文序列化与浏览器使用

本章目标:掌握 Context 的 JSON 序列化/反序列化实现聊天记录持久化与服务间迁移,了解在浏览器环境运行 pi-ai 的方式、限制与安全边界。

18.1 Context 就是纯 JSON

pi-ai 的 Context 对象(systemPrompt + messages)是普通可 JSON 序列化的结构——不需要任何自定义编解码器:

typescript
const context: Context = {
  systemPrompt: 'You are a helpful assistant.',
  messages: [
    { role: 'user', content: 'What is TypeScript?', timestamp: Date.now() },
  ],
};

const model = models.getModel('openai', 'gpt-4o-mini')!;
const response = await models.complete(model, context);
context.messages.push(response);

// 一行序列化整个上下文
const serialized = JSON.stringify(context);
// 存数据库、localStorage、文件……随你便
localStorage.setItem('conversation', serialized);

18.2 反序列化并继续对话

恢复同样简单——JSON.parse 回来就能直接用,甚至可以换一个模型继续:

typescript
// 之后:反序列化并接着聊
const restored: Context = JSON.parse(localStorage.getItem('conversation')!);
restored.messages.push({
  role: 'user',
  content: 'Tell me more about its type system',
  timestamp: Date.now(),
});

// 换个模型继续,上下文无缝衔接
const newModel = models.getModel('anthropic', 'claude-3-5-haiku-20241022')!;
const continuation = await models.complete(newModel, restored);

这组能力组合出的典型产品功能:会话分享链接(把序列化串放进 URL)、客服工单转移(A 客服的上下文交给 B)、离线草稿箱。

18.3 浏览器端运行

pi-ai 支持浏览器环境。核心入口与 Provider 工厂都是无副作用的,可以干净地打包进前端 bundle:

typescript
import { createModels } from '@earendil-works/pi-ai';
import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';

const models = createModels();
models.setProvider(anthropicProvider());

const model = models.getModel('anthropic', 'claude-3-5-haiku-20241022')!;
const response = await models.complete(model, {
  messages: [{ role: 'user', content: 'Hello!', timestamp: Date.now() }],
}, {
  apiKey: 'your-api-key', // 浏览器没有环境变量,显式传 key
});

浏览器与 Node 的关键差异:

能力Node浏览器
环境变量解析 API Key❌ 需显式传 key 或注入 CredentialStore
Amazon Bedrock (converse-stream)❌ 运行时报错
OAuth 登录流程❌ Node-only(惰性加载不污染 bundle)

CredentialStore 注入

文档建议:pass API keys explicitly — or inject a CredentialStore (e.g. localStorage-backed)——可以实现一个基于 localStorage 的凭据存储注入进去,让 Provider 认证自动从中解析。

18.4 安全警告:前端不放生产 Key

官方原话非常直白:

Security Warning: Exposing API keys in frontend code is dangerous. Anyone can extract and abuse your keys.

正确姿势是后端代理:浏览器 → 你的服务器(持有 key)→ LLM。前端直连只适合内部工具或 demo。

typescript
// 生产架构推荐:前端调自家后端,key 留在服务端
const res = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ messages: context.messages }),
});

18.5 打包与 Tree-shaking

两个打包相关的实用结论:

  1. OAuth 惰性加载——注册支持 OAuth 的 Provider 不会把 Node-only 代码拖进浏览器包;只有真正执行登录才会加载;
  2. 按需引入 Provider——从 @earendil-works/pi-ai/providers/xxx 子路径导入,未引用的 Provider 实现会被 tree-shaking 掉,显著减小产物体积。
typescript
// 只导入需要的 Provider,其余被 tree-shake 掉
import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';

本章小结

  • Context 是纯 JSON 结构,JSON.stringify/parse 即完成持久化与恢复;
  • 序列化能力支撑会话分享、工单转移等典型产品功能;
  • 浏览器可用但有三点限制:无环境变量、Bedrock 不可用、OAuth 仅 Node;
  • 前端暴露 API Key 极其危险,生产必须走后端代理;
  • Provider 从子路径按需导入 + OAuth 惰性加载,bundle 保持干净。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 持久化一个 pi-ai 会话上下文的标准做法是?

2. 浏览器环境中获取 API 认证的正确说法是?

3. 关于在生产前端代码中直接放 API Key,官方的态度是?

4. 把支持 OAuth 的 Provider 注册进浏览器应用,会导致什么?

🛠️ 动手实践

  1. 实现"导出对话为 .json 文件 / 导入恢复"功能,做成第 19 章 CLI 的子命令。
  2. 用 Vite 搭建最小 React 页面,浏览器端跑通一次 complete 调用(demo key)。
  3. 写一个后端代理路由(Express):接收前端消息数组,服务端注入 key 转发 pi-ai。

掌握了全部积木,下一章开始拼装真实应用——迷你编码助手。