diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7ce529cf33ec..3d9bdcc5fa34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,240 @@
# Changelog
+### [Version 1.19.31](https://github.com/lobehub/lobe-chat/compare/v1.19.30...v1.19.31)
+
+Released on **2024-09-24**
+
+#### 💄 Styles
+
+- **misc**: Add google gemini 1.5 002 series.
+
+
+
+Improvements and Fixes
+
+#### Styles
+
+- **misc**: Add google gemini 1.5 002 series, closes [#4118](https://github.com/lobehub/lobe-chat/issues/4118) ([10145fa](https://github.com/lobehub/lobe-chat/commit/10145fa))
+
+
diff --git a/src/layout/GlobalProvider/StoreInitialization.tsx b/src/layout/GlobalProvider/StoreInitialization.tsx
index 462b4b483a25..24345ef8ec8b 100644
--- a/src/layout/GlobalProvider/StoreInitialization.tsx
+++ b/src/layout/GlobalProvider/StoreInitialization.tsx
@@ -20,12 +20,14 @@ const StoreInitialization = memo(() => {
useTranslation('error');
const router = useRouter();
- const [isLogin, isSignedIn, useInitUserState, importUrlShareSettings] = useUserStore((s) => [
- authSelectors.isLogin(s),
- s.isSignedIn,
- s.useInitUserState,
- s.importUrlShareSettings,
- ]);
+ const [isLogin, isSignedIn, useInitUserState, importUrlShareSettings, isUserStateInit] =
+ useUserStore((s) => [
+ authSelectors.isLogin(s),
+ s.isSignedIn,
+ s.useInitUserState,
+ s.importUrlShareSettings,
+ s.isUserStateInit,
+ ]);
const { serverConfig } = useServerConfigStore();
@@ -74,8 +76,10 @@ const StoreInitialization = memo(() => {
// Import settings from the url
const searchParam = useSearchParams().get(LOBE_URL_IMPORT_NAME);
useEffect(() => {
- importUrlShareSettings(searchParam);
- }, [searchParam]);
+ // Why use `usUserStateInit`,
+ // see: https://github.com/lobehub/lobe-chat/pull/4072
+ if (searchParam && isUserStateInit) importUrlShareSettings(searchParam);
+ }, [searchParam, isUserStateInit]);
useEffect(() => {
if (mobile) {
diff --git a/src/libs/agent-runtime/ai360/index.ts b/src/libs/agent-runtime/ai360/index.ts
index 86c199154a16..c931293363ae 100644
--- a/src/libs/agent-runtime/ai360/index.ts
+++ b/src/libs/agent-runtime/ai360/index.ts
@@ -3,6 +3,14 @@ import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
export const LobeAi360AI = LobeOpenAICompatibleFactory({
baseURL: 'https://ai.360.cn/v1',
+ chatCompletion: {
+ handlePayload: (payload) => {
+ return {
+ ...payload,
+ stream: !payload.tools,
+ } as any;
+ },
+ },
debug: {
chatCompletion: () => process.env.DEBUG_AI360_CHAT_COMPLETION === '1',
},
diff --git a/src/libs/agent-runtime/google/index.test.ts b/src/libs/agent-runtime/google/index.test.ts
index 8b54fb8aabbe..469286a41eb6 100644
--- a/src/libs/agent-runtime/google/index.test.ts
+++ b/src/libs/agent-runtime/google/index.test.ts
@@ -304,6 +304,30 @@ describe('LobeGoogleAI', () => {
describe('private method', () => {
describe('convertContentToGooglePart', () => {
+ it('should handle text type messages', async () => {
+ const result = await instance['convertContentToGooglePart']({
+ type: 'text',
+ text: 'Hello',
+ });
+ expect(result).toEqual({ text: 'Hello' });
+ });
+
+ it('should handle base64 type images', async () => {
+ const base64Image =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
+ const result = await instance['convertContentToGooglePart']({
+ type: 'image_url',
+ image_url: { url: base64Image },
+ });
+
+ expect(result).toEqual({
+ inlineData: {
+ data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
+ mimeType: 'image/png',
+ },
+ });
+ });
+
it('should handle URL type images', async () => {
const imageUrl = 'http://example.com/image.png';
const mockBase64 = 'mockBase64Data';
@@ -357,7 +381,7 @@ describe('LobeGoogleAI', () => {
{ content: 'Hi', role: 'assistant' },
];
- const contents = await instance['buildGoogleMessages'](messages, 'gemini-pro');
+ const contents = await instance['buildGoogleMessages'](messages, 'gemini-1.0');
expect(contents).toHaveLength(3);
expect(contents).toEqual([
@@ -373,7 +397,7 @@ describe('LobeGoogleAI', () => {
{ content: 'Who are you', role: 'user' },
];
- const contents = await instance['buildGoogleMessages'](messages, 'gemini-pro');
+ const contents = await instance['buildGoogleMessages'](messages, 'gemini-1.0');
expect(contents).toHaveLength(3);
expect(contents).toEqual([
@@ -487,9 +511,6 @@ describe('LobeGoogleAI', () => {
});
});
- // 类似地添加 array/string/number/boolean 类型schema的测试用例
- // ...
-
it('should correctly convert nested schema', () => {
const schema: JSONSchema7 = {
type: 'object',
@@ -523,6 +544,36 @@ describe('LobeGoogleAI', () => {
},
});
});
+
+ it('should correctly convert array schema', () => {
+ const schema: JSONSchema7 = {
+ type: 'array',
+ items: { type: 'string' },
+ };
+ const converted = instance['convertSchemaObject'](schema);
+ expect(converted).toEqual({
+ type: FunctionDeclarationSchemaType.ARRAY,
+ items: { type: FunctionDeclarationSchemaType.STRING },
+ });
+ });
+
+ it('should correctly convert string schema', () => {
+ const schema: JSONSchema7 = { type: 'string' };
+ const converted = instance['convertSchemaObject'](schema);
+ expect(converted).toEqual({ type: FunctionDeclarationSchemaType.STRING });
+ });
+
+ it('should correctly convert number schema', () => {
+ const schema: JSONSchema7 = { type: 'number' };
+ const converted = instance['convertSchemaObject'](schema);
+ expect(converted).toEqual({ type: FunctionDeclarationSchemaType.NUMBER });
+ });
+
+ it('should correctly convert boolean schema', () => {
+ const schema: JSONSchema7 = { type: 'boolean' };
+ const converted = instance['convertSchemaObject'](schema);
+ expect(converted).toEqual({ type: FunctionDeclarationSchemaType.BOOLEAN });
+ });
});
describe('convertOAIMessagesToGoogleMessage', () => {
@@ -592,6 +643,49 @@ describe('LobeGoogleAI', () => {
],
});
});
+
+ it('should correctly convert function call message', async () => {
+ const message = {
+ role: 'assistant',
+ tool_calls: [
+ {
+ id: 'call_1',
+ function: {
+ name: 'get_current_weather',
+ arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
+ },
+ type: 'function',
+ },
+ ],
+ } as OpenAIChatMessage;
+
+ const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
+ expect(converted).toEqual({
+ role: 'function',
+ parts: [
+ {
+ functionCall: {
+ name: 'get_current_weather',
+ args: { location: 'London', unit: 'celsius' },
+ },
+ },
+ ],
+ });
+ });
+
+ it('should correctly handle empty content', async () => {
+ const message: OpenAIChatMessage = {
+ role: 'user',
+ content: '' as any, // explicitly set as empty string
+ };
+
+ const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
+
+ expect(converted).toEqual({
+ role: 'user',
+ parts: [{ text: '' }],
+ });
+ });
});
});
});
diff --git a/src/libs/agent-runtime/google/index.ts b/src/libs/agent-runtime/google/index.ts
index 1c6130aa3199..ecc80c9d2855 100644
--- a/src/libs/agent-runtime/google/index.ts
+++ b/src/libs/agent-runtime/google/index.ts
@@ -1,5 +1,6 @@
import {
Content,
+ FunctionCallPart,
FunctionDeclaration,
FunctionDeclarationSchemaProperty,
FunctionDeclarationSchemaType,
@@ -11,6 +12,7 @@ import { JSONSchema7 } from 'json-schema';
import { transform } from 'lodash-es';
import { imageUrlToBase64 } from '@/utils/imageToBase64';
+import { safeParseJSON } from '@/utils/safeParseJSON';
import { LobeRuntimeAI } from '../BaseAI';
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../error';
@@ -50,8 +52,9 @@ export class LobeGoogleAI implements LobeRuntimeAI {
this.baseURL = baseURL;
}
- async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
+ async chat(rawPayload: ChatStreamPayload, options?: ChatCompetitionOptions) {
try {
+ const payload = this.buildPayload(rawPayload);
const model = payload.model;
const contents = await this.buildGoogleMessages(payload.messages, model);
@@ -88,7 +91,11 @@ export class LobeGoogleAI implements LobeRuntimeAI {
},
{ apiVersion: 'v1beta', baseUrl: this.baseURL },
)
- .generateContentStream({ contents, tools: this.buildGoogleTools(payload.tools) });
+ .generateContentStream({
+ contents,
+ systemInstruction: payload.system as string,
+ tools: this.buildGoogleTools(payload.tools),
+ });
const googleStream = googleGenAIResultToStream(geminiStreamResult);
const [prod, useForDebug] = googleStream.tee();
@@ -111,6 +118,16 @@ export class LobeGoogleAI implements LobeRuntimeAI {
}
}
+ private buildPayload(payload: ChatStreamPayload) {
+ const system_message = payload.messages.find((m) => m.role === 'system');
+ const user_messages = payload.messages.filter((m) => m.role !== 'system');
+
+ return {
+ ...payload,
+ messages: user_messages,
+ system: system_message?.content,
+ };
+ }
private convertContentToGooglePart = async (content: UserMessageContentPart): Promise