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[ 服务状态 ] 监听到关闭指令,停止运行。")