Powered by AppSignal & Oban Pro

Workflows

livebook/04_workflows.livemd

Workflows

Section

This notebook defines a deterministic workflow, inspects the compiled graph, runs the workflow directly, then exposes it as one model-callable agent tool.

Mix.install(
  [
    {:kino, "~> 0.14"},
    {:jidoka, path: Path.expand("..", __DIR__)}
  ],
  consolidate_protocols: false
)
Jidoka.Kino.setup_notebook(model: "test:notebook-model", check_provider?: false)

Define A Workflow

defmodule NotebookRefundWorkflow do
  use Jidoka.Workflow

  workflow do
    id :notebook_refund_review
    description "Reviews a refund request with tenant context."

    input Zoi.object(%{
            order_id: Zoi.string(),
            amount: Zoi.float()
          })
  end

  steps do
    function :check_policy, {__MODULE__, :check_policy, 2},
      input: %{
        order_id: input(:order_id),
        amount: input(:amount),
        tenant: context(:tenant)
      }
  end

  output %{
    order_id: from(:check_policy, :order_id),
    approved: from(:check_policy, :approved),
    summary: from(:check_policy, :summary)
  }

  def check_policy(%{order_id: order_id, amount: amount, tenant: tenant}, _context) do
    approved = amount <= 100.0

    {:ok,
     %{
       order_id: order_id,
       approved: approved,
       summary: "Refund #{order_id} for #{tenant}: #{if(approved, do: "approved", else: "review")}"
     }}
  end
end

Inspect And Run It Directly

Jidoka.inspect(NotebookRefundWorkflow)
{:ok, workflow_output} =
  Jidoka.Workflow.run(
    NotebookRefundWorkflow,
    %{"order_id" => "A1001", "amount" => 42.50},
    context: %{tenant: "acme"}
  )

workflow_output

Expose It As An Agent Tool

defmodule NotebookRefundAgent do
  use Jidoka.Agent

  agent :notebook_refund_agent do
    model %{provider: :test, id: "notebook-model"}
    instructions "Use review_refund before answering refund questions."
  end

  tools do
    workflow NotebookRefundWorkflow,
      as: :review_refund,
      forward_context: {:only, [:tenant]},
      result: :structured
  end
end
{:ok, _inspection} = Jidoka.Kino.debug_agent(NotebookRefundAgent)
{:ok, _diagram} = Jidoka.Kino.agent_diagram(NotebookRefundAgent)

Run A Deterministic Tool Loop

fake_llm = fn _intent, journal ->
  llm_calls =
    journal.results
    |> Map.values()
    |> Enum.count(&amp;(&amp;1.kind == :llm))

  case llm_calls do
    0 ->
      {:ok,
       %{
         type: :operation,
         name: "review_refund",
         arguments: %{"order_id" => "A1001", "amount" => 42.50}
       }}

    1 ->
      {:ok, %{type: :final, content: "Refund A1001 is approved."}}
  end
end

request =
  Jidoka.Turn.Request.new!(
    input: "Can we refund A1001 for 42.50?",
    context: %{tenant: "acme"}
  )

{:ok, result} =
  NotebookRefundAgent.run_turn(request,
    llm: fake_llm,
    operation_context: %{parent_context: request.context}
  )

%{
  content: result.content,
  operations: Enum.map(result.agent_state.operation_results, &amp;Jidoka.project/1)
}
{:ok, _timeline} = Jidoka.Kino.timeline(result)
{:ok, _graph} = Jidoka.Kino.call_graph(result)