openvibers/openvibe.ai Commits
Add low cost Meme caption workflow (#384)
Changes
nnew file mode 100644@@@ -0,0 +1,20 @@++'use strict';++++const STR = (n) => ({ type: 'string', maxLength: n });++const template = {++ key: 'meme.caption', name: 'Meme: caption ideas', owner: 'meme', visibility: 'internal',++ description: 'Write a few original, short caption ideas for a known meme format.',++ input_schema: { type: 'object', additionalProperties: false, required: ['format', 'topic'], properties: { format: STR(120), topic: STR(300) } },++ output_schema: { type: 'object', additionalProperties: false, required: ['ideas'], properties: { ideas: { type: 'array', minItems: 3, maxItems: 5, items: STR(480) } } },++ system_prompt: 'You write original meme captions. Never quote a known meme, copy a template caption, use hateful slurs, or invent factual claims. Keep each idea short, varied, and safe for a general audience. Return three to five distinct caption ideas as JSON.',++ user_prompt: 'Meme format: {{format}}\nTopic or situation: {{topic}}\n\nWrite three to five original caption ideas. Each idea may use up to four short lines separated by a newline. Return JSON with an ideas array only.',++ default_route: 'meme.caption', metadata: { json_name: 'ideas' },++};++const workflow = {++ key: 'meme.caption', name: 'Original meme caption ideas', namespace: 'meme', owner: 'meme',++ description: 'Generate a handful of original, short caption ideas for a selected meme template.',++ input_schema: template.input_schema, output_schema: template.output_schema, default_route: 'meme.caption',++ steps: [{ kind: 'llm', template: 'meme.caption', operation: 'generate', output: 'json', params: { max_tokens: 700, temperature: 0.85 } }],++};++++module.exports = { templates: [template], workflows: [workflow] };@@@ -21,6 +21,7 @@ const core = require('./core');const products = require('./products');const chat = require('./chat');const research = require('./research');++const meme = require('./meme');const poolSeed = require('../providers/pool-seed');const { resolveSecret } = require('../util');@@@ -203,6 +204,8 @@ async function seed({ registry, quotas, config, env = process.env, db }) {// admin adds as kind anthropic. The others are skipped as unsupported, never converted.await registry.seedVersioned('route', 'default.messages', pool(120000, { constraints: { objective: 'correctness', latency_class: 'interactive' }, max_output_tokens: 8192 }));await registry.seedVersioned('route', 'default.json', pool(60000, { options: { temperature: 0.3 }, max_output_tokens: 2400, response_format: 'json' }));++ // Meme captions need fluency, not factual precision; choose the cheapest eligible JSON route by default.++ await registry.seedVersioned('route', 'meme.caption', pool(30000, { constraints: { objective: 'cheapest', latency_class: 'interactive' }, options: { temperature: 0.85 }, max_output_tokens: 700, response_format: 'json' }));await registry.seedVersioned('route', 'default.embedding', pool(30000, { 'capability': 'embed', constraints: { objective: 'balanced' } }));// media.analyze (WS-O task 5): the local model when there is one; the paid route is used only under a media.paid budget.// Both are background work (BA1): nobody watches a VOD's overview arrive, so the route says it may wait.@@@ -255,11 +258,11 @@ async function seed({ registry, quotas, config, env = process.env, db }) {await registry.seedVersioned('route', 'chat.member', { primary: { provider: 'shared', model: null }, fallbacks: [], options: {}, max_output_tokens: 600, response_format: 'text', timeout_ms: null, alias_of: 'default.chat' });// Templates, then workflows (workflows reference templates)-- for (const t of [...live.templates, ...core.templates, ...products.templates, ...media.templates, ...chat.templates, ...space.templates, ...research.templates]) {++ for (const t of [...live.templates, ...core.templates, ...products.templates, ...media.templates, ...chat.templates, ...space.templates, ...research.templates, ...meme.templates]) {const { key, ...def } = t;await registry.seedVersioned('template', key, { description: null, default_route: null, owner: 'ai', visibility: 'internal', metadata: {}, ...def });}-- for (const w of [...live.workflows, ...core.workflows, ...products.workflows, ...media.workflows, ...chat.workflows, ...space.workflows, ...research.workflows]) {++ for (const w of [...live.workflows, ...core.workflows, ...products.workflows, ...media.workflows, ...chat.workflows, ...space.workflows, ...research.workflows, ...meme.workflows]) {const { key, ...def } = w;await registry.seedVersioned('workflow', key, { description: null, default_route: null, cache_ttl_sec: null, metadata: {}, ...def });}@@@ -14,6 +14,7 @@ const SRC = (n) => Array.from({ length: n }, (_, i) => ({ source_type: 'news.artconst PASS = { role: 'chat', kind: 'chat_global', system: [{ text: 'You summarize chat.', cache: true }], user: 'Summarize: hi all', max_tokens: 80 };const EXAMPLES = {++ 'meme.caption': { format: 'Two choices', topic: 'choosing whether to fix the bug before lunch' },'ai.messages': { max_tokens: 64, messages: [{ role: 'user', content: 'Say hello' }] },'live.translate': { text: 'こんにちは、みなさん', from: 'ja', to: 'en', context: 'chat' },'live.paste.describe_image': { title: 'my screenshot', image: { data_url: PNG } },
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+'use strict';
+
+const STR = (n) => ({ type: 'string', maxLength: n });
+const template = {
+ key: 'meme.caption', name: 'Meme: caption ideas', owner: 'meme', visibility: 'internal',
+ description: 'Write a few original, short caption ideas for a known meme format.',
+ input_schema: { type: 'object', additionalProperties: false, required: ['format', 'topic'], properties: { format: STR(120), topic: STR(300) } },
+ output_schema: { type: 'object', additionalProperties: false, required: ['ideas'], properties: { ideas: { type: 'array', minItems: 3, maxItems: 5, items: STR(480) } } },
+ system_prompt: 'You write original meme captions. Never quote a known meme, copy a template caption, use hateful slurs, or invent factual claims. Keep each idea short, varied, and safe for a general audience. Return three to five distinct caption ideas as JSON.',
+ user_prompt: 'Meme format: {{format}}\nTopic or situation: {{topic}}\n\nWrite three to five original caption ideas. Each idea may use up to four short lines separated by a newline. Return JSON with an ideas array only.',
+ default_route: 'meme.caption', metadata: { json_name: 'ideas' },
+};
+const workflow = {
+ key: 'meme.caption', name: 'Original meme caption ideas', namespace: 'meme', owner: 'meme',
+ description: 'Generate a handful of original, short caption ideas for a selected meme template.',
+ input_schema: template.input_schema, output_schema: template.output_schema, default_route: 'meme.caption',
+ steps: [{ kind: 'llm', template: 'meme.caption', operation: 'generate', output: 'json', params: { max_tokens: 700, temperature: 0.85 } }],
+};
+
+module.exports = { templates: [template], workflows: [workflow] };
@@ -21,6 +21,7 @@ const core = require('./core');
@@ -21,6 +21,7 @@ const core = require('./core');
const products = require('./products');
const products = require('./products');
const chat = require('./chat');
const chat = require('./chat');
const research = require('./research');
const research = require('./research');
+const meme = require('./meme');
const poolSeed = require('../providers/pool-seed');
const poolSeed = require('../providers/pool-seed');
const { resolveSecret } = require('../util');
const { resolveSecret } = require('../util');
@@ -203,6 +204,8 @@ async function seed({ registry, quotas, config, env = process.env, db }) {
@@ -203,6 +204,8 @@ async function seed({ registry, quotas, config, env = process.env, db }) {
// admin adds as kind anthropic. The others are skipped as unsupported, never converted.
// admin adds as kind anthropic. The others are skipped as unsupported, never converted.
await registry.seedVersioned('route', 'default.messages', pool(120000, { constraints: { objective: 'correctness', latency_class: 'interactive' }, max_output_tokens: 8192 }));
await registry.seedVersioned('route', 'default.messages', pool(120000, { constraints: { objective: 'correctness', latency_class: 'interactive' }, max_output_tokens: 8192 }));
await registry.seedVersioned('route', 'default.json', pool(60000, { options: { temperature: 0.3 }, max_output_tokens: 2400, response_format: 'json' }));
await registry.seedVersioned('route', 'default.json', pool(60000, { options: { temperature: 0.3 }, max_output_tokens: 2400, response_format: 'json' }));
+ // Meme captions need fluency, not factual precision; choose the cheapest eligible JSON route by default.
+ await registry.seedVersioned('route', 'meme.caption', pool(30000, { constraints: { objective: 'cheapest', latency_class: 'interactive' }, options: { temperature: 0.85 }, max_output_tokens: 700, response_format: 'json' }));
await registry.seedVersioned('route', 'default.embedding', pool(30000, { 'capability': 'embed', constraints: { objective: 'balanced' } }));
await registry.seedVersioned('route', 'default.embedding', pool(30000, { 'capability': 'embed', constraints: { objective: 'balanced' } }));
// media.analyze (WS-O task 5): the local model when there is one; the paid route is used only under a media.paid budget.
// media.analyze (WS-O task 5): the local model when there is one; the paid route is used only under a media.paid budget.
// Both are background work (BA1): nobody watches a VOD's overview arrive, so the route says it may wait.
// Both are background work (BA1): nobody watches a VOD's overview arrive, so the route says it may wait.
@@ -255,11 +258,11 @@ async function seed({ registry, quotas, config, env = process.env, db }) {
@@ -255,11 +258,11 @@ async function seed({ registry, quotas, config, env = process.env, db }) {
await registry.seedVersioned('route', 'chat.member', { primary: { provider: 'shared', model: null }, fallbacks: [], options: {}, max_output_tokens: 600, response_format: 'text', timeout_ms: null, alias_of: 'default.chat' });
await registry.seedVersioned('route', 'chat.member', { primary: { provider: 'shared', model: null }, fallbacks: [], options: {}, max_output_tokens: 600, response_format: 'text', timeout_ms: null, alias_of: 'default.chat' });
// Templates, then workflows (workflows reference templates)
// Templates, then workflows (workflows reference templates)
- for (const t of [...live.templates, ...core.templates, ...products.templates, ...media.templates, ...chat.templates, ...space.templates, ...research.templates]) {
+ for (const t of [...live.templates, ...core.templates, ...products.templates, ...media.templates, ...chat.templates, ...space.templates, ...research.templates, ...meme.templates]) {
const { key, ...def } = t;
const { key, ...def } = t;
await registry.seedVersioned('template', key, { description: null, default_route: null, owner: 'ai', visibility: 'internal', metadata: {}, ...def });
await registry.seedVersioned('template', key, { description: null, default_route: null, owner: 'ai', visibility: 'internal', metadata: {}, ...def });
}
}
- for (const w of [...live.workflows, ...core.workflows, ...products.workflows, ...media.workflows, ...chat.workflows, ...space.workflows, ...research.workflows]) {
+ for (const w of [...live.workflows, ...core.workflows, ...products.workflows, ...media.workflows, ...chat.workflows, ...space.workflows, ...research.workflows, ...meme.workflows]) {
const { key, ...def } = w;
const { key, ...def } = w;
await registry.seedVersioned('workflow', key, { description: null, default_route: null, cache_ttl_sec: null, metadata: {}, ...def });
await registry.seedVersioned('workflow', key, { description: null, default_route: null, cache_ttl_sec: null, metadata: {}, ...def });
}
}
@@ -14,6 +14,7 @@ const SRC = (n) => Array.from({ length: n }, (_, i) => ({ source_type: 'news.art
@@ -14,6 +14,7 @@ const SRC = (n) => Array.from({ length: n }, (_, i) => ({ source_type: 'news.art
const PASS = { role: 'chat', kind: 'chat_global', system: [{ text: 'You summarize chat.', cache: true }], user: 'Summarize: hi all', max_tokens: 80 };
const PASS = { role: 'chat', kind: 'chat_global', system: [{ text: 'You summarize chat.', cache: true }], user: 'Summarize: hi all', max_tokens: 80 };
const EXAMPLES = {
const EXAMPLES = {
+ 'meme.caption': { format: 'Two choices', topic: 'choosing whether to fix the bug before lunch' },
'ai.messages': { max_tokens: 64, messages: [{ role: 'user', content: 'Say hello' }] },
'ai.messages': { max_tokens: 64, messages: [{ role: 'user', content: 'Say hello' }] },
'live.translate': { text: 'こんにちは、みなさん', from: 'ja', to: 'en', context: 'chat' },
'live.translate': { text: 'こんにちは、みなさん', from: 'ja', to: 'en', context: 'chat' },
'live.paste.describe_image': { title: 'my screenshot', image: { data_url: PNG } },
'live.paste.describe_image': { title: 'my screenshot', image: { data_url: PNG } },