# `Arcana.Config`
[🔗](https://github.com/georgeguimaraes/arcana/blob/main/lib/arcana/config.ex#L1)

Configuration management for Arcana.

Handles parsing and resolving configuration for embedders, chunkers,
and other pluggable components.

## Redacting Sensitive Values

Use `Arcana.Config.redact/1` to wrap any config value for safe inspection:

    config = Application.get_env(:arcana, :llm)
    inspect(Arcana.Config.redact(config))
    # => {"zai:glm-4.7", [api_key: "[REDACTED]"]}

## Embedder Configuration

    # Default: Local Bumblebee with bge-small-en-v1.5
    config :arcana, embedder: :local

    # Local with different model
    config :arcana, embedder: {:local, model: "BAAI/bge-large-en-v1.5"}

    # OpenAI (requires req_llm and OPENAI_API_KEY)
    config :arcana, embedder: :openai
    config :arcana, embedder: {:openai, model: "text-embedding-3-large"}

    # Custom function
    config :arcana, embedder: fn text -> {:ok, embedding} end

    # Custom module implementing Arcana.Embedder behaviour
    config :arcana, embedder: MyApp.CohereEmbedder
    config :arcana, embedder: {MyApp.CohereEmbedder, api_key: "..."}

## Chunker Configuration

    # Default: text_chunker-based chunking
    config :arcana, chunker: :default

    # Default chunker with custom options
    config :arcana, chunker: {:default, chunk_size: 512, chunk_overlap: 100}

    # Custom function (receives text, opts; returns list of chunk maps)
    config :arcana, chunker: fn text, _opts ->
      [%{text: text, chunk_index: 0, token_count: 10}]
    end

    # Custom module implementing Arcana.Chunker behaviour
    config :arcana, chunker: MyApp.SemanticChunker
    config :arcana, chunker: {MyApp.SemanticChunker, model: "..."}

## PDF Parser Configuration

    # Default: poppler's pdftotext
    config :arcana, pdf_parser: :poppler

    # Custom module implementing Arcana.FileParser.PDF behaviour
    config :arcana, pdf_parser: MyApp.PDFParser
    config :arcana, pdf_parser: {MyApp.PDFParser, some_option: "value"}

## Search Defaults

Set defaults for `Arcana.search/2`. Per-call options override these.
Any option accepted by `Arcana.Search.search/2` can be set globally.

    config :arcana, search: [
      limit: 10,
      threshold: 0.0,
      mode: :vector,              # :vector | :keyword | :hybrid
      vector_weight: 0.5,         # for hybrid mode
      keyword_weight: 0.5,        # for hybrid mode
      rewriter: &MyApp.rewrite/1, # query rewriter function
      hnsw_ef_search: 100,       # pgvector only, see Arcana.VectorStore.Pgvector
    ]

## Ask Defaults

Set defaults for `Arcana.ask/2`. Per-call options override these.
Any option accepted by `Arcana.Ask.ask/2` can be set globally.

    config :arcana, ask: [
      limit: 5,
      mode: :semantic,
      threshold: 0.0,
      prompt: &MyApp.custom_prompt/3
    ]

## Reranker Configuration

Set a global reranker that will be applied automatically by `Arcana.search/2`
and `Arcana.ask/2`. Per-call `:reranker` options override this. Pass
`reranker: false` per-call to disable for a single request.

    # Global reranker module
    config :arcana, reranker: Arcana.Reranker.CrossEncoder

    # With options (e.g. over_fetch multiplier, threshold)
    config :arcana, reranker: {Arcana.Reranker.CrossEncoder, over_fetch: 3}

    # Custom function: fn question, chunks, opts -> {:ok, reranked} end
    config :arcana, reranker: &MyApp.rerank/3

# `chunker`

Returns the configured chunker as a `{module, opts}` tuple.

# `current`

Returns the current Arcana configuration.

Useful for logging, debugging, and storing with evaluation runs
to track which settings produced which results.

## Example

    Arcana.Config.current()
    # => %{
    #   embedding: %{module: Arcana.Embedder.Local, model: "BAAI/bge-small-en-v1.5", dimensions: 384},
    #   vector_store: :pgvector
    # }

# `embedder`

Returns the configured embedder as a `{module, opts}` tuple.

# `fallback_parser`

Returns the configured fallback parser as `{module, opts}`, or `nil`.

Consulted for any extension without a native or registered parser:

    config :arcana, fallback_parser: {MyApp.ExtractionService, []}

`nil` and `false` both mean "no fallback".

# `file_parsers`

Returns the configured file parsers as a map of extension to
`{module, opts}`.

Extensions are normalized to lowercase with a leading dot, so
`%{"docx" => ...}` and `%{".DOCX" => ...}` both register `".docx"`.

    config :arcana, file_parsers: %{".docx" => {MyApp.DocxParser, []}}

Mapping an extension to `false` disables it: nothing parses it, not the
built-in route and not the `:fallback_parser`. Such entries come back
as `nil`.

    config :arcana, file_parsers: %{".pdf" => false}

# `get`

Returns the value for `key` from opts, falling back to the global app env.

Used to thread configuration like `:repo` and `:llm` from per-call opts
with a fallback to `config :arcana, key: value`.

## Examples

    repo = Arcana.Config.get(opts, :repo)
    llm = Arcana.Config.get(opts, :llm)

# `get_env`

Returns the value for `key` from the `:arcana` app env.

All runtime configuration reads in Arcana go through this function. Test
suites can install a custom reader with `install_env_reader/1` to shadow
keys per-process instead of mutating global state.

# `graph_enabled?`

Returns whether GraphRAG is enabled globally or for specific options.

Checks the `:graph` option in the provided opts first, then falls back
to the global configuration.

## Examples

    # Check global config
    Arcana.Config.graph_enabled?([])

    # Override with per-call option
    Arcana.Config.graph_enabled?(graph: true)

# `merge_app_opts`

Merges global keyword-list config under `app_key` with per-call opts.

Per-call opts override the global config. Used to thread namespace
configs like `:search`, `:ask`, and `:graph`.

## Examples

    # Reads `config :arcana, search: [limit: 10]` and merges
    opts = Arcana.Config.merge_app_opts(opts, :search)

    # Reads `config :arcana, ask: [limit: 5]` and merges
    opts = Arcana.Config.merge_app_opts(opts, :ask)

# `pdf_parser`

Returns the configured PDF parser as a `{module, opts}` tuple.

# `redact`

Wraps a config value for safe inspection with sensitive data redacted.

Returns a struct that implements the `Inspect` protocol and automatically
redacts sensitive keys like `:api_key`, `:token`, `:password`, etc.

## Example

    iex> config = {"zai:glm-4.7", [api_key: "secret123"]}
    iex> inspect(Arcana.Config.redact(config))
    ~s|{"zai:glm-4.7", [api_key: "[REDACTED]"]}|

# `repo!`

Resolves the Ecto repo configured for Arcana's mix tasks.

Accepts either configuration shape:

    # Explicit repo key
    config :arcana, repo: MyApp.Repo

    # Per-repo configuration (e.g. custom priv directory)
    config :arcana, MyApp.Repo, priv: "priv/my_repo"

When only the per-repo shape is present and exactly one repo is
configured, that repo is used (with a note printed to stderr). Raises
`ArgumentError` when no repo can be resolved, or when several per-repo
entries exist without an explicit `:repo` key to disambiguate.

`repo: nil` means "not set" and falls through to the per-repo scan.
`repo: false` is an explicit "no repo", so it raises the same "no Ecto
repo configured" error instead of quietly picking a per-repo entry.

Called without arguments, the `:repo` key is read through `get_env/2` so
process-scoped overrides installed via `install_env_reader/1` are honored.
Pass an explicit env keyword list to resolve against it directly.

# `require_repo!`

Returns the repo from opts or the global config, raising when neither
is set.

# `reranker`

Returns the configured reranker, resolving per-call opts and global config.

Returns `nil` if no reranker is set or if explicitly disabled with
`reranker: false`. Otherwise returns `{module_or_fun, opts}`.

# `resolve_chunker`

Resolves chunker from options, falling back to global config.

# `resolve_embedder`

Resolves embedder from options, falling back to global config.

# `strict_collections?`

Returns whether strict collection scoping is enabled.

When strict, an unknown collection name is an error
(`{:error, {:unknown_collection, name}}`) instead of silently widening
the operation to every collection. Checks the `:strict_collections`
option in the provided opts first (so `strict_collections: false` can
override a global `true` per call), then falls back to
`config :arcana, strict_collections: true`. Defaults to `false`; the
default will flip to `true` in Arcana 3.0.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
