diff --git a/src/app/api/chat/[provider]/agentRuntime.ts b/src/app/api/chat/[provider]/agentRuntime.ts index 12f27ebd2aa3..fbb640a35953 100644 --- a/src/app/api/chat/[provider]/agentRuntime.ts +++ b/src/app/api/chat/[provider]/agentRuntime.ts @@ -13,6 +13,7 @@ import { LobeAzureOpenAI, LobeBedrockAI, LobeGoogleAI, + LobeGroq, LobeMistralAI, LobeMoonshotAI, LobeOllamaAI, @@ -162,11 +163,16 @@ class AgentRuntime { runtimeModel = this.initAnthropic(payload); break; } - + case ModelProvider.Mistral: { runtimeModel = this.initMistral(payload); break; } + + case ModelProvider.Groq: { + runtimeModel = this.initGroq(payload); + break; + } } return new AgentRuntime(runtimeModel); @@ -261,13 +267,20 @@ class AgentRuntime { const baseURL = payload?.endpoint || ANTHROPIC_PROXY_URL; return new LobeAnthropicAI({ apiKey, baseURL }); } - + private static initMistral(payload: JWTPayload) { const { MISTRAL_API_KEY } = getServerConfig(); const apiKey = apiKeyManager.pick(payload?.apiKey || MISTRAL_API_KEY); return new LobeMistralAI({ apiKey }); } + + private static initGroq(payload: JWTPayload) { + const { GROQ_API_KEY } = getServerConfig(); + const apiKey = apiKeyManager.pick(payload?.apiKey || GROQ_API_KEY); + + return new LobeGroq({ apiKey }); + } } export default AgentRuntime; diff --git a/src/app/api/config/route.ts b/src/app/api/config/route.ts index cc242fb70630..856ed49fde2d 100644 --- a/src/app/api/config/route.ts +++ b/src/app/api/config/route.ts @@ -16,6 +16,7 @@ export const GET = async () => { ENABLED_ZHIPU, ENABLED_AWS_BEDROCK, ENABLED_GOOGLE, + ENABLED_GROQ, ENABLE_OAUTH_SSO, ENABLE_OLLAMA, ENABLED_PERPLEXITY, @@ -36,6 +37,7 @@ export const GET = async () => { anthropic: { enabled: ENABLED_ANTHROPIC }, bedrock: { enabled: ENABLED_AWS_BEDROCK }, google: { enabled: ENABLED_GOOGLE }, + groq: { enabled: ENABLED_GROQ }, mistral: { enabled: ENABLED_MISTRAL }, moonshot: { enabled: ENABLED_MOONSHOT }, ollama: { customModelName: OLLAMA_CUSTOM_MODELS, enabled: ENABLE_OLLAMA }, diff --git a/src/app/api/errorResponse.ts b/src/app/api/errorResponse.ts index b58ea9315efe..bbbeb25bdee7 100644 --- a/src/app/api/errorResponse.ts +++ b/src/app/api/errorResponse.ts @@ -50,6 +50,9 @@ const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => { case AgentRuntimeErrorType.MistralBizError: { return 481; } + case AgentRuntimeErrorType.GroqBizError: { + return 482; + } } return errorType as number; }; diff --git a/src/app/settings/llm/Groq/index.tsx b/src/app/settings/llm/Groq/index.tsx new file mode 100644 index 000000000000..453e8f9d2f42 --- /dev/null +++ b/src/app/settings/llm/Groq/index.tsx @@ -0,0 +1,52 @@ +import { Perplexity } from '@lobehub/icons'; +import { Input } from 'antd'; +import { useTheme } from 'antd-style'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { ModelProvider } from '@/libs/agent-runtime'; + +import Checker from '../components/Checker'; +import ProviderConfig from '../components/ProviderConfig'; +import { LLMProviderApiTokenKey, LLMProviderConfigKey } from '../const'; + +const providerKey = 'groq'; + +const GroqProvider = memo(() => { + const { t } = useTranslation('setting'); + + const theme = useTheme(); + + return ( + + ), + desc: t('llm.Groq.token.desc'), + label: t('llm.Groq.token.title'), + name: [LLMProviderConfigKey, providerKey, LLMProviderApiTokenKey], + }, + { + children: , + desc: t('llm.checker.desc'), + label: t('llm.checker.title'), + minWidth: '100%', + }, + ]} + provider={providerKey} + title={ + + } + /> + ); +}); + +export default GroqProvider; diff --git a/src/app/settings/llm/index.tsx b/src/app/settings/llm/index.tsx index c47a49c1de83..3d1948c16544 100644 --- a/src/app/settings/llm/index.tsx +++ b/src/app/settings/llm/index.tsx @@ -11,6 +11,7 @@ import Footer from '../features/Footer'; import Anthropic from './Anthropic'; import Bedrock from './Bedrock'; import Google from './Google'; +import Groq from './Groq'; import Mistral from './Mistral'; import Moonshot from './Moonshot'; import Ollama from './Ollama'; @@ -29,6 +30,7 @@ export default memo<{ showOllama: boolean }>(({ showOllama }) => { {showOllama && } + diff --git a/src/components/ModelProviderIcon/index.tsx b/src/components/ModelProviderIcon/index.tsx index 456b9f3d1985..741612b94879 100644 --- a/src/components/ModelProviderIcon/index.tsx +++ b/src/components/ModelProviderIcon/index.tsx @@ -3,6 +3,7 @@ import { Azure, Bedrock, Google, + Groq, Mistral, Moonshot, Ollama, @@ -69,6 +70,10 @@ const ModelProviderIcon = memo(({ provider }) => { return ; } + case ModelProvider.Groq: { + return ; + } + default: { return null; } diff --git a/src/config/modelProviders/groq.ts b/src/config/modelProviders/groq.ts new file mode 100644 index 000000000000..0589d2f19822 --- /dev/null +++ b/src/config/modelProviders/groq.ts @@ -0,0 +1,24 @@ +import { ModelProviderCard } from '@/types/llm'; + +const Groq: ModelProviderCard = { + chatModels: [ + { + displayName: 'LLaMA2-70b-chat', + id: 'llama2-70b-4096', + tokens: 4096, + }, + { + displayName: 'Mixtral-8x7b-Instruct-v0.1', + id: 'mixtral-8x7b-32768', + tokens: 32_768, + }, + { + displayName: 'Gemma-7b-it', + id: 'gemma-7b-it', + tokens: 8192, + }, + ], + id: 'groq', +}; + +export default Groq; diff --git a/src/config/modelProviders/index.ts b/src/config/modelProviders/index.ts index e30bd72edfc7..9ca0c4fcac69 100644 --- a/src/config/modelProviders/index.ts +++ b/src/config/modelProviders/index.ts @@ -3,6 +3,7 @@ import { ChatModelCard } from '@/types/llm'; import AnthropicProvider from './anthropic'; import BedrockProvider from './bedrock'; import GoogleProvider from './google'; +import GroqProvider from './groq'; import MistralProvider from './mistral'; import MoonshotProvider from './moonshot'; import OllamaProvider from './ollama'; @@ -15,6 +16,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [ ZhiPuProvider.chatModels, BedrockProvider.chatModels, GoogleProvider.chatModels, + GroqProvider.chatModels, MistralProvider.chatModels, MoonshotProvider.chatModels, OllamaProvider.chatModels, @@ -25,6 +27,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [ export { default as AnthropicProvider } from './anthropic'; export { default as BedrockProvider } from './bedrock'; export { default as GoogleProvider } from './google'; +export { default as GroqProvider } from './groq'; export { default as MistralProvider } from './mistral'; export { default as MoonshotProvider } from './moonshot'; export { default as OllamaProvider } from './ollama'; diff --git a/src/config/server/provider.ts b/src/config/server/provider.ts index 785125ed59f8..47aaff61076d 100644 --- a/src/config/server/provider.ts +++ b/src/config/server/provider.ts @@ -36,10 +36,13 @@ declare global { // Anthropic Provider ANTHROPIC_API_KEY?: string; ANTHROPIC_PROXY_URL?: string; - + // Mistral Provider MISTRAL_API_KEY?: string; + // Groq Provider + GROQ_API_KEY?: string; + // AWS Credentials AWS_REGION?: string; AWS_ACCESS_KEY_ID?: string; @@ -67,9 +70,11 @@ export const getProviderConfig = () => { const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY || ''; const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || ''; - + const MISTRAL_API_KEY = process.env.MISTRAL_API_KEY || ''; + const GROQ_API_KEY = process.env.GROQ_API_KEY || ''; + // region format: iad1,sfo1 let regions: string[] = []; if (process.env.OPENAI_FUNCTION_REGIONS) { @@ -97,7 +102,7 @@ export const getProviderConfig = () => { ENABLED_ANTHROPIC: !!ANTHROPIC_API_KEY, ANTHROPIC_API_KEY, ANTHROPIC_PROXY_URL: process.env.ANTHROPIC_PROXY_URL, - + ENABLED_MISTRAL: !!MISTRAL_API_KEY, MISTRAL_API_KEY, @@ -105,6 +110,9 @@ export const getProviderConfig = () => { MOONSHOT_API_KEY, MOONSHOT_PROXY_URL: process.env.MOONSHOT_PROXY_URL, + ENABLED_GROQ: !!GROQ_API_KEY, + GROQ_API_KEY, + ENABLED_AWS_BEDROCK: !!AWS_ACCESS_KEY_ID, AWS_REGION: process.env.AWS_REGION, AWS_ACCESS_KEY_ID: AWS_ACCESS_KEY_ID, diff --git a/src/const/settings.ts b/src/const/settings.ts index 20d0d4f136d3..a959f558e65b 100644 --- a/src/const/settings.ts +++ b/src/const/settings.ts @@ -66,6 +66,10 @@ export const DEFAULT_LLM_CONFIG: GlobalLLMConfig = { apiKey: '', enabled: false, }, + groq: { + apiKey: '', + enabled: false, + }, mistral: { apiKey: '', enabled: false, diff --git a/src/features/Conversation/Error/APIKeyForm/Groq.tsx b/src/features/Conversation/Error/APIKeyForm/Groq.tsx new file mode 100644 index 000000000000..c4152db13909 --- /dev/null +++ b/src/features/Conversation/Error/APIKeyForm/Groq.tsx @@ -0,0 +1,60 @@ +import { Groq } from '@lobehub/icons'; +import { Input } from 'antd'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { ModelProvider } from '@/libs/agent-runtime'; +import { useGlobalStore } from '@/store/global'; +import { modelProviderSelectors } from '@/store/global/selectors'; + +import { FormAction } from '../style'; + +const GroqForm = memo(() => { + const { t } = useTranslation('error'); + // const [showProxy, setShow] = useState(false); + + const [apiKey, setConfig] = useGlobalStore((s) => [ + modelProviderSelectors.groqAPIKey(s), + s.setModelProviderConfig, + ]); + + return ( + } + description={t('unlock.apikey.Groq.description')} + title={t('unlock.apikey.Groq.title')} + > + { + setConfig(ModelProvider.Groq, { apiKey: e.target.value }); + }} + placeholder={'*********************************'} + type={'block'} + value={apiKey} + /> + {/*{showProxy ? (*/} + {/* {*/} + {/* setConfig({ endpoint: e.target.value });*/} + {/* }}*/} + {/* placeholder={'https://api.openai.com/v1'}*/} + {/* type={'block'}*/} + {/* value={proxyUrl}*/} + {/* />*/} + {/*) : (*/} + {/* }*/} + {/* onClick={() => {*/} + {/* setShow(true);*/} + {/* }}*/} + {/* type={'text'}*/} + {/* >*/} + {/* {t('unlock.apikey.addProxyUrl')}*/} + {/* */} + {/*)}*/} + + ); +}); + +export default GroqForm; diff --git a/src/features/Conversation/Error/APIKeyForm/index.tsx b/src/features/Conversation/Error/APIKeyForm/index.tsx index 08376a50237c..aecd67507e31 100644 --- a/src/features/Conversation/Error/APIKeyForm/index.tsx +++ b/src/features/Conversation/Error/APIKeyForm/index.tsx @@ -9,6 +9,7 @@ import { useChatStore } from '@/store/chat'; import AnthropicForm from './Anthropic'; import BedrockForm from './Bedrock'; import GoogleForm from './Google'; +import GroqForm from './Groq'; import MistralForm from './Mistral'; import MoonshotForm from './Moonshot'; import OpenAIForm from './OpenAI'; @@ -55,6 +56,10 @@ const APIKeyForm = memo(({ id, provider }) => { return ; } + case ModelProvider.Groq: { + return ; + } + default: case ModelProvider.OpenAI: { return ; diff --git a/src/features/Conversation/Error/index.tsx b/src/features/Conversation/Error/index.tsx index 246c68b7e5fe..3443c90b64df 100644 --- a/src/features/Conversation/Error/index.tsx +++ b/src/features/Conversation/Error/index.tsx @@ -74,6 +74,7 @@ const ErrorMessageExtra = memo<{ data: ChatMessage }>(({ data }) => { case AgentRuntimeErrorType.InvalidGoogleAPIKey: case AgentRuntimeErrorType.InvalidPerplexityAPIKey: case AgentRuntimeErrorType.InvalidAnthropicAPIKey: + case AgentRuntimeErrorType.InvalidGroqAPIKey: case AgentRuntimeErrorType.NoOpenAIAPIKey: { return ; } diff --git a/src/libs/agent-runtime/error.ts b/src/libs/agent-runtime/error.ts index 37828b0faa3d..9e45df3d1e56 100644 --- a/src/libs/agent-runtime/error.ts +++ b/src/libs/agent-runtime/error.ts @@ -34,6 +34,9 @@ export const AgentRuntimeErrorType = { InvalidAnthropicAPIKey: 'InvalidAnthropicAPIKey', AnthropicBizError: 'AnthropicBizError', + + InvalidGroqAPIKey: 'InvalidGroqAPIKey', + GroqBizError: 'GroqBizError', } as const; export type ILobeAgentRuntimeErrorType = diff --git a/src/libs/agent-runtime/groq/index.ts b/src/libs/agent-runtime/groq/index.ts new file mode 100644 index 000000000000..39905c64f356 --- /dev/null +++ b/src/libs/agent-runtime/groq/index.ts @@ -0,0 +1,78 @@ +import { OpenAIStream, StreamingTextResponse } from 'ai'; +import OpenAI, { ClientOptions } from 'openai'; + +import { LobeRuntimeAI } from '../BaseAI'; +import { AgentRuntimeErrorType } from '../error'; +import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types'; +import { AgentRuntimeError } from '../utils/createError'; +import { debugStream } from '../utils/debugStream'; +import { desensitizeUrl } from '../utils/desensitizeUrl'; +import { handleOpenAIError } from '../utils/handleOpenAIError'; + +const DEFAULT_BASE_URL = 'https://api.groq.com/openai/v1'; + +export class LobeGroq implements LobeRuntimeAI { + private client: OpenAI; + + baseURL: string; + + constructor({ apiKey, baseURL = DEFAULT_BASE_URL, ...res }: ClientOptions) { + if (!apiKey) throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidGroqAPIKey); + + this.client = new OpenAI({ apiKey, baseURL, ...res }); + this.baseURL = this.client.baseURL; + } + + async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) { + try { + const response = await this.client.chat.completions.create( + payload as unknown as OpenAI.ChatCompletionCreateParamsStreaming, + ); + const [prod, debug] = response.tee(); + + if (process.env.DEBUG_GROQ_CHAT_COMPLETION === '1') { + debugStream(debug.toReadableStream()).catch(console.error); + } + + return new StreamingTextResponse(OpenAIStream(prod, options?.callback), { + headers: options?.headers, + }); + } catch (error) { + let desensitizedEndpoint = this.baseURL; + + if (this.baseURL !== DEFAULT_BASE_URL) { + desensitizedEndpoint = desensitizeUrl(this.baseURL); + } + + if ('status' in (error as any)) { + switch ((error as Response).status) { + case 401: { + throw AgentRuntimeError.chat({ + endpoint: desensitizedEndpoint, + error: error as any, + errorType: AgentRuntimeErrorType.InvalidGroqAPIKey, + provider: ModelProvider.Groq, + }); + } + + default: { + break; + } + } + } + + const { errorResult, RuntimeError } = handleOpenAIError(error); + + const errorType = RuntimeError || AgentRuntimeErrorType.GroqBizError; + + throw AgentRuntimeError.chat({ + endpoint: desensitizedEndpoint, + error: errorResult, + errorType, + provider: ModelProvider.Groq, + }); + } + } +} + +export default LobeGroq; diff --git a/src/libs/agent-runtime/index.ts b/src/libs/agent-runtime/index.ts index b73072c11ebf..bff5b94e2bca 100644 --- a/src/libs/agent-runtime/index.ts +++ b/src/libs/agent-runtime/index.ts @@ -4,6 +4,7 @@ export * from './BaseAI'; export { LobeBedrockAI } from './bedrock'; export * from './error'; export { LobeGoogleAI } from './google'; +export { LobeGroq } from './groq'; export { LobeMistralAI } from './mistral'; export { LobeMoonshotAI } from './moonshot'; export { LobeOllamaAI } from './ollama'; diff --git a/src/libs/agent-runtime/types/type.ts b/src/libs/agent-runtime/types/type.ts index b43eaf702079..24894f69d706 100644 --- a/src/libs/agent-runtime/types/type.ts +++ b/src/libs/agent-runtime/types/type.ts @@ -27,6 +27,7 @@ export enum ModelProvider { Bedrock = 'bedrock', ChatGLM = 'chatglm', Google = 'google', + Groq = 'groq', Mistral = 'mistral', Moonshot = 'moonshot', Ollama = 'ollama', diff --git a/src/locales/default/common.ts b/src/locales/default/common.ts index a08c96f8ebc7..5aa2ed6b4cc9 100644 --- a/src/locales/default/common.ts +++ b/src/locales/default/common.ts @@ -104,6 +104,7 @@ export default { azure: 'Azure', bedrock: 'AWS Bedrock', google: 'Google', + groq: 'Groq', mistral: 'Mistral AI', moonshot: 'Moonshot AI', ollama: 'Ollama', diff --git a/src/locales/default/error.ts b/src/locales/default/error.ts index ba00343c34bb..3d77a876336b 100644 --- a/src/locales/default/error.ts +++ b/src/locales/default/error.ts @@ -82,6 +82,9 @@ export default { InvalidAnthropicAPIKey: 'Anthropic API Key 不正确或为空,请检查 Anthropic API Key 后重试', AnthropicBizError: '请求 Anthropic AI 服务出错,请根据以下信息排查或重试', + InvalidGroqAPIKey: 'Groq API Key 不正确或为空,请检查 Groq API Key 后重试', + GroqBizError: '请求 Groq 服务出错,请根据以下信息排查或重试', + InvalidOllamaArgs: 'Ollama 配置不正确,请检查 Ollama 配置后重试', OllamaBizError: '请求 Ollama 服务出错,请根据以下信息排查或重试', OllamaServiceUnavailable: '未检测到 Ollama 服务,请检查是否正常启动', @@ -111,6 +114,10 @@ export default { description: '输入你的 Google API Key 即可开始会话。应用不会记录你的 API Key', title: '使用自定义 Google API Key', }, + Groq: { + description: '输入你的 Groq API Key 即可开始会话。应用不会记录你的 API Key', + title: '使用自定义 Groq API Key', + }, Mistral: { description: '输入你的 Mistral AI API Key 即可开始会话。应用不会记录你的 API Key', title: '使用自定义 Mistral AI API Key', diff --git a/src/locales/default/setting.ts b/src/locales/default/setting.ts index ade6cd400db8..dc83b9a6f8e4 100644 --- a/src/locales/default/setting.ts +++ b/src/locales/default/setting.ts @@ -100,6 +100,14 @@ export default { title: 'API Key', }, }, + Groq: { + title: 'Groq', + token: { + desc: '填入来自 Groq 的 API Key', + placeholder: 'Groq API Key', + title: 'API Key', + }, + }, Mistral: { title: 'Mistral AI', token: { diff --git a/src/services/_auth.ts b/src/services/_auth.ts index 93dcd44b54b1..fd30ec699cde 100644 --- a/src/services/_auth.ts +++ b/src/services/_auth.ts @@ -57,11 +57,15 @@ export const getProviderAuthPayload = (provider: string) => { const endpoint = modelProviderSelectors.anthropicProxyUrl(useGlobalStore.getState()); return { apiKey, endpoint }; } - + case ModelProvider.Mistral: { return { apiKey: modelProviderSelectors.mistralAPIKey(useGlobalStore.getState()) }; } + case ModelProvider.Groq: { + return { apiKey: modelProviderSelectors.groqAPIKey(useGlobalStore.getState()) }; + } + default: case ModelProvider.OpenAI: { const openai = modelProviderSelectors.openAIConfig(useGlobalStore.getState()); diff --git a/src/store/global/slices/settings/selectors/modelProvider.ts b/src/store/global/slices/settings/selectors/modelProvider.ts index 1980a8c0be43..6ee193ea0caf 100644 --- a/src/store/global/slices/settings/selectors/modelProvider.ts +++ b/src/store/global/slices/settings/selectors/modelProvider.ts @@ -4,6 +4,7 @@ import { AnthropicProvider, BedrockProvider, GoogleProvider, + GroqProvider, LOBE_DEFAULT_MODEL_LIST, MistralProvider, MoonshotProvider, @@ -58,6 +59,9 @@ const enableAnthropic = (s: GlobalStore) => modelProvider(s).anthropic.enabled; const anthropicAPIKey = (s: GlobalStore) => modelProvider(s).anthropic.apiKey; const anthropicProxyUrl = (s: GlobalStore) => modelProvider(s).anthropic.endpoint; +const enableGroq = (s: GlobalStore) => modelProvider(s).groq.enabled; +const groqAPIKey = (s: GlobalStore) => modelProvider(s).groq.apiKey; + // const azureModelList = (s: GlobalStore): ModelProviderCard => { // const azure = azureConfig(s); // return { @@ -151,6 +155,7 @@ const modelSelectList = (s: GlobalStore): ModelProviderCard[] => { { ...PerplexityProvider, enabled: enablePerplexity(s) }, { ...AnthropicProvider, enabled: enableAnthropic(s) }, { ...MistralProvider, enabled: enableMistral(s) }, + { ...GroqProvider, enabled: enableGroq(s) }, ]; }; @@ -229,8 +234,12 @@ export const modelProviderSelectors = { enableAnthropic, anthropicAPIKey, anthropicProxyUrl, - + // Mistral enableMistral, mistralAPIKey, + + // Groq + enableGroq, + groqAPIKey, }; diff --git a/src/types/settings/modelProvider.ts b/src/types/settings/modelProvider.ts index e80fa1971b67..9342d29e8156 100644 --- a/src/types/settings/modelProvider.ts +++ b/src/types/settings/modelProvider.ts @@ -71,11 +71,17 @@ export interface MistralConfig { enabled: boolean; } +export interface GroqConfig { + apiKey?: string; + enabled: boolean; +} + export interface GlobalLLMConfig { anthropic: AnthropicConfig; azure: AzureOpenAIConfig; bedrock: AWSBedrockConfig; google: GoogleConfig; + groq: GroqConfig; mistral: MistralConfig; moonshot: MoonshotConfig; ollama: OllamaConfig;