LLM میں ٹرسٹڈ سٹرکچرڈ ڈیٹا کیسے حاصل کریں۔

زبان کے ماڈلز کو کال کرنے کے زیادہ تر سبق اس پر ختم ہوتے ہیں: JSON.parse(response.content). یہ لائن پہلے 10 ٹیسٹ کیسز کے لیے کام کرتی ہے۔ پھر جب آپ اسے بھیجتے ہیں تو درخواست 400 کے آس پاس یہ یا تو ماڈل کی ایجاد کی تاریخ واپس کر دے گا، یا اگر آپ کا سکیما 5 کی اجازت دیتا ہے تو 8 صفوں کی اشیاء واپس کر دے گا، یا خاموشی سے غائب ایک فیلڈ کے ساتھ بالکل درست JSON آبجیکٹ واپس کر دے گا۔

میں Temploracraft کی تعمیر کے دوران اس مسئلے کا شکار ہوا، ایک ریزیوم ٹول جو اپ لوڈ کردہ دستاویزات لیتا ہے اور انہیں سٹرکچرڈ ڈیٹا میں تبدیل کرتا ہے جس میں ایپلی کیشن ترمیم کر سکتی ہے۔

ان پٹ واقعی غیر متوقع ہے۔ ایک دو کالم پی ڈی ایف، اصل میں ایک ٹیبل نہیں، بلکہ ایک ٹیبل، جس میں تقریباً 14 مختلف فارمیٹس میں تاریخیں لکھی گئی ہیں۔ آؤٹ پٹ سخت ہونا چاہیے کیونکہ نکالے گئے تمام فیلڈز انسانی پڑھنے کے قابل شکل میں ظاہر ہوتے ہیں۔ اگر ماڈل غلط طریقے سے تاریخ درج کرتا ہے، تو صارفین اسے تقریباً 2 سیکنڈ میں دیکھیں گے۔

یہ مضمون "ماڈل کچھ متن واپس کرتا ہے” اور "میری ایپلی کیشن میں قابل اعتماد ڈیٹا ہے” کے درمیان پرت کے بارے میں ہے۔ ہم آؤٹ پٹ کو محدود کرنے کے لیے تین میکانزم کا احاطہ کرتے ہیں، کیوں ایک سکیما کو پہلے ڈیزائن کرنا ایک طویل پرامپٹ لکھنے سے بہتر ہے، دوبارہ کوشش کرنے والا لوپ کیسے بنایا جائے جس سے پیسہ ضائع نہ ہو، اور ان غلطیوں کے بارے میں کیا کیا جائے جو دوبارہ کوششوں سے حل نہ ہوں۔

انڈیکس

شرطیں

یہاں کچھ چیزیں ہیں جو آپ کو آرام سے حاصل کرنے کی ضرورت ہوگی:

  • ٹائپ اسکرپٹ کا کام کا علم: مثالوں میں ٹائپ انفرنس اور جنرک کا ہلکا استعمال ہوتا ہے، لہذا آپ کو بغیر رکے ٹائپ تشریحات پڑھنے کے قابل ہونا چاہیے۔

  • زبان ماڈل API کو کم از کم ایک بار کال کیا گیا تھا۔ آپ کو ماہر بننے کی ضرورت نہیں ہے، لیکن آپ کو معلوم ہونا چاہیے کہ سسٹم پرامپٹ کیا ہے اور تقریباً ٹوکن کیا ہے۔

  • JSON اسکیما سے واقفیت مددگار ہے، لیکن اس کی ضرورت نہیں ہے۔ اہم حصے سامنے آنے پر میں وضاحت کروں گا۔

  • Node.js 18+ اگر آپ مثال چلانا چاہتے ہیں، کیونکہ اس میں مقامی استعمال ہوتا ہے۔ fetch اور متضاد تکرار کرنے والے۔

کوڈ کے نمونے اسکیما کی تعریف کے لیے Zod اور ماڈل کالز کے لیے Anthropic SDK کا استعمال کرتے ہیں، لیکن یہاں کی تمام تکنیکیں براہ راست دوسری توثیق لائبریریوں اور دیگر فراہم کنندگان میں ترجمہ کرتی ہیں۔ آئیڈیاز مخصوص پیکجوں سے زیادہ اہم ہیں۔

کیوں JSON کی درخواست کرنا کافی نہیں ہے۔

جب آپ کو سٹرکچرڈ ڈیٹا کی ضرورت ہوتی ہے، تو آپ کی پہلی جبلت شائستگی سے پوچھنا ہے۔ اگر آپ کچھ لکھتے ہیں جیسے "درست JSON کے ساتھ جواب دیں جو اس شکل سے میل کھاتا ہے اور اس میں کوئی دوسرا متن نہیں ہے” اور مثال کو پیسٹ کریں تو یہ کام کرے گا۔ ہم ترقی کے ذریعے کام جاری رکھے ہوئے ہیں۔ یہ ڈیمو میں کام کرتا ہے۔

مسئلہ یہ ہے کہ لینگویج ماڈل امکان کی بنیاد پر ایک وقت میں ایک ٹوکن تیار کرتا ہے، اور صارف کی ہدایات بہت سے اثرات میں سے صرف ایک ہیں۔ یہ ماڈل کی تربیت، ان پٹ دستاویز کی ظاہری شکل، اور ماڈل نے پچھلے سینکڑوں ٹوکنز سے جو کچھ بھی تیار کیا ہے اس کا مقابلہ کرتا ہے۔ زیادہ تر معاملات میں، آپ کی ہدایات جیت جائیں گی۔ کبھی کبھی ایسا نہیں ہوتا۔

یہاں ایک ناکامی ہے جسے میں نے اصل میں ایک ماڈل سے پروڈکشن نکالنے والی پائپ لائن سے جمع کیا ہے جو واضح طور پر سخت JSON کو واپس کرنے کے لیے بیان کیا گیا ہے:

  • ماڈل نے دو بار ایسا نہ کرنے کی ہدایت کے باوجود اپنے جوابات کو مارک ڈاون کوڈ کی باڑ میں لپیٹ لیا۔

  • واپس آیا "2019 - Present" الگ سے متعین اسکیما کے ساتھ ایک واحد تار کے طور پر startDate اور endDate بیٹری

  • یہ ایجاد کیا گیا تھا endDate کی "2024-12-31" کسی کردار کے لیے دستاویز کو واضح طور پر موجودہ کے طور پر نشان زد کیا گیا ہے۔

  • ایک تار لوٹایا۔ "null" اصل کے بجائے null.

  • میں نے ان کرداروں کے لیے 7 گولیاں دکھائی ہیں جہاں اسکیما زیادہ سے زیادہ 5 سیٹ کرتا ہے۔

  • انٹرمیڈیٹ آبجیکٹ کو چھوٹا کر دیا گیا کیونکہ جواب آؤٹ پٹ ٹوکن کی حد تک پہنچ گیا۔

براہ کرم نوٹ کریں کہ یہ سب ایک ہی قسم کی ناکامی نہیں ہیں۔ کوڈ کی باڑ اور تراشنا مصنوعی مسائل ہیں اور اکثر ماڈل کو دوبارہ کال کیے بغیر مقامی طور پر حل کیا جا سکتا ہے۔ ضم شدہ تاریخ کے تار اور اضافی گولیاں ایک اسکیما مسئلہ ہیں جہاں JSON اچھی طرح سے تجزیہ کرتا ہے، لیکن اس سے میل نہیں کھاتا جس کی مجھے اس کی طرح نظر آنے کی ضرورت ہے۔ ایجاد شدہ اختتامی تاریخ ایک معنوی مسئلہ ہے جہاں آؤٹ پٹ درست JSON اور اسکیما کے مطابق ہے، لیکن اصل میں ماخذ دستاویز کے لیے غلط ہے۔

ان تینوں زمروں کو تین مختلف جوابات کی ضرورت ہوتی ہے، اور ان کو ضم کرنا سب سے عام آرکیٹیکچرل غلطی ہے جسے ہم نکالے گئے کوڈ میں دیکھتے ہیں۔ اس مضمون کا باقی حصہ بنیادی طور پر ان کو الگ کرنے کے بارے میں ہے۔

پیداوار کو محدود کرنے کے تین طریقے

توثیق کوڈ لکھنے سے پہلے، یہ سمجھنا اچھا خیال ہے کہ API خود آپ کے صارفین پر کیا لاگو ہوتا ہے۔ تین میکانزم ہیں اور وہ معنی خیز طور پر مختلف ضمانتیں فراہم کرتے ہیں۔

JSON موڈ تینوں میں سب سے کمزور۔ درج ذیل جھنڈے مرتب کریں: response_format: { type: "json_object" }فراہم کنندہ اس بات کی ضمانت دیتا ہے کہ جواب مصنوعی طور پر درست JSON ہے۔ یہ واقعی مفید ہے کیونکہ یہ کوڈ کی باڑ اور تراشنے کے مسائل کو ایک ہی بار میں ختم کرتا ہے۔ یہ جو نہیں کرتا ہے وہ شکل کے بارے میں کسی چیز کی ضمانت دیتا ہے۔ آپ کو غلط کلیدوں، غلط قسم، یا آپ کی درخواست سے بالکل مختلف ساخت کے ساتھ درست JSON مل سکتا ہے۔

ٹول کال یہ وہ طریقہ کار ہے جس کا مجھے اکثر سامنا ہوتا ہے۔ پیرامیٹرز کے لیے JSON اسکیما کا استعمال کرتے ہوئے فنکشن کی وضاحت کریں اور پھر ماڈل کو اسے کال کرنے پر مجبور کریں۔ ماڈل دلائل واپس کرتا ہے جو مفت متن کے بجائے اس کے اسکیما سے ملتا ہے۔ سپورٹ وسیع ہے، اسکیماس درخواست کے ساتھ منتقل ہوتے ہیں تاکہ اشارے میں کوئی نقل نہ ہو، اور فراہم کنندگان سادہ JSON وضع کی نسبت زیادہ جارحانہ انداز میں آؤٹ پٹ کو محدود کرتے ہیں۔

انتھروپک SDK کی طرح دکھتا ہے یہ ہے:

const response = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 4096,
  tools: [
    {
      name: "emit_resume",
      description: "Return the parsed resume as structured data.",
      input_schema: jsonSchema,
    },
  ],
  tool_choice: { type: "tool", name: "emit_resume" },
  messages: [{ role: "user", content: resumeText }],
});

const block = response.content.find((b) => b.type === "tool_use");
const candidate = block?.input;

کہ tool_choice میدان ایک اہم حصہ ہے۔ اس کے بغیر، ماڈل فیصلہ کرتا ہے کہ آیا ٹول کو استعمال کرنا ہے یا نہیں، بعض اوقات اس کے بجائے نثر میں جواب دیتا ہے۔ کچھ ٹولز کو مجبور کرنے کے نتیجے میں اس شاخ کو مکمل طور پر ہٹا دیا جائے گا۔

محدود ضابطہ کشائی اگرچہ یہ سب سے طاقتور آپشن ہے، لیکن یہ سب سے زیادہ دستیاب آپشن نہیں ہے۔ ماڈل سے اسکیما کی پیروی کرنے کے لیے کہنے کے بجائے، رن ٹائم ٹوکن سیمپلر کو ہر قدم پر ماسک کرتا ہے تاکہ یہ یقینی بنایا جا سکے کہ غلط آؤٹ پٹ پیدا کرنے والے ٹوکنز کو بالکل بھی منتخب نہیں کیا گیا ہے۔ غلط آؤٹ پٹ کا مطلب یہ نہیں ہے کہ یہ واقع نہیں ہوگا، بلکہ یہ ناممکن ہو جاتا ہے۔

OpenAI اس ورژن کو سخت ساختہ آؤٹ پٹ کے ذریعے ظاہر کرتا ہے، اور اگر آپ اپنے ماڈل کو مقامی طور پر چلاتے ہیں، تو آپ لائبریری جیسے llama.cpp یا Outline سے GBNF گرامر استعمال کر سکتے ہیں۔

منفی پہلو یہ ہے کہ محدود ضابطہ کشائی ماڈل کو عجیب کونوں میں دھکیل سکتی ہے۔ اگر اسکیما کو کسی ایسے فیلڈ کی ضرورت ہے جو حقیقت میں دستاویز میں شامل نہیں ہے، تو ماڈل نہیں کہہ سکتا، لہذا یہ سلاٹ کو کسی چیز سے بھر دیتا ہے۔ ہم نے تجزیہ کرنے والی ناکامیوں کو کھوج لگانے میں مشکل فریب میں بدل دیا۔ یہی وجہ ہے کہ ہم نے مطلوبہ فیلڈز کو کالعدم قرار دیا ہے۔ ہم جلد ہی اس پر واپس آئیں گے۔

میرا پہلے سے طے شدہ ٹول کو انتہائی کالعدم اسکیما کے ساتھ کال کرنا اور سب سے اوپر توثیق شامل کرنا ہے۔ یہ مجموعہ بغیر کسی پریشانی کے زیادہ تر فوائد فراہم کرتا ہے۔

اسکیما سے شروع کریں، پرامپٹ سے نہیں۔

اگر آؤٹ پٹ کوالٹی ناقص ہے تو، میری جبلت ایک لمبا پرامپٹ لکھنا ہے۔ مزید مثالیں، زیادہ زور، زیادہ کیپٹلائزیشن۔ چونکہ اشارے نثر ہیں اور نثر ناقابل نفاذ ہے، اس لیے اس سے تھوڑی مدد ملتی ہے، لیکن اسے کم توسیعی بناتا ہے۔

ایک بہتر نقطہ نظر یہ ہے کہ اسکیما کو ایک بنیادی نمونے کے طور پر سمجھا جائے اور باقی سب کچھ اس سے حاصل کیا جائے۔ اس کی ایک بار وضاحت کریں اور TypeScript کی قسمیں، API کو بھیجی گئی JSON اسکیما، اور رن ٹائم توثیق کار بنانے کے لیے اس واحد تعریف کا استعمال کریں۔ جب شکل بدل جاتی ہے، تو تینوں ایک ساتھ بدل جاتے ہیں، اور کوئی راستہ نہیں ہوتا کہ وہ الگ ہو جائیں۔

زوڈ کا استعمال:

import { z } from "zod";

const YearMonth = z
  .string()
  .regex(/^\d{4}-\d{2}$/, "Expected a YYYY-MM date");

const Role = z.object({
  company: z.string().min(1),
  title: z.string().min(1),
  startDate: YearMonth,
  endDate: YearMonth.nullable(),
  bullets: z.array(z.string().min(1)).min(1).max(5),
});

const Resume = z.object({
  name: z.string().min(1),
  email: z.string().email().nullable(),
  roles: z.array(Role),
});

export type Resume = z.infer;

وہ تین تفصیلات اصل کام کر رہی ہیں۔

کہ YearMonth باقاعدہ اظہار اس سے کم ہے: z.string()وہ تنگی کلید ہے۔ ایک ننگی سٹرنگ فیلڈ ماڈل کو واپس آنے کی دعوت دیتی ہے۔ "January 2019" یا "2019 - Present" یا "01/2019"اور وہ سب توثیق پاس کرتے ہیں۔ اسکیما کی سطح پر فارمیٹ کو محدود کرنے کا مطلب یہ ہے کہ آپ کے ڈیٹ پروسیسنگ کوڈ میں غیر تین پرتوں والی تضادات فوری طور پر سامنے آئیں گی۔

کہ endDate فیلڈ اختیاری نہیں ہے اور کالعدم ہے۔ یہ واحد تبدیلی ہے جس نے شراب کے معیار کو سب سے زیادہ بہتر کیا۔ اختیاری فیلڈز کا استعمال ماڈل کو خاموشی سے انہیں چھوڑنے کی اجازت دیتا ہے اور "دستاویز نے یہ نہیں کہا” اور "ماڈل بھول گیا” کے درمیان فرق نہیں کر سکتا۔ کالعدم فیلڈز واضح فیصلے پر مجبور کرتی ہیں۔ null یہ ایک معنی خیز جواب ہے جس کا مطلب ہے کہ کردار فی الحال جاری ہے۔

کہ max(5) گولیوں پر بعد میں صف کو تراشنے کے بجائے براہ راست معاہدے میں مصنوعات کی رکاوٹوں کو انکوڈ کریں۔ اگر آپ کا ماڈل اس سے زیادہ ہے تو میں جاننا چاہوں گا کیونکہ اس کا عام طور پر مطلب یہ ہے کہ ماڈل نکالنے کے بجائے پیڈنگ کر رہا ہے۔

API کو اسکیما بھیجنے کے لیے، اسی تعریف سے JSON اسکیما اخذ کریں۔

import { zodToJsonSchema } from "zod-to-json-schema";

const jsonSchema = zodToJsonSchema(Resume, { target: "openApi3" });

اپنانے کے قابل ایک عادت: لکھنا۔ .describe() ان تمام شعبوں میں جہاں صرف نام ہی مبہم ہے۔ یہ وضاحتیں JSON اسکیما میں ختم ہوتی ہیں۔ یعنی ٹول ڈیفینیشن کے حصے کے طور پر ماڈل تک پہنچا ہے۔ اس کا مطلب یہ ہے کہ یہ ٹارگٹڈ، سٹرکچرڈ پرامپٹ ہدایات کے ساتھ کام کرتا ہے جو متعلقہ فیلڈز سے قطعی طور پر منسلک ہیں۔

const Role = z.object({
  company: z.string().min(1),
  title: z.string().min(1).describe("The person's job title, not the team name"),
  startDate: YearMonth,
  endDate: YearMonth.nullable().describe("null if this role is current"),
  bullets: z
    .array(z.string().min(1))
    .min(1)
    .max(5)
    .describe("Verbatim from the document. Do not rewrite or summarise."),
});

آخری وضاحت نے میرے لیے ناکامی کے پورے نمونے کو ختم کر دیا، جہاں ماڈل نے ذاتی بلٹ پوائنٹس کی نمائندگی کو بہتر بنانے میں مدد کی۔

تصدیق دو کام ہیں، ایک نہیں۔

جب جواب واپس آتا ہے، تو یہ چلانے کے لیے پرکشش ہوتا ہے۔ schema.parse() اور سمجھو کہ ہو گیا ہے۔ Zod آپ کو بتائے گا کہ کیا شکل درست ہے، اور اگر ایسا ہے تو جاری رکھیں۔

تاہم، مورفولوجیکل تصدیق اور معنوی تصدیق مختلف کام ہیں، اور صرف پہلا مفت ہے۔ زود مجھے بتا سکتا ہے۔ endDate ایک سٹرنگ میچ ہے YYYY-MM. مجھے نہیں معلوم کہ اختتامی تاریخ تاریخ آغاز سے پہلے ہے، اگر تاریخ مستقبل میں ہے، یا موجودہ کے طور پر درج کردار کی بھی آخری تاریخ ہے۔ وہ سب اسکیما کے لیے درست ہیں اور وہ سب غلط ہیں۔

تو دو پاس کریں۔ پہلا ساختی ہے اور اسکیموں سے آتا ہے۔ دوسرا ایک عام فنکشن ہے جو ڈومین کے بارے میں جو کچھ ہم جانتے ہیں اسے انکوڈ کرتا ہے۔

function findSemanticProblems(resume: Resume): string[] {
  const problems: string[] = [];
  const currentMonth = new Date().toISOString().slice(0, 7);

  for (const role of resume.roles) {
    if (role.startDate > currentMonth) {
      problems.push(`${role.company}: start date is in the future`);
    }
    if (role.endDate && role.endDate < role.startDate) {
      problems.push(`${role.company}: end date precedes start date`);
    }
  }

  const currentRoles = resume.roles.filter((r) => r.endDate === null);
  if (currentRoles.length > 1) {
    problems.push("More than one role is marked as current");
  }

  return problems;
}

اس میں سے کوئی بھی ہوشیار کوڈ نہیں ہے۔ بلکہ، یہ نقطہ ہے. یہ عام کاروباری منطق ہے کہ صارف کے اعمال کے بجائے ماڈل کے اعمال کو چیک کیا جائے۔ ناکامیوں کو جیسے ہی آپ دریافت کرتے ہیں ان کو لکھیں، اور ہر نئے چیک کو آپ کے ماڈل کی غلطیوں کے لیے مستقل ریگریشن ٹیسٹ کے طور پر پیش کریں۔

یہاں ایک مفید توازن موجود ہے۔ معنوی مسائل اکثر صارفین کو دوبارہ کوشش کرنے سے زیادہ نظر آتے ہیں، کیونکہ ماڈل دستیاب معلومات کے ساتھ اکثر بہتر نتائج حاصل کرنے سے قاصر رہتا ہے۔ اگر دستاویزات میں درحقیقت دو موجودہ کرداروں کی فہرست ہے، تو دستاویزات یہی کہتی ہیں اور اگر آپ دوبارہ ماڈل کی درخواست کرتے ہیں تو یہ تبدیل نہیں ہوگا۔

دوبارہ کوشش کرنے کا ایک لوپ بنائیں جو ٹوکنز کو نہ جلائے۔

اگر توثیق ناکام ہو جاتی ہے، تو ایک سادہ حل یہ ہے کہ ماڈل کو دوبارہ اسی پرامپٹ کے ساتھ کال کریں اور دوسرے نمونے کی امید کریں۔ یہ معقول محسوس کرنے کے لیے کافی موثر ہے، کیونکہ آپ پہلے ہی زیادہ تر کامیاب درخواستوں کے لیے پوری لاگت ادا کر رہے ہیں، اور اپنے بل کو ظاہر کرنے کے لیے کافی فضول خرچی ہے۔

دو تبدیلیوں سے بڑا فرق پڑتا ہے۔

پہلا یہ ہے کہ کوئی بھی رقم خرچ کرنے سے پہلے مقامی مرمت کی کوشش کریں۔ ناکامی کا اہم حصہ کاسمیٹک ہے اور اسے سٹرنگ پروسیسنگ کے ذریعے حل کیا جا سکتا ہے۔

function salvage(raw: string): string {
  let text = raw.trim();

  // Strip a Markdown fence the model added despite instructions.
  const fenced = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
  if (fenced) text = fenced[1];

  // Drop any prose before the first brace or after the last one.
  const start = text.indexOf("{");
  const end = text.lastIndexOf("}");
  if (start !== -1 && end > start) text = text.slice(start, end + 1);

  return text;
}

دوسرا یہ کہ ماڈل کو یاد کرتے وقت، آپ کو ایک نئی کوشش کے بجائے مرمت کی درخواست بھیجنی چاہیے۔ اصل پرامپٹ، ناکام آؤٹ پٹ، اور مخصوص توثیق کی غلطیاں شامل ہیں۔ ماڈل نے پہلے ہی مشکل نکالنے کا عمل مکمل کر لیا ہے اور آپ سے شروع کرنے کے بجائے چند نامی مسائل کو حل کرنے کے لیے کہہ رہا ہے۔ مرمت تیزی سے ہوتی ہے اور کم پیداوار پیدا کرتی ہے، اس طرح اخراجات کم ہوتے ہیں۔

async function requestRepair(
  originalPrompt: string,
  badOutput: string,
  error: z.ZodError,
): Promise {
  const issues = error.issues
    .map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`)
    .join("\n");

  const response = await client.messages.create({
    model: "claude-sonnet-5",
    max_tokens: 4096,
    messages: [
      { role: "user", content: originalPrompt },
      { role: "assistant", content: badOutput },
      {
        role: "user",
        content:
          `That output failed validation with these problems:\n${issues}\n\n` +
          `Return the corrected JSON only. Keep everything that was already correct.`,
      },
    ],
  });

  return response.content[0].type === "text" ? response.content[0].text : "";
}

ایک ساتھ لے کر، پورا لوپ ناکامی کی کلاسوں کو الگ کرتا ہے اور اخراجات کو محدود کرتا ہے۔

type Outcome =
  | { ok: true; value: T; attempts: number }
  | { ok: false; error: string; attempts: number };

async function extract(
  schema: z.ZodType,
  prompt: string,
  maxAttempts = 3,
): Promise> {
  let lastRaw = "";
  let lastError: z.ZodError | null = null;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    lastRaw =
      attempt === 1
        ? await callModel(prompt)
        : await requestRepair(prompt, lastRaw, lastError!);

    let candidate: unknown;
    try {
      candidate = JSON.parse(salvage(lastRaw));
    } catch {
      continue; // Syntax failure. Try again without a schema error to report.
    }

    const result = schema.safeParse(candidate);
    if (result.success) {
      return { ok: true, value: result.data, attempts: attempt };
    }
    lastError = result.error;
  }

  return {
    ok: false,
    error: lastError?.message ?? "Output was never parseable",
    attempts: maxAttempts,
  };
}

اس لوپ کے بارے میں دو چیزیں جان بوجھ کر ہیں۔ یہ ان کوششوں کی تعداد لوٹاتا ہے جو لاگ ان کی جانی چاہئیں کیونکہ ہر کامیابی کی کوششیں نکالنے والی پائپ لائن کے لیے واحد سب سے مفید صحت کا اشارہ ہے۔ اور کوششوں کی تعداد 3 تک محدود ہے۔ اگر تین کوششیں درست نتائج نہیں دیتی ہیں، تو چوتھی کوشش بہت کم مددگار ثابت ہوگی، اور اس وقت تک ادائیگی کرتے رہنے سے بہتر ہے کہ کارکردگی کو کم کرنا بہتر ہے۔

احترام کے قابل ایک اور فرق: شرح کو محدود کرنے والی غلطیاں اور اسکیما کی توثیق کی غلطیاں ایک جیسی نہیں ہیں اور دوبارہ کوشش کرنے کی پالیسی کا اشتراک نہیں کرنا چاہیے۔ چونکہ مسئلہ وقت کا ہے، شرح کو محدود کرنے کے لیے ایکسپونینشل بیک آف کی ضرورت ہے۔ اسکیما کی خرابیوں کے لیے فوری بحالی کی درخواستیں درکار ہوتی ہیں۔ مسئلہ صرف مواد کا ہے، اور انتظار کرنے کے باوجود کچھ نہیں بدلے گا۔

اسٹریمنگ اسٹرکچرڈ آؤٹ پٹ

سٹریمنگ اور سٹرکچرڈ آؤٹ پٹ ایک دوسرے کو اپنی طرف متوجہ کرتے ہیں۔ سٹریمنگ موجود ہے تاکہ صارف جواب مکمل ہونے سے پہلے پیش رفت دیکھ سکے، لیکن JSON آبجیکٹ کو اس وقت تک پارس نہیں کیا جا سکتا جب تک کہ اختتامی تسمہ نہ آجائے۔ اگر نکالنے میں 12 سیکنڈ لگتے ہیں، تو بہترین آپشن یہ ہے کہ یا تو 12 سیکنڈ کے لیے اسپنر دکھائیں یا معنی خیز مواد کو اسٹریم کرنے کا راستہ تلاش کریں۔

دو معقول طریقے ہیں:

پہلا جزوی JSON پارسر ہے جو ایک نامکمل سٹرنگ لیتا ہے، سب سے بڑا درست ڈھانچہ لوٹاتا ہے جس کا وہ اندازہ لگا سکتا ہے، افتتاحی تسمہ بند کر دیتا ہے، اور اس کے بعد آنے والی کسی بھی نامکمل اقدار کو رد کر دیتا ہے۔ لائبریریاں جیسے best-effort-json-parser do this یہ کام کرتا ہے، اور صحیح انتخاب ہے اگر آؤٹ پٹ دراصل ایک بڑی چیز ہے۔ لاگت یہ ہے کہ درمیانی ریاستیں گمراہ کن ہوسکتی ہیں کیونکہ فیلڈز کٹی ہوئی اقدار کے طور پر ظاہر ہوسکتی ہیں جو مکمل دکھائی دیتی ہیں۔

دوسرا، ترجیحی نقطہ نظر جب آؤٹ پٹ لسٹ ہو تو آؤٹ پٹ فارمیٹ کو تبدیل کرنا ہے تاکہ اسٹریمنگ فطری ہو۔ واحد اشیاء کی ایک صف کی درخواست کرنے کے بجائے، فی لائن ایک آبجیکٹ کی درخواست کریں۔ ہر لائن کو آزادانہ طور پر پارس کیا جا سکتا ہے، اس لیے آئٹمز کی توثیق کی جا سکتی ہے اور ان کے مکمل ہونے پر برآمد کیا جا سکتا ہے۔

const stream = client.messages.stream({
  model: "claude-sonnet-5",
  max_tokens: 4096,
  messages: [{ role: "user", content: prompt }],
});

let buffer = "";

for await (const event of stream) {
  if (event.type !== "content_block_delta") continue;
  buffer += event.delta.text ?? "";

  const lines = buffer.split("\n");
  buffer = lines.pop() ?? ""; // Keep the incomplete tail for the next chunk.

  for (const line of lines) {
    if (!line.trim()) continue;

    try {
      const parsed = Role.safeParse(JSON.parse(line));
      if (parsed.success) onRole(parsed.data);
    } catch {
      // A malformed line is dropped rather than failing the whole stream.
    }
  }
}

بفر ہینڈلنگ وہ جگہ ہے جہاں لوگ اسے غلط سمجھتے ہیں۔ نیٹ ورک کے ٹکڑوں کو لائن باؤنڈری کے ساتھ منسلک نہیں کیا جاتا ہے، لہذا ٹکڑوں کا اختتام اکثر کسی چیز کے بیچ میں ہوتا ہے۔ آخری عنصر کو واپس بفر میں ڈالنا اور اسے آگے منتقل کرنا لوپ کو صحیح بناتا ہے۔

یہ نمونہ مفت میں آئٹم کی مخصوص توثیق فراہم کرتا ہے، جو ایک بڑی چیز کو پارس کرنے پر حقیقی فائدہ ہے۔ ایک غلط اندراج باقی اقتباس کو باطل نہیں کرتا۔

ناقابل دوبارہ کوشش کی ناکامی۔

اب تک کی ہر چیز یہ مانتی ہے کہ اگر صارف صحیح سوالات پوچھتا ہے، تو ماڈل صحیح جوابات دے سکتا ہے۔ کچھ ناکامیاں اس طرح کام نہیں کرتی ہیں، اور دوبارہ کوشش کرنے والے امیدواروں کے طور پر ان کے ساتھ برتاؤ کرنے سے یقینی طور پر خراب ڈیٹا تیار کرنے کے دوران پیسہ ضائع ہوگا۔

ان میں سب سے اہم چیز اس سے نکالنا ہے جو ماخذ میں شامل نہیں ہے۔ اگر آپ کے ریزیومے میں درحقیقت ای میل ایڈریس نہیں ہے اور آپ کا اسکیما اس کی توقع رکھتا ہے، تو ماڈل قابل فہم معلومات پیدا کرے گا۔

دوبارہ کوشش کرنے سے شاید مختلف نتائج برآمد ہوں گے۔ یہ ایک ناکامی موڈ ہے جو محدود ضابطہ کشائی کو بہتر کے بجائے بدتر بناتا ہے۔ اس کی وجہ یہ ہے کہ گرائمر سلاٹس میں اچھی طرح سے تشکیل شدہ اقدار کی ضمانت دیتا ہے جو خالی ہونے چاہئیں۔ ترمیم اسکیما کی سطح پر ہے، اس لیے ایکسٹریکٹ اسکیما میں تقریباً تمام فیلڈز کالعدم ہیں۔

ماخذ کا ابہام بھی ایسا ہی معاملہ ہے۔ اگر کسی دستاویز میں دو مختلف جاب ٹائٹلز کے آگے تاریخ کی حد درج ہوتی ہے، تو اس میں کوئی قطعی اخراج نہیں ہوتا، صرف اندازہ لگایا جاتا ہے۔ دوبارہ کوشش کریں اور آپ کو اسی اعتماد کے ساتھ ایک اور اندازہ لگ جائے گا۔ ان حالات کے لیے اسکیما میں ٹرسٹ سگنل اور انٹرفیس میں ایک اور API کال کے بجائے جائزہ لینے کے مرحلے کی ضرورت ہوتی ہے۔

اس کے بعد خودکار کٹائی ہوتی ہے۔ اگر جواب آبجیکٹ انٹرمیڈیٹ آؤٹ پٹ ٹوکن کی حد تک پہنچ جاتا ہے، تو آپ کو مصنوعی طور پر کرپٹ JSON مل جائے گا، اور naive retry loop اسے ایک عارضی پارس ناکامی کے طور پر دیکھے گا، اسی حد کے ساتھ دوبارہ کوشش کریں، اور ہر بار اسی طرح ناکام ہو جائیں۔ روکنے کی وجہ کے جواب کو چیک کریں۔ اگر ٹوکن ختم ہونے کی وجہ سے ماڈل رک جاتا ہے تو، ان پٹ کو تقسیم کیے بغیر حد بڑھانا یا دوبارہ کوشش کرنا دوبارہ ناکام ہونے کی ضمانت ہے۔

عام اصول یہ ہے کہ آپ کی دوبارہ کوشش کی پالیسی سے یہ پوچھنا چاہیے کہ یہ کس قسم کی ناکامی ہے اس سے پہلے کہ یہ فیصلہ کیا جائے کہ آیا کال کو دہرانے سے مدد مل سکتی ہے۔

اصل قیمت

مندرجہ بالا تمام تہوں کی ایک قیمت ہے اور یہ فرض کرنے کے بجائے ماپنے کے قابل ہے۔

ٹریک کرنے کے لیے کلیدی نمبر ہیں: فی کامیاب نکالنے کی کوششوں کی تعداد. ہر درخواست کو نوٹ کریں اور اوسط کے بجائے p95 کو دیکھیں۔ کیونکہ اوسط والے دم چھپاتے ہیں جہاں پیسہ جاتا ہے۔ اگر اسکیما کی تبدیلی معیار کے رجعت کا سبب بنتی ہے، تو یہ نمبر دوسروں سے پہلے منتقل ہو جائے گا۔

زیادہ تر کام کے بوجھ کے مقابلے میں یہاں تیز کیشنگ زیادہ اہم ہے۔ ایکسٹریکٹ پرامپٹس غیر معمولی طور پر کیش فرینڈلی ہوتے ہیں کیونکہ سسٹم پرامپٹ، اسکیما، اور تمام اہم مثالیں تمام درخواستوں میں بائٹ بائٹ ایک جیسی ہوتی ہیں، اور صرف دستاویزات میں تبدیلی ہوتی ہے۔

پائپ لائنوں میں جہاں اسکیمے اور ہدایات ہزاروں ٹوکنز کے ساتھ چلتی ہیں، ان بلاکس کو کیشڈ پریفکس میں منتقل کرنے سے ان پٹ لاگت میں نمایاں کمی واقع ہوتی ہے، اور ہر دوبارہ کوشش پر رقم کی بچت ہوتی ہے کیونکہ مرمت کے بعد وہی سابقہ ​​دوبارہ بھیجا جاتا ہے۔

قابل فہم وجوہات کی بنا پر مرمت کی درخواست کرنا ایک نئی کوشش کرنے سے سستا ہے۔ ان پٹ اب بڑا ہے کیونکہ یہ اصل پرامپٹ، ناکام آؤٹ پٹ، اور غلطیوں کی فہرست بھیجتا ہے۔ تاہم، آؤٹ پٹ نمایاں طور پر کم ہو گیا ہے کیونکہ ماڈل پوری دستاویز کو دوبارہ تخلیق کرنے کے بجائے بہت کم فیلڈز میں ترمیم کرتا ہے۔ آؤٹ پٹ ٹوکن زیادہ تر فراہم کنندگان کے لیے ایک مہنگی سمت ہیں، لہذا لین دین عام طور پر فائدہ مند ہوتے ہیں۔

دو اور چیزیں شروع سے ہی سازگار ہیں۔ یعنی توثیق کی خرابی کے راستوں کی تقسیم اور مقامی ریکوری ہٹ ریٹ جو ہمیں بالکل بتاتے ہیں کہ کون سے سکیما فیلڈز مسئلہ کا باعث ہیں اور ناکامی کی مجموعی شرح سے کہیں زیادہ قابل عمل ہیں۔ salvage() یہ جواب کے ایک اہم حصے کو ٹھیک کرنے کے بارے میں ہے جس میں ایک فوری مسئلہ ہے جسے بار بار ادا کرنے کی بجائے ایک ہی بار میں حل کیا جا سکتا ہے۔

جب آپ کو اس میں سے کسی چیز کی ضرورت نہیں ہے۔

یہ مشین بعض حالات میں اپنی جگہ رکھتی ہے، اور ان حالات سے باہر یہ واقعی حد سے زیادہ ہے۔

اگر ماڈل کا آؤٹ پٹ براہ راست انسانوں کو پہنچایا جاتا ہے جو اسے نثر کے طور پر پڑھیں گے، تو ساختی پیداوار کی بالکل ضرورت نہیں ہے۔ چیٹ انٹرفیس، خلاصے، یا ای میل ڈرافٹ سبھی انسانوں کو توثیق کرنے والے کے طور پر بتاتے ہیں، اور ان کے سامنے اسکیما شامل کرنے سے ماڈل کو بغیر کسی فائدہ کے محدود کر دیا جاتا ہے۔

صرف دستاویز کی اقدار کے بجائے ایک یا دو فیلڈز نکالتے وقت روشنی کے ساتھ اچھی طرح سے ٹارگٹڈ پرامپٹس۔ safeParse بس ایک بار دوبارہ کوشش کریں اور آپ ٹھیک ہو جائیں گے۔ دوبارہ کوشش کرنے والے لوپس، سیمنٹک توثیق کی پرتیں، اور اسٹریمنگ پارسر بڑے پیمانے اور اسکیما کی پیچیدگی کے ذریعہ پیش کردہ مسائل کا جواب ہیں۔

اگر والیوم کم ہے اور ایک انسان بہر حال تمام نتائج کا جائزہ لے رہا ہے، توثیقی پرت صرف اس کام کو نقل کر رہی ہے جو کوئی پہلے سے کر رہا ہے۔ خام آؤٹ پٹ کو سرفیس کریں، جائزہ لینے والوں کو اسے ٹھیک کرنے دیں، پھر اپنا انجینئرنگ کا وقت کہیں اور گزاریں۔

اور اگر آپ اب بھی یہ دریافت کر رہے ہیں کہ فعالیت کیا ہے، تو اسے ابتدائی طور پر تعمیر کرنے کی مخالفت کریں۔ اسکیما تبدیل کرنے کے لیے سب سے مہنگی چیز ہے جب نکالنے کا کوڈ، توثیق کے اصول، اور ذخیرہ شدہ ڈیٹا سبھی اس پر منحصر ہوتے ہیں۔ ڈھیلے طریقے سے پروٹو ٹائپ کریں، معلوم کریں کہ آپ کو اصل میں کن فیلڈز کی ضرورت ہے، اور پھر بعد میں چیزوں کو سخت کریں۔

سب سے محفوظ اصول یہ ہے کہ اسکیما اور کے ساتھ شروع کریں۔ safeParseہر بعد کی پرت کو صرف اس صورت میں شامل کریں جب اصل ناکامی اس کا جواز پیش کرے۔ اس مضمون کی تمام تکنیکیں مخصوص پیداواری واقعات سے آتی ہیں، نہ کہ ڈیزائن دستاویزات سے۔

ختم

کام کرنے والے ڈیمو اور قابل اعتماد خصوصیت کے درمیان فرق تقریبا مکمل طور پر اس پرت پر ہے۔ ماڈل اب مشکل حصہ نہیں ہے، اور اشارے بھی عام طور پر مشکل حصہ نہیں ہیں۔ مشکل حصہ یہ فیصلہ کرنا ہے کہ کیا قبول کرنا ہے، جب آپ کو یہ نہیں ملتا ہے تو اس کا احساس کرنا، اور جب ایسا ہوتا ہے تو سمجھداری سے جواب دینا۔

تین خیالات سب سے زیادہ وزن رکھتے ہیں۔

  1. اسکیما ایک معاہدہ ہے اور باقی سب کچھ اس سے اخذ کیا جاتا ہے۔ ایک تعریف جو اقسام، API سکیما، اور validators تخلیق کرتی ہے اس کا مطلب ہے کہ یہ تینوں کبھی بھی ایک دوسرے سے الگ نہیں ہو سکتے۔

  2. نحو، اسکیما، اور سیمنٹک غلطیاں الگ کریں۔ ایسے لوپس کو دوبارہ آزمائیں جو مختلف وجوہات اور حل کا علاج کرتے ہیں اسی طرح ان مسائل پر پیسہ ضائع کرتے ہیں جنہیں بار بار کال کرنے سے حل نہیں کیا جا سکتا۔

  3. فیلڈ کو اختیاری کے بجائے کالعدم بنائیں۔ فیلڈز کو خاموشی سے چھوڑنے کی اجازت دینے کے بجائے ماڈل کو "یہ موجود نہیں تھا” کہنے پر مجبور کرنا خاموش فریب کی ایک پوری کلاس کو واضح، قابل تصدیق اقدار میں بدل دیتا ہے۔

ان کو درست کریں اور باقی صرف عام انجینئرنگ ہے۔ آپ توثیقی کوڈ لکھ رہے ہیں اور منطق کی دوبارہ کوشش کر رہے ہیں جو ڈویلپرز کئی دہائیوں سے ناقابل اعتماد ان پٹ پر کر رہے ہیں۔ زبان کے ماڈلز صرف ایک نئی قسم کے ناقابل اعتماد ان پٹ ہیں، اور وہ اسی نظم و ضبط کا اچھا جواب دیتے ہیں۔

حوالہ جات

Scroll to Top