fix: 飞书机器人群聊回复逻辑

This commit is contained in:
Jinyi Han
2026-03-19 09:57:00 +08:00
parent 34abe90bb2
commit 34452e2d4c
2 changed files with 459 additions and 26 deletions

View File

@@ -245,15 +245,15 @@ def _card(text):
return json.dumps({"config": {"wide_screen_mode": True}, "elements": [{"tag": "markdown", "content": text}]}, ensure_ascii=False)
def send_message(open_id, content, msg_type="text", use_card=False):
def send_message(receive_id, content, msg_type="text", use_card=False, receive_id_type="open_id"):
if use_card:
payload, real_type = _card(content), "interactive"
elif msg_type == "text":
payload, real_type = json.dumps({"text": content}, ensure_ascii=False), "text"
else:
payload, real_type = content, msg_type
body = CreateMessageRequest.builder().receive_id_type("open_id").request_body(
CreateMessageRequestBody.builder().receive_id(open_id).msg_type(real_type).content(payload).build()
body = CreateMessageRequest.builder().receive_id_type(receive_id_type).request_body(
CreateMessageRequestBody.builder().receive_id(receive_id).msg_type(real_type).content(payload).build()
).build()
response = client.im.v1.message.create(body)
if response.success():
@@ -367,29 +367,29 @@ def _describe_media(msg_type, file_path, filename):
return f"[{msg_type}]\n[File: source: {file_path}]"
def _send_local_file(open_id, file_path):
def _send_local_file(receive_id, file_path, receive_id_type="open_id"):
if not os.path.isfile(file_path):
send_message(open_id, f"⚠️ 文件不存在: {file_path}")
send_message(receive_id, f"⚠️ 文件不存在: {file_path}", receive_id_type=receive_id_type)
return False
ext = os.path.splitext(file_path)[1].lower()
if ext in _IMAGE_EXTS:
image_key = _upload_image_sync(file_path)
if image_key:
send_message(open_id, json.dumps({"image_key": image_key}, ensure_ascii=False), msg_type="image")
send_message(receive_id, json.dumps({"image_key": image_key}, ensure_ascii=False), msg_type="image", receive_id_type=receive_id_type)
return True
else:
file_key = _upload_file_sync(file_path)
if file_key:
msg_type = "media" if ext in _AUDIO_EXTS or ext in _VIDEO_EXTS else "file"
send_message(open_id, json.dumps({"file_key": file_key}, ensure_ascii=False), msg_type=msg_type)
send_message(receive_id, json.dumps({"file_key": file_key}, ensure_ascii=False), msg_type=msg_type, receive_id_type=receive_id_type)
return True
send_message(open_id, f"⚠️ 文件发送失败: {os.path.basename(file_path)}")
send_message(receive_id, f"⚠️ 文件发送失败: {os.path.basename(file_path)}", receive_id_type=receive_id_type)
return False
def _send_generated_files(open_id, raw_text):
def _send_generated_files(receive_id, raw_text, receive_id_type="open_id"):
for file_path in _extract_files(raw_text):
_send_local_file(open_id, file_path)
_send_local_file(receive_id, file_path, receive_id_type)
def _build_user_message(message):
@@ -430,21 +430,28 @@ def _build_user_message(message):
def handle_message(data):
event, message, sender = data.event, data.event.message, data.event.sender
open_id = sender.sender_id.open_id
chat_id = message.chat_id
if not PUBLIC_ACCESS and open_id not in ALLOWED_USERS:
print(f"未授权用户: {open_id}")
return
user_input, image_paths = _build_user_message(message)
if not user_input:
send_message(open_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}")
if chat_id:
send_message(chat_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}", receive_id_type="chat_id")
else:
send_message(open_id, f"⚠️ 暂不支持处理此类飞书消息:{message.message_type}")
return
print(f"收到消息 [{open_id}] ({message.message_type}, {len(image_paths)} images): {user_input[:200]}")
if message.message_type == "text" and user_input.startswith("/"):
return handle_command(open_id, user_input)
return handle_command(open_id, user_input, chat_id)
def run_agent():
user_tasks[open_id] = {"running": True}
try:
msg_id, dq, last_text = send_message(open_id, "思考中...", use_card=True), agent.put_task(user_input, source="feishu", images=image_paths), ""
if chat_id:
msg_id, dq, last_text = send_message(chat_id, "思考中...", use_card=True, receive_id_type="chat_id"), agent.put_task(user_input, source="feishu", images=image_paths), ""
else:
msg_id, dq, last_text = send_message(open_id, "思考中...", use_card=True), agent.put_task(user_input, source="feishu", images=image_paths), ""
while user_tasks.get(open_id, {}).get("running", False):
time.sleep(3)
item = None
@@ -462,47 +469,64 @@ def handle_message(data):
cut = show[-3000:]
if cut.count("```") % 2 == 1:
cut = "```\n" + cut
msg_id, last_text, show = send_message(open_id, "(继续...)", use_card=True), "", cut
if chat_id:
msg_id, last_text, show = send_message(chat_id, "(继续...)", use_card=True, receive_id_type="chat_id"), "", cut
else:
msg_id, last_text, show = send_message(open_id, "(继续...)", use_card=True), "", cut
display = show if done else show + ""
if display != last_text and msg_id:
update_message(msg_id, display)
last_text = display
if done:
_send_generated_files(open_id, raw)
if chat_id:
_send_generated_files(chat_id, raw, receive_id_type="chat_id")
else:
_send_generated_files(open_id, raw)
break
if not user_tasks.get(open_id, {}).get("running", True):
send_message(open_id, "已停止")
if chat_id:
send_message(chat_id, "已停止", receive_id_type="chat_id")
else:
send_message(open_id, "已停止")
except Exception as e:
import traceback
print(f"[ERROR] run_agent 异常: {e}")
traceback.print_exc()
send_message(open_id, f"错误: {str(e)}")
if chat_id:
send_message(chat_id, f"错误: {str(e)}", receive_id_type="chat_id")
else:
send_message(open_id, f"错误: {str(e)}")
finally:
user_tasks.pop(open_id, None)
threading.Thread(target=run_agent, daemon=True).start()
def handle_command(open_id, cmd):
def handle_command(open_id, cmd, chat_id=None):
def _send_cmd_response(content):
if chat_id:
send_message(chat_id, content, receive_id_type="chat_id")
else:
send_message(open_id, content)
if cmd == "/stop":
if open_id in user_tasks:
user_tasks[open_id]["running"] = False
agent.abort()
send_message(open_id, "正在停止...")
_send_cmd_response("正在停止...")
elif cmd == "/new":
agent.abort()
agent.history = []
send_message(open_id, "已清空当前共享上下文")
_send_cmd_response("已清空当前共享上下文")
elif cmd == "/help":
send_message(open_id, "命令列表:\n/stop - 停止当前任务\n/status - 查看状态\n/restore - 恢复上次对话历史\n/new - 开启新对话\n/help - 显示帮助")
_send_cmd_response("命令列表:\n/stop - 停止当前任务\n/status - 查看状态\n/restore - 恢复上次对话历史\n/new - 开启新对话\n/help - 显示帮助")
elif cmd == "/status":
send_message(open_id, f"状态: {'空闲' if not agent.is_running else '运行中'}")
_send_cmd_response(f"状态: {'空闲' if not agent.is_running else '运行中'}")
elif cmd == "/restore":
try:
files = glob.glob("./temp/model_responses_*.txt")
if not files:
return send_message(open_id, "没有找到历史记录")
return _send_cmd_response("没有找到历史记录")
latest = max(files, key=os.path.getmtime)
with open(latest, "r", encoding="utf-8") as f:
content = f.read()
@@ -515,11 +539,11 @@ def handle_command(open_id, cmd):
agent.history.extend([f"[USER]: {u}", f"[Agent] {r}"])
count += 1
agent.abort()
send_message(open_id, f"已恢复 {count} 轮对话\n来源: {os.path.basename(latest)}\n(仅恢复上下文,请输入新问题继续)")
_send_cmd_response(f"已恢复 {count} 轮对话\n来源: {os.path.basename(latest)}\n(仅恢复上下文,请输入新问题继续)")
except Exception as e:
send_message(open_id, f"恢复失败: {e}")
_send_cmd_response(f"恢复失败: {e}")
else:
send_message(open_id, f"未知命令: {cmd}")
_send_cmd_response(f"未知命令: {cmd}")
def main():