Какую проблему решаем?
Любой 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()
Создаем бота для тестирования постоянной памяти
Поведение:
При общении используй данные о пользователе в твоей JSON памяти и меняй свой стиль ответов согласно этим данным.
Инструкции по работе с памятью:
В память можно сохранять следующие данные о пользователе: имя, пол, возраст, привычки, интересы, увлечения.
Тестируем бота и работу памяти
Для начала мы просто назовем имя боту:
Видим, что имя попало в постоянную память.
Теперь назовем возраст:
Теперь протестируем изменения данных в памяти бота:
Теперь проверим как бот общается с такими данными в памяти:
{
"user_name": "Андрей",
"user_age": "45",
"user_habits": "кофе",
"user_interests": "горы, велосипеды, море, пробежки по утрам"
}
Итоги
Если у вас есть идеи по улучшению работы предложенной модели постоянной памяти, пишите в комментариях или мне в Телеграм.
P.S.
У меня уже выходила статья по данной проблеме (https://habr.com/ru/articles/791916/), но решение было не очень стабильным и требовало дополнительный запрос к ИИ каждый раз при внесении изменений в данные постоянной памяти.
Комментарии (6)
Pol1mus
15.06.2024 08:00+1Иметь вторую память для чат бота - голубая мечта. Складывать туда какие то значимые данные и потом вставлять их перед основной памятью, в системный промпт. Теоретически можно просто каждый новый запрос от юзера пропускать через дополнительный запрос на выделение этих данных, можно даже не сразу а параллельно и другой ллмкой.
Практически...тупить будет так же как с определением намерений юзера (как у яндекса с алисой, юзер говорит алиса вруби чонить, алиса понимает что надо включить панк-рок, и погромче, но на самом деле часто ошибается, а ведь в ее развитие вбухиваются миллионы если не миллиарды).
TAU15 Автор
15.06.2024 08:00+1В первой версии функции памяти мы так и делали - отдельным запросом анализировали что нужно изменить в данных постоянной памяти.
В версии которая описывается в данной статье есть два существенных момента для стабилизации работы:
ИИ может менять значения только определенного ключа в памяти - это существенно снижает риск того, что какие-то данные из памяти будут потеряны.
Решение о сохранении чего-то в постоянной памяти принимается за счет использования встроенного и хорошо отлаженного (по крайней мере в моделях OpenAI) механизма вызова функций. Тем самым экономия в два раза токенов в отличии от первой версии функции памяти.
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)
leon0399
15.06.2024 08:00Как человек, который пытался пользоваться этой функцией в ChatGPT, могу сказать что на данный момент это не работает. Намного лучше работать над размером контекста.
Например, может отказаться записывать что либо в список покупок, а потом почему-то записать в память, что я однажды попросил нарисовать мем на армянском языке (потом оооочень долго не мог понять, почему на всех картинках надписи по-армянски)
vadim_ipatov
А имя бот "забыл" (Максим вместо Андрея) или это скриншот из другой сессии?
TAU15 Автор
Там Максим это имя владельца бота, а Андрей имя пользователя который с ботов говорит