Files
2026-07-06 09:16:25 +08:00

125 lines
4.5 KiB
Python

import json
from typing import Callable, Optional
from mod.project.agent.social.unified_message import UnifiedMessage
class SocialEngine:
"""社交对话引擎, 封装 comMod._chat_generator"""
def __init__(self, com_mod=None):
self._com_mod = com_mod
def reply(
self,
message: UnifiedMessage,
on_stage: Optional[Callable[[str, str], None]] = None,
cancel_event=None,
account_config: dict = None,
) -> str:
"""
处理社交消息, 依据 SSE 事件阶段调用 on_stage 回调
on_stage(stage, content):
("thinking", "🤔 思考中...") - 首次 message_think
("tool_call", "🔧 {name}") - 每次 tool_call
("reply", content) - message_end 时发送累积正文
("error", error_text) - error 事件
"""
if not self._com_mod:
from mod.project.agent.comMod import main as ComModMain
self._com_mod = ComModMain()
self._com_mod.refresh_runtime_state()
account_config = account_config or {}
get = {
"message": message.content,
"session_id": message.session_key,
"prompt_id": "social_chat",
"smartMode": account_config.get("smart_mode") or "0",
"account_name": account_config.get("account_name") or "default",
"model": account_config.get("model") or "qwen3.5-flash",
}
accumulated = ""
thinking_sent = False
gen = self._com_mod._chat_generator(get)
try:
for sse_text in gen:
if self._is_cancelled(cancel_event):
return accumulated.strip()
event_type, data = self._parse_sse(sse_text)
if not event_type:
continue
if event_type == "message_think":
if not thinking_sent:
thinking_sent = True
self._emit_stage(on_stage, cancel_event, "thinking", "🤔 Thinking...")
elif event_type == "tool_call":
tool_name = data.get("tool", "Tool") if isinstance(data, dict) else "Tool"
self._emit_stage(on_stage, cancel_event, "tool_call", f"🔧 {tool_name}")
elif event_type == "message":
chunk = data if isinstance(data, str) else data.get("response", "")
accumulated += chunk
elif event_type == "error":
error_msg = data.get("msg", data) if isinstance(data, dict) else data
error_text = f"Error: {error_msg}"
self._emit_stage(on_stage, cancel_event, "error", error_text)
return error_text
elif event_type == "message_end":
break
if accumulated.strip() and not self._is_cancelled(cancel_event):
self._emit_stage(on_stage, cancel_event, "reply", accumulated.strip())
return accumulated.strip()
finally:
close = getattr(gen, "close", None)
if close:
try:
close()
except Exception:
pass
def _emit_stage(self, on_stage: Optional[Callable[[str, str], None]], cancel_event, stage: str, content: str):
"""未取消时发送阶段消息"""
if on_stage and not self._is_cancelled(cancel_event):
on_stage(stage, content)
def _is_cancelled(self, cancel_event) -> bool:
"""判断当前回复是否已取消"""
return bool(cancel_event and cancel_event.is_set())
def _parse_sse(self, sse_text: str) -> tuple:
"""解析 SSE 文本, 返回 (event_type, data)"""
if not sse_text.startswith("event: "):
return None, None
lines = sse_text.split("\n")
event_type = None
data = None
for line in lines:
if line.startswith("event: "):
event_type = line[7:]
elif line.startswith("data: "):
data_str = line[6:]
# SSE 转义换行反转义
data_str = data_str.replace('\\n', '\n')
if event_type in ("message", "message_think"):
data = data_str
continue
try:
data = json.loads(data_str)
except json.JSONDecodeError:
data = data_str
return event_type, data