基于LazyLLM多Agent大模型应用的开发框架,搭建本地大模型AI工具,你贴身的写作、论文小助手

作者:xcLeigh日期:2025/10/4

在这里插入图片描述

在这里插入图片描述

在搭建本地大模型作为写作、论文小助手时,开发者常面临诸多技术难题:模型部署需研究复杂 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,成就感直接拉满!

运行效果

LazyLLM

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 生成内容不符合预期

  1. 原因分析:可能是提示词不够清晰、准确,没有完整传达你的需求;也可能是模型对某些特定领域的知识理解不够深入,导致生成的内容偏离预期。
  2. 解决方法:仔细检查提示词,确保语言表达清晰、逻辑连贯,将所有关键信息准确传达给 LazyLLM 写作助手 Agent。如果是特定领域的问题,可以在提示词中增加一些相关的背景知识或示例,引导模型生成更符合要求的内容。此外,你还可以尝试调整提示词的表述方式,或者多次生成,从不同的结果中选择最符合需求的内容。

5.2 生成速度较慢

  1. 原因分析:生成速度可能受到多种因素的影响,如网络状况不佳、模型负载过高、提示词过于复杂等。
  2. 解决方法:首先检查网络连接是否稳定,可以尝试切换网络环境或重启网络设备。如果是模型负载过高,可以选择在非高峰时段进行生成,或者考虑使用性能更高的模型服务。对于过于复杂的提示词,可以适当简化,将一个大的任务拆分成多个小的任务,逐步生成内容,以提高生成速度。

5.3 格式问题

  1. 原因分析:LazyLLM 写作助手 Agent 生成的内容可能在格式上不符合你的要求,例如字体、字号、行距、段落格式等。
  2. 解决方法:在生成内容后,使用相应的文字编辑软件(如 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工具,你贴身的写作、论文小助手》 是转载文章,点击查看原文


相关推荐


自存19-48
北慕阳2025/10/2

19-菜单管理添加弹窗显示 <template> <button @click="dialogFormVisable = true ">打开</button> <el-dialog v-model="dialogFormVisable" :before-close="beforeClose" title="添加权限" width="500" > <el-form


🔥 连八股文都不懂还指望在前端混下去么
Gaby2025/10/2

废话只说一句:码字不易求个👍,收藏 === 学会,快行动起来吧!🙇‍🙇‍🙇‍。2024.03.04 由于篇幅限制更详细的内容已更新到 ☞ 我的 GitHub 上,有纠正错误和需要补充的小伙伴可以在这里留言,我会及时更新上去的。推荐个API管理神器 Apipost 用过的都知道好使 1. HTTP 和 HTTPS 1.http 和 https 的基本概念 http: 是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的超文本传输协议。 http


学习日报 20250923|MQ (Kafka)面试深度复盘
靈臺清明2025/10/2

MQ 面试深度复盘:从实战经验到底层设计全解析 在分布式系统架构面试中,消息队列(MQ)是考察候选人技术深度与实战经验的核心模块之一。本文基于真实面试场景,从 MQ 的实际应用、核心价值、产品选型、故障排查到架构设计,进行全面复盘总结,既适合面试备考记忆,也可作为技术文章发布,帮助更多开发者梳理 MQ 知识体系。 一、基础认知:你真的懂 MQ 的 “用武之地” 吗? 面试中,面试官往往从 “是否用过 MQ” 切入,逐步深入到 “为什么用”,核心是考察候选人对 MQ 核心价值的理解是否停留在


桌面预测类开发,桌面%性别,姓名预测%系统开发,基于python,scikit-learn机器学习算法(sklearn)实现,分类算法,CSV无数据库
合作小小程序员小小店10/1/2025

这一个也是和信号识别的那个项目demo一样。桌面很常用的开发框架tkinter,在没有pyqt之前一直用着,帮客户修改一下代码。人工智能应用开发套路还是一样,从csv获取数据集,进行数据集清洗去重等操作,完成数据清洗就可以构造模型进行模型fit了,最后模型预测评估调优。


常见开发语言在 Windows 上的默认编码格式
十启树9/30/2025

字符串是字节流,输出编码取决于手动设置(如 header 或 mb_* 函数)


【Docker】说说卷挂载与绑定挂载
你的人类朋友2025/10/5

前言 我最开始接触 Docker 的时候,遇到 mysql 这样的容器,我一般使用卷挂载。它的好处就是将挂载的位置交给 Docker 管理,我们规定卷的名字即可,不需要关心挂载的位置。我感觉这样很方便,所以后面我基本一遇到挂载就用卷挂载。 但是最近,我慢慢地开始喜欢上绑定挂载了。特别是要部署一个什么环境之类的【如 n8n、redis】,都会优先使用绑定挂载。这个挂载方式会让我更有一种掌控感。 今天就来总结这两种挂载方式的相关知识。 正文 一、什么是 Docker 数据挂载? 在 Docker 中


【OpenCV】图像处理入门:从基础到实战技巧
朋鱼燕2025/10/6

目录 1.对图像的基本理解 2.数据读取-图像 ​编辑 3.数据读取-视频 4.ROI区域 1.对图像的基本理解 图像是由一个个像素点组成的,RGB图像有三个通道,而灰度图像只有一个通道 RGB每个通道的像素点的值的范围是0-255,数值越大,对应该颜色通道的亮度越亮 2.数据读取-图像 在文件的路径下读取一张图像,不能包含中文 opencv的读取格式是BGR cv2.waitKey(0)按下任意键才关闭图像,换成1000的话是显示1000


cygwin + redis
欧的曼2025/10/8

1. 下载 Redis 源码 推荐安装稳定版(如 Redis 7.0.12,可从 Redis 官网下载页 获取最新稳定版链接): wget https://download.redis.io/releases/redis-7.0.12.tar.gz 2. 解压并进入源码目录 3. 编译 Redis(关键步骤) 找到Cygwin安装目录下的usr\include\dlfcn.h文件,修改如下代码,将#if __GNU_VISIBLE、#endif 这两行注释掉。(使用// 或 /


从入门到实战:全面解析Protobuf的安装配置、语法规范与高级应用——手把手教你用Protobuf实现高效数据序列化与跨语言通信
羑悻的小杀马特.2025/10/9

文章目录 本篇摘要一.`Protocol Buffers(Protobuf)`简介1. **核心定义**2. **核心作用**3. **对比优势**4. **使用关键点**总结 二.`基于windows及ubuntu22.04安装Protobuf``windows`ubuntu22.04 三.快速上手protobuf编写及测试规范说明编译命令编译生成结果 四.proto3语法解析之字段规则与消息定义五. `Protobuf 命令行decode操作`六.仓库链接七.本篇


基于单片机的Boost升压斩波电源电路
清风6666662025/10/11

基于单片机的Boost升压斩波电源电路设计 点击链接下载资料:https://download.csdn.net/download/m0_51061483/92081480 1. 系统功能概述 本系统以单片机为核心控制单元,设计并实现了一种Boost升压型斩波电源电路。系统能够实现输入5V电压,通过Boost电路升压至可调的20V输出范围。用户可通过按键设置目标输出电压,液晶LCD模块实时显示当前输出电压与设定电压,形成完整的闭环控制系统。 系统采用PWM控制技术与DA(数模转换)调

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0