PlantUML 在 LLM Agent 中的角色:让 AI 推理过程可视化与人机协同
LLM Agent 输出的 token 流对人不友好。PlantUML 是 Agent 把「思考」转成结构化可视图的优秀工具——本文整理 LangChain / AutoGPT / CrewAI 框架下 PlantUML 的角色。
Agent 的可解释性困境 当前 LLM Agent 的痛点:
过程不透明 :reasoning trace 是隐性 Prompt 链,开发者看不到决策依据
结果不好调试 :Agent 失败时,你不知道是哪一步出问题
多 Agent 复杂 :20 步 Agent 步骤出错后只能「重跑」
人机协同弱 :人想中途插入指令,但 Agent 没暴露「我现在在想 X」
PlantUML 是 Agent 自我表达 + 人介入的桥梁。
主要用例 1. 决策树可视化(ReAct Agent) ReAct Agent 是「Reasoning + Acting」交替框架。每一步:
1 2 3 4 5 Thought: I need to find X Action: search(query) Observation: result of search Thought: based on result, I now do Y Action: ...
把 trace 转 PlantUML activity:
1 2 3 4 5 6 7 8 ReAct Agent Flow ├── Thought: search ├── Action: search("plantuml") ├── Observation: 100 results ├── Thought: pick top 5 ├── Action: rank() ├── Observation: top 5 └── Final: 5 results
PlantUML 输出:
1 2 3 4 5 6 7 8 9 10 11 @startuml start :Thought: search; :Action: search("plantuml"); :Observation: 100 results; :Thought: pick top 5; :Action: rank(); :Observation: top 5; :Final: 5 results; stop @enduml
价值 :开发者一眼能看出「哪一步 observation 没推动 reasoning → 检查 prompt / 工具」。
2. 多 Agent 协作(CrewAI / AutoGen) 多 Agent 框架的「对话图」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @startuml left to right direction skinparam rectangle<<agent>> { BackgroundColor<<researcher>> #FFE0E0 BackgroundColor<<coder>> #E0FFE0 BackgroundColor<<critic>> #E0E0FF } rectangle "Researcher" <<researcher>> as r rectangle "Coder" <<coder>> as c rectangle "Critic" <<critic>> as crit r -> c : "research findings" c -> crit : "draft implementation" crit -> c : "feedback" crit -> r : "ask for more research" @enduml
这个图是状态机 而不是调用栈 ——表达的是「Agent 之间的信息流转」。
3. 工具调用决策(Function Calling) function_call 决策也可以画出来:
1 2 3 4 5 6 7 8 9 10 11 12 @startuml (*) --> "收到 user query" if "query 涉及数据库?" then -->[yes] "调用 db_query tool" --> "得到 records" --> "格式化" --> "返回" else -->[no] "直接 LLM 回答" --> "返回" endif @enduml
工程实现 在 LangChain 里追踪 trace LangChain 有 langchain.debug = True 输出详细 trace,但人类读 nested 不友好 。
1 2 3 4 5 6 7 8 import langchainfrom langchain.agents import AgentExecutorfrom langchain.tools import toollangchain.debug = True agent = AgentExecutor(...) result = agent.invoke({"input" : "What is PlantUML?" })
输出:
1 2 3 4 5 6 [chain/start] [1:chain:AgentExecutor] [llm/start] ... [tool/start] search [tool/end] ... [llm/end] ... [chain/end]
自定义 callback 输出 PlantUML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from langchain.callbacks.base import BaseCallbackHandlerclass PlantUMLCallback (BaseCallbackHandler ): def __init__ (self ): self .steps = [] def on_agent_action (self, action, **kwargs ): self .steps.append(f"Action: {action.tool} , Input: {action.tool_input} " ) def on_agent_finish (self, finish, **kwargs ): self .steps.append(f"Finish: {finish.return_values} " ) def to_puml (self ): puml = "@startuml\nstart\n" for s in self .steps: puml += f":{s} ;\n" puml += "stop\n@enduml\n" return puml cb = PlantUMLCallback() agent.invoke({"input" : "..." }, config={"callbacks" : [cb]}) print (cb.to_puml())
Trace 输出到 console 1 2 3 4 5 6 7 8 9 10 11 import plantumlpuml_server = plantuml.PlantUML(url='http://www.plantuml.com/plantuml/svg/' ) def render_trace (steps ): puml = "@startuml\nstart\n" for s in steps: puml += f":{s} ;\n" puml += "stop\n@enduml\n" return puml_server.processes(puml)
在 web UI 里动态显示 1 2 3 4 5 6 7 8 9 const pumlEncoder = require ('plantuml-encoder' );async function fetchAndRender (traceId ) { const trace = await fetch (`/api/agent/trace/${traceId} ` ).then (r => r.json ()); const puml = traceToPuml (trace); const encoded = pumlEncoder.encode (puml); document .getElementById ('trace-img' ).src = `https://www.plantuml.com/plantuml/svg/~1${encoded} ` ; }
LangSmith / LangGraph 的 PlantUML LangSmith 把 trace 可视化但不是 PlantUML 。手动导出 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from langgraph.graph import StateGraphgraph = StateGraph(MyState) graph.add_node("search" , search_node) graph.add_node("rank" , rank_node) graph.add_edge("search" , "rank" ) def to_puml (graph ): puml = "@startuml\n" for src, dst in graph.edges: puml += f"{src} --> {dst} \n" puml += "@enduml" return puml with open ("/tmp/agent-graph.puml" , "w" ) as f: f.write(to_puml(graph))
plantuml -tsvg /tmp/agent-graph.puml → 看可视化。
CrewAI 的内置可视化 CrewAI 有 crew.plot() 输出任务图:
1 2 crew = Crew(agents=[...], tasks=[...]) crew.plot()
底层用 NetworkX + matplotlib 。如果你想用 PlantUML:重新生成:
1 2 3 4 5 6 7 8 9 def crew_to_puml (crew ): puml = "@startuml\n" for agent in crew.agents: puml += f'rectangle "{agent.role} " as {agent.id } \n' for task in crew.tasks: for agent in task.assigned_agents: puml += f"{agent.id } --> {task.id } \n" puml += "@enduml" return puml
ReWOO / Plan-and-Execute Agent 的 Plan ReWOO 类 Agent(先写 plan,再逐步执行):
1 2 3 4 5 6 7 8 9 10 11 @startuml (*) --> "Plan:" --> "Step 1: search" --> "Step 2: extract" --> "Step 3: synthesize" --> "Execute plan:" --> "Run step 1" --> "Run step 2" --> "Run step 3" --> "Final answer" @enduml
这是 PlantUML 状态机 + activity 混合图——只展示决策流向。
多 Agent 状态机的实现 让 Multi-Agent 系统自我解释 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MultiAgentSystem : def __init__ (self ): self .transitions = [] def transition (self, from_agent, to_agent, reason ): self .transitions.append({ "from" : from_agent, "to" : to_agent, "reason" : reason }) def render (self ): puml = "@startuml\n" for agent in set (a["from" ] for a in self .transitions) | set (a["to" ] for a in self .transitions): puml += f'rectangle "{agent} " as {agent} \n' for t in self .transitions: puml += f"{t['from' ]} --> {t['to' ]} : {t['reason' ]} \n" puml += "@enduml" return puml
调试时:
1 2 3 4 5 6 mas = MultiAgentSystem() mas.transition("user" , "router" , "ask plan" ) mas.transition("router" , "researcher" , "needs data" ) mas.transition("researcher" , "critic" , "review" ) print (mas.render())
输出 plantuml → 看 agent 协作流。
调试协作时的「人介入」 PlantUML 图暴露 → 人能清晰地看到「我应该在哪儿按暂停、加约束」:
1 2 3 4 5 6 7 8 9 10 11 12 class InterventionHook : def __init__ (self, mas ): self .mas = mas def check (self, agent_output ): for rule in self .rules: if rule.matches(agent_output): return HumanInterventionRequest( agent=agent_output.agent, reason=rule.reason, diagram=self .mas.render() )
HumanInterventionRequest 把当前 PlantUML 状态附加,人看到一个当前协作的可视化快照 ,决定「approve / modify / abort」。
vs 其他可视化
工具
谁用
适合什么
LangSmith
LangChain
trace timeline、token 计数
LangGraph viz
LangGraph
graph statics
PlantUML
跨框架
易于嵌文档、版本控制、PDF 导出
Mermaid
LangGraph 默认
浏览器渲染
matplotlib
CrewAI 默认
静态 PNG
PlantUML 优势 :
文本易 diff → 可放 git
多输出格式(SVG/PNG/PDF)→ 嵌入 PPT
CJK 可读 → 中文 paper
!include → 复用子图
实战项目:2 个 ReAct Agent 协作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @startuml rectangle "User Query" as UQ rectangle "Agent A (Researcher)" as A rectangle "Agent B (Coder)" as B database "Tool Cache" as TC UQ --> A : task A --> TC : search TC --> A : result A --> B : handoff to coder B --> A : ask for more A --> B B --> UQ : final output @enduml
一个完整的例子:debug 一个出错的 Agent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from langchain_community.tools import DuckDuckGoSearchRunfrom my_callback import PlantUMLCallbackcb = PlantUMLCallback() tools = [DuckDuckGoSearchRun()] agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True , callbacks=[cb] ) try : result = agent.run("Tell me about plantuml.com" ) except Exception as e: print ("Error:" , e) finally : print (cb.to_puml())
agent 失败时输出失败时的状态机截图,便于复盘。
小结
PlantUML 在 Agent 场景下 4 个角色:debug 可视化、流程讲解、人介入快照、文档嵌入
实现成本最低:自定义 callback 输出 puml + 渲染 → SVG
多 Agent 框架图是**「Agent 之间信息流」,不是 「程序栈」**——一定要区分
下一步