fix: stt usage (#6855)

* fix: stt usage

* perf: skil ssrf with some api

* fix:test
This commit is contained in:
Archer
2026-04-29 16:34:19 +08:00
committed by GitHub
parent 073bd59141
commit 8d67db1eb2
11 changed files with 33 additions and 103 deletions
+1
View File
@@ -43,6 +43,7 @@ export function createProxyAxios(config?: AxiosRequestConfig, ssrfCheck = true)
/** @see https://github.com/axios/axios/issues/4531 */
export const axios = createProxyAxios();
export const axiosWithoutSSRF = createProxyAxios(undefined, false);
/**
* 内部相对路径请求专用的 axios 实例:
@@ -1,6 +1,6 @@
import type fs from 'fs';
import { getAxiosConfig } from '../config';
import { axios } from '../../../common/api/axios';
import { axiosWithoutSSRF } from '../../../common/api/axios';
import FormData from 'form-data';
import { type STTModelType } from '@fastgpt/global/core/ai/model.schema';
import { UserError } from '@fastgpt/global/common/error/utils';
@@ -24,22 +24,19 @@ export const aiTranscriptions = async ({
const aiAxiosConfig = getAxiosConfig();
const { data: result } = await axios<{ text: string; usage?: { total_tokens: number } }>({
method: 'post',
...(modelData.requestUrl
? { url: modelData.requestUrl }
: {
baseURL: aiAxiosConfig.baseUrl,
url: '/audio/transcriptions'
}),
// 管理员配置的 url,允许是内网
const { data: result } = await axiosWithoutSSRF.post<{
text: string;
usage?: { total_tokens: number };
}>(modelData.requestUrl ? modelData.requestUrl : '/audio/transcriptions', data, {
...(modelData.requestUrl ? {} : { baseURL: aiAxiosConfig.baseUrl }),
headers: {
Authorization: modelData.requestAuth
? `Bearer ${modelData.requestAuth}`
: aiAxiosConfig.authorization,
...data.getHeaders(),
...headers
},
data: data
}
});
return result;
+3 -2
View File
@@ -1,4 +1,4 @@
import { axios } from '../../../common/api/axios';
import { axiosWithoutSSRF } from '../../../common/api/axios';
import { getDefaultRerankModel } from '../model';
import { getAxiosConfig } from '../config';
import { type RerankModelItemType } from '@fastgpt/global/core/ai/model.schema';
@@ -93,8 +93,9 @@ export async function reRankRecall({
const { baseUrl, authorization } = getAxiosConfig();
const start = Date.now();
// 模型的请求 url,允许是内网
const requestUrl = model.requestUrl ? model.requestUrl : `${baseUrl}/rerank`;
const apiResult = await axios
const apiResult = await axiosWithoutSSRF
.post<PostReRankResponse>(
requestUrl,
{
@@ -24,7 +24,6 @@ import { JSONPath } from 'jsonpath-plus';
import { getSecretValue } from '../../../../common/secret/utils';
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
import { getLogger, LogCategories } from '../../../../common/logger';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { formatHttpError } from '../utils';
import { isInternalAddress, PRIVATE_URL_TEXT } from '../../../../common/system/utils';
import { serviceRequestMaxContentLength } from '../../../../common/system/constants';
@@ -504,10 +503,10 @@ async function fetchData({
return Promise.reject(PRIVATE_URL_TEXT);
}
// 都认为是用户的请求,强制 SSRF 检查
const { data: response } = await axios({
method,
maxContentLength: serviceRequestMaxContentLength,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
...headers
@@ -3,7 +3,6 @@ import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workfl
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { axios } from '../../../../common/api/axios';
import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { getLogger, LogCategories } from '../../../../common/logger';
@@ -122,7 +121,6 @@ async function fetchData({
}): Promise<Record<string, any>> {
const { data: response } = await axios({
method,
baseURL: `http://${SERVICE_LOCAL_HOST}`,
url,
headers: {
'Content-Type': 'application/json'
@@ -13,9 +13,9 @@ vi.mock('@fastgpt/service/common/string/tiktoken', () => ({
countPromptTokens: mockCountPromptTokens
}));
// rerank 现在改用统一 axios(带 SSRF 拦截),mock axios.post 返回 axios 风格的 { data, ... }
// rerank 走 axiosWithoutSSRF(管理员配置的 url 允许内网),mock 它的 .post 返回 axios 风格的 { data, ... }
vi.mock('@fastgpt/service/common/api/axios', () => ({
axios: {
axiosWithoutSSRF: {
post: (...args: any[]) => Promise.resolve(mockAxiosPost(...args)).then((data) => ({ data }))
}
}));
@@ -1,6 +1,6 @@
import { SandboxCodeTypeEnum } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
import type { AxiosInstance } from 'axios';
import axios from 'axios';
import { createProxyAxios } from '../../common/api/axios';
import { getLogger, LogCategories } from '../../common/logger';
const logger = getLogger(LogCategories.MODULE.WORKFLOW.CODE_SANDBOX);
@@ -17,14 +17,17 @@ export class CodeSandbox {
const baseUrl = process.env.CODE_SANDBOX_URL || '';
const token = process.env.CODE_SANDBOX_TOKEN || '';
this.client = axios.create({
baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`,
timeout: 180000,
headers: {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : undefined
}
});
this.client = createProxyAxios(
{
baseURL: `${baseUrl.replace(/\/$/, '')}/sandbox`,
timeout: 180000,
headers: {
'Content-Type': 'application/json',
Authorization: token ? `Bearer ${token}` : undefined
}
},
false
);
this.client.interceptors.response.use(
(response) => {
@@ -1,12 +1,12 @@
import { matchMdImg } from '@fastgpt/global/common/string/markdown';
import axios from 'axios';
import { createProxyAxios } from '../../common/api/axios';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { getLogger, LogCategories } from '../../common/logger';
export const useTextinServer = ({ appId, secretCode }: { appId: string; secretCode: string }) => {
const logger = getLogger(LogCategories.MODULE.DATASET.FILE);
// Init request
const instance = axios.create({
const instance = createProxyAxios({
baseURL: 'https://api.textin.com/ai/service/v1',
timeout: 300000,
headers: {
@@ -1,6 +1,6 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
import { axios } from '@fastgpt/service/common/api/axios';
import { axiosWithoutSSRF } from '@fastgpt/service/common/api/axios';
import { getErrText } from '@fastgpt/global/common/error/utils';
const baseUrl = process.env.AIPROXY_API_ENDPOINT;
@@ -14,7 +14,7 @@ async function handler(req: ApiRequestProps, res: ApiResponseType<any>) {
return Promise.reject('AIPROXY_API_ENDPOINT or AIPROXY_API_TOKEN is not set');
}
const { data } = await axios.post(`${baseUrl}/api/channel/`, req.body, {
const { data } = await axiosWithoutSSRF.post(`${baseUrl}/api/channel/`, req.body, {
headers: {
Authorization: `Bearer ${token}`
}
@@ -1,68 +0,0 @@
import { getLogger, LogCategories } from '@fastgpt/service/common/logger';
import { type Method } from 'axios';
import { createProxyAxios } from '@fastgpt/service/common/api/axios';
const logger = getLogger(LogCategories.NETWORK);
const url = process.env.API_PROXY_URL;
const token = process.env.API_PROXY_TOKEN;
const instance = createProxyAxios({
baseURL: url,
timeout: 60000, // 超时时间
headers: {
Authorization: `Bearer ${token}`
}
});
/**
* 响应数据检查
*/
const checkRes = (data: any) => {
if (data === undefined) {
logger.info('api proxy data is empty');
return Promise.reject('服务器异常');
}
return data.data;
};
const responseError = (err: any) => {
logger.error('API proxy request failed', { error: err });
if (!err) {
return Promise.reject({ message: '未知错误' });
}
if (typeof err === 'string') {
return Promise.reject({ message: err });
}
if (typeof err.message === 'string') {
return Promise.reject({ message: err.message });
}
if (typeof err.data === 'string') {
return Promise.reject({ message: err.data });
}
if (err?.response?.data) {
return Promise.reject(err?.response?.data);
}
return Promise.reject(err);
};
const request = <T>(url: string, data: any, method: Method): Promise<T> => {
/* 去空 */
for (const key in data) {
if (data[key] === undefined) {
delete data[key];
}
}
return instance
.request({
url,
method,
data: ['POST', 'PUT'].includes(method) ? data : undefined,
params: !['POST', 'PUT'].includes(method) ? data : undefined
})
.then((res) => checkRes(res.data))
.catch((err) => responseError(err));
};
// TODO: channel crud
export const ApiProxy = {};
@@ -2,9 +2,8 @@ import { UsageItemTypeEnum, UsageSourceEnum } from '@fastgpt/global/support/wall
import { createUsage, concatUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { formatModelChars2Points } from '@fastgpt/service/support/wallet/usage/utils';
import { i18nT } from '@fastgpt/web/i18n/utils';
import { getDefaultTTSModel } from '@fastgpt/service/core/ai/model';
import { getDefaultSTTModel } from '@fastgpt/service/core/ai/model';
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
import type { HelperBotTypeEnumType } from '@fastgpt/global/core/chat/helperBot/type';
export const pushHelperBotUsage = ({
teamId,
@@ -246,7 +245,7 @@ export const pushWhisperUsage = ({
tmbId: string;
duration: number;
}) => {
const whisperModel = getDefaultTTSModel();
const whisperModel = getDefaultSTTModel();
if (!whisperModel) return;