|
@@ -21,7 +21,52 @@ import { ModelConfigService } from '../model-config/model-config.service';
|
|
|
import { TenantService } from '../tenant/tenant.service';
|
|
import { TenantService } from '../tenant/tenant.service';
|
|
|
import { UserSettingService } from '../user/user-setting.service';
|
|
import { UserSettingService } from '../user/user-setting.service';
|
|
|
import { I18nService } from '../i18n/i18n.service';
|
|
import { I18nService } from '../i18n/i18n.service';
|
|
|
-import { ApiTags, ApiOperation, ApiResponse, ApiSecurity, ApiConsumes, ApiBody } from '@nestjs/swagger';
|
|
|
|
|
|
|
+import { ApiTags, ApiOperation, ApiResponse, ApiSecurity, ApiConsumes, ApiBody, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
|
|
|
+
|
|
|
|
|
+/** V1 API 聊天请求DTO */
|
|
|
|
|
+class V1ChatDto {
|
|
|
|
|
+ @ApiProperty({ description: '用户消息', example: '你好,请介绍一下你们的产品' })
|
|
|
|
|
+ message: string;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '是否使用流式响应', example: false, default: false })
|
|
|
|
|
+ stream?: boolean;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '选中的知识分组ID列表', example: [] })
|
|
|
|
|
+ selectedGroups?: string[];
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '选中的文件ID列表', example: [] })
|
|
|
|
|
+ selectedFiles?: string[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** V1 API 搜索请求DTO */
|
|
|
|
|
+class V1SearchDto {
|
|
|
|
|
+ @ApiProperty({ description: '搜索关键词', example: '产品介绍' })
|
|
|
|
|
+ query: string;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '返回结果数量', example: 5, default: 5 })
|
|
|
|
|
+ topK?: number;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '相似度阈值', example: 0.3, default: 0.3 })
|
|
|
|
|
+ threshold?: number;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '选中的知识分组ID列表' })
|
|
|
|
|
+ selectedGroups?: string[];
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '选中的文件ID列表' })
|
|
|
|
|
+ selectedFiles?: string[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** V1 API 文件上传DTO */
|
|
|
|
|
+class V1UploadDto {
|
|
|
|
|
+ @ApiProperty({ description: '处理模式', enum: ['fast', 'precise'], example: 'fast' })
|
|
|
|
|
+ mode?: 'fast' | 'precise';
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '分块大小', example: 1000 })
|
|
|
|
|
+ chunkSize?: number;
|
|
|
|
|
+
|
|
|
|
|
+ @ApiPropertyOptional({ description: '分块重叠大小', example: 200 })
|
|
|
|
|
+ chunkOverlap?: number;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
@ApiTags('API v1')
|
|
@ApiTags('API v1')
|
|
|
@ApiSecurity('x-api-key')
|
|
@ApiSecurity('x-api-key')
|
|
@@ -45,19 +90,14 @@ export class ApiV1Controller {
|
|
|
* Body: { message, stream?, selectedGroups?, selectedFiles? }
|
|
* Body: { message, stream?, selectedGroups?, selectedFiles? }
|
|
|
*/
|
|
*/
|
|
|
@Post('chat')
|
|
@Post('chat')
|
|
|
- @ApiOperation({ summary: 'RAG 聊天', description: '与知识库进行对话,支持流式和非流式响应' })
|
|
|
|
|
- @ApiResponse({ status: 200, description: '成功' })
|
|
|
|
|
|
|
+ @ApiOperation({ summary: 'RAG 聊天', description: '与知识库进行对话,支持流式(SSE)和非流式响应。使用API Key认证。' })
|
|
|
|
|
+ @ApiBody({ type: V1ChatDto })
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 流式返回或JSON响应' })
|
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
|
async chat(
|
|
async chat(
|
|
|
@Request() req,
|
|
@Request() req,
|
|
|
- @Body()
|
|
|
|
|
- body: {
|
|
|
|
|
- message: string;
|
|
|
|
|
- stream?: boolean;
|
|
|
|
|
- selectedGroups?: string[];
|
|
|
|
|
- selectedFiles?: string[];
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ @Body() body: V1ChatDto,
|
|
|
@Res() res: Response,
|
|
@Res() res: Response,
|
|
|
) {
|
|
) {
|
|
|
const { message, stream = false, selectedGroups, selectedFiles } = body;
|
|
const { message, stream = false, selectedGroups, selectedFiles } = body;
|
|
@@ -173,20 +213,14 @@ export class ApiV1Controller {
|
|
|
* Body: { query, topK?, threshold?, selectedGroups?, selectedFiles? }
|
|
* Body: { query, topK?, threshold?, selectedGroups?, selectedFiles? }
|
|
|
*/
|
|
*/
|
|
|
@Post('search')
|
|
@Post('search')
|
|
|
- @ApiOperation({ summary: '知识库搜索', description: '在知识库中搜索相关内容' })
|
|
|
|
|
- @ApiResponse({ status: 200, description: '成功' })
|
|
|
|
|
|
|
+ @ApiOperation({ summary: '知识库搜索', description: '在知识库中搜索相关内容,返回相似文档片段。使用API Key认证。' })
|
|
|
|
|
+ @ApiBody({ type: V1SearchDto })
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 返回搜索结果' })
|
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
|
async search(
|
|
async search(
|
|
|
@Request() req,
|
|
@Request() req,
|
|
|
- @Body()
|
|
|
|
|
- body: {
|
|
|
|
|
- query: string;
|
|
|
|
|
- topK?: number;
|
|
|
|
|
- threshold?: number;
|
|
|
|
|
- selectedGroups?: string[];
|
|
|
|
|
- selectedFiles?: string[];
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ @Body() body: V1SearchDto,
|
|
|
) {
|
|
) {
|
|
|
const { query, topK = 5, threshold = 0.3, selectedGroups, selectedFiles } = body;
|
|
const { query, topK = 5, threshold = 0.3, selectedGroups, selectedFiles } = body;
|
|
|
const user = req.user;
|
|
const user = req.user;
|
|
@@ -221,8 +255,8 @@ export class ApiV1Controller {
|
|
|
* List all files belonging to the caller's tenant.
|
|
* List all files belonging to the caller's tenant.
|
|
|
*/
|
|
*/
|
|
|
@Get('knowledge-bases')
|
|
@Get('knowledge-bases')
|
|
|
- @ApiOperation({ summary: '列出知识库文件', description: '获取当前租户的所有知识库文件' })
|
|
|
|
|
- @ApiResponse({ status: 200, description: '成功' })
|
|
|
|
|
|
|
+ @ApiOperation({ summary: '列出知识库文件', description: '获取当前租户的所有知识库文件列表。使用API Key认证。' })
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 返回文件列表' })
|
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
|
async listFiles(@Request() req) {
|
|
async listFiles(@Request() req) {
|
|
|
const user = req.user;
|
|
const user = req.user;
|
|
@@ -247,20 +281,20 @@ export class ApiV1Controller {
|
|
|
*/
|
|
*/
|
|
|
@Post('knowledge-bases/upload')
|
|
@Post('knowledge-bases/upload')
|
|
|
@UseInterceptors(FileInterceptor('file'))
|
|
@UseInterceptors(FileInterceptor('file'))
|
|
|
- @ApiOperation({ summary: '上传文件', description: '上传文件到知识库' })
|
|
|
|
|
|
|
+ @ApiOperation({ summary: '上传文件', description: '上传文件到知识库进行索引处理。使用API Key认证。' })
|
|
|
@ApiConsumes('multipart/form-data')
|
|
@ApiConsumes('multipart/form-data')
|
|
|
@ApiBody({
|
|
@ApiBody({
|
|
|
schema: {
|
|
schema: {
|
|
|
type: 'object',
|
|
type: 'object',
|
|
|
properties: {
|
|
properties: {
|
|
|
- file: { type: 'string', format: 'binary' },
|
|
|
|
|
- mode: { type: 'string', enum: ['fast', 'precise'], description: '处理模式' },
|
|
|
|
|
- chunkSize: { type: 'number', description: '分块大小' },
|
|
|
|
|
- chunkOverlap: { type: 'number', description: '分块重叠' },
|
|
|
|
|
|
|
+ file: { type: 'string', format: 'binary', description: '要上传的文件' },
|
|
|
|
|
+ mode: { type: 'string', enum: ['fast', 'precise'], description: '处理模式:fast(快速)或precise(高精度)', example: 'fast' },
|
|
|
|
|
+ chunkSize: { type: 'number', description: '分块大小', example: 1000 },
|
|
|
|
|
+ chunkOverlap: { type: 'number', description: '分块重叠大小', example: 200 },
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
})
|
|
})
|
|
|
- @ApiResponse({ status: 200, description: '成功' })
|
|
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 返回文件信息' })
|
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
|
async uploadFile(
|
|
async uploadFile(
|
|
@@ -295,8 +329,8 @@ export class ApiV1Controller {
|
|
|
* Delete a specific file from the knowledge base.
|
|
* Delete a specific file from the knowledge base.
|
|
|
*/
|
|
*/
|
|
|
@Delete('knowledge-bases/:id')
|
|
@Delete('knowledge-bases/:id')
|
|
|
- @ApiOperation({ summary: '删除文件', description: '从知识库删除指定文件' })
|
|
|
|
|
- @ApiResponse({ status: 200, description: '成功' })
|
|
|
|
|
|
|
+ @ApiOperation({ summary: '删除文件', description: '从知识库删除指定文件。使用API Key认证。' })
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 文件已删除' })
|
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
@ApiResponse({ status: 401, description: '认证失败' })
|
|
|
@ApiResponse({ status: 404, description: '文件不存在' })
|
|
@ApiResponse({ status: 404, description: '文件不存在' })
|
|
|
async deleteFile(@Request() req, @Param('id') id: string) {
|
|
async deleteFile(@Request() req, @Param('id') id: string) {
|
|
@@ -306,6 +340,9 @@ export class ApiV1Controller {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Get('knowledge-bases/:id')
|
|
@Get('knowledge-bases/:id')
|
|
|
|
|
+ @ApiOperation({ summary: '获取文件详情', description: '获取指定文件的详细信息。使用API Key认证。' })
|
|
|
|
|
+ @ApiResponse({ status: 200, description: '成功 - 返回文件详情' })
|
|
|
|
|
+ @ApiResponse({ status: 404, description: '文件不存在' })
|
|
|
async getFile(@Request() req, @Param('id') id: string) {
|
|
async getFile(@Request() req, @Param('id') id: string) {
|
|
|
const user = req.user;
|
|
const user = req.user;
|
|
|
const files = await this.knowledgeBaseService.findAll(user.id, user.tenantId);
|
|
const files = await this.knowledgeBaseService.findAll(user.id, user.tenantId);
|