mem1 0.0.4__py3-none-any.whl → 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mem1/__init__.py +7 -1
- mem1/config.py +27 -1
- mem1/llm.py +49 -2
- mem1/memory_es.py +99 -80
- mem1/prompts.py +33 -6
- mem1-0.0.6.dist-info/METADATA +191 -0
- mem1-0.0.6.dist-info/RECORD +11 -0
- mem1-0.0.4.dist-info/METADATA +0 -217
- mem1-0.0.4.dist-info/RECORD +0 -11
- {mem1-0.0.4.dist-info → mem1-0.0.6.dist-info}/WHEEL +0 -0
mem1/__init__.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Mem1 - 基于 Elasticsearch 的用户记忆系统
|
|
3
3
|
"""
|
|
4
|
+
import logging
|
|
4
5
|
|
|
5
|
-
__version__ = "0.
|
|
6
|
+
__version__ = "0.0.5"
|
|
7
|
+
|
|
8
|
+
# 屏蔽第三方库的详细日志(必须在导入前设置)
|
|
9
|
+
logging.getLogger("elastic_transport").setLevel(logging.WARNING)
|
|
10
|
+
logging.getLogger("elastic_transport.transport").setLevel(logging.WARNING)
|
|
11
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
6
12
|
|
|
7
13
|
from mem1.memory_es import Mem1Memory
|
|
8
14
|
from mem1.config import Mem1Config, LLMConfig
|
mem1/config.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""配置管理"""
|
|
2
2
|
import os
|
|
3
|
+
from typing import Optional
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
|
|
5
6
|
|
|
@@ -11,6 +12,17 @@ class LLMConfig(BaseModel):
|
|
|
11
12
|
base_url: str
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
class VLConfig(BaseModel):
|
|
16
|
+
"""视觉语言模型配置(可选,配置了 model 即启用,使用 dashscope SDK)"""
|
|
17
|
+
model: str = ""
|
|
18
|
+
api_key: str = ""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def enabled(self) -> bool:
|
|
22
|
+
"""只要配置了 model 就启用"""
|
|
23
|
+
return bool(self.model)
|
|
24
|
+
|
|
25
|
+
|
|
14
26
|
class MemoryConfig(BaseModel):
|
|
15
27
|
"""记忆系统配置"""
|
|
16
28
|
memory_dir: str
|
|
@@ -20,6 +32,7 @@ class MemoryConfig(BaseModel):
|
|
|
20
32
|
update_interval_minutes: int # 距上次更新超过 M 分钟触发
|
|
21
33
|
save_assistant_messages: bool # 是否保存 assistant 回复
|
|
22
34
|
max_assistant_chars: int # assistant 回复超过此长度触发摘要
|
|
35
|
+
context_days_limit: int # get_context 检索最近几天的对话
|
|
23
36
|
|
|
24
37
|
|
|
25
38
|
class ESConfig(BaseModel):
|
|
@@ -36,6 +49,7 @@ class ImagesConfig(BaseModel):
|
|
|
36
49
|
class Mem1Config(BaseModel):
|
|
37
50
|
"""Mem1 总配置"""
|
|
38
51
|
llm: LLMConfig
|
|
52
|
+
vl: VLConfig
|
|
39
53
|
memory: MemoryConfig
|
|
40
54
|
es: ESConfig
|
|
41
55
|
images: ImagesConfig
|
|
@@ -55,6 +69,10 @@ class Mem1Config(BaseModel):
|
|
|
55
69
|
- MEM1_MAX_PROFILE_CHARS: 画像最大字符数
|
|
56
70
|
- MEM1_UPDATE_INTERVAL_ROUNDS: 画像更新间隔轮数
|
|
57
71
|
- MEM1_UPDATE_INTERVAL_MINUTES: 画像更新间隔分钟数
|
|
72
|
+
|
|
73
|
+
可选的环境变量(VL 模型,使用 dashscope SDK):
|
|
74
|
+
- MEM1_VL_MODEL: VL 模型名(如 qwen-vl-max),配置即启用
|
|
75
|
+
- MEM1_VL_API_KEY: dashscope API 密钥
|
|
58
76
|
"""
|
|
59
77
|
# 必需配置检查
|
|
60
78
|
required_vars = {
|
|
@@ -83,6 +101,12 @@ class Mem1Config(BaseModel):
|
|
|
83
101
|
memory_dir = required_vars["MEM1_MEMORY_DIR"]
|
|
84
102
|
images_dir = f"{memory_dir}/images"
|
|
85
103
|
|
|
104
|
+
# VL 模型配置(可选,配置了 model 即启用,使用 dashscope SDK)
|
|
105
|
+
vl_config = VLConfig(
|
|
106
|
+
model=os.getenv("MEM1_VL_MODEL", ""),
|
|
107
|
+
api_key=os.getenv("MEM1_VL_API_KEY", "")
|
|
108
|
+
)
|
|
109
|
+
|
|
86
110
|
return cls(
|
|
87
111
|
llm=LLMConfig(
|
|
88
112
|
provider="openai",
|
|
@@ -90,6 +114,7 @@ class Mem1Config(BaseModel):
|
|
|
90
114
|
api_key=required_vars["MEM1_LLM_API_KEY"],
|
|
91
115
|
base_url=required_vars["MEM1_LLM_BASE_URL"]
|
|
92
116
|
),
|
|
117
|
+
vl=vl_config,
|
|
93
118
|
memory=MemoryConfig(
|
|
94
119
|
memory_dir=memory_dir,
|
|
95
120
|
auto_update_profile=required_vars["MEM1_AUTO_UPDATE_PROFILE"].lower() == "true",
|
|
@@ -97,7 +122,8 @@ class Mem1Config(BaseModel):
|
|
|
97
122
|
update_interval_rounds=int(required_vars["MEM1_UPDATE_INTERVAL_ROUNDS"]),
|
|
98
123
|
update_interval_minutes=int(required_vars["MEM1_UPDATE_INTERVAL_MINUTES"]),
|
|
99
124
|
save_assistant_messages=required_vars["MEM1_SAVE_ASSISTANT_MESSAGES"].lower() == "true",
|
|
100
|
-
max_assistant_chars=int(required_vars["MEM1_MAX_ASSISTANT_CHARS"])
|
|
125
|
+
max_assistant_chars=int(required_vars["MEM1_MAX_ASSISTANT_CHARS"]),
|
|
126
|
+
context_days_limit=int(os.getenv("MEM1_CONTEXT_DAYS_LIMIT", "31"))
|
|
101
127
|
),
|
|
102
128
|
es=ESConfig(
|
|
103
129
|
hosts=es_hosts,
|
mem1/llm.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""LLM 客户端"""
|
|
2
|
-
from typing import List, Dict
|
|
2
|
+
from typing import List, Dict, Optional
|
|
3
3
|
from openai import OpenAI
|
|
4
|
-
from mem1.config import LLMConfig
|
|
4
|
+
from mem1.config import LLMConfig, VLConfig
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class LLMClient:
|
|
@@ -41,3 +41,50 @@ class LLMClient:
|
|
|
41
41
|
response = self.client.chat.completions.create(**kwargs)
|
|
42
42
|
|
|
43
43
|
return response.choices[0].message.content
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class VLClient:
|
|
47
|
+
"""视觉语言模型客户端(基于 dashscope SDK)"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: VLConfig):
|
|
50
|
+
self.config = config
|
|
51
|
+
import dashscope
|
|
52
|
+
dashscope.api_key = config.api_key
|
|
53
|
+
|
|
54
|
+
def understand_image(
|
|
55
|
+
self,
|
|
56
|
+
image_path: str,
|
|
57
|
+
user_description: str = ""
|
|
58
|
+
) -> str:
|
|
59
|
+
"""理解图片内容(OCR + 图片理解)
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
image_path: 图片本地路径
|
|
63
|
+
user_description: 用户对图片的描述(可选)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
图片理解结果(包含 OCR 文字和内容描述)
|
|
67
|
+
"""
|
|
68
|
+
import dashscope
|
|
69
|
+
|
|
70
|
+
prompt = "请分析这张图片,完成以下任务:\n1. OCR识别:提取图片中的所有文字\n2. 内容理解:描述图片的主要内容和关键信息\n\n请用简洁的中文回答,格式如下:\n【文字内容】...\n【图片描述】..."
|
|
71
|
+
|
|
72
|
+
if user_description:
|
|
73
|
+
prompt += f"\n\n用户补充说明:{user_description}"
|
|
74
|
+
|
|
75
|
+
messages = [
|
|
76
|
+
{
|
|
77
|
+
"role": "user",
|
|
78
|
+
"content": [
|
|
79
|
+
{"image": image_path},
|
|
80
|
+
{"text": prompt}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
response = dashscope.MultiModalConversation.call(
|
|
86
|
+
model=self.config.model,
|
|
87
|
+
messages=messages
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return response.output.choices[0].message.content[0]["text"]
|
mem1/memory_es.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""基于 Elasticsearch 的记忆管理系统"""
|
|
2
|
-
import
|
|
2
|
+
import re
|
|
3
3
|
import shutil
|
|
4
4
|
import base64
|
|
5
5
|
import logging
|
|
@@ -8,7 +8,7 @@ from typing import List, Dict, Any, Optional
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from elasticsearch import Elasticsearch
|
|
10
10
|
from mem1.config import Mem1Config
|
|
11
|
-
from mem1.llm import LLMClient
|
|
11
|
+
from mem1.llm import LLMClient, VLClient
|
|
12
12
|
from mem1.prompts import ProfileTemplate, RECALL_DECISION_PROMPT, IMAGE_SEARCH_PROMPT, ASSISTANT_SUMMARY_PROMPT
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
@@ -23,10 +23,10 @@ class Mem1Memory:
|
|
|
23
23
|
"""基于 Elasticsearch 的用户记忆系统
|
|
24
24
|
|
|
25
25
|
数据存储(全部在 ES):
|
|
26
|
-
- ES 索引
|
|
26
|
+
- ES 索引 {index_name}: 历史对话记录 + 图片索引(按 user_id + topic_id 隔离)
|
|
27
27
|
- ES 索引 mem1_user_state: 用户更新状态(轮数、上次更新时间)
|
|
28
28
|
- ES 索引 mem1_user_profile: 用户画像(按 user_id 共享,跨话题)
|
|
29
|
-
-
|
|
29
|
+
- 本地文件: 图片文件存储
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
def __init__(
|
|
@@ -63,6 +63,9 @@ class Mem1Memory:
|
|
|
63
63
|
# LLM 客户端
|
|
64
64
|
self.llm = LLMClient(config.llm)
|
|
65
65
|
|
|
66
|
+
# VL 客户端(可选)
|
|
67
|
+
self.vl = VLClient(config.vl) if config.vl.enabled else None
|
|
68
|
+
|
|
66
69
|
# 业务场景模板
|
|
67
70
|
self.profile_template = profile_template or ProfileTemplate()
|
|
68
71
|
|
|
@@ -74,7 +77,7 @@ class Mem1Memory:
|
|
|
74
77
|
self.save_assistant_messages = config.memory.save_assistant_messages
|
|
75
78
|
self.max_assistant_chars = config.memory.max_assistant_chars
|
|
76
79
|
|
|
77
|
-
#
|
|
80
|
+
# 确保索引存在
|
|
78
81
|
self._ensure_state_index()
|
|
79
82
|
|
|
80
83
|
def _get_user_images_dir(self, user_id: str) -> Path:
|
|
@@ -83,21 +86,36 @@ class Mem1Memory:
|
|
|
83
86
|
images_dir.mkdir(parents=True, exist_ok=True)
|
|
84
87
|
return images_dir
|
|
85
88
|
|
|
86
|
-
def _get_images_index_path(self, user_id: str) -> Path:
|
|
87
|
-
"""获取图片索引文件路径"""
|
|
88
|
-
return self.images_dir / user_id / "_images.json"
|
|
89
|
-
|
|
90
89
|
def _load_images_index(self, user_id: str) -> List[Dict[str, str]]:
|
|
91
|
-
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
"""从对话记录中提取用户所有图片"""
|
|
91
|
+
try:
|
|
92
|
+
response = self.es.search(
|
|
93
|
+
index=self.index_name,
|
|
94
|
+
query={
|
|
95
|
+
"bool": {
|
|
96
|
+
"must": [
|
|
97
|
+
{"term": {"user_id": user_id}},
|
|
98
|
+
{"exists": {"field": "images"}}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
size=1000,
|
|
103
|
+
sort=[{"timestamp": {"order": "asc"}}]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
images = []
|
|
107
|
+
for hit in response["hits"]["hits"]:
|
|
108
|
+
conv_images = hit["_source"].get("images", [])
|
|
109
|
+
images.extend(conv_images)
|
|
110
|
+
return images
|
|
111
|
+
except Exception:
|
|
112
|
+
return []
|
|
96
113
|
|
|
97
|
-
def
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
def _save_image_to_conversation(self, conversation_entry: Dict, image_doc: Dict[str, str]) -> None:
|
|
115
|
+
"""将图片信息添加到对话记录"""
|
|
116
|
+
if "images" not in conversation_entry:
|
|
117
|
+
conversation_entry["images"] = []
|
|
118
|
+
conversation_entry["images"].append(image_doc)
|
|
101
119
|
|
|
102
120
|
def _get_profile(self, user_id: str) -> Optional[str]:
|
|
103
121
|
"""从 ES 获取用户画像"""
|
|
@@ -264,11 +282,19 @@ class Mem1Memory:
|
|
|
264
282
|
user_id = self.user_id
|
|
265
283
|
topic_id = self.topic_id
|
|
266
284
|
|
|
285
|
+
# 构建对话记录
|
|
286
|
+
conversation_entry = {
|
|
287
|
+
"user_id": user_id,
|
|
288
|
+
"topic_id": topic_id,
|
|
289
|
+
"timestamp": ts,
|
|
290
|
+
"messages": [],
|
|
291
|
+
"metadata": metadata or {}
|
|
292
|
+
}
|
|
293
|
+
|
|
267
294
|
# 处理图片
|
|
268
295
|
image_refs = []
|
|
269
296
|
if images:
|
|
270
297
|
user_images_dir = self._get_user_images_dir(user_id)
|
|
271
|
-
images_index = self._load_images_index(user_id)
|
|
272
298
|
timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
273
299
|
|
|
274
300
|
for img in images:
|
|
@@ -281,46 +307,49 @@ class Mem1Memory:
|
|
|
281
307
|
elif 'path' in img:
|
|
282
308
|
shutil.copy(img['path'], img_path)
|
|
283
309
|
|
|
284
|
-
|
|
285
|
-
image_refs.append(rel_path)
|
|
310
|
+
image_refs.append(filename)
|
|
286
311
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
312
|
+
# 生成图片描述(用户描述 + VL 理解)
|
|
313
|
+
user_desc = ""
|
|
314
|
+
for msg in messages:
|
|
315
|
+
if msg["role"] == "user":
|
|
316
|
+
user_desc = msg["content"]
|
|
317
|
+
break
|
|
293
318
|
|
|
294
|
-
|
|
319
|
+
# 如果启用了 VL 模型,调用视觉理解
|
|
320
|
+
if self.vl:
|
|
321
|
+
try:
|
|
322
|
+
vl_result = self.vl.understand_image(str(img_path), user_desc)
|
|
323
|
+
if user_desc:
|
|
324
|
+
description = f"【用户描述】{user_desc}\n\n{vl_result}"
|
|
325
|
+
else:
|
|
326
|
+
description = vl_result
|
|
327
|
+
logger.info(f"🖼️ VL 图片理解完成: {filename}")
|
|
328
|
+
except Exception as e:
|
|
329
|
+
logger.warning(f"⚠️ VL 图片理解失败: {e}, 使用用户描述")
|
|
330
|
+
description = user_desc or img['filename']
|
|
331
|
+
else:
|
|
332
|
+
description = user_desc or img['filename']
|
|
333
|
+
|
|
334
|
+
# 图片信息存入对话记录
|
|
335
|
+
self._save_image_to_conversation(conversation_entry, {
|
|
295
336
|
"filename": filename,
|
|
296
|
-
"path": rel_path,
|
|
297
337
|
"description": description,
|
|
298
338
|
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
299
339
|
"original_name": img['filename']
|
|
300
340
|
})
|
|
301
|
-
|
|
302
|
-
self._save_images_index(user_id, images_index)
|
|
303
|
-
|
|
304
|
-
# 构建对话记录(包含 topic_id)
|
|
305
|
-
conversation_entry = {
|
|
306
|
-
"user_id": user_id,
|
|
307
|
-
"topic_id": topic_id,
|
|
308
|
-
"timestamp": ts,
|
|
309
|
-
"messages": [],
|
|
310
|
-
"metadata": metadata or {}
|
|
311
|
-
}
|
|
312
341
|
|
|
342
|
+
# 处理消息
|
|
313
343
|
first_user_msg = True
|
|
314
344
|
for msg in messages:
|
|
315
345
|
if msg["role"] == "user":
|
|
316
346
|
msg_obj = {"role": "user", "content": msg["content"]}
|
|
317
347
|
if first_user_msg and image_refs:
|
|
318
|
-
msg_obj["
|
|
348
|
+
msg_obj["image_refs"] = image_refs
|
|
319
349
|
first_user_msg = False
|
|
320
350
|
conversation_entry["messages"].append(msg_obj)
|
|
321
351
|
elif self.save_assistant_messages and msg["role"] == "assistant":
|
|
322
352
|
content = msg["content"]
|
|
323
|
-
# 超长回复触发摘要
|
|
324
353
|
if len(content) > self.max_assistant_chars:
|
|
325
354
|
content = self._summarize_assistant_response(content)
|
|
326
355
|
conversation_entry["messages"].append({
|
|
@@ -475,11 +504,15 @@ class Mem1Memory:
|
|
|
475
504
|
|
|
476
505
|
def get_context(
|
|
477
506
|
self,
|
|
478
|
-
query: str,
|
|
479
|
-
include_normal: Optional[bool] = None,
|
|
507
|
+
query: str = "",
|
|
480
508
|
days_limit: Optional[int] = None
|
|
481
509
|
) -> Dict[str, Any]:
|
|
482
|
-
"""获取记忆上下文(当前话题)
|
|
510
|
+
"""获取记忆上下文(当前话题)
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
query: 用户问题(保留参数,暂未使用)
|
|
514
|
+
days_limit: 检索最近几天的对话,默认使用配置值
|
|
515
|
+
"""
|
|
483
516
|
user_id = self.user_id
|
|
484
517
|
profile_content = self._init_profile(user_id)
|
|
485
518
|
|
|
@@ -495,39 +528,21 @@ class Mem1Memory:
|
|
|
495
528
|
except Exception:
|
|
496
529
|
pass
|
|
497
530
|
|
|
498
|
-
|
|
531
|
+
# 强制检索最近 days_limit 天的对话
|
|
532
|
+
if days_limit is None:
|
|
533
|
+
days_limit = self.config.memory.context_days_limit
|
|
534
|
+
conversations = self.get_conversations(days_limit=days_limit)
|
|
535
|
+
normal_content = self._format_conversations_for_llm(conversations) if conversations else ""
|
|
536
|
+
|
|
537
|
+
return {
|
|
499
538
|
"current_time": current_time,
|
|
500
539
|
"user_id": user_id,
|
|
501
540
|
"topic_id": self.topic_id,
|
|
502
541
|
"import_content": profile_content,
|
|
503
|
-
"normal_content":
|
|
504
|
-
"
|
|
505
|
-
"
|
|
506
|
-
"recall_triggered_by": "none",
|
|
507
|
-
"profile_last_updated": profile_last_updated,
|
|
508
|
-
"conversations_count": 0
|
|
542
|
+
"normal_content": normal_content,
|
|
543
|
+
"conversations_count": len(conversations),
|
|
544
|
+
"profile_last_updated": profile_last_updated
|
|
509
545
|
}
|
|
510
|
-
|
|
511
|
-
# 判断是否需要历史记录
|
|
512
|
-
if include_normal is None:
|
|
513
|
-
need_history, reason = self._should_include_history(query)
|
|
514
|
-
result["recall_reason"] = reason
|
|
515
|
-
result["recall_triggered_by"] = "llm_decision"
|
|
516
|
-
elif include_normal:
|
|
517
|
-
need_history = True
|
|
518
|
-
result["recall_triggered_by"] = "manual"
|
|
519
|
-
else:
|
|
520
|
-
need_history = False
|
|
521
|
-
result["recall_triggered_by"] = "manual"
|
|
522
|
-
|
|
523
|
-
if need_history:
|
|
524
|
-
conversations = self.get_conversations(days_limit=days_limit)
|
|
525
|
-
if conversations:
|
|
526
|
-
result["normal_content"] = self._format_conversations_for_llm(conversations)
|
|
527
|
-
result["need_history"] = True
|
|
528
|
-
result["conversations_count"] = len(conversations)
|
|
529
|
-
|
|
530
|
-
return result
|
|
531
546
|
|
|
532
547
|
def _compress_profile(self, user_id: str, profile_content: str) -> str:
|
|
533
548
|
"""压缩用户画像"""
|
|
@@ -589,7 +604,7 @@ class Mem1Memory:
|
|
|
589
604
|
return []
|
|
590
605
|
|
|
591
606
|
images_desc = "\n".join([
|
|
592
|
-
f"[{i}] 文件名: {img['original_name']}, 时间: {img['timestamp']}, 描述: {img['description']
|
|
607
|
+
f"[{i}] 文件名: {img['original_name']}, 时间: {img['timestamp']}, 描述: {img['description']}"
|
|
593
608
|
for i, img in enumerate(images_index)
|
|
594
609
|
])
|
|
595
610
|
|
|
@@ -603,12 +618,16 @@ class Mem1Memory:
|
|
|
603
618
|
response = self.llm.generate(messages, response_format="text")
|
|
604
619
|
|
|
605
620
|
results = []
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
621
|
+
# 提取所有数字(支持多种格式:纯数字、[0]、0. 等)
|
|
622
|
+
import re
|
|
623
|
+
numbers = re.findall(r'\b(\d+)\b', response)
|
|
624
|
+
for num_str in numbers:
|
|
625
|
+
idx = int(num_str)
|
|
626
|
+
if 0 <= idx < len(images_index):
|
|
627
|
+
img = images_index[idx].copy()
|
|
628
|
+
img['abs_path'] = str((self._get_user_images_dir(user_id) / img['filename']).resolve())
|
|
629
|
+
if img not in results: # 去重
|
|
630
|
+
results.append(img)
|
|
612
631
|
|
|
613
632
|
logger.info(f"🖼️ 图片搜索: query='{query}', 找到 {len(results)} 张")
|
|
614
633
|
return results
|
mem1/prompts.py
CHANGED
|
@@ -32,6 +32,14 @@ class ProfileTemplate:
|
|
|
32
32
|
- 关注重点:
|
|
33
33
|
- 特殊要求:
|
|
34
34
|
|
|
35
|
+
## 周期性任务
|
|
36
|
+
(固定日程和重复性工作,格式:[周期] 任务内容)
|
|
37
|
+
- 例:[每周一] 提交周报、[每周五] 制定下周计划、[每月底] 月度汇总
|
|
38
|
+
|
|
39
|
+
## 关键数据
|
|
40
|
+
(重要的数字、金额、数量,用加粗标记)
|
|
41
|
+
- 例:处置 **97起** 案件、涉及金额 **230万元**、完成 **365个** 检查点
|
|
42
|
+
|
|
35
43
|
## 任务时间线
|
|
36
44
|
(用户提到的计划、截止日期、里程碑,格式:[YYYY-MM-DD] 事项)
|
|
37
45
|
|
|
@@ -111,15 +119,26 @@ DEFAULT_PROFILE_UPDATE_PROMPT = """你是用户画像分析专家。从对话记
|
|
|
111
119
|
4. 只记录用户明确表达的信息,不要推测
|
|
112
120
|
5. 如果某个章节没有信息,保留标题但内容留空
|
|
113
121
|
|
|
122
|
+
## 周期性任务提取
|
|
123
|
+
6. 识别固定日程:用户提到"每周一"、"每周五"、"每天"、"每月"等周期性任务
|
|
124
|
+
7. 记录到「周期性任务」章节,格式:[周期] 任务内容
|
|
125
|
+
8. 示例:[每周一] 提交周报、[每周五] 制定下周计划
|
|
126
|
+
|
|
127
|
+
## 关键数字保留【重要】
|
|
128
|
+
9. 所有具体数字必须原样保留并用 **加粗** 标记,禁止概括为"多个"、"若干"、"大量"
|
|
129
|
+
10. 记录到「关键数据」章节,格式:事项描述 **数字**
|
|
130
|
+
11. 正确示例:处置 **97起** 案件、涉及金额 **230万元** ✓
|
|
131
|
+
12. 错误示例:处置多起案件、涉及大额资金 ✗
|
|
132
|
+
|
|
114
133
|
## 时间敏感信息提取
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
13. 任务时间线:提取用户提到的日期、截止时间、计划安排、里程碑,记录到「任务时间线」
|
|
135
|
+
14. 待办事项:识别用户说"下次"、"回头"、"先这样"、"稍后"、"改天"等挂起信号,将未完成的请求记录到「待办事项」
|
|
136
|
+
15. 已完成事项:如果对话中确认某个待办已完成,将其从「待办事项」移除或标记为 [x]
|
|
118
137
|
|
|
119
138
|
## 矛盾检测
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
139
|
+
16. 发现用户前后说法矛盾时(如偏好、身份、需求不一致),不要直接覆盖旧信息
|
|
140
|
+
17. 将矛盾记录到「待澄清事项」,格式:⚠️ 用户曾说"..."(日期),但又说"..."(日期)
|
|
141
|
+
18. 只有用户明确澄清后,才能更新对应信息并移除待澄清标记
|
|
123
142
|
|
|
124
143
|
---
|
|
125
144
|
*最后更新: {timestamp}*
|
|
@@ -220,6 +239,14 @@ YUQING_PROFILE_TEMPLATE = ProfileTemplate(
|
|
|
220
239
|
- 关注重点:(时效性/准确性/全面性等)
|
|
221
240
|
- 图表偏好:
|
|
222
241
|
|
|
242
|
+
## 周期性任务
|
|
243
|
+
(固定日程和重复性工作,格式:[周期] 任务内容)
|
|
244
|
+
- 例:[每周一] 提交周报、[每周五] 制定下周计划、[每月底] 月度汇总
|
|
245
|
+
|
|
246
|
+
## 关键数据
|
|
247
|
+
(重要的数字、金额、数量,用加粗标记)
|
|
248
|
+
- 例:处置 **97起** 案件、涉及金额 **230万元**
|
|
249
|
+
|
|
223
250
|
## 任务时间线
|
|
224
251
|
(报告截止日期、汇报计划、定期任务,格式:[YYYY-MM-DD] 事项)
|
|
225
252
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mem1
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: 基于云服务的用户记忆系统
|
|
5
|
+
Project-URL: Homepage, https://github.com/sougannkyou/mem1
|
|
6
|
+
Project-URL: Repository, https://github.com/sougannkyou/mem1
|
|
7
|
+
Author: Song
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: langchain,llm,memory,user-profile
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: dashscope>=1.14.0
|
|
18
|
+
Requires-Dist: elasticsearch>=8.0.0
|
|
19
|
+
Requires-Dist: openai>=1.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: ipython>=8.0.0; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# mem1 - 用户记忆系统
|
|
27
|
+
|
|
28
|
+
让 AI 真正"记住"用户:三层记忆架构 + 图片记忆 + 话题隔离 + 业务场景解耦。
|
|
29
|
+
|
|
30
|
+
## 核心特性
|
|
31
|
+
|
|
32
|
+
- **三层记忆架构**:短期会话 → 用户画像 → 长期记录
|
|
33
|
+
- **话题隔离**:同一用户可有多个话题,对话按话题隔离,画像跨话题共享
|
|
34
|
+
- **图片记忆**:存储图片时自动调用 VL 模型生成描述(OCR + 内容理解),搜索时基于文字描述召回
|
|
35
|
+
- **业务解耦**:通过 ProfileTemplate 适配不同场景
|
|
36
|
+
- **画像自动更新**:基于对话轮数/时间自动触发 LLM 更新用户画像
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install mem1
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from mem1 import Mem1Memory, Mem1Config
|
|
48
|
+
|
|
49
|
+
# 从环境变量加载配置
|
|
50
|
+
config = Mem1Config.from_env()
|
|
51
|
+
|
|
52
|
+
# 创建记忆实例(绑定用户和话题)
|
|
53
|
+
memory = Mem1Memory(config, user_id="user001", topic_id="project_a")
|
|
54
|
+
|
|
55
|
+
# 添加对话
|
|
56
|
+
memory.add_conversation(
|
|
57
|
+
messages=[
|
|
58
|
+
{"role": "user", "content": "你好,我是张明"},
|
|
59
|
+
{"role": "assistant", "content": "你好张明!"}
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 获取上下文(含用户画像 + 最近对话)
|
|
64
|
+
ctx = memory.get_context()
|
|
65
|
+
print(ctx['import_content']) # 用户画像
|
|
66
|
+
print(ctx['normal_content']) # 最近对话记录
|
|
67
|
+
print(ctx['current_time']) # 当前时间
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 环境变量配置
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# LLM 配置
|
|
74
|
+
MEM1_LLM_API_KEY=your-api-key
|
|
75
|
+
MEM1_LLM_BASE_URL=https://api.deepseek.com
|
|
76
|
+
MEM1_LLM_MODEL=deepseek-chat
|
|
77
|
+
|
|
78
|
+
# ES 配置
|
|
79
|
+
MEM1_ES_HOSTS=http://localhost:9200
|
|
80
|
+
MEM1_ES_INDEX=conversation_history
|
|
81
|
+
|
|
82
|
+
# 记忆配置
|
|
83
|
+
MEM1_MEMORY_DIR=./memories
|
|
84
|
+
MEM1_AUTO_UPDATE_PROFILE=true
|
|
85
|
+
MEM1_MAX_PROFILE_CHARS=3000
|
|
86
|
+
MEM1_UPDATE_INTERVAL_ROUNDS=5
|
|
87
|
+
MEM1_UPDATE_INTERVAL_MINUTES=3
|
|
88
|
+
MEM1_SAVE_ASSISTANT_MESSAGES=true
|
|
89
|
+
MEM1_MAX_ASSISTANT_CHARS=500
|
|
90
|
+
MEM1_CONTEXT_DAYS_LIMIT=31
|
|
91
|
+
|
|
92
|
+
# VL 视觉模型(可选,配置 MODEL 即启用,使用 dashscope SDK)
|
|
93
|
+
MEM1_VL_MODEL=qwen-vl-max
|
|
94
|
+
MEM1_VL_API_KEY=your-dashscope-key
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 图片记忆
|
|
98
|
+
|
|
99
|
+
### 实现机制
|
|
100
|
+
|
|
101
|
+
1. **存储阶段**:`add_conversation()` 遇到图片时自动调用 VL 模型(如 Qwen-VL)
|
|
102
|
+
2. **VL 处理**:生成包含三部分的描述文本
|
|
103
|
+
- 【用户描述】用户发送图片时的文字说明
|
|
104
|
+
- 【文字内容】OCR 识别图片中的文字
|
|
105
|
+
- 【图片描述】VL 模型对图片内容的理解
|
|
106
|
+
3. **数据存储**:图片信息存入 ES 对话记录的 `images` 字段,图片文件存本地
|
|
107
|
+
4. **搜索召回**:`search_images()` 基于描述文本进行关键词匹配,返回图片路径
|
|
108
|
+
|
|
109
|
+
### ES 数据结构
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"user_id": "user001",
|
|
114
|
+
"topic_id": "default",
|
|
115
|
+
"timestamp": "2026-01-06 16:46:03",
|
|
116
|
+
"messages": [
|
|
117
|
+
{"role": "user", "content": "...", "image_refs": ["20260106_164603_report.png"]}
|
|
118
|
+
],
|
|
119
|
+
"images": [
|
|
120
|
+
{
|
|
121
|
+
"filename": "20260106_164603_report.png",
|
|
122
|
+
"description": "【用户描述】...\n\n【文字内容】...\n\n【图片描述】...",
|
|
123
|
+
"timestamp": "2026-01-06 16:46:16",
|
|
124
|
+
"original_name": "report.png"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 使用示例
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
# 添加带图片的对话
|
|
134
|
+
memory.add_conversation(
|
|
135
|
+
messages=[{"role": "user", "content": "这是今天的报表"}],
|
|
136
|
+
images=[{"path": "./report.png", "filename": "report.png"}]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 搜索图片(基于 VL 生成的描述)
|
|
140
|
+
results = memory.search_images(query="报表")
|
|
141
|
+
# 返回: [{"filename": "...", "description": "...", "abs_path": "..."}]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 核心接口
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
memory = Mem1Memory(config, user_id="user001", topic_id="project_a")
|
|
148
|
+
|
|
149
|
+
# 添加对话
|
|
150
|
+
memory.add_conversation(messages=[...], images=[...], metadata={...})
|
|
151
|
+
|
|
152
|
+
# 获取上下文(画像 + 最近 N 天对话)
|
|
153
|
+
ctx = memory.get_context(days_limit=31)
|
|
154
|
+
|
|
155
|
+
# 查询对话
|
|
156
|
+
convs = memory.get_conversations(days_limit=7)
|
|
157
|
+
all_convs = memory.get_all_conversations(days_limit=7)
|
|
158
|
+
|
|
159
|
+
# 图片搜索
|
|
160
|
+
results = memory.search_images(query="麻花")
|
|
161
|
+
|
|
162
|
+
# 话题管理
|
|
163
|
+
topics = memory.list_topics()
|
|
164
|
+
memory.delete_topic()
|
|
165
|
+
memory.delete_user()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## ES 索引
|
|
169
|
+
|
|
170
|
+
| 索引 | 用途 |
|
|
171
|
+
|------|------|
|
|
172
|
+
| `conversation_history` | 对话记录(含图片索引) |
|
|
173
|
+
| `mem1_user_state` | 用户状态 |
|
|
174
|
+
| `mem1_user_profile` | 用户画像 |
|
|
175
|
+
|
|
176
|
+
## LLM 提示词建议
|
|
177
|
+
|
|
178
|
+
使用 `get_context()` 获取上下文后,建议在 system prompt 中加入以下规则,避免 LLM 编造信息:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
## 重要规则
|
|
182
|
+
1. 回答必须基于上述对话记录中的实际内容,严禁编造任何信息
|
|
183
|
+
2. 涉及数字(金额、数量、百分比、日期等)时,必须从对话记录中原样提取,不得估算或编造
|
|
184
|
+
3. 需要汇总累加时,必须列出计算过程(如:23+31+18+25=97)
|
|
185
|
+
4. 涉及人名、公司名、账号名等实体时,必须使用对话中的原始名称
|
|
186
|
+
5. 如果对话记录中没有相关信息,请明确说"对话记录中未提及",不要猜测
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
mem1/__init__.py,sha256=tNsBrO4d7fujDIPpvl6pweVcg5kHr_EYRgslR8nWWEI,494
|
|
2
|
+
mem1/config.py,sha256=YWMzO3AIRp0PEa37fBzScRuY0TsoVJmHMi1xzDpmLsk,5123
|
|
3
|
+
mem1/langchain_middleware.py,sha256=h2mG7K2Tq1N7IovXMvCyvOhsAwTWOR1NAqivF4db2AE,6648
|
|
4
|
+
mem1/llm.py,sha256=Mq5a-3RMXeIXjVyv_W2-1JGzCSZ1PJ8hKGyXpQF8r6M,2632
|
|
5
|
+
mem1/memory_es.py,sha256=P836RJjA4TcOEqrA3ja9kQMD092mBokMRjllgsk0ki0,29198
|
|
6
|
+
mem1/memory_md.py,sha256=uu_TvdBoUpAncT1eissOSe1Y3vCy3iWMcuvCy3vCjEA,26258
|
|
7
|
+
mem1/memory_tools.py,sha256=b1YBiRNet0gXnW-KGIZ2KQclluB9Q6dli_DbWLS571k,3646
|
|
8
|
+
mem1/prompts.py,sha256=ISVnCnrZ1QJGcQOO6bK30ZPJPCpasd2Hs9n_MsVY_R4,9987
|
|
9
|
+
mem1-0.0.6.dist-info/METADATA,sha256=oaqbpU3W2JjdAyBUwb-3tyMOFeIlf9BNwMicShInxoQ,5642
|
|
10
|
+
mem1-0.0.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
mem1-0.0.6.dist-info/RECORD,,
|
mem1-0.0.4.dist-info/METADATA
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mem1
|
|
3
|
-
Version: 0.0.4
|
|
4
|
-
Summary: 基于云服务的用户记忆系统
|
|
5
|
-
Project-URL: Homepage, https://github.com/sougannkyou/mem1
|
|
6
|
-
Project-URL: Repository, https://github.com/sougannkyou/mem1
|
|
7
|
-
Author: Song
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: langchain,llm,memory,user-profile
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Python: >=3.12
|
|
17
|
-
Requires-Dist: elasticsearch>=8.0.0
|
|
18
|
-
Requires-Dist: openai>=1.0.0
|
|
19
|
-
Requires-Dist: pydantic>=2.0.0
|
|
20
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
21
|
-
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: ipython>=8.0.0; extra == 'dev'
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# mem1 - 用户记忆系统
|
|
26
|
-
|
|
27
|
-
让 AI 真正"记住"用户:三层记忆架构 + 图片记忆 + 话题隔离 + 业务场景解耦。
|
|
28
|
-
|
|
29
|
-
## 为什么需要 mem1?
|
|
30
|
-
|
|
31
|
-
LLM 本身无状态,每次对话都是"失忆"的。mem1 让 AI 助手能够:
|
|
32
|
-
- 记住用户是谁(身份、背景)
|
|
33
|
-
- 记住用户喜欢什么(偏好、习惯)
|
|
34
|
-
- 记住用户说过什么(历史对话、图片)
|
|
35
|
-
- 记住用户的反馈(表扬、批评)
|
|
36
|
-
|
|
37
|
-
## 核心特性
|
|
38
|
-
|
|
39
|
-
- **三层记忆架构**:短期会话 → 用户画像 → 长期记录,参考 ChatGPT Memory 设计
|
|
40
|
-
- **话题隔离**:同一用户可有多个话题,对话按话题隔离,画像跨话题共享
|
|
41
|
-
- **图片记忆**:支持存储和语义搜索用户发送的图片
|
|
42
|
-
- **业务解耦**:通过 ProfileTemplate 适配不同场景(舆情、电商、医疗等)
|
|
43
|
-
- **智能检索**:LLM 判断是否需要回溯历史,节省 token
|
|
44
|
-
- **画像自动更新**:基于对话轮数/时间自动触发 LLM 更新用户画像
|
|
45
|
-
- **助手回复摘要**:超长回复自动摘要,节省存储
|
|
46
|
-
|
|
47
|
-
## 三层记忆架构
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
51
|
-
│ Tier 1: 短期记忆 (LangChain 管理) │
|
|
52
|
-
│ - 当前会话 messages,会话结束即清空 │
|
|
53
|
-
└─────────────────────────────────────────────────────────────┘
|
|
54
|
-
↓ 会话结束时保存
|
|
55
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
56
|
-
│ Tier 2: 用户画像 (ES: mem1_user_profile) │
|
|
57
|
-
│ - LLM 从历史对话中提炼的结构化信息 │
|
|
58
|
-
│ - 基本信息、偏好习惯、任务时间线、待办事项、待澄清事项 │
|
|
59
|
-
│ - 跨话题共享 │
|
|
60
|
-
└─────────────────────────────────────────────────────────────┘
|
|
61
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
62
|
-
│ Tier 3: 长期记忆 (ES: conversation_history) │
|
|
63
|
-
│ - 原始对话记录(带时间戳、元数据、图片) │
|
|
64
|
-
│ - 按 user_id + topic_id 隔离 │
|
|
65
|
-
│ - 按需加载,避免 token 浪费 │
|
|
66
|
-
└─────────────────────────────────────────────────────────────┘
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## 安装
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
pip install mem1
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## 快速开始
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from mem1 import Mem1Memory, Mem1Config
|
|
79
|
-
|
|
80
|
-
# 方式1:从环境变量加载配置
|
|
81
|
-
config = Mem1Config.from_env()
|
|
82
|
-
|
|
83
|
-
# 方式2:手动配置
|
|
84
|
-
from mem1 import LLMConfig
|
|
85
|
-
from mem1.config import MemoryConfig, ESConfig, ImagesConfig
|
|
86
|
-
|
|
87
|
-
config = Mem1Config(
|
|
88
|
-
llm=LLMConfig(
|
|
89
|
-
provider="openai",
|
|
90
|
-
model="deepseek-chat",
|
|
91
|
-
api_key="your-api-key",
|
|
92
|
-
base_url="https://api.deepseek.com"
|
|
93
|
-
),
|
|
94
|
-
memory=MemoryConfig(
|
|
95
|
-
memory_dir="./memories",
|
|
96
|
-
auto_update_profile=True,
|
|
97
|
-
max_profile_chars=3000,
|
|
98
|
-
update_interval_rounds=5,
|
|
99
|
-
update_interval_minutes=3,
|
|
100
|
-
save_assistant_messages=True,
|
|
101
|
-
max_assistant_chars=500
|
|
102
|
-
),
|
|
103
|
-
es=ESConfig(
|
|
104
|
-
hosts=["http://localhost:9200"],
|
|
105
|
-
index_name="conversation_history"
|
|
106
|
-
),
|
|
107
|
-
images=ImagesConfig(
|
|
108
|
-
images_dir="./memories/images"
|
|
109
|
-
)
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
# 创建记忆实例(绑定用户和话题)
|
|
113
|
-
memory = Mem1Memory(
|
|
114
|
-
config=config,
|
|
115
|
-
user_id="user001",
|
|
116
|
-
topic_id="project_a" # 可选,默认 "default"
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
# 添加对话
|
|
120
|
-
memory.add_conversation(
|
|
121
|
-
messages=[
|
|
122
|
-
{"role": "user", "content": "你好,我是张明"},
|
|
123
|
-
{"role": "assistant", "content": "你好张明!"}
|
|
124
|
-
],
|
|
125
|
-
metadata={"topic": "自我介绍"}
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
# 获取上下文(含用户画像)
|
|
129
|
-
ctx = memory.get_context(query="帮我写报告")
|
|
130
|
-
print(ctx['import_content']) # 用户画像
|
|
131
|
-
print(ctx['current_time']) # 当前时间
|
|
132
|
-
|
|
133
|
-
# 手动更新画像
|
|
134
|
-
memory.update_profile()
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## 环境变量配置
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
# LLM 配置
|
|
141
|
-
MEM1_LLM_API_KEY=your-api-key
|
|
142
|
-
MEM1_LLM_BASE_URL=https://api.deepseek.com
|
|
143
|
-
MEM1_LLM_MODEL=deepseek-chat
|
|
144
|
-
|
|
145
|
-
# ES 配置
|
|
146
|
-
MEM1_ES_HOSTS=http://localhost:9200
|
|
147
|
-
MEM1_ES_INDEX=conversation_history
|
|
148
|
-
|
|
149
|
-
# 记忆配置
|
|
150
|
-
MEM1_MEMORY_DIR=./memories
|
|
151
|
-
MEM1_AUTO_UPDATE_PROFILE=true
|
|
152
|
-
MEM1_MAX_PROFILE_CHARS=3000
|
|
153
|
-
MEM1_UPDATE_INTERVAL_ROUNDS=5
|
|
154
|
-
MEM1_UPDATE_INTERVAL_MINUTES=3
|
|
155
|
-
MEM1_SAVE_ASSISTANT_MESSAGES=true
|
|
156
|
-
MEM1_MAX_ASSISTANT_CHARS=500
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## ES 索引
|
|
160
|
-
|
|
161
|
-
| 索引 | 用途 |
|
|
162
|
-
|------|------|
|
|
163
|
-
| `conversation_history` | 对话记录(按 user_id + topic_id 隔离) |
|
|
164
|
-
| `mem1_user_state` | 用户状态(更新轮数、时间) |
|
|
165
|
-
| `mem1_user_profile` | 用户画像(跨话题共享) |
|
|
166
|
-
|
|
167
|
-
## 核心接口
|
|
168
|
-
|
|
169
|
-
```python
|
|
170
|
-
# 创建实例(绑定用户和话题)
|
|
171
|
-
memory = Mem1Memory(config, user_id="user001", topic_id="project_a")
|
|
172
|
-
|
|
173
|
-
# 添加对话(支持图片、元数据)
|
|
174
|
-
memory.add_conversation(
|
|
175
|
-
messages=[...],
|
|
176
|
-
images=[{"filename": "截图.png", "path": "./test.png"}],
|
|
177
|
-
metadata={"topic": "舆情分析"}
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
# 获取上下文
|
|
181
|
-
ctx = memory.get_context(query="问题")
|
|
182
|
-
|
|
183
|
-
# 查询当前话题的对话
|
|
184
|
-
convs = memory.get_conversations(days_limit=7)
|
|
185
|
-
|
|
186
|
-
# 查询用户所有话题的对话
|
|
187
|
-
all_convs = memory.get_all_conversations(days_limit=7)
|
|
188
|
-
|
|
189
|
-
# 搜索图片
|
|
190
|
-
results = memory.search_images(query="麻花")
|
|
191
|
-
|
|
192
|
-
# 列出用户所有话题
|
|
193
|
-
topics = memory.list_topics()
|
|
194
|
-
|
|
195
|
-
# 删除当前话题
|
|
196
|
-
memory.delete_topic()
|
|
197
|
-
|
|
198
|
-
# 删除用户所有数据
|
|
199
|
-
memory.delete_user()
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## 示例
|
|
203
|
-
|
|
204
|
-
见 `examples/` 目录:
|
|
205
|
-
- `basic_usage.py` - 基础用法
|
|
206
|
-
- `langchain_integration.py` - LangChain 集成
|
|
207
|
-
- `batch_import.py` - 批量导入
|
|
208
|
-
- `image_usage.py` - 图片功能
|
|
209
|
-
|
|
210
|
-
## 参考资料
|
|
211
|
-
|
|
212
|
-
- [Reverse Engineering Latest ChatGPT Memory Feature](https://agentman.ai/blog/reverse-ngineering-latest-ChatGPT-memory-feature-and-building-your-own)
|
|
213
|
-
- [How ChatGPT's Memory Actually Works](https://manthanguptaa.in/posts/chatgpt_memory/)
|
|
214
|
-
|
|
215
|
-
## License
|
|
216
|
-
|
|
217
|
-
MIT
|
mem1-0.0.4.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
mem1/__init__.py,sha256=f722Z_XaYwc3dQVhLc4X8oy_ffB8qrR1SuvEH01MGf8,219
|
|
2
|
-
mem1/config.py,sha256=WxZVcj_Hy3Rdf0emUz1l17oomAKj5S_2n7Je0tj-HDs,4146
|
|
3
|
-
mem1/langchain_middleware.py,sha256=h2mG7K2Tq1N7IovXMvCyvOhsAwTWOR1NAqivF4db2AE,6648
|
|
4
|
-
mem1/llm.py,sha256=EKsZHxLrRn-OTxCsPHOYcUTjnEF5RVMnEM8fqWzdkbg,1114
|
|
5
|
-
mem1/memory_es.py,sha256=XPGoWYA2G9QkbrGOO3RVRQdLU4XiNw2CqsXnbV9w2TM,28263
|
|
6
|
-
mem1/memory_md.py,sha256=uu_TvdBoUpAncT1eissOSe1Y3vCy3iWMcuvCy3vCjEA,26258
|
|
7
|
-
mem1/memory_tools.py,sha256=b1YBiRNet0gXnW-KGIZ2KQclluB9Q6dli_DbWLS571k,3646
|
|
8
|
-
mem1/prompts.py,sha256=L0JGVa--V_h9KqqZnb1n1N9oaPqxBhxHFL71Us9J5qM,8685
|
|
9
|
-
mem1-0.0.4.dist-info/METADATA,sha256=rKxWIYdnDbYYQkNbTYdp5Ms9OEqarHU3JrBB8kBhxh4,7420
|
|
10
|
-
mem1-0.0.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
-
mem1-0.0.4.dist-info/RECORD,,
|
|
File without changes
|