Какую проблему решаем?

Любой GPT-бот на базе любой LLM (например на ChatGPT) должен сам и незаметно для пользователя запоминать в постоянную память нужные нам сведения из диалога.

Шаг№1: Создадим определение функции

{ 
    "name": "ai_memory_v3", 
    "description": "Call this function when you need to make adjustments to JSON memory.", 
    "parameters": { 
      "type": "object", 
      "properties": { 
        "key": { 
          "type": "string", 
          "description": "New or existing key from JSON memory", 
        },
        "value": { 
          "type": "string", 
          "description": "Text to be written by the specified key", 
        },
        "action": {
            "type": "string",
            "enum": ["+", '-', '.', '_'],
            "description": "Action type. `+` - means adding information to memory. `-` - means deleting information from memory. `.` - means changing information in memory. `_` - means clear all memory",
        },
      }, 
      "required": ["key", "value", "action"], 
    }, 
}, 

Параметры нашей функции:

  • "key" - Новый или существующий ключ из JSON памяти. Этот параметр указывает, к какому ключу будет применено действие.

  • "value" - Текст, который будет записан по указанному ключу. Этот параметр содержит данные, которые будут добавлены, изменены или удалены в памяти.

  • "action" - Тип действия, которое будет выполнено с памятью. Возможные значения:
         - + - добавление информации в память.
         - - - удаление информации из памяти.
         - . - изменение информации в памяти.
         - _ - очистка всей памяти.

Обработчик функции на нашем сервере

from flask import Flask, request, json
import os
import logging

# Инициализация логгера
logger = logging.getLogger(__name__)
config.ROOT_PATH = '/path/to/your/application/'  # Установите корректный путь к приложению

# Создаем экземпляр приложения Flask
app = Flask(__name__)

# Определение маршрута и функции для записи в постоянную память
@app.route('/write_memory_dict', methods=['POST'])
def write_memory_dict():
    # Получение данных из запроса в формате JSON
    data = GetJsonFromRequest(request)
    # Логирование данных запроса
    logger.debug(f"[/write_memory_dict] {data}")
    # Инициализация сообщения для пользователя
    Res = 'Покажи новое состояние памяти и получи подтверждение на внесение изменений пользователю.n'
    # Основная логика для обработки различных действий
    if data['action'] == '_':
        try:
            # Попытка удалить файл памяти
            os.remove(config.ROOT_PATH + 'json_memory/' + data['chat_id'])
        except Exception as e:
            # Если файл не найден, логируем предупреждение
            logger.warning(f"File not found {data['chat_id']}")
        # Ответ о том, что память очищена
        Res = 'Память очищена.n'
    else:
        # Имя файла соответствует chat_id
        filename = data['chat_id']
        memory_json = {}
        if filename is not None:
            # Чтение существующего файла памяти
            memory_json = read_file(config.ROOT_PATH + 'json_memory/' + filename)
            try:
                # Попытка преобразовать прочитанные данные из файла в JSON
                memory_json = json.loads(memory_json)
            except:
                # Если преобразование не удалось, считаем что данных нет
                memory_json = {}
        # Добавление данных в память
        if data['action'] == '+' and data['key'] in memory_json:
            memory_json[data['key']] += ', ' + data['value']
            Res = 'Данные добавлены в постоянную память.\n'
        if data['action'] == '+' and data['key'] not in memory_json:
            memory_json[data['key']] = data['value']
            Res = 'Данные добавлены в постоянную память.n'
        # Удаление данных из памяти
        if data['action'] == '-' and data['key'] in memory_json:
            memory_json.pop(data['key'])
            Res = 'Данные удалены из постоянной памяти.n'
        # Обновление данных в памяти
        if data['action'] == '.' and data['key'] in memory_json:
            memory_json[data['key']] = data['value']
            Res = 'Данные в постоянной памяти изменены.n'
        # Сохранение изменений в файл
        if filename is not None and memory_json is not {}:
            with open(config.ROOT_PATH + 'json_memory/' + filename, 'w', encoding='utf-8') as f:
                json.dump(memory_json, f, ensure_ascii=False, indent=4)
            # Логирование успешного сохранения
            logger.info(f"/write memory_json OK {filename}, {memory_json}")    
    # Возвращение ответа клиенту
    return json.dumps(Res), 200

# Функция для чтения файла, предполагается, что она определена где-то в коде
def read_file(file_path):
    # Тут должна быть ваша реализация функции чтения файла
    pass

# Функция для получения JSON из запроса, предполагается, что она определена где-то в коде
def GetJsonFromRequest(req):
    # Тут должна быть ваша реализация функции извлечения JSON из запроса
    pass

# Запустить приложение, если файл запущен напрямую
if __name__ == '__main__':
    app.run()

Создаем бота для тестирования постоянной памяти

Бот для теста постоянной памяти на платформе ProTalk
Бот для теста постоянной памяти на платформе ProTalk
Подключаем нашу функцию к боту
Подключаем нашу функцию к боту
Указываем что именно мы хотим заносить в постоянную память
Указываем что именно мы хотим заносить в постоянную память
Поведение:
При общении используй данные о пользователе в твоей JSON памяти и меняй свой стиль ответов согласно этим данным.
Инструкции по работе с памятью:
В память можно сохранять следующие данные о пользователе: имя, пол, возраст, привычки, интересы, увлечения.

Тестируем бота и работу памяти

Для начала мы просто назовем имя боту:

Начало общения с ботом с постоянной памятью
Начало общения с ботом с постоянной памятью

Видим, что имя попало в постоянную память.

Теперь назовем возраст:

Уже два ключа в нашем JSON словаре для постоянной памяти
Уже два ключа в нашем JSON словаре для постоянной памяти

Теперь протестируем изменения данных в памяти бота:

Ключ с данными по возрасту пользователя был успешно изменен на новое значение
Ключ с данными по возрасту пользователя был успешно изменен на новое значение

Теперь проверим как бот общается с такими данными в памяти:

{
    "user_name": "Андрей",
    "user_age": "45",
    "user_habits": "кофе",
    "user_interests": "горы, велосипеды, море, пробежки по утрам"
}
Бот отвечает с учетом данных о пользователе из постоянной памяти
Бот отвечает с учетом данных о пользователе из постоянной памяти

Итоги

Если у вас есть идеи по улучшению работы предложенной модели постоянной памяти, пишите в комментариях или мне в Телеграм.

P.S.

У меня уже выходила статья по данной проблеме (https://habr.com/ru/articles/791916/), но решение было не очень стабильным и требовало дополнительный запрос к ИИ каждый раз при внесении изменений в данные постоянной памяти.

Комментарии (6)


  1. vadim_ipatov
    15.06.2024 08:00
    +2

    А имя бот "забыл" (Максим вместо Андрея) или это скриншот из другой сессии?


    1. TAU15 Автор
      15.06.2024 08:00

      Там Максим это имя владельца бота, а Андрей имя пользователя который с ботов говорит


  1. Pol1mus
    15.06.2024 08:00
    +1

    Иметь вторую память для чат бота - голубая мечта. Складывать туда какие то значимые данные и потом вставлять их перед основной памятью, в системный промпт. Теоретически можно просто каждый новый запрос от юзера пропускать через дополнительный запрос на выделение этих данных, можно даже не сразу а параллельно и другой ллмкой.

    Практически...тупить будет так же как с определением намерений юзера (как у яндекса с алисой, юзер говорит алиса вруби чонить, алиса понимает что надо включить панк-рок, и погромче, но на самом деле часто ошибается, а ведь в ее развитие вбухиваются миллионы если не миллиарды).


    1. TAU15 Автор
      15.06.2024 08:00
      +1

      В первой версии функции памяти мы так и делали - отдельным запросом анализировали что нужно изменить в данных постоянной памяти.

      В версии которая описывается в данной статье есть два существенных момента для стабилизации работы:

      1. ИИ может менять значения только определенного ключа в памяти - это существенно снижает риск того, что какие-то данные из памяти будут потеряны.

      2. Решение о сохранении чего-то в постоянной памяти принимается за счет использования встроенного и хорошо отлаженного (по крайней мере в моделях OpenAI) механизма вызова функций. Тем самым экономия в два раза токенов в отличии от первой версии функции памяти.


  1. Pol1mus
    15.06.2024 08:00
    +1

    Получилось запустить функцию на джемини. Оказалось это совсем просто. При создании объекта в него передается список функций, их описание и параметры он сам смотрит (питон такое позволяет), и сам же их вызывает когда думает что это поможет ответить.

    Таким образом завести вторую, третью итп память не составит никакого труда.

    import google.generativeai as genai
    
    import cfg
    import my_google
    
    
    genai.configure(api_key=cfg.gemini_keys[0])
    
    
    def func1(location: str) -> str:
        '''Return weather forecast for location'''
        return f"The weather is good in {location}."
    
    
    def search_google(query: str) -> str:
        '''Search Google for query, return texts of found web pages.'''
        return my_google.search_v3(query, max_search=3, download_only=True)[:30000]
    
    
    
    model = genai.GenerativeModel('gemini-1.5-flash', tools=[func1,search_google])
    
    
    chat = model.start_chat(history=[], enable_automatic_function_calling = True)
    
    
    while 1:
        q = input('> ')
        r = chat.send_message(q).text.strip()
        print(r)
    


  1. leon0399
    15.06.2024 08:00

    Как человек, который пытался пользоваться этой функцией в ChatGPT, могу сказать что на данный момент это не работает. Намного лучше работать над размером контекста.

    Например, может отказаться записывать что либо в список покупок, а потом почему-то записать в память, что я однажды попросил нарисовать мем на армянском языке (потом оооочень долго не мог понять, почему на всех картинках надписи по-армянски)