Janus-Pro-7B开发入门:第一个“图文问答”Web应用快速开发指南

📅 发布时间:2026/7/11 18:59:50 👁️ 浏览次数:
Janus-Pro-7B开发入门:第一个“图文问答”Web应用快速开发指南
Janus-Pro-7B开发入门第一个“图文问答”Web应用快速开发指南你是不是也对那些能看懂图片、还能跟你聊天的AI应用感到好奇想自己动手做一个但又觉得门槛太高不知道从何下手别担心今天我们就来一起用最简单的Python Flask框架和一点点前端知识从零开始搭建一个能和Janus-Pro-7B模型对话的Web应用。整个过程就像搭积木你不需要是资深全栈工程师只要会一点Python基础跟着步骤走一两个小时就能看到自己的“图文问答”应用跑起来。我们的目标很简单让你快速获得成就感亲手把想法变成现实。1. 开始之前你需要准备什么在动手敲代码之前我们先花几分钟把“工具箱”准备好。整个过程非常简单几乎就是点几下鼠标和复制粘贴命令。首先你需要一个能运行Python代码的环境。我强烈推荐使用Anaconda来管理Python环境它能帮你避免各种依赖包冲突的麻烦。去Anaconda官网下载安装包一路“下一步”安装好就行。安装好Anaconda后我们打开命令行Windows叫“命令提示符”或“Anaconda Prompt”Mac/Linux叫“终端”创建一个专属这个项目的Python环境。这样做的好处是这个项目用到的所有库都装在这里不会影响你电脑上其他项目。conda create -n janus-web-app python3.9 conda activate janus-web-app第一行命令创建了一个名叫janus-web-app的新环境并指定使用Python 3.9版本。第二行命令是进入这个环境。看到命令行前面出现(janus-web-app)的提示就说明你已经成功切换过来了。接下来我们安装这个项目最核心的几个“零件”pip install flask requests pillowflask一个非常轻量、好上手的Python Web框架我们用它来搭建后端服务。requests一个用来发送HTTP请求的库我们靠它去和星图平台的AI模型API“打电话”。pillow一个处理图片的库我们需要它来验证用户上传的是不是有效的图片文件。好了环境准备完毕。整个过程就像准备做饭前先把菜洗好、米淘好一样。接下来我们就可以正式“开火”了。2. 搭建后端引擎用Flask创建API服务我们的Web应用分为两部分后端和前端。后端就像餐厅的后厨负责接收前端的订单用户上传的图片和问题调用AI模型API进行“烹饪”推理然后把“菜肴”答案端给前端。我们先来搭建这个“后厨”。在你的电脑上找一个合适的地方新建一个文件夹比如叫做janus_web_demo。然后在这个文件夹里创建一个Python文件命名为app.py。这个文件将是我们后端服务的全部代码所在。打开app.py我们开始写代码。首先引入必要的工具库from flask import Flask, request, jsonify, render_template import requests import os from werkzeug.utils import secure_filename from PIL import Image import io app Flask(__name__) app.config[UPLOAD_FOLDER] ./uploads # 图片临时存放目录 app.config[MAX_CONTENT_LENGTH] 5 * 1024 * 1024 # 限制上传文件大小为5MB ALLOWED_EXTENSIONS {png, jpg, jpeg, gif, bmp} # 创建上传目录 if not os.path.exists(app.config[UPLOAD_FOLDER]): os.makedirs(app.config[UPLOAD_FOLDER])这几行代码做了几件事导入了需要的库创建了一个Flask应用实例设置了一个叫uploads的文件夹用来临时存放用户上传的图片并且限制了图片大小不能超过5MB只允许常见的图片格式。接下来我们需要一个关键信息星图平台API的访问地址和密钥。这就像你要打电话必须知道电话号码一样。请注意这里的YOUR_API_KEY需要替换成你在星图平台实际获取的密钥。# 星图平台API配置此处需要替换为你的真实信息 XINGTU_API_URL https://api.xingtu.cn/v1/chat/completions # 请以星图平台最新文档为准 XINGTU_API_KEY YOUR_API_KEY_HERE # 请替换为你的实际API Key MODEL_NAME janus-pro-7b # 指定使用的模型怎么获取这个API Key呢通常你需要注册并登录星图平台在个人中心或开发者设置里找到API管理部分创建一个新的API密钥。拿到之后把上面代码里的YOUR_API_KEY_HERE替换掉就行了。现在我们来写一个辅助函数检查用户上传的文件是不是图片def allowed_file(filename): 检查文件扩展名是否允许 return . in filename and \ filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS然后我们创建这个应用最核心的两个“路由”。你可以把“路由”理解成餐厅里不同的服务窗口。第一个路由是网站的首页。当用户用浏览器打开我们的网站地址时就会看到这个页面。app.route(/) def index(): 渲染前端主页面 return render_template(index.html)render_template(index.html)这行代码告诉Flask去templates文件夹里找一个叫index.html的文件把它渲染成网页发给用户。这个index.html文件我们稍后再创建。第二个路由是整个应用的大脑它负责处理用户提交的图片和问题并调用AI模型。app.route(/ask, methods[POST]) def ask_janus(): 处理图文问答请求 # 1. 检查是否有文件上传 if image not in request.files: return jsonify({error: 没有上传图片文件}), 400 file request.files[image] # 2. 检查文件是否为空或文件名不合法 if file.filename : return jsonify({error: 未选择文件}), 400 if not allowed_file(file.filename): return jsonify({error: 文件类型不支持请上传图片文件}), 400 # 3. 获取用户的问题文本 question request.form.get(question, ).strip() if not question: return jsonify({error: 问题不能为空}), 400 # 4. 安全地保存上传的图片 filename secure_filename(file.filename) filepath os.path.join(app.config[UPLOAD_FOLDER], filename) file.save(filepath) # 5. 准备调用星图平台API headers { Authorization: fBearer {XINGTU_API_KEY}, Content-Type: application/json } # 将图片转换为Base64编码这是API能理解的格式 with open(filepath, rb) as img_file: image_data img_file.read() import base64 image_base64 base64.b64encode(image_data).decode(utf-8) # 6. 构建请求体告诉模型“请看这张图然后回答这个问题” payload { model: MODEL_NAME, messages: [ { role: user, content: [ {type: text, text: question}, { type: image_url, image_url: { url: fdata:image/jpeg;base64,{image_base64} } } ] } ], max_tokens: 500 # 限制回答的最大长度 } # 7. 发送请求到星图平台API try: response requests.post(XINGTU_API_URL, jsonpayload, headersheaders) response.raise_for_status() # 如果请求失败如4xx, 5xx错误抛出异常 result response.json() # 8. 从API响应中提取AI的回答 answer result[choices][0][message][content] # 9. 清理临时图片文件 os.remove(filepath) # 10. 将答案返回给前端 return jsonify({answer: answer}) except requests.exceptions.RequestException as e: # 处理网络或API请求错误 os.remove(filepath) # 出错时也记得清理文件 return jsonify({error: f请求AI服务失败: {str(e)}}), 500 except KeyError as e: # 处理API响应格式不符合预期的情况 os.remove(filepath) return jsonify({error: 解析AI响应时出错}), 500这段代码看起来长但逻辑很清晰就像一份烹饪步骤单检查顾客前端是否递来了图片。检查图片是不是空的或者格式不对。拿到顾客想问的问题。把图片暂时存到厨房服务器的台面上。准备好给AI大厨星图平台API的“订单”请求头。把图片转换成AI大厨能看懂的格式Base64并写好订单内容请求体。把订单发出去。等AI大厨做完菜返回答案把答案从包装里拿出来。收拾厨房台面删除临时图片文件。把菜肴答案端给顾客返回给前端。最后我们加上启动服务器的代码if __name__ __main__: # 设置为True可以在代码修改时自动重启服务开发时很方便 app.run(debugTrue, host0.0.0.0, port5000)好了后端“后厨”的代码就全部写完了。保存好app.py文件。接下来我们需要打造一个漂亮的“餐厅门面”——也就是前端页面。3. 打造用户界面一个简单的前端页面前端页面是用户直接看到和操作的部分。我们需要一个能上传图片、输入问题、展示答案的界面。在janus_web_demo文件夹里新建一个名为templates的文件夹。然后在templates文件夹里创建我们的前端页面文件index.html。这个页面我们会用最基础的HTML、CSS和一点点JavaScript来实现保证清晰易懂。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title我的第一个Janus-Pro图文问答应用/title style * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; } body { max-width: 800px; margin: 40px auto; padding: 20px; background-color: #f5f7fa; color: #333; line-height: 1.6; } .container { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08); } h1 { color: #2c3e50; margin-top: 0; border-bottom: 2px solid #eee; padding-bottom: 15px; } .upload-area { border: 2px dashed #ccc; border-radius: 8px; padding: 40px 20px; text-align: center; margin: 25px 0; background-color: #fafafa; transition: border-color 0.3s; cursor: pointer; } .upload-area:hover, .upload-area.dragover { border-color: #3498db; background-color: #f0f8ff; } #imagePreview { max-width: 100%; max-height: 300px; margin-top: 15px; border-radius: 6px; display: none; /* 默认隐藏有图片时才显示 */ } textarea { width: 100%; padding: 15px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; resize: vertical; min-height: 100px; margin: 15px 0; } button { background-color: #3498db; color: white; border: none; padding: 14px 28px; font-size: 16px; border-radius: 8px; cursor: pointer; width: 100%; transition: background-color 0.2s; font-weight: 600; } button:hover { background-color: #2980b9; } button:disabled { background-color: #95a5a6; cursor: not-allowed; } .answer-area { margin-top: 30px; padding: 20px; background-color: #f8f9fa; border-radius: 8px; border-left: 4px solid #3498db; display: none; /* 默认隐藏有答案时才显示 */ } .loading { text-align: center; color: #7f8c8d; display: none; /* 默认隐藏 */ } .error { color: #e74c3c; background-color: #fdf0ed; padding: 12px; border-radius: 6px; margin-top: 15px; display: none; } /style /head body div classcontainer h1️ 向Janus-Pro提问/h1 p上传一张图片然后向Janus-Pro-7B模型提出你的问题吧/p !-- 图片上传区域 -- div classupload-area iddropArea pstrong点击此处或拖拽图片到此处上传/strong/p p支持 PNG, JPG, GIF, BMP 格式最大5MB/p input typefile idimageInput acceptimage/* styledisplay: none; img idimagePreview alt图片预览 /div !-- 问题输入区域 -- div label forquestionInputstrong你的问题/strong/label textarea idquestionInput placeholder例如这张图片里有什么描述一下场景。或者图片中的物体是什么颜色的/textarea /div !-- 提交按钮 -- button idsubmitBtn向Janus-Pro提问/button !-- 加载指示器 -- div classloading idloadingIndicator pJanus-Pro正在思考中请稍候.../p /div !-- 错误信息显示 -- div classerror iderrorMessage/div !-- 答案显示区域 -- div classanswer-area idanswerArea h3 Janus-Pro的回答/h3 p idanswerText/p /div /div script // 获取页面上的关键元素 const dropArea document.getElementById(dropArea); const imageInput document.getElementById(imageInput); const imagePreview document.getElementById(imagePreview); const questionInput document.getElementById(questionInput); const submitBtn document.getElementById(submitBtn); const loadingIndicator document.getElementById(loadingIndicator); const errorMessage document.getElementById(errorMessage); const answerArea document.getElementById(answerArea); const answerText document.getElementById(answerText); let selectedFile null; // 用来保存用户选择的文件 // 1. 点击上传区域触发文件选择 dropArea.addEventListener(click, () { imageInput.click(); }); // 2. 处理文件选择 imageInput.addEventListener(change, (event) { handleFileSelection(event.target.files[0]); }); // 3. 处理拖拽上传 dropArea.addEventListener(dragover, (event) { event.preventDefault(); dropArea.classList.add(dragover); }); dropArea.addEventListener(dragleave, () { dropArea.classList.remove(dragover); }); dropArea.addEventListener(drop, (event) { event.preventDefault(); dropArea.classList.remove(dragover); if (event.dataTransfer.files.length) { handleFileSelection(event.dataTransfer.files[0]); } }); // 处理选中的文件 function handleFileSelection(file) { if (!file || !file.type.startsWith(image/)) { showError(请选择一个有效的图片文件。); return; } if (file.size 5 * 1024 * 1024) { // 5MB限制 showError(图片大小不能超过5MB。); return; } selectedFile file; // 预览图片 const reader new FileReader(); reader.onload (e) { imagePreview.src e.target.result; imagePreview.style.display block; }; reader.readAsDataURL(file); // 清空之前的错误和答案 hideError(); hideAnswer(); } // 4. 处理提交按钮点击 submitBtn.addEventListener(click, async () { // 基础验证 if (!selectedFile) { showError(请先上传一张图片。); return; } const question questionInput.value.trim(); if (!question) { showError(请输入你的问题。); return; } // 准备发送数据 const formData new FormData(); formData.append(image, selectedFile); formData.append(question, question); // 更新UI状态显示加载中禁用按钮 loadingIndicator.style.display block; submitBtn.disabled true; hideError(); hideAnswer(); try { // 发送POST请求到我们的后端 /ask 接口 const response await fetch(/ask, { method: POST, body: formData }); const result await response.json(); if (!response.ok) { // 如果后端返回错误如400, 500状态码 throw new Error(result.error || 请求失败状态码: ${response.status}); } // 请求成功显示答案 showAnswer(result.answer); } catch (error) { // 捕获并显示任何错误 showError(出错了: ${error.message}); } finally { // 无论成功失败都恢复UI状态 loadingIndicator.style.display none; submitBtn.disabled false; } }); // 几个辅助函数用来显示/隐藏界面元素 function showError(message) { errorMessage.textContent message; errorMessage.style.display block; } function hideError() { errorMessage.style.display none; } function showAnswer(text) { answerText.textContent text; answerArea.style.display block; // 平滑滚动到答案区域 answerArea.scrollIntoView({ behavior: smooth, block: nearest }); } function hideAnswer() { answerArea.style.display none; } // 5. 允许按回车键提交问题在文本框中 questionInput.addEventListener(keypress, (event) { if (event.key Enter !event.shiftKey) { event.preventDefault(); // 防止文本框内换行 submitBtn.click(); // 触发提交按钮点击 } }); /script /body /html这个页面虽然代码不少但结构很清晰上半部分是样式style标签内定义了页面的外观让它看起来更美观。下半部分是HTML结构和逻辑script标签内。用户可以通过点击或拖拽上传图片。在文本框中输入问题。点击按钮后JavaScript会把图片和问题打包发送给我们刚才写好的后端/ask接口。然后等待后端返回AI的答案并把它漂亮地展示出来。至此我们前后端的代码都写完了。你的项目文件夹结构现在应该是这样的janus_web_demo/ ├── app.py # 后端Flask应用 ├── uploads/ # 空文件夹运行时自动创建存放临时图片 └── templates/ └── index.html # 前端网页万事俱备只差最后一步运行它4. 运行与测试让你的应用活起来让我们回到命令行窗口确保你还在之前创建的janus-web-app环境中命令行前面有(janus-web-app)提示。使用cd命令进入你的项目文件夹janus_web_demo。例如如果你的文件夹在桌面cd ~/Desktop/janus_web_demo # Mac/Linux # 或者 cd C:\Users\你的用户名\Desktop\janus_web_demo # Windows然后运行我们的后端服务python app.py如果一切顺利你会看到类似下面的输出* Serving Flask app app * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://你的本地IP:5000 Press CTRLC to quit这表示你的Flask应用已经在本地电脑的5000端口上运行起来了。现在打开你常用的浏览器Chrome、Edge、Firefox等在地址栏输入http://127.0.0.1:5000或者http://localhost:5000然后按回车。恭喜你应该能看到自己亲手搭建的“图文问答”应用界面了。接下来就是激动人心的测试时刻上传图片点击虚线区域从电脑里选一张你喜欢的图片或者直接把图片拖进去。你会看到图片的预览。输入问题在下面的文本框里用自然语言向图片提问。比如上传一张猫的图片然后问“这只猫是什么颜色的”或者“描述一下这张图片里的场景。”点击提问点击“向Janus-Pro提问”按钮。页面会显示“正在思考中...”按钮也会暂时变灰。查看答案稍等几秒到十几秒取决于图片大小和网络答案就会显示在下方。如果遇到了错误比如“请求AI服务失败”请检查后端终端窗口是否有红色的错误信息app.py文件里的XINGTU_API_KEY是否已经替换成你从星图平台获取的真实密钥网络连接是否正常5. 接下来可以做什么看到自己的应用成功运行是不是很有成就感这个简单的Demo已经包含了Web应用与AI模型交互的核心流程。你可以以此为起点尝试更多有趣的事情功能上可以尝试美化界面用更专业的CSS框架比如Bootstrap或者前端框架比如Vue.js/React重写前端让它更漂亮、交互更流畅。对话历史修改后端把每次的问答记录保存到数据库比如SQLite或MySQL然后在前端展示一个历史问答列表。多轮对话现在的应用是“一问一答”。你可以尝试修改API请求把历史对话也传给模型让它能进行上下文连贯的多轮对话。支持更多模型除了Janus-Pro-7B星图平台还有很多其他模型。你可以尝试在页面上加一个下拉框让用户选择不同的模型来回答问题。部署上线现在应用只在你的电脑上运行。你可以把它部署到云服务器比如阿里云、腾讯云的ECS这样别人也能通过互联网访问你的应用了。部署时记得把app.run(debugTrue)改成debugFalse。代码优化错误处理现在的错误处理还比较基础。你可以增加更多细节比如网络超时重试、图片格式更细致的校验等。代码结构随着功能变多可以把app.py里的代码按功能拆分成不同的文件比如routes.py放路由utils.py放工具函数这样更易于维护。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。