DEV Community

shashank ms
shashank ms

Posted on

Using LLMs for Content Generation

I built a small content engine for my own blog workflow that turns a single topic into a structured outline, a social thread, and a newsletter draft. Instead of context-switching between three different tools, I send one prompt to an LLM and get back formatted assets I can edit immediately. In this tutorial, I will walk you through recreating it on Oxlo.ai.

What you'll need

Step 1: Configure the Oxlo.ai client

Set up the OpenAI-compatible client pointing to Oxlo.ai. I use llama-3.3-70b because it handles long-form instructions and formatting reliably.

from openai import OpenAI

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

MODEL = "llama-3.3-70b"

Step 2: Define the system prompt

The system prompt is the only product management we need. It tells the model to act as a content strategist and enforce a strict output format with clear separators.

SYSTEM_PROMPT = """You are a senior content strategist.
When given a topic, produce three sections in this exact order:

1. BLOG OUTLINE
   - Title
   - 3 to 5 H2 headings with one bullet each explaining the angle.

2. SOCIAL THREAD
   - 3 to 5 short posts suitable for Twitter/X.
   - Each post must be under 280 characters.
   - Number them sequentially.

3. NEWSLETTER
   - A subject line.
   - An opening paragraph under 100 words.
   - A "Read more" call to action.

Use plain text. Separate each section with exactly three hyphens on their own line: ---"""

Step 3: Create the generation function

I wrap the API call in a small function so I can reuse it across scripts. The function sends the topic as a user message and returns the raw text.

def generate_content_pack(topic: str) -> str:
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": f"Topic: {topic}"},
        ],
        temperature=0.7,
        max_tokens=2048,
    )
    return response.choices[0].message.content

Step 4: Parse the response into assets

The model returns one long string, so I split it on the --- separator to isolate each asset. This keeps the pipeline simple and avoids extra LLM calls.

def parse_assets(raw_text: str) -> dict:
    parts = [p.strip() for p in raw_text.split("---") if p.strip()]
    keys = ["outline", "social", "newsletter"]
    return {keys[i]: parts[i] for i in range(min(len(keys), len(parts)))}

Step 5: Add a batch runner

For my own use, I usually have a backlog of topics. I added a small runner that loops over a list, calls the API for each, and prints the results with clear headers.

def run_pipeline(topics: list[str]):
    for topic in topics:
        print(f"\n{'='*60}")
        print(f"TOPIC: {topic}")
        print(f"{'='*60}")
        
        raw = generate_content_pack(topic)
        assets = parse_assets(raw)
        
        for name, body in assets.items():
            print(f"\n{name.upper()}\n{'-'*40}")
            print(body)

Run it

I keep my topics in a simple list and call run_pipeline. Because Oxlo.ai uses request-based pricing, the cost is the same per topic regardless of how long the generated drafts are. That makes this pipeline predictable when I scale it up. Here is the entry point and example output for the topic "agentic AI workflows".

if __name__ == "__main__":
    topics = [
        "agentic AI workflows",
    ]
    run_pipeline(topics)

Example output:

============================================================
TOPIC: agentic AI workflows
============================================================

OUTLINE
----------------------------------------
Title: Building Agentic AI Workflows That Actually Ship

1. Defining the Agent Boundary
   - When to use a single LLM call versus a multi-step agent.

2. Tool Design
   - How to build robust functions the model can invoke reliably.

3. Error Handling
   - Why your agent needs a retry and fallback strategy from day one.

4. Observability
   - Logging intermediate steps so you can debug production failures.

5. Deployment Patterns
   - Moving from a Jupyter notebook to a stateful service.

---
SOCIAL
----------------------------------------
1/ Agents are not just chatbots with extra steps. The difference is state: an agent remembers what it tried, learns from tool output, and decides what to do next.

2/ Start with one tool. If your agent cannot reliably call a single function, adding five more will not help. Nail the schema first.

3/ The hardest part of agentic workflows is not the LLM. It is the failure mode. Build your retry logic before you build your prompt.

4/ Observability is not optional. You need to see the chain of thought, tool calls, and errors in one trace. Otherwise you are flying blind.

5/ Ship a simple agent that works end to end, then add complexity. A state machine with two nodes in production beats a twenty-node graph on localhost.

---
NEWSLETTER
----------------------------------------
Subject: The Real Work of Agentic AI

Agentic AI is the buzzword of the quarter, but shipping it is harder than the demos suggest. This week we look at the boundary between a script and an agent, and why tool design matters more than model choice.

Read more: https://yourblog.com/agentic-ai-workflows

Wrap-up

Two concrete ways to extend this. First, switch the parser to JSON mode by asking the model to return a JSON object and setting response_format={"type": "json_object"} in the Oxlo.ai call. That removes the string splitting entirely. Second, add a second pass with qwen-3-32b to translate the social thread into other languages if you run a multilingual blog. Both changes cost exactly one extra request per topic on Oxlo.ai, so the bill stays easy to reason about.

Top comments (0)