diff --git a/.env.example b/.env.example index bb77f59..e2fdce6 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,7 @@ LLM_MODEL=qwen2.5-7b-awq LLM_KEY=EMPTY # vLLM default key # LLM(Vision) Configuration +VL_BINDING=vllm # ollama, vllm, openai VL_BINDING_HOST=http://192.168.6.115:8001/v1 VL_MODEL=qwen2.5-vl-3b-awq VL_KEY=EMPTY diff --git a/app/config.py b/app/config.py index 10ee6ef..0c5faf5 100644 --- a/app/config.py +++ b/app/config.py @@ -18,6 +18,7 @@ class Settings(BaseSettings): LLM_KEY: str = "EMPTY" # vLLM default key # LLM (Vision) - vLLM + VL_BINDING: str = "vllm" # ollama, vllm, openai VL_BINDING_HOST: str = "http://192.168.6.115:8001/v1" VL_MODEL: str = "qwen2.5-vl-3b-awq" VL_KEY: str = "EMPTY" diff --git a/app/core/ingest.py b/app/core/ingest.py index 77a76b5..7c70498 100644 --- a/app/core/ingest.py +++ b/app/core/ingest.py @@ -6,7 +6,8 @@ from app.config import settings async def vl_image_caption_func(image_data: bytes, prompt: str = "请详细描述这张图片") -> str: """ - 使用 VL 模型 (vLLM OpenAI API) 生成图片描述 + 使用 VL 模型生成图片描述 + 支持 ollama 和 openai/vllm 协议 """ if not settings.VL_BINDING_HOST: return "[Image Processing Skipped: No VL Model Configured]" @@ -15,38 +16,53 @@ async def vl_image_caption_func(image_data: bytes, prompt: str = "请详细描 # 1. 编码图片为 Base64 base64_image = base64.b64encode(image_data).decode('utf-8') - # 2. 构造 OpenAI 格式请求 - # vLLM 支持 OpenAI Vision API - url = f"{settings.VL_BINDING_HOST}/chat/completions" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {settings.VL_KEY}" - } - - payload = { - "model": settings.VL_MODEL, - "messages": [ - { - "role": "user", - "content": [ - {"type": "text", "text": prompt}, - { - "type": "image_url", - "image_url": { - "url": f"data:image/jpeg;base64,{base64_image}" - } - } - ] - } - ], - "max_tokens": 300 - } - async with httpx.AsyncClient(timeout=30.0) as client: - response = await client.post(url, headers=headers, json=payload) - response.raise_for_status() - result = response.json() - description = result['choices'][0]['message']['content'] + if settings.VL_BINDING == "ollama": + # Ollama 协议 + url = f"{settings.VL_BINDING_HOST}/api/generate" + payload = { + "model": settings.VL_MODEL, + "prompt": prompt, + "images": [base64_image], + "stream": False + } + response = await client.post(url, json=payload) + response.raise_for_status() + result = response.json() + description = result.get('response', '') + + else: + # OpenAI / vLLM 协议 + url = f"{settings.VL_BINDING_HOST}/chat/completions" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {settings.VL_KEY}" + } + + payload = { + "model": settings.VL_MODEL, + "messages": [ + { + "role": "user", + "content": [ + {"type": "text", "text": prompt}, + { + "type": "image_url", + "image_url": { + "url": f"data:image/jpeg;base64,{base64_image}" + } + } + ] + } + ], + "max_tokens": 300 + } + + response = await client.post(url, headers=headers, json=payload) + response.raise_for_status() + result = response.json() + description = result['choices'][0]['message']['content'] + return f"[Image Description: {description}]" except Exception as e: @@ -70,14 +86,14 @@ async def process_pdf_with_images(file_bytes: bytes) -> str: text_content += f"--- Page {page_num + 1} Text ---\n{page_text}\n\n" # 2. 提取图片 - if False and settings.VL_BINDING_HOST: + if settings.VL_BINDING_HOST: for count, image_file_object in enumerate(page.images): try: # 获取图片数据 image_data = image_file_object.data # 简单验证图片有效性 - # Image.open(BytesIO(image_data)).verify() + Image.open(BytesIO(image_data)).verify() # 调用 VL 模型 caption = await vl_image_caption_func(image_data) diff --git a/app/static/admin.html b/app/static/admin.html index 8413afc..9607935 100644 --- a/app/static/admin.html +++ b/app/static/admin.html @@ -38,7 +38,7 @@

LightRAG Admin

当前租户: {{ currentTenantId }} - 返回首页 + 返回首页
@@ -113,41 +113,44 @@ - - - - - -
拖拽文件到此处或 点击上传
- -
-
- - -
- 提交 -
-
- -
- - - 删除 -
- 添加一组 QA -
- 提交所有 QA -
-
-
+ +
+ + + + +
拖拽文件到此处或 点击上传
+ +
+
+ + +
+ 提交 +
+
+ +
+ + + 删除 +
+ 添加一组 QA +
+ 提交所有 QA +
+
+
+
@@ -185,6 +188,7 @@ const documents = ref([]); const loadingDocs = ref(false); const showImportDialog = ref(false); + const importing = ref(false); const importType = ref('file'); const importText = ref(''); const qaList = ref([{ question: '', answer: '' }]); @@ -208,6 +212,7 @@ const urlParams = new URLSearchParams(window.location.search); const tokenParam = urlParams.get('token'); const tenantParam = urlParams.get('tenant_id'); + const isAdmin = ref(false); // 初始化 onMounted(() => { @@ -221,6 +226,7 @@ } else if (tokenParam === 'fzy') { // 管理员模式 currentPage.value = 'home'; + isAdmin.value = true; refreshTenants(); } else { ElMessage.warning('请在 URL 中提供有效的 token'); @@ -300,6 +306,7 @@ // 导入逻辑 const uploadFile = async (param) => { + importing.value = true; const formData = new FormData(); formData.append('file', param.file); try { @@ -309,11 +316,14 @@ fetchDocuments(); } catch (e) { ElMessage.error('上传失败'); + } finally { + importing.value = false; } }; const uploadText = async () => { if (!importText.value) return; + importing.value = true; try { await api.post('/ingest/text', { text: importText.value }); ElMessage.success('导入成功'); @@ -322,6 +332,8 @@ fetchDocuments(); } catch (e) { ElMessage.error('导入失败'); + } finally { + importing.value = false; } }; @@ -332,6 +344,7 @@ const validQA = qaList.value.filter(q => q.question && q.answer); if (validQA.length === 0) return ElMessage.warning('请填写 QA'); + importing.value = true; try { await api.post('/ingest/batch_qa', validQA); ElMessage.success(`成功导入 ${validQA.length} 条 QA`); @@ -340,6 +353,8 @@ fetchDocuments(); } catch (e) { ElMessage.error('导入失败'); + } finally { + importing.value = false; } }; @@ -447,13 +462,13 @@ return { currentPage, tenants, currentTenantId, activeTab, documents, loadingDocs, - showImportDialog, importType, importText, qaList, + showImportDialog, importType, importText, qaList, importing, showDocDialog, currentDocId, currentDocContent, docDetailLoading, queryInput, chatHistory, chatLoading, chatBox, goHome, refreshTenants, enterTenant, fetchDocuments, viewDocument, deleteDocument, deleteCurrentDoc, uploadFile, uploadText, addQA, removeQA, uploadQA, - sendQuery, renderMarkdown, formatDate + sendQuery, renderMarkdown, formatDate, isAdmin }; } });