rag-cpp exposes a flat, opaque-handle C ABI (include/rag/c/rag.h) so any
language with a C FFI can drive the engine. Errors cross as status codes, never
exceptions; strings you receive must be freed with the matching *_free.
Everything is an opaque handle (rag_engine, rag_embedder, rag_reranker,
rag_results) plus functions that take one. Fallible calls return a
rag_status; on failure, rag_engine_last_error(engine) returns a
human-readable message (the library keeps no per-thread state).
#include <rag/c/rag.h>
rag_engine* e = rag_engine_new();
rag_engine_add(e, "intro.md", "rag-cpp fuses BM25 with dense vectors.", NULL, NULL);
rag_engine_build(e);
rag_results* r = NULL;
if (rag_engine_search(e, "how does hybrid search work?", 5, &r) == RAG_OK) {
for (size_t i = 0; i < rag_results_count(r); ++i)
printf("[%.3f] chunk=%u lines %u-%u\n",
rag_results_score(r, i),
rag_results_chunk_id(r, i),
rag_results_start_line(r, i),
rag_results_end_line(r, i));
rag_results_free(r);
}
rag_engine_free(e);| Group | Functions |
|---|---|
| Version / errors | rag_version, rag_engine_last_error |
| Embedders | rag_embedder_hash, rag_embedder_free (network backends are C++ only: they take the host's HttpTransport) |
| Rerankers | rag_reranker_free |
| Engine lifecycle | rag_engine_new, rag_engine_free, rag_engine_set_embedder, rag_engine_set_reranker |
| Ingest | rag_engine_add, rag_engine_add_directory, rag_engine_build |
| Search | rag_engine_search → rag_results_* accessors |
| Results | rag_results_count, rag_results_score, rag_results_chunk_id, rag_results_doc_id, rag_results_start_line, rag_results_end_line, rag_results_free |
The full signatures and ownership rules are in the header — it is short and authoritative.
- Every handle you create with a constructor must be released with its
*_free. - Strings returned by the library must be released with
rag_string_free()(or the whole result viarag_results_free()). - The ABI is exception-free: a C++ exception never crosses the boundary; failures
surface as
rag_status+rag_engine_last_error().
Because the ABI is flat and stable, high-level bindings are thin:
- Python —
examples/ragcpp.pywraps the C ABI withctypes. Load the shared library, declare the signatures once, and you haveEngine.add/build/search. - Rust / Go / anything with a C FFI — declare the handful of
externfunctions above againstlibragcppand go.examples/polyglot/shows the cross-language pattern end to end.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build # produces build/libragcpp.{so,dylib,a}Point your FFI loader at the built libragcpp and include rag/c/rag.h for the
declarations (or transcribe them into your language's FFI, as ragcpp.py does).
- C API — you're embedding rag-cpp in-process in a non-C++ application and want direct, low-latency calls.
- RCP server — you want rag-cpp as a separate service other processes (or machines) talk to over a standard protocol, with capability discovery, filtering, and citations. Use the RCP client SDK in your language rather than the C ABI.