Skip to content

Getting started with Python ​

Python talks to SodaMem as a library. There is no server in the loop and no network hop — import sodamem and you are already past it.

Install ​

bash
pip install "sodamem[chroma,llm]"

Python 3.11 or newer. The two extras are doing different jobs:

  • chroma — vector search plus the local ONNX embedder. SodaMem.open() needs this.
  • llm — OpenAI-compatible providers (OpenAI / DeepSeek / Gemini wire format). Writing needs one; reading never does.

See Install extras for the full list.

Credentials ​

Extraction turns a conversation into facts, and that takes a model. Set the same four variables the server uses, so an embedded process and a container are configured identically:

bash
export SODAMEM_LLM_PROVIDER=openai        # openai | anthropic | deepseek | gemini
export SODAMEM_LLM_API_KEY=sk-...
export SODAMEM_LLM_MODEL=                 # provider default if empty
# export SODAMEM_LLM_BASE_URL=            # only for a non-api.openai.com endpoint

Reading needs none of this

search and build_context are the zero-LLM tier. Drop the extractor argument and a read-only store works with every variable above unset.

Your first store ​

python
from sodamem import SodaMem
from sodamem.llm import create_provider_from_env
from sodamem.memory.ingest.extractor import FactEventExtractorV2

# Writing needs a model to extract facts with; reading never does.
mem = SodaMem.open("./data", extractor=FactEventExtractorV2(create_provider_from_env()))

mem.ingest(
    [{"role": "user", "content": "Actually I moved from Kauai to Oahu."}],
    user_id="u1", session_id="s1", session_time="2023-05-25",
)

block = mem.build_context("where am I staying?", user_id="u1", token_budget=1000)
print(block.text)        # prompt-ready — zero LLM calls
print(block.citations)   # the exact evidence behind every line of it

SodaMem.open() creates ./data if it isn't there.

What comes back ​

build_context does not hand you a list of records to assemble yourself. It returns a block that is ready to drop into a prompt, already deduplicated and already inside the token budget you asked for, plus the citations that produced it:

evidence_id  = ev_fact:fact_6ada707b…
support      = "Can you recommend a good beach on Oahu that's not too crowded?"
predicate    = user wants a not-too-crowded beach on Oahu
entities     = location=Oahu | occasion=birthday
source       = session_40 / turn_10          ← the exact turn, not "some chat"
date         = 2023-05-25

Each citation is a link into the evidence chain, not a relevance score.

Corrections ​

Tell it something that contradicts what it knows and nothing is overwritten:

python
mem.ingest(
    [{"role": "user", "content": "I'm back on Kauai now."}],
    user_id="u1", session_id="s2", session_time="2024-02-01",
)

The earlier fact closes with a valid_until and a SUPERSEDES edge connects the two. It leaves search and context, and it stays readable. That is the whole difference from a delete — see Four time axes.

When to reach past build_context ​

you needuseLLM calls
context for a promptbuild_contextnone
raw hits to post-processsearchnone
"list every X you know about me"build_context(organizer=...)an organizer pass
a hard multi-hop question answeredanswera planner loop

The organizer argument is Python-only on purpose: /v1/context never accepts one, so the zero-LLM guarantee on that route cannot be flipped by a request parameter. Retrieval tiers explains the trade.

Nothing leaves the machine ​

No telemetry, no analytics, no callback. The only outbound request a default install ever makes is a one-time download of the 90 MB MiniLM embedding model into ~/.cache/chroma/. Pre-seed that cache and it runs air-gapped.

Next ​

Apache-2.0 licensed.