feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs
This commit is contained in:
@@ -1,223 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序 AI 对话路由 —— SSE 流式对话、历史对话列表、消息查询。
|
||||
|
||||
端点清单:
|
||||
- POST /api/ai/chat/stream — SSE 流式对话
|
||||
- GET /api/ai/conversations — 历史对话列表(分页)
|
||||
- GET /api/ai/conversations/{conversation_id}/messages — 对话消息列表
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.ai.bailian_client import BailianClient
|
||||
from app.ai.conversation_service import ConversationService
|
||||
from app.ai.apps.app1_chat import chat_stream
|
||||
from app.ai.schemas import ChatStreamRequest, SSEEvent
|
||||
from app.auth.dependencies import CurrentUser, get_current_user
|
||||
from app.database import get_connection
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/ai", tags=["小程序 AI 对话"])
|
||||
|
||||
|
||||
# ── 辅助:获取用户 nickname ──────────────────────────────────
|
||||
|
||||
|
||||
def _get_user_nickname(user_id: int) -> str:
|
||||
"""从 auth.users 查询用户 nickname,查不到返回空字符串。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT nickname FROM auth.users WHERE id = %s",
|
||||
(user_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row and row[0] else ""
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── 辅助:获取用户主要角色 ───────────────────────────────────
|
||||
|
||||
|
||||
def _get_user_role_label(roles: list[str]) -> str:
|
||||
"""从角色列表提取主要角色标签,用于 AI 上下文。"""
|
||||
if "store_manager" in roles or "owner" in roles:
|
||||
return "管理者"
|
||||
if "assistant" in roles or "coach" in roles:
|
||||
return "助教"
|
||||
return "用户"
|
||||
|
||||
|
||||
# ── 辅助:构建 BailianClient 实例 ────────────────────────────
|
||||
|
||||
|
||||
def _get_bailian_client() -> BailianClient:
|
||||
"""从环境变量构建 BailianClient,缺失时报错。"""
|
||||
api_key = os.environ.get("BAILIAN_API_KEY")
|
||||
base_url = os.environ.get("BAILIAN_BASE_URL")
|
||||
model = os.environ.get("BAILIAN_MODEL")
|
||||
if not api_key or not base_url or not model:
|
||||
raise RuntimeError(
|
||||
"百炼 API 环境变量缺失,需要 BAILIAN_API_KEY、BAILIAN_BASE_URL、BAILIAN_MODEL"
|
||||
)
|
||||
return BailianClient(api_key=api_key, base_url=base_url, model=model)
|
||||
|
||||
|
||||
# ── SSE 流式对话 ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.post("/chat/stream")
|
||||
async def ai_chat_stream(
|
||||
body: ChatStreamRequest,
|
||||
user: CurrentUser = Depends(get_current_user),
|
||||
):
|
||||
"""SSE 流式对话端点。
|
||||
|
||||
接收用户消息,通过百炼 API 流式返回 AI 回复。
|
||||
每个 SSE 事件格式:data: {json}\n\n
|
||||
事件类型:chunk(文本片段)/ done(完成)/ error(错误)
|
||||
"""
|
||||
if not body.message or not body.message.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="消息内容不能为空",
|
||||
)
|
||||
|
||||
nickname = _get_user_nickname(user.user_id)
|
||||
role_label = _get_user_role_label(user.roles)
|
||||
bailian = _get_bailian_client()
|
||||
conv_svc = ConversationService()
|
||||
|
||||
async def event_generator():
|
||||
"""SSE 事件生成器,逐事件 yield data: {json}\n\n 格式。"""
|
||||
try:
|
||||
async for event in chat_stream(
|
||||
message=body.message.strip(),
|
||||
user_id=user.user_id,
|
||||
nickname=nickname,
|
||||
role=role_label,
|
||||
site_id=user.site_id,
|
||||
source_page=body.source_page,
|
||||
page_context=body.page_context,
|
||||
screen_content=body.screen_content,
|
||||
bailian=bailian,
|
||||
conv_svc=conv_svc,
|
||||
):
|
||||
yield f"data: {event.model_dump_json()}\n\n"
|
||||
except Exception as e:
|
||||
# 兜底:生成器内部异常也以 SSE error 事件返回
|
||||
logger.error("SSE 生成器异常: %s", e, exc_info=True)
|
||||
error_event = SSEEvent(type="error", message=str(e))
|
||||
yield f"data: {error_event.model_dump_json()}\n\n"
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no", # nginx 禁用缓冲
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ── 历史对话列表 ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/conversations")
|
||||
async def list_conversations(
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
user: CurrentUser = Depends(get_current_user),
|
||||
):
|
||||
"""查询当前用户的历史对话列表,按时间倒序,分页。"""
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 1 or page_size > 100:
|
||||
page_size = 20
|
||||
|
||||
conv_svc = ConversationService()
|
||||
conversations = conv_svc.get_conversations(
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
# 为每条对话附加首条消息预览
|
||||
result = []
|
||||
for conv in conversations:
|
||||
item = {
|
||||
"id": conv["id"],
|
||||
"app_id": conv["app_id"],
|
||||
"source_page": conv.get("source_page"),
|
||||
"created_at": conv["created_at"],
|
||||
"first_message_preview": None,
|
||||
}
|
||||
# 查询首条 user 消息作为预览
|
||||
messages = conv_svc.get_messages(conv["id"])
|
||||
for msg in messages:
|
||||
if msg["role"] == "user":
|
||||
content = msg["content"] or ""
|
||||
item["first_message_preview"] = content[:50] if len(content) > 50 else content
|
||||
break
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ── 对话消息列表 ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/conversations/{conversation_id}/messages")
|
||||
async def get_conversation_messages(
|
||||
conversation_id: int,
|
||||
user: CurrentUser = Depends(get_current_user),
|
||||
):
|
||||
"""查询指定对话的所有消息,按时间升序。
|
||||
|
||||
验证对话归属当前用户和 site_id,防止越权访问。
|
||||
"""
|
||||
# 先验证对话归属
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id FROM biz.ai_conversations
|
||||
WHERE id = %s AND user_id = %s AND site_id = %s
|
||||
""",
|
||||
(conversation_id, str(user.user_id), user.site_id),
|
||||
)
|
||||
if not cur.fetchone():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="对话不存在或无权访问",
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
conv_svc = ConversationService()
|
||||
messages = conv_svc.get_messages(conversation_id)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": msg["id"],
|
||||
"role": msg["role"],
|
||||
"content": msg["content"],
|
||||
"tokens_used": msg.get("tokens_used"),
|
||||
"created_at": msg["created_at"],
|
||||
}
|
||||
for msg in messages
|
||||
]
|
||||
@@ -1,3 +1,7 @@
|
||||
# AI_CHANGELOG
|
||||
# - 2026-03-20 | Prompt: R3 项目类型筛选接口重建 | SkillFilterEnum/ProjectFilterEnum
|
||||
# 默认值从 .all 改为 .ALL,与新枚举值一致。
|
||||
|
||||
"""
|
||||
看板路由:BOARD-1(助教)、BOARD-2(客户)、BOARD-3(财务)。
|
||||
|
||||
@@ -30,7 +34,7 @@ router = APIRouter(prefix="/api/xcx/board", tags=["xcx-board"])
|
||||
@router.get("/coaches", response_model=CoachBoardResponse)
|
||||
async def get_coach_board(
|
||||
sort: CoachSortEnum = Query(default=CoachSortEnum.perf_desc),
|
||||
skill: SkillFilterEnum = Query(default=SkillFilterEnum.all),
|
||||
skill: SkillFilterEnum = Query(default=SkillFilterEnum.ALL),
|
||||
time: BoardTimeEnum = Query(default=BoardTimeEnum.month),
|
||||
user: CurrentUser = Depends(require_permission("view_board_coach")),
|
||||
):
|
||||
@@ -44,7 +48,7 @@ async def get_coach_board(
|
||||
@router.get("/customers", response_model=CustomerBoardResponse)
|
||||
async def get_customer_board(
|
||||
dimension: CustomerDimensionEnum = Query(default=CustomerDimensionEnum.recall),
|
||||
project: ProjectFilterEnum = Query(default=ProjectFilterEnum.all),
|
||||
project: ProjectFilterEnum = Query(default=ProjectFilterEnum.ALL),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_permission("view_board_customer")),
|
||||
|
||||
329
apps/backend/app/routers/xcx_chat.py
Normal file
329
apps/backend/app/routers/xcx_chat.py
Normal file
@@ -0,0 +1,329 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
小程序 CHAT 路由 —— CHAT-1/2/3/4 端点。
|
||||
|
||||
替代原 xcx_ai_chat.py(/api/ai/*),统一迁移到 /api/xcx/chat/* 路径。
|
||||
|
||||
端点清单:
|
||||
- GET /api/xcx/chat/history — CHAT-1 对话历史列表
|
||||
- GET /api/xcx/chat/{chat_id}/messages — CHAT-2a 通过 chatId 查询消息
|
||||
- GET /api/xcx/chat/messages?contextType=&contextId= — CHAT-2b 通过上下文查询消息
|
||||
- POST /api/xcx/chat/{chat_id}/messages — CHAT-3 发送消息(同步回复)
|
||||
- POST /api/xcx/chat/stream — CHAT-4 SSE 流式端点
|
||||
|
||||
所有端点使用 require_approved() 权限检查。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.ai.bailian_client import BailianClient
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.database import get_connection
|
||||
from app.middleware.permission import require_approved
|
||||
from app.schemas.xcx_chat import (
|
||||
ChatHistoryItem,
|
||||
ChatHistoryResponse,
|
||||
ChatMessageItem,
|
||||
ChatMessagesResponse,
|
||||
ChatStreamRequest,
|
||||
MessageBrief,
|
||||
ReferenceCard,
|
||||
SendMessageRequest,
|
||||
SendMessageResponse,
|
||||
)
|
||||
from app.services.chat_service import ChatService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/xcx/chat", tags=["小程序 CHAT"])
|
||||
|
||||
|
||||
# ── CHAT-1: 对话历史列表 ─────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/history", response_model=ChatHistoryResponse)
|
||||
async def list_chat_history(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
) -> ChatHistoryResponse:
|
||||
"""CHAT-1: 查询当前用户的对话历史列表,按最后消息时间倒序。"""
|
||||
svc = ChatService()
|
||||
items, total = svc.get_chat_history(
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return ChatHistoryResponse(
|
||||
items=[ChatHistoryItem(**item) for item in items],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# ── CHAT-2b: 通过上下文查询消息 ─────────────────────────────
|
||||
# ⚠️ 必须在 /{chat_id}/messages 之前注册,否则 "messages" 会被当作 chat_id 路径参数
|
||||
|
||||
|
||||
@router.get("/messages", response_model=ChatMessagesResponse)
|
||||
async def get_chat_messages_by_context(
|
||||
context_type: str = Query(..., alias="contextType"),
|
||||
context_id: str = Query(..., alias="contextId"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
) -> ChatMessagesResponse:
|
||||
"""CHAT-2b: 通过上下文类型和 ID 查询消息(自动查找/创建对话)。"""
|
||||
svc = ChatService()
|
||||
# 按复用规则查找或创建对话
|
||||
chat_id = svc.get_or_create_session(
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
context_type=context_type,
|
||||
context_id=context_id if context_id else None,
|
||||
)
|
||||
messages, total, resolved_chat_id = svc.get_messages(
|
||||
chat_id=chat_id,
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return ChatMessagesResponse(
|
||||
chat_id=resolved_chat_id,
|
||||
items=[_to_message_item(m) for m in messages],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# ── CHAT-2a: 通过 chatId 查询消息 ───────────────────────────
|
||||
|
||||
|
||||
@router.get("/{chat_id}/messages", response_model=ChatMessagesResponse)
|
||||
async def get_chat_messages(
|
||||
chat_id: int,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
) -> ChatMessagesResponse:
|
||||
"""CHAT-2a: 通过 chatId 查询对话消息列表,按 createdAt 正序。"""
|
||||
svc = ChatService()
|
||||
messages, total, resolved_chat_id = svc.get_messages(
|
||||
chat_id=chat_id,
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
return ChatMessagesResponse(
|
||||
chat_id=resolved_chat_id,
|
||||
items=[_to_message_item(m) for m in messages],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# ── CHAT-4: SSE 流式端点 ────────────────────────────────────
|
||||
# ⚠️ 必须在 /{chat_id}/messages 之前注册,否则 "stream" 会被当作 chat_id 路径参数
|
||||
|
||||
|
||||
@router.post("/stream")
|
||||
async def chat_stream(
|
||||
body: ChatStreamRequest,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
) -> StreamingResponse:
|
||||
"""CHAT-4: SSE 流式对话端点。
|
||||
|
||||
接收用户消息,通过百炼 API 流式返回 AI 回复。
|
||||
SSE 事件类型:message(逐 token)/ done(完成)/ error(错误)。
|
||||
|
||||
chatId 归属验证:不属于当前用户返回 HTTP 403(普通 JSON 错误,非 SSE)。
|
||||
"""
|
||||
if not body.content or not body.content.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="消息内容不能为空",
|
||||
)
|
||||
|
||||
svc = ChatService()
|
||||
content = body.content.strip()
|
||||
|
||||
# 归属验证(在 SSE 流开始前完成,失败时返回普通 HTTP 错误)
|
||||
svc._verify_ownership(body.chat_id, user.user_id, user.site_id)
|
||||
|
||||
# 存入用户消息(P5 PRD 合规:发送时即写入)
|
||||
user_msg_id, user_created_at = svc._save_message(body.chat_id, "user", content)
|
||||
|
||||
async def event_generator():
|
||||
"""SSE 事件生成器。
|
||||
|
||||
事件格式:
|
||||
- event: message\\ndata: {"token": "..."}\\n\\n
|
||||
- event: done\\ndata: {"messageId": ..., "createdAt": "..."}\\n\\n
|
||||
- event: error\\ndata: {"message": "..."}\\n\\n
|
||||
"""
|
||||
full_reply_parts: list[str] = []
|
||||
try:
|
||||
bailian = _get_bailian_client()
|
||||
|
||||
# 获取历史消息作为上下文
|
||||
messages = _build_ai_messages(body.chat_id)
|
||||
|
||||
# 流式调用百炼 API
|
||||
async for chunk in bailian.chat_stream(messages):
|
||||
full_reply_parts.append(chunk)
|
||||
yield f"event: message\ndata: {json.dumps({'token': chunk}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# 流结束:拼接完整回复并持久化
|
||||
full_reply = "".join(full_reply_parts)
|
||||
estimated_tokens = len(full_reply)
|
||||
|
||||
ai_msg_id, ai_created_at = svc._save_message(
|
||||
body.chat_id, "assistant", full_reply, tokens_used=estimated_tokens,
|
||||
)
|
||||
svc._update_session_metadata(body.chat_id, full_reply)
|
||||
|
||||
# 发送 done 事件
|
||||
done_data = json.dumps(
|
||||
{"messageId": ai_msg_id, "createdAt": ai_created_at},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
yield f"event: done\ndata: {done_data}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error("SSE 流式对话异常: %s", e, exc_info=True)
|
||||
|
||||
# 如果已有部分回复,仍然持久化
|
||||
if full_reply_parts:
|
||||
partial = "".join(full_reply_parts)
|
||||
try:
|
||||
svc._save_message(body.chat_id, "assistant", partial)
|
||||
svc._update_session_metadata(body.chat_id, partial)
|
||||
except Exception:
|
||||
logger.error("持久化部分回复失败", exc_info=True)
|
||||
|
||||
error_data = json.dumps(
|
||||
{"message": "AI 服务暂时不可用"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
yield f"event: error\ndata: {error_data}\n\n"
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ── CHAT-3: 发送消息(同步回复) ─────────────────────────────
|
||||
|
||||
|
||||
@router.post("/{chat_id}/messages", response_model=SendMessageResponse)
|
||||
async def send_message(
|
||||
chat_id: int,
|
||||
body: SendMessageRequest,
|
||||
user: CurrentUser = Depends(require_approved()),
|
||||
) -> SendMessageResponse:
|
||||
"""CHAT-3: 发送用户消息并获取同步 AI 回复。
|
||||
|
||||
chatId 归属验证:不属于当前用户返回 HTTP 403。
|
||||
AI 失败时返回错误提示消息(HTTP 200)。
|
||||
"""
|
||||
if not body.content or not body.content.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="消息内容不能为空",
|
||||
)
|
||||
|
||||
svc = ChatService()
|
||||
result = await svc.send_message_sync(
|
||||
chat_id=chat_id,
|
||||
content=body.content.strip(),
|
||||
user_id=user.user_id,
|
||||
site_id=user.site_id,
|
||||
)
|
||||
return SendMessageResponse(
|
||||
user_message=MessageBrief(**result["user_message"]),
|
||||
ai_reply=MessageBrief(**result["ai_reply"]),
|
||||
)
|
||||
|
||||
|
||||
# ── 辅助函数 ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _to_message_item(msg: dict) -> ChatMessageItem:
|
||||
"""将 chat_service 返回的消息 dict 转换为 ChatMessageItem。"""
|
||||
ref_card = msg.get("reference_card")
|
||||
reference_card = ReferenceCard(**ref_card) if ref_card and isinstance(ref_card, dict) else None
|
||||
return ChatMessageItem(
|
||||
id=msg["id"],
|
||||
role=msg["role"],
|
||||
content=msg["content"],
|
||||
created_at=msg["created_at"],
|
||||
reference_card=reference_card,
|
||||
)
|
||||
|
||||
|
||||
def _get_bailian_client() -> BailianClient:
|
||||
"""从环境变量构建 BailianClient,缺失时报错。"""
|
||||
api_key = os.environ.get("BAILIAN_API_KEY")
|
||||
base_url = os.environ.get("BAILIAN_BASE_URL")
|
||||
model = os.environ.get("BAILIAN_MODEL")
|
||||
if not api_key or not base_url or not model:
|
||||
raise RuntimeError(
|
||||
"百炼 API 环境变量缺失,需要 BAILIAN_API_KEY、BAILIAN_BASE_URL、BAILIAN_MODEL"
|
||||
)
|
||||
return BailianClient(api_key=api_key, base_url=base_url, model=model)
|
||||
|
||||
|
||||
def _build_ai_messages(chat_id: int) -> list[dict]:
|
||||
"""构建发送给 AI 的消息列表(含历史上下文)。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT role, content FROM biz.ai_messages
|
||||
WHERE conversation_id = %s
|
||||
ORDER BY created_at ASC
|
||||
""",
|
||||
(chat_id,),
|
||||
)
|
||||
history = cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
messages: list[dict] = []
|
||||
# 取最近 20 条
|
||||
recent = history[-20:] if len(history) > 20 else history
|
||||
for role, msg_content in recent:
|
||||
messages.append({"role": role, "content": msg_content})
|
||||
|
||||
# 如果没有 system 消息,添加默认 system prompt
|
||||
if not messages or messages[0]["role"] != "system":
|
||||
system_prompt = {
|
||||
"role": "system",
|
||||
"content": json.dumps(
|
||||
{"task": "你是台球门店的 AI 助手,根据用户的问题和当前页面上下文提供帮助。"},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
}
|
||||
messages.insert(0, system_prompt)
|
||||
|
||||
return messages
|
||||
Reference in New Issue
Block a user