Прежде всего, что такое LangChain? Это превосходный фреймворк для разработки приложений на основе языковых моделей. Один из нескольких способов, которыми вы, возможно, захотите использовать это, — это создание чат-бота. Если вы когда-либо пробовали OpenAI API, вы, возможно, знаете, что при использовании конечной точки ChatCompletion вам потребуется передать всю историю чата в API. Оболочка LangChain OpenAI позаботится об этом за вас, используя идею memory
. Кроме того, вы также можете сгенерировать summary
и добавить его в свой вызов API OpenAI, если вы не хотите передавать долгую историю чата или хотите сохранить некоторые из своих токенов.
В библиотеке Python для LangChain есть прекрасный встроенный класс под названием ConversationSummaryBufferMemory
, который в итоге объединяет идею memory
и summary
. Он хранит буфер недавних взаимодействий в памяти, но вместо того, чтобы просто полностью сбрасывать старые взаимодействия, он компилирует их в сводку и использует оба в вызове API. Узнать больше об этой идее можно здесь.
Однако в большинстве случаев вы можете предпочесть использовать библиотеку TS/JS вместо Python при создании веб-приложения чат-бота. К сожалению, класс ConversationSummaryBufferMemory
НЕТ!!! существуют в библиотеке TS/JS. В этой статье я покажу вам, как вы можете самостоятельно реализовать эту идею сохранения недавних взаимодействий и обобщения более старых, используя BufferWindowMemory
и ConversationSummaryMemory
, которые являются двумя встроенными классами в библиотеке TS/JS langChain.
Начиная!
Настраивать
Прежде чем мы начнем, убедитесь, что вы установили библиотеку langChain с помощью npm install -S langchain
и импортировали необходимые библиотеки.
import { OpenAI } from "langchain/llms/openai"; import { BaseMessage, HumanMessage, AIMessage, SystemMessage } from "langchain/schema" import { BaseChatMemory, ChatMessageHistory, ConversationSummaryMemory } from "langchain/memory" import { SUMMARY_PROMPT } from "langchain/dist/memory/prompt"; import { BufferMemory, BufferWindowMemory } from "langchain/memory"; import { ConversationChain, LLMChain } from "langchain/chains"; import { ChatMessagePromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate, PromptTemplate, SystemMessagePromptTemplate } from "langchain/prompts";
На самом деле мы не используем весь импорт, но я просто помещаю его туда, чтобы вы могли быстро просмотреть некоторые возможные варианты.
Прежде всего, чтобы мы могли обобщать старые сообщения и сохранять новые как есть, нам нужно хранить все сообщения где-то в нашем приложении. Давайте создадим простой массив типа Message
, в котором хранятся все прошлые Message
.
export type Message = { role: string // human or AI content: string } const messages = [] Array<Message>
Здесь мы будем инициализировать пустой массив, но если у вас есть прошлые сообщения, которые вы храните в базе данных, вы определенно можете инициализировать их этими сообщениями.
Вспомогательные функции
Давайте сначала создадим вспомогательную функцию, которая преобразует имеющиеся у нас сообщения в объект ChatMessageHistory
, чтобы мы могли быстро вызывать эту функцию всякий раз, когда мы хотим создать цепочку с прошлой историей или сделать некоторое обобщение для заданного набора сообщений. Это будет выглядеть следующим образом.
export const createChatMessageHistory = (messages: Array<Message>) => { const history = new ChatMessageHistory() messages.forEach((message)=> { switch (message.role) { case "human": { history.addUserMessage(message.content) break } case "AI": { history.addAIChatMessage(message.content) break } default: { history.addMessage(new SystemMessage(message.content)) break } } }) return history }
Эта функция принимает массив Message
в качестве входных данных и возвращает объект ChatMessageHistory
, содержащий заданные сообщения, в качестве выходных данных.
У нас также будет вспомогательная функция, которая суммирует заданный массив Message
.
const model = new OpenAI({ modelName: 'gpt-3.5-turbo', temperature: 0, openAIApiKey: OPENAI_API_KEY, //Remember to replace OPENAI_API_KEY with your own ones }) export const summarizeMessages = async (messages: Array<Message>) => { const summary_memory = new ConversationSummaryMemory({ llm: model, chatHistory: createChatMessageHistory(messages) }) const summary = await summary_memory.predictNewSummary(await summary_memory.chatHistory.getMessages(), "") return summary }
Цепочка бесед
Чтобы у нас была и сводка, и память, нам нужно создать цепочку с несколькими входами, используя template
, которая выглядит следующим образом.
const template = ` The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. Current conversation: System: {chat_summary} {recent_chat_history} Human: {human_input} AI:`
chat_summary
будет местом, где мы передаем сводку старых сообщений, а human_input
будет новым сообщением чата. recent_chat_history
будет автоматически управляться объектом BufferWindowMemory
.
Давайте создадим наш ConversationChain
с подсказкой шаблона.
Сначала нам нужно преобразовать строку шаблона в объект PromptTemplate
.
const prompt = new PromptTemplate({ inputVariables: ["chat_summary", "human_input", "recent_chat_history"], template: template, })
Давайте также создадим наш объект BufferWindowMemory
, который поможет нам отслеживать последние разговоры.
k = 2 const history = createChatMessageHistory(messages) const memory = new BufferWindowMemory({ chatHistory: history, memoryKey: "recent_chat_history", inputKey:"human_input", k: k, returnMessages: false, })
k
— это количество разговоров туда и обратно, которые можно использовать в качестве памяти. Давайте оставим его маленьким, чтобы мы могли быстро визуализировать результат. memoryKey
— это то место, где BufferWindowMemory
добавит/обновит историю, а inputKey
укажет, какие входные данные являются фактическими сообщениями чата, введенными пользователем, которые должны быть добавлены в историю.
СУПЕР ВАЖНО!!! Поскольку у нас есть несколько входных данных, inputKey
должен быть указан!!!
Затем можно создать ConversationChain
следующим образом.
const chain = new ConversationChain({ memory: memory, verbose: true, // Just to print everything out so that we can see what is actually happening llm: model, prompt: prompt, })
Отправить сообщение и начать чат
Теперь у нас есть все, что нам нужно, и мы готовы отправить несколько сообщений в OPENAI.
Помнить? То, что мы хотим отправить, — это комбинированное обобщение старых сообщений и новых. Вот как мы это сделаем.
export const sendMessage = async (pastMessages: Array<Message>, newMessage: Message, chain: ConversationChain) => { var summary = "" if (pastMessages.length > 2 * k) { summary = await summarizeMessages(pastMessages.slice(0, -2*k)) } console.log(`summary: ${summary} for messages til: ${(pastMessages.slice(0, -2*k)).slice(-1)}}`) try { const response = await chain.predict({ chat_summary: summary, human_input: newMessage.content, }) console.log(response) return response } catch (error) { console.log(error) throw error } }
Суммирование не производится, если длина прошлых сообщений меньше 2*k
. Когда мы вызываем chain.predict
, нам нужно включить как сводку, так и новое сообщение в качестве входных данных.
Используя приведенную выше функцию, теперь мы можем отправить сообщение, подобное следующему.
const newMessage: Message = { role: "human", content: "hey, how's going?" } const response = await sendMessage(messages, newMessage, chain) const responseMessage = { role: "AI", content: response } messages.push(newMessage) messages.push(responseMessage)
Поскольку всю историю сообщений мы храним сами, не забудьте добавить новое сообщение и ответ в массив.
Идеальный! Вот и все! Теперь вы можете общаться с OpenAI, обобщая старые разговоры и сохраняя недавние как есть! Надеюсь, это сэкономит вам немного жетонов и немного денег…
Спасибо за чтение! Хорошего хорошего дня!!!