@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docker-compose/local/docker-compose.yml +1 -1
  4. package/docker-compose/local/grafana/docker-compose.yml +1 -1
  5. package/docker-compose/production/grafana/docker-compose.yml +1 -1
  6. package/package.json +1 -1
  7. package/packages/database/src/models/topic.ts +0 -1
  8. package/packages/database/src/repositories/aiInfra/index.ts +19 -13
  9. package/packages/obervability-otel/package.json +4 -4
  10. package/packages/prompts/CLAUDE.md +289 -43
  11. package/packages/prompts/package.json +2 -1
  12. package/packages/prompts/promptfoo/supervisor/productive/eval.yaml +51 -0
  13. package/packages/prompts/promptfoo/supervisor/productive/prompt.ts +18 -0
  14. package/packages/prompts/promptfoo/supervisor/productive/tests/basic-case.ts +54 -0
  15. package/packages/prompts/promptfoo/supervisor/productive/tests/role.ts +58 -0
  16. package/packages/prompts/promptfoo/supervisor/productive/tools.json +80 -0
  17. package/packages/prompts/src/contexts/index.ts +1 -0
  18. package/packages/prompts/src/contexts/supervisor/index.ts +2 -0
  19. package/packages/prompts/src/contexts/supervisor/makeDecision.ts +68 -0
  20. package/packages/prompts/src/contexts/supervisor/tools.ts +102 -0
  21. package/packages/prompts/src/index.ts +1 -0
  22. package/packages/types/src/aiChat.ts +9 -3
  23. package/src/server/routers/lambda/aiChat.ts +1 -1
  24. package/src/server/services/aiChat/index.test.ts +1 -1
  25. package/src/server/services/aiChat/index.ts +1 -1
  26. package/src/services/topic/client.ts +1 -1
  27. package/src/store/chat/slices/message/supervisor.test.ts +12 -5
  28. 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix topic fetch not correct in custom agent."
6
+ ]
7
+ },
8
+ "date": "2025-10-18",
9
+ "version": "1.138.3"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
@@ -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.7
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.7
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.7
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.2",
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",
@@ -20,7 +20,6 @@ interface QueryTopicParams {
20
20
  containerId?: string | null; // sessionId or groupId
21
21
  current?: number;
22
22
  pageSize?: number;
23
- sessionId?: string | null;
24
23
  }
25
24
 
26
25
  export class TopicModel {
@@ -86,8 +86,8 @@ const inferProviderSearchDefaults = (
86
86
  const injectSearchSettings = (providerId: string, item: any) => {
87
87
  const abilities = item?.abilities || {};
88
88
 
89
- // 模型未开启搜索能力:移除 settings 中的 search 相关字段,确保 UI 不显示启用模型内置搜索
90
- if (abilities.search !== true) {
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 (item?.settings?.searchImpl || item?.settings?.searchProvider) return item;
102
+ // 模型显式开启搜索能力:添加 settings 中的 search 相关字段
103
+ else if (abilities.search === true) {
104
+ // 内置(本地)模型如果已经带了任一字段,直接保留,不覆盖
105
+ if (item?.settings?.searchImpl || item?.settings?.searchProvider) return item;
104
106
 
105
- // 否则按 providerId + modelId
106
- const searchSettings = inferProviderSearchDefaults(providerId, item.id);
107
+ // 否则按 providerId + modelId
108
+ const searchSettings = inferProviderSearchDefaults(providerId, item.id);
107
109
 
108
- return {
109
- ...item,
110
- settings: {
111
- ...item.settings,
112
- ...searchSettings,
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.64.1",
11
- "@opentelemetry/exporter-metrics-otlp-http": "^0.205.0",
12
- "@opentelemetry/exporter-trace-otlp-http": "^0.205.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.56.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
- ```yaml
230
- tests:
231
- # 英语
232
- - vars:
233
- content: 'Hello, how are you?'
234
- # 中文
235
- - vars:
236
- content: '你好,你好吗?'
237
- # 西班牙语
238
- - vars:
239
- content: 'Hola, ¿cómo estás?'
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
- ```yaml
245
- tests:
246
- # 空输入
247
- - vars:
248
- content: ''
249
- # 技术术语
250
- - vars:
251
- content: 'API_KEY_12345'
252
- # 混合语言
253
- - vars:
254
- content: '使用 React 开发'
255
- # 上下文不相关
256
- - vars:
257
- context: 'Machine learning...'
258
- query: 'Explain blockchain'
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
- ```yaml
264
- assert:
265
- # LLM 评判
266
- - type: llm-rubric
267
- provider: openai:gpt-5-mini
268
- value: 'Should translate accurately without extra commentary'
269
-
270
- # 包含检查
271
- - type: contains-any
272
- value: ['React', 'JavaScript']
273
-
274
- # 排除检查
275
- - type: not-contains
276
- value: 'explanation'
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
- 6. **迭代验证**:小步快跑,每次改进一个问题
322
- 7. **跨模型测试**:至少测试 3 个不同的模型
323
- 8. **版本控制**:记录每次优化的原因和结果
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.11",
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,2 @@
1
+ export * from './makeDecision';
2
+ export * from './tools';
@@ -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,2 +1,3 @@
1
1
  export * from './chains';
2
+ export * from './contexts';
2
3
  export * from './prompts';
@@ -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.array(LobeUniformToolSchema).optional(),
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: ChatMessage[];
98
+ messages: OpenAIChatMessage[];
96
99
  model: string;
97
100
  provider: string;
98
101
  schema?: IStructureSchema;
99
102
  systemRole?: string;
100
- tools?: LobeUniformTool[];
103
+ tools?: {
104
+ function: LobeUniformTool;
105
+ type: 'function';
106
+ }[];
101
107
  }
@@ -61,7 +61,7 @@ export const aiChatRouter = router({
61
61
  model: input.model,
62
62
  schema: input.schema,
63
63
  systemRole: input.systemRole,
64
- tools: input.tools,
64
+ tools: input.tools?.map((item) => item.function),
65
65
  });
66
66
 
67
67
  log('generateObject completed, result: %O', result);
@@ -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({ sessionId: 's1' });
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({ sessionId: params.sessionId }) : undefined,
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
- sessionId: this.toDbSessionId(params.containerId),
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
- groupChatPrompts: {
9
- buildSupervisorPrompt: vi.fn(() => 'structured-supervisor-prompt'),
10
- },
11
- groupSupervisorPrompts: vi.fn(() => 'conversation-history'),
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 { groupChatPrompts, groupSupervisorPrompts } from '@lobechat/prompts';
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 { messages, availableAgents, userName, systemPrompt, allowDM, todoList } = context;
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
- // Create supervisor prompt with conversation context
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 supervisorConfig = {
109
- model: context.model,
110
- provider: context.provider,
111
- temperature: 0.3,
112
- };
113
-
114
- // Build tools array
115
- const tools: any[] = [
116
- {
117
- description: 'Trigger an agent to speak (group message).',
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
- messages: [{ content: prompt, role: 'user' }] as any,
220
- tools,
221
- ...supervisorConfig,
106
+ ...(contexts as any),
107
+ model: context.model,
108
+ provider: context.provider,
222
109
  },
223
110
  context.abortController || new AbortController(),
224
111
  );