amrita_plugin_memory 0.1.0__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.
- amrita_plugin_memory/__init__.py +7 -0
- amrita_plugin_memory/config.py +83 -0
- amrita_plugin_memory/embed.py +70 -0
- amrita_plugin_memory/tools.py +378 -0
- amrita_plugin_memory/vector.py +434 -0
- amrita_plugin_memory-0.1.0.dist-info/METADATA +98 -0
- amrita_plugin_memory-0.1.0.dist-info/RECORD +10 -0
- amrita_plugin_memory-0.1.0.dist-info/WHEEL +5 -0
- amrita_plugin_memory-0.1.0.dist-info/licenses/LICENSE +661 -0
- amrita_plugin_memory-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Configuration for your_plugin_name plugin
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from amrita_core import ModelPreset
|
|
5
|
+
from nonebot import get_driver, get_plugin_config
|
|
6
|
+
from nonebot_plugin_localstore import get_plugin_data_dir
|
|
7
|
+
from nonebot_plugin_uniconf import BaseDataManager
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
DATA_PATH = get_plugin_data_dir().resolve()
|
|
11
|
+
DATA_PATH.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
VECTOR_DB_PATH = DATA_PATH / "vector_db.chroma"
|
|
13
|
+
PLUGIN_IM = "amrita_plugin_memory"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigFile(BaseModel):
|
|
17
|
+
short_term_expiry_days: int = Field(
|
|
18
|
+
default=3, description="短期记忆的过期天数,默认为3天"
|
|
19
|
+
)
|
|
20
|
+
long_term_expiry_days: int = Field(
|
|
21
|
+
default=30, description="长期记忆的过期天数,默认为30天"
|
|
22
|
+
)
|
|
23
|
+
permanent_expiry_days: int = Field(
|
|
24
|
+
default=365, description="永久记忆的过期天数,默认为1年"
|
|
25
|
+
)
|
|
26
|
+
per_session_memory_limit: int = Field(
|
|
27
|
+
default=50, description="每个会话的记忆数量限制,默认为50条"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EnvConfig(BaseModel):
|
|
32
|
+
vector_db_type: Literal["local", "remote"] = Field(
|
|
33
|
+
default="local", description="ChromaDB向量数据库类型"
|
|
34
|
+
)
|
|
35
|
+
vector_db_server: str = Field(
|
|
36
|
+
default="127.0.0.1",
|
|
37
|
+
description="ChromaDB向量数据库地址(当且仅当vector_db_type为remote时生效)",
|
|
38
|
+
)
|
|
39
|
+
vector_db_port: int = Field(
|
|
40
|
+
default=8000,
|
|
41
|
+
description="ChromaDB向量数据库端口(当且仅当vector_db_type为remote时生效)",
|
|
42
|
+
)
|
|
43
|
+
vector_db_server_ssl: bool = Field(
|
|
44
|
+
default=False,
|
|
45
|
+
description="ChromaDB向量数据库是否使用SSL(当且仅当vector_db_type为remote时生效)",
|
|
46
|
+
)
|
|
47
|
+
vector_db_remote_headers: dict = Field(
|
|
48
|
+
default={},
|
|
49
|
+
description="ChromaDB向量数据库远程服务器的请求头(当且仅当vector_db_type为remote时生效)",
|
|
50
|
+
)
|
|
51
|
+
vector_db_tenant: str = Field(
|
|
52
|
+
default="default",
|
|
53
|
+
description="ChromaDB向量数据库租户名称(当且仅当vector_db_type为remote时生效)",
|
|
54
|
+
)
|
|
55
|
+
vector_db_database: str = Field(
|
|
56
|
+
default="default",
|
|
57
|
+
description="ChromaDB向量数据库数据库名称(当且仅当vector_db_type为remote时生效)",
|
|
58
|
+
)
|
|
59
|
+
embedding_model_url: str = Field(
|
|
60
|
+
default="http://127.0.0.1:11434", description="Embedding模型地址"
|
|
61
|
+
)
|
|
62
|
+
embedding_model_name: str = Field(default="auto", description="Embedding模型名称")
|
|
63
|
+
embedding_proctol: Literal["openai-embed", "ollama-embed"] = Field(
|
|
64
|
+
default="ollama-embed", description="Embedding模型协议"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DataManager(BaseDataManager[ConfigFile]):
|
|
69
|
+
config: ConfigFile
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@get_driver().on_startup
|
|
73
|
+
async def setup():
|
|
74
|
+
await DataManager().safe_get_config()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build_preset() -> ModelPreset:
|
|
78
|
+
return ModelPreset(
|
|
79
|
+
model=env_config.embedding_model_name, base_url=env_config.embedding_model_url
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
env_config = get_plugin_config(EnvConfig)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from collections.abc import Iterable, Sequence
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
from amrita_core.protocol import ModelAdapter
|
|
6
|
+
from amrita_core.types import EmbeddingChunk
|
|
7
|
+
from openai import AsyncOpenAI
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OllamaEmbeddingAdapter(ModelAdapter):
|
|
11
|
+
async def call_embed(
|
|
12
|
+
self, texts: Iterable[str], **kwargs
|
|
13
|
+
) -> Sequence[EmbeddingChunk]:
|
|
14
|
+
endp = "/api/embed"
|
|
15
|
+
preset = self.preset
|
|
16
|
+
url = (
|
|
17
|
+
preset.base_url
|
|
18
|
+
if preset.base_url.endswith(endp)
|
|
19
|
+
else preset.base_url.rstrip("/") + endp
|
|
20
|
+
)
|
|
21
|
+
if isinstance(texts, str):
|
|
22
|
+
texts = [texts]
|
|
23
|
+
payload = {"model": preset.model, "input": texts, **kwargs}
|
|
24
|
+
async with aiohttp.ClientSession() as session:
|
|
25
|
+
async with session.post(url, json=payload) as response:
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
data: dict[str, Any] = await response.json()
|
|
28
|
+
if data.get("error"):
|
|
29
|
+
raise RuntimeError(f"Ollama error: {data['error']}")
|
|
30
|
+
|
|
31
|
+
return tuple(
|
|
32
|
+
EmbeddingChunk(
|
|
33
|
+
embedding=chunk,
|
|
34
|
+
index=index,
|
|
35
|
+
)
|
|
36
|
+
for index, chunk in enumerate(data["embeddings"])
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def get_adapter_protocol() -> Literal["ollama-embed"]:
|
|
41
|
+
return "ollama-embed"
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def get_type() -> Literal["embed"]:
|
|
45
|
+
return "embed"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OpenAIEmbeddingAdapter(ModelAdapter):
|
|
49
|
+
async def call_embed(
|
|
50
|
+
self, texts: Iterable[str], **kwargs
|
|
51
|
+
) -> Sequence[EmbeddingChunk]:
|
|
52
|
+
ps = self.preset
|
|
53
|
+
client = AsyncOpenAI(
|
|
54
|
+
base_url=ps.base_url, api_key=ps.api_key, max_retries=5, timeout=60
|
|
55
|
+
)
|
|
56
|
+
if isinstance(texts, str):
|
|
57
|
+
texts = [texts]
|
|
58
|
+
texts = list(texts)
|
|
59
|
+
embedd = await client.embeddings.create(
|
|
60
|
+
input=texts, model=ps.model, **kwargs, timeout=60
|
|
61
|
+
)
|
|
62
|
+
return [EmbeddingChunk.model_validate(data) for data in embedd.data]
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def get_adapter_protocol() -> Literal["openai-embed"]:
|
|
66
|
+
return "openai-embed"
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def get_type() -> Literal["embed"]:
|
|
70
|
+
return "embed"
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from amrita.plugins.chat.runtime import AmritaChatObject
|
|
5
|
+
from amrita_core import (
|
|
6
|
+
FunctionDefinitionSchema,
|
|
7
|
+
FunctionParametersSchema,
|
|
8
|
+
FunctionPropertySchema,
|
|
9
|
+
ToolContext,
|
|
10
|
+
on_tools,
|
|
11
|
+
)
|
|
12
|
+
from chromadb import QueryResult
|
|
13
|
+
from nonebot import logger
|
|
14
|
+
|
|
15
|
+
from .config import DataManager
|
|
16
|
+
from .vector import AsyncUserMemory, MemoryMetadata, get_db_conn
|
|
17
|
+
|
|
18
|
+
WRITE_MEMORY_FUN = FunctionDefinitionSchema(
|
|
19
|
+
name="write_memory",
|
|
20
|
+
description="将当前用户(或群组)的重要信息存入长期记忆",
|
|
21
|
+
parameters=FunctionParametersSchema(
|
|
22
|
+
type="object",
|
|
23
|
+
properties={
|
|
24
|
+
"content": FunctionPropertySchema(
|
|
25
|
+
type="string", description="记忆内容,简洁明了"
|
|
26
|
+
),
|
|
27
|
+
"tags": FunctionPropertySchema(
|
|
28
|
+
type="string",
|
|
29
|
+
description="分类标签,比如`preference`,`project`",
|
|
30
|
+
),
|
|
31
|
+
"importance": FunctionPropertySchema(
|
|
32
|
+
type="string",
|
|
33
|
+
description="重要性等级",
|
|
34
|
+
enum=["low", "medium", "high"],
|
|
35
|
+
),
|
|
36
|
+
},
|
|
37
|
+
required=["content", "tags", "importance"],
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
READ_MEMORY_FUN = FunctionDefinitionSchema(
|
|
41
|
+
name="read_memory",
|
|
42
|
+
description="从记忆库检索当前用户(或群组)相关信息",
|
|
43
|
+
parameters=FunctionParametersSchema(
|
|
44
|
+
type="object",
|
|
45
|
+
properties={
|
|
46
|
+
"query": FunctionPropertySchema(
|
|
47
|
+
type="string",
|
|
48
|
+
description="字符串,用空格分割关键词",
|
|
49
|
+
),
|
|
50
|
+
"top_k": FunctionPropertySchema(
|
|
51
|
+
type="integer", description="返回数量,默认5条", default=5
|
|
52
|
+
),
|
|
53
|
+
"importance": FunctionPropertySchema(
|
|
54
|
+
type="string",
|
|
55
|
+
description="重要性等级,Optional",
|
|
56
|
+
enum=["low", "medium", "high"],
|
|
57
|
+
nullable=True,
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
required=["query"],
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
UPDATE_FUN = FunctionDefinitionSchema(
|
|
66
|
+
name="update_memory",
|
|
67
|
+
description="更新指定unique_id的记忆内容",
|
|
68
|
+
parameters=FunctionParametersSchema(
|
|
69
|
+
type="object",
|
|
70
|
+
properties={
|
|
71
|
+
"id": FunctionPropertySchema(type="string", description="要更新的记忆ID"),
|
|
72
|
+
"content": FunctionPropertySchema(
|
|
73
|
+
type="string", description="新的记忆内容(如不修改可不传)"
|
|
74
|
+
),
|
|
75
|
+
"tags": FunctionPropertySchema(
|
|
76
|
+
type="string",
|
|
77
|
+
description="新的标签(如不修改可不传)",
|
|
78
|
+
),
|
|
79
|
+
"importance": FunctionPropertySchema(
|
|
80
|
+
type="string",
|
|
81
|
+
description="重要性等级(如不修改可不传)",
|
|
82
|
+
enum=["low", "medium", "high"],
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
required=["id"],
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
DELETE_FUN = FunctionDefinitionSchema(
|
|
91
|
+
name="delete_memory",
|
|
92
|
+
description="删除指定unique_id的记忆",
|
|
93
|
+
parameters=FunctionParametersSchema(
|
|
94
|
+
type="object",
|
|
95
|
+
properties={
|
|
96
|
+
"id": FunctionPropertySchema(type="string", description="要删除的记忆ID")
|
|
97
|
+
},
|
|
98
|
+
required=["id"],
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
LIST_MEMORY_FUN = FunctionDefinitionSchema(
|
|
104
|
+
name="list_memory",
|
|
105
|
+
description="列出当前用户(或群组)的所有记忆(返回tag+id)",
|
|
106
|
+
parameters=FunctionParametersSchema(
|
|
107
|
+
type="object",
|
|
108
|
+
properties={
|
|
109
|
+
"limit": FunctionPropertySchema(
|
|
110
|
+
type="integer", description="返回数量,默认5条", default=5
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
required=[],
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@on_tools(WRITE_MEMORY_FUN, custom_run=True, strict=True)
|
|
119
|
+
async def w(ctx: ToolContext) -> str:
|
|
120
|
+
assert isinstance(ctx.ctx.chat_object, AmritaChatObject)
|
|
121
|
+
nb_event = ctx.ctx.chat_object.event
|
|
122
|
+
ope = AsyncUserMemory(get_db_conn())
|
|
123
|
+
await ope.init()
|
|
124
|
+
logger.debug("开始写入记忆...")
|
|
125
|
+
logger.debug(f"会话ID: {nb_event.get_session_id()}")
|
|
126
|
+
logger.debug(f"记忆Payload {ctx.data['content']}")
|
|
127
|
+
if (
|
|
128
|
+
await ope.count_user_notes(nb_event.get_session_id())
|
|
129
|
+
> (await DataManager().safe_get_config()).per_session_memory_limit
|
|
130
|
+
):
|
|
131
|
+
return json.dumps(
|
|
132
|
+
{
|
|
133
|
+
"status": "error",
|
|
134
|
+
"message": "当前会话的记忆数量已超过限制,请清理后再试",
|
|
135
|
+
},
|
|
136
|
+
ensure_ascii=False,
|
|
137
|
+
indent=4,
|
|
138
|
+
)
|
|
139
|
+
meta = MemoryMetadata(
|
|
140
|
+
importance=ctx.data["importance"],
|
|
141
|
+
tags=ctx.data["tags"],
|
|
142
|
+
)
|
|
143
|
+
await ope.add_note(
|
|
144
|
+
nb_event.get_session_id(),
|
|
145
|
+
ctx.data["content"],
|
|
146
|
+
metadata=meta,
|
|
147
|
+
)
|
|
148
|
+
dmp = meta.model_dump()
|
|
149
|
+
dmp["status"] = "success"
|
|
150
|
+
return json.dumps(dmp, ensure_ascii=False, indent=4)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@on_tools(READ_MEMORY_FUN, custom_run=True, strict=True)
|
|
154
|
+
async def r(ctx: ToolContext) -> str:
|
|
155
|
+
assert isinstance(ctx.ctx.chat_object, AmritaChatObject)
|
|
156
|
+
nb_event = ctx.ctx.chat_object.event
|
|
157
|
+
ope = AsyncUserMemory(get_db_conn())
|
|
158
|
+
await ope.init()
|
|
159
|
+
logger.debug("开始检索记忆...")
|
|
160
|
+
logger.debug(f"会话ID: {nb_event.get_session_id()}")
|
|
161
|
+
res: QueryResult = await ope.query_notes(
|
|
162
|
+
nb_event.get_session_id(),
|
|
163
|
+
ctx.data["query"],
|
|
164
|
+
top_k=ctx.data["top_k"],
|
|
165
|
+
importance=ctx.data.get("importance"),
|
|
166
|
+
)
|
|
167
|
+
return json.dumps(res, ensure_ascii=False, indent=4)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@on_tools(UPDATE_FUN, custom_run=True, strict=True)
|
|
171
|
+
async def update_memory(ctx: ToolContext) -> str:
|
|
172
|
+
assert isinstance(ctx.ctx.chat_object, AmritaChatObject)
|
|
173
|
+
nb_event = ctx.ctx.chat_object.event
|
|
174
|
+
ope = AsyncUserMemory(get_db_conn())
|
|
175
|
+
await ope.init()
|
|
176
|
+
logger.debug("开始更新记忆...")
|
|
177
|
+
logger.debug(f"会话ID: {nb_event.get_session_id()}")
|
|
178
|
+
logger.debug(f"更新记忆ID: {ctx.data['id']}")
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
result = await ope.get_all_notes(
|
|
182
|
+
nb_event.get_session_id(), include=["metadatas", "documents"]
|
|
183
|
+
)
|
|
184
|
+
if not result or not result.get("ids"):
|
|
185
|
+
return json.dumps(
|
|
186
|
+
{
|
|
187
|
+
"status": "error",
|
|
188
|
+
"message": "未找到指定ID的记忆",
|
|
189
|
+
},
|
|
190
|
+
ensure_ascii=False,
|
|
191
|
+
indent=4,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# 查找要更新的记忆
|
|
195
|
+
target_index = -1
|
|
196
|
+
ids = result["ids"]
|
|
197
|
+
for i, doc_id in enumerate(ids):
|
|
198
|
+
if doc_id == ctx.data["id"]:
|
|
199
|
+
target_index = i
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
if target_index == -1:
|
|
203
|
+
return json.dumps(
|
|
204
|
+
{
|
|
205
|
+
"status": "error",
|
|
206
|
+
"message": "未找到指定ID的记忆",
|
|
207
|
+
},
|
|
208
|
+
ensure_ascii=False,
|
|
209
|
+
indent=4,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# 获取现有数据,处理可能为None的情况
|
|
213
|
+
documents = result.get("documents") or []
|
|
214
|
+
metadatas = result.get("metadatas") or []
|
|
215
|
+
|
|
216
|
+
existing_doc = documents[target_index] if target_index < len(documents) else ""
|
|
217
|
+
existing_meta = metadatas[target_index] if target_index < len(metadatas) else {}
|
|
218
|
+
|
|
219
|
+
# 确保有必要的元数据
|
|
220
|
+
if not existing_meta:
|
|
221
|
+
return json.dumps(
|
|
222
|
+
{
|
|
223
|
+
"status": "error",
|
|
224
|
+
"message": "记忆元数据损坏",
|
|
225
|
+
},
|
|
226
|
+
ensure_ascii=False,
|
|
227
|
+
indent=4,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# 构建更新后的数据
|
|
231
|
+
new_content = ctx.data.get("content", existing_doc)
|
|
232
|
+
new_tags = ctx.data.get("tags", existing_meta.get("tags", ""))
|
|
233
|
+
new_importance = ctx.data.get(
|
|
234
|
+
"importance", existing_meta.get("importance", "medium")
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# 处理创建时间
|
|
238
|
+
created_at_str = existing_meta.get("created_at", "")
|
|
239
|
+
if isinstance(created_at_str, str) and created_at_str:
|
|
240
|
+
# 处理ISO格式的时间字符串
|
|
241
|
+
if created_at_str.endswith("Z"):
|
|
242
|
+
created_at_str = created_at_str.replace("Z", "+00:00")
|
|
243
|
+
created_at = datetime.fromisoformat(created_at_str)
|
|
244
|
+
else:
|
|
245
|
+
created_at = datetime.now()
|
|
246
|
+
await ope.delete_note(nb_event.get_session_id(), ctx.data["id"])
|
|
247
|
+
meta = MemoryMetadata(
|
|
248
|
+
memory_id=ctx.data["id"],
|
|
249
|
+
importance=new_importance,
|
|
250
|
+
tags=new_tags,
|
|
251
|
+
created_at=created_at,
|
|
252
|
+
)
|
|
253
|
+
await ope.add_note(
|
|
254
|
+
nb_event.get_session_id(),
|
|
255
|
+
new_content,
|
|
256
|
+
metadata=meta,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return json.dumps(
|
|
260
|
+
{"status": "success", "message": "记忆更新成功", "id": ctx.data["id"]},
|
|
261
|
+
ensure_ascii=False,
|
|
262
|
+
indent=4,
|
|
263
|
+
)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"更新记忆时发生错误: {e}")
|
|
266
|
+
return json.dumps(
|
|
267
|
+
{
|
|
268
|
+
"status": "error",
|
|
269
|
+
"message": f"更新记忆失败: {e!s}",
|
|
270
|
+
},
|
|
271
|
+
ensure_ascii=False,
|
|
272
|
+
indent=4,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@on_tools(DELETE_FUN, custom_run=True, strict=True)
|
|
277
|
+
async def delete_memory(ctx: ToolContext) -> str:
|
|
278
|
+
assert isinstance(ctx.ctx.chat_object, AmritaChatObject)
|
|
279
|
+
nb_event = ctx.ctx.chat_object.event
|
|
280
|
+
ope = AsyncUserMemory(get_db_conn())
|
|
281
|
+
await ope.init()
|
|
282
|
+
logger.debug("开始删除记忆...")
|
|
283
|
+
logger.debug(f"会话ID: {nb_event.get_session_id()}")
|
|
284
|
+
logger.debug(f"删除记忆ID: {ctx.data['id']}")
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
# 检查记忆是否存在
|
|
288
|
+
result = await ope.get_all_notes(
|
|
289
|
+
nb_event.get_session_id(), include=["metadatas"]
|
|
290
|
+
)
|
|
291
|
+
if (
|
|
292
|
+
not result
|
|
293
|
+
or not result.get("ids")
|
|
294
|
+
or ctx.data["id"] not in (result.get("ids") or [])
|
|
295
|
+
):
|
|
296
|
+
return json.dumps(
|
|
297
|
+
{
|
|
298
|
+
"status": "error",
|
|
299
|
+
"message": "未找到指定ID的记忆",
|
|
300
|
+
},
|
|
301
|
+
ensure_ascii=False,
|
|
302
|
+
indent=4,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
await ope.delete_note(nb_event.get_session_id(), ctx.data["id"])
|
|
306
|
+
return json.dumps(
|
|
307
|
+
{"status": "success", "message": "记忆删除成功", "id": ctx.data["id"]},
|
|
308
|
+
ensure_ascii=False,
|
|
309
|
+
indent=4,
|
|
310
|
+
)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(f"删除记忆时发生错误: {e}")
|
|
313
|
+
return json.dumps(
|
|
314
|
+
{
|
|
315
|
+
"status": "error",
|
|
316
|
+
"message": f"删除记忆失败: {e!s}",
|
|
317
|
+
},
|
|
318
|
+
ensure_ascii=False,
|
|
319
|
+
indent=4,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@on_tools(LIST_MEMORY_FUN, custom_run=True, strict=True)
|
|
324
|
+
async def list_memory(ctx: ToolContext) -> str:
|
|
325
|
+
assert isinstance(ctx.ctx.chat_object, AmritaChatObject)
|
|
326
|
+
nb_event = ctx.ctx.chat_object.event
|
|
327
|
+
ope = AsyncUserMemory(get_db_conn())
|
|
328
|
+
await ope.init()
|
|
329
|
+
logger.debug("开始列出记忆...")
|
|
330
|
+
logger.debug(f"会话ID: {nb_event.get_session_id()}")
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
limit = ctx.data.get("limit", 5)
|
|
334
|
+
result = await ope.get_all_notes(
|
|
335
|
+
nb_event.get_session_id(), include=["metadatas", "documents"]
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
if not result or not result.get("ids"):
|
|
339
|
+
return json.dumps(
|
|
340
|
+
{"status": "success", "memories": [], "total": 0},
|
|
341
|
+
ensure_ascii=False,
|
|
342
|
+
indent=4,
|
|
343
|
+
)
|
|
344
|
+
memories = []
|
|
345
|
+
ids = result["ids"]
|
|
346
|
+
documents = result.get("documents") or [""] * len(ids)
|
|
347
|
+
metadatas = result.get("metadatas") or [{}] * len(ids)
|
|
348
|
+
|
|
349
|
+
for i, doc_id in enumerate(ids):
|
|
350
|
+
if i >= limit:
|
|
351
|
+
break
|
|
352
|
+
meta = metadatas[i] if i < len(metadatas) else {}
|
|
353
|
+
doc = documents[i] if i < len(documents) else ""
|
|
354
|
+
|
|
355
|
+
memories.append(
|
|
356
|
+
{
|
|
357
|
+
"id": doc_id,
|
|
358
|
+
"tags": meta.get("tags", ""),
|
|
359
|
+
"importance": meta.get("importance", "medium"),
|
|
360
|
+
"content_preview": doc[:50] + "..." if len(doc) > 50 else doc,
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return json.dumps(
|
|
365
|
+
{"status": "success", "memories": memories, "total": len(ids)},
|
|
366
|
+
ensure_ascii=False,
|
|
367
|
+
indent=4,
|
|
368
|
+
)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"列出记忆时发生错误: {e}")
|
|
371
|
+
return json.dumps(
|
|
372
|
+
{
|
|
373
|
+
"status": "error",
|
|
374
|
+
"message": f"列出记忆失败: {e!s}",
|
|
375
|
+
},
|
|
376
|
+
ensure_ascii=False,
|
|
377
|
+
indent=4,
|
|
378
|
+
)
|