MemoryOS 0.0.1__py3-none-any.whl → 0.1.13__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.

Potentially problematic release.


This version of MemoryOS might be problematic. Click here for more details.

Files changed (124) hide show
  1. memoryos-0.1.13.dist-info/METADATA +288 -0
  2. memoryos-0.1.13.dist-info/RECORD +122 -0
  3. memos/__init__.py +20 -1
  4. memos/api/start_api.py +420 -0
  5. memos/chunkers/__init__.py +4 -0
  6. memos/chunkers/base.py +24 -0
  7. memos/chunkers/factory.py +22 -0
  8. memos/chunkers/sentence_chunker.py +35 -0
  9. memos/configs/__init__.py +0 -0
  10. memos/configs/base.py +82 -0
  11. memos/configs/chunker.py +45 -0
  12. memos/configs/embedder.py +53 -0
  13. memos/configs/graph_db.py +45 -0
  14. memos/configs/internet_retriever.py +81 -0
  15. memos/configs/llm.py +71 -0
  16. memos/configs/mem_chat.py +81 -0
  17. memos/configs/mem_cube.py +89 -0
  18. memos/configs/mem_os.py +74 -0
  19. memos/configs/mem_reader.py +53 -0
  20. memos/configs/mem_scheduler.py +78 -0
  21. memos/configs/memory.py +195 -0
  22. memos/configs/parser.py +38 -0
  23. memos/configs/utils.py +8 -0
  24. memos/configs/vec_db.py +64 -0
  25. memos/deprecation.py +262 -0
  26. memos/embedders/__init__.py +0 -0
  27. memos/embedders/base.py +15 -0
  28. memos/embedders/factory.py +23 -0
  29. memos/embedders/ollama.py +74 -0
  30. memos/embedders/sentence_transformer.py +40 -0
  31. memos/exceptions.py +30 -0
  32. memos/graph_dbs/__init__.py +0 -0
  33. memos/graph_dbs/base.py +215 -0
  34. memos/graph_dbs/factory.py +21 -0
  35. memos/graph_dbs/neo4j.py +827 -0
  36. memos/hello_world.py +97 -0
  37. memos/llms/__init__.py +0 -0
  38. memos/llms/base.py +16 -0
  39. memos/llms/factory.py +25 -0
  40. memos/llms/hf.py +231 -0
  41. memos/llms/ollama.py +82 -0
  42. memos/llms/openai.py +34 -0
  43. memos/llms/utils.py +14 -0
  44. memos/log.py +78 -0
  45. memos/mem_chat/__init__.py +0 -0
  46. memos/mem_chat/base.py +30 -0
  47. memos/mem_chat/factory.py +21 -0
  48. memos/mem_chat/simple.py +200 -0
  49. memos/mem_cube/__init__.py +0 -0
  50. memos/mem_cube/base.py +29 -0
  51. memos/mem_cube/general.py +146 -0
  52. memos/mem_cube/utils.py +24 -0
  53. memos/mem_os/client.py +5 -0
  54. memos/mem_os/core.py +819 -0
  55. memos/mem_os/main.py +503 -0
  56. memos/mem_os/product.py +89 -0
  57. memos/mem_reader/__init__.py +0 -0
  58. memos/mem_reader/base.py +27 -0
  59. memos/mem_reader/factory.py +21 -0
  60. memos/mem_reader/memory.py +298 -0
  61. memos/mem_reader/simple_struct.py +241 -0
  62. memos/mem_scheduler/__init__.py +0 -0
  63. memos/mem_scheduler/base_scheduler.py +164 -0
  64. memos/mem_scheduler/general_scheduler.py +305 -0
  65. memos/mem_scheduler/modules/__init__.py +0 -0
  66. memos/mem_scheduler/modules/base.py +74 -0
  67. memos/mem_scheduler/modules/dispatcher.py +103 -0
  68. memos/mem_scheduler/modules/monitor.py +82 -0
  69. memos/mem_scheduler/modules/redis_service.py +146 -0
  70. memos/mem_scheduler/modules/retriever.py +41 -0
  71. memos/mem_scheduler/modules/schemas.py +146 -0
  72. memos/mem_scheduler/scheduler_factory.py +21 -0
  73. memos/mem_scheduler/utils.py +26 -0
  74. memos/mem_user/user_manager.py +488 -0
  75. memos/memories/__init__.py +0 -0
  76. memos/memories/activation/__init__.py +0 -0
  77. memos/memories/activation/base.py +42 -0
  78. memos/memories/activation/item.py +25 -0
  79. memos/memories/activation/kv.py +232 -0
  80. memos/memories/base.py +19 -0
  81. memos/memories/factory.py +34 -0
  82. memos/memories/parametric/__init__.py +0 -0
  83. memos/memories/parametric/base.py +19 -0
  84. memos/memories/parametric/item.py +11 -0
  85. memos/memories/parametric/lora.py +41 -0
  86. memos/memories/textual/__init__.py +0 -0
  87. memos/memories/textual/base.py +89 -0
  88. memos/memories/textual/general.py +286 -0
  89. memos/memories/textual/item.py +167 -0
  90. memos/memories/textual/naive.py +185 -0
  91. memos/memories/textual/tree.py +321 -0
  92. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  93. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  94. memos/memories/textual/tree_text_memory/organize/manager.py +305 -0
  95. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  96. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +263 -0
  97. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +89 -0
  98. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  99. memos/memories/textual/tree_text_memory/retrieve/recall.py +158 -0
  100. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  101. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +13 -0
  102. memos/memories/textual/tree_text_memory/retrieve/searcher.py +208 -0
  103. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +68 -0
  104. memos/memories/textual/tree_text_memory/retrieve/utils.py +48 -0
  105. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +335 -0
  106. memos/parsers/__init__.py +0 -0
  107. memos/parsers/base.py +15 -0
  108. memos/parsers/factory.py +19 -0
  109. memos/parsers/markitdown.py +22 -0
  110. memos/settings.py +8 -0
  111. memos/templates/__init__.py +0 -0
  112. memos/templates/mem_reader_prompts.py +98 -0
  113. memos/templates/mem_scheduler_prompts.py +65 -0
  114. memos/templates/mos_prompts.py +63 -0
  115. memos/types.py +55 -0
  116. memos/vec_dbs/__init__.py +0 -0
  117. memos/vec_dbs/base.py +105 -0
  118. memos/vec_dbs/factory.py +21 -0
  119. memos/vec_dbs/item.py +43 -0
  120. memos/vec_dbs/qdrant.py +292 -0
  121. memoryos-0.0.1.dist-info/METADATA +0 -53
  122. memoryos-0.0.1.dist-info/RECORD +0 -5
  123. {memoryos-0.0.1.dist-info → memoryos-0.1.13.dist-info}/LICENSE +0 -0
  124. {memoryos-0.0.1.dist-info → memoryos-0.1.13.dist-info}/WHEEL +0 -0
memos/hello_world.py ADDED
@@ -0,0 +1,97 @@
1
+ from memos import log
2
+
3
+
4
+ logger = log.get_logger(__name__)
5
+
6
+
7
+ def memos_hello_world() -> str:
8
+ logger.info("memos_hello_world function called.")
9
+ return "Hello world from memos!"
10
+
11
+
12
+ def memos_chend_hello_world() -> str:
13
+ logger.info("memos_chend_hello_world function called.")
14
+ return "Hello world from memos-chend!"
15
+
16
+
17
+ def memos_wanghy_hello_world() -> str:
18
+ logger.info("memos_wanghy_hello_world function called.")
19
+ return "Hello world from memos-wanghy!"
20
+
21
+
22
+ def memos_niusm_hello_world() -> str:
23
+ logger.info("memos_niusm_hello_world function called.")
24
+ return "Hello world from memos-niusm!"
25
+
26
+
27
+ def memos_huojh_hello_world(arr: list) -> list:
28
+ logger.info("memos_huojh_hello_world function called.")
29
+ if len(arr) <= 1:
30
+ return arr
31
+ else:
32
+ pivot = arr[0]
33
+ left = [x for x in arr[1:] if x < pivot]
34
+ right = [x for x in arr[1:] if x >= pivot]
35
+ return [*memos_huojh_hello_world(left), pivot, *memos_huojh_hello_world(right)]
36
+
37
+
38
+ def memos_dany_hello_world(para_1: int, para_2: str) -> str:
39
+ logger.info(f"logger.info: para_1 is {para_1}")
40
+ logger.debug(f"logger.debug: para_2 is {para_2}")
41
+ return f"return_value_{para_1}"
42
+
43
+
44
+ def memos_wangyzh_hello_world() -> str:
45
+ logger.info("memos_wangyzh_hello_world function called.")
46
+ return "Hello world from memos-wangyzh!"
47
+
48
+
49
+ def memos_zhaojihao_hello_world() -> str:
50
+ logger.info("memos_zhaojihao_hello_world function called.")
51
+ return "Hello world from memos-zhaojihao!"
52
+
53
+
54
+ def memos_yuqingchen_hello_world() -> str:
55
+ logger.info("memos_yuqingchen_hello_world function called.")
56
+ return "Hello world from memos-yuqingchen!"
57
+
58
+
59
+ def memos_chentang_hello_world(user_id: str = "locomo_exp_user_1", version: str = "default"):
60
+ import os
61
+
62
+ from memos.configs.memory import MemoryConfigFactory
63
+ from memos.memories.factory import MemoryFactory
64
+
65
+ config = MemoryConfigFactory(
66
+ backend="general_text",
67
+ config={
68
+ "extractor_llm": {
69
+ "backend": "openai",
70
+ "config": {
71
+ "model_name_or_path": os.getenv("MODEL"),
72
+ "temperature": 0,
73
+ "max_tokens": 8192,
74
+ "api_key": os.getenv("OPENAI_API_KEY"),
75
+ "api_base": os.getenv("OPENAI_BASE_URL"),
76
+ },
77
+ },
78
+ "vector_db": {
79
+ "backend": "qdrant",
80
+ "config": {
81
+ "path": f"outputs/locomo/memos-{version}/storages/{user_id}/qdrant",
82
+ "collection_name": "test_textual_memory",
83
+ "distance_metric": "cosine",
84
+ "vector_dimension": 768, # nomic-embed-text model's embedding dimension is 768
85
+ },
86
+ },
87
+ "embedder": {
88
+ "backend": "ollama",
89
+ "config": {
90
+ "model_name_or_path": os.getenv("EMBEDDING_MODEL"),
91
+ },
92
+ },
93
+ },
94
+ )
95
+ memory = MemoryFactory.from_config(config)
96
+
97
+ return memory
memos/llms/__init__.py ADDED
File without changes
memos/llms/base.py ADDED
@@ -0,0 +1,16 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from memos.configs.llm import BaseLLMConfig
4
+ from memos.types import MessageList
5
+
6
+
7
+ class BaseLLM(ABC):
8
+ """Base class for all LLMs."""
9
+
10
+ @abstractmethod
11
+ def __init__(self, config: BaseLLMConfig):
12
+ """Initialize the LLM with the given configuration."""
13
+
14
+ @abstractmethod
15
+ def generate(self, messages: MessageList, **kwargs) -> str:
16
+ """Generate a response from the LLM."""
memos/llms/factory.py ADDED
@@ -0,0 +1,25 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from memos.configs.llm import LLMConfigFactory
4
+ from memos.llms.base import BaseLLM
5
+ from memos.llms.hf import HFLLM
6
+ from memos.llms.ollama import OllamaLLM
7
+ from memos.llms.openai import OpenAILLM
8
+
9
+
10
+ class LLMFactory(BaseLLM):
11
+ """Factory class for creating LLM instances."""
12
+
13
+ backend_to_class: ClassVar[dict[str, Any]] = {
14
+ "openai": OpenAILLM,
15
+ "ollama": OllamaLLM,
16
+ "huggingface": HFLLM,
17
+ }
18
+
19
+ @classmethod
20
+ def from_config(cls, config_factory: LLMConfigFactory) -> BaseLLM:
21
+ backend = config_factory.backend
22
+ if backend not in cls.backend_to_class:
23
+ raise ValueError(f"Invalid backend: {backend}")
24
+ llm_class = cls.backend_to_class[backend]
25
+ return llm_class(config_factory.config)
memos/llms/hf.py ADDED
@@ -0,0 +1,231 @@
1
+ import torch
2
+
3
+ from transformers import (
4
+ AutoModelForCausalLM,
5
+ AutoTokenizer,
6
+ DynamicCache,
7
+ LogitsProcessorList,
8
+ TemperatureLogitsWarper,
9
+ TopKLogitsWarper,
10
+ TopPLogitsWarper,
11
+ )
12
+
13
+ from memos.configs.llm import HFLLMConfig
14
+ from memos.llms.base import BaseLLM
15
+ from memos.llms.utils import remove_thinking_tags
16
+ from memos.log import get_logger
17
+ from memos.types import MessageList
18
+
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class HFLLM(BaseLLM):
24
+ """
25
+ HFLLM: Transformers LLM class supporting cache-augmented generation (CAG) and sampling.
26
+ """
27
+
28
+ def __init__(self, config: HFLLMConfig):
29
+ """
30
+ Initialize the HFLLM model and tokenizer, and set up logits processors for sampling.
31
+ """
32
+ self.config = config
33
+
34
+ # Default model if not specified
35
+ if not self.config.model_name_or_path:
36
+ self.config.model_name_or_path = "Qwen/Qwen3-1.7B"
37
+
38
+ # Initialize hf model
39
+ self.model = AutoModelForCausalLM.from_pretrained(
40
+ self.config.model_name_or_path, torch_dtype="auto", device_map="auto"
41
+ )
42
+ self.tokenizer = AutoTokenizer.from_pretrained(
43
+ self.config.model_name_or_path, use_fast=True
44
+ )
45
+
46
+ # Logits processors for sampling
47
+ processors = []
48
+ if getattr(self.config, "temperature", 1.0) != 1.0:
49
+ processors.append(TemperatureLogitsWarper(self.config.temperature))
50
+ if getattr(self.config, "top_k", 0) > 0:
51
+ processors.append(TopKLogitsWarper(self.config.top_k))
52
+ if 0.0 < getattr(self.config, "top_p", 1.0) < 1.0:
53
+ processors.append(TopPLogitsWarper(self.config.top_p))
54
+ self.logits_processors = LogitsProcessorList(processors)
55
+
56
+ def generate(self, messages: MessageList, past_key_values: DynamicCache | None = None):
57
+ """
58
+ Generate a response from the model. If past_key_values is provided, use cache-augmented generation.
59
+ Args:
60
+ messages (MessageList): Chat messages for prompt construction.
61
+ past_key_values (DynamicCache | None): Optional KV cache for fast generation.
62
+ Returns:
63
+ str: Model response.
64
+ """
65
+ prompt = self.tokenizer.apply_chat_template(
66
+ messages, tokenize=False, add_generation_prompt=self.config.add_generation_prompt
67
+ )
68
+ logger.info(f"HFLLM prompt: {prompt}")
69
+ if past_key_values is None:
70
+ return self._generate_full(prompt)
71
+ else:
72
+ return self._generate_with_cache(prompt, past_key_values)
73
+
74
+ def _generate_full(self, prompt: str) -> str:
75
+ """
76
+ Generate output from scratch using the full prompt.
77
+ Args:
78
+ prompt (str): The input prompt string.
79
+ Returns:
80
+ str: Model response.
81
+ """
82
+ inputs = self.tokenizer([prompt], return_tensors="pt").to(self.model.device)
83
+ gen_kwargs = {
84
+ "max_new_tokens": getattr(self.config, "max_tokens", 128),
85
+ "do_sample": getattr(self.config, "do_sample", True),
86
+ }
87
+ if self.config.do_sample:
88
+ gen_kwargs["temperature"] = self.config.temperature
89
+ gen_kwargs["top_k"] = self.config.top_k
90
+ gen_kwargs["top_p"] = self.config.top_p
91
+ gen_ids = self.model.generate(
92
+ **inputs,
93
+ **gen_kwargs,
94
+ )
95
+ new_ids = [
96
+ out_ids[len(src_ids) :]
97
+ for src_ids, out_ids in zip(inputs.input_ids, gen_ids, strict=False)
98
+ ]
99
+ response = self.tokenizer.batch_decode(new_ids, skip_special_tokens=True)[0]
100
+ logger.info(f"Full-gen raw response: {response}")
101
+ return (
102
+ remove_thinking_tags(response)
103
+ if getattr(self.config, "remove_think_prefix", False)
104
+ else response
105
+ )
106
+
107
+ def _generate_with_cache(self, query: str, kv: DynamicCache) -> str:
108
+ """
109
+ Generate output incrementally using an existing KV cache.
110
+ Args:
111
+ query (str): The new user query string.
112
+ kv (DynamicCache): The prefilled KV cache.
113
+ Returns:
114
+ str: Model response.
115
+ """
116
+ query_ids = self.tokenizer(
117
+ query, return_tensors="pt", add_special_tokens=False
118
+ ).input_ids.to(self.model.device)
119
+ logits, kv = self._prefill(query_ids, kv)
120
+ next_token = self._select_next_token(logits)
121
+ generated = [next_token]
122
+ for _ in range(getattr(self.config, "max_tokens", 128) - 1):
123
+ if self._should_stop(next_token):
124
+ break
125
+ logits, kv = self._prefill(next_token, kv)
126
+ next_token = self._select_next_token(logits)
127
+ generated.append(next_token)
128
+ if generated:
129
+ concat = torch.cat(generated, dim=-1)
130
+ response = self.tokenizer.decode(concat[0], skip_special_tokens=True)
131
+ else:
132
+ response = ""
133
+ logger.info(f"Cache-gen raw response: {response}")
134
+ return (
135
+ remove_thinking_tags(response)
136
+ if getattr(self.config, "remove_think_prefix", False)
137
+ else response
138
+ )
139
+
140
+ @torch.no_grad()
141
+ def _prefill(
142
+ self, input_ids: torch.Tensor, kv: DynamicCache
143
+ ) -> tuple[torch.Tensor, DynamicCache]:
144
+ """
145
+ Forward the model once, returning last-step logits and updated KV cache.
146
+ Args:
147
+ input_ids (torch.Tensor): Input token IDs.
148
+ kv (DynamicCache): Existing KV cache.
149
+ Returns:
150
+ tuple[torch.Tensor, DynamicCache]: (last-step logits, updated KV cache)
151
+ """
152
+ out = self.model(
153
+ input_ids=input_ids,
154
+ use_cache=True,
155
+ past_key_values=kv,
156
+ return_dict=True,
157
+ )
158
+ return out.logits[:, -1, :], out.past_key_values
159
+
160
+ def _select_next_token(self, logits: torch.Tensor) -> torch.Tensor:
161
+ """
162
+ Select the next token from logits using sampling or argmax, depending on config.
163
+ Args:
164
+ logits (torch.Tensor): Logits for the next token.
165
+ Returns:
166
+ torch.Tensor: Selected token ID(s).
167
+ """
168
+ if getattr(self.config, "do_sample", True):
169
+ batch_size, _ = logits.size()
170
+ dummy_ids = torch.zeros((batch_size, 1), dtype=torch.long, device=logits.device)
171
+ filtered = self.logits_processors(dummy_ids, logits)
172
+ probs = torch.softmax(filtered, dim=-1)
173
+ return torch.multinomial(probs, num_samples=1)
174
+ return torch.argmax(logits, dim=-1, keepdim=True)
175
+
176
+ def _should_stop(self, token: torch.Tensor) -> bool:
177
+ """
178
+ Check if the given token is the EOS (end-of-sequence) token.
179
+ Args:
180
+ token (torch.Tensor): Token ID to check.
181
+ Returns:
182
+ bool: True if token is EOS, else False.
183
+ """
184
+ eos_id = self.tokenizer.eos_token_id
185
+ return eos_id is not None and token.item() == eos_id
186
+
187
+ def build_kv_cache(self, messages) -> DynamicCache:
188
+ """
189
+ Build a KV cache from chat messages via one forward pass.
190
+ Supports the following input types:
191
+ - str: Used as a system prompt.
192
+ - list[str]: Concatenated and used as a system prompt.
193
+ - list[dict]: Used directly as chat messages.
194
+ The messages are always converted to a standard chat template.
195
+ Raises:
196
+ ValueError: If the resulting prompt is empty after template processing.
197
+ Returns:
198
+ DynamicCache: The constructed KV cache object.
199
+ """
200
+ # Accept multiple input types and convert to standard chat messages
201
+ if isinstance(messages, str):
202
+ messages = [
203
+ {
204
+ "role": "system",
205
+ "content": f"Below is some information about the user.\n{messages}",
206
+ }
207
+ ]
208
+ elif isinstance(messages, list) and messages and isinstance(messages[0], str):
209
+ messages = [
210
+ {
211
+ "role": "system",
212
+ "content": f"Below is some information about the user.\n{' '.join(messages)}",
213
+ }
214
+ ]
215
+ prompt = self.tokenizer.apply_chat_template(
216
+ messages, tokenize=False, add_generation_prompt=False
217
+ )
218
+ inputs = self.tokenizer(prompt, return_tensors="pt")
219
+ inputs["input_ids"] = inputs["input_ids"].to(self.model.device, dtype=torch.long)
220
+ seq_len = inputs["input_ids"].size(-1)
221
+ if seq_len == 0:
222
+ raise ValueError(
223
+ "Prompt after chat template is empty, cannot build KV cache. Check your messages input."
224
+ )
225
+ kv = DynamicCache()
226
+ with torch.no_grad():
227
+ self.model(**inputs, use_cache=True, past_key_values=kv)
228
+ for i, (k, v) in enumerate(zip(kv.key_cache, kv.value_cache, strict=False)):
229
+ kv.key_cache[i] = k[:, :, :seq_len, :]
230
+ kv.value_cache[i] = v[:, :, :seq_len, :]
231
+ return kv
memos/llms/ollama.py ADDED
@@ -0,0 +1,82 @@
1
+ from typing import Any
2
+
3
+ from ollama import Client
4
+
5
+ from memos.configs.llm import OllamaLLMConfig
6
+ from memos.llms.base import BaseLLM
7
+ from memos.llms.utils import remove_thinking_tags
8
+ from memos.log import get_logger
9
+ from memos.types import MessageList
10
+
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class OllamaLLM(BaseLLM):
16
+ """Ollama LLM class."""
17
+
18
+ def __init__(self, config: OllamaLLMConfig):
19
+ self.config = config
20
+ self.api_base = config.api_base
21
+
22
+ # Default model if not specified
23
+ if not self.config.model_name_or_path:
24
+ self.config.model_name_or_path = "llama3.1:latest"
25
+
26
+ # Initialize ollama client
27
+ self.client = Client(host=self.api_base)
28
+
29
+ # Ensure the model exists locally
30
+ self._ensure_model_exists()
31
+
32
+ def _list_models(self) -> list[str]:
33
+ """
34
+ List all models available in the Ollama client.
35
+
36
+ Returns:
37
+ List of model names.
38
+ """
39
+ local_models = self.client.list()["models"]
40
+ return [model.model for model in local_models]
41
+
42
+ def _ensure_model_exists(self):
43
+ """
44
+ Ensure the specified model exists locally. If not, pull it from Ollama.
45
+ """
46
+ try:
47
+ local_models = self._list_models()
48
+ if self.config.model_name_or_path not in local_models:
49
+ logger.warning(
50
+ f"Model {self.config.model_name_or_path} not found locally. Pulling from Ollama..."
51
+ )
52
+ self.client.pull(self.config.model_name_or_path)
53
+ except Exception as e:
54
+ logger.warning(f"Could not verify model existence: {e}")
55
+
56
+ def generate(self, messages: MessageList) -> Any:
57
+ """
58
+ Generate a response from Ollama LLM.
59
+
60
+ Args:
61
+ messages: List of message dicts containing 'role' and 'content'.
62
+
63
+ Returns:
64
+ str: The generated response.
65
+ """
66
+ response = self.client.chat(
67
+ model=self.config.model_name_or_path,
68
+ messages=messages,
69
+ options={
70
+ "temperature": self.config.temperature,
71
+ "num_predict": self.config.max_tokens,
72
+ "top_p": self.config.top_p,
73
+ "top_k": self.config.top_k,
74
+ },
75
+ )
76
+ logger.info(f"Raw response from Ollama: {response.model_dump_json()}")
77
+
78
+ str_response = response["message"]["content"] or ""
79
+ if self.config.remove_think_prefix:
80
+ return remove_thinking_tags(str_response)
81
+ else:
82
+ return str_response
memos/llms/openai.py ADDED
@@ -0,0 +1,34 @@
1
+ import openai
2
+
3
+ from memos.configs.llm import OpenAILLMConfig
4
+ from memos.llms.base import BaseLLM
5
+ from memos.llms.utils import remove_thinking_tags
6
+ from memos.log import get_logger
7
+ from memos.types import MessageList
8
+
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class OpenAILLM(BaseLLM):
14
+ """OpenAI LLM class."""
15
+
16
+ def __init__(self, config: OpenAILLMConfig):
17
+ self.config = config
18
+ self.client = openai.Client(api_key=config.api_key, base_url=config.api_base)
19
+
20
+ def generate(self, messages: MessageList) -> str:
21
+ """Generate a response from OpenAI LLM."""
22
+ response = self.client.chat.completions.create(
23
+ model=self.config.model_name_or_path,
24
+ messages=messages,
25
+ temperature=self.config.temperature,
26
+ max_tokens=self.config.max_tokens,
27
+ top_p=self.config.top_p,
28
+ )
29
+ logger.info(f"Response from OpenAI: {response.model_dump_json()}")
30
+ response_content = response.choices[0].message.content
31
+ if self.config.remove_think_prefix:
32
+ return remove_thinking_tags(response_content)
33
+ else:
34
+ return response_content
memos/llms/utils.py ADDED
@@ -0,0 +1,14 @@
1
+ import re
2
+
3
+
4
+ def remove_thinking_tags(text: str) -> str:
5
+ """
6
+ Remove thinking tags from the generated text.
7
+
8
+ Args:
9
+ text: The generated text.
10
+
11
+ Returns:
12
+ str: The cleaned text.
13
+ """
14
+ return re.sub(r"^<think>.*?</think>\s*", "", text, flags=re.DOTALL).strip()
memos/log.py ADDED
@@ -0,0 +1,78 @@
1
+ import logging
2
+
3
+ from logging.config import dictConfig
4
+ from pathlib import Path
5
+ from sys import stdout
6
+
7
+ from memos import settings
8
+
9
+
10
+ selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
11
+
12
+
13
+ def _setup_logfile() -> Path:
14
+ """ensure the logger filepath is in place
15
+
16
+ Returns: the logfile Path
17
+ """
18
+ logfile = Path(settings.MEMOS_DIR / "logs" / "memos.log")
19
+ logfile.parent.mkdir(parents=True, exist_ok=True)
20
+ logfile.touch(exist_ok=True)
21
+ return logfile
22
+
23
+
24
+ LOGGING_CONFIG = {
25
+ "version": 1,
26
+ "disable_existing_loggers": False,
27
+ "formatters": {
28
+ "standard": {
29
+ "format": "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
30
+ },
31
+ "no_datetime": {
32
+ "format": "%(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
33
+ },
34
+ },
35
+ "filters": {
36
+ "package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX}
37
+ },
38
+ "handlers": {
39
+ "console": {
40
+ "level": selected_log_level,
41
+ "class": "logging.StreamHandler",
42
+ "stream": stdout,
43
+ "formatter": "no_datetime",
44
+ "filters": ["package_tree_filter"],
45
+ },
46
+ "file": {
47
+ "level": "DEBUG",
48
+ "class": "logging.handlers.RotatingFileHandler",
49
+ "filename": _setup_logfile(),
50
+ "maxBytes": 1024**2 * 10,
51
+ "backupCount": 3,
52
+ "formatter": "standard",
53
+ },
54
+ },
55
+ "root": { # Root logger handles all logs
56
+ "level": logging.DEBUG if settings.DEBUG else logging.INFO,
57
+ "handlers": ["console", "file"],
58
+ },
59
+ "loggers": {
60
+ "memos": {
61
+ "level": logging.DEBUG if settings.DEBUG else logging.INFO,
62
+ "propagate": True, # Let logs bubble up to root
63
+ },
64
+ },
65
+ }
66
+
67
+
68
+ def get_logger(name: str | None = None) -> logging.Logger:
69
+ """returns the project logger, scoped to a child name if provided
70
+ Args:
71
+ name: will define a child logger
72
+ """
73
+ dictConfig(LOGGING_CONFIG)
74
+
75
+ parent_logger = logging.getLogger("")
76
+ if name:
77
+ return parent_logger.getChild(name)
78
+ return parent_logger
File without changes
memos/mem_chat/base.py ADDED
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from memos.configs.mem_chat import BaseMemChatConfig
4
+ from memos.mem_cube.base import BaseMemCube
5
+
6
+
7
+ class BaseMemChat(ABC):
8
+ """Base class for all MemChat."""
9
+
10
+ @abstractmethod
11
+ def __init__(self, config: BaseMemChatConfig):
12
+ """Initialize the MemChat with the given configuration."""
13
+
14
+ @property
15
+ @abstractmethod
16
+ def mem_cube(self) -> BaseMemCube:
17
+ """The memory cube associated with this MemChat."""
18
+
19
+ @mem_cube.setter
20
+ @abstractmethod
21
+ def mem_cube(self, value: BaseMemCube) -> None:
22
+ """The memory cube associated with this MemChat."""
23
+
24
+ @abstractmethod
25
+ def run(self) -> None:
26
+ """Run the MemChat.
27
+
28
+ This `run` method can represent the core logic of a MemChat.
29
+ It could be an iterative chat process.
30
+ """
@@ -0,0 +1,21 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from memos.configs.mem_chat import MemChatConfigFactory
4
+ from memos.mem_chat.base import BaseMemChat
5
+ from memos.mem_chat.simple import SimpleMemChat
6
+
7
+
8
+ class MemChatFactory(BaseMemChat):
9
+ """Factory class for creating MemChat instances."""
10
+
11
+ backend_to_class: ClassVar[dict[str, Any]] = {
12
+ "simple": SimpleMemChat,
13
+ }
14
+
15
+ @classmethod
16
+ def from_config(cls, config_factory: MemChatConfigFactory) -> BaseMemChat:
17
+ backend = config_factory.backend
18
+ if backend not in cls.backend_to_class:
19
+ raise ValueError(f"Invalid backend: {backend}")
20
+ mem_chat_class = cls.backend_to_class[backend]
21
+ return mem_chat_class(config_factory.config)