Configuration¶
Settings load from a YAML file and a .env file into Pydantic models:
from pathlib import Path
from sci_etl_core import BaseAppConfig, load_config
config = load_config(BaseAppConfig, Path("config.yaml"), Path(".env"))
print(config.llm.model, config.pipeline.total_limit)
llm:
base_url: https://api.openai.com/v1
model: gpt-4o-mini
timeout: 120
http:
user_agent: "my-project/1.0 (mailto:you@example.org)"
max_retries: 3
timeout: 25
full_text:
max_concurrency: 4
pipeline:
search_query: "all:galaxy"
total_limit: 100
page_size: 100
search_delay: 3.0
sleep_between: 5.0
max_concurrency: 6
newest_first: true
search:
bm25: {title: 10, abstract: 4, body: 1}
fusion: {k: 60}
hybrid: {candidate_pool: 100, chunk_pool_factor: 5}
graph: {depth: 2, fanout: 8, min_weight: 0.35}
| Section | Model | Fields (defaults) | Builds |
|---|---|---|---|
llm |
LLMConfig |
api_key, base_url (https://api.openai.com/v1), model (gpt-4o-mini), timeout (120) |
AsyncOpenAICompatibleClient.from_config |
http |
HttpConfig |
user_agent (sci-etl-core/<installed version>), max_retries (3), backoff_factor (2.0), timeout (25) |
build_client(), AsyncArxivExtractor.from_config |
full_text |
RateLimitConfig |
max_concurrency (4), max_rate (unset), time_period (1.0) |
build_limiter() |
pipeline |
PipelineConfig |
search_query (""), total_limit (100), page_size (100), search_delay (3.0), sleep_between (5.0), max_concurrency (6), newest_first (false) |
AsyncETLPipeline.from_config, run_arguments(), AsyncArxivExtractor.from_config |
search |
SearchConfig |
bm25, fusion, hybrid, graph, with the defaults of the dataclasses they build |
bm25.to_weights(), fusion.to_params(), hybrid.to_params(), graph.to_params() |
Building components from the config¶
Each section builds, or is passed to, the components it configures. Values you pass yourself override the config:
from sci_etl_core import AsyncArxivExtractor, AsyncETLPipeline, AsyncOpenAICompatibleClient
from sci_etl_core.parsers import LatexTarballParser, PdfPlumberParser
extractor = AsyncArxivExtractor.from_config(
config.http,
config.pipeline,
client=config.http.build_client(),
pdf_parser=PdfPlumberParser(),
latex_parser=LatexTarballParser(),
rate_limiter=config.full_text.build_limiter(),
)
llm = AsyncOpenAICompatibleClient.from_config(config.llm)
pipeline = AsyncETLPipeline.from_config(
config.pipeline,
extractor=extractor,
relevance_filter=relevance_filter,
entity_extractor=entity_extractor,
exporter=exporter,
state_manager=state_manager,
destination="results.csv",
)
await pipeline.run(**config.pipeline.run_arguments())
AsyncArxivExtractor.from_config takes max_retries and backoff_factor
from http and search_delay from pipeline. AsyncOpenAICompatibleClient
takes api_key (the loaded SecretStr as-is), base_url, model, and
timeout as default_timeout. Both accept any other constructor argument,
such as logger or rate_limiter, as a keyword. The PubMed, Semantic
Scholar, and OpenAlex extractors have no from_config; pass
config.http.max_retries and config.http.backoff_factor to their
constructors yourself. The pipeline takes max_concurrency, and run_arguments() returns
query, page_size, total_limit, sleep_between, and newest_first for
run(). ETLPipeline.from_config works the same way.
The search section builds the parameter dataclasses for local search:
from sci_etl_core.search import AsyncHybridSearcher, AsyncSqliteFts5Store
store = AsyncSqliteFts5Store("search.db", weights=config.search.bm25.to_weights())
searcher = AsyncHybridSearcher(
store,
finder,
fusion=config.search.fusion.to_params(),
params=config.search.hybrid.to_params(),
)
Renamed pipeline settings¶
In 0.4, pipeline.max_records became total_limit and pipeline.max_workers
became max_concurrency, the names run() and the pipeline use. The old keys
still load, with a DeprecationWarning, until 0.5. Setting an old and a new key
to different values is a ConfigurationError.
Details¶
- API key. The key comes from the
LLM_API_KEYenvironment variable (choose another withapi_key_env_var=), which can be loaded from a.envfile: the one you pass, or else the first.envfound from the current working directory upward. It is stored as a PydanticSecretStr, so it doesn't show up in reprs or logs. Variables already set in the environment take precedence over.env; copy.env.exampleto get started. - The environment wins over YAML. When the variable is set, it overrides
any
llm.api_keyin the YAML file, which is used only as a fallback. Keep keys out of config files anyway. - Project-specific settings.
BaseAppConfigaccepts extra top-level keys, or you can subclass it. - Async loading.
load_config_asynctakes the same arguments. - Errors. A missing or unparseable YAML file, a file whose top level isn't
a mapping, and a validation failure all raise
ConfigurationError. Validation checks ranges too: counts such asmax_concurrency,page_size, andmax_retriesmust be at least 1, timeouts andtime_periodmust be positive, delays andtotal_limitmust not be negative, BM25 weights must be finite and not negative, andgraph.min_weightmust be finite.fusion.weightsis checked only whenfusion.to_params()builds the parameters, which raisesValueErrorfor a negative or non-finite weight. A validation message lists each failing key and the reason on its own line but never the value, so an API key can't reach a log through it.validate_config(config_cls, raw, source)applies the same checks to settings loaded some other way.