Skip to content
गाइड9 मिनट में पढ़ें

बेहतर कोडिंग एजेंट प्रशिक्षित करने के लिए प्रोसेस रिवॉर्ड डेटा कैसे जुटाएँ

Potato में PRM प्रशिक्षण के लिए प्रति-चरण रिवॉर्ड लेबल जुटाएँ, first-error और प्रति-चरण मोड के साथ, और एक स्क्रिप्ट से निर्यात को प्रशिक्षण के लायक़ बनाएँ।

Potato Team

प्रोसेस रिवॉर्ड मॉडल क्या हैं?

प्रोसेस रिवॉर्ड लेबल करने के दो तरीक़े: first-error मोड एक टूटन-बिंदु चिह्नित करता है, प्रति-चरण मोड हर चरण को रेट करता हैप्रोसेस रिवॉर्ड लेबल करने के दो तरीक़े

आउटकम रिवॉर्ड मॉडल कोडिंग एजेंट की ट्रैजेक्टरी के सिर्फ़ आख़िर को देखते हैं: कोड कंपाइल हुआ या नहीं, टेस्ट पास हुए या नहीं, समस्या हल हुई या नहीं। प्रोसेस रिवॉर्ड मॉडल इसकी जगह बीच के हर चरण को स्कोर करते हैं। हर चरण पर संकेत मिलने से प्रशिक्षण ठीक-ठीक पकड़ पाता है कि एजेंट कहाँ ग़लत हुआ।

हाल का काम इसी दिशा में गया है। AgentPRM एजेंट कामों के लिए प्रोसेस रिवॉर्ड को नए सिरे से परिभाषित करता है। वह हर क्रिया को इस आधार पर अंक देता है कि वह लक्ष्य की ओर कितनी प्रगति कराती है, न कि वह सही है या नहीं, और जिन बेसलाइनों से तुलना करता है उनसे 8 गुना से ज़्यादा बेहतर कंप्यूट दक्षता बताता है। ToolRM ने पाया कि प्राकृतिक भाषा के आउटपुट पर प्रशिक्षित रिवॉर्ड मॉडल tool call को ठीक से नहीं आँक पाते, इसलिए उसने tool-विशिष्ट रिवॉर्ड मॉडल बनाए और उन्हें परखने के लिए FC-RewardBench। इसके उलट, DeepSWE एक कोडिंग एजेंट को सिर्फ़ एक विरल परिणाम-रिवॉर्ड पर प्रशिक्षित करता है, यानी टेस्ट पास हुए या नहीं, और SWE-bench Verified पर 42.2% Pass@1 तथा टेस्ट-टाइम स्केलिंग के साथ 59% तक पहुँचता है। यही वह केवल-परिणाम वाला ढाँचा है जिसे प्रोसेस सुपरविज़न सुधारना चाहता है।

इन सबको चरण-स्तरीय मानवीय एनोटेशन की ज़रूरत है, और अड़चन आम तौर पर वहीं होती है। योजना के संदर्भ के लिए प्रोसेस रिवॉर्ड दस्तावेज़ देखें।

दो एनोटेशन मोड

First-Error मोड

एनोटेटर ट्रैजेक्टरी को ऊपर से नीचे पढ़ता है और उस पहले चरण पर क्लिक करता है जहाँ एजेंट ग़लती करता है। उससे पहले का हर चरण सही दर्ज होता है, और वह चरण तथा उसके बाद का सब कुछ ग़लत। यह तेज़ है क्योंकि हर ट्रेस पर एक ही निर्णय लेना होता है, और यह उस आम स्थिति के लिए ठीक है जिसमें पटरी से उतरा एजेंट फिर नहीं संभलता।

yaml
annotation_schemes:
  - annotation_type: process_reward
    name: step_rewards
    description: "Click the first step where the agent makes a mistake. Steps after it are flagged automatically."
    mode: first_error
    steps_key: structured_turns
    step_text_key: content

प्रति-चरण मोड

हर चरण को अपना लेबल मिलता है, जिससे वह पकड़ में आता है जो first-error नहीं पकड़ सकता: आंशिक सुधार, कोई हानिरहित चक्कर, या ऐसा चरण जो अपने आप में ठीक है पर संदर्भ में ग़लत। allow_neutral PRM800K जैसा तीसरा विकल्प जोड़ता है।

yaml
annotation_schemes:
  - annotation_type: process_reward
    name: step_rewards
    description: "Rate each step correct, neutral, or incorrect"
    mode: per_step
    steps_key: structured_turns
    step_text_key: content
    allow_neutral: true
    inline_with_trace: true

inline_with_trace नियंत्रणों को डिस्प्ले में हर चरण के साथ जोड़ देता है। लंबे ट्रेस में यह मायने रखता है, जहाँ अलग पैनल होने से एनोटेटर भूल जाता है कि वह कहाँ था।

प्रोजेक्ट सेट करना

चरण 1: ट्रेस तैयार करें

हर पंक्ति एक ट्रैजेक्टरी है। steps_key चरणों की सूची का नाम बताता है और step_text_key हर चरण के भीतर उस फ़ील्ड का जिसे दिखाना है:

json
{
  "id": "trace_001",
  "issue_description": "Fix the TypeError in parse_config() when the config file is empty",
  "structured_turns": [
    {"step_idx": 0, "type": "thinking", "content": "I need to find where the TypeError occurs. Let me read the file."},
    {"step_idx": 1, "type": "file_read", "content": "def parse_config(path):\n    with open(path) as f:\n        data = json.load(f)\n    return data['settings']"},
    {"step_idx": 2, "type": "thinking", "content": "When the file is empty json.load returns None, so None['settings'] raises. Add a check."},
    {"step_idx": 3, "type": "file_edit", "content": "+    if data is None:\n+        return {}"},
    {"step_idx": 4, "type": "bash_command", "content": "python -m pytest tests/test_config.py -v  ->  2 passed"}
  ]
}

किसी दूसरे एजेंट फ़ॉर्मैट से बदलना CLI का एक चरण है:

bash
python -m potato.trace_converter \
  --input claude_code_sessions.json \
  --input-format claude_code \
  --output data/traces.jsonl

--list-formats बताता है कि कौन-कौन से फ़ॉर्मैट समर्थित हैं, जिनमें claude_code, aider, swe_agent_trajectory, swebench, react, langchain और नौ और शामिल हैं। अगर पक्का न हो कि आपके पास कौन-सा है, तो --auto-detect इस्तेमाल करें।

चरण 2: कॉन्फ़िगरेशन लिखें

yaml
annotation_task_name: "PRM Data Collection"
task_dir: "."
 
data_files:
  - "data/traces.jsonl"
 
item_properties:
  id_key: id
  text_key: issue_description
 
instance_display:
  fields:
    - key: issue_description
      type: text
      label: "Issue"
 
    - key: structured_turns
      type: coding_trace
      label: "Agent trajectory"
      display_options:
        diff_view: unified
        show_file_tree: true
        show_reasoning: true
        terminal_theme: dark
        collapse_long_outputs: true
        max_output_lines: 50
 
annotation_schemes:
  - annotation_type: process_reward
    name: step_rewards
    description: "Click the first step where the agent makes an error"
    mode: first_error
    steps_key: structured_turns
    step_text_key: content
 
  - annotation_type: radio
    name: outcome
    description: "Did the agent resolve the issue?"
    labels:
      - name: resolved
        text: "Fully resolved"
      - name: partial
        text: "Partially resolved"
      - name: not_resolved
        text: "Not resolved"
 
  - annotation_type: text
    name: error_description
    description: "If the trajectory went wrong, describe the error briefly"
    rows: 3
    placeholder: "e.g. edited the wrong file"
    label_requirement:
      required: false
 
num_annotators_per_item: 2
 
attention_checks:
  enabled: true
  items_file: "data/attention_checks.json"
  frequency: 20
  min_response_time: 20.0
  failure_handling:
    warn_threshold: 2
    block_threshold: 5
 
user_config:
  allow_all_users: false
  users:
    - reviewer1@example.com
    - reviewer2@example.com
    - reviewer3@example.com
 
output_annotation_dir: "annotation_output/"
export_annotation_format: jsonl

लेबल ऑब्जेक्ट की पहचान name से होती है। text फ़ील्ड वह है जो एनोटेटर पढ़ता है, इसलिए शब्द बदलने पर भी संग्रहीत मान एक स्थिर पहचानकर्ता बना रहता है।

यहाँ काम का नियंत्रण min_response_time: 20.0 है। diff वाली बहु-चरण ट्रैजेक्टरी कोई बीस सेकंड में नहीं पढ़ता, इसलिए यह उस जल्दबाज़ी को पकड़ता है जिसे प्रति-ट्रेस समय-सीमा नहीं पकड़ती।

चरण 3: सर्वर चलाएँ

bash
potato start config.yaml -p 8000

प्रशिक्षण के लिए निर्यात

coding_eval एक्सपोर्टर प्रशिक्षण फ़ॉर्मैट सीधे लिखता है, इसलिए डेटा को ढालने का काम आपको नहीं करना पड़ता:

bash
python -m potato.export -c config.yaml -f coding_eval -o ./export/

यह चरण-स्तरीय लेबल के साथ prm_training_data.jsonl, एनोटेशन से बने DPO-शैली के चुने और ठुकराए गए ट्रेस के साथ preference_pairs.jsonl, swebench_results.jsonl और code_reviews.jsonl लिखता है। डिफ़ॉल्ट रूप से चारों लिखे जाते हैं। इसे सीमित करने के लिए एक्सपोर्टर का types विकल्प दें: --option types=prm,preference। अगर माँगे गए किसी प्रकार के लिए स्टडी में कोई डेटा नहीं मिला, तो यह ख़ाली फ़ाइल लिखने की जगह इसकी सूचना देता है।

एनोटेशन योजना के नाम के नीचे एक steps सूची और मोड के रूप में संग्रहीत होता है। reward सही के लिए 1, ग़लत के लिए -1, न्यूट्रल के लिए 0, और जिस चरण को किसी ने नहीं आँका उसके लिए null होता है:

json
{
  "step_rewards": {
    "mode": "first_error",
    "steps": [
      {"reward": 1, "source": "human", "verified": true},
      {"reward": 1, "source": "human", "verified": true},
      {"reward": -1, "source": "human", "verified": true},
      {"reward": -1, "source": "human", "verified": true}
    ]
  }
}

स्क्रिप्ट का सहारा तभी लें जब आपको ऐसा आकार चाहिए जो एक्सपोर्टर नहीं लिखता। आम वजह बिना आँके गए चरणों को ख़ुद सँभालना है, क्योंकि जिस चरण को किसी ने नहीं आँका वह 0 नहीं, null होता है:

python
import json
from pathlib import Path
 
SCHEME = "step_rewards"
 
examples = []
for f in Path("export/").rglob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            rec = json.loads(line)
            payload = (rec.get("labels", rec) or {}).get(SCHEME)
            if not payload:
                continue
 
            steps = payload.get("steps", [])
            # Drop traces with unjudged steps rather than guessing at them.
            if any(s.get("reward") is None for s in steps):
                continue
 
            item = items_by_id[rec["instance_id"]]      # your own data file
            examples.append({
                "trace_id": rec["instance_id"],
                "prompt": item["issue_description"],
                "steps": [
                    {"content": src["content"], "label": s["reward"]}
                    for src, s in zip(item["structured_turns"], steps)
                ],
            })
 
with open("prm_labels.jsonl", "w") as out:
    for ex in examples:
        out.write(json.dumps(ex) + "\n")
 
print(f"Wrote {len(examples)} traces")

प्रेफ़रेंस जोड़े इन्हीं एनोटेशन से बनते हैं। जहाँ दो ट्रेस ने एक ही समस्या पर काम किया, वहाँ एक्सपोर्टर उन्हें जोड़ता है और जिस ट्रेस की पहली ग़लती बाद में आती है, या जिसमें कोई ग़लती नहीं, उसे चुना गया ट्रेस मानता है।

विश्लेषण

पहली ग़लती कहाँ पड़ती है

python
import json
from pathlib import Path
 
SCHEME = "step_rewards"
 
traces = []
for f in Path("export/").rglob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            payload = (json.loads(line).get("labels", {}) or {}).get(SCHEME)
            if payload:
                traces.append(payload)
 
positions = []
all_correct = 0
for t in traces:
    steps = t.get("steps", [])
    first = next((i for i, s in enumerate(steps) if s.get("reward") == -1), None)
    if first is None:
        all_correct += 1
    elif len(steps) > 1:
        positions.append(first / (len(steps) - 1))
 
print(f"Traces annotated: {len(traces)}")
print(f"No errors: {all_correct} ({all_correct / len(traces):.1%})")
if positions:
    print(f"Average first-error position: {sum(positions) / len(positions):.2f} (0=start, 1=end)")

पहली ग़लती पर सहमति

दो एनोटेटर शायद ही कभी एक ही चरण चुनते हैं, और सटीक सहमति उनकी असल सहमति को कम करके दिखाती है। पहले स्थिति को बिन में बाँटने से ज़्यादा जानकारी वाली तुलना मिलती है:

python
 
def bin_position(steps):
    first = next((i for i, s in enumerate(steps) if s.get("reward") == -1), None)
    if first is None:
        return "all_correct"
    return "early_error" if first < len(steps) / 2 else "late_error"

इन बिन की तुलना उन एनोटेटरों के बीच करें जो एक जैसे आइटम साझा करते हैं, उस ओवरलैप का इस्तेमाल करते हुए जो num_annotators_per_item से मिलता है।

सुझाव

रफ़्तार के लिए first-error मोड इस्तेमाल करें। सर्च को दिशा देने वाले PRM के प्रशिक्षण के लिए यह एनोटेशन लागत के एक हिस्से में पर्याप्त संकेत देता है, और वैसे भी ज़्यादातर एजेंट सिलसिलेवार विफल होते हैं।

बारीकी चाहिए तो प्रति-चरण मोड इस्तेमाल करें। आंशिक सुधार और हानिरहित चक्कर लेबल के रूप में सिर्फ़ प्रति-चरण मोड में मौजूद होते हैं।

अनुभवी एनोटेटरों से शुरुआत करें। इसमें कोड, diff और टर्मिनल आउटपुट पढ़ना पड़ता है। छोटे समूह से शुरू करें, सहमति मापें, साझा उदाहरणों पर कैलिब्रेट करें, फिर विस्तार करें।

प्रतिक्रिया समय की न्यूनतम सीमा तय करें। ट्रैजेक्टरी लंबी हो जाती हैं, और न्यूनतम सीमा एनोटेटरों को diff पर से भागते हुए निकलने से रोकती है।

कैलिब्रेशन पास चलाएँ। प्रोडक्शन एनोटेशन शुरू होने से पहले सबसे वही दस से बीस ट्रेस लेबल करवाएँ और असहमतियों पर बात करें।