AI 应用工程化:MCP 工作流与 Agent 编排
MCP 协议、Tool Calling 架构、Agent 状态机、Prompt 版本管理与 AI 应用从 Demo 到生产的工程化路径。
Tool Calling 可视化、Multi-Agent 协作界面、Human-in-the-Loop 与 Agent 状态机的前端设计。
前置阅读:RAG 前端架构 · 体系第 4 篇
从 Chat 到 Agent 是 AI 产品进化的下一个阶段。Agent 能调用工具、执行多步任务、自主决策——前端需要设计的不再是对话框,而是任务执行的可视化工作台。
| Chat | Agent | |
|---|---|---|
| 交互模式 | 问答 | 任务委托 |
| 输出 | 文本 | 文本 + 工具调用 + 中间结果 |
| 控制权 | 用户主导 | Agent 自主 + 用户审批 |
| UI 复杂度 | 消息列表 | 状态机 + 工具链 + 进度 |
Idle → Planning → Executing → WaitingApproval → Completed
↓ ↓
ToolCall UserReject → Replan
↓
ToolResult → NextStep
↓
Error → Retry / Fallback
type AgentState =
| { status: "idle" }
| { status: "planning"; thought: string }
| { status: "executing"; tool: ToolCall; progress: number }
| { status: "waiting_approval"; action: ProposedAction }
| { status: "completed"; result: TaskResult }
| { status: "error"; error: AgentError; retryable: boolean };
Agent 调用工具时,用户需要看到「Agent 在做什么」:
function ToolCallCard({ call }: { call: ToolCall }) {
return (
<div className="tool-call">
<div className="tool-header">
<ToolIcon name={call.tool} />
<span>{call.tool}</span>
<StatusBadge status={call.status} />
</div>
<Collapsible title="参数">
<CodeBlock language="json">
{JSON.stringify(call.args, null, 2)}
</CodeBlock>
</Collapsible>
{call.result && (
<Collapsible title="结果">
<ToolResultRenderer result={call.result} />
</Collapsible>
)}
</div>
);
}
Agent 执行敏感操作前必须获得用户确认:
function ApprovalDialog({ action }: { action: ProposedAction }) {
return (
<Dialog>
<h3>Agent 请求执行以下操作</h3>
<ActionPreview action={action} />
<div className="action-details">
<p>工具:{action.tool}</p>
<p>影响:{action.impactDescription}</p>
</div>
<div className="actions">
<Button variant="danger" onClick={() => reject(action.id)}>
拒绝
</Button>
<Button variant="primary" onClick={() => approve(action.id)}>
批准执行
</Button>
</div>
</Dialog>
);
}
必须审批的操作类型:
┌─────────────────────────────────────────┐
│ Task: 分析 Q3 销售数据并生成报告 │
├──────────┬──────────┬───────────────────┤
│ 数据Agent │ 分析Agent│ 报告Agent │
│ ✅ 已获取 │ 🔄 分析中│ ⏳ 等待中 │
│ 3个数据源 │ 67% │ │
└──────────┴──────────┴───────────────────┘
│ │
│ [Agent 对话日志] │
│ 数据Agent: 已获取销售、库存、客户数据 │
│ 分析Agent: 正在计算同比环比... │
└─────────────────────────────────────────┘
// Server → Client 事件流
type AgentEvent =
| { type: "thought"; content: string }
| { type: "tool_call"; tool: string; args: Record<string, unknown> }
| { type: "tool_result"; tool: string; result: unknown }
| { type: "approval_required"; action: ProposedAction }
| { type: "text_delta"; content: string }
| { type: "done"; result: TaskResult }
| { type: "error"; message: string };
function useAgent(task: string) {
const [state, setState] = useState<AgentState>({ status: "idle" });
const [events, setEvents] = useState<AgentEvent[]>([]);
async function run() {
setState({ status: "planning", thought: "" });
const response = await fetch("/api/agent", {
method: "POST",
body: JSON.stringify({ task }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const event = JSON.parse(decoder.decode(value)) as AgentEvent;
setEvents((prev) => [...prev, event]);
updateStateFromEvent(event, setState);
}
}
return { state, events, run, approve, reject };
}
<ToolCallCard
role="region"
aria-live="polite"
aria-label={`工具调用:${tool.name},状态:${tool.status}`}
>
...
</ToolCallCard>
流式内容区用 aria-busy={isStreaming};审批对话框 trap focus,Esc 等价于 reject。
多 Agent:两个 Agent 同时改同一资源时,UI 显示 冲突卡片,让用户选保留哪条结果,而不是后写覆盖前写。
aria-busy、审批对话框 focus trap全部打勾 → 进入 第 5 篇:MCP 与 Agent 编排。