在这里插入图片描述

在这里插入图片描述

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

import lazyllm
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import os
import asyncio

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY'

# 创建聊天模块
chat = lazyllm.OnlineChatModule()

# 创建FastAPI应用
app = FastAPI(title="LazyLLM Chat API", version="1.0.0")

# 添加CORS中间件
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class ChatRequest(BaseModel):
    message: str
    model: str = "openai"


class ChatResponse(BaseModel):
    response: str
    status: str = "success"


@app.post("/chat", response_model=ChatResponse)
async def chat_endpoint(request: ChatRequest):
    try:
        # 使用线程池执行同步的chat调用
        loop = asyncio.get_event_loop()
        response = await loop.run_in_executor(None, lambda: chat(request.message))
        return ChatResponse(response=response)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Chat error: {str(e)}")


@app.get("/")
async def root():
    return {
        "message": "LazyLLM Chat API is running",
        "status": "healthy",
        "endpoints": {
            "chat": "POST /chat",
            "docs": "GET /docs"
        }
    }


@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "lazyllm-chat"}


if __name__ == "__main__":
    print("🚀 启动 LazyLLM Chat API 服务器...")
    print("📍 本地访问: http://localhost:23333")
    print("📚 API文档: http://localhost:23333/docs")
    print("⏹️  按 Ctrl+C 停止服务器")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=23333,
        log_level="info"
    )

家人们,这段代码堪称 “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

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

在这里插入图片描述

3.3 配置web服务应用

上面咱们配置了api服务应用,接下来咱们按照上面的思路配置一下web服务应用。

py代码

import lazyllm
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
import uvicorn
import os
import json

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY'

# 创建聊天模块
chat = lazyllm.OnlineChatModule()

# 创建FastAPI应用
app = FastAPI(title="LazyLLM Chat", version="1.0.0")

# 添加CORS支持
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# HTML页面内容
HTML_PAGE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LazyLLM Chat</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .chat-container {
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            width: 100%;
            max-width: 800px;
            height: 600px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .chat-header {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 20px;
            text-align: center;
        }

        .chat-header h1 {
            font-size: 24px;
            font-weight: 300;
        }

        .chat-messages {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .message {
            max-width: 70%;
            padding: 12px 16px;
            border-radius: 18px;
            margin-bottom: 10px;
            animation: fadeIn 0.3s ease-in;
        }

        .user-message {
            align-self: flex-end;
            background: #007bff;
            color: white;
            border-bottom-right-radius: 5px;
        }

        .bot-message {
            align-self: flex-start;
            background: #f1f3f5;
            color: #333;
            border-bottom-left-radius: 5px;
        }

        .chat-input {
            padding: 20px;
            background: #f8f9fa;
            border-top: 1px solid #e9ecef;
            display: flex;
            gap: 10px;
        }

        .message-input {
            flex: 1;
            padding: 12px 16px;
            border: 2px solid #e9ecef;
            border-radius: 25px;
            outline: none;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        .message-input:focus {
            border-color: #007bff;
        }

        .send-button {
            padding: 12px 24px;
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-weight: 500;
            transition: transform 0.2s;
        }

        .send-button:hover {
            transform: translateY(-2px);
        }

        .send-button:active {
            transform: translateY(0);
        }

        .typing-indicator {
            align-self: flex-start;
            background: #f1f3f5;
            color: #666;
            padding: 12px 16px;
            border-radius: 18px;
            border-bottom-left-radius: 5px;
            font-style: italic;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        /* 滚动条样式 */
        .chat-messages::-webkit-scrollbar {
            width: 6px;
        }

        .chat-messages::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h1>🤖 LazyLLM 智能聊天</h1>
        </div>

        <div class="chat-messages" id="chatMessages">
            <div class="message bot-message">
                你好!我是LazyLLM助手,有什么可以帮您的吗?
            </div>
        </div>

        <div class="chat-input">
            <input 
                type="text" 
                class="message-input" 
                id="messageInput" 
                placeholder="输入您的消息..." 
                autocomplete="off"
            >
            <button class="send-button" onclick="sendMessage()">发送</button>
        </div>
    </div>

    <script>
        const chatMessages = document.getElementById('chatMessages');
        const messageInput = document.getElementById('messageInput');

        // 自动聚焦输入框
        messageInput.focus();

        function addMessage(text, isUser = false) {
            const messageDiv = document.createElement('div');
            messageDiv.className = isUser ? 'message user-message' : 'message bot-message';
            messageDiv.textContent = text;
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function showTyping() {
            const typingDiv = document.createElement('div');
            typingDiv.className = 'message typing-indicator';
            typingDiv.id = 'typingIndicator';
            typingDiv.textContent = 'AI正在思考...';
            chatMessages.appendChild(typingDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function hideTyping() {
            const typingIndicator = document.getElementById('typingIndicator');
            if (typingIndicator) {
                typingIndicator.remove();
            }
        }

        async function sendMessage() {
            const message = messageInput.value.trim();
            if (!message) return;

            // 添加用户消息
            addMessage(message, true);
            messageInput.value = '';

            // 显示正在输入提示
            showTyping();

            try {
                const response = await fetch('/chat', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ message: message })
                });

                if (!response.ok) {
                    throw new Error('网络请求失败');
                }

                const data = await response.json();
                hideTyping();
                addMessage(data.response);

            } catch (error) {
                hideTyping();
                addMessage('抱歉,发生错误:' + error.message);
                console.error('Error:', error);
            }
        }

        // 支持回车键发送
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });

        // 自动调整输入框高度
        messageInput.addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = (this.scrollHeight) + 'px';
        });
    </script>
</body>
</html>
"""


@app.get("/", response_class=HTMLResponse)
async def get_chat_interface():
    """返回聊天界面"""
    return HTMLResponse(content=HTML_PAGE, media_type="text/html")


@app.post("/chat")
async def chat_endpoint(request: dict):
    """处理聊天消息"""
    try:
        message = request.get("message", "").strip()
        if not message:
            raise HTTPException(status_code=400, detail="消息不能为空")

        print(f"收到消息: {message}")

        # 调用LazyLLM聊天模块
        response = chat(message)

        print(f"AI回复: {response}")

        return JSONResponse(content={
            "response": response,
            "status": "success"
        })

    except Exception as e:
        print(f"错误: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "response": f"抱歉,发生错误: {str(e)}",
                "status": "error"
            }
        )


@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {"status": "healthy", "service": "lazyllm-chat"}


if __name__ == "__main__":
    print("🚀 启动 LazyLLM Web 聊天服务器...")
    print("📍 本地访问: http://localhost:23333")
    print("🤖 准备好与AI聊天了!")
    print("⏹️  按 Ctrl+C 停止服务器")

    # 测试基本功能
    try:
        test_response = chat("你好")
        print(f"✅ 基本功能测试成功: {test_response[:50]}...")
    except Exception as e:
        print(f"❌ 基本功能测试失败: {e}")
        print("请检查API Key设置")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=23333,
        log_level="info"
    )

运行效果

在这里插入图片描述

3.4 配置控制台服务应用

上面咱们配置了web服务应用,接下来咱们按照上面的思路配置一下控制台服务应用。

py代码

import lazyllm
import os

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '我这里用的是豆包,这里填写你的豆包API_KEY'


def main():
    print("🤖 LazyLLM 命令行聊天")
    print("输入 'quit' 或 'exit' 退出")
    print("-" * 50)

    chat = lazyllm.OnlineChatModule()

    while True:
        try:
            user_input = input("👤 你: ").strip()

            if user_input.lower() in ['quit', 'exit', 'q']:
                print("再见!👋")
                break

            if not user_input:
                continue

            print("🤖 AI: ", end="", flush=True)
            response = chat(user_input)
            print(response)
            print()

        except KeyboardInterrupt:
            print("\n再见!👋")
            break
        except Exception as e:
            print(f"❌ 错误: {e}")


if __name__ == "__main__":
    main()

运行效果

在这里插入图片描述

四、写作小助手配置

4.1 提示词配置

咱先明确目标:把 LazyLLM 的 “基础聊天功能” 升级成 “专属写作助手”,核心就俩事儿 ——写对提示词让 AI 懂 “写作规矩”,选对 MCP 模块让功能落地,全程像给手机装专属 APP 一样简单,小白也能跟着走!

【身份定义】你是一名专业写作助手,擅长学术论文、职场报告、自媒体文案等多种文体创作,语言严谨且流畅,拒绝口语化和无关内容。
【任务要求】当用户输入“写作需求+文体+字数/核心要点”时,你需要先拆解需求(比如用户说“写300字产品推广文案,产品是无线耳机,卖点是降噪+长续航”,你要先确认“文体:推广文案,字数:300字,核心要素:无线耳机、降噪、长续航”),再按“开头吸引注意力+中间讲卖点+结尾引导行动”的结构创作,创作后补充1-2句“修改建议”(比如“若需突出价格优势,可补充XX内容”)。
【禁忌提醒】不偏离用户指定的文体和字数,不添加与写作无关的闲聊内容,若用户需求模糊(比如只说“写篇文章”),要主动追问“请问需要哪种文体(如论文/文案)、大概多少字、有哪些核心要点呀?”

为啥这么写?举个反例:要是只写 “帮我写作”,AI 可能一会儿写散文一会儿写段子;但加了 “身份 + 任务 + 禁忌”,AI 就像拿到 “标准答案提纲”,绝不会跑偏。写完后点 “保存提示词”,这一步别忘!就像手机设置完壁纸要确认一样。

4.2 代码调整

import lazyllm
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
import uvicorn
import os
import json

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '填写你的豆包API-KEY'

# 创建聊天模块
chat = lazyllm.OnlineChatModule()

# 创建FastAPI应用
app = FastAPI(title="LazyLLM Chat", version="1.0.0")

# 添加CORS支持
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# HTML页面内容
HTML_PAGE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>写作小助手 - LazyLLM</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: '华文中宋';
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .chat-container {
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            width: 100%;
            max-width: 800px;
            height: 600px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .chat-header {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 20px;
            text-align: center;
        }

        .chat-header h1 {
            font-size: 24px;
            font-weight: 300;
        }

        .chat-messages {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .message {
            max-width: 70%;
            padding: 12px 16px;
            border-radius: 18px;
            margin-bottom: 10px;
            animation: fadeIn 0.3s ease-in;
        }

        .user-message {
            align-self: flex-end;
            background: #007bff;
            color: white;
            border-bottom-right-radius: 5px;
        }

        .bot-message {
            align-self: flex-start;
            background: #f1f3f5;
            color: #333;
            border-bottom-left-radius: 5px;
        }

        .chat-input {
            padding: 20px;
            background: #f8f9fa;
            border-top: 1px solid #e9ecef;
            display: flex;
            gap: 10px;
        }

        .message-input {
            flex: 1;
            padding: 12px 16px;
            border: 2px solid #e9ecef;
            border-radius: 25px;
            outline: none;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        .message-input:focus {
            border-color: #007bff;
        }

        .send-button {
            padding: 12px 24px;
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-weight: 500;
            transition: transform 0.2s;
        }

        .send-button:hover {
            transform: translateY(-2px);
        }

        .send-button:active {
            transform: translateY(0);
        }

        .typing-indicator {
            align-self: flex-start;
            background: #f1f3f5;
            color: #666;
            padding: 12px 16px;
            border-radius: 18px;
            border-bottom-left-radius: 5px;
            font-style: italic;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        /* 滚动条样式 */
        .chat-messages::-webkit-scrollbar {
            width: 6px;
        }

        .chat-messages::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h1>✨ 写作小助手 - LazyLLM</h1>
        </div>

        <div class="chat-messages" id="chatMessages">
            <div class="message bot-message">
                你好!我是写作小助手,有什么可以帮您的吗?
            </div>
        </div>

        <div class="chat-input">
            <input 
                type="text" 
                class="message-input" 
                id="messageInput" 
                placeholder="输入您的消息..." 
                autocomplete="off"
            >
            <button class="send-button" onclick="sendMessage()">发送</button>
        </div>
    </div>

    <script>
        const chatMessages = document.getElementById('chatMessages');
        const messageInput = document.getElementById('messageInput');

        // 自动聚焦输入框
        messageInput.focus();

        function addMessage(text, isUser = false) {
            const messageDiv = document.createElement('div');
            messageDiv.className = isUser ? 'message user-message' : 'message bot-message';
            messageDiv.textContent = text;
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function showTyping() {
            const typingDiv = document.createElement('div');
            typingDiv.className = 'message typing-indicator';
            typingDiv.id = 'typingIndicator';
            typingDiv.textContent = 'AI正在思考...';
            chatMessages.appendChild(typingDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function hideTyping() {
            const typingIndicator = document.getElementById('typingIndicator');
            if (typingIndicator) {
                typingIndicator.remove();
            }
        }

        async function sendMessage() {
            const message = messageInput.value.trim();
            if (!message) return;

            // 添加用户消息
            addMessage(message, true);
            messageInput.value = '';

            // 显示正在输入提示
            showTyping();

            try {
                const response = await fetch('/chat', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ message: message })
                });

                if (!response.ok) {
                    throw new Error('网络请求失败');
                }

                const data = await response.json();
                hideTyping();
                addMessage(data.response);

            } catch (error) {
                hideTyping();
                addMessage('抱歉,发生错误:' + error.message);
                console.error('Error:', error);
            }
        }

        // 支持回车键发送
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });

        // 自动调整输入框高度
        messageInput.addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = (this.scrollHeight) + 'px';
        });
    </script>
</body>
</html>
"""


@app.get("/", response_class=HTMLResponse)
async def get_chat_interface():
    """返回聊天界面"""
    return HTMLResponse(content=HTML_PAGE, media_type="text/html")


@app.post("/chat")
async def chat_endpoint(request: dict):
    """处理聊天消息"""
    try:
        message = request.get("message", "").strip()
        if not message:
            raise HTTPException(status_code=400, detail="消息不能为空")

        print(f"收到消息: {message}")

        # 调用LazyLLM聊天模块
        response = chat(message)

        print(f"AI回复: {response}")

        return JSONResponse(content={
            "response": response,
            "status": "success"
        })

    except Exception as e:
        print(f"错误: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "response": f"抱歉,发生错误: {str(e)}",
                "status": "error"
            }
        )


@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {"status": "healthy", "service": "lazyllm-chat"}


if __name__ == "__main__":
    print("🚀 启动 LazyLLM Web 聊天服务器...")
    print("📍 本地访问: http://localhost:23333")
    print("🤖 准备好与AI聊天了!")
    print("⏹️  按 Ctrl+C 停止服务器")

    # 测试基本功能
    try:
        test_response = chat("你好")
        print(f"✅ 基本功能测试成功: {test_response[:50]}...")
    except Exception as e:
        print(f"❌ 基本功能测试失败: {e}")
        print("请检查API Key设置")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=23333,
        log_level="info"
    )

4.3 写一篇散文

提示词: 帮我写一篇形容女生很漂亮的散文

运行效果

在这里插入图片描述

4.4 带大纲写一篇文章

py代码:

import lazyllm
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
import uvicorn
import os
import json

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '豆包API_KEY'

# 创建处理管道
toc_prompt = """你现在是一个智能助手。你的任务是理解用户的输入,将大纲以列表嵌套字典的列表。每个字典包含一个 `title` 和 `describe`,其中 `title` 中需要用Markdown格式标清层级,`describe` 是对该段的描述和写作指导。

请根据以下用户输入生成相应的列表嵌套字典:

输出示例:
[
    {
        "title": "# 一级标题",
        "describe": "请详细描述此标题的内容,提供背景信息和核心观点。"
    },
    {
        "title": "## 二级标题",
        "describe": "请详细描述标题的内容,提供具体的细节和例子来支持一级标题的观点。"
    },
    {
        "title": "### 三级标题",
        "describe": "请详细描述标题的内容,深入分析并提供更多的细节和数据支持。"
    }
]
用户输入如下:
"""

completion_prompt = """
你现在是一个智能助手。你的任务是接收一个包含 `title` 和 `describe` 的字典,并根据 `describe` 中的指导展开写作
输入示例:
{
    "title": "# 一级标题",
    "describe": "这是写作的描述。"
}

输出:
这是展开写作写的内容
接收如下:

"""

# 创建大纲生成器和内容生成器
toc_generator = lazyllm.OnlineChatModule()
content_generator = lazyllm.OnlineChatModule()

# 创建FastAPI应用
app = FastAPI(title="智能写作助手", version="1.0.0")

# 添加CORS支持
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# HTML页面内容
HTML_PAGE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能写作助手</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: '华文中宋', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            overflow: hidden;
        }

        .header {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 20px;
            text-align: center;
        }

        .main-content {
            display: flex;
            gap: 20px;
            padding: 20px;
        }

        .input-section {
            flex: 1;
        }

        .output-section {
            flex: 2;
        }

        .section-title {
            margin-bottom: 15px;
            color: #333;
            padding-bottom: 10px;
            border-bottom: 2px solid #f1f3f5;
        }

        textarea {
            width: 100%;
            padding: 15px;
            border: 2px solid #e9ecef;
            border-radius: 10px;
            min-height: 150px;
            resize: vertical;
            font-family: inherit;
            margin-bottom: 15px;
            font-size: 14px;
        }

        .button-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }

        button {
            padding: 12px 24px;
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-weight: 500;
            transition: transform 0.2s;
        }

        button:hover {
            transform: translateY(-2px);
        }

        button:active {
            transform: translateY(0);
        }

        .outline-container, .article-container {
            background: #f8f9fa;
            border-radius: 10px;
            padding: 20px;
            max-height: 400px;
            overflow-y: auto;
            margin-bottom: 20px;
        }

        .outline-item {
            margin-bottom: 15px;
            padding-left: 10px;
        }

        .outline-title {
            font-weight: bold;
            margin-bottom: 5px;
        }

        .outline-desc {
            color: #666;
            font-size: 14px;
        }

        .loading {
            color: #666;
            padding: 10px;
            font-style: italic;
        }

        .article-content h1, .article-content h2, .article-content h3 {
            margin-top: 20px;
            margin-bottom: 10px;
        }

        .article-content p {
            margin-bottom: 15px;
            line-height: 1.6;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>智能写作助手</h1>
        </div>

        <div class="main-content">
            <div class="input-section">
                <h2 class="section-title">写作主题</h2>
                <textarea id="topicInput" placeholder="请输入您想要写作的主题..."></textarea>

                <div class="button-group">
                    <button onclick="generateOutline()">生成大纲</button>
                    <button onclick="generateArticle()">生成文章</button>
                </div>

                <h2 class="section-title">生成的大纲</h2>
                <div class="outline-container" id="outlineContainer">
                    请先输入主题并生成大纲
                </div>
            </div>

            <div class="output-section">
                <h2 class="section-title">生成的文章</h2>
                <div class="article-container" id="articleContainer">
                    文章将在这里显示
                </div>
            </div>
        </div>
    </div>

    <script>
        let currentOutline = [];

        async function generateOutline() {
            const topic = document.getElementById('topicInput').value.trim();
            if (!topic) {
                alert('请输入写作主题');
                return;
            }

            const outlineContainer = document.getElementById('outlineContainer');
            outlineContainer.innerHTML = '<div class="loading">正在生成大纲...</div>';

            try {
                const response = await fetch('/generate-outline', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ topic: topic })
                });

                if (!response.ok) {
                    throw new Error('生成大纲失败');
                }

                const data = await response.json();
                currentOutline = data.outline;

                // 显示大纲
                let outlineHtml = '';
                currentOutline.forEach(item => {
                    outlineHtml += `
                        <div class="outline-item">
                            <div class="outline-title">${item.title}</div>
                            <div class="outline-desc">${item.describe}</div>
                        </div>
                    `;
                });

                outlineContainer.innerHTML = outlineHtml;

            } catch (error) {
                outlineContainer.innerHTML = `生成大纲时出错: ${error.message}`;
                console.error(error);
            }
        }

        async function generateArticle() {
            if (currentOutline.length === 0) {
                alert('请先生成大纲');
                return;
            }

            const articleContainer = document.getElementById('articleContainer');
            articleContainer.innerHTML = '<div class="loading">正在生成文章...</div>';

            try {
                const response = await fetch('/generate-article', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ outline: currentOutline })
                });

                if (!response.ok) {
                    throw new Error('生成文章失败');
                }

                const data = await response.json();
                articleContainer.innerHTML = data.article;

            } catch (error) {
                articleContainer.innerHTML = `生成文章时出错: ${error.message}`;
                console.error(error);
            }
        }
    </script>
</body>
</html>
"""


@app.get("/", response_class=HTMLResponse)
async def get_writer_interface():
    """返回智能写作助手界面"""
    return HTMLResponse(content=HTML_PAGE, media_type="text/html")


@app.post("/generate-outline")
async def generate_outline(request: dict):
    """生成写作大纲"""
    try:
        topic = request.get("topic", "").strip()
        if not topic:
            raise HTTPException(status_code=400, detail="主题不能为空")

        print(f"收到写作主题: {topic}")

        # 生成大纲
        full_prompt = toc_prompt + topic
        outline_str = toc_generator(full_prompt)

        # 解析为JSON
        outline = json.loads(outline_str)

        print(f"生成大纲成功,共{len(outline)}个节点")

        return JSONResponse(content={
            "outline": outline,
            "status": "success"
        })

    except json.JSONDecodeError:
        return JSONResponse(
            status_code=500,
            content={
                "message": "解析大纲失败",
                "status": "error"
            }
        )
    except Exception as e:
        print(f"生成大纲错误: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "message": f"生成大纲时发生错误: {str(e)}",
                "status": "error"
            }
        )


@app.post("/generate-article")
async def generate_article(request: dict):
    """根据大纲生成文章"""
    try:
        outline = request.get("outline", [])
        if not outline:
            raise HTTPException(status_code=400, detail="大纲不能为空")

        print(f"开始根据大纲生成文章,共{len(outline)}个节点")

        article_parts = []

        # 逐个处理大纲节点
        for item in outline:
            title = item.get("title", "")
            describe = item.get("describe", "")

            if not title or not describe:
                continue

            # 生成内容
            prompt = completion_prompt + json.dumps(item, ensure_ascii=False)
            content = content_generator(prompt)

            # 添加到文章部分
            article_parts.append(f"{title}\n\n{content}")

        # 合并所有部分
        full_article = "\n\n".join(article_parts)

        print("文章生成完成")

        return JSONResponse(content={
            "article": full_article,
            "status": "success"
        })

    except Exception as e:
        print(f"生成文章错误: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "message": f"生成文章时发生错误: {str(e)}",
                "status": "error"
            }
        )


@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {"status": "healthy", "service": "intelligent-writer"}


if __name__ == "__main__":
    print("🚀 启动智能写作助手服务器...")
    print("📍 本地访问: http://localhost:23333")
    print("⏹️  按 Ctrl+C 停止服务器")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=23333,
        log_level="info"
    )

运行效果

在这里插入图片描述

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_robot/

让我们把上面定义好的模型组装起来:

with 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')]

在上面代码中,我们首先实例化了一个顺序执行的 ppl,在这个 ppl 中先进行意图识别,设置ppl.cls。 然后为了保证意图识别的鲁棒性,在意图识别之后设置一个 ppl.cls_normalizer 的匿名函数, 将识别的结果仅映射到预制列表属性项中,即:确保识别的结果只在预制表内。

with switch(judge_on_full_input=False).bind(_0, ppl.input) as ppl.sw:

对于这行代码:

  • 我们首先关注 bind(_0, ppl.input), 其中 _0 是上一步输出的结果第0个参数,即意图列表中的一个意图。ppl.input是用户的输入(对应设计图中红色线条)。所以这行代码是给 switch 控制流设置了两个参数,第一个参数是意图,第二个参数是用户的输入。更多 bind 使用方法参见:参数绑定

  • 然后 judge_on_full_input=False,可以将输入分为两部分,第一部分是作为判断条件,剩下部分作为分支的输入,否则如果为 True 就会把整个输入作为判断条件和分支输入。

  • 最后我们将实例化后的 switch 也添加到了 ppl 上:ppl.sw。更多参见:Switch

剩下代码基本一致,都是设置条件和对应路由分支,以下面代码为例:

ppl.sw.case[chatflow_intent_list[0], chat]

该代码的第一个参数是意图列表的第一个元素,即"聊天",这个是判断条件的依据。 如果 switch 输入的第一个参数是“聊天”,那么就会走向这个分支 chat。而 chat 的输入就是 ppl.input

启动应用

最后,我们将控制流 ppl 套入一个客户端,并启动部署(start()),在部署完后保持客户端不关闭(wait())

WebModule(ppl, history=[chat], audio=True, port=8847).start().wait()

这里由于需要用到麦克风来捕获声音,所以设置了 audio=True

完整py代码:

import lazyllm
from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
import uvicorn
import os
import json
import tempfile
from pathlib import Path

# 设置API Key
os.environ['LAZYLLM_DOUBAO_API_KEY'] = '豆包API_KEY'

# 创建聊天模块
chat = lazyllm.OnlineChatModule()
classifier = lazyllm.OnlineChatModule()
painter = lazyllm.OnlineChatModule()
musician = lazyllm.OnlineChatModule()

# 定义意图列表
chatflow_intent_list = ["聊天", "语音识别", "图片问答", "画图", "生成音乐", "文字转语音"]

# 定义各类提示词
agent_prompt = f"""
现在你是一个意图分类引擎,负责根据对话信息分析用户输入文本并确定唯一的意图类别。
你只需要回复意图的名字即可,不要额外输出其他字段,也不要进行翻译。"intent_list"为所有意图名列表。

如果输入中带有attachments,根据attachments的后缀类型以最高优先级确定意图:
如果是图像后缀如.jpg、.png等,则输出:图片问答;
如果是音频后缀如.mp3、.wav等,则输出:语音识别。

## 示例
User: 你好啊
Assistant: 聊天
"""

painter_prompt = '现在你是一位绘图提示词大师,能够将用户输入的任意中文内容转换成英文绘图提示词,在本任务中你需要将任意输入内容转换成英文绘图提示词,并且你可以丰富和扩充提示词内容。'
musician_prompt = '现在你是一位作曲提示词大师,能够将用户输入的任意中文内容转换成英文作曲提示词,在本任务中你需要将任意输入内容转换成英文作曲提示词,并且你可以丰富和扩充提示词内容。'

# 创建FastAPI应用
app = FastAPI(title="LazyLLM 多模态聊天", version="1.0.0")

# 添加CORS支持
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 确保临时目录存在
TEMP_DIR = Path("temp_files")
TEMP_DIR.mkdir(exist_ok=True)

# HTML页面内容
HTML_PAGE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多模态助手 - LazyLLM</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: '华文中宋';
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .chat-container {
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            width: 100%;
            max-width: 800px;
            height: 600px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .chat-header {
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            padding: 20px;
            text-align: center;
        }

        .chat-header h1 {
            font-size: 24px;
            font-weight: 300;
        }

        .intent-buttons {
            background: #f0f2f5;
            padding: 10px;
            display: flex;
            gap: 8px;
            overflow-x: auto;
            border-bottom: 1px solid #e9ecef;
        }

        .function-btn {
            padding: 6px 12px;
            border: none;
            border-radius: 15px;
            background: white;
            cursor: pointer;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 5px;
            white-space: nowrap;
            transition: all 0.2s;
        }

        .function-btn:hover {
            background: #e6f7ff;
        }

        .function-btn.active {
            background: #007bff;
            color: white;
        }

        .chat-messages {
            flex: 1;
            padding: 20px;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .message {
            max-width: 70%;
            padding: 12px 16px;
            border-radius: 18px;
            margin-bottom: 10px;
            animation: fadeIn 0.3s ease-in;
        }

        .user-message {
            align-self: flex-end;
            background: #007bff;
            color: white;
            border-bottom-right-radius: 5px;
        }

        .bot-message {
            align-self: flex-start;
            background: #f1f3f5;
            color: #333;
            border-bottom-left-radius: 5px;
        }

        .message-content img {
            max-width: 100%;
            border-radius: 10px;
            margin-top: 5px;
        }

        .message-content audio {
            margin-top: 5px;
            width: 100%;
        }

        .chat-input {
            padding: 20px;
            background: #f8f9fa;
            border-top: 1px solid #e9ecef;
            display: flex;
            gap: 10px;
        }

        .input-actions {
            display: flex;
            gap: 10px;
            align-items: center;
        }

        .attachment-btn {
            background: white;
            border: 1px solid #e9ecef;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.2s;
        }

        .attachment-btn:hover {
            background: #e6f7ff;
        }

        #fileInput {
            display: none;
        }

        .message-input {
            flex: 1;
            padding: 12px 16px;
            border: 2px solid #e9ecef;
            border-radius: 25px;
            outline: none;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        .message-input:focus {
            border-color: #007bff;
        }

        .send-button {
            padding: 12px 24px;
            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-weight: 500;
            transition: transform 0.2s;
        }

        .send-button:hover {
            transform: translateY(-2px);
        }

        .send-button:active {
            transform: translateY(0);
        }

        .typing-indicator {
            align-self: flex-start;
            background: #f1f3f5;
            color: #666;
            padding: 12px 16px;
            border-radius: 18px;
            border-bottom-left-radius: 5px;
            font-style: italic;
        }

        .attachment-preview {
            align-self: flex-end;
            margin-bottom: 10px;
            max-width: 70%;
        }

        .attachment-preview img {
            max-width: 100%;
            border-radius: 10px;
        }

        .attachment-preview audio {
            width: 100%;
        }

        .remove-attachment {
            color: white;
            background: rgba(0,0,0,0.3);
            border: none;
            border-radius: 50%;
            width: 24px;
            height: 24px;
            cursor: pointer;
            position: absolute;
            top: 5px;
            right: 5px;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        /* 滚动条样式 */
        .chat-messages::-webkit-scrollbar {
            width: 6px;
        }

        .intent-buttons::-webkit-scrollbar {
            height: 4px;
        }

        .chat-messages::-webkit-scrollbar-track,
        .intent-buttons::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb,
        .intent-buttons::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 3px;
        }

        .chat-messages::-webkit-scrollbar-thumb:hover,
        .intent-buttons::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }
    </style>
</head>
<body>
    <div class="chat-container">
        <div class="chat-header">
            <h1>✨ 多模态助手 - LazyLLM</h1>
        </div>

        <div class="intent-buttons">
            <button class="function-btn active" onclick="setIntent('聊天')">💬 聊天</button>
            <button class="function-btn" onclick="setIntent('语音识别')">🎤 语音识别</button>
            <button class="function-btn" onclick="setIntent('图片问答')">🖼️ 图片问答</button>
            <button class="function-btn" onclick="setIntent('画图')">🎨 画图</button>
            <button class="function-btn" onclick="setIntent('生成音乐')">🎵 生成音乐</button>
            <button class="function-btn" onclick="setIntent('文字转语音')">🔊 文字转语音</button>
        </div>

        <div class="chat-messages" id="chatMessages">
            <div class="message bot-message">
                你好!我是多模态助手,有什么可以帮您的吗?您可以选择不同的功能按钮来使用各种服务。
            </div>
        </div>

        <div class="chat-input">
            <div class="input-actions">
                <label class="attachment-btn" for="fileInput">📎</label>
                <input type="file" id="fileInput" accept="image/*,audio/*">
            </div>
            <input 
                type="text" 
                class="message-input" 
                id="messageInput" 
                placeholder="输入您的消息..." 
                autocomplete="off"
            >
            <button class="send-button" onclick="sendMessage()">发送</button>
        </div>
    </div>

    <script>
        const chatMessages = document.getElementById('chatMessages');
        const messageInput = document.getElementById('messageInput');
        const fileInput = document.getElementById('fileInput');
        const functionButtons = document.querySelectorAll('.function-btn');
        let currentIntent = '聊天';
        let currentAttachment = null;

        // 自动聚焦输入框
        messageInput.focus();

        // 设置意图
        function setIntent(intent) {
            currentIntent = intent;
            // 更新按钮样式
            functionButtons.forEach(btn => {
                if (btn.getAttribute('onclick').includes(intent)) {
                    btn.classList.add('active');
                } else {
                    btn.classList.remove('active');
                }
            });
            // 清空输入和附件
            messageInput.value = '';
            clearAttachment();
        }

        // 添加消息
        function addMessage(content, isUser = false, isAttachment = false) {
            const messageDiv = document.createElement('div');
            messageDiv.className = isUser ? 'message user-message' : 'message bot-message';

            const contentDiv = document.createElement('div');
            contentDiv.className = 'message-content';

            if (isAttachment) {
                if (content.type.startsWith('image/')) {
                    contentDiv.innerHTML = `<img src="${content.url}" alt="附件图片">`;
                } else if (content.type.startsWith('audio/')) {
                    contentDiv.innerHTML = `<audio controls src="${content.url}">您的浏览器不支持音频播放</audio>`;
                }
                if (content.caption) {
                    const captionDiv = document.createElement('div');
                    captionDiv.textContent = content.caption;
                    captionDiv.style.marginTop = '5px';
                    contentDiv.appendChild(captionDiv);
                }
            } else {
                contentDiv.textContent = content;
            }

            messageDiv.appendChild(contentDiv);
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        // 显示附件预览
        function showAttachmentPreview(file) {
            clearAttachment();

            const previewDiv = document.createElement('div');
            previewDiv.className = 'attachment-preview';
            previewDiv.style.position = 'relative';
            previewDiv.id = 'attachmentPreview';

            const removeBtn = document.createElement('button');
            removeBtn.className = 'remove-attachment';
            removeBtn.textContent = '×';
            removeBtn.onclick = clearAttachment;

            if (file.type.startsWith('image/')) {
                const img = document.createElement('img');
                img.src = URL.createObjectURL(file);
                previewDiv.appendChild(img);
            } else if (file.type.startsWith('audio/')) {
                const audio = document.createElement('audio');
                audio.controls = true;
                audio.src = URL.createObjectURL(file);
                previewDiv.appendChild(audio);
            }

            previewDiv.appendChild(removeBtn);
            chatMessages.appendChild(previewDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;

            currentAttachment = file;
        }

        // 清除附件
        function clearAttachment() {
            const preview = document.getElementById('attachmentPreview');
            if (preview) {
                preview.remove();
            }
            currentAttachment = null;
            fileInput.value = '';
        }

        // 文件选择处理
        fileInput.addEventListener('change', function(e) {
            if (this.files && this.files[0]) {
                showAttachmentPreview(this.files[0]);
                // 根据文件类型自动切换意图
                if (this.files[0].type.startsWith('image/')) {
                    setIntent('图片问答');
                } else if (this.files[0].type.startsWith('audio/')) {
                    setIntent('语音识别');
                }
            }
        });

        function showTyping() {
            const typingDiv = document.createElement('div');
            typingDiv.className = 'message typing-indicator';
            typingDiv.id = 'typingIndicator';
            typingDiv.textContent = 'AI正在处理...';
            chatMessages.appendChild(typingDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }

        function hideTyping() {
            const typingIndicator = document.getElementById('typingIndicator');
            if (typingIndicator) {
                typingIndicator.remove();
            }
        }

        async function sendMessage() {
            const message = messageInput.value.trim();
            // 检查是否有内容或附件
            if (!message && !currentAttachment) return;

            // 添加用户消息
            if (message) {
                addMessage(message, true);
            }
            if (currentAttachment) {
                addMessage({
                    url: URL.createObjectURL(currentAttachment),
                    type: currentAttachment.type,
                    caption: message || '附件'
                }, true, true);
            }

            // 清空输入
            messageInput.value = '';
            const preview = document.getElementById('attachmentPreview');
            if (preview) {
                preview.remove();
            }

            // 显示正在处理提示
            showTyping();

            try {
                // 创建FormData
                const formData = new FormData();
                formData.append('message', message);
                formData.append('intent', currentIntent);
                if (currentAttachment) {
                    formData.append('file', currentAttachment);
                }

                const response = await fetch('/chat', {
                    method: 'POST',
                    body: formData
                });

                if (!response.ok) {
                    throw new Error('网络请求失败');
                }

                const data = await response.json();
                hideTyping();

                // 处理不同类型的响应
                if (data.response_type === 'image') {
                    addMessage({
                        url: data.response,
                        type: 'image/jpeg',
                        caption: ''
                    }, false, true);
                } else if (data.response_type === 'audio') {
                    addMessage({
                        url: data.response,
                        type: 'audio/mpeg',
                        caption: ''
                    }, false, true);
                } else {
                    addMessage(data.response, false);
                }

            } catch (error) {
                hideTyping();
                addMessage('抱歉,发生错误:' + error.message, false);
                console.error('Error:', error);
            } finally {
                // 重置附件
                currentAttachment = null;
                fileInput.value = '';
            }
        }

        // 支持回车键发送
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });

        // 自动调整输入框高度
        messageInput.addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = (this.scrollHeight) + 'px';
        });
    </script>
</body>
</html>
"""


@app.get("/", response_class=HTMLResponse)
async def get_chat_interface():
    """返回聊天界面"""
    return HTMLResponse(content=HTML_PAGE, media_type="text/html")


@app.post("/chat")
async def chat_endpoint(
        message: str = Form(""),
        intent: str = Form(""),
        file: UploadFile = File(None)
):
    """处理聊天消息"""
    try:
        message = message.strip()
        file_path = None
        file_type = None

        # 保存上传的文件
        if file:
            file_ext = os.path.splitext(file.filename)[1].lower()
            file_type = file.content_type
            with tempfile.NamedTemporaryFile(
                    suffix=file_ext,
                    dir=TEMP_DIR,
                    delete=False
            ) as temp_file:
                temp_file.write(await file.read())
                file_path = temp_file.name

        # 确定意图
        final_intent = intent
        if file:
            # 根据文件类型确定意图
            if file_type and file_type.startswith('image/'):
                final_intent = "图片问答"
            elif file_type and file_type.startswith('audio/'):
                final_intent = "语音识别"
        elif not intent or intent not in chatflow_intent_list:
            # 调用分类器确定意图
            classify_prompt = f"{agent_prompt}\nintent_list: {chatflow_intent_list}\nUser: {message}\nAssistant:"
            final_intent = classifier(classify_prompt).strip()
            if final_intent not in chatflow_intent_list:
                final_intent = "聊天"  # 默认使用聊天意图

        print(f"收到消息: {message}, 意图: {final_intent}, 文件: {file_path}")

        response_content = ""
        response_type = "text"

        # 根据不同意图处理
        if final_intent == "聊天":
            response_content = chat(message)

        elif final_intent == "语音识别":
            if file_path and file_type.startswith('audio/'):
                # 这里只是模拟语音识别,实际应用中需要调用语音识别API
                response_content = f"已识别音频内容: 这是模拟的语音识别结果(实际应用中会替换为真实识别内容)"
            else:
                response_content = "请上传音频文件进行语音识别"

        elif final_intent == "图片问答":
            if file_path and file_type.startswith('image/'):
                # 这里只是模拟图片问答,实际应用中需要调用图片理解API
                response_content = f"图片分析结果: 这是模拟的图片问答结果,针对您的问题:{message}(实际应用中会替换为真实分析内容)"
            else:
                response_content = "请上传图片进行问答"

        elif final_intent == "画图":
            # 生成英文绘图提示词
            prompt = f"{painter_prompt}\n用户输入: {message}\n英文提示词:"
            en_prompt = painter(prompt).strip()
            # 这里只是模拟画图功能,实际应用中需要调用画图API
            response_content = f"/placeholder-image?prompt={en_prompt}"  # 占位图URL
            response_type = "image"

        elif final_intent == "生成音乐":
            # 生成英文作曲提示词
            prompt = f"{musician_prompt}\n用户输入: {message}\n英文提示词:"
            en_prompt = musician(prompt).strip()
            # 这里只是模拟生成音乐,实际应用中需要调用音乐生成API
            response_content = f"/placeholder-audio?prompt={en_prompt}"  # 占位音频URL
            response_type = "audio"

        elif final_intent == "文字转语音":
            if message:
                # 这里只是模拟文字转语音,实际应用中需要调用TTS API
                response_content = f"/placeholder-audio?text={message}"  # 占位音频URL
                response_type = "audio"
            else:
                response_content = "请输入要转换的文字"

        print(f"AI回复: {response_content}, 类型: {response_type}")

        return JSONResponse(content={
            "response": response_content,
            "response_type": response_type,
            "status": "success",
            "intent": final_intent
        })

    except Exception as e:
        print(f"错误: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "response": f"抱歉,发生错误: {str(e)}",
                "response_type": "text",
                "status": "error"
            }
        )
    finally:
        # 清理临时文件
        if file_path and os.path.exists(file_path):
            try:
                os.remove(file_path)
            except:
                pass


@app.get("/placeholder-image")
async def placeholder_image(prompt: str):
    """占位图片,实际应用中应替换为真实的图片生成API"""
    return FileResponse("placeholder_image.jpg")  # 请准备一张占位图片


@app.get("/placeholder-audio")
async def placeholder_audio(prompt: str = None, text: str = None):
    """占位音频,实际应用中应替换为真实的音频生成API"""
    return FileResponse("placeholder_audio.mp3")  # 请准备一个占位音频


@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {"status": "healthy", "service": "lazyllm-multimodal-chat"}


if __name__ == "__main__":
    print("🚀 启动 LazyLLM 多模态聊天服务器...")
    print("📍 本地访问: http://localhost:23333")
    print("🤖 准备好与多模态AI聊天了!")
    print("⏹️  按 Ctrl+C 停止服务器")

    # 测试基本功能
    try:
        test_response = chat("你好")
        print(f"✅ 基本功能测试成功: {test_response[:50]}...")
    except Exception as e:
        print(f"❌ 基本功能测试失败: {e}")
        print("请检查API Key设置")

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=23333,
        log_level="info"
    )

运行效果:

在这里插入图片描述

五、常见问题与解决方法

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/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。


在这里插入图片描述

     📣 亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(或者关注下方公众号,看见后第一时间回复,还有海量编程资料等你来领!),博主看见后一定及时给您答复 💌💌💌

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐