Research-Stack/0-Core-Formalism/lean/Semantics/SearchServer.lean

119 lines
3.9 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Semantics.Search
import Lean.Data.Json
namespace SearchServer
open Lean Semantics.Search
-- ============================================================================
-- JSON helpers
-- ============================================================================
def jsonObj (fields : List (String × Json)) : Json :=
Json.mkObj fields
@[inline]
def parseString (j : Json) : Except String String :=
match j.getStr? with
| .ok s => .ok s
| .error e => .error s!"Expected string: {e}"
@[inline]
def parseNat (j : Json) : Except String Nat :=
match j.getNat? with
| .ok n => .ok n
| .error e => .error s!"Expected nat: {e}"
@[inline]
def parseUInt32 (j : Json) : Except String UInt32 :=
match j.getNat? with
| .ok n => .ok (UInt32.ofNat n)
| .error e => .error s!"Expected uint32: {e}"
@[inline]
def parseArray (j : Json) : Except String (Array Json) :=
match j.getArr? with
| .ok a => .ok a
| .error e => .error s!"Expected array: {e}"
@[inline]
def parseFin14 (j : Json) : Except String (Fin 14) :=
match j.getNat? with
| .ok n =>
if h : n < 14 then .ok ⟨n, h⟩ else .error s!"Axis {n} out of range (0-13)"
| .error e => .error s!"Expected axis index: {e}"
-- ============================================================================
-- Request / Response
-- ============================================================================
structure SearchRequest where
axes : List (Fin 14)
keywordIds : List String
records : List SearchRecord
structure SearchResponse where
results : List (String × UInt32)
-- ============================================================================
-- Parsing
-- ============================================================================
def parseSearchRecord (j : Json) : Except String SearchRecord := do
let id ← parseString (← j.getObjVal? "id")
let vecArr ← parseArray (← j.getObjVal? "vector")
let vector ← vecArr.toList.mapM (fun j => do let v ← parseUInt32 j; pure ⟨v⟩)
pure { id := id, vector := Array.mk vector }
instance : FromJson SearchRequest where
fromJson? j := do
let axesArr ← parseArray (← j.getObjVal? "axes")
let axes ← axesArr.toList.mapM parseFin14
let kwArr ← parseArray (← j.getObjVal? "keywordIds")
let keywordIds ← kwArr.toList.mapM parseString
let recArr ← parseArray (← j.getObjVal? "records")
let records ← recArr.toList.mapM parseSearchRecord
pure { axes := axes, keywordIds := keywordIds, records := records }
instance : ToJson SearchResponse where
toJson resp :=
jsonObj [("results", Json.arr (Array.mk (resp.results.map (fun p =>
jsonObj [("id", Json.str p.1), ("score", Json.num (JsonNumber.fromNat p.2.toUInt64.toNat))]
))))]
-- ============================================================================
-- Handler
-- ============================================================================
def handleRequest (req : SearchRequest) : SearchResponse :=
let ranked := hybridSearch req.axes req.keywordIds req.records
{ results := ranked.map (fun p => (p.1, p.2.val)) }
-- ============================================================================
-- I/O Loop (same JSON-lines protocol as BindServer)
-- ============================================================================
partial def serve : IO Unit := do
let stdin ← IO.getStdin
let stdout ← IO.getStdout
let line ← stdin.getLine
if line.isEmpty || line == "\n" then
return ()
match Json.parse line with
| .error e =>
stdout.putStrLn (Json.compress (jsonObj [("error", Json.str e)]))
stdout.flush
| .ok j =>
match fromJson? j with
| .error e =>
stdout.putStrLn (Json.compress (jsonObj [("error", Json.str e)]))
stdout.flush
| .ok req =>
let resp := handleRequest req
stdout.putStrLn (Json.compress (toJson resp))
stdout.flush
serve
end SearchServer
def main : IO Unit := SearchServer.serve