mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-17 04:55:51 +00:00
✨ feat: support no-fc models like deepseek r1 with online search (#6842)
* update crawler rule * feat: 完成联网集成 * update i18n * update tests * update tests * fix tests * improve performance * fix error issue * fix signal issue and improve implement * fix pricing in CNY * fix tests * filter empty providers * fix tests * improve search crawler env * fix search crawler env * fix documents
This commit is contained in:
@@ -36,7 +36,7 @@ tags:
|
||||
|
||||
<Image alt={'Clerk 添加 Webhooks 端点'} src={'https://github.com/lobehub/lobe-chat/assets/28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9'} />
|
||||
|
||||
在 endppint 中填写你的项目 URL,如 `https://your-project.com/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
|
||||
在 endpoint 中填写你的项目 URL,如 `https://your-project.com/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
|
||||
|
||||
<Callout type={'warning'}>URL 的`https://`不可缺失,须保持 URL 的完整性</Callout>
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ tags:
|
||||
|
||||
<Image alt={'Clerk 添加 Webhooks 端点'} src={'https://github.com/lobehub/lobe-chat/assets/28616219/f50f47fb-5e8e-4930-bf4e-8cf6f5b8afb9'} />
|
||||
|
||||
在 endppint 中填写你的 Vercel 项目的 URL,如 `https://your-project.vercel.app/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
|
||||
在 endpoint 中填写你的 Vercel 项目的 URL,如 `https://your-project.vercel.app/api/webhooks/clerk`。然后在订阅事件(Subscribe to events)中,勾选 user 的三个事件(`user.created` 、`user.deleted`、`user.updated`),然后点击创建。
|
||||
|
||||
<Callout type={'warning'}>URL 的`https://`不可缺失,须保持 URL 的完整性</Callout>
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "توقف",
|
||||
"warp": "تغيير السطر"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "جارٍ تحليل وفهم نواياك..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "جميع المحتويات",
|
||||
"allFiles": "جميع الملفات",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "تحديد ما إذا كان من الضروري البحث بناءً على محتوى المحادثة",
|
||||
"title": "الاتصال الذكي"
|
||||
},
|
||||
"disable": "النموذج الحالي لا يدعم استدعاء الوظائف، لذا لا يمكن استخدام وظيفة الاتصال الذكي",
|
||||
"off": {
|
||||
"desc": "استخدام المعرفة الأساسية للنموذج فقط، دون إجراء بحث عبر الإنترنت",
|
||||
"title": "إيقاف الاتصال"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "استخدام محرك البحث المدمج في النموذج"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "النموذج الحالي لا يدعم استدعاء الدوال، لذا يجب استخدام نموذج يدعم استدعاء الدوال للبحث عبر الإنترنت",
|
||||
"title": "نموذج البحث المساعد"
|
||||
},
|
||||
"title": "بحث عبر الإنترنت"
|
||||
},
|
||||
"searchAgentPlaceholder": "مساعد البحث...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Спри",
|
||||
"warp": "Нов ред"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Анализирам и разбирам вашето намерение..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Всички съдържания",
|
||||
"allFiles": "Всички файлове",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Интелигентно определяне на необходимостта от търсене въз основа на съдържанието на разговора",
|
||||
"title": "Интелигентно свързване"
|
||||
},
|
||||
"disable": "Текущият модел не поддържа извикване на функции, затова не може да се използва интелигентно свързване",
|
||||
"off": {
|
||||
"desc": "Използва само основните знания на модела, без интернет търсене",
|
||||
"title": "Изключване на свързването"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Използване на вградената търсачка на модела"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Текущият модел не поддържа извикване на функции, затова е необходимо да се комбинира с модел, който поддържа извикване на функции, за да се извърши търсене в интернет",
|
||||
"title": "Модел за търсене на помощ"
|
||||
},
|
||||
"title": "Търсене в интернет"
|
||||
},
|
||||
"searchAgentPlaceholder": "Търсач на помощ...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Stoppen",
|
||||
"warp": "Zeilenumbruch"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analysiere und verstehe Ihre Absicht..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Alle Inhalte",
|
||||
"allFiles": "Alle Dateien",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Intelligente Beurteilung, ob eine Suche basierend auf dem Gesprächsinhalt erforderlich ist",
|
||||
"title": "Intelligente Vernetzung"
|
||||
},
|
||||
"disable": "Das aktuelle Modell unterstützt keine Funktionsaufrufe, daher kann die intelligente Vernetzungsfunktion nicht verwendet werden",
|
||||
"off": {
|
||||
"desc": "Verwendet nur das Grundwissen des Modells, ohne Netzsuche",
|
||||
"title": "Vernetzung deaktivieren"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Verwenden Sie die integrierte Suchmaschine des Modells"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Das aktuelle Modell unterstützt keine Funktionsaufrufe, daher muss es mit einem Modell kombiniert werden, das Funktionsaufrufe unterstützt, um online zu suchen",
|
||||
"title": "Suchunterstützungsmodell"
|
||||
},
|
||||
"title": "Netzwerksuche"
|
||||
},
|
||||
"searchAgentPlaceholder": "Suchassistent...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Stop",
|
||||
"warp": "New Line"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analyzing and understanding your intent..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "All Content",
|
||||
"allFiles": "All Files",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Intelligently determine whether a search is needed based on the conversation content",
|
||||
"title": "Smart Online Search"
|
||||
},
|
||||
"disable": "The current model does not support function calls, so the smart online search feature is unavailable",
|
||||
"off": {
|
||||
"desc": "Use only the model's basic knowledge without performing a web search",
|
||||
"title": "Disable Online Search"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Use the model's built-in search engine"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "The current model does not support function calls, so it needs to be paired with a model that does support function calls for online searching.",
|
||||
"title": "Search Assistant Model"
|
||||
},
|
||||
"title": "Online Search"
|
||||
},
|
||||
"searchAgentPlaceholder": "Search assistants...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Detener",
|
||||
"warp": "Salto de línea"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analizando y comprendiendo su intención..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Todo el contenido",
|
||||
"allFiles": "Todos los archivos",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Determina inteligentemente si se necesita buscar según el contenido de la conversación",
|
||||
"title": "Conexión inteligente"
|
||||
},
|
||||
"disable": "El modelo actual no admite llamadas a funciones, por lo que no se puede utilizar la función de conexión inteligente",
|
||||
"off": {
|
||||
"desc": "Utiliza solo el conocimiento básico del modelo, sin realizar búsquedas en línea",
|
||||
"title": "Desactivar conexión"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Utilizar el motor de búsqueda integrado del modelo"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "El modelo actual no admite llamadas a funciones, por lo que se necesita combinarlo con un modelo que admita llamadas a funciones para realizar búsquedas en línea",
|
||||
"title": "Modelo de búsqueda auxiliar"
|
||||
},
|
||||
"title": "Búsqueda en línea"
|
||||
},
|
||||
"searchAgentPlaceholder": "Asistente de búsqueda...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "توقف",
|
||||
"warp": "خط جدید"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "در حال تحلیل و درک نیت شما..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "همه محتوا",
|
||||
"allFiles": "همه فایلها",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "به طور هوشمندانه بر اساس محتوای گفتگو تشخیص میدهد که آیا نیاز به جستجو است",
|
||||
"title": "اتصال هوشمند"
|
||||
},
|
||||
"disable": "مدل فعلی از فراخوانی توابع پشتیبانی نمیکند، بنابراین نمیتوان از ویژگی اتصال هوشمند استفاده کرد",
|
||||
"off": {
|
||||
"desc": "فقط از دانش پایه مدل استفاده میکند و جستجوی اینترنتی انجام نمیدهد",
|
||||
"title": "قطع اتصال"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "استفاده از موتور جستجوی داخلی مدل"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "مدل فعلی از فراخوانی توابع پشتیبانی نمیکند، بنابراین نیاز است که با مدلی که از فراخوانی توابع پشتیبانی میکند، برای جستجوی آنلاین ترکیب شود",
|
||||
"title": "مدل جستجوی کمکی"
|
||||
},
|
||||
"title": "جستجوی متصل"
|
||||
},
|
||||
"searchAgentPlaceholder": "جستجوی دستیار...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Arrêter",
|
||||
"warp": "Saut de ligne"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analyse et comprend votre intention..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Tout le contenu",
|
||||
"allFiles": "Tous les fichiers",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Détermine intelligemment si une recherche est nécessaire en fonction du contenu de la conversation",
|
||||
"title": "Connexion intelligente"
|
||||
},
|
||||
"disable": "Le modèle actuel ne prend pas en charge l'appel de fonctions, donc la fonctionnalité de connexion intelligente est indisponible",
|
||||
"off": {
|
||||
"desc": "Utilise uniquement les connaissances de base du modèle, sans recherche en ligne",
|
||||
"title": "Déconnexion"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Utiliser le moteur de recherche intégré du modèle"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Le modèle actuel ne prend pas en charge les appels de fonction, il doit donc être associé à un modèle prenant en charge les appels de fonction pour effectuer une recherche en ligne",
|
||||
"title": "Modèle d'assistance à la recherche"
|
||||
},
|
||||
"title": "Recherche en ligne"
|
||||
},
|
||||
"searchAgentPlaceholder": "Assistant de recherche...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Ferma",
|
||||
"warp": "A capo"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analizzando e comprendendo le tue intenzioni..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Tutti i contenuti",
|
||||
"allFiles": "Tutti i file",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Determina intelligentemente se è necessario cercare in base al contenuto della conversazione",
|
||||
"title": "Collegamento intelligente"
|
||||
},
|
||||
"disable": "Il modello attuale non supporta le chiamate di funzione, quindi non è possibile utilizzare la funzionalità di collegamento intelligente",
|
||||
"off": {
|
||||
"desc": "Utilizza solo la conoscenza di base del modello, senza effettuare ricerche online",
|
||||
"title": "Disattiva collegamento"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Utilizza il motore di ricerca integrato del modello"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Il modello attuale non supporta le chiamate di funzione, quindi è necessario utilizzarlo insieme a un modello che supporti le chiamate di funzione per cercare online",
|
||||
"title": "Modello di ricerca assistita"
|
||||
},
|
||||
"title": "Ricerca online"
|
||||
},
|
||||
"searchAgentPlaceholder": "Assistente di ricerca...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "停止",
|
||||
"warp": "改行"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "あなたの意図を分析し理解しています..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "すべてのコンテンツ",
|
||||
"allFiles": "すべてのファイル",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "会話の内容に基づいて、検索が必要かどうかを自動的に判断します",
|
||||
"title": "インテリジェント接続"
|
||||
},
|
||||
"disable": "現在のモデルは関数呼び出しをサポートしていないため、インテリジェント接続機能は使用できません",
|
||||
"off": {
|
||||
"desc": "モデルの基本知識のみを使用し、ネット検索は行いません",
|
||||
"title": "接続をオフ"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "モデル内蔵の検索エンジンを使用"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "現在のモデルは関数呼び出しをサポートしていないため、関数呼び出しをサポートするモデルと組み合わせてネット検索を行う必要があります",
|
||||
"title": "検索補助モデル"
|
||||
},
|
||||
"title": "ネット接続検索"
|
||||
},
|
||||
"searchAgentPlaceholder": "検索アシスタント...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "중지",
|
||||
"warp": "줄바꿈"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "귀하의 의도를 분석하고 이해하는 중입니다..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "모든 내용",
|
||||
"allFiles": "모든 파일",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "대화 내용을 기반으로 검색 필요성을 스마트하게 판단",
|
||||
"title": "스마트 연결"
|
||||
},
|
||||
"disable": "현재 모델은 함수 호출을 지원하지 않으므로 스마트 연결 기능을 사용할 수 없습니다",
|
||||
"off": {
|
||||
"desc": "모델의 기본 지식만 사용하고 네트워크 검색을 수행하지 않음",
|
||||
"title": "연결 끄기"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "모델 내장 검색 엔진 사용"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "현재 모델은 함수 호출을 지원하지 않으므로 함수 호출을 지원하는 모델과 함께 사용해야 인터넷 검색이 가능합니다.",
|
||||
"title": "검색 보조 모델"
|
||||
},
|
||||
"title": "연결 검색"
|
||||
},
|
||||
"searchAgentPlaceholder": "검색 도우미...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Stoppen",
|
||||
"warp": "Nieuwe regel"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Bezig met het analyseren en begrijpen van uw intentie..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Alle inhoud",
|
||||
"allFiles": "Alle bestanden",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Intelligente beoordeling of er gezocht moet worden op basis van de gesprekinhoud",
|
||||
"title": "Slimme verbinding"
|
||||
},
|
||||
"disable": "Het huidige model ondersteunt geen functieaanroepen, dus de slimme verbindingsfunctie kan niet worden gebruikt",
|
||||
"off": {
|
||||
"desc": "Gebruik alleen de basiskennis van het model, zonder online zoekopdrachten",
|
||||
"title": "Verbinding uitschakelen"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Gebruik de ingebouwde zoekmachine van het model"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Het huidige model ondersteunt geen functieaanroepen, dus het moet worden gecombineerd met een model dat functieaanroepen ondersteunt om online te zoeken",
|
||||
"title": "Zoekhulpmiddel model"
|
||||
},
|
||||
"title": "Online zoeken"
|
||||
},
|
||||
"searchAgentPlaceholder": "Zoekassistent...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Zatrzymaj",
|
||||
"warp": "Złamanie wiersza"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analizuję i rozumiem Twoje intencje..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Wszystkie treści",
|
||||
"allFiles": "Wszystkie pliki",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Inteligentne określenie, czy potrzebne jest wyszukiwanie na podstawie treści rozmowy",
|
||||
"title": "Inteligentne połączenie"
|
||||
},
|
||||
"disable": "Aktualny model nie obsługuje wywołań funkcji, więc nie można korzystać z inteligentnego połączenia",
|
||||
"off": {
|
||||
"desc": "Używaj tylko podstawowej wiedzy modelu, bez wyszukiwania w sieci",
|
||||
"title": "Wyłącz połączenie"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Użyj wbudowanej wyszukiwarki modelu"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Aktualny model nie obsługuje wywołań funkcji, dlatego wymaga współpracy z modelem obsługującym wywołania funkcji, aby móc przeszukiwać sieć",
|
||||
"title": "Model wspomagający wyszukiwanie"
|
||||
},
|
||||
"title": "Wyszukiwanie w sieci"
|
||||
},
|
||||
"searchAgentPlaceholder": "Wyszukaj pomocnika...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Parar",
|
||||
"warp": "Quebrar linha"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Analisando e compreendendo sua intenção..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Todo conteúdo",
|
||||
"allFiles": "Todos os arquivos",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Determina inteligentemente se é necessário pesquisar com base no conteúdo da conversa",
|
||||
"title": "Conexão Inteligente"
|
||||
},
|
||||
"disable": "O modelo atual não suporta chamadas de função, portanto, a funcionalidade de conexão inteligente não está disponível",
|
||||
"off": {
|
||||
"desc": "Usa apenas o conhecimento básico do modelo, sem realizar pesquisas na web",
|
||||
"title": "Desativar Conexão"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Usar o mecanismo de busca embutido no modelo"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "O modelo atual não suporta chamadas de função, portanto, é necessário combiná-lo com um modelo que suporte chamadas de função para realizar buscas na internet",
|
||||
"title": "Modelo de busca auxiliar"
|
||||
},
|
||||
"title": "Pesquisa Conectada"
|
||||
},
|
||||
"searchAgentPlaceholder": "Assistente de busca...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Остановить",
|
||||
"warp": "Перенос строки"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Анализ и понимание вашего намерения..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Все содержимое",
|
||||
"allFiles": "Все файлы",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Интеллектуально определяет необходимость поиска на основе содержания диалога",
|
||||
"title": "Интеллектуальное подключение к сети"
|
||||
},
|
||||
"disable": "Текущая модель не поддерживает вызовы функций, поэтому функция интеллектуального подключения к сети недоступна",
|
||||
"off": {
|
||||
"desc": "Использует только базовые знания модели, без сетевого поиска",
|
||||
"title": "Отключить подключение к сети"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Использовать встроенный поисковый движок модели"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Текущая модель не поддерживает вызов функций, поэтому необходимо использовать модель, поддерживающую вызов функций, для поиска в интернете",
|
||||
"title": "Модель поиска"
|
||||
},
|
||||
"title": "Поиск в сети"
|
||||
},
|
||||
"searchAgentPlaceholder": "Поиск помощника...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Dur",
|
||||
"warp": "Satır atla"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Niyetinizi analiz ediyor ve anlıyor..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Tüm İçerik",
|
||||
"allFiles": "Tüm Dosyalar",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Sohbet içeriğine göre akıllıca arama gerekip gerekmediğini belirler",
|
||||
"title": "Akıllı Bağlantı"
|
||||
},
|
||||
"disable": "Mevcut model fonksiyon çağrısını desteklemediği için akıllı bağlantı özelliği kullanılamaz",
|
||||
"off": {
|
||||
"desc": "Sadece modelin temel bilgilerini kullanır, ağ araması yapmaz",
|
||||
"title": "Bağlantıyı Kapat"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Modelin yerleşik arama motorunu kullan"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Mevcut model fonksiyon çağrısını desteklemiyor, bu nedenle çevrimiçi arama yapmak için fonksiyon çağrısını destekleyen bir model ile birlikte kullanılması gerekiyor",
|
||||
"title": "Arama Yardımcı Modeli"
|
||||
},
|
||||
"title": "Ağ Araması"
|
||||
},
|
||||
"searchAgentPlaceholder": "Arama Asistanı...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "Dừng",
|
||||
"warp": "Xuống dòng"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "Đang phân tích và hiểu ý định của bạn..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "Tất cả nội dung",
|
||||
"allFiles": "Tất cả tệp",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "Thông minh xác định xem có cần tìm kiếm dựa trên nội dung cuộc trò chuyện",
|
||||
"title": "Kết nối thông minh"
|
||||
},
|
||||
"disable": "Mô hình hiện tại không hỗ trợ gọi hàm, do đó không thể sử dụng chức năng kết nối thông minh",
|
||||
"off": {
|
||||
"desc": "Chỉ sử dụng kiến thức cơ bản của mô hình, không thực hiện tìm kiếm trên mạng",
|
||||
"title": "Tắt kết nối"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "Sử dụng công cụ tìm kiếm tích hợp của mô hình"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "Mô hình hiện tại không hỗ trợ gọi hàm, vì vậy cần kết hợp với mô hình hỗ trợ gọi hàm để tìm kiếm trực tuyến",
|
||||
"title": "Mô hình hỗ trợ tìm kiếm"
|
||||
},
|
||||
"title": "Tìm kiếm trực tuyến"
|
||||
},
|
||||
"searchAgentPlaceholder": "Trợ lý tìm kiếm...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "停止",
|
||||
"warp": "换行"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "正在分析并理解意图您的意图..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "所有内容",
|
||||
"allFiles": "所有文件",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "根据对话内容智能判断是否需要搜索",
|
||||
"title": "智能联网"
|
||||
},
|
||||
"disable": "当前模型不支持函数调用,因此无法使用智能联网功能",
|
||||
"off": {
|
||||
"desc": "仅使用模型的基础知识,不进行网络搜索",
|
||||
"title": "关闭联网"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "使用模型内置搜索引擎"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "当前模型不支持函数调用,因此需要搭配支持函数调用的模型才能联网搜索",
|
||||
"title": "搜索辅助模型"
|
||||
},
|
||||
"title": "联网搜索"
|
||||
},
|
||||
"searchAgentPlaceholder": "搜索助手...",
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
"stop": "停止",
|
||||
"warp": "換行"
|
||||
},
|
||||
"intentUnderstanding": {
|
||||
"title": "正在分析並理解您的意圖..."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"all": "所有內容",
|
||||
"allFiles": "所有檔案",
|
||||
@@ -144,7 +147,6 @@
|
||||
"desc": "根據對話內容智能判斷是否需要搜尋",
|
||||
"title": "智能連網"
|
||||
},
|
||||
"disable": "當前模型不支持函數調用,因此無法使用智能連網功能",
|
||||
"off": {
|
||||
"desc": "僅使用模型的基礎知識,不進行網路搜尋",
|
||||
"title": "關閉連網"
|
||||
@@ -155,6 +157,10 @@
|
||||
},
|
||||
"useModelBuiltin": "使用模型內建搜尋引擎"
|
||||
},
|
||||
"searchModel": {
|
||||
"desc": "當前模型不支持函數調用,因此需要搭配支持函數調用的模型才能聯網搜索",
|
||||
"title": "搜索輔助模型"
|
||||
},
|
||||
"title": "連網搜尋"
|
||||
},
|
||||
"searchAgentPlaceholder": "搜尋助手...",
|
||||
|
||||
@@ -3,8 +3,18 @@ import { CrawlUrlRule } from './type';
|
||||
import { crawUrlRules } from './urlRules';
|
||||
import { applyUrlRules } from './utils/appUrlRules';
|
||||
|
||||
interface CrawlOptions {
|
||||
impls?: string[];
|
||||
}
|
||||
|
||||
export class Crawler {
|
||||
impls = ['naive', 'jina', 'browserless'] as const;
|
||||
impls: CrawlImplType[];
|
||||
|
||||
constructor(options: CrawlOptions = {}) {
|
||||
this.impls = !!options.impls?.length
|
||||
? (options.impls.filter((impl) => Object.keys(crawlImpls).includes(impl)) as CrawlImplType[])
|
||||
: (['naive', 'jina', 'browserless'] as const);
|
||||
}
|
||||
|
||||
/**
|
||||
* 爬取网页内容
|
||||
|
||||
@@ -181,7 +181,10 @@ describe('POST handler', () => {
|
||||
const response = await POST(request as unknown as Request, { params: mockParams });
|
||||
|
||||
expect(response).toEqual(mockChatResponse);
|
||||
expect(AgentRuntime.prototype.chat).toHaveBeenCalledWith(mockChatPayload, { user: 'abc' });
|
||||
expect(AgentRuntime.prototype.chat).toHaveBeenCalledWith(mockChatPayload, {
|
||||
user: 'abc',
|
||||
signal: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error response when chat completion fails', async () => {
|
||||
|
||||
@@ -39,7 +39,11 @@ export const POST = checkAuth(async (req: Request, { params, jwtPayload, createR
|
||||
});
|
||||
}
|
||||
|
||||
return await agentRuntime.chat(data, { user: jwtPayload.userId, ...traceOptions });
|
||||
return await agentRuntime.chat(data, {
|
||||
user: jwtPayload.userId,
|
||||
...traceOptions,
|
||||
signal: req.signal,
|
||||
});
|
||||
} catch (e) {
|
||||
const {
|
||||
errorType = ChatErrorType.InternalServerError,
|
||||
|
||||
+4
-3
@@ -1,4 +1,5 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }, borderWidth: number = 2.5) => ({
|
||||
background: css`
|
||||
@@ -44,7 +45,7 @@ export const useStyles = createStyles(({ css, token }, borderWidth: number = 2.5
|
||||
`,
|
||||
}));
|
||||
|
||||
const Loader = () => {
|
||||
const CircleLoader = memo(() => {
|
||||
const { styles } = useStyles();
|
||||
|
||||
return (
|
||||
@@ -53,6 +54,6 @@ const Loader = () => {
|
||||
<div className={styles.background} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Loader;
|
||||
export default CircleLoader;
|
||||
@@ -4,10 +4,12 @@ import { z } from 'zod';
|
||||
export const getToolsConfig = () => {
|
||||
return createEnv({
|
||||
runtimeEnv: {
|
||||
CRAWLER_IMPLS: process.env.CRAWLER_IMPLS,
|
||||
SEARXNG_URL: process.env.SEARXNG_URL,
|
||||
},
|
||||
|
||||
server: {
|
||||
CRAWLER_IMPLS: z.string().optional(),
|
||||
SEARXNG_URL: z.string().url().optional(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,11 @@ export const DEFAUTT_AGENT_TTS_CONFIG: LobeAgentTTSConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_AGENT_SEARCH_FC_MODEL = {
|
||||
model: DEFAULT_MODEL,
|
||||
provider: ModelProvider.OpenAI,
|
||||
};
|
||||
|
||||
export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
|
||||
autoCreateTopicThreshold: 2,
|
||||
displayMode: 'chat',
|
||||
@@ -22,6 +27,7 @@ export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
|
||||
enableReasoning: false,
|
||||
historyCount: 8,
|
||||
reasoningBudgetToken: 1024,
|
||||
searchFCModel: DEFAULT_AGENT_SEARCH_FC_MODEL,
|
||||
searchMode: 'off',
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import InfoTooltip from '@/components/InfoTooltip';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentChatConfigSelectors } from '@/store/agent/slices/chat';
|
||||
|
||||
import FunctionCallingModelSelect from './FunctionCallingModelSelect';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
check: css`
|
||||
margin-inline-start: 12px;
|
||||
font-size: 16px;
|
||||
color: ${token.colorPrimary};
|
||||
`,
|
||||
content: css`
|
||||
flex: 1;
|
||||
width: 230px;
|
||||
`,
|
||||
description: css`
|
||||
width: 200px;
|
||||
font-size: 12px;
|
||||
color: ${token.colorTextSecondary};
|
||||
`,
|
||||
title: css`
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${token.colorText};
|
||||
`,
|
||||
}));
|
||||
|
||||
const FCSearchModel = () => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { styles } = useStyles();
|
||||
const [searchFCModel, updateAgentChatConfig] = useAgentStore((s) => [
|
||||
agentChatConfigSelectors.searchFCModel(s),
|
||||
s.updateAgentChatConfig,
|
||||
]);
|
||||
return (
|
||||
<Flexbox distribution={'space-between'} gap={16} horizontal padding={8}>
|
||||
<Flexbox align={'center'} gap={4} horizontal>
|
||||
<Flexbox className={styles.title}>{t('search.searchModel.title')}</Flexbox>
|
||||
<InfoTooltip title={t('search.searchModel.desc')} />
|
||||
</Flexbox>
|
||||
<FunctionCallingModelSelect
|
||||
onChange={(value) => {
|
||||
updateAgentChatConfig({ searchFCModel: value });
|
||||
}}
|
||||
value={searchFCModel}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default FCSearchModel;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Select, SelectProps } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
|
||||
import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
|
||||
import { WorkingModel } from '@/types/agent';
|
||||
import { EnabledProviderWithModels } from '@/types/aiProvider';
|
||||
|
||||
const useStyles = createStyles(({ css, prefixCls }) => ({
|
||||
select: css`
|
||||
&.${prefixCls}-select-dropdown .${prefixCls}-select-item-option-grouped {
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
interface ModelOption {
|
||||
label: any;
|
||||
provider: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ModelSelectProps {
|
||||
onChange?: (props: WorkingModel) => void;
|
||||
showAbility?: boolean;
|
||||
value?: WorkingModel;
|
||||
}
|
||||
|
||||
const ModelSelect = memo<ModelSelectProps>(({ value, onChange }) => {
|
||||
const enabledList = useEnabledChatModels();
|
||||
|
||||
const { styles } = useStyles();
|
||||
|
||||
const options = useMemo<SelectProps['options']>(() => {
|
||||
const getChatModels = (provider: EnabledProviderWithModels) =>
|
||||
provider.children
|
||||
.filter((model) => !!model.abilities.functionCall)
|
||||
.map((model) => ({
|
||||
label: <ModelItemRender {...model} {...model.abilities} showInfoTag={false} />,
|
||||
provider: provider.id,
|
||||
value: `${provider.id}/${model.id}`,
|
||||
}));
|
||||
|
||||
if (enabledList.length === 1) {
|
||||
const provider = enabledList[0];
|
||||
|
||||
return getChatModels(provider);
|
||||
}
|
||||
|
||||
return enabledList
|
||||
.filter((p) => !!getChatModels(p).length)
|
||||
.map((provider) => {
|
||||
const options = getChatModels(provider);
|
||||
|
||||
return {
|
||||
label: (
|
||||
<ProviderItemRender
|
||||
logo={provider.logo}
|
||||
name={provider.name}
|
||||
provider={provider.id}
|
||||
source={provider.source}
|
||||
/>
|
||||
),
|
||||
options,
|
||||
};
|
||||
});
|
||||
}, [enabledList]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={(value, option) => {
|
||||
const model = value.split('/').slice(1).join('/');
|
||||
onChange?.({ model, provider: (option as unknown as ModelOption).provider });
|
||||
}}
|
||||
options={options}
|
||||
popupClassName={styles.select}
|
||||
popupMatchSelectWidth={false}
|
||||
value={`${value?.provider}/${value?.model}`}
|
||||
variant={'filled'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModelSelect;
|
||||
@@ -12,6 +12,7 @@ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/c
|
||||
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
||||
import { SearchMode } from '@/types/search';
|
||||
|
||||
import FCSearchModel from './FCSearchModel';
|
||||
import ModelBuiltinSearch from './ModelBuiltinSearch';
|
||||
|
||||
const { Text } = Typography;
|
||||
@@ -38,10 +39,6 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
font-size: 12px;
|
||||
color: ${token.colorTextSecondary};
|
||||
`,
|
||||
disable: css`
|
||||
cursor: not-allowed;
|
||||
opacity: 0.45;
|
||||
`,
|
||||
iconWrapper: css`
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
@@ -80,8 +77,7 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
`,
|
||||
}));
|
||||
|
||||
const Item = memo<NetworkOption>(({ value, description, icon, label, disable }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const Item = memo<NetworkOption>(({ value, description, icon, label }) => {
|
||||
const { styles } = useStyles();
|
||||
const [mode, updateAgentChatConfig] = useAgentStore((s) => [
|
||||
agentChatConfigSelectors.agentSearchMode(s),
|
||||
@@ -96,12 +92,12 @@ const Item = memo<NetworkOption>(({ value, description, icon, label, disable })
|
||||
key={value}
|
||||
onClick={() => updateAgentChatConfig({ searchMode: value })}
|
||||
>
|
||||
<Flexbox className={disable ? styles.disable : ''} gap={8} horizontal>
|
||||
<Flexbox gap={8} horizontal>
|
||||
<div className={styles.iconWrapper}>{icon}</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{label}</div>
|
||||
<Text className={styles.description} type="secondary">
|
||||
{disable ? t('search.mode.disable') : description}
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
</Flexbox>
|
||||
@@ -132,34 +128,24 @@ const AINetworkSettings = memo<AINetworkSettingsProps>(() => {
|
||||
label: t('search.mode.off.title'),
|
||||
value: 'off',
|
||||
},
|
||||
// 等应用层联网功能做好以后再开启
|
||||
// {
|
||||
// description: t('search.mode.on.desc'),
|
||||
// icon: <WifiOutlined />,
|
||||
// label: t('search.mode.on.title'),
|
||||
// value: 'on',
|
||||
// },
|
||||
{
|
||||
description: t('search.mode.auto.desc'),
|
||||
disable: !supportFC,
|
||||
icon: <Icon icon={SparklesIcon} />,
|
||||
label: t('search.mode.auto.title'),
|
||||
value: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
const showDivider = isModelHasBuiltinSearchConfig || !supportFC;
|
||||
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
{options.map((option) => (
|
||||
<Item {...option} key={option.value} />
|
||||
))}
|
||||
|
||||
{isModelHasBuiltinSearchConfig && (
|
||||
<>
|
||||
<Divider style={{ margin: 0, paddingInline: 12 }} />
|
||||
<ModelBuiltinSearch />
|
||||
</>
|
||||
)}
|
||||
{showDivider && <Divider style={{ margin: 0, paddingInline: 12 }} />}
|
||||
{isModelHasBuiltinSearchConfig && <ModelBuiltinSearch />}
|
||||
{!supportFC && <FCSearchModel />}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,11 +7,10 @@ import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { getPrice } from '@/features/Conversation/Extras/Usage/UsageDetail/pricing';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { systemStatusSelectors } from '@/store/global/selectors';
|
||||
import { LobeDefaultAiModelListItem } from '@/types/aiModel';
|
||||
import { ModelPriceCurrency } from '@/types/llm';
|
||||
import { formatPriceByCurrency } from '@/utils/format';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => {
|
||||
return {
|
||||
@@ -40,19 +39,8 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
|
||||
const isShowCredit = useGlobalStore(systemStatusSelectors.isShowCredit) && !!pricing;
|
||||
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
|
||||
|
||||
const inputPrice = formatPriceByCurrency(pricing?.input, pricing?.currency as ModelPriceCurrency);
|
||||
const cachedInputPrice = formatPriceByCurrency(
|
||||
pricing?.cachedInput,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
const writeCacheInputPrice = formatPriceByCurrency(
|
||||
pricing?.writeCacheInput,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
const outputPrice = formatPriceByCurrency(
|
||||
pricing?.output,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
const formatPrice = getPrice(pricing || {});
|
||||
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
<Flexbox
|
||||
@@ -103,37 +91,41 @@ const ModelCard = memo<ModelCardProps>(({ pricing, id, provider, displayName })
|
||||
{pricing?.cachedInput && (
|
||||
<Tooltip
|
||||
title={t('messages.modelCard.pricing.inputCachedTokens', {
|
||||
amount: cachedInputPrice,
|
||||
amount: formatPrice.cachedInput,
|
||||
})}
|
||||
>
|
||||
<Flexbox gap={2} horizontal>
|
||||
<Icon icon={CircleFadingArrowUp} />
|
||||
{cachedInputPrice}
|
||||
{formatPrice.cachedInput}
|
||||
</Flexbox>
|
||||
</Tooltip>
|
||||
)}
|
||||
{pricing?.writeCacheInput && (
|
||||
<Tooltip
|
||||
title={t('messages.modelCard.pricing.writeCacheInputTokens', {
|
||||
amount: writeCacheInputPrice,
|
||||
amount: formatPrice.writeCacheInput,
|
||||
})}
|
||||
>
|
||||
<Flexbox gap={2} horizontal>
|
||||
<Icon icon={BookUp2Icon} />
|
||||
{writeCacheInputPrice}
|
||||
{formatPrice.writeCacheInput}
|
||||
</Flexbox>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('messages.modelCard.pricing.inputTokens', { amount: inputPrice })}>
|
||||
<Tooltip
|
||||
title={t('messages.modelCard.pricing.inputTokens', { amount: formatPrice.input })}
|
||||
>
|
||||
<Flexbox gap={2} horizontal>
|
||||
<Icon icon={ArrowUpFromDot} />
|
||||
{inputPrice}
|
||||
{formatPrice.input}
|
||||
</Flexbox>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('messages.modelCard.pricing.outputTokens', { amount: outputPrice })}>
|
||||
<Tooltip
|
||||
title={t('messages.modelCard.pricing.outputTokens', { amount: formatPrice.output })}
|
||||
>
|
||||
<Flexbox gap={2} horizontal>
|
||||
<Icon icon={ArrowDownToDot} />
|
||||
{outputPrice}
|
||||
{formatPrice.output}
|
||||
</Flexbox>
|
||||
</Tooltip>
|
||||
</Flexbox>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChatModelPricing } from '@/types/aiModel';
|
||||
import { ModelPriceCurrency } from '@/types/llm';
|
||||
import { formatPriceByCurrency } from '@/utils/format';
|
||||
|
||||
export const getPrice = (pricing: ChatModelPricing) => {
|
||||
const inputPrice = formatPriceByCurrency(pricing?.input, pricing?.currency as ModelPriceCurrency);
|
||||
const cachedInputPrice = formatPriceByCurrency(
|
||||
pricing?.cachedInput,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
const writeCacheInputPrice = formatPriceByCurrency(
|
||||
pricing?.writeCacheInput,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
const outputPrice = formatPriceByCurrency(
|
||||
pricing?.output,
|
||||
pricing?.currency as ModelPriceCurrency,
|
||||
);
|
||||
|
||||
return {
|
||||
cachedInput: Number(cachedInputPrice),
|
||||
input: Number(inputPrice),
|
||||
output: Number(outputPrice),
|
||||
writeCacheInput: Number(writeCacheInputPrice),
|
||||
};
|
||||
};
|
||||
@@ -70,7 +70,7 @@ describe('getDetailsToken', () => {
|
||||
const result = getDetailsToken(usage, mockModelCard);
|
||||
|
||||
expect(result.inputCached).toEqual({
|
||||
credit: 0, // 50 * 0.005 = 0.25, rounded to 0
|
||||
credit: 1,
|
||||
token: 50,
|
||||
});
|
||||
|
||||
@@ -165,12 +165,12 @@ describe('getDetailsToken', () => {
|
||||
const result = getDetailsToken(usage, mockModelCard);
|
||||
|
||||
// uncachedInput: (200 - 50) * 0.01 = 1.5 -> 2
|
||||
// cachedInput: 50 * 0.005 = 0.25 -> 0
|
||||
// cachedInput: 50 * 0.005 = 0.25 -> 1
|
||||
// totalOutput: 300 * 0.02 = 6
|
||||
// totalCredit = 2 + 0 + 6 = 8
|
||||
// totalCredit = 2 + 1 + 6 = 9
|
||||
|
||||
expect(result.totalTokens).toEqual({
|
||||
credit: 8,
|
||||
credit: 9,
|
||||
token: 500,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { LobeDefaultAiModelListItem } from '@/types/aiModel';
|
||||
import { ChatModelPricing, LobeDefaultAiModelListItem } from '@/types/aiModel';
|
||||
import { ModelTokensUsage } from '@/types/message';
|
||||
|
||||
import { getPrice } from './pricing';
|
||||
|
||||
const calcCredit = (token: number, pricing?: number) => {
|
||||
if (!pricing) return '-';
|
||||
|
||||
@@ -29,23 +31,26 @@ export const getDetailsToken = (
|
||||
? usage?.inputCacheMissTokens
|
||||
: totalInputTokens - (inputCacheTokens || 0);
|
||||
|
||||
// Pricing
|
||||
const formatPrice = getPrice(modelCard?.pricing as ChatModelPricing);
|
||||
|
||||
const inputCacheMissCredit = (
|
||||
!!inputCacheMissTokens ? calcCredit(inputCacheMissTokens, modelCard?.pricing?.input) : 0
|
||||
!!inputCacheMissTokens ? calcCredit(inputCacheMissTokens, formatPrice.input) : 0
|
||||
) as number;
|
||||
|
||||
const inputCachedCredit = (
|
||||
!!inputCacheTokens ? calcCredit(inputCacheTokens, modelCard?.pricing?.cachedInput) : 0
|
||||
!!inputCacheTokens ? calcCredit(inputCacheTokens, formatPrice.cachedInput) : 0
|
||||
) as number;
|
||||
|
||||
const inputWriteCachedCredit = !!inputWriteCacheTokens
|
||||
? (calcCredit(inputWriteCacheTokens, modelCard?.pricing?.writeCacheInput) as number)
|
||||
? (calcCredit(inputWriteCacheTokens, formatPrice.writeCacheInput) as number)
|
||||
: 0;
|
||||
|
||||
const totalOutputCredit = (
|
||||
!!totalOutputTokens ? calcCredit(totalOutputTokens, modelCard?.pricing?.output) : 0
|
||||
!!totalOutputTokens ? calcCredit(totalOutputTokens, formatPrice.output) : 0
|
||||
) as number;
|
||||
const totalInputCredit = (
|
||||
!!totalInputTokens ? calcCredit(totalInputTokens, modelCard?.pricing?.output) : 0
|
||||
!!totalInputTokens ? calcCredit(totalInputTokens, formatPrice.output) : 0
|
||||
) as number;
|
||||
|
||||
const totalCredit =
|
||||
@@ -69,13 +74,13 @@ export const getDetailsToken = (
|
||||
: undefined,
|
||||
inputCitation: !!usage.inputCitationTokens
|
||||
? {
|
||||
credit: calcCredit(usage.inputCitationTokens, modelCard?.pricing?.input),
|
||||
credit: calcCredit(usage.inputCitationTokens, formatPrice.input),
|
||||
token: usage.inputCitationTokens,
|
||||
}
|
||||
: undefined,
|
||||
inputText: !!inputTextTokens
|
||||
? {
|
||||
credit: calcCredit(inputTextTokens, modelCard?.pricing?.input),
|
||||
credit: calcCredit(inputTextTokens, formatPrice.input),
|
||||
token: inputTextTokens,
|
||||
}
|
||||
: undefined,
|
||||
@@ -89,13 +94,13 @@ export const getDetailsToken = (
|
||||
: undefined,
|
||||
outputReasoning: !!outputReasoningTokens
|
||||
? {
|
||||
credit: calcCredit(outputReasoningTokens, modelCard?.pricing?.output),
|
||||
credit: calcCredit(outputReasoningTokens, formatPrice.output),
|
||||
token: outputReasoningTokens,
|
||||
}
|
||||
: undefined,
|
||||
outputText: !!outputTextTokens
|
||||
? {
|
||||
credit: calcCredit(outputTextTokens, modelCard?.pricing?.output),
|
||||
credit: calcCredit(outputTextTokens, formatPrice.output),
|
||||
token: outputTextTokens,
|
||||
}
|
||||
: undefined,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import CircleLoader from '@/components/CircleLoader';
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
|
||||
const useStyles = createStyles(({ token }) => ({
|
||||
shinyText: shinyTextStylish(token),
|
||||
}));
|
||||
|
||||
const IntentUnderstanding = () => {
|
||||
const { styles } = useStyles();
|
||||
const { t } = useTranslation('chat');
|
||||
|
||||
return (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<CircleLoader />
|
||||
<Flexbox className={styles.shinyText} horizontal>
|
||||
{t('intentUnderstanding.title')}
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
};
|
||||
export default IntentUnderstanding;
|
||||
@@ -6,6 +6,7 @@ import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import Loader from '@/components/CircleLoader';
|
||||
import PluginAvatar from '@/features/PluginAvatar';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { chatSelectors } from '@/store/chat/selectors';
|
||||
@@ -14,8 +15,6 @@ import { toolSelectors } from '@/store/tool/selectors';
|
||||
import { shinyTextStylish } from '@/styles/loading';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
|
||||
import Loader from './Loader';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
apiName: css`
|
||||
overflow: hidden;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ChatMessage } from '@/types/message';
|
||||
|
||||
import { DefaultMessage } from '../Default';
|
||||
import FileChunks from './FileChunks';
|
||||
import IntentUnderstanding from './IntentUnderstanding';
|
||||
import Reasoning from './Reasoning';
|
||||
import SearchGrounding from './SearchGrounding';
|
||||
import Tool from './Tool';
|
||||
@@ -24,6 +25,8 @@ export const AssistantMessage = memo<
|
||||
|
||||
const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
|
||||
|
||||
const isIntentUnderstanding = useChatStore(aiChatSelectors.isIntentUnderstanding(id));
|
||||
|
||||
const showSearch = !!search && !!search.citations?.length;
|
||||
|
||||
// remove \n to avoid empty content
|
||||
@@ -32,6 +35,8 @@ export const AssistantMessage = memo<
|
||||
(!!props.reasoning && props.reasoning.content?.trim() !== '') ||
|
||||
(!props.reasoning && isReasoning);
|
||||
|
||||
const showFileChunks = !!chunksList && chunksList.length > 0;
|
||||
|
||||
return editing ? (
|
||||
<DefaultMessage
|
||||
content={content}
|
||||
@@ -44,16 +49,20 @@ export const AssistantMessage = memo<
|
||||
{showSearch && (
|
||||
<SearchGrounding citations={search?.citations} searchQueries={search?.searchQueries} />
|
||||
)}
|
||||
{!!chunksList && chunksList.length > 0 && <FileChunks data={chunksList} />}
|
||||
{showFileChunks && <FileChunks data={chunksList} />}
|
||||
{showReasoning && <Reasoning {...props.reasoning} id={id} />}
|
||||
{content && (
|
||||
<DefaultMessage
|
||||
addIdOnDOM={false}
|
||||
content={content}
|
||||
id={id}
|
||||
isToolCallGenerating={isToolCallGenerating}
|
||||
{...props}
|
||||
/>
|
||||
{isIntentUnderstanding ? (
|
||||
<IntentUnderstanding />
|
||||
) : (
|
||||
content && (
|
||||
<DefaultMessage
|
||||
addIdOnDOM={false}
|
||||
content={content}
|
||||
id={id}
|
||||
isToolCallGenerating={isToolCallGenerating}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{tools && (
|
||||
<Flexbox gap={8}>
|
||||
|
||||
@@ -115,10 +115,7 @@ const ModelSwitchPanel = memo<PropsWithChildren>(({ children }) => {
|
||||
provider={provider.id}
|
||||
source={provider.source}
|
||||
/>
|
||||
<Link
|
||||
href={isDeprecatedEdition ? '/settings/llm' : `/settings/provider/${provider.id}`}
|
||||
prefetch={false}
|
||||
>
|
||||
<Link href={isDeprecatedEdition ? '/settings/llm' : `/settings/provider/${provider.id}`}>
|
||||
<ActionIcon
|
||||
icon={LucideBolt}
|
||||
size={'small'}
|
||||
|
||||
@@ -9,19 +9,11 @@ export const useAgentEnableSearch = () => {
|
||||
agentChatConfigSelectors.agentSearchMode(s),
|
||||
]);
|
||||
|
||||
const isModelSupportToolUse = useAiInfraStore(
|
||||
aiModelSelectors.isModelSupportToolUse(model, provider),
|
||||
);
|
||||
const searchImpl = useAiInfraStore(aiModelSelectors.modelBuiltinSearchImpl(model, provider));
|
||||
|
||||
// 只要是内置的搜索实现,一定可以联网搜索
|
||||
if (searchImpl === 'internal') return true;
|
||||
|
||||
// 如果是关闭状态,一定不能联网搜索
|
||||
if (agentSearchMode === 'off') return false;
|
||||
|
||||
// 如果是智能模式,根据是否支持 Tool Calling 判断
|
||||
if (agentSearchMode === 'auto') {
|
||||
return isModelSupportToolUse;
|
||||
}
|
||||
return agentSearchMode !== 'off';
|
||||
};
|
||||
|
||||
@@ -65,6 +65,9 @@ export default {
|
||||
stop: '停止',
|
||||
warp: '换行',
|
||||
},
|
||||
intentUnderstanding: {
|
||||
title: '正在分析并理解意图您的意图...',
|
||||
},
|
||||
knowledgeBase: {
|
||||
all: '所有内容',
|
||||
allFiles: '所有文件',
|
||||
@@ -142,13 +145,11 @@ export default {
|
||||
searchQueries: '搜索关键词',
|
||||
title: '已搜索到 {{count}} 个结果',
|
||||
},
|
||||
|
||||
mode: {
|
||||
auto: {
|
||||
desc: '根据对话内容智能判断是否需要搜索',
|
||||
title: '智能联网',
|
||||
},
|
||||
disable: '当前模型不支持函数调用,因此无法使用智能联网功能',
|
||||
off: {
|
||||
desc: '仅使用模型的基础知识,不进行网络搜索',
|
||||
title: '关闭联网',
|
||||
@@ -159,7 +160,10 @@ export default {
|
||||
},
|
||||
useModelBuiltin: '使用模型内置搜索引擎',
|
||||
},
|
||||
|
||||
searchModel: {
|
||||
desc: '当前模型不支持函数调用,因此需要搭配支持函数调用的模型才能联网搜索',
|
||||
title: '搜索辅助模型',
|
||||
},
|
||||
title: '联网搜索',
|
||||
},
|
||||
searchAgentPlaceholder: '搜索助手...',
|
||||
|
||||
@@ -20,7 +20,14 @@ export const searchRouter = router({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const crawler = new Crawler();
|
||||
const envString = toolsEnv.CRAWLER_IMPLS || '';
|
||||
|
||||
// 处理全角逗号和多余空格
|
||||
let envValue = envString.replaceAll(',', ',').trim();
|
||||
|
||||
const impls = envValue.split(',').filter(Boolean);
|
||||
|
||||
const crawler = new Crawler({ impls });
|
||||
|
||||
const results = await pMap(
|
||||
input.urls,
|
||||
|
||||
@@ -877,6 +877,7 @@ describe('ChatService', () => {
|
||||
// 重新模拟模块,设置 isServerMode 为 true
|
||||
vi.doMock('@/const/version', () => ({
|
||||
isServerMode: true,
|
||||
isDeprecatedEdition: false,
|
||||
}));
|
||||
|
||||
// 需要在修改模拟后重新导入相关模块
|
||||
|
||||
+63
-30
@@ -32,6 +32,7 @@ import {
|
||||
userProfileSelectors,
|
||||
} from '@/store/user/selectors';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
import { WorkingModel } from '@/types/agent';
|
||||
import { ChatErrorType } from '@/types/fetch';
|
||||
import { ChatMessage, MessageToolCall } from '@/types/message';
|
||||
import type { ChatStreamPayload, OpenAIChatMessage } from '@/types/openai/chat';
|
||||
@@ -201,17 +202,10 @@ class ChatService {
|
||||
|
||||
// ============ 2. preprocess tools ============ //
|
||||
|
||||
let filterTools = toolSelectors.enabledSchema(pluginIds)(getToolStoreState());
|
||||
|
||||
// check this model can use function call
|
||||
const canUseFC = isCanUseFC(payload.model, payload.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;
|
||||
|
||||
const tools = shouldUseTools ? filterTools : undefined;
|
||||
const tools = this.prepareTools(pluginIds, {
|
||||
model: payload.model,
|
||||
provider: payload.provider!,
|
||||
});
|
||||
|
||||
// ============ 3. process extend params ============ //
|
||||
|
||||
@@ -433,15 +427,29 @@ class ChatService {
|
||||
onLoadingChange?.(true);
|
||||
|
||||
try {
|
||||
await this.getChatCompletion(params, {
|
||||
onErrorHandle: (error) => {
|
||||
errorHandle(new Error(error.message), error);
|
||||
},
|
||||
onFinish,
|
||||
onMessageHandle,
|
||||
signal: abortController?.signal,
|
||||
trace: this.mapTrace(trace, TraceTagMap.SystemChain),
|
||||
const oaiMessages = this.processMessages({
|
||||
messages: params.messages as any,
|
||||
model: params.model!,
|
||||
provider: params.provider!,
|
||||
tools: params.plugins,
|
||||
});
|
||||
const tools = this.prepareTools(params.plugins || [], {
|
||||
model: params.model!,
|
||||
provider: params.provider!,
|
||||
});
|
||||
|
||||
await this.getChatCompletion(
|
||||
{ ...params, messages: oaiMessages, tools },
|
||||
{
|
||||
onErrorHandle: (error) => {
|
||||
errorHandle(new Error(error.message), error);
|
||||
},
|
||||
onFinish,
|
||||
onMessageHandle,
|
||||
signal: abortController?.signal,
|
||||
trace: this.mapTrace(trace, TraceTagMap.SystemChain),
|
||||
},
|
||||
);
|
||||
|
||||
onLoadingChange?.(false);
|
||||
} catch (e) {
|
||||
@@ -451,7 +459,7 @@ class ChatService {
|
||||
|
||||
private processMessages = (
|
||||
{
|
||||
messages,
|
||||
messages = [],
|
||||
tools,
|
||||
model,
|
||||
provider,
|
||||
@@ -483,6 +491,7 @@ class ChatService {
|
||||
};
|
||||
|
||||
let postMessages = messages.map((m): OpenAIChatMessage => {
|
||||
const supportTools = isCanUseFC(model, provider);
|
||||
switch (m.role) {
|
||||
case 'user': {
|
||||
return { content: getContent(m), role: m.role };
|
||||
@@ -492,17 +501,23 @@ class ChatService {
|
||||
// signature is a signal of anthropic thinking mode
|
||||
const shouldIncludeThinking = m.reasoning && !!m.reasoning?.signature;
|
||||
|
||||
const content = shouldIncludeThinking
|
||||
? [
|
||||
{
|
||||
signature: m.reasoning!.signature,
|
||||
thinking: m.reasoning!.content,
|
||||
type: 'thinking',
|
||||
} as any,
|
||||
{ text: m.content, type: 'text' },
|
||||
]
|
||||
: m.content;
|
||||
|
||||
if (!supportTools) {
|
||||
return { content, role: m.role };
|
||||
}
|
||||
|
||||
return {
|
||||
content: shouldIncludeThinking
|
||||
? [
|
||||
{
|
||||
signature: m.reasoning!.signature,
|
||||
thinking: m.reasoning!.content,
|
||||
type: 'thinking',
|
||||
} as any,
|
||||
{ text: m.content, type: 'text' },
|
||||
]
|
||||
: m.content,
|
||||
content,
|
||||
role: m.role,
|
||||
tool_calls: m.tools?.map(
|
||||
(tool): MessageToolCall => ({
|
||||
@@ -518,6 +533,10 @@ class ChatService {
|
||||
}
|
||||
|
||||
case 'tool': {
|
||||
if (!supportTools) {
|
||||
return { content: m.content, role: 'user' };
|
||||
}
|
||||
|
||||
return {
|
||||
content: m.content,
|
||||
name: genToolCallingName(m.plugin!.identifier, m.plugin!.apiName, m.plugin?.type),
|
||||
@@ -669,6 +688,20 @@ class ChatService {
|
||||
|
||||
return reorderedMessages;
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
@@ -53,7 +53,7 @@ export interface ISessionService {
|
||||
|
||||
updateSessionChatConfig(
|
||||
id: string,
|
||||
config: DeepPartial<LobeAgentChatConfig>,
|
||||
config: Partial<LobeAgentChatConfig>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<any>;
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ exports[`agentSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG
|
||||
"enableReasoning": false,
|
||||
"historyCount": 8,
|
||||
"reasoningBudgetToken": 1024,
|
||||
"searchFCModel": {
|
||||
"model": "gpt-4o-mini",
|
||||
"provider": "openai",
|
||||
},
|
||||
"searchMode": "off",
|
||||
},
|
||||
"model": "gpt-3.5-turbo",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { contextCachingModels, thinkingWithToolClaudeModels } from '@/const/models';
|
||||
import { DEFAULT_AGENT_CHAT_CONFIG } from '@/const/settings';
|
||||
import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_SEARCH_FC_MODEL } from '@/const/settings';
|
||||
import { AgentStoreState } from '@/store/agent/initialState';
|
||||
import { LobeAgentChatConfig } from '@/types/agent';
|
||||
|
||||
@@ -14,6 +14,9 @@ const isAgentEnableSearch = (s: AgentStoreState) => agentSearchMode(s) !== 'off'
|
||||
const useModelBuiltinSearch = (s: AgentStoreState) =>
|
||||
currentAgentChatConfig(s).useModelBuiltinSearch;
|
||||
|
||||
const searchFCModel = (s: AgentStoreState) =>
|
||||
currentAgentChatConfig(s).searchFCModel || DEFAULT_AGENT_SEARCH_FC_MODEL;
|
||||
|
||||
const enableHistoryCount = (s: AgentStoreState) => {
|
||||
const config = currentAgentConfig(s);
|
||||
const chatConfig = currentAgentChatConfig(s);
|
||||
@@ -62,5 +65,6 @@ export const agentChatConfigSelectors = {
|
||||
enableHistoryDivider,
|
||||
historyCount,
|
||||
isAgentEnableSearch,
|
||||
searchFCModel,
|
||||
useModelBuiltinSearch,
|
||||
};
|
||||
|
||||
@@ -765,10 +765,12 @@ describe('chatMessage actions', () => {
|
||||
(fetch as Mock).mockResolvedValueOnce(new Response(aiResponse));
|
||||
|
||||
await act(async () => {
|
||||
const response = await result.current.internal_fetchAIChatMessage(
|
||||
const response = await result.current.internal_fetchAIChatMessage({
|
||||
messages,
|
||||
assistantMessageId,
|
||||
);
|
||||
messageId: assistantMessageId,
|
||||
model: 'gpt-4o-mini',
|
||||
provider: 'openai',
|
||||
});
|
||||
expect(response.isFunctionCall).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -784,7 +786,13 @@ describe('chatMessage actions', () => {
|
||||
|
||||
await act(async () => {
|
||||
expect(
|
||||
await result.current.internal_fetchAIChatMessage(messages, assistantMessageId),
|
||||
await result.current.internal_fetchAIChatMessage({
|
||||
model: 'gpt-4o-mini',
|
||||
provider: 'openai',
|
||||
|
||||
messages,
|
||||
messageId: assistantMessageId,
|
||||
}),
|
||||
).toEqual({
|
||||
isFunctionCall: false,
|
||||
});
|
||||
|
||||
@@ -13,10 +13,13 @@ import { messageService } from '@/services/message';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
||||
import { getAgentStoreState } from '@/store/agent/store';
|
||||
import { aiModelSelectors } from '@/store/aiInfra';
|
||||
import { getAiInfraStoreState } from '@/store/aiInfra/store';
|
||||
import { chatHelpers } from '@/store/chat/helpers';
|
||||
import { ChatStore } from '@/store/chat/store';
|
||||
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
||||
import { ChatMessage, CreateMessageParams, SendMessageParams } from '@/types/message';
|
||||
import { MessageSemanticSearchChunk } from '@/types/rag';
|
||||
import { setNamespace } from '@/utils/storeDebug';
|
||||
@@ -28,6 +31,7 @@ const n = setNamespace('ai');
|
||||
interface ProcessMessageParams {
|
||||
traceId?: string;
|
||||
isWelcomeQuestion?: boolean;
|
||||
inSearchWorkflow?: boolean;
|
||||
/**
|
||||
* the RAG query content, should be embedding and used in the semantic search
|
||||
*/
|
||||
@@ -70,11 +74,13 @@ export interface AIGenerateAction {
|
||||
/**
|
||||
* Retrieves an AI-generated chat message from the backend service
|
||||
*/
|
||||
internal_fetchAIChatMessage: (
|
||||
messages: ChatMessage[],
|
||||
messageId: string,
|
||||
params?: ProcessMessageParams,
|
||||
) => Promise<{
|
||||
internal_fetchAIChatMessage: (input: {
|
||||
messages: ChatMessage[];
|
||||
messageId: string;
|
||||
params?: ProcessMessageParams;
|
||||
model: string;
|
||||
provider: string;
|
||||
}) => Promise<{
|
||||
isFunctionCall: boolean;
|
||||
traceId?: string;
|
||||
}>;
|
||||
@@ -110,6 +116,8 @@ export interface AIGenerateAction {
|
||||
id?: string,
|
||||
action?: string,
|
||||
) => AbortController | undefined;
|
||||
|
||||
internal_toggleSearchWorkflow: (loading: boolean, id?: string) => void;
|
||||
}
|
||||
|
||||
export const generateAIChat: StateCreator<
|
||||
@@ -336,10 +344,97 @@ export const generateAIChat: StateCreator<
|
||||
|
||||
const assistantId = await get().internal_createMessage(assistantMessage);
|
||||
|
||||
// 3. fetch the AI response
|
||||
const { isFunctionCall } = await internal_fetchAIChatMessage(messages, assistantId, params);
|
||||
// 3. place a search with the search working model if this model is not support tool use
|
||||
const isModelSupportToolUse = aiModelSelectors.isModelSupportToolUse(
|
||||
model,
|
||||
provider!,
|
||||
)(getAiInfraStoreState());
|
||||
const isAgentEnableSearch = agentChatConfigSelectors.isAgentEnableSearch(getAgentStoreState());
|
||||
|
||||
// 4. if it's the function call message, trigger the function method
|
||||
if (isAgentEnableSearch && !isModelSupportToolUse) {
|
||||
const { model, provider } = agentChatConfigSelectors.searchFCModel(getAgentStoreState());
|
||||
|
||||
let isToolsCalling = false;
|
||||
let isError = false;
|
||||
|
||||
const abortController = get().internal_toggleChatLoading(
|
||||
true,
|
||||
assistantId,
|
||||
n('generateMessage(start)', { messageId: assistantId, messages }) as string,
|
||||
);
|
||||
|
||||
get().internal_toggleSearchWorkflow(true, assistantId);
|
||||
await chatService.fetchPresetTaskResult({
|
||||
params: { messages, model, provider, plugins: [WebBrowsingManifest.identifier] },
|
||||
onFinish: async (_, { toolCalls, usage }) => {
|
||||
if (toolCalls && toolCalls.length > 0) {
|
||||
get().internal_toggleToolCallingStreaming(assistantId, undefined);
|
||||
// update tools calling
|
||||
await get().internal_updateMessageContent(assistantId, '', {
|
||||
toolCalls,
|
||||
metadata: usage,
|
||||
model,
|
||||
provider,
|
||||
});
|
||||
}
|
||||
},
|
||||
abortController,
|
||||
onMessageHandle: async (chunk) => {
|
||||
if (chunk.type === 'tool_calls') {
|
||||
get().internal_toggleSearchWorkflow(false, assistantId);
|
||||
get().internal_toggleToolCallingStreaming(assistantId, chunk.isAnimationActives);
|
||||
get().internal_dispatchMessage({
|
||||
id: assistantId,
|
||||
type: 'updateMessage',
|
||||
value: { tools: get().internal_transformToolCalls(chunk.tool_calls) },
|
||||
});
|
||||
isToolsCalling = true;
|
||||
}
|
||||
|
||||
if (chunk.type === 'text') {
|
||||
abortController!.abort('not fc');
|
||||
}
|
||||
},
|
||||
onErrorHandle: async (error) => {
|
||||
isError = true;
|
||||
await messageService.updateMessageError(assistantId, error);
|
||||
await refreshMessages();
|
||||
},
|
||||
});
|
||||
|
||||
get().internal_toggleChatLoading(
|
||||
false,
|
||||
assistantId,
|
||||
n('generateMessage(start)', { messageId: assistantId, messages }) as string,
|
||||
);
|
||||
get().internal_toggleSearchWorkflow(false, assistantId);
|
||||
|
||||
// if there is error, then stop
|
||||
if (isError) return;
|
||||
|
||||
// if it's the function call message, trigger the function method
|
||||
if (isToolsCalling) {
|
||||
await refreshMessages();
|
||||
await triggerToolCalls(assistantId, {
|
||||
threadId: params?.threadId,
|
||||
inPortalThread: params?.inPortalThread,
|
||||
});
|
||||
|
||||
// then story the workflow
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. fetch the AI response
|
||||
const { isFunctionCall } = await internal_fetchAIChatMessage({
|
||||
messages,
|
||||
messageId: assistantId,
|
||||
params,
|
||||
model,
|
||||
provider: provider!,
|
||||
});
|
||||
|
||||
// 5. if it's the function call message, trigger the function method
|
||||
if (isFunctionCall) {
|
||||
await refreshMessages();
|
||||
await triggerToolCalls(assistantId, {
|
||||
@@ -348,7 +443,7 @@ export const generateAIChat: StateCreator<
|
||||
});
|
||||
}
|
||||
|
||||
// 5. summary history if context messages is larger than historyCount
|
||||
// 6. summary history if context messages is larger than historyCount
|
||||
const historyCount = agentChatConfigSelectors.historyCount(getAgentStoreState());
|
||||
|
||||
if (
|
||||
@@ -365,7 +460,7 @@ export const generateAIChat: StateCreator<
|
||||
await get().internal_summaryHistory(historyMessages);
|
||||
}
|
||||
},
|
||||
internal_fetchAIChatMessage: async (messages, messageId, params) => {
|
||||
internal_fetchAIChatMessage: async ({ messages, messageId, params, provider, model }) => {
|
||||
const {
|
||||
internal_toggleChatLoading,
|
||||
refreshMessages,
|
||||
@@ -382,7 +477,7 @@ export const generateAIChat: StateCreator<
|
||||
);
|
||||
|
||||
const agentConfig = agentSelectors.currentAgentConfig(getAgentStoreState());
|
||||
const chatConfig = agentConfig.chatConfig;
|
||||
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
||||
|
||||
const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
|
||||
|
||||
@@ -444,8 +539,8 @@ export const generateAIChat: StateCreator<
|
||||
abortController,
|
||||
params: {
|
||||
messages: preprocessMsgs,
|
||||
model: agentConfig.model,
|
||||
provider: agentConfig.provider,
|
||||
model,
|
||||
provider,
|
||||
...agentConfig.params,
|
||||
plugins: agentConfig.plugins,
|
||||
},
|
||||
@@ -529,6 +624,7 @@ export const generateAIChat: StateCreator<
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'reasoning': {
|
||||
// if there is no thinkingStartAt, it means the start of reasoning
|
||||
if (!thinkingStartAt) {
|
||||
@@ -639,4 +735,8 @@ export const generateAIChat: StateCreator<
|
||||
`toggleToolCallingStreaming/${!!streaming ? 'start' : 'end'}`,
|
||||
);
|
||||
},
|
||||
|
||||
internal_toggleSearchWorkflow: (loading, id) => {
|
||||
return get().internal_toggleLoadingArrays('searchWorkflowLoadingIds', loading, id);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ChatAIChatState {
|
||||
* is the AI message is reasoning
|
||||
*/
|
||||
reasoningLoadingIds: string[];
|
||||
searchWorkflowLoadingIds: string[];
|
||||
/**
|
||||
* the tool calling stream ids
|
||||
*/
|
||||
@@ -28,5 +29,6 @@ export const initialAiChatState: ChatAIChatState = {
|
||||
messageRAGLoadingIds: [],
|
||||
pluginApiLoadingIds: [],
|
||||
reasoningLoadingIds: [],
|
||||
searchWorkflowLoadingIds: [],
|
||||
toolCallingStreamIds: {},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
|
||||
import type { ChatStoreState } from '../../initialState';
|
||||
|
||||
const isMessageInReasoning = (id: string) => (s: ChatStoreState) =>
|
||||
s.reasoningLoadingIds.includes(id);
|
||||
|
||||
const isMessageInSearchWorkflow = (id: string) => (s: ChatStoreState) =>
|
||||
s.searchWorkflowLoadingIds.includes(id);
|
||||
|
||||
const isIntentUnderstanding = (id: string) => (s: ChatStoreState) =>
|
||||
isMessageInSearchWorkflow(id)(s);
|
||||
|
||||
export const aiChatSelectors = {
|
||||
isIntentUnderstanding,
|
||||
isMessageInReasoning,
|
||||
isMessageInSearchWorkflow,
|
||||
};
|
||||
|
||||
@@ -81,6 +81,8 @@ export interface ChatMessageAction {
|
||||
reasoning?: ModelReasoning;
|
||||
search?: GroundingSearch;
|
||||
metadata?: MessageMetadata;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
},
|
||||
) => Promise<void>;
|
||||
/**
|
||||
@@ -302,7 +304,11 @@ export const chatMessage: StateCreator<
|
||||
value: { tools: internal_transformToolCalls(extra?.toolCalls) },
|
||||
});
|
||||
} else {
|
||||
internal_dispatchMessage({ id, type: 'updateMessage', value: { content } });
|
||||
internal_dispatchMessage({
|
||||
id,
|
||||
type: 'updateMessage',
|
||||
value: { content },
|
||||
});
|
||||
}
|
||||
|
||||
await messageService.updateMessage(id, {
|
||||
@@ -311,6 +317,8 @@ export const chatMessage: StateCreator<
|
||||
reasoning: extra?.reasoning,
|
||||
search: extra?.search,
|
||||
metadata: extra?.metadata,
|
||||
model: extra?.model,
|
||||
provider: extra?.provider,
|
||||
});
|
||||
await refreshMessages();
|
||||
},
|
||||
|
||||
@@ -50,12 +50,13 @@ export interface ChatPluginAction {
|
||||
traceId?: string;
|
||||
threadId?: string;
|
||||
inPortalThread?: boolean;
|
||||
inSearchWorkflow?: boolean;
|
||||
}) => Promise<void>;
|
||||
summaryPluginContent: (id: string) => Promise<void>;
|
||||
|
||||
triggerToolCalls: (
|
||||
id: string,
|
||||
params?: { threadId?: string; inPortalThread?: boolean },
|
||||
params?: { threadId?: string; inPortalThread?: boolean; inSearchWorkflow?: boolean },
|
||||
) => Promise<void>;
|
||||
updatePluginState: (id: string, value: any) => Promise<void>;
|
||||
updatePluginArguments: <T = any>(id: string, value: T) => Promise<void>;
|
||||
@@ -209,7 +210,7 @@ export const chatPlugin: StateCreator<
|
||||
await get().internal_invokeDifferentTypePlugin(id, payload);
|
||||
},
|
||||
|
||||
triggerAIMessage: async ({ parentId, traceId, threadId, inPortalThread }) => {
|
||||
triggerAIMessage: async ({ parentId, traceId, threadId, inPortalThread, inSearchWorkflow }) => {
|
||||
const { internal_coreProcessMessage } = get();
|
||||
|
||||
const chats = inPortalThread
|
||||
@@ -220,6 +221,7 @@ export const chatPlugin: StateCreator<
|
||||
traceId,
|
||||
threadId,
|
||||
inPortalThread,
|
||||
inSearchWorkflow,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -245,7 +247,7 @@ export const chatPlugin: StateCreator<
|
||||
);
|
||||
},
|
||||
|
||||
triggerToolCalls: async (assistantId, { threadId, inPortalThread } = {}) => {
|
||||
triggerToolCalls: async (assistantId, { threadId, inPortalThread, inSearchWorkflow } = {}) => {
|
||||
const message = chatSelectors.getMessageById(assistantId)(get());
|
||||
if (!message || !message.tools) return;
|
||||
|
||||
@@ -281,7 +283,7 @@ export const chatPlugin: StateCreator<
|
||||
|
||||
const traceId = chatSelectors.getTraceIdByMessageId(latestToolId)(get());
|
||||
|
||||
await get().triggerAIMessage({ traceId, threadId, inPortalThread });
|
||||
await get().triggerAIMessage({ traceId, threadId, inPortalThread, inSearchWorkflow });
|
||||
},
|
||||
updatePluginState: async (id, value) => {
|
||||
const { refreshMessages } = get();
|
||||
|
||||
@@ -78,6 +78,10 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
|
||||
"enableReasoning": false,
|
||||
"historyCount": 8,
|
||||
"reasoningBudgetToken": 1024,
|
||||
"searchFCModel": {
|
||||
"model": "gpt-4o-mini",
|
||||
"provider": "openai",
|
||||
},
|
||||
"searchMode": "off",
|
||||
},
|
||||
"model": "gpt-3.5-turbo",
|
||||
|
||||
@@ -62,5 +62,11 @@ export const AgentChatConfigSchema = z.object({
|
||||
enableReasoningEffort: z.boolean().optional(),
|
||||
historyCount: z.number().optional(),
|
||||
reasoningBudgetToken: z.number().optional(),
|
||||
searchFCModel: z
|
||||
.object({
|
||||
model: z.string(),
|
||||
provider: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
searchMode: z.enum(['off', 'on', 'auto']).optional(),
|
||||
});
|
||||
|
||||
@@ -68,7 +68,6 @@ export interface ChatStreamPayload {
|
||||
*/
|
||||
n?: number;
|
||||
/**
|
||||
* @deprecated
|
||||
* 开启的插件列表
|
||||
*/
|
||||
plugins?: string[];
|
||||
|
||||
Reference in New Issue
Block a user