别再手动拖拽了!用Excalidraw+Ollama,5分钟搞定文本转时序图(保姆级教程)
在本地,用你的语言画一张时序图:Excalidraw与Ollama的私密协作指南
深夜,你刚和同事开完一个技术方案对齐会。白板上画满了潦草的线条和方框,大家七嘴八舌地讨论着“客户端”、“服务端”、“消息队列”之间的交互。会议结束,你回到工位,面对空白的文档,需要把刚才讨论的复杂流程整理成一个清晰的时序图,以便存档和分享。你打开绘图工具,开始拖拽生命线、绘制箭头、调整文本位置……半小时过去了,图还没画完一半,而最初的灵感与讨论的细节,正在这繁琐的重复劳动中逐渐消磨。
这场景是否似曾相识?我们花费大量时间在“画图”这个动作本身,而非思考和设计流程。有没有一种方法,能让我们用最自然的方式——语言——来描述交互,然后自动获得一张可编辑、可分享的图表?更重要的是,如果这个过程完全在本地完成,不依赖任何云端服务,不泄露一丝一毫的内部系统设计细节,那该有多好?
今天,这个想法可以成为你工作流中的标准配置。我们将一起搭建一个完全离线的、由你掌控的“文本到图表”生成器。它的核心是两个强大开源工具的联姻:Excalidraw,那个广受喜爱、风格独特的手绘风白板;以及 Ollama,一个让你能在个人电脑上轻松运行大型语言模型的工具。我们将绕过所有云端API,在你的笔记本或公司内网服务器上,构建一条从自然语言描述到精美时序图的自动化流水线。这不仅仅是提高效率,更是对数据主权和隐私的一次彻底回归。
1. 为什么选择完全本地化的方案?
在深入技术细节之前,我们有必要先厘清一个核心问题:当云端AI服务唾手可得时,为何要“自找麻烦”地在本地部署?答案远不止于“技术控”的偏好,它关乎效率、成本、安全与控制的深层平衡。
云端方案的隐形成本:调用OpenAI或Claude的API生成图表描述,看似简单快捷,但背后隐藏着诸多限制。首先是延迟,网络请求与响应在关键会议中可能成为卡点。其次是成本,频繁调用API用于图表生成,长期累积是一笔不小的开销。但最关键的,是数据隐私与安全。当你将“用户登录时,请求先经过API网关,再由认证服务校验令牌,最后查询用户数据库”这样的内部架构描述发送给第三方服务时,你无法确切知道这些数据会被如何存储、处理或潜在利用。对于金融、医疗或任何处理敏感信息的行业,这构成了不可接受的风险。
相比之下,本地化方案提供了截然不同的价值主张:
- 绝对的数据隐私:所有数据处理、模型推理均在本地设备或内网服务器完成,原始文本描述和生成的中间数据从未离开你的可控环境。
- 零网络依赖与极低延迟:无论是在飞机上、客户现场没有外网的环境,还是公司内网隔离区,你都能无缝使用。模型推理在本地CPU/GPU上进行,响应速度极快。
- 完全的可定制性与控制:你可以自由选择最适合你场景的模型(比如更擅长理解技术流程的Code Llama),调整模型参数,甚至针对你公司的特定术语(如内部服务名“宙斯”、“阿波罗”)对模型进行微调,使其理解力远超通用模型。
- 一次部署,无限使用:模型下载到本地后,除了电费,几乎没有后续边际成本。你可以尽情实验、生成,无需担心账单暴涨。
Ollama的出现,极大地降低了本地运行大模型的门槛。它像一个模型管理器和运行时容器,让你通过一行命令就能拉取并运行Llama 3、Mistral、Gemma等主流开源模型。而Excalidraw,以其简洁的JSON数据结构和友好的API,成为了将模型输出的结构化信息“画”出来的绝佳画布。两者的结合,创造了一个安全、高效、且充满乐趣的自动化工具链。
2. 搭建你的本地AI绘图工坊:环境准备与模型选型
让我们开始动手。整个过程可以清晰地分为三个层次:本地模型服务层、逻辑处理与桥接层、以及前端展示与交互层。我们将自底向上,一步步构建。
2.1 第一步:让Ollama在你的机器上安家
Ollama的安装过程简单到令人惊讶。它支持macOS、Linux和Windows(通过WSL2)。
对于macOS和Linux用户,打开终端,执行以下命令即可:
curl -fsSL https://ollama.ai/install.sh | sh
安装完成后,运行 ollama serve 来启动服务。它会默认在 11434 端口启动一个本地API服务。
现在,你需要选择一个模型。对于“文本描述转时序图”这个任务,模型需要具备良好的自然语言理解能力和结构化输出能力。以下是几个经过验证的推荐选择:
| 模型名称 | 参数量 | 特点与适用场景 | 启动命令示例 |
|---|---|---|---|
| Llama 3 | 8B / 70B | Meta最新开源模型,通用能力强,指令跟随和代码理解出色,8B版本在消费级GPU上运行流畅。 | ollama run llama3:8b |
| Mistral | 7B | 以“小体积,大智慧”著称,在常识推理和代码任务上表现优异,对硬件要求友好。 | ollama run mistral |
| Code Llama | 7B / 13B | 专为代码相关任务微调,对于理解“客户端调用服务端”这类技术流程描述有先天优势。 | ollama run codellama:7b |
| Gemma | 2B / 7B | Google出品,轻量且高效,2B版本甚至在无GPU的笔记本上也能快速响应,适合作为入门尝试。 | ollama run gemma:2b |
提示:首次运行
ollama run <模型名>时会自动下载模型,请确保网络通畅。对于公司内网环境,可以考虑在一台有外网的机器上拉取模型后,再通过Ollama的模型导出/导入功能进行分发。
我个人的建议是从 llama3:8b 或 mistral 开始。它们在小参数量模型中取得了很好的平衡。运行后,你可以直接在终端与它对话测试:
>>> 请将“用户点击登录按钮,前端发送请求到认证服务”这句话分解成发送方、接收方和动作。
如果模型能给出结构化的回答,说明它已准备就绪。接下来,我们需要让它为Excalidraw服务。
2.2 第二步:构建桥梁——一个简单的API服务
Ollama提供了与OpenAI API兼容的接口,这大大简化了我们的工作。我们需要创建一个简单的后端服务(可以用Python + FastAPI, Node.js + Express等),它主要做三件事:
- 接收前端发送的文本描述。
- 调用本地的Ollama API,通过精心设计的Prompt让模型理解并结构化描述。
- 将结构化的数据转换成Excalidraw能识别的元素数组(JSON格式),返回给前端。
这里是一个使用Python和FastAPI的极简示例,展示核心逻辑:
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import requests
import json
app = FastAPI()
# 定义请求体结构
class DiagramRequest(BaseModel):
description: str
# Ollama API地址 (默认本地11434端口)
OLLAMA_API_URL = "http://localhost:11434/api/generate"
@app.post("/generate-diagram")
async def generate_diagram(request: DiagramRequest):
# 1. 构建给模型的Prompt,这是成功的关键!
prompt = f"""
你是一个将自然语言流程描述转换为时序图组件分析专家。
请分析以下描述,识别出所有参与者(如用户、客户端、服务器、数据库等)以及它们之间按时间顺序发生的消息传递。
以严格的JSON格式输出,包含两个字段:
1. "participants": 一个参与者名称的列表。
2. "messages": 一个字典列表,每个字典包含 "from"(发送方)、 "to"(接收方)、 "action"(动作描述)字段。
描述:{request.description}
输出示例:
{{
"participants": ["用户", "前端", "认证服务"],
"messages": [
{{"from": "用户", "to": "前端", "action": "点击登录按钮"}},
{{"from": "前端", "to": "认证服务", "action": "发送用户名和密码"}}
]
}}
只输出JSON,不要有其他任何解释。
"""
# 2. 调用本地Ollama服务
payload = {
"model": "llama3:8b", # 替换成你运行的模型名
"prompt": prompt,
"stream": False,
"format": "json" # 强烈要求模型以JSON格式回复
}
try:
response = requests.post(OLLAMA_API_URL, json=payload, timeout=60)
response.raise_for_status()
result = response.json()
model_output = result["response"]
# 3. 解析模型输出的JSON
diagram_data = json.loads(model_output.strip())
# 4. 将分析结果转换为Excalidraw元素(核心转换逻辑)
excalidraw_elements = convert_to_excalidraw(diagram_data)
return {"elements": excalidraw_elements}
except requests.exceptions.RequestException as e:
raise HTTPException(status_code=500, detail=f"调用Ollama服务失败: {e}")
except json.JSONDecodeError as e:
raise HTTPException(status_code=500, detail=f"解析模型输出失败: {e}")
def convert_to_excalidraw(diagram_data):
"""将参与者与消息列表转换为Excalidraw元素数组"""
elements = []
participants = diagram_data.get("participants", [])
messages = diagram_data.get("messages", [])
# 布局参数
start_x = 100
start_y = 100
participant_spacing = 200
lifeline_length = len(messages) * 60 + 100
# 1. 绘制参与者(生命线顶部的标签)
for i, actor in enumerate(participants):
elements.append({
"type": "text",
"x": start_x + i * participant_spacing,
"y": start_y,
"text": actor,
"fontSize": 20,
"fontFamily": 1, # Excalidraw的手绘字体
"id": f"actor_{i}"
})
# 可选:为每个参与者画一条垂直的生命线(虚线)
elements.append({
"type": "line",
"x": start_x + i * participant_spacing + 40, # 粗略居中
"y": start_y + 40,
"points": [[0,0], [0, lifeline_length]],
"strokeStyle": "dashed",
"strokeWidth": 2,
"id": f"lifeline_{i}"
})
# 2. 绘制消息箭头
current_y = start_y + 80
for msg in messages:
try:
from_idx = participants.index(msg["from"])
to_idx = participants.index(msg["to"])
except ValueError:
# 如果参与者不在列表中,跳过或处理错误
continue
from_x = start_x + from_idx * participant_spacing + 40
to_x = start_x + to_idx * participant_spacing + 40
# 绘制箭头
elements.append({
"type": "arrow",
"x": from_x,
"y": current_y,
"points": [[0, 0], [to_x - from_x, 0]],
"startArrowhead": None,
"endArrowhead": "arrow",
"strokeWidth": 2
})
# 在箭头上方或下方添加消息文本
text_x = min(from_x, to_x) + abs(to_x - from_x) / 2 - 30 # 粗略居中
elements.append({
"type": "text",
"x": text_x,
"y": current_y - 20,
"text": msg["action"],
"fontSize": 16,
"fontFamily": 1
})
current_y += 60 # 每条消息下移一定距离
return elements
运行这个服务 (uvicorn main:app --reload),你就拥有了一个本地API端点 http://localhost:8000/generate-diagram。它接收一段文本,并返回Excalidraw可以直接渲染的元素数组。
3. 驯服模型:Prompt工程与解析优化
模型的表现,九成取决于你如何与它对话。一个模糊的指令会得到混乱的结果,而一个清晰、结构化、带有示例的Prompt,则能引导模型成为得力的助手。我们的目标是让模型成为一个“时序图分析师”。
3.1 设计一个强健的Prompt
上面的代码中已经包含了一个基础Prompt,但我们可以让它更强大。一个优秀的Prompt通常包含以下几个部分:
- 角色设定:明确告诉模型它要扮演什么角色。
- 任务描述:清晰、无歧义地说明需要它做什么。
- 输入输出格式规范:这是关键!必须明确要求模型以特定格式(如JSON)输出,并给出精确的字段定义和示例。
- 约束条件:告诉模型不要做什么,比如“只输出JSON,不要有任何额外解释”。
- 上下文或示例(可选但推荐):提供一个或多个例子,让模型更好地理解你的期望。
让我们优化一下之前的Prompt:
你是一个软件架构师,专门负责将自然语言描述的业务流程转换为精确的时序图组件定义。
你的任务:
分析用户提供的流程描述,识别出所有参与交互的实体(参与者)以及它们之间按严格时间顺序交换的消息。
请遵循以下规则:
- 参与者:通常是系统、服务、用户角色或外部组件(如“用户”、“前端APP”、“订单服务”、“支付网关”、“数据库”)。将它们提取为一个列表。
- 消息:每个消息必须明确有发送方(from)、接收方(to)和具体的动作描述(action)。动作描述应简洁,使用动词开头(如“发送请求”、“返回结果”、“查询数据”)。
- 忽略描述中的非功能性语句(如“很快地”、“然后系统会”),只关注核心交互。
- 如果描述中存在条件判断(如“如果验证失败”),将其视为一条独立的消息,动作描述中可包含条件。
你必须以以下JSON格式输出,且只输出此JSON对象:
{
"participants": ["参与者A", "参与者B", ...],
"messages": [
{"from": "发送方1", "to": "接收方1", "action": "动作描述1"},
{"from": "发送方2", "to": "接收方2", "action": "动作描述2"}
]
}
示例1:
输入:“用户在前端提交表单,前端调用API服务,服务再写入数据库。”
输出:
{
"participants": ["用户", "前端", "API服务", "数据库"],
"messages": [
{"from": "用户", "to": "前端", "action": "提交表单"},
{"from": "前端", "to": "API服务", "action": "调用API"},
{"from": "API服务", "to": "数据库", "action": "写入数据"}
]
}
示例2:
输入:“客户端发起登录,认证服务检查密码,若正确则返回令牌,否则返回错误。”
输出:
{
"participants": ["客户端", "认证服务"],
"messages": [
{"from": "客户端", "to": "认证服务", "action": "发起登录请求"},
{"from": "认证服务", "to": "客户端", "action": "返回令牌(密码正确)"},
{"from": "认证服务", "to": "客户端", "action": "返回错误信息(密码错误)"}
]
}
现在,请处理以下描述:
{用户输入的具体描述}
这个Prompt更详细,提供了更明确的规则和多个示例,能极大提高模型输出结构的准确性和稳定性。
3.2 处理模型的“不听话”与后处理校验
即使有最好的Prompt,模型也可能出错。常见的错误包括:
- 输出非纯JSON:模型可能在JSON前后添加解释性文字。
- 参与者识别不全或重复。
- 消息顺序错乱。
因此,在我们的后端服务中,后处理校验环节必不可少。在 convert_to_excalidraw 函数之前,可以增加一个校验模块:
def validate_and_clean_diagram_data(raw_data: dict) -> dict:
"""校验并清理从模型获得的图表数据"""
cleaned = {"participants": [], "messages": []}
# 1. 确保participants是列表且去重
if "participants" in raw_data and isinstance(raw_data["participants"], list):
# 使用集合去重,同时保持原始顺序(Python 3.7+ dict保持插入顺序)
seen = set()
cleaned["participants"] = [p for p in raw_data["participants"] if not (p in seen or seen.add(p))]
# 2. 清理messages
if "messages" in raw_data and isinstance(raw_data["messages"], list):
for msg in raw_data["messages"]:
if isinstance(msg, dict) and all(k in msg for k in ("from", "to", "action")):
# 确保消息的发送方和接收方都在参与者列表中,如果不在,则自动添加
for role in [msg["from"], msg["to"]]:
if role not in cleaned["participants"]:
cleaned["participants"].append(role)
cleaned["messages"].append(msg)
else:
# 记录日志,跳过格式错误的消息
print(f"警告:跳过格式错误的消息: {msg}")
# 3. 如果参与者或消息为空,抛出错误
if not cleaned["participants"] or not cleaned["messages"]:
raise ValueError("无法从描述中提取有效的参与者或消息。请尝试更清晰的描述。")
return cleaned
将这个函数插入到解析模型输出之后、转换Excalidraw元素之前,可以确保数据的清洁和有效,避免前端渲染时崩溃。
4. 前端集成:让Excalidraw动起来
最后一步,我们需要一个简单的前端界面,让用户输入文本,调用我们的本地API,并将结果渲染到Excalidraw画布上。我们可以利用Excalidraw提供的React组件或Vanilla JS API轻松实现。
这里提供一个使用原生JavaScript和Excalidraw嵌入的极简示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>本地AI时序图生成器</title>
<script src="https://unpkg.com/@excalidraw/excalidraw@latest/dist/excalidraw.production.min.js"></script>
<style>
body { font-family: sans-serif; margin: 20px; }
.container { display: flex; height: 90vh; gap: 20px; }
.input-panel { flex: 1; display: flex; flex-direction: column; }
textarea { flex: 1; padding: 15px; font-size: 16px; border: 2px solid #ccc; border-radius: 8px; resize: none; }
button { margin-top: 10px; padding: 15px; font-size: 18px; background: #4CAF50; color: white; border: none; border-radius: 8px; cursor: pointer; }
button:disabled { background: #cccccc; }
.excalidraw-container { flex: 2; border: 2px solid #eee; border-radius: 8px; }
</style>
</head>
<body>
<h1>🔒 本地AI时序图生成器</h1>
<p>描述你的流程,一键生成可编辑的时序图。所有处理均在您的电脑本地完成。</p>
<div class="container">
<div class="input-panel">
<textarea id="descriptionInput" placeholder="例如:用户点击购买按钮,前端调用订单服务创建订单,订单服务调用库存服务扣减库存,最后调用支付服务发起收款。">
</textarea>
<button id="generateBtn" onclick="generateDiagram()">生成时序图</button>
<div id="status"></div>
</div>
<div class="excalidraw-container" id="excalidraw"></div>
</div>
<script>
const { Excalidraw } = window.ExcalidrawLib;
let excalidrawAPI = null;
// 初始化Excalidraw
const excalidrawElement = document.getElementById("excalidraw");
excalidrawAPI = new Excalidraw.Excalidraw({
target: excalidrawElement,
props: {
// 可以在这里配置Excalidraw的初始属性,如主题、UI等
viewModeEnabled: false,
zenModeEnabled: false,
gridModeEnabled: false,
}
});
async function generateDiagram() {
const description = document.getElementById('descriptionInput').value.trim();
const button = document.getElementById('generateBtn');
const statusDiv = document.getElementById('status');
if (!description) {
statusDiv.textContent = "请输入流程描述。";
statusDiv.style.color = 'red';
return;
}
button.disabled = true;
button.textContent = '生成中...';
statusDiv.textContent = '正在调用本地AI模型分析...';
statusDiv.style.color = 'blue';
try {
// 调用我们本地部署的后端API
const response = await fetch('http://localhost:8000/generate-diagram', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ description: description })
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status}`);
}
const result = await response.json();
statusDiv.textContent = '解析成功,正在渲染图表...';
// 将获取到的元素更新到Excalidraw画布
// 注意:这里我们直接替换整个场景。更优雅的做法是增量更新。
excalidrawAPI.updateScene({
elements: result.elements,
appState: {
viewBackgroundColor: '#fafafa', // 设置一个干净的背景色
},
});
statusDiv.textContent = '✅ 时序图已生成!您可以在右侧画布上直接拖动、编辑。';
statusDiv.style.color = 'green';
} catch (error) {
console.error('生成失败:', error);
statusDiv.textContent = `生成失败: ${error.message}`;
statusDiv.style.color = 'red';
} finally {
button.disabled = false;
button.textContent = '生成时序图';
}
}
</script>
</body>
</html>
将这个HTML文件保存,并用浏览器打开(可能需要解决CORS问题,在生产环境中可通过后端代理或配置解决)。现在,你拥有了一个完全在本地运行的、从文本到可编辑时序图的一站式工具。
5. 超越时序图:扩展你的本地AI绘图能力
一旦核心管道打通,你会发现,这套本地化“文本转图表”的范式拥有巨大的扩展潜力。它不仅仅局限于时序图。
流程图(Flowchart):Prompt可以调整为识别“开始”、“结束”、“判断”、“流程”等关键词,并生成包含菱形决策框、流程框的图表结构。Excalidraw的图形库完全支持这些元素。
架构图(C4 Model或简单框图):你可以训练模型识别“微服务”、“数据库”、“消息队列”、“负载均衡器”等组件,并按照一定的布局规范(如分层布局)将它们排列出来,并用箭头表示依赖关系。
状态机图(State Diagram):从“订单从待支付状态,经过支付成功事件,转移到已完成状态”这样的描述中,提取状态和转移事件,生成状态节点和转移箭头。
关键在于设计针对特定图表类型的Prompt和编写对应的转换函数。例如,对于流程图,你的转换函数 convert_to_excalidraw 需要知道如何绘制菱形(type: "diamond")和不同的箭头样式。
更进一步,你可以将这个工具集成到你的日常开发环境中:
- VS Code插件:在编辑Markdown或文档时,选中一段描述,右键一键生成图表并插入。
- 命令行工具(CLI):通过终端命令
draw-diagram “用户登录流程...”,自动生成图表并保存为图片或.excalidraw文件。 - CI/CD流水线:从代码注释或API定义文件中自动生成架构图或序列图,并嵌入到自动生成的文档站点中。
我自己的使用习惯是,在编写技术设计文档时,会专门用一段注释 <!-- DIAGRAM: 客户端请求网关... --> 来描述关键流程。然后运行一个简单的脚本,批量处理文档中的所有此类注释,自动替换成生成的图表图片或可交互的Excalidraw链接。这确保了文档中的图表永远与最新的设计文字描述同步,实现了真正的“图文同源”。
搭建这样一套系统初看有些步骤,但一旦跑通,它带来的效率提升和心智负担的减轻是巨大的。你不再需要在外网、内网、安全性、付费之间纠结。你的描述语言就是最强大的绘图工具,而这一切,都运行在你完全信任的本地环境里。
更多推荐
所有评论(0)