Archive2Figure (a2f) skill for converting PDF archives into digital character figures. Upload PDF → Extract character features → Generate images → Retrieve results. Supports Chinese historical characters and realistic figure generation.

Install

openclaw skills install a2f

Archive2Figure (A2F) Skill

Overview

The A2F (Archive2Figure) skill converts PDF documents containing character information into high-quality digital figure images using AI generation. It's particularly optimized for Chinese historical characters and realistic portrait generation.

Note: For ClawScan-generated or scanned PDF archives, ensure the PDF is OCR-processed or text-searchable before uploading to archiveData for reliable feature extraction.

Workflow

  1. Extract Features: Upload PDF → Extract character features and tags
  2. Generate Images: Use features to generate character images
  3. Check Status: Poll for job completion and retrieve results

Table of Contents

Quick Start

Basic Usage

import httpx

API_BASE = "https://wuji.cyphy.com/api"

# Step 1: Upload PDF and extract features
async def extract_features(pdf_path, role_name):
    async with httpx.AsyncClient(timeout=60.0) as client:
        with open(pdf_path, "rb") as f:
            files = {"files[0]": (pdf_path, f, "application/pdf")}
            data = {
                "role": role_name,
                "loop_count": 2,
                "gen_method": "qwen"
            }
            response = await client.post(f"{API_BASE}/archiveData", data=data, files=files)
            return response.json()

# Step 2: Generate images
async def generate_images(prompt):
    async with httpx.AsyncClient(timeout=60.0) as client:
        payload = {
            "text": prompt,
            "negative_text": "低质量, 模糊, 变形",
            "gen_method": 5,
            "gen_size": 1,
            "img_num": 4,
            "json": 1
        }
        response = await client.post(f"{API_BASE}/a2fgen", json=payload)
        return response.json()

# Step 3: Check job status
async def check_status(job_id):
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.get(f"{API_BASE}/job_status/{job_id}")
        return response.json()

API Endpoints

1. Extract Features

Endpoint: POST /api/archiveData

Upload a PDF file and extract character features and tags.

Parameters:

ParameterTypeRequiredDescription
files[0]FileYesPDF file containing character information
rolestringYesCharacter name (e.g., "李清照")
loop_countintNoExtraction loops (default: 2)
gen_methodstringNoGeneration method (default: "qwen")
openclawintNoService flag (default: 1)

Request Example:

import httpx

async with httpx.AsyncClient() as client:
    with open("character.pdf", "rb") as f:
        files = {"files[0]": ("character.pdf", f, "application/pdf")}
        data = {
            "role": "李清照",
            "loop_count": 2,
            "gen_method": "qwen"
        }
        response = await client.post(
            "https://wuji.cyphy.com/api/archiveData",
            data=data,
            files=files
        )
        result = response.json()

Response Example:

{
  "李清照诗词中的自我形象研究_侯淑婉.pdf": {
    "role": ["主要人物"],
    "gender": ["女性"],
    "age": ["成人"],
    "ffeatures": ["皮肤白皙", "面容清秀", "表情细腻", "情感丰富"],
    "hairstyle": ["长发", "传统发型"],
    "headdress": [],
    "build": ["苗条"],
    "clothing": ["古代服饰", "轻薄衣衫", "色彩淡雅"],
    "footwear": [],
    "accessories": ["金钗", "玉饰", "首饰"],
    "activity": ["写诗", "游玩", "思念"],
    "background": ["宋代", "江南水乡", "封建社会", "家庭环境"],
    "context": ["诗词创作", "家庭生活", "社会动荡"],
    "vibe": ["婉约", "细腻", "坚韧"],
    "temperament": ["敏感", "多情", "独立"],
    "language": ["文言文", "宋代", "古代汉语", "文雅"],
    "voice": ["柔和", "深情"],
    "cusstyle": ["中国古典水墨画"]
  }
}

2. Generate Images

Endpoint: POST /api/a2fgen

Generate character images using extracted features.

Parameters:

ParameterTypeRequiredDescription
textstringYesCombined prompt text
negative_textstringNoNegative prompts (quality control)
gen_methodintNoGeneration method (default: 5)
gen_sizeintNoGeneration size (default: 1)
img_numintNoNumber of images (1-10, default: 4)
jsonintNoResponse format (default: 1)

Default Negative Prompt:

文本, 特写, 裁剪, 出框, 最差质量, 低质量, jpeg 伪影, pgl y, 重复, 病态, 残缺, 额外的手指, 变异的手, 画得不好的手, 画得不好的脸, 突变, 变形, 模糊, 脱水, 不良的解剖结构, 不良的比例, 额外的肢体, 克隆的脸, 毁容, 总体比例, 畸形的四肢, 缺失的手臂, 缺失的腿, 额外的手臂, 多余的腿, 融合的手指, 太多的手指, 长脖子, 水印, 印章

Request Example:

payload = {
    "text": "李清照, 女性, 成人, 肖像画, 正脸, 全身照, 站立, 看着观众, 皮肤白皙, 面容清秀, 古代服饰, 中国古典水墨画, 写实风格, 真人风格",
    "negative_text": "文本, 特写, 裁剪, 出框, 最差质量, 低质量",
    "gen_method": 5,
    "gen_size": 1,
    "img_num": 4,
    "json": 1
}

async with httpx.AsyncClient() as client:
    response = await client.post(
        "https://wuji.cyphy.com/api/a2fgen",
        json=payload
    )
    result = response.json()

Response Example:

{
  "job_id": "12345",
  "status": "processing",
  "message": "Job created successfully"
}

3. Check Job Status

Endpoint: GET /api/job_status/{job_id}

Check the status of an image generation job.

Parameters:

ParameterTypeDescription
job_idstringJob ID from generate-images response

Request Example:

async with httpx.AsyncClient() as client:
    response = await client.get(
        f"https://wuji.cyphy.com/api/job_status/{job_id}"
    )
    result = response.json()

Response Example (Processing):

{
  "job_id": "12345",
  "status": "processing",
  "progress": 50
}

Response Example (Completed):

{
  "job_id": "12345",
  "status": "completed",
  "progress": 100,
  "results": [
    "https://cdn.example.com/result1.png",
    "https://cdn.example.com/result2.png",
    "https://cdn.example.com/result3.png",
    "https://cdn.example.com/result4.png"
  ]
}

Complete Workflow

Full Example: PDF to Character Images

import httpx
import asyncio

API_BASE = "https://wuji.cyphy.com/api"

async def complete_a2f_workflow(pdf_path, role_name, style="写实风格,真人风格"):
    """Complete A2F workflow: PDF → Features → Images → Results"""

    # Step 1: Extract features
    print("Step 1: Extracting features from PDF...")
    async with httpx.AsyncClient(timeout=60.0) as client:
        with open(pdf_path, "rb") as f:
            files = {"files[0]": (pdf_path, f, "application/pdf")}
            data = {
                "role": role_name,
                "loop_count": 2,
                "gen_method": "qwen"
            }
            response = await client.post(f"{API_BASE}/archiveData", data=data, files=files)
            features_result = response.json()

    # Extract features from response
    pdf_key = list(features_result.keys())[0]
    features = features_result[pdf_key]
    print(f"Extracted features: {features['role']}, {features['gender']}")

    # Step 2: Build prompt from features
    print("Step 2: Building prompt and generating images...")
    prompt_parts = [
        role_name,
        features.get("gender", [""])[0],
        features.get("age", [""])[0],
        "肖像画, 正脸, 全身照, 站立, 看着观众"
    ]

    # Add detailed features
    for key in ["ffeatures", "clothing", "hairstyle", "accessories"]:
        if key in features and features[key]:
            prompt_parts.extend(features[key])

    # Add style
    prompt_parts.append(style)

    text = ", ".join(filter(None, prompt_parts))

    # Generate images
    async with httpx.AsyncClient(timeout=60.0) as client:
        payload = {
            "text": text,
            "negative_text": "文本, 特写, 裁剪, 出框, 最差质量, 低质量, jpeg 伪影, pgl y, 重复, 病态, 残缺, 额外的手指, 变异的手, 画得不好的手, 画得不好的脸, 突变, 变形, 模糊, 脱水, 不良的解剖结构, 不良的比例, 额外的肢体, 克隆的脸, 毁容, 总体比例, 畸形的四肢, 缺失的手臂, 缺失的腿, 额外的手臂, 多余的腿, 融合的手指, 太多的手指, 长脖子, 水印, 印章",
            "gen_method": 5,
            "gen_size": 1,
            "img_num": 4,
            "json": 1
        }
        response = await client.post(f"{API_BASE}/a2fgen", json=payload)
        gen_result = response.json()

    job_id = gen_result.get("job_id") or gen_result.get("id")
    print(f"Generation started. Job ID: {job_id}")

    # Step 3: Poll for completion
    print("Step 3: Waiting for generation to complete...")
    async with httpx.AsyncClient(timeout=30.0) as client:
        while True:
            response = await client.get(f"{API_BASE}/job_status/{job_id}")
            status_result = response.json()

            status = status_result.get("status")
            progress = status_result.get("progress", 0)

            print(f"Status: {status} ({progress}%)")

            if status == "completed":
                print("\nGeneration complete!")
                results = status_result.get("results", [])
                for i, url in enumerate(results, 1):
                    print(f"Image {i}: {url}")
                return results
            elif status == "failed":
                raise Exception(f"Generation failed: {status_result.get('error')}")

            await asyncio.sleep(5)

# Usage
if __name__ == "__main__":
    results = asyncio.run(complete_a2f_workflow(
        "李清照诗词中的自我形象研究_侯淑婉.pdf",
        "李清照"
    ))

Feature Extraction

Understanding Feature Categories

The feature extraction returns multiple categories:

CategoryDescriptionExample Values
roleCharacter role type["主要人物"]
genderGender["女性"]
ageAge range["成人"]
ffeaturesFacial features["皮肤白皙", "面容清秀"]
hairstyleHair style["长发", "传统发型"]
headdressHead accessories[]
buildBody type["苗条"]
clothingClothing["古代服饰", "轻薄衣衫"]
footwearShoes[]
accessoriesAccessories["金钗", "玉饰"]
activityActivities["写诗", "游玩"]
backgroundBackground setting["宋代", "江南水乡"]
contextContext["诗词创作"]
vibeAtmosphere["婉约", "细腻"]
temperamentPersonality["敏感", "多情"]
languageLanguage style["文言文"]
voiceVoice quality["柔和"]
cusstyleCustom style["中国古典水墨画"]

Building Effective Prompts

Good Prompt Structure:

[角色名], [性别], [年龄], [基础描述], [细节特征], [服装配饰], [风格]

Example:

# Base
prompt = "李清照, 女性, 成人"

# Pose & framing
prompt += ", 肖像画, 正脸, 全身照, 站立, 看着观众"

# Features
prompt += ", 皮肤白皙, 面容清秀, 表情细腻"

# Clothing & accessories
prompt += ", 古代服饰, 轻薄衣衫, 色彩淡雅, 金钗, 玉饰"

# Style
prompt += ", 写实风格, 真人风格"

# Result:
# "李清照, 女性, 成人, 肖像画, 正脸, 全身照, 站立, 看着观众, 皮肤白皙, 面容清秀, 表情细腻, 古代服饰, 轻薄衣衫, 色彩淡雅, 金钗, 玉饰, 写实风格, 真人风格"

Image Generation

Generation Parameters

ParameterValue RangeRecommendedEffect
gen_method1-105Generation algorithm
gen_size1-51Output resolution
img_num1-104Number of images

Negative Prompts

Use negative prompts to control quality:

# Basic quality control
basic_negative = "低质量, 模糊, 变形, 水印"

# Detailed quality control
detailed_negative = "文本, 特写, 裁剪, 出框, 最差质量, 低质量, jpeg 伪影, pgl y, 重复, 病态, 残缺, 额外的手指, 变异的手, 画得不好的手, 画得不好的脸, 突变, 变形, 模糊, 脱水, 不良的解剖结构, 不良的比例, 额外的肢体, 克隆的脸, 毁容, 总体比例, 畸形的四肢, 缺失的手臂, 缺失的腿, 额外的手臂, 多余的腿, 融合的手指, 太多的手指, 长脖子, 水印, 印章"

Response Formats

Feature Extraction Response

{
  "filename.pdf": {
    "role": ["主要人物"],
    "gender": ["女性"],
    "age": ["成人"],
    "ffeatures": ["皮肤白皙", "面容清秀"],
    "hairstyle": ["长发"],
    "clothing": ["古代服饰"],
    "accessories": ["金钗"],
    "activity": ["写诗"],
    "background": ["宋代"],
    "vibe": ["婉约"],
    "temperament": ["敏感"],
    "cusstyle": ["中国古典水墨画"]
  }
}

Generation Response

{
  "job_id": "17171",
  "status": "processing",
  "message": "Job created successfully"
}

Status Response (Processing)

{
  "job_id": "17171",
  "status": "processing",
  "progress": 45,
  "message": "Generating images..."
}

Status Response (Completed)

{
  "job_id": "17171",
  "status": "completed",
  "progress": 100,
  "results": [
    "https://cdn.example.com/image1.png",
    "https://cdn.example.com/image2.png",
    "https://cdn.example.com/image3.png",
    "https://cdn.example.com/image4.png"
  ]
}

Examples

Example 1: Generate Li Qingzhao Character

import asyncio
import httpx

async def generate_li_qingzhao():
    API_BASE = "https://wuji.cyphy.com/api"

    # Extract features
    async with httpx.AsyncClient() as client:
        with open("李清照.pdf", "rb") as f:
            files = {"files[0]": ("李清照.pdf", f, "application/pdf")}
            data = {"role": "李清照", "loop_count": 2, "gen_method": "qwen", "openclaw": 1}
            response = await client.post(f"{API_BASE}/archiveData", data=data, files=files)
            features = response.json()

    # Generate images
    prompt = "李清照, 女性, 成人, 肖像画, 正脸, 全身照, 站立, 写诗, 古代服饰, 中国古典水墨画, 写实风格"
    payload = {
        "text": prompt,
        "negative_text": "低质量, 模糊",
        "gen_method": 5,
        "img_num": 4,
        "json": 1
    }

    async with httpx.AsyncClient() as client:
        response = await client.post(f"{API_BASE}/a2fgen", json=payload)
        job = response.json()

    print(f"Job ID: {job['job_id']}")
    return job['job_id']

Example 2: Batch Character Generation

async def batch_generate_characters(pdf_files):
    """Generate multiple characters from multiple PDFs"""

    jobs = []

    for pdf_file, role_name in pdf_files:
        # Extract features
        async with httpx.AsyncClient() as client:
            with open(pdf_file, "rb") as f:
                files = {"files[0]": (pdf_file, f, "application/pdf")}
                data = {"role": role_name, "loop_count": 2, "gen_method": "qwen"}
                response = await client.post("https://wuji.cyphy.com/api/archiveData", data=data, files=files)
                features = response.json()

        # Build prompt and generate
        prompt = f"{role_name}, 写实风格, 真人风格"
        payload = {"text": prompt, "img_num": 4, "json": 1}

        async with httpx.AsyncClient() as client:
            response = await client.post("https://wuji.cyphy.com/api/a2fgen", json=payload)
            job = response.json()
            jobs.append({"role": role_name, "job_id": job["job_id"]})

    return jobs

Example 3: Custom Style Generation

async def generate_with_custom_style(pdf_path, role_name, style):
    """Generate character with custom style"""

    # Extract features
    async with httpx.AsyncClient() as client:
        with open(pdf_path, "rb") as f:
            files = {"files[0]": (pdf_path, f, "application/pdf")}
            data = {"role": role_name, "loop_count": 2, "gen_method": "qwen"}
            response = await client.post("https://wuji.cyphy.com/api/archiveData", data=data, files=files)
            features = response.json()

    # Custom style prompts
    style_prompts = {
        "anime": "动漫风格, 二次元, 动画角色",
        "oil_painting": "油画风格, 古典油画, 艺术绘画",
        "watercolor": "水彩风格, 水彩画, 淡雅",
        "3d_render": "3D渲染, CGI, 3D角色, 立体感"
    }

    prompt = f"{role_name}, {style_prompts.get(style, '写实风格')}"
    payload = {"text": prompt, "img_num": 4, "json": 1}

    async with httpx.AsyncClient() as client:
        response = await client.post("https://wuji.cyphy.com/api/a2fgen", json=payload)
        return response.json()

Best Practices

1. PDF Preparation

  • Use high-quality PDFs with clear character descriptions
  • Include detailed physical descriptions
  • Provide historical context and setting
  • Mention clothing, accessories, and activities

2. Prompt Engineering

DO:

# Good: Specific and structured
prompt = "李清照, 女性, 成人, 肖像画, 正脸, 全身照, 古代服饰, 写实风格"

DON'T:

# Bad: Too vague
prompt = "一个女人"

3. Error Handling

async def safe_generate(pdf_path, role_name):
    try:
        # Extract features
        async with httpx.AsyncClient(timeout=60.0) as client:
            with open(pdf_path, "rb") as f:
                files = {"files[0]": (pdf_path, f, "application/pdf")}
                data = {"role": role_name, "loop_count": 2, "gen_method": "qwen", "openclaw": 1}
                response = await client.post("https://wuji.cyphy.com/api/archiveData", data=data, files=files)
                response.raise_for_status()
                features = response.json()

        # Generate images
        payload = {"text": f"{role_name}, 写实风格", "img_num": 4, "json": 1}
        response = await client.post("https://wuji.cyphy.com/api/a2fgen", json=payload)
        response.raise_for_status()
        return response.json()

    except httpx.HTTPStatusError as e:
        print(f"HTTP error: {e.response.status_code}")
        print(f"Response: {e.response.text}")
    except Exception as e:
        print(f"Error: {str(e)}")

4. Polling Strategy

async def poll_with_backoff(job_id, max_wait=300):
    """Poll job status with exponential backoff"""

    start_time = time.time()
    wait_time = 2

    while time.time() - start_time < max_wait:
        async with httpx.AsyncClient() as client:
            response = await client.get(f"https://wuji.cyphy.com/api/job_status/{job_id}")
            result = response.json()

            if result["status"] == "completed":
                return result
            elif result["status"] == "failed":
                raise Exception(f"Job failed: {result.get('error')}")

        await asyncio.sleep(wait_time)
        wait_time = min(wait_time * 2, 10)  # Max 10 seconds

    raise TimeoutError("Job timed out")

5. Feature Selection

Not all features are equally important. Prioritize:

# High priority features
priority_features = [
    "gender",      # Essential for character appearance
    "age",         # Affects overall look
    "ffeatures",   # Facial characteristics
    "clothing",    # Historical accuracy
    "cusstyle"     # Art style
]

# Lower priority features
optional_features = [
    "language",    # Less relevant for visual
    "voice",       # Audio only
    "context"      # Background info
]

Troubleshooting

Common Issues

Issue: Feature extraction returns empty features

  • Solution: Ensure PDF contains character descriptions. Try a different PDF.

Issue: Generated images don't match character

  • Solution: Add more specific features to prompt. Include clothing, accessories, and physical traits.

Issue: Job status stays "processing" too long

  • Solution: Jobs can take 1-5 minutes. Implement timeout handling (e.g., 5 minutes).

Issue: "401 Unauthorized" error

  • Solution: Check API endpoint and authentication if required.

Issue: Low quality images

  • Solution: Enhance negative prompts, increase gen_method value, or adjust gen_size.

Debug Mode

import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
httpx_logger = logging.getLogger("httpx")
httpx_logger.setLevel(logging.DEBUG)

Testing

async def test_a2f_endpoints():
    """Test A2F endpoints"""

    # Test 1: Feature extraction
    print("Testing feature extraction...")
    # Add test code here

    # Test 2: Image generation
    print("Testing image generation...")
    # Add test code here

    # Test 3: Job status
    print("Testing job status...")
    # Add test code here

    print("All tests passed!")

Resources

Changelog

Version 1.0.0 (2026-05-05)

  • Initial release
  • Feature extraction from PDFs
  • Image generation with custom prompts
  • Job status polling
  • Complete workflow implementation