Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .agents/skills/system/api-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,28 @@ description: FastGPT API 开发规范。重点强调使用 zod schema 定义入

1. **所有 API 必须使用 zod schema 定义入参和出参**
2. **必须导出 schema 的 TypeScript 类型**
3. **必须在 schema 文件头部声明 API 信息(路由、方法、描述、标签)**
3. **必须在 schema 文件头部声明 API 信息(路由、方法、描述、标签),一次性管理员升级/清洗能力除外**
4. **入参必须使用 schema.parse() 验证**
5. **函数返回值必须使用 schema.parse() 验证**
6. **必须编写完整的 OpenAPI 文档**
6. **必须编写完整的 OpenAPI 文档,一次性管理员升级/清洗能力除外**

### 管理员升级与清洗能力的文档豁免

仅供系统管理员执行、用于一次性升级、迁移、修复或数据清洗的内部接口和脚本,不属于产品 API,不要求:

- 在 `packages/global/openapi/` 声明接口文档;
- 注册 OpenAPI Path;
- 编写 API 头部路由、方法、描述和标签信息。

豁免只针对文档,不豁免安全和校验要求:

- 管理员接口必须使用 `authSystemAdmin` 鉴权;
- API 入参必须使用 Zod Schema 和 `parseApiInput`;
- 返回值必须使用 Zod Schema 校验,空成功响应使用 `z.undefined()`;
- 数据清洗默认使用 dry-run,显式确认后才能写入,并输出成功、跳过和失败统计;
- 清洗逻辑应可重复执行,无法安全修复的数据必须跳过并报告,不得静默填入猜测值。

常规管理员产品接口(例如模型配置 CRUD、用户管理和系统配置)不因仅管理员可用而获得豁免,仍需按标准 API 流程维护文档。

## 开发流程

Expand Down Expand Up @@ -464,6 +482,7 @@ export default NextAPI(handler);
### 🔴 必须检查项 (阻塞性)

**Schema 文件** (`packages/global/openapi/.../api.ts`):
- [ ] **文档豁免判断**: 仅一次性管理员升级/清洗能力可跳过 OpenAPI 文档
- [ ] **API 声明**: 文件头部有 API 信息(路由、方法、描述、标签)
- [ ] **Schema 定义**: 入参和出参都使用 zod 定义
- [ ] **类型导出**: 导出 `z.infer<typeof Schema>` 类型
Expand Down
26 changes: 26 additions & 0 deletions document/content/self-host/upgrading/4-16/41602.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,32 @@ title: 'V4.16.0-beta2 (In Progress)'
description: 'FastGPT V4.16.0-beta2 release notes'
---

## ⚠️ Upgrade notes

### Clean up legacy system model configurations

This release applies strict schemas when system models are initialized or saved. Numeric strings, serialized price tiers, and missing fields saved by earlier releases may fail initialization validation. After upgrading, run a dry run first to inspect the model configurations that require cleanup. A dry run does not modify data or reload the model cache:

```bash
curl -X POST 'https://your-domain/api/admin/dataClean/cleanSystemModelConfigs' \
-H 'Content-Type: application/json' \
-H 'rootkey: YOUR_ROOT_KEY' \
-d '{"dryRun":true}'
```

After confirming that `invalidSamples` contains no records that require manual correction, run the cleanup:

```bash
curl -X POST 'https://your-domain/api/admin/dataClean/cleanSystemModelConfigs' \
-H 'Content-Type: application/json' \
-H 'rootkey: YOUR_ROOT_KEY' \
-d '{"dryRun":false}'
```

The cleanup converts valid numeric strings to numbers, parses serialized `priceTiers` arrays, and removes invalid optional numeric fields. Invalid or missing required numeric fields use system defaults: LLM `maxContext/maxResponse/quoteMaxToken` default to `16000/16000/13000`, Embedding `defaultToken/maxToken` default to `500/3000`, and price fields default to `0`. `functionCall` remains optional, and a missing Embedding `weight` defaults to `0`.

The write operation updates all matching records in one operation and immediately reloads the system model cache. The runtime cache is rebuilt even when no database record needs an update. The endpoint is safe to run repeatedly; a follow-up dry run should report `wouldUpdate` as `0`. Records that still fail the complete current model schema are not written, and all of them are listed in `invalidSamples`.

## 🚀 New

1. Moved Workflow app system settings to a dedicated panel in the canvas toolbar. The panel opens automatically when a new Workflow app is created.
Expand Down
31 changes: 31 additions & 0 deletions document/content/self-host/upgrading/4-16/41602.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,32 @@ title: 'V4.16.0-beta2(进行中)'
description: 'FastGPT V4.16.0-beta2 更新说明'
---

## 📦 升级指南

### 1. 清洗历史系统模型配置

本版本开始在系统模型初始化及保存时使用严格 Schema。此前版本保存的数字字符串、字符串形式的价格梯度或缺失字段可能导致初始化校验失败。升级后请先执行 dry-run,查看需要处理的模型配置;dry-run 不会修改数据或刷新缓存:

```bash
curl -X POST 'https://你的域名/api/admin/dataClean/cleanSystemModelConfigs' \
-H 'Content-Type: application/json' \
-H 'rootkey: 你的ROOT_KEY' \
-d '{"dryRun":true}'
```

确认 `invalidSamples` 中没有需要人工处理的数据后,执行正式清洗:

```bash
curl -X POST 'https://你的域名/api/admin/dataClean/cleanSystemModelConfigs' \
-H 'Content-Type: application/json' \
-H 'rootkey: 你的ROOT_KEY' \
-d '{"dryRun":false}'
```

清洗会将合法数字字符串转换为 number、将字符串形式的 `priceTiers` 转换为数组,并删除非法的可选数字。非法或缺失的必填数字使用系统默认值:LLM 的 `maxContext/maxResponse/quoteMaxToken` 分别为 `16000/16000/13000`,Embedding 的 `defaultToken/maxToken` 分别为 `500/3000`,价格为 `0`。`functionCall` 保持可选,Embedding 缺失的 `weight` 补为 `0`。

正式执行会统一写入数据并立即刷新系统模型缓存;即使没有记录需要更新,也会重新构建运行时缓存。接口可安全重复执行;再次 dry-run 时,`wouldUpdate` 应为 `0`。无法通过当前完整模型 Schema 的记录不会写入,详情会全部返回在 `invalidSamples` 中。

## 🚀 新增内容

1. 工作流应用的系统配置移至画布左侧工具栏中的独立配置面板,新建工作流应用时会自动打开。
Expand All @@ -23,3 +49,8 @@ description: 'FastGPT V4.16.0-beta2 更新说明'
4. 修复应用发布后,文件变量无法上传文件的问题。
5. 修复共享工作流工具的文件参数无法上传文件的问题。
6. 修复 S3 对象键和文件名包含空格、`%`、`#`、`?`、斜杠等特殊字符时,可能导致上传、解析、预览或下载异常的问题。

## 🛠️ 代码优化

1. 审计日志归档,不再删除,改成转存到 S3 冷归档。
2. 增加对 admin 配置的数据校验和清洗。
8 changes: 4 additions & 4 deletions document/data/doc-last-modified.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@
"content/openapi/app.mdx": "2026-05-29T19:31:16+08:00",
"content/openapi/chat.en.mdx": "2026-07-08T22:37:19+08:00",
"content/openapi/chat.mdx": "2026-07-08T22:37:19+08:00",
"content/openapi/dataset.en.mdx": "2026-08-11T16:50:38+08:00",
"content/openapi/dataset.mdx": "2026-08-11T16:50:38+08:00",
"content/openapi/dataset.en.mdx": "2026-08-11T20:41:16+08:00",
"content/openapi/dataset.mdx": "2026-08-11T20:41:16+08:00",
"content/openapi/index.en.mdx": "2026-04-26T21:08:47+08:00",
"content/openapi/index.mdx": "2026-04-26T21:08:47+08:00",
"content/openapi/intro.en.mdx": "2026-07-02T11:56:02+08:00",
Expand Down Expand Up @@ -337,8 +337,8 @@
"content/self-host/upgrading/4-15/4157.mdx": "2026-08-07T16:45:18+08:00",
"content/self-host/upgrading/4-16/41601.en.mdx": "2026-08-07T16:45:18+08:00",
"content/self-host/upgrading/4-16/41601.mdx": "2026-08-07T16:45:18+08:00",
"content/self-host/upgrading/4-16/41602.en.mdx": "2026-08-11T16:50:38+08:00",
"content/self-host/upgrading/4-16/41602.mdx": "2026-08-11T16:50:38+08:00",
"content/self-host/upgrading/4-16/41602.en.mdx": "2026-08-12T11:35:38+08:00",
"content/self-host/upgrading/4-16/41602.mdx": "2026-08-12T11:35:38+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-07-25T00:27:20+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-07-25T00:27:20+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-07-25T00:27:20+08:00",
Expand Down
43 changes: 38 additions & 5 deletions packages/global/core/ai/model.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ const PriceTypeSchema = z.object({
export type PriceType = z.infer<typeof PriceTypeSchema>;

const BaseModelItemSchema = z.object({
provider: z.string(),
model: z.string(),
name: z.string(),
provider: z.string().trim().min(1),
model: z.string().trim().min(1),
name: z.string().trim().min(1),
avatar: z.string().optional(), // model icon, from provider

isActive: z.boolean().optional(),
Expand Down Expand Up @@ -70,8 +70,8 @@ export const LLMModelItemSchema = PriceTypeSchema.extend(BaseModelItemSchema.sha
reasoning: z.boolean().optional(),
reasoningEffort: z.boolean().optional(),

functionCall: z.boolean(),
toolChoice: z.boolean(),
functionCall: z.boolean().optional(),
toolChoice: z.boolean().optional(),

defaultSystemChatPrompt: z.string().optional(),
defaultConfig: z.record(z.string(), z.any()).optional(),
Expand Down Expand Up @@ -127,3 +127,36 @@ export const STTModelItemSchema = PriceTypeSchema.extend(BaseModelItemSchema.sha
type: z.literal(ModelTypeEnum.stt)
});
export type STTModelType = z.infer<typeof STTModelItemSchema>;

export const SystemModelItemSchema = z.discriminatedUnion('type', [
LLMModelItemSchema,
EmbeddingModelItemSchema,
TTSModelItemSchema,
STTModelItemSchema,
RerankModelItemSchema
]);
export type SystemModelItemType = z.infer<typeof SystemModelItemSchema>;

export const PersistedSystemModelItemSchema = SystemModelItemSchema.transform((metadata) => {
const persistedMetadata = { ...metadata } as Record<string, unknown>;

delete persistedMetadata.avatar;
delete persistedMetadata.isCustom;
delete persistedMetadata.datasetProcess;
delete persistedMetadata.usedInClassify;
delete persistedMetadata.usedInExtractFields;
delete persistedMetadata.usedInToolCall;
delete persistedMetadata.useInEvaluation;

for (const [key, value] of Object.entries(persistedMetadata)) {
if (value === undefined) delete persistedMetadata[key];
}

if (Array.isArray(persistedMetadata.priceTiers)) {
delete persistedMetadata.charsPointsPrice;
delete persistedMetadata.inputPrice;
delete persistedMetadata.outputPrice;
}

return persistedMetadata as SystemModelItemType;
});
2 changes: 1 addition & 1 deletion packages/global/core/dataset/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export const DatasetTrainingSchema = z.object({
expireAt: z.coerce.date().meta({ description: '过期时间' }),
lockTime: z.coerce.date().meta({ description: '锁定时间' }),
mode: z.enum(TrainingModeEnum).meta({ description: '训练模式' }),
dataId: z.string().optional().meta({ description: '数据 ID' }),
dataId: ObjectIdSchema.optional().meta({ description: '数据 ID' }),
q: z.string().meta({ description: '问题/主文本' }),
a: z.string().meta({ description: '回答/补充文本' }),
imageId: z.string().optional().meta({ description: '图片 ID' }),
Expand Down
85 changes: 85 additions & 0 deletions packages/global/openapi/admin/core/ai/model/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { SystemModelItemSchema } from '../../../../../core/ai/model.schema';
import z from 'zod';

/* ============================================================================
* API: 更新系统模型配置
* Route: PUT /api/core/ai/model/update
* Method: PUT
* Description: 合并并严格校验指定系统模型的配置
* Tags: ['管理员系统配置', 'Write']
* ============================================================================ */

export const UpdateSystemModelBodySchema = z.object({
model: z.string().trim().min(1).meta({
example: 'gpt-5',
description: '待更新的模型标识'
}),
metadata: z.record(z.string(), z.unknown()).optional().meta({
description: '本次更新的模型配置字段;与已有配置合并后执行完整模型校验'
})
});
export type UpdateSystemModelBody = z.infer<typeof UpdateSystemModelBodySchema>;

export const UpdateSystemModelResponseSchema = z.undefined().meta({
description: '模型配置更新成功'
});
export type UpdateSystemModelResponse = z.infer<typeof UpdateSystemModelResponseSchema>;

const ImportedSystemModelRecordSchema = z
.object({
model: z.string().trim().min(1),
metadata: z.record(z.string(), z.unknown())
})
.transform(({ model, metadata }) => ({
model,
metadata: {
...metadata,
model,
name: typeof metadata.name === 'string' && metadata.name.trim() ? metadata.name.trim() : model
}
}))
.pipe(
z.object({
model: z.string(),
metadata: SystemModelItemSchema
})
);

const ImportedSystemModelListSchema = z.array(ImportedSystemModelRecordSchema);

const JsonSystemModelListSchema = z.string().transform((value, ctx) => {
try {
return JSON.parse(value) as unknown;
} catch {
ctx.addIssue({
code: 'custom',
message: 'config must be valid JSON'
});
return z.NEVER;
}
});

/* ============================================================================
* API: 导入系统模型配置
* Route: PUT /api/core/ai/model/updateWithJson
* Method: PUT
* Description: 使用 JSON 配置严格校验并覆盖系统模型记录
* Tags: ['管理员系统配置', 'Write']
* ============================================================================ */

export const UpdateSystemModelsWithJsonBodySchema = z.object({
config: JsonSystemModelListSchema.pipe(ImportedSystemModelListSchema).meta({
example:
'[{"model":"gpt-5","metadata":{"type":"llm","provider":"OpenAI","model":"gpt-5","name":"GPT-5","maxContext":400000,"maxResponse":128000,"quoteMaxToken":300000,"toolChoice":true,"isActive":true}}]',
description: '系统模型配置 JSON;解析后每条 metadata 必须符合完整模型 Schema'
})
});
export type UpdateSystemModelsWithJsonBody = z.input<typeof UpdateSystemModelsWithJsonBodySchema>;
export type ParsedSystemModelsWithJsonBody = z.output<typeof UpdateSystemModelsWithJsonBodySchema>;

export const UpdateSystemModelsWithJsonResponseSchema = z.undefined().meta({
description: '模型配置导入成功'
});
export type UpdateSystemModelsWithJsonResponse = z.infer<
typeof UpdateSystemModelsWithJsonResponseSchema
>;
59 changes: 59 additions & 0 deletions packages/global/openapi/admin/core/ai/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { OpenAPIPath } from '../../../../type';
import { DevApiTagsMap } from '../../../../tag';
import {
UpdateSystemModelBodySchema,
UpdateSystemModelResponseSchema,
UpdateSystemModelsWithJsonBodySchema,
UpdateSystemModelsWithJsonResponseSchema
} from './api';

export const AdminSystemModelPath: OpenAPIPath = {
'/core/ai/model/update': {
put: {
summary: '更新系统模型配置',
description: '合并并严格校验指定系统模型的配置',
tags: [DevApiTagsMap.adminSettings],
requestBody: {
content: {
'application/json': {
schema: UpdateSystemModelBodySchema
}
}
},
responses: {
200: {
description: '更新成功',
content: {
'application/json': {
schema: UpdateSystemModelResponseSchema
}
}
}
}
}
},
'/core/ai/model/updateWithJson': {
put: {
summary: '导入系统模型配置',
description: '严格校验 JSON 内容后覆盖系统模型配置',
tags: [DevApiTagsMap.adminSettings],
requestBody: {
content: {
'application/json': {
schema: UpdateSystemModelsWithJsonBodySchema
}
}
},
responses: {
200: {
description: '导入成功',
content: {
'application/json': {
schema: UpdateSystemModelsWithJsonResponseSchema
}
}
}
}
}
}
};
4 changes: 3 additions & 1 deletion packages/global/openapi/admin/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { AdminAppPath } from './app';
import { AdminRoutesPath } from '../routes';
import { AdminCommonPath } from '../common';
import type { OpenAPIPath } from '../../type';
import { AdminSystemModelPath } from './ai/model';

export const AdminCorePath: OpenAPIPath = {
...DashboardPath,
...AdminAppPath,
...AdminRoutesPath,
...AdminCommonPath
...AdminCommonPath,
...AdminSystemModelPath
};
Loading
Loading