Merge pull request #434 from SuanmoSuanyangTechnology/feature/app-share-config

feat(app): add API to retrieve app configuration fields
This commit is contained in:
Mark
2026-03-03 11:25:35 +08:00
committed by GitHub
4 changed files with 244 additions and 215 deletions

View File

@@ -2,25 +2,32 @@ import hashlib
import json import json
import uuid import uuid
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Depends, Query, Request from fastapi import APIRouter, Depends, Query, Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.error_codes import BizCode
from app.core.exceptions import BusinessException
from app.core.logging_config import get_business_logger from app.core.logging_config import get_business_logger
from app.core.response_utils import success from app.core.response_utils import success, fail
from app.db import get_db, get_db_read from app.db import get_db, get_db_read
from app.dependencies import get_share_user_id, ShareTokenData from app.dependencies import get_share_user_id, ShareTokenData
from app.models.app_model import App
from app.models.app_model import AppType
from app.repositories import knowledge_repository from app.repositories import knowledge_repository
from app.repositories.end_user_repository import EndUserRepository
from app.repositories.workflow_repository import WorkflowConfigRepository from app.repositories.workflow_repository import WorkflowConfigRepository
from app.schemas import release_share_schema, conversation_schema from app.schemas import release_share_schema, conversation_schema
from app.schemas.response_schema import PageData, PageMeta from app.schemas.response_schema import PageData, PageMeta
from app.services import workspace_service from app.services import workspace_service
from app.services.app_chat_service import AppChatService, get_app_chat_service
from app.services.auth_service import create_access_token from app.services.auth_service import create_access_token
from app.services.conversation_service import ConversationService from app.services.conversation_service import ConversationService
from app.services.release_share_service import ReleaseShareService from app.services.release_share_service import ReleaseShareService
from app.services.shared_chat_service import SharedChatService from app.services.shared_chat_service import SharedChatService
from app.services.app_chat_service import AppChatService, get_app_chat_service from app.services.workflow_service import WorkflowService
from app.utils.app_config_utils import dict_to_multi_agent_config, workflow_config_4_app_release, \ from app.utils.app_config_utils import workflow_config_4_app_release, \
agent_config_4_app_release, multi_agent_config_4_app_release agent_config_4_app_release, multi_agent_config_4_app_release
router = APIRouter(prefix="/public/share", tags=["Public Share"]) router = APIRouter(prefix="/public/share", tags=["Public Share"])
@@ -206,15 +213,13 @@ def list_conversations(
logger.debug(f"share_data:{share_data.user_id}") logger.debug(f"share_data:{share_data.user_id}")
other_id = share_data.user_id other_id = share_data.user_id
service = SharedChatService(db) service = SharedChatService(db)
share, release = service._get_release_by_share_token(share_data.share_token, password) share, release = service.get_release_by_share_token(share_data.share_token, password)
from app.repositories.end_user_repository import EndUserRepository
end_user_repo = EndUserRepository(db) end_user_repo = EndUserRepository(db)
new_end_user = end_user_repo.get_or_create_end_user( new_end_user = end_user_repo.get_or_create_end_user(
app_id=share.app_id, app_id=share.app_id,
other_id=other_id other_id=other_id
) )
logger.debug(new_end_user.id) logger.debug(new_end_user.id)
service = SharedChatService(db)
conversations, total = service.list_conversations( conversations, total = service.list_conversations(
share_token=share_data.share_token, share_token=share_data.share_token,
user_id=str(new_end_user.id), user_id=str(new_end_user.id),
@@ -293,19 +298,15 @@ async def chat(
# 提前验证和准备(在流式响应开始前完成) # 提前验证和准备(在流式响应开始前完成)
# 这样可以确保错误能正确返回,而不是在流式响应中间出错 # 这样可以确保错误能正确返回,而不是在流式响应中间出错
from app.models.app_model import AppType
try: try:
from app.core.exceptions import BusinessException
from app.core.error_codes import BizCode
from app.services.app_service import AppService
# 验证分享链接和密码 # 验证分享链接和密码
share, release = service._get_release_by_share_token(share_token, password) share, release = service.get_release_by_share_token(share_token, password)
# # Create end_user_id by concatenating app_id with user_id # # Create end_user_id by concatenating app_id with user_id
# end_user_id = f"{share.app_id}_{user_id}" # end_user_id = f"{share.app_id}_{user_id}"
# Store end_user_id in database with original user_id # Store end_user_id in database with original user_id
from app.repositories.end_user_repository import EndUserRepository
end_user_repo = EndUserRepository(db) end_user_repo = EndUserRepository(db)
new_end_user = end_user_repo.get_or_create_end_user( new_end_user = end_user_repo.get_or_create_end_user(
app_id=share.app_id, app_id=share.app_id,
@@ -318,7 +319,6 @@ async def chat(
"""获取存储类型和工作空间的ID""" """获取存储类型和工作空间的ID"""
# 直接通过 SQLAlchemy 查询 app仅查询未删除的应用 # 直接通过 SQLAlchemy 查询 app仅查询未删除的应用
from app.models.app_model import App
app = db.query(App).filter( app = db.query(App).filter(
App.id == appid, App.id == appid,
App.is_active.is_(True) App.is_active.is_(True)
@@ -359,12 +359,12 @@ async def chat(
app_type = release.app.type if release.app else None app_type = release.app.type if release.app else None
# 根据应用类型验证配置 # 根据应用类型验证配置
if app_type == "agent": if app_type == AppType.AGENT:
# Agent 类型:验证模型配置 # Agent 类型:验证模型配置
model_config_id = release.default_model_config_id model_config_id = release.default_model_config_id
if not model_config_id: if not model_config_id:
raise BusinessException("Agent 应用未配置模型", BizCode.AGENT_CONFIG_MISSING) raise BusinessException("Agent 应用未配置模型", BizCode.AGENT_CONFIG_MISSING)
elif app_type == "multi_agent": elif app_type == AppType.MULTI_AGENT:
# Multi-Agent 类型:验证多 Agent 配置 # Multi-Agent 类型:验证多 Agent 配置
config = release.config or {} config = release.config or {}
if not config.get("sub_agents"): if not config.get("sub_agents"):
@@ -638,6 +638,34 @@ async def chat(
# return success(data=conversation_schema.ChatResponse(**result).model_dump(mode="json")) # return success(data=conversation_schema.ChatResponse(**result).model_dump(mode="json"))
else: else:
from app.core.exceptions import BusinessException
from app.core.error_codes import BizCode
raise BusinessException(f"不支持的应用类型: {app_type}", BizCode.APP_TYPE_NOT_SUPPORTED) raise BusinessException(f"不支持的应用类型: {app_type}", BizCode.APP_TYPE_NOT_SUPPORTED)
@router.get("/config", summary="获取应用启动配置")
async def config_query(
password: str = Query(None, description="访问密码"),
share_data: ShareTokenData = Depends(get_share_user_id),
db: Session = Depends(get_db),
):
share_service = SharedChatService(db)
share_token = share_data.share_token
share, release = share_service.get_release_by_share_token(share_token, password)
if release.app.type == AppType.WORKFLOW:
workflow_service = WorkflowService(db)
content = {
"app_type": release.app.type,
"variables": workflow_service.get_start_node_variables(release.config)
}
elif release.app.type == AppType.AGENT:
content = {
"app_type": release.app.type,
"variables": release.config.get("variables")
}
elif release.app.type == AppType.MULTI_AGENT:
content = {
"app_type": release.app.type,
"variables": []
}
else:
return fail(msg="Unsupported app type", code=BizCode.APP_TYPE_NOT_SUPPORTED)
return success(data=content)

View File

@@ -433,7 +433,7 @@ class AppChatRequest(BaseModel):
user_id: Optional[str] = Field(default=None, description="用户ID用于会话管理") user_id: Optional[str] = Field(default=None, description="用户ID用于会话管理")
variables: Optional[Dict[str, Any]] = Field(default=None, description="自定义变量参数值") variables: Optional[Dict[str, Any]] = Field(default=None, description="自定义变量参数值")
stream: bool = Field(default=False, description="是否流式返回") stream: bool = Field(default=False, description="是否流式返回")
files: Optional[List[FileInput]] = Field(default=None, description="附件列表(支持多文件)") files: Optional[List[FileInput]] = Field(default=list, description="附件列表(支持多文件)")
class DraftRunRequest(BaseModel): class DraftRunRequest(BaseModel):

View File

@@ -21,6 +21,7 @@ from app.repositories import knowledge_repository
import json import json
from app.services.task_service import get_task_memory_write_result from app.services.task_service import get_task_memory_write_result
from app.tasks import write_message_task from app.tasks import write_message_task
logger = get_business_logger() logger = get_business_logger()
@@ -32,7 +33,7 @@ class SharedChatService:
self.conversation_service = ConversationService(db) self.conversation_service = ConversationService(db)
self.share_service = ReleaseShareService(db) self.share_service = ReleaseShareService(db)
def _get_release_by_share_token( def get_release_by_share_token(
self, self,
share_token: str, share_token: str,
password: Optional[str] = None password: Optional[str] = None
@@ -76,7 +77,7 @@ class SharedChatService:
password: Optional[str] = None password: Optional[str] = None
) -> Conversation: ) -> Conversation:
"""创建或获取会话""" """创建或获取会话"""
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 如果提供了 conversation_id尝试获取现有会话 # 如果提供了 conversation_id尝试获取现有会话
if conversation_id: if conversation_id:
@@ -140,7 +141,6 @@ class SharedChatService:
from sqlalchemy import select from sqlalchemy import select
from app.models import ModelApiKey from app.models import ModelApiKey
start_time = time.time() start_time = time.time()
actual_config_id = None actual_config_id = None
config_id = actual_config_id config_id = actual_config_id
@@ -149,12 +149,11 @@ class SharedChatService:
variables = {} variables = {}
# 获取发布版本和配置 # 获取发布版本和配置
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 获取 Agent 配置 # 获取 Agent 配置
config = release.config or {} config = release.config or {}
# 获取模型配置ID # 获取模型配置ID
model_config_id = release.default_model_config_id model_config_id = release.default_model_config_id
if not model_config_id: if not model_config_id:
@@ -313,7 +312,6 @@ class SharedChatService:
ModelApiKeyService.record_api_key_usage(self.db, api_key_obj.id) ModelApiKeyService.record_api_key_usage(self.db, api_key_obj.id)
return { return {
"conversation_id": conversation.id, "conversation_id": conversation.id,
"message": result["content"], "message": result["content"],
@@ -350,7 +348,6 @@ class SharedChatService:
actual_config_id = None actual_config_id = None
config_id = actual_config_id config_id = actual_config_id
if variables is None: if variables is None:
variables = {} variables = {}
# 兼容新旧字段名:使用 memory_config_id # 兼容新旧字段名:使用 memory_config_id
@@ -358,7 +355,7 @@ class SharedChatService:
try: try:
# 获取发布版本和配置 # 获取发布版本和配置
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 获取 Agent 配置 # 获取 Agent 配置
config = release.config or {} config = release.config or {}
@@ -550,7 +547,7 @@ class SharedChatService:
password: Optional[str] = None password: Optional[str] = None
) -> Conversation: ) -> Conversation:
"""获取会话消息""" """获取会话消息"""
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 获取会话 # 获取会话
conversation = self.conversation_service.get_conversation( conversation = self.conversation_service.get_conversation(
@@ -573,7 +570,7 @@ class SharedChatService:
pagesize: int = 20 pagesize: int = 20
) -> tuple[list[Conversation], int]: ) -> tuple[list[Conversation], int]:
"""列出会话""" """列出会话"""
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
conversations, total = self.conversation_service.list_conversations( conversations, total = self.conversation_service.list_conversations(
app_id=release.app_id, app_id=release.app_id,
@@ -603,8 +600,6 @@ class SharedChatService:
from app.services.multi_agent_service import MultiAgentService from app.services.multi_agent_service import MultiAgentService
from app.models import MultiAgentConfig from app.models import MultiAgentConfig
start_time = time.time() start_time = time.time()
actual_config_id = None actual_config_id = None
config_id = actual_config_id config_id = actual_config_id
@@ -613,7 +608,7 @@ class SharedChatService:
variables = {} variables = {}
# 获取发布版本和配置 # 获取发布版本和配置
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 获取或创建会话 # 获取或创建会话
conversation = self.create_or_get_conversation( conversation = self.create_or_get_conversation(
@@ -672,8 +667,6 @@ class SharedChatService:
} }
) )
return { return {
"conversation_id": conversation.id, "conversation_id": conversation.id,
"message": result.get("message", ""), "message": result.get("message", ""),
@@ -700,7 +693,6 @@ class SharedChatService:
) -> AsyncGenerator[str, None]: ) -> AsyncGenerator[str, None]:
"""多 Agent 聊天(流式)""" """多 Agent 聊天(流式)"""
start_time = time.time() start_time = time.time()
actual_config_id = None actual_config_id = None
config_id = actual_config_id config_id = actual_config_id
@@ -710,7 +702,7 @@ class SharedChatService:
try: try:
# 获取发布版本和配置 # 获取发布版本和配置
share, release = self._get_release_by_share_token(share_token, password) share, release = self.get_release_by_share_token(share_token, password)
# 获取或创建会话 # 获取或创建会话
conversation = self.create_or_get_conversation( conversation = self.create_or_get_conversation(
@@ -818,7 +810,6 @@ class SharedChatService:
} }
) )
except (GeneratorExit, asyncio.CancelledError): except (GeneratorExit, asyncio.CancelledError):
# 生成器被关闭或任务被取消,正常退出 # 生成器被关闭或任务被取消,正常退出
logger.debug("多 Agent 流式聊天被中断") logger.debug("多 Agent 流式聊天被中断")

View File

@@ -13,6 +13,7 @@ from sqlalchemy.orm import Session
from app.core.error_codes import BizCode from app.core.error_codes import BizCode
from app.core.exceptions import BusinessException from app.core.exceptions import BusinessException
from app.core.workflow.adapters.registry import PlatformAdapterRegistry from app.core.workflow.adapters.registry import PlatformAdapterRegistry
from app.core.workflow.nodes.enums import NodeType
from app.core.workflow.validator import validate_workflow_config from app.core.workflow.validator import validate_workflow_config
from app.db import get_db from app.db import get_db
from app.models import App from app.models import App
@@ -617,7 +618,8 @@ class WorkflowService:
"event": "end", "event": "end",
"data": { "data": {
"elapsed_time": payload.get("elapsed_time"), "elapsed_time": payload.get("elapsed_time"),
"message_length": len(payload.get("output", "")) "message_length": len(payload.get("output", "")),
"error": payload.get("error", "")
} }
} }
case "node_start" | "node_end" | "node_error" | "cycle_item": case "node_start" | "node_end" | "node_error" | "cycle_item":
@@ -779,6 +781,14 @@ class WorkflowService:
} }
} }
@staticmethod
def get_start_node_variables(config: dict) -> list:
nodes = config.get("nodes", [])
for node in nodes:
if node.get("type") == NodeType.START:
return node.get("config", {}).get("variables", [])
raise BusinessException("workflow config error - start node not found")
def _clean_event_for_json(self, event: dict[str, Any]) -> dict[str, Any]: def _clean_event_for_json(self, event: dict[str, Any]) -> dict[str, Any]:
"""清理事件数据,移除不可序列化的对象 """清理事件数据,移除不可序列化的对象