diff --git a/README.md b/README.md index 32a779d2..2f53a996 100644 --- a/README.md +++ b/README.md @@ -338,8 +338,9 @@ This project is licensed under the Apache License 2.0. For details, see the LICE Join our community to ask questions, share your work, and connect with fellow developers. -- **GitHub Issues**: Report bugs, request features, or track known issues via [GitHub Issues](https://github.com/redbear-ai/memorybear/issues). -- **GitHub Pull Requests**: Contribute code improvements or fixes through [Pull Requests](https://github.com/redbear-ai/memorybear/pulls). -- **GitHub Discussions**: Ask questions, share ideas, and engage with the community in [GitHub Discussions](https://github.com/redbear-ai/memorybear/discussions). +- **GitHub Issues**: Report bugs, request features, or track known issues via [GitHub Issues](https://github.com/SuanmoSuanyangTechnology/MemoryBear/issues). +- **GitHub Pull Requests**: Contribute code improvements or fixes through [Pull Requests](https://github.com/SuanmoSuanyangTechnology/MemoryBear/pulls). +- **GitHub Discussions**: Ask questions, share ideas, and engage with the community in [GitHub Discussions](https://github.com/SuanmoSuanyangTechnology/MemoryBear/discussions). - **WeChat**: Scan the QR code below to join our WeChat community group. -- **Contact**: If you are interested in contributing or collaborating, feel free to reach out at tianyou_hubm@redbearai.com \ No newline at end of file +- ![wecom-temp-114020-47fe87a75da439f09f5dc93a01593046](https://github.com/user-attachments/assets/8c81885c-4134-40d5-96e2-7f78cc082dc6) +- **Contact**: If you are interested in contributing or collaborating, feel free to reach out at tianyou_hubm@redbearai.com diff --git a/api/app/controllers/memory_agent_controller.py b/api/app/controllers/memory_agent_controller.py index 46a2954b..22830890 100644 --- a/api/app/controllers/memory_agent_controller.py +++ b/api/app/controllers/memory_agent_controller.py @@ -697,7 +697,7 @@ async def get_user_profile_api( current_user: User = Depends(get_current_user) ): """ - 获取用户详情,包含: + 获取工作空间下Popular Memory Tags,包含: - name: 用户名字(直接使用 end_user_id) - tags: 3个用户特征标签(从语句和实体中LLM总结) - hot_tags: 4个热门记忆标签 diff --git a/api/app/controllers/memory_dashboard_controller.py b/api/app/controllers/memory_dashboard_controller.py index 2afff491..e03c1846 100644 --- a/api/app/controllers/memory_dashboard_controller.py +++ b/api/app/controllers/memory_dashboard_controller.py @@ -5,7 +5,6 @@ from app.core.response_utils import success from app.db import get_db from app.dependencies import get_current_user from app.models.user_model import User -from app.schemas.memory_agent_schema import End_User_Information from app.schemas.response_schema import ApiResponse from app.services import memory_dashboard_service, memory_storage_service, workspace_service @@ -40,54 +39,7 @@ def get_workspace_total_end_users( api_logger.info(f"成功获取最新用户总数: total_num={total_end_users.get('total_num', 0)}") return success(data=total_end_users, msg="用户数量获取成功") -@router.post("/update/end_users", response_model=ApiResponse) -async def update_workspace_end_users( - user_input: End_User_Information, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_user), -): - """ - 更新工作空间的宿主信息 - """ - username = user_input.end_user_name # 要更新的用户名 - end_user_input_id = user_input.id # 宿主ID - workspace_id = current_user.current_workspace_id - - api_logger.info(f"用户 {current_user.username} 请求更新工作空间 {workspace_id} 的宿主信息") - api_logger.info(f"更新参数: username={username}, end_user_id={end_user_input_id}") - try: - # 导入更新函数 - from app.repositories.end_user_repository import update_end_user_other_name - import uuid - - # 转换 end_user_id 为 UUID 类型 - end_user_uuid = uuid.UUID(end_user_input_id) - - # 直接更新数据库中的 other_name 字段 - updated_count = update_end_user_other_name( - db=db, - end_user_id=end_user_uuid, - other_name=username - ) - - api_logger.info(f"成功更新宿主 {end_user_input_id} 的 other_name 为: {username}") - - return success( - data={ - "updated_count": updated_count, - "end_user_id": end_user_input_id, - "updated_other_name": username - }, - msg=f"成功更新 {updated_count} 个宿主的信息" - ) - - except Exception as e: - api_logger.error(f"更新宿主信息失败: {str(e)}") - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"更新宿主信息失败: {str(e)}" - ) diff --git a/api/app/controllers/memory_storage_controller.py b/api/app/controllers/memory_storage_controller.py index 63d9078a..f4175923 100644 --- a/api/app/controllers/memory_storage_controller.py +++ b/api/app/controllers/memory_storage_controller.py @@ -28,7 +28,6 @@ from app.services.memory_storage_service import ( search_dialogue, search_edges, search_entity, - search_entity_graph, search_statement, ) from fastapi import APIRouter, Depends @@ -412,21 +411,7 @@ async def search_entity_edges( api_logger.error(f"Search edges failed: {str(e)}") return fail(BizCode.INTERNAL_ERROR, "边查询失败", str(e)) -@router.get("/search/entity_graph", response_model=ApiResponse) -async def search_for_entity_graph( - end_user_id: Optional[str] = None, - current_user: User = Depends(get_current_user), - ) -> dict: - """ - 搜索所有实体之间的关系网络 - """ - api_logger.info(f"Search entity graph requested for end_user_id: {end_user_id}") - try: - result = await search_entity_graph(end_user_id) - return success(data=result, msg="查询成功") - except Exception as e: - api_logger.error(f"Search entity graph failed: {str(e)}") - return fail(BizCode.INTERNAL_ERROR, "实体图查询失败", str(e)) + @router.get("/analytics/hot_memory_tags", response_model=ApiResponse) diff --git a/api/app/controllers/user_memory_controllers.py b/api/app/controllers/user_memory_controllers.py index d99eb47e..3b7345b6 100644 --- a/api/app/controllers/user_memory_controllers.py +++ b/api/app/controllers/user_memory_controllers.py @@ -351,12 +351,11 @@ async def update_end_user_profile( 该接口可以更新用户的姓名、职位、部门、联系方式、电话和入职日期等信息。 所有字段都是可选的,只更新提供的字段。 - """ workspace_id = current_user.current_workspace_id end_user_id = profile_update.end_user_id - # 检查用户是否已选择工作空间 + # 验证工作空间 if workspace_id is None: api_logger.warning(f"用户 {current_user.username} 尝试更新用户信息但未选择工作空间") return fail(BizCode.INVALID_PARAMETER, "请先切换到一个工作空间", "current_workspace_id is None") @@ -366,57 +365,24 @@ async def update_end_user_profile( f"workspace={workspace_id}" ) - try: - # 查询终端用户 - end_user = db.query(EndUser).filter(EndUser.id == end_user_id).first() + # 调用 Service 层处理业务逻辑 + result = user_memory_service.update_end_user_profile(db, end_user_id, profile_update) - if not end_user: - api_logger.warning(f"终端用户不存在: end_user_id={end_user_id}") - return fail(BizCode.INVALID_PARAMETER, "终端用户不存在", f"end_user_id={end_user_id}") - - # 更新字段(只更新提供的字段,排除 end_user_id) - # 允许 None 值来重置字段(如 hire_date) - update_data = profile_update.model_dump(exclude_unset=True, exclude={'end_user_id'}) - - # 特殊处理 hire_date:如果提供了时间戳,转换为 DateTime - if 'hire_date' in update_data: - hire_date_timestamp = update_data['hire_date'] - if hire_date_timestamp is not None: - update_data['hire_date'] = timestamp_to_datetime(hire_date_timestamp) - # 如果是 None,保持 None(允许清空) - - for field, value in update_data.items(): - setattr(end_user, field, value) - - # 更新 updated_at 时间戳 - end_user.updated_at = datetime.datetime.now() - - # 更新 updatetime_profile 为当前时间 - end_user.updatetime_profile = datetime.datetime.now() - - # 提交更改 - db.commit() - db.refresh(end_user) - - # 构建响应数据 - profile_data = EndUserProfileResponse( - id=end_user.id, - other_name=end_user.other_name, - position=end_user.position, - department=end_user.department, - contact=end_user.contact, - phone=end_user.phone, - hire_date=end_user.hire_date, - updatetime_profile=end_user.updatetime_profile - ) - - api_logger.info(f"成功更新用户信息: end_user_id={end_user_id}, updated_fields={list(update_data.keys())}") - return success(data=UserMemoryService.convert_profile_to_dict_with_timestamp(profile_data), msg="更新成功") - - except Exception as e: - db.rollback() - api_logger.error(f"用户信息更新失败: end_user_id={end_user_id}, error={str(e)}") - return fail(BizCode.INTERNAL_ERROR, "用户信息更新失败", str(e)) + if result["success"]: + api_logger.info(f"成功更新用户信息: end_user_id={end_user_id}") + return success(data=result["data"], msg="更新成功") + else: + error_msg = result["error"] + api_logger.error(f"用户信息更新失败: end_user_id={end_user_id}, error={error_msg}") + + # 根据错误类型映射到合适的业务错误码 + if error_msg == "终端用户不存在": + return fail(BizCode.USER_NOT_FOUND, "终端用户不存在", error_msg) + elif error_msg == "无效的用户ID格式": + return fail(BizCode.INVALID_USER_ID, "无效的用户ID格式", error_msg) + else: + # 只有未预期的错误才使用 INTERNAL_ERROR + return fail(BizCode.INTERNAL_ERROR, "用户信息更新失败", error_msg) @router.get("/memory_space/timeline_memories", response_model=ApiResponse) async def memory_space_timeline_of_shared_memories(id: str, label: str,language_type: str="zh", diff --git a/api/app/repositories/data_config_repository.py b/api/app/repositories/data_config_repository.py index d26058b2..3df7f800 100644 --- a/api/app/repositories/data_config_repository.py +++ b/api/app/repositories/data_config_repository.py @@ -104,38 +104,6 @@ class DataConfigRepository: r.statement AS statement """ - # Entity graph within group (source node, edge, target node) - SEARCH_FOR_ENTITY_GRAPH = """ - MATCH (n:ExtractedEntity)-[r]->(m:ExtractedEntity) - WHERE n.group_id = $group_id - RETURN - { - entity_idx: n.entity_idx, - connect_strength: n.connect_strength, - description: n.description, - entity_type: n.entity_type, - name: n.name, - fact_summary: COALESCE(n.fact_summary, ''), - id: n.id - } AS sourceNode, - { - rel_id: elementId(r), - source_id: startNode(r).id, - target_id: endNode(r).id, - predicate: r.predicate, - statement_id: r.statement_id, - statement: r.statement - } AS edge, - { - entity_idx: m.entity_idx, - connect_strength: m.connect_strength, - description: m.description, - entity_type: m.entity_type, - name: m.name, - fact_summary: COALESCE(m.fact_summary, ''), - id: m.id - } AS targetNode - """ @staticmethod def update_reflection_config( db: Session, diff --git a/api/app/repositories/end_user_repository.py b/api/app/repositories/end_user_repository.py index b9e82693..c7d13f8f 100644 --- a/api/app/repositories/end_user_repository.py +++ b/api/app/repositories/end_user_repository.py @@ -276,42 +276,6 @@ def get_end_user_by_id(db: Session, end_user_id: uuid.UUID) -> Optional[EndUser] end_user = repo.get_end_user_by_id(end_user_id) return end_user -def update_end_user_other_name( - db: Session, - end_user_id: uuid.UUID, - other_name: str -) -> int: - """ - 通过 end_user_id 更新 end_user 表中的 other_name 字段 - - Args: - db: 数据库会话 - end_user_id: 宿主ID - other_name: 要更新的用户名 - - Returns: - int: 更新的记录数 - """ - try: - # 执行更新 - updated_count = ( - db.query(EndUser) - .filter(EndUser.id == end_user_id) - .update( - {EndUser.other_name: other_name}, - synchronize_session=False - ) - ) - - db.commit() - db_logger.info(f"成功更新宿主 {end_user_id} 的 other_name 为: {other_name}") - return updated_count - - except Exception as e: - db.rollback() - db_logger.error(f"更新宿主 {end_user_id} 的 other_name 时出错: {str(e)}") - raise - # 新增的缓存操作函数(保持与类方法一致的接口) def get_by_id(db: Session, end_user_id: uuid.UUID) -> Optional[EndUser]: """根据ID获取终端用户(用于缓存操作)""" diff --git a/api/app/schemas/memory_agent_schema.py b/api/app/schemas/memory_agent_schema.py index fbc0e45c..d4354c40 100644 --- a/api/app/schemas/memory_agent_schema.py +++ b/api/app/schemas/memory_agent_schema.py @@ -15,7 +15,3 @@ class Write_UserInput(BaseModel): messages: list[dict] group_id: str config_id: Optional[str] = None - -class End_User_Information(BaseModel): - end_user_name: str # 这是要更新的用户名 - id: str # 宿主ID,用于匹配条件 diff --git a/api/app/services/memory_agent_service.py b/api/app/services/memory_agent_service.py index 253dad2e..8170bdd8 100644 --- a/api/app/services/memory_agent_service.py +++ b/api/app/services/memory_agent_service.py @@ -1217,7 +1217,7 @@ def get_end_users_connected_configs_batch(end_user_ids: List[str], db: Session) """ from app.models.app_release_model import AppRelease from app.models.end_user_model import EndUser - from app.models.memory_config_model import MemoryConfig + from app.models.data_config_model import DataConfig from sqlalchemy import select logger.info(f"Batch getting connected configs for {len(end_user_ids)} end_users") @@ -1275,8 +1275,8 @@ def get_end_users_connected_configs_batch(end_user_ids: List[str], db: Session) # 批量查询 memory_config_name config_id_to_name = {} if memory_config_ids: - memory_configs = db.query(MemoryConfig).filter(MemoryConfig.id.in_(memory_config_ids)).all() - config_id_to_name = {str(mc.id): mc.config_name for mc in memory_configs} + memory_configs = db.query(DataConfig).filter(DataConfig.config_id.in_(memory_config_ids)).all() + config_id_to_name = {str(mc.config_id): mc.config_name for mc in memory_configs} # 4. 构建最终结果 for end_user_id, app_id in user_to_app.items(): @@ -1293,7 +1293,7 @@ def get_end_users_connected_configs_batch(end_user_ids: List[str], db: Session) memory_config_id = memory_obj.get('memory_content') if isinstance(memory_obj, dict) else None # 获取配置名称 - memory_config_name = config_id_to_name.get(memory_config_id) if memory_config_id else None + memory_config_name = config_id_to_name.get(str(memory_config_id)) if memory_config_id else None result[end_user_id] = { "memory_config_id": memory_config_id, diff --git a/api/app/services/memory_storage_service.py b/api/app/services/memory_storage_service.py index 9cac26ec..83d5923d 100644 --- a/api/app/services/memory_storage_service.py +++ b/api/app/services/memory_storage_service.py @@ -506,27 +506,6 @@ async def search_edges(end_user_id: Optional[str] = None) -> List[Dict[str, Any] return result -async def search_entity_graph(end_user_id: Optional[str] = None) -> Dict[str, Any]: - """搜索所有实体之间的关系网络(group 维度)。""" - result = await _neo4j_connector.execute_query( - DataConfigRepository.SEARCH_FOR_ENTITY_GRAPH, - group_id=end_user_id, - ) - # 对source_node 和 target_node 的 fact_summary进行截取,只截取前三条的内容(需要提取前三条“来源”) - for item in result: - source_fact = item["sourceNode"]["fact_summary"] - target_fact = item["targetNode"]["fact_summary"] - # 截取前三条“来源” - item["sourceNode"]["fact_summary"] = source_fact.split("\n")[:4] if source_fact else [] - item["targetNode"]["fact_summary"] = target_fact.split("\n")[:4] if target_fact else [] - # 与现有返回风格保持一致,携带搜索类型、数量与详情 - data = { - "search_for": "entity_graph", - "num": len(result), - "detials": result, - } - return data - async def analytics_hot_memory_tags( db: Session, diff --git a/api/app/services/user_memory_service.py b/api/app/services/user_memory_service.py index ae07256a..863bccb0 100644 --- a/api/app/services/user_memory_service.py +++ b/api/app/services/user_memory_service.py @@ -357,6 +357,101 @@ class UserMemoryService: data[key] = UserMemoryService._datetime_to_timestamp(original_value) return data + def update_end_user_profile( + self, + db: Session, + end_user_id: str, + profile_update: Any + ) -> Dict[str, Any]: + """ + 更新终端用户的基本信息 + + Args: + db: 数据库会话 + end_user_id: 终端用户ID (UUID) + profile_update: 包含更新字段的 Pydantic 模型 + + Returns: + { + "success": bool, + "data": dict, # 更新后的用户档案数据 + "error": Optional[str] + } + """ + try: + # 转换为UUID并查询用户 + user_uuid = uuid.UUID(end_user_id) + repo = EndUserRepository(db) + end_user = repo.get_by_id(user_uuid) + + if not end_user: + logger.warning(f"终端用户不存在: end_user_id={end_user_id}") + return { + "success": False, + "data": None, + "error": "终端用户不存在" + } + + # 获取更新数据(排除 end_user_id 字段) + update_data = profile_update.model_dump(exclude_unset=True, exclude={'end_user_id'}) + + # 特殊处理 hire_date:如果提供了时间戳,转换为 DateTime + if 'hire_date' in update_data: + hire_date_timestamp = update_data['hire_date'] + if hire_date_timestamp is not None: + from app.core.api_key_utils import timestamp_to_datetime + update_data['hire_date'] = timestamp_to_datetime(hire_date_timestamp) + # 如果是 None,保持 None(允许清空) + + # 更新字段 + for field, value in update_data.items(): + setattr(end_user, field, value) + + # 更新时间戳 + end_user.updated_at = datetime.now() + end_user.updatetime_profile = datetime.now() + + # 提交更改 + db.commit() + db.refresh(end_user) + + # 构建响应数据 + from app.schemas.end_user_schema import EndUserProfileResponse + profile_data = EndUserProfileResponse( + id=end_user.id, + other_name=end_user.other_name, + position=end_user.position, + department=end_user.department, + contact=end_user.contact, + phone=end_user.phone, + hire_date=end_user.hire_date, + updatetime_profile=end_user.updatetime_profile + ) + + logger.info(f"成功更新用户信息: end_user_id={end_user_id}, updated_fields={list(update_data.keys())}") + + return { + "success": True, + "data": self.convert_profile_to_dict_with_timestamp(profile_data), + "error": None + } + + except ValueError: + logger.error(f"无效的 end_user_id 格式: {end_user_id}") + return { + "success": False, + "data": None, + "error": "无效的用户ID格式" + } + except Exception as e: + db.rollback() + logger.error(f"用户信息更新失败: end_user_id={end_user_id}, error={str(e)}") + return { + "success": False, + "data": None, + "error": str(e) + } + async def get_cached_memory_insight( self, db: Session,