Skip to content

feat:Change the AI interface to openai format#31

Merged
hexqi merged 11 commits intoopentiny:mainfrom
lichunn:feat/refactoring-ai
Aug 29, 2025
Merged

feat:Change the AI interface to openai format#31
hexqi merged 11 commits intoopentiny:mainfrom
lichunn:feat/refactoring-ai

Conversation

@lichunn
Copy link
Contributor

@lichunn lichunn commented May 17, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Added knowledge-base search via a new /ai/search endpoint.
    • Added file upload endpoint /ai/uploadFile with cloud storage integration.
    • Streaming AI chat responses via SSE for real-time interaction.
  • Improvements

    • Switched AI chat to use the official OpenAI SDK with native streaming.
    • Messages and streaming/options now driven by request payload; authorization prioritizes provided token.
    • Multipart upload handling and temp-file cleanup configured.
  • Chores

    • Added Alibaba Cloud SDK and related dependencies.

@coderabbitai
Copy link

coderabbitai bot commented May 17, 2025

Walkthrough

Refactors AI chat to use the OpenAI SDK with optional streaming, adds Alibaba Cloud knowledge-base search and OSS upload flows, updates routes to expose /ai/search and /ai/uploadFile, extends multipart upload config and adds Alibaba Cloud SDK dependencies.

Changes

Cohort / File(s) Change Summary
Controller changes
app/controller/app-center/aiChat.ts
Replaced foundationModel logic with options from request body; derive/validate options.messages; extract apiKey from Bearer token; support streaming SSE responses when options.stream true; added search() method; simplified uploadFile to pass raw stream to service.
Router updates
app/router/appCenter/base.ts
Removed POST /ai/files; added POST /ai/search -> aiChat.search and POST /ai/uploadFile -> aiChat.uploadFile; /ai/chat remains.
Service: AI, search, upload
app/service/app-center/aiChat.ts
Replaced prior HTTP-based AI logic with OpenAI SDK chat.completions.create and native streaming; changed getAnswerFromAi signature to accept ChatCompletionMessageParam[]; removed legacy code-extraction and file-content helpers; added Alibaba Cloud OpenApi client helpers (createClient, createApiInfo, getSearchList) and search(content); implemented OSS upload flow (getUploadPolicy, uploadToOSS, uploadFile) with temp file handling and error logging.
Dependencies
package.json
Added Alibaba Cloud SDK packages (@alicloud/*) and mz-modules.
Configuration
config/config.default.ts
Added config.multipart upload/limits and daily cleanup cron; changed config.aiChat to prefer passed token over process.env.OPENAI_API_KEY for authorization.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I bugged the logs and hopped through the streams,
Searched Alibaba groves for bright knowledge beams.
Files climb to OSS on a soft moonlit trail,
OpenAI answers drift in an SSE gale.
I twitch my whiskers—new features set sail!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. uuid is added but not referenced in any of the changed files. If it is unused, please remove it to avoid unnecessary bloat.
  2. 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 errors

If a caller omits apiKey, baseUrl, or model, the service silently falls back to environment variables. This is fine for internal usage but unexpected for public APIs. Consider returning 400 when both request fields and env vars are missing, so mis-configured clients get fast feedback.


27-38: SSE headers are incomplete – browsers may buffer without X-Accel-Buffering: no

Some 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

WorkspaceId parameter is untyped (any) and the endpoint region is hard-coded to cn-beijing. Consider:
• Adding WorkspaceId: string type;
• Reading region from config/env so deployments outside cn-beijing don’t require code changes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e31f2f3 and b6601be.

📒 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)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. It returns the raw OpenAI response without wrapping it with ctx.helper.getResponseData
  2. 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 in createApiInfo.

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,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6601be and 4e956ea.

📒 Files selected for processing (1)
  • app/service/app-center/aiChat.ts (4 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
app/service/app-center/aiChat.ts (8)

48-68: Inconsistent return value and unreachable code.

  1. The function returns the raw OpenAI response in non-stream mode (line 67), but uses ctx.helper.getResponseData in error handling paths.
  2. 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 credentialsConfig1 suggests multiple configs, the endpoint is hardcoded, and there's no validation for required environment variables.


197-209: Add type definition for getSearchList parameters and return value.

The method should specify parameter and return types for better type safety.


211-234: Add input validation to the search method.

The method should validate that the content parameter 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 debug level with minimal details. Use error level and include more context for better debugging.


233-233: Wrap search results with getResponseData for consistency.

For consistency with other methods in this class, the search results should be wrapped with getResponseData.


67-68: 🛠️ Refactor suggestion

Wrap 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 suggestion

Remove 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: Should EnableRewrite be configurable?

The EnableRewrite parameter is hardcoded to true. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4e956ea and f0b23ef.

📒 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 messages changed from Array<AiMessage> to ChatCompletionMessageParam[], but the AiMessage type 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 1

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Search for AiMessage usage across all TypeScript files
rg 'AiMessage' -g '*.ts' -A1 -B1

Length of output: 308


Ignore AiMessage inconsistency warning

The AiMessage type is only declared and used within app/service/app-center/aiChat.ts and isn’t referenced elsewhere in the codebase. The separate IAiMessage in app/lib/interface.ts is unrelated. Switching to ChatCompletionMessageParam[] therefore has no downstream impact—no further changes are needed.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
app/controller/app-center/aiChat.ts (1)

62-67: Type mismatch between controller and service – content should be a string array.

The controller extracts content directly from the request body, but the service expects content to 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 for chatConfig parameter.

Using any type 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:

  1. Inconsistent naming (credentialsConfig1)
  2. Hardcoded endpoint
  3. 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 to getSearchList for 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 the search method and make the return format consistent.

The search method should validate its inputs and return results consistently using getResponseData.

-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

📥 Commits

Reviewing files that changed from the base of the PR and between 387201f and f6c2de0.

📒 Files selected for processing (2)
  • app/controller/app-center/aiChat.ts (1 hunks)
  • app/service/app-center/aiChat.ts (2 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between f6c2de0 and 567fc83.

📒 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 token parameter 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_name without checking if Metadata exists 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

📥 Commits

Reviewing files that changed from the base of the PR and between 567fc83 and c69fa32.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 with getResponseData for 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 stream is 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 for Metadata.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 for search(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 for createApiInfo.

-  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 chunk leaks 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.

📥 Commits

Reviewing files that changed from the base of the PR and between c69fa32 and f5708c1.

📒 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
No ALIBABA_CLOUD or 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.

@hexqi hexqi merged commit 43a846d into opentiny:main Aug 29, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments