View this page in the latest version of Appian. Build a Chatbot with Persistent History Share Share via LinkedIn Reddit Email Copy Link Print On This Page Tip: Interface patterns give you an opportunity to explore different interface designs. Be sure to check out How to Adapt a Pattern for Your Application. Goal Create a chatbot that maintains conversation continuity by storing and retrieving message history from a record type. This design pattern is not recommended for offline interfaces because reflecting immediate changes in an interface based on user interaction requires a connection to the server. This recipe demonstrates how to build a chatbot that stores conversation history in a record type and provides that history to an AI model as context for each new message. By persisting all interactions to your database, you can maintain conversation continuity across user sessions, allowing users to return to previous conversations and pick up where they left off. The pattern combines the chat component with a!callLanguageModel() to create an AI assistant that remembers the full context of each conversation, ensuring more relevant and coherent responses. Prerequisites Before you begin, ensure you have: A generative AI skill or AI model integration configured in your environment. A record type to store chat messages with fields for message content, role, user, and timestamp. Expression rules to query and format chat history. Solution overview This solution uses the chat component with a!callLanguageModel() to create an interactive AI assistant. Each message exchange is immediately saved to a record type, and the full conversation history is passed to the AI model to maintain context. Key components include: Chat component: Displays the conversation interface. AI model integration: Processes user messages with full conversation context. Record type: Stores all messages permanently. Refresh variables: Updates the interface immediately after new messages are saved. Set up the record type Create a record type called CFR Chat Message with the following fields: user (Text): The username of the person chatting. role (Text): Either "USER" or "ASSISTANT" to identify the message sender. messageContent (Text): The actual message text. createdOn (Date and Time): When the message was sent. Create supporting expression rules You'll need two expression rules to manage chat history. CFR_getChatHistoryByUserId CFR_getFormattedChatHistoryByUser Get chat history for display Create an expression rule called CFR_getChatHistoryByUserId that queries the last 20 messages for the current user. This rule returns record data formatted for display in the chat component. Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 a!queryRecordType( recordType: recordType!CFR Chat Message, filters: a!queryFilter( field: recordType!CFR Chat Message.fields.user, operator: "=", value: ri!username ), pagingInfo: a!pagingInfo( startIndex: 1, sort: a!sortInfo( field: recordType!CFR Chat Message.fields.createdOn, ascending: false() ) ) ).data Format history for the language model Create an expression rule called CFR_getFormattedChatHistoryByUser that returns the same messages formatted as a list of maps with role and messageContent keys. The AI model requires this specific format in its messages parameter. Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 a!forEach( items: a!queryRecordType( recordType: recordType!CFR Chat Message, filters: a!queryFilter( field: recordType!CFR Chat Message.fields.user, operator: "=", value: ri!username ), pagingInfo: a!pagingInfo( startIndex: 1, sort: a!sortInfo( field: recordType!CFR Chat Message.fields.createdOn, ascending: false() ) ) ).data, expression: a!map( role: fv!item[recordType!CFR Chat Message.fields.role], messageContent: fv!item[recordType!CFR Chat Message.fields.messageContent] ) ) Build the interface The following interface code implements the chat functionality with persistent history. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 a!localVariables( /* Fetch existing chat history and set up variables for the current session. */ local!lastUserMessage, local!modelResponse, /* Fetch the raw record data for the messages to display in the UI. */ local!messages: a!refreshVariable( value: rule!CFR_getChatHistoryByUserId(username: loggedInUser()), refreshAlways: true() ), /* Fetch history in a format specific to the language model as a list of maps with role and content. */ local!formattedHistory: rule!CFR_getFormattedChatHistoryByUser(username: loggedInUser()), a!chatField( title: "AI Assistant", /* Map the record fields to the chatMessage component for display. */ messages: a!forEach( items: local!messages, expression: a!chatMessage( messageContent: fv!item[recordType!CFR Chat Message.fields.messageContent], role: fv!item[recordType!CFR Chat Message.fields.role] ) ), placeholder: "Ask a question...", saveInto: { /* 1. Capture the text the user just typed. */ a!save(local!lastUserMessage, save!value), /* 2. Update the local context history with the user's new message immediately. */ a!save( local!formattedHistory, a!update( data: local!formattedHistory, index: count(local!formattedHistory) + 1, value: a!map(role: "USER", messageContent: save!value) ) ), /* 3. Call the language model using the updated history as context. */ a!save( local!modelResponse, a!callLanguageModel( prompt: "You are a helpful assistant. You are given the previous message history as context for the latest user message.", messages: local!formattedHistory ) ), /* 4. Write both the user's question and the AI response to the database. */ a!writeRecords( records: { /* User message entry. */ recordType!CFR Chat Message( recordType!CFR Chat Message.fields.user: loggedInUser(), recordType!CFR Chat Message.fields.role: "USER", recordType!CFR Chat Message.fields.messageContent: local!lastUserMessage, recordType!CFR Chat Message.fields.createdOn: now() ), /* Assistant response entry. */ recordType!CFR Chat Message( recordType!CFR Chat Message.fields.user: loggedInUser(), recordType!CFR Chat Message.fields.role: "ASSISTANT", recordType!CFR Chat Message.fields.messageContent: local!modelResponse, recordType!CFR Chat Message.fields.createdOn: now() ) } ), /* 5. Append the model response to the local history for the next turn in the conversation. */ a!save( local!formattedHistory, append( local!formattedHistory, a!map( role: "assistant", messageContent: local!modelResponse ) ) ) } ) ) How it works When a user sends a message, the interface performs these steps in sequence: Captures the message: The user's input is saved to local!lastUserMessage. Updates context: The message is added to local!formattedHistory so the AI model has the latest context. Calls the language model: The a!callLanguageModel() function processes the message using the full conversation history. Persists the exchange: Both the user's message and the AI's response are written to the database in a single operation. Refreshes the display: The interface automatically re-queries the database to show the new messages. Key design patterns This implementation uses several important patterns: Session continuity: Updating local!formattedHistory before calling the AI model ensures the AI has access to the user's latest message. Atomic persistence: Saving both messages in one a!writeRecords() call reduces server requests and ensures message pairs are stored together. Automatic refresh: Using refreshAlways: true() on local!messages ensures the interface displays new messages immediately after they're saved. Considerations Keep these points in mind when implementing this pattern: The interface loads the messages on initialization. Synchronous AI model calls may introduce latency. Consider your user experience requirements. Each message exchange writes two records to the record type. Plan for appropriate data retention policies. The AI model receives the full conversation history with each request, which affects AI action consumption. Feedback Was this page helpful? SHARE FEEDBACK Loading...