

在搭建本地大模型作为写作、论文小助手时,开发者常面临诸多技术难题:模型部署需研究复杂 API 服务,微调模型要应对框架选择与模型切换的困扰,工具落地还需掌握 Web 开发技能,这让初级开发者望而却步,资深专家也需为适配需求、集成新工具耗费大量精力。而 LazyLLM 多 Agent 大模型应用开发框架可有效解决这些问题,它打包了应用搭建、数据准备、模型部署、微调、评测等全环节工具。初级开发者借助预置组件即可打造有生产价值的 AI 工具,资深专家能依托其模块化设计集成自有算法与前沿工具,助力不同水平开发者低成本搭建专属本地大模型助手,摆脱技术卡壳难题。

一、前言:当 “AI 工具梦” 遇上 “技术拦路虎”?LazyLLM 来救场啦!
家人们,谁懂啊!想搭个本地大模型当写作、论文小助手,结果一开局就被 “技术大山” 拦住:要搞模型部署,得研究各种 API 服务的弯弯绕;想微调模型,选框架、切模型的操作能把人绕晕;好不容易搞定模型,还得懂 Web 开发才能把工具落地 —— 初级开发者看了直呼 “退!退!退!”,资深专家也得为适配不同需求、集成新工具费老大劲。

但别慌!现在 “救星” 来了 ——LazyLLM 多 Agent 大模型应用开发框架,简直是为解决这些痛点量身打造的。它把应用搭建、数据准备、模型部署、微调、评测等全环节的工具都打包好,初级开发者不用啃复杂技术细节,靠预置组件 “拼一拼”,就能搞出有生产价值的 AI 工具;资深专家也能借着它的模块化设计自由发挥,集成自家算法、前沿工具,轻松拿捏多样化需求。有了它,不管你是刚入门的 “AI 小白”,还是深耕领域的 “技术大牛”,都能低成本搭起专属的本地大模型写作、论文小助手,让 AI 助力学习工作,从此告别 “技术卡壳” 的烦恼!
二、讲一下 LazyLLM
LazyLLM 是构建和优化多 Agent 应用的一站式开发工具,为应用开发过程中的全部环节(包括应用搭建、数据准备、模型部署、模型微调、评测等)提供了大量的工具,协助开发者用极低的成本构建 AI 应用,并可以持续地迭代优化效果。

对于初级开发者,LazyLLM 彻底简化了AI应用的构建过程。用户不必了解不同模型 API 服务的构建细节,无需在微调模型时选择框架或切分模型,也不需要掌握任何Web开发知识,通过预置的组件和简单的拼接操作,初级开发者便能轻松构建出具备生产价值的工具。
对于资深的专家,LazyLLM 提供了极高的灵活性,为开发者提供了无限的可能性。其模块化设计支持高度的定制与扩展,使用户能够轻松集成自有算法、行业领先的生产工具以及最新的技术成果,从而快速构建适配多样化需求的强大应用。
📣 今天咱们用python本地搭建LazyLLM,并通过控制台、API、界面等方式应用!话不多说开始吧!
三、LazyLLM安装配置使用
3.1 安装应用
咱们先把基础门槛说清楚:电脑得提前装好 Python 运行环境哦~要是还没安排,直接戳博主这篇 Python 安装指南就能搞定;至于 LazyLLM 的安装配置,👆 点击跟着这份文案一步步来,保准不踩坑!
3.2 配置api服务应用
通过安装应用里面的教程,咱们已经安装好了LazyLLM,接下来咱们就开始写python代码调用LazyLLM,然后浏览器访问大模型。
py代码
1import lazyllm 2from fastapi import FastAPI, HTTPException 3from fastapi.middleware.cors import CORSMiddleware 4from pydantic import BaseModel 5import uvicorn 6import os 7import asyncio 8 9# 设置API Key 10os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY' 11 12# 创建聊天模块 13chat = lazyllm.OnlineChatModule() 14 15# 创建FastAPI应用 16app = FastAPI(title="LazyLLM Chat API", version="1.0.0") 17 18# 添加CORS中间件 19app.add_middleware( 20 CORSMiddleware, 21 allow_origins=["*"], 22 allow_credentials=True, 23 allow_methods=["*"], 24 allow_headers=["*"], 25) 26 27 28class ChatRequest(BaseModel): 29 message: str 30 model: str = "openai" 31 32 33class ChatResponse(BaseModel): 34 response: str 35 status: str = "success" 36 37 38@app.post("/chat", response_model=ChatResponse) 39async def chat_endpoint(request: ChatRequest): 40 try: 41 # 使用线程池执行同步的chat调用 42 loop = asyncio.get_event_loop() 43 response = await loop.run_in_executor(None, lambda: chat(request.message)) 44 return ChatResponse(response=response) 45 except Exception as e: 46 raise HTTPException(status_code=500, detail=f"Chat error: {str(e)}") 47 48 49@app.get("/") 50async def root(): 51 return { 52 "message": "LazyLLM Chat API is running", 53 "status": "healthy", 54 "endpoints": { 55 "chat": "POST /chat", 56 "docs": "GET /docs" 57 } 58 } 59 60 61@app.get("/health") 62async def health_check(): 63 return {"status": "healthy", "service": "lazyllm-chat"} 64 65 66if __name__ == "__main__": 67 print("🚀 启动 LazyLLM Chat API 服务器...") 68 print("📍 本地访问: http://localhost:23333") 69 print("📚 API文档: http://localhost:23333/docs") 70 print("⏹️ 按 Ctrl+C 停止服务器") 71 72 uvicorn.run( 73 app, 74 host="0.0.0.0", 75 port=23333, 76 log_level="info" 77 ) 78
家人们,这段代码堪称 “AI 新手的救命脚本”!先唠唠它干了啥 —— 开头先把 Python 界的 “明星工具” 们请上场:LazyLLM 负责当 “AI 聊天大脑”,FastAPI 负责搭 “API 小房子”,还贴心加了 CORS 中间件(懂的都懂,这是防止前端小伙伴跨域时 “撞墙”)。接着填个豆包 APIKey “激活大脑”,再用 OnlineChatModule 给 AI 安上 “聊天嘴”,最后写俩接口:/chat 负责接消息、回消息(还偷偷用 asyncio 搞了个 “线程池外挂”,怕同步调用卡壳),/ 和 /health 纯纯是 “报平安专用”,告诉你服务器没躺平。最贴心的是结尾还打印访问地址,生怕你找不到 API 文档入口,主打一个 “手把手教你当 AI 接口老板”!
别慌!就算你分不清 “async” 和 “sync”,这段代码也能让你半小时搞定 AI 聊天 API。第一步:把 “你的豆包 APIKey” 换成真家伙,像给手机插 SIM 卡一样简单;第二步:运行脚本,看着控制台蹦出 “启动成功” 的提示,比收到外卖到店通知还开心;第三步:打开http://localhost:23333/docs,点 “Try it out” 输句话,AI 秒回的感觉,比微信秒回还爽!唯一要注意的是,别把 APIKey 泄露出去 —— 不然别人用你的 Key 聊天,就像用你的奶茶券喝奶茶一样亏!总之,有了这段代码,不用再求后端大佬写接口,自己就能搞个专属 AI 聊天 API,成就感直接拉满!
运行效果

1Prompt:你好 2AI回复:你好呀!有什么问题都可以跟我说,我会尽力帮你解答。 3

3.3 配置web服务应用
上面咱们配置了api服务应用,接下来咱们按照上面的思路配置一下web服务应用。
py代码
1import lazyllm 2from fastapi import FastAPI, Request, HTTPException 3from fastapi.responses import HTMLResponse, JSONResponse 4import uvicorn 5import os 6import json 7 8# 设置API Key 9os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY' 10 11# 创建聊天模块 12chat = lazyllm.OnlineChatModule() 13 14# 创建FastAPI应用 15app = FastAPI(title="LazyLLM Chat", version="1.0.0") 16 17# 添加CORS支持 18from fastapi.middleware.cors import CORSMiddleware 19 20app.add_middleware( 21 CORSMiddleware, 22 allow_origins=["*"], 23 allow_credentials=True, 24 allow_methods=["*"], 25 allow_headers=["*"], 26) 27 28# HTML页面内容 29HTML_PAGE = """ 30<!DOCTYPE html> 31<html lang="zh-CN"> 32<head> 33 <meta charset="UTF-8"> 34 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 35 <title>LazyLLM Chat</title> 36 <style> 37 * { 38 margin: 0; 39 padding: 0; 40 box-sizing: border-box; 41 } 42 43 body { 44 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 45 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 46 min-height: 100vh; 47 display: flex; 48 justify-content: center; 49 align-items: center; 50 padding: 20px; 51 } 52 53 .chat-container { 54 background: white; 55 border-radius: 15px; 56 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 57 width: 100%; 58 max-width: 800px; 59 height: 600px; 60 display: flex; 61 flex-direction: column; 62 overflow: hidden; 63 } 64 65 .chat-header { 66 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 67 color: white; 68 padding: 20px; 69 text-align: center; 70 } 71 72 .chat-header h1 { 73 font-size: 24px; 74 font-weight: 300; 75 } 76 77 .chat-messages { 78 flex: 1; 79 padding: 20px; 80 overflow-y: auto; 81 display: flex; 82 flex-direction: column; 83 gap: 15px; 84 } 85 86 .message { 87 max-width: 70%; 88 padding: 12px 16px; 89 border-radius: 18px; 90 margin-bottom: 10px; 91 animation: fadeIn 0.3s ease-in; 92 } 93 94 .user-message { 95 align-self: flex-end; 96 background: #007bff; 97 color: white; 98 border-bottom-right-radius: 5px; 99 } 100 101 .bot-message { 102 align-self: flex-start; 103 background: #f1f3f5; 104 color: #333; 105 border-bottom-left-radius: 5px; 106 } 107 108 .chat-input { 109 padding: 20px; 110 background: #f8f9fa; 111 border-top: 1px solid #e9ecef; 112 display: flex; 113 gap: 10px; 114 } 115 116 .message-input { 117 flex: 1; 118 padding: 12px 16px; 119 border: 2px solid #e9ecef; 120 border-radius: 25px; 121 outline: none; 122 font-size: 14px; 123 transition: border-color 0.3s; 124 } 125 126 .message-input:focus { 127 border-color: #007bff; 128 } 129 130 .send-button { 131 padding: 12px 24px; 132 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 133 color: white; 134 border: none; 135 border-radius: 25px; 136 cursor: pointer; 137 font-weight: 500; 138 transition: transform 0.2s; 139 } 140 141 .send-button:hover { 142 transform: translateY(-2px); 143 } 144 145 .send-button:active { 146 transform: translateY(0); 147 } 148 149 .typing-indicator { 150 align-self: flex-start; 151 background: #f1f3f5; 152 color: #666; 153 padding: 12px 16px; 154 border-radius: 18px; 155 border-bottom-left-radius: 5px; 156 font-style: italic; 157 } 158 159 @keyframes fadeIn { 160 from { 161 opacity: 0; 162 transform: translateY(10px); 163 } 164 to { 165 opacity: 1; 166 transform: translateY(0); 167 } 168 } 169 170 /* 滚动条样式 */ 171 .chat-messages::-webkit-scrollbar { 172 width: 6px; 173 } 174 175 .chat-messages::-webkit-scrollbar-track { 176 background: #f1f1f1; 177 border-radius: 3px; 178 } 179 180 .chat-messages::-webkit-scrollbar-thumb { 181 background: #c1c1c1; 182 border-radius: 3px; 183 } 184 185 .chat-messages::-webkit-scrollbar-thumb:hover { 186 background: #a8a8a8; 187 } 188 </style> 189</head> 190<body> 191 <div class="chat-container"> 192 <div class="chat-header"> 193 <h1>🤖 LazyLLM 智能聊天</h1> 194 </div> 195 196 <div class="chat-messages" id="chatMessages"> 197 <div class="message bot-message"> 198 你好!我是LazyLLM助手,有什么可以帮您的吗? 199 </div> 200 </div> 201 202 <div class="chat-input"> 203 <input 204 type="text" 205 class="message-input" 206 id="messageInput" 207 placeholder="输入您的消息..." 208 autocomplete="off" 209 > 210 <button class="send-button" onclick="sendMessage()">发送</button> 211 </div> 212 </div> 213 214 <script> 215 const chatMessages = document.getElementById('chatMessages'); 216 const messageInput = document.getElementById('messageInput'); 217 218 // 自动聚焦输入框 219 messageInput.focus(); 220 221 function addMessage(text, isUser = false) { 222 const messageDiv = document.createElement('div'); 223 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 224 messageDiv.textContent = text; 225 chatMessages.appendChild(messageDiv); 226 chatMessages.scrollTop = chatMessages.scrollHeight; 227 } 228 229 function showTyping() { 230 const typingDiv = document.createElement('div'); 231 typingDiv.className = 'message typing-indicator'; 232 typingDiv.id = 'typingIndicator'; 233 typingDiv.textContent = 'AI正在思考...'; 234 chatMessages.appendChild(typingDiv); 235 chatMessages.scrollTop = chatMessages.scrollHeight; 236 } 237 238 function hideTyping() { 239 const typingIndicator = document.getElementById('typingIndicator'); 240 if (typingIndicator) { 241 typingIndicator.remove(); 242 } 243 } 244 245 async function sendMessage() { 246 const message = messageInput.value.trim(); 247 if (!message) return; 248 249 // 添加用户消息 250 addMessage(message, true); 251 messageInput.value = ''; 252 253 // 显示正在输入提示 254 showTyping(); 255 256 try { 257 const response = await fetch('/chat', { 258 method: 'POST', 259 headers: { 260 'Content-Type': 'application/json', 261 }, 262 body: JSON.stringify({ message: message }) 263 }); 264 265 if (!response.ok) { 266 throw new Error('网络请求失败'); 267 } 268 269 const data = await response.json(); 270 hideTyping(); 271 addMessage(data.response); 272 273 } catch (error) { 274 hideTyping(); 275 addMessage('抱歉,发生错误:' + error.message); 276 console.error('Error:', error); 277 } 278 } 279 280 // 支持回车键发送 281 messageInput.addEventListener('keypress', function(e) { 282 if (e.key === 'Enter') { 283 sendMessage(); 284 } 285 }); 286 287 // 自动调整输入框高度 288 messageInput.addEventListener('input', function() { 289 this.style.height = 'auto'; 290 this.style.height = (this.scrollHeight) + 'px'; 291 }); 292 </script> 293</body> 294</html> 295""" 296 297 298@app.get("/", response_class=HTMLResponse) 299async def get_chat_interface(): 300 """返回聊天界面""" 301 return HTMLResponse(content=HTML_PAGE, media_type="text/html") 302 303 304@app.post("/chat") 305async def chat_endpoint(request: dict): 306 """处理聊天消息""" 307 try: 308 message = request.get("message", "").strip() 309 if not message: 310 raise HTTPException(status_code=400, detail="消息不能为空") 311 312 print(f"收到消息: {message}") 313 314 # 调用LazyLLM聊天模块 315 response = chat(message) 316 317 print(f"AI回复: {response}") 318 319 return JSONResponse(content={ 320 "response": response, 321 "status": "success" 322 }) 323 324 except Exception as e: 325 print(f"错误: {e}") 326 return JSONResponse( 327 status_code=500, 328 content={ 329 "response": f"抱歉,发生错误: {str(e)}", 330 "status": "error" 331 } 332 ) 333 334 335@app.get("/health") 336async def health_check(): 337 """健康检查端点""" 338 return {"status": "healthy", "service": "lazyllm-chat"} 339 340 341if __name__ == "__main__": 342 print("🚀 启动 LazyLLM Web 聊天服务器...") 343 print("📍 本地访问: http://localhost:23333") 344 print("🤖 准备好与AI聊天了!") 345 print("⏹️ 按 Ctrl+C 停止服务器") 346 347 # 测试基本功能 348 try: 349 test_response = chat("你好") 350 print(f"✅ 基本功能测试成功: {test_response[:50]}...") 351 except Exception as e: 352 print(f"❌ 基本功能测试失败: {e}") 353 print("请检查API Key设置") 354 355 uvicorn.run( 356 app, 357 host="0.0.0.0", 358 port=23333, 359 log_level="info" 360 ) 361
运行效果

3.4 配置控制台服务应用
上面咱们配置了web服务应用,接下来咱们按照上面的思路配置一下控制台服务应用。
py代码
1import lazyllm 2import os 3 4# 设置API Key 5os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY' 6 7 8def main(): 9 print("🤖 LazyLLM 命令行聊天") 10 print("输入 'quit' 或 'exit' 退出") 11 print("-" * 50) 12 13 chat = lazyllm.OnlineChatModule() 14 15 while True: 16 try: 17 user_input = input("👤 你: ").strip() 18 19 if user_input.lower() in ['quit', 'exit', 'q']: 20 print("再见!👋") 21 break 22 23 if not user_input: 24 continue 25 26 print("🤖 AI: ", end="", flush=True) 27 response = chat(user_input) 28 print(response) 29 print() 30 31 except KeyboardInterrupt: 32 print("\n再见!👋") 33 break 34 except Exception as e: 35 print(f"❌ 错误: {e}") 36 37 38if __name__ == "__main__": 39 main() 40
运行效果

四、写作小助手配置
4.1 提示词配置
咱先明确目标:把 LazyLLM 的 “基础聊天功能” 升级成 “专属写作助手”,核心就俩事儿 ——写对提示词让 AI 懂 “写作规矩”,选对 MCP 模块让功能落地,全程像给手机装专属 APP 一样简单,小白也能跟着走!
1【身份定义】你是一名专业写作助手,擅长学术论文、职场报告、自媒体文案等多种文体创作,语言严谨且流畅,拒绝口语化和无关内容。 2【任务要求】当用户输入“写作需求+文体+字数/核心要点”时,你需要先拆解需求(比如用户说“写300字产品推广文案,产品是无线耳机,卖点是降噪+长续航”,你要先确认“文体:推广文案,字数:300字,核心要素:无线耳机、降噪、长续航”),再按“开头吸引注意力+中间讲卖点+结尾引导行动”的结构创作,创作后补充1-2句“修改建议”(比如“若需突出价格优势,可补充XX内容”)。 3【禁忌提醒】不偏离用户指定的文体和字数,不添加与写作无关的闲聊内容,若用户需求模糊(比如只说“写篇文章”),要主动追问“请问需要哪种文体(如论文/文案)、大概多少字、有哪些核心要点呀?” 4
为啥这么写?举个反例:要是只写 “帮我写作”,AI 可能一会儿写散文一会儿写段子;但加了 “身份 + 任务 + 禁忌”,AI 就像拿到 “标准答案提纲”,绝不会跑偏。写完后点 “保存提示词”,这一步别忘!就像手机设置完壁纸要确认一样。
4.2 代码调整
1import lazyllm 2from fastapi import FastAPI, Request, HTTPException 3from fastapi.responses import HTMLResponse, JSONResponse 4import uvicorn 5import os 6import json 7 8# 设置API Key 9os.environ['LAZYLLM_DOUBAO_API_KEY'] = '填写你的豆包API-KEY' 10 11# 创建聊天模块 12chat = lazyllm.OnlineChatModule() 13 14# 创建FastAPI应用 15app = FastAPI(title="LazyLLM Chat", version="1.0.0") 16 17# 添加CORS支持 18from fastapi.middleware.cors import CORSMiddleware 19 20app.add_middleware( 21 CORSMiddleware, 22 allow_origins=["*"], 23 allow_credentials=True, 24 allow_methods=["*"], 25 allow_headers=["*"], 26) 27 28# HTML页面内容 29HTML_PAGE = """ 30<!DOCTYPE html> 31<html lang="zh-CN"> 32<head> 33 <meta charset="UTF-8"> 34 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 35 <title>写作小助手 - LazyLLM</title> 36 <style> 37 * { 38 margin: 0; 39 padding: 0; 40 box-sizing: border-box; 41 } 42 43 body { 44 font-family: '华文中宋'; 45 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 46 min-height: 100vh; 47 display: flex; 48 justify-content: center; 49 align-items: center; 50 padding: 20px; 51 } 52 53 .chat-container { 54 background: white; 55 border-radius: 15px; 56 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 57 width: 100%; 58 max-width: 800px; 59 height: 600px; 60 display: flex; 61 flex-direction: column; 62 overflow: hidden; 63 } 64 65 .chat-header { 66 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 67 color: white; 68 padding: 20px; 69 text-align: center; 70 } 71 72 .chat-header h1 { 73 font-size: 24px; 74 font-weight: 300; 75 } 76 77 .chat-messages { 78 flex: 1; 79 padding: 20px; 80 overflow-y: auto; 81 display: flex; 82 flex-direction: column; 83 gap: 15px; 84 } 85 86 .message { 87 max-width: 70%; 88 padding: 12px 16px; 89 border-radius: 18px; 90 margin-bottom: 10px; 91 animation: fadeIn 0.3s ease-in; 92 } 93 94 .user-message { 95 align-self: flex-end; 96 background: #007bff; 97 color: white; 98 border-bottom-right-radius: 5px; 99 } 100 101 .bot-message { 102 align-self: flex-start; 103 background: #f1f3f5; 104 color: #333; 105 border-bottom-left-radius: 5px; 106 } 107 108 .chat-input { 109 padding: 20px; 110 background: #f8f9fa; 111 border-top: 1px solid #e9ecef; 112 display: flex; 113 gap: 10px; 114 } 115 116 .message-input { 117 flex: 1; 118 padding: 12px 16px; 119 border: 2px solid #e9ecef; 120 border-radius: 25px; 121 outline: none; 122 font-size: 14px; 123 transition: border-color 0.3s; 124 } 125 126 .message-input:focus { 127 border-color: #007bff; 128 } 129 130 .send-button { 131 padding: 12px 24px; 132 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 133 color: white; 134 border: none; 135 border-radius: 25px; 136 cursor: pointer; 137 font-weight: 500; 138 transition: transform 0.2s; 139 } 140 141 .send-button:hover { 142 transform: translateY(-2px); 143 } 144 145 .send-button:active { 146 transform: translateY(0); 147 } 148 149 .typing-indicator { 150 align-self: flex-start; 151 background: #f1f3f5; 152 color: #666; 153 padding: 12px 16px; 154 border-radius: 18px; 155 border-bottom-left-radius: 5px; 156 font-style: italic; 157 } 158 159 @keyframes fadeIn { 160 from { 161 opacity: 0; 162 transform: translateY(10px); 163 } 164 to { 165 opacity: 1; 166 transform: translateY(0); 167 } 168 } 169 170 /* 滚动条样式 */ 171 .chat-messages::-webkit-scrollbar { 172 width: 6px; 173 } 174 175 .chat-messages::-webkit-scrollbar-track { 176 background: #f1f1f1; 177 border-radius: 3px; 178 } 179 180 .chat-messages::-webkit-scrollbar-thumb { 181 background: #c1c1c1; 182 border-radius: 3px; 183 } 184 185 .chat-messages::-webkit-scrollbar-thumb:hover { 186 background: #a8a8a8; 187 } 188 </style> 189</head> 190<body> 191 <div class="chat-container"> 192 <div class="chat-header"> 193 <h1>✨ 写作小助手 - LazyLLM</h1> 194 </div> 195 196 <div class="chat-messages" id="chatMessages"> 197 <div class="message bot-message"> 198 你好!我是写作小助手,有什么可以帮您的吗? 199 </div> 200 </div> 201 202 <div class="chat-input"> 203 <input 204 type="text" 205 class="message-input" 206 id="messageInput" 207 placeholder="输入您的消息..." 208 autocomplete="off" 209 > 210 <button class="send-button" onclick="sendMessage()">发送</button> 211 </div> 212 </div> 213 214 <script> 215 const chatMessages = document.getElementById('chatMessages'); 216 const messageInput = document.getElementById('messageInput'); 217 218 // 自动聚焦输入框 219 messageInput.focus(); 220 221 function addMessage(text, isUser = false) { 222 const messageDiv = document.createElement('div'); 223 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 224 messageDiv.textContent = text; 225 chatMessages.appendChild(messageDiv); 226 chatMessages.scrollTop = chatMessages.scrollHeight; 227 } 228 229 function showTyping() { 230 const typingDiv = document.createElement('div'); 231 typingDiv.className = 'message typing-indicator'; 232 typingDiv.id = 'typingIndicator'; 233 typingDiv.textContent = 'AI正在思考...'; 234 chatMessages.appendChild(typingDiv); 235 chatMessages.scrollTop = chatMessages.scrollHeight; 236 } 237 238 function hideTyping() { 239 const typingIndicator = document.getElementById('typingIndicator'); 240 if (typingIndicator) { 241 typingIndicator.remove(); 242 } 243 } 244 245 async function sendMessage() { 246 const message = messageInput.value.trim(); 247 if (!message) return; 248 249 // 添加用户消息 250 addMessage(message, true); 251 messageInput.value = ''; 252 253 // 显示正在输入提示 254 showTyping(); 255 256 try { 257 const response = await fetch('/chat', { 258 method: 'POST', 259 headers: { 260 'Content-Type': 'application/json', 261 }, 262 body: JSON.stringify({ message: message }) 263 }); 264 265 if (!response.ok) { 266 throw new Error('网络请求失败'); 267 } 268 269 const data = await response.json(); 270 hideTyping(); 271 addMessage(data.response); 272 273 } catch (error) { 274 hideTyping(); 275 addMessage('抱歉,发生错误:' + error.message); 276 console.error('Error:', error); 277 } 278 } 279 280 // 支持回车键发送 281 messageInput.addEventListener('keypress', function(e) { 282 if (e.key === 'Enter') { 283 sendMessage(); 284 } 285 }); 286 287 // 自动调整输入框高度 288 messageInput.addEventListener('input', function() { 289 this.style.height = 'auto'; 290 this.style.height = (this.scrollHeight) + 'px'; 291 }); 292 </script> 293</body> 294</html> 295""" 296 297 298@app.get("/", response_class=HTMLResponse) 299async def get_chat_interface(): 300 """返回聊天界面""" 301 return HTMLResponse(content=HTML_PAGE, media_type="text/html") 302 303 304@app.post("/chat") 305async def chat_endpoint(request: dict): 306 """处理聊天消息""" 307 try: 308 message = request.get("message", "").strip() 309 if not message: 310 raise HTTPException(status_code=400, detail="消息不能为空") 311 312 print(f"收到消息: {message}") 313 314 # 调用LazyLLM聊天模块 315 response = chat(message) 316 317 print(f"AI回复: {response}") 318 319 return JSONResponse(content={ 320 "response": response, 321 "status": "success" 322 }) 323 324 except Exception as e: 325 print(f"错误: {e}") 326 return JSONResponse( 327 status_code=500, 328 content={ 329 "response": f"抱歉,发生错误: {str(e)}", 330 "status": "error" 331 } 332 ) 333 334 335@app.get("/health") 336async def health_check(): 337 """健康检查端点""" 338 return {"status": "healthy", "service": "lazyllm-chat"} 339 340 341if __name__ == "__main__": 342 print("🚀 启动 LazyLLM Web 聊天服务器...") 343 print("📍 本地访问: http://localhost:23333") 344 print("🤖 准备好与AI聊天了!") 345 print("⏹️ 按 Ctrl+C 停止服务器") 346 347 # 测试基本功能 348 try: 349 test_response = chat("你好") 350 print(f"✅ 基本功能测试成功: {test_response[:50]}...") 351 except Exception as e: 352 print(f"❌ 基本功能测试失败: {e}") 353 print("请检查API Key设置") 354 355 uvicorn.run( 356 app, 357 host="0.0.0.0", 358 port=23333, 359 log_level="info" 360 ) 361
4.3 写一篇散文
提示词: 帮我写一篇形容女生很漂亮的散文
运行效果

4.4 带大纲写一篇文章
py代码:
1import lazyllm 2from fastapi import FastAPI, Request, HTTPException 3from fastapi.responses import HTMLResponse, JSONResponse 4import uvicorn 5import os 6import json 7 8# 设置API Key 9os.environ['LAZYLLM_DOUBAO_API_KEY'] = '豆包API_KEY' 10 11# 创建处理管道 12toc_prompt = """你现在是一个智能助手。你的任务是理解用户的输入,将大纲以列表嵌套字典的列表。每个字典包含一个 [`title`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.title.md) 和 `describe`,其中 [`title`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.title.md) 中需要用Markdown格式标清层级,`describe` 是对该段的描述和写作指导。 13 14请根据以下用户输入生成相应的列表嵌套字典: 15 16输出示例: 17[ 18 { 19 "title": "# 一级标题", 20 "describe": "请详细描述此标题的内容,提供背景信息和核心观点。" 21 }, 22 { 23 "title": "## 二级标题", 24 "describe": "请详细描述标题的内容,提供具体的细节和例子来支持一级标题的观点。" 25 }, 26 { 27 "title": "### 三级标题", 28 "describe": "请详细描述标题的内容,深入分析并提供更多的细节和数据支持。" 29 } 30] 31用户输入如下: 32""" 33 34completion_prompt = """ 35你现在是一个智能助手。你的任务是接收一个包含 [`title`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.title.md) 和 `describe` 的字典,并根据 `describe` 中的指导展开写作 36输入示例: 37{ 38 "title": "# 一级标题", 39 "describe": "这是写作的描述。" 40} 41 42输出: 43这是展开写作写的内容 44接收如下: 45 46""" 47 48# 创建大纲生成器和内容生成器 49toc_generator = lazyllm.OnlineChatModule() 50content_generator = lazyllm.OnlineChatModule() 51 52# 创建FastAPI应用 53app = FastAPI(title="智能写作助手", version="1.0.0") 54 55# 添加CORS支持 56from fastapi.middleware.cors import CORSMiddleware 57 58app.add_middleware( 59 CORSMiddleware, 60 allow_origins=["*"], 61 allow_credentials=True, 62 allow_methods=["*"], 63 allow_headers=["*"], 64) 65 66# HTML页面内容 67HTML_PAGE = """ 68<!DOCTYPE html> 69<html lang="zh-CN"> 70<head> 71 <meta charset="UTF-8"> 72 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 73 <title>智能写作助手</title> 74 <style> 75 * { 76 margin: 0; 77 padding: 0; 78 box-sizing: border-box; 79 } 80 81 body { 82 font-family: '华文中宋', sans-serif; 83 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 84 min-height: 100vh; 85 padding: 20px; 86 } 87 88 .container { 89 max-width: 1200px; 90 margin: 0 auto; 91 background: white; 92 border-radius: 15px; 93 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 94 overflow: hidden; 95 } 96 97 .header { 98 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 99 color: white; 100 padding: 20px; 101 text-align: center; 102 } 103 104 .main-content { 105 display: flex; 106 gap: 20px; 107 padding: 20px; 108 } 109 110 .input-section { 111 flex: 1; 112 } 113 114 .output-section { 115 flex: 2; 116 } 117 118 .section-title { 119 margin-bottom: 15px; 120 color: #333; 121 padding-bottom: 10px; 122 border-bottom: 2px solid #f1f3f5; 123 } 124 125 textarea { 126 width: 100%; 127 padding: 15px; 128 border: 2px solid #e9ecef; 129 border-radius: 10px; 130 min-height: 150px; 131 resize: vertical; 132 font-family: inherit; 133 margin-bottom: 15px; 134 font-size: 14px; 135 } 136 137 .button-group { 138 display: flex; 139 gap: 10px; 140 margin-bottom: 20px; 141 } 142 143 button { 144 padding: 12px 24px; 145 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 146 color: white; 147 border: none; 148 border-radius: 25px; 149 cursor: pointer; 150 font-weight: 500; 151 transition: transform 0.2s; 152 } 153 154 button:hover { 155 transform: translateY(-2px); 156 } 157 158 button:active { 159 transform: translateY(0); 160 } 161 162 .outline-container, .article-container { 163 background: #f8f9fa; 164 border-radius: 10px; 165 padding: 20px; 166 max-height: 400px; 167 overflow-y: auto; 168 margin-bottom: 20px; 169 } 170 171 .outline-item { 172 margin-bottom: 15px; 173 padding-left: 10px; 174 } 175 176 .outline-title { 177 font-weight: bold; 178 margin-bottom: 5px; 179 } 180 181 .outline-desc { 182 color: #666; 183 font-size: 14px; 184 } 185 186 .loading { 187 color: #666; 188 padding: 10px; 189 font-style: italic; 190 } 191 192 .article-content h1, .article-content h2, .article-content h3 { 193 margin-top: 20px; 194 margin-bottom: 10px; 195 } 196 197 .article-content p { 198 margin-bottom: 15px; 199 line-height: 1.6; 200 } 201 </style> 202</head> 203<body> 204 <div class="container"> 205 <div class="header"> 206 <h1>智能写作助手</h1> 207 </div> 208 209 <div class="main-content"> 210 <div class="input-section"> 211 <h2 class="section-title">写作主题</h2> 212 <textarea id="topicInput" placeholder="请输入您想要写作的主题..."></textarea> 213 214 <div class="button-group"> 215 <button onclick="generateOutline()">生成大纲</button> 216 <button onclick="generateArticle()">生成文章</button> 217 </div> 218 219 <h2 class="section-title">生成的大纲</h2> 220 <div class="outline-container" id="outlineContainer"> 221 请先输入主题并生成大纲 222 </div> 223 </div> 224 225 <div class="output-section"> 226 <h2 class="section-title">生成的文章</h2> 227 <div class="article-container" id="articleContainer"> 228 文章将在这里显示 229 </div> 230 </div> 231 </div> 232 </div> 233 234 <script> 235 let currentOutline = []; 236 237 async function generateOutline() { 238 const topic = document.getElementById('topicInput').value.trim(); 239 if (!topic) { 240 alert('请输入写作主题'); 241 return; 242 } 243 244 const outlineContainer = document.getElementById('outlineContainer'); 245 outlineContainer.innerHTML = '<div class="loading">正在生成大纲...</div>'; 246 247 try { 248 const response = await fetch('/generate-outline', { 249 method: 'POST', 250 headers: { 251 'Content-Type': 'application/json', 252 }, 253 body: JSON.stringify({ topic: topic }) 254 }); 255 256 if (!response.ok) { 257 throw new Error('生成大纲失败'); 258 } 259 260 const data = await response.json(); 261 currentOutline = data.outline; 262 263 // 显示大纲 264 let outlineHtml = ''; 265 currentOutline.forEach(item => { 266 outlineHtml += ` 267 <div class="outline-item"> 268 <div class="outline-title">${item.title}</div> 269 <div class="outline-desc">${item.describe}</div> 270 </div> 271 `; 272 }); 273 274 outlineContainer.innerHTML = outlineHtml; 275 276 } catch (error) { 277 outlineContainer.innerHTML = `生成大纲时出错: ${error.message}`; 278 console.error(error); 279 } 280 } 281 282 async function generateArticle() { 283 if (currentOutline.length === 0) { 284 alert('请先生成大纲'); 285 return; 286 } 287 288 const articleContainer = document.getElementById('articleContainer'); 289 articleContainer.innerHTML = '<div class="loading">正在生成文章...</div>'; 290 291 try { 292 const response = await fetch('/generate-article', { 293 method: 'POST', 294 headers: { 295 'Content-Type': 'application/json', 296 }, 297 body: JSON.stringify({ outline: currentOutline }) 298 }); 299 300 if (!response.ok) { 301 throw new Error('生成文章失败'); 302 } 303 304 const data = await response.json(); 305 articleContainer.innerHTML = data.article; 306 307 } catch (error) { 308 articleContainer.innerHTML = `生成文章时出错: ${error.message}`; 309 console.error(error); 310 } 311 } 312 </script> 313</body> 314</html> 315""" 316 317 318@app.get("/", response_class=HTMLResponse) 319async def get_writer_interface(): 320 """返回智能写作助手界面""" 321 return HTMLResponse(content=HTML_PAGE, media_type="text/html") 322 323 324@app.post("/generate-outline") 325async def generate_outline(request: dict): 326 """生成写作大纲""" 327 try: 328 topic = request.get("topic", "").strip() 329 if not topic: 330 raise HTTPException(status_code=400, detail="主题不能为空") 331 332 print(f"收到写作主题: {topic}") 333 334 # 生成大纲 335 full_prompt = toc_prompt + topic 336 outline_str = toc_generator(full_prompt) 337 338 # 解析为JSON 339 outline = json.loads(outline_str) 340 341 print(f"生成大纲成功,共{len(outline)}个节点") 342 343 return JSONResponse(content={ 344 "outline": outline, 345 "status": "success" 346 }) 347 348 except json.JSONDecodeError: 349 return JSONResponse( 350 status_code=500, 351 content={ 352 "message": "解析大纲失败", 353 "status": "error" 354 } 355 ) 356 except Exception as e: 357 print(f"生成大纲错误: {e}") 358 return JSONResponse( 359 status_code=500, 360 content={ 361 "message": f"生成大纲时发生错误: {str(e)}", 362 "status": "error" 363 } 364 ) 365 366 367@app.post("/generate-article") 368async def generate_article(request: dict): 369 """根据大纲生成文章""" 370 try: 371 outline = request.get("outline", []) 372 if not outline: 373 raise HTTPException(status_code=400, detail="大纲不能为空") 374 375 print(f"开始根据大纲生成文章,共{len(outline)}个节点") 376 377 article_parts = [] 378 379 # 逐个处理大纲节点 380 for item in outline: 381 title = item.get("title", "") 382 describe = item.get("describe", "") 383 384 if not title or not describe: 385 continue 386 387 # 生成内容 388 prompt = completion_prompt + json.dumps(item, ensure_ascii=False) 389 content = content_generator(prompt) 390 391 # 添加到文章部分 392 article_parts.append(f"{title}\n\n{content}") 393 394 # 合并所有部分 395 full_article = "\n\n".join(article_parts) 396 397 print("文章生成完成") 398 399 return JSONResponse(content={ 400 "article": full_article, 401 "status": "success" 402 }) 403 404 except Exception as e: 405 print(f"生成文章错误: {e}") 406 return JSONResponse( 407 status_code=500, 408 content={ 409 "message": f"生成文章时发生错误: {str(e)}", 410 "status": "error" 411 } 412 ) 413 414 415@app.get("/health") 416async def health_check(): 417 """健康检查端点""" 418 return {"status": "healthy", "service": "intelligent-writer"} 419 420 421if __name__ == "__main__": 422 print("🚀 启动智能写作助手服务器...") 423 print("📍 本地访问: http://localhost:23333") 424 print("⏹️ 按 Ctrl+C 停止服务器") 425 426 uvicorn.run( 427 app, 428 host="0.0.0.0", 429 port=23333, 430 log_level="info" 431 ) 432
运行效果

4.5 多模态聊天
为了增加我们机器人的功能,让它不仅会画画,还要让它能语音识别、编曲、图文问答等,让它具有多媒体的能力。这里我们将引入以下模型:
ChatTTS:用于将文本转换为语音;musicgen-small:用于生成音乐;stable-diffusion-3-medium: 沿用上一节 绘画大师 的模型,用于生成图像;internvl-chat-2b-v1-5:用于图文问答;SenseVoiceSmall: 用于语音识别;
我们注意到引入的模型中有生成图像和生成音乐的模型,他们对提示词的要求都相对较高, 我们需要依靠一个 LLM 来实现提示词的生成和翻译,就像是上一节 绘画大师 那样。
另外由于引入了大量的模型,我们需要一个意图识别机器人来实现对用户意图的转发,把用户的意图路由给对应的模型。

具体帮助文档:https://docs.lazyllm.ai/zh-cn/stable/Cookbook/multimodal%5Frobot/
让我们把上面定义好的模型组装起来:
1with pipeline() as ppl: ppl.cls = base ppl.cls_normalizer = lambda x: x if x in chatflow_intent_list else chatflow_intent_list[0] with switch(judge_on_full_input=False).bind(_0, ppl.input) as ppl.sw: ppl.sw.case[chatflow_intent_list[0], chat] ppl.sw.case[chatflow_intent_list[1], TrainableModule('SenseVoiceSmall')] ppl.sw.case[chatflow_intent_list[2], TrainableModule('internvl-chat-2b-v1-5').deploy_method(deploy.LMDeploy)] ppl.sw.case[chatflow_intent_list[3], pipeline(base.share().prompt(painter_prompt), TrainableModule('stable-diffusion-3-medium'))] ppl.sw.case[chatflow_intent_list[4], pipeline(base.share().prompt(musician_prompt), TrainableModule('musicgen-small'))] ppl.sw.case[chatflow_intent_list[5], TrainableModule('ChatTTS')] 2
在上面代码中,我们首先实例化了一个顺序执行的 ppl,在这个 ppl 中先进行意图识别,设置ppl.cls。 然后为了保证意图识别的鲁棒性,在意图识别之后设置一个 ppl.cls_normalizer 的匿名函数, 将识别的结果仅映射到预制列表属性项中,即:确保识别的结果只在预制表内。
1with switch(judge_on_full_input=False).bind(_0, ppl.input) as ppl.sw: 2
对于这行代码:
- 我们首先关注
bind(_0, ppl.input), 其中 _0 是上一步输出的结果第0个参数,即意图列表中的一个意图。ppl.input是用户的输入(对应设计图中红色线条)。所以这行代码是给switch控制流设置了两个参数,第一个参数是意图,第二个参数是用户的输入。更多 bind 使用方法参见:参数绑定 - 然后
judge_on_full_input=False,可以将输入分为两部分,第一部分是作为判断条件,剩下部分作为分支的输入,否则如果为 True 就会把整个输入作为判断条件和分支输入。 - 最后我们将实例化后的 switch 也添加到了 ppl 上:
ppl.sw。更多参见:Switch。
剩下代码基本一致,都是设置条件和对应路由分支,以下面代码为例:
1ppl.sw.case[chatflow_intent_list[0], chat] 2
该代码的第一个参数是意图列表的第一个元素,即"聊天",这个是判断条件的依据。 如果 switch 输入的第一个参数是“聊天”,那么就会走向这个分支 chat。而 chat 的输入就是 ppl.input。
启动应用
最后,我们将控制流 ppl 套入一个客户端,并启动部署(start()),在部署完后保持客户端不关闭(wait())。
1WebModule(ppl, history=[chat], audio=True, port=8847).start().wait() 2
这里由于需要用到麦克风来捕获声音,所以设置了 audio=True。
完整py代码:
1import lazyllm 2from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form 3from fastapi.responses import HTMLResponse, JSONResponse, FileResponse 4import uvicorn 5import os 6import json 7import tempfile 8from pathlib import Path 9 10# 设置API Key 11os.environ['LAZYLLM_DOUBAO_API_KEY'] = '豆包API_KEY' 12 13# 创建聊天模块 14chat = lazyllm.OnlineChatModule() 15classifier = lazyllm.OnlineChatModule() 16painter = lazyllm.OnlineChatModule() 17musician = lazyllm.OnlineChatModule() 18 19# 定义意图列表 20chatflow_intent_list = ["聊天", "语音识别", "图片问答", "画图", "生成音乐", "文字转语音"] 21 22# 定义各类提示词 23agent_prompt = f""" 24现在你是一个意图分类引擎,负责根据对话信息分析用户输入文本并确定唯一的意图类别。 25你只需要回复意图的名字即可,不要额外输出其他字段,也不要进行翻译。"intent_list"为所有意图名列表。 26 27如果输入中带有attachments,根据attachments的后缀类型以最高优先级确定意图: 28如果是图像后缀如.jpg、.png等,则输出:图片问答; 29如果是音频后缀如.mp3、.wav等,则输出:语音识别。 30 31## 示例 32User: 你好啊 33Assistant: 聊天 34""" 35 36painter_prompt = '现在你是一位绘图提示词大师,能够将用户输入的任意中文内容转换成英文绘图提示词,在本任务中你需要将任意输入内容转换成英文绘图提示词,并且你可以丰富和扩充提示词内容。' 37musician_prompt = '现在你是一位作曲提示词大师,能够将用户输入的任意中文内容转换成英文作曲提示词,在本任务中你需要将任意输入内容转换成英文作曲提示词,并且你可以丰富和扩充提示词内容。' 38 39# 创建FastAPI应用 40app = FastAPI(title="LazyLLM 多模态聊天", version="1.0.0") 41 42# 添加CORS支持 43from fastapi.middleware.cors import CORSMiddleware 44 45app.add_middleware( 46 CORSMiddleware, 47 allow_origins=["*"], 48 allow_credentials=True, 49 allow_methods=["*"], 50 allow_headers=["*"], 51) 52 53# 确保临时目录存在 54TEMP_DIR = Path("temp_files") 55TEMP_DIR.mkdir(exist_ok=True) 56 57# HTML页面内容 58HTML_PAGE = """ 59<!DOCTYPE html> 60<html lang="zh-CN"> 61<head> 62 <meta charset="UTF-8"> 63 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 64 <title>多模态助手 - LazyLLM</title> 65 <style> 66 * { 67 margin: 0; 68 padding: 0; 69 box-sizing: border-box; 70 } 71 72 body { 73 font-family: '华文中宋'; 74 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 75 min-height: 100vh; 76 display: flex; 77 justify-content: center; 78 align-items: center; 79 padding: 20px; 80 } 81 82 .chat-container { 83 background: white; 84 border-radius: 15px; 85 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 86 width: 100%; 87 max-width: 800px; 88 height: 600px; 89 display: flex; 90 flex-direction: column; 91 overflow: hidden; 92 } 93 94 .chat-header { 95 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 96 color: white; 97 padding: 20px; 98 text-align: center; 99 } 100 101 .chat-header h1 { 102 font-size: 24px; 103 font-weight: 300; 104 } 105 106 .intent-buttons { 107 background: #f0f2f5; 108 padding: 10px; 109 display: flex; 110 gap: 8px; 111 overflow-x: auto; 112 border-bottom: 1px solid #e9ecef; 113 } 114 115 .function-btn { 116 padding: 6px 12px; 117 border: none; 118 border-radius: 15px; 119 background: white; 120 cursor: pointer; 121 font-size: 14px; 122 display: flex; 123 align-items: center; 124 gap: 5px; 125 white-space: nowrap; 126 transition: all 0.2s; 127 } 128 129 .function-btn:hover { 130 background: #e6f7ff; 131 } 132 133 .function-btn.active { 134 background: #007bff; 135 color: white; 136 } 137 138 .chat-messages { 139 flex: 1; 140 padding: 20px; 141 overflow-y: auto; 142 display: flex; 143 flex-direction: column; 144 gap: 15px; 145 } 146 147 .message { 148 max-width: 70%; 149 padding: 12px 16px; 150 border-radius: 18px; 151 margin-bottom: 10px; 152 animation: fadeIn 0.3s ease-in; 153 } 154 155 .user-message { 156 align-self: flex-end; 157 background: #007bff; 158 color: white; 159 border-bottom-right-radius: 5px; 160 } 161 162 .bot-message { 163 align-self: flex-start; 164 background: #f1f3f5; 165 color: #333; 166 border-bottom-left-radius: 5px; 167 } 168 169 .message-content img { 170 max-width: 100%; 171 border-radius: 10px; 172 margin-top: 5px; 173 } 174 175 .message-content audio { 176 margin-top: 5px; 177 width: 100%; 178 } 179 180 .chat-input { 181 padding: 20px; 182 background: #f8f9fa; 183 border-top: 1px solid #e9ecef; 184 display: flex; 185 gap: 10px; 186 } 187 188 .input-actions { 189 display: flex; 190 gap: 10px; 191 align-items: center; 192 } 193 194 .attachment-btn { 195 background: white; 196 border: 1px solid #e9ecef; 197 border-radius: 50%; 198 width: 40px; 199 height: 40px; 200 display: flex; 201 align-items: center; 202 justify-content: center; 203 cursor: pointer; 204 transition: all 0.2s; 205 } 206 207 .attachment-btn:hover { 208 background: #e6f7ff; 209 } 210 211 #fileInput { 212 display: none; 213 } 214 215 .message-input { 216 flex: 1; 217 padding: 12px 16px; 218 border: 2px solid #e9ecef; 219 border-radius: 25px; 220 outline: none; 221 font-size: 14px; 222 transition: border-color 0.3s; 223 } 224 225 .message-input:focus { 226 border-color: #007bff; 227 } 228 229 .send-button { 230 padding: 12px 24px; 231 background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); 232 color: white; 233 border: none; 234 border-radius: 25px; 235 cursor: pointer; 236 font-weight: 500; 237 transition: transform 0.2s; 238 } 239 240 .send-button:hover { 241 transform: translateY(-2px); 242 } 243 244 .send-button:active { 245 transform: translateY(0); 246 } 247 248 .typing-indicator { 249 align-self: flex-start; 250 background: #f1f3f5; 251 color: #666; 252 padding: 12px 16px; 253 border-radius: 18px; 254 border-bottom-left-radius: 5px; 255 font-style: italic; 256 } 257 258 .attachment-preview { 259 align-self: flex-end; 260 margin-bottom: 10px; 261 max-width: 70%; 262 } 263 264 .attachment-preview img { 265 max-width: 100%; 266 border-radius: 10px; 267 } 268 269 .attachment-preview audio { 270 width: 100%; 271 } 272 273 .remove-attachment { 274 color: white; 275 background: rgba(0,0,0,0.3); 276 border: none; 277 border-radius: 50%; 278 width: 24px; 279 height: 24px; 280 cursor: pointer; 281 position: absolute; 282 top: 5px; 283 right: 5px; 284 } 285 286 @keyframes fadeIn { 287 from { 288 opacity: 0; 289 transform: translateY(10px); 290 } 291 to { 292 opacity: 1; 293 transform: translateY(0); 294 } 295 } 296 297 /* 滚动条样式 */ 298 .chat-messages::-webkit-scrollbar { 299 width: 6px; 300 } 301 302 .intent-buttons::-webkit-scrollbar { 303 height: 4px; 304 } 305 306 .chat-messages::-webkit-scrollbar-track, 307 .intent-buttons::-webkit-scrollbar-track { 308 background: #f1f1f1; 309 border-radius: 3px; 310 } 311 312 .chat-messages::-webkit-scrollbar-thumb, 313 .intent-buttons::-webkit-scrollbar-thumb { 314 background: #c1c1c1; 315 border-radius: 3px; 316 } 317 318 .chat-messages::-webkit-scrollbar-thumb:hover, 319 .intent-buttons::-webkit-scrollbar-thumb:hover { 320 background: #a8a8a8; 321 } 322 </style> 323</head> 324<body> 325 <div class="chat-container"> 326 <div class="chat-header"> 327 <h1>✨ 多模态助手 - LazyLLM</h1> 328 </div> 329 330 <div class="intent-buttons"> 331 <button class="function-btn active" onclick="setIntent('聊天')">💬 聊天</button> 332 <button class="function-btn" onclick="setIntent('语音识别')">🎤 语音识别</button> 333 <button class="function-btn" onclick="setIntent('图片问答')">🖼️ 图片问答</button> 334 <button class="function-btn" onclick="setIntent('画图')">🎨 画图</button> 335 <button class="function-btn" onclick="setIntent('生成音乐')">🎵 生成音乐</button> 336 <button class="function-btn" onclick="setIntent('文字转语音')">🔊 文字转语音</button> 337 </div> 338 339 <div class="chat-messages" id="chatMessages"> 340 <div class="message bot-message"> 341 你好!我是多模态助手,有什么可以帮您的吗?您可以选择不同的功能按钮来使用各种服务。 342 </div> 343 </div> 344 345 <div class="chat-input"> 346 <div class="input-actions"> 347 <label class="attachment-btn" for="fileInput">📎</label> 348 <input type="file" id="fileInput" accept="image/*,audio/*"> 349 </div> 350 <input 351 type="text" 352 class="message-input" 353 id="messageInput" 354 placeholder="输入您的消息..." 355 autocomplete="off" 356 > 357 <button class="send-button" onclick="sendMessage()">发送</button> 358 </div> 359 </div> 360 361 <script> 362 const chatMessages = document.getElementById('chatMessages'); 363 const messageInput = document.getElementById('messageInput'); 364 const fileInput = document.getElementById('fileInput'); 365 const functionButtons = document.querySelectorAll('.function-btn'); 366 let currentIntent = '聊天'; 367 let currentAttachment = null; 368 369 // 自动聚焦输入框 370 messageInput.focus(); 371 372 // 设置意图 373 function setIntent(intent) { 374 currentIntent = intent; 375 // 更新按钮样式 376 functionButtons.forEach(btn => { 377 if (btn.getAttribute('onclick').includes(intent)) { 378 btn.classList.add('active'); 379 } else { 380 btn.classList.remove('active'); 381 } 382 }); 383 // 清空输入和附件 384 messageInput.value = ''; 385 clearAttachment(); 386 } 387 388 // 添加消息 389 function addMessage(content, isUser = false, isAttachment = false) { 390 const messageDiv = document.createElement('div'); 391 messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; 392 393 const contentDiv = document.createElement('div'); 394 contentDiv.className = 'message-content'; 395 396 if (isAttachment) { 397 if (content.type.startsWith('image/')) { 398 contentDiv.innerHTML = [`<img src="${content.url}" alt="附件图片">`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.img.md); 399 } else if (content.type.startsWith('audio/')) { 400 contentDiv.innerHTML = [`<audio controls src="${content.url}">您的浏览器不支持音频播放</audio>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.audio.md); 401 } 402 if (content.caption) { 403 const captionDiv = document.createElement('div'); 404 captionDiv.textContent = content.caption; 405 captionDiv.style.marginTop = '5px'; 406 contentDiv.appendChild(captionDiv); 407 } 408 } else { 409 contentDiv.textContent = content; 410 } 411 412 messageDiv.appendChild(contentDiv); 413 chatMessages.appendChild(messageDiv); 414 chatMessages.scrollTop = chatMessages.scrollHeight; 415 } 416 417 // 显示附件预览 418 function showAttachmentPreview(file) { 419 clearAttachment(); 420 421 const previewDiv = document.createElement('div'); 422 previewDiv.className = 'attachment-preview'; 423 previewDiv.style.position = 'relative'; 424 previewDiv.id = 'attachmentPreview'; 425 426 const removeBtn = document.createElement('button'); 427 removeBtn.className = 'remove-attachment'; 428 removeBtn.textContent = '×'; 429 removeBtn.onclick = clearAttachment; 430 431 if (file.type.startsWith('image/')) { 432 const img = document.createElement('img'); 433 img.src = URL.createObjectURL(file); 434 previewDiv.appendChild(img); 435 } else if (file.type.startsWith('audio/')) { 436 const audio = document.createElement('audio'); 437 audio.controls = true; 438 audio.src = URL.createObjectURL(file); 439 previewDiv.appendChild(audio); 440 } 441 442 previewDiv.appendChild(removeBtn); 443 chatMessages.appendChild(previewDiv); 444 chatMessages.scrollTop = chatMessages.scrollHeight; 445 446 currentAttachment = file; 447 } 448 449 // 清除附件 450 function clearAttachment() { 451 const preview = document.getElementById('attachmentPreview'); 452 if (preview) { 453 preview.remove(); 454 } 455 currentAttachment = null; 456 fileInput.value = ''; 457 } 458 459 // 文件选择处理 460 fileInput.addEventListener('change', function(e) { 461 if (this.files && this.files[0]) { 462 showAttachmentPreview(this.files[0]); 463 // 根据文件类型自动切换意图 464 if (this.files[0].type.startsWith('image/')) { 465 setIntent('图片问答'); 466 } else if (this.files[0].type.startsWith('audio/')) { 467 setIntent('语音识别'); 468 } 469 } 470 }); 471 472 function showTyping() { 473 const typingDiv = document.createElement('div'); 474 typingDiv.className = 'message typing-indicator'; 475 typingDiv.id = 'typingIndicator'; 476 typingDiv.textContent = 'AI正在处理...'; 477 chatMessages.appendChild(typingDiv); 478 chatMessages.scrollTop = chatMessages.scrollHeight; 479 } 480 481 function hideTyping() { 482 const typingIndicator = document.getElementById('typingIndicator'); 483 if (typingIndicator) { 484 typingIndicator.remove(); 485 } 486 } 487 488 async function sendMessage() { 489 const message = messageInput.value.trim(); 490 // 检查是否有内容或附件 491 if (!message && !currentAttachment) return; 492 493 // 添加用户消息 494 if (message) { 495 addMessage(message, true); 496 } 497 if (currentAttachment) { 498 addMessage({ 499 url: URL.createObjectURL(currentAttachment), 500 type: currentAttachment.type, 501 caption: message || '附件' 502 }, true, true); 503 } 504 505 // 清空输入 506 messageInput.value = ''; 507 const preview = document.getElementById('attachmentPreview'); 508 if (preview) { 509 preview.remove(); 510 } 511 512 // 显示正在处理提示 513 showTyping(); 514 515 try { 516 // 创建FormData 517 const formData = new FormData(); 518 formData.append('message', message); 519 formData.append('intent', currentIntent); 520 if (currentAttachment) { 521 formData.append('file', currentAttachment); 522 } 523 524 const response = await fetch('/chat', { 525 method: 'POST', 526 body: formData 527 }); 528 529 if (!response.ok) { 530 throw new Error('网络请求失败'); 531 } 532 533 const data = await response.json(); 534 hideTyping(); 535 536 // 处理不同类型的响应 537 if (data.response_type === 'image') { 538 addMessage({ 539 url: data.response, 540 type: 'image/jpeg', 541 caption: '' 542 }, false, true); 543 } else if (data.response_type === 'audio') { 544 addMessage({ 545 url: data.response, 546 type: 'audio/mpeg', 547 caption: '' 548 }, false, true); 549 } else { 550 addMessage(data.response, false); 551 } 552 553 } catch (error) { 554 hideTyping(); 555 addMessage('抱歉,发生错误:' + error.message, false); 556 console.error('Error:', error); 557 } finally { 558 // 重置附件 559 currentAttachment = null; 560 fileInput.value = ''; 561 } 562 } 563 564 // 支持回车键发送 565 messageInput.addEventListener('keypress', function(e) { 566 if (e.key === 'Enter') { 567 sendMessage(); 568 } 569 }); 570 571 // 自动调整输入框高度 572 messageInput.addEventListener('input', function() { 573 this.style.height = 'auto'; 574 this.style.height = (this.scrollHeight) + 'px'; 575 }); 576 </script> 577</body> 578</html> 579""" 580 581 582@app.get("/", response_class=HTMLResponse) 583async def get_chat_interface(): 584 """返回聊天界面""" 585 return HTMLResponse(content=HTML_PAGE, media_type="text/html") 586 587 588@app.post("/chat") 589async def chat_endpoint( 590 message: str = Form(""), 591 intent: str = Form(""), 592 file: UploadFile = File(None) 593): 594 """处理聊天消息""" 595 try: 596 message = message.strip() 597 file_path = None 598 file_type = None 599 600 # 保存上传的文件 601 if file: 602 file_ext = os.path.splitext(file.filename)[1].lower() 603 file_type = file.content_type 604 with tempfile.NamedTemporaryFile( 605 suffix=file_ext, 606 dir=TEMP_DIR, 607 delete=False 608 ) as temp_file: 609 temp_file.write(await file.read()) 610 file_path = temp_file.name 611 612 # 确定意图 613 final_intent = intent 614 if file: 615 # 根据文件类型确定意图 616 if file_type and file_type.startswith('image/'): 617 final_intent = "图片问答" 618 elif file_type and file_type.startswith('audio/'): 619 final_intent = "语音识别" 620 elif not intent or intent not in chatflow_intent_list: 621 # 调用分类器确定意图 622 classify_prompt = f"{agent_prompt}\nintent_list: {chatflow_intent_list}\nUser: {message}\nAssistant:" 623 final_intent = classifier(classify_prompt).strip() 624 if final_intent not in chatflow_intent_list: 625 final_intent = "聊天" # 默认使用聊天意图 626 627 print(f"收到消息: {message}, 意图: {final_intent}, 文件: {file_path}") 628 629 response_content = "" 630 response_type = "text" 631 632 # 根据不同意图处理 633 if final_intent == "聊天": 634 response_content = chat(message) 635 636 elif final_intent == "语音识别": 637 if file_path and file_type.startswith('audio/'): 638 # 这里只是模拟语音识别,实际应用中需要调用语音识别API 639 response_content = f"已识别音频内容: 这是模拟的语音识别结果(实际应用中会替换为真实识别内容)" 640 else: 641 response_content = "请上传音频文件进行语音识别" 642 643 elif final_intent == "图片问答": 644 if file_path and file_type.startswith('image/'): 645 # 这里只是模拟图片问答,实际应用中需要调用图片理解API 646 response_content = f"图片分析结果: 这是模拟的图片问答结果,针对您的问题:{message}(实际应用中会替换为真实分析内容)" 647 else: 648 response_content = "请上传图片进行问答" 649 650 elif final_intent == "画图": 651 # 生成英文绘图提示词 652 prompt = f"{painter_prompt}\n用户输入: {message}\n英文提示词:" 653 en_prompt = painter(prompt).strip() 654 # 这里只是模拟画图功能,实际应用中需要调用画图API 655 response_content = f"/placeholder-image?prompt={en_prompt}" # 占位图URL 656 response_type = "image" 657 658 elif final_intent == "生成音乐": 659 # 生成英文作曲提示词 660 prompt = f"{musician_prompt}\n用户输入: {message}\n英文提示词:" 661 en_prompt = musician(prompt).strip() 662 # 这里只是模拟生成音乐,实际应用中需要调用音乐生成API 663 response_content = f"/placeholder-audio?prompt={en_prompt}" # 占位音频URL 664 response_type = "audio" 665 666 elif final_intent == "文字转语音": 667 if message: 668 # 这里只是模拟文字转语音,实际应用中需要调用TTS API 669 response_content = f"/placeholder-audio?text={message}" # 占位音频URL 670 response_type = "audio" 671 else: 672 response_content = "请输入要转换的文字" 673 674 print(f"AI回复: {response_content}, 类型: {response_type}") 675 676 return JSONResponse(content={ 677 "response": response_content, 678 "response_type": response_type, 679 "status": "success", 680 "intent": final_intent 681 }) 682 683 except Exception as e: 684 print(f"错误: {e}") 685 return JSONResponse( 686 status_code=500, 687 content={ 688 "response": f"抱歉,发生错误: {str(e)}", 689 "response_type": "text", 690 "status": "error" 691 } 692 ) 693 finally: 694 # 清理临时文件 695 if file_path and os.path.exists(file_path): 696 try: 697 os.remove(file_path) 698 except: 699 pass 700 701 702@app.get("/placeholder-image") 703async def placeholder_image(prompt: str): 704 """占位图片,实际应用中应替换为真实的图片生成API""" 705 return FileResponse("placeholder_image.jpg") # 请准备一张占位图片 706 707 708@app.get("/placeholder-audio") 709async def placeholder_audio(prompt: str = None, text: str = None): 710 """占位音频,实际应用中应替换为真实的音频生成API""" 711 return FileResponse("placeholder_audio.mp3") # 请准备一个占位音频 712 713 714@app.get("/health") 715async def health_check(): 716 """健康检查端点""" 717 return {"status": "healthy", "service": "lazyllm-multimodal-chat"} 718 719 720if __name__ == "__main__": 721 print("🚀 启动 LazyLLM 多模态聊天服务器...") 722 print("📍 本地访问: http://localhost:23333") 723 print("🤖 准备好与多模态AI聊天了!") 724 print("⏹️ 按 Ctrl+C 停止服务器") 725 726 # 测试基本功能 727 try: 728 test_response = chat("你好") 729 print(f"✅ 基本功能测试成功: {test_response[:50]}...") 730 except Exception as e: 731 print(f"❌ 基本功能测试失败: {e}") 732 print("请检查API Key设置") 733 734 uvicorn.run( 735 app, 736 host="0.0.0.0", 737 port=23333, 738 log_level="info" 739 ) 740
运行效果:

五、常见问题与解决方法
5.1 生成内容不符合预期
- 原因分析:可能是提示词不够清晰、准确,没有完整传达你的需求;也可能是模型对某些特定领域的知识理解不够深入,导致生成的内容偏离预期。
- 解决方法:仔细检查提示词,确保语言表达清晰、逻辑连贯,将所有关键信息准确传达给 LazyLLM 写作助手 Agent。如果是特定领域的问题,可以在提示词中增加一些相关的背景知识或示例,引导模型生成更符合要求的内容。此外,你还可以尝试调整提示词的表述方式,或者多次生成,从不同的结果中选择最符合需求的内容。
5.2 生成速度较慢
- 原因分析:生成速度可能受到多种因素的影响,如网络状况不佳、模型负载过高、提示词过于复杂等。
- 解决方法:首先检查网络连接是否稳定,可以尝试切换网络环境或重启网络设备。如果是模型负载过高,可以选择在非高峰时段进行生成,或者考虑使用性能更高的模型服务。对于过于复杂的提示词,可以适当简化,将一个大的任务拆分成多个小的任务,逐步生成内容,以提高生成速度。
5.3 格式问题
- 原因分析:LazyLLM 写作助手 Agent 生成的内容可能在格式上不符合你的要求,例如字体、字号、行距、段落格式等。
- 解决方法:在生成内容后,使用相应的文字编辑软件(如 Word、WPS 等)对格式进行调整。根据你的具体需求,设置字体、字号、行距等参数,对段落进行排版,使文章格式符合要求。同时,一些公众号平台或论文投稿系统可能有特定的格式要求,在发布或投稿前,要按照相应的要求进行格式转换和调整。
六、总结和展望
LazyLLM 写作助手 Agent 为论文、专栏、公众号文章等内容创作提供了极大的便利,能够帮助创作者快速生成初稿,提高创作效率。通过合理构建提示词、准确传达需求,结合人工的润色和优化,可以创作出高质量的内容。然而,目前的人工智能写作工具仍然存在一些局限性,需要我们在使用过程中不断探索和改进。未来,随着人工智能技术的不断发展和完善,相信 LazyLLM 写作助手 Agent 等工具将在内容创作领域发挥更加重要的作用,为我们带来更多的创作灵感和便利。希望本文的教程能够帮助你更好地使用 LazyLLM 写作助手 Agent,开启高效创作之旅。

📣 还不赶紧?关注LazyLLM,打造自己的人工智能平台吧!
七、附件资源
安装教程:https://mp.weixin.qq.com/
帮助文档:https://github.com/LazyAGI/LazyLLM/blob/main/README.CN.md
更多教程文档: https://docs.lazyllm.ai/
联系博主
xcLeigh 博主,全栈领域优质创作者,博客专家,目前,活跃在CSDN、微信公众号、小红书、知乎、掘金、快手、思否、微博、51CTO、B站、腾讯云开发者社区、阿里云开发者社区等平台,全网拥有几十万的粉丝,全网统一IP为 xcLeigh。希望通过我的分享,让大家能在喜悦的情况下收获到有用的知识。主要分享编程、开发工具、算法、技术学习心得等内容。很多读者评价他的文章简洁易懂,尤其对于一些复杂的技术话题,他能通过通俗的语言来解释,帮助初学者更好地理解。博客通常也会涉及一些实践经验,项目分享以及解决实际开发中遇到的问题。如果你是开发领域的初学者,或者在学习一些新的编程语言或框架,关注他的文章对你有很大帮助。
亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。
愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。
至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。
💞 关注博主 🌀 带你实现畅游前后端!
🥇 从零到一学习Python 🌀 带你玩转Python技术流!
🏆 人工智能学习合集 🌀 搭配实例教程与实战案例,帮你构建完整 AI 知识体系
💦 注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://xcleigh.blog.csdn.net/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。

📣 亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(或者关注下方公众号,看见后第一时间回复,还有海量编程资料等你来领!),博主看见后一定及时给您答复 💌💌💌
《基于LazyLLM多Agent大模型应用的开发框架,搭建本地大模型AI工具,你贴身的写作、论文小助手》 是转载文章,点击查看原文。