draw_architecture.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. """
  2. lambdagentpaas 系统架构图 — PPT 优化版 v3
  3. 修复:
  4. - 卡片标题与正文留足间距 (title_gap 参数)
  5. - 显式计算文字实际占位, 防止与 va 基准点的计算误差
  6. - 增大所有卡片高度, 给正文足够空间
  7. - L1/L2 层高度相应增加
  8. - 所有尺寸支持 SCALE 参数统一缩放
  9. 用法:
  10. python3 docs/assets/draw_architecture.py
  11. python3 docs/assets/draw_architecture.py --scale 1.5 # 放大 1.5 倍
  12. python3 docs/assets/draw_architecture.py --scale 0.8 # 缩小到 0.8 倍
  13. """
  14. import argparse
  15. import os
  16. import matplotlib
  17. matplotlib.use("Agg")
  18. import matplotlib.pyplot as plt
  19. from matplotlib.patches import FancyBboxPatch, Rectangle
  20. plt.rcParams["font.sans-serif"] = [
  21. "PingFang HK", "PingFang SC", "Hiragino Sans GB",
  22. "STHeiti", "Heiti TC", "Songti SC", "Arial Unicode MS",
  23. ]
  24. plt.rcParams["axes.unicode_minus"] = False
  25. # ═══════════════════════════════════════════════════════════
  26. # 配色
  27. # ═══════════════════════════════════════════════════════════
  28. C = {
  29. "L3_bg": "#FFF4E0", "L3_edge": "#E67E22", "L3_header": "#F39C12",
  30. "L3_card": "#FFD79A",
  31. "L2_bg": "#E3F2FD", "L2_edge": "#1976D2", "L2_header": "#1E88E5",
  32. "L2_card": "#A8D0F2",
  33. "L2_safety_bg": "#FFEBEE", "L2_safety_edge": "#C62828",
  34. "L2_safety_header": "#E53935", "L2_safety_card": "#FFCDD2",
  35. "L1_bg": "#F3E5F5", "L1_edge": "#7B1FA2", "L1_header": "#8E24AA",
  36. "L1_card": "#D7A8E0", "L1_paper": "#C789D3",
  37. "L0_bg": "#E8F5E9", "L0_edge": "#388E3C", "L0_header": "#43A047",
  38. "L0_card": "#9FD9A5",
  39. "text": "#1A1A1A",
  40. "text_mid": "#37474F",
  41. "text_light":"#FFFFFF",
  42. "arrow": "#263238",
  43. }
  44. # ═══════════════════════════════════════════════════════════
  45. # 绘图辅助
  46. # ═══════════════════════════════════════════════════════════
  47. class Scaler:
  48. """统一缩放器 — 一个参数控制所有尺寸。"""
  49. def __init__(self, scale: float = 1.0):
  50. self.s = scale
  51. def fs(self, size: int) -> float:
  52. """字号缩放"""
  53. return size * self.s
  54. def lw(self, width: float) -> float:
  55. """线宽缩放"""
  56. return width * self.s
  57. # 数据单位的坐标 / 尺寸保持不变 (ax 坐标系固定 0-20, 0-15.5)
  58. # 只需要让 figsize 按 scale 变化即可让输出像素 = scale × 原始像素
  59. def figsize(self, w: float, h: float):
  60. return (w * self.s, h * self.s)
  61. def layer(ax, s: Scaler, x, y, w, h, header_h, label, bg, edge, header_color):
  62. """层: 外框 + 顶部 header 带"""
  63. ax.add_patch(FancyBboxPatch(
  64. (x, y), w, h,
  65. boxstyle="round,pad=0.08,rounding_size=0.18",
  66. facecolor=bg, edgecolor=edge, linewidth=s.lw(2.5), alpha=0.5, zorder=1,
  67. ))
  68. header_y = y + h - header_h
  69. ax.add_patch(Rectangle(
  70. (x + 0.1, header_y + 0.05), w - 0.2, header_h - 0.1,
  71. facecolor=header_color, edgecolor="none", alpha=0.92, zorder=2,
  72. ))
  73. ax.text(
  74. x + w / 2, header_y + header_h / 2, label,
  75. ha="center", va="center",
  76. fontsize=s.fs(17), fontweight="bold",
  77. color=C["text_light"], zorder=5,
  78. )
  79. def card(ax, s: Scaler, x, y, w, h, title, lines, face, edge,
  80. title_size=15, body_size=12,
  81. pad_top=0.22, title_gap=0.28):
  82. """
  83. 组件卡片 — 正确的文字布局
  84. 如果有 lines (正文):
  85. - 标题 va='top', 放在卡片顶部 (距顶 pad_top)
  86. - 正文 va='top', 在标题下方 title_gap + 标题高度 处
  87. 否则:
  88. - 标题居中显示
  89. """
  90. ax.add_patch(FancyBboxPatch(
  91. (x, y), w, h,
  92. boxstyle="round,pad=0.06,rounding_size=0.12",
  93. facecolor=face, edgecolor=edge, linewidth=s.lw(1.8), zorder=3,
  94. ))
  95. if lines:
  96. title_h = title_size / 72.0
  97. title_top_y = y + h - pad_top
  98. ax.text(
  99. x + w / 2, title_top_y, title,
  100. ha="center", va="top",
  101. fontsize=s.fs(title_size), fontweight="bold",
  102. color=C["text"], zorder=4,
  103. )
  104. body_top_y = title_top_y - title_h - title_gap
  105. body = "\n".join(lines)
  106. ax.text(
  107. x + w / 2, body_top_y, body,
  108. ha="center", va="top",
  109. fontsize=s.fs(body_size), color=C["text_mid"],
  110. linespacing=1.55, zorder=4,
  111. )
  112. else:
  113. ax.text(
  114. x + w / 2, y + h / 2, title,
  115. ha="center", va="center",
  116. fontsize=s.fs(title_size), fontweight="bold",
  117. color=C["text"], zorder=4,
  118. )
  119. def arrow_down(ax, s: Scaler, x, y_top, y_bot, color=None, lw=3):
  120. ax.annotate(
  121. "", xy=(x, y_bot), xytext=(x, y_top),
  122. arrowprops=dict(arrowstyle="-|>", color=color or C["arrow"],
  123. lw=s.lw(lw), mutation_scale=25),
  124. zorder=2,
  125. )
  126. # ═══════════════════════════════════════════════════════════
  127. # 主绘图
  128. # ═══════════════════════════════════════════════════════════
  129. def draw(output: str, scale: float = 1.0):
  130. """
  131. 绘制架构图。
  132. Args:
  133. output: 输出 PNG 路径
  134. scale: 全局缩放比例 (1.0 = 默认; 1.5 = 放大; 0.8 = 缩小)
  135. 所有字号、线宽、figsize 按此比例缩放
  136. """
  137. s = Scaler(scale)
  138. BASE_W, BASE_H = 20, 15.5 # 基础画布尺寸 (数据单位)
  139. fig, ax = plt.subplots(figsize=s.figsize(BASE_W, BASE_H))
  140. ax.set_xlim(0, BASE_W)
  141. ax.set_ylim(0, BASE_H)
  142. ax.axis("off")
  143. # ━━━ 顶部标题 ━━━
  144. ax.text(10, 15.0, "lambdagentpaas 系统架构",
  145. ha="center", va="center",
  146. fontsize=s.fs(28), fontweight="bold", color=C["text"])
  147. ax.text(10, 14.45, "形式化 λA 演算驱动的智能体编程语言 + PaaS 平台",
  148. ha="center", va="center",
  149. fontsize=s.fs(15), color=C["text_mid"], style="italic")
  150. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  151. # L3 应用层 y=11.3 ~ 13.95 h=2.65 header 0.6
  152. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  153. layer(ax, s, 0.3, 11.3, 19.4, 2.65, 0.6,
  154. "L3 应用层 — 真实案例",
  155. C["L3_bg"], C["L3_edge"], C["L3_header"])
  156. card(ax, s, 0.9, 11.45, 5.9, 1.85,
  157. "qaagent67wiki",
  158. ["海事法规问答", "1326 源文件 / 2989 wiki 页"],
  159. C["L3_card"], C["L3_edge"], title_size=18, body_size=13)
  160. card(ax, s, 7.05, 11.45, 5.9, 1.85,
  161. "agent67v2",
  162. ["多智能体编程助手", "42 工具 / 5 个子智能体"],
  163. C["L3_card"], C["L3_edge"], title_size=18, body_size=13)
  164. card(ax, s, 13.2, 11.45, 5.9, 1.85,
  165. "research67",
  166. ["科研助手 (IDEA → 论文)", "8 个子智能体 / 迭代优化"],
  167. C["L3_card"], C["L3_edge"], title_size=18, body_size=13)
  168. # L3 → L2
  169. for x in (3.85, 10, 16.15):
  170. arrow_down(ax, s, x, 11.25, 10.8)
  171. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  172. # L2 工程实现 y=5.0 ~ 10.75 h=5.75 header 0.55
  173. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  174. layer(ax, s, 0.3, 5.0, 19.4, 5.75, 0.55,
  175. "L2 工程实现 (agentpaas)",
  176. C["L2_bg"], C["L2_edge"], C["L2_header"])
  177. card(ax, s, 0.9, 9.05, 18.2, 1.0,
  178. "REST API (28 端点) + MCP Server (5 工具) + GitHub Action",
  179. [], C["L2_card"], C["L2_edge"], title_size=16)
  180. card(ax, s, 0.9, 7.2, 5.9, 1.7,
  181. "Instance 机制",
  182. ["template / instance", "YAML 深度合并 + workspace"],
  183. C["L2_card"], C["L2_edge"], title_size=15, body_size=12)
  184. card(ax, s, 7.05, 7.2, 5.9, 1.7,
  185. "双引擎执行",
  186. ["Recursive / CEK / Adaptive", "Python 栈 vs CEK Machine"],
  187. C["L2_card"], C["L2_edge"], title_size=15, body_size=12)
  188. card(ax, s, 13.2, 7.2, 5.9, 1.7,
  189. "多租户 + 安全",
  190. ["RBAC + Quota + Rate Limit", "审计日志 + API Key"],
  191. C["L2_card"], C["L2_edge"], title_size=15, body_size=12)
  192. # ── Safety Net ──
  193. safety_x, safety_y, safety_w, safety_h = 0.9, 5.15, 18.2, 1.9
  194. ax.add_patch(FancyBboxPatch(
  195. (safety_x, safety_y), safety_w, safety_h,
  196. boxstyle="round,pad=0.06,rounding_size=0.15",
  197. facecolor=C["L2_safety_bg"], edgecolor=C["L2_safety_edge"],
  198. linewidth=s.lw(2), zorder=3,
  199. ))
  200. safety_header_h = 0.5
  201. ax.add_patch(Rectangle(
  202. (safety_x + 0.1, safety_y + safety_h - safety_header_h - 0.05),
  203. safety_w - 0.2, safety_header_h - 0.05,
  204. facecolor=C["L2_safety_header"], edgecolor="none",
  205. alpha=0.92, zorder=4,
  206. ))
  207. ax.text(
  208. 10, safety_y + safety_h - safety_header_h / 2 - 0.05,
  209. "编译时四道安全网 Static Analysis",
  210. ha="center", va="center",
  211. fontsize=s.fs(15), fontweight="bold",
  212. color=C["text_light"], zorder=5,
  213. )
  214. checks = [
  215. ("Lint\n26 条规则", 2.0),
  216. ("Type Check\nT-Compose", 6.1),
  217. ("Cost Predict\n分级类型", 10.2),
  218. ("Store Indep.\nPair 汇合", 14.3),
  219. ]
  220. check_y, check_h = 5.25, 1.1
  221. for label, cx in checks:
  222. ax.add_patch(FancyBboxPatch(
  223. (cx, check_y), 3.0, check_h,
  224. boxstyle="round,pad=0.04,rounding_size=0.1",
  225. facecolor=C["L2_safety_card"],
  226. edgecolor=C["L2_safety_edge"],
  227. linewidth=s.lw(1.5), zorder=5,
  228. ))
  229. ax.text(
  230. cx + 1.5, check_y + check_h / 2, label,
  231. ha="center", va="center",
  232. fontsize=s.fs(13), fontweight="bold",
  233. color=C["text"], zorder=6, linespacing=1.5,
  234. )
  235. mid_y = check_y + check_h / 2
  236. for i in range(3):
  237. x1 = checks[i][1] + 3.0
  238. x2 = checks[i+1][1]
  239. ax.annotate(
  240. "", xy=(x2, mid_y), xytext=(x1, mid_y),
  241. arrowprops=dict(arrowstyle="->",
  242. color=C["L2_safety_edge"], lw=s.lw(2.5)),
  243. zorder=6,
  244. )
  245. ax.annotate(
  246. "", xy=(18.0, mid_y), xytext=(17.3, mid_y),
  247. arrowprops=dict(arrowstyle="->",
  248. color=C["L2_safety_edge"], lw=s.lw(2.5)),
  249. zorder=6,
  250. )
  251. ax.text(18.05, mid_y, " PASS", ha="left", va="center",
  252. fontsize=s.fs(14), fontweight="bold", color="#2E7D32", zorder=7)
  253. # L2 → L1
  254. for x in (5, 10, 15):
  255. arrow_down(ax, s, x, 4.95, 4.55)
  256. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  257. # L1 理论技术 y=1.5 ~ 4.5 h=3.0 header 0.55
  258. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  259. layer(ax, s, 0.3, 1.5, 19.4, 3.0, 0.55,
  260. "L1 理论技术 (lambdagent / λA 演算)",
  261. C["L1_bg"], C["L1_edge"], C["L1_header"])
  262. card(ax, s, 0.9, 2.55, 5.9, 1.35,
  263. "三层编程模型",
  264. ["Skill → Pattern → Orchestration"],
  265. C["L1_card"], C["L1_edge"], title_size=15, body_size=12)
  266. card(ax, s, 7.05, 2.55, 5.9, 1.35,
  267. "λA 构造子 11 + 5",
  268. ["Lam / Tool / Compose / Loop / Route / Pair / ..."],
  269. C["L1_card"], C["L1_edge"], title_size=15, body_size=12)
  270. card(ax, s, 13.2, 2.55, 5.9, 1.35,
  271. "6 个协作模式",
  272. ["review / fan_out / pipeline / escalation / ..."],
  273. C["L1_card"], C["L1_edge"], title_size=15, body_size=12)
  274. card(ax, s, 0.9, 1.6, 18.2, 0.9,
  275. "形式化理论基础 — 三篇论文",
  276. [],
  277. C["L1_paper"], C["L1_edge"], title_size=15)
  278. ax.text(
  279. 10, 1.78,
  280. "Paper I λA 类型系统 | Paper II 操作语义 + CEK | Paper III 类型与效果 + 分级类型",
  281. ha="center", va="center",
  282. fontsize=s.fs(12), color=C["text_mid"], style="italic", zorder=5,
  283. )
  284. # L1 → L0
  285. for x in (5, 10, 15):
  286. arrow_down(ax, s, x, 1.45, 1.15)
  287. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  288. # L0 基础设施 y=0.1 ~ 1.1 h=1.0 header 0.4
  289. # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  290. layer(ax, s, 0.3, 0.1, 19.4, 1.0, 0.4,
  291. "L0 基础设施",
  292. C["L0_bg"], C["L0_edge"], C["L0_header"])
  293. card(ax, s, 0.9, 0.2, 5.9, 0.48, "LLM Providers",
  294. [], C["L0_card"], C["L0_edge"], title_size=14)
  295. card(ax, s, 7.05, 0.2, 5.9, 0.48, "Storage",
  296. [], C["L0_card"], C["L0_edge"], title_size=14)
  297. card(ax, s, 13.2, 0.2, 5.9, 0.48, "Integration",
  298. [], C["L0_card"], C["L0_edge"], title_size=14)
  299. # 保存
  300. os.makedirs(os.path.dirname(output), exist_ok=True)
  301. plt.savefig(output, dpi=180, bbox_inches="tight",
  302. facecolor="white", edgecolor="none")
  303. plt.close(fig)
  304. print(f"✅ Saved: {output} (scale={scale})")
  305. if __name__ == "__main__":
  306. parser = argparse.ArgumentParser(description="Draw lambdagentpaas architecture diagram")
  307. parser.add_argument("--scale", type=float, default=1.0,
  308. help="全局缩放比例 (1.0=默认, 1.5=放大, 0.8=缩小)")
  309. parser.add_argument("--output", type=str, default=None,
  310. help="输出路径 (默认: docs/assets/lambdagentpaas-architecture.png)")
  311. args = parser.parse_args()
  312. here = os.path.dirname(os.path.abspath(__file__))
  313. output = args.output or os.path.join(here, "lambdagentpaas-architecture.png")
  314. draw(output, scale=args.scale)