【learn-claude-code-4j】S14FeiShu - 飞书群聊智能体

核心理念

"将智能体接入飞书,随时在群聊中对话"

问题

  • 智能体需要通过 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:飞书应用 ID
  • FEI_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 单例:服务只初始化一次
  • 消息裁剪:防止上下文过长
相关推荐
TImCheng06092 小时前
内容运营岗位适合考哪个AI证书,与算法认证侧重点分析
人工智能·算法·内容运营
Gofarlic_OMS2 小时前
Windchill的license合规使用报告自动化生成与审计追踪系统
大数据·运维·人工智能·云原生·自动化·云计算
爱上珍珠的贝壳2 小时前
ESP32-S3-CAM:豆包语音识别文字后控制小车(规划)
人工智能·音频·语音识别·esp32-s3·小车
hitgavin2 小时前
cursor配置飞书MCP(lark-mcp)
飞书·cursor·mcp
甜辣uu2 小时前
基于深度学习的CT图像肺结节分割与检测系统
人工智能·深度学习
赵域Phoenix2 小时前
混沌系统是什么?
人工智能·算法·机器学习
ASKED_20192 小时前
Claude Code:架构、治理与工程实践
人工智能·架构
xcbrand2 小时前
文旅行业品牌策划公司找哪家
大数据·运维·人工智能·python
好家伙VCC2 小时前
**发散创新:基于Rust的轻量级权限管理库设计与开源许可证实践**在现代分布式系统中,**权限控制(RBAC
java·开发语言·python·rust·开源