@lobehub/chat 1.69.1 → 1.69.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/models.json +9 -0
- package/locales/bg-BG/models.json +9 -0
- package/locales/de-DE/models.json +9 -0
- package/locales/en-US/models.json +9 -0
- package/locales/es-ES/models.json +9 -0
- package/locales/fa-IR/models.json +9 -0
- package/locales/fr-FR/models.json +9 -0
- package/locales/it-IT/models.json +9 -0
- package/locales/ja-JP/models.json +9 -0
- package/locales/ko-KR/models.json +9 -0
- package/locales/nl-NL/models.json +9 -0
- package/locales/pl-PL/models.json +9 -0
- package/locales/pt-BR/models.json +9 -0
- package/locales/ru-RU/models.json +9 -0
- package/locales/tr-TR/models.json +9 -0
- package/locales/vi-VN/models.json +9 -0
- package/locales/zh-CN/models.json +9 -0
- package/locales/zh-TW/models.json +9 -0
- package/package.json +3 -3
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +161 -0
- package/src/app/[variants]/(auth)/next-auth/signin/page.tsx +11 -0
- package/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx +1 -1
- package/src/{app/[variants]/(main)/profile/(home)/features/SSOProvidersList → components/NextAuth}/AuthIcons.tsx +8 -6
- package/src/libs/agent-runtime/UniformRuntime/index.ts +114 -0
- package/src/libs/agent-runtime/anthropic/handleAnthropicError.ts +15 -0
- package/src/libs/agent-runtime/anthropic/index.test.ts +10 -1
- package/src/libs/agent-runtime/anthropic/index.ts +58 -40
- package/src/libs/agent-runtime/azureai/index.ts +7 -1
- package/src/libs/agent-runtime/github/index.ts +20 -25
- package/src/libs/agent-runtime/index.ts +2 -0
- package/src/libs/agent-runtime/openai/index.ts +2 -22
- package/src/libs/agent-runtime/types/type.ts +1 -1
- package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +11 -7
- package/src/libs/agent-runtime/utils/openaiHelpers.ts +22 -0
- package/src/libs/next-auth/auth.config.ts +1 -0
- package/src/middleware.ts +1 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.69.
|
3
|
+
"version": "1.69.3",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -133,7 +133,7 @@
|
|
133
133
|
"@lobehub/chat-plugins-gateway": "^1.9.0",
|
134
134
|
"@lobehub/icons": "^1.73.1",
|
135
135
|
"@lobehub/tts": "^1.28.0",
|
136
|
-
"@lobehub/ui": "^1.165.
|
136
|
+
"@lobehub/ui": "^1.165.5",
|
137
137
|
"@neondatabase/serverless": "^0.10.4",
|
138
138
|
"@next/third-parties": "^15.2.0",
|
139
139
|
"@react-spring/web": "^9.7.5",
|
@@ -165,7 +165,7 @@
|
|
165
165
|
"epub2": "^3.0.2",
|
166
166
|
"fast-deep-equal": "^3.1.3",
|
167
167
|
"file-type": "^20.0.0",
|
168
|
-
"framer-motion": "^
|
168
|
+
"framer-motion": "^12.0.0",
|
169
169
|
"gpt-tokenizer": "^2.8.1",
|
170
170
|
"html-to-text": "^9.0.5",
|
171
171
|
"i18next": "^24.2.1",
|
@@ -0,0 +1,161 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { LobeChat } from '@lobehub/ui/brand';
|
4
|
+
import { Button, Col, Flex, Row, Skeleton, Typography } from 'antd';
|
5
|
+
import { createStyles } from 'antd-style';
|
6
|
+
import { AuthError } from 'next-auth';
|
7
|
+
import { signIn } from 'next-auth/react';
|
8
|
+
import { useRouter, useSearchParams } from 'next/navigation';
|
9
|
+
import { memo } from 'react';
|
10
|
+
import { useTranslation } from 'react-i18next';
|
11
|
+
|
12
|
+
import BrandWatermark from '@/components/BrandWatermark';
|
13
|
+
import AuthIcons from '@/components/NextAuth/AuthIcons';
|
14
|
+
import { DOCUMENTS_REFER_URL, PRIVACY_URL, TERMS_URL } from '@/const/url';
|
15
|
+
import { useUserStore } from '@/store/user';
|
16
|
+
|
17
|
+
const { Title, Paragraph } = Typography;
|
18
|
+
|
19
|
+
const useStyles = createStyles(({ css, token }) => ({
|
20
|
+
button: css`
|
21
|
+
text-transform: capitalize;
|
22
|
+
`,
|
23
|
+
container: css`
|
24
|
+
min-width: 360px;
|
25
|
+
border: 1px solid ${token.colorBorder};
|
26
|
+
border-radius: ${token.borderRadiusLG}px;
|
27
|
+
background: ${token.colorBgContainer};
|
28
|
+
`,
|
29
|
+
contentCard: css`
|
30
|
+
padding-block: 2.5rem;
|
31
|
+
padding-inline: 2rem;
|
32
|
+
`,
|
33
|
+
description: css`
|
34
|
+
margin: 0;
|
35
|
+
color: ${token.colorTextSecondary};
|
36
|
+
`,
|
37
|
+
footer: css`
|
38
|
+
padding: 1rem;
|
39
|
+
border-block-start: 1px solid ${token.colorBorder};
|
40
|
+
border-radius: 0 0 8px 8px;
|
41
|
+
|
42
|
+
color: ${token.colorTextDescription};
|
43
|
+
|
44
|
+
background: ${token.colorBgElevated};
|
45
|
+
`,
|
46
|
+
text: css`
|
47
|
+
text-align: center;
|
48
|
+
`,
|
49
|
+
title: css`
|
50
|
+
margin: 0;
|
51
|
+
color: ${token.colorTextHeading};
|
52
|
+
`,
|
53
|
+
}));
|
54
|
+
|
55
|
+
const BtnListLoading = memo(() => {
|
56
|
+
return (
|
57
|
+
<Flex gap={'small'} vertical>
|
58
|
+
<Skeleton.Button active style={{ minWidth: 300 }} />
|
59
|
+
<Skeleton.Button active style={{ minWidth: 300 }} />
|
60
|
+
<Skeleton.Button active style={{ minWidth: 300 }} />
|
61
|
+
</Flex>
|
62
|
+
);
|
63
|
+
});
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Follow the implementation from AuthJS official documentation,
|
67
|
+
* but using client components.
|
68
|
+
* ref: https://authjs.dev/guides/pages/signin
|
69
|
+
*/
|
70
|
+
export default memo(() => {
|
71
|
+
const { styles } = useStyles();
|
72
|
+
const { t } = useTranslation('clerk');
|
73
|
+
const router = useRouter();
|
74
|
+
|
75
|
+
const oAuthSSOProviders = useUserStore((s) => s.oAuthSSOProviders);
|
76
|
+
|
77
|
+
const searchParams = useSearchParams();
|
78
|
+
|
79
|
+
// Redirect back to the page url
|
80
|
+
const callbackUrl = searchParams.get('callbackUrl') ?? '';
|
81
|
+
|
82
|
+
const handleSignIn = async (provider: string) => {
|
83
|
+
try {
|
84
|
+
await signIn(provider, { redirectTo: callbackUrl });
|
85
|
+
} catch (error) {
|
86
|
+
// Signin can fail for a number of reasons, such as the user
|
87
|
+
// not existing, or the user not having the correct role.
|
88
|
+
// In some cases, you may want to redirect to a custom error
|
89
|
+
if (error instanceof AuthError) {
|
90
|
+
return router.push(`/next-auth/?error=${error.type}`);
|
91
|
+
}
|
92
|
+
|
93
|
+
// Otherwise if a redirects happens Next.js can handle it
|
94
|
+
// so you can just re-thrown the error and let Next.js handle it.
|
95
|
+
// Docs: https://nextjs.org/docs/app/api-reference/functions/redirect#server-component
|
96
|
+
throw error;
|
97
|
+
}
|
98
|
+
};
|
99
|
+
|
100
|
+
const footerBtns = [
|
101
|
+
{ href: DOCUMENTS_REFER_URL, id: 0, label: t('footerPageLink__help') },
|
102
|
+
{ href: PRIVACY_URL, id: 1, label: t('footerPageLink__privacy') },
|
103
|
+
{ href: TERMS_URL, id: 2, label: t('footerPageLink__terms') },
|
104
|
+
];
|
105
|
+
|
106
|
+
return (
|
107
|
+
<div className={styles.container}>
|
108
|
+
<div className={styles.contentCard}>
|
109
|
+
{/* Card Body */}
|
110
|
+
<Flex gap="large" vertical>
|
111
|
+
{/* Header */}
|
112
|
+
<div className={styles.text}>
|
113
|
+
<Title className={styles.title} level={4}>
|
114
|
+
<div>
|
115
|
+
<LobeChat size={48} />
|
116
|
+
</div>
|
117
|
+
{t('signIn.start.title', { applicationName: 'LobeChat' })}
|
118
|
+
</Title>
|
119
|
+
<Paragraph className={styles.description}>{t('signIn.start.subtitle')}</Paragraph>
|
120
|
+
</div>
|
121
|
+
{/* Content */}
|
122
|
+
<Flex gap="small" vertical>
|
123
|
+
{oAuthSSOProviders ? (
|
124
|
+
oAuthSSOProviders.map((provider) => (
|
125
|
+
<Button
|
126
|
+
className={styles.button}
|
127
|
+
icon={AuthIcons(provider, 16)}
|
128
|
+
key={provider}
|
129
|
+
onClick={() => handleSignIn(provider)}
|
130
|
+
>
|
131
|
+
{provider}
|
132
|
+
</Button>
|
133
|
+
))
|
134
|
+
) : (
|
135
|
+
<BtnListLoading />
|
136
|
+
)}
|
137
|
+
</Flex>
|
138
|
+
</Flex>
|
139
|
+
</div>
|
140
|
+
<div className={styles.footer}>
|
141
|
+
{/* Footer */}
|
142
|
+
<Row>
|
143
|
+
<Col span={12}>
|
144
|
+
<Flex justify="left" style={{ height: '100%' }}>
|
145
|
+
<BrandWatermark />
|
146
|
+
</Flex>
|
147
|
+
</Col>
|
148
|
+
<Col offset={4} span={8}>
|
149
|
+
<Flex justify="right">
|
150
|
+
{footerBtns.map((btn) => (
|
151
|
+
<Button key={btn.id} onClick={() => router.push(btn.href)} size="small" type="text">
|
152
|
+
{btn.label}
|
153
|
+
</Button>
|
154
|
+
))}
|
155
|
+
</Flex>
|
156
|
+
</Col>
|
157
|
+
</Row>
|
158
|
+
</div>
|
159
|
+
</div>
|
160
|
+
);
|
161
|
+
});
|
@@ -10,7 +10,7 @@ import { userService } from '@/services/user';
|
|
10
10
|
import { useUserStore } from '@/store/user';
|
11
11
|
import { userProfileSelectors } from '@/store/user/selectors';
|
12
12
|
|
13
|
-
import AuthIcons from '
|
13
|
+
import AuthIcons from '@/components/NextAuth/AuthIcons';
|
14
14
|
|
15
15
|
const { Item } = List;
|
16
16
|
|
@@ -12,10 +12,6 @@ import {
|
|
12
12
|
} from '@lobehub/ui/icons';
|
13
13
|
import React from 'react';
|
14
14
|
|
15
|
-
const iconProps = {
|
16
|
-
size: 32,
|
17
|
-
};
|
18
|
-
|
19
15
|
const iconComponents: { [key: string]: React.ElementType } = {
|
20
16
|
'auth0': Auth0,
|
21
17
|
'authelia': Authelia.Color,
|
@@ -29,9 +25,15 @@ const iconComponents: { [key: string]: React.ElementType } = {
|
|
29
25
|
'zitadel': Zitadel.Color,
|
30
26
|
};
|
31
27
|
|
32
|
-
|
28
|
+
/**
|
29
|
+
* Get the auth icons component for the given id
|
30
|
+
* @param id
|
31
|
+
* @param size default is 36
|
32
|
+
* @returns
|
33
|
+
*/
|
34
|
+
const AuthIcons = (id: string, size = 36) => {
|
33
35
|
const IconComponent = iconComponents[id] || iconComponents.default;
|
34
|
-
return <IconComponent {
|
36
|
+
return <IconComponent size={size}/>;
|
35
37
|
};
|
36
38
|
|
37
39
|
export default AuthIcons;
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import { LobeRuntimeAI } from '../BaseAI';
|
2
|
+
import { LobeOpenAI } from '../openai';
|
3
|
+
import { providerRuntimeMap } from '../runtimeMap';
|
4
|
+
import {
|
5
|
+
ChatCompetitionOptions,
|
6
|
+
type ChatCompletionErrorPayload,
|
7
|
+
ChatStreamPayload,
|
8
|
+
EmbeddingsOptions,
|
9
|
+
EmbeddingsPayload,
|
10
|
+
TextToImagePayload,
|
11
|
+
TextToSpeechPayload,
|
12
|
+
} from '../types';
|
13
|
+
|
14
|
+
export interface RuntimeItem {
|
15
|
+
id: string;
|
16
|
+
models?: string[];
|
17
|
+
runtime: LobeRuntimeAI;
|
18
|
+
}
|
19
|
+
|
20
|
+
interface ProviderInitParams extends Record<string, any> {
|
21
|
+
accessKeyId?: string;
|
22
|
+
accessKeySecret?: string;
|
23
|
+
apiKey?: string;
|
24
|
+
apiVersion?: string;
|
25
|
+
baseURL?: string;
|
26
|
+
baseURLOrAccountID?: string;
|
27
|
+
dangerouslyAllowBrowser?: boolean;
|
28
|
+
region?: string;
|
29
|
+
sessionToken?: string;
|
30
|
+
}
|
31
|
+
|
32
|
+
interface ProviderInstance {
|
33
|
+
apiType: keyof typeof providerRuntimeMap;
|
34
|
+
models?: string[];
|
35
|
+
params: ProviderInitParams;
|
36
|
+
runtime?: typeof LobeOpenAI;
|
37
|
+
}
|
38
|
+
|
39
|
+
interface UniformRuntimeOptions {
|
40
|
+
chat?: {
|
41
|
+
handleError?: (error: any) => Omit<ChatCompletionErrorPayload, 'provider'> | undefined;
|
42
|
+
};
|
43
|
+
}
|
44
|
+
|
45
|
+
class UniformRuntime {
|
46
|
+
private _runtimes: RuntimeItem[];
|
47
|
+
private _options: UniformRuntimeOptions;
|
48
|
+
|
49
|
+
constructor(id: string, providers: ProviderInstance[], options: UniformRuntimeOptions) {
|
50
|
+
if (providers.length === 0) {
|
51
|
+
throw new Error('empty providers');
|
52
|
+
}
|
53
|
+
|
54
|
+
this._runtimes = providers.map((options) => {
|
55
|
+
const providerAI = options.runtime ?? providerRuntimeMap[options.apiType] ?? LobeOpenAI;
|
56
|
+
const runtime: LobeRuntimeAI = new providerAI({ ...options.params, id });
|
57
|
+
|
58
|
+
return { id: options.apiType, models: options.models, runtime };
|
59
|
+
});
|
60
|
+
|
61
|
+
this._options = options;
|
62
|
+
}
|
63
|
+
|
64
|
+
// 检查下是否能匹配到特定模型,否则默认使用第一个 runtime
|
65
|
+
getRuntimeByModel(model: string) {
|
66
|
+
const runtimeItem =
|
67
|
+
this._runtimes.find((runtime) => runtime.models && runtime.models.includes(model)) ||
|
68
|
+
this._runtimes[0];
|
69
|
+
|
70
|
+
return runtimeItem.runtime;
|
71
|
+
}
|
72
|
+
|
73
|
+
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
74
|
+
try {
|
75
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
76
|
+
|
77
|
+
return await runtime.chat(payload, options);
|
78
|
+
} catch (e) {
|
79
|
+
if (this._options.chat?.handleError) {
|
80
|
+
const error = this._options.chat.handleError(e);
|
81
|
+
|
82
|
+
if (error) {
|
83
|
+
throw error;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
throw e;
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
async textToImage(payload: TextToImagePayload) {
|
92
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
93
|
+
|
94
|
+
return runtime.textToImage?.(payload);
|
95
|
+
}
|
96
|
+
|
97
|
+
async models() {
|
98
|
+
return this._runtimes[0].runtime.models?.();
|
99
|
+
}
|
100
|
+
|
101
|
+
async embeddings(payload: EmbeddingsPayload, options?: EmbeddingsOptions) {
|
102
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
103
|
+
|
104
|
+
return runtime.embeddings?.(payload, options);
|
105
|
+
}
|
106
|
+
|
107
|
+
async textToSpeech(payload: TextToSpeechPayload, options?: EmbeddingsOptions) {
|
108
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
109
|
+
|
110
|
+
return runtime.textToSpeech?.(payload, options);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
export default UniformRuntime;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export const handleAnthropicError = (error: any) => {
|
2
|
+
let errorResult: any = error;
|
3
|
+
|
4
|
+
if (error.error) {
|
5
|
+
errorResult = error.error;
|
6
|
+
|
7
|
+
if ('error' in errorResult) {
|
8
|
+
errorResult = errorResult.error;
|
9
|
+
}
|
10
|
+
} else {
|
11
|
+
errorResult = { headers: error.headers, stack: error.stack, status: error.status };
|
12
|
+
}
|
13
|
+
|
14
|
+
return { errorResult };
|
15
|
+
};
|
@@ -44,6 +44,15 @@ describe('LobeAnthropicAI', () => {
|
|
44
44
|
expect(instance).toBeInstanceOf(LobeAnthropicAI);
|
45
45
|
expect(instance.baseURL).toBe('https://api.anthropic.proxy');
|
46
46
|
});
|
47
|
+
|
48
|
+
it('should correctly initialize with different id', async () => {
|
49
|
+
const instance = new LobeAnthropicAI({
|
50
|
+
apiKey: 'test_api_key',
|
51
|
+
id: 'abc',
|
52
|
+
});
|
53
|
+
expect(instance).toBeInstanceOf(LobeAnthropicAI);
|
54
|
+
expect(instance['id']).toBe('abc');
|
55
|
+
});
|
47
56
|
});
|
48
57
|
|
49
58
|
describe('chat', () => {
|
@@ -347,7 +356,7 @@ describe('LobeAnthropicAI', () => {
|
|
347
356
|
// Assert
|
348
357
|
expect(e).toEqual({
|
349
358
|
endpoint: 'https://api.anthropic.com',
|
350
|
-
error: apiError,
|
359
|
+
error: apiError.error.error,
|
351
360
|
errorType: bizErrorType,
|
352
361
|
provider,
|
353
362
|
});
|
@@ -2,18 +2,23 @@
|
|
2
2
|
import '@anthropic-ai/sdk/shims/web';
|
3
3
|
import Anthropic from '@anthropic-ai/sdk';
|
4
4
|
import { ClientOptions } from 'openai';
|
5
|
+
import type { ChatModelCard } from '@/types/llm';
|
5
6
|
|
6
7
|
import { LobeRuntimeAI } from '../BaseAI';
|
7
8
|
import { AgentRuntimeErrorType } from '../error';
|
8
|
-
import {
|
9
|
+
import {
|
10
|
+
ChatCompetitionOptions,
|
11
|
+
type ChatCompletionErrorPayload,
|
12
|
+
ChatStreamPayload,
|
13
|
+
ModelProvider,
|
14
|
+
} from '../types';
|
9
15
|
import { AgentRuntimeError } from '../utils/createError';
|
10
16
|
import { debugStream } from '../utils/debugStream';
|
11
17
|
import { desensitizeUrl } from '../utils/desensitizeUrl';
|
12
18
|
import { buildAnthropicMessages, buildAnthropicTools } from '../utils/anthropicHelpers';
|
13
19
|
import { StreamingResponse } from '../utils/response';
|
14
20
|
import { AnthropicStream } from '../utils/streams';
|
15
|
-
|
16
|
-
import type { ChatModelCard } from '@/types/llm';
|
21
|
+
import { handleAnthropicError } from './handleAnthropicError';
|
17
22
|
|
18
23
|
export interface AnthropicModelCard {
|
19
24
|
display_name: string;
|
@@ -22,18 +27,24 @@ export interface AnthropicModelCard {
|
|
22
27
|
|
23
28
|
const DEFAULT_BASE_URL = 'https://api.anthropic.com';
|
24
29
|
|
30
|
+
interface AnthropicAIParams extends ClientOptions {
|
31
|
+
id?: string;
|
32
|
+
}
|
33
|
+
|
25
34
|
export class LobeAnthropicAI implements LobeRuntimeAI {
|
26
35
|
private client: Anthropic;
|
27
36
|
|
28
37
|
baseURL: string;
|
29
38
|
apiKey?: string;
|
39
|
+
private id: string;
|
30
40
|
|
31
|
-
constructor({ apiKey, baseURL = DEFAULT_BASE_URL, ...res }:
|
41
|
+
constructor({ apiKey, baseURL = DEFAULT_BASE_URL, id, ...res }: AnthropicAIParams = {}) {
|
32
42
|
if (!apiKey) throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
|
33
43
|
|
34
44
|
this.client = new Anthropic({ apiKey, baseURL, ...res });
|
35
45
|
this.baseURL = this.client.baseURL;
|
36
46
|
this.apiKey = apiKey;
|
47
|
+
this.id = id || ModelProvider.Anthropic;
|
37
48
|
}
|
38
49
|
|
39
50
|
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
@@ -57,42 +68,7 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
57
68
|
headers: options?.headers,
|
58
69
|
});
|
59
70
|
} catch (error) {
|
60
|
-
|
61
|
-
|
62
|
-
if (this.baseURL !== DEFAULT_BASE_URL) {
|
63
|
-
desensitizedEndpoint = desensitizeUrl(this.baseURL);
|
64
|
-
}
|
65
|
-
|
66
|
-
if ('status' in (error as any)) {
|
67
|
-
switch ((error as Response).status) {
|
68
|
-
case 401: {
|
69
|
-
throw AgentRuntimeError.chat({
|
70
|
-
endpoint: desensitizedEndpoint,
|
71
|
-
error: error as any,
|
72
|
-
errorType: AgentRuntimeErrorType.InvalidProviderAPIKey,
|
73
|
-
provider: ModelProvider.Anthropic,
|
74
|
-
});
|
75
|
-
}
|
76
|
-
|
77
|
-
case 403: {
|
78
|
-
throw AgentRuntimeError.chat({
|
79
|
-
endpoint: desensitizedEndpoint,
|
80
|
-
error: error as any,
|
81
|
-
errorType: AgentRuntimeErrorType.LocationNotSupportError,
|
82
|
-
provider: ModelProvider.Anthropic,
|
83
|
-
});
|
84
|
-
}
|
85
|
-
default: {
|
86
|
-
break;
|
87
|
-
}
|
88
|
-
}
|
89
|
-
}
|
90
|
-
throw AgentRuntimeError.chat({
|
91
|
-
endpoint: desensitizedEndpoint,
|
92
|
-
error: error as any,
|
93
|
-
errorType: AgentRuntimeErrorType.ProviderBizError,
|
94
|
-
provider: ModelProvider.Anthropic,
|
95
|
-
});
|
71
|
+
throw this.handleError(error);
|
96
72
|
}
|
97
73
|
}
|
98
74
|
|
@@ -191,6 +167,48 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
191
167
|
})
|
192
168
|
.filter(Boolean) as ChatModelCard[];
|
193
169
|
}
|
170
|
+
|
171
|
+
private handleError(error: any): ChatCompletionErrorPayload {
|
172
|
+
let desensitizedEndpoint = this.baseURL;
|
173
|
+
|
174
|
+
if (this.baseURL !== DEFAULT_BASE_URL) {
|
175
|
+
desensitizedEndpoint = desensitizeUrl(this.baseURL);
|
176
|
+
}
|
177
|
+
|
178
|
+
if ('status' in (error as any)) {
|
179
|
+
switch ((error as Response).status) {
|
180
|
+
case 401: {
|
181
|
+
throw AgentRuntimeError.chat({
|
182
|
+
endpoint: desensitizedEndpoint,
|
183
|
+
error: error as any,
|
184
|
+
errorType: AgentRuntimeErrorType.InvalidProviderAPIKey,
|
185
|
+
provider: this.id,
|
186
|
+
});
|
187
|
+
}
|
188
|
+
|
189
|
+
case 403: {
|
190
|
+
throw AgentRuntimeError.chat({
|
191
|
+
endpoint: desensitizedEndpoint,
|
192
|
+
error: error as any,
|
193
|
+
errorType: AgentRuntimeErrorType.LocationNotSupportError,
|
194
|
+
provider: this.id,
|
195
|
+
});
|
196
|
+
}
|
197
|
+
default: {
|
198
|
+
break;
|
199
|
+
}
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
const { errorResult } = handleAnthropicError(error);
|
204
|
+
|
205
|
+
throw AgentRuntimeError.chat({
|
206
|
+
endpoint: desensitizedEndpoint,
|
207
|
+
error: errorResult,
|
208
|
+
errorType: AgentRuntimeErrorType.ProviderBizError,
|
209
|
+
provider: this.id,
|
210
|
+
});
|
211
|
+
}
|
194
212
|
}
|
195
213
|
|
196
214
|
export default LobeAnthropicAI;
|
@@ -13,10 +13,16 @@ import { transformResponseToStream } from '../utils/openaiCompatibleFactory';
|
|
13
13
|
import { StreamingResponse } from '../utils/response';
|
14
14
|
import { OpenAIStream, createSSEDataExtractor } from '../utils/streams';
|
15
15
|
|
16
|
+
interface AzureAIParams {
|
17
|
+
apiKey?: string;
|
18
|
+
apiVersion?: string;
|
19
|
+
baseURL?: string;
|
20
|
+
}
|
21
|
+
|
16
22
|
export class LobeAzureAI implements LobeRuntimeAI {
|
17
23
|
client: ModelClient;
|
18
24
|
|
19
|
-
constructor(params?:
|
25
|
+
constructor(params?: AzureAIParams) {
|
20
26
|
if (!params?.apiKey || !params?.baseURL)
|
21
27
|
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
|
22
28
|
|
@@ -1,9 +1,9 @@
|
|
1
|
+
import type { ChatModelCard } from '@/types/llm';
|
2
|
+
|
1
3
|
import { AgentRuntimeErrorType } from '../error';
|
2
|
-
import { pruneReasoningPayload } from '../openai';
|
3
4
|
import { ModelProvider } from '../types';
|
4
5
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
5
|
-
|
6
|
-
import type { ChatModelCard } from '@/types/llm';
|
6
|
+
import { pruneReasoningPayload } from '../utils/openaiHelpers';
|
7
7
|
|
8
8
|
export interface GithubModelCard {
|
9
9
|
description: string;
|
@@ -39,27 +39,20 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
|
|
39
39
|
models: async ({ client }) => {
|
40
40
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
41
41
|
|
42
|
-
const functionCallKeywords = [
|
43
|
-
'function',
|
44
|
-
'tool',
|
45
|
-
];
|
42
|
+
const functionCallKeywords = ['function', 'tool'];
|
46
43
|
|
47
|
-
const visionKeywords = [
|
48
|
-
'vision',
|
49
|
-
];
|
44
|
+
const visionKeywords = ['vision'];
|
50
45
|
|
51
|
-
const reasoningKeywords = [
|
52
|
-
'deepseek-r1',
|
53
|
-
'o1',
|
54
|
-
'o3',
|
55
|
-
];
|
46
|
+
const reasoningKeywords = ['deepseek-r1', 'o1', 'o3'];
|
56
47
|
|
57
48
|
const modelsPage = (await client.models.list()) as any;
|
58
49
|
const modelList: GithubModelCard[] = modelsPage.body;
|
59
50
|
|
60
51
|
return modelList
|
61
52
|
.map((model) => {
|
62
|
-
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
53
|
+
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
54
|
+
(m) => model.name.toLowerCase() === m.id.toLowerCase(),
|
55
|
+
);
|
63
56
|
|
64
57
|
return {
|
65
58
|
contextWindowTokens: knownModel?.contextWindowTokens ?? undefined,
|
@@ -67,18 +60,20 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
|
|
67
60
|
displayName: model.friendly_name,
|
68
61
|
enabled: knownModel?.enabled || false,
|
69
62
|
functionCall:
|
70
|
-
functionCallKeywords.some(keyword =>
|
71
|
-
|
72
|
-
||
|
63
|
+
functionCallKeywords.some((keyword) =>
|
64
|
+
model.description.toLowerCase().includes(keyword),
|
65
|
+
) ||
|
66
|
+
knownModel?.abilities?.functionCall ||
|
67
|
+
false,
|
73
68
|
id: model.name,
|
74
69
|
reasoning:
|
75
|
-
reasoningKeywords.some(keyword => model.name.toLowerCase().includes(keyword))
|
76
|
-
|
77
|
-
|
70
|
+
reasoningKeywords.some((keyword) => model.name.toLowerCase().includes(keyword)) ||
|
71
|
+
knownModel?.abilities?.reasoning ||
|
72
|
+
false,
|
78
73
|
vision:
|
79
|
-
visionKeywords.some(keyword => model.description.toLowerCase().includes(keyword))
|
80
|
-
|
81
|
-
|
74
|
+
visionKeywords.some((keyword) => model.description.toLowerCase().includes(keyword)) ||
|
75
|
+
knownModel?.abilities?.vision ||
|
76
|
+
false,
|
82
77
|
};
|
83
78
|
})
|
84
79
|
.filter(Boolean) as ChatModelCard[];
|
@@ -19,6 +19,8 @@ export { LobeQwenAI } from './qwen';
|
|
19
19
|
export { LobeTogetherAI } from './togetherai';
|
20
20
|
export * from './types';
|
21
21
|
export { AgentRuntimeError } from './utils/createError';
|
22
|
+
export { LobeOpenAICompatibleFactory } from './utils/openaiCompatibleFactory';
|
23
|
+
export { pruneReasoningPayload } from './utils/openaiHelpers';
|
22
24
|
export { LobeVolcengineAI } from './volcengine';
|
23
25
|
export { LobeZeroOneAI } from './zeroone';
|
24
26
|
export { LobeZhipuAI } from './zhipu';
|
@@ -1,33 +1,13 @@
|
|
1
|
-
import { disableStreamModels, systemToUserModels } from '@/const/models';
|
2
1
|
import type { ChatModelCard } from '@/types/llm';
|
3
2
|
|
4
|
-
import {
|
3
|
+
import { ModelProvider } from '../types';
|
5
4
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
5
|
+
import { pruneReasoningPayload } from '../utils/openaiHelpers';
|
6
6
|
|
7
7
|
export interface OpenAIModelCard {
|
8
8
|
id: string;
|
9
9
|
}
|
10
10
|
|
11
|
-
export const pruneReasoningPayload = (payload: ChatStreamPayload) => {
|
12
|
-
return {
|
13
|
-
...payload,
|
14
|
-
frequency_penalty: 0,
|
15
|
-
messages: payload.messages.map((message: OpenAIChatMessage) => ({
|
16
|
-
...message,
|
17
|
-
role:
|
18
|
-
message.role === 'system'
|
19
|
-
? systemToUserModels.has(payload.model)
|
20
|
-
? 'user'
|
21
|
-
: 'developer'
|
22
|
-
: message.role,
|
23
|
-
})),
|
24
|
-
presence_penalty: 0,
|
25
|
-
stream: !disableStreamModels.has(payload.model),
|
26
|
-
temperature: 1,
|
27
|
-
top_p: 1,
|
28
|
-
};
|
29
|
-
};
|
30
|
-
|
31
11
|
export const LobeOpenAI = LobeOpenAICompatibleFactory({
|
32
12
|
baseURL: 'https://api.openai.com/v1',
|
33
13
|
chatCompletion: {
|