飞书 AI 智能助手制作流程文档
飞书 AI 智能助手 — 制作流程文档
基于 Python + Anthropic LLM + lark-cli 构建的全功能飞书 Bot,支持文档操作、消息发送、日历管理、多维表格查询、天气查询等。
一、项目概述
本项目实现了一个飞书 AI 智能助手 Bot,通过飞书消息接收用户指令,调用大语言模型(sc-qwen-modes)进行智能理解,并自动执行飞书相关操作。
核心能力:
-
创建/阅读/编辑飞书云文档
-
发送消息(Bot 身份或用户身份)
-
搜索飞书通讯录用户
-
查看/创建/搜索日历日程
-
查询/添加多维表格记录
-
查询城市实时天气
技术特点:
-
LLM 多轮工具调用循环(最多 5 轮)
-
Bot 身份回复,避免身份混乱
-
消息去重机制,防止递归回复
-
多策略容错(SDK + lark-cli 双路径)
二、项目文件说明
项目目录:feishu-bot/
| 文件 | 说明 |
|---|---|
bot.py | 核心文件,包含 Bot 全部逻辑(约 830 行) |
start.bat | Windows 启动脚本 |
server.py | 备用 Webhook 服务端(可选) |
server.log | 服务日志 |
bot.py 代码结构:
| 代码区域 | 行数 | 说明 |
|---|---|---|
| 导入与配置 | 1-21 | 导入依赖、配置 App ID/Secret、LLM 客户端 |
| 工具定义(TOOLS) | 24-177 | 定义 13 个 Function Calling 工具 Schema |
| 系统提示词(SYSTEM_PROMPT) | 180-225 | LLM 行为准则和身份策略 |
| 记忆与去重 | 230-235 | 会话记忆管理、防递归机制 |
| run_lark() | 238-246 | lark-cli 命令封装(支持 stdin) |
| execute_tool() | 249-283 | 工具调度分发 |
| create_doc() | 286-345 | 创建文档(lark-cli 主路径 + SDK 回退) |
| read_doc() | 348-360 | 读取文档内容 |
| update_doc() | 363-372 | 追加/覆盖文档 |
| search_docs() | 375-390 | 搜索文档 |
| send_msg() | 393-456 | 发送消息(Bot/用户双身份) |
| search_users() | 459-470 | 搜索用户 |
| get_self() | 473-479 | 获取当前用户信息 |
| calendar_agenda() | 482-505 | 查看日程 |
| calendar_create() | 508-517 | 创建日程 |
| calendar_search() | 520-540 | 搜索日程 |
| base_query() | 543-587 | 查询多维表格 |
| base_add_record() | 590-610 | 添加多维表格记录 |
| query_weather() | 613-650 | 查询天气(wttr.in API) |
| extract_token() | 653-657 | 从 URL 提取文档 token |
| send_reply() | 660-681 | 发送回复(SDK Bot 身份) |
| ai_reply() | 684-760 | LLM 多轮对话 + 工具调用核心 |
| process_event() | 763-791 | 事件处理(过滤去重) |
| main() | 794-830 | 主循环(启动 lark-cli event consume) |
三、依赖说明
3.1 Python 环境
-
Python 版本:3.7+(推荐 3.13)
-
操作系统:Windows 11 / macOS / Linux
3.2 Python 包
pip install anthropic lark_oapi
| 包名 | 用途 |
|---|---|
anthropic | Anthropic SDK,用于调用 LiteLLM 代理的模型 |
lark_oapi | 飞书开放平台 Python SDK,用于 Bot 身份发送消息和创建文档 |
3.3 外部工具 — lark-cli
npm install -g lark-cli
安装后需要授权登录:
lark-cli auth login
lark-cli 负责:
-
接收飞书消息事件(
event consume) -
文档操作(创建/读取/编辑/搜索)
-
用户搜索
-
日历管理
-
多维表格操作
3.4 飞书开放平台
需要在飞书开放平台创建一个企业自建应用,获取:
-
App ID:
-
App Secret:
-
配置权限:
-
im:message— 接收和发送消息 -
im:message:send_as_bot— Bot 身份发消息 -
docx:document— 文档读写 -
calendar:calendar— 日历读写 -
contact:user:readonly— 通讯录只读 -
drive:file— 云空间文件操作
-
3.5 LLM 服务
-
模型:
-
代理地址:
-
API Key:
3.6 天气 API
-
API:wttr.in(免费,无需注册)
-
方式:Python 标准库
urllib,无额外依赖
四、创建流程
步骤 1:环境准备
cmd
# 1. 安装 Python 3.7+
# 2. 安装 Node.js 及 npm
# 3. 安装 Python 依赖
pip install anthropic lark_oapi
# 4. 安装并登录 lark-cli
npm install -g lark-cli
lark-cli auth login
步骤 2:创建飞书应用
-
打开 飞书开放平台
-
创建企业自建应用
-
获取 App ID 和 App Secret
-
配置应用权限(见上文权限列表)
-
发布应用并获取审批
-
将 Bot 添加到需要使用的群聊中
步骤 3:拉取代码
将 bot.py 放入 feishu-bot/ 目录,修改其中的配置项:
# ============ 配置 ============
APP_ID = "你的 App ID"
APP_SECRET = "你的 App Secret"
LARK_CLI = r"lark-cli 的安装路径"
LLM = Anthropic(base_url="你的 LLM 代理地址", api_key="你的 API Key")
MODEL = "你的模型名"
步骤 4:启动 Bot
cd feishu-bot
python bot.py
启动后输出:
飞书 AI Bot
工具: 创建/阅读/编辑文档、搜索、发消息、搜用户
按 Ctrl+C 停止
[daemon] [event] listening for events (key=im.message.receive_v1)
此时在飞书中向 Bot 发消息即可与之对话。
步骤 5:验证功能
在飞书中向 Bot 发送以下测试指令:
| 测试指令 | 预期结果 |
|---|---|
| "你好" | Bot 回复自我介绍 |
| "帮我创建一个测试文档,标题叫 Hello,内容写你好世界" | 创建云文档并返回链接 |
| "搜索文档 Hello" | 返回搜索结果 |
| "今天有什么日程" | 返回今日日程列表 |
| "帮我查一下北京天气" | 返回北京实时天气和未来预报 |
| "搜索用户 张三" | 返回用户信息和 open_id |
五、核心架构
飞书用户 → 飞书服务器 → lark-cli event consume → bot.py ↓ process_event() (过滤去重) ↓ ai_reply() (LLM 多轮循环) ↓ ┌─ 文本回复 ─→ send_reply() │ ↓ │ 飞书 SDK API │ ↓ └─ 工具调用 ─→ execute_tool() ↓ run_lark() / SDK ↓ 飞书服务器
LLM 工具调用流程:
-
用户消息发送到 LLM,附带 13 个工具定义
-
LLM 判断是否需要调用工具
-
如需调用,返回
tool_use块,bot 执行对应 Python 函数 -
工具结果返回给 LLM,LLM 判断是否继续调用(最多 5 轮)
-
最终 LLM 生成文本回复,返回给用户
防递归机制(三层过滤):
-
sender_id匹配 — 跳过 Bot 自己的消息 -
message_id匹配 — 跳过刚发送的消息 ID -
content匹配 — 内容去重兜底
身份策略:
-
回复消息:SDK
app_id + app_secret→tenant_access_token→ Bot 身份 -
文档操作:lark-cli
--as user→ 使用用户 OAuth token(权限更全) -
发送消息:默认 Bot 身份(SDK),用户明确要求时用
--as user
六、踩坑记录
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 文档创建失败 | lark-cli --content 要求相对路径,tempfile 生成的是绝对路径 | 改用 stdin 传入内容:--content - |
| SDK 创建文档权限不足 | Bot 应用无 docx:document:create 权限 | 主路径改用 lark-cli --as user,SDK 作为回退 |
| Bot 回复显示为用户身份 | lark-cli --as bot 子进程与 daemon 冲突 | 改用 SDK 直接发消息 |
| 同时收到两条回复 | 多个 bot.py 进程同时运行 | 确保只运行一个实例,旧进程 kill 掉 |
| AI 无回复或空回复 | ThinkingBlock 类型混入文本块 | 过滤非 text 类型的 content block |
七、扩展指南
要添加新的能力(如天气查询),只需三步:
-
在
TOOLS数组中添加工具定义 Schema -
编写对应的 Python 函数
-
在
execute_tool()中添加elif分支
天气查询扩展示例
# 1. 工具定义
{
"name": "feishu_query_weather",
"description": "查询指定城市的实时天气和未来几天预报。",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称,支持中文或英文"},
},
"required": ["city"],
},
},
# 2. Python 函数
def query_weather(city: str) -> str:
encoded = urllib.parse.quote(city)
url = f"https://wttr.in/{encoded}?format=j1"
# ... HTTP 请求与解析 ...
# 3. 调度分发
elif name == "feishu_query_weather":
return query_weather(args["city"])
八、代码优化分析
8.1 bot.py 优化建议
| 序号 | 位置 | 问题 | 建议 |
|---|---|---|---|
| 1 | 第 4 行 | import textwrap 未使用 | 可移除 |
| 2 | 第 4 行 | import tempfile 未使用(已改用 stdin 方案) | 可移除 |
| 3 | 第 4 行 | import os 未使用 | 可移除 |
| 4 | 第 653 行 | extract_token() 内重复 import re(顶部第 4 行已导入) | 移除内部 import |
| 5 | 第 14-17 行 | App Secret 和 API Key 硬编码在代码中 | 改为 os.environ.get() 读取环境变量,避免将密钥提交到 git |
| 6 | 第 482-505 行 | calendar_agenda 返回数据可能是 list 或 dict,用 isinstance 判断 | 逻辑正确但可统一为 dict 处理 |
| 7 | 全局 | 单一文件 833 行,随着功能增多会越来越难以维护 | 后续可将工具函数拆分到独立模块(如 tools.py) |
8.2 server.py 分析
| 项目 | 说明 |
|---|---|
| 定位 | 备用的 Flask Webhook 方案,通过飞书开放平台事件订阅接收消息 |
| 依赖 | 除 lark_oapi 外还需 flask、pycryptodome、pyngrok |
| 启动方式 | python server.py --port 5000 --ngrok 或双击 start.bat |
| 与 bot.py 的区别 | bot.py 通过 lark-cli event consume 长连接接收消息,无需公网 URL;server.py 需要公网 URL(ngrok)接收飞书回调 |
| 当前状态 | 简易 echo 回复,无 LLM 集成,功能远弱于 bot.py |
| 建议 | 如主用 bot.py,server.py 可保留作为备份方案;如需使用需补充 pip install flask pycryptodome pyngrok |
8.3 start.bat 分析
| 项目 | 说明 |
|---|---|
| 定位 | Windows 启动脚本,仅用于启动 server.py |
| 问题 | Encrypt Key 和 Verification Token 为空,需手动填写 |
| 建议 | 如需启动 bot.py,应使用以下命令:python bot.py(无需 ngrok) |
九、完整代码文件
9.1 bot.py — 核心智能体
此文件约 833 行,包含以下完整代码:
"""
飞书 AI Bot — 支持飞书文档、消息等全功能控制
"""
import sys, json, subprocess, threading, textwrap, re, os, tempfile, urllib.request, urllib.parse
from collections import deque
from anthropic import Anthropic
import lark_oapi as lark
from lark_oapi.api.im.v1 import CreateMessageRequest, CreateMessageRequestBody
from lark_oapi.api.docx.v1 import CreateDocumentRequest, CreateDocumentRequestBody
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
# ============ 配置 ============
APP_ID = ""
APP_SECRET = ""
LARK_CLI = r""
LLM = Anthropic(base_url="", api_key="")
MODEL = ""
# SDK 客户端
sdk_client = lark.Client.builder().app_id(APP_ID).app_secret(APP_SECRET).build()
工具定义 (TOOLS) — 13 个 Function Calling 工具:
-
feishu_create_doc— 创建云文档 -
feishu_read_doc— 读取文档 -
feishu_update_doc— 追加/覆盖文档 -
feishu_search_docs— 搜索文档 -
feishu_send_message— 发送消息 -
feishu_search_users— 搜索用户 -
feishu_calendar_agenda— 查看日程 -
feishu_calendar_create— 创建日程 -
feishu_calendar_search— 搜索日程 -
feishu_base_query— 查询多维表格 -
feishu_base_add_record— 添加多维表格记录 -
feishu_get_current_user— 获取当前用户 -
feishu_query_weather— 查询天气
核心函数列表:
-
run_lark()— lark-cli 命令封装(支持 stdin) -
execute_tool()— 工具调度分发 -
create_doc()/read_doc()/update_doc()/search_docs()— 文档操作 -
send_msg()/search_users()/get_self()— 消息和用户 -
calendar_agenda()/calendar_create()/calendar_search()— 日历管理 -
base_query()/base_add_record()— 多维表格 -
query_weather()— 天气查询 -
send_reply()— Bot 身份回复 -
ai_reply()— LLM 多轮工具调用核心(最多 5 轮) -
process_event()— 事件处理与防递归过滤 -
main()— 主循环
完整代码请参见实际项目文件 feishu-bot/bot.py。
9.2 server.py — 备用 Webhook 服务端
"""
飞书 Bot 服务端 — 接收并回复飞书消息
需要配置:
1. 飞书开放平台 → 应用 → 事件订阅 → 配置回调地址
2. 设置 Encrypt Key(加密密钥)
3. 订阅 im.message.receive_v1 事件
4. 启动 ngrok 获取公网 URL
启动方式:
python server.py --port 5000
"""
import sys
import os
import json
import hashlib
import base64
import argparse
from flask import Flask, request, jsonify
from lark_oapi.api.im.v1 import CreateMessageRequest, CreateMessageRequestBody
import lark_oapi as lark
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
app = Flask(__name__)
# ============ 飞书应用凭据 ============
APP_ID = os.environ.get("FEISHU_APP_ID", "")
APP_SECRET = os.environ.get("FEISHU_APP_SECRET", "")
VERIFICATION_TOKEN = os.environ.get("FEISHU_VERIFICATION_TOKEN", "")
ENCRYPT_KEY = os.environ.get("FEISHU_ENCRYPT_KEY", "")
# ============ 飞书客户端 ============
client = lark.Client.builder() \
.app_id(APP_ID) \
.app_secret(APP_SECRET) \
.build()
def decrypt_event(encrypt_key: str, encrypted: str) -> dict:
"""解密飞书事件推送 — 需要 pip install pycryptodome"""
from Crypto.Cipher import AES
key = hashlib.sha256(encrypt_key.encode()).digest()
raw = base64.b64decode(encrypted)
iv = raw[:16]
ciphertext = raw[16:]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plaintext = cipher.decrypt(ciphertext)
pad_len = plaintext[-1]
plaintext = plaintext[:-pad_len]
return json.loads(plaintext.decode("utf-8"))
def send_text_reply(open_id: str, text: str):
"""发送文本回复 — 使用 SDK Bot 身份"""
req = CreateMessageRequest.builder() \
.receive_id_type("open_id") \
.request_body(CreateMessageRequestBody.builder()
.receive_id(open_id)
.msg_type("text")
.content(json.dumps({"text": text}))
.build()) \
.build()
return client.im.v1.message.create(req)
def handle_message(event: dict):
"""处理收到的消息 — echo 示例"""
msg_type = event.get("message", {}).get("message_type", "")
sender = event.get("sender", {}).get("sender_id", {})
sender_id = sender.get("open_id", "")
if msg_type != "text":
return
content_str = event.get("message", {}).get("content", "{}")
try:
content = json.loads(content_str)
text = content.get("text", "")
except json.JSONDecodeError:
print(f"[忽略] 无法解析消息内容: {content_str}", flush=True)
return
print(f"[收到] {sender_id}: {text}", flush=True)
# ----------------- 回复逻辑 -----------------
if text.strip().lower() in ("你好", "hello", "hi"):
reply = f"你好!我是你的飞书 CLI 助手,有什么可以帮你的?"
elif text.strip().lower() in ("帮助", "help"):
reply = "我能帮你:\n- 处理飞书文档\n- 发送消息\n- 查询信息\n\n直接跟我说你的需求即可!"
else:
reply = f"收到你的消息:「{text}」\n\n这是一个自动回复。"
result = send_text_reply(sender_id, reply)
if result.success():
print(f"[回复] -> {sender_id}: {reply}", flush=True)
else:
print(f"[回复失败] {result.code}: {result.msg}", flush=True)
@app.route("/", methods=["GET"])
def health():
"""健康检查"""
return "Feishu Bot Server is running"
@app.route("/webhook", methods=["POST"])
def webhook():
"""飞书事件回调入口"""
body = request.get_json(force=True, silent=True) or {}
# 1. URL 验证
if "challenge" in body and "token" in body:
token = body["token"]
challenge = body["challenge"]
encrypt_type = body.get("type", "")
if encrypt_type and ENCRYPT_KEY:
try:
decrypted = decrypt_event(ENCRYPT_KEY, challenge)
challenge = decrypted.get("challenge", challenge)
except Exception as e:
print(f"[验证失败] 解密错误: {e}", flush=True)
return jsonify({"code": 1, "msg": "decrypt failed"}), 400
if VERIFICATION_TOKEN and token != VERIFICATION_TOKEN:
return jsonify({"code": 1, "msg": "token mismatch"}), 400
return jsonify({"challenge": challenge})
# 2. 加密事件
if "encrypt" in body:
if not ENCRYPT_KEY:
print("[错误] 收到加密事件但 ENCRYPT_KEY 未配置", flush=True)
return jsonify({"code": 1, "msg": "encrypt key not configured"}), 500
try:
event_data = decrypt_event(ENCRYPT_KEY, body["encrypt"])
except Exception as e:
print(f"[错误] 解密事件失败: {e}", flush=True)
return jsonify({"code": 1, "msg": "decrypt failed"}), 400
body = event_data
# 3. 处理事件
event_type = body.get("header", {}).get("event_type", "")
event = body.get("event", {})
if event_type == "im.message.receive_v1":
handle_message(event)
return jsonify({"code": 0})
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=5000, help="服务端口 (默认 5000)")
parser.add_argument("--ngrok", action="store_true", help="自动启动 ngrok 隧道")
args = parser.parse_args()
port = args.port
public_url = None
if args.ngrok:
try:
from pyngrok import ngrok
tunnel = ngrok.connect(port, "http")
public_url = tunnel.public_url
print(f"[ngrok] 公网地址: {public_url}/webhook")
except Exception as e:
print(f"[ngrok] 启动失败: {e}")
if not public_url and not args.ngrok:
print("提示: 使用 --ngrok 自动获取公网 URL,或手动配置反向代理")
print(f"本地地址: http://localhost:{port}/webhook")
print(f"\n启动服务: http://0.0.0.0:{port}")
print("按 Ctrl+C 停止\n")
app.run(host="0.0.0.0", port=port, debug=True)
9.3 start.bat — Windows 启动脚本
@echo off
chcp 65001 >nul
cd /d "%~dp0"
:: ============ 配置区域 ============
:: 从飞书开放平台复制: 应用 → 事件订阅 → Encrypt Key
set FEISHU_ENCRYPT_KEY=
:: 从飞书开放平台复制: 应用 → 事件订阅 → Verification Token
set FEISHU_VERIFICATION_TOKEN=
:: =================================
if "%FEISHU_ENCRYPT_KEY%"=="" (
echo [警告] 未设置 FEISHU_ENCRYPT_KEY,事件解密将失败
echo 请编辑 start.bat,设置正确的 Encrypt Key
)
echo ========================================
echo 飞书 Bot 服务端启动
echo ========================================
echo.
python server.py --port 5000 --ngrok
pause
注意: 此脚本仅启动 server.py(Webhook 模式)。如需启动 bot.py(lark-cli 长连接模式),直接在终端运行 python bot.py 即可。
十、两种运行方案对比
| 特性 | bot.py(主方案) | server.py(备用方案) |
|---|---|---|
| 消息接收方式 | lark-cli event consume 长连接 | 飞书开放平台 HTTP 回调 |
| 是否需要公网 URL | 否 | 是(需 ngrok 或反向代理) |
| LLM 智能回复 | 是() | 否(规则匹配 echo) |
| 工具调用 | 13 个飞书操作工具 | 无 |
| 依赖数量 | anthropic + lark_oapi | flask + pycryptodome + pyngrok + lark_oapi |
| 启动命令 | python bot.py | python server.py --port 5000 --ngrok |
| 推荐场景 | 日常使用 | 备份 / 需自定义 Webhook 时 |

1051

被折叠的 条评论
为什么被折叠?



