feat: automate image pipeline and simplify gemini flow
This commit is contained in:
@@ -18,6 +18,7 @@ from db.pending_transfer_db import (
|
||||
retry_pending_transfer,
|
||||
)
|
||||
from services.dispatch_service import dispatch_service
|
||||
from services.service_auto_image_pipeline import auto_image_pipeline_service
|
||||
|
||||
logger = logging.getLogger("cs_agent")
|
||||
|
||||
@@ -86,6 +87,7 @@ class SystemOrchestrator:
|
||||
self._user_locks: Dict[str, asyncio.Lock] = {}
|
||||
self._pending_transfer_task: Optional[asyncio.Task] = None
|
||||
self._last_retry_transfer_time: Dict[str, float] = {}
|
||||
self._auto_pipeline_jobs: Dict[str, float] = {}
|
||||
|
||||
bus.subscribe("MESSAGE_OUTBOUND", self.handle_outbound_event)
|
||||
|
||||
@@ -223,6 +225,102 @@ class SystemOrchestrator:
|
||||
return "我在帮你看记录,稍等哈"
|
||||
return cleaned
|
||||
|
||||
@staticmethod
|
||||
def _extract_designer_name(transfer_cmd: str) -> str:
|
||||
text = str(transfer_cmd or "").strip()
|
||||
match = re.search(r"\[转移会话\],([^,]+),", text)
|
||||
return str(match.group(1)).strip() if match else ""
|
||||
|
||||
@staticmethod
|
||||
def _infer_processing_intent(requirement_text: str, history: Optional[List[dict]] = None) -> str:
|
||||
combined_parts = [str(requirement_text or "").lower()]
|
||||
for item in history or []:
|
||||
if item.get("role") == "user":
|
||||
combined_parts.append(str(item.get("content") or "").lower())
|
||||
combined = "\n".join(combined_parts)
|
||||
repair_keywords = ("修复", "高清", "清晰", "放大", "老照片")
|
||||
if any(k in combined for k in repair_keywords):
|
||||
return "repair"
|
||||
return "find_original"
|
||||
|
||||
@staticmethod
|
||||
def _collect_recent_image_urls(history: List[dict], fallback_urls: Optional[List[str]] = None) -> List[str]:
|
||||
urls: List[str] = []
|
||||
seen = set()
|
||||
|
||||
def add_url(url: str):
|
||||
value = str(url or "").strip()
|
||||
if not value or value in seen:
|
||||
return
|
||||
seen.add(value)
|
||||
urls.append(value)
|
||||
|
||||
for url in fallback_urls or []:
|
||||
add_url(url)
|
||||
|
||||
for item in reversed(history or []):
|
||||
if item.get("role") != "user":
|
||||
continue
|
||||
raw_urls = item.get("image_urls") or []
|
||||
if isinstance(raw_urls, str):
|
||||
for part in re.split(r"[\n#]+", raw_urls):
|
||||
add_url(part)
|
||||
elif isinstance(raw_urls, list):
|
||||
for part in raw_urls:
|
||||
add_url(part)
|
||||
content = str(item.get("content") or "")
|
||||
for match in re.findall(r"https?://[^\s#]+", content):
|
||||
add_url(match)
|
||||
if len(urls) >= 5:
|
||||
break
|
||||
return urls
|
||||
|
||||
def _schedule_auto_pipeline(
|
||||
self,
|
||||
*,
|
||||
session_key: str,
|
||||
customer_id: str,
|
||||
acc_id: str,
|
||||
designer_name: str,
|
||||
requirement_text: str,
|
||||
history: List[dict],
|
||||
image_urls: Optional[List[str]] = None,
|
||||
):
|
||||
resolved_urls = self._collect_recent_image_urls(history, image_urls)
|
||||
if not resolved_urls:
|
||||
logger.info(f"[Orchestrator] 自动处理跳过:未找到客户图片 user={customer_id} acc={acc_id}")
|
||||
return
|
||||
|
||||
intent = self._infer_processing_intent(requirement_text, history)
|
||||
signature_src = f"{session_key}|{designer_name}|{intent}|{'|'.join(resolved_urls)}"
|
||||
signature = str(abs(hash(signature_src)))
|
||||
now = time.time()
|
||||
last_run = self._auto_pipeline_jobs.get(signature, 0.0)
|
||||
if now - last_run < 600:
|
||||
logger.info(f"[Orchestrator] 自动处理已在近期触发,跳过重复任务 user={customer_id} acc={acc_id}")
|
||||
return
|
||||
self._auto_pipeline_jobs[signature] = now
|
||||
|
||||
async def _runner():
|
||||
try:
|
||||
result = await auto_image_pipeline_service.process_and_notify(
|
||||
session_key=session_key,
|
||||
customer_id=customer_id,
|
||||
acc_id=acc_id,
|
||||
designer_name=designer_name,
|
||||
requirement_text=requirement_text,
|
||||
image_urls=resolved_urls,
|
||||
intent=intent,
|
||||
)
|
||||
logger.info(
|
||||
f"[Orchestrator] 自动处理完成 user={customer_id} acc={acc_id} "
|
||||
f"ok={result.get('success')} uploaded={len(result.get('uploaded') or [])}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"[Orchestrator] 自动处理失败 user={customer_id} acc={acc_id}: {e}")
|
||||
|
||||
asyncio.create_task(_runner())
|
||||
|
||||
async def on_raw_message_received(self, platform: str, raw_data: dict):
|
||||
"""链路入口"""
|
||||
try:
|
||||
@@ -339,7 +437,7 @@ class SystemOrchestrator:
|
||||
except Exception as e:
|
||||
logger.warning(f"[Orchestrator] 订单消息处理异常: {e}")
|
||||
|
||||
async def _analyze_images_background(self, session_key: str, image_urls: List[str]):
|
||||
async def _analyze_images_background(self, session_key: str, image_urls: List[str], requirement_text: str = ""):
|
||||
"""后台静默分析图片,存入用户数据库用于数据标定"""
|
||||
try:
|
||||
from services.service_image_analyzer import image_analyzer_service
|
||||
@@ -348,9 +446,9 @@ class SystemOrchestrator:
|
||||
db = CustomerDatabase()
|
||||
profile = db.get_customer(session_key)
|
||||
|
||||
for url in image_urls:
|
||||
for url in (image_urls or [])[:1]:
|
||||
try:
|
||||
result = await image_analyzer_service.analyze(url)
|
||||
result = await image_analyzer_service.analyze(url, customer_requirement=requirement_text)
|
||||
result_json = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
# 更新最近一次分析
|
||||
@@ -454,7 +552,7 @@ class SystemOrchestrator:
|
||||
|
||||
# B2. 后台图片分析(不阻塞主流程,用于数据标定)
|
||||
if all_image_urls:
|
||||
asyncio.create_task(self._analyze_images_background(session_key, all_image_urls))
|
||||
asyncio.create_task(self._analyze_images_background(session_key, all_image_urls, combined_content))
|
||||
|
||||
history_start = time.time()
|
||||
history = await repo.get_chat_history(user_id, limit=12, acc_id=acc_id)
|
||||
@@ -504,6 +602,7 @@ class SystemOrchestrator:
|
||||
|
||||
# 转接场景:先发一句安抚话,再发转接指令
|
||||
if "[转移会话]" in std_res.reply_content:
|
||||
designer_name = self._extract_designer_name(std_res.reply_content)
|
||||
transfer_prelude = str(std_res.metadata.get("transfer_prelude") or "").strip()
|
||||
greet = StandardResponse(
|
||||
reply_content=transfer_prelude or "收到,我叫设计师来看下哈",
|
||||
@@ -547,6 +646,15 @@ class SystemOrchestrator:
|
||||
|
||||
if "[转移会话]" in std_res.reply_content:
|
||||
self._last_transfer_time[session_key] = time.time()
|
||||
self._schedule_auto_pipeline(
|
||||
session_key=session_key,
|
||||
customer_id=user_id,
|
||||
acc_id=acc_id,
|
||||
designer_name=self._extract_designer_name(std_res.reply_content),
|
||||
requirement_text=combined_content,
|
||||
history=history,
|
||||
image_urls=all_image_urls,
|
||||
)
|
||||
|
||||
except asyncio.CancelledError: pass
|
||||
except Exception as e: logger.exception(f"[Orchestrator] 处理失败: {e}")
|
||||
@@ -618,6 +726,15 @@ class SystemOrchestrator:
|
||||
)
|
||||
|
||||
self._last_transfer_time[f"{customer_id}@{acc_id}"] = time.time()
|
||||
history = await repo.get_chat_history(customer_id, limit=12, acc_id=acc_id)
|
||||
self._schedule_auto_pipeline(
|
||||
session_key=f"{customer_id}@{acc_id}",
|
||||
customer_id=customer_id,
|
||||
acc_id=acc_id,
|
||||
designer_name=designer_name,
|
||||
requirement_text=reason,
|
||||
history=history,
|
||||
)
|
||||
await asyncio.to_thread(complete_pending_transfer, row_id)
|
||||
logger.info(
|
||||
f"[Orchestrator] 待转接自动完成: pending_id={row_id} user={customer_id} designer={designer_name} reason={reason}"
|
||||
|
||||
Reference in New Issue
Block a user