MetaCore/FeishuAgent/main.py

157 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import json
import lark_oapi as lark
from lark_oapi.api.im.v1 import *
import llm_agent
import searcher
# ================= 飞书应用配置 =================
# 你需要在本机的环境变量中设置这两个值,或者在这里替换成硬编码进行调试
APP_ID = os.getenv("FEISHU_APP_ID", "填写你的_cli_app_id")
APP_SECRET = os.getenv("FEISHU_APP_SECRET", "填写你的_app_secret")
# ================= 初始化客户端 =================
# 此 Client 用来主动发请求:如发文字消息,传文件等
lark_client = lark.Client.builder().app_id(APP_ID).app_secret(APP_SECRET).build()
def reply_text(message_id: str, text: str):
"""回复纯文本消息给指定的飞书消息ID"""
print(f">> 正在发送文本回复: {text}")
request = ReplyMessageRequest.builder() \
.message_id(message_id) \
.request_body(ReplyMessageRequestBody.builder()
.content(json.dumps({"text": text}))
.msg_type("text")
.build()) \
.build()
response = lark_client.im.v1.message.reply(request)
if not response.success():
print(f"发送文本消息失败! 代码: {response.code}, 信息: {response.msg}")
def reply_file(message_id: str, file_path: str):
"""上传文件并回复给指定消息"""
print(f">> 正在上传文件到飞书云端: {file_path}")
filename = os.path.basename(file_path)
# 1. 读文件字节
with open(file_path, "rb") as f:
file_bytes = f.read()
# 2. 上传文件到飞书的开放平台文件中心
create_file_req = CreateFileRequest.builder() \
.request_body(CreateFileRequestBody.builder()
.file_type("stream")
.file_name(filename)
.file(file_bytes)
.build()) \
.build()
file_resp = lark_client.im.v1.file.create(create_file_req)
if not file_resp.success():
print(f"上传文件失败: [{file_resp.code}] {file_resp.msg}")
reply_text(message_id, "文件上传飞书服务器失败,可能是文件格式拦截或网络问题,请稍后再试。")
return
file_key = file_resp.data.file_key
print(f"上传成功!获得 key: {file_key},准备发生该附件消息...")
# 3. 把获得的文件 file_key 作为消息发出
msg_req = ReplyMessageRequest.builder() \
.message_id(message_id) \
.request_body(ReplyMessageRequestBody.builder()
.content(json.dumps({"file_key": file_key}))
.msg_type("file")
.build()) \
.build()
msg_resp = lark_client.im.v1.message.reply(msg_req)
if not msg_resp.success():
print(f"发送文件消息失败: {msg_resp.code}, {msg_resp.msg}")
else:
print(">> 文件已成功发送给用户!\n")
def on_message_receive(data: P2pImMessageReceiveV1) -> None:
"""这是一个事件监听推流回调函数,每次收到一条飞书单聊消息时,就会运行它"""
event = data.event
message = event.message
message_id = message.message_id
msg_type = message.message_type
# 我们只对文本消息感兴趣,忽略表情、图片等避免死循环和崩溃
if msg_type != "text":
return
content_str = message.content
try:
content_dict = json.loads(content_str)
text = content_dict.get("text", "").strip()
except Exception:
text = content_str.strip()
print(f"\n======================================")
print(f"[收到消息] 用户发送:{text}")
print(f"======================================")
# 交给 LLM (Gemma 4B) 解析意图
print("🤖 正在让 Gemma 4B 分析该意图...")
llm_result = llm_agent.process_message(text)
intent = llm_result.get("intent", "reply")
reply_str = llm_result.get("reply_text", "")
# 无论找没有找文件,如果有“过渡安抚”的话,都先回复过去
if reply_str:
reply_text(message_id, reply_str)
# 如果意图是派发文件,则触发 searcher 与发送动作
if intent == "send_file":
keyword = llm_result.get("keyword", "")
if not keyword:
print("解析出送文件意图,但没有关键字,跳过。")
return
print(f"🔍 Gemma指令: 在本地搜索包含【{keyword}】的文件...")
file_path = searcher.search_file(keyword)
if file_path:
print(f"找到目标: {file_path}")
reply_file(message_id, file_path)
else:
print("搜寻无果。")
reply_text(message_id, f"抱歉,我在本地资料苦里没找到名称带有「{keyword}」的文件。")
# ================= 定义长连接的事件分发器 =================
# 通过此 Builder 挂载你需要监听处理的事件类型
# 长连接WS模式下无需填入加密串和校验 Token留空即可
event_handler = lark.EventDispatcherHandler.builder("", "") \
.register_p2p_im_message_receive_v1(on_message_receive) \
.build()
if __name__ == "__main__":
print(r"""
_____ _ _ ____ _
| ___|___ ___| |__ | | | __ ) ___ | |_
| |_ / _ \/ __| '_ \ | | _____ | _ \ / _ \| __|
| _| __/\__ \ | | | | |__|_____| | |_) | (_) | |_
|_| \___||___/_| |_| |____| |____/ \___/ \__|
""")
print("\n[ 系统引导 ] 正在启动基于 Gemma 4B 的飞书智能自动回复助手...")
if "填写你的" in APP_ID:
print("\n❌ 警告:你还没有填写真实的 APP_ID 和 APP_SECRET")
print("请在代码头部或环境变量中,设置你的飞书应用凭证(如何获取请参阅 README.md 教程)。")
else:
# 打开长连接监听
# 注意: 这里的 log_level 用 DEBUG 会打印很多底层握手日志,这里我们将其适当改大或忽略避免刷屏
ws_client = lark.ws.Client(APP_ID, APP_SECRET, event_handler=event_handler, log_level=lark.LogLevel.INFO)
try:
print("[ 服务状态 ] 长连接订阅已成功建立,请向您的飞书机器人发消息测试...")
ws_client.start()
except KeyboardInterrupt:
print("\n[ 服务状态 ] 监听到关闭指令,停止运行。")