Прежде всего, что такое 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, обобщая старые разговоры и сохраняя недавние как есть! Надеюсь, это сэкономит вам немного жетонов и немного денег…

Спасибо за чтение! Хорошего хорошего дня!!!