@lobehub/chat 1.138.2 → 1.138.3
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/docker-compose/local/docker-compose.yml +1 -1
- package/docker-compose/local/grafana/docker-compose.yml +1 -1
- package/docker-compose/production/grafana/docker-compose.yml +1 -1
- package/package.json +1 -1
- package/packages/database/src/models/topic.ts +0 -1
- package/packages/database/src/repositories/aiInfra/index.ts +19 -13
- package/packages/obervability-otel/package.json +4 -4
- package/packages/prompts/CLAUDE.md +289 -43
- package/packages/prompts/package.json +2 -1
- package/packages/prompts/promptfoo/supervisor/productive/eval.yaml +51 -0
- package/packages/prompts/promptfoo/supervisor/productive/prompt.ts +18 -0
- package/packages/prompts/promptfoo/supervisor/productive/tests/basic-case.ts +54 -0
- package/packages/prompts/promptfoo/supervisor/productive/tests/role.ts +58 -0
- package/packages/prompts/promptfoo/supervisor/productive/tools.json +80 -0
- package/packages/prompts/src/contexts/index.ts +1 -0
- package/packages/prompts/src/contexts/supervisor/index.ts +2 -0
- package/packages/prompts/src/contexts/supervisor/makeDecision.ts +68 -0
- package/packages/prompts/src/contexts/supervisor/tools.ts +102 -0
- package/packages/prompts/src/index.ts +1 -0
- package/packages/types/src/aiChat.ts +9 -3
- package/src/server/routers/lambda/aiChat.ts +1 -1
- package/src/server/services/aiChat/index.test.ts +1 -1
- package/src/server/services/aiChat/index.ts +1 -1
- package/src/services/topic/client.ts +1 -1
- package/src/store/chat/slices/message/supervisor.test.ts +12 -5
- package/src/store/chat/slices/message/supervisor.ts +16 -129
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 1.138.3](https://github.com/lobehub/lobe-chat/compare/v1.138.2...v1.138.3)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-10-18**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix topic fetch not correct in custom agent.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix topic fetch not correct in custom agent, closes [#9761](https://github.com/lobehub/lobe-chat/issues/9761) ([ceffce2](https://github.com/lobehub/lobe-chat/commit/ceffce2))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 1.138.2](https://github.com/lobehub/lobe-chat/compare/v1.138.1...v1.138.2)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2025-10-16**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -230,7 +230,7 @@ services:
|
|
|
230
230
|
otel-tracing-test:
|
|
231
231
|
profiles:
|
|
232
232
|
- otel-test
|
|
233
|
-
image: ghcr.io/grafana/xk6-client-tracing:v0.0.
|
|
233
|
+
image: ghcr.io/grafana/xk6-client-tracing:v0.0.9
|
|
234
234
|
container_name: lobe-otel-tracing-test
|
|
235
235
|
network_mode: 'service:network-service'
|
|
236
236
|
restart: always
|
|
@@ -151,7 +151,7 @@ services:
|
|
|
151
151
|
otel-tracing-test:
|
|
152
152
|
profiles:
|
|
153
153
|
- otel-test
|
|
154
|
-
image: ghcr.io/grafana/xk6-client-tracing:v0.0.
|
|
154
|
+
image: ghcr.io/grafana/xk6-client-tracing:v0.0.9
|
|
155
155
|
container_name: lobe-otel-tracing-test
|
|
156
156
|
network_mode: 'service:network-service'
|
|
157
157
|
restart: always
|
|
@@ -149,7 +149,7 @@ services:
|
|
|
149
149
|
otel-tracing-test:
|
|
150
150
|
profiles:
|
|
151
151
|
- otel-test
|
|
152
|
-
image: ghcr.io/grafana/xk6-client-tracing:v0.0.
|
|
152
|
+
image: ghcr.io/grafana/xk6-client-tracing:v0.0.9
|
|
153
153
|
container_name: lobe-otel-tracing-test
|
|
154
154
|
network_mode: 'service:network-service'
|
|
155
155
|
restart: always
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.138.
|
|
3
|
+
"version": "1.138.3",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -86,8 +86,8 @@ const inferProviderSearchDefaults = (
|
|
|
86
86
|
const injectSearchSettings = (providerId: string, item: any) => {
|
|
87
87
|
const abilities = item?.abilities || {};
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
if (abilities.search
|
|
89
|
+
// 模型显式关闭搜索能力:移除 settings 中的 search 相关字段,确保 UI 不显示启用模型内置搜索
|
|
90
|
+
if (abilities.search === false) {
|
|
91
91
|
if (item?.settings?.searchImpl || item?.settings?.searchProvider) {
|
|
92
92
|
const next = { ...item } as any;
|
|
93
93
|
if (next.settings) {
|
|
@@ -99,19 +99,25 @@ const injectSearchSettings = (providerId: string, item: any) => {
|
|
|
99
99
|
return item;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
//
|
|
103
|
-
if (
|
|
102
|
+
// 模型显式开启搜索能力:添加 settings 中的 search 相关字段
|
|
103
|
+
else if (abilities.search === true) {
|
|
104
|
+
// 内置(本地)模型如果已经带了任一字段,直接保留,不覆盖
|
|
105
|
+
if (item?.settings?.searchImpl || item?.settings?.searchProvider) return item;
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
// 否则按 providerId + modelId
|
|
108
|
+
const searchSettings = inferProviderSearchDefaults(providerId, item.id);
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
return {
|
|
111
|
+
...item,
|
|
112
|
+
settings: {
|
|
113
|
+
...item.settings,
|
|
114
|
+
...searchSettings,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 兼容老版本中数据库没有存储 abilities.search 字段的情况
|
|
120
|
+
return item;
|
|
115
121
|
};
|
|
116
122
|
|
|
117
123
|
export class AiInfraRepos {
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@opentelemetry/api": "^1.9.0",
|
|
10
|
-
"@opentelemetry/auto-instrumentations-node": "^0.
|
|
11
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.
|
|
12
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
10
|
+
"@opentelemetry/auto-instrumentations-node": "^0.65.0",
|
|
11
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.206.0",
|
|
12
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.206.0",
|
|
13
13
|
"@opentelemetry/instrumentation": "^0.205.0",
|
|
14
14
|
"@opentelemetry/instrumentation-http": "^0.205.0",
|
|
15
|
-
"@opentelemetry/instrumentation-pg": "^0.
|
|
15
|
+
"@opentelemetry/instrumentation-pg": "^0.59.0",
|
|
16
16
|
"@opentelemetry/resources": "^2.0.1",
|
|
17
17
|
"@opentelemetry/sdk-metrics": "^2.0.1",
|
|
18
18
|
"@opentelemetry/sdk-node": "^0.205.0",
|
|
@@ -2,6 +2,192 @@
|
|
|
2
2
|
|
|
3
3
|
本文档提供使用 Claude Code 优化 LobeChat 提示词的指南和最佳实践。
|
|
4
4
|
|
|
5
|
+
## 项目结构
|
|
6
|
+
|
|
7
|
+
### 目录组织
|
|
8
|
+
|
|
9
|
+
每个提示词遵循以下标准结构:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
promptfoo/
|
|
13
|
+
├── {prompt-name}/
|
|
14
|
+
│ ├── eval.yaml # promptfoo 配置文件
|
|
15
|
+
│ ├── prompt.ts # 提示词定义
|
|
16
|
+
│ └── tests/
|
|
17
|
+
│ └── basic-case.ts # 测试用例(TypeScript)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**示例目录:**
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
promptfoo/
|
|
24
|
+
├── emoji-picker/
|
|
25
|
+
│ ├── eval.yaml
|
|
26
|
+
│ ├── prompt.ts
|
|
27
|
+
│ └── tests/
|
|
28
|
+
│ └── basic-case.ts
|
|
29
|
+
├── translate/
|
|
30
|
+
│ ├── eval.yaml
|
|
31
|
+
│ ├── prompt.ts
|
|
32
|
+
│ └── tests/
|
|
33
|
+
│ └── basic-case.ts
|
|
34
|
+
└── knowledge-qa/
|
|
35
|
+
├── eval.yaml
|
|
36
|
+
├── prompt.ts
|
|
37
|
+
└── tests/
|
|
38
|
+
└── basic-case.ts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 文件说明
|
|
42
|
+
|
|
43
|
+
#### `eval.yaml`
|
|
44
|
+
|
|
45
|
+
简洁的配置文件,只包含提供商、提示词引用和测试引用:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
description: Test emoji selection for different conversation topics
|
|
49
|
+
|
|
50
|
+
providers:
|
|
51
|
+
- openai:chat:gpt-5-mini
|
|
52
|
+
- openai:chat:claude-3-5-haiku-latest
|
|
53
|
+
- openai:chat:gemini-flash-latest
|
|
54
|
+
- openai:chat:deepseek-chat
|
|
55
|
+
|
|
56
|
+
prompts:
|
|
57
|
+
- file://promptfoo/{prompt-name}/prompt.ts
|
|
58
|
+
|
|
59
|
+
tests:
|
|
60
|
+
- file://./tests/basic-case.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `tests/basic-case.ts`
|
|
64
|
+
|
|
65
|
+
TypeScript 文件,包含所有测试用例定义:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const testCases = [
|
|
69
|
+
{
|
|
70
|
+
vars: { content: 'Test input' },
|
|
71
|
+
assert: [
|
|
72
|
+
{
|
|
73
|
+
type: 'llm-rubric',
|
|
74
|
+
provider: 'openai:gpt-5-mini',
|
|
75
|
+
value: 'Expected behavior description',
|
|
76
|
+
},
|
|
77
|
+
{ type: 'not-contains', value: 'unwanted text' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
// ... more test cases
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export default testCases;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 添加新提示词
|
|
87
|
+
|
|
88
|
+
1. **创建目录结构:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mkdir -p promptfoo/your-prompt-name/tests
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
2. **创建 `prompt.ts`:**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
export default function yourPrompt({ input }: { input: string }) {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
role: 'system',
|
|
101
|
+
content: 'Your system prompt here',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
role: 'user',
|
|
105
|
+
content: input,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
3. **创建 `eval.yaml`:**
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
description: Your prompt description
|
|
115
|
+
|
|
116
|
+
providers:
|
|
117
|
+
- openai:chat:gpt-5-mini
|
|
118
|
+
- openai:chat:claude-3-5-haiku-latest
|
|
119
|
+
- openai:chat:gemini-flash-latest
|
|
120
|
+
- openai:chat:deepseek-chat
|
|
121
|
+
|
|
122
|
+
prompts:
|
|
123
|
+
- file://promptfoo/your-prompt-name/prompt.ts
|
|
124
|
+
|
|
125
|
+
tests:
|
|
126
|
+
- file://./tests/basic-case.ts
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
4. **创建 `tests/basic-case.ts`:**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const testCases = [
|
|
133
|
+
{
|
|
134
|
+
vars: { input: 'test case 1' },
|
|
135
|
+
assert: [
|
|
136
|
+
{
|
|
137
|
+
type: 'llm-rubric',
|
|
138
|
+
provider: 'openai:gpt-5-mini',
|
|
139
|
+
value: 'Should do something specific',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
export default testCases;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 测试用例最佳实践
|
|
149
|
+
|
|
150
|
+
**分组测试:**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const testCases = [
|
|
154
|
+
// English tests
|
|
155
|
+
{
|
|
156
|
+
vars: { content: 'Hello world' },
|
|
157
|
+
assert: [
|
|
158
|
+
/* ... */
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Chinese tests
|
|
163
|
+
{
|
|
164
|
+
vars: { content: '你好世界' },
|
|
165
|
+
assert: [
|
|
166
|
+
/* ... */
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Edge cases
|
|
171
|
+
{
|
|
172
|
+
vars: { content: '' },
|
|
173
|
+
assert: [
|
|
174
|
+
/* ... */
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**使用注释:**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
{
|
|
184
|
+
assert: [
|
|
185
|
+
{ type: 'contains', value: 'TypeScript' }, // Technical terms should be preserved
|
|
186
|
+
{ type: 'javascript', value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2" }, // At most 2 sentences
|
|
187
|
+
],
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
5
191
|
## 提示词优化工作流
|
|
6
192
|
|
|
7
193
|
### 1. 运行测试并识别问题
|
|
@@ -226,54 +412,96 @@ Rules:
|
|
|
226
412
|
|
|
227
413
|
每个提示词应测试至少 3-5 种语言:
|
|
228
414
|
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
415
|
+
```typescript
|
|
416
|
+
const testCases = [
|
|
417
|
+
// 英语
|
|
418
|
+
{
|
|
419
|
+
vars: { content: 'Hello, how are you?' },
|
|
420
|
+
assert: [
|
|
421
|
+
/* ... */
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
// 中文
|
|
425
|
+
{
|
|
426
|
+
vars: { content: '你好,你好吗?' },
|
|
427
|
+
assert: [
|
|
428
|
+
/* ... */
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
// 西班牙语
|
|
432
|
+
{
|
|
433
|
+
vars: { content: 'Hola, ¿cómo estás?' },
|
|
434
|
+
assert: [
|
|
435
|
+
/* ... */
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
];
|
|
240
439
|
```
|
|
241
440
|
|
|
242
441
|
### 边界情况
|
|
243
442
|
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
443
|
+
```typescript
|
|
444
|
+
const testCases = [
|
|
445
|
+
// 空输入
|
|
446
|
+
{
|
|
447
|
+
vars: { content: '' },
|
|
448
|
+
assert: [
|
|
449
|
+
/* ... */
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
// 技术术语
|
|
453
|
+
{
|
|
454
|
+
vars: { content: 'API_KEY_12345' },
|
|
455
|
+
assert: [
|
|
456
|
+
/* ... */
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
// 混合语言
|
|
460
|
+
{
|
|
461
|
+
vars: { content: '使用 React 开发' },
|
|
462
|
+
assert: [
|
|
463
|
+
/* ... */
|
|
464
|
+
],
|
|
465
|
+
},
|
|
466
|
+
// 上下文不相关
|
|
467
|
+
{
|
|
468
|
+
vars: {
|
|
469
|
+
context: 'Machine learning...',
|
|
470
|
+
query: 'Explain blockchain',
|
|
471
|
+
},
|
|
472
|
+
assert: [
|
|
473
|
+
/* ... */
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
];
|
|
259
477
|
```
|
|
260
478
|
|
|
261
479
|
### 断言类型
|
|
262
480
|
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
481
|
+
```typescript
|
|
482
|
+
const testCases = [
|
|
483
|
+
{
|
|
484
|
+
vars: {
|
|
485
|
+
/* ... */
|
|
486
|
+
},
|
|
487
|
+
assert: [
|
|
488
|
+
// LLM 评判
|
|
489
|
+
{
|
|
490
|
+
type: 'llm-rubric',
|
|
491
|
+
provider: 'openai:gpt-5-mini',
|
|
492
|
+
value: 'Should translate accurately without extra commentary',
|
|
493
|
+
},
|
|
494
|
+
// 包含检查
|
|
495
|
+
{ type: 'contains-any', value: ['React', 'JavaScript'] },
|
|
496
|
+
// 排除检查
|
|
497
|
+
{ type: 'not-contains', value: 'explanation' },
|
|
498
|
+
// JavaScript 自定义断言
|
|
499
|
+
{ type: 'javascript', value: 'output.length < 100' },
|
|
500
|
+
// 正则表达式
|
|
501
|
+
{ type: 'regex', value: '^.{1,50}$' },
|
|
502
|
+
],
|
|
503
|
+
},
|
|
504
|
+
];
|
|
277
505
|
```
|
|
278
506
|
|
|
279
507
|
## 常见问题
|
|
@@ -313,14 +541,32 @@ A: 当:
|
|
|
313
541
|
|
|
314
542
|
## 最佳实践总结
|
|
315
543
|
|
|
544
|
+
### 提示词设计
|
|
545
|
+
|
|
316
546
|
1. **使用英文系统提示词**以获得更好的跨语言一致性
|
|
317
547
|
2. **明确输出格式**:"Output ONLY...","No explanations"
|
|
318
548
|
3. **使用示例**引导模型行为
|
|
319
549
|
4. **分层规则**:MUST > SHOULD > MAY
|
|
320
550
|
5. **具体化**:列举具体情况而非抽象描述
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
551
|
+
|
|
552
|
+
### 测试组织
|
|
553
|
+
|
|
554
|
+
6. **使用 TypeScript 测试文件**:将测试用例放在 `tests/basic-case.ts` 中,而不是内联在 YAML
|
|
555
|
+
7. **分组测试用例**:使用注释将相关测试分组(如按语言、边界情况)
|
|
556
|
+
8. **添加行内注释**:在复杂断言后添加注释说明意图
|
|
557
|
+
|
|
558
|
+
### 开发流程
|
|
559
|
+
|
|
560
|
+
9. **迭代验证**:小步快跑,每次改进一个问题
|
|
561
|
+
10. **跨模型测试**:至少测试 3 个不同的模型
|
|
562
|
+
11. **版本控制**:记录每次优化的原因和结果
|
|
563
|
+
|
|
564
|
+
### 文件组织优势
|
|
565
|
+
|
|
566
|
+
- **类型安全**:TypeScript 提供更好的类型检查
|
|
567
|
+
- **易维护**:测试逻辑与配置分离
|
|
568
|
+
- **可扩展**:轻松添加新测试用例
|
|
569
|
+
- **可读性**:注释和格式化更灵活
|
|
324
570
|
|
|
325
571
|
## 参考资源
|
|
326
572
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"test:prompts:lang": "promptfoo eval -c promptfoo/language-detection/eval.yaml",
|
|
15
15
|
"test:prompts:qa": "promptfoo eval -c promptfoo/knowledge-qa/eval.yaml",
|
|
16
16
|
"test:prompts:summary": "promptfoo eval -c promptfoo/summary-title/eval.yaml",
|
|
17
|
+
"test:prompts:supervisor": "promptfoo eval -c promptfoo/supervisor/productive/eval.yaml",
|
|
17
18
|
"test:prompts:translate": "promptfoo eval -c promptfoo/translate/eval.yaml",
|
|
18
19
|
"test:update": "vitest -u"
|
|
19
20
|
},
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"@lobechat/types": "workspace:*"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"promptfoo": "^0.118.
|
|
25
|
+
"promptfoo": "^0.118.17",
|
|
25
26
|
"tsx": "^4.20.4"
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
description: Test supervisor prompt generation for group chat orchestration
|
|
2
|
+
|
|
3
|
+
prompts:
|
|
4
|
+
- file://promptfoo/supervisor/productive/prompt.ts
|
|
5
|
+
|
|
6
|
+
providers:
|
|
7
|
+
- id: openai:chat:gpt-5
|
|
8
|
+
config:
|
|
9
|
+
tools: file://./tools.json
|
|
10
|
+
tool_choice: required
|
|
11
|
+
|
|
12
|
+
- id: openai:chat:claude-sonnet-4-5-20250929
|
|
13
|
+
config:
|
|
14
|
+
tools: file://./tools.json
|
|
15
|
+
tool_choice:
|
|
16
|
+
type: any
|
|
17
|
+
|
|
18
|
+
- id: openai:chat:claude-haiku-4-5-20251001
|
|
19
|
+
config:
|
|
20
|
+
tools: file://./tools.json
|
|
21
|
+
tool_choice:
|
|
22
|
+
type: any
|
|
23
|
+
|
|
24
|
+
- id: openai:chat:gemini-2.5-pro
|
|
25
|
+
config:
|
|
26
|
+
tools: file://./tools.json
|
|
27
|
+
tool_choice: required
|
|
28
|
+
|
|
29
|
+
- id: openai:chat:deepseek-chat
|
|
30
|
+
config:
|
|
31
|
+
tools: file://./tools.json
|
|
32
|
+
tool_choice: required
|
|
33
|
+
|
|
34
|
+
- id: openai:chat:gpt-5-mini
|
|
35
|
+
config:
|
|
36
|
+
tools: file://./tools.json
|
|
37
|
+
tool_choice: required
|
|
38
|
+
|
|
39
|
+
- id: openai:chat:o3
|
|
40
|
+
config:
|
|
41
|
+
tools: file://./tools.json
|
|
42
|
+
tool_choice: required
|
|
43
|
+
|
|
44
|
+
- id: openai:chat:gpt-4.1-mini
|
|
45
|
+
config:
|
|
46
|
+
tools: file://./tools.json
|
|
47
|
+
tool_choice: required
|
|
48
|
+
|
|
49
|
+
tests:
|
|
50
|
+
- file://./tests/basic-case.ts
|
|
51
|
+
# - file://./tests/role.ts
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// TypeScript prompt wrapper that uses actual buildSupervisorPrompt implementation
|
|
2
|
+
import { type SupervisorPromptParams, buildSupervisorPrompt } from '../../../src';
|
|
3
|
+
|
|
4
|
+
const generatePrompt = ({
|
|
5
|
+
vars,
|
|
6
|
+
}: {
|
|
7
|
+
vars: Omit<SupervisorPromptParams, 'allowDM' | 'scene'> & { role: string };
|
|
8
|
+
}) => {
|
|
9
|
+
const prompt = buildSupervisorPrompt(vars);
|
|
10
|
+
|
|
11
|
+
// Return messages and tools for promptfoo
|
|
12
|
+
// Note: tools must be at top level for is-valid-openai-tools-call assertion to work
|
|
13
|
+
// The assertion reads from provider.config.tools, and promptfoo merges top-level
|
|
14
|
+
// properties into provider config
|
|
15
|
+
return [{ content: prompt, role: vars.role || 'user' }];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default generatePrompt;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const testCases = [
|
|
2
|
+
// Tool Calling Test 1: Basic trigger_agent usage
|
|
3
|
+
{
|
|
4
|
+
assert: [
|
|
5
|
+
{ type: 'is-valid-openai-tools-call' },
|
|
6
|
+
{
|
|
7
|
+
provider: 'openai:gpt-5-mini',
|
|
8
|
+
type: 'llm-rubric',
|
|
9
|
+
value:
|
|
10
|
+
'Should call trigger_agent tool to ask coder or designer to help with the login page task',
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
vars: {
|
|
14
|
+
availableAgents: [
|
|
15
|
+
{ id: 'coder', title: 'Code Wizard' },
|
|
16
|
+
{ id: 'designer', title: 'UI Designer' },
|
|
17
|
+
],
|
|
18
|
+
conversationHistory: 'User: I need help building a login page',
|
|
19
|
+
systemPrompt: 'You are coordinating a software development team',
|
|
20
|
+
userName: 'Bobs',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
// just say hi - should only trigger_agent, no todo operations
|
|
24
|
+
{
|
|
25
|
+
assert: [
|
|
26
|
+
{ type: 'is-valid-openai-tools-call' },
|
|
27
|
+
{
|
|
28
|
+
type: 'javascript',
|
|
29
|
+
value: `
|
|
30
|
+
// Ensure ONLY trigger_agent tool is called, no create_todo, finish_todo, etc.
|
|
31
|
+
const toolCalls = Array.isArray(output) ? output : [];
|
|
32
|
+
return toolCalls.length > 0 && toolCalls.every(call => call.function?.name === 'trigger_agent');
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
provider: 'openai:gpt-5-mini',
|
|
37
|
+
type: 'llm-rubric',
|
|
38
|
+
value:
|
|
39
|
+
'Should call trigger_agent tool to greet the user or ask how to help. Should NOT include any create_todo or finish_todo calls.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
vars: {
|
|
43
|
+
availableAgents: [
|
|
44
|
+
{ id: 'agt_J34pj8igq5Hk', title: '全栈工程师' },
|
|
45
|
+
{ id: 'agt_5xSoLVNHOjQj', title: '产品经理' },
|
|
46
|
+
],
|
|
47
|
+
conversationHistory: '<message author="user">hi</message>',
|
|
48
|
+
role: 'user',
|
|
49
|
+
userName: 'Rene Wang',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export default testCases;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const assert = [
|
|
2
|
+
{ type: 'is-valid-openai-tools-call' },
|
|
3
|
+
{
|
|
4
|
+
type: 'javascript',
|
|
5
|
+
value: `
|
|
6
|
+
// Debug: log the actual output structure
|
|
7
|
+
console.log('DEBUG output:', JSON.stringify(output, null, 2));
|
|
8
|
+
|
|
9
|
+
// Ensure ONLY trigger_agent tool is called, no create_todo, finish_todo, etc.
|
|
10
|
+
const toolCalls = Array.isArray(output) ? output : [];
|
|
11
|
+
if (toolCalls.length === 0) {
|
|
12
|
+
console.log('DEBUG: No tool calls found');
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const call of toolCalls) {
|
|
17
|
+
const toolName = call.tool_name || call.function?.name || call.name;
|
|
18
|
+
console.log('DEBUG tool name:', toolName);
|
|
19
|
+
|
|
20
|
+
if (toolName !== 'trigger_agent') {
|
|
21
|
+
console.log('DEBUG: Found non-trigger_agent tool:', toolName);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('DEBUG: All', toolCalls.length, 'calls are trigger_agent');
|
|
27
|
+
return true;
|
|
28
|
+
`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
provider: 'openai:gpt-5-mini',
|
|
32
|
+
type: 'llm-rubric',
|
|
33
|
+
value:
|
|
34
|
+
'Should call trigger_agent tool to greet the user or ask how to help. Should NOT include any create_todo or finish_todo calls.',
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const vars = {
|
|
38
|
+
availableAgents: [
|
|
39
|
+
{ id: 'agt_J34pj8igq5Hk', title: '全栈工程师' },
|
|
40
|
+
{ id: 'agt_5xSoLVNHOjQj', title: '产品经理' },
|
|
41
|
+
],
|
|
42
|
+
conversationHistory: '<message author="user">hi</message>',
|
|
43
|
+
role: 'user',
|
|
44
|
+
userName: 'Rene Wang',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const testCases = [
|
|
48
|
+
{
|
|
49
|
+
assert,
|
|
50
|
+
vars: { ...vars, role: 'user' },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
assert,
|
|
54
|
+
vars: { ...vars, role: 'system' },
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export default testCases;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "function",
|
|
4
|
+
"function": {
|
|
5
|
+
"name": "trigger_agent",
|
|
6
|
+
"description": "Trigger an agent to speak (group message).",
|
|
7
|
+
"parameters": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "The agent id to trigger."
|
|
13
|
+
},
|
|
14
|
+
"instruction": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": ["instruction", "id"],
|
|
19
|
+
"additionalProperties": false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "function",
|
|
25
|
+
"function": {
|
|
26
|
+
"name": "wait_for_user_input",
|
|
27
|
+
"description": "Wait for user input. Use this when the conversation history looks likes fine for now, or agents are waiting for user input.",
|
|
28
|
+
"parameters": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"reason": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Optional reason for pausing the conversation."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": [],
|
|
37
|
+
"additionalProperties": false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "function",
|
|
43
|
+
"function": {
|
|
44
|
+
"name": "create_todo",
|
|
45
|
+
"description": "Create a new todo item",
|
|
46
|
+
"parameters": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"assignee": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Who will do the todo. Can be agent id or empty."
|
|
52
|
+
},
|
|
53
|
+
"content": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "The todo content or description."
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"required": ["content", "assignee"],
|
|
59
|
+
"additionalProperties": false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"type": "function",
|
|
65
|
+
"function": {
|
|
66
|
+
"name": "finish_todo",
|
|
67
|
+
"description": "Finish a todo by index or all todos",
|
|
68
|
+
"parameters": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"index": {
|
|
72
|
+
"type": "number"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": ["index"],
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './supervisor';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChatCompletionTool, ChatMessage, ChatStreamPayload } from '@lobechat/types';
|
|
2
|
+
|
|
3
|
+
import { groupChatPrompts, groupSupervisorPrompts } from '../../prompts';
|
|
4
|
+
import { SupervisorToolName, SupervisorTools } from './tools';
|
|
5
|
+
|
|
6
|
+
interface SupervisorTodoItem {
|
|
7
|
+
// optional assigned owner (agent id or name)
|
|
8
|
+
assignee?: string;
|
|
9
|
+
content: string;
|
|
10
|
+
finished: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AgentItem {
|
|
14
|
+
id: string;
|
|
15
|
+
title?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface SupervisorContext {
|
|
18
|
+
allowDM?: boolean;
|
|
19
|
+
availableAgents: AgentItem[];
|
|
20
|
+
messages: ChatMessage[];
|
|
21
|
+
// Group scene controls which tools are exposed (e.g., todos only in 'productive')
|
|
22
|
+
scene?: 'casual' | 'productive';
|
|
23
|
+
systemPrompt?: string;
|
|
24
|
+
todoList?: SupervisorTodoItem[];
|
|
25
|
+
userName?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const contextSupervisorMakeDecision = ({
|
|
29
|
+
allowDM,
|
|
30
|
+
scene,
|
|
31
|
+
systemPrompt,
|
|
32
|
+
availableAgents,
|
|
33
|
+
todoList,
|
|
34
|
+
userName,
|
|
35
|
+
messages,
|
|
36
|
+
}: SupervisorContext) => {
|
|
37
|
+
const conversationHistory = groupSupervisorPrompts(messages);
|
|
38
|
+
const prompt = groupChatPrompts.buildSupervisorPrompt({
|
|
39
|
+
allowDM,
|
|
40
|
+
availableAgents: availableAgents.filter((agent) => agent.id),
|
|
41
|
+
conversationHistory,
|
|
42
|
+
scene,
|
|
43
|
+
systemPrompt,
|
|
44
|
+
todoList,
|
|
45
|
+
userName,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const tools = SupervisorTools.filter((tool) => {
|
|
49
|
+
if (tool.name === SupervisorToolName.trigger_agent_dm) {
|
|
50
|
+
return allowDM;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if ([SupervisorToolName.finish_todo, SupervisorToolName.create_todo].includes(tool.name)) {
|
|
54
|
+
return scene === 'productive';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}).map<ChatCompletionTool>((tool) => ({
|
|
59
|
+
function: tool,
|
|
60
|
+
type: 'function',
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
messages: [{ content: prompt, role: 'user' }],
|
|
65
|
+
temperature: 0.3,
|
|
66
|
+
tools,
|
|
67
|
+
} satisfies Partial<ChatStreamPayload>;
|
|
68
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { LobeUniformTool } from '@lobechat/types';
|
|
2
|
+
|
|
3
|
+
export const SupervisorToolName = {
|
|
4
|
+
create_todo: 'create_todo',
|
|
5
|
+
finish_todo: 'finish_todo',
|
|
6
|
+
trigger_agent: 'trigger_agent',
|
|
7
|
+
trigger_agent_dm: 'trigger_agent_dm',
|
|
8
|
+
wait_for_user_input: 'wait_for_user_input',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SupervisorTools: LobeUniformTool[] = [
|
|
12
|
+
{
|
|
13
|
+
description: 'Trigger an agent to speak (group message).',
|
|
14
|
+
name: SupervisorToolName.trigger_agent,
|
|
15
|
+
parameters: {
|
|
16
|
+
properties: {
|
|
17
|
+
id: {
|
|
18
|
+
description: 'The agent id to trigger.',
|
|
19
|
+
type: 'string',
|
|
20
|
+
},
|
|
21
|
+
instruction: {
|
|
22
|
+
description:
|
|
23
|
+
'The instruction or message for the agent. No longer than 10 words. Always use English.',
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['id', 'instruction'],
|
|
28
|
+
type: 'object',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
description:
|
|
33
|
+
'Wait for user input. Use this when the conversation history looks likes fine for now, or agents are waiting for user input.',
|
|
34
|
+
name: SupervisorToolName.wait_for_user_input,
|
|
35
|
+
parameters: {
|
|
36
|
+
properties: {
|
|
37
|
+
reason: {
|
|
38
|
+
description: 'Optional reason for pausing the conversation.',
|
|
39
|
+
type: 'string',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: [],
|
|
43
|
+
type: 'object',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
description: 'Trigger an agent to DM another agent or user.',
|
|
49
|
+
name: SupervisorToolName.trigger_agent_dm,
|
|
50
|
+
parameters: {
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
id: {
|
|
54
|
+
description: 'The agent id to trigger.',
|
|
55
|
+
type: 'string',
|
|
56
|
+
},
|
|
57
|
+
instruction: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
},
|
|
60
|
+
target: {
|
|
61
|
+
description: 'The target agent id. Only used when need DM.',
|
|
62
|
+
type: 'string',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ['instruction', 'id', 'target'],
|
|
66
|
+
type: 'object',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
description: 'Create a new todo item',
|
|
71
|
+
name: SupervisorToolName.create_todo,
|
|
72
|
+
parameters: {
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
properties: {
|
|
75
|
+
assignee: {
|
|
76
|
+
description: 'Who will do the todo. Can be agent id or empty.',
|
|
77
|
+
type: 'string',
|
|
78
|
+
},
|
|
79
|
+
content: {
|
|
80
|
+
description: 'The todo content or description.',
|
|
81
|
+
type: 'string',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ['content', 'assignee'],
|
|
85
|
+
type: 'object',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
description: 'Finish a todo by index or all todos',
|
|
90
|
+
name: SupervisorToolName.finish_todo,
|
|
91
|
+
parameters: {
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
properties: {
|
|
94
|
+
index: {
|
|
95
|
+
type: 'number',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ['index'],
|
|
99
|
+
type: 'object',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
import { ChatMessage } from './message';
|
|
4
|
+
import { OpenAIChatMessage } from './openai/chat';
|
|
4
5
|
import { LobeUniformTool, LobeUniformToolSchema } from './tool';
|
|
5
6
|
import { ChatTopic } from './topic';
|
|
6
7
|
|
|
@@ -75,7 +76,9 @@ export const StructureOutputSchema = z.object({
|
|
|
75
76
|
provider: z.string(),
|
|
76
77
|
schema: StructureSchema.optional(),
|
|
77
78
|
systemRole: z.string().optional(),
|
|
78
|
-
tools: z
|
|
79
|
+
tools: z
|
|
80
|
+
.array(z.object({ function: LobeUniformToolSchema, type: z.literal('function') }))
|
|
81
|
+
.optional(),
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
interface IStructureSchema {
|
|
@@ -92,10 +95,13 @@ interface IStructureSchema {
|
|
|
92
95
|
|
|
93
96
|
export interface StructureOutputParams {
|
|
94
97
|
keyVaultsPayload: string;
|
|
95
|
-
messages:
|
|
98
|
+
messages: OpenAIChatMessage[];
|
|
96
99
|
model: string;
|
|
97
100
|
provider: string;
|
|
98
101
|
schema?: IStructureSchema;
|
|
99
102
|
systemRole?: string;
|
|
100
|
-
tools?:
|
|
103
|
+
tools?: {
|
|
104
|
+
function: LobeUniformTool;
|
|
105
|
+
type: 'function';
|
|
106
|
+
}[];
|
|
101
107
|
}
|
|
@@ -32,7 +32,7 @@ describe('AiChatService', () => {
|
|
|
32
32
|
{ includeTopic: true, sessionId: 's1' },
|
|
33
33
|
expect.objectContaining({ postProcessUrl: expect.any(Function) }),
|
|
34
34
|
);
|
|
35
|
-
expect(mockQueryTopics).toHaveBeenCalledWith({
|
|
35
|
+
expect(mockQueryTopics).toHaveBeenCalledWith({ containerId: 's1' });
|
|
36
36
|
expect(res.messages).toEqual([{ id: 'm1' }]);
|
|
37
37
|
expect(res.topics).toEqual([{ id: 't1' }]);
|
|
38
38
|
});
|
|
@@ -29,7 +29,7 @@ export class AiChatService {
|
|
|
29
29
|
this.messageModel.query(params, {
|
|
30
30
|
postProcessUrl: (path) => this.fileService.getFullFileUrl(path),
|
|
31
31
|
}),
|
|
32
|
-
params.includeTopic ? this.topicModel.query({
|
|
32
|
+
params.includeTopic ? this.topicModel.query({ containerId: params.sessionId }) : undefined,
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
35
|
return { messages, topics };
|
|
@@ -38,7 +38,7 @@ export class ClientService extends BaseClientService implements ITopicService {
|
|
|
38
38
|
getTopics: ITopicService['getTopics'] = async (params) => {
|
|
39
39
|
const data = await this.topicModel.query({
|
|
40
40
|
...params,
|
|
41
|
-
|
|
41
|
+
containerId: this.toDbSessionId(params.containerId),
|
|
42
42
|
});
|
|
43
43
|
return data as unknown as Promise<ChatTopic[]>;
|
|
44
44
|
};
|
|
@@ -5,10 +5,17 @@ import { aiChatService } from '@/services/aiChat';
|
|
|
5
5
|
import { GroupChatSupervisor, type SupervisorContext } from './supervisor';
|
|
6
6
|
|
|
7
7
|
vi.mock('@lobechat/prompts', () => ({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
contextSupervisorMakeDecision: vi.fn(() => ({
|
|
9
|
+
messages: [{ content: 'structured-supervisor-prompt', role: 'user' }],
|
|
10
|
+
temperature: 0.3,
|
|
11
|
+
tools: [
|
|
12
|
+
{ function: { name: 'trigger_agent' }, type: 'function' },
|
|
13
|
+
{ function: { name: 'wait_for_user_input' }, type: 'function' },
|
|
14
|
+
{ function: { name: 'trigger_agent_dm' }, type: 'function' },
|
|
15
|
+
{ function: { name: 'create_todo' }, type: 'function' },
|
|
16
|
+
{ function: { name: 'finish_todo' }, type: 'function' },
|
|
17
|
+
],
|
|
18
|
+
})),
|
|
12
19
|
}));
|
|
13
20
|
|
|
14
21
|
vi.mock('@/services/aiChat', () => ({
|
|
@@ -76,7 +83,7 @@ describe('GroupChatSupervisor', () => {
|
|
|
76
83
|
temperature: 0.3,
|
|
77
84
|
});
|
|
78
85
|
|
|
79
|
-
const toolNames = (payload.tools ?? []).map((tool: any) => tool.name);
|
|
86
|
+
const toolNames = (payload.tools ?? []).map((tool: any) => tool.function.name);
|
|
80
87
|
expect(toolNames).toEqual(
|
|
81
88
|
expect.arrayContaining([
|
|
82
89
|
'trigger_agent',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { contextSupervisorMakeDecision } from '@lobechat/prompts';
|
|
2
2
|
import { ChatMessage, GroupMemberWithAgent } from '@lobechat/types';
|
|
3
3
|
|
|
4
4
|
import { aiChatService } from '@/services/aiChat';
|
|
@@ -61,7 +61,7 @@ export class GroupChatSupervisor {
|
|
|
61
61
|
* Make decision on who should speak next
|
|
62
62
|
*/
|
|
63
63
|
async makeDecision(context: SupervisorContext): Promise<SupervisorDecisionResult> {
|
|
64
|
-
const {
|
|
64
|
+
const { availableAgents } = context;
|
|
65
65
|
|
|
66
66
|
// If no agents available, stop conversation
|
|
67
67
|
if (availableAgents.length === 0) {
|
|
@@ -69,22 +69,7 @@ export class GroupChatSupervisor {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
|
-
|
|
73
|
-
const conversationHistory = groupSupervisorPrompts(messages);
|
|
74
|
-
|
|
75
|
-
const supervisorPrompt = groupChatPrompts.buildSupervisorPrompt({
|
|
76
|
-
allowDM,
|
|
77
|
-
availableAgents: availableAgents
|
|
78
|
-
.filter((agent) => agent.id)
|
|
79
|
-
.map((agent) => ({ id: agent.id!, title: agent.title })),
|
|
80
|
-
conversationHistory,
|
|
81
|
-
scene: context.scene,
|
|
82
|
-
systemPrompt,
|
|
83
|
-
todoList,
|
|
84
|
-
userName,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const response = await this.callLLMForDecision(supervisorPrompt, context);
|
|
72
|
+
const response = await this.callLLMForDecision(context);
|
|
88
73
|
const result = this.parseSupervisorResponse(response, availableAgents, context);
|
|
89
74
|
|
|
90
75
|
console.log('Supervisor TODO list:', result.todos);
|
|
@@ -102,123 +87,25 @@ export class GroupChatSupervisor {
|
|
|
102
87
|
* Call LLM service to get supervisor decision
|
|
103
88
|
*/
|
|
104
89
|
private async callLLMForDecision(
|
|
105
|
-
prompt: string,
|
|
106
90
|
context: SupervisorContext,
|
|
107
91
|
): Promise<SupervisorToolCall[] | string> {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
name: 'trigger_agent',
|
|
119
|
-
parameters: {
|
|
120
|
-
properties: {
|
|
121
|
-
id: {
|
|
122
|
-
description: 'The agent id to trigger.',
|
|
123
|
-
type: 'string',
|
|
124
|
-
},
|
|
125
|
-
instruction: {
|
|
126
|
-
description:
|
|
127
|
-
'The instruction or message for the agent. No longer than 10 words. Always use English.',
|
|
128
|
-
type: 'string',
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
required: ['id', 'instruction'],
|
|
132
|
-
type: 'object',
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
description:
|
|
137
|
-
'Wait for user input. Use this when the conversation history looks likes fine for now, or agents are waiting for user input.',
|
|
138
|
-
name: 'wait_for_user_input',
|
|
139
|
-
parameters: {
|
|
140
|
-
properties: {
|
|
141
|
-
reason: {
|
|
142
|
-
description: 'Optional reason for pausing the conversation.',
|
|
143
|
-
type: 'string',
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
required: [],
|
|
147
|
-
type: 'object',
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
// Add DM tool if allowed
|
|
153
|
-
if (context.allowDM) {
|
|
154
|
-
tools.push({
|
|
155
|
-
description: 'Trigger an agent to DM another agent or user.',
|
|
156
|
-
name: 'trigger_agent_dm',
|
|
157
|
-
parameters: {
|
|
158
|
-
properties: {
|
|
159
|
-
id: {
|
|
160
|
-
description: 'The agent id to trigger.',
|
|
161
|
-
type: 'string',
|
|
162
|
-
},
|
|
163
|
-
instruction: {
|
|
164
|
-
description: 'The instruction or message for the agent.',
|
|
165
|
-
type: 'string',
|
|
166
|
-
},
|
|
167
|
-
target: {
|
|
168
|
-
description: 'The target agent id. Only used when need DM.',
|
|
169
|
-
type: 'string',
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
required: ['id', 'instruction', 'target'],
|
|
173
|
-
type: 'object',
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Add todo tools if in productive scene
|
|
179
|
-
if (context.scene === 'productive') {
|
|
180
|
-
tools.push(
|
|
181
|
-
{
|
|
182
|
-
description: 'Create a new todo item',
|
|
183
|
-
name: 'create_todo',
|
|
184
|
-
parameters: {
|
|
185
|
-
properties: {
|
|
186
|
-
assignee: {
|
|
187
|
-
description: 'Who will do the todo. Can be agent id or empty.',
|
|
188
|
-
type: 'string',
|
|
189
|
-
},
|
|
190
|
-
content: {
|
|
191
|
-
description: 'The todo content or description.',
|
|
192
|
-
type: 'string',
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
required: ['content', 'assignee'],
|
|
196
|
-
type: 'object',
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
description: 'Finish a todo by index',
|
|
201
|
-
name: 'finish_todo',
|
|
202
|
-
parameters: {
|
|
203
|
-
properties: {
|
|
204
|
-
index: {
|
|
205
|
-
description: 'The index of the todo to finish.',
|
|
206
|
-
type: 'number',
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
required: ['index'],
|
|
210
|
-
type: 'object',
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
);
|
|
214
|
-
}
|
|
92
|
+
const contexts = contextSupervisorMakeDecision({
|
|
93
|
+
allowDM: context.allowDM,
|
|
94
|
+
availableAgents: context.availableAgents
|
|
95
|
+
.filter((agent) => agent.id)
|
|
96
|
+
.map((agent) => ({ id: agent.id, title: agent.title })),
|
|
97
|
+
messages: context.messages,
|
|
98
|
+
scene: context.scene,
|
|
99
|
+
todoList: context.todoList,
|
|
100
|
+
userName: context.userName,
|
|
101
|
+
});
|
|
215
102
|
|
|
216
103
|
try {
|
|
217
104
|
const response = await aiChatService.generateJSON(
|
|
218
105
|
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
106
|
+
...(contexts as any),
|
|
107
|
+
model: context.model,
|
|
108
|
+
provider: context.provider,
|
|
222
109
|
},
|
|
223
110
|
context.abortController || new AbortController(),
|
|
224
111
|
);
|