Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
26 changes: 25 additions & 1 deletion 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 @@ -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
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