Skip to content
Open
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# MemOS home
.memos/

# FuXi CLI local state
.fuxi/

# Node dependencies
node_modules/

# Temporary files
tmp/
**/tmp_data/
Expand Down
19 changes: 15 additions & 4 deletions apps/memos-local-plugin/adapters/deepseek-harness/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,21 @@ export async function apply(
}));

registrations.push(ctx.on("session/event", (session: Session, event: SessionEvent): void => {
bridge!.onSessionEvent(
session as unknown as DshSessionLike,
event as unknown as DshSessionEventLike,
);
// Memory is an optional enhancement. A malformed/unexpected event shape
// must not break the host agent's event loop, so swallow with a warning.
try {
bridge!.onSessionEvent(
session as unknown as DshSessionLike,
event as unknown as DshSessionEventLike,
);
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
const stack = error instanceof Error && error.stack ? `\n${error.stack}` : "";
ctx.logger.warn(
`memos-local-memory: session event ignored (${event.type}): ${message}${stack}`,
);
}
}));

registrations.push(ctx.on("session/disposed", (session: Session): void => {
Expand Down
3 changes: 0 additions & 3 deletions docker/requirements-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ zstandard==0.23.0
prometheus_client==0.23.1
beartype==0.22.5
diskcache==5.6.3
iniconfig==2.3.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.3.0
Expand All @@ -178,11 +177,9 @@ more-itertools==10.8.0
pathable==0.4.4
pathvalidate==3.3.1
platformdirs==4.5.0
pluggy==1.6.0
psycopg2-binary==2.9.11
py-key-value-aio==0.2.8
py-key-value-shared==0.2.8
PyJWT==2.10.1
pytest==9.0.2
alibabacloud-oss-v2==1.2.2
tavily-python==0.5.0
3 changes: 0 additions & 3 deletions docker/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ httpx==0.28.1
httpx-sse==0.4.3
huggingface-hub==0.36.0
idna==3.11
iniconfig==2.3.0
itsdangerous==2.2.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
Expand Down Expand Up @@ -64,7 +63,6 @@ pathable==0.4.4
pathvalidate==3.3.1
pika==1.3.2
platformdirs==4.5.0
pluggy==1.6.0
portalocker==2.8.0
prometheus_client==0.23.1
protobuf==6.33.1
Expand All @@ -81,7 +79,6 @@ PyJWT==2.10.1
pymilvus==2.6.5
PyMySQL==1.1.2
pyperclip==1.11.0
pytest==9.0.2
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
python-multipart==0.0.20
Expand Down
20 changes: 11 additions & 9 deletions src/memos/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def _auth_headers():
return
try:
data_props = cls.parse_properties(content)
logger.info("nacos config:", data_props)
logger.info("nacos config: %s", data_props)
_update_env_from_dict(data_props)
logger.info("✅ parse Nacos setting is Properties ")
except Exception as e:
Expand Down Expand Up @@ -365,7 +365,7 @@ def minimax_config() -> dict[str, Any]:

@staticmethod
def vllm_config() -> dict[str, Any]:
"""Get Qwen configuration."""
"""Get vLLM configuration."""
return {
"model_name_or_path": os.getenv("MOS_CHAT_MODEL", "Qwen/Qwen3-1.7B"),
"temperature": float(os.getenv("MOS_CHAT_TEMPERATURE", "0.8")),
Expand All @@ -378,7 +378,7 @@ def vllm_config() -> dict[str, Any]:

@staticmethod
def get_activation_config() -> dict[str, Any]:
"""Get Ollama configuration."""
"""Get activation (kv_cache) configuration."""
return {
"backend": "kv_cache",
"config": {
Expand Down Expand Up @@ -547,7 +547,7 @@ def get_feedback_llm_config() -> dict[str, Any]:

@staticmethod
def get_activation_vllm_config() -> dict[str, Any]:
"""Get Ollama configuration."""
"""Get activation (vLLM kv_cache) configuration."""
return {
"backend": "vllm_kv_cache",
"config": {
Expand Down Expand Up @@ -580,7 +580,7 @@ def get_preference_memory_config() -> dict[str, Any]:

@staticmethod
def get_reranker_config() -> dict[str, Any]:
"""Get embedder configuration."""
"""Get reranker configuration."""
embedder_backend = os.getenv("MOS_RERANKER_BACKEND", "http_bge")

if embedder_backend in ["http_bge", "http_bge_strategy"]:
Expand All @@ -606,7 +606,7 @@ def get_reranker_config() -> dict[str, Any]:

@staticmethod
def get_feedback_reranker_config() -> dict[str, Any]:
"""Get embedder configuration."""
"""Get feedback reranker configuration."""
embedder_backend = os.getenv("MOS_FEEDBACK_RERANKER_BACKEND", "http_bge")

if embedder_backend in ["http_bge", "http_bge_strategy"]:
Expand Down Expand Up @@ -827,7 +827,7 @@ def get_neo4j_config(user_id: str | None = None) -> dict[str, Any]:

@staticmethod
def get_noshared_neo4j_config(user_id) -> dict[str, Any]:
"""Get Neo4j configuration."""
"""Get per-user non-shared Neo4j configuration."""
return {
"uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"),
"user": os.getenv("NEO4J_USER", "neo4j"),
Expand All @@ -840,7 +840,7 @@ def get_noshared_neo4j_config(user_id) -> dict[str, Any]:

@staticmethod
def get_neo4j_shared_config(user_id: str | None = None) -> dict[str, Any]:
"""Get Neo4j configuration."""
"""Get shared Neo4j configuration (multi-tenant)."""
return {
"uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"),
"user": os.getenv("NEO4J_USER", "neo4j"),
Expand All @@ -852,7 +852,9 @@ def get_neo4j_shared_config(user_id: str | None = None) -> dict[str, Any]:
"embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 3072)),
}

def get_milvus_config():
@staticmethod
def get_milvus_config() -> dict[str, Any]:
"""Get Milvus vector database configuration."""
return {
"collection_name": [
"explicit_preference",
Expand Down
54 changes: 50 additions & 4 deletions src/memos/api/handlers/chat_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import asyncio
import json
import logging
import os
import re
import time
Expand Down Expand Up @@ -48,6 +49,51 @@
from memos.types import MessageList


# Fields safe to log for chat requests. Sensitive/bulk content (query,
# history, system_prompt, filter) and credentials (business_key) are excluded
# or masked so request logging never leaks memory content or auth keys.
# frozenset for O(1) membership checks on the hot path.
_CHAT_REQ_LOG_WHITELIST = frozenset((
"user_id",
"manager_user_id",
"project_id",
"mem_cube_id",
"readable_cube_ids",
"writable_cube_ids",
"session_id",
"mode",
"top_k",
"threshold",
"relativity",
"pref_top_k",
"max_tokens",
"temperature",
"top_p",
"internet_search",
"include_preference",
"add_message_on_answer",
))


def _safe_chat_req_log(chat_req: Any, prefix: str) -> str:
"""Build a loggable summary of a chat request without sensitive content."""
try:
data = chat_req.model_dump()
except AttributeError:
data = getattr(chat_req, "__dict__", {})
safe = {k: v for k, v in data.items() if k in _CHAT_REQ_LOG_WHITELIST}
if "business_key" in data:
safe["business_key"] = "***" if data.get("business_key") else None
return f"{prefix} Chat Req: {safe}"


def _log_chat_req(logger: Any, chat_req: Any, prefix: str) -> None:
"""Emit the safe chat-request log line, skipping expensive serialization
entirely when INFO logging is disabled."""
if logger.isEnabledFor(logging.INFO):
logger.info(_safe_chat_req_log(chat_req, prefix))


class ChatHandler(BaseHandler):
"""
Handler for chat operations.
Expand Down Expand Up @@ -116,7 +162,7 @@ def handle_chat_complete(self, chat_req: APIChatCompleteRequest) -> dict[str, An
Raises:
HTTPException: If chat fails
"""
self.logger.info(f"[ChatHandler] Chat Req is: {chat_req}")
_log_chat_req(self.logger, chat_req, "[ChatHandler]")
try:
# Resolve readable cube IDs (for search)
readable_cube_ids = chat_req.readable_cube_ids or [chat_req.user_id]
Expand Down Expand Up @@ -251,7 +297,7 @@ def handle_chat_stream(self, chat_req: ChatRequest) -> StreamingResponse:
Raises:
HTTPException: If stream initialization fails
"""
self.logger.info(f"[ChatHandler] Chat Req is: {chat_req}")
_log_chat_req(self.logger, chat_req, "[ChatHandler]")
try:

def generate_chat_response() -> Generator[str, None, None]:
Expand Down Expand Up @@ -436,7 +482,7 @@ def handle_chat_stream_playground(self, chat_req: ChatPlaygroundRequest) -> Stre
Raises:
HTTPException: If stream initialization fails
"""
self.logger.info(f"[ChatHandler] Chat Req is: {chat_req}")
_log_chat_req(self.logger, chat_req, "[ChatHandler]")
try:

def generate_chat_response() -> Generator[str, None, None]:
Expand Down Expand Up @@ -780,7 +826,7 @@ def handle_chat_stream_for_business_user(
self, chat_req: ChatBusinessRequest
) -> StreamingResponse:
"""Chat API for business user."""
self.logger.info(f"[ChatBusinessHandler] Chat Req is: {chat_req}")
_log_chat_req(self.logger, chat_req, "[ChatBusinessHandler]")

# Validate business_key permission
business_chat_keys = os.environ.get("BUSINESS_CHAT_KEYS", "[]")
Expand Down
Loading