defmodule BeamWeaver.Examples.Support do @moduledoc false alias BeamWeaver.Config alias BeamWeaver.Core.Message alias BeamWeaver.Models @live_model_timeout 60_000 @prompt_cache_version "v1" def model do case Models.init_chat_model(model_id(), model_opts()) do {:ok, model} -> model {:error, error} -> raise ArgumentError, error.message end end def model_id, do: Config.get([:examples, :model]) || "openai:gpt-5.4-mini" def model_opts do [ api_key: api_key(), max_tokens: 2048, max_output_tokens: 2048, timeout: @live_model_timeout ] |> maybe_put_zai_defaults() end def api_key do key = Config.get([:examples, :api_keys], %{}) |> Map.get(provider()) if key in [nil, ""] do raise ArgumentError, "set #{String.upcase(provider())}_API_KEY to run the examples (model: #{model_id()})" end key end defp provider, do: model_id() |> String.split(":", parts: 2) |> hd() def prompt_cache_key(scope, static_prompt) do digest = static_prompt |> then(fn prompt -> :crypto.hash(:sha256, prompt) end) |> Base.url_encode64(padding: false) |> String.slice(0, 20) scope_slug = scope |> to_string() |> String.replace(~r/[^A-Za-z0-9_.-]+/, "-") model_slug = model_id() |> String.replace(~r/[^A-Za-z0-9_.-]+/, "-") |> String.slice(0, 16) "bwpc:#{@prompt_cache_version}:#{scope_slug}:#{model_slug}:#{digest}" end def prompt_cache_messages(%BeamWeaver.Anthropic.ChatModel{}, static_prompt, messages) do [ Message.system([ %{ type: :text, text: static_prompt, cache_control: %{type: :ephemeral} } ]) | messages ] end def prompt_cache_messages(_model, static_prompt, messages) do [Message.system(static_prompt) | messages] end def prompt_cache_opts(model, scope, static_prompt) do key = prompt_cache_key(scope, static_prompt) case model do %BeamWeaver.OpenAI.ChatModel{model: "gpt-5.6" <> _suffix} -> [ prompt_cache_key: key, prompt_cache_options: %{mode: :implicit, ttl: "30m"} ] %BeamWeaver.OpenAI.ChatModel{} -> [prompt_cache_key: key, prompt_cache_retention: :in_memory] %BeamWeaver.XAI.ChatModel{} -> [prompt_cache_key: key] %BeamWeaver.Moonshot.ChatModel{} -> [prompt_cache_key: key] %BeamWeaver.Anthropic.ChatModel{} -> [] %BeamWeaver.Google.ChatModel{} -> case Config.get([:examples, :cached_content]) do nil -> [] cached_content -> [cached_content: cached_content] end _model -> [] end end def cache_read_tokens(%Message{usage_metadata: metadata}) when is_map(metadata) do get_in(metadata, [:input_token_details, :cache_read]) || 0 end def cache_read_tokens(_message), do: 0 defp maybe_put_zai_defaults(opts) do if provider() == "zai" do Keyword.merge(opts, thinking: %{type: "disabled"}, reasoning_effort: "none") else opts end end end