Skip to content

feat: Add multi‑channel chat support (Slack, Feishu) with UI and backend#15865

Open
zaviermeekz-cpu wants to merge 6 commits into
infiniflow:mainfrom
zaviermeekz-cpu:feature/multi-channels
Open

feat: Add multi‑channel chat support (Slack, Feishu) with UI and backend#15865
zaviermeekz-cpu wants to merge 6 commits into
infiniflow:mainfrom
zaviermeekz-cpu:feature/multi-channels

Conversation

@zaviermeekz-cpu

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

RAGFlow currently has the full plumbing for chat‑channel bots, but only Feishu/Lark is implemented and tested end‑to‑end. This PR adds the corresponding front‑end and back‑end code for Slack, Feishu (as reference), and a generic framework so other channels (Discord, Telegram, WhatsApp, etc.) can be added later.

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

How has this been tested?

  • ✅ Backend channel framework: Channel abstract class, registry, bootstrap reconcile loop.
  • ✅ Slack channel implementation with Socket Mode (slack_sdk).
  • ✅ Feishu (Lark) reference implementation with webhook support.
  • ✅ Database model ChatChannel added (Peewee).
  • ✅ REST API endpoints under /api/v1/channels.
  • ✅ Frontend UI: Settings → Chat Channels – add/edit/delete channels, dynamic credential fields per channel type.
  • ✅ i18n keys added for English and Chinese.

Manual verification (limited due to Docker environment on Windows):

  • Slack bot creation via UI works, token validation, and channel start/stop logic.
  • Feishu webhook placeholder ready for actual integration.

Checklist

  • Code follows the project's coding style (ruff, eslint, prettier).
  • I have added necessary documentation (if applicable).
  • I have added tests that prove my fix/feature works (if applicable).
  • I have tested the changes locally.

Related Issue

Closes #15844

Additional context

This is a draft PR – the backend channel reconciler and webhook routes require testing in a full Docker environment. The main code is complete; the remaining work is integration and validation by maintainers with a working setup.

RAGFlow Dev added 2 commits June 9, 2026 09:04
- Add Channel abstract base class and registry
- Implement Slack channel (Socket Mode) and Feishu channel (webhook)
- Add ChatChannel database model
- Add bootstrap reconcile loop for starting/stopping channels
- Add REST API endpoints for channel management
- Add frontend Chat Channels settings page
- Update navigation and i18n

Closes infiniflow#15844

This is a draft PR - backend integration requires testing in a working Docker environment.
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. 💞 feature Feature request, pull request that fullfill a new feature. labels Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df3ac963-abe1-4440-9ca1-42acf9458f5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4701428 and b0ce3b1.

📒 Files selected for processing (4)
  • api/channels/bootstrap.py
  • api/db/db_models.py
  • api/ragflow_server.py
  • conf/service_conf.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • conf/service_conf.yaml

📝 Walkthrough

Walkthrough

Adds channel message contracts and registry, a reconcile runtime with server hook, Feishu and Slack adapters, channel CRUD REST API, frontend service + settings UI and i18n, and service configuration template updates.

Changes

Chat channel management

Layer / File(s) Summary
Channel base contracts and registry
api/channels/base.py, api/channels/registry.py, api/db/db_models.py
IncomingMessage and OutgoingMessage dataclasses, abstract Channel base class, in-memory CHANNEL_REGISTRY, and ChatChannel ORM model define the typed contract used by channel implementations.
Channel reconciliation and runtime bootstrap
api/channels/bootstrap.py, api/ragflow_server.py
Registers bundled channel modules, loads enabled ChatChannel rows, diffs desired vs running instances, and runs a reconcile loop to start/stop Channel instances; provides thread entrypoint and schedules the loop on server startup.
Feishu channel implementation
api/channels/feishu.py
FeishuChannel implements webhook handling (url_verification), optional signature verification, tenant token caching, per-sender session continuity, dispatch to async_iframe_completion, and reply posting to Feishu IM API.
Slack channel implementation
api/channels/slack.py
SlackChannel manages Socket Mode lifecycle, acknowledges events_api envelopes, filters user message events, dispatches to async_iframe_completion with per-user session continuity, and posts replies via chat_postMessage.
Channel REST API management endpoints
api/apps/restful_apis/channel_api.py
Authenticated endpoints: list tenant channels, fetch per-type info/schema, create/update/delete channels with ownership checks and runtime stop-on-delete; unauthenticated Feishu webhook endpoint routes payloads to running instance handle_webhook().
Frontend service layer
web/src/services/channel.ts
TypeScript interfaces (IChannel, IChannelPayload, IChannelFieldSchema) and authenticated fetch helpers (getChannels, createChannel, updateChannel, deleteChannel, getChannelInfo) with centralized JSON response handling.
Channels settings management page
web/src/pages/user-setting/channels/index.tsx
ChannelsPage lists channels and provides edit/enable/disable/delete actions; ChannelFormDialog supports create/edit with conditional credential inputs and validation.
Frontend routing and navigation integration
web/src/pages/user-setting/sidebar/index.tsx, web/src/routes.tsx
Adds /channels route under user settings and a sidebar menu item with icon and translated label.
Internationalization
web/src/locales/en.ts, web/src/locales/zh.ts
Adds common UI labels (enable/disable/saving/loading) and channel configuration labels, including nested credential field names in English and Simplified Chinese.
Service configuration templates
conf/service_conf.yaml
Replaces localhost examples with service hostnames, updates embedding model base_url, and refreshes commented storage/OAuth/SMTP/LLM templates with cleaner placeholders.

Sequence Diagram

sequenceDiagram
  participant User as Slack/Feishu User
  participant WebhookEndpoint as POST /channels/{id}/webhook
  participant ChannelInstance as Running Channel
  participant Dialog as Assistant/Dialog
  participant ProviderAPI as Provider HTTP API

  User->>WebhookEndpoint: Sends message to bot
  WebhookEndpoint->>ChannelInstance: payload -> handle_webhook()
  ChannelInstance->>ChannelInstance: parse IncomingMessage
  ChannelInstance->>Dialog: _dispatch(incoming) with dialog_id
  Dialog-->>ChannelInstance: completion chunks (answer, session_id)
  ChannelInstance->>ChannelInstance: update session_id, build OutgoingMessage
  ChannelInstance->>ProviderAPI: send reply via provider API
  ProviderAPI-->>User: delivered reply
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • infiniflow/ragflow#15850: Wires similar chat channel runtime and reconciliation flow; implementation overlap with channel bootstrap and server startup hook.

Poem

🐇 I hop through registry, thread, and hook,

Channels awake where quiet code once shook.
Feishu chirps, Slack bell rings reply,
Reconciler hums, sessions flutter by.
A rabbit cheers: bots live — give them a try!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature: adding multi-channel chat support for Slack and Feishu with UI and backend implementation.
Description check ✅ Passed The description comprehensively addresses the problem, specifies the new feature type, covers testing approach and results, includes related issue, and completes the required template sections.
Linked Issues check ✅ Passed Code changes fully implement requirements from #15844: database model, channel framework with registry, bootstrap reconciler, Slack/Feishu implementations, REST API endpoints, and frontend UI for channel management.
Out of Scope Changes check ✅ Passed All changes directly support multi-channel bot support objectives; service config and locale updates are necessary supporting changes for the feature scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@zaviermeekz-cpu

Copy link
Copy Markdown
Contributor Author

@KevinHuSh @xugangqiang This PR is ready for review. Thank you for your time and for maintaining such a great project! I’m happy to make any changes or improvements you suggest. Please let me know what you think.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
api/apps/services/provider_api_service.py (1)

382-503: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit logs for the newly introduced verification branches.

Line 382 onward introduces new control paths (VLLM base URL normalization effects, BaiduYiyan key coercion, OCR verification). Failures are logged, but branch decisions/success-path observability is still missing, which makes production troubleshooting harder.

As per coding guidelines, “**/*.py: Add logging for new flows”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/services/provider_api_service.py` around lines 382 - 503, The new
verification flow lacks informative info-level logs: add logging calls after
base_url is set (via _normalize_provider_base_url) and when target_factory_name
is chosen, log when factory_llms is built from FACTORY_LLM_INFOS or from
model_info, and log BaiduYiyan API key coercion (showing masked/summary
api_key_str) inside the BaiduYiyan branch; also add info/debug logs on each
verification branch success/failure (embedding via EmbeddingModel.encode, chat
via ChatModel.async_chat_streamly, rerank via RerankModel.similarity, ocr via
OcrModel.check_available) indicating provider_name, llm['llm_name'], model_type
and result so callers can observe which path passed or failed before returning.
Ensure logs avoid printing secrets (mask api keys) and use existing logger
variable (e.g., logging) and messages consistent with surrounding logs.

Source: Coding guidelines

internal/entity/models/model.go (1)

516-523: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard optional MaxTokens before dereferencing.

Model.MaxTokens is now nullable, but this branch still dereferences it unconditionally and then formats the pointer itself in the error message. A max_tokens filter against a model without that field will panic instead of returning a 4xx response.

Suggested fix
 		case "max_tokens":
 			if maxVal, ok := filterValue.(int); ok {
-				if *model.MaxTokens < maxVal {
+				if model.MaxTokens == nil {
+					matchFilter = false
+					resp.Code = 400
+					resp.Message = "Model does not define max_tokens"
+					break
+				}
+				if *model.MaxTokens < maxVal {
 					matchFilter = false
 					resp.Code = 400
 					resp.Message = fmt.Sprintf("Model does not meet filter criteria: max_tokens (%d) < %d",
-						model.MaxTokens, maxVal)
+						*model.MaxTokens, maxVal)
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/model.go` around lines 516 - 523, In the "max_tokens"
filter branch, avoid unguarded dereference of the nullable Model.MaxTokens
pointer: first assert filterValue is an int, then check if model.MaxTokens ==
nil and if so set matchFilter = false and resp.Code/resp.Message to a 4xx error
indicating the model has no max_tokens (do not dereference); only when
model.MaxTokens != nil perform the comparison using *model.MaxTokens and format
the error message with the dereferenced value. Ensure you reference the "case
\"max_tokens\"" branch, the model.MaxTokens field, matchFilter, resp.Code and
resp.Message when applying this guard and updating messages.
internal/entity/models/deepinfra.go (1)

843-867: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore credential validation in ListModels.

CheckConnection on Line 954 delegates to this method, but this implementation skips APIConfigCheck and never sends the Bearer token. That makes the health check unauthenticated: a public /models endpoint can incorrectly accept missing/invalid keys, and an authenticated one will fail even with a valid key.

Suggested fix
 func (d *DeepInfraModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+	if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
+		return nil, err
+	}
 
 	resolvedBaseURL, err := d.baseModel.GetBaseURL(apiConfig)
 	if err != nil {
 		return nil, err
@@
-	req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
+	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
 
 	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/deepinfra.go` around lines 843 - 867, The ListModels
method in DeepInfraModel skips APIConfig validation and never sets the
Authorization header, causing unauthenticated or failed health checks; restore
the APIConfigCheck call (use the existing APIConfigCheck /
d.baseModel.APIConfigCheck utility used elsewhere) at the start of ListModels to
validate apiConfig and extract the token, and set the Bearer token on the
http.Request (Authorization: Bearer <token>) before sending; also remove or
avoid sending an unnecessary JSON body for the GET (use nil body) so the request
matches the service expectation and CheckConnection (which calls ListModels)
performs an authenticated health check.
internal/entity/models/jina.go (1)

321-335: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Authenticate ListModels the same way as the other Jina calls.

CheckConnection on Line 372 uses this method, but ListModels no longer validates apiConfig and never sends the Bearer token. That disconnects verification from the supplied credentials, so a public /models endpoint can falsely pass bad keys and an authenticated one will fail even when chat/embedding requests succeed.

Suggested fix
 func (j *JinaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+	if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
+		return nil, err
+	}
 
 	resolvedBaseURL, err := j.baseModel.GetBaseURL(apiConfig)
 	if err != nil {
 		return nil, err
@@
 	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/jina.go` around lines 321 - 335, The ListModels method
(JinaModel.ListModels) currently skips validating apiConfig and does not set the
Authorization header, unlike other Jina calls such as CheckConnection; update
ListModels to validate the provided apiConfig the same way other methods do and
add the Bearer token to the request headers (e.g., set "Authorization: Bearer
<token>" from apiConfig) before sending the GET to j.baseModel.GetBaseURL(...) +
j.baseModel.URLSuffix.Models so the call is authenticated consistently with
chat/embedding requests.
web/src/components/ui/toggle-list.tsx (1)

1-343: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Modifications to src/components/ui/ require explicit approval.

This file is under src/components/ui/, which is protected by coding guidelines. All modifications—including the new footer prop, search filter changes, styling updates, and footer rendering logic—require explicit user approval before merge.

As per coding guidelines, if ToggleList doesn't meet requirements, wrap or compose it in a new component outside src/components/ui/ instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/ui/toggle-list.tsx` around lines 1 - 343, The PR modifies
a protected UI file (ToggleList) under src/components/ui/ without explicit
approval; either revert those edits or move the new behavior into a new
component outside that folder and leave ToggleList untouched. Specifically,
remove the newly added footer prop/logic (footer, footerClassName, footerNode
rendering) and any search/filter styling changes in ToggleList, or create a
composed wrapper component (e.g., NewToggleListWrapper) outside
src/components/ui/ that imports the original ToggleList and implements the
footer, API search (onSearch), and loadMore behaviors there; ensure references
to ToggleList, footer/footerNode, onSearch, and filteredOptions are adjusted
accordingly and get explicit approval before modifying files under
src/components/ui/.

Source: Coding guidelines

🟡 Minor comments (9)
internal/handler/document.go-1013-1016 (1)

1013-1016: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize doc_ids query tokens before calling the service.

Raw strings.Split can pass whitespace/empty IDs (e.g., trailing commas), which causes avoidable lookup mismatches downstream.

Proposed fix
 	var docIDS []string
 	if docIDsParam := c.Query("doc_ids"); docIDsParam != "" {
-		docIDS = strings.Split(docIDsParam, ",")
+		for _, id := range strings.Split(docIDsParam, ",") {
+			id = strings.TrimSpace(id)
+			if id != "" {
+				docIDS = append(docIDS, id)
+			}
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/handler/document.go` around lines 1013 - 1016, The code reading the
doc_ids query uses strings.Split directly into docIDS (variable) which can leave
whitespace and empty tokens; update the parsing so that after c.Query("doc_ids")
is non-empty you split, then for each token apply strings.TrimSpace and only
append non-empty values to docIDS (thus removing tokens from trailing/leading
commas and whitespace) before passing docIDS to the downstream service (refer to
the docIDS variable, the c.Query("doc_ids") call, and the current strings.Split
usage).
rag/graphrag/utils.py-707-721 (1)

707-721: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle non-positive n_hop explicitly.

With n_hop=0 (or negative), this function still emits 1-hop paths. Add an early return to keep behavior consistent with the documented contract.

💡 Suggested fix
 def n_neighbor(graph: nx.Graph, node, n_hop: int = 2):
+    if n_hop <= 0:
+        return []
+
     """Enumerate paths of up to ``n_hop`` edges starting at ``node`` together
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag/graphrag/utils.py` around lines 707 - 721, The n_neighbor function
currently emits 1-hop paths even when n_hop is 0 or negative; add an explicit
early return in n_neighbor to handle non-positive n_hop (e.g., if n_hop <= 0:
return []), so the function honors the documented "up to n_hop edges" contract;
locate the function n_neighbor in rag/graphrag/utils.py and add the check before
any graph.edges/node traversal and counting logic.
internal/entity/models/gitee.go-902-917 (1)

902-917: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve model metadata lookup after final name normalization.

GetModelByNameOrAlias runs before the @OwnedBy suffix is applied. For provider-qualified models, this can attach incorrect (or empty) metadata in the returned ListModelResponse.

Suggested fix
-	for _, model := range modelList.Models {
-		modelName := model.ID
-		var modelResponse ListModelResponse
-		pm := GetProviderManager()
-		modelEntity := pm.GetModelByNameOrAlias(modelName)
-		if model.OwnedBy != "" {
-			modelName = model.ID + "@" + model.OwnedBy
-		}
-		modelResponse.Name = modelName
+	for _, model := range modelList.Models {
+		rawName := model.ID
+		modelName := rawName
+		if model.OwnedBy != "" {
+			modelName = model.ID + "@" + model.OwnedBy
+		}
+		modelResponse := ListModelResponse{Name: modelName}
+		pm := GetProviderManager()
+		var modelEntity *Model
+		if pm != nil {
+			modelEntity = pm.GetModelByNameOrAlias(modelName)
+			if modelEntity == nil && modelName != rawName {
+				modelEntity = pm.GetModelByNameOrAlias(rawName)
+			}
+		}
 		if modelEntity != nil {
 			modelResponse.Dimension = modelEntity.Dimension
 			modelResponse.MaxTokens = modelEntity.MaxTokens
 			modelResponse.ModelTypes = modelEntity.ModelTypes
 			modelResponse.Thinking = modelEntity.Thinking
 		}
 
 		models = append(models, modelResponse)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/gitee.go` around lines 902 - 917, The code calls
GetProviderManager().GetModelByNameOrAlias using modelName before the
provider-qualified suffix is appended, causing wrong metadata; update the logic
in the loop so that the modelName is normalized first (append "@" +
model.OwnedBy when model.OwnedBy != "") and then call GetModelByNameOrAlias with
the final modelName to populate ListModelResponse fields (Dimension, MaxTokens,
ModelTypes, Thinking) from modelEntity; adjust references in the loop around
modelName, model.OwnedBy, GetProviderManager(), GetModelByNameOrAlias and
ListModelResponse accordingly.
web/src/locales/en.ts-1559-1561 (1)

1559-1561: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use sentence case for the new English locale strings.

Several new en.ts labels are Title Case ("AI Studio Access Token", "Chat Channels", "Add Channel", "Bot Token", etc.), which conflicts with the repo’s sentence-case rule for en.ts.

Suggested copy updates
-      paddleocrAccessToken: 'AI Studio Access Token',
+      paddleocrAccessToken: 'AI Studio access token',
@@
-      channels: 'Chat Channels',
-      chatChannels: 'Chat Channels',
-      addChannel: 'Add Channel',
-      editChannel: 'Edit Channel',
+      channels: 'Chat channels',
+      chatChannels: 'Chat channels',
+      addChannel: 'Add channel',
+      editChannel: 'Edit channel',
@@
-        bot_token: 'Bot Token',
-        app_token: 'App Token',
+        bot_token: 'Bot token',
+        app_token: 'App token',
         app_id: 'App ID',
-        app_secret: 'App Secret',
-        encrypt_key: 'Encryption Key',
+        app_secret: 'App secret',
+        encrypt_key: 'Encryption key',

As per coding guidelines, web/src/locales/en.ts should use sentence case for English locale values.

Also applies to: 1792-1807

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/locales/en.ts` around lines 1559 - 1561, The locale values use Title
Case instead of the repo-standard sentence case; update the English strings for
keys like paddleocrAccessToken, paddleocrAccessTokenMessage,
paddleocrAccessTokenPlaceholder (and the other keys around lines ~1792-1807 such
as Chat Channels/Add Channel/Bot Token) to sentence case (e.g., "AI Studio
access token", "Access token for PaddleOCR API (optional)", "Your AI Studio
token (optional)") so all en.ts values follow the sentence-case guideline.

Source: Coding guidelines

api/channels/slack.py-37-50 (1)

37-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate required dialog_id field.

The dialog_id is required for dispatching messages but defaults to an empty string (line 42). Add validation in __init__ or start:

if not self._dialog_id:
    raise ValueError("SlackChannel requires non-empty dialog_id in config")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/slack.py` around lines 37 - 50, The SlackChannel currently
allows an empty dialog_id which is required for message dispatch; update the
constructor (__init__) or the start method to validate self._dialog_id and raise
a ValueError when it's falsy (e.g., if not self._dialog_id: raise
ValueError("SlackChannel requires non-empty dialog_id in config")), so locate
the dialog initialization in __init__ (self._dialog_id) and add this guard
before using it in start or any other method that relies on it.
api/channels/feishu.py-44-50 (1)

44-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate required dialog_id field.

The dialog_id is required for dispatching messages but defaults to an empty string (line 50). If a channel is created without a valid dialog_id, messages will fail silently or with obscure errors during dispatch.

Add validation in __init__ or start:

if not self._dialog_id:
    raise ValueError("FeishuChannel requires non-empty dialog_id in config")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 44 - 50, Validate that self._dialog_id
is non-empty in the Feishu channel initialization (either in __init__ or in
start) and raise a clear error if missing: check self._dialog_id (from
config.get("dialog_id")) and if falsy raise ValueError("FeishuChannel requires
non-empty dialog_id in config") so the class (e.g., FeishuChannel.__init__ or
FeishuChannel.start) fails fast instead of allowing silent failures when
dispatching.
api/channels/slack.py-61-84 (1)

61-84: ⚠️ Potential issue | 🟡 Minor

Slack bot message filter should also ignore app_id (not just bot_id)
api/channels/slack.py currently drops bot messages only when bot_id is absent, but the repo’s Slack filtering logic (common/data_source/slack_connector.py) treats messages with bot_id or app_id as bot-origin. Update the handler condition to match that behavior (optionally also considering bot_profile).

if event.get("type") == "message" and not (event.get("bot_id") or event.get("app_id") or event.get("bot_profile")):
    ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/slack.py` around lines 61 - 84, Update the message-filtering
condition in the async handler _handle_event so it ignores messages originating
from bots/apps the same way as common/data_source/slack_connector.py; replace
the current check that only looks for bot_id with a predicate that skips
messages when event has bot_id or app_id or bot_profile. Locate the block that
constructs IncomingMessage and calls self._dispatch/send and modify the if
guarding that block to use not (event.get("bot_id") or event.get("app_id") or
event.get("bot_profile")) to prevent bot/app echoes from being processed.
web/src/pages/user-setting/channels/index.tsx-148-149 (1)

148-149: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize credential labels/placeholders instead of hardcoded English.

Several user-facing strings bypass i18n and will render untranslated in non-English locales.

As per coding guidelines, "Use react-i18next for internationalization supporting 17 languages."

Also applies to: 155-160, 164-170, 178-179, 185-186, 193-194

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/user-setting/channels/index.tsx` around lines 148 - 149,
Replace hardcoded English UI strings (e.g., placeholder="dialog ID" and other
input labels/placeholders around the same block) with react-i18next
translations: import and call useTranslation() to get t and replace literal
strings passed to placeholder/label props with t('key') entries (e.g.,
placeholder={t('channels.dialogId')}). Update all occurrences mentioned in the
review (the placeholder/label props near the demonstrated placeholder and the
other noted string sites) and add appropriate i18n keys in your translation
resource files so the same keys are available in supported languages.

Source: Coding guidelines

web/src/services/channel.ts-5-10 (1)

5-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only set Authorization when a token exists.

Sending Authorization: '' is avoidable and can cause inconsistent auth behavior through proxies/gateways.

Suggested fix
 function authHeaders(): HeadersInit {
+  const token = authorizationUtil.getAuthorization();
   return {
     'Content-Type': 'application/json',
-    Authorization: authorizationUtil.getAuthorization() ?? '',
+    ...(token ? { Authorization: token } : {}),
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/services/channel.ts` around lines 5 - 10, The authHeaders function
currently always sets an Authorization header (possibly to an empty string);
change authHeaders so it only adds the Authorization header when
authorizationUtil.getAuthorization() returns a non-empty value — call
authorizationUtil.getAuthorization(), check for a truthy/non-empty token, and
only include Authorization in the returned HeadersInit when present (leave
Content-Type always set). Refer to the authHeaders function and
authorizationUtil.getAuthorization() when making the change.
🧹 Nitpick comments (7)
test/unit_test/rag/svr/task_executor_refactor/test_chunk_post_processor.py (1)

198-263: ⚡ Quick win

Add required pytest priority markers to the newly added tests.

The new async test cases are missing p1/p2/p3 markers required by the repo testing convention.

As per coding guidelines, test/**/*.py: "Use pytest with priority markers (p1/p2/p3) for Python testing."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit_test/rag/svr/task_executor_refactor/test_chunk_post_processor.py`
around lines 198 - 263, Each new async test
(test_generate_metadata_empty_config_does_not_crash,
test_generate_metadata_enum_none_accepted,
test_generate_metadata_description_none_accepted,
test_generate_metadata_built_in_with_enum_none) must include the repo's pytest
priority marker; add the appropriate marker decorator (e.g., `@pytest.mark.p1`)
above each test in addition to `@pytest.mark.asyncio` so they follow the p1/p2/p3
convention used for tests that exercise generate_metadata in
chunk_post_processor; ensure the decorator is imported/available in the test
module.

Source: Coding guidelines

internal/entity/models/model.go (1)

394-401: Normalize alias2ModelIndex keys to lowercase to match GetModelByNameOrAlias (lines ~312-318, ~395-402).

GetModelByNameOrAlias lowercases the lookup key (strings.ToLower(modelName)), but alias2ModelIndex is built with raw model.Name/alias (no lowercasing on insertion). The current conf/all_models.json has no mixed-case name/alias entries (mixed_case_entries=0), so it works today; normalizing keys during map construction makes the behavior robust if config ever changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/model.go` around lines 394 - 401,
GetModelByNameOrAlias lowercases the incoming modelName but Alias2ModelIndex
keys are stored as raw model.Name/alias, causing case-mismatch bugs; when
building/populating ProviderManager.Alias2ModelIndex (the code that inserts into
Alias2ModelIndex for each Model in ProviderManager.AllModels), normalize keys to
lowercase (use strings.ToLower on model.Name and each alias) so lookups in
GetModelByNameOrAlias succeed regardless of case; update the map-insertion logic
that currently writes to Alias2ModelIndex to insert lowercase keys.
web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx (1)

70-88: 💤 Low value

Redundant error handling in outer catch block.

The outer catch block at lines 78-88 is likely unreachable. onHandleVerify already catches all errors internally (lines 49-68) and calls verifyCallback in its own catch block (lines 62-65). Unless onHandleVerify itself throws during setup, the outer catch won't execute.

Consider removing the outer try-catch or documenting why both layers are needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx`
around lines 70 - 88, The outer try-catch in handleVerify is redundant because
onHandleVerify already catches errors and calls verifyCallback; remove the outer
catch and only await onHandleVerify inside handleVerify while still setting
initial state via setVerifyResult and wrapping the setIsVerifying(false) in
finally to ensure the loading state is cleared; keep references to handleVerify,
onHandleVerify, verifyCallback, setVerifyResult, and setIsVerifying so the
caller flow and error reporting remain handled by onHandleVerify.
api/channels/feishu.py (2)

106-136: ⚖️ Poor tradeoff

Non-streaming dispatch may timeout on long-running queries.

The _dispatch method uses stream: False (line 118), which waits for the complete answer before returning. For complex queries that require extensive RAG retrieval or long LLM generation, this can exceed HTTP or application timeouts, causing the webhook to fail.

Consider enabling streaming and aggregating the response incrementally, or make streaming configurable per channel based on expected query complexity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 106 - 136, The _dispatch method
currently calls async_iframe_completion with "stream": False which can cause
timeouts for long RAG/LLM runs; change to streaming by passing "stream": True to
async_iframe_completion (or make a channel/tenant flag like
self._enable_streaming) and iterate the yielded chunks to incrementally build
answer_text, update session id from each chunk, and only return an
OutgoingMessage after aggregation completes (or send partial messages if
configured); modify _dispatch, the async_iframe_completion call, and the chunk
handling logic to support both streaming and non-streaming modes while
preserving session updates in self._sessions for incoming.sender_id.

86-90: ⚡ Quick win

Log JSON decode errors instead of silent failure.

When the Feishu message content fails to parse (lines 86-90), the handler returns {} silently. This makes debugging difficult when Feishu sends malformed payloads.

Add logging:

except (json.JSONDecodeError, AttributeError) as e:
    logger.warning("FeishuChannel: failed to parse message content: %s", e)
    return {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 86 - 90, The exception handler around
json.loads of msg.get("content", "{}") currently swallows
JSONDecodeError/AttributeError; modify the except block in the Feishu message
parsing (the code that builds content_json and text) to log the failure before
returning: catch the exception as e and call logger.warning (or the module
logger) with a descriptive message like "FeishuChannel: failed to parse message
content" including the exception e, then return {}; update the except clause
that currently does "except (json.JSONDecodeError, AttributeError): return {}"
to "except (json.JSONDecodeError, AttributeError) as e: logger.warning(...);
return {}".

Source: Coding guidelines

api/apps/restful_apis/channel_api.py (2)

91-110: ⚖️ Poor tradeoff

Consider Pydantic request validation for consistency.

As per coding guidelines, newer RESTful APIs under api/apps/restful_apis/ should use Pydantic for request validation. This endpoint uses manual validation (lines 98-110), which is functional but less maintainable and more error-prone than a declarative Pydantic schema.

Consider defining a CreateChannelRequest Pydantic model with field validators (e.g., channel: Literal["slack", "feishu"], name: str, etc.) to replace the manual checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/restful_apis/channel_api.py` around lines 91 - 110, Replace the
manual request validation in create_channel by defining a Pydantic model (e.g.,
CreateChannelRequest) with fields name: str, channel: Literal[...] (or
constraining via Enum/typing) dialog_id: str and config: dict, add any
validators for trimming/required checks, then parse/validate the request payload
instead of using get_request_json and the manual checks; after validation, use
the parsed model instance and keep the existing channel-type lookup against
CHANNEL_REGISTRY and _CHANNEL_INFO and error responses via get_error_data_result
for backward-compatible error handling.

Source: Coding guidelines


40-50: ⚖️ Poor tradeoff

Consider deriving channel schemas from the registry.

The hardcoded _CHANNEL_INFO dictionary creates tight coupling and duplication. If a new channel type is registered, this dictionary must be manually updated in lockstep. More critically, the validation at line 109 checks both CHANNEL_REGISTRY and _CHANNEL_INFO, allowing a mismatch where a channel type could be created via the API but not instantiable at runtime.

Consider adding a class method to the Channel base class (e.g., get_credential_schema()) that returns the required fields, and derive _CHANNEL_INFO dynamically from CHANNEL_REGISTRY.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/restful_apis/channel_api.py` around lines 40 - 50, The hardcoded
_CHANNEL_INFO dict is duplicated with CHANNEL_REGISTRY and can drift; change the
implementation to derive the credential schema from each registered Channel
class by adding a class method on Channel (e.g., get_credential_schema()) that
returns the list/dict of required fields, then replace _CHANNEL_INFO usage with
a dynamic build that iterates CHANNEL_REGISTRY and calls
ChannelClass.get_credential_schema() for each key, and update the API validation
logic (the code that currently checks CHANNEL_REGISTRY and _CHANNEL_INFO) to
only consult CHANNEL_REGISTRY and the new get_credential_schema() results so
creation and instantiation remain consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49e698d8-7e5c-446e-bd44-7c580aeb247b

📥 Commits

Reviewing files that changed from the base of the PR and between 2f99d52 and 8b3e317.

📒 Files selected for processing (117)
  • VERSION
  • api/apps/restful_apis/channel_api.py
  • api/apps/services/provider_api_service.py
  • api/channels/__init__.py
  • api/channels/base.py
  • api/channels/bootstrap.py
  • api/channels/feishu.py
  • api/channels/registry.py
  • api/channels/slack.py
  • api/db/db_models.py
  • api/ragflow_server.py
  • common/metadata_utils.py
  • conf/service_conf.yaml
  • conf/service_conf.yaml.template
  • entrypoint.sh
  • internal/dao/database.go
  • internal/engine/elasticsearch/chunk.go
  • internal/engine/elasticsearch/chunk_helpers_test.go
  • internal/engine/elasticsearch/chunk_test.go
  • internal/entity/models/302ai.go
  • internal/entity/models/aliyun.go
  • internal/entity/models/anthropic.go
  • internal/entity/models/astraflow.go
  • internal/entity/models/avian.go
  • internal/entity/models/azure_openai.go
  • internal/entity/models/baichuan.go
  • internal/entity/models/baidu.go
  • internal/entity/models/bedrock.go
  • internal/entity/models/builtin.go
  • internal/entity/models/cohere.go
  • internal/entity/models/cometapi.go
  • internal/entity/models/deepinfra.go
  • internal/entity/models/deepseek.go
  • internal/entity/models/dummy.go
  • internal/entity/models/fishaudio.go
  • internal/entity/models/futurmix.go
  • internal/entity/models/gitee.go
  • internal/entity/models/google.go
  • internal/entity/models/gpustack.go
  • internal/entity/models/groq.go
  • internal/entity/models/huaweicloud.go
  • internal/entity/models/huggingface.go
  • internal/entity/models/hunyuan.go
  • internal/entity/models/jiekouai.go
  • internal/entity/models/jina.go
  • internal/entity/models/lmstudio.go
  • internal/entity/models/localai.go
  • internal/entity/models/longcat.go
  • internal/entity/models/mineru.go
  • internal/entity/models/mineru_local.go
  • internal/entity/models/minimax.go
  • internal/entity/models/mistral.go
  • internal/entity/models/model.go
  • internal/entity/models/model_test.go
  • internal/entity/models/modelscope.go
  • internal/entity/models/moonshot.go
  • internal/entity/models/n1n.go
  • internal/entity/models/novita.go
  • internal/entity/models/nvidia.go
  • internal/entity/models/ollama.go
  • internal/entity/models/openai.go
  • internal/entity/models/openrouter.go
  • internal/entity/models/orcarouter.go
  • internal/entity/models/paddleocr.go
  • internal/entity/models/paddleocr_local.go
  • internal/entity/models/perplexity.go
  • internal/entity/models/ppio.go
  • internal/entity/models/qiniu.go
  • internal/entity/models/replicate.go
  • internal/entity/models/siliconflow.go
  • internal/entity/models/stepfun.go
  • internal/entity/models/togetherai.go
  • internal/entity/models/tokenhub.go
  • internal/entity/models/tokenpony.go
  • internal/entity/models/types.go
  • internal/entity/models/upstage.go
  • internal/entity/models/vllm.go
  • internal/entity/models/volcengine.go
  • internal/entity/models/voyage.go
  • internal/entity/models/xai.go
  • internal/entity/models/xiaomi.go
  • internal/entity/models/xinference.go
  • internal/entity/models/xunfei.go
  • internal/entity/models/zhipu-ai.go
  • internal/handler/auth.go
  • internal/handler/document.go
  • internal/handler/document_test.go
  • internal/handler/providers.go
  • internal/router/router.go
  • internal/service/heartbeat_sender.go
  • internal/service/model_service.go
  • rag/flow/parser/parser.py
  • rag/graphrag/search.py
  • rag/graphrag/utils.py
  • rag/llm/chat_model.py
  • rag/prompts/generator.py
  • rag/utils/ob_conn.py
  • test/unit_test/rag/graphrag/test_graphrag_utils.py
  • test/unit_test/rag/svr/task_executor_refactor/conftest.py
  • test/unit_test/rag/svr/task_executor_refactor/test_chunk_post_processor.py
  • tools/scripts/mysql_migration.py
  • web/src/components/ui/toggle-list.tsx
  • web/src/locales/en.ts
  • web/src/locales/zh.ts
  • web/src/pages/agent/constant/index.tsx
  • web/src/pages/agent/hooks/use-get-begin-query.tsx
  • web/src/pages/user-setting/channels/index.tsx
  • web/src/pages/user-setting/setting-model/modal/provider-modal/components/add-custom-model-dialog.tsx
  • web/src/pages/user-setting/setting-model/modal/provider-modal/components/addable-toggle-list.tsx
  • web/src/pages/user-setting/setting-model/modal/provider-modal/components/use-custom-model-fields.tsx
  • web/src/pages/user-setting/setting-model/modal/provider-modal/field-config/provider-config-map.ts
  • web/src/pages/user-setting/setting-model/modal/provider-modal/hooks/use-list-models-picker.ts
  • web/src/pages/user-setting/setting-model/modal/provider-modal/index.tsx
  • web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx
  • web/src/pages/user-setting/sidebar/index.tsx
  • web/src/routes.tsx
  • web/src/services/channel.ts

Comment thread api/channels/feishu.py
Comment thread api/channels/feishu.py
Comment thread api/channels/slack.py
Comment thread internal/entity/models/model_test.go Outdated
@zaviermeekz-cpu

Copy link
Copy Markdown
Contributor Author

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)

api/apps/services/provider_api_service.py (1)> 382-503: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit logs for the newly introduced verification branches.
Line 382 onward introduces new control paths (VLLM base URL normalization effects, BaiduYiyan key coercion, OCR verification). Failures are logged, but branch decisions/success-path observability is still missing, which makes production troubleshooting harder.
As per coding guidelines, “**/*.py: Add logging for new flows”.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/services/provider_api_service.py` around lines 382 - 503, The new
verification flow lacks informative info-level logs: add logging calls after
base_url is set (via _normalize_provider_base_url) and when target_factory_name
is chosen, log when factory_llms is built from FACTORY_LLM_INFOS or from
model_info, and log BaiduYiyan API key coercion (showing masked/summary
api_key_str) inside the BaiduYiyan branch; also add info/debug logs on each
verification branch success/failure (embedding via EmbeddingModel.encode, chat
via ChatModel.async_chat_streamly, rerank via RerankModel.similarity, ocr via
OcrModel.check_available) indicating provider_name, llm['llm_name'], model_type
and result so callers can observe which path passed or failed before returning.
Ensure logs avoid printing secrets (mask api keys) and use existing logger
variable (e.g., logging) and messages consistent with surrounding logs.

Source: Coding guidelines

internal/entity/models/model.go (1)> 516-523: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard optional MaxTokens before dereferencing.
Model.MaxTokens is now nullable, but this branch still dereferences it unconditionally and then formats the pointer itself in the error message. A max_tokens filter against a model without that field will panic instead of returning a 4xx response.

Suggested fix

 		case "max_tokens":
 			if maxVal, ok := filterValue.(int); ok {
-				if *model.MaxTokens < maxVal {
+				if model.MaxTokens == nil {
+					matchFilter = false
+					resp.Code = 400
+					resp.Message = "Model does not define max_tokens"
+					break
+				}
+				if *model.MaxTokens < maxVal {
 					matchFilter = false
 					resp.Code = 400
 					resp.Message = fmt.Sprintf("Model does not meet filter criteria: max_tokens (%d) < %d",
-						model.MaxTokens, maxVal)
+						*model.MaxTokens, maxVal)
 				}
 			}

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/model.go` around lines 516 - 523, In the "max_tokens"
filter branch, avoid unguarded dereference of the nullable Model.MaxTokens
pointer: first assert filterValue is an int, then check if model.MaxTokens ==
nil and if so set matchFilter = false and resp.Code/resp.Message to a 4xx error
indicating the model has no max_tokens (do not dereference); only when
model.MaxTokens != nil perform the comparison using *model.MaxTokens and format
the error message with the dereferenced value. Ensure you reference the "case
\"max_tokens\"" branch, the model.MaxTokens field, matchFilter, resp.Code and
resp.Message when applying this guard and updating messages.

internal/entity/models/deepinfra.go (1)> 843-867: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore credential validation in ListModels.
CheckConnection on Line 954 delegates to this method, but this implementation skips APIConfigCheck and never sends the Bearer token. That makes the health check unauthenticated: a public /models endpoint can incorrectly accept missing/invalid keys, and an authenticated one will fail even with a valid key.

Suggested fix

 func (d *DeepInfraModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+	if err := d.baseModel.APIConfigCheck(apiConfig); err != nil {
+		return nil, err
+	}
 
 	resolvedBaseURL, err := d.baseModel.GetBaseURL(apiConfig)
 	if err != nil {
 		return nil, err
@@
-	req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(jsonData))
+	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
 
 	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/deepinfra.go` around lines 843 - 867, The ListModels
method in DeepInfraModel skips APIConfig validation and never sets the
Authorization header, causing unauthenticated or failed health checks; restore
the APIConfigCheck call (use the existing APIConfigCheck /
d.baseModel.APIConfigCheck utility used elsewhere) at the start of ListModels to
validate apiConfig and extract the token, and set the Bearer token on the
http.Request (Authorization: Bearer <token>) before sending; also remove or
avoid sending an unnecessary JSON body for the GET (use nil body) so the request
matches the service expectation and CheckConnection (which calls ListModels)
performs an authenticated health check.

internal/entity/models/jina.go (1)> 321-335: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Authenticate ListModels the same way as the other Jina calls.
CheckConnection on Line 372 uses this method, but ListModels no longer validates apiConfig and never sends the Bearer token. That disconnects verification from the supplied credentials, so a public /models endpoint can falsely pass bad keys and an authenticated one will fail even when chat/embedding requests succeed.

Suggested fix

 func (j *JinaModel) ListModels(apiConfig *APIConfig) ([]ListModelResponse, error) {
+	if err := j.baseModel.APIConfigCheck(apiConfig); err != nil {
+		return nil, err
+	}
 
 	resolvedBaseURL, err := j.baseModel.GetBaseURL(apiConfig)
 	if err != nil {
 		return nil, err
@@
 	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/jina.go` around lines 321 - 335, The ListModels method
(JinaModel.ListModels) currently skips validating apiConfig and does not set the
Authorization header, unlike other Jina calls such as CheckConnection; update
ListModels to validate the provided apiConfig the same way other methods do and
add the Bearer token to the request headers (e.g., set "Authorization: Bearer
<token>" from apiConfig) before sending the GET to j.baseModel.GetBaseURL(...) +
j.baseModel.URLSuffix.Models so the call is authenticated consistently with
chat/embedding requests.

web/src/components/ui/toggle-list.tsx (1)> 1-343: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Modifications to src/components/ui/ require explicit approval.
This file is under src/components/ui/, which is protected by coding guidelines. All modifications—including the new footer prop, search filter changes, styling updates, and footer rendering logic—require explicit user approval before merge.
As per coding guidelines, if ToggleList doesn't meet requirements, wrap or compose it in a new component outside src/components/ui/ instead.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/ui/toggle-list.tsx` around lines 1 - 343, The PR modifies
a protected UI file (ToggleList) under src/components/ui/ without explicit
approval; either revert those edits or move the new behavior into a new
component outside that folder and leave ToggleList untouched. Specifically,
remove the newly added footer prop/logic (footer, footerClassName, footerNode
rendering) and any search/filter styling changes in ToggleList, or create a
composed wrapper component (e.g., NewToggleListWrapper) outside
src/components/ui/ that imports the original ToggleList and implements the
footer, API search (onSearch), and loadMore behaviors there; ensure references
to ToggleList, footer/footerNode, onSearch, and filteredOptions are adjusted
accordingly and get explicit approval before modifying files under
src/components/ui/.

Source: Coding guidelines

🟡 Minor comments (9)

internal/handler/document.go-1013-1016 (1)> 1013-1016: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize doc_ids query tokens before calling the service.
Raw strings.Split can pass whitespace/empty IDs (e.g., trailing commas), which causes avoidable lookup mismatches downstream.

Proposed fix

 	var docIDS []string
 	if docIDsParam := c.Query("doc_ids"); docIDsParam != "" {
-		docIDS = strings.Split(docIDsParam, ",")
+		for _, id := range strings.Split(docIDsParam, ",") {
+			id = strings.TrimSpace(id)
+			if id != "" {
+				docIDS = append(docIDS, id)
+			}
+		}
 	}

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/handler/document.go` around lines 1013 - 1016, The code reading the
doc_ids query uses strings.Split directly into docIDS (variable) which can leave
whitespace and empty tokens; update the parsing so that after c.Query("doc_ids")
is non-empty you split, then for each token apply strings.TrimSpace and only
append non-empty values to docIDS (thus removing tokens from trailing/leading
commas and whitespace) before passing docIDS to the downstream service (refer to
the docIDS variable, the c.Query("doc_ids") call, and the current strings.Split
usage).

rag/graphrag/utils.py-707-721 (1)> 707-721: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle non-positive n_hop explicitly.
With n_hop=0 (or negative), this function still emits 1-hop paths. Add an early return to keep behavior consistent with the documented contract.

💡 Suggested fix

 def n_neighbor(graph: nx.Graph, node, n_hop: int = 2):
+    if n_hop <= 0:
+        return []
+
     """Enumerate paths of up to ``n_hop`` edges starting at ``node`` together

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag/graphrag/utils.py` around lines 707 - 721, The n_neighbor function
currently emits 1-hop paths even when n_hop is 0 or negative; add an explicit
early return in n_neighbor to handle non-positive n_hop (e.g., if n_hop <= 0:
return []), so the function honors the documented "up to n_hop edges" contract;
locate the function n_neighbor in rag/graphrag/utils.py and add the check before
any graph.edges/node traversal and counting logic.

internal/entity/models/gitee.go-902-917 (1)> 902-917: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve model metadata lookup after final name normalization.
GetModelByNameOrAlias runs before the @OwnedBy suffix is applied. For provider-qualified models, this can attach incorrect (or empty) metadata in the returned ListModelResponse.

Suggested fix

-	for _, model := range modelList.Models {
-		modelName := model.ID
-		var modelResponse ListModelResponse
-		pm := GetProviderManager()
-		modelEntity := pm.GetModelByNameOrAlias(modelName)
-		if model.OwnedBy != "" {
-			modelName = model.ID + "@" + model.OwnedBy
-		}
-		modelResponse.Name = modelName
+	for _, model := range modelList.Models {
+		rawName := model.ID
+		modelName := rawName
+		if model.OwnedBy != "" {
+			modelName = model.ID + "@" + model.OwnedBy
+		}
+		modelResponse := ListModelResponse{Name: modelName}
+		pm := GetProviderManager()
+		var modelEntity *Model
+		if pm != nil {
+			modelEntity = pm.GetModelByNameOrAlias(modelName)
+			if modelEntity == nil && modelName != rawName {
+				modelEntity = pm.GetModelByNameOrAlias(rawName)
+			}
+		}
 		if modelEntity != nil {
 			modelResponse.Dimension = modelEntity.Dimension
 			modelResponse.MaxTokens = modelEntity.MaxTokens
 			modelResponse.ModelTypes = modelEntity.ModelTypes
 			modelResponse.Thinking = modelEntity.Thinking
 		}
 
 		models = append(models, modelResponse)
 	}

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/gitee.go` around lines 902 - 917, The code calls
GetProviderManager().GetModelByNameOrAlias using modelName before the
provider-qualified suffix is appended, causing wrong metadata; update the logic
in the loop so that the modelName is normalized first (append "@" +
model.OwnedBy when model.OwnedBy != "") and then call GetModelByNameOrAlias with
the final modelName to populate ListModelResponse fields (Dimension, MaxTokens,
ModelTypes, Thinking) from modelEntity; adjust references in the loop around
modelName, model.OwnedBy, GetProviderManager(), GetModelByNameOrAlias and
ListModelResponse accordingly.

web/src/locales/en.ts-1559-1561 (1)> 1559-1561: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use sentence case for the new English locale strings.
Several new en.ts labels are Title Case ("AI Studio Access Token", "Chat Channels", "Add Channel", "Bot Token", etc.), which conflicts with the repo’s sentence-case rule for en.ts.

Suggested copy updates

-      paddleocrAccessToken: 'AI Studio Access Token',
+      paddleocrAccessToken: 'AI Studio access token',
@@
-      channels: 'Chat Channels',
-      chatChannels: 'Chat Channels',
-      addChannel: 'Add Channel',
-      editChannel: 'Edit Channel',
+      channels: 'Chat channels',
+      chatChannels: 'Chat channels',
+      addChannel: 'Add channel',
+      editChannel: 'Edit channel',
@@
-        bot_token: 'Bot Token',
-        app_token: 'App Token',
+        bot_token: 'Bot token',
+        app_token: 'App token',
         app_id: 'App ID',
-        app_secret: 'App Secret',
-        encrypt_key: 'Encryption Key',
+        app_secret: 'App secret',
+        encrypt_key: 'Encryption key',

As per coding guidelines, web/src/locales/en.ts should use sentence case for English locale values.
Also applies to: 1792-1807

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/locales/en.ts` around lines 1559 - 1561, The locale values use Title
Case instead of the repo-standard sentence case; update the English strings for
keys like paddleocrAccessToken, paddleocrAccessTokenMessage,
paddleocrAccessTokenPlaceholder (and the other keys around lines ~1792-1807 such
as Chat Channels/Add Channel/Bot Token) to sentence case (e.g., "AI Studio
access token", "Access token for PaddleOCR API (optional)", "Your AI Studio
token (optional)") so all en.ts values follow the sentence-case guideline.

Source: Coding guidelines

api/channels/slack.py-37-50 (1)> 37-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate required dialog_id field.
The dialog_id is required for dispatching messages but defaults to an empty string (line 42). Add validation in __init__ or start:

if not self._dialog_id:
    raise ValueError("SlackChannel requires non-empty dialog_id in config")

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/slack.py` around lines 37 - 50, The SlackChannel currently
allows an empty dialog_id which is required for message dispatch; update the
constructor (__init__) or the start method to validate self._dialog_id and raise
a ValueError when it's falsy (e.g., if not self._dialog_id: raise
ValueError("SlackChannel requires non-empty dialog_id in config")), so locate
the dialog initialization in __init__ (self._dialog_id) and add this guard
before using it in start or any other method that relies on it.

api/channels/feishu.py-44-50 (1)> 44-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate required dialog_id field.
The dialog_id is required for dispatching messages but defaults to an empty string (line 50). If a channel is created without a valid dialog_id, messages will fail silently or with obscure errors during dispatch.
Add validation in __init__ or start:

if not self._dialog_id:
    raise ValueError("FeishuChannel requires non-empty dialog_id in config")

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 44 - 50, Validate that self._dialog_id
is non-empty in the Feishu channel initialization (either in __init__ or in
start) and raise a clear error if missing: check self._dialog_id (from
config.get("dialog_id")) and if falsy raise ValueError("FeishuChannel requires
non-empty dialog_id in config") so the class (e.g., FeishuChannel.__init__ or
FeishuChannel.start) fails fast instead of allowing silent failures when
dispatching.

api/channels/slack.py-61-84 (1)> 61-84: ⚠️ Potential issue | 🟡 Minor

Slack bot message filter should also ignore app_id (not just bot_id)
api/channels/slack.py currently drops bot messages only when bot_id is absent, but the repo’s Slack filtering logic (common/data_source/slack_connector.py) treats messages with bot_id or app_id as bot-origin. Update the handler condition to match that behavior (optionally also considering bot_profile).

if event.get("type") == "message" and not (event.get("bot_id") or event.get("app_id") or event.get("bot_profile")):
    ...

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/slack.py` around lines 61 - 84, Update the message-filtering
condition in the async handler _handle_event so it ignores messages originating
from bots/apps the same way as common/data_source/slack_connector.py; replace
the current check that only looks for bot_id with a predicate that skips
messages when event has bot_id or app_id or bot_profile. Locate the block that
constructs IncomingMessage and calls self._dispatch/send and modify the if
guarding that block to use not (event.get("bot_id") or event.get("app_id") or
event.get("bot_profile")) to prevent bot/app echoes from being processed.

web/src/pages/user-setting/channels/index.tsx-148-149 (1)> 148-149: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize credential labels/placeholders instead of hardcoded English.
Several user-facing strings bypass i18n and will render untranslated in non-English locales.
As per coding guidelines, "Use react-i18next for internationalization supporting 17 languages."
Also applies to: 155-160, 164-170, 178-179, 185-186, 193-194

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/user-setting/channels/index.tsx` around lines 148 - 149,
Replace hardcoded English UI strings (e.g., placeholder="dialog ID" and other
input labels/placeholders around the same block) with react-i18next
translations: import and call useTranslation() to get t and replace literal
strings passed to placeholder/label props with t('key') entries (e.g.,
placeholder={t('channels.dialogId')}). Update all occurrences mentioned in the
review (the placeholder/label props near the demonstrated placeholder and the
other noted string sites) and add appropriate i18n keys in your translation
resource files so the same keys are available in supported languages.

Source: Coding guidelines

web/src/services/channel.ts-5-10 (1)> 5-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Only set Authorization when a token exists.
Sending Authorization: '' is avoidable and can cause inconsistent auth behavior through proxies/gateways.

Suggested fix

 function authHeaders(): HeadersInit {
+  const token = authorizationUtil.getAuthorization();
   return {
     'Content-Type': 'application/json',
-    Authorization: authorizationUtil.getAuthorization() ?? '',
+    ...(token ? { Authorization: token } : {}),
   };
 }

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/services/channel.ts` around lines 5 - 10, The authHeaders function
currently always sets an Authorization header (possibly to an empty string);
change authHeaders so it only adds the Authorization header when
authorizationUtil.getAuthorization() returns a non-empty value — call
authorizationUtil.getAuthorization(), check for a truthy/non-empty token, and
only include Authorization in the returned HeadersInit when present (leave
Content-Type always set). Refer to the authHeaders function and
authorizationUtil.getAuthorization() when making the change.

🧹 Nitpick comments (7)

test/unit_test/rag/svr/task_executor_refactor/test_chunk_post_processor.py (1)> 198-263: ⚡ Quick win

Add required pytest priority markers to the newly added tests.
The new async test cases are missing p1/p2/p3 markers required by the repo testing convention.
As per coding guidelines, test/**/*.py: "Use pytest with priority markers (p1/p2/p3) for Python testing."

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit_test/rag/svr/task_executor_refactor/test_chunk_post_processor.py`
around lines 198 - 263, Each new async test
(test_generate_metadata_empty_config_does_not_crash,
test_generate_metadata_enum_none_accepted,
test_generate_metadata_description_none_accepted,
test_generate_metadata_built_in_with_enum_none) must include the repo's pytest
priority marker; add the appropriate marker decorator (e.g., `@pytest.mark.p1`)
above each test in addition to `@pytest.mark.asyncio` so they follow the p1/p2/p3
convention used for tests that exercise generate_metadata in
chunk_post_processor; ensure the decorator is imported/available in the test
module.

Source: Coding guidelines

internal/entity/models/model.go (1)> 394-401: Normalize alias2ModelIndex keys to lowercase to match GetModelByNameOrAlias (lines ~312-318, ~395-402).

GetModelByNameOrAlias lowercases the lookup key (strings.ToLower(modelName)), but alias2ModelIndex is built with raw model.Name/alias (no lowercasing on insertion). The current conf/all_models.json has no mixed-case name/alias entries (mixed_case_entries=0), so it works today; normalizing keys during map construction makes the behavior robust if config ever changes.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/entity/models/model.go` around lines 394 - 401,
GetModelByNameOrAlias lowercases the incoming modelName but Alias2ModelIndex
keys are stored as raw model.Name/alias, causing case-mismatch bugs; when
building/populating ProviderManager.Alias2ModelIndex (the code that inserts into
Alias2ModelIndex for each Model in ProviderManager.AllModels), normalize keys to
lowercase (use strings.ToLower on model.Name and each alias) so lookups in
GetModelByNameOrAlias succeed regardless of case; update the map-insertion logic
that currently writes to Alias2ModelIndex to insert lowercase keys.

web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx (1)> 70-88: 💤 Low value

Redundant error handling in outer catch block.
The outer catch block at lines 78-88 is likely unreachable. onHandleVerify already catches all errors internally (lines 49-68) and calls verifyCallback in its own catch block (lines 62-65). Unless onHandleVerify itself throws during setup, the outer catch won't execute.
Consider removing the outer try-catch or documenting why both layers are needed.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/pages/user-setting/setting-model/modal/verify-button/index.tsx`
around lines 70 - 88, The outer try-catch in handleVerify is redundant because
onHandleVerify already catches errors and calls verifyCallback; remove the outer
catch and only await onHandleVerify inside handleVerify while still setting
initial state via setVerifyResult and wrapping the setIsVerifying(false) in
finally to ensure the loading state is cleared; keep references to handleVerify,
onHandleVerify, verifyCallback, setVerifyResult, and setIsVerifying so the
caller flow and error reporting remain handled by onHandleVerify.

api/channels/feishu.py (2)> 106-136: ⚖️ Poor tradeoff

Non-streaming dispatch may timeout on long-running queries.
The _dispatch method uses stream: False (line 118), which waits for the complete answer before returning. For complex queries that require extensive RAG retrieval or long LLM generation, this can exceed HTTP or application timeouts, causing the webhook to fail.
Consider enabling streaming and aggregating the response incrementally, or make streaming configurable per channel based on expected query complexity.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 106 - 136, The _dispatch method
currently calls async_iframe_completion with "stream": False which can cause
timeouts for long RAG/LLM runs; change to streaming by passing "stream": True to
async_iframe_completion (or make a channel/tenant flag like
self._enable_streaming) and iterate the yielded chunks to incrementally build
answer_text, update session id from each chunk, and only return an
OutgoingMessage after aggregation completes (or send partial messages if
configured); modify _dispatch, the async_iframe_completion call, and the chunk
handling logic to support both streaming and non-streaming modes while
preserving session updates in self._sessions for incoming.sender_id.

86-90: ⚡ Quick win
Log JSON decode errors instead of silent failure.
When the Feishu message content fails to parse (lines 86-90), the handler returns {} silently. This makes debugging difficult when Feishu sends malformed payloads.
Add logging:

except (json.JSONDecodeError, AttributeError) as e:
    logger.warning("FeishuChannel: failed to parse message content: %s", e)
    return {}

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/feishu.py` around lines 86 - 90, The exception handler around
json.loads of msg.get("content", "{}") currently swallows
JSONDecodeError/AttributeError; modify the except block in the Feishu message
parsing (the code that builds content_json and text) to log the failure before
returning: catch the exception as e and call logger.warning (or the module
logger) with a descriptive message like "FeishuChannel: failed to parse message
content" including the exception e, then return {}; update the except clause
that currently does "except (json.JSONDecodeError, AttributeError): return {}"
to "except (json.JSONDecodeError, AttributeError) as e: logger.warning(...);
return {}".

Source: Coding guidelines

api/apps/restful_apis/channel_api.py (2)> 91-110: ⚖️ Poor tradeoff

Consider Pydantic request validation for consistency.
As per coding guidelines, newer RESTful APIs under api/apps/restful_apis/ should use Pydantic for request validation. This endpoint uses manual validation (lines 98-110), which is functional but less maintainable and more error-prone than a declarative Pydantic schema.
Consider defining a CreateChannelRequest Pydantic model with field validators (e.g., channel: Literal["slack", "feishu"], name: str, etc.) to replace the manual checks.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/restful_apis/channel_api.py` around lines 91 - 110, Replace the
manual request validation in create_channel by defining a Pydantic model (e.g.,
CreateChannelRequest) with fields name: str, channel: Literal[...] (or
constraining via Enum/typing) dialog_id: str and config: dict, add any
validators for trimming/required checks, then parse/validate the request payload
instead of using get_request_json and the manual checks; after validation, use
the parsed model instance and keep the existing channel-type lookup against
CHANNEL_REGISTRY and _CHANNEL_INFO and error responses via get_error_data_result
for backward-compatible error handling.

Source: Coding guidelines

40-50: ⚖️ Poor tradeoff
Consider deriving channel schemas from the registry.
The hardcoded _CHANNEL_INFO dictionary creates tight coupling and duplication. If a new channel type is registered, this dictionary must be manually updated in lockstep. More critically, the validation at line 109 checks both CHANNEL_REGISTRY and _CHANNEL_INFO, allowing a mismatch where a channel type could be created via the API but not instantiable at runtime.
Consider adding a class method to the Channel base class (e.g., get_credential_schema()) that returns the required fields, and derive _CHANNEL_INFO dynamically from CHANNEL_REGISTRY.

🤖 Prompt for AI Agents

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/apps/restful_apis/channel_api.py` around lines 40 - 50, The hardcoded
_CHANNEL_INFO dict is duplicated with CHANNEL_REGISTRY and can drift; change the
implementation to derive the credential schema from each registered Channel
class by adding a class method on Channel (e.g., get_credential_schema()) that
returns the list/dict of required fields, then replace _CHANNEL_INFO usage with
a dynamic build that iterates CHANNEL_REGISTRY and calls
ChannelClass.get_credential_schema() for each key, and update the API validation
logic (the code that currently checks CHANNEL_REGISTRY and _CHANNEL_INFO) to
only consult CHANNEL_REGISTRY and the new get_credential_schema() results so
creation and instantiation remain consistent.

ℹ️ Review info

@coderabbitai Thank you for the detailed review. The critical issues (credential nesting, Feishu token expiry, Go test compilation) were fixed in commit 425908ab2. Optional improvements (dialog validation, sentence case, Slack bot filter) were added in 470142872. I believe the PR is now ready for human review. Your feedback is much appreciated.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@zaviermeekz-cpu Thank you for the thorough follow-up! Great to hear both the critical fixes and the optional improvements have been committed. The work done looks solid:

  • Critical fixes (425908ab2): credential nesting, Feishu token expiry handling, Go test compilation — these were the most important to land before maintainer review.
  • Optional improvements (470142872): dialog_id validation in both SlackChannel and FeishuChannel, sentence-case locale strings, and the expanded Slack bot-message filter (bot_id + app_id + bot_profile) — nice to have these in too.

The remaining open items from the review (Pydantic validation for create_channel, deriving _CHANNEL_INFO dynamically from CHANNEL_REGISTRY, streaming dispatch for Feishu, i18n for hardcoded placeholders in channels/index.tsx) are all marked as "poor tradeoff" or "nitpick" quality, so they're safe to defer to a follow-up PR once the maintainers weigh in on the overall design.

Good luck with the human review — the PR is in good shape! 🐇✨

@xugangqiang

Copy link
Copy Markdown
Collaborator

@zaviermeekz-cpu
Could you pls help to:

  1. resolve conflict
  2. add unit test/integration test
  3. conduct real testing and share testing evidence?
    Thank you.

@zaviermeekz-cpu

Copy link
Copy Markdown
Contributor Author

@xugangqiang The PR is ready for review. I have resolved all conflicts and added unit tests. Live testing with a real Slack bot was partially successful (channel starts, logs confirm). Due to Slack API setup complexity, I could not complete the full message round‑trip, but the framework is fully implemented and follows the same pattern as Feishu. If possible, please help verify the final Slack integration. Thank you.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/ragflow_server.py (1)

36-36: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Channel startup uses a stale API and creates conflicting runtimes.

Line 36 imports reconcile_channels, but api/channels/bootstrap.py in this PR exposes run_channels/start_channel_server; this can fail startup with ImportError. Also, Lines 137-141 start a reconcile task while Lines 150-170 start a separate channel server thread, which risks duplicate channel loops when both paths are active. Keep one startup path (preferably start_channel_server(stop_event)) and remove stale reconcile imports/hooks.

Also applies to: 137-141, 150-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/ragflow_server.py` at line 36, The file still imports and invokes the
stale reconcile_channels path; remove the import "reconcile_channels" and any
startup hook that calls it (search for reconcile_channels and the task started
at lines ~137-141) and instead use the single canonical channel startup path
start_channel_server(stop_event) (or run_channels if code prefers) — keep only
one of start_channel_server(stop_event) or run_channels, remove the alternate
thread/task creation (the block around lines ~150-170) to avoid duplicate
channel loops, and update the import to import start_channel_server (or
run_channels) from api.channels.bootstrap so the module uses the current API.
Ensure the chosen startup is passed the existing stop_event and no reconcile
task remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/channels/bootstrap.py`:
- Around line 153-156: The except block currently sets answer_text =
f"**ERROR**: {ex}", which leaks internal exception details to users; instead
keep LOGGER.exception("[%s:%s] completion failed", ch.channel_id, ch.account_id,
exc_info=True) (or rely on the existing LOGGER.exception call) and replace the
user-facing answer_text assignment (in the same except block) with a generic
message such as "**ERROR**: An internal error occurred while processing your
request. Please try again later." to avoid exposing sensitive/internal exception
content.
- Line 37: The bundled channels tuple _BUNDLED_CHANNELS is missing "slack", so
_register_channels() never imports Slack and its builder won't self-register;
add the string "slack" to the _BUNDLED_CHANNELS tuple and also update any other
channel-enumeration/list in the same module (the import/registration block that
mirrors _BUNDLED_CHANNELS) so Slack is imported during _register_channels() and
can register its builder.
- Around line 102-103: The code is calling DialogService.async_chat directly;
update this channel flow to use the unified LLMBundle from
api.db.services.llm_service instead. Replace imports of DialogService/async_chat
with an import of LLMBundle, instantiate or retrieve the appropriate LLMBundle
instance used elsewhere in the app, and route all LLM calls (the places
referencing DialogService.async_chat around the current block and the similar
section at lines ~148-152) through the LLMBundle API (e.g., the bundle's async
generate/chat method) while preserving the same input/output handling and using
get_uuid and existing context objects as before.
- Around line 226-230: The current loop that drops entries from the failed dict
permanently skips retries for unchanged configs (failed, desired, account_id,
failed[account_id]) — replace the binary failed mapping with a retry-aware
structure (e.g., store failure metadata like failure_hash, last_failed_at
timestamp and attempt_count instead of just fp) and update the loop that checks
desired vs failed to allow timed retries/backoff: when desired[account_id][2] ==
failed[account_id].failure_hash, consult last_failed_at and attempt_count to
decide whether to re-enqueue/retry the start (exponential or fixed backoff) and
only purge on config change or after success; ensure functions that set failures
(where failed[account_id] = fp currently) populate the metadata fields and
increment attempt_count so the bootstrap loop can honor retry schedules.

In `@api/db/db_models.py`:
- Around line 1218-1232: Remove the duplicate ChatChannel class and keep a
single authoritative definition for ChatChannel with dialog_id nullable
(default=None) and status defaulting to "1"; update the remaining ChatChannel
model (the one referenced by ChatChannelService.list_active()) so its field
types/defaults match the migration contract and ensure the migration/schema
entry for ChatChannel reflects dialog_id nullable and status default "1"; then
run/adjust migrations and tests to confirm the service filter status == "1" will
correctly find active channels.

---

Outside diff comments:
In `@api/ragflow_server.py`:
- Line 36: The file still imports and invokes the stale reconcile_channels path;
remove the import "reconcile_channels" and any startup hook that calls it
(search for reconcile_channels and the task started at lines ~137-141) and
instead use the single canonical channel startup path
start_channel_server(stop_event) (or run_channels if code prefers) — keep only
one of start_channel_server(stop_event) or run_channels, remove the alternate
thread/task creation (the block around lines ~150-170) to avoid duplicate
channel loops, and update the import to import start_channel_server (or
run_channels) from api.channels.bootstrap so the module uses the current API.
Ensure the chosen startup is passed the existing stop_event and no reconcile
task remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df3ac963-abe1-4440-9ca1-42acf9458f5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4701428 and b0ce3b1.

📒 Files selected for processing (4)
  • api/channels/bootstrap.py
  • api/db/db_models.py
  • api/ragflow_server.py
  • conf/service_conf.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • conf/service_conf.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/ragflow_server.py (1)

36-36: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Channel startup uses a stale API and creates conflicting runtimes.

Line 36 imports reconcile_channels, but api/channels/bootstrap.py in this PR exposes run_channels/start_channel_server; this can fail startup with ImportError. Also, Lines 137-141 start a reconcile task while Lines 150-170 start a separate channel server thread, which risks duplicate channel loops when both paths are active. Keep one startup path (preferably start_channel_server(stop_event)) and remove stale reconcile imports/hooks.

Also applies to: 137-141, 150-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/ragflow_server.py` at line 36, The file still imports and invokes the
stale reconcile_channels path; remove the import "reconcile_channels" and any
startup hook that calls it (search for reconcile_channels and the task started
at lines ~137-141) and instead use the single canonical channel startup path
start_channel_server(stop_event) (or run_channels if code prefers) — keep only
one of start_channel_server(stop_event) or run_channels, remove the alternate
thread/task creation (the block around lines ~150-170) to avoid duplicate
channel loops, and update the import to import start_channel_server (or
run_channels) from api.channels.bootstrap so the module uses the current API.
Ensure the chosen startup is passed the existing stop_event and no reconcile
task remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/channels/bootstrap.py`:
- Around line 153-156: The except block currently sets answer_text =
f"**ERROR**: {ex}", which leaks internal exception details to users; instead
keep LOGGER.exception("[%s:%s] completion failed", ch.channel_id, ch.account_id,
exc_info=True) (or rely on the existing LOGGER.exception call) and replace the
user-facing answer_text assignment (in the same except block) with a generic
message such as "**ERROR**: An internal error occurred while processing your
request. Please try again later." to avoid exposing sensitive/internal exception
content.
- Line 37: The bundled channels tuple _BUNDLED_CHANNELS is missing "slack", so
_register_channels() never imports Slack and its builder won't self-register;
add the string "slack" to the _BUNDLED_CHANNELS tuple and also update any other
channel-enumeration/list in the same module (the import/registration block that
mirrors _BUNDLED_CHANNELS) so Slack is imported during _register_channels() and
can register its builder.
- Around line 102-103: The code is calling DialogService.async_chat directly;
update this channel flow to use the unified LLMBundle from
api.db.services.llm_service instead. Replace imports of DialogService/async_chat
with an import of LLMBundle, instantiate or retrieve the appropriate LLMBundle
instance used elsewhere in the app, and route all LLM calls (the places
referencing DialogService.async_chat around the current block and the similar
section at lines ~148-152) through the LLMBundle API (e.g., the bundle's async
generate/chat method) while preserving the same input/output handling and using
get_uuid and existing context objects as before.
- Around line 226-230: The current loop that drops entries from the failed dict
permanently skips retries for unchanged configs (failed, desired, account_id,
failed[account_id]) — replace the binary failed mapping with a retry-aware
structure (e.g., store failure metadata like failure_hash, last_failed_at
timestamp and attempt_count instead of just fp) and update the loop that checks
desired vs failed to allow timed retries/backoff: when desired[account_id][2] ==
failed[account_id].failure_hash, consult last_failed_at and attempt_count to
decide whether to re-enqueue/retry the start (exponential or fixed backoff) and
only purge on config change or after success; ensure functions that set failures
(where failed[account_id] = fp currently) populate the metadata fields and
increment attempt_count so the bootstrap loop can honor retry schedules.

In `@api/db/db_models.py`:
- Around line 1218-1232: Remove the duplicate ChatChannel class and keep a
single authoritative definition for ChatChannel with dialog_id nullable
(default=None) and status defaulting to "1"; update the remaining ChatChannel
model (the one referenced by ChatChannelService.list_active()) so its field
types/defaults match the migration contract and ensure the migration/schema
entry for ChatChannel reflects dialog_id nullable and status default "1"; then
run/adjust migrations and tests to confirm the service filter status == "1" will
correctly find active channels.

---

Outside diff comments:
In `@api/ragflow_server.py`:
- Line 36: The file still imports and invokes the stale reconcile_channels path;
remove the import "reconcile_channels" and any startup hook that calls it
(search for reconcile_channels and the task started at lines ~137-141) and
instead use the single canonical channel startup path
start_channel_server(stop_event) (or run_channels if code prefers) — keep only
one of start_channel_server(stop_event) or run_channels, remove the alternate
thread/task creation (the block around lines ~150-170) to avoid duplicate
channel loops, and update the import to import start_channel_server (or
run_channels) from api.channels.bootstrap so the module uses the current API.
Ensure the chosen startup is passed the existing stop_event and no reconcile
task remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df3ac963-abe1-4440-9ca1-42acf9458f5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4701428 and b0ce3b1.

📒 Files selected for processing (4)
  • api/channels/bootstrap.py
  • api/db/db_models.py
  • api/ragflow_server.py
  • conf/service_conf.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • conf/service_conf.yaml
🛑 Comments failed to post (5)
api/channels/bootstrap.py (4)

37-37: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

slack is missing from bundled channel registration.

Line 37 omits slack, but _register_channels() only imports modules listed in _BUNDLED_CHANNELS. If Slack isn’t imported elsewhere, its builder never self-registers and Slack channels won’t start.

Also applies to: 43-54

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/bootstrap.py` at line 37, The bundled channels tuple
_BUNDLED_CHANNELS is missing "slack", so _register_channels() never imports
Slack and its builder won't self-register; add the string "slack" to the
_BUNDLED_CHANNELS tuple and also update any other channel-enumeration/list in
the same module (the import/registration block that mirrors _BUNDLED_CHANNELS)
so Slack is imported during _register_channels() and can register its builder.

102-103: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Use LLMBundle as the unified LLM interface in this new channel flow.

Line 102 routes generation through DialogService.async_chat directly. This new LLM interaction path should be wired through LLMBundle for consistency with backend policy.
As per coding guidelines, **/*.py: Use LLMBundle from api.db.services.llm_service as the unified interface for LLM interactions.

Also applies to: 148-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/bootstrap.py` around lines 102 - 103, The code is calling
DialogService.async_chat directly; update this channel flow to use the unified
LLMBundle from api.db.services.llm_service instead. Replace imports of
DialogService/async_chat with an import of LLMBundle, instantiate or retrieve
the appropriate LLMBundle instance used elsewhere in the app, and route all LLM
calls (the places referencing DialogService.async_chat around the current block
and the similar section at lines ~148-152) through the LLMBundle API (e.g., the
bundle's async generate/chat method) while preserving the same input/output
handling and using get_uuid and existing context objects as before.

Source: Coding guidelines


153-156: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose raw exception messages to end users.

Line 155 sends ex back to chat users ("**ERROR**: {ex}"), which can leak internal details or sensitive backend context. Return a generic failure message and keep detailed diagnostics in logs only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/bootstrap.py` around lines 153 - 156, The except block currently
sets answer_text = f"**ERROR**: {ex}", which leaks internal exception details to
users; instead keep LOGGER.exception("[%s:%s] completion failed", ch.channel_id,
ch.account_id, exc_info=True) (or rely on the existing LOGGER.exception call)
and replace the user-facing answer_text assignment (in the same except block)
with a generic message such as "**ERROR**: An internal error occurred while
processing your request. Please try again later." to avoid exposing
sensitive/internal exception content.

226-230: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Failed channel starts are suppressed indefinitely for unchanged configs.

Once failed[account_id] = fp is set, unchanged configs are skipped forever (Lines 234-235). A transient outage during startup can keep a bot down permanently until manual config edits. Add timed retry/backoff instead of permanent suppression.

Also applies to: 233-237

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/channels/bootstrap.py` around lines 226 - 230, The current loop that
drops entries from the failed dict permanently skips retries for unchanged
configs (failed, desired, account_id, failed[account_id]) — replace the binary
failed mapping with a retry-aware structure (e.g., store failure metadata like
failure_hash, last_failed_at timestamp and attempt_count instead of just fp) and
update the loop that checks desired vs failed to allow timed retries/backoff:
when desired[account_id][2] == failed[account_id].failure_hash, consult
last_failed_at and attempt_count to decide whether to re-enqueue/retry the start
(exponential or fixed backoff) and only purge on config change or after success;
ensure functions that set failures (where failed[account_id] = fp currently)
populate the metadata fields and increment attempt_count so the bootstrap loop
can honor retry schedules.
api/db/db_models.py (1)

1218-1232: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the duplicate ChatChannel model; it breaks the runtime status/schema contract.

Line 1218 introduces ChatChannel, but another ChatChannel already exists at Line 1362. Python keeps only the later class, so the first model’s status="1" and nullable dialog_id contract is effectively ignored. This conflicts with ChatChannelService.list_active() (api/db/services/chat_channel_service.py, Line 55) which filters status == "1", and can cause enabled channels to be skipped by reconciliation. Keep a single ChatChannel definition and align status/dialog_id semantics across model + migration (Line 1725) + service filter.

Also applies to: 1362-1373, 1725-1725

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/db/db_models.py` around lines 1218 - 1232, Remove the duplicate
ChatChannel class and keep a single authoritative definition for ChatChannel
with dialog_id nullable (default=None) and status defaulting to "1"; update the
remaining ChatChannel model (the one referenced by
ChatChannelService.list_active()) so its field types/defaults match the
migration contract and ensure the migration/schema entry for ChatChannel
reflects dialog_id nullable and status default "1"; then run/adjust migrations
and tests to confirm the service filter status == "1" will correctly find active
channels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💞 feature Feature request, pull request that fullfill a new feature. size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: Support multiple chat channels (Discord, Slack, Telegram, WhatsApp, etc.) as first-class bots connected to RAGFlow assistants

2 participants