基于LazyLLM多Agent大模型应用的开发框架,搭建本地大模型AI工具,你贴身的写作、论文小助手
在搭建本地大模型作为写作、论文小助手时,开发者常面临诸多技术难题:模型部署需研究复杂 API 服务,微调模型要应对框架选择与模型切换的困扰,工具落地还需掌握 Web 开发技能,这让初级开发者望而却步,资深专家也需为适配需求、集成新工具耗费大量精力。而 LazyLLM 多 Agent 大模型应用开发框架可有效解决这些问题,它打包了应用搭建、数据准备、模型部署、微调、评测等全环节工具。初级开发者借助预
在搭建本地大模型作为写作、论文小助手时,开发者常面临诸多技术难题:模型部署需研究复杂 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,成就感直接拉满!
运行效果
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 生成内容不符合预期
- 原因分析:可能是提示词不够清晰、准确,没有完整传达你的需求;也可能是模型对某些特定领域的知识理解不够深入,导致生成的内容偏离预期。
- 解决方法:仔细检查提示词,确保语言表达清晰、逻辑连贯,将所有关键信息准确传达给 LazyLLM 写作助手 Agent。如果是特定领域的问题,可以在提示词中增加一些相关的背景知识或示例,引导模型生成更符合要求的内容。此外,你还可以尝试调整提示词的表述方式,或者多次生成,从不同的结果中选择最符合需求的内容。
5.2 生成速度较慢
- 原因分析:生成速度可能受到多种因素的影响,如网络状况不佳、模型负载过高、提示词过于复杂等。
- 解决方法:首先检查网络连接是否稳定,可以尝试切换网络环境或重启网络设备。如果是模型负载过高,可以选择在非高峰时段进行生成,或者考虑使用性能更高的模型服务。对于过于复杂的提示词,可以适当简化,将一个大的任务拆分成多个小的任务,逐步生成内容,以提高生成速度。
5.3 格式问题
- 原因分析:LazyLLM 写作助手 Agent 生成的内容可能在格式上不符合你的要求,例如字体、字号、行距、段落格式等。
- 解决方法:在生成内容后,使用相应的文字编辑软件(如 Word、WPS 等)对格式进行调整。根据你的具体需求,设置字体、字号、行距等参数,对段落进行排版,使文章格式符合要求。同时,一些公众号平台或论文投稿系统可能有特定的格式要求,在发布或投稿前,要按照相应的要求进行格式转换和调整。
六、总结和展望
LazyLLM 写作助手 Agent 为论文、专栏、公众号文章等内容创作提供了极大的便利,能够帮助创作者快速生成初稿,提高创作效率。通过合理构建提示词、准确传达需求,结合人工的润色和优化,可以创作出高质量的内容。然而,目前的人工智能写作工具仍然存在一些局限性,需要我们在使用过程中不断探索和改进。未来,随着人工智能技术的不断发展和完善,相信 LazyLLM 写作助手 Agent 等工具将在内容创作领域发挥更加重要的作用,为我们带来更多的创作灵感和便利。希望本文的教程能够帮助你更好地使用 LazyLLM 写作助手 Agent,开启高效创作之旅。
📣 还不赶紧?关注LazyLLM,打造自己的人工智能平台吧!
七、附件资源
安装教程:https://mp.weixin.qq.com/
帮助文档:https://github.com/LazyAGI/LazyLLM/blob/main/README.CN.md
更多教程文档: https://docs.lazyllm.ai/
联系博主
xcLeigh 博主,全栈领域优质创作者,博客专家,目前,活跃在CSDN、微信公众号、小红书、知乎、掘金、快手、思否、微博、51CTO、B站、腾讯云开发者社区、阿里云开发者社区等平台,全网拥有几十万的粉丝,全网统一IP为 xcLeigh。希望通过我的分享,让大家能在喜悦的情况下收获到有用的知识。主要分享编程、开发工具、算法、技术学习心得等内容。很多读者评价他的文章简洁易懂,尤其对于一些复杂的技术话题,他能通过通俗的语言来解释,帮助初学者更好地理解。博客通常也会涉及一些实践经验,项目分享以及解决实际开发中遇到的问题。如果你是开发领域的初学者,或者在学习一些新的编程语言或框架,关注他的文章对你有很大帮助。
亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。
愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。
至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。
💞 关注博主 🌀 带你实现畅游前后端!
🥇 从零到一学习Python 🌀 带你玩转Python技术流!
🏆 人工智能学习合集 🌀 搭配实例教程与实战案例,帮你构建完整 AI 知识体系
💦 注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://xcleigh.blog.csdn.net/,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。
📣 亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(或者关注下方公众号,看见后第一时间回复,还有海量编程资料等你来领!),博主看见后一定及时给您答复 💌💌💌
更多推荐
所有评论(0)