Skip to content
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ report/
cov-report/
.tox/
.nox/
report/
cov-report/
.coverage
.coverage.*
.cache
Expand Down
28 changes: 26 additions & 2 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,26 @@ def get_memreader_config() -> dict[str, Any]:

return {"backend": "openai", "config": config}

@staticmethod
def get_qwen_llm_config() -> dict[str, Any] | None:
if not os.getenv("QWEN_API_KEY"):
return None
return {
"backend": "qwen",
"config": {
"model_name_or_path": os.getenv("QWEN_MODEL", "qwen-flash"),
"temperature": float(os.getenv("QWEN_TEMPERATURE", "0.8")),
"max_tokens": int(os.getenv("QWEN_MAX_TOKENS", "8000")),
"top_p": float(os.getenv("QWEN_TOP_P", "0.9")),
"top_k": int(os.getenv("QWEN_TOP_K", "50")),
"remove_think_prefix": os.getenv("QWEN_REMOVE_THINK_PREFIX", "true").lower()
== "true",
"api_key": os.getenv("QWEN_API_KEY", ""),
"api_base": os.getenv("QWEN_API_BASE", ""),
"model_schema": os.getenv("QWEN_MODEL_SCHEMA", "memos.configs.llm.QwenLLMConfig"),
},
}

@staticmethod
def get_memreader_general_llm_config() -> dict[str, Any]:
"""Get general LLM configuration for non-chat/doc tasks.
Expand Down Expand Up @@ -639,6 +659,7 @@ def get_oss_config() -> dict[str, Any] | None:

return config

@staticmethod
def get_internet_config() -> dict[str, Any]:
"""Get internet retriever configuration.

Expand Down Expand Up @@ -705,8 +726,9 @@ def get_internet_config() -> dict[str, Any]:

@staticmethod
def get_nli_config() -> dict[str, Any]:
"""Get NLI model configuration."""
"""Get relation-judge configuration for memory-version candidate matching."""
return {
"provider": os.getenv("MEM_VERSION_RELATION_JUDGE_PROVIDER", "llm"),
"base_url": os.getenv("NLI_MODEL_BASE_URL", "http://localhost:32532"),
}

Expand Down Expand Up @@ -872,7 +894,7 @@ def get_scheduler_config() -> dict[str, Any]:
),
"context_window_size": int(os.getenv("MOS_SCHEDULER_CONTEXT_WINDOW_SIZE", "5")),
"thread_pool_max_workers": int(
os.getenv("MOS_SCHEDULER_THREAD_POOL_MAX_WORKERS", "200")
os.getenv("MOS_SCHEDULER_THREAD_POOL_MAX_WORKERS", "50")
),
"consume_interval_seconds": float(
os.getenv("MOS_SCHEDULER_CONSUME_INTERVAL_SECONDS", "0.01")
Expand Down Expand Up @@ -952,6 +974,7 @@ def get_product_default_config() -> dict[str, Any]:
"backend": reader_config["backend"],
"config": {
"llm": APIConfig.get_memreader_config(),
"qwen_llm": APIConfig.get_qwen_llm_config(),
# General LLM for non-chat/doc tasks (hallucination filter, rewrite, merge, etc.)
"general_llm": APIConfig.get_memreader_general_llm_config(),
# Image parser LLM (requires vision model)
Expand Down Expand Up @@ -986,6 +1009,7 @@ def get_product_default_config() -> dict[str, Any]:
"SKILLS_LOCAL_DIR", "/tmp/upload_skill_memory/"
),
},
"memory_version_switch": os.getenv("MEM_READER_MEM_VERSION_SWITCH", "off"),
},
},
"enable_textual_memory": True,
Expand Down
2 changes: 2 additions & 0 deletions src/memos/api/handlers/add_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from memos.multi_mem_cube.composite_cube import CompositeCubeView
from memos.multi_mem_cube.single_cube import SingleCubeView
from memos.multi_mem_cube.views import MemCubeView
from memos.plugins.hooks import hookable
from memos.types import MessageList


Expand All @@ -37,6 +38,7 @@ def __init__(self, dependencies: HandlerDependencies):
"naive_mem_cube", "mem_reader", "mem_scheduler", "feedback_server"
)

@hookable("add")
def handle_add_memories(self, add_req: APIADDRequest) -> MemoryResponse:
"""
Main handler for add memories endpoint.
Expand Down
33 changes: 26 additions & 7 deletions src/memos/api/handlers/component_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
from memos.mem_scheduler.orm_modules.base_model import BaseDBManager
from memos.mem_scheduler.scheduler_factory import SchedulerFactory
from memos.memories.textual.simple_tree import SimpleTreeTextMemory
from memos.memories.textual.tree_text_memory.organize.history_manager import MemoryHistoryManager
from memos.memories.textual.tree_text_memory.organize.manager import MemoryManager
from memos.memories.textual.tree_text_memory.retrieve.retrieve_utils import FastTokenizer
from memos.plugins.component_bootstrap import build_plugin_context
from memos.plugins.manager import plugin_manager


if TYPE_CHECKING:
from memos.memories.textual.tree import TreeTextMemory
from memos.extras.nli_model.client import NLIClient
from memos.mem_agent.deepsearch_agent import DeepSearchMemAgent
from memos.memories.textual.tree_text_memory.retrieve.internet_retriever_factory import (
InternetRetrieverFactory,
Expand Down Expand Up @@ -122,6 +122,12 @@ def init_server() -> dict[str, Any]:
existing code that uses the components.
"""
logger.info("Initializing MemOS server components...")
logger.info(
"[INIT_SERVER] env_MEMSCHEDULER_STREAM_KEY_PREFIX=%s, env_MEMSCHEDULER_REDIS_STREAM_KEY_PREFIX=%s, env_POLAR_DB_DB_NAME=%s",
os.getenv("MEMSCHEDULER_STREAM_KEY_PREFIX"),
os.getenv("MEMSCHEDULER_REDIS_STREAM_KEY_PREFIX"),
os.getenv("POLAR_DB_DB_NAME"),
)

# Initialize Redis client first as it is a core dependency for features like scheduler status tracking
if os.getenv("MEMSCHEDULER_USE_REDIS_QUEUE", "False").lower() == "true":
Expand Down Expand Up @@ -169,10 +175,25 @@ def init_server() -> dict[str, Any]:
else None
)
embedder = EmbedderFactory.from_config(embedder_config)
nli_client = NLIClient(base_url=nli_client_config["base_url"])
memory_history_manager = MemoryHistoryManager(nli_client=nli_client, graph_db=graph_db)

plugin_context = build_plugin_context(
graph_db=graph_db,
embedder=embedder,
default_cube_config=default_cube_config,
nli_client_config=nli_client_config,
mem_reader_config=mem_reader_config,
reranker_config=reranker_config,
feedback_reranker_config=feedback_reranker_config,
internet_retriever_config=internet_retriever_config,
)
plugin_manager.discover()
plugin_manager.init_components(plugin_context)

# Pass graph_db to mem_reader for recall operations (deduplication, conflict detection)
mem_reader = MemReaderFactory.from_config(mem_reader_config, graph_db=graph_db)
mem_reader = MemReaderFactory.from_config(
mem_reader_config,
graph_db=graph_db,
)
reranker = RerankerFactory.from_config(reranker_config)
feedback_reranker = RerankerFactory.from_config(feedback_reranker_config)
internet_retriever = InternetRetrieverFactory.from_config(
Expand Down Expand Up @@ -303,6 +324,4 @@ def init_server() -> dict[str, Any]:
"feedback_server": feedback_server,
"redis_client": redis_client,
"deepsearch_agent": deepsearch_agent,
"nli_client": nli_client,
"memory_history_manager": memory_history_manager,
}
1 change: 1 addition & 0 deletions src/memos/api/handlers/formatters_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def separate_knowledge_and_conversation_mem(memories: list[dict[str, Any]]):
sources = item.get("metadata", {}).get("sources", [])
if (
item["metadata"]["memory_type"] != "RawFileMemory"
and sources
and len(sources) > 0
and "type" in sources[0]
and sources[0]["type"] == "file"
Expand Down
10 changes: 10 additions & 0 deletions src/memos/api/server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@
from memos.api.exceptions import APIExceptionHandler
from memos.api.middleware.request_context import RequestContextMiddleware
from memos.api.routers.server_router import router as server_router
from memos.plugins.manager import plugin_manager


load_dotenv()

plugin_manager.discover()

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
logger.info(
"[SERVER_API] load_dotenv completed. env_MEMSCHEDULER_STREAM_KEY_PREFIX=%s, env_MEMSCHEDULER_REDIS_STREAM_KEY_PREFIX=%s",
os.getenv("MEMSCHEDULER_STREAM_KEY_PREFIX"),
os.getenv("MEMSCHEDULER_REDIS_STREAM_KEY_PREFIX"),
)

app = FastAPI(
title="MemOS Server REST APIs",
Expand Down Expand Up @@ -49,6 +57,8 @@ def health_check():
# Fallback for unknown errors
app.exception_handler(Exception)(APIExceptionHandler.global_exception_handler)

plugin_manager.init_app(app)


if __name__ == "__main__":
import argparse
Expand Down
1 change: 1 addition & 0 deletions src/memos/chunkers/sentence_chunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ def chunk(self, text: str) -> list[str] | list[Chunk]:
chunks.append(chunk)

logger.debug(f"Generated {len(chunks)} chunks from input text")

return chunks
9 changes: 8 additions & 1 deletion src/memos/configs/mem_reader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Any, ClassVar
from typing import Any, ClassVar, Literal

from pydantic import ConfigDict, Field, field_validator, model_validator

Expand Down Expand Up @@ -76,6 +76,13 @@ class MultiModalStructMemReaderConfig(BaseMemReaderConfig):
default=None,
description="Skills directory for the MemReader",
)
memory_version_switch: Literal["on", "off"] = Field(
default="off",
description="Turn on memory version or off",
)

# Allow passing additional fields without raising validation errors
model_config = ConfigDict(extra="allow", strict=True)


class StrategyStructMemReaderConfig(BaseMemReaderConfig):
Expand Down
61 changes: 0 additions & 61 deletions src/memos/extras/nli_model/client.py

This file was deleted.

69 changes: 0 additions & 69 deletions src/memos/extras/nli_model/server/README.md

This file was deleted.

23 changes: 0 additions & 23 deletions src/memos/extras/nli_model/server/config.py

This file was deleted.

Loading
Loading