okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
12f1695
main
1"""A local streaming model server for transport benchmarks. 2 3Run explicitly: python3 test/manual/mock_openai_server.py [port] [cert key] 4The base URL picks the stream: http://127.0.0.1:PORT/<rate>/<tokens>[/<option>...]/v1 5streams <tokens> deltas at <rate> per second (0 for as fast as the socket 6takes them), one SSE event per write, over HTTP/1.1 chunked encoding on 7kept-alive connections. Options: t<percent> streams that share of the 8deltas as one python tool call's arguments after the text (Gemini sends the 9call whole, as one more delta), and s<id> names the stream so parallel 10streams keep separate timings. The route picks the wire format: /responses, 11/chat/completions, Anthropic's /messages, Gemini's /vertex, or Cloud Code 12Assist's /antigravity. With 13a certificate and key it serves https instead. A request to /timings/<id> 14(or /timings for stream 0) returns the send time (ns, CLOCK_REALTIME) of 15every delta of that stream's last response, so a client can compute 16per-token latency. 17""" 18 19import asyncio 20import json 21import random 22import ssl 23import sys 24import time 25from collections .abc import Iterator 26 27WORDS = [ 28"the" , 29" quick" , 30" brown" , 31" fox" , 32" jumps" , 33" over" , 34" lazy" , 35" dog" , 36"." , 37"\n" , 38] 39timings :dict [str ,list [int ]]= {} 40 41# Each stream yields (is_delta, event); deltas are paced and timed. 42Events = Iterator [tuple [bool ,bytes ]] 43 44 45def sse (name :str | None ,payload :object )-> bytes : 46data = json .dumps (payload ,separators = ("," ,":" )) 47head = f"event: { name } \n" if name else "" 48return f" { head } data: { data } \n\n" .encode () 49 50 51def chunked (body :bytes )-> bytes : 52return b"%x\r\n%s\r\n" % (len (body ),body ) 53 54 55def split (text :str ,parts :int )-> list [str ]: 56"""text in at most parts nonempty pieces of about equal size.""" 57if parts <= 0 : 58return [] 59size = - (- len (text )// parts ) 60chunks = [text [i * size : (i + 1 )* size ]for i in range (parts )] 61return [chunk for chunk in chunks if chunk ]or [text ] 62 63 64def plan (count :int ,tool_percent :int )-> tuple [list [str ],list [str ],str ]: 65"""count deltas: text words, then tool_percent of them as pieces of one 66python call's arguments, and those arguments whole.""" 67calls = count * tool_percent // 100 68words = [WORDS [i % len (WORDS )]for i in range (count - calls )] 69code = " " .join (f"print( { i } )" for i in range (max (calls ,1 ))) 70arguments = json .dumps ({"code" :code }) 71return words ,split (arguments ,calls )if calls else [],arguments 72 73 74def message (text :str ,status :str )-> dict : 75content = [{"type" :"output_text" ,"text" :text ,"annotations" : [],"logprobs" : []}] 76return { 77"id" :"msg_0001" , 78"type" :"message" , 79"status" :status , 80"role" :"assistant" , 81"content" :content , 82 } 83 84 85def function_call (arguments :str ,status :str )-> dict : 86return { 87"id" :"fc_0001" , 88"type" :"function_call" , 89"status" :status , 90"call_id" :"call_0001" , 91"name" :"python" , 92"arguments" :arguments , 93 } 94 95 96def responses (words :list [str ],calls :list [str ],arguments :str )-> Events : 97sequence = iter (range (1_000_000 )) 98 99def event (kind :str ,** fields :object )-> bytes : 100return sse (kind , {"type" :kind ,"sequence_number" :next (sequence ),** fields }) 101 102created = { 103"id" :"resp_0001" , 104"object" :"response" , 105"status" :"in_progress" , 106"model" :"mock" , 107"output" : [], 108 } 109yield False ,event ("response.created" ,response = created ) 110yield ( 111False , 112event ( 113"response.output_item.added" , 114output_index = 0 , 115item = message ("" ,"in_progress" ), 116 ), 117 ) 118for word in words : 119yield ( 120True , 121event ( 122"response.output_text.delta" , 123item_id = "msg_0001" , 124output_index = 0 , 125content_index = 0 , 126delta = word , 127logprobs = [], 128obfuscation = "x7Qp2LmN" , 129 ), 130 ) 131output = [message ("" .join (words ),"completed" )] 132yield False ,event ("response.output_item.done" ,output_index = 0 ,item = output [0 ]) 133if calls : 134yield ( 135False , 136event ( 137"response.output_item.added" , 138output_index = 1 , 139item = function_call ("" ,"in_progress" ), 140 ), 141 ) 142for chunk in calls : 143yield ( 144True , 145event ( 146"response.function_call_arguments.delta" , 147item_id = "fc_0001" , 148output_index = 1 , 149delta = chunk , 150obfuscation = "x7Qp2LmN" , 151 ), 152 ) 153output .append (function_call (arguments ,"completed" )) 154yield False ,event ("response.output_item.done" ,output_index = 1 ,item = output [1 ]) 155usage = { 156"input_tokens" :1200 , 157"input_tokens_details" : {"cached_tokens" :1000 }, 158"output_tokens" :len (words )+ len (calls ), 159"output_tokens_details" : {"reasoning_tokens" :0 }, 160 } 161final = {** created ,"status" :"completed" ,"output" :output ,"usage" :usage } 162yield False ,event ("response.completed" ,response = final ) 163 164 165def chat (words :list [str ],calls :list [str ],_arguments :str )-> Events : 166def chunk (delta :dict ,finish :str | None = None )-> bytes : 167choice = {"index" :0 ,"delta" :delta ,"logprobs" :None ,"finish_reason" :finish } 168return sse ( 169None , 170 { 171"id" :"chatcmpl-0001" , 172"object" :"chat.completion.chunk" , 173"created" :1700000000 , 174"model" :"mock" , 175"service_tier" :"default" , 176"system_fingerprint" :"fp_0001" , 177"choices" : [choice ], 178"usage" :None , 179"obfuscation" :"x7Qp2LmN" , 180 }, 181 ) 182 183yield False ,chunk ({"role" :"assistant" ,"content" :"" ,"refusal" :None }) 184for word in words : 185yield True ,chunk ({"content" :word }) 186if calls : 187opened = { 188"index" :0 , 189"id" :"call_0001" , 190"type" :"function" , 191"function" : {"name" :"python" ,"arguments" :"" }, 192 } 193yield False ,chunk ({"tool_calls" : [opened ]}) 194for piece in calls : 195yield ( 196True , 197chunk ({"tool_calls" : [{"index" :0 ,"function" : {"arguments" :piece }}]}), 198 ) 199yield False ,chunk ({},"tool_calls" if calls else "stop" ) 200usage = { 201"prompt_tokens" :1200 , 202"completion_tokens" :len (words )+ len (calls ), 203"prompt_tokens_details" : {"cached_tokens" :1000 }, 204 } 205yield ( 206False , 207sse ( 208None , 209 { 210"id" :"chatcmpl-0001" , 211"object" :"chat.completion.chunk" , 212"choices" : [], 213"usage" :usage , 214 }, 215 ), 216 ) 217yield False ,b"data: [DONE]\n\n" 218 219 220def messages (words :list [str ],calls :list [str ],_arguments :str )-> Events : 221def event (kind :str ,** fields :object )-> bytes : 222return sse (kind , {"type" :kind ,** fields }) 223 224start = { 225"id" :"msg_0001" , 226"type" :"message" , 227"role" :"assistant" , 228"model" :"mock" , 229"content" : [], 230"stop_reason" :None , 231"usage" : { 232"input_tokens" :200 , 233"cache_read_input_tokens" :1000 , 234"output_tokens" :1 , 235 }, 236 } 237yield False ,event ("message_start" ,message = start ) 238yield ( 239False , 240event ( 241"content_block_start" ,index = 0 ,content_block = {"type" :"text" ,"text" :"" } 242 ), 243 ) 244for word in words : 245yield ( 246True , 247event ( 248"content_block_delta" , 249index = 0 , 250delta = {"type" :"text_delta" ,"text" :word }, 251 ), 252 ) 253yield False ,event ("content_block_stop" ,index = 0 ) 254if calls : 255block = {"type" :"tool_use" ,"id" :"toolu_0001" ,"name" :"python" ,"input" : {}} 256yield False ,event ("content_block_start" ,index = 1 ,content_block = block ) 257for piece in calls : 258yield ( 259True , 260event ( 261"content_block_delta" , 262index = 1 , 263delta = {"type" :"input_json_delta" ,"partial_json" :piece }, 264 ), 265 ) 266yield False ,event ("content_block_stop" ,index = 1 ) 267delta = {"stop_reason" :"tool_use" if calls else "end_turn" ,"stop_sequence" :None } 268yield ( 269False , 270event ( 271"message_delta" , 272delta = delta , 273usage = {"output_tokens" :len (words )+ len (calls )}, 274 ), 275 ) 276yield False ,event ("message_stop" ) 277 278 279def gemini (words :list [str ],calls :list [str ],arguments :str ,wrapped :bool )-> Events : 280def chunk (parts :list [dict ],** candidate :object )-> bytes : 281response = { 282"candidates" : [ 283 {"content" : {"role" :"model" ,"parts" :parts },"index" :0 ,** candidate } 284 ], 285"usageMetadata" : { 286"promptTokenCount" :1200 , 287"totalTokenCount" :1200 , 288"trafficType" :"ON_DEMAND" , 289 }, 290"modelVersion" :"gemini-mock" , 291"responseId" :"resp_0001" , 292 } 293return sse ( 294None , 295 {"response" :response ,"traceId" :"trace_0001" }if wrapped else response , 296 ) 297 298for word in words : 299yield True ,chunk ([{"text" :word }]) 300if calls : 301call = {"functionCall" : {"name" :"python" ,"args" :json .loads (arguments )}} 302yield True ,chunk ([call ]) 303usage = { 304"promptTokenCount" :1200 , 305"candidatesTokenCount" :len (words )+ len (calls ), 306"cachedContentTokenCount" :1000 , 307"thoughtsTokenCount" :0 , 308 } 309final = chunk ([{"text" :"" }],finishReason = "STOP" ) 310data = json .loads (final .decode ().removeprefix ("data: " )) 311 (data ["response" ]if wrapped else data )["usageMetadata" ]= usage 312yield False ,sse (None ,data ) 313 314 315def vertex (words :list [str ],calls :list [str ],arguments :str )-> Events : 316return gemini (words ,calls ,arguments ,wrapped = False ) 317 318 319def antigravity (words :list [str ],calls :list [str ],arguments :str )-> Events : 320"""Cloud Code Assist wraps each Gemini response in a response field.""" 321return gemini (words ,calls ,arguments ,wrapped = True ) 322 323 324async def stream (writer :asyncio .StreamWriter ,path :str )-> None : 325segments = path .split ("/" ) 326rate ,count = float (segments [1 ]),int (segments [2 ]) 327options = segments [3 :segments .index ("v1" )] 328name = next ((option [1 :]for option in options if option .startswith ("s" )),"0" ) 329share = int (next ((option [1 :]for option in options if option .startswith ("t" )),"0" )) 330routes = { 331"/chat/completions" :chat , 332"/messages" :messages , 333"/vertex" :vertex , 334"/antigravity" :antigravity , 335 } 336wire = next ( 337 (events for route ,events in routes .items ()if path .endswith (route )),responses 338 ) 339head = "HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\ntransfer-encoding: chunked\r\n\r\n" 340writer .write (head .encode ()) 341sent = timings [name ]= [] 342interval = 1 / rate if rate > 0 else 0.0 343# A random phase, so parallel streams do not all send in the same instant. 344started = time .monotonic ()+ random .random ()* interval 345for is_delta ,event in wire (* plan (count ,share )): 346if is_delta and interval : 347delay = started + len (sent )* interval - time .monotonic () 348if delay > 0 : 349await asyncio .sleep (delay ) 350if is_delta : 351sent .append (time .time_ns ()) 352writer .write (chunked (event )) 353await writer .drain () 354writer .write (b"0\r\n\r\n" ) 355await writer .drain () 356 357 358async def handle (reader :asyncio .StreamReader ,writer :asyncio .StreamWriter )-> None : 359try : 360while request := (await reader .readuntil (b"\r\n" )).decode (): 361_ ,path ,_ = request .split (" " ,2 ) 362length = 0 363while (line := await reader .readuntil (b"\r\n" ))!= b"\r\n" : 364name ,_ ,value = line .decode ().partition (":" ) 365if name .strip ().lower ()== "content-length" : 366length = int (value ) 367await reader .readexactly (length ) 368if path .startswith ("/timings" ): 369body = json .dumps ( 370timings .get (path .removeprefix ("/timings" ).strip ("/" )or "0" , []) 371 ).encode () 372head = f"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: { len ( body ) } \r\n\r\n" 373writer .write (head .encode ()+ body ) 374else : 375await stream (writer ,path ) 376await writer .drain () 377except (ConnectionError ,asyncio .IncompleteReadError ): 378pass 379finally : 380writer .close () 381 382 383async def main (port :int ,tls :list [str ])-> None : 384context = None 385if tls : 386context = ssl .create_default_context (ssl .Purpose .CLIENT_AUTH ) 387context .load_cert_chain (* tls ) 388server = await asyncio .start_server (handle ,"127.0.0.1" ,port ,ssl = context ) 389f"listening on { 'https' if tls else 'http' } ://127.0.0.1: { port } " ,flush = True ) 390async with server : 391await server .serve_forever () 392 393 394if __name__ == "__main__" : 395asyncio .run (main (int (sys .argv [1 ])if len (sys .argv )> 1 else 8765 ,sys .argv [2 :4 ]))