/** * AI zpracovani textu: vytazeni udaju. * * Sluzba: https://api.openai.com/v1 * Endpoint: POST /chat/completions * * Vzor **vystupu, ktery si urcuje uzivatel**. Pole se popisou v kroku stromu * a model je vrati jako objekt. Manifest je predem znat nemuze, proto se vraci * jeden parametr typu `object` a vedle nej priznak, co se opravdu naslo. * * Nenalezeny udaj je `null`, ne vymysleny. Model, ktery hada, je horsi nez * model, ktery rekne "nevim" - vymyslene IC se poznat neda. */ export const manifest = { id: 'ai-text.extract', name: 'Vytáhnout údaje', description: 'Z volného textu získá strukturovaná data podle polí, která popíšete. ' + 'Co v textu není, vrátí prázdné, nedomýšlí si.', inputs: [ { id: 'fields', label: 'Pole k vytažení', type: 'string', required: true, multiline: true, hint: 'Jedno pole na řádek ve tvaru nazev: popis. Například:\n' + 'cisloObjednavky: číslo objednávky, obvykle ve tvaru OBJ-1234\n' + 'castka: celková částka v korunách jako číslo', }, { id: 'text', label: 'Text', type: 'string', required: true, multiline: true, }, ], outputs: [ { id: 'data', label: 'Vytažené údaje', type: 'object', required: true }, { id: 'found', label: 'Kolik polí se našlo', type: 'number', required: true }, { id: 'missing', label: 'Co se nenašlo', type: 'string', required: false }, { id: 'complete', label: 'Našlo se všechno', type: 'boolean', required: true }, ], timeoutMs: 45000, }; export async function run(inputs, ctx) { const { pick, first, text } = ctx.util; const fields = inputs.fields .split('\n') .map((line) => line.trim()) .filter(Boolean) .map((line) => { const at = line.indexOf(':'); return at === -1 ? { name: line, description: '' } : { name: line.slice(0, at).trim(), description: line.slice(at + 1).trim() }; }) .filter((field) => field.name !== ''); if (fields.length === 0) ctx.fail('Nepopsali jste žádné pole, takže není co vytáhnout.'); const wanted = fields.map((f) => `- ${f.name}${f.description ? `: ${f.description}` : ''}`); const system = 'Vytáhni z textu uvedená pole a vrať POUZE platný JSON objekt s přesně těmito klíči:\n' + `${wanted.join('\n')}\n` + 'Co v textu není, vrať jako null. Nic si nedomýšlej a nic nedopočítávej.'; const { body } = await ctx.http.post('/chat/completions', { model: ctx.config.model || 'gpt-4o-mini', messages: [ { role: 'system', content: system }, { role: 'user', content: inputs.text }, ], response_format: { type: 'json_object' }, }); const content = text(pick(pick(first(pick(body, 'choices')), 'message'), 'content')); if (!content) ctx.fail('Model nevrátil odpověď.', body); let parsed; try { parsed = JSON.parse(content); } catch { ctx.fail(`Model nevrátil platný JSON: ${content}`); } // Vysledek se sklada podle **popsanych poli**, ne podle toho, co model poslal. // Kdyby si pridal klic navic, strom by o nem nevedel a jen by mu zabiral misto. const data = {}; const missing = []; for (const field of fields) { const value = parsed[field.name]; data[field.name] = value === undefined ? null : value; if (value === undefined || value === null || value === '') missing.push(field.name); } if (missing.length > 0) ctx.log(`Nenašlo se: ${missing.join(', ')}.`); return { data, found: fields.length - missing.length, missing: missing.length > 0 ? missing.join(', ') : null, complete: missing.length === 0, }; }