* fix(summarization): resolve fraction triggers from declared context_window, degrade instead of crashing the agent build
A fraction trigger/keep clause requires profile["max_input_tokens"], which any
third-party OpenAI-compatible model lacks, so SummarizationMiddleware
construction raised ValueError out of create_summarization_middleware and failed
the whole agent build (#3103).
- factory: translate a declared model context_window into the langchain
profile (metadata-only, never reaches the provider payload); explicit
caller/override profiles win
- summarization factory: drop unusable fraction trigger clauses (absolute
clauses survive), fall a fraction keep back to the messages default, and
disable compaction with an actionable warning only when no usable trigger
clause remains — the agent build never dies from summarization config
- docs: config.example.yaml, ModelConfig.context_window, summarization.md
* refactor(summarization): share the default keep constant with the fraction fallback
The fraction-keep degradation fallback hardcoded ("messages", 20),
duplicating SummarizationConfig.keep's default_factory literal. Move the
value to a shared DEFAULT_KEEP constant so the two cannot drift apart.
* fix(summarization): keep trigger-null + fraction-keep constructing after degradation
A trigger of None with a fraction keep hit the all-clauses-dropped branch
(has_usable_trigger=False) and disabled compaction, and the accompanying
warning claimed configured triggers were all fraction-based when none were
configured. Only report nothing-usable when trigger clauses actually
existed; trigger:null keeps constructing the never-firing middleware with
the degraded keep, matching its behavior outside the degradation path.
* fix(summarization): address review — keep manual compaction, validate ContextSize, pin wiring
Review follow-ups on #4901:
- When every configured trigger is a dropped fraction clause, keep
constructing the never-firing middleware (trigger=None) instead of
returning None: manual /compact runs with force=True and never consults
trigger clauses, so it must keep working for a profile-less model
rather than reporting 'compaction is disabled'. The warning now says
auto-compaction will not fire while manual compaction remains.
- ContextSize gains a config-load validator: fraction values must be in
(0,1] (a percent-style 80 instead of 0.8 previously produced a threshold
the context could never reach — a silently inert trigger), absolute
values must be positive.
- New un-monkeypatched integration test pins the shipped wiring
(context_window declared -> real factory attaches profile -> fraction
clause survives -> middleware constructs), which the stubbed
middleware-side tests and kwarg-capturing factory-side tests each
stopped short of.
- Docs (summarization.md + config.example.yaml) clarify that the fraction
resolves against the summary/anchor model's context_window
(summarization.model_name when set, else the run model), including the
mismatch caveat for a larger-window summary model.
* fix(summarization): reject non-finite ContextSize values at config load
YAML .nan / .inf pass pydantic's float parsing, and nan <= 0 is False,
so the positivity check alone let them through as dead thresholds
(count >= nan is always False) — the same silent-inert-trigger class the
range validator was added to close. Guard with math.isfinite first,
consistent with the existing non-finite guards on mem0 timeout_seconds
and poll_after_seconds.
* fix(summarization): merge context_window into inferred profile, require whole message counts
- construct the model first, then merge max_input_tokens into the
provider-inferred langchain profile: passing profile= to the
constructor replaced the whole inferred metadata (tool_calling,
structured_output, io capabilities, output limits) with the single
key. An explicitly configured profile is still never clobbered.
- reject non-integral ContextSize values for type=messages at config
load: langchain slices the message list with them, so a float index
raised TypeError mid-compaction.
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>