Skip to content

Commit 79d8306

Browse files
authored
Merge pull request #341 from HKUDS/dev
release: v1.1.2 — CI fix & release notes cleanup
2 parents c2882bc + 6e00642 commit 79d8306

2 files changed

Lines changed: 35 additions & 43 deletions

File tree

assets/releases/ver1-1-2.md

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,45 @@
44

55
## Highlights
66

7-
### TutorBot — Schema-Driven Channels Tab, Token Reveal & API-Side Channel Reload (#338)
8-
Replaced the hard-coded Telegram-only Channels tab in `web/app/(workspace)/agents/page.tsx` with a generic, schema-driven editor that works for **every** built-in or plugin channel without any per-channel front-end code. A new `GET /api/v1/tutorbot/channels/schema` endpoint introspects every discovered `XxxChannel` / `XxxConfig` pair (Telegram, Slack, Discord, Matrix, WhatsApp, Email, Feishu, WeCom, QQ, DingTalk, etc.) and ships back a flattened JSON-Schema payload (`$ref`s inlined), the default config, the channel's `display_name`, and a list of dot-paths for any string field whose name hints at a secret (`token`, `secret`, `password`, `api_key`, `encrypt_key`). The new `SchemaField` React component recursively renders the schema — booleans become checkboxes, enums become selects, string-arrays become one-per-line textareas, nested `Optional[Model]` becomes inline fieldsets, and any flagged secret renders as a `password` input with an inline eye-toggle (`Eye` / `EyeOff`) for explicit reveal. The Channels tab now also surfaces a `last_reload_error` banner so users immediately see when on-disk config and live listeners have diverged.
7+
### Schema-Driven Channels Tab with Token Reveal (#338)
8+
The Channels tab in the Agents page is no longer hard-coded for Telegram. It now auto-discovers **every** channel (Telegram, Slack, Discord, Matrix, Email, Feishu, …) and renders a form directly from each channel's Pydantic config schema — no per-channel front-end code required. Secret fields (tokens, passwords, API keys) render as masked inputs with an eye-toggle for explicit reveal. A `last_reload_error` banner warns when live listeners failed to restart after a config change.
99

10-
### Channel Secret Masking End-to-End
11-
Added `mask_channel_secrets` and `_is_secret_field` helpers to `TutorBotManager` and rewired `TutorBotInstance.to_dict` with three explicit modes: list-of-channel-names (default for the `GET /tutorbot` index), nested dict with `***` masks (default for `GET /tutorbot/{bot_id}`), and full plaintext only when the caller passes `?include_secrets=true` (used exclusively by the admin edit form). `POST /tutorbot` and `PATCH /tutorbot/{bot_id}` responses are likewise masked. A new `tests/services/tutorbot/test_channel_secrets.py` (223 lines) and `tests/api/test_tutorbot_channel_schema.py` (185 lines) pin the masking rules, snake-case property names, secret-field detection (deliberately excluding non-string fields like `user_token_read_only: bool`), and the schema-endpoint shape across every built-in channel.
10+
### Channel Secret Masking
11+
API responses no longer expose raw channel secrets. Tokens and passwords are replaced with `***` by default; the admin edit form uses `?include_secrets=true` to fetch plaintext when needed. Create and update responses are likewise masked.
1212

13-
### TutorBot — Concurrent Reload Lock & 422 on Bad Channel Config
14-
`PATCH /tutorbot/{bot_id}` now validates `payload.channels` against `ChannelsConfig` at the API boundary and returns a structured 422 (`{message, errors}`) instead of letting the bad config slip through to disk and blow up later inside `reload_channels`. `reload_channels` itself was hardened: a per-instance `asyncio.Lock` serialises concurrent invocations to prevent duplicate listeners, channel-manager construction was extracted into a shared `_build_channel_manager` so `start_bot` and `reload_channels` produce identical behaviour, and any teardown/rebuild failure is captured into `instance.last_reload_error` (cleared on the next success) and surfaced via the API.
13+
### Channel Config Validation & Reload Hardening
14+
`PATCH /tutorbot/{bot_id}` now validates channel payloads upfront and returns a 422 with structured errors instead of silently persisting bad config. `reload_channels` is serialised with a per-instance lock to prevent duplicate listeners, and any failure is recorded in `last_reload_error` so the UI can surface it.
1515

16-
### RAG Service — One Backend, One Pipeline
17-
Removed ~2 600 lines of stale RAG scaffolding (`deeptutor/services/rag/components/{base,routing,chunkers,embedders,indexers,parsers,retrievers}/*`, `services/rag/pipeline.py`, `services/rag/types.py`, `services/rag/utils/__init__.py`, `services/rag/parsers/__init__.py`) that had been carrying placeholders for never-shipped pipelines. The factory (`services/rag/factory.py`) now exposes a single `LlamaIndexPipeline` keyed by `kb_base_dir`, and `RAGService` was simplified to a thin facade over it. `normalize_provider_name`, `LEGACY_PROVIDER_ALIASES`, `has_pipeline`, and the `_validate_registered_provider` API handler are all gone — `rag_provider` is now a stub field that silently coerces any legacy value (`lightrag`, `raganything`, …) into `llamaindex` while flagging the KB for re-indexing.
16+
### RAG Simplified to a Single Pipeline
17+
Removed ~2,600 lines of unused RAG scaffolding (chunkers, embedders, indexers, parsers, retrievers, pipeline orchestrator, type definitions) that existed as placeholders for never-shipped backends. The RAG service is now a thin wrapper over the single LlamaIndex pipeline. Legacy `rag_provider` values (e.g. `lightrag`) are silently coerced to `llamaindex` and the KB is flagged for re-indexing.
1818

1919
### Centralized File Type Routing
20-
Extracted file-type classification into a single `deeptutor/services/rag/file_routing.py` module (`FileTypeRouter`, `DocumentType`, `FileClassification`) with a flat per-extension API: `get_document_type`, `classify_files`, `read_text_file`, `needs_parser`, `is_text_readable`, `get_supported_extensions`, `get_glob_patterns`. The provider-keyed variants (`get_extensions_for_provider`, `get_glob_patterns_for_provider`) have been removed since there is only one provider; every call-site (`knowledge/initializer.py`, `knowledge/manager.py`, `api/routers/knowledge.py`, `services/rag/pipelines/llamaindex.py`, `deeptutor_cli/kb.py`) now uses the flat helpers. Unknown extensions still fall through to a content-sniff (binary check + UTF-8 probe) before being marked `UNKNOWN`.
20+
Consolidated file-type classification into a single `FileTypeRouter` module with a flat API (`get_document_type`, `classify_files`, `get_supported_extensions`, etc.). The old per-provider extension helpers are gone — there's only one provider. Unknown extensions still fall through to content sniffing before being rejected.
2121

22-
### RAG/KB Consistency — No Phantom Knowledge Bases
23-
Closed every code path that could quietly invoke RAG against a non-existent or placeholder knowledge base:
22+
### No More Phantom Knowledge Bases
23+
Closed every code path that could silently call RAG against a non-existent KB:
2424

25-
- **`deep_solve`** strips `rag` from the effective tool set when no KB is attached and emits a `trace_kind=warning` progress event so the UI can explain why retrieval was disabled.
26-
- **`deep_research`** drops `kb` from `request_config.sources` when no KB is attached, emits a warning, and surfaces a hard error if downgrading leaves the sources list empty (so the run aborts cleanly instead of producing an empty report).
27-
- **`SolveToolRuntime.execute("rag", kb_name=None)`** short-circuits with a structured `ToolResult(success=False, metadata={"skipped": True, "reason": "no_kb_selected"})` and never forwards to the registry, keeping the ReAct loop alive with a clear observation.
28-
- **`ResearchPipeline._call_tool`** for `rag` / `rag_hybrid` / `rag_naive` returns a structured `status: skipped` JSON (with a matching `tool_result` trace event) when `kb_name` is missing, instead of falling back to the old `DE-all` placeholder. Unknown tool types now return a structured `status: failed, reason: unknown_tool` instead of silently retrying RAG.
29-
- **`DecomposeAgent`** drops the `ai_textbook` default and disables RAG defensively when no `kb_name` is provided, even when the runtime config still says `enable_rag: True`.
25+
- **deep_solve** strips the `rag` tool when no KB is attached and warns the user.
26+
- **deep_research** drops `kb` from sources, warns, and aborts if no sources remain.
27+
- **SolveToolRuntime** — returns a graceful "no KB selected" observation instead of crashing, keeping the ReAct loop alive.
28+
- **ResearchPipeline** returns a structured "skipped" event instead of falling back to the old `DE-all` placeholder.
29+
- **DecomposeAgent** — no longer defaults to `ai_textbook`; disables RAG when no KB is provided.
3030

31-
A new `tests/capabilities/test_rag_consistency.py` (233 lines) and `tests/agents/research/test_research_pipeline_rag.py` (146 lines) lock in the contracts end-to-end.
32-
33-
### Agentic Chat Pipeline — Externalized Prompts
34-
Extracted every hard-coded zh/en string out of `deeptutor/agents/chat/agentic_pipeline.py` (337 → 108 lines, –68%) into two new YAML files: `deeptutor/agents/chat/prompts/{zh,en}/agentic_chat.yaml` (178 lines each). Stage labels, `kb_hint` injection, the thinking / acting / observing / responding system + user templates, and all UI-visible notices (no-tools, ReAct fallback, etc.) are now editable copy without a code change. Loaded via the existing `get_prompt_manager()` plumbing with a graceful fallback to `_text(zh=, en=)` if the YAML is missing.
31+
### Externalized Chat Prompts
32+
Moved all hard-coded zh/en strings out of `AgenticChatPipeline` into editable YAML files (`agentic_chat.yaml` for each language). Stage labels, system prompts, user templates, and UI notices are now configurable without code changes. Falls back gracefully if the YAML is missing.
3533

3634
### Thai README (#337)
37-
Added a Thai-language README (`README_TH.md`) and wired it into the language switcher in the main README header.
35+
Added `README_TH.md` with Thai-language documentation.
3836

3937
### Bug Fixes
40-
- **Research pipeline crashed when no KB was selected**`rag_cfg.get("kb_name", "DE-all")` was falling back to a hard-coded knowledge base name that no longer exists in most installs, surfacing as `KnowledgeBaseNotFoundError` partway through a research run. Now short-circuits with a structured skip event.
41-
- **Decompose agent attempted RAG against a non-existent default KB**`kb_name="ai_textbook"` was the constructor default; replaced with `None` plus a defensive `enable_rag = False` guard.
42-
- **Bad channel config silently persisted on disk and blew up at next bot start**`PATCH /tutorbot/{bot_id}` now rejects with a 422 listing the validation errors before anything reaches `_save_bot_config`.
43-
- **Concurrent `reload_channels` could spin up duplicate listeners** — serialised via `instance.reload_lock`; failure leaves the bot running channel-less with `last_reload_error` set instead of in an inconsistent half-rebuilt state.
44-
- **Channel tokens leaked through `GET /api/v1/tutorbot/{bot_id}` and the create/patch responses** — secrets are now masked unless the caller explicitly opts in via `?include_secrets=true`.
45-
46-
### Test Suite Expansion
47-
Added 6 new test modules covering the v1.1.2 surface area:
48-
49-
- `tests/services/rag/test_file_routing.py` (108 lines) — extension classification (case-insensitive), unknown-extension content sniffing (text vs. binary), `read_text_file` encoding fallback chain, `classify_files` bucketing, and the helper getters.
50-
- `tests/services/config/test_knowledge_base_config.py` (147 lines) — pins the stub-provider contract: any legacy or unknown `rag_provider` value coerces to `llamaindex` and the on-disk migration flips legacy KBs to `needs_reindex: true`.
51-
- `tests/api/test_tutorbot_channel_schema.py` (185 lines) — `resolve_config_model`, `inline_refs` (with nested `$defs`), `collect_secret_fields` (string-only secret detection), and the `GET /api/v1/tutorbot/channels/schema` integration shape across telegram / slack / discord.
52-
- `tests/services/tutorbot/test_channel_secrets.py` (223 lines) — `mask_channel_secrets` deep-walk semantics for nested objects, lists, tuples, multiple email/slack secret keys, and the three `to_dict` modes (default = list, mask, include).
53-
- `tests/capabilities/test_rag_consistency.py` (233 lines) — `deep_solve` and `deep_research` strip `rag` / `kb` when no KB is attached, emit the right warning events, and (for research) surface a hard error if sources go empty.
54-
- `tests/agents/research/test_research_pipeline_rag.py` (146 lines) — pins the removal of the `DE-all` fallback in `ResearchPipeline._call_tool` and `DecomposeAgent`'s defensive RAG disablement.
55-
56-
Extended `tests/agents/solve/test_tool_runtime.py` with two new cases for the `rag`-without-`kb_name` short-circuit (both `None` and empty string), `tests/api/test_knowledge_router.py` to flip the legacy "reject unsupported provider" tests into "coerce to llamaindex" tests, `tests/api/test_tutorbot_router.py` with the secret-handling cases for the stopped-bot GET route, and `tests/services/rag/test_rag_pipelines.py` + `tests/tools/test_rag_tool.py` to track the components → `file_routing` move.
38+
- **Research pipeline crashed without a KB** — the `DE-all` fallback KB no longer exists in most installs; now short-circuits with a structured skip event.
39+
- **Decompose agent tried RAG against `ai_textbook`** — replaced the hard-coded default with `None` and a defensive guard.
40+
- **Bad channel config persisted silently** — now rejected at the API boundary with a 422 before reaching disk.
41+
- **Concurrent `reload_channels` created duplicate listeners** — serialised via an asyncio lock; failure leaves the bot channel-less with a clear error instead of half-rebuilt.
42+
- **Channel tokens leaked in API responses** — now masked by default across all endpoints.
43+
44+
### Test Suite
45+
Added 6 new test modules (1,042 lines total): file-type routing, KB config migration, channel schema introspection, channel secret masking, RAG/KB consistency at the capability layer, and research pipeline RAG safety. Extended existing tests for the tool runtime, knowledge router, TutorBot router, and RAG pipeline modules.
5746

5847
## Community Contributions
5948

deeptutor/tutorbot/channels/base.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from pathlib import Path
77
from typing import Any
88

9-
from loguru import logger
10-
119
from deeptutor.tutorbot.bus.events import InboundMessage, OutboundMessage
1210
from deeptutor.tutorbot.bus.queue import MessageBus
1311

1412

13+
def _logger():
14+
from loguru import logger as _log
15+
return _log
16+
17+
1518
class BaseChannel(ABC):
1619
"""
1720
Abstract base class for chat channel implementations.
@@ -46,7 +49,7 @@ async def transcribe_audio(self, file_path: str | Path) -> str:
4649
provider = GroqTranscriptionProvider(api_key=self.transcription_api_key)
4750
return await provider.transcribe(file_path)
4851
except Exception as e:
49-
logger.warning("{}: audio transcription failed: {}", self.name, e)
52+
_logger().warning("{}: audio transcription failed: {}", self.name, e)
5053
return ""
5154

5255
@abstractmethod
@@ -80,7 +83,7 @@ def is_allowed(self, sender_id: str) -> bool:
8083
"""Check if *sender_id* is permitted. Empty list → deny all; ``"*"`` → allow all."""
8184
allow_list = getattr(self.config, "allow_from", [])
8285
if not allow_list:
83-
logger.warning("{}: allow_from is empty — all access denied", self.name)
86+
_logger().warning("{}: allow_from is empty — all access denied", self.name)
8487
return False
8588
if "*" in allow_list:
8689
return True
@@ -109,7 +112,7 @@ async def _handle_message(
109112
session_key: Optional session key override (e.g. thread-scoped sessions).
110113
"""
111114
if not self.is_allowed(sender_id):
112-
logger.warning(
115+
_logger().warning(
113116
"Access denied for sender {} on channel {}. "
114117
"Add them to allowFrom list in config to grant access.",
115118
sender_id, self.name,

0 commit comments

Comments
 (0)