Ragex.AI.Provider.Shared (Ragex v0.32.2)

View Source

Helpers shared by the AI provider implementations (Ragex.AI.Provider.Anthropic, Ragex.AI.Provider.OpenAI, Ragex.AI.Provider.DeepSeekR1).

Extracted because mix credo --strict (Credo.Check.Design.DuplicatedCode) found each of these pieces of logic copy-pasted near-identically across two or three provider modules:

  • resolve_config/3 / resolve_api_key/2: the opts > provider config > default resolution used by Anthropic and OpenAI's private get_config/1 and get_api_key/0 functions.
  • to_api_message/1, format_api_tool_calls/1: the OpenAI-compatible chat message/tool-call formatting duplicated byte-for-byte between OpenAI and DeepSeekR1.
  • start_streaming_task/3: the Task.async + Req.post(..., into: ...) SSE launcher duplicated across all three providers' stream_api/ stream_generate implementations.

Each provider still owns its own event-parsing loop (Anthropic, OpenAI, and DeepSeek use different SSE payload shapes), so only the genuinely identical plumbing lives here.

Summary

Functions

Formats tool-call structs (atom- or string-keyed) into OpenAI-compatible API format.

Resolves this provider's API key: runtime :ai_keys config first, falling back to the given environment variable.

Resolves provider request config with opts > application provider config > defaults precedence, matching the pattern previously duplicated in each provider's private get_config/1.

Launches a background Task that POSTs an SSE request and forwards each chunk to the calling process as {:stream_chunk, data}, followed by either :stream_done or {:stream_error, reason}.

Converts a generic (atom- or string-keyed) message map into OpenAI-compatible API format.

Types

provider_config()

@type provider_config() :: %{
  endpoint: String.t(),
  model: String.t(),
  temperature: float() | number(),
  max_tokens: pos_integer(),
  stream: boolean()
}

provider_defaults()

@type provider_defaults() :: %{
  endpoint: String.t(),
  model: String.t(),
  temperature: float() | number(),
  max_tokens: pos_integer()
}

Functions

format_api_tool_calls(tool_calls)

@spec format_api_tool_calls([map()]) :: [map()]

Formats tool-call structs (atom- or string-keyed) into OpenAI-compatible API format.

resolve_api_key(provider_key, env_var)

@spec resolve_api_key(atom(), String.t()) :: {:ok, String.t()} | {:error, :no_api_key}

Resolves this provider's API key: runtime :ai_keys config first, falling back to the given environment variable.

resolve_config(provider_key, opts, defaults)

@spec resolve_config(atom(), keyword(), provider_defaults()) :: provider_config()

Resolves provider request config with opts > application provider config > defaults precedence, matching the pattern previously duplicated in each provider's private get_config/1.

start_streaming_task(url, body, headers)

@spec start_streaming_task(String.t(), map(), [{String.t(), String.t()}] | keyword()) ::
  Task.t()

Launches a background Task that POSTs an SSE request and forwards each chunk to the calling process as {:stream_chunk, data}, followed by either :stream_done or {:stream_error, reason}.

Callers are expected to build their own Stream.resource/3 around receive, since each provider parses a different SSE event shape.

to_api_message(msg)

@spec to_api_message(map()) :: map()

Converts a generic (atom- or string-keyed) message map into OpenAI-compatible API format.