Skip to content

Commit 663c157

Browse files
authored
Feat: add time log for threaddict and change openai packacge singleton (#314)
* feat: add logs for cube * feat: add loggers for mem * add dict timer log * fix: change ci code
1 parent 05dac26 commit 663c157

File tree

6 files changed

+130
-9
lines changed

6 files changed

+130
-9
lines changed

src/memos/llms/openai.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import hashlib
2+
import json
3+
14
from collections.abc import Generator
5+
from typing import ClassVar
26

37
import openai
48

@@ -13,11 +17,44 @@
1317

1418

1519
class OpenAILLM(BaseLLM):
16-
"""OpenAI LLM class."""
20+
"""OpenAI LLM class with singleton pattern."""
21+
22+
_instances: ClassVar[dict] = {} # Class variable to store instances
23+
24+
def __new__(cls, config: OpenAILLMConfig) -> "OpenAILLM":
25+
config_hash = cls._get_config_hash(config)
26+
27+
if config_hash not in cls._instances:
28+
logger.info(f"Creating new OpenAI LLM instance for config hash: {config_hash}")
29+
instance = super().__new__(cls)
30+
cls._instances[config_hash] = instance
31+
else:
32+
logger.info(f"Reusing existing OpenAI LLM instance for config hash: {config_hash}")
33+
34+
return cls._instances[config_hash]
1735

1836
def __init__(self, config: OpenAILLMConfig):
37+
# Avoid duplicate initialization
38+
if hasattr(self, "_initialized"):
39+
return
40+
1941
self.config = config
2042
self.client = openai.Client(api_key=config.api_key, base_url=config.api_base)
43+
self._initialized = True
44+
logger.info("OpenAI LLM instance initialized")
45+
46+
@classmethod
47+
def _get_config_hash(cls, config: OpenAILLMConfig) -> str:
48+
"""Generate hash value of configuration"""
49+
config_dict = config.model_dump()
50+
config_str = json.dumps(config_dict, sort_keys=True)
51+
return hashlib.md5(config_str.encode()).hexdigest()
52+
53+
@classmethod
54+
def clear_cache(cls):
55+
"""Clear all cached instances"""
56+
cls._instances.clear()
57+
logger.info("OpenAI LLM instance cache cleared")
2158

2259
def generate(self, messages: MessageList) -> str:
2360
"""Generate a response from OpenAI LLM."""
@@ -71,15 +108,50 @@ def generate_stream(self, messages: MessageList, **kwargs) -> Generator[str, Non
71108

72109

73110
class AzureLLM(BaseLLM):
74-
"""Azure OpenAI LLM class."""
111+
"""Azure OpenAI LLM class with singleton pattern."""
112+
113+
_instances: ClassVar[dict] = {} # Class variable to store instances
114+
115+
def __new__(cls, config: AzureLLMConfig):
116+
# Generate hash value of config as cache key
117+
config_hash = cls._get_config_hash(config)
118+
119+
if config_hash not in cls._instances:
120+
logger.info(f"Creating new Azure LLM instance for config hash: {config_hash}")
121+
instance = super().__new__(cls)
122+
cls._instances[config_hash] = instance
123+
else:
124+
logger.info(f"Reusing existing Azure LLM instance for config hash: {config_hash}")
125+
126+
return cls._instances[config_hash]
75127

76128
def __init__(self, config: AzureLLMConfig):
129+
# Avoid duplicate initialization
130+
if hasattr(self, "_initialized"):
131+
return
132+
77133
self.config = config
78134
self.client = openai.AzureOpenAI(
79135
azure_endpoint=config.base_url,
80136
api_version=config.api_version,
81137
api_key=config.api_key,
82138
)
139+
self._initialized = True
140+
logger.info("Azure LLM instance initialized")
141+
142+
@classmethod
143+
def _get_config_hash(cls, config: AzureLLMConfig) -> str:
144+
"""Generate hash value of configuration"""
145+
# Convert config to dict and sort to ensure consistency
146+
config_dict = config.model_dump()
147+
config_str = json.dumps(config_dict, sort_keys=True)
148+
return hashlib.md5(config_str.encode()).hexdigest()
149+
150+
@classmethod
151+
def clear_cache(cls):
152+
"""Clear all cached instances"""
153+
cls._instances.clear()
154+
logger.info("Azure LLM instance cache cleared")
83155

84156
def generate(self, messages: MessageList) -> str:
85157
"""Generate a response from Azure OpenAI LLM."""

src/memos/mem_cube/general.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import time
23

34
from typing import Literal
45

@@ -23,11 +24,13 @@ class GeneralMemCube(BaseMemCube):
2324
def __init__(self, config: GeneralMemCubeConfig):
2425
"""Initialize the MemCube with a configuration."""
2526
self.config = config
27+
time_start = time.time()
2628
self._text_mem: BaseTextMemory | None = (
2729
MemoryFactory.from_config(config.text_mem)
2830
if config.text_mem.backend != "uninitialized"
2931
else None
3032
)
33+
logger.info(f"init_text_mem in {time.time() - time_start} seconds")
3134
self._act_mem: BaseActMemory | None = (
3235
MemoryFactory.from_config(config.act_mem)
3336
if config.act_mem.backend != "uninitialized"
@@ -137,7 +140,6 @@ def init_from_dir(
137140
if default_config is not None:
138141
config = merge_config_with_default(config, default_config)
139142
logger.info(f"Applied default config to cube {config.cube_id}")
140-
141143
mem_cube = GeneralMemCube(config)
142144
mem_cube.load(dir, memory_types)
143145
return mem_cube

src/memos/mem_os/core.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -483,14 +483,14 @@ def register_mem_cube(
483483
self.mem_cubes[mem_cube_id] = mem_cube_name_or_path
484484
logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
485485
elif os.path.exists(mem_cube_name_or_path):
486-
self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
486+
mem_cube_obj = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
487+
self.mem_cubes[mem_cube_id] = mem_cube_obj
487488
else:
488489
logger.warning(
489490
f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo."
490491
)
491-
self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_remote_repo(
492-
mem_cube_name_or_path
493-
)
492+
mem_cube_obj = GeneralMemCube.init_from_remote_repo(mem_cube_name_or_path)
493+
self.mem_cubes[mem_cube_id] = mem_cube_obj
494494
# Check if cube already exists in database
495495
existing_cube = self.user_manager.get_cube(mem_cube_id)
496496

@@ -592,9 +592,13 @@ def search(
592592
install_cube_ids = user_cube_ids
593593
# create exist dict in mem_cubes and avoid one search slow
594594
tmp_mem_cubes = {}
595+
time_start_cube_get = time.time()
595596
for mem_cube_id in install_cube_ids:
596597
if mem_cube_id in self.mem_cubes:
597598
tmp_mem_cubes[mem_cube_id] = self.mem_cubes.get(mem_cube_id)
599+
logger.info(
600+
f"time search: transform cube time user_id: {target_user_id} time is: {time.time() - time_start_cube_get}"
601+
)
598602

599603
for mem_cube_id, mem_cube in tmp_mem_cubes.items():
600604
if (

src/memos/mem_os/product.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,10 +775,14 @@ def register_mem_cube(
775775
return
776776

777777
# Create MemCube from path
778+
time_start = time.time()
778779
if os.path.exists(mem_cube_name_or_path):
779780
mem_cube = GeneralMemCube.init_from_dir(
780781
mem_cube_name_or_path, memory_types, default_config
781782
)
783+
logger.info(
784+
f"time register_mem_cube: init_from_dir time is: {time.time() - time_start}"
785+
)
782786
else:
783787
logger.warning(
784788
f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo."

src/memos/memories/textual/tree.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import shutil
44
import tempfile
5+
import time
56

67
from datetime import datetime
78
from pathlib import Path
@@ -32,15 +33,28 @@ class TreeTextMemory(BaseTextMemory):
3233

3334
def __init__(self, config: TreeTextMemoryConfig):
3435
"""Initialize memory with the given configuration."""
36+
time_start = time.time()
3537
self.config: TreeTextMemoryConfig = config
3638
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
3739
config.extractor_llm
3840
)
41+
logger.info(f"time init: extractor_llm time is: {time.time() - time_start}")
42+
43+
time_start_ex = time.time()
3944
self.dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
4045
config.dispatcher_llm
4146
)
47+
logger.info(f"time init: dispatcher_llm time is: {time.time() - time_start_ex}")
48+
49+
time_start_em = time.time()
4250
self.embedder: OllamaEmbedder = EmbedderFactory.from_config(config.embedder)
51+
logger.info(f"time init: embedder time is: {time.time() - time_start_em}")
52+
53+
time_start_gs = time.time()
4354
self.graph_store: Neo4jGraphDB = GraphStoreFactory.from_config(config.graph_db)
55+
logger.info(f"time init: graph_store time is: {time.time() - time_start_gs}")
56+
57+
time_start_rr = time.time()
4458
if config.reranker is None:
4559
default_cfg = RerankerConfigFactory.model_validate(
4660
{
@@ -54,9 +68,10 @@ def __init__(self, config: TreeTextMemoryConfig):
5468
self.reranker = RerankerFactory.from_config(default_cfg)
5569
else:
5670
self.reranker = RerankerFactory.from_config(config.reranker)
57-
71+
logger.info(f"time init: reranker time is: {time.time() - time_start_rr}")
5872
self.is_reorganize = config.reorganize
5973

74+
time_start_mm = time.time()
6075
self.memory_manager: MemoryManager = MemoryManager(
6176
self.graph_store,
6277
self.embedder,
@@ -69,7 +84,8 @@ def __init__(self, config: TreeTextMemoryConfig):
6984
},
7085
is_reorganize=self.is_reorganize,
7186
)
72-
87+
logger.info(f"time init: memory_manager time is: {time.time() - time_start_mm}")
88+
time_start_ir = time.time()
7389
# Create internet retriever if configured
7490
self.internet_retriever = None
7591
if config.internet_retriever is not None:
@@ -81,6 +97,7 @@ def __init__(self, config: TreeTextMemoryConfig):
8197
)
8298
else:
8399
logger.info("No internet retriever configured")
100+
logger.info(f"time init: internet_retriever time is: {time.time() - time_start_ir}")
84101

85102
def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> list[str]:
86103
"""Add memories.

0 commit comments

Comments
 (0)