An Elixir client for fal.ai - Serverless AI/ML inference platform.

Hex.pm Documentation License

About

FalEx provides a robust and user-friendly Elixir client for seamless integration with fal.ai endpoints. It supports synchronous execution, queued jobs, streaming responses, real-time WebSocket connections, and automatic file handling.

Features

  • 🚀 Simple API - Intuitive interface matching the JavaScript client
  • 📦 Queue Support - Submit long-running jobs and poll for results
  • 🌊 Streaming - Real-time streaming responses with Server-Sent Events
  • 🔌 WebSocket - Bidirectional real-time communication
  • 📤 File Uploads - Automatic file upload and URL transformation
  • 🔐 Secure - API key authentication with proxy support
  • 🧩 Type Safe - Full type specifications for better developer experience

Installation

Add fal_ex to your list of dependencies in mix.exs:

def deps do
  [
    {:fal_ex, "~> 0.1.0"}
  ]
end

Then run:

mix deps.get

Configuration

Configure FalEx in your application's config files:

# config/config.exs
config :fal_ex,
  api_key: "your-api-key"

# config/runtime.exs (for runtime configuration)
import Config

if config_env() == :prod do
  config :fal_ex,
    api_key: System.fetch_env!("FAL_KEY")
end

Environment Variables

You can also use environment variables (these override application config):

# Single API key
export FAL_KEY="your-api-key"

# Or using key ID and secret
export FAL_KEY_ID="your-key-id"
export FAL_KEY_SECRET="your-key-secret"

Programmatic Configuration

Configure the client programmatically at runtime:

# Configure globally
FalEx.config(credentials: "your-api-key")

# Or with more options
FalEx.config(
  credentials: {"key-id", "key-secret"},
  proxy_url: "/api/fal/proxy"
)

Creating a Custom Client

For more control, create a custom client instance:

config = FalEx.Config.new(
  credentials: System.get_env("FAL_KEY"),
  base_url: "https://fal.run"
)

client = FalEx.Client.create(config)

Basic Usage

Synchronous Execution

Run models synchronously (blocks until complete):

{:ok, result} = FalEx.run("fal-ai/fast-sdxl",
  input: %{
    prompt: "A serene mountain landscape at sunset",
    image_size: "landscape_16_9"
  }
)

IO.inspect(result["images"])

Queue-Based Execution

For long-running models or when you want progress updates, use the queue system:

{:ok, result} = FalEx.subscribe("fal-ai/flux/dev",
  input: %{
    prompt: "A detailed architectural drawing",
    num_images: 4
  },
  logs: true,
  on_queue_update: fn status ->
    IO.puts("Status: #{inspect(status.status)}")
    
    # Print logs if available
    Enum.each(status[:logs] || [], fn log ->
      IO.puts("[#{log["level"]}] #{log["message"]}")
    end)
  end
)

# Note: For fast models, the request may complete synchronously
# and you'll get the result immediately with a single status update

Streaming Responses

Stream partial results from compatible models (requires endpoints that support SSE streaming):

# Get the stream
{:ok, stream} = FalEx.stream("fal-ai/flux/dev",
  input: %{
    prompt: "A beautiful sunset over mountains",
    image_size: "landscape_4_3",
    num_images: 1
  }
)

# Process events as they arrive
stream
|> Stream.each(fn event ->
  IO.inspect(event, label: "Stream event")
  
  # Events typically have a "type" field indicating progress
  case event do
    %{"type" => "progress", "data" => data} ->
      IO.puts("Progress: #{inspect(data)}")
    
    %{"type" => "output", "data" => data} ->
      IO.puts("Got output: #{inspect(data["images"])}")
      
    _ ->
      :ok
  end
end)
|> Stream.run()

Real-time WebSocket

For bidirectional communication:

{:ok, conn} = FalEx.realtime()
|> FalEx.Realtime.connect("fal-ai/camera-turbo-stream",
  on_message: fn message ->
    IO.inspect(message, label: "Received")
  end,
  on_error: fn error ->
    IO.puts("Error: #{error}")
  end
)

# Send messages
FalEx.Realtime.send(conn, %{
  type: "frame",
  data: base64_encoded_image
})

File Handling

FalEx automatically handles file uploads:

# Automatic file upload from path
{:ok, result} = FalEx.run("fal-ai/face-to-sticker",
  input: %{
    image_url: "/path/to/local/image.jpg",  # Automatically uploaded
    prompt: "cartoon style"
  }
)

# Direct binary upload
image_data = File.read!("avatar.png")
{:ok, result} = FalEx.run("fal-ai/image-to-image",
  input: %{
    image: image_data,  # Automatically uploaded
    prompt: "enhance details"
  }
)

# Manual upload
{:ok, url} = FalEx.storage()
|> FalEx.Storage.upload(image_data,
  file_name: "avatar.png",
  content_type: "image/png"
)

Advanced Usage

Queue Operations

Direct queue API access:

queue = FalEx.queue()

# Submit a job
{:ok, %{"request_id" => request_id}} = FalEx.Queue.submit(queue, "fal-ai/fast-sdxl",
  input: %{prompt: "A lighthouse"},
  webhook_url: "https://example.com/webhook"
)

# Check status
{:ok, status} = FalEx.Queue.status(queue, "fal-ai/fast-sdxl",
  request_id: request_id,
  logs: true
)

# Cancel if needed
:ok = FalEx.Queue.cancel(queue, "fal-ai/fast-sdxl",
  request_id: request_id
)

# Get result when complete
{:ok, result} = FalEx.Queue.result(queue, "fal-ai/fast-sdxl",
  request_id: request_id
)

Custom Middleware

Add custom request/response handling:

defmodule MyApp.FalMiddleware do
  @behaviour Tesla.Middleware
  
  def call(env, next, _opts) do
    # Add custom header
    env = Tesla.put_header(env, "x-custom-header", "value")
    
    # Call next middleware
    case Tesla.run(env, next) do
      {:ok, env} ->
        # Log response
        Logger.info("FAL Response: #{env.status}")
        {:ok, env}
        
      error ->
        error
    end
  end
end

# Use in config
FalEx.config(
  credentials: "...",
  request_middleware: MyApp.FalMiddleware
)

Error Handling

FalEx provides detailed error types:

case FalEx.run("fal-ai/fast-sdxl", input: %{}) do
  {:ok, result} ->
    IO.inspect(result.data)
    
  {:error, %FalEx.Response.ValidationError{} = error} ->
    IO.puts("Validation failed: #{error.message}")
    
    Enum.each(error.errors, fn error ->
      IO.puts("  #{Enum.join(error.loc, ".")}: #{error.msg}")
    end)
    
  {:error, %FalEx.Response.ApiError{} = error} ->
    IO.puts("API error (#{error.status}): #{error.message}")
    
  {:error, reason} ->
    IO.puts("Unexpected error: #{inspect(reason)}")
end

Proxy Configuration

For production deployments, use a proxy to protect your API keys:

# Configure client to use proxy
FalEx.config(proxy_url: "/api/fal/proxy")

# In your Phoenix app, add a proxy endpoint
defmodule MyAppWeb.FalProxyController do
  use MyAppWeb, :controller
  
  def proxy(conn, params) do
    # Forward request to fal.ai with server-side credentials
    # Implementation depends on your setup
  end
end

Examples

Text to Image

{:ok, result} = FalEx.run("fal-ai/fast-sdxl",
  input: %{
    prompt: "A futuristic city with flying cars",
    negative_prompt: "blurry, low quality",
    image_size: "square_hd",
    num_inference_steps: 25,
    guidance_scale: 7.5
  }
)

[image | _] = result.data["images"]
IO.puts("Generated image: #{image["url"]}")

Image to Image

source_image = File.read!("source.jpg")

{:ok, result} = FalEx.run("fal-ai/image-to-image",
  input: %{
    image: source_image,
    prompt: "Transform to watercolor painting",
    strength: 0.8
  }
)

LLM Streaming

FalEx.stream("fal-ai/llama-3",
  input: %{
    prompt: "Explain quantum computing",
    max_tokens: 500,
    temperature: 0.7
  }
)
|> Stream.scan("", fn chunk, acc ->
  text = chunk["text"] || ""
  IO.write(text)
  acc <> text
end)
|> Enum.to_list()
|> List.last()

Whisper Transcription

audio_data = File.read!("recording.mp3")

{:ok, result} = FalEx.run("fal-ai/whisper",
  input: %{
    audio: audio_data,
    language: "en",
    task: "transcribe",
    include_timestamps: true
  }
)

IO.puts("Transcription: #{result.data["text"]}")

Available Models

See the fal.ai model gallery for available models and their parameters.

Popular models include:

  • fal-ai/fast-sdxl - Fast SDXL image generation
  • fal-ai/llama-3 - Meta's Llama 3 language model
  • fal-ai/whisper - OpenAI's Whisper speech recognition
  • fal-ai/stable-video - Stable Video Diffusion
  • fal-ai/face-to-sticker - Convert faces to stickers
  • fal-ai/image-to-image - Image transformation

Testing

# Run tests
mix test

# Run tests with coverage
mix test --cover

# Run dialyzer
mix dialyzer

# Run credo
mix credo

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by the official fal-js JavaScript client
  • Built with Tesla HTTP client
  • WebSocket support via WebSockex

Support