feat: 完整功能部署 v1.0
新增功能: - 天网协作系统 (HTTP API 端口 6060) - 三种工作流 (查找图片/处理图片/转人工派单) - 图片任务数据库 (支持客户后续增加需求) - 图绘派单系统集成 (API: 8005) - 文字检测与加价 (60-80 元高价值订单) - 风险评估与接单判断 - 作图失败自动转人工 新增文档: - 项目功能汇总.md - 三种工作流功能说明.md - 文字加价功能说明.md - 风险评估功能说明.md - 图片任务数据库功能说明.md - 图绘派单系统集成说明.md - 作图失败转接人工说明.md - DEPLOYMENT.md - TIANWANG_INTEGRATION.md 核心修改: - core/pydantic_ai_agent.py - core/workflow.py - core/websocket_client.py - image/image_analyzer.py - services/service_tuhui_dispatch.py - db/image_tasks_db.py 版本:v1.0 日期:2026-02-28
This commit is contained in:
0
scripts/__pycache__/chat_ui.cpython-310.pyc
Normal file → Executable file
0
scripts/__pycache__/chat_ui.cpython-310.pyc
Normal file → Executable file
0
scripts/chat_log_viewer.py
Normal file → Executable file
0
scripts/chat_log_viewer.py
Normal file → Executable file
0
scripts/chat_ui.py
Normal file → Executable file
0
scripts/chat_ui.py
Normal file → Executable file
0
scripts/init_designer_roster.py
Normal file → Executable file
0
scripts/init_designer_roster.py
Normal file → Executable file
196
scripts/multi_process_launcher.py
Normal file
196
scripts/multi_process_launcher.py
Normal file
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
多进程异步并行启动器
|
||||
按客户 ID hash 分配到不同进程,实现真正的并行处理
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import logging
|
||||
from multiprocessing import Process, cpu_count
|
||||
from typing import List, Dict
|
||||
import hashlib
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='[%(asctime)s] %(levelname)s: %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WorkerProcess:
|
||||
"""工作进程"""
|
||||
|
||||
def __init__(self, worker_id: int, shard_keys: List[str]):
|
||||
self.worker_id = worker_id
|
||||
self.shard_keys = shard_keys
|
||||
self.process = None
|
||||
|
||||
def start(self):
|
||||
"""启动工作进程"""
|
||||
self.process = Process(
|
||||
target=self._run,
|
||||
args=(self.worker_id, self.shard_keys),
|
||||
name=f"ai-cs-worker-{self.worker_id}"
|
||||
)
|
||||
self.process.start()
|
||||
logger.info(f"Worker {self.worker_id} 启动 (PID: {self.process.pid})")
|
||||
|
||||
def _run(self, worker_id: int, shard_keys: List[str]):
|
||||
"""工作进程入口"""
|
||||
try:
|
||||
# 设置进程环境变量
|
||||
os.environ['AI_CS_WORKER_ID'] = str(worker_id)
|
||||
os.environ['AI_CS_SHARD_KEYS'] = ','.join(shard_keys)
|
||||
|
||||
# 导入并启动 WebSocket 客户端
|
||||
from core.websocket_client import QingjianAPIClient
|
||||
|
||||
logger.info(f"Worker {worker_id} 初始化 Agent...")
|
||||
client = QingjianAPIClient(enable_agent=True)
|
||||
|
||||
# 只处理分配给这个 worker 的客户
|
||||
client.shard_keys = set(shard_keys)
|
||||
|
||||
logger.info(f"Worker {worker_id} 开始处理消息...")
|
||||
import asyncio
|
||||
asyncio.run(client.connect())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info(f"Worker {worker_id} 收到退出信号")
|
||||
except Exception as e:
|
||||
logger.error(f"Worker {worker_id} 异常:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def stop(self):
|
||||
"""停止工作进程"""
|
||||
if self.process and self.process.is_alive():
|
||||
self.process.terminate()
|
||||
self.process.join(timeout=5)
|
||||
logger.info(f"Worker {self.worker_id} 已停止")
|
||||
|
||||
|
||||
class Coordinator:
|
||||
"""协调器 - 管理多个工作进程"""
|
||||
|
||||
def __init__(self, num_workers: int = None):
|
||||
self.num_workers = num_workers or max(2, cpu_count())
|
||||
self.workers: List[WorkerProcess] = []
|
||||
self.running = False
|
||||
|
||||
def _get_shard_key(self, acc_id: str, from_id: str) -> int:
|
||||
"""根据店铺 ID + 客户 ID 计算分片 key"""
|
||||
key = f"{acc_id}:{from_id}"
|
||||
hash_value = int(hashlib.md5(key.encode()).hexdigest(), 16)
|
||||
return hash_value % self.num_workers
|
||||
|
||||
def _load_customer_shards(self) -> Dict[int, List[str]]:
|
||||
"""加载客户分片信息
|
||||
|
||||
Returns:
|
||||
{shard_id: [customer_key1, customer_key2, ...]}
|
||||
"""
|
||||
# 从数据库或配置文件加载客户列表
|
||||
# 这里简化处理,实际应该从数据库加载活跃客户
|
||||
shards = {i: [] for i in range(self.num_workers)}
|
||||
|
||||
# TODO: 从数据库加载活跃客户列表
|
||||
# customers = db.query(...).all()
|
||||
# for customer in customers:
|
||||
# shard_id = self._get_shard_key(customer.acc_id, customer.from_id)
|
||||
# shards[shard_id].append(f"{customer.acc_id}:{customer.from_id}")
|
||||
|
||||
logger.info(f"已加载 {sum(len(v) for v in shards.values())} 个客户分片")
|
||||
return shards
|
||||
|
||||
def start(self):
|
||||
"""启动所有工作进程"""
|
||||
logger.info(f"启动协调器,工作进程数:{self.num_workers}")
|
||||
|
||||
shards = self._load_customer_shards()
|
||||
|
||||
# 启动工作进程
|
||||
for worker_id in range(self.num_workers):
|
||||
worker = WorkerProcess(
|
||||
worker_id=worker_id,
|
||||
shard_keys=shards.get(worker_id, [])
|
||||
)
|
||||
worker.start()
|
||||
self.workers.append(worker)
|
||||
|
||||
self.running = True
|
||||
|
||||
# 注册信号处理
|
||||
signal.signal(signal.SIGINT, self._signal_handler)
|
||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||
|
||||
# 监控工作进程
|
||||
self._monitor_workers()
|
||||
|
||||
def _monitor_workers(self):
|
||||
"""监控工作进程健康状态"""
|
||||
import time
|
||||
|
||||
while self.running:
|
||||
# 检查工作进程是否存活
|
||||
for worker in self.workers:
|
||||
if worker.process and not worker.process.is_alive():
|
||||
logger.warning(f"Worker {worker.worker_id} 已退出,尝试重启...")
|
||||
# 重启工作进程
|
||||
worker.start()
|
||||
|
||||
time.sleep(10) # 每 10 秒检查一次
|
||||
|
||||
def _signal_handler(self, signum, frame):
|
||||
"""信号处理"""
|
||||
logger.info(f"收到信号 {signum},正在停止所有工作进程...")
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
"""停止所有工作进程"""
|
||||
self.running = False
|
||||
|
||||
for worker in self.workers:
|
||||
worker.stop()
|
||||
|
||||
logger.info("所有工作进程已停止")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='AI 客服多进程启动器')
|
||||
parser.add_argument(
|
||||
'--workers',
|
||||
type=int,
|
||||
default=None,
|
||||
help='工作进程数(默认:CPU 核心数)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("AI 客服系统 - 多进程异步并行模式")
|
||||
logger.info("=" * 60)
|
||||
|
||||
coordinator = Coordinator(num_workers=args.workers)
|
||||
|
||||
try:
|
||||
coordinator.start()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("收到退出信号")
|
||||
coordinator.stop()
|
||||
except Exception as e:
|
||||
logger.error(f"启动失败:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user