♻️ refactor: refactor sensenova provider with LobeOpenAICompatibleFactory (#5116)

This commit is contained in:
Zhijie He
2024-12-25 11:40:20 +08:00
committed by GitHub
parent 13f1eb4cda
commit 5656f39999
20 changed files with 109 additions and 459 deletions
+1 -1
View File
@@ -196,7 +196,7 @@ ENV \
# Qwen
QWEN_API_KEY="" QWEN_MODEL_LIST="" QWEN_PROXY_URL="" \
# SenseNova
SENSENOVA_ACCESS_KEY_ID="" SENSENOVA_ACCESS_KEY_SECRET="" SENSENOVA_MODEL_LIST="" \
SENSENOVA_API_KEY="" SENSENOVA_MODEL_LIST="" \
# SiliconCloud
SILICONCLOUD_API_KEY="" SILICONCLOUD_MODEL_LIST="" SILICONCLOUD_PROXY_URL="" \
# Spark
+1 -1
View File
@@ -231,7 +231,7 @@ ENV \
# Qwen
QWEN_API_KEY="" QWEN_MODEL_LIST="" QWEN_PROXY_URL="" \
# SenseNova
SENSENOVA_ACCESS_KEY_ID="" SENSENOVA_ACCESS_KEY_SECRET="" SENSENOVA_MODEL_LIST="" \
SENSENOVA_API_KEY="" SENSENOVA_MODEL_LIST="" \
# SiliconCloud
SILICONCLOUD_API_KEY="" SILICONCLOUD_MODEL_LIST="" SILICONCLOUD_PROXY_URL="" \
# Spark
@@ -1,44 +0,0 @@
'use client';
import { Input } from 'antd';
import { useTranslation } from 'react-i18next';
import { SenseNovaProviderCard } from '@/config/modelProviders';
import { GlobalLLMProviderKey } from '@/types/user/settings';
import { KeyVaultsConfigKey } from '../../const';
import { ProviderItem } from '../../type';
const providerKey: GlobalLLMProviderKey = 'sensenova';
export const useSenseNovaProvider = (): ProviderItem => {
const { t } = useTranslation('modelProvider');
return {
...SenseNovaProviderCard,
apiKeyItems: [
{
children: (
<Input.Password
autoComplete={'new-password'}
placeholder={t(`${providerKey}.sensenovaAccessKeyID.placeholder`)}
/>
),
desc: t(`${providerKey}.sensenovaAccessKeyID.desc`),
label: t(`${providerKey}.sensenovaAccessKeyID.title`),
name: [KeyVaultsConfigKey, providerKey, 'sensenovaAccessKeyID'],
},
{
children: (
<Input.Password
autoComplete={'new-password'}
placeholder={t(`${providerKey}.sensenovaAccessKeySecret.placeholder`)}
/>
),
desc: t(`${providerKey}.sensenovaAccessKeySecret.desc`),
label: t(`${providerKey}.sensenovaAccessKeySecret.title`),
name: [KeyVaultsConfigKey, providerKey, 'sensenovaAccessKeySecret'],
},
],
};
};
@@ -20,6 +20,7 @@ import {
OpenRouterProviderCard,
PerplexityProviderCard,
QwenProviderCard,
SenseNovaProviderCard,
SiliconCloudProviderCard,
SparkProviderCard,
StepfunProviderCard,
@@ -39,7 +40,6 @@ import { useGithubProvider } from './Github';
import { useHuggingFaceProvider } from './HuggingFace';
import { useOllamaProvider } from './Ollama';
import { useOpenAIProvider } from './OpenAI';
import { useSenseNovaProvider } from './SenseNova';
import { useWenxinProvider } from './Wenxin';
export const useProviderList = (): ProviderItem[] => {
@@ -51,7 +51,6 @@ export const useProviderList = (): ProviderItem[] => {
const GithubProvider = useGithubProvider();
const HuggingFaceProvider = useHuggingFaceProvider();
const WenxinProvider = useWenxinProvider();
const SenseNovaProvider = useSenseNovaProvider();
return useMemo(
() => [
@@ -81,7 +80,7 @@ export const useProviderList = (): ProviderItem[] => {
SparkProviderCard,
ZhiPuProviderCard,
ZeroOneProviderCard,
SenseNovaProvider,
SenseNovaProviderCard,
StepfunProviderCard,
MoonshotProviderCard,
BaichuanProviderCard,
@@ -102,7 +101,6 @@ export const useProviderList = (): ProviderItem[] => {
GithubProvider,
WenxinProvider,
HuggingFaceProvider,
SenseNovaProvider,
],
);
};
+3 -6
View File
@@ -113,8 +113,7 @@ export const getLLMConfig = () => {
HUGGINGFACE_API_KEY: z.string().optional(),
ENABLED_SENSENOVA: z.boolean(),
SENSENOVA_ACCESS_KEY_ID: z.string().optional(),
SENSENOVA_ACCESS_KEY_SECRET: z.string().optional(),
SENSENOVA_API_KEY: z.string().optional(),
ENABLED_XAI: z.boolean(),
XAI_API_KEY: z.string().optional(),
@@ -234,10 +233,8 @@ export const getLLMConfig = () => {
ENABLED_HUGGINGFACE: !!process.env.HUGGINGFACE_API_KEY,
HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY,
ENABLED_SENSENOVA:
!!process.env.SENSENOVA_ACCESS_KEY_ID && !!process.env.SENSENOVA_ACCESS_KEY_SECRET,
SENSENOVA_ACCESS_KEY_ID: process.env.SENSENOVA_ACCESS_KEY_ID,
SENSENOVA_ACCESS_KEY_SECRET: process.env.SENSENOVA_ACCESS_KEY_SECRET,
ENABLED_SENSENOVA: !!process.env.SENSENOVA_API_KEY,
SENSENOVA_API_KEY: process.env.SENSENOVA_API_KEY,
ENABLED_XAI: !!process.env.XAI_API_KEY,
XAI_API_KEY: process.env.XAI_API_KEY,
-3
View File
@@ -42,9 +42,6 @@ export interface JWTPayload {
wenxinAccessKey?: string;
wenxinSecretKey?: string;
sensenovaAccessKeyID?: string;
sensenovaAccessKeySecret?: string;
/**
* user id
* in client db mode it's a uuid
@@ -1,49 +0,0 @@
import { SenseNova } from '@lobehub/icons';
import { Input } from 'antd';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { ModelProvider } from '@/libs/agent-runtime';
import { useUserStore } from '@/store/user';
import { keyVaultsConfigSelectors } from '@/store/user/selectors';
import { FormAction } from '../style';
const SenseNovaForm = memo(() => {
const { t } = useTranslation('modelProvider');
const [sensenovaAccessKeyID, sensenovaAccessKeySecret, setConfig] = useUserStore((s) => [
keyVaultsConfigSelectors.sensenovaConfig(s).sensenovaAccessKeyID,
keyVaultsConfigSelectors.sensenovaConfig(s).sensenovaAccessKeySecret,
s.updateKeyVaultConfig,
]);
return (
<FormAction
avatar={<SenseNova color={SenseNova.colorPrimary} size={56} />}
description={t('sensenova.unlock.description')}
title={t('sensenova.unlock.title')}
>
<Input.Password
autoComplete={'new-password'}
onChange={(e) => {
setConfig(ModelProvider.SenseNova, { sensenovaAccessKeyID: e.target.value });
}}
placeholder={t('sensenova.sensenovaAccessKeyID.placeholder')}
type={'block'}
value={sensenovaAccessKeyID}
/>
<Input.Password
autoComplete={'new-password'}
onChange={(e) => {
setConfig(ModelProvider.SenseNova, { sensenovaAccessKeySecret: e.target.value });
}}
placeholder={t('sensenova.sensenovaAccessKeySecret.placeholder')}
type={'block'}
value={sensenovaAccessKeySecret}
/>
</FormAction>
);
});
export default SenseNovaForm;
@@ -10,7 +10,6 @@ import { GlobalLLMProviderKey } from '@/types/user/settings';
import BedrockForm from './Bedrock';
import ProviderApiKeyForm from './ProviderApiKeyForm';
import SenseNovaForm from './SenseNova';
import WenxinForm from './Wenxin';
interface APIKeyFormProps {
@@ -67,8 +66,6 @@ const APIKeyForm = memo<APIKeyFormProps>(({ id, provider }) => {
<Center gap={16} style={{ maxWidth: 300 }}>
{provider === ModelProvider.Bedrock ? (
<BedrockForm />
) : provider === ModelProvider.SenseNova ? (
<SenseNovaForm />
) : provider === ModelProvider.Wenxin ? (
<WenxinForm />
) : (
+1 -1
View File
@@ -333,7 +333,7 @@ class AgentRuntime {
}
case ModelProvider.SenseNova: {
runtimeModel = await LobeSenseNovaAI.fromAPIKey(params.sensenova);
runtimeModel = new LobeSenseNovaAI(params.sensenova);
break;
}
-1
View File
@@ -15,7 +15,6 @@ export { LobeOpenAI } from './openai';
export { LobeOpenRouterAI } from './openrouter';
export { LobePerplexityAI } from './perplexity';
export { LobeQwenAI } from './qwen';
export { LobeSenseNovaAI } from './sensenova';
export { LobeTogetherAI } from './togetherai';
export * from './types';
export { AgentRuntimeError } from './utils/createError';
@@ -1,18 +0,0 @@
// @vitest-environment node
import { generateApiToken } from './authToken';
describe('generateApiToken', () => {
it('should throw an error if no apiKey is provided', async () => {
await expect(generateApiToken()).rejects.toThrow('Invalid apiKey');
});
it('should throw an error if apiKey is invalid', async () => {
await expect(generateApiToken('invalid')).rejects.toThrow('Invalid apiKey');
});
it('should return a token if a valid apiKey is provided', async () => {
const apiKey = 'id:secret';
const token = await generateApiToken(apiKey);
expect(token).toBeDefined();
});
});
@@ -1,27 +0,0 @@
import { SignJWT } from 'jose';
// https://console.sensecore.cn/help/docs/model-as-a-service/nova/overview/Authorization
export const generateApiToken = async (apiKey?: string): Promise<string> => {
if (!apiKey) {
throw new Error('Invalid apiKey');
}
const [id, secret] = apiKey.split(':');
if (!id || !secret) {
throw new Error('Invalid apiKey');
}
const currentTime = Math.floor(Date.now() / 1000);
const payload = {
exp: currentTime + 1800,
iss: id,
nbf: currentTime - 5,
};
const jwt = await new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
.sign(new TextEncoder().encode(secret));
return jwt;
};
+79 -149
View File
@@ -1,142 +1,49 @@
// @vitest-environment node
import { OpenAI } from 'openai';
import OpenAI from 'openai';
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ChatStreamCallbacks, LobeOpenAI } from '@/libs/agent-runtime';
import * as debugStreamModule from '@/libs/agent-runtime/utils/debugStream';
import { LobeOpenAICompatibleRuntime } from '@/libs/agent-runtime';
import { ModelProvider } from '@/libs/agent-runtime';
import { AgentRuntimeErrorType } from '@/libs/agent-runtime';
import * as authTokenModule from './authToken';
import * as debugStreamModule from '../utils/debugStream';
import { LobeSenseNovaAI } from './index';
const bizErrorType = 'ProviderBizError';
const invalidErrorType = 'InvalidProviderAPIKey';
const provider = ModelProvider.SenseNova;
const defaultBaseURL = 'https://api.sensenova.cn/compatible-mode/v1';
const bizErrorType = AgentRuntimeErrorType.ProviderBizError;
const invalidErrorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
// Mock相关依赖
vi.mock('./authToken');
// Mock the console.error to avoid polluting test output
vi.spyOn(console, 'error').mockImplementation(() => {});
let instance: LobeOpenAICompatibleRuntime;
beforeEach(() => {
instance = new LobeSenseNovaAI({ apiKey: 'test' });
// 使用 vi.spyOn 来模拟 chat.completions.create 方法
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
new ReadableStream() as any,
);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('LobeSenseNovaAI', () => {
beforeEach(() => {
// Mock generateApiToken
vi.spyOn(authTokenModule, 'generateApiToken').mockResolvedValue('mocked_token');
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('fromAPIKey', () => {
describe('init', () => {
it('should correctly initialize with an API key', async () => {
const lobeSenseNovaAI = await LobeSenseNovaAI.fromAPIKey({ apiKey: 'test_api_key' });
expect(lobeSenseNovaAI).toBeInstanceOf(LobeSenseNovaAI);
expect(lobeSenseNovaAI.baseURL).toEqual('https://api.sensenova.cn/compatible-mode/v1');
});
it('should throw an error if API key is invalid', async () => {
vi.spyOn(authTokenModule, 'generateApiToken').mockRejectedValue(new Error('Invalid API Key'));
try {
await LobeSenseNovaAI.fromAPIKey({ apiKey: 'asd' });
} catch (e) {
expect(e).toEqual({ errorType: invalidErrorType });
}
const instance = new LobeSenseNovaAI({ apiKey: 'test_api_key' });
expect(instance).toBeInstanceOf(LobeSenseNovaAI);
expect(instance.baseURL).toEqual(defaultBaseURL);
});
});
describe('chat', () => {
let instance: LobeSenseNovaAI;
beforeEach(async () => {
instance = await LobeSenseNovaAI.fromAPIKey({
apiKey: 'test_api_key',
});
// Mock chat.completions.create
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
new ReadableStream() as any,
);
});
it('should return a StreamingTextResponse on successful API call', async () => {
const result = await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0,
});
expect(result).toBeInstanceOf(Response);
});
it('should handle callback and headers correctly', async () => {
// 模拟 chat.completions.create 方法返回一个可读流
const mockCreateMethod = vi
.spyOn(instance['client'].chat.completions, 'create')
.mockResolvedValue(
new ReadableStream({
start(controller) {
controller.enqueue({
id: 'chatcmpl-8xDx5AETP8mESQN7UB30GxTN2H1SO',
object: 'chat.completion.chunk',
created: 1709125675,
model: 'gpt-3.5-turbo-0125',
system_fingerprint: 'fp_86156a94a0',
choices: [
{ index: 0, delta: { content: 'hello' }, logprobs: null, finish_reason: null },
],
});
controller.close();
},
}) as any,
);
// 准备 callback 和 headers
const mockCallback: ChatStreamCallbacks = {
onStart: vi.fn(),
onToken: vi.fn(),
};
const mockHeaders = { 'Custom-Header': 'TestValue' };
// 执行测试
const result = await instance.chat(
{
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0,
},
{ callback: mockCallback, headers: mockHeaders },
);
// 验证 callback 被调用
await result.text(); // 确保流被消费
// 验证 headers 被正确传递
expect(result.headers.get('Custom-Header')).toEqual('TestValue');
// 清理
mockCreateMethod.mockRestore();
});
it('should transform messages correctly', async () => {
const spyOn = vi.spyOn(instance['client'].chat.completions, 'create');
await instance.chat({
frequency_penalty: 0,
messages: [
{ content: 'Hello', role: 'user' },
{ content: [{ type: 'text', text: 'Hello again' }], role: 'user' },
],
model: 'SenseChat',
temperature: 0,
top_p: 1,
});
const calledWithParams = spyOn.mock.calls[0][0];
expect(calledWithParams.frequency_penalty).toBeUndefined(); // frequency_penalty 0 should be undefined
expect(calledWithParams.messages[1].content).toEqual([{ type: 'text', text: 'Hello again' }]);
expect(calledWithParams.temperature).toBeUndefined(); // temperature 0 should be undefined
expect(calledWithParams.top_p).toBeUndefined(); // top_p 1 should be undefined
});
describe('Error', () => {
it('should return SenseNovaAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
it('should return QwenBizError with an openai error response when OpenAI.APIError is thrown', async () => {
// Arrange
const apiError = new OpenAI.APIError(
400,
@@ -156,31 +63,31 @@ describe('LobeSenseNovaAI', () => {
try {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0,
model: 'max-32k',
temperature: 0.999,
});
} catch (e) {
expect(e).toEqual({
endpoint: 'https://api.sensenova.cn/compatible-mode/v1',
endpoint: defaultBaseURL,
error: {
error: { message: 'Bad Request' },
status: 400,
},
errorType: bizErrorType,
provider: 'sensenova',
provider,
});
}
});
it('should throw AgentRuntimeError with NoOpenAIAPIKey if no apiKey is provided', async () => {
it('should throw AgentRuntimeError with InvalidQwenAPIKey if no apiKey is provided', async () => {
try {
await LobeSenseNovaAI.fromAPIKey({ apiKey: '' });
new LobeSenseNovaAI({});
} catch (e) {
expect(e).toEqual({ errorType: invalidErrorType });
}
});
it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
it('should return QwenBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
// Arrange
const errorInfo = {
stack: 'abc',
@@ -196,23 +103,23 @@ describe('LobeSenseNovaAI', () => {
try {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0.2,
model: 'max-32k',
temperature: 0.999,
});
} catch (e) {
expect(e).toEqual({
endpoint: 'https://api.sensenova.cn/compatible-mode/v1',
endpoint: defaultBaseURL,
error: {
cause: { message: 'api is undefined' },
stack: 'abc',
},
errorType: bizErrorType,
provider: 'sensenova',
provider,
});
}
});
it('should return OpenAIBizError with an cause response with desensitize Url', async () => {
it('should return QwenBizError with an cause response with desensitize Url', async () => {
// Arrange
const errorInfo = {
stack: 'abc',
@@ -220,10 +127,10 @@ describe('LobeSenseNovaAI', () => {
};
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
instance = await LobeSenseNovaAI.fromAPIKey({
instance = new LobeSenseNovaAI({
apiKey: 'test',
baseURL: 'https://abc.com/v2',
baseURL: 'https://api.abc.com/v1',
});
vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
@@ -232,18 +139,40 @@ describe('LobeSenseNovaAI', () => {
try {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'gpt-3.5-turbo',
temperature: 0,
model: 'max-32k',
temperature: 0.999,
});
} catch (e) {
expect(e).toEqual({
endpoint: 'https://***.com/v2',
endpoint: 'https://api.***.com/v1',
error: {
cause: { message: 'api is undefined' },
stack: 'abc',
},
errorType: bizErrorType,
provider: 'sensenova',
provider,
});
}
});
it('should throw an InvalidQwenAPIKey error type on 401 status code', async () => {
// Mock the API call to simulate a 401 error
const error = new Error('InvalidApiKey') as any;
error.status = 401;
vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);
try {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'max-32k',
temperature: 0.999,
});
} catch (e) {
expect(e).toEqual({
endpoint: defaultBaseURL,
error: new Error('InvalidApiKey'),
errorType: invalidErrorType,
provider,
});
}
});
@@ -258,14 +187,14 @@ describe('LobeSenseNovaAI', () => {
try {
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0,
model: 'max-32k',
temperature: 0.999,
});
} catch (e) {
expect(e).toEqual({
endpoint: 'https://api.sensenova.cn/compatible-mode/v1',
endpoint: defaultBaseURL,
errorType: 'AgentRuntimeError',
provider: 'sensenova',
provider,
error: {
name: genericError.name,
cause: genericError.cause,
@@ -278,7 +207,7 @@ describe('LobeSenseNovaAI', () => {
});
describe('DEBUG', () => {
it('should call debugStream and return StreamingTextResponse when DEBUG_OPENAI_CHAT_COMPLETION is 1', async () => {
it('should call debugStream and return StreamingTextResponse when DEBUG_SENSENOVA_CHAT_COMPLETION is 1', async () => {
// Arrange
const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流
const mockDebugStream = new ReadableStream({
@@ -306,8 +235,9 @@ describe('LobeSenseNovaAI', () => {
// 假设的测试函数调用,你可能需要根据实际情况调整
await instance.chat({
messages: [{ content: 'Hello', role: 'user' }],
model: 'SenseChat',
temperature: 0,
model: 'max-32k',
stream: true,
temperature: 0.999,
});
// 验证 debugStream 被调用
+21 -96
View File
@@ -1,98 +1,23 @@
import OpenAI, { ClientOptions } from 'openai';
import { ModelProvider } from '../types';
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
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';
import { convertOpenAIMessages } from '../utils/openaiHelpers';
import { StreamingResponse } from '../utils/response';
import { OpenAIStream } from '../utils/streams';
import { generateApiToken } from './authToken';
export const LobeSenseNovaAI = LobeOpenAICompatibleFactory({
baseURL: 'https://api.sensenova.cn/compatible-mode/v1',
chatCompletion: {
handlePayload: (payload) => {
const { frequency_penalty, temperature, top_p, ...rest } = payload;
const DEFAULT_BASE_URL = 'https://api.sensenova.cn/compatible-mode/v1';
export class LobeSenseNovaAI implements LobeRuntimeAI {
private client: OpenAI;
baseURL: string;
constructor(oai: OpenAI) {
this.client = oai;
this.baseURL = this.client.baseURL;
}
static async fromAPIKey({ apiKey, baseURL = DEFAULT_BASE_URL, ...res }: ClientOptions = {}) {
const invalidSenseNovaAPIKey = AgentRuntimeError.createError(
AgentRuntimeErrorType.InvalidProviderAPIKey,
);
if (!apiKey) throw invalidSenseNovaAPIKey;
let token: string;
try {
token = await generateApiToken(apiKey);
} catch {
throw invalidSenseNovaAPIKey;
}
const header = { Authorization: `Bearer ${token}` };
const llm = new OpenAI({ apiKey, baseURL, defaultHeaders: header, ...res });
return new LobeSenseNovaAI(llm);
}
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
try {
const params = await this.buildCompletionsParams(payload);
const response = await this.client.chat.completions.create(
params as unknown as OpenAI.ChatCompletionCreateParamsStreaming,
);
const [prod, debug] = response.tee();
if (process.env.DEBUG_SENSENOVA_CHAT_COMPLETION === '1') {
debugStream(debug.toReadableStream()).catch(console.error);
}
return StreamingResponse(OpenAIStream(prod), {
headers: options?.headers,
});
} catch (error) {
const { errorResult, RuntimeError } = handleOpenAIError(error);
const errorType = RuntimeError || AgentRuntimeErrorType.ProviderBizError;
let desensitizedEndpoint = this.baseURL;
if (this.baseURL !== DEFAULT_BASE_URL) {
desensitizedEndpoint = desensitizeUrl(this.baseURL);
}
throw AgentRuntimeError.chat({
endpoint: desensitizedEndpoint,
error: errorResult,
errorType,
provider: ModelProvider.SenseNova,
});
}
}
private async buildCompletionsParams(payload: ChatStreamPayload) {
const { frequency_penalty, messages, temperature, top_p, ...params } = payload;
return {
messages: await convertOpenAIMessages(messages as any),
...params,
frequency_penalty: (frequency_penalty !== undefined && frequency_penalty > 0 && frequency_penalty <= 2) ? frequency_penalty : undefined,
stream: true,
temperature: (temperature !== undefined && temperature > 0 && temperature <= 2) ? temperature : undefined,
top_p: (top_p !== undefined && top_p > 0 && top_p < 1) ? top_p : undefined,
};
}
}
export default LobeSenseNovaAI;
return {
...rest,
frequency_penalty: (frequency_penalty !== undefined && frequency_penalty > 0 && frequency_penalty <= 2) ? frequency_penalty : undefined,
stream: true,
temperature: (temperature !== undefined && temperature > 0 && temperature <= 2) ? temperature : undefined,
top_p: (top_p !== undefined && top_p > 0 && top_p < 1) ? top_p : undefined,
} as any;
},
},
debug: {
chatCompletion: () => process.env.DEBUG_SENSENOVA_CHAT_COMPLETION === '1',
},
provider: ModelProvider.SenseNova,
});
-17
View File
@@ -134,23 +134,6 @@ export default {
title: '下载指定的 Ollama 模型',
},
},
sensenova: {
sensenovaAccessKeyID: {
desc: '填入 SenseNova Access Key ID',
placeholder: 'SenseNova Access Key ID',
title: 'Access Key ID',
},
sensenovaAccessKeySecret: {
desc: '填入 SenseNova Access Key Secret',
placeholder: 'SenseNova Access Key Secret',
title: 'Access Key Secret',
},
unlock: {
description:
'输入你的 Access Key ID / Access Key Secret 即可开始会话。应用不会记录你的鉴权配置',
title: '使用自定义 SenseNova 鉴权信息',
},
},
wenxin: {
accessKey: {
desc: '填入百度千帆平台的 Access Key',
-15
View File
@@ -100,21 +100,6 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
return { apiKey };
}
case ModelProvider.SenseNova: {
const { SENSENOVA_ACCESS_KEY_ID, SENSENOVA_ACCESS_KEY_SECRET } = llmConfig;
const sensenovaAccessKeyID = apiKeyManager.pick(
payload?.sensenovaAccessKeyID || SENSENOVA_ACCESS_KEY_ID,
);
const sensenovaAccessKeySecret = apiKeyManager.pick(
payload?.sensenovaAccessKeySecret || SENSENOVA_ACCESS_KEY_SECRET,
);
const apiKey = sensenovaAccessKeyID + ':' + sensenovaAccessKeySecret;
return { apiKey };
}
}
};
-14
View File
@@ -25,20 +25,6 @@ export const getProviderAuthPayload = (provider: string) => {
};
}
case ModelProvider.SenseNova: {
const { sensenovaAccessKeyID, sensenovaAccessKeySecret } = keyVaultsConfigSelectors.sensenovaConfig(
useUserStore.getState(),
);
const apiKey = (sensenovaAccessKeyID || '') + ':' + (sensenovaAccessKeySecret || '')
return {
apiKey,
sensenovaAccessKeyID: sensenovaAccessKeyID,
sensenovaAccessKeySecret: sensenovaAccessKeySecret,
};
}
case ModelProvider.Wenxin: {
const { secretKey, accessKey } = keyVaultsConfigSelectors.wenxinConfig(
useUserStore.getState(),
@@ -16,7 +16,6 @@ const openAIConfig = (s: UserStore) => keyVaultsSettings(s).openai || {};
const bedrockConfig = (s: UserStore) => keyVaultsSettings(s).bedrock || {};
const wenxinConfig = (s: UserStore) => keyVaultsSettings(s).wenxin || {};
const ollamaConfig = (s: UserStore) => keyVaultsSettings(s).ollama || {};
const sensenovaConfig = (s: UserStore) => keyVaultsSettings(s).sensenova || {};
const azureConfig = (s: UserStore) => keyVaultsSettings(s).azure || {};
const cloudflareConfig = (s: UserStore) => keyVaultsSettings(s).cloudflare || {};
const getVaultByProvider = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
@@ -46,6 +45,5 @@ export const keyVaultsConfigSelectors = {
ollamaConfig,
openAIConfig,
password,
sensenovaConfig,
wenxinConfig,
};
@@ -70,7 +70,6 @@ const bedrockConfig = (s: UserStore) => currentLLMSettings(s).bedrock;
const ollamaConfig = (s: UserStore) => currentLLMSettings(s).ollama;
const azureConfig = (s: UserStore) => currentLLMSettings(s).azure;
const cloudflareConfig = (s: UserStore) => currentLLMSettings(s).cloudflare;
const sensenovaConfig = (s: UserStore) => currentLLMSettings(s).sensenova;
const isAzureEnabled = (s: UserStore) => currentLLMSettings(s).azure.enabled;
@@ -89,5 +88,4 @@ export const modelConfigSelectors = {
ollamaConfig,
openAIConfig,
sensenovaConfig,
};
+1 -6
View File
@@ -21,11 +21,6 @@ export interface CloudflareKeyVault {
baseURLOrAccountID?: string;
}
export interface SenseNovaKeyVault {
sensenovaAccessKeyID?: string;
sensenovaAccessKeySecret?: string;
}
export interface WenxinKeyVault {
accessKey?: string;
secretKey?: string;
@@ -60,7 +55,7 @@ export interface UserKeyVaults {
password?: string;
perplexity?: OpenAICompatibleKeyVault;
qwen?: OpenAICompatibleKeyVault;
sensenova?: SenseNovaKeyVault;
sensenova?: OpenAICompatibleKeyVault;
siliconcloud?: OpenAICompatibleKeyVault;
spark?: OpenAICompatibleKeyVault;
stepfun?: OpenAICompatibleKeyVault;