flutter_llm_memory 0.1.0 copy "flutter_llm_memory: ^0.1.0" to clipboard
flutter_llm_memory: ^0.1.0 copied to clipboard

Intelligent memory layer for LLM chat apps. Manages conversation history and token budgets with sliding window, summarization, and hybrid strategies.

flutter_llm_memory #

A memory layer that decides which conversation messages to send to an LLM, so requests stay within the context window and cost stays down.

The problem #

LLMs are stateless. To keep a conversation coherent you resend the history on every request. As the conversation grows, each request gets larger, costs rise, and eventually you hit the model's context limit and the request fails. This package sits between your chat code and your LLM client and manages what gets sent: it tracks history, estimates tokens, prunes or summarizes old messages, and reports how many tokens you saved.

Install #

dependencies:
  flutter_llm_memory: ^0.1.0
dart pub add flutter_llm_memory

Quick start #

import 'package:flutter_llm_memory/flutter_llm_memory.dart';

final memory = ConversationMemory(
  strategy: SlidingWindowStrategy(maxTokens: 4000),
);

await memory.add(Message.user('Hello, I need help with my order'));
await memory.add(Message.assistant('Sure, what is your order number?'));
await memory.add(Message.user('It is 48213'));

// Build the messages to send to your LLM.
final (context, stats) = await memory.buildContext();

// `context` is a List<Message>. Map it to your provider's request shape.
final payload = context
    .map((message) => {'role': message.role.name, 'content': message.content})
    .toList();

print('Sending ${stats.currentTokens} tokens, saved ${stats.tokensSaved}');

The package never calls an LLM. You map context to your provider and make the request yourself.

Strategies #

Strategy Best for LLM calls needed
SlidingWindowStrategy Short sessions, no summarization No
SummarizationStrategy Long sessions, fresh summary each time Yes
HybridMemoryStrategy Long sessions, incremental memory Yes

SlidingWindowStrategy #

Keeps the most recent messages that fit in the budget. System messages are always kept.

final strategy = SlidingWindowStrategy(maxTokens: 4000);

SummarizationStrategy #

Once the conversation exceeds the budget, older messages are summarized by your function and replaced with a single summary message. The most recent keepRecentCount messages stay in full.

final strategy = SummarizationStrategy(
  maxTokens: 4000,
  keepRecentCount: 10,
  summarizer: (olderMessages) => summarizeWithMyLlm(olderMessages),
);

HybridMemoryStrategy #

Keeps a running summary that grows incrementally plus a window of recent messages. It summarizes only the oldest overflow messages per event, so it makes one summarization call per overflow instead of resummarizing everything.

final strategy = HybridMemoryStrategy(
  maxTokens: 4000,
  keepRecentCount: 8,
  summarizeChunkSize: 4,
  summarizer: (olderMessages) => summarizeWithMyLlm(olderMessages),
);

If your summarizer throws, all three summarizing strategies fall back to sliding window behavior for that call instead of crashing.

Persistence #

History is in memory by default. Provide a MemoryStorage adapter to persist it, then restore on startup.

final memory = await ConversationMemory.restore(
  storage: SharedPreferencesStorage(),
  strategy: HybridMemoryStrategy(
    maxTokens: 4000,
    summarizer: summarizeWithMyLlm,
  ),
);

MemoryStorage is a three-method interface (save, load, clear). The package ships InMemoryStorage. A SharedPreferencesStorage reference implementation lives in the example app; copy it or adapt it for Hive or SQLite.

Stats #

Every buildContext() returns a MemoryStats, also available as memory.stats after the call.

final (context, stats) = await memory.buildContext();

stats.currentTokens;        // tokens in the context that will be sent
stats.totalTokens;          // tokens in the full untrimmed history
stats.tokensSaved;          // totalTokens - currentTokens
stats.messageCount;         // messages in the full history
stats.contextMessageCount;  // messages in the returned context
stats.compressionRatio;     // currentTokens / totalTokens, 1.0 means no compression
stats.isSummarized;         // whether a summary block is present

Bring your own LLM #

This package does not depend on any provider and never makes a network call. You pass a summarizer and you send the built context yourself. Here is a summarizer backed by OpenAI:

Future<String> summarizeWithMyLlm(List<Message> messages) async {
  final transcript =
      messages.map((m) => '${m.role.name}: ${m.content}').join('\n');
  final response = await http.post(
    Uri.parse('https://api.openai.com/v1/chat/completions'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $openAiKey',
    },
    body: jsonEncode({
      'model': 'gpt-4o-mini',
      'messages': [
        {'role': 'system', 'content': 'Summarize this conversation concisely.'},
        {'role': 'user', 'content': transcript},
      ],
    }),
  );
  final data = jsonDecode(response.body) as Map<String, dynamic>;
  return data['choices'][0]['message']['content'] as String;
}

Model token limits reference #

Model Context window
GPT-4o 128k
Claude Sonnet 200k
Gemini 1.5 Pro 1M
Llama 3 70B 8k

Set maxTokens to about 80% of the model limit to leave room for the response.

Contributing #

PRs are welcome. For anything larger than a bug fix, open an issue first so we can agree on the approach before you write code.

License #

MIT. See LICENSE.

0
likes
160
points
28
downloads

Documentation

API reference

Publisher

verified publishermhammad.app

Weekly Downloads

Intelligent memory layer for LLM chat apps. Manages conversation history and token budgets with sliding window, summarization, and hybrid strategies.

Repository (GitHub)
View/report issues

Topics

#ai #llm #memory #chat #context

License

MIT (license)

Dependencies

meta

More

Packages that depend on flutter_llm_memory