Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d1b6af1
fix: prompt and LLM-plumbing defects found in the agent-node audit
DNSdecoded Jul 29, 2026
43ab52b
fix(citations): close numbering gaps between the answer and the sourc…
DNSdecoded Jul 29, 2026
ce3c9b1
fix(citations): drop dangling markers without leaving stray whitespace
DNSdecoded Jul 30, 2026
e2dc9e2
fix(llm): size LLM_MAX_TOKENS for thinking plus answer
DNSdecoded Jul 31, 2026
69ca80e
feat(providers): surface token-limit truncation to the user
DNSdecoded Jul 31, 2026
f213151
fix(library): keep ingested PDFs under their paper_id
DNSdecoded Jul 31, 2026
dee1226
fix(agent): stop pipeline timeouts and repair faithfulness scoring
DNSdecoded Aug 2, 2026
57a755a
chore(agent): log NLI duration separately from the completeness call
DNSdecoded Aug 2, 2026
f0d3993
fix(agent): don't skip the first evaluation on the loop budget
DNSdecoded Aug 2, 2026
7c0c2ec
docs(env): document the new timeout, budget and NLI knobs
DNSdecoded Aug 2, 2026
54d5928
docs(readme): document the timeout, budget and NLI calibration knobs
DNSdecoded Aug 2, 2026
df5e9e7
fix(agent): size AGENT_TIMEOUT so evaluation actually fits
DNSdecoded Aug 2, 2026
5dfe1a9
fix(agent): close two fail-open paths in faithfulness verification
DNSdecoded Aug 2, 2026
f9f122b
fix(citations): stop markers resolving to papers the prompt never showed
DNSdecoded Aug 2, 2026
7a236d6
fix(citations,report): whole-line dangling markers and localized fall…
DNSdecoded Aug 2, 2026
dfc92ef
fix(citations): compact the answer on the /query and /chat SSE paths
DNSdecoded Aug 2, 2026
d99bc31
docs(readme): document the SSE done-event contract and citation integ…
DNSdecoded Aug 2, 2026
9a82bf3
fix(streaming): stop the unary timeout from cutting long answers mid-…
DNSdecoded Aug 2, 2026
0dcc156
docs(config): correct the false claim about the failover budget
DNSdecoded Aug 2, 2026
f40b5fd
diag(streaming): log why a stream died, and mark the answer incomplete
DNSdecoded Aug 2, 2026
14d54e0
@
DNSdecoded Aug 7, 2026
284b74b
docs(thinking): document thinking_level and the medium-default trap
DNSdecoded Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 57 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,50 @@ LLM_FALLBACK_MODEL=gemma-4-26b-a4b-it

# Response generation parameters
LLM_TEMPERATURE=0.3
LLM_MAX_TOKENS=2048
LLM_MAX_TOKENS=8192
AGENT_MAX_TOKENS=8192
AGENT_TIMEOUT=120
# Hard ceiling for one agentic run (seconds). Measured on a CPU-only box:
# ~45s retrieval + ~50s generation + ~25s evaluation. Anything near 120 leaves no
# room for AGENT_EVAL_RESERVE_S below, so verification gets skipped on every run.
AGENT_TIMEOUT=300
# Per-request HTTP timeout for every LLM call (seconds). Without it the SDK
# defaults apply (OpenAI: 600s x 2 retries), so ONE stalled request outlives the
# whole agent budget. Sized for a legitimate call, not for the failover chain:
# agent answer generation is unary and measured 20-50s on CPU, so a materially
# lower value aborts real generations. Failover walks up to 3 (provider, model)
# attempts sequentially, so a fully-stalled chain can reach ~180s — past
# AGENT_REFLEXION_BUDGET_S, though still inside AGENT_TIMEOUT, which finalises
# the draft rather than 504.
LLM_REQUEST_TIMEOUT_S=60
# Separate budget for STREAMED calls. For Gemini the HTTP timeout covers the whole
# stream rather than the gap between chunks, so reusing the 60s unary value above
# tore down long answers mid-generation (WinError 10054, truncated text).
LLM_STREAM_TIMEOUT_S=300
# Thinking budget: 0=off (cheapest), -1=dynamic, N=cap N thinking tokens.
# Note: some models (e.g. gemini-3.6-flash) reject a literal 0 budget. The
# Gemini backend detects that once per model and omits the field instead, so
# 0 stays safe — it just means "no thinking config" on those models.
AGENT_THINKING_BUDGET=0
# Thinking LEVEL — the Gemini 3.x control, which SUPERSEDES thinking_budget above.
# gemini-3.6-flash rejects a budget outright and defaults to MEDIUM thinking when
# nothing is sent, so "budget 0" used to mean medium thinking in practice, billed
# and taken out of LLM_MAX_TOKENS. Values: minimal | low | medium | high, or empty
# to accept the model's own default.
LLM_THINKING_LEVEL=minimal
AGENT_THINKING_LEVEL=minimal
# Max sub-queries per reflexion cycle (main agent-latency lever)
AGENT_MAX_SUB_QUERIES=3
# Wall-clock budget for the reflexion loop (seconds). Once exceeded, the
# Wall-clock budget for the reflexion LOOP (seconds). Once exceeded, the
# evaluator finalizes the current draft instead of starting another
# retrieve -> generate -> verify cycle. Keep BELOW AGENT_TIMEOUT so it fires
# first and the user gets an answer rather than a 504 that discards the work.
# retrieve -> generate -> verify cycle. Applies from iteration 2 onwards only:
# on a CPU-only box the first pass alone runs past it, and gating iteration 1
# here shipped every answer with no faithfulness score and no confidence.
AGENT_REFLEXION_BUDGET_S=90
# Wall-clock room that must remain under AGENT_TIMEOUT for the evaluator to
# attempt an evaluation at all — this one CAN skip iteration 1, but only when
# finishing would overrun the timeout and discard the draft entirely. Sized for
# one NLI pass plus one completeness call (measured ~30s + ~15s on CPU, doubled).
AGENT_EVAL_RESERVE_S=90
# Context caps for agentic mode. Wider than standard RAG because the agent pools
# passages from several tools and the 12-chunk cut truncated equation chunks.
AGENT_MAX_CONTEXT_CHUNKS=20
Expand Down Expand Up @@ -301,11 +330,32 @@ NLI_MODEL_NAME=MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7
# cross-encoder/nli-deberta-v3-base: 0=contradiction, 1=entailment, 2=neutral
NLI_ENTAILMENT_INDEX=0

# A claim scoring below this is logged as ungrounded.
FAITHFULNESS_THRESHOLD=0.5
# A claim scoring below this is logged as ungrounded. MUST be calibrated against
# the model above — this is not a "confidence percentage" you can set by taste.
# Measured for the default model: a sentence copied VERBATIM out of its own chunk
# scores median 0.226 / max 0.428, an unrelated paper's sentence median 0.099 /
# p90 0.158. The old 0.5 sat above every positive, so faithfulness read ~0 for
# every answer ever produced. 0.15 gives recall 0.70 at false-positive 0.10-0.15.
# Recalibrate (positives vs cross-paper negatives) whenever NLI_MODEL_NAME changes.
FAITHFULNESS_THRESHOLD=0.15
# What to do with ungrounded claims: warn | strip | regen
FAITHFULNESS_ENFORCE=warn

# Fraction of an answer's claims that must be grounded for the reflexion loop to
# accept it and for the finalizer to trust it enough to abstain on completeness
# alone. Below 1.0 by design: per-claim recall is ~0.70, so even a fully grounded
# answer lands near 0.70 — a 0.75 bar could never fire.
AGENT_FAITHFULNESS_ACCEPT=0.6

# NLI cost is linear in pairs AND in premise length. Measured on a CPU-only box
# with the int8 ONNX model: 1.15s/pair at 512 tokens, 0.4s at 256. A 30-sentence
# answer citing multi-chunk papers hit ~275 pairs = 318s in ONE reflexion pass.
# These two knobs bound it (measured no quality cost: positive median 0.226 at
# 256 vs 0.221 at 512). Chunks arrive rerank-ordered, so the first are the best
# support. Drop the cap to 1 if the NLI pass still dominates your run.
NLI_MAX_SEQ_LENGTH=256
NLI_MAX_CHUNKS_PER_CITATION=2


# ==============================================================================
# Caching (in-process, per worker)
Expand Down
Loading
Loading