mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-24 23:12:53 +08:00
* feat(agent): size agent history by the window and calibrate token estimates Agent history was capped at history_turns (default 5) because compaction could not survive a turn. With checkpoints persisted, load history by tokens instead, and let compaction bound the session. Budget. The loader drops the oldest turns that do not fit, and what it drops is lost: neither replayed nor summarized. A budget equal to the compaction threshold (the earlier attempt, since reverted) let the loader trim first, so whenever one turn was larger than the system prompt plus the new question the request never crossed the threshold; nothing was summarized and the session became a sliding window with no checkpoint. agent.HistoryTokenBudget is now the whole window: overflow reaches the first round's compaction, which summarizes it into a new checkpoint. A turn-by-turn regression test drives the real loader, budget and compactor and fails as soon as a stored turn after the checkpoint is missing from the loaded history; restoring the old budget fails it. Summarizer input. History can now reach the window, so the part being summarized can outgrow one request. An overflowing request degrades to a raw archive that is never persisted, so every later turn would fail the same way. Prepare keeps the newest messages that fit, the prompt states how many were left out, and file paths still come from all of them. Calibration. cl100k counts Chinese at about a token a character where Qwen and DeepSeek spend about 0.6. The estimator now carries a scale (clamped to 0.5-1.5, text only), learned from consecutive requests in a turn: they share tools, system prompt and earlier messages, so the provider's growth between them prices exactly the appended messages, without mixing in how a provider renders tool schemas. The turn's scale is stored on its usage (context_token_scale); the next turn's loader prices history with it and hands it to the engine. Loading, the first compaction check and compaction itself share one estimator, so they agree; calibrating only the trigger would let the loader drop turns again, which the regression test covers. Also: history is priced as the engine sends it (redacted KB/Wiki results cost one line); a checkpoint older than the rows read is matched by the assistant rows' (created_at, id) order; a read ending on a turn's question keeps that turn; a compaction discarded for freeing under 5% still persists its checkpoint; a checkpoint write matching no row is an error; a partial index backs the newest-checkpoint lookup. Agent mode no longer reads history_turns: the field leaves AgentConfig, the editor hides it in smart-reasoning mode, and quick-answer keeps its turn cap. * fix(agent): address review of window-sized history loading Summarization. The summarizer call had a fixed 60s total timeout. With history loaded up to the window, a request could carry ~175k tokens and be cut off while prefilling or writing normally; the degraded result left no checkpoint, so the next turn reloaded the same history and failed the same way. Summarization now streams and is cancelled only after no output for the engine's stall timeout (120s by default, LLMCallTimeout overrides), with the overall ceiling left to the transport, as for the engine's own rounds and as in Codex and Pi. A stopped turn is not retried. When the summarizer still fails, the raw archive is persisted as a checkpoint marked degraded so the loop is broken, and the next compaction refines it as the previous summary. The archive is held to the summary budget, newest messages first, so a degraded compaction always frees room. Loading. The loader priced turns as sent but returned and held them as stored, so wiki pages stored in full and sent as one line could pile up in memory. It now returns the as-sent history and releases each turn's stored rows once replayed, keeping about a page of rows at a time. Index. The partial checkpoint index is replaced by a composite (session_id, created_at DESC, id DESC) index serving the backwards page, the newest-checkpoint lookup and the recent-messages reads. It is built with CREATE INDEX CONCURRENTLY so writes to messages are not blocked; the migration holds that single statement, since a multi-statement query runs as an implicit transaction. A PostgreSQL integration test, skipped without WEKNORA_MIGRATION_TEST_POSTGRES_DSN, applies the chain, runs 000106 down and up through golang-migrate, and checks both queries use the index without a sort; the SQLite migration test checks the same plans. Calibration. It no longer waits for a total token count, and a usage with only a scale is persisted. A turn that measures nothing carries forward the scale it started from, so the newest turn always has the latest one.
974 KiB
974 KiB