# LLM e Reduce

> Obtenha JSON validado de um modelo de linguagem com client.llm e use client.reduce para escolher o melhor de vários resultados, votar, juntar ou mesclar.

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

**`client.llm`** pede a um modelo de linguagem uma **saída estruturada**: você envia um prompt de sistema, uma entrada e um JSON Schema, e recebe de volta um objeto que segue o schema. **`client.reduce`** executa isoladamente a etapa de fan-in do Reduce: escolhe o melhor de vários resultados, conta os resultados, faz uma votação, junta textos ou mescla JSON. É o trabalho que o nó [**Escolher o melhor** (Choose Best)](https://nodaro.ai/docs/nodes/automate/choose-best) faz em um workflow. Os dois custam créditos conforme o nível do modelo.

## Métodos
| Método | O que faz |
| --- | --- |
| [`llm.structured(input)`](#llmstructuredinput) | Obtém um objeto validado de um modelo de linguagem, em uma única requisição |
| [`llm.structuredJob(input)`](#llmstructuredjobinput) | A mesma chamada, como um job que você consulta periodicamente |
| [`reduce.run(input)`](#reduceruninput) | Escolhe, conta, vota, junta ou mescla muitas entradas em uma só |

## client.llm
Saída estruturada: entram seu prompt de sistema e seu JSON Schema, sai um objeto validado. A plataforma escolhe a rota do modelo, força a saída em JSON, valida essa saída com o seu schema e devolve as respostas inválidas ao modelo antes de desistir. A cobrança é feita como `llm-structured`, conforme o nível do modelo.

### llm.structured(input)
Pergunta ao modelo e espera a resposta (`POST /v1/llm/structured`). Uma chamada pode levar vários minutos, mais que o tempo limite padrão de 60 segundos do cliente. Para ela, crie o cliente com um `timeoutMs` maior ou use `structuredJob()`.

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

<TypeTable
type={{
system: { type: 'string', required: true, description: "O prompt de sistema, com até 100.000 caracteres." },
input: { type: 'string', required: true, description: "A mensagem do usuário, de 1 a 100.000 caracteres." },
jsonSchema: { type: 'Record<string, unknown>', required: true, description: "Um JSON Schema cujo type é object, com até 64 KB e 20 níveis de profundidade." },
schemaName: { type: 'string', description: "Um nome para o schema, que o modelo vê, com até 64 caracteres." },
llmModel: { type: 'string', description: "O ID do modelo. Omita-o para usar o padrão." },
reasoningEffort: { type: 'string', description: "none, low, medium, high, xhigh ou max, conforme o modelo. xhigh e max são cobrados um nível acima, com teto em premium." },
maxRetries: { type: 'number', default: '2', description: "Quantas respostas inválidas são devolvidas ao modelo antes de a chamada falhar, de 0 a 3." },
origin: { type: 'string', description: "O nome do seu app, gravado no job. client.jobs.list({ origin }) encontra as suas execuções." },
advancedMode: { type: 'boolean', description: "Apenas modelos Gemini: executa na API do próprio fabricante, onde temperature e maxTokens têm efeito total. É cobrado um nível acima, com teto em premium." },
temperature: { type: 'number', description: "A temperatura de amostragem." },
maxTokens: { type: 'number', description: "O tamanho máximo da resposta, em tokens." },
}}
/>

```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)
A mesma chamada, como um job (`POST /v1/llm/structured/jobs`). Retorna um `jobId` na hora; consulte o job periodicamente com [`client.jobs.getStatus()`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions). Um job também pode criar o rascunho a partir de um vídeo: a plataforma analisa o vídeo primeiro e depois acrescenta a análise à sua entrada.

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

<TypeTable
type={{
'...': { type: 'LlmStructuredInput', description: "Todos os campos de structured()." },
label: { type: 'string', description: "Um título para a execução, com até 120 caracteres, mostrado nas listas de execuções." },
videoUrl: { type: 'string', description: "Um vídeo que serve de base para o rascunho. Ele é analisado primeiro, em um job separado que pertence a você." },
videoAnalysis: { type: '{ llmModel?: string; selectionMode?: "choose" | "combine" }', description: "As opções dessa análise." },
analysisJobId: { type: 'string', description: "Um job seu de análise de vídeo já concluído, que serve de base para o rascunho em vez de analisar o vídeo de novo. Evita uma segunda cobrança de análise. Não pode ser combinado com 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" })
```

Enquanto o job é executado, o `output_data` dele contém `stage`: `analyzing` (nos rascunhos a partir de vídeo) ou `drafting`. Quando o job termina, `output_data` contém `output`, `inputTokens` e `outputTokens`, além de `analysisJobId` e `analysisCredits` no caso de um rascunho a partir de vídeo.

- `analysisJobId` falha com 422 quando o job não é seu, não existe ou não é uma análise de vídeo concluída. Os códigos são `analysis_not_found`, `not_analysis`, `analysis_failed`, `analysis_not_ready` e `invalid_analysis`.
- Em uma plataforma sem esta rota, o método lança `NotFoundError`.
- Uma instância self-hosted que envia as chamadas de modelo de linguagem ao Nodaro Cloud responde `503 provider_unavailable`. Trate isso como um recurso indisponível nessa instância, não como um erro passageiro.

## client.reduce
### reduce.run(input)
Reduz muitas entradas a uma só. Equivale à ferramenta `reduce` do MCP e ao nó [Escolher o melhor](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: "Como reduzir as entradas." },
inputs: { type: 'string[]', required: true, description: "Até 1.000 entradas, como texto ou URLs." },
strategyConfig: { type: 'Record<string, unknown>', default: '{}', description: "As configurações da estratégia. Veja a tabela abaixo." },
workflowId: { type: 'string', description: "Um workflow em cujo histórico de execuções esta execução vai aparecer." },
}}
/>

| Estratégia | `strategyConfig` | O que retorna |
| --- | --- | --- |
| `pick-best-llm` | `{ criteria, inputKind?, llmModel? }`. `inputKind` é `"text"` ou `"image-url"`. `llmModel` escolhe o modelo juiz, e o nível de créditos dele se aplica. | A entrada que um modelo de linguagem julga a melhor, com o índice e o raciocínio |
| `concat` | `{ separator? }`, uma linha em branco por padrão | Todas as entradas unidas em um único texto |
| `first-non-empty` | nenhuma | A primeira entrada que não está vazia |
| `count` | nenhuma | O número de entradas |
| `vote` | `{ caseSensitive? }`, `false` por padrão | A entrada mais frequente. Em caso de empate, vence a primeira. |
| `merge-json` | `{ strategy? }`: `"deep"` (o padrão) ou `"shallow"` | As entradas JSON mescladas em um único objeto |

```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
```

O resultado é `{ jobId, output, meta }`. `output` é o valor escolhido ou combinado, como string. `meta.summary` sempre vem preenchido. `pick-best-llm` e `vote` preenchem `meta.selectedIndex`, e `pick-best-llm` também preenche `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 } }
```

Quando todas as entradas estão vazias ou têm só espaços em branco, a chamada falha com um `NodaroError` de status 400 e `code` igual a `no_valid_inputs`. Os créditos são reservados como em qualquer geração, então um saldo insuficiente lança `InsufficientCreditsError`.

## Frequently asked questions

### Como obter de um modelo de linguagem um JSON que siga o meu schema?

Chame client.llm.structured com um prompt de sistema, uma entrada e um objeto JSON Schema. A plataforma obriga o modelo a responder nesse formato, valida a resposta, tenta de novo quando ela é inválida e retorna o JSON já convertido em objeto.

### Quando usar structuredJob em vez de structured?

Use structuredJob para chamadas longas. structured espera a resposta em uma única requisição, o que pode levar minutos, mais que o tempo limite padrão de 60 segundos. structuredJob retorna um jobId na hora.

### Como escolher a melhor entre várias imagens geradas?

Chame client.reduce.run com strategyId pick-best-llm, seus critérios e inputKind image-url, e passe as URLs das imagens como inputs. O resultado indica a URL escolhida, o índice dela e o raciocínio do modelo.

### Quais estratégias do Reduce não precisam de um modelo de linguagem?

concat, first-non-empty, count, vote e merge-json são executadas sem modelo. Apenas pick-best-llm pede a um modelo de linguagem que faça o julgamento.
