If you generate product scenes regularly, you have probably noticed that your best prompts share a structure, and your worst ones are the ones where you improvised. A prompt that names the surface, the background, the viewing angle and the space around the product tends to work. A prompt that says "nice lifestyle shot of a mug" tends not to.
The FAQ on the Seedream 5.5 text-to-image page spells out that structure: describe the subject, action or pose, setting, framing, light and style, and keep the instructions consistent. For a product scene, it adds: name the surface, background, viewing angle and the space you need around the product.
That reads a lot like a schema. So this post does the obvious developer thing and turns it into a small Python helper that builds prompts from fields, flags missing ones, and keeps a series consistent.
Text-to-image, briefly
On the page, text-to-image is described as starting with words: describe the subject, setting, composition and style, choose a canvas ratio, review the estimated credits, and develop the idea without uploading a reference image. Image-to-image is the other mode, where a reference is added alongside the prompt.
Since text-to-image has no reference to lean on, everything the model needs has to be in the text. That is exactly why a structured prompt helps most here.
The fields
From the FAQ, the fields are:
| Field | Example |
|---|---|
| subject | a matte white ceramic mug |
| action_or_pose | standing upright, handle to the right |
| setting | on a light oak table by a window |
| surface | light oak table top |
| background | softly blurred kitchen shelves |
| angle | slightly above eye level |
| space | empty area on the left for a headline |
| light | morning daylight from the left |
| style | clean product photography |
The last three product fields, surface, angle and space, are the ones most often left out, and the ones the page singles out for product scenes.
The builder
from dataclasses import dataclass, asdict
REQUIRED = ["subject", "setting", "framing", "light", "style"]
PRODUCT = ["surface", "background", "angle", "space"]
@dataclass
class Prompt:
subject: str = ""
action_or_pose: str = ""
setting: str = ""
framing: str = ""
light: str = ""
style: str = ""
surface: str = ""
background: str = ""
angle: str = ""
space: str = ""
product: bool = False
def missing(self):
need = REQUIRED + (PRODUCT if self.product else [])
return [k for k in need if not getattr(self, k).strip()]
def render(self):
parts = [self.subject, self.action_or_pose, self.setting,
self.surface and f"surface: {self.surface}",
self.background and f"background: {self.background}",
self.framing, self.angle and f"viewed {self.angle}",
self.space, self.light, self.style]
return ". ".join(p for p in parts if p) + "."
p = Prompt(
subject="A matte white ceramic mug",
action_or_pose="standing upright, handle to the right",
setting="on a light oak table by a window",
surface="light oak",
background="softly blurred kitchen shelves",
framing="medium close-up, mug in the right third",
angle="slightly above eye level",
space="empty area on the left for a headline",
light="morning daylight from the left",
style="clean product photography",
product=True,
)
print(p.missing()) # []
print(p.render())
Running missing() before you paste a prompt is a cheap way to catch the "nice lifestyle shot" problem: if it returns ['surface', 'angle', 'space'], you know what to add.
Keeping a series consistent
The FAQ's advice to keep instructions consistent matters most for a series: five product shots for one listing, or one scene in several colours. With a dataclass, consistency is a copy with one field changed:
from dataclasses import replace
variants = [replace(p, subject=f"A matte {c} ceramic mug")
for c in ["white", "sage green", "charcoal"]]
for v in variants:
print(v.render())
Only the subject changes; surface, light, angle and style stay identical. When you compare results, any difference comes from the one field you changed.
Why not just a template string?
A plain f-string template would work for one prompt. The dataclass earns its place in three situations.
First, validation. A template happily renders an empty field as an empty gap, and the prompt still looks fine at a glance. missing() makes the gap explicit before you spend credits on it.
Second, variants. replace() produces a new prompt with exactly one field changed, which is the property you want when comparing results. With templates, it is easy to edit two things by accident.
Third, storage. asdict(p) gives you a dictionary you can write to JSON next to the generated image. Months later, you can see exactly which surface, angle and light produced the shot you liked, and rebuild it.
If you work in a team, the same file becomes a shared vocabulary: everyone fills the same fields, and reviews can point at a field ("the angle is too high") instead of a sentence.
Choosing the canvas
The FAQ also gives a simple rule for aspect ratio: square for product tiles and profile artwork, portrait for mobile posts and posters, landscape for banners, presentations and wider scenes. The space field and the ratio go together: a landscape banner needs the empty headline area described explicitly, or the model may fill it.
Available controls and credit estimates depend on the selected model, so check them in the workspace before submitting.
After generation
The page notes that you can open a completed image to preview or download it, save it to favourites, or use it again as a reference with a revised prompt. Your creations and task status stay together in the image workspace. If a result is close but not right, change one field and regenerate, rather than rewriting the whole prompt.
Checklist
- all required fields filled; for product scenes, surface, background, angle and space too;
-
missing()returns an empty list; - series variants change exactly one field;
- the canvas ratio matches the destination and the
spacefield; - model controls and credit estimate checked before submitting.
Takeaway
Seedream 5.5's own FAQ already describes a good prompt structure. Writing it down as fields, even in a twenty-line dataclass, makes it hard to skip the parts that matter, and makes series consistent by default.




Top comments (0)