[add] app chat v1
This commit is contained in:
@@ -361,7 +361,8 @@ async def draft_run(
|
|||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
user=current_user
|
user=current_user
|
||||||
)
|
)
|
||||||
if storage_type is None: storage_type = 'neo4j'
|
if storage_type is None:
|
||||||
|
storage_type = 'neo4j'
|
||||||
user_rag_memory_id = ''
|
user_rag_memory_id = ''
|
||||||
if workspace_id:
|
if workspace_id:
|
||||||
|
|
||||||
@@ -370,7 +371,8 @@ async def draft_run(
|
|||||||
name="USER_RAG_MERORY",
|
name="USER_RAG_MERORY",
|
||||||
workspace_id=workspace_id
|
workspace_id=workspace_id
|
||||||
)
|
)
|
||||||
if knowledge: user_rag_memory_id = str(knowledge.id)
|
if knowledge:
|
||||||
|
user_rag_memory_id = str(knowledge.id)
|
||||||
|
|
||||||
|
|
||||||
# 提前验证和准备(在流式响应开始前完成)
|
# 提前验证和准备(在流式响应开始前完成)
|
||||||
|
|||||||
97
api/app/controllers/order_controller.py
Normal file
97
api/app/controllers/order_controller.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from fastapi import APIRouter, Depends, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
import os
|
||||||
|
|
||||||
|
from app.db import get_db
|
||||||
|
from app.dependencies import get_current_user
|
||||||
|
from app.models.user_model import User
|
||||||
|
from app.schemas.order_schema import CreateOrderRequest
|
||||||
|
from app.schemas.response_schema import ApiResponse
|
||||||
|
from app.services.order_service import get_order_service
|
||||||
|
from app.core.logging_config import get_api_logger
|
||||||
|
from app.core.response_utils import success, error
|
||||||
|
|
||||||
|
# Get API logger
|
||||||
|
api_logger = get_api_logger()
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/order",
|
||||||
|
tags=["Order"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("", response_model=ApiResponse)
|
||||||
|
async def create_order(
|
||||||
|
order_data: CreateOrderRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
api_logger.info(f"User {current_user.id} creating order for product {order_data.product_id}")
|
||||||
|
|
||||||
|
# Get external API configuration from environment
|
||||||
|
external_api_url = os.getenv("EXTERNAL_ORDER_API_URL")
|
||||||
|
api_key = os.getenv("EXTERNAL_ORDER_API_KEY")
|
||||||
|
|
||||||
|
# Get order service instance
|
||||||
|
order_service = get_order_service(
|
||||||
|
external_api_url=external_api_url,
|
||||||
|
api_key=api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
# Forward request to external API
|
||||||
|
result = await order_service.create_order(
|
||||||
|
order_data=order_data,
|
||||||
|
user_id=str(current_user.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
api_logger.info(f"Order created successfully: {result.get('order_id')}")
|
||||||
|
|
||||||
|
return success(data=result, msg="Order created successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
api_logger.error(f"Failed to create order: {str(e)}", exc_info=True)
|
||||||
|
return error(msg=str(e), code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{order_id}", response_model=ApiResponse)
|
||||||
|
async def get_order(
|
||||||
|
order_id: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""Get order details from external API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order_id: Order ID
|
||||||
|
db: Database session
|
||||||
|
current_user: Current authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API response with order details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
api_logger.info(f"User {current_user.id} fetching order {order_id}")
|
||||||
|
|
||||||
|
# Get external API configuration
|
||||||
|
external_api_url = os.getenv("EXTERNAL_ORDER_API_URL")
|
||||||
|
api_key = os.getenv("EXTERNAL_ORDER_API_KEY")
|
||||||
|
|
||||||
|
# Get order service instance
|
||||||
|
order_service = get_order_service(
|
||||||
|
external_api_url=external_api_url,
|
||||||
|
api_key=api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch order from external API
|
||||||
|
result = await order_service.get_order(order_id)
|
||||||
|
|
||||||
|
api_logger.info(f"Order {order_id} fetched successfully")
|
||||||
|
|
||||||
|
return success(data=result, msg="Order fetched successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
api_logger.error(f"Failed to fetch order {order_id}: {str(e)}", exc_info=True)
|
||||||
|
return error(msg=str(e), code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
@@ -2,14 +2,30 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from fastapi import APIRouter, Depends, Request, Body
|
from fastapi import APIRouter, Depends, Request, Body
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import Optional, Annotated
|
||||||
|
|
||||||
|
from starlette.responses import StreamingResponse
|
||||||
|
|
||||||
|
from app.core.api_key_auth import require_api_key
|
||||||
from app.db import get_db
|
from app.db import get_db
|
||||||
from app.core.response_utils import success
|
from app.core.response_utils import success
|
||||||
from app.core.logging_config import get_business_logger
|
from app.core.logging_config import get_business_logger
|
||||||
from app.core.api_key_auth import require_api_key
|
from app.dependencies import get_app_or_workspace
|
||||||
from app.schemas.api_key_schema import ApiKeyAuth
|
from app.repositories import knowledge_repository
|
||||||
|
from app.schemas import AppChatRequest, conversation_schema
|
||||||
|
from app.models.app_model import App
|
||||||
|
from app.models.app_model import AppType
|
||||||
|
from app.repositories.end_user_repository import EndUserRepository
|
||||||
|
from app.core.exceptions import BusinessException
|
||||||
|
from app.core.error_codes import BizCode
|
||||||
|
from app.services import workspace_service
|
||||||
|
from app.services.app_chat_service import AppChatService, get_app_chat_service
|
||||||
|
from app.services.app_service import AppService
|
||||||
|
from app.services.conversation_service import ConversationService, get_conversation_service
|
||||||
|
from app.services.workflow_service import WorkflowService, get_workflow_service
|
||||||
|
from app.utils.app_config_utils import dict_to_multi_agent_config,dict_to_agent_config,dict_to_workflow_config
|
||||||
|
|
||||||
router = APIRouter(prefix="/apps", tags=["V1 - App API"])
|
router = APIRouter(prefix="/app", tags=["V1 - App API"])
|
||||||
logger = get_business_logger()
|
logger = get_business_logger()
|
||||||
|
|
||||||
|
|
||||||
@@ -19,28 +35,232 @@ async def list_apps():
|
|||||||
return success(data=[], msg="App API - Coming Soon")
|
return success(data=[], msg="App API - Coming Soon")
|
||||||
|
|
||||||
# /v1/apps/{resource_id}/chat
|
# /v1/apps/{resource_id}/chat
|
||||||
@router.post("/{resource_id}/chat")
|
|
||||||
@require_api_key(scopes=["app"])
|
|
||||||
async def chat_with_agent_demo(
|
# async def chat(
|
||||||
resource_id: uuid.UUID,
|
# request: Request,
|
||||||
request: Request,
|
# api_key_auth: ApiKeyAuth = None,
|
||||||
api_key_auth: ApiKeyAuth = None,
|
# db: Session = Depends(get_db),
|
||||||
|
# message: str = Body(..., description="聊天消息内容"),
|
||||||
|
# ):
|
||||||
|
# """
|
||||||
|
# Agent 聊天接口demo
|
||||||
|
|
||||||
|
# scopes: 所需的权限范围列表["app", "rag", "memory"]
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# resource_id: 如果是应用的apikey传的是应用id; 如果是服务的apikey传的是工作空间id
|
||||||
|
# message: 请求参数
|
||||||
|
# request: 声明请求
|
||||||
|
# api_key_auth: 包含验证后的API Key 信息
|
||||||
|
# db: db_session
|
||||||
|
# """
|
||||||
|
# logger.info(f"API Key Auth: {api_key_auth}")
|
||||||
|
# logger.info(f"Resource ID: {resource_id}")
|
||||||
|
# logger.info(f"Message: {message}")
|
||||||
|
# return success(data={"received": True}, msg="消息已接收")
|
||||||
|
|
||||||
|
|
||||||
|
def _checkAppConfig(app: App):
|
||||||
|
if app.type == AppType.AGENT:
|
||||||
|
if not app.current_release.config:
|
||||||
|
raise BusinessException("Agent 应用未配置模型", BizCode.AGENT_CONFIG_MISSING)
|
||||||
|
elif app.type == AppType.MULTI_AGENT:
|
||||||
|
if not app.current_release.config:
|
||||||
|
raise BusinessException("Multi-Agent 应用未配置模型", BizCode.AGENT_CONFIG_MISSING)
|
||||||
|
elif app.type == AppType.WORKFLOW:
|
||||||
|
if not app.current_release.config:
|
||||||
|
raise BusinessException("工作流应用未配置模型", BizCode.AGENT_CONFIG_MISSING)
|
||||||
|
else:
|
||||||
|
raise BusinessException("不支持的应用类型", BizCode.AGENT_CONFIG_MISSING)
|
||||||
|
|
||||||
|
@router.post("/chat")
|
||||||
|
# @require_api_key(scopes=["app"])
|
||||||
|
async def chat(
|
||||||
|
payload: AppChatRequest,
|
||||||
|
app: App = Depends(get_app_or_workspace),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
message: str = Body(..., description="聊天消息内容"),
|
conversation_service: Annotated[ConversationService, Depends(get_conversation_service)] = None,
|
||||||
|
app_chat_service: Annotated[AppChatService, Depends(get_app_chat_service)] = None,
|
||||||
|
|
||||||
):
|
):
|
||||||
"""
|
other_id = payload.user_id
|
||||||
Agent 聊天接口demo
|
workspace_id = app.workspace_id
|
||||||
|
end_user_repo = EndUserRepository(db)
|
||||||
|
new_end_user = end_user_repo.get_or_create_end_user(
|
||||||
|
app_id=app.id,
|
||||||
|
other_id=other_id,
|
||||||
|
original_user_id=other_id # Save original user_id to other_id
|
||||||
|
)
|
||||||
|
end_user_id = str(new_end_user.id)
|
||||||
|
|
||||||
scopes: 所需的权限范围列表["app", "rag", "memory"]
|
# 提前验证和准备(在流式响应开始前完成)
|
||||||
|
storage_type = workspace_service.get_workspace_storage_type_without_auth(
|
||||||
|
db=db,
|
||||||
|
workspace_id=workspace_id
|
||||||
|
)
|
||||||
|
if storage_type is None:
|
||||||
|
storage_type = 'neo4j'
|
||||||
|
user_rag_memory_id = ''
|
||||||
|
if storage_type == 'rag':
|
||||||
|
if workspace_id:
|
||||||
|
knowledge = knowledge_repository.get_knowledge_by_name(
|
||||||
|
db=db,
|
||||||
|
name="USER_RAG_MERORY",
|
||||||
|
workspace_id=workspace_id
|
||||||
|
)
|
||||||
|
if knowledge:
|
||||||
|
user_rag_memory_id = str(knowledge.id)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"未找到名为 'USER_RAG_MERORY' 的知识库,workspace_id: {workspace_id},将使用 neo4j 存储")
|
||||||
|
storage_type = 'neo4j'
|
||||||
|
else:
|
||||||
|
logger.warning("workspace_id 为空,无法使用 rag 存储,将使用 neo4j 存储")
|
||||||
|
storage_type = 'neo4j'
|
||||||
|
app_type = app.type
|
||||||
|
# check app config
|
||||||
|
_checkAppConfig(app)
|
||||||
|
|
||||||
Args:
|
# 获取或创建会话(提前验证)
|
||||||
resource_id: 如果是应用的apikey传的是应用id; 如果是服务的apikey传的是工作空间id
|
conversation = conversation_service.create_or_get_conversation(
|
||||||
message: 请求参数
|
app_id=app.id,
|
||||||
request: 声明请求
|
workspace_id=workspace_id,
|
||||||
api_key_auth: 包含验证后的API Key 信息
|
user_id=end_user_id,
|
||||||
db: db_session
|
is_draft=False
|
||||||
"""
|
)
|
||||||
logger.info(f"API Key Auth: {api_key_auth}")
|
|
||||||
logger.info(f"Resource ID: {resource_id}")
|
if app_type == AppType.AGENT:
|
||||||
logger.info(f"Message: {message}")
|
agent_config = dict_to_agent_config(app.current_release.config)
|
||||||
return success(data={"received": True}, msg="消息已接收")
|
# 流式返回
|
||||||
|
if payload.stream:
|
||||||
|
async def event_generator():
|
||||||
|
async for event in app_chat_service.agnet_chat_stream(
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id= end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
config=app.current_release.config,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
):
|
||||||
|
yield event
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
event_generator(),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"X-Accel-Buffering": "no"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 非流式返回
|
||||||
|
result = await app_chat_service.agnet_chat(
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id=end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
config= agent_config,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
)
|
||||||
|
return success(data=conversation_schema.ChatResponse(**result))
|
||||||
|
elif app_type == AppType.MULTI_AGENT:
|
||||||
|
# 多 Agent 流式返回
|
||||||
|
config = dict_to_multi_agent_config(app.current_release.config)
|
||||||
|
if payload.stream:
|
||||||
|
async def event_generator():
|
||||||
|
async for event in app_chat_service.multi_agent_chat_stream(
|
||||||
|
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id=end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
config=config,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
):
|
||||||
|
yield event
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
event_generator(),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"X-Accel-Buffering": "no"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 多 Agent 非流式返回
|
||||||
|
result = await app_chat_service.multi_agent_chat(
|
||||||
|
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id=end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
config=config,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return success(data=conversation_schema.ChatResponse(**result))
|
||||||
|
elif app_type == AppType.WORKFLOW:
|
||||||
|
# 多 Agent 流式返回
|
||||||
|
config = dict_to_workflow_config(app.current_release.config)
|
||||||
|
if payload.stream:
|
||||||
|
async def event_generator():
|
||||||
|
async for event in app_chat_service.workflow_chat_stream(
|
||||||
|
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id=end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
config=config,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
):
|
||||||
|
yield event
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
event_generator(),
|
||||||
|
media_type="text/event-stream",
|
||||||
|
headers={
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"X-Accel-Buffering": "no"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 非流式返回
|
||||||
|
result = await app_chat_service.workflow_chat(
|
||||||
|
|
||||||
|
message=payload.message,
|
||||||
|
conversation_id=conversation.id, # 使用已创建的会话 ID
|
||||||
|
user_id=end_user_id, # 转换为字符串
|
||||||
|
variables=payload.variables,
|
||||||
|
config=config,
|
||||||
|
web_search=payload.web_search,
|
||||||
|
memory=payload.memory,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return success(data=conversation_schema.ChatResponse(**result))
|
||||||
|
else:
|
||||||
|
from app.core.exceptions import BusinessException
|
||||||
|
from app.core.error_codes import BizCode
|
||||||
|
raise BusinessException(f"不支持的应用类型: {app_type}", BizCode.APP_TYPE_NOT_SUPPORTED)
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, status
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status, Request
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from jose import jwt, JWTError
|
from jose import jwt, JWTError
|
||||||
|
|
||||||
from app.db import get_db, SessionLocal
|
from app.db import get_db, SessionLocal
|
||||||
|
from app.models import App
|
||||||
from app.schemas import token_schema
|
from app.schemas import token_schema
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.security import get_token_id
|
from app.core.security import get_token_id
|
||||||
@@ -27,6 +28,51 @@ security_logger = get_security_logger()
|
|||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||||
|
|
||||||
|
|
||||||
|
class APIKeyExtractor:
|
||||||
|
"""
|
||||||
|
Custom dependency to extract API Key from request headers
|
||||||
|
|
||||||
|
Supports two formats:
|
||||||
|
1. Authorization: Bearer <api_key>
|
||||||
|
2. X-API-Key: <api_key>
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def __call__(self, request: Request) -> str:
|
||||||
|
"""Extract API Key from request headers
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI Request object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API Key string
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If API Key is not found
|
||||||
|
"""
|
||||||
|
# Try Authorization header first
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
if auth_header and " " in auth_header:
|
||||||
|
auth_scheme, auth_token = auth_header.split(" ", 1)
|
||||||
|
if auth_scheme.lower() == "bearer":
|
||||||
|
return auth_token
|
||||||
|
|
||||||
|
# Try X-API-Key header
|
||||||
|
api_key = request.headers.get("X-API-Key")
|
||||||
|
if api_key:
|
||||||
|
return api_key
|
||||||
|
|
||||||
|
# No API Key found
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="API Key not found in request headers",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
api_key_extractor = APIKeyExtractor()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user(
|
async def get_current_user(
|
||||||
token: str = Depends(oauth2_scheme),
|
token: str = Depends(oauth2_scheme),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
@@ -469,4 +515,75 @@ async def get_share_user_id(
|
|||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
|
||||||
|
|
||||||
|
async def get_app_or_workspace(
|
||||||
|
api_key: str = Depends(api_key_extractor),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
) -> App | Workspace:
|
||||||
|
"""
|
||||||
|
Get App or Workspace from API Key
|
||||||
|
|
||||||
|
Supports two API Key formats:
|
||||||
|
1. Authorization: Bearer <api_key>
|
||||||
|
2. X-API-Key: <api_key>
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: API Key extracted from request headers
|
||||||
|
db: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
App or Workspace object based on API Key
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If API Key is invalid or not found
|
||||||
|
"""
|
||||||
|
from app.services.api_key_service import ApiKeyAuthService
|
||||||
|
from app.repositories.app_repository import get_apps_by_id
|
||||||
|
from app.repositories.workspace_repository import get_workspace_by_id
|
||||||
|
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate API Key",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth_logger.debug(f"Validating API Key: {api_key[:10]}...")
|
||||||
|
|
||||||
|
# Validate API Key
|
||||||
|
api_key_obj = ApiKeyAuthService.validate_api_key(db, api_key)
|
||||||
|
if not api_key_obj:
|
||||||
|
auth_logger.warning(f"Invalid or expired API Key: {api_key[:10]}...")
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
auth_logger.debug(f"API Key validated successfully, type: {api_key_obj.type}")
|
||||||
|
|
||||||
|
# Return App or Workspace based on API Key type
|
||||||
|
if (api_key_obj.type == "agent" or api_key.type == "multi_agent") and api_key_obj.resource_id:
|
||||||
|
# App API Key
|
||||||
|
app = get_apps_by_id(db, api_key_obj.resource_id)
|
||||||
|
if not app:
|
||||||
|
auth_logger.warning(f"App not found for API Key: {api_key_obj.resource_id}")
|
||||||
|
raise credentials_exception
|
||||||
|
auth_logger.info(f"App access granted: {app.id}")
|
||||||
|
return app
|
||||||
|
|
||||||
|
elif api_key_obj.type == "service":
|
||||||
|
# Workspace API Key
|
||||||
|
workspace = get_workspace_by_id(db, api_key_obj.workspace_id)
|
||||||
|
if not workspace:
|
||||||
|
auth_logger.warning(f"Workspace not found for API Key: {api_key_obj.workspace_id}")
|
||||||
|
raise credentials_exception
|
||||||
|
auth_logger.info(f"Workspace access granted: {workspace.id}")
|
||||||
|
return workspace
|
||||||
|
|
||||||
|
else:
|
||||||
|
auth_logger.warning(f"Unsupported API Key type: {api_key_obj.type}")
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
auth_logger.error(f"Error validating API Key: {str(e)}", exc_info=True)
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,38 +2,10 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request
|
from fastapi import FastAPI, APIRouter
|
||||||
|
from fastapi import HTTPException, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from app.core.response_utils import fail
|
|
||||||
from app.core.logging_config import LoggingConfig, get_logger
|
|
||||||
from app.core.exceptions import BusinessException
|
|
||||||
from app.core.error_codes import BizCode, HTTP_MAPPING
|
|
||||||
from app.controllers import (
|
|
||||||
model_controller,
|
|
||||||
task_controller,
|
|
||||||
test_controller,
|
|
||||||
user_controller,
|
|
||||||
auth_controller,
|
|
||||||
workspace_controller,
|
|
||||||
setup_controller,
|
|
||||||
file_controller,
|
|
||||||
document_controller,
|
|
||||||
knowledge_controller,
|
|
||||||
chunk_controller,
|
|
||||||
knowledgeshare_controller,
|
|
||||||
app_controller,
|
|
||||||
upload_controller,
|
|
||||||
memory_agent_controller,
|
|
||||||
memory_storage_controller,
|
|
||||||
memory_dashboard_controller,
|
|
||||||
multi_agent_controller,
|
|
||||||
)
|
|
||||||
|
|
||||||
from fastapi import FastAPI, APIRouter
|
|
||||||
|
|
||||||
app = FastAPI(title="Data Config API", version="1.0.0")
|
|
||||||
router = APIRouter(prefix="/memory", tags=["Memory"])
|
|
||||||
|
|
||||||
# 管理端 API (JWT 认证)
|
# 管理端 API (JWT 认证)
|
||||||
from app.controllers import manager_router
|
from app.controllers import manager_router
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from .file_schema import File, FileCreate, FileUpdate
|
|||||||
from .tenant_schema import Tenant, TenantCreate, TenantUpdate
|
from .tenant_schema import Tenant, TenantCreate, TenantUpdate
|
||||||
from .chunk_schema import ChunkCreate, ChunkUpdate, ChunkRetrieve
|
from .chunk_schema import ChunkCreate, ChunkUpdate, ChunkRetrieve
|
||||||
from .knowledgeshare_schema import KnowledgeShare, KnowledgeShareCreate
|
from .knowledgeshare_schema import KnowledgeShare, KnowledgeShareCreate
|
||||||
|
from .order_schema import CreateOrderRequest, OrderResponse, ExternalOrderResponse
|
||||||
from .app_schema import (
|
from .app_schema import (
|
||||||
|
AppChatRequest,
|
||||||
DraftRunRequest,
|
DraftRunRequest,
|
||||||
DraftRunResponse,
|
DraftRunResponse,
|
||||||
DraftRunStreamChunk,
|
DraftRunStreamChunk,
|
||||||
@@ -73,6 +75,10 @@ __all__ = [
|
|||||||
"ChunkRetrieve",
|
"ChunkRetrieve",
|
||||||
"KnowledgeShare",
|
"KnowledgeShare",
|
||||||
"KnowledgeShareCreate",
|
"KnowledgeShareCreate",
|
||||||
|
"CreateOrderRequest",
|
||||||
|
"OrderResponse",
|
||||||
|
"ExternalOrderResponse",
|
||||||
|
"AppChatRequest",
|
||||||
"DraftRunRequest",
|
"DraftRunRequest",
|
||||||
"DraftRunResponse",
|
"DraftRunResponse",
|
||||||
"DraftRunStreamChunk",
|
"DraftRunStreamChunk",
|
||||||
|
|||||||
@@ -334,6 +334,13 @@ class AppShare(BaseModel):
|
|||||||
|
|
||||||
# ---------- Draft Run Schemas ----------
|
# ---------- Draft Run Schemas ----------
|
||||||
|
|
||||||
|
class AppChatRequest(BaseModel):
|
||||||
|
message: str = Field(..., description="用户消息")
|
||||||
|
conversation_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="自定义变量参数值")
|
||||||
|
stream: bool = Field(default=False, description="是否流式返回")
|
||||||
|
|
||||||
class DraftRunRequest(BaseModel):
|
class DraftRunRequest(BaseModel):
|
||||||
"""试运行请求"""
|
"""试运行请求"""
|
||||||
message: str = Field(..., description="用户消息")
|
message: str = Field(..., description="用户消息")
|
||||||
|
|||||||
63
api/app/schemas/order_schema.py
Normal file
63
api/app/schemas/order_schema.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
Order Schema
|
||||||
|
|
||||||
|
Defines request and response models for order operations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class CreateOrderRequest(BaseModel):
|
||||||
|
"""Create order request model"""
|
||||||
|
|
||||||
|
product_id: str = Field(..., description="Product ID")
|
||||||
|
quantity: int = Field(..., gt=0, description="Order quantity")
|
||||||
|
customer_name: Optional[str] = Field(None, description="Customer name")
|
||||||
|
customer_email: Optional[str] = Field(None, description="Customer email")
|
||||||
|
notes: Optional[str] = Field(None, description="Order notes")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"product_id": "PROD-001",
|
||||||
|
"quantity": 2,
|
||||||
|
"customer_name": "John Doe",
|
||||||
|
"customer_email": "john@example.com",
|
||||||
|
"notes": "Please deliver before 5pm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OrderResponse(BaseModel):
|
||||||
|
"""Order response model"""
|
||||||
|
|
||||||
|
order_id: str = Field(..., description="Order ID")
|
||||||
|
status: str = Field(..., description="Order status")
|
||||||
|
product_id: str = Field(..., description="Product ID")
|
||||||
|
quantity: int = Field(..., description="Order quantity")
|
||||||
|
total_amount: Optional[float] = Field(None, description="Total amount")
|
||||||
|
created_at: Optional[str] = Field(None, description="Creation timestamp")
|
||||||
|
message: Optional[str] = Field(None, description="Response message")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"order_id": "ORD-20231224-001",
|
||||||
|
"status": "pending",
|
||||||
|
"product_id": "PROD-001",
|
||||||
|
"quantity": 2,
|
||||||
|
"total_amount": 199.99,
|
||||||
|
"created_at": "2023-12-24T10:30:00Z",
|
||||||
|
"message": "Order created successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalOrderResponse(BaseModel):
|
||||||
|
"""External API response model (flexible structure)"""
|
||||||
|
|
||||||
|
success: bool = Field(default=True, description="Request success status")
|
||||||
|
data: Optional[Any] = Field(None, description="Response data")
|
||||||
|
error: Optional[str] = Field(None, description="Error message")
|
||||||
|
code: Optional[int] = Field(None, description="Response code")
|
||||||
485
api/app/services/app_chat_service.py
Normal file
485
api/app/services/app_chat_service.py
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
"""基于分享链接的聊天服务"""
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from typing import Optional, Dict, Any, AsyncGenerator, Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.core.agent.langchain_agent import LangChainAgent
|
||||||
|
from app.core.logging_config import get_business_logger
|
||||||
|
from app.db import get_db
|
||||||
|
from app.models import MultiAgentConfig, AgentConfig
|
||||||
|
from app.schemas.prompt_schema import render_prompt_message, PromptMessageRole
|
||||||
|
from app.services.conversation_service import ConversationService
|
||||||
|
from app.services.draft_run_service import create_knowledge_retrieval_tool, create_long_term_memory_tool
|
||||||
|
from app.services.draft_run_service import create_web_search_tool
|
||||||
|
from app.services.model_service import ModelApiKeyService
|
||||||
|
from app.services.multi_agent_orchestrator import MultiAgentOrchestrator
|
||||||
|
|
||||||
|
logger = get_business_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class AppChatService:
|
||||||
|
"""基于分享链接的聊天服务"""
|
||||||
|
|
||||||
|
def __init__(self, db: Session):
|
||||||
|
self.db = db
|
||||||
|
self.conversation_service = ConversationService(db)
|
||||||
|
|
||||||
|
async def agnet_chat(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
conversation_id: uuid.UUID,
|
||||||
|
config: AgentConfig,
|
||||||
|
user_id: Optional[str] = None,
|
||||||
|
variables: Optional[Dict[str, Any]] = None,
|
||||||
|
web_search: bool = False,
|
||||||
|
memory: bool = True,
|
||||||
|
storage_type: Optional[str] = None,
|
||||||
|
user_rag_memory_id: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""聊天(非流式)"""
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
config_id = None
|
||||||
|
|
||||||
|
if variables is None:
|
||||||
|
variables = {}
|
||||||
|
|
||||||
|
# 获取模型配置ID
|
||||||
|
model_config_id = config.default_model_config_id
|
||||||
|
api_key_obj = ModelApiKeyService.get_a_api_key(model_config_id)
|
||||||
|
# 处理系统提示词(支持变量替换)
|
||||||
|
system_prompt = config.get("system_prompt", "")
|
||||||
|
if variables:
|
||||||
|
system_prompt_rendered = render_prompt_message(
|
||||||
|
system_prompt,
|
||||||
|
PromptMessageRole.USER,
|
||||||
|
variables
|
||||||
|
)
|
||||||
|
system_prompt = system_prompt_rendered.get_text_content() or system_prompt
|
||||||
|
|
||||||
|
# 准备工具列表
|
||||||
|
tools = []
|
||||||
|
|
||||||
|
# 添加知识库检索工具
|
||||||
|
knowledge_retrieval = config.get("knowledge_retrieval")
|
||||||
|
if knowledge_retrieval:
|
||||||
|
knowledge_bases = knowledge_retrieval.get("knowledge_bases", [])
|
||||||
|
kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")]
|
||||||
|
if kb_ids:
|
||||||
|
kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids, user_id)
|
||||||
|
tools.append(kb_tool)
|
||||||
|
|
||||||
|
# 添加长期记忆工具
|
||||||
|
memory_flag = False
|
||||||
|
if memory == True:
|
||||||
|
memory_config = config.get("memory", {})
|
||||||
|
if memory_config.get("enabled") and user_id:
|
||||||
|
memory_flag = True
|
||||||
|
memory_tool = create_long_term_memory_tool(memory_config, user_id)
|
||||||
|
tools.append(memory_tool)
|
||||||
|
|
||||||
|
web_tools = config.get("tools")
|
||||||
|
web_search_choice = web_tools.get("web_search", {})
|
||||||
|
web_search_enable = web_search_choice.get("enabled", False)
|
||||||
|
if web_search == True:
|
||||||
|
if web_search_enable == True:
|
||||||
|
search_tool = create_web_search_tool({})
|
||||||
|
tools.append(search_tool)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"已添加网络搜索工具",
|
||||||
|
extra={
|
||||||
|
"tool_count": len(tools)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取模型参数
|
||||||
|
model_parameters = config.get("model_parameters", {})
|
||||||
|
|
||||||
|
# 创建 LangChain Agent
|
||||||
|
agent = LangChainAgent(
|
||||||
|
model_name=api_key_obj.model_name,
|
||||||
|
api_key=api_key_obj.api_key,
|
||||||
|
provider=api_key_obj.provider,
|
||||||
|
api_base=api_key_obj.api_base,
|
||||||
|
temperature=model_parameters.get("temperature", 0.7),
|
||||||
|
max_tokens=model_parameters.get("max_tokens", 2000),
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
tools=tools,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# 加载历史消息
|
||||||
|
history = []
|
||||||
|
memory_config = {"enabled": True, 'max_history': 10}
|
||||||
|
if memory_config.get("enabled"):
|
||||||
|
messages = self.conversation_service.get_messages(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
limit=memory_config.get("max_history", 10)
|
||||||
|
)
|
||||||
|
history = [
|
||||||
|
{"role": msg.role, "content": msg.content}
|
||||||
|
for msg in messages
|
||||||
|
]
|
||||||
|
|
||||||
|
# 调用 Agent
|
||||||
|
result = await agent.chat(
|
||||||
|
message=message,
|
||||||
|
history=history,
|
||||||
|
context=None,
|
||||||
|
end_user_id=user_id,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id,
|
||||||
|
config_id=config_id,
|
||||||
|
memory_flag=memory_flag
|
||||||
|
)
|
||||||
|
|
||||||
|
# 保存消息
|
||||||
|
self.conversation_service.save_conversation_messages(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
user_message=message,
|
||||||
|
assistant_message=result["content"]
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
return {
|
||||||
|
"conversation_id": conversation_id,
|
||||||
|
"message": result["content"],
|
||||||
|
"usage": result.get("usage", {
|
||||||
|
"prompt_tokens": 0,
|
||||||
|
"completion_tokens": 0,
|
||||||
|
"total_tokens": 0
|
||||||
|
}),
|
||||||
|
"elapsed_time": elapsed_time
|
||||||
|
}
|
||||||
|
|
||||||
|
async def agnet_chat_stream(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
conversation_id: uuid.UUID,
|
||||||
|
config: AgentConfig,
|
||||||
|
user_id: Optional[str] = None,
|
||||||
|
variables: Optional[Dict[str, Any]] = None,
|
||||||
|
web_search: bool = False,
|
||||||
|
memory: bool = True,
|
||||||
|
storage_type: Optional[str] = None,
|
||||||
|
user_rag_memory_id: Optional[str] = None,
|
||||||
|
) -> AsyncGenerator[str, None]:
|
||||||
|
"""聊天(流式)"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_time = time.time()
|
||||||
|
config_id = None
|
||||||
|
|
||||||
|
if variables is None:
|
||||||
|
variables = {}
|
||||||
|
|
||||||
|
# 获取模型配置ID
|
||||||
|
model_config_id = config.default_model_config_id
|
||||||
|
api_key_obj = ModelApiKeyService.get_a_api_key(model_config_id)
|
||||||
|
# 处理系统提示词(支持变量替换)
|
||||||
|
system_prompt = config.get("system_prompt", "")
|
||||||
|
if variables:
|
||||||
|
system_prompt_rendered = render_prompt_message(
|
||||||
|
system_prompt,
|
||||||
|
PromptMessageRole.USER,
|
||||||
|
variables
|
||||||
|
)
|
||||||
|
system_prompt = system_prompt_rendered.get_text_content() or system_prompt
|
||||||
|
|
||||||
|
# 准备工具列表
|
||||||
|
tools = []
|
||||||
|
|
||||||
|
# 添加知识库检索工具
|
||||||
|
knowledge_retrieval = config.get("knowledge_retrieval")
|
||||||
|
if knowledge_retrieval:
|
||||||
|
knowledge_bases = knowledge_retrieval.get("knowledge_bases", [])
|
||||||
|
kb_ids = [kb.get("kb_id") for kb in knowledge_bases if kb.get("kb_id")]
|
||||||
|
if kb_ids:
|
||||||
|
kb_tool = create_knowledge_retrieval_tool(knowledge_retrieval, kb_ids, user_id)
|
||||||
|
tools.append(kb_tool)
|
||||||
|
|
||||||
|
# 添加长期记忆工具
|
||||||
|
memory_flag = False
|
||||||
|
if memory:
|
||||||
|
memory_config = config.get("memory", {})
|
||||||
|
if memory_config.get("enabled") and user_id:
|
||||||
|
memory_flag = True
|
||||||
|
memory_tool = create_long_term_memory_tool(memory_config, user_id)
|
||||||
|
tools.append(memory_tool)
|
||||||
|
|
||||||
|
web_tools = config.get("tools")
|
||||||
|
web_search_choice = web_tools.get("web_search", {})
|
||||||
|
web_search_enable = web_search_choice.get("enabled", False)
|
||||||
|
if web_search == True:
|
||||||
|
if web_search_enable == True:
|
||||||
|
search_tool = create_web_search_tool({})
|
||||||
|
tools.append(search_tool)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"已添加网络搜索工具",
|
||||||
|
extra={
|
||||||
|
"tool_count": len(tools)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取模型参数
|
||||||
|
model_parameters = config.get("model_parameters", {})
|
||||||
|
|
||||||
|
# 创建 LangChain Agent
|
||||||
|
agent = LangChainAgent(
|
||||||
|
model_name=api_key_obj.model_name,
|
||||||
|
api_key=api_key_obj.api_key,
|
||||||
|
provider=api_key_obj.provider,
|
||||||
|
api_base=api_key_obj.api_base,
|
||||||
|
temperature=model_parameters.get("temperature", 0.7),
|
||||||
|
max_tokens=model_parameters.get("max_tokens", 2000),
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
tools=tools,
|
||||||
|
streaming=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 加载历史消息
|
||||||
|
history = []
|
||||||
|
memory_config = {"enabled": True, 'max_history': 10}
|
||||||
|
if memory_config.get("enabled"):
|
||||||
|
messages = self.conversation_service.get_messages(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
limit=memory_config.get("max_history", 10)
|
||||||
|
)
|
||||||
|
history = [
|
||||||
|
{"role": msg.role, "content": msg.content}
|
||||||
|
for msg in messages
|
||||||
|
]
|
||||||
|
|
||||||
|
# 发送开始事件
|
||||||
|
yield f"event: start\ndata: {json.dumps({'conversation_id': str(conversation_id)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
# 流式调用 Agent
|
||||||
|
full_content = ""
|
||||||
|
async for chunk in agent.chat_stream(
|
||||||
|
message=message,
|
||||||
|
history=history,
|
||||||
|
context=None,
|
||||||
|
end_user_id=user_id,
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id,
|
||||||
|
config_id=config_id,
|
||||||
|
memory_flag=memory_flag
|
||||||
|
):
|
||||||
|
full_content += chunk
|
||||||
|
# 发送消息块事件
|
||||||
|
yield f"event: message\ndata: {json.dumps({'content': chunk}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 保存消息
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="user",
|
||||||
|
content=message
|
||||||
|
)
|
||||||
|
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="assistant",
|
||||||
|
content=full_content,
|
||||||
|
meta_data={
|
||||||
|
"model": api_key_obj.model_name,
|
||||||
|
"usage": {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 发送结束事件
|
||||||
|
end_data = {"elapsed_time": elapsed_time, "message_length": len(full_content)}
|
||||||
|
yield f"event: end\ndata: {json.dumps(end_data, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"流式聊天完成",
|
||||||
|
extra={
|
||||||
|
"conversation_id": str(conversation_id),
|
||||||
|
"elapsed_time": elapsed_time,
|
||||||
|
"message_length": len(full_content)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except (GeneratorExit, asyncio.CancelledError):
|
||||||
|
# 生成器被关闭或任务被取消,正常退出
|
||||||
|
logger.debug("流式聊天被中断")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"流式聊天失败: {str(e)}", exc_info=True)
|
||||||
|
# 发送错误事件
|
||||||
|
yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
async def multi_agent_chat(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
conversation_id: uuid.UUID,
|
||||||
|
config: MultiAgentConfig,
|
||||||
|
user_id: Optional[str] = None,
|
||||||
|
variables: Optional[Dict[str, Any]] = None,
|
||||||
|
web_search: bool = False,
|
||||||
|
memory: bool = True,
|
||||||
|
storage_type: Optional[str] = None,
|
||||||
|
user_rag_memory_id: Optional[str] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""多 Agent 聊天(非流式)"""
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
actual_config_id = None
|
||||||
|
config_id = actual_config_id
|
||||||
|
|
||||||
|
if variables is None:
|
||||||
|
variables = {}
|
||||||
|
|
||||||
|
# 2. 创建编排器
|
||||||
|
orchestrator = MultiAgentOrchestrator(self.db, config)
|
||||||
|
|
||||||
|
# 3. 执行任务
|
||||||
|
result = await orchestrator.execute(
|
||||||
|
message=message,
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
user_id=user_id,
|
||||||
|
variables=variables,
|
||||||
|
use_llm_routing=True, # 默认启用 LLM 路由
|
||||||
|
web_search=web_search, # 网络搜索参数
|
||||||
|
memory=memory # 记忆功能参数
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 保存消息
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="user",
|
||||||
|
content=message
|
||||||
|
)
|
||||||
|
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="assistant",
|
||||||
|
content=result.get("message", ""),
|
||||||
|
meta_data={
|
||||||
|
"mode": result.get("mode"),
|
||||||
|
"elapsed_time": result.get("elapsed_time"),
|
||||||
|
"sub_results": result.get("sub_results")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"conversation_id": conversation_id,
|
||||||
|
"message": result.get("message", ""),
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 0,
|
||||||
|
"completion_tokens": 0,
|
||||||
|
"total_tokens": 0
|
||||||
|
},
|
||||||
|
"elapsed_time": elapsed_time
|
||||||
|
}
|
||||||
|
|
||||||
|
async def multi_agent_chat_stream(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
conversation_id: uuid.UUID,
|
||||||
|
config: MultiAgentConfig,
|
||||||
|
user_id: Optional[str] = None,
|
||||||
|
variables: Optional[Dict[str, Any]] = None,
|
||||||
|
web_search: bool = False,
|
||||||
|
memory: bool = True,
|
||||||
|
storage_type: Optional[str] = None,
|
||||||
|
user_rag_memory_id: Optional[str] = None,
|
||||||
|
) -> AsyncGenerator[str, None]:
|
||||||
|
"""多 Agent 聊天(流式)"""
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
actual_config_id = None
|
||||||
|
config_id = actual_config_id
|
||||||
|
|
||||||
|
if variables is None:
|
||||||
|
variables = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# 发送开始事件
|
||||||
|
yield f"event: start\ndata: {json.dumps({'conversation_id': str(conversation_id)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
full_content = ""
|
||||||
|
|
||||||
|
# 2. 创建编排器
|
||||||
|
orchestrator = MultiAgentOrchestrator(self.db, config)
|
||||||
|
|
||||||
|
# 3. 流式执行任务
|
||||||
|
async for event in orchestrator.execute_stream(
|
||||||
|
message=message,
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
user_id=user_id,
|
||||||
|
variables=variables,
|
||||||
|
use_llm_routing=True,
|
||||||
|
web_search=web_search, # 网络搜索参数
|
||||||
|
memory=memory, # 记忆功能参数
|
||||||
|
storage_type=storage_type,
|
||||||
|
user_rag_memory_id=user_rag_memory_id
|
||||||
|
):
|
||||||
|
yield event
|
||||||
|
# 尝试提取内容(用于保存)
|
||||||
|
if "data:" in event:
|
||||||
|
try:
|
||||||
|
data_line = event.split("data: ", 1)[1].strip()
|
||||||
|
data = json.loads(data_line)
|
||||||
|
if "content" in data:
|
||||||
|
full_content += data["content"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 保存消息
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="user",
|
||||||
|
content=message
|
||||||
|
)
|
||||||
|
|
||||||
|
self.conversation_service.add_message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="assistant",
|
||||||
|
content=full_content,
|
||||||
|
meta_data={
|
||||||
|
"elapsed_time": elapsed_time
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"多 Agent 流式聊天完成",
|
||||||
|
extra={
|
||||||
|
"conversation_id": str(conversation_id),
|
||||||
|
"elapsed_time": elapsed_time,
|
||||||
|
"message_length": len(full_content)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
except (GeneratorExit, asyncio.CancelledError):
|
||||||
|
# 生成器被关闭或任务被取消,正常退出
|
||||||
|
logger.debug("多 Agent 流式聊天被中断")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"多 Agent 流式聊天失败: {str(e)}", exc_info=True)
|
||||||
|
# 发送错误事件
|
||||||
|
yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 依赖注入函数 ====================
|
||||||
|
|
||||||
|
def get_app_chat_service(
|
||||||
|
db: Annotated[Session, Depends(get_db)]
|
||||||
|
) -> ChatService:
|
||||||
|
"""获取工作流服务(依赖注入)"""
|
||||||
|
return ChatService(db)
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
"""会话服务"""
|
"""会话服务"""
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Optional, List, Tuple
|
from typing import Optional, List, Tuple, Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import select, desc
|
from sqlalchemy import select, desc
|
||||||
|
|
||||||
|
from app.db import get_db
|
||||||
from app.models import Conversation, Message
|
from app.models import Conversation, Message
|
||||||
from app.core.exceptions import ResourceNotFoundException, BusinessException
|
from app.core.exceptions import ResourceNotFoundException, BusinessException
|
||||||
from app.core.error_codes import BizCode
|
from app.core.error_codes import BizCode
|
||||||
@@ -227,3 +230,53 @@ class ConversationService:
|
|||||||
"workspace_id": str(workspace_id)
|
"workspace_id": str(workspace_id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_or_get_conversation(
|
||||||
|
self,
|
||||||
|
app_id: uuid.UUID,
|
||||||
|
workspace_id: uuid.UUID,
|
||||||
|
is_draft: bool = False,
|
||||||
|
conversation_id: Optional[uuid.UUID] = None,
|
||||||
|
user_id: Optional[str] = None,
|
||||||
|
) -> Conversation:
|
||||||
|
"""创建或获取会话"""
|
||||||
|
|
||||||
|
# 如果提供了 conversation_id,尝试获取现有会话
|
||||||
|
if conversation_id:
|
||||||
|
try:
|
||||||
|
conversation = self.get_conversation(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
workspace_id=workspace_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# 验证会话是否属于该应用
|
||||||
|
if conversation.app_id != app_id:
|
||||||
|
raise BusinessException("会话不属于该应用", BizCode.INVALID_CONVERSATION)
|
||||||
|
return conversation
|
||||||
|
except ResourceNotFoundException:
|
||||||
|
logger.warning(
|
||||||
|
"会话不存在,将创建新会话",
|
||||||
|
extra={"conversation_id": str(conversation_id)}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建新会话(使用发布版本的配置)
|
||||||
|
conversation = self.create_conversation(
|
||||||
|
app_id=app_id,
|
||||||
|
workspace_id=workspace_id,
|
||||||
|
user_id=user_id,
|
||||||
|
is_draft=is_draft
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"为分享链接创建新会话"
|
||||||
|
)
|
||||||
|
|
||||||
|
return conversation
|
||||||
|
|
||||||
|
# ==================== 依赖注入函数 ====================
|
||||||
|
|
||||||
|
def get_conversation_service(
|
||||||
|
db: Annotated[Session, Depends(get_db)]
|
||||||
|
) -> ConversationService:
|
||||||
|
"""获取工作流服务(依赖注入)"""
|
||||||
|
return ConversationService(db)
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ class ModelApiKeyService:
|
|||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_keys_by_model(db: Session, model_config_id: uuid.UUID, is_active: bool = True) -> List[ModelApiKey]:
|
def get_api_keys_by_model(db: Session, model_config_id: uuid.UUID, is_active: bool = True) -> list[ModelApiKey]:
|
||||||
"""根据模型配置ID获取API Key列表"""
|
"""根据模型配置ID获取API Key列表"""
|
||||||
if not ModelConfigRepository.get_by_id(db, model_config_id):
|
if not ModelConfigRepository.get_by_id(db, model_config_id):
|
||||||
raise BusinessException("模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
raise BusinessException("模型配置不存在", BizCode.MODEL_NOT_FOUND)
|
||||||
@@ -409,3 +409,11 @@ class ModelApiKeyService:
|
|||||||
if success:
|
if success:
|
||||||
db.commit()
|
db.commit()
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_a_api_key(db: Session, model_config_id: uuid.UUID) -> ModelApiKey:
|
||||||
|
|
||||||
|
api_kes = ModelApiKeyService.get_api_keys_by_model(db, model_config_id)
|
||||||
|
if api_kes and len(api_kes) > 0:
|
||||||
|
return api_kes[0]
|
||||||
|
raise BusinessException("没有可用的 API Key", BizCode.AGENT_CONFIG_MISSING)
|
||||||
|
|||||||
205
api/app/services/order_service.py
Normal file
205
api/app/services/order_service.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"""
|
||||||
|
Order Service
|
||||||
|
|
||||||
|
Handles order operations including forwarding requests to external APIs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import httpx
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from app.schemas.order_schema import CreateOrderRequest
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderService:
|
||||||
|
"""Order service for handling order operations"""
|
||||||
|
|
||||||
|
def __init__(self, external_api_url: Optional[str] = None, api_key: Optional[str] = None):
|
||||||
|
"""Initialize order service
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_api_url: External API base URL
|
||||||
|
api_key: API key for authentication
|
||||||
|
"""
|
||||||
|
# Default external API URL (replace with actual URL)
|
||||||
|
self.external_api_url = external_api_url or "https://api.example.com/v1"
|
||||||
|
self.api_key = api_key
|
||||||
|
self.timeout = 30.0 # 30 seconds timeout
|
||||||
|
|
||||||
|
async def create_order(
|
||||||
|
self,
|
||||||
|
order_data: CreateOrderRequest,
|
||||||
|
user_id: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Create order by forwarding request to external API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order_data: Order creation data
|
||||||
|
user_id: Current user ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Order response data
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
httpx.HTTPError: If external API request fails
|
||||||
|
Exception: For other errors
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Prepare request payload
|
||||||
|
payload = {
|
||||||
|
"product_id": order_data.product_id,
|
||||||
|
"quantity": order_data.quantity,
|
||||||
|
"customer_name": order_data.customer_name,
|
||||||
|
"customer_email": order_data.customer_email,
|
||||||
|
"notes": order_data.notes,
|
||||||
|
"user_id": user_id # Include user ID for tracking
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare headers
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "MemoryBear-OrderService/1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add API key if configured
|
||||||
|
if self.api_key:
|
||||||
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
||||||
|
|
||||||
|
logger.info(f"Forwarding order creation request to external API: {self.external_api_url}/orders")
|
||||||
|
logger.debug(f"Request payload: {payload}")
|
||||||
|
|
||||||
|
# Make async HTTP request to external API
|
||||||
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||||
|
response = await client.post(
|
||||||
|
f"{self.external_api_url}/orders",
|
||||||
|
json=payload,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log response status
|
||||||
|
logger.info(f"External API response status: {response.status_code}")
|
||||||
|
|
||||||
|
# Raise exception for 4xx/5xx status codes
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
response_data = response.json()
|
||||||
|
logger.debug(f"External API response data: {response_data}")
|
||||||
|
|
||||||
|
# Transform external API response to internal format
|
||||||
|
return self._transform_external_response(response_data)
|
||||||
|
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
logger.error(f"External API returned error status: {e.response.status_code}")
|
||||||
|
logger.error(f"Error response: {e.response.text}")
|
||||||
|
|
||||||
|
# Try to parse error response
|
||||||
|
try:
|
||||||
|
error_data = e.response.json()
|
||||||
|
error_message = error_data.get("message") or error_data.get("error") or "External API error"
|
||||||
|
except Exception:
|
||||||
|
error_message = f"External API error: {e.response.status_code}"
|
||||||
|
|
||||||
|
raise Exception(f"Failed to create order: {error_message}")
|
||||||
|
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
logger.error(f"External API request timeout after {self.timeout}s")
|
||||||
|
raise Exception("Order creation timeout - external service not responding")
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"External API request failed: {str(e)}")
|
||||||
|
raise Exception(f"Failed to connect to external order service: {str(e)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during order creation: {str(e)}", exc_info=True)
|
||||||
|
raise Exception(f"Order creation failed: {str(e)}")
|
||||||
|
|
||||||
|
def _transform_external_response(self, external_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Transform external API response to internal format
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_data: Response data from external API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Transformed response data
|
||||||
|
"""
|
||||||
|
# Handle different response formats from external API
|
||||||
|
# Adjust this based on actual external API response structure
|
||||||
|
|
||||||
|
if "data" in external_data:
|
||||||
|
# Format 1: {"success": true, "data": {...}}
|
||||||
|
data = external_data["data"]
|
||||||
|
elif "order" in external_data:
|
||||||
|
# Format 2: {"order": {...}}
|
||||||
|
data = external_data["order"]
|
||||||
|
else:
|
||||||
|
# Format 3: Direct response
|
||||||
|
data = external_data
|
||||||
|
|
||||||
|
# Extract fields with fallbacks
|
||||||
|
return {
|
||||||
|
"order_id": data.get("order_id") or data.get("id") or "UNKNOWN",
|
||||||
|
"status": data.get("status") or "pending",
|
||||||
|
"product_id": data.get("product_id") or "",
|
||||||
|
"quantity": data.get("quantity") or 0,
|
||||||
|
"total_amount": data.get("total_amount") or data.get("amount"),
|
||||||
|
"created_at": data.get("created_at") or data.get("timestamp"),
|
||||||
|
"message": external_data.get("message") or "Order created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_order(self, order_id: str) -> Dict[str, Any]:
|
||||||
|
"""Get order details from external API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order_id: Order ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Order details
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if self.api_key:
|
||||||
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
||||||
|
|
||||||
|
logger.info(f"Fetching order {order_id} from external API")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||||
|
response = await client.get(
|
||||||
|
f"{self.external_api_url}/orders/{order_id}",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to fetch order {order_id}: {str(e)}")
|
||||||
|
raise Exception(f"Failed to fetch order: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# Singleton instance
|
||||||
|
_order_service_instance: Optional[OrderService] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_order_service(
|
||||||
|
external_api_url: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None
|
||||||
|
) -> OrderService:
|
||||||
|
"""Get order service instance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_api_url: External API URL (optional, uses default if not provided)
|
||||||
|
api_key: API key (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
OrderService instance
|
||||||
|
"""
|
||||||
|
global _order_service_instance
|
||||||
|
|
||||||
|
if _order_service_instance is None:
|
||||||
|
_order_service_instance = OrderService(
|
||||||
|
external_api_url=external_api_url,
|
||||||
|
api_key=api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
return _order_service_instance
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
from sqlalchemy.orm import Session
|
|
||||||
from typing import List, Optional
|
|
||||||
import uuid
|
|
||||||
import secrets
|
|
||||||
import hashlib
|
|
||||||
import datetime
|
import datetime
|
||||||
from fastapi import HTTPException, status
|
import hashlib
|
||||||
|
import secrets
|
||||||
|
import uuid
|
||||||
|
from os import getenv
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
from app.core.error_codes import BizCode
|
from app.core.error_codes import BizCode
|
||||||
from app.core.exceptions import BusinessException, PermissionDeniedException
|
from app.core.exceptions import BusinessException, PermissionDeniedException
|
||||||
from app.models.tenant_model import Tenants
|
from app.core.logging_config import get_business_logger
|
||||||
from app.models.user_model import User
|
from app.models.user_model import User
|
||||||
from app.models.app_model import App
|
from app.models.workspace_model import Workspace, WorkspaceRole, InviteStatus, WorkspaceMember
|
||||||
from app.models.end_user_model import EndUser
|
from app.repositories import workspace_repository
|
||||||
from app.models.workspace_model import Workspace, WorkspaceRole, WorkspaceInvite, InviteStatus, WorkspaceMember
|
from app.repositories.workspace_invite_repository import WorkspaceInviteRepository
|
||||||
from app.schemas.workspace_schema import (
|
from app.schemas.workspace_schema import (
|
||||||
WorkspaceCreate,
|
WorkspaceCreate,
|
||||||
WorkspaceUpdate,
|
WorkspaceUpdate,
|
||||||
@@ -21,15 +24,9 @@ from app.schemas.workspace_schema import (
|
|||||||
InviteAcceptRequest,
|
InviteAcceptRequest,
|
||||||
WorkspaceMemberUpdate
|
WorkspaceMemberUpdate
|
||||||
)
|
)
|
||||||
from app.repositories import workspace_repository
|
|
||||||
from app.repositories.workspace_invite_repository import WorkspaceInviteRepository
|
|
||||||
from app.core.logging_config import get_business_logger
|
|
||||||
from app.core.config import settings
|
|
||||||
from app.services import user_service
|
|
||||||
from os import getenv
|
|
||||||
# 获取业务逻辑专用日志器
|
# 获取业务逻辑专用日志器
|
||||||
business_logger = get_business_logger()
|
business_logger = get_business_logger()
|
||||||
import os #
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
def switch_workspace(
|
def switch_workspace(
|
||||||
@@ -802,3 +799,4 @@ def get_workspace_models_configs(
|
|||||||
f"llm={configs.get('llm')}, embedding={configs.get('embedding')}, rerank={configs.get('rerank')}"
|
f"llm={configs.get('llm')}, embedding={configs.get('embedding')}, rerank={configs.get('rerank')}"
|
||||||
)
|
)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user