跳到正文
DEV Community· MICHAEL-MAURICE·· 3 小時前AI 評分53

Microsoft.Extensions.AI 10.10 如何用 Reducer 縮減聊天記錄佔用的上下文窗口

Your Chat History Is Eating the Context Window — Fix It With a Reducer

AI 導讀

在 Microsoft.Extensions.AI 10.10 中,IChatReducer、ReducingChatClient 和 UseChatReducer 可用於縮減送入模型的聊天記錄。

正文

Turn nine of a support chat feels fine. Turn ninety is a different animal. Every request resends the system prompt, three agent loops of tool noise, the user's third restatement of the same bug, and yesterday's dark-mode tangent. The model does not mind. Your invoice does — and attention quality usually dies before the hard token limit.

messages.TakeLast(20) in one controller is not a policy. The second entry point forgets it. A tool-using agent keeps a function call without its result. Someone asks why the bot forgot the account ID from turn four.

Microsoft.Extensions.AI 10.10 (experimental MEAI001) gives you IChatReducer + ReducingChatClient + UseChatReducer. This post is the short "what breaks first" guide. The full offline-tested project (counting, summarizing, token-aware reducers, session store, tests) is in Tech Skill Builder — $5/month for a very limited time only (normally $10). Grab it now before it goes back to $10.

What a reducer actually does

Task<IEnumerable<ChatMessage>> ReduceAsync(
    IEnumerable<ChatMessage> messages,
    CancellationToken cancellationToken);

ReducingChatClient runs that on the inbound list, then forwards the reduced list to the inner client for both normal and streaming calls:

IChatClient client = new ChatClientBuilder(inner)
    .UseChatReducer(reducer)
    .Build();

Reduction is about what the model sees. Keep the full transcript in your session store for GET /history. Do not delete the system of record because you trimmed the wire payload.

Trap 1: Orphan tool pairs

Naive "keep last N" walks into this the first week you enable tools. A FunctionCallContent without its matching FunctionResultContent (or the reverse) gets you provider 400s that look like model flakiness.

Built-in counting and summarizing reducers exclude tool call/result messages from the reduced view. Your raw store can still keep them for debug. If you write a custom reducer, plant a mixed history in a unit test and assert neither content type survives unless you deliberately opt out.

Trap 2: Counting messages when finance counts tokens

MessageCountingChatReducer(targetCount) keeps the first system message and up to targetCount recent non-system turns. Cheap and predictable for FAQ / short troubleshooting. Six huge pasted stack traces still blow the bill.

A custom TokenAwareChatReducer estimates tokens (characters÷4 is fine as a guardrail), always keeps the first system prompt, walks newest → oldest until the budget fills, and drops tool pairs by default:

public sealed class TokenAwareChatReducer : IChatReducer
{
    public TokenAwareChatReducer(int tokenBudget, bool dropToolPairs = true) { /* ... */ }

    public Task<IEnumerable<ChatMessage>> ReduceAsync(
        IEnumerable<ChatMessage> messages,
        CancellationToken cancellationToken)
    {
        // keep system → walk newest→oldest under budget → drop incomplete tool pairs
        // full implementation in the complete project
        return Task.FromResult<IEnumerable<ChatMessage>>(/* reduced */);
    }
}

Trap 3: Summarizing with the flagship model

SummarizingChatReducer(summarizer, targetCount, threshold) fires when unsummarized non-system messages exceed targetCount + threshold. Older turns become one assistant summary; recent turns stay verbatim; system stays; tool content stays out of summarizer input.

Point the summarizer at a small, cheap (or offline) client. Reusing your frontier model for every compaction invents a second invoice while you celebrate the first savings. Also: summarization may stamp __summary__ into AdditionalProperties on a boundary message. Preview endpoints should clone messages first so you do not mutate the live session store.

Trap 4: Forgetting the system prompt is sacred

Silence the system message to "save tokens" and you have a different product. Every reducer shape above keeps it. If the system prompt alone is huge, fix the prompt — do not quietly drop it in production.

Minimal session surface

Method Path Purpose
POST /api/sessions create session
POST /api/sessions/{id}/messages chat (returns before/after counts)
GET /api/sessions/{id}/history raw transcript
GET /api/sessions/{id}/reduced counting / summarizing / token preview

Config: mode, target count, threshold, token budget, system prompt. One pipeline shared by API, worker, and CLI. Swap the offline inner client for OpenAI/Azure/Ollama later — keep the reducer layer.

Pin Microsoft.Extensions.AI 10.10 and treat MEAI001 as usable-but-evolving. Experimental does not mean toy; it means names can move.


Want the runnable .NET 10 package with counting / summarizing / token-aware modes, an offline summarizer, and session APIs? The complete working project is in Tech Skill Builder — $5/month for a very limited time only (normally $10). Grab it now before it goes back to $10.

來源:DEV Community · dev.to