Skip to content

Quickstart — Semvec REST API for semantic memory (5 min)

The lowest-friction way to try Semvec is the REST API: one semvec serve command, then call HTTP from any client. No Python integration required, every endpoint shape and JSON schema is fixed.

For tighter per-turn latency, in-process state sharing, or custom embedders inside the host process, drop into the Python library quickstart further down.

Prerequisites

  • Python 3.10 or newer (python --version)
  • An embedder. Cheapest: pip install sentence-transformers

REST API quickstart

Install

pip install "semvec[api]" sentence-transformers

Start the server

semvec serve --port 8001

Server is ready when it logs Uvicorn running on http://0.0.0.0:8001.

Call it

# 1. Health check
curl -s http://localhost:8001/v1/health
# → {"status":"ok","active_sessions":0,"version":"1.0.0"}

# 2. Create a session under your own id (0.7.3+; alternatively omit
#    session_id on /v1/run and reuse the id from its response)
curl -s -X POST http://localhost:8001/v1/session/create \
  -H 'Content-Type: application/json' \
  -d '{"session_id": "demo"}'
# → {"session_id":"demo","created":true}

# 3. Run a turn
curl -s -X POST http://localhost:8001/v1/run \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "demo",
    "message": "We currently support SEPA payments but not iDEAL."
  }'
# → {"session_id":"demo","context":"[Semvec Context | Turn 0 | 0 memories]",
#    "top_similarity":0.0,"short_circuit":false,"drift_score":0.0,
#    "drift_detected":false,"drift_phase":"stable","dedup_signal":null,
#    "retrieval_error":false}
#   (empty context — nothing stored yet)

# 4. Store the answer your LLM produced for that turn
curl -s -X POST http://localhost:8001/v1/store \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "demo",
    "response": "SEPA covers the eurozone; iDEAL is the dominant method in NL."
  }'
# → {"session_id":"demo"}

# 5. Ask a follow-up — the context block is built automatically
curl -s -X POST http://localhost:8001/v1/run \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "demo",
    "message": "Which markets does iDEAL unlock for us?"
  }'
# → {"session_id":"demo",
#    "context":"[Semvec Context | Turn 1 | 1 memories]\nRelevant context:\n  1. [1.00] Q: We currently support SEPA payments but not iDEAL. A: SEPA covers the eurozone; iDEAL is the dominant method in NL.",
#    "top_similarity":0.3517,"short_circuit":false,"drift_score":0.3241,
#    "drift_detected":false,"drift_phase":"shifting","dedup_signal":null,
#    "retrieval_error":false}
#   (the stored answer was paired with turn 3's buffered question, then retrieved)

Two shortcuts: /v1/run without a session_id creates a fresh session and returns its id; and /v1/run also accepts a response field carrying the previous turn's LLM answer, storing it inline before retrieval. A /v1/run with a session_id that does not exist returns 404 — create it first (step 2) or omit the field.

The context field returned by /v1/run is the constant-cost input you pass to your LLM, instead of the growing transcript. Whether the conversation has run for 3 turns or 30 000, the size stays bounded.

See the REST API reference for the full endpoint catalogue, the CLI reference for semvec serve flags and SEMVEC_* environment variables.

You are productive

If /v1/health returns 200 and /v1/run returns a context block, you have a working Semvec. Move on:

Python library quickstart

For applications that need tighter latency than the REST round-trip, or in-process control over embedders and state, use the library directly.

Install

pip install semvec sentence-transformers

Minimal example

from semvec import SemvecState, SemvecConfig
from sentence_transformers import SentenceTransformer

# 1. Set up Semvec + your embedder
model = SentenceTransformer("all-MiniLM-L6-v2")
# dimension MUST match your embedder's output size.
# all-MiniLM-L6-v2 = 384, all-mpnet-base-v2 = 768,
# OpenAI text-embedding-3-small = 1536.
# Mismatch raises EmbeddingError at the first state.update().
state = SemvecState(config=SemvecConfig(dimension=384))

# 2. Feed it a conversation, turn by turn
conversation = [
    "I want to discuss our European customer onboarding flow.",
    "We currently support SEPA payments but not iDEAL.",
    "Which markets does iDEAL unlock for us?",
]
for text in conversation:
    result = state.update(model.encode(text), text)
    print(f"phase={result['phase']:14}  fsm={result['fsm']:.3f}")
# phase: conversation stage (initialization / exploration / convergence / resonance / stability / instability)
# fsm:   stability score in [0, 1] — gate expensive actions on fsm > 0.7
# See concepts-glossary.md for the full list of metrics returned by update().

# 3. Get a compact context block for the next LLM call
from semvec.token_reduction import SemvecStateSerializer
context = SemvecStateSerializer().serialize(state, query_text="Should we add iDEAL?")
print(context[:400], "...")

The context string above is a compact block ready to inject into your next LLM call — produced in-process instead of over HTTP.

This minimal path is dense-cosine only

SemvecStateSerializer().serialize() retrieves by dense cosine similarity only — no BM25 fusion, no cross-encoder reranking. The documented benchmark accuracy uses hybrid BM25 + cross-encoder rerank, which live in SemvecSession.run(), not the raw serializer. For retrieval that matches those numbers, use the session facade with make_cross_encoder_reranker + enable_bm25=True — see Retrieval quality.

For real applications, use the turn facade SemvecSession. It runs the same composition the REST /v1/run endpoint does (retrieve → optional BM25 → optional rerank → MMR → drift → context block) and returns a TurnResult. This is the path the hackathon starter should copy:

import numpy as np
from semvec import SemvecConfig, SemvecState, SemvecSession
from sentence_transformers import SentenceTransformer

# Bring your own embedder (BYOE): any object with get_dimension()/get_embedding().
class STEmbedder:
    def __init__(self, name="all-MiniLM-L6-v2"):
        self._m = SentenceTransformer(name)
        self._dim = int(self._m.get_sentence_embedding_dimension() or 384)

    def get_dimension(self) -> int:
        return self._dim

    def get_embedding(self, text: str) -> np.ndarray:
        if not text.strip():
            return np.zeros(self._dim, dtype=np.float64)
        v = self._m.encode(text, normalize_embeddings=True, convert_to_numpy=True,
                           show_progress_bar=False)
        return np.asarray(v, dtype=np.float64)

embedder = STEmbedder()
config = SemvecConfig(dimension=embedder.get_dimension())
session = SemvecSession(SemvecState(config=config), embedder, config)

# One turn = your message + (optionally) the previous LLM answer to store.
result = session.run_sync(
    "Which markets does iDEAL unlock for us?",
    response="SEPA covers the eurozone; iDEAL is the dominant method in NL.",
)
print(result.context)        # constant-cost block for your next LLM call
print(result.drift_phase)    # "stable" | "shifting" | "drifted"
# Real output (captured against all-MiniLM-L6-v2):
#   [Semvec Context | Turn 1 | 1 memories]
#   Relevant context:
#     1. [1.00] SEPA covers the eurozone; iDEAL is the dominant method in NL.
#   shifting

To match the published benchmark recall, add BM25 + a reranker (needs pip install "semvec[hybrid]" sentence-transformers) — see Retrieval quality. Every REST SEMVEC_* knob has a library parameter here — see the env → parameter map.

Common stumbles

Error Fix
ImportError: No module named 'sentence_transformers' pip install sentence-transformers
RuntimeError: Explicit embedder required You called a surface that needs embedder= — pass one in, or install sentence-transformers to let Semvec auto-load the default.
EmbeddingError: dimension mismatch Your model and SemvecConfig.dimension disagree. Set dimension= to match (384 for MiniLM, 1536 for OpenAI text-embedding-3-small).
REST /v1/run returns 503 Embedder not ready. Wait for the Uvicorn running on log line, or pre-warm with /v1/health.
REST /v1/run returns 401 Endpoint requires a licence JWT. Set SEMVEC_LICENSE_KEY="eyJ..." (Pro / Enterprise key, or a Community evaluation key issued via vertrieb@versino.de) before starting semvec serve.

More symptoms and fixes in Troubleshooting.