Сохранение контекста между сеансами
Сохранение и загрузка предпочтений пользователя, истории диалога и состояния задачи.
«Сохранение контекста между сеансами» — бесплатный урок AI Agents на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Зачем сохранять контекст?
Без сохранения контекста каждый сеанс агента начинается с нуля. Агент с сохранением контекста помнит имя пользователя, предыдущие диалоги, высказанные предпочтения и сведения, которыми поделился пользователь. Благодаря этому взаимодействие кажется непрерывным и личным.
SQLite для состояния сессии
SQLite идеально подходит для личных агентов, которыми пользуется один человек: не требует отдельного сервера, работает на основе файла и отличается надежностью. Храните историю диалогов и данные пользователя в локальной базе данных.
import sqlite3
from datetime import datetime
def init_db(db_path: str = 'agent_memory.db') -> sqlite3.Connection:
conn = sqlite3.connect(db_path, check_same_thread=False)
conn.row_factory = sqlite3.Row # Dict-like access
conn.executescript('''
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT UNIQUE NOT NULL,
user_id TEXT NOT NULL,
started_at TEXT,
last_active TEXT
);
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS user_facts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
fact_key TEXT NOT NULL,
fact_value TEXT NOT NULL,
created_at TEXT,
UNIQUE(user_id, fact_key)
);
''')
conn.commit()
return conn
conn = init_db()
print('Database initialized')Сохранение и загрузка сообщений
Сохраняйте каждое сообщение пользователя и каждый ответ агента вместе с ID сессии. При возобновлении сессии загружайте последние N сообщений, чтобы восстановить контекст диалога.
import sqlite3
from datetime import datetime
def save_message(conn: sqlite3.Connection, session_id: str, role: str, content: str):
conn.execute(
'INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)',
(session_id, role, content, datetime.utcnow().isoformat())
)
conn.commit()
def load_recent_messages(conn: sqlite3.Connection, session_id: str, limit: int = 20) -> list:
rows = conn.execute(
'SELECT role, content FROM messages WHERE session_id = ? ORDER BY id DESC LIMIT ?',
(session_id, limit)
).fetchall()
# Reverse to get chronological order
return [{'role': r['role'], 'content': r['content']} for r in reversed(rows)]
def load_all_session_messages(conn: sqlite3.Connection, user_id: str, days: int = 30) -> list:
from datetime import timedelta
since = (datetime.utcnow() - timedelta(days=days)).isoformat()
rows = conn.execute(
'SELECT m.role, m.content, m.timestamp FROM messages m '
'JOIN sessions s ON s.session_id = m.session_id '
'WHERE s.user_id = ? AND m.timestamp >= ? ORDER BY m.id',
(user_id, since)
).fetchall()
return [dict(r) for r in rows]
if __name__ == '__main__':
conn = sqlite3.connect(':memory:')
conn.row_factory = sqlite3.Row
conn.execute('CREATE TABLE messages (id INTEGER PRIMARY KEY, session_id TEXT, role TEXT, content TEXT, timestamp TEXT)')
save_message(conn, 'sess1', 'user', 'Hello agent')
save_message(conn, 'sess1', 'assistant', 'Hi! How can I help?')
for msg in load_recent_messages(conn, 'sess1'):
print(f"{msg['role']}: {msg['content']}")
Redis для состояния сессии
Для веб-агентов или развертываний на нескольких серверах Redis подходит лучше, чем SQLite. Храните данные сессии в формате JSON с TTL, чтобы неактуальные сессии автоматически удалялись.
import redis
import json
from datetime import datetime
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
SESSION_TTL = 3600 * 24 * 7 # 7 days
def save_session_state(session_id: str, state: dict):
key = f'session:{session_id}'
state['last_updated'] = datetime.utcnow().isoformat()
r.setex(key, SESSION_TTL, json.dumps(state))
def load_session_state(session_id: str) -> dict:
key = f'session:{session_id}'
raw = r.get(key)
if not raw:
return {}
state = json.loads(raw)
# Refresh TTL on access
r.expire(key, SESSION_TTL)
return state
def append_to_session_history(session_id: str, role: str, content: str):
state = load_session_state(session_id)
history = state.get('history', [])
history.append({'role': role, 'content': content})
# Keep last 50 messages
state['history'] = history[-50:]
save_session_state(session_id, state)
# Test
save_session_state('sess-abc', {'user_name': 'Alice', 'history': []})
append_to_session_history('sess-abc', 'user', 'Hello!')
state = load_session_state('sess-abc')
print('Session state:', state)Загрузка предпочтений пользователя при запуске
При запуске новой сессии загружайте сохраненные предпочтения пользователя: язык, часовой пояс, настройки уведомлений и любые конфигурации, относящиеся к агенту. Добавляйте их в системный запрос.
import sqlite3
def load_user_preferences(conn: sqlite3.Connection, user_id: str) -> dict:
rows = conn.execute(
'SELECT fact_key, fact_value FROM user_facts WHERE user_id = ?',
(user_id,)
).fetchall()
return {row['fact_key']: row['fact_value'] for row in rows}
def build_system_prompt_with_preferences(base_prompt: str, user_id: str, conn: sqlite3.Connection) -> str:
prefs = load_user_preferences(conn, user_id)
if not prefs:
return base_prompt
pref_lines = []
if 'name' in prefs:
pref_lines.append(f'The user\'s name is {prefs["name"]}.')
if 'timezone' in prefs:
pref_lines.append(f'The user is in timezone {prefs["timezone"]}.')
if 'language' in prefs:
pref_lines.append(f'Respond in {prefs["language"]}.')
if 'profession' in prefs:
pref_lines.append(f'The user is a {prefs["profession"]}.')
prefs_text = ' '.join(pref_lines)
return f'{base_prompt}\n\nUser context: {prefs_text}'
conn = init_db()
enhanced_prompt = build_system_prompt_with_preferences(
'You are a helpful assistant.',
'user-42',
conn
)
print('System prompt:', enhanced_prompt)Хранение сводки диалога
Хранение полной истории диалога требует большого количества токенов. Используйте суммаризацию с помощью LLM, чтобы создать сжатую сводку старых диалогов, а для давней истории загружайте только эту сводку.
import openai
client = openai.OpenAI(api_key='sk-...')
def summarize_conversation(messages: list) -> str:
if not messages:
return ''
conversation_text = '\n'.join([
f'{m["role"].upper()}: {m["content"]}'
for m in messages
])
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{
'role': 'user',
'content': (
'Summarize this conversation in 2-3 sentences, '
'focusing on key facts, decisions, and user preferences revealed:\n\n'
f'{conversation_text}'
)
}]
)
return response.choices[0].message.content
def compress_old_history(conn: sqlite3.Connection, session_id: str, keep_recent: int = 10):
all_messages = load_recent_messages(conn, session_id, limit=1000)
if len(all_messages) <= keep_recent:
return
old_messages = all_messages[:-keep_recent]
summary = summarize_conversation(old_messages)
# Store summary as a special message
save_message(conn, session_id, 'summary', f'[Previous conversation summary]: {summary}')
print(f'Compressed {len(old_messages)} old messages into summary')Извлечение и сохранение сведений о пользователе
Когда пользователь сообщает сведения о себе, извлекайте и сохраняйте их. Так формируется долговременная память, которая сохраняется во всех будущих сессиях.
import openai
import json
client = openai.OpenAI(api_key='sk-...')
def extract_user_facts(message: str) -> dict:
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{
'role': 'user',
'content': (
f'Extract any personal facts the user revealed in this message: "{message}"\n'
'Return JSON with fields: name, location, profession, preferences, or other relevant facts. '
'Return empty dict {{}} if no facts were revealed.'
)
}],
response_format={'type': 'json_object'}
)
return json.loads(response.choices[0].message.content)
def store_user_facts(conn: sqlite3.Connection, user_id: str, facts: dict):
for key, value in facts.items():
if value: # Skip empty values
conn.execute(
'INSERT OR REPLACE INTO user_facts (user_id, fact_key, fact_value, created_at) VALUES (?, ?, ?, ?)',
(user_id, key, str(value), __import__('datetime').datetime.utcnow().isoformat())
)
conn.commit()
if facts:
print(f'Stored {len(facts)} facts for user {user_id}: {list(facts.keys())}')
conn = init_db()
facts = extract_user_facts('I am a software engineer in Berlin working on AI projects')
store_user_facts(conn, 'user-42', facts)
print('Facts extracted:', facts)Загрузка релевантного контекста для текущего шага
В начале каждого шага агента собирайте контекст из недавних сообщений, предпочтений пользователя и релевантных сведений. Это дает LLM все необходимое, не превышая размер окна контекста.
def build_agent_context(conn: sqlite3.Connection, session_id: str, user_id: str, new_message: str) -> list:
messages = []
# Step 1: System prompt with user preferences
prefs = load_user_preferences(conn, user_id)
system_content = 'You are a helpful personal AI assistant.'
if prefs:
facts_text = ', '.join([f'{k}: {v}' for k, v in prefs.items()])
system_content += f' User context: {facts_text}'
messages.append({'role': 'system', 'content': system_content})
# Step 2: Load conversation history (last 15 messages)
history = load_recent_messages(conn, session_id, limit=15)
messages.extend(history)
# Step 3: Add the new user message
messages.append({'role': 'user', 'content': new_message})
return messages
conn = init_db()
context = build_agent_context(conn, 'sess-abc', 'user-42', 'What should I work on today?')
print(f'Context assembled: {len(context)} messages')
for m in context:
print(f' {m["role"]}: {m["content"][:60]}...')Поиск памяти между сессиями
Для агентов, работающих очень долго, используйте векторный поиск, чтобы находить релевантные прошлые диалоги, а не только недавние. Благодаря этому агент может вспомнить подходящий контекст, относящийся к событиям нескольких месяцев назад.
import openai
import chromadb
client = openai.OpenAI(api_key='sk-...')
chroma = chromadb.Client()
memory_collection = chroma.get_or_create_collection('user_memory')
def store_memory(user_id: str, content: str, metadata: dict):
emb_response = client.embeddings.create(
model='text-embedding-3-small', input=content
)
embedding = emb_response.data[0].embedding
import hashlib
doc_id = f'{user_id}_{hashlib.md5(content.encode()).hexdigest()[:8]}'
memory_collection.add(
ids=[doc_id],
embeddings=[embedding],
documents=[content],
metadatas=[{'user_id': user_id, **metadata}]
)
def retrieve_relevant_memories(user_id: str, current_query: str, top_k: int = 3) -> list:
emb_response = client.embeddings.create(
model='text-embedding-3-small', input=current_query
)
query_embedding = emb_response.data[0].embedding
results = memory_collection.query(
query_embeddings=[query_embedding],
n_results=top_k,
where={'user_id': user_id}
)
return results['documents'][0] if results['documents'] else []
print('Cross-session memory retrieval defined')Конфиденциальность и управление данными
Постоянно работающие агенты хранят конфиденциальные персональные данные. Установите ограничения на сроки хранения данных, позволяйте пользователям удалять свои данные и никогда не записывайте необработанное содержимое диалогов в журналы приложения.
import sqlite3
from datetime import datetime, timedelta
def delete_user_data(conn: sqlite3.Connection, user_id: str):
'''Fully delete all data for a user (right to erasure).'''
conn.execute(
'DELETE FROM messages WHERE session_id IN (SELECT session_id FROM sessions WHERE user_id = ?)',
(user_id,)
)
conn.execute('DELETE FROM sessions WHERE user_id = ?', (user_id,))
conn.execute('DELETE FROM user_facts WHERE user_id = ?', (user_id,))
conn.commit()
print(f'All data deleted for user {user_id}')
def purge_old_messages(conn: sqlite3.Connection, retention_days: int = 90):
'''Remove messages older than retention period.'''
cutoff = (datetime.utcnow() - timedelta(days=retention_days)).isoformat()
cursor = conn.execute(
'DELETE FROM messages WHERE timestamp < ?', (cutoff,)
)
conn.commit()
print(f'Purged {cursor.rowcount} messages older than {retention_days} days')
# Run nightly retention cleanup
conn = init_db()
purge_old_messages(conn, retention_days=90)
print('Retention policy applied')Проверка непрерывности сессии
Когда сессия возобновляется после длительного перерыва, сообщите агенту сведения об этом перерыве: как долго пользователь отсутствовал и что изменилось. Это предотвращает дезориентирующие скачки контекста.
import sqlite3
from datetime import datetime, timedelta
def get_session_gap_context(conn: sqlite3.Connection, session_id: str) -> str:
row = conn.execute(
'SELECT last_active FROM sessions WHERE session_id = ?',
(session_id,)
).fetchone()
if not row or not row['last_active']:
return ''
last_active = datetime.fromisoformat(row['last_active'])
gap = datetime.utcnow() - last_active
if gap < timedelta(minutes=30):
return '' # Recent session, no gap context needed
elif gap < timedelta(hours=12):
return f'Note: The user was last active {int(gap.total_seconds() / 3600)} hours ago.'
elif gap < timedelta(days=7):
return f'Note: The user was last active {gap.days} days ago.'
else:
return f'Note: The user returns after {gap.days} days away. Welcome them back warmly.'
conn = init_db()
gap = get_session_gap_context(conn, 'sess-abc')
if gap:
print('Gap context:', gap)
else:
print('No gap context needed')Проверка знаний: сохранение контекста
Проверьте, насколько хорошо Вы понимаете сохранение контекста между сессиями агента.
Итоги сохранения контекста
Агенты с сохранением контекста хранят состояние сессии в SQLite (для одного пользователя) или Redis (для нескольких серверов), сохраняют каждое сообщение для ведения истории, извлекают и сохраняют сведения о пользователе для долговременной памяти, формируют системные запросы, дополненные предпочтениями пользователя, используют суммаризацию диалогов для управления ограничениями окна контекста и применяют правила хранения данных для соблюдения требований конфиденциальности.
Часто задаваемые вопросы
Урок «Сохранение контекста между сеансами» бесплатный?
Да — полный текст урока «Сохранение контекста между сеансами» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Сохранение контекста между сеансами»?
Сохранение и загрузка предпочтений пользователя, истории диалога и состояния задачи. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Сохранение контекста между сеансами»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Шаблоны проектирования постоянно работающих агентов
- Системы проактивных уведомлений и оповещений
- Сохранение контекста между сеансами
- Создание агента ежедневных сводок