अध्याय २ · खण्ड II · 26 मिनेट
संरचित आउटपुट: JSON फिर्ता पाउने
LLM-in-a-pipeline अनुप्रयोगहरूको कार्यकर्ता ढाँचा। सधैँ parse हुने JSON पाउनु, यसलाई schema विरुद्ध validate गर्नु, र यो गलत हुने साना तरिकाहरू सम्हाल्नु।
यदि तपाईंले वास्तविक LLM-powered प्रणाली निर्माण गर्नुहुन्छ भने, तपाईंका अधिकांश कलहरूले user लाई prose फर्काउने छैनन् — तिनले तपाईंको कोडले process गर्ने structured data फर्काउनेछन्। Extraction, वर्गीकरण, tagging, form-filling, tool invocation: यी सबै structured-output कामहरू हुन्। भाषा मोडेलबाट JSON भरपर्दो रूपमा फिर्ता पाउनु आफ्नै एउटा सानो कला हुन्छ, विशिष्ट pitfall र विशिष्ट प्रविधिहरूसहित। यो खण्डले कला सिकाउँछ।
विशेष रूपमा JSON किन
JSON पूर्वनिर्धारित हो किनकि:
- हरेक programming भाषामा यसका लागि parser छ।
- संरचना (keys र values) प्राकृतिक रूपमा prompts ले fields वर्णन गर्ने तरिकासँग मेल खान्छ।
- Schema विरुद्ध validate गर्न सजिलो छ।
- LLMs, ठूलो मात्रामा code र web data मा तालिम पाएकोले, यसलाई धाराप्रवाह उत्पादन गर्छन्।
तपाईंले मोडेललाई XML, YAML, वा custom ढाँचा मागिन सक्नुहुन्छ। नमाग्नुहोस्। हरेक विकल्प पृथ्वीको हरेक parsing library र मोडेल दुवैले खराब समर्थन गर्छन्। JSON प्रयोग गर्नुहोस्।
नैव दृष्टिकोण र किन असफल हुन्छ
प्रवृत्ति भद्रसँग सोध्नु हो:
prompt = """Extract the fields from this receipt:
"KHALTI PAYMENT — Rs 1,240 at Bhatbhateni on 2026-06-15"
Return JSON with vendor, amount, date."""
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
temperature=0.0,
messages=[{"role": "user", "content": prompt}],
)
data = json.loads(response.content[0].text)
यो अधिकांश समय काम गर्छ। तर “अधिकांश समय” “उत्पादनमा” होइन, किनकि कहिलेकाहीँ मोडेलले फर्काउँछ:
Here's the JSON:
```json
{
"vendor": "Bhatbhateni",
"amount": 1240,
"date": "2026-06-15"
}
Let me know if you need any changes!
वा:
```text
{
vendor: "Bhatbhateni",
amount: 1240,
date: "2026-06-15", // note: converted to ISO format
}
वा:
{"vendor":"Bhatbhateni","amount":"Rs 1,240","date":"June 15, 2026"}
यीमध्ये कुनै पनि json.loads वा downstream कोड तोड्नेछ, र विफलता मोड “तपाईंका 5% users ले local testing मा तपाईंले कहिल्यै नदेख्ने bug भेट्छन्” हो।
JSON output का लागि पाँच बानी
Structured output लाई भरपर्दो बनाउने प्रविधिहरू:
1. तपाईं चाहनुभएको ठ्याक्कै schema देखाउनुहोस्
"Return JSON with exactly these fields:
{
"vendor": string (the merchant name),
"amount_npr": number (in Nepali rupees, integer if possible),
"date": string (YYYY-MM-DD format)
}
मोडेललाई अब हरेक field नाम, हरेक type, र हरेक convention थाहा छ। अस्पष्टता नाटकीय रूपमा घट्छ।
2. “केवल JSON फर्काउनुहोस्” भन्नुहोस्
"Return only the JSON object. No markdown code fences, no prose
before or after, no explanations."
यो एक निर्देशनले “JSON वरिपरि अतिरिक्त पाठ” समस्याको 80% हटाउँछ। यो सधैँ पर्याप्त हुँदैन (केही मोडेलले अझै fences थप्छन्), त्यसैले हामी downstream पनि रक्षा गर्छौं।
3. सकारात्मक उदाहरण दिनुहोस्
"For example, given the input 'ESewa Rs. 500 at 2026-05-10',
you should return:
{"vendor": "eSewa", "amount_npr": 500, "date": "2026-05-10"}
"
Few-shot उदाहरणहरू, फेरि। एउटा उदाहरण structured output का लागि सामान्यतया पर्याप्त छ; दुई सुरक्षित छ।
4. विफलता केस परिभाषित गर्नुहोस्
"If the receipt is invalid or fields cannot be determined, return:
{"error": "parse_failed", "reason": "brief explanation"}
"
Error case आफै मान्य JSON हो, त्यसैले downstream कोडसँग एउटा parsing बाटो छ। फिर्ता आउने अरू केही prompt वा मोडेलमा bug हो।
5. Strip र defensive parse
माथिका चार बानीसँगै, stray markdown fences विरुद्ध रक्षा गर्नुहोस्:
import json
import re
def parse_llm_json(text: str) -> dict:
# Strip common markdown code fences
text = text.strip()
text = re.sub(r"^```(?:json)?\s*", "", text)
text = re.sub(r"\s*```$", "", text)
return json.loads(text)
यसले “मोडेलले markdown fences थप्यो” लाई outage बाट non-event मा बदल्छ।
Anthropic को समर्पित समर्थन
Anthropic (र OpenAI, र अरू) संग structured output का लागि विशिष्ट features छन्:
Prefill — तपाईं assistant को प्रतिक्रिया यसको लागि सुरु गर्न सक्नुहुन्छ। यदि तपाईंले { सँग prefill गर्नुभयो भने, मोडेल essentially JSON सँग जारी राख्न बाध्य हुन्छ:
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=400,
temperature=0.0,
messages=[
{"role": "user", "content": prompt},
{"role": "assistant", "content": "{"}, # prefill
],
)
# The response starts inside the JSON; prepend the opening brace when parsing
json_text = "{" + response.content[0].text
data = json.loads(json_text)
यो बलियो प्रविधि हो — मोडेल शाब्दिक रूपमा markdown fences वा prose सँग सुरु गर्न सक्दैन, किनकि तपाईंले पहिले नै { सँग प्रतिक्रिया सुरु गर्नुभएको छ।
Tool use — Anthropic को tool-use API (अध्याय 6) ले तपाईंलाई JSON schema सँग tool घोषणा गर्न दिन्छ, र मोडेलको output guarantee गरिएको schema conform गर्छ। यो सबैभन्दा बलियो guarantee हो, तर यसमा बढी setup छ — हामी यसलाई अध्याय 6 मा राम्ररी ओगट्छौं।
Course 04 का अधिकांश उदाहरणहरूका लागि हामी “prompt + defensive parser” दृष्टिकोण प्रयोग गर्छौं किनकि यो कुनै पनि provider सँग काम गर्छ। जब तपाईंलाई थाहा हुन्छ तपाईं Anthropic मा हुनुहुन्छ, absolute reliability का लागि prefill वा tool use लाई प्राथमिकता दिनुहोस्।
Pydantic सँग validation
JSON पाउनु सही JSON पाउनु जस्तै होइन। "amount_npr": "one thousand" सँगको प्रतिक्रिया मान्य JSON हो तर बेकार।
Programmatically प्रयोग हुने कुनै पनि structured output का लागि, pydantic जस्तो schema library सँग validate गर्नुहोस्:
from pydantic import BaseModel, Field, ValidationError
from typing import Optional
from datetime import date
class ReceiptFields(BaseModel):
vendor: str = Field(min_length=1, max_length=200)
amount_npr: int = Field(ge=0, le=100_000_000)
date: date
def extract_receipt(text: str) -> Optional[ReceiptFields]:
prompt = build_extraction_prompt(text)
response = client.messages.create(...)
raw = parse_llm_json(response.content[0].text)
try:
return ReceiptFields(**raw)
except ValidationError as e:
logger.warning(f"Validation failed for receipt: {e}")
return None
अब तपाईंसँग तीन outcomes छन्:
- मान्य JSON, मान्य schema → typed
ReceiptFieldsobject, प्रयोग गर्न सुरक्षित। - मान्य JSON, अमान्य schema →
None, logged, downstream कोडले सम्हाल्छ। - अमान्य JSON →
json.loadsraise हुन्छ, उही handler ले समात्छ।
यीमध्ये हरेक ज्ञात प्रतिक्रियासँग ज्ञात विफलता मोड हो। कोड बोरिङ रहन्छ।
पूर्ण extraction pipeline
Receipt केसका लागि सबै सँगै राख्दा:
import json
import re
from datetime import date
from typing import Optional
from pydantic import BaseModel, Field, ValidationError
import anthropic
client = anthropic.Anthropic()
class ReceiptFields(BaseModel):
vendor: str = Field(min_length=1, max_length=200)
amount_npr: int = Field(ge=0, le=100_000_000)
date: date
SYSTEM_PROMPT = "You are a receipt-parsing assistant. Return valid JSON only."
USER_PROMPT_TEMPLATE = """Extract fields from this Nepali/English receipt.
Receipt text:
{receipt_text}
Return only JSON with exactly these fields:
{{
"vendor": string (merchant name),
"amount_npr": integer (in NPR, no commas or currency symbols),
"date": string (YYYY-MM-DD format)
}}
Example — given 'ESewa Rs. 500 at 2026-05-10', return:
{{"vendor": "eSewa", "amount_npr": 500, "date": "2026-05-10"}}
If the input is not a receipt, return:
{{"error": "not_a_receipt", "reason": "short explanation"}}
Return only the JSON object. No markdown, no prose."""
def parse_llm_json(text: str) -> dict:
text = text.strip()
text = re.sub(r"^```(?:json)?\s*", "", text)
text = re.sub(r"\s*```$", "", text)
return json.loads(text)
def extract_receipt(receipt_text: str) -> Optional[ReceiptFields]:
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=300,
temperature=0.0,
system=SYSTEM_PROMPT,
messages=[{
"role": "user",
"content": USER_PROMPT_TEMPLATE.format(receipt_text=receipt_text),
}],
)
raw = parse_llm_json(response.content[0].text)
if "error" in raw:
return None
try:
return ReceiptFields(**raw)
except ValidationError:
return None
if __name__ == "__main__":
receipt = "KHALTI PAYMENT — Rs 1,240 at Bhatbhateni on 2026-06-15"
fields = extract_receipt(receipt)
if fields:
print(f"Vendor: {fields.vendor}")
print(f"Amount: NPR {fields.amount_npr}")
print(f"Date: {fields.date}")
else:
print("Could not parse receipt.")
Eighty लाइन। खाली input, non-receipt input, malformed JSON, र अमान्य field मानहरू सम्हाल्छ। Happy path तीन लाइन usage कोड हो। यो हरेक वास्तविक LLM-powered extraction pipeline तपाईंले निर्माण गर्ने आकार हो।
आफ्नो बुझाइ जाँच्नुहोस्
Quick check
—एक साथीको JSON extraction pipeline उत्पादनमा लगभग 3% पटक असफल हुन्छ किनकि मोडेलले कहिलेकाहीँ output लाई markdown code fences मा बेर्छ। दुई-भागको समाधान के हो?
Quick check
—तपाईंको prompt ले भरपर्दो रूपमा मान्य JSON उत्पादन गर्छ, तर एक field, `amount_npr`, ले कहिलेकाहीँ 'one thousand' वा 'Rs 500' जस्ता strings समावेश गर्छ integer को सट्टा। सही रक्षा के हो?
अब के आउँछ
तपाईंले LLM बाट structured output प्राप्त गर्न सक्नुहुन्छ। अध्याय 2 को अन्तिम खण्डले उत्पादन वास्तविकताको बाँकी भाग सम्हाल्छ: त्रुटि, rate limits, र खराब प्रतिक्रिया। Network कलहरू असफल हुन्छन्; APIs 429s फर्काउँछन्; कहिलेकाहीँ मोडेलले केवल अस्वीकार गर्छ। दिन 1 देखि यी केसका लागि निर्माण गर्नु त्यो एप हो जुन तपाईंलाई pager नबजाई एक वर्षसम्म चल्छ र त्यो एप जुन तपाईंलाई हप्तामा तीन पटक pager बजाउँछ — बीचको फरक हो।