Packages

FlowStone integration for altar_ai - AI-powered data pipeline assets. Provides FlowStone.AI.Resource for unified AI access and FlowStone.AI.Assets DSL helpers (classify_each, enrich_each, embed_each) with telemetry bridging.

Current section

Files

Jump to
flowstone_ai lib flowstone ai resource.ex
Raw

lib/flowstone/ai/resource.ex

defmodule FlowStone.AI.Resource do
@moduledoc """
FlowStone Resource that provides AI capabilities to assets.
## Usage
In your pipeline:
asset :enriched do
requires [:ai]
execute fn ctx, %{raw: data} ->
{:ok, result} = FlowStone.AI.Resource.generate(ctx.resources.ai, "classify: \#{data}")
{:ok, Map.put(data, :classification, result.content)}
end
end
## Configuration
config :flowstone_ai,
adapter: Altar.AI.Adapters.Composite,
adapter_opts: []
The resource will use the configured adapter, defaulting to `Altar.AI.Adapters.Composite`
for automatic provider fallback.
"""
@behaviour FlowStone.Resource
defstruct [:adapter, :opts]
@type t :: %__MODULE__{
adapter: struct(),
opts: keyword()
}
@doc """
Set up the AI resource.
This is the FlowStone.Resource callback. For manual initialization, use `init/1`.
## Options
* `:adapter` - The altar_ai adapter module to use (default: `Altar.AI.Adapters.Composite`)
* `:adapter_opts` - Options to pass to the adapter initialization (default: `[]`)
"""
@impl true
def setup(config) when is_map(config) do
# Convert map config to keyword list for init
opts = Map.to_list(config)
init(opts)
end
@doc """
Initialize the AI resource.
## Options
* `:adapter` - The altar_ai adapter module to use (default: `Altar.AI.Adapters.Composite`)
* `:adapter_opts` - Options to pass to the adapter initialization (default: `[]`)
## Examples
{:ok, resource} = FlowStone.AI.Resource.init()
{:ok, resource} = FlowStone.AI.Resource.init(
adapter: Altar.AI.Adapters.Gemini,
adapter_opts: [api_key: "..."]
)
"""
def init(opts \\ []) do
adapter_mod = Keyword.get(opts, :adapter, get_config(:adapter, Altar.AI.Adapters.Composite))
adapter_opts = Keyword.get(opts, :adapter_opts, get_config(:adapter_opts, []))
adapter =
if adapter_mod == Altar.AI.Adapters.Composite do
Altar.AI.Adapters.Composite.default()
else
adapter_mod.new(adapter_opts)
end
{:ok, %__MODULE__{adapter: adapter, opts: opts}}
end
@impl true
def teardown(_resource), do: :ok
@impl true
def health_check(%__MODULE__{adapter: adapter}) do
# Check if adapter has basic capabilities
case Altar.AI.capabilities(adapter) do
%{generate: true} -> :healthy
_ -> {:unhealthy, :no_capabilities}
end
rescue
_ -> {:unhealthy, :adapter_error}
end
@doc """
Generate text using the AI adapter.
Delegates to `Altar.AI.generate/3`.
## Examples
{:ok, response} = FlowStone.AI.Resource.generate(resource, "Explain quantum computing")
IO.puts(response.content)
"""
@spec generate(t(), String.t(), keyword()) :: {:ok, Altar.AI.Response.t()} | {:error, term()}
def generate(%__MODULE__{adapter: adapter}, prompt, opts \\ []) do
Altar.AI.generate(adapter, prompt, opts)
end
@doc """
Generate embeddings for text.
Delegates to `Altar.AI.embed/3`.
## Examples
{:ok, vector} = FlowStone.AI.Resource.embed(resource, "Hello world")
length(vector) # => 768 (or adapter-specific dimension)
"""
@spec embed(t(), String.t(), keyword()) :: {:ok, list(float())} | {:error, term()}
def embed(%__MODULE__{adapter: adapter}, text, opts \\ []) do
Altar.AI.embed(adapter, text, opts)
end
@doc """
Generate embeddings for multiple texts in batch.
Delegates to `Altar.AI.batch_embed/3`.
## Examples
{:ok, vectors} = FlowStone.AI.Resource.batch_embed(resource, ["text1", "text2"])
length(vectors) # => 2
"""
@spec batch_embed(t(), list(String.t()), keyword()) ::
{:ok, list(list(float()))} | {:error, term()}
def batch_embed(%__MODULE__{adapter: adapter}, texts, opts \\ []) do
Altar.AI.batch_embed(adapter, texts, opts)
end
@doc """
Classify text into one of the provided labels.
Delegates to `Altar.AI.classify/4`.
## Examples
{:ok, result} = FlowStone.AI.Resource.classify(
resource,
"I love this product!",
["positive", "negative", "neutral"]
)
result.label # => "positive"
result.confidence # => 0.95
"""
@spec classify(t(), String.t(), list(String.t()), keyword()) ::
{:ok, Altar.AI.Classification.t()} | {:error, term()}
def classify(%__MODULE__{adapter: adapter}, text, labels, opts \\ []) do
Altar.AI.classify(adapter, text, labels, opts)
end
@doc """
Get the capabilities of the configured adapter.
Delegates to `Altar.AI.capabilities/1`.
## Examples
capabilities = FlowStone.AI.Resource.capabilities(resource)
capabilities.text_generation # => true
"""
@spec capabilities(t()) :: Altar.AI.Capabilities.t()
def capabilities(%__MODULE__{adapter: adapter}) do
Altar.AI.capabilities(adapter)
end
# Private helpers
defp get_config(key, default) do
Application.get_env(:flowstone_ai, key, default)
end
end