feat(app): add API to retrieve app configuration fields

This commit is contained in:
Eternity
2026-03-03 10:27:01 +08:00
parent 7cec966979
commit 07fea23dd0
3 changed files with 243 additions and 214 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

@@ -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,10 +33,10 @@ 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
) -> tuple[ReleaseShare, AppRelease]: ) -> tuple[ReleaseShare, AppRelease]:
"""通过 share_token 获取发布版本""" """通过 share_token 获取发布版本"""
# 获取分享配置 # 获取分享配置
@@ -69,14 +70,14 @@ class SharedChatService:
return share, release return share, release
def create_or_get_conversation( def create_or_get_conversation(
self, self,
share_token: str, share_token: str,
conversation_id: Optional[uuid.UUID] = None, conversation_id: Optional[uuid.UUID] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
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:
@@ -118,21 +119,21 @@ class SharedChatService:
return conversation return conversation
async def chat( async def chat(
self, self,
share_token: str, share_token: str,
message: str, message: str,
conversation_id: Optional[uuid.UUID] = None, conversation_id: Optional[uuid.UUID] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None, variables: Optional[Dict[str, Any]] = None,
password: Optional[str] = None, password: Optional[str] = None,
web_search: bool = False, web_search: bool = False,
memory: bool = True, memory: bool = True,
storage_type: Optional[str] = None, storage_type: Optional[str] = None,
user_rag_memory_id: Optional[str] = None, user_rag_memory_id: Optional[str] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""聊天(非流式)""" """聊天(非流式)"""
actual_config_id = None actual_config_id = None
config_id=actual_config_id config_id = actual_config_id
from app.core.agent.langchain_agent import LangChainAgent from app.core.agent.langchain_agent import LangChainAgent
from app.services.draft_run_service import create_knowledge_retrieval_tool, create_long_term_memory_tool from app.services.draft_run_service import create_knowledge_retrieval_tool, create_long_term_memory_tool
from app.services.model_parameter_merger import ModelParameterMerger from app.services.model_parameter_merger import ModelParameterMerger
@@ -140,21 +141,19 @@ 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
if variables is None: if variables is None:
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:
@@ -212,21 +211,21 @@ class SharedChatService:
knowledge_bases = knowledge_retrieval.get("knowledge_bases", []) knowledge_bases = knowledge_retrieval.get("knowledge_bases", [])
kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")] kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")]
if kb_ids: if kb_ids:
kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids,user_id) kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids, user_id)
tools.append(kb_tool) tools.append(kb_tool)
# 添加长期记忆工具 # 添加长期记忆工具
memory_flag=False memory_flag = False
if memory: if memory:
memory_config = config.get("memory", {}) memory_config = config.get("memory", {})
if memory_config.get("enabled") and user_id: if memory_config.get("enabled") and user_id:
memory_flag=True memory_flag = True
memory_tool = create_long_term_memory_tool(memory_config, user_id) memory_tool = create_long_term_memory_tool(memory_config, user_id)
tools.append(memory_tool) tools.append(memory_tool)
web_tools=config.get("tools") web_tools = config.get("tools")
web_search_choice = web_tools.get("web_search", {}) web_search_choice = web_tools.get("web_search", {})
web_search_enable = web_search_choice.get("enabled",False) web_search_enable = web_search_choice.get("enabled", False)
if web_search: if web_search:
if web_search_enable: if web_search_enable:
search_tool = create_web_search_tool({}) search_tool = create_web_search_tool({})
@@ -257,7 +256,7 @@ class SharedChatService:
# 加载历史消息 # 加载历史消息
history = [] history = []
memory_config={"enabled":True,'max_history':10} memory_config = {"enabled": True, 'max_history': 10}
if memory_config.get("enabled"): if memory_config.get("enabled"):
messages = self.conversation_service.get_messages( messages = self.conversation_service.get_messages(
conversation_id=conversation.id, conversation_id=conversation.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"],
@@ -326,17 +324,17 @@ class SharedChatService:
} }
async def chat_stream( async def chat_stream(
self, self,
share_token: str, share_token: str,
message: str, message: str,
conversation_id: Optional[uuid.UUID] = None, conversation_id: Optional[uuid.UUID] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None, variables: Optional[Dict[str, Any]] = None,
password: Optional[str] = None, password: Optional[str] = None,
web_search: bool = False, web_search: bool = False,
memory: bool = True, memory: bool = True,
storage_type:Optional[str] = None, storage_type: Optional[str] = None,
user_rag_memory_id: Optional[str] = None, user_rag_memory_id: Optional[str] = None,
) -> AsyncGenerator[str, None]: ) -> AsyncGenerator[str, None]:
"""聊天(流式)""" """聊天(流式)"""
from app.core.agent.langchain_agent import LangChainAgent from app.core.agent.langchain_agent import LangChainAgent
@@ -347,9 +345,8 @@ class SharedChatService:
import json import json
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
if variables is None: if variables is None:
variables = {} variables = {}
@@ -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 {}
@@ -421,11 +418,11 @@ class SharedChatService:
knowledge_bases = knowledge_retrieval.get("knowledge_bases", []) knowledge_bases = knowledge_retrieval.get("knowledge_bases", [])
kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")] kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")]
if kb_ids: if kb_ids:
kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids,user_id) kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids, user_id)
tools.append(kb_tool) tools.append(kb_tool)
# 添加长期记忆工具 # 添加长期记忆工具
memory_flag=False memory_flag = False
if memory: if memory:
memory_config = config.get("memory", {}) memory_config = config.get("memory", {})
if memory_config.get("enabled") and user_id: if memory_config.get("enabled") and user_id:
@@ -484,14 +481,14 @@ class SharedChatService:
full_content = "" full_content = ""
total_tokens = 0 total_tokens = 0
async for chunk in agent.chat_stream( async for chunk in agent.chat_stream(
message=message, message=message,
history=history, history=history,
context=None, context=None,
end_user_id=user_id, end_user_id=user_id,
storage_type=storage_type, storage_type=storage_type,
user_rag_memory_id=user_rag_memory_id, user_rag_memory_id=user_rag_memory_id,
config_id=config_id, config_id=config_id,
memory_flag=memory_flag memory_flag=memory_flag
): ):
if isinstance(chunk, int): if isinstance(chunk, int):
total_tokens = chunk total_tokens = chunk
@@ -544,13 +541,13 @@ class SharedChatService:
yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n" yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
def get_conversation_messages( def get_conversation_messages(
self, self,
share_token: str, share_token: str,
conversation_id: uuid.UUID, conversation_id: uuid.UUID,
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(
@@ -565,15 +562,15 @@ class SharedChatService:
return conversation return conversation
def list_conversations( def list_conversations(
self, self,
share_token: str, share_token: str,
user_id: Optional[str] = None, user_id: Optional[str] = None,
password: Optional[str] = None, password: Optional[str] = None,
page: int = 1, page: int = 1,
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,
@@ -587,15 +584,15 @@ class SharedChatService:
return conversations, total return conversations, total
async def multi_agent_chat( async def multi_agent_chat(
self, self,
share_token: str, share_token: str,
message: str, message: str,
conversation_id: Optional[uuid.UUID] = None, conversation_id: Optional[uuid.UUID] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None, variables: Optional[Dict[str, Any]] = None,
password: Optional[str] = None, password: Optional[str] = None,
web_search: bool = False, web_search: bool = False,
memory: bool = True, memory: bool = True,
storage_type: Optional[str] = None, storage_type: Optional[str] = None,
user_rag_memory_id: Optional[str] = None user_rag_memory_id: Optional[str] = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
@@ -603,17 +600,15 @@ 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
if variables is None: if variables is None:
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", ""),
@@ -686,31 +679,30 @@ class SharedChatService:
} }
async def multi_agent_chat_stream( async def multi_agent_chat_stream(
self, self,
share_token: str, share_token: str,
message: str, message: str,
conversation_id: Optional[uuid.UUID] = None, conversation_id: Optional[uuid.UUID] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
variables: Optional[Dict[str, Any]] = None, variables: Optional[Dict[str, Any]] = None,
password: Optional[str] = None, password: Optional[str] = None,
web_search: bool = False, web_search: bool = False,
memory: bool = True, memory: bool = True,
storage_type: Optional[str] = None, storage_type: Optional[str] = None,
user_rag_memory_id:Optional[str] = None user_rag_memory_id: Optional[str] = None
) -> 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
if variables is None: if variables is None:
variables = {} variables = {}
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(
@@ -773,10 +765,10 @@ class SharedChatService:
full_content = "" full_content = ""
async for event in multi_agent_service.run_stream( async for event in multi_agent_service.run_stream(
app_id=release.app_id, app_id=release.app_id,
request=multi_agent_request, request=multi_agent_request,
storage_type=storage_type, storage_type=storage_type,
user_rag_memory_id=user_rag_memory_id user_rag_memory_id=user_rag_memory_id
): ):
# 直接转发事件 # 直接转发事件
yield event yield event
@@ -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]:
"""清理事件数据,移除不可序列化的对象 """清理事件数据,移除不可序列化的对象