patdev/k3-a40-bootstrap
01.3k
1import json,sys,time,urllib.request,urllib.error2base=sys.argv[1]; H={"Content-Type":"application/json","anthropic-version":"2023-06-01","User-Agent":"audit"}3def post(path,body,stream=False,timeout=240):4 req=urllib.request.Request(base+path,data=json.dumps(body).encode(),headers=H)5 try:6 r=urllib.request.urlopen(req,timeout=timeout)7 if not stream: return r.status, json.load(r)8 evs=[]9 for raw in r:10 l=raw.decode("utf-8","ignore").strip()11 if l.startswith("data:"):12 try: evs.append(json.loads(l[5:]))13 except: evs.append({"type":"_raw","raw":l[:80]})14 return r.status, evs15 except urllib.error.HTTPError as e:16 return e.code, e.read().decode()[:300]17def say(nom,ok,info=""): print(f" [{'OK ' if ok else 'BUG'}] {nom} {info}")18M="claude-ornith"19# 1 non-stream + thinking : bloc thinking present ?20s,d=post("/v1/messages",{"model":M,"max_tokens":400,"thinking":{"type":"enabled","budget_tokens":300},"messages":[{"role":"user","content":"Combien font 12*12 ? Reponds en un mot."}]})21types=[b["type"] for b in d.get("content",[])] if isinstance(d,dict) else d22say("non-stream thinking",s==200 and "thinking" in types and "text" in types,f"{s} blocs={types} usage={d.get('usage') if isinstance(d,dict) else ''}")23# 2 stop_sequences24s,d=post("/v1/messages",{"model":M,"max_tokens":200,"thinking":{"type":"disabled"},"stop_sequences":["FIN"],"messages":[{"role":"user","content":"Ecris exactement : alpha beta FIN gamma delta"}]})25txt="".join(b.get("text","") for b in d.get("content",[])) if isinstance(d,dict) else ""26say("stop_sequences",s==200 and "gamma" not in txt and d.get("stop_reason") in ("stop_sequence","end_turn"),f"{s} stop={d.get('stop_reason') if isinstance(d,dict) else d} texte={txt[:40]!r}")27# 3 max_tokens -> stop_reason max_tokens28s,d=post("/v1/messages",{"model":M,"max_tokens":5,"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Raconte une longue histoire."}]})29say("max_tokens stop_reason",s==200 and d.get("stop_reason")=="max_tokens",f"{s} stop={d.get('stop_reason') if isinstance(d,dict) else d} out={d.get('usage',{}).get('output_tokens') if isinstance(d,dict) else ''}")30# 4 system en liste de blocs + cache_control31s,d=post("/v1/messages",{"model":M,"max_tokens":60,"thinking":{"type":"disabled"},"system":[{"type":"text","text":"Tu reponds toujours en majuscules.","cache_control":{"type":"ephemeral"}}],"messages":[{"role":"user","content":"dis bonjour"}]})32txt="".join(b.get("text","") for b in d.get("content",[])) if isinstance(d,dict) else str(d)33say("system liste+cache_control",s==200 and txt.strip()==txt.strip().upper() and len(txt)>2,f"{s} {txt[:40]!r}")34# 5 tool_use en flux : input_json_delta reconstitue un JSON valide, stop_reason tool_use35tools=[{"name":"meteo","description":"donne la meteo d'une ville","input_schema":{"type":"object","properties":{"ville":{"type":"string"},"unite":{"type":"string","enum":["C","F"]}},"required":["ville"]}}]36s,evs=post("/v1/messages",{"model":M,"max_tokens":300,"stream":True,"thinking":{"type":"disabled"},"tools":tools,"tool_choice":{"type":"any"},"messages":[{"role":"user","content":"Quelle meteo a Lyon en celsius ?"}]},stream=True)37partial="";name=None;stop=None;ids=set()38for ev in evs:39 if ev.get("type")=="content_block_start" and ev["content_block"]["type"]=="tool_use": name=ev["content_block"]["name"]; ids.add(ev["content_block"].get("id"))40 if ev.get("type")=="content_block_delta" and ev["delta"].get("type")=="input_json_delta": partial+=ev["delta"]["partial_json"]41 if ev.get("type")=="message_delta": stop=ev["delta"].get("stop_reason")42try: args=json.loads(partial or "{}"); okj=True43except Exception: args=partial; okj=False44say("tool_use flux",s==200 and name=="meteo" and okj and "ville" in str(args) and stop=="tool_use" and all(i for i in ids),f"{s} nom={name} args={args} stop={stop} id={ids}")45# 6 tool_result is_error + suite46s,d=post("/v1/messages",{"model":M,"max_tokens":120,"thinking":{"type":"disabled"},"tools":tools,"messages":[{"role":"user","content":"Quelle meteo a Lyon ?"},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_9","name":"meteo","input":{"ville":"Lyon"}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_9","is_error":True,"content":"Erreur: service indisponible"}]}]})47txt="".join(b.get("text","") for b in d.get("content",[])) if isinstance(d,dict) else str(d)48say("tool_result is_error",s==200 and len(txt)>5,f"{s} {txt[:70]!r}")49# 7 redacted_thinking dans l'historique50s,d=post("/v1/messages",{"model":M,"max_tokens":60,"thinking":{"type":"enabled","budget_tokens":200},"messages":[{"role":"user","content":"Dis A"},{"role":"assistant","content":[{"type":"redacted_thinking","data":"xxxx"},{"type":"text","text":"A"}]},{"role":"user","content":"Maintenant dis B"}]})51say("redacted_thinking historique",s==200,f"{s} {d if s!=200 else ''}")52# 8 usage : cache_read_input_tokens expose (Claude Code l'affiche) -- 2 appels identiques53body={"model":M,"max_tokens":20,"thinking":{"type":"disabled"},"system":"Tu es concis. "*200,"messages":[{"role":"user","content":"Dis oui."}]}54post("/v1/messages",body); s,d=post("/v1/messages",body)55u=d.get("usage",{}) if isinstance(d,dict) else {}56say("usage cache_read_input_tokens",u.get("cache_read_input_tokens") not in (None,0),f"usage={u}")57# 9 count_tokens avec system + tools58s,d=post("/v1/messages/count_tokens",{"model":M,"system":"Tu es utile.","tools":tools,"messages":[{"role":"user","content":"Salut"}]})59say("count_tokens system+tools",s==200 and isinstance(d,dict) and d.get("input_tokens",0)>20,f"{s} {d}")60# 10 temperature/top_k/metadata acceptes61s,d=post("/v1/messages",{"model":M,"max_tokens":20,"temperature":0.2,"top_p":0.9,"top_k":40,"metadata":{"user_id":"u1"},"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Dis ok."}]})62say("temperature/top_p/top_k/metadata",s==200,f"{s} {d if s!=200 else ''}")63# 11 flux : signature_delta present sur le bloc thinking ? ordre thinking->text ?64s,evs=post("/v1/messages",{"model":M,"max_tokens":300,"stream":True,"thinking":{"type":"enabled","budget_tokens":200},"messages":[{"role":"user","content":"Combien font 3*7 ? Un mot."}]},stream=True)65types=[ev["content_block"]["type"] for ev in evs if ev.get("type")=="content_block_start"]66sig=any(ev.get("type")=="content_block_delta" and ev["delta"].get("type")=="signature_delta" for ev in evs)67stops=[ev.get("type") for ev in evs if ev.get("type") in ("message_start","message_stop")]68say("flux ordre/structure",s==200 and types[:1]==["thinking"] and "message_start" in stops and "message_stop" in stops,f"blocs={types} signature_delta={sig}")69# 12 modele inconnu -> erreur propre70s,d=post("/v1/messages",{"model":"claude-inexistant-9","max_tokens":10,"messages":[{"role":"user","content":"x"}]})71say("modele inconnu",s in (200,400,404),f"{s} {str(d)[:80]}")72# 13 message assistant vide en fin (prefill) refuse ou gere73s,d=post("/v1/messages",{"model":M,"max_tokens":20,"thinking":{"type":"disabled"},"messages":[{"role":"user","content":"Complete : la capitale de la France est"},{"role":"assistant","content":"Paris"}]})74say("prefill assistant final",s==200,f"{s} {str(d)[:80] if s!=200 else ''}")75 