@lobehub/chat 1.96.13 → 1.96.14
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/docs/self-hosting/advanced/online-search.mdx +21 -21
- package/docs/self-hosting/advanced/online-search.zh-CN.mdx +29 -29
- package/package.json +2 -3
- package/src/libs/model-runtime/google/index.test.ts +93 -36
- package/src/libs/model-runtime/google/index.ts +50 -64
- package/src/libs/model-runtime/utils/streams/google-ai.test.ts +416 -17
- package/src/libs/model-runtime/utils/streams/google-ai.ts +17 -17
- package/src/libs/model-runtime/utils/streams/vertex-ai.test.ts +129 -0
- package/src/libs/model-runtime/utils/streams/vertex-ai.ts +16 -16
- package/src/libs/model-runtime/vertexai/index.ts +9 -3
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.96.14](https://github.com/lobehub/lobe-chat/compare/v1.96.13...v1.96.14)
|
6
|
+
|
7
|
+
<sup>Released on **2025-07-01**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Migrate to `@google/genai` SDK for Google Gemini API and Vertex AI.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Migrate to `@google/genai` SDK for Google Gemini API and Vertex AI, closes [#7884](https://github.com/lobehub/lobe-chat/issues/7884) ([fef3e5f](https://github.com/lobehub/lobe-chat/commit/fef3e5f))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.96.13](https://github.com/lobehub/lobe-chat/compare/v1.96.12...v1.96.13)
|
6
31
|
|
7
32
|
<sup>Released on **2025-07-01**</sup>
|
package/changelog/v1.json
CHANGED
@@ -58,19 +58,19 @@ SEARCH_PROVIDERS="searxng"
|
|
58
58
|
|
59
59
|
Supported search engines include:
|
60
60
|
|
61
|
-
| Value | Description
|
62
|
-
| ------------ |
|
63
|
-
| `anspire` | Search service provided by [Anspire](https://anspire.ai/).
|
64
|
-
| `bocha` | Search service from [Bocha](https://open.bochaai.com/).
|
65
|
-
| `brave` | [Brave](https://search.brave.com/help/api), a privacy-friendly search source.
|
66
|
-
| `exa` | [Exa](https://exa.ai/), a search API designed for AI.
|
67
|
-
| `firecrawl` | Search capabilities via [Firecrawl](https://firecrawl.dev/).
|
68
|
-
| `google` | Uses [Google Programmable Search Engine](https://programmablesearchengine.google.com/).
|
69
|
-
| `jina` | Semantic search provided by [Jina AI](https://jina.ai/).
|
70
|
-
| `kagi` | Premium search API by [Kagi](https://kagi.com/), requires a subscription key.
|
71
|
-
| `search1api` | Aggregated search capabilities from [Search1API](https://www.search1api.com).
|
72
|
-
| `searxng` | Use a self-hosted or public [SearXNG](https://searx.space/) instance.
|
73
|
-
| `tavily` | [Tavily](https://www.tavily.com/), offers fast web summaries and answers.
|
61
|
+
| Value | Description | Environment Variable |
|
62
|
+
| ------------ | --------------------------------------------------------------------------------------- | ------------------------------------------- |
|
63
|
+
| `anspire` | Search service provided by [Anspire](https://anspire.ai/). | `ANSPIRE_API_KEY` |
|
64
|
+
| `bocha` | Search service from [Bocha](https://open.bochaai.com/). | `BOCHA_API_KEY` |
|
65
|
+
| `brave` | [Brave](https://search.brave.com/help/api), a privacy-friendly search source. | `BRAVE_API_KEY` |
|
66
|
+
| `exa` | [Exa](https://exa.ai/), a search API designed for AI. | `EXA_API_KEY` |
|
67
|
+
| `firecrawl` | Search capabilities via [Firecrawl](https://firecrawl.dev/). | `FIRECRAWL_API_KEY` |
|
68
|
+
| `google` | Uses [Google Programmable Search Engine](https://programmablesearchengine.google.com/). | `GOOGLE_PSE_API_KEY` `GOOGLE_PSE_ENGINE_ID` |
|
69
|
+
| `jina` | Semantic search provided by [Jina AI](https://jina.ai/). | `JINA_READER_API_KEY` |
|
70
|
+
| `kagi` | Premium search API by [Kagi](https://kagi.com/), requires a subscription key. | `KAGI_API_KEY` |
|
71
|
+
| `search1api` | Aggregated search capabilities from [Search1API](https://www.search1api.com). | `SEARCH1API_CRAWL_API_KEY` |
|
72
|
+
| `searxng` | Use a self-hosted or public [SearXNG](https://searx.space/) instance. | `SEARXNG_URL` |
|
73
|
+
| `tavily` | [Tavily](https://www.tavily.com/), offers fast web summaries and answers. | `TAVILY_API_KEY` |
|
74
74
|
|
75
75
|
> ⚠️ Some search providers require you to apply for an API Key and configure it in your `.env` file.
|
76
76
|
|
@@ -98,8 +98,8 @@ BROWSERLESS_BLOCK_ADS=1
|
|
98
98
|
|
99
99
|
> 📌 Supported values:
|
100
100
|
>
|
101
|
-
>
|
102
|
-
>
|
101
|
+
> - `1`: Enable ad blocking (recommended);
|
102
|
+
> - `0`: Disable ad blocking (default).
|
103
103
|
|
104
104
|
> ✅ It is recommended to use with `BROWSERLESS_STEALTH_MODE=1` to enhance stealth and scraping success rate.
|
105
105
|
|
@@ -115,8 +115,8 @@ BROWSERLESS_STEALTH_MODE=1
|
|
115
115
|
|
116
116
|
> 📌 Supported values:
|
117
117
|
>
|
118
|
-
>
|
119
|
-
>
|
118
|
+
> - `1`: Enable stealth mode (recommended);
|
119
|
+
> - `0`: Disable stealth mode (default).
|
120
120
|
|
121
121
|
> ⚠️ Some websites use advanced anti-scraping techniques. Enabling stealth mode can significantly improve scraping success rate.
|
122
122
|
|
@@ -156,8 +156,8 @@ TAVILY_SEARCH_DEPTH=basic
|
|
156
156
|
|
157
157
|
Supported values:
|
158
158
|
|
159
|
-
|
160
|
-
|
159
|
+
- `basic`: Fast search, returns brief results;
|
160
|
+
- `advanced`: Deep search, returns more context and web page details.
|
161
161
|
|
162
162
|
---
|
163
163
|
|
@@ -171,8 +171,8 @@ TAVILY_EXTRACT_DEPTH=basic
|
|
171
171
|
|
172
172
|
Supported values:
|
173
173
|
|
174
|
-
|
175
|
-
|
174
|
+
- `basic`: Extracts basic info like title and content summary;
|
175
|
+
- `advanced`: Extracts structured data, lists, charts, and more from web pages.
|
176
176
|
|
177
177
|
---
|
178
178
|
|
@@ -16,7 +16,7 @@ LobeChat 支持为 AI 配置**联网搜索功能**,使其能够实时获取互
|
|
16
16
|
联网搜索可以让 AI 获取时效性内容,如最新新闻、技术动态或产品信息。你可以使用开源的 SearXNG 自行部署,也可以选择集成主流搜索引擎服务,如 Search1API、Google、Brave 等,根据你的使用场景自由组合。
|
17
17
|
</Callout>
|
18
18
|
|
19
|
-
通过设置搜索服务环境变量 `SEARCH_PROVIDERS` 和对应的 API Key,LobeChat 将在多个搜索源中查询并返回结果。你还可以搭配配置爬虫服务环境变量 `CRAWLER_IMPLS`(如 `browserless`、`firecrawl`、`tavily`
|
19
|
+
通过设置搜索服务环境变量 `SEARCH_PROVIDERS` 和对应的 API Key,LobeChat 将在多个搜索源中查询并返回结果。你还可以搭配配置爬虫服务环境变量 `CRAWLER_IMPLS`(如 `browserless`、`firecrawl`、`tavily` 等)以提取网页内容,实现搜索 + 阅读的增强能力。
|
20
20
|
|
21
21
|
# 核心环境变量
|
22
22
|
|
@@ -30,15 +30,15 @@ CRAWLER_IMPLS="native,search1api"
|
|
30
30
|
|
31
31
|
支持的爬虫类型如下:
|
32
32
|
|
33
|
-
| 值
|
34
|
-
| ------------- |
|
35
|
-
| `browserless` | 基于 [Browserless](https://www.browserless.io/) 的无头浏览器爬虫,适合渲染复杂页面。
|
36
|
-
| `exa` | 使用 [Exa](https://exa.ai/) 提供的爬虫能力,需申请 API。
|
37
|
-
| `firecrawl` | [Firecrawl](https://firecrawl.dev/) 无头浏览器 API,适合现代网站抓取。
|
38
|
-
| `jina` | 使用 [Jina AI](https://jina.ai/) 的爬虫服务,支持快速提取摘要信息。
|
39
|
-
| `native` | 内置通用爬虫,适用于标准网页结构。
|
33
|
+
| 值 | 说明 | 环境变量 |
|
34
|
+
| ------------- | ---------------------------------------------------------------- | -------------------------- |
|
35
|
+
| `browserless` | 基于 [Browserless](https://www.browserless.io/) 的无头浏览器爬虫,适合渲染复杂页面。 | `BROWSERLESS_TOKEN` |
|
36
|
+
| `exa` | 使用 [Exa](https://exa.ai/) 提供的爬虫能力,需申请 API。 | `EXA_API_KEY` |
|
37
|
+
| `firecrawl` | [Firecrawl](https://firecrawl.dev/) 无头浏览器 API,适合现代网站抓取。 | `FIRECRAWL_API_KEY` |
|
38
|
+
| `jina` | 使用 [Jina AI](https://jina.ai/) 的爬虫服务,支持快速提取摘要信息。 | `JINA_READER_API_KEY` |
|
39
|
+
| `native` | 内置通用爬虫,适用于标准网页结构。 | |
|
40
40
|
| `search1api` | 利用 [Search1API](https://www.search1api.com) 提供的页面抓取能力,适合结构化内容提取。 | `SEARCH1API_CRAWL_API_KEY` |
|
41
|
-
| `tavily` | 使用 [Tavily](https://www.tavily.com/) 的网页抓取与摘要 API。
|
41
|
+
| `tavily` | 使用 [Tavily](https://www.tavily.com/) 的网页抓取与摘要 API。 | `TAVILY_API_KEY` |
|
42
42
|
|
43
43
|
> 💡 设置多个爬虫可提升成功率,系统将根据优先级尝试不同爬虫。
|
44
44
|
|
@@ -54,19 +54,19 @@ SEARCH_PROVIDERS="searxng"
|
|
54
54
|
|
55
55
|
支持的搜索引擎如下:
|
56
56
|
|
57
|
-
| 值
|
58
|
-
| ------------ |
|
59
|
-
| `anspire` | 基于 [Anspire(安思派)](https://anspire.ai/) 提供的搜索服务。
|
60
|
-
| `bocha` | 基于 [Bocha(博查)](https://open.bochaai.com/) 提供的搜索服务。
|
61
|
-
| `brave` | [Brave](https://search.brave.com/help/api),隐私友好的搜索源。
|
62
|
-
| `exa` | [Exa](https://exa.ai/),面向 AI 的搜索 API。
|
63
|
-
| `firecrawl` | 支持 [Firecrawl](https://firecrawl.dev/) 提供的搜索服务。
|
57
|
+
| 值 | 说明 | 环境变量 |
|
58
|
+
| ------------ | ------------------------------------------------------------------------------------- | ------------------------------------------- |
|
59
|
+
| `anspire` | 基于 [Anspire(安思派)](https://anspire.ai/) 提供的搜索服务。 | `ANSPIRE_API_KEY` |
|
60
|
+
| `bocha` | 基于 [Bocha(博查)](https://open.bochaai.com/) 提供的搜索服务。 | `BOCHA_API_KEY` |
|
61
|
+
| `brave` | [Brave](https://search.brave.com/help/api),隐私友好的搜索源。 | `BRAVE_API_KEY` |
|
62
|
+
| `exa` | [Exa](https://exa.ai/),面向 AI 的搜索 API。 | `EXA_API_KEY` |
|
63
|
+
| `firecrawl` | 支持 [Firecrawl](https://firecrawl.dev/) 提供的搜索服务。 | `FIRECRAWL_API_KEY` |
|
64
64
|
| `google` | 使用 [Google Programmable Search Engine](https://programmablesearchengine.google.com/)。 | `GOOGLE_PSE_API_KEY` `GOOGLE_PSE_ENGINE_ID` |
|
65
|
-
| `jina` | 使用 [Jina AI](https://jina.ai/) 提供的语义搜索服务。
|
66
|
-
| `kagi` | [Kagi](https://kagi.com/) 提供的高级搜索 API,需订阅 Key。
|
67
|
-
| `search1api` | 使用 [Search1API](https://www.search1api.com) 聚合搜索能力。
|
68
|
-
| `searxng` | 使用自托管或公共 [SearXNG](https://searx.space/) 实例。
|
69
|
-
| `tavily` | [Tavily](https://www.tavily.com/),快速网页摘要与答案返回。
|
65
|
+
| `jina` | 使用 [Jina AI](https://jina.ai/) 提供的语义搜索服务。 | `JINA_READER_API_KEY` |
|
66
|
+
| `kagi` | [Kagi](https://kagi.com/) 提供的高级搜索 API,需订阅 Key。 | `KAGI_API_KEY` |
|
67
|
+
| `search1api` | 使用 [Search1API](https://www.search1api.com) 聚合搜索能力。 | `SEARCH1API_CRAWL_API_KEY` |
|
68
|
+
| `searxng` | 使用自托管或公共 [SearXNG](https://searx.space/) 实例。 | `SEARXNG_URL` |
|
69
|
+
| `tavily` | [Tavily](https://www.tavily.com/),快速网页摘要与答案返回。 | `TAVILY_API_KEY` |
|
70
70
|
|
71
71
|
> ⚠️ 某些搜索提供商需要单独申请 API Key,并在 `.env` 中配置相关凭证。
|
72
72
|
|
@@ -94,8 +94,8 @@ BROWSERLESS_BLOCK_ADS=1
|
|
94
94
|
|
95
95
|
> 📌 支持的值:
|
96
96
|
>
|
97
|
-
>
|
98
|
-
>
|
97
|
+
> - `1`:启用广告拦截(推荐);
|
98
|
+
> - `0`:禁用广告拦截(默认)。
|
99
99
|
|
100
100
|
> ✅ 建议与 `BROWSERLESS_STEALTH_MODE=1` 一起使用,提高爬虫的隐蔽性和成功率。
|
101
101
|
|
@@ -111,8 +111,8 @@ BROWSERLESS_STEALTH_MODE=1
|
|
111
111
|
|
112
112
|
> 📌 支持的值:
|
113
113
|
>
|
114
|
-
>
|
115
|
-
>
|
114
|
+
> - `1`:启用隐身模式(推荐);
|
115
|
+
> - `0`:禁用隐身模式(默认)。
|
116
116
|
|
117
117
|
> ⚠️ 某些网站存在高级反爬机制,启用隐身模式可以显著提升抓取成功率。
|
118
118
|
|
@@ -152,8 +152,8 @@ TAVILY_SEARCH_DEPTH=basic
|
|
152
152
|
|
153
153
|
支持的值:
|
154
154
|
|
155
|
-
|
156
|
-
|
155
|
+
- `basic`: 快速搜索,返回简要结果;
|
156
|
+
- `advanced`: 深度搜索,返回更多上下文和网页信息。
|
157
157
|
|
158
158
|
---
|
159
159
|
|
@@ -167,8 +167,8 @@ TAVILY_EXTRACT_DEPTH=basic
|
|
167
167
|
|
168
168
|
支持的值:
|
169
169
|
|
170
|
-
|
171
|
-
|
170
|
+
- `basic`: 提取标题、正文摘要等基础信息;
|
171
|
+
- `advanced`: 提取网页的结构化信息、列表、图表等更多内容。
|
172
172
|
|
173
173
|
---
|
174
174
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.96.
|
3
|
+
"version": "1.96.14",
|
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",
|
@@ -135,8 +135,7 @@
|
|
135
135
|
"@codesandbox/sandpack-react": "^2.20.0",
|
136
136
|
"@cyntler/react-doc-viewer": "^1.17.0",
|
137
137
|
"@electric-sql/pglite": "0.2.17",
|
138
|
-
"@google
|
139
|
-
"@google/generative-ai": "^0.24.1",
|
138
|
+
"@google/genai": "^1.6.0",
|
140
139
|
"@huggingface/inference": "^2.8.1",
|
141
140
|
"@icons-pack/react-simple-icons": "9.6.0",
|
142
141
|
"@khmyznikov/pwa-install": "0.3.9",
|
@@ -1,9 +1,10 @@
|
|
1
1
|
// @vitest-environment edge-runtime
|
2
|
-
import {
|
2
|
+
import { GenerateContentResponse, Tool } from '@google/genai';
|
3
3
|
import OpenAI from 'openai';
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
5
5
|
|
6
6
|
import { OpenAIChatMessage } from '@/libs/model-runtime';
|
7
|
+
import { ChatStreamPayload } from '@/types/openai/chat';
|
7
8
|
import * as imageToBase64Module from '@/utils/imageToBase64';
|
8
9
|
|
9
10
|
import * as debugStreamModule from '../utils/debugStream';
|
@@ -22,9 +23,8 @@ beforeEach(() => {
|
|
22
23
|
instance = new LobeGoogleAI({ apiKey: 'test' });
|
23
24
|
|
24
25
|
// 使用 vi.spyOn 来模拟 chat.completions.create 方法
|
25
|
-
|
26
|
-
|
27
|
-
} as any);
|
26
|
+
const mockStreamData = (async function* (): AsyncGenerator<GenerateContentResponse> {})();
|
27
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(mockStreamData);
|
28
28
|
});
|
29
29
|
|
30
30
|
afterEach(() => {
|
@@ -60,9 +60,9 @@ describe('LobeGoogleAI', () => {
|
|
60
60
|
controller.close();
|
61
61
|
},
|
62
62
|
});
|
63
|
-
vi.spyOn(instance['client'], '
|
64
|
-
|
65
|
-
|
63
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
|
64
|
+
mockStream as any,
|
65
|
+
);
|
66
66
|
|
67
67
|
const result = await instance.chat({
|
68
68
|
messages: [{ content: 'Hello', role: 'user' }],
|
@@ -208,9 +208,9 @@ describe('LobeGoogleAI', () => {
|
|
208
208
|
},
|
209
209
|
});
|
210
210
|
|
211
|
-
vi.spyOn(instance['client'], '
|
212
|
-
|
213
|
-
|
211
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
|
212
|
+
mockStream as any,
|
213
|
+
);
|
214
214
|
});
|
215
215
|
|
216
216
|
it('should call debugStream in DEBUG mode', async () => {
|
@@ -224,9 +224,9 @@ describe('LobeGoogleAI', () => {
|
|
224
224
|
controller.close();
|
225
225
|
},
|
226
226
|
});
|
227
|
-
vi.spyOn(instance['client'], '
|
228
|
-
|
229
|
-
|
227
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockResolvedValue(
|
228
|
+
mockStream as any,
|
229
|
+
);
|
230
230
|
const debugStreamSpy = vi
|
231
231
|
.spyOn(debugStreamModule, 'debugStream')
|
232
232
|
.mockImplementation(() => Promise.resolve());
|
@@ -250,9 +250,7 @@ describe('LobeGoogleAI', () => {
|
|
250
250
|
|
251
251
|
const apiError = new Error(message);
|
252
252
|
|
253
|
-
vi.spyOn(instance['client'], '
|
254
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
255
|
-
} as any);
|
253
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
256
254
|
|
257
255
|
try {
|
258
256
|
await instance.chat({
|
@@ -271,9 +269,7 @@ describe('LobeGoogleAI', () => {
|
|
271
269
|
|
272
270
|
const apiError = new Error(message);
|
273
271
|
|
274
|
-
vi.spyOn(instance['client'], '
|
275
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
276
|
-
} as any);
|
272
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
277
273
|
|
278
274
|
try {
|
279
275
|
await instance.chat({
|
@@ -292,9 +288,7 @@ describe('LobeGoogleAI', () => {
|
|
292
288
|
|
293
289
|
const apiError = new Error(message);
|
294
290
|
|
295
|
-
vi.spyOn(instance['client'], '
|
296
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
297
|
-
} as any);
|
291
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
298
292
|
|
299
293
|
try {
|
300
294
|
await instance.chat({
|
@@ -326,9 +320,7 @@ describe('LobeGoogleAI', () => {
|
|
326
320
|
|
327
321
|
const apiError = new Error(message);
|
328
322
|
|
329
|
-
vi.spyOn(instance['client'], '
|
330
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
331
|
-
} as any);
|
323
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
332
324
|
|
333
325
|
try {
|
334
326
|
await instance.chat({
|
@@ -354,9 +346,7 @@ describe('LobeGoogleAI', () => {
|
|
354
346
|
const apiError = new Error('Error message');
|
355
347
|
|
356
348
|
// 使用 vi.spyOn 来模拟 chat.completions.create 方法
|
357
|
-
vi.spyOn(instance['client'], '
|
358
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
359
|
-
} as any);
|
349
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
360
350
|
|
361
351
|
// Act
|
362
352
|
try {
|
@@ -392,9 +382,7 @@ describe('LobeGoogleAI', () => {
|
|
392
382
|
};
|
393
383
|
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
|
394
384
|
|
395
|
-
vi.spyOn(instance['client'], '
|
396
|
-
generateContentStream: vi.fn().mockRejectedValue(apiError),
|
397
|
-
} as any);
|
385
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(apiError);
|
398
386
|
|
399
387
|
// Act
|
400
388
|
try {
|
@@ -418,9 +406,9 @@ describe('LobeGoogleAI', () => {
|
|
418
406
|
// Arrange
|
419
407
|
const genericError = new Error('Generic Error');
|
420
408
|
|
421
|
-
vi.spyOn(instance['client'], '
|
422
|
-
|
423
|
-
|
409
|
+
vi.spyOn(instance['client'].models, 'generateContentStream').mockRejectedValue(
|
410
|
+
genericError,
|
411
|
+
);
|
424
412
|
|
425
413
|
// Act
|
426
414
|
try {
|
@@ -642,11 +630,11 @@ describe('LobeGoogleAI', () => {
|
|
642
630
|
const googleTools = instance['buildGoogleTools'](tools);
|
643
631
|
|
644
632
|
expect(googleTools).toHaveLength(1);
|
645
|
-
expect((googleTools![0] as
|
633
|
+
expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
|
646
634
|
name: 'testTool',
|
647
635
|
description: 'A test tool',
|
648
636
|
parameters: {
|
649
|
-
type: '
|
637
|
+
type: 'OBJECT',
|
650
638
|
properties: {
|
651
639
|
param1: { type: 'string' },
|
652
640
|
param2: { type: 'number' },
|
@@ -655,6 +643,75 @@ describe('LobeGoogleAI', () => {
|
|
655
643
|
},
|
656
644
|
});
|
657
645
|
});
|
646
|
+
|
647
|
+
it('should also add tools when tool_calls exists', () => {
|
648
|
+
const tools: OpenAI.ChatCompletionTool[] = [
|
649
|
+
{
|
650
|
+
function: {
|
651
|
+
name: 'testTool',
|
652
|
+
description: 'A test tool',
|
653
|
+
parameters: {
|
654
|
+
type: 'object',
|
655
|
+
properties: {
|
656
|
+
param1: { type: 'string' },
|
657
|
+
param2: { type: 'number' },
|
658
|
+
},
|
659
|
+
required: ['param1'],
|
660
|
+
},
|
661
|
+
},
|
662
|
+
type: 'function',
|
663
|
+
},
|
664
|
+
];
|
665
|
+
|
666
|
+
const payload: ChatStreamPayload = {
|
667
|
+
messages: [
|
668
|
+
{
|
669
|
+
role: 'user',
|
670
|
+
content: '',
|
671
|
+
tool_calls: [
|
672
|
+
{ function: { name: 'some_func', arguments: '' }, id: 'func_1', type: 'function' },
|
673
|
+
],
|
674
|
+
},
|
675
|
+
],
|
676
|
+
model: 'gemini-2.5-flash-preview-04-17',
|
677
|
+
temperature: 1,
|
678
|
+
};
|
679
|
+
|
680
|
+
const googleTools = instance['buildGoogleTools'](tools, payload);
|
681
|
+
|
682
|
+
expect(googleTools).toHaveLength(1);
|
683
|
+
expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
|
684
|
+
name: 'testTool',
|
685
|
+
description: 'A test tool',
|
686
|
+
parameters: {
|
687
|
+
type: 'OBJECT',
|
688
|
+
properties: {
|
689
|
+
param1: { type: 'string' },
|
690
|
+
param2: { type: 'number' },
|
691
|
+
},
|
692
|
+
required: ['param1'],
|
693
|
+
},
|
694
|
+
});
|
695
|
+
});
|
696
|
+
|
697
|
+
it('should handle googleSearch', () => {
|
698
|
+
const payload: ChatStreamPayload = {
|
699
|
+
messages: [
|
700
|
+
{
|
701
|
+
role: 'user',
|
702
|
+
content: '',
|
703
|
+
},
|
704
|
+
],
|
705
|
+
model: 'gemini-2.5-flash-preview-04-17',
|
706
|
+
temperature: 1,
|
707
|
+
enabledSearch: true,
|
708
|
+
};
|
709
|
+
|
710
|
+
const googleTools = instance['buildGoogleTools'](undefined, payload);
|
711
|
+
|
712
|
+
expect(googleTools).toHaveLength(1);
|
713
|
+
expect(googleTools![0] as Tool).toEqual({ googleSearch: {} });
|
714
|
+
});
|
658
715
|
});
|
659
716
|
|
660
717
|
describe('convertOAIMessagesToGoogleMessage', () => {
|