核心理念
"将智能体接入飞书,随时在群聊中对话"
问题
- 智能体需要通过 API 调用使用,不够便捷
- 团队协作需要即时沟通场景
- 缺少群聊机器人的接入方案
我们需要:一个飞书群聊中可用的智能体。
解决方案
S14FeiShu 架构
┌─────────────────────────────────────┐
│ 飞书开放平台 │
│ │
│ • APP_ID / APP_SECRET │
│ • WebSocket 长连接 │
│ • 消息事件回调 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 飞书事件处理器 │
│ │
│ • P2MessageReceiveV1 │
│ • 解析消息内容 │
│ • 获取发送者 openId │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 智能体服务(后台运行) │
│ │
│ • ReAct 循环 │
│ • 工具集(Bash/Read/Write/Edit) │
│ • 消息历史管理 │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 飞书消息发送 │
│ │
│ • CreateMessage API │
│ • 回复至群聊/私聊 │
└─────────────────────────────────────┘
核心组件详解
1. 飞书事件处理器
使用 EventDispatcher 处理飞书消息事件:
java
private static final EventDispatcher EVENT_HANDLER = EventDispatcher.newBuilder("", "")
.onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {
@Override
public void handle(P2MessageReceiveV1 event) throws Exception {
P2MessageReceiveV1Data receiveData = event.getEvent();
System.out.printf("[ onP2MessageReceiveV1 access ], data: %s\n", Jsons.DEFAULT.toJson(receiveData));
String openId = receiveData.getSender().getSenderId().getOpenId();
EventMessage message = receiveData.getMessage();
String reply = chat(JSON.parseObject(message.getContent()).getString("text"));
sendMessage(openId, reply);
}
})
.build();
消息内容为 JSON 格式,需要解析提取文本:
java
JSON.parseObject(message.getContent()).getString("text")
2. WebSocket 长连接启动
使用飞书 SDK 的 WebSocket Client 建立长连接:
java
public static void main(String[] args) {
// 初始化智能体(确保只初始化一次)
S14FeiShu.initAgent();
Client cli = new Client.Builder(System.getenv("FEI_SHU_APP_ID"), System.getenv("FEI_SHU_APP_SECRET"))
.eventHandler(EVENT_HANDLER)
.build();
cli.start();
// 使用 CountDownLatch 实现优雅的阻塞
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("程序被中断: " + e.getMessage());
}
}
需要配置环境变量:
FEI_SHU_APP_ID:飞书应用 IDFEI_SHU_APP_SECRET:飞书应用密钥
3. 消息发送机制
使用飞书 IM API 发送消息:
java
public static void sendMessage(String openId, String content) throws Exception {
// 构建 client
com.lark.oapi.Client client = com.lark.oapi.Client.newBuilder(System.getenv("FEI_SHU_APP_ID")
, System.getenv("FEI_SHU_APP_SECRET")).build();
// 创建请求对象
CreateMessageReq req = CreateMessageReq.newBuilder()
.receiveIdType("open_id")
.createMessageReqBody(CreateMessageReqBody.newBuilder()
.receiveId(openId)
.msgType("text")
.content(String.format("{\"text\":\"%s\"}", content))
.uuid(UUID.randomUUID().toString())
.build())
.build();
// 发起请求
CreateMessageResp resp = client.im().v1().message().create(req);
// 处理服务端错误
if (!resp.success()) {
System.out.println(String.format("code:%s,msg:%s,reqId:%s, resp:%s",
resp.getCode(), resp.getMsg(), resp.getRequestId()
, Jsons.createGSON(true, false)
.toJson(JsonParser.parseString(new String(resp.getRawResponse().getBody()
, StandardCharsets.UTF_8)))));
return;
}
// 业务数据处理
System.out.println(Jsons.DEFAULT.toJson(resp.getData()));
}
关键参数:
receiveIdType:接收者 ID 类型(open_id / user_id / union_id)msgType:消息类型(text / post / image 等)uuid:消息唯一标识
4. 消息历史管理
同 S13Cli,智能体启动后保存对话历史,支持上下文理解:
java
private static final int MAX_MESSAGES = 20;
public static String chat(String query) {
try {
List<ChatCompletionMessageParam> messages = state.getMessages();
messages.add(ChatCompletionMessageParam.ofUser(
ChatCompletionUserMessageParam.builder().content(query).build()
));
if (messages.size() > MAX_MESSAGES) {
List<ChatCompletionMessageParam> trimmed = Commons.getLastN(messages, MAX_MESSAGES);
state.setMessages(new ArrayList<>(trimmed));
} else {
state.setMessages(messages);
}
ChatCompletionMessageParam last = reActs.start(state);
return Commons.getText(last);
} catch (Exception e) {
return "出错:" + e.getMessage();
}
}
使用 Kimi 模型(Mooonshotai/Kimi-K2.5)进行对话。
5. Guice 依赖注入配置
java
@Override
protected void configure() {
Multibinder<LeadTool> leadToolBinder = Multibinder.newSetBinder(binder(), LeadTool.class);
leadToolBinder.addBinding().to(BashTool.class);
leadToolBinder.addBinding().to(ReadFileTool.class);
leadToolBinder.addBinding().to(WriteFileTool.class);
leadToolBinder.addBinding().to(EditFileTool.class);
bind(OpenAIClient.class).toInstance(Commons.getKimiClient());
bind(ReActs.class).to(ReActsImpl.class);
bind(LeadReAct.class).in(Singleton.class);
bind(TeammateReAct.class).to(DefaultTeammateReAct.class);
}
工具集包含:
- BashTool:执行命令行
- ReadFileTool:读取文件
- WriteFileTool:写入文件
- EditFileTool:编辑文件
6. 服务初始化
全局单例模式确保只初始化一次:
java
public static volatile Injector injector;
public static volatile ReActs reActs;
public static volatile LeadState state;
public static void initAgent() {
if (injector == null) {
injector = Guice.createInjector(new S14FeiShu());
reActs = injector.getInstance(ReActs.class);
state = LeadState.builder()
.name("lead")
.role("lead")
.model("Pro/moonshotai/Kimi-K2.5")
.prompt("你当前工作目录为 " + Commons.CWD + ",作为编程智能体,使用 Bash 完成任务,直接执行、无需解释")
.workDir(Commons.CWD)
.messages(new ArrayList<>())
.build();
}
}
使用流程
完整使用流程:
1. 飞书开放平台配置
• 创建企业应用
• 开启 IM 权限
• 配置应用撤回和发消息权限
• 获取 APP_ID 和 APP_SECRET
2. 启动服务
$ export FEI_SHU_APP_ID=xxx
$ export FEI_SHU_APP_SECRET=xxx
$ java -cp target/xxx.jar org.jc.agents.S14FeiShu
智能体服务已启动
3. 群聊对话
在飞书群聊中 @智能体 "列出当前目录文件"
智能体: [执行 ls 命令]
4. 连续对话
@智能体 "帮我创建一个文件 hello.txt"
>> 已创建...
核心要义
"One bot, instant access to your coding agent in team chat"
一个机器人,团队群聊中随时访问你的编程智能体
设计原则:
- WebSocket 长连接:实时接收消息
- 事件驱动:消息回调处理
- 消息历史:保留上下文,理解连续对话
- 环境变量:安全配置凭证
设计亮点:
EventDispatcher:飞书事件统一处理WebSocket Client:长连接维持CreateMessage API:消息回复Guice 单例:服务只初始化一次消息裁剪:防止上下文过长