@lobehub/chat 1.138.2 → 1.138.4

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 (41) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -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.test.ts +656 -0
  9. package/packages/database/src/repositories/aiInfra/index.ts +19 -13
  10. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +585 -0
  11. package/packages/model-runtime/src/core/contextBuilders/google.ts +201 -0
  12. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +191 -179
  13. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +305 -47
  14. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +93 -84
  15. package/packages/model-runtime/src/providers/anthropic/generateObject.ts +3 -3
  16. package/packages/model-runtime/src/providers/google/generateObject.test.ts +588 -83
  17. package/packages/model-runtime/src/providers/google/generateObject.ts +104 -6
  18. package/packages/model-runtime/src/providers/google/index.test.ts +0 -395
  19. package/packages/model-runtime/src/providers/google/index.ts +28 -194
  20. package/packages/model-runtime/src/providers/openai/index.test.ts +18 -17
  21. package/packages/model-runtime/src/types/structureOutput.ts +3 -4
  22. package/packages/obervability-otel/package.json +4 -4
  23. package/packages/prompts/CLAUDE.md +289 -43
  24. package/packages/prompts/package.json +2 -1
  25. package/packages/prompts/promptfoo/supervisor/productive/eval.yaml +51 -0
  26. package/packages/prompts/promptfoo/supervisor/productive/prompt.ts +18 -0
  27. package/packages/prompts/promptfoo/supervisor/productive/tests/basic-case.ts +54 -0
  28. package/packages/prompts/promptfoo/supervisor/productive/tests/role.ts +58 -0
  29. package/packages/prompts/promptfoo/supervisor/productive/tools.json +80 -0
  30. package/packages/prompts/src/contexts/index.ts +1 -0
  31. package/packages/prompts/src/contexts/supervisor/index.ts +2 -0
  32. package/packages/prompts/src/contexts/supervisor/makeDecision.ts +68 -0
  33. package/packages/prompts/src/contexts/supervisor/tools.ts +102 -0
  34. package/packages/prompts/src/index.ts +1 -0
  35. package/packages/types/src/aiChat.ts +9 -4
  36. package/src/server/routers/lambda/aiChat.ts +0 -1
  37. package/src/server/services/aiChat/index.test.ts +1 -1
  38. package/src/server/services/aiChat/index.ts +1 -1
  39. package/src/services/topic/client.ts +1 -1
  40. package/src/store/chat/slices/message/supervisor.test.ts +12 -5
  41. package/src/store/chat/slices/message/supervisor.ts +16 -129
@@ -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
 
@@ -74,8 +75,9 @@ export const StructureOutputSchema = z.object({
74
75
  model: z.string(),
75
76
  provider: z.string(),
76
77
  schema: StructureSchema.optional(),
77
- systemRole: z.string().optional(),
78
- tools: z.array(LobeUniformToolSchema).optional(),
78
+ tools: z
79
+ .array(z.object({ function: LobeUniformToolSchema, type: z.literal('function') }))
80
+ .optional(),
79
81
  });
80
82
 
81
83
  interface IStructureSchema {
@@ -92,10 +94,13 @@ interface IStructureSchema {
92
94
 
93
95
  export interface StructureOutputParams {
94
96
  keyVaultsPayload: string;
95
- messages: ChatMessage[];
97
+ messages: OpenAIChatMessage[];
96
98
  model: string;
97
99
  provider: string;
98
100
  schema?: IStructureSchema;
99
101
  systemRole?: string;
100
- tools?: LobeUniformTool[];
102
+ tools?: {
103
+ function: LobeUniformTool;
104
+ type: 'function';
105
+ }[];
101
106
  }
@@ -60,7 +60,6 @@ export const aiChatRouter = router({
60
60
  messages: input.messages,
61
61
  model: input.model,
62
62
  schema: input.schema,
63
- systemRole: input.systemRole,
64
63
  tools: input.tools,
65
64
  });
66
65
 
@@ -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
  );