@lobehub/chat 1.49.16 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +12 -0
  3. package/docs/usage/agents/model.mdx +16 -0
  4. package/docs/usage/agents/model.zh-CN.mdx +16 -0
  5. package/locales/ar/discover.json +4 -0
  6. package/locales/ar/models.json +3 -0
  7. package/locales/ar/setting.json +12 -0
  8. package/locales/bg-BG/discover.json +4 -0
  9. package/locales/bg-BG/models.json +3 -0
  10. package/locales/bg-BG/setting.json +12 -0
  11. package/locales/de-DE/discover.json +4 -0
  12. package/locales/de-DE/models.json +3 -0
  13. package/locales/de-DE/setting.json +12 -0
  14. package/locales/en-US/discover.json +4 -0
  15. package/locales/en-US/models.json +3 -0
  16. package/locales/en-US/setting.json +12 -0
  17. package/locales/es-ES/discover.json +4 -0
  18. package/locales/es-ES/models.json +3 -0
  19. package/locales/es-ES/setting.json +12 -0
  20. package/locales/fa-IR/discover.json +4 -0
  21. package/locales/fa-IR/models.json +3 -0
  22. package/locales/fa-IR/setting.json +12 -0
  23. package/locales/fr-FR/discover.json +4 -0
  24. package/locales/fr-FR/models.json +3 -0
  25. package/locales/fr-FR/setting.json +12 -0
  26. package/locales/it-IT/discover.json +4 -0
  27. package/locales/it-IT/models.json +3 -0
  28. package/locales/it-IT/setting.json +12 -0
  29. package/locales/ja-JP/discover.json +4 -0
  30. package/locales/ja-JP/models.json +3 -0
  31. package/locales/ja-JP/setting.json +12 -0
  32. package/locales/ko-KR/discover.json +4 -0
  33. package/locales/ko-KR/models.json +15 -0
  34. package/locales/ko-KR/setting.json +12 -0
  35. package/locales/nl-NL/discover.json +4 -0
  36. package/locales/nl-NL/models.json +3 -0
  37. package/locales/nl-NL/setting.json +12 -0
  38. package/locales/pl-PL/discover.json +4 -0
  39. package/locales/pl-PL/models.json +3 -0
  40. package/locales/pl-PL/setting.json +12 -0
  41. package/locales/pt-BR/discover.json +4 -0
  42. package/locales/pt-BR/models.json +3 -0
  43. package/locales/pt-BR/setting.json +12 -0
  44. package/locales/ru-RU/discover.json +4 -0
  45. package/locales/ru-RU/models.json +3 -0
  46. package/locales/ru-RU/setting.json +12 -0
  47. package/locales/tr-TR/discover.json +4 -0
  48. package/locales/tr-TR/models.json +3 -0
  49. package/locales/tr-TR/setting.json +12 -0
  50. package/locales/vi-VN/discover.json +4 -0
  51. package/locales/vi-VN/models.json +3 -0
  52. package/locales/vi-VN/setting.json +12 -0
  53. package/locales/zh-CN/discover.json +4 -0
  54. package/locales/zh-CN/models.json +4 -1
  55. package/locales/zh-CN/setting.json +12 -0
  56. package/locales/zh-TW/discover.json +4 -0
  57. package/locales/zh-TW/models.json +3 -0
  58. package/locales/zh-TW/setting.json +12 -0
  59. package/package.json +1 -1
  60. package/src/app/(main)/discover/(detail)/model/[...slugs]/features/ParameterList/index.tsx +10 -0
  61. package/src/config/aiModels/github.ts +18 -7
  62. package/src/config/aiModels/openai.ts +35 -2
  63. package/src/features/AgentSetting/AgentModal/index.tsx +27 -3
  64. package/src/libs/agent-runtime/github/index.ts +3 -3
  65. package/src/libs/agent-runtime/openai/index.ts +7 -5
  66. package/src/libs/agent-runtime/utils/streams/openai.test.ts +202 -0
  67. package/src/libs/agent-runtime/utils/streams/openai.ts +9 -8
  68. package/src/locales/default/discover.ts +4 -0
  69. package/src/locales/default/setting.ts +12 -0
  70. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -0
  71. package/src/types/agent/index.ts +6 -0
  72. package/src/types/llm.ts +5 -0
@@ -2,7 +2,7 @@ import { LOBE_DEFAULT_MODEL_LIST } from '@/config/modelProviders';
2
2
  import type { ChatModelCard } from '@/types/llm';
3
3
 
4
4
  import { AgentRuntimeErrorType } from '../error';
5
- import { o1Models, pruneO1Payload } from '../openai';
5
+ import { pruneReasoningPayload, reasoningModels } from '../openai';
6
6
  import { ModelProvider } from '../types';
7
7
  import {
8
8
  CHAT_MODELS_BLOCK_LIST,
@@ -37,8 +37,8 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
37
37
  handlePayload: (payload) => {
38
38
  const { model } = payload;
39
39
 
40
- if (o1Models.has(model)) {
41
- return { ...pruneO1Payload(payload), stream: false } as any;
40
+ if (reasoningModels.has(model)) {
41
+ return { ...pruneReasoningPayload(payload), stream: false } as any;
42
42
  }
43
43
 
44
44
  return { ...payload, stream: payload.stream ?? true };
@@ -2,21 +2,23 @@ import { ChatStreamPayload, ModelProvider, OpenAIChatMessage } from '../types';
2
2
  import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
3
 
4
4
  // TODO: 临时写法,后续要重构成 model card 展示配置
5
- export const o1Models = new Set([
5
+ export const reasoningModels = new Set([
6
6
  'o1-preview',
7
7
  'o1-preview-2024-09-12',
8
8
  'o1-mini',
9
9
  'o1-mini-2024-09-12',
10
10
  'o1',
11
11
  'o1-2024-12-17',
12
+ 'o3-mini',
13
+ 'o3-mini-2025-01-31',
12
14
  ]);
13
15
 
14
- export const pruneO1Payload = (payload: ChatStreamPayload) => ({
16
+ export const pruneReasoningPayload = (payload: ChatStreamPayload) => ({
15
17
  ...payload,
16
18
  frequency_penalty: 0,
17
19
  messages: payload.messages.map((message: OpenAIChatMessage) => ({
18
20
  ...message,
19
- role: message.role === 'system' ? 'user' : message.role,
21
+ role: message.role === 'system' ? 'developer' : message.role,
20
22
  })),
21
23
  presence_penalty: 0,
22
24
  temperature: 1,
@@ -29,8 +31,8 @@ export const LobeOpenAI = LobeOpenAICompatibleFactory({
29
31
  handlePayload: (payload) => {
30
32
  const { model } = payload;
31
33
 
32
- if (o1Models.has(model)) {
33
- return pruneO1Payload(payload) as any;
34
+ if (reasoningModels.has(model)) {
35
+ return pruneReasoningPayload(payload) as any;
34
36
  }
35
37
 
36
38
  return { ...payload, stream: payload.stream ?? true };
@@ -754,6 +754,7 @@ describe('OpenAIStream', () => {
754
754
  ].map((i) => `${i}\n`),
755
755
  );
756
756
  });
757
+
757
758
  it('should handle reasoning in litellm', async () => {
758
759
  const data = [
759
760
  {
@@ -954,5 +955,206 @@ describe('OpenAIStream', () => {
954
955
  ].map((i) => `${i}\n`),
955
956
  );
956
957
  });
958
+
959
+ it('should handle reasoning in siliconflow', async () => {
960
+ const data = [
961
+ {
962
+ id: '1',
963
+ object: 'chat.completion.chunk',
964
+ created: 1737563070,
965
+ model: 'deepseek-reasoner',
966
+ system_fingerprint: 'fp_1c5d8833bc',
967
+ choices: [
968
+ {
969
+ index: 0,
970
+ delta: { role: 'assistant', reasoning_content: '', content: '' },
971
+ logprobs: null,
972
+ finish_reason: null,
973
+ },
974
+ ],
975
+ },
976
+ {
977
+ id: '1',
978
+ object: 'chat.completion.chunk',
979
+ created: 1737563070,
980
+ model: 'deepseek-reasoner',
981
+ system_fingerprint: 'fp_1c5d8833bc',
982
+ choices: [
983
+ {
984
+ index: 0,
985
+ delta: { reasoning_content: '您好', content: '' },
986
+ logprobs: null,
987
+ finish_reason: null,
988
+ },
989
+ ],
990
+ },
991
+ {
992
+ id: '1',
993
+ object: 'chat.completion.chunk',
994
+ created: 1737563070,
995
+ model: 'deepseek-reasoner',
996
+ system_fingerprint: 'fp_1c5d8833bc',
997
+ choices: [
998
+ {
999
+ index: 0,
1000
+ delta: { reasoning_content: '!', content: '' },
1001
+ logprobs: null,
1002
+ finish_reason: null,
1003
+ },
1004
+ ],
1005
+ },
1006
+ {
1007
+ id: '1',
1008
+ object: 'chat.completion.chunk',
1009
+ created: 1737563070,
1010
+ model: 'deepseek-reasoner',
1011
+ system_fingerprint: 'fp_1c5d8833bc',
1012
+ choices: [
1013
+ {
1014
+ index: 0,
1015
+ delta: { content: '你好', reasoning_content: null },
1016
+ logprobs: null,
1017
+ finish_reason: null,
1018
+ },
1019
+ ],
1020
+ },
1021
+ {
1022
+ id: '1',
1023
+ object: 'chat.completion.chunk',
1024
+ created: 1737563070,
1025
+ model: 'deepseek-reasoner',
1026
+ system_fingerprint: 'fp_1c5d8833bc',
1027
+ choices: [
1028
+ {
1029
+ index: 0,
1030
+ delta: { content: '很高兴', reasoning_cont: null },
1031
+ logprobs: null,
1032
+ finish_reason: null,
1033
+ },
1034
+ ],
1035
+ },
1036
+ {
1037
+ id: '1',
1038
+ object: 'chat.completion.chunk',
1039
+ created: 1737563070,
1040
+ model: 'deepseek-reasoner',
1041
+ system_fingerprint: 'fp_1c5d8833bc',
1042
+ choices: [
1043
+ {
1044
+ index: 0,
1045
+ delta: { content: '为您', reasoning_content: null },
1046
+ logprobs: null,
1047
+ finish_reason: null,
1048
+ },
1049
+ ],
1050
+ },
1051
+ {
1052
+ id: '1',
1053
+ object: 'chat.completion.chunk',
1054
+ created: 1737563070,
1055
+ model: 'deepseek-reasoner',
1056
+ system_fingerprint: 'fp_1c5d8833bc',
1057
+ choices: [
1058
+ {
1059
+ index: 0,
1060
+ delta: { content: '提供', reasoning_content: null },
1061
+ logprobs: null,
1062
+ finish_reason: null,
1063
+ },
1064
+ ],
1065
+ },
1066
+ {
1067
+ id: '1',
1068
+ object: 'chat.completion.chunk',
1069
+ created: 1737563070,
1070
+ model: 'deepseek-reasoner',
1071
+ system_fingerprint: 'fp_1c5d8833bc',
1072
+ choices: [
1073
+ {
1074
+ index: 0,
1075
+ delta: { content: '帮助。', reasoning_content: null },
1076
+ logprobs: null,
1077
+ finish_reason: null,
1078
+ },
1079
+ ],
1080
+ },
1081
+ {
1082
+ id: '1',
1083
+ object: 'chat.completion.chunk',
1084
+ created: 1737563070,
1085
+ model: 'deepseek-reasoner',
1086
+ system_fingerprint: 'fp_1c5d8833bc',
1087
+ choices: [
1088
+ {
1089
+ index: 0,
1090
+ delta: { content: '', reasoning_content: null },
1091
+ logprobs: null,
1092
+ finish_reason: 'stop',
1093
+ },
1094
+ ],
1095
+ usage: {
1096
+ prompt_tokens: 6,
1097
+ completion_tokens: 104,
1098
+ total_tokens: 110,
1099
+ prompt_tokens_details: { cached_tokens: 0 },
1100
+ completion_tokens_details: { reasoning_tokens: 70 },
1101
+ prompt_cache_hit_tokens: 0,
1102
+ prompt_cache_miss_tokens: 6,
1103
+ },
1104
+ },
1105
+ ];
1106
+
1107
+ const mockOpenAIStream = new ReadableStream({
1108
+ start(controller) {
1109
+ data.forEach((chunk) => {
1110
+ controller.enqueue(chunk);
1111
+ });
1112
+
1113
+ controller.close();
1114
+ },
1115
+ });
1116
+
1117
+ const protocolStream = OpenAIStream(mockOpenAIStream);
1118
+
1119
+ const decoder = new TextDecoder();
1120
+ const chunks = [];
1121
+
1122
+ // @ts-ignore
1123
+ for await (const chunk of protocolStream) {
1124
+ chunks.push(decoder.decode(chunk, { stream: true }));
1125
+ }
1126
+
1127
+ expect(chunks).toEqual(
1128
+ [
1129
+ 'id: 1',
1130
+ 'event: reasoning',
1131
+ `data: ""\n`,
1132
+ 'id: 1',
1133
+ 'event: reasoning',
1134
+ `data: "您好"\n`,
1135
+ 'id: 1',
1136
+ 'event: reasoning',
1137
+ `data: "!"\n`,
1138
+ 'id: 1',
1139
+ 'event: text',
1140
+ `data: "你好"\n`,
1141
+ 'id: 1',
1142
+ 'event: text',
1143
+ `data: "很高兴"\n`,
1144
+ 'id: 1',
1145
+ 'event: text',
1146
+ `data: "为您"\n`,
1147
+ 'id: 1',
1148
+ 'event: text',
1149
+ `data: "提供"\n`,
1150
+ 'id: 1',
1151
+ 'event: text',
1152
+ `data: "帮助。"\n`,
1153
+ 'id: 1',
1154
+ 'event: stop',
1155
+ `data: "stop"\n`,
1156
+ ].map((i) => `${i}\n`),
1157
+ );
1158
+ });
957
1159
  });
958
1160
  });
@@ -37,9 +37,8 @@ export const transformOpenAIStream = (
37
37
  return { data: errorData, id: 'first_chunk_error', type: 'error' };
38
38
  }
39
39
 
40
- // maybe need another structure to add support for multiple choices
41
-
42
40
  try {
41
+ // maybe need another structure to add support for multiple choices
43
42
  const item = chunk.choices[0];
44
43
  if (!item) {
45
44
  return { data: chunk, id: chunk.id, type: 'data' };
@@ -88,12 +87,10 @@ export const transformOpenAIStream = (
88
87
  return { data: item.finish_reason, id: chunk.id, type: 'stop' };
89
88
  }
90
89
 
91
- if (typeof item.delta?.content === 'string') {
92
- return { data: item.delta.content, id: chunk.id, type: 'text' };
93
- }
94
-
95
- // DeepSeek reasoner 会将 thinking 放在 reasoning_content 字段中
96
- // litellm 处理 reasoning content 时 不会设定 content = null
90
+ // DeepSeek reasoner will put thinking in the reasoning_content field
91
+ // litellm will not set content = null when processing reasoning content
92
+ // en: siliconflow has encountered a situation where both content and reasoning_content are present, so the parsing order go ahead
93
+ // refs: https://github.com/lobehub/lobe-chat/issues/5681
97
94
  if (
98
95
  item.delta &&
99
96
  'reasoning_content' in item.delta &&
@@ -102,6 +99,10 @@ export const transformOpenAIStream = (
102
99
  return { data: item.delta.reasoning_content, id: chunk.id, type: 'reasoning' };
103
100
  }
104
101
 
102
+ if (typeof item.delta?.content === 'string') {
103
+ return { data: item.delta.content, id: chunk.id, type: 'text' };
104
+ }
105
+
105
106
  // 无内容情况
106
107
  if (item.delta && item.delta.content === null) {
107
108
  return { data: item.delta, id: chunk.id, type: 'data' };
@@ -127,6 +127,10 @@ export default {
127
127
  title: '话题新鲜度',
128
128
  },
129
129
  range: '范围',
130
+ reasoning_effort: {
131
+ desc: '此设置用于控制模型在生成回答前的推理强度。低强度优先响应速度并节省 Token,高强度提供更完整的推理,但会消耗更多 Token 并降低响应速度。默认值为中,平衡推理准确性与响应速度。',
132
+ title: '推理强度',
133
+ },
130
134
  temperature: {
131
135
  desc: '此设置影响模型回应的多样性。较低的值会导致更可预测和典型的回应,而较高的值则鼓励更多样化和不常见的回应。当值设为0时,模型对于给定的输入总是给出相同的回应。',
132
136
  title: '随机性',
@@ -202,6 +202,9 @@ export default {
202
202
  enableMaxTokens: {
203
203
  title: '开启单次回复限制',
204
204
  },
205
+ enableReasoningEffort: {
206
+ title: '开启推理强度调整',
207
+ },
205
208
  frequencyPenalty: {
206
209
  desc: '值越大,越有可能降低重复字词',
207
210
  title: '频率惩罚度',
@@ -218,6 +221,15 @@ export default {
218
221
  desc: '值越大,越有可能扩展到新话题',
219
222
  title: '话题新鲜度',
220
223
  },
224
+ reasoningEffort: {
225
+ desc: '值越大,推理能力越强,但可能会增加响应时间和 Token 消耗',
226
+ options: {
227
+ high: '高',
228
+ low: '低',
229
+ medium: '中',
230
+ },
231
+ title: '推理强度',
232
+ },
221
233
  temperature: {
222
234
  desc: '值越大,回复越随机',
223
235
  title: '随机性',
@@ -421,6 +421,11 @@ export const generateAIChat: StateCreator<
421
421
  ? agentConfig.params.max_tokens
422
422
  : undefined;
423
423
 
424
+ // 5. handle reasoning_effort
425
+ agentConfig.params.reasoning_effort = chatConfig.enableReasoningEffort
426
+ ? agentConfig.params.reasoning_effort
427
+ : undefined;
428
+
424
429
  let isFunctionCall = false;
425
430
  let msgTraceId: string | undefined;
426
431
  let output = '';
@@ -68,6 +68,11 @@ export interface LobeAgentChatConfig {
68
68
  enableHistoryCount?: boolean;
69
69
  enableMaxTokens?: boolean;
70
70
 
71
+ /**
72
+ * 自定义推理强度
73
+ */
74
+ enableReasoningEffort?: boolean;
75
+
71
76
  /**
72
77
  * 历史消息条数
73
78
  */
@@ -82,6 +87,7 @@ export const AgentChatConfigSchema = z.object({
82
87
  enableCompressHistory: z.boolean().optional(),
83
88
  enableHistoryCount: z.boolean().optional(),
84
89
  enableMaxTokens: z.boolean().optional(),
90
+ enableReasoningEffort: z.boolean().optional(),
85
91
  historyCount: z.number().optional(),
86
92
  });
87
93
 
package/src/types/llm.ts CHANGED
@@ -159,6 +159,11 @@ export interface LLMParams {
159
159
  * 生成文本的随机度量,用于控制文本的创造性和多样性
160
160
  * @default 1
161
161
  */
162
+ reasoning_effort?: string;
163
+ /**
164
+ * 控制模型推理能力
165
+ * @default medium
166
+ */
162
167
  temperature?: number;
163
168
  /**
164
169
  * 控制生成文本中最高概率的单个 token