@my-life-buddies/buddy-runtime 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -184
- package/dist/conversation/agent.d.ts +5 -6
- package/dist/conversation/agent.d.ts.map +1 -1
- package/dist/conversation/agent.js +63 -24
- package/dist/conversation/agent.js.map +1 -1
- package/dist/conversation/index.d.ts +1 -3
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js +13 -36
- package/dist/conversation/index.js.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts +7 -3
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.js +19 -12
- package/dist/conversation/mlbMessageAgentMessageConverter.js.map +1 -1
- package/dist/conversation/writeMLBMessage.d.ts +7 -3
- package/dist/conversation/writeMLBMessage.d.ts.map +1 -1
- package/dist/conversation/writeMLBMessage.js +6 -5
- package/dist/conversation/writeMLBMessage.js.map +1 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/platform/mlbClient.d.ts +45 -19
- package/dist/platform/mlbClient.d.ts.map +1 -1
- package/dist/platform/mlbClient.js +79 -32
- package/dist/platform/mlbClient.js.map +1 -1
- package/dist/server.js +3 -3
- package/dist/tools/dataAccess.d.ts +3 -3
- package/dist/tools/dataAccess.d.ts.map +1 -1
- package/dist/tools/dataAccess.js +5 -5
- package/dist/tools/dataAccess.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.js +1 -1
- package/dist/tools/resource.d.ts +3 -3
- package/dist/tools/resource.js +7 -7
- package/dist/tools/widgets.d.ts +4 -4
- package/dist/tools/widgets.d.ts.map +1 -1
- package/dist/tools/widgets.js +13 -11
- package/dist/tools/widgets.js.map +1 -1
- package/dist/types.d.ts +118 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/platform/client.d.ts +0 -48
- package/dist/platform/client.d.ts.map +0 -1
- package/dist/platform/client.js +0 -24
- package/dist/platform/client.js.map +0 -1
- package/dist/platform/widgets.d.ts +0 -75
- package/dist/platform/widgets.d.ts.map +0 -1
- package/dist/platform/widgets.js +0 -88
- package/dist/platform/widgets.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
# @my-life-buddies/buddy-runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
搭子运行时,基于 [`@earendil-works/pi-agent-core`](https://www.npmjs.com/package/@earendil-works/pi-agent-core)。
|
|
4
|
+
它提供了让搭子接入平台的基本能力:接收消息、模型调用、回复消息链路,并可以快速接入平台所提供的包括资料引入、记忆系统、授权数据、主动性、付费服务等特色化能力。
|
|
5
|
+
开发者只需基于搭子的领域逻辑书写工具、人设等。
|
|
4
6
|
|
|
5
|
-
## 1.
|
|
7
|
+
## 1. 快速开始
|
|
8
|
+
|
|
9
|
+
### 1.1 安装
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
12
|
npm install @my-life-buddies/buddy-runtime typebox
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
### 1.2 注册搭子
|
|
16
|
+
|
|
17
|
+
通过平台开发者接口 `POST /cli/buddies` 注册,提交名称、头像、简介,平台分配搭子 id 与令牌;之后改档案用 `PUT /cli/buddies/:id/meta`。可选的模型 id 查 `GET /cli/models`。
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
### 1.3 搭子工程框架
|
|
14
20
|
|
|
15
21
|
```
|
|
16
22
|
my-buddy/
|
|
17
23
|
├─ package.json
|
|
18
|
-
├─ .env MLB_BUDDY_ID
|
|
24
|
+
├─ .env MLB_BUDDY_ID、MLB_BUDDY_TOKEN
|
|
19
25
|
└─ src/main.ts
|
|
20
26
|
```
|
|
21
27
|
|
|
@@ -38,7 +44,7 @@ const planMenu: AgentTool<typeof PLAN_MENU_PARAMS, undefined> = {
|
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
await BuddyServer.start({
|
|
41
|
-
model: "kimi-k3",
|
|
47
|
+
model: "kimi-k3",
|
|
42
48
|
buddy: () => ({
|
|
43
49
|
initialState: {
|
|
44
50
|
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
@@ -56,103 +62,189 @@ await BuddyServer.start({
|
|
|
56
62
|
}
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
### 1.4 启动
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
在 `.env` 里配好环境变量,`npm run dev` 启动
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
| 变量 | 说明 |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `MLB_BUDDY_ID` | 必填,搭子 id |
|
|
72
|
+
| `MLB_BUDDY_TOKEN` | 必填,搭子令牌 |
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
- 在自己电脑上连测试网关时,平台把这个进程当作 `<id>-dev` 分身,会话与正式搭子分开。
|
|
75
|
+
- 改完代码想自动重启,在启动命令里加 `--watch`。
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
完整示例:[examples/aunt-tu/](examples/aunt-tu/)(记忆、小挂件)、[examples/stacy-sims/](examples/stacy-sims/)(资料)。
|
|
78
|
+
|
|
79
|
+
## 2. 配置
|
|
80
|
+
|
|
81
|
+
### 2.1 `BuddyServer.start(options)`
|
|
82
|
+
|
|
83
|
+
连上平台开始处理消息,返回 `{ stop(): Promise<void> }`。
|
|
84
|
+
|
|
85
|
+
| 参数 | 说明 |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `model` | 必填,模型 id,取值见 `GET /cli/models`,进程内所有会话共用。没填或运行时不认识这个 id 时启动失败 |
|
|
88
|
+
| `buddy` | 必填,`(conversation: Conversation) => BuddyOptions \| Promise<BuddyOptions>`,见 2.2、2.3 |
|
|
89
|
+
| `welcome` | 可选,用户打开一场还没聊过的会话时发的问候,不经过模型 |
|
|
90
|
+
| `errorReply` | 可选,模型出错或空回复时发的兜底回复 |
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
`buddy` 在每场会话收到第一条消息时调用一次。会话空闲 30 分钟被回收、或进程重启后,下一条消息会重新调用,所以闭包里的变量不能当长期存储,要记住的东西写进记忆(3.2)。
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
### 2.2 `BuddyOptions`
|
|
95
|
+
|
|
96
|
+
`buddy` 的返回值。字段名和签名取自 pi-agent-core 的 `AgentOptions` / `AgentState`,详见 pi 的文档。
|
|
76
97
|
|
|
77
98
|
```ts
|
|
78
|
-
type BuddyOptions =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
type BuddyOptions = Pick<AgentOptions,
|
|
100
|
+
| "beforeToolCall" | "afterToolCall"
|
|
101
|
+
| "shouldStopAfterTurn" | "prepareNextTurnWithContext"
|
|
102
|
+
| "thinkingBudgets" | "toolExecution"
|
|
103
|
+
> & {
|
|
104
|
+
platformTools?: readonly PlatformToolName[];
|
|
105
|
+
proactiveTools?: readonly string[];
|
|
106
|
+
initialState: Pick<AgentState, "systemPrompt"> & Partial<Pick<AgentState, "tools" | "thinkingLevel">>;
|
|
107
|
+
};
|
|
84
108
|
```
|
|
85
109
|
|
|
86
110
|
| 字段 | 说明 |
|
|
87
111
|
|---|---|
|
|
88
|
-
| `
|
|
89
|
-
| `initialState.
|
|
90
|
-
| `initialState.
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
112
|
+
| `initialState.systemPrompt` | 必填,人设与做事规则,只放不随回合变化的内容。模型实际收到的系统提示见第 4 节 |
|
|
113
|
+
| `initialState.tools` | 可选,pi 的 `AgentTool` 数组 |
|
|
114
|
+
| `initialState.thinkingLevel` | 可选,默认 `off`。模型不支持的级别在请求时就近换成支持的一档,建会话时打一条 warn |
|
|
115
|
+
| `platformTools` | 可选,给模型注册哪些平台工具,默认 `[]`,见第 3 节 |
|
|
116
|
+
| `proactiveTools` | 可选,主动回合里还能用哪些工具,默认 `[]`(一个都不给)。写工具名,`platformTools` 选中的和 `initialState.tools` 里自己的都行;写了没注册的名字建会话时抛异常。见 3.5 |
|
|
117
|
+
| 其余字段 | 原样交给 pi;`beforeToolCall` 抛异常时按放行处理 |
|
|
118
|
+
|
|
119
|
+
写工具时注意:
|
|
93
120
|
|
|
94
|
-
|
|
121
|
+
- `label` 必填,是工具执行时用户看到的状态文案,空字符串表示不显示。
|
|
122
|
+
- `execute` 的返回值必须带 `details`,可以是 `undefined`;失败直接抛异常,模型能看到异常信息。
|
|
123
|
+
- 先单独声明为 `AgentTool<typeof 参数 schema>` 再放进数组,`execute` 的参数才有类型;直接写在数组里时参数类型是 `unknown`。
|
|
124
|
+
- 工具名不能与平台工具或其他工具重复。
|
|
95
125
|
|
|
96
|
-
|
|
126
|
+
模型、密钥与会话历史由运行时管理,不在配置里。
|
|
97
127
|
|
|
98
|
-
|
|
128
|
+
### 2.3 `Conversation`
|
|
129
|
+
|
|
130
|
+
`buddy` 拿到的参数,工具闭包里也能用。
|
|
99
131
|
|
|
100
132
|
```ts
|
|
101
133
|
interface Conversation {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
readonly signal: AbortSignal; // 会话删除、空闲淘汰、stop 时中止
|
|
105
|
-
readonly platform: PlatformClient; // 已绑好这场会话的平台能力
|
|
106
|
-
}
|
|
134
|
+
// 会话键:私聊以 p_ 开头,群聊以 g_ 开头。开发者自己存的数据要按它隔离
|
|
135
|
+
key: string;
|
|
107
136
|
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
// 日志,每行开头带搭子 id
|
|
138
|
+
log: BuddyLogger;
|
|
139
|
+
|
|
140
|
+
// 资料,见 3.1:resources/ 里的 .md,进程启动时读好,所有会话共用
|
|
141
|
+
resources: {
|
|
142
|
+
list(): string[];
|
|
143
|
+
read(file: string): string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// 记忆,见 3.2:这场会话的长期记忆,存在平台上
|
|
147
|
+
memory: {
|
|
110
148
|
list(): Promise<MemoryEntry[]>;
|
|
111
149
|
write(key: string, text: string): Promise<void>;
|
|
112
150
|
delete(key: string): Promise<void>;
|
|
113
151
|
};
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
|
|
153
|
+
// 授权数据,见 3.3:私聊不传 memberId,群聊传消息前缀里的 m_…
|
|
154
|
+
dataAccess: {
|
|
155
|
+
query(request: { datasets: string[]; memberId?: string }): Promise<DatasetAccessResult[]>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// 小挂件,见 3.4
|
|
159
|
+
widgets: {
|
|
160
|
+
list(): Promise<WidgetSummary[]>;
|
|
161
|
+
create(input: { type: string; title: string; idempotencyKey?: string }): Promise<{ id: string }>;
|
|
162
|
+
read(id: string): Promise<Widget>;
|
|
163
|
+
readModule(id: string, module: string): Promise<{ records: WidgetRecord[] }>;
|
|
164
|
+
rename(id: string, title: string): Promise<void>;
|
|
165
|
+
write(
|
|
166
|
+
id: string,
|
|
167
|
+
input: { module: string; recordId: string; data: Record<string, unknown>; expectedRevision?: number },
|
|
168
|
+
): Promise<{ revision?: number }>;
|
|
169
|
+
deleteRecord(id: string, module: string, recordId: string): Promise<void>;
|
|
170
|
+
delete(id: string): Promise<void>;
|
|
171
|
+
setPresentation(id: string, input: { summary: string; imageMediaId: string }): Promise<void>;
|
|
172
|
+
send(id: string, options: { idempotencyKey: string }): void;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// 主动服务,见 3.5:提订阅建议,用户在 App 里确认后才生效
|
|
176
|
+
proactive: {
|
|
177
|
+
propose(request: ProactiveSubscriptionRequest): Promise<{ id: string; status: "pending" }>;
|
|
178
|
+
};
|
|
118
179
|
}
|
|
119
180
|
```
|
|
120
181
|
|
|
121
|
-
`
|
|
182
|
+
`memory`、`dataAccess`、`widgets`、`proactive` 已经绑好这场会话,直接调用,不用传令牌或拼接口路径。
|
|
183
|
+
|
|
184
|
+
## 3. 平台能力
|
|
122
185
|
|
|
123
|
-
| 能力 |
|
|
186
|
+
| 能力 | `conversation` 对象方法 | 默认注册成的模型工具 | 默认注册成模型工具的条件 |
|
|
124
187
|
|---|---|---|---|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
188
|
+
| 资料 | `conversation.resources.list` / `conversation.resources.read` | `resource_read` | platformTools 声明 && `resources/` 下有 `.md` |
|
|
189
|
+
| 记忆 | `conversation.memory` | 无,默认不注册成模型工具,开发者可以根据具体场景,注册成适合场景需要的模型工具 | — |
|
|
190
|
+
| 授权数据 | `conversation.dataAccess.query` | `data_access_query` | platformTools 声明 && 声明过至少一个 Dataset |
|
|
191
|
+
| 小挂件 | `conversation.widgets.list` / `conversation.widgets.create` / `conversation.widgets.read` / `conversation.widgets.readModule` / `conversation.widgets.rename` / `conversation.widgets.write` / `conversation.widgets.deleteRecord` / `conversation.widgets.delete` / `conversation.widgets.setPresentation` / `conversation.widgets.send` | `widget_create` / `widget_write` / `widget_read` / `widget_delete` / `widget_send` | platformTools 声明 && 登记过至少一个小挂件类型 |
|
|
192
|
+
| 主动服务 | `conversation.proactive.propose` | 无 | — |
|
|
193
|
+
|
|
194
|
+
### 3.1 资料
|
|
131
195
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
196
|
+
把 `.md` 资料放进工程目录(`package.json` 所在目录)下的 `resources/`,`platformTools` 写上 `resource_read`,模型就能按需读。
|
|
197
|
+
|
|
198
|
+
- 工程目录从入口脚本往上找,与启动时的工作目录无关。
|
|
199
|
+
- 启动时递归读取 `.md`,跳过 `node_modules`、`venv`、`.venv`、`__pycache__` 与点开头的文件和目录;资料改了要重启进程。
|
|
200
|
+
- 系统提示末尾追加资料清单,模型按清单里的相对路径读。没有资料或没选 `resource_read` 时,不注册工具,也不追加清单。
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
my-buddy/ 工程目录
|
|
204
|
+
├─ package.json
|
|
205
|
+
├─ resources/
|
|
206
|
+
│ └─ research/
|
|
207
|
+
│ └─ 01-writings.md 清单里写作 research/01-writings.md
|
|
208
|
+
└─ src/main.ts 入口脚本
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**在自己的代码里用**
|
|
212
|
+
|
|
213
|
+
调用 `conversation.resources`,不需要写进 `platformTools`:
|
|
214
|
+
|
|
215
|
+
| 方法 | 做什么 |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `list()` | 全部资料的路径,相对 `resources/`,按路径排序 |
|
|
218
|
+
| `read(file)` | 读一篇的正文,`file` 照 `list()` 给的路径原样填;没有这篇时抛异常 |
|
|
135
219
|
|
|
136
220
|
```ts
|
|
137
|
-
|
|
138
|
-
platformTools: ["data_access_query", "widget_read"],
|
|
139
|
-
initialState: { systemPrompt: PERSONA, tools: [remember] },
|
|
140
|
-
};
|
|
221
|
+
const notes = conversation.resources.read("research/01-writings.md");
|
|
141
222
|
```
|
|
142
223
|
|
|
143
|
-
|
|
224
|
+
- 两个方法都是同步的,读的是进程启动时读好的那份。
|
|
225
|
+
- 主动服务回合里也能调。
|
|
226
|
+
|
|
227
|
+
完整示例见 [examples/stacy-sims/](examples/stacy-sims/)。
|
|
144
228
|
|
|
145
|
-
|
|
229
|
+
### 3.2 记忆
|
|
230
|
+
|
|
231
|
+
这场会话的长期记忆,存在平台上,只在本会话内可见,会话删除时一并清除。
|
|
232
|
+
|
|
233
|
+
- `list()`:列出全部,按 `key` 字典序。
|
|
234
|
+
- `write(key, text)`:覆盖写一条。
|
|
235
|
+
- `delete(key)`:删一条,不存在也算成功。
|
|
236
|
+
|
|
237
|
+
`key` 非空、不含空白与 `/`、最长 64 字;`text` 单条不超过 16 KB;每场会话最多 64 条、合计不超过 256 KB。超限、`key` 不合法或网络出错时抛异常,异常信息是平台给出的原因,在工具里调用时模型能看到。
|
|
146
238
|
|
|
147
239
|
```ts
|
|
148
240
|
interface MemoryEntry {
|
|
149
|
-
key: string;
|
|
241
|
+
key: string;
|
|
150
242
|
text: string;
|
|
151
243
|
updatedAt: number; // 最后写入时刻,毫秒
|
|
152
244
|
}
|
|
153
245
|
```
|
|
154
246
|
|
|
155
|
-
|
|
247
|
+
运行时不提供记忆工具,要让模型记东西,自己包一个:
|
|
156
248
|
|
|
157
249
|
```ts
|
|
158
250
|
const REMEMBER_PARAMS = Type.Object({ topic: Type.String(), text: Type.String() });
|
|
@@ -166,7 +258,7 @@ await BuddyServer.start({
|
|
|
166
258
|
description: "把一条关于对方的印象记下来,同一主题覆盖旧的。",
|
|
167
259
|
parameters: REMEMBER_PARAMS,
|
|
168
260
|
execute: async (_id, { topic, text }) => {
|
|
169
|
-
await conversation.
|
|
261
|
+
await conversation.memory.write(topic, text);
|
|
170
262
|
return { content: [{ type: "text", text: "记下了。" }], details: undefined };
|
|
171
263
|
},
|
|
172
264
|
};
|
|
@@ -175,15 +267,13 @@ await BuddyServer.start({
|
|
|
175
267
|
});
|
|
176
268
|
```
|
|
177
269
|
|
|
178
|
-
|
|
270
|
+
### 3.3 授权数据
|
|
179
271
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
经用户同意,搭子可以读取用户授权数据。数据按 Dataset 分项,每项内容与字段固定,用户逐项授权。开发者要做的是声明需要哪些 Dataset,然后让模型用 `data_access_query` 查,或者在自己的工具里查。会话与真实用户身份由平台绑定,搭子拿不到真实用户 id。
|
|
272
|
+
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。
|
|
183
273
|
|
|
184
274
|
**声明需要哪些数据**
|
|
185
275
|
|
|
186
|
-
|
|
276
|
+
`PUT /cli/buddies/:id/data-requirements` 提交完整清单,每次覆盖上一次:
|
|
187
277
|
|
|
188
278
|
```json
|
|
189
279
|
{
|
|
@@ -194,11 +284,9 @@ await BuddyServer.start({
|
|
|
194
284
|
```
|
|
195
285
|
|
|
196
286
|
- `id`:从下表选。
|
|
197
|
-
- `purpose
|
|
198
|
-
- 改了某项的 `purpose
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
目前支持的 Dataset:
|
|
287
|
+
- `purpose`:用途,1~200 字。用户看到这句话再决定是否同意。
|
|
288
|
+
- 改了某项的 `purpose`,用户要重新同意;从清单里删掉的,搭子立即读不到。
|
|
289
|
+
- 改完清单要重启搭子进程,模型才能看到新清单。
|
|
202
290
|
|
|
203
291
|
| Dataset | 内容 | `data.items` 每条的字段 |
|
|
204
292
|
|---|---|---|
|
|
@@ -213,65 +301,70 @@ await BuddyServer.start({
|
|
|
213
301
|
|
|
214
302
|
部分字段可能缺失,取值前先判断。
|
|
215
303
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
`platformTools` 里有 `data_access_query`,且声明过至少一项数据,模型就有了这个工具,工具说明里列着开发者声明的数据与用途。开发者可以在系统提示里写明什么时候查,例如「聊到作息时,先用 data_access_query 查 health.sleep」。
|
|
304
|
+
**查询**
|
|
219
305
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
调用 `conversation.platform.dataAccess.query`,不需要写进 `platformTools`:
|
|
306
|
+
- 让模型查:`platformTools` 写上 `data_access_query`,工具说明里会列出声明过的 Dataset 与用途。可以在系统提示里写明什么时候查,比如「聊到作息时,先用 data_access_query 查 health.sleep」。
|
|
307
|
+
- 在工具里查:调用 `conversation.dataAccess.query`。
|
|
223
308
|
|
|
224
309
|
```ts
|
|
225
|
-
const [sleep] = await conversation.
|
|
310
|
+
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
226
311
|
if (sleep.status === "available") {
|
|
227
312
|
// sleep.data.items 是每天一条的睡眠记录
|
|
228
313
|
}
|
|
229
314
|
```
|
|
230
315
|
|
|
231
|
-
- `datasets`:一次最多 8 个;响应超过 512 KiB
|
|
232
|
-
- `memberId
|
|
233
|
-
|
|
234
|
-
**查询结果**
|
|
316
|
+
- `datasets`:一次最多 8 个;响应超过 512 KiB 时抛异常,减少 Dataset 再查。
|
|
317
|
+
- `memberId`:私聊不传;群聊必传,取群消息开头 `[名字 #m_…]` 里的 `m_…`,查这位成员的数据,传错时抛异常。
|
|
235
318
|
|
|
236
|
-
每个 Dataset
|
|
319
|
+
每个 Dataset 各自一个状态,只有 `available` 带 `data`:
|
|
237
320
|
|
|
238
321
|
| 状态 | 意思 |
|
|
239
322
|
|---|---|
|
|
240
323
|
| `available` | 读到了 |
|
|
241
324
|
| `notDeclared` | 搭子没有声明这项数据 |
|
|
242
|
-
| `notGranted` |
|
|
325
|
+
| `notGranted` | 用户没有同意,或改过用途后还没重新同意 |
|
|
243
326
|
| `disabledInGroup` | 这位成员在这个群里关掉了这项数据 |
|
|
244
327
|
| `unavailable` | 平台上还没有这位用户的这项数据,或数据已过期 |
|
|
245
328
|
|
|
246
|
-
|
|
329
|
+
**数据只在当轮可见**
|
|
330
|
+
|
|
331
|
+
查到的数据只用于这一轮回复:平台不存原文,搭子进程里的那份也在这一轮结束时被抹掉,下一轮读不到。凡是可能把它带出去的地方——工具结果、工具参数、那一步的正文和心声——这一轮统统不写进平台。
|
|
332
|
+
|
|
333
|
+
这么设计是为了**用户撤销授权时能真的收回**:数据没有留在任何地方,不需要事后去清。代价是这一轮的记录不完整,搭子重启后读不到自己当时的推理和工具参数。
|
|
334
|
+
|
|
335
|
+
由此有两条实操结论:
|
|
247
336
|
|
|
248
|
-
|
|
337
|
+
- **想让某个结论以后还在场,就在回复里说出来**(用户也会看到)。模型对这些数据的记忆只能活在它说过的话里——下次它读到的是「查了 health.sleep,可用」,不是具体数字。
|
|
338
|
+
- **别把查到的数据写进记忆、小挂件或别的工具的参数**。那些地方不脱敏,写进去就永久留下了。
|
|
249
339
|
|
|
250
|
-
|
|
340
|
+
### 3.4 小挂件
|
|
341
|
+
|
|
342
|
+
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。模型通过平台工具、开发者通过 `conversation.widgets`,都能建小挂件、写记录、读记录、删记录,并把它发进聊天。
|
|
251
343
|
|
|
252
344
|
**登记类型**
|
|
253
345
|
|
|
254
|
-
|
|
346
|
+
- `PUT /cli/buddies/:id/widget-types/:typeId/schema`:交 schema。每个模块写一句 `description` 给模型读,每条记录的形状是一份 JSON Schema。
|
|
347
|
+
- `PUT /cli/buddies/:id/widget-types/:typeId/page`:交展示页面,单个 HTML 文件。
|
|
255
348
|
|
|
256
|
-
|
|
349
|
+
**让模型使用**
|
|
257
350
|
|
|
258
|
-
|
|
351
|
+
把要用的 `widget_*` 写进 `platformTools`。
|
|
259
352
|
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
353
|
+
- 工具说明里附上各类型的模块与字段。类型在进程里第一次用到时读取,改了类型要重启进程。
|
|
354
|
+
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型,见第 4 节。
|
|
355
|
+
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
356
|
+
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
263
357
|
|
|
264
|
-
|
|
358
|
+
**在自己的代码里用**
|
|
265
359
|
|
|
266
|
-
调用 `conversation.
|
|
360
|
+
调用 `conversation.widgets`,不需要写进 `platformTools`:
|
|
267
361
|
|
|
268
362
|
```ts
|
|
269
|
-
execute: async (toolCallId, params
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return { content: [{ type: "text", text: "行程建好了。" }], details: undefined };
|
|
363
|
+
execute: async (toolCallId, params) => {
|
|
364
|
+
const { id } = await conversation.widgets.create({ type: "menu", title: "本周菜单", idempotencyKey: `menu:${toolCallId}` });
|
|
365
|
+
await conversation.widgets.write(id, { module: "菜单", recordId: "2026-09-14", data: { dishes: ["红烧肉", "清炒时蔬"] } });
|
|
366
|
+
conversation.widgets.send(id, { idempotencyKey: `menu-send:${toolCallId}` });
|
|
367
|
+
return { content: [{ type: "text", text: "菜单建好了。" }], details: undefined };
|
|
275
368
|
},
|
|
276
369
|
```
|
|
277
370
|
|
|
@@ -281,109 +374,87 @@ execute: async (toolCallId, params, signal) => {
|
|
|
281
374
|
| `create({ type, title, idempotencyKey? })` | 新建一个,回 `{ id }`;同一个 `idempotencyKey` 重复建回同一个 id | `POST /internal/v1/widgets` |
|
|
282
375
|
| `read(id)` | 读整个小挂件,含各模块的记录 | `GET /internal/v1/widgets/:widgetId` |
|
|
283
376
|
| `readModule(id, module)` | 读一个模块,回 `{ records }` | `GET /internal/v1/widgets/:widgetId/:module` |
|
|
284
|
-
| `rename(id, title)` | 改标题,id
|
|
285
|
-
| `write(id, { module, recordId, data, expectedRevision? })` |
|
|
377
|
+
| `rename(id, title)` | 改标题,id 不变,聊天里发过的卡片跟着显示新标题 | `PUT /internal/v1/widgets/:widgetId` |
|
|
378
|
+
| `write(id, { module, recordId, data, expectedRevision? })` | 写一条记录,已存在就整条覆盖,回 `{ revision }`;带 `expectedRevision` 时版本号对不上,平台回 409 | `PUT /internal/v1/widgets/:widgetId/:module/:recordId` |
|
|
286
379
|
| `deleteRecord(id, module, recordId)` | 删一条记录 | `DELETE /internal/v1/widgets/:widgetId/:module/:recordId` |
|
|
287
|
-
| `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
380
|
+
| `delete(id)` | 删整个小挂件连记录;聊天里发过的卡片留着,点开显示已删除 | `DELETE /internal/v1/widgets/:widgetId` |
|
|
381
|
+
| `setPresentation(id, { summary, imageMediaId })` | 设聊天里卡片的摘要(不超过 500 字)和封面图,`imageMediaId` 传空串去掉封面 | `PUT /internal/v1/widgets/:widgetId/presentation` |
|
|
382
|
+
| `send(id, { idempotencyKey })` | 发进聊天:放进发送队列就返回,不等平台确认送达;同一个 `idempotencyKey` 重复发只落一条 | `POST /internal/v1/messages` |
|
|
290
383
|
|
|
291
|
-
-
|
|
384
|
+
- `list` 和 `read` 回的 `canEdit` 为 `false` 的,是别的会话转发来的引用,只能读;改名、写记录、删除、设卡片时平台回 403。
|
|
292
385
|
- `data` 要符合登记的 schema,不符合时平台回 400。
|
|
293
|
-
- 平台回非 2xx、网络出错、超过 30 秒或响应超过 4 MiB
|
|
386
|
+
- 平台回非 2xx、网络出错、超过 30 秒或响应超过 4 MiB 时抛异常,异常的 `status` 是 HTTP 状态码,不带平台给的原因。写操作不自动重试,失败后先读一遍真实状态再决定。
|
|
387
|
+
- 主动服务回合里,除了 `list`、`read`、`readModule`,其余方法都会抛异常,见 3.5。
|
|
294
388
|
|
|
295
389
|
用法见 [examples/aunt-tu/](examples/aunt-tu/)。
|
|
296
390
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
资料放在工程目录(`package.json` 所在目录)下的 `resources/`。工程目录由入口脚本向上定位,与启动进程的工作目录无关。
|
|
300
|
-
|
|
301
|
-
- 启动时递归读取 `.md`,跳过 `node_modules`、`venv`、`.venv`、`__pycache__` 及点开头的目录/文件;资料变更后需重启进程。
|
|
302
|
-
- `platformTools` 里有 `read_resource` 时,系统提示末尾追加资料清单;模型通过清单中的相对路径读取内容。
|
|
303
|
-
- 没有资料或名单里没有 `read_resource` 时,不注册 `read_resource`,也不追加资料清单。
|
|
391
|
+
### 3.5 主动服务
|
|
304
392
|
|
|
305
|
-
|
|
306
|
-
my-buddy/ 工程目录
|
|
307
|
-
├─ package.json
|
|
308
|
-
├─ resources/ 资料目录
|
|
309
|
-
│ └─ research/
|
|
310
|
-
│ └─ 01-writings.md 清单中写作 research/01-writings.md
|
|
311
|
-
└─ src/main.ts 入口脚本
|
|
312
|
-
```
|
|
393
|
+
让搭子在约定的时机主动找用户说话,比如每周一次复盘。只支持私聊。
|
|
313
394
|
|
|
314
|
-
|
|
395
|
+
1. 开发者的工具调用 `conversation.proactive.propose` 提订阅建议。订阅建成时是 `pending`,用户在 App 里确认后才生效。
|
|
396
|
+
2. 触发条件满足,且没被免打扰时段、冷却时间、每日上限拦下时,平台发起一次主动回合。
|
|
397
|
+
3. 模型判断要不要开口:要就直接写出发给用户的消息,不要就输出跳过标记。平台复核后把消息发给用户。
|
|
315
398
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
**图片**
|
|
326
|
-
|
|
327
|
-
用户发的图不需要开发者处理。平台在拉取消息时把图片内容一起给运行时,运行时把它放进会话历史,之后每次请求模型原样带上:
|
|
328
|
-
|
|
329
|
-
- 图片在用户上传时由平台统一处理成 JPEG,最长边不超过 2048 px、不超过 5 MiB。
|
|
330
|
-
- 图片消息交给模型时是一条 user 消息:正文前标着「[图片 #消息 id]」,用户配了文字接在后面,再跟图片本身。
|
|
331
|
-
- 引用了别的消息的,开头加一行:引用文字写「[引用 发言人:原话]」,引用图片写「[引用 发言人的图片 #被引消息 id]」,模型按 id 对上是哪一张。
|
|
332
|
-
- 转发进聊天记录卡的图,跟在卡里那条正文后面。
|
|
333
|
-
- 图片已经读不到时换成「[图片无法读取]」;`model` 选的模型不支持看图时换成「[图片:当前模型不支持看图]」。
|
|
334
|
-
|
|
335
|
-
## 4. 会话历史:MLBMessage → AgentMessage → LLMMessage
|
|
336
|
-
|
|
337
|
-
开发者不需要保存会话历史,也不需要把聊天里的消息换成模型消息,这些由运行时完成。每场会话的消息都保存在平台上,搭子进程重启后历史照样在。从平台上的 `MLBMessage` 到模型收到的 `LLMMessage`:
|
|
338
|
-
|
|
339
|
-
```
|
|
340
|
-
MLBMessage[] → AgentMessage[] → convertToLlm() → LLMMessage[] → LLM
|
|
341
|
-
▲ (1) │ ▲ (2) │
|
|
342
|
-
└─────(4)──────┘ └──────────────────(3)─────────────────────┘
|
|
399
|
+
```ts
|
|
400
|
+
await conversation.proactive.propose({
|
|
401
|
+
title: "每周复盘",
|
|
402
|
+
reason: "在约定时间结合最近聊天做一次简短复盘",
|
|
403
|
+
sources: ["schedule"],
|
|
404
|
+
eventTypes: ["schedule.due"],
|
|
405
|
+
allowedDataScopes: [],
|
|
406
|
+
schedule: { at: firstReviewAt, intervalMinutes: 7 * 24 * 60 },
|
|
407
|
+
});
|
|
343
408
|
```
|
|
344
409
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
410
|
+
| 字段 | 说明 |
|
|
411
|
+
|---|---|
|
|
412
|
+
| `title` | 必填,订阅名称,最长 100 字 |
|
|
413
|
+
| `reason` | 必填,为什么要主动找用户,最长 500 字 |
|
|
414
|
+
| `sources` | 必填,触发来源,`[]` 表示不限 |
|
|
415
|
+
| `eventTypes` | 必填,至少一个,触发事件类型 |
|
|
416
|
+
| `allowedDataScopes` | 必填,主动回合里允许读的 Dataset,只能从已声明的里选,`[]` 表示不读 |
|
|
417
|
+
| `schedule` | 可选,定时触发:`at` 为首次时刻(UTC 毫秒),`intervalMinutes` 为重复间隔(60~525600),按固定间隔算,不随夏令时调整。用它时 `eventTypes` 要含 `schedule.due`,`sources` 为 `[]` 或含 `schedule` |
|
|
418
|
+
| `quietHours` | 可选,免打扰时段,缺省 `{ start: "22:30", end: "08:00", timezone: "Asia/Shanghai" }` |
|
|
419
|
+
| `cooldownMinutes` | 可选,冷却时间,缺省 120,取 0~10080 |
|
|
420
|
+
| `dailyLimit` | 可选,每天最多几次,缺省 3,取 1~20 |
|
|
351
421
|
|
|
352
|
-
|
|
422
|
+
字段不合法、在群聊里调用,或同一用户对这只搭子已有 20 个未撤销的订阅时抛异常,异常信息只有 HTTP 状态码。
|
|
353
423
|
|
|
354
|
-
|
|
355
|
-
|---|---|
|
|
356
|
-
| `MLB_BUDDY_ID` | 搭子 id,注册时由平台分配 |
|
|
357
|
-
| `MLB_BUDDY_TOKEN` | 搭子令牌,注册时由平台分配 |
|
|
424
|
+
主动回合里:
|
|
358
425
|
|
|
359
|
-
`
|
|
426
|
+
- **模型能用的工具只有 `proactiveTools` 里点名的那些,默认一个都没有。** 用户不在场,所以不默认信任任何工具——名字叫 `read_xxx` 的也可能在里面发请求、写外部系统。没点名的工具模型仍然看得见,调了会被挡回一条说明,工具本身不执行。`data_access_query` 另外只能读订阅的 `allowedDataScopes`。
|
|
427
|
+
- 系统提示和普通回合一字不差,记忆不会被附上去。要让模型用记忆,自己把 `conversation.memory.list` 包成工具注册进来,再写进 `proactiveTools`。
|
|
428
|
+
- 调用 `memory.write`、`memory.delete`、`proactive.propose`,以及 `widgets` 上除 `list`、`read`、`readModule` 以外的方法,会抛异常;开发者的钩子照常执行。
|
|
429
|
+
- 用户这时发来消息,主动回合中止,先回复用户;超过 4 分钟也中止。
|
|
430
|
+
- 主动回合的过程不下发给用户:平台在会话流里记两条不可见的记录(叫你判断了什么、你判断的结果),中间的工具步照常写回但也不下发。**开口的那条消息才是用户看得到的**,之后作为搭子说过的话出现在会话历史里。
|
|
360
431
|
|
|
361
|
-
|
|
432
|
+
## 4. 模型收到什么
|
|
362
433
|
|
|
363
|
-
|
|
434
|
+
开发者不用保存会话历史,也不用自己拼模型消息。每场会话的消息存在平台上,进程重启后运行时从平台取回。每次请求模型时,运行时组出以下内容。
|
|
364
435
|
|
|
365
|
-
|
|
366
|
-
- 会话消息拉取遇到临时网络错误时会主动指数退避重试(1 秒起步、最高 30 秒);新的 SSE 通知会取消等待并立即重拉。
|
|
436
|
+
**系统提示**,按顺序拼接,段间空一行:
|
|
367
437
|
|
|
368
|
-
|
|
438
|
+
1. 平台的系统提示:对每场会话都一样的规则,比如消息开头的时间怎么读、用户所在时区去哪看。
|
|
439
|
+
2. `initialState.systemPrompt`。
|
|
440
|
+
3. 资料清单:选了 `resource_read` 且有资料时才有。
|
|
369
441
|
|
|
370
|
-
|
|
442
|
+
**工具**:选中的平台工具在前,`initialState.tools` 在后。
|
|
371
443
|
|
|
372
|
-
|
|
444
|
+
**消息**:这场会话的完整历史,按时间先后排列。一轮回复里模型可能请求多次,比如调完工具接着说,每次都带上当时的完整历史。
|
|
373
445
|
|
|
374
|
-
|
|
446
|
+
- 用户消息、工具结果、运行时提醒的开头带 GMT 时间,如「[2026-09-10 14:00:30 GMT] 」;搭子自己说的话不带。
|
|
447
|
+
- 历史末尾多一条「现在的时间(平台附注,不是谁说的话)」,时间是请求时刻,只在这一次请求里。
|
|
448
|
+
- 群聊里成员说的话开头标「[名字 #m_…] 」。
|
|
449
|
+
- 平台交代的情况(比如这场会话里已有哪些小挂件)是一条以「平台交代的情况:」开头的消息,情况有变化才追加新的一份。
|
|
450
|
+
- 引用了别的消息时,开头加一行「[引用 发言人:原话]」,引用图片写「[引用 发言人的图片 #被引消息 id]」。
|
|
451
|
+
- 转发的聊天记录卡按条目展开,每条先写「【名字】」再写正文。
|
|
452
|
+
- 查过授权数据的那一轮,结束后按 3.3 替换。
|
|
375
453
|
|
|
376
|
-
|
|
377
|
-
await conversation.platform.proactive.propose({
|
|
378
|
-
title: "每周复盘",
|
|
379
|
-
reason: "在约定时间结合最近聊天做一次简短复盘",
|
|
380
|
-
sources: ["schedule"],
|
|
381
|
-
eventTypes: ["schedule.due"],
|
|
382
|
-
allowedDataScopes: [],
|
|
383
|
-
schedule: { at: firstReviewAt, intervalMinutes: 7 * 24 * 60 },
|
|
384
|
-
});
|
|
385
|
-
```
|
|
454
|
+
**图片**:用户发的图不用开发者处理。
|
|
386
455
|
|
|
387
|
-
|
|
456
|
+
- 平台在上传时统一处理成 JPEG,最长边不超过 2048 px、不超过 5 MiB。
|
|
457
|
+
- 图片消息是一条 user 消息:先是「[图片 #消息 id]」和用户配的文字,再跟图片本身;聊天记录卡里的图跟在所在条目的正文后面。
|
|
458
|
+
- 图片读不到时换成「[图片无法读取]」;`model` 不支持看图时换成「[图片:当前模型不支持看图]」。
|
|
388
459
|
|
|
389
|
-
|
|
460
|
+
在钩子里读会话历史时,除了 pi 的 `user` / `assistant` / `toolResult`,还会遇到 `role` 为 `note` 的消息:运行时补给模型的提醒,比如一轮回复没说出话时的提示,发给模型时换成 user 消息。
|