ylib-syim
v0.0.7
Published
多 IM / 多 Agent 的会话路由与上下文管理(支持 /new)
Readme
@ylib/syim
多 IM(钉钉、飞书等)/ 多 Agent 的会话路由与上下文管理,支持 /new、/reset 重开上下文。可作为库接入现有项目,或配合 OpenClaw 使用。
本仓库为独立项目(已从 OpenClaw monorepo 拆出),依赖用 npm 管理。
钉钉桥接(连接远程 yuce-gpt Gateway)
本地调试:
npm install
npx tsx scripts/dingtalk-stdio-bridge.ts打包成单文件 JS(仍需本机 Node,分发更方便):
npm run build:bridge
node dist/dingtalk-stdio-bridge.cjsbuild:bridge 外置上述重型依赖并 默认压缩,产物约 200KB+(未压缩调试用 npm run build:bridge:debug)。运行前项目根 npm install;仅拷贝 cjs 见 scripts/bridge-runtime-package.json。全量单文件:npm run build:bridge:all。体积构成与继续缩小思路见 docs/packaging-二进制与分发.md(文内「主库还能再缩小吗」)。
说明与「真·无 Node 二进制」的可行性见 docs/packaging-二进制与分发.md。
一键安装配置 + 后台服务 + im-agent-hub-ctl 管理命令:见 scripts/install/README.md。日常升级代码与依赖可执行 im-agent-hub-ctl update(或 update-help / version 查看说明与当前状态)。若使用 bundle,update 后请再执行 npm run build:bridge。
仓库内默认 openclaw.json 可为精简结构:仅包含 channels.dingtalk-connector(钉钉插件 / @dingtalk-real-ai/dingtalk-connector 所需字段)。安装脚本只合并该通道,不会自动写入 bindings 等其它顶级键。
直接使用方式
im-agent-hub 是库:在你的代码里创建 Hub、配置 Router、Agents、Adapters,然后把收到的 IM 消息转成 InboundMessage 调用 hub.handleInbound(input),回复会通过对应 adapter 发回 IM。
import {
createHub,
createDingTalkAdapter,
createRuleBasedRouter,
createInMemorySessionStore,
createLangChainLikeAgent,
} from "@ylib/syim";
const hub = createHub({
router: createRuleBasedRouter({ defaultAgentId: "main" }),
agents: [
createLangChainLikeAgent({
id: "main",
systemPrompt: "你是助手。",
runnable: async ({ messages }) => {
const last = messages[messages.length - 1];
return { content: `回复:${last?.content ?? ""}` };
},
}),
],
adapters: [
createDingTalkAdapter({
config: { clientId: "钉钉 AppKey", clientSecret: "钉钉 AppSecret" },
}),
],
sessions: createInMemorySessionStore(),
});
// 当从钉钉收到消息时,把事件转成 InboundMessage 再调用:
await hub.handleInbound({
provider: "dingtalk",
peer: { kind: "direct", id: "用户 staffId" }, // 或 kind: "group", id: "群 openConversationId"
sender: { id: "用户 id", name: "用户名" },
messageId: "消息 id",
timestamp: Date.now(),
text: "用户发的文字",
});多 Agent 与 Bindings(与主项目一致)
主项目通过 agents.list + bindings 配置多个 agent,并按 channel/accountId/peer 匹配路由。im-agent-hub 提供相同语义:
- agents:多个
Agent(每个有id、run,可选meta做 per-agent 配置)。 - bindings:
AgentRouteBinding[],每项为{ agentId, match }。match支持:channel、accountId?、peer?、guildId?(Discord)、teamId?(Slack)、roles?(角色列表)。 - 匹配顺序(与主项目一致):peer 精确 → guildId + roles → guildId → teamId → account → channel → defaultAgentId。
方式一:用 createRouterFromBindings 自己组 Hub
import {
createHub,
createRouterFromBindings,
createDingTalkAdapter,
createInMemorySessionStore,
createLangChainLikeAgent,
} from "@ylib/syim";
const agents = [
createLangChainLikeAgent({ id: "main", runnable: async ({ messages }) => ({ content: "主助手回复" }) }),
createLangChainLikeAgent({ id: "support", runnable: async ({ messages }) => ({ content: "客服回复" }) }),
];
const hub = createHub({
router: createRouterFromBindings({
defaultAgentId: "main",
bindings: [
{ agentId: "support", match: { channel: "dingtalk", peer: { kind: "group", id: "群openConversationId" } } },
{ agentId: "main", match: { channel: "dingtalk" } },
],
}),
agents,
adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
sessions: createInMemorySessionStore(),
});方式二:用 createHubFromConfig 一次传入配置
import { createHubFromConfig, createDingTalkAdapter, createInMemorySessionStore, createLangChainLikeAgent } from "@ylib/syim";
const hub = createHubFromConfig({
defaultAgentId: "main",
agents: [
createLangChainLikeAgent({ id: "main", runnable: async ({ messages }) => ({ content: "主助手" }) }),
createLangChainLikeAgent({ id: "support", runnable: async ({ messages }) => ({ content: "客服" }) }),
],
adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
bindings: [
{ agentId: "support", match: { channel: "dingtalk", peer: { kind: "group", id: "某群ID" } } },
{ agentId: "main", match: { channel: "dingtalk" } },
],
sessions: createInMemorySessionStore(),
});同一钉钉账号下:该群消息走 support,其他单聊/群走 main。
Session 持久化与 HTTP 入口(与主项目能力对齐)
- Session 持久化:
createFileSessionStore({ dir? })将会话落盘(默认~/.openclaw/im-agent-hub/sessions),pm2 重启后会话可恢复。pnpm run dev时设置PERSIST_SESSIONS=1或SESSION_DIR=/path即可启用。 - HTTP 入口:
createInboundServer({ hub, healthPath?, auth? })/startInboundServer({ hub, port })提供GET /health、POST /inbound;可选auth: { type: 'bearer' | 'api-key', token }。
媒体、记忆与工具(与主项目能力对齐)
- 入站附件:
downloadInboundAttachments(input, { dir?, timeoutMs? })将input.attachments中带url的项下载到本地并填回path,再交hub.handleInbound。 - 出站附件:
OutboundMessage.attachments可带{ path, contentType?, fileName? },钉钉 adapter 会上传后发文件消息。 - 记忆:
HubConfig.memory传入MemoryStore(如createInMemoryMemoryStore()),hub 会注入到agent.run({ memory })。 - 工具:
HubConfig.tools为Record<string, (args) => Promise<string>>;agent 返回toolCalls时 hub 执行并拼入回复。 - 白名单:
HubConfig.allowFrom?: (input: InboundMessage) => boolean,仅当返回 true 时处理消息。
如何测试(单 agent 收所有 IM)
用「一个简单 agent 接收所有 IM 消息」来验证 Hub 与路由是否正常。
1. 启动示例服务
在 packages/im-agent-hub 下执行:
cd packages/im-agent-hub
pnpm install
# 可选:配置钉钉后回复会真实发到钉钉;不配置则仅 HTTP 测试,回复在控制台打印
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run example:single-agent服务监听 http://localhost:3100(可用 PORT=3000 改端口)。
2. 用 curl 模拟一条 IM 消息
任意一条「入站消息」都会路由到同一个 agent(main),并得到回复 [测试 agent] 收到:xxx。
单聊示例(把 YOUR_STAFF_ID 换成你的钉钉 userId):
curl -X POST http://localhost:3100/inbound -H "Content-Type: application/json" -d '{
"provider": "dingtalk",
"peer": { "kind": "direct", "id": "YOUR_STAFF_ID" },
"sender": { "id": "YOUR_STAFF_ID", "name": "测试" },
"messageId": "test-1",
"timestamp": 1700000000000,
"text": "你好"
}'成功时返回 {"ok":true,"text":"你好","agentId":"main"}。若已配置钉钉,钉钉端会收到 [测试 agent] 收到:你好。
3. 测试 /new、/reset
再发一条,内容为 /new 或 /reset:
curl -X POST http://localhost:3100/inbound -H "Content-Type: application/json" -d '{"provider":"dingtalk","peer":{"kind":"direct","id":"YOUR_STAFF_ID"},"sender":{"id":"YOUR_STAFF_ID","name":"测试"},"messageId":"test-2","timestamp":1700000000000,"text":"/new"}'会返回成功,且若配置了钉钉会收到「已开启新的上下文。」。
4. 用真实钉钉收消息
若要让「钉钉里发消息 → 同一 agent 回复」,可再开一个终端跑 Stream 收消息(与上面示例共用同一套 agent 逻辑):
DINGTALK_CLIENT_ID=xxx DINGTALK_CLIENT_SECRET=yyy pnpm run start:dingtalk钉钉里给机器人发消息,会由 start:dingtalk 里的 Hub 处理并回发(该脚本内也是单 agent,逻辑与 example-single-agent 一致)。
使用 iflow 接口作为 Agent
若后端是 iflow(OpenAI 兼容) 接口,可用内置的 createIflowAgent,让所有 IM 消息对接该 agent。
环境变量
| 变量 | 必填 | 说明 |
|------|------|------|
| IFLOW_API_KEY | 是 | API Key,请勿写入代码,仅通过环境变量传入 |
| IFLOW_BASE_URL | 否 | 默认 http://92.113.117.22:10068/proxy/iflow |
一键跑示例(HTTP + 可选钉钉回发)
cd packages/im-agent-hub
export IFLOW_API_KEY=sk-你的key
# 可选:钉钉回发
export DINGTALK_CLIENT_ID=xxx
export DINGTALK_CLIENT_SECRET=yyy
pnpm run example:iflow-agent然后 curl -X POST http://localhost:3100/inbound ... 或钉钉内发消息,回复由 iflow 接口生成。
在代码中使用
import { createHubFromConfig, createIflowAgent, createDingTalkAdapter, createInMemorySessionStore } from "@ylib/syim";
const hub = createHubFromConfig({
defaultAgentId: "main",
agents: [
createIflowAgent({
id: "main",
systemPrompt: "你是助手。",
historyLimit: 20,
// baseUrl / apiKey 不传则从环境变量 IFLOW_BASE_URL、IFLOW_API_KEY 读取
}),
],
adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
bindings: [{ agentId: "main", match: { channel: "dingtalk" } }],
sessions: createInMemorySessionStore(),
});本地测试钉钉 IM
不跑完整 OpenClaw 和钉钉 connector 时,可以用包里的开发服务器只测「Hub + 钉钉发送」是否正常。
1. 安装依赖
在 openclaw 仓库根目录执行:
pnpm install2. 配置钉钉应用
- 在 钉钉开放平台 创建企业内部应用,拿到 AppKey(clientId)和 AppSecret(clientSecret)。
- 开通机器人能力、单聊/群聊发送权限(与 dingtalk-openclaw-connector 文档一致)。
3. 启动开发服务器
在 packages/im-agent-hub 下执行(需先 pnpm install 以安装 tsx):
cd packages/im-agent-hub
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run dev或先导出环境变量再启动:
export DINGTALK_CLIENT_ID=你的AppKey
export DINGTALK_CLIENT_SECRET=你的AppSecret
pnpm run dev服务会在 http://localhost:3100 启动(可通过 PORT=3000 pnpm run dev 改端口)。
4. 发送测试消息
- 单聊:
peer.id填你的钉钉 staffId(或 unionId,视应用权限而定)。 - 群聊:
peer填{ "kind": "group", "id": "群的 openConversationId" }。
示例(单聊,把 YOUR_STAFF_ID 换成真实 userId):
curl -X POST http://localhost:3100/inbound \
-H "Content-Type: application/json" \
-d '{
"provider": "dingtalk",
"peer": { "kind": "direct", "id": "YOUR_STAFF_ID" },
"sender": { "id": "YOUR_STAFF_ID", "name": "测试" },
"messageId": "test-1",
"timestamp": 1700000000000,
"text": "你好"
}'成功时接口返回 {"ok":true,"text":"你好"},钉钉端会收到机器人回复(当前 dev-server 为 echo 风格:「收到:你好」)。
5. 测试 /new、/reset
对同一 peer 再发一条:
curl -X POST http://localhost:3100/inbound \
-H "Content-Type: application/json" \
-d '{"provider":"dingtalk","peer":{"kind":"direct","id":"YOUR_STAFF_ID"},"sender":{"id":"YOUR_STAFF_ID","name":"测试"},"messageId":"test-2","timestamp":1700000000000,"text":"/new"}'钉钉会收到「已开启新的上下文。」,后续消息会使用新的会话上下文。
钉钉完整收发流程(Stream 收消息 + Hub 回发)
在不启动 OpenClaw 的情况下,用与官方 dingtalk-openclaw-connector 相同的 Stream 模式收钉钉消息,由 im-agent-hub 处理并回发。
1. 钉钉应用配置
- 在 钉钉开放平台 创建企业内部应用,开启机器人能力。
- 消息接收方式选择 Stream 模式(与官方 connector 一致),并保存 AppKey、AppSecret。
- 为应用开通「机器人发送消息到群聊」「机器人发送单聊消息」等权限。
2. 启动 Stream 收消息服务
在 packages/im-agent-hub 下执行:
cd packages/im-agent-hub
pnpm install
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run start:dingtalk可选:DEBUG=1 可打开 dingtalk-stream 调试日志。
3. 在钉钉里发消息
- 单聊:在钉钉里找到你的机器人,发「你好」等文字,机器人会经 Hub 回复(当前为 echo:「收到:你好」)。
- 群聊:把机器人拉进群,在群里 @机器人 或发消息(视钉钉机器人设置),同样会走 Hub 回发。
- 发送 /new 或 /reset 可重开会话上下文。
4. 自定义 Agent(如接 LLM)
当前 scripts/dingtalk-stream-server.ts 里使用的是内置 echo 风格 agent。要接真实 LLM,可复制该脚本到自己的项目,或修改脚本中的 createLangChainLikeAgent 的 runnable,改为调用你的 LangChain / OpenAI 等接口。
Connector 模式(多账号、项目配置)
pnpm run start:connector 使用官方 dingtalk-openclaw-connector,支持多企业钉钉部署。配置读取优先级:
- 项目目录
openclaw.json(与 package.json 同目录) ~/.openclaw/openclaw.json- 环境变量
DINGTALK_CLIENT_ID/DINGTALK_CLIENT_SECRET(单账号)
在项目根目录创建或复制 openclaw.json:至少配置 channels.dingtalk-connector。dingtalk-stdio-bridge 与精简 openclaw.json(仅 channels)即可工作;若使用 start:connector 且需多 Agent 路由,再按需增加 bindings 等键。
将 /v1/chat/completions 代理到 yuce-gpt(可选)
若希望 对话能力由 yuce-gpt 提供(复用其 Agent、技能、记忆等),可将 im-agent-hub 的 POST /v1/chat/completions 配置为反向代理到 yuce-gpt:
- yuce-gpt 端点:
POST https://<yuce-gpt-host>/api/v1/yucegpt/v1/chat/completions - 鉴权:在代理请求头中增加
Authorization: Bearer <OPENCLAW_GATEWAY_API_KEY>(yuce-gpt 侧需配置同名环境变量OPENCLAW_GATEWAY_API_KEY及OPENCLAW_GATEWAY_TENANT_ID/USER_ID/USER_NAME)。 - 透传:将
X-OpenClaw-Agent-Id、X-OpenClaw-Session-Key及 body 原样转发。
实现方式:在 createInboundServer 中若启用 enableChatCompletions,可改为不调用本地 createChatCompletionsHandler({ hub }),而是将请求 proxy 到上述 yuce-gpt URL(需自行用 fetch/axios 或 nginx 做反向代理)。详见 yuce-gpt 仓库文档:doc/OpenClaw与ACP会话形式对接说明.md。
与 OpenClaw / 钉钉 connector 的关系
- im-agent-hub:提供「会话路由 + 上下文 + 多 adapter 发送」;钉钉发送逻辑复制自官方 connector 的 OpenAPI。
- 完整收发可选两种方式:
- 本包内置:
pnpm run start:dingtalk使用 dingtalk-stream 收消息,交给 hub 处理并用本包钉钉 adapter 回发(无需 OpenClaw)。 - Connector 模式:
pnpm run start:connector加载 dingtalk-openclaw-connector,支持多账号,配置从项目openclaw.json读取。
- 本包内置:
