Parlant 自定义前端开发指南:从 React 聊天组件到基于会话事件的自建聊天界面
【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlant
在 Parlant 中,面向客户的 AI Agent 能力全部通过会话(Session)与事件(Event)体系对外暴露,而文档 docs/production/custom-frontend.md 专门讲的就是如何把这套能力接进你自己的产品前端:既可以用官方parlant-chat-react组件快速嵌入一个完整的聊天界面,也可以直接基于 Parlant 客户端 SDK 的会话 API 自建任意框架(Vue、Angular、原生 JavaScript)的聊天 UI。读完本篇,你可以掌握两条路径的完整接入方式:widget 的属性配置与组件替换,以及"创建会话 → 发送事件 → 长轮询监听事件 → 渲染消息"这一自建前端的核心调用链,并能对照仓库源码理解每个 API 参数在服务端的真实行为。
两条集成路径总览
Parlant 服务端(FastAPI 应用)负责会话状态、事件追加与 Agent 编排,前端只需要通过 HTTP 接口与之交互。仓库中自带了一个完整的参考实现前端 src/parlant/api/chat/(React + Vite 应用),其 API 封装见 api.ts,会话视图中的事件轮询逻辑见 session-view.tsx,可作为自建前端时的活参考。
- 路径一:官方 React widget。适合 React 技术栈、希望快速上线标准聊天体验的场景。
- 路径二:自建前端。适合非 React 框架、深度定制 UI/交互、或需要接管完整会话生命周期的场景。
路径一:parlant-chat-react 官方组件
parlant-chat-react是一个开源的 React 聊天组件,提供完整的聊天界面并直接连接你的 Parlant 服务端 Agent。
安装与基本接入
通过 npm 或 yarn 安装:
npm install parlant-chat-react # 或 yarn add parlant-chat-react在 React 应用中集成:
import React from 'react'; import ParlantChatbox from 'parlant-chat-react'; function App() { return ( <div> <h1>My Application</h1> <ParlantChatbox server="http://localhost:8800" // 你的 Parlant 服务端 URL agentId="your-agent-id" // 你的 Agent ID /> </div> ); } export default App;其中server指向你的 Parlant 服务端(本地示例用http://localhost:8800),agentId为要对话的 Agent 标识。
配置属性(Props)
组件支持如下配置:
<ParlantChatbox // 必需属性 server="http://localhost:8800" agentId="your-agent-id" // 可选属性 sessionId="existing-session-id" // 续接已有会话 customerId="customer-123" // 关联到指定客户 float={true} // 以浮动弹窗形式展示 titleFn={(session) => `Chat ${session.id}`} // 动态生成标题 />sessionId:传入已有会话 ID 可恢复历史对话,而不是每次新开一个会话;customerId:将本次对话归属到具体客户,对应服务端会话创建参数中的customer_id(见下文);float:开启浮动聊天窗口模式,适合网站右下角悬浮入口;titleFn:接收会话对象回调生成动态标题,常用于显示会话主题或时间。
常见定制方式
用 CSS 类名覆盖样式
通过classNames属性为各区域注入自定义类名:
<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" classNames={{ chatboxWrapper: "my-chat-wrapper", chatbox: "my-chatbox", messagesArea: "my-messages", agentMessage: "my-agent-bubble", customerMessage: "my-customer-bubble", textarea: "my-input-field", popupButton: "my-popup-btn" }} />这些类名分别覆盖组件外壳、聊天主体、消息区、Agent/客户消息气泡、输入框和浮动按钮,配合你自己的样式表即可实现品牌化外观。
替换具体子组件
components属性允许用你自己的组件替换内部实现,例如自定义浮动按钮和 Agent 消息渲染:
<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" components={{ popupButton: ({ toggleChatOpen }) => ( <button onClick={toggleChatOpen} className="custom-chat-button" > 💬 Chat with us </button> ), agentMessage: ({ message }) => ( <div className="custom-agent-message"> <img src="./assets/agent-avatar.png" alt="Agent" /> <p>{message.data.message}</p> </div> ) }} />注意agentMessage收到的message就是 Parlant 的事件对象,文案在message.data.message字段——这与自建前端路径中EventDTO.data.message的结构完全一致。
浮动聊天模式
<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" float={true} popupButton={<ChatIcon size={24} color="white" />} />float={true}加上自定义popupButton,即可把整个聊天压缩成一个可展开的悬浮图标。
该 widget 本身是开源项目,你可以查阅其源码作为会话管理、事件处理与 UI 状态同步的最佳实践参考,再套用到 Vue、Angular 或原生 JavaScript 等其它框架上。
路径二:基于 Parlant Client SDK 自建前端
当你需要比 React 组件更强的控制力,或使用的不是 React 时,可以直接调用 Parlant 客户端 API 自建前端。整体流程分五步:初始化客户端 → 创建会话 → 发送客户消息 → 监听会话事件 → 渲染到 UI。
Step 1: 初始化 Parlant 客户端
import { ParlantClient } from 'parlant-client'; class ParlantChat { private client: ParlantClient; private sessionId: string | null = null; private lastOffset: number = 0; constructor(serverUrl: string) { this.client = new ParlantClient({ environment: serverUrl }); } }import { ParlantClient } from 'parlant-client'; class ParlantChat { constructor(serverUrl) { this.client = new ParlantClient({ environment: serverUrl }); this.sessionId = null; this.lastOffset = 0; } }environment传入服务端 URL。lastOffset用于记录客户端已消费到的事件偏移量,是长轮询去重的关键状态。从仓库结构看,parlant-clientSDK 是由服务端的 OpenAPI 定义生成的(仓库中有 generate_client_sdk.py 与 fern 配置),因此 SDK 方法与下面讲到的服务端端点一一对应。
Step 2: 创建会话
async createSession(agentId: string, customerId?: string): Promise<string> { try { const session = await this.client.sessions.create({ agentId: agentId, customerId: customerId, title: `Chat Session ${new Date().toLocaleString()}` }); this.sessionId = session.id; console.log('Session created:', this.sessionId); // 启动事件监听 this.startEventMonitoring(); return this.sessionId; } catch (error) { console.error('Failed to create session:', error); throw error; } }对照服务端实现 SessionCreationParamsDTO,创建会话的参数为:
| 参数 | 说明 |
|---|---|
agent_id | 必填,要对话的 Agent |
customer_id | 可选;不提供时服务端会自动创建一个 guest 客户 |
title | 可选,会话标题,最长 200 字符 |
metadata/labels | 可选的元数据与标签,可用于会话分类检索 |
返回的会话对象(SessionDTO)包含id、agent_id、customer_id、creation_utc、mode、consumption_offsets、metadata、labels等字段,其中consumption_offsets.client用于服务端追踪客户端已消费到的事件偏移。
JavaScript 版本实现相同:
async createSession(agentId, customerId) { try { const session = await this.client.sessions.create({ agentId: agentId, customerId: customerId, title: `Chat Session ${new Date().toLocaleString()}` }); this.sessionId = session.id; console.log('Session created:', this.sessionId); this.startEventMonitoring(); return this.sessionId; } catch (error) { console.error('Failed to create session:', error); throw error; } }Step 3: 发送客户消息
async sendMessage(message: string): Promise<void> { if (!this.sessionId) { throw new Error('No active session'); } try { await this.client.sessions.createEvent(this.sessionId, { kind: "message", source: "customer", message: message }); // 消息将在事件监听回传时出现在 UI 上 console.log('Message sent:', message); } catch (error) { console.error('Failed to send message:', error); throw error; } }JavaScript 版本:
async sendMessage(message) { if (!this.sessionId) { throw new Error('No active session'); } try { await this.client.sessions.createEvent(this.sessionId, { kind: "message", source: "customer", message: message }); console.log('Message sent:', message); } catch (error) { console.error('Failed to send message:', error); throw error; } }服务端对应参数模型见 EventCreationParamsDTO,其中:
kind取值来自 EventKindDTO:message(消息)、tool(工具调用)、status(状态)、custom(自定义);source取值来自 EventSourceDTO:customer、customer_ui、human_agent、human_agent_on_behalf_of_ai_agent、ai_agent、system。自建前端以客户身份发消息时用customer;- 除
message外还支持data、metadata、participant、status、guidelines等字段,可用于发送自定义事件或附带参与者信息。
Step 4: 监听会话事件(长轮询)
这是自建前端的核心——通过listEvents长轮询持续获取新事件:
private async startEventMonitoring(): Promise<void> { if (!this.sessionId) return; while (true) { try { // 长轮询获取新事件 const events = await this.client.sessions.listEvents(this.sessionId, { minOffset: this.lastOffset, waitForData: 30, // 最多等待 30 秒新事件 kinds: ["message", "status"] // 只取消息与状态事件 }); for (const event of events) { await this.handleEvent(event); this.lastOffset = Math.max(this.lastOffset, event.offset + 1); } } catch (error) { console.error('Event monitoring error:', error); // 出错后等待再重试 await new Promise(resolve => setTimeout(resolve, 5000)); } } } private async handleEvent(event: any): Promise<void> { if (event.kind === "message") { this.displayMessage(event); } else if (event.kind === "status") { this.updateStatus(event.data.status); } }JavaScript 版本:
async startEventMonitoring() { if (!this.sessionId) return; while (true) { try { const events = await this.client.sessions.listEvents(this.sessionId, { minOffset: this.lastOffset, waitForData: 30, kinds: ["message", "status"] }); for (const event of events) { await this.handleEvent(event); this.lastOffset = Math.max(this.lastOffset, event.offset + 1); } } catch (error) { console.error('Event monitoring error:', error); await new Promise(resolve => setTimeout(resolve, 5000)); } } } async handleEvent(event) { if (event.kind === "message") { this.displayMessage(event); } else if (event.kind === "status") { this.updateStatus(event.data.status); } }服务端行为(源码级说明)。对应的 API 端点为GET /{session_id}/events,实现在 list_events,要点:
wait_for_data长轮询语义:wait_for_data = 0时立即返回当前匹配事件;wait_for_data > 0时若超时内没有新事件,服务端抛出504 Gateway Timeout;已有匹配事件则立即返回。服务端默认值为 60 秒,文档示例用 30 秒。因此上面的catch分支 + 5 秒延迟重试不是可选项,而是必须处理的正常路径——504 超时会被 SDK 表现为错误,重试循环即恢复监听;min_offset去重:事件在会话内按offset顺序编号,客户端始终从lastOffset开始拉取并推进到event.offset + 1,保证不重复、不遗漏;kinds过滤:逗号分隔的 kind 列表(message,tool,status,custom),只订阅 UI 需要的类型可减少数据量;- SSE 模式:端点还支持
sse=true参数,以text/event-stream持续推送事件,wait_for_data此时作为两次事件间的空闲超时(见 SSE 分支实现),对不想维护轮询循环的前端是另一种选择; - 事件结构:返回的 EventDTO 包含
id、source、kind、offset、creation_utc、trace_id、data、metadata、deleted等字段,官方示例负载形如{"message": "Hello, I need help...", "participant": {"id": "cust_123xy", "display_name": "John Doe"}}(见 event_example)。
Step 5: 在 UI 中渲染消息与状态
private displayMessage(event: any): void { const messageElement = document.createElement('div'); messageElement.className = `message ${event.source}`; // 按消息来源区分样式 switch (event.source) { case 'customer': messageElement.classList.add('customer-message'); break; case 'ai_agent': messageElement.classList.add('agent-message'); break; case 'human_agent': messageElement.classList.add('human-agent-message'); const agentName = event.data.participant?.display_name || 'Agent'; messageElement.innerHTML = ` <div class="agent-info">${agentName}</div> <div class="message-content">${event.data.message}</div> `; break; } const chatContainer = document.getElementById('chat-messages'); if (chatContainer) { chatContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } } private updateStatus(status: string): void { const statusElement = document.getElementById('chat-status'); if (statusElement) { switch (status) { case 'processing': statusElement.textContent = 'Agent is thinking...'; break; case 'typing': statusElement.textContent = 'Agent is typing...'; break; case 'ready': statusElement.textContent = ''; break; } } }JavaScript 版本:
displayMessage(event) { const messageElement = document.createElement('div'); messageElement.className = `message ${event.source}`; switch (event.source) { case 'customer': messageElement.classList.add('customer-message'); messageElement.innerHTML = `<div class="message-content">${event.data.message}</div>`; break; case 'ai_agent': messageElement.classList.add('agent-message'); messageElement.innerHTML = `<div class="message-content">${event.data.message}</div>`; break; case 'human_agent': messageElement.classList.add('human-agent-message'); const agentName = event.data.participant?.display_name || 'Agent'; messageElement.innerHTML = ` <div class="agent-info">${agentName}</div> <div class="message-content">${event.data.message}</div> `; break; } const chatContainer = document.getElementById('chat-messages'); if (chatContainer) { chatContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } } updateStatus(status) { const statusElement = document.getElementById('chat-status'); if (statusElement) { switch (status) { case 'processing': statusElement.textContent = 'Agent is thinking...'; break; case 'typing': statusElement.textContent = 'Agent is typing...'; break; case 'ready': statusElement.textContent = ''; break; } } }对照源码有两点补充:
- 来源枚举比示例更宽:
source除customer/ai_agent/human_agent外还有customer_ui、system、human_agent_on_behalf_of_ai_agent(EventSourceDTO),涉及人工接管(human handoff)场景时建议一并处理; - 状态枚举更完整:SessionStatusDTO 定义了
acknowledged、cancelled、processing、ready、typing、error六种状态,上面的updateStatus只处理了三种,健壮的实现应对error做提示、对cancelled做收尾处理。
Step 6: 完整 HTML 示例
下面是一个不依赖构建工具之外的完整页面,演示自建实现(parlant-client通过 npm 安装后由 Vite/webpack 等打包器引入):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Custom Parlant Chat</title> <style> .chat-container { max-width: 500px; margin: 50px auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; } .chat-header { background: #007bff; color: white; padding: 15px; text-align: center; } .chat-messages { height: 400px; padding: 15px; overflow-y: auto; background: #f8f9fa; } .message { margin: 10px 0; padding: 10px; border-radius: 8px; max-width: 80%; } .customer-message { background: #007bff; color: white; margin-left: auto; text-align: right; } .agent-message { background: white; border: 1px solid #ddd; } .human-agent-message { background: #28a745; color: white; } .chat-input { display: flex; padding: 15px; background: white; } .chat-input input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px; } .chat-input button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } #chat-status { font-style: italic; color: #666; padding: 5px 15px; } </style> </head> <body> <div class="chat-container"> <div class="chat-header"> <h3>Customer Support Chat</h3> </div> <div id="chat-status"></div> <div id="chat-messages" class="chat-messages"></div> <div class="chat-input"> <input type="text" id="message-input" placeholder="Type your message..." onkeypress="handleKeyPress(event)" /> <button onclick="sendMessage()">Send</button> </div> </div> <script type="module"> import { ParlantClient } from 'parlant-client'; // 由打包器解析 npm 包 // 初始化你的自定义聊天 const chat = new ParlantChat('http://localhost:8800'); // 启动聊天会话 chat.createSession('your-agent-id') .then(sessionId => { console.log('Chat ready!', sessionId); }) .catch(error => { console.error('Failed to start chat:', error); }); // 将函数暴露到全局 window.sendMessage = () => chat.sendUserMessage(); window.handleKeyPress = (event) => { if (event.key === 'Enter') { chat.sendUserMessage(); } }; </script> </body> </html>其中ParlantChat类即 Step 1–5 中 TypeScript/JavaScript 各方法的组合,sendUserMessage从输入框取值后调用sendMessage。
关键实现原则与源码依据
官方文档总结了五条自建前端应遵循的原则,结合仓库源码可以进一步理解其必要性:
- 事件驱动架构:聊天界面完全由 Parlant 会话事件驱动,UI 状态与服务端状态保持一致。服务端所有交互(客户消息、Agent 回复、状态变化)都以事件形式写入会话,前端不维护独立的对话状态。
- 长轮询(Long Polling):
listEvents的waitForData参数让服务端在有新事件时才返回,避免高频轮询;其语义(超时返回 504、已有匹配事件立即返回)由 list_events 实现 的文档字符串明确定义。 - 状态同步:始终以 Parlant 事件为准渲染,而非乐观更新 UI——即使客户消息由本地发出,也要等
customer来源的事件从监听链路回来后显示,这样消息的顺序与offset天然对齐。 - 错误处理:网络错误与 504 超时是长轮询的常态,需要重试逻辑(示例中为 5 秒延迟后重新进入轮询循环)。
- 响应式设计:确保聊天界面在桌面和移动端都可用。
除上述文档要点外,还有两个值得了解的机制:服务端会记录consumption_offsets.client(客户端已消费的最大偏移,见 ConsumptionOffsetsDTO),可用于断线重连后的状态校准;会话创建时若不带customer_id,服务端自动创建 guest 客户,这对应 widget 中customerId为可选的原因。
参考路径与延伸阅读
- 原始文档:docs/production/custom-frontend.md
- 会话 API 与服务端事件模型:src/parlant/api/sessions.py
- 仓库内置参考前端(React + Vite):src/parlant/api/chat/,API 封装 api.ts
- 会话 API 测试用例:tests/api/test_sessions.py
- SDK 生成脚本:scripts/generate_client_sdk.py、scripts/fern/fern.config.json
适用前提:以上所有示例假设你已部署一个本地或远程 Parlant 服务端(示例 URL 为http://localhost:8800),并且已创建好至少一个 Agent(可用agentId标识)。widget 路径仅适用于 React 技术栈;自建前端路径适用于任意能发起 HTTP 请求的前端环境,事件订阅可选择长轮询或sse=true的 SSE 流两种方式。
【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考