okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
331aa22
main
1"""Read-only context inspection against actual model requests and catalog data.""" 2 3import json 4import unittest 5import urllib .error 6from urllib .parse import urlencode 7 8from harness import Albedo ,Provider ,exclusive ,Reply ,text 9 10 11SECRET = "credential-must-never-appear-in-inspector" 12CATALOG = { 13"fixture-cloud" : { 14"id" :"fixture-cloud" , 15"name" :"Fixture Cloud" , 16"env" : ["FIXTURE_API_KEY" ], 17"api" :"http://127.0.0.1/v1" , 18"models" : { 19"fixture" : { 20"id" :"fixture" , 21"limit" : {"context" :200000 ,"output" :8000 }, 22"modalities" : {"input" : ["text" ]}, 23 } 24 }, 25 } 26} 27 28 29# exclusive: replaces the global models catalog and asserts catalog resolution 30@exclusive 31class ContextTest (unittest .TestCase ): 32def setUp (self ): 33def reply (request ): 34if "without provider usage" in json .dumps (request ): 35return Reply ("text" ,"done" ,usage = None ) 36return text ("done" ,usage = {"input_tokens" :20 ,"output_tokens" :1 }) 37 38self .provider = Provider (reply ,catalog = CATALOG ) 39self .addCleanup (self .provider .close ) 40 41def prepare (app ): 42 (app .home / "models.json" ).unlink (missing_ok = True ) 43app .write_extensions ( 44 { 45"models" : { 46"url" :self .provider .url + "/models.json" , 47"refreshHours" :24 , 48 } 49 } 50 ) 51 52self .app = Albedo ( 53self .provider , 54protocol = "responses" , 55prepare = prepare , 56providers = { 57"fixture" : { 58"baseUrl" :self .provider .url + "/v1" , 59"apiKey" :SECRET , 60"model" :"fixture" , 61"protocol" :"responses" , 62 }, 63 }, 64 ) 65self .app .__enter__ () 66self .addCleanup (self .app .__exit__ ,None ,None ,None ) 67self .session = self .app .session () 68self .route = f"/sessions/ { self . session } /context" 69 70def read (self ,route ): 71with self .app .api (route )as response : 72return json .load (response ) 73 74def turn (self ,message ): 75self .app .prompt (self .session ,message ).close () 76self .app .idle (self .session ) 77 78def test_pending_inspection_is_read_only_and_snapshot_matches_request (self ): 79pending = self .read (self .route ) 80self .assertEqual (pending ["state" ],"pending" ) 81self .assertIsNone (pending ["snapshot_id" ]) 82self .assertEqual (set (pending ["reason" ]), {"code" ,"detail" }) 83self .assertEqual (pending ["reason" ]["code" ],"context_pending" ) 84self .assertIsInstance (pending ["reason" ]["detail" ],str ) 85self .assertTrue (pending ["reason" ]["detail" ].strip ()) 86self .assertLessEqual (len (pending ["reason" ]["detail" ]),4096 ) 87self .assertEqual (self .provider .requests , []) 88self .turn ("inspect exact composition" ) 89self .assertEqual (len (self .provider .requests ),1 ) 90sent = self .provider .requests [0 ]["request" ] 91snapshot = self .read (self .route ) 92self .assertEqual (snapshot ["state" ],"ready" ) 93self .assertIsNone (snapshot ["reason" ]) 94self .assertEqual ( 95 (snapshot ["provider" ],snapshot ["model" ]), ("fixture" ,"fixture" ) 96 ) 97self .assertEqual (snapshot ["context_window_tokens" ],200000 ) 98compaction = snapshot ["compaction" ] 99self .assertEqual (compaction ["provider_input_tokens" ],20 ) 100self .assertIsNone (compaction ["provider_cached_input_tokens" ]) 101self .assertEqual ( 102compaction ["estimate_method" ], 103"local byte-based estimate; not provider token usage" , 104 ) 105self .assertEqual (compaction ["status" ],"not_needed" ) 106self .assertEqual (compaction ["input_limit_tokens" ],200000 ) 107self .assertEqual (compaction ["trigger_free_percent" ],20 ) 108self .assertIn ("fixture-cloud" ,compaction ["source" ]) 109self .assertIn ("models.dev catalog" ,compaction ["source" ]) 110self .assertIn ("<extension-context" ,sent ["instructions" ]) 111self .assertNotIn ("<extension-context" ,json .dumps (sent ["input" ])) 112self .assertTrue ( 113all ( 114section ["preview" ]["text" ] 115and len (section ["preview" ]["text" ].encode ())<= 1024 116for section in snapshot ["sections" ] 117 ) 118 ) 119self .assertNotIn (SECRET ,json .dumps (snapshot )) 120 121pages = {} 122for section in snapshot ["sections" ]: 123parts = [ 124self .read ( 125self .route 126+ "?" 127+ urlencode ( 128 { 129"view" :"section" , 130"snapshot_id" :snapshot ["snapshot_id" ], 131"section_id" :section ["id" ], 132"page" :page , 133 } 134 ) 135 ) 136for page in range (section ["page_count" ]) 137 ] 138self .assertTrue (all (len (part ["text" ].encode ())<= 32768 for part in parts )) 139self .assertTrue ( 140all (part ["snapshot_id" ]== snapshot ["snapshot_id" ]for part in parts ) 141 ) 142pages [section ["id" ]]= "" .join (part ["text" ]for part in parts ) 143self .assertEqual (pages ["instructions" ],sent ["instructions" ]) 144self .assertEqual (json .loads (pages ["tools" ]),sent ["tools" ]) 145self .assertIn ("inspect exact composition" ,pages ["history" ]) 146self .assertNotIn (SECRET ,json .dumps (pages )) 147self .assertEqual (len (self .provider .requests ),1 ) 148 149def test_combining_marks_cannot_make_context_pages_unbounded (self ): 150message = "a" + "\u0301" * 20000 151self .turn (message ) 152session = self .read (f"/sessions/ { self . session } ?tail=0" ) 153listed = next ( 154item 155for item in self .read ("/sessions" )["items" ] 156if item ["id" ]== self .session 157 ) 158for representation in (session ,listed ): 159for field in ("name" ,"automatic_name" ): 160self .assertTrue (representation [field ].startswith ("a" )) 161self .assertLessEqual (len (representation [field ]),4096 ) 162resource = session ["configuration_resource" ] 163configuration = self .read (resource ["url" ]) 164self .assertEqual (configuration ,resource ["value" ]) 165self .assertIsNone (configuration ["name" ]) 166self .assertEqual (listed ["name" ],session ["name" ]) 167self .assertEqual (listed ["automatic_name" ],session ["automatic_name" ]) 168snapshot = self .read (self .route ) 169history = next ( 170section for section in snapshot ["sections" ]if section ["id" ]== "history" 171 ) 172parts = [] 173for page in range (history ["page_count" ]): 174part = self .read ( 175self .route 176+ "?" 177+ urlencode ( 178 { 179"view" :"section" , 180"snapshot_id" :snapshot ["snapshot_id" ], 181"section_id" :"history" , 182"page" :page , 183 } 184 ) 185 ) 186self .assertLessEqual (len (part ["text" ]),8000 ) 187self .assertLessEqual (len (part ["text" ].encode ()),32000 ) 188parts .append (part ["text" ]) 189self .assertGreater (len (parts ),1 ) 190history_text = "" .join (parts ) 191self .assertIn (message ,history_text ) 192self .assertEqual (history ["preview" ]["text" ],history_text [:256 ]) 193self .assertTrue (history ["preview" ]["truncated" ]) 194 195def test_usage_absence_model_selection_and_unknown_catalog_model (self ): 196self .turn ("without provider usage" ) 197compaction = self .read (self .route )["compaction" ] 198self .assertIsNone (compaction ["provider_input_tokens" ]) 199self .assertIsNotNone (compaction ["estimated_input_tokens" ]) 200resource = self .read (f"/sessions/ { self . session } " )["configuration_resource" ] 201with self .app .api ( 202resource ["url" ], 203 {"model" :"changed-model" }, 204method = "PATCH" , 205headers = {"If-Match" :resource ["etag" ]}, 206 )as response : 207changed = json .load (response ) 208self .assertEqual (changed ["session" ]["model" ],"changed-model" ) 209pending = self .read (self .route ) 210self .assertEqual (pending ["state" ],"pending" ) 211self .assertIsNone (pending ["snapshot_id" ]) 212self .assertEqual (len (self .provider .requests ),1 ) 213self .turn ("a model outside the catalog" ) 214unknown = self .read (self .route ) 215self .assertIsNone (unknown ["context_window_tokens" ]) 216self .assertEqual (unknown ["compaction" ]["status" ],"unknown" ) 217 218 219class ContextReplayTest (unittest .TestCase ): 220def test_inspection_preserves_replay_and_replaces_prior_request_for_both_protocols ( 221self , 222 ): 223for protocol in ("responses" ,"chat_completions" ): 224with self .subTest (protocol = protocol ): 225opaque = "opaque-replay-must-only-reach-provider" 226if protocol == "responses" : 227replay = { 228"type" :"reasoning" , 229"id" :"fixed-reasoning" , 230"summary" : [], 231"encrypted_content" :opaque , 232 } 233message = { 234"type" :"message" , 235"role" :"assistant" , 236"content" : [{"type" :"output_text" ,"text" :"first answer" }], 237 } 238initial = Reply ( 239"raw" , 240events = [ 241 { 242"type" :"response.completed" , 243"response" : { 244"id" :"fixed-response" , 245"status" :"completed" , 246"output" : [replay ,message ], 247"usage" : { 248"input_tokens" :31 , 249"output_tokens" :2 , 250"input_tokens_details" : {"cached_tokens" :9 }, 251 }, 252 }, 253 } 254 ], 255 ) 256else : 257replay = { 258"role" :"assistant" , 259"content" :"first answer" , 260"reasoning_content" :opaque , 261 } 262initial = Reply ( 263"raw" , 264events = [ 265 { 266"id" :"fixed-chat" , 267"choices" : [ 268 {"index" :0 ,"delta" :replay ,"finish_reason" :None } 269 ], 270 }, 271 { 272"id" :"fixed-chat" , 273"choices" : [ 274 {"index" :0 ,"delta" : {},"finish_reason" :"stop" } 275 ], 276"usage" : { 277"prompt_tokens" :31 , 278"completion_tokens" :2 , 279"prompt_tokens_details" : {"cached_tokens" :9 }, 280 }, 281 }, 282 ], 283 ) 284 285def reply (request ): 286inputs = request .get ("input" ,request .get ("messages" , [])) 287users = [item for item in inputs if item .get ("role" )== "user" ] 288return ( 289initial 290if len (users )== 1 291else Reply ("text" ,"later answer" ,usage = None ) 292 ) 293 294provider = Provider (reply ) 295try : 296with Albedo (provider ,protocol = protocol )as app : 297session = app .session () 298route = f"/sessions/ { session } /context" 299 300def inspect (): 301with app .api (route )as response : 302summary = json .load (response ) 303contents = {} 304for section in summary .get ("sections" , []): 305parts = [] 306for index in range (section ["page_count" ]): 307with app .api ( 308route 309+ "?" 310+ urlencode ( 311 { 312"view" :"section" , 313"snapshot_id" :summary ["snapshot_id" ], 314"section_id" :section ["id" ], 315"page" :index , 316 } 317 ) 318 )as response : 319parts .append (json .load (response )["text" ]) 320contents [section ["id" ]]= "" .join (parts ) 321return summary ,contents 322 323app .prompt (session ,"first request" ).close () 324app .idle (session ) 325first ,pages = inspect () 326self .assertNotIn ("first answer" ,pages ["history" ]) 327self .assertNotIn (opaque ,json .dumps ((first ,pages ))) 328self .assertEqual (len (provider .requests ),1 ) 329app .prompt (session ,"replacement request" ).close () 330app .idle (session ) 331second ,pages = inspect () 332with self .assertRaises (urllib .error .HTTPError )as obsolete : 333app .api ( 334route 335+ "?" 336+ urlencode ( 337 { 338"view" :"section" , 339"snapshot_id" :first ["snapshot_id" ], 340"section_id" :first ["sections" ][0 ]["id" ], 341"page" :0 , 342 } 343 ) 344 ) 345self .assertEqual (obsolete .exception .code ,410 ) 346self .assertEqual ( 347json .load (obsolete .exception )["code" ],"context_changed" 348 ) 349self .assertIn ("replacement request" ,pages ["history" ]) 350self .assertNotIn ("later answer" ,pages ["history" ]) 351self .assertNotIn (opaque ,json .dumps ((second ,pages ))) 352sent = provider .requests [- 1 ]["request" ] 353inputs = sent .get ("input" ,sent .get ("messages" , [])) 354self .assertIn (replay ,inputs ) 355self .assertEqual (len (provider .requests ),2 ) 356inspect () 357self .assertEqual (len (provider .requests ),2 ) 358finally : 359provider .close ()