名称:构建聊天界面 描述:| 使用自定义后端、身份验证和上下文注入构建AI聊天界面。 适用于将聊天UI与AI智能体集成、为聊天添加身份验证、注入用户/页面上下文、 或实现httpOnly cookie代理的场景。涵盖ChatKitServer、useChatKit和MCP身份验证模式。 不适用于构建没有持久化或自定义智能体集成的简单聊天机器人。
构建聊天界面
构建生产级的AI聊天界面,支持自定义后端集成。
快速开始
# 后端(Python)
uv add chatkit-sdk agents httpx
# 前端(React)
npm install @openai/chatkit-react
核心架构
前端(React) 后端(Python)
┌─────────────────┐ ┌─────────────────┐
│ useChatKit() │───HTTP/SSE───>│ ChatKitServer │
│ - 自定义fetch │ │ - respond() │
│ - 身份验证头 │ │ - 存储 │
│ - 页面上下文 │ │ - 智能体 │
└─────────────────┘ └─────────────────┘
后端模式
1. 带自定义智能体的ChatKit服务器
from chatkit.server import ChatKitServer
from chatkit.agents import stream_agent_response
from agents import Agent, Runner
class CustomChatKitServer(ChatKitServer[RequestContext]):
"""使用自定义智能体扩展ChatKit服务器。"""
async def respond(
self,
thread: ThreadMetadata,
input_user_message: UserMessageItem | None,
context: RequestContext,
) -> AsyncIterator[ThreadStreamEvent]:
if not input_user_message:
return
# 加载对话历史
previous_items = await self.store.load_thread_items(
thread.id, after=None, limit=10, order="desc", context=context
)
# 为提示构建历史字符串
history_str = "
".join([
f"{item.role}: {item.content}"
for item in reversed(previous_items.data)
])
# 从元数据中提取上下文
user_info = context.metadata.get('userInfo', {})
page_context = context.metadata.get('pageContext', {})
# 在指令中创建带上下文的智能体
agent = Agent(
name="助手",
tools=[your_search_tool],
instructions=f"{history_str}
用户: {user_info.get('name')}
{system_prompt}",
)
# 运行带流式输出的智能体
result = Runner.run_streamed(agent, input_user_message.content)
async for event in stream_agent_response(context, result):
yield event
2. 数据库持久化
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
DATABASE_URL = os.getenv("DATABASE_URL").replace("postgresql://", "postgresql+asyncpg://")
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)
# 启动时预热连接池
async def warmup_pool():
async with engine.begin() as conn:
await conn.execute(text("SELECT 1"))
3. JWT/JWKS身份验证
from jose import jwt
import httpx
async def get_current_user(authorization: str = Header()):
token = authorization.replace("Bearer ", "")
async with httpx.AsyncClient() as client:
jwks = (await client.get(JWKS_URL)).json()
payload = jwt.decode(token, jwks, algorithms=["RS256"])
return payload
前端模式
1. 自定义Fetch拦截器
const { control, sendUserMessage } = useChatKit({
api: {
url: `${backendUrl}/chatkit`,
domainKey: domainKey,
// 自定义fetch以注入身份验证和上下文
fetch: async (url: string, options: RequestInit) => {
if (!isLoggedIn) {
throw new Error('用户必须登录');
}
const pageContext = getPageContext();
const userInfo = { id: userId, name: user.name };
// 将元数据注入请求体
let modifiedOptions = { ...options };
if (modifiedOptions.body && typeof modifiedOptions.body === 'string') {
const parsed = JSON.parse(modifiedOptions.body);
if (parsed.params?.input) {
parsed.params.input.metadata = {
userId, userInfo, pageContext,
...parsed.params.input.metadata,
};
modifiedOptions.body = JSON.stringify(parsed);
}
}
return fetch(url, {
...modifiedOptions,
headers: {
...modifiedOptions.headers,
'X-User-ID': userId,
'Content-Type': 'application/json',
},
});
},
},
});
2. 页面上下文提取
const getPageContext = useCallback(() => {
if (typeof window === 'undefined') return null;
const metaDescription = document.querySelector('meta[name="description"]')
?.getAttribute('content') || '';
const mainContent = document.querySelector('article') ||
document.querySelector('main') ||
document.body;
const headings = Array.from(mainContent.querySelectorAll('h1, h2, h3'))
.slice(0, 5)
.map(h => h.textContent?.trim())
.filter(Boolean)
.join(', ');
return {
url: window.location.href,
title: document.title,
path: window.location.pathname,
description: metaDescription,
headings: headings,
};
}, []);
3. 脚本加载检测
const [scriptStatus, setScriptStatus] = useState<'pending' | 'ready' | 'error'>(
isBrowser && window.customElements?.get('openai-chatkit') ? 'ready' : 'pending'
);
useEffect(() => {
if (!isBrowser || scriptStatus !== 'pending') return;
if (window.customElements?.get('openai-chatkit')) {
setScriptStatus('ready');
return;
}
customElements.whenDefined('openai-chatkit').then(() => {
setScriptStatus('ready');
});
}, []);
// 仅在就绪时渲染
{isOpen && scriptStatus === 'ready' && <ChatKit control={control} />}
Next.js集成
httpOnly Cookie代理
当身份验证令牌位于httpOnly cookie中时(JavaScript无法读取):
// app/api/chatkit/route.ts
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function POST(request: NextRequest) {
const cookieStore = await cookies();
const idToken = cookieStore.get("auth_token")?.value;
if (!idToken) {
return NextResponse.json({ error: "未认证" }, { status: 401 });
}
const response = await fetch(`${API_BASE}/chatkit`, {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,
"Content-Type": "application/json",
},
body: await request.text(),
});
// 处理SSE流式传输
if (response.headers.get("content-type")?.includes("text/event-stream")) {
return new Response(response.body, {
status: response.status,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}
return NextResponse.json(await response.json(), { status: response.status });
}
脚本加载策略
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
{/* 对于Web组件,必须使用beforeInteractive */}
<Script
src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"
strategy="beforeInteractive"
/>
</head>
<body>{children}</body>
</html>
);
}
MCP工具身份验证
MCP协议不转发身份验证头。通过系统提示传递凭据:
SYSTEM_PROMPT = """你是助手。
## 身份验证上下文
- 用户ID: {user_id}
- 访问令牌: {access_token}
关键:调用任何MCP工具时,请包含:
- user_id: "{user_id}"
- access_token: "{access_token}"
"""
# 使用凭据格式化
instructions = SYSTEM_PROMPT.format(
user_id=context.user_id,
access_token=context.metadata.get("access_token", ""),
)
常见问题
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 历史记录不在提示中 | 智能体不记得对话 | 在系统提示中包含历史记录字符串 |
| 上下文未传输 | 智能体缺少用户/页面信息 | 添加到请求元数据中,在后端提取 |
| 脚本未加载 | 组件渲染失败 | 检测脚本加载状态,等待渲染 |
| 缺少身份验证头 | 后端拒绝请求 | 使用自定义fetch拦截器 |
| httpOnly cookie | 无法从JS读取令牌 | 创建服务器端API路由代理 |
| 首次请求缓慢 | 7+秒延迟 | 预热数据库连接池 |
验证
运行:python3 scripts/verify.py
预期结果:✓ building-chat-interfaces 技能就绪
如果验证失败
- 检查:references/文件夹中有chatkit-integration-patterns.md
- 如果仍然失败,请停止并报告
相关技能(分层系统)
- streaming-llm-responses - 第2层:响应生命周期、进度更新、客户端效果
- building-chat-widgets - 第3层:交互式小部件、实体标记、编辑器工具
- fetching-library-docs - ChatKit文档:
--library-id /openai/chatkit --topic useChatKit
参考资料
- references/chatkit-integration-patterns.md - 完整的模式与证据
- references/nextjs-httponly-proxy.md - Next.js cookie代理模式