| |
| import argparse, ast, hashlib, json, os, re, subprocess, urllib.request, urllib.error |
| from datetime import datetime |
| from pathlib import Path |
|
|
| API="http://127.0.0.1:8000/v1/chat/completions" |
| MODELS="http://127.0.0.1:8000/v1/models" |
| MODEL="deepseek70b-7816" |
| ROOT=Path("/home/harness_user_1/dgx_ai_factory") |
| OUTROOT=ROOT/"data/generated/deepseek70b_7816_code_sft_2000_structured_v6" |
| LOGROOT=ROOT/"logs/data2000_structured_hotfix_v6" |
|
|
| CATEGORIES=[ |
| ("python_algorithm","advanced","python"), |
| ("python_data_pipeline","advanced","python"), |
| ("python_debugging","advanced","python"), |
| ("python_concurrency","advanced","python"), |
| ("fastapi_backend","advanced","python"), |
| ("linux_bash","advanced","bash"), |
| ("sql_database","advanced","sql"), |
| ("testing_quality","advanced","python"), |
| ("performance_optimization","advanced","python"), |
| ("ml_tooling","advanced","python"), |
| ("system_automation","advanced","bash"), |
| ("api_integration","advanced","python"), |
| ] |
| ANGLES=[ |
| "์คํ ๊ฐ๋ฅํ ์์ฑ ์์ ์ ์ต์ํ์ ๊ฒ์ฆ ์ฝ๋๋ฅผ ํฌํจํ์ธ์.", |
| "์ค๋ฌด์์ ๋ฐ์ํ ์ ์๋ ์คํจ ์กฐ๊ฑด๊ณผ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํฌํจํ์ธ์.", |
| "๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๊ณผ ํ์ฅ์ฑ์ ๊ณ ๋ คํ ๊ตฌํ์ ์๊ตฌํ์ธ์.", |
| "์ฌํ ๊ฐ๋ฅํ ๋ฒ๊ทธ ์ํฉ๊ณผ ์์ ์ ํ ์ฐจ์ด๋ฅผ ํฌํจํ์ธ์.", |
| "ํ
์คํธ ๊ฐ๋ฅํ ํจ์ ๋จ์ ์ค๊ณ์ ํ
์คํธ ์์ ๋ฅผ ํฌํจํ์ธ์.", |
| "์
๋ ฅ ๊ฒ์ฆ๊ณผ ๋ช
ํํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํฌํจํ์ธ์.", |
| "๋์์ฑ ๋๋ ์ฌ์๋ ์ํฉ์์ ์์ ํ๊ฒ ๋์ํ๋๋ก ์๊ตฌํ์ธ์.", |
| "๋์ฉ๋ ์
๋ ฅ์ ๊ฐ์ ํ๊ณ ์คํธ๋ฆฌ๋ฐ ๋๋ ๋ฐฐ์น ์ฒ๋ฆฌ๋ฅผ ๊ณ ๋ คํ์ธ์.", |
| ] |
| SCHEMA={ |
| "type":"object", |
| "properties":{ |
| "instruction":{"type":"string","minLength":40}, |
| "response":{"type":"string","minLength":220}, |
| "category":{"type":"string"}, |
| "difficulty":{"type":"string"}, |
| "language":{"type":"string"} |
| }, |
| "required":["instruction","response","category","difficulty","language"], |
| "additionalProperties":False |
| } |
| THINK_BLOCK=re.compile(r"<think>.*?</think>",re.I|re.S) |
| THINK_TAG=re.compile(r"</?think>",re.I) |
| CJK=re.compile(r"[\u4e00-\u9fff]") |
| BAD=re.compile(r"\bTODO\b|\bFIXME\b|\bTBD\b|placeholder|์๋ต|์ค๋ต|omitted|truncated|continue here",re.I) |
| FENCE=re.compile(r"```([A-Za-z0-9_+\-]*)\s*\n(.*?)```",re.S) |
| DANGEROUS=re.compile(r"rm\s+-rf\s+/(?:\s|$)|mkfs\.|dd\s+if=.*of=/dev/|:\(\)\s*\{\s*:\|:&\s*\};:",re.I) |
|
|
| def now(): return datetime.now().astimezone().isoformat() |
|
|
| def fsync_append(path,obj): |
| path.parent.mkdir(parents=True,exist_ok=True) |
| with path.open("a",encoding="utf-8") as f: |
| f.write(json.dumps(obj,ensure_ascii=False,separators=(",",":"))+"\n") |
| f.flush(); os.fsync(f.fileno()) |
|
|
| def atomic_json(path,obj): |
| path.parent.mkdir(parents=True,exist_ok=True) |
| tmp=path.with_suffix(path.suffix+".tmp") |
| with tmp.open("w",encoding="utf-8") as f: |
| json.dump(obj,f,ensure_ascii=False,indent=2); f.flush(); os.fsync(f.fileno()) |
| os.replace(tmp,path) |
|
|
| def normalize_text(s): |
| if not isinstance(s,str): return s |
| s=s.replace("ฤ "," ").replace("ฤ","\n").replace("ฤ","\t") |
| s=THINK_BLOCK.sub("",s) |
| s=THINK_TAG.sub("",s) |
| return s.replace("\r\n","\n").replace("\r","\n").strip() |
|
|
| def parse_content(content): |
| s=normalize_text(content or "") |
| try: return json.loads(s),None |
| except Exception as e: |
| a=s.find("{"); b=s.rfind("}") |
| if a>=0 and b>a: |
| try:return json.loads(s[a:b+1]),"extracted_json" |
| except Exception:pass |
| return None,f"json_parse:{type(e).__name__}" |
|
|
| def validate(obj,expected): |
| reasons=[] |
| if not isinstance(obj,dict): return ["not_object"],None |
| obj={k:normalize_text(v) if isinstance(v,str) else v for k,v in obj.items()} |
| for k in ("instruction","response","category","difficulty","language"): |
| if not isinstance(obj.get(k),str) or not obj[k].strip(): reasons.append(f"missing_or_empty:{k}") |
| if reasons: return sorted(set(reasons)),obj |
| if len(obj["instruction"])<40: reasons.append("instruction_too_short") |
| if len(obj["response"])<220: reasons.append("response_too_short") |
| if len(obj["response"])>14000: reasons.append("response_too_long") |
| if obj["category"]!=expected[0]: reasons.append("category_mismatch") |
| if obj["difficulty"]!=expected[1]: reasons.append("difficulty_mismatch") |
| if obj["language"]!=expected[2]: reasons.append("language_mismatch") |
| alltext=obj["instruction"]+"\n"+obj["response"] |
| if THINK_TAG.search(alltext) or "<think>" in alltext.lower(): reasons.append("think_tag") |
| if CJK.search(alltext): reasons.append("han_cjk_char") |
| if BAD.search(alltext): reasons.append("todo_or_incomplete") |
| if DANGEROUS.search(alltext): reasons.append("dangerous_destructive_pattern") |
| if obj["response"].count("```")%2: reasons.append("unbalanced_fence") |
| for lang,code in FENCE.findall(obj["response"]): |
| if lang.lower() in ("python","py"): |
| try: ast.parse(code) |
| except SyntaxError as e: reasons.append("python_syntax_error:"+e.msg) |
| elif lang.lower()=="json": |
| try: json.loads(code) |
| except Exception: reasons.append("json_fence_parse_error") |
| if re.search(r"json\.dumps\(|Define the JSON data|Convert the dictionary to a JSON string",obj["response"],re.I): |
| reasons.append("meta_json_generation_instead_of_solution") |
| if re.search(r'"instruction"\s*:\s*""|"response"\s*:\s*"\+\+"',obj["response"]): |
| reasons.append("degenerate_embedded_schema") |
| return sorted(set(reasons)),obj |
|
|
| def http_json(url,payload=None,timeout=900): |
| data=None; headers={} |
| if payload is not None: |
| data=json.dumps(payload,ensure_ascii=False).encode(); headers["Content-Type"]="application/json" |
| req=urllib.request.Request(url,data=data,headers=headers) |
| try: |
| with urllib.request.urlopen(req,timeout=timeout) as r: |
| return r.status,json.loads(r.read().decode("utf-8","replace")) |
| except urllib.error.HTTPError as e: |
| raw=e.read().decode("utf-8","replace") |
| try: body=json.loads(raw) |
| except Exception: body={"raw":raw} |
| return e.code,body |
| except Exception as e: |
| return 0,{"error":repr(e)} |
|
|
| def structured_payload(category,difficulty,language,variation,mode="json_schema"): |
| schema=json.loads(json.dumps(SCHEMA)) |
| schema["properties"]["category"]["enum"]=[category] |
| schema["properties"]["difficulty"]["enum"]=[difficulty] |
| schema["properties"]["language"]["enum"]=[language] |
| system=( |
| "๋น์ ์ ์ฝ๋ฉ ๊ฐ๋ฐ ๋น์๋ฅผ ์ํ ๊ณ ํ์ง SFT ๋ฐ์ดํฐ ์์ฑ๊ธฐ์
๋๋ค. ์ผ๋ฐ ๋ํ๋ ์ก๋ด์ ๋ง๋ค์ง ๋ง์ญ์์ค. " |
| "ํ๋์ ๋
๋ฆฝ์ ์ธ ์ค๋ฌดํ ์ฝ๋ฉ ๋ฌธ์ ์ ๊ทธ์ ๋ํ ์์ฑ๋ ์ต์ข
๋ต๋ณ์ ๋ง๋์ญ์์ค. " |
| "instruction์ ์ค์ ์ฌ์ฉ์์ ๊ตฌ์ฒด์ ์ธ ๊ฐ๋ฐ ์์ฒญ์ด์ด์ผ ํ๋ฉฐ ๋น ๋ฌธ์์ด์ด๋ฉด ์ ๋ฉ๋๋ค. " |
| "response๋ ๋ฐ๋ก ํ์ต์ ์ฌ์ฉํ ์ต์ข
Assistant ๋ต๋ณ์
๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์คํ ๊ฐ๋ฅํ ์ฝ๋, ์ค๋ฅ ์ฒ๋ฆฌ, ํ
์คํธ๋ฅผ ํฌํจํ์ญ์์ค. " |
| "<think> ๋๋ ๋ด๋ถ ์ถ๋ก ์ ์ถ๋ ฅํ์ง ๋ง์ญ์์ค. TODO/FIXME/์๋ต/placeholder๋ฅผ ์ฌ์ฉํ์ง ๋ง์ญ์์ค. " |
| "JSON์ ๋ง๋๋ Python ์์ ๋ฅผ ๋ต์ผ๋ก ์ฐ์ง ๋ง์ญ์์ค. ์ง์ ๋ JSON ๊ฐ์ฒด ๊ทธ ์์ฒด๋ง ๋ฐํํ์ญ์์ค. " |
| "๋ฐ๋์ instruction, response, category, difficulty, language ๋ค์ฏ ํ๋๋ง ๋ฐํํ์ญ์์ค." |
| ) |
| user=(f"์๋ก์ด ์ฝ๋ฉ SFT ์ํ 1๊ฐ๋ฅผ ์์ฑํ์ธ์.\ncategory={category}\ndifficulty={difficulty}\nlanguage={language}\n" |
| f"variation_id=V6-{variation:06d}\n์ถ๊ฐ ์กฐ๊ฑด: {ANGLES[variation%len(ANGLES)]}\n" |
| "์ด์ ์ํ์ ๋ณต์ฌํ์ง ๋ง๊ณ ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ ์ํฉ, ์
๋ ฅ/์ถ๋ ฅ ๋๋ ์คํจ ์กฐ๊ฑด์ ํฌํจํด ์๋ก ๋ค๋ฅธ ๊ณผ์ ๋ก ๋ง๋์ธ์.") |
| p={"model":MODEL,"messages":[{"role":"system","content":system},{"role":"user","content":user}], |
| "temperature":0.35,"top_p":0.9,"max_tokens":900} |
| if mode=="json_schema": |
| p["response_format"]={"type":"json_schema","json_schema":{"name":"coding_sft_record","schema":schema}} |
| elif mode=="structured_outputs": |
| p["structured_outputs"]={"json":schema} |
| elif mode=="json_object": |
| p["response_format"]={"type":"json_object"} |
| else: node-7.example.invalid ValueError(mode) |
| return p |
|
|
| def api_probe(): |
| code,body=http_json(MODELS,timeout=10) |
| if code!=200:return None,{"models_http":code,"body":body} |
| ids=[x.get("id") for x in body.get("data",[])] |
| if MODEL not in ids:return None,{"models_http":code,"ids":ids,"error":"LoRA model id missing"} |
| results=[] |
| for mode in ("json_schema","structured_outputs","json_object"): |
| code,b=http_json(API,structured_payload("python_algorithm","advanced","python",900001,mode),timeout=900) |
| rec={"mode":mode,"http":code} |
| if code==200: |
| try: content=b["choices"][0]["message"]["content"] |
| except Exception: |
| rec["error"]="missing_content"; results.append(rec); continue |
| obj,perr=parse_content(content) |
| vr,norm=validate(obj,("python_algorithm","advanced","python")) if obj is not None else ([perr],None) |
| rec["parse_error"]=perr; rec["validation"]=vr; rec["preview"]=normalize_text(content)[:500] |
| results.append(rec) |
| if not vr:return mode,{"selected":mode,"results":results} |
| else: |
| rec["body"]=b;results.append(rec) |
| return None,{"selected":None,"results":results} |
|
|
| def mem_available_gib_local(): |
| vals={} |
| for line in Path("/proc/meminfo").read_text().splitlines(): |
| if ":" in line: |
| k,v=line.split(":",1);p=v.split() |
| if p and p[0].isdigit(): vals[k]=int(p[0])*1024 |
| return vals.get("MemAvailable",0)/(1024**3) |
|
|
| def mem_available_gib_sub(): |
| try: |
| p=subprocess.run(["ssh","-o","BatchMode=yes","-o","ConnectTimeout=5","harness_user_2@192.0.2.13", |
| "awk '/MemAvailable:/ {print $2}' /proc/meminfo"],capture_output=True,text=True,timeout=10) |
| if p.returncode:return None |
| return float(p.stdout.strip())/1024/1024 |
| except Exception:return None |
|
|
| def load_hashes(path): |
| out=set() |
| if not path.is_file():return out |
| for line in path.read_text(errors="replace").splitlines(): |
| try:o=json.loads(line) |
| except Exception:continue |
| ins=normalize_text(o.get("instruction","")).lower() |
| if ins:out.add(hashlib.sha256(ins.encode()).hexdigest()) |
| return out |
|
|
| def selftest(): |
| good={"instruction":"Python์์ ๋์ฉ๋ JSONL ํ์ผ์ ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์์ผ๋ก ์ค๋ณต ์ ๊ฑฐํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์ฆํ๋ ๋๊ตฌ๋ฅผ ์์ฑํ์ธ์.", |
| "response":"์ ์ฒด ํ์ผ์ ํ๊บผ๋ฒ์ ์ฝ์ง ์๊ณ ์ค ๋จ์๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.\n```python\nfrom pathlib import Path\n\ndef unique_count(path: Path) -> int:\n seen=set()\n with path.open(encoding='utf-8') as f:\n for line in f:\n seen.add(line.rstrip('\\n'))\n return len(seen)\n\nassert callable(unique_count)\n```\n์ค๋ฌด์์๋ JSON ํ์ฑ ์ค๋ฅ๋ฅผ ๋ณ๋ quarantine ํ์ผ๋ก ๊ธฐ๋กํ๊ณ , ๋์ฉ๋ ๋ฐ์ดํฐ์์๋ ์ธ๋ถ ์ ์ฅ์๋ ํด์ ํํฐ์
๋์ผ๋ก seen ์งํฉ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ ํํ๋ ๋ฐฉ์์ผ๋ก ํ์ฅํ ์ ์์ต๋๋ค.", |
| "category":"python_data_pipeline","difficulty":"advanced","language":"python"} |
| vr,_=validate(good,("python_data_pipeline","advanced","python")); assert not vr,vr |
| bad=dict(good);bad["response"]="<think>x</think> TODO" |
| vr,_=validate(bad,("python_data_pipeline","advanced","python")); assert "todo_or_incomplete" in vr |
| assert normalize_text("Aฤ BฤC")=="A B\nC" |
| print("SELFTEST=PASS") |
|
|
| def main(): |
| ap=argparse.ArgumentParser() |
| ap.add_argument("--target",type=int,default=2000) |
| ap.add_argument("--max-attempts",type=int,default=5000) |
| ap.add_argument("--mode",choices=["json_schema","structured_outputs","json_object"]) |
| ap.add_argument("--self-test",action="store_true") |
| a=ap.parse_args() |
| if a.self_test:selftest();return 0 |
| if not a.mode:node-7.example.invalid SystemExit("--mode required") |
|
|
| OUTROOT.mkdir(parents=True,exist_ok=True);LOGROOT.mkdir(parents=True,exist_ok=True) |
| accepted=OUTROOT/"accepted.jsonl"; rejected=OUTROOT/"rejected.jsonl"; statep=OUTROOT/"state.json" |
| state={"phase":"generation","accepted":0,"target":a.target,"attempts":0,"rejected":0,"api_failures":0, |
| "mode":a.mode,"started_at":now(),"updated_at":now()} |
| if statep.is_file(): |
| try: |
| old=json.loads(statep.read_text()) |
| if old.get("mode")==a.mode:state.update(old);state["phase"]="generation";state["target"]=a.target |
| except Exception:pass |
| hashes=load_hashes(accepted);state["accepted"]=len(hashes);atomic_json(statep,state) |
|
|
| while state["accepted"]<a.target and state["attempts"]<a.max_attempts: |
| state["attempts"]+=1;variation=state["attempts"];expected=CATEGORIES[(variation-1)%len(CATEGORIES)] |
| code,body=http_json(API,structured_payload(*expected,variation,a.mode),timeout=900) |
| if code!=200: |
| state["api_failures"]+=1 |
| fsync_append(rejected,{"attempt":variation,"time":now(),"reason":["api_http"],"http":code,"body":body}) |
| else: |
| try:content=body["choices"][0]["message"]["content"] |
| except Exception: |
| content=None;state["rejected"]+=1 |
| fsync_append(rejected,{"attempt":variation,"time":now(),"reason":["api_missing_content"],"body":body}) |
| if content is not None: |
| obj,perr=parse_content(content) |
| if obj is None: |
| state["rejected"]+=1 |
| fsync_append(rejected,{"attempt":variation,"time":now(),"reason":[perr],"raw":content[:20000]}) |
| else: |
| vr,obj=validate(obj,expected) |
| if not vr: |
| h=hashlib.sha256(obj["instruction"].strip().lower().encode()).hexdigest() |
| if h in hashes:vr=["duplicate_instruction"] |
| else:hashes.add(h) |
| if vr: |
| state["rejected"]+=1 |
| fsync_append(rejected,{"attempt":variation,"time":now(),"reason":vr,"raw":content[:20000],"parsed":obj}) |
| else: |
| fsync_append(accepted,{"id":f"CODE-V6-{state['accepted']+1:05d}",**obj});state["accepted"]+=1 |
|
|
| if state["attempts"]%3==0: |
| mm=mem_available_gib_local();sm=mem_available_gib_sub() |
| state["main_mem_available_gib"]=mm;state["sub_mem_available_gib"]=sm |
| if mm<2.0 or (sm is not None and sm<2.0): |
| state["phase"]="safe_stop_low_memory";state["updated_at"]=now();atomic_json(statep,state) |
| print(f"SAFE_STOP_LOW_MEMORY main={mm:.2f} sub={sm}");return 20 |
| state["updated_at"]=now();atomic_json(statep,state) |
| print(f"PROGRESS accepted={state['accepted']} attempts={state['attempts']} rejected={state['rejected']} api_failures={state['api_failures']}",flush=True) |
|
|
| state["phase"]="complete" if state["accepted"]>=a.target else "max_attempts";state["updated_at"]=now();atomic_json(statep,state) |
| report={"accepted":state["accepted"],"target":a.target,"attempts":state["attempts"],"rejected":state["rejected"], |
| "api_failures":state["api_failures"],"mode":a.mode,"accepted_file":str(accepted), |
| "sha256":hashlib.sha256(accepted.read_bytes()).hexdigest() if accepted.is_file() else None,"finished_at":now()} |
| atomic_json(OUTROOT/"final_report.json",report);print(json.dumps(report,ensure_ascii=False,indent=2)) |
| return 0 if state["accepted"]>=a.target else 2 |
|
|
| if __name__=="__main__":node-7.example.invalid SystemExit(main()) |
|
|