SWIRLS_
Cookbook

Write a Blog Post

Research, draft, and polish a blog post using LLM nodes.

A form captures a title, brief, and tone. An HTTP node fetches research. Two LLM nodes draft and then edit the post.

form blog_request {
  label: "Blog Request"
  enabled: true
  schema: @json {
    {
      "type": "object",
      "required": ["title", "brief"],
      "properties": {
        "title": { "type": "string", "title": "Title" },
        "brief": { "type": "string", "title": "Brief or outline" },
        "tone": { "type": "string", "title": "Tone", "enum": ["professional", "casual", "technical"] }
      },
      "additionalProperties": false
    }
  }
}

graph write_blog {
  label: "Write Blog Post"
  description: "Research, draft, and edit a blog post"

  root {
    type: code
    label: "Normalize"
    inputSchema: @json {
      {
        "type": "object",
        "required": ["title", "brief"],
        "properties": {
          "title": { "type": "string" },
          "brief": { "type": "string" },
          "tone": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "required": ["title", "brief", "tone"],
        "properties": {
          "title": { "type": "string" },
          "brief": { "type": "string" },
          "tone": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    code: @ts {
      const { title, brief, tone } = context.nodes.root.input
      return {
        title: title.trim(),
        brief: brief.trim(),
        tone: (tone ?? "professional").trim(),
      }
    }
  }

  node research {
    type: http
    label: "Fetch research"
    method: "POST"
    url: @ts {
      return "https://api.tavily.com/search"
    }
    headers: {
      "Content-Type": "application/json"
    }
    body: @ts {
      return JSON.stringify({
        api_key: context.secrets.TAVILY_API_KEY,
        query: context.nodes.root.output.title,
        max_results: 5
      })
    }
    schema: @json {
      { "type": "object", "properties": { "results": { "type": "array" } } }
    }
  }

  node draft {
    type: ai
    label: "Draft post"
    kind: object
    model: "anthropic/claude-3.5-sonnet"
    prompt: @ts {
      const { title, brief, tone } = context.nodes.root.output
      const research = JSON.stringify(context.nodes.research?.output?.results ?? [])
      return `Write a blog post.

Title: ${title}
Brief: ${brief}
Tone: ${tone}
Research context: ${research}

Structure with an introduction, subheadings, and conclusion.
Output in Markdown. Target 800-1200 words.`
    }
    temperature: 0.7
    maxTokens: 4000
    schema: @json {
      {
        "type": "object",
        "required": ["content"],
        "properties": { "content": { "type": "string" } }
      }
    }
  }

  node edit {
    type: ai
    label: "Edit and polish"
    kind: object
    model: "anthropic/claude-3.5-sonnet"
    prompt: @ts {
      const draft = context.nodes.draft.output.content
      const tone = context.nodes.root.output.tone
      return `Edit this blog post for clarity, flow, and grammar.
Tighten verbose sentences. Ensure the tone is ${tone}.

Draft:
${draft}`
    }
    temperature: 0.3
    schema: @json {
      {
        "type": "object",
        "required": ["content"],
        "properties": { "content": { "type": "string" } }
      }
    }
  }

  flow {
    root -> research
    research -> draft
    draft -> edit
  }
}

trigger on_blog_request {
  form:blog_request -> write_blog
  enabled: true
}

How it works

  • The form collects a title, brief, and optional tone (defaults to professional).
  • The root node trims whitespace and fills in the default tone.
  • The research HTTP node queries Tavily and returns up to five results as context.
  • The draft AI node writes an 800-1200 word Markdown post using the title, brief, tone, and research.
  • The edit AI node polishes the draft for clarity, flow, and grammar at a lower temperature.
  • The trigger fires the graph on every form submission.

Run it

swirls login
swirls secret set TAVILY_API_KEY <your-key>
swirls deploy

Open the Portal, navigate to the form, and submit a request. Check the Executions view to inspect each node's output.

Customize

  • Add a review step between draft and edit to collect human feedback before the editing pass.
  • Swap Tavily for another search API by updating the research node URL and body.
  • Add an HTTP node after edit to publish the finished post to your CMS.
  • Add persistence to build a library of generated posts.
  • Add a schedule trigger for recurring content generation.

On this page