Metadata-Version: 2.5
Name: loomic-design-tool
Version: 0.1.0
Summary: A portable, approval-gated design prompt and image-generation workflow for Python agents.
Project-URL: Homepage, https://github.com/Liu-De-Long/loomic-design-tool
Project-URL: Repository, https://github.com/Liu-De-Long/loomic-design-tool
Project-URL: Issues, https://github.com/Liu-De-Long/loomic-design-tool/issues
Project-URL: Changelog, https://github.com/Liu-De-Long/loomic-design-tool/blob/main/CHANGELOG.md
Author: Liu-De-Long
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
License-File: THIRD_PARTY_NOTICES.md
Keywords: agent,design,image-generation,langgraph,prompt-compiler
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: aiosqlite>=0.20
Requires-Dist: httpx>=0.27
Requires-Dist: pillow>=10
Requires-Dist: pydantic<3,>=2.8
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: hatchling>=1.27; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Description-Content-Type: text/markdown

# loomic-design-tool

`loomic-design-tool` is a portable Python workflow for turning a host-owned design request into a structured prompt, an editable draft, an explicitly approved image-generation job, and durable image artifacts.

It exposes one model-visible tool, `design_workflow`. Business inputs such as the user request, selected image IDs, task, and output settings are injected by the host through a `request_provider`; the model cannot invent them in the tool call.

## What is included

- Nine built-in design task definitions and a replaceable `TaskRegistry`
- Structured prompt candidate schema, one schema-repair attempt, deterministic rendering, fingerprints, provenance, and cache
- Image inspection with project-scoped, pixel-sensitive caching
- Editable drafts and explicit approval records
- Atomic approval claim and idempotent request/job recovery
- One non-retried generation request with partial-result preservation
- SQLite WAL workflow storage and local image Artifact storage
- Separate OpenAI-compatible prompt, vision, and image endpoints
- Framework-neutral callbacks plus a LangGraph interrupt/resume adapter

Loomic's canvas UI, WebSocket protocol, application database, and route layer are intentionally not included.

## Install

```bash
pip install loomic-design-tool
```

For a LangGraph agent Tool:

```bash
pip install "loomic-design-tool[langgraph]"
```

Python 3.11, 3.12, and 3.13 are supported.

## Framework-neutral usage

```python
import asyncio
from uuid import uuid4

from loomic_design_tool import (
    CallbackInteractionAdapter,
    DesignRuntime,
    DesignToolConfig,
)


async def interact(kind, payload):
    if kind == "clarification":
        # Present payload["questionSet"] to a real user and return their answer.
        return {
            "action": "answer",
            "decisionId": f"answer_{uuid4().hex}",
            "answers": {"targetStyle": "option_1"},
        }

    # Present payload["draft"] to a real user. Never replace this with silent approval.
    draft = payload["draft"]
    return {
        "action": "approve",
        "decisionId": f"approval_{uuid4().hex}",
        "approvedBy": "user@example.com",
        "draftRevision": draft["revision"],
    }


async def main():
    config = DesignToolConfig.from_env()
    runtime = DesignRuntime(config)
    await runtime.initialize()
    source = await runtime.artifact_store.import_file(
        "room.png", project_id="demo", title="room.png"
    )
    job = await runtime.run(
        {
            "requestId": f"request_{uuid4().hex}",
            "projectId": "demo",
            "userRequest": "把这个空间改成现代极简风，保持结构和视角",
            "taskId": "style_transfer",
            "inputs": [{"artifactId": source.artifact_id, "role": "editBase"}],
            "parameters": {"targetStyle": "现代极简"},
            "outputSpec": {"count": 1, "aspectRatio": "source"},
        },
        CallbackInteractionAdapter(interact),
    )
    print(job.model_dump(by_alias=True))


asyncio.run(main())
```

Without an interaction adapter, `runtime.start()` or `runtime.run()` stops at `needs_input` or `needs_approval`. It never calls the billable image provider merely because no UI is available. A host may later call `runtime.resume(job_id, decision)`.

## One LangGraph Tool

```python
from loomic_design_tool.adapters import LangGraphInteractionAdapter
from loomic_design_tool.tools import create_design_workflow_tool

tool = create_design_workflow_tool(
    runtime,
    request_provider=current_design_request,
    interaction_adapter=LangGraphInteractionAdapter(),
    name="design_workflow",
)
```

The Tool schema contains only an injected tool-call ID. LangGraph pauses the same Tool invocation for clarification or approval and resumes it with an `ApprovalDecision` payload. See [`examples/langgraph_tool.py`](examples/langgraph_tool.py).

## Configuration

All standalone environment variables use the `LOOMIC_DESIGN_` prefix. Prompt, vision, and image services may use different endpoints and credentials:

```text
LOOMIC_DESIGN_DATABASE_PATH=.loomic-design/design.sqlite3
LOOMIC_DESIGN_ARTIFACT_DIR=.loomic-design/artifacts
LOOMIC_DESIGN_PROMPT_BASE_URL=https://api.example.com/v1
LOOMIC_DESIGN_PROMPT_API_KEY=...
LOOMIC_DESIGN_PROMPT_MODEL=gpt-4.1-mini
LOOMIC_DESIGN_VISION_BASE_URL=https://api.example.com/v1
LOOMIC_DESIGN_VISION_API_KEY=...
LOOMIC_DESIGN_VISION_MODEL=gpt-4.1-mini
LOOMIC_DESIGN_IMAGE_BASE_URL=https://api.example.com/v1
LOOMIC_DESIGN_IMAGE_API_KEY=...
LOOMIC_DESIGN_IMAGE_MODEL=gpt-image-1
LOOMIC_DESIGN_IMAGE_QUALITY=low
LOOMIC_DESIGN_TIMEOUT_SECONDS=300
```

Keys use Pydantic `SecretStr`. They are used only in Authorization headers and are not written to logs, drafts, jobs, artifacts, or SQLite.

## Adapting storage and providers

Implement the protocols in `loomic_design_tool.protocols` and pass them to `DesignRuntime`:

```python
runtime = DesignRuntime(
    config,
    provider=my_provider,
    store=my_workflow_store,
    artifact_store=my_artifact_store,
    task_registry=my_task_registry,
)
```

Provider methods must be single-attempt. In particular, `generate()` must not perform a hidden retry or fall back to a mock image. The runtime treats a timeout or disconnect as billing-uncertain and does not automatically resubmit.

## Safety invariants

- At most four input images and one `editBase`
- One or two generated outputs per approved job
- Image URLs must be public HTTPS endpoints on port 443; redirects and private IPs are rejected
- No image-generation call without an approval record containing the draft revision and human identity
- Approval is atomically claimed before generation
- A restarted `queued` or `running` job becomes `interrupted`, never silently retried
- Each saved output is persisted before the next output; partial results remain available

See [SECURITY.md](SECURITY.md) and [MIGRATION.md](MIGRATION.md) for host-integration guidance.

## Development

```bash
python -m pip install -e ".[langgraph,dev]"
ruff format --check .
ruff check .
pytest
python -m build
```

## License

Apache License 2.0. See [LICENSE](LICENSE), [NOTICE](NOTICE), and
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
