Merge branch 'refs/heads/develop' into fix/memory_bug_fix

This commit is contained in:
lixinyue
2026-01-19 18:04:29 +08:00
102 changed files with 3013 additions and 1540 deletions

View File

@@ -527,6 +527,7 @@ class AppChatService:
conversation_id: uuid.UUID,
config: WorkflowConfig,
app_id: uuid.UUID,
release_id: uuid.UUID,
workspace_id: uuid.UUID,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
@@ -549,6 +550,7 @@ class AppChatService:
payload=payload,
config=config,
workspace_id=workspace_id,
release_id=release_id,
)
async def workflow_chat_stream(
@@ -557,6 +559,7 @@ class AppChatService:
conversation_id: uuid.UUID,
config: WorkflowConfig,
app_id: uuid.UUID,
release_id: uuid.UUID,
workspace_id: uuid.UUID,
user_id: str = None,
variables: Optional[Dict[str, Any]] = None,
@@ -565,7 +568,7 @@ class AppChatService:
storage_type: Optional[str] = None,
user_rag_memory_id: Optional[str] = None,
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[dict, None]:
"""聊天(流式)"""
workflow_service = WorkflowService(self.db)
payload = DraftRunRequest(
@@ -580,6 +583,7 @@ class AppChatService:
payload=payload,
config=config,
workspace_id=workspace_id,
release_id=release_id
):
yield event

View File

@@ -129,7 +129,7 @@ class AppService:
Raises:
ResourceNotFoundException: 当应用不存在时
"""
app = get_apps_by_id(self.db,app_id)
app = get_apps_by_id(self.db, app_id)
if not app:
logger.warning("应用不存在", extra={"app_id": str(app_id)})
raise ResourceNotFoundException("应用", str(app_id))
@@ -227,7 +227,6 @@ class AppService:
if not model_api_key:
raise ResourceNotFoundException("模型配置", str(multi_agent_config.default_model_config_id))
# 3. 检查子 Agent 配置
if not multi_agent_config.sub_agents or len(multi_agent_config.sub_agents) == 0:
raise BusinessException(
@@ -281,10 +280,10 @@ class AppService:
)
def _create_agent_config(
self,
app_id: uuid.UUID,
config_data: app_schema.AgentConfigCreate,
now: datetime.datetime
self,
app_id: uuid.UUID,
config_data: app_schema.AgentConfigCreate,
now: datetime.datetime
) -> None:
"""创建 Agent 配置(内部方法)
@@ -313,10 +312,10 @@ class AppService:
logger.debug("Agent 配置已创建", extra={"app_id": str(app_id)})
def _create_multi_agent_config(
self,
app_id: uuid.UUID,
config_data: Dict[str, Any],
now: datetime.datetime
self,
app_id: uuid.UUID,
config_data: Dict[str, Any],
now: datetime.datetime
) -> None:
"""创建多 Agent 配置(内部方法)
@@ -411,9 +410,9 @@ class AppService:
return 1 if max_ver is None else int(max_ver) + 1
def _convert_to_schema(
self,
app: App,
current_workspace_id: uuid.UUID
self,
app: App,
current_workspace_id: uuid.UUID
) -> app_schema.App:
"""将 App 模型转换为 Schema并设置 is_shared 字段
@@ -447,9 +446,9 @@ class AppService:
# ==================== 应用管理 ====================
def get_app(
self,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> App:
"""获取应用详情
@@ -469,11 +468,11 @@ class AppService:
return app
def create_app(
self,
*,
user_id: uuid.UUID,
workspace_id: uuid.UUID,
data: app_schema.AppCreate
self,
*,
user_id: uuid.UUID,
workspace_id: uuid.UUID,
data: app_schema.AppCreate
) -> App:
"""创建应用
@@ -535,11 +534,11 @@ class AppService:
raise BusinessException(f"应用创建失败: {str(e)}", BizCode.INTERNAL_ERROR, cause=e)
def update_app(
self,
*,
app_id: uuid.UUID,
data: app_schema.AppUpdate,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
data: app_schema.AppUpdate,
workspace_id: Optional[uuid.UUID] = None
) -> App:
"""更新应用基本信息
@@ -578,10 +577,10 @@ class AppService:
return app
def delete_app(
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> None:
"""删除应用
@@ -612,12 +611,12 @@ class AppService:
)
def copy_app(
self,
*,
app_id: uuid.UUID,
user_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None,
new_name: Optional[str] = None
self,
*,
app_id: uuid.UUID,
user_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None,
new_name: Optional[str] = None
) -> App:
"""复制应用(包括基础信息和配置)
@@ -716,16 +715,16 @@ class AppService:
raise BusinessException(f"应用复制失败: {str(e)}", BizCode.INTERNAL_ERROR, cause=e)
def list_apps(
self,
*,
workspace_id: uuid.UUID,
type: Optional[str] = None,
visibility: Optional[str] = None,
status: Optional[str] = None,
search: Optional[str] = None,
include_shared: bool = True,
page: int = 1,
pagesize: int = 10,
self,
*,
workspace_id: uuid.UUID,
type: Optional[str] = None,
visibility: Optional[str] = None,
status: Optional[str] = None,
search: Optional[str] = None,
include_shared: bool = True,
page: int = 1,
pagesize: int = 10,
) -> Tuple[List[App], int]:
"""列出工作空间中的应用(分页)
@@ -759,8 +758,7 @@ class AppService:
)
# 构建查询条件
filters = []
filters.append(App.is_active == True)
filters = [App.is_active == True]
if type:
filters.append(App.type == type)
if visibility:
@@ -813,9 +811,9 @@ class AppService:
return items, int(total)
def get_apps_by_ids(
self,
app_ids: List[str],
workspace_id: uuid.UUID
self,
app_ids: List[str],
workspace_id: uuid.UUID
) -> List[App]:
"""根据ID列表获取应用
@@ -846,11 +844,11 @@ class AppService:
# ==================== Agent 配置管理 ====================
def update_agent_config(
self,
*,
app_id: uuid.UUID,
data: app_schema.AgentConfigUpdate,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
data: app_schema.AgentConfigUpdate,
workspace_id: Optional[uuid.UUID] = None
) -> AgentConfig:
"""更新 Agent 配置
@@ -875,7 +873,8 @@ class AppService:
self._validate_workspace_access(app, workspace_id)
stmt = select(AgentConfig).where(AgentConfig.app_id == app_id, AgentConfig.is_active==True).order_by(AgentConfig.updated_at.desc())
stmt = select(AgentConfig).where(AgentConfig.app_id == app_id, AgentConfig.is_active == True).order_by(
AgentConfig.updated_at.desc())
agent_cfg: Optional[AgentConfig] = self.db.scalars(stmt).first()
now = datetime.datetime.now()
@@ -918,10 +917,10 @@ class AppService:
return agent_cfg
def get_agent_config(
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> AgentConfig:
"""获取 Agent 配置
@@ -948,7 +947,12 @@ class AppService:
# 只读操作,允许访问共享应用
self._validate_app_accessible(app, workspace_id)
stmt = select(AgentConfig).where(AgentConfig.app_id == app_id, AgentConfig.is_active == True).order_by(AgentConfig.updated_at.desc())
stmt = select(AgentConfig).where(
AgentConfig.app_id == app_id,
AgentConfig.is_active.is_(True)
).order_by(
AgentConfig.updated_at.desc()
)
config = self.db.scalars(stmt).first()
if config:
@@ -1166,13 +1170,13 @@ class AppService:
# ==================== 应用发布管理 ====================
def publish(
self,
*,
app_id: uuid.UUID,
publisher_id: uuid.UUID,
version_name: str,
workspace_id: Optional[uuid.UUID] = None,
release_notes: Optional[str] = None
self,
*,
app_id: uuid.UUID,
publisher_id: uuid.UUID,
version_name: str,
workspace_id: Optional[uuid.UUID] = None,
release_notes: Optional[str] = None
) -> AppRelease:
"""发布应用(创建不可变快照)
@@ -1200,7 +1204,8 @@ class AppService:
default_model_config_id = None
if app.type == AppType.AGENT:
stmt = select(AgentConfig).where(AgentConfig.app_id == app_id, AgentConfig.is_active == True).order_by(AgentConfig.updated_at.desc())
stmt = select(AgentConfig).where(AgentConfig.app_id == app_id, AgentConfig.is_active == True).order_by(
AgentConfig.updated_at.desc())
agent_cfg = self.db.scalars(stmt).first()
if not agent_cfg:
raise BusinessException("Agent 应用缺少配置,无法发布", BizCode.AGENT_CONFIG_MISSING)
@@ -1236,8 +1241,7 @@ class AppService:
default_model_config_id = multi_agent_cfg.default_model_config_id
# 4. 构建配置快照
config = {
"model_parameters": model_parameters_to_dict(multi_agent_cfg.model_parameters),
"master_agent_id": str(multi_agent_cfg.master_agent_id),
@@ -1264,6 +1268,7 @@ class AppService:
raise BusinessException("应用缺少有效配置,无法发布", BizCode.CONFIG_MISSING)
config = {
"id": str(workflow_cfg.id),
"nodes": workflow_cfg.nodes,
"edges": workflow_cfg.edges,
"variables": workflow_cfg.variables,
@@ -1285,7 +1290,7 @@ class AppService:
id=uuid.uuid4(),
app_id=app_id,
version=version,
version_name = version_name,
version_name=version_name,
release_notes=release_notes,
name=app.name,
description=app.description,
@@ -1319,10 +1324,10 @@ class AppService:
return release
def get_current_release(
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> Optional[AppRelease]:
"""获取当前发布版本
@@ -1349,10 +1354,10 @@ class AppService:
return self.db.get(AppRelease, app.current_release_id)
def list_releases(
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> List[AppRelease]:
"""列出应用的所有发布版本(倒序)
@@ -1381,11 +1386,11 @@ class AppService:
return list(self.db.scalars(stmt).all())
def rollback(
self,
*,
app_id: uuid.UUID,
version: int,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
version: int,
workspace_id: Optional[uuid.UUID] = None
) -> AppRelease:
"""回滚到指定版本
@@ -1434,12 +1439,12 @@ class AppService:
# ==================== 应用分享功能 ====================
def share_app(
self,
*,
app_id: uuid.UUID,
target_workspace_ids: List[uuid.UUID],
user_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
target_workspace_ids: List[uuid.UUID],
user_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> AppShare:
"""分享应用到其他工作空间
@@ -1457,7 +1462,6 @@ class AppService:
BusinessException: 当应用不在指定工作空间或目标工作空间无效时
"""
logger.info(
"分享应用",
extra={
@@ -1536,11 +1540,11 @@ class AppService:
return shares
def unshare_app(
self,
*,
app_id: uuid.UUID,
target_workspace_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
target_workspace_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> None:
"""取消应用分享
@@ -1594,10 +1598,10 @@ class AppService:
)
def list_app_shares(
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
workspace_id: Optional[uuid.UUID] = None
) -> List[AppShare]:
"""列出应用的所有分享记录
@@ -1637,14 +1641,14 @@ class AppService:
# ==================== 试运行功能 ====================
async def draft_run(
self,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
) -> Dict[str, Any]:
"""试运行 Agent使用当前草稿配置
@@ -1736,14 +1740,14 @@ class AppService:
return result
async def draft_run_stream(
self,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
self,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
):
"""试运行 Agent流式返回
@@ -1794,30 +1798,30 @@ class AppService:
# 4. 调用流式试运行服务
draft_service = DraftRunService(self.db)
async for event in draft_service.run_stream(
agent_config=agent_cfg,
model_config=model_config,
message=message,
workspace_id=workspace_id,
conversation_id=conversation_id,
user_id=user_id,
variables=variables
agent_config=agent_cfg,
model_config=model_config,
message=message,
workspace_id=workspace_id,
conversation_id=conversation_id,
user_id=user_id,
variables=variables
):
yield event
# ==================== 多模型对比试运行 ====================
async def draft_run_compare(
self,
*,
app_id: uuid.UUID,
message: str,
models: List[app_schema.ModelCompareItem],
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None,
parallel: bool = True,
timeout: int = 60
self,
*,
app_id: uuid.UUID,
message: str,
models: List[app_schema.ModelCompareItem],
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None,
parallel: bool = True,
timeout: int = 60
) -> Dict[str, Any]:
"""多模型对比试运行
@@ -1907,17 +1911,17 @@ class AppService:
return result
async def draft_run_compare_stream(
self,
*,
app_id: uuid.UUID,
message: str,
models: List[app_schema.ModelCompareItem],
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None,
parallel: bool = True,
timeout: int = 60
self,
*,
app_id: uuid.UUID,
message: str,
models: List[app_schema.ModelCompareItem],
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None,
parallel: bool = True,
timeout: int = 60
):
"""多模型对比试运行(流式返回)
@@ -1982,15 +1986,15 @@ class AppService:
# 4. 调用 DraftRunService 的流式对比方法
draft_service = DraftRunService(self.db)
async for event in draft_service.run_compare_stream(
agent_config=agent_cfg,
models=model_configs,
message=message,
workspace_id=workspace_id,
conversation_id=conversation_id,
user_id=user_id,
variables=variables,
parallel=parallel,
timeout=timeout
agent_config=agent_cfg,
models=model_configs,
message=message,
workspace_id=workspace_id,
conversation_id=conversation_id,
user_id=user_id,
variables=variables,
parallel=parallel,
timeout=timeout
):
yield event
@@ -2009,7 +2013,8 @@ def create_app(db: Session, *, user_id: uuid.UUID, workspace_id: uuid.UUID, data
return service.create_app(user_id=user_id, workspace_id=workspace_id, data=data)
def update_app(db: Session, *, app_id: uuid.UUID, data: app_schema.AppUpdate, workspace_id: uuid.UUID | None = None) -> App:
def update_app(db: Session, *, app_id: uuid.UUID, data: app_schema.AppUpdate,
workspace_id: uuid.UUID | None = None) -> App:
"""更新应用(向后兼容接口)"""
service = AppService(db)
return service.update_app(app_id=app_id, data=data, workspace_id=workspace_id)
@@ -2021,12 +2026,15 @@ def delete_app(db: Session, *, app_id: uuid.UUID, workspace_id: uuid.UUID | None
return service.delete_app(app_id=app_id, workspace_id=workspace_id)
def update_agent_config(db: Session, *, app_id: uuid.UUID, data: app_schema.AgentConfigUpdate, workspace_id: uuid.UUID | None = None) -> AgentConfig:
def update_agent_config(db: Session, *, app_id: uuid.UUID, data: app_schema.AgentConfigUpdate,
workspace_id: uuid.UUID | None = None) -> AgentConfig:
"""更新 Agent 配置(向后兼容接口)"""
service = AppService(db)
return service.update_agent_config(app_id=app_id, data=data, workspace_id=workspace_id)
def update_workflow_config(db: Session, *, app_id: uuid.UUID, data: WorkflowConfigUpdate, workspace_id: uuid.UUID | None = None) -> WorkflowConfig:
def update_workflow_config(db: Session, *, app_id: uuid.UUID, data: WorkflowConfigUpdate,
workspace_id: uuid.UUID | None = None) -> WorkflowConfig:
"""更新 Agent 配置(向后兼容接口)"""
service = AppService(db)
return service.update_workflow_config(app_id=app_id, data=data, workspace_id=workspace_id)
@@ -2040,6 +2048,7 @@ def get_agent_config(db: Session, *, app_id: uuid.UUID, workspace_id: uuid.UUID
service = AppService(db)
return service.get_agent_config(app_id=app_id, workspace_id=workspace_id)
def get_workflow_config(db: Session, *, app_id: uuid.UUID, workspace_id: uuid.UUID | None = None) -> WorkflowConfig:
"""获取 Agent 配置(向后兼容接口)
@@ -2049,13 +2058,20 @@ def get_workflow_config(db: Session, *, app_id: uuid.UUID, workspace_id: uuid.UU
return service.get_workflow_config(app_id=app_id, workspace_id=workspace_id)
def publish(db: Session, *, app_id: uuid.UUID, publisher_id: uuid.UUID, workspace_id: uuid.UUID | None = None,version_name:str, release_notes: Optional[str] = None) -> AppRelease:
def publish(db: Session, *, app_id: uuid.UUID, publisher_id: uuid.UUID, workspace_id: uuid.UUID | None = None,
version_name: str, release_notes: Optional[str] = None) -> AppRelease:
"""发布应用(向后兼容接口)"""
service = AppService(db)
return service.publish(app_id=app_id, publisher_id=publisher_id,version_name = version_name, workspace_id=workspace_id, release_notes=release_notes)
return service.publish(app_id=app_id, publisher_id=publisher_id, version_name=version_name,
workspace_id=workspace_id, release_notes=release_notes)
def get_current_release(db: Session, *, app_id: uuid.UUID, workspace_id: uuid.UUID | None = None) -> Optional[AppRelease]:
def get_current_release(
db: Session,
*,
app_id: uuid.UUID,
workspace_id: uuid.UUID | None = None
) -> Optional[AppRelease]:
"""获取当前发布版本(向后兼容接口)"""
service = AppService(db)
return service.get_current_release(app_id=app_id, workspace_id=workspace_id)
@@ -2074,16 +2090,16 @@ def rollback(db: Session, *, app_id: uuid.UUID, version: int, workspace_id: uuid
def list_apps(
db: Session,
*,
workspace_id: uuid.UUID,
type: Optional[str] = None,
visibility: Optional[str] = None,
status: Optional[str] = None,
search: Optional[str] = None,
include_shared: bool = True,
page: int = 1,
pagesize: int = 10,
db: Session,
*,
workspace_id: uuid.UUID,
type: Optional[str] = None,
visibility: Optional[str] = None,
status: Optional[str] = None,
search: Optional[str] = None,
include_shared: bool = True,
page: int = 1,
pagesize: int = 10,
) -> Tuple[List[App], int]:
"""列出应用(向后兼容接口)"""
service = AppService(db)
@@ -2100,9 +2116,9 @@ def list_apps(
def get_apps_by_ids(
db: Session,
app_ids: List[str],
workspace_id: uuid.UUID
db: Session,
app_ids: List[str],
workspace_id: uuid.UUID
) -> List[App]:
"""根据ID列表获取应用向后兼容接口"""
service = AppService(db)
@@ -2112,14 +2128,14 @@ def get_apps_by_ids(
# ==================== 向后兼容的函数接口 ====================
async def draft_run(
db: Session,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
db: Session,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
) -> Dict[str, Any]:
"""试运行 Agent向后兼容接口"""
service = AppService(db)
@@ -2134,30 +2150,28 @@ async def draft_run(
async def draft_run_stream(
db: Session,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
db: Session,
*,
app_id: uuid.UUID,
message: str,
conversation_id: Optional[str] = None,
user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None,
workspace_id: Optional[uuid.UUID] = None
):
"""试运行 Agent 流式返回(向后兼容接口)"""
service = AppService(db)
async for event in service.draft_run_stream(
app_id=app_id,
message=message,
conversation_id=conversation_id,
user_id=user_id,
variables=variables,
workspace_id=workspace_id
app_id=app_id,
message=message,
conversation_id=conversation_id,
user_id=user_id,
variables=variables,
workspace_id=workspace_id
):
yield event
# ==================== 依赖注入函数 ====================
def get_app_service(

View File

@@ -705,3 +705,75 @@ class EmotionAnalyticsService:
health_summary=summary,
suggestions=suggestions
)
async def get_cached_suggestions(
self,
end_user_id: str,
db: Session,
) -> Optional[Dict[str, Any]]:
"""从 Redis 缓存获取个性化情绪建议
Args:
end_user_id: 宿主ID用户组ID
db: 数据库会话(保留参数以保持接口兼容性)
Returns:
Dict: 缓存的建议数据,如果不存在或已过期返回 None
"""
try:
from app.cache.memory.emotion_memory import EmotionMemoryCache
logger.info(f"尝试从 Redis 缓存获取情绪建议: user={end_user_id}")
# 从 Redis 获取缓存
cached_data = await EmotionMemoryCache.get_emotion_suggestions(end_user_id)
if cached_data is None:
logger.info(f"用户 {end_user_id} 的建议缓存不存在或已过期")
return None
logger.info(f"成功从 Redis 缓存获取建议: user={end_user_id}")
return cached_data
except Exception as e:
logger.error(f"从 Redis 缓存获取建议失败: {str(e)}", exc_info=True)
return None
async def save_suggestions_cache(
self,
end_user_id: str,
suggestions_data: Dict[str, Any],
db: Session,
expires_hours: int = 24
) -> None:
"""保存建议到 Redis 缓存
Args:
end_user_id: 宿主ID用户组ID
suggestions_data: 建议数据
db: 数据库会话(保留参数以保持接口兼容性)
expires_hours: 过期时间小时默认24小时
"""
try:
from app.cache.memory.emotion_memory import EmotionMemoryCache
logger.info(f"保存建议到 Redis 缓存: user={end_user_id}, expires={expires_hours}小时")
# 计算过期时间(秒)
expire_seconds = expires_hours * 3600
# 保存到 Redis
success = await EmotionMemoryCache.set_emotion_suggestions(
user_id=end_user_id,
suggestions_data=suggestions_data,
expire=expire_seconds
)
if success:
logger.info(f"建议缓存保存成功: user={end_user_id}")
else:
logger.warning(f"建议缓存保存失败: user={end_user_id}")
except Exception as e:
logger.error(f"保存建议缓存失败: {str(e)}", exc_info=True)
# 不抛出异常,缓存失败不应影响主流程

View File

@@ -11,6 +11,22 @@ from app.repositories.home_page_repository import HomePageRepository
from app.schemas.home_page_schema import HomeStatistics, WorkspaceInfo
class HomePageService:
DEFAULT_RETURN_DATA: Dict[str, Any] = {
"message": "",
"introduction": {
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
},
"introduction_en": {
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
}
@staticmethod
def get_home_statistics(db: Session, tenant_id: UUID) -> HomeStatistics:
@@ -82,60 +98,36 @@ class HomePageService:
:param version: 系统版本号(如 "0.2.0"
:return: 对应版本的详细介绍
"""
# 1. 定义 JSON 文件路径(使用 Path 处理跨平台路径问题
json_file_path = Path(__file__).parent.parent / "version_info.json"
# 转换为绝对路径,便于调试
json_abs_path = json_file_path.resolve()
# 2. 定义 JSON 文件路径(简化路径处理,保留绝对路径调试特性
json_abs_path = Path(__file__).parent.parent / "version_info.json"
json_abs_path = json_abs_path.resolve()
# 3. 初始化返回结果(深拷贝默认模板,避免修改原常量)
from copy import deepcopy
result = deepcopy(HomePageService.DEFAULT_RETURN_DATA)
try:
# 2. 读取 JSON 文件
# 4. 简化文件存在性判断(合并逻辑,减少分支)
if not json_abs_path.exists():
return {
"message": f"版本介绍文件不存在:{json_abs_path}",
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
result["message"] = f"版本介绍文件不存在:{json_abs_path}"
return result
# 5. 读取并解析 JSON 文件(简化文件操作流程)
with open(json_abs_path, "r", encoding="utf-8") as f:
changelogs = json.load(f)
# 3. 匹配对应版本的介绍,若版本不存在返回默认提示
if version not in changelogs:
return {
"message": f"暂未查询到 {version} 版本的详细介绍",
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
return changelogs[version]
# 6. 简化版本匹配逻辑,直接返回结果或更新提示信息
if version in changelogs:
return changelogs[version]
result["message"] = f"暂未查询到 {version} 版本的详细介绍"
return result
except FileNotFoundError as e:
# 处理文件不存在异常
return {
"message": f"系统内部错误:{str(e)}",
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
result["message"] = f"系统内部错误:{str(e)}"
return result
except json.JSONDecodeError:
# 处理 JSON 格式错误
return {
"message": "版本介绍文件格式错误,无法解析 JSON",
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
result["message"] = "版本介绍文件格式错误,无法解析 JSON"
return result
except Exception as e:
# 处理其他未知异常
return {
"message": f"加载版本介绍失败:{str(e)}",
"codeName": "",
"releaseDate": "",
"upgradePosition": "",
"coreUpgrades": []
}
result["message"] = f"加载版本介绍失败:{str(e)}"
return result

View File

@@ -7,6 +7,7 @@ user profiles from memory summaries.
"""
import logging
import asyncio
from datetime import datetime
from typing import List, Optional
@@ -372,4 +373,114 @@ class ImplicitMemoryService:
except Exception as e:
logger.error(f"Failed to get behavior habits for user {user_id}: {e}")
raise
async def generate_complete_profile(
self,
user_id: str
) -> dict:
"""生成完整的用户画像包含所有4个模块
Args:
user_id: 用户ID
Returns:
Dict: 包含所有模块的完整画像数据
"""
logger.info(f"生成完整用户画像: user={user_id}")
try:
# 并行调用4个分析方法
preferences, portrait, interest_areas, habits = await asyncio.gather(
self.get_preference_tags(user_id=user_id),
self.get_dimension_portrait(user_id=user_id),
self.get_interest_area_distribution(user_id=user_id),
self.get_behavior_habits(user_id=user_id)
)
# 转换为可序列化的格式
profile_data = {
"preferences": [tag.model_dump(mode='json') for tag in preferences],
"portrait": portrait.model_dump(mode='json'),
"interest_areas": interest_areas.model_dump(mode='json'),
"habits": [habit.model_dump(mode='json') for habit in habits]
}
logger.info(f"完整用户画像生成完成: user={user_id}")
return profile_data
except Exception as e:
logger.error(f"生成完整用户画像失败: {str(e)}", exc_info=True)
raise
async def get_cached_profile(
self,
end_user_id: str,
db: Session
) -> Optional[dict]:
"""从 Redis 缓存获取完整用户画像
Args:
end_user_id: 终端用户ID
db: 数据库会话(保留参数以保持接口兼容性)
Returns:
Dict: 缓存的画像数据,如果不存在或已过期返回 None
"""
try:
from app.cache.memory.implicit_memory import ImplicitMemoryCache
logger.info(f"尝试从 Redis 缓存获取用户画像: user={end_user_id}")
# 从 Redis 获取缓存
cached_data = await ImplicitMemoryCache.get_user_profile(end_user_id)
if cached_data is None:
logger.info(f"用户 {end_user_id} 的画像缓存不存在或已过期")
return None
logger.info(f"成功从 Redis 缓存获取用户画像: user={end_user_id}")
return cached_data
except Exception as e:
logger.error(f"从 Redis 缓存获取用户画像失败: {str(e)}", exc_info=True)
return None
async def save_profile_cache(
self,
end_user_id: str,
profile_data: dict,
db: Session,
expires_hours: int = 168 # 默认7天
) -> None:
"""保存用户画像到 Redis 缓存
Args:
end_user_id: 终端用户ID
profile_data: 画像数据
db: 数据库会话(保留参数以保持接口兼容性)
expires_hours: 过期时间小时默认168小时7天
"""
try:
from app.cache.memory.implicit_memory import ImplicitMemoryCache
logger.info(f"保存用户画像到 Redis 缓存: user={end_user_id}, expires={expires_hours}小时")
# 计算过期时间(秒)
expire_seconds = expires_hours * 3600
# 保存到 Redis
success = await ImplicitMemoryCache.set_user_profile(
user_id=end_user_id,
profile_data=profile_data,
expire=expire_seconds
)
if success:
logger.info(f"用户画像缓存保存成功: user={end_user_id}")
else:
logger.warning(f"用户画像缓存保存失败: user={end_user_id}")
except Exception as e:
logger.error(f"保存用户画像缓存失败: {str(e)}", exc_info=True)
# 不抛出异常,缓存失败不应影响主流程

View File

@@ -4,7 +4,7 @@
import datetime
import logging
import uuid
from typing import Any, Annotated, AsyncGenerator
from typing import Any, Annotated, AsyncGenerator, Optional
from deprecated import deprecated
from fastapi import Depends
@@ -14,15 +14,14 @@ from app.core.error_codes import BizCode
from app.core.exceptions import BusinessException
from app.core.workflow.validator import validate_workflow_config
from app.db import get_db
from app.models.conversation_model import Message
from app.models.workflow_model import WorkflowConfig, WorkflowExecution
from app.repositories.conversation_repository import MessageRepository
from app.repositories.workflow_repository import (
WorkflowConfigRepository,
WorkflowExecutionRepository,
WorkflowNodeExecutionRepository
)
from app.schemas import DraftRunRequest
from app.services.conversation_service import ConversationService
from app.services.multi_agent_service import convert_uuids_to_str
logger = logging.getLogger(__name__)
@@ -36,7 +35,7 @@ class WorkflowService:
self.config_repo = WorkflowConfigRepository(db)
self.execution_repo = WorkflowExecutionRepository(db)
self.node_execution_repo = WorkflowNodeExecutionRepository(db)
self.message_repo = MessageRepository(db)
self.conversation_service = ConversationService(db)
# ==================== 配置管理 ====================
@@ -266,6 +265,7 @@ class WorkflowService:
workflow_config_id: uuid.UUID,
app_id: uuid.UUID,
trigger_type: str,
release_id: uuid.UUID | None = None,
triggered_by: uuid.UUID | None = None,
conversation_id: uuid.UUID | None = None,
input_data: dict[str, Any] | None = None
@@ -273,6 +273,7 @@ class WorkflowService:
"""创建工作流执行记录
Args:
release_id: 应用发布 ID
workflow_config_id: 工作流配置 ID
app_id: 应用 ID
trigger_type: 触发类型
@@ -289,6 +290,7 @@ class WorkflowService:
execution = WorkflowExecution(
workflow_config_id=workflow_config_id,
app_id=app_id,
release_id=release_id,
conversation_id=conversation_id,
execution_id=execution_id,
trigger_type=trigger_type,
@@ -337,6 +339,7 @@ class WorkflowService:
self,
execution_id: str,
status: str,
token_usage: int | None = None,
output_data: dict[str, Any] | None = None,
error_message: str | None = None,
error_node_id: str | None = None
@@ -346,6 +349,7 @@ class WorkflowService:
Args:
execution_id: 执行 ID
status: 状态
token_usage: token消耗
output_data: 输出数据
error_message: 错误信息
error_node_id: 出错节点 ID
@@ -364,6 +368,8 @@ class WorkflowService:
)
execution.status = status
if token_usage is not None:
execution.token_usage = token_usage
if output_data is not None:
execution.output_data = convert_uuids_to_str(output_data)
if error_message is not None:
@@ -414,12 +420,14 @@ class WorkflowService:
payload: DraftRunRequest,
config: WorkflowConfig,
workspace_id: uuid.UUID,
release_id: uuid.UUID | None = None,
):
"""运行工作流
Args:
workspace_id:
config:
release_id: 发布 ID
workspace_id:工作空间 ID
config: 配置
payload:
app_id: 应用 ID
@@ -463,7 +471,8 @@ class WorkflowService:
trigger_type="manual",
triggered_by=None,
conversation_id=conversation_id_uuid,
input_data=input_data
input_data=input_data,
release_id=release_id,
)
# 3. 构建工作流配置字典
@@ -507,20 +516,20 @@ class WorkflowService:
# 更新执行结果
if result.get("status") == "completed":
token_usage = result.get("token_usage", {}) or {}
self.update_execution_status(
execution.execution_id,
"completed",
output_data=result
output_data=result,
token_usage=token_usage.get("total_tokens", None)
)
final_messages = result.get("messages", [])[init_message_length:]
for message in final_messages:
message_obj = Message(
self.conversation_service.add_message(
conversation_id=conversation_id_uuid,
role=message["role"],
content=message["content"],
content=message["content"]
)
self.message_repo.add_message(message_obj)
self.db.commit()
logger.info(f"Workflow Run Success, "
f"execution_id: {execution.execution_id}, message count: {len(final_messages)}")
else:
@@ -562,10 +571,12 @@ class WorkflowService:
payload: DraftRunRequest,
config: WorkflowConfig,
workspace_id: uuid.UUID,
release_id: Optional[uuid.UUID] = None,
):
"""运行工作流(流式)
Args:
release_id: 发布id
workspace_id:
app_id: 应用 ID
payload: 请求对象(包含 message, variables, conversation_id 等)
@@ -611,7 +622,8 @@ class WorkflowService:
trigger_type="manual",
triggered_by=None,
conversation_id=conversation_id_uuid,
input_data=input_data
input_data=input_data,
release_id=release_id,
)
# 3. 构建工作流配置字典
@@ -653,21 +665,21 @@ class WorkflowService:
if event.get("event") == "workflow_end":
status = event.get("data", {}).get("status")
token_usage = event.get("data", {}).get("token_usage", {}) or {}
if status == "completed":
self.update_execution_status(
execution.execution_id,
"completed",
output_data=event.get("data")
output_data=event.get("data"),
token_usage=token_usage.get("total_tokens", None)
)
final_messages = event.get("data", {}).get("messages", [])[init_message_length:]
for message in final_messages:
message_obj = Message(
self.conversation_service.add_message(
conversation_id=conversation_id_uuid,
role=message["role"],
content=message["content"],
content=message["content"]
)
self.message_repo.add_message(message_obj)
self.db.commit()
logger.info(f"Workflow Run Success, "
f"execution_id: {execution.execution_id}, message count: {len(final_messages)}")
elif status == "failed":
@@ -784,10 +796,12 @@ class WorkflowService:
# 更新执行结果
if result.get("status") == "completed":
token_usage = result.get("data").get("token_usage", {}) or {}
self.update_execution_status(
execution.execution_id,
"completed",
output_data=result.get("node_outputs", {})
output_data=result.get("node_outputs", {}),
token_usage=token_usage.get("total_tokens", None)
)
else:
self.update_execution_status(
@@ -882,13 +896,14 @@ class WorkflowService:
):
# 直接转发事件executor 已经返回正确格式)
if event.get("event") == "workflow_end":
token_usage = event.get("data").get("token_usage", {}) or {}
status = event.get("data", {}).get("status")
if status == "completed":
self.update_execution_status(
execution_id,
"completed",
output_data=event.get("data")
output_data=event.get("data"),
token_usage=token_usage.get("total_tokens", None)
)
elif status == "failed":
self.update_execution_status(