自建 AI Agent 实战:自己搭建的 Agent 如何获取飞书输入数据(文本 / 图片 / 文件全支持)
·
前言
在搭建自动化测试 Agent、业务 AI 智能体时,我们经常需要让自建服务通过飞书接收用户输入:
- 文本指令(如:淘宝搜索商品、执行自动化用例)
- 图片 / 文件上传(如:Excel 测试用例、TXT 配置文件)
本文从零到一完整实现:本地 Agent 服务 → 接收飞书消息回调 → 解析文本 / 图片 / 文件 → 过滤重复 / 历史消息所有代码可直接运行,附带全套排坑方案,适合搭建个人 / 企业智能体。
一、整体流程梳理
飞书消息 → 飞书开放平台服务器 → 内网穿透(ngrok) → 本地 FastAPI 服务 → Agent 获取结构化输入
核心链路:
- 用户在飞书发送消息 / 文件
- 飞书通过事件订阅推送 POST 回调
- ngrok 将公网流量转发到本地 8001 端口
- FastAPI 接收、验签、解析消息
- 去重 + 过滤历史消息,输出标准 JSON 给 Agent 使用
二、前期准备
2.1 飞书开放平台配置
进入飞书开放平台https://open.feishu.cn/,点击开发者后台,创建企业自建应用
也可以快速开发机器人,这边选择企业自建应用
- 「应用能力」→ 开启机器人
- 「权限管理」→ 直接导入即可,批量导入/导出权限,直接复制,黏贴导入
{ "scopes": { "tenant": [ "aily:file:read", "aily:file:write", "application:application.app_message_stats.overview:readonly", "application:application:self_manage", "application:bot.menu:write", "cardkit:card:write", "contact:contact.base:readonly", "contact:user.employee_id:readonly", "corehr:file:download", "docs:document.content:read", "event:ip_list", "im:chat", "im:chat.access_event.bot_p2p_chat:read", "im:chat.members:bot_access", "im:message", "im:message.group_at_msg:readonly", "im:message.group_msg", "im:message.p2p_msg:readonly", "im:message:readonly", "im:message:send_as_bot", "im:resource", "sheets:spreadsheet", "wiki:wiki:readonly" ], "user": [ "aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read" ] } } - 「事件与回调」→ 事件配置,添加事件:
im.message.receive_v1(接收用户发给机器人的消息) - 记录关键信息:在加密策略:记录你的Verification Token
- 权限管理开启:
im:messageim:message:readim:message.file:read(文件必开)

2.2 内网穿透(ngrok)
飞书只能访问公网地址,本地服务必须做穿透:
- 注册https://ngrok.com/,下载 Windows 客户端
- 配置 authtoken:
ngrok config add-authtoken 你的token - 映射本地端口:
ngrok http 8001 - 复制生成的公网 HTTPS 地址,填入飞书「事件订阅请求地址」:
https://xxx.ngrok-free.dev/feishu/receive

2.4 版本管理与发布
创建版本,填入版本号和更新说明,保存即可发布,这样你的飞书客户端就会收到发布成功的通知
2.4 Python 依赖安装
pip install fastapi uvicorn requests pandas openpyxl
三、核心代码实现
3.1 基础接收服务(URL 校验 + 消息接收 + 去重 + 过滤)
解决三大问题:
- 飞书 URL 校验(challenge 返回)
- 消息重复推送(飞书重试机制)
- 历史 / 删除消息干扰(时间戳过滤)
from fastapi import FastAPI, Request
import json
import time
app = FastAPI()
# 飞书配置(从开放平台复制)
FEISHU_VERIFY_TOKEN = "你的Verification Token"
# 消息去重集合 + 过期时间(10分钟内消息才处理)
processed_event_ids = set()
EXPIRE_SECONDS = 600
@app.post("/feishu/receive")
async def receive_feishu_msg(request: Request):
# 1. 获取原始消息体
body = await request.body()
raw_data = json.loads(body.decode("utf-8"))
# 2. 飞书URL校验(配置回调时必过)
if raw_data.get("type") == "url_verification":
return {"challenge": raw_data.get("challenge")}
try:
# 基础字段抽取
header = raw_data["header"]
event = raw_data["event"]
event_id = header["event_id"] # 消息唯一ID
msg_time = int(event["message"]["create_time"]) // 1000
msg_type = event["message"]["message_type"]
content = json.loads(event["message"]["content"])
# 3. 消息去重:同一event_id只处理一次
if event_id in processed_event_ids:
return {"code": 200, "msg": "重复消息,已忽略"}
# 4. 过滤历史消息(只处理10分钟内新消息)
if time.time() - msg_time > EXPIRE_SECONDS:
processed_event_ids.add(event_id)
return {"code": 200, "msg": "历史消息,已过滤"}
# 标记为已处理
processed_event_ids.add(event_id)
# 5. 多类型消息解析
result = {}
if msg_type == "text":
result = {
"type": "文本",
"text": content.get("text", "")
}
elif msg_type == "image":
result = {
"type": "图片",
"image_key": content.get("image_key", "")
}
elif msg_type == "file":
result = {
"type": "文件",
"file_name": content.get("name", ""),
"file_key": content.get("file_key", "")
}
else:
result = {"type": "未知类型"}
# 标准结构化输出(Agent直接使用)
return {
"code": 200,
"msg": "success",
"data": {
"event_id": event_id,
"message": result
}
}
except Exception as e:
return {"code": 500, "msg": f"解析失败:{str(e)}"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
四、进阶:接收文件并读取内容(TXT/Excel)
仅拿到file_key无法获取文件内容,需调用飞书接口下载并读取
from fastapi import FastAPI, Request
import json
import time
import requests
import pandas as pd
app = FastAPI()
# 飞书配置
FEISHU_VERIFY_TOKEN = "你的Verification Token"
FEISHU_APP_ID = "你的App ID"
FEISHU_APP_SECRET = "你的App Secret"
processed_event_ids = set()
EXPIRE_SECONDS = 600
# ========== 获取飞书接口凭证 ==========
def get_tenant_token():
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
resp = requests.post(url, json={
"app_id": FEISHU_APP_ID,
"app_secret": FEISHU_APP_SECRET
})
return resp.json()["tenant_access_token"]
# ========== 下载并读取飞书文件 ==========
def parse_file_content(file_key, file_name):
token = get_tenant_token()
headers = {"Authorization": f"Bearer {token}"}
# 1. 获取文件下载链接
info_resp = requests.get(
f"https://open.feishu.cn/open-apis/im/v1/files/{file_key}",
headers=headers
)
download_url = info_resp.json()["data"]["download_url"]
# 2. 下载文件
file_bytes = requests.get(download_url, headers=headers).content
# 3. 根据类型解析
try:
if file_name.endswith(".txt"):
return {"type": "txt", "content": file_bytes.decode("utf-8")}
elif file_name.endswith(".xlsx"):
df = pd.read_excel(pd.compat.BytesIO(file_bytes))
return {"type": "excel", "content": df.to_dict(orient="records")}
else:
return {"type": "unknown", "content": "不支持该格式"}
except:
return {"type": "error", "content": "文件读取失败"}
# ========== 主接收接口 ==========
@app.post("/feishu/receive")
async def receive_feishu_msg(request: Request):
body = await request.body()
raw_data = json.loads(body.decode("utf-8"))
if raw_data.get("type") == "url_verification":
return {"challenge": raw_data.get("challenge")}
try:
event_id = raw_data["header"]["event_id"]
msg_time = int(raw_data["event"]["message"]["create_time"])//1000
msg_type = raw_data["event"]["message"]["message_type"]
content = json.loads(raw_data["event"]["message"]["content"])
# 去重 + 时间过滤
if event_id in processed_event_ids:
return {"code":200,"msg":"重复消息"}
if time.time() - msg_time > EXPIRE_SECONDS:
processed_event_ids.add(event_id)
return {"code":200,"msg":"历史消息"}
processed_event_ids.add(event_id)
# 消息解析
if msg_type == "text":
data = {"type":"文本","text":content["text"]}
elif msg_type == "file":
file_content = parse_file_content(content["file_key"], content["name"])
data = {
"type":"文件",
"name":content["name"],
"file_content": file_content
}
else:
data = {"type":msg_type}
return {"code":200,"msg":"success","data":data}
except Exception as e:
return {"code":500,"msg":str(e)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
五、运行效果
代码运行之后:可以在飞书输入简单文字,后端会接收到你输入的文字
5.1 文本消息输入
飞书发送:你好:
{
"code": 200,
"msg": "success",
"data": {
"type": "文本",
"text": "你好"
}
}
5.2 文件输入(TXT/Excel)
上传test.txt,内容:Agent:
{
"code": 200,
"data": {
"type": "文件",
"name": "test.txt",
"file_content": {
"type": "txt",
"content": "Agent"
}
}
}
六、高频问题排坑(实战必看)
6.1 验签失败(403 Forbidden)
- 飞书 v2.0 事件使用SHA256+timestamp+nonce+body验签
- 测试阶段可临时跳过验签,优先保证消息接收
6.2 一条消息重复打印多次
- 原因:飞书未及时收到 200 响应,自动重试
- 解决:使用
event_id做内存去重(本文已实现)
6.3 收到很久以前的聊天记录
- 原因:飞书会推送订阅前的历史消息
- 解决:按时间戳过滤,只处理 10 分钟内消息
6.4 仅支持文本,图片 / 文件报错
- 原因:未兼容
image/file类型结构 - 解决:按
message_type分支解析
6.5 飞书回调配置失败
- 必须使用HTTPS公网地址
- 路由必须完全一致:
/feishu/receive - 必须正确返回
challenge
更多推荐
所有评论(0)