Custom Chat Provider
Importimport { Custom Chat Provider } from "@ant-design/x-sdk"; |
Docs |
Versionsupported since 2.0.0 |
Importimport { Custom Chat Provider } from "@ant-design/x-sdk"; |
Docs |
Versionsupported since 2.0.0 |
When the built-in Chat Provider doesn't meet your needs, you can implement the abstract class AbstractChatProvider (which only contains three abstract methods) to convert data from different model providers or Agentic services into a unified format that useXChat can consume, enabling seamless integration and switching between different models and agents.
AbstractChatProvider is an abstract class used to define the interface for Chat Provider. When you need to use a custom data service, you can inherit from AbstractChatProvider and implement its methods. You can refer to the Playground - Toolbox for examples.
type MessageStatus = 'local' | 'loading' | 'updating' | 'success' | 'error';interface ChatProviderConfig<Input, Output> {request: XRequestClass<Input, Output> | (() => XRequestClass<Input, Output>);}interface TransformMessage<ChatMessage, Output> {originMessage?: ChatMessage;chunk: Output;chunks: Output[];status: MessageStatus;}abstract class AbstractChatProvider<ChatMessage, Input, Output> {constructor(config: ChatProviderConfig<Input, Output>): void;/*** Transform parameters passed to onRequest. You can merge or additionally process them with the params in the request configuration when instantiating the Provider* @param requestParams Request parameters* @param options Request configuration, from the request configuration when instantiating the Provider*/abstract transformParams(requestParams: Partial<Input>,options: XRequestOptions<Input, Output>,): Input;/*** Convert parameters passed to onRequest into a local (user-sent) ChatMessage for message rendering* @param requestParams Parameters passed to onRequest*/abstract transformLocalMessage(requestParams: Partial<Input>): ChatMessage;/*** Can transform messages when updating returned data, and will also update messages* @param info*/abstract transformMessage(info: TransformMessage<ChatMessage, Output>): ChatMessage;}
Below is a custom Provider example showing how to customize a Chat Provider. Detailed explanations follow the code example.
// Type definitionstype CustomInput = {query: string;};type CustomOutput = {data: {content: string; // Text contentattachments?: {// Optional: Document attachmentsname: string; // File nameurl: string; // Download linktype: string; // File type, e.g., 'pdf', 'docx', 'image'size?: number; // File size in bytes, optional}[];};};type CustomMessage = {content: string;role: 'user' | 'assistant';// Optional: Attachment information for displaying download links or previewsattachments?: {name: string;url: string;type: string;size?: number;}[];};class CustomProvider<ChatMessage extends CustomMessage = CustomMessage,Input extends CustomInput = CustomInput,Output extends CustomOutput = CustomOutput,> extends AbstractChatProvider<ChatMessage, Input, Output> {transformParams(requestParams: Partial<Input>,options: XRequestOptions<Input, Output, ChatMessage>,): Input {if (typeof requestParams !== 'object') {throw new Error('requestParams must be an object');}return {...(options?.params || {}),...(requestParams || {}),} as Input;}transformLocalMessage(requestParams: Partial<Input>): ChatMessage {return {content: requestParams.query,role: 'user',} as unknown as ChatMessage;}transformMessage(info: TransformMessage<ChatMessage, Output>): ChatMessage {const { originMessage, chunk } = info || {};if (!chunk || !chunk?.data || (chunk?.data && chunk?.data?.includes('[DONE]'))) {return {content: originMessage?.content || '',role: 'assistant',attachments: originMessage?.attachments || [],} as ChatMessage;}try {const data = JSON.parse(chunk.data);const content = originMessage?.content || '';// Merge attachment information to avoid data lossconst existingAttachments = originMessage?.attachments || [];const newAttachments = data.attachments || [];const mergedAttachments = [...existingAttachments];// Only add new attachments to avoid duplicatesnewAttachments.forEach((newAttach) => {if (!mergedAttachments.some((existing) => existing.url === newAttach.url)) {mergedAttachments.push(newAttach);}});return {content: `${content || ''}${data.content || ''}`,role: 'assistant',attachments: mergedAttachments,} as ChatMessage;} catch {// If not JSON format, treat as plain textreturn {content: `${originMessage?.content || ''}${chunk.data || ''}`,role: 'assistant',attachments: originMessage?.attachments || [],} as ChatMessage;}}}
Agentic service streaming interface https://xxx.agent.com/api/stream.Request parameters:
{"query": "Help me summarize today's tech news"}
Response data:
id:1data: {"content":"Okay,"}id:2data: {"content":"I'll help you"}id:3data: {"content":"summarize today's"}id:4data: {"content":"tech news,"}id:5data: {"content":"Report has been generated","attachments":[{"name":"Tech News Summary.pdf","url":"https://example.com/download/report.pdf","type":"pdf","size":102400}]}
CustomInput and CustomOutput types.CustomInput type:
{query: string;}
Since we need to parse the JSON string and extract the content field for concatenation, and handle attachment information if present, the CustomOutput type is:
{data: {content: string; // Text contentattachments?: { // Optional: Document attachmentsname: string; // File nameurl: string; // Download linktype: string; // File type, e.g., 'pdf', 'docx', 'image'size?: number; // File size in bytes, optional}[];};}
All responses use the unified data.content field for text content and optional data.attachments field for attachment information.
useXChat to be directly consumable by Bubble.List, so we can define CustomMessage as:{content: string;role: 'user' | 'assistant';// Optional: Attachment information for displaying download links or previewsattachments?: {name: string;url: string;type: string;size?: number;}[];}
AbstractChatProvider and implement its methods to get CustomProvider. AbstractChatProvider only requires implementing three methods.transformParams is used to transform parameters passed to onRequest. You can merge or additionally process them with the params in the request configuration when instantiating the Provider.transformLocalMessage converts parameters passed to onRequest into a local (user-sent) ChatMessage for user message rendering, and will also update messages for message list rendering.transformMessage can transform data into ChatMessage data type when updating returned data, and will also update messages for message list rendering.CustomProvider and pass it to useXChat to complete the custom Provider usage.const [provider] = React.useState(new CustomProvider({request: XRequest<CustomInput, CustomOutput>('https://xxx.agent.com/api/stream', {manual: true,}),}),);const { onRequest, messages, setMessages, setMessage, isRequesting, abort, onReload } = useXChat({provider,});
onRequest({query: "Help me summarize today's tech news",});
If your service supports returning document attachments, you can use it like this:
// Send request and handle responses with attachmentsonRequest({query: 'Generate a project summary report',});// The response will include attachment information, which can be displayed as download links in message bubbles// The messages data will include the attachments field
Notes:
We welcome community contributions for new Chat Providers! Please follow these specifications for Chat Provider development.
This guide will help you contribute to Ant Design. Please take a few minutes to read this contribution guide before submitting an issue or pull request.
Chat Providers should follow these specifications:
packages/x-sdk/src/chat-providers directory.packages/x-sdk/src/chat-providers/type directory.Chat Provider theme files should follow these naming rules:
[Vendor][Type][Version].ts.[Vendor][ModelName].ts.