fix: 1. 增加 ollama vl 模型配置 2. 优化简易后台
This commit is contained in:
parent
ccf8bd6902
commit
eaa6fc6fe7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,8 +16,23 @@ 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
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
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",
|
||||
|
|
@ -42,11 +58,11 @@ async def vl_image_caption_func(image_data: bytes, prompt: str = "请详细描
|
|||
"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']
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<h1 @click="goHome" style="cursor: pointer;">LightRAG Admin</h1>
|
||||
<div v-if="currentPage === 'tenant'">
|
||||
<el-tag type="success" size="large">当前租户: {{ currentTenantId }}</el-tag>
|
||||
<el-button type="primary" link @click="goHome">返回首页</el-button>
|
||||
<el-button v-if="isAdmin" type="primary" link @click="goHome">返回首页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -113,7 +113,8 @@
|
|||
</div>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<el-dialog v-model="showImportDialog" title="导入知识" width="600px">
|
||||
<el-dialog v-model="showImportDialog" title="导入知识" width="600px" :close-on-click-modal="!importing" :close-on-press-escape="!importing" :show-close="!importing">
|
||||
<div v-loading="importing" element-loading-text="正在导入中,请稍候...(大文件可能需要较长时间)">
|
||||
<el-tabs v-model="importType">
|
||||
<el-tab-pane label="文件上传" name="file">
|
||||
<el-upload
|
||||
|
|
@ -122,6 +123,7 @@
|
|||
action="#"
|
||||
:http-request="uploadFile"
|
||||
:show-file-list="false"
|
||||
:disabled="importing"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">拖拽文件到此处或 <em>点击上传</em></div>
|
||||
|
|
@ -131,23 +133,24 @@
|
|||
</el-upload>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="纯文本" name="text">
|
||||
<el-input v-model="importText" type="textarea" rows="10" placeholder="请输入文本内容..."></el-input>
|
||||
<el-input v-model="importText" type="textarea" rows="10" placeholder="请输入文本内容..." :disabled="importing"></el-input>
|
||||
<div style="margin-top: 10px; text-align: right;">
|
||||
<el-button type="primary" @click="uploadText">提交</el-button>
|
||||
<el-button type="primary" @click="uploadText" :loading="importing">提交</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="QA 问答" name="qa">
|
||||
<div v-for="(qa, index) in qaList" :key="index" style="margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 15px;">
|
||||
<el-input v-model="qa.question" placeholder="问题 (Question)" style="margin-bottom: 5px;"></el-input>
|
||||
<el-input v-model="qa.answer" type="textarea" placeholder="回答 (Answer)"></el-input>
|
||||
<el-button type="danger" link size="small" @click="removeQA(index)" v-if="qaList.length > 1">删除</el-button>
|
||||
<el-input v-model="qa.question" placeholder="问题 (Question)" style="margin-bottom: 5px;" :disabled="importing"></el-input>
|
||||
<el-input v-model="qa.answer" type="textarea" placeholder="回答 (Answer)" :disabled="importing"></el-input>
|
||||
<el-button type="danger" link size="small" @click="removeQA(index)" v-if="qaList.length > 1" :disabled="importing">删除</el-button>
|
||||
</div>
|
||||
<el-button type="primary" plain style="width: 100%; margin-bottom: 10px;" @click="addQA">添加一组 QA</el-button>
|
||||
<el-button type="primary" plain style="width: 100%; margin-bottom: 10px;" @click="addQA" :disabled="importing">添加一组 QA</el-button>
|
||||
<div style="text-align: right;">
|
||||
<el-button type="primary" @click="uploadQA">提交所有 QA</el-button>
|
||||
<el-button type="primary" @click="uploadQA" :loading="importing">提交所有 QA</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 文档详情弹窗 -->
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue