Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
1053 lines
49 KiB
Python
1053 lines
49 KiB
Python
"""[DEPRECATED] Old Chapter 9 (multi-Agent) figure generator.
|
||
|
||
⚠️ DO NOT RUN: After the 2026-03-12 refactoring, chapter numbers changed (original Ch 9 → Ch 10).
|
||
The figures generated by this file actually correspond to the current [Chapter 10] content (multi-Agent collaboration), but are still saved as fig9-*.svg.
|
||
Running it would overwrite the current correct [Chapter 9] (multimodal & real-time interaction) figures, causing content misalignment.
|
||
|
||
The current correct Chapter 9 SVGs (multimodal/voice/Computer Use/VLA/Sim2Real) were
|
||
renamed from original fig8-*.svg in commit a33c88f on 2026-03-12; the current correct Chapter 10 SVGs
|
||
(multi-Agent collaboration) were **manually migrated** from the content originally generated by this file to fig10-*.svg.
|
||
|
||
To regenerate figures from this file, you should instead generate fig10-*.svg and reference them according to chapter10.md.
|
||
|
||
Original (now Ch 10) figure list:
|
||
fig10-1: Shared context vs independent context (concrete context windows)
|
||
fig10-2: Phase-based role switching (prompt/tool-set changes per phase)
|
||
fig10-3: Proposer-Reviewer loop (Slidev PPT iterative feedback)
|
||
fig10-4: Manager sequential coordination (sequential sub-agent delegation)
|
||
fig10-5: Book translation Agent architecture (NEW — Exp 10.4)
|
||
fig10-6: Manager parallel coordination (concurrent agents + message bus)
|
||
fig10-7: Phone + Computer dual Agent (NEW — Exp 10.5/10.6)
|
||
fig10-8: Parallel web scraping (NEW — Exp 10.7)
|
||
fig10-9: Handoff chain pattern (peer control passing)
|
||
fig10-10: MetaGPT SOP pipeline (PM→Arch→Eng→QA with artifacts)
|
||
fig10-11: AI town architecture (memory stream + reflection + planning)
|
||
fig10-12: Voice Werewolf Agent system (NEW — Exp 10.9)
|
||
"""
|
||
|
||
import sys
|
||
print(
|
||
"ERROR: gen_ch9_figs.py is DEPRECATED. Running it would overwrite the\n"
|
||
"correct Chapter 9 (multimodal) figures with old Chapter 10 (multi-agent) content.\n"
|
||
"See module docstring at the top of this file for the rename history.",
|
||
file=sys.stderr,
|
||
)
|
||
sys.exit(2)
|
||
import sys, os
|
||
|
||
sys.path.insert(0, os.path.dirname(__file__))
|
||
from svg_lib import (
|
||
SVG, COLORS, FONT, MONO,
|
||
FS_TITLE, FS_BODY, FS_SMALL, FS_TINY, FS_LABEL, STROKE_W, CORNER_R,
|
||
_escape,
|
||
)
|
||
|
||
OUT = os.path.join(os.path.dirname(__file__), 'images')
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-1: Shared context vs independent context
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_1():
|
||
W, H = 780, 560
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 30, 'Shared context vs independent context', size=FS_TITLE, bold=True)
|
||
|
||
col_w = 350
|
||
lx, rx = 20, W - col_w - 20
|
||
|
||
# ── Left: shared context ──
|
||
s.group_box(lx, 55, col_w, 480, 'Shared context (single Agent, multiple phases)')
|
||
|
||
ctx_x, ctx_w = lx + 15, col_w - 30
|
||
phases = [
|
||
('Phase 1: Requirements Analyst', 'medium', [
|
||
'sys: "Your responsibility is to fully understand the requirements..."',
|
||
'tools: [ask_question, save_req]',
|
||
'user: "Write a CSV analysis script"',
|
||
'agent: "What file types need to be processed?"',
|
||
]),
|
||
('Phase 2: Software Engineer', 'light', [
|
||
'sys: "Write code based on confirmed requirements..."',
|
||
'tools: [write_file, execute_code]',
|
||
'agent: write_file("analyze.py", ...)',
|
||
'agent: execute_code("python test.py")',
|
||
]),
|
||
('Phase 3: Code Reviewer', 'light', [
|
||
'sys: "Review code quality and security..."',
|
||
'tools: [run_linter, run_tests]',
|
||
'agent: run_linter → 2 warnings',
|
||
'agent: approve_code()',
|
||
]),
|
||
]
|
||
|
||
cy = 82
|
||
for title, fill, lines in phases:
|
||
ph = 18 + len(lines) * 18 + 10
|
||
s.rect(ctx_x, cy, ctx_w, ph, fill=fill, rx=4)
|
||
s.text(ctx_x + 8, cy + 14, title, size=FS_SMALL, bold=True, anchor='start')
|
||
for i, ln in enumerate(lines):
|
||
s.mono(ctx_x + 12, cy + 32 + i * 18, ln, size=12)
|
||
cy += ph + 2
|
||
|
||
s.rect(ctx_x, cy, ctx_w, 28, fill='code_bg', rx=3)
|
||
s.text(ctx_x + ctx_w // 2, cy + 14, '↑ All phases share the same conversation history', size=FS_TINY, bold=True)
|
||
cy += 36
|
||
|
||
s.text(lx + col_w // 2, cy + 10, '✓ Complete execution trace', size=FS_SMALL, fill='text_light')
|
||
s.text(lx + col_w // 2, cy + 32, '✗ Context expands rapidly', size=FS_SMALL, fill='text_light')
|
||
|
||
# ── Right: independent context ──
|
||
s.group_box(rx, 55, col_w, 480, 'Independent context (true multi-Agent)')
|
||
|
||
agents_data = [
|
||
('Glossary Agent', [
|
||
'sys: "Identify terms and translate..."',
|
||
'tools: [search_dict, write_file]',
|
||
'→ glossary.json',
|
||
]),
|
||
('Translation Agent', [
|
||
'sys: "Translate this chapter..."',
|
||
'tools: [read_file, write_file]',
|
||
'→ chapter3_zh.md',
|
||
]),
|
||
('Proofreading Agent', [
|
||
'sys: "Check terminology consistency..."',
|
||
'tools: [read_file, write_file]',
|
||
'→ review_report.md',
|
||
]),
|
||
]
|
||
|
||
ay = 82
|
||
for name, lines in agents_data:
|
||
ah = 18 + len(lines) * 18 + 8
|
||
s.rect(rx + 15, ay, ctx_w, ah, fill='light', rx=4)
|
||
s.text(rx + 23, ay + 14, name, size=FS_SMALL, bold=True, anchor='start')
|
||
for i, ln in enumerate(lines):
|
||
s.mono(rx + 27, ay + 32 + i * 18, ln, size=12)
|
||
ay += ah + 8
|
||
|
||
fs_y = ay + 5
|
||
s.rect(rx + 15, fs_y, ctx_w, 65, fill='medium', rx=4)
|
||
s.text(rx + 15 + ctx_w // 2, fs_y + 16, 'Shared file system', size=FS_SMALL, bold=True)
|
||
files = ['glossary.json', 'chapter3_zh.md', 'review_report.md']
|
||
s.mono(rx + 27, fs_y + 38, ' '.join(files), size=11)
|
||
s.text(rx + 15 + ctx_w // 2, fs_y + 55, '+ Tool call parameters pass structured data', size=FS_TINY, fill='text_light')
|
||
|
||
s.text(rx + col_w // 2, fs_y + 82, '✓ Modular · Extensible · Parallel', size=FS_SMALL, fill='text_light')
|
||
s.text(rx + col_w // 2, fs_y + 104, '✗ Information synchronization complex', size=FS_SMALL, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-1.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-2: Phase-based role switching
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_2():
|
||
W, H = 780, 520
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Phase-based role switching: Coding Agent three phases', size=FS_TITLE, bold=True)
|
||
|
||
phases = [
|
||
('Requirements Analyst', 'medium',
|
||
'"Your responsibility is to fully understand the requirements.\nDo not rush to implement; at this stage\nyour task is to ask questions and confirm."',
|
||
['ask_clarifying_question(q)', 'save_requirement(k, v)', 'complete_requirements_analysis()'],
|
||
'complete_requirements_analysis()'),
|
||
('Software Engineer', 'light',
|
||
'"Write high-quality Python code based on the confirmed requirements.\nFollow modular design and error handling best practices."',
|
||
['write_file(path, content)', 'read_file(path)', 'execute_code(code)'],
|
||
'submit_for_review()'),
|
||
('Code Reviewer', '#e8e8e8',
|
||
'"Evaluate code quality from multiple dimensions: \nfunctional correctness, coding standards, \nand security. Adopt critical thinking."',
|
||
['run_linter(file)', 'run_tests(file)', 'analyze_complexity(file)'],
|
||
None),
|
||
]
|
||
|
||
s.rect(30, 55, W - 60, 28, fill='code_bg', rx=3)
|
||
s.text(W // 2, 69, '▼ Continuous flow within the same context — conversation history fully preserved across stages ▼', size=FS_SMALL, bold=True)
|
||
|
||
pw = 225
|
||
gap = 18
|
||
px_start = (W - 3 * pw - 2 * gap) // 2
|
||
py = 100
|
||
|
||
for i, (role, fill, prompt, tools, trigger) in enumerate(phases):
|
||
x = px_start + i * (pw + gap)
|
||
|
||
s.rect(x, py, pw, 380, fill=fill, rx=6)
|
||
s.text(x + pw // 2, py + 22, f'Stage {i + 1}', size=FS_TINY, fill='text_light')
|
||
s.text(x + pw // 2, py + 42, role, size=FS_BODY, bold=True)
|
||
|
||
s.rect(x + 8, py + 60, pw - 16, 88, fill='code_bg', rx=3)
|
||
s.text(x + 14, py + 75, 'System Prompt', size=FS_TINY, fill='text_light', anchor='start')
|
||
for j, ln in enumerate(prompt.split('\n')):
|
||
s.text(x + 14, py + 92 + j * 16, ln, size=12, anchor='start', fill='text_light')
|
||
|
||
s.rect(x + 8, py + 158, pw - 16, 18 + len(tools) * 20, fill='white', rx=3)
|
||
s.text(x + 14, py + 172, 'Tool Set', size=FS_TINY, fill='text_light', anchor='start')
|
||
for j, tool in enumerate(tools):
|
||
s.mono(x + 14, py + 190 + j * 20, tool, size=11)
|
||
|
||
if trigger:
|
||
ty = py + 290
|
||
s.rect(x + 8, ty, pw - 16, 48, fill='dark', rx=12)
|
||
s.text(x + pw // 2, ty + 16, 'Trigger Transition', size=FS_TINY, fill='white')
|
||
s.mono(x + pw // 2, ty + 34, trigger, size=10, anchor='middle', fill='white')
|
||
|
||
if i < 2:
|
||
ax1 = x + pw + 2
|
||
ax2 = x + pw + gap - 2
|
||
ay = py + 310
|
||
s.arrow(ax1, ay, ax2, ay)
|
||
|
||
s.text(W // 2, H - 10, 'Role Transition: Update system prompt + tool set, conversation history and state continuously preserved',
|
||
size=FS_SMALL, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-2.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-3: Proposer-Reviewer Loop (Slidev PPT Generation)
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_3():
|
||
W, H = 780, 520
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Proposer-Reviewer Loop: Slidev PPT Generation', size=FS_TITLE, bold=True)
|
||
|
||
# Editor (Proposer)
|
||
ex, ey, ew, eh = 30, 65, 300, 200
|
||
s.rect(ex, ey, ew, eh, fill='light')
|
||
s.text(ex + ew // 2, ey + 22, 'Proposer Agent', size=FS_BODY, bold=True)
|
||
s.text(ex + 12, ey + 48, 'Input: Extended paper abstract (2000 characters)', size=FS_TINY, anchor='start', fill='text_light')
|
||
editor_lines = [
|
||
'---',
|
||
'theme: academic',
|
||
'---',
|
||
'# Transformer Attention Mechanism',
|
||
'',
|
||
'## Core Idea',
|
||
'- Self-attention computes Q·K^T/√d',
|
||
'- Multi-head attention processes in parallel',
|
||
]
|
||
ch = s.code_block(ex + 10, ey + 62, ew - 20, editor_lines, font_size=11, line_h=14)
|
||
s.text(ex + ew // 2, ey + eh - 10, 'Understand content structure → Decompose into slides', size=FS_TINY, fill='text_light')
|
||
|
||
# Critic (Reviewer)
|
||
cx, cy, cw, ch_h = 450, 65, 300, 200
|
||
s.rect(cx, cy, cw, ch_h, fill='medium')
|
||
s.text(cx + cw // 2, cy + 22, 'Reviewer Agent', size=FS_BODY, bold=True)
|
||
|
||
s.rect(cx + 10, cy + 42, cw - 20, 38, fill='code_bg', rx=3)
|
||
s.text(cx + 18, cy + 55, '① Slidev rendering → PDF/PNG', size=FS_TINY, anchor='start')
|
||
s.text(cx + 18, cy + 70, '② Vision LLM multi-dimensional evaluation', size=FS_TINY, anchor='start')
|
||
|
||
feedback_items = [
|
||
'Page Issue Type Severity',
|
||
'P3 Content too dense High',
|
||
'P7 Font too small Medium',
|
||
'P11 Color mismatch Low',
|
||
]
|
||
s.rect(cx + 10, cy + 86, cw - 20, 75, fill='code_bg', rx=3)
|
||
s.text(cx + 18, cy + 100, 'Structured Feedback:', size=FS_TINY, anchor='start', bold=True)
|
||
for i, fb in enumerate(feedback_items):
|
||
s.mono(cx + 18, cy + 118 + i * 15, fb, size=11)
|
||
s.text(cx + cw // 2, cy + ch_h - 10, 'Rendering + visual analysis → actionable improvement suggestions', size=FS_TINY, fill='text_light')
|
||
|
||
# Arrows between Editor and Critic
|
||
mid_y1 = ey + 70
|
||
mid_y2 = ey + eh - 50
|
||
s.arrow(ex + ew + 2, mid_y1, cx - 2, mid_y1)
|
||
s.text((ex + ew + cx) / 2, mid_y1 - 12, 'Slidev Code', size=FS_SMALL, bold=True)
|
||
|
||
s.arrow(cx - 2, mid_y2, ex + ew + 2, mid_y2)
|
||
s.text((ex + ew + cx) / 2, mid_y2 + 16, 'Structured Feedback', size=FS_SMALL, bold=True)
|
||
|
||
# Iteration timeline
|
||
iy = 290
|
||
s.rect(30, iy, W - 60, 100, fill='code_bg', rx=4)
|
||
s.text(W // 2, iy + 18, 'Iterative Improvement Process', size=FS_BODY, bold=True)
|
||
|
||
rounds = [
|
||
('Round 1', '12-page draft\n5 issues', 'light'),
|
||
('Round 2', '14 pages (split dense pages)\n2 issues', 'light'),
|
||
('Round 3', '14 pages (font corrected)\n0 issues ✓', 'medium'),
|
||
]
|
||
rw = 190
|
||
rx_start = (W - 3 * rw - 2 * 30) // 2
|
||
for i, (name, desc, fill) in enumerate(rounds):
|
||
rx = rx_start + i * (rw + 30)
|
||
ry = iy + 35
|
||
s.rect(rx, ry, rw, 52, fill=fill, rx=3)
|
||
s.text(rx + 10, ry + 16, name, size=FS_SMALL, bold=True, anchor='start')
|
||
for j, ln in enumerate(desc.split('\n')):
|
||
s.text(rx + 10, ry + 34 + j * 16, ln, size=FS_TINY, anchor='start', fill='text_light')
|
||
if i < 2:
|
||
s.arrow(rx + rw + 4, ry + 26, rx + rw + 26, ry + 26, color='dark')
|
||
|
||
# Why not single agent
|
||
wy = 405
|
||
s.rect(30, wy, W - 60, 90, fill='light', rx=4)
|
||
s.text(W // 2, wy + 20, 'Why not use a single agent?', size=FS_BODY, bold=True)
|
||
|
||
single_x = 60
|
||
dual_x = W // 2 + 20
|
||
s.text(single_x, wy + 45, 'Single Agent: Renderings × N rounds → context explosion', size=FS_TINY, anchor='start', fill='text_light')
|
||
s.text(single_x, wy + 63, '(1080p screenshot = thousands of tokens × 14 pages × 5 rounds)', size=FS_TINY, anchor='start', fill='text_light')
|
||
s.text(dual_x, wy + 45, 'Dual Agent: Critic only sees current version', size=FS_TINY, anchor='start')
|
||
s.text(dual_x, wy + 63, 'Editor only accumulates text feedback → clean context', size=FS_TINY, anchor='start')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-3.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-4: Manager sequential coordination
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_4():
|
||
W, H = 780, 480
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Manager sequential coordination: Sub-Agent as tool', size=FS_TITLE, bold=True)
|
||
|
||
# Manager
|
||
mx, my, mw, mh = 240, 60, 300, 100
|
||
s.rect(mx, my, mw, mh, fill='medium')
|
||
s.text(mx + mw // 2, my + 22, 'Manager Agent', size=FS_BODY, bold=True)
|
||
s.text(mx + mw // 2, my + 46, 'Task understanding → decomposition → scheduling → synthesis', size=FS_TINY, fill='text_light')
|
||
s.text(mx + mw // 2, my + 66, 'Tool set: [call_agent_A, call_agent_B,', size=FS_TINY, fill='text_light')
|
||
s.text(mx + mw // 2, my + 82, 'call_agent_C, search, write_file]', size=FS_TINY, fill='text_light')
|
||
|
||
# Sub-agents in sequence
|
||
agents = [
|
||
('Sub-Agent A', 'Data collection', 'Search technical documentation\nExtract key information', 'light'),
|
||
('Sub-Agent B', 'Analysis and processing', 'Compare and analyze data\nGenerate statistical report', 'light'),
|
||
('Sub-Agent C', 'Report generation', 'Write final report\nFormat output', 'light'),
|
||
]
|
||
aw = 210
|
||
ax_start = (W - 3 * aw - 2 * 25) // 2
|
||
ay = 240
|
||
|
||
for i, (name, role, desc, fill) in enumerate(agents):
|
||
x = ax_start + i * (aw + 25)
|
||
s.rect(x, ay, aw, 120, fill=fill, rx=6)
|
||
s.text(x + aw // 2, ay + 20, name, size=FS_SMALL, bold=True)
|
||
s.text(x + aw // 2, ay + 40, f'Roles: {role}', size=FS_TINY, fill='text_light')
|
||
for j, ln in enumerate(desc.split('\n')):
|
||
s.text(x + aw // 2, ay + 62 + j * 18, ln, size=FS_TINY, fill='text_light')
|
||
|
||
badge_labels = [f'Step {i + 1}']
|
||
s.badge(x + aw - 55, ay + 95, 50, 20, badge_labels[0], fill='dark', font_size=FS_TINY)
|
||
|
||
# Arrow from Manager to sub-agent
|
||
s.arrow(mx + mw // 2 - 100 + i * 100, my + mh + 2,
|
||
x + aw // 2, ay - 2, color='dark')
|
||
|
||
# Sequential arrow between sub-agents
|
||
if i < 2:
|
||
s.arrow(x + aw + 2, ay + 60, x + aw + 23, ay + 60)
|
||
|
||
# Data flow
|
||
dy = 380
|
||
s.rect(30, dy, W - 60, 80, fill='code_bg', rx=4)
|
||
s.text(W // 2, dy + 18, 'Sequential execution flow', size=FS_BODY, bold=True)
|
||
|
||
flow_items = [
|
||
'Manager calls Agent A',
|
||
'→ A returns data',
|
||
'→ Manager passes to B',
|
||
'→ B returns analysis',
|
||
'→ Manager passes to C',
|
||
'→ C returns report',
|
||
]
|
||
fx_start = 55
|
||
for i, item in enumerate(flow_items):
|
||
s.text(fx_start + i * 118, dy + 42, item, size=FS_TINY, anchor='start',
|
||
fill='text' if 'Call' in item or 'Return' in item else 'text_light')
|
||
|
||
s.text(W // 2, dy + 65, 'Manager perspective: calling Agent = calling tool (send request → receive response)',
|
||
size=FS_SMALL, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-4.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-5: Book translation Agent architecture (Exp 9.4)
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_5():
|
||
W, H = 780, 540
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Experiment 9.4: Book translation Agent — Manager mode', size=FS_TITLE, bold=True)
|
||
|
||
# Manager at top
|
||
mx, my, mw, mh = 240, 55, 300, 70
|
||
s.rect(mx, my, mw, mh, fill='medium')
|
||
s.text(mx + mw // 2, my + 22, 'Manager Agent', size=FS_BODY, bold=True)
|
||
s.text(mx + mw // 2, my + 48, 'Task planning · Progress monitoring · Exception handling · Result synthesis', size=FS_TINY, fill='text_light')
|
||
|
||
# Three sub-agents
|
||
sub_agents = [
|
||
(30, 'Glossary Agent', 'Glossary of terms',
|
||
['Receive entire book → Identify specialized terms', 'Search specialized dictionaries + translation conventions', 'Output: glossary.json'],
|
||
['{"attention": "attention",', ' "transformer": "Transformer",', ' "backprop": "backpropagation"}']),
|
||
(270, 'Translation Agent ×N', 'Chapter translation',
|
||
['Input: chapter + glossary + guide', 'Strictly translate terms according to the glossary', 'Output: chapter{n}_zh.md'],
|
||
['"...attention mechanism computes the similarity of', ' Query·Key^T ..."']),
|
||
(520, 'Proofreading Agent', 'Full-text review',
|
||
['Scan and verify term consistency', 'Check fluency and readability', 'Output: review_report.md'],
|
||
['P3: "attention"→"focus" inconsistency', 'P8: Long sentence suggested to split']),
|
||
]
|
||
|
||
aw = 230
|
||
ay = 170
|
||
|
||
for x, name, role, desc, output in sub_agents:
|
||
s.rect(x, ay, aw, 185, fill='light', rx=6)
|
||
s.text(x + aw // 2, ay + 20, name, size=FS_SMALL, bold=True)
|
||
s.text(x + aw // 2, ay + 38, role, size=FS_TINY, fill='text_light')
|
||
|
||
for i, ln in enumerate(desc):
|
||
s.text(x + 12, ay + 60 + i * 18, ln, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
s.rect(x + 8, ay + 115, aw - 16, 10 + len(output) * 15, fill='code_bg', rx=3)
|
||
for i, ln in enumerate(output):
|
||
s.mono(x + 14, ay + 128 + i * 15, ln, size=10)
|
||
|
||
# Arrow from Manager
|
||
s.arrow(mx + mw // 2, my + mh + 2, x + aw // 2, ay - 2, color='dark')
|
||
|
||
# Sequential arrows between sub-agents
|
||
s.arrow(30 + aw + 4, ay + 90, 270 - 4, ay + 90, label='Glossary')
|
||
s.arrow(270 + aw + 4, ay + 90, 520 - 4, ay + 90, label='Translation')
|
||
|
||
# Shared file system
|
||
fy = 375
|
||
s.rect(30, fy, W - 60, 70, fill='medium', rx=6)
|
||
s.text(W // 2, fy + 18, 'Shared file system', size=FS_BODY, bold=True)
|
||
files = [
|
||
('glossary.json', 'Glossary of terms'),
|
||
('chapter{1..10}_zh.md', 'Chapter translation'),
|
||
('review_report.md', 'Review report'),
|
||
('translation_guide.md', 'Translation guide'),
|
||
]
|
||
fw = (W - 80) // len(files)
|
||
for i, (fname, desc) in enumerate(files):
|
||
cx = 50 + i * fw + fw // 2
|
||
s.mono(cx, fy + 40, fname, size=11, anchor='middle')
|
||
s.text(cx, fy + 58, desc, size=FS_TINY, fill='text_light')
|
||
|
||
# Key insight
|
||
ky = 460
|
||
s.rect(30, ky, W - 60, 60, fill='code_bg', rx=4)
|
||
s.text(W // 2, ky + 18, 'Context isolation advantages', size=FS_BODY, bold=True)
|
||
s.text(W // 2, ky + 42,
|
||
'Glossary: only view terms | Translation: only view current chapter + glossary | Manager: only maintain file index',
|
||
size=FS_TINY, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-5.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-6: Manager parallel coordination
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_6():
|
||
W, H = 780, 500
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Manager parallel coordination: message bus architecture', size=FS_TITLE, bold=True)
|
||
|
||
# Orchestration Agent
|
||
ox, oy, ow, oh = 240, 55, 300, 70
|
||
s.rect(ox, oy, ow, oh, fill='medium')
|
||
s.text(ox + ow // 2, oy + 22, 'Orchestration Agent', size=FS_BODY, bold=True)
|
||
s.text(ox + ow // 2, oy + 48, 'Parallel scheduling · Real-time monitoring · Result aggregation', size=FS_TINY, fill='text_light')
|
||
|
||
# Message bus
|
||
bus_y = 155
|
||
s.rect(50, bus_y, W - 100, 36, fill='dark', rx=4)
|
||
s.text(W // 2, bus_y + 18, 'Message Bus', size=FS_SMALL, fill='white', bold=True)
|
||
|
||
s.arrow(ox + ow // 2, oy + oh + 2, ox + ow // 2, bus_y - 2)
|
||
|
||
# Parallel agents
|
||
agents = [
|
||
('Agent 1', 'Data collection', 'Running ◎', 'light'),
|
||
('Agent 2', 'Content analysis', 'Running ◎', 'light'),
|
||
('Agent 3', 'Chart Generation', 'Completed ✓', 'medium'),
|
||
('Agent 4', 'Format Validation', 'Waiting ○', 'code_bg'),
|
||
]
|
||
aw = 160
|
||
gap = 14
|
||
total = len(agents) * aw + (len(agents) - 1) * gap
|
||
ax_start = (W - total) // 2
|
||
ay = 225
|
||
|
||
for i, (name, role, status, fill) in enumerate(agents):
|
||
x = ax_start + i * (aw + gap)
|
||
s.rect(x, ay, aw, 100, fill=fill, rx=6)
|
||
s.text(x + aw // 2, ay + 20, name, size=FS_SMALL, bold=True)
|
||
s.text(x + aw // 2, ay + 40, role, size=FS_TINY, fill='text_light')
|
||
s.text(x + aw // 2, ay + 65, status, size=FS_TINY,
|
||
fill='text_light' if 'Waiting' in status else 'text')
|
||
s.text(x + aw // 2, ay + 82, 'Independent Context', size=FS_TINY, fill='text_light')
|
||
|
||
s.arrow(x + aw // 2, bus_y + 38, x + aw // 2, ay - 2, color='dark')
|
||
|
||
# Message examples
|
||
my = 350
|
||
s.rect(30, my, W - 60, 125, fill='code_bg', rx=4)
|
||
s.text(W // 2, my + 18, 'Message Bus Communication Example', size=FS_BODY, bold=True)
|
||
|
||
messages = [
|
||
('Orch → Agent 1', '{"type":"start","task":"Collect arxiv papers","params":{"query":"LLM agent"}}'),
|
||
('Agent 3 → Orch', '{"type":"completed","agent_id":"3","result":"charts/fig1.svg generated"}'),
|
||
('Agent 1 → Agent 2', '{"type":"data_ready","source":"agent_1","file":"raw_data.json"}'),
|
||
('Orch → Agent 4', '{"type":"start","depends_on":["agent_2","agent_3"]}'),
|
||
]
|
||
for i, (sender, msg) in enumerate(messages):
|
||
y = my + 40 + i * 22
|
||
s.text(40, y, sender, size=FS_TINY, bold=True, anchor='start')
|
||
s.mono(200, y, msg, size=10, anchor='start')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-6.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-7: Phone + Computer Dual Agent (Exp 9.5/9.6)
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_7():
|
||
W, H = 780, 560
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Experiment 9.5/9.6: Phone + Computer Dual Agent', size=FS_TITLE, bold=True)
|
||
|
||
# Phone Agent (left)
|
||
px, py, pw, ph = 30, 65, 310, 240
|
||
s.rect(px, py, pw, ph, fill='light', rx=6)
|
||
s.text(px + pw // 2, py + 22, 'Phone Agent', size=FS_BODY, bold=True)
|
||
s.text(px + pw // 2, py + 42, 'Node.js · Real-time Voice Call', size=FS_TINY, fill='text_light')
|
||
|
||
phone_pipeline = [
|
||
('User Voice', 'Microphone Input', 'medium'),
|
||
('VAD + ASR', 'Silero VAD → STT Transcription', 'light'),
|
||
('LLM Inference', 'Understand Intent + Extract Information', 'light'),
|
||
('TTS Synthesis', 'Generate Voice Reply → Playback', 'medium'),
|
||
]
|
||
for i, (label, desc, fill) in enumerate(phone_pipeline):
|
||
y = py + 60 + i * 42
|
||
s.rect(px + 10, y, pw - 20, 36, fill=fill, rx=3)
|
||
s.text(px + 20, y + 14, label, size=FS_TINY, bold=True, anchor='start')
|
||
s.text(px + 20, y + 28, desc, size=11, anchor='start', fill='text_light')
|
||
if i < len(phone_pipeline) - 1:
|
||
s.arrow(px + pw // 2, y + 38, px + pw // 2, y + 42, color='dark')
|
||
|
||
# Computer Agent (right)
|
||
cx, cy, cw, ch_h = 440, 65, 310, 240
|
||
s.rect(cx, cy, cw, ch_h, fill='light', rx=6)
|
||
s.text(cx + cw // 2, cy + 22, 'Computer Agent', size=FS_BODY, bold=True)
|
||
s.text(cx + cw // 2, cy + 42, 'Python · Browser Automation', size=FS_TINY, fill='text_light')
|
||
|
||
comp_pipeline = [
|
||
('Screenshot', 'Current Browser Page', 'medium'),
|
||
('Vision LLM', 'Understand Page Structure + Form Fields', 'light'),
|
||
('Action Planning', 'Locate Fields → Plan Input Sequence', 'light'),
|
||
('Execute Actions', 'Click / Input / Submit', 'medium'),
|
||
]
|
||
for i, (label, desc, fill) in enumerate(comp_pipeline):
|
||
y = cy + 60 + i * 42
|
||
s.rect(cx + 10, y, cw - 20, 36, fill=fill, rx=3)
|
||
s.text(cx + 20, y + 14, label, size=FS_TINY, bold=True, anchor='start')
|
||
s.text(cx + 20, y + 28, desc, size=11, anchor='start', fill='text_light')
|
||
if i < len(comp_pipeline) - 1:
|
||
s.arrow(cx + cw // 2, y + 38, cx + cw // 2, y + 42, color='dark')
|
||
|
||
# WebSocket connection between agents
|
||
ws_y = py + ph + 15
|
||
s.rect(30, ws_y, W - 60, 36, fill='dark', rx=4)
|
||
s.text(W // 2, ws_y + 18, 'WebSocket Bidirectional Communication (ws://localhost:8849)', size=FS_SMALL, fill='white', bold=True)
|
||
|
||
s.arrow(px + pw // 2, py + ph + 2, px + pw // 2, ws_y - 2, color='dark')
|
||
s.arrow(cx + cw // 2, cy + ch_h + 2, cx + cw // 2, ws_y - 2, color='dark')
|
||
|
||
# Message examples
|
||
my = ws_y + 50
|
||
s.rect(30, my, W - 60, 150, fill='code_bg', rx=4)
|
||
s.text(W // 2, my + 18, 'Real-time Bidirectional Message Stream (Use phone and computer simultaneously)', size=FS_BODY, bold=True)
|
||
|
||
msgs = [
|
||
('Phone → Computer', '[FROM_PHONE_AGENT] User says name is Zhang San', '→'),
|
||
('Computer → Phone', '[FROM_COMPUTER_AGENT] Name filled in, ID number required', '←'),
|
||
('Phone → Computer', '[FROM_PHONE_AGENT] ID number 310101199001011234', '→'),
|
||
('Computer → Phone', '[FROM_COMPUTER_AGENT] Form submitted, registration successful', '←'),
|
||
]
|
||
for i, (sender, content, direction) in enumerate(msgs):
|
||
y = my + 42 + i * 26
|
||
s.text(42, y, sender, size=FS_TINY, bold=True, anchor='start',
|
||
fill='text' if '→' == direction else 'text_light')
|
||
s.mono(210, y, content, size=10, anchor='start')
|
||
|
||
# Key point
|
||
s.text(W // 2, my + 140,
|
||
'Key: Two agents run independent ReAct loops in parallel without blocking each other',
|
||
size=FS_SMALL, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-7.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-8: Parallel Web Scraping Agent (Exp 9.7)
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_8():
|
||
W, H = 780, 530
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Experiment 9.7: Parallel Web Scraping — Cascade Termination', size=FS_TITLE, bold=True)
|
||
|
||
# Orchestration Agent
|
||
ox, oy, ow, oh = 230, 55, 320, 65
|
||
s.rect(ox, oy, ow, oh, fill='medium')
|
||
s.text(ox + ow // 2, oy + 20, 'Orchestration Agent', size=FS_BODY, bold=True)
|
||
s.text(ox + ow // 2, oy + 44, 'Dynamic creation · Real-time monitoring · Cascade termination', size=FS_TINY, fill='text_light')
|
||
|
||
# Parallel Computer Use Agents
|
||
agents = [
|
||
('Agent 1', 'cs.edu.cn', 'Searching... ◎', 'light'),
|
||
('Agent 2', 'math.edu.cn', 'Not found ✗', '#e8e8e8'),
|
||
('Agent 3', 'phys.edu.cn', 'Found! ✓', 'medium'),
|
||
('Agent 4', 'chem.edu.cn', 'Terminated ⊘', 'code_bg'),
|
||
('Agent 5', 'bio.edu.cn', 'Terminated ⊘', 'code_bg'),
|
||
]
|
||
aw = 130
|
||
gap = 12
|
||
total_w = len(agents) * aw + (len(agents) - 1) * gap
|
||
ax_start = (W - total_w) // 2
|
||
ay = 160
|
||
|
||
for i, (name, url, status, fill) in enumerate(agents):
|
||
x = ax_start + i * (aw + gap)
|
||
s.rect(x, ay, aw, 95, fill=fill, rx=4)
|
||
s.text(x + aw // 2, ay + 16, name, size=FS_SMALL, bold=True)
|
||
s.mono(x + aw // 2, ay + 35, url, size=10, anchor='middle')
|
||
s.text(x + aw // 2, ay + 55, 'Faculty Directory Search', size=FS_TINY, fill='text_light')
|
||
s.text(x + aw // 2, ay + 75, status, size=FS_TINY,
|
||
bold=('Found' in status), fill='text' if 'Found' in status else 'text_light')
|
||
|
||
s.arrow(ox + ow // 2, oy + oh + 2, x + aw // 2, ay - 2, color='dark')
|
||
|
||
# Cascade termination flow
|
||
ty = 280
|
||
s.rect(30, ty, W - 60, 120, fill='code_bg', rx=4)
|
||
s.text(W // 2, ty + 18, 'Cascade Termination Timeline', size=FS_BODY, bold=True)
|
||
|
||
timeline = [
|
||
('t=0s', 'Start 5 agents\nparallel search for teacher "Zhang Wei"'),
|
||
('t=12s', 'Agent 2 completed\nNot found → Normal exit'),
|
||
('t=18s', 'Agent 3 found target!\nSend target_found'),
|
||
('t=18.1s', 'Orch broadcasts terminate\nto Agents 1,4,5'),
|
||
('t=19s', 'All confirm termination\nAggregate results and return'),
|
||
]
|
||
tw = 130
|
||
tx_start = (W - len(timeline) * tw) // 2
|
||
for i, (time, desc) in enumerate(timeline):
|
||
x = tx_start + i * tw
|
||
s.text(x + tw // 2, ty + 42, time, size=FS_SMALL, bold=True)
|
||
for j, ln in enumerate(desc.split('\n')):
|
||
s.text(x + tw // 2, ty + 60 + j * 16, ln, size=FS_TINY, fill='text_light')
|
||
if i < len(timeline) - 1:
|
||
s.arrow(x + tw - 2, ty + 55, x + tw + 4, ty + 55, color='dark')
|
||
|
||
# Result and comparison
|
||
ry = 420
|
||
s.rect(30, ry, 340, 85, fill='light', rx=4)
|
||
s.text(200, ry + 18, 'Result found', size=FS_BODY, bold=True)
|
||
result_lines = [
|
||
'Name: Zhang Wei School: School of Physics',
|
||
'Position: Professor Field: Quantum Computing',
|
||
'Email: zhangwei@phys.edu.cn',
|
||
]
|
||
for i, ln in enumerate(result_lines):
|
||
s.mono(50, ry + 40 + i * 16, ln, size=11)
|
||
|
||
s.rect(400, ry, 350, 85, fill='medium', rx=4)
|
||
s.text(575, ry + 18, 'Performance Comparison', size=FS_BODY, bold=True)
|
||
s.text(420, ry + 42, 'Serial: 10 websites × 30s = ~5 minutes', size=FS_TINY, anchor='start', fill='text_light')
|
||
s.text(420, ry + 60, 'Parallel: 18s to find + 1s to terminate = 19s', size=FS_TINY, anchor='start', bold=True)
|
||
s.text(420, ry + 78, 'Speedup: ~15× (with cascade termination optimization)', size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-8.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-9: Handoff Chain Pattern
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_9():
|
||
W, H = 780, 440
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Handoff Chain Pattern: Peer-to-peer handoff + Contract-based collaboration', size=FS_TITLE, bold=True)
|
||
|
||
nodes = [
|
||
('Agent A', 'Requirements Analysis', 'Output: structured requirements document \nspec.json', 'medium'),
|
||
('Agent B', 'Architecture Design', 'Output: technical design document \ndesign.md', 'light'),
|
||
('Agent C', 'Code Implementation', 'Output: source code \nsrc/*.py', 'light'),
|
||
('Agent D', 'Test Verification', 'Output: test report \ntest_report.md', 'medium'),
|
||
]
|
||
|
||
nw, nh = 160, 130
|
||
gap = 22
|
||
total_w = len(nodes) * nw + (len(nodes) - 1) * gap
|
||
nx_start = (W - total_w) // 2
|
||
ny = 60
|
||
|
||
for i, (name, role, output, fill) in enumerate(nodes):
|
||
x = nx_start + i * (nw + gap)
|
||
s.rect(x, ny, nw, nh, fill=fill, rx=6)
|
||
s.text(x + nw // 2, ny + 20, name, size=FS_SMALL, bold=True)
|
||
s.text(x + nw // 2, ny + 40, role, size=FS_TINY, fill='text_light')
|
||
|
||
s.rect(x + 8, ny + 55, nw - 16, 50, fill='code_bg', rx=3)
|
||
for j, ln in enumerate(output.split('\n')):
|
||
s.text(x + nw // 2, ny + 72 + j * 16, ln, size=FS_TINY, fill='text_light')
|
||
|
||
s.text(x + nw // 2, ny + nh - 8, 'Handoff after completion →', size=FS_TINY, fill='text_light')
|
||
|
||
if i < len(nodes) - 1:
|
||
s.arrow(x + nw + 4, ny + nh // 2, x + nw + gap - 4, ny + nh // 2)
|
||
|
||
# Handoff data detail
|
||
hy = 215
|
||
s.rect(30, hy, W - 60, 90, fill='code_bg', rx=4)
|
||
s.text(W // 2, hy + 18, 'Handoff Content (Agent A → Agent B Example)', size=FS_BODY, bold=True)
|
||
|
||
handoff_fields = [
|
||
('Trigger Condition', 'A completes requirements document → is_complete=True'),
|
||
('Target Agent', 'target="architect" (Agent B)'),
|
||
('Handoff Content', 'files=["spec.json"] + summary="E-commerce system: 3 microservices, REST API"'),
|
||
('Post-handoff Status', 'status="exit" (release resources, do not remain on standby)'),
|
||
]
|
||
for i, (field, value) in enumerate(handoff_fields):
|
||
y = hy + 38 + i * 16
|
||
s.text(42, y, field + ':', size=FS_TINY, bold=True, anchor='start')
|
||
s.text(150, y, value, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
# Comparison with Manager mode
|
||
cy = 320
|
||
s.rect(30, cy, 340, 100, fill='light', rx=4)
|
||
s.text(200, cy + 18, 'Decentralization Advantages', size=FS_SMALL, bold=True)
|
||
advantages = [
|
||
'✓ No central Manager needed to understand all roles',
|
||
'✓ Clear responsibility boundaries, interface decoupling',
|
||
'✓ Engineer can have multiple parallel instances',
|
||
]
|
||
for i, adv in enumerate(advantages):
|
||
s.text(48, cy + 42 + i * 20, adv, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
s.rect(400, cy, 350, 100, fill='light', rx=4)
|
||
s.text(575, cy + 18, 'Decentralization Limitations', size=FS_SMALL, bold=True)
|
||
limits = [
|
||
'✗ Lack of global optimization perspective',
|
||
'✗ Difficult exception handling (no central coordination)',
|
||
'✗ Fixed process, hard to adjust dynamically',
|
||
]
|
||
for i, lim in enumerate(limits):
|
||
s.text(418, cy + 42 + i * 20, lim, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-9.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# fig9-10: MetaGPT SOP Pipeline
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_10():
|
||
W, H = 780, 530
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'MetaGPT SOP Pipeline: Standardized Document-Driven', size=FS_TITLE, bold=True)
|
||
|
||
roles = [
|
||
('Product Manager', 'medium',
|
||
'Input: User Requirement Description',
|
||
['Feature List + Priority', 'User Stories (5 items)', 'Acceptance Criteria'],
|
||
'docs/PRD.md'),
|
||
('Architect', 'light',
|
||
'Input: PRD.md',
|
||
['Tech Stack: FastAPI+React', 'API Specification (OpenAPI)', 'Database Schema'],
|
||
'docs/design.md'),
|
||
('Engineer ×3', 'light',
|
||
'Input: design.md + module specification',
|
||
['Module A: User Service', 'Module B: Order Service', 'Module C: Payment Service'],
|
||
'src/*.py'),
|
||
('QA Engineer', 'medium',
|
||
'Input: src/ + PRD.md',
|
||
['Unit Tests (pytest)', 'Integration Tests (API)', 'Bug Report → Engineer'],
|
||
'docs/test_report.md'),
|
||
]
|
||
|
||
rw = 170
|
||
gap = 16
|
||
total_w = len(roles) * rw + (len(roles) - 1) * gap
|
||
rx_start = (W - total_w) // 2
|
||
ry = 55
|
||
|
||
for i, (name, fill, input_desc, outputs, artifact) in enumerate(roles):
|
||
x = rx_start + i * (rw + gap)
|
||
|
||
s.rect(x, ry, rw, 230, fill=fill, rx=6)
|
||
s.text(x + rw // 2, ry + 20, name, size=FS_SMALL, bold=True)
|
||
|
||
s.text(x + 8, ry + 42, input_desc, size=11, anchor='start', fill='text_light')
|
||
|
||
s.rect(x + 8, ry + 58, rw - 16, 20 + len(outputs) * 16, fill='code_bg', rx=3)
|
||
s.text(x + 14, ry + 72, 'Output:', size=FS_TINY, bold=True, anchor='start')
|
||
for j, out in enumerate(outputs):
|
||
s.text(x + 14, ry + 88 + j * 16, out, size=11, anchor='start', fill='text_light')
|
||
|
||
s.rect(x + 8, ry + 168, rw - 16, 30, fill='dark', rx=12)
|
||
s.mono(x + rw // 2, ry + 183, artifact, size=11, anchor='middle', fill='white')
|
||
|
||
if i < len(roles) - 1:
|
||
ax1 = x + rw + 2
|
||
ax2 = x + rw + gap - 2
|
||
s.arrow(ax1, ry + 115, ax2, ry + 115)
|
||
|
||
# QA → Engineer feedback loop
|
||
qa_x = rx_start + 3 * (rw + gap) + rw // 2
|
||
eng_x = rx_start + 2 * (rw + gap) + rw // 2
|
||
s.arrow_curved(qa_x, ry + 230 + 5, eng_x, ry + 230 + 5, curve=-30, label='Bug Fix', dash=True)
|
||
|
||
# Shared file system
|
||
fy = 310
|
||
s.rect(30, fy, W - 60, 50, fill='medium', rx=4)
|
||
s.text(W // 2, fy + 16, 'Shared Project Directory', size=FS_SMALL, bold=True)
|
||
s.mono(W // 2, fy + 36, 'docs/PRD.md docs/design.md src/*.py docs/test_report.md',
|
||
size=11, anchor='middle')
|
||
|
||
# Key insight
|
||
ky = 375
|
||
s.rect(30, ky, W - 60, 130, fill='code_bg', rx=4)
|
||
s.text(W // 2, ky + 18, 'MetaGPT Core Design', size=FS_BODY, bold=True)
|
||
|
||
insights = [
|
||
('Standardized Documents', 'Each role outputs a clear format — downstream only needs to understand the format, not the upstream thought process'),
|
||
('Interface Decoupling', 'Improve PM (switch to a stronger model) — as long as the output conforms to the PRD format, downstream requires zero modification'),
|
||
('No Manager', 'Control flows naturally along the DAG: PM→Arch→Eng→QA, no central scheduling overhead'),
|
||
('Exception Channel', 'QA test failure → Bug report routed by module back to Engineer → iterative fix'),
|
||
]
|
||
for i, (title, desc) in enumerate(insights):
|
||
y = ky + 42 + i * 24
|
||
s.text(42, y, '▸ ' + title, size=FS_SMALL, bold=True, anchor='start')
|
||
s.text(180, y, desc, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-10.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# Fig9-11: VLA Architecture (Vision-Language-Action) — Comparison of Three Technical Paths
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_11():
|
||
W, H = 900, 620
|
||
s = SVG(W, H)
|
||
|
||
# Common Input
|
||
in_x, in_y, in_w, in_h = 230, 55, 440, 64
|
||
s.rect(in_x, in_y, in_w, in_h, fill='medium', rx=6)
|
||
s.text(in_x + in_w / 2, in_y + 22, 'Common Input: camera image + language instruction', size=FS_SMALL, bold=True)
|
||
s.text(in_x + in_w / 2, in_y + 44, '"Put the red block into the blue box"', size=FS_TINY, fill='text_light')
|
||
|
||
# Arrows leading to three branches
|
||
s.arrow(in_x + 70, in_y + in_h + 2, 145, 165) # → OpenVLA
|
||
s.arrow(W / 2, in_y + in_h + 2, W / 2, 165) # → π₀
|
||
s.arrow(in_x + in_w - 70, in_y + in_h + 2, 755, 165) # → RT-2
|
||
|
||
# Three-Column Architecture
|
||
col_w, col_gap = 270, 30
|
||
cols_total = 3 * col_w + 2 * col_gap
|
||
sx0 = (W - cols_total) / 2 # = 30
|
||
|
||
columns = [
|
||
('OpenVLA', 'Open source · discrete action tokens', 'light', [
|
||
('Vision encoder', 'DINOv2 + SigLIP', 'Extract pixel features'),
|
||
('LLM backbone', 'Llama 2 (7B)', 'Understand instructions and scenes'),
|
||
('Decoding method', 'Autoregressive · text tokens', 'Discretize actions into options'),
|
||
('Action output', '"a=[3,−2,5,...]" tokens', 'Generate 7-DOF control values step by step'),
|
||
]),
|
||
('π₀(Pi-Zero)', 'Diffusion policy · smooth trajectory', 'medium', [
|
||
('Vision encoder', 'ViT multi-view fusion', 'Extract pixel features'),
|
||
('Mixture-of-Transformers', 'Fast-slow separated backbone', 'Language slow thinking + control fast thinking'),
|
||
('Decoding method', 'Diffusion denoising iteration', 'Coarse-to-fine refinement of entire trajectory'),
|
||
('Action output', 'Continuous action sequence (50 steps/batch)', 'High-frequency smooth control signal'),
|
||
]),
|
||
('RT-2', 'Language model as action model', 'light', [
|
||
('Vision-language backbone', 'PaLI-X / PaLM-E', 'VLM end-to-end understanding'),
|
||
('Action representation', 'Action → natural language tokens', '"move arm 5cm right"'),
|
||
('Decoding method', 'Reuse VLM autoregression', 'Shared weights with text generation'),
|
||
('Action output', 'Text description → controller parsing', 'Inherit VLM\'s semantic generalization'),
|
||
]),
|
||
]
|
||
|
||
top_y = 165
|
||
title_h = 50
|
||
row_h = 80
|
||
for i, (name, tag, fill, rows) in enumerate(columns):
|
||
cx = sx0 + i * (col_w + col_gap)
|
||
#Column container
|
||
col_total_h = title_h + len(rows) * row_h + 14
|
||
s.rect(cx, top_y, col_w, col_total_h, fill='white', stroke='border')
|
||
#Title bar
|
||
s.rect(cx, top_y, col_w, title_h, fill=fill)
|
||
s.text(cx + col_w / 2, top_y + 18, name, size=FS_BODY, bold=True)
|
||
s.text(cx + col_w / 2, top_y + 38, tag, size=FS_TINY, fill='text_light')
|
||
|
||
#Rows
|
||
for j, (label, value, hint) in enumerate(rows):
|
||
ry = top_y + title_h + 8 + j * row_h
|
||
s.rect(cx + 10, ry, col_w - 20, row_h - 8, fill='code_bg', rx=4)
|
||
s.text(cx + col_w / 2, ry + 18, label, size=FS_TINY, bold=True)
|
||
s.text(cx + col_w / 2, ry + 38, value, size=FS_TINY)
|
||
s.text(cx + col_w / 2, ry + 56, hint, size=FS_TINY, fill='text_light')
|
||
|
||
# Bottom: unified output layer
|
||
out_y = top_y + title_h + 4 * row_h + 14 + 24
|
||
s.rect(30, out_y, W - 60, 50, fill='darker', rx=6)
|
||
s.text(W / 2, out_y + 18,
|
||
'Robot control signals: 7-DOF joint angles / end-effector pose',
|
||
size=FS_SMALL, bold=True, fill='white')
|
||
s.text(W / 2, out_y + 36,
|
||
'The difference lies in "how to tell the robot what to do next," but both ultimately fall into a unified control interface',
|
||
size=FS_TINY, fill='white')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-11.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# Fig 9-12: Voice Werewolf Agent System (Exp 9.9)
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def fig9_12():
|
||
W, H = 780, 550
|
||
s = SVG(W, H)
|
||
|
||
s.text(W // 2, 28, 'Experiment 9.9: Voice Werewolf — Information Permission Control', size=FS_TITLE, bold=True)
|
||
|
||
# Judge (code-driven)
|
||
jx, jy, jw, jh = 260, 55, 260, 75
|
||
s.rect(jx, jy, jw, jh, fill='dark', rx=6)
|
||
s.text(jx + jw // 2, jy + 20, 'Judge (code-driven)', size=FS_BODY, bold=True, fill='white')
|
||
s.text(jx + jw // 2, jy + 42, 'Game state · Phase control · Information distribution', size=FS_TINY, fill='white')
|
||
s.text(jx + jw // 2, jy + 58, 'Night → Day → Vote → Settle', size=FS_TINY, fill='white')
|
||
|
||
# Role agents
|
||
roles = [
|
||
(40, 'Werewolf 1', '🐺', 'medium',
|
||
['Visible: Teammate identities', 'Strategy: Disguise as villager', 'Night: Choose target']),
|
||
(185, 'Werewolf 2', '🐺', 'medium',
|
||
['Visible: Teammate identities', 'Strategy: Follow votes to protect', 'Night: Negotiate target']),
|
||
(330, 'Seer', '🔮', 'light',
|
||
['Visible: Investigation results', 'Strategy: Choose timing to reveal', 'Night: Check 1 person']),
|
||
(475, 'Witch', '🧪', 'light',
|
||
['Visible: Death/healing', 'Strategy: Save potion/antidote', 'Night: Save/poison 1 person']),
|
||
(620, 'Villager ×2', '👤', '#e8e8e8',
|
||
['Visible: Public information only', 'Strategy: Logical reasoning', 'Day: Analyze speech']),
|
||
]
|
||
|
||
aw, ay = 135, 180
|
||
for x, name, icon, fill, info in roles:
|
||
s.rect(x, ay, aw, 140, fill=fill, rx=6)
|
||
s.text(x + aw // 2, ay + 18, f'{icon} {name}', size=FS_SMALL, bold=True)
|
||
for i, ln in enumerate(info):
|
||
s.text(x + aw // 2, ay + 42 + i * 20, ln, size=11, fill='text_light')
|
||
|
||
# Arrow from Judge
|
||
s.arrow(jx + jw // 2, jy + jh + 2, x + aw // 2, ay - 2, color='dark')
|
||
|
||
# Permission badge
|
||
if 'Werewolf' in name:
|
||
s.badge(x + aw - 45, ay + aw - 15, 40, 18, 'Mutual knowledge', fill='darker', font_size=11)
|
||
elif 'Seer' in name:
|
||
s.badge(x + aw - 55, ay + aw - 15, 50, 18, 'Investigation results', fill='darker', font_size=10)
|
||
|
||
# Info permission control
|
||
iy = 340
|
||
s.rect(30, iy, W - 60, 90, fill='code_bg', rx=4)
|
||
s.text(W // 2, iy + 18, 'Information permission control: judge filters context by role', size=FS_BODY, bold=True)
|
||
|
||
perms = [
|
||
('Werewolf', 'All werewolf identities + night discussion + public speech'),
|
||
('Seer', 'Investigation results (only self-investigated) + public speech'),
|
||
('Witch', 'Deaths of the night + antidote/poison status + public speech'),
|
||
('Villager', 'Public speech only + voting records (zero private information)'),
|
||
]
|
||
pw = (W - 80) // 2
|
||
for i, (role, perm) in enumerate(perms):
|
||
row, col = i // 2, i % 2
|
||
x = 50 + col * pw
|
||
y = iy + 40 + row * 22
|
||
s.text(x, y, role + ':', size=FS_TINY, bold=True, anchor='start')
|
||
s.text(x + 55, y, perm, size=FS_TINY, anchor='start', fill='text_light')
|
||
|
||
# Voice interaction
|
||
vy = 445
|
||
s.rect(30, vy, W - 60, 85, fill='light', rx=4)
|
||
s.text(W // 2, vy + 18, 'Real-time voice interaction (ASR + LLM + TTS)', size=FS_BODY, bold=True)
|
||
|
||
voice_flow = [
|
||
('Day discussion', 'Judge manages speaking order\nspeaking in seat order'),
|
||
('Voting phase', 'Collect all player votes\ntally votes and announce results'),
|
||
('Night phase', 'Judge wakes roles in sequence\nprivate voice channel'),
|
||
('Human player', 'Random role assignment\nvoice-based voting/speech'),
|
||
]
|
||
vw = (W - 80) // len(voice_flow)
|
||
for i, (title, desc) in enumerate(voice_flow):
|
||
cx = 50 + i * vw + vw // 2
|
||
s.text(cx, vy + 42, title, size=FS_SMALL, bold=True)
|
||
for j, ln in enumerate(desc.split('\n')):
|
||
s.text(cx, vy + 60 + j * 16, ln, size=FS_TINY, fill='text_light')
|
||
|
||
s.save(os.path.join(OUT, 'fig9-12.svg'))
|
||
|
||
|
||
# ════════════════════════════════════════════════════════════════════
|
||
# Main
|
||
# ════════════════════════════════════════════════════════════════════
|
||
|
||
def main():
|
||
os.makedirs(OUT, exist_ok=True)
|
||
|
||
figs = [
|
||
('fig9-1', fig9_1),
|
||
('fig9-2', fig9_2),
|
||
('fig9-3', fig9_3),
|
||
('fig9-4', fig9_4),
|
||
('fig9-5', fig9_5),
|
||
('fig9-6', fig9_6),
|
||
('fig9-7', fig9_7),
|
||
('fig9-8', fig9_8),
|
||
('fig9-9', fig9_9),
|
||
('fig9-10', fig9_10),
|
||
('fig9-11', fig9_11),
|
||
('fig9-12', fig9_12),
|
||
]
|
||
|
||
for name, func in figs:
|
||
func()
|
||
print(f' ✓ {name}')
|
||
|
||
print(f'\nGenerated {len(figs)} figures in {OUT}/')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|