Merge branch 'develop' of http://47.106.207.27:3000/AI260626/Mutual_Fund into develop_feature_customer
# Conflicts: # .gitignore # api/deps.py # api/router.py # main.py # tests/test_customer_agent_router.py # tests/test_knowledge_router.py
This commit is contained in:
+67
@@ -62,6 +62,73 @@ data/files/
|
|||||||
*.csv
|
*.csv
|
||||||
*.xlsx
|
*.xlsx
|
||||||
*.parquet
|
*.parquet
|
||||||
|
# Python 缓存与编译文件
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# 虚拟环境(非常重要,防止把本地依赖包提交上去)
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.idea
|
||||||
|
.venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 操作系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# pytest 测试产物
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
coverage.xml
|
||||||
|
htmlcov/
|
||||||
|
junit.xml
|
||||||
|
|
||||||
|
# mypy 类型检查缓存
|
||||||
|
.mypy_cache/
|
||||||
|
# 向量库、本地数据库、模型文件(大文件不要提交git)
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.milvus
|
||||||
|
*.neo4j
|
||||||
|
*.bin
|
||||||
|
*.pth
|
||||||
|
*.ckpt
|
||||||
|
*.onnx
|
||||||
|
|
||||||
|
# 本地导出数据、临时csv/excel
|
||||||
|
data/tmp/
|
||||||
|
data/output/
|
||||||
|
*.csv
|
||||||
|
*.xlsx
|
||||||
|
*.parquet
|
||||||
|
|
||||||
|
# 本地临时需求&开发计划文档
|
||||||
|
DK2_客服Agent模块完整开发计划(v1.1).md
|
||||||
|
DK4_客服Agent需求文档(修复完善版v1.4).md
|
||||||
|
客服Agent模块 · TODO List(最终修复版v1.2).md
|
||||||
|
|
||||||
|
|
||||||
DK2_客服Agent模块完整开发计划(v1.1).md
|
DK2_客服Agent模块完整开发计划(v1.1).md
|
||||||
DK4_客服Agent需求文档(修复完善版v1.4).md
|
DK4_客服Agent需求文档(修复完善版v1.4).md
|
||||||
|
|||||||
Generated
-10
@@ -1,10 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 已忽略包含查询文件的默认文件夹
|
|
||||||
/queries/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
Generated
-1
@@ -1 +0,0 @@
|
|||||||
Mutual_Fund
|
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
|
||||||
<option name="chatAppRouterInfo" value="chat-session" />
|
|
||||||
<option name="progress" value="1.0" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-20
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ClaudeCodeTabState">
|
|
||||||
<option name="tabSessions">
|
|
||||||
<map>
|
|
||||||
<entry key="0">
|
|
||||||
<value>
|
|
||||||
<TabSessionState>
|
|
||||||
<option name="provider" value="claude" />
|
|
||||||
<option name="sessionId" value="ed440cf1-c034-44b5-b5db-bc5afb824005" />
|
|
||||||
<option name="cwd" value="D:\Projects\py\Mutual_Fund" />
|
|
||||||
<option name="model" value="claude-sonnet-5[1m]" />
|
|
||||||
<option name="permissionMode" value="default" />
|
|
||||||
</TabSessionState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/Mutual_Fund.iml" filepath="$PROJECT_DIR$/.idea/Mutual_Fund.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""Anonymous customer-service Agent HTTP endpoints."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
|
from service.customer_agent.chat import QueryTooLongError
|
||||||
|
from agent.customer_agent.session import SessionOwnershipError
|
||||||
|
from utils.request_id import get_request_id, new_request_id
|
||||||
|
from utils.response import fail, success
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def _runtime(request: Request):
|
||||||
|
runtime = getattr(request.app.state, "customer_agent_runtime", None)
|
||||||
|
if runtime is None:
|
||||||
|
raise RuntimeError("customer agent runtime is not configured")
|
||||||
|
return runtime
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/session/create")
|
||||||
|
async def create_session(request: Request):
|
||||||
|
runtime = _runtime(request)
|
||||||
|
session_id = await runtime.session_service.create_session()
|
||||||
|
return success({"session_id": session_id, "customer_id": None})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/chat")
|
||||||
|
async def chat(request: Request, session_id: str, query: str):
|
||||||
|
runtime = _runtime(request)
|
||||||
|
trace_id = request.headers.get("X-Trace-Id") or get_request_id() or new_request_id()
|
||||||
|
try:
|
||||||
|
await runtime.session_service.verify_session_ownership(session_id)
|
||||||
|
retry_after = await runtime.session_service.consume_chat_quota(session_id)
|
||||||
|
if retry_after is not None:
|
||||||
|
response = fail(429, "请求过于频繁", {"retry_after": retry_after})
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=429,
|
||||||
|
headers={"Retry-After": str(retry_after)},
|
||||||
|
content=response.model_dump(),
|
||||||
|
)
|
||||||
|
result = await runtime.agent.handle(session_id, query, trace_id=trace_id)
|
||||||
|
except SessionOwnershipError as exc:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=exc.code,
|
||||||
|
content=fail(exc.code, exc.message).model_dump(),
|
||||||
|
)
|
||||||
|
except QueryTooLongError as exc:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content=fail(400, str(exc)).model_dump(),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def events():
|
||||||
|
yield f"data: {json.dumps(result, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
events(), media_type="text/event-stream", headers={"X-Trace-Id": trace_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/session/end")
|
||||||
|
async def end_session(request: Request, body: dict):
|
||||||
|
runtime = _runtime(request)
|
||||||
|
session_id = body.get("session_id", "")
|
||||||
|
try:
|
||||||
|
await runtime.session_service.verify_session_ownership(session_id)
|
||||||
|
except SessionOwnershipError as exc:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=exc.code,
|
||||||
|
content=fail(exc.code, exc.message).model_dump(),
|
||||||
|
)
|
||||||
|
if hasattr(runtime.redis, "delete"):
|
||||||
|
await runtime.redis.delete(
|
||||||
|
f"session:{session_id}", f"session:{session_id}:messages",
|
||||||
|
f"rate:limit:anon:{session_id}:chat",
|
||||||
|
)
|
||||||
|
return success({"session_id": session_id, "archived": False})
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"""Knowledge-base upload, preview, and confirmation endpoints."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from service.knowledge_base.upload import KnowledgeUploadService, UploadValidationError
|
||||||
|
from api.deps import require_knowledge_operator
|
||||||
|
from model.sys_user import SysUser
|
||||||
|
from utils.response import fail, success
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def _service(request: Request) -> KnowledgeUploadService:
|
||||||
|
service = getattr(request.app.state, "knowledge_upload_service", None)
|
||||||
|
if service is None:
|
||||||
|
raise RuntimeError("knowledge upload service is not configured")
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_int(value):
|
||||||
|
return None if value in (None, "") else int(value)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/documents")
|
||||||
|
async def list_documents(
|
||||||
|
request: Request,
|
||||||
|
_: SysUser = Depends(require_knowledge_operator),
|
||||||
|
):
|
||||||
|
return success(await _service(request).list_documents())
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/documents/{doc_id}")
|
||||||
|
async def get_document(
|
||||||
|
request: Request,
|
||||||
|
doc_id: str,
|
||||||
|
_: SysUser = Depends(require_knowledge_operator),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
result = await _service(request).get_document(doc_id)
|
||||||
|
if result is None:
|
||||||
|
response = fail(404, "文档不存在")
|
||||||
|
return JSONResponse(status_code=404, content=response.model_dump())
|
||||||
|
return success(result)
|
||||||
|
except (UploadValidationError, ValueError) as exc:
|
||||||
|
response = fail(400, str(exc))
|
||||||
|
return JSONResponse(status_code=400, content=response.model_dump())
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/documents/preview")
|
||||||
|
async def preview_document_upload(
|
||||||
|
request: Request,
|
||||||
|
_: SysUser = Depends(require_knowledge_operator),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
form = await request.form()
|
||||||
|
upload = form.get("file")
|
||||||
|
if upload is None or not hasattr(upload, "read"):
|
||||||
|
raise UploadValidationError("缺少上传文件")
|
||||||
|
result = await _service(request).preview(
|
||||||
|
upload.filename,
|
||||||
|
await upload.read(),
|
||||||
|
strategy=form.get("strategy", ""),
|
||||||
|
chunk_size=_optional_int(form.get("chunk_size")),
|
||||||
|
chunk_overlap=_optional_int(form.get("chunk_overlap")),
|
||||||
|
)
|
||||||
|
return success(result)
|
||||||
|
except (UploadValidationError, ValueError) as exc:
|
||||||
|
response = fail(400, str(exc))
|
||||||
|
return JSONResponse(status_code=400, content=response.model_dump())
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/documents/confirm")
|
||||||
|
async def confirm_document_upload(
|
||||||
|
request: Request,
|
||||||
|
_: SysUser = Depends(require_knowledge_operator),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
result = await _service(request).confirm(
|
||||||
|
upload_id=body.get("upload_id", ""),
|
||||||
|
title=body.get("title", ""),
|
||||||
|
doc_id=body.get("doc_id", ""),
|
||||||
|
collection_name=body.get("collection_name", ""),
|
||||||
|
strategy=body.get("strategy", ""),
|
||||||
|
chunk_size=body.get("chunk_size"),
|
||||||
|
chunk_overlap=body.get("chunk_overlap"),
|
||||||
|
)
|
||||||
|
return success(result)
|
||||||
|
except (UploadValidationError, ValueError) as exc:
|
||||||
|
response = fail(400, str(exc))
|
||||||
|
return JSONResponse(status_code=400, content=response.model_dump())
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/documents/{doc_id}")
|
||||||
|
async def delete_document(
|
||||||
|
request: Request,
|
||||||
|
doc_id: str,
|
||||||
|
_: SysUser = Depends(require_knowledge_operator),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
result = await _service(request).delete_document(doc_id)
|
||||||
|
return success(result)
|
||||||
|
except (UploadValidationError, ValueError) as exc:
|
||||||
|
response = fail(400, str(exc))
|
||||||
|
return JSONResponse(status_code=400, content=response.model_dump())
|
||||||
+10
@@ -30,6 +30,16 @@ async def get_current_user(
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def require_knowledge_operator(user: SysUser = Depends(get_current_user)) -> SysUser:
|
||||||
|
"""Allow only administrators or explicitly assigned knowledge operators."""
|
||||||
|
if user.user_type == "ADMIN":
|
||||||
|
return user
|
||||||
|
if user.user_type != "EMPLOYEE" or user.employee_role not in KNOWLEDGE_OPERATOR_ROLES:
|
||||||
|
raise ForbiddenError("仅运营人员可以管理知识库")
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def require_knowledge_operator(
|
async def require_knowledge_operator(
|
||||||
user: SysUser = Depends(get_current_user),
|
user: SysUser = Depends(get_current_user),
|
||||||
) -> SysUser:
|
) -> SysUser:
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
"""
|
"""
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from api.chat import customer_agent, knowledge
|
||||||
|
from api.routers import product, questionnaire
|
||||||
|
from api.routers import account, auth, holdings, purchase, redeem
|
||||||
from api.routers import auth, customer_agent, knowledge, product, questionnaire
|
from api.routers import auth, customer_agent, knowledge, product, questionnaire
|
||||||
from api.routers import account, holdings, purchase, redeem
|
from api.routers import account, holdings, purchase, redeem
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ def client() -> AsyncMilvusClient:
|
|||||||
return _client
|
return _client
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_database(milvus_client: AsyncMilvusClient | None = None) -> None:
|
||||||
|
"""Create the configured project database only when it does not exist."""
|
||||||
|
target = milvus_client or client()
|
||||||
|
database_name = settings.milvus.db_name
|
||||||
|
if not database_name:
|
||||||
|
raise RuntimeError("MILVUS_DB must be configured")
|
||||||
|
if database_name not in await target.list_databases():
|
||||||
|
await target.create_database(database_name)
|
||||||
|
|
||||||
|
|
||||||
async def init_db() -> None:
|
async def init_db() -> None:
|
||||||
client() # 急切建连;失败由注册表重试(config/database/__init__.py)
|
client() # 急切建连;失败由注册表重试(config/database/__init__.py)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
fastapi
|
||||||
|
python-multipart
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
pyjwt
|
||||||
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
|
sqlalchemy
|
||||||
|
aiomysql
|
||||||
|
redis
|
||||||
|
pymilvus
|
||||||
|
neo4j
|
||||||
|
python-dotenv
|
||||||
|
pypdf
|
||||||
|
|
||||||
fastapi~=0.141.1
|
fastapi~=0.141.1
|
||||||
sqlalchemy~=2.0.52
|
sqlalchemy~=2.0.52
|
||||||
httpx~=0.28.1
|
httpx~=0.28.1
|
||||||
|
|||||||
+4
-28
@@ -265,6 +265,7 @@ CREATE TABLE IF NOT EXISTS conversation_archive (
|
|||||||
role VARCHAR(16) NOT NULL COMMENT 'user/assistant/system',
|
role VARCHAR(16) NOT NULL COMMENT 'user/assistant/system',
|
||||||
content MEDIUMTEXT NULL COMMENT '对话内容',
|
content MEDIUMTEXT NULL COMMENT '对话内容',
|
||||||
tool_calls JSON NULL COMMENT '工具调用记录 [{"tool":"nl2sql",...}]',
|
tool_calls JSON NULL COMMENT '工具调用记录 [{"tool":"nl2sql",...}]',
|
||||||
|
trace_id VARCHAR(64) NULL COMMENT '请求链路追踪ID',
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
trace_id VARCHAR(64) NULL COMMENT '请求链路追踪ID',
|
trace_id VARCHAR(64) NULL COMMENT '请求链路追踪ID',
|
||||||
KEY idx_session (session_id),
|
KEY idx_session (session_id),
|
||||||
@@ -341,7 +342,7 @@ CREATE TABLE IF NOT EXISTS audit_log (
|
|||||||
target VARCHAR(128) NULL COMMENT '操作对象(单号/ID)',
|
target VARCHAR(128) NULL COMMENT '操作对象(单号/ID)',
|
||||||
detail TEXT NULL COMMENT '详情(JSON 字符串)',
|
detail TEXT NULL COMMENT '详情(JSON 字符串)',
|
||||||
ip VARCHAR(64) NULL,
|
ip VARCHAR(64) NULL,
|
||||||
trace_id VARCHAR(64) NULL COMMENT '链路号',
|
trace_id VARCHAR(32) NULL COMMENT '链路号',
|
||||||
status VARCHAR(8) NOT NULL DEFAULT '成功' COMMENT '成功/失败',
|
status VARCHAR(8) NOT NULL DEFAULT '成功' COMMENT '成功/失败',
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
KEY idx_user (user_id),
|
KEY idx_user (user_id),
|
||||||
@@ -410,16 +411,7 @@ CREATE TABLE IF NOT EXISTS memory_unit (
|
|||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/demoted/archived/deleted',
|
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/demoted/archived/deleted',
|
||||||
valid_until DATE NULL COMMENT '有效期',
|
valid_until DATE NULL COMMENT '有效期',
|
||||||
KEY idx_customer_status (customer_id, status),
|
KEY idx_customer_status (customer_id, status),
|
||||||
KEY idx_tag (tag),
|
KEY idx_tag (tag)
|
||||||
session_id VARCHAR(64) NULL COMMENT '记忆来源会话',
|
|
||||||
agent_run_id VARCHAR(64) NULL COMMENT '产生该记忆的Agent运行ID',
|
|
||||||
evidence_ref VARCHAR(128) NULL COMMENT '证据引用',
|
|
||||||
milvus_id VARCHAR(128) NULL COMMENT 'Milvus向量ID',
|
|
||||||
graph_node_id VARCHAR(128) NULL COMMENT 'Neo4j节点或关系ID',
|
|
||||||
valid_from DATETIME NULL COMMENT '记忆生效时间',
|
|
||||||
last_verified_at DATETIME NULL COMMENT '最近确认时间',
|
|
||||||
KEY idx_customer_tag_status (customer_id, tag, status),
|
|
||||||
KEY idx_agent_run (agent_run_id);
|
|
||||||
) COMMENT='记忆单元表(三层记忆中期主体,向量镜像在 Milvus customer_memory)';
|
) COMMENT='记忆单元表(三层记忆中期主体,向量镜像在 Milvus customer_memory)';
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
@@ -450,20 +442,4 @@ CREATE TABLE IF NOT EXISTS portfolio_benchmark (
|
|||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
UNIQUE KEY uk_risk_level (risk_level)
|
UNIQUE KEY uk_risk_level (risk_level)
|
||||||
) COMMENT='组合基准配置表(投顾Agent 再平衡参照,运营可调整)';
|
) COMMENT='组合基准配置表(投顾Agent 再平衡参照,运营可调整)';
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
|
||||||
-- 25 客户资金账户表(现金账户,一人一户)
|
|
||||||
-- ---------------------------------------------------------------------
|
|
||||||
CREATE TABLE IF NOT EXISTS fin_account (
|
|
||||||
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID,一对一关联 sys_user.id',
|
|
||||||
balance DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '账户总余额(元),含冻结部分;可用余额=balance-frozen_amount',
|
|
||||||
frozen_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '冻结金额(元),申购在途/待确认订单占用',
|
|
||||||
currency VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '币种,当前仅 CNY',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT '正常' COMMENT '账户状态:正常/冻结/销户',
|
|
||||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,扣款/入账 CAS 用',
|
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (customer_id),
|
|
||||||
KEY idx_status (status)
|
|
||||||
) COMMENT='客户资金账户表(现金余额,申购扣款/赎回入账的账务载体)';
|
|
||||||
@@ -5,6 +5,7 @@ from types import SimpleNamespace
|
|||||||
import httpx
|
import httpx
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from api.chat.customer_agent import router
|
||||||
from api.routers.customer_agent import router
|
from api.routers.customer_agent import router
|
||||||
from rag.intent import Intent
|
from rag.intent import Intent
|
||||||
from service.customer_agent.chat import AnonymousCustomerAgent
|
from service.customer_agent.chat import AnonymousCustomerAgent
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import unittest
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from api.chat.knowledge import (
|
||||||
from api.routers.knowledge import (
|
from api.routers.knowledge import (
|
||||||
confirm_document_upload,
|
confirm_document_upload,
|
||||||
delete_document,
|
delete_document,
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ class Code:
|
|||||||
SERVER_ERROR = 500
|
SERVER_ERROR = 500
|
||||||
LLM_FAIL = 1001 # LLM 调用失败
|
LLM_FAIL = 1001 # LLM 调用失败
|
||||||
KB_NO_RESULT = 1002 # 知识库检索无结果
|
KB_NO_RESULT = 1002 # 知识库检索无结果
|
||||||
SQL_GEN_FAIL = 1003 # NL2SQL 生成失败
|
SQL_GEN_FAIL = 1003 # nl2sql 生成失败
|
||||||
RISK_TRIGGERED = 1004 # 风控规则触发 / 交易被拦截
|
RISK_TRIGGERED = 1004 # 风控规则触发 / 交易被拦截
|
||||||
NOT_SUITABLE = 1005 # 适当性不匹配
|
NOT_SUITABLE = 1005 # 适当性不匹配
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user