第 31 章

Agents API:托管 Agent 生命周期控制与并发 Agent 编排

第三十一章:Artifacts 系统:代码、图表与网页的实时生成与部署

31.1 Artifacts 改变了什么

在 Artifacts 出现之前,Claude 生成代码或 HTML 的方式是把它们作为纯文本输出在对话框里。用户必须手动复制代码,粘贴到本地编辑器,自己运行。这种"输出即文本"的模式限制了 Claude 作为工具的实用价值。

Artifacts 将 Claude 从"文本生成器"升级为"可运行工件生产者"。 当 Claude 生成一个 React 组件时,用户不需要任何本地环境——它直接在对话界面旁边的预览窗口中编译运行。这是一个根本性的体验飞跃。

对于开发者而言,Artifacts 系统还有更深远的意义:Claude API 支持生成 Artifacts,这意味着可以在自己的产品中嵌入"Claude 生成可运行内容"的能力。

31.2 六种 Artifact 类型详解

类型一:代码(Code)

代码 Artifact 是最基础的类型,适合生成任意编程语言的独立可运行代码。

触发时机:

适合生成代码 Artifact 的请求:
✓ "写一个 Python 脚本,读取 CSV 并生成统计报告"
✓ "实现一个二叉树的中序遍历函数"
✓ "写一个 bash 脚本自动备份 MySQL 数据库"

不适合的请求:
✗ "解释一下什么是递归"(概念解释,不需要 Artifact)
✗ 只需要一两行代码片段的情况

代码 Artifact 的组成:

# 代码 Artifact 示例:CSV 数据分析脚本

import csv
import statistics
from collections import defaultdict
from typing import Dict, List, Tuple

def analyze_csv(filepath: str) -> Dict:
    """
    分析 CSV 文件并返回统计信息
    
    Args:
        filepath: CSV 文件路径
    
    Returns:
        包含列统计信息的字典
    """
    data = defaultdict(list)
    
    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        headers = reader.fieldnames or []
        
        for row in reader:
            for key, value in row.items():
                try:
                    data[key].append(float(value))
                except ValueError:
                    data[key].append(value)
    
    results = {}
    for col, values in data.items():
        numeric = [v for v in values if isinstance(v, (int, float))]
        if numeric:
            results[col] = {
                "type": "numeric",
                "count": len(numeric),
                "mean": statistics.mean(numeric),
                "median": statistics.median(numeric),
                "stdev": statistics.stdev(numeric) if len(numeric) > 1 else 0,
                "min": min(numeric),
                "max": max(numeric)
            }
        else:
            unique_values = set(str(v) for v in values)
            results[col] = {
                "type": "categorical",
                "count": len(values),
                "unique": len(unique_values),
                "most_common": max(unique_values, key=lambda x: values.count(x))
            }
    
    return results


if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("Usage: python analyze.py <csv_file>")
        sys.exit(1)
    
    results = analyze_csv(sys.argv[1])
    for col, stats in results.items():
        print(f"\n{col}:")
        for key, val in stats.items():
            if isinstance(val, float):
                print(f"  {key}: {val:.2f}")
            else:
                print(f"  {key}: {val}")

类型二:SVG 图形

SVG Artifact 生成矢量图形,在 Claude.ai 界面中实时渲染,支持无损缩放。

典型用途:系统架构图

<!-- SVG Artifact 示例:微服务架构图 -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500" 
     font-family="Arial, sans-serif" font-size="14">
  
  <!-- 背景 -->
  <rect width="800" height="500" fill="#f8f9fa" rx="10"/>
  
  <!-- API Gateway -->
  <rect x="300" y="30" width="200" height="60" rx="8"
        fill="#4285f4" stroke="#2b5dcc" stroke-width="2"/>
  <text x="400" y="55" text-anchor="middle" fill="white" 
        font-weight="bold">API Gateway</text>
  <text x="400" y="75" text-anchor="middle" fill="#cce5ff" 
        font-size="12">nginx / Kong</text>
  
  <!-- 用户服务 -->
  <rect x="100" y="160" width="160" height="60" rx="8"
        fill="#34a853" stroke="#1e7e34"/>
  <text x="180" y="185" text-anchor="middle" fill="white" 
        font-weight="bold">User Service</text>
  <text x="180" y="205" text-anchor="middle" fill="#c3e6cb" 
        font-size="12">:8001</text>
  
  <!-- 订单服务 -->
  <rect x="320" y="160" width="160" height="60" rx="8"
        fill="#34a853" stroke="#1e7e34"/>
  <text x="400" y="185" text-anchor="middle" fill="white" 
        font-weight="bold">Order Service</text>
  <text x="400" y="205" text-anchor="middle" fill="#c3e6cb" 
        font-size="12">:8002</text>
  
  <!-- 支付服务 -->
  <rect x="540" y="160" width="160" height="60" rx="8"
        fill="#34a853" stroke="#1e7e34"/>
  <text x="620" y="185" text-anchor="middle" fill="white" 
        font-weight="bold">Payment Service</text>
  <text x="620" y="205" text-anchor="middle" fill="#c3e6cb" 
        font-size="12">:8003</text>
  
  <!-- 数据库 -->
  <ellipse cx="180" cy="370" rx="70" ry="30" 
           fill="#ea4335" stroke="#b31412"/>
  <rect x="110" y="340" width="140" height="60" fill="#ea4335"/>
  <ellipse cx="180" cy="400" rx="70" ry="30" 
           fill="#ea4335" stroke="#b31412"/>
  <text x="180" y="375" text-anchor="middle" fill="white" 
        font-weight="bold">User DB</text>
  
  <!-- 连接线 -->
  <line x1="400" y1="90" x2="180" y2="160" 
        stroke="#666" stroke-width="2" marker-end="url(#arrow)"/>
  <line x1="400" y1="90" x2="400" y2="160" 
        stroke="#666" stroke-width="2" marker-end="url(#arrow)"/>
  <line x1="400" y1="90" x2="620" y2="160" 
        stroke="#666" stroke-width="2" marker-end="url(#arrow)"/>
  
  <!-- 箭头定义 -->
  <defs>
    <marker id="arrow" markerWidth="10" markerHeight="7" 
            refX="9" refY="3.5" orient="auto">
      <polygon points="0 0, 10 3.5, 0 7" fill="#666"/>
    </marker>
  </defs>
</svg>

类型三:HTML 网页

HTML Artifact 生成完整可运行的网页,在内嵌浏览器中预览,支持 JavaScript 交互。

示例:交互式数据看板

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>销售数据看板</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
               background: #f0f2f5; padding: 24px; }
        .dashboard { max-width: 1200px; margin: 0 auto; }
        h1 { color: #1a1a2e; margin-bottom: 24px; font-size: 24px; }
        .cards { display: grid; grid-template-columns: repeat(3, 1fr); 
                 gap: 16px; margin-bottom: 24px; }
        .card { background: white; border-radius: 12px; padding: 20px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
        .card-value { font-size: 32px; font-weight: 700; color: #1a1a2e; }
        .card-label { color: #666; font-size: 14px; margin-top: 4px; }
        .card-change { font-size: 13px; margin-top: 8px; }
        .up { color: #52c41a; }
        .down { color: #ff4d4f; }
        .chart-container { background: white; border-radius: 12px;
                           padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
    </style>
</head>
<body>
    <div class="dashboard">
        <h1>月度销售数据看板</h1>
        
        <div class="cards">
            <div class="card">
                <div class="card-value">¥2,318,000</div>
                <div class="card-label">本月 GMV</div>
                <div class="card-change up">↑ 12.3% vs 上月</div>
            </div>
            <div class="card">
                <div class="card-value">4,821</div>
                <div class="card-label">订单数</div>
                <div class="card-change up">↑ 8.7% vs 上月</div>
            </div>
            <div class="card">
                <div class="card-value">¥480</div>
                <div class="card-label">客单价</div>
                <div class="card-change down">↓ 3.2% vs 上月</div>
            </div>
        </div>
        
        <div class="chart-container">
            <canvas id="salesChart"></canvas>
        </div>
    </div>
    
    <script>
        const ctx = document.getElementById('salesChart');
        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
                datasets: [{
                    label: 'GMV(万元)',
                    data: [185, 172, 198, 215, 206, 231.8],
                    backgroundColor: 'rgba(66, 133, 244, 0.8)',
                    borderRadius: 6,
                }]
            },
            options: {
                responsive: true,
                plugins: { legend: { position: 'top' } },
                scales: { y: { beginAtZero: false, min: 150 } }
            }
        });
    </script>
</body>
</html>

类型四:React 组件

React Artifact 是 Claude.ai 最强大的 Artifact 类型,在浏览器中实时编译并运行:

// React Artifact 示例:交互式任务管理组件
import { useState } from "react";

const PRIORITIES = ["高", "中", "低"];
const PRIORITY_COLORS = { 高: "bg-red-100 text-red-700", 中: "bg-yellow-100 text-yellow-700", 低: "bg-green-100 text-green-700" };

export default function TaskManager() {
  const [tasks, setTasks] = useState([
    { id: 1, title: "完成 API 文档", priority: "高", done: false },
    { id: 2, title: "代码审查:PR #234", priority: "中", done: false },
    { id: 3, title: "更新依赖版本", priority: "低", done: true },
  ]);
  const [newTitle, setNewTitle] = useState("");
  const [newPriority, setNewPriority] = useState("中");

  const addTask = () => {
    if (!newTitle.trim()) return;
    setTasks(prev => [...prev, {
      id: Date.now(), title: newTitle.trim(),
      priority: newPriority, done: false
    }]);
    setNewTitle("");
  };

  const toggleDone = (id) => {
    setTasks(prev => prev.map(t => t.id === id ? {...t, done: !t.done} : t));
  };

  const deleteTask = (id) => {
    setTasks(prev => prev.filter(t => t.id !== id));
  };

  const pending = tasks.filter(t => !t.done);
  const done = tasks.filter(t => t.done);

  return (
    <div className="max-w-lg mx-auto p-6 font-sans">
      <h1 className="text-2xl font-bold text-gray-800 mb-6">任务管理</h1>
      
      {/* 添加任务 */}
      <div className="flex gap-2 mb-6">
        <input
          className="flex-1 border border-gray-300 rounded-lg px-3 py-2 text-sm"
          placeholder="新任务标题..."
          value={newTitle}
          onChange={e => setNewTitle(e.target.value)}
          onKeyDown={e => e.key === "Enter" && addTask()}
        />
        <select
          className="border border-gray-300 rounded-lg px-3 py-2 text-sm"
          value={newPriority}
          onChange={e => setNewPriority(e.target.value)}
        >
          {PRIORITIES.map(p => <option key={p}>{p}</option>)}
        </select>
        <button
          onClick={addTask}
          className="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-blue-600"
        >
          添加
        </button>
      </div>

      {/* 待办任务 */}
      <div className="mb-4">
        <h2 className="text-sm font-semibold text-gray-500 mb-2">
          待办 ({pending.length})
        </h2>
        {pending.map(task => (
          <div key={task.id} 
               className="flex items-center gap-3 p-3 bg-white border border-gray-200 rounded-lg mb-2">
            <input type="checkbox" onChange={() => toggleDone(task.id)} 
                   className="w-4 h-4 cursor-pointer" />
            <span className="flex-1 text-sm text-gray-800">{task.title}</span>
            <span className={`text-xs px-2 py-1 rounded-full ${PRIORITY_COLORS[task.priority]}`}>
              {task.priority}
            </span>
            <button onClick={() => deleteTask(task.id)}
                    className="text-gray-400 hover:text-red-500 text-lg">×</button>
          </div>
        ))}
      </div>

      {/* 已完成 */}
      {done.length > 0 && (
        <div>
          <h2 className="text-sm font-semibold text-gray-400 mb-2">
            已完成 ({done.length})
          </h2>
          {done.map(task => (
            <div key={task.id}
                 className="flex items-center gap-3 p-3 bg-gray-50 border border-gray-100 rounded-lg mb-2 opacity-60">
              <input type="checkbox" checked readOnly className="w-4 h-4" />
              <span className="flex-1 text-sm text-gray-500 line-through">{task.title}</span>
              <button onClick={() => deleteTask(task.id)}
                      className="text-gray-300 hover:text-red-500 text-lg">×</button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

类型五:Mermaid 图表

Mermaid Artifact 生成各类技术图表,无需任何图形设计工具:

sequenceDiagram
    autonumber
    actor User as 用户
    participant FE as 前端
    participant GW as API Gateway
    participant Auth as Auth Service
    participant Order as Order Service
    participant DB as Database

    User->>FE: 提交订单请求
    FE->>GW: POST /api/orders (Bearer token)
    GW->>Auth: 验证 JWT Token
    Auth-->>GW: Token 有效,user_id=123
    GW->>Order: 创建订单 (user_id=123)
    Order->>DB: INSERT INTO orders...
    DB-->>Order: order_id=456
    Order-->>GW: 201 Created {order_id: 456}
    GW-->>FE: 201 Created {order_id: 456}
    FE-->>User: 订单创建成功 #456
erDiagram
    USER {
        int id PK
        string email UK
        string name
        datetime created_at
    }
    ORDER {
        int id PK
        int user_id FK
        decimal total_amount
        string status
        datetime created_at
    }
    ORDER_ITEM {
        int id PK
        int order_id FK
        int product_id FK
        int quantity
        decimal unit_price
    }
    PRODUCT {
        int id PK
        string name
        decimal price
        int stock
    }

    USER ||--o{ ORDER : "creates"
    ORDER ||--|{ ORDER_ITEM : "contains"
    PRODUCT ||--o{ ORDER_ITEM : "included in"

类型六:Markdown 文档

Markdown Artifact 适合生成独立文档,可以下载和在其他系统中使用。

31.3 在 API 中控制 Artifact 生成

通过 Claude API,可以在自己的产品中实现类似 Claude.ai 的 Artifact 生成能力:

import anthropic
import json
import re

def generate_artifact(
    prompt: str,
    artifact_type: str = "react",
    additional_context: str = ""
) -> dict:
    """
    通过 API 生成 Artifact
    
    artifact_type: code | svg | html | react | mermaid | markdown
    """
    client = anthropic.Anthropic()
    
    type_instructions = {
        "react": """生成一个完整的 React 组件作为 Artifact。
要求:
- 使用函数组件和 Hooks
- 可以使用 Tailwind CSS 类名
- 可以从 'recharts' 导入图表组件
- 导出为默认导出(export default)
- 组件必须完整可运行,不依赖外部文件
以 ```react 代码块包裹输出""",

        "html": """生成完整的 HTML 页面作为 Artifact。
要求:
- 包含完整的 HTML5 文档结构
- CSS 写在 <style> 标签内
- JavaScript 写在 <script> 标签内
- 可以使用 CDN 引入第三方库
以 ```html 代码块包裹输出""",

        "svg": """生成 SVG 图形作为 Artifact。
要求:
- 输出完整的 <svg> 元素
- 使用 viewBox 属性确保可缩放
- 图形清晰、专业、信息密度适当
以 ```svg 代码块包裹输出""",

        "mermaid": """生成 Mermaid 图表作为 Artifact。
以 ```mermaid 代码块包裹输出""",
        
        "python": """生成完整可运行的 Python 代码作为 Artifact。
以 ```python 代码块包裹输出""",
    }
    
    instruction = type_instructions.get(artifact_type, 
                                        f"生成 {artifact_type} 类型的 Artifact")
    
    system = f"""你是一个专业的代码生成助手。
{instruction}
{additional_context}
只输出请求的 Artifact 内容,不需要额外解释。"""
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=4096,
        system=system,
        messages=[{"role": "user", "content": prompt}]
    )
    
    content = response.content[0].text
    
    # 提取代码块
    pattern = rf"```{artifact_type}\n(.*?)```"
    match = re.search(pattern, content, re.DOTALL)
    
    if match:
        artifact_content = match.group(1).strip()
    else:
        # 尝试通用代码块
        generic_match = re.search(r"```\w*\n(.*?)```", content, re.DOTALL)
        artifact_content = generic_match.group(1).strip() if generic_match else content
    
    return {
        "type": artifact_type,
        "content": artifact_content,
        "raw_response": content
    }


# 使用示例
result = generate_artifact(
    prompt="创建一个展示月度销售趋势的柱状图组件,使用 recharts",
    artifact_type="react"
)

print(f"Artifact 类型: {result['type']}")
print(f"内容预览:\n{result['content'][:500]}...")

31.4 Artifact 的迭代修改

用户通常需要对 Artifact 进行多轮修改。设计一个支持上下文保留的迭代流程:

class ArtifactIterator:
    """支持多轮迭代的 Artifact 修改器"""
    
    def __init__(self, artifact_type: str = "react"):
        self.client = anthropic.Anthropic()
        self.artifact_type = artifact_type
        self.messages = []
        self.current_artifact = None
        self.version_history = []
    
    def create(self, prompt: str) -> str:
        """创建初始 Artifact"""
        self.messages.append({"role": "user", "content": prompt})
        
        response = self.client.messages.create(
            model="claude-opus-4-5",
            max_tokens=4096,
            system=self._get_system(),
            messages=self.messages
        )
        
        assistant_text = response.content[0].text
        self.messages.append({"role": "assistant", "content": assistant_text})
        
        self.current_artifact = self._extract_artifact(assistant_text)
        self.version_history.append(self.current_artifact)
        
        return self.current_artifact
    
    def modify(self, modification_request: str) -> str:
        """在当前版本基础上进行修改"""
        # 将修改请求添加到对话历史
        self.messages.append({
            "role": "user",
            "content": f"请修改当前的 {self.artifact_type}:{modification_request}"
        })
        
        response = self.client.messages.create(
            model="claude-opus-4-5",
            max_tokens=4096,
            system=self._get_system(),
            messages=self.messages
        )
        
        assistant_text = response.content[0].text
        self.messages.append({"role": "assistant", "content": assistant_text})
        
        new_artifact = self._extract_artifact(assistant_text)
        if new_artifact:
            self.current_artifact = new_artifact
            self.version_history.append(new_artifact)
        
        return self.current_artifact
    
    def revert(self, version_index: int = -2) -> str:
        """回退到历史版本"""
        if abs(version_index) <= len(self.version_history):
            self.current_artifact = self.version_history[version_index]
            return self.current_artifact
        return self.current_artifact
    
    def _get_system(self) -> str:
        type_map = {
            "react": "生成使用 Tailwind CSS 的 React 函数组件,用 ```react 包裹",
            "html": "生成完整 HTML 页面,用 ```html 包裹",
            "mermaid": "生成 Mermaid 图表,用 ```mermaid 包裹",
        }
        return type_map.get(self.artifact_type, f"生成 {self.artifact_type} 代码")
    
    def _extract_artifact(self, text: str) -> str | None:
        pattern = rf"```{self.artifact_type}\n(.*?)```"
        match = re.search(pattern, text, re.DOTALL)
        if match:
            return match.group(1).strip()
        generic = re.search(r"```\w*\n(.*?)```", text, re.DOTALL)
        return generic.group(1).strip() if generic else None


# 使用示例
iterator = ArtifactIterator("react")

# 创建初始版本
v1 = iterator.create("创建一个简单的计数器组件,有加减按钮")

# 第一次修改
v2 = iterator.modify("添加一个重置按钮,将计数器归零")

# 第二次修改
v3 = iterator.modify("把背景色改为浅蓝色,按钮改为圆形")

# 如果不满意,回退到 v2
previous = iterator.revert(-2)

31.5 在产品中嵌入 Artifact 渲染

如果你在构建自己的 AI 产品,可以实现类似 Claude.ai 的 Artifact 渲染:

# 后端:判断输出类型,返回结构化数据
def parse_artifact_from_response(text: str) -> dict:
    """
    从 Claude 回复中解析 Artifact
    返回结构化的 artifact 对象供前端渲染
    """
    # 检测各类型代码块
    type_patterns = {
        "react": r"```react\n(.*?)```",
        "html": r"```html\n(.*?)```",
        "svg": r"```svg\n(.*?)```",
        "mermaid": r"```mermaid\n(.*?)```",
        "python": r"```python\n(.*?)```",
    }
    
    for artifact_type, pattern in type_patterns.items():
        match = re.search(pattern, text, re.DOTALL)
        if match:
            return {
                "has_artifact": True,
                "type": artifact_type,
                "content": match.group(1).strip(),
                "text_around": text[:match.start()].strip()
            }
    
    return {"has_artifact": False, "content": text}
// 前端:根据 artifact 类型渲染不同组件
function ArtifactRenderer({ artifact }) {
  if (!artifact.has_artifact) {
    return <ChatMessage text={artifact.content} />;
  }

  switch (artifact.type) {
    case 'html':
      return (
        <div className="artifact-container">
          <iframe
            srcDoc={artifact.content}
            sandbox="allow-scripts allow-same-origin"
            className="artifact-iframe"
          />
        </div>
      );
    
    case 'mermaid':
      return <MermaidDiagram code={artifact.content} />;
    
    case 'react':
      // 需要使用 Babel standalone 在浏览器中编译
      return <ReactSandbox code={artifact.content} />;
    
    case 'svg':
      return (
        <div 
          dangerouslySetInnerHTML={{ __html: artifact.content }}
          className="artifact-svg"
        />
      );
    
    default:
      return <CodeBlock language={artifact.type} code={artifact.content} />;
  }
}

小结

Artifacts 系统将 Claude 的价值从"建议提供者"升级为"可运行工件生产者"。六种类型各有其适用场景:

通过 API 可以在自己的产品中实现完整的 Artifact 生成和渲染能力,无需依赖 Claude.ai 界面。

下一章将介绍 Claude.ai API 集成——如何通过官方接口扩展 Managed Agent 的能力,以及企业级 API 访问模式。

本章评分
4.6  / 5  (3 评分)

💬 留言讨论