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

Retrieval-augmented generation for the Akashi agent framework: a provider-neutral Retriever seam, a pure-Dart in-memory vector store, document chunking, and glue to wire retrieval into an agent as a tool.

example/akashi_rag_example.dart

// A self-contained tour of akashi_rag: build a KnowledgeBase, give an agent a
// retrieval tool over it, and answer a question grounded in the indexed docs.
//
// It runs offline with NO API key: a bag-of-words embedder stands in for a real
// embedding model, and a scripted model stands in for a real LLM. To go live,
// swap `_BagOfWordsEmbedder` for `provider.embeddingModel('text-embedding-004')`
// and `_ScriptedModel` for `provider.languageModel('gemini-2.5-flash')` (see the
// comment at the bottom).
//
// Run with: dart run example/akashi_rag_example.dart
import 'dart:io';

import 'package:akashi/akashi.dart';
import 'package:akashi_rag/akashi_rag.dart';

Future<void> main() async {
  // 1. Build a knowledge base and index a tiny corpus.
  final kb = KnowledgeBase(
    embedder: _BagOfWordsEmbedder(const [
      'refund',
      'refunds',
      'shipping',
      'ship',
      'warranty',
      'days',
      'return',
    ]),
    store: InMemoryVectorStore(),
  );
  await kb.addDocuments(const [
    Document(
      id: 'refunds',
      text: 'Refunds are processed within 5 business days of approval.',
      metadata: {'source': 'refunds'},
    ),
    Document(
      id: 'shipping',
      text: 'Orders ship within 1 to 2 days via standard post.',
      metadata: {'source': 'shipping'},
    ),
    Document(
      id: 'warranty',
      text: 'Every product carries a 2 year limited warranty.',
      metadata: {'source': 'warranty'},
    ),
  ]);

  // 2. Give an agent a retrieval tool over the knowledge base.
  final agent = ToolLoopAgent<Object?>(
    model: _ScriptedModel([
      [
        const ToolCallCompletePart(
          toolCallId: 'c1',
          toolName: 'search_knowledge_base',
          input: {'query': 'how long do refunds take'},
        ),
        const FinishPart(FinishReason.stop),
      ],
      [
        const TextDeltaPart('Refunds are processed within 5 business days.'),
        const FinishPart(FinishReason.stop),
      ],
    ]),
    instructions: 'Answer using the knowledge base. Cite the snippet you used.',
    tools: [retrievalTool(kb)],
  );

  // 3. Run a query end to end.
  await for (final event in agent.stream('How long do refunds take?')) {
    switch (event) {
      case ToolResult(:final result):
        stdout.writeln('[retrieved]\n${result.output}\n');
      case TextDelta(:final text):
        stdout.write(text);
      case RunFinish():
        stdout.writeln();
      default:
        break;
    }
  }

  // ---------------------------------------------------------------------------
  // Going live — swap the fakes for real providers (akashi_google shown):
  //
  //   final provider = GoogleProvider(apiKey: Platform.environment['GEMINI_API_KEY']!);
  //   final kb = KnowledgeBase(
  //     embedder: provider.embeddingModel('text-embedding-004')!,
  //     store: InMemoryVectorStore(),
  //   );
  //   final agent = ToolLoopAgent(
  //     model: provider.languageModel('gemini-2.5-flash'),
  //     tools: [retrievalTool(kb)],
  //   );
  //
  // Scaling ingestion — for a large corpus, express chunk → embed → upsert as an
  // akashi_workflow Pipeline (in your own code; akashi_rag depends only on akashi):
  //
  //   final ingest = Pipeline.input<Document>()
  //       .stage('chunk', (doc, _) async => chunker.chunk(doc))
  //       .stage('embed', (chunks, _) async => /* embed + zip into EmbeddedChunks */)
  //       .stage('store', (embedded, _) async { await store.upsert(embedded); });
  //   await workflow.pipeline(documents, ingest); // bounded concurrency + retries
  // ---------------------------------------------------------------------------
}

/// A deterministic bag-of-words embedder so the example needs no API key.
final class _BagOfWordsEmbedder implements EmbeddingModel {
  _BagOfWordsEmbedder(this.vocabulary);

  final List<String> vocabulary;

  @override
  String get providerId => 'fake';

  @override
  String get modelId => 'bag-of-words';

  @override
  Future<List<List<double>>> embed(List<String> inputs) async => [
        for (final input in inputs)
          [
            for (final term in vocabulary)
              input.toLowerCase().contains(term) ? 1.0 : 0.0,
          ],
      ];
}

/// A scripted stand-in [LanguageModel]: first turn calls the retrieval tool, the
/// second answers.
final class _ScriptedModel implements LanguageModel {
  _ScriptedModel(this._turns);

  final List<List<ModelStreamPart>> _turns;
  int _index = 0;

  @override
  String get providerId => 'scripted';

  @override
  String get modelId => 'scripted';

  @override
  Stream<ModelStreamPart> stream(ModelRequest request) async* {
    final turn = _index < _turns.length
        ? _turns[_index]
        : const <ModelStreamPart>[FinishPart(FinishReason.stop)];
    _index++;
    for (final part in turn) {
      yield part;
    }
  }

  @override
  Future<ModelResponse> generate(ModelRequest request) async =>
      const ModelResponse(
        message: AssistantMessage([]),
        finishReason: FinishReason.stop,
        usage: Usage.zero,
      );
}
0
likes
150
points
31
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Retrieval-augmented generation for the Akashi agent framework: a provider-neutral Retriever seam, a pure-Dart in-memory vector store, document chunking, and glue to wire retrieval into an agent as a tool.

Repository (GitHub)
View/report issues

Topics

#ai #agents #rag #embeddings #vector-search

License

MIT (license)

Dependencies

akashi

More

Packages that depend on akashi_rag