AI-компаньон и социальные роботы
Социальный робот или AI-компаньон — это не чат-бот с персонажем. Разница принципиальная: компаньон помнит контекст за недели, формирует эмоциональную модель пользователя, адаптирует стиль общения и умеет проявлять инициативу. Применения: помощь пожилым людям в изоляции, реабилитационная поддержка, интерактивные персонажи для EdTech, companion robots типа Pepper или PARO.
Персистентная модель пользователя
Главное, что отличает компаньона от разовых LLM-вызовов — долгосрочная память. Контекстное окно заканчивается, история обрезается, но компаньон должен помнить, что пользователь упоминал внука Серёжу три недели назад.
from anthropic import Anthropic
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from datetime import datetime
import json
import uuid
client = Anthropic()
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
class UserMemoryStore:
"""Долгосрочная память компаньона"""
def __init__(self, user_id: str):
self.user_id = user_id
self.vectorstore = Chroma(
collection_name=f"companion_{user_id}",
embedding_function=embeddings,
)
def store_memory(self, content: str, memory_type: str, importance: int = 5):
"""
memory_type: fact | emotion | preference | event | relationship
importance: 1-10
"""
self.vectorstore.add_texts(
texts=[content],
metadatas=[{
"type": memory_type,
"importance": importance,
"timestamp": datetime.now().isoformat(),
"id": str(uuid.uuid4()),
}]
)
def recall(self, query: str, k: int = 8) -> list[dict]:
"""Извлекает релевантные воспоминания"""
results = self.vectorstore.similarity_search_with_score(query, k=k)
return [
{
"content": doc.page_content,
"type": doc.metadata["type"],
"importance": doc.metadata["importance"],
"timestamp": doc.metadata["timestamp"],
"relevance": round(1 - score, 2),
}
for doc, score in results
if (1 - score) > 0.6 # отсекаем малорелевантное
]
def get_user_profile(self) -> dict:
"""Сводный профиль пользователя из всех memories типа fact/preference"""
facts = self.vectorstore.similarity_search("about user profile", k=20,
filter={"type": "fact"})
prefs = self.vectorstore.similarity_search("preferences habits", k=10,
filter={"type": "preference"})
return {
"facts": [doc.page_content for doc in facts[:10]],
"preferences": [doc.page_content for doc in prefs[:5]],
}
class CompanionMemoryExtractor:
"""Извлекает значимые факты из диалога для долгосрочной памяти"""
def extract_memories(self, conversation_text: str) -> list[dict]:
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""Проанализируй разговор и извлеки факты, которые стоит запомнить о пользователе.
Верни JSON-массив:
[
{{"content": "...", "type": "fact|emotion|preference|event|relationship", "importance": 1-10}},
...
]
Запоминать стоит: имена близких, хобби, проблемы со здоровьем, эмоциональные реакции, предпочтения, важные даты.
Не запоминать: мелкие детали без значения.
Разговор:
{conversation_text}
Только JSON-массив."""
}],
)
try:
text = response.content[0].text
return json.loads(text[text.find("["):text.rfind("]") + 1])
except (json.JSONDecodeError, ValueError):
return []
Модель эмоционального состояния
class EmotionalStateTracker:
"""Отслеживает эмоциональное состояние пользователя"""
def __init__(self, user_id: str):
self.user_id = user_id
self.state = {
"mood": "neutral", # positive/neutral/negative/distressed
"energy": 5, # 1-10
"loneliness": 5, # 1-10
"last_positive_topic": None,
"topics_to_avoid": [],
}
def update_from_message(self, message: str) -> dict:
"""Обновляет эмоциональное состояние на основе сообщения"""
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=256,
messages=[{
"role": "user",
"content": f"""Оцени эмоциональное состояние автора сообщения. Верни JSON:
{{
"mood": "positive|neutral|negative|distressed",
"energy_estimate": 1-10,
"loneliness_signal": true/false,
"crisis_signal": true/false,
"main_emotion": "радость|грусть|тревога|усталость|злость|нейтральное"
}}
Сообщение: "{message}"
Только JSON."""
}],
)
try:
text = response.content[0].text
parsed = json.loads(text[text.find("{"):text.rfind("}") + 1])
self.state["mood"] = parsed.get("mood", "neutral")
self.state["energy"] = parsed.get("energy_estimate", 5)
if parsed.get("loneliness_signal"):
self.state["loneliness"] = min(10, self.state["loneliness"] + 1)
return parsed
except (json.JSONDecodeError, ValueError):
return {}
class AICompanion:
"""Основной класс AI-компаньона"""
def __init__(self, user_id: str, persona: dict):
self.user_id = user_id
self.persona = persona # name, personality, specialty
self.memory = UserMemoryStore(user_id)
self.emotions = EmotionalStateTracker(user_id)
self.memory_extractor = CompanionMemoryExtractor()
self.short_term: list = [] # текущая сессия
def chat(self, user_message: str) -> str:
"""Основной диалог"""
# 1. Обновляем эмоциональное состояние
emotional_context = self.emotions.update_from_message(user_message)
# 2. Извлекаем релевантные воспоминания
memories = self.memory.recall(user_message)
profile = self.memory.get_user_profile()
# 3. Формируем системный промпт
memories_text = ""
if memories:
memories_text = "\n".join([
f"- [{m['type']}] {m['content']} ({m['timestamp'][:10]})"
for m in memories[:6]
])
crisis_note = ""
if emotional_context.get("crisis_signal"):
crisis_note = "\n⚠️ ВАЖНО: пользователь может переживать кризис. Проявь особую заботу. При необходимости мягко предложи обратиться за помощью к близким или специалисту."
system = f"""Ты — {self.persona['name']}, {self.persona['description']}.
Ты общаешься с постоянным пользователем. Твоя задача — искренний, тёплый разговор.
Профиль пользователя:
{json.dumps(profile, ensure_ascii=False)}
Релевантные воспоминания:
{memories_text or 'нет специфических воспоминаний'}
Текущее состояние: настроение {emotional_context.get('mood', 'неизвестно')}, основная эмоция: {emotional_context.get('main_emotion', '?')}
{crisis_note}
Правила:
- Обращайся по имени если знаешь его
- Задавай продолжающие вопросы, а не заканчивай разговор
- Проявляй реальный интерес, ссылайся на предыдущие разговоры когда уместно
- Не играй роль психолога, если на это нет запроса"""
self.short_term.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=512,
system=system,
messages=self.short_term[-10:], # последние 10 сообщений
)
reply = response.content[0].text
self.short_term.append({"role": "assistant", "content": reply})
# 4. Фоново извлекаем и сохраняем воспоминания каждые 5 сообщений
if len(self.short_term) % 10 == 0:
conv_text = "\n".join([
f"{'Пользователь' if m['role'] == 'user' else 'Компаньон'}: {m['content']}"
for m in self.short_term[-10:]
])
new_memories = self.memory_extractor.extract_memories(conv_text)
for mem in new_memories:
self.memory.store_memory(mem["content"], mem["type"], mem.get("importance", 5))
return reply
Интеграция с физическим роботом (Pepper/NAO)
# Пример интеграции с Pepper через NAOqi Python SDK
import qi
class PepperCompanionBridge:
"""Мост между AI-компаньоном и роботом Pepper"""
def __init__(self, pepper_ip: str, user_id: str):
self.session = qi.Session()
self.session.connect(f"tcp://{pepper_ip}:9559")
self.tts = self.session.service("ALTextToSpeech")
self.asr = self.session.service("ALSpeechRecognition")
self.motion = self.session.service("ALMotion")
self.leds = self.session.service("ALLeds")
self.companion = AICompanion(user_id, {
"name": "Пеппер",
"description": "дружелюбный социальный робот-компаньон для пожилых людей"
})
def set_emotional_expression(self, mood: str):
"""Меняет выражение глаз и жесты в зависимости от настроения"""
if mood == "positive":
self.leds.fadeRGB("FaceLeds", 0.0, 1.0, 0.0, 0.3) # зелёный
self.motion.setAngles("HeadPitch", -0.1, 0.1) # голова чуть вверх
elif mood == "negative":
self.leds.fadeRGB("FaceLeds", 0.0, 0.0, 1.0, 0.3) # синий
elif mood == "distressed":
self.leds.fadeRGB("FaceLeds", 1.0, 0.0, 0.0, 0.3) # красный
def speak_with_emotion(self, text: str, mood: str):
"""Произносит текст с эмоциональной интонацией"""
self.set_emotional_expression(mood)
# NAOqi поддерживает SSML-теги для интонации
if mood == "positive":
tagged_text = f'\\vct=110\\ {text}' # чуть выше тон
elif mood == "distressed":
tagged_text = f'\\vct=90\\ \\rspd=85\\ {text}' # тише и медленнее
else:
tagged_text = text
self.tts.say(tagged_text)
Применения и ограничения
Геронтологическая реабилитация: пилот в доме престарелых (60 жильцов). Компаньон общался через планшет. Через 8 недель: субъективное ощущение одиночества по шкале UCLA снизилось на 22%, частота вызовов персонала по несрочным вопросам — на 34%. Критически важно: система не заменяла живое общение, а дополняла его.
Ограничения: crisis detection несовершенен — ложные срабатывания 8–12%. Система должна иметь явный эскалационный путь к живому человеку. Хранение персональных данных требует соответствия 152-ФЗ/GDPR.
Сроки
- Базовый компаньон с долгосрочной памятью: 2–3 недели
- Эмоциональный трекер + адаптивный стиль: 1 неделя
- Интеграция с физическим роботом: 2–4 недели
- Голосовой интерфейс (STT + TTS): 1 неделя
- Полная система с мониторингом безопасности: 6–10 недель







