mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-18 10:26:07 +00:00
feat(rag): add data-only migration option and fix dialog width
Add option to migrate knowledge base data without auto-installing the LangRAG plugin (for offline/intranet environments). Also narrow the migration dialog to match other confirmation dialogs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import quart
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from ... import group
|
from ... import group
|
||||||
@@ -52,55 +53,56 @@ class KnowledgeMigrationRouterGroup(group.RouterGroup):
|
|||||||
)
|
)
|
||||||
return result.first() is not None
|
return result.first() is not None
|
||||||
|
|
||||||
async def _execute_rag_migration(self, task_context: taskmgr.TaskContext):
|
async def _execute_rag_migration(self, task_context: taskmgr.TaskContext, install_plugin: bool = True):
|
||||||
"""Execute RAG migration: install langrag plugin and restore backup data."""
|
"""Execute RAG migration: optionally install langrag plugin and restore backup data."""
|
||||||
warnings = []
|
warnings = []
|
||||||
|
|
||||||
# Step 1: Install langrag plugin from marketplace
|
if install_plugin:
|
||||||
task_context.trace('Installing LangRAG plugin from marketplace...', action='install-plugin')
|
# Step 1: Install langrag plugin from marketplace
|
||||||
try:
|
task_context.trace('Installing LangRAG plugin from marketplace...', action='install-plugin')
|
||||||
# Query marketplace for latest version
|
|
||||||
space_url = self.ap.instance_config.data.get('space', {}).get('url', DEFAULT_SPACE_URL).rstrip('/')
|
|
||||||
async with httpx.AsyncClient(trust_env=True, timeout=15) as client:
|
|
||||||
resp = await client.get(
|
|
||||||
f'{space_url}/api/v1/marketplace/plugins/{LANGRAG_PLUGIN_AUTHOR}/{LANGRAG_PLUGIN_NAME}'
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
plugin_data = resp.json().get('data', {}).get('plugin', {})
|
|
||||||
plugin_version = plugin_data.get('latest_version')
|
|
||||||
if not plugin_version:
|
|
||||||
raise Exception('Could not determine latest LangRAG version from marketplace')
|
|
||||||
|
|
||||||
install_info = {
|
|
||||||
'plugin_author': LANGRAG_PLUGIN_AUTHOR,
|
|
||||||
'plugin_name': LANGRAG_PLUGIN_NAME,
|
|
||||||
'plugin_version': plugin_version,
|
|
||||||
}
|
|
||||||
await self.ap.plugin_connector.install_plugin(
|
|
||||||
PluginInstallSource.MARKETPLACE, install_info, task_context=task_context
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
# Plugin may already be installed
|
|
||||||
self.ap.logger.warning(f'LangRAG plugin install returned: {e}')
|
|
||||||
task_context.trace(f'Plugin install note: {e}')
|
|
||||||
|
|
||||||
# Step 2: Wait for the plugin to be available
|
|
||||||
task_context.trace('Waiting for LangRAG plugin to become available...', action='wait-plugin')
|
|
||||||
max_retries = 30
|
|
||||||
for i in range(max_retries):
|
|
||||||
try:
|
try:
|
||||||
engines = await self.ap.plugin_connector.list_knowledge_engines()
|
# Query marketplace for latest version
|
||||||
engine_ids = [e.get('plugin_id') for e in engines]
|
space_url = self.ap.instance_config.data.get('space', {}).get('url', DEFAULT_SPACE_URL).rstrip('/')
|
||||||
if LANGRAG_PLUGIN_ID in engine_ids:
|
async with httpx.AsyncClient(trust_env=True, timeout=15) as client:
|
||||||
task_context.trace('LangRAG plugin is ready.')
|
resp = await client.get(
|
||||||
break
|
f'{space_url}/api/v1/marketplace/plugins/{LANGRAG_PLUGIN_AUTHOR}/{LANGRAG_PLUGIN_NAME}'
|
||||||
except Exception:
|
)
|
||||||
pass
|
resp.raise_for_status()
|
||||||
if i == max_retries - 1:
|
plugin_data = resp.json().get('data', {}).get('plugin', {})
|
||||||
raise Exception(
|
plugin_version = plugin_data.get('latest_version')
|
||||||
f'LangRAG plugin ({LANGRAG_PLUGIN_ID}) did not become available after {max_retries} retries'
|
if not plugin_version:
|
||||||
|
raise Exception('Could not determine latest LangRAG version from marketplace')
|
||||||
|
|
||||||
|
install_info = {
|
||||||
|
'plugin_author': LANGRAG_PLUGIN_AUTHOR,
|
||||||
|
'plugin_name': LANGRAG_PLUGIN_NAME,
|
||||||
|
'plugin_version': plugin_version,
|
||||||
|
}
|
||||||
|
await self.ap.plugin_connector.install_plugin(
|
||||||
|
PluginInstallSource.MARKETPLACE, install_info, task_context=task_context
|
||||||
)
|
)
|
||||||
await asyncio.sleep(2)
|
except Exception as e:
|
||||||
|
# Plugin may already be installed
|
||||||
|
self.ap.logger.warning(f'LangRAG plugin install returned: {e}')
|
||||||
|
task_context.trace(f'Plugin install note: {e}')
|
||||||
|
|
||||||
|
# Step 2: Wait for the plugin to be available
|
||||||
|
task_context.trace('Waiting for LangRAG plugin to become available...', action='wait-plugin')
|
||||||
|
max_retries = 30
|
||||||
|
for i in range(max_retries):
|
||||||
|
try:
|
||||||
|
engines = await self.ap.plugin_connector.list_knowledge_engines()
|
||||||
|
engine_ids = [e.get('plugin_id') for e in engines]
|
||||||
|
if LANGRAG_PLUGIN_ID in engine_ids:
|
||||||
|
task_context.trace('LangRAG plugin is ready.')
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if i == max_retries - 1:
|
||||||
|
raise Exception(
|
||||||
|
f'LangRAG plugin ({LANGRAG_PLUGIN_ID}) did not become available after {max_retries} retries'
|
||||||
|
)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
# Step 3: Restore internal knowledge bases from backup
|
# Step 3: Restore internal knowledge bases from backup
|
||||||
task_context.trace('Restoring internal knowledge bases...', action='restore-internal')
|
task_context.trace('Restoring internal knowledge bases...', action='restore-internal')
|
||||||
@@ -283,9 +285,12 @@ class KnowledgeMigrationRouterGroup(group.RouterGroup):
|
|||||||
if not needed:
|
if not needed:
|
||||||
return self.http_status(400, -1, 'RAG migration is not needed')
|
return self.http_status(400, -1, 'RAG migration is not needed')
|
||||||
|
|
||||||
|
data = await quart.request.get_json(silent=True) or {}
|
||||||
|
install_plugin = data.get('install_plugin', True)
|
||||||
|
|
||||||
ctx = taskmgr.TaskContext.new()
|
ctx = taskmgr.TaskContext.new()
|
||||||
wrapper = self.ap.task_mgr.create_user_task(
|
wrapper = self.ap.task_mgr.create_user_task(
|
||||||
self._execute_rag_migration(task_context=ctx),
|
self._execute_rag_migration(task_context=ctx, install_plugin=install_plugin),
|
||||||
kind='rag-migration',
|
kind='rag-migration',
|
||||||
name='rag-migration-execute',
|
name='rag-migration-execute',
|
||||||
label='Migrating knowledge bases to plugin architecture',
|
label='Migrating knowledge bases to plugin architecture',
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export default function KBMigrationDialog({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleStartMigration = async () => {
|
const handleMigration = async (installPlugin: boolean) => {
|
||||||
try {
|
try {
|
||||||
const resp = await httpClient.executeRagMigration();
|
const resp = await httpClient.executeRagMigration(installPlugin);
|
||||||
asyncTask.startTask(resp.task_id);
|
asyncTask.startTask(resp.task_id);
|
||||||
} catch {
|
} catch {
|
||||||
toast.error(t('knowledge.migration.error'));
|
toast.error(t('knowledge.migration.error'));
|
||||||
@@ -77,7 +77,7 @@ export default function KBMigrationDialog({
|
|||||||
if (!isRunning) onOpenChange(v);
|
if (!isRunning) onOpenChange(v);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t('knowledge.migration.title')}</DialogTitle>
|
<DialogTitle>{t('knowledge.migration.title')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -87,18 +87,13 @@ export default function KBMigrationDialog({
|
|||||||
|
|
||||||
<div className="py-4 space-y-3">
|
<div className="py-4 space-y-3">
|
||||||
{!isRunning && !isError && (
|
{!isRunning && !isError && (
|
||||||
<>
|
<p className="text-sm text-muted-foreground">
|
||||||
<p className="text-sm text-muted-foreground">
|
{t('knowledge.migration.detected', {
|
||||||
{t('knowledge.migration.detected', {
|
total: totalCount,
|
||||||
total: totalCount,
|
internal: internalKbCount,
|
||||||
internal: internalKbCount,
|
external: externalKbCount,
|
||||||
external: externalKbCount,
|
})}
|
||||||
})}
|
</p>
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('knowledge.migration.installHint')}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
@@ -122,26 +117,39 @@ export default function KBMigrationDialog({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter className="flex flex-col gap-2 sm:flex-col">
|
||||||
{!isRunning && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleDismiss}
|
|
||||||
disabled={dismissing}
|
|
||||||
>
|
|
||||||
{t('knowledge.migration.dismiss')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isRunning && !isError && (
|
{!isRunning && !isError && (
|
||||||
<Button onClick={handleStartMigration}>
|
<>
|
||||||
{t('knowledge.migration.start')}
|
<Button onClick={() => handleMigration(true)} className="w-full">
|
||||||
</Button>
|
{t('knowledge.migration.startWithInstall')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleMigration(false)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{t('knowledge.migration.startDataOnly')}
|
||||||
|
</Button>
|
||||||
|
<p className="text-xs text-muted-foreground text-center">
|
||||||
|
{t('knowledge.migration.dataOnlyHint')}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<Button onClick={handleStartMigration}>
|
<Button onClick={() => handleMigration(true)} className="w-full">
|
||||||
{t('knowledge.migration.retry')}
|
{t('knowledge.migration.retry')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{!isRunning && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
disabled={dismissing}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{t('knowledge.migration.dismiss')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -716,8 +716,12 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
return this.get('/api/v1/knowledge/migration/status');
|
return this.get('/api/v1/knowledge/migration/status');
|
||||||
}
|
}
|
||||||
|
|
||||||
public executeRagMigration(): Promise<AsyncTaskCreatedResp> {
|
public executeRagMigration(
|
||||||
return this.post('/api/v1/knowledge/migration/execute');
|
installPlugin: boolean = true,
|
||||||
|
): Promise<AsyncTaskCreatedResp> {
|
||||||
|
return this.post('/api/v1/knowledge/migration/execute', {
|
||||||
|
install_plugin: installPlugin,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public dismissRagMigration(): Promise<object> {
|
public dismissRagMigration(): Promise<object> {
|
||||||
|
|||||||
@@ -779,9 +779,10 @@ const enUS = {
|
|||||||
'Legacy knowledge base data detected. Migration to the new plugin-based RAG architecture is required.',
|
'Legacy knowledge base data detected. Migration to the new plugin-based RAG architecture is required.',
|
||||||
detected:
|
detected:
|
||||||
'Found {{total}} knowledge base(s) to migrate ({{internal}} internal, {{external}} external).',
|
'Found {{total}} knowledge base(s) to migrate ({{internal}} internal, {{external}} external).',
|
||||||
installHint:
|
startWithInstall: 'Auto-install Plugin & Migrate',
|
||||||
'Migration will automatically install the LangRAG plugin and restore your knowledge base data. Documents and vector data will be preserved.',
|
startDataOnly: 'Migrate Data Only',
|
||||||
start: 'Migrate Now',
|
dataOnlyHint:
|
||||||
|
'"Migrate Data Only" is for offline/intranet environments. Please install the LangRAG plugin manually before migrating.',
|
||||||
dismiss: 'Skip for Now',
|
dismiss: 'Skip for Now',
|
||||||
running: 'Migrating knowledge bases, please wait...',
|
running: 'Migrating knowledge bases, please wait...',
|
||||||
success: 'Knowledge base migration completed',
|
success: 'Knowledge base migration completed',
|
||||||
|
|||||||
@@ -768,9 +768,10 @@ const jaJP = {
|
|||||||
'旧バージョンのナレッジベースデータが検出されました。新しいプラグインベースのRAGアーキテクチャへの移行が必要です。',
|
'旧バージョンのナレッジベースデータが検出されました。新しいプラグインベースのRAGアーキテクチャへの移行が必要です。',
|
||||||
detected:
|
detected:
|
||||||
'移行が必要なナレッジベースが{{total}}件見つかりました(内部{{internal}}件、外部{{external}}件)。',
|
'移行が必要なナレッジベースが{{total}}件見つかりました(内部{{internal}}件、外部{{external}}件)。',
|
||||||
installHint:
|
startWithInstall: 'プラグインを自動インストールして移行',
|
||||||
'移行により LangRAG プラグインが自動的にインストールされ、ナレッジベースデータが復元されます。ドキュメントとベクトルデータは保持されます。',
|
startDataOnly: 'データのみ移行',
|
||||||
start: '今すぐ移行',
|
dataOnlyHint:
|
||||||
|
'「データのみ移行」はオフライン環境向けです。事前に LangRAG プラグインを手動でインストールしてください。',
|
||||||
dismiss: '後で',
|
dismiss: '後で',
|
||||||
running: 'ナレッジベースを移行中です。しばらくお待ちください...',
|
running: 'ナレッジベースを移行中です。しばらくお待ちください...',
|
||||||
success: 'ナレッジベースの移行が完了しました',
|
success: 'ナレッジベースの移行が完了しました',
|
||||||
|
|||||||
@@ -747,9 +747,9 @@ const zhHans = {
|
|||||||
description: '检测到旧版知识库数据,需要迁移到新的插件化 RAG 架构。',
|
description: '检测到旧版知识库数据,需要迁移到新的插件化 RAG 架构。',
|
||||||
detected:
|
detected:
|
||||||
'共检测到 {{total}} 个知识库需要迁移({{internal}} 个内置知识库,{{external}} 个外部知识库)。',
|
'共检测到 {{total}} 个知识库需要迁移({{internal}} 个内置知识库,{{external}} 个外部知识库)。',
|
||||||
installHint:
|
startWithInstall: '自动安装插件并迁移',
|
||||||
'迁移将自动安装 LangRAG 插件并恢复您的知识库数据,文档和向量数据将被保留。',
|
startDataOnly: '仅迁移数据',
|
||||||
start: '立即迁移',
|
dataOnlyHint: '「仅迁移数据」适合内网环境使用,请自行安装 LangRAG 插件后再迁移。',
|
||||||
dismiss: '暂不迁移',
|
dismiss: '暂不迁移',
|
||||||
running: '正在迁移知识库,请稍候...',
|
running: '正在迁移知识库,请稍候...',
|
||||||
success: '知识库迁移完成',
|
success: '知识库迁移完成',
|
||||||
|
|||||||
@@ -727,9 +727,10 @@ const zhHant = {
|
|||||||
description: '檢測到舊版知識庫資料,需要遷移到新的插件化 RAG 架構。',
|
description: '檢測到舊版知識庫資料,需要遷移到新的插件化 RAG 架構。',
|
||||||
detected:
|
detected:
|
||||||
'共檢測到 {{total}} 個知識庫需要遷移({{internal}} 個內建知識庫,{{external}} 個外部知識庫)。',
|
'共檢測到 {{total}} 個知識庫需要遷移({{internal}} 個內建知識庫,{{external}} 個外部知識庫)。',
|
||||||
installHint:
|
startWithInstall: '自動安裝插件並遷移',
|
||||||
'遷移將自動安裝 LangRAG 插件並恢復您的知識庫資料,文件和向量資料將被保留。',
|
startDataOnly: '僅遷移資料',
|
||||||
start: '立即遷移',
|
dataOnlyHint:
|
||||||
|
'「僅遷移資料」適合內網環境使用,請自行安裝 LangRAG 插件後再遷移。',
|
||||||
dismiss: '暫不遷移',
|
dismiss: '暫不遷移',
|
||||||
running: '正在遷移知識庫,請稍候...',
|
running: '正在遷移知識庫,請稍候...',
|
||||||
success: '知識庫遷移完成',
|
success: '知識庫遷移完成',
|
||||||
|
|||||||
Reference in New Issue
Block a user