feat:Change the AI interface to openai format#31
Conversation
WalkthroughRefactors AI chat to use the OpenAI SDK with optional streaming, adds Alibaba Cloud knowledge-base search and OSS upload flows, updates routes to expose Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Router
participant Controller
participant Service
participant AlibabaCloud
Client->>Router: POST /app-center/api/ai/search (body: content[])
Router->>Controller: aiChat.search()
Controller->>Service: search(content)
Service->>AlibabaCloud: OpenApi Retrieve-like request
AlibabaCloud-->>Service: KB search results
Service-->>Controller: mapped results
Controller-->>Client: JSON response
sequenceDiagram
participant Client
participant Controller
participant Service
participant OpenAI
Client->>Controller: POST /app-center/api/ai/chat (body includes options, messages, stream?)
Controller->>Service: getAnswerFromAi(messages, {apiKey, baseUrl, model, stream, tools})
alt stream = true
Service->>OpenAI: chat.completions.create(..., stream:true)
OpenAI-->>Service: streamed chunks (async iterable)
Service->>Controller: chunk stream
Controller->>Client: SSE events (data: <chunk>) ... data: [DONE]
else stream = false
Service->>OpenAI: chat.completions.create(..., stream:false)
OpenAI-->>Service: full completion
Service-->>Controller: full response
Controller-->>Client: JSON response
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
package.json (1)
72-79: Verify that the new dependencies are actually required and pin security-sensitive versions
uuidis added but not referenced in any of the changed files. If it is unused, please remove it to avoid unnecessary bloat.- All newly-added Alibaba Cloud packages are referenced indirectly from the service, but they are specified with
^ranges. Because these SDKs are still rapidly evolving, a future minor bump could introduce breaking API or auth changes. Locking to the exact working versions (e.g.~1.7.1) or using an explicit lockfile entry is safer.app/controller/app-center/aiChat.ts (2)
22-26: Nullable values may leak downstream – add sane fallbacks or 400 errorsIf a caller omits
apiKey,baseUrl, ormodel, the service silently falls back to environment variables. This is fine for internal usage but unexpected for public APIs. Consider returning400when both request fields and env vars are missing, so mis-configured clients get fast feedback.
27-38: SSE headers are incomplete – browsers may buffer withoutX-Accel-Buffering: noSome reverse proxies (e.g. Nginx) buffer responses by default. Adding
X-Accel-Buffering: no(or the proxy-specific equivalent) greatly improves real-time delivery of streamed chunks.ctx.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', - Connection: 'keep-alive' + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no' });app/service/app-center/aiChat.ts (1)
152-165: Hard-coded endpoint & missing type annotations
WorkspaceIdparameter is untyped (any) and the endpoint region is hard-coded tocn-beijing. Consider:
• AddingWorkspaceId: stringtype;
• Reading region from config/env so deployments outsidecn-beijingdon’t require code changes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/controller/app-center/aiChat.ts(2 hunks)app/router/appCenter/base.ts(1 hunks)app/service/app-center/aiChat.ts(4 hunks)package.json(1 hunks)
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
app/service/app-center/aiChat.ts (1)
44-67: Fix return value inconsistency and unreachable code.The non-streaming mode return path has two issues:
- It returns the raw OpenAI response without wrapping it with
ctx.helper.getResponseData- It returns inside the try block, making the code after the finally block unreachable
- } else { - return result; - } + } else { + result = result; + }Then after the finally block:
- if (!res) { - return this.ctx.helper.getResponseData(`调用AI大模型接口未返回正确数据.`); - } + return this.ctx.helper.getResponseData(result);
🧹 Nitpick comments (2)
app/service/app-center/aiChat.ts (2)
174-174: Fix JSDoc and parameter naming increateApiInfo.The parameter name doesn't follow camelCase convention and the JSDoc param description doesn't match.
/** * @remarks * API 相关 * - * @param path - string Path parameters + * @param workspaceId - string Workspace identifier * @returns OpenApi.Params */ - private createApiInfo(WorkspaceId): $OpenApi.Params { + private createApiInfo(workspaceId: string): $OpenApi.Params {
175-193: Extract hardcoded API values as constants.API-related constants like action, version, etc. should be extracted for better maintainability.
+ // At the top of the file, outside the class + const API_CONSTANTS = { + ACTION: 'Retrieve', + VERSION: '2023-12-29', + PROTOCOL: 'HTTPS', + METHOD: 'POST', + AUTH_TYPE: 'AK', + STYLE: 'ROA', + REQ_BODY_TYPE: 'json', + BODY_TYPE: 'json' + }; // Then in the method let params = new $OpenApi.Params({ - // 接口名称 - action: 'Retrieve', - // 接口版本 - version: '2023-12-29', - // 接口协议 - protocol: 'HTTPS', - // 接口 HTTP 方法 - method: 'POST', - authType: 'AK', - style: 'ROA', + action: API_CONSTANTS.ACTION, + version: API_CONSTANTS.VERSION, + protocol: API_CONSTANTS.PROTOCOL, + method: API_CONSTANTS.METHOD, + authType: API_CONSTANTS.AUTH_TYPE, + style: API_CONSTANTS.STYLE,
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (8)
app/service/app-center/aiChat.ts (8)
48-68: Inconsistent return value and unreachable code.
- The function returns the raw OpenAI response in non-stream mode (line 67), but uses
ctx.helper.getResponseDatain error handling paths.- The code after
finally(lines 78-80) is unreachable since all paths return before it.
154-166: Improve client configuration and naming.The variable name
credentialsConfig1suggests multiple configs, the endpoint is hardcoded, and there's no validation for required environment variables.
197-209: Add type definition forgetSearchListparameters and return value.The method should specify parameter and return types for better type safety.
211-234: Add input validation to thesearchmethod.The method should validate that the
contentparameter is not empty and check for required environment variables before proceeding.
226-230: Improve error logging in the catch block.The current error logging uses
debuglevel with minimal details. Useerrorlevel and include more context for better debugging.
233-233: Wrap search results withgetResponseDatafor consistency.For consistency with other methods in this class, the search results should be wrapped with
getResponseData.
67-68: 🛠️ Refactor suggestionWrap non-stream result with getResponseData for consistency.
Return a consistent response structure by wrapping the non-streaming result with
getResponseData.- return result; + return this.ctx.helper.getResponseData(result);
78-80: 🛠️ Refactor suggestionRemove unreachable code.
This code block will never execute as all paths return before reaching this point.
- if (!res) { - return this.ctx.helper.getResponseData(`调用AI大模型接口未返回正确数据.`); - }
🧹 Nitpick comments (1)
app/service/app-center/aiChat.ts (1)
218-218: ShouldEnableRewritebe configurable?The
EnableRewriteparameter is hardcoded totrue. Consider making this configurable via environment variables or function parameters for flexibility.- queries['EnableRewrite'] = true; + queries['EnableRewrite'] = process.env.ALIBABA_CLOUD_ENABLE_REWRITE !== 'false';
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/service/app-center/aiChat.ts(4 hunks)
🔇 Additional comments (2)
app/service/app-center/aiChat.ts (2)
198-198: Good null-safety handling.Using the nullish coalescing operator (
??) to provide a default empty array is a good practice to prevent "Cannot read properties of undefined" errors.
45-45:Details
❌ Incorrect review comment
Messages parameter type inconsistency.
The parameter
messageschanged fromArray<AiMessage>toChatCompletionMessageParam[], but theAiMessagetype defined in line 24 is still used elsewhere in the codebase. This type change may lead to inconsistencies and type errors.
🏁 Script executed:
#!/bin/bash # Find usages of AiMessage to verify the impact of this type change rg --type=typescript "AiMessage" -A 1 -B 1Length of output: 77
🏁 Script executed:
#!/bin/bash # Search for AiMessage usage across all TypeScript files rg 'AiMessage' -g '*.ts' -A1 -B1Length of output: 308
Ignore AiMessage inconsistency warning
The
AiMessagetype is only declared and used withinapp/service/app-center/aiChat.tsand isn’t referenced elsewhere in the codebase. The separateIAiMessageinapp/lib/interface.tsis unrelated. Switching toChatCompletionMessageParam[]therefore has no downstream impact—no further changes are needed.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (5)
app/controller/app-center/aiChat.ts (1)
62-67: Type mismatch between controller and service –contentshould be a string array.The controller extracts
contentdirectly from the request body, but the service expectscontentto be a string array. This mismatch could cause runtime errors during API calls.public async search() { const { ctx } = this; - const { content } = ctx.request.body; + const content = ctx.request.body?.content; + + // Validate input + if (!content || !Array.isArray(content)) { + ctx.status = 400; + ctx.body = ctx.helper.getResponseData('content should be a non-empty array of strings', false); + return; + } ctx.body = await ctx.service.appCenter.aiChat.search(content); }app/service/app-center/aiChat.ts (4)
36-56: Add proper type definition forchatConfigparameter.Using
anytype reduces type safety and makes the codebase harder to maintain. Create an interface to describe the expected structure.+interface ChatConfig { + apiKey?: string; + baseUrl?: string; + model?: string; + streamStatus: boolean; +} -async getAnswerFromAi(messages: ChatCompletionMessageParam[], chatConfig: any) { +async getAnswerFromAi(messages: ChatCompletionMessageParam[], chatConfig: ChatConfig) {
66-79: Improve client configuration and naming.There are several issues in the client creation:
- Inconsistent naming (
credentialsConfig1)- Hardcoded endpoint
- No validation for required environment variables
private createClient(): OpenApi { - const credentialsConfig1 = new Config({ + // Validate required environment variables + if (!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || !process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET) { + this.ctx.logger.error('Missing required Alibaba Cloud credentials'); + throw new Error('Missing required Alibaba Cloud credentials'); + } + + const credentialsConfig = new Config({ type: 'access_key', accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID, accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET }); - let credential = new Credential(credentialsConfig1); + let credential = new Credential(credentialsConfig); let config = new $OpenApi.Config({ credential: credential }); - config.endpoint = `bailian.cn-beijing.aliyuncs.com`; + config.endpoint = process.env.ALIBABA_CLOUD_ENDPOINT || `bailian.cn-beijing.aliyuncs.com`; return new OpenApi(config); }
110-122: Add proper type definitions togetSearchListfor better type safety.The method should have proper parameter and return types for better type safety and code clarity.
-private getSearchList(res) { +interface SearchNode { + Score: number; + Text: string; + Metadata: { + doc_name?: string; + }; +} + +interface SearchResponse { + body?: { + Data?: { + Nodes?: SearchNode[]; + }; + }; +} + +interface SearchResult { + data: Array<{ score: number; content: string; doc_name?: string }>; +} + +private getSearchList(res: SearchResponse): SearchResult { const list = res?.body?.Data?.Nodes ?? []; return { data: list.map((node) => { return { score: node.Score, content: node.Text, doc_name: node.Metadata.doc_name }; }) }; }
124-150: Add input validation to thesearchmethod and make the return format consistent.The
searchmethod should validate its inputs and return results consistently usinggetResponseData.-async search(content: string[]) { +async search(content: string[]) { + if (!content || content.length === 0) { + return this.ctx.helper.getResponseData('搜索内容不能为空', false); + } + + if (!process.env.ALIBABA_CLOUD_WORKSPACE_ID || !process.env.ALIBABA_CLOUD_INDEX_ID) { + this.ctx.logger.error('Missing required Alibaba Cloud environment variables'); + return this.ctx.helper.getResponseData('知识库检索配置不完整', false); + } + let res: any = null; let client = this.createClient(); let params = this.createApiInfo(process.env.ALIBABA_CLOUD_WORKSPACE_ID); // query params let queries: { [key: string]: any } = {}; const QUERY = 'Query'; const ENABLE_REWRITE = 'EnableRewrite'; const INDEX_ID = 'IndexId'; queries[QUERY] = content; queries[ENABLE_REWRITE] = true; queries[INDEX_ID] = process.env.ALIBABA_CLOUD_INDEX_ID; // runtime options let runtime = new $Util.RuntimeOptions({}); let request = new $OpenApi.OpenApiRequest({ query: OpenApiUtil.query(queries) }); try { res = await client.callApi(params, request, runtime); } catch (e) { - this.ctx.logger.debug('Alibaba Cloud search failed', e); + this.ctx.logger.error('Alibaba Cloud search failed', { + error: e.message, + stack: e.stack, + params: { + workspaceId: process.env.ALIBABA_CLOUD_WORKSPACE_ID, + indexId: process.env.ALIBABA_CLOUD_INDEX_ID + } + }); return this.ctx.helper.getResponseData('知识库检索接口调用失败'); } - return this.getSearchList(res); + const searchResults = this.getSearchList(res); + return this.ctx.helper.getResponseData(searchResults); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/controller/app-center/aiChat.ts(1 hunks)app/service/app-center/aiChat.ts(2 hunks)
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
app/service/app-center/aiChat.ts (2)
74-87: Address previously identified configuration issues.The client configuration needs improvements already identified in past reviews regarding variable naming, endpoint configuration, and environment variable validation.
132-159: Address previously identified issues in search method.Multiple improvements needed for this method as identified in past reviews, including input validation, enhanced error logging, and consistent return value formatting.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/controller/app-center/aiChat.ts(1 hunks)app/router/appCenter/base.ts(1 hunks)app/service/app-center/aiChat.ts(2 hunks)config/config.default.ts(2 hunks)package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app/router/appCenter/base.ts
- package.json
- app/controller/app-center/aiChat.ts
🔇 Additional comments (7)
config/config.default.ts (2)
89-100: Well-configured multipart file upload settings.The multipart configuration properly implements security best practices with file size limits, type restrictions, and automated cleanup. The stream mode is appropriate for handling file uploads efficiently.
276-276: Good enhancement for dynamic token configuration.The change to prioritize the
tokenparameter over the environment variable provides better flexibility for dynamic API key management while maintaining backward compatibility.app/service/app-center/aiChat.ts (5)
13-23: Comprehensive imports for enhanced functionality.The new imports properly support the refactored AI service with OpenAI SDK integration, Alibaba Cloud services, and file upload capabilities. All imports appear to be utilized in the implementation.
41-64: Simplified implementation using OpenAI SDK.The refactored method successfully leverages the OpenAI SDK's native streaming capabilities, significantly simplifying the codebase by removing custom HTTP request handling. The error handling structure is appropriate for the new approach.
96-116: Well-structured API parameter configuration.The method properly configures the Alibaba Cloud API parameters with correct protocol, authentication, and content type settings for knowledge base retrieval.
166-187: Well-implemented credential retrieval.The method properly handles authentication, timeouts, and error cases while retrieving upload credentials from the DashScope API. Good use of descriptive error messages.
195-265: Robust file upload implementation with proper cleanup.Both methods implement comprehensive file upload handling with proper temporary file management, error handling, and cleanup. The use of streams and form data is appropriate for OSS integration.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (5)
app/service/app-center/aiChat.ts (5)
74-87: Duplicate issue: Improve client configuration and naming.The issues with variable naming, hardcoded endpoint, and missing validation are still present from previous reviews.
- const credentialsConfig1 = new Config({ + const credentialsConfig = new Config({ type: 'access_key', accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID, accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET }); + + if (!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || !process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET) { + throw new Error('Missing required Alibaba Cloud credentials'); + } + - let credential = new Credential(credentialsConfig1); + let credential = new Credential(credentialsConfig); let config = new $OpenApi.Config({ credential: credential }); - config.endpoint = `bailian.cn-beijing.aliyuncs.com`; + config.endpoint = process.env.ALIBABA_CLOUD_ENDPOINT || `bailian.cn-beijing.aliyuncs.com`;
118-130: Duplicate issue: Null safety improved but new issue with Metadata access.Good use of nullish coalescing for the Nodes array, but accessing
node.Metadata.doc_namewithout checking ifMetadataexists could cause runtime errors.return { data: list.map((node) => { return { score: node.Score, content: node.Text, - doc_name: node.Metadata.doc_name + doc_name: node.Metadata?.doc_name || 'Unknown' }; }) };
157-157: Duplicate issue: Wrap search results with getResponseData for consistency.For consistency with other methods in this class, the search results should be wrapped with
getResponseData.- return this.getSearchList(res); + const searchResults = this.getSearchList(res); + return this.ctx.helper.getResponseData(searchResults);
41-64: Simplified OpenAI integration approved, but address type safety concerns.The refactor to use the official OpenAI SDK is a significant improvement, making the code much cleaner and more maintainable. However, there are still some type safety issues that need addressing.
Apply this diff to improve type safety:
- async getAnswerFromAi(messages: ChatCompletionMessageParam[], chatConfig: any) { + interface ChatConfig { + apiKey?: string; + baseUrl?: string; + model?: string; + streamStatus: boolean; + } + + async getAnswerFromAi(messages: ChatCompletionMessageParam[], chatConfig: ChatConfig) {
132-158: Missing input validation and error handling improvements needed.The search method lacks proper input validation and detailed error logging as noted in previous reviews.
async search(content: string[]) { + if (!content || content.length === 0) { + return this.ctx.helper.getResponseData('搜索内容不能为空', false); + } + + if (!process.env.ALIBABA_CLOUD_WORKSPACE_ID || !process.env.ALIBABA_CLOUD_INDEX_ID) { + this.logger.error('Missing required Alibaba Cloud environment variables'); + return this.ctx.helper.getResponseData('知识库检索配置不完整', false); + } + let res: any = null; let client = this.createClient(); let params = this.createApiInfo(process.env.ALIBABA_CLOUD_WORKSPACE_ID);Also improve error logging:
} catch (e) { - this.logger.error('知识库检索接口调用失败', e); + this.logger.error('知识库检索接口调用失败', { + error: e.message, + stack: e.stack, + params: { + workspaceId: process.env.ALIBABA_CLOUD_WORKSPACE_ID, + indexId: process.env.ALIBABA_CLOUD_INDEX_ID + } + });
🧹 Nitpick comments (1)
app/service/app-center/aiChat.ts (1)
96-116: Add type annotation for WorkspaceId parameter.The parameter lacks a type annotation which reduces type safety.
- private createApiInfo(WorkspaceId): $OpenApi.Params { + private createApiInfo(WorkspaceId: string): $OpenApi.Params {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/controller/app-center/aiChat.ts(1 hunks)app/service/app-center/aiChat.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/controller/app-center/aiChat.ts
🔇 Additional comments (1)
app/service/app-center/aiChat.ts (1)
13-23: Import organization looks good, but consider consolidating related imports.The new imports are properly organized and necessary for the refactored functionality. Consider grouping related imports together for better readability.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (12)
app/service/app-center/aiChat.ts (10)
156-160: Log structured error details.- } catch (e) { - this.ctx.logger.error('Alibaba Cloud search failed', e); - return this.ctx.helper.getResponseData(`知识库检索接口调用失败:${e}`); + } catch (e: any) { + this.ctx.logger.error('Alibaba Cloud search failed', { + error: e?.message, + stack: e?.stack, + workspaceId: process.env.ALIBABA_CLOUD_WORKSPACE_ID, + indexId: process.env.ALIBABA_CLOUD_INDEX_ID + }); + return this.ctx.helper.getResponseData(`知识库检索接口调用失败:${e?.message || e}`);
162-163: Wrap search results withgetResponseDatafor consistency.- return this.getSearchList(res); + const searchResults = this.getSearchList(res); + return this.ctx.helper.getResponseData(searchResults);
254-266: Make temp file cleanup resilient in both success and error paths.- fs.unlinkSync(tmpFilePath); + try { + fs.unlinkSync(tmpFilePath); + } catch (unlinkErr) { + this.ctx.logger.warn('清理临时文件失败(成功路径):', unlinkErr); + } @@ - if (fs.existsSync(tmpFilePath)) { - fs.unlinkSync(tmpFilePath); - } - this.ctx.logger.error('文件上传失败:', e); + try { + if (fs.existsSync(tmpFilePath)) { + fs.unlinkSync(tmpFilePath); + } + } catch (unlinkErr) { + this.ctx.logger.warn('清理临时文件失败(异常路径):', unlinkErr); + } + this.ctx.logger.error('文件上传失败:', { error: (e as any)?.message, filename, modelName });
62-67: Unify return shape without breaking streaming.Return raw stream when
streamis true; otherwise wrap for consistency.- return result; + if (chatConfig.stream) { + return result; + } + return this.ctx.helper.getResponseData(result);
80-91: Client config: rename var, validate creds, and make endpoint configurable.- const credentialsConfig1 = new Config({ + const credentialsConfig = new Config({ type: 'access_key', accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID, accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET }); - let credential = new Credential(credentialsConfig1); + if (!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || !process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET) { + throw new Error('Missing required Alibaba Cloud credentials'); + } + let credential = new Credential(credentialsConfig); let config = new $OpenApi.Config({ credential: credential }); - config.endpoint = `bailian.cn-beijing.aliyuncs.com`; + config.endpoint = process.env.ALIBABA_CLOUD_ENDPOINT || 'bailian.cn-beijing.aliyuncs.com';
123-134: Null-safety forMetadata.doc_name.- doc_name: node.Metadata.doc_name + doc_name: node.Metadata?.doc_name ?? 'Unknown'
137-149: Validate input and env before calling KB API; accept string or string[].- async search(content: string[]) { + async search(content: string | string[]) { let res: any = null; @@ - // query params + // input/env validation + const items = Array.isArray(content) ? content.filter(Boolean) : [content].filter(Boolean); + if (!items.length) { + return this.ctx.helper.getResponseData('搜索内容不能为空', false); + } + if (!process.env.ALIBABA_CLOUD_WORKSPACE_ID || !process.env.ALIBABA_CLOUD_INDEX_ID) { + this.ctx.logger.error('Missing Alibaba Cloud workspace/index env vars'); + return this.ctx.helper.getResponseData('知识库检索配置不完整', false); + } + // query params let queries: { [key: string]: any } = {}; @@ - queries[QUERY] = content; + queries[QUERY] = items;
170-186: Type + validation for upload policy; guard invalid responses.- async getUploadPolicy(modelName, apiKey) { + async getUploadPolicy(modelName: string, apiKey: string) { + if (!modelName || !apiKey) { + return this.ctx.helper.getResponseData('模型名称和API密钥不能为空', false); + } try { const result = await this.ctx.curl('https://dashscope.aliyuncs.com/api/v1/uploads', { @@ timeout: 5000 }); - return result.data.data; - } catch (e) { - this.ctx.logger.error('获取上传凭证失败:', e); - return this.ctx.helper.getResponseData(`获取上传凭证失败:${e}`); + const data = result?.data?.data; + if (!data) { + throw new Error('Invalid response from upload policy API'); + } + return data; + } catch (e: any) { + this.ctx.logger.error('获取上传凭证失败:', { error: e?.message, modelName }); + return this.ctx.helper.getResponseData(`获取上传凭证失败:${e?.message || e}`); }
236-245: Validate stream + fields; ensure filename present.- async uploadFile(stream) { + async uploadFile(stream: any) { + if (!stream || !stream.fields) { + return this.ctx.helper.getResponseData('无效的文件流', false); + } const { modelName, apiKey } = stream.fields; - const filename = stream.filename; + if (!modelName || !apiKey) { + return this.ctx.helper.getResponseData('缺少必要的上传参数', false); + } + const filename = stream.filename; + if (!filename) { + return this.ctx.helper.getResponseData('文件名不能为空', false); + }
251-253: Guard policy fetch result before OSS upload.- const policyData = await this.getUploadPolicy(modelName, apiKey); + const policyData = await this.getUploadPolicy(modelName, apiKey); + // if getUploadPolicy returned an error envelope + if (!policyData || (policyData.success === false)) { + throw new Error('获取上传凭证失败'); + }app/controller/app-center/aiChat.ts (2)
72-77: Align controller/service types forsearch(content)and validate input.public async search() { const { ctx } = this; - const { content } = ctx.request.body; - - ctx.body = await ctx.service.appCenter.aiChat.search(content); + const content: string = String(ctx.request.body?.content || '').trim(); + if (!content) { + ctx.status = 400; + ctx.body = ctx.helper.getResponseData('content is required', false); + return; + } + ctx.body = await ctx.service.appCenter.aiChat.search([content]); }
30-57: Streaming: send deltas, handle loop errors, and terminate with proper SSE newline.if (stream) { @@ - for await (const chunk of result) { - ctx.res.write(`data: ${JSON.stringify(chunk)}\n\n`); // SSE 格式 - } + try { + for await (const chunk of result) { + const content = chunk?.choices?.[0]?.delta?.content || ''; + if (content) { + ctx.res.write(`data: ${JSON.stringify({ content })}\n\n`); + } + } + } catch (streamErr: any) { + ctx.logger.error(`流式响应中断: ${streamErr?.message}`); + ctx.res.write(`data: ${JSON.stringify({ error: `流式响应中断: ${streamErr?.message}` })}\n\n`); + } @@ - ctx.res.write('data: [DONE]'); + ctx.res.write('data: [DONE]\n\n'); } catch (e: any) { this.ctx.logger.error(`调用AI大模型接口失败: ${(e as Error).message}`); } finally { - console.log('end'); - ctx.res.end(); // 关闭连接 + ctx.res.end(); // 关闭连接 }
🧹 Nitpick comments (3)
app/service/app-center/aiChat.ts (1)
101-121: Add types and better param naming forcreateApiInfo.- private createApiInfo(WorkspaceId): $OpenApi.Params { + private createApiInfo(workspaceId: string): $OpenApi.Params { let params = new $OpenApi.Params({ @@ - pathname: `/${WorkspaceId}/index/retrieve`, + pathname: `/${workspaceId}/index/retrieve`,app/controller/app-center/aiChat.ts (2)
20-23: Message validation response should set 400 status.- if (!messages || !Array.isArray(messages)) { - return this.ctx.helper.getResponseData('Not passing the correct message parameter'); - } + if (!messages || !Array.isArray(messages)) { + ctx.status = 400; + return this.ctx.helper.getResponseData('Not passing the correct message parameter', false); + }
46-48: SSE payloads should not stream full chunks.Streaming entire
chunkleaks provider internals and increases bandwidth.The diff in the streaming comment above extracts
delta.content, which is typically expected by clients. Verify your frontend SSE consumer expects{ content }rather than raw chunks.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
app/controller/app-center/aiChat.ts(1 hunks)app/service/app-center/aiChat.ts(2 hunks)package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
🔇 Additional comments (2)
app/service/app-center/aiChat.ts (2)
226-229: Propagate errors instead of returning helper inside service internals.Returning a helper-wrapped error breaks callers that destructure
{ ossUrl, expireTime }.
[ suggest_essential_refactor ]- } catch (e) { - this.ctx.logger.error('上传文件到OSS失败:', e); - return this.ctx.helper.getResponseData(`上传文件到OSS失败:${e}`); - } + } catch (e: any) { + this.ctx.logger.error('上传文件到OSS失败:', { error: e?.message, filePath, filename }); + throw e; + }
41-67: Remove KB API content type check suggestion
NoALIBABA_CLOUDor KB API calls exist in app/service/app-center/aiChat.ts or elsewhere in the repo—this comment can be ignored.Likely an incorrect or invalid review comment.
English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Improvements
Chores