66nearest one you've already answered, and if it's close enough, return the stored
77answer without calling the model.
88
9- The whole thing is the SemanticCache class below. The one knob that matters is
10- the similarity threshold.
9+ The core is the SemanticCache class below (the `ask` method is the whole idea).
10+ The one knob that matters is the similarity threshold.
1111"""
1212
1313import asyncio
1414import os
15+ import sys
1516import time
1617
1718from dotenv import load_dotenv
2122
2223load_dotenv ()
2324
25+
26+ def _require (name : str ) -> str :
27+ value = os .getenv (name )
28+ if not value :
29+ sys .exit (f"Missing { name } . Copy .env.example to .env and fill in your keys." )
30+ return value
31+
32+
33+ # fail fast with a clear message if credentials are missing
34+ MOSS_PROJECT_ID = _require ("MOSS_PROJECT_ID" )
35+ MOSS_PROJECT_KEY = _require ("MOSS_PROJECT_KEY" )
36+ _require ("OPENAI_API_KEY" ) # read by AsyncOpenAI from the environment
37+
2438# cosine similarity above which a cached answer is "close enough" to reuse.
2539# too low -> you answer questions people didn't quite ask; too high -> you miss.
2640THRESHOLD = 0.92
2741
28- moss = MossClient (
29- project_id = os .getenv ("MOSS_PROJECT_ID" ),
30- project_key = os .getenv ("MOSS_PROJECT_KEY" ),
31- )
42+ moss = MossClient (project_id = MOSS_PROJECT_ID , project_key = MOSS_PROJECT_KEY )
3243llm = AsyncOpenAI ()
3344
3445
3546class SemanticCache :
36- """A tiny store of past questions -> answers, looked up by meaning."""
47+ """A small store of past questions -> answers, looked up by meaning."""
3748
3849 def __init__ (self , store ):
3950 self .store = store
@@ -42,7 +53,9 @@ async def ask(self, question: str) -> tuple[str, bool]:
4253 # 1. look for the closest question we've already answered
4354 hit = await self .store .query (question , QueryOptions (top_k = 1 ))
4455 if hit .docs and hit .docs [0 ].score >= THRESHOLD :
45- return hit .docs [0 ].metadata ["answer" ], True # cache hit — no LLM call
56+ answer = hit .docs [0 ].metadata .get ("answer" )
57+ if answer is not None :
58+ return answer , True # cache hit — no LLM call
4659
4760 # 2. miss: ask the model once
4861 resp = await llm .chat .completions .create (
@@ -59,8 +72,12 @@ async def ask(self, question: str) -> tuple[str, bool]:
5972
6073
6174async def main ():
62- # a fresh in-memory session acts as the cache store for this run
63- store = await moss .session ("qa-cache" )
75+ # A Moss session is the cache store. It holds the entries in memory for this
76+ # run, so the demo is deterministic: the first question is a MISS and its
77+ # paraphrase is a HIT. To make the cache persist across runs and processes,
78+ # call `await store.push_index()` to upload it as a cloud index (it will then
79+ # auto-load by name on the next run).
80+ store = await moss .session (index_name = "qa-cache" )
6481 cache = SemanticCache (store )
6582
6683 # the 2nd question means the same as the 1st, phrased differently -> cache hit
0 commit comments