DEV Community

shashank ms
shashank ms

Posted on

LLM Models for Text Generation and Creative Writing

I am building a creative writing assistant that turns a one-sentence premise into a polished short story. It drafts prose, critiques its own output, and revises in a second pass. Writers and developers who need programmatic fiction generation can use this pipeline directly or drop it into a larger application.

What you'll need

  • Python 3.10 or newer
  • An Oxlo.ai API key from https://portal.oxlo.ai
  • The OpenAI SDK installed with pip install openai

Step 1: Configure the client

First, I set up the OpenAI SDK to point at Oxlo.ai and verify the connection with a lightweight ping. I use llama-3.3-70b because it handles general instruction following reliably.

import os
from openai import OpenAI

OXLO_API_KEY = os.getenv("OXLO_API_KEY", "YOUR_OXLO_API_KEY")

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=OXLO_API_KEY)

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "user", "content": "Reply with exactly: connection ok"}
    ],
)
assert "connection ok" in response.choices[0].message.content.lower()
print("Oxlo.ai client ready")

Step 2: Define the system prompt

The system prompt locks the model into a specific fiction writing style and output format. Keeping this in a dedicated constant makes it easy to tweak tone or pacing rules later.

SYSTEM_PROMPT = """You are a fiction writing engine.
Rules:
- Write in the third person.
- Use vivid sensory details.
- Keep paragraphs under 120 words.
- Do not use bullet points or headers inside the story.
- Output only the story text, no preamble or postscript.
"""

Step 3: Generate a rough draft

I wrap the draft generation in a function that accepts a premise, genre, tone, and target word count. The function injects those parameters into the user message and returns the raw story text.

def generate_draft(premise: str, genre: str, tone: str, word_count: int) -> str:
    user_msg = (
        f"Write a {genre} story in a {tone} tone.\n"
        f"Premise: {premise}\n"
        f"Target length: roughly {word_count} words."
    )

    resp = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_msg},
        ],
        temperature=0.9,
        max_tokens=2048,
    )
    return resp.choices[0].message.content.strip()

Step 4: Critique the draft

A second pass with qwen-3-32b analyzes the draft for pacing, clichés, and sensory specificity. Treating critique as a separate call keeps concerns isolated and makes it easy to swap in a different reasoning model later.

CRITIQUE_PROMPT = """You are a developmental editor.
Analyze the provided story draft and return exactly three bullets:
1. Pacing: note any sections that drag or rush.
2. Imagery: highlight one strong image and one missing sensory channel.
3. Dialogue: flag any exposition disguised as speech.
Be concise. Do not rewrite the story.
"""

def critique_draft(draft: str) -> str:
    resp = client.chat.completions.create(
        model="qwen-3-32b",
        messages=[
            {"role": "system", "content": CRITIQUE_PROMPT},
            {"role": "user", "content": draft},
        ],
        temperature=0.3,
        max_tokens=1024,
    )
    return resp.choices[0].message.content.strip()

Step 5: Revise with feedback

Now I feed both the original draft and the critique back to kimi-k2.6. This model handles long context and agentic tasks well, so it can follow multiple constraints at once without losing the narrative thread.

REVISION_PROMPT = """You are a fiction writing engine.
Rewrite the provided story draft using the attached editor critique.
Preserve the original premise, genre, and tone.
Keep paragraphs under 120 words.
Output only the revised story text.
"""

def revise_story(draft: str, critique: str, premise: str, genre: str, tone: str) -> str:
    user_msg = (
        f"Original premise: {premise}\n"
        f"Genre: {genre} | Tone: {tone}\n\n"
        f"--- DRAFT ---\n{draft}\n\n"
        f"--- CRITIQUE ---\n{critique}"
    )

    resp = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": REVISION_PROMPT},
            {"role": "user", "content": user_msg},
        ],
        temperature=0.8,
        max_tokens=2048,
    )
    return resp.choices[0].message.content.strip()

Step 6: Wire the pipeline

The last block ties the three stages together. Running this script executes the full draft, critique, and revision cycle in one go.

def main():
    premise = "A programmer finds a bug that only appears when the moon is full"
    genre = "sci-fi"
    tone = "noir"
    target_words = 400

    print("Generating draft...")
    draft = generate_draft(premise, genre, tone, target_words)
    print("\n--- DRAFT ---\n")
    print(draft)

    print("\nGenerating critique...")
    critique = critique_draft(draft)
    print("\n--- CRITIQUE ---\n")
    print(critique)

    print("\nRevising...")
    final = revise_story(draft, critique, premise, genre, tone)
    print("\n--- FINAL STORY ---\n")
    print(final)

if __name__ == "__main__":
    main()

Run it

Save the script as creative_writer.py, export your key, and run it.

export OXLO_API_KEY="sk-oxlo.ai-..."
python creative_writer.py

The draft comes back quickly because Oxlo.ai serves popular models without cold starts. You will see output similar to this, condensed for brevity:

Generating draft...

--- DRAFT ---

The cursor blinked in time with Janelle's heartbeat...

Generating critique...

--- CRITIQUE ---

1. Pacing: The opening lab scene establishes mood well, but the middle transition from discovery to confrontation feels compressed.
2. Imagery: Strong visual of the blue crash dump. Missing tactile details, especially temperature or texture.
3. Dialogue: The line where the senior engineer explains the lunar cycle reads as exposition.

Revising...

--- FINAL STORY ---

The cursor blinked in time with Janelle's heartbeat, each flash casting a pale glow across the frost that had begun to form inside the server room...

Next steps

Swap llama-3.3-70b for deepseek-v3.2 if you want tighter reasoning in the draft stage, or add Oxlo.ai JSON mode to extract structured outlines before generating prose. If you turn this into a service, the flat per-request pricing means your cost stays predictable even when users submit long world-building prompts.

Top comments (0)