Skip to main content

Human-readable LLM conversation workflows

Project description

Narrative Flow

Human-readable LLM conversation workflows that can be automated with Python.

Overview

Narrative Flow lets you define multi-turn LLM conversations in a simple Markdown format, then run them programmatically. This is useful when you've developed a workflow interactively (in ChatGPT, Claude, Google AI Studio, etc.) and want to automate it.

Key features:

  • 📝 Human-readable format - Workflows are Markdown files with YAML frontmatter
  • 🔄 Multi-turn conversations - Build up context across multiple messages
  • 📤 Variable substitution - Insert input values into prompts
  • 🎯 Extraction - Pull specific values from responses using a separate LLM call
  • 📊 Execution logs - Full conversation history saved as Markdown
  • 🚀 Release automation - Versioning and changelogs managed with release-please on main via promotion PRs and merge-commit back-merges for develop

Installation

pip install narrative-flow

Or with uv:

uv add narrative-flow

Or install from source:

git clone https://github.com/hpowers/narrative-flow
cd narrative-flow
uv sync

Quick Start

1. Set your OpenRouter API key

export OPENROUTER_API_KEY="your-api-key"

2. Create a workflow file

Create my_workflow.workflow.md:

---
name: greeting_workflow
description: A simple example workflow

models:
  conversation: openai/gpt-4o
  extraction: openai/gpt-4o-mini

inputs:
  - name: topic
    description: The topic to discuss

outputs:
  - name: summary
    description: A summary of the discussion
    type: string
---

## Initial Question

Tell me three interesting facts about {{ topic }}.

## Follow Up

Which of those facts do you think is most surprising to most people, and why?

## Extract: summary

Summarize the most surprising fact in one sentence.

3. Run your workflow

From Python:

from narrative_flow import parse_workflow, execute_workflow, save_log

# Parse the workflow
workflow = parse_workflow("my_workflow.workflow.md")

# Execute with inputs
result = execute_workflow(workflow, {"topic": "octopuses"})

# Check results
if result.success:
    print(result.outputs["summary"])
else:
    print(f"Error: {result.error}")

# Save the execution log
save_log(result, output_dir="logs")

From command line:

# Run with inline inputs
narrative-flow run my_workflow.workflow.md --input topic="octopuses"

# Run with inputs from JSON file
narrative-flow run my_workflow.workflow.md --inputs-file inputs.json

# Validate a workflow without running it
narrative-flow validate my_workflow.workflow.md

Workflow File Format

Frontmatter

---
name: workflow_name # Required: identifier
description: What it does # Optional

models:
  conversation: model/id # Required: for main conversation
  extraction: model/id # Required: for extracting values

retries: 3 # Optional: retry count (default: 3)

inputs:
  - name: var_name # Required
    description: What it is # Optional
    required: true # Optional (default: true)
    default: fallback # Optional

outputs:
  - name: var_name # Must match an Extract step
    description: What it is # Optional
    type: string # Optional: string | string_list (default: string)
---

Message Steps

Any ## heading that doesn't start with Extract: is a message step:

## Step Name

Your message content here. Use {{ variable }} for substitution.

You can use **any** Markdown formatting—it gets sent to the model as-is.

You can also make roles explicit:

## User

Ask the model a question.

## Assistant

Provide a few-shot assistant response.

## User and ## Assistant steps are treated as explicit roles. Other non-Extract: headings are treated as user messages for backward compatibility.

Extraction Steps

## Extract: variable_name extracts a value from the last assistant response:

## Extract: result_var

Natural language instruction for what to extract.
Return only the extracted value, no explanation.

The extracted value is stored and can be used in subsequent steps as {{ result_var }}.

Output Types

Outputs are typed so extraction is deterministic (the extractor enforces JSON formatting automatically):

  • string -> extraction must return a JSON string (e.g., "value")
  • string_list -> extraction must return a JSON array of strings (e.g., ["a", "b"])

Template Strictness

Undefined {{ variables }} raise an error to fail fast. Missing required inputs and undefined variables result in a failed WorkflowResult with a clear error message.

API Reference

parse_workflow(source: str | Path) -> WorkflowDefinition

Parse a workflow from a file path or string content.

execute_workflow(workflow, inputs, api_key=None) -> WorkflowResult

Execute a workflow with the given inputs.

  • workflow: A WorkflowDefinition from parse_workflow()
  • inputs: Dict of input variable values
  • api_key: Optional OpenRouter API key (defaults to OPENROUTER_API_KEY env var)

This function returns a WorkflowResult on both success and failure. Check result.success and result.error instead of relying on exceptions.

save_log(result, output_dir=".", filename=None) -> Path

Save execution results as a Markdown log file.

generate_log(result) -> str

Generate log content as a string without saving.

WorkflowResult

The result object contains:

result.success           # bool: whether execution succeeded
result.error             # str | None: error message if failed
result.outputs           # dict: extracted output values (str or list[str])
result.inputs            # dict: input values used
result.conversation_history  # list[Message]: full conversation
result.step_results      # list[StepResult]: per-step details

OpenRouter Models

This library uses OpenRouter to access various LLMs. Some popular model IDs:

  • openai/gpt-4o - GPT-4o
  • openai/gpt-4o-mini - GPT-4o Mini
  • anthropic/claude-sonnet-4.5 - Claude 4.5 Sonnet
  • google/gemini-2.5-pro - Gemini 2.5 Pro
  • google/gemini-2.5-flash - Gemini 2.5 Flash

See OpenRouter Models for the full list.

Examples

See the examples/ directory for complete workflow examples:

Example Description Inputs
topic_explainer.workflow.md Explore any topic and extract key insights topic
blog_post_generator.workflow.md Full blog post with iterative refinement—shows mid-conversation extraction subject, audience, tone
key_takeaways_list.workflow.md Extract a list of concise takeaways topic
youtube_short_titler.workflow.md Generate titles/descriptions using MrBeast's principles short_transcript, episode_transcript
few_shot_assistant.workflow.md Demonstrate explicit User/Assistant steps with few-shot prompting product
# Try the simple example
narrative-flow run examples/topic_explainer.workflow.md --input topic="black holes"

# Try the advanced example
narrative-flow run examples/blog_post_generator.workflow.md \
  --input subject="productivity for developers" \
  --input audience="software engineers"

Contributing and Release Flow

For the short, practical guide to feature branches, squash merges, promotion PRs, and release-please, see CONTRIBUTING.md.

Development

# Install dev dependencies
uv sync --extra dev

# Run tests
uv run pytest

# Run linting
pre-commit run --all-files

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

narrative_flow-1.0.0.tar.gz (38.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

narrative_flow-1.0.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

Details for the file narrative_flow-1.0.0.tar.gz.

File metadata

  • Download URL: narrative_flow-1.0.0.tar.gz
  • Upload date:
  • Size: 38.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for narrative_flow-1.0.0.tar.gz
Algorithm Hash digest
SHA256 23e474381a0f31803ef44cba94a3809771fb2e22d526d954c8392dad7137f815
MD5 4021577c34860852a891bfa5c53ec155
BLAKE2b-256 6b62c8117cbb721c1c5ed401ba416a2d01c06d6a19242fa12bb88f6e879059b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for narrative_flow-1.0.0.tar.gz:

Publisher: publish.yml on hpowers/narrative-flow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file narrative_flow-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: narrative_flow-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 17.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for narrative_flow-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 07d5a70f12b107747a6ac3d468cc2e2fcf3b6cee9e9e4feb2741df95caebd3bb
MD5 833de53a6c036d868e0804bbae44bd85
BLAKE2b-256 6817ae3fd9e45b0883c357044de033a9e11e275d8c45c481fc9c90c1e6c45523

See more details on using hashes here.

Provenance

The following attestation bundles were made for narrative_flow-1.0.0-py3-none-any.whl:

Publisher: publish.yml on hpowers/narrative-flow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page