mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-06 17:47:39 +02:00
125 lines
4.5 KiB
Python
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
|