Genace

LangChain image generation: preserve job IDs when submission fails

·

For LangChain image generation, return a useful reference as soon as the API accepts work, and retain any known local ID when submission fails. Keeping a tool invocation open until rendering finishes can delay the agent’s next decision. Use two custom tools: one creates an image job, and the other checks a known job. This is an application wrapper built with LangChain’s tool decorator, not a preinstalled Genace integration.

1. Define narrow tool inputs

Install langchain and requests in your Python environment. Set GENACE_API_KEY before starting the agent process. Keep the credential in the closure or process environment so the model never needs to provide it as an argument.

import os
import re
import requests
from langchain.tools import tool

base = "https://genace.ai/api/v1"

def call(path: str, payload=None):
    response = requests.request(
        "POST" if payload is not None else "GET", f"{base}{path}",
        headers={"Authorization": f"Bearer {os.environ['GENACE_API_KEY']}"},
        json=payload, timeout=45,
    )
    if payload is not None and response.status_code == 502:
        try:
            body = response.json()
        except ValueError:
            body = {}
        error = body.get("error") if isinstance(body, dict) else None
        if isinstance(error, dict):
            outcomes = {
                "UPSTREAM_SUBMISSION_UNCERTAIN": "uncertain",
                "UPSTREAM_SUBMISSION_FAILED": "rejected",
            }
            outcome = outcomes.get(error.get("code"))
            job_id = error.get("job_id")
            if outcome and isinstance(job_id, str) and job_id.strip():
                return {"submission_outcome": outcome, "job_id": job_id,
                        "error_code": error["code"]}
    response.raise_for_status()
    return response.json()

@tool
def submit_image(prompt: str) -> dict:
    """Create one paid image job after the user's requested action is authorized."""
    prompt = prompt.strip()
    if not prompt or len(prompt) > 2000:
        raise ValueError("Prompt must contain 1-2000 characters")
    accepted = call("/images/generations", {
        "model": "nano-banana", "prompt": prompt, "n": 1,
    })
    if "submission_outcome" in accepted:
        return accepted
    return {"job_id": accepted["job_id"], "state": accepted["state"],
            "credits_held": accepted["credits_held"]}

@tool
def inspect_generation(job_id: str) -> dict:
    """Inspect an existing job once; this tool does not submit another image."""
    if not re.fullmatch(r"[A-Za-z0-9_-]{1,100}", job_id):
        raise ValueError("Invalid job identifier")
    job = call(f"/jobs/{job_id}?wait=0")
    return {"job_id": job["id"], "state": job["state"],
            "outputs": job.get("outputs"), "error": job.get("error")}

tools = [submit_image, inspect_generation]

The prompt cap and identifier pattern are wrapper policies. They are not a promise about every possible future Genace identifier. Keep them under your control and adjust only after reviewing the actual API response shape.

The helper reads two recognized submission-error bodies before calling raise_for_status. Raising first would discard the local ID from the normal tool result, even though Genace provided it for recovery. Only the safe code and identifier are returned, not raw error messages, headers, or credentials. Unrecognized errors still raise; a local ID is not invented when the response lacks one.

submission_outcome is an application-wrapper label, not a Genace job state. An accepted 202 still returns the usual ID, state, and held credits. A rejected submission does not prove its refund settlement is already complete. An uncertain submission retains its credit hold and must not trigger another POST or cancellation as a refund shortcut. Inspect the saved local ID and consult submission uncertainty before acting.

2. Attach policy to the agent workflow

Pass the tools list to the agent construction code appropriate for your selected LangChain version and model provider. Your system instructions should distinguish the paid creation tool from the observation tool. A description helps the model choose correctly, but enforce real user authorization and spending allowances in application code before enabling submission.

Store the accepted ID outside conversational memory. If a chat is truncated or the agent restarts, a database operation record can still recover the result. Tool output should contain the identifier, model choice, and accounting fields your application needs without returning request headers or the API key.

3. Avoid agent-driven busy polling

An immediate status tool is useful for a user asking whether a known image is ready. It should not cause the agent to call itself repeatedly in a tight loop. Use a scheduler or worker to collect completion, or let the next user action request a fresh status check. No customer callback URL is part of the submission contract shown here.

Check terminal state before interpreting outputs. Succeeded may provide image URLs; failed and canceled are complete lifecycle outcomes too. Pending jobs can have null outputs, and a job failure may arrive with a successful status HTTP response.

Common mistakes and verification

Do not let automatic tool retries repeat an uncertain POST. The API route does not expose request idempotency support. Distinguish a tool transport exception from a failed remote job and preserve the accepted identifier whenever one exists.

If a POST loses its response entirely, this helper raises a transport exception because there is no body to inspect. Disable automatic submission-tool retries and preserve your operation context for investigation rather than treating the exception as a rejection. GET exceptions are observation failures: the original input ID remains your recovery reference. In particular, do not interpret every generic 502 as a recognized Genace submission outcome.

Verify the tool schemas contain no credential field, a submission returns promptly with a job ID, and a pending fixture does not trigger endless agent turns. Verify failed fixtures expose the error object and that your own policy can deny a paid tool before any HTTP request.

Before real generation, mock HTTP 202, both recognized 502 codes with local IDs, a generic 502, a recognized error without an ID, and a disconnected POST. Assert one HTTP call per invocation, preservation of known IDs, no raw error details in structured results, and no automatic resubmission. Exercise all five status states with null outputs. Offline Python tests of these functions do not replace testing tool registration on your installed LangChain version.

See LangChain’s tool documentation for decorator behavior, nonblocking generation patterns for orchestration, and LangGraph state handling for a graph-based alternative.

Continue with agents and automation

Models covered on this page

Where these facts come from