genkit_ops 0.1.3
genkit_ops: ^0.1.3 copied to clipboard
Caching, terminal diagnostics, redaction, and operational middleware for Genkit Dart generate calls.
// Customer support cache example.
//
// Scenario:
// A support product receives many repeated questions:
// - "How do I reset my password?"
// - "What is your refund policy?"
// - "How can I cancel my subscription?"
//
// Without caching, each repeated question may create another paid model call.
// With genkit_ops, repeated model requests can be returned from memory while
// the terminal still shows cache hit/miss, latency, finish reason, and token
// usage reported by the provider.
//
// Before running this example in a real app:
// 1. Add a Genkit model provider package, such as Google AI or Vertex AI.
// 2. Register that provider plugin in the Genkit plugins list below.
// 3. Configure a default model or pass a model to _ai.generate().
import 'package:genkit/genkit.dart';
import 'package:genkit_ops/genkit_ops.dart';
/// A simplified support ticket from an ecommerce application.
final class SupportTicket {
/// Creates a support ticket.
const SupportTicket({
required this.id,
required this.tenantId,
required this.customerQuestion,
required this.customerPlan,
required this.locale,
});
/// Internal ticket id used by the application.
final String id;
/// Tenant, workspace, or merchant id.
final String tenantId;
/// Customer's original question.
final String customerQuestion;
/// Customer's plan or segment, used in the prompt.
final String customerPlan;
/// Locale used both in the prompt and cache key.
final String locale;
}
/// Version of the policy prompt used by support answers.
///
/// Bump this value whenever refund, delivery, cancellation, or security policy
/// text changes. Changing it invalidates old cache keys without clearing the
/// whole store.
const _supportPolicyVersion = 'support-policy-2026-01';
/// Memory cache for support replies.
///
/// In production, this interface can be backed by Redis, a database, Hive, Isar,
/// or another store by implementing `CacheStore<ModelResponse>`.
final _supportCache = InMemoryCacheStore<ModelResponse>(
defaultTtl: const Duration(minutes: 30),
maxEntries: 1000,
);
/// Terminal logger for local development.
///
/// The default info level prints metadata such as cache status, latency, finish
/// reason, and token usage. It does not print raw prompt/output payloads.
final _supportLogger = GenkitOpsLogger(
level: GenkitOpsLogLevel.info,
sink: (message) {
// Replace this with package:logging, Sentry breadcrumbs, or your internal
// logger if you do not want to print directly in production.
print(message);
},
);
final _ai = Genkit(
plugins: [
// Register your model provider plugin here.
//
// Example shape:
// googleAI(),
//
// Then either configure a default model on Genkit or pass `model:` to
// _ai.generate().
GenkitOpsPlugin(cacheStore: _supportCache, logger: _supportLogger),
],
);
/// Generates a support reply with cache and terminal diagnostics.
Future<String> answerSupportTicket(SupportTicket ticket) async {
final response = await _ai.generate(
prompt: _buildSupportPrompt(ticket),
context: {
// Only selected context keys affect the cache key. See contextKeys below.
'tenantId': ticket.tenantId,
'locale': ticket.locale,
},
use: genkitOps(
policy: CachePolicy(
// Namespace prevents one tenant's cache from being reused by another.
namespace: 'support-replies-${ticket.tenantId}',
// Deterministic invalidation for policy/prompt changes.
keyVersion: _supportPolicyVersion,
// Repeated answers are useful for a short support window, but should
// expire so policy updates and fresh context can take effect.
ttl: const Duration(minutes: 30),
// Only these context values are included in the cache identity.
contextKeys: const ['tenantId', 'locale'],
),
logger: _supportLogger,
),
);
return response.text;
}
String _buildSupportPrompt(SupportTicket ticket) {
return '''
You are a concise support assistant for an ecommerce application.
Rules:
- Answer in the customer's locale.
- Give clear next steps.
- Do not invent refund, cancellation, or security policy details.
- If policy details are missing, ask the customer to contact support.
- Keep the answer under 120 words.
Customer metadata:
- Plan: ${ticket.customerPlan}
- Locale: ${ticket.locale}
Customer question:
${ticket.customerQuestion}
''';
}
Future<void> main() async {
final firstTicket = SupportTicket(
id: 'ticket-1001',
tenantId: 'merchant-acme',
customerQuestion: 'How do I reset my password?',
customerPlan: 'Pro',
locale: 'en-US',
);
final repeatedTicket = SupportTicket(
id: 'ticket-1002',
tenantId: 'merchant-acme',
customerQuestion: 'How do I reset my password?',
customerPlan: 'Pro',
locale: 'en-US',
);
// Expected behavior after a model provider is configured:
// 1. The first call is a cache miss and reaches the model.
// 2. The second call has the same prompt/config/context and returns from
// cache while the logger prints a cache-hit event.
final firstAnswer = await answerSupportTicket(firstTicket);
final secondAnswer = await answerSupportTicket(repeatedTicket);
print('First answer:\n$firstAnswer\n');
print('Second answer:\n$secondAnswer\n');
}