Sluzba byla v katalogu bez pristupovych udaju a s appId 'ai-text', tedy mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel co vykonat. - sluzba stoji na api.openai.com/v1 stejne jako OpenAI, overeni GET /models - model se bere z konektoru (ctx.config.model), ne z kazdeho kroku zvlast - skripty ai-text.classify, ai-text.extract, ai-text.generate Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny text. Tady je uloha pevna a vystup taky - category a confidence, objekt s popsanymi poli, text s poctem slov. Na to jde ve strome navazat podminkou, na volny text ne. Zjisteno u toho a zapsane do 21-realne-sluzby.md: proxy vraci na neznamou aplikaci 200 s prazdnym telem, takze overeni konektoru u sluzby bez verifyPath projde i tam, kde nic nebezi. Zatim neopraveno. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
107 lines
3.4 KiB
JavaScript
107 lines
3.4 KiB
JavaScript
/**
|
|
* AI zpracovani textu: zarazeni do kategorie.
|
|
*
|
|
* Sluzba: https://api.openai.com/v1 (nebezi u nas, adresa je v katalogu)
|
|
* Endpoint: POST /chat/completions
|
|
*
|
|
* Rozdil proti sluzbe OpenAI: tam se posila volny dotaz a vraci se volny text.
|
|
* Tady je uloha pevna a **vystup taky** - `category` a `confidence`. Prave na
|
|
* to se da ve strome navazat podminkou, coz u volneho textu nejde.
|
|
*
|
|
* Model se bere z konektoru (`ctx.config.model`), aby se nemusel vyplnovat
|
|
* u kazdeho kroku znovu.
|
|
*/
|
|
|
|
export const manifest = {
|
|
id: 'ai-text.classify',
|
|
name: 'Zařadit do kategorie',
|
|
description:
|
|
'Rozhodne, do které z uvedených kategorií text patří. Vrací kategorii ' +
|
|
'a jistotu, takže se za krokem dá větvit podle toho, jak si je model jistý.',
|
|
|
|
inputs: [
|
|
{
|
|
id: 'categories',
|
|
label: 'Seznam kategorií',
|
|
type: 'string',
|
|
required: true,
|
|
hint: 'Oddělte čárkou, například: porucha, dotaz, reklamace.',
|
|
},
|
|
{
|
|
id: 'text',
|
|
label: 'Text',
|
|
type: 'string',
|
|
required: true,
|
|
multiline: true,
|
|
hint: 'Co se má zařadit. Typicky předmět a tělo ticketu.',
|
|
},
|
|
{
|
|
id: 'instructions',
|
|
label: 'Doplňující pravidla',
|
|
type: 'string',
|
|
required: false,
|
|
multiline: true,
|
|
hint: 'Například: reklamací se rozumí jen stížnost na už dodané zboží.',
|
|
},
|
|
],
|
|
|
|
outputs: [
|
|
{ id: 'category', label: 'Kategorie', type: 'string', required: true },
|
|
{ id: 'confidence', label: 'Jistota', type: 'number', required: true },
|
|
{ id: 'matched', label: 'Kategorie ze seznamu', type: 'boolean', required: true },
|
|
],
|
|
|
|
timeoutMs: 45000,
|
|
};
|
|
|
|
export async function run(inputs, ctx) {
|
|
const { pick, first, text, num, need } = ctx.util;
|
|
|
|
const categories = inputs.categories
|
|
.split(',')
|
|
.map((item) => item.trim())
|
|
.filter(Boolean);
|
|
if (categories.length < 2) ctx.fail('Uveďte aspoň dvě kategorie, jinak není z čeho vybírat.');
|
|
|
|
const system =
|
|
'Jsi klasifikátor. Vrátíš POUZE platný JSON ve tvaru ' +
|
|
'{"category": "...", "confidence": 0.0}. Pole category musí být přesně jedna ' +
|
|
`z těchto hodnot: ${categories.join(', ')}. Confidence je číslo od 0 do 1.` +
|
|
(inputs.instructions ? ` Další pravidla: ${inputs.instructions}` : '');
|
|
|
|
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}`);
|
|
}
|
|
|
|
const category = need(text(pick(parsed, 'category')), 'kategorii');
|
|
|
|
/*
|
|
* Model obcas vrati kategorii, ktera v seznamu neni, i kdyz ji ma zakazanou.
|
|
* Neni to duvod krok shodit - vysledek se vrati a `matched` rekne, ze je
|
|
* mimo seznam. Strom se pak muze rozhodnout, jestli mu verit.
|
|
*/
|
|
const matched = categories.some((item) => item.toLowerCase() === category.toLowerCase());
|
|
if (!matched) ctx.log(`Model vrátil "${category}", což v seznamu není.`);
|
|
|
|
return {
|
|
category,
|
|
confidence: num(pick(parsed, 'confidence'), 0) ?? 0,
|
|
matched,
|
|
};
|
|
}
|