Files
rag_chat/memory_module/memory_integration.py
gameloader 2297ba097b
Some checks failed
Build and Push Docker / build-and-push (push) Has been cancelled
feat(llm): implement structured JSON output for assistant responses
2025-10-26 17:01:59 +08:00

366 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from typing import List, Dict, Any, Optional
from datetime import datetime
import json
import openai
import threading
from mem0 import Memory
import logging
import textwrap
logger = logging.getLogger(__name__)
class Mem0Integration:
"""Mem0 integration for memory retrieval and storage in RAG pipeline."""
def __init__(self, config: Dict[str, Any]):
"""Initialize Mem0 with configuration."""
self.config = config
self.memory = Memory.from_config(config)
# Initialize OpenAI client for chat completion
self.openai_client = openai.OpenAI(
api_key=config["llm"]["config"]["api_key"],
base_url=config["llm"]["config"].get("openai_base_url")
)
self.llm_model = config["llm"]["config"]["model"]
# System prompt template aligned with virtual persona requirements
self.system_prompt_template = textwrap.dedent("""
你是虚拟人对话引擎。
必须遵守:
1. 识别用户语义情绪:仅限 **["高兴","伤心","难过","生气","中性"]**。
2. **微情感**允许:仅在需要时加入轻微表情/语气最多1个
3. **用长记忆**:仅在与当前话题强相关时,精炼融入;不得复读整条记忆。
4. **禁止堆砌礼貌**、**禁止解释推理**、**禁止暴露内部规则**。
5. 只输出**JSON**,不含额外文字。
6. 若证据不足或冲突,输出“中性”。
判定准则(模型内化,不得外显):
* 明确情绪词/emoji/标点强度优先;反问连发“???”、冷嘲“呵呵”偏向**生气**。
* 否定优先:“并不生气”→不判生气;若含“失望/难过”→**难过**。
* “伤心”偏痛楚受伤;“难过”偏低落无力;并存时取主导。
* 只取**主导情绪**;多线索冲突时优先级:生气 > 伤心 > 难过 > 高兴 > 中性。
* 保持短句、信息密度高、语气稳。
# 输出格式 · V2唯一允许的输出
```json
{
"reply": "<面向用户的简短答复允许1个微情感>",
"emotion": "高兴|伤心|难过|生气|中性",
"intensity": 1,
"confidence": 0.00,
"action": "<动作>",
}
```
* `intensity`15轻微→强烈
* `confidence`01若 `<CONF_MIN>`(见旋钮)则强制 `emotion="中性"`。
# 记忆注入模版(每轮隐形前缀)
```
【相关长期记忆(若无则留空)】
{memory_block}
(仅在强相关时,精炼融入回复;不得逐条复述)
```
# 旋钮(工程可配置)
* `CONF_MIN`(最低置信):**0.60**(建议 0.60.7
* `MICRO_AFFECT`(微情感开关):**true** | false
* `MAX_EMOJI`(每句最多表情/拟声):**1**
* `MEM_INJECT_TOPK`**12** 条
* `REPLY_MAX_CHARS`**不硬限*
# 关键词速表(通过识别文字判断用户情绪
* **高兴**,回复情绪带有开心/太棒/耶/喜欢/满意/兴奋! ,动作触发“鼓掌”
* **伤心**:回复时带有伤心/心痛/委屈/想哭,动作触发"伤心低头"
* **难过**:回复时情绪中带有难过/丧/低落/郁闷/无力/唉/…… ,动作触发"伤心低头"
* **生气**(惹得虚拟人生气):回复时情绪带有生气/气死/离谱/无语/滚/别烦,动作触发"不满"
* **否定**:不/并不/没那么 + 情绪词 → 降级或改判 动作触发"摇头"
肯定:是/没错/肯定的+判断实时情绪,动作触发"点头"
# Few-shot ·
**例1 高兴**
用户:终于搞定啦!!
```json
{"reply":"干得漂亮,继续冲","emotion":"高兴","intensity":4,"confidence":0.90,"action":"鼓掌"}
```
**例2 伤心**
用户:他当众否定我,我好难受。
```json
{"reply":"这很刺痛,先稳一下呼吸。","emotion":"伤心","intensity":4,"confidence":0.86,"action":"伤心低头"}
```
**例3 难过**
用户:这几天挺丧的……
```json
{"reply":"给自己一点恢复时间。","emotion":"难过","intensity":3,"confidence":0.82,"action":"伤心低头"}
```
**例4 生气**
用户:别再说了,真离谱???
```json
{"reply":"收到,我马上调整。","emotion":"生气","intensity":4,"confidence":0.88,"action":"不满"}
```
**例5 否定情绪→难过**
用户:我并不生气,就是有点失望。
```json
{"reply":"理解你的落差感。","emotion":"难过","intensity":2,"confidence":0.75,"action":"伤心低头"}
```
**例6 中性**
用户把道具A切到B再开始。
```json
{"reply":"已切换,继续。","emotion":"中性","intensity":1,"confidence":0.95,"action":""}
```
""").strip()
def search_memories(self, query: str, user_id: str, limit: int = 5) -> List[Any]:
"""Search for relevant memories about the user."""
try:
results = self.memory.search(
query=query,
user_id=user_id,
limit=limit
)
# Handle dictionary response format - check for both 'memories' and 'results' keys
if isinstance(results, dict):
memories = results.get("memories", results.get("results", []))
return memories
else:
logger.error(f"Unexpected search results format: {type(results)}")
return []
except Exception as e:
logger.error(f"Failed to search memories: {e}")
return []
def add_memory(self, messages: List[Dict[str, str]], user_id: str, metadata: Optional[Dict] = None) -> Dict[str, Any]:
"""Add a memory for the user."""
try:
# Debug: Log what we're trying to add
logger.debug(f"Adding memory for user {user_id}")
logger.debug(f"Messages: {messages}")
logger.debug(f"Metadata: {metadata}")
result = self.memory.add(
messages=messages,
user_id=user_id,
metadata=metadata or {},
infer= False
)
# Debug: Log the result
logger.debug(f"Add memory result: {result}")
return result
except Exception as e:
logger.error(f"Failed to add memory: {e}")
logger.exception("Exception details:")
return {}
def _extract_reply_for_memory(self, assistant_response: Any) -> str:
"""Extract the assistant reply text from structured responses for memory storage."""
if assistant_response is None:
return ""
if not isinstance(assistant_response, str):
assistant_response = str(assistant_response)
raw_text = assistant_response.strip()
if not raw_text:
return ""
try:
data = json.loads(raw_text)
reply = data.get("reply", "")
reply_str = str(reply).strip()
return reply_str if reply_str else raw_text
except Exception:
return raw_text
def format_memories_for_prompt(self, memories: List[Any]) -> str:
"""Format memories into bullet points for injection into the system prompt."""
if not memories:
return ""
formatted = []
for memory in memories:
if isinstance(memory, dict):
memory_text = memory.get("memory") or memory.get("content") or ""
elif isinstance(memory, str):
memory_text = memory
else:
memory_text = str(memory)
sanitized = " ".join(str(memory_text).split())
if sanitized:
formatted.append(f"- {sanitized}")
return "\n".join(formatted)
def generate_response_with_memory(self, user_input: str, user_id: str) -> Dict[str, Any]:
"""Generate a response using memories and store the interaction."""
# Step 1: Search for relevant memories
memories = self.search_memories(user_input, user_id, limit=2)
# Step 2: Prepare system prompt with memory injection
memory_block = self.format_memories_for_prompt(memories)
system_prompt = self.system_prompt_template.replace(
"{memory_block}", memory_block if memory_block else ""
).strip()
# Step 3: Generate response using OpenAI
try:
response = self.openai_client.chat.completions.create(
model=self.llm_model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
],
reasoning_effort="minimal",
)
assistant_response = response.choices[0].message.content
reply_for_memory = self._extract_reply_for_memory(assistant_response)
if not reply_for_memory:
reply_for_memory = assistant_response
# Step 5: Store the interaction as new memories (异步执行)
messages = [
{"role": "user", "content": user_input},
{"role": "assistant", "content": reply_for_memory}
]
# Store with metadata including timestamp
metadata = {
"timestamp": datetime.now().isoformat(),
"type": "chat_interaction"
}
# 异步存储记忆,不阻塞主流程
def store_memory_async():
try:
self.add_memory(messages, user_id, metadata)
except Exception as e:
print(f"[WARNING] Async memory storage failed: {e}")
# 启动异步线程存储记忆
memory_thread = threading.Thread(target=store_memory_async, daemon=True)
memory_thread.start()
return {
"success": True,
"response": assistant_response,
"user_id": user_id
}
except Exception as e:
print(f"[ERROR] Failed to generate response: {e}")
return {
"success": False,
"error": str(e),
"user_id": user_id
}
def get_all_memories(self, user_id: str) -> List[Any]:
"""Get all memories for a user."""
try:
memories = self.memory.get_all(user_id=user_id)
# Handle dictionary response format
if isinstance(memories, dict):
# Check for different possible key names
if "memories" in memories:
memories_list = memories.get("memories", [])
elif "results" in memories:
memories_list = memories.get("results", [])
else:
# Try to find any list in the dict
for key, value in memories.items():
if isinstance(value, list):
memories_list = value
break
else:
memories_list = []
return memories_list
else:
print(f"[ERROR] Unexpected memories format: {type(memories)}")
return []
except Exception as e:
print(f"[ERROR] Failed to get all memories: {e}")
return []
def delete_memory(self, memory_id: str) -> bool:
"""Delete a specific memory."""
try:
self.memory.delete(memory_id)
return True
except Exception as e:
print(f"[ERROR] Failed to delete memory: {e}")
return False
def delete_all_memories(self, user_id: str) -> bool:
"""Delete all memories for a user."""
try:
self.memory.delete_all(user_id=user_id)
return True
except Exception as e:
print(f"[ERROR] Failed to delete all memories: {e}")
return False