MemoryOS 2.0.3__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.
- memoryos-2.0.3.dist-info/METADATA +418 -0
- memoryos-2.0.3.dist-info/RECORD +315 -0
- memoryos-2.0.3.dist-info/WHEEL +4 -0
- memoryos-2.0.3.dist-info/entry_points.txt +3 -0
- memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
- memos/__init__.py +20 -0
- memos/api/client.py +571 -0
- memos/api/config.py +1018 -0
- memos/api/context/dependencies.py +50 -0
- memos/api/exceptions.py +53 -0
- memos/api/handlers/__init__.py +62 -0
- memos/api/handlers/add_handler.py +158 -0
- memos/api/handlers/base_handler.py +194 -0
- memos/api/handlers/chat_handler.py +1401 -0
- memos/api/handlers/component_init.py +388 -0
- memos/api/handlers/config_builders.py +190 -0
- memos/api/handlers/feedback_handler.py +93 -0
- memos/api/handlers/formatters_handler.py +237 -0
- memos/api/handlers/memory_handler.py +316 -0
- memos/api/handlers/scheduler_handler.py +497 -0
- memos/api/handlers/search_handler.py +222 -0
- memos/api/handlers/suggestion_handler.py +117 -0
- memos/api/mcp_serve.py +614 -0
- memos/api/middleware/request_context.py +101 -0
- memos/api/product_api.py +38 -0
- memos/api/product_models.py +1206 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +477 -0
- memos/api/routers/server_router.py +394 -0
- memos/api/server_api.py +44 -0
- memos/api/start_api.py +433 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/charactertext_chunker.py +41 -0
- memos/chunkers/factory.py +24 -0
- memos/chunkers/markdown_chunker.py +62 -0
- memos/chunkers/sentence_chunker.py +54 -0
- memos/chunkers/simple_chunker.py +50 -0
- memos/cli.py +113 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +59 -0
- memos/configs/embedder.py +88 -0
- memos/configs/graph_db.py +236 -0
- memos/configs/internet_retriever.py +100 -0
- memos/configs/llm.py +151 -0
- memos/configs/mem_agent.py +54 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +105 -0
- memos/configs/mem_os.py +83 -0
- memos/configs/mem_reader.py +91 -0
- memos/configs/mem_scheduler.py +385 -0
- memos/configs/mem_user.py +70 -0
- memos/configs/memory.py +324 -0
- memos/configs/parser.py +38 -0
- memos/configs/reranker.py +18 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +80 -0
- memos/context/context.py +355 -0
- memos/dependency.py +52 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/ark.py +95 -0
- memos/embedders/base.py +106 -0
- memos/embedders/factory.py +29 -0
- memos/embedders/ollama.py +77 -0
- memos/embedders/sentence_transformer.py +49 -0
- memos/embedders/universal_api.py +51 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +274 -0
- memos/graph_dbs/factory.py +27 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/nebular.py +1794 -0
- memos/graph_dbs/neo4j.py +1942 -0
- memos/graph_dbs/neo4j_community.py +1058 -0
- memos/graph_dbs/polardb.py +5446 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +25 -0
- memos/llms/deepseek.py +13 -0
- memos/llms/factory.py +38 -0
- memos/llms/hf.py +443 -0
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +135 -0
- memos/llms/openai.py +222 -0
- memos/llms/openai_new.py +198 -0
- memos/llms/qwen.py +13 -0
- memos/llms/utils.py +14 -0
- memos/llms/vllm.py +218 -0
- memos/log.py +237 -0
- memos/mem_agent/base.py +19 -0
- memos/mem_agent/deepsearch_agent.py +391 -0
- memos/mem_agent/factory.py +36 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +30 -0
- memos/mem_cube/general.py +240 -0
- memos/mem_cube/navie.py +172 -0
- memos/mem_cube/utils.py +169 -0
- memos/mem_feedback/base.py +15 -0
- memos/mem_feedback/feedback.py +1192 -0
- memos/mem_feedback/simple_feedback.py +40 -0
- memos/mem_feedback/utils.py +230 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +1203 -0
- memos/mem_os/main.py +582 -0
- memos/mem_os/product.py +1608 -0
- memos/mem_os/product_server.py +455 -0
- memos/mem_os/utils/default_config.py +359 -0
- memos/mem_os/utils/format_utils.py +1403 -0
- memos/mem_os/utils/reference_utils.py +162 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +47 -0
- memos/mem_reader/factory.py +53 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/multi_modal_struct.py +965 -0
- memos/mem_reader/read_multi_modal/__init__.py +43 -0
- memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
- memos/mem_reader/read_multi_modal/base.py +273 -0
- memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
- memos/mem_reader/read_multi_modal/image_parser.py +359 -0
- memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
- memos/mem_reader/read_multi_modal/string_parser.py +139 -0
- memos/mem_reader/read_multi_modal/system_parser.py +327 -0
- memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
- memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
- memos/mem_reader/read_multi_modal/user_parser.py +218 -0
- memos/mem_reader/read_multi_modal/utils.py +358 -0
- memos/mem_reader/simple_struct.py +912 -0
- memos/mem_reader/strategy_struct.py +163 -0
- memos/mem_reader/utils.py +157 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
- memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +1319 -0
- memos/mem_scheduler/general_modules/__init__.py +0 -0
- memos/mem_scheduler/general_modules/api_misc.py +137 -0
- memos/mem_scheduler/general_modules/base.py +80 -0
- memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
- memos/mem_scheduler/general_modules/misc.py +313 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
- memos/mem_scheduler/general_modules/task_threads.py +315 -0
- memos/mem_scheduler/general_scheduler.py +1495 -0
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
- memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
- memos/mem_scheduler/monitors/general_monitor.py +394 -0
- memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
- memos/mem_scheduler/optimized_scheduler.py +410 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
- memos/mem_scheduler/orm_modules/base_model.py +729 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/orm_modules/redis_model.py +699 -0
- memos/mem_scheduler/scheduler_factory.py +23 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
- memos/mem_scheduler/schemas/api_schemas.py +233 -0
- memos/mem_scheduler/schemas/general_schemas.py +55 -0
- memos/mem_scheduler/schemas/message_schemas.py +173 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
- memos/mem_scheduler/schemas/task_schemas.py +132 -0
- memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
- memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
- memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
- memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
- memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
- memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/api_utils.py +77 -0
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +50 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/metrics.py +125 -0
- memos/mem_scheduler/utils/misc_utils.py +290 -0
- memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
- memos/mem_scheduler/utils/status_tracker.py +229 -0
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
- memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +502 -0
- memos/mem_user/persistent_factory.py +98 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/redis_persistent_user_manager.py +225 -0
- memos/mem_user/user_manager.py +488 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +56 -0
- memos/memories/activation/kv.py +292 -0
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +42 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +92 -0
- memos/memories/textual/general.py +236 -0
- memos/memories/textual/item.py +304 -0
- memos/memories/textual/naive.py +187 -0
- memos/memories/textual/prefer_text_memory/__init__.py +0 -0
- memos/memories/textual/prefer_text_memory/adder.py +504 -0
- memos/memories/textual/prefer_text_memory/config.py +106 -0
- memos/memories/textual/prefer_text_memory/extractor.py +221 -0
- memos/memories/textual/prefer_text_memory/factory.py +85 -0
- memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
- memos/memories/textual/prefer_text_memory/spliter.py +132 -0
- memos/memories/textual/prefer_text_memory/utils.py +93 -0
- memos/memories/textual/preference.py +344 -0
- memos/memories/textual/simple_preference.py +161 -0
- memos/memories/textual/simple_tree.py +69 -0
- memos/memories/textual/tree.py +459 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
- memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
- memos/memos_tools/dinding_report_bot.py +453 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +142 -0
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +310 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/multi_mem_cube/__init__.py +0 -0
- memos/multi_mem_cube/composite_cube.py +86 -0
- memos/multi_mem_cube/single_cube.py +874 -0
- memos/multi_mem_cube/views.py +54 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +21 -0
- memos/parsers/markitdown.py +28 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +25 -0
- memos/reranker/concat.py +103 -0
- memos/reranker/cosine_local.py +102 -0
- memos/reranker/factory.py +72 -0
- memos/reranker/http_bge.py +324 -0
- memos/reranker/http_bge_strategy.py +327 -0
- memos/reranker/noop.py +19 -0
- memos/reranker/strategies/__init__.py +4 -0
- memos/reranker/strategies/base.py +61 -0
- memos/reranker/strategies/concat_background.py +94 -0
- memos/reranker/strategies/concat_docsource.py +110 -0
- memos/reranker/strategies/dialogue_common.py +109 -0
- memos/reranker/strategies/factory.py +31 -0
- memos/reranker/strategies/single_turn.py +107 -0
- memos/reranker/strategies/singleturn_outmem.py +98 -0
- memos/settings.py +10 -0
- memos/templates/__init__.py +0 -0
- memos/templates/advanced_search_prompts.py +211 -0
- memos/templates/cloud_service_prompt.py +107 -0
- memos/templates/instruction_completion.py +66 -0
- memos/templates/mem_agent_prompts.py +85 -0
- memos/templates/mem_feedback_prompts.py +822 -0
- memos/templates/mem_reader_prompts.py +1096 -0
- memos/templates/mem_reader_strategy_prompts.py +238 -0
- memos/templates/mem_scheduler_prompts.py +626 -0
- memos/templates/mem_search_prompts.py +93 -0
- memos/templates/mos_prompts.py +403 -0
- memos/templates/prefer_complete_prompt.py +735 -0
- memos/templates/tool_mem_prompts.py +139 -0
- memos/templates/tree_reorganize_prompts.py +230 -0
- memos/types/__init__.py +34 -0
- memos/types/general_types.py +151 -0
- memos/types/openai_chat_completion_types/__init__.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
- memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
- memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
- memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
- memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
- memos/utils.py +123 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +117 -0
- memos/vec_dbs/factory.py +23 -0
- memos/vec_dbs/item.py +50 -0
- memos/vec_dbs/milvus.py +654 -0
- memos/vec_dbs/qdrant.py +355 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from memos.memories.textual.item import (
|
|
2
|
+
TextualMemoryItem,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def split_continuous_references(text: str) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Split continuous reference tags into individual reference tags.
|
|
9
|
+
|
|
10
|
+
Converts patterns like [1:92ff35fb, 4:bfe6f044] to [1:92ff35fb] [4:bfe6f044]
|
|
11
|
+
|
|
12
|
+
Only processes text if:
|
|
13
|
+
1. '[' appears exactly once
|
|
14
|
+
2. ']' appears exactly once
|
|
15
|
+
3. Contains commas between '[' and ']'
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
text (str): Text containing reference tags
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
str: Text with split reference tags, or original text if conditions not met
|
|
22
|
+
"""
|
|
23
|
+
# Early return if text is empty
|
|
24
|
+
if not text:
|
|
25
|
+
return text
|
|
26
|
+
# Check if '[' appears exactly once
|
|
27
|
+
if text.count("[") != 1:
|
|
28
|
+
return text
|
|
29
|
+
# Check if ']' appears exactly once
|
|
30
|
+
if text.count("]") != 1:
|
|
31
|
+
return text
|
|
32
|
+
# Find positions of brackets
|
|
33
|
+
open_bracket_pos = text.find("[")
|
|
34
|
+
close_bracket_pos = text.find("]")
|
|
35
|
+
|
|
36
|
+
# Check if brackets are in correct order
|
|
37
|
+
if open_bracket_pos >= close_bracket_pos:
|
|
38
|
+
return text
|
|
39
|
+
# Extract content between brackets
|
|
40
|
+
content_between_brackets = text[open_bracket_pos + 1 : close_bracket_pos]
|
|
41
|
+
# Check if there's a comma between brackets
|
|
42
|
+
if "," not in content_between_brackets:
|
|
43
|
+
return text
|
|
44
|
+
text = text.replace(content_between_brackets, content_between_brackets.replace(", ", "]["))
|
|
45
|
+
text = text.replace(content_between_brackets, content_between_brackets.replace(",", "]["))
|
|
46
|
+
|
|
47
|
+
return text
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def process_streaming_references_complete(text_buffer: str) -> tuple[str, str]:
|
|
51
|
+
"""
|
|
52
|
+
Complete streaming reference processing to ensure reference tags are never split.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
text_buffer (str): The accumulated text buffer.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
tuple[str, str]: (processed_text, remaining_buffer)
|
|
59
|
+
"""
|
|
60
|
+
import re
|
|
61
|
+
|
|
62
|
+
# Pattern to match complete reference tags: [refid:memoriesID]
|
|
63
|
+
complete_pattern = r"\[\d+:[^\]]+\]"
|
|
64
|
+
|
|
65
|
+
# Find all complete reference tags
|
|
66
|
+
complete_matches = list(re.finditer(complete_pattern, text_buffer))
|
|
67
|
+
|
|
68
|
+
if complete_matches:
|
|
69
|
+
# Find the last complete tag
|
|
70
|
+
last_match = complete_matches[-1]
|
|
71
|
+
end_pos = last_match.end()
|
|
72
|
+
|
|
73
|
+
# Check if there's any incomplete reference after the last complete one
|
|
74
|
+
remaining_text = text_buffer[end_pos:]
|
|
75
|
+
|
|
76
|
+
# Look for potential incomplete reference patterns after the last complete tag
|
|
77
|
+
incomplete_pattern = r"\[\d*:?[^\]]*$"
|
|
78
|
+
if re.search(incomplete_pattern, remaining_text):
|
|
79
|
+
# There's a potential incomplete reference, find where it starts
|
|
80
|
+
incomplete_match = re.search(incomplete_pattern, remaining_text)
|
|
81
|
+
if incomplete_match:
|
|
82
|
+
incomplete_start = end_pos + incomplete_match.start()
|
|
83
|
+
processed_text = text_buffer[:incomplete_start]
|
|
84
|
+
remaining_buffer = text_buffer[incomplete_start:]
|
|
85
|
+
|
|
86
|
+
# Apply reference splitting to the processed text
|
|
87
|
+
processed_text = split_continuous_references(processed_text)
|
|
88
|
+
return processed_text, remaining_buffer
|
|
89
|
+
|
|
90
|
+
# No incomplete reference after the last complete tag, process all
|
|
91
|
+
processed_text = split_continuous_references(text_buffer)
|
|
92
|
+
return processed_text, ""
|
|
93
|
+
|
|
94
|
+
# Check for incomplete reference tags - be more specific about what constitutes a potential reference
|
|
95
|
+
# Look for opening bracket with number and colon that could be a reference tag
|
|
96
|
+
opening_pattern = r"\[\d+:"
|
|
97
|
+
opening_matches = list(re.finditer(opening_pattern, text_buffer))
|
|
98
|
+
|
|
99
|
+
if opening_matches:
|
|
100
|
+
# Find the last opening tag
|
|
101
|
+
last_opening = opening_matches[-1]
|
|
102
|
+
opening_start = last_opening.start()
|
|
103
|
+
|
|
104
|
+
# Check if this might be a complete reference tag (has closing bracket after the pattern)
|
|
105
|
+
remaining_text = text_buffer[last_opening.end() :]
|
|
106
|
+
if "]" in remaining_text:
|
|
107
|
+
# This looks like a complete reference tag, process it
|
|
108
|
+
processed_text = split_continuous_references(text_buffer)
|
|
109
|
+
return processed_text, ""
|
|
110
|
+
else:
|
|
111
|
+
# Incomplete reference tag, keep it in buffer
|
|
112
|
+
processed_text = text_buffer[:opening_start]
|
|
113
|
+
processed_text = split_continuous_references(processed_text)
|
|
114
|
+
return processed_text, text_buffer[opening_start:]
|
|
115
|
+
|
|
116
|
+
# More sophisticated check for potential reference patterns
|
|
117
|
+
# Only hold back text if we see a pattern that could be the start of a reference tag
|
|
118
|
+
potential_ref_pattern = r"\[\d*:?$" # Matches [, [1, [12:, etc. at end of buffer
|
|
119
|
+
if re.search(potential_ref_pattern, text_buffer):
|
|
120
|
+
# Find the position of the potential reference start
|
|
121
|
+
match = re.search(potential_ref_pattern, text_buffer)
|
|
122
|
+
if match:
|
|
123
|
+
ref_start = match.start()
|
|
124
|
+
processed_text = text_buffer[:ref_start]
|
|
125
|
+
processed_text = split_continuous_references(processed_text)
|
|
126
|
+
return processed_text, text_buffer[ref_start:]
|
|
127
|
+
|
|
128
|
+
# Check for standalone [ only at the very end of the buffer
|
|
129
|
+
# This prevents cutting off mathematical expressions like [ \Delta U = Q - W ]
|
|
130
|
+
if text_buffer.endswith("["):
|
|
131
|
+
# Only hold back the single [ character
|
|
132
|
+
processed_text = text_buffer[:-1]
|
|
133
|
+
processed_text = split_continuous_references(processed_text)
|
|
134
|
+
return processed_text, "["
|
|
135
|
+
|
|
136
|
+
# No reference-like patterns found, process all text
|
|
137
|
+
processed_text = split_continuous_references(text_buffer)
|
|
138
|
+
return processed_text, ""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def prepare_reference_data(memories_list: list[TextualMemoryItem]) -> list[dict]:
|
|
142
|
+
# Prepare reference data
|
|
143
|
+
reference = []
|
|
144
|
+
for memories in memories_list:
|
|
145
|
+
if isinstance(memories, TextualMemoryItem):
|
|
146
|
+
memories_json = memories.model_dump()
|
|
147
|
+
memories_json["metadata"]["ref_id"] = f"{memories.id.split('-')[0]}"
|
|
148
|
+
memories_json["metadata"]["embedding"] = []
|
|
149
|
+
memories_json["metadata"]["sources"] = []
|
|
150
|
+
memories_json["metadata"]["memory"] = memories.memory
|
|
151
|
+
memories_json["metadata"]["id"] = memories.id
|
|
152
|
+
reference.append({"metadata": memories_json["metadata"]})
|
|
153
|
+
else:
|
|
154
|
+
memories_json = memories
|
|
155
|
+
memories_json["metadata"]["ref_id"] = f"{memories_json['id'].split('-')[0]}"
|
|
156
|
+
memories_json["metadata"]["embedding"] = []
|
|
157
|
+
memories_json["metadata"]["sources"] = []
|
|
158
|
+
memories_json["metadata"]["memory"] = memories_json["memory"]
|
|
159
|
+
memories_json["metadata"]["id"] = memories_json["id"]
|
|
160
|
+
reference.append({"metadata": memories_json["metadata"]})
|
|
161
|
+
|
|
162
|
+
return reference
|
|
File without changes
|
memos/mem_reader/base.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
from memos.configs.mem_reader import BaseMemReaderConfig
|
|
5
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from memos.graph_dbs.base import BaseGraphDB
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseMemReader(ABC):
|
|
13
|
+
"""MemReader interface class for reading information."""
|
|
14
|
+
|
|
15
|
+
# Optional graph database for recall operations (for deduplication, conflict
|
|
16
|
+
# detection .etc)
|
|
17
|
+
graph_db: "BaseGraphDB | None" = None
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def __init__(self, config: BaseMemReaderConfig):
|
|
21
|
+
"""Initialize the MemReader with the given configuration."""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def set_graph_db(self, graph_db: "BaseGraphDB | None") -> None:
|
|
25
|
+
"""
|
|
26
|
+
Set the graph database instance for recall operations.
|
|
27
|
+
|
|
28
|
+
This enables the mem-reader to perform:
|
|
29
|
+
- Semantic deduplication: avoid storing duplicate memories
|
|
30
|
+
- Conflict detection: detect contradictions with existing memories
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
graph_db: The graph database instance, or None to disable recall operations.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def get_memory(
|
|
38
|
+
self, scene_data: list, type: str, info: dict[str, Any], mode: str = "fast"
|
|
39
|
+
) -> list[list[TextualMemoryItem]]:
|
|
40
|
+
"""Various types of memories extracted from scene_data"""
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def fine_transfer_simple_mem(
|
|
44
|
+
self, input_memories: list[list[TextualMemoryItem]], type: str
|
|
45
|
+
) -> list[list[TextualMemoryItem]]:
|
|
46
|
+
"""Fine Transform TextualMemoryItem List into another list of
|
|
47
|
+
TextualMemoryItem objects via calling llm to better understand users."""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Optional
|
|
2
|
+
|
|
3
|
+
from memos.configs.mem_reader import MemReaderConfigFactory
|
|
4
|
+
from memos.mem_reader.base import BaseMemReader
|
|
5
|
+
from memos.mem_reader.multi_modal_struct import MultiModalStructMemReader
|
|
6
|
+
from memos.mem_reader.simple_struct import SimpleStructMemReader
|
|
7
|
+
from memos.mem_reader.strategy_struct import StrategyStructMemReader
|
|
8
|
+
from memos.memos_tools.singleton import singleton_factory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from memos.graph_dbs.base import BaseGraphDB
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MemReaderFactory(BaseMemReader):
|
|
16
|
+
"""Factory class for creating MemReader instances."""
|
|
17
|
+
|
|
18
|
+
backend_to_class: ClassVar[dict[str, Any]] = {
|
|
19
|
+
"simple_struct": SimpleStructMemReader,
|
|
20
|
+
"strategy_struct": StrategyStructMemReader,
|
|
21
|
+
"multimodal_struct": MultiModalStructMemReader,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
@singleton_factory()
|
|
26
|
+
def from_config(
|
|
27
|
+
cls,
|
|
28
|
+
config_factory: MemReaderConfigFactory,
|
|
29
|
+
graph_db: Optional["BaseGraphDB | None"] = None,
|
|
30
|
+
) -> BaseMemReader:
|
|
31
|
+
"""
|
|
32
|
+
Create a MemReader instance from configuration.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config_factory: Configuration factory for the MemReader.
|
|
36
|
+
graph_db: Optional graph database instance for recall operations
|
|
37
|
+
(deduplication, conflict detection). Can also be set later
|
|
38
|
+
via reader.set_graph_db().
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Configured MemReader instance.
|
|
42
|
+
"""
|
|
43
|
+
backend = config_factory.backend
|
|
44
|
+
if backend not in cls.backend_to_class:
|
|
45
|
+
raise ValueError(f"Invalid backend: {backend}")
|
|
46
|
+
reader_class = cls.backend_to_class[backend]
|
|
47
|
+
reader = reader_class(config_factory.config)
|
|
48
|
+
|
|
49
|
+
# Set graph_db if provided (for recall operations)
|
|
50
|
+
if graph_db is not None:
|
|
51
|
+
reader.set_graph_db(graph_db)
|
|
52
|
+
|
|
53
|
+
return reader
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from memos.llms.base import BaseLLM
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Memory:
|
|
8
|
+
"""Class representing the memory structure for storing and organizing memory content."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
user_id: str,
|
|
13
|
+
session_id: str,
|
|
14
|
+
created_at: datetime,
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the Memory structure.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
user_id: User identifier
|
|
21
|
+
session_id: Session identifier
|
|
22
|
+
created_at: Creation timestamp
|
|
23
|
+
"""
|
|
24
|
+
self.objective_memory: dict[str, dict[str, Any]] = {}
|
|
25
|
+
self.subjective_memory: dict[str, dict[str, Any]] = {}
|
|
26
|
+
self.scene_memory = {
|
|
27
|
+
"qa_pair": {
|
|
28
|
+
"section": [],
|
|
29
|
+
"info": {
|
|
30
|
+
"user_id": user_id,
|
|
31
|
+
"session_id": session_id,
|
|
32
|
+
"created_at": created_at,
|
|
33
|
+
"summary": "",
|
|
34
|
+
"label": [],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
"document": {
|
|
38
|
+
"section": [],
|
|
39
|
+
"info": {
|
|
40
|
+
"user_id": user_id,
|
|
41
|
+
"session_id": session_id,
|
|
42
|
+
"created_at": created_at,
|
|
43
|
+
"doc_type": "", # pdf, txt, etc.
|
|
44
|
+
"doc_category": "", # research_paper, news, etc.
|
|
45
|
+
"doc_name": "",
|
|
46
|
+
"summary": "",
|
|
47
|
+
"label": [],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Convert the Memory object to a dictionary.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary representation of the Memory object
|
|
58
|
+
"""
|
|
59
|
+
return {
|
|
60
|
+
"objective_memory": self.objective_memory,
|
|
61
|
+
"subjective_memory": self.subjective_memory,
|
|
62
|
+
"scene_memory": self.scene_memory,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def update_user_memory(
|
|
66
|
+
self,
|
|
67
|
+
memory_type: str,
|
|
68
|
+
key: str,
|
|
69
|
+
value: Any,
|
|
70
|
+
origin_data: str,
|
|
71
|
+
confidence_score: float = 1.0,
|
|
72
|
+
timestamp: str | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Update a memory item in either objective_memory or subjective_memory.
|
|
76
|
+
If a key already exists, the new memory item's info will replace the existing one,
|
|
77
|
+
and the values will be connected.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
memory_type: Type of memory to update ('objective' or 'subjective')
|
|
81
|
+
key: Key for the memory item. Must be one of:
|
|
82
|
+
|
|
83
|
+
| Memory Type | Key | Description |
|
|
84
|
+
|-------------------|----------------------|---------------------------------------------------------|
|
|
85
|
+
| objective_memory | nickname | User's preferred name or alias |
|
|
86
|
+
| objective_memory | gender | User's gender (male, female, other) |
|
|
87
|
+
| objective_memory | personality | User's personality traits or MBTI type |
|
|
88
|
+
| objective_memory | birth | User's birthdate or age information |
|
|
89
|
+
| objective_memory | education | User's educational background |
|
|
90
|
+
| objective_memory | work | User's professional history |
|
|
91
|
+
| objective_memory | achievement | User's notable accomplishments |
|
|
92
|
+
| objective_memory | occupation | User's current job or role |
|
|
93
|
+
| objective_memory | residence | User's home location or living situation |
|
|
94
|
+
| objective_memory | location | User's current geographical location |
|
|
95
|
+
| objective_memory | income | User's financial information |
|
|
96
|
+
| objective_memory | preference | User's likes and dislikes |
|
|
97
|
+
| objective_memory | expertise | User's skills and knowledge areas |
|
|
98
|
+
| objective_memory | language | User's language proficiency |
|
|
99
|
+
| objective_memory | hobby | User's recreational activities |
|
|
100
|
+
| objective_memory | goal | User's long-term aspirations |
|
|
101
|
+
|-------------------|----------------------|---------------------------------------------------------|
|
|
102
|
+
| subjective_memory | current_mood | User's current emotional state |
|
|
103
|
+
| subjective_memory | response_style | User's preferred interaction style |
|
|
104
|
+
| subjective_memory | language_style | User's language patterns and preferences |
|
|
105
|
+
| subjective_memory | information_density | User's preference for detail level in responses |
|
|
106
|
+
| subjective_memory | interaction_pace | User's preferred conversation speed and frequency |
|
|
107
|
+
| subjective_memory | followed_topic | Topics the user is currently interested in |
|
|
108
|
+
| subjective_memory | current_goal | User's immediate objectives in the conversation |
|
|
109
|
+
| subjective_memory | content_type | User's preferred field of interest (e.g., technology, finance, etc.) |
|
|
110
|
+
| subjective_memory | role_preference | User's preferred assistant role (e.g., domain expert, translation assistant, etc.) |
|
|
111
|
+
|
|
112
|
+
value: Value to store
|
|
113
|
+
origin_data: Original data that led to this memory
|
|
114
|
+
confidence_score: Confidence score (0.0 to 1.0)
|
|
115
|
+
timestamp: Timestamp string, if None current time will be used
|
|
116
|
+
"""
|
|
117
|
+
if timestamp is None:
|
|
118
|
+
timestamp = datetime.now()
|
|
119
|
+
|
|
120
|
+
memory_item = {
|
|
121
|
+
"value": value,
|
|
122
|
+
"info": {
|
|
123
|
+
"timestamp": timestamp,
|
|
124
|
+
"confidence_score": confidence_score,
|
|
125
|
+
"origin_data": origin_data,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if memory_type == "objective":
|
|
130
|
+
memory_dict = self.objective_memory
|
|
131
|
+
elif memory_type == "subjective":
|
|
132
|
+
memory_dict = self.subjective_memory
|
|
133
|
+
else:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"Invalid memory_type: {memory_type}. Must be 'objective' or 'subjective'."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Check if key already exists
|
|
139
|
+
if key in memory_dict:
|
|
140
|
+
existing_item = memory_dict[key]
|
|
141
|
+
|
|
142
|
+
# Connect the values (keep history but present as a connected string)
|
|
143
|
+
combined_value = f"{existing_item['value']} | {value}"
|
|
144
|
+
|
|
145
|
+
# Update the memory item with combined value and new info (using the newest info)
|
|
146
|
+
memory_dict[key] = {
|
|
147
|
+
"value": combined_value,
|
|
148
|
+
"info": memory_item["info"], # Use the new info
|
|
149
|
+
}
|
|
150
|
+
else:
|
|
151
|
+
# If key doesn't exist, simply add the new memory item
|
|
152
|
+
memory_dict[key] = memory_item
|
|
153
|
+
|
|
154
|
+
def add_qa_batch(
|
|
155
|
+
self, batch_summary: str, pair_summaries: list[dict], themes: list[str], order: int
|
|
156
|
+
) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Add a batch of Q&A pairs to the scene memory as a single subsection.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
batch_summary: The summary of the entire batch
|
|
162
|
+
pair_summaries: List of dictionaries, each containing:
|
|
163
|
+
- question: The summarized question for a single pair
|
|
164
|
+
- summary: The original dialogue for a single pair
|
|
165
|
+
- prompt: The prompt used for summarization
|
|
166
|
+
- time: The extracted time information (if any)
|
|
167
|
+
themes: List of themes associated with the batch
|
|
168
|
+
order: Order of the batch in the sequence
|
|
169
|
+
"""
|
|
170
|
+
qa_subsection = {
|
|
171
|
+
"subsection": {},
|
|
172
|
+
"info": {
|
|
173
|
+
"summary": batch_summary,
|
|
174
|
+
"label": themes,
|
|
175
|
+
"origin_data": "",
|
|
176
|
+
"order": order,
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for pair in pair_summaries:
|
|
181
|
+
qa_subsection["subsection"][pair["question"]] = {
|
|
182
|
+
"summary": pair["summary"],
|
|
183
|
+
"sources": pair["prompt"].split("\n\n", 1)[-1],
|
|
184
|
+
"time": pair.get("time", ""), # Add time field with default empty string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
self.scene_memory["qa_pair"]["section"].append(qa_subsection)
|
|
188
|
+
|
|
189
|
+
def add_document_chunk_group(
|
|
190
|
+
self, summary: str, label: list[str], order: int, sub_chunks: list
|
|
191
|
+
) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Add a group of document chunks as a single section with multiple facts in the subsection.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
summary: The summary of the large chunk
|
|
197
|
+
label: List of theme labels for the large chunk
|
|
198
|
+
order: Order of the large chunk in the sequence
|
|
199
|
+
sub_chunks: List of dictionaries containing small chunks information,
|
|
200
|
+
each with keys: 'question', 'chunk_text', 'prompt'
|
|
201
|
+
"""
|
|
202
|
+
doc_section = {
|
|
203
|
+
"subsection": {},
|
|
204
|
+
"info": {
|
|
205
|
+
"summary": summary,
|
|
206
|
+
"label": label,
|
|
207
|
+
"origin_data": "",
|
|
208
|
+
"order": order,
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Add each small chunk as a fact in the subsection
|
|
213
|
+
for sub_chunk in sub_chunks:
|
|
214
|
+
question = sub_chunk["question"]
|
|
215
|
+
doc_section["subsection"][question] = {
|
|
216
|
+
"summary": sub_chunk["chunk_text"],
|
|
217
|
+
"sources": sub_chunk["prompt"].split("\n\n", 1)[-1],
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
self.scene_memory["document"]["section"].append(doc_section)
|
|
221
|
+
|
|
222
|
+
def process_qa_pair_summaries(self, llm: BaseLLM | None = None) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Process all qa_pair subsection summaries to generate a section summary.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
llm: Optional LLM instance to generate summary. If None, concatenates subsection summaries.
|
|
228
|
+
Returns:
|
|
229
|
+
The generated section summary
|
|
230
|
+
"""
|
|
231
|
+
all_summaries = []
|
|
232
|
+
all_labels = set()
|
|
233
|
+
|
|
234
|
+
# Collect all subsection summaries and labels
|
|
235
|
+
for section in self.scene_memory["qa_pair"]["section"]:
|
|
236
|
+
if "info" in section and "summary" in section["info"]:
|
|
237
|
+
all_summaries.append(section["info"]["summary"])
|
|
238
|
+
if "info" in section and "label" in section["info"]:
|
|
239
|
+
all_labels.update(section["info"]["label"])
|
|
240
|
+
|
|
241
|
+
# Generate summary
|
|
242
|
+
if llm is not None:
|
|
243
|
+
# Use LLM to generate a coherent summary
|
|
244
|
+
all_summaries_str = "\n".join(all_summaries)
|
|
245
|
+
messages = [
|
|
246
|
+
{
|
|
247
|
+
"role": "user",
|
|
248
|
+
"content": f"Summarize this text into a concise and objective sentence that captures its main idea. Provide only the required content directly, without including any additional information.\n\n{all_summaries_str}",
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
section_summary = llm.generate(messages)
|
|
252
|
+
else:
|
|
253
|
+
# Simple concatenation of summaries
|
|
254
|
+
section_summary = " ".join(all_summaries)
|
|
255
|
+
|
|
256
|
+
# Update the section info
|
|
257
|
+
self.scene_memory["qa_pair"]["info"]["summary"] = section_summary
|
|
258
|
+
self.scene_memory["qa_pair"]["info"]["label"] = list(all_labels)
|
|
259
|
+
|
|
260
|
+
def process_document_summaries(self, llm=None) -> str:
|
|
261
|
+
"""
|
|
262
|
+
Process all document subsection summaries to generate a section summary.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
llm: Optional LLM instance to generate summary. If None, concatenates subsection summaries.
|
|
266
|
+
Returns:
|
|
267
|
+
The generated section summary
|
|
268
|
+
"""
|
|
269
|
+
all_summaries = []
|
|
270
|
+
all_labels = set()
|
|
271
|
+
|
|
272
|
+
# Collect all subsection summaries and labels
|
|
273
|
+
for section in self.scene_memory["document"]["section"]:
|
|
274
|
+
if "info" in section and "summary" in section["info"]:
|
|
275
|
+
all_summaries.append(section["info"]["summary"])
|
|
276
|
+
if "info" in section and "label" in section["info"]:
|
|
277
|
+
all_labels.update(section["info"]["label"])
|
|
278
|
+
|
|
279
|
+
# Generate summary
|
|
280
|
+
if llm is not None:
|
|
281
|
+
# Use LLM to generate a coherent summary
|
|
282
|
+
all_summaries_str = "\n".join(all_summaries)
|
|
283
|
+
messages = [
|
|
284
|
+
{
|
|
285
|
+
"role": "user",
|
|
286
|
+
"content": f"Summarize this text into a concise and objective sentence that captures its main idea. Provide only the required content directly, without including any additional information.\n\n{all_summaries_str}",
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
section_summary = llm.generate(messages)
|
|
290
|
+
else:
|
|
291
|
+
# Simple concatenation of summaries
|
|
292
|
+
section_summary = " ".join(all_summaries)
|
|
293
|
+
|
|
294
|
+
# Update the section info
|
|
295
|
+
self.scene_memory["document"]["info"]["summary"] = section_summary
|
|
296
|
+
self.scene_memory["document"]["info"]["label"] = list(all_labels)
|
|
297
|
+
|
|
298
|
+
return section_summary
|