Configuration models

The full FoodScholarConfig schema — every section, field, type, and default. For an example-first walkthrough and recipes, see the Configuration guide; this page is the exhaustive field reference, generated from the models themselves.

Top level

.. py:pydantic_model:: FoodScholarConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:annotate (foodscholar.config.AnnotateConfig) <foodscholar.config.FoodScholarConfig.annotate>

  • py:

    obj:corpus (foodscholar.config.CorpusConfig) <foodscholar.config.FoodScholarConfig.corpus>

  • py:

    obj:layer_a (foodscholar.config.LayerAConfig) <foodscholar.config.FoodScholarConfig.layer_a>

  • py:

    obj:layer_b (foodscholar.config.LayerBConfig) <foodscholar.config.FoodScholarConfig.layer_b>

  • py:

    obj:layer_c (foodscholar.config.LayerCConfig) <foodscholar.config.FoodScholarConfig.layer_c>

  • py:

    obj:llm (foodscholar.config.LLMConfig | None) <foodscholar.config.FoodScholarConfig.llm>

  • py:

    obj:ontology (foodscholar.config.OntologyConfig | None) <foodscholar.config.FoodScholarConfig.ontology>

  • py:

    obj:storage (foodscholar.config.StorageConfig) <foodscholar.config.FoodScholarConfig.storage>

Loading

.. py:function:: resolve_config(config)

module:

foodscholar.config

Normalize any supported config source into a FoodScholarConfig.

Accepts a YAML file path, a Python dict, or an already-validated config object. ${ENV} substitution runs over dicts and strings too, so in-code configs can carry env placeholders the same way YAML can.

.. py:function:: load_config(path)

module:

foodscholar.config

Storage

.. py:pydantic_model:: StorageConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:card_store (foodscholar.config.CardStoreConfig) <foodscholar.config.StorageConfig.card_store>

  • py:

    obj:chunk_store (foodscholar.config.ChunkStoreConfig) <foodscholar.config.StorageConfig.chunk_store>

  • py:

    obj:graph_store (foodscholar.config.GraphStoreConfig) <foodscholar.config.StorageConfig.graph_store>

.. py:pydantic_model:: ChunkStoreConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:api_key (str | None) <foodscholar.config.ChunkStoreConfig.api_key>

  • py:

    obj:backend (Literal['elastic', 'memory']) <foodscholar.config.ChunkStoreConfig.backend>

  • py:

    obj:bulk_size (int) <foodscholar.config.ChunkStoreConfig.bulk_size>

  • py:

    obj:index (str | None) <foodscholar.config.ChunkStoreConfig.index>

  • py:

    obj:password (str | None) <foodscholar.config.ChunkStoreConfig.password>

  • py:

    obj:url (str | None) <foodscholar.config.ChunkStoreConfig.url>

  • py:

    obj:username (str | None) <foodscholar.config.ChunkStoreConfig.username>

.. py:pydantic_field:: ChunkStoreConfig.api_key

module:

foodscholar.config

type:

str | None

value:

None

.. py:pydantic_field:: ChunkStoreConfig.password

module:

foodscholar.config

type:

str | None

value:

None

.. py:pydantic_field:: ChunkStoreConfig.bulk_size

module:

foodscholar.config

type:

int

value:

500

.. py:pydantic_model:: GraphStoreConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:backend (Literal['neo4j', 'memory']) <foodscholar.config.GraphStoreConfig.backend>

  • py:

    obj:password (str | None) <foodscholar.config.GraphStoreConfig.password>

  • py:

    obj:url (str | None) <foodscholar.config.GraphStoreConfig.url>

  • py:

    obj:user (str | None) <foodscholar.config.GraphStoreConfig.user>

.. py:pydantic_field:: GraphStoreConfig.password

module:

foodscholar.config

type:

str | None

value:

None

LLM

.. py:pydantic_model:: LLMConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

LLM client configuration: a primary provider plus an ordered fallback chain. The chain is tried in order; each entry is attempted only if all earlier ones errored (timeout, rate limit, auth, service down).

Fields:
  • py:

    obj:fallbacks (list[foodscholar.config.ProviderConfig]) <foodscholar.config.LLMConfig.fallbacks>

  • py:

    obj:max_retries (int) <foodscholar.config.LLMConfig.max_retries>

  • py:

    obj:primary (foodscholar.config.ProviderConfig) <foodscholar.config.LLMConfig.primary>

  • py:

    obj:timeout_s (float) <foodscholar.config.LLMConfig.timeout_s>

.. py:pydantic_model:: ProviderConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

One LLM provider + model.

API keys can be supplied either in this section (api_key: — useful for in-code configs and Docker secrets) or via the provider’s standard environment variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GROQ_API_KEY, GEMINI_API_KEY). The config value wins when both are set. Ollama needs no key — just a running daemon at host.

Fields:
  • py:

    obj:api_key (str | None) <foodscholar.config.ProviderConfig.api_key>

  • py:

    obj:host (str | None) <foodscholar.config.ProviderConfig.host>

  • py:

    obj:model (str) <foodscholar.config.ProviderConfig.model>

  • py:

    obj:provider (Literal['anthropic', 'openai', 'openrouter', 'groq', 'gemini', 'ollama']) <foodscholar.config.ProviderConfig.provider>

Corpus & ontology

.. py:pydantic_model:: CorpusConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:annotated_snapshot_path (pathlib.Path | None) <foodscholar.config.CorpusConfig.annotated_snapshot_path>

  • py:

    obj:chunks_path (pathlib.Path) <foodscholar.config.CorpusConfig.chunks_path>

  • py:

    obj:ignore_source_types (list[Literal['abstract', 'textbook', 'guide']]) <foodscholar.config.CorpusConfig.ignore_source_types>

.. py:pydantic_field:: CorpusConfig.annotated_snapshot_path

module:

foodscholar.config

type:

Path | None

value:

None

.. py:pydantic_field:: CorpusConfig.ignore_source_types

module:

foodscholar.config

type:

list[SourceType]

optional:

.. py:pydantic_model:: OntologyConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:cache_path (pathlib.Path | None) <foodscholar.config.OntologyConfig.cache_path>

  • py:

    obj:foodon_path (pathlib.Path) <foodscholar.config.OntologyConfig.foodon_path>

  • py:

    obj:include_imports (bool) <foodscholar.config.OntologyConfig.include_imports>

  • py:

    obj:prefix_filter (list[str] | None) <foodscholar.config.OntologyConfig.prefix_filter>

.. py:pydantic_field:: OntologyConfig.prefix_filter

module:

foodscholar.config

type:

list[str] | None

value:

[‘FOODON:’]

Annotation

.. py:pydantic_model:: AnnotateConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:batch_size (int) <foodscholar.config.AnnotateConfig.batch_size>

  • py:

    obj:embedder (str) <foodscholar.config.AnnotateConfig.embedder>

  • py:

    obj:gliner (foodscholar.config.GLinerConfig) <foodscholar.config.AnnotateConfig.gliner>

  • py:

    obj:linker (foodscholar.config.LinkerConfig) <foodscholar.config.AnnotateConfig.linker>

  • py:

    obj:ner (Literal['gliner']) <foodscholar.config.AnnotateConfig.ner>

.. py:pydantic_field:: AnnotateConfig.ner

module:

foodscholar.config

type:

Literal[‘gliner’]

value:

‘gliner’

.. py:pydantic_field:: AnnotateConfig.batch_size

module:

foodscholar.config

type:

int

value:

16

.. py:pydantic_model:: GLinerConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

GLiNER-bio NER configuration. Defaults match the validated prototype.

Fields:
  • py:

    obj:batch_size (int) <foodscholar.config.GLinerConfig.batch_size>

  • py:

    obj:flat_ner (bool) <foodscholar.config.GLinerConfig.flat_ner>

  • py:

    obj:labels (list[str]) <foodscholar.config.GLinerConfig.labels>

  • py:

    obj:max_length (int) <foodscholar.config.GLinerConfig.max_length>

  • py:

    obj:model_id (str) <foodscholar.config.GLinerConfig.model_id>

  • py:

    obj:threshold (float) <foodscholar.config.GLinerConfig.threshold>

.. py:pydantic_model:: LinkerConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Entity-linking configuration.

Backend = hnsw (local hnswlib index, default) or elastic (ES dense_vector, opt-in). The HNSW index is built on first use from the loaded FoodOn ontology and cached to nel_index_path / nel_metadata_path (auto-derived when those are left unset).

Fields:
  • py:

    obj:es_index (str | None) <foodscholar.config.LinkerConfig.es_index>

  • py:

    obj:nel_backend (Literal['hnsw', 'elastic']) <foodscholar.config.LinkerConfig.nel_backend>

  • py:

    obj:nel_encoder (Literal['sapbert', 'biolord', 'minilm', 'mpnet']) <foodscholar.config.LinkerConfig.nel_encoder>

  • py:

    obj:nel_index_path (pathlib.Path | None) <foodscholar.config.LinkerConfig.nel_index_path>

  • py:

    obj:nel_metadata_path (pathlib.Path | None) <foodscholar.config.LinkerConfig.nel_metadata_path>

  • py:

    obj:nel_min_sim (float) <foodscholar.config.LinkerConfig.nel_min_sim>

  • py:

    obj:nel_top_k (int) <foodscholar.config.LinkerConfig.nel_top_k>

.. py:pydantic_field:: LinkerConfig.nel_index_path

module:

foodscholar.config

type:

Path | None

value:

None

.. py:pydantic_field:: LinkerConfig.nel_metadata_path

module:

foodscholar.config

type:

Path | None

value:

None

Layer A

.. py:pydantic_model:: LayerAConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:alias_shelves (bool) <foodscholar.config.LayerAConfig.alias_shelves>

  • py:

    obj:backbone_max_children (int) <foodscholar.config.LayerAConfig.backbone_max_children>

  • py:

    obj:blacklist_terms (list[str]) <foodscholar.config.LayerAConfig.blacklist_terms>

  • py:

    obj:bottom_up_grouping (foodscholar.config.BottomUpGroupingConfig) <foodscholar.config.LayerAConfig.bottom_up_grouping>

  • py:

    obj:collapse_single_child_chains (bool) <foodscholar.config.LayerAConfig.collapse_single_child_chains>

  • py:

    obj:facet_overrides (dict[Literal['foods', 'health', 'sustainability', 'dietary_patterns', 'allergies', 'nutrients'], foodscholar.config.FacetConfig]) <foodscholar.config.LayerAConfig.facet_overrides>

  • py:

    obj:facets (list[Literal['foods', 'health', 'sustainability', 'dietary_patterns', 'allergies', 'nutrients']]) <foodscholar.config.LayerAConfig.facets>

  • py:

    obj:link_blocklist (list[foodscholar.config.LinkBlocklistEntry]) <foodscholar.config.LayerAConfig.link_blocklist>

  • py:

    obj:max_depth (int) <foodscholar.config.LayerAConfig.max_depth>

  • py:

    obj:min_link_confidence (float) <foodscholar.config.LayerAConfig.min_link_confidence>

  • py:

    obj:min_support (int) <foodscholar.config.LayerAConfig.min_support>

  • py:

    obj:projection (Literal['backbone', 'prune']) <foodscholar.config.LayerAConfig.projection>

  • py:

    obj:semantic_consolidation (foodscholar.config.SemanticConsolidationConfig) <foodscholar.config.LayerAConfig.semantic_consolidation>

  • py:

    obj:umbrella_direct_share_max (float) <foodscholar.config.LayerAConfig.umbrella_direct_share_max>

  • py:

    obj:umbrella_lifted_share_min (float) <foodscholar.config.LayerAConfig.umbrella_lifted_share_min>

  • py:

    obj:umbrella_min_count (int) <foodscholar.config.LayerAConfig.umbrella_min_count>

.. py:pydantic_field:: LayerAConfig.umbrella_direct_share_max

module:

foodscholar.config

type:

float

value:

0.1

.. py:pydantic_field:: LayerAConfig.umbrella_lifted_share_min

module:

foodscholar.config

type:

float

value:

0.85

.. py:pydantic_field:: LayerAConfig.umbrella_min_count

module:

foodscholar.config

type:

int

value:

25

.. py:pydantic_field:: LayerAConfig.link_blocklist

module:

foodscholar.config

type:

list[LinkBlocklistEntry]

optional:

.. py:pydantic_field:: LayerAConfig.projection :module: foodscholar.config :type: Literal[‘backbone’, ‘prune’] :value: ‘backbone’

.. py:pydantic_field:: LayerAConfig.backbone_max_children :module: foodscholar.config :type: int :value: 12

.. py:pydantic_field:: LayerAConfig.alias_shelves :module: foodscholar.config :type: bool :value: True

.. py:pydantic_field:: LayerAConfig.facet_overrides :module: foodscholar.config :type: dict[Facet, FacetConfig] :optional:

.. py:pydantic_field:: LayerAConfig.semantic_consolidation :module: foodscholar.config :type: SemanticConsolidationConfig :optional:

.. py:pydantic_field:: LayerAConfig.bottom_up_grouping :module: foodscholar.config :type: BottomUpGroupingConfig :optional:

Layer B

.. py:pydantic_model:: LayerBConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Layer B (theme discovery) — dual-pass + merge per the brief.

See layer_b_construction_brief.md §5 for the full knob list and the accompanying plan for the v1 decisions (Leiden default, LLM labels by default, embedded-fraction gate, etc.). algorithm="bertopic" swaps the Pass-1 discovery backend; Leiden remains the default.

Fields:
  • py:

    obj:algorithm (Literal['leiden', 'bertopic']) <foodscholar.config.LayerBConfig.algorithm>

  • py:

    obj:audit (foodscholar.config.LayerBAuditConfig) <foodscholar.config.LayerBConfig.audit>

  • py:

    obj:bertopic (foodscholar.config.BertopicConfig) <foodscholar.config.LayerBConfig.bertopic>

  • py:

    obj:global_similarity_max_chunks (int) <foodscholar.config.LayerBConfig.global_similarity_max_chunks>

  • py:

    obj:labeling (foodscholar.config.LabelingConfig) <foodscholar.config.LayerBConfig.labeling>

  • py:

    obj:leiden (foodscholar.config.LeidenConfig) <foodscholar.config.LayerBConfig.leiden>

  • py:

    obj:merge (foodscholar.config.MergeConfig) <foodscholar.config.LayerBConfig.merge>

  • py:

    obj:min_chunks_per_shelf (int) <foodscholar.config.LayerBConfig.min_chunks_per_shelf>

  • py:

    obj:min_embedded_fraction (float) <foodscholar.config.LayerBConfig.min_embedded_fraction>

  • py:

    obj:pass1_mode (Literal['global', 'per_shelf']) <foodscholar.config.LayerBConfig.pass1_mode>

  • py:

    obj:relatedness (foodscholar.config.RelatednessConfig) <foodscholar.config.LayerBConfig.relatedness>

  • py:

    obj:scope (Literal['direct', 'subtree']) <foodscholar.config.LayerBConfig.scope>

  • py:

    obj:similarity (foodscholar.config.SimilarityConfig) <foodscholar.config.LayerBConfig.similarity>

.. py:pydantic_field:: LayerBConfig.algorithm

module:

foodscholar.config

type:

Literal[‘leiden’, ‘bertopic’]

value:

‘leiden’

.. py:pydantic_field:: LayerBConfig.scope

module:

foodscholar.config

type:

Literal[‘direct’, ‘subtree’]

value:

‘direct’

.. py:pydantic_field:: LayerBConfig.min_embedded_fraction

module:

foodscholar.config

type:

float

value:

0.8

.. py:pydantic_field:: LayerBConfig.pass1_mode

module:

foodscholar.config

type:

Literal[‘global’, ‘per_shelf’]

value:

‘per_shelf’

.. py:pydantic_field:: LayerBConfig.global_similarity_max_chunks

module:

foodscholar.config

type:

int

value:

50000

.. py:pydantic_model:: SimilarityConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Pass 1 (similarity) graph + algorithm knobs.

algorithm is restricted to "leiden" in v1 — HDBSCAN is documented as a fallback in the brief but cut from v1 per the implementation plan.

Fields:
  • py:

    obj:algorithm (Literal['leiden']) <foodscholar.config.SimilarityConfig.algorithm>

  • py:

    obj:edge_threshold (float) <foodscholar.config.SimilarityConfig.edge_threshold>

  • py:

    obj:knn_k (int) <foodscholar.config.SimilarityConfig.knn_k>

  • py:

    obj:require_mutual (bool) <foodscholar.config.SimilarityConfig.require_mutual>

.. py:pydantic_model:: RelatednessConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Pass 2 (relatedness) graph knobs.

  • tau_strict: minimum entity-link confidence to participate in edges.

  • min_shared_ids: edge created iff >= this many shared FoodOn IDs.

  • max_doc_frequency: entities appearing in > this fraction of the shelf’s chunks are dropped (they carry no discriminative signal).

  • always_exclude_iris: never-edge-creators. The umbrella class FOODON:00001002 (‘food product’) is the default exclusion — it survived Layer A and gets ancestor-propagated onto almost every chunk.

Fields:
  • py:

    obj:algorithm (Literal['leiden']) <foodscholar.config.RelatednessConfig.algorithm>

  • py:

    obj:always_exclude_iris (list[str]) <foodscholar.config.RelatednessConfig.always_exclude_iris>

  • py:

    obj:max_doc_frequency (float) <foodscholar.config.RelatednessConfig.max_doc_frequency>

  • py:

    obj:min_shared_ids (int) <foodscholar.config.RelatednessConfig.min_shared_ids>

  • py:

    obj:tau_strict (float) <foodscholar.config.RelatednessConfig.tau_strict>

.. py:pydantic_model:: LeidenConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Shared by both passes. random_state is the determinism contract — same chunks + same seed = identical theme assignment across runs.

Fields:
  • py:

    obj:min_community_size (int) <foodscholar.config.LeidenConfig.min_community_size>

  • py:

    obj:n_iterations (int) <foodscholar.config.LeidenConfig.n_iterations>

  • py:

    obj:random_state (int) <foodscholar.config.LeidenConfig.random_state>

  • py:

    obj:resolution (float) <foodscholar.config.LeidenConfig.resolution>

.. py:pydantic_model:: MergeConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Greedy pair-assignment merge. combined_similarity = chunk_weight * J(chunks) + entity_weight * J(entities) per (sim_i, rel_j); pairs at or above dedupe_threshold collapse into discovery_pass="merged" themes.

Fields:
  • py:

    obj:chunk_weight (float) <foodscholar.config.MergeConfig.chunk_weight>

  • py:

    obj:dedupe_threshold (float) <foodscholar.config.MergeConfig.dedupe_threshold>

  • py:

    obj:entity_weight (float) <foodscholar.config.MergeConfig.entity_weight>

.. py:pydantic_model:: LabelingConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Theme labeling. "llm" is v1 default — navigation labels need to read well and per-run cost is ~$0.60. "keyword" (pure c-TF-IDF) is a free deterministic fallback. c-TF-IDF is always computed and fed to the LLM as keyword context.

Fields:
  • py:

    obj:llm_max_tokens (int) <foodscholar.config.LabelingConfig.llm_max_tokens>

  • py:

    obj:strategy (Literal['keyword', 'llm']) <foodscholar.config.LabelingConfig.strategy>

  • py:

    obj:top_keywords (int) <foodscholar.config.LabelingConfig.top_keywords>

Layer C

.. py:pydantic_model:: LayerCConfig

module:

foodscholar.config

Bases: :py:class:~pydantic.main.BaseModel

Fields:
  • py:

    obj:benchmark_out_dir (str) <foodscholar.config.LayerCConfig.benchmark_out_dir>

  • py:

    obj:grounding_check (Literal['strict', 'lenient', 'off']) <foodscholar.config.LayerCConfig.grounding_check>

  • py:

    obj:group_char_budget (int) <foodscholar.config.LayerCConfig.group_char_budget>

  • py:

    obj:llm_model (str) <foodscholar.config.LayerCConfig.llm_model>

  • py:

    obj:map_reduce_threshold (int) <foodscholar.config.LayerCConfig.map_reduce_threshold>

  • py:

    obj:max_summary_chars (int) <foodscholar.config.LayerCConfig.max_summary_chars>

  • py:

    obj:prompt_version (str) <foodscholar.config.LayerCConfig.prompt_version>

  • py:

    obj:safety_sensitive_facets (list[Literal['foods', 'health', 'sustainability', 'dietary_patterns', 'allergies', 'nutrients']]) <foodscholar.config.LayerCConfig.safety_sensitive_facets>

  • py:

    obj:sample_size (int) <foodscholar.config.LayerCConfig.sample_size>

  • py:

    obj:stage1_method (Literal['lexrank', 'lsa', 'luhn', 'textrank', 'nltk_freq']) <foodscholar.config.LayerCConfig.stage1_method>

  • py:

    obj:stage1_sentences (int) <foodscholar.config.LayerCConfig.stage1_sentences>

.. py:pydantic_field:: LayerCConfig.stage1_sentences

module:

foodscholar.config

type:

int

value:

8

.. py:pydantic_field:: LayerCConfig.map_reduce_threshold

module:

foodscholar.config

type:

int

value:

400

.. py:pydantic_field:: LayerCConfig.group_char_budget

module:

foodscholar.config

type:

int

value:

20000

.. py:pydantic_field:: LayerCConfig.max_summary_chars

module:

foodscholar.config

type:

int

value:

4000