mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 13:06:21 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5947148c01 | |||
| b0cb96e5c2 | |||
| f1d732d166 | |||
| c6de50e385 | |||
| b04a5d7906 | |||
| 088cc2c56c | |||
| acb49c1393 | |||
| e82d4b7274 | |||
| 2dc03b47d6 | |||
| 3e64ee659e | |||
| f653ce1737 | |||
| eeabb69088 | |||
| 356cf029dd | |||
| 6e7b420347 | |||
| ee464838ac | |||
| ec5af1b4c7 |
+100
@@ -2,6 +2,106 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
### [Version 1.132.19](https://github.com/lobehub/lobe-chat/compare/v1.132.18...v1.132.19)
|
||||
|
||||
<sup>Released on **2025-09-29**</sup>
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 1.132.18](https://github.com/lobehub/lobe-chat/compare/v1.132.17...v1.132.18)
|
||||
|
||||
<sup>Released on **2025-09-28**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Refactor tools-engine and fix search token count.
|
||||
|
||||
#### 💄 Styles
|
||||
|
||||
- **misc**: Update i18n.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Refactor tools-engine and fix search token count, closes [#9448](https://github.com/lobehub/lobe-chat/issues/9448) ([e82d4b7](https://github.com/lobehub/lobe-chat/commit/e82d4b7))
|
||||
|
||||
#### Styles
|
||||
|
||||
- **misc**: Update i18n, closes [#9449](https://github.com/lobehub/lobe-chat/issues/9449) ([b04a5d7](https://github.com/lobehub/lobe-chat/commit/b04a5d7))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 1.132.17](https://github.com/lobehub/lobe-chat/compare/v1.132.16...v1.132.17)
|
||||
|
||||
<sup>Released on **2025-09-27**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Fix input empty group name.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Fix input empty group name, closes [#9441](https://github.com/lobehub/lobe-chat/issues/9441) ([f653ce1](https://github.com/lobehub/lobe-chat/commit/f653ce1))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 1.132.16](https://github.com/lobehub/lobe-chat/compare/v1.132.15...v1.132.16)
|
||||
|
||||
<sup>Released on **2025-09-26**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Resolve qwen-image-edit imageUrls conversion issue.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Resolve qwen-image-edit imageUrls conversion issue, closes [#9414](https://github.com/lobehub/lobe-chat/issues/9414) ([ec5af1b](https://github.com/lobehub/lobe-chat/commit/ec5af1b))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 1.132.15](https://github.com/lobehub/lobe-chat/compare/v1.132.14...v1.132.15)
|
||||
|
||||
<sup>Released on **2025-09-25**</sup>
|
||||
|
||||
@@ -382,14 +382,14 @@ In addition, these plugins are not limited to news aggregation, but can also ext
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
| Recent Submits | Description |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Web](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | Smart web search that reads and analyzes pages to deliver comprehensive answers from Google results.<br/>`web` `search` |
|
||||
| [Bing_websearch](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | Search for information from the internet base BingApi<br/>`bingsearch` |
|
||||
| [Google CSE](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | Searches Google through their official CSE API.<br/>`web` `search` |
|
||||
| [Tongyi wanxiang Image Generator](https://lobechat.com/discover/plugin/alps-tongyi-image)<br/><sup>By **YoungTx** on **2024-08-09**</sup> | This plugin uses Alibaba's Tongyi Wanxiang model to generate images based on text prompts.<br/>`image` `tongyi` `wanxiang` |
|
||||
| Recent Submits | Description |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-09-27**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
|
||||
| [Web](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | Smart web search that reads and analyzes pages to deliver comprehensive answers from Google results.<br/>`web` `search` |
|
||||
| [Bing_websearch](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | Search for information from the internet base BingApi<br/>`bingsearch` |
|
||||
| [Google CSE](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | Searches Google through their official CSE API.<br/>`web` `search` |
|
||||
|
||||
> 📊 Total plugins: [<kbd>**41**</kbd>](https://lobechat.com/discover/plugins)
|
||||
> 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
|
||||
+7
-7
@@ -375,14 +375,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
| 最近新增 | 描述 |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自 Google 结果的全面答案。<br/>`网页` `搜索` |
|
||||
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
|
||||
| [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
|
||||
| [通义万象图像生成器](https://lobechat.com/discover/plugin/alps-tongyi-image)<br/><sup>By **YoungTx** on **2024-08-09**</sup> | 此插件使用阿里巴巴的通义万象模型根据文本提示生成图像。<br/>`图像` `通义` `万象` |
|
||||
| 最近新增 | 描述 |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-09-27**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
|
||||
| [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自 Google 结果的全面答案。<br/>`网页` `搜索` |
|
||||
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
|
||||
| [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
|
||||
|
||||
> 📊 Total plugins: [<kbd>**41**</kbd>](https://lobechat.com/discover/plugins)
|
||||
> 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
|
||||
|
||||
<!-- PLUGIN LIST -->
|
||||
|
||||
|
||||
@@ -336,7 +336,6 @@ export default class Browser {
|
||||
vibrancy: 'sidebar',
|
||||
visualEffectState: 'active',
|
||||
webPreferences: {
|
||||
backgroundThrottling: false,
|
||||
contextIsolation: true,
|
||||
preload: join(preloadDir, 'index.js'),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Refactor tools-engine and fix search token count."],
|
||||
"improvements": ["Update i18n."]
|
||||
},
|
||||
"date": "2025-09-28",
|
||||
"version": "1.132.18"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Fix input empty group name."]
|
||||
},
|
||||
"date": "2025-09-27",
|
||||
"version": "1.132.17"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Resolve qwen-image-edit imageUrls conversion issue."]
|
||||
},
|
||||
"date": "2025-09-26",
|
||||
"version": "1.132.16"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Add proxyUrl configuration for NEW API provider."]
|
||||
|
||||
@@ -16,6 +16,7 @@ table agents {
|
||||
provider text
|
||||
system_role text
|
||||
tts jsonb
|
||||
virtual boolean [default: false]
|
||||
opening_message text
|
||||
opening_questions text[] [default: `[]`]
|
||||
accessed_at "timestamp with time zone" [not null, default: `now()`]
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 هو نموذج أساسي بمعمارية MoE يتمتع بقدرات قوية للغاية في البرمجة وقدرات الوكيل (Agent)، بإجمالي معلمات يبلغ 1 تريليون والمعلمات المُفعَّلة 32 مليار. في اختبارات الأداء المعيارية للفئات الرئيسية مثل الاستدلال المعرفي العام والبرمجة والرياضيات والوكلاء (Agent)، تفوق أداء نموذج K2 على النماذج المفتوحة المصدر السائدة الأخرى."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 هو نموذج لغوي خبير هجين واسع النطاق (MoE) تم تطويره بواسطة AI جانب القمر المظلم، يحتوي على تريليون معلمة إجمالية و 32 مليار معلمة تنشيط في كل تمريرة أمامية. تم تحسينه لقدرات الوكيل، بما في ذلك استخدام الأدوات المتقدمة، الاستدلال، وتركيب الشيفرة."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "يستخدم منتج كيمي المساعد الذكي أحدث نموذج كبير من كيمي، وقد يحتوي على ميزات لم تستقر بعد. يدعم فهم الصور، وسيختار تلقائيًا نموذج 8k/32k/128k كنموذج للتسعير بناءً على طول سياق الطلب."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "تغطي نماذج Ollama مجموعة واسعة من مجالات توليد الشيفرة، والعمليات الرياضية، ومعالجة اللغات المتعددة، والتفاعل الحواري، وتدعم احتياجات النشر على مستوى المؤسسات والتخصيص المحلي."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "توفر Ollama Cloud خدمة استدلال مُدارة رسميًا، تتيح الوصول الفوري إلى مكتبة نماذج Ollama، وتدعم واجهة متوافقة مع OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI هي مؤسسة رائدة عالميًا في أبحاث الذكاء الاصطناعي، حيث دفعت النماذج التي طورتها مثل سلسلة GPT حدود معالجة اللغة الطبيعية. تلتزم OpenAI بتغيير العديد من الصناعات من خلال حلول الذكاء الاصطناعي المبتكرة والفعالة. تتمتع منتجاتهم بأداء ملحوظ وفعالية من حيث التكلفة، وتستخدم على نطاق واسع في البحث والتجارة والتطبيقات الابتكارية."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "Kimi-k2 е базов модел с MoE архитектура, който притежава изключителни възможности за работа с код и агентни функции. Общият брой параметри е 1T, а активните параметри са 32B. В бенчмарковете за основни категории като общо знание и разсъждение, програмиране, математика и агентни задачи, моделът K2 превъзхожда другите водещи отворени модели."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 е голям мащабен смесен експертен (MoE) езиков модел, разработен от AI на тъмната страна на Луната, с общо 1 трилион параметри и 32 милиарда активирани параметри при всяко предно преминаване. Той е оптимизиран за агентски способности, включително усъвършенствано използване на инструменти, разсъждения и синтез на код."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi интелигентен асистент използва най-новия Kimi голям модел, който може да съдържа нестабилни функции. Поддържа разбиране на изображения и автоматично избира 8k/32k/128k модел за таксуване в зависимост от дължината на контекста на заявката."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Моделите, предоставени от Ollama, обхващат широк спектър от области, включително генериране на код, математически операции, многоезично обработване и диалогова интеракция, отговарящи на разнообразните нужди на предприятията и локализирани внедрявания."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud предлага официално хоствана услуга за изчисления, която осигурява достъп до библиотеката с модели на Ollama веднага след изваждане от кутията и поддържа интерфейс, съвместим с OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI е водеща световна изследователска институция в областта на изкуствения интелект, чийто модели, като серията GPT, напредват в границите на обработката на естествен език. OpenAI се стреми да трансформира множество индустрии чрез иновации и ефективни AI решения. Продуктите им предлагат значителна производителност и икономичност, широко използвани в изследвания, бизнес и иновационни приложения."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 ist ein Basis-Modell mit MoE-Architektur und besonders starken Fähigkeiten im Bereich Code und Agenten. Es verfügt über insgesamt 1T Parameter und 32B aktivierte Parameter. In Benchmark-Tests der wichtigsten Kategorien – allgemeines Wissens-Reasoning, Programmierung, Mathematik und Agenten – übertrifft das K2-Modell die Leistung anderer gängiger Open‑Source‑Modelle."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 ist ein von Moon's Dark Side AI entwickeltes großes gemischtes Expertenmodell (MoE) mit insgesamt 1 Billion Parametern und 32 Milliarden aktivierten Parametern pro Vorwärtsdurchlauf. Es ist auf Agentenfähigkeiten optimiert, einschließlich fortgeschrittener Werkzeugnutzung, Schlussfolgerungen und Code-Synthese."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Das Kimi intelligente Assistenzprodukt verwendet das neueste Kimi Großmodell, das möglicherweise noch instabile Funktionen enthält. Es unterstützt die Bildverarbeitung und wählt automatisch das Abrechnungsmodell 8k/32k/128k basierend auf der Länge des angeforderten Kontexts aus."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Die von Ollama angebotenen Modelle decken ein breites Spektrum ab, darunter Code-Generierung, mathematische Berechnungen, mehrsprachige Verarbeitung und dialogbasierte Interaktionen, und unterstützen die vielfältigen Anforderungen an unternehmensgerechte und lokal angepasste Bereitstellungen."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud bietet offiziell gehostete Inferenzdienste, mit sofortigem Zugriff auf die Ollama-Modellbibliothek und Unterstützung für OpenAI-kompatible Schnittstellen."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI ist eine weltweit führende Forschungsinstitution im Bereich der künstlichen Intelligenz, deren entwickelte Modelle wie die GPT-Serie die Grenzen der Verarbeitung natürlicher Sprache vorantreiben. OpenAI setzt sich dafür ein, durch innovative und effiziente KI-Lösungen verschiedene Branchen zu transformieren. Ihre Produkte zeichnen sich durch herausragende Leistung und Wirtschaftlichkeit aus und finden breite Anwendung in Forschung, Wirtschaft und innovativen Anwendungen."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "Kimi-K2 is a Mixture-of-Experts (MoE) foundation model with exceptional coding and agent capabilities, featuring 1T total parameters and 32B activated parameters. In benchmark evaluations across core categories — general knowledge reasoning, programming, mathematics, and agent tasks — the K2 model outperforms other leading open-source models."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 is a large-scale Mixture of Experts (MoE) language model developed by Moon's Dark Side AI, featuring a total of 1 trillion parameters and 32 billion activated parameters per forward pass. It is optimized for agent capabilities, including advanced tool usage, reasoning, and code synthesis."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "The Kimi Smart Assistant product uses the latest Kimi large model, which may include features that are not yet stable. It supports image understanding and will automatically select the 8k/32k/128k model as the billing model based on the length of the request context."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollama provides models that cover a wide range of fields, including code generation, mathematical operations, multilingual processing, and conversational interaction, catering to diverse enterprise-level and localized deployment needs."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud offers officially hosted inference services, providing out-of-the-box access to the Ollama model library and supporting OpenAI-compatible interfaces."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI is a global leader in artificial intelligence research, with models like the GPT series pushing the frontiers of natural language processing. OpenAI is committed to transforming multiple industries through innovative and efficient AI solutions. Their products demonstrate significant performance and cost-effectiveness, widely used in research, business, and innovative applications."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 es un modelo base con arquitectura MoE que ofrece potentes capacidades para código y agentes, con 1T parámetros totales y 32B parámetros activados. En las pruebas de referencia en categorías principales como razonamiento de conocimiento general, programación, matemáticas y agentes, el rendimiento del modelo K2 supera al de otros modelos de código abierto más extendidos."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 es un modelo de lenguaje de expertos mixtos a gran escala (MoE) desarrollado por la IA del lado oscuro de la luna, con un total de un billón de parámetros y 32 mil millones de parámetros activados por cada pasada hacia adelante. Está optimizado para capacidades de agente, incluyendo el uso avanzado de herramientas, razonamiento y síntesis de código."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "El producto asistente inteligente Kimi utiliza el último modelo grande de Kimi, que puede incluir características que aún no están estables. Soporta la comprensión de imágenes y seleccionará automáticamente el modelo de facturación de 8k/32k/128k según la longitud del contexto de la solicitud."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Los modelos ofrecidos por Ollama abarcan ampliamente áreas como la generación de código, cálculos matemáticos, procesamiento multilingüe e interacciones conversacionales, apoyando diversas necesidades de implementación empresarial y local."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud ofrece un servicio de inferencia alojado oficialmente, acceso inmediato a la biblioteca de modelos de Ollama y soporte para interfaces compatibles con OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI es una de las principales instituciones de investigación en inteligencia artificial a nivel mundial, cuyos modelos, como la serie GPT, están a la vanguardia del procesamiento del lenguaje natural. OpenAI se dedica a transformar múltiples industrias a través de soluciones de IA innovadoras y eficientes. Sus productos ofrecen un rendimiento y una rentabilidad significativos, siendo ampliamente utilizados en investigación, negocios y aplicaciones innovadoras."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 یک مدل پایه با معماری MoE است که دارای توانمندیهای بسیار قوی در حوزهٔ برنامهنویسی و عاملها (Agent) میباشد. مجموع پارامترها 1T و پارامترهای فعالشده 32B است. در آزمونهای بنچمارک در دستههای اصلی مانند استدلال دانش عمومی، برنامهنویسی، ریاضیات و Agent، عملکرد مدل K2 از سایر مدلهای متنباز مرسوم پیشی گرفته است."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 یک مدل زبان متخصص ترکیبی بزرگمقیاس (MoE) است که توسط هوش مصنوعی ماه تاریک توسعه یافته است، با مجموع ۱ تریلیون پارامتر و ۳۲ میلیارد پارامتر فعال در هر عبور رو به جلو. این مدل برای توانمندیهای نمایندگی بهینه شده است، از جمله استفاده پیشرفته از ابزارها، استدلال و ترکیب کد."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "محصول دستیار هوشمند کیمی از جدیدترین مدل بزرگ کیمی استفاده میکند و ممکن است شامل ویژگیهای ناپایدار باشد. از درک تصویر پشتیبانی میکند و بهطور خودکار بر اساس طول متن درخواست، مدلهای 8k/32k/128k را بهعنوان مدل محاسبه انتخاب میکند."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "مدلهای ارائهشده توسط Ollama طیف گستردهای از تولید کد، محاسبات ریاضی، پردازش چندزبانه و تعاملات گفتگویی را پوشش میدهند و از نیازهای متنوع استقرار در سطح سازمانی و محلی پشتیبانی میکنند."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud خدمات استنتاج میزبانی شده رسمی را ارائه میدهد که بهصورت آماده استفاده به کتابخانه مدلهای Ollama دسترسی میدهد و از رابطهای سازگار با OpenAI پشتیبانی میکند."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI یک موسسه پیشرو در تحقیقات هوش مصنوعی در سطح جهان است که مدلهایی مانند سری GPT را توسعه داده و مرزهای پردازش زبان طبیعی را پیش برده است. OpenAI متعهد به تغییر صنایع مختلف از طریق راهحلهای نوآورانه و کارآمد هوش مصنوعی است. محصولات آنها دارای عملکرد برجسته و اقتصادی بوده و به طور گسترده در تحقیقات، تجارت و کاربردهای نوآورانه استفاده میشوند."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 est un modèle de base à architecture MoE doté de capacités remarquables en programmation et en agents autonomes, avec 1T de paramètres au total et 32B de paramètres activés. Dans les principaux tests de référence couvrant le raisonnement général, la programmation, les mathématiques et les agents, le modèle K2 surpasse les autres modèles open source majeurs."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 est un modèle de langage à experts mixtes à grande échelle (MoE) développé par l'IA de la face cachée de la Lune, avec un total de 1 000 milliards de paramètres et 32 milliards de paramètres activés par passage avant. Il est optimisé pour les capacités d'agent, incluant l'utilisation avancée d'outils, le raisonnement et la synthèse de code."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Le produit d'assistant intelligent Kimi utilise le dernier modèle Kimi, qui peut inclure des fonctionnalités encore instables. Il prend en charge la compréhension des images et choisit automatiquement le modèle de facturation 8k/32k/128k en fonction de la longueur du contexte de la demande."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Les modèles proposés par Ollama couvrent largement des domaines tels que la génération de code, les calculs mathématiques, le traitement multilingue et les interactions conversationnelles, répondant à des besoins diversifiés pour le déploiement en entreprise et la localisation."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud offre un service d'inférence hébergé officiellement, permettant un accès prêt à l'emploi à la bibliothèque de modèles Ollama, avec prise en charge d'une interface compatible OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI est un institut de recherche en intelligence artificielle de premier plan au monde, dont les modèles, tels que la série GPT, font progresser les frontières du traitement du langage naturel. OpenAI s'engage à transformer plusieurs secteurs grâce à des solutions IA innovantes et efficaces. Leurs produits offrent des performances et une rentabilité remarquables, largement utilisés dans la recherche, le commerce et les applications innovantes."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 è un modello di base con architettura MoE che offre potenti capacità di programmazione e di agent, con 1T di parametri totali e 32B di parametri attivi. Nei benchmark delle principali categorie — ragionamento su conoscenze generali, programmazione, matematica e agent — il modello K2 supera gli altri modelli open source più diffusi."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 è un modello linguistico esperto ibrido su larga scala (MoE) sviluppato da Moon's Dark Side AI, con un totale di 1 trilione di parametri e 32 miliardi di parametri attivati per ogni passaggio in avanti. È ottimizzato per capacità di agente, inclusi l'uso avanzato di strumenti, il ragionamento e la sintesi del codice."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Il prodotto Kimi Smart Assistant utilizza il più recente modello Kimi, che potrebbe includere funzionalità non ancora stabili. Supporta la comprensione delle immagini e selezionerà automaticamente il modello di fatturazione 8k/32k/128k in base alla lunghezza del contesto della richiesta."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "I modelli forniti da Ollama coprono ampiamente aree come generazione di codice, operazioni matematiche, elaborazione multilingue e interazioni conversazionali, supportando esigenze diversificate per implementazioni aziendali e localizzate."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud offre un servizio di inferenza ospitato ufficialmente, con accesso immediato alla libreria di modelli Ollama e supporto per interfacce compatibili con OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI è un'agenzia di ricerca sull'intelligenza artificiale leader a livello globale, i cui modelli come la serie GPT hanno spinto in avanti il campo dell'elaborazione del linguaggio naturale. OpenAI si impegna a trasformare diversi settori attraverso soluzioni AI innovative ed efficienti. I loro prodotti offrono prestazioni e costi notevoli, trovando ampio utilizzo nella ricerca, nel commercio e nelle applicazioni innovative."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 は高度なコード処理能力とエージェント機能を備えた MoE(Mixture of Experts)アーキテクチャの基盤モデルで、総パラメータ数は1T、アクティブパラメータは32Bです。一般的な知識推論、プログラミング、数学、エージェントなどの主要カテゴリにおけるベンチマークで、K2モデルは他の主要なオープンソースモデルを上回る性能を示しています。"
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 は、月の裏側 AI によって開発された大規模混合専門家(MoE)言語モデルで、総パラメータ数は1兆、1回のフォワードパスで320億の活性化パラメータを持ちます。エージェント能力に最適化されており、高度なツール使用、推論、コード合成を含みます。"
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi スマートアシスタント製品は最新の Kimi 大モデルを使用しており、まだ安定していない機能が含まれている可能性があります。画像理解をサポートし、リクエストのコンテキストの長さに応じて 8k/32k/128k モデルを請求モデルとして自動的に選択します。"
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollamaが提供するモデルは、コード生成、数学演算、多言語処理、対話インタラクションなどの分野を広くカバーし、企業向けおよびローカライズされた展開の多様なニーズに対応しています。"
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud は公式にホストされた推論サービスを提供し、すぐに使える Ollama モデルライブラリへのアクセスと OpenAI 互換インターフェースをサポートします。"
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAIは、世界をリードする人工知能研究機関であり、GPTシリーズなどのモデルを開発し、自然言語処理の最前線を推進しています。OpenAIは、革新と効率的なAIソリューションを通じて、さまざまな業界を変革することに取り組んでいます。彼らの製品は、顕著な性能と経済性を持ち、研究、ビジネス、革新アプリケーションで広く使用されています。"
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2는 강력한 코드 처리 및 에이전트(Agent) 기능을 갖춘 MoE(혼합 전문가) 아키텍처 기반 모델로, 총 파라미터 수는 1T(1조), 활성화 파라미터는 32B(320억)입니다. 일반 지식 추론, 프로그래밍, 수학, 에이전트 등 주요 분야의 벤치마크 성능 테스트에서 K2 모델은 다른 주요 오픈 소스 모델들을 능가합니다."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2는 월면 AI가 개발한 대규모 혼합 전문가(MoE) 언어 모델로, 총 1조 개의 파라미터와 매 전방 전달 시 320억 개의 활성화 파라미터를 보유하고 있습니다. 이 모델은 고급 도구 사용, 추론 및 코드 합성을 포함한 에이전트 능력에 최적화되어 있습니다."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi 스마트 어시스턴트 제품은 최신 Kimi 대형 모델을 사용하며, 아직 안정되지 않은 기능이 포함될 수 있습니다. 이미지 이해를 지원하며, 요청의 맥락 길이에 따라 8k/32k/128k 모델을 청구 모델로 자동 선택합니다."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollama가 제공하는 모델은 코드 생성, 수학 연산, 다국어 처리 및 대화 상호작용 등 다양한 분야를 포괄하며, 기업급 및 로컬 배포의 다양한 요구를 지원합니다."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud는 공식 호스팅 추론 서비스를 제공하며, 즉시 사용 가능한 Ollama 모델 라이브러리에 접근할 수 있고 OpenAI 호환 인터페이스를 지원합니다."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI는 세계 최고의 인공지능 연구 기관으로, 개발한 모델인 GPT 시리즈는 자연어 처리의 최전선에서 혁신을 이끌고 있습니다. OpenAI는 혁신적이고 효율적인 AI 솔루션을 통해 여러 산업을 변화시키는 데 전념하고 있습니다. 그들의 제품은 뛰어난 성능과 경제성을 갖추고 있어 연구, 비즈니스 및 혁신적인 응용 프로그램에서 널리 사용됩니다."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 is een basismodel met een MoE-architectuur dat beschikt over zeer sterke codeer- en agentcapaciteiten. Het heeft in totaal 1T parameters en 32B actieve parameters. In benchmarktests op belangrijke categorieën zoals algemene kennisredenering, programmeren, wiskunde en agenttaken overtreft het K2-model de prestaties van andere gangbare open-sourcemodellen."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 is een grootschalig gemengd expertsysteem (MoE) taalmodel ontwikkeld door Moon's Dark Side AI, met in totaal 1 biljoen parameters en 32 miljard geactiveerde parameters per voorwaartse doorgang. Het is geoptimaliseerd voor agentcapaciteiten, waaronder geavanceerd gebruik van tools, redeneren en code-synthese."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi slimme assistent product maakt gebruik van het nieuwste Kimi grote model, dat mogelijk nog niet stabiele functies bevat. Ondersteunt beeldbegrip en kiest automatisch het 8k/32k/128k model als factureringsmodel op basis van de lengte van de context van het verzoek."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "De modellen van Ollama bestrijken een breed scala aan gebieden, waaronder codegeneratie, wiskundige berekeningen, meertalige verwerking en interactieve dialogen, en voldoen aan de diverse behoeften van bedrijfs- en lokale implementaties."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud biedt officieel beheerde inferentiediensten, waarmee u direct toegang krijgt tot de Ollama-modelbibliotheek en ondersteuning voor OpenAI-compatibele interfaces."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI is 's werelds toonaangevende onderzoeksinstituut op het gebied van kunstmatige intelligentie, wiens ontwikkelde modellen zoals de GPT-serie de grenzen van natuurlijke taalverwerking verleggen. OpenAI streeft ernaar verschillende industrieën te transformeren door middel van innovatieve en efficiënte AI-oplossingen. Hun producten bieden opmerkelijke prestaties en kosteneffectiviteit, en worden op grote schaal gebruikt in onderzoek, commercie en innovatieve toepassingen."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 to bazowy model z architekturą MoE, dysponujący wyjątkowymi możliwościami w zakresie kodowania i agentów, z łączną liczbą parametrów 1T oraz 32B parametrów aktywacyjnych. W standardowych testach wydajności (benchmarkach) dla głównych kategorii takich jak wnioskowanie z wiedzy ogólnej, programowanie, matematyka i agenty, model K2 przewyższa inne popularne otwarte modele."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 to duży, hybrydowy model ekspertowy (MoE) języka opracowany przez AI z Ciemnej Strony Księżyca, posiadający 1 bilion parametrów ogółem oraz 32 miliardy aktywowanych parametrów na pojedyncze przejście w przód. Model jest zoptymalizowany pod kątem zdolności agentowych, w tym zaawansowanego korzystania z narzędzi, wnioskowania i syntezy kodu."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Produkt Kimi Smart Assistant korzysta z najnowszego modelu Kimi, który może zawierać cechy jeszcze niestabilne. Obsługuje zrozumienie obrazów i automatycznie wybiera model 8k/32k/128k jako model rozliczeniowy w zależności od długości kontekstu żądania."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Modele oferowane przez Ollama obejmują szeroki zakres zastosowań, w tym generowanie kodu, obliczenia matematyczne, przetwarzanie wielojęzyczne i interakcje konwersacyjne, wspierając różnorodne potrzeby wdrożeń na poziomie przedsiębiorstw i lokalnych."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud oferuje oficjalnie hostowaną usługę inferencji, umożliwiającą natychmiastowy dostęp do biblioteki modeli Ollama oraz obsługę interfejsu zgodnego z OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI jest wiodącą na świecie instytucją badawczą w dziedzinie sztucznej inteligencji, której modele, takie jak seria GPT, przesuwają granice przetwarzania języka naturalnego. OpenAI dąży do zmiany wielu branż poprzez innowacyjne i efektywne rozwiązania AI. Ich produkty charakteryzują się znaczną wydajnością i opłacalnością, znajdując szerokie zastosowanie w badaniach, biznesie i innowacyjnych aplikacjach."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 é um modelo base com arquitetura MoE que oferece capacidades avançadas para programação e agentes, com 1T de parâmetros totais e 32B de parâmetros ativados. Em testes de benchmark nas principais categorias — raciocínio de conhecimento geral, programação, matemática e agentes — o desempenho do modelo K2 supera outros modelos de código aberto mais populares."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 é um modelo de linguagem de especialistas híbridos em larga escala (MoE) desenvolvido pela AI do Lado Escuro da Lua, com um total de 1 trilhão de parâmetros e 32 bilhões de parâmetros ativados por passagem. Ele é otimizado para capacidades de agente, incluindo uso avançado de ferramentas, raciocínio e síntese de código."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "O produto assistente inteligente Kimi utiliza o mais recente modelo Kimi, que pode conter recursos ainda não estáveis. Suporta compreensão de imagens e seleciona automaticamente o modelo de cobrança de 8k/32k/128k com base no comprimento do contexto da solicitação."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Os modelos oferecidos pela Ollama abrangem amplamente áreas como geração de código, operações matemáticas, processamento multilíngue e interações de diálogo, atendendo a diversas necessidades de implantação em nível empresarial e local."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud oferece um serviço de inferência hospedado oficialmente, com acesso imediato à biblioteca de modelos Ollama e suporte à interface compatível com OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI é uma das principais instituições de pesquisa em inteligência artificial do mundo, cujos modelos, como a série GPT, estão na vanguarda do processamento de linguagem natural. A OpenAI se dedica a transformar vários setores por meio de soluções de IA inovadoras e eficientes. Seus produtos apresentam desempenho e custo-benefício significativos, sendo amplamente utilizados em pesquisa, negócios e aplicações inovadoras."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 — это базовая модель архитектуры MoE с выдающимися возможностями в области программирования и агентов. Общий объём параметров — 1 трлн, активируемые параметры — 32 млрд. В бенчмарках по основным категориям (общее знание и рассуждение, программирование, математика, агенты и пр.) модель K2 демонстрирует результаты выше, чем у других ведущих открытых моделей."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 — это крупномасштабная смешанная экспертная (MoE) языковая модель, разработанная ИИ с обратной стороны Луны, с общим количеством параметров в 1 триллион и 32 миллиардами активных параметров на один проход вперёд. Она оптимизирована для агентских возможностей, включая продвинутое использование инструментов, рассуждения и синтез кода."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Продукт Kimi Smart Assistant использует последнюю модель Kimi, которая может содержать нестабильные функции. Поддерживает понимание изображений и автоматически выбирает модель 8k/32k/128k в качестве модели для выставления счетов в зависимости от длины контекста запроса."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Модели, предлагаемые Ollama, охватывают широкий спектр областей, включая генерацию кода, математические вычисления, многоязыковую обработку и диалоговое взаимодействие, поддерживая разнообразные потребности в развертывании на уровне предприятий и локализации."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud предоставляет официально управляемые сервисы вывода, обеспечивая мгновенный доступ к библиотеке моделей Ollama и поддерживая интерфейс, совместимый с OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI является ведущим мировым исследовательским институтом в области искусственного интеллекта, чьи модели, такие как серия GPT, продвигают границы обработки естественного языка. OpenAI стремится изменить множество отраслей с помощью инновационных и эффективных AI-решений. Их продукты обладают выдающимися характеристиками и экономичностью, широко используются в исследованиях, бизнесе и инновационных приложениях."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2, son derece güçlü kod yazma ve Agent yeteneklerine sahip MoE mimarisine dayanan bir temel modeldir; toplam parametre sayısı 1T, aktif (etkin) parametre sayısı 32B. Genel bilgi çıkarımı, programlama, matematik ve Agent gibi ana kategorilerde yapılan karşılaştırmalı performans testlerinde K2 modelinin performansı diğer önde gelen açık kaynak modellerinin üzerindedir."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2, Ay'ın Karanlık Yüzü AI tarafından geliştirilen, toplamda 1 trilyon parametreye ve her ileri geçişte 32 milyar aktif parametreye sahip büyük ölçekli bir Karışık Uzman (MoE) dil modelidir. Gelişmiş araç kullanımı, muhakeme ve kod sentezi dahil olmak üzere ajan yetenekleri için optimize edilmiştir."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi akıllı asistan ürünü, en son Kimi büyük modelini kullanır ve henüz kararlı olmayan özellikler içerebilir. Görüntü anlayışını desteklerken, isteğin bağlam uzunluğuna göre 8k/32k/128k modelini faturalama modeli olarak otomatik olarak seçecektir."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollama'nın sunduğu modeller, kod üretimi, matematiksel işlemler, çok dilli işleme ve diyalog etkileşimi gibi alanları kapsamaktadır. Kurumsal düzeyde ve yerelleştirilmiş dağıtım için çeşitli ihtiyaçları desteklemektedir."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud, resmi olarak barındırılan çıkarım hizmetleri sunar, kutudan çıkar çıkmaz Ollama model kütüphanesine erişim sağlar ve OpenAI uyumlu arayüzleri destekler."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI, dünya çapında lider bir yapay zeka araştırma kuruluşudur. Geliştirdiği modeller, GPT serisi gibi, doğal dil işleme alanında öncü adımlar atmaktadır. OpenAI, yenilikçi ve etkili yapay zeka çözümleri ile birçok sektörü dönüştürmeyi hedeflemektedir. Ürünleri, belirgin performans ve maliyet etkinliği ile araştırma, ticaret ve yenilikçi uygulamalarda yaygın olarak kullanılmaktadır."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 là một mô hình nền tảng kiến trúc MoE với khả năng xử lý mã và Agent rất mạnh, tổng số tham số 1T, tham số kích hoạt 32B. Trong các bài kiểm tra chuẩn về hiệu năng ở các hạng mục chính như suy luận kiến thức tổng quát, lập trình, toán học và Agent, mô hình K2 cho hiệu năng vượt trội so với các mô hình mã nguồn mở phổ biến khác."
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 là mô hình ngôn ngữ chuyên gia hỗn hợp quy mô lớn (MoE) do AI Mặt Trăng Tối phát triển, với tổng cộng 1 nghìn tỷ tham số và 32 tỷ tham số kích hoạt mỗi lần truyền tiến. Nó được tối ưu hóa cho khả năng đại lý, bao gồm sử dụng công cụ nâng cao, suy luận và tổng hợp mã."
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Sản phẩm trợ lý thông minh Kimi sử dụng mô hình lớn Kimi mới nhất, có thể chứa các tính năng chưa ổn định. Hỗ trợ hiểu hình ảnh, đồng thời tự động chọn mô hình 8k/32k/128k làm mô hình tính phí dựa trên độ dài ngữ cảnh yêu cầu."
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Mô hình do Ollama cung cấp bao quát rộng rãi các lĩnh vực như tạo mã, tính toán toán học, xử lý đa ngôn ngữ và tương tác đối thoại, hỗ trợ nhu cầu đa dạng cho triển khai doanh nghiệp và địa phương."
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud cung cấp dịch vụ suy luận được quản lý chính thức, truy cập ngay lập tức vào thư viện mô hình Ollama và hỗ trợ giao diện tương thích với OpenAI."
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI là tổ chức nghiên cứu trí tuệ nhân tạo hàng đầu thế giới, với các mô hình như dòng GPT đã thúc đẩy ranh giới của xử lý ngôn ngữ tự nhiên. OpenAI cam kết thay đổi nhiều ngành công nghiệp thông qua các giải pháp AI sáng tạo và hiệu quả. Sản phẩm của họ có hiệu suất và tính kinh tế nổi bật, được sử dụng rộng rãi trong nghiên cứu, thương mại và ứng dụng đổi mới."
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 是一款具备超强代码和 Agent 能力的 MoE 架构基础模型,总参数 1T,激活参数 32B。在通用知识推理、编程、数学、Agent 等主要类别的基准性能测试中,K2 模型的性能超过其他主流开源模型。"
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 是由月之暗面 AI 开发的大规模混合专家 (MoE) 语言模型,具有 1 万亿总参数和每次前向传递 320 亿激活参数。它针对代理能力进行了优化,包括高级工具使用、推理和代码合成。"
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi 智能助手产品使用最新的 Kimi 大模型,可能包含尚未稳定的特性。支持图片理解,同时会自动根据请求的上下文长度选择 8k/32k/128k 模型作为计费模型"
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollama 提供的模型广泛涵盖代码生成、数学运算、多语种处理和对话互动等领域,支持企业级和本地化部署的多样化需求。"
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud 提供官方托管的推理服务,开箱即用地访问 Ollama 模型库,并支持 OpenAI 兼容接口。"
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI 是全球领先的人工智能研究机构,其开发的模型如GPT系列推动了自然语言处理的前沿。OpenAI 致力于通过创新和高效的AI解决方案改变多个行业。他们的产品具有显著的性能和经济性,广泛用于研究、商业和创新应用。"
|
||||
},
|
||||
|
||||
@@ -1916,6 +1916,9 @@
|
||||
"kimi-k2-turbo-preview": {
|
||||
"description": "kimi-k2 是一款具備超強程式碼與 Agent 能力的 MoE 架構的基礎模型,總參數 1T,激活參數 32B。在通用知識推理、程式設計、數學與 Agent 等主要類別的基準效能測試中,K2 模型的表現超越其他主流開源模型。"
|
||||
},
|
||||
"kimi-k2:1t": {
|
||||
"description": "Kimi K2 是由月之暗面 AI 開發的大規模混合專家 (MoE) 語言模型,具有 1 兆總參數和每次前向傳遞 320 億激活參數。它針對代理能力進行了優化,包括高級工具使用、推理和程式碼合成。"
|
||||
},
|
||||
"kimi-latest": {
|
||||
"description": "Kimi 智能助手產品使用最新的 Kimi 大模型,可能包含尚未穩定的特性。支持圖片理解,同時會自動根據請求的上下文長度選擇 8k/32k/128k 模型作為計費模型"
|
||||
},
|
||||
|
||||
@@ -110,6 +110,9 @@
|
||||
"ollama": {
|
||||
"description": "Ollama 提供的模型廣泛涵蓋代碼生成、數學運算、多語種處理和對話互動等領域,支持企業級和本地化部署的多樣化需求。"
|
||||
},
|
||||
"ollamacloud": {
|
||||
"description": "Ollama Cloud 提供官方託管的推理服務,開箱即用地存取 Ollama 模型庫,並支援 OpenAI 相容介面。"
|
||||
},
|
||||
"openai": {
|
||||
"description": "OpenAI 是全球領先的人工智慧研究機構,其開發的模型如 GPT 系列推動了自然語言處理的前沿。OpenAI 致力於透過創新和高效的 AI 解決方案改變多個行業。他們的產品具有顯著的性能和經濟性,廣泛用於研究、商業和創新應用。"
|
||||
},
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lobehub/chat",
|
||||
"version": "1.132.15",
|
||||
"version": "1.132.19",
|
||||
"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.",
|
||||
"keywords": [
|
||||
"framework",
|
||||
@@ -122,7 +122,7 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@anthropic-ai/sdk": "^0.63.0",
|
||||
"@anthropic-ai/sdk": "^0.64.0",
|
||||
"@auth/core": "^0.40.0",
|
||||
"@aws-sdk/client-s3": "~3.893.0",
|
||||
"@aws-sdk/s3-request-presigner": "~3.893.0",
|
||||
@@ -360,6 +360,7 @@
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-mdx": "^3.1.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"require-in-the-middle": "^7.5.2",
|
||||
"semantic-release": "^21.1.2",
|
||||
"serwist": "^9.2.1",
|
||||
"stylelint": "^15.11.0",
|
||||
@@ -371,7 +372,7 @@
|
||||
"vite": "^7.1.5",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.17.0",
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Core types and interfaces
|
||||
export type * from './types';
|
||||
export * from './types';
|
||||
|
||||
// Base classes
|
||||
export { BaseProcessor } from './base/BaseProcessor';
|
||||
@@ -28,5 +28,14 @@ export {
|
||||
ToolMessageReorder,
|
||||
} from './processors';
|
||||
|
||||
// Constants
|
||||
export { PipelineError, ProcessorError, ProcessorType } from './types';
|
||||
// Tools Engine
|
||||
export type {
|
||||
FunctionCallChecker,
|
||||
GenerateToolsParams,
|
||||
PluginEnableChecker,
|
||||
ToolNameGenerator,
|
||||
ToolsEngineOptions,
|
||||
ToolsGenerationContext,
|
||||
ToolsGenerationResult,
|
||||
} from './tools';
|
||||
export { filterValidManifests, ToolsEngine, validateManifest } from './tools';
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
import debug from 'debug';
|
||||
|
||||
import {
|
||||
FunctionCallChecker,
|
||||
GenerateToolsParams,
|
||||
LobeChatPluginManifest,
|
||||
PluginEnableChecker,
|
||||
ToolsEngineOptions,
|
||||
ToolsGenerationContext,
|
||||
ToolsGenerationResult,
|
||||
UniformTool,
|
||||
} from './types';
|
||||
import { generateToolName } from './utils';
|
||||
|
||||
const log = debug('context-engine:tools-engine');
|
||||
|
||||
/**
|
||||
* Tools Engine - Unified processing of tools array construction and transformation
|
||||
*/
|
||||
export class ToolsEngine {
|
||||
private manifestSchemas: Map<string, LobeChatPluginManifest>;
|
||||
private enableChecker?: PluginEnableChecker;
|
||||
private functionCallChecker?: FunctionCallChecker;
|
||||
private defaultToolIds: string[];
|
||||
private options: ToolsEngineOptions;
|
||||
|
||||
constructor(options: ToolsEngineOptions) {
|
||||
this.options = options;
|
||||
this.defaultToolIds = options.defaultToolIds || [];
|
||||
log(
|
||||
'Initializing ToolsEngine with %d manifest schemas and %d default tools',
|
||||
options.manifestSchemas.length,
|
||||
this.defaultToolIds.length,
|
||||
);
|
||||
|
||||
// Convert manifest schemas to Map for improved lookup performance
|
||||
this.manifestSchemas = new Map(
|
||||
options.manifestSchemas.map((schema) => [schema.identifier, schema]),
|
||||
);
|
||||
this.enableChecker = options.enableChecker;
|
||||
this.functionCallChecker = options.functionCallChecker;
|
||||
|
||||
log(
|
||||
'ToolsEngine initialized with plugins: %o, default tools: %o',
|
||||
Array.from(this.manifestSchemas.keys()),
|
||||
this.defaultToolIds,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tools array
|
||||
* @param params Tools generation parameters
|
||||
* @returns Processed tools array, or undefined if tools should not be enabled
|
||||
*/
|
||||
generateTools(params: GenerateToolsParams): UniformTool[] | undefined {
|
||||
const { toolIds = [], model, provider, context } = params;
|
||||
|
||||
// Merge user-provided tool IDs with default tool IDs
|
||||
const allToolIds = [...toolIds, ...this.defaultToolIds];
|
||||
|
||||
log(
|
||||
'Generating tools for model=%s, provider=%s, pluginIds=%o (includes %d default tools)',
|
||||
model,
|
||||
provider,
|
||||
allToolIds,
|
||||
this.defaultToolIds.length,
|
||||
);
|
||||
|
||||
// 1. Check if model supports Function Calling
|
||||
if (!this.checkFunctionCallSupport(model, provider)) {
|
||||
log('Function calling not supported for model=%s, provider=%s', model, provider);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 2. Filter and validate plugins
|
||||
const { enabledManifests } = this.filterEnabledPlugins(allToolIds, model, provider, context);
|
||||
|
||||
// 3. If no tools available, return undefined
|
||||
if (enabledManifests.length === 0) {
|
||||
log('No enabled manifests found, returning undefined');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 4. Convert to UniformTool format
|
||||
const tools = this.convertManifestsToTools(enabledManifests);
|
||||
log('Generated %d tools from %d manifests', tools.length, enabledManifests.length);
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tools array (detailed version)
|
||||
* @param params Tools generation parameters
|
||||
* @returns Detailed tools generation result
|
||||
*/
|
||||
generateToolsDetailed(params: GenerateToolsParams): ToolsGenerationResult {
|
||||
const { toolIds = [], model, provider, context } = params;
|
||||
|
||||
// Merge user-provided tool IDs with default tool IDs
|
||||
const allToolIds = [...toolIds, ...this.defaultToolIds];
|
||||
|
||||
log(
|
||||
'Generating detailed tools for model=%s, provider=%s, pluginIds=%o (includes %d default tools)',
|
||||
model,
|
||||
provider,
|
||||
allToolIds,
|
||||
this.defaultToolIds.length,
|
||||
);
|
||||
|
||||
// Filter and validate plugins
|
||||
const { enabledManifests, filteredPlugins } = this.filterEnabledPlugins(
|
||||
allToolIds,
|
||||
model,
|
||||
provider,
|
||||
context,
|
||||
);
|
||||
|
||||
// Convert to UniformTool format
|
||||
const tools = this.convertManifestsToTools(enabledManifests);
|
||||
|
||||
log(
|
||||
'Generated detailed result: enabled=%d, filtered=%d, tools=%d',
|
||||
enabledManifests.length,
|
||||
filteredPlugins.length,
|
||||
tools.length,
|
||||
);
|
||||
|
||||
return {
|
||||
enabledToolIds: enabledManifests.map((m) => m.identifier),
|
||||
filteredTools: filteredPlugins,
|
||||
tools: tools.length > 0 ? tools : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model supports Function Calling
|
||||
*/
|
||||
private checkFunctionCallSupport(model: string, provider: string): boolean {
|
||||
if (this.functionCallChecker) {
|
||||
const result = this.functionCallChecker(model, provider);
|
||||
log('Function calling check result for %s/%s: %s', model, provider, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Default to assuming Function Calling is supported
|
||||
log('No function calling checker provided, defaulting to true');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter enabled plugins
|
||||
*/
|
||||
private filterEnabledPlugins(
|
||||
pluginIds: string[],
|
||||
model: string,
|
||||
provider: string,
|
||||
context?: ToolsGenerationContext,
|
||||
): {
|
||||
enabledManifests: LobeChatPluginManifest[];
|
||||
filteredPlugins: Array<{
|
||||
id: string;
|
||||
reason: 'not_found' | 'disabled' | 'incompatible';
|
||||
}>;
|
||||
} {
|
||||
const enabledManifests: LobeChatPluginManifest[] = [];
|
||||
const filteredPlugins: Array<{
|
||||
id: string;
|
||||
reason: 'not_found' | 'disabled' | 'incompatible';
|
||||
}> = [];
|
||||
|
||||
log('Filtering plugins: %o', pluginIds);
|
||||
|
||||
for (const pluginId of pluginIds) {
|
||||
const manifest = this.manifestSchemas.get(pluginId);
|
||||
|
||||
if (!manifest) {
|
||||
log('Plugin not found: %s', pluginId);
|
||||
filteredPlugins.push({ id: pluginId, reason: 'not_found' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use injected checker function or default check logic
|
||||
const isEnabled = this.enableChecker
|
||||
? this.enableChecker({
|
||||
context,
|
||||
manifest,
|
||||
model,
|
||||
pluginId,
|
||||
provider,
|
||||
})
|
||||
: this.defaultEnabledCheck();
|
||||
|
||||
if (isEnabled) {
|
||||
log('Plugin enabled: %s', pluginId);
|
||||
enabledManifests.push(manifest);
|
||||
} else {
|
||||
log('Plugin disabled: %s', pluginId);
|
||||
filteredPlugins.push({ id: pluginId, reason: 'disabled' });
|
||||
}
|
||||
}
|
||||
|
||||
log(
|
||||
'Filtering complete: enabled=%d, filtered=%d',
|
||||
enabledManifests.length,
|
||||
filteredPlugins.length,
|
||||
);
|
||||
return { enabledManifests, filteredPlugins };
|
||||
}
|
||||
|
||||
/**
|
||||
* Default enabled check logic
|
||||
*/
|
||||
private defaultEnabledCheck(): boolean {
|
||||
// Default to enabling all tools
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert manifests to UniformTool array
|
||||
*/
|
||||
private convertManifestsToTools(manifests: LobeChatPluginManifest[]): UniformTool[] {
|
||||
log('Converting %d manifests to tools', manifests.length);
|
||||
|
||||
// Use simplified conversion logic to avoid external package dependencies
|
||||
const tools = manifests.flatMap((manifest) =>
|
||||
manifest.api.map((api) => ({
|
||||
function: {
|
||||
description: api.description,
|
||||
name: this.generateToolName(manifest.identifier, api.name, manifest.type),
|
||||
parameters: api.parameters,
|
||||
},
|
||||
type: 'function' as const,
|
||||
})),
|
||||
);
|
||||
|
||||
log('Converted to %d tools', tools.length);
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tool calling name
|
||||
* Uses external generator if provided, otherwise uses default logic from utils
|
||||
*/
|
||||
private generateToolName(identifier: string, apiName: string, type?: string): string {
|
||||
// If external name generator is provided, use it
|
||||
if (this.options.generateToolName) {
|
||||
return this.options.generateToolName(identifier, apiName, type);
|
||||
}
|
||||
|
||||
// Use default tool name generation logic from utils
|
||||
return generateToolName(identifier, apiName, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的插件列表(用于调试和监控)
|
||||
*/
|
||||
getAvailablePlugins(): string[] {
|
||||
return Array.from(this.manifestSchemas.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查特定插件是否可用
|
||||
*/
|
||||
hasPlugin(pluginId: string): boolean {
|
||||
return this.manifestSchemas.has(pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件的 manifest
|
||||
*/
|
||||
getPluginManifest(pluginId: string): LobeChatPluginManifest | undefined {
|
||||
return this.manifestSchemas.get(pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新插件 manifest schemas(用于动态添加插件)
|
||||
*/
|
||||
updateManifestSchemas(manifestSchemas: LobeChatPluginManifest[]): void {
|
||||
this.manifestSchemas.clear();
|
||||
for (const schema of manifestSchemas) {
|
||||
this.manifestSchemas.set(schema.identifier, schema);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个插件 manifest
|
||||
*/
|
||||
addPluginManifest(manifest: LobeChatPluginManifest): void {
|
||||
this.manifestSchemas.set(manifest.identifier, manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除插件 manifest
|
||||
*/
|
||||
removePluginManifest(pluginId: string): boolean {
|
||||
return this.manifestSchemas.delete(pluginId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,814 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ToolsEngine } from '../ToolsEngine';
|
||||
import type { LobeChatPluginManifest } from '../types';
|
||||
|
||||
// Mock manifest schemas for testing
|
||||
const mockWebBrowsingManifest: LobeChatPluginManifest = {
|
||||
api: [
|
||||
{
|
||||
description: 'Search the web',
|
||||
name: 'search',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: 'lobe-web-browsing',
|
||||
meta: {
|
||||
title: 'Web Browsing',
|
||||
description: 'Browse the web',
|
||||
},
|
||||
type: 'builtin',
|
||||
};
|
||||
|
||||
const mockDalleManifest: LobeChatPluginManifest = {
|
||||
api: [
|
||||
{
|
||||
description: 'Generate images',
|
||||
name: 'generateImage',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prompt: { type: 'string', description: 'Image prompt' },
|
||||
},
|
||||
required: ['prompt'],
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: 'dalle',
|
||||
meta: {
|
||||
title: 'DALL-E',
|
||||
description: 'Generate images',
|
||||
},
|
||||
type: 'builtin',
|
||||
};
|
||||
|
||||
describe('ToolsEngine', () => {
|
||||
describe('constructor', () => {
|
||||
it('should initialize with manifest schemas', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
|
||||
});
|
||||
|
||||
expect(engine.hasPlugin('lobe-web-browsing')).toBe(true);
|
||||
expect(engine.hasPlugin('dalle')).toBe(true);
|
||||
expect(engine.hasPlugin('non-existent')).toBe(false);
|
||||
});
|
||||
|
||||
it('should store available plugins', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
|
||||
});
|
||||
|
||||
const availablePlugins = engine.getAvailablePlugins();
|
||||
expect(availablePlugins).toEqual(['lobe-web-browsing', 'dalle']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTools', () => {
|
||||
it('should return undefined when function calling is not supported', () => {
|
||||
const mockFunctionCallChecker = vi.fn().mockReturnValue(false);
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
functionCallChecker: mockFunctionCallChecker,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-3.5-turbo',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when no plugins are enabled', () => {
|
||||
const mockEnableChecker = vi.fn().mockReturnValue(false);
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
enableChecker: mockEnableChecker,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockEnableChecker).toHaveBeenCalledWith({
|
||||
pluginId: 'lobe-web-browsing',
|
||||
manifest: mockWebBrowsingManifest,
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
context: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate tools when plugins are enabled', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
enableChecker: () => true,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'lobe-web-browsing____search____builtin',
|
||||
description: 'Search the web',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should pass context to enable checker', () => {
|
||||
const mockEnableChecker = vi.fn().mockReturnValue(true);
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
enableChecker: mockEnableChecker,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const context = { isSearchEnabled: true };
|
||||
engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
context,
|
||||
});
|
||||
|
||||
expect(mockEnableChecker).toHaveBeenCalledWith({
|
||||
pluginId: 'lobe-web-browsing',
|
||||
manifest: mockWebBrowsingManifest,
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
context,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-existent plugins gracefully', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
enableChecker: () => true,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing', 'non-existent'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateToolsDetailed', () => {
|
||||
it('should return detailed results with filtered plugins', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
|
||||
enableChecker: ({ pluginId }) => pluginId === 'lobe-web-browsing',
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateToolsDetailed({
|
||||
toolIds: ['lobe-web-browsing', 'dalle', 'non-existent'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.enabledToolIds).toEqual(['lobe-web-browsing']);
|
||||
expect(result.filteredTools).toEqual([
|
||||
{ id: 'dalle', reason: 'disabled' },
|
||||
{ id: 'non-existent', reason: 'not_found' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('plugin management', () => {
|
||||
it('should allow adding new plugin manifest', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
});
|
||||
|
||||
expect(engine.hasPlugin('dalle')).toBe(false);
|
||||
|
||||
engine.addPluginManifest(mockDalleManifest);
|
||||
|
||||
expect(engine.hasPlugin('dalle')).toBe(true);
|
||||
expect(engine.getPluginManifest('dalle')).toBe(mockDalleManifest);
|
||||
});
|
||||
|
||||
it('should allow removing plugin manifest', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
|
||||
});
|
||||
|
||||
expect(engine.hasPlugin('dalle')).toBe(true);
|
||||
|
||||
const removed = engine.removePluginManifest('dalle');
|
||||
|
||||
expect(removed).toBe(true);
|
||||
expect(engine.hasPlugin('dalle')).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow updating all manifest schemas', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
});
|
||||
|
||||
expect(engine.getAvailablePlugins()).toEqual(['lobe-web-browsing']);
|
||||
|
||||
engine.updateManifestSchemas([mockDalleManifest]);
|
||||
|
||||
expect(engine.getAvailablePlugins()).toEqual(['dalle']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default behavior', () => {
|
||||
it('should use default enable checker when none provided', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should use default function call checker when none provided', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: [mockWebBrowsingManifest],
|
||||
enableChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['lobe-web-browsing'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ToolsEngine Integration Tests (migrated from enabledSchema)', () => {
|
||||
// Mock manifest data similar to the original tool selector tests
|
||||
const mockManifests: LobeChatPluginManifest[] = [
|
||||
{
|
||||
identifier: 'plugin-1',
|
||||
api: [{ name: 'api-1', description: 'API 1', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Plugin 1',
|
||||
description: 'Plugin 1 description',
|
||||
avatar: '🔧',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'plugin-2',
|
||||
api: [{ name: 'api-2', description: 'API 2', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Plugin 2',
|
||||
description: 'Plugin 2 description',
|
||||
avatar: '⚙️',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'plugin-3',
|
||||
api: [
|
||||
{
|
||||
name: 'api-3',
|
||||
description: '123123',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: { a: { type: 'string' } },
|
||||
},
|
||||
// This should not appear in the final result
|
||||
url: 'bac',
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: 'Plugin 3',
|
||||
description: 'Plugin 3 description',
|
||||
avatar: '🛠️',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'plugin-4',
|
||||
api: [{ name: 'api-4', description: 'API 4', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Plugin 4',
|
||||
description: 'Plugin 4 description',
|
||||
avatar: '🔩',
|
||||
},
|
||||
type: 'standalone',
|
||||
},
|
||||
{
|
||||
identifier: 'long-long-plugin-with-id',
|
||||
api: [
|
||||
{
|
||||
name: 'long-long-manifest-long-long-apiName',
|
||||
description: 'Long API',
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: 'Long Plugin',
|
||||
description: 'Long plugin description',
|
||||
avatar: '📏',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'builtin-1',
|
||||
api: [{ name: 'builtin-api-1', description: 'Builtin API 1', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Builtin 1',
|
||||
description: 'Builtin 1 description',
|
||||
avatar: '🏗️',
|
||||
},
|
||||
type: 'builtin',
|
||||
},
|
||||
];
|
||||
|
||||
const createTestEngine = () => {
|
||||
return new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: () => true, // Always allow function calls for tests
|
||||
});
|
||||
};
|
||||
|
||||
describe('basic tool generation', () => {
|
||||
it('should return correct tools array for multiple plugins', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1', 'plugin-2'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-1____api-1',
|
||||
description: 'API 1',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-2____api-2',
|
||||
description: 'API 2',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle standalone plugin type correctly', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-4'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-4____api-4____standalone',
|
||||
description: 'API 4',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle builtin plugin type correctly', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['builtin-1'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'builtin-1____builtin-api-1____builtin',
|
||||
description: 'Builtin API 1',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array when no plugins match', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: [],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle non-existent plugins gracefully', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['non-existent-plugin'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('long name handling (MD5 hash)', () => {
|
||||
it('should return MD5 hash for long API names', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['long-long-plugin-with-id'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result![0]).toEqual({
|
||||
type: 'function',
|
||||
function: {
|
||||
// The long action name should be hashed
|
||||
name: expect.stringMatching(/^long-long-plugin-with-id____MD5HASH_[a-f0-9]+$/),
|
||||
description: 'Long API',
|
||||
parameters: {},
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the specific hash matches expected value from original test
|
||||
expect(result![0].function.name).toContain('MD5HASH_');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parameter handling and filtering', () => {
|
||||
// fix https://github.com/lobehub/lobe-chat/issues/2036
|
||||
it('should not include URL field in function parameters', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-3'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result![0].function).toEqual({
|
||||
description: '123123',
|
||||
name: 'plugin-3____api-3',
|
||||
parameters: {
|
||||
properties: {
|
||||
a: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
});
|
||||
|
||||
// Ensure URL is not included
|
||||
expect(result![0].function).not.toHaveProperty('url');
|
||||
});
|
||||
|
||||
it('should preserve all other API properties correctly', () => {
|
||||
const engine = createTestEngine();
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-3'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
const func = result![0].function;
|
||||
expect(func.description).toBe('123123');
|
||||
expect(func.name).toBe('plugin-3____api-3');
|
||||
expect(func.parameters).toEqual({
|
||||
type: 'object',
|
||||
properties: { a: { type: 'string' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('function calling support', () => {
|
||||
it('should return undefined when function calling is not supported', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: () => false, // Disable function calls
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1'],
|
||||
model: 'gpt-3.5-turbo',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should respect function calling checker logic', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: (model: string) => model.includes('gpt-4'), // Only allow GPT-4
|
||||
});
|
||||
|
||||
// Should work with GPT-4
|
||||
const result1 = engine.generateTools({
|
||||
toolIds: ['plugin-1'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
expect(result1).toBeDefined();
|
||||
expect(result1).toHaveLength(1);
|
||||
|
||||
// Should not work with GPT-3.5
|
||||
const result2 = engine.generateTools({
|
||||
toolIds: ['plugin-1'],
|
||||
model: 'gpt-3.5-turbo',
|
||||
provider: 'openai',
|
||||
});
|
||||
expect(result2).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('plugin enable filtering', () => {
|
||||
it('should respect enable checker logic', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: () => true,
|
||||
enableChecker: ({ pluginId }) => pluginId === 'plugin-1', // Only enable plugin-1
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1', 'plugin-2'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result![0].function.name).toBe('plugin-1____api-1');
|
||||
});
|
||||
|
||||
it('should return undefined when no plugins are enabled', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: () => true,
|
||||
enableChecker: () => false, // Disable all plugins
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1', 'plugin-2'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('detailed generation results', () => {
|
||||
it('should provide detailed filtering information', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: mockManifests,
|
||||
functionCallChecker: () => true,
|
||||
enableChecker: ({ pluginId }) => pluginId === 'plugin-1',
|
||||
});
|
||||
|
||||
const result = engine.generateToolsDetailed({
|
||||
toolIds: ['plugin-1', 'plugin-2', 'non-existent'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result.tools).toHaveLength(1);
|
||||
expect(result.enabledToolIds).toEqual(['plugin-1']);
|
||||
expect(result.filteredTools).toEqual([
|
||||
{ id: 'plugin-2', reason: 'disabled' },
|
||||
{ id: 'non-existent', reason: 'not_found' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Migration tests: Demonstrating that ToolsEngine can replace enabledSchema
|
||||
* These tests show the equivalence between the old selector-based approach
|
||||
* and the new ToolsEngine-based approach
|
||||
*/
|
||||
describe('enabledSchema Migration to ToolsEngine', () => {
|
||||
// Sample manifest data that mimics the old toolSelectors test data
|
||||
const sampleManifests: LobeChatPluginManifest[] = [
|
||||
{
|
||||
identifier: 'plugin-1',
|
||||
api: [{ name: 'api-1', description: 'API 1', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Plugin 1',
|
||||
description: 'Plugin 1 description',
|
||||
avatar: '🔧',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'plugin-2',
|
||||
api: [{ name: 'api-2', description: 'API 2', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Plugin 2',
|
||||
description: 'Plugin 2 description',
|
||||
avatar: '⚙️',
|
||||
},
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
identifier: 'standalone-plugin',
|
||||
api: [{ name: 'standalone-api', description: 'Standalone API', parameters: {} }],
|
||||
meta: {
|
||||
title: 'Standalone Plugin',
|
||||
description: 'Standalone plugin description',
|
||||
avatar: '🔩',
|
||||
},
|
||||
type: 'standalone',
|
||||
},
|
||||
];
|
||||
|
||||
describe('basic functionality comparison', () => {
|
||||
it('should generate the same tool names as enabledSchema did', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: sampleManifests,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1', 'plugin-2'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
// These should match the format that enabledSchema produced
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-1____api-1',
|
||||
description: 'API 1',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-2____api-2',
|
||||
description: 'API 2',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle type suffixes the same way enabledSchema did', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: sampleManifests,
|
||||
functionCallChecker: () => true,
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['standalone-plugin'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result![0].function.name).toBe('standalone-plugin____standalone-api____standalone');
|
||||
});
|
||||
});
|
||||
|
||||
describe('advantages of ToolsEngine over enabledSchema', () => {
|
||||
it('provides detailed filtering information that enabledSchema could not', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: sampleManifests,
|
||||
functionCallChecker: () => true,
|
||||
enableChecker: ({ pluginId }) => pluginId === 'plugin-1',
|
||||
});
|
||||
|
||||
const result = engine.generateToolsDetailed({
|
||||
toolIds: ['plugin-1', 'plugin-2', 'non-existent'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
tools: [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-1____api-1',
|
||||
description: 'API 1',
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
enabledToolIds: ['plugin-1'],
|
||||
filteredTools: [
|
||||
{ id: 'plugin-2', reason: 'disabled' },
|
||||
{ id: 'non-existent', reason: 'not_found' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('supports function calling checks that enabledSchema relied on external logic for', () => {
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: sampleManifests,
|
||||
functionCallChecker: (model: string) => model.includes('gpt-4'),
|
||||
});
|
||||
|
||||
// Should work with GPT-4
|
||||
const result1 = engine.generateTools({
|
||||
toolIds: ['plugin-1'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
expect(result1).toBeDefined();
|
||||
|
||||
// Should not work with older models
|
||||
const result2 = engine.generateTools({
|
||||
toolIds: ['plugin-1'],
|
||||
model: 'gpt-3.5-turbo',
|
||||
provider: 'openai',
|
||||
});
|
||||
expect(result2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('encapsulates all tool generation logic in one place', () => {
|
||||
// This demonstrates that ToolsEngine combines:
|
||||
// 1. Manifest filtering (what enabledSchema did)
|
||||
// 2. Function calling support checks (what prepareTools did)
|
||||
// 3. Tool name generation (what genToolCallingName did)
|
||||
// 4. Plugin enable checking (custom logic)
|
||||
|
||||
const engine = new ToolsEngine({
|
||||
manifestSchemas: sampleManifests,
|
||||
functionCallChecker: (model: string, provider: string) => {
|
||||
return model.includes('gpt') && provider === 'openai';
|
||||
},
|
||||
enableChecker: ({ pluginId, model }) => {
|
||||
// Custom business logic that was scattered before
|
||||
if (model === 'gpt-3.5-turbo') return false;
|
||||
return pluginId !== 'plugin-2'; // Skip plugin-2 for demo
|
||||
},
|
||||
});
|
||||
|
||||
const result = engine.generateTools({
|
||||
toolIds: ['plugin-1', 'plugin-2'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
// Only plugin-1 should be enabled due to custom logic
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result![0].function.name).toBe('plugin-1____api-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { generateToolName } from '../utils';
|
||||
|
||||
describe('generateToolName', () => {
|
||||
describe('basic functionality', () => {
|
||||
it('should generate tool name with identifier and api name', () => {
|
||||
const result = generateToolName('test-plugin', 'myAction');
|
||||
expect(result).toBe('test-plugin____myAction');
|
||||
});
|
||||
|
||||
it('should generate tool name with type suffix', () => {
|
||||
const result = generateToolName('test-plugin', 'myAction', 'builtin');
|
||||
expect(result).toBe('test-plugin____myAction____builtin');
|
||||
});
|
||||
|
||||
it('should handle default type', () => {
|
||||
const result = generateToolName('test-plugin', 'myAction', 'default');
|
||||
expect(result).toBe('test-plugin____myAction');
|
||||
});
|
||||
|
||||
it('should handle undefined type as default', () => {
|
||||
const result = generateToolName('test-plugin', 'myAction');
|
||||
expect(result).toBe('test-plugin____myAction');
|
||||
});
|
||||
});
|
||||
|
||||
describe('long name handling', () => {
|
||||
it('should shorten long action names using hash', () => {
|
||||
// Create a normal identifier with a very long action name
|
||||
const identifier = 'my-plugin';
|
||||
const longActionName = 'very-long-action-name-that-will-cause-the-total-length-to-exceed-64-characters';
|
||||
const result = generateToolName(identifier, longActionName, 'builtin');
|
||||
|
||||
// The result should be shorter than the original would have been
|
||||
const originalLength = `${identifier}____${longActionName}____builtin`.length;
|
||||
expect(result.length).toBeLessThan(originalLength);
|
||||
|
||||
// Should contain the identifier, MD5HASH prefix, and type
|
||||
expect(result).toContain(identifier);
|
||||
expect(result).toContain('MD5HASH_');
|
||||
expect(result).toContain('____builtin');
|
||||
expect(result).toMatch(/^my-plugin____MD5HASH_[a-f0-9]+____builtin$/);
|
||||
});
|
||||
|
||||
it('should handle identifier that is itself long', () => {
|
||||
// Test the original limitation - when identifier itself is very long
|
||||
const veryLongIdentifier = 'very-long-plugin-identifier-that-will-cause-overflow';
|
||||
const actionName = 'action';
|
||||
const result = generateToolName(veryLongIdentifier, actionName, 'builtin');
|
||||
|
||||
// When the total length exceeds 64, even short action names get hashed
|
||||
expect(result).toContain(veryLongIdentifier);
|
||||
expect(result).toContain('MD5HASH_');
|
||||
expect(result).toContain('____builtin');
|
||||
|
||||
// Verify the pattern matches the expected format
|
||||
expect(result).toMatch(new RegExp(`^${veryLongIdentifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}____MD5HASH_[a-f0-9]+____builtin$`));
|
||||
});
|
||||
|
||||
it('should keep short names unchanged', () => {
|
||||
const result = generateToolName('short', 'action', 'type');
|
||||
expect(result).toBe('short____action____type');
|
||||
expect(result.length).toBeLessThan(64);
|
||||
});
|
||||
|
||||
it('should handle edge case at exactly 64 characters', () => {
|
||||
// Create a name that's exactly 64 characters
|
||||
const identifier = 'a'.repeat(20);
|
||||
const actionName = 'b'.repeat(20);
|
||||
const type = 'c'.repeat(16); // 20 + 4 + 20 + 4 + 16 = 64
|
||||
|
||||
const result = generateToolName(identifier, actionName, type);
|
||||
|
||||
// Should be shortened because it's >= 64
|
||||
expect(result.length).toBeLessThan(64);
|
||||
expect(result).toContain('MD5HASH_');
|
||||
});
|
||||
});
|
||||
|
||||
describe('special characters and edge cases', () => {
|
||||
it('should handle identifiers with special characters', () => {
|
||||
const result = generateToolName('my-plugin_v2', 'action-name', 'builtin');
|
||||
expect(result).toBe('my-plugin_v2____action-name____builtin');
|
||||
});
|
||||
|
||||
it('should handle empty action name', () => {
|
||||
const result = generateToolName('plugin', '', 'builtin');
|
||||
expect(result).toBe('plugin________builtin');
|
||||
});
|
||||
|
||||
it('should handle numeric identifiers and action names', () => {
|
||||
const result = generateToolName('plugin123', 'action456', 'type789');
|
||||
expect(result).toBe('plugin123____action456____type789');
|
||||
});
|
||||
|
||||
it('should be consistent for same inputs', () => {
|
||||
const result1 = generateToolName('plugin', 'action', 'type');
|
||||
const result2 = generateToolName('plugin', 'action', 'type');
|
||||
expect(result1).toBe(result2);
|
||||
});
|
||||
|
||||
it('should produce different results for different inputs', () => {
|
||||
const result1 = generateToolName('plugin1', 'action', 'type');
|
||||
const result2 = generateToolName('plugin2', 'action', 'type');
|
||||
expect(result1).not.toBe(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hash consistency', () => {
|
||||
it('should generate consistent hash for same long action name', () => {
|
||||
const identifier = 'plugin';
|
||||
const longActionName = 'very-long-action-name-that-will-also-cause-overflow';
|
||||
|
||||
const result1 = generateToolName(identifier, longActionName, 'builtin');
|
||||
const result2 = generateToolName(identifier, longActionName, 'builtin');
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
expect(result1).toContain('MD5HASH_');
|
||||
});
|
||||
|
||||
it('should generate different hashes for different long action names', () => {
|
||||
const identifier = 'plugin';
|
||||
const longActionName1 = 'very-long-action-name-that-will-also-cause-overflow-1';
|
||||
const longActionName2 = 'very-long-action-name-that-will-also-cause-overflow-2';
|
||||
|
||||
const result1 = generateToolName(identifier, longActionName1, 'builtin');
|
||||
const result2 = generateToolName(identifier, longActionName2, 'builtin');
|
||||
|
||||
expect(result1).not.toBe(result2);
|
||||
expect(result1).toContain('MD5HASH_');
|
||||
expect(result2).toContain('MD5HASH_');
|
||||
});
|
||||
});
|
||||
|
||||
describe('real-world examples', () => {
|
||||
it('should handle builtin tools correctly', () => {
|
||||
const result = generateToolName('lobe-image-designer', 'text2image', 'builtin');
|
||||
expect(result).toBe('lobe-image-designer____text2image____builtin');
|
||||
});
|
||||
|
||||
it('should handle web browsing tools correctly', () => {
|
||||
const result = generateToolName('lobe-web-browsing', 'search');
|
||||
expect(result).toBe('lobe-web-browsing____search');
|
||||
});
|
||||
|
||||
it('should handle plugin tools correctly', () => {
|
||||
const result = generateToolName('custom-plugin', 'customAction', 'plugin');
|
||||
expect(result).toBe('custom-plugin____customAction____plugin');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { LobeChatPluginManifest } from '../types';
|
||||
import { filterValidManifests, validateManifest } from '../utils';
|
||||
|
||||
// Mock manifest schemas
|
||||
const mockBuiltinManifest: LobeChatPluginManifest = {
|
||||
api: [
|
||||
{
|
||||
description: 'Built-in tool',
|
||||
name: 'builtinAction',
|
||||
parameters: { type: 'object', properties: {} },
|
||||
},
|
||||
],
|
||||
identifier: 'builtin-tool',
|
||||
meta: { title: 'Builtin Tool' },
|
||||
type: 'builtin',
|
||||
};
|
||||
|
||||
describe('utils', () => {
|
||||
describe('validateManifest', () => {
|
||||
it('should validate correct manifest', () => {
|
||||
const result = validateManifest(mockBuiltinManifest);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject manifest without identifier', () => {
|
||||
const invalidManifest = { ...mockBuiltinManifest, identifier: undefined };
|
||||
|
||||
const result = validateManifest(invalidManifest);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject manifest without api', () => {
|
||||
const invalidManifest = { ...mockBuiltinManifest, api: undefined };
|
||||
|
||||
const result = validateManifest(invalidManifest);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject manifest with empty api array', () => {
|
||||
const invalidManifest = { ...mockBuiltinManifest, api: [] };
|
||||
|
||||
const result = validateManifest(invalidManifest);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject non-object manifests', () => {
|
||||
expect(validateManifest(null)).toBe(false);
|
||||
expect(validateManifest(undefined)).toBe(false);
|
||||
expect(validateManifest('string')).toBe(false);
|
||||
expect(validateManifest(123)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterValidManifests', () => {
|
||||
it('should separate valid and invalid manifests', () => {
|
||||
const validManifest = mockBuiltinManifest;
|
||||
const invalidManifest = { identifier: 'invalid' }; // missing api
|
||||
|
||||
const result = filterValidManifests([validManifest, invalidManifest]);
|
||||
|
||||
expect(result.valid).toEqual([validManifest]);
|
||||
expect(result.invalid).toEqual([invalidManifest]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// Core ToolsEngine class
|
||||
export { ToolsEngine } from './ToolsEngine';
|
||||
|
||||
// Types and interfaces
|
||||
export type {
|
||||
FunctionCallChecker,
|
||||
GenerateToolsParams,
|
||||
PluginEnableChecker,
|
||||
ToolNameGenerator,
|
||||
ToolsEngineOptions,
|
||||
ToolsGenerationContext,
|
||||
ToolsGenerationResult,
|
||||
} from './types';
|
||||
|
||||
// Utility functions
|
||||
export { filterValidManifests, generateToolName, validateManifest } from './utils';
|
||||
@@ -0,0 +1,130 @@
|
||||
export interface LobeChatPluginApi {
|
||||
description: string;
|
||||
name: string;
|
||||
parameters: Record<string, any>;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface LobeChatPluginManifest {
|
||||
api: LobeChatPluginApi[];
|
||||
identifier: string;
|
||||
meta: any;
|
||||
systemRole?: string;
|
||||
type?: 'default' | 'standalone' | 'markdown' | 'mcp' | 'builtin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools generation context
|
||||
*/
|
||||
export interface ToolsGenerationContext {
|
||||
/** Additional extension context */
|
||||
[key: string]: any;
|
||||
/** Whether image generation is allowed */
|
||||
allowImageGeneration?: boolean;
|
||||
/** Environment information */
|
||||
environment?: 'desktop' | 'web';
|
||||
/** Whether search is enabled */
|
||||
isSearchEnabled?: boolean;
|
||||
/** Model name for context-aware plugin filtering */
|
||||
model?: string;
|
||||
/** Provider name for context-aware plugin filtering */
|
||||
provider?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin enable checker function
|
||||
*/
|
||||
export type PluginEnableChecker = (params: {
|
||||
context?: ToolsGenerationContext;
|
||||
manifest: LobeChatPluginManifest;
|
||||
model: string;
|
||||
pluginId: string;
|
||||
provider: string;
|
||||
}) => boolean;
|
||||
|
||||
/**
|
||||
* Function calling support checker function
|
||||
*/
|
||||
export type FunctionCallChecker = (model: string, provider: string) => boolean;
|
||||
|
||||
/**
|
||||
* Tools generation parameters
|
||||
*/
|
||||
export interface GenerateToolsParams {
|
||||
/** Additional context information */
|
||||
context?: ToolsGenerationContext;
|
||||
/** Model name */
|
||||
model: string;
|
||||
/** Provider name */
|
||||
provider: string;
|
||||
/** List of tool IDs to enable */
|
||||
toolIds?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool name generator function
|
||||
*/
|
||||
export type ToolNameGenerator = (identifier: string, apiName: string, type?: string) => string;
|
||||
|
||||
/**
|
||||
* ToolsEngine configuration options
|
||||
*/
|
||||
export interface ToolsEngineOptions {
|
||||
/** Default tool IDs that will always be added to the end of the tools list */
|
||||
defaultToolIds?: string[];
|
||||
/** Optional plugin enable checker function */
|
||||
enableChecker?: PluginEnableChecker;
|
||||
/** Optional function calling support checker function */
|
||||
functionCallChecker?: FunctionCallChecker;
|
||||
/** Optional tool name generator function */
|
||||
generateToolName?: ToolNameGenerator;
|
||||
/** Statically injected manifest schemas */
|
||||
manifestSchemas: LobeChatPluginManifest[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools generation result
|
||||
*/
|
||||
export interface ToolsGenerationResult {
|
||||
/** List of enabled tool IDs */
|
||||
enabledToolIds: string[];
|
||||
/** Filtered plugins and their reasons */
|
||||
filteredTools: Array<{
|
||||
id: string;
|
||||
reason: 'not_found' | 'disabled' | 'incompatible';
|
||||
}>;
|
||||
/** Generated tools array */
|
||||
tools?: UniformTool[];
|
||||
}
|
||||
|
||||
export interface UniformFunctions {
|
||||
/**
|
||||
* The description of what the function does.
|
||||
* @type {string}
|
||||
* @memberof UniformFunctions
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
|
||||
* @type {string}
|
||||
* @memberof UniformFunctions
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
|
||||
* @type {{ [key: string]: any }}
|
||||
* @memberof UniformFunctions
|
||||
*/
|
||||
parameters?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UniformTool {
|
||||
function: UniformFunctions;
|
||||
|
||||
/**
|
||||
* The type of the tool. Currently, only `function` is supported.
|
||||
*/
|
||||
type: 'function';
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { LobeChatPluginManifest } from './types';
|
||||
|
||||
// Tool naming constants
|
||||
const PLUGIN_SCHEMA_SEPARATOR = '____';
|
||||
const PLUGIN_SCHEMA_API_MD5_PREFIX = 'MD5HASH_';
|
||||
|
||||
/**
|
||||
* Simple hash function for tool name shortening
|
||||
*/
|
||||
const genToolCallShortMD5Hash = (name: string): string => {
|
||||
// Simple hash function for tool names (fallback if no crypto available)
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
const char = name.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash).toString(16).slice(0, 16);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate tool calling name
|
||||
* Default tool name generation logic (copied from @lobechat/utils)
|
||||
*/
|
||||
export const generateToolName = (identifier: string, name: string, type: string = 'default'): string => {
|
||||
const pluginType = type && type !== 'default' ? `${PLUGIN_SCHEMA_SEPARATOR + type}` : '';
|
||||
|
||||
// Use plugin identifier as prefix to avoid conflicts
|
||||
let apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + name + pluginType;
|
||||
|
||||
// OpenAI GPT function_call name can't be longer than 64 characters
|
||||
// So we need to use hash to shorten the name
|
||||
// and then find the correct apiName in response by hash
|
||||
if (apiName.length >= 64) {
|
||||
const hashContent = PLUGIN_SCHEMA_API_MD5_PREFIX + genToolCallShortMD5Hash(name);
|
||||
apiName = identifier + PLUGIN_SCHEMA_SEPARATOR + hashContent + pluginType;
|
||||
}
|
||||
|
||||
return apiName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate manifest schema structure
|
||||
*/
|
||||
export function validateManifest(manifest: any): manifest is LobeChatPluginManifest {
|
||||
return Boolean(
|
||||
manifest &&
|
||||
typeof manifest === 'object' &&
|
||||
typeof manifest.identifier === 'string' &&
|
||||
Array.isArray(manifest.api) &&
|
||||
manifest.api.length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter valid manifest schemas
|
||||
*/
|
||||
export function filterValidManifests(manifestSchemas: any[]): {
|
||||
invalid: any[];
|
||||
valid: LobeChatPluginManifest[];
|
||||
} {
|
||||
const valid: LobeChatPluginManifest[] = [];
|
||||
const invalid: any[] = [];
|
||||
|
||||
for (const manifest of manifestSchemas) {
|
||||
if (validateManifest(manifest)) {
|
||||
valid.push(manifest);
|
||||
} else {
|
||||
invalid.push(manifest);
|
||||
}
|
||||
}
|
||||
|
||||
return { invalid, valid };
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "agents" ADD COLUMN IF NOT EXISTS "virtual" boolean DEFAULT false;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -245,6 +245,13 @@
|
||||
"when": 1758825944181,
|
||||
"tag": "0034_fix_chat_group",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"version": "7",
|
||||
"when": 1759116400580,
|
||||
"tag": "0035_add_virtual",
|
||||
"breakpoints": true
|
||||
}
|
||||
],
|
||||
"version": "6"
|
||||
|
||||
@@ -626,5 +626,11 @@
|
||||
"bps": true,
|
||||
"folderMillis": 1758825944181,
|
||||
"hash": "1ba9b1f74ea13348da98d6fcdad7867ab4316ed565bf75d84d160c526cdac14b"
|
||||
},
|
||||
{
|
||||
"sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"],
|
||||
"bps": true,
|
||||
"folderMillis": 1759116400580,
|
||||
"hash": "433ddae88e785f2db734e49a4c115eee93e60afe389f7919d66e5ba9aa159a37"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -55,6 +55,8 @@ export const agents = pgTable(
|
||||
systemRole: text('system_role'),
|
||||
tts: jsonb('tts').$type<LobeAgentTTSConfig>(),
|
||||
|
||||
virtual: boolean('virtual').default(false),
|
||||
|
||||
openingMessage: text('opening_message'),
|
||||
openingQuestions: text('opening_questions').array().default([]),
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"./novita": "./src/aiModels/novita.ts",
|
||||
"./nvidia": "./src/aiModels/nvidia.ts",
|
||||
"./ollama": "./src/aiModels/ollama.ts",
|
||||
"./ollamacloud": "./src/aiModels/ollamacloud.ts",
|
||||
"./openai": "./src/aiModels/openai.ts",
|
||||
"./openrouter": "./src/aiModels/openrouter.ts",
|
||||
"./perplexity": "./src/aiModels/perplexity.ts",
|
||||
|
||||
@@ -36,6 +36,7 @@ import { default as newapi } from './newapi';
|
||||
import { default as novita } from './novita';
|
||||
import { default as nvidia } from './nvidia';
|
||||
import { default as ollama } from './ollama';
|
||||
import { default as ollamacloud } from './ollamacloud';
|
||||
import { default as openai } from './openai';
|
||||
import { default as openrouter } from './openrouter';
|
||||
import { default as perplexity } from './perplexity';
|
||||
@@ -120,6 +121,7 @@ export const LOBE_DEFAULT_MODEL_LIST = buildDefaultModelList({
|
||||
novita,
|
||||
nvidia,
|
||||
ollama,
|
||||
ollamacloud,
|
||||
openai,
|
||||
openrouter,
|
||||
perplexity,
|
||||
@@ -186,6 +188,7 @@ export { default as newapi } from './newapi';
|
||||
export { default as novita } from './novita';
|
||||
export { default as nvidia } from './nvidia';
|
||||
export { default as ollama } from './ollama';
|
||||
export { default as ollamacloud } from './ollamacloud';
|
||||
export { gptImage1ParamsSchema, default as openai, openaiChatModels } from './openai';
|
||||
export { default as openrouter } from './openrouter';
|
||||
export { default as perplexity } from './perplexity';
|
||||
|
||||
@@ -10,7 +10,6 @@ const ollamaChatModels: AIChatModelCard[] = [
|
||||
description:
|
||||
'DeepSeek V3.1:下一代推理模型,提升了复杂推理与链路思考能力,适合需要深入分析的任务。',
|
||||
displayName: 'DeepSeek V3.1',
|
||||
enabled: true,
|
||||
id: 'deepseek-v3.1:671b',
|
||||
type: 'chat',
|
||||
},
|
||||
@@ -23,7 +22,6 @@ const ollamaChatModels: AIChatModelCard[] = [
|
||||
description:
|
||||
'GPT-OSS 20B 是 OpenAI 发布的开源大语言模型,采用 MXFP4 量化技术,适合在高端消费级GPU或Apple Silicon Mac上运行。该模型在对话生成、代码编写和推理任务方面表现出色,支持函数调用和工具使用。',
|
||||
displayName: 'GPT-OSS 20B',
|
||||
enabled: true,
|
||||
id: 'gpt-oss:20b',
|
||||
releasedAt: '2025-08-05',
|
||||
type: 'chat',
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { AIChatModelCard } from '../types/aiModel';
|
||||
|
||||
const ollamaCloudModels: AIChatModelCard[] = [
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
},
|
||||
contextWindowTokens: 163_840,
|
||||
description:
|
||||
'DeepSeek V3.1:下一代推理模型,提升了复杂推理与链路思考能力,适合需要深入分析的任务。',
|
||||
displayName: 'DeepSeek V3.1',
|
||||
enabled: true,
|
||||
id: 'deepseek-v3.1:671b',
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
},
|
||||
contextWindowTokens: 131_072,
|
||||
description:
|
||||
'GPT-OSS 20B 是 OpenAI 发布的开源大语言模型,采用 MXFP4 量化技术,适合在高端消费级GPU或Apple Silicon Mac上运行。该模型在对话生成、代码编写和推理任务方面表现出色,支持函数调用和工具使用。',
|
||||
displayName: 'GPT-OSS 20B',
|
||||
id: 'gpt-oss:20b',
|
||||
releasedAt: '2025-08-05',
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
},
|
||||
contextWindowTokens: 131_072,
|
||||
description:
|
||||
'GPT-OSS 120B 是 OpenAI 发布的大型开源语言模型,采用 MXFP4 量化技术,为旗舰级模型。需要多GPU或高性能工作站环境运行,在复杂推理、代码生成和多语言处理方面具备卓越性能,支持高级函数调用和工具集成。',
|
||||
displayName: 'GPT-OSS 120B',
|
||||
id: 'gpt-oss:120b',
|
||||
releasedAt: '2025-08-05',
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
reasoning: true,
|
||||
},
|
||||
contextWindowTokens: 131_072,
|
||||
description:
|
||||
'Kimi K2 是由月之暗面 AI 开发的大规模混合专家 (MoE) 语言模型,具有 1 万亿总参数和每次前向传递 320 亿激活参数。它针对代理能力进行了优化,包括高级工具使用、推理和代码合成。',
|
||||
displayName: 'Kimi K2',
|
||||
id: 'kimi-k2:1t',
|
||||
type: 'chat',
|
||||
},
|
||||
{
|
||||
abilities: {
|
||||
functionCall: true,
|
||||
},
|
||||
contextWindowTokens: 262_144,
|
||||
description:
|
||||
'阿里巴巴针对代理和编码任务的高性能长上下文模型。',
|
||||
displayName: 'Qwen3 Coder 480B',
|
||||
id: 'qwen3-coder:480b',
|
||||
type: 'chat',
|
||||
},
|
||||
];
|
||||
|
||||
export const allModels = [...ollamaCloudModels];
|
||||
|
||||
export default allModels;
|
||||
@@ -37,6 +37,7 @@ export enum ModelProvider {
|
||||
Novita = 'novita',
|
||||
Nvidia = 'nvidia',
|
||||
Ollama = 'ollama',
|
||||
OllamaCloud = 'ollamacloud',
|
||||
OpenAI = 'openai',
|
||||
OpenRouter = 'openrouter',
|
||||
PPIO = 'ppio',
|
||||
|
||||
@@ -19,6 +19,7 @@ export { LobeMoonshotAI } from './providers/moonshot';
|
||||
export { LobeNebiusAI } from './providers/nebius';
|
||||
export { LobeNewAPIAI } from './providers/newapi';
|
||||
export { LobeOllamaAI } from './providers/ollama';
|
||||
export { LobeOllamaCloudAI } from './providers/ollamacloud';
|
||||
export { LobeOpenAI } from './providers/openai';
|
||||
export { LobeOpenRouterAI } from './providers/openrouter';
|
||||
export { LobePerplexityAI } from './providers/perplexity';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ModelProvider } from 'model-bank';
|
||||
|
||||
import { createOpenAICompatibleRuntime } from '../../core/openaiCompatibleFactory';
|
||||
import { processMultiProviderModelList } from '../../utils/modelParse';
|
||||
|
||||
export const LobeOllamaCloudAI = createOpenAICompatibleRuntime({
|
||||
baseURL: 'https://api.ollama.com/v1',
|
||||
chatCompletion: {
|
||||
handlePayload: (payload) => {
|
||||
const { model, ...rest } = payload;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
model,
|
||||
} as any;
|
||||
},
|
||||
},
|
||||
debug: {
|
||||
chatCompletion: () => process.env.DEBUG_OLLAMA_CLOUD_CHAT_COMPLETION === '1',
|
||||
},
|
||||
models: async ({ client }) => {
|
||||
try {
|
||||
const modelsPage = (await client.models.list()) as any;
|
||||
const modelList = Array.isArray(modelsPage?.data)
|
||||
? modelsPage.data
|
||||
: Array.isArray(modelsPage)
|
||||
? modelsPage
|
||||
: [];
|
||||
|
||||
return await processMultiProviderModelList(modelList, 'ollamacloud');
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Failed to fetch Ollama Cloud models. Please ensure your Ollama Cloud API key is valid:',
|
||||
error,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
provider: ModelProvider.OllamaCloud,
|
||||
});
|
||||
@@ -700,5 +700,167 @@ describe('createQwenImage', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should convert imageUrls array to imageUrl for qwen-image-edit', async () => {
|
||||
const mockImageUrl =
|
||||
'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/imageUrls-converted.jpg';
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
output: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: [{ image: mockImageUrl }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
request_id: 'req-imageUrls-123',
|
||||
}),
|
||||
});
|
||||
|
||||
const payload: CreateImagePayload = {
|
||||
model: 'qwen-image-edit',
|
||||
params: {
|
||||
prompt: 'Edit this image to add a dog',
|
||||
imageUrls: [
|
||||
'https://example.com/source-image-1.jpg',
|
||||
'https://example.com/source-image-2.jpg',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await createQwenImage(payload, mockOptions);
|
||||
|
||||
expect(result).toEqual({
|
||||
imageUrl: mockImageUrl,
|
||||
});
|
||||
|
||||
const [url, options] = (fetch as any).mock.calls[0];
|
||||
const body = JSON.parse(options.body);
|
||||
|
||||
// Verify that the first imageUrl from imageUrls array was used
|
||||
expect(body.input.messages[0].content[0].image).toBe(
|
||||
'https://example.com/source-image-1.jpg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use first imageUrl when imageUrls has multiple elements', async () => {
|
||||
const mockImageUrl = 'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/first-element.jpg';
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
output: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: [{ image: mockImageUrl }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
request_id: 'req-first-element',
|
||||
}),
|
||||
});
|
||||
|
||||
const payload: CreateImagePayload = {
|
||||
model: 'qwen-image-edit',
|
||||
params: {
|
||||
prompt: 'Use the first image only',
|
||||
imageUrls: [
|
||||
'https://example.com/first-image.jpg',
|
||||
'https://example.com/second-image.jpg',
|
||||
'https://example.com/third-image.jpg',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await createQwenImage(payload, mockOptions);
|
||||
|
||||
const [url, options] = (fetch as any).mock.calls[0];
|
||||
const body = JSON.parse(options.body);
|
||||
|
||||
// Should use only the first image from the array
|
||||
expect(body.input.messages[0].content[0].image).toBe('https://example.com/first-image.jpg');
|
||||
});
|
||||
|
||||
it('should throw error when imageUrls is empty array', async () => {
|
||||
const payload: CreateImagePayload = {
|
||||
model: 'qwen-image-edit',
|
||||
params: {
|
||||
prompt: 'Edit this image',
|
||||
imageUrls: [], // Empty array
|
||||
},
|
||||
};
|
||||
|
||||
await expect(createQwenImage(payload, mockOptions)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: 'ProviderBizError',
|
||||
provider: 'qwen',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prioritize imageUrl over imageUrls when both are provided', async () => {
|
||||
const mockImageUrl = 'https://dashscope.oss-cn-beijing.aliyuncs.com/aigc/priority-test.jpg';
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
output: {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: [{ image: mockImageUrl }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
request_id: 'req-priority-test',
|
||||
}),
|
||||
});
|
||||
|
||||
const payload: CreateImagePayload = {
|
||||
model: 'qwen-image-edit',
|
||||
params: {
|
||||
prompt: 'Test priority between imageUrl and imageUrls',
|
||||
imageUrl: 'https://example.com/priority-image.jpg',
|
||||
imageUrls: [
|
||||
'https://example.com/should-not-use-1.jpg',
|
||||
'https://example.com/should-not-use-2.jpg',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await createQwenImage(payload, mockOptions);
|
||||
|
||||
const [url, options] = (fetch as any).mock.calls[0];
|
||||
const body = JSON.parse(options.body);
|
||||
|
||||
// Should use imageUrl, not imageUrls
|
||||
expect(body.input.messages[0].content[0].image).toBe(
|
||||
'https://example.com/priority-image.jpg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when neither imageUrl nor imageUrls are provided', async () => {
|
||||
const payload: CreateImagePayload = {
|
||||
model: 'qwen-image-edit',
|
||||
params: {
|
||||
prompt: 'Edit this image',
|
||||
// Neither imageUrl nor imageUrls provided
|
||||
},
|
||||
};
|
||||
|
||||
await expect(createQwenImage(payload, mockOptions)).rejects.toEqual(
|
||||
expect.objectContaining({
|
||||
errorType: 'ProviderBizError',
|
||||
provider: 'qwen',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,8 +98,15 @@ async function createImageEdit(
|
||||
const endpoint = `https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation`;
|
||||
log('Creating image edit with model: %s, endpoint: %s', model, endpoint);
|
||||
|
||||
if (!params.imageUrl) {
|
||||
throw new Error('imageUrl is required for qwen-image-edit model');
|
||||
// Handle imageUrls to imageUrl conversion
|
||||
let imageUrl = params.imageUrl;
|
||||
if (!imageUrl && params.imageUrls && params.imageUrls.length > 0) {
|
||||
imageUrl = params.imageUrls[0];
|
||||
log('Converting imageUrls to imageUrl: using first image %s', imageUrl);
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
throw new Error('imageUrl or imageUrls is required for qwen-image-edit model');
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
@@ -107,7 +114,7 @@ async function createImageEdit(
|
||||
input: {
|
||||
messages: [
|
||||
{
|
||||
content: [{ image: params.imageUrl }, { text: params.prompt }],
|
||||
content: [{ image: imageUrl }, { text: params.prompt }],
|
||||
role: 'user',
|
||||
},
|
||||
],
|
||||
@@ -152,10 +159,10 @@ async function createImageEdit(
|
||||
throw new Error('No image found in response content');
|
||||
}
|
||||
|
||||
const imageUrl = imageContent.image;
|
||||
log('Image edit generated successfully: %s', imageUrl);
|
||||
const resultImageUrl = imageContent.image;
|
||||
log('Image edit generated successfully: %s', resultImageUrl);
|
||||
|
||||
return { imageUrl };
|
||||
return { imageUrl: resultImageUrl };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,11 +231,11 @@ export async function createQwenImage(
|
||||
};
|
||||
}
|
||||
|
||||
const imageUrl = taskStatus.output.results[0].url;
|
||||
log('Image generated successfully: %s', imageUrl);
|
||||
const generatedImageUrl = taskStatus.output.results[0].url;
|
||||
log('Image generated successfully: %s', generatedImageUrl);
|
||||
|
||||
return {
|
||||
data: { imageUrl },
|
||||
data: { imageUrl: generatedImageUrl },
|
||||
status: 'success',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { LobeNewAPIAI } from './providers/newapi';
|
||||
import { LobeNovitaAI } from './providers/novita';
|
||||
import { LobeNvidiaAI } from './providers/nvidia';
|
||||
import { LobeOllamaAI } from './providers/ollama';
|
||||
import { LobeOllamaCloudAI } from './providers/ollamacloud';
|
||||
import { LobeOpenAI } from './providers/openai';
|
||||
import { LobeOpenRouterAI } from './providers/openrouter';
|
||||
import { LobePerplexityAI } from './providers/perplexity';
|
||||
@@ -99,6 +100,7 @@ export const providerRuntimeMap = {
|
||||
novita: LobeNovitaAI,
|
||||
nvidia: LobeNvidiaAI,
|
||||
ollama: LobeOllamaAI,
|
||||
ollamacloud: LobeOllamaCloudAI,
|
||||
openai: LobeOpenAI,
|
||||
openrouter: LobeOpenRouterAI,
|
||||
perplexity: LobePerplexityAI,
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
|
||||
novita?: OpenAICompatibleKeyVault;
|
||||
nvidia?: OpenAICompatibleKeyVault;
|
||||
ollama?: OpenAICompatibleKeyVault;
|
||||
ollamacloud?: OpenAICompatibleKeyVault;
|
||||
openai?: OpenAICompatibleKeyVault;
|
||||
openrouter?: OpenAICompatibleKeyVault;
|
||||
password?: string;
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ const GroupItem = memo<SessionGroupItem>(({ id, name }) => {
|
||||
onChangeEnd={async (input) => {
|
||||
if (name !== input) {
|
||||
if (!input) return;
|
||||
if (input.length === 0 || input.length > 20)
|
||||
if (input.length === 0 || input.length > 20 || input.trim() === '')
|
||||
return message.warning(t('sessionGroup.tooLong'));
|
||||
|
||||
await updateSessionGroupName(id, input);
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ const CreateGroupModal = memo<CreateGroupModalProps>(
|
||||
onCancel?.(e);
|
||||
}}
|
||||
onOk={async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (input.length === 0 || input.length > 20)
|
||||
if (input.length === 0 || input.length > 20 || input.trim() === '')
|
||||
return message.warning(t('sessionGroup.tooLong'));
|
||||
|
||||
setLoading(true);
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
MoonshotProviderCard,
|
||||
NovitaProviderCard,
|
||||
NvidiaProviderCard,
|
||||
OllamaCloudProviderCard,
|
||||
OpenRouterProviderCard,
|
||||
PPIOProviderCard,
|
||||
PerplexityProviderCard,
|
||||
@@ -116,6 +117,7 @@ export const useProviderList = (): ProviderItem[] => {
|
||||
InfiniAIProviderCard,
|
||||
AkashChatProviderCard,
|
||||
Ai302ProviderCard,
|
||||
OllamaCloudProviderCard,
|
||||
],
|
||||
[
|
||||
AzureProvider,
|
||||
|
||||
@@ -37,6 +37,7 @@ import NewAPIProvider from './newapi';
|
||||
import NovitaProvider from './novita';
|
||||
import NvidiaProvider from './nvidia';
|
||||
import OllamaProvider from './ollama';
|
||||
import OllamaCloudProvider from './ollamacloud';
|
||||
import OpenAIProvider from './openai';
|
||||
import OpenRouterProvider from './openrouter';
|
||||
import PerplexityProvider from './perplexity';
|
||||
@@ -125,6 +126,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
|
||||
{ ...AzureProvider, chatModels: [] },
|
||||
AzureAIProvider,
|
||||
OllamaProvider,
|
||||
OllamaCloudProvider,
|
||||
VLLMProvider,
|
||||
XinferenceProvider,
|
||||
AnthropicProvider,
|
||||
@@ -233,6 +235,7 @@ export { default as NewAPIProviderCard } from './newapi';
|
||||
export { default as NovitaProviderCard } from './novita';
|
||||
export { default as NvidiaProviderCard } from './nvidia';
|
||||
export { default as OllamaProviderCard } from './ollama';
|
||||
export { default as OllamaCloudProviderCard } from './ollamacloud';
|
||||
export { default as OpenAIProviderCard } from './openai';
|
||||
export { default as OpenRouterProviderCard } from './openrouter';
|
||||
export { default as PerplexityProviderCard } from './perplexity';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ModelProviderCard } from '@/types/llm';
|
||||
|
||||
const OllamaCloud: ModelProviderCard = {
|
||||
chatModels: [],
|
||||
checkModel: 'gpt-oss:20b',
|
||||
description:
|
||||
'Ollama Cloud 提供官方托管的推理服务,开箱即用地访问 Ollama 模型库,并支持 OpenAI 兼容接口。',
|
||||
id: 'ollamacloud',
|
||||
modelsUrl: 'https://ollama.com/library',
|
||||
name: 'Ollama Cloud',
|
||||
settings: {
|
||||
sdkType: 'openai',
|
||||
showModelFetcher: true,
|
||||
},
|
||||
url: 'https://ollama.com/cloud',
|
||||
};
|
||||
|
||||
export default OllamaCloud;
|
||||
@@ -71,6 +71,8 @@ export const getLLMConfig = () => {
|
||||
WENXIN_API_KEY: z.string().optional(),
|
||||
|
||||
ENABLED_OLLAMA: z.boolean(),
|
||||
ENABLED_OLLAMA_CLOUD: z.boolean(),
|
||||
OLLAMA_CLOUD_API_KEY: z.string().optional(),
|
||||
|
||||
ENABLED_VLLM: z.boolean(),
|
||||
VLLM_API_KEY: z.string().optional(),
|
||||
@@ -267,6 +269,8 @@ export const getLLMConfig = () => {
|
||||
WENXIN_API_KEY: process.env.WENXIN_API_KEY,
|
||||
|
||||
ENABLED_OLLAMA: process.env.ENABLED_OLLAMA !== '0',
|
||||
ENABLED_OLLAMA_CLOUD: !!process.env.OLLAMA_CLOUD_API_KEY,
|
||||
OLLAMA_CLOUD_API_KEY: process.env.OLLAMA_CLOUD_API_KEY,
|
||||
|
||||
ENABLED_VLLM: !!process.env.VLLM_API_KEY,
|
||||
VLLM_API_KEY: process.env.VLLM_API_KEY,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Center, Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { createChatToolsEngine } from '@/helpers/toolEngineering';
|
||||
import { useModelContextWindowTokens } from '@/hooks/useModelContextWindowTokens';
|
||||
import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
|
||||
import { useTokenCount } from '@/hooks/useTokenCount';
|
||||
@@ -45,22 +46,31 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
|
||||
const [historyCount, enableHistoryCount] = useAgentStore((s) => [
|
||||
agentChatConfigSelectors.historyCount(s),
|
||||
agentChatConfigSelectors.enableHistoryCount(s),
|
||||
// need to re-render by search mode
|
||||
agentChatConfigSelectors.isAgentEnableSearch(s),
|
||||
]);
|
||||
|
||||
const maxTokens = useModelContextWindowTokens(model, provider);
|
||||
|
||||
// Tool usage token
|
||||
const canUseTool = useModelSupportToolUse(model, provider);
|
||||
const plugins = useAgentStore(agentSelectors.currentAgentPlugins);
|
||||
const pluginIds = useAgentStore(agentSelectors.currentAgentPlugins);
|
||||
|
||||
const toolsString = useToolStore((s) => {
|
||||
const pluginSystemRoles = toolSelectors.enabledSystemRoles(plugins)(s);
|
||||
const schemaNumber = toolSelectors
|
||||
.enabledSchema(plugins)(s)
|
||||
.map((i) => JSON.stringify(i))
|
||||
.join('');
|
||||
const toolsEngine = createChatToolsEngine({ model, provider });
|
||||
|
||||
const { tools, enabledToolIds } = toolsEngine.generateToolsDetailed({
|
||||
model,
|
||||
provider,
|
||||
toolIds: pluginIds,
|
||||
});
|
||||
const schemaNumber = tools?.map((i) => JSON.stringify(i)).join('') || '';
|
||||
|
||||
const pluginSystemRoles = toolSelectors.enabledSystemRoles(enabledToolIds)(s);
|
||||
|
||||
return pluginSystemRoles + schemaNumber;
|
||||
});
|
||||
|
||||
const toolsToken = useTokenCount(canUseTool ? toolsString : '');
|
||||
|
||||
// Chat usage token
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as agentSelectors from '@/store/agent/selectors';
|
||||
import * as aiInfraSelectors from '@/store/aiInfra/selectors';
|
||||
|
||||
import { getSearchConfig } from './getSearchConfig';
|
||||
|
||||
// Mock the store dependencies
|
||||
vi.mock('@/store/agent', () => ({
|
||||
getAgentStoreState: () => ({}),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/agent/selectors', () => ({
|
||||
agentChatConfigSelectors: {
|
||||
currentChatConfig: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/store/aiInfra', () => ({
|
||||
getAiInfraStoreState: () => ({}),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/aiInfra/selectors', () => ({
|
||||
aiProviderSelectors: {
|
||||
isProviderHasBuiltinSearch: vi.fn(),
|
||||
},
|
||||
aiModelSelectors: {
|
||||
isModelHasBuiltinSearch: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('getSearchConfig', () => {
|
||||
const model = 'gpt-4';
|
||||
const provider = 'openai';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return correct config when search is enabled and no builtin search', () => {
|
||||
vi.mocked(agentSelectors.agentChatConfigSelectors.currentChatConfig).mockReturnValue({
|
||||
searchMode: 'on',
|
||||
useModelBuiltinSearch: false,
|
||||
} as any);
|
||||
|
||||
vi.mocked(aiInfraSelectors.aiProviderSelectors.isProviderHasBuiltinSearch).mockReturnValue(
|
||||
() => false,
|
||||
);
|
||||
vi.mocked(aiInfraSelectors.aiModelSelectors.isModelHasBuiltinSearch).mockReturnValue(
|
||||
() => false,
|
||||
);
|
||||
|
||||
const result = getSearchConfig(model, provider);
|
||||
|
||||
expect(result).toEqual({
|
||||
enabledSearch: true,
|
||||
isProviderHasBuiltinSearch: false,
|
||||
isModelHasBuiltinSearch: false,
|
||||
useModelSearch: false,
|
||||
useApplicationBuiltinSearchTool: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct config when search is disabled', () => {
|
||||
vi.mocked(agentSelectors.agentChatConfigSelectors.currentChatConfig).mockReturnValue({
|
||||
searchMode: 'off',
|
||||
useModelBuiltinSearch: false,
|
||||
} as any);
|
||||
|
||||
const result = getSearchConfig(model, provider);
|
||||
|
||||
expect(result.enabledSearch).toBe(false);
|
||||
expect(result.useApplicationBuiltinSearchTool).toBe(false);
|
||||
});
|
||||
|
||||
it('should prefer model search when available and enabled', () => {
|
||||
vi.mocked(agentSelectors.agentChatConfigSelectors.currentChatConfig).mockReturnValue({
|
||||
searchMode: 'on',
|
||||
useModelBuiltinSearch: true,
|
||||
} as any);
|
||||
|
||||
vi.mocked(aiInfraSelectors.aiProviderSelectors.isProviderHasBuiltinSearch).mockReturnValue(
|
||||
() => true,
|
||||
);
|
||||
vi.mocked(aiInfraSelectors.aiModelSelectors.isModelHasBuiltinSearch).mockReturnValue(
|
||||
() => false,
|
||||
);
|
||||
|
||||
const result = getSearchConfig(model, provider);
|
||||
|
||||
expect(result).toEqual({
|
||||
enabledSearch: true,
|
||||
isProviderHasBuiltinSearch: true,
|
||||
isModelHasBuiltinSearch: false,
|
||||
useModelSearch: true,
|
||||
useApplicationBuiltinSearchTool: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use model search when model has builtin search and it is enabled', () => {
|
||||
vi.mocked(agentSelectors.agentChatConfigSelectors.currentChatConfig).mockReturnValue({
|
||||
searchMode: 'on',
|
||||
useModelBuiltinSearch: true,
|
||||
} as any);
|
||||
|
||||
vi.mocked(aiInfraSelectors.aiProviderSelectors.isProviderHasBuiltinSearch).mockReturnValue(
|
||||
() => false,
|
||||
);
|
||||
vi.mocked(aiInfraSelectors.aiModelSelectors.isModelHasBuiltinSearch).mockReturnValue(
|
||||
() => true,
|
||||
);
|
||||
|
||||
const result = getSearchConfig(model, provider);
|
||||
|
||||
expect(result).toEqual({
|
||||
enabledSearch: true,
|
||||
isProviderHasBuiltinSearch: false,
|
||||
isModelHasBuiltinSearch: true,
|
||||
useModelSearch: true,
|
||||
useApplicationBuiltinSearchTool: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use model search when model has builtin search but preference is disabled', () => {
|
||||
vi.mocked(agentSelectors.agentChatConfigSelectors.currentChatConfig).mockReturnValue({
|
||||
searchMode: 'on',
|
||||
useModelBuiltinSearch: false,
|
||||
} as any);
|
||||
|
||||
vi.mocked(aiInfraSelectors.aiProviderSelectors.isProviderHasBuiltinSearch).mockReturnValue(
|
||||
() => false,
|
||||
);
|
||||
vi.mocked(aiInfraSelectors.aiModelSelectors.isModelHasBuiltinSearch).mockReturnValue(
|
||||
() => true,
|
||||
);
|
||||
|
||||
const result = getSearchConfig(model, provider);
|
||||
|
||||
expect(result).toEqual({
|
||||
enabledSearch: true,
|
||||
isProviderHasBuiltinSearch: false,
|
||||
isModelHasBuiltinSearch: true,
|
||||
useModelSearch: false,
|
||||
useApplicationBuiltinSearchTool: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getAgentStoreState } from '@/store/agent';
|
||||
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
||||
import { getAiInfraStoreState } from '@/store/aiInfra';
|
||||
import { aiModelSelectors, aiProviderSelectors } from '@/store/aiInfra/selectors';
|
||||
|
||||
/**
|
||||
* Search configuration result
|
||||
*/
|
||||
export interface SearchConfig {
|
||||
/** Whether search is enabled in chat config */
|
||||
enabledSearch: boolean;
|
||||
/** Whether model has builtin search capability */
|
||||
isModelHasBuiltinSearch: boolean;
|
||||
/** Whether provider has builtin search capability */
|
||||
isProviderHasBuiltinSearch: boolean;
|
||||
/** Whether to use application's builtin search tool */
|
||||
useApplicationBuiltinSearchTool: boolean;
|
||||
/** Whether to use model's builtin search */
|
||||
useModelSearch: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search configuration for given model and provider
|
||||
* This centralizes the search logic that was duplicated across multiple places
|
||||
*/
|
||||
export const getSearchConfig = (model: string, provider: string): SearchConfig => {
|
||||
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
||||
const aiInfraStoreState = getAiInfraStoreState();
|
||||
|
||||
const enabledSearch = chatConfig.searchMode !== 'off';
|
||||
const isProviderHasBuiltinSearch =
|
||||
aiProviderSelectors.isProviderHasBuiltinSearch(provider)(aiInfraStoreState);
|
||||
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
||||
model,
|
||||
provider,
|
||||
)(aiInfraStoreState);
|
||||
|
||||
const useModelSearch =
|
||||
((isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && chatConfig.useModelBuiltinSearch) ||
|
||||
false;
|
||||
|
||||
const useApplicationBuiltinSearchTool = enabledSearch && !useModelSearch;
|
||||
|
||||
return {
|
||||
enabledSearch,
|
||||
isModelHasBuiltinSearch,
|
||||
isProviderHasBuiltinSearch,
|
||||
useApplicationBuiltinSearchTool,
|
||||
useModelSearch,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { isDeprecatedEdition } from '@/const/version';
|
||||
import { aiModelSelectors, getAiInfraStoreState } from '@/store/aiInfra';
|
||||
import { getUserStoreState } from '@/store/user';
|
||||
import { modelProviderSelectors } from '@/store/user/selectors';
|
||||
|
||||
export const isCanUseFC = (model: string, provider: string): boolean => {
|
||||
// TODO: remove isDeprecatedEdition condition in V2.0
|
||||
if (isDeprecatedEdition) {
|
||||
return modelProviderSelectors.isModelEnabledFunctionCall(model)(getUserStoreState());
|
||||
}
|
||||
|
||||
return aiModelSelectors.isModelSupportToolUse(model, provider)(getAiInfraStoreState()) || false;
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { createChatToolsEngine, createToolsEngine, getEnabledTools } from './index';
|
||||
|
||||
// Mock the store and helper dependencies
|
||||
vi.mock('@/store/tool', () => ({
|
||||
getToolStoreState: () => ({
|
||||
builtinTools: [
|
||||
{
|
||||
identifier: 'search',
|
||||
manifest: {
|
||||
api: [
|
||||
{
|
||||
description: 'Search the web',
|
||||
name: 'search',
|
||||
parameters: {
|
||||
properties: {
|
||||
query: { description: 'Search query', type: 'string' },
|
||||
},
|
||||
required: ['query'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: 'search',
|
||||
meta: {
|
||||
title: 'Web Search',
|
||||
description: 'Search tool',
|
||||
avatar: '🔍',
|
||||
},
|
||||
type: 'builtin',
|
||||
} as unknown as LobeChatPluginManifest,
|
||||
type: 'builtin' as const,
|
||||
},
|
||||
{
|
||||
identifier: 'lobe-web-browsing',
|
||||
manifest: {
|
||||
api: [
|
||||
{
|
||||
description:
|
||||
'a search service. Useful for when you need to answer questions about current events. Input should be a search query. Output is a JSON array of the query results',
|
||||
name: 'search',
|
||||
parameters: {
|
||||
properties: {
|
||||
query: { description: 'The search query', type: 'string' },
|
||||
},
|
||||
required: ['query'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
],
|
||||
identifier: 'lobe-web-browsing',
|
||||
meta: {
|
||||
title: 'Web Browsing',
|
||||
avatar: '🌐',
|
||||
},
|
||||
type: 'builtin',
|
||||
} as unknown as LobeChatPluginManifest,
|
||||
type: 'builtin' as const,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/store/tool/selectors', () => ({
|
||||
pluginSelectors: {
|
||||
installedPluginManifestList: () => [],
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../isCanUseFC', () => ({
|
||||
isCanUseFC: () => true,
|
||||
}));
|
||||
|
||||
vi.mock('@/helpers/getSearchConfig', () => ({
|
||||
getSearchConfig: () => ({
|
||||
useApplicationBuiltinSearchTool: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('toolEngineering', () => {
|
||||
describe('createToolsEngine', () => {
|
||||
it('should generate tools array for enabled plugins', () => {
|
||||
const toolsEngine = createToolsEngine();
|
||||
const result = toolsEngine.generateTools({
|
||||
toolIds: ['search'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result![0]).toMatchObject({
|
||||
function: {
|
||||
description: 'Search the web',
|
||||
name: 'search____search____builtin',
|
||||
parameters: {
|
||||
properties: {
|
||||
query: { description: 'Search query', type: 'string' },
|
||||
},
|
||||
required: ['query'],
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'function',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined when no plugins match', () => {
|
||||
const toolsEngine = createToolsEngine();
|
||||
const result = toolsEngine.generateTools({
|
||||
toolIds: ['non-existent'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return detailed result with correct field names', () => {
|
||||
const toolsEngine = createToolsEngine();
|
||||
const result = toolsEngine.generateToolsDetailed({
|
||||
toolIds: ['search'],
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('enabledToolIds');
|
||||
expect(result).toHaveProperty('filteredTools');
|
||||
expect(result).toHaveProperty('tools');
|
||||
expect(result.enabledToolIds).toEqual(['search']);
|
||||
expect(result.filteredTools).toEqual([]);
|
||||
expect(result.tools).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createChatToolsEngine', () => {
|
||||
it('should include web browsing tool as default when no tools are provided', () => {
|
||||
const toolsEngine = createChatToolsEngine({
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
const result = toolsEngine.generateToolsDetailed({
|
||||
toolIds: [], // No explicitly enabled tools
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result.enabledToolIds).toContain('lobe-web-browsing');
|
||||
});
|
||||
|
||||
it('should include web browsing tool alongside user-provided tools', () => {
|
||||
const toolsEngine = createChatToolsEngine({
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
const result = toolsEngine.generateToolsDetailed({
|
||||
toolIds: ['search'], // User explicitly enables search tool
|
||||
model: 'gpt-4',
|
||||
provider: 'openai',
|
||||
});
|
||||
|
||||
expect(result.enabledToolIds).toEqual(['search', 'lobe-web-browsing']);
|
||||
expect(result.enabledToolIds).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Migration functions', () => {
|
||||
describe('getEnabledTools', () => {
|
||||
it('should return empty array when no tool IDs provided', () => {
|
||||
const result = getEnabledTools([], 'gpt-4', 'openai');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return tools for valid tool IDs', () => {
|
||||
const result = getEnabledTools(['search'], 'gpt-4', 'openai');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveProperty('type', 'function');
|
||||
expect(result[0].function).toHaveProperty('name', 'search____search____builtin');
|
||||
});
|
||||
|
||||
it('should use provided model and provider', () => {
|
||||
const result = getEnabledTools(['search'], 'gpt-3.5-turbo', 'anthropic');
|
||||
expect(result).toBeDefined();
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return empty array for non-existent tools', () => {
|
||||
const result = getEnabledTools(['non-existent-tool'], 'gpt-4', 'openai');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tools Engineering - Unified tools processing using ToolsEngine
|
||||
*/
|
||||
import { ToolsEngine } from '@lobechat/context-engine';
|
||||
import type { PluginEnableChecker } from '@lobechat/context-engine';
|
||||
import { ChatCompletionTool, WorkingModel } from '@lobechat/types';
|
||||
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
||||
|
||||
import { getSearchConfig } from '@/helpers/getSearchConfig';
|
||||
import { getToolStoreState } from '@/store/tool';
|
||||
import { pluginSelectors } from '@/store/tool/selectors';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
|
||||
import { isCanUseFC } from '../isCanUseFC';
|
||||
|
||||
/**
|
||||
* Tools engine configuration options
|
||||
*/
|
||||
export interface ToolsEngineConfig {
|
||||
/** Additional manifests to include beyond the standard ones */
|
||||
additionalManifests?: LobeChatPluginManifest[];
|
||||
/** Default tool IDs that will always be added to the end of the tools list */
|
||||
defaultToolIds?: string[];
|
||||
/** Custom enable checker for plugins */
|
||||
enableChecker?: PluginEnableChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize ToolsEngine with current manifest schemas and configurable options
|
||||
*/
|
||||
export const createToolsEngine = (config: ToolsEngineConfig = {}): ToolsEngine => {
|
||||
const { enableChecker, additionalManifests = [], defaultToolIds } = config;
|
||||
|
||||
const toolStoreState = getToolStoreState();
|
||||
|
||||
// Get all available plugin manifests
|
||||
const pluginManifests = pluginSelectors.installedPluginManifestList(toolStoreState);
|
||||
|
||||
// Get all builtin tool manifests
|
||||
const builtinManifests = toolStoreState.builtinTools.map(
|
||||
(tool) => tool.manifest as LobeChatPluginManifest,
|
||||
);
|
||||
|
||||
// Combine all manifests
|
||||
const allManifests = [...pluginManifests, ...builtinManifests, ...additionalManifests];
|
||||
|
||||
return new ToolsEngine({
|
||||
defaultToolIds,
|
||||
enableChecker,
|
||||
functionCallChecker: isCanUseFC,
|
||||
manifestSchemas: allManifests,
|
||||
});
|
||||
};
|
||||
|
||||
export const createChatToolsEngine = (workingModel: WorkingModel) =>
|
||||
createToolsEngine({
|
||||
// Add WebBrowsingManifest as default tool
|
||||
defaultToolIds: [WebBrowsingManifest.identifier],
|
||||
// Create search-aware enableChecker for this request
|
||||
enableChecker: ({ pluginId }) => {
|
||||
// For WebBrowsingManifest, apply search logic
|
||||
if (pluginId === WebBrowsingManifest.identifier) {
|
||||
const searchConfig = getSearchConfig(workingModel.model, workingModel.provider);
|
||||
return searchConfig.useApplicationBuiltinSearchTool;
|
||||
}
|
||||
|
||||
// For all other plugins, enable by default
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Provides the same functionality using ToolsEngine with enhanced capabilities
|
||||
*
|
||||
* @param toolIds - Array of tool IDs to generate tools for
|
||||
* @param model - Model name for function calling compatibility check (optional)
|
||||
* @param provider - Provider name for function calling compatibility check (optional)
|
||||
* @returns Array of ChatCompletionTool objects
|
||||
*/
|
||||
export const getEnabledTools = (
|
||||
toolIds: string[] = [],
|
||||
model: string,
|
||||
provider: string,
|
||||
): ChatCompletionTool[] => {
|
||||
const toolsEngine = createToolsEngine();
|
||||
|
||||
return (
|
||||
toolsEngine.generateTools({
|
||||
model: model, // Use provided model or fallback
|
||||
provider: provider, // Use provided provider or fallback
|
||||
toolIds,
|
||||
}) || []
|
||||
);
|
||||
};
|
||||
@@ -40,6 +40,9 @@ export const getServerGlobalConfig = async () => {
|
||||
enabled: isDesktop ? true : undefined,
|
||||
fetchOnClient: isDesktop ? false : !process.env.OLLAMA_PROXY_URL,
|
||||
},
|
||||
ollamacloud: {
|
||||
enabledKey: 'ENABLED_OLLAMA_CLOUD',
|
||||
},
|
||||
qwen: {
|
||||
withDeploymentName: true,
|
||||
},
|
||||
|
||||
@@ -98,6 +98,14 @@ const getParamsFromPayload = (provider: string, payload: ClientSecretPayload) =>
|
||||
return { apiKey };
|
||||
}
|
||||
|
||||
case ModelProvider.OllamaCloud: {
|
||||
const { OLLAMA_CLOUD_API_KEY } = llmConfig;
|
||||
|
||||
const apiKey = apiKeyManager.pick(payload?.apiKey || OLLAMA_CLOUD_API_KEY);
|
||||
|
||||
return { apiKey };
|
||||
}
|
||||
|
||||
case ModelProvider.TencentCloud: {
|
||||
const { TENCENT_CLOUD_API_KEY } = llmConfig;
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DEFAULT_USER_AVATAR } from '@/const/meta';
|
||||
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
||||
import * as isCanUseFCModule from '@/helpers/isCanUseFC';
|
||||
import * as toolEngineeringModule from '@/helpers/toolEngineering';
|
||||
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
||||
import { aiModelSelectors } from '@/store/aiInfra';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
import { toolSelectors } from '@/store/tool/selectors';
|
||||
import { modelProviderSelectors } from '@/store/user/selectors';
|
||||
import { DalleManifest } from '@/tools/dalle';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
import { ChatErrorType } from '@/types/index';
|
||||
@@ -63,6 +66,11 @@ vi.mock('../_auth', () => ({
|
||||
createHeaderWithAuth: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
// Mock isCanUseFC to control function calling behavior in tests
|
||||
vi.mock('@/helpers/isCanUseFC', () => ({
|
||||
isCanUseFC: vi.fn(() => true), // Default to true, tests can override
|
||||
}));
|
||||
|
||||
describe('ChatService', () => {
|
||||
describe('createAssistantMessage', () => {
|
||||
it('should process messages and call getChatCompletion with the right parameters', async () => {
|
||||
@@ -111,26 +119,6 @@ describe('ChatService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not use tools for models in the vision model whitelist', async () => {
|
||||
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
||||
const messages = [{ content: 'Hello', role: 'user' }] as ChatMessage[];
|
||||
const modelInWhitelist = 'gpt-4-vision-preview';
|
||||
|
||||
await chatService.createAssistantMessage({
|
||||
messages,
|
||||
model: modelInWhitelist,
|
||||
plugins: ['plugin1'],
|
||||
});
|
||||
|
||||
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tools: undefined,
|
||||
model: modelInWhitelist,
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
describe('extendParams functionality', () => {
|
||||
it('should add reasoning parameters when model supports enableReasoning and user enables it', async () => {
|
||||
const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
|
||||
@@ -355,10 +343,12 @@ describe('ChatService', () => {
|
||||
|
||||
expect(getChatCompletionSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
enabledSearch: undefined,
|
||||
messages: [
|
||||
{ content: 'Hello', role: 'user' },
|
||||
{ content: 'Hey', role: 'assistant' },
|
||||
],
|
||||
tools: undefined,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
@@ -870,16 +860,24 @@ describe('ChatService', () => {
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasBuiltinSearch').mockReturnValueOnce(() => false);
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasExtendParams').mockReturnValueOnce(() => false);
|
||||
|
||||
// Mock tool selectors
|
||||
vi.spyOn(toolSelectors, 'enabledSchema').mockReturnValueOnce(() => [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
]);
|
||||
// Mock createChatToolsEngine to return tools with web browsing
|
||||
const mockToolsEngine = {
|
||||
generateToolsDetailed: vi.fn().mockReturnValue({
|
||||
tools: [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
],
|
||||
enabledToolIds: [WebBrowsingManifest.identifier],
|
||||
}),
|
||||
};
|
||||
vi.spyOn(toolEngineeringModule, 'createChatToolsEngine').mockReturnValue(
|
||||
mockToolsEngine as any,
|
||||
);
|
||||
|
||||
await chatService.createAssistantMessage({ messages, plugins: [] });
|
||||
|
||||
@@ -913,16 +911,24 @@ describe('ChatService', () => {
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasBuiltinSearch').mockReturnValueOnce(() => true);
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasExtendParams').mockReturnValueOnce(() => false);
|
||||
|
||||
// Mock tool selectors
|
||||
vi.spyOn(toolSelectors, 'enabledSchema').mockReturnValueOnce(() => [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
]);
|
||||
// Mock createChatToolsEngine to return tools with web browsing
|
||||
const mockToolsEngine = {
|
||||
generateToolsDetailed: vi.fn().mockReturnValue({
|
||||
tools: [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
],
|
||||
enabledToolIds: [WebBrowsingManifest.identifier],
|
||||
}),
|
||||
};
|
||||
vi.spyOn(toolEngineeringModule, 'createChatToolsEngine').mockReturnValue(
|
||||
mockToolsEngine as any,
|
||||
);
|
||||
|
||||
await chatService.createAssistantMessage({ messages, plugins: [] });
|
||||
|
||||
@@ -950,16 +956,24 @@ describe('ChatService', () => {
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasBuiltinSearch').mockReturnValueOnce(() => true);
|
||||
vi.spyOn(aiModelSelectors, 'isModelHasExtendParams').mockReturnValueOnce(() => false);
|
||||
|
||||
// Mock tool selectors
|
||||
vi.spyOn(toolSelectors, 'enabledSchema').mockReturnValueOnce(() => [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
]);
|
||||
// Mock createChatToolsEngine to return tools with web browsing
|
||||
const mockToolsEngine = {
|
||||
generateToolsDetailed: vi.fn().mockReturnValue({
|
||||
tools: [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: WebBrowsingManifest.identifier + '____search',
|
||||
description: 'Search the web',
|
||||
},
|
||||
},
|
||||
],
|
||||
enabledToolIds: [WebBrowsingManifest.identifier],
|
||||
}),
|
||||
};
|
||||
vi.spyOn(toolEngineeringModule, 'createChatToolsEngine').mockReturnValue(
|
||||
mockToolsEngine as any,
|
||||
);
|
||||
|
||||
await chatService.createAssistantMessage({ messages, plugins: [] });
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ChatMessage } from '@lobechat/types';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as isCanUseFCModule from '@/helpers/isCanUseFC';
|
||||
|
||||
import { contextEngineering } from './contextEngineering';
|
||||
import * as helpers from './helper';
|
||||
|
||||
@@ -360,7 +362,7 @@ describe('contextEngineering', () => {
|
||||
|
||||
it('should not include tool_calls for assistant message if model does not support tools', async () => {
|
||||
// Mock isCanUseFC to return false
|
||||
vi.spyOn(helpers, 'isCanUseFC').mockReturnValue(false);
|
||||
vi.spyOn(isCanUseFCModule, 'isCanUseFC').mockReturnValue(false);
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{
|
||||
|
||||
@@ -18,13 +18,13 @@ import { historySummaryPrompt } from '@lobechat/prompts';
|
||||
import { ChatMessage, OpenAIChatMessage } from '@lobechat/types';
|
||||
|
||||
import { INBOX_GUIDE_SYSTEMROLE } from '@/const/guide';
|
||||
import { isCanUseFC } from '@/helpers/isCanUseFC';
|
||||
import { getToolStoreState } from '@/store/tool';
|
||||
import { toolSelectors } from '@/store/tool/selectors';
|
||||
import { VARIABLE_GENERATORS } from '@/utils/client/parserPlaceholder';
|
||||
import { genToolCallingName } from '@/utils/toolCall';
|
||||
|
||||
import { isCanUseFC, isCanUseVideo, isCanUseVision } from './helper';
|
||||
|
||||
import { isCanUseVideo, isCanUseVision } from './helper';
|
||||
|
||||
interface ContextEngineeringContext {
|
||||
enableHistoryCount?: boolean;
|
||||
|
||||
@@ -6,15 +6,6 @@ import { aiModelSelectors, aiProviderSelectors } from '@/store/aiInfra/selectors
|
||||
import { getUserStoreState, useUserStore } from '@/store/user';
|
||||
import { modelConfigSelectors, modelProviderSelectors } from '@/store/user/selectors';
|
||||
|
||||
export const isCanUseFC = (model: string, provider: string): boolean => {
|
||||
// TODO: remove isDeprecatedEdition condition in V2.0
|
||||
if (isDeprecatedEdition) {
|
||||
return modelProviderSelectors.isModelEnabledFunctionCall(model)(getUserStoreState());
|
||||
}
|
||||
|
||||
return aiModelSelectors.isModelSupportToolUse(model, provider)(getAiInfraStoreState()) || false;
|
||||
};
|
||||
|
||||
export const isCanUseVision = (model: string, provider: string): boolean => {
|
||||
// TODO: remove isDeprecatedEdition condition in V2.0
|
||||
if (isDeprecatedEdition) {
|
||||
|
||||
+25
-47
@@ -7,21 +7,21 @@ import { ModelProvider } from 'model-bank';
|
||||
import { enableAuth } from '@/const/auth';
|
||||
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
||||
import { isDeprecatedEdition, isDesktop } from '@/const/version';
|
||||
import { getSearchConfig } from '@/helpers/getSearchConfig';
|
||||
import { createChatToolsEngine, createToolsEngine } from '@/helpers/toolEngineering';
|
||||
import { getAgentStoreState } from '@/store/agent';
|
||||
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
||||
import { aiModelSelectors, aiProviderSelectors, getAiInfraStoreState } from '@/store/aiInfra';
|
||||
import { getSessionStoreState } from '@/store/session';
|
||||
import { sessionMetaSelectors } from '@/store/session/selectors';
|
||||
import { getToolStoreState } from '@/store/tool';
|
||||
import { pluginSelectors, toolSelectors } from '@/store/tool/selectors';
|
||||
import { pluginSelectors } from '@/store/tool/selectors';
|
||||
import { getUserStoreState, useUserStore } from '@/store/user';
|
||||
import {
|
||||
preferenceSelectors,
|
||||
userGeneralSettingsSelectors,
|
||||
userProfileSelectors,
|
||||
} from '@/store/user/selectors';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
import { WorkingModel } from '@/types/agent';
|
||||
import { ChatMessage } from '@/types/message';
|
||||
import type { ChatStreamPayload } from '@/types/openai/chat';
|
||||
import { fetchWithInvokeStream } from '@/utils/electron/desktopRemoteRPCFetch';
|
||||
@@ -38,7 +38,7 @@ import { createHeaderWithAuth } from '../_auth';
|
||||
import { API_ENDPOINTS } from '../_url';
|
||||
import { initializeWithClientStore } from './clientModelRuntime';
|
||||
import { contextEngineering } from './contextEngineering';
|
||||
import { findDeploymentName, isCanUseFC, isEnableFetchOnClient } from './helper';
|
||||
import { findDeploymentName, isEnableFetchOnClient } from './helper';
|
||||
import { FetchOptions } from './types';
|
||||
|
||||
interface GetChatCompletionPayload extends Partial<Omit<ChatStreamPayload, 'messages'>> {
|
||||
@@ -82,33 +82,28 @@ class ChatService {
|
||||
params,
|
||||
);
|
||||
|
||||
// =================== 0. process search =================== //
|
||||
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
||||
const aiInfraStoreState = getAiInfraStoreState();
|
||||
const enabledSearch = chatConfig.searchMode !== 'off';
|
||||
const isProviderHasBuiltinSearch = aiProviderSelectors.isProviderHasBuiltinSearch(
|
||||
payload.provider!,
|
||||
)(aiInfraStoreState);
|
||||
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
||||
payload.model,
|
||||
payload.provider!,
|
||||
)(aiInfraStoreState);
|
||||
const searchConfig = getSearchConfig(payload.model, payload.provider!);
|
||||
|
||||
const useModelSearch =
|
||||
(isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && chatConfig.useModelBuiltinSearch;
|
||||
|
||||
const useApplicationBuiltinSearchTool = enabledSearch && !useModelSearch;
|
||||
// =================== 1. preprocess tools =================== //
|
||||
|
||||
const pluginIds = [...(enabledPlugins || [])];
|
||||
|
||||
if (useApplicationBuiltinSearchTool) {
|
||||
pluginIds.push(WebBrowsingManifest.identifier);
|
||||
}
|
||||
const toolsEngine = createChatToolsEngine({
|
||||
model: payload.model,
|
||||
provider: payload.provider!,
|
||||
});
|
||||
|
||||
// ============ 1. preprocess messages ============ //
|
||||
const { tools, enabledToolIds } = toolsEngine.generateToolsDetailed({
|
||||
model: payload.model,
|
||||
provider: payload.provider!,
|
||||
toolIds: pluginIds,
|
||||
});
|
||||
|
||||
// ============ 2. preprocess messages ============ //
|
||||
|
||||
const agentStoreState = getAgentStoreState();
|
||||
const agentConfig = agentSelectors.currentAgentConfig(agentStoreState);
|
||||
const chatConfig = agentChatConfigSelectors.currentChatConfig(agentStoreState);
|
||||
|
||||
// Apply context engineering with preprocessing configuration
|
||||
const oaiMessages = await contextEngineering({
|
||||
@@ -123,19 +118,13 @@ class ChatService {
|
||||
provider: payload.provider!,
|
||||
sessionId: options?.trace?.sessionId,
|
||||
systemRole: agentConfig.systemRole,
|
||||
tools: pluginIds,
|
||||
});
|
||||
|
||||
// ============ 2. preprocess tools ============ //
|
||||
|
||||
const tools = this.prepareTools(pluginIds, {
|
||||
model: payload.model,
|
||||
provider: payload.provider!,
|
||||
tools: enabledToolIds,
|
||||
});
|
||||
|
||||
// ============ 3. process extend params ============ //
|
||||
|
||||
let extendParams: Record<string, any> = {};
|
||||
const aiInfraStoreState = getAiInfraStoreState();
|
||||
|
||||
const isModelHasExtendParams = aiModelSelectors.isModelHasExtendParams(
|
||||
payload.model,
|
||||
@@ -209,7 +198,7 @@ class ChatService {
|
||||
{
|
||||
...params,
|
||||
...extendParams,
|
||||
enabledSearch: enabledSearch && useModelSearch ? true : undefined,
|
||||
enabledSearch: searchConfig.enabledSearch && searchConfig.useModelSearch ? true : undefined,
|
||||
messages: oaiMessages,
|
||||
tools,
|
||||
},
|
||||
@@ -421,9 +410,12 @@ class ChatService {
|
||||
provider: params.provider!,
|
||||
tools: params.plugins,
|
||||
});
|
||||
const tools = this.prepareTools(params.plugins || [], {
|
||||
// Use simple tools engine without complex search logic
|
||||
const toolsEngine = createToolsEngine();
|
||||
const tools = toolsEngine.generateTools({
|
||||
model: params.model!,
|
||||
provider: params.provider!,
|
||||
toolIds: params.plugins,
|
||||
});
|
||||
|
||||
// remove plugins
|
||||
@@ -484,20 +476,6 @@ class ChatService {
|
||||
|
||||
return agentRuntime.chat(data, { signal: params.signal });
|
||||
};
|
||||
|
||||
private prepareTools = (pluginIds: string[], { model, provider }: WorkingModel) => {
|
||||
let filterTools = toolSelectors.enabledSchema(pluginIds)(getToolStoreState());
|
||||
|
||||
// check this model can use function call
|
||||
const canUseFC = isCanUseFC(model, provider!);
|
||||
|
||||
// the rule that model can use tools:
|
||||
// 1. tools is not empty
|
||||
// 2. model can use function call
|
||||
const shouldUseTools = filterTools.length > 0 && canUseFC;
|
||||
|
||||
return shouldUseTools ? filterTools : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export const chatService = new ChatService();
|
||||
|
||||
@@ -59,101 +59,6 @@ const mockState = {
|
||||
} as ToolStoreState;
|
||||
|
||||
describe('toolSelectors', () => {
|
||||
describe('enabledSchema', () => {
|
||||
it('enabledSchema should return correct ChatCompletionFunctions array', () => {
|
||||
const result = toolSelectors.enabledSchema(['plugin-1', 'plugin-2'])(mockState);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-1____api-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-2____api-2',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('enabledSchema should return with standalone plugin', () => {
|
||||
const result = toolSelectors.enabledSchema(['plugin-4'])({
|
||||
...mockState,
|
||||
installedPlugins: [
|
||||
...mockState.installedPlugins,
|
||||
{
|
||||
identifier: 'plugin-4',
|
||||
manifest: {
|
||||
identifier: 'plugin-4',
|
||||
api: [{ name: 'api-4' }],
|
||||
type: 'standalone',
|
||||
},
|
||||
type: 'plugin',
|
||||
},
|
||||
],
|
||||
} as ToolStoreState);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'plugin-4____api-4____standalone',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('enabledSchema should return md5 hash apiName', () => {
|
||||
const result = toolSelectors.enabledSchema(['long-long-plugin-with-id'])({
|
||||
...mockState,
|
||||
installedPlugins: [
|
||||
...mockState.installedPlugins,
|
||||
{
|
||||
identifier: 'long-long-plugin-with-id',
|
||||
manifest: {
|
||||
identifier: 'long-long-plugin-with-id',
|
||||
api: [{ name: 'long-long-manifest-long-long-apiName' }],
|
||||
},
|
||||
type: 'plugin',
|
||||
},
|
||||
],
|
||||
} as ToolStoreState);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'long-long-plugin-with-id____MD5HASH_396eae4c671da3fb',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('enabledSchema should return empty', () => {
|
||||
const result = toolSelectors.enabledSchema([])(mockState);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
// fix https://github.com/lobehub/lobe-chat/issues/2036
|
||||
it('should not contain url', () => {
|
||||
const result = toolSelectors.enabledSchema(['plugin-3'])(mockState);
|
||||
expect(result[0].function).toEqual({
|
||||
description: '123123',
|
||||
name: 'plugin-3____api-3',
|
||||
parameters: {
|
||||
properties: {
|
||||
a: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result[0].function).not.toHaveProperty('url');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToolManifestLoadingStatus', () => {
|
||||
it('should return "loading" if the plugin manifest is being loaded', () => {
|
||||
const result = toolSelectors.getManifestLoadingStatus('plugin-2')(mockState);
|
||||
|
||||
@@ -2,30 +2,16 @@ import { pluginPrompts } from '@lobechat/prompts';
|
||||
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
||||
|
||||
import { MetaData } from '@/types/meta';
|
||||
import { ChatCompletionTool } from '@/types/openai/chat';
|
||||
import { LobeToolMeta } from '@/types/tool/tool';
|
||||
import { globalAgentContextManager } from '@/utils/client/GlobalAgentContextManager';
|
||||
import { hydrationPrompt } from '@/utils/promptTemplate';
|
||||
import { genToolCallingName } from '@/utils/toolCall';
|
||||
import { convertPluginManifestToToolsCalling } from '@/utils/toolManifest';
|
||||
|
||||
import { pluginHelpers } from '../helpers';
|
||||
import { ToolStoreState } from '../initialState';
|
||||
import { builtinToolSelectors } from '../slices/builtin/selectors';
|
||||
import { pluginSelectors } from '../slices/plugin/selectors';
|
||||
|
||||
const enabledSchema =
|
||||
(tools: string[] = []) =>
|
||||
(s: ToolStoreState): ChatCompletionTool[] => {
|
||||
const manifests = pluginSelectors
|
||||
.installedPluginManifestList(s)
|
||||
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
|
||||
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
||||
.filter((m) => tools.includes(m?.identifier));
|
||||
|
||||
return convertPluginManifestToToolsCalling(manifests);
|
||||
};
|
||||
|
||||
const enabledSystemRoles =
|
||||
(tools: string[] = []) =>
|
||||
(s: ToolStoreState) => {
|
||||
@@ -122,7 +108,6 @@ const isToolHasUI = (id: string) => (s: ToolStoreState) => {
|
||||
};
|
||||
|
||||
export const toolSelectors = {
|
||||
enabledSchema,
|
||||
enabledSystemRoles,
|
||||
getManifestById,
|
||||
getManifestLoadingStatus,
|
||||
|
||||
Reference in New Issue
Block a user