Adding reverse proxy support to DeepSeek chat completion (#3328) * added reverse proxy settings to deepseek chat completion * Update chat-completions.js * Update chat-completions.js * Update chat-completions.js * Update chat-completions.js * Update chat-completions.js * Update chat-completions.js * Unify API key requirement --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -2692,7 +2692,7 @@ | ||
| 2692 | 2692 | <option value="windowai">Window AI</option> |
| 2693 | 2693 | </optgroup> |
| 2694 | 2694 | </select> |
| 2695 | 2695 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2696 | 2696 | <div class="inline-drawer-toggle inline-drawer-header"> |
| 2697 | 2697 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> |
| 2698 | 2698 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| @@ -2755,7 +2755,7 @@ | ||
| 2755 | 2755 | </div> |
| 2756 | 2756 | </div> |
| 2757 | 2757 | </div> |
| 2758 | 2758 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2759 | 2759 | <div class="reverse_proxy_warning"> |
| 2760 | 2760 | <b> |
| 2761 | 2761 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> |
| @@ -1922,7 +1922,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1922 | 1922 | } |
| 1923 | 1923 | |
| 1924 | 1924 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite |
| 1925 | 1925 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { |
| 1926 | 1926 | await validateReverseProxy(); |
| 1927 | 1927 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; |
| 1928 | 1928 | generate_data['proxy_password'] = oai_settings.proxy_password; |
| @@ -3370,7 +3370,7 @@ async function getStatusOpen() { | ||
| 3370 | 3370 | chat_completion_source: oai_settings.chat_completion_source, |
| 3371 | 3371 | }; |
| 3372 | 3372 | |
| 3373 | 3373 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { |
| 3374 | 3374 | await validateReverseProxy(); |
| 3375 | 3375 | } |
| 3376 | 3376 | |
| @@ -4749,7 +4749,7 @@ async function onConnectButtonClick(e) { | ||
| 4749 | 4749 | await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek); |
| 4750 | 4750 | } |
| 4751 | 4751 | |
| 4752 | 4752 | if (!secret_state[SECRET_KEYS.DEEPSEEK] && !oai_settings.reverse_proxy) { |
| 4753 | 4753 | console.log('No secret key saved for DeepSeek'); |
| 4754 | 4754 | return; |
| 4755 | 4755 | } |
| @@ -639,6 +639,89 @@ async function sendCohereRequest(request, response) { | ||
| 639 | 639 | } |
| 640 | 640 | } |
| 641 | 641 | |
| 642 | +/** | |
| 643 | + * Sends a request to DeepSeek API. | |
| 644 | + * @param {express.Request} request Express request | |
| 645 | + * @param {express.Response} response Express response | |
| 646 | + */ | |
| 647 | +async function sendDeepSeekRequest(request, response) { | |
| 648 | + const apiUrl = new URL(request.body.reverse_proxy || API_DEEPSEEK).toString(); | |
| 649 | + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | |
| 650 | + | |
| 651 | + if (!apiKey && !request.body.reverse_proxy) { | |
| 652 | + console.log('DeepSeek API key is missing.'); | |
| 653 | + return response.status(400).send({ error: true }); | |
| 654 | + } | |
| 655 | + | |
| 656 | + const controller = new AbortController(); | |
| 657 | + request.socket.removeAllListeners('close'); | |
| 658 | + request.socket.on('close', function () { | |
| 659 | + controller.abort(); | |
| 660 | + }); | |
| 661 | + | |
| 662 | + try { | |
| 663 | + let bodyParams = {}; | |
| 664 | + | |
| 665 | + if (request.body.logprobs > 0) { | |
| 666 | + bodyParams['top_logprobs'] = request.body.logprobs; | |
| 667 | + bodyParams['logprobs'] = true; | |
| 668 | + } | |
| 669 | + | |
| 670 | + const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; | |
| 671 | + const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); | |
| 672 | + | |
| 673 | + const requestBody = { | |
| 674 | + 'messages': processedMessages, | |
| 675 | + 'model': request.body.model, | |
| 676 | + 'temperature': request.body.temperature, | |
| 677 | + 'max_tokens': request.body.max_tokens, | |
| 678 | + 'stream': request.body.stream, | |
| 679 | + 'presence_penalty': request.body.presence_penalty, | |
| 680 | + 'frequency_penalty': request.body.frequency_penalty, | |
| 681 | + 'top_p': request.body.top_p, | |
| 682 | + 'stop': request.body.stop, | |
| 683 | + 'seed': request.body.seed, | |
| 684 | + ...bodyParams, | |
| 685 | + }; | |
| 686 | + | |
| 687 | + const config = { | |
| 688 | + method: 'POST', | |
| 689 | + headers: { | |
| 690 | + 'Content-Type': 'application/json', | |
| 691 | + 'Authorization': 'Bearer ' + apiKey, | |
| 692 | + }, | |
| 693 | + body: JSON.stringify(requestBody), | |
| 694 | + signal: controller.signal, | |
| 695 | + }; | |
| 696 | + | |
| 697 | + console.log('DeepSeek request:', requestBody); | |
| 698 | + | |
| 699 | + const generateResponse = await fetch(apiUrl + '/chat/completions', config); | |
| 700 | + | |
| 701 | + if (request.body.stream) { | |
| 702 | + forwardFetchResponse(generateResponse, response); | |
| 703 | + } else { | |
| 704 | + if (!generateResponse.ok) { | |
| 705 | + const errorText = await generateResponse.text(); | |
| 706 | + console.log(`DeepSeek API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | |
| 707 | + const errorJson = tryParse(errorText) ?? { error: true }; | |
| 708 | + return response.status(500).send(errorJson); | |
| 709 | + } | |
| 710 | + const generateResponseJson = await generateResponse.json(); | |
| 711 | + console.log('DeepSeek response:', generateResponseJson); | |
| 712 | + return response.send(generateResponseJson); | |
| 713 | + } | |
| 714 | + } catch (error) { | |
| 715 | + console.log('Error communicating with DeepSeek API: ', error); | |
| 716 | + if (!response.headersSent) { | |
| 717 | + response.send({ error: true }); | |
| 718 | + } else { | |
| 719 | + response.end(); | |
| 720 | + } | |
| 721 | + } | |
| 722 | +} | |
| 723 | + | |
| 724 | + | |
| 642 | 725 | export const router = express.Router(); |
| 643 | 726 | |
| 644 | 727 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { |
| @@ -683,8 +766,8 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o | ||
| 683 | 766 | api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); |
| 684 | 767 | headers = {}; |
| 685 | 768 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { |
| 686 | 769 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); |
| 687 | 770 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); |
| 688 | 771 | headers = {}; |
| 689 | 772 | } else { |
| 690 | 773 | console.log('This chat completion source is not supported yet.'); |
| @@ -844,6 +927,7 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 844 | 927 | case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response); |
| 845 | 928 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); |
| 846 | 929 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); |
| 930 | + case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); | |
| 847 | 931 | } |
| 848 | 932 | |
| 849 | 933 | let apiUrl; |
| @@ -957,19 +1041,6 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 957 | 1041 | apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY); |
| 958 | 1042 | headers = {}; |
| 959 | 1043 | bodyParams = {}; |
| 960 | - } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { | |
| 961 | - apiUrl = API_DEEPSEEK; | |
| 962 | - apiKey = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | |
| 963 | - headers = {}; | |
| 964 | - bodyParams = {}; | |
| 965 | - | |
| 966 | - if (request.body.logprobs > 0) { | |
| 967 | - bodyParams['top_logprobs'] = request.body.logprobs; | |
| 968 | - bodyParams['logprobs'] = true; | |
| 969 | - } | |
| 970 | - | |
| 971 | - const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; | |
| 972 | - request.body.messages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); | |
| 973 | 1044 | } else { |
| 974 | 1045 | console.log('This chat completion source is not supported yet.'); |
| 975 | 1046 | return response.status(400).send({ error: true }); |
| @@ -1107,4 +1178,3 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 1107 | 1178 | } |
| 1108 | 1179 | } |
| 1109 | 1180 | }); |
| 1110 | - | |