From 2bf1123647a727aecca21a8c2a4c61f9e92db641 Mon Sep 17 00:00:00 2001 From: wayyoungboy <1017761807@qq.com> Date: Thu, 8 Jan 2026 23:52:19 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E9=83=A8=E5=88=86=E9=85=8D=E7=BD=AEkey?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/powermem-integration.md | 42 +++++--- main/xiaozhi-server/config.yaml | 8 +- .../providers/memory/powermem/powermem.py | 101 +++++++++++------- 3 files changed, 97 insertions(+), 54 deletions(-) diff --git a/docs/powermem-integration.md b/docs/powermem-integration.md index 818e65ed..5b194106 100644 --- a/docs/powermem-integration.md +++ b/docs/powermem-integration.md @@ -59,7 +59,9 @@ Memory: llm_model: qwen-plus # 嵌入模型配置 embedding_api_key: 你的嵌入模型API密钥 - embedding_model: text-embedding-v3 + embedding_model: text-embedding-v4 + # 可选:自定义嵌入服务地址(OpenAI兼容模式) + # embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 ``` ### 配置参数详解 @@ -72,10 +74,21 @@ Memory: | `embedding_provider` | 嵌入模型提供商 | `qwen` | `qwen`, `zhipu`, `openai`, 等 | | `llm_api_key` | LLM API 密钥 | - | - | | `llm_model` | LLM 模型名称 | - | 根据提供商选择 | -| `llm_base_url` | LLM API 地址(可选) | - | - | +| `llm_base_url` | LLM API 地址(可选) | - | 根据provider自动选择参数名 | | `embedding_api_key` | 嵌入模型 API 密钥 | - | - | | `embedding_model` | 嵌入模型名称 | - | 根据提供商选择 | -| `embedding_base_url` | 嵌入模型 API 地址(可选) | - | - | +| `embedding_base_url` | 嵌入模型 API 地址(可选) | - | 通用配置,根据provider自动选择 | +| `embedding_openai_base_url` | OpenAI兼容的嵌入服务地址(可选) | - | 优先级高于 embedding_base_url | +| `embedding_dashscope_base_url` | 阿里云灵积嵌入服务地址(可选) | - | 优先级高于 embedding_base_url | + +#### Base URL 配置优先级 + +| 配置项 | 优先级 | 适用 Provider | +|--------|--------|---------------| +| `embedding_openai_base_url` | 最高 | openai | +| `embedding_dashscope_base_url` | 最高 | qwen | +| `embedding_base_url` | 中 | 通用,根据 provider 自动选择 | +| 默认值 | 最低 | 使用 provider 的默认服务地址 | ### 记忆模式说明 @@ -102,7 +115,9 @@ Memory: llm_api_key: sk-xxxxxxxxxxxxxxxx llm_model: qwen-plus embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v3 + embedding_model: text-embedding-v4 + # 可选:使用 OpenAI 兼容模式的服务地址 + embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 ``` ### 使用智谱免费 LLM(完全免费方案) @@ -118,14 +133,14 @@ Memory: powermem: type: powermem database_provider: sqlite - llm_provider: zhipu - embedding_provider: zhipu + llm_provider: openai # 使用 openai 兼容模式 + embedding_provider: openai # 使用 openai 兼容模式 llm_api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx llm_model: glm-4-flash llm_base_url: https://open.bigmodel.cn/api/paas/v4/ embedding_api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx embedding_model: embedding-3 - embedding_base_url: https://open.bigmodel.cn/api/paas/v4/ + embedding_openai_base_url: https://open.bigmodel.cn/api/paas/v4/ ``` ### 使用 OpenAI @@ -142,7 +157,7 @@ Memory: llm_base_url: https://api.openai.com/v1 embedding_api_key: sk-xxxxxxxxxxxxxxxx embedding_model: text-embedding-3-small - embedding_base_url: https://api.openai.com/v1 + embedding_openai_base_url: https://api.openai.com/v1 ``` ### 使用 OceanBase(最佳性能方案) @@ -164,7 +179,8 @@ Memory: llm_api_key: sk-xxxxxxxxxxxxxxxx llm_model: qwen-plus embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v3 + embedding_model: text-embedding-v4 + embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 # OceanBase 数据库连接配置 vector_store: provider: oceanbase @@ -198,10 +214,11 @@ Memory: model: qwen-plus # 嵌入模型配置 embedder: - provider: qwen + provider: openai # 使用 OpenAI 兼容模式 config: api_key: sk-xxxxxxxxxxxxxxxx - model: text-embedding-v3 + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 ``` ## 设备记忆隔离 @@ -231,7 +248,8 @@ Memory: llm_api_key: sk-xxxxxxxxxxxxxxxx llm_model: qwen-plus embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v3 + embedding_model: text-embedding-v4 + embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 # OceanBase 数据库连接配置 vector_store: provider: oceanbase diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index e5420b2c..324fb972 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -301,9 +301,15 @@ Memory: # 智谱AI(免费): https://bigmodel.cn/usercenter/proj-mgmt/apikeys llm_api_key: 你的LLM API密钥 llm_model: qwen-plus + # llm_base_url: https://dashscope.aliyuncs.com/api/v1 # 可选,自定义LLM服务地址 # 嵌入模型配置 (使用通义千问为例) embedding_api_key: 你的嵌入模型API密钥 - embedding_model: text-embedding-v3 + embedding_model: text-embedding-v4 + embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 # OpenAI兼容的嵌入服务地址 + # embedding_base_url: https://dashscope.aliyuncs.com/api/v1 # 可选,自定义嵌入模型服务地址 + # 或使用独立的base_url配置(优先级更高): + # embedding_openai_base_url: https://api.openai.com/v1 # OpenAI兼容的嵌入服务地址 + # embedding_dashscope_base_url: https://dashscope.aliyuncs.com/api/v1 # 阿里云灵积嵌入服务地址 nomem: # 不想使用记忆功能,可以使用nomem type: nomem diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index c1891932..fc883c68 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -21,13 +21,13 @@ TAG = __name__ class MemoryProvider(MemoryProviderBase): """ PowerMem memory provider implementation. - + PowerMem is an open-source agent memory component that provides efficient memory management for AI agents. - + Supports multiple storage backends (sqlite, oceanbase, postgres), LLM providers (qwen, openai, etc.) and embedding providers. - + Config options: - enable_user_profile: bool - Enable UserMemory for user profiling (requires OceanBase) - database_provider: str - Storage backend (sqlite, oceanbase, postgres) @@ -40,16 +40,16 @@ class MemoryProvider(MemoryProviderBase): self.use_powermem = False self.memory_client = None self.enable_user_profile = False - + try: # Check if user profile mode is enabled self.enable_user_profile = config.get("enable_user_profile", False) - + # Get configuration parameters database_provider = config.get("database_provider", "sqlite") llm_provider = config.get("llm_provider", "qwen") embedding_provider = config.get("embedding_provider", "qwen") - + # UserMemory requires OceanBase if self.enable_user_profile and database_provider not in ["oceanbase"]: logger.bind(tag=TAG).warning( @@ -57,13 +57,13 @@ class MemoryProvider(MemoryProviderBase): "Falling back to AsyncMemory mode." ) self.enable_user_profile = False - + # Build powermem configuration dict # PowerMem supports two config styles: # 1. powermem style: database, llm, embedding # 2. mem0 style: vector_store, llm, embedder powermem_config = {} - + # Configure vector store / database if "vector_store" in config: powermem_config["vector_store"] = config["vector_store"] @@ -74,7 +74,7 @@ class MemoryProvider(MemoryProviderBase): "provider": database_provider, "config": {} } - + # Configure LLM if "llm" in config: powermem_config["llm"] = config["llm"] @@ -84,13 +84,23 @@ class MemoryProvider(MemoryProviderBase): llm_config["api_key"] = config["llm_api_key"] if "llm_model" in config: llm_config["model"] = config["llm_model"] + # Handle base_url based on provider type + # - qwen provider uses dashscope_base_url + # - openai provider uses openai_base_url if "llm_base_url" in config: - llm_config["base_url"] = config["llm_base_url"] + if llm_provider == "qwen": + llm_config["dashscope_base_url"] = config["llm_base_url"] + else: + llm_config["openai_base_url"] = config["llm_base_url"] + if "openai_base_url" in config: + llm_config["openai_base_url"] = config["openai_base_url"] + if "dashscope_base_url" in config: + llm_config["dashscope_base_url"] = config["dashscope_base_url"] powermem_config["llm"] = { "provider": llm_provider, "config": llm_config } - + # Configure embedder if "embedder" in config: powermem_config["embedder"] = config["embedder"] @@ -100,13 +110,25 @@ class MemoryProvider(MemoryProviderBase): embedder_config["api_key"] = config["embedding_api_key"] if "embedding_model" in config: embedder_config["model"] = config["embedding_model"] + # Handle base_url based on provider type + # - qwen provider uses dashscope_base_url + # - openai provider uses openai_base_url + # Priority: embedding_xxx_base_url > embedding_base_url > xxx_base_url if "embedding_base_url" in config: - embedder_config["base_url"] = config["embedding_base_url"] + if embedding_provider == "qwen": + embedder_config["dashscope_base_url"] = config["embedding_base_url"] + else: + embedder_config["openai_base_url"] = config["embedding_base_url"] + # Embedding-specific base_url (higher priority) + if "embedding_openai_base_url" in config: + embedder_config["openai_base_url"] = config["embedding_openai_base_url"] + if "embedding_dashscope_base_url" in config: + embedder_config["dashscope_base_url"] = config["embedding_dashscope_base_url"] powermem_config["embedder"] = { "provider": embedding_provider, "config": embedder_config } - + # Initialize memory client based on mode if self.enable_user_profile: from powermem import UserMemory @@ -116,14 +138,14 @@ class MemoryProvider(MemoryProviderBase): from powermem import AsyncMemory self.memory_client = AsyncMemory(config=powermem_config) memory_mode = "AsyncMemory (普通记忆模式)" - + self.use_powermem = True - + logger.bind(tag=TAG).info( f"PowerMem initialized successfully: mode={memory_mode}, " f"database={database_provider}, llm={llm_provider}, embedding={embedding_provider}" ) - + except ImportError as e: logger.bind(tag=TAG).error( f"PowerMem not installed. Please install with: pip install powermem. Error: {e}" @@ -137,17 +159,17 @@ class MemoryProvider(MemoryProviderBase): async def save_memory(self, msgs): """ Save conversation messages to PowerMem. - + Args: msgs: List of message objects with 'role' and 'content' attributes - + Returns: Result from PowerMem API or None if failed """ if not self.use_powermem or self.memory_client is None: logger.bind(tag=TAG).warning("PowerMem is not available, skipping save_memory") return None - + if len(msgs) < 2: logger.bind(tag=TAG).debug("Not enough messages to save (need at least 2)") return None @@ -159,16 +181,16 @@ class MemoryProvider(MemoryProviderBase): for message in msgs if message.role != "system" ] - + # Add memory using PowerMem SDK result = await self.memory_client.add( messages=messages, user_id=self.role_id ) - + logger.bind(tag=TAG).debug(f"Save memory result: {result}") return result - + except Exception as e: logger.bind(tag=TAG).error(f"Error saving memory: {str(e)}") logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") @@ -177,24 +199,24 @@ class MemoryProvider(MemoryProviderBase): async def query_memory(self, query: str) -> str: """ Query memories from PowerMem based on similarity search. - + Args: query: The search query string - + Returns: Formatted string of relevant memories or empty string if none found """ if not self.use_powermem or self.memory_client is None: logger.bind(tag=TAG).warning("PowerMem is not available, skipping query_memory") return "" - + try: if not getattr(self, "role_id", None): logger.bind(tag=TAG).debug("No role_id set, returning empty memory") return "" result_parts = [] - + # If user profile mode is enabled, include user profile in results if self.enable_user_profile: profile = await self.get_user_profile() @@ -207,7 +229,7 @@ class MemoryProvider(MemoryProviderBase): user_id=self.role_id, limit=30 ) - + if results and "results" in results: # Format each memory entry with its update time memories = [] @@ -218,7 +240,7 @@ class MemoryProvider(MemoryProviderBase): timestamp = str(entry["updated_at"]) elif "created_at" in entry and entry["created_at"]: timestamp = str(entry["created_at"]) - + if timestamp: try: # Parse and reformat the timestamp (remove milliseconds if present) @@ -231,7 +253,7 @@ class MemoryProvider(MemoryProviderBase): formatted_time = timestamp else: formatted_time = "" - + memory = entry.get("memory", "") or entry.get("content", "") if memory: if formatted_time: @@ -251,7 +273,7 @@ class MemoryProvider(MemoryProviderBase): final_result = "\n\n".join(result_parts) logger.bind(tag=TAG).debug(f"Query results: {final_result}") return final_result - + except Exception as e: logger.bind(tag=TAG).error(f"Error querying memory: {str(e)}") logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") @@ -260,40 +282,37 @@ class MemoryProvider(MemoryProviderBase): async def get_user_profile(self) -> str: """ Get user profile from PowerMem (only available in UserMemory mode). - + Returns: Formatted user profile string or empty string if not available """ if not self.use_powermem or self.memory_client is None: return "" - + if not self.enable_user_profile: logger.bind(tag=TAG).debug("User profile mode is not enabled") return "" - + try: if not getattr(self, "role_id", None): return "" - + # Get user profile using UserMemory SDK profile = await self.memory_client.get_profile(user_id=self.role_id) - + if not profile: return "" - + # Format profile as readable string profile_parts = [] for key, value in profile.items(): if value: profile_parts.append(f"- {key}: {value}") - + return "\n".join(profile_parts) - + except Exception as e: logger.bind(tag=TAG).error(f"Error getting user profile: {str(e)}") logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") return "" - -# Register the memory provider instance -powermem = MemoryProvider({}) From 564adbd8518510073e5be8d3d5db59652cda5420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E7=A3=8A?= Date: Fri, 9 Jan 2026 12:25:17 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(powermem):=20=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=BB=93=E6=9E=84=E5=B9=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E6=95=B0=E6=8D=AE=E5=BA=93=E7=94=A8=E6=88=B7=E7=94=BB?= =?UTF-8?q?=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构配置结构为模块化设计(llm/embedder/vector_store) - 用户画像功能支持oceanbase/seekdb/sqlite三种存储后端 - 更新文档说明和示例配置 --- docs/powermem-integration.md | 248 +++++++++--------- main/xiaozhi-server/config.yaml | 62 +++-- .../providers/memory/powermem/powermem.py | 50 ++-- main/xiaozhi-server/requirements.txt | 2 +- 4 files changed, 186 insertions(+), 176 deletions(-) diff --git a/docs/powermem-integration.md b/docs/powermem-integration.md index 5b194106..86e47925 100644 --- a/docs/powermem-integration.md +++ b/docs/powermem-integration.md @@ -46,49 +46,58 @@ selected_module: Memory: powermem: type: powermem - # 是否启用用户画像功能(需要OceanBase) - enable_user_profile: false - # 数据库提供者: oceanbase(推荐,最佳性能), seekdb, postgres, sqlite(轻量备选) - database_provider: sqlite # 资源充足时建议使用 oceanbase 或 seekdb - # LLM提供者: qwen(默认), openai, 等 - llm_provider: qwen - # 嵌入模型提供者: qwen(默认), openai, 等 - embedding_provider: qwen - # LLM配置 - llm_api_key: 你的LLM API密钥 - llm_model: qwen-plus - # 嵌入模型配置 - embedding_api_key: 你的嵌入模型API密钥 - embedding_model: text-embedding-v4 - # 可选:自定义嵌入服务地址(OpenAI兼容模式) - # embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + # 是否启用用户画像功能 + # 用户画像支持: oceanbase、seekdb、sqlite (powermem 0.3.0+) + enable_user_profile: true + + # ========== LLM 配置 ========== + llm: + provider: openai # 可选: qwen, openai, zhipu 等 + config: + api_key: 你的LLM API密钥 + model: qwen-plus + # openai_base_url: https://api.openai.com/v1 # 可选,自定义服务地址 + + # ========== Embedding 配置 ========== + embedder: + provider: openai # 可选: qwen, openai 等 + config: + api_key: 你的嵌入模型API密钥 + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + + # ========== Database 配置 ========== + vector_store: + provider: sqlite # 可选: oceanbase(推荐), seekdb(推荐), postgres, sqlite(轻量) + config: {} # SQLite 无需额外配置 ``` ### 配置参数详解 -| 参数 | 说明 | 默认值 | 可选值 | -|------|------|--------|--------| -| `enable_user_profile` | 启用用户画像模式 | `false` | `true`(需OceanBase), `false` | -| `database_provider` | 存储后端类型 | `sqlite` | `oceanbase`(推荐), `seekdb`, `postgres`, `sqlite`(轻量) | -| `llm_provider` | LLM 提供商 | `qwen` | `qwen`, `zhipu`(免费), `openai`, 等 | -| `embedding_provider` | 嵌入模型提供商 | `qwen` | `qwen`, `zhipu`, `openai`, 等 | -| `llm_api_key` | LLM API 密钥 | - | - | -| `llm_model` | LLM 模型名称 | - | 根据提供商选择 | -| `llm_base_url` | LLM API 地址(可选) | - | 根据provider自动选择参数名 | -| `embedding_api_key` | 嵌入模型 API 密钥 | - | - | -| `embedding_model` | 嵌入模型名称 | - | 根据提供商选择 | -| `embedding_base_url` | 嵌入模型 API 地址(可选) | - | 通用配置,根据provider自动选择 | -| `embedding_openai_base_url` | OpenAI兼容的嵌入服务地址(可选) | - | 优先级高于 embedding_base_url | -| `embedding_dashscope_base_url` | 阿里云灵积嵌入服务地址(可选) | - | 优先级高于 embedding_base_url | +#### LLM 配置 -#### Base URL 配置优先级 +| 参数 | 说明 | 可选值 | +|------|------|--------| +| `llm.provider` | LLM 提供商 | `qwen`, `openai`, `zhipu` 等 | +| `llm.config.api_key` | API 密钥 | - | +| `llm.config.model` | 模型名称 | 根据提供商选择 | +| `llm.config.openai_base_url` | 自定义服务地址(可选) | - | -| 配置项 | 优先级 | 适用 Provider | -|--------|--------|---------------| -| `embedding_openai_base_url` | 最高 | openai | -| `embedding_dashscope_base_url` | 最高 | qwen | -| `embedding_base_url` | 中 | 通用,根据 provider 自动选择 | -| 默认值 | 最低 | 使用 provider 的默认服务地址 | +#### Embedding 配置 + +| 参数 | 说明 | 可选值 | +|------|------|--------| +| `embedder.provider` | 嵌入模型提供商 | `qwen`, `openai` 等 | +| `embedder.config.api_key` | API 密钥 | - | +| `embedder.config.model` | 模型名称 | 根据提供商选择 | +| `embedder.config.openai_base_url` | 自定义服务地址(可选) | - | + +#### Database 配置 + +| 参数 | 说明 | 可选值 | +|------|------|--------| +| `vector_store.provider` | 存储后端类型 | `oceanbase`(推荐), `seekdb`(推荐), `postgres`, `sqlite`(轻量) | +| `vector_store.config` | 数据库连接配置 | 根据 provider 设置 | ### 记忆模式说明 @@ -97,7 +106,9 @@ PowerMem 支持两种记忆模式: | 模式 | 配置 | 功能 | 存储要求 | |------|------|------|----------| | **普通记忆** | `enable_user_profile: false` | 对话记忆存储与检索 | 支持所有数据库 | -| **用户画像** | `enable_user_profile: true` | 记忆 + 自动提取用户画像 | 仅支持 OceanBase | +| **用户画像** | `enable_user_profile: true` | 记忆 + 自动提取用户画像 | oceanbase、seekdb、sqlite | + +> 📌 **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。 ### 使用通义千问(推荐) @@ -109,15 +120,21 @@ PowerMem 支持两种记忆模式: Memory: powermem: type: powermem - database_provider: sqlite - llm_provider: qwen - embedding_provider: qwen - llm_api_key: sk-xxxxxxxxxxxxxxxx - llm_model: qwen-plus - embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v4 - # 可选:使用 OpenAI 兼容模式的服务地址 - embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + enable_user_profile: true + llm: + provider: qwen + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: qwen-plus + embedder: + provider: openai + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + vector_store: + provider: sqlite + config: {} ``` ### 使用智谱免费 LLM(完全免费方案) @@ -132,15 +149,22 @@ Memory: Memory: powermem: type: powermem - database_provider: sqlite - llm_provider: openai # 使用 openai 兼容模式 - embedding_provider: openai # 使用 openai 兼容模式 - llm_api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx - llm_model: glm-4-flash - llm_base_url: https://open.bigmodel.cn/api/paas/v4/ - embedding_api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx - embedding_model: embedding-3 - embedding_openai_base_url: https://open.bigmodel.cn/api/paas/v4/ + enable_user_profile: true + llm: + provider: openai # 使用 openai 兼容模式 + config: + api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx + model: glm-4-flash + openai_base_url: https://open.bigmodel.cn/api/paas/v4/ + embedder: + provider: openai + config: + api_key: xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx + model: embedding-3 + openai_base_url: https://open.bigmodel.cn/api/paas/v4/ + vector_store: + provider: sqlite + config: {} ``` ### 使用 OpenAI @@ -149,15 +173,22 @@ Memory: Memory: powermem: type: powermem - database_provider: sqlite - llm_provider: openai - embedding_provider: openai - llm_api_key: sk-xxxxxxxxxxxxxxxx - llm_model: gpt-4o-mini - llm_base_url: https://api.openai.com/v1 - embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-3-small - embedding_openai_base_url: https://api.openai.com/v1 + enable_user_profile: true + llm: + provider: openai + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: gpt-4o-mini + openai_base_url: https://api.openai.com/v1 + embedder: + provider: openai + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: text-embedding-3-small + openai_base_url: https://api.openai.com/v1 + vector_store: + provider: sqlite + config: {} ``` ### 使用 OceanBase(最佳性能方案) @@ -173,15 +204,18 @@ OceanBase 是 PowerMem 的最佳搭档,可实现最大性能释放: Memory: powermem: type: powermem - database_provider: oceanbase - llm_provider: qwen - embedding_provider: qwen - llm_api_key: sk-xxxxxxxxxxxxxxxx - llm_model: qwen-plus - embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v4 - embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 - # OceanBase 数据库连接配置 + enable_user_profile: true + llm: + provider: qwen + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: qwen-plus + embedder: + provider: openai + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 vector_store: provider: oceanbase config: @@ -189,36 +223,9 @@ Memory: port: 2881 user: root@test password: your_password - database: powermem -``` - - -### 高级配置 - -如果需要更精细的控制,可以使用完整的配置结构: - -```yaml -Memory: - powermem: - type: powermem - # 向量存储配置 - vector_store: - provider: sqlite - config: - path: ./data/powermem.db - # LLM 配置 - llm: - provider: qwen - config: - api_key: sk-xxxxxxxxxxxxxxxx - model: qwen-plus - # 嵌入模型配置 - embedder: - provider: openai # 使用 OpenAI 兼容模式 - config: - api_key: sk-xxxxxxxxxxxxxxxx - model: text-embedding-v4 - openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + db_name: powermem + collection_name: memories # 默认值 + embedding_model_dims: 1536 # 嵌入向量维度,必需参数 ``` ## 设备记忆隔离 @@ -233,6 +240,8 @@ PowerMem 会自动使用设备 ID(`device_id`)作为 `user_id` 进行记忆 PowerMem 提供 `UserMemory` 类,可自动从对话中提取用户画像信息。 +> 📌 **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。 + ### 启用用户画像 在配置中设置 `enable_user_profile: true` 即可启用: @@ -242,23 +251,20 @@ Memory: powermem: type: powermem enable_user_profile: true # 启用用户画像 - database_provider: oceanbase # 必须使用 OceanBase - llm_provider: qwen - embedding_provider: qwen - llm_api_key: sk-xxxxxxxxxxxxxxxx - llm_model: qwen-plus - embedding_api_key: sk-xxxxxxxxxxxxxxxx - embedding_model: text-embedding-v4 - embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 - # OceanBase 数据库连接配置 - vector_store: - provider: oceanbase + llm: + provider: qwen config: - host: 127.0.0.1 - port: 2881 - user: root@test - password: your_password - database: powermem + api_key: sk-xxxxxxxxxxxxxxxx + model: qwen-plus + embedder: + provider: openai + config: + api_key: sk-xxxxxxxxxxxxxxxx + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + vector_store: + provider: sqlite # 用户画像支持: oceanbase、seekdb、sqlite + config: {} ``` ### 用户画像能力 @@ -276,7 +282,7 @@ Memory: 1. **用户画像**:用户的基本信息、兴趣爱好等 2. **相关记忆**:与当前对话相关的历史记忆 -> ⚠️ **注意**:`UserMemory` 功能需要 OceanBase 作为存储后端,其他数据库暂不支持。 +> ✅ **版本说明**:PowerMem 0.3.0+ 版本,用户画像功能支持 OceanBase、SeekDB、SQLite 三种存储后端。 ## 与其他记忆组件的对比 diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 324fb972..09e4bd8c 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -284,32 +284,46 @@ Memory: # 官网: https://www.powermem.ai/ # 使用示例: https://github.com/oceanbase/powermem/tree/main/examples type: powermem - # 是否启用用户画像功能(需要OceanBase作为存储后端) - # - false: 使用普通记忆模式(AsyncMemory),支持所有数据库 - # - true: 使用用户画像模式(UserMemory),自动提取用户信息,仅支持OceanBase - enable_user_profile: false - # 数据库提供者: oceanbase(推荐,最佳性能), seekdb(推荐,AI应用存储一体), postgres, sqlite(轻量备选) - # 资源充足时建议使用 oceanbase 或 seekdb - # 注意:用户画像功能(enable_user_profile: true)仅支持oceanbase - database_provider: sqlite - # LLM提供者: qwen(默认), zhipu(免费glm-4-flash), openai, 等 - llm_provider: qwen - # 嵌入模型提供者: qwen(默认), zhipu, openai, 等 - embedding_provider: qwen - # LLM配置 (使用通义千问为例) + # 是否启用用户画像功能 + # - false: 使用普通记忆模式(AsyncMemory) + # - true: 使用用户画像模式(UserMemory),自动提取用户信息 + # 用户画像功能支持: oceanbase、seekdb、sqlite (powermem 0.3.0+) + enable_user_profile: true + + # ========== LLM 配置 ========== # 通义千问: https://bailian.console.aliyun.com/?apiKey=1#/api-key # 智谱AI(免费): https://bigmodel.cn/usercenter/proj-mgmt/apikeys - llm_api_key: 你的LLM API密钥 - llm_model: qwen-plus - # llm_base_url: https://dashscope.aliyuncs.com/api/v1 # 可选,自定义LLM服务地址 - # 嵌入模型配置 (使用通义千问为例) - embedding_api_key: 你的嵌入模型API密钥 - embedding_model: text-embedding-v4 - embedding_openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 # OpenAI兼容的嵌入服务地址 - # embedding_base_url: https://dashscope.aliyuncs.com/api/v1 # 可选,自定义嵌入模型服务地址 - # 或使用独立的base_url配置(优先级更高): - # embedding_openai_base_url: https://api.openai.com/v1 # OpenAI兼容的嵌入服务地址 - # embedding_dashscope_base_url: https://dashscope.aliyuncs.com/api/v1 # 阿里云灵积嵌入服务地址 + llm: + provider: openai # 可选: qwen, openai, zhipu 等 + config: + api_key: 你的LLM API密钥 + model: qwen-plus + # openai_base_url: https://api.openai.com/v1 # 可选,自定义服务地址 + + # ========== Embedding 配置 ========== + embedder: + provider: openai # 可选: qwen, openai 等 + config: + api_key: 你的嵌入模型API密钥 + model: text-embedding-v4 + openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + + # ========== Database 配置 ========== + # oceanbase(推荐,最佳性能), seekdb(推荐,AI应用存储一体), postgres, sqlite(轻量备选) + # 用户画像功能支持: oceanbase、seekdb、sqlite + vector_store: + provider: sqlite + config: {} # SQLite 无需额外配置 + # OceanBase 配置示例: + # provider: oceanbase + # config: + # host: 127.0.0.1 + # port: 2881 + # user: root@test + # password: your_password + # db_name: powermem + # collection_name: memories # 默认值 + # embedding_model_dims: 1536 # 嵌入向量维度,必需参数 nomem: # 不想使用记忆功能,可以使用nomem type: nomem diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index fc883c68..76f84bb7 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -10,6 +10,7 @@ @Author: wayyoungboy """ +import asyncio import traceback from typing import Optional, Dict, Any @@ -40,6 +41,7 @@ class MemoryProvider(MemoryProviderBase): self.use_powermem = False self.memory_client = None self.enable_user_profile = False + self.last_profile_content = "" # Cache for user profile from UserMemory try: # Check if user profile mode is enabled @@ -50,14 +52,6 @@ class MemoryProvider(MemoryProviderBase): llm_provider = config.get("llm_provider", "qwen") embedding_provider = config.get("embedding_provider", "qwen") - # UserMemory requires OceanBase - if self.enable_user_profile and database_provider not in ["oceanbase"]: - logger.bind(tag=TAG).warning( - f"UserMemory requires OceanBase as storage backend, but got {database_provider}. " - "Falling back to AsyncMemory mode." - ) - self.enable_user_profile = False - # Build powermem configuration dict # PowerMem supports two config styles: # 1. powermem style: database, llm, embedding @@ -183,12 +177,22 @@ class MemoryProvider(MemoryProviderBase): ] # Add memory using PowerMem SDK - result = await self.memory_client.add( + result = self.memory_client.add( messages=messages, user_id=self.role_id ) + # Handle both sync and async returns + if asyncio.iscoroutine(result): + result = await result logger.bind(tag=TAG).debug(f"Save memory result: {result}") + + # Cache user profile if UserMemory mode and profile was extracted + if self.enable_user_profile and result: + if result.get('profile_extracted'): + self.last_profile_content = result.get('profile_content', '') + logger.bind(tag=TAG).debug(f"User profile extracted: {self.last_profile_content}") + return result except Exception as e: @@ -282,6 +286,9 @@ class MemoryProvider(MemoryProviderBase): async def get_user_profile(self) -> str: """ Get user profile from PowerMem (only available in UserMemory mode). + + In PowerMem 0.3.0+, user profile is automatically extracted during add() + and cached in last_profile_content. Returns: Formatted user profile string or empty string if not available @@ -293,26 +300,9 @@ class MemoryProvider(MemoryProviderBase): logger.bind(tag=TAG).debug("User profile mode is not enabled") return "" - try: - if not getattr(self, "role_id", None): - return "" + # Return cached profile content from last add() operation + if self.last_profile_content: + return self.last_profile_content - # Get user profile using UserMemory SDK - profile = await self.memory_client.get_profile(user_id=self.role_id) - - if not profile: - return "" - - # Format profile as readable string - profile_parts = [] - for key, value in profile.items(): - if value: - profile_parts.append(f"- {key}: {value}") - - return "\n".join(profile_parts) - - except Exception as e: - logger.bind(tag=TAG).error(f"Error getting user profile: {str(e)}") - logger.bind(tag=TAG).debug(f"Detailed error: {traceback.format_exc()}") - return "" + return "" diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 20167f39..b343742a 100644 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -23,7 +23,7 @@ loguru==0.7.3 requests==2.32.5 cozepy==0.20.0 mem0ai==1.0.0 -powermem>=0.2.1 +powermem>=0.3.0 bs4==0.0.2 modelscope==1.32.0 sherpa_onnx==1.12.17 From 06a2e6fbfa46859e89911cd63fe44cb01bb767a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E7=A3=8A?= Date: Fri, 9 Jan 2026 12:34:55 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(powermem):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E5=86=85=E5=AD=98=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E8=80=85=E5=AE=9E=E4=BE=8B=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/memory/powermem/powermem.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py index 96387ec4..a92b8ab3 100644 --- a/main/xiaozhi-server/core/providers/memory/powermem/powermem.py +++ b/main/xiaozhi-server/core/providers/memory/powermem/powermem.py @@ -308,6 +308,3 @@ class MemoryProvider(MemoryProviderBase): return "" - -# Register the memory provider instance -powermem = MemoryProvider({})