@my-life-buddies/buddy-runtime 0.9.0 → 0.11.0
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 +222 -134
- package/dist/conversation/agent.d.ts +10 -4
- package/dist/conversation/agent.d.ts.map +1 -1
- package/dist/conversation/agent.js +93 -68
- package/dist/conversation/agent.js.map +1 -1
- package/dist/conversation/compaction.d.ts +2 -2
- package/dist/conversation/compaction.d.ts.map +1 -1
- package/dist/conversation/compaction.js +1 -1
- package/dist/conversation/compaction.js.map +1 -1
- package/dist/conversation/index.d.ts +12 -10
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js +20 -20
- package/dist/conversation/index.js.map +1 -1
- package/dist/dataAccessCatalog.d.ts +56 -0
- package/dist/dataAccessCatalog.d.ts.map +1 -0
- package/dist/dataAccessCatalog.js +18 -0
- package/dist/dataAccessCatalog.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mlbClient.d.ts +5 -7
- package/dist/mlbClient.d.ts.map +1 -1
- package/dist/mlbClient.js +6 -18
- package/dist/mlbClient.js.map +1 -1
- package/dist/model.d.ts +8 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -6
- package/dist/model.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +25 -35
- package/dist/server.js.map +1 -1
- package/dist/tools/dataAccess.d.ts +1 -1
- package/dist/tools/dataAccess.d.ts.map +1 -1
- package/dist/tools/dataAccess.js +4 -4
- package/dist/tools/dataAccess.js.map +1 -1
- package/dist/tools/dataAccessRequest.d.ts +7 -0
- package/dist/tools/dataAccessRequest.d.ts.map +1 -0
- package/dist/tools/{permissions.js → dataAccessRequest.js} +7 -7
- package/dist/tools/dataAccessRequest.js.map +1 -0
- package/dist/tools/registry.d.ts +1 -4
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +1 -15
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/resource.d.ts +4 -3
- package/dist/tools/resource.d.ts.map +1 -1
- package/dist/tools/resource.js +18 -13
- package/dist/tools/resource.js.map +1 -1
- package/dist/tools/{widgets.d.ts → widget.d.ts} +6 -4
- package/dist/tools/widget.d.ts.map +1 -0
- package/dist/tools/{widgets.js → widget.js} +13 -9
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +56 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -3
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/tools/permissions.d.ts +0 -7
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/widgets.d.ts.map +0 -1
- package/dist/tools/widgets.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# @my-life-buddies/buddy-runtime
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
搭子服务的运行时基座,负责平台通信与会话管理,开发者可专注在领域逻辑。主要职责:
|
|
4
|
+
|
|
5
|
+
1. **平台通信**:基于平台协议接收消息、调用模型,并将回复写回平台。运行时本身无状态,会话历史、记忆等数据均持久化在平台 —— **开发者无需为会话历史与记忆自建存储,服务运维无需关注数据持久**。
|
|
6
|
+
|
|
7
|
+
2. **会话管理**:搭子与每位用户的私聊、群聊各对应一个会话,每个会话持有一个基于 [`@earendil-works/pi-agent-core`](https://www.npmjs.com/package/@earendil-works/pi-agent-core) 的 Agent 实例。基座接管会话的完整生命周期:冷启重建、空闲回收、超长历史压缩 —— **开发者无需关注 Agent 生命周期,基座全接管**。
|
|
8
|
+
|
|
9
|
+
3. **会话配置**:每个会话实例创建时调用开发者传入的 Buddy 配置工厂函数,入参为当前会话的 `conversation` 对象,提供已绑定当前会话的平台能力,包括资料、提问、记忆、用户授权数据、小挂件、主动服务、付费服务等 —— **开发者可以尽情组合各种数据和能力,提供有特色差异的服务**。
|
|
10
|
+
|
|
11
|
+
开始创造你的「搭子」吧!
|
|
6
12
|
|
|
7
13
|
## 1. 快速开始
|
|
8
14
|
|
|
@@ -12,22 +18,22 @@
|
|
|
12
18
|
npm install @my-life-buddies/buddy-runtime typebox
|
|
13
19
|
```
|
|
14
20
|
|
|
15
|
-
### 1.2
|
|
21
|
+
### 1.2 创建搭子
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
通过 `@my-life-buddies/cli` 完成搭搭账号注册,并创建一个新的搭子,获取 buddyId & buddyToken
|
|
18
24
|
|
|
19
25
|
### 1.3 搭子工程框架
|
|
20
26
|
|
|
21
27
|
```
|
|
22
28
|
my-buddy/
|
|
23
|
-
├─
|
|
24
|
-
├─ .
|
|
25
|
-
└─
|
|
29
|
+
├─ .env BUDDY_ID、BUDDY_TOKEN
|
|
30
|
+
├─ src/index.ts
|
|
31
|
+
└─ package.json
|
|
26
32
|
```
|
|
27
33
|
|
|
28
34
|
```ts
|
|
29
|
-
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
30
35
|
import { BuddyServer } from "@my-life-buddies/buddy-runtime";
|
|
36
|
+
import type { AgentTool } from "@my-life-buddies/buddy-runtime";
|
|
31
37
|
import { Type } from "typebox";
|
|
32
38
|
|
|
33
39
|
const PLAN_MENU_PARAMS = Type.Object({ date: Type.String() });
|
|
@@ -44,10 +50,12 @@ const planMenu: AgentTool<typeof PLAN_MENU_PARAMS, undefined> = {
|
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
await BuddyServer.start({
|
|
47
|
-
|
|
53
|
+
buddyId: process.env.BUDDY_ID!,
|
|
54
|
+
token: process.env.BUDDY_TOKEN!,
|
|
48
55
|
dataRequirements: [],
|
|
49
56
|
buddy: () => ({
|
|
50
57
|
initialState: {
|
|
58
|
+
model: "kimi-k3",
|
|
51
59
|
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
52
60
|
tools: [planMenu],
|
|
53
61
|
},
|
|
@@ -55,26 +63,6 @@ await BuddyServer.start({
|
|
|
55
63
|
});
|
|
56
64
|
```
|
|
57
65
|
|
|
58
|
-
```json
|
|
59
|
-
{
|
|
60
|
-
"scripts": {
|
|
61
|
-
"start": "node --env-file-if-exists=.env src/main.ts"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 1.4 启动
|
|
67
|
-
|
|
68
|
-
在 `.env` 里配好环境变量,`npm run dev` 启动
|
|
69
|
-
|
|
70
|
-
| 变量 | 说明 |
|
|
71
|
-
|---|---|
|
|
72
|
-
| `MLB_BUDDY_ID` | 必填,搭子 id |
|
|
73
|
-
| `MLB_BUDDY_TOKEN` | 必填,搭子令牌 |
|
|
74
|
-
|
|
75
|
-
- 在自己电脑上连测试网关时,平台把这个进程当作 `<id>-dev` 分身,会话与正式搭子分开。
|
|
76
|
-
- 改完代码想自动重启,在启动命令里加 `--watch`。
|
|
77
|
-
|
|
78
66
|
完整示例:[examples/aunt-tu/](examples/aunt-tu/)(提问、记忆、小挂件)、[examples/stacy-sims/](examples/stacy-sims/)(资料)。
|
|
79
67
|
|
|
80
68
|
## 2. 配置
|
|
@@ -83,54 +71,74 @@ await BuddyServer.start({
|
|
|
83
71
|
|
|
84
72
|
连上平台开始处理消息,返回 `{ stop(): Promise<void> }`。
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
```ts
|
|
75
|
+
interface StartOptions {
|
|
76
|
+
// 必填。搭子 id 与令牌,注册时平台分配,见 1.2。平台托管时以环境变量 BUDDY_ID、BUDDY_TOKEN 注入
|
|
77
|
+
buddyId: string;
|
|
78
|
+
token: string;
|
|
79
|
+
|
|
80
|
+
// 必填。这只搭子需要的用户数据及用途,见 3.4;不需要时传 []
|
|
81
|
+
dataRequirements: { id: DataAccessDatasetId; purpose: string }[];
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
// 必填。每场会话建起来时调一次,返回这场会话的配置,见 2.2、2.3
|
|
84
|
+
buddy: (conversation: Conversation) => BuddyOptions | Promise<BuddyOptions>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
94
87
|
|
|
95
|
-
|
|
88
|
+
Runtime 启动时只在本地校验 `dataRequirements`,不会额外调用接口注册声明。Runtime 根据自身导出的能力目录补齐标题、来源和 Schema 版本;实际调用 `data_access_query`、发送 HealthKit 权限卡或申请使用数据的主动服务时,会把完整声明随业务请求携带给平台。平台在该次请求中更新用途版本并校验 Grant。
|
|
96
89
|
|
|
97
|
-
|
|
90
|
+
**buddy 的生命周期**:`buddy` 在每场会话收到第一条消息时调用一次。会话空闲 30 分钟被回收、或进程重启后,下一条消息会重新调用,所以闭包里的变量不能当长期存储,需要存储会话状态可以根据场景使用 3.3 记忆、3.5 小挂件实现。
|
|
98
91
|
|
|
99
|
-
|
|
92
|
+
**buddy 的会话压缩**:会话历史长到一定程度时,运行时在会话被回收后把早先的对话压成一份摘要写回平台,下次重建时模型看到的是「摘要 + 最近的原文」。开发者不用做任何事,也没有开关。
|
|
100
93
|
|
|
101
94
|
### 2.2 `BuddyOptions`
|
|
102
95
|
|
|
103
|
-
`buddy`
|
|
96
|
+
`buddy` 的返回值。
|
|
104
97
|
|
|
105
98
|
```ts
|
|
106
|
-
type
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
99
|
+
import type {
|
|
100
|
+
AfterToolCallContext,
|
|
101
|
+
AfterToolCallResult,
|
|
102
|
+
AgentTool,
|
|
103
|
+
BeforeToolCallContext,
|
|
104
|
+
BeforeToolCallResult,
|
|
105
|
+
ModelId,
|
|
106
|
+
ShouldStopAfterTurnContext,
|
|
107
|
+
ThinkingLevel,
|
|
108
|
+
} from "@my-life-buddies/buddy-runtime";
|
|
109
|
+
|
|
110
|
+
interface BuddyOptions {
|
|
111
|
+
// 必填。这场会话的初始状态
|
|
112
|
+
initialState: {
|
|
113
|
+
// 必填。模型 id,ModelId 是 "kimi-k2.6" | "kimi-k3" | "deepseek-v4-flash" | "deepseek-v4-pro"。每场会话可以选不同的模型
|
|
114
|
+
model: ModelId;
|
|
115
|
+
|
|
116
|
+
// 必填。人设与做事规则。为保证模型缓存,只放不随回合变化的内容
|
|
117
|
+
systemPrompt: string;
|
|
118
|
+
|
|
119
|
+
// 可选。模型只看得到这里的工具;平台工具从 conversation.tools 里取来放进去,见第 3 节
|
|
120
|
+
tools?: AgentTool[];
|
|
121
|
+
|
|
122
|
+
// 可选,默认 "off"。ThinkingLevel 是 "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"。
|
|
123
|
+
// 模型不支持的级别在请求时就近换成支持的一档,建会话时打一条 warn
|
|
124
|
+
thinkingLevel?: ThinkingLevel;
|
|
125
|
+
};
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| `initialState.systemPrompt` | 必填,人设与做事规则,只放不随回合变化的内容。模型实际收到的系统提示见第 4 节 |
|
|
120
|
-
| `initialState.tools` | 可选,pi 的 `AgentTool` 数组 |
|
|
121
|
-
| `initialState.thinkingLevel` | 可选,默认 `off`。模型不支持的级别在请求时就近换成支持的一档,建会话时打一条 warn |
|
|
122
|
-
| `platformTools` | 可选,给模型注册哪些平台工具,默认 `[]`,见第 3 节 |
|
|
123
|
-
| `proactiveTools` | 可选,主动回合里还能用哪些工具,默认 `[]`(一个都不给)。写工具名,`platformTools` 选中的和 `initialState.tools` 里自己的都行;写了没注册的名字建会话时抛异常。见 3.6 |
|
|
124
|
-
| 其余字段 | 原样交给 pi;`beforeToolCall` 抛异常时按放行处理 |
|
|
127
|
+
// 可选。模型出错或一句话没说时发的兜底回复,用户和模型之后都看得到;主动回合不发
|
|
128
|
+
errorReply?: string;
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
// 可选。工具执行前调用,context 里有这次的 toolCall、校验过的 args 和当前上下文。
|
|
131
|
+
// 返回 { block: true, reason } 时工具不执行,模型看到 reason;抛异常时工具也不执行,模型看到异常信息
|
|
132
|
+
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- 先单独声明为 `AgentTool<typeof 参数 schema>` 再放进数组,`execute` 的参数才有类型;直接写在数组里时参数类型是 `unknown`。
|
|
131
|
-
- 工具名不能与平台工具或其他工具重复。
|
|
134
|
+
// 可选。工具执行完调用,context 里带着执行结果;返回的 content、details、isError 会替换结果里的同名字段,抛异常时结果换成异常信息
|
|
135
|
+
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
// 可选。模型说完一步(这一步的工具也跑完)后调用,返回 true 就不再请求模型。
|
|
138
|
+
// 这一步发出了权限请求卡或题卡时,这一轮直接停下,不调它
|
|
139
|
+
shouldStopAfterTurn?: (context: ShouldStopAfterTurnContext, signal?: AbortSignal) => boolean | Promise<boolean>;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
134
142
|
|
|
135
143
|
### 2.3 `Conversation`
|
|
136
144
|
|
|
@@ -144,8 +152,23 @@ interface Conversation {
|
|
|
144
152
|
// 日志,每行开头带搭子 id
|
|
145
153
|
log: BuddyLogger;
|
|
146
154
|
|
|
155
|
+
// 平台工具,见第 3 节:运行时造好、绑好这场会话,挑要用的放进 initialState.tools
|
|
156
|
+
tools: {
|
|
157
|
+
resource_list: AgentTool;
|
|
158
|
+
resource_read: AgentTool;
|
|
159
|
+
ask_question: AgentTool;
|
|
160
|
+
data_access_query: AgentTool;
|
|
161
|
+
data_access_request: AgentTool;
|
|
162
|
+
data_access_read_result: AgentTool;
|
|
163
|
+
widget_create: AgentTool;
|
|
164
|
+
widget_write: AgentTool;
|
|
165
|
+
widget_read: AgentTool;
|
|
166
|
+
widget_delete: AgentTool;
|
|
167
|
+
widget_send: AgentTool;
|
|
168
|
+
};
|
|
169
|
+
|
|
147
170
|
// 资料,见 3.1:resources/ 里的 .md,进程启动时读好,所有会话共用
|
|
148
|
-
|
|
171
|
+
resource: {
|
|
149
172
|
list(): string[];
|
|
150
173
|
read(file: string): string;
|
|
151
174
|
};
|
|
@@ -160,10 +183,12 @@ interface Conversation {
|
|
|
160
183
|
// 授权数据,见 3.4:私聊不传 memberId,群聊传消息前缀里的 m_…
|
|
161
184
|
dataAccess: {
|
|
162
185
|
query(request: { datasets: string[]; memberId?: string }): Promise<DatasetAccessResult[]>;
|
|
186
|
+
request(input: DataAccessRequestInput & { idempotencyKey: string }): Promise<void>;
|
|
187
|
+
readResult(requestId: string): Promise<DataAccessReadResult>;
|
|
163
188
|
};
|
|
164
189
|
|
|
165
190
|
// 小挂件,见 3.5
|
|
166
|
-
|
|
191
|
+
widget: {
|
|
167
192
|
list(): Promise<WidgetSummary[]>;
|
|
168
193
|
create(input: { type: string; title: string; idempotencyKey?: string }): Promise<{ id: string }>;
|
|
169
194
|
read(id: string): Promise<Widget>;
|
|
@@ -179,34 +204,44 @@ interface Conversation {
|
|
|
179
204
|
send(id: string, options: { idempotencyKey: string }): void;
|
|
180
205
|
};
|
|
181
206
|
|
|
182
|
-
// 主动服务,见 3.6
|
|
207
|
+
// 主动服务,见 3.6:propose 提订阅建议,用户在 App 里确认后才生效;isRunning 判断现在是不是主动回合
|
|
183
208
|
proactive: {
|
|
184
209
|
propose(request: ProactiveSubscriptionRequest): Promise<{ id: string; status: "pending" }>;
|
|
210
|
+
isRunning(): boolean;
|
|
185
211
|
};
|
|
186
212
|
}
|
|
187
213
|
```
|
|
188
214
|
|
|
189
|
-
`memory`、`dataAccess`、`
|
|
215
|
+
`tools`、`memory`、`dataAccess`、`widget`、`proactive` 已经绑好这场会话,直接用,不用传令牌或拼接口路径。
|
|
190
216
|
|
|
191
217
|
## 3. 平台能力
|
|
192
218
|
|
|
193
|
-
| 能力 | `conversation` 对象方法 |
|
|
194
|
-
|
|
195
|
-
| 资料 | `conversation.
|
|
196
|
-
| 提问 | 无 | `ask_question` |
|
|
197
|
-
| 记忆 | `conversation.memory` |
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
219
|
+
| 能力 | `conversation` 对象方法 | 模型工具(`conversation.tools` 上) |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| 资料 | `conversation.resource.list` / `conversation.resource.read` | `resource_list` / `resource_read` |
|
|
222
|
+
| 提问 | 无 | `ask_question` |
|
|
223
|
+
| 记忆 | `conversation.memory` | 无,开发者可以根据具体场景,自己组装成适合的模型工具 |
|
|
224
|
+
| 用户授权数据 | `conversation.dataAccess.query` / `conversation.dataAccess.request` / `conversation.dataAccess.readResult` | `data_access_query` / `data_access_request` / `data_access_read_result` |
|
|
225
|
+
| 小挂件 | `conversation.widget.list` / `conversation.widget.create` / `conversation.widget.read` / `conversation.widget.readModule` / `conversation.widget.rename` / `conversation.widget.write` / `conversation.widget.deleteRecord` / `conversation.widget.delete` / `conversation.widget.setPresentation` / `conversation.widget.send` | `widget_create` / `widget_write` / `widget_read` / `widget_delete` / `widget_send` |
|
|
226
|
+
| 主动服务 | `conversation.proactive.propose` / `conversation.proactive.isRunning` | 无 |
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
buddy: (conversation) => ({
|
|
230
|
+
initialState: {
|
|
231
|
+
model: "kimi-k3",
|
|
232
|
+
systemPrompt: PERSONA,
|
|
233
|
+
tools: [conversation.tools.resource_list, conversation.tools.resource_read, conversation.tools.ask_question, ...myTools],
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
```
|
|
202
237
|
|
|
203
238
|
### 3.1 资料
|
|
204
239
|
|
|
205
|
-
把 `.md` 资料放进工程目录(`package.json` 所在目录)下的 `resources
|
|
240
|
+
把 `.md` 资料放进工程目录(`package.json` 所在目录)下的 `resources/`,把 `conversation.tools.resource_list` 和 `conversation.tools.resource_read` 放进 `initialState.tools`,模型先列出有哪些资料,再按需读。
|
|
206
241
|
|
|
207
242
|
- 工程目录从入口脚本往上找,与启动时的工作目录无关。
|
|
208
243
|
- 启动时递归读取 `.md`,跳过 `node_modules`、`venv`、`.venv`、`__pycache__` 与点开头的文件和目录;资料改了要重启进程。
|
|
209
|
-
-
|
|
244
|
+
- `resource_list` 列出全部资料的相对路径,`resource_read` 按路径读一篇。没有资料时 `resource_list` 回「没有资料」。
|
|
210
245
|
|
|
211
246
|
```
|
|
212
247
|
my-buddy/ 工程目录
|
|
@@ -214,12 +249,12 @@ my-buddy/ 工程目录
|
|
|
214
249
|
├─ resources/
|
|
215
250
|
│ └─ research/
|
|
216
251
|
│ └─ 01-writings.md 清单里写作 research/01-writings.md
|
|
217
|
-
└─ src/
|
|
252
|
+
└─ src/index.ts 入口脚本
|
|
218
253
|
```
|
|
219
254
|
|
|
220
255
|
**在自己的代码里用**
|
|
221
256
|
|
|
222
|
-
调用 `conversation.
|
|
257
|
+
调用 `conversation.resource`,不需要把这两个工具放进 `initialState.tools`:
|
|
223
258
|
|
|
224
259
|
| 方法 | 做什么 |
|
|
225
260
|
|---|---|
|
|
@@ -227,7 +262,7 @@ my-buddy/ 工程目录
|
|
|
227
262
|
| `read(file)` | 读一篇的正文,`file` 照 `list()` 给的路径原样填;没有这篇时抛异常 |
|
|
228
263
|
|
|
229
264
|
```ts
|
|
230
|
-
const notes = conversation.
|
|
265
|
+
const notes = conversation.resource.read("research/01-writings.md");
|
|
231
266
|
```
|
|
232
267
|
|
|
233
268
|
- 两个方法都是同步的,读的是进程启动时读好的那份。
|
|
@@ -239,7 +274,7 @@ const notes = conversation.resources.read("research/01-writings.md");
|
|
|
239
274
|
|
|
240
275
|
让模型发一张题卡请用户选:一个问题加 2 到 4 个可以直接点的选项。私聊、群聊都能用。
|
|
241
276
|
|
|
242
|
-
把 `ask_question`
|
|
277
|
+
把 `conversation.tools.ask_question` 放进 `initialState.tools`,模型就能发题卡。
|
|
243
278
|
|
|
244
279
|
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30 字,不能重复);不合规时模型会看到错误,改了再调。
|
|
245
280
|
- 用户点选项,就是用选项原文发一条消息,跟自己打字一样;也可以不点,直接打字。
|
|
@@ -271,7 +306,6 @@ interface MemoryEntry {
|
|
|
271
306
|
const REMEMBER_PARAMS = Type.Object({ topic: Type.String(), text: Type.String() });
|
|
272
307
|
|
|
273
308
|
await BuddyServer.start({
|
|
274
|
-
model: "kimi-k3",
|
|
275
309
|
dataRequirements: [],
|
|
276
310
|
buddy: (conversation) => {
|
|
277
311
|
const remember: AgentTool<typeof REMEMBER_PARAMS, undefined> = {
|
|
@@ -284,58 +318,51 @@ await BuddyServer.start({
|
|
|
284
318
|
return { content: [{ type: "text", text: "记下了。" }], details: undefined };
|
|
285
319
|
},
|
|
286
320
|
};
|
|
287
|
-
return { initialState: { systemPrompt: PERSONA, tools: [remember] } };
|
|
321
|
+
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
288
322
|
},
|
|
289
323
|
});
|
|
290
324
|
```
|
|
291
325
|
|
|
292
|
-
### 3.4
|
|
326
|
+
### 3.4 用户授权数据
|
|
293
327
|
|
|
294
328
|
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。
|
|
295
329
|
|
|
296
330
|
这里有三层不同概念:
|
|
297
331
|
|
|
298
|
-
1.
|
|
332
|
+
1. **Runtime 能力目录**:当前 SDK 支持哪些 Dataset,由包导出的 `DATA_ACCESS_CAPABILITIES` 提供。
|
|
299
333
|
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset,以及各自用途,写在 `BuddyServer.start.dataRequirements`。
|
|
300
334
|
3. **用户 Grant**:用户是否同意这只搭子读取某个已声明 Dataset,由客户端授权流程管理,搭子不能代替用户授予。
|
|
301
335
|
|
|
302
|
-
|
|
336
|
+
把 `data_access_query`、`data_access_request` 等平台工具放进 `initialState.tools`,只决定模型能不能调用它们,不等于声明 Dataset,也不等于获得用户授权。
|
|
303
337
|
|
|
304
|
-
|
|
338
|
+
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `dataRequirements`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在 `dataRequirements` 中声明。
|
|
305
339
|
|
|
306
|
-
|
|
340
|
+
**读取 Runtime 能力目录**
|
|
307
341
|
|
|
308
|
-
|
|
342
|
+
开发者工具和项目代码直接从 Runtime 包导入,不需要网络请求:
|
|
309
343
|
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"datasets": [
|
|
320
|
-
{
|
|
321
|
-
"id": "health.sleep",
|
|
322
|
-
"title": "睡眠",
|
|
323
|
-
"source": "healthkit",
|
|
324
|
-
"schemaVersion": 1
|
|
325
|
-
}
|
|
326
|
-
]
|
|
344
|
+
```ts
|
|
345
|
+
import {
|
|
346
|
+
DATA_ACCESS_CAPABILITIES,
|
|
347
|
+
dataAccessCapability,
|
|
348
|
+
type DataAccessDatasetId,
|
|
349
|
+
} from "@my-life-buddies/buddy-runtime";
|
|
350
|
+
|
|
351
|
+
for (const capability of DATA_ACCESS_CAPABILITIES) {
|
|
352
|
+
console.log(capability.id, capability.title, capability.source, capability.schemaVersion);
|
|
327
353
|
}
|
|
354
|
+
|
|
355
|
+
const sleep = dataAccessCapability("health.sleep");
|
|
328
356
|
```
|
|
329
357
|
|
|
330
|
-
|
|
358
|
+
这份导出是开发者可选 `id` 的权威来源。新增 Dataset 随 Runtime 版本发布;developer-platform、脚手架和预检都应读取该导出,不再维护一份手写枚举。
|
|
331
359
|
|
|
332
360
|
**声明需要哪些数据**
|
|
333
361
|
|
|
334
|
-
声明写在 `BuddyServer.start` 的初始化参数里,Runtime
|
|
362
|
+
声明写在 `BuddyServer.start` 的初始化参数里,Runtime 在本地展开成带标题、来源和 Schema 版本的完整声明:
|
|
335
363
|
|
|
336
364
|
```ts
|
|
337
365
|
await BuddyServer.start({
|
|
338
|
-
model: "kimi-k3",
|
|
339
366
|
dataRequirements: [
|
|
340
367
|
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" },
|
|
341
368
|
],
|
|
@@ -343,13 +370,11 @@ await BuddyServer.start({
|
|
|
343
370
|
});
|
|
344
371
|
```
|
|
345
372
|
|
|
346
|
-
- `id
|
|
373
|
+
- `id`:从 `DATA_ACCESS_CAPABILITIES` 中选,TypeScript 会收窄为 `DataAccessDatasetId`。
|
|
347
374
|
- `purpose`:用途,1~200 字。用户看到这句话再决定是否同意。
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
- 本地进程连接线上网关时使用 `<buddyId>-dev` 身份,其声明和用户授权与正式搭子隔离,不会覆盖线上版本。
|
|
352
|
-
- 可用 `GET /cli/buddies/:id/data-requirements` 查看某搭子当前已经上传并生效的声明;这个接口只读。
|
|
375
|
+
- Runtime 不在启动时注册声明;查询、HealthKit 权限卡和主动服务请求会携带当前完整声明。
|
|
376
|
+
- Server 在实际请求时按声明用途和 Schema 计算版本;`id`、`purpose` 与 Schema 都没变化时保留已有 Grant,变化后要求重新授权。
|
|
377
|
+
- 本地进程连接线上网关时使用 `<buddyId>-dev` 身份,实际使用形成的授权上下文与正式搭子隔离。
|
|
353
378
|
|
|
354
379
|
| Dataset | 内容 | `data.items` 每条的字段 |
|
|
355
380
|
|---|---|---|
|
|
@@ -364,11 +389,65 @@ await BuddyServer.start({
|
|
|
364
389
|
|
|
365
390
|
部分字段可能缺失,取值前先判断。
|
|
366
391
|
|
|
367
|
-
|
|
392
|
+
**模型工具与代码接口**
|
|
368
393
|
|
|
369
|
-
-
|
|
370
|
-
-
|
|
371
|
-
-
|
|
394
|
+
- `data_access_query` / `conversation.dataAccess.query`:查询已授权 Dataset。
|
|
395
|
+
- `data_access_request` / `conversation.dataAccess.request`:在私聊中发数据授权卡;发卡后当前回合结束,等待用户操作。
|
|
396
|
+
- `data_access_read_result` / `conversation.dataAccess.readResult`:按授权卡的 `requestId` 读取一次性位置或日历结果;HealthKit 不走这个接口。
|
|
397
|
+
|
|
398
|
+
模型工具要从 `conversation.tools` 取来放进 `initialState.tools` 才能调用;没声明 Dataset 时照常注册,查询的每一项都回 `notDeclared`。开发者自己的工具直接调用 `conversation.dataAccess`,不用把这几个工具放进去。
|
|
399
|
+
|
|
400
|
+
**HealthKit:先查询,缺授权再发卡**
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
await BuddyServer.start({
|
|
404
|
+
dataRequirements: [
|
|
405
|
+
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" },
|
|
406
|
+
],
|
|
407
|
+
buddy: (conversation) => ({
|
|
408
|
+
initialState: {
|
|
409
|
+
model: "kimi-k3",
|
|
410
|
+
systemPrompt: [
|
|
411
|
+
"聊到作息时,先用 data_access_query 查询 health.sleep。",
|
|
412
|
+
"返回 notGranted 时,说明当前问题为什么需要数据,再用 data_access_request 发 HealthKit 授权卡。",
|
|
413
|
+
"用户处理授权卡后会开启新回合;再次调用 data_access_query,不要调用 data_access_read_result。",
|
|
414
|
+
"用户拒绝后不要在同一任务里反复申请。",
|
|
415
|
+
].join("\n"),
|
|
416
|
+
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
417
|
+
},
|
|
418
|
+
}),
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
完整链路:
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
data_access_query
|
|
426
|
+
→ notGranted
|
|
427
|
+
→ data_access_request { kind: "healthkit", datasets: ["health.sleep"], reason: "..." }
|
|
428
|
+
→ 用户在授权卡操作,平台更新 Grant 和 Snapshot
|
|
429
|
+
→ 隐藏结果开启新回合
|
|
430
|
+
→ data_access_query
|
|
431
|
+
→ available + data
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
查询接口不会把“从未授权”和“用途变化后需重新确认”分成两种模型状态,都会返回 `notGranted`;重新确认原因由用户侧授权页面展示。
|
|
435
|
+
|
|
436
|
+
**位置和日历:读取一次性结果**
|
|
437
|
+
|
|
438
|
+
位置和日历不属于 Dataset,不写进 `dataRequirements`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
439
|
+
|
|
440
|
+
```text
|
|
441
|
+
data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
442
|
+
→ 用户在授权卡操作
|
|
443
|
+
→ 隐藏结果开启新回合并带回 requestId
|
|
444
|
+
→ data_access_read_result { requestId }
|
|
445
|
+
→ available + data,或 unavailable
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7。授权请求第一期只支持私聊;群聊需要引导目标成员去私聊操作。
|
|
449
|
+
|
|
450
|
+
**开发者代码查询 Dataset**
|
|
372
451
|
|
|
373
452
|
```ts
|
|
374
453
|
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
@@ -403,7 +482,7 @@ if (sleep.status === "available") {
|
|
|
403
482
|
|
|
404
483
|
### 3.5 小挂件
|
|
405
484
|
|
|
406
|
-
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。模型通过平台工具、开发者通过 `conversation.
|
|
485
|
+
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。模型通过平台工具、开发者通过 `conversation.widget`,都能建小挂件、写记录、读记录、删记录,并把它发进聊天。
|
|
407
486
|
|
|
408
487
|
**放进工程目录**
|
|
409
488
|
|
|
@@ -416,27 +495,27 @@ my-buddy/ 工程目录
|
|
|
416
495
|
│ └─ menu/ 类型 id 是 menu
|
|
417
496
|
│ ├─ schema.json 每个模块写一句 description 给模型读,每条记录的形状是一份 JSON Schema
|
|
418
497
|
│ └─ index.html 展示页面,单个 HTML 文件
|
|
419
|
-
└─ src/
|
|
498
|
+
└─ src/index.ts
|
|
420
499
|
```
|
|
421
500
|
|
|
422
501
|
**让模型使用**
|
|
423
502
|
|
|
424
|
-
把要用的 `widget_*`
|
|
503
|
+
把要用的 `conversation.tools.widget_*` 放进 `initialState.tools`。
|
|
425
504
|
|
|
426
505
|
- 工具说明里附上各类型的模块与字段。改了 `widgets/` 要重启进程。
|
|
427
|
-
-
|
|
506
|
+
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型。
|
|
428
507
|
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
429
508
|
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
430
509
|
|
|
431
510
|
**在自己的代码里用**
|
|
432
511
|
|
|
433
|
-
调用 `conversation.
|
|
512
|
+
调用 `conversation.widget`,不需要把 `widget_*` 放进工具:
|
|
434
513
|
|
|
435
514
|
```ts
|
|
436
515
|
execute: async (toolCallId, params) => {
|
|
437
|
-
const { id } = await conversation.
|
|
438
|
-
await conversation.
|
|
439
|
-
conversation.
|
|
516
|
+
const { id } = await conversation.widget.create({ type: "menu", title: "本周菜单", idempotencyKey: `menu:${toolCallId}` });
|
|
517
|
+
await conversation.widget.write(id, { module: "菜单", recordId: "2026-09-14", data: { dishes: ["红烧肉", "清炒时蔬"] } });
|
|
518
|
+
conversation.widget.send(id, { idempotencyKey: `menu-send:${toolCallId}` });
|
|
440
519
|
return { content: [{ type: "text", text: "菜单建好了。" }], details: undefined };
|
|
441
520
|
},
|
|
442
521
|
```
|
|
@@ -496,8 +575,17 @@ await conversation.proactive.propose({
|
|
|
496
575
|
|
|
497
576
|
主动回合里:
|
|
498
577
|
|
|
499
|
-
-
|
|
500
|
-
-
|
|
501
|
-
|
|
578
|
+
- **工具清单和普通回合一样,运行时不按工具名拦。** `widget_create`、`widget_write`、`widget_delete`、`widget_send`、`ask_question`、`data_access_request`、`data_access_read_result` 调了会抛异常,模型看到错误;`data_access_query` 只能读订阅的 `allowedDataScopes`。
|
|
579
|
+
- **开发者自己的工具,运行时不拦。** 用户不在场,工具里要是会发请求、写外部系统,就在 `beforeToolCall` 里按 `conversation.proactive.isRunning()` 挡掉:
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
beforeToolCall: async ({ toolCall }) =>
|
|
583
|
+
conversation.proactive.isRunning() && toolCall.name !== "read_plan"
|
|
584
|
+
? { block: true, reason: "这一轮用户不在场,这个工具用不了。" }
|
|
585
|
+
: undefined,
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
- 系统提示和普通回合一字不差,记忆不会被附上去。要让模型用记忆,自己把 `conversation.memory.list` 包成工具放进 `initialState.tools`。
|
|
589
|
+
- 调用 `memory.write`、`memory.delete`、`proactive.propose`,以及 `widget` 上除 `list`、`read`、`readModule` 以外的方法,会抛异常;开发者的钩子照常执行。
|
|
502
590
|
- 用户这时发来消息,主动回合中止,先回复用户;超过 4 分钟也中止。
|
|
503
|
-
- 主动回合的过程不下发给用户:平台在会话流里记两条不可见的记录(叫你判断了什么、你判断的结果),中间的工具步照常写回但也不下发。**开口的那条消息才是用户看得到的**,之后作为搭子说过的话出现在会话历史里。
|
|
591
|
+
- 主动回合的过程不下发给用户:平台在会话流里记两条不可见的记录(叫你判断了什么、你判断的结果),中间的工具步照常写回但也不下发。**开口的那条消息才是用户看得到的**,之后作为搭子说过的话出现在会话历史里。
|
|
@@ -14,13 +14,14 @@ export interface AgentDeps {
|
|
|
14
14
|
key: string;
|
|
15
15
|
buddyId: string;
|
|
16
16
|
client: MLBClient;
|
|
17
|
-
model
|
|
17
|
+
/** 平台网关地址:按开发者的 initialState.model 造模型条目时,baseUrl 指到它的 /internal/v1 */
|
|
18
|
+
gatewayUrl: string;
|
|
18
19
|
streamFn: StreamFn;
|
|
19
20
|
/** 搭子令牌:经 Agent 的 getApiKey 交给模型通道,变成 Authorization: Bearer 头 */
|
|
20
21
|
token: string;
|
|
21
|
-
/** 小挂件类型目录(server
|
|
22
|
+
/** 小挂件类型目录(server 启动时上传平台的那些);空数组时 widget_* 照常注册,说明里的类型目录为空 */
|
|
22
23
|
widgetTypes: WidgetType[];
|
|
23
|
-
/** 搭子目录下 resources/
|
|
24
|
+
/** 搭子目录下 resources/ 里的资料;空数组时 resource_list 回「没有资料」 */
|
|
24
25
|
resources: Resource[];
|
|
25
26
|
dataRequirements: readonly DataRequirement[];
|
|
26
27
|
factory: BuddyFactory;
|
|
@@ -28,11 +29,16 @@ export interface AgentDeps {
|
|
|
28
29
|
/** Agent 事件的去处。pi 会等它返回,所以只能同步做事 */
|
|
29
30
|
onEvent: (agent: Agent, event: AgentEvent) => void;
|
|
30
31
|
}
|
|
31
|
-
/**
|
|
32
|
+
/** createAgent 建好的:Agent、这场会话的模型条目、授权数据记账,以及开发者配的兜底话 */
|
|
32
33
|
export interface CreatedAgent {
|
|
33
34
|
agent: Agent;
|
|
35
|
+
/** 按开发者 initialState.model 造的模型条目:重建历史时抄身份、判断收不收图片都按它 */
|
|
36
|
+
model: Model<Api>;
|
|
34
37
|
dataAccessUsage: DataAccessUsageTracker;
|
|
38
|
+
/** BuddyOptions.errorReply:普通回合出错或一句话没说时由 index.ts 的 runTurn 发;没配是 undefined */
|
|
39
|
+
errorReply: string | undefined;
|
|
35
40
|
}
|
|
41
|
+
/** 跑开发者工厂拿配置,叠上运行时预置,建 Agent。模型 id 运行时不支持、工具清单重名、拉平台 system prompt 失败 → throw。 */
|
|
36
42
|
export declare function createAgent(deps: AgentDeps): Promise<CreatedAgent>;
|
|
37
43
|
/**
|
|
38
44
|
* 每次请求模型时接在历史末尾那条 user 消息的正文;现在的时间由 convertToLlm 按这条消息的 timestamp 拼在开头。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA8B,eAAe,EAAE,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAE5I,MAAM,WAAW,SAAS;IACzB,mCAAmC;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACrD,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,uDAAuD;IACvD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACnD;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,EAAE,sBAAsB,CAAC;IACxC,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CA2LxE;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,iHAAuB,CAAC;AAEtD,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAErF;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUxD"}
|