Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
154
sidecar/embedding_server.py
Normal file
154
sidecar/embedding_server.py
Normal file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from sentence_transformers import SentenceTransformer
|
||||
|
||||
|
||||
DEFAULT_MODEL = "all-MiniLM-L6-v2"
|
||||
WARMUP_INPUT = "Logseq embedding server warmup"
|
||||
_MODELS = {}
|
||||
|
||||
|
||||
def configure_logging(log_file):
|
||||
if not log_file:
|
||||
return
|
||||
|
||||
log_dir = os.path.dirname(log_file)
|
||||
if log_dir:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
logging.basicConfig(
|
||||
filename=log_file,
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
)
|
||||
|
||||
|
||||
def load_model(model_name):
|
||||
if model_name not in _MODELS:
|
||||
logging.info("Loading embedding model: %s", model_name)
|
||||
_MODELS[model_name] = SentenceTransformer(model_name)
|
||||
return _MODELS[model_name]
|
||||
|
||||
|
||||
def encode_texts(model, texts):
|
||||
return model.encode(
|
||||
texts,
|
||||
normalize_embeddings=True,
|
||||
show_progress_bar=False,
|
||||
)
|
||||
|
||||
|
||||
def warm_model(model_name):
|
||||
logging.info("Warming embedding model: %s", model_name)
|
||||
model = load_model(model_name)
|
||||
encode_texts(model, [WARMUP_INPUT])
|
||||
logging.info("Embedding model warmup finished: %s", model_name)
|
||||
|
||||
|
||||
def normalize_input(value):
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
if isinstance(value, list) and all(isinstance(item, str) for item in value):
|
||||
return value
|
||||
raise ValueError("input must be a string or a list of strings")
|
||||
|
||||
|
||||
class EmbeddingHandler(BaseHTTPRequestHandler):
|
||||
server_version = "LogseqEmbeddingServer/1.0"
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/healthz":
|
||||
self.send_json({"status": "ok"})
|
||||
return
|
||||
|
||||
self.send_json({"error": "not found"}, status=404)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path != "/v1/embeddings":
|
||||
self.send_json({"error": "not found"}, status=404)
|
||||
return
|
||||
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
payload = json.loads(self.rfile.read(length).decode("utf-8"))
|
||||
model_name = payload.get("model") or self.server.default_model
|
||||
texts = normalize_input(payload.get("input"))
|
||||
model = load_model(model_name)
|
||||
vectors = encode_texts(model, texts).tolist()
|
||||
self.send_json(
|
||||
{
|
||||
"object": "list",
|
||||
"model": model_name,
|
||||
"data": [
|
||||
{
|
||||
"object": "embedding",
|
||||
"index": index,
|
||||
"embedding": vector,
|
||||
}
|
||||
for index, vector in enumerate(vectors)
|
||||
],
|
||||
}
|
||||
)
|
||||
except Exception as error:
|
||||
logging.exception("Embedding request failed")
|
||||
self.send_json({"error": str(error)}, status=400)
|
||||
|
||||
def log_message(self, _format, *_args):
|
||||
return
|
||||
|
||||
def send_json(self, payload, status=200):
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
try:
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
except BrokenPipeError:
|
||||
logging.exception("Embedding response write failed")
|
||||
raise
|
||||
|
||||
|
||||
class EmbeddingServer(ThreadingHTTPServer):
|
||||
def __init__(self, server_address, handler_class, default_model):
|
||||
super().__init__(server_address, handler_class)
|
||||
self.default_model = default_model
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
logging.exception("Unhandled embedding server error from %s", client_address)
|
||||
super().handle_error(request, client_address)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--host", default="127.0.0.1")
|
||||
parser.add_argument("--port", type=int, default=8765)
|
||||
parser.add_argument("--model", default=DEFAULT_MODEL)
|
||||
parser.add_argument("--log-file")
|
||||
args = parser.parse_args()
|
||||
|
||||
configure_logging(args.log_file)
|
||||
try:
|
||||
warm_model(args.model)
|
||||
server = EmbeddingServer((args.host, args.port), EmbeddingHandler, args.model)
|
||||
logging.info(
|
||||
"Embedding server listening on http://%s:%s/v1/embeddings",
|
||||
args.host,
|
||||
args.port,
|
||||
)
|
||||
print(
|
||||
f"Embedding server listening on http://{args.host}:{args.port}/v1/embeddings",
|
||||
flush=True,
|
||||
)
|
||||
server.serve_forever()
|
||||
except Exception:
|
||||
logging.exception("Embedding server failed")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user