Powered by AppSignal & Oban Pro

Imp 05: Operate And Live Checks

05_operate_and_live_checks.livemd

Imp 05: Operate And Live Checks

imp_checkout? = fn path ->
  is_binary(path) and File.regular?(Path.join(path, "mix.exs")) and
    File.regular?(Path.join(path, "lib/imp.ex"))
end

explicit_repo = System.get_env("IMP_PATH")

if explicit_repo && not imp_checkout?.(Path.expand(explicit_repo)) do
  raise "IMP_PATH does not point to an Imp source checkout or unpacked package"
end

repo =
  [explicit_repo, Path.expand("..", __DIR__), File.cwd!()]
  |> Enum.reject(&is_nil/1)
  |> Enum.map(&Path.expand/1)
  |> Enum.find(imp_checkout?)

if repo do
  # Prefer the notebook's own Imp checkout even when Livebook was launched
  # from an unrelated Mix project. Unpacked package archives may omit
  # mix.lock, so pin it only when the source checkout actually provides it.
  install_opts =
    if File.regular?(Path.join(repo, "mix.lock")),
      do: [lockfile: Path.join(repo, "mix.lock")],
      else: []

  Mix.install([{:imp, path: repo}], install_opts)
else
  # Standalone notebook: install the tagged Git source release.
  Mix.install([{:imp, github: "deepfates/imp", tag: "v0.4.0"}])
end
live_provider_enabled? = System.get_env("LIVE_PROVIDER") == "1"

Operational boundaries

The earlier notebooks built Imp programs. This chapter is about the choices an application developer must make before putting provider calls in production: credentials, timeouts, retries, redaction, concurrency, cost limits, and failure behavior.

Application Checklist

Before swapping a useful deterministic program to a live provider, decide the host application's operational boundaries:

[
  runtime_config: [:model_name, :api_key, :timeout, :temperature],
  tests: [:static_lm_unit_tests, :opt_in_live_smoke],
  safety: [:redaction, :tool_policy, :provider_failure_behavior],
  operations: [:telemetry, :cost_limits, :rate_limits, :rollout_plan]
]

Security Posture

Unknown external keys remain strings:

key = "external_key_#{System.unique_integer([:positive])}"
example = Imp.example(%{key => "value"})

{
  Imp.get(example, key),
  try do
    String.to_existing_atom(key)
  rescue
    ArgumentError -> :not_interned
  end
}

Trace redaction — secret-shaped values in tool traffic are replaced before any event or log leaves the boundary:

echo = Imp.tool(:echo, "echo", fn input -> input end)

trace =
  Imp.trace(fn ->
    Imp.Tool.call(echo, %{api_key: "sk-live", nested: %{token: "secret"}})
  end)

Enum.map(trace.events, fn {name, _measurements, metadata} -> {name, metadata} end)

Optional Live Provider

This cell requires LIVE_PROVIDER=1, OPENAI_API_KEY, and OPENAI_MODEL in your environment. Do not paste secrets into the notebook.

if live_provider_enabled? && System.get_env("OPENAI_API_KEY") && System.get_env("OPENAI_MODEL") do
  model = System.fetch_env!("OPENAI_MODEL")

  lm =
    Imp.req_llm("openai:#{model}",
      api_key: System.fetch_env!("OPENAI_API_KEY"),
      temperature: 0,
      max_completion_tokens: 40
    )

  program = Imp.predict("question -> answer", lm: lm)
  Imp.call(program, %{question: "Reply with exactly: pong"})
else
  {:skip, "Set LIVE_PROVIDER=1, OPENAI_API_KEY, and OPENAI_MODEL before running the live provider cell."}
end

Live application probe

The next cell is the consumer-facing live probe. It runs only when the three explicit environment variables above are present and otherwise stays provider-free.

Exercise The Live Runtime

This cell validates the same live provider path and asserts the expected output when credentials are present.

if live_provider_enabled? && System.get_env("OPENAI_API_KEY") && System.get_env("OPENAI_MODEL") do
  model = System.fetch_env!("OPENAI_MODEL")

  lm =
    Imp.req_llm("openai:#{model}",
      api_key: System.fetch_env!("OPENAI_API_KEY"),
      temperature: 0,
      max_completion_tokens: 40
    )

  program = Imp.predict("question -> answer", lm: lm)
  {:ok, prediction} = Imp.call(program, %{question: "Reply with exactly: pong"})
  answer = prediction |> Imp.get(:answer, "") |> to_string() |> String.downcase()

  unless String.contains?(answer, "pong") do
    raise "live runtime returned an invalid result: #{inspect(Imp.to_map(prediction))}"
  end

  Imp.to_map(prediction)
else
  {:skip, "Set LIVE_PROVIDER=1, OPENAI_API_KEY, and OPENAI_MODEL before running the live runtime cell."}
end

Saving Without Secrets

lm = Imp.req_llm("openai:gpt-test", api_key: "not-persisted", temperature: 0)
program = Imp.predict("question -> answer", lm: lm)

path = Path.join(System.tmp_dir!(), "imp-no-secret.json")
Imp.save!(program, path)
state = File.read!(path)
loaded = Imp.load!(path)
File.rm(path)

{
  String.contains?(state, "not-persisted"),
  Keyword.has_key?(loaded.lm.opts, :api_key),
  loaded.lm
}

Canonical Deployment Reference

For a production application, use examples/deployment. It is the canonical Imp deployment path: a supervised application loads a checksummed artifact, rebinds credentials from the environment, and runs provider calls through bounded workers. The static answer setting in that reference is only a smoke mode; production uses IMP_MODEL and IMP_API_KEY.