openvibers/openvibe.ai Commits
Seed workflow tools.describe (roadmap WS-O task 11): a draft description of one tool from its descriptor (summary, how-to, inputs and outputs, limits, FAQ), every statement cited to the descriptor and unknowns in gaps, never auto-published; route alias tools.describe. The plan's other seeds exist (wiki.generate_page, news.summarize_story, reviews.summarize_entity, deals.enrich_deal, trade.summarize_market_context, and coupons.extract_coupon and codes.generate_docs under those names)
Changes
@@@ -3,7 +3,7 @@* Seed workflows the publication products consume (plan §15.13). Every one returns a DRAFT /* EVIDENCE package — never publication truth — with citations that index into the numbered* input.sources, plus `gaps` for what the sources do not support. The owning product (Wiki,-- * Blog, News, Reviews, Deals, Coupons, Trade, Codes, Games) decides what to publish.++ * Blog, News, Reviews, Deals, Coupons, Trade, Codes, Games, Tools) decides what to publish.** Hard rules carried into the prompts and the schemas:* - no invented facts, prices, ratings, dates, quotes (Reviews has no rating field at all;@@@ -53,6 +53,8 @@ const templates = ['Project: {{project}}{{#audience}}\nAudience: {{audience}}{{/audience}}\n\n'),productTemplate('games.generate_lore', 'Games: generate lore', 'Write lore for the world that stays consistent with the canon sources (cite them where you rely on them), and list any contradiction with canon you had to resolve. Invented fiction is expected here; invented canon citations are not.', 'games.generate_lore','World: {{world}}\nRequest: {{prompt}}\n\n'),++ productTemplate('tools.describe', 'Tools: describe a tool', 'Describe one OpenVibe tool for the people who use it, from its descriptor (the numbered sources: its registry entry, inputs, outputs and limits): a one-line summary, what it does, how to use it step by step, what each input and output means, its limits, and a short FAQ. Cite the descriptor fields each statement rests on. Do not claim formats, sizes or features the descriptor does not list; say what is unknown in gaps.', 'tools.describe',++ 'Tool: {{tool}}{{#audience}}\nAudience: {{audience}}{{/audience}}\n\n'),productTemplate('moderation.classify', 'Moderation: classify content', 'Classify the content under OpenVibe policy. Profanity, insults, crude jokes and trash talk are ALLOWED and are not violations. Flag only: credible threats of violence, any sexual content involving minors, doxxing (sharing private personal information), targeted harassment campaigns, spam/scams, sexual content where not allowed, and self-harm encouragement. Quote the exact spans you flag. When unsure, choose review, not block.', 'moderation.classify','{{#context}}Context: {{context}}\n\n{{/context}}Content to classify:\n"""\n{{text}}\n"""\n\n'),];@@@ -142,6 +144,18 @@ const workflows = [output_schema: OBJ({ title: STR(200), lore_markdown: STR(20000), entities: ARR(OBJ({ name: STR(120), kind: STR(60), description: STR(1000) }), 30), contradictions: ARR(STR(500), 10), citations: CITES, gaps: GAPS }),default_route: 'games.generate_lore',},++ {++ key: 'tools.describe', name: 'Describe a tool', namespace: 'tools',++ input_schema: OBJ({ tool: STR(120), audience: STR(200), sources: SOURCES(1) }, ['tool', 'sources']),++ output_schema: OBJ({++ summary: STR(300), description_markdown: STR(6000),++ how_to: ARR(OBJ({ step: STR(600), citations: CITES }), 15),++ fields: ARR(OBJ({ name: STR(120), direction: { enum: ['input', 'output'] }, meaning: STR(600), citations: CITES }), 40),++ limits: ARR(CLAIM, 15), faq: ARR(OBJ({ question: STR(300), answer: STR(1200), citations: CITES }), 12),++ citations: CITES, gaps: GAPS,++ }),++ default_route: 'tools.describe',++ },{key: 'moderation.classify', name: 'Classify content for moderation', namespace: 'moderation',input_schema: OBJ({ text: STR(8000), context: STR(1000), sources: SOURCES(0) }, ['text']),@@@ -19,7 +19,7 @@ const products = require('./products');const { resolveSecret } = require('../util');const ROLE_TIMEOUT = { chat: 20000, vision: 30000, director: 25000, summary: 30000, legacy: 30000 };--const HISTORICAL_ROUTES = ['wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify'];++const HISTORICAL_ROUTES = ['wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify', 'tools.describe'];function sharedProviderRecord(config, env) {const s = config.shared;@@@ -43,6 +43,7 @@ const EXAMPLES = {'codes.generate_docs': { project: 'demo', sources: [{ source_type: 'code.file', title: 'index.js', content: 'module.exports = 1;' }] },'games.generate_lore': { world: 'Hobo Quest', prompt: 'The origin of the tin-can knights', sources: SRC(1) },'moderation.classify': { text: 'you absolute potato, gg', context: 'arena chat' },++ 'tools.describe': { tool: 'img.convert', sources: [{ source_type: 'tools.tool', title: 'img.convert descriptor', content: '{"id":"img.convert","inputs":["file","format"]}' }] },};for (const k of new Set(Object.values(KIND_TO_WORKFLOW).concat('live.complete'))) EXAMPLES[k] = PASS;@@@ -59,11 +60,11 @@ t.test('boot seeds every required workflow key', async () => {h = await boot({ env: { OV_MEDIA_INTERNAL_URL: mediaBase } });const keys = h.registry.listWorkflows().map(w => w.key);for (const k of ['wiki.generate_space', 'wiki.generate_page', 'blog.draft_post', 'news.summarize_story', 'news.compare_perspectives', 'reviews.summarize_entity', 'deals.enrich_deal',-- 'coupons.extract_coupon', 'trade.summarize_market_context', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify',++ 'coupons.extract_coupon', 'trade.summarize_market_context', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify', 'tools.describe','live.translate', 'live.stream.describe_frame', 'live.stream.summarize', 'live.viewers.reply', 'live.chat.insight', 'live.hero.slogans', 'live.stream.recap', 'network.site_copy']) {assert.ok(keys.includes(k), `missing ${k}`);}-- for (const k of ['default.chat', 'default.json', 'default.embedding', 'wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify']) {++ for (const k of ['default.chat', 'default.json', 'default.embedding', 'wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'tools.describe', 'moderation.classify']) {assert.ok(h.registry.getRoute(k), `missing historical route ${k}`);}});
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
* Seed workflows the publication products consume (plan §15.13). Every one returns a DRAFT /
* Seed workflows the publication products consume (plan §15.13). Every one returns a DRAFT /
* EVIDENCE package — never publication truth — with citations that index into the numbered
* EVIDENCE package — never publication truth — with citations that index into the numbered
* input.sources, plus `gaps` for what the sources do not support. The owning product (Wiki,
* input.sources, plus `gaps` for what the sources do not support. The owning product (Wiki,
- * Blog, News, Reviews, Deals, Coupons, Trade, Codes, Games) decides what to publish.
+ * Blog, News, Reviews, Deals, Coupons, Trade, Codes, Games, Tools) decides what to publish.
*
*
* Hard rules carried into the prompts and the schemas:
* Hard rules carried into the prompts and the schemas:
* - no invented facts, prices, ratings, dates, quotes (Reviews has no rating field at all;
* - no invented facts, prices, ratings, dates, quotes (Reviews has no rating field at all;
@@ -53,6 +53,8 @@ const templates = [
@@ -53,6 +53,8 @@ const templates = [
'Project: {{project}}{{#audience}}\nAudience: {{audience}}{{/audience}}\n\n'),
'Project: {{project}}{{#audience}}\nAudience: {{audience}}{{/audience}}\n\n'),
productTemplate('games.generate_lore', 'Games: generate lore', 'Write lore for the world that stays consistent with the canon sources (cite them where you rely on them), and list any contradiction with canon you had to resolve. Invented fiction is expected here; invented canon citations are not.', 'games.generate_lore',
productTemplate('games.generate_lore', 'Games: generate lore', 'Write lore for the world that stays consistent with the canon sources (cite them where you rely on them), and list any contradiction with canon you had to resolve. Invented fiction is expected here; invented canon citations are not.', 'games.generate_lore',
'World: {{world}}\nRequest: {{prompt}}\n\n'),
'World: {{world}}\nRequest: {{prompt}}\n\n'),
+ productTemplate('tools.describe', 'Tools: describe a tool', 'Describe one OpenVibe tool for the people who use it, from its descriptor (the numbered sources: its registry entry, inputs, outputs and limits): a one-line summary, what it does, how to use it step by step, what each input and output means, its limits, and a short FAQ. Cite the descriptor fields each statement rests on. Do not claim formats, sizes or features the descriptor does not list; say what is unknown in gaps.', 'tools.describe',
+ 'Tool: {{tool}}{{#audience}}\nAudience: {{audience}}{{/audience}}\n\n'),
productTemplate('moderation.classify', 'Moderation: classify content', 'Classify the content under OpenVibe policy. Profanity, insults, crude jokes and trash talk are ALLOWED and are not violations. Flag only: credible threats of violence, any sexual content involving minors, doxxing (sharing private personal information), targeted harassment campaigns, spam/scams, sexual content where not allowed, and self-harm encouragement. Quote the exact spans you flag. When unsure, choose review, not block.', 'moderation.classify',
productTemplate('moderation.classify', 'Moderation: classify content', 'Classify the content under OpenVibe policy. Profanity, insults, crude jokes and trash talk are ALLOWED and are not violations. Flag only: credible threats of violence, any sexual content involving minors, doxxing (sharing private personal information), targeted harassment campaigns, spam/scams, sexual content where not allowed, and self-harm encouragement. Quote the exact spans you flag. When unsure, choose review, not block.', 'moderation.classify',
'{{#context}}Context: {{context}}\n\n{{/context}}Content to classify:\n"""\n{{text}}\n"""\n\n'),
'{{#context}}Context: {{context}}\n\n{{/context}}Content to classify:\n"""\n{{text}}\n"""\n\n'),
];
];
@@ -142,6 +144,18 @@ const workflows = [
@@ -142,6 +144,18 @@ const workflows = [
output_schema: OBJ({ title: STR(200), lore_markdown: STR(20000), entities: ARR(OBJ({ name: STR(120), kind: STR(60), description: STR(1000) }), 30), contradictions: ARR(STR(500), 10), citations: CITES, gaps: GAPS }),
output_schema: OBJ({ title: STR(200), lore_markdown: STR(20000), entities: ARR(OBJ({ name: STR(120), kind: STR(60), description: STR(1000) }), 30), contradictions: ARR(STR(500), 10), citations: CITES, gaps: GAPS }),
default_route: 'games.generate_lore',
default_route: 'games.generate_lore',
},
},
+ {
+ key: 'tools.describe', name: 'Describe a tool', namespace: 'tools',
+ input_schema: OBJ({ tool: STR(120), audience: STR(200), sources: SOURCES(1) }, ['tool', 'sources']),
+ output_schema: OBJ({
+ summary: STR(300), description_markdown: STR(6000),
+ how_to: ARR(OBJ({ step: STR(600), citations: CITES }), 15),
+ fields: ARR(OBJ({ name: STR(120), direction: { enum: ['input', 'output'] }, meaning: STR(600), citations: CITES }), 40),
+ limits: ARR(CLAIM, 15), faq: ARR(OBJ({ question: STR(300), answer: STR(1200), citations: CITES }), 12),
+ citations: CITES, gaps: GAPS,
+ }),
+ default_route: 'tools.describe',
+ },
{
{
key: 'moderation.classify', name: 'Classify content for moderation', namespace: 'moderation',
key: 'moderation.classify', name: 'Classify content for moderation', namespace: 'moderation',
input_schema: OBJ({ text: STR(8000), context: STR(1000), sources: SOURCES(0) }, ['text']),
input_schema: OBJ({ text: STR(8000), context: STR(1000), sources: SOURCES(0) }, ['text']),
@@ -19,7 +19,7 @@ const products = require('./products');
@@ -19,7 +19,7 @@ const products = require('./products');
const { resolveSecret } = require('../util');
const { resolveSecret } = require('../util');
const ROLE_TIMEOUT = { chat: 20000, vision: 30000, director: 25000, summary: 30000, legacy: 30000 };
const ROLE_TIMEOUT = { chat: 20000, vision: 30000, director: 25000, summary: 30000, legacy: 30000 };
-const HISTORICAL_ROUTES = ['wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify'];
+const HISTORICAL_ROUTES = ['wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify', 'tools.describe'];
function sharedProviderRecord(config, env) {
function sharedProviderRecord(config, env) {
const s = config.shared;
const s = config.shared;
@@ -43,6 +43,7 @@ const EXAMPLES = {
@@ -43,6 +43,7 @@ const EXAMPLES = {
'codes.generate_docs': { project: 'demo', sources: [{ source_type: 'code.file', title: 'index.js', content: 'module.exports = 1;' }] },
'codes.generate_docs': { project: 'demo', sources: [{ source_type: 'code.file', title: 'index.js', content: 'module.exports = 1;' }] },
'games.generate_lore': { world: 'Hobo Quest', prompt: 'The origin of the tin-can knights', sources: SRC(1) },
'games.generate_lore': { world: 'Hobo Quest', prompt: 'The origin of the tin-can knights', sources: SRC(1) },
'moderation.classify': { text: 'you absolute potato, gg', context: 'arena chat' },
'moderation.classify': { text: 'you absolute potato, gg', context: 'arena chat' },
+ 'tools.describe': { tool: 'img.convert', sources: [{ source_type: 'tools.tool', title: 'img.convert descriptor', content: '{"id":"img.convert","inputs":["file","format"]}' }] },
};
};
for (const k of new Set(Object.values(KIND_TO_WORKFLOW).concat('live.complete'))) EXAMPLES[k] = PASS;
for (const k of new Set(Object.values(KIND_TO_WORKFLOW).concat('live.complete'))) EXAMPLES[k] = PASS;
@@ -59,11 +60,11 @@ t.test('boot seeds every required workflow key', async () => {
@@ -59,11 +60,11 @@ t.test('boot seeds every required workflow key', async () => {
h = await boot({ env: { OV_MEDIA_INTERNAL_URL: mediaBase } });
h = await boot({ env: { OV_MEDIA_INTERNAL_URL: mediaBase } });
const keys = h.registry.listWorkflows().map(w => w.key);
const keys = h.registry.listWorkflows().map(w => w.key);
for (const k of ['wiki.generate_space', 'wiki.generate_page', 'blog.draft_post', 'news.summarize_story', 'news.compare_perspectives', 'reviews.summarize_entity', 'deals.enrich_deal',
for (const k of ['wiki.generate_space', 'wiki.generate_page', 'blog.draft_post', 'news.summarize_story', 'news.compare_perspectives', 'reviews.summarize_entity', 'deals.enrich_deal',
- 'coupons.extract_coupon', 'trade.summarize_market_context', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify',
+ 'coupons.extract_coupon', 'trade.summarize_market_context', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify', 'tools.describe',
'live.translate', 'live.stream.describe_frame', 'live.stream.summarize', 'live.viewers.reply', 'live.chat.insight', 'live.hero.slogans', 'live.stream.recap', 'network.site_copy']) {
'live.translate', 'live.stream.describe_frame', 'live.stream.summarize', 'live.viewers.reply', 'live.chat.insight', 'live.hero.slogans', 'live.stream.recap', 'network.site_copy']) {
assert.ok(keys.includes(k), `missing ${k}`);
assert.ok(keys.includes(k), `missing ${k}`);
}
}
- for (const k of ['default.chat', 'default.json', 'default.embedding', 'wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'moderation.classify']) {
+ for (const k of ['default.chat', 'default.json', 'default.embedding', 'wiki.generate', 'blog.draft', 'news.summarize', 'reviews.summarize', 'deals.enrich', 'coupons.extract', 'trade.summarize', 'codes.generate_docs', 'games.generate_lore', 'tools.describe', 'moderation.classify']) {
assert.ok(h.registry.getRoute(k), `missing historical route ${k}`);
assert.ok(h.registry.getRoute(k), `missing historical route ${k}`);
}
}
});
});