# LLM ובחירת המיטב

> מקבלים ממודל שפה JSON תקין עם client.llm, ועם client.reduce בוחרים את הטובה מבין הרבה תוצאות, עורכים ביניהן הצבעה, מאחדים או ממזגים אותן.

Source: https://nodaro.ai/he/docs/developers/sdk/llm-and-reduce

**`client.llm`** מבקש ממודל שפה **פלט מובנה**: שולחים פרומפט מערכת, קלט ו-JSON Schema, ומקבלים בחזרה אובייקט שתואם לסכמה. המשאב **`client.reduce`** מריץ בנפרד את שלב ה-Reduce, שבו הרבה תוצאות מתכנסות לתוצאה אחת: הוא בוחר את הטובה ביותר מביניהן, סופר אותן, עורך ביניהן הצבעה, מאחד טקסטים או ממזג JSON — מה שרכיב [בחירת המיטב](https://nodaro.ai/docs/nodes/automate/choose-best) עושה בתהליך. קריאה למודל שפה בכל אחד מהשניים מחויבת לפי הטוקנים שהיא משתמשת בהם, בתעריף של המודל. ראו את הסעיף [איך מחויבות קריאות LLM](https://nodaro.ai/docs/concepts/credits#llm-calls) בדף קרדיטים.

## מתודות
| מתודה | מה היא עושה |
| --- | --- |
| [`llm.structured(input)`](#llmstructuredinput) | קבלת אובייקט תקין ממודל שפה, במסגרת בקשה אחת |
| [`llm.structuredJob(input)`](#llmstructuredjobinput) | אותה קריאה, כמשימה שבודקים את הסטטוס שלה מדי כמה שניות |
| [`reduce.run(input)`](#reduceruninput) | בחירה, ספירה, הצבעה, איחוד או מיזוג של הרבה קלטים לתוצאה אחת |

## client.llm
פלט מובנה: שולחים את פרומפט המערכת ואת ה-JSON Schema שלכם, ומקבלים אובייקט שנבדק מול הסכמה. הפלטפורמה בוחרת את השירות שמריץ את המודל, כופה פלט JSON, בודקת אותו מול הסכמה שלכם ושולחת למודל בחזרה תשובות לא תקינות לפני שהיא מוותרת. הקריאה מחויבת בתור `llm-structured`, לפי הטוקנים שהיא השתמשה בהם, בתעריף של המודל בשירות שטיפל בה. קודם משוריינים קרדיטים, וכשהיתרה נמוכה מהשריון, המתודה זורקת `InsufficientCreditsError`. אחר כך הקריאה מחויבת לפי השימוש בפועל. על כשל שהספק חייב עליו משלמים לפי השימוש הזה. המודל Claude Fable 5 זמין. לאומדן מחיר של קריאה טיפוסית, העבירו את `llmPriceKey("llm-structured", model)` ל-[`credits.modelCosts()`](https://nodaro.ai/docs/developers/sdk/models-and-credits#creditsmodelcostsids).

### llm.structured(input)‎
פונה למודל ומחכה לתשובה (`POST /v1/llm/structured`). קריאה יכולה להימשך כמה דקות, יותר מפסק הזמן של 60 שניות שהלקוח מגדיר כברירת מחדל. בשביל קריאה כזו, צרו את הלקוח עם ערך `timeoutMs` גדול יותר, או השתמשו ב-`structuredJob()`.

```ts
structured<T>(input: LlmStructuredInput): Promise<{
jobId: string
output: T
usage: { inputTokens: number; outputTokens: number }
}>
```

<TypeTable
type={{
system: { type: 'string', required: true, description: "פרומפט המערכת, עד 100,000 תווים." },
input: { type: 'string', required: true, description: "הודעת המשתמש, מ-1 עד 100,000 תווים." },
jsonSchema: { type: 'Record<string, unknown>', required: true, description: "JSON Schema שהסוג שלו הוא object, בגודל של עד 64 KB ובעומק של עד 20 רמות." },
schemaName: { type: 'string', description: "שם לסכמה שהמודל רואה, עד 64 תווים." },
llmModel: { type: 'string', description: "מזהה המודל. השמיטו אותו כדי להשתמש במודל ברירת המחדל." },
reasoningEffort: { type: 'string', description: "none, low, medium, high, xhigh או max, לפי המודל. לרמת המאמץ אין מחיר משלה: רמה גבוהה יותר עולה יותר רק בגלל הטוקנים שהמודל כותב. במודל Claude, כל רמת מאמץ מריצה את הקריאה ב-API של היצרן עצמו, בתעריף של אותו שירות." },
maxRetries: { type: 'number', default: '2', description: "כמה תשובות לא תקינות נשלחות בחזרה למודל לפני שהקריאה נכשלת, מ-0 עד 3." },
origin: { type: 'string', description: "שם האפליקציה שלכם, שנשמר במשימה. עם client.jobs.list({ origin })‎ אפשר למצוא את ההרצות שלכם." },
advancedMode: { type: 'boolean', description: "מודלי Gemini ו-Claude: מריץ את הקריאה ב-API של היצרן עצמו, שבו temperature ו-maxTokens חלים במלואם. הקריאה מחויבת בתעריף של אותו שירות." },
temperature: { type: 'number', description: "טמפרטורת הדגימה." },
maxTokens: { type: 'number', description: "האורך המרבי של התשובה, בטוקנים." },
}}
/>

```ts
type Plan = { title: string; scenes: string[] }

const { output } = await client.llm.structured<Plan>({
system: "You write production plans for short films.",
input: "A rainy chase through Rome, 60 seconds.",
jsonSchema: {
type: "object",
properties: {
title: { type: "string" },
scenes: { type: "array", items: { type: "string" } },
},
required: ["title", "scenes"],
},
schemaName: "production_plan",
})
console.log(output.title, output.scenes.length)
```

### llm.structuredJob(input)‎
אותה קריאה, כמשימה (`POST /v1/llm/structured/jobs`). המתודה מחזירה `jobId` מיד; בדקו את סטטוס המשימה מדי כמה שניות עם [`client.jobs.getStatus()`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions). משימה יכולה גם לבסס את הטיוטה על סרטון: הפלטפורמה מנתחת קודם את הסרטון, ואז מוסיפה את הניתוח לקלט שלכם.

```ts
structuredJob(input: LlmStructuredJobInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
'...': { type: 'LlmStructuredInput', description: "כל השדות של structured()‎." },
label: { type: 'string', description: "כותרת להרצה, עד 120 תווים, שמוצגת ברשימות ההרצות." },
videoUrl: { type: 'string', description: "סרטון שהטיוטה תתבסס עליו. הסרטון מנותח קודם, כמשימה נפרדת ששייכת לכם." },
videoAnalysis: { type: '{ llmModel?: string; selectionMode?: "choose" | "combine" }', description: "אפשרויות לניתוח הזה." },
analysisJobId: { type: 'string', description: "משימת ניתוח וידאו שלכם שהסתיימה, שהטיוטה תתבסס עליה במקום לנתח את הסרטון שוב. כך לא משלמים פעמיים על ניתוח. אי אפשר לשלב את השדה הזה עם videoAnalysis." },
}}
/>

```ts
const { jobId } = await client.llm.structuredJob({
system: "You write production plans.",
input: "A rainy chase through Rome.",
jsonSchema: { type: "object", properties: { title: { type: "string" } }, required: ["title"] },
origin: "my-app",
label: "Rome chase",
})

// later, even from another session
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") {
console.log((data.output_data as { output: { title: string } }).output.title)
}
const { data: runs } = await client.jobs.list({ type: "llm-structured", origin: "my-app" })
```

בזמן שהמשימה רצה, השדה `output_data` שלה כולל את `stage`: `analyzing` (בטיוטות מסרטון) או `drafting`. כשהמשימה מסתיימת, השדה `output_data` כולל את `output`, `inputTokens` ו-`outputTokens`, ובטיוטה מסרטון גם את `analysisJobId` ו-`analysisCredits`.

- בקשה עם `analysisJobId` נדחית עם 422 כשהמשימה לא שייכת לכם, לא קיימת או שהיא לא ניתוח וידאו שהסתיים. הקודים הם `analysis_not_found`, `not_analysis`, `analysis_failed`, `analysis_not_ready` ו-`invalid_analysis`.
- מול פלטפורמה שהנתיב הזה לא קיים בה, המתודה זורקת `NotFoundError`.
- במופע באירוח עצמי שמעביר ל-Nodaro Cloud את הקריאות שלו למודלי שפה, הנתיב מחזיר `503 provider_unavailable`. התייחסו לתשובה הזו כסימן שהיכולת לא זמינה במופע הזה, ולא כשגיאה חולפת.

## client.reduce
### reduce.run(input)‎
הופכת הרבה קלטים לתוצאה אחת. המתודה עובדת כמו כלי ה-MCP `reduce` וכמו רכיב [בחירת המיטב](https://nodaro.ai/docs/nodes/automate/choose-best).

```ts
run(input: ReduceInput): Promise<ReduceResult>
```

<TypeTable
type={{
strategyId: { type: '"pick-best-llm" | "concat" | "first-non-empty" | "count" | "vote" | "merge-json"', required: true, description: "מה לעשות עם הקלטים." },
inputs: { type: 'string[]', required: true, description: "עד 1,000 קלטים, כטקסט או ככתובות URL." },
strategyConfig: { type: 'Record<string, unknown>', default: '{}', description: "ההגדרות של האסטרטגיה. ראו את הטבלה שלמטה." },
workflowId: { type: 'string', description: "תהליך שההרצה הזו תופיע תחתיו, בהיסטוריית ההרצות שלו." },
}}
/>

| אסטרטגיה | `strategyConfig` | מה היא מחזירה |
| --- | --- | --- |
| `pick-best-llm` | `{ criteria, inputKind?, llmModel? }`. הערך של `inputKind` הוא `"text"` או `"image-url"`. השדה `llmModel` בוחר את מודל השופט. הקריאה של השופט מחויבת לפי טוקן, בתעריף של המודל הזה. | הקלט שמודל השפה קובע שהוא הטוב ביותר, עם המיקום שלו ועם הנימוק |
| `concat` | `{ separator? }`, שורה ריקה כברירת מחדל | כל הקלטים, מאוחדים לטקסט אחד |
| `first-non-empty` | אין | הקלט הראשון שלא ריק |
| `count` | אין | מספר הקלטים |
| `vote` | `{ caseSensitive? }`, `false` כברירת מחדל | הקלט שמופיע הכי הרבה פעמים. בתיקו, הראשון מנצח. |
| `merge-json` | `{ strategy? }`: `"deep"` (ברירת המחדל) או `"shallow"` | קלטי ה-JSON, ממוזגים לאובייקט אחד |

```ts
const result = await client.reduce.run({
strategyId: "pick-best-llm",
strategyConfig: { criteria: "The sharpest image with no artifacts", inputKind: "image-url" },
inputs: [url1, url2, url3, url4, url5],
})
console.log(result.output)             // the chosen URL
console.log(result.meta.selectedIndex) // 0 to 4
console.log(result.meta.reasoning)     // why the model chose it
```

התוצאה היא `{ jobId, output, meta }`. השדה `output` הוא הערך שנבחר או שאוחד, כמחרוזת. ב-`meta.summary` תמיד יש ערך. האסטרטגיות `pick-best-llm` ו-`vote` מגדירות את `meta.selectedIndex`, והאסטרטגיה `pick-best-llm` מגדירה גם את `meta.reasoning`.

```ts
// Majority vote
const winner = await client.reduce.run({ strategyId: "vote", inputs: ["red", "blue", "red"] })

// Deep-merge JSON fragments
const merged = await client.reduce.run({
strategyId: "merge-json",
inputs: [JSON.stringify({ a: 1, nested: { x: 1 } }), JSON.stringify({ b: 2, nested: { y: 2 } })],
})
JSON.parse(merged.output) // { a: 1, b: 2, nested: { x: 1, y: 2 } }
```

כשכל הקלטים ריקים או שיש בהם רק תווי רווח, הקריאה נכשלת עם שגיאת `NodaroError` שהסטטוס שלה הוא 400 וה-`code` שלה הוא `no_valid_inputs`. קרדיטים משוריינים כמו בכל יצירה, ולכן כשהיתרה לא מספיקה, המתודה זורקת `InsufficientCreditsError`.

## שאלות נפוצות

### איך מקבלים ממודל שפה JSON שתואם לסכמה שלכם?

קראו למתודה client.llm.structured עם פרומפט מערכת, קלט ואובייקט JSON Schema. הפלטפורמה כופה על המודל לענות במבנה הזה, בודקת את התשובה, מנסה שוב כשהתשובה לא תקינה ומחזירה את האובייקט המפוענח.

### מתי כדאי להשתמש במתודה structuredJob במקום במתודה structured?

השתמשו במתודה structuredJob בקריאות ארוכות. המתודה structured מחכה לתשובה במסגרת בקשה אחת, וזה יכול להימשך דקות, יותר מפסק הזמן של 60 שניות שמוגדר כברירת מחדל. המתודה structuredJob מחזירה jobId מיד.

### איך בוחרים את הטובה מבין כמה תמונות שנוצרו?

קראו למתודה client.reduce.run עם האסטרטגיה pick-best-llm, עם הקריטריונים שלכם ועם הערך image-url בשדה inputKind, והעבירו את כתובות התמונות בשדה inputs. התוצאה מציינת את הכתובת שנבחרה, את המיקום שלה בין הקלטים ואת הנימוק של המודל.

### כמה עולה קריאת LLM עם פלט מובנה?

קריאת LLM עם פלט מובנה מחויבת לפי הטוקנים שהיא השתמשה בהם בפועל, בתעריף של המודל בשירות שטיפל בה. קודם משוריינים קרדיטים, ואחר כך הקריאה מחויבת לפי השימוש בפועל. המתודה client.credits.modelCosts עם המפתח llm-structured:<model>‎ מחזירה אומדן של קריאה טיפוסית.

### אילו אסטרטגיות של Reduce לא צריכות מודל שפה?

האסטרטגיות concat, first-non-empty, count, vote ו-merge-json רצות בלי מודל. רק האסטרטגיה pick-best-llm מבקשת ממודל שפה לשפוט.
