/** * OpenAI: dotaz na jazykovy model. * * Sluzba: https://api.openai.com/v1 (nebezi u nas, adresa je v katalogu sluzeb) * Endpoint: POST /chat/completions * * Model je **volny text s vychozi hodnotou**, ne vyber z pevneho seznamu. * Seznam by zestarl pri kazdem vydani noveho modelu a krok stromu by pak * odmital hodnotu, kterou ucet umi. Co ucet umi, vrati `openai.list-models`. */ export const manifest = { id: 'openai.chat', name: 'Zeptat se modelu', description: 'Pošle otázku vybranému modelu OpenAI a vrátí odpověď včetně spotřeby tokenů. ' + 'Hodí se na shrnutí, klasifikaci i sepsání odpovědi zákazníkovi.', inputs: [ { id: 'model', label: 'Model', type: 'string', required: false, default: 'gpt-4o-mini', hint: 'Například gpt-4o-mini, gpt-4o nebo gpt-4.1. Seznam vrátí operace Načíst seznam modelů.', }, { id: 'instructions', label: 'Instrukce pro model', type: 'string', required: false, multiline: true, hint: 'Role a pravidla, například: Jsi zákaznická podpora, odpovídej česky a stručně.', }, { id: 'prompt', label: 'Otázka', type: 'string', required: true, multiline: true, hint: 'Text dotazu. Může obsahovat data z předchozích kroků.', }, { id: 'temperature', label: 'Teplota', type: 'number', required: false, hint: 'Od 0 do 2. Nižší je předvídatelnější. Nevyplněné se neposílá vůbec, ' + 'protože novější modely teplotu nepřijímají.', }, { id: 'maxTokens', label: 'Strop na délku odpovědi', type: 'number', required: false, hint: 'V tokenech. Bez vyplnění rozhoduje model.', }, { id: 'jsonOutput', label: 'Vyžádat odpověď v JSONu', type: 'boolean', required: false, default: false, hint: 'Model pak vrátí platný JSON. V instrukcích popište, jaká pole má mít.', }, ], outputs: [ { id: 'text', label: 'Odpověď', type: 'string', required: true }, { id: 'model', label: 'Použitý model', type: 'string', required: true }, { id: 'finishReason', label: 'Důvod ukončení', type: 'string', required: false }, { id: 'promptTokens', label: 'Tokeny na vstupu', type: 'number', required: false }, { id: 'completionTokens', label: 'Tokeny na výstupu', type: 'number', required: false }, { id: 'totalTokens', label: 'Tokeny celkem', type: 'number', required: false }, ], // Delsi odpoved trva. Vychozich 15 s je na generovani textu malo. timeoutMs: 60000, }; export async function run(inputs, ctx) { const { pick, text, num, need } = ctx.util; const messages = []; if (inputs.instructions) messages.push({ role: 'system', content: inputs.instructions }); messages.push({ role: 'user', content: inputs.prompt }); const body = { model: inputs.model, messages }; // Nevyplnene se neposila. Novejsi modely `temperature` odmitaji uplne, // takze poslat vychozi hodnotu by krok rozbilo tam, kde o ni nikdo nestal. if (inputs.temperature !== null) body.temperature = inputs.temperature; if (inputs.maxTokens !== null) body.max_completion_tokens = inputs.maxTokens; if (inputs.jsonOutput) body.response_format = { type: 'json_object' }; const { body: answer } = await ctx.http.post('/chat/completions', body); const choice = ctx.util.first(pick(answer, 'choices')); if (!choice) ctx.fail('OpenAI nevrátilo žádnou odpověď.', answer); const usage = pick(answer, 'usage') ?? {}; return { text: need(text(pick(pick(choice, 'message'), 'content')), 'odpověď modelu'), model: need(text(pick(answer, 'model')), 'název modelu'), finishReason: text(pick(choice, 'finish_reason')), promptTokens: num(pick(usage, 'prompt_tokens')), completionTokens: num(pick(usage, 'completion_tokens')), totalTokens: num(pick(usage, 'total_tokens')), }; }